cclaw-cli 0.48.4 → 0.48.6
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.js +32 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +44 -5
- package/dist/content/hooks.js +111 -42
- package/dist/content/ideate-command.js +11 -0
- package/dist/content/iron-laws.d.ts +134 -0
- package/dist/content/iron-laws.js +182 -0
- package/dist/content/meta-skill.js +1 -0
- package/dist/content/next-command.js +12 -0
- package/dist/content/observe.js +189 -4
- package/dist/content/ops-command.js +11 -0
- package/dist/content/session-hooks.js +3 -1
- package/dist/content/stage-schema.d.ts +16 -0
- package/dist/content/stage-schema.js +82 -5
- package/dist/content/stages/review.js +2 -2
- package/dist/content/stages/tdd.js +7 -7
- package/dist/content/start-command.js +12 -0
- package/dist/content/subagents.js +26 -0
- package/dist/content/view-command.js +11 -0
- package/dist/eval/agents/workflow.js +22 -2
- package/dist/gate-evidence.js +30 -1
- package/dist/harness-adapters.js +3 -0
- package/dist/install.js +8 -0
- package/dist/internal/advance-stage.js +10 -2
- package/dist/internal/envelope-validate.d.ts +7 -0
- package/dist/internal/envelope-validate.js +66 -0
- package/dist/internal/knowledge-digest.d.ts +7 -0
- package/dist/internal/knowledge-digest.js +93 -0
- package/dist/knowledge-store.d.ts +8 -0
- package/dist/knowledge-store.js +95 -0
- package/dist/tdd-cycle.d.ts +7 -0
- package/dist/tdd-cycle.js +29 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
export const IRON_LAWS = [
|
|
3
|
+
{
|
|
4
|
+
id: "tdd-red-before-write",
|
|
5
|
+
title: "RED before production write",
|
|
6
|
+
rule: "Do not edit production code in tdd stage before a failing RED test exists for the slice.",
|
|
7
|
+
rationale: "Prevents implementation-first behavior and keeps RED as executable specification.",
|
|
8
|
+
enforcement: "PreToolUse",
|
|
9
|
+
severity: "hard-gate",
|
|
10
|
+
appliesTo: ["tdd"],
|
|
11
|
+
hookMatcher: {
|
|
12
|
+
toolPattern: "write|edit|multiedit|applypatch|shell|bash",
|
|
13
|
+
payloadPattern: "\\.(ts|tsx|js|jsx|py|go|java|rs|rb|php|c|cc|cpp|h|hpp)"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "plan-requires-approval",
|
|
18
|
+
title: "No implementation before plan approval",
|
|
19
|
+
rule: "Do not perform write-like actions while plan stage is pending WAIT_FOR_CONFIRM approval.",
|
|
20
|
+
rationale: "Locks intent before execution and reduces expensive rework from unapproved paths.",
|
|
21
|
+
enforcement: "PreToolUse",
|
|
22
|
+
severity: "hard-gate",
|
|
23
|
+
appliesTo: ["plan"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "runtime-writes-managed-only",
|
|
27
|
+
title: "Runtime writes are managed",
|
|
28
|
+
rule: `Do not mutate ${RUNTIME_ROOT}/state, ${RUNTIME_ROOT}/hooks, or ${RUNTIME_ROOT}/skills by ad-hoc edits unless using cclaw-managed commands.`,
|
|
29
|
+
rationale: "Protects generated runtime integrity and avoids drift that silently breaks hooks or skills.",
|
|
30
|
+
enforcement: "PreToolUse",
|
|
31
|
+
severity: "hard-gate",
|
|
32
|
+
appliesTo: "all",
|
|
33
|
+
hookMatcher: {
|
|
34
|
+
toolPattern: "write|edit|multiedit|delete|applypatch|shell|bash",
|
|
35
|
+
payloadPattern: "\\.cclaw/(state|hooks|skills)"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "flow-state-read-fresh",
|
|
40
|
+
title: "Fresh flow-state read required",
|
|
41
|
+
rule: `Before mutating actions, a fresh read of ${RUNTIME_ROOT}/state/flow-state.json must exist within guard freshness window.`,
|
|
42
|
+
rationale: "Prevents stale-stage mutations after context shifts or multi-agent divergence.",
|
|
43
|
+
enforcement: "PreToolUse",
|
|
44
|
+
severity: "hard-gate",
|
|
45
|
+
appliesTo: "all"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "review-layer-order",
|
|
49
|
+
title: "Review layers are sequential",
|
|
50
|
+
rule: "Review stage must complete Layer 1 spec compliance before Layer 2 quality/security passes.",
|
|
51
|
+
rationale: "Stops premature quality discussion when acceptance criteria are not yet satisfied.",
|
|
52
|
+
enforcement: "advisory",
|
|
53
|
+
severity: "soft-gate",
|
|
54
|
+
appliesTo: ["review"]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "review-criticals-close-before-ship",
|
|
58
|
+
title: "No ship with open criticals",
|
|
59
|
+
rule: "Ship decisions are blocked when review-army contains open Critical findings or ship blockers.",
|
|
60
|
+
rationale: "Enforces explicit risk closure before release finalization.",
|
|
61
|
+
enforcement: "PreToolUse",
|
|
62
|
+
severity: "hard-gate",
|
|
63
|
+
appliesTo: ["ship"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "ship-preflight-required",
|
|
67
|
+
title: "Preflight required before finalization",
|
|
68
|
+
rule: "Do not execute release finalization actions until ship preflight gate is passed.",
|
|
69
|
+
rationale: "Catches regressions before irreversible release steps.",
|
|
70
|
+
enforcement: "PreToolUse",
|
|
71
|
+
severity: "hard-gate",
|
|
72
|
+
appliesTo: ["ship"]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "subagent-task-self-contained",
|
|
76
|
+
title: "Subagent tasks are self-contained",
|
|
77
|
+
rule: "Delegated tasks must include explicit objective, constraints, and expected output, not just references.",
|
|
78
|
+
rationale: "Avoids context loss and low-quality delegation in isolated worker contexts.",
|
|
79
|
+
enforcement: "advisory",
|
|
80
|
+
severity: "soft-gate",
|
|
81
|
+
appliesTo: "all"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "no-secrets-in-artifacts",
|
|
85
|
+
title: "Never log secrets in artifacts",
|
|
86
|
+
rule: "Secrets/tokens/passwords must not be written to review, ship, or runtime state artifacts.",
|
|
87
|
+
rationale: "Prevents accidental credential leakage through generated workflow artifacts.",
|
|
88
|
+
enforcement: "PostToolUse",
|
|
89
|
+
severity: "hard-gate",
|
|
90
|
+
appliesTo: "all"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "stop-clean-or-checkpointed",
|
|
94
|
+
title: "Stop only from clean checkpoint",
|
|
95
|
+
rule: "Do not end a session with dirty state unless checkpoint explicitly records unresolved work and blockers.",
|
|
96
|
+
rationale: "Protects continuity and prevents silent half-finished sessions.",
|
|
97
|
+
enforcement: "Stop",
|
|
98
|
+
severity: "hard-gate",
|
|
99
|
+
appliesTo: "all"
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
export function isIronLawId(value) {
|
|
103
|
+
return IRON_LAWS.some((law) => law.id === value);
|
|
104
|
+
}
|
|
105
|
+
export function normalizeStrictLawIds(ids) {
|
|
106
|
+
if (!Array.isArray(ids))
|
|
107
|
+
return [];
|
|
108
|
+
const unique = new Set();
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
if (typeof id !== "string")
|
|
111
|
+
continue;
|
|
112
|
+
const trimmed = id.trim();
|
|
113
|
+
if (!trimmed || !isIronLawId(trimmed))
|
|
114
|
+
continue;
|
|
115
|
+
unique.add(trimmed);
|
|
116
|
+
}
|
|
117
|
+
return [...unique];
|
|
118
|
+
}
|
|
119
|
+
export function ironLawRuntimeDocument(options = {}) {
|
|
120
|
+
const mode = options.mode === "strict" ? "strict" : "advisory";
|
|
121
|
+
const strictLawSet = new Set(normalizeStrictLawIds(options.strictLaws));
|
|
122
|
+
const laws = IRON_LAWS.map((law) => ({
|
|
123
|
+
id: law.id,
|
|
124
|
+
title: law.title,
|
|
125
|
+
rule: law.rule,
|
|
126
|
+
enforcement: law.enforcement,
|
|
127
|
+
severity: law.severity,
|
|
128
|
+
appliesTo: law.appliesTo === "all" ? "all" : [...law.appliesTo],
|
|
129
|
+
strict: mode === "strict" || strictLawSet.has(law.id),
|
|
130
|
+
hookMatcher: "hookMatcher" in law ? law.hookMatcher : undefined
|
|
131
|
+
}));
|
|
132
|
+
return {
|
|
133
|
+
version: 1,
|
|
134
|
+
generatedAt: options.nowIso ?? new Date().toISOString(),
|
|
135
|
+
mode,
|
|
136
|
+
strictLaws: [...strictLawSet],
|
|
137
|
+
laws
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function ironLawsAgentsMdBlock() {
|
|
141
|
+
const rows = IRON_LAWS.map((law) => {
|
|
142
|
+
return `| \`${law.id}\` | ${law.rule} | ${law.enforcement} | ${law.severity} |`;
|
|
143
|
+
}).join("\n");
|
|
144
|
+
return `### Iron Laws
|
|
145
|
+
|
|
146
|
+
These rules are always-on. Hook-enforced laws can block actions in strict mode.
|
|
147
|
+
|
|
148
|
+
| ID | Rule | Enforced by | Level |
|
|
149
|
+
|---|---|---|---|
|
|
150
|
+
${rows}
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
export function ironLawsSkillMarkdown() {
|
|
154
|
+
const list = IRON_LAWS.map((law, index) => {
|
|
155
|
+
const applies = law.appliesTo === "all" ? "all stages" : law.appliesTo.join(", ");
|
|
156
|
+
return `### ${index + 1}. ${law.title}
|
|
157
|
+
|
|
158
|
+
- **ID:** \`${law.id}\`
|
|
159
|
+
- **Rule:** ${law.rule}
|
|
160
|
+
- **Why:** ${law.rationale}
|
|
161
|
+
- **Applies to:** ${applies}
|
|
162
|
+
- **Enforced by:** ${law.enforcement} (${law.severity})
|
|
163
|
+
`;
|
|
164
|
+
}).join("\n");
|
|
165
|
+
return `---
|
|
166
|
+
name: iron-laws
|
|
167
|
+
description: "Non-negotiable workflow constraints enforced by cclaw hooks and routing."
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
# Iron Laws
|
|
171
|
+
|
|
172
|
+
These are cclaw's non-negotiable constraints for harness sessions.
|
|
173
|
+
Use them as the final arbitration layer when local instructions conflict.
|
|
174
|
+
|
|
175
|
+
${list}
|
|
176
|
+
|
|
177
|
+
## Practical rule
|
|
178
|
+
|
|
179
|
+
If a law says stop, stop and surface the blocking reason with the smallest safe
|
|
180
|
+
next step.
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
@@ -85,6 +85,7 @@ Load utility skills only when triggered by the current task:
|
|
|
85
85
|
- verification-before-completion before completion claims
|
|
86
86
|
- finishing-a-development-branch during ship/finalization
|
|
87
87
|
- document-review, receiving-code-review, and execution context skills
|
|
88
|
+
- iron-laws as policy arbitration when instructions conflict
|
|
88
89
|
- language rule packs from \`.cclaw/config.yaml\` when enabled
|
|
89
90
|
|
|
90
91
|
Custom project skills under \`.cclaw/custom-skills/\` are opt-in supplements,
|
|
@@ -102,6 +102,18 @@ This is the only progression command the user needs to drive the entire flow. St
|
|
|
102
102
|
regenerating the retro draft.
|
|
103
103
|
- No special resume command needed — \`/cc-next\` IS the resume command.
|
|
104
104
|
|
|
105
|
+
## Headless mode
|
|
106
|
+
|
|
107
|
+
When orchestrated by another skill/subagent, emit exactly one JSON envelope and
|
|
108
|
+
no narrative text:
|
|
109
|
+
|
|
110
|
+
\`\`\`json
|
|
111
|
+
{"version":"1","kind":"gate-result","stage":"review","payload":{"command":"/cc-next","decision":"resume_or_advance","nextStage":"ship"},"emittedAt":"<ISO-8601>"}
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
Validate envelopes with:
|
|
115
|
+
\`cclaw internal envelope-validate --stdin\`
|
|
116
|
+
|
|
105
117
|
## Primary skill
|
|
106
118
|
|
|
107
119
|
**${skillRel}** — full protocol and stage table.
|
package/dist/content/observe.js
CHANGED
|
@@ -179,6 +179,7 @@ ${RUNTIME_SHELL_DETECT_ROOT}
|
|
|
179
179
|
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
180
180
|
FLOW_STATE_FILE="$STATE_DIR/flow-state.json"
|
|
181
181
|
TDD_LOG_FILE="$STATE_DIR/tdd-cycle-log.jsonl"
|
|
182
|
+
IRON_LAWS_FILE="$STATE_DIR/iron-laws.json"
|
|
182
183
|
GUARD_STATE_FILE="$STATE_DIR/workflow-guard.json"
|
|
183
184
|
GUARD_LOG="$STATE_DIR/workflow-guard.jsonl"
|
|
184
185
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
@@ -247,6 +248,14 @@ TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
|
247
248
|
NOW_EPOCH=$(date +%s 2>/dev/null || echo "0")
|
|
248
249
|
REASONS=""
|
|
249
250
|
|
|
251
|
+
ACTIVE_AGENT="\${CCLAW_ACTIVE_AGENT:-}"
|
|
252
|
+
if [ -z "$ACTIVE_AGENT" ]; then
|
|
253
|
+
if command -v jq >/dev/null 2>&1; then
|
|
254
|
+
ACTIVE_AGENT=$(printf '%s' "$INPUT" | jq -r '.agent_name // .agent // .input.agent_name // .input.agent // .tool_input.agent_name // .tool_input.agent // ""' 2>/dev/null || echo "")
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
ACTIVE_AGENT_LOWER=$(printf '%s' "$ACTIVE_AGENT" | tr '[:upper:]' '[:lower:]')
|
|
258
|
+
|
|
250
259
|
CURRENT_STAGE="none"
|
|
251
260
|
CURRENT_RUN="active"
|
|
252
261
|
if [ -f "$FLOW_STATE_FILE" ]; then
|
|
@@ -287,6 +296,87 @@ PY
|
|
|
287
296
|
fi
|
|
288
297
|
fi
|
|
289
298
|
|
|
299
|
+
SHIP_PREFLIGHT_PASSED="false"
|
|
300
|
+
if [ -f "$FLOW_STATE_FILE" ]; then
|
|
301
|
+
if command -v jq >/dev/null 2>&1; then
|
|
302
|
+
SHIP_PREFLIGHT_PASSED=$(jq -r '
|
|
303
|
+
if ((.stageGateCatalog.ship.passed // []) | index("ship_preflight_passed")) == null
|
|
304
|
+
then "false"
|
|
305
|
+
else "true"
|
|
306
|
+
end
|
|
307
|
+
' "$FLOW_STATE_FILE" 2>/dev/null || echo "false")
|
|
308
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
309
|
+
SHIP_PREFLIGHT_PASSED=$(python3 - "$FLOW_STATE_FILE" <<'PY'
|
|
310
|
+
import json
|
|
311
|
+
import sys
|
|
312
|
+
value = "false"
|
|
313
|
+
try:
|
|
314
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
315
|
+
parsed = json.load(fh)
|
|
316
|
+
ship = ((parsed.get("stageGateCatalog") or {}).get("ship") or {}).get("passed") or []
|
|
317
|
+
if isinstance(ship, list) and "ship_preflight_passed" in ship:
|
|
318
|
+
value = "true"
|
|
319
|
+
except Exception:
|
|
320
|
+
value = "false"
|
|
321
|
+
print(value)
|
|
322
|
+
PY
|
|
323
|
+
)
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
IRON_LAW_STRICT_ALL="false"
|
|
328
|
+
IRON_LAW_STRICT_IDS=""
|
|
329
|
+
if [ -f "$IRON_LAWS_FILE" ]; then
|
|
330
|
+
if command -v jq >/dev/null 2>&1; then
|
|
331
|
+
IRON_LAW_STRICT_ALL=$(jq -r 'if (.mode // "advisory") == "strict" then "true" else "false" end' "$IRON_LAWS_FILE" 2>/dev/null || echo "false")
|
|
332
|
+
IRON_LAW_STRICT_IDS=$(jq -r '(.laws // []) | map(select(.strict == true) | (.id // "")) | map(select(length > 0)) | join(",")' "$IRON_LAWS_FILE" 2>/dev/null || echo "")
|
|
333
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
334
|
+
IRON_LAW_STRICT_ALL=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
335
|
+
import json
|
|
336
|
+
import sys
|
|
337
|
+
mode = "false"
|
|
338
|
+
try:
|
|
339
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
340
|
+
parsed = json.load(fh)
|
|
341
|
+
if str(parsed.get("mode", "advisory")) == "strict":
|
|
342
|
+
mode = "true"
|
|
343
|
+
except Exception:
|
|
344
|
+
mode = "false"
|
|
345
|
+
print(mode)
|
|
346
|
+
PY
|
|
347
|
+
)
|
|
348
|
+
IRON_LAW_STRICT_IDS=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
349
|
+
import json
|
|
350
|
+
import sys
|
|
351
|
+
out = []
|
|
352
|
+
try:
|
|
353
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
354
|
+
parsed = json.load(fh)
|
|
355
|
+
for row in parsed.get("laws", []):
|
|
356
|
+
if isinstance(row, dict) and row.get("strict") and isinstance(row.get("id"), str):
|
|
357
|
+
out.append(row["id"].strip())
|
|
358
|
+
except Exception:
|
|
359
|
+
out = []
|
|
360
|
+
print(",".join([v for v in out if v]))
|
|
361
|
+
PY
|
|
362
|
+
)
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
iron_law_is_strict() {
|
|
367
|
+
local law_id="$1"
|
|
368
|
+
if [ "$IRON_LAW_STRICT_ALL" = "true" ]; then
|
|
369
|
+
return 0
|
|
370
|
+
fi
|
|
371
|
+
if [ -z "$IRON_LAW_STRICT_IDS" ]; then
|
|
372
|
+
return 1
|
|
373
|
+
fi
|
|
374
|
+
case ",$IRON_LAW_STRICT_IDS," in
|
|
375
|
+
*",$law_id,"*) return 0 ;;
|
|
376
|
+
*) return 1 ;;
|
|
377
|
+
esac
|
|
378
|
+
}
|
|
379
|
+
|
|
290
380
|
LAST_FLOW_READ_AT=0
|
|
291
381
|
if [ -f "$GUARD_STATE_FILE" ]; then
|
|
292
382
|
if command -v jq >/dev/null 2>&1; then
|
|
@@ -332,6 +422,14 @@ is_mutating_tool() {
|
|
|
332
422
|
esac
|
|
333
423
|
}
|
|
334
424
|
|
|
425
|
+
is_execution_or_mutating_tool() {
|
|
426
|
+
case "$1" in
|
|
427
|
+
write|edit|multiedit|multi_edit|delete|applypatch|apply_patch) return 0 ;;
|
|
428
|
+
shell|bash|runcommand|run_command|execcommand|exec_command|terminal) return 0 ;;
|
|
429
|
+
*) return 1 ;;
|
|
430
|
+
esac
|
|
431
|
+
}
|
|
432
|
+
|
|
335
433
|
is_plan_mode_safe_tool() {
|
|
336
434
|
case "$1" in
|
|
337
435
|
read|readfile|open|view|cat|head|tail) return 0 ;;
|
|
@@ -817,6 +915,65 @@ if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
|
817
915
|
fi
|
|
818
916
|
fi
|
|
819
917
|
|
|
918
|
+
if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
919
|
+
if [ "$ACTIVE_AGENT_LOWER" = "tdd-red" ] && is_tdd_production_write_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
920
|
+
if [ -n "$REASONS" ]; then
|
|
921
|
+
REASONS="$REASONS,tdd_red_agent_cannot_write_production"
|
|
922
|
+
else
|
|
923
|
+
REASONS="tdd_red_agent_cannot_write_production"
|
|
924
|
+
fi
|
|
925
|
+
fi
|
|
926
|
+
if [ "$ACTIVE_AGENT_LOWER" = "tdd-green" ] && is_tdd_test_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
927
|
+
if [ -n "$REASONS" ]; then
|
|
928
|
+
REASONS="$REASONS,tdd_green_agent_cannot_write_tests"
|
|
929
|
+
else
|
|
930
|
+
REASONS="tdd_green_agent_cannot_write_tests"
|
|
931
|
+
fi
|
|
932
|
+
fi
|
|
933
|
+
if [ "$ACTIVE_AGENT_LOWER" = "tdd-refactor" ]; then
|
|
934
|
+
TDD_AGENT_STATE=$(tdd_cycle_state)
|
|
935
|
+
if [ "$TDD_AGENT_STATE" != "green_done" ]; then
|
|
936
|
+
if [ -n "$REASONS" ]; then
|
|
937
|
+
REASONS="$REASONS,tdd_refactor_before_green"
|
|
938
|
+
else
|
|
939
|
+
REASONS="tdd_refactor_before_green"
|
|
940
|
+
fi
|
|
941
|
+
fi
|
|
942
|
+
fi
|
|
943
|
+
fi
|
|
944
|
+
|
|
945
|
+
if is_mutating_tool "$TOOL_LOWER"; then
|
|
946
|
+
if [ "$LAST_FLOW_READ_AT" -le 0 ] || [ "$NOW_EPOCH" -le 0 ] || [ $((NOW_EPOCH - LAST_FLOW_READ_AT)) -gt "$MAX_FLOW_READ_AGE_SEC" ]; then
|
|
947
|
+
if [ -n "$REASONS" ]; then
|
|
948
|
+
REASONS="$REASONS,mutating_without_recent_flow_read"
|
|
949
|
+
else
|
|
950
|
+
REASONS="mutating_without_recent_flow_read"
|
|
951
|
+
fi
|
|
952
|
+
fi
|
|
953
|
+
fi
|
|
954
|
+
|
|
955
|
+
if is_mutating_tool "$TOOL_LOWER" && printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/(state|hooks|skills)'; then
|
|
956
|
+
if ! is_cclaw_cli_payload "$PAYLOAD_LOWER"; then
|
|
957
|
+
if [ -n "$REASONS" ]; then
|
|
958
|
+
REASONS="$REASONS,runtime_write_requires_managed_only"
|
|
959
|
+
else
|
|
960
|
+
REASONS="runtime_write_requires_managed_only"
|
|
961
|
+
fi
|
|
962
|
+
fi
|
|
963
|
+
fi
|
|
964
|
+
|
|
965
|
+
if [ "$CURRENT_STAGE" = "ship" ] && is_execution_or_mutating_tool "$TOOL_LOWER"; then
|
|
966
|
+
if printf '%s' "$PAYLOAD_LOWER" | grep -Eq '(npm publish|pnpm publish|yarn publish|gh release create|git push[[:space:]].*--tags|npm version)'; then
|
|
967
|
+
if [ "$SHIP_PREFLIGHT_PASSED" != "true" ]; then
|
|
968
|
+
if [ -n "$REASONS" ]; then
|
|
969
|
+
REASONS="$REASONS,ship_preflight_required"
|
|
970
|
+
else
|
|
971
|
+
REASONS="ship_preflight_required"
|
|
972
|
+
fi
|
|
973
|
+
fi
|
|
974
|
+
fi
|
|
975
|
+
fi
|
|
976
|
+
|
|
820
977
|
if is_preimplementation_stage "$CURRENT_STAGE" && ! is_plan_mode_safe_tool "$TOOL_LOWER"; then
|
|
821
978
|
if ! is_mutating_tool "$TOOL_LOWER"; then
|
|
822
979
|
if ! printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/' && ! is_cclaw_cli_payload "$PAYLOAD_LOWER"; then
|
|
@@ -882,12 +1039,22 @@ PY
|
|
|
882
1039
|
fi
|
|
883
1040
|
|
|
884
1041
|
if [ -n "$REASONS" ]; then
|
|
885
|
-
if printf '%s' "$REASONS" | grep -Eq '
|
|
1042
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_red_agent_cannot_write_production'; then
|
|
1043
|
+
NOTE="Cclaw workflow guard: tdd-red agent is limited to test-side RED work and cannot edit production files."
|
|
1044
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_green_agent_cannot_write_tests'; then
|
|
1045
|
+
NOTE="Cclaw workflow guard: tdd-green agent can implement production fixes but should not author new RED tests."
|
|
1046
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_refactor_before_green'; then
|
|
1047
|
+
NOTE="Cclaw workflow guard: tdd-refactor requires a green_done cycle state before refactor edits."
|
|
1048
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red'; then
|
|
886
1049
|
NOTE="Cclaw workflow guard: Write a failing test first before editing production files during tdd stage (state=\${TDD_CYCLE_STATE})."
|
|
1050
|
+
elif printf '%s' "$REASONS" | grep -Eq 'ship_preflight_required'; then
|
|
1051
|
+
NOTE="Cclaw workflow guard: ship finalization command detected before ship_preflight_passed gate. Run preflight and record evidence first."
|
|
887
1052
|
elif printf '%s' "$REASONS" | grep -Eq 'tdd_cycle_counts_unavailable'; then
|
|
888
1053
|
NOTE="Cclaw workflow guard: unable to inspect run-scoped tdd-cycle counts (missing usable jq/python3/awk). Install one of these tools before writing production code in tdd."
|
|
889
|
-
elif printf '%s' "$REASONS" | grep -Eq 'direct_flow_state_edit'; then
|
|
890
|
-
NOTE="Cclaw workflow guard:
|
|
1054
|
+
elif printf '%s' "$REASONS" | grep -Eq 'runtime_write_requires_managed_only|direct_flow_state_edit'; then
|
|
1055
|
+
NOTE="Cclaw workflow guard: runtime write to managed ${RUNTIME_ROOT} internals detected (\${REASONS}). Prefer cclaw-managed helpers (stage-complete, sync, command contracts) instead of ad-hoc edits."
|
|
1056
|
+
elif printf '%s' "$REASONS" | grep -Eq 'mutating_without_recent_flow_read'; then
|
|
1057
|
+
NOTE="Cclaw workflow guard: mutating action requires a fresh read of ${RUNTIME_ROOT}/state/flow-state.json before edits."
|
|
891
1058
|
else
|
|
892
1059
|
NOTE="Cclaw workflow guard: detected potential flow violation (\${REASONS}). Re-read ${RUNTIME_ROOT}/state/flow-state.json, avoid source edits before tdd stage, and enforce RED -> GREEN -> REFACTOR discipline inside tdd."
|
|
893
1060
|
fi
|
|
@@ -913,9 +1080,27 @@ if [ -n "$REASONS" ]; then
|
|
|
913
1080
|
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red' && [ "$TDD_ENFORCEMENT_MODE" = "strict" ]; then
|
|
914
1081
|
SHOULD_BLOCK="true"
|
|
915
1082
|
fi
|
|
1083
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red' && iron_law_is_strict "tdd-red-before-write"; then
|
|
1084
|
+
SHOULD_BLOCK="true"
|
|
1085
|
+
fi
|
|
1086
|
+
if printf '%s' "$REASONS" | grep -Eq 'runtime_write_requires_managed_only|direct_flow_state_edit' && iron_law_is_strict "runtime-writes-managed-only"; then
|
|
1087
|
+
SHOULD_BLOCK="true"
|
|
1088
|
+
fi
|
|
1089
|
+
if printf '%s' "$REASONS" | grep -Eq 'mutating_without_recent_flow_read|stage_invocation_without_recent_flow_read' && iron_law_is_strict "flow-state-read-fresh"; then
|
|
1090
|
+
SHOULD_BLOCK="true"
|
|
1091
|
+
fi
|
|
1092
|
+
if printf '%s' "$REASONS" | grep -Eq 'ship_preflight_required' && iron_law_is_strict "ship-preflight-required"; then
|
|
1093
|
+
SHOULD_BLOCK="true"
|
|
1094
|
+
fi
|
|
1095
|
+
if printf '%s' "$REASONS" | grep -Eq 'implementation_write_before_plan_completion' && iron_law_is_strict "plan-requires-approval"; then
|
|
1096
|
+
SHOULD_BLOCK="true"
|
|
1097
|
+
fi
|
|
916
1098
|
if printf '%s' "$REASONS" | grep -Eq 'tdd_cycle_counts_unavailable'; then
|
|
917
1099
|
SHOULD_BLOCK="true"
|
|
918
1100
|
fi
|
|
1101
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_red_agent_cannot_write_production|tdd_green_agent_cannot_write_tests|tdd_refactor_before_green'; then
|
|
1102
|
+
SHOULD_BLOCK="true"
|
|
1103
|
+
fi
|
|
919
1104
|
if [ "$WORKFLOW_GUARD_MODE" = "strict" ] || [ "$SHOULD_BLOCK" = "true" ]; then
|
|
920
1105
|
printf '[cclaw] %s (blocked by workflow guard)\n' "$NOTE" >&2
|
|
921
1106
|
exit 1
|
|
@@ -1289,7 +1474,7 @@ export function codexHooksJsonWithObservation() {
|
|
|
1289
1474
|
command: hookDispatcherCommand("workflow-guard.sh")
|
|
1290
1475
|
}, {
|
|
1291
1476
|
type: "command",
|
|
1292
|
-
command: "bash -lc 'if ! command -v cclaw >/dev/null 2>&1; then echo \"[cclaw] codex hook: cclaw binary is required for verify-current-state\" >&2; exit 1; fi; cclaw internal verify-current-state --quiet >/dev/null || true'"
|
|
1477
|
+
command: "bash -lc 'if ! command -v cclaw >/dev/null 2>&1; then echo \"[cclaw] codex hook: cclaw binary is required for verify-current-state\" >&2; exit 1; fi; MODE=\"${CCLAW_WORKFLOW_GUARD_MODE:-advisory}\"; if [ \"$MODE\" = \"strict\" ]; then cclaw internal verify-current-state --quiet >/dev/null; else cclaw internal verify-current-state --quiet >/dev/null || true; fi'"
|
|
1293
1478
|
}]
|
|
1294
1479
|
}],
|
|
1295
1480
|
PreToolUse: [{
|
|
@@ -33,6 +33,17 @@ Subcommands:
|
|
|
33
33
|
- \`rewind\` -> \`${RUNTIME_ROOT}/commands/rewind.md\`
|
|
34
34
|
3. Unknown subcommand -> print supported values and stop.
|
|
35
35
|
|
|
36
|
+
## Headless mode
|
|
37
|
+
|
|
38
|
+
For skill-to-skill dispatch, emit exactly one JSON envelope:
|
|
39
|
+
|
|
40
|
+
\`\`\`json
|
|
41
|
+
{"version":"1","kind":"stage-output","stage":"ship","payload":{"command":"/cc-ops","subcommand":"archive","status":"completed"},"emittedAt":"<ISO-8601>"}
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
Validate envelopes with:
|
|
45
|
+
\`cclaw internal envelope-validate --stdin\`
|
|
46
|
+
|
|
36
47
|
## Primary skill
|
|
37
48
|
|
|
38
49
|
**${RUNTIME_ROOT}/skills/${OPS_SKILL_FOLDER}/SKILL.md**
|
|
@@ -29,7 +29,8 @@ When a new session begins in any harness:
|
|
|
29
29
|
2. **Load knowledge:** Stream the tail of \`.cclaw/knowledge.jsonl\` (strict JSONL store) and surface the most relevant rules/patterns.
|
|
30
30
|
3. **Check for in-progress work:** If the last stage is incomplete, remind the user and offer to resume.
|
|
31
31
|
4. **Load suggestion memory:** Read \`.cclaw/state/suggestion-memory.json\` and honor \`enabled=false\` opt-out.
|
|
32
|
-
5. **
|
|
32
|
+
5. **Load iron laws:** Read \`.cclaw/state/iron-laws.json\` to know which laws are strict in this repo.
|
|
33
|
+
6. **Read AGENTS.md:** The cclaw block contains routing and rules — follow them.
|
|
33
34
|
|
|
34
35
|
### What to show the user at session start
|
|
35
36
|
|
|
@@ -133,5 +134,6 @@ Session boundary behavior (real hooks inject context automatically; guidelines c
|
|
|
133
134
|
- **Resume:** Re-read state, verify artifact, re-load knowledge, continue from last step.
|
|
134
135
|
|
|
135
136
|
Skill: \`.cclaw/skills/session/SKILL.md\`
|
|
137
|
+
Policy: \`.cclaw/skills/iron-laws/SKILL.md\`
|
|
136
138
|
`;
|
|
137
139
|
}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { FlowStage, FlowTrack, TransitionRule } from "../types.js";
|
|
2
2
|
import type { StageAutoSubagentDispatch, StageSchema } from "./stages/schema-types.js";
|
|
3
3
|
export type { ArtifactValidation, CrossStageTrace, ReviewSection, StageAutoSubagentDispatch, StageGate, StageSchema, StageSchemaInput } from "./stages/schema-types.js";
|
|
4
|
+
export declare const SKILL_ENVELOPE_KINDS: readonly ["stage-output", "gate-result", "delegation-record"];
|
|
5
|
+
export type SkillEnvelopeKind = (typeof SKILL_ENVELOPE_KINDS)[number];
|
|
6
|
+
export interface SkillEnvelope {
|
|
7
|
+
version: "1";
|
|
8
|
+
kind: SkillEnvelopeKind;
|
|
9
|
+
stage: FlowStage;
|
|
10
|
+
payload: unknown;
|
|
11
|
+
emittedAt: string;
|
|
12
|
+
agent?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SkillEnvelopeValidation {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
errors: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function validateSkillEnvelope(value: unknown): SkillEnvelopeValidation;
|
|
19
|
+
export declare function parseSkillEnvelope(raw: string): SkillEnvelope | null;
|
|
4
20
|
/** Transition guard: agents with `mode: "mandatory"` in auto-subagent dispatch for this stage. */
|
|
5
21
|
export declare function mandatoryDelegationsForStage(stage: FlowStage): string[];
|
|
6
22
|
export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
|
|
@@ -2,6 +2,65 @@ import { FLOW_STAGES, FLOW_TRACKS, TRACK_STAGES } from "../types.js";
|
|
|
2
2
|
import { STAGE_TO_SKILL_FOLDER } from "../constants.js";
|
|
3
3
|
import { BRAINSTORM, SCOPE, DESIGN, SPEC, PLAN, TDD, REVIEW, SHIP } from "./stages/index.js";
|
|
4
4
|
import { tddStageForTrack } from "./stages/tdd.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// NOTE: The former QUESTION_FORMAT_SPEC / ERROR_BUDGET_SPEC exports were
|
|
7
|
+
// hoisted into `src/content/meta-skill.ts` (Shared Decision + Tool-Use
|
|
8
|
+
// Protocol). They are no longer re-exported from here to avoid duplication
|
|
9
|
+
// and drift. Stage skills cite the meta-skill by path instead.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export const SKILL_ENVELOPE_KINDS = [
|
|
12
|
+
"stage-output",
|
|
13
|
+
"gate-result",
|
|
14
|
+
"delegation-record"
|
|
15
|
+
];
|
|
16
|
+
const FLOW_STAGE_SET = new Set(FLOW_STAGES);
|
|
17
|
+
const SKILL_ENVELOPE_KIND_SET = new Set(SKILL_ENVELOPE_KINDS);
|
|
18
|
+
function asRecord(value) {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
export function validateSkillEnvelope(value) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const record = asRecord(value);
|
|
27
|
+
if (!record) {
|
|
28
|
+
return { ok: false, errors: ["envelope must be a JSON object"] };
|
|
29
|
+
}
|
|
30
|
+
if (record.version !== "1") {
|
|
31
|
+
errors.push('envelope.version must equal "1".');
|
|
32
|
+
}
|
|
33
|
+
if (typeof record.kind !== "string" || !SKILL_ENVELOPE_KIND_SET.has(record.kind)) {
|
|
34
|
+
errors.push(`envelope.kind must be one of: ${SKILL_ENVELOPE_KINDS.join(", ")}.`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof record.stage !== "string" || !FLOW_STAGE_SET.has(record.stage)) {
|
|
37
|
+
errors.push(`envelope.stage must be one of: ${FLOW_STAGES.join(", ")}.`);
|
|
38
|
+
}
|
|
39
|
+
if (!Object.prototype.hasOwnProperty.call(record, "payload")) {
|
|
40
|
+
errors.push("envelope.payload is required.");
|
|
41
|
+
}
|
|
42
|
+
if (typeof record.emittedAt !== "string" || Number.isNaN(Date.parse(record.emittedAt))) {
|
|
43
|
+
errors.push("envelope.emittedAt must be an ISO-8601 timestamp string.");
|
|
44
|
+
}
|
|
45
|
+
if (record.agent !== undefined && typeof record.agent !== "string") {
|
|
46
|
+
errors.push("envelope.agent must be a string when present.");
|
|
47
|
+
}
|
|
48
|
+
return { ok: errors.length === 0, errors };
|
|
49
|
+
}
|
|
50
|
+
export function parseSkillEnvelope(raw) {
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const validation = validateSkillEnvelope(parsed);
|
|
59
|
+
if (!validation.ok) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
5
64
|
const ARTIFACT_STAGE_BY_PATH = {
|
|
6
65
|
".cclaw/artifacts/01-brainstorm.md": "brainstorm",
|
|
7
66
|
".cclaw/artifacts/02-scope.md": "scope",
|
|
@@ -191,9 +250,26 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
191
250
|
{
|
|
192
251
|
agent: "test-author",
|
|
193
252
|
mode: "mandatory",
|
|
194
|
-
when: "Always during TDD cycle (RED
|
|
195
|
-
purpose: "
|
|
196
|
-
requiresUserGate: false
|
|
253
|
+
when: "Always during TDD cycle (RED phase).",
|
|
254
|
+
purpose: "Produce failing RED tests only; no production writes.",
|
|
255
|
+
requiresUserGate: false,
|
|
256
|
+
skill: "tdd-red-phase"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
agent: "test-author",
|
|
260
|
+
mode: "mandatory",
|
|
261
|
+
when: "Always during TDD cycle (GREEN phase).",
|
|
262
|
+
purpose: "Implement minimum production changes to satisfy RED and prove full-suite GREEN.",
|
|
263
|
+
requiresUserGate: false,
|
|
264
|
+
skill: "tdd-green-phase"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
agent: "test-author",
|
|
268
|
+
mode: "mandatory",
|
|
269
|
+
when: "Always during TDD cycle (REFACTOR phase).",
|
|
270
|
+
purpose: "Refactor only after GREEN proof, preserving behavior and test pass state.",
|
|
271
|
+
requiresUserGate: false,
|
|
272
|
+
skill: "tdd-refactor-phase"
|
|
197
273
|
},
|
|
198
274
|
{
|
|
199
275
|
agent: "doc-updater",
|
|
@@ -208,8 +284,9 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
208
284
|
agent: "reviewer",
|
|
209
285
|
mode: "mandatory",
|
|
210
286
|
when: "Always in review stage.",
|
|
211
|
-
purpose: "
|
|
212
|
-
requiresUserGate: false
|
|
287
|
+
purpose: "Layer 1 spec compliance pass plus coordination of parallel Layer 2 fan-out (correctness, performance, architecture, external-safety) with source-tagged findings.",
|
|
288
|
+
requiresUserGate: false,
|
|
289
|
+
skill: "review-spec-pass"
|
|
213
290
|
},
|
|
214
291
|
{
|
|
215
292
|
agent: "security-reviewer",
|
|
@@ -54,7 +54,7 @@ export const REVIEW = {
|
|
|
54
54
|
"Layer 2b: check security — validation, auth, secrets, injection.",
|
|
55
55
|
"Layer 2c: check performance — queries, memory, caching, hot paths.",
|
|
56
56
|
"Layer 2d: check architecture fit — design compliance, coupling, interfaces.",
|
|
57
|
-
"Reconcile multi-agent findings into `.cclaw/artifacts/07-review-army.json` (dedup + confidence + conflict notes).",
|
|
57
|
+
"Reconcile multi-agent findings into `.cclaw/artifacts/07-review-army.json` (dedup + confidence + conflict notes + source tags from spec/correctness/security/performance/architecture/external-safety passes).",
|
|
58
58
|
"Classify and prioritize all findings.",
|
|
59
59
|
"Write review report artifact with explicit verdict.",
|
|
60
60
|
"If verdict is BLOCKED, include the remediation route token `ROUTE_BACK_TO_TDD` and the rewind command payload."
|
|
@@ -202,7 +202,7 @@ export const REVIEW = {
|
|
|
202
202
|
artifactValidation: [
|
|
203
203
|
{ section: "Layer 1 Verdict", required: true, validationRule: "Per-criterion pass/fail with references." },
|
|
204
204
|
{ section: "Layer 2 Findings", required: false, validationRule: "Each finding has severity, description, and resolution status. Security coverage must include either explicit security findings or `NO_CHANGE_ATTESTATION: <reason>` when no security-relevant changes were found." },
|
|
205
|
-
{ section: "Review Army Contract", required: true, validationRule: "Structured findings include id/severity/confidence/fingerprint/reportedBy/status with dedup reconciliation summary." },
|
|
205
|
+
{ section: "Review Army Contract", required: true, validationRule: "Structured findings include id/severity/confidence/fingerprint/reportedBy/status and source tags from {spec, correctness, security, performance, architecture, external-safety} with dedup reconciliation summary." },
|
|
206
206
|
{ section: "Review Readiness Dashboard", required: false, validationRule: "Includes a per-pass table (Layer 1 / Layer 2 / Adversarial / Schema) with a 'Completed at' column, a Delegation log snapshot block (path .cclaw/state/delegation-log.json with required/completed/waived/pending), a Staleness signal block (commit at last review pass and current commit), and a Headline with open critical blockers + ship recommendation. At minimum, the section text must contain the substrings 'Completed at', 'delegation-log.json', 'commit at last review pass', and 'Ship recommendation'." },
|
|
207
207
|
{ section: "Completeness Score", required: false, validationRule: "Records AC coverage, task coverage, test-slice coverage, and adversarial-review pass status as numeric or boolean values. At minimum, a line like 'AC coverage: N/M' or 'AC coverage: 100%'." },
|
|
208
208
|
{ section: "Incoming Feedback Queue", required: false, validationRule: "When external review feedback exists, include a queue summary with per-item disposition (resolved / accepted-risk / rejected-with-evidence) and evidence refs." },
|