@genn-inc/cluebase-cli 0.0.1

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 (38) hide show
  1. package/README.md +101 -0
  2. package/bin/cluebase-cli.mjs +11 -0
  3. package/package.json +17 -0
  4. package/src/cli-command.mjs +515 -0
  5. package/src/cli-invocation.mjs +17 -0
  6. package/src/code-evidence-analyzer.mjs +2041 -0
  7. package/src/contracts.mjs +36 -0
  8. package/src/generated-code-evidence-contract.mjs +22 -0
  9. package/src/generated-sdk-version-contract.mjs +5 -0
  10. package/src/generated-source-path-policy.mjs +20 -0
  11. package/src/lifecycle-guard.mjs +202 -0
  12. package/src/path-policy.mjs +81 -0
  13. package/src/setup-ai-contract.mjs +221 -0
  14. package/src/setup-check-constants.mjs +110 -0
  15. package/src/setup-check-scan-a.mjs +849 -0
  16. package/src/setup-check-scan-b.mjs +994 -0
  17. package/src/setup-check.mjs +575 -0
  18. package/src/setup-discover-check.mjs +755 -0
  19. package/src/setup-doctor-deadline.mjs +221 -0
  20. package/src/setup-doctor-env.mjs +331 -0
  21. package/src/setup-doctor-file-boundary.mjs +426 -0
  22. package/src/setup-doctor-probe.mjs +719 -0
  23. package/src/setup-doctor-quality-checks-a.mjs +593 -0
  24. package/src/setup-doctor-quality-checks-b.mjs +638 -0
  25. package/src/setup-doctor-quality-shared.mjs +382 -0
  26. package/src/setup-doctor-quality.mjs +209 -0
  27. package/src/setup-doctor-route-scan.mjs +160 -0
  28. package/src/setup-doctor-sdk-probe.mjs +340 -0
  29. package/src/setup-doctor.mjs +545 -0
  30. package/src/setup-documents.mjs +112 -0
  31. package/src/setup-help.mjs +130 -0
  32. package/src/setup-prepare.mjs +360 -0
  33. package/src/setup-repository-discovery.mjs +764 -0
  34. package/src/setup-step-builders-discover.mjs +701 -0
  35. package/src/setup-step-builders-events.mjs +229 -0
  36. package/src/setup-step-builders-implement.mjs +710 -0
  37. package/src/setup-step-commands.mjs +427 -0
  38. package/src/setup-tool.mjs +27 -0
@@ -0,0 +1,427 @@
1
+ // Slash command content generator for the Cluebase setup flow.
2
+ //
3
+ // Generates 9 step-level slash command files under `.claude/commands/`:
4
+ //
5
+ // - cluebase-discover.md — STEP 1: boundary discovery (AI grep + Read)
6
+ // - cluebase-discover-review.md — STEP 2: self-review of STEP 1 output (AI)
7
+ // - cluebase-discover-context.md — STEP 3: field-semantic enrichment (AI)
8
+ // - cluebase-discover-check.md — STEP 4: bash validation of discoveries.json
9
+ // - cluebase-implement.md — STEP 5: SDK call insertion + env file write
10
+ // - cluebase-implement-check.md — STEP 6: bash static check + diff snapshot
11
+ // - cluebase-implement-review.md — STEP 7: self-review of inserted code (AI)
12
+ // - cluebase-events.md — STEP 8: guided business value event track (AI)
13
+ // - cluebase-doctor.md — STEP 9: bash connectivity check
14
+ //
15
+ // The customer types ONE slash command at a time and the AI coding tool
16
+ // performs a single, narrowly-scoped responsibility per turn. Each STEP
17
+ // finishes with a Japanese one-line hand-off telling the user which
18
+ // slash command to type next. State recovery is encoded directly in
19
+ // each STEP so re-running a STEP after a crash resumes from the last
20
+ // completed sub-task (per `_progress.completed_substeps` in
21
+ // `.cluebase/discoveries.json` / `completed_files` in
22
+ // `.cluebase/implementation.json`).
23
+ //
24
+ // One command = one responsibility: shorter prompts, sharper goal, fewer
25
+ // cross-step responsibility leaks.
26
+ //
27
+ // =========================================================================
28
+ // WHERE THE SETUP PROMPT CONTENT LIVES:
29
+ //
30
+ // The AI prompt content for STEP 1 / 2 / 3 / 5 / 7 below is written here and
31
+ // nowhere else. The web setup surface does not carry it: what that surface
32
+ // generates is the environment block and the CLI invocation
33
+ // (`apps/web/src/features/setup/code-snippets.ts`), not these prompts. So a
34
+ // customer who never runs the CLI never sees the load-bearing rules stated
35
+ // below -- the two-argument `cluebase.identify` signature, the dict-only Python
36
+ // `cluebase.init` signature, where the lifecycle call belongs, and the rule
37
+ // against adding `cluebase.track`.
38
+ //
39
+ // Nothing checks that. There is no second copy to drift from and no test
40
+ // comparing the two surfaces, so changing a rule here changes it for CLI
41
+ // customers only, silently. Anyone adding these rules to the web surface has
42
+ // to add the comparison at the same time, or the copies part without a
43
+ // failure anywhere.
44
+ //
45
+ // =========================================================================
46
+
47
+ import { mkdir, writeFile } from "node:fs/promises";
48
+ import { dirname, join, relative } from "node:path";
49
+ import {
50
+ buildStep1Discover,
51
+ buildStep2SelfReview,
52
+ buildStep3DiscoverContext,
53
+ buildStep4DiscoverCheck,
54
+ } from "./setup-step-builders-discover.mjs";
55
+ import {
56
+ buildStep5Implement,
57
+ buildStep6SetupCheck,
58
+ buildStep7Review,
59
+ buildStep9SetupDoctor,
60
+ } from "./setup-step-builders-implement.mjs";
61
+ import { buildStep8ValueEvents } from "./setup-step-builders-events.mjs";
62
+
63
+ // --- Step file definition ------------------------------------------------
64
+ //
65
+ // Each entry below produces ONE file under `.claude/commands/`. Each STEP
66
+ // represents ONE atomic responsibility: the AI tool runs it, completes
67
+ // the single goal, persists progress to
68
+ // `.cluebase/discoveries.json:_progress.completed_substeps`, prints the
69
+ // Japanese hand-off line for the next STEP, and stops.
70
+ //
71
+ // `kind`:
72
+ // - "ai" = the prompt instructs the AI tool to perform Read / Grep /
73
+ // Glob / Write / Edit operations.
74
+ // - "bash" = the prompt instructs the AI to run a cluebase-cli helper
75
+ // command via Bash (setup-discover-check / setup-check /
76
+ // setup-doctor).
77
+ //
78
+ // `id` is the marker recorded in
79
+ // `.cluebase/discoveries.json:_progress.completed_substeps`. Re-running a
80
+ // STEP after a crash uses this id to skip already-completed work.
81
+
82
+ const STEP_FILES = [
83
+ {
84
+ index: 1,
85
+ file: "cluebase-discover.md",
86
+ kind: "ai",
87
+ builder: buildStep1Discover,
88
+ id: "step1_discover",
89
+ title: "STEP 1 — Discover (boundary enumeration)",
90
+ singleGoal:
91
+ "The ONLY goal of this command is to grep + Read the customer codebase exhaustively and produce the initial `.cluebase/discoveries.json` (lifecycle boundary candidates + db_schema grounding + env file paths). SDK call insertion, env file writes, code changes, and connectivity checks are ALL out of scope.",
92
+ scopeOut: [
93
+ "STEP 2 (= /cluebase-discover-review): self-review / fix of discoveries.json",
94
+ "STEP 3 (= /cluebase-discover-context): field-semantic enrichment (`available_fields` / `organization_context`)",
95
+ "STEP 4 (= /cluebase-discover-check): bash schema validation",
96
+ "STEP 5+ (= /cluebase-implement and later): any source code change",
97
+ ],
98
+ completion:
99
+ "`.cluebase/discoveries.json` is written with all boundaries + db_schema + env_files populated, `_progress.completed_substeps` is patched with `step1_discover`, and the Japanese hand-off line for STEP 2 is printed.",
100
+ nextHandoff: "/cluebase-discover-review",
101
+ },
102
+ {
103
+ index: 2,
104
+ file: "cluebase-discover-review.md",
105
+ kind: "ai",
106
+ builder: buildStep2SelfReview,
107
+ id: "step2_review",
108
+ title: "STEP 2 — Self-review of discoveries.json",
109
+ singleGoal:
110
+ "The ONLY goal of this command is to re-examine the existing `.cluebase/discoveries.json` and apply the rubric (server-side handler exclusion, read-path re-check, completeness sweep, evidence integrity, cluebase_init_* file existence, sentinel guard safety). The file may be rewritten in place. NO other artifact is touched.",
111
+ scopeOut: [
112
+ "STEP 1 (= /cluebase-discover): initial boundary enumeration (do NOT re-grep the entire repo from scratch)",
113
+ "STEP 3 (= /cluebase-discover-context): adding `available_fields` / `organization_context` (left to STEP 3)",
114
+ "STEP 5+ (= /cluebase-implement and later): code changes",
115
+ ],
116
+ completion:
117
+ "`.cluebase/discoveries.json` converges (rubric clean within 3 iterations), `_progress.completed_substeps` is patched with `step2_review`, and the Japanese hand-off line for STEP 3 is printed.",
118
+ nextHandoff: "/cluebase-discover-context",
119
+ },
120
+ {
121
+ index: 3,
122
+ file: "cluebase-discover-context.md",
123
+ kind: "ai",
124
+ builder: buildStep3DiscoverContext,
125
+ id: "step3_context",
126
+ title: "STEP 3 — Field semantic context enrichment",
127
+ singleGoal:
128
+ "The ONLY goal of this command is to enrich `.cluebase/discoveries.json` with two new pieces: per-site `available_fields` (concrete variable paths reachable at each insertion line) + top-level `organization_context` (the customer's company/organization label) + `env_lines` pre-computed for STEP 5. The customer code is NOT modified.",
129
+ scopeOut: [
130
+ "STEP 1 (= /cluebase-discover): boundary discovery",
131
+ "STEP 2 (= /cluebase-discover-review): rubric-based self-review",
132
+ "STEP 4 (= /cluebase-discover-check): bash validation",
133
+ "STEP 5+ (= /cluebase-implement and later): code edits, env file writes",
134
+ ],
135
+ completion:
136
+ "`.cluebase/discoveries.json` gains `available_fields` / `organization_context` / `env_lines`, `_progress.completed_substeps` is patched with `step3_context`, and the Japanese hand-off line for STEP 4 is printed.",
137
+ nextHandoff: "/cluebase-discover-check",
138
+ },
139
+ {
140
+ index: 4,
141
+ file: "cluebase-discover-check.md",
142
+ kind: "bash",
143
+ builder: buildStep4DiscoverCheck,
144
+ id: "step4_check",
145
+ title: "STEP 4 — Bash validation of discoveries.json",
146
+ singleGoal:
147
+ "The ONLY goal of this command is to mechanically validate `.cluebase/discoveries.json` via the `setup-discover-check` CLI helper, surface any error/warning, and (when an error is found) instruct the user which earlier STEP to re-run by removing the matching substepId from `_progress.completed_substeps`. No AI judgment, no code edits.",
148
+ scopeOut: [
149
+ "STEP 1-3: writing or modifying `.cluebase/discoveries.json` itself (only substepId removal allowed when re-running an earlier STEP)",
150
+ "STEP 5+ (= /cluebase-implement and later): code edits, env file writes",
151
+ ],
152
+ completion:
153
+ "`setup-discover-check` reports passed=true, `_progress.completed_substeps` is patched with `step4_check`, and the Japanese hand-off line for STEP 5 is printed. On failure: the user is told exactly which earlier STEP to re-run.",
154
+ nextHandoff: "/cluebase-implement",
155
+ },
156
+ {
157
+ index: 5,
158
+ file: "cluebase-implement.md",
159
+ kind: "ai",
160
+ builder: buildStep5Implement,
161
+ id: "step5_implement",
162
+ title: "STEP 5 — Implement (SDK call insertion + env file write)",
163
+ singleGoal:
164
+ "The ONLY goal of this command is to insert Cluebase SDK lifecycle calls (cluebase.init / cluebase.identify / cluebase.group / cluebase.reset + observer wire-ups) at the file:line positions recorded in `.cluebase/discoveries.json`, add the SDK as a package-manifest dependency, run the install command, and write the pre-computed `env_lines` into the customer's frontend/backend env files. NO discoveries.json edits, NO bash validation, NO connectivity checks.",
165
+ scopeOut: [
166
+ "STEP 1-4: modifying `.cluebase/discoveries.json` (it is read-only here)",
167
+ "STEP 6 (= /cluebase-implement-check): bash static check + diff snapshot generation",
168
+ "STEP 7 (= /cluebase-implement-review): self-review of the inserted code",
169
+ "STEP 8 (= /cluebase-events): business value event track insertion (cluebase.track is added there, not here)",
170
+ "STEP 9 (= /cluebase-doctor): real-server connectivity check",
171
+ ],
172
+ completion:
173
+ "All lifecycle / observer call sites in discoveries.json are inserted via Edit, SDK install is run successfully, env files are upserted with `env_lines`, `_progress.completed_substeps` is patched with `step5_implement`, and the Japanese hand-off line for STEP 6 is printed.",
174
+ nextHandoff: "/cluebase-implement-check",
175
+ },
176
+ {
177
+ index: 6,
178
+ file: "cluebase-implement-check.md",
179
+ kind: "bash",
180
+ builder: buildStep6SetupCheck,
181
+ id: "step6_check",
182
+ title: "STEP 6 — Bash static check + diff snapshot",
183
+ singleGoal:
184
+ "The ONLY goal of this command is to run `setup-check --require-sdk-lifecycle --repo . --write-snapshot` via Bash. This writes `.cluebase/setup-check.json` (the report) and `.cluebase/setup-diff.patch` (the diff snapshot). The AI tool MUST NOT interpret the JSON here — STEP 7 owns interpretation + auto-fix.",
185
+ scopeOut: [
186
+ "STEP 5: inserting lifecycle calls (already done)",
187
+ "STEP 7 (= /cluebase-implement-review): interpreting setup-check.json, applying P0/P1 fixes, loop logic",
188
+ "STEP 9 (= /cluebase-doctor): connectivity check",
189
+ ],
190
+ completion:
191
+ "`.cluebase/setup-check.json` and `.cluebase/setup-diff.patch` are written, `_progress.completed_substeps` is patched with `step6_check`, and the Japanese hand-off line for STEP 7 is printed.",
192
+ nextHandoff: "/cluebase-implement-review",
193
+ },
194
+ {
195
+ index: 7,
196
+ file: "cluebase-implement-review.md",
197
+ kind: "ai",
198
+ builder: buildStep7Review,
199
+ id: "step7_review",
200
+ title: "STEP 7 — Self-review + Auto-fix loop",
201
+ singleGoal:
202
+ "The ONLY goal of this command is to audit the implementation diff against the P0/P1 rubric, surgically fix any P0/P1 issue via Edit, refresh setup-check.json via Bash, and loop up to 3 iterations until convergence. `.cluebase/setup-review-findings.md` is written with either `NO_P0_P1` or the residual table.",
203
+ scopeOut: [
204
+ "STEP 5: inserting lifecycle calls (already done)",
205
+ "STEP 6: running setup-check (already done; only re-runs are allowed inside the loop)",
206
+ "STEP 8 (= /cluebase-events): business value event track insertion",
207
+ "STEP 9 (= /cluebase-doctor): real-server connectivity check",
208
+ "Modifying `.cluebase/discoveries.json` (it is read-only here)",
209
+ ],
210
+ completion:
211
+ "Either no P0/P1 remains (`setup-review-findings.md` says `NO_P0_P1`) or 3 iterations completed with residual findings printed to the user, `_progress.completed_substeps` is patched with `step7_review`, and the Japanese hand-off line for STEP 8 is printed.",
212
+ nextHandoff: "/cluebase-events",
213
+ },
214
+ {
215
+ index: 8,
216
+ file: "cluebase-events.md",
217
+ kind: "ai",
218
+ builder: buildStep8ValueEvents,
219
+ id: "step8_events",
220
+ title: "STEP 8 — Business value events (guided cluebase.track)",
221
+ singleGoal:
222
+ "The ONLY goal of this command is to instrument the customer's selected business VALUE milestones (paid conversion / cancellation / plan change / signup / invite / activation / any custom milestone) with a single cluebase.track call at the authoritative site, verify arrival + account linkage via the existing setup doctor, and record the mapping in `.cluebase/business-events.json`. Business value events are the ONLY place cluebase.track is added; lifecycle wiring, env writes, and connectivity gating are out of scope.",
223
+ scopeOut: [
224
+ "STEP 1-4 (= /cluebase-discover .. /cluebase-discover-check): re-discovering or editing lifecycle boundaries in discoveries.json (read-only grounding here)",
225
+ "STEP 5 (= /cluebase-implement): cluebase.init / cluebase.identify / cluebase.group / cluebase.reset placement + env writes (already done; do NOT add track there)",
226
+ "STEP 9 (= /cluebase-doctor): the holistic production-level connectivity gate",
227
+ ],
228
+ completion:
229
+ "Every selected value milestone is either instrumented with one cluebase.track at its authoritative site, recorded as external-source-only, or the selection is empty; `.cluebase/business-events.json` records the mapping, at least one arrival verification is attempted via `setup-doctor --local` + published batch evidence, `_progress.completed_substeps` is patched with `step8_events`, and the Japanese hand-off line for STEP 9 is printed.",
230
+ nextHandoff: "/cluebase-doctor",
231
+ },
232
+ {
233
+ index: 9,
234
+ file: "cluebase-doctor.md",
235
+ kind: "bash",
236
+ builder: buildStep9SetupDoctor,
237
+ id: "step9_doctor",
238
+ title: "STEP 9 — Production-level setup verification",
239
+ singleGoal:
240
+ "The ONLY goal of this command is to run `setup-doctor --local` via Bash. It probes Cluebase token issue, browser ingest, and backend ingest with SDK-equivalent payloads from customer env files, checks downstream browser/backend batch publish evidence, scans for forbidden customer-backend Cluebase routes, and runs 30 data-quality checks. `/cluebase-doctor` is intentionally STATELESS — re-run any time after fixes.",
241
+ scopeOut: [
242
+ "STEP 1-8 (= /cluebase-discover .. /cluebase-events): anything that modifies discoveries.json, customer code, business-events.json, or runs the static check",
243
+ "Persisting progress (this STEP is stateless — DO NOT write to the `_progress` array; it is intentionally re-runnable)",
244
+ ],
245
+ completion:
246
+ "`setup-doctor --local` either passes (API/published checks + downstream browser/backend batch publish evidence + customer-backend Cluebase route scan + data-quality error checks green) and the Japanese success message is printed, or fails and the user is given concrete env-fix / dev-server-restart instructions plus the offer to re-run `/cluebase-doctor`.",
247
+ nextHandoff: null,
248
+ },
249
+ ];
250
+
251
+ // State-recovery contract embedded at the top of every stateful STEP
252
+ // (STEP 1-8). STEP 9 is stateless — see STATELESS_STATE_HEADER. The
253
+ // contract MUST be small enough to keep the per-STEP prompt short, so we
254
+ // keep it to the absolute essentials (skip-if-completed + patch-on-finish).
255
+ const STATEFUL_STATE_HEADER = `## State recovery (read BEFORE doing anything)
256
+
257
+ This command is **idempotent + resumable**. Before doing any work:
258
+
259
+ 1. Use the Read tool on \`.cluebase/discoveries.json\`. If \`_progress.completed_substeps\` exists and contains \`<SUBSTEP_ID>\`, this STEP has already finished — respond with the single Japanese line "STEP <STEP_INDEX> は完了済みです。 次は Claude Code で <NEXT_HANDOFF> を実行してください。" and STOP. Do NOT redo the work.
260
+ 2. If the substepId is NOT present (= first run, or a previous crash), proceed with the STEP body below.
261
+ 3. **After the STEP body completes successfully**, use the Write/Edit tool to patch \`.cluebase/discoveries.json\` so its top-level \`_progress\` field is:
262
+ \`\`\`jsonc
263
+ {
264
+ "_progress": {
265
+ "completed_substeps": ["<previously-completed ids>", "<SUBSTEP_ID>"],
266
+ "last_updated_at": "<ISO 8601 UTC timestamp>"
267
+ }
268
+ }
269
+ \`\`\`
270
+ Preserve every other top-level field. The array dedups — do not add a duplicate id.
271
+ 4. **STEP 5 only** — also append per-file completion to \`.cluebase/implementation.json\`'s \`completed_files\` array. Before re-inserting into a file, check the array; if the file is present, skip that insertion (the entry is the proof of prior work).
272
+ 5. **Customer escape hatch**: \`rm -rf .cluebase/\` followed by re-running the STEP gives a clean re-run.
273
+
274
+ ---
275
+
276
+ `;
277
+
278
+ const STATELESS_STATE_HEADER = `## Stateless health check (read BEFORE doing anything)
279
+
280
+ \`/cluebase-doctor\` does NOT write to \`.cluebase/discoveries.json:_progress\` or \`.cluebase/implementation.json\`. Re-run it any time after env / dev-server / project-key changes — there is no "completed" state to skip.
281
+
282
+ ---
283
+
284
+ `;
285
+
286
+ // Compose a single STEP's slash command body by wrapping the existing
287
+ // per-STEP builder output with:
288
+ // - a header that pins this STEP's single goal + scope-out items +
289
+ // completion criterion (so the AI tool sees the responsibility
290
+ // contract BEFORE the long instruction body),
291
+ // - the state-recovery contract,
292
+ // - the original per-STEP prompt body produced by `builder(...)`,
293
+ // - a small footer reminding the AI to record progress + print the
294
+ // hand-off line for the next STEP.
295
+ function buildStepBody(step, { documentsUrl }) {
296
+ const goalLine = `## SINGLE GOAL (= this command's only purpose)\n\n${step.singleGoal}`;
297
+ const scopeOutBlock = step.scopeOut.length
298
+ ? `## SCOPE OUT (= DO NOT do these — they belong to other STEP commands)\n\n${step.scopeOut.map((line) => `- ${line}`).join("\n")}`
299
+ : "";
300
+ const completionBlock = `## Completion criterion\n\n${step.completion}`;
301
+ const handoffLine = step.nextHandoff
302
+ ? `Hand-off (last action of this STEP): print one Japanese line in this exact shape and STOP — \`<STEP ${step.index} completion summary>。 次は Claude Code で ${step.nextHandoff} を実行してください。\` Substitute concrete numbers / counts where the per-STEP body specifies them; the per-STEP body below provides the exact wording.`
303
+ : `Hand-off (last action of this STEP): print the STEP 9 success or failure block in Japanese exactly as specified by the per-STEP body below, then STOP.`;
304
+
305
+ const stateHeader =
306
+ step.id === "step9_doctor"
307
+ ? STATELESS_STATE_HEADER
308
+ : STATEFUL_STATE_HEADER.replaceAll("<SUBSTEP_ID>", step.id)
309
+ .replaceAll("<STEP_INDEX>", String(step.index))
310
+ .replaceAll("<NEXT_HANDOFF>", step.nextHandoff ?? "/cluebase-doctor");
311
+
312
+ const body = step.builder({ documentsUrl });
313
+
314
+ return `# ${step.title} (= ${step.file.replace(/\.md$/, "")})
315
+
316
+ ${goalLine}
317
+
318
+ ${scopeOutBlock}
319
+
320
+ ${completionBlock}
321
+
322
+ ${stateHeader}## STEP body
323
+
324
+ ${body}
325
+
326
+ ---
327
+
328
+ ${handoffLine}
329
+ `;
330
+ }
331
+
332
+ const STEP_COMMAND_NAMES = STEP_FILES.map((step) =>
333
+ step.file.replace(/\.md$/, ""),
334
+ );
335
+
336
+ const toCodexStepInvocation = (commandName) => `$${commandName}`;
337
+
338
+ const toCodexSkillText = (text) => {
339
+ let normalized = text
340
+ .replaceAll("Claude Code", "Codex")
341
+ .replaceAll("Claude writes", "Codex writes")
342
+ .replaceAll("Claude sets", "Codex sets")
343
+ .replaceAll("Claude setup", "Codex setup")
344
+ .replaceAll("Claude", "Codex")
345
+ .replaceAll("slash command", "Codex skill command")
346
+ .replaceAll("slash commands", "Codex skill commands");
347
+
348
+ for (const commandName of STEP_COMMAND_NAMES) {
349
+ normalized = normalized.replaceAll(
350
+ `/${commandName}`,
351
+ toCodexStepInvocation(commandName),
352
+ );
353
+ }
354
+
355
+ return normalized;
356
+ };
357
+
358
+ function buildCodexStepSkillBody(step, { documentsUrl }) {
359
+ const commandName = step.file.replace(/\.md$/, "");
360
+ const body = toCodexSkillText(buildStepBody(step, { documentsUrl }));
361
+ return `---
362
+ name: ${commandName}
363
+ description: Use in Codex when running Cluebase setup STEP ${step.index} (${step.title}).
364
+ setup_step_id: ${step.id}
365
+ ---
366
+
367
+ ${body}`;
368
+ }
369
+
370
+ // Test hook: expose the step builders so external tests can assert the
371
+ // generated prompt content (semantic-detection language, halt rules, db_schema
372
+ // grounding instructions, etc.) without going through filesystem writes.
373
+ // This is a documented internal surface — keep the keys stable.
374
+ export const STEP_BUILDERS_FOR_TEST = {
375
+ step1Discover: buildStep1Discover,
376
+ step2SelfReview: buildStep2SelfReview,
377
+ step3DiscoverContext: buildStep3DiscoverContext,
378
+ step4DiscoverCheck: buildStep4DiscoverCheck,
379
+ step5Implement: buildStep5Implement,
380
+ step6SetupCheck: buildStep6SetupCheck,
381
+ step7Review: buildStep7Review,
382
+ step8ValueEvents: buildStep8ValueEvents,
383
+ step9SetupDoctor: buildStep9SetupDoctor,
384
+ };
385
+
386
+ // Test hook for the 8-file step layout. Returns the step definitions so
387
+ // tests can assert single-goal headers / scope-out blocks / state-recovery
388
+ // header presence / per-step file mapping without going through
389
+ // filesystem writes.
390
+ export const STEP_DEFINITIONS_FOR_TEST = STEP_FILES;
391
+ export { buildStepBody as BUILD_STEP_BODY_FOR_TEST };
392
+ export { buildCodexStepSkillBody as BUILD_CODEX_STEP_SKILL_BODY_FOR_TEST };
393
+
394
+ export async function writeCluebaseStepCommands({ repoRoot, documentsUrl }) {
395
+ const commandsDir = join(repoRoot, ".claude", "commands");
396
+ await mkdir(commandsDir, { recursive: true });
397
+
398
+ const writtenAbs = [];
399
+ for (const step of STEP_FILES) {
400
+ const content = buildStepBody(step, { documentsUrl });
401
+ const absPath = join(commandsDir, step.file);
402
+ await writeFile(absPath, content, "utf8");
403
+ writtenAbs.push(absPath);
404
+ }
405
+
406
+ return writtenAbs.map((abs) => relative(repoRoot, abs));
407
+ }
408
+
409
+ export async function writeCodexStepSkills({ repoRoot, documentsUrl }) {
410
+ const skillsDir = join(repoRoot, ".agents", "skills");
411
+ await mkdir(skillsDir, { recursive: true });
412
+
413
+ const writtenAbs = [];
414
+ for (const step of STEP_FILES) {
415
+ const commandName = step.file.replace(/\.md$/, "");
416
+ const absPath = join(skillsDir, commandName, "SKILL.md");
417
+ await mkdir(dirname(absPath), { recursive: true });
418
+ await writeFile(
419
+ absPath,
420
+ buildCodexStepSkillBody(step, { documentsUrl }),
421
+ "utf8",
422
+ );
423
+ writtenAbs.push(absPath);
424
+ }
425
+
426
+ return writtenAbs.map((abs) => relative(repoRoot, abs));
427
+ }
@@ -0,0 +1,27 @@
1
+ import { resolve } from "node:path";
2
+
3
+ /**
4
+ * Install the canonical nine setup commands for both supported coding tools.
5
+ * The setup flow owns one command per step; role-based skill bundles are not
6
+ * part of the customer-facing MVP contract.
7
+ */
8
+ export const installSetupSteps = async ({ repoRoot, documentsUrl } = {}) => {
9
+ const resolvedRepoRoot = resolve(repoRoot ?? ".");
10
+ const { writeCluebaseStepCommands, writeCodexStepSkills } = await import(
11
+ "./setup-step-commands.mjs",
12
+ );
13
+
14
+ const setupCommands = await writeCluebaseStepCommands({
15
+ repoRoot: resolvedRepoRoot,
16
+ documentsUrl: documentsUrl ?? "",
17
+ });
18
+ const setupSkills = await writeCodexStepSkills({
19
+ repoRoot: resolvedRepoRoot,
20
+ documentsUrl: documentsUrl ?? "",
21
+ });
22
+
23
+ return {
24
+ setup_commands: setupCommands,
25
+ codex_setup_skills: setupSkills,
26
+ };
27
+ };