loki-mode 7.117.1 → 7.118.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/completion-council.sh +50 -3
- package/autonomy/hooks/migration-hooks.sh +296 -0
- package/autonomy/lib/proof-generator.py +91 -0
- package/autonomy/loki +11 -1
- package/autonomy/run.sh +45 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/skills/healing.md +19 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.118.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.118.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.118.0
|
|
@@ -92,6 +92,42 @@ COUNCIL_MIN_ITERATIONS=${LOKI_COUNCIL_MIN_ITERATIONS:-3}
|
|
|
92
92
|
if [ "$COUNCIL_MIN_ITERATIONS" -lt 1 ] 2>/dev/null; then
|
|
93
93
|
COUNCIL_MIN_ITERATIONS=1
|
|
94
94
|
fi
|
|
95
|
+
|
|
96
|
+
# _council_effective_min_iter (SaaS #122): resolve the effective MIN_ITERATIONS
|
|
97
|
+
# floor as a function of DETECTED_COMPLEXITY, computed at council-run time (NOT at
|
|
98
|
+
# source time -- DETECTED_COMPLEXITY is populated by run.sh:detect_complexity
|
|
99
|
+
# before the loop, but AFTER this file is sourced). The floor is a HARD gate in
|
|
100
|
+
# council_should_stop: while ITERATION_COUNT < floor the council is not allowed to
|
|
101
|
+
# even evaluate, so a genuinely-complete SIMPLE app (e.g. a small invoice app) that
|
|
102
|
+
# claims done at iteration 1 was still FORCED through ~2 extra idle iterations
|
|
103
|
+
# (~15min) before the council could approve. Lowering the floor to 1 for the
|
|
104
|
+
# `simple` tier removes only those FORCED idle iterations; it does NOT skip any
|
|
105
|
+
# verification. At floor=1, `ITERATION_COUNT=1` makes `1 -lt 1` false, so the
|
|
106
|
+
# council is ALLOWED to convene -- the evidence gate, checklist gate, aggregate
|
|
107
|
+
# vote, provenance, boot smoke and devil's advocate all still run and can still
|
|
108
|
+
# reject. Standard/complex keep floor 3.
|
|
109
|
+
#
|
|
110
|
+
# Precedence (the explicit-override case must win so a user who sets
|
|
111
|
+
# LOKI_COUNCIL_MIN_ITERATIONS=3 on a simple app is honored, not silently lowered):
|
|
112
|
+
# 1. explicit LOKI_COUNCIL_MIN_ITERATIONS env set (non-empty) -> that value
|
|
113
|
+
# (already clamped into COUNCIL_MIN_ITERATIONS above), verbatim.
|
|
114
|
+
# 2. DETECTED_COMPLEXITY == simple (default floor, no override) -> 1.
|
|
115
|
+
# 3. otherwise -> COUNCIL_MIN_ITERATIONS (the standard/complex default of 3).
|
|
116
|
+
# When DETECTED_COMPLEXITY is empty (unset) this returns COUNCIL_MIN_ITERATIONS --
|
|
117
|
+
# a no-op that preserves the historical standard-path behavior.
|
|
118
|
+
_council_effective_min_iter() {
|
|
119
|
+
# Explicit user override wins: read the RAW env var (COUNCIL_MIN_ITERATIONS
|
|
120
|
+
# collapses "user set 3" and "default 3" into one value and can't distinguish).
|
|
121
|
+
if [ -n "${LOKI_COUNCIL_MIN_ITERATIONS:-}" ]; then
|
|
122
|
+
printf '%s' "${COUNCIL_MIN_ITERATIONS}"
|
|
123
|
+
return 0
|
|
124
|
+
fi
|
|
125
|
+
if [ "${DETECTED_COMPLEXITY:-}" = "simple" ]; then
|
|
126
|
+
printf '%s' 1
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
printf '%s' "${COUNCIL_MIN_ITERATIONS}"
|
|
130
|
+
}
|
|
95
131
|
COUNCIL_CONVERGENCE_WINDOW=${LOKI_COUNCIL_CONVERGENCE_WINDOW:-3}
|
|
96
132
|
COUNCIL_STAGNATION_LIMIT=${LOKI_COUNCIL_STAGNATION_LIMIT:-5}
|
|
97
133
|
COUNCIL_DONE_SIGNAL_LIMIT=${LOKI_COUNCIL_DONE_SIGNAL_LIMIT:-10}
|
|
@@ -3470,7 +3506,10 @@ _council_should_check_now() {
|
|
|
3470
3506
|
local completion_claimed="${2:-false}"
|
|
3471
3507
|
local iter="${ITERATION_COUNT:-0}"
|
|
3472
3508
|
local interval="${COUNCIL_CHECK_INTERVAL:-5}"
|
|
3473
|
-
|
|
3509
|
+
# SaaS #122: tier-aware effective floor (simple->1) so the no-claim early
|
|
3510
|
+
# check can also fire at iteration 1 on a genuinely-done simple app.
|
|
3511
|
+
local min_iter
|
|
3512
|
+
min_iter="$(_council_effective_min_iter)"
|
|
3474
3513
|
|
|
3475
3514
|
# Circuit breaker and explicit-claim paths are UNCHANGED (they already bypass
|
|
3476
3515
|
# the interval); return check-now for them without touching the early logic.
|
|
@@ -3505,8 +3544,16 @@ council_should_stop() {
|
|
|
3505
3544
|
return 1 # Council disabled, don't stop
|
|
3506
3545
|
fi
|
|
3507
3546
|
|
|
3508
|
-
# Don't check before minimum iterations
|
|
3509
|
-
|
|
3547
|
+
# Don't check before minimum iterations. SaaS #122: the floor is tier-aware
|
|
3548
|
+
# (simple->1) via _council_effective_min_iter so a genuinely-complete simple
|
|
3549
|
+
# app that claims done at iteration 1 is not FORCED through ~2 extra idle
|
|
3550
|
+
# iterations before the council may evaluate. This only changes WHETHER the
|
|
3551
|
+
# council is allowed to convene early; every verification gate inside
|
|
3552
|
+
# council_evaluate still runs and can still reject. Standard/complex keep 3,
|
|
3553
|
+
# and an explicit LOKI_COUNCIL_MIN_ITERATIONS override is always honored.
|
|
3554
|
+
local _min_iter
|
|
3555
|
+
_min_iter="$(_council_effective_min_iter)"
|
|
3556
|
+
if [ "$ITERATION_COUNT" -lt "$_min_iter" ]; then
|
|
3510
3557
|
return 1
|
|
3511
3558
|
fi
|
|
3512
3559
|
|
|
@@ -652,6 +652,288 @@ with open(sys.argv[1], 'w') as f:
|
|
|
652
652
|
return 0
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
#===============================================================================
|
|
656
|
+
# RANK 4: Golden-master boundary-equivalence for heal/migrate
|
|
657
|
+
#
|
|
658
|
+
# The whole-suite exit-code check in hook_healing_phase_gate is necessary but not
|
|
659
|
+
# sufficient: a transform can keep unit tests green while ALTERING an observable
|
|
660
|
+
# boundary (CLI stdout, HTTP response, file/DB delta). Golden-master capture
|
|
661
|
+
# records the OBSERVED output of each detected boundary during archaeology; the
|
|
662
|
+
# modernize->validate gate re-runs each boundary and compares to that baseline
|
|
663
|
+
# PER-BOUNDARY. A changed boundary is BLOCKED unless a documented-intentional-
|
|
664
|
+
# change record exists for exactly that boundary.
|
|
665
|
+
#
|
|
666
|
+
# These are deterministic shell/python functions (NOT LLM calls), consistent with
|
|
667
|
+
# the file contract at the top: they run whether the agent cooperates or not.
|
|
668
|
+
#===============================================================================
|
|
669
|
+
|
|
670
|
+
# Enumerate the boundaries to golden-master. Sources of truth (all merged, first
|
|
671
|
+
# occurrence of a boundary id wins):
|
|
672
|
+
# 1. .loki/healing/boundaries.json -- canonical heal boundary manifest that
|
|
673
|
+
# archaeology is instructed to write (see the heal prompt). Array of
|
|
674
|
+
# {id, boundary_type, command} (or `characterization_test`).
|
|
675
|
+
# 2. .loki/healing/features.json -- the migrate-flow feature list; each
|
|
676
|
+
# entry's `characterization_test` is a reproducible boundary invocation.
|
|
677
|
+
# 3. .loki/healing/characterization-tests/*.json -- per-boundary characterization
|
|
678
|
+
# files (heal archaeology's tool output); any object carrying a runnable
|
|
679
|
+
# `command`/`characterization_test`/`cmd` field is a boundary.
|
|
680
|
+
# heal writes (1) and/or (3); migrate writes (2). Reading all three keeps the
|
|
681
|
+
# golden-master populated in BOTH real flows, not just the synthetic fixture.
|
|
682
|
+
# Emits one TSV line per boundary: <boundary_id>\t<invocation_command>
|
|
683
|
+
# Prints nothing (returns 0) when no boundaries are detectable -- callers must
|
|
684
|
+
# treat "no boundaries" as an honest degrade, never a green verdict.
|
|
685
|
+
_heal_enumerate_boundaries() {
|
|
686
|
+
local heal_dir="$1"
|
|
687
|
+
HEAL_DIR="$heal_dir" python3 -c "
|
|
688
|
+
import json, os, sys, glob
|
|
689
|
+
|
|
690
|
+
heal_dir = os.environ['HEAL_DIR']
|
|
691
|
+
seen = set()
|
|
692
|
+
out = []
|
|
693
|
+
|
|
694
|
+
def emit(bid, cmd):
|
|
695
|
+
if not cmd or not str(cmd).strip():
|
|
696
|
+
return
|
|
697
|
+
bid = str(bid).replace('\t', ' ').replace('\n', ' ').strip()
|
|
698
|
+
if not bid or bid in seen:
|
|
699
|
+
return
|
|
700
|
+
seen.add(bid)
|
|
701
|
+
out.append((bid, str(cmd).replace('\n', ' ').replace('\r', ' ')))
|
|
702
|
+
|
|
703
|
+
def cmd_of(it):
|
|
704
|
+
for k in ('command', 'characterization_test', 'cmd'):
|
|
705
|
+
v = it.get(k)
|
|
706
|
+
if v and str(v).strip():
|
|
707
|
+
return v
|
|
708
|
+
return None
|
|
709
|
+
|
|
710
|
+
def id_of(it, default='boundary'):
|
|
711
|
+
fid = it.get('id') or it.get('name') or it.get('description') or default
|
|
712
|
+
btype = it.get('boundary_type') or it.get('category') or it.get('type') or 'cli'
|
|
713
|
+
return '%s:%s' % (btype, fid)
|
|
714
|
+
|
|
715
|
+
def load(path):
|
|
716
|
+
try:
|
|
717
|
+
with open(path) as f:
|
|
718
|
+
return json.load(f)
|
|
719
|
+
except Exception:
|
|
720
|
+
return None
|
|
721
|
+
|
|
722
|
+
# (1) canonical heal boundary manifest
|
|
723
|
+
data = load(os.path.join(heal_dir, 'boundaries.json'))
|
|
724
|
+
if data is not None:
|
|
725
|
+
items = data if isinstance(data, list) else data.get('boundaries', data.get('features', []))
|
|
726
|
+
for it in items if isinstance(items, list) else []:
|
|
727
|
+
if isinstance(it, dict):
|
|
728
|
+
emit(id_of(it), cmd_of(it))
|
|
729
|
+
|
|
730
|
+
# (2) migrate feature list
|
|
731
|
+
data = load(os.path.join(heal_dir, 'features.json'))
|
|
732
|
+
if data is not None:
|
|
733
|
+
items = data if isinstance(data, list) else data.get('features', [])
|
|
734
|
+
for it in items if isinstance(items, list) else []:
|
|
735
|
+
if isinstance(it, dict):
|
|
736
|
+
emit(id_of(it), cmd_of(it))
|
|
737
|
+
|
|
738
|
+
# (3) per-boundary characterization files
|
|
739
|
+
for path in sorted(glob.glob(os.path.join(heal_dir, 'characterization-tests', '*.json'))):
|
|
740
|
+
data = load(path)
|
|
741
|
+
if data is None:
|
|
742
|
+
continue
|
|
743
|
+
items = data if isinstance(data, list) else [data]
|
|
744
|
+
for it in items:
|
|
745
|
+
if isinstance(it, dict) and cmd_of(it):
|
|
746
|
+
base = os.path.splitext(os.path.basename(path))[0]
|
|
747
|
+
emit(id_of(it, default=base), cmd_of(it))
|
|
748
|
+
|
|
749
|
+
for bid, cmd in out:
|
|
750
|
+
print('%s\t%s' % (bid, cmd))
|
|
751
|
+
" 2>/dev/null || true
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
# Stable, filesystem-safe filename for a boundary id's golden-master artifact.
|
|
755
|
+
_heal_boundary_artifact_name() {
|
|
756
|
+
local bid="$1"
|
|
757
|
+
# sha of the id keeps names stable + collision-free across weird chars.
|
|
758
|
+
local h
|
|
759
|
+
h=$(printf '%s' "$bid" | shasum 2>/dev/null | awk '{print $1}')
|
|
760
|
+
[[ -z "$h" ]] && h=$(printf '%s' "$bid" | cksum | awk '{print $1}')
|
|
761
|
+
printf 'boundary-%s.json' "$h"
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
# Run a single boundary invocation and capture OBSERVED output as a golden-master
|
|
765
|
+
# artifact. Captures stdout (the compared channel) + exit code + the reproduction
|
|
766
|
+
# command (so validate can re-run it). stderr is captured into a separate field and
|
|
767
|
+
# is NOT compared: on a real Py2->Py3 boundary stderr carries DeprecationWarnings
|
|
768
|
+
# and tracebacks that vary run-to-run and would false-block equivalence.
|
|
769
|
+
# Deterministic-by-contract: the boundary command's STDOUT must not emit
|
|
770
|
+
# nondeterministic tokens (timestamps/pids); normalization of such is a documented
|
|
771
|
+
# limitation for the real integration, out of scope for the deterministic core.
|
|
772
|
+
# Returns 0 on capture, 1 on failure to write.
|
|
773
|
+
hook_capture_behavioral_baseline() {
|
|
774
|
+
local codebase_path="${1:-${LOKI_CODEBASE_PATH:-.}}"
|
|
775
|
+
local heal_dir="${codebase_path}/.loki/healing"
|
|
776
|
+
local baseline_dir="${heal_dir}/behavioral-baseline"
|
|
777
|
+
|
|
778
|
+
[[ "${LOKI_HEAL_MODE:-false}" != "true" ]] && return 0
|
|
779
|
+
|
|
780
|
+
mkdir -p "$baseline_dir" 2>/dev/null || return 1
|
|
781
|
+
|
|
782
|
+
local captured=0
|
|
783
|
+
local any=0
|
|
784
|
+
while IFS=$'\t' read -r bid cmd; do
|
|
785
|
+
[[ -z "$bid" || -z "$cmd" ]] && continue
|
|
786
|
+
any=1
|
|
787
|
+
local out rc art errf
|
|
788
|
+
errf=$(mktemp)
|
|
789
|
+
# stdout is the compared channel; stderr is diverted to $errf (recorded
|
|
790
|
+
# separately, never compared).
|
|
791
|
+
out=$(cd "$codebase_path" && eval "$cmd" 2>"$errf")
|
|
792
|
+
rc=$?
|
|
793
|
+
local err
|
|
794
|
+
err=$(cat "$errf" 2>/dev/null); rm -f "$errf"
|
|
795
|
+
art="$baseline_dir/$(_heal_boundary_artifact_name "$bid")"
|
|
796
|
+
# Write a self-describing artifact: id + reproduction command + observed
|
|
797
|
+
# stdout (compared) + stderr (recorded, not compared) + observed exit.
|
|
798
|
+
# validate re-runs `command` and compares stdout+exit only.
|
|
799
|
+
if BID="$bid" CMD="$cmd" OUT="$out" ERR="$err" RC="$rc" ART="$art" python3 -c "
|
|
800
|
+
import json, os
|
|
801
|
+
art = {
|
|
802
|
+
'boundary': os.environ['BID'],
|
|
803
|
+
'boundary_type': os.environ['BID'].split(':', 1)[0],
|
|
804
|
+
'command': os.environ['CMD'],
|
|
805
|
+
'stdout': os.environ['OUT'],
|
|
806
|
+
'stderr': os.environ.get('ERR', ''),
|
|
807
|
+
'exit_code': int(os.environ['RC']),
|
|
808
|
+
'captured_by': 'hook_capture_behavioral_baseline',
|
|
809
|
+
'note': 'stdout+exit_code are the compared channels; stderr is informational only.',
|
|
810
|
+
}
|
|
811
|
+
with open(os.environ['ART'], 'w') as f:
|
|
812
|
+
json.dump(art, f, indent=2, sort_keys=True)
|
|
813
|
+
" 2>/dev/null; then
|
|
814
|
+
captured=$((captured + 1))
|
|
815
|
+
fi
|
|
816
|
+
done < <(_heal_enumerate_boundaries "$heal_dir")
|
|
817
|
+
|
|
818
|
+
if [[ "$any" -eq 0 ]]; then
|
|
819
|
+
# Honest degrade: no boundaries detected. Do NOT fabricate artifacts.
|
|
820
|
+
echo "BASELINE: no boundaries detected (no runnable boundary command in boundaries.json / features.json / characterization-tests/); nothing to golden-master."
|
|
821
|
+
return 0
|
|
822
|
+
fi
|
|
823
|
+
echo "BASELINE: captured ${captured} golden-master artifact(s) into behavioral-baseline/"
|
|
824
|
+
return 0
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
# Compare current boundary outputs to the golden-master baseline PER-BOUNDARY.
|
|
828
|
+
# Emits a diff report enumerating EVERY changed boundary. A boundary is "changed"
|
|
829
|
+
# when its current stdout OR exit code differs from the baseline artifact, UNLESS
|
|
830
|
+
# a documented-intentional-change record exists for exactly that boundary id in
|
|
831
|
+
# behavioral-baseline/intentional-changes.json.
|
|
832
|
+
# Echoes a human-readable report on stdout.
|
|
833
|
+
# Returns:
|
|
834
|
+
# 0 - no baseline / no boundaries (honest degrade; caller falls through), OR
|
|
835
|
+
# all boundaries equivalent (or every change documented-intentional).
|
|
836
|
+
# 1 - at least one undocumented boundary change -> caller must BLOCK.
|
|
837
|
+
hook_compare_behavioral_baseline() {
|
|
838
|
+
local codebase_path="${1:-${LOKI_CODEBASE_PATH:-.}}"
|
|
839
|
+
local heal_dir="${codebase_path}/.loki/healing"
|
|
840
|
+
local baseline_dir="${heal_dir}/behavioral-baseline"
|
|
841
|
+
local intentional="${baseline_dir}/intentional-changes.json"
|
|
842
|
+
|
|
843
|
+
# Honest degrade: off/greenfield/no-baseline -> byte-identical no-op. The
|
|
844
|
+
# caller falls through to its existing whole-suite check unchanged.
|
|
845
|
+
[[ "${LOKI_HEAL_MODE:-false}" != "true" ]] && return 0
|
|
846
|
+
[[ -d "$baseline_dir" ]] || return 0
|
|
847
|
+
# Any golden-master artifacts at all?
|
|
848
|
+
local art_count
|
|
849
|
+
art_count=$(find "$baseline_dir" -type f -name 'boundary-*.json' 2>/dev/null | wc -l | tr -d ' ')
|
|
850
|
+
if [[ "${art_count:-0}" -eq 0 ]]; then
|
|
851
|
+
echo "BOUNDARY-CHECK: no golden-master baseline present; skipping per-boundary comparison (honest degrade)."
|
|
852
|
+
return 0
|
|
853
|
+
fi
|
|
854
|
+
|
|
855
|
+
local changed=0
|
|
856
|
+
local report=""
|
|
857
|
+
local art
|
|
858
|
+
while IFS= read -r art; do
|
|
859
|
+
[[ -z "$art" ]] && continue
|
|
860
|
+
# Read stored boundary id + reproduction command in one python call. A
|
|
861
|
+
# trailing sentinel is appended to each so command-substitution's trailing
|
|
862
|
+
# -newline strip does not corrupt a value (bid/cmd are single-line by
|
|
863
|
+
# construction, but this keeps the read exact and robust).
|
|
864
|
+
local bid cmd base_rc
|
|
865
|
+
bid=$(ART="$art" python3 -c "import json,os;print(json.load(open(os.environ['ART']))['boundary'])" 2>/dev/null) || continue
|
|
866
|
+
cmd=$(ART="$art" python3 -c "import json,os;print(json.load(open(os.environ['ART']))['command'])" 2>/dev/null) || continue
|
|
867
|
+
base_rc=$(ART="$art" python3 -c "import json,os;print(json.load(open(os.environ['ART']))['exit_code'])" 2>/dev/null)
|
|
868
|
+
|
|
869
|
+
# Re-run the boundary NOW. stderr diverted (not compared), matching the
|
|
870
|
+
# capture channel; only stdout + exit are compared.
|
|
871
|
+
local cur_out cur_rc
|
|
872
|
+
cur_out=$(cd "$codebase_path" && eval "$cmd" 2>/dev/null)
|
|
873
|
+
cur_rc=$?
|
|
874
|
+
|
|
875
|
+
# Byte-exact stdout comparison via python (handles trailing newlines /
|
|
876
|
+
# binary-ish content that shell string compare would mangle).
|
|
877
|
+
if ART="$art" CUR="$cur_out" python3 -c "
|
|
878
|
+
import json, os, sys
|
|
879
|
+
base = json.load(open(os.environ['ART']))['stdout']
|
|
880
|
+
sys.exit(0 if base == os.environ['CUR'] else 1)
|
|
881
|
+
" 2>/dev/null && [[ "$cur_rc" == "$base_rc" ]]; then
|
|
882
|
+
continue
|
|
883
|
+
fi
|
|
884
|
+
# Recover baseline stdout for the report (may be multi-line).
|
|
885
|
+
local base_out
|
|
886
|
+
base_out=$(ART="$art" python3 -c "import json,os,sys;sys.stdout.write(json.load(open(os.environ['ART']))['stdout'])" 2>/dev/null)
|
|
887
|
+
|
|
888
|
+
# Boundary changed. Is it documented-intentional for THIS boundary id?
|
|
889
|
+
local is_doc="false"
|
|
890
|
+
if [[ -f "$intentional" ]]; then
|
|
891
|
+
is_doc=$(INT="$intentional" BID="$bid" python3 -c "
|
|
892
|
+
import json, os
|
|
893
|
+
try:
|
|
894
|
+
data = json.load(open(os.environ['INT']))
|
|
895
|
+
except Exception:
|
|
896
|
+
print('false'); raise SystemExit
|
|
897
|
+
changes = data.get('changes', []) if isinstance(data, dict) else []
|
|
898
|
+
bid = os.environ['BID']
|
|
899
|
+
for c in changes:
|
|
900
|
+
if isinstance(c, dict) and c.get('boundary') == bid and c.get('is_intentional') is True:
|
|
901
|
+
print('true'); break
|
|
902
|
+
else:
|
|
903
|
+
print('false')
|
|
904
|
+
" 2>/dev/null || echo false)
|
|
905
|
+
fi
|
|
906
|
+
|
|
907
|
+
if [[ "$is_doc" == "true" ]]; then
|
|
908
|
+
report+=" [documented-intentional] ${bid}: change accepted (intentional-changes.json)"$'\n'
|
|
909
|
+
continue
|
|
910
|
+
fi
|
|
911
|
+
|
|
912
|
+
changed=$((changed + 1))
|
|
913
|
+
report+=" [CHANGED] ${bid}"$'\n'
|
|
914
|
+
report+=" command: ${cmd}"$'\n'
|
|
915
|
+
report+=" baseline: exit=${base_rc} stdout=$(printf '%q' "$base_out")"$'\n'
|
|
916
|
+
report+=" current: exit=${cur_rc} stdout=$(printf '%q' "$cur_out")"$'\n'
|
|
917
|
+
done < <(find "$baseline_dir" -type f -name 'boundary-*.json' 2>/dev/null)
|
|
918
|
+
|
|
919
|
+
if [[ "$changed" -gt 0 ]]; then
|
|
920
|
+
echo "BOUNDARY-DIFF REPORT (${changed} undocumented boundary change(s)):"
|
|
921
|
+
printf '%s' "$report"
|
|
922
|
+
echo " To accept a change intentionally, add a record to:"
|
|
923
|
+
echo " ${intentional}"
|
|
924
|
+
echo " {\"changes\":[{\"boundary\":\"<id>\",\"is_intentional\":true,\"reason\":\"...\"}]}"
|
|
925
|
+
return 1
|
|
926
|
+
fi
|
|
927
|
+
|
|
928
|
+
if [[ -n "$report" ]]; then
|
|
929
|
+
echo "BOUNDARY-CHECK: all changed boundaries are documented-intentional:"
|
|
930
|
+
printf '%s' "$report"
|
|
931
|
+
else
|
|
932
|
+
echo "BOUNDARY-CHECK: all ${art_count} boundary(ies) equivalent to golden-master baseline."
|
|
933
|
+
fi
|
|
934
|
+
return 0
|
|
935
|
+
}
|
|
936
|
+
|
|
655
937
|
# Hook: healing_phase_gate - mechanical verification before healing phase transition
|
|
656
938
|
hook_healing_phase_gate() {
|
|
657
939
|
local from_phase="${1:-}"
|
|
@@ -748,6 +1030,20 @@ except: print(0)
|
|
|
748
1030
|
echo "GATE_BLOCKED: Tests do not pass after modernization"
|
|
749
1031
|
return 1
|
|
750
1032
|
fi
|
|
1033
|
+
# RANK 4: whole-suite exit code is necessary but NOT sufficient. A
|
|
1034
|
+
# transform can keep unit tests green while altering an observable
|
|
1035
|
+
# boundary. Compare each boundary to its golden-master baseline; block
|
|
1036
|
+
# any undocumented boundary change. Honest degrade (no baseline / no
|
|
1037
|
+
# boundaries) falls through as a no-op -- never a false-green.
|
|
1038
|
+
local boundary_report boundary_rc
|
|
1039
|
+
boundary_report=$(hook_compare_behavioral_baseline "$codebase_path")
|
|
1040
|
+
boundary_rc=$?
|
|
1041
|
+
if [[ "$boundary_rc" -ne 0 ]]; then
|
|
1042
|
+
echo "GATE_BLOCKED: boundary equivalence violated after modernization (unit tests green but an observable boundary changed)."
|
|
1043
|
+
echo "$boundary_report"
|
|
1044
|
+
return 1
|
|
1045
|
+
fi
|
|
1046
|
+
[[ -n "$boundary_report" ]] && echo "$boundary_report"
|
|
751
1047
|
;;
|
|
752
1048
|
esac
|
|
753
1049
|
|
|
@@ -130,6 +130,46 @@ def _collect_council(loki_dir):
|
|
|
130
130
|
rec = _read_json(os.path.join(votes_dir, vf), default=None)
|
|
131
131
|
if not isinstance(rec, dict):
|
|
132
132
|
continue
|
|
133
|
+
# completion-council.sh writes ROUND-SUMMARY files (round-N.json,
|
|
134
|
+
# devils-advocate-round-N.json) shaped
|
|
135
|
+
# {round, verdict, votes:[{member, role, vote, reason}]} -- NOT a flat
|
|
136
|
+
# per-reviewer record. When we see that shape, expand the nested votes[]
|
|
137
|
+
# into per-reviewer rows (the real trust signal) and adopt the round
|
|
138
|
+
# verdict; otherwise fall back to the flat single-record shape. Without
|
|
139
|
+
# this, rec.get("role") is empty and the proof's council section renders
|
|
140
|
+
# blank reviewers even though the council genuinely ran and voted (#125).
|
|
141
|
+
nested = rec.get("votes")
|
|
142
|
+
if isinstance(nested, list) and nested:
|
|
143
|
+
rv = str(rec.get("verdict") or rec.get("result") or "")
|
|
144
|
+
if rv and not final_verdict:
|
|
145
|
+
final_verdict = rv
|
|
146
|
+
if threshold is None and rec.get("threshold") is not None:
|
|
147
|
+
tm = rec.get("total_members")
|
|
148
|
+
threshold = (
|
|
149
|
+
"%s/%s" % (rec.get("threshold"), tm)
|
|
150
|
+
if tm is not None else rec.get("threshold")
|
|
151
|
+
)
|
|
152
|
+
round_tag = str(rec.get("round") or "")
|
|
153
|
+
da = "devil" in vf.lower()
|
|
154
|
+
for v in nested:
|
|
155
|
+
if not isinstance(v, dict):
|
|
156
|
+
continue
|
|
157
|
+
role = str(v.get("role") or v.get("member") or "")
|
|
158
|
+
vote = str(v.get("vote") or v.get("decision") or "")
|
|
159
|
+
# Skip empty vote rows (a blank role AND blank vote is noise, not
|
|
160
|
+
# a reviewer) so the proof never shows a phantom "-> " reviewer.
|
|
161
|
+
if not role and not vote:
|
|
162
|
+
continue
|
|
163
|
+
if da and role:
|
|
164
|
+
role = "%s (devil's advocate)" % role
|
|
165
|
+
reviewers.append({
|
|
166
|
+
"role": role,
|
|
167
|
+
"vote": vote,
|
|
168
|
+
"summary": str(
|
|
169
|
+
v.get("reason") or v.get("summary") or v.get("rationale") or ""
|
|
170
|
+
) + (" [round %s]" % round_tag if round_tag else ""),
|
|
171
|
+
})
|
|
172
|
+
continue
|
|
133
173
|
reviewers.append({
|
|
134
174
|
"role": str(rec.get("role") or rec.get("reviewer") or ""),
|
|
135
175
|
"vote": str(rec.get("vote") or rec.get("decision") or ""),
|
|
@@ -222,6 +262,57 @@ def _collect_quality_gates(loki_dir):
|
|
|
222
262
|
total += 1
|
|
223
263
|
if status == "passed":
|
|
224
264
|
passed += 1
|
|
265
|
+
|
|
266
|
+
# #125: run.sh does NOT write state/quality-gates.json; it writes the gate
|
|
267
|
+
# outcomes under .loki/quality/ (a "<gate>.pass" marker on pass, plus
|
|
268
|
+
# per-gate result JSONs). When the aggregate above is empty/absent, read the
|
|
269
|
+
# real per-gate artifacts so a build that genuinely ran+passed its gates is
|
|
270
|
+
# not reported as quality_gates:{passed:0,total:0} (a receipt understatement
|
|
271
|
+
# that reads as "no verification ran"). Deterministic FACT from disk markers,
|
|
272
|
+
# never an LLM opinion. Only fills gates the aggregate did not already cover.
|
|
273
|
+
if not gates:
|
|
274
|
+
quality_dir = os.path.join(loki_dir, "quality")
|
|
275
|
+
seen = set()
|
|
276
|
+
# (gate name in proof, marker/result filename stem under quality/)
|
|
277
|
+
markers = [
|
|
278
|
+
("static_analysis", "static-analysis"),
|
|
279
|
+
("unit_tests", "unit-tests"),
|
|
280
|
+
]
|
|
281
|
+
for gate_name, stem in markers:
|
|
282
|
+
pass_marker = os.path.join(quality_dir, stem + ".pass")
|
|
283
|
+
result_json = os.path.join(quality_dir, stem + ".json")
|
|
284
|
+
status = None
|
|
285
|
+
if os.path.exists(pass_marker):
|
|
286
|
+
status = "passed"
|
|
287
|
+
elif os.path.exists(result_json):
|
|
288
|
+
rj = _read_json(result_json, default=None)
|
|
289
|
+
if isinstance(rj, dict):
|
|
290
|
+
status = _norm_gate_status(
|
|
291
|
+
rj.get("passed", rj.get("status", "not_run"))
|
|
292
|
+
)
|
|
293
|
+
if status is not None:
|
|
294
|
+
gates.append({"name": gate_name, "status": status})
|
|
295
|
+
seen.add(gate_name)
|
|
296
|
+
total += 1
|
|
297
|
+
if status == "passed":
|
|
298
|
+
passed += 1
|
|
299
|
+
# test-results.json carries the suite outcome even when no .pass marker
|
|
300
|
+
# (e.g. {status:"verified"} = tests ran and passed).
|
|
301
|
+
if "unit_tests" not in seen:
|
|
302
|
+
tr = _read_json(
|
|
303
|
+
os.path.join(quality_dir, "test-results.json"), default=None
|
|
304
|
+
)
|
|
305
|
+
if isinstance(tr, dict):
|
|
306
|
+
st = str(tr.get("status") or "")
|
|
307
|
+
if st in ("verified", "pass", "passed"):
|
|
308
|
+
gates.append({"name": "unit_tests", "status": "passed"})
|
|
309
|
+
total += 1
|
|
310
|
+
passed += 1
|
|
311
|
+
elif st:
|
|
312
|
+
gates.append(
|
|
313
|
+
{"name": "unit_tests", "status": _norm_gate_status(st)}
|
|
314
|
+
)
|
|
315
|
+
total += 1
|
|
225
316
|
return {"passed": passed, "total": total, "gates": gates}
|
|
226
317
|
|
|
227
318
|
|
package/autonomy/loki
CHANGED
|
@@ -14784,7 +14784,7 @@ HEALING RULES:
|
|
|
14784
14784
|
5. INCREMENTAL ONLY: Change ONE component at a time. Verify ALL characterization tests pass after each change.
|
|
14785
14785
|
|
|
14786
14786
|
Phase-specific instructions:
|
|
14787
|
-
- archaeology: Map dependencies, catalog friction, extract knowledge, write characterization tests. Do NOT modify source code.
|
|
14787
|
+
- archaeology: Map dependencies, catalog friction, extract knowledge, write characterization tests. Do NOT modify source code. ALSO write .loki/healing/boundaries.json: a JSON array of the OBSERVABLE system boundaries (CLI commands, HTTP endpoints, file/DB-producing entrypoints). Each element: {\"id\": string, \"boundary_type\": \"cli\"|\"http\"|\"file\"|\"db\", \"command\": string}. The \"command\" MUST be a deterministic shell invocation that exercises that boundary and prints its observable output to stdout (avoid timestamps/PIDs). These commands are golden-mastered into .loki/healing/behavioral-baseline/ and re-run at the modernize->validate gate to block any undocumented boundary change. If no observable boundary exists, write an empty array [] (do NOT invent one).
|
|
14788
14788
|
- stabilize: Add logging and observability. Extract config. Add type hints. Do NOT change behavior.
|
|
14789
14789
|
- isolate: Create adapter interfaces at component boundaries. Add integration tests.
|
|
14790
14790
|
- modernize: Replace components behind adapters. Verify characterization tests pass.
|
|
@@ -14845,6 +14845,16 @@ except Exception: pass
|
|
|
14845
14845
|
|
|
14846
14846
|
emit_event healing cli complete "phase=$phase" "exit=$heal_exit" 2>/dev/null || true
|
|
14847
14847
|
|
|
14848
|
+
# RANK 4: populate .loki/healing/behavioral-baseline/ during archaeology.
|
|
14849
|
+
# The dir is mkdir'd above but was never populated; the modernize->validate
|
|
14850
|
+
# gate needs a golden-master per detected boundary to compare against.
|
|
14851
|
+
# Deterministic capture (NOT an LLM call): re-runs each boundary's
|
|
14852
|
+
# characterization_test and records observed stdout+exit. Honest degrade when
|
|
14853
|
+
# no boundaries are detected. Byte-identical no-op when the hook is absent.
|
|
14854
|
+
if [ "$phase" = "archaeology" ] && type hook_capture_behavioral_baseline &>/dev/null; then
|
|
14855
|
+
hook_capture_behavioral_baseline "$codebase_path" 2>/dev/null || true
|
|
14856
|
+
fi
|
|
14857
|
+
|
|
14848
14858
|
if [ "$heal_exit" -eq 0 ]; then
|
|
14849
14859
|
echo ""
|
|
14850
14860
|
echo -e "${GREEN}Healing phase '${phase}' complete.${NC}"
|
package/autonomy/run.sh
CHANGED
|
@@ -9890,12 +9890,41 @@ run_doc_quality_gate() {
|
|
|
9890
9890
|
local score=100
|
|
9891
9891
|
local issues=()
|
|
9892
9892
|
|
|
9893
|
+
# F52 (doc-scope, gate half): for a "simple"-tier project the accepted
|
|
9894
|
+
# documentation standard is README.md + USAGE.md, NOT the .loki/docs/
|
|
9895
|
+
# architecture suite. auto_generate_docs_if_needed deliberately skips the
|
|
9896
|
+
# suite for simple tier, so the manifest / API.md checks below MUST NOT
|
|
9897
|
+
# penalize it (else the gate would nag for docs we intentionally did not
|
|
9898
|
+
# generate, forcing extra iterations -- the exact waste F52 removes). Score
|
|
9899
|
+
# simple tier on README + USAGE only. Mirrors DOC_SCOPE_INSTRUCTION_SIMPLE.
|
|
9900
|
+
local doc_tier="${DETECTED_COMPLEXITY:-standard}"
|
|
9901
|
+
|
|
9893
9902
|
# Check 1: README.md exists
|
|
9894
9903
|
if [ ! -f "$project_dir/README.md" ] || [ ! -s "$project_dir/README.md" ]; then
|
|
9895
9904
|
score=$((score - 20))
|
|
9896
9905
|
issues+=("README.md missing or empty")
|
|
9897
9906
|
fi
|
|
9898
9907
|
|
|
9908
|
+
if [ "$doc_tier" = "simple" ]; then
|
|
9909
|
+
# USAGE.md is the always-on end-user handoff doc (usage_doc_instruction);
|
|
9910
|
+
# for a simple project it plus README is sufficient. Penalize only if the
|
|
9911
|
+
# always-on USAGE.md is absent/empty.
|
|
9912
|
+
if [ ! -f "$project_dir/USAGE.md" ] || [ ! -s "$project_dir/USAGE.md" ]; then
|
|
9913
|
+
score=$((score - 20))
|
|
9914
|
+
issues+=("USAGE.md missing or empty")
|
|
9915
|
+
fi
|
|
9916
|
+
if [ ${#issues[@]} -gt 0 ]; then
|
|
9917
|
+
log_warn "Documentation Gate: Score $score/100 (simple tier: README + USAGE)"
|
|
9918
|
+
for issue in "${issues[@]}"; do
|
|
9919
|
+
log_warn " - $issue"
|
|
9920
|
+
done
|
|
9921
|
+
else
|
|
9922
|
+
log_info "Documentation Gate: PASS (Score $score/100, simple tier: README + USAGE)"
|
|
9923
|
+
fi
|
|
9924
|
+
[ "$score" -ge 70 ]
|
|
9925
|
+
return $?
|
|
9926
|
+
fi
|
|
9927
|
+
|
|
9899
9928
|
# Check 2: Documentation freshness
|
|
9900
9929
|
local manifest="$project_dir/.loki/docs/docs-manifest.json"
|
|
9901
9930
|
if [ -f "$manifest" ]; then
|
|
@@ -9956,6 +9985,22 @@ run_doc_quality_gate() {
|
|
|
9956
9985
|
auto_generate_docs_if_needed() {
|
|
9957
9986
|
[ "${LOKI_AUTO_DOCS:-true}" = "true" ] || return 0
|
|
9958
9987
|
|
|
9988
|
+
# F52 (doc-scope, generator half): a trivial "simple"-tier project does not
|
|
9989
|
+
# warrant the eight-file architecture suite. 'loki docs generate' runs the
|
|
9990
|
+
# provider agentically (claude -p), which writes ARCHITECTURE/API/COMPONENTS/
|
|
9991
|
+
# DECISIONS/SETUP/TESTING/README/CLAUDE to the project -- ~270s of pure token
|
|
9992
|
+
# + wall-clock burn with no reader for a 5-line CLI. The agent already wrote
|
|
9993
|
+
# README.md + USAGE.md under DOC_SCOPE_INSTRUCTION_SIMPLE, so skip the suite.
|
|
9994
|
+
# Parity: this is the generator-side mirror of DOC_SCOPE_INSTRUCTION_SIMPLE
|
|
9995
|
+
# (build_prompt.ts / run.sh:14421); the gate half (run_doc_quality_gate)
|
|
9996
|
+
# accepts README+USAGE for simple so skipping here does not fail Gate 7.
|
|
9997
|
+
# DETECTED_COMPLEXITY is set once by run_autonomous before the first
|
|
9998
|
+
# build_prompt, so it is populated by the time this runs (post code-review).
|
|
9999
|
+
if [ "${DETECTED_COMPLEXITY:-standard}" = "simple" ]; then
|
|
10000
|
+
log_info "Auto-documentation: simple project -- README + USAGE only, skipping architecture suite (F52 doc-scope)"
|
|
10001
|
+
return 0
|
|
10002
|
+
fi
|
|
10003
|
+
|
|
9959
10004
|
local project_dir="${TARGET_DIR:-.}"
|
|
9960
10005
|
local manifest="$project_dir/.loki/docs/docs-manifest.json"
|
|
9961
10006
|
local needs_gen=false
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.
|
|
5
|
+
**Version:** v7.118.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.118.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([c1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -814,4 +814,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
814
814
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
815
815
|
`),process.stderr.write(K8),2}}e1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var SZ=await NZ(Bun.argv.slice(2));process.exit(SZ);
|
|
816
816
|
|
|
817
|
-
//# debugId=
|
|
817
|
+
//# debugId=01D367D3C33A5E7664756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.118.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.118.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/skills/healing.md
CHANGED
|
@@ -411,10 +411,28 @@ When `loki heal` is active, the code review specialist pool includes:
|
|
|
411
411
|
failure-modes.json # Cataloged failure modes
|
|
412
412
|
institutional-knowledge.md # Extracted tribal knowledge
|
|
413
413
|
healing-progress.json # Component-by-component healing status
|
|
414
|
-
|
|
414
|
+
boundaries.json # Observable system boundaries (archaeology writes)
|
|
415
|
+
behavioral-baseline/ # Golden-master per-boundary outputs (archaeology captures)
|
|
415
416
|
characterization-tests/ # Tests that capture current behavior
|
|
416
417
|
```
|
|
417
418
|
|
|
419
|
+
### Golden-master boundary equivalence (modernize -> validate gate)
|
|
420
|
+
|
|
421
|
+
Whole-suite exit code is necessary but NOT sufficient: a transform can keep unit
|
|
422
|
+
tests green while altering an observable boundary (CLI stdout/exit, HTTP, file/DB
|
|
423
|
+
delta). Archaeology writes `.loki/healing/boundaries.json` -- a JSON array of
|
|
424
|
+
`{"id","boundary_type":"cli"|"http"|"file"|"db","command"}` -- where `command` is a
|
|
425
|
+
deterministic invocation that prints the boundary's observable output. During
|
|
426
|
+
archaeology each boundary is golden-mastered into `behavioral-baseline/`
|
|
427
|
+
(stdout + exit are the compared channels; stderr is recorded but never compared,
|
|
428
|
+
so Py2->Py3 warnings do not false-block). At the `modernize -> validate` gate every
|
|
429
|
+
boundary is re-run and compared PER-BOUNDARY; any change is BLOCKED with a diff
|
|
430
|
+
report unless a per-boundary record exists in
|
|
431
|
+
`behavioral-baseline/intentional-changes.json`
|
|
432
|
+
(`{"changes":[{"boundary":"<id>","is_intentional":true,"reason":"..."}]}`).
|
|
433
|
+
Honest degrade: no boundaries / no baseline / heal mode off is a byte-identical
|
|
434
|
+
no-op, never a false-green. (Deterministic shell hooks, not LLM calls.)
|
|
435
|
+
|
|
418
436
|
**Progress Tracking:**
|
|
419
437
|
```json
|
|
420
438
|
{
|