loki-mode 7.87.0 → 7.89.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/crash.sh +57 -30
- package/autonomy/lib/own-render.py +617 -0
- package/autonomy/lib/proof-generator.py +27 -1
- package/autonomy/lib/wiki-generator.py +192 -4
- package/autonomy/loki +183 -36
- package/autonomy/run.sh +419 -34
- package/autonomy/spec-interrogation.sh +51 -5
- package/autonomy/telemetry.sh +89 -12
- package/bin/loki +89 -8
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +213 -0
- package/dashboard/static/assets/mermaid.min.js +2030 -0
- package/dashboard/static/index.html +314 -182
- package/dashboard/telemetry.py +62 -15
- package/docs/INSTALLATION.md +2 -2
- package/docs/PRIVACY.md +65 -38
- package/loki-ts/dist/loki.js +249 -243
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/autonomy/run.sh
CHANGED
|
@@ -592,6 +592,84 @@ if [ -f "${SCRIPT_DIR}/app-runner.sh" ]; then
|
|
|
592
592
|
source "${SCRIPT_DIR}/app-runner.sh"
|
|
593
593
|
fi
|
|
594
594
|
|
|
595
|
+
# Build-time HOME isolation (F49).
|
|
596
|
+
#
|
|
597
|
+
# When Loki executes/tests the GENERATED app during a build (the app-runner
|
|
598
|
+
# server launch, restarts, and the project's own test suite in
|
|
599
|
+
# enforce_test_coverage), the child process inherits Loki's environment --
|
|
600
|
+
# including the user's REAL $HOME. A generated app that defaults its state file
|
|
601
|
+
# to a HOME-relative path (e.g. a todo CLI writing ~/.todo.json) would then
|
|
602
|
+
# litter the user's home directory with Loki's in-build test data. These two
|
|
603
|
+
# helpers give those in-build executions an isolated HOME/XDG/TMPDIR rooted
|
|
604
|
+
# under .loki/ so the app can run normally but cannot write into the real home.
|
|
605
|
+
#
|
|
606
|
+
# Scope: ONLY Loki's own in-build test executions are sandboxed. The user's
|
|
607
|
+
# later manual `npm start`/run of the app is unaffected -- they invoke it
|
|
608
|
+
# themselves in their own shell with their own HOME.
|
|
609
|
+
|
|
610
|
+
# Lazily create and echo a persistent isolated HOME directory under .loki.
|
|
611
|
+
# Persistent (not a per-call mktemp) so an app launched in one iteration and
|
|
612
|
+
# restarted in a later one keeps a stable home, and so its state survives
|
|
613
|
+
# across the build the same way a real run would.
|
|
614
|
+
_loki_app_sandbox_dir() {
|
|
615
|
+
local _base="${TARGET_DIR:-.}/.loki/app-sandbox"
|
|
616
|
+
if [ ! -d "$_base/home" ]; then
|
|
617
|
+
mkdir -p "$_base/home" "$_base/config" "$_base/data" \
|
|
618
|
+
"$_base/cache" "$_base/state" "$_base/tmp" 2>/dev/null || return 1
|
|
619
|
+
fi
|
|
620
|
+
printf '%s\n' "$_base"
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# Run a command with the build-time app sandbox env applied, then restore the
|
|
624
|
+
# previous environment. Works for both shell functions (app_runner_start) and
|
|
625
|
+
# external commands because it only mutates env vars around the call. The
|
|
626
|
+
# background app launched by app_runner_start captures the sandbox HOME at fork
|
|
627
|
+
# time and keeps it for its lifetime; restoring here ensures the orchestrator
|
|
628
|
+
# (and the next iteration's provider invocation, which needs the REAL HOME for
|
|
629
|
+
# OAuth/credentials) is never left pointing at the sandbox.
|
|
630
|
+
_loki_with_app_sandbox() {
|
|
631
|
+
local _sb
|
|
632
|
+
_sb=$(_loki_app_sandbox_dir 2>/dev/null) || _sb=""
|
|
633
|
+
# If the sandbox could not be created, run unsandboxed rather than failing
|
|
634
|
+
# the build -- correctness of the app run takes priority over isolation.
|
|
635
|
+
if [ -z "$_sb" ]; then
|
|
636
|
+
"$@"
|
|
637
|
+
return $?
|
|
638
|
+
fi
|
|
639
|
+
# Resolve to an absolute path so child `cd` into the target dir cannot make
|
|
640
|
+
# a relative HOME point somewhere unexpected.
|
|
641
|
+
local _abs
|
|
642
|
+
_abs=$(cd "$_sb" 2>/dev/null && pwd) || _abs="$_sb"
|
|
643
|
+
|
|
644
|
+
# Save current values plus their set/unset state so restore is exact.
|
|
645
|
+
local _had_home="${HOME+x}" _old_home="${HOME:-}"
|
|
646
|
+
local _had_xch="${XDG_CONFIG_HOME+x}" _old_xch="${XDG_CONFIG_HOME:-}"
|
|
647
|
+
local _had_xdh="${XDG_DATA_HOME+x}" _old_xdh="${XDG_DATA_HOME:-}"
|
|
648
|
+
local _had_xcache="${XDG_CACHE_HOME+x}" _old_xcache="${XDG_CACHE_HOME:-}"
|
|
649
|
+
local _had_xsh="${XDG_STATE_HOME+x}" _old_xsh="${XDG_STATE_HOME:-}"
|
|
650
|
+
local _had_tmp="${TMPDIR+x}" _old_tmp="${TMPDIR:-}"
|
|
651
|
+
|
|
652
|
+
export HOME="$_abs/home"
|
|
653
|
+
export XDG_CONFIG_HOME="$_abs/config"
|
|
654
|
+
export XDG_DATA_HOME="$_abs/data"
|
|
655
|
+
export XDG_CACHE_HOME="$_abs/cache"
|
|
656
|
+
export XDG_STATE_HOME="$_abs/state"
|
|
657
|
+
export TMPDIR="$_abs/tmp"
|
|
658
|
+
|
|
659
|
+
local _rc=0
|
|
660
|
+
"$@" || _rc=$?
|
|
661
|
+
|
|
662
|
+
# Restore exactly (re-export prior value, or unset if it was unset before).
|
|
663
|
+
if [ -n "$_had_home" ]; then export HOME="$_old_home"; else unset HOME; fi
|
|
664
|
+
if [ -n "$_had_xch" ]; then export XDG_CONFIG_HOME="$_old_xch"; else unset XDG_CONFIG_HOME; fi
|
|
665
|
+
if [ -n "$_had_xdh" ]; then export XDG_DATA_HOME="$_old_xdh"; else unset XDG_DATA_HOME; fi
|
|
666
|
+
if [ -n "$_had_xcache" ]; then export XDG_CACHE_HOME="$_old_xcache"; else unset XDG_CACHE_HOME; fi
|
|
667
|
+
if [ -n "$_had_xsh" ]; then export XDG_STATE_HOME="$_old_xsh"; else unset XDG_STATE_HOME; fi
|
|
668
|
+
if [ -n "$_had_tmp" ]; then export TMPDIR="$_old_tmp"; else unset TMPDIR; fi
|
|
669
|
+
|
|
670
|
+
return "$_rc"
|
|
671
|
+
}
|
|
672
|
+
|
|
595
673
|
# Playwright Smoke Test module (v5.46.0)
|
|
596
674
|
if [ -f "${SCRIPT_DIR}/playwright-verify.sh" ]; then
|
|
597
675
|
# shellcheck source=playwright-verify.sh
|
|
@@ -4641,6 +4719,49 @@ check_notification_triggers() {
|
|
|
4641
4719
|
# Task Queue Auto-Tracking (for degraded mode providers)
|
|
4642
4720
|
#===============================================================================
|
|
4643
4721
|
|
|
4722
|
+
# Derive a plain-language, honest summary of the spec this run is building from,
|
|
4723
|
+
# for the dashboard iteration card (Task G). Mirrors the "Building:" start
|
|
4724
|
+
# headline (run.sh:17061) and proof-generator's source resolution so the card,
|
|
4725
|
+
# the banner, and the proof artifact all describe the same real intent.
|
|
4726
|
+
#
|
|
4727
|
+
# Echoes two tab-separated fields: <human spec label>\t<spec kind>, where kind is
|
|
4728
|
+
# one of: brief | prd | codebase-analysis. The label is plain English with no
|
|
4729
|
+
# RARV jargon. Best-effort and never fails (used only to enrich a card).
|
|
4730
|
+
_loki_iteration_spec_summary() {
|
|
4731
|
+
local prd="${1:-}"
|
|
4732
|
+
local label="" kind=""
|
|
4733
|
+
|
|
4734
|
+
# 1. A recorded one-line brief (`loki start "<brief>"`) is the user's own
|
|
4735
|
+
# words; show them verbatim, truncated to stay on one tidy line.
|
|
4736
|
+
if [ -f ".loki/state/brief.txt" ]; then
|
|
4737
|
+
local _brief
|
|
4738
|
+
_brief="$(tr '\n' ' ' < .loki/state/brief.txt 2>/dev/null | sed 's/ */ /g; s/^ //; s/ $//')"
|
|
4739
|
+
if [ -n "$_brief" ]; then
|
|
4740
|
+
if [ "${#_brief}" -gt 80 ]; then
|
|
4741
|
+
_brief="${_brief:0:77}..."
|
|
4742
|
+
fi
|
|
4743
|
+
printf '%s\t%s' "$_brief" "brief"
|
|
4744
|
+
return 0
|
|
4745
|
+
fi
|
|
4746
|
+
fi
|
|
4747
|
+
|
|
4748
|
+
# 2. A generated PRD means there was no user spec; we are reverse-engineering
|
|
4749
|
+
# one from the code. Say exactly that rather than naming the internal file.
|
|
4750
|
+
case "$prd" in
|
|
4751
|
+
""|*.loki/generated-prd.md|*.loki/generated-prd.json)
|
|
4752
|
+
label="the codebase (no spec provided)"
|
|
4753
|
+
kind="codebase-analysis"
|
|
4754
|
+
;;
|
|
4755
|
+
*)
|
|
4756
|
+
# 3. A real user PRD / spec file: name it by basename, no path noise.
|
|
4757
|
+
label="$(basename "$prd" 2>/dev/null || printf '%s' "$prd")"
|
|
4758
|
+
kind="prd"
|
|
4759
|
+
;;
|
|
4760
|
+
esac
|
|
4761
|
+
|
|
4762
|
+
printf '%s\t%s' "$label" "$kind"
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4644
4765
|
# Track iteration start - create task in in-progress queue
|
|
4645
4766
|
track_iteration_start() {
|
|
4646
4767
|
local iteration="$1"
|
|
@@ -4694,6 +4815,19 @@ except: pass
|
|
|
4694
4815
|
local prd_escaped
|
|
4695
4816
|
prd_escaped=$(printf '%s' "${prd:-Codebase Analysis}" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
4696
4817
|
|
|
4818
|
+
# Task G: derive a plain-language, honest summary of what this run is building
|
|
4819
|
+
# from, so the card title/description describe the real work instead of the
|
|
4820
|
+
# generic "Iteration N" / "RARV iteration N" placeholder. Best-effort.
|
|
4821
|
+
local _spec_label="" _spec_kind=""
|
|
4822
|
+
local _spec_summary
|
|
4823
|
+
_spec_summary=$(_loki_iteration_spec_summary "$prd")
|
|
4824
|
+
_spec_label="${_spec_summary%%$'\t'*}"
|
|
4825
|
+
_spec_kind="${_spec_summary##*$'\t'}"
|
|
4826
|
+
# Escape for safe embedding in the python -c literals below.
|
|
4827
|
+
local _spec_label_esc _spec_kind_esc
|
|
4828
|
+
_spec_label_esc=$(printf '%s' "$_spec_label" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
4829
|
+
_spec_kind_esc=$(printf '%s' "$_spec_kind" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
4830
|
+
|
|
4697
4831
|
# Build enriched task JSON with pending task context.
|
|
4698
4832
|
# Must initialize to empty: this script runs under `set -u` (line 152),
|
|
4699
4833
|
# so `local task_json` without a value leaves it unset. When the pending
|
|
@@ -4704,25 +4838,33 @@ except: pass
|
|
|
4704
4838
|
task_json=$(python3 -c "
|
|
4705
4839
|
import json, sys
|
|
4706
4840
|
ctx = json.loads('''$next_task_context''')
|
|
4707
|
-
#
|
|
4708
|
-
#
|
|
4709
|
-
#
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4841
|
+
# Task G: the card must describe the REAL work in plain language, not the
|
|
4842
|
+
# generic 'Iteration N' / 'RARV iteration N' placeholder. Prefer the pending
|
|
4843
|
+
# PRD task's own title/description/criteria. When those are absent, fall back to
|
|
4844
|
+
# a plain-language summary derived from the spec this run is building from, and
|
|
4845
|
+
# OMIT acceptance criteria rather than show RARV phase-name boilerplate (only
|
|
4846
|
+
# state what is really known).
|
|
4847
|
+
spec_label = '${_spec_label_esc}'
|
|
4848
|
+
spec_kind = '${_spec_kind_esc}'
|
|
4849
|
+
if spec_kind == 'codebase-analysis':
|
|
4850
|
+
fallback_title = 'Analyzing the codebase and generating a spec'
|
|
4851
|
+
fallback_desc = 'Reading the existing code to reverse-engineer a spec, then building against it.'
|
|
4852
|
+
elif spec_kind == 'brief':
|
|
4853
|
+
fallback_title = 'Building: ' + spec_label
|
|
4854
|
+
fallback_desc = 'Building from your brief: ' + spec_label
|
|
4855
|
+
else:
|
|
4856
|
+
fallback_title = 'Building from ' + spec_label
|
|
4857
|
+
fallback_desc = 'Implementing the spec in ' + spec_label + ' and verifying it.'
|
|
4716
4858
|
task = {
|
|
4717
4859
|
'id': 'iteration-$iteration',
|
|
4718
4860
|
'type': 'iteration',
|
|
4719
|
-
'title': ctx.get('current_task') or
|
|
4720
|
-
'description': ctx.get('description') or
|
|
4861
|
+
'title': ctx.get('current_task') or fallback_title,
|
|
4862
|
+
'description': ctx.get('description') or fallback_desc,
|
|
4721
4863
|
'status': 'in_progress',
|
|
4722
4864
|
'priority': 'medium',
|
|
4723
4865
|
'startedAt': '$(date -u +%Y-%m-%dT%H:%M:%SZ)',
|
|
4724
4866
|
'provider': '${PROVIDER_NAME:-claude}',
|
|
4725
|
-
'acceptance_criteria': ctx.get('acceptance_criteria') or
|
|
4867
|
+
'acceptance_criteria': ctx.get('acceptance_criteria') or [],
|
|
4726
4868
|
'notes': [],
|
|
4727
4869
|
'logs': [{
|
|
4728
4870
|
'timestamp': '$(date -u +%Y-%m-%dT%H:%M:%SZ)',
|
|
@@ -4742,29 +4884,75 @@ print(json.dumps(task, indent=2))
|
|
|
4742
4884
|
" 2>/dev/null) || task_json=""
|
|
4743
4885
|
fi
|
|
4744
4886
|
|
|
4745
|
-
# Fallback
|
|
4887
|
+
# Fallback when there is no pending PRD task (codebase-analysis run or empty
|
|
4888
|
+
# queue). Task G: this was the worst placeholder card -- 'Iteration N' /
|
|
4889
|
+
# 'RARV iteration N' with RARV-phase-name acceptance criteria, which tells a
|
|
4890
|
+
# watching user nothing about the real work. Build an honest, plain-language
|
|
4891
|
+
# card from the spec summary instead, and omit acceptance criteria (we have no
|
|
4892
|
+
# real per-iteration criteria here -- do not invent RARV jargon).
|
|
4746
4893
|
if [[ -z "${task_json:-}" ]]; then
|
|
4747
4894
|
local _start_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
4895
|
+
task_json=$(python3 -c "
|
|
4896
|
+
import json
|
|
4897
|
+
spec_label = '${_spec_label_esc}'
|
|
4898
|
+
spec_kind = '${_spec_kind_esc}'
|
|
4899
|
+
if spec_kind == 'codebase-analysis':
|
|
4900
|
+
title = 'Analyzing the codebase and generating a spec'
|
|
4901
|
+
desc = 'Reading the existing code to reverse-engineer a spec, then building against it.'
|
|
4902
|
+
elif spec_kind == 'brief':
|
|
4903
|
+
title = 'Building: ' + spec_label
|
|
4904
|
+
desc = 'Building from your brief: ' + spec_label
|
|
4905
|
+
else:
|
|
4906
|
+
title = 'Building from ' + spec_label
|
|
4907
|
+
desc = 'Implementing the spec in ' + spec_label + ' and verifying it.'
|
|
4908
|
+
task = {
|
|
4909
|
+
'id': '$task_id',
|
|
4910
|
+
'type': 'iteration',
|
|
4911
|
+
'title': title,
|
|
4912
|
+
'description': desc,
|
|
4913
|
+
'status': 'in_progress',
|
|
4914
|
+
'priority': 'medium',
|
|
4915
|
+
'startedAt': '$_start_ts',
|
|
4916
|
+
'provider': '${PROVIDER_NAME:-claude}',
|
|
4917
|
+
'acceptance_criteria': [],
|
|
4918
|
+
'notes': [],
|
|
4919
|
+
'logs': [{
|
|
4920
|
+
'timestamp': '$_start_ts',
|
|
4921
|
+
'iteration': $iteration,
|
|
4922
|
+
'level': 'info',
|
|
4923
|
+
'phase': 'BOOTSTRAP',
|
|
4924
|
+
'message': 'Iteration $iteration started'
|
|
4925
|
+
}]
|
|
4926
|
+
}
|
|
4927
|
+
print(json.dumps(task, indent=2))
|
|
4928
|
+
" 2>/dev/null) || task_json=""
|
|
4929
|
+
fi
|
|
4930
|
+
|
|
4931
|
+
# Last-resort safety net if python is unavailable. Still avoids the generic
|
|
4932
|
+
# 'RARV iteration' wording and omits boilerplate acceptance criteria.
|
|
4933
|
+
if [[ -z "${task_json:-}" ]]; then
|
|
4934
|
+
local _start_ts2="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
4935
|
+
local _safe_title
|
|
4936
|
+
case "$_spec_kind" in
|
|
4937
|
+
codebase-analysis) _safe_title="Analyzing the codebase and generating a spec" ;;
|
|
4938
|
+
brief) _safe_title="Building from your brief" ;;
|
|
4939
|
+
*) _safe_title="Building from the spec" ;;
|
|
4940
|
+
esac
|
|
4748
4941
|
task_json=$(cat <<EOF
|
|
4749
4942
|
{
|
|
4750
4943
|
"id": "$task_id",
|
|
4751
4944
|
"type": "iteration",
|
|
4752
|
-
"title": "
|
|
4753
|
-
"description": "
|
|
4945
|
+
"title": "$_safe_title",
|
|
4946
|
+
"description": "$_safe_title (iteration $iteration).",
|
|
4754
4947
|
"status": "in_progress",
|
|
4755
4948
|
"priority": "medium",
|
|
4756
|
-
"startedAt": "$
|
|
4949
|
+
"startedAt": "$_start_ts2",
|
|
4757
4950
|
"provider": "${PROVIDER_NAME:-claude}",
|
|
4758
|
-
"acceptance_criteria": [
|
|
4759
|
-
"REASON phase identifies next task without errors",
|
|
4760
|
-
"ACT phase produces verifiable artifacts (code/docs/tests)",
|
|
4761
|
-
"REFLECT phase records progress in CONTINUITY.md",
|
|
4762
|
-
"VERIFY phase passes automated tests / quality gates"
|
|
4763
|
-
],
|
|
4951
|
+
"acceptance_criteria": [],
|
|
4764
4952
|
"notes": [],
|
|
4765
4953
|
"logs": [
|
|
4766
4954
|
{
|
|
4767
|
-
"timestamp": "$
|
|
4955
|
+
"timestamp": "$_start_ts2",
|
|
4768
4956
|
"iteration": $iteration,
|
|
4769
4957
|
"level": "info",
|
|
4770
4958
|
"phase": "BOOTSTRAP",
|
|
@@ -7907,9 +8095,57 @@ sys.stdout.write(t.strip())
|
|
|
7907
8095
|
# unit-tests.pass is only read for the status-line display (run.sh ~2183,
|
|
7908
8096
|
# PASS vs PENDING); keeping the touch preserves the historical
|
|
7909
8097
|
# non-blocking behavior for legitimate no-test projects.
|
|
8098
|
+
#
|
|
8099
|
+
# F56/F53 (verification-gap honesty): a generated project that shipped
|
|
8100
|
+
# source but no runnable tests previously recorded a bare "not_run" and
|
|
8101
|
+
# the gate passed through silently -- so a real logic bug (F53: todo-id
|
|
8102
|
+
# numbering) reached the receipt unverified, and a TESTING.md that
|
|
8103
|
+
# DESCRIBES tests shipped with NONE executed (F56: test docs without test
|
|
8104
|
+
# execution). We do not scaffold+run tests here (generating correct test
|
|
8105
|
+
# code for an arbitrary project is the agent's job, not the gate's, and
|
|
8106
|
+
# would overreach a pass/fail gate). Instead we make the no-runner record
|
|
8107
|
+
# HONEST about the gap: if the project has generated source, the gap is
|
|
8108
|
+
# "source present, no runnable tests"; if a TESTING.md also claims tests,
|
|
8109
|
+
# the gap is the stronger "test docs without execution" mismatch. The
|
|
8110
|
+
# record stays non-blocking (runner=="none" short-circuits the evidence
|
|
8111
|
+
# gate as before; no infinite hang), but the receipt and the operator now
|
|
8112
|
+
# SEE the gap instead of a silent pass, and "no tests" never reads as
|
|
8113
|
+
# "tests verified".
|
|
8114
|
+
local _vgap="none" _vgap_summary="No test runner detected"
|
|
8115
|
+
local _has_src=false _has_testdoc=false
|
|
8116
|
+
# Generated source present? Look only at top-of-tree, skipping vendored /
|
|
8117
|
+
# tooling dirs, so this is cheap and never walks node_modules.
|
|
8118
|
+
if find "${TARGET_DIR:-.}" -maxdepth 3 -type f \
|
|
8119
|
+
\( -name '*.py' -o -name '*.js' -o -name '*.ts' -o -name '*.jsx' \
|
|
8120
|
+
-o -name '*.tsx' -o -name '*.go' -o -name '*.rs' -o -name '*.java' \
|
|
8121
|
+
-o -name '*.rb' -o -name '*.php' \) \
|
|
8122
|
+
-not -path '*/node_modules/*' -not -path '*/.loki/*' \
|
|
8123
|
+
-not -path '*/.git/*' -not -path '*/vendor/*' -not -path '*/dist/*' \
|
|
8124
|
+
-not -path '*/build/*' -not -path '*/.venv/*' -not -path '*/venv/*' \
|
|
8125
|
+
-print -quit 2>/dev/null | grep -q .; then
|
|
8126
|
+
_has_src=true
|
|
8127
|
+
fi
|
|
8128
|
+
# TESTING.md (project root or .loki/docs) that documents tests despite no
|
|
8129
|
+
# runner -- the docs-without-execution mismatch.
|
|
8130
|
+
local _td
|
|
8131
|
+
for _td in "${TARGET_DIR:-.}/TESTING.md" "${TARGET_DIR:-.}/.loki/docs/TESTING.md"; do
|
|
8132
|
+
if [ -f "$_td" ] && grep -qiE 'test|coverage' "$_td" 2>/dev/null; then
|
|
8133
|
+
_has_testdoc=true
|
|
8134
|
+
break
|
|
8135
|
+
fi
|
|
8136
|
+
done
|
|
8137
|
+
if [ "$_has_testdoc" = "true" ]; then
|
|
8138
|
+
_vgap="test_docs_without_execution"
|
|
8139
|
+
_vgap_summary="TESTING.md documents tests but no runner ran any (unverified)"
|
|
8140
|
+
log_warn "Verification gap: TESTING.md describes tests but no test runner executed -- shipping test docs without test execution"
|
|
8141
|
+
elif [ "$_has_src" = "true" ]; then
|
|
8142
|
+
_vgap="source_without_tests"
|
|
8143
|
+
_vgap_summary="Source present but no runnable tests detected (unverified logic)"
|
|
8144
|
+
log_warn "Verification gap: generated source present but no runnable tests -- logic is unverified by execution"
|
|
8145
|
+
fi
|
|
7910
8146
|
touch "$quality_dir/unit-tests.pass"
|
|
7911
8147
|
cat > "$quality_dir/test-results.json" << TREOF
|
|
7912
|
-
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"none","pass":"inconclusive","summary":"
|
|
8148
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"none","pass":"inconclusive","summary":"$_vgap_summary","command":null,"exit_code":null,"status":"not_run","passed_count":null,"failed_count":null,"verification_gap":"$_vgap"}
|
|
7913
8149
|
TREOF
|
|
7914
8150
|
# Finding #598: stamp the per-iteration freshness marker so a later
|
|
7915
8151
|
# completion-route capture (ensure_completion_test_evidence) reuses this
|
|
@@ -7946,8 +8182,11 @@ TREOF
|
|
|
7946
8182
|
[ -n "$_tr_passed_n" ] || _tr_passed_n=null
|
|
7947
8183
|
[ -n "$_tr_failed_n" ] || _tr_failed_n=null
|
|
7948
8184
|
|
|
8185
|
+
# verification_gap is "none" whenever a real runner executed: the suite ran,
|
|
8186
|
+
# so there is no docs-without-execution / source-without-tests gap. Keeping
|
|
8187
|
+
# the key on BOTH writers gives consumers a single stable schema shape.
|
|
7949
8188
|
cat > "$quality_dir/test-results.json" << TREOF
|
|
7950
|
-
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"$test_runner","pass":$test_passed,"min_coverage":$min_coverage,"summary":"$details","command":"$_tr_cmd","exit_code":$_tr_exit,"status":"$_tr_status","passed_count":$_tr_passed_n,"failed_count":$_tr_failed_n}
|
|
8189
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"$test_runner","pass":$test_passed,"min_coverage":$min_coverage,"summary":"$details","command":"$_tr_cmd","exit_code":$_tr_exit,"status":"$_tr_status","passed_count":$_tr_passed_n,"failed_count":$_tr_failed_n,"verification_gap":"none"}
|
|
7951
8190
|
TREOF
|
|
7952
8191
|
# Finding #598: stamp the per-iteration freshness marker (see above).
|
|
7953
8192
|
printf '%s\n' "${ITERATION_COUNT:-0}" > "$quality_dir/.test-results.iter" 2>/dev/null || true
|
|
@@ -8177,7 +8416,9 @@ ensure_completion_test_evidence() {
|
|
|
8177
8416
|
[ -f "$_results_file" ] && _mtime_before=$(stat -f %m "$_results_file" 2>/dev/null || stat -c %Y "$_results_file" 2>/dev/null || echo "")
|
|
8178
8417
|
# The gate decides on the persisted file; a red suite (nonzero rc) is expected
|
|
8179
8418
|
# and must not abort the completion path here.
|
|
8180
|
-
|
|
8419
|
+
# F49: the project's test suite may exec the app, which can write into HOME;
|
|
8420
|
+
# run it under the isolated build-time HOME.
|
|
8421
|
+
_loki_with_app_sandbox enforce_test_coverage || true
|
|
8181
8422
|
mkdir -p "$quality_dir" 2>/dev/null || true
|
|
8182
8423
|
local _mtime_after=""
|
|
8183
8424
|
[ -f "$_results_file" ] && _mtime_after=$(stat -f %m "$_results_file" 2>/dev/null || stat -c %Y "$_results_file" 2>/dev/null || echo "")
|
|
@@ -11757,6 +11998,94 @@ ${_commits}"
|
|
|
11757
11998
|
}
|
|
11758
11999
|
|
|
11759
12000
|
|
|
12001
|
+
# Auto-wiki regeneration after an iteration (v7.88.2). Mirrors the
|
|
12002
|
+
# _intelligent_usage_regen contract: best-effort, NON-blocking, never fails the
|
|
12003
|
+
# iteration (every path returns 0; the caller also guards with `|| true`).
|
|
12004
|
+
#
|
|
12005
|
+
# Default-ON; opt out with LOKI_WIKI_AUTO=0. Off-TTY / CI safe: no prompts, no
|
|
12006
|
+
# stream-fighting, byte-identical behavior (no TTY checks, no interactive paths;
|
|
12007
|
+
# the generator is deterministic given the same codebase index).
|
|
12008
|
+
#
|
|
12009
|
+
# INCREMENTAL change-detection (E3): regenerating the wiki walks + hashes the
|
|
12010
|
+
# source set, which is wasted work on an iteration that did not change the
|
|
12011
|
+
# codebase structure. We avoid even spawning python on an unchanged repo with a
|
|
12012
|
+
# cheap bash-level signal: a hash over the source file list and their mtimes,
|
|
12013
|
+
# cached at .loki/wiki/.auto-hash. We only invoke the generator when that hash
|
|
12014
|
+
# differs from the cached value. The generator ALSO has its own content-hash
|
|
12015
|
+
# signature gate (wiki-manifest.json), so this is a fast pre-filter, not the
|
|
12016
|
+
# only guard -- if the cheap signal ever misfires, the generator still skips a
|
|
12017
|
+
# truly-unchanged codebase. The cheap signal uses find (file list + mtimes),
|
|
12018
|
+
# which is far cheaper than the generator's full byte-hash of every file.
|
|
12019
|
+
_auto_wiki_regen() {
|
|
12020
|
+
# Opt-out honored first so a disabled run does zero work.
|
|
12021
|
+
if [ "${LOKI_WIKI_AUTO:-1}" = "0" ]; then
|
|
12022
|
+
return 0
|
|
12023
|
+
fi
|
|
12024
|
+
local target_dir="${TARGET_DIR:-.}"
|
|
12025
|
+
# python3 is required for the generator; bail silently if absent.
|
|
12026
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
12027
|
+
return 0
|
|
12028
|
+
fi
|
|
12029
|
+
local gen="$SCRIPT_DIR/lib/wiki-generator.py"
|
|
12030
|
+
if [ ! -f "$gen" ]; then
|
|
12031
|
+
return 0
|
|
12032
|
+
fi
|
|
12033
|
+
|
|
12034
|
+
# Cheap structure signal: a hash over (path, mtime, size) of every source
|
|
12035
|
+
# file, excluding the usual noise dirs (and .loki itself, so writing the
|
|
12036
|
+
# wiki never changes the signal). Computed with a small stat-only python
|
|
12037
|
+
# walk -- portable across macOS/BSD and Linux (GNU vs BSD `find -printf`
|
|
12038
|
+
# diverge), and far cheaper than the generator's full byte-hash of every
|
|
12039
|
+
# file because it reads no file contents. python3 is already required above.
|
|
12040
|
+
local hash_file="$target_dir/.loki/wiki/.auto-hash"
|
|
12041
|
+
local cur_hash
|
|
12042
|
+
cur_hash=$(python3 - "$target_dir" <<'PY' 2>/dev/null || true
|
|
12043
|
+
import hashlib, os, sys
|
|
12044
|
+
root = sys.argv[1]
|
|
12045
|
+
SKIP = {"node_modules", ".git", ".loki", "dist", "build", "venv", ".venv",
|
|
12046
|
+
"__pycache__", "target", "vendor", ".next", ".cache", "out"}
|
|
12047
|
+
h = hashlib.sha256()
|
|
12048
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
12049
|
+
dirnames[:] = sorted(d for d in dirnames if d not in SKIP)
|
|
12050
|
+
for fn in sorted(filenames):
|
|
12051
|
+
p = os.path.join(dirpath, fn)
|
|
12052
|
+
try:
|
|
12053
|
+
st = os.stat(p)
|
|
12054
|
+
except OSError:
|
|
12055
|
+
continue
|
|
12056
|
+
rel = os.path.relpath(p, root)
|
|
12057
|
+
h.update(("%s|%d|%d\n" % (rel, int(st.st_mtime), st.st_size)).encode("utf-8", "replace"))
|
|
12058
|
+
sys.stdout.write(h.hexdigest())
|
|
12059
|
+
PY
|
|
12060
|
+
)
|
|
12061
|
+
if [ -n "$cur_hash" ] && [ -f "$hash_file" ]; then
|
|
12062
|
+
local prev_hash
|
|
12063
|
+
prev_hash=$(cat "$hash_file" 2>/dev/null || echo "")
|
|
12064
|
+
if [ "$cur_hash" = "$prev_hash" ]; then
|
|
12065
|
+
# Structure unchanged since the last wiki -- skip without spawning
|
|
12066
|
+
# the generator. No token burn on an unchanged repo.
|
|
12067
|
+
return 0
|
|
12068
|
+
fi
|
|
12069
|
+
fi
|
|
12070
|
+
|
|
12071
|
+
log_info "Auto-regenerating project wiki (codebase structure changed)..."
|
|
12072
|
+
# The generator is deterministic and self-incremental; --quiet keeps the
|
|
12073
|
+
# iteration log clean. Never let a wiki failure surface to the iteration.
|
|
12074
|
+
if python3 "$gen" --root "$target_dir" --quiet >/dev/null 2>&1; then
|
|
12075
|
+
# Record the cheap signal only on a successful generation so a failed
|
|
12076
|
+
# run is retried next iteration rather than silently skipped.
|
|
12077
|
+
if [ -n "$cur_hash" ]; then
|
|
12078
|
+
mkdir -p "$target_dir/.loki/wiki" 2>/dev/null || true
|
|
12079
|
+
printf '%s\n' "$cur_hash" > "$hash_file" 2>/dev/null || true
|
|
12080
|
+
fi
|
|
12081
|
+
log_info "Project wiki refreshed -> $target_dir/.loki/wiki/"
|
|
12082
|
+
else
|
|
12083
|
+
log_info "Auto-wiki regen skipped (generator returned non-zero); keeping existing wiki."
|
|
12084
|
+
fi
|
|
12085
|
+
return 0
|
|
12086
|
+
}
|
|
12087
|
+
|
|
12088
|
+
|
|
11760
12089
|
# Magic Modules COMPOUND: record successful component patterns (v6.77.0)
|
|
11761
12090
|
# Called at end of each iteration to capture generated/updated components
|
|
11762
12091
|
# as semantic memory patterns via magic.core.memory_bridge.
|
|
@@ -12465,6 +12794,24 @@ build_prompt() {
|
|
|
12465
12794
|
# and to the dashboard/Purple Lab UI.
|
|
12466
12795
|
local usage_doc_instruction="USAGE_DOC_REQUIRED: Before invoking loki_complete_task (or touching .loki/signals/COMPLETION_REQUESTED), write USAGE.md at the project root. Detect the stack from package.json/requirements.txt/Cargo.toml/go.mod/etc. and include these sections: (1) Prerequisites (runtimes, ports, env vars), (2) Install (exact command, e.g. 'npm install' or 'pip install -r requirements.txt'), (3) Start (exact command, e.g. 'npm start' or 'python server.py'), (4) Verify -- 2 to 3 copy-paste commands the user can run to confirm it works (curl examples for APIs with expected output, browser URL for web UIs, command invocation for CLIs), (5) Stop (Ctrl+C or 'lsof -ti:PORT | xargs kill -9' for backgrounded servers). Keep it under 100 lines, plain Markdown, no emojis. If USAGE.md already exists and is accurate, leave it; otherwise create or update it."
|
|
12467
12796
|
|
|
12797
|
+
# DOC_SCOPE instruction (F52): scale generated documentation to the detected
|
|
12798
|
+
# project complexity. A trivial one-file app does not warrant a nine-file
|
|
12799
|
+
# architecture suite (ARCHITECTURE/COMPONENTS/DECISIONS/API/SETUP/TESTING) --
|
|
12800
|
+
# that is token and iteration burn with no reader. USAGE.md (above) and
|
|
12801
|
+
# HANDOFF.md (rendered post-completion) are written regardless of tier, so this
|
|
12802
|
+
# string only governs the OPTIONAL architecture suite. simple -> minimal set;
|
|
12803
|
+
# standard/complex -> full suite only where it genuinely helps a reader.
|
|
12804
|
+
# MUST stay byte-identical to DOC_SCOPE_INSTRUCTION_{SIMPLE,FULL} in
|
|
12805
|
+
# loki-ts/src/runner/build_prompt.ts (parity-locked, same precedent as
|
|
12806
|
+
# COMPOSE_INSTRUCTION). Tier is read from DETECTED_COMPLEXITY (set once per run
|
|
12807
|
+
# by run_autonomous before the first build_prompt); cache-stable within a run.
|
|
12808
|
+
local doc_scope_instruction
|
|
12809
|
+
if [ "${DETECTED_COMPLEXITY:-standard}" = "simple" ]; then
|
|
12810
|
+
doc_scope_instruction="DOC_SCOPE: This is a small, simple project. Keep documentation minimal and proportional: a short README.md (what it is, install, run) plus the required USAGE.md is sufficient. Do NOT generate a multi-file architecture documentation suite (ARCHITECTURE.md, COMPONENTS.md, DECISIONS.md, API.md, SETUP.md, TESTING.md) for a project this small -- it is overhead with no reader and wastes iterations. Only add one of those files if the code genuinely warrants it (e.g. write API.md only when there is a real public API surface). Never claim a doc exists that you did not write."
|
|
12811
|
+
else
|
|
12812
|
+
doc_scope_instruction="DOC_SCOPE: Scale documentation to what a reader of THIS project actually needs -- do not generate empty boilerplate. Beyond the required USAGE.md, write a README.md and add architecture-suite docs (ARCHITECTURE.md, COMPONENTS.md, DECISIONS.md, API.md, SETUP.md, TESTING.md) only where each one carries real, project-specific content (e.g. API.md only with a real public API, COMPONENTS.md only with multiple distinct components, DECISIONS.md only when there are non-obvious design decisions). Prefer fewer accurate docs over a complete-looking suite of stubs. Never claim a doc exists that you did not write."
|
|
12813
|
+
fi
|
|
12814
|
+
|
|
12468
12815
|
# v7.7.8: LSP grounding instruction. The lsp-proxy MCP server (auto-mounted
|
|
12469
12816
|
# when a language server is on PATH) exposes four tools that ground the
|
|
12470
12817
|
# agent in real workspace symbols instead of hallucinated names. Before
|
|
@@ -12883,15 +13230,15 @@ except Exception:
|
|
|
12883
13230
|
else
|
|
12884
13231
|
if [ $retry -eq 0 ]; then
|
|
12885
13232
|
if [ -n "$prd" ]; then
|
|
12886
|
-
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"
|
|
13233
|
+
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 $doc_scope_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
12887
13234
|
else
|
|
12888
|
-
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"
|
|
13235
|
+
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 $doc_scope_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
12889
13236
|
fi
|
|
12890
13237
|
else
|
|
12891
13238
|
if [ -n "$prd" ]; then
|
|
12892
|
-
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"
|
|
13239
|
+
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 $doc_scope_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
12893
13240
|
else
|
|
12894
|
-
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"
|
|
13241
|
+
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 $doc_scope_instruction $compose_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
12895
13242
|
fi
|
|
12896
13243
|
fi
|
|
12897
13244
|
fi
|
|
@@ -12933,6 +13280,7 @@ except Exception:
|
|
|
12933
13280
|
printf 'You are a coding assistant. Analyze this codebase and suggest improvements. Write working code and commit changes.\n'
|
|
12934
13281
|
fi
|
|
12935
13282
|
printf '%s\n' "$usage_doc_instruction"
|
|
13283
|
+
printf '%s\n' "$doc_scope_instruction"
|
|
12936
13284
|
printf '%s\n' "$compose_instruction"
|
|
12937
13285
|
printf '%s\n' "$lsp_grounding_instruction"
|
|
12938
13286
|
printf '%s\n' "$agents_md_instruction"
|
|
@@ -12967,6 +13315,7 @@ except Exception:
|
|
|
12967
13315
|
printf '%s\n' "$autonomous_suffix"
|
|
12968
13316
|
printf '%s\n' "$memory_instruction"
|
|
12969
13317
|
printf '%s\n' "$usage_doc_instruction"
|
|
13318
|
+
printf '%s\n' "$doc_scope_instruction"
|
|
12970
13319
|
printf '%s\n' "$compose_instruction"
|
|
12971
13320
|
printf '%s\n' "$lsp_grounding_instruction"
|
|
12972
13321
|
printf '%s\n' "$agents_md_instruction"
|
|
@@ -15343,7 +15692,9 @@ if __name__ == "__main__":
|
|
|
15343
15692
|
if [ "${APP_RUNNER_INITIALIZED:-}" != "true" ] && [ $exit_code -eq 0 ] && \
|
|
15344
15693
|
[ "${LOKI_APP_RUNNER:-true}" = "true" ] && type app_runner_init &>/dev/null; then
|
|
15345
15694
|
if app_runner_init; then
|
|
15346
|
-
|
|
15695
|
+
# F49: sandbox the generated app's HOME so its in-build run
|
|
15696
|
+
# cannot write into the user's real home directory.
|
|
15697
|
+
_loki_with_app_sandbox app_runner_start || log_warn "App runner: failed to start application"
|
|
15347
15698
|
APP_RUNNER_INITIALIZED=true
|
|
15348
15699
|
fi
|
|
15349
15700
|
fi
|
|
@@ -15353,7 +15704,8 @@ if __name__ == "__main__":
|
|
|
15353
15704
|
# App Runner: restart on code changes (v5.45.0)
|
|
15354
15705
|
if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_should_restart &>/dev/null; then
|
|
15355
15706
|
if app_runner_should_restart; then
|
|
15356
|
-
|
|
15707
|
+
# F49: re-launch under the same isolated HOME as the initial start.
|
|
15708
|
+
_loki_with_app_sandbox app_runner_restart || log_warn "App runner: failed to restart application"
|
|
15357
15709
|
fi
|
|
15358
15710
|
fi
|
|
15359
15711
|
|
|
@@ -15378,7 +15730,8 @@ if __name__ == "__main__":
|
|
|
15378
15730
|
if [ -f ".loki/app-runner/restart-signal" ]; then
|
|
15379
15731
|
rm -f ".loki/app-runner/restart-signal"
|
|
15380
15732
|
log_info "App runner: restart signal received from dashboard"
|
|
15381
|
-
|
|
15733
|
+
# F49: dashboard-triggered restart uses the isolated HOME too.
|
|
15734
|
+
_loki_with_app_sandbox app_runner_restart || true
|
|
15382
15735
|
fi
|
|
15383
15736
|
if [ -f ".loki/app-runner/stop-signal" ]; then
|
|
15384
15737
|
rm -f ".loki/app-runner/stop-signal"
|
|
@@ -15438,7 +15791,9 @@ if __name__ == "__main__":
|
|
|
15438
15791
|
# Test coverage gate
|
|
15439
15792
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
15440
15793
|
log_info "Quality gate: test suite (pass/fail)..."
|
|
15441
|
-
|
|
15794
|
+
# F49: isolate HOME so the project's suite cannot pollute the
|
|
15795
|
+
# user's real home when it execs the generated app.
|
|
15796
|
+
if _loki_with_app_sandbox enforce_test_coverage; then
|
|
15442
15797
|
clear_gate_failure "test_coverage"
|
|
15443
15798
|
else
|
|
15444
15799
|
local tc_count
|
|
@@ -15746,6 +16101,12 @@ else:
|
|
|
15746
16101
|
# Magic Modules COMPOUND capture (v6.77.0): record component patterns
|
|
15747
16102
|
_magic_compound_capture
|
|
15748
16103
|
|
|
16104
|
+
# Auto-wiki regeneration after every iteration (v7.88.2). Best-effort,
|
|
16105
|
+
# non-blocking, incremental (only regenerates when the codebase
|
|
16106
|
+
# structure changed). Default-on; opt out LOKI_WIKI_AUTO=0. The `|| true`
|
|
16107
|
+
# is belt-and-suspenders -- the function already returns 0 on every path.
|
|
16108
|
+
_auto_wiki_regen 2>/dev/null || true
|
|
16109
|
+
|
|
15749
16110
|
# BUG-QG-008: Track iteration for convergence regardless of exit code
|
|
15750
16111
|
if type council_track_iteration &>/dev/null; then
|
|
15751
16112
|
council_track_iteration "$log_file"
|
|
@@ -17367,6 +17728,30 @@ main() {
|
|
|
17367
17728
|
generate_proof_of_run "$result" || true
|
|
17368
17729
|
fi
|
|
17369
17730
|
|
|
17731
|
+
# Finish-and-own (v7.88.0): write a plain-English ownership handoff
|
|
17732
|
+
# (HANDOFF.md) for a non-technical owner. Runs AFTER the proof so the
|
|
17733
|
+
# "is it working?" verdict reads the receipt's honest headline. Default-on,
|
|
17734
|
+
# opt out with LOKI_HANDOFF=0. Fire-and-forget: best-effort, never blocks
|
|
17735
|
+
# completion (same contract as the proof + usage-regen). A pure render over
|
|
17736
|
+
# the proof + completion + USAGE.md, so it cannot fabricate.
|
|
17737
|
+
if [ "${LOKI_HANDOFF:-1}" != "0" ]; then
|
|
17738
|
+
local _own_render="$SCRIPT_DIR/lib/own-render.py"
|
|
17739
|
+
if [ -f "$_own_render" ] && command -v python3 >/dev/null 2>&1; then
|
|
17740
|
+
# The renderer prints the plain-English doc on stdout (--md); the hook
|
|
17741
|
+
# places it at the project root as HANDOFF.md. Write to a temp then
|
|
17742
|
+
# move, so a partial write never leaves a truncated HANDOFF.md.
|
|
17743
|
+
local _handoff_dir _handoff_md _handoff_tmp
|
|
17744
|
+
_handoff_dir="${TARGET_DIR:-.}"
|
|
17745
|
+
_handoff_md="$_handoff_dir/HANDOFF.md"
|
|
17746
|
+
_handoff_tmp="$_handoff_dir/.HANDOFF.md.tmp"
|
|
17747
|
+
if python3 "$_own_render" --loki-dir "$LOKI_DIR" --md > "$_handoff_tmp" 2>/dev/null; then
|
|
17748
|
+
mv -f "$_handoff_tmp" "$_handoff_md" 2>/dev/null || rm -f "$_handoff_tmp" 2>/dev/null || true
|
|
17749
|
+
else
|
|
17750
|
+
rm -f "$_handoff_tmp" 2>/dev/null || true
|
|
17751
|
+
fi
|
|
17752
|
+
fi
|
|
17753
|
+
fi
|
|
17754
|
+
|
|
17370
17755
|
# R7 (zero-config first run): "what next / go deeper" framing. Only when the
|
|
17371
17756
|
# CLI flagged this as a TTFV first run and stdout is a TTY, so it stays
|
|
17372
17757
|
# silent in CI / pipes and never fires for normal PRD runs. The wording
|