sb-codex-tool 0.1.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.
package/dist/cli.js ADDED
@@ -0,0 +1,3583 @@
1
+ // src/lib/assignment.ts
2
+ import path5 from "node:path";
3
+
4
+ // src/lib/cycle.ts
5
+ import path2 from "node:path";
6
+
7
+ // src/lib/fs.ts
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import path from "node:path";
10
+ function ensureDir(directory) {
11
+ mkdirSync(directory, { recursive: true });
12
+ }
13
+ function readTextIfPresent(filePath) {
14
+ if (!existsSync(filePath)) {
15
+ return null;
16
+ }
17
+ return readFileSync(filePath, "utf8");
18
+ }
19
+ function readJsonIfPresent(filePath) {
20
+ const text = readTextIfPresent(filePath);
21
+ if (text === null) {
22
+ return null;
23
+ }
24
+ return JSON.parse(text);
25
+ }
26
+ function writeFileIfMissing(filePath, content) {
27
+ ensureDir(path.dirname(filePath));
28
+ if (existsSync(filePath)) {
29
+ return "kept";
30
+ }
31
+ writeFileSync(filePath, content, "utf8");
32
+ return "created";
33
+ }
34
+ function writeFileIfChanged(filePath, content) {
35
+ ensureDir(path.dirname(filePath));
36
+ if (!existsSync(filePath)) {
37
+ writeFileSync(filePath, content, "utf8");
38
+ return "created";
39
+ }
40
+ const current = readFileSync(filePath, "utf8");
41
+ if (current === content) {
42
+ return "unchanged";
43
+ }
44
+ writeFileSync(filePath, content, "utf8");
45
+ return "updated";
46
+ }
47
+ function ensureLines(filePath, lines, header) {
48
+ ensureDir(path.dirname(filePath));
49
+ if (!existsSync(filePath)) {
50
+ const content = `${header}
51
+
52
+ ${lines.join("\n")}
53
+ `;
54
+ writeFileSync(filePath, content, "utf8");
55
+ return { created: true, added: [...lines] };
56
+ }
57
+ const current = readFileSync(filePath, "utf8");
58
+ const missing = lines.filter((line) => !current.includes(line));
59
+ if (missing.length === 0) {
60
+ return { created: false, added: [] };
61
+ }
62
+ const next = current.includes(header) ? `${current.trimEnd()}
63
+ ${missing.join("\n")}
64
+ ` : `${current.trimEnd()}
65
+
66
+ ${header}
67
+
68
+ ${missing.join("\n")}
69
+ `;
70
+ writeFileSync(filePath, next, "utf8");
71
+ return { created: false, added: missing };
72
+ }
73
+
74
+ // src/lib/cycle.ts
75
+ function formatDateStamp(date = /* @__PURE__ */ new Date()) {
76
+ const year = String(date.getFullYear());
77
+ const month = String(date.getMonth() + 1).padStart(2, "0");
78
+ const day = String(date.getDate()).padStart(2, "0");
79
+ return `${year}-${month}-${day}`;
80
+ }
81
+ function normalizeSlug(input) {
82
+ return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
83
+ }
84
+ function humanizeSlug(slug) {
85
+ return slug.split("-").filter((part) => part.length > 0).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
86
+ }
87
+ function parseCycleDescriptor(planPath) {
88
+ const match = planPath.match(
89
+ /^\.sb-codex-tool\/plans\/(\d{4}-\d{2}-\d{2})-(.+)-approved\.md$/
90
+ );
91
+ if (match === null) {
92
+ throw new Error(`Unable to derive cycle metadata from ${planPath}.`);
93
+ }
94
+ return {
95
+ dateStamp: match[1],
96
+ slug: match[2]
97
+ };
98
+ }
99
+ function readCycleTitle(root, planPath, fallbackSlug) {
100
+ const text = readTextIfPresent(path2.join(root, planPath));
101
+ const heading = text?.match(/^# Approved Plan: (.+)$/m)?.[1];
102
+ if (heading !== void 0) {
103
+ return heading.trim();
104
+ }
105
+ return humanizeSlug(fallbackSlug);
106
+ }
107
+
108
+ // src/lib/paths.ts
109
+ import { existsSync as existsSync2 } from "node:fs";
110
+ import path3 from "node:path";
111
+ var STATE_ROOT_NAME = ".sb-codex-tool";
112
+ var WORKFLOW_NAMES = [
113
+ "clarify",
114
+ "plan",
115
+ "execute",
116
+ "refactor",
117
+ "verify"
118
+ ];
119
+ var REQUIRED_DIRECTORIES = [
120
+ STATE_ROOT_NAME,
121
+ `${STATE_ROOT_NAME}/plans`,
122
+ `${STATE_ROOT_NAME}/runs`,
123
+ `${STATE_ROOT_NAME}/summaries`,
124
+ `${STATE_ROOT_NAME}/handoffs`,
125
+ `${STATE_ROOT_NAME}/guides`,
126
+ `${STATE_ROOT_NAME}/index`,
127
+ `${STATE_ROOT_NAME}/reviews`,
128
+ `${STATE_ROOT_NAME}/logs`,
129
+ `${STATE_ROOT_NAME}/logs/work-journal`,
130
+ `${STATE_ROOT_NAME}/ignore`,
131
+ `${STATE_ROOT_NAME}/workflows`
132
+ ];
133
+ var REQUIRED_FILES = [
134
+ `${STATE_ROOT_NAME}/project.md`,
135
+ `${STATE_ROOT_NAME}/state.md`,
136
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`,
137
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
138
+ `${STATE_ROOT_NAME}/index/current.json`,
139
+ `${STATE_ROOT_NAME}/index/README.md`,
140
+ `${STATE_ROOT_NAME}/plans/README.md`,
141
+ `${STATE_ROOT_NAME}/runs/README.md`,
142
+ `${STATE_ROOT_NAME}/summaries/README.md`,
143
+ `${STATE_ROOT_NAME}/handoffs/README.md`,
144
+ `${STATE_ROOT_NAME}/reviews/README.md`,
145
+ `${STATE_ROOT_NAME}/logs/work-journal/README.md`,
146
+ `${STATE_ROOT_NAME}/ignore/base.ignore`,
147
+ "AGENTS.md",
148
+ ".gitignore",
149
+ ".ignore",
150
+ ".rgignore"
151
+ ];
152
+ var REQUIRED_WORKFLOW_FILES = WORKFLOW_NAMES.map(
153
+ (name) => `${STATE_ROOT_NAME}/workflows/${name}.md`
154
+ );
155
+ var ROOT_MARKERS = [
156
+ "package.json",
157
+ `${STATE_ROOT_NAME}/state.md`,
158
+ "docs/menu/implementation.md",
159
+ ".git"
160
+ ];
161
+ function hasRootMarker(directory) {
162
+ return ROOT_MARKERS.some((marker) => existsSync2(path3.join(directory, marker)));
163
+ }
164
+ function resolveProjectRoot(start = process.cwd()) {
165
+ let current = path3.resolve(start);
166
+ while (true) {
167
+ if (hasRootMarker(current)) {
168
+ return current;
169
+ }
170
+ const parent = path3.dirname(current);
171
+ if (parent === current) {
172
+ return path3.resolve(start);
173
+ }
174
+ current = parent;
175
+ }
176
+ }
177
+ function statePath(root, ...segments) {
178
+ return path3.join(root, STATE_ROOT_NAME, ...segments);
179
+ }
180
+
181
+ // src/lib/current-state.ts
182
+ function renderBulletList(items) {
183
+ if (items.length === 0) {
184
+ return "- none";
185
+ }
186
+ return items.map((item) => `- ${item}`).join("\n");
187
+ }
188
+ function renderReference(label, value) {
189
+ return `- ${label}: ${value ?? "none yet"}`;
190
+ }
191
+ function renderHotPath(hotPath) {
192
+ return hotPath.map((file, index) => `${index + 1}. ${file}`).join("\n");
193
+ }
194
+ function renderCurrentCycle(current) {
195
+ return [
196
+ `- Current stage: ${current.currentStage}`,
197
+ `- Latest approved plan: ${current.latestApprovedPlan ?? "none yet"}`,
198
+ `- Latest relevant summary: ${current.latestRelevantSummary ?? "none yet"}`,
199
+ `- Latest lifecycle run: ${current.latestRun ?? "none yet"}`,
200
+ `- Latest consistency review: ${current.latestConsistencyReview ?? "none yet"}`,
201
+ `- Latest assignment lifecycle: ${current.latestAssignmentLifecycle ?? "none yet"}`,
202
+ `- Current task guide: ${current.currentGuide ?? "none yet"}`,
203
+ `- Current handoff: ${current.currentHandoff ?? "none yet"}`,
204
+ `- Current review: ${current.currentReview ?? "none yet"}`
205
+ ].join("\n");
206
+ }
207
+ function renderAssignmentGuides(assignmentGuides) {
208
+ const entries = Object.entries(assignmentGuides);
209
+ if (entries.length === 0) {
210
+ return "- none";
211
+ }
212
+ return entries.sort(([left], [right]) => left.localeCompare(right)).map(([agent, guidePath]) => `- ${agent}: ${guidePath}`).join("\n");
213
+ }
214
+ function renderImplementationReference(implementationMenuPath) {
215
+ if (implementationMenuPath === null) {
216
+ return "- If this repo has implementation contracts, add them here.\n";
217
+ }
218
+ return `- ${implementationMenuPath}
219
+ `;
220
+ }
221
+ function createInitialCurrentIndex() {
222
+ return {
223
+ version: 2,
224
+ currentStage: "clarify",
225
+ nextAction: "Review AGENTS.md and customize .sb-codex-tool/project.md.",
226
+ latestApprovedPlan: null,
227
+ latestRelevantSummary: null,
228
+ latestRun: null,
229
+ latestConsistencyReview: null,
230
+ latestAssignmentLifecycle: null,
231
+ currentGuide: null,
232
+ currentHandoff: null,
233
+ currentReview: null,
234
+ currentFocusModules: [
235
+ "AGENTS.md",
236
+ `${STATE_ROOT_NAME}/project.md`,
237
+ `${STATE_ROOT_NAME}/state.md`,
238
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`
239
+ ],
240
+ hotPath: [
241
+ `${STATE_ROOT_NAME}/project.md`,
242
+ `${STATE_ROOT_NAME}/state.md`,
243
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`,
244
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`
245
+ ],
246
+ activeAgents: {
247
+ main: "unassigned",
248
+ subagents: [],
249
+ verification: null,
250
+ consistency: null
251
+ },
252
+ assignmentGuides: {},
253
+ notes: [
254
+ "Keep this file current enough for a fresh agent to start from the hot path."
255
+ ]
256
+ };
257
+ }
258
+ function normalizeCurrentIndex(current) {
259
+ const initial = createInitialCurrentIndex();
260
+ return {
261
+ ...initial,
262
+ ...current,
263
+ version: Math.max(current?.version ?? 0, initial.version),
264
+ currentFocusModules: current?.currentFocusModules ?? initial.currentFocusModules,
265
+ hotPath: current?.hotPath ?? initial.hotPath,
266
+ activeAgents: {
267
+ ...initial.activeAgents,
268
+ ...current?.activeAgents,
269
+ subagents: current?.activeAgents?.subagents ?? initial.activeAgents.subagents
270
+ },
271
+ assignmentGuides: current?.assignmentGuides ?? initial.assignmentGuides,
272
+ notes: current?.notes ?? initial.notes
273
+ };
274
+ }
275
+ function renderStateMarkdown(current) {
276
+ return `# Current State
277
+
278
+ ## Current Workflow Stage
279
+
280
+ - ${current.currentStage}
281
+
282
+ ## One Next Action
283
+
284
+ - ${current.nextAction}
285
+
286
+ ## Current Focus Modules
287
+
288
+ ${renderBulletList(current.currentFocusModules)}
289
+
290
+ ## References
291
+
292
+ ${renderReference("Latest approved plan", current.latestApprovedPlan)}
293
+ ${renderReference("Latest relevant summary", current.latestRelevantSummary)}
294
+ ${renderReference("Latest lifecycle run", current.latestRun)}
295
+ ${renderReference("Latest consistency review", current.latestConsistencyReview)}
296
+ ${renderReference("Latest assignment lifecycle", current.latestAssignmentLifecycle)}
297
+ ${renderReference("Current task guide", current.currentGuide)}
298
+ ${renderReference("Current handoff", current.currentHandoff)}
299
+ ${renderReference("Current review", current.currentReview)}
300
+
301
+ ## Active Agent Map
302
+
303
+ - Main agent: ${current.activeAgents.main ?? "none"}
304
+ - Execution subagents: ${current.activeAgents.subagents.length > 0 ? current.activeAgents.subagents.join(", ") : "none"}
305
+ - Verification agent: ${current.activeAgents.verification ?? "none"}
306
+ - Code consistency agent: ${current.activeAgents.consistency ?? "none"}
307
+
308
+ ## Active Assignment Guides
309
+
310
+ ${renderAssignmentGuides(current.assignmentGuides)}
311
+
312
+ ## Notes
313
+
314
+ ${renderBulletList(current.notes)}
315
+ `;
316
+ }
317
+ function renderReadThisFirstMarkdown(current, implementationMenuPath) {
318
+ return `# Read This First
319
+
320
+ ## Hot Path
321
+
322
+ Read in this order before implementation or verification:
323
+
324
+ ${renderHotPath(current.hotPath)}
325
+
326
+ ## Additional Repo Docs
327
+
328
+ ${renderImplementationReference(implementationMenuPath)}
329
+ - docs/implementation/verification-contract.md
330
+ - docs/implementation/acceptance-checklist.md
331
+
332
+ ## Current Cycle
333
+
334
+ ${renderCurrentCycle(current)}
335
+
336
+ ## Default Ignore Guidance
337
+
338
+ - Ignore build outputs, caches, and bulky generated artifacts by default.
339
+ - Keep ${STATE_ROOT_NAME}/logs/work-journal/ out of the default hot path.
340
+ - Do not ignore AGENTS.md, guide files, state files, or current summaries.
341
+ `;
342
+ }
343
+ function readCurrentIndex(root) {
344
+ const current = readJsonIfPresent(statePath(root, "index", "current.json"));
345
+ if (current === null) {
346
+ return null;
347
+ }
348
+ return normalizeCurrentIndex(current);
349
+ }
350
+ function writeCurrentIndex(root, current) {
351
+ return writeFileIfChanged(
352
+ statePath(root, "index", "current.json"),
353
+ JSON.stringify(current, null, 2) + "\n"
354
+ );
355
+ }
356
+ function writeCurrentState(root, current) {
357
+ return writeFileIfChanged(
358
+ statePath(root, "state.md"),
359
+ renderStateMarkdown(current)
360
+ );
361
+ }
362
+ function writeCurrentArtifacts(root, current, implementationMenuPath) {
363
+ return {
364
+ index: writeCurrentIndex(root, current),
365
+ state: writeCurrentState(root, current),
366
+ readThisFirst: writeFileIfChanged(
367
+ statePath(root, "guides", "read-this-first.md"),
368
+ renderReadThisFirstMarkdown(current, implementationMenuPath)
369
+ )
370
+ };
371
+ }
372
+
373
+ // src/lib/markdown-sections.ts
374
+ function readSectionBody(content, heading) {
375
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
376
+ const section = content.match(
377
+ new RegExp(`## ${escapedHeading}\\s+([\\s\\S]*?)(?=\\n## |$)`)
378
+ );
379
+ return section?.[1] ?? null;
380
+ }
381
+ function collectBulletItems(sectionBody) {
382
+ const items = [];
383
+ let currentItem = [];
384
+ const flushCurrentItem = () => {
385
+ if (currentItem.length === 0) {
386
+ return;
387
+ }
388
+ items.push(currentItem.join("\n"));
389
+ currentItem = [];
390
+ };
391
+ const lines = sectionBody.trim().split("\n").map((line) => line.replace(/\s+$/u, ""));
392
+ for (const line of lines) {
393
+ const trimmed = line.trim();
394
+ if (trimmed.length === 0) {
395
+ continue;
396
+ }
397
+ if (trimmed.startsWith("- ")) {
398
+ flushCurrentItem();
399
+ currentItem = [trimmed];
400
+ continue;
401
+ }
402
+ if (currentItem.length > 0) {
403
+ currentItem.push(` ${trimmed}`);
404
+ }
405
+ }
406
+ flushCurrentItem();
407
+ return items;
408
+ }
409
+ function extractSectionLines(content, heading) {
410
+ const sectionBody = readSectionBody(content, heading);
411
+ if (sectionBody === null) {
412
+ return [];
413
+ }
414
+ return collectBulletItems(sectionBody);
415
+ }
416
+ function stripBulletPrefix(lines) {
417
+ return lines.map(
418
+ (line) => line.split("\n").map(
419
+ (segment, index) => index === 0 ? segment.replace(/^- /u, "") : segment.replace(/^\s+/u, "")
420
+ ).join("\n")
421
+ );
422
+ }
423
+
424
+ // src/lib/templates/context.ts
425
+ import { existsSync as existsSync3 } from "node:fs";
426
+ import path4 from "node:path";
427
+ function buildTemplateContext(root) {
428
+ const implementationMenu = path4.join(root, "docs/menu/implementation.md");
429
+ return {
430
+ root,
431
+ projectName: path4.basename(root),
432
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
433
+ implementationMenuPath: existsSync3(implementationMenu) ? "docs/menu/implementation.md" : null
434
+ };
435
+ }
436
+
437
+ // src/lib/templates/shared.ts
438
+ function alwaysReadList(context) {
439
+ const items = [
440
+ "- AGENTS.md",
441
+ `- ${STATE_ROOT_NAME}/project.md`,
442
+ `- ${STATE_ROOT_NAME}/state.md`,
443
+ `- ${STATE_ROOT_NAME}/guides/read-this-first.md`,
444
+ `- ${STATE_ROOT_NAME}/guides/code-consistency.md`
445
+ ];
446
+ if (context.implementationMenuPath !== null) {
447
+ items.push(`- ${context.implementationMenuPath}`);
448
+ }
449
+ return items.join("\n");
450
+ }
451
+ function dirReadmeTemplate(title, purpose) {
452
+ return `# ${title}
453
+
454
+ ${purpose}
455
+ `;
456
+ }
457
+ function workflowTemplate(name, purpose, outputs) {
458
+ return `# $${name}
459
+
460
+ ## Purpose
461
+
462
+ ${purpose}
463
+
464
+ ## Required Outputs
465
+
466
+ ${outputs}
467
+
468
+ ## Completion Rule
469
+
470
+ - This stage is complete only when its required outputs are written clearly
471
+ enough for a fresh agent to inspect.
472
+
473
+ ## Failure Conditions
474
+
475
+ - Required outputs are missing
476
+ - The stage overlaps ambiguously with another stage
477
+ - The next agent cannot tell what happened or what to do next
478
+
479
+ ## Related State Files
480
+
481
+ - ${STATE_ROOT_NAME}/state.md
482
+ - ${STATE_ROOT_NAME}/plans/
483
+ - ${STATE_ROOT_NAME}/summaries/
484
+ - ${STATE_ROOT_NAME}/guides/
485
+ `;
486
+ }
487
+
488
+ // src/lib/templates/repo-documents.ts
489
+ function agentsTemplate(context) {
490
+ return `# AGENTS.md
491
+
492
+ ## Purpose
493
+
494
+ This repository uses \`sb-codex-tool\` as its workflow and runtime scaffold.
495
+
496
+ Use the toolkit state and guide files to keep work inspectable, compact, and
497
+ verification-friendly.
498
+
499
+ ## Required Workflow
500
+
501
+ 1. Non-trivial work starts with an approved plan.
502
+ 2. Each task defines \`files\`, \`action\`, \`verify\`, and \`done\`.
503
+ 3. Non-trivial work proceeds through \`execute -> refactor -> verify\`.
504
+ 4. Final verification is always performed by a fresh agent.
505
+ 5. Verification is a closure step, not test-only behavior.
506
+
507
+ ## Agent Roles
508
+
509
+ - The main agent owns orchestration and user communication.
510
+ - Main-agent progress updates to the user are always in Korean.
511
+ - Subagents are bounded workers and must be reset or replaced after completion.
512
+ - Every new implementation agent reads \`${STATE_ROOT_NAME}/guides/code-consistency.md\` first.
513
+ - The main agent references \`${STATE_ROOT_NAME}/guides/code-consistency.md\` before assigning new work.
514
+
515
+ ## Code Quality Rules
516
+
517
+ - Prefer reuse before adding parallel duplicate implementations.
518
+ - Keep files short and single-purpose.
519
+ - Keep functions short and single-purpose.
520
+ - Prefer simple top-down control flow.
521
+ - Avoid clever abstractions and speculative generalization.
522
+ - Keep naming predictable and module boundaries explicit.
523
+ - Write code that a fresh agent can read quickly.
524
+
525
+ ## Verification Rules
526
+
527
+ - Final verification must be performed by a fresh agent.
528
+ - Verification compares plan vs actual and records deferred issues.
529
+ - Verification checks next-agent guidance and readability, not only tests.
530
+ - Keep work journal updates outside the default hot path.
531
+
532
+ ## State and Guide Rules
533
+
534
+ - Hot path starts with \`${STATE_ROOT_NAME}/project.md\` and \`${STATE_ROOT_NAME}/state.md\`.
535
+ - Update next-agent guidance after non-trivial code changes.
536
+ - If new conventions are introduced, update \`${STATE_ROOT_NAME}/guides/code-consistency.md\`.
537
+
538
+ ## Work Journal Rules
539
+
540
+ - After verified completion, update the human work journal under
541
+ \`${STATE_ROOT_NAME}/logs/work-journal/\`.
542
+ - The work journal is for people, not for agent continuity state.
543
+
544
+ ## Git Rules
545
+
546
+ - Use git as context support only.
547
+ - Keep changed-file scope visible when available.
548
+ - Do not rely on destructive git automation.
549
+
550
+ ## Repo Entry Points
551
+
552
+ ${alwaysReadList(context)}
553
+ `;
554
+ }
555
+ function projectTemplate(context) {
556
+ return `# Project Brief
557
+
558
+ ## Purpose
559
+
560
+ - Project name: ${context.projectName}
561
+ - Replace this section with the actual project purpose.
562
+ - Keep this file short and update it when the architecture truth changes.
563
+
564
+ ## Core Constraints
565
+
566
+ - Follow the workflow defined in AGENTS.md.
567
+ - Keep state inspectable through ${STATE_ROOT_NAME}/.
568
+ - Optimize for reuse, readability, and low complexity.
569
+
570
+ ## Important Entrypoints
571
+
572
+ - AGENTS.md
573
+ - ${STATE_ROOT_NAME}/state.md
574
+ - ${STATE_ROOT_NAME}/guides/read-this-first.md
575
+ - ${STATE_ROOT_NAME}/guides/code-consistency.md
576
+
577
+ ## Always-Read Docs or Files
578
+
579
+ ${alwaysReadList(context)}
580
+
581
+ ## Architecture Truth
582
+
583
+ - Replace with a short summary of the current architecture.
584
+ - Keep module boundaries explicit.
585
+ - Prefer top-down, easy-to-modify flows.
586
+ `;
587
+ }
588
+ function codeConsistencyTemplate() {
589
+ return `# Code Consistency Guide
590
+
591
+ ## Purpose
592
+
593
+ This file defines the structural consistency rules every new agent reads before
594
+ implementation.
595
+
596
+ ## Architecture Style Summary
597
+
598
+ - Prefer small modules with single responsibilities.
599
+ - Keep orchestration code separate from filesystem, git, and template logic.
600
+ - Favor simple top-down flow over clever abstraction chains.
601
+
602
+ ## Naming Rules
603
+
604
+ - Use direct names that reveal responsibility quickly.
605
+ - Keep command names aligned with workflow stages.
606
+ - Keep template and state file names predictable.
607
+
608
+ ## Module Boundary Rules
609
+
610
+ - CLI parsing stays separate from command logic.
611
+ - Command logic stays separate from state persistence.
612
+ - Template generation stays separate from command output.
613
+ - Verification logic stays separate from execution logic.
614
+
615
+ ## Reuse Rules
616
+
617
+ - Reuse existing helpers before creating parallel implementations.
618
+ - Extract shared logic only when reuse is current and obvious.
619
+ - Do not over-generalize for hypothetical future use.
620
+
621
+ ## Readability Rules
622
+
623
+ - Keep files short and focused.
624
+ - Keep functions short and focused.
625
+ - Prefer explicit data flow over hidden side effects.
626
+ - Add short comments only when the control flow is not self-evident.
627
+
628
+ ## Anti-Patterns
629
+
630
+ - Clever abstractions that hide simple behavior
631
+ - Overlong files and functions
632
+ - Mixed responsibilities in one module
633
+ - Vague naming
634
+ - Hidden state transitions
635
+
636
+ ## Reference Files To Read First
637
+
638
+ - AGENTS.md
639
+ - ${STATE_ROOT_NAME}/project.md
640
+ - ${STATE_ROOT_NAME}/state.md
641
+ - ${STATE_ROOT_NAME}/guides/read-this-first.md
642
+
643
+ ## Known Consistency Debt
644
+
645
+ - Fill this section with real debt as the project evolves.
646
+ `;
647
+ }
648
+ function buildRepoDocumentFiles(context) {
649
+ return [
650
+ { path: "AGENTS.md", content: agentsTemplate(context) },
651
+ { path: `${STATE_ROOT_NAME}/project.md`, content: projectTemplate(context) },
652
+ {
653
+ path: `${STATE_ROOT_NAME}/guides/code-consistency.md`,
654
+ content: codeConsistencyTemplate()
655
+ }
656
+ ];
657
+ }
658
+
659
+ // src/lib/templates/state-documents.ts
660
+ function stateTemplate() {
661
+ return renderStateMarkdown(createInitialCurrentIndex());
662
+ }
663
+ function readThisFirstTemplate(context) {
664
+ return renderReadThisFirstMarkdown(
665
+ createInitialCurrentIndex(),
666
+ context.implementationMenuPath
667
+ );
668
+ }
669
+ function workJournalTemplate() {
670
+ return `# Work Journal
671
+
672
+ Daily human-readable logs live in this directory.
673
+
674
+ Recommended filename pattern:
675
+
676
+ - YYYY-MM-DD.md
677
+
678
+ Required sections for each entry:
679
+
680
+ - Date
681
+ - Summary
682
+ - Completed
683
+ - Changed Areas
684
+ - Verification
685
+ - Open Issues
686
+ - Next
687
+ `;
688
+ }
689
+ function indexReadmeTemplate() {
690
+ return `# Index
691
+
692
+ This directory stores compact navigational artifacts and structured current
693
+ state references used by status and verification helpers.
694
+ `;
695
+ }
696
+ function currentIndexTemplate() {
697
+ return JSON.stringify(createInitialCurrentIndex(), null, 2) + "\n";
698
+ }
699
+ function buildStateDocumentFiles(context) {
700
+ return [
701
+ { path: `${STATE_ROOT_NAME}/state.md`, content: stateTemplate() },
702
+ {
703
+ path: `${STATE_ROOT_NAME}/guides/read-this-first.md`,
704
+ content: readThisFirstTemplate(context)
705
+ },
706
+ {
707
+ path: `${STATE_ROOT_NAME}/plans/README.md`,
708
+ content: dirReadmeTemplate("Plans", "Store draft and approved plans here.")
709
+ },
710
+ {
711
+ path: `${STATE_ROOT_NAME}/runs/README.md`,
712
+ content: dirReadmeTemplate("Runs", "Store launch and run metadata here.")
713
+ },
714
+ {
715
+ path: `${STATE_ROOT_NAME}/summaries/README.md`,
716
+ content: dirReadmeTemplate(
717
+ "Summaries",
718
+ "Store execution, verification, and reconciliation summaries here."
719
+ )
720
+ },
721
+ {
722
+ path: `${STATE_ROOT_NAME}/handoffs/README.md`,
723
+ content: dirReadmeTemplate(
724
+ "Handoffs",
725
+ "Store next-agent guidance and interrupted-work handoffs here."
726
+ )
727
+ },
728
+ {
729
+ path: `${STATE_ROOT_NAME}/reviews/README.md`,
730
+ content: dirReadmeTemplate(
731
+ "Reviews",
732
+ "Store consistency, verification, and acceptance review artifacts here."
733
+ )
734
+ },
735
+ {
736
+ path: `${STATE_ROOT_NAME}/logs/work-journal/README.md`,
737
+ content: workJournalTemplate()
738
+ },
739
+ {
740
+ path: `${STATE_ROOT_NAME}/index/README.md`,
741
+ content: indexReadmeTemplate()
742
+ },
743
+ {
744
+ path: `${STATE_ROOT_NAME}/index/current.json`,
745
+ content: currentIndexTemplate()
746
+ }
747
+ ];
748
+ }
749
+
750
+ // src/lib/templates/documents.ts
751
+ function buildDocumentFiles(context) {
752
+ return [
753
+ ...buildRepoDocumentFiles(context),
754
+ ...buildStateDocumentFiles(context)
755
+ ];
756
+ }
757
+
758
+ // src/lib/templates/ignore.ts
759
+ var SHARED_IGNORE_LINES = [
760
+ "node_modules/",
761
+ "dist/",
762
+ "build/",
763
+ "coverage/",
764
+ ".DS_Store"
765
+ ];
766
+ function buildBaseIgnoreTemplate() {
767
+ return `# sb-codex-tool default agent-ignore patterns
768
+ ${SHARED_IGNORE_LINES.join("\n")}
769
+ ${STATE_ROOT_NAME}/logs/work-journal/
770
+ `;
771
+ }
772
+ function getGitIgnoreLines() {
773
+ return [...SHARED_IGNORE_LINES];
774
+ }
775
+ function getSearchIgnoreLines() {
776
+ return [
777
+ ...SHARED_IGNORE_LINES,
778
+ `${STATE_ROOT_NAME}/logs/work-journal/`
779
+ ];
780
+ }
781
+
782
+ // src/lib/templates/workflows.ts
783
+ function buildWorkflowFile(name, purpose, outputs) {
784
+ return {
785
+ path: `${STATE_ROOT_NAME}/workflows/${name}.md`,
786
+ content: workflowTemplate(name, purpose, outputs.join("\n"))
787
+ };
788
+ }
789
+ function buildWorkflowFiles() {
790
+ return [
791
+ buildWorkflowFile(
792
+ "clarify",
793
+ "Turn an ambiguous request into a compact brief with clear acceptance criteria and boundaries.",
794
+ [
795
+ "- compact brief",
796
+ "- acceptance criteria",
797
+ "- boundaries",
798
+ "- assumptions",
799
+ "- non-goals"
800
+ ]
801
+ ),
802
+ buildWorkflowFile(
803
+ "plan",
804
+ "Create a decision-complete plan with executable tasks and explicit verification criteria.",
805
+ [
806
+ "- objective",
807
+ "- acceptance criteria",
808
+ "- boundaries",
809
+ "- task list with files/action/verify/done"
810
+ ]
811
+ ),
812
+ buildWorkflowFile(
813
+ "execute",
814
+ "Advance work task by task while keeping scope, blockers, and status explicit.",
815
+ [
816
+ "- task progression",
817
+ "- status updates",
818
+ "- blocker notes",
819
+ "- changed-file scope"
820
+ ]
821
+ ),
822
+ buildWorkflowFile(
823
+ "refactor",
824
+ "Reduce complexity and improve reuse, readability, and maintainability before closure.",
825
+ [
826
+ "- simplification notes",
827
+ "- reuse decisions",
828
+ "- readability improvements",
829
+ "- complexity reductions"
830
+ ]
831
+ ),
832
+ buildWorkflowFile(
833
+ "verify",
834
+ "Perform fresh-agent closure after refactor with evidence, reconciliation, and next-agent checks.",
835
+ [
836
+ "- fresh-agent verification result",
837
+ "- plan-vs-actual reconciliation",
838
+ "- deferred issues",
839
+ "- next-agent guidance check",
840
+ "- work journal precondition"
841
+ ]
842
+ )
843
+ ];
844
+ }
845
+
846
+ // src/lib/templates/generated-files.ts
847
+ function buildGeneratedFiles(context) {
848
+ return [
849
+ ...buildDocumentFiles(context),
850
+ ...buildWorkflowFiles(),
851
+ {
852
+ path: `${STATE_ROOT_NAME}/ignore/base.ignore`,
853
+ content: buildBaseIgnoreTemplate()
854
+ }
855
+ ];
856
+ }
857
+
858
+ // src/lib/assignment.ts
859
+ function readAllowedFileScope(root, guidePath) {
860
+ if (guidePath === null) {
861
+ return [];
862
+ }
863
+ const text = readTextIfPresent(path5.join(root, guidePath));
864
+ if (text === null) {
865
+ return [];
866
+ }
867
+ return extractSectionLines(text, "Allowed File Scope");
868
+ }
869
+ function buildAssignmentGuide(agentName, title, current, assignmentSlug, allowedFileScope) {
870
+ const fileScope = allowedFileScope.length > 0 ? allowedFileScope.join("\n") : "- Replace with the bounded file scope for this assignment.";
871
+ return `# Assignment Guide: ${title}
872
+
873
+ ## Assigned Agent
874
+
875
+ - ${agentName}
876
+
877
+ ## Assignment Slug
878
+
879
+ - ${assignmentSlug}
880
+
881
+ ## Objective
882
+
883
+ - Replace with the bounded objective for ${agentName}.
884
+
885
+ ## Current Cycle References
886
+
887
+ - Current plan: ${current.latestApprovedPlan ?? "none yet"}
888
+ - Latest summary: ${current.latestRelevantSummary ?? "none yet"}
889
+ - Current guide: ${current.currentGuide ?? "none yet"}
890
+ - Latest lifecycle run: ${current.latestRun ?? "none yet"}
891
+
892
+ ## Allowed File Scope
893
+
894
+ ${fileScope}
895
+
896
+ ## Required References
897
+
898
+ - AGENTS.md
899
+ - ${STATE_ROOT_NAME}/guides/code-consistency.md
900
+ - ${current.latestApprovedPlan ?? `${STATE_ROOT_NAME}/plans/README.md`}
901
+ - ${current.latestRelevantSummary ?? `${STATE_ROOT_NAME}/summaries/README.md`}
902
+ - ${current.currentGuide ?? `${STATE_ROOT_NAME}/guides/read-this-first.md`}
903
+
904
+ ## Consistency Expectations
905
+
906
+ - Read ${STATE_ROOT_NAME}/guides/code-consistency.md before implementation.
907
+ - Reuse existing helpers before adding parallel implementations.
908
+ - Keep files and functions short, simple, and readable.
909
+ - Keep module boundaries explicit and avoid clever abstractions.
910
+
911
+ ## Verification Expectations
912
+
913
+ - Keep the work bounded to the allowed file scope unless the main agent expands it.
914
+ - Report any blocker or scope mismatch back to the main agent immediately.
915
+ - Do not self-approve final completion; final verification stays fresh-agent-only.
916
+
917
+ ## Completion Rule
918
+
919
+ - When the bounded task is complete, the main agent must either close and replace this subagent or clear its context before same-role reuse.
920
+ `;
921
+ }
922
+ function createAssignmentGuide(start, agentName, requestedSlug, requestedTitle) {
923
+ const root = resolveProjectRoot(start);
924
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
925
+ if (current.latestApprovedPlan === null) {
926
+ throw new Error("assign requires a current approved plan.");
927
+ }
928
+ if (current.latestRelevantSummary === null) {
929
+ throw new Error("assign requires a current summary.");
930
+ }
931
+ if (current.currentGuide === null) {
932
+ throw new Error("assign requires a current task guide.");
933
+ }
934
+ const assignmentSlug = normalizeSlug(requestedSlug);
935
+ if (assignmentSlug.length === 0) {
936
+ throw new Error("Assignment slug must contain at least one alphanumeric character.");
937
+ }
938
+ const agentSlug = normalizeSlug(agentName);
939
+ if (agentSlug.length === 0) {
940
+ throw new Error("Agent name must contain at least one alphanumeric character.");
941
+ }
942
+ const title = requestedTitle?.trim().length ? requestedTitle.trim() : humanizeSlug(assignmentSlug);
943
+ const { dateStamp } = parseCycleDescriptor(current.latestApprovedPlan);
944
+ const assignmentPath = `${STATE_ROOT_NAME}/guides/${dateStamp}-${agentSlug}-${assignmentSlug}-assignment.md`;
945
+ const allowedFileScope = readAllowedFileScope(root, current.currentGuide);
946
+ const writeResult = writeFileIfMissing(
947
+ path5.join(root, assignmentPath),
948
+ buildAssignmentGuide(agentName, title, current, assignmentSlug, allowedFileScope)
949
+ );
950
+ const createdFiles = [];
951
+ const keptFiles = [];
952
+ if (writeResult === "created") {
953
+ createdFiles.push(assignmentPath);
954
+ } else {
955
+ keptFiles.push(assignmentPath);
956
+ }
957
+ const nextSubagents = current.activeAgents.subagents.includes(agentName) ? current.activeAgents.subagents : [...current.activeAgents.subagents, agentName];
958
+ const nextFocusModules = current.currentFocusModules.includes(assignmentPath) ? current.currentFocusModules : [...current.currentFocusModules, assignmentPath];
959
+ const nextCurrent = normalizeCurrentIndex({
960
+ ...current,
961
+ currentFocusModules: nextFocusModules,
962
+ activeAgents: {
963
+ ...current.activeAgents,
964
+ subagents: nextSubagents
965
+ },
966
+ assignmentGuides: {
967
+ ...current.assignmentGuides,
968
+ [agentName]: assignmentPath
969
+ },
970
+ notes: [
971
+ `Current work cycle: ${humanizeSlug(parseCycleDescriptor(current.latestApprovedPlan).slug)}.`,
972
+ `Prepared assignment guide for ${agentName}: ${assignmentPath}.`,
973
+ `Previous relevant summary: ${current.latestRelevantSummary}`
974
+ ]
975
+ });
976
+ const updatedFiles = [];
977
+ const currentWrites = writeCurrentArtifacts(
978
+ root,
979
+ nextCurrent,
980
+ buildTemplateContext(root).implementationMenuPath
981
+ );
982
+ if (currentWrites.index !== "unchanged") {
983
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
984
+ }
985
+ if (currentWrites.state !== "unchanged") {
986
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
987
+ }
988
+ if (currentWrites.readThisFirst !== "unchanged") {
989
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
990
+ }
991
+ return {
992
+ root,
993
+ agentName,
994
+ title,
995
+ assignmentPath,
996
+ createdFiles,
997
+ keptFiles,
998
+ updatedFiles
999
+ };
1000
+ }
1001
+
1002
+ // src/commands/assign.ts
1003
+ function runAssign(args) {
1004
+ const [agentName, slug, ...titleParts] = args;
1005
+ if (agentName === void 0 || slug === void 0) {
1006
+ console.error("Usage: sb-codex-tool assign <agent-name> <slug> [title words]");
1007
+ return 1;
1008
+ }
1009
+ const title = titleParts.length > 0 ? titleParts.join(" ") : void 0;
1010
+ const result = createAssignmentGuide(process.cwd(), agentName, slug, title);
1011
+ console.log(`Project root: ${result.root}`);
1012
+ console.log(`Assigned agent: ${result.agentName}`);
1013
+ console.log(`Assignment: ${result.title}`);
1014
+ console.log("");
1015
+ console.log("Artifacts:");
1016
+ console.log(`- assignment guide: ${result.assignmentPath}`);
1017
+ console.log("");
1018
+ console.log(`Created files: ${result.createdFiles.length}`);
1019
+ for (const file of result.createdFiles) {
1020
+ console.log(`- ${file}`);
1021
+ }
1022
+ if (result.updatedFiles.length > 0) {
1023
+ console.log("");
1024
+ console.log("Updated state:");
1025
+ for (const file of result.updatedFiles) {
1026
+ console.log(`- ${file}`);
1027
+ }
1028
+ }
1029
+ return 0;
1030
+ }
1031
+
1032
+ // src/lib/work-cycle.ts
1033
+ import path7 from "node:path";
1034
+
1035
+ // src/lib/git.ts
1036
+ import { spawnSync } from "node:child_process";
1037
+ function runGit(root, args) {
1038
+ const result = spawnSync("git", args, {
1039
+ cwd: root,
1040
+ encoding: "utf8",
1041
+ stdio: ["ignore", "pipe", "ignore"]
1042
+ });
1043
+ if (result.status !== 0) {
1044
+ return null;
1045
+ }
1046
+ return result.stdout.trim();
1047
+ }
1048
+ function getGitContext(root) {
1049
+ const insideWorkTree = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
1050
+ if (insideWorkTree !== "true") {
1051
+ return {
1052
+ available: false,
1053
+ branch: null,
1054
+ dirty: false,
1055
+ changedFiles: []
1056
+ };
1057
+ }
1058
+ const branch = runGit(root, ["branch", "--show-current"]);
1059
+ const status = runGit(root, ["status", "--porcelain"]) ?? "";
1060
+ const changedFiles = status.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.slice(3));
1061
+ return {
1062
+ available: true,
1063
+ branch,
1064
+ dirty: changedFiles.length > 0,
1065
+ changedFiles
1066
+ };
1067
+ }
1068
+
1069
+ // src/lib/run-records.ts
1070
+ import path6 from "node:path";
1071
+ function readLifecycleRunRecord(root, relativePath) {
1072
+ if (relativePath === null) {
1073
+ return null;
1074
+ }
1075
+ return readJsonIfPresent(path6.join(root, relativePath));
1076
+ }
1077
+ function lifecycleRunPath(dateStamp, slug) {
1078
+ return `${STATE_ROOT_NAME}/runs/${dateStamp}-${slug}-run.json`;
1079
+ }
1080
+ function renderGitContextSection(runPath, git) {
1081
+ const lines = [
1082
+ "## Git Context",
1083
+ "",
1084
+ `- Run artifact: ${runPath}`,
1085
+ `- Git available: ${git.available ? "yes" : "no"}`,
1086
+ `- Branch: ${git.available ? git.branch ?? "detached" : "unavailable"}`,
1087
+ `- Dirty: ${git.available ? git.dirty ? "yes" : "no" : "unavailable"}`
1088
+ ];
1089
+ if (!git.available) {
1090
+ lines.push("- Changed files: unavailable outside a Git repository");
1091
+ return `${lines.join("\n")}
1092
+ `;
1093
+ }
1094
+ if (git.changedFiles.length === 0) {
1095
+ lines.push("- Changed files: none");
1096
+ return `${lines.join("\n")}
1097
+ `;
1098
+ }
1099
+ lines.push("- Changed files:");
1100
+ for (const file of git.changedFiles) {
1101
+ lines.push(` - ${file}`);
1102
+ }
1103
+ return `${lines.join("\n")}
1104
+ `;
1105
+ }
1106
+ function writeLifecycleRunRecord(root, options) {
1107
+ const now = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1108
+ const relativePath = lifecycleRunPath(options.dateStamp, options.slug);
1109
+ const absolutePath = path6.join(root, relativePath);
1110
+ const previous = readJsonIfPresent(absolutePath);
1111
+ const record = {
1112
+ version: 1,
1113
+ cycleId: `${options.dateStamp}-${options.slug}`,
1114
+ dateStamp: options.dateStamp,
1115
+ slug: options.slug,
1116
+ title: options.title,
1117
+ phase: options.phase,
1118
+ stage: options.stage,
1119
+ verdict: options.verdict,
1120
+ git: options.git,
1121
+ paths: options.paths,
1122
+ startedAt: previous?.startedAt ?? now,
1123
+ updatedAt: now,
1124
+ closedAt: options.verdict === "pass" || options.verdict === "pass_with_concerns" ? now : previous?.closedAt ?? null
1125
+ };
1126
+ return {
1127
+ path: relativePath,
1128
+ writeResult: writeFileIfChanged(
1129
+ absolutePath,
1130
+ JSON.stringify(record, null, 2) + "\n"
1131
+ ),
1132
+ record
1133
+ };
1134
+ }
1135
+
1136
+ // src/lib/work-cycle.ts
1137
+ function planTemplate(title) {
1138
+ return `# Approved Plan: ${title}
1139
+
1140
+ ## Objective
1141
+
1142
+ - Replace with the concrete objective for this work cycle.
1143
+
1144
+ ## Acceptance Criteria
1145
+
1146
+ - Replace with the acceptance criteria that define completion.
1147
+
1148
+ ## Boundaries
1149
+
1150
+ - Replace with in-scope and out-of-scope notes.
1151
+
1152
+ ## Tasks
1153
+
1154
+ ### Task 1
1155
+
1156
+ - files: \`fill-in-file-scope\`
1157
+ - action: describe the concrete implementation task
1158
+ - verify: describe how this task will be checked
1159
+ - done: no
1160
+ `;
1161
+ }
1162
+ function executionSummaryTemplate(title, guidePath, planPath) {
1163
+ return `# Execution Summary: ${title}
1164
+
1165
+ ## Purpose
1166
+
1167
+ - Capture implementation progress for the current work cycle before fresh verification.
1168
+
1169
+ ## Scope
1170
+
1171
+ - Replace with the actual implementation scope.
1172
+
1173
+ ## Implemented Surface
1174
+
1175
+ - not started yet
1176
+
1177
+ ## Checks Run
1178
+
1179
+ - none yet
1180
+
1181
+ ## Plan vs Actual
1182
+
1183
+ - Update this section as implementation progresses.
1184
+
1185
+ ## Refactor Notes
1186
+
1187
+ - Update this section after refactor.
1188
+
1189
+ ## Deferred Issues
1190
+
1191
+ - Add deferred issues if they exist.
1192
+
1193
+ ## Next-Agent Guidance
1194
+
1195
+ - Start from this execution summary as the latest relevant summary.
1196
+ - Then review \`${planPath}\`.
1197
+ - Then review \`${guidePath}\`.
1198
+ `;
1199
+ }
1200
+ function handoffTemplate(title, guidePath, planPath, summaryPath, gitContextSection) {
1201
+ return `# Handoff: ${title}
1202
+
1203
+ ## Goal
1204
+
1205
+ - Enable the next fresh agent to continue this work cycle without hidden context.
1206
+
1207
+ ## Read In This Order
1208
+
1209
+ 1. AGENTS.md
1210
+ 2. ${STATE_ROOT_NAME}/project.md
1211
+ 3. ${STATE_ROOT_NAME}/state.md
1212
+ 4. ${STATE_ROOT_NAME}/guides/read-this-first.md
1213
+ 5. ${STATE_ROOT_NAME}/guides/code-consistency.md
1214
+ 6. ${summaryPath}
1215
+ 7. ${planPath}
1216
+ 8. ${guidePath}
1217
+
1218
+ ## Current Status
1219
+
1220
+ - Replace with the current implementation status.
1221
+
1222
+ ${gitContextSection}
1223
+
1224
+ ## Expected Verification Checks
1225
+
1226
+ - Replace with the checks relevant to this work cycle.
1227
+
1228
+ ## Open Risks
1229
+
1230
+ - Replace with the current blocker or risk list.
1231
+ `;
1232
+ }
1233
+ function reviewTemplate(title) {
1234
+ return `# Fresh Verification Review: ${title}
1235
+
1236
+ ## Verification Target
1237
+
1238
+ - ${title}
1239
+
1240
+ ## Verdict
1241
+
1242
+ - pending
1243
+
1244
+ ## Required Checks
1245
+
1246
+ - Contract reading order completed
1247
+ - Acceptance criteria reviewed
1248
+ - State and guide artifacts inspected
1249
+ - Relevant code and tests inspected
1250
+ - Required checks run
1251
+
1252
+ ## Checks Run
1253
+
1254
+ - Add the checks run during the fresh verification pass.
1255
+
1256
+ ## Findings
1257
+
1258
+ - Add findings here in severity order.
1259
+
1260
+ ## Concerns
1261
+
1262
+ - Add non-blocking concerns here.
1263
+
1264
+ ## Missing Evidence
1265
+
1266
+ - Add any blocked evidence here.
1267
+
1268
+ ## Work Journal Decision
1269
+
1270
+ - Record whether verified closure is complete enough to update the work journal.
1271
+ `;
1272
+ }
1273
+ function scopeGuideTemplate(title, planPath, previousSummary) {
1274
+ return `# Scope Guide: ${title}
1275
+
1276
+ ## Purpose
1277
+
1278
+ - Narrow the current work cycle to a bounded file scope and clear verification expectations.
1279
+
1280
+ ## Working Goal
1281
+
1282
+ - Replace with the concrete goal for this cycle.
1283
+
1284
+ ## Primary Plan
1285
+
1286
+ - ${planPath}
1287
+
1288
+ ## Allowed File Scope
1289
+
1290
+ - Replace with the files or modules that should be touched.
1291
+
1292
+ ## Recommended References
1293
+
1294
+ - AGENTS.md
1295
+ - ${STATE_ROOT_NAME}/project.md
1296
+ - ${STATE_ROOT_NAME}/state.md
1297
+ - ${STATE_ROOT_NAME}/guides/code-consistency.md
1298
+ ${previousSummary === null ? "" : `- ${previousSummary}
1299
+ `}
1300
+ ## Verification Expectations
1301
+
1302
+ - Replace with the checks and verdict expectations for this cycle.
1303
+ `;
1304
+ }
1305
+ function beginWorkCycle(start, requestedSlug, requestedTitle) {
1306
+ const root = resolveProjectRoot(start);
1307
+ const slug = normalizeSlug(requestedSlug);
1308
+ if (slug.length === 0) {
1309
+ throw new Error("Work-cycle slug must contain at least one alphanumeric character.");
1310
+ }
1311
+ const title = requestedTitle?.trim().length ? requestedTitle.trim() : humanizeSlug(slug);
1312
+ const dateStamp = formatDateStamp();
1313
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
1314
+ const previousSummary = current.latestRelevantSummary;
1315
+ const planPath = `${STATE_ROOT_NAME}/plans/${dateStamp}-${slug}-approved.md`;
1316
+ const summaryPath = `${STATE_ROOT_NAME}/summaries/${dateStamp}-${slug}-execution-summary.md`;
1317
+ const handoffPath = `${STATE_ROOT_NAME}/handoffs/${dateStamp}-${slug}-to-verification.md`;
1318
+ const reviewPath = `${STATE_ROOT_NAME}/reviews/${dateStamp}-${slug}-fresh-verification.md`;
1319
+ const guidePath = `${STATE_ROOT_NAME}/guides/${dateStamp}-${slug}-scope.md`;
1320
+ const runPath = lifecycleRunPath(dateStamp, slug);
1321
+ const sameCycle = current.latestApprovedPlan === planPath;
1322
+ const generatedFiles = [
1323
+ { path: planPath, content: planTemplate(title) },
1324
+ { path: summaryPath, content: executionSummaryTemplate(title, guidePath, planPath) },
1325
+ { path: reviewPath, content: reviewTemplate(title) },
1326
+ { path: guidePath, content: scopeGuideTemplate(title, planPath, previousSummary) }
1327
+ ];
1328
+ const createdFiles = [];
1329
+ const keptFiles = [];
1330
+ for (const file of generatedFiles) {
1331
+ const result = writeFileIfMissing(path7.join(root, file.path), file.content);
1332
+ if (result === "created") {
1333
+ createdFiles.push(file.path);
1334
+ } else {
1335
+ keptFiles.push(file.path);
1336
+ }
1337
+ }
1338
+ const latestSummary = sameCycle ? current.latestRelevantSummary ?? summaryPath : summaryPath;
1339
+ const git = getGitContext(root);
1340
+ const handoffContent = handoffTemplate(
1341
+ title,
1342
+ guidePath,
1343
+ planPath,
1344
+ summaryPath,
1345
+ renderGitContextSection(runPath, git)
1346
+ );
1347
+ const handoffWrite = writeFileIfMissing(path7.join(root, handoffPath), handoffContent);
1348
+ if (handoffWrite === "created") {
1349
+ createdFiles.push(handoffPath);
1350
+ } else {
1351
+ keptFiles.push(handoffPath);
1352
+ }
1353
+ const currentGuide = sameCycle ? current.currentGuide ?? guidePath : guidePath;
1354
+ const currentHandoff = sameCycle ? current.currentHandoff ?? handoffPath : handoffPath;
1355
+ const currentReview = sameCycle ? current.currentReview ?? reviewPath : reviewPath;
1356
+ const latestRun = sameCycle ? current.latestRun ?? runPath : runPath;
1357
+ const updatedFiles = [];
1358
+ const runWrite = writeLifecycleRunRecord(root, {
1359
+ dateStamp,
1360
+ slug,
1361
+ title,
1362
+ phase: "begin",
1363
+ stage: sameCycle ? current.currentStage : "clarify",
1364
+ verdict: "pending",
1365
+ git,
1366
+ paths: {
1367
+ planPath,
1368
+ executionSummaryPath: summaryPath,
1369
+ handoffPath: currentHandoff,
1370
+ reviewPath: currentReview,
1371
+ guidePath: currentGuide,
1372
+ verificationSummaryPath: null,
1373
+ journalPath: null
1374
+ }
1375
+ });
1376
+ if (runWrite.writeResult === "created") {
1377
+ createdFiles.push(runWrite.path);
1378
+ } else if (runWrite.writeResult === "updated") {
1379
+ updatedFiles.push(runWrite.path);
1380
+ }
1381
+ const nextCurrent = normalizeCurrentIndex({
1382
+ ...current,
1383
+ currentStage: sameCycle ? current.currentStage : "clarify",
1384
+ nextAction: sameCycle ? current.nextAction : `Fill in ${planPath} and narrow ${guidePath} before implementation starts.`,
1385
+ latestApprovedPlan: planPath,
1386
+ latestRelevantSummary: latestSummary,
1387
+ latestRun,
1388
+ currentGuide,
1389
+ currentHandoff,
1390
+ currentReview,
1391
+ currentFocusModules: [
1392
+ "AGENTS.md",
1393
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
1394
+ latestRun,
1395
+ latestSummary,
1396
+ planPath,
1397
+ currentGuide,
1398
+ currentHandoff,
1399
+ currentReview
1400
+ ],
1401
+ hotPath: [
1402
+ `${STATE_ROOT_NAME}/project.md`,
1403
+ `${STATE_ROOT_NAME}/state.md`,
1404
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`,
1405
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
1406
+ latestSummary,
1407
+ planPath,
1408
+ currentGuide
1409
+ ],
1410
+ activeAgents: sameCycle ? current.activeAgents : {
1411
+ main: "current main session",
1412
+ subagents: [],
1413
+ verification: null,
1414
+ consistency: null
1415
+ },
1416
+ notes: sameCycle ? current.notes : [
1417
+ `Current work cycle: ${title}.`,
1418
+ `Fill in the approved plan and scope guide before implementation starts.`,
1419
+ previousSummary === null ? "No previous summary was recorded." : `Previous relevant summary: ${previousSummary}`
1420
+ ]
1421
+ });
1422
+ const currentWrites = writeCurrentArtifacts(
1423
+ root,
1424
+ nextCurrent,
1425
+ buildTemplateContext(root).implementationMenuPath
1426
+ );
1427
+ if (currentWrites.index !== "unchanged") {
1428
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
1429
+ }
1430
+ if (currentWrites.state !== "unchanged") {
1431
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
1432
+ }
1433
+ if (currentWrites.readThisFirst !== "unchanged") {
1434
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
1435
+ }
1436
+ return {
1437
+ root,
1438
+ slug,
1439
+ title,
1440
+ createdFiles,
1441
+ keptFiles,
1442
+ updatedFiles,
1443
+ planPath,
1444
+ summaryPath,
1445
+ runPath: runWrite.path,
1446
+ handoffPath,
1447
+ reviewPath,
1448
+ guidePath
1449
+ };
1450
+ }
1451
+
1452
+ // src/commands/begin.ts
1453
+ function runBegin(args) {
1454
+ const [slug, ...titleParts] = args;
1455
+ if (slug === void 0) {
1456
+ console.error("Usage: sb-codex-tool begin <slug> [title words]");
1457
+ return 1;
1458
+ }
1459
+ const title = titleParts.length > 0 ? titleParts.join(" ") : void 0;
1460
+ const result = beginWorkCycle(process.cwd(), slug, title);
1461
+ console.log(`Project root: ${result.root}`);
1462
+ console.log(`Work cycle: ${result.title}`);
1463
+ console.log("");
1464
+ console.log("Artifacts:");
1465
+ console.log(`- plan: ${result.planPath}`);
1466
+ console.log(`- summary: ${result.summaryPath}`);
1467
+ console.log(`- run: ${result.runPath}`);
1468
+ console.log(`- handoff: ${result.handoffPath}`);
1469
+ console.log(`- review: ${result.reviewPath}`);
1470
+ console.log(`- guide: ${result.guidePath}`);
1471
+ console.log("");
1472
+ console.log(`Created files: ${result.createdFiles.length}`);
1473
+ for (const file of result.createdFiles) {
1474
+ console.log(`- ${file}`);
1475
+ }
1476
+ if (result.updatedFiles.length > 0) {
1477
+ console.log("");
1478
+ console.log("Updated state:");
1479
+ for (const file of result.updatedFiles) {
1480
+ console.log(`- ${file}`);
1481
+ }
1482
+ }
1483
+ return 0;
1484
+ }
1485
+
1486
+ // src/lib/close-cycle.ts
1487
+ import path9 from "node:path";
1488
+
1489
+ // src/lib/work-journal.ts
1490
+ import path8 from "node:path";
1491
+ function escapeRegExp(value) {
1492
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1493
+ }
1494
+ function normalizeList(items, fallback) {
1495
+ const unique = items.filter((item, index) => items.indexOf(item) === index);
1496
+ return unique.length > 0 ? unique : [fallback];
1497
+ }
1498
+ function renderBullet(item) {
1499
+ return item.split("\n").map((line, index) => index === 0 ? `- ${line}` : ` ${line}`).join("\n");
1500
+ }
1501
+ function renderBulletList2(items) {
1502
+ return normalizeList(items, "None.").map((item) => renderBullet(item)).join("\n");
1503
+ }
1504
+ function buildEntry(input) {
1505
+ return `## Entry: ${input.title}
1506
+
1507
+ ### Date
1508
+
1509
+ - ${input.dateStamp}
1510
+
1511
+ ### Summary
1512
+
1513
+ ${renderBulletList2(input.summaryLines)}
1514
+
1515
+ ### Completed
1516
+
1517
+ ${renderBulletList2(input.completedLines)}
1518
+
1519
+ ### Changed Areas
1520
+
1521
+ ${renderBulletList2(input.changedAreas)}
1522
+
1523
+ ### Verification
1524
+
1525
+ - Fresh verification verdict: \`${input.verdict}\`
1526
+ - Verification summary: \`${input.verificationSummaryPath}\`
1527
+
1528
+ ### Open Issues
1529
+
1530
+ ${renderBulletList2(input.openIssues)}
1531
+
1532
+ ### Next
1533
+
1534
+ - ${input.nextAction}
1535
+ `;
1536
+ }
1537
+ function getWorkJournalPath(root, dateStamp) {
1538
+ return path8.join(root, STATE_ROOT_NAME, "logs", "work-journal", `${dateStamp}.md`);
1539
+ }
1540
+ function writeWorkJournalEntry(root, input) {
1541
+ const journalPath = getWorkJournalPath(root, input.dateStamp);
1542
+ const current = readTextIfPresent(journalPath);
1543
+ const entry = buildEntry(input).trimEnd();
1544
+ if (current === null) {
1545
+ return writeFileIfChanged(
1546
+ journalPath,
1547
+ `# ${input.dateStamp}
1548
+
1549
+ ${entry}
1550
+ `
1551
+ );
1552
+ }
1553
+ const marker = `## Entry: ${input.title}`;
1554
+ const pattern = new RegExp(
1555
+ `${escapeRegExp(marker)}[\\s\\S]*?(?=\\n## Entry: |$)`,
1556
+ "m"
1557
+ );
1558
+ const next = pattern.test(current) ? current.replace(pattern, entry) : `${current.trimEnd()}
1559
+
1560
+ ${entry}
1561
+ `;
1562
+ return writeFileIfChanged(journalPath, next.endsWith("\n") ? next : `${next}
1563
+ `);
1564
+ }
1565
+
1566
+ // src/lib/close-cycle.ts
1567
+ var VERDICTS = ["pass", "pass_with_concerns", "fail", "blocked"];
1568
+ var REVIEW_PLACEHOLDERS = {
1569
+ findings: [
1570
+ "- Add findings here in severity order.",
1571
+ "- None."
1572
+ ],
1573
+ concerns: [
1574
+ "- Add non-blocking concerns here.",
1575
+ "- None."
1576
+ ],
1577
+ missingEvidence: [
1578
+ "- Add any blocked evidence here.",
1579
+ "- None."
1580
+ ]
1581
+ };
1582
+ function isClosingVerdict(verdict) {
1583
+ return verdict === "pass" || verdict === "pass_with_concerns";
1584
+ }
1585
+ function extractVerdict(content) {
1586
+ const verdict = extractSectionLines(content, "Verdict")[0]?.replace(/^- /, "");
1587
+ if (verdict === void 0 || !VERDICTS.includes(verdict)) {
1588
+ throw new Error("close requires the current review to contain a final verification verdict.");
1589
+ }
1590
+ return verdict;
1591
+ }
1592
+ function sectionContainsPlaceholder(lines, placeholders) {
1593
+ return lines.some((line) => placeholders.includes(line));
1594
+ }
1595
+ function sectionHasExplicitDetail(lines, placeholders) {
1596
+ return lines.some((line) => !placeholders.includes(line));
1597
+ }
1598
+ function parseReview(content) {
1599
+ const review = {
1600
+ verdict: extractVerdict(content),
1601
+ findings: extractSectionLines(content, "Findings"),
1602
+ concerns: extractSectionLines(content, "Concerns"),
1603
+ missingEvidence: extractSectionLines(content, "Missing Evidence"),
1604
+ checksRun: extractSectionLines(content, "Checks Run")
1605
+ };
1606
+ if (review.verdict === "pass_with_concerns" && (sectionContainsPlaceholder(review.concerns, REVIEW_PLACEHOLDERS.concerns) || !sectionHasExplicitDetail(review.concerns, REVIEW_PLACEHOLDERS.concerns))) {
1607
+ throw new Error(
1608
+ "close requires explicit concerns in the review before pass_with_concerns can close the cycle."
1609
+ );
1610
+ }
1611
+ if (review.verdict === "fail" && (sectionContainsPlaceholder(review.findings, REVIEW_PLACEHOLDERS.findings) || !sectionHasExplicitDetail(review.findings, REVIEW_PLACEHOLDERS.findings))) {
1612
+ throw new Error(
1613
+ "close requires explicit findings in the review before fail can be recorded."
1614
+ );
1615
+ }
1616
+ if (review.verdict === "blocked" && (sectionContainsPlaceholder(
1617
+ review.missingEvidence,
1618
+ REVIEW_PLACEHOLDERS.missingEvidence
1619
+ ) || !sectionHasExplicitDetail(
1620
+ review.missingEvidence,
1621
+ REVIEW_PLACEHOLDERS.missingEvidence
1622
+ ))) {
1623
+ throw new Error(
1624
+ "close requires explicit missing evidence details before blocked can be recorded."
1625
+ );
1626
+ }
1627
+ return review;
1628
+ }
1629
+ function updateWorkJournalDecision(content, verdict) {
1630
+ const decision = isClosingVerdict(verdict) ? "Verified closure is complete enough to update the work journal." : "Do not update the work journal as verified completion yet.";
1631
+ return content.replace(
1632
+ /(## Work Journal Decision\n\n)- .*?(\n)/,
1633
+ `$1- ${decision}$2`
1634
+ );
1635
+ }
1636
+ function readChecksRun(root, summaryPath) {
1637
+ const text = readTextIfPresent(path9.join(root, summaryPath));
1638
+ if (text === null) {
1639
+ return ["- none recorded"];
1640
+ }
1641
+ const section = text.match(/## Checks Run\s+([\s\S]*?)\n## /);
1642
+ if (section === null) {
1643
+ return ["- none recorded"];
1644
+ }
1645
+ const lines = section[1].trim().split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- "));
1646
+ return lines.length > 0 ? lines : ["- none recorded"];
1647
+ }
1648
+ function readSummaryContext(root, summaryPath) {
1649
+ const text = readTextIfPresent(path9.join(root, summaryPath));
1650
+ if (text === null) {
1651
+ return {
1652
+ scope: [],
1653
+ implementedSurface: [],
1654
+ nextAgentGuidance: []
1655
+ };
1656
+ }
1657
+ return {
1658
+ scope: extractSectionLines(text, "Scope"),
1659
+ implementedSurface: extractSectionLines(text, "Implemented Surface"),
1660
+ nextAgentGuidance: extractSectionLines(text, "Next-Agent Guidance")
1661
+ };
1662
+ }
1663
+ function readAllowedFileScope2(root, guidePath) {
1664
+ if (guidePath === null) {
1665
+ return [];
1666
+ }
1667
+ const text = readTextIfPresent(path9.join(root, guidePath));
1668
+ if (text === null) {
1669
+ return [];
1670
+ }
1671
+ return extractSectionLines(text, "Allowed File Scope");
1672
+ }
1673
+ function buildVerificationSummary(title, review, planPath, executionSummaryPath, reviewPath, runPath, gitContextSection, guidePath, checksRun) {
1674
+ const closureComplete = isClosingVerdict(review.verdict);
1675
+ const effectiveChecks = review.checksRun.length > 0 ? review.checksRun : checksRun;
1676
+ return `# Verification Summary: ${title}
1677
+
1678
+ ## Verdict
1679
+
1680
+ - ${review.verdict}
1681
+
1682
+ ## Verification Scope
1683
+
1684
+ - ${title}
1685
+ - ${planPath}
1686
+ - ${executionSummaryPath}
1687
+ - ${reviewPath}
1688
+ ${guidePath === null ? "" : `- ${guidePath}
1689
+ `}
1690
+ ${gitContextSection}
1691
+ ## Checks Run
1692
+
1693
+ ${effectiveChecks.join("\n")}
1694
+
1695
+ ## Evidence
1696
+
1697
+ - The fresh verification verdict is recorded in \`${reviewPath}\`.
1698
+ - Current-state artifacts can be updated from one shared writer after the
1699
+ review result is already present.
1700
+ - The latest verification summary is available for the next-agent hot path.
1701
+ ${closureComplete ? "- Verified closure is complete enough to update the work journal." : "- Verified closure is not complete enough to update the work journal yet."}
1702
+
1703
+ ## Plan vs Actual
1704
+
1705
+ - Planned: record the fresh verification result for the current cycle
1706
+ - Actual: the close flow reads the review artifact and records the result
1707
+ - Planned: keep current-state artifacts aligned after closure
1708
+ - Actual: the close flow updates the latest summary and hot-path references together
1709
+
1710
+ ## Findings
1711
+
1712
+ ${review.findings.length > 0 ? review.findings.join("\n") : "- None."}
1713
+
1714
+ ## Concerns
1715
+
1716
+ ${review.concerns.length > 0 ? review.concerns.join("\n") : "- None."}
1717
+
1718
+ ## Missing Evidence
1719
+
1720
+ ${review.missingEvidence.length > 0 ? review.missingEvidence.join("\n") : "- None."}
1721
+
1722
+ ## Closure Decision
1723
+
1724
+ - ${closureComplete ? "Verified closure is complete." : "Verified closure is not complete; keep the cycle in verify."}
1725
+
1726
+ ## Related Review Artifact
1727
+
1728
+ - ${reviewPath}
1729
+ `;
1730
+ }
1731
+ function closeCurrentCycle(start) {
1732
+ const root = resolveProjectRoot(start);
1733
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
1734
+ if (current.currentStage !== "verify") {
1735
+ throw new Error("close requires the current stage to be verify.");
1736
+ }
1737
+ if (current.latestApprovedPlan === null) {
1738
+ throw new Error("close requires a current approved plan.");
1739
+ }
1740
+ if (current.latestRelevantSummary === null) {
1741
+ throw new Error("close requires a current execution summary.");
1742
+ }
1743
+ if (current.currentReview === null) {
1744
+ throw new Error("close requires a current review artifact.");
1745
+ }
1746
+ const { dateStamp, slug } = parseCycleDescriptor(current.latestApprovedPlan);
1747
+ const title = readCycleTitle(root, current.latestApprovedPlan, slug);
1748
+ const runPath = lifecycleRunPath(dateStamp, slug);
1749
+ const verificationSummaryPath = `${STATE_ROOT_NAME}/summaries/${dateStamp}-${slug}-verification-summary.md`;
1750
+ const reviewAbsolutePath = path9.join(root, current.currentReview);
1751
+ const reviewBefore = readTextIfPresent(reviewAbsolutePath);
1752
+ if (reviewBefore === null) {
1753
+ throw new Error(`close requires the review artifact ${current.currentReview}.`);
1754
+ }
1755
+ const review = parseReview(reviewBefore);
1756
+ const createdFiles = [];
1757
+ const updatedFiles = [];
1758
+ const closingSummaryContext = isClosingVerdict(review.verdict) ? readSummaryContext(root, current.latestRelevantSummary) : null;
1759
+ const git = getGitContext(root);
1760
+ if (closingSummaryContext !== null && closingSummaryContext.nextAgentGuidance.length === 0) {
1761
+ throw new Error(
1762
+ "close requires the current execution summary to include explicit Next-Agent Guidance before verified closure."
1763
+ );
1764
+ }
1765
+ const reviewWrite = writeFileIfChanged(
1766
+ reviewAbsolutePath,
1767
+ updateWorkJournalDecision(reviewBefore, review.verdict)
1768
+ );
1769
+ if (reviewWrite === "updated") {
1770
+ updatedFiles.push(current.currentReview);
1771
+ }
1772
+ const checksRun = readChecksRun(root, current.latestRelevantSummary);
1773
+ const journalPath = isClosingVerdict(review.verdict) ? `${STATE_ROOT_NAME}/logs/work-journal/${dateStamp}.md` : null;
1774
+ const nextStage = isClosingVerdict(review.verdict) ? "clarify" : "verify";
1775
+ const runWrite = writeLifecycleRunRecord(root, {
1776
+ dateStamp,
1777
+ slug,
1778
+ title,
1779
+ phase: "close",
1780
+ stage: nextStage,
1781
+ verdict: review.verdict,
1782
+ git,
1783
+ paths: {
1784
+ planPath: current.latestApprovedPlan,
1785
+ executionSummaryPath: current.latestRelevantSummary,
1786
+ handoffPath: current.currentHandoff,
1787
+ reviewPath: current.currentReview,
1788
+ guidePath: current.currentGuide,
1789
+ verificationSummaryPath,
1790
+ journalPath
1791
+ }
1792
+ });
1793
+ if (runWrite.writeResult === "created") {
1794
+ createdFiles.push(runWrite.path);
1795
+ } else if (runWrite.writeResult === "updated") {
1796
+ updatedFiles.push(runWrite.path);
1797
+ }
1798
+ const verificationSummary = buildVerificationSummary(
1799
+ title,
1800
+ review,
1801
+ current.latestApprovedPlan,
1802
+ current.latestRelevantSummary,
1803
+ current.currentReview,
1804
+ runWrite.path,
1805
+ renderGitContextSection(runWrite.path, git),
1806
+ current.currentGuide,
1807
+ checksRun
1808
+ );
1809
+ const verificationWrite = writeFileIfChanged(
1810
+ path9.join(root, verificationSummaryPath),
1811
+ verificationSummary
1812
+ );
1813
+ if (verificationWrite === "created") {
1814
+ createdFiles.push(verificationSummaryPath);
1815
+ } else if (verificationWrite === "updated") {
1816
+ updatedFiles.push(verificationSummaryPath);
1817
+ }
1818
+ const nextAction = isClosingVerdict(review.verdict) ? "Start the next implementation increment from a new approved plan." : `Address the latest ${review.verdict} verification result for ${title} and rerun fresh verification.`;
1819
+ if (isClosingVerdict(review.verdict)) {
1820
+ const allowedFileScope = readAllowedFileScope2(root, current.currentGuide);
1821
+ const journalWrite = writeWorkJournalEntry(root, {
1822
+ dateStamp,
1823
+ title,
1824
+ verdict: review.verdict,
1825
+ summaryLines: [
1826
+ `Closed the ${title} increment with verdict \`${review.verdict}\`.`,
1827
+ ...stripBulletPrefix(closingSummaryContext.scope)
1828
+ ],
1829
+ completedLines: [
1830
+ ...stripBulletPrefix(closingSummaryContext.implementedSurface),
1831
+ `Recorded the fresh verification verdict in \`${current.currentReview}\`.`,
1832
+ `Wrote \`${verificationSummaryPath}\`.`,
1833
+ "Updated current-state artifacts for the next step."
1834
+ ],
1835
+ reviewPath: current.currentReview,
1836
+ verificationSummaryPath,
1837
+ changedAreas: stripBulletPrefix(allowedFileScope).length > 0 ? stripBulletPrefix(allowedFileScope) : [
1838
+ current.currentReview,
1839
+ verificationSummaryPath,
1840
+ current.latestApprovedPlan,
1841
+ current.currentGuide ?? "no current guide recorded",
1842
+ `${STATE_ROOT_NAME}/state.md`,
1843
+ `${STATE_ROOT_NAME}/index/current.json`,
1844
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`
1845
+ ],
1846
+ openIssues: review.verdict === "pass" ? ["None."] : review.concerns.map((line) => line.replace(/^- /, "")),
1847
+ nextAction
1848
+ });
1849
+ if (journalWrite === "created") {
1850
+ createdFiles.push(journalPath);
1851
+ } else if (journalWrite === "updated") {
1852
+ updatedFiles.push(journalPath);
1853
+ }
1854
+ }
1855
+ const nextCurrent = normalizeCurrentIndex({
1856
+ ...current,
1857
+ currentStage: nextStage,
1858
+ nextAction,
1859
+ latestRelevantSummary: verificationSummaryPath,
1860
+ latestRun: runWrite.path,
1861
+ currentFocusModules: [
1862
+ "AGENTS.md",
1863
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
1864
+ verificationSummaryPath,
1865
+ runWrite.path,
1866
+ current.currentReview,
1867
+ current.latestApprovedPlan,
1868
+ current.currentGuide ?? `${STATE_ROOT_NAME}/guides/read-this-first.md`
1869
+ ],
1870
+ hotPath: [
1871
+ `${STATE_ROOT_NAME}/project.md`,
1872
+ `${STATE_ROOT_NAME}/state.md`,
1873
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`,
1874
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
1875
+ verificationSummaryPath,
1876
+ current.latestApprovedPlan,
1877
+ current.currentGuide ?? `${STATE_ROOT_NAME}/guides/read-this-first.md`
1878
+ ],
1879
+ activeAgents: {
1880
+ ...current.activeAgents,
1881
+ verification: null
1882
+ },
1883
+ notes: isClosingVerdict(review.verdict) ? [
1884
+ `Current work cycle: ${title}.`,
1885
+ `The increment is closed with verdict ${review.verdict}.`,
1886
+ "The next increment can start from a new approved plan.",
1887
+ `Previous relevant summary: ${current.latestRelevantSummary}`
1888
+ ] : [
1889
+ `Current work cycle: ${title}.`,
1890
+ `The latest fresh verification verdict is ${review.verdict}.`,
1891
+ "Address the verification result before attempting closure again.",
1892
+ `Previous relevant summary: ${current.latestRelevantSummary}`
1893
+ ]
1894
+ });
1895
+ const currentWrites = writeCurrentArtifacts(
1896
+ root,
1897
+ nextCurrent,
1898
+ buildTemplateContext(root).implementationMenuPath
1899
+ );
1900
+ if (currentWrites.index !== "unchanged") {
1901
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
1902
+ }
1903
+ if (currentWrites.state !== "unchanged") {
1904
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
1905
+ }
1906
+ if (currentWrites.readThisFirst !== "unchanged") {
1907
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
1908
+ }
1909
+ return {
1910
+ root,
1911
+ title,
1912
+ verdict: review.verdict,
1913
+ reviewPath: current.currentReview,
1914
+ verificationSummaryPath,
1915
+ runPath: runWrite.path,
1916
+ journalPath,
1917
+ createdFiles,
1918
+ updatedFiles
1919
+ };
1920
+ }
1921
+
1922
+ // src/commands/close.ts
1923
+ function runClose(args) {
1924
+ if (args.length > 0) {
1925
+ console.error("Usage: sb-codex-tool close");
1926
+ return 1;
1927
+ }
1928
+ const result = closeCurrentCycle(process.cwd());
1929
+ console.log(`Project root: ${result.root}`);
1930
+ console.log(`Work cycle: ${result.title}`);
1931
+ console.log(`Verdict: ${result.verdict}`);
1932
+ console.log("");
1933
+ console.log("Closure artifacts:");
1934
+ console.log(`- review: ${result.reviewPath}`);
1935
+ console.log(`- verification summary: ${result.verificationSummaryPath}`);
1936
+ console.log(`- run: ${result.runPath}`);
1937
+ console.log(`- work journal: ${result.journalPath ?? "not updated for this verdict"}`);
1938
+ console.log("");
1939
+ console.log(`Created files: ${result.createdFiles.length}`);
1940
+ for (const file of result.createdFiles) {
1941
+ console.log(`- ${file}`);
1942
+ }
1943
+ if (result.updatedFiles.length > 0) {
1944
+ console.log("");
1945
+ console.log("Updated files:");
1946
+ for (const file of result.updatedFiles) {
1947
+ console.log(`- ${file}`);
1948
+ }
1949
+ }
1950
+ return 0;
1951
+ }
1952
+
1953
+ // src/lib/assignment-lifecycle.ts
1954
+ import path10 from "node:path";
1955
+ var ASSIGNMENT_LIFECYCLE_ACTIONS = ["close", "clear", "replace"];
1956
+ function buildLifecycleArtifact(agentName, title, action, assignmentPath, replacementAssignmentPath) {
1957
+ return `# Assignment Lifecycle: ${title}
1958
+
1959
+ ## Agent
1960
+
1961
+ - ${agentName}
1962
+
1963
+ ## Decision
1964
+
1965
+ - ${action}
1966
+
1967
+ ## Completed Assignment Guide
1968
+
1969
+ - ${assignmentPath}
1970
+
1971
+ ## Lifecycle Rule Applied
1972
+
1973
+ - Default rule is close and replace.
1974
+ - Clear is allowed only for the same narrow role after context reset.
1975
+ - Final verification remains fresh-agent-only.
1976
+
1977
+ ## Replacement Assignment
1978
+
1979
+ - ${replacementAssignmentPath ?? "none"}
1980
+
1981
+ ## Next Main-Agent Action
1982
+
1983
+ - ${replacementAssignmentPath === null ? "Keep the agent inactive until a new bounded assignment is created." : `Continue with the replacement assignment guide at \`${replacementAssignmentPath}\`.`}
1984
+ `;
1985
+ }
1986
+ function findAssignmentGuide(current, agentName) {
1987
+ const fromMap = current.assignmentGuides[agentName];
1988
+ if (fromMap !== void 0) {
1989
+ return fromMap;
1990
+ }
1991
+ if (current.latestApprovedPlan === null) {
1992
+ return null;
1993
+ }
1994
+ const { dateStamp } = parseCycleDescriptor(current.latestApprovedPlan);
1995
+ const agentSlug = normalizeSlug(agentName);
1996
+ return current.currentFocusModules.find(
1997
+ (entry) => entry.startsWith(`${STATE_ROOT_NAME}/guides/${dateStamp}-${agentSlug}-`) && entry.endsWith("-assignment.md")
1998
+ ) ?? null;
1999
+ }
2000
+ function removeAssignmentGuide(assignmentGuides, agentName) {
2001
+ const next = { ...assignmentGuides };
2002
+ delete next[agentName];
2003
+ return next;
2004
+ }
2005
+ function withoutItem(items, target) {
2006
+ return items.filter((item) => item !== target);
2007
+ }
2008
+ function ensureFocusModules(currentFocusModules, items) {
2009
+ const next = [...currentFocusModules];
2010
+ for (const item of items) {
2011
+ if (item !== null && !next.includes(item)) {
2012
+ next.push(item);
2013
+ }
2014
+ }
2015
+ return next;
2016
+ }
2017
+ function readAssignmentTitle(root, assignmentPath) {
2018
+ const text = readTextIfPresent(path10.join(root, assignmentPath));
2019
+ const match = text?.match(/^# Assignment Guide: (.+)$/m);
2020
+ return match?.[1]?.trim() ?? humanizeSlug(normalizeSlug(agentNameFromPath(assignmentPath)));
2021
+ }
2022
+ function agentNameFromPath(assignmentPath) {
2023
+ const fileName = path10.basename(assignmentPath, ".md");
2024
+ return fileName;
2025
+ }
2026
+ function completeAssignment(start, agentName, action, replacement) {
2027
+ const root = resolveProjectRoot(start);
2028
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
2029
+ if (current.latestApprovedPlan === null) {
2030
+ throw new Error("complete-assignment requires a current approved plan.");
2031
+ }
2032
+ if (!current.activeAgents.subagents.includes(agentName)) {
2033
+ throw new Error(`complete-assignment requires ${agentName} to be an active subagent.`);
2034
+ }
2035
+ const assignmentPath = findAssignmentGuide(current, agentName);
2036
+ if (assignmentPath === null) {
2037
+ throw new Error(`complete-assignment requires an active assignment guide for ${agentName}.`);
2038
+ }
2039
+ if (action === "replace" && replacement === void 0) {
2040
+ throw new Error("complete-assignment replace requires a replacement agent and slug.");
2041
+ }
2042
+ const createdFiles = [];
2043
+ const updatedFiles = [];
2044
+ let replacementAssignmentPath = null;
2045
+ let latestCurrent = current;
2046
+ if (action === "replace" && replacement !== void 0) {
2047
+ const replacementResult = createAssignmentGuide(
2048
+ root,
2049
+ replacement.agentName,
2050
+ replacement.slug,
2051
+ replacement.title
2052
+ );
2053
+ replacementAssignmentPath = replacementResult.assignmentPath;
2054
+ createdFiles.push(...replacementResult.createdFiles);
2055
+ updatedFiles.push(...replacementResult.updatedFiles);
2056
+ latestCurrent = normalizeCurrentIndex(readCurrentIndex(root));
2057
+ }
2058
+ const { dateStamp, slug } = parseCycleDescriptor(latestCurrent.latestApprovedPlan ?? current.latestApprovedPlan);
2059
+ const agentSlug = normalizeSlug(agentName);
2060
+ const lifecyclePath = `${STATE_ROOT_NAME}/handoffs/${dateStamp}-${slug}-${agentSlug}-assignment-lifecycle.md`;
2061
+ const assignmentTitle = readAssignmentTitle(root, assignmentPath);
2062
+ const lifecycleWrite = writeFileIfChanged(
2063
+ path10.join(root, lifecyclePath),
2064
+ buildLifecycleArtifact(
2065
+ agentName,
2066
+ assignmentTitle,
2067
+ action,
2068
+ assignmentPath,
2069
+ replacementAssignmentPath
2070
+ )
2071
+ );
2072
+ if (lifecycleWrite === "created") {
2073
+ createdFiles.push(lifecyclePath);
2074
+ } else if (lifecycleWrite === "updated") {
2075
+ updatedFiles.push(lifecyclePath);
2076
+ }
2077
+ const nextSubagents = withoutItem(latestCurrent.activeAgents.subagents, agentName);
2078
+ const nextAssignmentGuides = removeAssignmentGuide(latestCurrent.assignmentGuides, agentName);
2079
+ const nextFocusModules = ensureFocusModules(
2080
+ withoutItem(latestCurrent.currentFocusModules, assignmentPath),
2081
+ [lifecyclePath, replacementAssignmentPath]
2082
+ );
2083
+ const nextCurrent = normalizeCurrentIndex({
2084
+ ...latestCurrent,
2085
+ latestAssignmentLifecycle: lifecyclePath,
2086
+ currentFocusModules: nextFocusModules,
2087
+ activeAgents: {
2088
+ ...latestCurrent.activeAgents,
2089
+ subagents: nextSubagents
2090
+ },
2091
+ assignmentGuides: nextAssignmentGuides,
2092
+ notes: [
2093
+ `Current work cycle: ${humanizeSlug(parseCycleDescriptor(latestCurrent.latestApprovedPlan ?? current.latestApprovedPlan).slug)}.`,
2094
+ `Recorded ${action} lifecycle decision for ${agentName}: ${lifecyclePath}.`,
2095
+ replacementAssignmentPath === null ? `The completed assignment guide was ${assignmentPath}.` : `Replacement assignment guide: ${replacementAssignmentPath}.`,
2096
+ `Previous relevant summary: ${latestCurrent.latestRelevantSummary}`
2097
+ ]
2098
+ });
2099
+ const currentWrites = writeCurrentArtifacts(
2100
+ root,
2101
+ nextCurrent,
2102
+ buildTemplateContext(root).implementationMenuPath
2103
+ );
2104
+ if (currentWrites.index !== "unchanged") {
2105
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
2106
+ }
2107
+ if (currentWrites.state !== "unchanged") {
2108
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
2109
+ }
2110
+ if (currentWrites.readThisFirst !== "unchanged") {
2111
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
2112
+ }
2113
+ return {
2114
+ root,
2115
+ agentName,
2116
+ action,
2117
+ lifecyclePath,
2118
+ replacementAssignmentPath,
2119
+ createdFiles,
2120
+ updatedFiles
2121
+ };
2122
+ }
2123
+
2124
+ // src/commands/complete-assignment.ts
2125
+ function runCompleteAssignment(args) {
2126
+ const [agentName, action, third, ...rest] = args;
2127
+ if (agentName === void 0 || action === void 0 || !ASSIGNMENT_LIFECYCLE_ACTIONS.includes(action)) {
2128
+ console.error(
2129
+ "Usage: sb-codex-tool complete-assignment <agent-name> <close|clear|replace> [replacement-agent] [replacement-slug] [title words]"
2130
+ );
2131
+ return 1;
2132
+ }
2133
+ const replacement = action === "replace" ? {
2134
+ agentName: third,
2135
+ slug: rest[0],
2136
+ title: rest.slice(1).length > 0 ? rest.slice(1).join(" ") : void 0
2137
+ } : void 0;
2138
+ if (action === "replace" && (replacement?.agentName === void 0 || replacement.slug === void 0)) {
2139
+ console.error(
2140
+ "Usage: sb-codex-tool complete-assignment <agent-name> replace <replacement-agent> <replacement-slug> [title words]"
2141
+ );
2142
+ return 1;
2143
+ }
2144
+ const result = completeAssignment(process.cwd(), agentName, action, replacement);
2145
+ console.log(`Project root: ${result.root}`);
2146
+ console.log(`Completed assignment for: ${result.agentName}`);
2147
+ console.log(`Lifecycle action: ${result.action}`);
2148
+ console.log(`Lifecycle artifact: ${result.lifecyclePath}`);
2149
+ console.log(`Replacement assignment: ${result.replacementAssignmentPath ?? "none"}`);
2150
+ console.log("");
2151
+ console.log(`Created files: ${result.createdFiles.length}`);
2152
+ for (const file of result.createdFiles) {
2153
+ console.log(`- ${file}`);
2154
+ }
2155
+ if (result.updatedFiles.length > 0) {
2156
+ console.log("");
2157
+ console.log("Updated files:");
2158
+ for (const file of result.updatedFiles) {
2159
+ console.log(`- ${file}`);
2160
+ }
2161
+ }
2162
+ return 0;
2163
+ }
2164
+
2165
+ // src/lib/doctor.ts
2166
+ import { existsSync as existsSync5 } from "node:fs";
2167
+ import path12 from "node:path";
2168
+
2169
+ // src/lib/state-coherence.ts
2170
+ import { existsSync as existsSync4 } from "node:fs";
2171
+ import path11 from "node:path";
2172
+ function createIssue(label, detail, level = "fail") {
2173
+ return { level, label, detail };
2174
+ }
2175
+ function validateLatestRunCoherence(current, latestRunRecord) {
2176
+ if (current.latestRun === null) {
2177
+ return [];
2178
+ }
2179
+ if (latestRunRecord === null) {
2180
+ return [
2181
+ createIssue(
2182
+ "latest run coherence",
2183
+ "current state references a latest run that could not be read"
2184
+ )
2185
+ ];
2186
+ }
2187
+ const issues = [];
2188
+ if (latestRunRecord.stage !== current.currentStage) {
2189
+ issues.push(
2190
+ createIssue(
2191
+ "latest run coherence",
2192
+ `current stage ${current.currentStage} does not match latest run stage ${latestRunRecord.stage}`
2193
+ )
2194
+ );
2195
+ }
2196
+ if (current.latestApprovedPlan !== latestRunRecord.paths.planPath) {
2197
+ issues.push(
2198
+ createIssue(
2199
+ "latest run coherence",
2200
+ "current approved plan does not match the latest run plan path"
2201
+ )
2202
+ );
2203
+ }
2204
+ const allowedSummaryPaths = [
2205
+ latestRunRecord.paths.executionSummaryPath,
2206
+ latestRunRecord.paths.verificationSummaryPath
2207
+ ].filter((value) => value !== null);
2208
+ if (current.latestRelevantSummary !== null && !allowedSummaryPaths.includes(current.latestRelevantSummary)) {
2209
+ issues.push(
2210
+ createIssue(
2211
+ "latest run coherence",
2212
+ "current latest summary does not match the latest run summary paths"
2213
+ )
2214
+ );
2215
+ }
2216
+ if (current.currentGuide !== latestRunRecord.paths.guidePath) {
2217
+ issues.push(
2218
+ createIssue(
2219
+ "latest run coherence",
2220
+ "current guide does not match the latest run guide path"
2221
+ )
2222
+ );
2223
+ }
2224
+ if (current.currentHandoff !== latestRunRecord.paths.handoffPath) {
2225
+ issues.push(
2226
+ createIssue(
2227
+ "latest run coherence",
2228
+ "current handoff does not match the latest run handoff path"
2229
+ )
2230
+ );
2231
+ }
2232
+ if (current.currentReview !== latestRunRecord.paths.reviewPath) {
2233
+ issues.push(
2234
+ createIssue(
2235
+ "latest run coherence",
2236
+ "current review does not match the latest run review path"
2237
+ )
2238
+ );
2239
+ }
2240
+ return issues;
2241
+ }
2242
+ function validateVerifyStage(current) {
2243
+ const issues = [];
2244
+ if (current.currentStage !== "verify") {
2245
+ if (current.activeAgents.verification !== null) {
2246
+ issues.push(
2247
+ createIssue(
2248
+ "verification-agent coherence",
2249
+ "verification agent is still assigned outside the verify stage"
2250
+ )
2251
+ );
2252
+ }
2253
+ return issues;
2254
+ }
2255
+ if (current.activeAgents.verification === null) {
2256
+ issues.push(
2257
+ createIssue(
2258
+ "verification-agent coherence",
2259
+ "verify stage requires a pending or assigned verification agent"
2260
+ )
2261
+ );
2262
+ }
2263
+ const missingArtifacts = [];
2264
+ if (current.currentGuide === null) {
2265
+ missingArtifacts.push("current guide");
2266
+ }
2267
+ if (current.currentHandoff === null) {
2268
+ missingArtifacts.push("current handoff");
2269
+ }
2270
+ if (current.currentReview === null) {
2271
+ missingArtifacts.push("current review");
2272
+ }
2273
+ if (current.latestRelevantSummary === null) {
2274
+ missingArtifacts.push("latest relevant summary");
2275
+ }
2276
+ if (current.latestApprovedPlan === null) {
2277
+ missingArtifacts.push("latest approved plan");
2278
+ }
2279
+ if (missingArtifacts.length > 0) {
2280
+ issues.push(
2281
+ createIssue(
2282
+ "verify-stage readiness",
2283
+ `verify stage is missing required references: ${missingArtifacts.join(", ")}`
2284
+ )
2285
+ );
2286
+ }
2287
+ return issues;
2288
+ }
2289
+ function validateAssignmentGuides(root, current) {
2290
+ const issues = [];
2291
+ const activeSubagents = new Set(current.activeAgents.subagents);
2292
+ const guideEntries = Object.entries(current.assignmentGuides);
2293
+ for (const agentName of activeSubagents) {
2294
+ if (current.assignmentGuides[agentName] === void 0) {
2295
+ issues.push(
2296
+ createIssue(
2297
+ "assignment-guide coherence",
2298
+ `active subagent ${agentName} is missing an assignment guide`
2299
+ )
2300
+ );
2301
+ }
2302
+ }
2303
+ for (const [agentName, guidePath] of guideEntries) {
2304
+ if (!activeSubagents.has(agentName)) {
2305
+ issues.push(
2306
+ createIssue(
2307
+ "assignment-guide coherence",
2308
+ `assignment guide for ${agentName} is still registered without an active subagent`
2309
+ )
2310
+ );
2311
+ continue;
2312
+ }
2313
+ if (!existsSync4(path11.join(root, guidePath))) {
2314
+ issues.push(
2315
+ createIssue(
2316
+ "assignment-guide coherence",
2317
+ `assignment guide for ${agentName} is missing: ${guidePath}`
2318
+ )
2319
+ );
2320
+ }
2321
+ }
2322
+ return issues;
2323
+ }
2324
+ function collectStateCoherenceIssues(root, current, latestRunRecord) {
2325
+ if (current === null) {
2326
+ return [];
2327
+ }
2328
+ return [
2329
+ ...validateLatestRunCoherence(current, latestRunRecord),
2330
+ ...validateVerifyStage(current),
2331
+ ...validateAssignmentGuides(root, current)
2332
+ ];
2333
+ }
2334
+
2335
+ // src/lib/doctor.ts
2336
+ var PLAN_PLACEHOLDERS = [
2337
+ "Replace with the concrete objective for this work cycle.",
2338
+ "Replace with the acceptance criteria that define completion.",
2339
+ "Replace with in-scope and out-of-scope notes.",
2340
+ "fill-in-file-scope",
2341
+ "describe the concrete implementation task",
2342
+ "describe how this task will be checked"
2343
+ ];
2344
+ var GUIDE_PLACEHOLDERS = [
2345
+ "Replace with the concrete goal for this cycle.",
2346
+ "Replace with the files or modules that should be touched.",
2347
+ "Replace with the checks and verdict expectations for this cycle."
2348
+ ];
2349
+ var EXECUTION_SUMMARY_PLACEHOLDERS = [
2350
+ "Replace with the actual implementation scope.",
2351
+ "not started yet",
2352
+ "none yet",
2353
+ "Update this section as implementation progresses.",
2354
+ "Update this section after refactor.",
2355
+ "Add deferred issues if they exist."
2356
+ ];
2357
+ var HANDOFF_PLACEHOLDERS = [
2358
+ "Replace with the current implementation status.",
2359
+ "Replace with the checks relevant to this work cycle.",
2360
+ "Replace with the current blocker or risk list."
2361
+ ];
2362
+ var CONSISTENCY_REVIEW_PLACEHOLDERS = [
2363
+ "Add findings here in severity order.",
2364
+ "Add follow-up recommendations here.",
2365
+ `Record whether ${STATE_ROOT_NAME}/guides/code-consistency.md needs an update.`
2366
+ ];
2367
+ function findPlaceholderMatches(text, placeholders) {
2368
+ return placeholders.filter((placeholder) => text.includes(placeholder));
2369
+ }
2370
+ function validatePlaceholderArtifact(root, check) {
2371
+ if (check.relativePath === null) {
2372
+ return null;
2373
+ }
2374
+ const text = readTextIfPresent(path12.join(root, check.relativePath));
2375
+ if (text === null) {
2376
+ return {
2377
+ level: "fail",
2378
+ label: check.label,
2379
+ detail: `missing artifact: ${check.relativePath}`
2380
+ };
2381
+ }
2382
+ const matches = findPlaceholderMatches(text, check.placeholders);
2383
+ if (matches.length === 0) {
2384
+ return {
2385
+ level: "ok",
2386
+ label: check.label,
2387
+ detail: "no scaffold placeholders detected"
2388
+ };
2389
+ }
2390
+ return {
2391
+ level: "fail",
2392
+ label: check.label,
2393
+ detail: `contains placeholder content: ${matches.join("; ")}`
2394
+ };
2395
+ }
2396
+ function pushExistenceChecks(root, items, labelPrefix, results) {
2397
+ for (const item of items) {
2398
+ results.push(
2399
+ existsSync5(path12.join(root, item)) ? {
2400
+ level: "ok",
2401
+ label: `${labelPrefix}: ${item}`,
2402
+ detail: "present"
2403
+ } : {
2404
+ level: "fail",
2405
+ label: `${labelPrefix}: ${item}`,
2406
+ detail: "missing"
2407
+ }
2408
+ );
2409
+ }
2410
+ }
2411
+ function validateAgents(root) {
2412
+ const file = path12.join(root, "AGENTS.md");
2413
+ const text = readTextIfPresent(file);
2414
+ if (text === null) {
2415
+ return [
2416
+ {
2417
+ level: "fail",
2418
+ label: "AGENTS.md",
2419
+ detail: "missing"
2420
+ }
2421
+ ];
2422
+ }
2423
+ const snippets = [
2424
+ "fresh agent",
2425
+ "Korean",
2426
+ "code-consistency.md",
2427
+ "work journal",
2428
+ "refactor"
2429
+ ];
2430
+ const missing = snippets.filter((snippet) => !text.includes(snippet));
2431
+ if (missing.length === 0) {
2432
+ return [
2433
+ {
2434
+ level: "ok",
2435
+ label: "AGENTS.md",
2436
+ detail: "contains required operational guidance"
2437
+ }
2438
+ ];
2439
+ }
2440
+ return [
2441
+ {
2442
+ level: "warn",
2443
+ label: "AGENTS.md",
2444
+ detail: `missing guidance snippets: ${missing.join(", ")}`
2445
+ }
2446
+ ];
2447
+ }
2448
+ function validateIndex(root) {
2449
+ const file = path12.join(root, ".sb-codex-tool/index/current.json");
2450
+ const current = readJsonIfPresent(file);
2451
+ if (current === null) {
2452
+ return {
2453
+ level: "fail",
2454
+ label: "index/current.json",
2455
+ detail: "missing or unreadable"
2456
+ };
2457
+ }
2458
+ const requiredKeys = [
2459
+ "currentStage",
2460
+ "nextAction",
2461
+ "hotPath",
2462
+ "activeAgents"
2463
+ ];
2464
+ const missing = requiredKeys.filter((key) => !(key in current));
2465
+ if (missing.length > 0) {
2466
+ return {
2467
+ level: "warn",
2468
+ label: "index/current.json",
2469
+ detail: `missing keys: ${missing.join(", ")}`
2470
+ };
2471
+ }
2472
+ return {
2473
+ level: "ok",
2474
+ label: "index/current.json",
2475
+ detail: "structured current state is present"
2476
+ };
2477
+ }
2478
+ function validateConsistencyGuide(root) {
2479
+ const file = path12.join(root, ".sb-codex-tool/guides/code-consistency.md");
2480
+ const text = readTextIfPresent(file);
2481
+ if (text === null) {
2482
+ return {
2483
+ level: "fail",
2484
+ label: "code-consistency.md",
2485
+ detail: "missing"
2486
+ };
2487
+ }
2488
+ const requiredSections = [
2489
+ "Naming Rules",
2490
+ "Module Boundary Rules",
2491
+ "Reuse Rules",
2492
+ "Readability Rules",
2493
+ "Anti-Patterns"
2494
+ ];
2495
+ const missing = requiredSections.filter((section) => !text.includes(section));
2496
+ if (missing.length === 0) {
2497
+ return {
2498
+ level: "ok",
2499
+ label: "code-consistency.md",
2500
+ detail: "contains the expected guidance sections"
2501
+ };
2502
+ }
2503
+ return {
2504
+ level: "warn",
2505
+ label: "code-consistency.md",
2506
+ detail: `missing sections: ${missing.join(", ")}`
2507
+ };
2508
+ }
2509
+ function validateIgnoreStrategy(root) {
2510
+ const files = [".ignore", ".rgignore"];
2511
+ const requiredPattern = ".sb-codex-tool/logs/work-journal/";
2512
+ const missing = files.filter((file) => {
2513
+ const text = readTextIfPresent(path12.join(root, file));
2514
+ return text === null || !text.includes(requiredPattern);
2515
+ });
2516
+ if (missing.length === 0) {
2517
+ return {
2518
+ level: "ok",
2519
+ label: "ignore strategy",
2520
+ detail: "default search hot path excludes work-journal noise"
2521
+ };
2522
+ }
2523
+ return {
2524
+ level: "warn",
2525
+ label: "ignore strategy",
2526
+ detail: `missing work-journal exclusion in: ${missing.join(", ")}`
2527
+ };
2528
+ }
2529
+ function validateCurrentReferences(root) {
2530
+ const current = readCurrentIndex(root);
2531
+ if (current === null) {
2532
+ return [];
2533
+ }
2534
+ const referencedPaths = [
2535
+ current.latestApprovedPlan,
2536
+ current.latestRelevantSummary,
2537
+ current.latestRun,
2538
+ current.latestConsistencyReview,
2539
+ current.currentGuide,
2540
+ current.currentHandoff,
2541
+ current.currentReview,
2542
+ ...current.hotPath
2543
+ ].filter((value) => value !== null);
2544
+ const uniquePaths = referencedPaths.filter(
2545
+ (value, index, array) => array.indexOf(value) === index
2546
+ );
2547
+ return uniquePaths.map(
2548
+ (relativePath) => existsSync5(path12.join(root, relativePath)) ? {
2549
+ level: "ok",
2550
+ label: `current reference: ${relativePath}`,
2551
+ detail: "present"
2552
+ } : {
2553
+ level: "fail",
2554
+ label: `current reference: ${relativePath}`,
2555
+ detail: "missing"
2556
+ }
2557
+ );
2558
+ }
2559
+ function validateReadThisFirst(root) {
2560
+ const current = readCurrentIndex(root);
2561
+ const text = readTextIfPresent(path12.join(root, ".sb-codex-tool/guides/read-this-first.md"));
2562
+ if (current === null || text === null) {
2563
+ return {
2564
+ level: "fail",
2565
+ label: "read-this-first hot path",
2566
+ detail: "missing current index or read-this-first guide"
2567
+ };
2568
+ }
2569
+ const hotPathSection = text.match(
2570
+ /## Hot Path\s+Read in this order before implementation or verification:\s+([\s\S]*?)\n## Additional Repo Docs/
2571
+ );
2572
+ if (hotPathSection === null) {
2573
+ return {
2574
+ level: "fail",
2575
+ label: "read-this-first hot path",
2576
+ detail: "missing hot-path section structure"
2577
+ };
2578
+ }
2579
+ const actualHotPath = hotPathSection[1].trim().split("\n").map((line) => line.trim()).filter((line) => /^\d+\.\s+/.test(line)).map((line) => line.replace(/^\d+\.\s+/, ""));
2580
+ const missing = current.hotPath.filter((relativePath) => !text.includes(relativePath));
2581
+ if (missing.length === 0) {
2582
+ const orderMatches = actualHotPath.length === current.hotPath.length && actualHotPath.every((value, index) => value === current.hotPath[index]);
2583
+ if (!orderMatches) {
2584
+ return {
2585
+ level: "fail",
2586
+ label: "read-this-first hot path",
2587
+ detail: "hot-path order does not match current state"
2588
+ };
2589
+ }
2590
+ const mismatches = [];
2591
+ const cycleChecks = [
2592
+ [`Current stage: ${current.currentStage}`, "current stage"],
2593
+ [
2594
+ `Latest approved plan: ${current.latestApprovedPlan ?? "none yet"}`,
2595
+ "latest approved plan"
2596
+ ],
2597
+ [
2598
+ `Latest relevant summary: ${current.latestRelevantSummary ?? "none yet"}`,
2599
+ "latest relevant summary"
2600
+ ],
2601
+ [
2602
+ `Latest lifecycle run: ${current.latestRun ?? "none yet"}`,
2603
+ "latest lifecycle run"
2604
+ ],
2605
+ [
2606
+ `Current task guide: ${current.currentGuide ?? "none yet"}`,
2607
+ "current task guide"
2608
+ ],
2609
+ [
2610
+ `Current handoff: ${current.currentHandoff ?? "none yet"}`,
2611
+ "current handoff"
2612
+ ],
2613
+ [
2614
+ `Current review: ${current.currentReview ?? "none yet"}`,
2615
+ "current review"
2616
+ ]
2617
+ ];
2618
+ for (const [expected, label] of cycleChecks) {
2619
+ if (!text.includes(expected)) {
2620
+ mismatches.push(label);
2621
+ }
2622
+ }
2623
+ if (mismatches.length === 0) {
2624
+ return {
2625
+ level: "ok",
2626
+ label: "read-this-first hot path",
2627
+ detail: "matches the current hot-path references and cycle metadata"
2628
+ };
2629
+ }
2630
+ return {
2631
+ level: "fail",
2632
+ label: "read-this-first hot path",
2633
+ detail: `stale current-cycle metadata: ${mismatches.join(", ")}`
2634
+ };
2635
+ }
2636
+ return {
2637
+ level: "fail",
2638
+ label: "read-this-first hot path",
2639
+ detail: `missing current hot-path references: ${missing.join(", ")}`
2640
+ };
2641
+ }
2642
+ function validateCurrentArtifactSemantics(root) {
2643
+ const current = readCurrentIndex(root);
2644
+ if (current === null) {
2645
+ return [];
2646
+ }
2647
+ const checks = [
2648
+ {
2649
+ label: "approved plan readiness",
2650
+ relativePath: current.latestApprovedPlan,
2651
+ placeholders: PLAN_PLACEHOLDERS
2652
+ },
2653
+ {
2654
+ label: "current task guide readiness",
2655
+ relativePath: current.currentGuide,
2656
+ placeholders: GUIDE_PLACEHOLDERS
2657
+ },
2658
+ {
2659
+ label: "current handoff readiness",
2660
+ relativePath: current.currentHandoff,
2661
+ placeholders: HANDOFF_PLACEHOLDERS
2662
+ },
2663
+ {
2664
+ label: "latest consistency review readiness",
2665
+ relativePath: current.latestConsistencyReview,
2666
+ placeholders: CONSISTENCY_REVIEW_PLACEHOLDERS
2667
+ }
2668
+ ];
2669
+ if (current.latestRelevantSummary !== null && current.latestRelevantSummary.endsWith("-execution-summary.md")) {
2670
+ checks.push({
2671
+ label: "execution summary readiness",
2672
+ relativePath: current.latestRelevantSummary,
2673
+ placeholders: EXECUTION_SUMMARY_PLACEHOLDERS
2674
+ });
2675
+ }
2676
+ return checks.map((check) => validatePlaceholderArtifact(root, check)).filter((result) => result !== null);
2677
+ }
2678
+ function runDoctor(start = process.cwd()) {
2679
+ const root = resolveProjectRoot(start);
2680
+ const results = [];
2681
+ const current = readCurrentIndex(root);
2682
+ const latestRunRecord = readLifecycleRunRecord(root, current?.latestRun ?? null);
2683
+ pushExistenceChecks(root, REQUIRED_DIRECTORIES, "directory", results);
2684
+ pushExistenceChecks(root, REQUIRED_FILES, "file", results);
2685
+ pushExistenceChecks(root, REQUIRED_WORKFLOW_FILES, "workflow", results);
2686
+ results.push(...validateAgents(root));
2687
+ results.push(validateIndex(root));
2688
+ results.push(validateConsistencyGuide(root));
2689
+ results.push(validateIgnoreStrategy(root));
2690
+ results.push(...validateCurrentReferences(root));
2691
+ results.push(validateReadThisFirst(root));
2692
+ results.push(...validateCurrentArtifactSemantics(root));
2693
+ results.push(...collectStateCoherenceIssues(root, current, latestRunRecord));
2694
+ return { root, results };
2695
+ }
2696
+
2697
+ // src/commands/doctor.ts
2698
+ function formatResult(level) {
2699
+ if (level === "ok") {
2700
+ return "[ok]";
2701
+ }
2702
+ if (level === "warn") {
2703
+ return "[warn]";
2704
+ }
2705
+ return "[fail]";
2706
+ }
2707
+ function runDoctorCommand() {
2708
+ const report = runDoctor();
2709
+ let hasFailure = false;
2710
+ console.log(`Project root: ${report.root}`);
2711
+ console.log("");
2712
+ for (const result of report.results) {
2713
+ console.log(`${formatResult(result.level)} ${result.label}: ${result.detail}`);
2714
+ if (result.level === "fail") {
2715
+ hasFailure = true;
2716
+ }
2717
+ }
2718
+ return hasFailure ? 1 : 0;
2719
+ }
2720
+
2721
+ // src/lib/launch.ts
2722
+ import { spawnSync as spawnSync2 } from "node:child_process";
2723
+ import { existsSync as existsSync7, writeFileSync as writeFileSync2 } from "node:fs";
2724
+ import path14 from "node:path";
2725
+
2726
+ // src/lib/codex.ts
2727
+ import { existsSync as existsSync6 } from "node:fs";
2728
+ import path13 from "node:path";
2729
+ function findExecutableInPath(binaryName, searchPath) {
2730
+ for (const entry of searchPath.split(path13.delimiter)) {
2731
+ if (entry.trim().length === 0) {
2732
+ continue;
2733
+ }
2734
+ const candidate = path13.join(entry, binaryName);
2735
+ if (existsSync6(candidate)) {
2736
+ return candidate;
2737
+ }
2738
+ }
2739
+ return null;
2740
+ }
2741
+ function resolveCodexBinary(root = process.cwd(), searchPath = process.env.PATH ?? "") {
2742
+ const localBinary = path13.join(root, "node_modules", ".bin", "codex");
2743
+ if (existsSync6(localBinary)) {
2744
+ return localBinary;
2745
+ }
2746
+ return findExecutableInPath("codex", searchPath);
2747
+ }
2748
+
2749
+ // src/lib/launch.ts
2750
+ function getInstructionSurface(hotPath) {
2751
+ return ["AGENTS.md", ...hotPath].filter(
2752
+ (value, index, array) => array.indexOf(value) === index
2753
+ );
2754
+ }
2755
+ function writeInstructionSurfaceFile(root, instructionSurface) {
2756
+ const relativePath = ".sb-codex-tool/index/current-launch-instructions.txt";
2757
+ const absolutePath = path14.join(root, relativePath);
2758
+ ensureDir(path14.dirname(absolutePath));
2759
+ writeFileSync2(absolutePath, instructionSurface.join("\n") + "\n", "utf8");
2760
+ return relativePath;
2761
+ }
2762
+ function writeLaunchMetadata(root, relativePath, metadata) {
2763
+ const absolutePath = path14.join(root, relativePath);
2764
+ ensureDir(path14.dirname(absolutePath));
2765
+ writeFileSync2(absolutePath, JSON.stringify(metadata, null, 2) + "\n", "utf8");
2766
+ }
2767
+ function recordLaunchStart(root, args, current, codexBinary, instructionSurface, instructionSurfaceFile, missingHotPath) {
2768
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "-");
2769
+ const relativePath = `.sb-codex-tool/runs/launch-${timestamp}.json`;
2770
+ const metadata = {
2771
+ version: 2,
2772
+ launchedAt: (/* @__PURE__ */ new Date()).toISOString(),
2773
+ completedAt: null,
2774
+ cwd: root,
2775
+ args,
2776
+ codexBinary,
2777
+ instructionSurface,
2778
+ instructionSurfaceFile,
2779
+ currentStage: current?.currentStage ?? "unknown",
2780
+ latestPlan: current?.latestApprovedPlan ?? null,
2781
+ latestSummary: current?.latestRelevantSummary ?? null,
2782
+ latestRun: current?.latestRun ?? null,
2783
+ preflight: {
2784
+ missingHotPath,
2785
+ scaffoldPresent: true
2786
+ },
2787
+ exitStatus: null,
2788
+ failedReason: null
2789
+ };
2790
+ writeLaunchMetadata(root, relativePath, metadata);
2791
+ return { relativePath, metadata };
2792
+ }
2793
+ function finalizeLaunchMetadata(root, relativePath, metadata, exitStatus, failedReason) {
2794
+ writeLaunchMetadata(root, relativePath, {
2795
+ ...metadata,
2796
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2797
+ exitStatus,
2798
+ failedReason
2799
+ });
2800
+ }
2801
+ function launchCodex(start, args, env = process.env, spawnImpl = spawnSync2) {
2802
+ const root = resolveProjectRoot(start);
2803
+ const stateRoot = statePath(root);
2804
+ if (!existsSync7(stateRoot)) {
2805
+ throw new Error("Missing .sb-codex-tool scaffold. Run `sb-codex-tool setup` first.");
2806
+ }
2807
+ const current = readJsonIfPresent(statePath(root, "index", "current.json"));
2808
+ const instructionSurface = getInstructionSurface(current?.hotPath ?? []);
2809
+ const missingHotPath = instructionSurface.filter((file) => !existsSync7(path14.join(root, file)));
2810
+ const instructionSurfaceFile = writeInstructionSurfaceFile(root, instructionSurface);
2811
+ const codexBinary = resolveCodexBinary(root, env.PATH ?? "");
2812
+ const launch = recordLaunchStart(
2813
+ root,
2814
+ args,
2815
+ current,
2816
+ codexBinary,
2817
+ instructionSurface,
2818
+ instructionSurfaceFile,
2819
+ missingHotPath
2820
+ );
2821
+ if (missingHotPath.length > 0) {
2822
+ finalizeLaunchMetadata(
2823
+ root,
2824
+ launch.relativePath,
2825
+ launch.metadata,
2826
+ 1,
2827
+ `missing hot-path files: ${missingHotPath.join(", ")}`
2828
+ );
2829
+ throw new Error(
2830
+ `Launch preflight failed because hot-path files are missing: ${missingHotPath.join(", ")}.`
2831
+ );
2832
+ }
2833
+ if (codexBinary === null) {
2834
+ finalizeLaunchMetadata(
2835
+ root,
2836
+ launch.relativePath,
2837
+ launch.metadata,
2838
+ 1,
2839
+ "Codex binary not found in PATH or node_modules/.bin."
2840
+ );
2841
+ throw new Error("Codex binary not found in PATH. Install or expose `codex` first.");
2842
+ }
2843
+ const result = spawnImpl(codexBinary, args, {
2844
+ cwd: root,
2845
+ env: {
2846
+ ...env,
2847
+ SB_CODEX_TOOL_PROJECT_INSTRUCTIONS: instructionSurface.join("\n"),
2848
+ SB_CODEX_TOOL_PROJECT_INSTRUCTIONS_FILE: instructionSurfaceFile,
2849
+ SB_CODEX_TOOL_CURRENT_STAGE: current?.currentStage ?? "unknown",
2850
+ SB_CODEX_TOOL_LATEST_PLAN: current?.latestApprovedPlan ?? "",
2851
+ SB_CODEX_TOOL_LATEST_SUMMARY: current?.latestRelevantSummary ?? "",
2852
+ SB_CODEX_TOOL_LATEST_RUN: current?.latestRun ?? ""
2853
+ },
2854
+ stdio: "inherit"
2855
+ });
2856
+ finalizeLaunchMetadata(
2857
+ root,
2858
+ launch.relativePath,
2859
+ launch.metadata,
2860
+ result.status ?? 1,
2861
+ result.error?.message ?? null
2862
+ );
2863
+ return {
2864
+ root,
2865
+ launchFile: launch.relativePath,
2866
+ instructionSurface,
2867
+ instructionSurfaceFile,
2868
+ exitStatus: result.status ?? 1
2869
+ };
2870
+ }
2871
+
2872
+ // src/commands/launch.ts
2873
+ function runLaunch(args) {
2874
+ try {
2875
+ const result = launchCodex(process.cwd(), args);
2876
+ console.log(`Launch metadata: ${result.launchFile}`);
2877
+ console.log(`Instruction surface file: ${result.instructionSurfaceFile}`);
2878
+ console.log("Instruction surface for this session:");
2879
+ for (const file of result.instructionSurface) {
2880
+ console.log(`- ${file}`);
2881
+ }
2882
+ return result.exitStatus;
2883
+ } catch (error) {
2884
+ console.error(error instanceof Error ? error.message : String(error));
2885
+ return 1;
2886
+ }
2887
+ }
2888
+
2889
+ // src/lib/prepare-verify.ts
2890
+ import path15 from "node:path";
2891
+ function readSummaryContext2(root, summaryPath) {
2892
+ const text = readTextIfPresent(path15.join(root, summaryPath));
2893
+ if (text === null) {
2894
+ throw new Error(`prepare-verify requires the execution summary ${summaryPath}.`);
2895
+ }
2896
+ return {
2897
+ scope: extractSectionLines(text, "Scope"),
2898
+ implementedSurface: extractSectionLines(text, "Implemented Surface"),
2899
+ deferredIssues: extractSectionLines(text, "Deferred Issues"),
2900
+ nextAgentGuidance: extractSectionLines(text, "Next-Agent Guidance")
2901
+ };
2902
+ }
2903
+ function readVerificationExpectations(root, guidePath) {
2904
+ const text = readTextIfPresent(path15.join(root, guidePath));
2905
+ if (text === null) {
2906
+ throw new Error(`prepare-verify requires the current guide ${guidePath}.`);
2907
+ }
2908
+ return extractSectionLines(text, "Verification Expectations");
2909
+ }
2910
+ function ensureExplicitLines(lines, label) {
2911
+ const values = stripBulletPrefix(lines).map((line) => line.trim()).filter(
2912
+ (line) => line.length > 0 && line !== "none" && line !== "none." && line !== "not started yet" && line !== "none yet"
2913
+ );
2914
+ if (values.length === 0) {
2915
+ throw new Error(`prepare-verify requires explicit ${label} before verify can start.`);
2916
+ }
2917
+ return values;
2918
+ }
2919
+ function renderBulletList3(items) {
2920
+ return items.map((item) => `- ${item}`).join("\n");
2921
+ }
2922
+ function buildHandoff(title, summaryContext, verificationExpectations, summaryPath, planPath, guidePath, runPath, gitContextSection) {
2923
+ const implementedSurface = ensureExplicitLines(
2924
+ summaryContext.implementedSurface,
2925
+ "implemented surface details"
2926
+ );
2927
+ const nextAgentGuidance = ensureExplicitLines(
2928
+ summaryContext.nextAgentGuidance,
2929
+ "next-agent guidance"
2930
+ );
2931
+ const checks = ensureExplicitLines(
2932
+ verificationExpectations,
2933
+ "verification expectations"
2934
+ );
2935
+ const scope = stripBulletPrefix(summaryContext.scope).filter((line) => line.trim().length > 0);
2936
+ const risks = stripBulletPrefix(summaryContext.deferredIssues).filter((line) => line.trim().length > 0 && line.trim().toLowerCase() !== "none.");
2937
+ return `# Handoff: ${title}
2938
+
2939
+ ## Goal
2940
+
2941
+ - Enable the next fresh agent to continue this work cycle without hidden context.
2942
+
2943
+ ## Read In This Order
2944
+
2945
+ 1. AGENTS.md
2946
+ 2. ${STATE_ROOT_NAME}/project.md
2947
+ 3. ${STATE_ROOT_NAME}/state.md
2948
+ 4. ${STATE_ROOT_NAME}/guides/read-this-first.md
2949
+ 5. ${STATE_ROOT_NAME}/guides/code-consistency.md
2950
+ 6. ${summaryPath}
2951
+ 7. ${planPath}
2952
+ 8. ${guidePath}
2953
+
2954
+ ## Current Status
2955
+
2956
+ ${renderBulletList3([
2957
+ ...scope,
2958
+ ...implementedSurface,
2959
+ "Implementation and refactor are ready for fresh verification."
2960
+ ])}
2961
+
2962
+ ${gitContextSection}
2963
+ ## Expected Verification Checks
2964
+
2965
+ ${renderBulletList3(checks)}
2966
+
2967
+ ## Open Risks
2968
+
2969
+ ${renderBulletList3(risks.length > 0 ? risks : ["None."])}
2970
+
2971
+ ## Next-Agent Guidance
2972
+
2973
+ ${renderBulletList3(nextAgentGuidance)}
2974
+ `;
2975
+ }
2976
+ function prepareVerify(start) {
2977
+ const root = resolveProjectRoot(start);
2978
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
2979
+ if (current.latestApprovedPlan === null) {
2980
+ throw new Error("prepare-verify requires a current approved plan.");
2981
+ }
2982
+ if (current.latestRelevantSummary === null) {
2983
+ throw new Error("prepare-verify requires a current execution summary.");
2984
+ }
2985
+ if (current.currentGuide === null) {
2986
+ throw new Error("prepare-verify requires a current task guide.");
2987
+ }
2988
+ if (current.currentHandoff === null) {
2989
+ throw new Error("prepare-verify requires a current handoff artifact.");
2990
+ }
2991
+ if (current.currentReview === null) {
2992
+ throw new Error("prepare-verify requires a current review artifact.");
2993
+ }
2994
+ const { dateStamp, slug } = parseCycleDescriptor(current.latestApprovedPlan);
2995
+ const title = readCycleTitle(root, current.latestApprovedPlan, slug);
2996
+ const summaryContext = readSummaryContext2(root, current.latestRelevantSummary);
2997
+ const verificationExpectations = readVerificationExpectations(root, current.currentGuide);
2998
+ const git = getGitContext(root);
2999
+ const runWrite = writeLifecycleRunRecord(root, {
3000
+ dateStamp,
3001
+ slug,
3002
+ title,
3003
+ phase: "prepare-verify",
3004
+ stage: "verify",
3005
+ verdict: "pending",
3006
+ git,
3007
+ paths: {
3008
+ planPath: current.latestApprovedPlan,
3009
+ executionSummaryPath: current.latestRelevantSummary,
3010
+ handoffPath: current.currentHandoff,
3011
+ reviewPath: current.currentReview,
3012
+ guidePath: current.currentGuide,
3013
+ verificationSummaryPath: null,
3014
+ journalPath: null
3015
+ }
3016
+ });
3017
+ const handoffWrite = writeFileIfChanged(
3018
+ path15.join(root, current.currentHandoff),
3019
+ buildHandoff(
3020
+ title,
3021
+ summaryContext,
3022
+ verificationExpectations,
3023
+ current.latestRelevantSummary,
3024
+ current.latestApprovedPlan,
3025
+ current.currentGuide,
3026
+ runWrite.path,
3027
+ renderGitContextSection(runWrite.path, git)
3028
+ )
3029
+ );
3030
+ const nextCurrent = normalizeCurrentIndex({
3031
+ ...current,
3032
+ currentStage: "verify",
3033
+ nextAction: `Run fresh verification for the ${title} increment.`,
3034
+ latestRun: runWrite.path,
3035
+ currentFocusModules: [
3036
+ "AGENTS.md",
3037
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
3038
+ current.latestRelevantSummary,
3039
+ current.latestApprovedPlan,
3040
+ current.currentGuide,
3041
+ current.currentHandoff,
3042
+ current.currentReview,
3043
+ runWrite.path
3044
+ ],
3045
+ hotPath: [
3046
+ `${STATE_ROOT_NAME}/project.md`,
3047
+ `${STATE_ROOT_NAME}/state.md`,
3048
+ `${STATE_ROOT_NAME}/guides/read-this-first.md`,
3049
+ `${STATE_ROOT_NAME}/guides/code-consistency.md`,
3050
+ current.latestRelevantSummary,
3051
+ current.latestApprovedPlan,
3052
+ current.currentGuide
3053
+ ],
3054
+ activeAgents: {
3055
+ ...current.activeAgents,
3056
+ verification: "pending assignment"
3057
+ },
3058
+ notes: [
3059
+ `Current work cycle: ${title}.`,
3060
+ "Execution and refactor are complete.",
3061
+ "Handoff and current-state artifacts are aligned for fresh verification.",
3062
+ `Previous relevant summary: ${current.latestRelevantSummary}`
3063
+ ]
3064
+ });
3065
+ const updatedFiles = [];
3066
+ if (runWrite.writeResult === "created" || runWrite.writeResult === "updated") {
3067
+ updatedFiles.push(runWrite.path);
3068
+ }
3069
+ if (handoffWrite === "created" || handoffWrite === "updated") {
3070
+ updatedFiles.push(current.currentHandoff);
3071
+ }
3072
+ const currentWrites = writeCurrentArtifacts(
3073
+ root,
3074
+ nextCurrent,
3075
+ buildTemplateContext(root).implementationMenuPath
3076
+ );
3077
+ if (currentWrites.index !== "unchanged") {
3078
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
3079
+ }
3080
+ if (currentWrites.state !== "unchanged") {
3081
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
3082
+ }
3083
+ if (currentWrites.readThisFirst !== "unchanged") {
3084
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
3085
+ }
3086
+ return {
3087
+ root,
3088
+ title,
3089
+ runPath: runWrite.path,
3090
+ handoffPath: current.currentHandoff,
3091
+ updatedFiles
3092
+ };
3093
+ }
3094
+
3095
+ // src/commands/prepare-verify.ts
3096
+ function runPrepareVerify(args) {
3097
+ if (args.length > 0) {
3098
+ console.error("Usage: sb-codex-tool prepare-verify");
3099
+ return 1;
3100
+ }
3101
+ const result = prepareVerify(process.cwd());
3102
+ console.log(`Project root: ${result.root}`);
3103
+ console.log(`Work cycle: ${result.title}`);
3104
+ console.log(`Updated run: ${result.runPath}`);
3105
+ console.log(`Updated handoff: ${result.handoffPath}`);
3106
+ console.log("");
3107
+ console.log(`Updated files: ${result.updatedFiles.length}`);
3108
+ for (const file of result.updatedFiles) {
3109
+ console.log(`- ${file}`);
3110
+ }
3111
+ return 0;
3112
+ }
3113
+
3114
+ // src/lib/consistency-review.ts
3115
+ import path16 from "node:path";
3116
+ function readAllowedFileScope3(root, guidePath) {
3117
+ if (guidePath === null) {
3118
+ return [];
3119
+ }
3120
+ const text = readTextIfPresent(path16.join(root, guidePath));
3121
+ if (text === null) {
3122
+ return [];
3123
+ }
3124
+ return extractSectionLines(text, "Allowed File Scope");
3125
+ }
3126
+ function buildConsistencyReview(agentName, title, current, allowedFileScope) {
3127
+ const fileScope = allowedFileScope.length > 0 ? allowedFileScope.join("\n") : "- Replace with the bounded file scope for this review.";
3128
+ return `# Code Consistency Review: ${title}
3129
+
3130
+ ## Assigned Consistency Agent
3131
+
3132
+ - ${agentName}
3133
+
3134
+ ## Review Target
3135
+
3136
+ - Current plan: ${current.latestApprovedPlan ?? "none yet"}
3137
+ - Latest summary: ${current.latestRelevantSummary ?? "none yet"}
3138
+ - Current guide: ${current.currentGuide ?? "none yet"}
3139
+ - Latest lifecycle run: ${current.latestRun ?? "none yet"}
3140
+
3141
+ ## Review Scope
3142
+
3143
+ ${fileScope}
3144
+
3145
+ ## Required References
3146
+
3147
+ - AGENTS.md
3148
+ - ${STATE_ROOT_NAME}/guides/code-consistency.md
3149
+ - ${current.latestApprovedPlan ?? `${STATE_ROOT_NAME}/plans/README.md`}
3150
+ - ${current.latestRelevantSummary ?? `${STATE_ROOT_NAME}/summaries/README.md`}
3151
+ - ${current.currentGuide ?? `${STATE_ROOT_NAME}/guides/read-this-first.md`}
3152
+
3153
+ ## Review Questions
3154
+
3155
+ - Do naming and module boundaries still match the consistency guide?
3156
+ - Did the latest increment preserve reuse, readability, and low complexity?
3157
+ - Are there new anti-patterns or consistency drifts that should be recorded?
3158
+ - Is the code still easy for a fresh agent to read and modify?
3159
+
3160
+ ## Findings
3161
+
3162
+ - Add findings here in severity order.
3163
+
3164
+ ## Recommendations
3165
+
3166
+ - Add follow-up recommendations here.
3167
+
3168
+ ## Guide Update Decision
3169
+
3170
+ - Record whether ${STATE_ROOT_NAME}/guides/code-consistency.md needs an update.
3171
+ `;
3172
+ }
3173
+ function createConsistencyReview(start, agentName, requestedTitle) {
3174
+ const root = resolveProjectRoot(start);
3175
+ const current = normalizeCurrentIndex(readCurrentIndex(root));
3176
+ if (current.latestApprovedPlan === null) {
3177
+ throw new Error("review-consistency requires a current approved plan.");
3178
+ }
3179
+ if (current.latestRelevantSummary === null) {
3180
+ throw new Error("review-consistency requires a current summary.");
3181
+ }
3182
+ if (current.currentGuide === null) {
3183
+ throw new Error("review-consistency requires a current task guide.");
3184
+ }
3185
+ const agentSlug = normalizeSlug(agentName);
3186
+ if (agentSlug.length === 0) {
3187
+ throw new Error("Agent name must contain at least one alphanumeric character.");
3188
+ }
3189
+ const { dateStamp, slug } = parseCycleDescriptor(current.latestApprovedPlan);
3190
+ const cycleTitle = readCycleTitle(root, current.latestApprovedPlan, slug);
3191
+ const title = requestedTitle?.trim().length ? requestedTitle.trim() : `${cycleTitle} Consistency Review`;
3192
+ const reviewPath = `${STATE_ROOT_NAME}/reviews/${dateStamp}-${slug}-${agentSlug}-consistency-review.md`;
3193
+ const allowedFileScope = readAllowedFileScope3(root, current.currentGuide);
3194
+ const writeResult = writeFileIfMissing(
3195
+ path16.join(root, reviewPath),
3196
+ buildConsistencyReview(agentName, title, current, allowedFileScope)
3197
+ );
3198
+ const createdFiles = [];
3199
+ const keptFiles = [];
3200
+ if (writeResult === "created") {
3201
+ createdFiles.push(reviewPath);
3202
+ } else {
3203
+ keptFiles.push(reviewPath);
3204
+ }
3205
+ const nextFocusModules = current.currentFocusModules.includes(reviewPath) ? current.currentFocusModules : [...current.currentFocusModules, reviewPath];
3206
+ const nextCurrent = normalizeCurrentIndex({
3207
+ ...current,
3208
+ latestConsistencyReview: reviewPath,
3209
+ currentFocusModules: nextFocusModules,
3210
+ activeAgents: {
3211
+ ...current.activeAgents,
3212
+ consistency: agentName
3213
+ },
3214
+ notes: [
3215
+ `Current work cycle: ${humanizeSlug(slug)}.`,
3216
+ `Prepared consistency review for ${agentName}: ${reviewPath}.`,
3217
+ `Previous relevant summary: ${current.latestRelevantSummary}`
3218
+ ]
3219
+ });
3220
+ const updatedFiles = [];
3221
+ const currentWrites = writeCurrentArtifacts(
3222
+ root,
3223
+ nextCurrent,
3224
+ buildTemplateContext(root).implementationMenuPath
3225
+ );
3226
+ if (currentWrites.index !== "unchanged") {
3227
+ updatedFiles.push(`${STATE_ROOT_NAME}/index/current.json`);
3228
+ }
3229
+ if (currentWrites.state !== "unchanged") {
3230
+ updatedFiles.push(`${STATE_ROOT_NAME}/state.md`);
3231
+ }
3232
+ if (currentWrites.readThisFirst !== "unchanged") {
3233
+ updatedFiles.push(`${STATE_ROOT_NAME}/guides/read-this-first.md`);
3234
+ }
3235
+ return {
3236
+ root,
3237
+ agentName,
3238
+ title,
3239
+ reviewPath,
3240
+ createdFiles,
3241
+ keptFiles,
3242
+ updatedFiles
3243
+ };
3244
+ }
3245
+
3246
+ // src/commands/review-consistency.ts
3247
+ function runReviewConsistency(args) {
3248
+ const [agentName, ...titleParts] = args;
3249
+ if (agentName === void 0) {
3250
+ console.error("Usage: sb-codex-tool review-consistency <agent-name> [title words]");
3251
+ return 1;
3252
+ }
3253
+ const title = titleParts.length > 0 ? titleParts.join(" ") : void 0;
3254
+ const result = createConsistencyReview(process.cwd(), agentName, title);
3255
+ console.log(`Project root: ${result.root}`);
3256
+ console.log(`Consistency agent: ${result.agentName}`);
3257
+ console.log(`Review: ${result.title}`);
3258
+ console.log("");
3259
+ console.log("Artifacts:");
3260
+ console.log(`- consistency review: ${result.reviewPath}`);
3261
+ console.log("");
3262
+ console.log(`Created files: ${result.createdFiles.length}`);
3263
+ for (const file of result.createdFiles) {
3264
+ console.log(`- ${file}`);
3265
+ }
3266
+ if (result.updatedFiles.length > 0) {
3267
+ console.log("");
3268
+ console.log("Updated state:");
3269
+ for (const file of result.updatedFiles) {
3270
+ console.log(`- ${file}`);
3271
+ }
3272
+ }
3273
+ return 0;
3274
+ }
3275
+
3276
+ // src/lib/scaffold.ts
3277
+ import { existsSync as existsSync8 } from "node:fs";
3278
+ import path17 from "node:path";
3279
+ function ensureDirectories(root) {
3280
+ const created = [];
3281
+ for (const relativeDir of REQUIRED_DIRECTORIES) {
3282
+ const absoluteDir = path17.join(root, relativeDir);
3283
+ const existed = existsSync8(absoluteDir);
3284
+ ensureDir(absoluteDir);
3285
+ if (!existed) {
3286
+ created.push(relativeDir);
3287
+ }
3288
+ }
3289
+ return created;
3290
+ }
3291
+ function scaffoldProject(start = process.cwd()) {
3292
+ const root = resolveProjectRoot(start);
3293
+ const context = buildTemplateContext(root);
3294
+ const createdDirectories = ensureDirectories(root);
3295
+ const createdFiles = [];
3296
+ const keptFiles = [];
3297
+ const updatedFiles = [];
3298
+ for (const file of buildGeneratedFiles(context)) {
3299
+ const result = writeFileIfMissing(path17.join(root, file.path), file.content);
3300
+ if (result === "created") {
3301
+ createdFiles.push(file.path);
3302
+ } else {
3303
+ keptFiles.push(file.path);
3304
+ }
3305
+ }
3306
+ const gitIgnore = ensureLines(
3307
+ path17.join(root, ".gitignore"),
3308
+ getGitIgnoreLines(),
3309
+ "# Added by sb-codex-tool"
3310
+ );
3311
+ if (gitIgnore.created || gitIgnore.added.length > 0) {
3312
+ updatedFiles.push(".gitignore");
3313
+ }
3314
+ const ignore = ensureLines(
3315
+ path17.join(root, ".ignore"),
3316
+ getSearchIgnoreLines(),
3317
+ "# Added by sb-codex-tool"
3318
+ );
3319
+ if (ignore.created || ignore.added.length > 0) {
3320
+ updatedFiles.push(".ignore");
3321
+ }
3322
+ const rgIgnore = ensureLines(
3323
+ path17.join(root, ".rgignore"),
3324
+ getSearchIgnoreLines(),
3325
+ "# Added by sb-codex-tool"
3326
+ );
3327
+ if (rgIgnore.created || rgIgnore.added.length > 0) {
3328
+ updatedFiles.push(".rgignore");
3329
+ }
3330
+ const workflowReadme = writeFileIfMissing(
3331
+ statePath(root, "workflows", "README.md"),
3332
+ "# Workflows\n\nThis directory stores the canonical workflow stage assets.\n"
3333
+ );
3334
+ if (workflowReadme === "created") {
3335
+ createdFiles.push(`${statePath(".", "workflows", "README.md")}`);
3336
+ }
3337
+ return {
3338
+ root,
3339
+ createdFiles,
3340
+ keptFiles,
3341
+ createdDirectories,
3342
+ updatedFiles
3343
+ };
3344
+ }
3345
+
3346
+ // src/commands/setup.ts
3347
+ function runSetup() {
3348
+ const summary = scaffoldProject();
3349
+ const codexBinary = resolveCodexBinary();
3350
+ console.log(`Project root: ${summary.root}`);
3351
+ console.log("");
3352
+ console.log(`Created directories: ${summary.createdDirectories.length}`);
3353
+ for (const directory of summary.createdDirectories) {
3354
+ console.log(`- ${directory}`);
3355
+ }
3356
+ console.log("");
3357
+ console.log(`Created files: ${summary.createdFiles.length}`);
3358
+ for (const file of summary.createdFiles) {
3359
+ console.log(`- ${file}`);
3360
+ }
3361
+ if (summary.updatedFiles.length > 0) {
3362
+ console.log("");
3363
+ console.log("Updated files:");
3364
+ for (const file of summary.updatedFiles) {
3365
+ console.log(`- ${file}`);
3366
+ }
3367
+ }
3368
+ console.log("");
3369
+ if (codexBinary === null) {
3370
+ console.log("Codex binary: missing");
3371
+ console.log("Remediation: install Codex or expose `codex` in PATH before using the launch wrapper.");
3372
+ } else {
3373
+ console.log(`Codex binary: ${codexBinary}`);
3374
+ }
3375
+ return 0;
3376
+ }
3377
+
3378
+ // src/lib/status.ts
3379
+ function getStatus(start = process.cwd()) {
3380
+ const root = resolveProjectRoot(start);
3381
+ const current = readCurrentIndex(root);
3382
+ const git = getGitContext(root);
3383
+ const latestRun = current?.latestRun ?? null;
3384
+ const latestRunRecord = readLifecycleRunRecord(root, latestRun);
3385
+ const coherenceIssues = collectStateCoherenceIssues(root, current, latestRunRecord);
3386
+ return {
3387
+ root,
3388
+ stage: current?.currentStage ?? "unknown",
3389
+ nextAction: current?.nextAction ?? "No next action recorded.",
3390
+ latestPlan: current?.latestApprovedPlan ?? null,
3391
+ latestSummary: current?.latestRelevantSummary ?? null,
3392
+ latestRun,
3393
+ latestRunRecord,
3394
+ latestConsistencyReview: current?.latestConsistencyReview ?? null,
3395
+ latestAssignmentLifecycle: current?.latestAssignmentLifecycle ?? null,
3396
+ currentGuide: current?.currentGuide ?? null,
3397
+ currentReview: current?.currentReview ?? null,
3398
+ currentHandoff: current?.currentHandoff ?? null,
3399
+ hotPath: current?.hotPath ?? [
3400
+ ".sb-codex-tool/project.md",
3401
+ ".sb-codex-tool/state.md",
3402
+ ".sb-codex-tool/guides/read-this-first.md",
3403
+ ".sb-codex-tool/guides/code-consistency.md"
3404
+ ],
3405
+ agents: current?.activeAgents,
3406
+ assignmentGuides: current?.assignmentGuides ?? {},
3407
+ coherenceIssues,
3408
+ git
3409
+ };
3410
+ }
3411
+
3412
+ // src/commands/status.ts
3413
+ function runStatus() {
3414
+ const status = getStatus();
3415
+ console.log(`Project root: ${status.root}`);
3416
+ console.log(`Current stage: ${status.stage}`);
3417
+ console.log(`Next action: ${status.nextAction}`);
3418
+ console.log(`Latest approved plan: ${status.latestPlan ?? "none"}`);
3419
+ console.log(`Latest summary: ${status.latestSummary ?? "none"}`);
3420
+ console.log(`Latest run: ${status.latestRun ?? "none"}`);
3421
+ console.log(`Latest consistency review: ${status.latestConsistencyReview ?? "none"}`);
3422
+ console.log(`Latest assignment lifecycle: ${status.latestAssignmentLifecycle ?? "none"}`);
3423
+ console.log(`Current guide: ${status.currentGuide ?? "none"}`);
3424
+ console.log(`Current handoff: ${status.currentHandoff ?? "none"}`);
3425
+ console.log(`Current review: ${status.currentReview ?? "none"}`);
3426
+ if (status.latestRunRecord !== null) {
3427
+ console.log("");
3428
+ console.log("Latest run details:");
3429
+ console.log(`- Title: ${status.latestRunRecord.title}`);
3430
+ console.log(`- Phase: ${status.latestRunRecord.phase}`);
3431
+ console.log(`- Stage: ${status.latestRunRecord.stage}`);
3432
+ console.log(`- Verdict: ${status.latestRunRecord.verdict}`);
3433
+ console.log("Run-linked artifacts:");
3434
+ console.log(`- Plan: ${status.latestRunRecord.paths.planPath}`);
3435
+ console.log(`- Execution summary: ${status.latestRunRecord.paths.executionSummaryPath}`);
3436
+ console.log(`- Guide: ${status.latestRunRecord.paths.guidePath ?? "none"}`);
3437
+ console.log(`- Handoff: ${status.latestRunRecord.paths.handoffPath ?? "none"}`);
3438
+ console.log(`- Review: ${status.latestRunRecord.paths.reviewPath ?? "none"}`);
3439
+ console.log(
3440
+ `- Verification summary: ${status.latestRunRecord.paths.verificationSummaryPath ?? "none"}`
3441
+ );
3442
+ console.log(`- Work journal: ${status.latestRunRecord.paths.journalPath ?? "none"}`);
3443
+ console.log("Recorded run git context:");
3444
+ if (status.latestRunRecord.git.available) {
3445
+ console.log(`- Branch: ${status.latestRunRecord.git.branch ?? "detached"}`);
3446
+ console.log(`- Dirty: ${status.latestRunRecord.git.dirty ? "yes" : "no"}`);
3447
+ console.log("- Changed files:");
3448
+ if (status.latestRunRecord.git.changedFiles.length === 0) {
3449
+ console.log(" - none");
3450
+ } else {
3451
+ for (const file of status.latestRunRecord.git.changedFiles) {
3452
+ console.log(` - ${file}`);
3453
+ }
3454
+ }
3455
+ } else {
3456
+ console.log("- Branch: unavailable");
3457
+ console.log("- Dirty: unavailable");
3458
+ console.log("- Changed files: unavailable");
3459
+ }
3460
+ }
3461
+ console.log("");
3462
+ console.log("Hot path:");
3463
+ for (const file of status.hotPath) {
3464
+ console.log(`- ${file}`);
3465
+ }
3466
+ console.log("");
3467
+ console.log("Active agents:");
3468
+ console.log(`- Main: ${status.agents?.main ?? "unassigned"}`);
3469
+ console.log(
3470
+ `- Subagents: ${status.agents?.subagents && status.agents.subagents.length > 0 ? status.agents.subagents.join(", ") : "none"}`
3471
+ );
3472
+ console.log(`- Verification: ${status.agents?.verification ?? "none"}`);
3473
+ console.log(`- Consistency: ${status.agents?.consistency ?? "none"}`);
3474
+ console.log("");
3475
+ console.log("Assignment guides:");
3476
+ const assignmentEntries = Object.entries(status.assignmentGuides);
3477
+ if (assignmentEntries.length === 0) {
3478
+ console.log("- none");
3479
+ } else {
3480
+ for (const [agent, guidePath] of assignmentEntries.sort(([left], [right]) => left.localeCompare(right))) {
3481
+ console.log(`- ${agent}: ${guidePath}`);
3482
+ }
3483
+ }
3484
+ console.log("");
3485
+ console.log("Semantic issues:");
3486
+ if (status.coherenceIssues.length === 0) {
3487
+ console.log("- none");
3488
+ } else {
3489
+ for (const issue of status.coherenceIssues) {
3490
+ console.log(`- [${issue.level}] ${issue.label}: ${issue.detail}`);
3491
+ }
3492
+ }
3493
+ console.log("");
3494
+ if (status.git.available) {
3495
+ console.log(`Git branch: ${status.git.branch ?? "detached"}`);
3496
+ console.log(`Git dirty: ${status.git.dirty ? "yes" : "no"}`);
3497
+ console.log("Changed files:");
3498
+ if (status.git.changedFiles.length === 0) {
3499
+ console.log("- none");
3500
+ } else {
3501
+ for (const file of status.git.changedFiles) {
3502
+ console.log(`- ${file}`);
3503
+ }
3504
+ }
3505
+ } else {
3506
+ console.log("Git branch: unavailable");
3507
+ console.log("Git dirty: unavailable");
3508
+ }
3509
+ return 0;
3510
+ }
3511
+
3512
+ // src/cli.ts
3513
+ function printHelp() {
3514
+ console.log("sb-codex-tool");
3515
+ console.log("");
3516
+ console.log("Usage:");
3517
+ console.log(" sb-codex-tool assign <agent-name> <slug> [title words]");
3518
+ console.log(" sb-codex-tool begin <slug> [title words]");
3519
+ console.log(" sb-codex-tool close");
3520
+ console.log(" sb-codex-tool complete-assignment <agent-name> <close|clear|replace> [replacement-agent] [replacement-slug] [title words]");
3521
+ console.log(" sb-codex-tool prepare-verify");
3522
+ console.log(" sb-codex-tool review-consistency <agent-name> [title words]");
3523
+ console.log(" sb-codex-tool setup");
3524
+ console.log(" sb-codex-tool doctor");
3525
+ console.log(" sb-codex-tool status");
3526
+ console.log(" sb-codex-tool [codex args]");
3527
+ console.log("");
3528
+ console.log("Commands:");
3529
+ console.log(" assign Create a bounded assignment guide and update active subagents.");
3530
+ console.log(" begin Create the next work-cycle artifacts and update current state.");
3531
+ console.log(" close Finalize the current cycle from the recorded review verdict.");
3532
+ console.log(" complete-assignment Record close/clear/replace lifecycle handling for a bounded subagent task.");
3533
+ console.log(" prepare-verify Align handoff, run state, and current state for fresh verification.");
3534
+ console.log(" review-consistency Create a consistency review artifact and update visible review state.");
3535
+ console.log(" setup Create the sb-codex-tool scaffold in the current project.");
3536
+ console.log(" doctor Validate the scaffold and required operational files.");
3537
+ console.log(" status Show current stage, next action, hot path, and git context.");
3538
+ console.log(" help Show this message.");
3539
+ console.log("");
3540
+ console.log("When no command is provided, sb-codex-tool launches Codex through the wrapper.");
3541
+ }
3542
+ function main(argv) {
3543
+ const [command, ...rest] = argv;
3544
+ if (command === void 0) {
3545
+ return runLaunch([]);
3546
+ }
3547
+ if (command === "setup") {
3548
+ return runSetup();
3549
+ }
3550
+ if (command === "begin") {
3551
+ return runBegin(rest);
3552
+ }
3553
+ if (command === "assign") {
3554
+ return runAssign(rest);
3555
+ }
3556
+ if (command === "close") {
3557
+ return runClose(rest);
3558
+ }
3559
+ if (command === "complete-assignment") {
3560
+ return runCompleteAssignment(rest);
3561
+ }
3562
+ if (command === "review-consistency") {
3563
+ return runReviewConsistency(rest);
3564
+ }
3565
+ if (command === "prepare-verify") {
3566
+ return runPrepareVerify(rest);
3567
+ }
3568
+ if (command === "doctor") {
3569
+ return runDoctorCommand();
3570
+ }
3571
+ if (command === "status") {
3572
+ return runStatus();
3573
+ }
3574
+ if (command === "help" || command === "--help" || command === "-h") {
3575
+ printHelp();
3576
+ return 0;
3577
+ }
3578
+ if (command.startsWith("-")) {
3579
+ return runLaunch(argv);
3580
+ }
3581
+ return runLaunch(argv);
3582
+ }
3583
+ process.exit(main(process.argv.slice(2)));