cclaw-cli 0.10.1 → 0.11.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/dist/config.js +83 -3
- package/dist/content/hooks.js +81 -11
- package/dist/content/meta-skill.d.ts +0 -8
- package/dist/content/meta-skill.js +51 -341
- package/dist/content/next-command.js +2 -1
- package/dist/content/protocols.d.ts +7 -0
- package/dist/content/protocols.js +95 -0
- package/dist/content/skills.js +183 -313
- package/dist/content/stage-common-guidance.d.ts +2 -0
- package/dist/content/stage-common-guidance.js +71 -0
- package/dist/content/stage-schema.d.ts +8 -0
- package/dist/content/stage-schema.js +135 -1
- package/dist/content/start-command.js +19 -13
- package/dist/doctor.js +21 -23
- package/dist/flow-state.d.ts +4 -0
- package/dist/flow-state.js +4 -1
- package/dist/gate-evidence.d.ts +9 -1
- package/dist/gate-evidence.js +121 -17
- package/dist/install.js +10 -0
- package/dist/policy.js +16 -13
- package/dist/runs.js +21 -4
- package/dist/track-heuristics.d.ts +12 -0
- package/dist/track-heuristics.js +144 -0
- package/dist/types.d.ts +26 -3
- package/dist/types.js +6 -3
- package/package.json +1 -1
package/dist/gate-evidence.js
CHANGED
|
@@ -36,21 +36,56 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
36
36
|
const stage = flowState.currentStage;
|
|
37
37
|
const schema = stageSchema(stage);
|
|
38
38
|
const catalog = flowState.stageGateCatalog[stage];
|
|
39
|
-
const required = schema.requiredGates
|
|
39
|
+
const required = schema.requiredGates
|
|
40
|
+
.filter((gate) => gate.tier === "required")
|
|
41
|
+
.map((gate) => gate.id);
|
|
42
|
+
const recommended = schema.requiredGates
|
|
43
|
+
.filter((gate) => gate.tier === "recommended")
|
|
44
|
+
.map((gate) => gate.id);
|
|
45
|
+
const conditional = schema.requiredGates
|
|
46
|
+
.filter((gate) => gate.tier === "conditional")
|
|
47
|
+
.map((gate) => gate.id);
|
|
40
48
|
const requiredSet = new Set(required);
|
|
49
|
+
const recommendedSet = new Set(recommended);
|
|
50
|
+
const conditionalSet = new Set(conditional);
|
|
51
|
+
const allowedSet = new Set([...required, ...recommended, ...conditional]);
|
|
41
52
|
const issues = [];
|
|
42
53
|
const catalogRequired = unique(catalog.required);
|
|
54
|
+
const catalogRecommended = unique(catalog.recommended ?? []);
|
|
55
|
+
const catalogConditional = unique(catalog.conditional ?? []);
|
|
56
|
+
const catalogTriggered = unique(catalog.triggered ?? []);
|
|
43
57
|
const missingInCatalog = required.filter((gateId) => !catalogRequired.includes(gateId));
|
|
44
58
|
const unexpectedInCatalog = catalogRequired.filter((gateId) => !requiredSet.has(gateId));
|
|
59
|
+
const missingRecommendedInCatalog = recommended.filter((gateId) => !catalogRecommended.includes(gateId));
|
|
60
|
+
const unexpectedRecommendedInCatalog = catalogRecommended.filter((gateId) => !recommendedSet.has(gateId));
|
|
61
|
+
const missingConditionalInCatalog = conditional.filter((gateId) => !catalogConditional.includes(gateId));
|
|
62
|
+
const unexpectedConditionalInCatalog = catalogConditional.filter((gateId) => !conditionalSet.has(gateId));
|
|
45
63
|
for (const gateId of missingInCatalog) {
|
|
46
64
|
issues.push(`gate "${gateId}" missing from stageGateCatalog.required for stage "${stage}".`);
|
|
47
65
|
}
|
|
48
66
|
for (const gateId of unexpectedInCatalog) {
|
|
49
67
|
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.required for stage "${stage}".`);
|
|
50
68
|
}
|
|
69
|
+
for (const gateId of missingRecommendedInCatalog) {
|
|
70
|
+
issues.push(`gate "${gateId}" missing from stageGateCatalog.recommended for stage "${stage}".`);
|
|
71
|
+
}
|
|
72
|
+
for (const gateId of unexpectedRecommendedInCatalog) {
|
|
73
|
+
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.recommended for stage "${stage}".`);
|
|
74
|
+
}
|
|
75
|
+
for (const gateId of missingConditionalInCatalog) {
|
|
76
|
+
issues.push(`gate "${gateId}" missing from stageGateCatalog.conditional for stage "${stage}".`);
|
|
77
|
+
}
|
|
78
|
+
for (const gateId of unexpectedConditionalInCatalog) {
|
|
79
|
+
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.conditional for stage "${stage}".`);
|
|
80
|
+
}
|
|
81
|
+
for (const gateId of catalogTriggered) {
|
|
82
|
+
if (!conditionalSet.has(gateId)) {
|
|
83
|
+
issues.push(`triggered gate "${gateId}" is not defined as conditional for stage "${stage}".`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
51
86
|
const blockedSet = new Set(catalog.blocked);
|
|
52
87
|
for (const gateId of catalog.passed) {
|
|
53
|
-
if (!
|
|
88
|
+
if (!allowedSet.has(gateId)) {
|
|
54
89
|
issues.push(`passed gate "${gateId}" is not defined for stage "${stage}".`);
|
|
55
90
|
continue;
|
|
56
91
|
}
|
|
@@ -63,7 +98,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
63
98
|
}
|
|
64
99
|
}
|
|
65
100
|
for (const gateId of catalog.blocked) {
|
|
66
|
-
if (!
|
|
101
|
+
if (!allowedSet.has(gateId)) {
|
|
67
102
|
issues.push(`blocked gate "${gateId}" is not defined for stage "${stage}".`);
|
|
68
103
|
}
|
|
69
104
|
}
|
|
@@ -91,14 +126,25 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
128
|
const passedSet = new Set(catalog.passed);
|
|
129
|
+
const triggeredConditionalSet = new Set([
|
|
130
|
+
...catalogTriggered.filter((gateId) => conditionalSet.has(gateId)),
|
|
131
|
+
...catalog.passed.filter((gateId) => conditionalSet.has(gateId)),
|
|
132
|
+
...catalog.blocked.filter((gateId) => conditionalSet.has(gateId))
|
|
133
|
+
]);
|
|
94
134
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
95
|
-
const
|
|
135
|
+
const missingRecommended = recommended.filter((gateId) => !passedSet.has(gateId));
|
|
136
|
+
const missingTriggeredConditional = [...triggeredConditionalSet].filter((gateId) => !passedSet.has(gateId));
|
|
137
|
+
const blockingBlocked = catalog.blocked.filter((gateId) => requiredSet.has(gateId) || triggeredConditionalSet.has(gateId));
|
|
138
|
+
const complete = missingRequired.length === 0 && missingTriggeredConditional.length === 0 && blockingBlocked.length === 0;
|
|
96
139
|
if (flowState.completedStages.includes(stage) && !complete) {
|
|
97
140
|
if (missingRequired.length > 0) {
|
|
98
141
|
issues.push(`stage "${stage}" is marked completed but required gates are not passed: ${missingRequired.join(", ")}.`);
|
|
99
142
|
}
|
|
100
|
-
if (
|
|
101
|
-
issues.push(`stage "${stage}" is marked completed but
|
|
143
|
+
if (missingTriggeredConditional.length > 0) {
|
|
144
|
+
issues.push(`stage "${stage}" is marked completed but triggered conditional gates are not passed: ${missingTriggeredConditional.join(", ")}.`);
|
|
145
|
+
}
|
|
146
|
+
if (blockingBlocked.length > 0) {
|
|
147
|
+
issues.push(`stage "${stage}" is marked completed but has blocking blocked gates: ${blockingBlocked.join(", ")}.`);
|
|
102
148
|
}
|
|
103
149
|
}
|
|
104
150
|
return {
|
|
@@ -106,10 +152,15 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
106
152
|
stage,
|
|
107
153
|
issues,
|
|
108
154
|
requiredCount: required.length,
|
|
155
|
+
recommendedCount: recommended.length,
|
|
156
|
+
conditionalCount: conditional.length,
|
|
157
|
+
triggeredConditionalCount: triggeredConditionalSet.size,
|
|
109
158
|
passedCount: catalog.passed.length,
|
|
110
159
|
blockedCount: catalog.blocked.length,
|
|
111
160
|
complete,
|
|
112
|
-
missingRequired
|
|
161
|
+
missingRequired,
|
|
162
|
+
missingRecommended,
|
|
163
|
+
missingTriggeredConditional
|
|
113
164
|
};
|
|
114
165
|
}
|
|
115
166
|
export function verifyCompletedStagesGateClosure(flowState) {
|
|
@@ -118,16 +169,37 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
118
169
|
for (const stage of flowState.completedStages) {
|
|
119
170
|
const schema = stageSchema(stage);
|
|
120
171
|
const catalog = flowState.stageGateCatalog[stage];
|
|
121
|
-
const required = schema.requiredGates
|
|
172
|
+
const required = schema.requiredGates
|
|
173
|
+
.filter((gate) => gate.tier === "required")
|
|
174
|
+
.map((gate) => gate.id);
|
|
175
|
+
const conditional = schema.requiredGates
|
|
176
|
+
.filter((gate) => gate.tier === "conditional")
|
|
177
|
+
.map((gate) => gate.id);
|
|
178
|
+
const conditionalSet = new Set(conditional);
|
|
122
179
|
const passedSet = new Set(catalog.passed);
|
|
180
|
+
const triggeredSet = new Set([
|
|
181
|
+
...(catalog.triggered ?? []).filter((gateId) => conditionalSet.has(gateId)),
|
|
182
|
+
...catalog.passed.filter((gateId) => conditionalSet.has(gateId)),
|
|
183
|
+
...catalog.blocked.filter((gateId) => conditionalSet.has(gateId))
|
|
184
|
+
]);
|
|
123
185
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
124
|
-
|
|
125
|
-
|
|
186
|
+
const missingTriggeredConditional = [...triggeredSet].filter((gateId) => !passedSet.has(gateId));
|
|
187
|
+
const blockingBlocked = catalog.blocked.filter((gateId) => required.includes(gateId) || triggeredSet.has(gateId));
|
|
188
|
+
if (missingRequired.length > 0 || missingTriggeredConditional.length > 0 || blockingBlocked.length > 0) {
|
|
189
|
+
openStages.push({
|
|
190
|
+
stage,
|
|
191
|
+
missingRequired,
|
|
192
|
+
missingTriggeredConditional,
|
|
193
|
+
blocked: [...blockingBlocked]
|
|
194
|
+
});
|
|
126
195
|
if (missingRequired.length > 0) {
|
|
127
196
|
issues.push(`completed stage "${stage}" has unpassed required gates: ${missingRequired.join(", ")}.`);
|
|
128
197
|
}
|
|
129
|
-
if (
|
|
130
|
-
issues.push(`completed stage "${stage}"
|
|
198
|
+
if (missingTriggeredConditional.length > 0) {
|
|
199
|
+
issues.push(`completed stage "${stage}" has unpassed triggered conditional gates: ${missingTriggeredConditional.join(", ")}.`);
|
|
200
|
+
}
|
|
201
|
+
if (blockingBlocked.length > 0) {
|
|
202
|
+
issues.push(`completed stage "${stage}" still has blocking blocked gates: ${blockingBlocked.join(", ")}.`);
|
|
131
203
|
}
|
|
132
204
|
}
|
|
133
205
|
}
|
|
@@ -135,29 +207,55 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
135
207
|
}
|
|
136
208
|
export function reconcileCurrentStageGateCatalog(flowState) {
|
|
137
209
|
const stage = flowState.currentStage;
|
|
138
|
-
const required = stageSchema(stage).requiredGates
|
|
210
|
+
const required = stageSchema(stage).requiredGates
|
|
211
|
+
.filter((gate) => gate.tier === "required")
|
|
212
|
+
.map((gate) => gate.id);
|
|
213
|
+
const recommended = stageSchema(stage).requiredGates
|
|
214
|
+
.filter((gate) => gate.tier === "recommended")
|
|
215
|
+
.map((gate) => gate.id);
|
|
216
|
+
const conditional = stageSchema(stage).requiredGates
|
|
217
|
+
.filter((gate) => gate.tier === "conditional")
|
|
218
|
+
.map((gate) => gate.id);
|
|
139
219
|
const requiredSet = new Set(required);
|
|
220
|
+
const recommendedSet = new Set(recommended);
|
|
221
|
+
const conditionalSet = new Set(conditional);
|
|
222
|
+
const allowedSet = new Set([...required, ...recommended, ...conditional]);
|
|
140
223
|
const catalog = flowState.stageGateCatalog[stage];
|
|
141
224
|
const notes = [];
|
|
142
225
|
const before = {
|
|
143
226
|
required: [...catalog.required],
|
|
227
|
+
recommended: [...catalog.recommended],
|
|
228
|
+
conditional: [...catalog.conditional],
|
|
229
|
+
triggered: [...catalog.triggered],
|
|
144
230
|
passed: [...catalog.passed],
|
|
145
231
|
blocked: [...catalog.blocked]
|
|
146
232
|
};
|
|
147
233
|
const passedSet = new Set(unique(catalog.passed).filter((gateId) => {
|
|
148
|
-
const keep =
|
|
234
|
+
const keep = allowedSet.has(gateId);
|
|
149
235
|
if (!keep) {
|
|
150
236
|
notes.push(`removed unknown passed gate "${gateId}"`);
|
|
151
237
|
}
|
|
152
238
|
return keep;
|
|
153
239
|
}));
|
|
154
240
|
const blockedSet = new Set(unique(catalog.blocked).filter((gateId) => {
|
|
155
|
-
const keep =
|
|
241
|
+
const keep = allowedSet.has(gateId);
|
|
156
242
|
if (!keep) {
|
|
157
243
|
notes.push(`removed unknown blocked gate "${gateId}"`);
|
|
158
244
|
}
|
|
159
245
|
return keep;
|
|
160
246
|
}));
|
|
247
|
+
const triggeredSet = new Set(unique(catalog.triggered).filter((gateId) => {
|
|
248
|
+
const keep = conditionalSet.has(gateId);
|
|
249
|
+
if (!keep) {
|
|
250
|
+
notes.push(`removed unknown triggered gate "${gateId}"`);
|
|
251
|
+
}
|
|
252
|
+
return keep;
|
|
253
|
+
}));
|
|
254
|
+
for (const gateId of [...passedSet, ...blockedSet]) {
|
|
255
|
+
if (conditionalSet.has(gateId)) {
|
|
256
|
+
triggeredSet.add(gateId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
161
259
|
for (const gateId of [...passedSet]) {
|
|
162
260
|
if (!blockedSet.has(gateId))
|
|
163
261
|
continue;
|
|
@@ -180,10 +278,16 @@ export function reconcileCurrentStageGateCatalog(flowState) {
|
|
|
180
278
|
}
|
|
181
279
|
const after = {
|
|
182
280
|
required: [...required],
|
|
183
|
-
|
|
184
|
-
|
|
281
|
+
recommended: [...recommended],
|
|
282
|
+
conditional: [...conditional],
|
|
283
|
+
triggered: conditional.filter((gateId) => triggeredSet.has(gateId)),
|
|
284
|
+
passed: [...required, ...recommended, ...conditional].filter((gateId) => passedSet.has(gateId)),
|
|
285
|
+
blocked: [...required, ...recommended, ...conditional].filter((gateId) => blockedSet.has(gateId) && !passedSet.has(gateId))
|
|
185
286
|
};
|
|
186
287
|
const changed = !sameStringArray(before.required, after.required) ||
|
|
288
|
+
!sameStringArray(before.recommended, after.recommended) ||
|
|
289
|
+
!sameStringArray(before.conditional, after.conditional) ||
|
|
290
|
+
!sameStringArray(before.triggered, after.triggered) ||
|
|
187
291
|
!sameStringArray(before.passed, after.passed) ||
|
|
188
292
|
!sameStringArray(before.blocked, after.blocked);
|
|
189
293
|
const nextState = changed
|
package/dist/install.js
CHANGED
|
@@ -15,8 +15,10 @@ import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
|
15
15
|
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson, codexHooksJson } from "./content/hooks.js";
|
|
16
16
|
import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
|
|
17
17
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
18
|
+
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
18
19
|
import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
|
|
19
20
|
import { TDD_WAVE_WALKTHROUGH_MARKDOWN, stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
|
|
21
|
+
import { stageCommonGuidanceMarkdown } from "./content/stage-common-guidance.js";
|
|
20
22
|
import { STAGE_EXAMPLES_REFERENCE_DIR, stageExamplesReferenceMarkdown } from "./content/examples.js";
|
|
21
23
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
|
|
22
24
|
import { HARNESS_TOOL_REFS_DIR, HARNESS_TOOL_REFS_INDEX_MD, harnessToolRefMarkdown } from "./content/harness-tool-refs.js";
|
|
@@ -185,6 +187,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
185
187
|
// always-rendered TDD skill stays under the line-budget and the reference
|
|
186
188
|
// is loaded on demand.
|
|
187
189
|
await writeFileSafe(runtimePath(projectRoot, ...STAGE_EXAMPLES_REFERENCE_DIR.split("/"), "tdd-wave-walkthrough.md"), TDD_WAVE_WALKTHROUGH_MARKDOWN);
|
|
190
|
+
await writeFileSafe(runtimePath(projectRoot, ...STAGE_EXAMPLES_REFERENCE_DIR.split("/"), "common-guidance.md"), stageCommonGuidanceMarkdown());
|
|
188
191
|
// Utility skills (not flow stages)
|
|
189
192
|
await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
|
|
190
193
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
|
|
@@ -194,6 +197,9 @@ async function writeSkills(projectRoot, config) {
|
|
|
194
197
|
await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
|
|
195
198
|
await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
|
|
196
199
|
await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
|
|
200
|
+
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "decision.md"), decisionProtocolMarkdown());
|
|
201
|
+
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "completion.md"), completionProtocolMarkdown());
|
|
202
|
+
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "ethos.md"), ethosProtocolMarkdown());
|
|
197
203
|
for (const folder of UTILITY_SKILL_FOLDERS) {
|
|
198
204
|
const generator = UTILITY_SKILL_MAP[folder];
|
|
199
205
|
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
|
|
@@ -773,6 +779,10 @@ async function ensureSessionStateFiles(projectRoot) {
|
|
|
773
779
|
if (!(await exists(contextModePath))) {
|
|
774
780
|
await writeFileSafe(contextModePath, `${JSON.stringify(createInitialContextModeState(), null, 2)}\n`);
|
|
775
781
|
}
|
|
782
|
+
const knowledgeDigestPath = path.join(stateDir, "knowledge-digest.md");
|
|
783
|
+
if (!(await exists(knowledgeDigestPath))) {
|
|
784
|
+
await writeFileSafe(knowledgeDigestPath, "# Knowledge digest (auto-generated)\n\n(no entries yet)\n");
|
|
785
|
+
}
|
|
776
786
|
}
|
|
777
787
|
async function writeRulebook(projectRoot) {
|
|
778
788
|
await writeFileSafe(runtimePath(projectRoot, "rules", "RULES.md"), RULEBOOK_MARKDOWN);
|
package/dist/policy.js
CHANGED
|
@@ -40,16 +40,15 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
40
40
|
"## Process",
|
|
41
41
|
"## Verification",
|
|
42
42
|
"## Interaction Protocol",
|
|
43
|
-
"## Common Rationalizations",
|
|
44
43
|
"## Anti-Patterns & Red Flags",
|
|
45
44
|
"## HARD-GATE",
|
|
46
45
|
"## Checklist",
|
|
47
46
|
"## Context Loading",
|
|
48
47
|
"## Automatic Subagent Dispatch",
|
|
49
|
-
"## Cognitive Patterns",
|
|
50
48
|
"## Cross-Stage Traceability",
|
|
51
|
-
"##
|
|
52
|
-
"##
|
|
49
|
+
"## Artifact Validation",
|
|
50
|
+
"## Completion Parameters",
|
|
51
|
+
"## Shared Stage Guidance"
|
|
53
52
|
]) {
|
|
54
53
|
rules.push({
|
|
55
54
|
filePath: skillFile,
|
|
@@ -102,15 +101,17 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
102
101
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:session:hard_gate" },
|
|
103
102
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Start Protocol", name: "utility_skill:session:start" },
|
|
104
103
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Stop Protocol", name: "utility_skill:session:stop" },
|
|
105
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "##
|
|
106
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "##
|
|
107
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Stage
|
|
108
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "##
|
|
109
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "##
|
|
110
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "##
|
|
111
|
-
{ file: runtimeFile("
|
|
104
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Routing flow", name: "meta_skill:routing_flow" },
|
|
105
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Task classification", name: "meta_skill:task_classification" },
|
|
106
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Stage quick map", name: "meta_skill:stage_quick_map" },
|
|
107
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Contextual skill activation", name: "meta_skill:contextual_skills" },
|
|
108
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Protocol references", name: "meta_skill:protocol_refs" },
|
|
109
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure guardrails", name: "meta_skill:failure_guardrails" },
|
|
110
|
+
{ file: runtimeFile("references/protocols/decision.md"), needle: "# Decision Protocol", name: "protocol:decision" },
|
|
111
|
+
{ file: runtimeFile("references/protocols/completion.md"), needle: "# Stage Completion Protocol", name: "protocol:completion" },
|
|
112
|
+
{ file: runtimeFile("references/protocols/ethos.md"), needle: "# Engineering Ethos", name: "protocol:ethos" },
|
|
112
113
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
|
|
113
|
-
{ file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "
|
|
114
|
+
{ file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "common-guidance.md", name: "stage_skill:shared_guidance_reference" },
|
|
114
115
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:security:hard_gate" },
|
|
115
116
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## Checklist", name: "utility_skill:security:checklist" },
|
|
116
117
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## Severity Classification", name: "utility_skill:security:severity" },
|
|
@@ -153,7 +154,9 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
153
154
|
{ file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
|
|
154
155
|
{ file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_jump_", name: "hooks:workflow_guard:stage_jump_reason" },
|
|
155
156
|
{ file: runtimeFile("hooks/context-monitor.sh"), needle: "remaining is", name: "hooks:context:threshold_warning" },
|
|
156
|
-
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" }
|
|
157
|
+
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" },
|
|
158
|
+
{ file: runtimeFile("hooks/session-start.sh"), needle: "Knowledge digest", name: "hooks:session_start:knowledge_digest" },
|
|
159
|
+
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "Knowledge digest", name: "hooks:opencode:knowledge_digest" }
|
|
157
160
|
];
|
|
158
161
|
if (activeHarnesses.has("opencode")) {
|
|
159
162
|
utilitySkillChecks.push({
|
package/dist/runs.js
CHANGED
|
@@ -128,11 +128,15 @@ function sanitizeGuardEvidence(value) {
|
|
|
128
128
|
return next;
|
|
129
129
|
}
|
|
130
130
|
function sanitizeStageGateCatalog(value, fallback) {
|
|
131
|
+
const uniqueStrings = (items) => [...new Set(items)];
|
|
131
132
|
const next = {};
|
|
132
133
|
for (const stage of COMMAND_FILE_ORDER) {
|
|
133
134
|
const base = fallback[stage];
|
|
134
135
|
next[stage] = {
|
|
135
136
|
required: [...base.required],
|
|
137
|
+
recommended: [...base.recommended],
|
|
138
|
+
conditional: [...base.conditional],
|
|
139
|
+
triggered: [...base.triggered],
|
|
136
140
|
passed: [...base.passed],
|
|
137
141
|
blocked: [...base.blocked]
|
|
138
142
|
};
|
|
@@ -147,11 +151,24 @@ function sanitizeStageGateCatalog(value, fallback) {
|
|
|
147
151
|
continue;
|
|
148
152
|
}
|
|
149
153
|
const typed = rawStage;
|
|
150
|
-
const
|
|
154
|
+
const stageState = next[stage];
|
|
155
|
+
const allowedGateIds = new Set([
|
|
156
|
+
...stageState.required,
|
|
157
|
+
...stageState.recommended,
|
|
158
|
+
...stageState.conditional
|
|
159
|
+
]);
|
|
160
|
+
const conditionalGateIds = new Set(stageState.conditional);
|
|
161
|
+
const passed = sanitizeStringArray(typed.passed).filter((gate) => allowedGateIds.has(gate));
|
|
162
|
+
const blocked = sanitizeStringArray(typed.blocked).filter((gate) => allowedGateIds.has(gate));
|
|
163
|
+
const triggeredFromState = sanitizeStringArray(typed.triggered).filter((gate) => conditionalGateIds.has(gate));
|
|
164
|
+
const touchedConditionals = [...passed, ...blocked].filter((gate) => conditionalGateIds.has(gate));
|
|
151
165
|
next[stage] = {
|
|
152
|
-
required: [...
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
required: [...stageState.required],
|
|
167
|
+
recommended: [...stageState.recommended],
|
|
168
|
+
conditional: [...stageState.conditional],
|
|
169
|
+
triggered: uniqueStrings([...triggeredFromState, ...touchedConditionals]),
|
|
170
|
+
passed,
|
|
171
|
+
blocked
|
|
155
172
|
};
|
|
156
173
|
}
|
|
157
174
|
return next;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FlowTrack, TrackHeuristicRule, TrackHeuristicsConfig } from "./types.js";
|
|
2
|
+
export interface TrackResolution {
|
|
3
|
+
track: FlowTrack;
|
|
4
|
+
reason: string;
|
|
5
|
+
matchedTokens: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveTrackFromPrompt(prompt: string, config: TrackHeuristicsConfig | undefined): TrackResolution;
|
|
8
|
+
export declare const TRACK_HEURISTICS_DEFAULTS: {
|
|
9
|
+
readonly fallback: "standard";
|
|
10
|
+
readonly priority: ("quick" | "medium" | "standard")[];
|
|
11
|
+
readonly tracks: Record<"quick" | "medium" | "standard", TrackHeuristicRule>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { FLOW_TRACKS } from "./types.js";
|
|
2
|
+
const DEFAULT_RULES = {
|
|
3
|
+
quick: {
|
|
4
|
+
triggers: [
|
|
5
|
+
"bug",
|
|
6
|
+
"bugfix",
|
|
7
|
+
"fix",
|
|
8
|
+
"hotfix",
|
|
9
|
+
"patch",
|
|
10
|
+
"typo",
|
|
11
|
+
"regression",
|
|
12
|
+
"copy change",
|
|
13
|
+
"rename",
|
|
14
|
+
"bump",
|
|
15
|
+
"upgrade dep",
|
|
16
|
+
"config tweak",
|
|
17
|
+
"docs only",
|
|
18
|
+
"comment",
|
|
19
|
+
"lint",
|
|
20
|
+
"format",
|
|
21
|
+
"small",
|
|
22
|
+
"tiny",
|
|
23
|
+
"one-liner",
|
|
24
|
+
"revert"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
medium: {
|
|
28
|
+
triggers: [
|
|
29
|
+
"add endpoint",
|
|
30
|
+
"add field",
|
|
31
|
+
"extend existing",
|
|
32
|
+
"wire integration",
|
|
33
|
+
"small migration",
|
|
34
|
+
"new screen following existing pattern"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
standard: {
|
|
38
|
+
triggers: [
|
|
39
|
+
"new feature",
|
|
40
|
+
"refactor",
|
|
41
|
+
"migration",
|
|
42
|
+
"platform",
|
|
43
|
+
"architecture",
|
|
44
|
+
"schema",
|
|
45
|
+
"integrate",
|
|
46
|
+
"workflow",
|
|
47
|
+
"onboarding"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const DEFAULT_PRIORITY = ["standard", "medium", "quick"];
|
|
52
|
+
const DEFAULT_FALLBACK = "standard";
|
|
53
|
+
function hasToken(promptLower, token) {
|
|
54
|
+
return promptLower.includes(token.toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
function matchRule(promptLower, rule) {
|
|
57
|
+
if (!rule)
|
|
58
|
+
return [];
|
|
59
|
+
const matches = [];
|
|
60
|
+
for (const trigger of rule.triggers ?? []) {
|
|
61
|
+
if (hasToken(promptLower, trigger)) {
|
|
62
|
+
matches.push(trigger);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const pattern of rule.patterns ?? []) {
|
|
66
|
+
try {
|
|
67
|
+
const regex = new RegExp(pattern, "iu");
|
|
68
|
+
if (regex.test(promptLower)) {
|
|
69
|
+
matches.push(`/${pattern}/`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Ignore invalid custom regex entries; config validation should catch these.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [...new Set(matches)];
|
|
77
|
+
}
|
|
78
|
+
function isValidTrack(value) {
|
|
79
|
+
return FLOW_TRACKS.includes(value);
|
|
80
|
+
}
|
|
81
|
+
function mergeRules(base, overrides) {
|
|
82
|
+
const merged = { ...base };
|
|
83
|
+
const over = overrides?.tracks;
|
|
84
|
+
if (!over)
|
|
85
|
+
return merged;
|
|
86
|
+
for (const track of FLOW_TRACKS) {
|
|
87
|
+
const rule = over[track];
|
|
88
|
+
if (!rule)
|
|
89
|
+
continue;
|
|
90
|
+
merged[track] = {
|
|
91
|
+
triggers: rule.triggers ?? merged[track].triggers,
|
|
92
|
+
patterns: rule.patterns ?? merged[track].patterns,
|
|
93
|
+
veto: rule.veto ?? merged[track].veto
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return merged;
|
|
97
|
+
}
|
|
98
|
+
function resolvePriority(config) {
|
|
99
|
+
const configured = config?.priority ?? [];
|
|
100
|
+
const filtered = configured.filter((track) => isValidTrack(track));
|
|
101
|
+
const unique = [...new Set(filtered)];
|
|
102
|
+
if (unique.length === 0)
|
|
103
|
+
return [...DEFAULT_PRIORITY];
|
|
104
|
+
// Ensure all tracks are still represented in deterministic order.
|
|
105
|
+
for (const track of FLOW_TRACKS) {
|
|
106
|
+
if (!unique.includes(track))
|
|
107
|
+
unique.push(track);
|
|
108
|
+
}
|
|
109
|
+
return unique;
|
|
110
|
+
}
|
|
111
|
+
function resolveFallback(config) {
|
|
112
|
+
return config?.fallback && isValidTrack(config.fallback) ? config.fallback : DEFAULT_FALLBACK;
|
|
113
|
+
}
|
|
114
|
+
export function resolveTrackFromPrompt(prompt, config) {
|
|
115
|
+
const promptLower = prompt.toLowerCase();
|
|
116
|
+
const rules = mergeRules(DEFAULT_RULES, config);
|
|
117
|
+
const priority = resolvePriority(config);
|
|
118
|
+
const fallback = resolveFallback(config);
|
|
119
|
+
for (const track of priority) {
|
|
120
|
+
const rule = rules[track];
|
|
121
|
+
const vetoes = rule.veto ?? [];
|
|
122
|
+
if (vetoes.some((token) => hasToken(promptLower, token))) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const matched = matchRule(promptLower, rule);
|
|
126
|
+
if (matched.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
track,
|
|
129
|
+
reason: `matched ${track} heuristic`,
|
|
130
|
+
matchedTokens: matched
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
track: fallback,
|
|
136
|
+
reason: `no explicit match, fallback=${fallback}`,
|
|
137
|
+
matchedTokens: []
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export const TRACK_HEURISTICS_DEFAULTS = {
|
|
141
|
+
fallback: DEFAULT_FALLBACK,
|
|
142
|
+
priority: DEFAULT_PRIORITY,
|
|
143
|
+
tracks: DEFAULT_RULES
|
|
144
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"];
|
|
2
2
|
export type FlowStage = (typeof FLOW_STAGES)[number];
|
|
3
|
-
export declare const FLOW_TRACKS: readonly ["quick", "standard"];
|
|
3
|
+
export declare const FLOW_TRACKS: readonly ["quick", "medium", "standard"];
|
|
4
4
|
export type FlowTrack = (typeof FLOW_TRACKS)[number];
|
|
5
5
|
/**
|
|
6
6
|
* Ordered stages that make up each flow track.
|
|
7
7
|
*
|
|
8
8
|
* - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
|
|
9
|
+
* - `medium` keeps product framing but skips heavy scope/design lock-in:
|
|
10
|
+
* brainstorm -> spec -> plan -> tdd -> review -> ship.
|
|
9
11
|
* - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
|
|
10
12
|
* small bug fixes or single-purpose changes where the spec is already known.
|
|
11
13
|
* It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
|
|
@@ -16,8 +18,8 @@ export type HarnessId = (typeof HARNESS_IDS)[number];
|
|
|
16
18
|
/**
|
|
17
19
|
* Init profiles pre-fill `cclaw init` flags for common install shapes.
|
|
18
20
|
*
|
|
19
|
-
* - `minimal` — single-harness (claude),
|
|
20
|
-
* contributors
|
|
21
|
+
* - `minimal` — single-harness (claude), medium track default, no git hook guards. For solo
|
|
22
|
+
* contributors who still want brainstorm/spec/plan rigor without full scope+design overhead.
|
|
21
23
|
* - `standard` — default harness set, standard track, no git hook guards, advisory guards.
|
|
22
24
|
* Matches the pre-profile default behavior.
|
|
23
25
|
* - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
|
|
@@ -35,6 +37,22 @@ export type InitProfile = (typeof INIT_PROFILES)[number];
|
|
|
35
37
|
*/
|
|
36
38
|
export declare const LANGUAGE_RULE_PACKS: readonly ["typescript", "python", "go"];
|
|
37
39
|
export type LanguageRulePack = (typeof LANGUAGE_RULE_PACKS)[number];
|
|
40
|
+
export interface TrackHeuristicRule {
|
|
41
|
+
triggers?: string[];
|
|
42
|
+
patterns?: string[];
|
|
43
|
+
veto?: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface TrackHeuristicsConfig {
|
|
46
|
+
/** Track used when no trigger/pattern matches. */
|
|
47
|
+
fallback?: FlowTrack;
|
|
48
|
+
/**
|
|
49
|
+
* Track evaluation order. First matching track wins.
|
|
50
|
+
* Example: ["standard", "medium", "quick"].
|
|
51
|
+
*/
|
|
52
|
+
priority?: FlowTrack[];
|
|
53
|
+
/** Per-track matching rules. */
|
|
54
|
+
tracks?: Partial<Record<FlowTrack, TrackHeuristicRule>>;
|
|
55
|
+
}
|
|
38
56
|
export interface VibyConfig {
|
|
39
57
|
version: string;
|
|
40
58
|
flowVersion: string;
|
|
@@ -54,6 +72,11 @@ export interface VibyConfig {
|
|
|
54
72
|
* the language in question. Disabled packs have no on-disk footprint.
|
|
55
73
|
*/
|
|
56
74
|
languageRulePacks?: LanguageRulePack[];
|
|
75
|
+
/**
|
|
76
|
+
* Optional prompt-to-track mapping overrides for /cc classification.
|
|
77
|
+
* If omitted, cclaw uses built-in defaults.
|
|
78
|
+
*/
|
|
79
|
+
trackHeuristics?: TrackHeuristicsConfig;
|
|
57
80
|
}
|
|
58
81
|
export interface TransitionRule {
|
|
59
82
|
from: FlowStage;
|
package/dist/types.js
CHANGED
|
@@ -8,25 +8,28 @@ export const FLOW_STAGES = [
|
|
|
8
8
|
"review",
|
|
9
9
|
"ship"
|
|
10
10
|
];
|
|
11
|
-
export const FLOW_TRACKS = ["quick", "standard"];
|
|
11
|
+
export const FLOW_TRACKS = ["quick", "medium", "standard"];
|
|
12
12
|
/**
|
|
13
13
|
* Ordered stages that make up each flow track.
|
|
14
14
|
*
|
|
15
15
|
* - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
|
|
16
|
+
* - `medium` keeps product framing but skips heavy scope/design lock-in:
|
|
17
|
+
* brainstorm -> spec -> plan -> tdd -> review -> ship.
|
|
16
18
|
* - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
|
|
17
19
|
* small bug fixes or single-purpose changes where the spec is already known.
|
|
18
20
|
* It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
|
|
19
21
|
*/
|
|
20
22
|
export const TRACK_STAGES = {
|
|
21
23
|
standard: FLOW_STAGES,
|
|
24
|
+
medium: ["brainstorm", "spec", "plan", "tdd", "review", "ship"],
|
|
22
25
|
quick: ["spec", "tdd", "review", "ship"]
|
|
23
26
|
};
|
|
24
27
|
export const HARNESS_IDS = ["claude", "cursor", "opencode", "codex"];
|
|
25
28
|
/**
|
|
26
29
|
* Init profiles pre-fill `cclaw init` flags for common install shapes.
|
|
27
30
|
*
|
|
28
|
-
* - `minimal` — single-harness (claude),
|
|
29
|
-
* contributors
|
|
31
|
+
* - `minimal` — single-harness (claude), medium track default, no git hook guards. For solo
|
|
32
|
+
* contributors who still want brainstorm/spec/plan rigor without full scope+design overhead.
|
|
30
33
|
* - `standard` — default harness set, standard track, no git hook guards, advisory guards.
|
|
31
34
|
* Matches the pre-profile default behavior.
|
|
32
35
|
* - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
|