loki-mode 9.17.0 → 9.17.2
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/issue-providers.sh +0 -24
- package/autonomy/lib/agent_readiness.py +1 -79
- package/autonomy/lib/outcome_ledger.py +0 -122
- package/autonomy/lib/proof-generator.py +4 -71
- package/autonomy/lib/verdict.py +4 -25
- package/autonomy/loki +5 -262
- package/autonomy/notify.sh +1 -70
- package/autonomy/queue-consumer.sh +18 -290
- package/autonomy/run.sh +65 -178
- package/autonomy/verify.sh +100 -12
- package/completions/_loki +0 -3
- package/completions/loki.bash +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +1 -168
- package/dashboard/static/index.html +222 -249
- package/docs/VERIFICATION-COST.md +20 -170
- package/loki-ts/dist/loki.js +311 -305
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +0 -25
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/autonomy/lib/gate_policy.py +0 -166
- package/docs/QUEUE-OPERATIONS.md +0 -107
package/autonomy/loki
CHANGED
|
@@ -1040,15 +1040,15 @@ show_help() {
|
|
|
1040
1040
|
echo " agent analyze api assets audit bench checkpoint (cp) ci cleanup"
|
|
1041
1041
|
echo " cluster cockpit code compliance completions compound config context (ctx)"
|
|
1042
1042
|
echo " cost council crash dashboard demo deploy docker docs doctor dogfood"
|
|
1043
|
-
echo " enterprise estimate explain export failover
|
|
1043
|
+
echo " enterprise estimate explain export failover github grill heal help"
|
|
1044
1044
|
echo " import init intent"
|
|
1045
1045
|
echo " issue kpis logs magic mcp memory metrics migrate modernize monitor"
|
|
1046
1046
|
echo " next notify onboard open optimize otel outcomes own (handoff) pause plan preview"
|
|
1047
|
-
echo " projects proof (receipt) provider quick quickstart rc
|
|
1047
|
+
echo " projects proof (receipt) provider quick quickstart rc remote report reset"
|
|
1048
1048
|
echo " resume review rollback run sandbox secrets secure self-update sentrux"
|
|
1049
1049
|
echo " serve setup-skill share ship spec start state stats status steer stop"
|
|
1050
1050
|
echo " syslog telemetry template test tour trigger trust trust-metrics"
|
|
1051
|
-
echo " ultracode update
|
|
1051
|
+
echo " ultracode update verify version voice watch watchdog web welcome why"
|
|
1052
1052
|
echo " wiki worktree (wt)"
|
|
1053
1053
|
echo ""
|
|
1054
1054
|
echo "Any command: loki <command> --help"
|
|
@@ -11905,14 +11905,7 @@ STALE_DAYS = 90
|
|
|
11905
11905
|
p = os.environ['LOKI_CATALOG_PATH']
|
|
11906
11906
|
try:
|
|
11907
11907
|
updated = json.load(open(p))['updated']
|
|
11908
|
-
|
|
11909
|
-
# (doctor.ts:545). This is the TEXT-mode twin of that computation, and it was
|
|
11910
|
-
# missed when the JSON one was fixed: the parity gate compares BOTH surfaces,
|
|
11911
|
-
# so fixing only --json left doctor text-mode still diverging 5 vs 6 and the
|
|
11912
|
-
# gate still red. Two copies of one calculation is the actual defect here;
|
|
11913
|
-
# they are left as two only because the text path prints and the JSON path
|
|
11914
|
-
# returns a dict.
|
|
11915
|
-
age = (datetime.datetime.now(datetime.timezone.utc).date() - datetime.date.fromisoformat(updated)).days
|
|
11908
|
+
age = (datetime.date.today() - datetime.date.fromisoformat(updated)).days
|
|
11916
11909
|
except Exception:
|
|
11917
11910
|
print('warn|Catalog unreadable or missing an ISO \"updated\" date -- cannot determine age')
|
|
11918
11911
|
else:
|
|
@@ -12036,17 +12029,6 @@ else:
|
|
|
12036
12029
|
local _blk_key="other"
|
|
12037
12030
|
case "$_doctor_blockers" in
|
|
12038
12031
|
*"No AI provider CLI"*) _blk_key="no_provider" ;;
|
|
12039
|
-
# This doctor DETECTS the logged-out / expired wall above
|
|
12040
|
-
# (:11610, :11615) but had no arm for it, so the single most
|
|
12041
|
-
# common post-install failure was reported as `other` -- the one
|
|
12042
|
-
# bucket that cannot be acted on. not_logged_in is already a
|
|
12043
|
-
# first-class enum value (telemetry.sh:204) and the Bun doctor
|
|
12044
|
-
# already reports it, so without this arm the SAME host answered
|
|
12045
|
-
# `other` on bash and `not_logged_in` on Bun and the two routes'
|
|
12046
|
-
# counts could not be added together. Ordered directly after
|
|
12047
|
-
# no_provider because install-vs-authenticate need opposite fixes
|
|
12048
|
-
# and a missing provider is the earlier wall.
|
|
12049
|
-
*"not logged in"*|*"login has expired"*) _blk_key="not_logged_in" ;;
|
|
12050
12032
|
*"Node.js is not installed"*|*"Node.js must be"*) _blk_key="node" ;;
|
|
12051
12033
|
*"Python 3 is not installed"*|*"Python 3 must be"*) _blk_key="python3" ;;
|
|
12052
12034
|
*"jq is not installed"*) _blk_key="jq" ;;
|
|
@@ -12330,18 +12312,7 @@ _cat_path = os.environ['LOKI_CATALOG_PATH']
|
|
|
12330
12312
|
try:
|
|
12331
12313
|
import datetime as _dt
|
|
12332
12314
|
_cat_updated = json.load(open(_cat_path))['updated']
|
|
12333
|
-
|
|
12334
|
-
# both sides at UTC midnight (doctor.ts:545 says so, to keep the day count
|
|
12335
|
-
# from shifting with the host timezone). Anywhere west of UTC the two
|
|
12336
|
-
# disagree for the hours between local midnight and UTC midnight, and
|
|
12337
|
-
# doctor --json is compared BYTE FOR BYTE between routes by bun-parity.
|
|
12338
|
-
#
|
|
12339
|
-
# Measured on this host at 02:45 UTC / 22:45 local: bash reported age_days 5
|
|
12340
|
-
# and Bun reported 6, from the same file at the same instant. It reproduced
|
|
12341
|
-
# across two full gate runs hours apart, so it is not a midnight-rollover
|
|
12342
|
-
# flake. It is a real parity defect that stays invisible while the local date
|
|
12343
|
-
# and the UTC date agree, which is most of the day.
|
|
12344
|
-
_cat_age = (_dt.datetime.now(_dt.timezone.utc).date() - _dt.date.fromisoformat(_cat_updated)).days
|
|
12315
|
+
_cat_age = (_dt.date.today() - _dt.date.fromisoformat(_cat_updated)).days
|
|
12345
12316
|
if _cat_age > CATALOG_STALE_DAYS:
|
|
12346
12317
|
# Deliberately NOT counted. The block comment above states catalog age is
|
|
12347
12318
|
# excluded from pass/fail/warn and from 'ok' so a stale catalog can never
|
|
@@ -13803,90 +13774,12 @@ set_ttfv_lightweight_profile() {
|
|
|
13803
13774
|
# never pollutes the v7.8.1 generated-PRD-reuse signature logic. The brief text
|
|
13804
13775
|
# is the project intent; the rest is a minimal scaffold the agent fills in.
|
|
13805
13776
|
# Usage: synthesize_brief_prd <output_file> <brief_text>
|
|
13806
|
-
# _brief_acceptance_criteria <brief_text>: acceptance criteria derived from what
|
|
13807
|
-
# the user ACTUALLY asked for, not constants.
|
|
13808
|
-
#
|
|
13809
|
-
# WHY. Every one-liner used to get byte-identical Requirements and Success
|
|
13810
|
-
# Criteria: "build a todo app" and "build a Stripe billing dashboard" produced
|
|
13811
|
-
# the same acceptance criteria, and the user's own words appeared exactly once,
|
|
13812
|
-
# under Overview. So the completion council, the checklist and the evidence gate
|
|
13813
|
-
# were all checking generic prose rather than the request. That is the weakest
|
|
13814
|
-
# input shape getting the least specific help, which is backwards -- a cheap
|
|
13815
|
-
# model's output quality depends more on how precisely the target is stated than
|
|
13816
|
-
# on the model.
|
|
13817
|
-
#
|
|
13818
|
-
# DETERMINISTIC ON PURPOSE. No model call: this runs before a provider is even
|
|
13819
|
-
# selected, must work with no API key, and must not add latency or cost to the
|
|
13820
|
-
# first thing a new user does. It is keyword-to-obligation mapping, which is
|
|
13821
|
-
# honest about being shallow -- it turns stated nouns into checkable lines and
|
|
13822
|
-
# claims nothing about intent it cannot see. Anything cleverer belongs in the
|
|
13823
|
-
# spec-interrogation grill, which already runs after this and does call a model.
|
|
13824
|
-
#
|
|
13825
|
-
# STABLE IDs. Each criterion is emitted as "AC-<AXIS>-NNN: <text>" rather than a
|
|
13826
|
-
# bare bullet. An anonymous bullet cannot be referred to: a receipt can say "3 of
|
|
13827
|
-
# 8 gates passed" but never "AC-PERSIST-001 is satisfied by this test", drift
|
|
13828
|
-
# cannot be tracked per criterion, and two runs of the same spec produce lists
|
|
13829
|
-
# nothing can diff. The ID is what turns a criterion into a citable claim, which
|
|
13830
|
-
# is the whole point of shipping a receipt someone can check.
|
|
13831
|
-
#
|
|
13832
|
-
# The axis is derived from WHICH obligation fired, not from the criterion's
|
|
13833
|
-
# position, so IDs are stable across runs: adding a payment criterion never
|
|
13834
|
-
# renumbers the persistence one. Same reason we do not use a running counter.
|
|
13835
|
-
_brief_acceptance_criteria() {
|
|
13836
|
-
local t
|
|
13837
|
-
t="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
|
13838
|
-
local out=""
|
|
13839
|
-
# _bac <AXIS> <text>. The sequence is per-axis and always 001 today because
|
|
13840
|
-
# each axis fires at most once; the NNN slot exists so a second criterion on
|
|
13841
|
-
# the same axis can be added later without renumbering the first.
|
|
13842
|
-
_bac() { out="${out}- AC-${1}-001: ${2}"$'\n'; }
|
|
13843
|
-
|
|
13844
|
-
# Persistence. The single most common churn report is "I submitted the form
|
|
13845
|
-
# and nothing happened", so a stated store or form becomes an explicit
|
|
13846
|
-
# survives-a-reload obligation rather than an implied one.
|
|
13847
|
-
case "$t" in
|
|
13848
|
-
*save*|*persist*|*store*|*databas*|*crud*|*todo*|*note*|*task*|*record*)
|
|
13849
|
-
_bac PERSIST "Data the user creates survives a page reload and a server restart (it is written to a real store, not held in memory)." ;;
|
|
13850
|
-
esac
|
|
13851
|
-
case "$t" in
|
|
13852
|
-
*form*|*submit*|*signup*|*"sign up"*|*contact*|*upload*|*checkout*)
|
|
13853
|
-
_bac FORM "Every form actually submits: the happy path writes real data and the user sees a confirmation, and a validation failure shows an inline error." ;;
|
|
13854
|
-
esac
|
|
13855
|
-
case "$t" in
|
|
13856
|
-
*auth*|*login*|*"log in"*|*"sign in"*|*account*|*user*|*password*|*session*)
|
|
13857
|
-
_bac AUTH "Authentication works end to end: a real signup, a real login, and a protected route that returns 401 when logged out." ;;
|
|
13858
|
-
esac
|
|
13859
|
-
case "$t" in
|
|
13860
|
-
*api*|*endpoint*|*rest*|*graphql*|*backend*|*server*)
|
|
13861
|
-
_bac API "Each endpoint returns real data with correct status codes, and is callable with curl without a browser." ;;
|
|
13862
|
-
esac
|
|
13863
|
-
case "$t" in
|
|
13864
|
-
*payment*|*stripe*|*billing*|*subscription*|*checkout*|*invoice*)
|
|
13865
|
-
_bac PAY "The payment path is wired to the provider's test mode and a test transaction completes; no mocked charge stands in for the integration." ;;
|
|
13866
|
-
esac
|
|
13867
|
-
case "$t" in
|
|
13868
|
-
*search*|*filter*|*sort*)
|
|
13869
|
-
_bac SEARCH "Search or filtering queries the real dataset and returns different results for different inputs." ;;
|
|
13870
|
-
esac
|
|
13871
|
-
case "$t" in
|
|
13872
|
-
*dashboard*|*chart*|*graph*|*analytic*|*report*|*metric*)
|
|
13873
|
-
_bac DATA "Every figure shown traces to a real query. No hardcoded sample numbers." ;;
|
|
13874
|
-
esac
|
|
13875
|
-
case "$t" in
|
|
13876
|
-
*page*|*landing*|*site*|*website*|*ui*|*app*|*frontend*)
|
|
13877
|
-
_bac UI "The page renders with real content, no lorem ipsum, and no placeholder image where a real asset belongs." ;;
|
|
13878
|
-
esac
|
|
13879
|
-
printf '%s' "$out"
|
|
13880
|
-
}
|
|
13881
|
-
|
|
13882
13777
|
synthesize_brief_prd() {
|
|
13883
13778
|
local out_file="$1"
|
|
13884
13779
|
local brief_text="$2"
|
|
13885
13780
|
local out_dir
|
|
13886
13781
|
out_dir="$(dirname "$out_file")"
|
|
13887
13782
|
mkdir -p "$out_dir" 2>/dev/null || true
|
|
13888
|
-
local _derived
|
|
13889
|
-
_derived="$(_brief_acceptance_criteria "$brief_text")"
|
|
13890
13783
|
cat > "$out_file" << BRIEFEOF
|
|
13891
13784
|
# Project Brief
|
|
13892
13785
|
|
|
@@ -13902,7 +13795,6 @@ $brief_text
|
|
|
13902
13795
|
## Success Criteria
|
|
13903
13796
|
- A user can run the result and observe the core behavior described above.
|
|
13904
13797
|
- No errors on a clean start; the happy path works end to end.
|
|
13905
|
-
${_derived}
|
|
13906
13798
|
|
|
13907
13799
|
## Constraints
|
|
13908
13800
|
- This is a fast first pass (zero-config first run). Keep scope tight.
|
|
@@ -14004,10 +13896,6 @@ cmd_quick() {
|
|
|
14004
13896
|
find "$LOKI_DIR" -maxdepth 1 -name 'quick-prd-*.md' -mtime +1 -delete 2>/dev/null || true
|
|
14005
13897
|
find "$LOKI_DIR" -maxdepth 1 -name 'brief-prd-*.md' -mtime +1 -delete 2>/dev/null || true
|
|
14006
13898
|
local quick_prd="$LOKI_DIR/quick-prd-$$.md"
|
|
14007
|
-
# Same spec-derived criteria as the brief path: a quick task was getting the
|
|
14008
|
-
# identical generic Success Criteria regardless of what was asked.
|
|
14009
|
-
local _derived
|
|
14010
|
-
_derived="$(_brief_acceptance_criteria "$task_desc")"
|
|
14011
13899
|
cat > "$quick_prd" << QPRDEOF
|
|
14012
13900
|
# Quick Task
|
|
14013
13901
|
|
|
@@ -14024,7 +13912,6 @@ $task_desc
|
|
|
14024
13912
|
- Task is completed as described
|
|
14025
13913
|
- No errors or regressions introduced
|
|
14026
13914
|
- Code follows project conventions
|
|
14027
|
-
${_derived}
|
|
14028
13915
|
|
|
14029
13916
|
## Constraints
|
|
14030
13917
|
- This is a quick single-task execution
|
|
@@ -19489,20 +19376,6 @@ main() {
|
|
|
19489
19376
|
# happened to it afterwards -- reverted, reworked, or survived.
|
|
19490
19377
|
cmd_outcomes "$@"
|
|
19491
19378
|
;;
|
|
19492
|
-
verdict)
|
|
19493
|
-
# All five trust signals in one block. The individual measurements
|
|
19494
|
-
# existed and were unreachable; this is the surface a reviewer reads.
|
|
19495
|
-
cmd_verdict "$@"
|
|
19496
|
-
;;
|
|
19497
|
-
readiness)
|
|
19498
|
-
# Can an agent verify its own work here? Measured, not LLM-scored.
|
|
19499
|
-
cmd_readiness "$@"
|
|
19500
|
-
;;
|
|
19501
|
-
gates)
|
|
19502
|
-
# What blocks here vs only advises, and what promoting a gate would
|
|
19503
|
-
# have cost. Reports only; never promotes.
|
|
19504
|
-
cmd_gates "$@"
|
|
19505
|
-
;;
|
|
19506
19379
|
secure)
|
|
19507
19380
|
# Secure-by-default gate surface: inspect findings + manage waivers.
|
|
19508
19381
|
cmd_secure "$@"
|
|
@@ -30978,136 +30851,6 @@ cmd_outcomes() {
|
|
|
30978
30851
|
LOKI_DIR="${LOKI_DIR:-.loki}" python3 "$lib" "$@"
|
|
30979
30852
|
}
|
|
30980
30853
|
|
|
30981
|
-
# loki verdict: the five measured trust signals, in one block a reviewer reads
|
|
30982
|
-
# in ten seconds.
|
|
30983
|
-
#
|
|
30984
|
-
# WHY A COMMAND AND NOT JUST A LIBRARY. We measure five things nobody else does
|
|
30985
|
-
# -- did the work survive, does the spec still match intent, was this the agent
|
|
30986
|
-
# or a human rescue, does the completion claim name real work, which model
|
|
30987
|
-
# decided -- and each was correct, tested, and unreachable. A moat nobody can
|
|
30988
|
-
# see is not a moat. This is the surface.
|
|
30989
|
-
#
|
|
30990
|
-
# There is deliberately NO composite score. Averaging a revert count, a hash
|
|
30991
|
-
# comparison, a diff hash, a path match and a model id yields a number whose
|
|
30992
|
-
# movement nobody can explain, which is what competitors already ship. UNKNOWN
|
|
30993
|
-
# is PRINTED, never suppressed: a reviewer must tell "we checked and it is fine"
|
|
30994
|
-
# from "we could not check". See autonomy/lib/verdict.py.
|
|
30995
|
-
cmd_verdict() {
|
|
30996
|
-
local lib="${_LOKI_SCRIPT_DIR}/lib/verdict.py"
|
|
30997
|
-
if [ ! -f "$lib" ]; then
|
|
30998
|
-
echo "verdict renderer is not installed at $lib" >&2
|
|
30999
|
-
return 2
|
|
31000
|
-
fi
|
|
31001
|
-
case "${1:-}" in
|
|
31002
|
-
--help|-h|help)
|
|
31003
|
-
echo -e "${BOLD}loki verdict${NC} - the five measured trust signals, in one readable block"
|
|
31004
|
-
echo ""
|
|
31005
|
-
echo "Usage: loki verdict [--json]"
|
|
31006
|
-
echo ""
|
|
31007
|
-
echo "Prints one line each for outcome, intent, authorship, grounding and"
|
|
31008
|
-
echo "model: what survived, whether the spec still matches intent, whether"
|
|
31009
|
-
echo "this was the agent or a human rescue, whether the completion claim"
|
|
31010
|
-
echo "named real work, and which model decided."
|
|
31011
|
-
echo ""
|
|
31012
|
-
echo "Every line is either a measured fact or an explicit UNKNOWN. There is"
|
|
31013
|
-
echo "no composite score: a number nobody can explain is not evidence."
|
|
31014
|
-
echo "Read-only: it never writes to the repo it analyses."
|
|
31015
|
-
return 0
|
|
31016
|
-
;;
|
|
31017
|
-
esac
|
|
31018
|
-
LOKI_DIR="${LOKI_DIR:-.loki}" python3 "$lib" "$@"
|
|
31019
|
-
}
|
|
31020
|
-
|
|
31021
|
-
# loki readiness: can an autonomous agent verify its own work in THIS repo?
|
|
31022
|
-
#
|
|
31023
|
-
# WHY THIS IS NOT A COPY OF FACTORY AI'S AGENT READINESS MODEL. Theirs is
|
|
31024
|
-
# LLM-scored -- their report objects record modelUsed and reasoningEffort, so
|
|
31025
|
-
# the number is a model's opinion and two runs can disagree about the same
|
|
31026
|
-
# commit. Every criterion here is a file that exists or does not, a command
|
|
31027
|
-
# present or absent: same commit, same answer, every machine, no key, no spend.
|
|
31028
|
-
#
|
|
31029
|
-
# No percentage and no letter grade. A composite invites ranking, ranking
|
|
31030
|
-
# invites gaming, and the individual signals are the actionable part -- "there
|
|
31031
|
-
# is no test command" tells you what to do, "readiness 62%" does not. Criteria
|
|
31032
|
-
# that cannot be determined report UNKNOWN by name rather than counting as
|
|
31033
|
-
# failures. See autonomy/lib/agent_readiness.py.
|
|
31034
|
-
cmd_gates() {
|
|
31035
|
-
local lib="${_LOKI_SCRIPT_DIR}/lib/gate_policy.py"
|
|
31036
|
-
if [ ! -f "$lib" ]; then
|
|
31037
|
-
echo "gate policy reporter is not installed at $lib" >&2
|
|
31038
|
-
return 2
|
|
31039
|
-
fi
|
|
31040
|
-
case "${1:-}" in
|
|
31041
|
-
--help|-h|help)
|
|
31042
|
-
echo -e "${BOLD}loki gates${NC} - what blocks here, and what only advises"
|
|
31043
|
-
echo ""
|
|
31044
|
-
echo "Usage: loki gates [.loki-dir] [--json]"
|
|
31045
|
-
echo ""
|
|
31046
|
-
echo "Ona's Veto Exec ships an audit-first ladder: start in audit mode,"
|
|
31047
|
-
echo "review what matched, then promote the confirmed rules to block. The"
|
|
31048
|
-
echo "middle step is the load-bearing one -- a policy you cannot safely"
|
|
31049
|
-
echo "turn on is a policy nobody turns on."
|
|
31050
|
-
echo ""
|
|
31051
|
-
echo "We had both ends and nothing between them: gates are advisory or"
|
|
31052
|
-
echo "blocking, three promotion knobs exist, and the failure ledger has"
|
|
31053
|
-
echo "counted per-gate hits all along. Nothing joined them, so deciding"
|
|
31054
|
-
echo "whether to promote a gate meant guessing."
|
|
31055
|
-
echo ""
|
|
31056
|
-
echo "For each gate this prints its mode, how many times it has fired,"
|
|
31057
|
-
echo "and -- for an advisory one -- the exact variable that promotes it."
|
|
31058
|
-
echo ""
|
|
31059
|
-
echo "Deterministic: reads two files and the environment. No model, no"
|
|
31060
|
-
echo "key, no spend. Counts come from"
|
|
31061
|
-
echo ".loki/quality/gate-failure-count.json; open it and count them"
|
|
31062
|
-
echo "yourself."
|
|
31063
|
-
echo ""
|
|
31064
|
-
echo "A gate with no ledger entry reports 'not measured', never 0: an"
|
|
31065
|
-
echo "absent measurement is not evidence a gate never fired."
|
|
31066
|
-
echo ""
|
|
31067
|
-
echo "This command NEVER promotes a gate. Promotion stays an explicit"
|
|
31068
|
-
echo "operator act via the named variable."
|
|
31069
|
-
return 0
|
|
31070
|
-
;;
|
|
31071
|
-
esac
|
|
31072
|
-
python3 "$lib" "$@"
|
|
31073
|
-
}
|
|
31074
|
-
|
|
31075
|
-
cmd_readiness() {
|
|
31076
|
-
local lib="${_LOKI_SCRIPT_DIR}/lib/agent_readiness.py"
|
|
31077
|
-
if [ ! -f "$lib" ]; then
|
|
31078
|
-
echo "readiness assessor is not installed at $lib" >&2
|
|
31079
|
-
return 2
|
|
31080
|
-
fi
|
|
31081
|
-
case "${1:-}" in
|
|
31082
|
-
--help|-h|help)
|
|
31083
|
-
echo -e "${BOLD}loki readiness${NC} - can an agent verify its own work in this repo?"
|
|
31084
|
-
echo ""
|
|
31085
|
-
echo "Usage: loki readiness [path] [--json] [--fix]"
|
|
31086
|
-
echo ""
|
|
31087
|
-
echo "Measures whether this repo gives an agent a way to check itself: a"
|
|
31088
|
-
echo "test command, a build, CI config, typed sources, a lockfile. Not"
|
|
31089
|
-
echo "general code quality -- the narrower question every agent depends on."
|
|
31090
|
-
echo ""
|
|
31091
|
-
echo "Deterministic: no model, no key, no spend. Same commit, same answer."
|
|
31092
|
-
echo "Criteria that cannot be determined report UNKNOWN rather than failing."
|
|
31093
|
-
echo ""
|
|
31094
|
-
echo -e "${BOLD}--fix${NC} writes the missing files whose content can be derived"
|
|
31095
|
-
echo "honestly (README.md, AGENTS.md, .gitignore) as TODO stubs, then"
|
|
31096
|
-
echo "re-measures and reports what is true AFTER the change."
|
|
31097
|
-
echo ""
|
|
31098
|
-
echo "It deliberately REFUSES to generate a test command, a lockfile or a"
|
|
31099
|
-
echo "CI config. Guessing one writes a line that lies: an invented"
|
|
31100
|
-
echo "'npm test' in a repo with no runner fails forever, and this check"
|
|
31101
|
-
echo "would then report the criterion present for something that does not"
|
|
31102
|
-
echo "work. Those stay reported, never generated."
|
|
31103
|
-
echo ""
|
|
31104
|
-
echo "Without --fix it is read-only and never writes to the repo."
|
|
31105
|
-
return 0
|
|
31106
|
-
;;
|
|
31107
|
-
esac
|
|
31108
|
-
python3 "$lib" "$@"
|
|
31109
|
-
}
|
|
31110
|
-
|
|
31111
30854
|
cmd_wiki() {
|
|
31112
30855
|
local subcmd="${1:-}"
|
|
31113
30856
|
shift 2>/dev/null || true
|
package/autonomy/notify.sh
CHANGED
|
@@ -86,68 +86,6 @@ _get_discord_color() {
|
|
|
86
86
|
# Slack Notifications
|
|
87
87
|
#===============================================================================
|
|
88
88
|
|
|
89
|
-
# Verdict fields for a Slack card, from the newest Evidence Receipt.
|
|
90
|
-
#
|
|
91
|
-
# Roadmap item 9: "put the receipt where review already happens". Slack
|
|
92
|
-
# notifications carried the event name and the project name and nothing else --
|
|
93
|
-
# grep for receipt/verdict/proof in this file returned 0. A team watching a
|
|
94
|
-
# channel saw "build finished" and had to go somewhere else to learn whether it
|
|
95
|
-
# was verified, which is the surface problem item 9 names: verification nobody
|
|
96
|
-
# sees does not build trust.
|
|
97
|
-
#
|
|
98
|
-
# Emits Slack `fields` entries (the payload already has that array), so this
|
|
99
|
-
# adds to the card rather than restructuring it.
|
|
100
|
-
#
|
|
101
|
-
# SILENT AND EMPTY ON ANY DOUBT. No receipt, unreadable JSON, no python -- emit
|
|
102
|
-
# nothing. A notification is a side channel; it must never fail a build, and it
|
|
103
|
-
# must never guess a verdict. An absent receipt yields no verdict field at all
|
|
104
|
-
# rather than a reassuring default.
|
|
105
|
-
_slack_verdict_fields() {
|
|
106
|
-
local loki_dir="${LOKI_DIR:-.loki}"
|
|
107
|
-
[ -d "$loki_dir/proofs" ] || return 0
|
|
108
|
-
command -v python3 >/dev/null 2>&1 || return 0
|
|
109
|
-
python3 - "$loki_dir" <<'PY' 2>/dev/null || true
|
|
110
|
-
import glob, json, os, sys
|
|
111
|
-
d = sys.argv[1]
|
|
112
|
-
paths = sorted(glob.glob(os.path.join(d, "proofs", "*", "proof.json")))
|
|
113
|
-
if not paths:
|
|
114
|
-
sys.exit(0)
|
|
115
|
-
try:
|
|
116
|
-
r = json.load(open(paths[-1]))
|
|
117
|
-
except (OSError, ValueError):
|
|
118
|
-
sys.exit(0)
|
|
119
|
-
|
|
120
|
-
def esc(s):
|
|
121
|
-
return json.dumps(str(s))[1:-1]
|
|
122
|
-
|
|
123
|
-
out = []
|
|
124
|
-
qg = r.get("quality_gates") or {}
|
|
125
|
-
gates = qg.get("gates") or []
|
|
126
|
-
if gates:
|
|
127
|
-
passed = sum(1 for g in gates if str(g.get("status", "")).startswith("pass"))
|
|
128
|
-
out.append(("Gates", f"{passed}/{len(gates)} passed"))
|
|
129
|
-
|
|
130
|
-
git = (r.get("facts") or {}).get("git") or {}
|
|
131
|
-
# base_sha empty means the receipt cannot be anchored, so it cannot be
|
|
132
|
-
# verified. Say that plainly rather than showing a diff count that implies it.
|
|
133
|
-
if not git.get("base_sha"):
|
|
134
|
-
out.append(("Verified", "no (unanchored receipt)"))
|
|
135
|
-
else:
|
|
136
|
-
diff = git.get("diff") or {}
|
|
137
|
-
if diff.get("count") is not None:
|
|
138
|
-
out.append(("Files changed", str(diff["count"])))
|
|
139
|
-
|
|
140
|
-
run_id = r.get("run_id")
|
|
141
|
-
if run_id:
|
|
142
|
-
out.append(("Receipt", esc(run_id)))
|
|
143
|
-
|
|
144
|
-
print(",".join(
|
|
145
|
-
'{"title": "%s", "value": "%s", "short": true}' % (esc(t), esc(v))
|
|
146
|
-
for t, v in out
|
|
147
|
-
))
|
|
148
|
-
PY
|
|
149
|
-
}
|
|
150
|
-
|
|
151
89
|
_notify_slack() {
|
|
152
90
|
local event="$1"
|
|
153
91
|
local title="$2"
|
|
@@ -168,13 +106,6 @@ _notify_slack() {
|
|
|
168
106
|
escaped_event="$(_json_escape "$event")"
|
|
169
107
|
escaped_project="$(_json_escape "$project")"
|
|
170
108
|
|
|
171
|
-
# Receipt-derived fields. Empty on any doubt, and the ${var:+,...} expansion
|
|
172
|
-
# below means an empty value adds no trailing comma -- a malformed payload
|
|
173
|
-
# would make Slack reject the whole card, so the failure mode of "no receipt"
|
|
174
|
-
# must be a card without verdict fields, never a card that does not send.
|
|
175
|
-
local verdict_fields
|
|
176
|
-
verdict_fields="$(_slack_verdict_fields 2>/dev/null || true)"
|
|
177
|
-
|
|
178
109
|
# Build Slack payload with attachment
|
|
179
110
|
local payload
|
|
180
111
|
payload=$(cat <<PAYLOAD
|
|
@@ -185,7 +116,7 @@ _notify_slack() {
|
|
|
185
116
|
"text": "$escaped_message",
|
|
186
117
|
"fields": [
|
|
187
118
|
{"title": "Event", "value": "$escaped_event", "short": true},
|
|
188
|
-
{"title": "Project", "value": "$escaped_project", "short": true}
|
|
119
|
+
{"title": "Project", "value": "$escaped_project", "short": true}
|
|
189
120
|
],
|
|
190
121
|
"footer": "Loki Mode",
|
|
191
122
|
"ts": $(date +%s)
|