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
@@ -30,13 +30,13 @@ export interface TddCycleParseIssue {
30
30
  export interface ParseTddCycleLogOptions {
31
31
  /**
32
32
  * Collect one issue per dropped/malformed line. Callers that care
33
- * (doctor, red-evidence) can surface them; hooks keep soft-fail.
33
+ * (sync/runtime checks, red-evidence) can surface them; hooks keep soft-fail.
34
34
  */
35
35
  issues?: TddCycleParseIssue[];
36
36
  /**
37
37
  * When true, reject lines that omit required fields instead of
38
38
  * back-filling them with defaults. Used by validation paths
39
- * (`validateTddCycleOrder`, `cclaw doctor`) to avoid silently
39
+ * (`validateTddCycleOrder`, `npx cclaw-cli sync` fail-fast) to avoid silently
40
40
  * bucketing unscoped rows into "runId=active, stage=tdd". Soft paths
41
41
  * (generated hooks) keep the legacy defaults so a half-written file
42
42
  * never takes the session down.
@@ -92,7 +92,7 @@ export interface RalphLoopStatus {
92
92
  * Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
93
93
  * The goal is to give the model a single source of truth for "am I done
94
94
  * iterating?" — it collapses per-slice progress and distinct closed AC IDs
95
- * (from GREEN rows) into a single artifact the next-command contract reads.
95
+ * (from GREEN rows) into a single artifact the progression contract reads.
96
96
  */
97
97
  export declare function computeRalphLoopStatus(entries: TddCycleEntry[], options?: {
98
98
  runId?: string;
package/dist/tdd-cycle.js CHANGED
@@ -201,7 +201,7 @@ export function pathMatchesTarget(candidate, target) {
201
201
  * Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
202
202
  * The goal is to give the model a single source of truth for "am I done
203
203
  * iterating?" — it collapses per-slice progress and distinct closed AC IDs
204
- * (from GREEN rows) into a single artifact the next-command contract reads.
204
+ * (from GREEN rows) into a single artifact the progression contract reads.
205
205
  */
206
206
  export function computeRalphLoopStatus(entries, options = {}) {
207
207
  const runId = options.runId ?? "active";
package/dist/types.d.ts CHANGED
@@ -71,8 +71,7 @@ export interface TrackHeuristicsConfig {
71
71
  * when the harness supports native dispatch, otherwise fulfilled via
72
72
  * an explicit in-session role switch with evidence).
73
73
  *
74
- * Track gating: `enforceOnTracks` lists the tracks where the doctor
75
- * check escalates to a warning. Tracks outside this list still see
74
+ * Track gating: `enforceOnTracks` lists the tracks where the sync/runtime check escalates to a warning. Tracks outside this list still see
76
75
  * the skill prose but leave the decision to the user.
77
76
  *
78
77
  * All fields optional; sensible defaults: disabled, threshold 5, no
@@ -85,7 +84,7 @@ export interface SliceReviewConfig {
85
84
  filesChangedThreshold?: number;
86
85
  /** Glob hints; any plan-task touchPath match triggers review. */
87
86
  touchTriggers?: string[];
88
- /** Tracks on which missed reviews escalate to a doctor warning. */
87
+ /** Tracks on which missed reviews escalate to a sync/runtime warning. */
89
88
  enforceOnTracks?: FlowTrack[];
90
89
  }
91
90
  /**
@@ -116,6 +115,17 @@ export interface TddPathConfig {
116
115
  export interface CompoundConfig {
117
116
  recurrenceThreshold?: number;
118
117
  }
118
+ /**
119
+ * Early-stage Ralph loop policy for brainstorm/scope/design.
120
+ *
121
+ * - enabled: when false, skip early-loop gate/diagnostics and hook writes.
122
+ * - maxIterations: capped producer/critic iterations before convergence
123
+ * escalation. Defaults to 3.
124
+ */
125
+ export interface EarlyLoopConfig {
126
+ enabled?: boolean;
127
+ maxIterations?: number;
128
+ }
119
129
  export interface IronLawsConfig {
120
130
  /**
121
131
  * Per-law escape hatch: list the iron-law ids that must always be strict,
@@ -128,13 +138,13 @@ export interface IronLawsConfig {
128
138
  /**
129
139
  * Optional opt-in audit toggles for additional stage lint gates.
130
140
  *
131
- * Disabled by default so existing projects are not forced into stricter
132
- * checks until they explicitly enable them in config.
141
+ * `scopePreAudit` stays opt-in (disabled by default). `staleDiagramAudit` is
142
+ * default-on and can be explicitly disabled when teams intentionally skip it.
133
143
  */
134
144
  export interface OptInAuditsConfig {
135
145
  /** When true, scope lint requires a filled `Pre-Scope System Audit` section. */
136
146
  scopePreAudit?: boolean;
137
- /** When true, design lint runs stale diagram drift checks against blast radius files. */
147
+ /** Default true; when enabled, design lint runs stale diagram drift checks against blast radius files. */
138
148
  staleDiagramAudit?: boolean;
139
149
  }
140
150
  export interface ReviewLoopExternalSecondOpinionConfig {
@@ -176,6 +186,8 @@ export interface CclawConfig {
176
186
  tdd?: TddPathConfig;
177
187
  /** Compound-stage recurrence policy overrides. */
178
188
  compound?: CompoundConfig;
189
+ /** Early-stage producer/critic loop policy overrides. */
190
+ earlyLoop?: EarlyLoopConfig;
179
191
  /** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
180
192
  gitHookGuards?: boolean;
181
193
  /** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
@@ -208,10 +220,6 @@ export interface CclawConfig {
208
220
  /** Optional runtime knobs for outside-voice review loops. */
209
221
  reviewLoop?: ReviewLoopConfig;
210
222
  }
211
- /**
212
- * @deprecated Use `CclawConfig` instead.
213
- */
214
- export type VibyConfig = CclawConfig;
215
223
  export interface TransitionRule {
216
224
  from: FlowStage;
217
225
  to: FlowStage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.51.30",
3
+ "version": "1.0.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "scripts": {
19
19
  "clean:dist": "node -e \"import('node:fs/promises').then((fs) => fs.rm('dist', { recursive: true, force: true }))\"",
20
- "build": "npm run clean:dist && tsc -p tsconfig.json && node scripts/chmod-bin.mjs",
20
+ "build": "npm run clean:dist && tsc -p tsconfig.json && npm run build:hook-bundle && node scripts/chmod-bin.mjs",
21
+ "build:hook-bundle": "esbuild src/runtime/run-hook.entry.ts --bundle --platform=node --format=esm --outfile=dist/runtime/run-hook.mjs",
21
22
  "test": "vitest run",
22
23
  "test:watch": "vitest",
23
24
  "test:coverage": "vitest run --coverage",
@@ -47,6 +48,7 @@
47
48
  "@stryker-mutator/vitest-runner": "^9.6.1",
48
49
  "@types/node": "^24.7.2",
49
50
  "@vitest/coverage-v8": "^3.2.4",
51
+ "esbuild": "^0.28.0",
50
52
  "typescript": "^5.9.3",
51
53
  "vitest": "^3.2.4"
52
54
  }
@@ -1,83 +0,0 @@
1
- /**
2
- * hook-inline-snippets.ts
3
- *
4
- * Runtime `.cclaw/hooks/run-hook.mjs` is a **standalone Node script** that
5
- * cannot import from `cclaw-cli` — it must work inside the end-user's
6
- * project even when the CLI is not installed. Two derived computations,
7
- * though, must remain 1:1 with the canonical TS implementations:
8
- *
9
- * 1. `computeCompoundReadinessInline` mirrors
10
- * `src/knowledge-store.ts::computeCompoundReadiness`.
11
- * 2. `computeRalphLoopStatusInline` mirrors
12
- * `src/tdd-cycle.ts::computeRalphLoopStatus`.
13
- *
14
- * Previously those bodies lived inline in `src/content/node-hooks.ts` — a
15
- * ~2000-line file — next to unrelated hook-handler code. Any silent drift
16
- * only surfaced when someone remembered to update both sides.
17
- *
18
- * This module centralizes the inline JavaScript snippets so:
19
- *
20
- * - There is exactly **one place** (this file) that holds each inline
21
- * JS body.
22
- * - Each snippet carries an explicit "mirrors X, parity enforced by Y"
23
- * header comment and is emitted into `run-hook.mjs` verbatim.
24
- * - `src/content/node-hooks.ts` only interpolates the snippets, it no
25
- * longer owns their source code.
26
- *
27
- * Parity with the TypeScript canonical implementations is enforced by
28
- * `tests/unit/ralph-loop-parity.test.ts`. Any structural change to the
29
- * canonical TS code MUST:
30
- *
31
- * 1. Update the matching snippet below.
32
- * 2. Re-run `npm test tests/unit/ralph-loop-parity.test.ts`.
33
- *
34
- * DO NOT inline tests here — keep the parity check in its dedicated test
35
- * file.
36
- */
37
- /**
38
- * Inline JS helpers used by both compound-readiness and ralph-loop
39
- * snippets. Kept small and locked: they are shared across the two inline
40
- * routines and must not grow into a hidden utility namespace.
41
- *
42
- * - `normalizeCompoundLastUpdatedAt` produces a stable ISO-8601 UTC
43
- * timestamp so the hook-written `compound-readiness.json` is byte-equal
44
- * to the CLI-written version for the same input.
45
- * - `countArchivedRunsInline` counts immediate subdirectories of
46
- * `<root>/.cclaw/runs/` so both the hook and the CLI see the same
47
- * `archivedRunsCount` for the small-project relaxation.
48
- * - `formatCompoundReadinessLineInline` mirrors the one-line summary shape
49
- * used by `src/internal/compound-readiness.ts::formatCompoundReadinessLine`
50
- * so session-start and internal CLI command stay wording-compatible.
51
- */
52
- export declare const HOOK_INLINE_SHARED_HELPERS = "\nfunction normalizeCompoundLastUpdatedAt(date) {\n return date.toISOString().replace(/\\.\\d{3}Z$/u, \"Z\");\n}\n\n// Count archived runs as sub-directories under `.cclaw/runs/`. Missing\n// dir returns 0; unexpected errors return undefined so the caller can\n// skip the small-project relaxation rather than guess.\nasync function countArchivedRunsInline(root) {\n const dir = path.join(root, RUNTIME_ROOT, \"runs\");\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((entry) => entry.isDirectory()).length;\n } catch (error) {\n const code = error && typeof error === \"object\" && \"code\" in error ? error.code : null;\n if (code === \"ENOENT\") return 0;\n return undefined;\n }\n}\n\nfunction formatCompoundReadinessLineInline(readiness) {\n if (!readiness || typeof readiness !== \"object\") {\n return \"\";\n }\n const ready = Array.isArray(readiness.ready) ? readiness.ready : [];\n const readyCount =\n typeof readiness.readyCount === \"number\" && Number.isFinite(readiness.readyCount)\n ? Math.trunc(readiness.readyCount)\n : ready.length;\n const clusterCount =\n typeof readiness.clusterCount === \"number\" && Number.isFinite(readiness.clusterCount)\n ? Math.trunc(readiness.clusterCount)\n : 0;\n const threshold =\n typeof readiness.threshold === \"number\" && Number.isFinite(readiness.threshold)\n ? Math.trunc(readiness.threshold)\n : COMPOUND_RECURRENCE_THRESHOLD;\n if (readyCount === 0) {\n return \"Compound readiness: no candidates (clusters=\" +\n String(clusterCount) + \", threshold=\" + String(threshold) + \")\";\n }\n const critical = ready.filter(\n (entry) => entry && typeof entry === \"object\" && entry.severity === \"critical\"\n ).length;\n const criticalSuffix = critical > 0 ? \" (critical=\" + String(critical) + \")\" : \"\";\n return \"Compound readiness: clusters=\" + String(clusterCount) +\n \", ready=\" + String(readyCount) + criticalSuffix;\n}\n";
53
- /**
54
- * Inline mirror of `src/knowledge-store.ts::computeCompoundReadiness`.
55
- *
56
- * Parity enforced by
57
- * `tests/unit/ralph-loop-parity.test.ts::compound-readiness parity`.
58
- *
59
- * Signature contract:
60
- * async function computeCompoundReadinessInline(root, options) -> CompoundReadiness
61
- *
62
- * Accepted options (all optional):
63
- * - prereadRaw: string | undefined — pre-read `knowledge.jsonl` contents.
64
- * - threshold: integer >= 1 — default recurrence threshold.
65
- * - archivedRunsCount: integer >= 0 — enables small-project relaxation.
66
- * - maxReady: integer >= 1 — cap on returned `ready` cluster count
67
- * (default 10).
68
- *
69
- * Depends on: `SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD`,
70
- * `SMALL_PROJECT_RECURRENCE_THRESHOLD`, `COMPOUND_RECURRENCE_THRESHOLD`,
71
- * and `HOOK_INLINE_SHARED_HELPERS` being in the same runtime scope.
72
- */
73
- export declare const COMPOUND_READINESS_INLINE_SOURCE = "\nasync function computeCompoundReadinessInline(root, options) {\n const filePath = path.join(root, RUNTIME_ROOT, \"knowledge.jsonl\");\n // Caller may supply pre-read raw to avoid double-reading knowledge.jsonl.\n const raw = typeof (options && options.prereadRaw) === \"string\"\n ? options.prereadRaw\n : await readTextFile(filePath, \"\");\n const baseThresholdRaw = options && options.threshold;\n const baseThreshold = Number.isInteger(baseThresholdRaw) && baseThresholdRaw >= 1\n ? baseThresholdRaw\n : COMPOUND_RECURRENCE_THRESHOLD;\n const archivedRunsCount =\n typeof (options && options.archivedRunsCount) === \"number\" &&\n Number.isFinite(options.archivedRunsCount) &&\n options.archivedRunsCount >= 0\n ? Math.floor(options.archivedRunsCount)\n : undefined;\n const smallProjectRelaxationApplied =\n archivedRunsCount !== undefined &&\n archivedRunsCount < SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD &&\n baseThreshold > SMALL_PROJECT_RECURRENCE_THRESHOLD;\n const threshold = smallProjectRelaxationApplied\n ? SMALL_PROJECT_RECURRENCE_THRESHOLD\n : baseThreshold;\n const maxReady = Number.isInteger(options && options.maxReady) && options.maxReady >= 1\n ? options.maxReady\n : 10;\n const normalize = (value) => String(value == null ? \"\" : value).trim().replace(/\\s+/gu, \" \").toLowerCase();\n const severityWeight = (sev) => {\n if (sev === \"critical\") return 3;\n if (sev === \"important\") return 2;\n if (sev === \"suggestion\") return 1;\n return 0;\n };\n const buckets = new Map();\n for (const rawLine of raw.split(/\\r?\\n/gu)) {\n const line = rawLine.trim();\n if (line.length === 0) continue;\n let row;\n try { row = JSON.parse(line); } catch { continue; }\n if (!row || typeof row !== \"object\" || Array.isArray(row)) continue;\n if (row.maturity === \"lifted-to-enforcement\" || typeof row.superseded_by === \"string\") continue;\n const type = typeof row.type === \"string\" ? row.type : \"\";\n const trigger = typeof row.trigger === \"string\" ? row.trigger : \"\";\n const action = typeof row.action === \"string\" ? row.action : \"\";\n if (type.length === 0 || trigger.length === 0 || action.length === 0) continue;\n const key = type + \"||\" + normalize(trigger) + \"||\" + normalize(action);\n const frequency = Number.isInteger(row.frequency) && row.frequency > 0 ? Math.floor(row.frequency) : 1;\n const lastSeen = typeof row.last_seen_ts === \"string\" ? row.last_seen_ts : \"\";\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = {\n trigger,\n action,\n recurrence: frequency,\n entryCount: 1,\n severity: typeof row.severity === \"string\" ? row.severity : undefined,\n lastSeenTs: lastSeen,\n types: new Set([type]),\n maturity: new Set([typeof row.maturity === \"string\" ? row.maturity : \"raw\"])\n };\n buckets.set(key, bucket);\n continue;\n }\n bucket.recurrence += frequency;\n bucket.entryCount += 1;\n bucket.types.add(type);\n bucket.maturity.add(typeof row.maturity === \"string\" ? row.maturity : \"raw\");\n if (row.severity === \"critical\") {\n bucket.severity = \"critical\";\n } else if (row.severity === \"important\" && bucket.severity !== \"critical\") {\n bucket.severity = \"important\";\n }\n if (lastSeen && Date.parse(lastSeen) > Date.parse(bucket.lastSeenTs || \"0\")) {\n bucket.lastSeenTs = lastSeen;\n }\n }\n const ready = [];\n for (const bucket of buckets.values()) {\n const criticalOverride = bucket.severity === \"critical\";\n const meetsRecurrence = bucket.recurrence >= threshold;\n if (!criticalOverride && !meetsRecurrence) continue;\n ready.push({\n trigger: bucket.trigger,\n action: bucket.action,\n recurrence: bucket.recurrence,\n entryCount: bucket.entryCount,\n qualification: criticalOverride && !meetsRecurrence ? \"critical_override\" : \"recurrence\",\n ...(bucket.severity ? { severity: bucket.severity } : {}),\n lastSeenTs: bucket.lastSeenTs,\n types: Array.from(bucket.types).sort(),\n maturity: Array.from(bucket.maturity).sort()\n });\n }\n ready.sort((a, b) => {\n const sevDiff = severityWeight(b.severity) - severityWeight(a.severity);\n if (sevDiff !== 0) return sevDiff;\n if (b.recurrence !== a.recurrence) return b.recurrence - a.recurrence;\n const recencyDiff = Date.parse(b.lastSeenTs || \"0\") - Date.parse(a.lastSeenTs || \"0\");\n if (!Number.isNaN(recencyDiff) && recencyDiff !== 0) return recencyDiff;\n return String(a.trigger).localeCompare(String(b.trigger));\n });\n return {\n schemaVersion: 2,\n threshold,\n baseThreshold,\n ...(archivedRunsCount !== undefined ? { archivedRunsCount } : {}),\n smallProjectRelaxationApplied,\n clusterCount: buckets.size,\n readyCount: ready.length,\n ready: ready.slice(0, maxReady),\n lastUpdatedAt: normalizeCompoundLastUpdatedAt(new Date())\n };\n}\n";
74
- /**
75
- * Inline mirror of `src/tdd-cycle.ts::computeRalphLoopStatus`.
76
- *
77
- * Parity enforced by
78
- * `tests/unit/ralph-loop-parity.test.ts::ralph-loop parity`.
79
- *
80
- * Signature contract:
81
- * async function computeRalphLoopStatusInline(stateDir, runId) -> RalphLoopStatus
82
- */
83
- export declare const RALPH_LOOP_INLINE_SOURCE = "\nasync function computeRalphLoopStatusInline(stateDir, runId) {\n const filePath = path.join(stateDir, \"tdd-cycle-log.jsonl\");\n const raw = await readTextFile(filePath, \"\");\n const sliceMap = new Map();\n const acClosed = new Set();\n const redOpenSlices = [];\n let loopIteration = 0;\n for (const rawLine of raw.split(/\\r?\\n/gu)) {\n const line = rawLine.trim();\n if (line.length === 0) continue;\n let row;\n try { row = JSON.parse(line); } catch { continue; }\n if (!row || typeof row !== \"object\" || Array.isArray(row)) continue;\n const rowRun = typeof row.runId === \"string\" && row.runId.length > 0 ? row.runId : runId;\n if (rowRun !== runId) continue;\n const slice = typeof row.slice === \"string\" && row.slice.length > 0 ? row.slice : \"S-unknown\";\n let state = sliceMap.get(slice);\n if (!state) {\n state = { slice, redCount: 0, greenCount: 0, refactorCount: 0, redOpen: false, acIds: [] };\n sliceMap.set(slice, state);\n }\n const exitCode = typeof row.exitCode === \"number\" ? row.exitCode : undefined;\n if (row.phase === \"red\") {\n state.redCount += 1;\n if (exitCode !== undefined && exitCode !== 0) state.redOpen = true;\n } else if (row.phase === \"green\") {\n state.greenCount += 1;\n state.redOpen = false;\n loopIteration += 1;\n if (Array.isArray(row.acIds)) {\n for (const acId of row.acIds) {\n if (typeof acId !== \"string\" || acId.length === 0) continue;\n acClosed.add(acId);\n if (!state.acIds.includes(acId)) state.acIds.push(acId);\n }\n }\n } else if (row.phase === \"refactor\") {\n state.refactorCount += 1;\n }\n }\n for (const state of sliceMap.values()) {\n if (state.redOpen) redOpenSlices.push(state.slice);\n }\n const slices = Array.from(sliceMap.values()).sort((a, b) => a.slice.localeCompare(b.slice, \"en\"));\n return {\n schemaVersion: 1,\n runId,\n loopIteration,\n redOpen: redOpenSlices.length > 0,\n redOpenSlices,\n acClosed: Array.from(acClosed).sort(),\n sliceCount: slices.length,\n slices,\n lastUpdatedAt: new Date().toISOString()\n };\n}\n";
@@ -1,302 +0,0 @@
1
- /**
2
- * hook-inline-snippets.ts
3
- *
4
- * Runtime `.cclaw/hooks/run-hook.mjs` is a **standalone Node script** that
5
- * cannot import from `cclaw-cli` — it must work inside the end-user's
6
- * project even when the CLI is not installed. Two derived computations,
7
- * though, must remain 1:1 with the canonical TS implementations:
8
- *
9
- * 1. `computeCompoundReadinessInline` mirrors
10
- * `src/knowledge-store.ts::computeCompoundReadiness`.
11
- * 2. `computeRalphLoopStatusInline` mirrors
12
- * `src/tdd-cycle.ts::computeRalphLoopStatus`.
13
- *
14
- * Previously those bodies lived inline in `src/content/node-hooks.ts` — a
15
- * ~2000-line file — next to unrelated hook-handler code. Any silent drift
16
- * only surfaced when someone remembered to update both sides.
17
- *
18
- * This module centralizes the inline JavaScript snippets so:
19
- *
20
- * - There is exactly **one place** (this file) that holds each inline
21
- * JS body.
22
- * - Each snippet carries an explicit "mirrors X, parity enforced by Y"
23
- * header comment and is emitted into `run-hook.mjs` verbatim.
24
- * - `src/content/node-hooks.ts` only interpolates the snippets, it no
25
- * longer owns their source code.
26
- *
27
- * Parity with the TypeScript canonical implementations is enforced by
28
- * `tests/unit/ralph-loop-parity.test.ts`. Any structural change to the
29
- * canonical TS code MUST:
30
- *
31
- * 1. Update the matching snippet below.
32
- * 2. Re-run `npm test tests/unit/ralph-loop-parity.test.ts`.
33
- *
34
- * DO NOT inline tests here — keep the parity check in its dedicated test
35
- * file.
36
- */
37
- /**
38
- * Inline JS helpers used by both compound-readiness and ralph-loop
39
- * snippets. Kept small and locked: they are shared across the two inline
40
- * routines and must not grow into a hidden utility namespace.
41
- *
42
- * - `normalizeCompoundLastUpdatedAt` produces a stable ISO-8601 UTC
43
- * timestamp so the hook-written `compound-readiness.json` is byte-equal
44
- * to the CLI-written version for the same input.
45
- * - `countArchivedRunsInline` counts immediate subdirectories of
46
- * `<root>/.cclaw/runs/` so both the hook and the CLI see the same
47
- * `archivedRunsCount` for the small-project relaxation.
48
- * - `formatCompoundReadinessLineInline` mirrors the one-line summary shape
49
- * used by `src/internal/compound-readiness.ts::formatCompoundReadinessLine`
50
- * so session-start and internal CLI command stay wording-compatible.
51
- */
52
- export const HOOK_INLINE_SHARED_HELPERS = `
53
- function normalizeCompoundLastUpdatedAt(date) {
54
- return date.toISOString().replace(/\\.\\d{3}Z$/u, "Z");
55
- }
56
-
57
- // Count archived runs as sub-directories under \`.cclaw/runs/\`. Missing
58
- // dir returns 0; unexpected errors return undefined so the caller can
59
- // skip the small-project relaxation rather than guess.
60
- async function countArchivedRunsInline(root) {
61
- const dir = path.join(root, RUNTIME_ROOT, "runs");
62
- try {
63
- const entries = await fs.readdir(dir, { withFileTypes: true });
64
- return entries.filter((entry) => entry.isDirectory()).length;
65
- } catch (error) {
66
- const code = error && typeof error === "object" && "code" in error ? error.code : null;
67
- if (code === "ENOENT") return 0;
68
- return undefined;
69
- }
70
- }
71
-
72
- function formatCompoundReadinessLineInline(readiness) {
73
- if (!readiness || typeof readiness !== "object") {
74
- return "";
75
- }
76
- const ready = Array.isArray(readiness.ready) ? readiness.ready : [];
77
- const readyCount =
78
- typeof readiness.readyCount === "number" && Number.isFinite(readiness.readyCount)
79
- ? Math.trunc(readiness.readyCount)
80
- : ready.length;
81
- const clusterCount =
82
- typeof readiness.clusterCount === "number" && Number.isFinite(readiness.clusterCount)
83
- ? Math.trunc(readiness.clusterCount)
84
- : 0;
85
- const threshold =
86
- typeof readiness.threshold === "number" && Number.isFinite(readiness.threshold)
87
- ? Math.trunc(readiness.threshold)
88
- : COMPOUND_RECURRENCE_THRESHOLD;
89
- if (readyCount === 0) {
90
- return "Compound readiness: no candidates (clusters=" +
91
- String(clusterCount) + ", threshold=" + String(threshold) + ")";
92
- }
93
- const critical = ready.filter(
94
- (entry) => entry && typeof entry === "object" && entry.severity === "critical"
95
- ).length;
96
- const criticalSuffix = critical > 0 ? " (critical=" + String(critical) + ")" : "";
97
- return "Compound readiness: clusters=" + String(clusterCount) +
98
- ", ready=" + String(readyCount) + criticalSuffix;
99
- }
100
- `;
101
- /**
102
- * Inline mirror of `src/knowledge-store.ts::computeCompoundReadiness`.
103
- *
104
- * Parity enforced by
105
- * `tests/unit/ralph-loop-parity.test.ts::compound-readiness parity`.
106
- *
107
- * Signature contract:
108
- * async function computeCompoundReadinessInline(root, options) -> CompoundReadiness
109
- *
110
- * Accepted options (all optional):
111
- * - prereadRaw: string | undefined — pre-read `knowledge.jsonl` contents.
112
- * - threshold: integer >= 1 — default recurrence threshold.
113
- * - archivedRunsCount: integer >= 0 — enables small-project relaxation.
114
- * - maxReady: integer >= 1 — cap on returned `ready` cluster count
115
- * (default 10).
116
- *
117
- * Depends on: `SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD`,
118
- * `SMALL_PROJECT_RECURRENCE_THRESHOLD`, `COMPOUND_RECURRENCE_THRESHOLD`,
119
- * and `HOOK_INLINE_SHARED_HELPERS` being in the same runtime scope.
120
- */
121
- export const COMPOUND_READINESS_INLINE_SOURCE = `
122
- async function computeCompoundReadinessInline(root, options) {
123
- const filePath = path.join(root, RUNTIME_ROOT, "knowledge.jsonl");
124
- // Caller may supply pre-read raw to avoid double-reading knowledge.jsonl.
125
- const raw = typeof (options && options.prereadRaw) === "string"
126
- ? options.prereadRaw
127
- : await readTextFile(filePath, "");
128
- const baseThresholdRaw = options && options.threshold;
129
- const baseThreshold = Number.isInteger(baseThresholdRaw) && baseThresholdRaw >= 1
130
- ? baseThresholdRaw
131
- : COMPOUND_RECURRENCE_THRESHOLD;
132
- const archivedRunsCount =
133
- typeof (options && options.archivedRunsCount) === "number" &&
134
- Number.isFinite(options.archivedRunsCount) &&
135
- options.archivedRunsCount >= 0
136
- ? Math.floor(options.archivedRunsCount)
137
- : undefined;
138
- const smallProjectRelaxationApplied =
139
- archivedRunsCount !== undefined &&
140
- archivedRunsCount < SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD &&
141
- baseThreshold > SMALL_PROJECT_RECURRENCE_THRESHOLD;
142
- const threshold = smallProjectRelaxationApplied
143
- ? SMALL_PROJECT_RECURRENCE_THRESHOLD
144
- : baseThreshold;
145
- const maxReady = Number.isInteger(options && options.maxReady) && options.maxReady >= 1
146
- ? options.maxReady
147
- : 10;
148
- const normalize = (value) => String(value == null ? "" : value).trim().replace(/\\s+/gu, " ").toLowerCase();
149
- const severityWeight = (sev) => {
150
- if (sev === "critical") return 3;
151
- if (sev === "important") return 2;
152
- if (sev === "suggestion") return 1;
153
- return 0;
154
- };
155
- const buckets = new Map();
156
- for (const rawLine of raw.split(/\\r?\\n/gu)) {
157
- const line = rawLine.trim();
158
- if (line.length === 0) continue;
159
- let row;
160
- try { row = JSON.parse(line); } catch { continue; }
161
- if (!row || typeof row !== "object" || Array.isArray(row)) continue;
162
- if (row.maturity === "lifted-to-enforcement" || typeof row.superseded_by === "string") continue;
163
- const type = typeof row.type === "string" ? row.type : "";
164
- const trigger = typeof row.trigger === "string" ? row.trigger : "";
165
- const action = typeof row.action === "string" ? row.action : "";
166
- if (type.length === 0 || trigger.length === 0 || action.length === 0) continue;
167
- const key = type + "||" + normalize(trigger) + "||" + normalize(action);
168
- const frequency = Number.isInteger(row.frequency) && row.frequency > 0 ? Math.floor(row.frequency) : 1;
169
- const lastSeen = typeof row.last_seen_ts === "string" ? row.last_seen_ts : "";
170
- let bucket = buckets.get(key);
171
- if (!bucket) {
172
- bucket = {
173
- trigger,
174
- action,
175
- recurrence: frequency,
176
- entryCount: 1,
177
- severity: typeof row.severity === "string" ? row.severity : undefined,
178
- lastSeenTs: lastSeen,
179
- types: new Set([type]),
180
- maturity: new Set([typeof row.maturity === "string" ? row.maturity : "raw"])
181
- };
182
- buckets.set(key, bucket);
183
- continue;
184
- }
185
- bucket.recurrence += frequency;
186
- bucket.entryCount += 1;
187
- bucket.types.add(type);
188
- bucket.maturity.add(typeof row.maturity === "string" ? row.maturity : "raw");
189
- if (row.severity === "critical") {
190
- bucket.severity = "critical";
191
- } else if (row.severity === "important" && bucket.severity !== "critical") {
192
- bucket.severity = "important";
193
- }
194
- if (lastSeen && Date.parse(lastSeen) > Date.parse(bucket.lastSeenTs || "0")) {
195
- bucket.lastSeenTs = lastSeen;
196
- }
197
- }
198
- const ready = [];
199
- for (const bucket of buckets.values()) {
200
- const criticalOverride = bucket.severity === "critical";
201
- const meetsRecurrence = bucket.recurrence >= threshold;
202
- if (!criticalOverride && !meetsRecurrence) continue;
203
- ready.push({
204
- trigger: bucket.trigger,
205
- action: bucket.action,
206
- recurrence: bucket.recurrence,
207
- entryCount: bucket.entryCount,
208
- qualification: criticalOverride && !meetsRecurrence ? "critical_override" : "recurrence",
209
- ...(bucket.severity ? { severity: bucket.severity } : {}),
210
- lastSeenTs: bucket.lastSeenTs,
211
- types: Array.from(bucket.types).sort(),
212
- maturity: Array.from(bucket.maturity).sort()
213
- });
214
- }
215
- ready.sort((a, b) => {
216
- const sevDiff = severityWeight(b.severity) - severityWeight(a.severity);
217
- if (sevDiff !== 0) return sevDiff;
218
- if (b.recurrence !== a.recurrence) return b.recurrence - a.recurrence;
219
- const recencyDiff = Date.parse(b.lastSeenTs || "0") - Date.parse(a.lastSeenTs || "0");
220
- if (!Number.isNaN(recencyDiff) && recencyDiff !== 0) return recencyDiff;
221
- return String(a.trigger).localeCompare(String(b.trigger));
222
- });
223
- return {
224
- schemaVersion: 2,
225
- threshold,
226
- baseThreshold,
227
- ...(archivedRunsCount !== undefined ? { archivedRunsCount } : {}),
228
- smallProjectRelaxationApplied,
229
- clusterCount: buckets.size,
230
- readyCount: ready.length,
231
- ready: ready.slice(0, maxReady),
232
- lastUpdatedAt: normalizeCompoundLastUpdatedAt(new Date())
233
- };
234
- }
235
- `;
236
- /**
237
- * Inline mirror of `src/tdd-cycle.ts::computeRalphLoopStatus`.
238
- *
239
- * Parity enforced by
240
- * `tests/unit/ralph-loop-parity.test.ts::ralph-loop parity`.
241
- *
242
- * Signature contract:
243
- * async function computeRalphLoopStatusInline(stateDir, runId) -> RalphLoopStatus
244
- */
245
- export const RALPH_LOOP_INLINE_SOURCE = `
246
- async function computeRalphLoopStatusInline(stateDir, runId) {
247
- const filePath = path.join(stateDir, "tdd-cycle-log.jsonl");
248
- const raw = await readTextFile(filePath, "");
249
- const sliceMap = new Map();
250
- const acClosed = new Set();
251
- const redOpenSlices = [];
252
- let loopIteration = 0;
253
- for (const rawLine of raw.split(/\\r?\\n/gu)) {
254
- const line = rawLine.trim();
255
- if (line.length === 0) continue;
256
- let row;
257
- try { row = JSON.parse(line); } catch { continue; }
258
- if (!row || typeof row !== "object" || Array.isArray(row)) continue;
259
- const rowRun = typeof row.runId === "string" && row.runId.length > 0 ? row.runId : runId;
260
- if (rowRun !== runId) continue;
261
- const slice = typeof row.slice === "string" && row.slice.length > 0 ? row.slice : "S-unknown";
262
- let state = sliceMap.get(slice);
263
- if (!state) {
264
- state = { slice, redCount: 0, greenCount: 0, refactorCount: 0, redOpen: false, acIds: [] };
265
- sliceMap.set(slice, state);
266
- }
267
- const exitCode = typeof row.exitCode === "number" ? row.exitCode : undefined;
268
- if (row.phase === "red") {
269
- state.redCount += 1;
270
- if (exitCode !== undefined && exitCode !== 0) state.redOpen = true;
271
- } else if (row.phase === "green") {
272
- state.greenCount += 1;
273
- state.redOpen = false;
274
- loopIteration += 1;
275
- if (Array.isArray(row.acIds)) {
276
- for (const acId of row.acIds) {
277
- if (typeof acId !== "string" || acId.length === 0) continue;
278
- acClosed.add(acId);
279
- if (!state.acIds.includes(acId)) state.acIds.push(acId);
280
- }
281
- }
282
- } else if (row.phase === "refactor") {
283
- state.refactorCount += 1;
284
- }
285
- }
286
- for (const state of sliceMap.values()) {
287
- if (state.redOpen) redOpenSlices.push(state.slice);
288
- }
289
- const slices = Array.from(sliceMap.values()).sort((a, b) => a.slice.localeCompare(b.slice, "en"));
290
- return {
291
- schemaVersion: 1,
292
- runId,
293
- loopIteration,
294
- redOpen: redOpenSlices.length > 0,
295
- redOpenSlices,
296
- acClosed: Array.from(acClosed).sort(),
297
- sliceCount: slices.length,
298
- slices,
299
- lastUpdatedAt: new Date().toISOString()
300
- };
301
- }
302
- `;
@@ -1,8 +0,0 @@
1
- import { type IdeateFrameId } from "./ideate-frames.js";
2
- export interface IdeateCommandOptions {
3
- frameIds?: readonly IdeateFrameId[];
4
- mode?: "repo-grounded" | "elsewhere-software" | "elsewhere-non-software" | "narrow";
5
- }
6
- export declare function minimumDistinctIdeateFrames(frameCount: number, mode?: IdeateCommandOptions["mode"]): number;
7
- export declare function ideateCommandContract(options?: IdeateCommandOptions): string;
8
- export declare function ideateCommandSkillMarkdown(options?: IdeateCommandOptions): string;