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.
- package/README.md +24 -18
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +289 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +354 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +183 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +99 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +125 -0
- package/dist/artifact-linter/shared.d.ts +247 -0
- package/dist/artifact-linter/shared.js +1517 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +82 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +130 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +198 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +4 -159
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +10 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +15 -13
- package/dist/content/core-agents.d.ts +46 -29
- package/dist/content/core-agents.js +216 -82
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- package/dist/content/diff-command.js +1 -1
- package/dist/content/examples.d.ts +0 -3
- package/dist/content/examples.js +197 -752
- package/dist/content/harness-doc.js +20 -2
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +11 -27
- package/dist/content/meta-skill.js +7 -7
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +163 -95
- package/dist/content/opencode-plugin.js +15 -29
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/runtime-shared-snippets.d.ts +8 -0
- package/dist/content/runtime-shared-snippets.js +80 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.d.ts +1 -0
- package/dist/content/skills.js +69 -7
- package/dist/content/stage-schema.js +147 -61
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +7 -4
- package/dist/content/stages/review.js +12 -12
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +3 -3
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.js +11 -10
- package/dist/content/status-command.js +5 -5
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +65 -81
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +187 -154
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +28 -99
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -23
- package/dist/gate-evidence.js +111 -153
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +48 -19
- package/dist/install.js +190 -32
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +479 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +161 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +5 -28
- package/dist/knowledge-store.js +57 -84
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +7 -9
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +13 -16
- package/dist/run-persistence.js +20 -15
- package/dist/runtime/run-hook.entry.d.ts +3 -0
- package/dist/runtime/run-hook.entry.js +5 -0
- package/dist/runtime/run-hook.mjs +9477 -0
- package/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -83
- package/dist/content/hook-inline-snippets.js +0 -302
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-command.js +0 -315
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-frames.js +0 -140
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/ideate-ranking.js +0 -65
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2201
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
|
@@ -5,7 +5,7 @@ interface InternalIo {
|
|
|
5
5
|
stderr: Writable;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Count archived runs as sub-directories under `.cclaw/
|
|
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, "
|
|
45
|
+
return path.join(projectRoot, RUNTIME_ROOT, "archive");
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* Count archived runs as sub-directories under `.cclaw/
|
|
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,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,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
|
|
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,
|
|
@@ -2,29 +2,21 @@ import { type FlowStage } from "./types.js";
|
|
|
2
2
|
export type KnowledgeEntryType = "rule" | "pattern" | "lesson" | "compound";
|
|
3
3
|
export type KnowledgeEntryConfidence = "high" | "medium" | "low";
|
|
4
4
|
export type KnowledgeEntrySeverity = "critical" | "important" | "suggestion";
|
|
5
|
-
export type
|
|
6
|
-
export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
|
|
7
|
-
export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "ideate" | "manual";
|
|
5
|
+
export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "idea" | "manual";
|
|
8
6
|
export interface KnowledgeEntry {
|
|
9
7
|
type: KnowledgeEntryType;
|
|
10
8
|
trigger: string;
|
|
11
9
|
action: string;
|
|
12
10
|
confidence: KnowledgeEntryConfidence;
|
|
13
11
|
severity?: KnowledgeEntrySeverity;
|
|
14
|
-
domain: string | null;
|
|
15
12
|
stage: FlowStage | null;
|
|
16
13
|
origin_stage: FlowStage | null;
|
|
17
|
-
origin_run: string | null;
|
|
18
14
|
frequency: number;
|
|
19
|
-
universality: KnowledgeEntryUniversality;
|
|
20
|
-
maturity: KnowledgeEntryMaturity;
|
|
21
15
|
created: string;
|
|
22
16
|
first_seen_ts: string;
|
|
23
17
|
last_seen_ts: string;
|
|
24
18
|
project: string | null;
|
|
25
19
|
source?: KnowledgeEntrySource | null;
|
|
26
|
-
supersedes?: string[];
|
|
27
|
-
superseded_by?: string;
|
|
28
20
|
}
|
|
29
21
|
export interface KnowledgeSeedEntry {
|
|
30
22
|
type: KnowledgeEntryType;
|
|
@@ -32,27 +24,18 @@ export interface KnowledgeSeedEntry {
|
|
|
32
24
|
action: string;
|
|
33
25
|
confidence: KnowledgeEntryConfidence;
|
|
34
26
|
severity?: KnowledgeEntrySeverity;
|
|
35
|
-
domain?: string | null;
|
|
36
27
|
stage?: FlowStage | null;
|
|
37
28
|
origin_stage?: FlowStage | null;
|
|
38
|
-
origin_run?: string | null;
|
|
39
|
-
/** @deprecated Use `origin_run`. Accepted only for legacy JSONL/backfill inputs. */
|
|
40
|
-
origin_feature?: string | null;
|
|
41
29
|
frequency?: number;
|
|
42
|
-
universality?: KnowledgeEntryUniversality;
|
|
43
|
-
maturity?: KnowledgeEntryMaturity;
|
|
44
30
|
created?: string;
|
|
45
31
|
first_seen_ts?: string;
|
|
46
32
|
last_seen_ts?: string;
|
|
47
33
|
project?: string | null;
|
|
48
34
|
source?: KnowledgeEntrySource | null;
|
|
49
|
-
supersedes?: string[];
|
|
50
|
-
superseded_by?: string;
|
|
51
35
|
}
|
|
52
36
|
export interface AppendKnowledgeDefaults {
|
|
53
37
|
stage?: FlowStage | null;
|
|
54
38
|
originStage?: FlowStage | null;
|
|
55
|
-
originRun?: string | null;
|
|
56
39
|
project?: string | null;
|
|
57
40
|
source?: KnowledgeEntrySource | null;
|
|
58
41
|
nowIso?: string;
|
|
@@ -100,8 +83,6 @@ export interface CompoundReadinessCluster {
|
|
|
100
83
|
lastSeenTs: string;
|
|
101
84
|
/** Entry types observed (rule/pattern/lesson/compound). */
|
|
102
85
|
types: KnowledgeEntryType[];
|
|
103
|
-
/** Distinct maturity values observed across the cluster. */
|
|
104
|
-
maturity: KnowledgeEntryMaturity[];
|
|
105
86
|
}
|
|
106
87
|
export interface CompoundReadiness {
|
|
107
88
|
schemaVersion: 2;
|
|
@@ -143,7 +124,7 @@ export interface ComputeCompoundReadinessOptions {
|
|
|
143
124
|
maxReady?: number;
|
|
144
125
|
now?: Date;
|
|
145
126
|
/**
|
|
146
|
-
* Count of archived runs under `.cclaw/
|
|
127
|
+
* Count of archived runs under `.cclaw/archive/`. When supplied and
|
|
147
128
|
* `< SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD`, the effective threshold
|
|
148
129
|
* is lowered to `min(threshold, SMALL_PROJECT_RECURRENCE_THRESHOLD)`.
|
|
149
130
|
* Matches the rule documented in `docs/config.md`.
|
|
@@ -169,15 +150,11 @@ export declare function effectiveCompoundThreshold(baseThreshold: number, archiv
|
|
|
169
150
|
*
|
|
170
151
|
* Clustering key: `(type, normalizeText(trigger), normalizeText(action))`
|
|
171
152
|
* which mirrors the compound readiness clustering in runtime state.
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* as ready.
|
|
153
|
+
* The readiness surface intentionally stays simple: no maturity/supersede
|
|
154
|
+
* suppression logic in this pass.
|
|
175
155
|
*/
|
|
176
156
|
export declare function computeCompoundReadiness(entries: KnowledgeEntry[], options?: ComputeCompoundReadinessOptions): CompoundReadiness;
|
|
177
|
-
export
|
|
178
|
-
allowLegacyOriginFeature?: boolean;
|
|
179
|
-
}
|
|
180
|
-
export declare function validateKnowledgeEntry(entry: unknown, options?: ValidateKnowledgeEntryOptions): {
|
|
157
|
+
export declare function validateKnowledgeEntry(entry: unknown): {
|
|
181
158
|
ok: boolean;
|
|
182
159
|
errors: string[];
|
|
183
160
|
};
|