@dzhechkov/p-replicator 1.13.1 → 1.13.3
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/.dz-manifest.json +93 -25
- package/README.md +19 -1
- package/package.json +5 -4
- package/sbom.json +194 -24
- package/scripts/check-pipeline-gaps.sh +510 -21
- package/src/utils.js +2 -0
- package/templates/.claude/commands/feature.md +43 -1
- package/templates/.claude/commands/replicate.md +9 -1
- package/templates/.claude/hooks/check-dangling-refs.cjs +89 -0
- package/templates/.claude/hooks/check-docs-complete.cjs +7 -0
- package/templates/.claude/hooks/check-review-contract.cjs +205 -0
- package/templates/.claude/hooks/statusline.cjs +1 -1
- package/templates/.claude/rules/cost-of-detection-ladder.md +37 -4
- package/templates/.claude/rules/docker-ports.md +28 -0
- package/templates/.claude/rules/feature-lifecycle.md +21 -0
- package/templates/.claude/rules/replicate-pipeline.md +5 -4
- package/templates/.claude/skills/brutal-honesty-review/resources/assessment-rubrics.md +12 -2
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/security-patterns-library.md +45 -0
- package/templates/.claude/skills/requirements-validator/SKILL.md +18 -5
- package/templates/.claude/skills/requirements-validator/references/feature-report-contracts.md +67 -0
- package/templates/.claude/skills/requirements-validator/references/scoring-system.md +15 -6
- package/tests/e2e/feature-contour.test.js +176 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/01_specification.md +15 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/02_pseudocode.md +19 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/03_architecture.md +3 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/04_refinement.md +3 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/05_completion.md +7 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/review-report.md +11 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/validation-report.md +10 -0
- package/tests/fixtures/feature-contour/tests/demo.test.js +16 -0
- package/tests/snapshot/baseline.json +17 -14
- package/tests/unit/capture-source-path.test.js +73 -24
- package/tests/unit/check-dangling-refs.test.js +91 -0
- package/tests/unit/check-review-contract.test.js +181 -0
- package/tests/unit/guard-honest-input-meta.test.js +49 -0
- package/tests/unit/honest-failure-rules.test.js +40 -3
- package/tests/unit/negative-conclusion-gate.test.js +3 -3
- package/tests/unit/optional-doc-idiom.test.js +83 -0
- package/tests/unit/quote-provenance.test.js +4 -0
- package/tests/unit/sync-templates-guard.test.js +46 -3
- package/tests/unit/traceability-completion-gate.test.js +267 -0
- package/tests/unit/traceability-negative-fixture.test.js +19 -5
- package/tests/unit/verdict-vocabulary.test.js +72 -0
|
@@ -6,7 +6,8 @@ export LC_ALL=C
|
|
|
6
6
|
|
|
7
7
|
usage() {
|
|
8
8
|
cat >&2 <<'USAGE'
|
|
9
|
-
Usage: check-pipeline-gaps.sh PROJECT_ROOT [--traceability]
|
|
9
|
+
Usage: check-pipeline-gaps.sh PROJECT_ROOT [--traceability] [--completion]
|
|
10
|
+
[--report-revision] [--criterion-scenarios]
|
|
10
11
|
[--role-map-source PATH] [--project-role-map-source PATH]
|
|
11
12
|
|
|
12
13
|
Exit 0: all checked ID sets are equal
|
|
@@ -296,10 +297,358 @@ process_contour() {
|
|
|
296
297
|
fi
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
mode_not_established() {
|
|
301
|
+
local mode="$1"
|
|
302
|
+
shift
|
|
303
|
+
printf 'NOT-ESTABLISHED %s\n' "$*"
|
|
304
|
+
case "$mode" in
|
|
305
|
+
completion) COMPLETION_INCONCLUSIVE_COUNT=$((COMPLETION_INCONCLUSIVE_COUNT + 1)) ;;
|
|
306
|
+
report-revision) REVISION_INCONCLUSIVE_COUNT=$((REVISION_INCONCLUSIVE_COUNT + 1)) ;;
|
|
307
|
+
criterion-scenarios) SCENARIO_INCONCLUSIVE_COUNT=$((SCENARIO_INCONCLUSIVE_COUNT + 1)) ;;
|
|
308
|
+
esac
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
mode_gap() {
|
|
312
|
+
local mode="$1"
|
|
313
|
+
shift
|
|
314
|
+
printf '%s\n' "$*"
|
|
315
|
+
case "$mode" in
|
|
316
|
+
completion) COMPLETION_GAP_COUNT=$((COMPLETION_GAP_COUNT + 1)) ;;
|
|
317
|
+
report-revision) REVISION_GAP_COUNT=$((REVISION_GAP_COUNT + 1)) ;;
|
|
318
|
+
criterion-scenarios) SCENARIO_GAP_COUNT=$((SCENARIO_GAP_COUNT + 1)) ;;
|
|
319
|
+
esac
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
safe_mode_role_file() {
|
|
323
|
+
local mode="$1" contour="$2" role="$3" directory="$4" target="$5"
|
|
324
|
+
local file="$directory/$target" current="$directory" component
|
|
325
|
+
local -a target_components
|
|
326
|
+
IFS='/' read -r -a target_components <<< "$target"
|
|
327
|
+
for component in "${target_components[@]}"; do
|
|
328
|
+
current="$current/$component"
|
|
329
|
+
if [ -L "$current" ]; then
|
|
330
|
+
mode_not_established "$mode" "contour=$contour role=$role path=$current symlink is not allowed"
|
|
331
|
+
return 1
|
|
332
|
+
fi
|
|
333
|
+
done
|
|
334
|
+
if [ ! -f "$file" ] || [ ! -r "$file" ]; then
|
|
335
|
+
mode_not_established "$mode" "contour=$contour role=$role path=$file missing or unreadable"
|
|
336
|
+
return 1
|
|
337
|
+
fi
|
|
338
|
+
return 0
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
prepare_ac_ids() {
|
|
342
|
+
local mode="$1" contour="$2" spec_file="$3" stem="$4" output="$5"
|
|
343
|
+
local records="$stem.spec.records" raw="$stem.spec.ac.raw"
|
|
344
|
+
local kind first rest duplicate
|
|
345
|
+
if ! extract_specification "$spec_file" "$records"; then
|
|
346
|
+
mode_not_established "$mode" "contour=$contour specification extractor failed path=$spec_file"
|
|
347
|
+
return 1
|
|
348
|
+
fi
|
|
349
|
+
: > "$raw"
|
|
350
|
+
while IFS=$'\t' read -r kind first rest; do
|
|
351
|
+
[ -z "$kind" ] && continue
|
|
352
|
+
case "$kind" in
|
|
353
|
+
ID)
|
|
354
|
+
case "$first" in AC-*) printf '%s\n' "$first" >> "$raw" ;; esac
|
|
355
|
+
;;
|
|
356
|
+
MALFORMED)
|
|
357
|
+
case "$rest" in
|
|
358
|
+
AC-*) mode_gap "$mode" "MALFORMED contour=$contour $mode specification line=$first value=$rest" ;;
|
|
359
|
+
esac
|
|
360
|
+
;;
|
|
361
|
+
esac
|
|
362
|
+
done < "$records"
|
|
363
|
+
if [ -s "$raw" ]; then
|
|
364
|
+
while IFS= read -r duplicate; do
|
|
365
|
+
[ -n "$duplicate" ] && mode_gap "$mode" "DUPLICATE contour=$contour $mode specification $duplicate"
|
|
366
|
+
done < <(sort "$raw" | uniq -d)
|
|
367
|
+
fi
|
|
368
|
+
sort -u "$raw" > "$output"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
extract_completion_rows() {
|
|
372
|
+
local source="$1" output="$2"
|
|
373
|
+
awk '
|
|
374
|
+
function trim(value) {
|
|
375
|
+
sub(/^[[:space:]]*/, "", value)
|
|
376
|
+
sub(/[[:space:]]*$/, "", value)
|
|
377
|
+
return value
|
|
378
|
+
}
|
|
379
|
+
BEGIN { section = 0; header = 0; separator = 0 }
|
|
380
|
+
{ sub(/\r$/, "") }
|
|
381
|
+
$0 == "## Criterion coverage" { section = 1; next }
|
|
382
|
+
section && /^##[[:space:]]/ { exit }
|
|
383
|
+
section && trim($0) == "| Criterion | Test file | Test title |" {
|
|
384
|
+
header = 1
|
|
385
|
+
next
|
|
386
|
+
}
|
|
387
|
+
header && !separator {
|
|
388
|
+
if (trim($0) ~ /^\|[[:space:]]*-+[[:space:]]*\|[[:space:]]*-+[[:space:]]*\|[[:space:]]*-+[[:space:]]*\|$/) {
|
|
389
|
+
separator = 1
|
|
390
|
+
next
|
|
391
|
+
}
|
|
392
|
+
print "ERROR\tseparator"
|
|
393
|
+
exit
|
|
394
|
+
}
|
|
395
|
+
separator {
|
|
396
|
+
if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^##?[[:space:]]/ || $0 !~ /^[[:space:]]*\|/) exit
|
|
397
|
+
count = split($0, cell, "|")
|
|
398
|
+
if (count != 5) { print "ERROR\trow\t" NR; next }
|
|
399
|
+
print "ROW\t" trim(cell[2]) "\t" trim(cell[3]) "\t" trim(cell[4])
|
|
400
|
+
}
|
|
401
|
+
END {
|
|
402
|
+
if (!section) print "ERROR\tsection"
|
|
403
|
+
else if (!header) print "ERROR\theader"
|
|
404
|
+
else if (!separator) print "ERROR\tseparator"
|
|
405
|
+
}
|
|
406
|
+
' "$source" > "$output"
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
extract_scenario_rows() {
|
|
410
|
+
local source="$1" output="$2"
|
|
411
|
+
awk '
|
|
412
|
+
function trim(value) {
|
|
413
|
+
sub(/^[[:space:]]*/, "", value)
|
|
414
|
+
sub(/[[:space:]]*$/, "", value)
|
|
415
|
+
return value
|
|
416
|
+
}
|
|
417
|
+
BEGIN { section = 0; header = 0; separator = 0 }
|
|
418
|
+
{ sub(/\r$/, "") }
|
|
419
|
+
$0 == "## Criterion scenarios" { section = 1; next }
|
|
420
|
+
section && /^##[[:space:]]/ { exit }
|
|
421
|
+
section && trim($0) == "| Criterion | Scenario |" { header = 1; next }
|
|
422
|
+
header && !separator {
|
|
423
|
+
if (trim($0) ~ /^\|[[:space:]]*-+[[:space:]]*\|[[:space:]]*-+[[:space:]]*\|$/) {
|
|
424
|
+
separator = 1
|
|
425
|
+
next
|
|
426
|
+
}
|
|
427
|
+
print "ERROR\tseparator"
|
|
428
|
+
exit
|
|
429
|
+
}
|
|
430
|
+
separator {
|
|
431
|
+
if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^##?[[:space:]]/ || $0 !~ /^[[:space:]]*\|/) exit
|
|
432
|
+
count = split($0, cell, "|")
|
|
433
|
+
if (count != 4) { print "ERROR\trow\t" NR; next }
|
|
434
|
+
print "ROW\t" trim(cell[2]) "\t" trim(cell[3])
|
|
435
|
+
}
|
|
436
|
+
END {
|
|
437
|
+
if (!section) print "ERROR\tsection"
|
|
438
|
+
else if (!header) print "ERROR\theader"
|
|
439
|
+
else if (!separator) print "ERROR\tseparator"
|
|
440
|
+
}
|
|
441
|
+
' "$source" > "$output"
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
coverage_test_file() {
|
|
445
|
+
local contour="$1" id="$2" relative="$3" title="$4"
|
|
446
|
+
local candidate="$PROJECT_ROOT/$relative" current="$PROJECT_ROOT" component grep_status
|
|
447
|
+
local -a components
|
|
448
|
+
if [ -z "$relative" ]; then
|
|
449
|
+
mode_gap completion "GAP contour=$contour completion $id test file is empty"
|
|
450
|
+
return
|
|
451
|
+
fi
|
|
452
|
+
case "$relative" in
|
|
453
|
+
/*|../*|*/../*|*/..|..)
|
|
454
|
+
mode_gap completion "GAP contour=$contour completion $id test file $relative escapes the project root"
|
|
455
|
+
return
|
|
456
|
+
;;
|
|
457
|
+
esac
|
|
458
|
+
IFS='/' read -r -a components <<< "$relative"
|
|
459
|
+
for component in "${components[@]}"; do
|
|
460
|
+
current="$current/$component"
|
|
461
|
+
if [ -L "$current" ]; then
|
|
462
|
+
mode_gap completion "GAP contour=$contour completion $id test file $relative uses a symlink"
|
|
463
|
+
return
|
|
464
|
+
fi
|
|
465
|
+
done
|
|
466
|
+
if [ ! -e "$candidate" ]; then
|
|
467
|
+
mode_gap completion "GAP contour=$contour completion $id test file $relative does not exist"
|
|
468
|
+
return
|
|
469
|
+
fi
|
|
470
|
+
if [ ! -f "$candidate" ]; then
|
|
471
|
+
mode_gap completion "GAP contour=$contour completion $id test file $relative is not a regular file"
|
|
472
|
+
return
|
|
473
|
+
fi
|
|
474
|
+
if [ -z "$title" ]; then
|
|
475
|
+
mode_gap completion "GAP contour=$contour completion $id test title is empty"
|
|
476
|
+
return
|
|
477
|
+
fi
|
|
478
|
+
grep -Fq -- "$title" "$candidate"
|
|
479
|
+
grep_status=$?
|
|
480
|
+
if [ "$grep_status" -eq 1 ]; then
|
|
481
|
+
mode_gap completion "GAP contour=$contour completion $id test file $relative does not contain title \"$title\""
|
|
482
|
+
elif [ "$grep_status" -ne 0 ]; then
|
|
483
|
+
mode_not_established completion "contour=$contour completion $id test file $relative could not be read"
|
|
484
|
+
fi
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
process_completion_contour() {
|
|
488
|
+
local contour="$1" directory="$2" spec_target="$3" completion_target="$4"
|
|
489
|
+
local spec_file="$directory/$spec_target" completion_file="$directory/$completion_target"
|
|
490
|
+
local stem spec_ids records row_ids missing orphan duplicates kind id test_file title detail
|
|
491
|
+
if [ -L "$directory" ]; then
|
|
492
|
+
mode_not_established completion "contour=$contour path=$directory symlink directory is not allowed"
|
|
493
|
+
return
|
|
494
|
+
fi
|
|
495
|
+
if ! safe_mode_role_file completion "$contour" specification "$directory" "$spec_target"; then return; fi
|
|
496
|
+
if ! safe_mode_role_file completion "$contour" completion "$directory" "$completion_target"; then return; fi
|
|
497
|
+
MODE_CONTOUR_SEQ=$((MODE_CONTOUR_SEQ + 1))
|
|
498
|
+
stem="$TEMP_DIR/mode-contour-$MODE_CONTOUR_SEQ"
|
|
499
|
+
spec_ids="$stem.spec.ids"
|
|
500
|
+
records="$stem.completion.records"
|
|
501
|
+
row_ids="$stem.completion.ids"
|
|
502
|
+
missing="$stem.completion.missing"
|
|
503
|
+
orphan="$stem.completion.orphan"
|
|
504
|
+
duplicates="$stem.completion.duplicates"
|
|
505
|
+
if ! prepare_ac_ids completion "$contour" "$spec_file" "$stem" "$spec_ids"; then return; fi
|
|
506
|
+
if ! extract_completion_rows "$completion_file" "$records"; then
|
|
507
|
+
mode_not_established completion "contour=$contour completion table could not be read from $completion_target"
|
|
508
|
+
return
|
|
509
|
+
fi
|
|
510
|
+
if awk -F '\t' '$1 == "ERROR" { found = 1 } END { exit !found }' "$records"; then
|
|
511
|
+
detail="$(awk -F '\t' '$1 == "ERROR" { print $2; exit }' "$records")"
|
|
512
|
+
mode_not_established completion "contour=$contour completion Criterion coverage table missing or malformed detail=$detail"
|
|
513
|
+
return
|
|
514
|
+
fi
|
|
515
|
+
: > "$row_ids"
|
|
516
|
+
while IFS=$'\t' read -r kind id test_file title; do
|
|
517
|
+
[ "$kind" = ROW ] || continue
|
|
518
|
+
printf '%s\n' "$id" >> "$row_ids"
|
|
519
|
+
coverage_test_file "$contour" "$id" "$test_file" "$title"
|
|
520
|
+
done < "$records"
|
|
521
|
+
sort "$row_ids" | uniq -d > "$duplicates"
|
|
522
|
+
while IFS= read -r id; do
|
|
523
|
+
[ -n "$id" ] && mode_gap completion "GAP contour=$contour completion $id has duplicate rows in Criterion coverage"
|
|
524
|
+
done < "$duplicates"
|
|
525
|
+
sort -u "$row_ids" > "$stem.completion.unique"
|
|
526
|
+
comm -23 "$spec_ids" "$stem.completion.unique" > "$missing"
|
|
527
|
+
comm -23 "$stem.completion.unique" "$spec_ids" > "$orphan"
|
|
528
|
+
while IFS= read -r id; do
|
|
529
|
+
[ -n "$id" ] && mode_gap completion "GAP contour=$contour completion $id has no row in Criterion coverage"
|
|
530
|
+
done < "$missing"
|
|
531
|
+
while IFS= read -r id; do
|
|
532
|
+
[ -n "$id" ] && mode_gap completion "GAP contour=$contour completion row $id is not declared in the specification"
|
|
533
|
+
done < "$orphan"
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
process_revision_contour() {
|
|
537
|
+
local contour="$1" directory="$2" spec_target="$3"
|
|
538
|
+
local spec_file="$directory/$spec_target" report_file="$directory/validation-report.md"
|
|
539
|
+
local lines declared actual count
|
|
540
|
+
if [ -L "$directory" ]; then
|
|
541
|
+
mode_not_established report-revision "contour=$contour path=$directory symlink directory is not allowed"
|
|
542
|
+
return
|
|
543
|
+
fi
|
|
544
|
+
if ! safe_mode_role_file report-revision "$contour" specification "$directory" "$spec_target"; then return; fi
|
|
545
|
+
if ! safe_mode_role_file report-revision "$contour" validation-report "$directory" validation-report.md; then return; fi
|
|
546
|
+
lines="$(awk 'NR <= 20 { sub(/\r$/, "") } NR <= 20 && /^Spec revision:/ { print }' "$report_file")" || {
|
|
547
|
+
mode_not_established report-revision "contour=$contour report-revision validation-report.md could not be read"
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
count="$(printf '%s\n' "$lines" | awk 'NF { count++ } END { print count + 0 }')"
|
|
551
|
+
if [ "$count" -eq 0 ]; then
|
|
552
|
+
mode_not_established report-revision "contour=$contour report-revision line missing in validation-report.md"
|
|
553
|
+
return
|
|
554
|
+
fi
|
|
555
|
+
if [ "$count" -ne 1 ] || [[ ! "$lines" =~ ^Spec[[:space:]]revision:[[:space:]]sha256:([a-f0-9]{64})$ ]]; then
|
|
556
|
+
mode_not_established report-revision "contour=$contour report-revision line malformed or repeated in validation-report.md"
|
|
557
|
+
return
|
|
558
|
+
fi
|
|
559
|
+
declared="${BASH_REMATCH[1]}"
|
|
560
|
+
actual="$(sha256sum -- "$spec_file")" || {
|
|
561
|
+
mode_not_established report-revision "contour=$contour report-revision specification digest could not be established"
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
actual="${actual%% *}"
|
|
565
|
+
if [ "$declared" != "$actual" ]; then
|
|
566
|
+
mode_gap report-revision "GAP contour=$contour report-revision validation-report.md sha256:${declared:0:12}… != specification sha256:${actual:0:12}…"
|
|
567
|
+
fi
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
process_scenario_contour() {
|
|
571
|
+
local contour="$1" directory="$2" spec_target="$3"
|
|
572
|
+
local spec_file="$directory/$spec_target" report_file="$directory/validation-report.md"
|
|
573
|
+
local stem spec_ids records row_ids missing orphan duplicates kind id scenario detail
|
|
574
|
+
if [ -L "$directory" ]; then
|
|
575
|
+
mode_not_established criterion-scenarios "contour=$contour path=$directory symlink directory is not allowed"
|
|
576
|
+
return
|
|
577
|
+
fi
|
|
578
|
+
if ! safe_mode_role_file criterion-scenarios "$contour" specification "$directory" "$spec_target"; then return; fi
|
|
579
|
+
if ! safe_mode_role_file criterion-scenarios "$contour" validation-report "$directory" validation-report.md; then return; fi
|
|
580
|
+
MODE_CONTOUR_SEQ=$((MODE_CONTOUR_SEQ + 1))
|
|
581
|
+
stem="$TEMP_DIR/mode-contour-$MODE_CONTOUR_SEQ"
|
|
582
|
+
spec_ids="$stem.spec.ids"
|
|
583
|
+
records="$stem.scenario.records"
|
|
584
|
+
row_ids="$stem.scenario.ids"
|
|
585
|
+
missing="$stem.scenario.missing"
|
|
586
|
+
orphan="$stem.scenario.orphan"
|
|
587
|
+
duplicates="$stem.scenario.duplicates"
|
|
588
|
+
if ! prepare_ac_ids criterion-scenarios "$contour" "$spec_file" "$stem" "$spec_ids"; then return; fi
|
|
589
|
+
if ! extract_scenario_rows "$report_file" "$records"; then
|
|
590
|
+
mode_not_established criterion-scenarios "contour=$contour criterion-scenarios table could not be read from validation-report.md"
|
|
591
|
+
return
|
|
592
|
+
fi
|
|
593
|
+
if awk -F '\t' '$1 == "ERROR" { found = 1 } END { exit !found }' "$records"; then
|
|
594
|
+
detail="$(awk -F '\t' '$1 == "ERROR" { print $2; exit }' "$records")"
|
|
595
|
+
mode_not_established criterion-scenarios "contour=$contour criterion-scenarios table missing or malformed in validation-report.md detail=$detail"
|
|
596
|
+
return
|
|
597
|
+
fi
|
|
598
|
+
: > "$row_ids"
|
|
599
|
+
while IFS=$'\t' read -r kind id scenario; do
|
|
600
|
+
[ "$kind" = ROW ] || continue
|
|
601
|
+
printf '%s\n' "$id" >> "$row_ids"
|
|
602
|
+
if [ -z "$scenario" ]; then
|
|
603
|
+
mode_gap criterion-scenarios "GAP contour=$contour criterion-scenarios $id scenario is empty"
|
|
604
|
+
fi
|
|
605
|
+
done < "$records"
|
|
606
|
+
sort "$row_ids" | uniq -d > "$duplicates"
|
|
607
|
+
while IFS= read -r id; do
|
|
608
|
+
[ -n "$id" ] && mode_gap criterion-scenarios "GAP contour=$contour criterion-scenarios $id has duplicate scenario rows"
|
|
609
|
+
done < "$duplicates"
|
|
610
|
+
sort -u "$row_ids" > "$stem.scenario.unique"
|
|
611
|
+
comm -23 "$spec_ids" "$stem.scenario.unique" > "$missing"
|
|
612
|
+
comm -23 "$stem.scenario.unique" "$spec_ids" > "$orphan"
|
|
613
|
+
while IFS= read -r id; do
|
|
614
|
+
[ -n "$id" ] && mode_gap criterion-scenarios "GAP contour=$contour criterion-scenarios $id has no scenario row"
|
|
615
|
+
done < "$missing"
|
|
616
|
+
while IFS= read -r id; do
|
|
617
|
+
[ -n "$id" ] && mode_gap criterion-scenarios "GAP contour=$contour criterion-scenarios row $id is not declared in the specification"
|
|
618
|
+
done < "$orphan"
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
emit_mode_verdict() {
|
|
622
|
+
local mode="$1" gaps="$2" inconclusive="$3"
|
|
623
|
+
if [ "$inconclusive" -gt 0 ]; then
|
|
624
|
+
printf 'VERDICT %s=NOT-ESTABLISHED features=%s gaps=%s inconclusive=%s\n' \
|
|
625
|
+
"$mode" "$FEATURE_COUNT" "$gaps" "$inconclusive"
|
|
626
|
+
return 2
|
|
627
|
+
fi
|
|
628
|
+
if [ "$gaps" -gt 0 ]; then
|
|
629
|
+
printf 'VERDICT %s=FAIL features=%s gaps=%s inconclusive=0\n' "$mode" "$FEATURE_COUNT" "$gaps"
|
|
630
|
+
return 1
|
|
631
|
+
fi
|
|
632
|
+
printf 'VERDICT %s=PASS features=%s gaps=0 inconclusive=0\n' "$mode" "$FEATURE_COUNT"
|
|
633
|
+
return 0
|
|
634
|
+
}
|
|
635
|
+
|
|
299
636
|
GAP_COUNT=0
|
|
300
637
|
INCONCLUSIVE_COUNT=0
|
|
301
638
|
FEATURE_COUNT=0
|
|
302
639
|
CONTOUR_SEQ=0
|
|
640
|
+
MODE_CONTOUR_SEQ=0
|
|
641
|
+
COMPLETION_GAP_COUNT=0
|
|
642
|
+
COMPLETION_INCONCLUSIVE_COUNT=0
|
|
643
|
+
REVISION_GAP_COUNT=0
|
|
644
|
+
REVISION_INCONCLUSIVE_COUNT=0
|
|
645
|
+
SCENARIO_GAP_COUNT=0
|
|
646
|
+
SCENARIO_INCONCLUSIVE_COUNT=0
|
|
647
|
+
TRACEABILITY_MODE=0
|
|
648
|
+
COMPLETION_MODE=0
|
|
649
|
+
REPORT_REVISION_MODE=0
|
|
650
|
+
CRITERION_SCENARIOS_MODE=0
|
|
651
|
+
EXPLICIT_MODE_COUNT=0
|
|
303
652
|
|
|
304
653
|
PROJECT_ROOT="${1:-}"
|
|
305
654
|
if [ -z "$PROJECT_ROOT" ]; then
|
|
@@ -312,7 +661,10 @@ FEATURE_ROLE_MAP_SOURCE=""
|
|
|
312
661
|
PROJECT_ROLE_MAP_SOURCE=""
|
|
313
662
|
while [ "$#" -gt 0 ]; do
|
|
314
663
|
case "$1" in
|
|
315
|
-
--traceability) shift ;;
|
|
664
|
+
--traceability) TRACEABILITY_MODE=1; EXPLICIT_MODE_COUNT=$((EXPLICIT_MODE_COUNT + 1)); shift ;;
|
|
665
|
+
--completion) COMPLETION_MODE=1; EXPLICIT_MODE_COUNT=$((EXPLICIT_MODE_COUNT + 1)); shift ;;
|
|
666
|
+
--report-revision) REPORT_REVISION_MODE=1; EXPLICIT_MODE_COUNT=$((EXPLICIT_MODE_COUNT + 1)); shift ;;
|
|
667
|
+
--criterion-scenarios) CRITERION_SCENARIOS_MODE=1; EXPLICIT_MODE_COUNT=$((EXPLICIT_MODE_COUNT + 1)); shift ;;
|
|
316
668
|
--role-map-source)
|
|
317
669
|
[ "$#" -ge 2 ] || { usage; exit 2; }
|
|
318
670
|
FEATURE_ROLE_MAP_SOURCE="$2"; shift 2
|
|
@@ -325,6 +677,9 @@ while [ "$#" -gt 0 ]; do
|
|
|
325
677
|
esac
|
|
326
678
|
done
|
|
327
679
|
|
|
680
|
+
# Before modes were added the checker always ran traceability, even without an explicit flag.
|
|
681
|
+
if [ "$EXPLICIT_MODE_COUNT" -eq 0 ]; then TRACEABILITY_MODE=1; fi
|
|
682
|
+
|
|
328
683
|
if [ ! -d "$PROJECT_ROOT" ] || [ -L "$PROJECT_ROOT" ]; then
|
|
329
684
|
printf 'NOT-ESTABLISHED project-root=%s missing or a symlink\n' "$PROJECT_ROOT"
|
|
330
685
|
exit 2
|
|
@@ -336,6 +691,14 @@ for utility in awk sort uniq comm wc mktemp; do
|
|
|
336
691
|
exit 2
|
|
337
692
|
fi
|
|
338
693
|
done
|
|
694
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ] && ! command -v sha256sum >/dev/null 2>&1; then
|
|
695
|
+
printf 'NOT-ESTABLISHED required utility=sha256sum is unavailable\n'
|
|
696
|
+
exit 2
|
|
697
|
+
fi
|
|
698
|
+
if [ "$COMPLETION_MODE" -eq 1 ] && ! command -v grep >/dev/null 2>&1; then
|
|
699
|
+
printf 'NOT-ESTABLISHED required utility=grep is unavailable\n'
|
|
700
|
+
exit 2
|
|
701
|
+
fi
|
|
339
702
|
|
|
340
703
|
FEATURE_ROLE_MAP_SOURCE="${FEATURE_ROLE_MAP_SOURCE:-$PROJECT_ROOT/.claude/commands/feature.md}"
|
|
341
704
|
PROJECT_ROLE_MAP_SOURCE="${PROJECT_ROLE_MAP_SOURCE:-$PROJECT_ROOT/.claude/skills/sparc-prd-mini/SKILL.md}"
|
|
@@ -347,8 +710,22 @@ if ! parse_role_map "$PROJECT_ROLE_MAP_SOURCE" '### Project-level default' PROJE
|
|
|
347
710
|
INCONCLUSIVE_COUNT=$((INCONCLUSIVE_COUNT + 1))
|
|
348
711
|
fi
|
|
349
712
|
if [ "$INCONCLUSIVE_COUNT" -gt 0 ]; then
|
|
350
|
-
|
|
351
|
-
|
|
713
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
714
|
+
printf 'VERDICT traceability=NOT-ESTABLISHED features=0 gaps=0 inconclusive=%s\n' \
|
|
715
|
+
"$INCONCLUSIVE_COUNT"
|
|
716
|
+
fi
|
|
717
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
718
|
+
printf 'VERDICT completion=NOT-ESTABLISHED features=0 gaps=0 inconclusive=%s\n' \
|
|
719
|
+
"$INCONCLUSIVE_COUNT"
|
|
720
|
+
fi
|
|
721
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
722
|
+
printf 'VERDICT report-revision=NOT-ESTABLISHED features=0 gaps=0 inconclusive=%s\n' \
|
|
723
|
+
"$INCONCLUSIVE_COUNT"
|
|
724
|
+
fi
|
|
725
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
726
|
+
printf 'VERDICT criterion-scenarios=NOT-ESTABLISHED features=0 gaps=0 inconclusive=%s\n' \
|
|
727
|
+
"$INCONCLUSIVE_COUNT"
|
|
728
|
+
fi
|
|
352
729
|
exit 2
|
|
353
730
|
fi
|
|
354
731
|
|
|
@@ -364,50 +741,162 @@ trap 'if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ]; then rm -r -- "$TEMP_DIR"
|
|
|
364
741
|
|
|
365
742
|
DOCS_ROOT="$PROJECT_ROOT/docs"
|
|
366
743
|
CHECKED_CONTOURS=0
|
|
744
|
+
COMPLETION_CHECKED_CONTOURS=0
|
|
745
|
+
REVISION_CHECKED_CONTOURS=0
|
|
746
|
+
SCENARIO_CHECKED_CONTOURS=0
|
|
367
747
|
if [ ! -d "$DOCS_ROOT" ] || [ -L "$DOCS_ROOT" ]; then
|
|
368
|
-
|
|
748
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
749
|
+
not_established "docs-root=$DOCS_ROOT missing or a symlink"
|
|
750
|
+
fi
|
|
751
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
752
|
+
mode_not_established completion "docs-root=$DOCS_ROOT missing or a symlink"
|
|
753
|
+
fi
|
|
754
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
755
|
+
mode_not_established report-revision "docs-root=$DOCS_ROOT missing or a symlink"
|
|
756
|
+
fi
|
|
757
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
758
|
+
mode_not_established criterion-scenarios "docs-root=$DOCS_ROOT missing or a symlink"
|
|
759
|
+
fi
|
|
369
760
|
else
|
|
370
761
|
PROJECT_SPEC="$DOCS_ROOT/$PROJECT_SPECIFICATION"
|
|
371
762
|
PROJECT_PSEUDO="$DOCS_ROOT/$PROJECT_PSEUDOCODE"
|
|
372
|
-
|
|
373
|
-
|
|
763
|
+
PROJECT_COMPLETION="$DOCS_ROOT/$PROJECT_COMPLETION"
|
|
764
|
+
PROJECT_VALIDATION="$DOCS_ROOT/validation-report.md"
|
|
765
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ] && { [ -e "$PROJECT_SPEC" ] || [ -L "$PROJECT_SPEC" ] || \
|
|
766
|
+
[ -e "$PROJECT_PSEUDO" ] || [ -L "$PROJECT_PSEUDO" ]; }; then
|
|
374
767
|
process_contour project "$DOCS_ROOT" "$PROJECT_SPECIFICATION" "$PROJECT_PSEUDOCODE" ''
|
|
375
768
|
CHECKED_CONTOURS=$((CHECKED_CONTOURS + 1))
|
|
376
769
|
fi
|
|
770
|
+
if [ "$COMPLETION_MODE" -eq 1 ] && { [ -e "$PROJECT_SPEC" ] || [ -L "$PROJECT_SPEC" ] || \
|
|
771
|
+
[ -e "$PROJECT_COMPLETION" ] || [ -L "$PROJECT_COMPLETION" ]; }; then
|
|
772
|
+
process_completion_contour project "$DOCS_ROOT" "$PROJECT_SPECIFICATION" "$PROJECT_COMPLETION"
|
|
773
|
+
COMPLETION_CHECKED_CONTOURS=$((COMPLETION_CHECKED_CONTOURS + 1))
|
|
774
|
+
fi
|
|
775
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ] && { [ -e "$PROJECT_SPEC" ] || [ -L "$PROJECT_SPEC" ] || \
|
|
776
|
+
[ -e "$PROJECT_VALIDATION" ] || [ -L "$PROJECT_VALIDATION" ]; }; then
|
|
777
|
+
process_revision_contour project "$DOCS_ROOT" "$PROJECT_SPECIFICATION"
|
|
778
|
+
REVISION_CHECKED_CONTOURS=$((REVISION_CHECKED_CONTOURS + 1))
|
|
779
|
+
fi
|
|
780
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ] && { [ -e "$PROJECT_SPEC" ] || [ -L "$PROJECT_SPEC" ] || \
|
|
781
|
+
[ -e "$PROJECT_VALIDATION" ] || [ -L "$PROJECT_VALIDATION" ]; }; then
|
|
782
|
+
process_scenario_contour project "$DOCS_ROOT" "$PROJECT_SPECIFICATION"
|
|
783
|
+
SCENARIO_CHECKED_CONTOURS=$((SCENARIO_CHECKED_CONTOURS + 1))
|
|
784
|
+
fi
|
|
377
785
|
|
|
378
786
|
FEATURES_ROOT="$DOCS_ROOT/features"
|
|
379
787
|
if [ -L "$FEATURES_ROOT" ]; then
|
|
380
|
-
|
|
788
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
789
|
+
not_established "features-root=$FEATURES_ROOT symlink is not allowed"
|
|
790
|
+
fi
|
|
791
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
792
|
+
mode_not_established completion "features-root=$FEATURES_ROOT symlink is not allowed"
|
|
793
|
+
fi
|
|
794
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
795
|
+
mode_not_established report-revision "features-root=$FEATURES_ROOT symlink is not allowed"
|
|
796
|
+
fi
|
|
797
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
798
|
+
mode_not_established criterion-scenarios "features-root=$FEATURES_ROOT symlink is not allowed"
|
|
799
|
+
fi
|
|
381
800
|
elif [ -d "$FEATURES_ROOT" ]; then
|
|
382
801
|
for feature_dir in "$FEATURES_ROOT"/*; do
|
|
383
802
|
if [ ! -e "$feature_dir" ] && [ ! -L "$feature_dir" ]; then continue; fi
|
|
384
803
|
if [ ! -d "$feature_dir" ] && [ ! -L "$feature_dir" ]; then continue; fi
|
|
385
804
|
feature_slug="${feature_dir##*/}"
|
|
386
805
|
FEATURE_COUNT=$((FEATURE_COUNT + 1))
|
|
387
|
-
CHECKED_CONTOURS=$((CHECKED_CONTOURS + 1))
|
|
388
806
|
if [[ ! "$feature_slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
|
389
|
-
|
|
807
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
808
|
+
CHECKED_CONTOURS=$((CHECKED_CONTOURS + 1))
|
|
809
|
+
not_established "contour=$feature_slug invalid feature slug"
|
|
810
|
+
fi
|
|
811
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
812
|
+
COMPLETION_CHECKED_CONTOURS=$((COMPLETION_CHECKED_CONTOURS + 1))
|
|
813
|
+
mode_not_established completion "contour=$feature_slug invalid feature slug"
|
|
814
|
+
fi
|
|
815
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
816
|
+
REVISION_CHECKED_CONTOURS=$((REVISION_CHECKED_CONTOURS + 1))
|
|
817
|
+
mode_not_established report-revision "contour=$feature_slug invalid feature slug"
|
|
818
|
+
fi
|
|
819
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
820
|
+
SCENARIO_CHECKED_CONTOURS=$((SCENARIO_CHECKED_CONTOURS + 1))
|
|
821
|
+
mode_not_established criterion-scenarios "contour=$feature_slug invalid feature slug"
|
|
822
|
+
fi
|
|
390
823
|
continue
|
|
391
824
|
fi
|
|
392
|
-
|
|
393
|
-
|
|
825
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
826
|
+
CHECKED_CONTOURS=$((CHECKED_CONTOURS + 1))
|
|
827
|
+
process_contour "$feature_slug" "$feature_dir" \
|
|
828
|
+
"$FEATURE_SPECIFICATION" "$FEATURE_PSEUDOCODE" "$feature_slug"
|
|
829
|
+
fi
|
|
830
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
831
|
+
COMPLETION_CHECKED_CONTOURS=$((COMPLETION_CHECKED_CONTOURS + 1))
|
|
832
|
+
process_completion_contour "$feature_slug" "$feature_dir" \
|
|
833
|
+
"$FEATURE_SPECIFICATION" "$FEATURE_COMPLETION"
|
|
834
|
+
fi
|
|
835
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
836
|
+
REVISION_CHECKED_CONTOURS=$((REVISION_CHECKED_CONTOURS + 1))
|
|
837
|
+
process_revision_contour "$feature_slug" "$feature_dir" "$FEATURE_SPECIFICATION"
|
|
838
|
+
fi
|
|
839
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
840
|
+
SCENARIO_CHECKED_CONTOURS=$((SCENARIO_CHECKED_CONTOURS + 1))
|
|
841
|
+
process_scenario_contour "$feature_slug" "$feature_dir" "$FEATURE_SPECIFICATION"
|
|
842
|
+
fi
|
|
394
843
|
done
|
|
395
844
|
fi
|
|
396
845
|
fi
|
|
397
846
|
|
|
398
|
-
if [ "$CHECKED_CONTOURS" -eq 0 ]; then
|
|
847
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ] && [ "$CHECKED_CONTOURS" -eq 0 ]; then
|
|
399
848
|
not_established "project=$PROJECT_ROOT has no applicable project or feature contour"
|
|
400
849
|
fi
|
|
850
|
+
if [ "$COMPLETION_MODE" -eq 1 ] && [ "$COMPLETION_CHECKED_CONTOURS" -eq 0 ]; then
|
|
851
|
+
mode_not_established completion "project=$PROJECT_ROOT has no applicable project or feature contour"
|
|
852
|
+
fi
|
|
853
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ] && [ "$REVISION_CHECKED_CONTOURS" -eq 0 ]; then
|
|
854
|
+
mode_not_established report-revision "project=$PROJECT_ROOT has no applicable project or feature contour"
|
|
855
|
+
fi
|
|
856
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ] && [ "$SCENARIO_CHECKED_CONTOURS" -eq 0 ]; then
|
|
857
|
+
mode_not_established criterion-scenarios "project=$PROJECT_ROOT has no applicable project or feature contour"
|
|
858
|
+
fi
|
|
401
859
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
860
|
+
TRACE_STATUS=0
|
|
861
|
+
if [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
862
|
+
if [ "$GAP_COUNT" -gt 0 ]; then
|
|
863
|
+
printf 'VERDICT traceability=FAIL features=%s gaps=%s inconclusive=%s\n' \
|
|
864
|
+
"$FEATURE_COUNT" "$GAP_COUNT" "$INCONCLUSIVE_COUNT"
|
|
865
|
+
TRACE_STATUS=1
|
|
866
|
+
elif [ "$INCONCLUSIVE_COUNT" -gt 0 ]; then
|
|
867
|
+
printf 'VERDICT traceability=NOT-ESTABLISHED features=%s gaps=0 inconclusive=%s\n' \
|
|
868
|
+
"$FEATURE_COUNT" "$INCONCLUSIVE_COUNT"
|
|
869
|
+
TRACE_STATUS=2
|
|
870
|
+
else
|
|
871
|
+
printf 'VERDICT traceability=PASS features=%s gaps=0 inconclusive=0\n' "$FEATURE_COUNT"
|
|
872
|
+
fi
|
|
406
873
|
fi
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
874
|
+
|
|
875
|
+
COMPLETION_STATUS=0
|
|
876
|
+
REVISION_STATUS=0
|
|
877
|
+
SCENARIO_STATUS=0
|
|
878
|
+
if [ "$COMPLETION_MODE" -eq 1 ]; then
|
|
879
|
+
emit_mode_verdict completion "$COMPLETION_GAP_COUNT" "$COMPLETION_INCONCLUSIVE_COUNT"
|
|
880
|
+
COMPLETION_STATUS=$?
|
|
881
|
+
fi
|
|
882
|
+
if [ "$REPORT_REVISION_MODE" -eq 1 ]; then
|
|
883
|
+
emit_mode_verdict report-revision "$REVISION_GAP_COUNT" "$REVISION_INCONCLUSIVE_COUNT"
|
|
884
|
+
REVISION_STATUS=$?
|
|
885
|
+
fi
|
|
886
|
+
if [ "$CRITERION_SCENARIOS_MODE" -eq 1 ]; then
|
|
887
|
+
emit_mode_verdict criterion-scenarios "$SCENARIO_GAP_COUNT" "$SCENARIO_INCONCLUSIVE_COUNT"
|
|
888
|
+
SCENARIO_STATUS=$?
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
if [ "$EXPLICIT_MODE_COUNT" -le 1 ] && [ "$TRACEABILITY_MODE" -eq 1 ]; then
|
|
892
|
+
exit "$TRACE_STATUS"
|
|
893
|
+
fi
|
|
894
|
+
if [ "$INCONCLUSIVE_COUNT" -gt 0 ] || [ "$COMPLETION_STATUS" -eq 2 ] || \
|
|
895
|
+
[ "$REVISION_STATUS" -eq 2 ] || [ "$SCENARIO_STATUS" -eq 2 ]; then
|
|
410
896
|
exit 2
|
|
411
897
|
fi
|
|
412
|
-
|
|
898
|
+
if [ "$TRACE_STATUS" -eq 1 ] || [ "$COMPLETION_STATUS" -eq 1 ] || \
|
|
899
|
+
[ "$REVISION_STATUS" -eq 1 ] || [ "$SCENARIO_STATUS" -eq 1 ]; then
|
|
900
|
+
exit 1
|
|
901
|
+
fi
|
|
413
902
|
exit 0
|
package/src/utils.js
CHANGED
|
@@ -354,8 +354,10 @@ const COMPONENTS = {
|
|
|
354
354
|
'write-insight': 'Validate and append one harvest insight to the project Markdown carrier',
|
|
355
355
|
'check-ports': 'Enforce docker-ports Правило №0 against a real compose (invoke deliberately; exits 0/1/2)',
|
|
356
356
|
'check-growth-trace': 'Did the M5 growth seed reach docs/Specification.md (invoke deliberately; exits 0/1/2)',
|
|
357
|
+
'check-dangling-refs': 'Does shipped prose reference a file that does not exist? (invoke deliberately; exits 0 within baseline, 1 above it, 2 when .claude is absent)',
|
|
357
358
|
'check-look-trace': 'Did the Phase-0.5 source-look seed reach docs/Specification.md (invoke deliberately; exits 0/1/2)',
|
|
358
359
|
'check-look-origin': 'Did a third-party-analysis HYPOTHESIS row reach Specification.md without dated live confirmation (invoke deliberately; exits 0/1/2)',
|
|
360
|
+
'check-review-contract': 'Does review-report.md answer every AC id, disclose the reviewer family and name the spec revision it judged (exits 0/1/2)',
|
|
359
361
|
'capture-source-path': 'Phase-0.5 «путь» axis: click through the source product in a browser and emit FR-LOOK rows (external Playwright; exits 0/1/2)',
|
|
360
362
|
'check-docs-complete': 'Are Phase-1 documents written and placeholder-free, before the Phase-2 swarm (invoke deliberately; exits 0/1/2)',
|
|
361
363
|
'check-swarm-receipts': 'Did every parallel work unit deliver its named terminal file, before the coordinator aggregates (invoke deliberately; exits 0/1/2)',
|