cclaw-cli 0.51.29 → 0.55.2

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 (151) hide show
  1. package/README.md +22 -16
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +245 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +323 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +162 -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 +65 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +115 -0
  14. package/dist/artifact-linter/shared.d.ts +246 -0
  15. package/dist/artifact-linter/shared.js +1488 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +46 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +108 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +124 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +2 -18
  25. package/dist/cli.js +8 -246
  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 +9 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.js +13 -10
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +51 -7
  36. package/dist/content/decision-protocol.d.ts +1 -1
  37. package/dist/content/decision-protocol.js +1 -1
  38. package/dist/content/examples.js +6 -6
  39. package/dist/content/harness-doc.js +20 -2
  40. package/dist/content/hook-inline-snippets.d.ts +17 -4
  41. package/dist/content/hook-inline-snippets.js +218 -5
  42. package/dist/content/hook-manifest.d.ts +2 -2
  43. package/dist/content/hook-manifest.js +2 -2
  44. package/dist/content/hooks.d.ts +1 -0
  45. package/dist/content/hooks.js +32 -137
  46. package/dist/content/idea-command.d.ts +8 -0
  47. package/dist/content/{ideate-command.js → idea-command.js} +57 -50
  48. package/dist/content/idea-frames.d.ts +31 -0
  49. package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
  50. package/dist/content/idea-ranking.d.ts +25 -0
  51. package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
  52. package/dist/content/iron-laws.d.ts +0 -1
  53. package/dist/content/iron-laws.js +31 -16
  54. package/dist/content/learnings.js +1 -1
  55. package/dist/content/meta-skill.js +11 -13
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +45 -11
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/session-hooks.js +1 -1
  60. package/dist/content/skills.js +19 -7
  61. package/dist/content/stage-command.js +1 -1
  62. package/dist/content/stage-schema.js +44 -2
  63. package/dist/content/stages/_lint-metadata/index.js +26 -2
  64. package/dist/content/stages/brainstorm.js +13 -7
  65. package/dist/content/stages/design.js +16 -11
  66. package/dist/content/stages/plan.js +9 -6
  67. package/dist/content/stages/review.js +4 -4
  68. package/dist/content/stages/schema-types.d.ts +1 -1
  69. package/dist/content/stages/scope.js +15 -12
  70. package/dist/content/stages/ship.js +2 -2
  71. package/dist/content/stages/spec.js +9 -3
  72. package/dist/content/stages/tdd.js +14 -4
  73. package/dist/content/start-command.d.ts +2 -2
  74. package/dist/content/start-command.js +24 -21
  75. package/dist/content/status-command.js +8 -8
  76. package/dist/content/subagents.js +61 -7
  77. package/dist/content/templates.d.ts +1 -1
  78. package/dist/content/templates.js +104 -152
  79. package/dist/content/tree-command.js +2 -2
  80. package/dist/content/utility-skills.d.ts +2 -2
  81. package/dist/content/utility-skills.js +2 -2
  82. package/dist/content/view-command.js +4 -2
  83. package/dist/delegation.d.ts +2 -0
  84. package/dist/delegation.js +2 -1
  85. package/dist/early-loop.d.ts +66 -0
  86. package/dist/early-loop.js +275 -0
  87. package/dist/flow-state.d.ts +1 -1
  88. package/dist/flow-state.js +1 -1
  89. package/dist/gate-evidence.d.ts +8 -0
  90. package/dist/gate-evidence.js +141 -5
  91. package/dist/harness-adapters.d.ts +2 -2
  92. package/dist/harness-adapters.js +54 -122
  93. package/dist/harness-selection.d.ts +31 -0
  94. package/dist/harness-selection.js +214 -0
  95. package/dist/install.js +166 -38
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +480 -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 +170 -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 +3 -8
  125. package/dist/knowledge-store.js +16 -29
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +5 -7
  128. package/dist/run-archive.d.ts +1 -1
  129. package/dist/run-archive.js +16 -16
  130. package/dist/run-persistence.js +112 -12
  131. package/dist/tdd-cycle.d.ts +3 -3
  132. package/dist/tdd-cycle.js +1 -1
  133. package/dist/types.d.ts +18 -10
  134. package/package.json +1 -1
  135. package/dist/content/finish-command.d.ts +0 -2
  136. package/dist/content/finish-command.js +0 -26
  137. package/dist/content/ideate-command.d.ts +0 -8
  138. package/dist/content/ideate-frames.d.ts +0 -31
  139. package/dist/content/ideate-ranking.d.ts +0 -25
  140. package/dist/content/next-command.d.ts +0 -20
  141. package/dist/content/next-command.js +0 -298
  142. package/dist/content/seed-shelf.d.ts +0 -36
  143. package/dist/content/seed-shelf.js +0 -301
  144. package/dist/content/stage-common-guidance.d.ts +0 -1
  145. package/dist/content/stage-common-guidance.js +0 -106
  146. package/dist/doctor-registry.d.ts +0 -10
  147. package/dist/doctor-registry.js +0 -186
  148. package/dist/doctor.d.ts +0 -17
  149. package/dist/doctor.js +0 -2206
  150. package/dist/internal/hook-manifest.d.ts +0 -16
  151. package/dist/internal/hook-manifest.js +0 -77
@@ -5,7 +5,7 @@ interface InternalIo {
5
5
  stderr: Writable;
6
6
  }
7
7
  /**
8
- * Count archived runs as sub-directories under `.cclaw/runs/`. Missing
8
+ * Count archived runs as sub-directories under `.cclaw/archive/`. Missing
9
9
  * dir / ENOENT is interpreted as zero — callers should NOT conflate
10
10
  * that with "unknown" (undefined); we only return undefined on
11
11
  * unexpected errors so the caller can choose to skip the relaxation
@@ -42,10 +42,10 @@ function stateDir(projectRoot) {
42
42
  return path.join(projectRoot, RUNTIME_ROOT, "state");
43
43
  }
44
44
  function archiveRunsDir(projectRoot) {
45
- return path.join(projectRoot, RUNTIME_ROOT, "runs");
45
+ return path.join(projectRoot, RUNTIME_ROOT, "archive");
46
46
  }
47
47
  /**
48
- * Count archived runs as sub-directories under `.cclaw/runs/`. Missing
48
+ * Count archived runs as sub-directories under `.cclaw/archive/`. Missing
49
49
  * dir / ENOENT is interpreted as zero — callers should NOT conflate
50
50
  * that with "unknown" (undefined); we only return undefined on
51
51
  * unexpected errors so the caller can choose to skip the relaxation
@@ -0,0 +1,7 @@
1
+ import type { Writable } from "node:stream";
2
+ interface InternalIo {
3
+ stdout: Writable;
4
+ stderr: Writable;
5
+ }
6
+ export declare function runEarlyLoopStatusCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
7
+ export {};
@@ -0,0 +1,90 @@
1
+ import path from "node:path";
2
+ import { readConfig } from "../config.js";
3
+ import { RUNTIME_ROOT } from "../constants.js";
4
+ import { computeEarlyLoopStatus, formatEarlyLoopStatusLine, isEarlyLoopStage } from "../early-loop.js";
5
+ import { writeFileSafe } from "../fs-utils.js";
6
+ import { readFlowState } from "../runs.js";
7
+ function parseArgs(tokens) {
8
+ const args = { json: false, quiet: false, write: true };
9
+ for (let index = 0; index < tokens.length; index += 1) {
10
+ const token = tokens[index];
11
+ const nextToken = tokens[index + 1];
12
+ if (token === "--json") {
13
+ args.json = true;
14
+ continue;
15
+ }
16
+ if (token === "--quiet") {
17
+ args.quiet = true;
18
+ continue;
19
+ }
20
+ if (token === "--write") {
21
+ args.write = true;
22
+ continue;
23
+ }
24
+ if (token === "--no-write") {
25
+ args.write = false;
26
+ continue;
27
+ }
28
+ if (token === "--stage") {
29
+ if (!nextToken || nextToken.startsWith("--")) {
30
+ throw new Error("--stage requires a value: brainstorm | scope | design");
31
+ }
32
+ if (!isEarlyLoopStage(nextToken)) {
33
+ throw new Error("--stage must be one of: brainstorm, scope, design");
34
+ }
35
+ args.stage = nextToken;
36
+ index += 1;
37
+ continue;
38
+ }
39
+ if (token.startsWith("--stage=")) {
40
+ const stageRaw = token.slice("--stage=".length);
41
+ if (!isEarlyLoopStage(stageRaw)) {
42
+ throw new Error("--stage must be one of: brainstorm, scope, design");
43
+ }
44
+ args.stage = stageRaw;
45
+ continue;
46
+ }
47
+ if (token === "--run-id") {
48
+ if (!nextToken || nextToken.startsWith("--")) {
49
+ throw new Error("--run-id requires a value.");
50
+ }
51
+ args.runId = nextToken.trim();
52
+ index += 1;
53
+ continue;
54
+ }
55
+ if (token.startsWith("--run-id=")) {
56
+ args.runId = token.slice("--run-id=".length).trim();
57
+ continue;
58
+ }
59
+ throw new Error(`Unknown early-loop-status flag: ${token}`);
60
+ }
61
+ return args;
62
+ }
63
+ function stateDir(projectRoot) {
64
+ return path.join(projectRoot, RUNTIME_ROOT, "state");
65
+ }
66
+ export async function runEarlyLoopStatusCommand(projectRoot, argv, io) {
67
+ const args = parseArgs(argv);
68
+ const flow = await readFlowState(projectRoot).catch(() => null);
69
+ const config = await readConfig(projectRoot).catch(() => null);
70
+ const stage = args.stage ?? flow?.currentStage;
71
+ if (!isEarlyLoopStage(stage)) {
72
+ io.stderr.write("cclaw internal early-loop-status: current stage is not an early-loop stage. Pass --stage=brainstorm|scope|design.\n");
73
+ return 1;
74
+ }
75
+ const runId = (args.runId ?? flow?.activeRunId ?? "active").trim() || "active";
76
+ const status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl"), { maxIterations: config?.earlyLoop?.maxIterations });
77
+ if (args.write) {
78
+ const target = path.join(stateDir(projectRoot), "early-loop.json");
79
+ await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
80
+ }
81
+ if (!args.quiet) {
82
+ if (args.json) {
83
+ io.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
84
+ }
85
+ else {
86
+ io.stdout.write(`${formatEarlyLoopStatusLine(status)}\n`);
87
+ }
88
+ }
89
+ return 0;
90
+ }
@@ -0,0 +1,7 @@
1
+ import type { Writable } from "node:stream";
2
+ interface InternalIo {
3
+ stdout: Writable;
4
+ stderr: Writable;
5
+ }
6
+ export declare function runRuntimeIntegrityCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
7
+ export {};
@@ -0,0 +1,288 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readConfig } from "../config.js";
4
+ import { RUNTIME_ROOT } from "../constants.js";
5
+ import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "../codex-feature-flag.js";
6
+ import { exists } from "../fs-utils.js";
7
+ import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames } from "../harness-adapters.js";
8
+ import { validateHookDocument } from "../hook-schema.js";
9
+ import { MANAGED_RESOURCE_MANIFEST_REL_PATH, validateManagedResourceManifest } from "../managed-resources.js";
10
+ import { CorruptFlowStateError, readFlowState } from "../runs.js";
11
+ function parseArgs(tokens) {
12
+ const args = { json: false, quiet: false };
13
+ for (const token of tokens) {
14
+ if (token === "--json") {
15
+ args.json = true;
16
+ continue;
17
+ }
18
+ if (token === "--quiet") {
19
+ args.quiet = true;
20
+ continue;
21
+ }
22
+ throw new Error(`Unknown runtime-integrity flag: ${token}`);
23
+ }
24
+ return args;
25
+ }
26
+ function stripJsonCommentsOutsideStrings(input) {
27
+ let out = "";
28
+ let i = 0;
29
+ let inString = false;
30
+ let escape = false;
31
+ while (i < input.length) {
32
+ const c = input[i];
33
+ if (inString) {
34
+ out += c;
35
+ if (escape) {
36
+ escape = false;
37
+ }
38
+ else if (c === "\\") {
39
+ escape = true;
40
+ }
41
+ else if (c === "\"") {
42
+ inString = false;
43
+ }
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (c === "\"") {
48
+ inString = true;
49
+ out += c;
50
+ i += 1;
51
+ continue;
52
+ }
53
+ const next = input[i + 1];
54
+ if (c === "/" && next === "/") {
55
+ while (i < input.length && input[i] !== "\n" && input[i] !== "\r")
56
+ i += 1;
57
+ continue;
58
+ }
59
+ if (c === "/" && next === "*") {
60
+ i += 2;
61
+ while (i < input.length - 1 && !(input[i] === "*" && input[i + 1] === "/"))
62
+ i += 1;
63
+ i = Math.min(i + 2, input.length);
64
+ continue;
65
+ }
66
+ out += c;
67
+ i += 1;
68
+ }
69
+ return out;
70
+ }
71
+ function parseJsonWithRecovery(raw) {
72
+ try {
73
+ return JSON.parse(raw);
74
+ }
75
+ catch {
76
+ // Continue with comment/trailing-comma recovery.
77
+ }
78
+ try {
79
+ const normalized = stripJsonCommentsOutsideStrings(raw).replace(/,\s*([}\]])/gu, "$1");
80
+ return JSON.parse(normalized);
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ function okFinding(id, message) {
87
+ return {
88
+ id,
89
+ severity: "error",
90
+ ok: true,
91
+ message
92
+ };
93
+ }
94
+ function errorFinding(id, message, details) {
95
+ return {
96
+ id,
97
+ severity: "error",
98
+ ok: false,
99
+ message,
100
+ ...(details && details.length > 0 ? { details } : {})
101
+ };
102
+ }
103
+ function warningFinding(id, ok, message, details) {
104
+ return {
105
+ id,
106
+ severity: "warning",
107
+ ok,
108
+ message,
109
+ ...(details && details.length > 0 ? { details } : {})
110
+ };
111
+ }
112
+ async function checkStaleSentinel(projectRoot) {
113
+ const sentinelPath = path.join(projectRoot, RUNTIME_ROOT, "state", ".init-in-progress");
114
+ if (!(await exists(sentinelPath))) {
115
+ return warningFinding("stale_init_sentinel", true, "No stale init sentinel detected.");
116
+ }
117
+ let startedAt = "unknown time";
118
+ try {
119
+ const raw = await fs.readFile(sentinelPath, "utf8");
120
+ const parsed = JSON.parse(raw);
121
+ if (parsed && typeof parsed.startedAt === "string" && parsed.startedAt.trim().length > 0) {
122
+ startedAt = parsed.startedAt;
123
+ }
124
+ }
125
+ catch {
126
+ // best-effort parse only
127
+ }
128
+ return warningFinding("stale_init_sentinel", false, "Detected stale .init-in-progress sentinel from a previous interrupted sync/init run.", [`startedAt: ${startedAt}`, `path: ${sentinelPath}`]);
129
+ }
130
+ async function checkFlowState(projectRoot) {
131
+ try {
132
+ await readFlowState(projectRoot);
133
+ return okFinding("flow_state", "Flow state is readable.");
134
+ }
135
+ catch (error) {
136
+ if (error instanceof CorruptFlowStateError) {
137
+ return errorFinding("flow_state", "Corrupt flow-state detected.", [error.message]);
138
+ }
139
+ return errorFinding("flow_state", "Flow-state read failed.", [
140
+ error instanceof Error ? error.message : String(error)
141
+ ]);
142
+ }
143
+ }
144
+ async function checkManagedManifest(projectRoot) {
145
+ const manifestPath = path.join(projectRoot, MANAGED_RESOURCE_MANIFEST_REL_PATH);
146
+ if (!(await exists(manifestPath))) {
147
+ return errorFinding("managed_manifest", "Managed resource manifest is missing.", [`missing: ${manifestPath}`]);
148
+ }
149
+ const rawText = await fs.readFile(manifestPath, "utf8");
150
+ let parsed;
151
+ try {
152
+ parsed = JSON.parse(rawText);
153
+ }
154
+ catch (error) {
155
+ return errorFinding("managed_manifest", "Managed resource manifest is not valid JSON.", [error instanceof Error ? error.message : String(error)]);
156
+ }
157
+ const issues = validateManagedResourceManifest(parsed);
158
+ if (issues.length > 0) {
159
+ const detail = issues.slice(0, 12).map((issue) => {
160
+ const scope = issue.path ?? (issue.index !== undefined ? `resources[${issue.index}]` : "manifest");
161
+ return `${scope}.${issue.field}: ${issue.message}`;
162
+ });
163
+ return errorFinding("managed_manifest", "Managed resource manifest schema validation failed.", detail);
164
+ }
165
+ return okFinding("managed_manifest", "Managed resource manifest is valid.");
166
+ }
167
+ function hookFilePath(projectRoot, harness) {
168
+ if (harness === "claude")
169
+ return path.join(projectRoot, ".claude/hooks/hooks.json");
170
+ if (harness === "cursor")
171
+ return path.join(projectRoot, ".cursor/hooks.json");
172
+ return path.join(projectRoot, ".codex/hooks.json");
173
+ }
174
+ async function checkHookDocument(projectRoot, harness) {
175
+ const hookPath = hookFilePath(projectRoot, harness);
176
+ if (!(await exists(hookPath))) {
177
+ return errorFinding(`hook_document_${harness}`, `Hook document is missing for ${harness}.`, [`missing: ${hookPath}`]);
178
+ }
179
+ const raw = await fs.readFile(hookPath, "utf8");
180
+ const parsed = parseJsonWithRecovery(raw);
181
+ if (parsed === null) {
182
+ return errorFinding(`hook_document_${harness}`, `Hook document for ${harness} is unparseable JSON.`, [`path: ${hookPath}`]);
183
+ }
184
+ const validation = validateHookDocument(harness, parsed);
185
+ if (!validation.ok) {
186
+ return errorFinding(`hook_document_${harness}`, `Hook document for ${harness} is invalid.`, validation.errors);
187
+ }
188
+ return okFinding(`hook_document_${harness}`, `Hook document for ${harness} is valid.`);
189
+ }
190
+ async function checkHarnessShims(projectRoot, harnesses) {
191
+ const findings = [];
192
+ const expectedFiles = harnessShimFileNames();
193
+ const expectedSkillFolders = harnessShimSkillNames();
194
+ for (const harness of harnesses) {
195
+ const adapter = HARNESS_ADAPTERS[harness];
196
+ const base = path.join(projectRoot, adapter.commandDir);
197
+ const missing = [];
198
+ for (const fileName of expectedFiles) {
199
+ const target = adapter.shimKind === "skill"
200
+ ? path.join(base, fileName.replace(/\.md$/u, ""), "SKILL.md")
201
+ : path.join(base, fileName);
202
+ if (!(await exists(target))) {
203
+ missing.push(target);
204
+ }
205
+ }
206
+ if (adapter.shimKind === "skill") {
207
+ for (const folder of expectedSkillFolders) {
208
+ const target = path.join(base, folder, "SKILL.md");
209
+ if (!(await exists(target))) {
210
+ missing.push(target);
211
+ }
212
+ }
213
+ }
214
+ if (missing.length > 0) {
215
+ findings.push(errorFinding(`shim_drift_${harness}`, `Harness shim drift detected for ${harness}.`, missing));
216
+ }
217
+ else {
218
+ findings.push(okFinding(`shim_drift_${harness}`, `Harness shims for ${harness} are present.`));
219
+ }
220
+ }
221
+ return findings;
222
+ }
223
+ async function checkCodexHooksFlag(harnesses) {
224
+ if (!harnesses.includes("codex")) {
225
+ return warningFinding("codex_hooks_flag", true, "Codex harness is not enabled.");
226
+ }
227
+ const configTomlPath = codexConfigPath();
228
+ let existing;
229
+ try {
230
+ existing = await readCodexConfig(configTomlPath);
231
+ }
232
+ catch (error) {
233
+ return warningFinding("codex_hooks_flag", false, "Could not read Codex config.toml to validate codex_hooks.", [error instanceof Error ? error.message : String(error)]);
234
+ }
235
+ const state = classifyCodexHooksFlag(existing);
236
+ if (state === "enabled") {
237
+ return warningFinding("codex_hooks_flag", true, "Codex hooks feature flag is enabled.");
238
+ }
239
+ return warningFinding("codex_hooks_flag", false, "Codex hooks file is present, but [features] codex_hooks is not true in Codex config.", [`configPath: ${configTomlPath}`, `state: ${state}`]);
240
+ }
241
+ function buildReport(findings) {
242
+ const errors = findings.filter((finding) => !finding.ok && finding.severity === "error").length;
243
+ const warnings = findings.filter((finding) => !finding.ok && finding.severity === "warning").length;
244
+ return {
245
+ ok: errors === 0,
246
+ generatedAt: new Date().toISOString(),
247
+ findings,
248
+ summary: { errors, warnings }
249
+ };
250
+ }
251
+ function writeTextReport(io, report) {
252
+ io.stdout.write(`runtime-integrity: ${report.ok ? "ok" : "failed"}\n`);
253
+ io.stdout.write(`errors=${report.summary.errors} warnings=${report.summary.warnings}\n`);
254
+ for (const finding of report.findings) {
255
+ if (finding.ok)
256
+ continue;
257
+ io.stdout.write(`[${finding.severity}] ${finding.id}: ${finding.message}\n`);
258
+ for (const detail of finding.details ?? []) {
259
+ io.stdout.write(` - ${detail}\n`);
260
+ }
261
+ }
262
+ }
263
+ export async function runRuntimeIntegrityCommand(projectRoot, argv, io) {
264
+ const args = parseArgs(argv);
265
+ const config = await readConfig(projectRoot);
266
+ const harnesses = config.harnesses;
267
+ const findings = [];
268
+ findings.push(await checkStaleSentinel(projectRoot));
269
+ findings.push(await checkFlowState(projectRoot));
270
+ findings.push(await checkManagedManifest(projectRoot));
271
+ findings.push(...await checkHarnessShims(projectRoot, harnesses));
272
+ for (const harness of harnesses) {
273
+ if (harness === "claude" || harness === "cursor" || harness === "codex") {
274
+ findings.push(await checkHookDocument(projectRoot, harness));
275
+ }
276
+ }
277
+ findings.push(await checkCodexHooksFlag(harnesses));
278
+ const report = buildReport(findings);
279
+ if (!args.quiet) {
280
+ if (args.json) {
281
+ io.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
282
+ }
283
+ else {
284
+ writeTextReport(io, report);
285
+ }
286
+ }
287
+ return report.ok ? 0 : 1;
288
+ }
@@ -95,7 +95,7 @@ export async function runTddRedEvidenceCommand(projectRoot, tokens, io) {
95
95
  if (!effectiveRunId || effectiveRunId.trim().length === 0) {
96
96
  const reason = "tdd-red-evidence: cannot scope check — no --runId provided and " +
97
97
  "flow-state.json has no activeRunId. Pass --runId=<id> explicitly " +
98
- "or run `cclaw doctor` to reconcile state.";
98
+ "or run `npx cclaw-cli sync` to reconcile state.";
99
99
  if (!args.quiet) {
100
100
  io.stdout.write(`${JSON.stringify({
101
101
  ok: false,
@@ -4,7 +4,7 @@ export type KnowledgeEntryConfidence = "high" | "medium" | "low";
4
4
  export type KnowledgeEntrySeverity = "critical" | "important" | "suggestion";
5
5
  export type KnowledgeEntryUniversality = "project" | "personal" | "universal";
6
6
  export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
7
- export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "ideate" | "manual";
7
+ export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "idea" | "manual";
8
8
  export interface KnowledgeEntry {
9
9
  type: KnowledgeEntryType;
10
10
  trigger: string;
@@ -36,8 +36,6 @@ export interface KnowledgeSeedEntry {
36
36
  stage?: FlowStage | null;
37
37
  origin_stage?: FlowStage | null;
38
38
  origin_run?: string | null;
39
- /** @deprecated Use `origin_run`. Accepted only for legacy JSONL/backfill inputs. */
40
- origin_feature?: string | null;
41
39
  frequency?: number;
42
40
  universality?: KnowledgeEntryUniversality;
43
41
  maturity?: KnowledgeEntryMaturity;
@@ -143,7 +141,7 @@ export interface ComputeCompoundReadinessOptions {
143
141
  maxReady?: number;
144
142
  now?: Date;
145
143
  /**
146
- * Count of archived runs under `.cclaw/runs/`. When supplied and
144
+ * Count of archived runs under `.cclaw/archive/`. When supplied and
147
145
  * `< SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD`, the effective threshold
148
146
  * is lowered to `min(threshold, SMALL_PROJECT_RECURRENCE_THRESHOLD)`.
149
147
  * Matches the rule documented in `docs/config.md`.
@@ -174,10 +172,7 @@ export declare function effectiveCompoundThreshold(baseThreshold: number, archiv
174
172
  * as ready.
175
173
  */
176
174
  export declare function computeCompoundReadiness(entries: KnowledgeEntry[], options?: ComputeCompoundReadinessOptions): CompoundReadiness;
177
- export interface ValidateKnowledgeEntryOptions {
178
- allowLegacyOriginFeature?: boolean;
179
- }
180
- export declare function validateKnowledgeEntry(entry: unknown, options?: ValidateKnowledgeEntryOptions): {
175
+ export declare function validateKnowledgeEntry(entry: unknown): {
181
176
  ok: boolean;
182
177
  errors: string[];
183
178
  };
@@ -149,7 +149,7 @@ const KNOWLEDGE_SOURCE_SET = new Set([
149
149
  "stage",
150
150
  "retro",
151
151
  "compound",
152
- "ideate",
152
+ "idea",
153
153
  "manual"
154
154
  ]);
155
155
  const FLOW_STAGE_SET = new Set(FLOW_STAGES);
@@ -175,8 +175,8 @@ KNOWLEDGE_ALLOWED_KEYS.add("source");
175
175
  KNOWLEDGE_ALLOWED_KEYS.add("severity");
176
176
  KNOWLEDGE_ALLOWED_KEYS.add("supersedes");
177
177
  KNOWLEDGE_ALLOWED_KEYS.add("superseded_by");
178
- function keyAllowedInKnowledgeEntry(key, options) {
179
- return KNOWLEDGE_ALLOWED_KEYS.has(key) || (options.allowLegacyOriginFeature === true && key === "origin_feature");
178
+ function keyAllowedInKnowledgeEntry(key) {
179
+ return KNOWLEDGE_ALLOWED_KEYS.has(key);
180
180
  }
181
181
  function knowledgePath(projectRoot) {
182
182
  return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
@@ -219,13 +219,6 @@ function emptyKnowledgeSnapshot() {
219
219
  entryByIndex: new Map()
220
220
  };
221
221
  }
222
- function normalizeLegacyKnowledgeEntry(entry) {
223
- const { origin_feature: legacyOriginRun, ...rest } = entry;
224
- return {
225
- ...rest,
226
- origin_run: entry.origin_run ?? legacyOriginRun ?? null
227
- };
228
- }
229
222
  function parseKnowledgeSnapshot(raw) {
230
223
  const lines = stripBom(raw).split(/\r?\n/u);
231
224
  const entries = [];
@@ -238,12 +231,12 @@ function parseKnowledgeSnapshot(raw) {
238
231
  continue;
239
232
  try {
240
233
  const parsed = JSON.parse(trimmed);
241
- const validated = validateKnowledgeEntry(parsed, { allowLegacyOriginFeature: true });
234
+ const validated = validateKnowledgeEntry(parsed);
242
235
  if (!validated.ok) {
243
236
  malformedLines += 1;
244
237
  continue;
245
238
  }
246
- const entry = normalizeLegacyKnowledgeEntry(parsed);
239
+ const entry = parsed;
247
240
  entries.push(entry);
248
241
  const key = dedupeKey(entry);
249
242
  if (!keyToIndex.has(key)) {
@@ -296,24 +289,20 @@ function isNullableString(value) {
296
289
  function isNullableStage(value) {
297
290
  return value === null || (typeof value === "string" && FLOW_STAGE_SET.has(value));
298
291
  }
299
- export function validateKnowledgeEntry(entry, options = {}) {
292
+ export function validateKnowledgeEntry(entry) {
300
293
  const errors = [];
301
294
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
302
295
  return { ok: false, errors: ["Knowledge entry must be a JSON object."] };
303
296
  }
304
297
  const obj = entry;
305
298
  for (const key of Object.keys(obj)) {
306
- if (!keyAllowedInKnowledgeEntry(key, options)) {
299
+ if (!keyAllowedInKnowledgeEntry(key)) {
307
300
  errors.push(`Unknown key "${key}" in knowledge entry.`);
308
301
  }
309
302
  }
310
303
  for (const key of KNOWLEDGE_REQUIRED_KEYS) {
311
304
  if (!Object.prototype.hasOwnProperty.call(obj, key)) {
312
- if (key !== "origin_run" ||
313
- options.allowLegacyOriginFeature !== true ||
314
- !Object.prototype.hasOwnProperty.call(obj, "origin_feature")) {
315
- errors.push(`Missing required key "${key}".`);
316
- }
305
+ errors.push(`Missing required key "${key}".`);
317
306
  }
318
307
  }
319
308
  if (!KNOWLEDGE_TYPE_SET.has(obj.type)) {
@@ -341,11 +330,7 @@ export function validateKnowledgeEntry(entry, options = {}) {
341
330
  if (!isNullableStage(obj.origin_stage)) {
342
331
  errors.push(`origin_stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
343
332
  }
344
- const originRun = Object.prototype.hasOwnProperty.call(obj, "origin_run")
345
- ? obj.origin_run
346
- : options.allowLegacyOriginFeature === true
347
- ? obj.origin_feature
348
- : undefined;
333
+ const originRun = obj.origin_run;
349
334
  if (!isNullableString(originRun)) {
350
335
  errors.push("origin_run must be string or null.");
351
336
  }
@@ -380,10 +365,12 @@ export function validateKnowledgeEntry(entry, options = {}) {
380
365
  (typeof obj.superseded_by !== "string" || obj.superseded_by.trim().length === 0)) {
381
366
  errors.push("superseded_by must be a non-empty string when present.");
382
367
  }
383
- if (obj.source !== undefined &&
384
- obj.source !== null &&
385
- (typeof obj.source !== "string" || !KNOWLEDGE_SOURCE_SET.has(obj.source))) {
386
- errors.push("source must be one of: stage, retro, compound, ideate, manual, or null.");
368
+ const sourceAllowed = obj.source === undefined ||
369
+ obj.source === null ||
370
+ (typeof obj.source === "string" &&
371
+ KNOWLEDGE_SOURCE_SET.has(obj.source));
372
+ if (!sourceAllowed) {
373
+ errors.push("source must be one of: stage, retro, compound, idea, manual, or null.");
387
374
  }
388
375
  return { ok: errors.length === 0, errors };
389
376
  }
@@ -391,7 +378,7 @@ export function materializeKnowledgeEntry(seed, defaults = {}) {
391
378
  const now = normalizeUtcIso(defaults.nowIso ?? nowUtcIso());
392
379
  const stage = seed.stage ?? defaults.stage ?? null;
393
380
  const originStage = seed.origin_stage ?? defaults.originStage ?? stage ?? null;
394
- const originRun = seed.origin_run ?? seed.origin_feature ?? defaults.originRun ?? null;
381
+ const originRun = seed.origin_run ?? defaults.originRun ?? null;
395
382
  const source = seed.source ?? defaults.source ?? null;
396
383
  const entry = {
397
384
  type: seed.type,
@@ -60,7 +60,7 @@ export function isManagedGeneratedPath(relPath) {
60
60
  return false;
61
61
  if (relPath.startsWith(`${RUNTIME_ROOT}/artifacts/`))
62
62
  return false;
63
- if (relPath.startsWith(`${RUNTIME_ROOT}/runs/`))
63
+ if (relPath.startsWith(`${RUNTIME_ROOT}/archive/`))
64
64
  return false;
65
65
  if (relPath === `${RUNTIME_ROOT}/state/flow-state.json`)
66
66
  return false;
@@ -209,7 +209,29 @@ export class ManagedResourceSession {
209
209
  }
210
210
  }
211
211
  static async create(options) {
212
- const previous = await readManagedResourceManifest(options.projectRoot).catch(() => null);
212
+ const manifestPath = path.join(options.projectRoot, MANAGED_RESOURCE_MANIFEST_REL_PATH);
213
+ let previous = null;
214
+ if (await exists(manifestPath)) {
215
+ let raw;
216
+ try {
217
+ raw = JSON.parse(await fs.readFile(manifestPath, "utf8"));
218
+ }
219
+ catch (error) {
220
+ throw new Error(`[sync fail-fast] Managed resource manifest is corrupt JSON (${MANAGED_RESOURCE_MANIFEST_REL_PATH}): ${error instanceof Error ? error.message : String(error)}. Fix/remove the manifest and rerun \`npx cclaw-cli sync\`.`);
221
+ }
222
+ const issues = validateManagedResourceManifest(raw);
223
+ if (issues.length > 0) {
224
+ const detail = issues.slice(0, 12)
225
+ .map((issue) => {
226
+ const scope = issue.path ?? (issue.index !== undefined ? `resources[${issue.index}]` : "manifest");
227
+ return `${scope}.${issue.field}: ${issue.message}`;
228
+ })
229
+ .join("; ");
230
+ throw new Error(`[sync fail-fast] Managed resource manifest is malformed (${MANAGED_RESOURCE_MANIFEST_REL_PATH}): ${detail}. ` +
231
+ `Fix/remove the manifest and rerun \`npx cclaw-cli sync\`.`);
232
+ }
233
+ previous = await readManagedResourceManifest(options.projectRoot).catch(() => null);
234
+ }
213
235
  return new ManagedResourceSession(options, previous);
214
236
  }
215
237
  shouldManage(filePath) {
package/dist/policy.js CHANGED
@@ -62,11 +62,9 @@ export async function policyChecks(projectRoot, options = {}) {
62
62
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Manual Actions", name: "utility_skill:learnings:manual_actions" },
63
63
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:learnings:hard_gate" },
64
64
  { file: runtimeFile("commands/start.md"), needle: "## Algorithm", name: "utility_command:start:algorithm" },
65
- { file: runtimeFile("commands/next.md"), needle: "## Algorithm", name: "utility_command:next:algorithm" },
66
- { file: runtimeFile("commands/next.md"), needle: "closeout.shipSubstate", name: "utility_command:next:closeout_chain" },
67
- { file: runtimeFile("commands/ideate.md"), needle: "## Algorithm", name: "utility_command:ideate:algorithm" },
68
- { file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## Protocol", name: "utility_skill:ideate:protocol" },
69
- { file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:ideate:hard_gate" },
65
+ { file: runtimeFile("commands/idea.md"), needle: "## Algorithm", name: "utility_command:idea:algorithm" },
66
+ { file: runtimeFile("skills/flow-idea/SKILL.md"), needle: "## Protocol", name: "utility_skill:idea:protocol" },
67
+ { file: runtimeFile("skills/flow-idea/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:idea:hard_gate" },
70
68
  { file: runtimeFile("commands/view.md"), needle: "## Routing", name: "utility_command:view:routing" },
71
69
  { file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Status Subcommand", name: "utility_skill:view:status_section" },
72
70
  { file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Tree Subcommand", name: "utility_skill:view:tree_section" },
@@ -96,7 +94,7 @@ export async function policyChecks(projectRoot, options = {}) {
96
94
  { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Protocol Behavior", name: "meta_skill:protocol_behavior" },
97
95
  { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure guardrails", name: "meta_skill:failure_guardrails" },
98
96
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
99
- { file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "## Shared Stage Guidance", name: "stage_skill:shared_guidance_inline" },
97
+ { file: runtimeFile("skills/brainstorm/SKILL.md"), needle: "## Shared Stage Guidance", name: "stage_skill:shared_guidance_inline" },
100
98
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "activeRunId", name: "hooks:session_start:active_run" },
101
99
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "write_to_cclaw_runtime", name: "hooks:guard:risky_write_advisory" },
102
100
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
@@ -127,7 +125,7 @@ export async function policyChecks(projectRoot, options = {}) {
127
125
  });
128
126
  utilitySkillChecks.push({
129
127
  file: ".cursor/rules/cclaw-workflow.mdc",
130
- needle: "/cc-next",
128
+ needle: "/cc",
131
129
  name: "rules:cursor:next_command_guidance"
132
130
  });
133
131
  }
@@ -56,6 +56,6 @@ export declare function archiveRun(projectRoot: string, runName?: string, option
56
56
  * Counts entries in the canonical JSONL knowledge store. An "active" entry is one
57
57
  * non-empty line that parses as JSON with the required `type` field belonging to the
58
58
  * allowed set. Malformed lines are ignored (not counted) but do not throw so that a
59
- * hand-edited file cannot break doctor/archive flows.
59
+ * hand-edited file cannot break sync/archive flows.
60
60
  */
61
61
  export declare function countActiveKnowledgeEntries(text: string): number;