loki-mode 7.129.5 → 8.0.1

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 (75) hide show
  1. package/README.md +121 -9
  2. package/SKILL.md +66 -5
  3. package/VERSION +1 -1
  4. package/autonomy/app-runner.sh +37 -0
  5. package/autonomy/completion-council.sh +862 -28
  6. package/autonomy/council-v2.sh +39 -0
  7. package/autonomy/crash.sh +3 -2
  8. package/autonomy/grill.sh +42 -0
  9. package/autonomy/hooks/validate-bash.sh +301 -4
  10. package/autonomy/lib/cockpit-render.sh +8 -2
  11. package/autonomy/lib/cr-rematerialize.py +101 -4
  12. package/autonomy/lib/deadline.py +957 -0
  13. package/autonomy/lib/dependency-setup.sh +205 -0
  14. package/autonomy/lib/done-recognition.sh +45 -0
  15. package/autonomy/lib/efficiency_cost.py +13 -6
  16. package/autonomy/lib/no_mock_scan.py +793 -0
  17. package/autonomy/lib/prd-enrich.sh +42 -0
  18. package/autonomy/lib/proof-analytics-props.py +69 -0
  19. package/autonomy/lib/proof-generator.py +326 -92
  20. package/autonomy/lib/proof-template.html +15 -12
  21. package/autonomy/lib/proof-verify.py +169 -105
  22. package/autonomy/lib/requirements_contract.py +848 -0
  23. package/autonomy/lib/sdk-mode.sh +103 -0
  24. package/autonomy/lib/secret-scan.sh +139 -0
  25. package/autonomy/lib/spec-expand.sh +208 -0
  26. package/autonomy/lib/tree_digest.py +266 -0
  27. package/autonomy/lib/voter-agents.sh +58 -8
  28. package/autonomy/lib/workspace_diff.py +141 -0
  29. package/autonomy/loki +196 -17
  30. package/autonomy/playwright-verify.sh +501 -0
  31. package/autonomy/prd-checklist.sh +17 -8
  32. package/autonomy/provider-offer.sh +111 -0
  33. package/autonomy/run.sh +4587 -671
  34. package/autonomy/sandbox.sh +42 -0
  35. package/autonomy/spec-interrogation.sh +148 -5
  36. package/autonomy/spec.sh +118 -1
  37. package/autonomy/telemetry.sh +133 -7
  38. package/autonomy/verify.sh +107 -0
  39. package/bin/loki +110 -3
  40. package/completions/_loki +10 -0
  41. package/completions/loki.bash +1 -1
  42. package/dashboard/__init__.py +1 -1
  43. package/dashboard/audit.py +96 -7
  44. package/dashboard/build_supervisor.py +1619 -0
  45. package/dashboard/control.py +8 -0
  46. package/dashboard/prompt_optimizer.py +7 -0
  47. package/dashboard/server.py +577 -22
  48. package/dashboard/static/trust.html +1 -1
  49. package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
  50. package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
  51. package/docs/INSTALLATION.md +19 -2
  52. package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
  53. package/docs/PLANS-INDEX.md +33 -0
  54. package/docs/PRIVACY.md +29 -1
  55. package/docs/SIGNED-RECEIPTS.md +102 -0
  56. package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
  57. package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
  58. package/docs/V8-AGENT-SDK-PLAN.md +692 -0
  59. package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
  60. package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
  61. package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
  62. package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
  63. package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
  64. package/docs/alternative-installations.md +4 -4
  65. package/loki-ts/dist/loki.js +674 -285
  66. package/loki-ts/package.json +2 -0
  67. package/mcp/__init__.py +1 -1
  68. package/package.json +7 -6
  69. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  70. package/providers/claude.sh +75 -0
  71. package/providers/codex.sh +85 -13
  72. package/references/sdk-mode.md +106 -0
  73. package/skills/model-selection.md +1 -1
  74. package/skills/providers.md +42 -0
  75. package/skills/quality-gates.md +22 -0
package/autonomy/loki CHANGED
@@ -143,6 +143,15 @@ if [ -f "$_LOKI_SCRIPT_DIR/lib/config-map.sh" ]; then
143
143
  source "$_LOKI_SCRIPT_DIR/lib/config-map.sh"
144
144
  fi
145
145
 
146
+ # v8.1: one-switch SDK activation resolver (LOKI_SDK_MODE=off|judges|full,
147
+ # default off). Idempotent; run.sh sources+resolves it too. Sourced here so any
148
+ # in-CLI SDK judge site sees the resolved per-site flags. No-op when mode unset.
149
+ if [ -f "$_LOKI_SCRIPT_DIR/lib/sdk-mode.sh" ]; then
150
+ # shellcheck source=autonomy/lib/sdk-mode.sh
151
+ source "$_LOKI_SCRIPT_DIR/lib/sdk-mode.sh"
152
+ loki_sdk_resolve_mode
153
+ fi
154
+
146
155
  # Resolve the script's real path (handles symlinks)
147
156
  resolve_script_path() {
148
157
  local script="$1"
@@ -824,6 +833,7 @@ show_help() {
824
833
  echo "Usage: loki <command> [options]"
825
834
  echo ""
826
835
  echo "New here? Try these in order:"
836
+ echo " loki welcome Friendly first-run opener (what Loki is, next steps)"
827
837
  echo " loki doctor Check your setup is ready (a few seconds)"
828
838
  echo " loki quickstart Guided first build from your idea (no PRD needed)"
829
839
  echo " loki start ./prd.md Build from a spec (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
@@ -856,6 +866,7 @@ show_help() {
856
866
  echo " stop Stop execution immediately"
857
867
  echo " pause Pause after current session"
858
868
  echo " resume Resume paused execution"
869
+ echo " steer \"<note>\" Nudge a running build (needs LOKI_PROMPT_INJECTION=1)"
859
870
  echo ""
860
871
  echo "Verify / trust:"
861
872
  echo " verify [base] Deterministic PR verification (CI-gate exit codes)"
@@ -910,6 +921,8 @@ show_help() {
910
921
  echo " --budget USD Set cost budget limit (display in dashboard/status)"
911
922
  echo " --bmad-project PATH Use BMAD Method project artifacts as input"
912
923
  echo " --openspec PATH Use OpenSpec change directory as input"
924
+ echo " --config PATH Load settings from a config file (.yaml/.json/.env). Alias: --env-file, --vars"
925
+ echo " Precedence: CLI flag > env var > --config file > defaults. Secrets via \${VAR}."
913
926
  echo ""
914
927
  echo "Deprecated alias 'run': 'loki run <issue-ref>' still works as an alias"
915
928
  echo "for 'loki start'. See its options with 'loki run --help'."
@@ -3328,6 +3341,52 @@ cmd_pause() {
3328
3341
  fi
3329
3342
  }
3330
3343
 
3344
+ # RUN-25 iter 22 (Wave D #3): `loki steer "<note>"` writes an operator directive
3345
+ # the running loop actually reads (.loki/HUMAN_INPUT.md), replacing the dead
3346
+ # steering.md hint the cockpit used to advertise (nothing read it). The loop only
3347
+ # INJECTS the note when LOKI_PROMPT_INJECTION=1 (default off, for safety), so we
3348
+ # write the note and tell the operator the exact flag -- honest: the control now
3349
+ # genuinely persists the directive instead of silently no-op'ing.
3350
+ cmd_steer() {
3351
+ case "${1:-}" in
3352
+ --help|-h|help|"")
3353
+ echo -e "${BOLD}Loki Mode -- steer the running build${NC}"
3354
+ echo ""
3355
+ echo "Usage: loki steer \"<guidance>\""
3356
+ echo ""
3357
+ echo "Writes your guidance to .loki/HUMAN_INPUT.md, which the running"
3358
+ echo "loop reads at the next iteration boundary (when LOKI_PROMPT_INJECTION=1)."
3359
+ echo ""
3360
+ echo " loki steer \"focus on the auth flow, skip the docs for now\""
3361
+ echo ""
3362
+ echo "The loop injects the note only when prompt injection is enabled:"
3363
+ echo " LOKI_PROMPT_INJECTION=1 loki start ... (or export it before starting)"
3364
+ [ -z "${1:-}" ] && return 2
3365
+ return 0
3366
+ ;;
3367
+ esac
3368
+ if [ ! -d "$LOKI_DIR" ]; then
3369
+ echo -e "${YELLOW}No .loki directory found.${NC} Start a session first: loki start"
3370
+ return 1
3371
+ fi
3372
+ local note="$*"
3373
+ # Append with a timestamp so multiple steers accumulate; the reader consumes
3374
+ # and clears the file each iteration, so this is the pending-directive buffer.
3375
+ {
3376
+ printf '## Operator directive (%s)\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
3377
+ printf '%s\n\n' "$note"
3378
+ } >> "$LOKI_DIR/HUMAN_INPUT.md"
3379
+ emit_event session cli steer "chars=${#note}" 2>/dev/null || true
3380
+ echo -e "${GREEN}Steering note written to .loki/HUMAN_INPUT.md${NC}"
3381
+ if [ "${LOKI_PROMPT_INJECTION:-false}" != "1" ] && [ "${LOKI_PROMPT_INJECTION:-false}" != "true" ]; then
3382
+ echo -e "${YELLOW}Note: prompt injection is OFF, so the loop will NOT read this yet.${NC}"
3383
+ echo -e "${DIM}Enable it: export LOKI_PROMPT_INJECTION=1 (then the next iteration ingests the note).${NC}"
3384
+ else
3385
+ echo -e "${DIM}The next iteration will read and apply it.${NC}"
3386
+ fi
3387
+ return 0
3388
+ }
3389
+
3331
3390
  # Resume paused execution
3332
3391
  cmd_resume() {
3333
3392
  # v7.6.2 B-13 fix: --help must print help, not act on session state.
@@ -3835,6 +3894,8 @@ WHYJSON
3835
3894
  _LOKI_WHY_STATE="$state_file" _LOKI_WHY_COMPLETION="$completion_file" \
3836
3895
  _LOKI_WHY_HEAD_SHA="$_why_head_sha" \
3837
3896
  _LOKI_WHY_LAST_ERROR="$loki_dir/state/LAST_ERROR.json" \
3897
+ _LOKI_WHY_UNCERTAINTY="$loki_dir/signals/UNCERTAINTY_ESCALATION" \
3898
+ _LOKI_WHY_CONVERGENCE="$loki_dir/council/convergence.log" \
3838
3899
  _LOKI_WHY_HANDOFFS="$loki_dir/memory/handoffs" python3 - <<'WHYTXT'
3839
3900
  import json, os, glob
3840
3901
  def load(p):
@@ -3926,6 +3987,32 @@ print()
3926
3987
  print(f" What happened: {meaning}")
3927
3988
  print(f" What to do : {action}")
3928
3989
 
3990
+ # RUN-25 iter 22b/iter 23 (Wave D #6): when a run is PAUSED / live and actually
3991
+ # STUCK, name the REAL stall reason instead of the generic "resume". The runner
3992
+ # drops .loki/signals/UNCERTAINTY_ESCALATION when its proactive stuck-detector
3993
+ # fires (repeated no-progress / uncertainty), and council/convergence.log records
3994
+ # consecutive-no-change. Surface those so "why is it stuck" has a real answer, not
3995
+ # a generic resume hint. Reads existing files only (no new state, no spend).
3996
+ _unc = os.environ.get("_LOKI_WHY_UNCERTAINTY", "")
3997
+ if _unc and os.path.exists(_unc):
3998
+ print()
3999
+ print(" Stall reason : the runner's proactive stuck-detector escalated")
4000
+ print(" (UNCERTAINTY_ESCALATION): repeated no-progress or")
4001
+ print(" low-confidence iterations. It is not making headway on its own.")
4002
+ # Add the consecutive-no-change count from the convergence log tail if present.
4003
+ _conv = os.environ.get("_LOKI_WHY_CONVERGENCE", "")
4004
+ if _conv and os.path.exists(_conv):
4005
+ try:
4006
+ with open(_conv) as _cf:
4007
+ _tail = _cf.read().splitlines()[-1:]
4008
+ if _tail:
4009
+ print(f" Latest convergence signal: {_tail[0][:120]}")
4010
+ except Exception:
4011
+ pass
4012
+ print(" Unstick it : give the loop a concrete next step:")
4013
+ print(" loki steer \"<what to try next>\" (needs LOKI_PROMPT_INJECTION=1)")
4014
+ print(" or narrow the spec / fix the blocker, then loki resume.")
4015
+
3929
4016
  # Surface the structured per-iteration error record, if one exists at
3930
4017
  # .loki/state/LAST_ERROR.json (the run.sh writer that produces it ships in the
3931
4018
  # SAME v7.92.0 release; on an older engine the file is simply absent and this
@@ -9372,14 +9459,40 @@ cmd_assets() {
9372
9459
  esac
9373
9460
  }
9374
9461
 
9375
- # Guard: check if output file exists and warn before overwriting
9462
+ # Guard: check if output file exists and warn before overwriting.
9463
+ #
9464
+ # NON-INTERACTIVE SAFETY: this is the single chokepoint every export format
9465
+ # (_export_json / _export_markdown / _export_csv / _export_timeline) routes
9466
+ # through, so the guard lives here once rather than in four callers. Without it
9467
+ # the prompt BLOCKS FOREVER whenever stdin is not closed and not answerable -- a
9468
+ # CI lane, a pipeline, or a test harness that leaves stdin attached. That wedged
9469
+ # local-ci for 40+ minutes at tests/cli/test-alias-forwarding.sh (the runner sat
9470
+ # at 0.0% CPU in state S waiting on a read that could never be satisfied).
9471
+ #
9472
+ # Semantics mirror the established precedent at the BMAD prompt (search
9473
+ # LOKI_AUTO_CONFIRM): LOKI_AUTO_CONFIRM=true (falling back to CI) means ANSWER
9474
+ # YES, and echoes an auditable "y (auto-confirmed)" line. A non-interactive
9475
+ # shell WITHOUT that opt-in FAILS CLOSED: it refuses the overwrite and names the
9476
+ # env var, because silently clobbering a user's file in CI is the worse failure.
9477
+ # `read` is `|| reply=""` guarded so an EOF cannot abort a `set -e` caller.
9376
9478
  _export_check_overwrite() {
9377
9479
  local output="$1"
9378
9480
  if [ -n "$output" ] && [ -f "$output" ]; then
9379
9481
  echo -e "${YELLOW}Warning: File already exists: $output${NC}"
9482
+ local _auto="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
9483
+ if [ "$_auto" = "true" ]; then
9484
+ echo -n "Overwrite? [y/N] "
9485
+ echo "y (auto-confirmed)"
9486
+ return 0
9487
+ fi
9488
+ if [ ! -t 0 ]; then
9489
+ echo "Export cancelled (non-interactive: refusing to overwrite '$output'." >&2
9490
+ echo "Set LOKI_AUTO_CONFIRM=true to overwrite, or choose a new output path.)" >&2
9491
+ return 1
9492
+ fi
9380
9493
  echo -n "Overwrite? [y/N] "
9381
9494
  local reply
9382
- read -r reply
9495
+ read -r reply || reply=""
9383
9496
  case "$reply" in
9384
9497
  [yY]|[yY][eE][sS]) return 0 ;;
9385
9498
  *) echo "Export cancelled."; return 1 ;;
@@ -10044,7 +10157,18 @@ cmd_config_edit() {
10044
10157
 
10045
10158
  if [ -z "$config_file" ]; then
10046
10159
  echo -e "${YELLOW}No config file found.${NC}"
10047
- read -p "Create one? [Y/n]: " create
10160
+ # Non-interactive safety (same class as _export_check_overwrite): a bare
10161
+ # `read` here blocks forever when stdin is attached but unanswerable.
10162
+ # This prompt DEFAULTS TO YES, so auto-confirm and non-interactive both
10163
+ # take the create path; only the explicit interactive "n" declines.
10164
+ local create=""
10165
+ local _auto="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
10166
+ if [ "$_auto" = "true" ] || [ ! -t 0 ]; then
10167
+ echo "Create one? [Y/n]: Y (non-interactive default; run 'loki config init' to control this)"
10168
+ create="Y"
10169
+ else
10170
+ read -r -p "Create one? [Y/n]: " create || create=""
10171
+ fi
10048
10172
  create="${create:-Y}"
10049
10173
  if [[ "$create" =~ ^[Yy] ]]; then
10050
10174
  cmd_config_init
@@ -10555,15 +10679,25 @@ cmd_doctor() {
10555
10679
  # Check if at least one provider is installed (detect_any_provider is the
10556
10680
  # shared helper from provider-offer.sh, extracted from this exact loop).
10557
10681
  if ! detect_any_provider; then
10558
- echo -e " ${RED}FAIL${NC} No AI provider CLI installed -- at least one is required"
10559
- echo -e " ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
10560
- fail_count=$((fail_count + 1))
10561
- # v7.29.0: on a TTY (non-json), append the consent-gated install offer.
10562
- # In report mode the helper is a no-op on non-TTY/CI, so the doctor
10563
- # output stays byte-identical for the bun-parity matrix. --json never
10564
- # reaches here (cmd_doctor dispatches to cmd_doctor_json earlier).
10565
- if declare -f offer_provider_install >/dev/null 2>&1; then
10566
- offer_provider_install report || true
10682
+ # The bundled Claude Agent SDK runs the main loop with no separate CLI,
10683
+ # but ONLY when it is genuinely usable: binary extracted, credentials
10684
+ # present, and the SDK loop actually active. detect_bundled_sdk_provider
10685
+ # fails closed on any doubt, so when it returns false we emit today's
10686
+ # blocker verbatim. Mirrored in loki-ts/src/commands/doctor.ts.
10687
+ if declare -f detect_bundled_sdk_provider >/dev/null 2>&1 && detect_bundled_sdk_provider; then
10688
+ echo -e " ${GREEN}PASS${NC} Bundled Claude Agent SDK is usable -- no separate CLI needed"
10689
+ pass_count=$((pass_count + 1))
10690
+ else
10691
+ echo -e " ${RED}FAIL${NC} No AI provider CLI installed -- at least one is required"
10692
+ echo -e " ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
10693
+ fail_count=$((fail_count + 1))
10694
+ # v7.29.0: on a TTY (non-json), append the consent-gated install offer.
10695
+ # In report mode the helper is a no-op on non-TTY/CI, so the doctor
10696
+ # output stays byte-identical for the bun-parity matrix. --json never
10697
+ # reaches here (cmd_doctor dispatches to cmd_doctor_json earlier).
10698
+ if declare -f offer_provider_install >/dev/null 2>&1; then
10699
+ offer_provider_install report || true
10700
+ fi
10567
10701
  fi
10568
10702
  fi
10569
10703
  echo ""
@@ -10891,6 +11025,13 @@ except Exception:
10891
11025
  fi
10892
11026
  else
10893
11027
  echo -e " ${YELLOW}WARN${NC} Bun not found -- 'loki cockpit' uses the text summary + browser dashboard (install bun for the inline-image frame)"
11028
+ # Report the protocol line here too. Without it doctor's Cockpit section
11029
+ # is asymmetric: the bun branch prints BOTH "Inline-image protocol" and
11030
+ # "Render path", the no-bun branch printed only "Render path". A user
11031
+ # reading doctor on a machine without bun could not tell whether the
11032
+ # inline-image path was unsupported by their terminal or simply never
11033
+ # probed. Naming the reason keeps the section self-explanatory.
11034
+ echo -e " ${DIM} -- ${NC} Inline-image protocol: not probed (needs bun to run the cockpit renderer)"
10894
11035
  echo -e " ${DIM} -- ${NC} Render path: text + browser dashboard"
10895
11036
  fi
10896
11037
  echo ""
@@ -12977,7 +13118,12 @@ cmd_init() {
12977
13118
  return 1
12978
13119
  fi
12979
13120
  fi
12980
- echo -e "${YELLOW}Reinitializing existing .loki/ directory${NC}"
13121
+ # stderr, not stdout: `loki init --json` writes a machine-readable
13122
+ # template list to stdout, and this human-facing banner was landing in
13123
+ # the middle of it -- the second `init` in a directory emitted invalid
13124
+ # JSON, so any tool parsing it broke on reinit but worked on first run.
13125
+ # Diagnostics belong on stderr; stdout is the data channel.
13126
+ echo -e "${YELLOW}Reinitializing existing .loki/ directory${NC}" >&2
12981
13127
  fi
12982
13128
 
12983
13129
  local version=$(get_version)
@@ -13286,7 +13432,10 @@ print(p or '')
13286
13432
  return 1
13287
13433
  fi
13288
13434
  fi
13289
- echo -e "${YELLOW}Reinitializing existing .loki/ directory in $target_dir${NC}"
13435
+ # stderr for the same reason as the sibling banner above: stdout is the
13436
+ # machine-readable channel. Fixing only the path the failing test
13437
+ # exercised would leave this one to resurface the same bug.
13438
+ echo -e "${YELLOW}Reinitializing existing .loki/ directory in $target_dir${NC}" >&2
13290
13439
  fi
13291
13440
 
13292
13441
  # Build config JSON
@@ -16692,7 +16841,7 @@ def _resolve_session_pin(alias):
16692
16841
  # override-path clamp). A 'sonnet' override dispatches sonnet.
16693
16842
  # - No override (session pin): the runner maps alias -> tier ->
16694
16843
  # resolve_model_for_tier -> clamp(model, real_tier). Use _resolve_session_pin.
16695
- # A 'sonnet' session pin dispatches OPUS (development tier).
16844
+ # A 'sonnet' session pin dispatches SONNET (development tier; CLAUDE_DEFAULT_DEVELOPMENT=sonnet since v7.104.0).
16696
16845
  # This is the task-568 fix: price the actually-dispatched model on BOTH routes.
16697
16846
  _is_override = (_session_model_source == '.loki/state/model-override')
16698
16847
  if _is_override:
@@ -17508,7 +17657,17 @@ main() {
17508
17657
  && _loki_telemetry_enabled; then
17509
17658
  _loki_disclose_telemetry_once
17510
17659
  fi
17511
- loki_telemetry "cli_command" "command=$command" 2>/dev/null || true
17660
+ # The command value is passed through the FIXED ALLOWLIST at the boundary
17661
+ # (_loki_known_command, autonomy/telemetry.sh): a real subcommand goes out
17662
+ # verbatim, anything else becomes the literal "other". Without this, the
17663
+ # honest typo `loki ./client-acme-merger-prd.md` put the raw file name on the
17664
+ # wire, because the payload builder is a pass-through dict, not an allowlist.
17665
+ # Fail closed: if the sanitizer is somehow undefined, send "other".
17666
+ if declare -f _loki_known_command >/dev/null 2>&1; then
17667
+ loki_telemetry "cli_command" "command=$(_loki_known_command "$command")" 2>/dev/null || true
17668
+ else
17669
+ loki_telemetry "cli_command" "command=other" 2>/dev/null || true
17670
+ fi
17512
17671
 
17513
17672
  # Unified config-file pre-pass (#691). For session commands ONLY, honor
17514
17673
  # LOKI_CONFIG_FILE / --config|--vars|--env-file and load the file BEFORE the
@@ -17581,6 +17740,9 @@ main() {
17581
17740
  pause)
17582
17741
  cmd_pause "$@"
17583
17742
  ;;
17743
+ steer)
17744
+ cmd_steer "$@"
17745
+ ;;
17584
17746
  resume)
17585
17747
  cmd_resume "$@"
17586
17748
  ;;
@@ -29473,6 +29635,20 @@ except Exception:
29473
29635
  fi
29474
29636
 
29475
29637
  # Gate 2: Security Scan
29638
+ # Deny filter for the secret patterns below. This scanner sees RAW DIFF
29639
+ # LINES with no file context, so unlike the other two copies of this matcher
29640
+ # (autonomy/lib/secret-scan.sh, verify.sh) it cannot skip test paths -- it
29641
+ # has to judge the VALUE. A credential whose value literally says "test",
29642
+ # "example", "xxx", or "not-called" is a fixture; real leaked keys are not
29643
+ # named after their own fakeness.
29644
+ #
29645
+ # Concretely, this is what reported 2 CRITICAL findings on the v8 PR:
29646
+ # sk-ant-xxx (loki-ts/test/runner/oauth_dev.test.ts)
29647
+ # sk-ant-test-not-called (loki-ts/tests/runner/quality_gates.test.ts)
29648
+ # Both exist to prove the SDK path fails closed without a real key. Flagging
29649
+ # them trains people to ignore CRITICAL, which is how a real one gets missed.
29650
+ local _ci_secret_deny='(test|example|sample|fixture|dummy|fake|placeholder|redacted|changeme|your[-_]|xxx+|not[-_]called|\$\{|\$[A-Za-z_]|process\.env|os\.(environ|getenv)|<[^>]*>)'
29651
+
29476
29652
  _ci_scan() {
29477
29653
  local pattern="$1" sev="$2" cat="$3" finding="$4" suggestion="$5"
29478
29654
  # Only scan added lines (starting with "+") to avoid flagging deleted code
@@ -29481,7 +29657,10 @@ except Exception:
29481
29657
  local ml
29482
29658
  ml=$(echo "$match" | cut -d: -f1)
29483
29659
  _ci_add_finding "diff" "$ml" "$sev" "$cat" "$finding" "$suggestion"
29484
- done < <(echo "$diff_content" | grep -n '^+' | grep -E "$pattern" 2>/dev/null || true)
29660
+ done < <(printf '%s\n' "$diff_content" \
29661
+ | grep -n '^+' \
29662
+ | grep -E "$pattern" 2>/dev/null \
29663
+ | grep -vEi "$_ci_secret_deny" 2>/dev/null || true)
29485
29664
  }
29486
29665
 
29487
29666
  # Hardcoded secrets