cclaw-cli 0.46.13 → 0.46.15
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/README.md +16 -11
- package/dist/artifact-linter.d.ts +2 -0
- package/dist/artifact-linter.js +11 -1
- package/dist/config.d.ts +12 -7
- package/dist/config.js +79 -10
- package/dist/content/compound-command.d.ts +5 -2
- package/dist/content/compound-command.js +47 -16
- package/dist/content/contracts.js +1 -1
- package/dist/content/examples.d.ts +1 -0
- package/dist/content/examples.js +13 -0
- package/dist/content/harnesses-doc.js +11 -0
- package/dist/content/learnings.d.ts +2 -1
- package/dist/content/learnings.js +5 -3
- package/dist/content/observe.d.ts +2 -1
- package/dist/content/observe.js +174 -14
- package/dist/content/research-playbooks.js +36 -0
- package/dist/content/stage-schema.js +8 -1
- package/dist/content/stages/design.js +14 -7
- package/dist/content/templates.js +49 -0
- package/dist/content/utility-skills.js +1 -1
- package/dist/gate-evidence.js +56 -1
- package/dist/install.d.ts +3 -3
- package/dist/install.js +13 -8
- package/dist/knowledge-store.d.ts +3 -0
- package/dist/knowledge-store.js +11 -1
- package/dist/types.d.ts +30 -2
- package/package.json +1 -1
package/dist/content/observe.js
CHANGED
|
@@ -156,9 +156,12 @@ exit 0
|
|
|
156
156
|
export function workflowGuardScript(options = {}) {
|
|
157
157
|
const workflowGuardMode = options.workflowGuardMode === "strict" ? "strict" : "advisory";
|
|
158
158
|
const tddEnforcementMode = options.tddEnforcementMode === "strict" ? "strict" : "advisory";
|
|
159
|
-
const
|
|
160
|
-
? options.
|
|
161
|
-
: "**/*.test
|
|
159
|
+
const tddTestPathPatterns = options.tddTestPathPatterns && options.tddTestPathPatterns.length > 0
|
|
160
|
+
? options.tddTestPathPatterns.join(",")
|
|
161
|
+
: "**/*.test.*,**/tests/**,**/__tests__/**";
|
|
162
|
+
const tddProductionPathPatterns = options.tddProductionPathPatterns && options.tddProductionPathPatterns.length > 0
|
|
163
|
+
? options.tddProductionPathPatterns.join(",")
|
|
164
|
+
: "";
|
|
162
165
|
return `#!/usr/bin/env bash
|
|
163
166
|
# cclaw workflow guard hook — generated by cclaw sync
|
|
164
167
|
# Enforces stage-aware command discipline and recent flow-state read hygiene.
|
|
@@ -166,7 +169,8 @@ set -uo pipefail
|
|
|
166
169
|
WORKFLOW_GUARD_MODE="\${CCLAW_WORKFLOW_GUARD_MODE:-${workflowGuardMode}}"
|
|
167
170
|
MAX_FLOW_READ_AGE_SEC="\${CCLAW_WORKFLOW_GUARD_MAX_AGE_SEC:-1800}"
|
|
168
171
|
TDD_ENFORCEMENT_MODE="${tddEnforcementMode}"
|
|
169
|
-
|
|
172
|
+
TDD_TEST_PATH_PATTERNS="${tddTestPathPatterns}"
|
|
173
|
+
TDD_PRODUCTION_PATH_PATTERNS="${tddProductionPathPatterns}"
|
|
170
174
|
|
|
171
175
|
${RUNTIME_SHELL_DETECT_ROOT}
|
|
172
176
|
|
|
@@ -425,34 +429,153 @@ is_preimplementation_stage() {
|
|
|
425
429
|
esac
|
|
426
430
|
}
|
|
427
431
|
|
|
432
|
+
normalize_payload_path() {
|
|
433
|
+
local raw="$1"
|
|
434
|
+
local normalized="$raw"
|
|
435
|
+
normalized=$(printf '%s' "$normalized" | tr '\\\\' '/')
|
|
436
|
+
normalized=$(printf '%s' "$normalized" | tr '[:upper:]' '[:lower:]')
|
|
437
|
+
normalized="\${normalized#./}"
|
|
438
|
+
printf '%s' "$normalized"
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
extract_payload_paths() {
|
|
442
|
+
if command -v jq >/dev/null 2>&1; then
|
|
443
|
+
printf '%s' "$INPUT" | jq -r '
|
|
444
|
+
[.. | objects | (.path?, .file_path?, .filepath?) | select(type == "string" and length > 0)]
|
|
445
|
+
| unique
|
|
446
|
+
| .[]
|
|
447
|
+
' 2>/dev/null || printf ''
|
|
448
|
+
return 0
|
|
449
|
+
fi
|
|
450
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
451
|
+
INPUT_JSON="$INPUT" python3 - <<'PY'
|
|
452
|
+
import json
|
|
453
|
+
import os
|
|
454
|
+
|
|
455
|
+
def visit(node, acc):
|
|
456
|
+
if isinstance(node, dict):
|
|
457
|
+
for key in ("path", "file_path", "filepath"):
|
|
458
|
+
value = node.get(key)
|
|
459
|
+
if isinstance(value, str) and value.strip():
|
|
460
|
+
acc.add(value.strip())
|
|
461
|
+
for value in node.values():
|
|
462
|
+
visit(value, acc)
|
|
463
|
+
elif isinstance(node, list):
|
|
464
|
+
for value in node:
|
|
465
|
+
visit(value, acc)
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
payload = json.loads(os.environ.get("INPUT_JSON", "{}"))
|
|
469
|
+
except Exception:
|
|
470
|
+
payload = {}
|
|
471
|
+
|
|
472
|
+
items = set()
|
|
473
|
+
visit(payload, items)
|
|
474
|
+
for value in sorted(items):
|
|
475
|
+
print(value)
|
|
476
|
+
PY
|
|
477
|
+
return 0
|
|
478
|
+
fi
|
|
479
|
+
printf ''
|
|
480
|
+
return 0
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
matches_path_patterns() {
|
|
484
|
+
local candidate="$1"
|
|
485
|
+
local patterns_csv="$2"
|
|
486
|
+
[ -n "$candidate" ] || return 1
|
|
487
|
+
[ -n "$patterns_csv" ] || return 1
|
|
488
|
+
local old_ifs="$IFS"
|
|
489
|
+
IFS=','
|
|
490
|
+
for pattern in $patterns_csv; do
|
|
491
|
+
local normalized_pattern
|
|
492
|
+
normalized_pattern=$(normalize_payload_path "$pattern")
|
|
493
|
+
[ -n "$normalized_pattern" ] || continue
|
|
494
|
+
case "$candidate" in
|
|
495
|
+
$normalized_pattern)
|
|
496
|
+
IFS="$old_ifs"
|
|
497
|
+
return 0
|
|
498
|
+
;;
|
|
499
|
+
esac
|
|
500
|
+
done
|
|
501
|
+
IFS="$old_ifs"
|
|
502
|
+
return 1
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
is_code_like_path() {
|
|
506
|
+
local candidate="$1"
|
|
507
|
+
printf '%s' "$candidate" | grep -Eq '\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift)$'
|
|
508
|
+
}
|
|
509
|
+
|
|
428
510
|
is_tdd_test_payload() {
|
|
429
511
|
local text="$1"
|
|
430
|
-
|
|
431
|
-
|
|
512
|
+
local payload_paths="$2"
|
|
513
|
+
if [ -n "$payload_paths" ]; then
|
|
514
|
+
while IFS= read -r raw_path; do
|
|
515
|
+
[ -n "$raw_path" ] || continue
|
|
516
|
+
local normalized
|
|
517
|
+
normalized=$(normalize_payload_path "$raw_path")
|
|
518
|
+
if matches_path_patterns "$normalized" "$TDD_TEST_PATH_PATTERNS"; then
|
|
519
|
+
return 0
|
|
520
|
+
fi
|
|
521
|
+
done <<< "$payload_paths"
|
|
432
522
|
fi
|
|
433
|
-
if printf '%s' "$
|
|
523
|
+
if printf '%s' "$text" | grep -Eq '/tests?/|/__tests__/|\\.test\\.'; then
|
|
434
524
|
return 0
|
|
435
525
|
fi
|
|
436
526
|
return 1
|
|
437
527
|
}
|
|
438
528
|
|
|
439
|
-
|
|
529
|
+
is_tdd_production_path() {
|
|
530
|
+
local normalized="$1"
|
|
531
|
+
[ -n "$normalized" ] || return 1
|
|
532
|
+
if printf '%s' "$normalized" | grep -Eq '(^|/)\\.cclaw/'; then
|
|
533
|
+
return 1
|
|
534
|
+
fi
|
|
535
|
+
if matches_path_patterns "$normalized" "$TDD_TEST_PATH_PATTERNS"; then
|
|
536
|
+
return 1
|
|
537
|
+
fi
|
|
538
|
+
if [ -n "$TDD_PRODUCTION_PATH_PATTERNS" ]; then
|
|
539
|
+
matches_path_patterns "$normalized" "$TDD_PRODUCTION_PATH_PATTERNS"
|
|
540
|
+
return $?
|
|
541
|
+
fi
|
|
542
|
+
is_code_like_path "$normalized"
|
|
543
|
+
return $?
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
is_tdd_production_write_payload() {
|
|
440
547
|
local text="$1"
|
|
548
|
+
local payload_paths="$2"
|
|
549
|
+
if [ -n "$payload_paths" ]; then
|
|
550
|
+
while IFS= read -r raw_path; do
|
|
551
|
+
[ -n "$raw_path" ] || continue
|
|
552
|
+
local normalized
|
|
553
|
+
normalized=$(normalize_payload_path "$raw_path")
|
|
554
|
+
if is_tdd_production_path "$normalized"; then
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
done <<< "$payload_paths"
|
|
558
|
+
return 1
|
|
559
|
+
fi
|
|
560
|
+
if [ -n "$TDD_PRODUCTION_PATH_PATTERNS" ]; then
|
|
561
|
+
return 1
|
|
562
|
+
fi
|
|
441
563
|
if printf '%s' "$text" | grep -Eq '\\.cclaw/'; then
|
|
442
564
|
return 1
|
|
443
565
|
fi
|
|
444
566
|
if ! printf '%s' "$text" | grep -Eq '\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift)'; then
|
|
445
567
|
return 1
|
|
446
568
|
fi
|
|
447
|
-
if is_tdd_test_payload "$text"; then
|
|
569
|
+
if is_tdd_test_payload "$text" ""; then
|
|
448
570
|
return 1
|
|
449
571
|
fi
|
|
450
572
|
return 0
|
|
451
573
|
}
|
|
452
574
|
|
|
453
|
-
|
|
575
|
+
tdd_cycle_counts() {
|
|
454
576
|
if [ ! -f "$TDD_LOG_FILE" ] || [ ! -s "$TDD_LOG_FILE" ]; then
|
|
455
|
-
|
|
577
|
+
printf '0:0'
|
|
578
|
+
return 0
|
|
456
579
|
fi
|
|
457
580
|
local red_count="0"
|
|
458
581
|
local green_count="0"
|
|
@@ -512,12 +635,37 @@ PY
|
|
|
512
635
|
fi
|
|
513
636
|
[ -n "$red_count" ] || red_count="0"
|
|
514
637
|
[ -n "$green_count" ] || green_count="0"
|
|
638
|
+
printf '%s:%s' "$red_count" "$green_count"
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
has_open_red_cycle() {
|
|
642
|
+
local counts
|
|
643
|
+
counts=$(tdd_cycle_counts)
|
|
644
|
+
local red_count="\${counts%%:*}"
|
|
645
|
+
local green_count="\${counts##*:}"
|
|
515
646
|
if [ "$red_count" -gt "$green_count" ]; then
|
|
516
647
|
return 0
|
|
517
648
|
fi
|
|
518
649
|
return 1
|
|
519
650
|
}
|
|
520
651
|
|
|
652
|
+
tdd_cycle_state() {
|
|
653
|
+
local counts
|
|
654
|
+
counts=$(tdd_cycle_counts)
|
|
655
|
+
local red_count="\${counts%%:*}"
|
|
656
|
+
local green_count="\${counts##*:}"
|
|
657
|
+
if [ "$red_count" -le 0 ]; then
|
|
658
|
+
printf 'need_red'
|
|
659
|
+
return 0
|
|
660
|
+
fi
|
|
661
|
+
if [ "$red_count" -gt "$green_count" ]; then
|
|
662
|
+
printf 'red_open'
|
|
663
|
+
return 0
|
|
664
|
+
fi
|
|
665
|
+
printf 'green_done'
|
|
666
|
+
return 0
|
|
667
|
+
}
|
|
668
|
+
|
|
521
669
|
detect_target_stage() {
|
|
522
670
|
local text="$1"
|
|
523
671
|
for stage in brainstorm scope design spec plan tdd review ship; do
|
|
@@ -546,6 +694,11 @@ FLOW_COMMAND_INVOKED=0
|
|
|
546
694
|
if is_flow_progression_command "$PAYLOAD_LOWER"; then
|
|
547
695
|
FLOW_COMMAND_INVOKED=1
|
|
548
696
|
fi
|
|
697
|
+
MUTATION_PATHS=""
|
|
698
|
+
if is_mutating_tool "$TOOL_LOWER"; then
|
|
699
|
+
MUTATION_PATHS=$(extract_payload_paths)
|
|
700
|
+
fi
|
|
701
|
+
TDD_CYCLE_STATE="unknown"
|
|
549
702
|
if [ -n "$TARGET_STAGE" ] && [ "$CURRENT_STAGE" != "none" ]; then
|
|
550
703
|
CURRENT_IDX=$(stage_index "$CURRENT_STAGE")
|
|
551
704
|
TARGET_IDX=$(stage_index "$TARGET_STAGE")
|
|
@@ -583,8 +736,13 @@ if is_preimplementation_stage "$CURRENT_STAGE" && is_mutating_tool "$TOOL_LOWER"
|
|
|
583
736
|
fi
|
|
584
737
|
|
|
585
738
|
if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
586
|
-
if
|
|
587
|
-
if
|
|
739
|
+
if is_tdd_production_write_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
740
|
+
if has_open_red_cycle; then
|
|
741
|
+
TDD_CYCLE_STATE="red_open"
|
|
742
|
+
else
|
|
743
|
+
TDD_CYCLE_STATE=$(tdd_cycle_state)
|
|
744
|
+
fi
|
|
745
|
+
if [ "$TDD_CYCLE_STATE" = "need_red" ]; then
|
|
588
746
|
if [ -n "$REASONS" ]; then
|
|
589
747
|
REASONS="$REASONS,tdd_write_without_open_red"
|
|
590
748
|
else
|
|
@@ -659,7 +817,9 @@ PY
|
|
|
659
817
|
fi
|
|
660
818
|
|
|
661
819
|
if [ -n "$REASONS" ]; then
|
|
662
|
-
if printf '%s' "$REASONS" | grep -Eq '
|
|
820
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red'; then
|
|
821
|
+
NOTE="Cclaw workflow guard: Write a failing test first before editing production files during tdd stage (state=\${TDD_CYCLE_STATE})."
|
|
822
|
+
elif printf '%s' "$REASONS" | grep -Eq 'direct_flow_state_edit'; then
|
|
663
823
|
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
824
|
else
|
|
665
825
|
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."
|
|
@@ -102,6 +102,42 @@ Summarize citable domain practices for a narrow design decision.
|
|
|
102
102
|
|
|
103
103
|
- Cite authoritative sources (official docs/standards).
|
|
104
104
|
- State uncertainty explicitly when consensus is weak.
|
|
105
|
+
`,
|
|
106
|
+
"research-fleet.md": `# Parallel Research Fleet Playbook
|
|
107
|
+
|
|
108
|
+
## Purpose
|
|
109
|
+
|
|
110
|
+
Run a four-lens investigation before design lock so architecture choices are grounded
|
|
111
|
+
in current ecosystem data, not intuition.
|
|
112
|
+
|
|
113
|
+
## Dispatch Lenses (fan-out)
|
|
114
|
+
|
|
115
|
+
Launch four independent investigation threads in parallel when the harness supports it
|
|
116
|
+
(or sequentially with explicit role-switch logs when it does not):
|
|
117
|
+
|
|
118
|
+
1. **stack-researcher** — dependency compatibility, alternatives, deprecations.
|
|
119
|
+
2. **features-researcher** — domain conventions and product/UX patterns.
|
|
120
|
+
3. **architecture-researcher** — architecture options and trade-off matrix.
|
|
121
|
+
4. **pitfalls-researcher** — known failure modes, CVEs, and operational traps.
|
|
122
|
+
|
|
123
|
+
## Output Contract
|
|
124
|
+
|
|
125
|
+
Write findings to \`.cclaw/artifacts/02a-research.md\` with these sections:
|
|
126
|
+
|
|
127
|
+
- \`## Stack Analysis\`
|
|
128
|
+
- \`## Features & Patterns\`
|
|
129
|
+
- \`## Architecture Options\`
|
|
130
|
+
- \`## Pitfalls & Risks\`
|
|
131
|
+
- \`## Synthesis\`
|
|
132
|
+
|
|
133
|
+
Each section must contain concrete notes and at least one evidence reference
|
|
134
|
+
(source URL, file path, or command output anchor).
|
|
135
|
+
|
|
136
|
+
## Guardrails
|
|
137
|
+
|
|
138
|
+
- Investigate first; no production code edits in this playbook.
|
|
139
|
+
- Keep lenses independent during fan-out; merge only in synthesis.
|
|
140
|
+
- If any lens is incomplete, record it explicitly in \`## Synthesis\` as a blocker.
|
|
105
141
|
`,
|
|
106
142
|
"git-history.md": `# Git History Playbook
|
|
107
143
|
|
|
@@ -13,6 +13,7 @@ const REQUIRED_GATE_IDS = {
|
|
|
13
13
|
"scope_user_approved"
|
|
14
14
|
],
|
|
15
15
|
design: [
|
|
16
|
+
"design_research_complete",
|
|
16
17
|
"design_architecture_locked",
|
|
17
18
|
"design_data_flow_mapped",
|
|
18
19
|
"design_failure_modes_mapped",
|
|
@@ -53,7 +54,13 @@ const REQUIRED_GATE_IDS = {
|
|
|
53
54
|
const REQUIRED_ARTIFACT_SECTIONS = {
|
|
54
55
|
brainstorm: ["Context", "Problem", "Approaches", "Selected Direction"],
|
|
55
56
|
scope: ["Scope Mode", "In Scope / Out of Scope", "Completion Dashboard", "Scope Summary"],
|
|
56
|
-
design: [
|
|
57
|
+
design: [
|
|
58
|
+
"Research Fleet Synthesis",
|
|
59
|
+
"Architecture Boundaries",
|
|
60
|
+
"Architecture Diagram",
|
|
61
|
+
"Failure Mode Table",
|
|
62
|
+
"Completion Dashboard"
|
|
63
|
+
],
|
|
57
64
|
spec: ["Acceptance Criteria", "Edge Cases", "Testability Map", "Approval"],
|
|
58
65
|
plan: ["Task List", "Dependency Batches", "Acceptance Mapping", "WAIT_FOR_CONFIRM"],
|
|
59
66
|
tdd: ["RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability", "Verification Ladder"],
|
|
@@ -21,6 +21,7 @@ export const DESIGN = {
|
|
|
21
21
|
],
|
|
22
22
|
checklist: [
|
|
23
23
|
"Trivial-Change Escape Hatch — If scope artifact shows ≤3 files, zero new interfaces, and no cross-module data flow, skip full review sections. Produce a mini-design: one paragraph of rationale, list of changed files, one risk to watch. Proceed to spec.",
|
|
24
|
+
"Parallel Research Fleet — run `research/research-fleet.md` before architecture lock. Record 4-lens findings in `.cclaw/artifacts/02a-research.md` and summarize resulting decisions in `## Research Fleet Synthesis`.",
|
|
24
25
|
"Design Doc Check — read existing design docs, scope artifact, brainstorm artifact. If a design doc exists that covers this area, check for 'Supersedes:' and use the latest. Use upstream artifacts as source of truth.",
|
|
25
26
|
"Codebase Investigation — Before any design decision, read the actual code in the blast radius. List every file that will be touched, its current responsibilities, and existing patterns (error handling, naming, test style). Design must conform to discovered patterns, not impose new ones without justification.",
|
|
26
27
|
"Step 0: Scope Challenge — what existing code solves sub-problems? Minimum change set? Complexity check: 8+ files or 2+ new services = complexity smell → flag for possible scope reduction.",
|
|
@@ -50,6 +51,7 @@ export const DESIGN = {
|
|
|
50
51
|
],
|
|
51
52
|
process: [
|
|
52
53
|
"Read upstream artifacts (brainstorm, scope).",
|
|
54
|
+
"Run the research fleet playbook and write `.cclaw/artifacts/02a-research.md` before locking architecture choices.",
|
|
53
55
|
"Investigate codebase: read files in blast radius, catalogue current patterns and responsibilities.",
|
|
54
56
|
"Run Step 0 scope challenge: existing code leverage, minimum change set, complexity check.",
|
|
55
57
|
"Walk through each review section interactively.",
|
|
@@ -62,12 +64,14 @@ export const DESIGN = {
|
|
|
62
64
|
"Write design lock artifact for downstream spec/plan."
|
|
63
65
|
],
|
|
64
66
|
requiredGates: [
|
|
67
|
+
{ id: "design_research_complete", description: "Parallel research artifact is complete and synthesized into design decisions." },
|
|
65
68
|
{ id: "design_architecture_locked", description: "Architecture boundaries are explicit and approved." },
|
|
66
69
|
{ id: "design_data_flow_mapped", description: "Data/state flow includes edge-case paths." },
|
|
67
70
|
{ id: "design_failure_modes_mapped", description: "Failure modes and mitigations are documented." },
|
|
68
71
|
{ id: "design_test_and_perf_defined", description: "Test strategy and performance budget are defined." }
|
|
69
72
|
],
|
|
70
73
|
requiredEvidence: [
|
|
74
|
+
"Research artifact written to `.cclaw/artifacts/02a-research.md` with stack/features/architecture/pitfalls sections plus synthesis.",
|
|
71
75
|
"Artifact written to `.cclaw/artifacts/03-design.md`.",
|
|
72
76
|
"Failure-mode table exists with mitigations.",
|
|
73
77
|
"Test strategy includes unit/integration/e2e expectations.",
|
|
@@ -77,15 +81,18 @@ export const DESIGN = {
|
|
|
77
81
|
],
|
|
78
82
|
inputs: ["scope contract", "system constraints", "non-functional requirements"],
|
|
79
83
|
requiredContext: [
|
|
84
|
+
"parallel research synthesis from `.cclaw/artifacts/02a-research.md`",
|
|
80
85
|
"existing architecture and boundaries",
|
|
81
86
|
"operational constraints",
|
|
82
87
|
"security and reliability expectations"
|
|
83
88
|
],
|
|
84
89
|
researchPlaybooks: [
|
|
90
|
+
"research/research-fleet.md",
|
|
85
91
|
"research/framework-docs-lookup.md",
|
|
86
92
|
"research/best-practices-lookup.md"
|
|
87
93
|
],
|
|
88
94
|
outputs: [
|
|
95
|
+
"parallel research synthesis artifact",
|
|
89
96
|
"architecture lock",
|
|
90
97
|
"risk and failure map",
|
|
91
98
|
"test and performance baseline",
|
|
@@ -110,17 +117,12 @@ export const DESIGN = {
|
|
|
110
117
|
"Missing data-flow edge cases",
|
|
111
118
|
"No performance budget for critical path",
|
|
112
119
|
"Batching multiple design issues into one question",
|
|
113
|
-
"Skipping review sections because plan seems simple",
|
|
114
120
|
"Agreeing with user's architecture choice without evaluating alternatives",
|
|
115
|
-
"Hedging every recommendation with 'it depends' instead of taking a position",
|
|
116
|
-
"No explicit architecture boundary section",
|
|
117
|
-
"No failure recovery strategy",
|
|
118
|
-
"No defined test/perf baseline",
|
|
119
121
|
"No NOT-in-scope output section",
|
|
120
|
-
"No What-already-exists output section",
|
|
121
122
|
"Design decisions made without reading the actual code first"
|
|
122
123
|
],
|
|
123
124
|
policyNeedles: [
|
|
125
|
+
"Parallel Research Fleet",
|
|
124
126
|
"Architecture",
|
|
125
127
|
"Data Flow",
|
|
126
128
|
"Failure Modes and Mitigation",
|
|
@@ -186,11 +188,16 @@ export const DESIGN = {
|
|
|
186
188
|
],
|
|
187
189
|
completionStatus: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED"],
|
|
188
190
|
crossStageTrace: {
|
|
189
|
-
readsFrom: [
|
|
191
|
+
readsFrom: [
|
|
192
|
+
".cclaw/artifacts/01-brainstorm.md",
|
|
193
|
+
".cclaw/artifacts/02-scope.md",
|
|
194
|
+
".cclaw/artifacts/02a-research.md"
|
|
195
|
+
],
|
|
190
196
|
writesTo: [".cclaw/artifacts/03-design.md"],
|
|
191
197
|
traceabilityRule: "Every architecture decision must trace to a scope boundary. Every downstream spec requirement must trace to a design decision."
|
|
192
198
|
},
|
|
193
199
|
artifactValidation: [
|
|
200
|
+
{ section: "Research Fleet Synthesis", required: true, validationRule: "Must summarize all four lenses (stack/features/architecture/pitfalls) and map findings to concrete design decisions." },
|
|
194
201
|
{ section: "Codebase Investigation", required: false, validationRule: "Must list blast-radius files with current responsibilities and discovered patterns." },
|
|
195
202
|
{ section: "Search Before Building", required: false, validationRule: "For each technical choice: Layer 1 (exact match), Layer 2 (partial match), Layer 3 (inspiration), EUREKA labels with reuse-first default." },
|
|
196
203
|
{ section: "Architecture Boundaries", required: true, validationRule: "Must list component boundaries with ownership." },
|
|
@@ -152,6 +152,47 @@ inputs_hash: sha256:pending
|
|
|
152
152
|
- Deferred:
|
|
153
153
|
- Explicitly excluded:
|
|
154
154
|
|
|
155
|
+
## Learnings
|
|
156
|
+
- None this stage.
|
|
157
|
+
`,
|
|
158
|
+
"02a-research.md": `---
|
|
159
|
+
stage: design
|
|
160
|
+
schema_version: 1
|
|
161
|
+
version: 0.18.0
|
|
162
|
+
feature: <feature-id>
|
|
163
|
+
locked_decisions: []
|
|
164
|
+
inputs_hash: sha256:pending
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
# Research Report
|
|
168
|
+
|
|
169
|
+
## Stack Analysis
|
|
170
|
+
| Topic | Finding | Evidence |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| Dependency compatibility | | |
|
|
173
|
+
| Alternatives/deprecations | | |
|
|
174
|
+
|
|
175
|
+
## Features & Patterns
|
|
176
|
+
| Topic | Finding | Evidence |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| Domain conventions | | |
|
|
179
|
+
| UX/product patterns | | |
|
|
180
|
+
|
|
181
|
+
## Architecture Options
|
|
182
|
+
| Option | Trade-offs | Recommendation | Evidence |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| A | | | |
|
|
185
|
+
| B | | | |
|
|
186
|
+
|
|
187
|
+
## Pitfalls & Risks
|
|
188
|
+
| Risk | Impact | Mitigation | Evidence |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| | | | |
|
|
191
|
+
|
|
192
|
+
## Synthesis
|
|
193
|
+
- Key decisions informed by research:
|
|
194
|
+
- Open questions:
|
|
195
|
+
|
|
155
196
|
## Learnings
|
|
156
197
|
- None this stage.
|
|
157
198
|
`,
|
|
@@ -178,6 +219,14 @@ inputs_hash: sha256:pending
|
|
|
178
219
|
| Layer 2 | | |
|
|
179
220
|
| Layer 3 | | |
|
|
180
221
|
|
|
222
|
+
## Research Fleet Synthesis
|
|
223
|
+
| Lens | Key findings | Design impact | Evidence |
|
|
224
|
+
|---|---|---|---|
|
|
225
|
+
| stack-researcher | | | |
|
|
226
|
+
| features-researcher | | | |
|
|
227
|
+
| architecture-researcher | | | |
|
|
228
|
+
| pitfalls-researcher | | | |
|
|
229
|
+
|
|
181
230
|
## Architecture Boundaries
|
|
182
231
|
| Component | Responsibility | Owner |
|
|
183
232
|
|---|---|---|
|
|
@@ -1469,7 +1469,7 @@ For each lens, write either a knowledge entry **or** the explicit string
|
|
|
1469
1469
|
## Output protocol
|
|
1470
1470
|
|
|
1471
1471
|
For every harvested insight, append one strict-schema JSON line to
|
|
1472
|
-
\`.cclaw/knowledge.jsonl\` (fields: \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`).
|
|
1472
|
+
\`.cclaw/knowledge.jsonl\` (fields: \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`; optional: \`source\`, \`severity\`).
|
|
1473
1473
|
See the \`learnings\` skill for the canonical shape. Choose \`type\`:
|
|
1474
1474
|
|
|
1475
1475
|
- \`compound\` for process/speed accelerators.
|
package/dist/gate-evidence.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { checkReviewVerdictConsistency, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
|
|
3
|
+
import { checkReviewVerdictConsistency, extractMarkdownSectionBody, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
|
|
4
4
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
5
5
|
import { stageSchema } from "./content/stage-schema.js";
|
|
6
6
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
@@ -26,6 +26,23 @@ async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
async function readArtifactMarkdown(projectRoot, artifactFile) {
|
|
30
|
+
const candidates = [
|
|
31
|
+
path.join(projectRoot, RUNTIME_ROOT, "artifacts", artifactFile),
|
|
32
|
+
path.join(projectRoot, artifactFile)
|
|
33
|
+
];
|
|
34
|
+
for (const candidate of candidates) {
|
|
35
|
+
if (!(await exists(candidate)))
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
return await fs.readFile(candidate, "utf8");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Try next location.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
29
46
|
function unique(values) {
|
|
30
47
|
return [...new Set(values)];
|
|
31
48
|
}
|
|
@@ -36,6 +53,13 @@ function sameStringArray(a, b) {
|
|
|
36
53
|
}
|
|
37
54
|
const RECONCILIATION_NOTICES_FILE = "reconciliation-notices.json";
|
|
38
55
|
const RECONCILIATION_NOTICES_SCHEMA_VERSION = 1;
|
|
56
|
+
const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
|
|
57
|
+
"Stack Analysis",
|
|
58
|
+
"Features & Patterns",
|
|
59
|
+
"Architecture Options",
|
|
60
|
+
"Pitfalls & Risks",
|
|
61
|
+
"Synthesis"
|
|
62
|
+
];
|
|
39
63
|
export const RECONCILIATION_NOTICES_REL_PATH = `${RUNTIME_ROOT}/state/${RECONCILIATION_NOTICES_FILE}`;
|
|
40
64
|
function isFlowStageValue(value) {
|
|
41
65
|
return typeof value === "string" && FLOW_STAGES.includes(value);
|
|
@@ -222,6 +246,37 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
222
246
|
}
|
|
223
247
|
}
|
|
224
248
|
}
|
|
249
|
+
if (stage === "design") {
|
|
250
|
+
const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
|
|
251
|
+
if (researchGateRequired) {
|
|
252
|
+
const researchMarkdown = await readArtifactMarkdown(projectRoot, "02a-research.md");
|
|
253
|
+
if (!researchMarkdown) {
|
|
254
|
+
issues.push("design research gate blocked (design_research_complete): missing `.cclaw/artifacts/02a-research.md`.");
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const missingSections = [];
|
|
258
|
+
for (const section of DESIGN_RESEARCH_REQUIRED_SECTIONS) {
|
|
259
|
+
const body = extractMarkdownSectionBody(researchMarkdown, section);
|
|
260
|
+
if (body === null) {
|
|
261
|
+
missingSections.push(section);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const meaningfulLines = body
|
|
265
|
+
.split(/\r?\n/gu)
|
|
266
|
+
.map((line) => line.trim())
|
|
267
|
+
.filter((line) => line.length > 0)
|
|
268
|
+
.filter((line) => !/^\|?(?:[-:\s|])+$/u.test(line));
|
|
269
|
+
const nonPlaceholder = meaningfulLines.filter((line) => !/\b(?:TODO|TBD|FIXME|pending|<fill-in>)\b/iu.test(line));
|
|
270
|
+
if (nonPlaceholder.length === 0) {
|
|
271
|
+
missingSections.push(`${section} (empty or placeholder)`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (missingSections.length > 0) {
|
|
275
|
+
issues.push(`design research gate blocked (design_research_complete): ${missingSections.join(", ")}.`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
225
280
|
}
|
|
226
281
|
const passedSet = new Set(catalog.passed);
|
|
227
282
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
package/dist/install.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export declare function syncCclaw(projectRoot: string): Promise<void>;
|
|
|
12
12
|
* stamps are rewritten so the on-disk config reflects the installed CLI.
|
|
13
13
|
*
|
|
14
14
|
* Shape preservation: if the user previously hand-authored advanced keys
|
|
15
|
-
* (e.g. `
|
|
16
|
-
* yaml. If their existing config is minimal, the upgrade keeps it
|
|
17
|
-
* advanced knobs are never silently added.
|
|
15
|
+
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
16
|
+
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
17
|
+
* minimal — advanced knobs are never silently added.
|
|
18
18
|
*/
|
|
19
19
|
export declare function upgradeCclaw(projectRoot: string): Promise<void>;
|
|
20
20
|
export declare function uninstallCclaw(projectRoot: string): Promise<void>;
|
package/dist/install.js
CHANGED
|
@@ -245,7 +245,9 @@ async function writeSkills(projectRoot, config) {
|
|
|
245
245
|
await writeFileSafe(runtimePath(projectRoot, "skills", "using-git-worktrees", "SKILL.md"), featureCommandSkillMarkdown());
|
|
246
246
|
await writeFileSafe(runtimePath(projectRoot, "skills", "tdd-cycle-log", "SKILL.md"), tddLogCommandSkillMarkdown());
|
|
247
247
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-retro", "SKILL.md"), retroCommandSkillMarkdown());
|
|
248
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-compound", "SKILL.md"), compoundCommandSkillMarkdown(
|
|
248
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-compound", "SKILL.md"), compoundCommandSkillMarkdown({
|
|
249
|
+
recurrenceThreshold: config?.compound?.recurrenceThreshold
|
|
250
|
+
}));
|
|
249
251
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-rewind", "SKILL.md"), rewindCommandSkillMarkdown());
|
|
250
252
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-archive", "SKILL.md"), archiveCommandSkillMarkdown());
|
|
251
253
|
await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
|
|
@@ -306,7 +308,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
306
308
|
await writeFileSafe(runtimePath(projectRoot, ...playbookDirSegments, harnessPlaybookFileName(harness)), harnessPlaybookMarkdown(harness));
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
|
-
async function writeUtilityCommands(projectRoot) {
|
|
311
|
+
async function writeUtilityCommands(projectRoot, config) {
|
|
310
312
|
await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
|
|
311
313
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
312
314
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
@@ -319,7 +321,9 @@ async function writeUtilityCommands(projectRoot) {
|
|
|
319
321
|
await writeFileSafe(runtimePath(projectRoot, "commands", "feature.md"), featureCommandContract());
|
|
320
322
|
await writeFileSafe(runtimePath(projectRoot, "commands", "tdd-log.md"), tddLogCommandContract());
|
|
321
323
|
await writeFileSafe(runtimePath(projectRoot, "commands", "retro.md"), retroCommandContract());
|
|
322
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "compound.md"), compoundCommandContract(
|
|
324
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "compound.md"), compoundCommandContract({
|
|
325
|
+
recurrenceThreshold: config.compound?.recurrenceThreshold
|
|
326
|
+
}));
|
|
323
327
|
await writeFileSafe(runtimePath(projectRoot, "commands", "archive.md"), archiveCommandContract());
|
|
324
328
|
await writeFileSafe(runtimePath(projectRoot, "commands", "rewind.md"), rewindCommandContract());
|
|
325
329
|
}
|
|
@@ -627,7 +631,8 @@ async function writeHooks(projectRoot, config) {
|
|
|
627
631
|
await writeFileSafe(path.join(hooksDir, "workflow-guard.sh"), workflowGuardScript({
|
|
628
632
|
workflowGuardMode: config.strictness ?? "advisory",
|
|
629
633
|
tddEnforcementMode: config.tddEnforcement ?? "advisory",
|
|
630
|
-
|
|
634
|
+
tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
|
|
635
|
+
tddProductionPathPatterns: config.tdd?.productionPathPatterns
|
|
631
636
|
}));
|
|
632
637
|
await writeFileSafe(path.join(hooksDir, "context-monitor.sh"), contextMonitorScript());
|
|
633
638
|
const opencodePluginSource = opencodePluginJs();
|
|
@@ -1146,7 +1151,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
|
|
|
1146
1151
|
await cleanLegacyArtifacts(projectRoot);
|
|
1147
1152
|
await cleanStaleFiles(projectRoot);
|
|
1148
1153
|
await writeCommandContracts(projectRoot);
|
|
1149
|
-
await writeUtilityCommands(projectRoot);
|
|
1154
|
+
await writeUtilityCommands(projectRoot, config);
|
|
1150
1155
|
await writeSkills(projectRoot, config);
|
|
1151
1156
|
await writeContextModes(projectRoot);
|
|
1152
1157
|
await writeArtifactTemplates(projectRoot);
|
|
@@ -1195,9 +1200,9 @@ export async function syncCclaw(projectRoot) {
|
|
|
1195
1200
|
* stamps are rewritten so the on-disk config reflects the installed CLI.
|
|
1196
1201
|
*
|
|
1197
1202
|
* Shape preservation: if the user previously hand-authored advanced keys
|
|
1198
|
-
* (e.g. `
|
|
1199
|
-
* yaml. If their existing config is minimal, the upgrade keeps it
|
|
1200
|
-
* advanced knobs are never silently added.
|
|
1203
|
+
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
1204
|
+
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
1205
|
+
* minimal — advanced knobs are never silently added.
|
|
1201
1206
|
*/
|
|
1202
1207
|
export async function upgradeCclaw(projectRoot) {
|
|
1203
1208
|
const advancedKeysPresent = await detectAdvancedKeys(projectRoot);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type FlowStage } from "./types.js";
|
|
2
2
|
export type KnowledgeEntryType = "rule" | "pattern" | "lesson" | "compound";
|
|
3
3
|
export type KnowledgeEntryConfidence = "high" | "medium" | "low";
|
|
4
|
+
export type KnowledgeEntrySeverity = "critical" | "important" | "suggestion";
|
|
4
5
|
export type KnowledgeEntryUniversality = "project" | "personal" | "universal";
|
|
5
6
|
export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
|
|
6
7
|
export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "ideate" | "manual";
|
|
@@ -9,6 +10,7 @@ export interface KnowledgeEntry {
|
|
|
9
10
|
trigger: string;
|
|
10
11
|
action: string;
|
|
11
12
|
confidence: KnowledgeEntryConfidence;
|
|
13
|
+
severity?: KnowledgeEntrySeverity;
|
|
12
14
|
domain: string | null;
|
|
13
15
|
stage: FlowStage | null;
|
|
14
16
|
origin_stage: FlowStage | null;
|
|
@@ -27,6 +29,7 @@ export interface KnowledgeSeedEntry {
|
|
|
27
29
|
trigger: string;
|
|
28
30
|
action: string;
|
|
29
31
|
confidence: KnowledgeEntryConfidence;
|
|
32
|
+
severity?: KnowledgeEntrySeverity;
|
|
30
33
|
domain?: string | null;
|
|
31
34
|
stage?: FlowStage | null;
|
|
32
35
|
origin_stage?: FlowStage | null;
|