cclaw-cli 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact-linter.d.ts +20 -0
- package/dist/artifact-linter.js +368 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -2
- package/dist/config.d.ts +4 -4
- package/dist/config.js +56 -5
- package/dist/constants.d.ts +4 -4
- package/dist/constants.js +6 -3
- package/dist/content/autoplan.js +51 -4
- package/dist/content/contexts.d.ts +9 -0
- package/dist/content/contexts.js +65 -0
- package/dist/content/hooks.d.ts +6 -2
- package/dist/content/hooks.js +448 -16
- package/dist/content/meta-skill.js +26 -0
- package/dist/content/next-command.d.ts +9 -0
- package/dist/content/next-command.js +138 -0
- package/dist/content/observe.d.ts +5 -1
- package/dist/content/observe.js +506 -24
- package/dist/content/skills.js +126 -0
- package/dist/content/stage-schema.d.ts +7 -0
- package/dist/content/stage-schema.js +70 -12
- package/dist/content/subagents.js +33 -0
- package/dist/content/templates.d.ts +1 -0
- package/dist/content/templates.js +182 -77
- package/dist/content/utility-skills.d.ts +5 -1
- package/dist/content/utility-skills.js +208 -2
- package/dist/delegation.d.ts +21 -0
- package/dist/delegation.js +94 -0
- package/dist/doctor.d.ts +5 -1
- package/dist/doctor.js +274 -23
- package/dist/fs-utils.d.ts +10 -0
- package/dist/fs-utils.js +47 -0
- package/dist/gate-evidence.d.ts +26 -0
- package/dist/gate-evidence.js +157 -0
- package/dist/harness-adapters.js +2 -0
- package/dist/hook-schema.d.ts +6 -0
- package/dist/hook-schema.js +45 -0
- package/dist/hook-schemas/claude-hooks.v1.json +12 -0
- package/dist/hook-schemas/codex-hooks.v1.json +12 -0
- package/dist/hook-schemas/cursor-hooks.v1.json +15 -0
- package/dist/install.js +431 -16
- package/dist/policy.d.ts +5 -1
- package/dist/policy.js +52 -1
- package/dist/runs.js +8 -3
- package/dist/trace-matrix.d.ts +13 -0
- package/dist/trace-matrix.js +182 -0
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { lintArtifact, validateReviewArmy } from "./artifact-linter.js";
|
|
2
|
+
import { stageSchema } from "./content/stage-schema.js";
|
|
3
|
+
import { readFlowState, writeFlowState } from "./runs.js";
|
|
4
|
+
function unique(values) {
|
|
5
|
+
return [...new Set(values)];
|
|
6
|
+
}
|
|
7
|
+
function sameStringArray(a, b) {
|
|
8
|
+
if (a.length !== b.length)
|
|
9
|
+
return false;
|
|
10
|
+
return a.every((value, index) => value === b[index]);
|
|
11
|
+
}
|
|
12
|
+
export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
13
|
+
const stage = flowState.currentStage;
|
|
14
|
+
const schema = stageSchema(stage);
|
|
15
|
+
const catalog = flowState.stageGateCatalog[stage];
|
|
16
|
+
const required = schema.requiredGates.map((gate) => gate.id);
|
|
17
|
+
const requiredSet = new Set(required);
|
|
18
|
+
const issues = [];
|
|
19
|
+
const catalogRequired = unique(catalog.required);
|
|
20
|
+
const missingInCatalog = required.filter((gateId) => !catalogRequired.includes(gateId));
|
|
21
|
+
const unexpectedInCatalog = catalogRequired.filter((gateId) => !requiredSet.has(gateId));
|
|
22
|
+
for (const gateId of missingInCatalog) {
|
|
23
|
+
issues.push(`gate "${gateId}" missing from stageGateCatalog.required for stage "${stage}".`);
|
|
24
|
+
}
|
|
25
|
+
for (const gateId of unexpectedInCatalog) {
|
|
26
|
+
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.required for stage "${stage}".`);
|
|
27
|
+
}
|
|
28
|
+
const blockedSet = new Set(catalog.blocked);
|
|
29
|
+
for (const gateId of catalog.passed) {
|
|
30
|
+
if (!requiredSet.has(gateId)) {
|
|
31
|
+
issues.push(`passed gate "${gateId}" is not defined for stage "${stage}".`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (blockedSet.has(gateId)) {
|
|
35
|
+
issues.push(`gate "${gateId}" cannot be both passed and blocked.`);
|
|
36
|
+
}
|
|
37
|
+
const evidence = flowState.guardEvidence[gateId];
|
|
38
|
+
if (typeof evidence !== "string" || evidence.trim().length === 0) {
|
|
39
|
+
issues.push(`passed gate "${gateId}" is missing guardEvidence entry.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const gateId of catalog.blocked) {
|
|
43
|
+
if (!requiredSet.has(gateId)) {
|
|
44
|
+
issues.push(`blocked gate "${gateId}" is not defined for stage "${stage}".`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const shouldValidateArtifact = catalog.passed.length > 0 || flowState.completedStages.includes(stage);
|
|
48
|
+
if (shouldValidateArtifact) {
|
|
49
|
+
const lint = await lintArtifact(projectRoot, stage);
|
|
50
|
+
if (!lint.passed) {
|
|
51
|
+
const failedRequired = lint.findings
|
|
52
|
+
.filter((finding) => finding.required && !finding.found)
|
|
53
|
+
.map((finding) => finding.section);
|
|
54
|
+
if (failedRequired.length > 0) {
|
|
55
|
+
issues.push(`artifact validation failed for required sections: ${failedRequired.join(", ")}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (stage === "review") {
|
|
59
|
+
const reviewArmy = await validateReviewArmy(projectRoot);
|
|
60
|
+
if (!reviewArmy.valid) {
|
|
61
|
+
issues.push(`review-army validation failed: ${reviewArmy.errors.join("; ")}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: issues.length === 0,
|
|
67
|
+
stage,
|
|
68
|
+
issues,
|
|
69
|
+
requiredCount: required.length,
|
|
70
|
+
passedCount: catalog.passed.length,
|
|
71
|
+
blockedCount: catalog.blocked.length
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function reconcileCurrentStageGateCatalog(flowState) {
|
|
75
|
+
const stage = flowState.currentStage;
|
|
76
|
+
const required = stageSchema(stage).requiredGates.map((gate) => gate.id);
|
|
77
|
+
const requiredSet = new Set(required);
|
|
78
|
+
const catalog = flowState.stageGateCatalog[stage];
|
|
79
|
+
const notes = [];
|
|
80
|
+
const before = {
|
|
81
|
+
required: [...catalog.required],
|
|
82
|
+
passed: [...catalog.passed],
|
|
83
|
+
blocked: [...catalog.blocked]
|
|
84
|
+
};
|
|
85
|
+
const passedSet = new Set(unique(catalog.passed).filter((gateId) => {
|
|
86
|
+
const keep = requiredSet.has(gateId);
|
|
87
|
+
if (!keep) {
|
|
88
|
+
notes.push(`removed unknown passed gate "${gateId}"`);
|
|
89
|
+
}
|
|
90
|
+
return keep;
|
|
91
|
+
}));
|
|
92
|
+
const blockedSet = new Set(unique(catalog.blocked).filter((gateId) => {
|
|
93
|
+
const keep = requiredSet.has(gateId);
|
|
94
|
+
if (!keep) {
|
|
95
|
+
notes.push(`removed unknown blocked gate "${gateId}"`);
|
|
96
|
+
}
|
|
97
|
+
return keep;
|
|
98
|
+
}));
|
|
99
|
+
for (const gateId of [...passedSet]) {
|
|
100
|
+
if (!blockedSet.has(gateId))
|
|
101
|
+
continue;
|
|
102
|
+
const evidence = flowState.guardEvidence[gateId];
|
|
103
|
+
if (typeof evidence === "string" && evidence.trim().length > 0) {
|
|
104
|
+
blockedSet.delete(gateId);
|
|
105
|
+
notes.push(`resolved overlap for "${gateId}" in favor of passed (evidence present)`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
passedSet.delete(gateId);
|
|
109
|
+
notes.push(`resolved overlap for "${gateId}" in favor of blocked (missing evidence)`);
|
|
110
|
+
}
|
|
111
|
+
for (const gateId of [...passedSet]) {
|
|
112
|
+
const evidence = flowState.guardEvidence[gateId];
|
|
113
|
+
if (typeof evidence === "string" && evidence.trim().length > 0)
|
|
114
|
+
continue;
|
|
115
|
+
passedSet.delete(gateId);
|
|
116
|
+
blockedSet.add(gateId);
|
|
117
|
+
notes.push(`moved "${gateId}" from passed to blocked (missing evidence)`);
|
|
118
|
+
}
|
|
119
|
+
const after = {
|
|
120
|
+
required: [...required],
|
|
121
|
+
passed: required.filter((gateId) => passedSet.has(gateId)),
|
|
122
|
+
blocked: required.filter((gateId) => blockedSet.has(gateId) && !passedSet.has(gateId))
|
|
123
|
+
};
|
|
124
|
+
const changed = !sameStringArray(before.required, after.required) ||
|
|
125
|
+
!sameStringArray(before.passed, after.passed) ||
|
|
126
|
+
!sameStringArray(before.blocked, after.blocked);
|
|
127
|
+
const nextState = changed
|
|
128
|
+
? {
|
|
129
|
+
...flowState,
|
|
130
|
+
stageGateCatalog: {
|
|
131
|
+
...flowState.stageGateCatalog,
|
|
132
|
+
[stage]: after
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
: flowState;
|
|
136
|
+
return {
|
|
137
|
+
nextState,
|
|
138
|
+
reconciliation: {
|
|
139
|
+
stage,
|
|
140
|
+
changed,
|
|
141
|
+
before,
|
|
142
|
+
after,
|
|
143
|
+
notes
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export async function reconcileAndWriteCurrentStageGateCatalog(projectRoot) {
|
|
148
|
+
const state = await readFlowState(projectRoot);
|
|
149
|
+
const { nextState, reconciliation } = reconcileCurrentStageGateCatalog(state);
|
|
150
|
+
if (reconciliation.changed) {
|
|
151
|
+
await writeFlowState(projectRoot, nextState);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...reconciliation,
|
|
155
|
+
wrote: reconciliation.changed
|
|
156
|
+
};
|
|
157
|
+
}
|
package/dist/harness-adapters.js
CHANGED
|
@@ -62,6 +62,7 @@ Before responding to a coding request:
|
|
|
62
62
|
${stageList}
|
|
63
63
|
| \`/cc-learn\` | \`.cclaw/skills/learnings/SKILL.md\` + \`.cclaw/commands/learn.md\` |
|
|
64
64
|
| \`/cc-autoplan\` | \`.cclaw/skills/autoplan/SKILL.md\` + \`.cclaw/commands/autoplan.md\` |
|
|
65
|
+
| \`/cc-next\` | \`.cclaw/skills/flow-next-step/SKILL.md\` + \`.cclaw/commands/next.md\` |
|
|
65
66
|
|
|
66
67
|
**Stage order:** brainstorm > scope > design > spec > plan > test > build > review > ship.
|
|
67
68
|
One stage per invocation. Gates must pass before handoff.
|
|
@@ -151,6 +152,7 @@ export async function syncHarnessShims(projectRoot, harnesses) {
|
|
|
151
152
|
// Utility command shims
|
|
152
153
|
await writeFileSafe(path.join(commandDir, "cc-learn.md"), utilityShimContent(harness, "learn", "learnings", "learn.md"));
|
|
153
154
|
await writeFileSafe(path.join(commandDir, "cc-autoplan.md"), utilityShimContent(harness, "autoplan", "autoplan", "autoplan.md"));
|
|
155
|
+
await writeFileSafe(path.join(commandDir, "cc-next.md"), utilityShimContent(harness, "next", "flow-next-step", "next.md"));
|
|
154
156
|
}
|
|
155
157
|
await syncAgentFiles(projectRoot);
|
|
156
158
|
await syncAgentsMd(projectRoot);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type HookSchemaHarness = "claude" | "cursor" | "codex";
|
|
2
|
+
export interface HookSchemaValidationResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function validateHookDocument(harness: HookSchemaHarness, document: unknown): HookSchemaValidationResult;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import claudeHooksSchema from "./hook-schemas/claude-hooks.v1.json" with { type: "json" };
|
|
2
|
+
import codexHooksSchema from "./hook-schemas/codex-hooks.v1.json" with { type: "json" };
|
|
3
|
+
import cursorHooksSchema from "./hook-schemas/cursor-hooks.v1.json" with { type: "json" };
|
|
4
|
+
const SCHEMA_MAP = {
|
|
5
|
+
claude: claudeHooksSchema,
|
|
6
|
+
cursor: cursorHooksSchema,
|
|
7
|
+
codex: codexHooksSchema
|
|
8
|
+
};
|
|
9
|
+
function toObject(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function validateHookDocument(harness, document) {
|
|
16
|
+
const descriptor = SCHEMA_MAP[harness];
|
|
17
|
+
const root = toObject(document);
|
|
18
|
+
if (!root) {
|
|
19
|
+
return { ok: false, errors: ["hook document must be a JSON object"] };
|
|
20
|
+
}
|
|
21
|
+
const errors = [];
|
|
22
|
+
const version = root.cclawHookSchemaVersion;
|
|
23
|
+
if (version !== descriptor.schemaVersion) {
|
|
24
|
+
errors.push(`expected cclawHookSchemaVersion=${descriptor.schemaVersion}, got ${JSON.stringify(version)}`);
|
|
25
|
+
}
|
|
26
|
+
if (harness === "cursor" && root.version !== 1) {
|
|
27
|
+
errors.push(`cursor hooks require version=1, got ${JSON.stringify(root.version)}`);
|
|
28
|
+
}
|
|
29
|
+
const hooks = toObject(root.hooks);
|
|
30
|
+
if (!hooks) {
|
|
31
|
+
errors.push("missing hooks object");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
for (const eventName of descriptor.requiredEvents) {
|
|
35
|
+
const eventValue = hooks[eventName];
|
|
36
|
+
if (!Array.isArray(eventValue) || eventValue.length === 0) {
|
|
37
|
+
errors.push(`missing required event array "${eventName}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
ok: errors.length === 0,
|
|
43
|
+
errors
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "cclaw://hooks/cursor/v1",
|
|
4
|
+
"harness": "cursor",
|
|
5
|
+
"schemaVersion": 1,
|
|
6
|
+
"requiredEvents": [
|
|
7
|
+
"sessionStart",
|
|
8
|
+
"sessionResume",
|
|
9
|
+
"sessionClear",
|
|
10
|
+
"sessionCompact",
|
|
11
|
+
"preToolUse",
|
|
12
|
+
"postToolUse",
|
|
13
|
+
"stop"
|
|
14
|
+
]
|
|
15
|
+
}
|