cclaw-cli 0.51.30 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +24 -18
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +289 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +354 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +183 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +99 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +125 -0
  14. package/dist/artifact-linter/shared.d.ts +247 -0
  15. package/dist/artifact-linter/shared.js +1517 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +82 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +130 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +198 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +1 -6
  25. package/dist/cli.js +4 -159
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +10 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.d.ts +1 -1
  34. package/dist/content/closeout-guidance.js +15 -13
  35. package/dist/content/core-agents.d.ts +46 -29
  36. package/dist/content/core-agents.js +216 -82
  37. package/dist/content/decision-protocol.d.ts +1 -1
  38. package/dist/content/decision-protocol.js +1 -1
  39. package/dist/content/diff-command.js +1 -1
  40. package/dist/content/examples.d.ts +0 -3
  41. package/dist/content/examples.js +197 -752
  42. package/dist/content/harness-doc.js +20 -2
  43. package/dist/content/hook-manifest.d.ts +2 -2
  44. package/dist/content/hook-manifest.js +2 -2
  45. package/dist/content/hooks.d.ts +1 -0
  46. package/dist/content/hooks.js +32 -137
  47. package/dist/content/idea.d.ts +60 -0
  48. package/dist/content/idea.js +404 -0
  49. package/dist/content/iron-laws.d.ts +0 -1
  50. package/dist/content/iron-laws.js +31 -16
  51. package/dist/content/learnings.d.ts +2 -4
  52. package/dist/content/learnings.js +11 -27
  53. package/dist/content/meta-skill.js +7 -7
  54. package/dist/content/node-hooks.d.ts +10 -0
  55. package/dist/content/node-hooks.js +163 -95
  56. package/dist/content/opencode-plugin.js +15 -29
  57. package/dist/content/reference-patterns.js +2 -2
  58. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  59. package/dist/content/runtime-shared-snippets.js +80 -0
  60. package/dist/content/session-hooks.js +1 -1
  61. package/dist/content/skills.d.ts +1 -0
  62. package/dist/content/skills.js +69 -7
  63. package/dist/content/stage-schema.js +147 -61
  64. package/dist/content/stages/_lint-metadata/index.js +26 -2
  65. package/dist/content/stages/brainstorm.js +13 -7
  66. package/dist/content/stages/design.js +16 -11
  67. package/dist/content/stages/plan.js +7 -4
  68. package/dist/content/stages/review.js +12 -12
  69. package/dist/content/stages/schema-types.d.ts +2 -2
  70. package/dist/content/stages/scope.js +15 -12
  71. package/dist/content/stages/ship.js +3 -3
  72. package/dist/content/stages/spec.js +9 -3
  73. package/dist/content/stages/tdd.js +14 -4
  74. package/dist/content/start-command.js +11 -10
  75. package/dist/content/status-command.js +5 -5
  76. package/dist/content/subagent-context-skills.js +156 -1
  77. package/dist/content/subagents.d.ts +0 -5
  78. package/dist/content/subagents.js +65 -81
  79. package/dist/content/templates.d.ts +1 -1
  80. package/dist/content/templates.js +187 -154
  81. package/dist/content/tree-command.js +2 -2
  82. package/dist/content/utility-skills.d.ts +2 -2
  83. package/dist/content/utility-skills.js +28 -99
  84. package/dist/content/view-command.js +4 -2
  85. package/dist/delegation.d.ts +2 -0
  86. package/dist/delegation.js +2 -1
  87. package/dist/early-loop.d.ts +66 -0
  88. package/dist/early-loop.js +275 -0
  89. package/dist/flow-state.d.ts +5 -6
  90. package/dist/flow-state.js +4 -6
  91. package/dist/gate-evidence.d.ts +0 -23
  92. package/dist/gate-evidence.js +111 -153
  93. package/dist/harness-adapters.d.ts +2 -2
  94. package/dist/harness-adapters.js +48 -19
  95. package/dist/install.js +190 -32
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +479 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +161 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +5 -28
  125. package/dist/knowledge-store.js +57 -84
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +7 -9
  128. package/dist/retro-gate.js +8 -90
  129. package/dist/run-archive.d.ts +1 -1
  130. package/dist/run-archive.js +13 -16
  131. package/dist/run-persistence.js +20 -15
  132. package/dist/runtime/run-hook.entry.d.ts +3 -0
  133. package/dist/runtime/run-hook.entry.js +5 -0
  134. package/dist/runtime/run-hook.mjs +9477 -0
  135. package/dist/tdd-cycle.d.ts +3 -3
  136. package/dist/tdd-cycle.js +1 -1
  137. package/dist/types.d.ts +18 -10
  138. package/package.json +4 -2
  139. package/dist/content/hook-inline-snippets.d.ts +0 -83
  140. package/dist/content/hook-inline-snippets.js +0 -302
  141. package/dist/content/ideate-command.d.ts +0 -8
  142. package/dist/content/ideate-command.js +0 -315
  143. package/dist/content/ideate-frames.d.ts +0 -31
  144. package/dist/content/ideate-frames.js +0 -140
  145. package/dist/content/ideate-ranking.d.ts +0 -25
  146. package/dist/content/ideate-ranking.js +0 -65
  147. package/dist/content/next-command.d.ts +0 -20
  148. package/dist/content/next-command.js +0 -298
  149. package/dist/content/seed-shelf.d.ts +0 -36
  150. package/dist/content/seed-shelf.js +0 -301
  151. package/dist/content/stage-common-guidance.d.ts +0 -1
  152. package/dist/content/stage-common-guidance.js +0 -106
  153. package/dist/doctor-registry.d.ts +0 -10
  154. package/dist/doctor-registry.js +0 -186
  155. package/dist/doctor.d.ts +0 -17
  156. package/dist/doctor.js +0 -2201
  157. package/dist/internal/hook-manifest.d.ts +0 -16
  158. package/dist/internal/hook-manifest.js +0 -77
  159. package/dist/trace-matrix.d.ts +0 -27
  160. package/dist/trace-matrix.js +0 -226
@@ -5,128 +5,57 @@
5
5
  export function languageTypescriptSkill() {
6
6
  return `---
7
7
  name: language-typescript
8
- description: "TypeScript rule pack. Opt-in language lens. Use when reviewing or writing TypeScript/JavaScript diffs during tdd or review enforces type-safety, runtime-boundary validation, and idiomatic patterns."
8
+ description: "TypeScript rule pack. Compact opt-in lens for tdd/review when diffs touch TS/JS files."
9
9
  ---
10
10
 
11
11
  # TypeScript Rule Pack
12
12
 
13
- ## Quick Start
14
-
15
- > 1. Activate during tdd or review whenever the diff touches \`.ts\`, \`.tsx\`, \`.mts\`, \`.cts\`, or \`.js\` files.
16
- > 2. Walk the rule tiers in order. Tier-1 violations block merge. Tier-2 need a named follow-up.
17
- > 3. Cite each finding as \`file:line — <rule id> — <one-line remediation>\`.
18
-
19
- ## HARD-GATE
20
-
21
- Do not approve a TypeScript change that ships \`any\`, \`@ts-ignore\`, or
22
- \`@ts-expect-error\` *without* (a) a comment explaining why, (b) a linked issue,
23
- and (c) an assertion that the blast radius is bounded to the current file.
24
- No exceptions in production code paths.
25
-
26
- ## Tier 1 — blocking rules
27
-
28
- 1. **No silent \`any\`.** Unknown inputs must be typed as \`unknown\` first, then narrowed.
29
- 2. **Runtime validate trust boundaries.** HTTP bodies, env vars, file contents, and
30
- IPC payloads must be parsed through a schema validator (zod, valibot, io-ts) before
31
- being treated as typed data.
32
- 3. **No \`as\` without a narrowing reason.** \`value as Foo\` is only acceptable when
33
- preceded by a runtime check that proves the shape (e.g. \`if ("id" in value)\`).
34
- 4. **Exhaustive switches on discriminated unions.** Every \`switch\` on a tagged
35
- union must end with a \`default\` branch that assigns to \`never\` to surface
36
- missing cases at compile time.
37
- 5. **Promise hygiene.** No unawaited promises in \`async\` functions; no
38
- \`void promise\` unless documented. Use \`@typescript-eslint/no-floating-promises\`.
39
- 6. **Null-safety at the boundary.** Optional chaining (\`?.\`) and nullish
40
- coalescing (\`??\`) must only be used when the null path is handled, not as a
41
- silent default.
13
+ Use this only when a diff includes \`.ts\`, \`.tsx\`, \`.mts\`, \`.cts\`, or \`.js\`.
42
14
 
43
- ## Tier 2 — follow-up rules
15
+ ## Blocking rules
44
16
 
45
- 7. Prefer \`readonly\` for arrays/object fields that are not mutated.
46
- 8. Prefer \`type\` aliases for unions, \`interface\` for extendable object shapes.
47
- 9. Name generic parameters descriptively once they carry semantic meaning (\`TEvent\`, \`TPayload\`).
48
- 10. Avoid re-exporting entire namespaces; named re-exports keep bundle analysis tractable.
49
- 11. Co-locate test fixtures with their types to keep drift visible.
17
+ 1. **No silent \`any\` or blanket \`@ts-ignore\`.** Unknown input starts as \`unknown\` and gets narrowed.
18
+ 2. **Validate trust boundaries at runtime.** HTTP/env/file/IPC payloads require schema parse before typed use.
19
+ 3. **No floating promises.** Await promises or explicitly document fire-and-forget behavior.
20
+ 4. **Exhaustive union handling.** Discriminated-union switches must fail loudly on missing branches.
50
21
 
51
- ## Anti-patterns
22
+ ## Follow-up rules
52
23
 
53
- - "It compiles, ship it" — compilation is necessary, not sufficient. Runtime boundary validation is the gate.
54
- - Casting library return types to tighten them without reading the library's actual contract.
55
- - Wrapping every function in \`try/catch\` and swallowing the error errors must either be rethrown typed or mapped to a Result/Either shape.
56
- - Using enums where a string-literal union would do (enums carry runtime cost and erase at tree-shaking time only when \`const\`).
24
+ - Prefer immutable/readonly data by default.
25
+ - Keep types local and explicit at module boundaries.
26
+ - Add/adjust tests when changing inferred public behavior.
57
27
 
58
- ## Review output shape
28
+ ## Output format
59
29
 
60
- \`\`\`
61
- - **Rule:** T1-2 (runtime validate trust boundaries)
62
- - **File:line:** src/api/users.ts:42
63
- - **Finding:** POST body cast directly to \`UserCreateInput\`; no schema parse.
64
- - **Remediation:** Parse through \`userCreateSchema\` (zod) before passing to the service layer.
65
- \`\`\`
30
+ \`file:line — rule id — concise remediation\`
66
31
  `;
67
32
  }
68
33
  export function languagePythonSkill() {
69
34
  return `---
70
35
  name: language-python
71
- description: "Python rule pack. Opt-in language lens. Use when reviewing or writing Python diffs during tdd or review enforces typing, exception hygiene, and idiomatic patterns."
36
+ description: "Python rule pack. Compact opt-in lens for tdd/review when diffs touch Python files."
72
37
  ---
73
38
 
74
39
  # Python Rule Pack
75
40
 
76
- ## Quick Start
77
-
78
- > 1. Activate during tdd or review whenever the diff touches \`.py\` / \`.pyi\` files.
79
- > 2. Walk the rule tiers in order. Tier-1 violations block merge. Tier-2 need a named follow-up.
80
- > 3. Cite each finding as \`file:line — <rule id> — <one-line remediation>\`.
81
-
82
- ## HARD-GATE
83
-
84
- Do not approve a Python change that catches bare \`except:\` or \`except Exception:\`
85
- in production code *without* (a) re-raising, (b) logging with \`logger.exception\`, or
86
- (c) a comment explaining the intentional swallow. Silent broad catches are the
87
- single biggest source of "works on my machine" bugs in Python services.
88
-
89
- ## Tier 1 — blocking rules
90
-
91
- 1. **Type hints on public APIs.** Every exported function, method, and dataclass
92
- must have full type hints. Use \`from __future__ import annotations\` or PEP 604 union syntax.
93
- 2. **No mutable default arguments.** \`def f(x=[])\` is a bug. Use \`None\` + inline default.
94
- 3. **Exception specificity.** Catch the narrowest exception class you actually handle.
95
- 4. **Context managers for resources.** Files, sockets, DB sessions, locks — always \`with\`.
96
- 5. **No bare \`assert\` in production code.** \`assert\` is stripped under \`python -O\`.
97
- For invariants, raise \`ValueError\`/\`RuntimeError\` explicitly.
98
- 6. **Deterministic imports.** No conditional imports at module top level except for
99
- platform branches; no import-time side effects.
100
-
101
- ## Tier 2 — follow-up rules
102
-
103
- 7. Prefer \`@dataclass(slots=True, frozen=True)\` for value objects.
104
- 8. Prefer \`pathlib.Path\` over \`os.path\` for new code.
105
- 9. Use f-strings for interpolation; reserve \`%\` and \`.format\` for logger messages (lazy eval).
106
- 10. Use \`logging.getLogger(__name__)\` per module; never the root logger.
107
- 11. Pin dependency ranges in \`pyproject.toml\`; lock with \`uv lock\` / \`pip-compile\`.
41
+ Use this only when a diff includes \`.py\` / \`.pyi\`.
108
42
 
109
- ## Async-specific
43
+ ## Blocking rules
110
44
 
111
- - Do not mix \`requests\`/sync I/O inside \`async def\`. Use \`httpx.AsyncClient\` / \`aiofiles\`.
112
- - \`asyncio.gather\` with \`return_exceptions=False\` cancels siblings on first failure — be explicit.
113
- - Every task created with \`asyncio.create_task\` must have its reference kept and awaited.
45
+ 1. **No broad silent catches.** Avoid bare \`except\` / \`except Exception\` unless re-raised or justified.
46
+ 2. **No mutable defaults.** Use \`None\` + local initialization.
47
+ 3. **Type exported surfaces.** Public functions/classes include clear type hints.
48
+ 4. **Resource safety by default.** File/DB/network handles use context managers.
114
49
 
115
- ## Anti-patterns
50
+ ## Follow-up rules
116
51
 
117
- - Using \`**kwargs\` to avoid writing a real signature.
118
- - Monkey-patching modules from tests without a \`contextlib.contextmanager\` cleanup.
119
- - Treating \`__init__.py\` as a place to run logic (imports only).
120
- - Re-inventing \`itertools\`/\`functools\` instead of using stdlib.
52
+ - Prefer explicit, narrow exceptions.
53
+ - Keep async and sync I/O models separated.
54
+ - Add/adjust tests with behavior changes.
121
55
 
122
- ## Review output shape
56
+ ## Output format
123
57
 
124
- \`\`\`
125
- - **Rule:** P1-3 (exception specificity)
126
- - **File:line:** users/service.py:88
127
- - **Finding:** \`except Exception\` around DB call silently drops integrity errors.
128
- - **Remediation:** Catch \`IntegrityError\` explicitly; re-raise everything else.
129
- \`\`\`
58
+ \`file:line — rule id — concise remediation\`
130
59
  `;
131
60
  }
132
61
  export function languageGoSkill() {
@@ -211,7 +140,7 @@ export const LANGUAGE_RULE_PACK_FILES = {
211
140
  };
212
141
  /**
213
142
  * Folder (relative to runtime root) that holds every enabled language rule
214
- * pack. A single folder keeps discovery trivial for hooks and for `doctor`.
143
+ * pack. A single folder keeps discovery trivial for hooks and for `sync`.
215
144
  */
216
145
  export const LANGUAGE_RULE_PACK_DIR = ["rules", "lang"];
217
146
  export const LANGUAGE_RULE_PACK_GENERATORS = {
@@ -221,7 +150,7 @@ export const LANGUAGE_RULE_PACK_GENERATORS = {
221
150
  };
222
151
  /**
223
152
  * Legacy per-language folders under `.cclaw/skills/` used in v0.7.0. Listed
224
- * here so `cclaw sync` and `doctor` can surface drift and the installer can
153
+ * here so `cclaw sync` and `sync` can surface drift and the installer can
225
154
  * clean them up after the move to `.cclaw/rules/lang/`.
226
155
  */
227
156
  export const LEGACY_LANGUAGE_RULE_PACK_FOLDERS = [
@@ -32,8 +32,10 @@ ${conversationLanguagePolicyMarkdown()}
32
32
  - \`diff\` -> use the **Diff Subcommand** section in \`${RUNTIME_ROOT}/skills/${VIEW_SKILL_FOLDER}/SKILL.md\`
33
33
  3. Unknown subcommand -> print supported values and stop.
34
34
 
35
- ## Headless mode
35
+ ## Headless mode (CI/automation only)
36
36
 
37
+ Headless envelopes are a machine-mode exception for CI/automation orchestration.
38
+ In normal interactive runs, respond with concise read-only prose instead.
37
39
  For machine orchestration, emit one JSON envelope:
38
40
 
39
41
  \`\`\`json
@@ -43,7 +45,7 @@ For machine orchestration, emit one JSON envelope:
43
45
  Use the parsed/defaulted subcommand in both \`payload.command\` and \`payload.subcommand\`; do not collapse \`tree\` or \`diff\` responses to \`status\`.
44
46
 
45
47
  Validate envelopes with:
46
- \`cclaw internal envelope-validate --stdin\`
48
+ \`npx cclaw-cli internal envelope-validate --stdin\`
47
49
 
48
50
  ## Primary skill
49
51
 
@@ -34,6 +34,7 @@ export interface DelegationTokenUsage {
34
34
  output: number;
35
35
  model: string;
36
36
  }
37
+ export type DelegationWaiverAcceptedBy = "user-flag";
37
38
  export type DelegationEntry = {
38
39
  stage: string;
39
40
  agent: string;
@@ -56,6 +57,7 @@ export type DelegationEntry = {
56
57
  */
57
58
  taskId?: string;
58
59
  waiverReason?: string;
60
+ acceptedBy?: DelegationWaiverAcceptedBy;
59
61
  ts?: string;
60
62
  /**
61
63
  * Run id the entry belongs to. Older ledgers written before 0.5.17 may omit this;
@@ -198,6 +198,7 @@ function isDelegationEntry(value) {
198
198
  (o.endTs === undefined || typeof o.endTs === "string") &&
199
199
  (o.taskId === undefined || typeof o.taskId === "string") &&
200
200
  (o.waiverReason === undefined || typeof o.waiverReason === "string") &&
201
+ (o.acceptedBy === undefined || o.acceptedBy === "user-flag") &&
201
202
  waiverOk &&
202
203
  (o.runId === undefined || typeof o.runId === "string") &&
203
204
  (o.fulfillmentMode === undefined ||
@@ -269,7 +270,7 @@ function parseLedger(raw, runId) {
269
270
  // and entry, the entry has no fulfillmentMode, and there is no
270
271
  // dispatch-surface or dispatch-id evidence on the row. We honor
271
272
  // that by tagging fulfillmentMode = "legacy-inferred" so callers
272
- // (stage-complete, doctor) can require an explicit `--rerecord`
273
+ // (stage-complete, sync/runtime checks) can require an explicit `--rerecord`
273
274
  // before the row counts as proof-era.
274
275
  const ledgerHasNoVersion = ledgerSchemaVersion === undefined || ledgerSchemaVersion === 1;
275
276
  const entryHasNoVersion = item.schemaVersion === undefined || item.schemaVersion === 1;
@@ -0,0 +1,66 @@
1
+ export declare const EARLY_LOOP_STAGES: readonly ["brainstorm", "scope", "design"];
2
+ export type EarlyLoopStage = (typeof EARLY_LOOP_STAGES)[number];
3
+ export type EarlyLoopConcernSeverity = "critical" | "important" | "suggestion";
4
+ export interface EarlyLoopConcern {
5
+ id: string;
6
+ severity: EarlyLoopConcernSeverity;
7
+ locator: string;
8
+ summary: string;
9
+ firstSeenIteration: number;
10
+ lastSeenIteration: number;
11
+ resolvedAtIteration?: number;
12
+ }
13
+ export interface EarlyLoopStatus {
14
+ schemaVersion: 1;
15
+ stage: EarlyLoopStage;
16
+ runId: string;
17
+ iteration: number;
18
+ maxIterations: number;
19
+ openConcerns: EarlyLoopConcern[];
20
+ resolvedConcerns: EarlyLoopConcern[];
21
+ lastSeenConcernIds: string[];
22
+ convergenceTripped: boolean;
23
+ escalationReason?: string;
24
+ lastUpdatedAt: string;
25
+ }
26
+ export interface EarlyLoopLogConcern {
27
+ id: string;
28
+ severity: EarlyLoopConcernSeverity;
29
+ locator: string;
30
+ summary: string;
31
+ }
32
+ export interface EarlyLoopLogEntry {
33
+ ts: string;
34
+ runId: string;
35
+ stage: string;
36
+ iteration?: number;
37
+ concerns: EarlyLoopLogConcern[];
38
+ resolvedConcernIds: string[];
39
+ }
40
+ export interface EarlyLoopParseIssue {
41
+ lineNumber: number;
42
+ reason: string;
43
+ rawLine: string;
44
+ }
45
+ export interface ParseEarlyLoopLogOptions {
46
+ issues?: EarlyLoopParseIssue[];
47
+ strict?: boolean;
48
+ }
49
+ export interface DeriveEarlyLoopStatusOptions {
50
+ stage: EarlyLoopStage;
51
+ runId: string;
52
+ maxIterations?: number;
53
+ now?: Date;
54
+ }
55
+ export interface ComputeEarlyLoopStatusOptions {
56
+ maxIterations?: number;
57
+ now?: Date;
58
+ parseIssues?: EarlyLoopParseIssue[];
59
+ strictParse?: boolean;
60
+ }
61
+ export declare function isEarlyLoopStage(value: unknown): value is EarlyLoopStage;
62
+ export declare function normalizeEarlyLoopMaxIterations(value: number | undefined): number;
63
+ export declare function parseEarlyLoopLog(text: string, options?: ParseEarlyLoopLogOptions): EarlyLoopLogEntry[];
64
+ export declare function deriveEarlyLoopStatus(entries: EarlyLoopLogEntry[], options: DeriveEarlyLoopStatusOptions): EarlyLoopStatus;
65
+ export declare function computeEarlyLoopStatus(stage: EarlyLoopStage, runId: string, concernsLogPath: string, options?: ComputeEarlyLoopStatusOptions): Promise<EarlyLoopStatus>;
66
+ export declare function formatEarlyLoopStatusLine(status: EarlyLoopStatus): string;
@@ -0,0 +1,275 @@
1
+ import fs from "node:fs/promises";
2
+ import { DEFAULT_EARLY_LOOP_MAX_ITERATIONS } from "./config.js";
3
+ export const EARLY_LOOP_STAGES = ["brainstorm", "scope", "design"];
4
+ const CONCERN_ID_PREFIX = "C-";
5
+ function severityWeight(severity) {
6
+ if (severity === "critical")
7
+ return 3;
8
+ if (severity === "important")
9
+ return 2;
10
+ return 1;
11
+ }
12
+ function normalizeSeverity(value) {
13
+ if (value === "critical" || value === "important" || value === "suggestion") {
14
+ return value;
15
+ }
16
+ return "important";
17
+ }
18
+ function normalizeText(value, fallback) {
19
+ if (typeof value !== "string")
20
+ return fallback;
21
+ const trimmed = value.trim();
22
+ return trimmed.length > 0 ? trimmed : fallback;
23
+ }
24
+ function stableConcernFallbackId(locator, summary) {
25
+ const seed = `${locator}::${summary}`.trim().toLowerCase();
26
+ let hash = 0;
27
+ for (let index = 0; index < seed.length; index += 1) {
28
+ hash = (Math.imul(31, hash) + seed.charCodeAt(index)) >>> 0;
29
+ }
30
+ return `${CONCERN_ID_PREFIX}${hash.toString(16).padStart(8, "0")}`;
31
+ }
32
+ function normalizeConcernId(id, locator, summary) {
33
+ if (typeof id === "string") {
34
+ const trimmed = id.trim();
35
+ if (trimmed.length > 0) {
36
+ return trimmed;
37
+ }
38
+ }
39
+ return stableConcernFallbackId(locator, summary);
40
+ }
41
+ function normalizeConcerns(value) {
42
+ if (!Array.isArray(value))
43
+ return [];
44
+ const concerns = [];
45
+ for (const row of value) {
46
+ if (!row || typeof row !== "object" || Array.isArray(row))
47
+ continue;
48
+ const typed = row;
49
+ const locator = normalizeText(typed.locator, "unknown-location");
50
+ const summary = normalizeText(typed.summary, "missing-summary");
51
+ concerns.push({
52
+ id: normalizeConcernId(typed.id, locator, summary),
53
+ severity: normalizeSeverity(typed.severity),
54
+ locator,
55
+ summary
56
+ });
57
+ }
58
+ return concerns;
59
+ }
60
+ function normalizeResolvedConcernIds(value) {
61
+ if (!Array.isArray(value))
62
+ return [];
63
+ return value
64
+ .filter((entry) => typeof entry === "string")
65
+ .map((entry) => entry.trim())
66
+ .filter((entry) => entry.length > 0);
67
+ }
68
+ export function isEarlyLoopStage(value) {
69
+ return typeof value === "string" && EARLY_LOOP_STAGES.includes(value);
70
+ }
71
+ export function normalizeEarlyLoopMaxIterations(value) {
72
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1) {
73
+ return value;
74
+ }
75
+ return DEFAULT_EARLY_LOOP_MAX_ITERATIONS;
76
+ }
77
+ export function parseEarlyLoopLog(text, options = {}) {
78
+ const strict = options.strict === true;
79
+ const issues = options.issues;
80
+ const normalized = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
81
+ const lines = normalized.split(/\r?\n/u);
82
+ const entries = [];
83
+ for (let index = 0; index < lines.length; index += 1) {
84
+ const raw = lines[index] ?? "";
85
+ const line = raw.trim();
86
+ if (line.length === 0)
87
+ continue;
88
+ const lineNumber = index + 1;
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(line);
92
+ }
93
+ catch (error) {
94
+ issues?.push({
95
+ lineNumber,
96
+ reason: `json-parse-failed: ${error instanceof Error ? error.message : String(error)}`,
97
+ rawLine: raw
98
+ });
99
+ continue;
100
+ }
101
+ const runId = normalizeText(parsed.runId, "");
102
+ const stage = normalizeText(parsed.stage, "");
103
+ const concerns = normalizeConcerns(parsed.concerns);
104
+ const resolvedConcernIds = normalizeResolvedConcernIds(parsed.resolvedConcernIds);
105
+ const iteration = typeof parsed.iteration === "number" &&
106
+ Number.isInteger(parsed.iteration) &&
107
+ parsed.iteration >= 1
108
+ ? parsed.iteration
109
+ : undefined;
110
+ if (strict) {
111
+ const missing = [];
112
+ if (runId.length === 0)
113
+ missing.push("runId");
114
+ if (stage.length === 0)
115
+ missing.push("stage");
116
+ if (concerns.length === 0 && resolvedConcernIds.length === 0) {
117
+ missing.push("concerns/resolvedConcernIds");
118
+ }
119
+ if (missing.length > 0) {
120
+ issues?.push({
121
+ lineNumber,
122
+ reason: `missing-required-fields: ${missing.join(",")}`,
123
+ rawLine: raw
124
+ });
125
+ continue;
126
+ }
127
+ }
128
+ entries.push({
129
+ ts: normalizeText(parsed.ts, ""),
130
+ runId: runId.length > 0 ? runId : "active",
131
+ stage: stage.length > 0 ? stage : "brainstorm",
132
+ iteration,
133
+ concerns,
134
+ resolvedConcernIds
135
+ });
136
+ }
137
+ return entries;
138
+ }
139
+ function sortConcerns(a, b) {
140
+ const severityDiff = severityWeight(b.severity) - severityWeight(a.severity);
141
+ if (severityDiff !== 0)
142
+ return severityDiff;
143
+ if (a.firstSeenIteration !== b.firstSeenIteration) {
144
+ return a.firstSeenIteration - b.firstSeenIteration;
145
+ }
146
+ if (a.lastSeenIteration !== b.lastSeenIteration) {
147
+ return a.lastSeenIteration - b.lastSeenIteration;
148
+ }
149
+ return a.id.localeCompare(b.id, "en");
150
+ }
151
+ export function deriveEarlyLoopStatus(entries, options) {
152
+ const maxIterations = normalizeEarlyLoopMaxIterations(options.maxIterations);
153
+ const concerns = new Map();
154
+ const filtered = entries.filter((entry) => entry.runId === options.runId && entry.stage === options.stage);
155
+ let previousConcernSnapshotKey = "";
156
+ let sameConcernStreak = 0;
157
+ let convergenceTripped = false;
158
+ let escalationReason;
159
+ let currentIteration = 0;
160
+ let lastSeenConcernIds = [];
161
+ for (const entry of filtered) {
162
+ currentIteration += 1;
163
+ const iteration = entry.iteration ?? currentIteration;
164
+ const seenThisIteration = new Set();
165
+ for (const concern of entry.concerns) {
166
+ seenThisIteration.add(concern.id);
167
+ const existing = concerns.get(concern.id);
168
+ if (!existing) {
169
+ concerns.set(concern.id, {
170
+ id: concern.id,
171
+ severity: concern.severity,
172
+ locator: concern.locator,
173
+ summary: concern.summary,
174
+ firstSeenIteration: iteration,
175
+ lastSeenIteration: iteration
176
+ });
177
+ continue;
178
+ }
179
+ existing.lastSeenIteration = iteration;
180
+ existing.locator = concern.locator;
181
+ existing.summary = concern.summary;
182
+ if (severityWeight(concern.severity) >= severityWeight(existing.severity)) {
183
+ existing.severity = concern.severity;
184
+ }
185
+ delete existing.resolvedAtIteration;
186
+ }
187
+ for (const concernId of entry.resolvedConcernIds) {
188
+ const existing = concerns.get(concernId);
189
+ if (!existing)
190
+ continue;
191
+ if (seenThisIteration.has(concernId))
192
+ continue;
193
+ if (existing.resolvedAtIteration === undefined) {
194
+ existing.resolvedAtIteration = iteration;
195
+ }
196
+ }
197
+ for (const concern of concerns.values()) {
198
+ if (concern.resolvedAtIteration !== undefined)
199
+ continue;
200
+ if (seenThisIteration.has(concern.id))
201
+ continue;
202
+ concern.resolvedAtIteration = iteration;
203
+ }
204
+ const openConcernIds = Array.from(concerns.values())
205
+ .filter((concern) => concern.resolvedAtIteration === undefined)
206
+ .map((concern) => concern.id)
207
+ .sort((a, b) => a.localeCompare(b, "en"));
208
+ lastSeenConcernIds = openConcernIds;
209
+ const snapshotKey = openConcernIds.join("|");
210
+ if (snapshotKey.length > 0 && snapshotKey === previousConcernSnapshotKey) {
211
+ sameConcernStreak += 1;
212
+ if (!convergenceTripped && sameConcernStreak >= 2) {
213
+ convergenceTripped = true;
214
+ escalationReason = `same concerns ${sameConcernStreak} iterations in a row`;
215
+ }
216
+ }
217
+ else {
218
+ sameConcernStreak = snapshotKey.length > 0 ? 1 : 0;
219
+ }
220
+ previousConcernSnapshotKey = snapshotKey;
221
+ }
222
+ const openConcerns = Array.from(concerns.values())
223
+ .filter((concern) => concern.resolvedAtIteration === undefined)
224
+ .sort(sortConcerns);
225
+ const resolvedConcerns = Array.from(concerns.values())
226
+ .filter((concern) => concern.resolvedAtIteration !== undefined)
227
+ .sort((a, b) => {
228
+ if (a.resolvedAtIteration !== b.resolvedAtIteration) {
229
+ return a.resolvedAtIteration - b.resolvedAtIteration;
230
+ }
231
+ return sortConcerns(a, b);
232
+ });
233
+ if (!convergenceTripped && openConcerns.length > 0 && currentIteration >= maxIterations) {
234
+ convergenceTripped = true;
235
+ escalationReason = `max iterations ${maxIterations} reached with ${openConcerns.length} open concern(s)`;
236
+ }
237
+ return {
238
+ schemaVersion: 1,
239
+ stage: options.stage,
240
+ runId: options.runId,
241
+ iteration: currentIteration,
242
+ maxIterations,
243
+ openConcerns,
244
+ resolvedConcerns,
245
+ lastSeenConcernIds,
246
+ convergenceTripped,
247
+ ...(escalationReason ? { escalationReason } : {}),
248
+ lastUpdatedAt: (options.now ?? new Date()).toISOString()
249
+ };
250
+ }
251
+ export async function computeEarlyLoopStatus(stage, runId, concernsLogPath, options = {}) {
252
+ let raw = "";
253
+ try {
254
+ raw = await fs.readFile(concernsLogPath, "utf8");
255
+ }
256
+ catch (error) {
257
+ if (error.code !== "ENOENT") {
258
+ throw error;
259
+ }
260
+ }
261
+ const parsed = parseEarlyLoopLog(raw, {
262
+ issues: options.parseIssues,
263
+ strict: options.strictParse
264
+ });
265
+ return deriveEarlyLoopStatus(parsed, {
266
+ stage,
267
+ runId,
268
+ maxIterations: options.maxIterations,
269
+ now: options.now
270
+ });
271
+ }
272
+ export function formatEarlyLoopStatusLine(status) {
273
+ const convergence = status.convergenceTripped ? "tripped" : "clear";
274
+ return `Early Loop: stage=${status.stage}, iter=${status.iteration}/${status.maxIterations}, open=${status.openConcerns.length}, convergence=${convergence}`;
275
+ }
@@ -32,14 +32,13 @@ export interface RetroState {
32
32
  /**
33
33
  * Ship closeout substate machine.
34
34
  *
35
- * After ship completes, cclaw auto-chains retro compound → archive.
35
+ * After ship completes, cclaw auto-chains post-ship review → archive.
36
36
  * Each step is interruptible: `/cc` reads `shipSubstate` and resumes
37
37
  * from the correct step even across sessions.
38
38
  *
39
39
  * - `idle` — ship not complete, or closeout not yet started.
40
- * - `retro_review` — 09-retro.md draft exists; awaiting user edit/accept/skip.
41
- * - `compound_review` retro accepted; compound pass awaiting execution
42
- * (or user skip).
40
+ * - `post_ship_review` — unified closeout leg: retro acceptance/edit/skip
41
+ * plus compound pass execution (or explicit skip).
43
42
  * - `ready_to_archive` — retro + compound done; archive is the next
44
43
  * automatic step.
45
44
  * - `archived` — archive completed in this session (transient — archive
@@ -53,7 +52,7 @@ export interface RetroState {
53
52
  * These are not duplicates: `done` lives in stage transitions; `archived` /
54
53
  * `idle` live in closeout lifecycle state.
55
54
  */
56
- export declare const SHIP_SUBSTATES: readonly ["idle", "retro_review", "compound_review", "ready_to_archive", "archived"];
55
+ export declare const SHIP_SUBSTATES: readonly ["idle", "post_ship_review", "ready_to_archive", "archived"];
57
56
  export type ShipSubstate = (typeof SHIP_SUBSTATES)[number];
58
57
  export interface CloseoutState {
59
58
  shipSubstate: ShipSubstate;
@@ -85,7 +84,7 @@ export interface FlowState {
85
84
  rewinds: RewindRecord[];
86
85
  /** Mandatory retrospective gate status before archive. */
87
86
  retro: RetroState;
88
- /** Ship → retrocompound → archive substate for resumable closeout. */
87
+ /** Ship → post_ship_review → archive substate for resumable closeout. */
89
88
  closeout: CloseoutState;
90
89
  }
91
90
  export interface InitialFlowStateOptions {
@@ -5,14 +5,13 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
5
5
  /**
6
6
  * Ship closeout substate machine.
7
7
  *
8
- * After ship completes, cclaw auto-chains retro compound → archive.
8
+ * After ship completes, cclaw auto-chains post-ship review → archive.
9
9
  * Each step is interruptible: `/cc` reads `shipSubstate` and resumes
10
10
  * from the correct step even across sessions.
11
11
  *
12
12
  * - `idle` — ship not complete, or closeout not yet started.
13
- * - `retro_review` — 09-retro.md draft exists; awaiting user edit/accept/skip.
14
- * - `compound_review` retro accepted; compound pass awaiting execution
15
- * (or user skip).
13
+ * - `post_ship_review` — unified closeout leg: retro acceptance/edit/skip
14
+ * plus compound pass execution (or explicit skip).
16
15
  * - `ready_to_archive` — retro + compound done; archive is the next
17
16
  * automatic step.
18
17
  * - `archived` — archive completed in this session (transient — archive
@@ -28,8 +27,7 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
28
27
  */
29
28
  export const SHIP_SUBSTATES = [
30
29
  "idle",
31
- "retro_review",
32
- "compound_review",
30
+ "post_ship_review",
33
31
  "ready_to_archive",
34
32
  "archived"
35
33
  ];
@@ -29,29 +29,6 @@ export interface CompletedStagesClosureResult {
29
29
  blocked: string[];
30
30
  }>;
31
31
  }
32
- export declare const RECONCILIATION_NOTICES_REL_PATH = ".cclaw/state/reconciliation-notices.json";
33
- export interface ReconciliationNotice {
34
- id: string;
35
- runId: string;
36
- stage: FlowStage;
37
- gateId: string;
38
- reason: string;
39
- demotedAt: string;
40
- }
41
- export interface ReconciliationNoticesPayload {
42
- schemaVersion: number;
43
- notices: ReconciliationNotice[];
44
- parseOk: boolean;
45
- schemaOk: boolean;
46
- }
47
- export interface ReconciliationNoticeBuckets {
48
- activeBlocked: ReconciliationNotice[];
49
- currentStageBlocked: ReconciliationNotice[];
50
- unsynced: ReconciliationNotice[];
51
- staleRun: ReconciliationNotice[];
52
- }
53
- export declare function readReconciliationNotices(projectRoot: string): Promise<ReconciliationNoticesPayload>;
54
- export declare function classifyReconciliationNotices(flowState: FlowState, notices: ReconciliationNotice[]): ReconciliationNoticeBuckets;
55
32
  export declare function verifyCurrentStageGateEvidence(projectRoot: string, flowState: FlowState): Promise<GateEvidenceCheckResult>;
56
33
  export declare function verifyCompletedStagesGateClosure(flowState: FlowState): CompletedStagesClosureResult;
57
34
  export interface GateReconciliationResult {