loki-mode 7.93.0 → 7.94.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/autonomy/run.sh CHANGED
@@ -13383,7 +13383,7 @@ except (json.JSONDecodeError, KeyError, TypeError, OSError):
13383
13383
  ITERATION_COUNT=0
13384
13384
  fi
13385
13385
  ;;
13386
- failed|max_iterations_reached|max_retries_exceeded|exited|council_approved|council_force_approved|completion_promise_fulfilled)
13386
+ failed|max_iterations_reached|max_retries_exceeded|exited|council_approved|council_force_approved|completion_promise_fulfilled|reuse_already_satisfied)
13387
13387
  log_info "Previous session ended with status: $prev_status. Resetting for new session."
13388
13388
  RETRY_COUNT=0
13389
13389
  ITERATION_COUNT=0
@@ -14859,6 +14859,40 @@ if os.path.exists(pending_path):
14859
14859
  existing_ids = {t.get("id") for t in existing if isinstance(t, dict)}
14860
14860
  added = 0
14861
14861
 
14862
+ # Reuse done-recognition (v7.94.0): if a satisfied-requirements manifest exists
14863
+ # AND its prd_sha matches THIS PRD's hash, skip any feature whose title is
14864
+ # already satisfied, so an incremental reuse run rebuilds ONLY the gap. A stale
14865
+ # or absent manifest is ignored (full build -- the safe default). The sha is
14866
+ # computed over the SAME PRD file bytes the gate hashed (hashlib.sha256), so the
14867
+ # guard matches byte-for-byte. Title match is normalized (case-insensitive, with
14868
+ # a leading "feature:/requirement:/epic:/story:" heading prefix stripped) so a
14869
+ # model-returned title like "User login" matches the parser's heading title
14870
+ # "Feature: User login". bash 3.2 safe: all normalization happens in python.
14871
+ def _dr_norm_title(s):
14872
+ s = (s or "").strip().lower()
14873
+ for _pfx in ("feature:", "requirement:", "epic:", "story:", "user story:"):
14874
+ if s.startswith(_pfx):
14875
+ s = s[len(_pfx):].strip()
14876
+ break
14877
+ return s
14878
+
14879
+ _dr_satisfied = set()
14880
+ try:
14881
+ import hashlib
14882
+ _dr_manifest = ".loki/state/satisfied-requirements.json"
14883
+ if os.path.isfile(_dr_manifest):
14884
+ with open(_dr_manifest, "r") as _mf:
14885
+ _dr_data = json.load(_mf)
14886
+ _dr_manifest_sha = (_dr_data.get("prd_sha") or "").strip()
14887
+ with open(prd_path, "rb") as _pf:
14888
+ _dr_cur_sha = hashlib.sha256(_pf.read()).hexdigest()
14889
+ if _dr_manifest_sha and _dr_manifest_sha == _dr_cur_sha:
14890
+ for _t in _dr_data.get("satisfied", []):
14891
+ if isinstance(_t, str) and _t.strip():
14892
+ _dr_satisfied.add(_dr_norm_title(_t))
14893
+ except Exception:
14894
+ _dr_satisfied = set()
14895
+
14862
14896
  # BUG-V63-001 fix: extract audience once with flag to break both loops
14863
14897
  audience = "a user"
14864
14898
  audience_found = False
@@ -14878,6 +14912,12 @@ for i, feat in enumerate(features):
14878
14912
  if task_id in existing_ids:
14879
14913
  continue
14880
14914
 
14915
+ # Skip features the done-recognition gate verified as already satisfied
14916
+ # (manifest-driven, title-keyed, normalized match). Only unmet requirements
14917
+ # become tasks, so the RARV loop works only the gap.
14918
+ if _dr_satisfied and _dr_norm_title(feat.get("title")) in _dr_satisfied:
14919
+ continue
14920
+
14881
14921
  criteria = extract_acceptance_criteria(feat["section"], sections)
14882
14922
 
14883
14923
  # Build a rich description
@@ -15238,6 +15278,39 @@ except Exception:
15238
15278
  load_state
15239
15279
  local retry=$RETRY_COUNT
15240
15280
 
15281
+ # Reuse done-recognition gate (v7.94.0). On a no-PRD run that is REUSING an
15282
+ # already-generated PRD, model-verify whether the codebase already satisfies
15283
+ # that spec BEFORE rebuilding a task queue and re-running the RARV loop.
15284
+ # Routes to one of three outcomes (the verdict is the model's, grounded in
15285
+ # re-run tests + code; the only deterministic shortcut is NEGATIVE -> build):
15286
+ # done -> refresh the verified-completion record, finalize, return 0
15287
+ # so the queue/loop is skipped and main()'s terminal block
15288
+ # finishes the run (no wasted iterations, no stray delegate
15289
+ # branch -- this runs BEFORE the start-sha/delegate block).
15290
+ # incomplete -> write .loki/state/satisfied-requirements.json so
15291
+ # populate_prd_queue builds ONLY the unsatisfied items, then
15292
+ # fall through to the (now incremental) build.
15293
+ # inconclusive-> fall through to the normal full build (safe default).
15294
+ # Default-on; LOKI_DONE_RECOGNITION=0 disables it. Armed only on a reuse of
15295
+ # an existing generated PRD; `update` (stale PRD) may never fast-stop as done.
15296
+ case "${GENERATED_PRD_ACTION:-}" in
15297
+ reuse|user_owned|update)
15298
+ local _done_recog_lib="$SCRIPT_DIR/lib/done-recognition.sh"
15299
+ if [ -f "$_done_recog_lib" ]; then
15300
+ # shellcheck source=lib/done-recognition.sh
15301
+ source "$_done_recog_lib" 2>/dev/null || true
15302
+ if declare -f reuse_done_recognition_gate >/dev/null 2>&1; then
15303
+ if reuse_done_recognition_gate "$prd_path"; then
15304
+ # done verdict: the gate finalized; main()'s terminal
15305
+ # block (run_autonomous's caller) runs the COMPLETED
15306
+ # marker, proof-of-run, and HANDOFF.md.
15307
+ return 0
15308
+ fi
15309
+ fi
15310
+ fi
15311
+ ;;
15312
+ esac
15313
+
15241
15314
  # Capture run-start SHA for the evidence hard gate (v7.19.1).
15242
15315
  # Fresh-run-aware: recapture HEAD when ITERATION_COUNT==0 (fresh invocation,
15243
15316
  # reset, or corrupted/missing baseline); preserve only on a genuine resume
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.93.0"
10
+ __version__ = "7.94.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.93.0
5
+ **Version:** v7.94.0
6
6
 
7
7
  ---
8
8
 
@@ -0,0 +1,536 @@
1
+ # PRD-Reuse Done-Recognition Gate -- Implementation Plan
2
+
3
+ Architect design. DESIGN ONLY: no production code in this doc, one file created
4
+ (this plan). Read-only against the engine. No emojis. No em dashes.
5
+
6
+ Repo: /Users/lokesh/git/loki-mode
7
+ Feature: when a no-PRD run reuses a previously generated PRD, model-verify
8
+ whether the codebase already satisfies that PRD before rebuilding a task queue
9
+ and re-running the RARV loop. If verified done, finish through the normal
10
+ completion path and tell the user clearly. If not, build only the unsatisfied
11
+ requirements. If inconclusive, build (safe default). Never fake-green.
12
+
13
+ ---
14
+
15
+ ## 1. The bug, restated as a control-flow gap
16
+
17
+ On a no-PRD run over a project Loki already built and completed, the auto-detect
18
+ block in `run_autonomous()` (autonomy/run.sh:15140-15217) calls
19
+ `decide_generated_prd_action()` (autonomy/run.sh:5590). When the codebase
20
+ signature matches the stored one it returns `reuse`, and the block does exactly:
21
+
22
+ reuse) ... prd_path="$_gen_prd" ;;
23
+
24
+ Control then falls straight through to `populate_prd_queue "$prd_path"`
25
+ (autonomy/run.sh:15439), which extracts all `prd-*` features from the PRD
26
+ (autonomy/run.sh:14580, dedup only by `existing_ids` at 14878), and into the
27
+ main RARV loop. Nothing on the `reuse` path ever asks "is this reused PRD
28
+ already satisfied by the current codebase?" So a genuinely-done project gets a
29
+ fresh 11-task queue plus new iteration-N work, re-running RARV over completed
30
+ work. The prior run had already written `.loki/signals/COMPLETION_REQUESTED`,
31
+ `.loki/state/completion.json`, and `completion-evidence.md`; the reuse path
32
+ ignores all of it.
33
+
34
+ The fix is a single localized gate between the action decision and the
35
+ queue/loop, that re-verifies ground truth with the model and routes to one of
36
+ three outcomes.
37
+
38
+ ---
39
+
40
+ ## 2. Integration point (exact function + line region)
41
+
42
+ Two-part, both inside `run_autonomous()`:
43
+
44
+ ### 2a. Set up the gate inputs in the existing decision block (15140-15217)
45
+ No structural change. The block already computes `GENERATED_PRD_ACTION`,
46
+ `_gen_prd`, and `_prd_date`. We add nothing here except (optionally) reading the
47
+ gate's outcome variable later. Leave 15140-15217 byte-stable for the static
48
+ prompt prefix; the action variable continues to be set once per run.
49
+
50
+ ### 2b. Add the gate as a self-contained function called AFTER load_state,
51
+ AFTER the restarted-run snapshot, and BEFORE the delegate-branch/start-sha block
52
+ and queue build.
53
+
54
+ Verified line ordering inside `run_autonomous()`:
55
+ - 15238 `load_state` (15239 already reads `ITERATION_COUNT` right after).
56
+ - ~15248 start-sha capture (`_start_sha_file`, `mkdir .loki/state`).
57
+ - 15259 delegate-branch block (`LOKI_DELEGATE_BRANCH`, spawns `loki/delegate-*`).
58
+ - 15313-15319 restarted-run snapshot + export.
59
+ - 15328-15331 `LOKI_TRUST_RUN_ID` minted (`record_trust_event_bash run_start`).
60
+ - 15439 `populate_prd_queue`.
61
+
62
+ Required call site: BETWEEN `load_state` (15238) and the start-sha / delegate
63
+ block (~15248-15259). At that window `ITERATION_COUNT` is live (15239 reads it,
64
+ so the gate's fresh-vs-resume disclosure works) AND neither the start-sha capture
65
+ nor the delegate branch has run yet, so a verified-done project never spawns a
66
+ stray `loki/delegate-*` branch or moves the diff window. Placing the gate at
67
+ 15319 (after the delegate block) would defeat that, which is why the call site is
68
+ the pre-15248 window, not the restarted-snapshot line.
69
+
70
+ One ordering tradeoff to fold in at edit time: `LOKI_TRUST_RUN_ID` is minted at
71
+ 15328-15331, AFTER this window. If the done path records a trust event, either
72
+ mint the run id inside the gate or hoist 15328-15331 above the gate. Prefer
73
+ hoisting the mint (15328-15331) to just after `load_state` so both the gate and
74
+ the existing call site see a stable id; verify no other consumer depends on its
75
+ current position before moving it.
76
+
77
+ Why not 15218 with a bare `exit 0`: 15218 is before `load_state`, so
78
+ `ITERATION_COUNT` is not yet restored, and a bare `exit` skips main's terminal
79
+ finalization (section 6.1). The gate must instead return through `run_autonomous`
80
+ so `main()` runs its terminal block.
81
+
82
+ - Before `populate_prd_queue` (15439): a done verdict must short-circuit the
83
+ ENTIRE queue-build + loop, and an incomplete verdict must hand the queue
84
+ builder its "only-unsatisfied" filter before it runs.
85
+
86
+ New function name: `reuse_done_recognition_gate` (call once, run-scoped).
87
+ The function returns control by either (a) running the council-parity
88
+ finalization subset and `return 0` from `run_autonomous`, so `main()`'s terminal
89
+ block (18595-18629) finishes the run (done -- see section 6.1), or (b) writing a
90
+ satisfied-requirements manifest and falling through (incomplete), or (c) doing
91
+ nothing and falling through (inconclusive / fast-path build).
92
+
93
+ ---
94
+
95
+ ## 3. Which GENERATED_PRD_ACTION values arm the gate
96
+
97
+ The gate runs only on a no-PRD run that is reusing an existing generated PRD:
98
+
99
+ - `reuse` -> ARM the gate. Canonical bug case (codebase unchanged).
100
+ - `user_owned` -> ARM the gate. Codebase unchanged, PRD hand-edited; same
101
+ "already built" situation. The model verifies against the
102
+ hand-edited requirements.
103
+ - `update` -> ARM the gate, but FAST-STOP DISABLED. Codebase changed
104
+ since the PRD, so the PRD is stale by definition (that is why
105
+ the action is `update`). Judging "already done?" against a
106
+ spec that is about to be rewritten is a false-stop risk, so on
107
+ the `update` path the gate may resolve ONLY to incomplete or
108
+ inconclusive -- never to a fast-stop `done`. Its value here is
109
+ exclusively the incremental-queue case: mark which still-valid
110
+ requirements are met so the rebuild touches only the gap. A
111
+ model `done` on the `update` path is treated as inconclusive
112
+ (fall through to build / normal update flow).
113
+ - `generate` -> DO NOT arm. First run / forced regen (LOKI_PRD_REGEN). There
114
+ is no prior PRD to be "already satisfied"; maps to the
115
+ negative fast-path (build).
116
+
117
+ Guard condition at the call site:
118
+
119
+ case "${GENERATED_PRD_ACTION:-}" in
120
+ reuse|user_owned|update) reuse_done_recognition_gate "$prd_path" ;;
121
+ esac
122
+
123
+ Only `reuse` and `user_owned` (codebase unchanged) may terminate as a
124
+ fast-stop `done`. `update` may resolve only to incomplete/inconclusive. For
125
+ `reuse`/`user_owned`, "incomplete" is still possible (user deleted code, a
126
+ requirement regressed). The model decides the verdict; the action selects whether
127
+ to arm AND whether a `done` verdict is allowed to fast-stop.
128
+
129
+ ---
130
+
131
+ ## 4. Fast-path hints (cheap, EXCLUSIVELY negative)
132
+
133
+ Before any model call, evaluate cheap deterministic signals. CRITICAL CONSTRAINT
134
+ (requirements 1 and 3): the fast path may only ever short-circuit toward BUILD
135
+ (skip the model, fall through to queue). There is NO deterministic
136
+ "checklist all-verified -> stop" shortcut. A positive done verdict is ALWAYS the
137
+ model's, grounded in re-verified reality. The very bug shows the deterministic
138
+ checklist artifact was stale/desynced (14-vs-19 discrepancy), so it can never be
139
+ trusted as a positive done signal.
140
+
141
+ Negative fast-path: skip the model and fall through to BUILD when ground-truth
142
+ "surely not done" signals hold, e.g.:
143
+ - No `.loki/signals/COMPLETION_REQUESTED` AND no `.loki/state/completion.json`
144
+ AND no `.loki/checklist/checklist.json`: the project was never completed by a
145
+ prior run; there is nothing plausibly done. Build. (Cheapest, most common
146
+ miss-avoidance: never pay for a model call on a project with zero completion
147
+ footprint.)
148
+ - Provider unavailable / degraded (`_loki_done_recog_provider_ok` mirrors
149
+ `_loki_prd_enrich_provider_ok`, autonomy/lib/prd-enrich.sh:65): cannot
150
+ model-verify -> inconclusive -> build (never assert done offline).
151
+ - Explicit opt-out (LOKI_REUSE_DONE_RECOG=0): build.
152
+
153
+ These are hints/inputs only. Their PRESENCE (a completion signal exists) does NOT
154
+ short-circuit to done; it merely makes the model call worthwhile. The model still
155
+ re-runs tests and re-checks each requirement.
156
+
157
+ ---
158
+
159
+ ## 5. Model-intelligence verification design
160
+
161
+ Mirror the proven, mockable single-call pattern of prd-enrich
162
+ (autonomy/lib/prd-enrich.sh). New library: `autonomy/lib/done-recognition.sh`,
163
+ sourced at the gate call site the same way prd-enrich.sh is sourced at
164
+ autonomy/run.sh:14931-14937.
165
+
166
+ ### 5.1 The single model-call primitive (the ONE provider touch, mockable)
167
+
168
+ _loki_done_recog_invoke <prompt> # echoes the raw model response
169
+
170
+ Implementation copies `_loki_prd_enrich_invoke` (autonomy/lib/prd-enrich.sh:43)
171
+ verbatim in shape: `command -v claude`, `timeout "${LOKI_DONE_RECOG_TIMEOUT}"`,
172
+ `claude --dangerously-skip-permissions -p "$prompt"`, return 1 on any
173
+ nonzero/empty. Tests stub THIS function to return canned JSON, exactly as
174
+ prd-enrich tests stub `_loki_prd_enrich_invoke`. This is the injection seam that
175
+ makes a bash+model gate deterministically testable.
176
+
177
+ Bounds (mirror prd-enrich):
178
+ : "${LOKI_DONE_RECOG_TIMEOUT:=180}"
179
+ : "${LOKI_DONE_RECOG_MAX_PRD_CHARS:=16000}"
180
+ : "${LOKI_DONE_RECOG_MAX_TEST_CHARS:=4000}"
181
+
182
+ ### 5.2 Ground-truth re-verification BEFORE the model call
183
+
184
+ The model must judge against re-run reality, not artifacts alone. Before
185
+ building the prompt, the gate captures fresh ground truth:
186
+
187
+ 1. Tests: reuse `ensure_completion_test_evidence` (autonomy/run.sh:9094) which
188
+ calls `enforce_test_coverage` (autonomy/run.sh:8595) and persists
189
+ `.loki/quality/test-results.json`. This is the SAME evidence axis the
190
+ completion council/evidence gate reads, so the gate cannot reach a verdict
191
+ that contradicts the council. `enforce_test_coverage` is CWD-safe (it anchors
192
+ every path on `${TARGET_DIR:-.}` internally, confirmed at 8595-8607), so it
193
+ is safe at this pre-loop site. Swallow rc with `|| true` (red tests are data,
194
+ not a crash). If no runner exists, the file records `runner:none` and the
195
+ test axis is honestly inconclusive (feeds "inconclusive -> build" unless the
196
+ model can establish done by code inspection alone, which it should treat
197
+ conservatively).
198
+
199
+ 2. Requirements list: derive from the reused PRD (`$prd_path`, i.e.
200
+ `.loki/generated-prd.md`). Cap to LOKI_DONE_RECOG_MAX_PRD_CHARS.
201
+
202
+ 3. Existing completion evidence (as INPUT/hint only): the prior
203
+ `completion-evidence.md`, `.loki/state/completion.json`, and
204
+ `.loki/checklist/checklist.json` are passed to the model as context labeled
205
+ "PRIOR CLAIMS, possibly stale -- verify against the code, do not trust."
206
+
207
+ ### 5.3 The prompt and the structured return
208
+
209
+ The prompt instructs the model to: read the PRD requirements; for EACH
210
+ requirement, inspect the actual code (and the fresh test-results.json) and
211
+ decide whether it is met NOW; treat all prior Loki artifacts as unverified
212
+ claims; and return ONLY a JSON object (no prose, no fences), defensively parsed
213
+ (slice first `{` to last `}`, mirroring prd-enrich's array slice at
214
+ prd-enrich.sh:326):
215
+
216
+ {
217
+ "verdict": "done" | "incomplete" | "inconclusive",
218
+ "summary": "<one sentence for the user>",
219
+ "tests": { "passed": <int>, "total": <int>, "green": true|false },
220
+ "requirements": [
221
+ { "id": "<stable id or title slug>",
222
+ "title": "<requirement>",
223
+ "status": "met" | "unmet" | "uncertain",
224
+ "evidence": "<file:line or test name proving it>" }
225
+ ]
226
+ }
227
+
228
+ Verdict rules enforced in the prompt AND re-derived defensively in bash/python
229
+ (never trust the model's top-line verdict blindly):
230
+ - `done` requires ALL requirements `met` AND tests green in the fresh
231
+ test-results.json (or `runner:none` with the model citing concrete code
232
+ evidence for every requirement). If the model says `done` but the fresh
233
+ test-results.json shows red or any requirement is `unmet`/`uncertain`, the gate
234
+ DOWNGRADES to `incomplete` (or `inconclusive`). The trust decision is grounded
235
+ in re-verified reality, so a model overclaim cannot fake-green.
236
+ - `incomplete` when one or more requirements are `unmet` (with at least one
237
+ `met`, or none).
238
+ - `inconclusive` when the model could not establish ground truth (parse failure,
239
+ empty response, all `uncertain`, provider error). Falls to build.
240
+
241
+ This satisfies requirement 1 (model judgment, not a hardcoded checklist rule),
242
+ requirement 3 (trust moat: done is model-verified against re-run tests + code,
243
+ never asserted from a stale artifact; inconclusive -> build), and keeps the
244
+ verdict consistent with the council's evidence axis.
245
+
246
+ ---
247
+
248
+ ## 6. Outcome routing
249
+
250
+ ### 6.1 done -> finish through the normal completion path (NOT a bare exit)
251
+
252
+ A bare `exit 0` would skip finalization and risk leaving status/dashboard in a
253
+ "running" state with no fresh trust evidence. Instead, REFRESH the verified
254
+ completion record and exit through the same machinery a normal finish uses.
255
+
256
+ Verified exit path. The gate runs INSIDE `run_autonomous()` (the pre-loop
257
+ window in section 2b). `main()` calls `run_autonomous "$PRD_PATH"` at
258
+ autonomy/run.sh:18515 (`|| result=$?`) and then UNCONDITIONALLY runs its terminal
259
+ finalization at 18595-18629 -- `_advance_current_phase "COMPLETED"`, the COMPLETED
260
+ marker (18604-18606), proof-of-run (18597-18598), and HANDOFF.md (18614-18629).
261
+ That terminal block lives in `main()`, NOT in `run_autonomous()`. So when the
262
+ gate `return`s from `run_autonomous` (with or without entering the loop), control
263
+ returns to `main()` at 18515 and the terminal finalization runs exactly once.
264
+ This is the mechanism that makes step 3's receipt promise real -- a pre-loop
265
+ `return` reaches the terminal because the terminal is in the caller, not in the
266
+ loop body.
267
+
268
+ Concretely, on `done` the gate:
269
+ 1. Writes a fresh verified-completion record reflecting the NOW re-run results.
270
+ The fresh `.loki/quality/test-results.json` is already on disk. Reuse the
271
+ standalone completion-summary writer `build_completion_summary`
272
+ (autonomy/run.sh:3026), which emits both `.loki/state/completion.json`
273
+ (3196-3210) and `completion-evidence.md` -- the SAME writer the council
274
+ approval path uses (called from completion-council.sh:3211). Confirm it is
275
+ callable standalone from the pre-loop site (it reads git/state, not loop
276
+ locals); if it requires loop-set vars, pass them explicitly or factor the
277
+ write. The per-requirement `requirements[]` array is recorded as the evidence
278
+ body, so the record is reconciled-and-refreshed, not a parallel fabricated one.
279
+ 2. Runs the same finalization subset the council approval path runs (mirroring
280
+ autonomy/run.sh:17688-17704), then RETURNS from `run_autonomous` so `main()`'s
281
+ terminal block (18595-18629) does the COMPLETED marker, phase-advance, proof,
282
+ and handoff. To avoid double-writing the COMPLETED marker / phase, the gate
283
+ should do the council-parity pieces that the main terminal does NOT
284
+ (`council_write_report`, `run_memory_consolidation`, `on_run_complete`,
285
+ `emit_completion_summary complete`, `save_state ... reuse_already_satisfied`)
286
+ and let main own `_advance_current_phase "COMPLETED"` + the COMPLETED marker
287
+ (18604-18606). Verify at edit time which subset main already covers so neither
288
+ is run twice:
289
+ type council_write_report &>/dev/null && council_write_report
290
+ run_memory_consolidation
291
+ on_run_complete # autonomy/run.sh:3516 (optional PR/summary/ping)
292
+ emit_completion_summary complete # autonomy/run.sh:3367
293
+ save_state ${RETRY_COUNT:-0} "reuse_already_satisfied" 0
294
+ return 0 # back to main() -> terminal finalization at 18595-18629
295
+ Do NOT `exit`. Do NOT enter the loop. A `return 0` (clean) is correct here; a
296
+ `return 2` is unnecessary because there is no loop to stop -- the pre-loop
297
+ return already skips queue-build and iteration.
298
+
299
+ Alternative (if `build_completion_summary` proves loop-coupled): have the gate
300
+ only write the COMPLETED marker + refreshed completion.json + advance phase,
301
+ then `return 0`; main's `is_completed()` check (17205) is INSIDE the loop and
302
+ so is irrelevant on the pre-loop return -- the loop never runs. Either way the
303
+ single guaranteed terminal is main's 18595-18629. Pick the
304
+ `build_completion_summary`-reuse route if it is standalone-safe (preferred:
305
+ one writer, no divergence); fall back to the marker-only route otherwise.
306
+ 3. The proof-of-run + HANDOFF.md generation (autonomy/run.sh:18597-18629) runs in
307
+ main's terminal block after `run_autonomous` returns, so the user gets a
308
+ receipt with the honest re-run headline.
309
+
310
+ This is the "interacts with completion machinery without contradicting it"
311
+ requirement: we reconcile WITH the prior evidence, refreshed against re-run
312
+ reality, and exit through the one true completion path. No double-gating: the
313
+ council never runs because the loop never starts; the gate produced the same
314
+ kind of evidence the council would have read.
315
+
316
+ ### 6.2 incomplete -> incremental queue (only unsatisfied requirements)
317
+
318
+ `populate_prd_queue` (14580) builds ALL features; its only filter is the
319
+ `existing_ids` dedup at 14878. To make it incremental we add ONE read-point:
320
+
321
+ - The gate writes a satisfied-requirements manifest:
322
+ `.loki/state/satisfied-requirements.json`:
323
+
324
+ {
325
+ "prd_sha": "<hash of the PRD the verdict was computed against>",
326
+ "generated_at": "<iso8601>",
327
+ "satisfied": ["prd-001 title or stable id", "..."],
328
+ "source": "reuse-done-recognition"
329
+ }
330
+
331
+ Identity is by the same requirement key the queue builder uses to mint task
332
+ ids (`prd-NNN` is positional at 14877; to be robust, key on the feature TITLE
333
+ and let the manifest store titles, matched case-insensitively in the builder).
334
+
335
+ - `populate_prd_queue` reads the manifest once (guarded: only when it exists AND
336
+ its `prd_sha` matches the current PRD hash, so a stale manifest is ignored) and
337
+ SKIPS any feature whose title is in `satisfied[]`, alongside the existing
338
+ `existing_ids` skip at 14878:
339
+
340
+ if task_title_satisfied(feat["title"]): # new, manifest-driven
341
+ continue
342
+
343
+ Result: only unmet requirements become `prd-*` tasks; the RARV loop works only
344
+ the remaining gap, not a from-scratch rebuild. If the manifest is absent or
345
+ stale, the builder behaves exactly as today (full build) -- safe default.
346
+
347
+ - Disclosure: the gate logs "N of M requirements already satisfied; building
348
+ only the K unmet (<titles>). Pass --fresh-prd to rebuild from scratch."
349
+
350
+ Then the gate falls through (no exit); the run proceeds into queue-build and the
351
+ loop normally, now scoped to the gap.
352
+
353
+ ### 6.3 inconclusive -> build (safe default, never falsely done)
354
+
355
+ Do nothing: write no manifest, no completion. Log "Could not verify whether the
356
+ project already satisfies its spec (<reason>); proceeding to build." Control
357
+ falls through to the normal full queue-build + loop. This is the prd-enrich
358
+ graceful-fallback contract (provider missing / timeout / unparsable ->
359
+ deterministic fall-through), applied to the done decision. NEVER declare done on
360
+ inconclusive.
361
+
362
+ ---
363
+
364
+ ## 7. User-facing messaging (enterprise UX, requirement 2)
365
+
366
+ - done:
367
+ "This project already satisfies its spec. Verified N/N requirements met and
368
+ tests green (re-ran the suite now). Nothing to build. To rebuild from scratch
369
+ run `loki start --fresh-prd`; to extend it, edit the spec or pass a new/changed
370
+ PRD." (emitted via log + emit_completion_summary complete.)
371
+ Risk-mitigating clarity: a user who genuinely WANTS to extend a done project
372
+ must immediately see the two escape hatches (--fresh-prd, new/changed spec).
373
+ Make that the last line of the done message so it is unmissable.
374
+
375
+ - incomplete:
376
+ "This project partially satisfies its spec: K of M requirements still need
377
+ work (<short list>). Building only those. (Pass --fresh-prd to rebuild
378
+ everything.)"
379
+
380
+ - inconclusive:
381
+ "Could not confirm whether the existing code already satisfies the reused spec
382
+ (<reason: provider unavailable / tests inconclusive / unparsable verdict>).
383
+ Proceeding to build to be safe."
384
+
385
+ All strings: plain text, no emojis, no em dashes (repo convention).
386
+
387
+ ---
388
+
389
+ ## 8. Interaction with existing council / completion machinery (no double-gate)
390
+
391
+ - On `done`: the loop never starts, so council_should_stop / council_vote /
392
+ council_evidence_gate never run. The gate produces the SAME evidence those
393
+ gates read (fresh test-results.json + completion.json), so there is no
394
+ contradictory or parallel notion of "done." The completion finalization
395
+ subset it runs is the SAME one the council approval path runs
396
+ (run.sh:17688-17704), so dashboards/markers/memory stay consistent.
397
+ - On `incomplete`/`inconclusive`: the gate adds no completion state; the normal
398
+ loop, council, and evidence gate are fully in force for the (possibly reduced)
399
+ remaining work. No behavior change to those paths.
400
+ - The fast-path uses the council's artifacts (COMPLETION_REQUESTED,
401
+ completion.json, checklist.json) only as NEGATIVE inputs (their absence ->
402
+ build), never as a positive done shortcut, so it cannot inherit the stale-
403
+ artifact bug.
404
+
405
+ ---
406
+
407
+ ## 9. Test strategy (deterministic bash + mocked model)
408
+
409
+ New test: `tests/test-reuse-done-recognition.sh`, style mirroring the prd-enrich
410
+ and stop-scoping tests (bash, set -u, ok/bad counters, mktemp -d under /tmp with
411
+ trap-rm on every exit, no network, no real agent). The model call is injected by
412
+ overriding `_loki_done_recog_invoke` to echo canned JSON, exactly as prd-enrich
413
+ tests override `_loki_prd_enrich_invoke`.
414
+
415
+ Cases:
416
+ - T1 done verdict + green test-results.json -> gate routes to completion:
417
+ assert `.loki/COMPLETED` written, `.loki/state/completion.json` refreshed,
418
+ NO `.loki/queue/pending.json` prd-* tasks built, normal-terminal return.
419
+ - T2 model says done but fresh test-results.json is RED -> DOWNGRADE: assert NOT
420
+ done (incomplete/inconclusive), no COMPLETED marker (no fake-green).
421
+ - T3 model says done but a requirement is `unmet` -> downgrade to incomplete;
422
+ assert satisfied-requirements manifest excludes the unmet one.
423
+ - T4 incomplete -> manifest written with the met titles; stub populate read-point
424
+ test asserts only unmet features become tasks (feed a 3-feature PRD, mark 2
425
+ satisfied, assert 1 task).
426
+ - T5 inconclusive (invoke returns nonzero / empty / unparsable) -> no manifest,
427
+ no completion, falls through to full build.
428
+ - T6 negative fast-path: no COMPLETION_REQUESTED + no completion.json + no
429
+ checklist -> model NOT called (assert the stub recorded zero calls), full
430
+ build.
431
+ - T7 provider not ok (stub `_loki_done_recog_provider_ok` false) -> inconclusive
432
+ fast-path, build, model NOT called.
433
+ - T8 action gating: GENERATED_PRD_ACTION=generate -> gate not armed (no model
434
+ call); reuse/user_owned/update -> armed.
435
+ - T9 manifest staleness: prd_sha mismatch -> populate_prd_queue ignores manifest
436
+ and does a full build (safe default).
437
+ - T10 no-emoji/no-em-dash static grep over the new lib and the new strings.
438
+
439
+ Register in scripts/local-ci.sh (mirror the prd-enrich registration):
440
+ run_check "tests/test-reuse-done-recognition.sh (reuse done-recognition gate)" \
441
+ "bash tests/test-reuse-done-recognition.sh 2>&1 | tail -3"
442
+
443
+ Sourceability: the gate logic lives in `autonomy/lib/done-recognition.sh` (like
444
+ prd-enrich.sh), which is independently sourceable in the test without sourcing
445
+ run.sh. The `populate_prd_queue` read-point change is tested by exercising
446
+ populate_prd_queue in a temp project (it is already invoked in integration-style
447
+ tests) or by awk-extracting the python heredoc filter, defaulting to the
448
+ sourceable-lib approach for the gate proper.
449
+
450
+ ---
451
+
452
+ ## 10. Rollout
453
+
454
+ - Phase 1 (one release): env-gated DEFAULT-ON with a documented opt-out, so the
455
+ intelligent default ships immediately (requirement 5: unified default, not a
456
+ new knob) but operators retain an escape hatch:
457
+ LOKI_REUSE_DONE_RECOG=1 # default (on)
458
+ LOKI_REUSE_DONE_RECOG=0 # opt out (legacy reuse-then-build behavior)
459
+ No new user-facing CLI flag is added; --fresh-prd / LOKI_PRD_REGEN already
460
+ exist as the rebuild escape hatch and are surfaced in every gate message.
461
+ - Because the safe default on any uncertainty is BUILD (the legacy behavior), the
462
+ blast radius of a wrong gate is bounded: worst case it behaves like today.
463
+ - After one release of field evidence (proof-of-run + trust metrics show the gate
464
+ is correctly recognizing done without false-done), drop the env gate to
465
+ hard-on (keep `=0` opt-out for one more release, then remove).
466
+
467
+ ---
468
+
469
+ ## 11. Risks and mitigations
470
+
471
+ - R1 (highest, requirement 2): a genuinely-done project the user WANTS to extend.
472
+ Mitigation: the done message's final line names BOTH escape hatches
473
+ (`--fresh-prd` to rebuild, edit/pass a new spec to extend). The gate only
474
+ fires on a NO-PRD reuse run; the moment the user supplies a changed/new PRD,
475
+ the user-PRD path (found_prd at 15159, or persist_user_prd) takes precedence
476
+ and the gate's reuse arming does not apply.
477
+ - R2 false-done (fake-green): mitigated by re-running tests NOW
478
+ (ensure_completion_test_evidence) and DOWNGRADING any model `done` that the
479
+ fresh test-results.json or an `unmet`/`uncertain` requirement contradicts. The
480
+ positive verdict is never sourced from a stale artifact.
481
+ - R3 false-incomplete (rebuilds something already done): bounded -- worst case is
482
+ today's behavior (it rebuilds). The incremental manifest only ever REDUCES
483
+ work; if the model is unsure it marks `uncertain` (not satisfied) and that
484
+ requirement is rebuilt. No regression vs status quo.
485
+ - R4 model cost on every reuse run: the negative fast-path skips the call
486
+ entirely on projects with no completion footprint (the common case for a fresh
487
+ reuse that is clearly not done is rare; the common reuse over a built project
488
+ pays one bounded call). Timeout-bounded (LOKI_DONE_RECOG_TIMEOUT). On non-
489
+ claude/degraded providers the call is skipped (inconclusive -> build).
490
+ - R5 manifest/PRD identity drift: manifest is guarded by `prd_sha`; a mismatch is
491
+ ignored and a full build runs. Keying on feature title (not positional
492
+ prd-NNN) avoids index-shift misskips.
493
+ - R6 finalization ordering: the gate runs after load_state and before the
494
+ delegate-branch/start-sha block, so a done verdict never spawns a delegate
495
+ branch or moves the diff window. Verify final line placement at edit time
496
+ against the live ordering of 15238 (load_state), the delegate block (~15259),
497
+ and 15439 (populate_prd_queue).
498
+
499
+ ---
500
+
501
+ ## 12. Files touched (for the eventual implementation, not this doc)
502
+
503
+ - autonomy/lib/done-recognition.sh (NEW): `_loki_done_recog_invoke`,
504
+ `_loki_done_recog_provider_ok`, `reuse_done_recognition_gate`, payload/parse
505
+ python heredocs. Mirrors autonomy/lib/prd-enrich.sh.
506
+ - autonomy/run.sh:
507
+ - new call site after ~15319 (source the lib like 14931-14937; arm the gate per
508
+ section 3 guard).
509
+ - populate_prd_queue (14580): one manifest read-point + per-feature skip near
510
+ 14878 (section 6.2).
511
+ - reuse the existing finalization functions (3367, 3516, 1653, 13190, 17692)
512
+ and completion-summary writer `build_completion_summary` (3026, which writes
513
+ 3196-3210) on the done path -- no new completion code.
514
+ - tests/test-reuse-done-recognition.sh (NEW).
515
+ - scripts/local-ci.sh: register the new test.
516
+ - CHANGELOG.md + version-bump locations: per the repo's standard bump list (see
517
+ the prior reuse design's section 9 for the canonical 14-location list).
518
+ - Docs: a short note in the no-PRD reuse documentation that a reuse run now
519
+ verifies whether the project is already done and stops fast if so.
520
+
521
+ ---
522
+
523
+ ## 13. Divergences from the prior design (loki-plan/v7734-prd-reuse-design.md)
524
+
525
+ That doc designed the reuse/update/generate DECISION and the codebase signature.
526
+ It is now implemented (decide_generated_prd_action at run.sh:5590). This plan
527
+ builds the next layer ON TOP of its `reuse` outcome:
528
+ - The prior doc's `reuse` ends at "point prd_path at the generated PRD and run."
529
+ This plan inserts the done-recognition gate between that and the queue/loop.
530
+ - The prior doc relied on a deterministic signature to decide reuse-vs-update.
531
+ This plan deliberately does NOT add any deterministic done-shortcut; the
532
+ done/incomplete TRUST decision is the model's, grounded in re-run tests + code,
533
+ precisely because the signature/checklist artifacts proved untrustworthy as a
534
+ done signal (the 14-vs-19 checklist desync in the live bug). The signature
535
+ still does its job (reuse-vs-update); the new gate adds the satisfied-or-not
536
+ judgment the signature was never meant to make.