cclaw-cli 0.1.0 → 0.2.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 (49) hide show
  1. package/README.md +28 -0
  2. package/dist/artifact-linter.d.ts +20 -0
  3. package/dist/artifact-linter.js +368 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +22 -5
  6. package/dist/config.d.ts +4 -4
  7. package/dist/config.js +55 -3
  8. package/dist/constants.d.ts +4 -4
  9. package/dist/constants.js +6 -3
  10. package/dist/content/autoplan.js +51 -4
  11. package/dist/content/contexts.d.ts +9 -0
  12. package/dist/content/contexts.js +65 -0
  13. package/dist/content/hooks.d.ts +6 -2
  14. package/dist/content/hooks.js +448 -16
  15. package/dist/content/meta-skill.js +26 -0
  16. package/dist/content/next-command.d.ts +9 -0
  17. package/dist/content/next-command.js +138 -0
  18. package/dist/content/observe.d.ts +5 -1
  19. package/dist/content/observe.js +506 -24
  20. package/dist/content/skills.js +126 -0
  21. package/dist/content/stage-schema.d.ts +7 -0
  22. package/dist/content/stage-schema.js +70 -12
  23. package/dist/content/subagents.js +33 -0
  24. package/dist/content/templates.d.ts +1 -0
  25. package/dist/content/templates.js +182 -77
  26. package/dist/content/utility-skills.d.ts +5 -1
  27. package/dist/content/utility-skills.js +208 -2
  28. package/dist/delegation.d.ts +21 -0
  29. package/dist/delegation.js +94 -0
  30. package/dist/doctor.d.ts +5 -1
  31. package/dist/doctor.js +274 -29
  32. package/dist/fs-utils.d.ts +10 -0
  33. package/dist/fs-utils.js +47 -0
  34. package/dist/gate-evidence.d.ts +26 -0
  35. package/dist/gate-evidence.js +157 -0
  36. package/dist/harness-adapters.js +10 -26
  37. package/dist/hook-schema.d.ts +6 -0
  38. package/dist/hook-schema.js +45 -0
  39. package/dist/hook-schemas/claude-hooks.v1.json +12 -0
  40. package/dist/hook-schemas/codex-hooks.v1.json +12 -0
  41. package/dist/hook-schemas/cursor-hooks.v1.json +15 -0
  42. package/dist/install.js +395 -15
  43. package/dist/policy.d.ts +5 -1
  44. package/dist/policy.js +52 -1
  45. package/dist/runs.js +8 -3
  46. package/dist/trace-matrix.d.ts +13 -0
  47. package/dist/trace-matrix.js +182 -0
  48. package/dist/types.d.ts +11 -1
  49. package/package.json +2 -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
+ }
@@ -1,12 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { COMMAND_FILE_ORDER, RUNTIME_ROOT } from "./constants.js";
4
- import { CCLAW_AGENTS, agentMarkdown, agentsAgentsMdBlock } from "./content/agents.js";
5
- import { autoplanAgentsMdBlock } from "./content/autoplan.js";
6
- import { learningsAgentsMdBlock } from "./content/learnings.js";
7
- import { sessionHooksAgentsMdBlock } from "./content/session-hooks.js";
8
- import { hooksAgentsMdBlock } from "./content/hooks.js";
9
- import { subagentsAgentsMdBlock } from "./content/subagents.js";
4
+ import { CCLAW_AGENTS, agentMarkdown } from "./content/agents.js";
10
5
  import { stageSkillFolder } from "./content/skills.js";
11
6
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
12
7
  export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
@@ -48,9 +43,10 @@ Do not skip required handoff gates.
48
43
  function agentsMdBlock() {
49
44
  const stageList = COMMAND_FILE_ORDER.map((s) => `| \`/cc-${s}\` | \`.cclaw/skills/${stageSkillFolder(s)}/SKILL.md\` + \`.cclaw/commands/${s}.md\` |`).join("\n");
50
45
  return `${CCLAW_MARKER_START}
51
- ## Cclaw — Structured Development Flow
46
+ ## Cclaw — Workflow Adapter
52
47
 
53
- > Auto-generated by \`cclaw sync\`. Do not edit manually run \`npx cclaw sync\`.
48
+ > Auto-generated by \`cclaw sync\`. Do not edit this managed block manually.
49
+ > Existing project rules in this repository take precedence over cclaw defaults.
54
50
 
55
51
  ### Activation Rule
56
52
 
@@ -66,32 +62,19 @@ Before responding to a coding request:
66
62
  ${stageList}
67
63
  | \`/cc-learn\` | \`.cclaw/skills/learnings/SKILL.md\` + \`.cclaw/commands/learn.md\` |
68
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\` |
69
66
 
70
67
  **Stage order:** brainstorm > scope > design > spec > plan > test > build > review > ship.
71
- One stage per invocation. Gates must pass before handoff. Artifacts in \`.cclaw/artifacts/\`.
68
+ One stage per invocation. Gates must pass before handoff.
72
69
 
73
70
  ### Verification Discipline
74
71
 
75
72
  No completion claims without fresh evidence. No "Done" / "All good" / "Tests pass" without running the command in this message.
76
73
 
77
- ### File Map
74
+ ### Detail Level
78
75
 
79
- | Path | Purpose |
80
- |---|---|
81
- | \`.cclaw/commands/*.md\` | Stage commands (thin orchestrators) |
82
- | \`.cclaw/skills/*/SKILL.md\` | Full stage instructions |
83
- | \`.cclaw/rules/\` | RULES.md + rules.json |
84
- | \`.cclaw/state/flow-state.json\` | Flow state & gate tracking |
85
- | \`.cclaw/artifacts/*.md\` | Stage evidence artifacts |
86
- | \`.cclaw/agents/*.md\` | Specialist agent personas |
87
- | \`.cclaw/learnings.jsonl\` | Project knowledge base |
88
-
89
- ${learningsAgentsMdBlock()}
90
- ${autoplanAgentsMdBlock()}
91
- ${agentsAgentsMdBlock()}
92
- ${subagentsAgentsMdBlock()}
93
- ${sessionHooksAgentsMdBlock()}
94
- ${hooksAgentsMdBlock()}
76
+ - This managed AGENTS block is intentionally minimal for cross-project use.
77
+ - Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
95
78
  ${CCLAW_MARKER_END}`;
96
79
  }
97
80
  /** Removes the cclaw AGENTS.md block. */
@@ -169,6 +152,7 @@ export async function syncHarnessShims(projectRoot, harnesses) {
169
152
  // Utility command shims
170
153
  await writeFileSafe(path.join(commandDir, "cc-learn.md"), utilityShimContent(harness, "learn", "learnings", "learn.md"));
171
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"));
172
156
  }
173
157
  await syncAgentFiles(projectRoot);
174
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,12 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "cclaw://hooks/claude/v1",
4
+ "harness": "claude",
5
+ "schemaVersion": 1,
6
+ "requiredEvents": [
7
+ "SessionStart",
8
+ "PreToolUse",
9
+ "PostToolUse",
10
+ "Stop"
11
+ ]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "cclaw://hooks/codex/v1",
4
+ "harness": "codex",
5
+ "schemaVersion": 1,
6
+ "requiredEvents": [
7
+ "SessionStart",
8
+ "PreToolUse",
9
+ "PostToolUse",
10
+ "Stop"
11
+ ]
12
+ }
@@ -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
+ }