@iowarp/clio-coder 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +224 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +9 -0
  6. package/README.md +798 -0
  7. package/SECURITY.md +72 -0
  8. package/assets/clio-coder-logo-128.webp +0 -0
  9. package/damage-control-rules.yaml +419 -0
  10. package/dist/acp-UMLFVA3F.js +92 -0
  11. package/dist/agents-Q4MYPMUW.js +91 -0
  12. package/dist/auth-O6HYIJ6J.js +521 -0
  13. package/dist/chunk-262G75JS.js +35 -0
  14. package/dist/chunk-26BZQOAD.js +1281 -0
  15. package/dist/chunk-2J63S4SF.js +508 -0
  16. package/dist/chunk-3DANZDGR.js +717 -0
  17. package/dist/chunk-4UQA7NCT.js +29 -0
  18. package/dist/chunk-527KG6XR.js +497 -0
  19. package/dist/chunk-5LDRNKX2.js +1063 -0
  20. package/dist/chunk-5N2FG33Q.js +25 -0
  21. package/dist/chunk-67MTHP2E.js +135 -0
  22. package/dist/chunk-6CWDTGUC.js +20 -0
  23. package/dist/chunk-7BHLZB3A.js +2115 -0
  24. package/dist/chunk-7RBKDI66.js +348 -0
  25. package/dist/chunk-AMFR5YA3.js +541 -0
  26. package/dist/chunk-BBUH4VAA.js +1224 -0
  27. package/dist/chunk-BYEU76JP.js +899 -0
  28. package/dist/chunk-CLJ5HLUD.js +458 -0
  29. package/dist/chunk-D5YD55AR.js +116 -0
  30. package/dist/chunk-DXQNI4PC.js +61 -0
  31. package/dist/chunk-E3NYWENM.js +1004 -0
  32. package/dist/chunk-GNGDQYDU.js +34688 -0
  33. package/dist/chunk-GOTUR54M.js +9 -0
  34. package/dist/chunk-HBU5MTAM.js +41 -0
  35. package/dist/chunk-HMYNFFY4.js +28 -0
  36. package/dist/chunk-JPOWPFCU.js +1010 -0
  37. package/dist/chunk-JWHCJDCI.js +1215 -0
  38. package/dist/chunk-KBR4MZZR.js +41 -0
  39. package/dist/chunk-KKKPTZLM.js +93 -0
  40. package/dist/chunk-ME6DNWIU.js +66 -0
  41. package/dist/chunk-NI4DEJMC.js +88 -0
  42. package/dist/chunk-O4EJEDHO.js +659 -0
  43. package/dist/chunk-PIDUD6M2.js +31 -0
  44. package/dist/chunk-PS4PFJQP.js +29459 -0
  45. package/dist/chunk-QV47YRF4.js +48 -0
  46. package/dist/chunk-RQDWMVRB.js +279 -0
  47. package/dist/chunk-TFSSEXL6.js +136 -0
  48. package/dist/chunk-TKHQ4DGZ.js +8290 -0
  49. package/dist/chunk-TPOCL34A.js +2876 -0
  50. package/dist/chunk-UGYAX5YI.js +565 -0
  51. package/dist/chunk-UHTSULZS.js +461 -0
  52. package/dist/chunk-UU3R62TT.js +128 -0
  53. package/dist/chunk-UWIJNAOB.js +3906 -0
  54. package/dist/chunk-VOO7NYPP.js +914 -0
  55. package/dist/chunk-VPAWTYLY.js +117 -0
  56. package/dist/chunk-WD6AJM35.js +1216 -0
  57. package/dist/chunk-X3BR7HWV.js +115 -0
  58. package/dist/chunk-X3NE4WVW.js +120 -0
  59. package/dist/chunk-XNISANGE.js +1395 -0
  60. package/dist/chunk-XV4ZJ6ZM.js +3177 -0
  61. package/dist/cli/index.js +236 -0
  62. package/dist/clio-KIQ5SNDS.js +53 -0
  63. package/dist/components-JVHMUBEB.js +653 -0
  64. package/dist/config-ZFCDBMDC.js +372 -0
  65. package/dist/configure-G4E3A2PG.js +27 -0
  66. package/dist/context-CDXTP2MP.js +293 -0
  67. package/dist/context-E3KIFVXI.js +185 -0
  68. package/dist/context-clear-3F4PLXOS.js +102 -0
  69. package/dist/context-index-Q7YSYTR3.js +106 -0
  70. package/dist/docs-YIETIWZI.js +280 -0
  71. package/dist/doctor-M5HJJZOL.js +61 -0
  72. package/dist/domains/agents/builtins/architect.md +33 -0
  73. package/dist/domains/agents/builtins/coder.md +31 -0
  74. package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
  75. package/dist/domains/agents/builtins/debugger.md +30 -0
  76. package/dist/domains/agents/builtins/documenter.md +31 -0
  77. package/dist/domains/agents/builtins/git-master.md +30 -0
  78. package/dist/domains/agents/builtins/provenance.md +30 -0
  79. package/dist/domains/agents/builtins/researcher.md +71 -0
  80. package/dist/domains/agents/builtins/scout.md +42 -0
  81. package/dist/domains/agents/builtins/tester.md +31 -0
  82. package/dist/domains/agents/builtins/verifier.md +30 -0
  83. package/dist/domains/agents/builtins/wiki-writer.md +41 -0
  84. package/dist/eval-B3KZZESM.js +2674 -0
  85. package/dist/evidence-V67CHM35.js +233 -0
  86. package/dist/evolve-YDZSUQYA.js +518 -0
  87. package/dist/extensions-SRG7XCAH.js +207 -0
  88. package/dist/fleet-CA2CRTVG.js +760 -0
  89. package/dist/fleet-preflight-CLIAX7YR.js +21 -0
  90. package/dist/init-2OZDJE2D.js +227 -0
  91. package/dist/memory-3PIQQAKX.js +207 -0
  92. package/dist/models-DY35XI7Y.js +237 -0
  93. package/dist/paths-5OMXW7Z4.js +57 -0
  94. package/dist/preload-KZVHET2B.js +11 -0
  95. package/dist/reset-PIFYNOS3.js +216 -0
  96. package/dist/run-3VSPP24F.js +735 -0
  97. package/dist/share-D36RQCXM.js +241 -0
  98. package/dist/skills-F2MRLELY.js +445 -0
  99. package/dist/skills-eval-E2ZTW4PL.js +932 -0
  100. package/dist/targets-DZMEZAH4.js +977 -0
  101. package/dist/trace-7NYCUI2J.js +250 -0
  102. package/dist/uninstall-AD3JWHBB.js +322 -0
  103. package/dist/upgrade-WYYBKGDY.js +301 -0
  104. package/dist/usage-ULIDAGFF.js +755 -0
  105. package/dist/version-ROZ6CZKH.js +16 -0
  106. package/dist/wiki-generate-PKFIX6OB.js +377 -0
  107. package/dist/worker/entry.js +1739 -0
  108. package/docs/README.md +93 -0
  109. package/docs/acp.md +120 -0
  110. package/docs/alcf-provider.md +72 -0
  111. package/docs/architecture.md +172 -0
  112. package/docs/artifact-versions.md +54 -0
  113. package/docs/built-in-agents.md +265 -0
  114. package/docs/capacity-and-scheduling.md +97 -0
  115. package/docs/commands-and-modes.md +554 -0
  116. package/docs/config-knobs-audit.md +115 -0
  117. package/docs/configuration-and-targets.md +812 -0
  118. package/docs/context-engine.md +236 -0
  119. package/docs/dispatch-architecture-rationale.md +126 -0
  120. package/docs/documentation-coverage.md +46 -0
  121. package/docs/documentation-guide.md +166 -0
  122. package/docs/environment-variables.md +105 -0
  123. package/docs/eval-runner.md +205 -0
  124. package/docs/evals-internal.md +298 -0
  125. package/docs/evidence-and-memory.md +243 -0
  126. package/docs/evolution.md +143 -0
  127. package/docs/exit-codes-and-output.md +74 -0
  128. package/docs/extensions-and-sharing.md +306 -0
  129. package/docs/fleet-demo-runbook.md +179 -0
  130. package/docs/fleet-dispatch.md +591 -0
  131. package/docs/glossary.md +75 -0
  132. package/docs/html/agents_blueprint.html +936 -0
  133. package/docs/html/alcf_blueprint.html +324 -0
  134. package/docs/html/architecture_blueprint.html +850 -0
  135. package/docs/html/commands_blueprint.html +794 -0
  136. package/docs/html/config_knobs_audit_blueprint.html +178 -0
  137. package/docs/html/configuration_blueprint.html +1080 -0
  138. package/docs/html/context_blueprint.html +603 -0
  139. package/docs/html/documentation_blueprint.html +832 -0
  140. package/docs/html/environment_blueprint.html +404 -0
  141. package/docs/html/eval_blueprint.html +743 -0
  142. package/docs/html/evals_internal_blueprint.html +190 -0
  143. package/docs/html/evolution_blueprint.html +674 -0
  144. package/docs/html/extensions_blueprint.html +2065 -0
  145. package/docs/html/fleet_dispatch_blueprint.html +286 -0
  146. package/docs/html/index.html +919 -0
  147. package/docs/html/lifecycle_blueprint.html +723 -0
  148. package/docs/html/memory_blueprint.html +699 -0
  149. package/docs/html/middleware_blueprint.html +664 -0
  150. package/docs/html/models_blueprint.html +2366 -0
  151. package/docs/html/observability_blueprint.html +683 -0
  152. package/docs/html/provider_adapter_blueprint.html +245 -0
  153. package/docs/html/safety_blueprint.html +1386 -0
  154. package/docs/html/shared.css +571 -0
  155. package/docs/html/shared.js +143 -0
  156. package/docs/html/skills_blueprint.html +671 -0
  157. package/docs/html/soak_blueprint.html +182 -0
  158. package/docs/html/tool_usage_blueprint.html +350 -0
  159. package/docs/html/tools_blueprint.html +2249 -0
  160. package/docs/html/trace_blueprint.html +235 -0
  161. package/docs/html/tui_design_blueprint.html +314 -0
  162. package/docs/html/validation_blueprint.html +961 -0
  163. package/docs/html/worker_dispatch_blueprint.html +231 -0
  164. package/docs/installation-and-lifecycle.md +308 -0
  165. package/docs/middleware-and-components.md +148 -0
  166. package/docs/model-catalog.md +189 -0
  167. package/docs/observability.md +233 -0
  168. package/docs/proactive-memory.md +452 -0
  169. package/docs/prompt-envelope-and-tools.md +142 -0
  170. package/docs/provider-adapter-cookbook.md +148 -0
  171. package/docs/release-cut-checklist.md +138 -0
  172. package/docs/safety-model.md +357 -0
  173. package/docs/scientific-validation.md +105 -0
  174. package/docs/session-lifecycle.md +156 -0
  175. package/docs/skills-marketplace.md +46 -0
  176. package/docs/tool-usage.md +527 -0
  177. package/docs/trace-store.md +132 -0
  178. package/docs/troubleshooting.md +33 -0
  179. package/docs/tui-design.md +239 -0
  180. package/docs/worker-dispatch-mechanics.md +242 -0
  181. package/package.json +132 -0
  182. package/skills/README.md +408 -0
  183. package/skills/git/commit-crafting/SKILL.md +79 -0
  184. package/skills/git/commit-crafting/evals.md +92 -0
  185. package/skills/git/create-pr/SKILL.md +116 -0
  186. package/skills/git/create-pr/evals.md +114 -0
  187. package/skills/git/investigate-issue/SKILL.md +139 -0
  188. package/skills/git/investigate-issue/evals.md +94 -0
  189. package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
  190. package/skills/git/resolve-merge-conflicts/evals.md +58 -0
  191. package/skills/git/review-changes/SKILL.md +103 -0
  192. package/skills/git/review-changes/evals.md +85 -0
  193. package/skills/git/worktree-create/SKILL.md +92 -0
  194. package/skills/git/worktree-create/evals.md +97 -0
  195. package/skills/git/worktree-create/references/worktree-setup.md +66 -0
  196. package/skills/git/worktree-merge/SKILL.md +95 -0
  197. package/skills/git/worktree-merge/evals.md +114 -0
  198. package/skills/skill-marketplace.json +261 -0
  199. package/skills/workflow/cut-it/SKILL.md +86 -0
  200. package/skills/workflow/cut-it/evals.md +42 -0
  201. package/src/domains/agents/builtins/architect.md +33 -0
  202. package/src/domains/agents/builtins/coder.md +31 -0
  203. package/src/domains/agents/builtins/context-bootstrap.md +38 -0
  204. package/src/domains/agents/builtins/debugger.md +30 -0
  205. package/src/domains/agents/builtins/documenter.md +31 -0
  206. package/src/domains/agents/builtins/git-master.md +30 -0
  207. package/src/domains/agents/builtins/provenance.md +30 -0
  208. package/src/domains/agents/builtins/researcher.md +71 -0
  209. package/src/domains/agents/builtins/scout.md +42 -0
  210. package/src/domains/agents/builtins/tester.md +31 -0
  211. package/src/domains/agents/builtins/verifier.md +30 -0
  212. package/src/domains/agents/builtins/wiki-writer.md +41 -0
  213. package/src/domains/agents/fleets/build-review.md +34 -0
  214. package/src/domains/agents/fleets/build-test.md +35 -0
  215. package/src/domains/agents/fleets/sdlc.md +86 -0
  216. package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
  217. package/src/domains/prompts/fragments/identity/clio.md +26 -0
  218. package/src/domains/prompts/fragments/operating/contract.md +64 -0
  219. package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
  220. package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
  221. package/src/domains/prompts/fragments/safety/read-only.md +13 -0
  222. package/src/domains/prompts/fragments/safety/suggest.md +13 -0
  223. package/src/domains/prompts/fragments/wiki/page.md +75 -0
  224. package/src/domains/prompts/fragments/wiki/plan.md +48 -0
  225. package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
  226. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
@@ -0,0 +1,3906 @@
1
+ import {
2
+ renderCodewikiDigest
3
+ } from "./chunk-X3BR7HWV.js";
4
+ import {
5
+ loadMemoryRecordsSync
6
+ } from "./chunk-VOO7NYPP.js";
7
+ import {
8
+ WIKI_INDEX,
9
+ WIKI_PLAN_FILE,
10
+ WIKI_QUICKSTART,
11
+ changedPathsSince,
12
+ compileGlobRegex,
13
+ computeWikiContentHash,
14
+ computeWikiContentHashOfDir,
15
+ currentWikiGitHead,
16
+ isGeneratedWikiFile,
17
+ listWikiPages,
18
+ listWikiPagesInDir,
19
+ normalizeGlobInput,
20
+ readWikiMeta,
21
+ readWikiPage,
22
+ readWikiPlanFile,
23
+ renderWikiPage,
24
+ resolveSourcePath,
25
+ scopePlanForUpdate,
26
+ unclaimedCandidates,
27
+ wikiCompleteness,
28
+ wikiDir,
29
+ wikiMarkdownFilesInDir,
30
+ wikiMetaPath,
31
+ wikiStaleness,
32
+ writeWikiMeta,
33
+ writeWikiPlanFile
34
+ } from "./chunk-GNGDQYDU.js";
35
+ import {
36
+ computeFingerprint,
37
+ computeFingerprintAsync,
38
+ computeFingerprintCached,
39
+ isStale,
40
+ readClioState,
41
+ statePath,
42
+ writeClioState
43
+ } from "./chunk-RQDWMVRB.js";
44
+ import {
45
+ buildCodewiki,
46
+ codewikiNeedsBackfill,
47
+ codewikiPath,
48
+ createSlicer,
49
+ detectProjectType,
50
+ enumerateWorkspaceFiles,
51
+ parseCodewikiRaw,
52
+ readCodewiki,
53
+ readCodewikiAsync,
54
+ syncCodewiki,
55
+ updateCodewikiPaths,
56
+ writeCodewiki
57
+ } from "./chunk-TPOCL34A.js";
58
+ import {
59
+ classifyProjectPreload
60
+ } from "./chunk-KBR4MZZR.js";
61
+ import {
62
+ ceilChars
63
+ } from "./chunk-TKHQ4DGZ.js";
64
+ import {
65
+ BusChannels
66
+ } from "./chunk-D5YD55AR.js";
67
+ import {
68
+ clioDataDir,
69
+ resolveClioDirs
70
+ } from "./chunk-X3NE4WVW.js";
71
+ import {
72
+ resolvePackageRoot
73
+ } from "./chunk-DXQNI4PC.js";
74
+
75
+ // src/domains/context/extension.ts
76
+ import { createHash as createHash2 } from "node:crypto";
77
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
78
+ import { isAbsolute, relative as relative3 } from "node:path";
79
+
80
+ // src/domains/context/adoption.ts
81
+ import { createHash } from "node:crypto";
82
+ import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
83
+ import { homedir } from "node:os";
84
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
85
+ var MAX_SOURCE_BYTES = 128 * 1024;
86
+ var MAX_TOTAL_SOURCE_BYTES = 1024 * 1024;
87
+ var MAX_RECURSIVE_FILES = 80;
88
+ var MAX_RULES_PER_SOURCE = 4;
89
+ var MAX_IMPORTED_RULES = 20;
90
+ var PROVIDER_LABELS = {
91
+ "claude-code": "Claude Code",
92
+ agents: "Agent Skills",
93
+ codex: "Codex",
94
+ gemini: "Gemini",
95
+ cursor: "Cursor",
96
+ copilot: "GitHub Copilot",
97
+ opencode: "OpenCode"
98
+ };
99
+ var KIND_LABELS = {
100
+ instructions: "instructions",
101
+ settings: "settings",
102
+ command: "command",
103
+ agent: "agent",
104
+ skill: "skill",
105
+ rule: "rule"
106
+ };
107
+ var GENERATED_OR_SECRET_DIRS = /* @__PURE__ */ new Set([
108
+ ".git",
109
+ ".clio-coder",
110
+ ".antigravitycli",
111
+ "node_modules",
112
+ "dist",
113
+ "build",
114
+ "target",
115
+ ".venv",
116
+ "venv",
117
+ "cache",
118
+ "caches",
119
+ "history",
120
+ "histories",
121
+ "sessions",
122
+ "logs",
123
+ "tmp",
124
+ "temp",
125
+ "state",
126
+ "projects"
127
+ ]);
128
+ var UNSAFE_FILE_NAME_RE = /(^\.env(?:\.|$)|secret|credential|token|password|history|cache|session|\.log$|state\.json$)/i;
129
+ var RULE_KEYWORDS = /\b(always|never|must|should|prefer|avoid|do not|don't|use|run|keep|write|allow|allows|deny|denies|enable|enables|define|defines)\b/i;
130
+ var SECRET_LINE_PATTERNS = [
131
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
132
+ /\bauthorization\s*[:=]\s*bearer\s+\S+/i,
133
+ /\b(?:api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|password|secret|credential)\b\s*[:=]\s*["']?[^\s"'`,;]{8,}/i,
134
+ /\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b/,
135
+ /\bsk-[A-Za-z0-9_-]{20,}\b/,
136
+ /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/,
137
+ /\bAIza[0-9A-Za-z_-]{20,}\b/
138
+ ];
139
+ function sha256(text) {
140
+ return createHash("sha256").update(text).digest("hex");
141
+ }
142
+ function normalizeText(text) {
143
+ return text.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
144
+ }
145
+ function isRecord(value) {
146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147
+ }
148
+ function isSubpath(parent, child) {
149
+ const rel = relative(parent, child);
150
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep}`);
151
+ }
152
+ function displayPath(filePath, cwd, home) {
153
+ const abs = resolve(filePath);
154
+ const cwdAbs = resolve(cwd);
155
+ const homeAbs = resolve(home);
156
+ if (isSubpath(cwdAbs, abs)) {
157
+ const rel = relative(cwdAbs, abs).split(sep).join("/");
158
+ return rel.length > 0 ? rel : basename(abs);
159
+ }
160
+ if (isSubpath(homeAbs, abs)) {
161
+ const rel = relative(homeAbs, abs).split(sep).join("/");
162
+ return rel.length > 0 ? `~/${rel}` : "~";
163
+ }
164
+ return abs;
165
+ }
166
+ function unsafePathName(filePath) {
167
+ const name2 = basename(filePath);
168
+ return UNSAFE_FILE_NAME_RE.test(name2);
169
+ }
170
+ function safeDirEntryName(name2) {
171
+ const lower = name2.toLowerCase();
172
+ if (GENERATED_OR_SECRET_DIRS.has(lower)) return false;
173
+ if (lower.includes("secret") || lower.includes("credential") || lower.includes("token")) return false;
174
+ return true;
175
+ }
176
+ function hasSecretLikeLine(text) {
177
+ for (const line of text.split("\n")) {
178
+ if (SECRET_LINE_PATTERNS.some((pattern) => pattern.test(line))) return true;
179
+ }
180
+ return false;
181
+ }
182
+ function collectFiles(dir, maxDepth, accept, out3 = [], depth = 0) {
183
+ if (out3.length >= MAX_RECURSIVE_FILES) return out3;
184
+ let entries;
185
+ try {
186
+ entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
187
+ } catch {
188
+ return out3;
189
+ }
190
+ for (const entry of entries) {
191
+ if (out3.length >= MAX_RECURSIVE_FILES) break;
192
+ if (entry.isSymbolicLink()) continue;
193
+ const filePath = join(dir, entry.name);
194
+ if (entry.isDirectory()) {
195
+ if (depth >= maxDepth || !safeDirEntryName(entry.name)) continue;
196
+ collectFiles(filePath, maxDepth, accept, out3, depth + 1);
197
+ continue;
198
+ }
199
+ if (!entry.isFile()) continue;
200
+ if (unsafePathName(filePath)) continue;
201
+ if (accept(filePath)) out3.push(filePath);
202
+ }
203
+ return out3;
204
+ }
205
+ function markdownLike(filePath) {
206
+ const ext = extname(filePath).toLowerCase();
207
+ return ext === ".md" || ext === ".mdc";
208
+ }
209
+ function jsonLike(filePath) {
210
+ return extname(filePath).toLowerCase() === ".json";
211
+ }
212
+ function discoverCandidateSpecs(cwd, home, includeGlobal) {
213
+ const specs = [];
214
+ const seen = /* @__PURE__ */ new Set();
215
+ let order = 0;
216
+ const add = (path, scope, provider, kind) => {
217
+ const abs = resolve(path);
218
+ if (seen.has(abs)) return;
219
+ seen.add(abs);
220
+ const scopedDir = scope === "project" && kind === "instructions" ? relative(cwd, dirname(abs)) : "";
221
+ specs.push({
222
+ path: abs,
223
+ scope,
224
+ provider,
225
+ kind,
226
+ ...scope === "project" && kind === "instructions" ? { directoryScope: scopedDir.length === 0 ? "." : scopedDir.split(sep).join("/") } : {},
227
+ order
228
+ });
229
+ order += 1;
230
+ };
231
+ const addProject = (relPath, provider, kind) => {
232
+ add(join(cwd, relPath), "project", provider, kind);
233
+ };
234
+ const addProjectDir = (relPath, provider, kind, maxDepth, accept) => {
235
+ for (const filePath of collectFiles(join(cwd, relPath), maxDepth, accept)) add(filePath, "project", provider, kind);
236
+ };
237
+ addProject("CLAUDE.md", "claude-code", "instructions");
238
+ addProject(join(".claude", "CLAUDE.md"), "claude-code", "instructions");
239
+ addProject("AGENTS.md", "codex", "instructions");
240
+ addProject("CODEX.md", "codex", "instructions");
241
+ addProject(join(".codex", "AGENTS.md"), "codex", "instructions");
242
+ addProject("GEMINI.md", "gemini", "instructions");
243
+ addProject(join(".gemini", "GEMINI.md"), "gemini", "instructions");
244
+ addProject(join(".github", "copilot-instructions.md"), "copilot", "instructions");
245
+ const visibleWorkspaceFiles = enumerateWorkspaceFiles(cwd);
246
+ for (const relPath of visibleWorkspaceFiles) {
247
+ const parts = relPath.split("/");
248
+ if (parts.length - 1 > 6) continue;
249
+ const name2 = parts.at(-1);
250
+ if (name2 !== "CLAUDE.md" && name2 !== "AGENTS.md" && name2 !== "CODEX.md" && name2 !== "GEMINI.md") continue;
251
+ const filePath = join(cwd, relPath);
252
+ const provider = name2 === "CLAUDE.md" ? "claude-code" : name2 === "GEMINI.md" ? "gemini" : "codex";
253
+ add(filePath, "project", provider, "instructions");
254
+ }
255
+ addProject(join(".claude", "settings.json"), "claude-code", "settings");
256
+ addProjectDir(join(".claude", "commands"), "claude-code", "command", 3, markdownLike);
257
+ addProjectDir(join(".claude", "agents"), "claude-code", "agent", 2, markdownLike);
258
+ addProjectDir(join(".claude", "skills"), "claude-code", "skill", 4, markdownLike);
259
+ addProjectDir(join(".agents", "skills"), "agents", "skill", 4, markdownLike);
260
+ addProjectDir(join(".codex", "skills"), "codex", "skill", 4, markdownLike);
261
+ addProjectDir(join(".opencode", "skills"), "opencode", "skill", 4, markdownLike);
262
+ addProject(join(".gemini", "settings.json"), "gemini", "settings");
263
+ addProject(join(".gemini", "config.json"), "gemini", "settings");
264
+ addProjectDir(
265
+ join(".gemini", "rules"),
266
+ "gemini",
267
+ "rule",
268
+ 2,
269
+ (filePath) => markdownLike(filePath) || jsonLike(filePath)
270
+ );
271
+ addProjectDir(
272
+ join(".gemini", "config"),
273
+ "gemini",
274
+ "rule",
275
+ 2,
276
+ (filePath) => markdownLike(filePath) || jsonLike(filePath)
277
+ );
278
+ addProjectDir(join(".cursor", "rules"), "cursor", "rule", 0, markdownLike);
279
+ addProjectDir(join(".github", "skills"), "copilot", "skill", 4, markdownLike);
280
+ if (includeGlobal) add(join(home, ".codex", "AGENTS.md"), "global", "codex", "instructions");
281
+ return specs.filter((spec) => existsSync(spec.path));
282
+ }
283
+ function stripFrontmatter(text) {
284
+ if (!text.startsWith("---\n")) return text;
285
+ const end = text.indexOf("\n---", 4);
286
+ if (end === -1) return text;
287
+ return text.slice(end + "\n---".length).replace(/^\n+/, "");
288
+ }
289
+ function stripCodeFences(text) {
290
+ const lines = [];
291
+ let inFence = false;
292
+ for (const line of text.split("\n")) {
293
+ if (/^\s*```/.test(line)) {
294
+ inFence = !inFence;
295
+ continue;
296
+ }
297
+ if (!inFence) lines.push(line);
298
+ }
299
+ return lines.join("\n");
300
+ }
301
+ function isBarePathOrFileName(text) {
302
+ const token = /^`([^`]+)`$/.exec(text)?.[1] ?? text;
303
+ if (/\s/.test(token)) return false;
304
+ if (!/^[A-Za-z0-9_@.+~/-]+$/.test(token)) return false;
305
+ return token.includes("/") || extname(token).length > 1;
306
+ }
307
+ function cleanRuleText(raw) {
308
+ let text = raw.replace(/<!--.*?-->/g, "").replace(/^\[[ xX]\]\s+/, "").replace(/\s+/g, " ").trim();
309
+ text = text.replace(/^[#>\s]+/, "").trim();
310
+ if (text.length < 8 || text.length > 260) return null;
311
+ if (/^(todo|note|example|marker)\b/i.test(text)) return null;
312
+ if (/\$[A-Z][A-Z0-9_]*|\{\{[^}]+\}\}|<\/?[A-Z][A-Z0-9_-]*>/i.test(text)) return null;
313
+ if (isBarePathOrFileName(text)) return null;
314
+ if (!RULE_KEYWORDS.test(text)) return null;
315
+ if (hasSecretLikeLine(text)) return null;
316
+ if (text.length > 200) text = `${text.slice(0, 197).trimEnd()}\u2026`;
317
+ return text;
318
+ }
319
+ function extractMarkdownRules(content) {
320
+ const text = stripCodeFences(stripFrontmatter(content));
321
+ const rules = [];
322
+ const seen = /* @__PURE__ */ new Set();
323
+ for (const line of text.split("\n")) {
324
+ const trimmed = line.trim();
325
+ if (trimmed.length === 0 || trimmed.startsWith("|") || /^#{1,6}\s/.test(trimmed)) continue;
326
+ const bullet = /^(?:[-*+]\s+|\d+\.\s+)(.+?)\s*$/.exec(trimmed)?.[1] ?? null;
327
+ const candidate = bullet ?? (/^[A-Z][^.!?]{8,220}[.!?]?$/.test(trimmed) ? trimmed : null);
328
+ if (!candidate) continue;
329
+ const cleaned = cleanRuleText(candidate);
330
+ if (!cleaned) continue;
331
+ const key = normalizeRuleKey(cleaned);
332
+ if (seen.has(key)) continue;
333
+ seen.add(key);
334
+ rules.push(cleaned);
335
+ if (rules.length >= MAX_RULES_PER_SOURCE) break;
336
+ }
337
+ return rules;
338
+ }
339
+ function stringArray(value) {
340
+ if (!Array.isArray(value)) return [];
341
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
342
+ }
343
+ function stringField(record, key) {
344
+ const value = record[key];
345
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
346
+ }
347
+ function formatShortList(items, limit = 4) {
348
+ const cleaned = items.map((item) => item.replace(/\s+/g, " ").trim()).filter(Boolean);
349
+ const shown = cleaned.slice(0, limit).map((item) => `\`${item.length > 48 ? `${item.slice(0, 45)}\u2026` : item}\``);
350
+ if (cleaned.length > limit) shown.push(`+${cleaned.length - limit} more`);
351
+ return shown.join(", ");
352
+ }
353
+ function extractSettingsRules(provider, content) {
354
+ let parsed;
355
+ try {
356
+ parsed = JSON.parse(content);
357
+ } catch {
358
+ return [];
359
+ }
360
+ if (!isRecord(parsed)) return [];
361
+ const rules = [];
362
+ if (provider === "claude-code") {
363
+ const permissions = parsed.permissions;
364
+ if (isRecord(permissions)) {
365
+ const allow = stringArray(permissions.allow);
366
+ const deny = stringArray(permissions.deny);
367
+ const claudePermissionSetting = stringField(permissions, "defaultMode");
368
+ if (allow.length > 0) rules.push(`Claude Code allows tools: ${formatShortList(allow)}.`);
369
+ if (deny.length > 0) rules.push(`Claude Code denies tools: ${formatShortList(deny)}.`);
370
+ if (claudePermissionSetting)
371
+ rules.push(`Claude Code declares permission setting \`${claudePermissionSetting}\` for this project.`);
372
+ }
373
+ if (parsed.enableAllProjectMcpServers === true) rules.push("Claude Code enables all project MCP servers.");
374
+ }
375
+ if (provider === "gemini") {
376
+ const contextFileName = stringField(parsed, "contextFileName");
377
+ if (contextFileName) rules.push(`Gemini uses \`${contextFileName}\` as an additional context file.`);
378
+ const mcpServers = parsed.mcpServers;
379
+ if (isRecord(mcpServers) && Object.keys(mcpServers).length > 0) {
380
+ rules.push(`Gemini project settings define MCP servers: ${formatShortList(Object.keys(mcpServers))}.`);
381
+ }
382
+ }
383
+ return rules.map((rule) => cleanRuleText(rule)).filter((rule) => Boolean(rule));
384
+ }
385
+ function extractRules(spec, content) {
386
+ if (spec.kind === "settings" || jsonLike(spec.path)) return extractSettingsRules(spec.provider, content);
387
+ return extractMarkdownRules(content);
388
+ }
389
+ function normalizeRuleKey(text) {
390
+ return text.toLowerCase().replace(/`([^`]+)`/g, "$1").replace(/[^a-z0-9.+:#/-]+/g, " ").replace(/\s+/g, " ").trim().replace(/[.!?]+$/, "");
391
+ }
392
+ function classifyConflict(text) {
393
+ const normalized = text.toLowerCase().replace(/`/g, "");
394
+ const packageManager2 = /\b(?:always\s+|must\s+|should\s+|prefer\s+|use\s+|run\s+)*(?:use|prefer|run)\s+(pnpm|npm|yarn|bun)\b/.exec(
395
+ normalized
396
+ );
397
+ if (packageManager2?.[1]) return { key: "package-manager", value: packageManager2[1] };
398
+ const testRunner = /\b(?:always\s+|must\s+|should\s+|prefer\s+|use\s+|run\s+)*(?:use|prefer|run)\s+(node:test|vitest|jest|pytest|cargo test|go test)\b/.exec(
399
+ normalized
400
+ );
401
+ if (testRunner?.[1]) return { key: "test-runner", value: testRunner[1] };
402
+ return void 0;
403
+ }
404
+ function sourcePriority(source) {
405
+ const kindPriority = {
406
+ instructions: 0,
407
+ rule: 1e3,
408
+ settings: 2e3,
409
+ command: 3e3,
410
+ agent: 4e3,
411
+ skill: 5e3
412
+ };
413
+ const scopeDepth = source.directoryScope ? source.directoryScope.split("/").filter((part) => part !== ".").length : 0;
414
+ return (source.scope === "project" ? 0 : 1e4) + kindPriority[source.kind] + scopeDepth * 10 + source.order;
415
+ }
416
+ function buildImportPlan(sources) {
417
+ const candidates = [];
418
+ for (const source of sources) {
419
+ const rules = extractRules(
420
+ { path: source.path, scope: source.scope, provider: source.provider, kind: source.kind, order: 0 },
421
+ source.content
422
+ );
423
+ rules.forEach((text, index) => {
424
+ const conflict = classifyConflict(text);
425
+ candidates.push({ text, source, index, ...conflict ? { conflict } : {} });
426
+ });
427
+ }
428
+ candidates.sort((a, b) => {
429
+ const sourceDelta = sourcePriority(a.source) - sourcePriority(b.source);
430
+ return sourceDelta !== 0 ? sourceDelta : a.index - b.index;
431
+ });
432
+ const accepted = [];
433
+ const byNormalizedText = /* @__PURE__ */ new Map();
434
+ const byConflictKey = /* @__PURE__ */ new Map();
435
+ const conflicts = /* @__PURE__ */ new Map();
436
+ for (const candidate of candidates) {
437
+ const normalized = normalizeRuleKey(candidate.text);
438
+ const existing = byNormalizedText.get(normalized);
439
+ if (existing) {
440
+ if (!existing.sources.includes(candidate.source.displayPath)) existing.sources.push(candidate.source.displayPath);
441
+ if (!existing.providers.includes(candidate.source.providerLabel))
442
+ existing.providers.push(candidate.source.providerLabel);
443
+ if (candidate.source.directoryScope) {
444
+ existing.directoryScopes ??= [];
445
+ if (!existing.directoryScopes.includes(candidate.source.directoryScope)) {
446
+ existing.directoryScopes.push(candidate.source.directoryScope);
447
+ }
448
+ }
449
+ continue;
450
+ }
451
+ if (candidate.conflict) {
452
+ const prior = byConflictKey.get(candidate.conflict.key);
453
+ if (prior && prior.value !== candidate.conflict.value) {
454
+ let conflict = conflicts.get(candidate.conflict.key);
455
+ if (!conflict) {
456
+ conflict = {
457
+ key: candidate.conflict.key,
458
+ kept: prior.rule.text,
459
+ keptSources: [...prior.rule.sources],
460
+ skipped: []
461
+ };
462
+ conflicts.set(candidate.conflict.key, conflict);
463
+ }
464
+ conflict.skipped.push({
465
+ text: candidate.text,
466
+ source: candidate.source.displayPath,
467
+ provider: candidate.source.providerLabel
468
+ });
469
+ continue;
470
+ }
471
+ }
472
+ const rule = {
473
+ text: candidate.text,
474
+ sources: [candidate.source.displayPath],
475
+ providers: [candidate.source.providerLabel],
476
+ ...candidate.source.directoryScope ? { directoryScopes: [candidate.source.directoryScope] } : {},
477
+ ...candidate.conflict ? { conflictKey: candidate.conflict.key } : {}
478
+ };
479
+ accepted.push(rule);
480
+ byNormalizedText.set(normalized, rule);
481
+ if (candidate.conflict) byConflictKey.set(candidate.conflict.key, { value: candidate.conflict.value, rule });
482
+ if (accepted.length >= MAX_IMPORTED_RULES) break;
483
+ }
484
+ return { importedRules: accepted, conflicts: [...conflicts.values()] };
485
+ }
486
+ function rejectedCandidate(spec, displayPath2, reason, evidence) {
487
+ return {
488
+ path: spec.path,
489
+ displayPath: displayPath2,
490
+ scope: spec.scope,
491
+ provider: spec.provider,
492
+ kind: spec.kind,
493
+ ...spec.directoryScope ? { directoryScope: spec.directoryScope } : {},
494
+ reason,
495
+ fingerprintSha256: sha256(`rejected\0${reason}\0${evidence}`)
496
+ };
497
+ }
498
+ function statEvidence(stat) {
499
+ return `${stat.size}:${Math.floor(stat.mtimeMs)}:${stat.mode}`;
500
+ }
501
+ function readCandidate(spec, cwd, home) {
502
+ const shown = displayPath(spec.path, cwd, home);
503
+ if (unsafePathName(spec.path)) {
504
+ return rejectedCandidate(spec, shown, "unsafe path name", "");
505
+ }
506
+ let stat;
507
+ try {
508
+ const lst = lstatSync(spec.path);
509
+ if (lst.isSymbolicLink()) {
510
+ return rejectedCandidate(spec, shown, "symlink skipped", statEvidence(lst));
511
+ }
512
+ stat = statSync(spec.path);
513
+ } catch {
514
+ return null;
515
+ }
516
+ if (!stat.isFile()) return null;
517
+ if (stat.size > MAX_SOURCE_BYTES) {
518
+ return rejectedCandidate(spec, shown, "file too large", statEvidence(stat));
519
+ }
520
+ let raw;
521
+ try {
522
+ raw = readFileSync(spec.path, "utf8");
523
+ } catch {
524
+ return rejectedCandidate(spec, shown, "unreadable", statEvidence(stat));
525
+ }
526
+ if (raw.includes("\0")) {
527
+ return rejectedCandidate(spec, shown, "binary content", raw);
528
+ }
529
+ const content = normalizeText(raw);
530
+ if (hasSecretLikeLine(content)) {
531
+ return rejectedCandidate(spec, shown, "secret-like content", content);
532
+ }
533
+ const rules = extractRules(spec, content);
534
+ return {
535
+ path: spec.path,
536
+ displayPath: shown,
537
+ scope: spec.scope,
538
+ provider: spec.provider,
539
+ providerLabel: PROVIDER_LABELS[spec.provider],
540
+ kind: spec.kind,
541
+ kindLabel: KIND_LABELS[spec.kind],
542
+ ...spec.directoryScope ? { directoryScope: spec.directoryScope } : {},
543
+ content,
544
+ contentSha256: sha256(content),
545
+ byteLength: Buffer.byteLength(content, "utf8"),
546
+ itemCount: rules.length,
547
+ order: spec.order
548
+ };
549
+ }
550
+ function sourceSnapshot(source) {
551
+ if ("content" in source) {
552
+ return {
553
+ path: source.path,
554
+ scope: source.scope,
555
+ provider: source.provider,
556
+ kind: source.kind,
557
+ ...source.directoryScope ? { directoryScope: source.directoryScope } : {},
558
+ sha256: source.contentSha256
559
+ };
560
+ }
561
+ return {
562
+ path: source.path,
563
+ scope: source.scope,
564
+ provider: source.provider,
565
+ kind: source.kind,
566
+ ...source.directoryScope ? { directoryScope: source.directoryScope } : {},
567
+ sha256: source.fingerprintSha256,
568
+ status: "rejected",
569
+ reason: source.reason
570
+ };
571
+ }
572
+ function sourceSetHash(snapshots) {
573
+ const hash = createHash("sha256");
574
+ for (const snapshot of snapshots) {
575
+ hash.update(
576
+ `${snapshot.scope}\0${snapshot.provider}\0${snapshot.kind}\0${snapshot.path}\0${snapshot.directoryScope ?? ""}\0${snapshot.status ?? "accepted"}\0${snapshot.reason ?? ""}\0${snapshot.sha256}
577
+ `
578
+ );
579
+ }
580
+ return hash.digest("hex");
581
+ }
582
+ function adoptionSnapshotsHash(snapshots) {
583
+ return sourceSetHash([...snapshots].sort((a, b) => a.path.localeCompare(b.path)));
584
+ }
585
+ function scanAgentConfigs(options) {
586
+ const cwd = resolve(options.cwd);
587
+ const home = resolve(options.homeDir ?? homedir());
588
+ const includeGlobal = options.includeGlobal === true;
589
+ const sources = [];
590
+ const rejected = [];
591
+ let acceptedSourceBytes = 0;
592
+ for (const spec of discoverCandidateSpecs(cwd, home, includeGlobal)) {
593
+ const result = readCandidate(spec, cwd, home);
594
+ if (!result) continue;
595
+ if (!("content" in result)) {
596
+ rejected.push(result);
597
+ continue;
598
+ }
599
+ if (acceptedSourceBytes + result.byteLength > MAX_TOTAL_SOURCE_BYTES) {
600
+ rejected.push({
601
+ path: result.path,
602
+ displayPath: result.displayPath,
603
+ scope: result.scope,
604
+ provider: result.provider,
605
+ kind: result.kind,
606
+ ...result.directoryScope ? { directoryScope: result.directoryScope } : {},
607
+ reason: "aggregate source byte budget exceeded",
608
+ fingerprintSha256: sha256(`rejected\0aggregate source byte budget exceeded\0${result.contentSha256}`)
609
+ });
610
+ continue;
611
+ }
612
+ acceptedSourceBytes += result.byteLength;
613
+ sources.push(result);
614
+ }
615
+ sources.sort((a, b) => {
616
+ const scopeDelta = (a.scope === "project" ? 0 : 1) - (b.scope === "project" ? 0 : 1);
617
+ return scopeDelta !== 0 ? scopeDelta : a.order - b.order;
618
+ });
619
+ const plan = buildImportPlan(sources);
620
+ const sourceSnapshots = [...sources.map(sourceSnapshot), ...rejected.map(sourceSnapshot)];
621
+ return {
622
+ cwd,
623
+ homeDir: home,
624
+ includeGlobal,
625
+ sources,
626
+ rejected,
627
+ importedRules: plan.importedRules,
628
+ conflicts: plan.conflicts,
629
+ sourceHash: sourceSetHash(sourceSnapshots),
630
+ sourceSnapshots
631
+ };
632
+ }
633
+ function renderSourceList(paths) {
634
+ if (paths.length <= 3) return paths.map((path) => `\`${path}\``).join(", ");
635
+ return `${paths.slice(0, 3).map((path) => `\`${path}\``).join(", ")} +${paths.length - 3} more`;
636
+ }
637
+ function quoteShort(text) {
638
+ const cleaned = text.replace(/\s+/g, " ").trim();
639
+ return `\u201C${cleaned.length > 120 ? `${cleaned.slice(0, 117).trimEnd()}\u2026` : cleaned}\u201D`;
640
+ }
641
+ function renderImportedAgentContext(scan) {
642
+ if (scan.sources.length === 0 && scan.rejected.length === 0) return "";
643
+ const lines = [
644
+ "Conflict policy: CLIO-CODER.md conventions and hard invariants are canonical; project-local imports win over explicit global imports; duplicate rules are merged by normalized text.",
645
+ "",
646
+ "### Adopted rules",
647
+ ""
648
+ ];
649
+ if (scan.importedRules.length === 0) {
650
+ lines.push("- No project-specific rules were adopted from external agent configs.");
651
+ } else {
652
+ for (const rule of scan.importedRules) {
653
+ const scoped = rule.directoryScopes?.filter((scope) => scope !== ".") ?? [];
654
+ lines.push(
655
+ `- ${rule.text} Sources: ${renderSourceList(rule.sources)}.${scoped.length > 0 ? ` Scopes: ${renderSourceList(scoped.map((scope) => `${scope}/**`))}.` : ""}`
656
+ );
657
+ }
658
+ }
659
+ lines.push("", "### Source provenance", "");
660
+ for (const source of scan.sources) {
661
+ const adopted = source.itemCount === 1 ? "1 candidate" : `${source.itemCount} candidates`;
662
+ const scoped = source.directoryScope && source.directoryScope !== "." ? `, scope ${source.directoryScope}/**` : "";
663
+ lines.push(
664
+ `- ${source.providerLabel} ${source.kindLabel} (${source.scope}${scoped}): \`${source.displayPath}\`; ${adopted}.`
665
+ );
666
+ }
667
+ if (scan.sources.length === 0) lines.push("- No supported project-local agent config files were found.");
668
+ if (scan.conflicts.length > 0) {
669
+ lines.push("", "### Skipped conflicts", "");
670
+ for (const conflict of scan.conflicts) {
671
+ for (const skipped of conflict.skipped) {
672
+ lines.push(
673
+ `- ${quoteShort(skipped.text)} from \`${skipped.source}\` conflicted with ${renderSourceList(conflict.keptSources)} on ${conflict.key}; kept ${quoteShort(conflict.kept)}.`
674
+ );
675
+ }
676
+ }
677
+ }
678
+ if (scan.rejected.length > 0) {
679
+ lines.push("", "### Rejected sources", "");
680
+ for (const rejected of scan.rejected.slice(0, 8)) {
681
+ const provider = rejected.provider ? `${PROVIDER_LABELS[rejected.provider]} ` : "";
682
+ lines.push(`- ${provider}\`${rejected.displayPath}\`: skipped ${rejected.reason}.`);
683
+ }
684
+ if (scan.rejected.length > 8) lines.push(`- ${scan.rejected.length - 8} more rejected sources omitted.`);
685
+ }
686
+ return lines.join("\n");
687
+ }
688
+ function inferAdoptionCwd(snapshots, home) {
689
+ for (const snapshot of snapshots) {
690
+ if (snapshot.scope !== "project") continue;
691
+ let candidate = resolve(snapshot.path, "..");
692
+ for (; ; ) {
693
+ const match = discoverCandidateSpecs(candidate, home, false).some(
694
+ (spec) => spec.path === resolve(snapshot.path) && spec.scope === snapshot.scope && spec.provider === snapshot.provider && spec.kind === snapshot.kind
695
+ );
696
+ if (match) return candidate;
697
+ const parent = resolve(candidate, "..");
698
+ if (parent === candidate) break;
699
+ candidate = parent;
700
+ }
701
+ }
702
+ return null;
703
+ }
704
+ function inferAdoptionHome(snapshots) {
705
+ const global = snapshots.find((snapshot) => snapshot.scope === "global");
706
+ return global ? resolve(global.path, "..", "..") : null;
707
+ }
708
+ function adoptionSourcesChanged(snapshots, options = {}) {
709
+ const homeDir = resolve(options.homeDir ?? inferAdoptionHome(snapshots) ?? homedir());
710
+ const cwd = options.cwd ? resolve(options.cwd) : inferAdoptionCwd(snapshots, homeDir);
711
+ if (!cwd) {
712
+ for (const snapshot of snapshots) {
713
+ const current2 = readCandidate(
714
+ {
715
+ path: snapshot.path,
716
+ scope: snapshot.scope,
717
+ provider: snapshot.provider,
718
+ kind: snapshot.kind,
719
+ order: 0
720
+ },
721
+ resolve(snapshot.path, ".."),
722
+ homeDir
723
+ );
724
+ if (!current2 || adoptionSnapshotsHash([sourceSnapshot(current2)]) !== adoptionSnapshotsHash([snapshot])) {
725
+ return true;
726
+ }
727
+ }
728
+ return false;
729
+ }
730
+ const current = scanAgentConfigs({
731
+ cwd,
732
+ homeDir,
733
+ includeGlobal: options.includeGlobal ?? snapshots.some((snapshot) => snapshot.scope === "global")
734
+ }).sourceSnapshots;
735
+ return adoptionSnapshotsHash(current) !== adoptionSnapshotsHash(snapshots);
736
+ }
737
+
738
+ // src/domains/context/bootstrap.ts
739
+ import { execFileSync } from "node:child_process";
740
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
741
+ import { dirname as dirname2, join as join4, parse } from "node:path";
742
+
743
+ // src/domains/context/clio-md.ts
744
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
745
+ import { join as join2 } from "node:path";
746
+ var FOOTER_RE = /<!--\s*clio:fingerprint v1\s*\n([\s\S]*?)\n\s*-->/;
747
+ var H1_RE = /^#\s+(.+?)\s*$/gm;
748
+ var H2_RE = /^##\s+(.+?)\s*$/gm;
749
+ function normalizeSource(source) {
750
+ return source.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").trimStart();
751
+ }
752
+ function normalizeInline(value) {
753
+ return value.replace(/\s+/g, " ").trim().replace(/^#{1,6}\s+/, "").trim();
754
+ }
755
+ function normalizeNestedMarkdown(value) {
756
+ return normalizeSource(value).trim().replace(/^(#{1,2})(\s+)/gm, "###$2");
757
+ }
758
+ function charLen(value) {
759
+ return [...value].length;
760
+ }
761
+ function parseFooter(source) {
762
+ const warnings = [];
763
+ const markerCount = (source.match(/clio:fingerprint v1/g) ?? []).length;
764
+ const match = FOOTER_RE.exec(source);
765
+ if (!match) {
766
+ if (markerCount > 0) {
767
+ return {
768
+ body: source,
769
+ footer: null,
770
+ firstInit: false,
771
+ errors: ["malformed fingerprint footer"],
772
+ warnings
773
+ };
774
+ }
775
+ return { body: source, footer: null, firstInit: true, errors: [], warnings };
776
+ }
777
+ const footerEnd = match.index + match[0].length;
778
+ const trailing = source.slice(footerEnd).trim();
779
+ if (trailing.length > 0) warnings.push("trailing content after fingerprint footer ignored");
780
+ let parsed;
781
+ try {
782
+ parsed = JSON.parse(match[1] ?? "");
783
+ } catch {
784
+ return {
785
+ body: source.slice(0, match.index),
786
+ footer: null,
787
+ firstInit: false,
788
+ errors: ["fingerprint footer is not valid JSON"],
789
+ warnings
790
+ };
791
+ }
792
+ const footer = validateFooter(parsed);
793
+ if (!footer) {
794
+ return {
795
+ body: source.slice(0, match.index),
796
+ footer: null,
797
+ firstInit: false,
798
+ errors: ["fingerprint footer has invalid shape"],
799
+ warnings
800
+ };
801
+ }
802
+ return {
803
+ body: source.slice(0, match.index),
804
+ footer,
805
+ firstInit: false,
806
+ errors: [],
807
+ warnings
808
+ };
809
+ }
810
+ function validateFooter(value) {
811
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
812
+ const obj = value;
813
+ if (typeof obj.initAt !== "string" || obj.initAt.length === 0) return null;
814
+ if (typeof obj.model !== "string" || obj.model.length === 0) return null;
815
+ if (!(typeof obj.gitHead === "string" || obj.gitHead === null)) return null;
816
+ if (typeof obj.treeHash !== "string" || !/^[0-9a-f]{64}$/.test(obj.treeHash)) return null;
817
+ if (typeof obj.loc !== "number" || !Number.isInteger(obj.loc) || obj.loc < 0) return null;
818
+ return {
819
+ initAt: obj.initAt,
820
+ model: obj.model,
821
+ gitHead: obj.gitHead,
822
+ treeHash: obj.treeHash,
823
+ loc: obj.loc
824
+ };
825
+ }
826
+ function readSections(body) {
827
+ const headings = [...body.matchAll(H2_RE)];
828
+ const sections = [];
829
+ for (let i = 0; i < headings.length; i += 1) {
830
+ const heading = headings[i];
831
+ if (!heading || heading.index === void 0) continue;
832
+ const next = headings[i + 1];
833
+ const title = (heading[1] ?? "").trim();
834
+ const start = heading.index + heading[0].length;
835
+ const end = next?.index ?? body.length;
836
+ sections.push({ title, body: body.slice(start, end).trim() });
837
+ }
838
+ return sections;
839
+ }
840
+ function sectionBody(sections, title) {
841
+ return sections.find((section) => section.title.toLowerCase() === title.toLowerCase())?.body ?? "";
842
+ }
843
+ var RESERVED_SECTION_TITLES = /* @__PURE__ */ new Set(["conventions", "hard invariants", "imported agent context"]);
844
+ function extraSections(sections) {
845
+ return sections.filter((section) => !RESERVED_SECTION_TITLES.has(section.title.toLowerCase()));
846
+ }
847
+ function identityParagraph(afterH1) {
848
+ const beforeFirstSection = afterH1.split(/^##\s+/m)[0] ?? "";
849
+ const paragraphs = beforeFirstSection.trim().split(/\n\s*\n/).map((part) => part.replace(/\s+/g, " ").trim()).filter((part) => part.length > 0);
850
+ return paragraphs[0] ?? "";
851
+ }
852
+ function parseBullets(section) {
853
+ if (section.trim().length === 0) return [];
854
+ return section.split("\n").map((line) => /^[-*]\s+(.+?)\s*$/.exec(line)?.[1]?.trim()).filter((line) => Boolean(line));
855
+ }
856
+ function parseNumbered(section) {
857
+ if (section.trim().length === 0) return [];
858
+ return section.split("\n").map((line) => /^\d+\.\s+(.+?)\s*$/.exec(line)?.[1]?.trim()).filter((line) => Boolean(line));
859
+ }
860
+ function parseClioMd(source) {
861
+ const normalized = normalizeSource(source);
862
+ const warnings = [];
863
+ const footerResult = parseFooter(normalized);
864
+ warnings.push(...footerResult.warnings);
865
+ if (footerResult.errors.length > 0) return { ok: false, errors: footerResult.errors, warnings };
866
+ const h1Matches = [...footerResult.body.matchAll(H1_RE)];
867
+ if (h1Matches.length === 0) return { ok: false, errors: ["missing H1 heading"], warnings };
868
+ if (h1Matches.length > 1) return { ok: false, errors: ["more than one H1 heading"], warnings };
869
+ const h1 = h1Matches[0];
870
+ if (!h1 || h1.index === void 0) return { ok: false, errors: ["missing H1 heading"], warnings };
871
+ const projectName2 = (h1[1] ?? "").trim();
872
+ const errors = [];
873
+ if (projectName2.length === 0) errors.push("project name must not be empty");
874
+ if (charLen(projectName2) > 80) warnings.push("project name is longer than the generator target");
875
+ const afterH1 = footerResult.body.slice(h1.index + h1[0].length);
876
+ const identity = identityParagraph(afterH1);
877
+ if (identity.length === 0) errors.push("identity paragraph is required");
878
+ if (charLen(identity) > 600) warnings.push("identity paragraph is longer than the generator target");
879
+ const sections = readSections(afterH1);
880
+ const conventions = parseBullets(sectionBody(sections, "conventions"));
881
+ const invariants = parseNumbered(sectionBody(sections, "hard invariants"));
882
+ const customSections = extraSections(sections);
883
+ const importedAgentContext = sectionBody(sections, "imported agent context") || null;
884
+ if (conventions.length > 6) warnings.push("conventions exceed the generator target of six bullets");
885
+ if (invariants.length > 3) warnings.push("hard invariants exceed the generator target of three numbered rules");
886
+ if (customSections.length > 8) warnings.push("custom sections exceed the generator target of eight H2 sections");
887
+ for (const [index, item] of conventions.entries()) {
888
+ if (charLen(item) > 200) warnings.push(`convention ${index + 1} is longer than the generator target`);
889
+ }
890
+ for (const [index, item] of invariants.entries()) {
891
+ if (charLen(item) > 280) warnings.push(`hard invariant ${index + 1} is longer than the generator target`);
892
+ }
893
+ for (const [index, section] of customSections.entries()) {
894
+ if (charLen(section.title) > 80)
895
+ warnings.push(`custom section ${index + 1} title is longer than the generator target`);
896
+ if (charLen(section.body) > 2500)
897
+ warnings.push(`custom section ${index + 1} body is longer than the generator target`);
898
+ }
899
+ if (errors.length > 0) return { ok: false, errors, warnings };
900
+ return {
901
+ ok: true,
902
+ value: {
903
+ projectName: projectName2,
904
+ identity,
905
+ conventions,
906
+ invariants,
907
+ sections: customSections,
908
+ importedAgentContext,
909
+ fingerprint: footerResult.footer,
910
+ firstInit: footerResult.firstInit,
911
+ warnings
912
+ }
913
+ };
914
+ }
915
+ function validateForSerialization(input) {
916
+ const test = parseClioMd(renderWithoutParse(input));
917
+ if (!test.ok) {
918
+ throw new Error(`CLIO-CODER.md serialization failed validation: ${test.errors.join("; ")}`);
919
+ }
920
+ }
921
+ function renderWithoutParse(input) {
922
+ const lines = [`# ${normalizeInline(input.projectName)}`, "", normalizeInline(input.identity)];
923
+ const conventions = input.conventions.map((item) => normalizeInline(item)).filter((item) => item.length > 0);
924
+ if (conventions.length > 0) {
925
+ lines.push("", "## Conventions", "", ...conventions.map((item) => `- ${item}`));
926
+ }
927
+ const invariants = input.invariants.map((item) => normalizeInline(item)).filter((item) => item.length > 0);
928
+ if (invariants.length > 0) {
929
+ lines.push("", "## Hard invariants", "", ...invariants.map((item, index) => `${index + 1}. ${item}`));
930
+ }
931
+ for (const section of input.sections ?? []) {
932
+ const title = normalizeInline(section.title);
933
+ const body = normalizeNestedMarkdown(section.body);
934
+ if (title.length > 0 && body.length > 0) lines.push("", `## ${title}`, "", body);
935
+ }
936
+ const imported = input.importedAgentContext ? normalizeNestedMarkdown(input.importedAgentContext) : void 0;
937
+ if (imported && imported.length > 0) {
938
+ lines.push("", "## Imported agent context", "", imported);
939
+ }
940
+ if (!input.fingerprint) return `${lines.join("\n")}
941
+ `;
942
+ const footer = JSON.stringify(input.fingerprint, null, 2);
943
+ return `${lines.join("\n")}
944
+
945
+ <!-- clio:fingerprint v1
946
+ ${footer}
947
+ -->
948
+ `;
949
+ }
950
+ function serializeClioMd(input) {
951
+ validateForSerialization(input);
952
+ return renderWithoutParse(input);
953
+ }
954
+ function renderProjectTypeFragment(projectType) {
955
+ return `<project-type>${projectType}</project-type>`;
956
+ }
957
+ function renderProjectContextFragment(parsed) {
958
+ const sections = [`# ${parsed.projectName}`, parsed.identity];
959
+ if (parsed.conventions.length > 0) {
960
+ sections.push("## Conventions", ...parsed.conventions.map((item) => `- ${item}`));
961
+ }
962
+ if (parsed.invariants.length > 0) {
963
+ sections.push("## Hard invariants", ...parsed.invariants.map((item, index) => `${index + 1}. ${item}`));
964
+ }
965
+ for (const section of parsed.sections) {
966
+ sections.push(`## ${section.title}`, section.body);
967
+ }
968
+ if (parsed.importedAgentContext) {
969
+ sections.push("## Imported agent context", parsed.importedAgentContext);
970
+ }
971
+ return `<project-context>
972
+ ${sections.join("\n\n")}
973
+ </project-context>`;
974
+ }
975
+ function tryReadClioMd(cwd) {
976
+ const filePath = join2(cwd, "CLIO-CODER.md");
977
+ if (!existsSync2(filePath)) return null;
978
+ let content;
979
+ try {
980
+ content = readFileSync2(filePath, "utf8");
981
+ } catch (err) {
982
+ const message = err instanceof Error ? err.message : String(err);
983
+ return { ok: false, error: message };
984
+ }
985
+ const parsed = parseClioMd(content);
986
+ if (!parsed.ok) return { ok: false, error: parsed.errors.join("; ") };
987
+ return { ok: true, value: parsed.value };
988
+ }
989
+
990
+ // src/domains/context/project-metadata.ts
991
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
992
+ import { join as join3 } from "node:path";
993
+ import { parse as parseYaml } from "yaml";
994
+ var MAX_DESCRIPTION_CHARS = 400;
995
+ var MAX_NAME_CHARS = 80;
996
+ function readText(cwd, relative6) {
997
+ try {
998
+ return readFileSync3(join3(cwd, relative6), "utf8");
999
+ } catch {
1000
+ return null;
1001
+ }
1002
+ }
1003
+ function clean(value, max) {
1004
+ if (typeof value !== "string") return null;
1005
+ const collapsed = value.replace(/\s+/gu, " ").trim();
1006
+ if (collapsed.length === 0) return null;
1007
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1).trimEnd()}\u2026` : collapsed;
1008
+ }
1009
+ function name(value) {
1010
+ return clean(value, MAX_NAME_CHARS);
1011
+ }
1012
+ function description(value) {
1013
+ const cleaned = clean(value, MAX_DESCRIPTION_CHARS);
1014
+ return cleaned === null ? null : clean(cleaned.replace(/\.$/u, ""), MAX_DESCRIPTION_CHARS) ?? null;
1015
+ }
1016
+ function jsonRecord(cwd, relative6) {
1017
+ const raw = readText(cwd, relative6);
1018
+ if (raw === null) return null;
1019
+ try {
1020
+ const parsed = JSON.parse(raw);
1021
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
1022
+ } catch {
1023
+ return null;
1024
+ }
1025
+ }
1026
+ function tomlTableValue(raw, table, key) {
1027
+ const lines = raw.split(/\r?\n/u);
1028
+ let inTable = false;
1029
+ for (const line of lines) {
1030
+ const trimmed = line.trim();
1031
+ if (trimmed.startsWith("[")) {
1032
+ inTable = trimmed === `[${table}]`;
1033
+ continue;
1034
+ }
1035
+ if (!inTable) continue;
1036
+ const match = /^([A-Za-z0-9_-]+)\s*=\s*(.*)$/u.exec(trimmed);
1037
+ if (!match || match[1] !== key) continue;
1038
+ const value = (match[2] ?? "").trim();
1039
+ const quoted = /^"((?:[^"\\]|\\.)*)"$/u.exec(value) ?? /^'([^']*)'$/u.exec(value);
1040
+ if (!quoted) return null;
1041
+ return (quoted[1] ?? "").replace(/\\"/gu, '"');
1042
+ }
1043
+ return null;
1044
+ }
1045
+ function keyEqualsValue(raw, key) {
1046
+ const match = new RegExp(`^[ \\t]*${key}[ \\t]*(?:\\+?=)[ \\t]*(.+)$`, "mu").exec(raw);
1047
+ const value = match?.[1]?.trim();
1048
+ if (!value) return null;
1049
+ return value.replace(/^"(.*)"$/su, "$1").trim();
1050
+ }
1051
+ function iniSectionValue(raw, section, key) {
1052
+ const lines = raw.split(/\r?\n/u);
1053
+ let inSection = false;
1054
+ for (const line of lines) {
1055
+ const trimmed = line.trim();
1056
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1057
+ inSection = trimmed === `[${section}]`;
1058
+ continue;
1059
+ }
1060
+ if (!inSection) continue;
1061
+ const match = /^([A-Za-z0-9_.-]+)\s*=\s*(.*)$/u.exec(trimmed);
1062
+ if (!match || match[1] !== key) continue;
1063
+ const value = (match[2] ?? "").trim();
1064
+ return value.length > 0 ? value : null;
1065
+ }
1066
+ return null;
1067
+ }
1068
+ function xmlTagValue(raw, tag) {
1069
+ const match = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "u").exec(raw);
1070
+ return match?.[1] ?? null;
1071
+ }
1072
+ function yamlRecord(cwd, relative6) {
1073
+ const raw = readText(cwd, relative6);
1074
+ if (raw === null) return null;
1075
+ try {
1076
+ const parsed = parseYaml(raw);
1077
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+ var packageJson = (cwd) => {
1083
+ const record = jsonRecord(cwd, "package.json");
1084
+ if (!record) return null;
1085
+ return { file: "package.json", fragment: { name: name(record.name), description: description(record.description) } };
1086
+ };
1087
+ var pyprojectToml = (cwd) => {
1088
+ const raw = readText(cwd, "pyproject.toml");
1089
+ if (raw === null) return null;
1090
+ const projectName2 = tomlTableValue(raw, "project", "name") ?? tomlTableValue(raw, "tool.poetry", "name");
1091
+ const projectDescription = tomlTableValue(raw, "project", "description") ?? tomlTableValue(raw, "tool.poetry", "description");
1092
+ return {
1093
+ file: "pyproject.toml",
1094
+ fragment: { name: name(projectName2), description: description(projectDescription) }
1095
+ };
1096
+ };
1097
+ var cargoToml = (cwd) => {
1098
+ const raw = readText(cwd, "Cargo.toml");
1099
+ if (raw === null) return null;
1100
+ return {
1101
+ file: "Cargo.toml",
1102
+ fragment: {
1103
+ name: name(tomlTableValue(raw, "package", "name")),
1104
+ description: description(tomlTableValue(raw, "package", "description"))
1105
+ }
1106
+ };
1107
+ };
1108
+ var setupCfg = (cwd) => {
1109
+ const raw = readText(cwd, "setup.cfg");
1110
+ if (raw === null) return null;
1111
+ return {
1112
+ file: "setup.cfg",
1113
+ fragment: {
1114
+ name: name(iniSectionValue(raw, "metadata", "name")),
1115
+ description: description(iniSectionValue(raw, "metadata", "description"))
1116
+ }
1117
+ };
1118
+ };
1119
+ var composerJson = (cwd) => {
1120
+ const record = jsonRecord(cwd, "composer.json");
1121
+ if (!record) return null;
1122
+ return {
1123
+ file: "composer.json",
1124
+ fragment: { name: name(record.name), description: description(record.description) }
1125
+ };
1126
+ };
1127
+ var pomXml = (cwd) => {
1128
+ const raw = readText(cwd, "pom.xml");
1129
+ if (raw === null) return null;
1130
+ return {
1131
+ file: "pom.xml",
1132
+ fragment: {
1133
+ name: name(xmlTagValue(raw, "name") ?? xmlTagValue(raw, "artifactId")),
1134
+ description: description(xmlTagValue(raw, "description"))
1135
+ }
1136
+ };
1137
+ };
1138
+ var cmakeLists = (cwd) => {
1139
+ const raw = readText(cwd, "CMakeLists.txt");
1140
+ if (raw === null) return null;
1141
+ const call = /^[ \t]*project[ \t]*\(([\s\S]*?)\)/imu.exec(raw)?.[1];
1142
+ if (!call) return { file: "CMakeLists.txt", fragment: {} };
1143
+ const projectName2 = /^\s*([A-Za-z0-9_.+-]+)/u.exec(call)?.[1];
1144
+ const projectDescription = /\bDESCRIPTION\s+"((?:[^"\\]|\\.)*)"/u.exec(call)?.[1];
1145
+ return {
1146
+ file: "CMakeLists.txt",
1147
+ fragment: { name: name(projectName2), description: description(projectDescription) }
1148
+ };
1149
+ };
1150
+ var doxyfile = (cwd) => {
1151
+ for (const relative6 of ["Doxyfile", "Doxyfile.in", "docs/Doxyfile", "doc/Doxyfile"]) {
1152
+ const raw = readText(cwd, relative6);
1153
+ if (raw === null) continue;
1154
+ return {
1155
+ file: relative6,
1156
+ fragment: {
1157
+ name: name(keyEqualsValue(raw, "PROJECT_NAME")),
1158
+ description: description(keyEqualsValue(raw, "PROJECT_BRIEF"))
1159
+ }
1160
+ };
1161
+ }
1162
+ return null;
1163
+ };
1164
+ var rDescription = (cwd) => {
1165
+ const raw = readText(cwd, "DESCRIPTION");
1166
+ if (raw === null || !/^Package:\s*\S/mu.test(raw)) return null;
1167
+ const field = (key) => {
1168
+ const match = new RegExp(`^${key}:[ \\t]*([\\s\\S]*?)(?=\\n\\S|$)`, "mu").exec(raw);
1169
+ return match?.[1] ?? null;
1170
+ };
1171
+ return {
1172
+ file: "DESCRIPTION",
1173
+ fragment: {
1174
+ name: name(field("Package")),
1175
+ description: description(field("Title") ?? field("Description"))
1176
+ }
1177
+ };
1178
+ };
1179
+ var gemspec = (cwd) => {
1180
+ let entries;
1181
+ try {
1182
+ entries = readdirSync2(cwd).filter((entry) => entry.endsWith(".gemspec"));
1183
+ } catch {
1184
+ return null;
1185
+ }
1186
+ entries.sort();
1187
+ const relative6 = entries[0];
1188
+ if (!relative6) return null;
1189
+ const raw = readText(cwd, relative6);
1190
+ if (raw === null) return null;
1191
+ const field = (key) => new RegExp(`\\.${key}\\s*=\\s*["']([^"']+)["']`, "u").exec(raw)?.[1] ?? null;
1192
+ return {
1193
+ file: relative6,
1194
+ fragment: { name: name(field("name")), description: description(field("summary") ?? field("description")) }
1195
+ };
1196
+ };
1197
+ var citationCff = (cwd) => {
1198
+ const record = yamlRecord(cwd, "CITATION.cff");
1199
+ if (!record) return null;
1200
+ return { file: "CITATION.cff", fragment: { name: name(record.title), description: description(record.abstract) } };
1201
+ };
1202
+ var codemetaJson = (cwd) => {
1203
+ const record = jsonRecord(cwd, "codemeta.json");
1204
+ if (!record) return null;
1205
+ return { file: "codemeta.json", fragment: { name: name(record.name), description: description(record.description) } };
1206
+ };
1207
+ var zenodoJson = (cwd) => {
1208
+ const record = jsonRecord(cwd, ".zenodo.json");
1209
+ if (!record) return null;
1210
+ return { file: ".zenodo.json", fragment: { name: name(record.title), description: description(record.description) } };
1211
+ };
1212
+ var METADATA_READERS = [
1213
+ packageJson,
1214
+ pyprojectToml,
1215
+ cargoToml,
1216
+ setupCfg,
1217
+ composerJson,
1218
+ pomXml,
1219
+ cmakeLists,
1220
+ doxyfile,
1221
+ rDescription,
1222
+ gemspec,
1223
+ citationCff,
1224
+ codemetaJson,
1225
+ zenodoJson
1226
+ ];
1227
+ var README_CANDIDATES = [
1228
+ "README.md",
1229
+ "README.rst",
1230
+ "README.adoc",
1231
+ "README.asciidoc",
1232
+ "README.org",
1233
+ "README.txt",
1234
+ "README.markdown",
1235
+ "README",
1236
+ "readme.md",
1237
+ "readme.rst",
1238
+ "readme.txt",
1239
+ "docs/README.md",
1240
+ "docs/index.rst",
1241
+ "doc/README.md"
1242
+ ];
1243
+ function findReadme(cwd) {
1244
+ for (const candidate of README_CANDIDATES) {
1245
+ if (!existsSync3(join3(cwd, candidate))) continue;
1246
+ const content = readText(cwd, candidate);
1247
+ if (content !== null && content.trim().length > 0) return { path: candidate, content };
1248
+ }
1249
+ return null;
1250
+ }
1251
+ function stripReadmeMarkup(raw) {
1252
+ return raw.replace(/<!--[\s\S]*?-->/gu, "").replace(/<picture\b[\s\S]*?<\/picture>/giu, "").replace(/^\s*<h[1-6]\b[^>]*>[\s\S]*?<\/h[1-6]>\s*$/gimu, "").replace(/\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)/gu, "").replace(/!\[[^\]]*\]\([^)]*\)/gu, "").replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1").replace(/<img\b[^>]*>/giu, "").replace(/<[^>]+>/gu, "").replace(/^\s*\.\.\s+\S+::.*(?:\n(?:[ \t]+.*)?)*/gmu, "").replace(/^\s*\.\.\s+_?\|?[^\n]*\|?:.*$/gmu, "").replace(/\|[A-Za-z0-9_-]+\|/gu, "").replace(/:[a-z]+:`([^`]*)`/gu, "$1").replace(/^\s*:[A-Za-z0-9_-]+:.*$/gmu, "").replace(/^\s*image:{1,2}[^\n]*$/gmu, "").replace(/[*_~`]/gu, "");
1253
+ }
1254
+ function isStructuralLine(line) {
1255
+ const trimmed = line.trim();
1256
+ if (trimmed.length === 0) return true;
1257
+ if (/^#{1,6}\s+/u.test(trimmed)) return true;
1258
+ if (/^={1,6}\s+/u.test(trimmed)) return true;
1259
+ if (/^[=\-~^"'#*+_]{3,}$/u.test(trimmed)) return true;
1260
+ if (/^[-|:\s]+$/u.test(trimmed)) return true;
1261
+ return false;
1262
+ }
1263
+ function isCalloutParagraph(lines) {
1264
+ const content = lines.filter((line) => line.trim().length > 0);
1265
+ if (content.length === 0) return false;
1266
+ return content.every((line) => /^\s*>/u.test(line));
1267
+ }
1268
+ function readmeSummary(content) {
1269
+ const paragraphs = stripReadmeMarkup(content).split(/\n\s*\n/u).map((part) => part.split(/\r?\n/u)).filter((lines) => !isCalloutParagraph(lines)).map(
1270
+ (lines) => lines.filter((line) => !isStructuralLine(line)).join(" ").trim()
1271
+ ).filter(
1272
+ (part) => part.length >= 20 && /[A-Za-z]{3}/u.test(part) && !part.startsWith("```") && !/^table of contents$/iu.test(part)
1273
+ );
1274
+ const first = paragraphs[0];
1275
+ if (!first) return null;
1276
+ return description(first);
1277
+ }
1278
+ function readmeTitle(content) {
1279
+ const lines = stripReadmeMarkup(content).split(/\r?\n/u);
1280
+ for (let index = 0; index < lines.length; index += 1) {
1281
+ const line = (lines[index] ?? "").trim();
1282
+ if (line.length === 0) continue;
1283
+ const hash = /^#{1,3}\s+(.+?)\s*$/u.exec(line)?.[1] ?? /^={1,3}\s+(.+?)\s*$/u.exec(line)?.[1];
1284
+ if (hash) return name(hash);
1285
+ const next = (lines[index + 1] ?? "").trim();
1286
+ if (/^[=\-~^]{3,}$/u.test(next) && !isStructuralLine(line)) return name(line);
1287
+ return null;
1288
+ }
1289
+ return null;
1290
+ }
1291
+ function readProjectMetadata(cwd) {
1292
+ const out3 = { name: null, description: null, nameSource: null, descriptionSource: null };
1293
+ for (const reader of METADATA_READERS) {
1294
+ if (out3.name !== null && out3.description !== null) break;
1295
+ let read;
1296
+ try {
1297
+ read = reader(cwd);
1298
+ } catch {
1299
+ continue;
1300
+ }
1301
+ if (!read) continue;
1302
+ if (out3.name === null && read.fragment.name) {
1303
+ out3.name = read.fragment.name;
1304
+ out3.nameSource = read.file;
1305
+ }
1306
+ if (out3.description === null && read.fragment.description) {
1307
+ out3.description = read.fragment.description;
1308
+ out3.descriptionSource = read.file;
1309
+ }
1310
+ }
1311
+ if (out3.name !== null && out3.description !== null) return out3;
1312
+ const readme = findReadme(cwd);
1313
+ if (!readme) return out3;
1314
+ if (out3.name === null) {
1315
+ const title = readmeTitle(readme.content);
1316
+ if (title) {
1317
+ out3.name = title;
1318
+ out3.nameSource = readme.path;
1319
+ }
1320
+ }
1321
+ if (out3.description === null) {
1322
+ const summary = readmeSummary(readme.content);
1323
+ if (summary) {
1324
+ out3.description = summary;
1325
+ out3.descriptionSource = readme.path;
1326
+ }
1327
+ }
1328
+ return out3;
1329
+ }
1330
+
1331
+ // src/domains/context/prompt-context.ts
1332
+ function renderPromptContext(cwd) {
1333
+ const projectType = detectProjectType(cwd);
1334
+ const pieces = [renderProjectTypeFragment(projectType)];
1335
+ const warnings = [];
1336
+ const clio = tryReadClioMd(cwd);
1337
+ let clioMd = null;
1338
+ if (clio?.ok) {
1339
+ clioMd = clio.value;
1340
+ pieces.push(renderProjectContextFragment(clio.value));
1341
+ }
1342
+ if (clio && !clio.ok) warnings.push(`clio: malformed CLIO-CODER.md ignored: ${clio.error}`);
1343
+ const codewiki = readCodewiki(cwd);
1344
+ if (codewiki) {
1345
+ const state = readClioState(cwd);
1346
+ const stale = state ? isStale(state.fingerprint, computeFingerprintCached(cwd, codewiki)) : true;
1347
+ const suffix = stale ? " (stale; run /context refresh)" : "";
1348
+ pieces.push(`<codewiki>available${suffix}; use code_nav</codewiki>`);
1349
+ }
1350
+ const staleness = wikiStaleness(cwd);
1351
+ if (staleness.state !== "absent") {
1352
+ const pages = listWikiPages(cwd);
1353
+ const completeness = wikiCompleteness(cwd);
1354
+ const notes = [];
1355
+ if (completeness && completeness.owed > 0) {
1356
+ notes.push(
1357
+ `incomplete: ${completeness.pagesWritten} of ${completeness.pagesPlanned} planned pages written, ${completeness.owed} owed`
1358
+ );
1359
+ }
1360
+ if (staleness.state === "stale") notes.push("stale");
1361
+ const suffix = notes.length > 0 ? ` (${notes.join("; ")}; run clio-coder context wiki --update)` : "";
1362
+ pieces.push(`<wiki>${pages.length} pages at .clio-coder/wiki (start: quickstart.md)${suffix}</wiki>`);
1363
+ }
1364
+ return { text: pieces.join("\n\n"), clioMd, warnings };
1365
+ }
1366
+
1367
+ // src/domains/context/bootstrap.ts
1368
+ function out(io, message) {
1369
+ io?.stdout(message);
1370
+ }
1371
+ function warn(io, message) {
1372
+ io?.stderr(message);
1373
+ }
1374
+ function progress(input, event) {
1375
+ input.onProgress?.(event);
1376
+ }
1377
+ function readJsonFile(filePath) {
1378
+ try {
1379
+ return JSON.parse(readFileSync4(filePath, "utf8"));
1380
+ } catch {
1381
+ return null;
1382
+ }
1383
+ }
1384
+ function stringField2(value, key) {
1385
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
1386
+ const field = value[key];
1387
+ return typeof field === "string" && field.trim().length > 0 ? field.trim() : null;
1388
+ }
1389
+ function titleFromPackageName(raw) {
1390
+ const base = raw.includes("/") ? raw.split("/").pop() ?? raw : raw;
1391
+ return base.split(/[-_]+/).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
1392
+ }
1393
+ function projectNameFrom(metadata, cwd) {
1394
+ const declared = metadata.name;
1395
+ if (declared) {
1396
+ return metadata.nameSource === "package.json" ? titleFromPackageName(declared) : declared.slice(0, 80);
1397
+ }
1398
+ return titleFromPackageName(parse(cwd).base || "Project");
1399
+ }
1400
+ function projectName(cwd) {
1401
+ return projectNameFrom(readProjectMetadata(cwd), cwd);
1402
+ }
1403
+ function projectTypeLabel(projectType) {
1404
+ switch (projectType) {
1405
+ case "typescript":
1406
+ return "TypeScript/Node.js";
1407
+ case "javascript":
1408
+ return "JavaScript/Node.js";
1409
+ case "python":
1410
+ return "Python";
1411
+ case "rust":
1412
+ return "Rust";
1413
+ case "go":
1414
+ return "Go";
1415
+ case "c":
1416
+ return "C";
1417
+ case "c++":
1418
+ return "C++";
1419
+ case "java":
1420
+ return "Java";
1421
+ case "ruby":
1422
+ return "Ruby";
1423
+ case "c#":
1424
+ return "C#";
1425
+ case "polyglot":
1426
+ return "polyglot";
1427
+ case "dotfiles":
1428
+ return "dotfiles";
1429
+ case "unknown":
1430
+ return "software";
1431
+ }
1432
+ }
1433
+ function allContextText(files) {
1434
+ return files.map((file) => file.content).join("\n\n");
1435
+ }
1436
+ function resolveDefaultIdentity(cwd, projectType, files) {
1437
+ const metadata = readProjectMetadata(cwd);
1438
+ const name2 = projectNameFrom(metadata, cwd);
1439
+ const context = allContextText(files);
1440
+ if (/Clio owns the agent loop/i.test(context) && /pi-(?:ai|SDK)/i.test(context)) {
1441
+ return {
1442
+ text: [
1443
+ "Clio Coder is IOWarp's orchestrator coding agent, named for the Greek muse of history and developed by the Gnosis Research Center at Illinois Tech under PI @akougkas. CLIO stands for Context Layer for Input/Output.",
1444
+ "pi-ai is accessed through the engine boundary.",
1445
+ "Clio owns the agent loop, TUI, session format, tool registry, and identity."
1446
+ ].join(" "),
1447
+ bare: false,
1448
+ descriptionSource: null
1449
+ };
1450
+ }
1451
+ const stack = projectTypeLabel(projectType);
1452
+ const head = `${name2} is a ${stack} project.`;
1453
+ const description2 = metadata.description;
1454
+ if (!description2) return { text: head.slice(0, 600), bare: true, descriptionSource: null };
1455
+ const cleaned = description2.replace(/\.$/, "").trim();
1456
+ const role = /^[a-z]/.test(cleaned) ? `It is ${cleaned}.` : `${cleaned}.`;
1457
+ return { text: `${head} ${role}`.slice(0, 600), bare: false, descriptionSource: metadata.descriptionSource };
1458
+ }
1459
+ function stabilizedIdentity(input, modelIdentity) {
1460
+ const deterministic = resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles);
1461
+ const model = typeof modelIdentity === "string" ? modelIdentity.trim() : "";
1462
+ if (deterministic.bare && model.length > 0) return model;
1463
+ return deterministic.text;
1464
+ }
1465
+ function pushUnique(target, value) {
1466
+ if (target.includes(value)) return;
1467
+ target.push(value);
1468
+ }
1469
+ var SKIP_BULLET_PATTERNS = [/^marker\s*[:=]/i, /marker[-_:]\s*\w+-\w+/i, /^todo\b/i, /^note\b/i, /^example\b/i];
1470
+ function harvestSiblingBullets(files) {
1471
+ const out3 = [];
1472
+ for (const file of files) {
1473
+ for (const rawLine of file.content.split("\n")) {
1474
+ const match = /^[\s>]*[-*]\s+(.+?)\s*$/.exec(rawLine);
1475
+ if (!match) continue;
1476
+ const bullet = match[1]?.trim();
1477
+ if (!bullet || bullet.length < 5) continue;
1478
+ if (bullet.length > 200) continue;
1479
+ if (SKIP_BULLET_PATTERNS.some((re) => re.test(bullet))) continue;
1480
+ if (!RULE_KEYWORDS.test(bullet)) continue;
1481
+ out3.push(bullet);
1482
+ }
1483
+ }
1484
+ return out3;
1485
+ }
1486
+ function inferConventions(cwd, projectType, files) {
1487
+ const conventions = [];
1488
+ const context = allContextText(files);
1489
+ const pkg = readJsonFile(join4(cwd, "package.json"));
1490
+ const packageText = typeof pkg === "object" && pkg !== null ? JSON.stringify(pkg) : "";
1491
+ if (/Local imports end in `?\.js`?/i.test(context) || projectType === "typescript" && existsSync4(join4(cwd, "tsconfig.json"))) {
1492
+ pushUnique(conventions, "Local imports end in `.js`. Tests use `node:test`. Avoid `any` without a tracking issue.");
1493
+ } else if (/node:test/i.test(context) || /node --import tsx --test|node --test/.test(packageText)) {
1494
+ pushUnique(conventions, "Tests use `node:test`.");
1495
+ }
1496
+ if (/No em-dash|em-dash/i.test(context)) {
1497
+ pushUnique(
1498
+ conventions,
1499
+ "No em-dash clause separators in code, comments, commits, or responses. Write full sentences."
1500
+ );
1501
+ }
1502
+ if (/Commit subjects|conventional commit|Imperative, lowercase|lowercase-typed subjects/i.test(context)) {
1503
+ pushUnique(
1504
+ conventions,
1505
+ "Commit subjects are imperative, lowercase, conventional, at most 72 characters, and end without a period."
1506
+ );
1507
+ }
1508
+ for (const bullet of harvestSiblingBullets(files)) pushUnique(conventions, bullet);
1509
+ return conventions.slice(0, 6);
1510
+ }
1511
+ function inferInvariants(files) {
1512
+ const context = allContextText(files);
1513
+ const invariants = [];
1514
+ if (/Engine boundary/i.test(context)) {
1515
+ pushUnique(invariants, "Engine boundary. Only `src/engine/**` may value-import `@earendil-works/pi-*`.");
1516
+ }
1517
+ if (/Worker isolation/i.test(context)) {
1518
+ pushUnique(
1519
+ invariants,
1520
+ "Worker isolation. `src/worker/**` never imports `src/domains/**` except `src/domains/providers`."
1521
+ );
1522
+ }
1523
+ if (/Domain independence/i.test(context)) {
1524
+ pushUnique(
1525
+ invariants,
1526
+ "Domain independence. `src/domains/<x>/**` never imports `src/domains/<y>/extension.ts` for `y != x`."
1527
+ );
1528
+ }
1529
+ return invariants.slice(0, 3);
1530
+ }
1531
+ function indexedSourceFileCount(codewiki) {
1532
+ return codewiki.files.filter((file) => file.lang !== "config").length;
1533
+ }
1534
+ function measureProjectPreload(cwd) {
1535
+ const promptContext = renderPromptContext(cwd);
1536
+ return classifyProjectPreload({ hasClioMd: promptContext.clioMd !== null, text: promptContext.text });
1537
+ }
1538
+ function packageScripts(cwd) {
1539
+ const pkg = readJsonFile(join4(cwd, "package.json"));
1540
+ if (typeof pkg !== "object" || pkg === null || Array.isArray(pkg)) return {};
1541
+ const scripts = pkg.scripts;
1542
+ if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts)) return {};
1543
+ const out3 = {};
1544
+ for (const [name2, command] of Object.entries(scripts)) {
1545
+ if (typeof command === "string" && command.trim().length > 0) out3[name2] = command.trim();
1546
+ }
1547
+ return out3;
1548
+ }
1549
+ function packageManager(cwd) {
1550
+ const declared = stringField2(readJsonFile(join4(cwd, "package.json")), "packageManager")?.split("@")[0];
1551
+ if (declared === "pnpm" || declared === "yarn" || declared === "bun" || declared === "npm") return declared;
1552
+ if (existsSync4(join4(cwd, "pnpm-lock.yaml"))) return "pnpm";
1553
+ if (existsSync4(join4(cwd, "yarn.lock"))) return "yarn";
1554
+ if (existsSync4(join4(cwd, "bun.lock")) || existsSync4(join4(cwd, "bun.lockb"))) return "bun";
1555
+ return "npm";
1556
+ }
1557
+ var MUTATING_SCRIPT_RE = /(?:^|\s)--(?:fix|write)(?:\s|$)/;
1558
+ function verificationSection(cwd) {
1559
+ const scripts = packageScripts(cwd);
1560
+ const pm = packageManager(cwd);
1561
+ const hasScript = (name2) => typeof scripts[name2] === "string";
1562
+ const command = (name2) => `\`${pm} run ${name2}\``;
1563
+ const gate = (name2) => {
1564
+ if (hasScript(`${name2}:check`)) return `${name2}:check`;
1565
+ return hasScript(name2) && !MUTATING_SCRIPT_RE.test(scripts[name2] ?? "") ? name2 : null;
1566
+ };
1567
+ const lines = [];
1568
+ const baseline = ["typecheck", "lint", "format"].map(gate).filter((name2) => name2 !== null).map(command);
1569
+ if (baseline.length > 0) {
1570
+ lines.push(`Before handoff, run ${baseline.join(", ")}.`);
1571
+ }
1572
+ if (hasScript("build")) {
1573
+ lines.push(`Run ${command("build")} after CLI, worker, packaging, or generated-dist changes.`);
1574
+ }
1575
+ const targeted = ["test:contracts", "test:smoke", "check:boundaries"].filter(hasScript).map(command);
1576
+ if (targeted.length > 0) {
1577
+ lines.push(`Use targeted checks for narrower risk: ${targeted.join(", ")}.`);
1578
+ }
1579
+ if (hasScript("test")) {
1580
+ lines.push(`Run ${command("test")} when behavior crosses domains, tool contracts, smoke flows, or boundaries.`);
1581
+ }
1582
+ if (hasScript("ci")) {
1583
+ lines.push(`Use ${command("ci")} for the full local gate before committing broad or shared behavior changes.`);
1584
+ }
1585
+ if (lines.length === 0) return null;
1586
+ return { title: "Verification expectations", body: lines.join(" ") };
1587
+ }
1588
+ var VERIFICATION_SECTION_RE = /\bverification\b/i;
1589
+ function createModelGroundingCorpus(input) {
1590
+ let siblingBudget = 64e3;
1591
+ const siblingEvidence = [];
1592
+ for (const file of input.siblingFiles) {
1593
+ if (siblingBudget <= 0) break;
1594
+ const chunk = file.content.slice(0, siblingBudget);
1595
+ siblingEvidence.push(chunk);
1596
+ siblingBudget -= chunk.length;
1597
+ }
1598
+ const indexedPaths = new Set(input.codewiki.files.map((file) => file.path));
1599
+ const pm = packageManager(input.cwd);
1600
+ const scripts = packageScripts(input.cwd);
1601
+ const evidence = [
1602
+ ...siblingEvidence,
1603
+ (input.existingClioMdText ?? "").slice(0, 8e3),
1604
+ input.expectedProjectName ?? "",
1605
+ input.projectType,
1606
+ [...indexedPaths].join("\n").slice(0, 64e3),
1607
+ ...Object.entries(scripts).flatMap(([name2, command]) => [`${pm} run ${name2}`, `${pm} ${name2}`, command]),
1608
+ input.codewiki.symbols.map((symbol) => symbol.name).join("\n").slice(0, 128e3)
1609
+ ].join("\n");
1610
+ return { lower: evidence.toLowerCase(), indexedPaths };
1611
+ }
1612
+ var CODE_TOKEN_RE = /`([^`\n]+)`/g;
1613
+ function groundedToken(token, evidence) {
1614
+ return evidence.lower.includes(token.toLowerCase()) || evidence.indexedPaths.has(token.replace(/^\.\//, ""));
1615
+ }
1616
+ function groundedModelBody(body, evidence) {
1617
+ const kept = [];
1618
+ let inFence = false;
1619
+ for (const rawLine of body.split(/\r?\n/)) {
1620
+ const trimmed = rawLine.trim();
1621
+ if (trimmed.startsWith("```")) {
1622
+ inFence = !inFence;
1623
+ continue;
1624
+ }
1625
+ if (trimmed.length === 0 || /^#{1,6}\s|^---+$/.test(trimmed)) continue;
1626
+ const line = inFence ? `\`${trimmed.replace(/`/g, "")}\`` : trimmed;
1627
+ const codeTokens = [...line.matchAll(CODE_TOKEN_RE)].map((match) => match[1]?.trim()).filter((token) => token !== void 0 && token.length > 0);
1628
+ if (codeTokens.length === 0) continue;
1629
+ if (!codeTokens.every((token) => groundedToken(token, evidence))) continue;
1630
+ if (line.replace(CODE_TOKEN_RE, "").trim().length === 0 && !inFence) continue;
1631
+ kept.push(line);
1632
+ }
1633
+ const bounded = [];
1634
+ let length = 0;
1635
+ for (const line of kept) {
1636
+ const nextLength = length + (bounded.length > 0 ? 1 : 0) + line.length;
1637
+ if (nextLength > 1200) break;
1638
+ bounded.push(line);
1639
+ length = nextLength;
1640
+ }
1641
+ return bounded.join("\n").trim();
1642
+ }
1643
+ function sanitizeModelSection(section, evidence) {
1644
+ const body = groundedModelBody(section.body, evidence);
1645
+ if (body.length < 40) return null;
1646
+ const title = section.title.replace(/\s+/g, " ").trim();
1647
+ return title.length > 0 ? { title, body } : null;
1648
+ }
1649
+ function stabilizeGeneratedOutput(input, base, onModelSectionRetained, groundModelOutput = false) {
1650
+ const existing = input.existingClioMd;
1651
+ const conventions = [];
1652
+ for (const convention of existing?.conventions ?? []) pushUnique(conventions, convention);
1653
+ for (const convention of inferConventions(input.cwd, input.projectType, input.siblingFiles)) {
1654
+ pushUnique(conventions, convention);
1655
+ }
1656
+ const invariants = [];
1657
+ for (const invariant of existing?.invariants ?? []) pushUnique(invariants, invariant);
1658
+ for (const invariant of inferInvariants(input.siblingFiles)) pushUnique(invariants, invariant);
1659
+ const verification = verificationSection(input.cwd);
1660
+ const inferredSections = inferHeuristicSections(input);
1661
+ const existingSections = existing?.sections ?? [];
1662
+ const ordinarySections = [];
1663
+ const modelSections = /* @__PURE__ */ new Set();
1664
+ const seenSectionTitles = /* @__PURE__ */ new Set();
1665
+ const addSection = (section) => {
1666
+ if (VERIFICATION_SECTION_RE.test(section.title)) return false;
1667
+ const titleKey = section.title.replace(/\s+/g, " ").trim().toLowerCase();
1668
+ if (seenSectionTitles.has(titleKey)) return false;
1669
+ seenSectionTitles.add(titleKey);
1670
+ ordinarySections.push(section);
1671
+ return true;
1672
+ };
1673
+ for (const section of existing ? [...existingSections, ...inferredSections] : inferredSections) addSection(section);
1674
+ const groundingCorpus = groundModelOutput ? createModelGroundingCorpus(input) : null;
1675
+ for (const section of base.sections ?? []) {
1676
+ const sanitized = groundingCorpus ? sanitizeModelSection(section, groundingCorpus) : section;
1677
+ if (sanitized && addSection(sanitized)) modelSections.add(sanitized);
1678
+ }
1679
+ const ordinaryLimit = verification ? 6 : 7;
1680
+ const retainedOrdinarySections = ordinarySections.slice(0, ordinaryLimit);
1681
+ for (const section of retainedOrdinarySections) {
1682
+ if (modelSections.has(section)) onModelSectionRetained?.();
1683
+ }
1684
+ return {
1685
+ ...base,
1686
+ projectName: existing?.projectName ?? projectName(input.cwd),
1687
+ identity: existing?.identity ?? stabilizedIdentity(input, base.identity),
1688
+ conventions: conventions.slice(0, 6),
1689
+ invariants: invariants.slice(0, 3),
1690
+ sections: [...retainedOrdinarySections, ...verification ? [verification] : []]
1691
+ };
1692
+ }
1693
+ function codewikiSections(codewiki) {
1694
+ const sections = [];
1695
+ const entryPoints = codewikiEntryPoints(codewiki, 8);
1696
+ const indexedCount = indexedSourceFileCount(codewiki);
1697
+ if (entryPoints.length > 0) {
1698
+ sections.push({
1699
+ title: "Context retrieval",
1700
+ body: [
1701
+ `The codewiki currently indexes ${indexedCount} source file${indexedCount === 1 ? "" : "s"}.`,
1702
+ `Start orientation with these indexed entry points: ${entryPoints.map((entry) => `\`${entry}\``).join(", ")}.`,
1703
+ "Use `code_nav` (modes: symbol, path, entries, outline, deps, dependents, wiki) before broad reads when the task is navigational."
1704
+ ].join(" ")
1705
+ });
1706
+ }
1707
+ return sections;
1708
+ }
1709
+ function inferHeuristicSections(input) {
1710
+ const sections = [...codewikiSections(input.codewiki)];
1711
+ const invariants = inferInvariants(input.siblingFiles);
1712
+ if (invariants.length > 0) {
1713
+ sections.push({
1714
+ title: "Architecture boundaries",
1715
+ body: invariants.map((item) => `- ${item}`).join("\n")
1716
+ });
1717
+ }
1718
+ if (input.adoption.sources.length > 0) {
1719
+ const sourceNames = input.adoption.sources.map((source) => `\`${source.displayPath}\``).slice(0, 8).join(", ");
1720
+ sections.push({
1721
+ title: "Agent context interop",
1722
+ body: [
1723
+ `Bootstrap scanned ${input.adoption.sources.length} sibling context source${input.adoption.sources.length === 1 ? "" : "s"}: ${sourceNames}${input.adoption.sources.length > 8 ? ", ..." : ""}.`,
1724
+ "Run `clio-coder context init --adopt` to refresh the managed provenance section when those sources change."
1725
+ ].join(" ")
1726
+ });
1727
+ }
1728
+ return sections.slice(0, 6);
1729
+ }
1730
+ function heuristicBootstrapOutputSync(input) {
1731
+ return stabilizeGeneratedOutput(input, {
1732
+ projectName: projectName(input.cwd),
1733
+ identity: resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles).text,
1734
+ conventions: inferConventions(input.cwd, input.projectType, input.siblingFiles),
1735
+ invariants: inferInvariants(input.siblingFiles),
1736
+ sections: inferHeuristicSections(input)
1737
+ });
1738
+ }
1739
+ var heuristicBootstrapOutput = heuristicBootstrapOutputSync;
1740
+ function bootstrapOutputFromParsed(parsed) {
1741
+ const out3 = {
1742
+ projectName: parsed.projectName,
1743
+ identity: parsed.identity,
1744
+ conventions: [...parsed.conventions],
1745
+ invariants: [...parsed.invariants]
1746
+ };
1747
+ if (parsed.sections.length > 0) {
1748
+ out3.sections = parsed.sections.map((section) => ({ title: section.title, body: section.body }));
1749
+ }
1750
+ if (parsed.importedAgentContext) out3.importedAgentContext = parsed.importedAgentContext;
1751
+ return out3;
1752
+ }
1753
+ function existingClioMdBootstrapOutput(cwd) {
1754
+ const parsed = tryReadClioMd(cwd);
1755
+ if (!parsed?.ok) return null;
1756
+ return bootstrapOutputFromParsed(parsed.value);
1757
+ }
1758
+ function fallbackBootstrapOutput(input) {
1759
+ const existing = input.existingClioMd ? bootstrapOutputFromParsed(input.existingClioMd) : existingClioMdBootstrapOutput(input.cwd);
1760
+ if (existing) return { mode: "existing", output: existing };
1761
+ return { mode: "heuristic", output: heuristicBootstrapOutputSync(input) };
1762
+ }
1763
+ function readExistingClioMdText(cwd) {
1764
+ try {
1765
+ return readFileSync4(join4(cwd, "CLIO-CODER.md"), "utf8");
1766
+ } catch {
1767
+ return null;
1768
+ }
1769
+ }
1770
+ function replaceImportedAgentContext(output, importedAgentContext) {
1771
+ const { importedAgentContext: _old, ...rest } = output;
1772
+ if (importedAgentContext.length === 0) return rest;
1773
+ return { ...rest, importedAgentContext };
1774
+ }
1775
+ function loadBootstrapSiblingFiles(adoption) {
1776
+ return adoption.sources.map((source) => ({
1777
+ source: source.scope,
1778
+ path: source.path,
1779
+ content: source.content
1780
+ }));
1781
+ }
1782
+ function gitStatus(cwd) {
1783
+ try {
1784
+ return execFileSync("git", ["status", "--short"], {
1785
+ cwd,
1786
+ encoding: "utf8",
1787
+ stdio: ["ignore", "pipe", "ignore"]
1788
+ });
1789
+ } catch {
1790
+ return "";
1791
+ }
1792
+ }
1793
+ function countStatusLines(status) {
1794
+ return status.split("\n").map((line) => line.trim()).filter(Boolean).length;
1795
+ }
1796
+ function basenameList(paths) {
1797
+ const names = paths.map((path) => parse(path).base).sort((a, b) => a.localeCompare(b));
1798
+ if (names.length === 0) return "";
1799
+ if (names.length <= 3) return names.join(", ");
1800
+ return `${names.slice(0, 3).join(", ")} +${names.length - 3}`;
1801
+ }
1802
+ function formatAdoptionLine(summary) {
1803
+ const adoption = summary.adoption;
1804
+ const shouldRender = summary.action === "previewed" || adoption.mode === "adopt" || adoption.globalSourceCount > 0 || adoption.conflictCount > 0 || adoption.rejectedCount > 0;
1805
+ if (!shouldRender) return null;
1806
+ const global = adoption.includeGlobal ? `${adoption.globalSourceCount} global` : "global off";
1807
+ const verb = adoption.mode === "adopt" ? "imported" : "scanned";
1808
+ return ` adoption ${verb} ${adoption.sourceCount} source${adoption.sourceCount === 1 ? "" : "s"} (${adoption.projectSourceCount} project, ${global}); ${adoption.importedRuleCount} candidate rule${adoption.importedRuleCount === 1 ? "" : "s"}; ${adoption.conflictCount} conflict${adoption.conflictCount === 1 ? "" : "s"}; ${adoption.rejectedCount} rejected`;
1809
+ }
1810
+ function formatBootstrapSummary(summary) {
1811
+ const contextLine = summary.contextFileCount > 0 ? `folded ${summary.contextFileCount} context file${summary.contextFileCount === 1 ? "" : "s"} (${basenameList(summary.contextFileNames)})` : "no sibling context files found";
1812
+ const dirtyLine = summary.dirtyFiles === 0 ? "workspace clean" : `workspace has ${summary.dirtyFiles} dirty file${summary.dirtyFiles === 1 ? "" : "s"}`;
1813
+ if (summary.action === "previewed") {
1814
+ const adoptionLine2 = formatAdoptionLine(summary);
1815
+ return [
1816
+ "clio-coder context init preview",
1817
+ ` ${contextLine}; codewiki would index ${summary.codewikiEntries} entr${summary.codewikiEntries === 1 ? "y" : "ies"}; ${dirtyLine}; no files written`,
1818
+ ...adoptionLine2 ? [adoptionLine2] : [],
1819
+ ""
1820
+ ].join("\n");
1821
+ }
1822
+ const adoptionLine = formatAdoptionLine(summary);
1823
+ const proposalLine = summary.proposalPath ? ` proposal written ${summary.proposalPath}` : null;
1824
+ if (summary.action === "preserved") {
1825
+ return [
1826
+ "clio-coder context init preserved CLIO-CODER.md",
1827
+ ` ${contextLine}; codewiki rebuilt ${summary.codewikiEntries} entr${summary.codewikiEntries === 1 ? "y" : "ies"}; state refreshed; ${dirtyLine}`,
1828
+ " CLIO-CODER.md is treated as human-owned. Use --apply to replace it with a generated draft, --propose to write an ignored proposal, or --adopt to refresh only imported agent context.",
1829
+ ...adoptionLine ? [adoptionLine] : [],
1830
+ ""
1831
+ ].join("\n");
1832
+ }
1833
+ if (summary.action === "proposed") {
1834
+ return [
1835
+ "clio-coder context init proposed CLIO-CODER.md",
1836
+ ` ${contextLine}; codewiki rebuilt ${summary.codewikiEntries} entr${summary.codewikiEntries === 1 ? "y" : "ies"}; state refreshed; ${dirtyLine}`,
1837
+ ...proposalLine ? [proposalLine] : [],
1838
+ " CLIO-CODER.md was not changed. Re-run with --apply only after reviewing the proposal.",
1839
+ ...adoptionLine ? [adoptionLine] : [],
1840
+ ""
1841
+ ].join("\n");
1842
+ }
1843
+ return [
1844
+ `clio-coder context init ${summary.action} CLIO-CODER.md`,
1845
+ ` ${contextLine}; codewiki rebuilt ${summary.codewikiEntries} entr${summary.codewikiEntries === 1 ? "y" : "ies"}; state refreshed; ${dirtyLine}`,
1846
+ " git policy: .clio-coder/ stays ignored by default; CLIO-CODER.md stays versioned and human-owned. Force-add .clio-coder assets only when you explicitly intend to share them.",
1847
+ ...proposalLine ? [proposalLine] : [],
1848
+ ...adoptionLine ? [adoptionLine] : [],
1849
+ ""
1850
+ ].join("\n");
1851
+ }
1852
+ var CLIO_GITIGNORE_LINE = ".clio-coder/";
1853
+ var CLIO_GITIGNORE_DYNAMIC_LINES = /* @__PURE__ */ new Set([
1854
+ ".clio-coder/codewiki.json",
1855
+ ".clio-coder/state.json",
1856
+ ".clio-coder/handoffs/"
1857
+ ]);
1858
+ function normalizedGitignoreLines(content) {
1859
+ return content.split(/\r?\n/).map((line) => line.trim());
1860
+ }
1861
+ function hasBlanketClioIgnore(content) {
1862
+ return normalizedGitignoreLines(content).some(isBlanketClioIgnoreLine);
1863
+ }
1864
+ function isBlanketClioIgnoreLine(line) {
1865
+ if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) return false;
1866
+ return /^(?:\/|\*\*\/)?\.clio-coder(?:\/|\/\*|\/\*\*)?$/.test(line);
1867
+ }
1868
+ function hasDynamicOnlyClioIgnore(content) {
1869
+ const lines = new Set(normalizedGitignoreLines(content));
1870
+ for (const line of CLIO_GITIGNORE_DYNAMIC_LINES) {
1871
+ if (!lines.has(line)) return false;
1872
+ }
1873
+ return true;
1874
+ }
1875
+ function migrateClioGitignore(content) {
1876
+ const lines = content.split(/\r?\n/);
1877
+ const kept = lines.filter((line) => {
1878
+ const trimmed = line.trim();
1879
+ return !isBlanketClioIgnoreLine(trimmed) && !CLIO_GITIGNORE_DYNAMIC_LINES.has(trimmed);
1880
+ });
1881
+ while (kept.length > 0 && kept[kept.length - 1]?.trim() === "") kept.pop();
1882
+ const prefix = kept.length > 0 ? [...kept, ""] : [];
1883
+ return `${[...prefix, CLIO_GITIGNORE_LINE].join("\n")}
1884
+ `;
1885
+ }
1886
+ async function ensureGitignore(cwd, input) {
1887
+ const gitignorePath = join4(cwd, ".gitignore");
1888
+ let content = "";
1889
+ try {
1890
+ content = readFileSync4(gitignorePath, "utf8");
1891
+ } catch {
1892
+ content = "";
1893
+ }
1894
+ if (hasBlanketClioIgnore(content)) {
1895
+ if (hasDynamicOnlyClioIgnore(content)) writeFileSync(gitignorePath, migrateClioGitignore(content), "utf8");
1896
+ return;
1897
+ }
1898
+ if (hasDynamicOnlyClioIgnore(content)) {
1899
+ writeFileSync(gitignorePath, migrateClioGitignore(content), "utf8");
1900
+ return;
1901
+ }
1902
+ const confirmed = input.confirmGitignore ? await input.confirmGitignore() : false;
1903
+ if (!confirmed) {
1904
+ warn(
1905
+ input.io,
1906
+ `clio-coder context init: .gitignore does not ignore .clio-coder/; local context, skills, agents, and handoffs may leak into commits.
1907
+ rerun with --yes to append '${CLIO_GITIGNORE_LINE}' to .gitignore without prompting, or add that line yourself.
1908
+ `
1909
+ );
1910
+ return;
1911
+ }
1912
+ writeFileSync(gitignorePath, migrateClioGitignore(content), "utf8");
1913
+ }
1914
+ function serializeBootstrapOutput(output) {
1915
+ return serializeClioMd({ ...output, fingerprint: null });
1916
+ }
1917
+ function writeClioMdFile(cwd, output) {
1918
+ const clioMdPath = join4(cwd, "CLIO-CODER.md");
1919
+ mkdirSync(dirname2(clioMdPath), { recursive: true });
1920
+ const serialized = serializeBootstrapOutput(output);
1921
+ const parsed = parseClioMd(serialized);
1922
+ if (!parsed.ok) throw new Error(`bootstrap produced invalid CLIO-CODER.md: ${parsed.errors.join("; ")}`);
1923
+ writeFileSync(clioMdPath, serialized, "utf8");
1924
+ return clioMdPath;
1925
+ }
1926
+ function timestampForPath(now) {
1927
+ return now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
1928
+ }
1929
+ function writeClioMdProposal(cwd, now, output) {
1930
+ const dir = join4(cwd, ".clio-coder", "proposals");
1931
+ mkdirSync(dir, { recursive: true });
1932
+ const proposalPath = join4(dir, `CLIO-CODER-${timestampForPath(now)}.md`);
1933
+ writeFileSync(proposalPath, serializeBootstrapOutput(output), "utf8");
1934
+ return proposalPath;
1935
+ }
1936
+ function boundedStateString(value, maxChars) {
1937
+ if (value === void 0) return void 0;
1938
+ const bounded = value.replace(/\s+/g, " ").trim().slice(0, maxChars);
1939
+ return bounded.length > 0 ? bounded : void 0;
1940
+ }
1941
+ function safeStateCounter(value) {
1942
+ return value !== void 0 && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1943
+ }
1944
+ function durableGenerationTelemetry(telemetry) {
1945
+ const state = {
1946
+ mode: telemetry.mode,
1947
+ parserOutcome: telemetry.parserOutcome
1948
+ };
1949
+ const fallbackReason = boundedStateString(telemetry.fallbackReason, 500);
1950
+ if (fallbackReason) state.fallbackReason = fallbackReason;
1951
+ const run = telemetry.run;
1952
+ if (!run) return state;
1953
+ state.structuredOutputMode = run.structuredOutputMode;
1954
+ for (const [key, value] of [
1955
+ ["runId", run.runId],
1956
+ ["targetId", run.targetId],
1957
+ ["wireModelId", run.wireModelId],
1958
+ ["runtimeId", run.runtimeId],
1959
+ ["runtimeKind", run.runtimeKind],
1960
+ ["thinkingLevel", run.thinkingLevel]
1961
+ ]) {
1962
+ const bounded = boundedStateString(value, 512);
1963
+ if (bounded) state[key] = bounded;
1964
+ }
1965
+ for (const [key, value] of [
1966
+ ["tokenCount", run.tokens?.total],
1967
+ ["toolCalls", run.toolCalls],
1968
+ ["durationMs", run.durationMs],
1969
+ ["promptBytes", run.promptBytes],
1970
+ ["outputBytes", run.outputBytes]
1971
+ ]) {
1972
+ const counter = safeStateCounter(value);
1973
+ if (counter !== void 0) state[key] = counter;
1974
+ }
1975
+ return state;
1976
+ }
1977
+ function writeProjectState(cwd, projectType, now, indexedAt, adoption, recordAdoption, codewikiVersion, generation, generated) {
1978
+ const finalFingerprint = computeFingerprint(cwd);
1979
+ const statePath2 = statePath(cwd);
1980
+ const prev = readClioState(cwd);
1981
+ const lastBootstrap = generated ? durableGenerationTelemetry(generation) : prev?.lastBootstrap ?? durableGenerationTelemetry(generation);
1982
+ const contextSources = recordAdoption ? adoption.sourceSnapshots : prev?.contextSources;
1983
+ const contextSourceHash = contextSources ? adoptionSnapshotsHash(contextSources) : void 0;
1984
+ writeClioState(cwd, {
1985
+ version: 1,
1986
+ projectType,
1987
+ fingerprint: finalFingerprint,
1988
+ codewikiVersion,
1989
+ lastInitAt: now.toISOString(),
1990
+ lastSessionAt: now.toISOString(),
1991
+ lastIndexedAt: indexedAt,
1992
+ lastBootstrap,
1993
+ ...contextSources ? { contextSources } : {},
1994
+ ...contextSourceHash ? { contextSourceHash } : {}
1995
+ });
1996
+ return statePath2;
1997
+ }
1998
+ function persistCodewikiForGeneration(cwd, projectType, indexedAt, codewiki) {
1999
+ writeCodewiki(cwd, codewiki);
2000
+ const prev = readClioState(cwd);
2001
+ writeClioState(cwd, {
2002
+ version: 1,
2003
+ projectType,
2004
+ fingerprint: computeFingerprint(cwd, codewiki),
2005
+ codewikiVersion: codewiki.version,
2006
+ ...prev?.contextSources ? { contextSources: prev.contextSources } : {},
2007
+ ...prev?.contextSourceHash ? { contextSourceHash: prev.contextSourceHash } : {},
2008
+ ...prev?.lastInitAt ? { lastInitAt: prev.lastInitAt } : {},
2009
+ ...prev?.lastBootstrap ? { lastBootstrap: prev.lastBootstrap } : {},
2010
+ lastSessionAt: prev?.lastSessionAt ?? indexedAt,
2011
+ lastIndexedAt: indexedAt
2012
+ });
2013
+ }
2014
+ function summarizeAdoption(adoption, mode) {
2015
+ let projectSourceCount = 0;
2016
+ let globalSourceCount = 0;
2017
+ for (const source of adoption.sources) {
2018
+ if (source.scope === "global") globalSourceCount += 1;
2019
+ else projectSourceCount += 1;
2020
+ }
2021
+ return {
2022
+ mode,
2023
+ sourceCount: adoption.sources.length,
2024
+ projectSourceCount,
2025
+ globalSourceCount,
2026
+ importedRuleCount: adoption.importedRules.length,
2027
+ conflictCount: adoption.conflicts.length,
2028
+ rejectedCount: adoption.rejected.length,
2029
+ includeGlobal: adoption.includeGlobal
2030
+ };
2031
+ }
2032
+ function codewikiEntryPoints(codewiki, limit = 6) {
2033
+ const fileById = new Map(codewiki.files.map((file) => [file.id, file]));
2034
+ const tagged = codewiki.files.filter((file) => file.lang !== "config" && file.role === "entry").map((file) => file.path).sort((a, b) => a.localeCompare(b));
2035
+ if (tagged.length >= limit) return tagged.slice(0, limit);
2036
+ const inDegree = /* @__PURE__ */ new Map();
2037
+ for (const edge of codewiki.edges) {
2038
+ if (!("toFileId" in edge)) continue;
2039
+ inDegree.set(edge.toFileId, (inDegree.get(edge.toFileId) ?? 0) + 1);
2040
+ }
2041
+ const ranked = [...inDegree.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([fileId]) => fileById.get(fileId)?.path).filter((path) => typeof path === "string").filter((path) => !tagged.includes(path));
2042
+ return [...tagged, ...ranked].slice(0, limit);
2043
+ }
2044
+ async function runBootstrap(input = {}) {
2045
+ const cwd = input.cwd ?? process.cwd();
2046
+ const projectType = detectProjectType(cwd);
2047
+ progress(input, {
2048
+ phase: "scan",
2049
+ status: "started",
2050
+ message: "scanning project and sibling agent context",
2051
+ detail: projectType
2052
+ });
2053
+ const adoption = scanAgentConfigs({
2054
+ cwd,
2055
+ ...input.homeDir ? { homeDir: input.homeDir } : {},
2056
+ includeGlobal: input.includeGlobalImports === true
2057
+ });
2058
+ const siblingFiles = loadBootstrapSiblingFiles(adoption);
2059
+ progress(input, {
2060
+ phase: "scan",
2061
+ status: "completed",
2062
+ message: siblingFiles.length === 0 ? "no sibling context files found" : `folded ${siblingFiles.length} sibling context file${siblingFiles.length === 1 ? "" : "s"}`,
2063
+ current: siblingFiles.length,
2064
+ total: siblingFiles.length
2065
+ });
2066
+ const now = input.now?.() ?? /* @__PURE__ */ new Date();
2067
+ const indexedAt = now.toISOString();
2068
+ progress(input, { phase: "codewiki", status: "started", message: "building codewiki index" });
2069
+ const codewiki = await buildCodewiki({ cwd, language: projectType, generatedAt: indexedAt });
2070
+ const codewikiEntryCount = indexedSourceFileCount(codewiki);
2071
+ progress(input, {
2072
+ phase: "codewiki",
2073
+ status: "completed",
2074
+ message: `indexed ${codewikiEntryCount} source file${codewikiEntryCount === 1 ? "" : "s"}`,
2075
+ current: codewikiEntryCount,
2076
+ total: codewikiEntryCount
2077
+ });
2078
+ if (input.preview !== true) {
2079
+ await ensureGitignore(cwd, input);
2080
+ persistCodewikiForGeneration(cwd, projectType, indexedAt, codewiki);
2081
+ }
2082
+ const hadClioMd = existsSync4(join4(cwd, "CLIO-CODER.md"));
2083
+ const useExistingClioMdAsSource = hadClioMd && input.rewriteClioMd !== true;
2084
+ const existingClioMdText = useExistingClioMdAsSource ? readExistingClioMdText(cwd) : null;
2085
+ const existingClioMd = useExistingClioMdAsSource ? tryReadClioMd(cwd) : null;
2086
+ const existingParsed = existingClioMd?.ok ? existingClioMd.value : void 0;
2087
+ const replaceClioMd = input.applyClioMd === true || input.rewriteClioMd === true;
2088
+ if (input.adopt === true && input.preview !== true && hadClioMd && !existingParsed && !replaceClioMd && input.proposeClioMd !== true) {
2089
+ const detail = existingClioMd && !existingClioMd.ok ? ` (${existingClioMd.error})` : "";
2090
+ throw new Error(
2091
+ `cannot refresh Imported agent context because CLIO-CODER.md is malformed${detail}; use --apply or --rewrite after reviewing the handbook`
2092
+ );
2093
+ }
2094
+ const shouldGenerate = !hadClioMd || replaceClioMd || input.proposeClioMd === true || input.generate !== void 0;
2095
+ let output;
2096
+ let generation = {
2097
+ mode: shouldGenerate ? "heuristic" : existingParsed ? "existing" : "heuristic",
2098
+ parserOutcome: "not-run"
2099
+ };
2100
+ let reportedGeneration;
2101
+ const reportGeneration = (reported) => {
2102
+ if (reportedGeneration === void 0) reportedGeneration = reported;
2103
+ };
2104
+ if (shouldGenerate) {
2105
+ progress(input, {
2106
+ phase: "generate",
2107
+ status: "started",
2108
+ message: input.generate ? "drafting CLIO-CODER.md with the bootstrap agent" : "drafting CLIO-CODER.md with heuristic"
2109
+ });
2110
+ output = await (input.generate ?? heuristicBootstrapOutput)({
2111
+ cwd,
2112
+ expectedProjectName: projectName(cwd),
2113
+ projectType,
2114
+ siblingFiles,
2115
+ adoption,
2116
+ codewiki,
2117
+ ...existingParsed ? { existingClioMd: existingParsed } : {},
2118
+ ...existingClioMdText ? { existingClioMdText } : {},
2119
+ ...input.onProgress ? { progress: input.onProgress } : {},
2120
+ reportGeneration
2121
+ });
2122
+ generation = reportedGeneration ?? generation;
2123
+ let retainedModelSections = 0;
2124
+ output = stabilizeGeneratedOutput(
2125
+ {
2126
+ cwd,
2127
+ expectedProjectName: projectName(cwd),
2128
+ projectType,
2129
+ siblingFiles,
2130
+ adoption,
2131
+ codewiki,
2132
+ ...existingParsed ? { existingClioMd: existingParsed } : {},
2133
+ ...existingClioMdText ? { existingClioMdText } : {}
2134
+ },
2135
+ output,
2136
+ () => {
2137
+ retainedModelSections += 1;
2138
+ },
2139
+ generation.mode === "model"
2140
+ );
2141
+ if (generation.mode === "model" && retainedModelSections === 0) {
2142
+ generation = {
2143
+ ...generation,
2144
+ mode: "heuristic",
2145
+ fallbackReason: "Scout draft contributed no evidence-grounded custom sections"
2146
+ };
2147
+ }
2148
+ progress(input, {
2149
+ phase: "generate",
2150
+ status: "completed",
2151
+ message: `${output.projectName}: ${output.sections?.length ?? 0} custom section${(output.sections?.length ?? 0) === 1 ? "" : "s"}`,
2152
+ detail: `${generation.mode}; parser=${generation.parserOutcome}`
2153
+ });
2154
+ } else {
2155
+ output = existingParsed ? bootstrapOutputFromParsed(existingParsed) : await heuristicBootstrapOutput({ cwd, projectType, siblingFiles, adoption, codewiki });
2156
+ progress(input, {
2157
+ phase: "generate",
2158
+ status: "completed",
2159
+ message: hadClioMd ? "preserving existing CLIO-CODER.md; no generated rewrite requested" : "using heuristic CLIO-CODER.md draft"
2160
+ });
2161
+ }
2162
+ if (input.adopt === true) {
2163
+ output = replaceImportedAgentContext(output, renderImportedAgentContext(adoption));
2164
+ }
2165
+ const readNames = siblingFiles.map((file) => file.path).sort((a, b) => a.localeCompare(b));
2166
+ const previewStatus = gitStatus(cwd);
2167
+ if (input.preview === true) {
2168
+ progress(input, { phase: "clio-md", status: "completed", message: "preview ready; no files written" });
2169
+ const summary2 = {
2170
+ action: "previewed",
2171
+ contextFileCount: readNames.length,
2172
+ contextFileNames: readNames,
2173
+ codewikiEntries: codewikiEntryCount,
2174
+ dirtyFiles: countStatusLines(previewStatus),
2175
+ adoption: summarizeAdoption(adoption, "preview")
2176
+ };
2177
+ out(input.io, formatBootstrapSummary(summary2));
2178
+ return {
2179
+ clioMdPath: join4(cwd, "CLIO-CODER.md"),
2180
+ statePath: statePath(cwd),
2181
+ siblingFiles,
2182
+ output,
2183
+ projectType,
2184
+ summary: summary2,
2185
+ adoption,
2186
+ telemetry: { generation },
2187
+ preload: measureProjectPreload(cwd)
2188
+ };
2189
+ }
2190
+ let clioMdPath = join4(cwd, "CLIO-CODER.md");
2191
+ let proposalPath;
2192
+ let action = "preserved";
2193
+ progress(input, {
2194
+ phase: "clio-md",
2195
+ status: "started",
2196
+ message: hadClioMd ? "preserving CLIO-CODER.md" : "writing CLIO-CODER.md"
2197
+ });
2198
+ if (!hadClioMd) {
2199
+ clioMdPath = writeClioMdFile(cwd, output);
2200
+ action = "wrote";
2201
+ } else if (replaceClioMd) {
2202
+ clioMdPath = writeClioMdFile(cwd, output);
2203
+ action = "refreshed";
2204
+ } else if (input.adopt === true && existingParsed) {
2205
+ clioMdPath = writeClioMdFile(cwd, output);
2206
+ action = "refreshed";
2207
+ } else if (input.proposeClioMd === true) {
2208
+ proposalPath = writeClioMdProposal(cwd, now, output);
2209
+ action = "proposed";
2210
+ }
2211
+ progress(input, {
2212
+ phase: "clio-md",
2213
+ status: "completed",
2214
+ message: action === "wrote" ? "CLIO-CODER.md written" : action === "refreshed" ? "CLIO-CODER.md refreshed" : action === "proposed" ? "CLIO-CODER.md proposal written" : "CLIO-CODER.md preserved",
2215
+ detail: proposalPath ?? clioMdPath
2216
+ });
2217
+ const adoptionApplied = input.adopt === true && (action === "wrote" || action === "refreshed");
2218
+ progress(input, { phase: "state", status: "started", message: "persisting codewiki and project state" });
2219
+ const statePath2 = writeProjectState(
2220
+ cwd,
2221
+ projectType,
2222
+ now,
2223
+ indexedAt,
2224
+ adoption,
2225
+ adoptionApplied,
2226
+ codewiki.version,
2227
+ generation,
2228
+ shouldGenerate
2229
+ );
2230
+ progress(input, {
2231
+ phase: "state",
2232
+ status: "completed",
2233
+ message: `state updated; ${codewikiEntryCount} codewiki entr${codewikiEntryCount === 1 ? "y" : "ies"}`
2234
+ });
2235
+ const postStatus = gitStatus(cwd);
2236
+ const summary = {
2237
+ action,
2238
+ contextFileCount: readNames.length,
2239
+ contextFileNames: readNames,
2240
+ codewikiEntries: codewikiEntryCount,
2241
+ dirtyFiles: countStatusLines(postStatus),
2242
+ adoption: summarizeAdoption(adoption, adoptionApplied ? "adopt" : "scan"),
2243
+ ...proposalPath ? { proposalPath } : {}
2244
+ };
2245
+ out(input.io, formatBootstrapSummary(summary));
2246
+ const preload = measureProjectPreload(cwd);
2247
+ out(input.io, ` preload: ${preload.label}
2248
+ `);
2249
+ if (preload.mode === "full" && preload.nearLimit) {
2250
+ warn(
2251
+ input.io,
2252
+ ` warning: project context is within 10% of the preload limit (${preload.chars} chars of 8000, ${preload.lines} lines of 220); the next growth may flip it to a synopsis
2253
+ `
2254
+ );
2255
+ }
2256
+ progress(input, {
2257
+ phase: "done",
2258
+ status: "completed",
2259
+ message: `${summary.action} CLIO-CODER.md; ${summary.dirtyFiles} dirty file${summary.dirtyFiles === 1 ? "" : "s"}; preload: ${preload.label}`
2260
+ });
2261
+ return {
2262
+ clioMdPath,
2263
+ statePath: statePath2,
2264
+ siblingFiles,
2265
+ output,
2266
+ projectType,
2267
+ summary,
2268
+ adoption,
2269
+ telemetry: { generation },
2270
+ preload
2271
+ };
2272
+ }
2273
+
2274
+ // src/domains/context/clear.ts
2275
+ import { existsSync as existsSync5, rmSync } from "node:fs";
2276
+ import { join as join5 } from "node:path";
2277
+ var ACCUMULATED_CONTEXT_PATHS = [
2278
+ ".clio-coder/codewiki.json",
2279
+ ".clio-coder/state.json",
2280
+ ".clio-coder/handoffs",
2281
+ ".clio-coder/proposals"
2282
+ ];
2283
+ var PRESERVED_CONTEXT_PATHS = [
2284
+ "CLIO-CODER.md",
2285
+ ".clio-coder/agents",
2286
+ ".clio-coder/skills",
2287
+ ".clio-coder/wiki"
2288
+ ];
2289
+ function out2(io, message) {
2290
+ io?.stdout(message);
2291
+ }
2292
+ function relativeContextPath(cwd, relPath) {
2293
+ return join5(cwd, ...relPath.split("/"));
2294
+ }
2295
+ function removeIfPresent(cwd, relPath, removed) {
2296
+ const absPath = relativeContextPath(cwd, relPath);
2297
+ if (!existsSync5(absPath)) return;
2298
+ rmSync(absPath, { recursive: true, force: true });
2299
+ removed.push(relPath);
2300
+ }
2301
+ async function runContextClear(input = {}) {
2302
+ const cwd = input.cwd ?? process.cwd();
2303
+ const confirmed = await input.confirmContext?.();
2304
+ if (confirmed !== true) {
2305
+ out2(input.io, "clio-coder context reset cancelled; no files removed.\n");
2306
+ return { action: "cancelled", removed: [], preserved: [...PRESERVED_CONTEXT_PATHS] };
2307
+ }
2308
+ const removed = [];
2309
+ for (const relPath of ACCUMULATED_CONTEXT_PATHS) removeIfPresent(cwd, relPath, removed);
2310
+ const preserved = [...PRESERVED_CONTEXT_PATHS];
2311
+ if (input.all === true) {
2312
+ const confirmedAll = await input.confirmAll?.();
2313
+ if (confirmedAll === true) {
2314
+ removeIfPresent(cwd, "CLIO-CODER.md", removed);
2315
+ const index = preserved.indexOf("CLIO-CODER.md");
2316
+ if (index !== -1) preserved.splice(index, 1);
2317
+ }
2318
+ }
2319
+ out2(
2320
+ input.io,
2321
+ [
2322
+ `clio-coder context reset removed ${removed.length === 0 ? "nothing" : removed.join(", ")}`,
2323
+ ` preserved ${preserved.join(", ")}`,
2324
+ ""
2325
+ ].join("\n")
2326
+ );
2327
+ return { action: "cleared", removed, preserved };
2328
+ }
2329
+
2330
+ // src/domains/context/refresh.ts
2331
+ import { writeFileSync as writeFileSync3 } from "node:fs";
2332
+ import { join as join8 } from "node:path";
2333
+
2334
+ // src/domains/context/wiki/generate.ts
2335
+ import {
2336
+ closeSync,
2337
+ copyFileSync,
2338
+ existsSync as existsSync6,
2339
+ mkdirSync as mkdirSync3,
2340
+ mkdtempSync,
2341
+ openSync,
2342
+ readdirSync as readdirSync3,
2343
+ readFileSync as readFileSync6,
2344
+ renameSync,
2345
+ rmSync as rmSync3,
2346
+ statSync as statSync2,
2347
+ writeSync
2348
+ } from "node:fs";
2349
+ import { dirname as dirname4, join as join7 } from "node:path";
2350
+
2351
+ // src/domains/context/wiki/assemble.ts
2352
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
2353
+ import { basename as basename2, dirname as dirname3, join as join6, normalize, posix, relative as relative2 } from "node:path";
2354
+ var SOURCE_CITATION = /`((?:src|tests?|scripts|benchmarks|docs|packages|apps|lib|config|\.github)\/[^`\s:#]+)(?::(\d+)(?:-\d+)?)?(?::[A-Za-z_$][\w$.-]*)?`/g;
2355
+ var INTERNAL_LINK = /\[[^\]]*\]\((?![a-z][a-z\d+.-]*:|\/\/|#)([^)\s]+\.md)(?:#[^)\s]*)?\)/gi;
2356
+ var REPAIR_NOTE = /^<!-- clio:wiki .*-->$/gm;
2357
+ function readText2(filePath) {
2358
+ try {
2359
+ return readFileSync5(filePath, "utf8");
2360
+ } catch {
2361
+ return "";
2362
+ }
2363
+ }
2364
+ function writeText(filePath, text) {
2365
+ mkdirSync2(dirname3(filePath), { recursive: true });
2366
+ writeFileSync2(filePath, text, "utf8");
2367
+ }
2368
+ function stripRepairNotes(body) {
2369
+ return body.replace(REPAIR_NOTE, "").replace(/\n{3,}/g, "\n\n");
2370
+ }
2371
+ function renderRepairNote(issues) {
2372
+ const links = issues.filter((issue) => issue.kind === "link").map((issue) => issue.reference);
2373
+ const citations = issues.filter((issue) => issue.kind === "citation").map((issue) => issue.reference);
2374
+ const parts = [];
2375
+ if (links.length > 0) parts.push(`unresolved links: ${[...new Set(links)].join(", ")}`);
2376
+ if (citations.length > 0) parts.push(`unresolved sources: ${[...new Set(citations)].join(", ")}`);
2377
+ return parts.length === 0 ? "" : `
2378
+ <!-- clio:wiki ${parts.join("; ")} -->
2379
+ `;
2380
+ }
2381
+ function repairPage(dir, sourceRoot, relPath, knownPages) {
2382
+ const filePath = join6(dir, relPath);
2383
+ const original = readText2(filePath);
2384
+ const parsed = readWikiPage({ pagePath: relPath, content: original, sourceRoot });
2385
+ const body = stripRepairNotes(parsed.body);
2386
+ if (body.replace(/^#.*$/gm, "").trim().length === 0) {
2387
+ return { metadata: parsed.metadata, changed: false, issues: [], empty: true };
2388
+ }
2389
+ const issues = [];
2390
+ for (const cited of parsed.unresolvedPaths) {
2391
+ issues.push({ page: relPath, kind: "citation", reference: cited });
2392
+ }
2393
+ for (const match of body.matchAll(SOURCE_CITATION)) {
2394
+ const cited = match[1] ?? "";
2395
+ if (resolveSourcePath(sourceRoot, cited) === null) {
2396
+ issues.push({ page: relPath, kind: "citation", reference: cited });
2397
+ }
2398
+ }
2399
+ for (const match of body.matchAll(INTERNAL_LINK)) {
2400
+ const href = match[1] ?? "";
2401
+ const target = posix.normalize(posix.join(posix.dirname(relPath), href));
2402
+ if (target.startsWith("..") || !knownPages.has(target)) {
2403
+ issues.push({ page: relPath, kind: "link", reference: href });
2404
+ }
2405
+ }
2406
+ const rebuilt = `${renderWikiPage(parsed.metadata, body).trimEnd()}
2407
+ ${renderRepairNote(issues)}`;
2408
+ if (rebuilt !== original) writeText(filePath, rebuilt);
2409
+ return { metadata: parsed.metadata, changed: rebuilt !== original, issues, empty: false };
2410
+ }
2411
+ function linkTo(fromDir, target) {
2412
+ const href = posix.relative(fromDir === "." ? "" : fromDir, target);
2413
+ return href.length > 0 ? href : posix.basename(target);
2414
+ }
2415
+ function escapeCell(value) {
2416
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
2417
+ }
2418
+ function codeList(values, limit) {
2419
+ if (values.length === 0) return "\u2014";
2420
+ const shown = values.slice(0, limit).map((value) => `\`${escapeCell(value)}\``);
2421
+ return values.length > limit ? `${shown.join(", ")}, \u2026` : shown.join(", ");
2422
+ }
2423
+ function renderQuickstart(sourceRoot, plan, pages) {
2424
+ const content = pages.filter((page) => page.path !== WIKI_QUICKSTART);
2425
+ const lines = [`# ${basename2(sourceRoot)} wiki`, ""];
2426
+ if (plan.overview.trim().length > 0) lines.push(plan.overview.trim(), "");
2427
+ lines.push(
2428
+ `This wiki is generated by \`clio-coder context wiki\` from ${content.length} page${content.length === 1 ? "" : "s"}. Each page opens with front matter naming the sources, symbols, and tests it documents.`,
2429
+ "",
2430
+ "## Pages",
2431
+ ""
2432
+ );
2433
+ let currentSection = null;
2434
+ for (const page of content) {
2435
+ const section = posix.dirname(page.path);
2436
+ if (section !== "." && section !== currentSection) {
2437
+ lines.push(`- **${section}/**`);
2438
+ currentSection = section;
2439
+ }
2440
+ const indent = section === "." ? "" : " ";
2441
+ const summary = page.metadata.summary.length > 0 ? ` \u2014 ${page.metadata.summary}` : "";
2442
+ lines.push(`${indent}- [${page.metadata.title}](${page.path})${summary}`);
2443
+ }
2444
+ const routable = content.filter(
2445
+ (page) => page.metadata.sources.length > 0 || page.metadata.symbols.length > 0 || page.metadata.tests.length > 0
2446
+ );
2447
+ if (routable.length > 0) {
2448
+ lines.push(
2449
+ "",
2450
+ "## Task routing",
2451
+ "",
2452
+ "| Area | Page | Sources | Symbols | Tests | Validate |",
2453
+ "| --- | --- | --- | --- | --- | --- |"
2454
+ );
2455
+ for (const page of routable) {
2456
+ lines.push(
2457
+ `| ${escapeCell(page.metadata.title)} | [${escapeCell(page.metadata.title)}](${page.path}) | ${codeList(page.metadata.sources, 3)} | ${codeList(page.metadata.symbols, 3)} | ${codeList(page.metadata.tests, 2)} | ${codeList(page.metadata.validate, 1)} |`
2458
+ );
2459
+ }
2460
+ }
2461
+ return `${lines.join("\n")}
2462
+ `;
2463
+ }
2464
+ function renderIndex(dir, pages, sections) {
2465
+ const title = dir === "." ? "Wiki" : dir.split("/").join(" / ");
2466
+ const lines = [`# ${title}`, ""];
2467
+ if (pages.length > 0) {
2468
+ for (const page of pages) {
2469
+ const summary = page.metadata.summary.length > 0 ? ` \u2014 ${page.metadata.summary}` : "";
2470
+ lines.push(`- [${page.metadata.title}](${linkTo(dir, page.path)})${summary}`);
2471
+ }
2472
+ lines.push("");
2473
+ }
2474
+ if (sections.length > 0) {
2475
+ lines.push("## Sections", "");
2476
+ for (const section of sections)
2477
+ lines.push(`- [${posix.basename(section)}/](${linkTo(dir, `${section}/${WIKI_INDEX}`)})`);
2478
+ lines.push("");
2479
+ }
2480
+ return `${lines.join("\n").trimEnd()}
2481
+ `;
2482
+ }
2483
+ function assembleWikiTree(input) {
2484
+ const { dir, sourceRoot } = input;
2485
+ const authored = wikiMarkdownFilesInDir(dir).filter((relPath) => !isGeneratedWikiFile(relPath));
2486
+ const knownPages = /* @__PURE__ */ new Set([...authored, WIKI_QUICKSTART]);
2487
+ const assembled = [];
2488
+ const issues = [];
2489
+ const dropped = [];
2490
+ let repaired = 0;
2491
+ for (const relPath of authored) {
2492
+ const result = repairPage(dir, sourceRoot, relPath, knownPages);
2493
+ if (result.empty) {
2494
+ rmSync2(join6(dir, relPath), { force: true });
2495
+ dropped.push(relPath);
2496
+ continue;
2497
+ }
2498
+ if (result.changed) repaired += 1;
2499
+ issues.push(...result.issues);
2500
+ assembled.push({ path: relPath, metadata: result.metadata });
2501
+ }
2502
+ writeText(join6(dir, WIKI_QUICKSTART), renderQuickstart(sourceRoot, input.plan, assembled));
2503
+ const directories = /* @__PURE__ */ new Set(["."]);
2504
+ for (const page of assembled) {
2505
+ let section = posix.dirname(page.path);
2506
+ while (section !== "." && section.length > 0) {
2507
+ directories.add(section);
2508
+ section = posix.dirname(section);
2509
+ }
2510
+ }
2511
+ for (const directory of directories) {
2512
+ const pagesHere = assembled.filter((page) => posix.dirname(page.path) === directory);
2513
+ const sectionsHere = [...directories].filter((candidate) => candidate !== "." && posix.dirname(candidate) === directory).sort();
2514
+ const indexPath = directory === "." ? WIKI_INDEX : `${directory}/${WIKI_INDEX}`;
2515
+ writeText(join6(dir, indexPath), renderIndex(directory, pagesHere, sectionsHere));
2516
+ }
2517
+ for (const relPath of wikiMarkdownFilesInDir(dir)) {
2518
+ if (!isGeneratedWikiFile(relPath) || relPath === WIKI_QUICKSTART) continue;
2519
+ const directory = posix.dirname(relPath);
2520
+ if (!directories.has(directory)) rmSync2(join6(dir, relPath), { force: true });
2521
+ }
2522
+ return { pages: listWikiPagesInDir(dir), repaired, dropped, issues };
2523
+ }
2524
+ function pageSourceIndex(dir, sourceRoot) {
2525
+ const index = /* @__PURE__ */ new Map();
2526
+ for (const relPath of wikiMarkdownFilesInDir(dir)) {
2527
+ if (isGeneratedWikiFile(relPath)) continue;
2528
+ const { metadata } = readWikiPage({ pagePath: relPath, content: readText2(join6(dir, relPath)), sourceRoot });
2529
+ index.set(relPath, [...metadata.sources, ...metadata.tests]);
2530
+ }
2531
+ return index;
2532
+ }
2533
+ function normalizeRepoPath(sourceRoot, candidate) {
2534
+ const resolved = normalize(join6(sourceRoot, candidate));
2535
+ const rel = relative2(sourceRoot, resolved);
2536
+ return rel.split("\\").join("/");
2537
+ }
2538
+
2539
+ // src/domains/context/wiki/plan.ts
2540
+ var WIKI_DEPTH_STRATEGY = {
2541
+ simple: { areaDepth: 1, areaShare: 0.08, minAreaLines: 400 },
2542
+ medium: { areaDepth: 2, areaShare: 0.03, minAreaLines: 250 },
2543
+ detailed: { areaDepth: 3, areaShare: 8e-3, minAreaLines: 150 }
2544
+ };
2545
+ var MAX_PAGE_SOURCES = 8;
2546
+ function areaForPath(path, maxDepth) {
2547
+ const directories = path.split("/").filter(Boolean).slice(0, -1);
2548
+ if (directories.length === 0) return ".";
2549
+ return directories.slice(0, Math.max(1, maxDepth)).join("/");
2550
+ }
2551
+ function classifyDepth(sourceFiles, sourceLines) {
2552
+ if (sourceFiles <= 150 && sourceLines <= 3e4) return "simple";
2553
+ if (sourceFiles <= 800 && sourceLines <= 15e4) return "medium";
2554
+ return "detailed";
2555
+ }
2556
+ function slugSegment(segment) {
2557
+ const slug = segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2558
+ return slug.length > 0 ? slug : "section";
2559
+ }
2560
+ function areaSegments(area) {
2561
+ const parts = area.split("/").filter((part) => part.length > 0 && part !== ".");
2562
+ if (parts.length === 0) return ["root"];
2563
+ if (parts[0] !== "src") return parts;
2564
+ return parts.length === 1 ? ["source"] : parts.slice(1);
2565
+ }
2566
+ function pagePathForArea(area) {
2567
+ return `${areaSegments(area).map(slugSegment).join("/")}.md`;
2568
+ }
2569
+ function titleForArea(area) {
2570
+ const name2 = areaSegments(area).join(" ").replace(/[-_]+/g, " ").trim();
2571
+ return name2.length > 0 ? name2.charAt(0).toUpperCase() + name2.slice(1) : "Overview";
2572
+ }
2573
+ function rankedSources(files) {
2574
+ return [...files].sort((a, b) => {
2575
+ const roleRank = (file) => file.role === "entry" ? 0 : file.role === "test" ? 2 : 1;
2576
+ return roleRank(a) - roleRank(b) || b.loc - a.loc || a.path.localeCompare(b.path);
2577
+ }).slice(0, MAX_PAGE_SOURCES).map((file) => file.path);
2578
+ }
2579
+ function collectAreas(source, areaDepth) {
2580
+ const byKey = /* @__PURE__ */ new Map();
2581
+ for (const file of source) {
2582
+ const key = areaForPath(file.path, areaDepth);
2583
+ const area = byKey.get(key) ?? { key, files: [], lines: 0 };
2584
+ area.files.push(file);
2585
+ area.lines += Math.max(0, file.loc);
2586
+ byKey.set(key, area);
2587
+ }
2588
+ return [...byKey.values()].sort((a, b) => b.lines - a.lines || a.key.localeCompare(b.key));
2589
+ }
2590
+ function overviewPage(source) {
2591
+ const entries = source.filter((file) => file.role === "entry");
2592
+ return {
2593
+ path: "architecture.md",
2594
+ title: "Architecture",
2595
+ intent: "Explain what this repository is, its top-level composition, how a request or command flows through it, and where the boundaries between its major areas are.",
2596
+ sources: rankedSources(entries.length > 0 ? entries : source),
2597
+ status: "pending",
2598
+ attempts: 0
2599
+ };
2600
+ }
2601
+ function buildCandidatePlan(codewiki, depth) {
2602
+ const source = codewiki.files.filter((file) => file.lang !== "config");
2603
+ const totalLines = source.reduce((total, file) => total + Math.max(0, file.loc), 0);
2604
+ const { areaDepth, areaShare, minAreaLines } = WIKI_DEPTH_STRATEGY[depth];
2605
+ const threshold = Math.max(minAreaLines, Math.floor(totalLines * areaShare));
2606
+ const areas = collectAreas(source, areaDepth);
2607
+ const included = areas.filter((area) => area.lines >= threshold);
2608
+ const selected = included.length > 0 ? included : areas.slice(0, 1);
2609
+ const selectedKeys = new Set(selected.map((area) => area.key));
2610
+ const extras = /* @__PURE__ */ new Map();
2611
+ for (const area of areas) {
2612
+ if (selectedKeys.has(area.key)) continue;
2613
+ const ancestor = selected.find((candidate) => area.key.startsWith(`${candidate.key}/`));
2614
+ const host = ancestor?.key ?? selected[0]?.key;
2615
+ if (host === void 0) continue;
2616
+ extras.set(host, [...extras.get(host) ?? [], ...area.files]);
2617
+ }
2618
+ const pages = selected.map((area) => {
2619
+ const files = [...area.files, ...extras.get(area.key) ?? []];
2620
+ return {
2621
+ path: pagePathForArea(area.key),
2622
+ title: titleForArea(area.key),
2623
+ intent: `Document ${area.key} (${area.files.length} indexed files, ${area.lines} lines): what it owns, its entry points and important symbols, the state and lifecycle invariants it enforces, an upstream caller and a downstream dependency, the focused tests that prove its behavior, and what to watch when editing it.`,
2624
+ sources: rankedSources(files),
2625
+ status: "pending",
2626
+ attempts: 0
2627
+ };
2628
+ });
2629
+ return dedupePagePaths({
2630
+ version: 1,
2631
+ overview: "",
2632
+ pages: [overviewPage(source), ...pages]
2633
+ });
2634
+ }
2635
+ function dedupePagePaths(plan) {
2636
+ const seen = /* @__PURE__ */ new Set();
2637
+ const pages = [];
2638
+ for (const page of plan.pages) {
2639
+ let path = page.path;
2640
+ let suffix = 2;
2641
+ while (seen.has(path)) {
2642
+ path = page.path.replace(/\.md$/, `-${suffix}.md`);
2643
+ suffix += 1;
2644
+ }
2645
+ seen.add(path);
2646
+ pages.push({ ...page, path });
2647
+ }
2648
+ return { ...plan, pages };
2649
+ }
2650
+ function planWikiGeneration(codewiki, requestedDepth = "auto") {
2651
+ const source = codewiki.files.filter((file) => file.lang !== "config");
2652
+ const sourceFiles = source.length;
2653
+ const sourceLines = source.reduce((total, file) => total + Math.max(0, file.loc), 0);
2654
+ const depth = requestedDepth === "auto" ? classifyDepth(sourceFiles, sourceLines) : requestedDepth;
2655
+ return { requestedDepth, depth, sourceFiles, sourceLines, plan: buildCandidatePlan(codewiki, depth) };
2656
+ }
2657
+
2658
+ // src/domains/context/wiki/generate.ts
2659
+ function progress2(input, event) {
2660
+ input.onProgress?.(event);
2661
+ }
2662
+ function indexedSourceFileCount2(codewiki) {
2663
+ return codewiki.files.filter((file) => file.lang !== "config").length;
2664
+ }
2665
+ async function loadOrBuildCodewiki(cwd) {
2666
+ const existing = readCodewiki(cwd);
2667
+ const prev = readClioState(cwd);
2668
+ if (existing && !codewikiNeedsBackfill(existing)) {
2669
+ const fingerprint = computeFingerprint(cwd, existing);
2670
+ if (prev && !isStale(prev.fingerprint, fingerprint)) return existing;
2671
+ }
2672
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
2673
+ const projectType = detectProjectType(cwd);
2674
+ const rebuilt = await buildCodewiki({ cwd, language: projectType, generatedAt });
2675
+ writeCodewiki(cwd, rebuilt);
2676
+ writeClioState(cwd, {
2677
+ version: 1,
2678
+ projectType: prev?.projectType ?? projectType,
2679
+ fingerprint: computeFingerprint(cwd, rebuilt),
2680
+ codewikiVersion: rebuilt.version,
2681
+ ...prev?.contextSources ? { contextSources: prev.contextSources } : {},
2682
+ ...prev?.contextSourceHash ? { contextSourceHash: prev.contextSourceHash } : {},
2683
+ ...prev?.lastBootstrap ? { lastBootstrap: prev.lastBootstrap } : {},
2684
+ ...prev?.lastInitAt ? { lastInitAt: prev.lastInitAt } : {},
2685
+ lastSessionAt: prev?.lastSessionAt ?? generatedAt,
2686
+ lastIndexedAt: generatedAt
2687
+ });
2688
+ return rebuilt;
2689
+ }
2690
+ function failed(problems, pages) {
2691
+ return { status: "failed", pages, problems };
2692
+ }
2693
+ var STAGING_PREFIX = "wiki-staging-";
2694
+ var WIKI_PREV_DIR = "wiki-prev";
2695
+ var WIKI_LOCK_FILE = "wiki.lock";
2696
+ function clioDir(cwd) {
2697
+ return join7(cwd, ".clio-coder");
2698
+ }
2699
+ function isProcessAlive(pid) {
2700
+ try {
2701
+ process.kill(pid, 0);
2702
+ return true;
2703
+ } catch (err) {
2704
+ return err.code === "EPERM";
2705
+ }
2706
+ }
2707
+ function readLockPid(lockPath) {
2708
+ try {
2709
+ const parsed = Number.parseInt(readFileSync6(lockPath, "utf8").trim(), 10);
2710
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
2711
+ } catch {
2712
+ return null;
2713
+ }
2714
+ }
2715
+ function acquireWikiLock(cwd) {
2716
+ const dir = clioDir(cwd);
2717
+ mkdirSync3(dir, { recursive: true });
2718
+ const lockPath = join7(dir, WIKI_LOCK_FILE);
2719
+ const release = () => {
2720
+ try {
2721
+ rmSync3(lockPath, { force: true });
2722
+ } catch {
2723
+ }
2724
+ };
2725
+ const create = () => {
2726
+ const fd = openSync(lockPath, "wx");
2727
+ try {
2728
+ writeSync(fd, String(process.pid));
2729
+ } finally {
2730
+ closeSync(fd);
2731
+ }
2732
+ };
2733
+ try {
2734
+ create();
2735
+ return { ok: true, release };
2736
+ } catch (err) {
2737
+ if (err.code !== "EEXIST") throw err;
2738
+ }
2739
+ const holder = readLockPid(lockPath);
2740
+ if (holder !== null && isProcessAlive(holder)) {
2741
+ return {
2742
+ ok: false,
2743
+ problem: `wiki generation is already running (lock held by pid ${holder} at ${lockPath})`
2744
+ };
2745
+ }
2746
+ try {
2747
+ rmSync3(lockPath, { force: true });
2748
+ create();
2749
+ return { ok: true, release };
2750
+ } catch (err) {
2751
+ if (err.code !== "EEXIST") throw err;
2752
+ const racer = readLockPid(lockPath);
2753
+ return {
2754
+ ok: false,
2755
+ problem: `wiki generation is already running (lock held by pid ${racer ?? "unknown"} at ${lockPath})`
2756
+ };
2757
+ }
2758
+ }
2759
+ function removeDir(dir) {
2760
+ try {
2761
+ rmSync3(dir, { recursive: true, force: true });
2762
+ } catch {
2763
+ }
2764
+ }
2765
+ function seedStaging(cwd, stagingDir) {
2766
+ const source = wikiDir(cwd);
2767
+ for (const relPath of wikiMarkdownFilesInDir(source)) {
2768
+ const target = join7(stagingDir, relPath);
2769
+ mkdirSync3(dirname4(target), { recursive: true });
2770
+ try {
2771
+ copyFileSync(join7(source, relPath), target);
2772
+ } catch {
2773
+ }
2774
+ }
2775
+ }
2776
+ function adoptOrCreateStaging(cwd) {
2777
+ const dir = clioDir(cwd);
2778
+ mkdirSync3(dir, { recursive: true });
2779
+ let entries;
2780
+ try {
2781
+ entries = readdirSync3(dir, { withFileTypes: true });
2782
+ } catch {
2783
+ entries = [];
2784
+ }
2785
+ const candidates = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(STAGING_PREFIX)).map((entry) => join7(dir, entry.name)).sort((a, b) => {
2786
+ const at = statSync2(a).mtimeMs;
2787
+ const bt = statSync2(b).mtimeMs;
2788
+ return bt - at;
2789
+ });
2790
+ const resumable = candidates.find((candidate) => existsSync6(join7(candidate, WIKI_PLAN_FILE)));
2791
+ for (const candidate of candidates) {
2792
+ if (candidate !== resumable) removeDir(candidate);
2793
+ }
2794
+ if (resumable !== void 0) return { dir: resumable, adopted: true };
2795
+ const fresh = mkdtempSync(join7(dir, STAGING_PREFIX));
2796
+ seedStaging(cwd, fresh);
2797
+ return { dir: fresh, adopted: false };
2798
+ }
2799
+ function promoteStaging(cwd, stagingDir, writeMeta) {
2800
+ const wiki = wikiDir(cwd);
2801
+ const prev = join7(clioDir(cwd), WIKI_PREV_DIR);
2802
+ removeDir(prev);
2803
+ const hadWiki = existsSync6(wiki);
2804
+ if (hadWiki) renameSync(wiki, prev);
2805
+ try {
2806
+ renameSync(stagingDir, wiki);
2807
+ writeMeta();
2808
+ } catch (err) {
2809
+ const problems = [`wiki promotion failed: ${err instanceof Error ? err.message : String(err)}`];
2810
+ removeDir(stagingDir);
2811
+ if (hadWiki) {
2812
+ removeDir(wiki);
2813
+ try {
2814
+ renameSync(prev, wiki);
2815
+ } catch {
2816
+ problems.push(`previous wiki could not be restored and remains at ${prev}`);
2817
+ return { ok: false, problems };
2818
+ }
2819
+ }
2820
+ return { ok: false, problems };
2821
+ }
2822
+ if (hadWiki) removeDir(prev);
2823
+ return { ok: true };
2824
+ }
2825
+ function resolvePlan(input) {
2826
+ const staged = input.adopted ? readWikiPlanFile(input.stagingDir) : null;
2827
+ if (staged !== null) return { plan: staged, resumed: true, unclaimedAreas: [] };
2828
+ if (input.mode === "update" && input.previousPlan !== void 0) {
2829
+ const pageSources = pageSourceIndex(input.stagingDir, input.cwd);
2830
+ const changed = new Set(
2831
+ changedPathsSince(input.cwd, input.gitHead).map((path) => normalizeRepoPath(input.cwd, path))
2832
+ );
2833
+ return {
2834
+ plan: scopePlanForUpdate({
2835
+ plan: input.previousPlan,
2836
+ changedPaths: changed,
2837
+ existingPages: new Set(wikiMarkdownFilesInDir(input.stagingDir)),
2838
+ pageSources
2839
+ }),
2840
+ resumed: false,
2841
+ unclaimedAreas: unclaimedCandidates(input.previousPlan, input.candidate, pageSources)
2842
+ };
2843
+ }
2844
+ return { plan: input.candidate, resumed: false, unclaimedAreas: [] };
2845
+ }
2846
+ function reconcilePlan(plan, stagingDir) {
2847
+ const contentFiles = wikiMarkdownFilesInDir(stagingDir).filter((relPath) => !isGeneratedWikiFile(relPath));
2848
+ const onDisk = new Set(contentFiles);
2849
+ const planned = new Set(plan.pages.map((page) => page.path));
2850
+ const adopted = contentFiles.filter((relPath) => !planned.has(relPath)).map((relPath) => ({
2851
+ path: relPath,
2852
+ title: relPath.replace(/\.md$/, ""),
2853
+ intent: "",
2854
+ sources: [],
2855
+ status: "written",
2856
+ attempts: 1
2857
+ }));
2858
+ return {
2859
+ ...plan,
2860
+ pages: [
2861
+ ...plan.pages.map(
2862
+ (page) => onDisk.has(page.path) ? { ...page, status: "written" } : { ...page, status: "pending" }
2863
+ ),
2864
+ ...adopted
2865
+ ]
2866
+ };
2867
+ }
2868
+ async function runWikiGenerate(input = { model: "configured-clio-target" }) {
2869
+ const cwd = input.cwd ?? process.cwd();
2870
+ const existingMeta = readWikiMeta(cwd);
2871
+ const mode = input.mode ?? (existingMeta ? "update" : "init");
2872
+ const lock = acquireWikiLock(cwd);
2873
+ if (!lock.ok) {
2874
+ progress2(input, { phase: "generate", status: "failed", message: lock.problem });
2875
+ progress2(input, { phase: "done", status: "failed", message: "wiki generation failed", detail: lock.problem });
2876
+ return failed([lock.problem], listWikiPagesInDir(wikiDir(cwd)).length);
2877
+ }
2878
+ try {
2879
+ progress2(input, { phase: "codewiki", status: "started", message: "loading codewiki for wiki generation" });
2880
+ const codewiki = await loadOrBuildCodewiki(cwd);
2881
+ const sourceTreeHash = computeFingerprint(cwd, codewiki).treeHash;
2882
+ const generation = planWikiGeneration(codewiki, input.depth ?? "auto");
2883
+ progress2(input, {
2884
+ phase: "codewiki",
2885
+ status: "completed",
2886
+ message: `loaded ${indexedSourceFileCount2(codewiki)} source file${indexedSourceFileCount2(codewiki) === 1 ? "" : "s"}`,
2887
+ current: indexedSourceFileCount2(codewiki),
2888
+ total: indexedSourceFileCount2(codewiki)
2889
+ });
2890
+ const staging = adoptOrCreateStaging(cwd);
2891
+ const resolved = resolvePlan({
2892
+ cwd,
2893
+ stagingDir: staging.dir,
2894
+ adopted: staging.adopted,
2895
+ mode,
2896
+ candidate: generation.plan,
2897
+ previousPlan: existingMeta?.plan,
2898
+ gitHead: existingMeta?.gitHead ?? null
2899
+ });
2900
+ const owed = resolved.plan.pages.filter((page) => page.status !== "written").length;
2901
+ progress2(input, {
2902
+ phase: "codewiki",
2903
+ status: "running",
2904
+ message: `selected ${generation.depth} wiki strategy`,
2905
+ detail: `${generation.sourceFiles} source files; ${generation.sourceLines} lines; ${resolved.plan.pages.length} pages planned; ${owed} to write` + (resolved.resumed ? "; resuming an interrupted run" : "")
2906
+ });
2907
+ writeWikiPlanFile(staging.dir, resolved.plan);
2908
+ const beforeHash = computeWikiContentHash(cwd);
2909
+ if (!input.generate) {
2910
+ const problem = "wiki generation requires a model runtime";
2911
+ progress2(input, { phase: "generate", status: "failed", message: problem });
2912
+ progress2(input, { phase: "done", status: "failed", message: "wiki generation failed" });
2913
+ return failed([problem], listWikiPagesInDir(wikiDir(cwd)).length);
2914
+ }
2915
+ progress2(input, {
2916
+ phase: "generate",
2917
+ status: "started",
2918
+ message: mode === "init" ? "drafting project wiki" : "updating project wiki"
2919
+ });
2920
+ try {
2921
+ await input.generate({
2922
+ cwd,
2923
+ mode,
2924
+ outputDir: staging.dir,
2925
+ codewiki,
2926
+ generation,
2927
+ plan: resolved.plan,
2928
+ resumed: resolved.resumed,
2929
+ unclaimedAreas: resolved.unclaimedAreas,
2930
+ gitHead: existingMeta?.gitHead ?? null,
2931
+ ...input.onProgress ? { progress: input.onProgress } : {}
2932
+ });
2933
+ } catch (err) {
2934
+ const problem = err instanceof Error ? err.message : String(err);
2935
+ progress2(input, { phase: "generate", status: "failed", message: "wiki generator failed", detail: problem });
2936
+ progress2(input, {
2937
+ phase: "done",
2938
+ status: "failed",
2939
+ message: "wiki generation failed",
2940
+ detail: `staged work kept at ${staging.dir}; rerun to resume`
2941
+ });
2942
+ return failed([problem], listWikiPagesInDir(wikiDir(cwd)).length);
2943
+ }
2944
+ progress2(input, { phase: "generate", status: "completed", message: "wiki generator completed" });
2945
+ const workedPlan = readWikiPlanFile(staging.dir, resolved.plan) ?? resolved.plan;
2946
+ const report = assembleWikiTree({ dir: staging.dir, sourceRoot: cwd, plan: workedPlan });
2947
+ const finalPlan = reconcilePlan(workedPlan, staging.dir);
2948
+ const pendingCount = finalPlan.pages.filter((page) => page.status !== "written").length;
2949
+ progress2(input, {
2950
+ phase: "state",
2951
+ status: "running",
2952
+ message: `assembled ${report.pages.length} page${report.pages.length === 1 ? "" : "s"}`,
2953
+ detail: `${report.repaired} repaired; ${report.issues.length} unresolved reference${report.issues.length === 1 ? "" : "s"}` + (report.dropped.length > 0 ? `; ${report.dropped.length} empty page dropped` : "") + (pendingCount > 0 ? `; ${pendingCount} page${pendingCount === 1 ? "" : "s"} still to write` : "")
2954
+ });
2955
+ rmSync3(join7(staging.dir, WIKI_PLAN_FILE), { force: true });
2956
+ const afterHash = computeWikiContentHashOfDir(staging.dir);
2957
+ const stagedPages = listWikiPagesInDir(staging.dir);
2958
+ const problems = report.issues.map((issue) => `${issue.page} has an unresolved ${issue.kind}: ${issue.reference}`);
2959
+ const writeMeta = () => {
2960
+ writeWikiMeta(cwd, {
2961
+ version: 1,
2962
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2963
+ gitHead: currentWikiGitHead(cwd),
2964
+ sourceTreeHash,
2965
+ model: input.model,
2966
+ contentHash: afterHash,
2967
+ pages: stagedPages,
2968
+ generation: {
2969
+ requestedDepth: generation.requestedDepth,
2970
+ depth: generation.depth,
2971
+ sourceFiles: generation.sourceFiles,
2972
+ sourceLines: generation.sourceLines,
2973
+ pagesPlanned: finalPlan.pages.length,
2974
+ pagesWritten: finalPlan.pages.length - pendingCount
2975
+ },
2976
+ plan: finalPlan
2977
+ });
2978
+ };
2979
+ const done = (status) => ({
2980
+ status,
2981
+ pages: stagedPages.length,
2982
+ pending: pendingCount,
2983
+ ...problems.length > 0 ? { problems } : {}
2984
+ });
2985
+ if (afterHash === beforeHash && computeWikiContentHash(cwd) === beforeHash && existingMeta) {
2986
+ removeDir(staging.dir);
2987
+ progress2(input, {
2988
+ phase: "state",
2989
+ status: "completed",
2990
+ message: "wiki unchanged; metadata preserved",
2991
+ detail: wikiMetaPath(cwd)
2992
+ });
2993
+ progress2(input, { phase: "done", status: "completed", message: "wiki unchanged" });
2994
+ return done("noop");
2995
+ }
2996
+ progress2(input, { phase: "state", status: "running", message: "promoting wiki" });
2997
+ const promoted = promoteStaging(cwd, staging.dir, writeMeta);
2998
+ if (!promoted.ok) {
2999
+ progress2(input, {
3000
+ phase: "done",
3001
+ status: "failed",
3002
+ message: "wiki promotion failed",
3003
+ detail: promoted.problems.join("; ")
3004
+ });
3005
+ return failed(promoted.problems, listWikiPagesInDir(wikiDir(cwd)).length);
3006
+ }
3007
+ progress2(input, { phase: "state", status: "completed", message: "wiki metadata written" });
3008
+ progress2(input, {
3009
+ phase: "done",
3010
+ status: "completed",
3011
+ message: pendingCount > 0 ? "wiki partially generated" : "wiki generated",
3012
+ ...pendingCount > 0 ? {
3013
+ detail: `${pendingCount} page${pendingCount === 1 ? "" : "s"} remain; run \`clio-coder context wiki --update\` to finish`
3014
+ } : {}
3015
+ });
3016
+ return done("generated");
3017
+ } finally {
3018
+ lock.release();
3019
+ }
3020
+ }
3021
+
3022
+ // src/domains/context/refresh.ts
3023
+ function curateClioMd(cwd, codewiki) {
3024
+ try {
3025
+ const parsed = tryReadClioMd(cwd);
3026
+ if (!parsed?.ok) return "absent";
3027
+ const handbook = parsed.value;
3028
+ const fresh = new Map(codewikiSections(codewiki).map((section) => [section.title, section.body]));
3029
+ let changed = false;
3030
+ const sections = handbook.sections.map((section) => {
3031
+ const body = fresh.get(section.title);
3032
+ if (body === void 0 || body === section.body) return section;
3033
+ changed = true;
3034
+ return { title: section.title, body };
3035
+ });
3036
+ if (!changed) return "unchanged";
3037
+ writeFileSync3(
3038
+ join8(cwd, "CLIO-CODER.md"),
3039
+ serializeClioMd({
3040
+ projectName: handbook.projectName,
3041
+ identity: handbook.identity,
3042
+ conventions: handbook.conventions,
3043
+ invariants: handbook.invariants,
3044
+ sections,
3045
+ ...handbook.importedAgentContext ? { importedAgentContext: handbook.importedAgentContext } : {},
3046
+ fingerprint: handbook.fingerprint
3047
+ }),
3048
+ "utf8"
3049
+ );
3050
+ return "updated";
3051
+ } catch {
3052
+ return "absent";
3053
+ }
3054
+ }
3055
+ function indexedSourceFileCount3(codewiki) {
3056
+ return codewiki.files.filter((file) => file.lang !== "config").length;
3057
+ }
3058
+ var STALE_WIKI_REFRESH_HINT = "wiki is stale; run clio-coder context refresh --wiki or clio-coder context wiki --update";
3059
+ var NO_WIKI_HINT = "no wiki exists, so --wiki had nothing to update; run clio-coder context wiki to build one";
3060
+ function incompleteWikiHint(cwd) {
3061
+ const completeness = wikiCompleteness(cwd);
3062
+ if (!completeness || completeness.owed === 0) return void 0;
3063
+ return `wiki is incomplete: ${completeness.pagesWritten} of ${completeness.pagesPlanned} planned pages written, ${completeness.owed} owed; run clio-coder context wiki --update to resume`;
3064
+ }
3065
+ async function runContextRefresh(input = {}) {
3066
+ const cwd = input.cwd ?? process.cwd();
3067
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
3068
+ const prev = readClioState(cwd);
3069
+ const hasWiki = readWikiMeta(cwd) !== null;
3070
+ const projectType = prev?.projectType ?? detectProjectType(cwd);
3071
+ const indexedAt = now().toISOString();
3072
+ input.onProgress?.({ phase: "codewiki", status: "started", message: "rebuilding codewiki" });
3073
+ const codewiki = await buildCodewiki({ cwd, language: projectType, generatedAt: indexedAt });
3074
+ writeCodewiki(cwd, codewiki);
3075
+ const fingerprint = computeFingerprint(cwd, codewiki);
3076
+ const clioMd = curateClioMd(cwd, codewiki);
3077
+ input.onProgress?.({ phase: "state", status: "running", message: "writing state" });
3078
+ writeClioState(cwd, {
3079
+ version: 1,
3080
+ projectType,
3081
+ fingerprint,
3082
+ codewikiVersion: codewiki.version,
3083
+ ...prev?.contextSources ? { contextSources: prev.contextSources } : {},
3084
+ ...prev?.contextSourceHash ? { contextSourceHash: prev.contextSourceHash } : {},
3085
+ ...prev?.lastBootstrap ? { lastBootstrap: prev.lastBootstrap } : {},
3086
+ ...prev?.lastInitAt ? { lastInitAt: prev.lastInitAt } : {},
3087
+ lastSessionAt: prev?.lastSessionAt ?? indexedAt,
3088
+ lastIndexedAt: indexedAt
3089
+ });
3090
+ const entries = indexedSourceFileCount3(codewiki);
3091
+ let wikiResult;
3092
+ let hint;
3093
+ if (input.wiki === true && hasWiki) {
3094
+ wikiResult = await runWikiGenerate({
3095
+ cwd,
3096
+ mode: "update",
3097
+ model: input.wikiModel ?? "unresolved-documenter-target",
3098
+ ...input.wikiGenerate ? { generate: input.wikiGenerate } : {},
3099
+ ...input.onProgress ? { onProgress: input.onProgress } : {}
3100
+ });
3101
+ } else if (input.wiki === true) {
3102
+ hint = NO_WIKI_HINT;
3103
+ } else if (hasWiki) {
3104
+ hint = incompleteWikiHint(cwd) ?? (wikiStaleness(cwd).state === "stale" ? STALE_WIKI_REFRESH_HINT : void 0);
3105
+ }
3106
+ const handbookNote = clioMd === "updated" ? "; CLIO-CODER.md index sections updated" : "";
3107
+ input.io?.stdout(
3108
+ `clio-coder context refresh: codewiki rebuilt (${entries} source file${entries === 1 ? "" : "s"})${handbookNote}
3109
+ `
3110
+ );
3111
+ input.onProgress?.({ phase: "done", status: "completed", message: "context refreshed" });
3112
+ return {
3113
+ action: "refreshed",
3114
+ codewikiEntries: entries,
3115
+ fingerprint,
3116
+ clioMd,
3117
+ ...wikiResult ? { wiki: wikiResult } : {},
3118
+ ...hint ? { hint } : {}
3119
+ };
3120
+ }
3121
+
3122
+ // src/domains/context/extension.ts
3123
+ function persistState(cwd, fingerprint, indexedAt, prev, codewikiVersion) {
3124
+ writeClioState(cwd, {
3125
+ version: 1,
3126
+ projectType: prev?.projectType ?? detectProjectType(cwd),
3127
+ fingerprint,
3128
+ codewikiVersion,
3129
+ ...prev?.contextSources ? { contextSources: prev.contextSources } : {},
3130
+ ...prev?.contextSourceHash ? { contextSourceHash: prev.contextSourceHash } : {},
3131
+ ...prev?.lastBootstrap ? { lastBootstrap: prev.lastBootstrap } : {},
3132
+ ...prev?.lastInitAt ? { lastInitAt: prev.lastInitAt } : {},
3133
+ lastSessionAt: prev?.lastSessionAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3134
+ lastIndexedAt: indexedAt
3135
+ });
3136
+ }
3137
+ async function ensureCodewikiFresh(cwd) {
3138
+ if (process.env.CLIO_CODER_BOOTSTRAP_GENERATE_CHILD === "1") return;
3139
+ const state = readClioState(cwd);
3140
+ if (!state && !existsSync7(codewikiPath(cwd))) return;
3141
+ const slicer = createSlicer();
3142
+ const codewiki = await readCodewikiAsync(cwd);
3143
+ const fingerprint = await computeFingerprintAsync(cwd, codewiki, { slicer });
3144
+ const stale = !state || isStale(state.fingerprint, fingerprint) || !existsSync7(codewikiPath(cwd)) || !codewiki || codewikiNeedsBackfill(codewiki);
3145
+ if (!stale) return;
3146
+ const indexedAt = (/* @__PURE__ */ new Date()).toISOString();
3147
+ const projectType = state?.projectType ?? detectProjectType(cwd);
3148
+ const synced = codewiki ? await syncCodewiki(cwd, codewiki, { slicer }) : await buildCodewiki({ cwd, language: projectType, generatedAt: indexedAt }, { slicer });
3149
+ writeCodewiki(cwd, synced);
3150
+ persistState(cwd, await computeFingerprintAsync(cwd, synced, { slicer }), indexedAt, state, synced.version);
3151
+ }
3152
+ var CONTEXT_STATE_CACHE_TTL_MS = 1500;
3153
+ var CODEWIKI_CACHE_LIMIT = 4;
3154
+ function codewikiContentHash(raw) {
3155
+ return createHash2("sha256").update(raw).digest("hex");
3156
+ }
3157
+ function memoryCount() {
3158
+ try {
3159
+ return loadMemoryRecordsSync(clioDataDir()).length;
3160
+ } catch {
3161
+ return 0;
3162
+ }
3163
+ }
3164
+ function resolveClioMdState(cwd) {
3165
+ const clio = tryReadClioMd(cwd);
3166
+ if (!clio) return "none";
3167
+ if (!clio.ok) return "malformed";
3168
+ const state = readClioState(cwd);
3169
+ if (state?.contextSources !== void 0 && adoptionSourcesChanged(state.contextSources, { cwd })) {
3170
+ return "stale";
3171
+ }
3172
+ return "ok";
3173
+ }
3174
+ function createContextStateReader() {
3175
+ let cached = null;
3176
+ return {
3177
+ read(cwd = process.cwd()) {
3178
+ const now = Date.now();
3179
+ if (cached && cached.cwd === cwd && now - cached.at < CONTEXT_STATE_CACHE_TTL_MS) return cached.state;
3180
+ const state = { clioMd: resolveClioMdState(cwd), memoryCount: memoryCount() };
3181
+ cached = { cwd, at: now, state };
3182
+ return state;
3183
+ },
3184
+ invalidate(cwd) {
3185
+ if (!cached) return;
3186
+ if (!cwd || cached.cwd === cwd) cached = null;
3187
+ }
3188
+ };
3189
+ }
3190
+ function collectStartupHints(cwd, options = {}) {
3191
+ const hints = [];
3192
+ let projectType;
3193
+ try {
3194
+ projectType = detectProjectType(cwd);
3195
+ } catch {
3196
+ projectType = "unknown";
3197
+ }
3198
+ const clio = tryReadClioMd(cwd);
3199
+ if (!clio && projectType !== "unknown" && options.noContextFiles !== true) {
3200
+ hints.push("clio: No CLIO-CODER.md detected. Run /context init to explore the repo and bootstrap context.");
3201
+ }
3202
+ if (clio && !clio.ok) {
3203
+ hints.push(`clio: malformed CLIO-CODER.md ignored: ${clio.error}`);
3204
+ }
3205
+ const state = readClioState(cwd);
3206
+ if (!state) return hints;
3207
+ if (state.contextSources !== void 0 && adoptionSourcesChanged(state.contextSources, { cwd })) {
3208
+ hints.push("clio: Imported agent context changed. Run /context init --adopt to refresh.");
3209
+ }
3210
+ return hints;
3211
+ }
3212
+ function createContextBundle(_context, options = {}) {
3213
+ let lastCwd = process.cwd();
3214
+ let startupHints = [];
3215
+ const contextState = createContextStateReader();
3216
+ const codewikiCache = /* @__PURE__ */ new Map();
3217
+ const rememberCodewiki = (cwd, hash, codewiki) => {
3218
+ codewikiCache.delete(cwd);
3219
+ codewikiCache.set(cwd, { hash, codewiki });
3220
+ while (codewikiCache.size > CODEWIKI_CACHE_LIMIT) {
3221
+ const oldest = codewikiCache.keys().next().value;
3222
+ if (oldest === void 0) break;
3223
+ codewikiCache.delete(oldest);
3224
+ }
3225
+ };
3226
+ const readCachedCodewiki = (cwd) => {
3227
+ let raw;
3228
+ try {
3229
+ raw = readFileSync7(codewikiPath(cwd), "utf8");
3230
+ } catch {
3231
+ codewikiCache.delete(cwd);
3232
+ return null;
3233
+ }
3234
+ const hash = codewikiContentHash(raw);
3235
+ const cached = codewikiCache.get(cwd);
3236
+ if (cached && cached.hash === hash) {
3237
+ rememberCodewiki(cwd, hash, cached.codewiki);
3238
+ return cached.codewiki;
3239
+ }
3240
+ const codewiki = parseCodewikiRaw(raw);
3241
+ if (!codewiki) {
3242
+ codewikiCache.delete(cwd);
3243
+ return null;
3244
+ }
3245
+ rememberCodewiki(cwd, hash, codewiki);
3246
+ return codewiki;
3247
+ };
3248
+ const writeCachedCodewiki = (cwd, codewiki) => {
3249
+ const serialized = writeCodewiki(cwd, codewiki);
3250
+ rememberCodewiki(cwd, codewikiContentHash(serialized), codewiki);
3251
+ };
3252
+ const onStart = () => {
3253
+ lastCwd = process.cwd();
3254
+ void ensureCodewikiFresh(lastCwd).catch(() => {
3255
+ });
3256
+ startupHints = collectStartupHints(lastCwd, options);
3257
+ if (process.env.CLIO_CODER_INTERACTIVE === "1") return;
3258
+ for (const hint of startupHints) process.stderr.write(`${hint}
3259
+ `);
3260
+ };
3261
+ let incrementalQueue = Promise.resolve();
3262
+ const noteFileChanges = (paths, cwd = lastCwd) => {
3263
+ incrementalQueue = incrementalQueue.then(async () => {
3264
+ if (paths.length === 0) return;
3265
+ const codewiki = readCachedCodewiki(cwd);
3266
+ if (!codewiki) return;
3267
+ const rel = paths.map((p) => isAbsolute(p) ? relative3(cwd, p) : p).filter((p) => p.length > 0 && !p.startsWith(".."));
3268
+ const slicer = createSlicer();
3269
+ const updated = await updateCodewikiPaths(cwd, codewiki, rel, { slicer });
3270
+ if (updated === codewiki) return;
3271
+ writeCachedCodewiki(cwd, updated);
3272
+ persistState(
3273
+ cwd,
3274
+ await computeFingerprintAsync(cwd, updated, { slicer }),
3275
+ (/* @__PURE__ */ new Date()).toISOString(),
3276
+ readClioState(cwd),
3277
+ updated.version
3278
+ );
3279
+ contextState.invalidate(cwd);
3280
+ }).catch(() => {
3281
+ });
3282
+ };
3283
+ let unsubscribeSessionStart = null;
3284
+ const extension = {
3285
+ start() {
3286
+ unsubscribeSessionStart = _context.bus.on(BusChannels.SessionStart, onStart);
3287
+ },
3288
+ async stop() {
3289
+ unsubscribeSessionStart?.();
3290
+ unsubscribeSessionStart = null;
3291
+ await incrementalQueue;
3292
+ const slicer = createSlicer();
3293
+ const projectType = detectProjectType(lastCwd);
3294
+ const state = readClioState(lastCwd);
3295
+ let codewiki = readCachedCodewiki(lastCwd);
3296
+ let fingerprint = await computeFingerprintAsync(lastCwd, codewiki, { slicer });
3297
+ let lastIndexedAt = state?.lastIndexedAt;
3298
+ if (!state || isStale(state.fingerprint, fingerprint) || !codewiki || codewikiNeedsBackfill(codewiki)) {
3299
+ lastIndexedAt = (/* @__PURE__ */ new Date()).toISOString();
3300
+ codewiki = codewiki ? await syncCodewiki(lastCwd, codewiki, { slicer }) : await buildCodewiki({ cwd: lastCwd, language: projectType, generatedAt: lastIndexedAt }, { slicer });
3301
+ writeCachedCodewiki(lastCwd, codewiki);
3302
+ fingerprint = await computeFingerprintAsync(lastCwd, codewiki, { slicer });
3303
+ }
3304
+ writeClioState(lastCwd, {
3305
+ version: 1,
3306
+ projectType,
3307
+ fingerprint,
3308
+ ...codewiki ? { codewikiVersion: codewiki.version } : {},
3309
+ ...state?.contextSources ? { contextSources: state.contextSources } : {},
3310
+ ...state?.contextSourceHash ? { contextSourceHash: state.contextSourceHash } : {},
3311
+ ...state?.lastBootstrap ? { lastBootstrap: state.lastBootstrap } : {},
3312
+ ...state?.lastInitAt ? { lastInitAt: state.lastInitAt } : {},
3313
+ lastSessionAt: (/* @__PURE__ */ new Date()).toISOString(),
3314
+ ...lastIndexedAt ? { lastIndexedAt } : {}
3315
+ });
3316
+ }
3317
+ };
3318
+ const contract = {
3319
+ async runBootstrap(input) {
3320
+ const emitProgress = (event) => {
3321
+ _context.bus.emit(BusChannels.ContextActivity, { kind: "context-init", at: Date.now(), ...event });
3322
+ input?.onProgress?.(event);
3323
+ };
3324
+ try {
3325
+ const result = await runBootstrap(input ? { ...input, onProgress: emitProgress } : { onProgress: emitProgress });
3326
+ const cwd = input?.cwd ?? process.cwd();
3327
+ contextState.invalidate(cwd);
3328
+ if (cwd === lastCwd) startupHints = collectStartupHints(cwd, options);
3329
+ return result;
3330
+ } catch (err) {
3331
+ emitProgress({
3332
+ phase: "done",
3333
+ status: "failed",
3334
+ message: "context init failed",
3335
+ detail: err instanceof Error ? err.message : String(err)
3336
+ });
3337
+ throw err;
3338
+ }
3339
+ },
3340
+ async runContextClear(input) {
3341
+ const emitProgress = (event) => {
3342
+ _context.bus.emit(BusChannels.ContextActivity, { kind: "context-clear", at: Date.now(), ...event });
3343
+ };
3344
+ emitProgress({ phase: "done", status: "started", message: "clearing context" });
3345
+ try {
3346
+ const result = await runContextClear(input);
3347
+ const cwd = input?.cwd ?? process.cwd();
3348
+ contextState.invalidate(cwd);
3349
+ if (cwd === lastCwd) startupHints = collectStartupHints(cwd, options);
3350
+ emitProgress({ phase: "done", status: "completed", message: "context cleared" });
3351
+ return result;
3352
+ } catch (err) {
3353
+ emitProgress({
3354
+ phase: "done",
3355
+ status: "failed",
3356
+ message: "context clear failed",
3357
+ detail: err instanceof Error ? err.message : String(err)
3358
+ });
3359
+ throw err;
3360
+ }
3361
+ },
3362
+ async runContextRefresh(input) {
3363
+ const emitProgress = (event) => {
3364
+ _context.bus.emit(BusChannels.ContextActivity, { kind: "context-refresh", at: Date.now(), ...event });
3365
+ input?.onProgress?.(event);
3366
+ };
3367
+ try {
3368
+ const result = await runContextRefresh(
3369
+ input ? { ...input, onProgress: emitProgress } : { onProgress: emitProgress }
3370
+ );
3371
+ const cwd = input?.cwd ?? process.cwd();
3372
+ contextState.invalidate(cwd);
3373
+ if (cwd === lastCwd) startupHints = collectStartupHints(cwd, options);
3374
+ return result;
3375
+ } catch (err) {
3376
+ emitProgress({
3377
+ phase: "done",
3378
+ status: "failed",
3379
+ message: "context refresh failed",
3380
+ detail: err instanceof Error ? err.message : String(err)
3381
+ });
3382
+ throw err;
3383
+ }
3384
+ },
3385
+ async runWikiGenerate(input) {
3386
+ const emitProgress = (event) => {
3387
+ _context.bus.emit(BusChannels.ContextActivity, { kind: "context-wiki", at: Date.now(), ...event });
3388
+ input?.onProgress?.(event);
3389
+ };
3390
+ try {
3391
+ return await runWikiGenerate(
3392
+ input ? { ...input, onProgress: emitProgress } : { model: "configured-clio-target", onProgress: emitProgress }
3393
+ );
3394
+ } catch (err) {
3395
+ emitProgress({
3396
+ phase: "done",
3397
+ status: "failed",
3398
+ message: "context wiki failed",
3399
+ detail: err instanceof Error ? err.message : String(err)
3400
+ });
3401
+ throw err;
3402
+ }
3403
+ },
3404
+ renderPromptContext,
3405
+ projectStructuredContext(cwd = process.cwd()) {
3406
+ const clio = tryReadClioMd(cwd);
3407
+ if (!clio?.ok) return null;
3408
+ const verification = clio.value.sections.find(
3409
+ (section) => section.title.toLowerCase() === "verification expectations"
3410
+ );
3411
+ const verificationBody = verification?.body.trim() ?? "";
3412
+ return {
3413
+ projectName: clio.value.projectName,
3414
+ conventions: [...clio.value.conventions],
3415
+ invariants: [...clio.value.invariants],
3416
+ ...verificationBody.length > 0 ? { verificationExpectations: verificationBody } : {}
3417
+ };
3418
+ },
3419
+ contextState: contextState.read,
3420
+ startupHints: () => [...startupHints],
3421
+ noteFileChanges
3422
+ };
3423
+ return { extension, contract };
3424
+ }
3425
+
3426
+ // src/domains/context/manifest.ts
3427
+ var ContextManifest = {
3428
+ name: "context",
3429
+ dependsOn: ["resources"]
3430
+ };
3431
+
3432
+ // src/domains/context/operator-profile.ts
3433
+ import { createHash as createHash3 } from "node:crypto";
3434
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
3435
+ import { join as join9 } from "node:path";
3436
+ import { parse as parseYaml2 } from "yaml";
3437
+ var RESPONSE_POSTURES = ["concise", "balanced", "thorough"];
3438
+ var VALIDATION_PREFERENCES = ["tests-first", "manual", "trust"];
3439
+ var COMMIT_MESSAGE_STYLES = ["conventional", "descriptive", "terse"];
3440
+ var OPERATOR_PROFILE_MAX_CHARS = 700;
3441
+ var OPERATOR_PROFILE_MAX_LOCAL_PATHS = 8;
3442
+ function isRecord2(value) {
3443
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3444
+ }
3445
+ function readEnum(value, allowed, field, issues) {
3446
+ if (value === void 0) return void 0;
3447
+ if (typeof value === "string" && allowed.includes(value)) return value;
3448
+ issues.push(`${field} must be one of ${allowed.join(", ")}`);
3449
+ return void 0;
3450
+ }
3451
+ function readProfileBlob(value, issues) {
3452
+ if (!isRecord2(value)) {
3453
+ if (value !== null && value !== void 0) issues.push("profile must be a mapping at the root");
3454
+ return {};
3455
+ }
3456
+ const profile = {};
3457
+ const posture = readEnum(value.responsePosture, RESPONSE_POSTURES, "responsePosture", issues);
3458
+ if (posture) profile.responsePosture = posture;
3459
+ const validation = readEnum(value.validationPreference, VALIDATION_PREFERENCES, "validationPreference", issues);
3460
+ if (validation) profile.validationPreference = validation;
3461
+ const commit = readEnum(value.commitMessageStyle, COMMIT_MESSAGE_STYLES, "commitMessageStyle", issues);
3462
+ if (commit) profile.commitMessageStyle = commit;
3463
+ if (value.localOnlyPaths !== void 0) {
3464
+ if (Array.isArray(value.localOnlyPaths)) {
3465
+ const paths = value.localOnlyPaths.filter((item) => typeof item === "string" && item.length > 0);
3466
+ if (paths.length > 0) profile.localOnlyPaths = paths.slice(0, OPERATOR_PROFILE_MAX_LOCAL_PATHS);
3467
+ } else {
3468
+ issues.push("localOnlyPaths must be an array of strings");
3469
+ }
3470
+ }
3471
+ return profile;
3472
+ }
3473
+ function readProfileFile(path, issues) {
3474
+ if (!existsSync8(path)) return { profile: {}, present: false };
3475
+ try {
3476
+ return { profile: readProfileBlob(parseYaml2(readFileSync8(path, "utf8")), issues), present: true };
3477
+ } catch (err) {
3478
+ issues.push(`${path}: invalid YAML: ${err instanceof Error ? err.message : String(err)}`);
3479
+ return { profile: {}, present: false };
3480
+ }
3481
+ }
3482
+ function loadOperatorProfile(cwd, options = {}) {
3483
+ const issues = [];
3484
+ const userPath = options.userPath ?? join9(resolveClioDirs().config, "profile.yaml");
3485
+ const projectPath = join9(cwd, ".clio-coder", "profile.yaml");
3486
+ const user = readProfileFile(userPath, issues);
3487
+ const project = readProfileFile(projectPath, issues);
3488
+ const profile = { ...user.profile, ...project.profile };
3489
+ let origin = "none";
3490
+ let sourcePath;
3491
+ if (project.present) {
3492
+ origin = "project";
3493
+ sourcePath = projectPath;
3494
+ } else if (user.present) {
3495
+ origin = "user";
3496
+ sourcePath = userPath;
3497
+ }
3498
+ const result = { profile, origin, issues };
3499
+ if (sourcePath !== void 0) {
3500
+ result.sourcePath = sourcePath;
3501
+ result.hash = createHash3("sha256").update(JSON.stringify(profile)).digest("hex").slice(0, 16);
3502
+ }
3503
+ return result;
3504
+ }
3505
+ function renderOperatorProfile(profile) {
3506
+ const lines = [];
3507
+ if (profile.responsePosture) lines.push(`- Response posture: ${profile.responsePosture}.`);
3508
+ if (profile.validationPreference) lines.push(`- Validation preference: ${profile.validationPreference}.`);
3509
+ if (profile.commitMessageStyle) lines.push(`- Commit-message style: ${profile.commitMessageStyle}.`);
3510
+ if (profile.localOnlyPaths && profile.localOnlyPaths.length > 0) {
3511
+ lines.push(`- Keep local-only (do not push or share): ${profile.localOnlyPaths.join(", ")}.`);
3512
+ }
3513
+ if (lines.length === 0) return { text: "", tokenEstimate: 0 };
3514
+ const full = `## Operator profile
3515
+ ${lines.join("\n")}`;
3516
+ const text = full.length > OPERATOR_PROFILE_MAX_CHARS ? `${full.slice(0, OPERATOR_PROFILE_MAX_CHARS - 1)}\u2026` : full;
3517
+ return { text, tokenEstimate: ceilChars(text.length) };
3518
+ }
3519
+
3520
+ // src/domains/context/project-rules.ts
3521
+ import { createHash as createHash4 } from "node:crypto";
3522
+ import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
3523
+ import { join as join10, relative as relative4, sep as sep2 } from "node:path";
3524
+ import { parse as parseYaml3 } from "yaml";
3525
+ var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
3526
+ function isRecord3(value) {
3527
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3528
+ }
3529
+ function readStringArray(value) {
3530
+ if (!Array.isArray(value)) return void 0;
3531
+ const out3 = [];
3532
+ for (const item of value) {
3533
+ if (typeof item === "string" && item.length > 0) out3.push(item);
3534
+ }
3535
+ return out3.length > 0 ? out3 : void 0;
3536
+ }
3537
+ function validateGlobArray(value, field, id, issues) {
3538
+ const parsed = readStringArray(value);
3539
+ if (parsed === void 0) {
3540
+ issues.push(`${id}: frontmatter ${field} must be a non-empty string array`);
3541
+ return void 0;
3542
+ }
3543
+ for (const pattern of parsed) {
3544
+ try {
3545
+ compileGlobRegex(pattern);
3546
+ } catch (err) {
3547
+ issues.push(
3548
+ `${id}: invalid ${field} glob ${JSON.stringify(pattern)}: ${err instanceof Error ? err.message : String(err)}`
3549
+ );
3550
+ return void 0;
3551
+ }
3552
+ }
3553
+ return parsed;
3554
+ }
3555
+ function walkMarkdown(dir, root, out3) {
3556
+ let entries;
3557
+ try {
3558
+ entries = readdirSync4(dir, { withFileTypes: true });
3559
+ } catch {
3560
+ return;
3561
+ }
3562
+ for (const entry of entries) {
3563
+ const full = join10(dir, entry.name);
3564
+ if (entry.isDirectory()) walkMarkdown(full, root, out3);
3565
+ else if (entry.isFile() && entry.name.endsWith(".md")) out3.push(full);
3566
+ }
3567
+ }
3568
+ function parseRule(filePath, root, issues) {
3569
+ let text;
3570
+ try {
3571
+ text = readFileSync9(filePath, "utf8").replace(/\r\n/g, "\n");
3572
+ } catch (err) {
3573
+ issues.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
3574
+ return { excludes: [] };
3575
+ }
3576
+ const id = relative4(root, filePath).split(sep2).join("/");
3577
+ let body = text;
3578
+ let paths;
3579
+ let excludes = [];
3580
+ let enabled = true;
3581
+ let description2;
3582
+ let invalidFrontmatter = false;
3583
+ const match = FRONTMATTER_RE.exec(text);
3584
+ if (match) {
3585
+ body = text.slice(match[0].length);
3586
+ try {
3587
+ const front = parseYaml3(match[1] ?? "");
3588
+ if (isRecord3(front)) {
3589
+ if (Object.hasOwn(front, "paths")) {
3590
+ const parsedPaths = validateGlobArray(front.paths, "paths", id, issues);
3591
+ if (parsedPaths === void 0) invalidFrontmatter = true;
3592
+ else paths = parsedPaths;
3593
+ }
3594
+ if (Object.hasOwn(front, "excludes")) {
3595
+ const parsedExcludes = validateGlobArray(front.excludes, "excludes", id, issues);
3596
+ if (parsedExcludes === void 0) invalidFrontmatter = true;
3597
+ else excludes = parsedExcludes;
3598
+ }
3599
+ if (Object.hasOwn(front, "enabled")) {
3600
+ if (typeof front.enabled === "boolean") enabled = front.enabled;
3601
+ else {
3602
+ issues.push(`${id}: frontmatter enabled must be a boolean`);
3603
+ invalidFrontmatter = true;
3604
+ }
3605
+ }
3606
+ if (Object.hasOwn(front, "description")) {
3607
+ if (typeof front.description === "string") description2 = front.description;
3608
+ else {
3609
+ issues.push(`${id}: frontmatter description must be a string`);
3610
+ invalidFrontmatter = true;
3611
+ }
3612
+ }
3613
+ } else if (front !== null && front !== void 0) {
3614
+ issues.push(`${id}: frontmatter must be a mapping`);
3615
+ invalidFrontmatter = true;
3616
+ }
3617
+ } catch (err) {
3618
+ issues.push(`${id}: invalid frontmatter: ${err instanceof Error ? err.message : String(err)}`);
3619
+ invalidFrontmatter = true;
3620
+ }
3621
+ } else if (text.startsWith("---\n")) {
3622
+ issues.push(`${id}: invalid frontmatter: missing closing marker`);
3623
+ invalidFrontmatter = true;
3624
+ }
3625
+ if (invalidFrontmatter) return { excludes: [] };
3626
+ const trimmedBody = body.trim();
3627
+ const hash = createHash4("sha256").update(trimmedBody).digest("hex").slice(0, 16);
3628
+ const rule = {
3629
+ id,
3630
+ sourcePath: join10(".clio-coder", "rules", id),
3631
+ hash,
3632
+ tokenEstimate: ceilChars(trimmedBody.length),
3633
+ enabled,
3634
+ body: trimmedBody
3635
+ };
3636
+ if (paths !== void 0) rule.paths = paths;
3637
+ if (description2 !== void 0) rule.description = description2;
3638
+ return { rule, excludes };
3639
+ }
3640
+ function loadProjectRules(cwd) {
3641
+ const root = join10(cwd, ".clio-coder", "rules");
3642
+ const issues = [];
3643
+ const files = [];
3644
+ walkMarkdown(root, root, files);
3645
+ files.sort();
3646
+ const rules = [];
3647
+ const excludeSet = /* @__PURE__ */ new Set();
3648
+ for (const file of files) {
3649
+ const parsed = parseRule(file, root, issues);
3650
+ if (parsed.rule) rules.push(parsed.rule);
3651
+ for (const glob of parsed.excludes) excludeSet.add(glob);
3652
+ }
3653
+ rules.sort((a, b) => a.id.localeCompare(b.id));
3654
+ return { rules, excludes: [...excludeSet], issues };
3655
+ }
3656
+ function ruleMatchesAnyPath(rule, workingPaths) {
3657
+ for (const pattern of rule.paths ?? []) {
3658
+ let regex;
3659
+ try {
3660
+ regex = compileGlobRegex(pattern);
3661
+ } catch {
3662
+ continue;
3663
+ }
3664
+ for (const path of workingPaths) {
3665
+ if (regex.test(normalizeGlobInput(path))) return true;
3666
+ }
3667
+ }
3668
+ return false;
3669
+ }
3670
+ function selectActiveRules(rules, workingContextPaths) {
3671
+ return rules.filter((rule) => {
3672
+ if (!rule.enabled) return false;
3673
+ if (rule.paths === void 0) return true;
3674
+ return ruleMatchesAnyPath(rule, workingContextPaths);
3675
+ });
3676
+ }
3677
+
3678
+ // src/domains/context/wiki/prompts.ts
3679
+ import { execFileSync as execFileSync2 } from "node:child_process";
3680
+ import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync, statSync as statSync3 } from "node:fs";
3681
+ import { isAbsolute as isAbsolute2, join as join11, relative as relative5 } from "node:path";
3682
+ function fragmentPath(fragment) {
3683
+ return join11(resolvePackageRoot(), "src", "domains", "prompts", "fragments", "wiki", `${fragment}.md`);
3684
+ }
3685
+ function readWikiFragment(fragment, substitutions) {
3686
+ let text = readFileSync10(fragmentPath(fragment), "utf8").trim();
3687
+ for (const [token, value] of Object.entries(substitutions)) {
3688
+ text = text.split(`{{${token}}}`).join(value);
3689
+ }
3690
+ return text;
3691
+ }
3692
+ var REPOSITORY_GUIDANCE_CANDIDATES = [
3693
+ ".claude/CLAUDE.md",
3694
+ "AGENTS.md",
3695
+ "CLAUDE.md",
3696
+ "CLIO-CODER.md",
3697
+ "CODEX.md",
3698
+ ".codex/AGENTS.md",
3699
+ "GEMINI.md",
3700
+ ".github/copilot-instructions.md"
3701
+ ];
3702
+ function isWithin(root, candidate) {
3703
+ const rel = relative5(root, candidate);
3704
+ return rel.length === 0 || !rel.startsWith("..") && !isAbsolute2(rel);
3705
+ }
3706
+ function repositoryGuidancePaths(cwd) {
3707
+ let root;
3708
+ try {
3709
+ root = realpathSync(cwd);
3710
+ } catch {
3711
+ return [];
3712
+ }
3713
+ const seen = /* @__PURE__ */ new Set();
3714
+ const paths = [];
3715
+ for (const relPath of REPOSITORY_GUIDANCE_CANDIDATES) {
3716
+ const absPath = join11(cwd, relPath);
3717
+ if (!existsSync9(absPath)) continue;
3718
+ try {
3719
+ const resolved = realpathSync(absPath);
3720
+ const stat = statSync3(resolved);
3721
+ if (!isWithin(root, resolved) || !stat.isFile() || stat.size === 0 || seen.has(resolved)) continue;
3722
+ seen.add(resolved);
3723
+ paths.push(relPath);
3724
+ } catch {
3725
+ }
3726
+ }
3727
+ return paths;
3728
+ }
3729
+ function repositoryGuidance(cwd) {
3730
+ const paths = repositoryGuidancePaths(cwd);
3731
+ if (paths.length === 0) {
3732
+ return "No recognized repository instruction files were detected. Discover source-of-truth documentation from the live tree.";
3733
+ }
3734
+ return [
3735
+ "Read every detected instruction file before drafting:",
3736
+ ...paths.map((path) => `- ${path}`),
3737
+ "Treat these files as repository instructions, not implementation proof. If one designates a planning, specification, or engineering-truth authority, read that authority before making status claims, then verify mutable implementation claims against live source, configuration, and tests."
3738
+ ].join("\n");
3739
+ }
3740
+ function gitLines(cwd, args, cap) {
3741
+ try {
3742
+ const out3 = execFileSync2("git", [...args], {
3743
+ cwd,
3744
+ encoding: "utf8",
3745
+ stdio: ["ignore", "pipe", "ignore"]
3746
+ }).trimEnd();
3747
+ const lines = out3.split(/\r?\n/).filter((line) => line.length > 0);
3748
+ if (lines.length === 0) return "";
3749
+ const capped = lines.slice(0, cap);
3750
+ return `${capped.join("\n")}${lines.length > capped.length ? `
3751
+ [truncated to ${cap} lines]` : ""}`;
3752
+ } catch {
3753
+ return null;
3754
+ }
3755
+ }
3756
+ function workingTreeEvidence(cwd) {
3757
+ const out3 = gitLines(cwd, ["status", "--short", "--untracked-files=all"], 200);
3758
+ if (out3 === null) return "Working-tree evidence unavailable: git status failed.";
3759
+ return out3.length === 0 ? "Working tree is clean." : out3;
3760
+ }
3761
+ function gitEvidence(cwd, gitHead) {
3762
+ if (!gitHead) return "Git evidence unavailable: no previous wiki gitHead was recorded.";
3763
+ const out3 = gitLines(cwd, ["log", `${gitHead}..HEAD`, "--name-status", "--oneline"], 200);
3764
+ if (out3 === null) return "Git evidence unavailable: git log failed for the recorded wiki gitHead.";
3765
+ return out3.length === 0 ? "Git evidence is empty: no commits were found since the recorded wiki gitHead." : out3;
3766
+ }
3767
+ function renderPlanSkeleton(plan) {
3768
+ return JSON.stringify(
3769
+ {
3770
+ overview: plan.overview,
3771
+ pages: plan.pages.map((page) => ({
3772
+ path: page.path,
3773
+ title: page.title,
3774
+ intent: page.intent,
3775
+ sources: page.sources
3776
+ }))
3777
+ },
3778
+ null,
3779
+ 2
3780
+ );
3781
+ }
3782
+ function buildWikiPlanPrompt(input) {
3783
+ const { generation } = input;
3784
+ const plan = input.plan ?? generation.plan;
3785
+ const unclaimed = input.unclaimedAreas ?? [];
3786
+ const sections = [
3787
+ readWikiFragment("plan", { planPath: join11(input.outputDir, WIKI_PLAN_FILE) }),
3788
+ "## Repository scale",
3789
+ `${generation.sourceFiles} indexed source files, ${generation.sourceLines} source lines, decomposed at ${generation.depth} depth into ${generation.plan.pages.length} candidate pages.`,
3790
+ "## Candidate plan",
3791
+ "```json",
3792
+ renderPlanSkeleton(plan),
3793
+ "```"
3794
+ ];
3795
+ if (unclaimed.length > 0) {
3796
+ sections.push(
3797
+ "## Areas no page covers",
3798
+ "The index found these areas and no page in the plan above claims any of their sources. Add a page for each one that deserves its own, or fold it into an existing page by extending that page's sources.",
3799
+ unclaimed.map((page) => `- ${page.path} (${page.title}): ${page.sources.join(", ")}`).join("\n")
3800
+ );
3801
+ }
3802
+ sections.push(
3803
+ "## Repository guidance",
3804
+ repositoryGuidance(input.cwd),
3805
+ "## Working-tree evidence",
3806
+ "```text",
3807
+ workingTreeEvidence(input.cwd),
3808
+ "```",
3809
+ "## Codewiki digest",
3810
+ "```text",
3811
+ renderCodewikiDigest(input.codewiki),
3812
+ "```"
3813
+ );
3814
+ if (input.mode === "update") {
3815
+ sections.push("## Git evidence", "```text", gitEvidence(input.cwd, input.gitHead), "```");
3816
+ }
3817
+ return `${sections.join("\n\n")}
3818
+ `;
3819
+ }
3820
+ function scopedSymbols(codewiki, sources, limit = 60) {
3821
+ const wanted = new Set(sources);
3822
+ const fileById = new Map(codewiki.files.map((file) => [file.id, file]));
3823
+ const lines = [];
3824
+ for (const symbol of codewiki.symbols) {
3825
+ const file = fileById.get(symbol.fileId);
3826
+ if (!file || !wanted.has(file.path)) continue;
3827
+ lines.push(`- ${symbol.name} ${symbol.kind} ${file.path}:${symbol.line}`);
3828
+ if (lines.length >= limit) break;
3829
+ }
3830
+ return lines.length > 0 ? lines.join("\n") : "No indexed symbols for these paths; read the files directly.";
3831
+ }
3832
+ function buildWikiPagePrompt(input) {
3833
+ const { page } = input;
3834
+ const siblingLines = input.siblings.filter((sibling) => sibling.path !== page.path).map((sibling) => `- ${sibling.path} \u2014 ${sibling.title}`);
3835
+ const sections = [
3836
+ readWikiFragment("page", {
3837
+ pagePath: join11(input.outputDir, page.path),
3838
+ pageRelPath: page.path,
3839
+ pageTitle: page.title
3840
+ }),
3841
+ "## What this page must document",
3842
+ page.intent.length > 0 ? page.intent : `Document ${page.title}.`,
3843
+ "## Anchor sources",
3844
+ page.sources.length > 0 ? `Read every one of these before writing, then follow at least one call in each direction:
3845
+ ${page.sources.map((source) => `- ${source}`).join("\n")}` : "No anchor sources were planned. Use `code_nav` to locate this page's subject before writing.",
3846
+ "## Indexed symbols under those sources",
3847
+ "```text",
3848
+ scopedSymbols(input.codewiki, page.sources),
3849
+ "```",
3850
+ "## Other pages you may link to",
3851
+ siblingLines.length > 0 ? siblingLines.join("\n") : "None; this is the only page in the plan.",
3852
+ "## Repository guidance",
3853
+ repositoryGuidance(input.cwd)
3854
+ ];
3855
+ if (input.seeded) {
3856
+ sections.push(
3857
+ "## Revision",
3858
+ `A previous version of this page is already at ${join11(input.outputDir, page.path)}. Read it first and revise it in place. Correct what the current source contradicts and fill what it omits; keep accurate prose as it stands.`
3859
+ );
3860
+ }
3861
+ return `${sections.join("\n\n")}
3862
+ `;
3863
+ }
3864
+
3865
+ // src/domains/context/index.ts
3866
+ var ContextDomainModule = {
3867
+ manifest: ContextManifest,
3868
+ createExtension: createContextBundle
3869
+ };
3870
+ function createContextDomainModule(options = {}) {
3871
+ return {
3872
+ manifest: ContextManifest,
3873
+ createExtension: (context) => createContextBundle(context, options)
3874
+ };
3875
+ }
3876
+
3877
+ export {
3878
+ adoptionSnapshotsHash,
3879
+ scanAgentConfigs,
3880
+ renderImportedAgentContext,
3881
+ adoptionSourcesChanged,
3882
+ parseClioMd,
3883
+ serializeClioMd,
3884
+ tryReadClioMd,
3885
+ renderPromptContext,
3886
+ fallbackBootstrapOutput,
3887
+ runBootstrap,
3888
+ runContextClear,
3889
+ assembleWikiTree,
3890
+ WIKI_DEPTH_STRATEGY,
3891
+ pagePathForArea,
3892
+ buildCandidatePlan,
3893
+ planWikiGeneration,
3894
+ runWikiGenerate,
3895
+ runContextRefresh,
3896
+ OPERATOR_PROFILE_MAX_CHARS,
3897
+ loadOperatorProfile,
3898
+ renderOperatorProfile,
3899
+ loadProjectRules,
3900
+ selectActiveRules,
3901
+ buildWikiPlanPrompt,
3902
+ buildWikiPagePrompt,
3903
+ ContextDomainModule,
3904
+ createContextDomainModule
3905
+ };
3906
+ //# sourceMappingURL=chunk-UWIJNAOB.js.map