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.
@@ -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.
@@ -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 'tdd_write_without_open_red'; then
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: direct flow-state edit bypasses the canonical stage-complete helper (\${REASONS}). Prefer: bash ${RUNTIME_ROOT}/hooks/stage-complete.sh <stage>. In strict mode this is blocked."
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. **Read AGENTS.md:** The cclaw block contains routing and rules follow them.
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 → GREEN → REFACTOR).",
195
- purpose: "Guarantee failing tests, traceable implementation, and full-suite verification.",
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: "Run spec compliance and code-quality passes with file evidence.",
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." },