@dzhechkov/p-replicator 1.13.1 → 1.13.2

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.
Files changed (27) hide show
  1. package/.dz-manifest.json +65 -13
  2. package/README.md +19 -1
  3. package/package.json +3 -3
  4. package/sbom.json +142 -12
  5. package/scripts/check-pipeline-gaps.sh +510 -21
  6. package/src/utils.js +1 -0
  7. package/templates/.claude/commands/feature.md +43 -1
  8. package/templates/.claude/hooks/check-review-contract.cjs +205 -0
  9. package/templates/.claude/hooks/statusline.cjs +1 -1
  10. package/templates/.claude/rules/replicate-pipeline.md +4 -3
  11. package/templates/.claude/skills/requirements-validator/SKILL.md +18 -5
  12. package/templates/.claude/skills/requirements-validator/references/feature-report-contracts.md +67 -0
  13. package/templates/.claude/skills/requirements-validator/references/scoring-system.md +15 -6
  14. package/tests/e2e/feature-contour.test.js +176 -0
  15. package/tests/fixtures/feature-contour/docs/features/demo-gate/01_specification.md +15 -0
  16. package/tests/fixtures/feature-contour/docs/features/demo-gate/02_pseudocode.md +19 -0
  17. package/tests/fixtures/feature-contour/docs/features/demo-gate/03_architecture.md +3 -0
  18. package/tests/fixtures/feature-contour/docs/features/demo-gate/04_refinement.md +3 -0
  19. package/tests/fixtures/feature-contour/docs/features/demo-gate/05_completion.md +7 -0
  20. package/tests/fixtures/feature-contour/docs/features/demo-gate/review-report.md +11 -0
  21. package/tests/fixtures/feature-contour/docs/features/demo-gate/validation-report.md +10 -0
  22. package/tests/fixtures/feature-contour/tests/demo.test.js +16 -0
  23. package/tests/snapshot/baseline.json +9 -7
  24. package/tests/unit/check-review-contract.test.js +181 -0
  25. package/tests/unit/honest-failure-rules.test.js +18 -3
  26. package/tests/unit/sync-templates-guard.test.js +46 -3
  27. package/tests/unit/traceability-completion-gate.test.js +267 -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
- printf 'VERDICT traceability=NOT-ESTABLISHED features=0 gaps=0 inconclusive=%s\n' \
351
- "$INCONCLUSIVE_COUNT"
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
- not_established "docs-root=$DOCS_ROOT missing or a symlink"
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
- if [ -e "$PROJECT_SPEC" ] || [ -L "$PROJECT_SPEC" ] || \
373
- [ -e "$PROJECT_PSEUDO" ] || [ -L "$PROJECT_PSEUDO" ]; then
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
- not_established "features-root=$FEATURES_ROOT symlink is not allowed"
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
- not_established "contour=$feature_slug invalid feature slug"
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
- process_contour "$feature_slug" "$feature_dir" \
393
- "$FEATURE_SPECIFICATION" "$FEATURE_PSEUDOCODE" "$feature_slug"
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
- if [ "$GAP_COUNT" -gt 0 ]; then
403
- printf 'VERDICT traceability=FAIL features=%s gaps=%s inconclusive=%s\n' \
404
- "$FEATURE_COUNT" "$GAP_COUNT" "$INCONCLUSIVE_COUNT"
405
- exit 1
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
- if [ "$INCONCLUSIVE_COUNT" -gt 0 ]; then
408
- printf 'VERDICT traceability=NOT-ESTABLISHED features=%s gaps=0 inconclusive=%s\n' \
409
- "$FEATURE_COUNT" "$INCONCLUSIVE_COUNT"
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
- printf 'VERDICT traceability=PASS features=%s gaps=0 inconclusive=0\n' "$FEATURE_COUNT"
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
@@ -356,6 +356,7 @@ const COMPONENTS = {
356
356
  'check-growth-trace': 'Did the M5 growth seed reach docs/Specification.md (invoke deliberately; exits 0/1/2)',
357
357
  'check-look-trace': 'Did the Phase-0.5 source-look seed reach docs/Specification.md (invoke deliberately; exits 0/1/2)',
358
358
  'check-look-origin': 'Did a third-party-analysis HYPOTHESIS row reach Specification.md without dated live confirmation (invoke deliberately; exits 0/1/2)',
359
+ '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
360
  '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
361
  'check-docs-complete': 'Are Phase-1 documents written and placeholder-free, before the Phase-2 swarm (invoke deliberately; exits 0/1/2)',
361
362
  'check-swarm-receipts': 'Did every parallel work unit deliver its named terminal file, before the coordinator aggregates (invoke deliberately; exits 0/1/2)',