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.
Files changed (112) hide show
  1. package/README.md +16 -12
  2. package/SKILL.md +5 -5
  3. package/VERSION +1 -1
  4. package/autonomy/CONSTITUTION.md +9 -2
  5. package/autonomy/completion-council.sh +113 -0
  6. package/autonomy/lib/sentrux-gate.sh +1 -1
  7. package/autonomy/loki +2 -2
  8. package/autonomy/run.sh +445 -96
  9. package/autonomy/spec-interrogation.sh +549 -0
  10. package/dashboard/__init__.py +1 -1
  11. package/dashboard/auth.py +117 -2
  12. package/dashboard/server.py +9 -10
  13. package/docs/ACKNOWLEDGEMENTS.md +1 -1
  14. package/docs/COMPARISON.md +10 -10
  15. package/docs/COMPETITIVE-ANALYSIS.md +2 -2
  16. package/docs/INSTALLATION.md +2 -2
  17. package/docs/OPEN-CORE-BOUNDARY.md +6 -5
  18. package/docs/P0-SWEEP-PLAN.md +163 -0
  19. package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +192 -0
  20. package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +2 -2
  21. package/docs/architecture/STATE-MACHINES.md +18 -19
  22. package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +1 -1
  23. package/docs/auto-claude-comparison.md +16 -13
  24. package/docs/certification/01-core-concepts/lesson.md +12 -11
  25. package/docs/certification/01-core-concepts/quiz.md +6 -6
  26. package/docs/certification/05-troubleshooting/lesson.md +23 -13
  27. package/docs/certification/05-troubleshooting/quiz.md +3 -3
  28. package/docs/certification/README.md +1 -1
  29. package/docs/certification/answer-key.md +2 -2
  30. package/docs/certification/certification-exam.md +9 -9
  31. package/docs/competitive/bolt-new-analysis.md +2 -2
  32. package/docs/competitive/emergence-others-analysis.md +14 -14
  33. package/docs/competitive/replit-lovable-analysis.md +7 -7
  34. package/docs/cursor-comparison.md +15 -12
  35. package/docs/dashboard-guide.md +9 -7
  36. package/docs/enterprise/security.md +43 -3
  37. package/docs/prd-purple-lab-platform-v2.md +1 -1
  38. package/docs/prd-purple-lab-platform.md +3 -3
  39. package/docs/show-hn-post.md +3 -3
  40. package/loki-ts/dist/loki.js +2 -2
  41. package/mcp/__init__.py +1 -1
  42. package/package.json +2 -2
  43. package/plugins/loki-mode/.claude-plugin/plugin.json +2 -2
  44. package/plugins/loki-mode/README.md +1 -1
  45. package/references/magic-rarv-integration.md +1 -1
  46. package/references/quality-control.md +5 -5
  47. package/references/sdlc-phases.md +1 -2
  48. package/skills/00-index.md +1 -1
  49. package/skills/artifacts.md +1 -1
  50. package/skills/healing.md +1 -1
  51. package/skills/magic-modules.md +3 -3
  52. package/skills/quality-gates.md +52 -39
  53. package/skills/testing.md +1 -1
  54. package/web-app/dist/assets/{AdminPage-CKUOsWZW.js → AdminPage-CcCJ0Sjt.js} +1 -1
  55. package/web-app/dist/assets/{Avatar-CL9Id9Hi.js → Avatar-DK8kmayw.js} +1 -1
  56. package/web-app/dist/assets/{Badge-B12zwlD7.js → Badge-4uAWnemi.js} +1 -1
  57. package/web-app/dist/assets/{Button-CFLVoduT.js → Button-BBMk33tk.js} +1 -1
  58. package/web-app/dist/assets/ComparePage-bt9rwvST.js +1 -0
  59. package/web-app/dist/assets/{GitHubIssuesPanel-CSitxtAX.js → GitHubIssuesPanel-WDbH47UM.js} +1 -1
  60. package/web-app/dist/assets/{GitHubPRsPanel-BIT06FRo.js → GitHubPRsPanel-C2CiYtTx.js} +1 -1
  61. package/web-app/dist/assets/{HomePage-pU_0fGny.js → HomePage-BQk-MUjn.js} +4 -4
  62. package/web-app/dist/assets/{LoginPage-DTZtt2Yb.js → LoginPage-DMOZVGGL.js} +1 -1
  63. package/web-app/dist/assets/{MagicPage-10zfra8o.js → MagicPage-Bzp2Nt1z.js} +1 -1
  64. package/web-app/dist/assets/{MetricsPage-C-wiKUkv.js → MetricsPage-C39JVdsw.js} +1 -1
  65. package/web-app/dist/assets/{NotFoundPage-BDkcmhYe.js → NotFoundPage-6vT_U9UL.js} +1 -1
  66. package/web-app/dist/assets/{ProjectPage-CiCavQ8n.js → ProjectPage-BfFcZp-E.js} +3 -3
  67. package/web-app/dist/assets/{ProjectsPage-BLCXQwwC.js → ProjectsPage-CPMBf8Wt.js} +1 -1
  68. package/web-app/dist/assets/{SettingsPage-PkxtaMyg.js → SettingsPage-BnNN6ETl.js} +1 -1
  69. package/web-app/dist/assets/{ShowcasePage-iECp8Tha.js → ShowcasePage-WDrMf-cx.js} +1 -1
  70. package/web-app/dist/assets/{SystemSettingsPage-DS6Anno1.js → SystemSettingsPage-DX4jb2e8.js} +1 -1
  71. package/web-app/dist/assets/{TeamsPage-ls6h6bNL.js → TeamsPage-BCfqcXzu.js} +1 -1
  72. package/web-app/dist/assets/{TemplatesPage-Bk0QzlPt.js → TemplatesPage-CZvmimDj.js} +1 -1
  73. package/web-app/dist/assets/{TerminalOutput-4-1hWCtZ.js → TerminalOutput-BlRqFwWV.js} +1 -1
  74. package/web-app/dist/assets/{activity-DH3ih2nS.js → activity-CacZsUyr.js} +1 -1
  75. package/web-app/dist/assets/{bell-Gn17S6uv.js → bell-DK2qtHnk.js} +1 -1
  76. package/web-app/dist/assets/{bot-Cbycc3VE.js → bot-CkcUtHad.js} +1 -1
  77. package/web-app/dist/assets/{check-nIAqa-kf.js → check-CbCPjX3M.js} +1 -1
  78. package/web-app/dist/assets/{chevron-left-D2jcWDll.js → chevron-left-5NUKWw3i.js} +1 -1
  79. package/web-app/dist/assets/{circle-alert-CpL4Bhvt.js → circle-alert-S7uFoxC2.js} +1 -1
  80. package/web-app/dist/assets/{clock-IW4Wq86N.js → clock-CaQRrIrs.js} +1 -1
  81. package/web-app/dist/assets/{cloud-Cn8nNuH2.js → cloud-DBAX6c0r.js} +1 -1
  82. package/web-app/dist/assets/{code-xml-BiJBteXf.js → code-xml-De5-EXv3.js} +1 -1
  83. package/web-app/dist/assets/{copy-CnqkyNsi.js → copy-CUkT6k1v.js} +1 -1
  84. package/web-app/dist/assets/{database-CKSReqa5.js → database-BAWf1Gwt.js} +1 -1
  85. package/web-app/dist/assets/{dollar-sign-CDzDY64R.js → dollar-sign-Ji8zk86R.js} +1 -1
  86. package/web-app/dist/assets/{file-code-corner-Box4IwG1.js → file-code-corner-ChtXoBwS.js} +1 -1
  87. package/web-app/dist/assets/{file-plus-DpGqlXF8.js → file-plus-bFa37P76.js} +1 -1
  88. package/web-app/dist/assets/{folder-open-B57dAoBv.js → folder-open-DhXpXscO.js} +1 -1
  89. package/web-app/dist/assets/{git-commit-horizontal-BVbucmO5.js → git-commit-horizontal-DVPeDQ3j.js} +1 -1
  90. package/web-app/dist/assets/{globe-BkOnKl4x.js → globe-BPZgPeeu.js} +1 -1
  91. package/web-app/dist/assets/{hammer-DRbIQ4QU.js → hammer-jLCaujYH.js} +1 -1
  92. package/web-app/dist/assets/{index-CM_b_EhP.js → index-B-0iHBPO.js} +2 -2
  93. package/web-app/dist/assets/{layers-B78BiFiU.js → layers-B1vsrsFW.js} +1 -1
  94. package/web-app/dist/assets/{lightbulb-B-Itbm9g.js → lightbulb-C-uLoq9Y.js} +1 -1
  95. package/web-app/dist/assets/{loader-circle-Oq6NQhW2.js → loader-circle-JTfD-ZuM.js} +1 -1
  96. package/web-app/dist/assets/{lock-DbJ9zxbw.js → lock-G9rxD4gZ.js} +1 -1
  97. package/web-app/dist/assets/{mail-CzMRod6m.js → mail-BJ0PTN_V.js} +1 -1
  98. package/web-app/dist/assets/{package-WZ5osvej.js → package-CXClfLOO.js} +1 -1
  99. package/web-app/dist/assets/{plus-j08lFR-K.js → plus-EoL5OCB7.js} +1 -1
  100. package/web-app/dist/assets/{refresh-cw-CIr7E-g2.js → refresh-cw-BjREUnVq.js} +1 -1
  101. package/web-app/dist/assets/{rotate-ccw-gwoXxDeE.js → rotate-ccw-DahWX07H.js} +1 -1
  102. package/web-app/dist/assets/{save-B8fV_ZpE.js → save-Dek3gCn1.js} +1 -1
  103. package/web-app/dist/assets/{server-D5dO1paz.js → server-D6V1BAia.js} +1 -1
  104. package/web-app/dist/assets/{shield-alert-Du08zhdg.js → shield-alert-BtTK5Sxb.js} +1 -1
  105. package/web-app/dist/assets/{trash-2-DEKSVae5.js → trash-2-BT5o_g0r.js} +1 -1
  106. package/web-app/dist/assets/{trending-down-DBiXUtxJ.js → trending-down-D4Jk7KF3.js} +1 -1
  107. package/web-app/dist/assets/{trending-up-BgmK_tHq.js → trending-up-EQFTzhEo.js} +1 -1
  108. package/web-app/dist/assets/{upload-IaViyeVD.js → upload-JfI5lCSE.js} +1 -1
  109. package/web-app/dist/assets/{usePolling-PiRLqNu6.js → usePolling-BnhPUuGd.js} +1 -1
  110. package/web-app/dist/assets/{user-BB5J8wAF.js → user-DSUiUYtj.js} +1 -1
  111. package/web-app/dist/index.html +1 -1
  112. package/web-app/dist/assets/ComparePage-Dg0UdZAk.js +0 -1
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env bash
2
+ # autonomy/spec-interrogation.sh - P2-1 spec interrogation + P2-2 assumption ledger.
3
+ #
4
+ # Net-new spec-robustness capability. Loki stays accurate even when the input
5
+ # spec is WRONG, ambiguous, or incomplete by DETECTING spec defects in the
6
+ # DISCOVERY phase and SURFACING them as first-class RECORDED ASSUMPTIONS, never
7
+ # silently autocorrecting.
8
+ #
9
+ # It reuses two existing building blocks unchanged:
10
+ # - autonomy/grill.sh the Devil's-Advocate spec interrogation (provider
11
+ # subcall) that writes .loki/grill/report.md.
12
+ # - autonomy/prd-analyzer.py deterministic missing-dimension detection that
13
+ # already generates assumption text (_make_assumption).
14
+ #
15
+ # This module:
16
+ # 1. classifies grill's report.md into structured findings
17
+ # (ambiguous / contradictory / underspecified / missing) with a
18
+ # deterministic severity (high / medium) -- NO LLM, reproducible.
19
+ # 2. records each spec gap as a first-class ledger entry under .loki/assumptions/.
20
+ # 3. exposes spec_ledger_high_unresolved_count for the completion gate
21
+ # (council_assumption_ledger_gate in completion-council.sh).
22
+ #
23
+ # Design note (auto-acknowledgment lifecycle):
24
+ # The completion gate blocks iff an entry is severity=high AND confirmed=false
25
+ # AND acknowledged=false. In autonomous (non-TTY) mode no human can ever set
26
+ # confirmed=yes, so the auto-acknowledgment lifecycle (run.sh) marks an
27
+ # assumption acknowledged once it has been injected into the build prompt at
28
+ # least once. That is the OPPOSITE of silent autocorrect: the gap is recorded,
29
+ # prompt-injected, and surfaced in proof-of-done. LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1
30
+ # disables auto-ack for a human-in-the-loop path (only confirmed=true clears).
31
+ #
32
+ # Provider-aware + clean degrade: grill needs a provider CLI; when absent we log
33
+ # an honest message, skip the grill subcall (NO fabricated questions), but STILL
34
+ # fold prd-analyzer's deterministic missing-dimension assumptions into the ledger
35
+ # as medium (non-blocking) so degrade still surfaces something.
36
+ #
37
+ # Opt-out knobs (all default-on):
38
+ # LOKI_SPEC_GRILL=0 skip interrogation entirely
39
+ # LOKI_ASSUMPTION_GATE=0 completion gate is pass-through (gate file)
40
+ # LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1 require human confirmed=true (no auto-ack)
41
+
42
+ set -uo pipefail
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Logging shims. run.sh provides log_* helpers; when sourced standalone (tests,
46
+ # direct invocation) fall back to stderr so the module is self-contained.
47
+ # ---------------------------------------------------------------------------
48
+ if ! type log_info >/dev/null 2>&1; then
49
+ log_info() { printf '%s\n' "$*" >&2; }
50
+ fi
51
+ if ! type log_warn >/dev/null 2>&1; then
52
+ log_warn() { printf '[warn] %s\n' "$*" >&2; }
53
+ fi
54
+ if ! type log_step >/dev/null 2>&1; then
55
+ log_step() { printf '%s\n' "$*" >&2; }
56
+ fi
57
+
58
+ SPEC_LEDGER_DIR_DEFAULT=".loki/assumptions"
59
+
60
+ # Resolve the ledger directory (respects TARGET_DIR like the rest of the runner).
61
+ _spec_ledger_dir() {
62
+ printf '%s/%s' "${TARGET_DIR:-.}" "$SPEC_LEDGER_DIR_DEFAULT"
63
+ }
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Deterministic severity for a grill finding given its section + line text.
67
+ # HIGH: security / scale / reliability / missing-or-untestable acceptance
68
+ # criteria / explicit contradiction. MEDIUM: everything else.
69
+ # Echoes "high" or "medium".
70
+ # ---------------------------------------------------------------------------
71
+ spec_interrogation_severity_for() {
72
+ local section="$1"
73
+ local line="$2"
74
+ local lc_section lc_line
75
+ lc_section="$(printf '%s' "$section" | tr '[:upper:]' '[:lower:]')"
76
+ lc_line="$(printf '%s' "$line" | tr '[:upper:]' '[:lower:]')"
77
+
78
+ # Explicit contradiction keywords escalate to high regardless of section.
79
+ case "$lc_line" in
80
+ *contradict*|*conflict*|*inconsistent*|*mutually\ exclusive*)
81
+ printf 'high'; return 0 ;;
82
+ esac
83
+
84
+ # Section-driven severity.
85
+ case "$lc_section" in
86
+ *security*|*scale*|*reliability*)
87
+ printf 'high'; return 0 ;;
88
+ esac
89
+
90
+ # Missing or untestable acceptance criteria are high (cannot verify done).
91
+ case "$lc_line" in
92
+ *acceptance\ criteria*|*acceptance\ criterion*|*testable*|*measurable*|*definition\ of\ done*)
93
+ printf 'high'; return 0 ;;
94
+ esac
95
+
96
+ printf 'medium'
97
+ }
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # Map a grill section heading to a finding class.
101
+ # Echoes one of: ambiguous | contradictory | underspecified | missing
102
+ # (contradictory is also forced at line level when a contradiction keyword hits).
103
+ # ---------------------------------------------------------------------------
104
+ spec_interrogation_class_for() {
105
+ local section="$1"
106
+ local line="$2"
107
+ local lc_section lc_line
108
+ lc_section="$(printf '%s' "$section" | tr '[:upper:]' '[:lower:]')"
109
+ lc_line="$(printf '%s' "$line" | tr '[:upper:]' '[:lower:]')"
110
+
111
+ case "$lc_line" in
112
+ *contradict*|*conflict*|*inconsistent*|*mutually\ exclusive*)
113
+ printf 'contradictory'; return 0 ;;
114
+ esac
115
+
116
+ case "$lc_section" in
117
+ *security*|*scale*|*reliability*) printf 'missing'; return 0 ;;
118
+ *unstated\ assumption*) printf 'underspecified'; return 0 ;;
119
+ *ambiguit*|*acceptance*) printf 'ambiguous'; return 0 ;;
120
+ esac
121
+ printf 'ambiguous'
122
+ }
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Map a grill section heading to an "affects" area for the ledger.
126
+ # ---------------------------------------------------------------------------
127
+ _spec_affects_for() {
128
+ local section="$1"
129
+ local lc_section
130
+ lc_section="$(printf '%s' "$section" | tr '[:upper:]' '[:lower:]')"
131
+ case "$lc_section" in
132
+ *security*) printf 'security' ;;
133
+ *scale*|*reliability*) printf 'scale-reliability' ;;
134
+ *acceptance*|*ambiguit*) printf 'acceptance-criteria' ;;
135
+ *unstated\ assumption*) printf 'requirements' ;;
136
+ *) printf 'requirements' ;;
137
+ esac
138
+ }
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Stable, dedupe-safe id for a gap: a-<8 hex of the gap text>.
142
+ # Idempotent: the same gap text always yields the same id, so re-running
143
+ # DISCOVERY does not duplicate ledger entries.
144
+ # ---------------------------------------------------------------------------
145
+ _spec_gap_id() {
146
+ local gap="$1"
147
+ local h
148
+ if command -v shasum >/dev/null 2>&1; then
149
+ h="$(printf '%s' "$gap" | shasum 2>/dev/null | cut -c1-8)"
150
+ elif command -v sha1sum >/dev/null 2>&1; then
151
+ h="$(printf '%s' "$gap" | sha1sum 2>/dev/null | cut -c1-8)"
152
+ else
153
+ # cksum fallback (always present): pad/truncate to 8 chars.
154
+ h="$(printf '%s' "$gap" | cksum 2>/dev/null | cut -d' ' -f1)"
155
+ h="$(printf '%08x' "${h:-0}" 2>/dev/null | cut -c1-8)"
156
+ fi
157
+ printf 'a-%s' "${h:-00000000}"
158
+ }
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Write (or skip-if-present) one ledger entry.
162
+ # Usage: spec_ledger_write <gap> <assumption> <why> <severity> <class> <affects> <source>
163
+ # Idempotent on the gap id. Returns 0 always (best-effort; never fails a run).
164
+ # ---------------------------------------------------------------------------
165
+ spec_ledger_write() {
166
+ local gap="$1" assumption="$2" why="$3" severity="$4" class="$5" affects="$6" source="$7"
167
+ local dir id file
168
+ dir="$(_spec_ledger_dir)"
169
+ mkdir -p "$dir" 2>/dev/null || return 0
170
+ id="$(_spec_gap_id "$gap")"
171
+ file="$dir/$id.json"
172
+ # Idempotent: if this gap is already recorded, do not overwrite (preserves
173
+ # any confirmed/acknowledged state set since).
174
+ [ -f "$file" ] && return 0
175
+
176
+ local ts
177
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
178
+ _SL_ID="$id" _SL_GAP="$gap" _SL_ASSUMP="$assumption" _SL_WHY="$why" \
179
+ _SL_SEV="$severity" _SL_CLASS="$class" _SL_AFFECTS="$affects" \
180
+ _SL_SOURCE="$source" _SL_TS="$ts" _SL_FILE="$file" python3 -c '
181
+ import json, os, tempfile
182
+ rec = {
183
+ "id": os.environ["_SL_ID"],
184
+ "gap": os.environ["_SL_GAP"],
185
+ "assumption": os.environ["_SL_ASSUMP"],
186
+ "why": os.environ["_SL_WHY"],
187
+ "severity": os.environ["_SL_SEV"],
188
+ "class": os.environ["_SL_CLASS"],
189
+ "affects": os.environ["_SL_AFFECTS"],
190
+ "source": os.environ["_SL_SOURCE"],
191
+ "confirmed": False,
192
+ "acknowledged": False,
193
+ "created_at": os.environ["_SL_TS"],
194
+ }
195
+ out = os.environ["_SL_FILE"]
196
+ d = os.path.dirname(out)
197
+ fd, tmp = tempfile.mkstemp(dir=d, suffix=".tmp")
198
+ try:
199
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
200
+ json.dump(rec, f, indent=2)
201
+ os.replace(tmp, out)
202
+ except Exception:
203
+ try: os.unlink(tmp)
204
+ except OSError: pass
205
+ raise
206
+ ' 2>/dev/null || true
207
+ return 0
208
+ }
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Classify a grill report.md into ledger entries.
212
+ # Usage: spec_interrogation_classify_report <report.md path>
213
+ # Pure: reads the markdown, writes ledger entries. No provider call. This is the
214
+ # function the test (a) drives with a fixture report.
215
+ # Returns 0 on success (including zero findings), 1 if the report is missing.
216
+ # ---------------------------------------------------------------------------
217
+ spec_interrogation_classify_report() {
218
+ local report="$1"
219
+ [ -f "$report" ] || return 1
220
+
221
+ local section=""
222
+ local line stripped q
223
+ while IFS= read -r line || [ -n "$line" ]; do
224
+ # Track the current "### Section" heading.
225
+ case "$line" in
226
+ "### "*)
227
+ section="${line#"### "}"
228
+ continue ;;
229
+ "## "*)
230
+ # A top-level heading (e.g. "## Grill findings") is not a finding
231
+ # section; reset so stray numbered lines under it are ignored
232
+ # until a real ### section starts.
233
+ section=""
234
+ continue ;;
235
+ esac
236
+
237
+ [ -z "$section" ] && continue
238
+
239
+ # Finding lines look like "1. <question>" or "- <question>".
240
+ case "$line" in
241
+ [0-9]*". "*)
242
+ q="${line#*. }" ;;
243
+ "- "*)
244
+ q="${line#- }" ;;
245
+ *)
246
+ continue ;;
247
+ esac
248
+
249
+ # Trim leading/trailing whitespace.
250
+ stripped="$(printf '%s' "$q" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
251
+ [ -z "$stripped" ] && continue
252
+ # Skip explicit "None identified." placeholders (no fabricated findings).
253
+ case "$stripped" in
254
+ "None identified"*|"None."*|"None"|"N/A"*) continue ;;
255
+ esac
256
+
257
+ local sev class affects assumption
258
+ sev="$(spec_interrogation_severity_for "$section" "$stripped")"
259
+ class="$(spec_interrogation_class_for "$section" "$stripped")"
260
+ affects="$(_spec_affects_for "$section")"
261
+ # No-fabrication: the finding is a QUESTION; the honest assumption is a
262
+ # stated default, NOT an invented resolution the build will not follow.
263
+ assumption="Spec gives no answer; proceeding with the implementer default for ${affects}."
264
+
265
+ spec_ledger_write \
266
+ "$stripped" \
267
+ "$assumption" \
268
+ "grill: ${section}" \
269
+ "$sev" \
270
+ "$class" \
271
+ "$affects" \
272
+ "grill"
273
+ done < "$report"
274
+ return 0
275
+ }
276
+
277
+ # ---------------------------------------------------------------------------
278
+ # Fold prd-analyzer's deterministic missing-dimension assumptions into the
279
+ # ledger as medium (non-blocking). Reads .loki/prd-observations.md "Assumptions
280
+ # Made" section. Best-effort; runs even when no provider is available so degrade
281
+ # still surfaces something. Usage: spec_ledger_fold_prd_observations [path]
282
+ # ---------------------------------------------------------------------------
283
+ spec_ledger_fold_prd_observations() {
284
+ local obs="${1:-${TARGET_DIR:-.}/.loki/prd-observations.md}"
285
+ [ -f "$obs" ] || return 0
286
+
287
+ local in_section="false" line item
288
+ while IFS= read -r line || [ -n "$line" ]; do
289
+ case "$line" in
290
+ "## Assumptions Made"*) in_section="true"; continue ;;
291
+ "## "*) in_section="false"; continue ;;
292
+ esac
293
+ [ "$in_section" = "true" ] || continue
294
+ case "$line" in
295
+ "- "*)
296
+ item="${line#- }"
297
+ item="$(printf '%s' "$item" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" ;;
298
+ *)
299
+ continue ;;
300
+ esac
301
+ [ -z "$item" ] && continue
302
+ # The analyzer emits "No assumptions needed; PRD is comprehensive" when
303
+ # the PRD is clean: that is not a gap, skip it.
304
+ case "$item" in
305
+ "No assumptions needed"*) continue ;;
306
+ esac
307
+ # Use the analyzer's assumption text as the gap so each distinct missing
308
+ # dimension gets its own ledger entry (the dedupe id derives from the gap
309
+ # text; a constant gap would collapse all dimensions into one entry).
310
+ spec_ledger_write \
311
+ "Missing PRD dimension: ${item}" \
312
+ "$item" \
313
+ "prd-analyzer: missing dimension" \
314
+ "medium" \
315
+ "missing" \
316
+ "requirements" \
317
+ "prd-analyzer"
318
+ done < "$obs"
319
+ return 0
320
+ }
321
+
322
+ # ---------------------------------------------------------------------------
323
+ # Count ledger entries that BLOCK completion: severity=high AND confirmed=false
324
+ # AND acknowledged=false. Echoes an integer. Used by the council gate and the
325
+ # completion summary. Zero when the ledger dir is absent.
326
+ # ---------------------------------------------------------------------------
327
+ spec_ledger_high_unresolved_count() {
328
+ local dir
329
+ dir="$(_spec_ledger_dir)"
330
+ if [ ! -d "$dir" ]; then printf '0'; return 0; fi
331
+ _SL_DIR="$dir" python3 -c '
332
+ import glob, json, os
333
+ d = os.environ["_SL_DIR"]
334
+ n = 0
335
+ for p in glob.glob(os.path.join(d, "a-*.json")):
336
+ try:
337
+ with open(p) as f:
338
+ r = json.load(f)
339
+ except Exception:
340
+ continue
341
+ if r.get("severity") == "high" and not r.get("confirmed") and not r.get("acknowledged"):
342
+ n += 1
343
+ print(n)
344
+ ' 2>/dev/null || printf '0'
345
+ }
346
+
347
+ # Total ledger entries + high count, "total high" on one line. For summaries.
348
+ spec_ledger_counts() {
349
+ local dir
350
+ dir="$(_spec_ledger_dir)"
351
+ if [ ! -d "$dir" ]; then printf '0 0'; return 0; fi
352
+ _SL_DIR="$dir" python3 -c '
353
+ import glob, json, os
354
+ d = os.environ["_SL_DIR"]
355
+ total = high = 0
356
+ for p in glob.glob(os.path.join(d, "a-*.json")):
357
+ try:
358
+ with open(p) as f:
359
+ r = json.load(f)
360
+ except Exception:
361
+ continue
362
+ total += 1
363
+ if r.get("severity") == "high":
364
+ high += 1
365
+ print("%d %d" % (total, high))
366
+ ' 2>/dev/null || printf '0 0'
367
+ }
368
+
369
+ # ---------------------------------------------------------------------------
370
+ # Auto-acknowledgment lifecycle helper: set acknowledged=true on every ledger
371
+ # entry. run.sh calls this once an iteration AFTER assumptions are injected into
372
+ # the build prompt (unless LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1). Best-effort.
373
+ # ---------------------------------------------------------------------------
374
+ spec_ledger_acknowledge_all() {
375
+ [ "${LOKI_ASSUMPTIONS_REQUIRE_CONFIRM:-0}" = "1" ] && return 0
376
+ local dir
377
+ dir="$(_spec_ledger_dir)"
378
+ [ -d "$dir" ] || return 0
379
+ _SL_DIR="$dir" python3 -c '
380
+ import glob, json, os, tempfile
381
+ d = os.environ["_SL_DIR"]
382
+ for p in glob.glob(os.path.join(d, "a-*.json")):
383
+ try:
384
+ with open(p) as f:
385
+ r = json.load(f)
386
+ except Exception:
387
+ continue
388
+ if r.get("acknowledged"):
389
+ continue
390
+ r["acknowledged"] = True
391
+ fd, tmp = tempfile.mkstemp(dir=os.path.dirname(p), suffix=".tmp")
392
+ try:
393
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
394
+ json.dump(r, f, indent=2)
395
+ os.replace(tmp, p)
396
+ except Exception:
397
+ try: os.unlink(tmp)
398
+ except OSError: pass
399
+ ' 2>/dev/null || true
400
+ return 0
401
+ }
402
+
403
+ # ---------------------------------------------------------------------------
404
+ # Build a compact prompt-injection block listing high-severity assumptions, so
405
+ # the build agent sees the spec gaps it must respect. Echoes the block (empty
406
+ # when no high-sev entries). Used by build_prompt in run.sh.
407
+ # ---------------------------------------------------------------------------
408
+ spec_ledger_prompt_block() {
409
+ local dir
410
+ dir="$(_spec_ledger_dir)"
411
+ [ -d "$dir" ] || return 0
412
+ _SL_DIR="$dir" python3 -c '
413
+ import glob, json, os
414
+ d = os.environ["_SL_DIR"]
415
+ rows = []
416
+ for p in sorted(glob.glob(os.path.join(d, "a-*.json"))):
417
+ try:
418
+ with open(p) as f:
419
+ r = json.load(f)
420
+ except Exception:
421
+ continue
422
+ if r.get("severity") != "high" or r.get("confirmed"):
423
+ continue
424
+ rows.append("- [%s] %s -> assumed: %s" % (r.get("affects",""), r.get("gap",""), r.get("assumption","")))
425
+ if rows:
426
+ print("SPEC ASSUMPTIONS (high-severity, recorded because the spec was ambiguous; respect these or fix the spec): " + " ".join(rows))
427
+ ' 2>/dev/null || true
428
+ return 0
429
+ }
430
+
431
+ # ---------------------------------------------------------------------------
432
+ # Regenerate the human-readable ledger rollup .loki/assumptions/ledger.md.
433
+ # Best-effort. Called after writes and surfaced in proof-of-done.
434
+ # ---------------------------------------------------------------------------
435
+ spec_ledger_rebuild_md() {
436
+ local dir
437
+ dir="$(_spec_ledger_dir)"
438
+ [ -d "$dir" ] || return 0
439
+ _SL_DIR="$dir" python3 -c '
440
+ import glob, json, os, tempfile
441
+ d = os.environ["_SL_DIR"]
442
+ entries = []
443
+ for p in sorted(glob.glob(os.path.join(d, "a-*.json"))):
444
+ try:
445
+ with open(p) as f:
446
+ entries.append(json.load(f))
447
+ except Exception:
448
+ continue
449
+ lines = ["# Assumption ledger", ""]
450
+ if not entries:
451
+ lines.append("No assumptions recorded. The spec was complete and unambiguous.")
452
+ else:
453
+ high = sum(1 for e in entries if e.get("severity") == "high")
454
+ lines.append("Total assumptions: %d (%d high-severity)" % (len(entries), high))
455
+ lines.append("")
456
+ for e in entries:
457
+ state = "confirmed" if e.get("confirmed") else ("acknowledged" if e.get("acknowledged") else "OPEN")
458
+ lines.append("## %s [%s / %s / %s]" % (e.get("id",""), e.get("severity",""), e.get("class",""), state))
459
+ lines.append("")
460
+ lines.append("- Gap: %s" % e.get("gap",""))
461
+ lines.append("- Assumption: %s" % e.get("assumption",""))
462
+ lines.append("- Why: %s" % e.get("why",""))
463
+ lines.append("- Affects: %s" % e.get("affects",""))
464
+ lines.append("- Source: %s" % e.get("source",""))
465
+ lines.append("")
466
+ out = os.path.join(d, "ledger.md")
467
+ fd, tmp = tempfile.mkstemp(dir=d, suffix=".tmp")
468
+ try:
469
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
470
+ f.write("\n".join(lines) + "\n")
471
+ os.replace(tmp, out)
472
+ except Exception:
473
+ try: os.unlink(tmp)
474
+ except OSError: pass
475
+ ' 2>/dev/null || true
476
+ return 0
477
+ }
478
+
479
+ # ---------------------------------------------------------------------------
480
+ # DISCOVERY orchestrator: run spec interrogation and populate the ledger.
481
+ # Usage: spec_interrogation_run <spec_path>
482
+ # Default-on; LOKI_SPEC_GRILL=0 opts out. Always non-fatal to the run.
483
+ # ---------------------------------------------------------------------------
484
+ spec_interrogation_run() {
485
+ local spec_path="${1:-}"
486
+
487
+ if [ "${LOKI_SPEC_GRILL:-1}" = "0" ]; then
488
+ return 0
489
+ fi
490
+
491
+ # Source grill.sh for grill_main + grill_check_provider. Best-effort: if it
492
+ # is missing we still fold prd-analyzer assumptions below.
493
+ local _self_dir grill_sh
494
+ _self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
495
+ grill_sh="$_self_dir/grill.sh"
496
+ local grill_available="false"
497
+ if [ -f "$grill_sh" ]; then
498
+ # shellcheck disable=SC1090
499
+ . "$grill_sh" 2>/dev/null && grill_available="true"
500
+ fi
501
+
502
+ log_step "Spec interrogation (DISCOVERY): surfacing ambiguities as recorded assumptions..."
503
+
504
+ # Provider-aware grill subcall. Degrade cleanly (no fabricated questions).
505
+ if [ "$grill_available" = "true" ] && type grill_check_provider >/dev/null 2>&1; then
506
+ if grill_check_provider 2>/dev/null; then
507
+ local report_dir
508
+ report_dir="${TARGET_DIR:-.}/.loki/grill"
509
+ # grill_main resolves the spec source itself; pass the explicit path
510
+ # when we have one so it grills exactly the active spec.
511
+ if [ -n "$spec_path" ] && [ -f "$spec_path" ]; then
512
+ grill_main "$spec_path" --out "$report_dir" >/dev/null 2>&1 || \
513
+ log_warn "Spec interrogation: grill subcall failed; continuing with prd-analyzer assumptions only."
514
+ else
515
+ grill_main --out "$report_dir" >/dev/null 2>&1 || \
516
+ log_warn "Spec interrogation: grill subcall failed; continuing with prd-analyzer assumptions only."
517
+ fi
518
+ local report="$report_dir/report.md"
519
+ if [ -f "$report" ]; then
520
+ spec_interrogation_classify_report "$report" || true
521
+ fi
522
+ else
523
+ log_warn "Spec interrogation: no provider CLI available; skipping the Devil's-Advocate grill (no fabricated questions). Recording prd-analyzer assumptions only."
524
+ fi
525
+ else
526
+ log_warn "Spec interrogation: grill module unavailable; recording prd-analyzer assumptions only."
527
+ fi
528
+
529
+ # Always fold prd-analyzer's deterministic missing-dimension assumptions
530
+ # (works with no provider) so degrade still surfaces something.
531
+ spec_ledger_fold_prd_observations || true
532
+
533
+ spec_ledger_rebuild_md || true
534
+
535
+ local counts total high
536
+ counts="$(spec_ledger_counts)"
537
+ total="${counts%% *}"
538
+ high="${counts##* }"
539
+ if [ "${total:-0}" != "0" ]; then
540
+ log_info "Spec interrogation recorded ${total} assumption(s) (${high} high-severity) under .loki/assumptions/."
541
+ fi
542
+ return 0
543
+ }
544
+
545
+ # Allow direct execution for debugging: bash autonomy/spec-interrogation.sh <spec>
546
+ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
547
+ spec_interrogation_run "${1:-}"
548
+ exit $?
549
+ fi
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.45.1"
10
+ __version__ = "7.47.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: