loki-mode 7.45.1 → 7.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/SKILL.md +5 -5
- package/VERSION +1 -1
- package/autonomy/CONSTITUTION.md +9 -2
- package/autonomy/completion-council.sh +113 -0
- package/autonomy/lib/sentrux-gate.sh +1 -1
- package/autonomy/loki +2 -2
- package/autonomy/run.sh +445 -96
- package/autonomy/spec-interrogation.sh +549 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/auth.py +117 -2
- package/dashboard/server.py +9 -10
- package/docs/ACKNOWLEDGEMENTS.md +1 -1
- package/docs/COMPARISON.md +10 -10
- package/docs/COMPETITIVE-ANALYSIS.md +2 -2
- package/docs/INSTALLATION.md +2 -2
- package/docs/OPEN-CORE-BOUNDARY.md +6 -5
- package/docs/P0-SWEEP-PLAN.md +163 -0
- package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +192 -0
- package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +2 -2
- package/docs/architecture/STATE-MACHINES.md +18 -19
- package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +1 -1
- package/docs/auto-claude-comparison.md +16 -13
- package/docs/certification/01-core-concepts/lesson.md +12 -11
- package/docs/certification/01-core-concepts/quiz.md +6 -6
- package/docs/certification/05-troubleshooting/lesson.md +23 -13
- package/docs/certification/05-troubleshooting/quiz.md +3 -3
- package/docs/certification/README.md +1 -1
- package/docs/certification/answer-key.md +2 -2
- package/docs/certification/certification-exam.md +9 -9
- package/docs/competitive/bolt-new-analysis.md +2 -2
- package/docs/competitive/emergence-others-analysis.md +14 -14
- package/docs/competitive/replit-lovable-analysis.md +7 -7
- package/docs/cursor-comparison.md +15 -12
- package/docs/dashboard-guide.md +9 -7
- package/docs/enterprise/security.md +43 -3
- package/docs/prd-purple-lab-platform-v2.md +1 -1
- package/docs/prd-purple-lab-platform.md +3 -3
- package/docs/show-hn-post.md +3 -3
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +2 -2
- package/plugins/loki-mode/README.md +1 -1
- package/references/magic-rarv-integration.md +1 -1
- package/references/quality-control.md +5 -5
- package/references/sdlc-phases.md +1 -2
- package/skills/00-index.md +1 -1
- package/skills/artifacts.md +1 -1
- package/skills/healing.md +1 -1
- package/skills/magic-modules.md +3 -3
- package/skills/quality-gates.md +52 -39
- package/skills/testing.md +1 -1
- package/web-app/dist/assets/{AdminPage-CKUOsWZW.js → AdminPage-CcCJ0Sjt.js} +1 -1
- package/web-app/dist/assets/{Avatar-CL9Id9Hi.js → Avatar-DK8kmayw.js} +1 -1
- package/web-app/dist/assets/{Badge-B12zwlD7.js → Badge-4uAWnemi.js} +1 -1
- package/web-app/dist/assets/{Button-CFLVoduT.js → Button-BBMk33tk.js} +1 -1
- package/web-app/dist/assets/ComparePage-bt9rwvST.js +1 -0
- package/web-app/dist/assets/{GitHubIssuesPanel-CSitxtAX.js → GitHubIssuesPanel-WDbH47UM.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BIT06FRo.js → GitHubPRsPanel-C2CiYtTx.js} +1 -1
- package/web-app/dist/assets/{HomePage-pU_0fGny.js → HomePage-BQk-MUjn.js} +4 -4
- package/web-app/dist/assets/{LoginPage-DTZtt2Yb.js → LoginPage-DMOZVGGL.js} +1 -1
- package/web-app/dist/assets/{MagicPage-10zfra8o.js → MagicPage-Bzp2Nt1z.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C-wiKUkv.js → MetricsPage-C39JVdsw.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BDkcmhYe.js → NotFoundPage-6vT_U9UL.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-CiCavQ8n.js → ProjectPage-BfFcZp-E.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BLCXQwwC.js → ProjectsPage-CPMBf8Wt.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-PkxtaMyg.js → SettingsPage-BnNN6ETl.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-iECp8Tha.js → ShowcasePage-WDrMf-cx.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DS6Anno1.js → SystemSettingsPage-DX4jb2e8.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-ls6h6bNL.js → TeamsPage-BCfqcXzu.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Bk0QzlPt.js → TemplatesPage-CZvmimDj.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-4-1hWCtZ.js → TerminalOutput-BlRqFwWV.js} +1 -1
- package/web-app/dist/assets/{activity-DH3ih2nS.js → activity-CacZsUyr.js} +1 -1
- package/web-app/dist/assets/{bell-Gn17S6uv.js → bell-DK2qtHnk.js} +1 -1
- package/web-app/dist/assets/{bot-Cbycc3VE.js → bot-CkcUtHad.js} +1 -1
- package/web-app/dist/assets/{check-nIAqa-kf.js → check-CbCPjX3M.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D2jcWDll.js → chevron-left-5NUKWw3i.js} +1 -1
- package/web-app/dist/assets/{circle-alert-CpL4Bhvt.js → circle-alert-S7uFoxC2.js} +1 -1
- package/web-app/dist/assets/{clock-IW4Wq86N.js → clock-CaQRrIrs.js} +1 -1
- package/web-app/dist/assets/{cloud-Cn8nNuH2.js → cloud-DBAX6c0r.js} +1 -1
- package/web-app/dist/assets/{code-xml-BiJBteXf.js → code-xml-De5-EXv3.js} +1 -1
- package/web-app/dist/assets/{copy-CnqkyNsi.js → copy-CUkT6k1v.js} +1 -1
- package/web-app/dist/assets/{database-CKSReqa5.js → database-BAWf1Gwt.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CDzDY64R.js → dollar-sign-Ji8zk86R.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-Box4IwG1.js → file-code-corner-ChtXoBwS.js} +1 -1
- package/web-app/dist/assets/{file-plus-DpGqlXF8.js → file-plus-bFa37P76.js} +1 -1
- package/web-app/dist/assets/{folder-open-B57dAoBv.js → folder-open-DhXpXscO.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-BVbucmO5.js → git-commit-horizontal-DVPeDQ3j.js} +1 -1
- package/web-app/dist/assets/{globe-BkOnKl4x.js → globe-BPZgPeeu.js} +1 -1
- package/web-app/dist/assets/{hammer-DRbIQ4QU.js → hammer-jLCaujYH.js} +1 -1
- package/web-app/dist/assets/{index-CM_b_EhP.js → index-B-0iHBPO.js} +2 -2
- package/web-app/dist/assets/{layers-B78BiFiU.js → layers-B1vsrsFW.js} +1 -1
- package/web-app/dist/assets/{lightbulb-B-Itbm9g.js → lightbulb-C-uLoq9Y.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Oq6NQhW2.js → loader-circle-JTfD-ZuM.js} +1 -1
- package/web-app/dist/assets/{lock-DbJ9zxbw.js → lock-G9rxD4gZ.js} +1 -1
- package/web-app/dist/assets/{mail-CzMRod6m.js → mail-BJ0PTN_V.js} +1 -1
- package/web-app/dist/assets/{package-WZ5osvej.js → package-CXClfLOO.js} +1 -1
- package/web-app/dist/assets/{plus-j08lFR-K.js → plus-EoL5OCB7.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CIr7E-g2.js → refresh-cw-BjREUnVq.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-gwoXxDeE.js → rotate-ccw-DahWX07H.js} +1 -1
- package/web-app/dist/assets/{save-B8fV_ZpE.js → save-Dek3gCn1.js} +1 -1
- package/web-app/dist/assets/{server-D5dO1paz.js → server-D6V1BAia.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Du08zhdg.js → shield-alert-BtTK5Sxb.js} +1 -1
- package/web-app/dist/assets/{trash-2-DEKSVae5.js → trash-2-BT5o_g0r.js} +1 -1
- package/web-app/dist/assets/{trending-down-DBiXUtxJ.js → trending-down-D4Jk7KF3.js} +1 -1
- package/web-app/dist/assets/{trending-up-BgmK_tHq.js → trending-up-EQFTzhEo.js} +1 -1
- package/web-app/dist/assets/{upload-IaViyeVD.js → upload-JfI5lCSE.js} +1 -1
- package/web-app/dist/assets/{usePolling-PiRLqNu6.js → usePolling-BnhPUuGd.js} +1 -1
- package/web-app/dist/assets/{user-BB5J8wAF.js → user-DSUiUYtj.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/web-app/dist/assets/ComparePage-Dg0UdZAk.js +0 -1
package/autonomy/run.sh
CHANGED
|
@@ -2634,6 +2634,27 @@ except Exception:
|
|
|
2634
2634
|
fi
|
|
2635
2635
|
fi
|
|
2636
2636
|
|
|
2637
|
+
# P2-2: assumption-ledger summary for proof-of-done. "done" means "done, plus
|
|
2638
|
+
# here are the N places your spec was ambiguous and what Loki assumed."
|
|
2639
|
+
# spec_ledger_counts echoes "<total> <high>"; defined when spec-interrogation.sh
|
|
2640
|
+
# is sourced (run.sh sources it in DISCOVERY). Guarded for safety.
|
|
2641
|
+
local assumptions_total=0 assumptions_high=0 assumption_lines=""
|
|
2642
|
+
if type spec_ledger_counts &>/dev/null; then
|
|
2643
|
+
local _ac
|
|
2644
|
+
_ac="$(spec_ledger_counts 2>/dev/null || echo '0 0')"
|
|
2645
|
+
assumptions_total="${_ac%% *}"
|
|
2646
|
+
assumptions_high="${_ac##* }"
|
|
2647
|
+
case "$assumptions_total" in ''|*[!0-9]*) assumptions_total=0 ;; esac
|
|
2648
|
+
case "$assumptions_high" in ''|*[!0-9]*) assumptions_high=0 ;; esac
|
|
2649
|
+
if [ "$assumptions_total" != "0" ]; then
|
|
2650
|
+
local _ledger_md="$loki_dir/assumptions/ledger.md"
|
|
2651
|
+
if [ -f "$_ledger_md" ]; then
|
|
2652
|
+
# Pull just the "## <id> ..." headings as a terse one-per-line list.
|
|
2653
|
+
assumption_lines="$(grep '^## ' "$_ledger_md" 2>/dev/null | sed 's/^## / - /' || true)"
|
|
2654
|
+
fi
|
|
2655
|
+
fi
|
|
2656
|
+
fi
|
|
2657
|
+
|
|
2637
2658
|
# ---- Durable human-readable file: .loki/COMPLETION.txt --------------------
|
|
2638
2659
|
{
|
|
2639
2660
|
echo "Loki Mode run summary"
|
|
@@ -2668,6 +2689,14 @@ except Exception:
|
|
|
2668
2689
|
echo "$evidence_inconclusive_line"
|
|
2669
2690
|
echo ""
|
|
2670
2691
|
fi
|
|
2692
|
+
if [ "$assumptions_total" != "0" ]; then
|
|
2693
|
+
echo "Spec assumptions recorded: $assumptions_total ($assumptions_high high-severity)"
|
|
2694
|
+
echo " These are places your spec was ambiguous and what Loki assumed. See .loki/assumptions/ledger.md"
|
|
2695
|
+
if [ -n "$assumption_lines" ]; then
|
|
2696
|
+
echo "$assumption_lines"
|
|
2697
|
+
fi
|
|
2698
|
+
echo ""
|
|
2699
|
+
fi
|
|
2671
2700
|
echo "Review the work:"
|
|
2672
2701
|
echo " $review_cmd"
|
|
2673
2702
|
echo ""
|
|
@@ -2691,6 +2720,8 @@ except Exception:
|
|
|
2691
2720
|
_LOKI_CS_DELEGATE_BRANCH="$delegate_branch" \
|
|
2692
2721
|
_LOKI_CS_PR_URL="$pr_url" \
|
|
2693
2722
|
_LOKI_CS_TS="$ts" \
|
|
2723
|
+
_LOKI_CS_ASSUMPTIONS_TOTAL="$assumptions_total" \
|
|
2724
|
+
_LOKI_CS_ASSUMPTIONS_HIGH="$assumptions_high" \
|
|
2694
2725
|
_LOKI_CS_OUT_FILE="$loki_dir/state/completion.json" \
|
|
2695
2726
|
python3 -c "
|
|
2696
2727
|
import json, os, tempfile
|
|
@@ -2710,6 +2741,8 @@ rec = {
|
|
|
2710
2741
|
'delegate_branch': os.environ.get('_LOKI_CS_DELEGATE_BRANCH', ''),
|
|
2711
2742
|
'pr_url': os.environ.get('_LOKI_CS_PR_URL', ''),
|
|
2712
2743
|
'timestamp': os.environ.get('_LOKI_CS_TS', ''),
|
|
2744
|
+
'assumptions_total': i(os.environ.get('_LOKI_CS_ASSUMPTIONS_TOTAL')),
|
|
2745
|
+
'assumptions_high': i(os.environ.get('_LOKI_CS_ASSUMPTIONS_HIGH')),
|
|
2713
2746
|
}
|
|
2714
2747
|
d = os.path.dirname(out)
|
|
2715
2748
|
fd, tmp = tempfile.mkstemp(dir=d, suffix='.json')
|
|
@@ -7035,7 +7068,6 @@ enforce_test_coverage() {
|
|
|
7035
7068
|
|
|
7036
7069
|
local min_coverage="${LOKI_MIN_COVERAGE:-80}"
|
|
7037
7070
|
local test_passed=true
|
|
7038
|
-
local coverage_pct=0
|
|
7039
7071
|
local test_runner="none"
|
|
7040
7072
|
local details=""
|
|
7041
7073
|
|
|
@@ -7262,12 +7294,12 @@ TREOF
|
|
|
7262
7294
|
if [ "$test_passed" = "true" ]; then
|
|
7263
7295
|
touch "$quality_dir/unit-tests.pass"
|
|
7264
7296
|
rm -f "$loki_dir/signals/TESTS_FAILED" 2>/dev/null || true
|
|
7265
|
-
log_info "Test
|
|
7297
|
+
log_info "Test suite gate: $test_runner passed"
|
|
7266
7298
|
return 0
|
|
7267
7299
|
else
|
|
7268
7300
|
rm -f "$quality_dir/unit-tests.pass"
|
|
7269
7301
|
echo "tests_failed" > "$loki_dir/signals/TESTS_FAILED" 2>/dev/null || true
|
|
7270
|
-
log_warn "Test
|
|
7302
|
+
log_warn "Test suite gate: $test_runner FAILED"
|
|
7271
7303
|
return 1
|
|
7272
7304
|
fi
|
|
7273
7305
|
}
|
|
@@ -7380,7 +7412,7 @@ run_doc_staleness_check() {
|
|
|
7380
7412
|
}
|
|
7381
7413
|
|
|
7382
7414
|
# ============================================================================
|
|
7383
|
-
# Documentation Quality Gate - Gate
|
|
7415
|
+
# Documentation Quality Gate - Gate 7 (Documentation Coverage)
|
|
7384
7416
|
# Checks README, documentation freshness, and package API docs
|
|
7385
7417
|
# ============================================================================
|
|
7386
7418
|
|
|
@@ -7533,6 +7565,139 @@ run_magic_debate_gate() {
|
|
|
7533
7565
|
return 0
|
|
7534
7566
|
}
|
|
7535
7567
|
|
|
7568
|
+
# ============================================================================
|
|
7569
|
+
# Mock Integrity Gate (P0-3): wire tests/detect-mock-problems.sh as a blocking
|
|
7570
|
+
# gate. The detector scans test files for mock patterns that mask real failures
|
|
7571
|
+
# (tautological assertions, inline-mock-only tests, conditional/empty bodies,
|
|
7572
|
+
# high internal-mock ratios). Invoked with --strict so it exits 1 iff CRITICAL
|
|
7573
|
+
# or HIGH findings exist; MED/LOW never block (they are routed to a findings
|
|
7574
|
+
# file for next-iteration injection). Opt out with LOKI_GATE_MOCK=false.
|
|
7575
|
+
#
|
|
7576
|
+
# Scan-target note: the wrapper exports LOKI_SCAN_DIR=TARGET_DIR at the detector
|
|
7577
|
+
# invocation, and the detector honors it (tests/detect-mock-problems.sh:23), so
|
|
7578
|
+
# the gate scans the target project, not the loki-mode tree. When LOKI_SCAN_DIR
|
|
7579
|
+
# is unset the detector falls back to its own repo (the default for loki-mode's
|
|
7580
|
+
# own test run); the wrapper always sets it, so the target is what gets scanned.
|
|
7581
|
+
# ============================================================================
|
|
7582
|
+
enforce_mock_integrity() {
|
|
7583
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
7584
|
+
local quality_dir="$loki_dir/quality"
|
|
7585
|
+
mkdir -p "$quality_dir"
|
|
7586
|
+
local findings_file="$quality_dir/mock-findings.txt"
|
|
7587
|
+
local detector="$SCRIPT_DIR/../tests/detect-mock-problems.sh"
|
|
7588
|
+
local gate_timeout="${LOKI_GATE_TIMEOUT:-300}"
|
|
7589
|
+
|
|
7590
|
+
if [ ! -f "$detector" ]; then
|
|
7591
|
+
log_info "Mock integrity gate: detector not found, skipping (inconclusive)"
|
|
7592
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7593
|
+
return 0
|
|
7594
|
+
fi
|
|
7595
|
+
|
|
7596
|
+
local output rc
|
|
7597
|
+
output=$(cd "${TARGET_DIR:-.}" && LOKI_SCAN_DIR="${TARGET_DIR:-.}" \
|
|
7598
|
+
timeout "$gate_timeout" bash "$detector" --strict 2>&1)
|
|
7599
|
+
rc=$?
|
|
7600
|
+
|
|
7601
|
+
# timeout exit 124 -- treat as inconclusive (do not block on a hang)
|
|
7602
|
+
if [ "$rc" -eq 124 ]; then
|
|
7603
|
+
log_warn "Mock integrity gate: detector timed out after ${gate_timeout}s -- inconclusive"
|
|
7604
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7605
|
+
return 0
|
|
7606
|
+
fi
|
|
7607
|
+
|
|
7608
|
+
if [ "$rc" -ne 0 ]; then
|
|
7609
|
+
# --strict exits 1 iff CRITICAL or HIGH found. Persist per-finding text.
|
|
7610
|
+
{
|
|
7611
|
+
echo "# Mock integrity findings (CRITICAL/HIGH block this iteration)"
|
|
7612
|
+
echo "$output" | grep -E '\[(CRITICAL|HIGH|MEDIUM|LOW)\]' || true
|
|
7613
|
+
} > "$findings_file"
|
|
7614
|
+
log_warn "Mock integrity gate: CRITICAL/HIGH mock problems detected -- BLOCK"
|
|
7615
|
+
return 1
|
|
7616
|
+
fi
|
|
7617
|
+
|
|
7618
|
+
# Pass: record any MED/LOW findings for injection, then clear the block file.
|
|
7619
|
+
local med_low
|
|
7620
|
+
med_low=$(echo "$output" | grep -E '\[(MEDIUM|LOW)\]' || true)
|
|
7621
|
+
if [ -n "$med_low" ]; then
|
|
7622
|
+
{
|
|
7623
|
+
echo "# Mock integrity advisory findings (MED/LOW, non-blocking)"
|
|
7624
|
+
echo "$med_low"
|
|
7625
|
+
} > "$findings_file"
|
|
7626
|
+
else
|
|
7627
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7628
|
+
fi
|
|
7629
|
+
log_info "Mock integrity gate: PASS"
|
|
7630
|
+
return 0
|
|
7631
|
+
}
|
|
7632
|
+
|
|
7633
|
+
# ============================================================================
|
|
7634
|
+
# Test Mutation Integrity Gate (P0-3): wire tests/detect-test-mutations.sh as a
|
|
7635
|
+
# blocking gate. The detector flags assertion-value mutations that look like
|
|
7636
|
+
# test-fitting (tests changed to match buggy output). We do NOT pass --strict:
|
|
7637
|
+
# --strict blocks on ANY finding (over-blocks on MED/LOW). Instead we parse
|
|
7638
|
+
# stdout and block only when a [HIGH] line is present; MED/LOW are routed to a
|
|
7639
|
+
# findings file for next-iteration injection. Opt out with LOKI_GATE_MUTATION=false.
|
|
7640
|
+
#
|
|
7641
|
+
# Scan-target note: same as the mock gate -- the wrapper exports
|
|
7642
|
+
# LOKI_SCAN_DIR=TARGET_DIR and the detector honors it
|
|
7643
|
+
# (tests/detect-test-mutations.sh:33), so the gate scans the target project, not
|
|
7644
|
+
# the loki-mode tree. The Check-5 git history is also read from that directory.
|
|
7645
|
+
# ============================================================================
|
|
7646
|
+
enforce_mutation_integrity() {
|
|
7647
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
7648
|
+
local quality_dir="$loki_dir/quality"
|
|
7649
|
+
mkdir -p "$quality_dir"
|
|
7650
|
+
local findings_file="$quality_dir/mutation-findings.txt"
|
|
7651
|
+
local detector="$SCRIPT_DIR/../tests/detect-test-mutations.sh"
|
|
7652
|
+
local gate_timeout="${LOKI_GATE_TIMEOUT:-300}"
|
|
7653
|
+
|
|
7654
|
+
if [ ! -f "$detector" ]; then
|
|
7655
|
+
log_info "Mutation integrity gate: detector not found, skipping (inconclusive)"
|
|
7656
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7657
|
+
return 0
|
|
7658
|
+
fi
|
|
7659
|
+
|
|
7660
|
+
local output rc
|
|
7661
|
+
# No --strict: it over-blocks on MED/LOW. Decide on [HIGH] lines instead.
|
|
7662
|
+
output=$(cd "${TARGET_DIR:-.}" && LOKI_SCAN_DIR="${TARGET_DIR:-.}" \
|
|
7663
|
+
timeout "$gate_timeout" bash "$detector" 2>&1)
|
|
7664
|
+
rc=$?
|
|
7665
|
+
|
|
7666
|
+
if [ "$rc" -eq 124 ]; then
|
|
7667
|
+
log_warn "Mutation integrity gate: detector timed out after ${gate_timeout}s -- inconclusive"
|
|
7668
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7669
|
+
return 0
|
|
7670
|
+
fi
|
|
7671
|
+
|
|
7672
|
+
local high_count
|
|
7673
|
+
high_count=$(echo "$output" | grep -c '\[HIGH\]' || true)
|
|
7674
|
+
# grep -c returns 0 with no matches but may print empty under set -e edge; normalize.
|
|
7675
|
+
[ -z "$high_count" ] && high_count=0
|
|
7676
|
+
|
|
7677
|
+
if [ "$high_count" -gt 0 ]; then
|
|
7678
|
+
{
|
|
7679
|
+
echo "# Test mutation findings (HIGH blocks this iteration)"
|
|
7680
|
+
echo "$output" | grep -E '\[(HIGH|MEDIUM|MED|LOW)\]' || true
|
|
7681
|
+
} > "$findings_file"
|
|
7682
|
+
log_warn "Mutation integrity gate: $high_count HIGH test-fitting finding(s) -- BLOCK"
|
|
7683
|
+
return 1
|
|
7684
|
+
fi
|
|
7685
|
+
|
|
7686
|
+
# Pass: route any MED/LOW findings to injection file, else clear it.
|
|
7687
|
+
local med_low
|
|
7688
|
+
med_low=$(echo "$output" | grep -E '\[(MEDIUM|MED|LOW)\]' || true)
|
|
7689
|
+
if [ -n "$med_low" ]; then
|
|
7690
|
+
{
|
|
7691
|
+
echo "# Test mutation advisory findings (MED/LOW, non-blocking)"
|
|
7692
|
+
echo "$med_low"
|
|
7693
|
+
} > "$findings_file"
|
|
7694
|
+
else
|
|
7695
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
7696
|
+
fi
|
|
7697
|
+
log_info "Mutation integrity gate: PASS"
|
|
7698
|
+
return 0
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7536
7701
|
# ============================================================================
|
|
7537
7702
|
# 3-Reviewer Parallel Code Review (v5.35.0)
|
|
7538
7703
|
# Specialist pool from skills/quality-gates.md with blind review
|
|
@@ -7785,6 +7950,97 @@ MANAGED_REVIEW
|
|
|
7785
7950
|
return 0
|
|
7786
7951
|
}
|
|
7787
7952
|
|
|
7953
|
+
# _dispatch_reviewer: single-reviewer provider invocation, factored out of
|
|
7954
|
+
# run_code_review so the blind-council loop AND the Devil's-Advocate re-review
|
|
7955
|
+
# (P0-4) share ONE dispatch path. This preserves the load-bearing claude trust
|
|
7956
|
+
# guards (no --model/Fable routing, --bare, --disallowedTools, caveman OFF) for
|
|
7957
|
+
# both callers; a hand-written parallel dispatcher would drift from them.
|
|
7958
|
+
# Args: $1 = prompt text, $2 = output file path. Writes the model reply to $2.
|
|
7959
|
+
_dispatch_reviewer() {
|
|
7960
|
+
local prompt_text="$1"
|
|
7961
|
+
local review_output="$2"
|
|
7962
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
7963
|
+
claude)
|
|
7964
|
+
# SECURITY-REVIEW MODEL GUARD (evidence-based routing, item 4b):
|
|
7965
|
+
# Reviewers deliberately do NOT pass --model, so they run on
|
|
7966
|
+
# the account default model and are NEVER routed to Fable by a
|
|
7967
|
+
# mid-flight model override or LOKI_FABLE_ARCHITECT (those only
|
|
7968
|
+
# rewrite the iteration's tier_param, not this dispatch). This
|
|
7969
|
+
# must stay true. The official model-config docs CONTRADICT
|
|
7970
|
+
# routing security review to Fable: Fable's safety classifiers
|
|
7971
|
+
# refuse cybersecurity content, and in non-interactive (-p)
|
|
7972
|
+
# mode a flagged request ends the turn with stop_reason
|
|
7973
|
+
# "refusal" instead of a transparent Opus re-run. A refused
|
|
7974
|
+
# security reviewer would return no VERDICT and break the
|
|
7975
|
+
# unanimous-council gate. Defensive-cyber capability lives in
|
|
7976
|
+
# Mythos 5 (Project Glasswing), not Fable. If a future change
|
|
7977
|
+
# adds --model here, the security-sentinel reviewer must be
|
|
7978
|
+
# pinned to opus, never fable.
|
|
7979
|
+
# EMBED 2 + 3 (v7.33.0). This is a trust-gate council subcall.
|
|
7980
|
+
# $prompt_text is fully self-contained (the diff, changed files,
|
|
7981
|
+
# checks, and strict VERDICT/FINDINGS output format), output is
|
|
7982
|
+
# captured to $review_output, and it deliberately does NOT pass
|
|
7983
|
+
# --model or go through buildAutoFlags. So:
|
|
7984
|
+
# EMBED 2 (--bare): the prompt needs no hooks/LSP/CLAUDE.md/
|
|
7985
|
+
# MCP discovery, so --bare is safe and cheaper. Opt out
|
|
7986
|
+
# LOKI_BARE_SUBCALLS=0.
|
|
7987
|
+
# EMBED 3 (--disallowedTools): raise the cost of a reviewer
|
|
7988
|
+
# casually mutating the tree (a parallel agent once ran
|
|
7989
|
+
# `git reset --hard` and wiped uncommitted work). Deny
|
|
7990
|
+
# Edit/Write/NotebookEdit + git mutation forms (incl. the
|
|
7991
|
+
# git -C / --git-dir evasions); read-only git stays allowed.
|
|
7992
|
+
# Guardrail, not a sandbox -- echo>/sed -i/etc. remain; the
|
|
7993
|
+
# real net is commit-before-agent-wave. Opt out
|
|
7994
|
+
# LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
|
|
7995
|
+
local _rv_argv=("--dangerously-skip-permissions")
|
|
7996
|
+
if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
|
|
7997
|
+
_rv_argv+=("--bare")
|
|
7998
|
+
fi
|
|
7999
|
+
if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
|
|
8000
|
+
_rv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
|
|
8001
|
+
fi
|
|
8002
|
+
# EMBED 3b (--allowedTools, #167): positive least-privilege
|
|
8003
|
+
# allowlist. DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1).
|
|
8004
|
+
# Emitted ALONGSIDE the denylist: verified live (claude
|
|
8005
|
+
# 2.1.177) that deny precedence holds even under
|
|
8006
|
+
# --dangerously-skip-permissions, so the denylist still
|
|
8007
|
+
# hard-blocks mutations while this narrows the surface to
|
|
8008
|
+
# read/inspect tools. See loki_review_allowlist.
|
|
8009
|
+
if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
|
|
8010
|
+
_rv_argv+=("--allowedTools" "$(loki_review_allowlist)")
|
|
8011
|
+
fi
|
|
8012
|
+
# caveman HARD-SUPPRESS (parsed output): this is a trust-gate
|
|
8013
|
+
# subcall whose output is parsed for "^VERDICT:" + findings. A
|
|
8014
|
+
# globally-active caveman would compress/reword that line and
|
|
8015
|
+
# silently flip the verdict, so we UNCONDITIONALLY disable
|
|
8016
|
+
# caveman here with CAVEMAN_DEFAULT_MODE=off (the activate hook
|
|
8017
|
+
# then deletes its flag and emits nothing). Set inline, not via
|
|
8018
|
+
# the helper, so the carve-out holds even when the helper is
|
|
8019
|
+
# out of scope. No-op when caveman is absent.
|
|
8020
|
+
CAVEMAN_DEFAULT_MODE=off \
|
|
8021
|
+
claude "${_rv_argv[@]}" -p "$prompt_text" \
|
|
8022
|
+
--output-format text > "$review_output" 2>/dev/null
|
|
8023
|
+
;;
|
|
8024
|
+
codex)
|
|
8025
|
+
codex exec --full-auto --skip-git-repo-check "$prompt_text" \
|
|
8026
|
+
> "$review_output" 2>/dev/null
|
|
8027
|
+
;;
|
|
8028
|
+
cline)
|
|
8029
|
+
invoke_cline_capture "$prompt_text" \
|
|
8030
|
+
> "$review_output" 2>/dev/null
|
|
8031
|
+
;;
|
|
8032
|
+
aider)
|
|
8033
|
+
invoke_aider_capture "$prompt_text" \
|
|
8034
|
+
> "$review_output" 2>/dev/null
|
|
8035
|
+
;;
|
|
8036
|
+
*)
|
|
8037
|
+
echo "VERDICT: PASS" > "$review_output"
|
|
8038
|
+
echo "FINDINGS:" >> "$review_output"
|
|
8039
|
+
echo "- [Low] Unknown provider, review skipped" >> "$review_output"
|
|
8040
|
+
;;
|
|
8041
|
+
esac
|
|
8042
|
+
}
|
|
8043
|
+
|
|
7788
8044
|
run_code_review() {
|
|
7789
8045
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
7790
8046
|
local review_dir="$loki_dir/quality/reviews"
|
|
@@ -7873,7 +8129,7 @@ MANAGED_SELECTION
|
|
|
7873
8129
|
# Select specialists via keyword scoring (python3 reads files, not env vars)
|
|
7874
8130
|
# Loads from agents/types.json when available, falls back to hardcoded pool (v6.7.0)
|
|
7875
8131
|
# v7.4.20: gate legacy-healing-auditor on healing-mode signals to match
|
|
7876
|
-
# the documented contract in skills/quality-gates.md (
|
|
8132
|
+
# the documented contract in skills/quality-gates.md (conditional backward-compat auditor, not one of the 8 numbered gates).
|
|
7877
8133
|
local healing_active="false"
|
|
7878
8134
|
if [ "${LOKI_HEAL_MODE:-}" = "true" ] || [ "${LOKI_HEAL_MODE:-}" = "1" ]; then
|
|
7879
8135
|
healing_active="true"
|
|
@@ -7969,7 +8225,7 @@ if files_path and os.path.exists(files_path):
|
|
|
7969
8225
|
search_text = diff_text + " " + files_text
|
|
7970
8226
|
|
|
7971
8227
|
# v7.4.20: gate legacy-healing-auditor on healing-mode signals to match
|
|
7972
|
-
# skills/quality-gates.md (
|
|
8228
|
+
# skills/quality-gates.md (conditional backward-compat auditor, not one of the 8 numbered gates) which documents it as conditional. The
|
|
7973
8229
|
# auditor BLOCKs on missing characterization tests / missing adapters, which
|
|
7974
8230
|
# is a contract a greenfield project never agreed to maintain. agentbudget
|
|
7975
8231
|
# regression: the auditor pinned 9 of 10 iterations to forced PAUSE because
|
|
@@ -8097,91 +8353,11 @@ BUILD_PROMPT
|
|
|
8097
8353
|
|
|
8098
8354
|
log_step "Dispatching reviewer: $reviewer_name"
|
|
8099
8355
|
|
|
8100
|
-
# Launch blind review in background (
|
|
8356
|
+
# Launch blind review in background (shared dispatch helper).
|
|
8101
8357
|
(
|
|
8102
8358
|
local prompt_text
|
|
8103
8359
|
prompt_text=$(cat "$review_prompt_file")
|
|
8104
|
-
|
|
8105
|
-
claude)
|
|
8106
|
-
# SECURITY-REVIEW MODEL GUARD (evidence-based routing, item 4b):
|
|
8107
|
-
# Reviewers deliberately do NOT pass --model, so they run on
|
|
8108
|
-
# the account default model and are NEVER routed to Fable by a
|
|
8109
|
-
# mid-flight model override or LOKI_FABLE_ARCHITECT (those only
|
|
8110
|
-
# rewrite the iteration's tier_param, not this dispatch). This
|
|
8111
|
-
# must stay true. The official model-config docs CONTRADICT
|
|
8112
|
-
# routing security review to Fable: Fable's safety classifiers
|
|
8113
|
-
# refuse cybersecurity content, and in non-interactive (-p)
|
|
8114
|
-
# mode a flagged request ends the turn with stop_reason
|
|
8115
|
-
# "refusal" instead of a transparent Opus re-run. A refused
|
|
8116
|
-
# security reviewer would return no VERDICT and break the
|
|
8117
|
-
# unanimous-council gate. Defensive-cyber capability lives in
|
|
8118
|
-
# Mythos 5 (Project Glasswing), not Fable. If a future change
|
|
8119
|
-
# adds --model here, the security-sentinel reviewer must be
|
|
8120
|
-
# pinned to opus, never fable.
|
|
8121
|
-
# EMBED 2 + 3 (v7.33.0). This is a 3-reviewer council
|
|
8122
|
-
# subcall. $prompt_text is fully self-contained (built above
|
|
8123
|
-
# into $review_prompt_file with the diff, changed files,
|
|
8124
|
-
# checks, and strict VERDICT/FINDINGS output format), output
|
|
8125
|
-
# is captured to $review_output, and it deliberately does NOT
|
|
8126
|
-
# pass --model or go through buildAutoFlags. So:
|
|
8127
|
-
# EMBED 2 (--bare): the prompt needs no hooks/LSP/CLAUDE.md/
|
|
8128
|
-
# MCP discovery, so --bare is safe and cheaper. Opt out
|
|
8129
|
-
# LOKI_BARE_SUBCALLS=0.
|
|
8130
|
-
# EMBED 3 (--disallowedTools): raise the cost of a reviewer
|
|
8131
|
-
# casually mutating the tree (a parallel agent once ran
|
|
8132
|
-
# `git reset --hard` and wiped uncommitted work). Deny
|
|
8133
|
-
# Edit/Write/NotebookEdit + git mutation forms (incl. the
|
|
8134
|
-
# git -C / --git-dir evasions); read-only git stays allowed.
|
|
8135
|
-
# Guardrail, not a sandbox -- echo>/sed -i/etc. remain; the
|
|
8136
|
-
# real net is commit-before-agent-wave. Opt out
|
|
8137
|
-
# LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
|
|
8138
|
-
local _rv_argv=("--dangerously-skip-permissions")
|
|
8139
|
-
if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
|
|
8140
|
-
_rv_argv+=("--bare")
|
|
8141
|
-
fi
|
|
8142
|
-
if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
|
|
8143
|
-
_rv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
|
|
8144
|
-
fi
|
|
8145
|
-
# EMBED 3b (--allowedTools, #167): positive least-privilege
|
|
8146
|
-
# allowlist. DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1).
|
|
8147
|
-
# Emitted ALONGSIDE the denylist: verified live (claude
|
|
8148
|
-
# 2.1.177) that deny precedence holds even under
|
|
8149
|
-
# --dangerously-skip-permissions, so the denylist still
|
|
8150
|
-
# hard-blocks mutations while this narrows the surface to
|
|
8151
|
-
# read/inspect tools. See loki_review_allowlist.
|
|
8152
|
-
if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
|
|
8153
|
-
_rv_argv+=("--allowedTools" "$(loki_review_allowlist)")
|
|
8154
|
-
fi
|
|
8155
|
-
# caveman HARD-SUPPRESS (parsed output): this is a trust-gate
|
|
8156
|
-
# subcall whose output is parsed for "^VERDICT:" + findings. A
|
|
8157
|
-
# globally-active caveman would compress/reword that line and
|
|
8158
|
-
# silently flip the verdict, so we UNCONDITIONALLY disable
|
|
8159
|
-
# caveman here with CAVEMAN_DEFAULT_MODE=off (the activate hook
|
|
8160
|
-
# then deletes its flag and emits nothing). Set inline, not via
|
|
8161
|
-
# the helper, so the carve-out holds even when the helper is
|
|
8162
|
-
# out of scope. No-op when caveman is absent.
|
|
8163
|
-
CAVEMAN_DEFAULT_MODE=off \
|
|
8164
|
-
claude "${_rv_argv[@]}" -p "$prompt_text" \
|
|
8165
|
-
--output-format text > "$review_output" 2>/dev/null
|
|
8166
|
-
;;
|
|
8167
|
-
codex)
|
|
8168
|
-
codex exec --full-auto --skip-git-repo-check "$prompt_text" \
|
|
8169
|
-
> "$review_output" 2>/dev/null
|
|
8170
|
-
;;
|
|
8171
|
-
cline)
|
|
8172
|
-
invoke_cline_capture "$prompt_text" \
|
|
8173
|
-
> "$review_output" 2>/dev/null
|
|
8174
|
-
;;
|
|
8175
|
-
aider)
|
|
8176
|
-
invoke_aider_capture "$prompt_text" \
|
|
8177
|
-
> "$review_output" 2>/dev/null
|
|
8178
|
-
;;
|
|
8179
|
-
*)
|
|
8180
|
-
echo "VERDICT: PASS" > "$review_output"
|
|
8181
|
-
echo "FINDINGS:" >> "$review_output"
|
|
8182
|
-
echo "- [Low] Unknown provider, review skipped" >> "$review_output"
|
|
8183
|
-
;;
|
|
8184
|
-
esac
|
|
8360
|
+
_dispatch_reviewer "$prompt_text" "$review_output"
|
|
8185
8361
|
) &
|
|
8186
8362
|
pids+=($!)
|
|
8187
8363
|
register_pid "$!" "code-reviewer" "name=$reviewer_name"
|
|
@@ -8314,12 +8490,108 @@ AGG_SCRIPT
|
|
|
8314
8490
|
"iteration=$ITERATION_COUNT"
|
|
8315
8491
|
|
|
8316
8492
|
# Anti-sycophancy check: unanimous PASS is suspicious
|
|
8317
|
-
if [ "$pass_count" -eq "$reviewer_count" ] && [ "$fail_count" -eq 0 ]; then
|
|
8493
|
+
if [ "$pass_count" -eq "$reviewer_count" ] && [ "$fail_count" -eq 0 ] && [ "$reviewer_count" -gt 0 ]; then
|
|
8318
8494
|
log_warn "ANTI-SYCOPHANCY: All $reviewer_count reviewers passed unanimously"
|
|
8319
8495
|
log_warn "Devil's advocate note: Unanimous approval may indicate insufficient scrutiny"
|
|
8320
8496
|
log_warn "Consider manual review of $review_dir/$review_id/"
|
|
8321
8497
|
echo "UNANIMOUS_PASS: All reviewers approved - potential sycophancy risk" \
|
|
8322
8498
|
>> "$review_dir/$review_id/anti-sycophancy.txt"
|
|
8499
|
+
|
|
8500
|
+
# P0-4: Devil's-Advocate re-review. The bare warning above was INERT --
|
|
8501
|
+
# it never changed the verdict. Now, on unanimous PASS, dispatch ONE
|
|
8502
|
+
# additional adversarial reviewer (reusing _dispatch_reviewer so the
|
|
8503
|
+
# same trust guards + provider routing apply) whose sole job is to find
|
|
8504
|
+
# a Critical/High issue the unanimous council missed. If it does, we set
|
|
8505
|
+
# has_blocking=true so the EXISTING blocking decision below fires and the
|
|
8506
|
+
# gate returns 1. Runs in the FOREGROUND (no &) so has_blocking mutates
|
|
8507
|
+
# this (parent) shell, not a subshell. Opt out LOKI_GATE_DEVILS_ADVOCATE=false.
|
|
8508
|
+
if [ "${LOKI_GATE_DEVILS_ADVOCATE:-true}" = "true" ]; then
|
|
8509
|
+
log_info "Devil's Advocate: re-reviewing unanimous PASS for missed Critical/High issues..."
|
|
8510
|
+
local da_output="$review_dir/$review_id/devils-advocate.txt"
|
|
8511
|
+
local da_prompt_file="$review_dir/$review_id/devils-advocate-prompt.txt"
|
|
8512
|
+
export LOKI_DA_PROMPT_DIFF_FILE="$diff_file"
|
|
8513
|
+
export LOKI_DA_PROMPT_FILES_FILE="$files_file"
|
|
8514
|
+
export LOKI_DA_PROMPT_OUT="$da_prompt_file"
|
|
8515
|
+
python3 << 'BUILD_DA_PROMPT'
|
|
8516
|
+
import os
|
|
8517
|
+
|
|
8518
|
+
with open(os.environ["LOKI_DA_PROMPT_FILES_FILE"], "r") as f:
|
|
8519
|
+
files = f.read().strip()
|
|
8520
|
+
with open(os.environ["LOKI_DA_PROMPT_DIFF_FILE"], "r") as f:
|
|
8521
|
+
diff = f.read().strip()
|
|
8522
|
+
|
|
8523
|
+
prompt = f"""You are a Devil's Advocate reviewer. Three independent reviewers ALL approved this change. Unanimous approval is a red flag for insufficient scrutiny. Your SOLE job is to find a Critical or High severity issue they missed.
|
|
8524
|
+
|
|
8525
|
+
Be adversarial and concrete. Hunt for: security holes, data loss, race conditions, broken error handling, silent failures, off-by-one and boundary bugs, resource leaks, injection, and logic that does not match intent. Do NOT rubber-stamp. If after genuine effort you find no Critical/High issue, say so honestly -- do not invent one.
|
|
8526
|
+
|
|
8527
|
+
Files changed:
|
|
8528
|
+
{files}
|
|
8529
|
+
|
|
8530
|
+
Diff:
|
|
8531
|
+
{diff}
|
|
8532
|
+
|
|
8533
|
+
Output format (STRICT - follow exactly):
|
|
8534
|
+
VERDICT: PASS or FAIL
|
|
8535
|
+
FINDINGS:
|
|
8536
|
+
- [severity] description (file:line)
|
|
8537
|
+
Severity levels: Critical, High, Medium, Low
|
|
8538
|
+
|
|
8539
|
+
Output VERDICT: FAIL only if you found a real Critical or High issue. Otherwise output VERDICT: PASS."""
|
|
8540
|
+
|
|
8541
|
+
with open(os.environ["LOKI_DA_PROMPT_OUT"], "w") as f:
|
|
8542
|
+
f.write(prompt)
|
|
8543
|
+
BUILD_DA_PROMPT
|
|
8544
|
+
unset LOKI_DA_PROMPT_DIFF_FILE LOKI_DA_PROMPT_FILES_FILE LOKI_DA_PROMPT_OUT
|
|
8545
|
+
|
|
8546
|
+
local da_prompt_text
|
|
8547
|
+
da_prompt_text=$(cat "$da_prompt_file")
|
|
8548
|
+
# Foreground (no &) so a Critical/High finding can set has_blocking
|
|
8549
|
+
# in THIS shell. || true so a non-zero CLI exit under set -e does not
|
|
8550
|
+
# abort the gate; a missing/empty reply is treated as no finding.
|
|
8551
|
+
_dispatch_reviewer "$da_prompt_text" "$da_output" || true
|
|
8552
|
+
|
|
8553
|
+
if [ -f "$da_output" ] && [ -s "$da_output" ]; then
|
|
8554
|
+
local da_verdict
|
|
8555
|
+
da_verdict=$(grep -i "^VERDICT:" "$da_output" | head -1 | sed 's/^VERDICT:[[:space:]]*//' | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')
|
|
8556
|
+
if [ "$da_verdict" = "FAIL" ] && grep -qiE "\[(Critical|High)\]" "$da_output"; then
|
|
8557
|
+
has_blocking=true
|
|
8558
|
+
# Audit accuracy: aggregate.json was written above (line ~8429)
|
|
8559
|
+
# with has_blocking=false (entering this block requires a
|
|
8560
|
+
# unanimous PASS, so the field was necessarily false). The DA
|
|
8561
|
+
# only ever raises it false->true, so patch the persisted
|
|
8562
|
+
# record to reflect the final outcome. Targeted field update
|
|
8563
|
+
# (not a re-move of the write) keeps every other reader of
|
|
8564
|
+
# aggregate.json undisturbed.
|
|
8565
|
+
export LOKI_DA_AGG_FILE="$review_dir/$review_id/aggregate.json"
|
|
8566
|
+
python3 << 'DA_AGG_PATCH' || true
|
|
8567
|
+
import json, os
|
|
8568
|
+
agg_file = os.environ["LOKI_DA_AGG_FILE"]
|
|
8569
|
+
try:
|
|
8570
|
+
with open(agg_file) as f:
|
|
8571
|
+
data = json.load(f)
|
|
8572
|
+
data["has_blocking"] = True
|
|
8573
|
+
with open(agg_file, "w") as f:
|
|
8574
|
+
json.dump(data, f, indent=2)
|
|
8575
|
+
except (OSError, ValueError):
|
|
8576
|
+
pass
|
|
8577
|
+
DA_AGG_PATCH
|
|
8578
|
+
unset LOKI_DA_AGG_FILE
|
|
8579
|
+
log_error "DEVIL'S ADVOCATE: found Critical/High issue the unanimous council missed -- BLOCK"
|
|
8580
|
+
{
|
|
8581
|
+
echo "DEVILS_ADVOCATE_BLOCK: Critical/High found after unanimous PASS"
|
|
8582
|
+
grep -iE "\[(Critical|High)\]" "$da_output" || true
|
|
8583
|
+
} >> "$review_dir/$review_id/anti-sycophancy.txt"
|
|
8584
|
+
else
|
|
8585
|
+
log_info "Devil's Advocate: no additional Critical/High issues found"
|
|
8586
|
+
echo "DEVILS_ADVOCATE_PASS: no Critical/High beyond unanimous council" \
|
|
8587
|
+
>> "$review_dir/$review_id/anti-sycophancy.txt"
|
|
8588
|
+
fi
|
|
8589
|
+
else
|
|
8590
|
+
log_warn "Devil's Advocate: no usable output (treating as no finding)"
|
|
8591
|
+
echo "DEVILS_ADVOCATE_NO_OUTPUT: reviewer produced no usable reply" \
|
|
8592
|
+
>> "$review_dir/$review_id/anti-sycophancy.txt"
|
|
8593
|
+
fi
|
|
8594
|
+
fi
|
|
8323
8595
|
fi
|
|
8324
8596
|
|
|
8325
8597
|
# Blocking decision
|
|
@@ -11342,6 +11614,17 @@ build_prompt() {
|
|
|
11342
11614
|
gate_failure_context="${gate_failure_context}FIX THESE ISSUES BEFORE PROCEEDING WITH NEW WORK."
|
|
11343
11615
|
fi
|
|
11344
11616
|
|
|
11617
|
+
# P2-2: high-severity spec-assumption context. When DISCOVERY recorded any
|
|
11618
|
+
# high-severity assumption (the spec was ambiguous in a high-impact place),
|
|
11619
|
+
# surface it to the build agent so it implements with the gap in view (or
|
|
11620
|
+
# fixes the spec) instead of obliviously coding past it. spec_ledger_prompt_block
|
|
11621
|
+
# is defined when spec-interrogation.sh is sourced (run.sh sources it in
|
|
11622
|
+
# DISCOVERY); guarded so build_prompt is safe when the module is absent.
|
|
11623
|
+
local assumption_context=""
|
|
11624
|
+
if type spec_ledger_prompt_block &>/dev/null; then
|
|
11625
|
+
assumption_context="$(spec_ledger_prompt_block 2>/dev/null || true)"
|
|
11626
|
+
fi
|
|
11627
|
+
|
|
11345
11628
|
# Human directive injection (from HUMAN_INPUT.md)
|
|
11346
11629
|
# NOTE: Do NOT unset LOKI_HUMAN_INPUT here - build_prompt runs in a subshell
|
|
11347
11630
|
# (command substitution) so unset would not affect the parent shell.
|
|
@@ -11594,15 +11877,15 @@ except Exception:
|
|
|
11594
11877
|
else
|
|
11595
11878
|
if [ $retry -eq 0 ]; then
|
|
11596
11879
|
if [ -n "$prd" ]; then
|
|
11597
|
-
echo "Loki Mode with PRD at $prd. $update_instruction $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11880
|
+
echo "Loki Mode with PRD at $prd. $update_instruction $human_directive $gate_failure_context $assumption_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11598
11881
|
else
|
|
11599
|
-
echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11882
|
+
echo "Loki Mode. $human_directive $gate_failure_context $assumption_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11600
11883
|
fi
|
|
11601
11884
|
else
|
|
11602
11885
|
if [ -n "$prd" ]; then
|
|
11603
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11886
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $assumption_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11604
11887
|
else
|
|
11605
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11888
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $assumption_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $usage_doc_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
11606
11889
|
fi
|
|
11607
11890
|
fi
|
|
11608
11891
|
fi
|
|
@@ -11707,6 +11990,7 @@ except Exception:
|
|
|
11707
11990
|
fi
|
|
11708
11991
|
[ -n "$human_directive" ] && printf '%s\n' "$human_directive"
|
|
11709
11992
|
[ -n "$gate_failure_context" ] && printf '%s\n' "$gate_failure_context"
|
|
11993
|
+
[ -n "$assumption_context" ] && printf '%s\n' "$assumption_context"
|
|
11710
11994
|
[ -n "$queue_tasks" ] && printf '%s\n' "$queue_tasks"
|
|
11711
11995
|
[ -n "$bmad_context" ] && printf '%s\n' "$bmad_context"
|
|
11712
11996
|
[ -n "$openspec_context" ] && printf '%s\n' "$openspec_context"
|
|
@@ -12827,6 +13111,22 @@ except Exception:
|
|
|
12827
13111
|
fi
|
|
12828
13112
|
fi
|
|
12829
13113
|
|
|
13114
|
+
# P2-1: Spec interrogation (DISCOVERY phase, BEFORE iteration 1 begins
|
|
13115
|
+
# coding). Auto-detects spec ambiguities/contradictions/underspecification
|
|
13116
|
+
# via the Devil's-Advocate grill + prd-analyzer, classifies them with a
|
|
13117
|
+
# deterministic severity, and records every gap as a first-class assumption
|
|
13118
|
+
# under .loki/assumptions/. Default-on; LOKI_SPEC_GRILL=0 opts out.
|
|
13119
|
+
# Provider-aware and degrades cleanly (no provider -> prd-analyzer
|
|
13120
|
+
# assumptions only, no fabricated questions). Best-effort: never blocks the
|
|
13121
|
+
# run. The completion-side teeth are council_assumption_ledger_gate.
|
|
13122
|
+
if [ -f "${SCRIPT_DIR}/spec-interrogation.sh" ]; then
|
|
13123
|
+
# shellcheck disable=SC1090
|
|
13124
|
+
. "${SCRIPT_DIR}/spec-interrogation.sh" 2>/dev/null || true
|
|
13125
|
+
if type spec_interrogation_run &>/dev/null; then
|
|
13126
|
+
spec_interrogation_run "$prd_path" || true
|
|
13127
|
+
fi
|
|
13128
|
+
fi
|
|
13129
|
+
|
|
12830
13130
|
# Auto-derive completion promise from PRD (v6.10.0)
|
|
12831
13131
|
# When PRD exists but no explicit promise, auto-derive one and switch to checkpoint mode
|
|
12832
13132
|
if [ -n "$prd_path" ] && [ -f "$prd_path" ] && [ -z "$COMPLETION_PROMISE" ]; then
|
|
@@ -12988,6 +13288,18 @@ except Exception as exc:
|
|
|
12988
13288
|
local prompt
|
|
12989
13289
|
prompt=$(build_prompt "$retry" "$prd_path" "$ITERATION_COUNT")
|
|
12990
13290
|
|
|
13291
|
+
# P2-2 auto-acknowledgment lifecycle: build_prompt just injected the
|
|
13292
|
+
# high-severity spec assumptions into the prompt (assumption_context), so
|
|
13293
|
+
# the agent has now SEEN them. Mark them acknowledged so the completion
|
|
13294
|
+
# gate is not a permanent dead-end in autonomous (non-TTY) mode where no
|
|
13295
|
+
# human can ever set confirmed=true. This is the opposite of silent
|
|
13296
|
+
# autocorrect: the gap was recorded, prompt-injected, and is surfaced in
|
|
13297
|
+
# proof-of-done. LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1 disables auto-ack so
|
|
13298
|
+
# only a human confirmation clears the block (the helper checks the knob).
|
|
13299
|
+
if type spec_ledger_acknowledge_all &>/dev/null; then
|
|
13300
|
+
spec_ledger_acknowledge_all 2>/dev/null || true
|
|
13301
|
+
fi
|
|
13302
|
+
|
|
12991
13303
|
# BUG #5 fix: Clear LOKI_HUMAN_INPUT in the parent shell after build_prompt
|
|
12992
13304
|
# consumed it. build_prompt runs in a subshell (command substitution), so
|
|
12993
13305
|
# any unset inside it does not affect the parent. Clear here to prevent
|
|
@@ -13963,14 +14275,14 @@ if __name__ == "__main__":
|
|
|
13963
14275
|
fi
|
|
13964
14276
|
# Test coverage gate
|
|
13965
14277
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
13966
|
-
log_info "Quality gate: test
|
|
14278
|
+
log_info "Quality gate: test suite (pass/fail)..."
|
|
13967
14279
|
if enforce_test_coverage; then
|
|
13968
14280
|
clear_gate_failure "test_coverage"
|
|
13969
14281
|
else
|
|
13970
14282
|
local tc_count
|
|
13971
14283
|
tc_count=$(track_gate_failure "test_coverage")
|
|
13972
14284
|
gate_failures="${gate_failures}test_coverage,"
|
|
13973
|
-
log_warn "Test
|
|
14285
|
+
log_warn "Test suite gate FAILED ($tc_count consecutive) - must pass next iteration"
|
|
13974
14286
|
fi
|
|
13975
14287
|
fi
|
|
13976
14288
|
# BUG-ST-002: Check pause signal between quality gates (after test coverage)
|
|
@@ -13981,6 +14293,30 @@ if __name__ == "__main__":
|
|
|
13981
14293
|
fi
|
|
13982
14294
|
continue
|
|
13983
14295
|
fi
|
|
14296
|
+
# Mock integrity gate (P0-3): block on CRITICAL/HIGH mock problems.
|
|
14297
|
+
if [ "${LOKI_GATE_MOCK:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
14298
|
+
log_info "Quality gate: mock integrity..."
|
|
14299
|
+
if enforce_mock_integrity; then
|
|
14300
|
+
clear_gate_failure "mock_integrity"
|
|
14301
|
+
else
|
|
14302
|
+
local mk_count
|
|
14303
|
+
mk_count=$(track_gate_failure "mock_integrity")
|
|
14304
|
+
gate_failures="${gate_failures}mock_integrity,"
|
|
14305
|
+
log_warn "Mock integrity gate FAILED ($mk_count consecutive) - CRITICAL/HIGH mock problems"
|
|
14306
|
+
fi
|
|
14307
|
+
fi
|
|
14308
|
+
# Test mutation integrity gate (P0-3): block on HIGH test-fitting.
|
|
14309
|
+
if [ "${LOKI_GATE_MUTATION:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
14310
|
+
log_info "Quality gate: test mutation integrity..."
|
|
14311
|
+
if enforce_mutation_integrity; then
|
|
14312
|
+
clear_gate_failure "mutation_integrity"
|
|
14313
|
+
else
|
|
14314
|
+
local mt_count
|
|
14315
|
+
mt_count=$(track_gate_failure "mutation_integrity")
|
|
14316
|
+
gate_failures="${gate_failures}mutation_integrity,"
|
|
14317
|
+
log_warn "Mutation integrity gate FAILED ($mt_count consecutive) - HIGH test-fitting detected"
|
|
14318
|
+
fi
|
|
14319
|
+
fi
|
|
13984
14320
|
# Code review gate (upgraded from advisory, with escalation)
|
|
13985
14321
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
13986
14322
|
log_info "Quality gate: code review..."
|
|
@@ -14052,7 +14388,7 @@ if __name__ == "__main__":
|
|
|
14052
14388
|
if [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
14053
14389
|
run_doc_staleness_check
|
|
14054
14390
|
fi
|
|
14055
|
-
# Documentation quality gate - Gate
|
|
14391
|
+
# Documentation quality gate - Gate 7 (Documentation Coverage)
|
|
14056
14392
|
if [ "${LOKI_GATE_DOC_COVERAGE:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
14057
14393
|
log_info "Quality gate: documentation coverage..."
|
|
14058
14394
|
if run_doc_quality_gate; then
|
|
@@ -14276,6 +14612,17 @@ if __name__ == "__main__":
|
|
|
14276
14612
|
log_warn "Completion claim rejected: held-out spec-eval gate found failing held-out acceptance check(s)."
|
|
14277
14613
|
log_warn " Details under .loki/council/heldout-block.json ; opt out with LOKI_HELDOUT_GATE=0"
|
|
14278
14614
|
# Fall through; keep iterating until the held-out checks pass.
|
|
14615
|
+
# P2-2: the assumption ledger gate must also guard the DEFAULT
|
|
14616
|
+
# completion-promise route, not only the interval-gated council path.
|
|
14617
|
+
# Otherwise an agent can self-assert "done" while a high-severity spec
|
|
14618
|
+
# assumption is still unresolved, bypassing the spec-robustness gate.
|
|
14619
|
+
# Mirrors the evidence/held-out gate arms above. Opt-out: the gate's
|
|
14620
|
+
# own LOKI_ASSUMPTION_GATE=0 (returns 0 immediately when disabled, so
|
|
14621
|
+
# this branch never fires). Gate output is printed by the gate itself.
|
|
14622
|
+
elif [ "$_completion_claimed" = 1 ] && type council_assumption_ledger_gate &>/dev/null && ! council_assumption_ledger_gate; then
|
|
14623
|
+
log_warn "Completion claim rejected: assumption ledger gate found unresolved high-severity spec assumption(s)."
|
|
14624
|
+
log_warn " Details under .loki/council/assumption-block.json ; opt out with LOKI_ASSUMPTION_GATE=0"
|
|
14625
|
+
# Fall through; keep iterating until high-sev assumptions resolve.
|
|
14279
14626
|
elif [ "$_completion_claimed" = 1 ]; then
|
|
14280
14627
|
echo ""
|
|
14281
14628
|
if [ -n "$COMPLETION_PROMISE" ]; then
|
|
@@ -14751,6 +15098,8 @@ check_human_intervention() {
|
|
|
14751
15098
|
log_info "Council force-review: blocked by evidence hard gate"
|
|
14752
15099
|
elif type council_heldout_gate &>/dev/null && ! council_heldout_gate; then
|
|
14753
15100
|
log_info "Council force-review: blocked by held-out spec-eval hard gate"
|
|
15101
|
+
elif type council_assumption_ledger_gate &>/dev/null && ! council_assumption_ledger_gate; then
|
|
15102
|
+
log_info "Council force-review: blocked by assumption ledger hard gate"
|
|
14754
15103
|
elif type council_vote &>/dev/null && council_vote; then
|
|
14755
15104
|
log_header "COMPLETION COUNCIL: FORCE REVIEW - PROJECT COMPLETE"
|
|
14756
15105
|
# BUG #17 fix: Write COMPLETED marker, generate council report, and
|