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.
@@ -1092,6 +1092,14 @@ export async function validateReviewArmy(projectRoot) {
1092
1092
  }
1093
1093
  const severitySet = new Set(["Critical", "Important", "Suggestion"]);
1094
1094
  const statusSet = new Set(["open", "accepted", "resolved"]);
1095
+ const sourceSet = new Set([
1096
+ "spec",
1097
+ "correctness",
1098
+ "security",
1099
+ "performance",
1100
+ "architecture",
1101
+ "external-safety"
1102
+ ]);
1095
1103
  const findingIds = new Set();
1096
1104
  const openCriticalIds = new Set();
1097
1105
  if (!Array.isArray(root.findings)) {
@@ -1128,6 +1136,17 @@ export async function validateReviewArmy(projectRoot) {
1128
1136
  if (!isStringArray(o.reportedBy) || o.reportedBy.length === 0) {
1129
1137
  errors.push(`findings[${i}].reportedBy must be a non-empty string array.`);
1130
1138
  }
1139
+ if (o.sources !== undefined) {
1140
+ if (!isStringArray(o.sources) || o.sources.length === 0) {
1141
+ errors.push(`findings[${i}].sources must be a non-empty string array when present.`);
1142
+ }
1143
+ else {
1144
+ const invalidSources = o.sources.filter((source) => !sourceSet.has(source));
1145
+ if (invalidSources.length > 0) {
1146
+ errors.push(`findings[${i}].sources contains unknown values: ${invalidSources.join(", ")}.`);
1147
+ }
1148
+ }
1149
+ }
1131
1150
  if (o.location === undefined || o.location === null) {
1132
1151
  errors.push(`findings[${i}].location is required and must be an object with file + line.`);
1133
1152
  }
@@ -1231,6 +1250,19 @@ export async function validateReviewArmy(projectRoot) {
1231
1250
  }
1232
1251
  }
1233
1252
  }
1253
+ if (rec.layerCoverage !== undefined) {
1254
+ if (rec.layerCoverage === null || typeof rec.layerCoverage !== "object" || Array.isArray(rec.layerCoverage)) {
1255
+ errors.push("reconciliation.layerCoverage must be an object when present.");
1256
+ }
1257
+ else {
1258
+ const coverage = rec.layerCoverage;
1259
+ for (const source of sourceSet) {
1260
+ if (coverage[source] !== undefined && typeof coverage[source] !== "boolean") {
1261
+ errors.push(`reconciliation.layerCoverage.${source} must be boolean when present.`);
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1234
1266
  }
1235
1267
  return { valid: errors.length === 0, errors };
1236
1268
  }
package/dist/config.d.ts CHANGED
@@ -42,7 +42,7 @@ export declare function readConfig(projectRoot: string): Promise<CclawConfig>;
42
42
  * the user set them explicitly. Keeps the default template small and honest:
43
43
  * only knobs a new user would meaningfully flip show up.
44
44
  */
45
- type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "tdd" | "compound" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview";
45
+ type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "tdd" | "compound" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview" | "ironLaws";
46
46
  /**
47
47
  * Options controlling the serialisation shape of `config.yaml`.
48
48
  *
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parse, stringify } from "yaml";
4
4
  import { CCLAW_VERSION, DEFAULT_HARNESSES, FLOW_VERSION, RUNTIME_ROOT } from "./constants.js";
5
+ import { isIronLawId, normalizeStrictLawIds } from "./content/iron-laws.js";
5
6
  import { exists, writeFileSafe } from "./fs-utils.js";
6
7
  import { FLOW_TRACKS, HARNESS_IDS, LANGUAGE_RULE_PACKS } from "./types.js";
7
8
  const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
@@ -25,7 +26,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
25
26
  "defaultTrack",
26
27
  "languageRulePacks",
27
28
  "trackHeuristics",
28
- "sliceReview"
29
+ "sliceReview",
30
+ "ironLaws"
29
31
  ]);
30
32
  /**
31
33
  * Config keys always present in the minimal init template. Everything else
@@ -141,7 +143,11 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
141
143
  },
142
144
  gitHookGuards: false,
143
145
  defaultTrack,
144
- languageRulePacks: []
146
+ languageRulePacks: [],
147
+ ironLaws: {
148
+ mode: "advisory",
149
+ strictLaws: []
150
+ }
145
151
  };
146
152
  }
147
153
  /**
@@ -409,6 +415,36 @@ export async function readConfig(projectRoot) {
409
415
  enforceOnTracks: enforceOnTracks ?? DEFAULT_SLICE_REVIEW_TRACKS
410
416
  };
411
417
  }
418
+ const ironLawsRaw = parsed.ironLaws;
419
+ let ironLaws = undefined;
420
+ if (Object.prototype.hasOwnProperty.call(parsed, "ironLaws")) {
421
+ if (!isRecord(ironLawsRaw)) {
422
+ throw configValidationError(fullPath, `"ironLaws" must be an object`);
423
+ }
424
+ const unknownIronLawKeys = Object.keys(ironLawsRaw).filter((key) => key !== "mode" && key !== "strictLaws");
425
+ if (unknownIronLawKeys.length > 0) {
426
+ throw configValidationError(fullPath, `"ironLaws" has unknown key(s): ${unknownIronLawKeys.join(", ")}`);
427
+ }
428
+ const modeRaw = ironLawsRaw.mode;
429
+ if (modeRaw !== undefined && modeRaw !== "advisory" && modeRaw !== "strict") {
430
+ throw configValidationError(fullPath, `"ironLaws.mode" must be "advisory" or "strict"`);
431
+ }
432
+ const strictLawIdsRaw = validateStringArray(ironLawsRaw.strictLaws, "ironLaws.strictLaws", fullPath) ?? [];
433
+ const unknownStrictLawIds = strictLawIdsRaw.filter((id) => !isIronLawId(id));
434
+ if (unknownStrictLawIds.length > 0) {
435
+ throw configValidationError(fullPath, `"ironLaws.strictLaws" contains unknown law id(s): ${unknownStrictLawIds.join(", ")}`);
436
+ }
437
+ ironLaws = {
438
+ mode: modeRaw === "strict" ? "strict" : "advisory",
439
+ strictLaws: normalizeStrictLawIds(strictLawIdsRaw)
440
+ };
441
+ }
442
+ else {
443
+ ironLaws = {
444
+ mode: strictness,
445
+ strictLaws: []
446
+ };
447
+ }
412
448
  return {
413
449
  version: parsed.version ?? CCLAW_VERSION,
414
450
  flowVersion: parsed.flowVersion ?? FLOW_VERSION,
@@ -428,7 +464,8 @@ export async function readConfig(projectRoot) {
428
464
  defaultTrack,
429
465
  languageRulePacks,
430
466
  trackHeuristics,
431
- sliceReview
467
+ sliceReview,
468
+ ironLaws
432
469
  };
433
470
  }
434
471
  function isMinimalKey(key) {
@@ -452,7 +489,8 @@ function buildSerializableConfig(config, options = {}) {
452
489
  "defaultTrack",
453
490
  "languageRulePacks",
454
491
  "trackHeuristics",
455
- "sliceReview"
492
+ "sliceReview",
493
+ "ironLaws"
456
494
  ];
457
495
  for (const key of ordered) {
458
496
  const value = config[key];
@@ -506,7 +544,8 @@ export async function detectAdvancedKeys(projectRoot) {
506
544
  "defaultTrack",
507
545
  "languageRulePacks",
508
546
  "trackHeuristics",
509
- "sliceReview"
547
+ "sliceReview",
548
+ "ironLaws"
510
549
  ];
511
550
  const present = new Set();
512
551
  for (const key of advancedCandidates) {
@@ -48,6 +48,7 @@ STATE_FILE="$ROOT/${RUNTIME_ROOT}/state/flow-state.json"
48
48
  ACTIVE_FEATURE_FILE="$ROOT/${RUNTIME_ROOT}/state/active-feature.json"
49
49
  CHECKPOINT_FILE="$ROOT/${RUNTIME_ROOT}/state/checkpoint.json"
50
50
  ACTIVITY_FILE="$ROOT/${RUNTIME_ROOT}/state/stage-activity.jsonl"
51
+ IRON_LAWS_FILE="$ROOT/${RUNTIME_ROOT}/state/iron-laws.json"
51
52
  SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
52
53
  CONTEXT_WARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/state/context-warnings.jsonl"
53
54
  CONTEXT_MODE_FILE="$ROOT/${RUNTIME_ROOT}/state/context-mode.json"
@@ -352,74 +353,99 @@ if [ -f "$META_SKILL" ]; then
352
353
  META_CONTENT=$(cat "$META_SKILL" 2>/dev/null || echo "")
353
354
  fi
354
355
 
355
- # --- Build compact knowledge digest (stage-biased, top entries only) ---
356
+ # --- Build compact knowledge digest (stage + branch + diff aware) ---
356
357
  KNOWLEDGE_DIGEST=""
357
358
  LEARNINGS_COUNT=0
358
359
  if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
359
360
  LEARNINGS_COUNT=$(grep -c '^{' "$KNOWLEDGE_FILE" 2>/dev/null || echo "0")
361
+ fi
362
+
363
+ if command -v cclaw >/dev/null 2>&1 && [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
364
+ BRANCH_NAME=""
365
+ if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
366
+ BRANCH_NAME=$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
367
+ fi
368
+ DIFF_FILES_CSV=""
369
+ if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
370
+ DIFF_FILES_CSV=$(git -C "$ROOT" diff --name-only HEAD~5..HEAD 2>/dev/null | head -n 20 | tr '\n' ',' | sed 's/,$//' || echo "")
371
+ fi
372
+ OPEN_GATES_CSV=""
373
+ if [ -f "$STATE_FILE" ] && command -v jq >/dev/null 2>&1; then
374
+ OPEN_GATES_CSV=$(jq -r --arg stage "$STAGE" '
375
+ (.stageGateCatalog[$stage].required // [])
376
+ - (.stageGateCatalog[$stage].passed // [])
377
+ | join(",")
378
+ ' "$STATE_FILE" 2>/dev/null || echo "")
379
+ fi
380
+ DIGEST_CMD=(cclaw internal knowledge-digest --stage="$STAGE" --limit=8)
381
+ if [ -n "$BRANCH_NAME" ]; then
382
+ DIGEST_CMD+=("--branch=$BRANCH_NAME")
383
+ fi
384
+ if [ -n "$DIFF_FILES_CSV" ]; then
385
+ DIGEST_CMD+=("--diff-files=$DIFF_FILES_CSV")
386
+ fi
387
+ if [ -n "$OPEN_GATES_CSV" ]; then
388
+ DIGEST_CMD+=("--open-gates=$OPEN_GATES_CSV")
389
+ fi
390
+ KNOWLEDGE_DIGEST=$("\${DIGEST_CMD[@]}" 2>/dev/null || echo "")
391
+ fi
392
+
393
+ if [ -z "$KNOWLEDGE_DIGEST" ] && [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
360
394
  if command -v jq >/dev/null 2>&1; then
361
- KNOWLEDGE_DIGEST=$(tail -n 200 "$KNOWLEDGE_FILE" 2>/dev/null | jq -Rsc --arg stage "$STAGE" '
395
+ KNOWLEDGE_DIGEST=$(tail -n 120 "$KNOWLEDGE_FILE" 2>/dev/null | jq -Rsc --arg stage "$STAGE" '
362
396
  split("\\n")
363
397
  | map(select(length > 0))
364
398
  | map(try fromjson catch null)
365
399
  | map(select(type == "object"))
366
400
  | map(select((.stage // null) == $stage or (.stage // null) == null))
367
401
  | reverse
368
- | .[0:8]
402
+ | .[0:6]
369
403
  | map("- [" + ((.confidence // "unknown")|tostring) + " • " + ((.stage // "global")|tostring) + " • " + ((.domain // "general")|tostring) + "] " + ((.trigger // "trigger")|tostring) + " -> " + ((.action // "action")|tostring))
370
404
  | join("\\n")
371
405
  ' 2>/dev/null || echo "")
406
+ else
407
+ KNOWLEDGE_DIGEST=$(tail -n 6 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
408
+ fi
409
+ fi
410
+
411
+ if [ -n "$KNOWLEDGE_DIGEST" ]; then
412
+ printf '# Knowledge digest (auto-generated)\\n\\n%s\\n' "$KNOWLEDGE_DIGEST" > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
413
+ elif [ -f "$KNOWLEDGE_DIGEST_FILE" ]; then
414
+ printf '# Knowledge digest (auto-generated)\\n\\n(no matching entries for current stage)\\n' > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
415
+ fi
416
+
417
+ IRON_LAWS_SUMMARY=""
418
+ if [ -f "$IRON_LAWS_FILE" ]; then
419
+ if command -v jq >/dev/null 2>&1; then
420
+ IRON_LAWS_SUMMARY=$(jq -r '
421
+ (.laws // [])
422
+ | map("- [" + (if (.strict // false) then "strict" else "advisory" end) + "] " + ((.id // "law")|tostring) + " -> " + ((.rule // "")|tostring))
423
+ | .[0:6]
424
+ | join("\\n")
425
+ ' "$IRON_LAWS_FILE" 2>/dev/null || echo "")
372
426
  elif command -v python3 >/dev/null 2>&1; then
373
- KNOWLEDGE_DIGEST=$(python3 - "$KNOWLEDGE_FILE" "$STAGE" <<'PY'
427
+ IRON_LAWS_SUMMARY=$(python3 - "$IRON_LAWS_FILE" <<'PY'
374
428
  import json
375
429
  import sys
376
-
377
- path = sys.argv[1]
378
- stage = sys.argv[2]
379
- entries = []
430
+ out = []
380
431
  try:
381
- with open(path, "r", encoding="utf-8") as fh:
382
- lines = fh.readlines()[-200:]
383
- for raw in lines:
384
- raw = raw.strip()
385
- if not raw:
386
- continue
387
- try:
388
- obj = json.loads(raw)
389
- except Exception:
390
- continue
391
- if not isinstance(obj, dict):
392
- continue
393
- row_stage = obj.get("stage")
394
- if row_stage not in (stage, None):
432
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
433
+ parsed = json.load(fh)
434
+ for row in (parsed.get("laws") or [])[:6]:
435
+ if not isinstance(row, dict):
395
436
  continue
396
- entries.append(obj)
437
+ strict = "strict" if row.get("strict") else "advisory"
438
+ law_id = str(row.get("id") or "law")
439
+ rule = str(row.get("rule") or "")
440
+ out.append(f"- [{strict}] {law_id} -> {rule}")
397
441
  except Exception:
398
- entries = []
399
-
400
- entries = list(reversed(entries))[:8]
401
- out = []
402
- for obj in entries:
403
- conf = str(obj.get("confidence", "unknown"))
404
- row_stage = str(obj.get("stage", "global"))
405
- domain = str(obj.get("domain", "general"))
406
- trigger = str(obj.get("trigger", "trigger"))
407
- action = str(obj.get("action", "action"))
408
- out.append(f"- [{conf} • {row_stage} • {domain}] {trigger} -> {action}")
442
+ out = []
409
443
  print("\\n".join(out))
410
444
  PY
411
445
  )
412
- else
413
- KNOWLEDGE_DIGEST=$(tail -n 8 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
414
446
  fi
415
447
  fi
416
448
 
417
- if [ -n "$KNOWLEDGE_DIGEST" ]; then
418
- printf '# Knowledge digest (auto-generated)\\n\\n%s\\n' "$KNOWLEDGE_DIGEST" > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
419
- elif [ -f "$KNOWLEDGE_DIGEST_FILE" ]; then
420
- printf '# Knowledge digest (auto-generated)\\n\\n(no matching entries for current stage)\\n' > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
421
- fi
422
-
423
449
  # --- Installed cclaw-cli version vs. project's recorded version (one-block
424
450
  # upgrade-check, gstack-style). Purely informational — we never block. ---
425
451
  VERSION_NOTE=""
@@ -503,6 +529,11 @@ if [ -n "$KNOWLEDGE_DIGEST" ]; then
503
529
  Knowledge digest (top relevant entries):
504
530
  $KNOWLEDGE_DIGEST"
505
531
  fi
532
+ if [ -n "$IRON_LAWS_SUMMARY" ]; then
533
+ CTX="$CTX
534
+ Iron laws (enforced policy highlights):
535
+ $IRON_LAWS_SUMMARY"
536
+ fi
506
537
  if [ -n "$META_CONTENT" ]; then
507
538
  CTX="$CTX
508
539
 
@@ -545,6 +576,7 @@ STATE_FILE="$STATE_DIR/flow-state.json"
545
576
  CHECKPOINT_FILE="$STATE_DIR/checkpoint.json"
546
577
  CHECKPOINT_TMP="$STATE_DIR/checkpoint.json.tmp.$$"
547
578
  CHECKPOINT_LOCK_DIR="$STATE_DIR/.checkpoint.lock"
579
+ IRON_LAWS_FILE="$STATE_DIR/iron-laws.json"
548
580
  STAGE="none"
549
581
  ACTIVE_RUN="none"
550
582
  LOOP_COUNT=""
@@ -617,6 +649,38 @@ if command -v git >/dev/null 2>&1; then
617
649
  fi
618
650
  fi
619
651
 
652
+ STRICT_STOP_DIRTY="false"
653
+ if [ -f "$IRON_LAWS_FILE" ]; then
654
+ if command -v jq >/dev/null 2>&1; then
655
+ STRICT_STOP_DIRTY=$(jq -r '
656
+ if (.mode // "advisory") == "strict" then "true"
657
+ elif ((.laws // []) | any(.id == "stop-clean-or-checkpointed" and .strict == true)) then "true"
658
+ else "false"
659
+ end
660
+ ' "$IRON_LAWS_FILE" 2>/dev/null || echo "false")
661
+ elif command -v python3 >/dev/null 2>&1; then
662
+ STRICT_STOP_DIRTY=$(python3 - "$IRON_LAWS_FILE" <<'PY'
663
+ import json
664
+ import sys
665
+ value = "false"
666
+ try:
667
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
668
+ parsed = json.load(fh)
669
+ if str(parsed.get("mode", "advisory")) == "strict":
670
+ value = "true"
671
+ else:
672
+ for row in parsed.get("laws", []):
673
+ if isinstance(row, dict) and row.get("id") == "stop-clean-or-checkpointed" and row.get("strict") is True:
674
+ value = "true"
675
+ break
676
+ except Exception:
677
+ value = "false"
678
+ print(value)
679
+ PY
680
+ )
681
+ fi
682
+ fi
683
+
620
684
  TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
621
685
  mkdir -p "$STATE_DIR" 2>/dev/null || true
622
686
  CHECKPOINT_WRITTEN=0
@@ -742,6 +806,11 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
742
806
  CHECKPOINT_NOTE="Checkpoint update failed. Review ${RUNTIME_ROOT}/state/checkpoint.json manually."
743
807
  fi
744
808
 
809
+ if [ "$DIRTY_STATE" = "dirty" ] && [ "$STRICT_STOP_DIRTY" = "true" ]; then
810
+ printf '[cclaw] Stop blocked by iron law "stop-clean-or-checkpointed": working tree is dirty. Commit/revert changes or update checkpoint blockers before ending the session.\\n' >&2
811
+ exit 1
812
+ fi
813
+
745
814
  RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until /cc-ops archive (or cclaw archive runtime)."
746
815
 
747
816
  # --- Escape for JSON ---
@@ -58,6 +58,17 @@ same session, or save/discard the backlog.
58
58
  6. **Present the handoff prompt** with four concrete options — not A/B/C
59
59
  letters. Default = "Start /cc on the top recommendation".
60
60
 
61
+ ## Headless mode
62
+
63
+ For skill-to-skill invocation, emit exactly one JSON envelope:
64
+
65
+ \`\`\`json
66
+ {"version":"1","kind":"stage-output","stage":"brainstorm","payload":{"command":"/cc-ideate","artifact":".cclaw/artifacts/ideate-<date>-<slug>.md","recommendation":"I-1"},"emittedAt":"<ISO-8601>"}
67
+ \`\`\`
68
+
69
+ Validate envelopes with:
70
+ \`cclaw internal envelope-validate --stdin\`
71
+
61
72
  ## Primary skill
62
73
 
63
74
  **${RUNTIME_ROOT}/skills/${IDEATE_SKILL_FOLDER}/SKILL.md**
@@ -0,0 +1,134 @@
1
+ import type { FlowStage } from "../types.js";
2
+ export type IronLawEnforcementPoint = "PreToolUse" | "PostToolUse" | "SessionStart" | "Stop" | "advisory";
3
+ export type IronLawSeverity = "hard-gate" | "soft-gate";
4
+ export interface IronLawDefinition {
5
+ id: string;
6
+ title: string;
7
+ rule: string;
8
+ rationale: string;
9
+ enforcement: IronLawEnforcementPoint;
10
+ severity: IronLawSeverity;
11
+ appliesTo: "all" | FlowStage[];
12
+ hookMatcher?: {
13
+ toolPattern?: string;
14
+ payloadPattern?: string;
15
+ };
16
+ }
17
+ export interface IronLawRuntimeRecord {
18
+ id: string;
19
+ title: string;
20
+ rule: string;
21
+ enforcement: IronLawEnforcementPoint;
22
+ severity: IronLawSeverity;
23
+ appliesTo: "all" | FlowStage[];
24
+ strict: boolean;
25
+ hookMatcher?: {
26
+ toolPattern?: string;
27
+ payloadPattern?: string;
28
+ };
29
+ }
30
+ export interface IronLawRuntimeDocument {
31
+ version: 1;
32
+ generatedAt: string;
33
+ mode: "advisory" | "strict";
34
+ strictLaws: string[];
35
+ laws: IronLawRuntimeRecord[];
36
+ }
37
+ export declare const IRON_LAWS: readonly [{
38
+ readonly id: "tdd-red-before-write";
39
+ readonly title: "RED before production write";
40
+ readonly rule: "Do not edit production code in tdd stage before a failing RED test exists for the slice.";
41
+ readonly rationale: "Prevents implementation-first behavior and keeps RED as executable specification.";
42
+ readonly enforcement: "PreToolUse";
43
+ readonly severity: "hard-gate";
44
+ readonly appliesTo: ["tdd"];
45
+ readonly hookMatcher: {
46
+ readonly toolPattern: "write|edit|multiedit|applypatch|shell|bash";
47
+ readonly payloadPattern: "\\.(ts|tsx|js|jsx|py|go|java|rs|rb|php|c|cc|cpp|h|hpp)";
48
+ };
49
+ }, {
50
+ readonly id: "plan-requires-approval";
51
+ readonly title: "No implementation before plan approval";
52
+ readonly rule: "Do not perform write-like actions while plan stage is pending WAIT_FOR_CONFIRM approval.";
53
+ readonly rationale: "Locks intent before execution and reduces expensive rework from unapproved paths.";
54
+ readonly enforcement: "PreToolUse";
55
+ readonly severity: "hard-gate";
56
+ readonly appliesTo: ["plan"];
57
+ }, {
58
+ readonly id: "runtime-writes-managed-only";
59
+ readonly title: "Runtime writes are managed";
60
+ readonly rule: "Do not mutate .cclaw/state, .cclaw/hooks, or .cclaw/skills by ad-hoc edits unless using cclaw-managed commands.";
61
+ readonly rationale: "Protects generated runtime integrity and avoids drift that silently breaks hooks or skills.";
62
+ readonly enforcement: "PreToolUse";
63
+ readonly severity: "hard-gate";
64
+ readonly appliesTo: "all";
65
+ readonly hookMatcher: {
66
+ readonly toolPattern: "write|edit|multiedit|delete|applypatch|shell|bash";
67
+ readonly payloadPattern: "\\.cclaw/(state|hooks|skills)";
68
+ };
69
+ }, {
70
+ readonly id: "flow-state-read-fresh";
71
+ readonly title: "Fresh flow-state read required";
72
+ readonly rule: "Before mutating actions, a fresh read of .cclaw/state/flow-state.json must exist within guard freshness window.";
73
+ readonly rationale: "Prevents stale-stage mutations after context shifts or multi-agent divergence.";
74
+ readonly enforcement: "PreToolUse";
75
+ readonly severity: "hard-gate";
76
+ readonly appliesTo: "all";
77
+ }, {
78
+ readonly id: "review-layer-order";
79
+ readonly title: "Review layers are sequential";
80
+ readonly rule: "Review stage must complete Layer 1 spec compliance before Layer 2 quality/security passes.";
81
+ readonly rationale: "Stops premature quality discussion when acceptance criteria are not yet satisfied.";
82
+ readonly enforcement: "advisory";
83
+ readonly severity: "soft-gate";
84
+ readonly appliesTo: ["review"];
85
+ }, {
86
+ readonly id: "review-criticals-close-before-ship";
87
+ readonly title: "No ship with open criticals";
88
+ readonly rule: "Ship decisions are blocked when review-army contains open Critical findings or ship blockers.";
89
+ readonly rationale: "Enforces explicit risk closure before release finalization.";
90
+ readonly enforcement: "PreToolUse";
91
+ readonly severity: "hard-gate";
92
+ readonly appliesTo: ["ship"];
93
+ }, {
94
+ readonly id: "ship-preflight-required";
95
+ readonly title: "Preflight required before finalization";
96
+ readonly rule: "Do not execute release finalization actions until ship preflight gate is passed.";
97
+ readonly rationale: "Catches regressions before irreversible release steps.";
98
+ readonly enforcement: "PreToolUse";
99
+ readonly severity: "hard-gate";
100
+ readonly appliesTo: ["ship"];
101
+ }, {
102
+ readonly id: "subagent-task-self-contained";
103
+ readonly title: "Subagent tasks are self-contained";
104
+ readonly rule: "Delegated tasks must include explicit objective, constraints, and expected output, not just references.";
105
+ readonly rationale: "Avoids context loss and low-quality delegation in isolated worker contexts.";
106
+ readonly enforcement: "advisory";
107
+ readonly severity: "soft-gate";
108
+ readonly appliesTo: "all";
109
+ }, {
110
+ readonly id: "no-secrets-in-artifacts";
111
+ readonly title: "Never log secrets in artifacts";
112
+ readonly rule: "Secrets/tokens/passwords must not be written to review, ship, or runtime state artifacts.";
113
+ readonly rationale: "Prevents accidental credential leakage through generated workflow artifacts.";
114
+ readonly enforcement: "PostToolUse";
115
+ readonly severity: "hard-gate";
116
+ readonly appliesTo: "all";
117
+ }, {
118
+ readonly id: "stop-clean-or-checkpointed";
119
+ readonly title: "Stop only from clean checkpoint";
120
+ readonly rule: "Do not end a session with dirty state unless checkpoint explicitly records unresolved work and blockers.";
121
+ readonly rationale: "Protects continuity and prevents silent half-finished sessions.";
122
+ readonly enforcement: "Stop";
123
+ readonly severity: "hard-gate";
124
+ readonly appliesTo: "all";
125
+ }];
126
+ export declare function isIronLawId(value: string): boolean;
127
+ export declare function normalizeStrictLawIds(ids: string[] | undefined): string[];
128
+ export declare function ironLawRuntimeDocument(options?: {
129
+ mode?: "advisory" | "strict";
130
+ strictLaws?: string[];
131
+ nowIso?: string;
132
+ }): IronLawRuntimeDocument;
133
+ export declare function ironLawsAgentsMdBlock(): string;
134
+ export declare function ironLawsSkillMarkdown(): string;