oc_orchestrator
Loading...
Searching...
No Matches
wordgen.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_orchestrator/wordgen.py
4
5"""
6Random word generator for session and plan names.
7
8Purpose
9-------
10Produces readable random-word identifiers (e.g., ``sunny-weaving-honey``)
11to replace timestamp-based defaults for sessions and plans.
12
13Responsibilities
14----------------
15- Maintain three curated word lists (adjectives, gerunds, nouns)
16- Generate kebab-case n-word identifiers via :func:`random_words`
17- Produce unique names with collision avoidance via :func:`random_name`
18- Generate numbered plan filenames via :func:`random_plan_filename`
19
20Diagnostics
21-----------
22Domain: OC_CHAT.MAIN
23Levels:
24 L2 — module load lifecycle
25 L3 — generation events (name produced, collisions retried)
26 L4 — per-call word selection details
27
28Contracts
29---------
30- :func:`random_words` returns ``list[str]`` of lowercase words
31- :func:`random_name` returns a unique kebab-case string (no underscores)
32- :func:`random_plan_filename` returns ``NNN-word-word-word.md``
33- Word lists are module-level tuples; immutable after import
34- No external dependencies required
35
36Dependencies
37------------
38- oc_diagnostics (structured debug logging via _dbg)
39"""
40from __future__ import annotations
41
42import random
43from typing import Callable
44
45from oc_diagnostics import _dbg
46
47_ADJECTIVES: tuple[str, ...] = (
48 "amber", "ancient", "azure", "bold", "bright", "broad", "calm", "clear",
49 "cold", "cool", "crisp", "crystal", "curious", "dark", "deep", "distant",
50 "dreamy", "drifting", "dusky", "electric", "emerald", "eternal", "fading",
51 "fierce", "floating", "fluid", "fragrant", "free", "frosted", "frozen",
52 "gentle", "gilded", "gleaming", "glowing", "golden", "graceful", "grand",
53 "hidden", "hollow", "humble", "hushed", "icy", "idle", "indigo", "inner",
54 "jade", "jovial", "keen", "kind", "light", "lonely", "lucid", "luminous",
55 "mellow", "misty", "moonlit", "mossy", "muted", "mystic", "narrow",
56 "nimble", "noble", "northern", "ocean", "open", "pale", "patient",
57 "peaceful", "phantom", "quiet", "radiant", "rapid", "rare", "raw",
58 "restless", "rising", "rosy", "rustic", "sacred", "scattered", "serene",
59 "shimmering", "silent", "silver", "simple", "sleek", "slender", "slow",
60 "smooth", "soft", "solar", "solemn", "solitary", "somber", "sparkling",
61 "still", "stoic", "sturdy", "subtle", "summer", "sunlit", "sunny",
62 "supple", "swift", "tender", "timeless", "twilight", "vast", "velvet",
63 "verdant", "vibrant", "vivid", "wandering", "warm", "weathered", "wild",
64 "windy", "wintry", "wise", "wooden", "woven", "young", "zephyr",
65 "acorn", "breezy", "burnished", "canopy", "cedar", "cobalt", "coral",
66 "crimson", "dawning", "dense", "dappled", "earthy", "enchanted",
67 "fern", "floral", "foggy", "granite", "grassy", "harvest", "heather",
68 "highland", "hazy", "ivory", "jasper", "lavender", "leafy", "linen",
69 "lofty", "lunar", "maple", "marble", "meadow", "midnight", "mineral",
70 "morning", "mosaic", "murky", "native", "nebula", "onyx", "opaque",
71 "opaline", "orchid", "overcast", "pearlescent", "pebbled", "petal",
72 "pine", "plains", "polished", "porous", "primal", "pristine", "quartz",
73 "reedy", "regal", "remote", "rippling", "roaming", "rocky", "rugged",
74 "sagebrush", "sandy", "sapphire", "saturated", "shadowed", "shallow",
75 "shaded", "shifting", "shoreline", "silken", "skeletal", "sleepy",
76 "sloped", "smoky", "snowy", "speckled", "spiral", "starlit", "steady",
77 "steep", "stony", "stormy", "stranded", "stripped", "sunken", "sunward",
78 "tapered", "temperate", "textured", "tidal", "timbered", "tinted",
79 "towering", "tranquil", "tropical", "tundra", "turquoise", "twisted",
80 "undulant", "uneven", "untamed", "upland", "veiled", "volcanic",
81 "watery", "winding", "woolly", "worn", "wrapped", "amber", "ashen",
82 "barren", "billowing", "bleached", "brackish", "braided", "breathless",
83 "brine", "briny", "broken", "cascading", "ceaseless", "charcoal",
84 "chill", "chiseled", "churning", "circling", "coppery", "crested",
85 "crossing", "crunchy", "crystalline", "damp", "debris", "decayed",
86 "dewed", "dim", "dormant", "dried", "dusty", "fading", "fallen",
87 "fallow", "fertile", "filmy", "flinty", "flowing", "foamy", "forested",
88 "fractured", "fragile", "glistening", "glittering", "gravel", "gray",
89 "green", "grotto", "groundless", "gushing", "halcyon", "hammered",
90 "highland", "hollow", "horizoned", "humid", "incandescent", "inky",
91 "island", "knotted", "lakeward", "lapping", "latticed", "leaning",
92 "limestone", "loamy", "lonesome", "loosened", "lowland", "luminant",
93)
94
95_GERUNDS: tuple[str, ...] = (
96 "blooming", "blossoming", "bouncing", "breathing", "brewing", "bridging",
97 "burning", "carving", "cascading", "casting", "chasing", "circling",
98 "climbing", "coiling", "cooling", "crossing", "dancing", "drifting",
99 "echoing", "falling", "fading", "floating", "flowing", "flying",
100 "folding", "forging", "gathering", "gliding", "gleaming", "glowing",
101 "growing", "guiding", "healing", "humming", "hunting", "journeying",
102 "leaping", "learning", "lifting", "listening", "looping", "mapping",
103 "melting", "mending", "merging", "mirroring", "moving", "murmuring",
104 "opening", "pacing", "passing", "pausing", "peeling", "reaching",
105 "reading", "resting", "returning", "rising", "roaming", "rolling",
106 "rooting", "running", "sailing", "searching", "seeking", "shifting",
107 "shining", "singing", "sinking", "sleeping", "sliding", "soaring",
108 "sorting", "sparkling", "spinning", "spreading", "sprouting", "staying",
109 "streaming", "stretching", "sweeping", "swelling", "swirling",
110 "threading", "tumbling", "turning", "unfolding", "walking", "wandering",
111 "watching", "weaving", "weaving", "whistling", "winding", "wondering",
112 "working", "wrapping", "yielding", "arching", "awakening", "bending",
113 "binding", "braiding", "branching", "breaking", "calming", "changing",
114 "charting", "clearing", "collecting", "combining", "condensing",
115 "connecting", "containing", "converging", "cooling", "covering",
116 "cresting", "curling", "curving", "cutting", "deepening", "descending",
117 "digging", "dimming", "dissolving", "diverging", "diving", "drawing",
118 "edging", "emerging", "encircling", "ending", "eroding", "escaping",
119 "etching", "expanding", "extending", "filtering", "finding", "finishing",
120 "flashing", "flattening", "fleeing", "flickering", "flipping", "flooding",
121 "flushing", "focusing", "following", "forming", "fracturing", "gathering",
122 "grounding", "hanging", "hiding", "holding", "hovering", "illuminating",
123 "intersecting", "intertwining", "isolating", "joining", "keeping",
124 "kindling", "knotting", "landing", "launching", "layering", "leaving",
125 "lining", "linking", "loosening", "lowering", "making", "marking",
126 "meeting", "misting", "narrowing", "navigating", "nearing", "nesting",
127 "noting", "nudging", "outlining", "overflowing", "overlapping",
128 "parting", "peaking", "pooling", "pouring", "pressing", "pulling",
129 "pushing", "radiating", "raising", "raking", "redirecting", "reflecting",
130 "releasing", "remaining", "renewing", "repeating", "resolving",
131 "retreating", "revealing", "rewinding", "rippling", "routing",
132 "scattering", "sealing", "sending", "separating", "setting", "shaping",
133 "sharing", "sheltering", "showing", "signaling", "skimming", "skipping",
134 "smoothing", "spiraling", "stabilizing", "stacking", "starting",
135 "steadying", "stepping", "stitching", "stopping", "submerging",
136 "surfacing", "surrounding", "swaying", "tapping", "teaching", "testing",
137 "tightening", "tilting", "tracing", "tracking", "trading", "trailing",
138 "transforming", "traveling", "traversing", "trimming", "twisting",
139 "uncovering", "unfolding", "unwinding", "upwelling", "vanishing",
140 "varying", "venturing", "viewing", "waking", "warming", "weathering",
141)
142
143_NOUNS: tuple[str, ...] = (
144 "amber", "anchor", "arc", "ash", "basin", "bay", "beacon", "bloom",
145 "blossom", "bone", "branch", "bridge", "brook", "candle", "canyon",
146 "cedar", "channel", "clay", "clearing", "cliff", "cloud", "coal",
147 "coast", "comet", "compass", "coral", "crest", "current", "dawn",
148 "delta", "den", "desert", "dew", "drift", "dune", "dust", "echo",
149 "ember", "estuary", "fawn", "feather", "fern", "field", "fjord",
150 "flame", "flint", "fog", "forest", "frost", "gale", "garden", "gate",
151 "glade", "glacier", "gorge", "grain", "granite", "grove", "harbor",
152 "haven", "hawk", "heath", "hill", "hollow", "honey", "horizon",
153 "hue", "inlet", "island", "jade", "kelp", "lagoon", "lake", "lantern",
154 "lark", "laurel", "leaf", "ledge", "lighthouse", "loch", "log",
155 "marsh", "meadow", "mesa", "mist", "moon", "moonbeam", "moss",
156 "mountain", "mud", "nest", "night", "oak", "ocean", "orbit", "otter",
157 "passage", "path", "peat", "pebble", "peak", "pine", "plain", "pool",
158 "prairie", "quartz", "rain", "rapids", "ravine", "reef", "ridge",
159 "rift", "ripple", "river", "rock", "root", "rose", "ruin", "salt",
160 "sand", "sandbar", "sea", "sedge", "sediment", "shadow", "shale",
161 "shelf", "shore", "shrub", "sierra", "signal", "sky", "slate",
162 "snow", "soil", "spring", "star", "starlight", "steppe", "stone",
163 "storm", "strand", "stream", "summit", "swamp", "swift", "thorn",
164 "tide", "timber", "tundra", "vale", "valley", "vapor", "vine",
165 "volcano", "wave", "wetland", "willow", "wind", "woodland", "wraith",
166 "acorn", "algae", "alder", "alluvium", "anemone", "antler", "aster",
167 "aurora", "bark", "bedrock", "birch", "blight", "bluff", "bog",
168 "boulder", "bract", "brine", "burrow", "butte", "cairn", "canopy",
169 "cape", "carve", "cascade", "cave", "chamomile", "chasm", "cirque",
170 "cobble", "copse", "cove", "crater", "crag", "creek", "crevice",
171 "crust", "culm", "cyme", "dale", "datum", "debris", "declivity",
172 "depths", "dewdrop", "divide", "downdraft", "downpour", "dragonfly",
173 "eddy", "elevation", "elm", "erosion", "escarpment", "esteem",
174 "expanse", "faultline", "fen", "fernbed", "fissure", "flare",
175 "floodplain", "flume", "foliage", "foothill", "ford", "geyser",
176 "gust", "habitat", "halo", "hardwood", "headland", "heartwood",
177 "helix", "heron", "hillside", "hoarfrost", "hotspring", "humus",
178 "hurricane", "hydra", "icefall", "icefield", "ignite", "impasse",
179 "inlet", "iris", "ironwood", "isthmus", "jetty", "juniper", "karst",
180 "kettle", "kindling", "knoll", "lichen", "lignite", "limb", "linden",
181 "lithosphere", "loam", "loess", "lumen", "lupine", "mantle",
182 "manzanita", "marl", "massif", "millstone", "moraine", "morass",
183 "mudflat", "mulch", "mussel", "narrows", "nocturne", "nodule",
184 "nutrient", "obsidian", "olivine", "onyx", "opal", "outcrop",
185 "overhang", "oxbow", "oyster", "parcel", "pasture", "penumbra",
186 "permafrost", "pillar", "pinnacle", "plateau", "plover", "plume",
187 "polarity", "pollen", "pond", "poplar", "pumice", "reedbed",
188 "remnant", "resin", "rill", "rivulet", "rockfall", "runoff",
189 "rushes", "sandstone", "sapwood", "savanna", "scarp", "schist",
190 "scree", "seabed", "seam", "seawall", "sedge", "seep", "shingle",
191 "silhouette", "silt", "sinew", "skyline", "slough", "snowdrift",
192 "snowfield", "softwood", "solstice", "spillway", "spruce", "spur",
193 "starling", "strata", "subsoil", "sundew", "tableland", "talus",
194 "tamarack", "tangent", "tendril", "terrace", "thicket", "thornbush",
195 "thunderhead", "topsoil", "torrent", "treeline", "tributary",
196 "turf", "twilight", "understory", "upland", "uplift", "vestige",
197 "vista", "vortex", "watershed", "weathervane", "wetgrass",
198 "windbreak", "windfall", "windrow", "winterkill", "wren", "yew",
199)
200
201
202def random_words(n: int = 3) -> list[str]:
203 """Return n random words from the internal word lists.
204
205 For n=3 draws one adjective, one gerund, one noun (in order).
206 For other n, samples from the combined pool without replacement.
207 """
208 func = "random_words"
209 if n == 3:
210 words = [
211 random.choice(_ADJECTIVES),
212 random.choice(_GERUNDS),
213 random.choice(_NOUNS),
214 ]
215 else:
216 pool = list(_ADJECTIVES + _GERUNDS + _NOUNS)
217 words = random.sample(pool, min(n, len(pool)))
218 _dbg("OC_CHAT.MAIN", func, f"selected {words}", 4)
219 return words
220
221
223 exists_fn: Callable[[str], bool] | None = None,
224 n: int = 3,
225 max_attempts: int = 5,
226) -> str:
227 """Generate a unique kebab-case random name.
228
229 Retries on collision (via exists_fn) up to max_attempts times.
230 Falls back to appending a 4-digit suffix if all attempts collide.
231
232 Args:
233 exists_fn: Optional callable; returns True if name already taken.
234 n: Number of words in the name.
235 max_attempts: Maximum retries before falling back to suffix.
236
237 Returns:
238 Kebab-case string such as ``sunny-weaving-honey``.
239 """
240 func = "random_name"
241 for attempt in range(max_attempts):
242 name = "-".join(random_words(n))
243 if exists_fn is None or not exists_fn(name):
244 _dbg("OC_CHAT.MAIN", func, f"generated {name!r} (attempt {attempt + 1})", 3)
245 return name
246 _dbg("OC_CHAT.MAIN", func, f"collision on {name!r}, retrying", 3)
247
248 # All attempts collided — append a 4-digit suffix
249 name = f"{'-'.join(random_words(n))}-{random.randint(1000, 9999)}"
250 _dbg("OC_CHAT.MAIN", func, f"fallback to suffixed {name!r}", 3)
251 return name
252
253
254def random_plan_filename(prefix_number: int, n: int = 3) -> str:
255 """Generate a numbered plan filename with random-word tail.
256
257 Format: ``NNN-word-word-word.md`` (zero-padded 3-digit prefix).
258 Example: ``007-sunny-weaving-honey.md``
259
260 Args:
261 prefix_number: Numeric prefix (0–999).
262 n: Number of random words in the tail.
263
264 Returns:
265 Filename string including the ``.md`` extension.
266 """
267 func = "random_plan_filename"
268 tail = "-".join(random_words(n))
269 filename = f"{prefix_number:03d}-{tail}.md"
270 _dbg("OC_CHAT.MAIN", func, f"generated {filename!r}", 3)
271 return filename
list[str] random_words(int n=3)
Definition wordgen.py:202
str random_name(Callable[[str], bool]|None exists_fn=None, int n=3, int max_attempts=5)
Definition wordgen.py:226
str random_plan_filename(int prefix_number, int n=3)
Definition wordgen.py:254