cclaw-cli 0.43.0 → 0.45.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.
@@ -154,6 +154,7 @@ exit 0
154
154
  `;
155
155
  }
156
156
  export function workflowGuardScript(options = {}) {
157
+ const workflowGuardMode = options.workflowGuardMode === "strict" ? "strict" : "advisory";
157
158
  const tddEnforcementMode = options.tddEnforcementMode === "strict" ? "strict" : "advisory";
158
159
  const tddTestGlobs = options.tddTestGlobs && options.tddTestGlobs.length > 0
159
160
  ? options.tddTestGlobs.join(",")
@@ -162,7 +163,7 @@ export function workflowGuardScript(options = {}) {
162
163
  # cclaw workflow guard hook — generated by cclaw sync
163
164
  # Enforces stage-aware command discipline and recent flow-state read hygiene.
164
165
  set -uo pipefail
165
- WORKFLOW_GUARD_MODE="\${CCLAW_WORKFLOW_GUARD_MODE:-advisory}"
166
+ WORKFLOW_GUARD_MODE="\${CCLAW_WORKFLOW_GUARD_MODE:-${workflowGuardMode}}"
166
167
  MAX_FLOW_READ_AGE_SEC="\${CCLAW_WORKFLOW_GUARD_MAX_AGE_SEC:-1800}"
167
168
  TDD_ENFORCEMENT_MODE="${tddEnforcementMode}"
168
169
  TDD_TEST_GLOBS="${tddTestGlobs}"
@@ -342,6 +343,81 @@ is_cclaw_cli_payload() {
342
343
  printf '%s' "$1" | grep -Eq '(cclaw |npx cclaw |/cc-|/cc[^[:alnum:]_-])'
343
344
  }
344
345
 
346
+ extract_flow_state_after_json() {
347
+ if command -v jq >/dev/null 2>&1; then
348
+ printf '%s' "$INPUT" | jq -r '
349
+ .tool_input?.content //
350
+ .input?.content //
351
+ .arguments?.content //
352
+ .params?.content //
353
+ .payload?.content //
354
+ .content //
355
+ .input?.new_string //
356
+ .tool_input?.new_string //
357
+ ""
358
+ ' 2>/dev/null || echo ""
359
+ return 0
360
+ fi
361
+
362
+ if command -v python3 >/dev/null 2>&1; then
363
+ INPUT_JSON="$INPUT" python3 - <<'PY'
364
+ import json
365
+ import os
366
+
367
+ try:
368
+ payload = json.loads(os.environ.get("INPUT_JSON", "{}"))
369
+ except Exception:
370
+ payload = {}
371
+
372
+ def pick(value):
373
+ if not isinstance(value, dict):
374
+ return ""
375
+ for key in ("tool_input", "input", "arguments", "params", "payload"):
376
+ nested = value.get(key)
377
+ if isinstance(nested, dict):
378
+ content = nested.get("content")
379
+ if isinstance(content, str) and content.strip():
380
+ return content
381
+ new_string = nested.get("new_string")
382
+ if isinstance(new_string, str) and new_string.strip():
383
+ return new_string
384
+ content = value.get("content")
385
+ if isinstance(content, str) and content.strip():
386
+ return content
387
+ return ""
388
+
389
+ print(pick(payload))
390
+ PY
391
+ return 0
392
+ fi
393
+
394
+ printf ''
395
+ return 0
396
+ }
397
+
398
+ verify_flow_state_candidate() {
399
+ local candidate_json="$1"
400
+ [ -n "$candidate_json" ] || return 1
401
+ local tmp_file="$STATE_DIR/.flow-state-candidate.$$.$RANDOM.json"
402
+ printf '%s' "$candidate_json" > "$tmp_file" 2>/dev/null || {
403
+ rm -f "$tmp_file" 2>/dev/null || true
404
+ return 1
405
+ }
406
+
407
+ local verify_cmd=(npx -y cclaw-cli internal verify-flow-state-diff --after-file="$tmp_file" --quiet)
408
+ if command -v cclaw >/dev/null 2>&1; then
409
+ verify_cmd=(cclaw internal verify-flow-state-diff --after-file="$tmp_file" --quiet)
410
+ fi
411
+
412
+ if "\${verify_cmd[@]}" >/dev/null 2>&1; then
413
+ rm -f "$tmp_file" 2>/dev/null || true
414
+ return 0
415
+ fi
416
+
417
+ rm -f "$tmp_file" 2>/dev/null || true
418
+ return 1
419
+ }
420
+
345
421
  is_preimplementation_stage() {
346
422
  case "$1" in
347
423
  brainstorm|scope|design|spec|plan) return 0 ;;
@@ -480,6 +556,22 @@ if [ -n "$TARGET_STAGE" ] && [ "$CURRENT_STAGE" != "none" ]; then
480
556
  fi
481
557
  fi
482
558
 
559
+ if is_mutating_tool "$TOOL_LOWER" && printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/state/flow-state\.json'; then
560
+ if [ -n "$REASONS" ]; then
561
+ REASONS="$REASONS,direct_flow_state_edit"
562
+ else
563
+ REASONS="direct_flow_state_edit"
564
+ fi
565
+ FLOW_STATE_AFTER_JSON=$(extract_flow_state_after_json)
566
+ if [ -n "$FLOW_STATE_AFTER_JSON" ]; then
567
+ if ! verify_flow_state_candidate "$FLOW_STATE_AFTER_JSON"; then
568
+ REASONS="$REASONS,flow_state_edit_failed_internal_validation"
569
+ fi
570
+ else
571
+ REASONS="$REASONS,flow_state_edit_without_serialized_content"
572
+ fi
573
+ fi
574
+
483
575
  if is_preimplementation_stage "$CURRENT_STAGE" && is_mutating_tool "$TOOL_LOWER"; then
484
576
  if ! printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/'; then
485
577
  if [ -n "$REASONS" ]; then
@@ -567,7 +659,11 @@ PY
567
659
  fi
568
660
 
569
661
  if [ -n "$REASONS" ]; then
570
- 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."
662
+ if printf '%s' "$REASONS" | grep -Eq 'direct_flow_state_edit'; then
663
+ 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."
664
+ else
665
+ 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."
666
+ fi
571
667
  if command -v jq >/dev/null 2>&1; then
572
668
  ENTRY=$(jq -n -c \
573
669
  --arg ts "$TS" \
@@ -1826,6 +1922,9 @@ export function codexHooksJsonWithObservation() {
1826
1922
  hooks: [{
1827
1923
  type: "command",
1828
1924
  command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1925
+ }, {
1926
+ type: "command",
1927
+ command: "bash -lc 'if command -v cclaw >/dev/null 2>&1; then cclaw internal verify-current-state --quiet >/dev/null || true; else npx -y cclaw-cli internal verify-current-state --quiet >/dev/null || true; fi'"
1829
1928
  }]
1830
1929
  }],
1831
1930
  PreToolUse: [{
@@ -99,16 +99,21 @@ Shared closeout sequence applied by every stage skill.
99
99
  ## Required order
100
100
 
101
101
  1. Verify mandatory delegations are completed or explicitly waived.
102
- 2. Update \`.cclaw/state/flow-state.json\`:
103
- - mark passed gates,
104
- - clear blocked gates that are resolved,
105
- - update \`guardEvidence\`.
106
- 3. Persist stage artifact under \`.cclaw/artifacts/\`.
107
- 4. Run \`npx cclaw doctor\` and resolve failures.
108
- 5. **Capture through-flow learnings** see the policy below. Knowledge
102
+ 2. Persist stage artifact under \`.cclaw/artifacts/\`.
103
+ 3. Use the canonical helper:
104
+ - \`bash .cclaw/hooks/stage-complete.sh <stage>\`
105
+ - helper responsibilities: validate mandatory delegations, validate
106
+ current-stage gate evidence/artifact lint, update
107
+ \`stageGateCatalog\` + \`guardEvidence\`, and advance \`currentStage\`.
108
+ 4. Legacy fallback (only when helper is unavailable): manually edit
109
+ \`.cclaw/state/flow-state.json\` to mark passed gates, clear resolved
110
+ blocked gates, and update \`guardEvidence\`. This path is legacy and
111
+ intentionally noisy in workflow guards.
112
+ 5. Run \`npx cclaw doctor\` and resolve failures.
113
+ 6. **Capture through-flow learnings** — see the policy below. Knowledge
109
114
  accrues continuously across stages, not just at retro.
110
- 6. Notify user with stage completion and next action (\`/cc-next\`).
111
- 7. Stop; do not auto-run the next stage unless user asks.
115
+ 7. Notify user with stage completion and next action (\`/cc-next\`).
116
+ 8. Stop; do not auto-run the next stage unless user asks.
112
117
 
113
118
  ## Through-flow knowledge capture
114
119
 
@@ -222,7 +222,10 @@ function completionParametersBlock(schema) {
222
222
  - \`gates\`: ${gateList}
223
223
  - \`artifact\`: \`${RUNTIME_ROOT}/artifacts/${schema.artifactFile}\`
224
224
  - \`mandatory delegations\`: ${mandatory}
225
-
225
+ - \`completion helper\`: \`bash .cclaw/hooks/stage-complete.sh ${schema.stage}\`
226
+ - Fill \`## Learnings\` before closeout: either \`- None this stage.\` or JSON bullets with required keys \`type\`, \`trigger\`, \`action\`, \`confidence\` (knowledge-schema compatible).
227
+ - Record mandatory delegation completion/waiver in \`${RUNTIME_ROOT}/state/delegation-log.json\` with rationale as needed.
228
+ - Use the completion helper instead of raw \`flow-state.json\` edits (legacy direct edits trigger workflow-guard warnings or strict-mode blocks).
226
229
  Apply shared completion logic from:
227
230
  \`${COMPLETION_PROTOCOL_PATH}\`
228
231
  `;
@@ -58,8 +58,15 @@ Rollback / fallback: <if decision proves wrong>
58
58
 
59
59
  ## Self-improvement reminder
60
60
 
61
- Before closeout, capture 1-3 reusable insights in \`.cclaw/knowledge.jsonl\`
62
- whenever the stage produced non-obvious decisions, patterns, or lessons.
61
+ Before closeout, fill the artifact \`## Learnings\` section (do not write
62
+ \`.cclaw/knowledge.jsonl\` by hand):
63
+ - \`- None this stage.\` when nothing reusable emerged.
64
+ - Or 1-3 JSON bullets with required keys \`type\`, \`trigger\`, \`action\`,
65
+ \`confidence\` (optional fields may mirror knowledge.jsonl schema keys).
66
+ During \`bash .cclaw/hooks/stage-complete.sh <stage>\`, cclaw validates those
67
+ bullets, appends unique entries to \`.cclaw/knowledge.jsonl\`, and stamps a
68
+ harvest marker in the artifact.
69
+
63
70
  Prefer \`type=rule|pattern|lesson\` (\`compound\` stays retro-focused).
64
71
 
65
72
  Track policy:
@@ -67,8 +74,8 @@ Track policy:
67
74
  recommended for other stages.
68
75
  - \`quick\`: recommended only.
69
76
 
70
- "No learning captured" is acceptable only with an explicit reason
71
- (for example, purely mechanical edits with no new decisions).
77
+ \`- None this stage.\` is acceptable only when the stage produced no reusable
78
+ insight (for example, purely mechanical edits with no new decisions).
72
79
 
73
80
  ## Progressive disclosure baseline
74
81
 
@@ -42,6 +42,7 @@ export const DESIGN = {
42
42
  "If a section has no issues, say 'No issues found' and move on.",
43
43
  "Do not skip failure-mode mapping.",
44
44
  "For design baseline approval: present the full baseline. **STOP.** Do NOT proceed until user explicitly approves the design.",
45
+ "**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `bash .cclaw/hooks/stage-complete.sh design` (do not hand-edit `.cclaw/state/flow-state.json`).",
45
46
  "Take a firm position on every recommendation. Do NOT hedge with 'it depends' or 'you could do either'. State your opinion, then justify it.",
46
47
  "Use pushback patterns for weak framing: if the user says 'it's just a small change', respond with 'small changes to shared interfaces have outsized blast radius — let's map it'. If 'we'll refactor later', respond with 'later never comes — show me the refactor ticket or do it now'.",
47
48
  "When the user's proposed architecture is suboptimal, say so directly. Offer the alternative with concrete trade-offs, do not bury criticism in praise.",
@@ -29,7 +29,7 @@ export const PLAN = {
29
29
  "Map scope Locked Decisions — every D-XX from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
30
30
  "Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
31
31
  "Define checkpoints — mark points where progress should be validated before continuing.",
32
- "WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then update `flow-state.json` and tell user to run `/cc-next`."
32
+ "WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then close the stage with `bash .cclaw/hooks/stage-complete.sh plan` and tell user to run `/cc-next`."
33
33
  ],
34
34
  interactionProtocol: [
35
35
  "Plan in read-only mode relative to implementation.",
@@ -38,7 +38,8 @@ export const PLAN = {
38
38
  "Attach verification step to every task.",
39
39
  "Preserve locked scope boundaries: no silent scope reduction language in task rows.",
40
40
  "Enforce WAIT_FOR_CONFIRM: present the plan summary with options (A) Approve / (B) Revise / (C) Reject.",
41
- "**STOP.** Do NOT proceed until user explicitly approves. Then update `flow-state.json` and tell user to run `/cc-next`."
41
+ "**STOP.** Do NOT proceed until user explicitly approves.",
42
+ "**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `bash .cclaw/hooks/stage-complete.sh plan` and tell the user to run `/cc-next`."
42
43
  ],
43
44
  process: [
44
45
  "Build dependency graph and ordered slices.",
@@ -41,7 +41,8 @@ export const SCOPE = {
41
41
  "Record explicit in-scope and out-of-scope contract.",
42
42
  "Once the user accepts or rejects a recommendation, commit fully. Do not re-argue.",
43
43
  "Produce a clean scope summary after all issues are resolved.",
44
- "**STOP.** Wait for explicit user approval of scope contract before advancing to design."
44
+ "**STOP.** Wait for explicit user approval of scope contract before advancing to design.",
45
+ "**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `bash .cclaw/hooks/stage-complete.sh scope` (do not hand-edit `.cclaw/state/flow-state.json`)."
45
46
  ],
46
47
  process: [
47
48
  "Run premise challenge and existing-solution leverage check.",
@@ -92,18 +92,11 @@ export const TDD = {
92
92
  ],
93
93
  commonRationalizations: [
94
94
  "Writing code before failing test",
95
- "Asserting implementation details instead of behavior",
96
- "Big-bang implementation across multiple slices",
97
95
  "Partial test runs presented as GREEN",
98
96
  "Skipping evidence capture",
99
97
  "Undocumented refactor changes",
100
- "Adding features beyond what RED tests require",
101
- "No failing test output (RED missing)",
102
- "Implementation edits appear before RED evidence",
103
98
  "No full-suite GREEN evidence",
104
- "No refactor notes",
105
- "Multiple tasks implemented in one pass without justification",
106
- "Files changed outside current slice scope"
99
+ "Multiple tasks implemented in one pass without justification"
107
100
  ],
108
101
  policyNeedles: ["RED", "GREEN", "REFACTOR", "failing test", "full test suite", "acceptance criteria", "traceable to plan slice"],
109
102
  artifactFile: "06-tdd.md",
@@ -45,6 +45,9 @@ inputs_hash: sha256:pending
45
45
  ## Assumptions and Open Questions
46
46
  - **Assumptions:**
47
47
  - **Open questions (or "None"):**
48
+
49
+ ## Learnings
50
+ - None this stage.
48
51
  `,
49
52
  "02-scope.md": `---
50
53
  stage: scope
@@ -148,6 +151,9 @@ inputs_hash: sha256:pending
148
151
  - Accepted scope:
149
152
  - Deferred:
150
153
  - Explicitly excluded:
154
+
155
+ ## Learnings
156
+ - None this stage.
151
157
  `,
152
158
  "03-design.md": `---
153
159
  stage: design
@@ -241,6 +247,9 @@ inputs_hash: sha256:pending
241
247
  | Distribution & Delivery Review | | |
242
248
 
243
249
  **Decisions made:** 0 | **Unresolved:** 0
250
+
251
+ ## Learnings
252
+ - None this stage.
244
253
  `,
245
254
  "04-spec.md": `---
246
255
  stage: spec
@@ -294,6 +303,9 @@ inputs_hash: sha256:pending
294
303
  ## Approval
295
304
  - Approved by:
296
305
  - Date:
306
+
307
+ ## Learnings
308
+ - None this stage.
297
309
  `,
298
310
  "05-plan.md": `---
299
311
  stage: plan
@@ -370,6 +382,9 @@ Execution rule: complete and verify each batch before starting the next batch.
370
382
  ## WAIT_FOR_CONFIRM
371
383
  - Status: pending
372
384
  - Confirmed by:
385
+
386
+ ## Learnings
387
+ - None this stage.
373
388
  `,
374
389
  "06-tdd.md": `---
375
390
  stage: tdd
@@ -434,6 +449,9 @@ inputs_hash: sha256:pending
434
449
  | Slice | Reproduction test | RED-without-fix evidence | GREEN-with-fix evidence | Revert-guard note |
435
450
  |---|---|---|---|---|
436
451
  | S-1 | | | | |
452
+
453
+ ## Learnings
454
+ - None this stage.
437
455
  `,
438
456
  "07-review.md": `---
439
457
  stage: review
@@ -504,6 +522,9 @@ inputs_hash: sha256:pending
504
522
 
505
523
  ## Final Verdict
506
524
  - APPROVED | APPROVED_WITH_CONCERNS | BLOCKED
525
+
526
+ ## Learnings
527
+ - None this stage.
507
528
  `,
508
529
  "07-review-army.json": `{
509
530
  "version": 1,
@@ -571,6 +592,9 @@ inputs_hash: sha256:pending
571
592
  - Run \`/cc-ops retro\` before archive.
572
593
  - Retro artifact path: \`.cclaw/artifacts/09-retro.md\`
573
594
  - Archive remains blocked until retro gate is complete.
595
+
596
+ ## Learnings
597
+ - None this stage.
574
598
  `,
575
599
  "09-retro.md": `---
576
600
  stage: retro
@@ -611,6 +635,9 @@ inputs_hash: sha256:pending
611
635
  - RETRO_COMPLETE: yes
612
636
  - Completed at (UTC):
613
637
  - Notes:
638
+
639
+ ## Learnings
640
+ - None this stage.
614
641
  `
615
642
  };
616
643
  export const RULEBOOK_MARKDOWN = `# Cclaw Rulebook
package/dist/install.js CHANGED
@@ -23,7 +23,7 @@ import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/a
23
23
  import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
24
24
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
25
25
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
26
- import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
26
+ import { sessionStartScript, stopCheckpointScript, stageCompleteScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
27
27
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
28
28
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
29
29
  import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
@@ -616,11 +616,13 @@ async function writeHooks(projectRoot, config) {
616
616
  await ensureDir(hooksDir);
617
617
  await writeFileSafe(path.join(hooksDir, "session-start.sh"), sessionStartScript());
618
618
  await writeFileSafe(path.join(hooksDir, "stop-checkpoint.sh"), stopCheckpointScript());
619
+ await writeFileSafe(path.join(hooksDir, "stage-complete.sh"), stageCompleteScript());
619
620
  await writeFileSafe(path.join(hooksDir, "pre-compact.sh"), preCompactScript());
620
621
  await writeFileSafe(path.join(hooksDir, "prompt-guard.sh"), promptGuardScript({
621
622
  strictMode: config.promptGuardMode === "strict"
622
623
  }));
623
624
  await writeFileSafe(path.join(hooksDir, "workflow-guard.sh"), workflowGuardScript({
625
+ workflowGuardMode: config.strictness ?? "advisory",
624
626
  tddEnforcementMode: config.tddEnforcement ?? "advisory",
625
627
  tddTestGlobs: config.tddTestGlobs
626
628
  }));
@@ -631,6 +633,7 @@ async function writeHooks(projectRoot, config) {
631
633
  for (const script of [
632
634
  "session-start.sh",
633
635
  "stop-checkpoint.sh",
636
+ "stage-complete.sh",
634
637
  "pre-compact.sh",
635
638
  "prompt-guard.sh",
636
639
  "workflow-guard.sh",
@@ -1260,7 +1263,12 @@ function stripManagedHookCommands(value) {
1260
1263
  }
1261
1264
  function isManagedRuntimeHookCommand(command) {
1262
1265
  const normalized = command.trim().replace(/\s+/gu, " ");
1263
- return /(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized);
1266
+ if (/(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized)) {
1267
+ return true;
1268
+ }
1269
+ // Codex UserPromptSubmit non-blocking state nudge:
1270
+ // bash -lc '... cclaw internal verify-current-state --quiet ...'
1271
+ return /internal verify-current-state --quiet/u.test(normalized);
1264
1272
  }
1265
1273
  async function removeManagedHookEntries(hookFilePath) {
1266
1274
  if (!(await exists(hookFilePath)))
@@ -0,0 +1,7 @@
1
+ import type { Writable } from "node:stream";
2
+ interface InternalIo {
3
+ stdout: Writable;
4
+ stderr: Writable;
5
+ }
6
+ export declare function runInternalCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
7
+ export {};