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
package/dist/policy.js
CHANGED
|
@@ -5,9 +5,11 @@ import { stageSchema, stagePolicyNeedles } from "./content/stage-schema.js";
|
|
|
5
5
|
import { stageSkillFolder } from "./content/skills.js";
|
|
6
6
|
import { exists } from "./fs-utils.js";
|
|
7
7
|
const POLICY_RULES = [];
|
|
8
|
-
|
|
8
|
+
const ALL_HARNESSES = ["claude", "cursor", "opencode", "codex"];
|
|
9
|
+
export async function policyChecks(projectRoot, options = {}) {
|
|
9
10
|
const checks = [];
|
|
10
11
|
const rules = [...POLICY_RULES];
|
|
12
|
+
const activeHarnesses = new Set(options.harnesses && options.harnesses.length > 0 ? options.harnesses : ALL_HARNESSES);
|
|
11
13
|
for (const stage of COMMAND_FILE_ORDER) {
|
|
12
14
|
const folder = stageSkillFolder(stage);
|
|
13
15
|
const schema = stageSchema(stage);
|
|
@@ -92,15 +94,19 @@ export async function policyChecks(projectRoot) {
|
|
|
92
94
|
{ file: runtimeFile("skills/autoplan/SKILL.md"), needle: "## Decision Taxonomy", name: "utility_skill:autoplan:taxonomy" },
|
|
93
95
|
{ file: runtimeFile("skills/autoplan/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:autoplan:hard_gate" },
|
|
94
96
|
{ file: runtimeFile("skills/autoplan/SKILL.md"), needle: "## Restore Points", name: "utility_skill:autoplan:restore_points" },
|
|
97
|
+
{ file: runtimeFile("skills/autoplan/SKILL.md"), needle: "## Scope Mode Heuristics (Phase 2)", name: "utility_skill:autoplan:scope_heuristics" },
|
|
95
98
|
{ file: runtimeFile("commands/learn.md"), needle: "## Subcommands", name: "utility_command:learn:subcommands" },
|
|
96
99
|
{ file: runtimeFile("commands/autoplan.md"), needle: "## Phase Sequence", name: "utility_command:autoplan:phases" },
|
|
97
100
|
{ file: runtimeFile("commands/autoplan.md"), needle: "## Decision Principles", name: "utility_command:autoplan:principles" },
|
|
101
|
+
{ file: runtimeFile("commands/autoplan.md"), needle: "## Scope Mode Heuristics (Phase 2)", name: "utility_command:autoplan:scope_heuristics" },
|
|
98
102
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:sdd:hard_gate" },
|
|
99
103
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Status Contract", name: "utility_skill:sdd:status_contract" },
|
|
100
104
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "Implementer", name: "utility_skill:sdd:implementer_template" },
|
|
105
|
+
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:sdd:routing_notes" },
|
|
101
106
|
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:parallel:hard_gate" },
|
|
102
107
|
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Review Army", name: "utility_skill:parallel:review_army" },
|
|
103
108
|
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Reconciliation", name: "utility_skill:parallel:reconciliation" },
|
|
109
|
+
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:parallel:routing_notes" },
|
|
104
110
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:session:hard_gate" },
|
|
105
111
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Start Protocol", name: "utility_skill:session:start" },
|
|
106
112
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Stop Protocol", name: "utility_skill:session:stop" },
|
|
@@ -110,13 +116,16 @@ export async function policyChecks(projectRoot) {
|
|
|
110
116
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure Modes", name: "meta_skill:failure_modes" },
|
|
111
117
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Contextual Skills", name: "meta_skill:contextual_skills" },
|
|
112
118
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Decision Protocol", name: "meta_skill:decision_protocol" },
|
|
119
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Progressive Disclosure (Depth / See Also)", name: "meta_skill:progressive_disclosure" },
|
|
113
120
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
|
|
121
|
+
{ file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "## Progressive Disclosure", name: "stage_skill:progressive_disclosure" },
|
|
114
122
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:security:hard_gate" },
|
|
115
123
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## Checklist", name: "utility_skill:security:checklist" },
|
|
116
124
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## Severity Classification", name: "utility_skill:security:severity" },
|
|
117
125
|
{ file: runtimeFile("skills/debugging/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:debugging:hard_gate" },
|
|
118
126
|
{ file: runtimeFile("skills/debugging/SKILL.md"), needle: "## The Protocol", name: "utility_skill:debugging:protocol" },
|
|
119
127
|
{ file: runtimeFile("skills/debugging/SKILL.md"), needle: "Step 1 — Reproduce", name: "utility_skill:debugging:reproduce" },
|
|
128
|
+
{ file: runtimeFile("skills/debugging/SKILL.md"), needle: "## Testing-Specific Anti-Patterns", name: "utility_skill:debugging:test_antipatterns" },
|
|
120
129
|
{ file: runtimeFile("skills/performance/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:performance:hard_gate" },
|
|
121
130
|
{ file: runtimeFile("skills/performance/SKILL.md"), needle: "## Workflow", name: "utility_skill:performance:workflow" },
|
|
122
131
|
{ file: runtimeFile("skills/performance/SKILL.md"), needle: "## Core Web Vitals Reference", name: "utility_skill:performance:cwv" },
|
|
@@ -126,6 +135,22 @@ export async function policyChecks(projectRoot) {
|
|
|
126
135
|
{ file: runtimeFile("skills/docs/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:docs:hard_gate" },
|
|
127
136
|
{ file: runtimeFile("skills/docs/SKILL.md"), needle: "## ADR (Architecture Decision Record)", name: "utility_skill:docs:adr" },
|
|
128
137
|
{ file: runtimeFile("skills/docs/SKILL.md"), needle: "## README Guidance", name: "utility_skill:docs:readme" },
|
|
138
|
+
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:executing_plans:hard_gate" },
|
|
139
|
+
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## Execution Protocol", name: "utility_skill:executing_plans:protocol" },
|
|
140
|
+
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## Wave Checklist", name: "utility_skill:executing_plans:waves" },
|
|
141
|
+
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:context_engineering:hard_gate" },
|
|
142
|
+
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## Context Modes", name: "utility_skill:context_engineering:modes" },
|
|
143
|
+
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## Mode Switching Protocol", name: "utility_skill:context_engineering:switch" },
|
|
144
|
+
{ file: runtimeFile("skills/source-driven-development/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:source_driven:hard_gate" },
|
|
145
|
+
{ file: runtimeFile("skills/source-driven-development/SKILL.md"), needle: "## Protocol", name: "utility_skill:source_driven:protocol" },
|
|
146
|
+
{ file: runtimeFile("skills/source-driven-development/SKILL.md"), needle: "## Selection Heuristics", name: "utility_skill:source_driven:heuristics" },
|
|
147
|
+
{ file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:frontend_accessibility:hard_gate" },
|
|
148
|
+
{ file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## Checklist", name: "utility_skill:frontend_accessibility:checklist" },
|
|
149
|
+
{ file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## Anti-Patterns", name: "utility_skill:frontend_accessibility:anti_patterns" },
|
|
150
|
+
{ file: runtimeFile("contexts/default.md"), needle: "Context Mode: default", name: "context_mode:default" },
|
|
151
|
+
{ file: runtimeFile("contexts/execution.md"), needle: "Context Mode: execution", name: "context_mode:execution" },
|
|
152
|
+
{ file: runtimeFile("contexts/review.md"), needle: "Context Mode: review", name: "context_mode:review" },
|
|
153
|
+
{ file: runtimeFile("contexts/incident.md"), needle: "Context Mode: incident", name: "context_mode:incident" },
|
|
129
154
|
{ file: runtimeFile("hooks/session-start.sh"), needle: "ACTIVE_RUN=", name: "hooks:session_start:active_run" },
|
|
130
155
|
{ file: runtimeFile("hooks/session-start.sh"), needle: "checkpoint.json", name: "hooks:session_start:checkpoint_ref" },
|
|
131
156
|
{ file: runtimeFile("hooks/session-start.sh"), needle: "stage-activity.jsonl", name: "hooks:session_start:activity_ref" },
|
|
@@ -133,11 +158,37 @@ export async function policyChecks(projectRoot) {
|
|
|
133
158
|
{ file: runtimeFile("hooks/session-start.sh"), needle: "context-warnings.jsonl", name: "hooks:session_start:context_warning_ref" },
|
|
134
159
|
{ file: runtimeFile("hooks/stop-checkpoint.sh"), needle: "checkpoint.json", name: "hooks:stop:checkpoint_write" },
|
|
135
160
|
{ file: runtimeFile("hooks/prompt-guard.sh"), needle: "write_to_cclaw_runtime", name: "hooks:guard:risky_write_advisory" },
|
|
161
|
+
{ file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
|
|
162
|
+
{ file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_jump_", name: "hooks:workflow_guard:stage_jump_reason" },
|
|
136
163
|
{ file: runtimeFile("hooks/context-monitor.sh"), needle: "remaining is", name: "hooks:context:threshold_warning" },
|
|
137
164
|
{ file: runtimeFile("hooks/observe.sh"), needle: "stage-activity.jsonl", name: "hooks:observe:activity_write" },
|
|
138
165
|
{ file: runtimeFile("hooks/summarize-observations.mjs"), needle: "frequent-errors-", name: "hooks:summarize:runtime_module" },
|
|
139
166
|
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" }
|
|
140
167
|
];
|
|
168
|
+
if (activeHarnesses.has("opencode")) {
|
|
169
|
+
utilitySkillChecks.push({
|
|
170
|
+
file: ".opencode/plugins/cclaw-plugin.mjs",
|
|
171
|
+
needle: "\"tool.execute.before\"",
|
|
172
|
+
name: "hooks:opencode:deployed_tool_hook"
|
|
173
|
+
});
|
|
174
|
+
utilitySkillChecks.push({
|
|
175
|
+
file: ".opencode/plugins/cclaw-plugin.mjs",
|
|
176
|
+
needle: "workflow-guard.sh",
|
|
177
|
+
name: "hooks:opencode:deployed_workflow_guard"
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (activeHarnesses.has("cursor")) {
|
|
181
|
+
utilitySkillChecks.push({
|
|
182
|
+
file: ".cursor/rules/cclaw-workflow.mdc",
|
|
183
|
+
needle: "cclaw-managed-cursor-workflow-rule",
|
|
184
|
+
name: "rules:cursor:managed_marker"
|
|
185
|
+
});
|
|
186
|
+
utilitySkillChecks.push({
|
|
187
|
+
file: ".cursor/rules/cclaw-workflow.mdc",
|
|
188
|
+
needle: "/cc-next",
|
|
189
|
+
name: "rules:cursor:next_command_guidance"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
141
192
|
for (const check of utilitySkillChecks) {
|
|
142
193
|
rules.push({
|
|
143
194
|
filePath: check.file,
|
package/dist/runs.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { COMMAND_FILE_ORDER, RUNTIME_ROOT } from "./constants.js";
|
|
4
4
|
import { ARTIFACT_TEMPLATES } from "./content/templates.js";
|
|
5
5
|
import { createInitialFlowState } from "./flow-state.js";
|
|
6
|
-
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
6
|
+
import { ensureDir, exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
|
|
7
7
|
const FLOW_STATE_REL_PATH = `${RUNTIME_ROOT}/state/flow-state.json`;
|
|
8
8
|
const RUNS_DIR_REL_PATH = `${RUNTIME_ROOT}/runs`;
|
|
9
9
|
const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
|
|
@@ -13,6 +13,9 @@ const FLOW_STAGE_SET = new Set(COMMAND_FILE_ORDER);
|
|
|
13
13
|
function flowStatePath(projectRoot) {
|
|
14
14
|
return path.join(projectRoot, FLOW_STATE_REL_PATH);
|
|
15
15
|
}
|
|
16
|
+
function flowStateLockPath(projectRoot) {
|
|
17
|
+
return path.join(projectRoot, RUNTIME_ROOT, "state", ".flow-state.lock");
|
|
18
|
+
}
|
|
16
19
|
function runsRoot(projectRoot) {
|
|
17
20
|
return path.join(projectRoot, RUNS_DIR_REL_PATH);
|
|
18
21
|
}
|
|
@@ -231,8 +234,10 @@ export async function readFlowState(projectRoot) {
|
|
|
231
234
|
return coerceFlowState(parsed);
|
|
232
235
|
}
|
|
233
236
|
export async function writeFlowState(projectRoot, state) {
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
await withDirectoryLock(flowStateLockPath(projectRoot), async () => {
|
|
238
|
+
const safe = coerceFlowState({ ...state }, state.activeRunId);
|
|
239
|
+
await writeFileSafe(flowStatePath(projectRoot), `${JSON.stringify(safe, null, 2)}\n`);
|
|
240
|
+
});
|
|
236
241
|
}
|
|
237
242
|
export async function listRuns(projectRoot) {
|
|
238
243
|
const root = runsRoot(projectRoot);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TraceEntry {
|
|
2
|
+
criterionId: string;
|
|
3
|
+
taskIds: string[];
|
|
4
|
+
testSlices: string[];
|
|
5
|
+
reviewFindings: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface TraceMatrix {
|
|
8
|
+
entries: TraceEntry[];
|
|
9
|
+
orphanedCriteria: string[];
|
|
10
|
+
orphanedTasks: string[];
|
|
11
|
+
orphanedTests: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function buildTraceMatrix(projectRoot: string): Promise<TraceMatrix>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { RUNTIME_ROOT } from "./constants.js";
|
|
4
|
+
import { exists } from "./fs-utils.js";
|
|
5
|
+
import { readFlowState } from "./runs.js";
|
|
6
|
+
function activeArtifactPath(projectRoot, name) {
|
|
7
|
+
return path.join(projectRoot, RUNTIME_ROOT, "artifacts", name);
|
|
8
|
+
}
|
|
9
|
+
function canonicalRunArtifactPath(projectRoot, runId, name) {
|
|
10
|
+
return path.join(projectRoot, RUNTIME_ROOT, "runs", runId, "artifacts", name);
|
|
11
|
+
}
|
|
12
|
+
async function readArtifact(projectRoot, name, activeRunId) {
|
|
13
|
+
const runId = activeRunId.trim();
|
|
14
|
+
const candidates = runId.length > 0
|
|
15
|
+
? [canonicalRunArtifactPath(projectRoot, runId, name), activeArtifactPath(projectRoot, name)]
|
|
16
|
+
: [activeArtifactPath(projectRoot, name)];
|
|
17
|
+
for (const candidate of candidates) {
|
|
18
|
+
if (await exists(candidate)) {
|
|
19
|
+
return fs.readFile(candidate, "utf8");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function uniqPreserve(ids) {
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const id of ids) {
|
|
28
|
+
if (!seen.has(id)) {
|
|
29
|
+
seen.add(id);
|
|
30
|
+
out.push(id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function criterionTokens(text) {
|
|
36
|
+
return text.match(/\bAC-\d+\b/g) ?? [];
|
|
37
|
+
}
|
|
38
|
+
/** AC-1, AC-12, etc. */
|
|
39
|
+
function parseAcceptanceCriterionIds(specMd) {
|
|
40
|
+
return uniqPreserve(criterionTokens(specMd));
|
|
41
|
+
}
|
|
42
|
+
/** Map task id -> AC ids mentioned on same table row or bullet line as the task. */
|
|
43
|
+
function parsePlanTaskAcLinks(planMd) {
|
|
44
|
+
const map = new Map();
|
|
45
|
+
const lines = planMd.split(/\r?\n/);
|
|
46
|
+
const taskRe = /\b(T-\d+)\b/;
|
|
47
|
+
const acRe = /\b(AC-\d+)\b/g;
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const tm = taskRe.exec(line);
|
|
50
|
+
if (!tm) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const taskId = tm[1];
|
|
54
|
+
const acs = [...line.matchAll(acRe)].map((m) => m[0]);
|
|
55
|
+
if (acs.length === 0) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const prev = map.get(taskId) ?? [];
|
|
59
|
+
map.set(taskId, uniqPreserve([...prev, ...acs]));
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
/** All T-N ids appearing in the plan (best-effort). */
|
|
64
|
+
function parsePlanTaskIds(planMd) {
|
|
65
|
+
const re = /\bT-\d+\b/g;
|
|
66
|
+
return uniqPreserve(planMd.match(re) ?? []);
|
|
67
|
+
}
|
|
68
|
+
/** Map slice id -> task ids on same line. */
|
|
69
|
+
function parseTddSliceTaskLinks(tddMd) {
|
|
70
|
+
const map = new Map();
|
|
71
|
+
const sliceRe = /\b(S-\d+)\b/g;
|
|
72
|
+
const taskRe = /\b(T-\d+)\b/g;
|
|
73
|
+
const lines = tddMd.split(/\r?\n/);
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
const slices = [...line.matchAll(sliceRe)].map((m) => m[1]);
|
|
76
|
+
if (slices.length === 0) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const tasks = [...line.matchAll(taskRe)].map((m) => m[1]);
|
|
80
|
+
for (const s of slices) {
|
|
81
|
+
const prev = map.get(s) ?? [];
|
|
82
|
+
map.set(s, uniqPreserve([...prev, ...tasks]));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return map;
|
|
86
|
+
}
|
|
87
|
+
function parseTddSliceIds(tddMd) {
|
|
88
|
+
return uniqPreserve(tddMd.match(/\bS-\d+\b/g) ?? []);
|
|
89
|
+
}
|
|
90
|
+
/** Body of Layer 1 spec compliance section in 07-review.md */
|
|
91
|
+
function extractLayer1Section(reviewMd) {
|
|
92
|
+
const lines = reviewMd.split(/\r?\n/);
|
|
93
|
+
let start = -1;
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
if (/^##\s+Layer\s*1/i.test(lines[i])) {
|
|
96
|
+
start = i + 1;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (start < 0) {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
const buf = [];
|
|
104
|
+
for (let i = start; i < lines.length; i++) {
|
|
105
|
+
const line = lines[i];
|
|
106
|
+
if (/^##\s+/.test(line)) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
buf.push(line);
|
|
110
|
+
}
|
|
111
|
+
return buf.join("\n");
|
|
112
|
+
}
|
|
113
|
+
function layer1LinesForCriterion(layer1, criterionId) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const line of layer1.split(/\r?\n/)) {
|
|
116
|
+
if (criterionTokens(line).includes(criterionId)) {
|
|
117
|
+
out.push(line.trim());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
export async function buildTraceMatrix(projectRoot) {
|
|
123
|
+
const { activeRunId } = await readFlowState(projectRoot);
|
|
124
|
+
const spec = await readArtifact(projectRoot, "04-spec.md", activeRunId);
|
|
125
|
+
const plan = await readArtifact(projectRoot, "05-plan.md", activeRunId);
|
|
126
|
+
const tdd = await readArtifact(projectRoot, "06-tdd.md", activeRunId);
|
|
127
|
+
const review = await readArtifact(projectRoot, "07-review.md", activeRunId);
|
|
128
|
+
const criterionIds = spec ? parseAcceptanceCriterionIds(spec) : [];
|
|
129
|
+
const taskToAcs = plan ? parsePlanTaskAcLinks(plan) : new Map();
|
|
130
|
+
const allTaskIds = plan ? parsePlanTaskIds(plan) : [];
|
|
131
|
+
const sliceToTasks = tdd ? parseTddSliceTaskLinks(tdd) : new Map();
|
|
132
|
+
const allSliceIds = tdd ? parseTddSliceIds(tdd) : [];
|
|
133
|
+
const layer1 = review ? extractLayer1Section(review) : "";
|
|
134
|
+
const acToTasks = new Map();
|
|
135
|
+
for (const [task, acs] of taskToAcs) {
|
|
136
|
+
for (const ac of acs) {
|
|
137
|
+
const prev = acToTasks.get(ac) ?? [];
|
|
138
|
+
if (!prev.includes(task)) {
|
|
139
|
+
acToTasks.set(ac, [...prev, task]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const entries = criterionIds.map((criterionId) => {
|
|
144
|
+
const taskIds = acToTasks.get(criterionId) ?? [];
|
|
145
|
+
const testSlices = [];
|
|
146
|
+
for (const [slice, tasks] of sliceToTasks) {
|
|
147
|
+
if (tasks.some((t) => taskIds.includes(t))) {
|
|
148
|
+
testSlices.push(slice);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
criterionId,
|
|
153
|
+
taskIds: uniqPreserve(taskIds),
|
|
154
|
+
testSlices: uniqPreserve(testSlices),
|
|
155
|
+
reviewFindings: layer1LinesForCriterion(layer1, criterionId)
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
const orphanedCriteria = criterionIds.filter((ac) => (acToTasks.get(ac) ?? []).length === 0);
|
|
159
|
+
const tasksWithSlice = new Set();
|
|
160
|
+
for (const tasks of sliceToTasks.values()) {
|
|
161
|
+
for (const t of tasks) {
|
|
162
|
+
tasksWithSlice.add(t);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const orphanedTasks = allTaskIds.filter((t) => !tasksWithSlice.has(t));
|
|
166
|
+
const orphanedTests = allSliceIds.filter((s) => {
|
|
167
|
+
const tasks = sliceToTasks.get(s) ?? [];
|
|
168
|
+
if (tasks.length === 0) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return tasks.every((t) => {
|
|
172
|
+
const acs = taskToAcs.get(t);
|
|
173
|
+
return !acs || acs.length === 0;
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
entries,
|
|
178
|
+
orphanedCriteria,
|
|
179
|
+
orphanedTasks,
|
|
180
|
+
orphanedTests
|
|
181
|
+
};
|
|
182
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,10 +2,20 @@ export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "sp
|
|
|
2
2
|
export type FlowStage = (typeof FLOW_STAGES)[number];
|
|
3
3
|
export declare const HARNESS_IDS: readonly ["claude", "cursor", "opencode", "codex"];
|
|
4
4
|
export type HarnessId = (typeof HARNESS_IDS)[number];
|
|
5
|
-
export interface
|
|
5
|
+
export interface VibyConfig {
|
|
6
6
|
version: string;
|
|
7
7
|
flowVersion: string;
|
|
8
8
|
harnesses: HarnessId[];
|
|
9
|
+
/** When true, stage skills instruct the agent to continue to the following stage after gates pass. */
|
|
10
|
+
autoAdvance?: boolean;
|
|
11
|
+
/** Merge project bootstrap learnings with a global learnings file. */
|
|
12
|
+
globalLearnings?: boolean;
|
|
13
|
+
/** Optional absolute or project-relative path to global learnings JSONL. */
|
|
14
|
+
globalLearningsPath?: string;
|
|
15
|
+
/** Prompt guard behavior for runtime write-risk detection hooks. */
|
|
16
|
+
promptGuardMode?: "advisory" | "strict";
|
|
17
|
+
/** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
|
|
18
|
+
gitHookGuards?: boolean;
|
|
9
19
|
}
|
|
10
20
|
export interface TransitionRule {
|
|
11
21
|
from: FlowStage;
|