loki-mode 9.12.6 → 9.17.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/README.md +81 -101
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/intent.sh +414 -0
- package/autonomy/issue-providers.sh +24 -0
- package/autonomy/lib/agent_readiness.py +280 -0
- package/autonomy/lib/claim_grounding.py +171 -0
- package/autonomy/lib/config-map.sh +10 -6
- package/autonomy/lib/decision_record.py +198 -0
- package/autonomy/lib/failure_memory.py +199 -0
- package/autonomy/lib/gate_policy.py +166 -0
- package/autonomy/lib/outcome_ledger.py +620 -0
- package/autonomy/lib/preedit_snapshot.py +216 -0
- package/autonomy/lib/proof-generator.py +71 -4
- package/autonomy/lib/verdict.py +204 -0
- package/autonomy/loki +430 -15
- package/autonomy/notify.sh +70 -1
- package/autonomy/provider-offer.sh +25 -1
- package/autonomy/queue-consumer.sh +290 -18
- package/autonomy/run.sh +527 -12
- package/autonomy/telemetry.sh +8 -1
- package/completions/_loki +5 -0
- package/completions/loki.bash +2 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/run.py +13 -2
- package/dashboard/scim.py +221 -0
- package/dashboard/server.py +190 -1
- package/dashboard/static/index.html +248 -55
- package/docs/GATE-FAILURE-TRIAGE.md +254 -0
- package/docs/LOOP-CANDIDATE-PROPOSAL-v1.md +167 -0
- package/docs/LOOP-HARNESS-AUDIT.md +53 -0
- package/docs/QUEUE-OPERATIONS.md +107 -0
- package/docs/VERIFICATION-COST.md +273 -0
- package/docs/WANG-PRINCIPLES-PLAN.md +1 -1
- package/loki-ts/dist/loki.js +414 -416
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +25 -0
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Intent Ledger: does the SPEC still say what the person actually wanted?
|
|
3
|
+
#
|
|
4
|
+
# THE PROBLEM THIS ANSWERS. Our verification proves code matches spec. It cannot
|
|
5
|
+
# prove the spec was RIGHT. 8090 AI documents a real build where "the software
|
|
6
|
+
# converged with the interpretation. The interpretation had diverged from the
|
|
7
|
+
# intent." A perfect verification gate passes that build, and the build is still
|
|
8
|
+
# wrong. That gap sits upstream of every gate we have, and no competitor ships an
|
|
9
|
+
# answer -- 8090 published the argument and their own "Tests" module has zero
|
|
10
|
+
# documentation pages and zero changelog entries.
|
|
11
|
+
#
|
|
12
|
+
# WHAT WE DELIBERATELY DID NOT BUILD. The obvious feature is a semantic fidelity
|
|
13
|
+
# score: ask a model "does this spec faithfully express this intent?" and print a
|
|
14
|
+
# percentage. That is an LLM judgment wearing the costume of a measurement, and
|
|
15
|
+
# it is precisely what our receipts exist to refuse -- they already separate
|
|
16
|
+
# deterministic FACTS from AI ASSESSMENTS. There is no similarity number, no
|
|
17
|
+
# embedding distance, and no percent-aligned figure anywhere in this file, and a
|
|
18
|
+
# future contributor adding one would be removing the reason it is trustworthy.
|
|
19
|
+
#
|
|
20
|
+
# WHAT IS ACTUALLY MEASURABLE, and it is enough. autonomy/spec.sh already
|
|
21
|
+
# persists a content_hash per requirement into .loki/spec/spec.lock. So if an
|
|
22
|
+
# intent statement records WHICH requirement it was affirmed against AND that
|
|
23
|
+
# requirement's hash AT THAT MOMENT, then divergence is pure hash comparison:
|
|
24
|
+
#
|
|
25
|
+
# the intent was affirmed against requirement R at hash H;
|
|
26
|
+
# R now hashes to H';
|
|
27
|
+
# nobody re-affirmed.
|
|
28
|
+
#
|
|
29
|
+
# That is 8090's failure mode, detected deterministically, re-derivable by hand,
|
|
30
|
+
# with no model in the loop. `content_hash_at_link` is the entire design -- store
|
|
31
|
+
# only a requirement id and every verdict collapses to UNKNOWN.
|
|
32
|
+
#
|
|
33
|
+
# WHAT ALREADY EXISTED (checked before building, not assumed). Three of the four
|
|
34
|
+
# things this was scoped to do are already shipped and are NOT rebuilt here:
|
|
35
|
+
# - ASSUMED-BUT-NOT-STATED -> spec-interrogation.sh:284 (.loki/assumptions/)
|
|
36
|
+
# - spec-vs-built divergence -> spec.sh + verify.sh:2161 (spec.lock, drift gate)
|
|
37
|
+
# - pre-committed predictions -> lib/expectation-ledger.py
|
|
38
|
+
# This file references the assumption store by id. A second store would drift
|
|
39
|
+
# from the first.
|
|
40
|
+
|
|
41
|
+
set -uo pipefail
|
|
42
|
+
|
|
43
|
+
_INTENT_SCHEMA_VERSION="1.0"
|
|
44
|
+
|
|
45
|
+
# Named refusals. Mirrors the outcome ledger's ANCHOR_REASONS: a status we cannot
|
|
46
|
+
# compute is reported by NAME, never as 0 and never as a pass. The most important
|
|
47
|
+
# entry is no_distinct_intent_source -- see intent_do_status.
|
|
48
|
+
_intent_unknown_reason() {
|
|
49
|
+
case "${1:-}" in
|
|
50
|
+
no_intent_record) echo "no intent has been recorded (run: loki intent record)" ;;
|
|
51
|
+
no_spec_lock) echo "no .loki/spec/spec.lock (run: loki spec lock)" ;;
|
|
52
|
+
no_distinct_intent_source) echo "the spec IS the user's own document, so intent and interpretation are the same artifact" ;;
|
|
53
|
+
link_missing_content_hash) echo "link predates hash recording, so staleness cannot be computed" ;;
|
|
54
|
+
requirement_id_not_in_lock) echo "the linked requirement id is not in the current lock" ;;
|
|
55
|
+
*) echo "unmeasurable" ;;
|
|
56
|
+
esac
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_intent_dir() { echo "${LOKI_DIR:-.loki}/intent"; }
|
|
60
|
+
_intent_file() { echo "$(_intent_dir)/intent.json"; }
|
|
61
|
+
_intent_lock() { echo "${LOKI_DIR:-.loki}/spec/spec.lock"; }
|
|
62
|
+
|
|
63
|
+
_intent_now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
|
64
|
+
|
|
65
|
+
_intent_sha256() {
|
|
66
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
67
|
+
printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
|
|
68
|
+
else
|
|
69
|
+
printf '%s' "$1" | sha256sum | awk '{print $1}'
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# record: capture an intent statement as a first-class artifact.
|
|
75
|
+
#
|
|
76
|
+
# THE TEXT IS COPIED, NEVER REFERENCED. autonomy/loki:2079 deletes
|
|
77
|
+
# .loki/state/brief.txt at the start of a later non-brief run, deliberately, so a
|
|
78
|
+
# stale one-liner cannot be inherited. An intent record whose only evidence is a
|
|
79
|
+
# file that a later run removes would be unmeasurable by construction, so the
|
|
80
|
+
# statement text and its sha256 are stored inline.
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
intent_do_record() {
|
|
83
|
+
local statement=""
|
|
84
|
+
local source="explicit"
|
|
85
|
+
while [ $# -gt 0 ]; do
|
|
86
|
+
case "$1" in
|
|
87
|
+
--statement) statement="${2:-}"; shift 2 ;;
|
|
88
|
+
*) shift ;;
|
|
89
|
+
esac
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
if [ -z "$statement" ]; then
|
|
93
|
+
local brief="${LOKI_DIR:-.loki}/state/brief.txt"
|
|
94
|
+
if [ -f "$brief" ]; then
|
|
95
|
+
statement="$(cat "$brief" 2>/dev/null || true)"
|
|
96
|
+
source="brief"
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [ -z "$statement" ]; then
|
|
101
|
+
echo "No intent to record." >&2
|
|
102
|
+
echo "Give one explicitly: loki intent record --statement \"what you actually want\"" >&2
|
|
103
|
+
return 2
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
local dir; dir="$(_intent_dir)"
|
|
107
|
+
mkdir -p "$dir" || return 3
|
|
108
|
+
local file; file="$(_intent_file)"
|
|
109
|
+
local sha; sha="$(_intent_sha256 "$statement")"
|
|
110
|
+
local sid="${sha:0:12}"
|
|
111
|
+
local now; now="$(_intent_now)"
|
|
112
|
+
|
|
113
|
+
python3 - "$file" "$sid" "$statement" "$sha" "$source" "$now" "$_INTENT_SCHEMA_VERSION" <<'PYEOF'
|
|
114
|
+
import json, os, sys
|
|
115
|
+
path, sid, text, sha, source, now, schema = sys.argv[1:8]
|
|
116
|
+
doc = {"schema_version": schema, "statements": []}
|
|
117
|
+
if os.path.isfile(path):
|
|
118
|
+
try:
|
|
119
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
120
|
+
doc = json.load(fh)
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
doc.setdefault("statements", [])
|
|
124
|
+
# Idempotent on statement id, matching spec_ledger_write: recording the same
|
|
125
|
+
# intent twice must not create a second record or reset its link history.
|
|
126
|
+
for s in doc["statements"]:
|
|
127
|
+
if s.get("id") == sid:
|
|
128
|
+
print("already recorded: " + sid)
|
|
129
|
+
sys.exit(0)
|
|
130
|
+
doc["statements"].append({
|
|
131
|
+
"id": sid,
|
|
132
|
+
"text": text,
|
|
133
|
+
"text_sha256": sha,
|
|
134
|
+
"source": source,
|
|
135
|
+
"recorded_at": now,
|
|
136
|
+
"assumption_ids": [],
|
|
137
|
+
"links": [],
|
|
138
|
+
})
|
|
139
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
140
|
+
json.dump(doc, fh, indent=2)
|
|
141
|
+
fh.write("\n")
|
|
142
|
+
print("recorded: " + sid)
|
|
143
|
+
PYEOF
|
|
144
|
+
return $?
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# link: bind an intent statement to a spec requirement AT ITS CURRENT HASH.
|
|
149
|
+
#
|
|
150
|
+
# Capturing content_hash_at_link is the whole measurement. Without it a link
|
|
151
|
+
# records only "these are related", which no later comparison can falsify.
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
intent_do_link() {
|
|
154
|
+
local sid="${1:-}" rid="${2:-}"
|
|
155
|
+
if [ -z "$sid" ] || [ -z "$rid" ]; then
|
|
156
|
+
echo "Usage: loki intent link <statement_id> <requirement_id>" >&2
|
|
157
|
+
return 2
|
|
158
|
+
fi
|
|
159
|
+
local file; file="$(_intent_file)"
|
|
160
|
+
local lock; lock="$(_intent_lock)"
|
|
161
|
+
[ -f "$file" ] || { echo "no intent record; run: loki intent record" >&2; return 2; }
|
|
162
|
+
[ -f "$lock" ] || { echo "no spec.lock; run: loki spec lock" >&2; return 2; }
|
|
163
|
+
|
|
164
|
+
python3 - "$file" "$lock" "$sid" "$rid" "$(_intent_now)" <<'PYEOF'
|
|
165
|
+
import hashlib, json, sys
|
|
166
|
+
ipath, lpath, sid, rid, now = sys.argv[1:6]
|
|
167
|
+
with open(ipath, "r", encoding="utf-8") as fh:
|
|
168
|
+
doc = json.load(fh)
|
|
169
|
+
with open(lpath, "r", encoding="utf-8") as fh:
|
|
170
|
+
raw = fh.read()
|
|
171
|
+
lock = json.loads(raw)
|
|
172
|
+
lock_hash = hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
173
|
+
|
|
174
|
+
req = None
|
|
175
|
+
for r in lock.get("requirements", []):
|
|
176
|
+
if r.get("id") == rid:
|
|
177
|
+
req = r
|
|
178
|
+
break
|
|
179
|
+
if req is None:
|
|
180
|
+
sys.stderr.write("requirement id not in spec.lock: " + rid + "\n")
|
|
181
|
+
sys.exit(2)
|
|
182
|
+
|
|
183
|
+
stmt = None
|
|
184
|
+
for s in doc.get("statements", []):
|
|
185
|
+
if s.get("id") == sid:
|
|
186
|
+
stmt = s
|
|
187
|
+
break
|
|
188
|
+
if stmt is None:
|
|
189
|
+
sys.stderr.write("statement id not recorded: " + sid + "\n")
|
|
190
|
+
sys.exit(2)
|
|
191
|
+
|
|
192
|
+
for l in stmt.setdefault("links", []):
|
|
193
|
+
if l.get("requirement_id") == rid:
|
|
194
|
+
print("already linked: " + sid + " -> " + rid)
|
|
195
|
+
sys.exit(0)
|
|
196
|
+
|
|
197
|
+
ch = req.get("content_hash", "")
|
|
198
|
+
stmt["links"].append({
|
|
199
|
+
"requirement_id": rid,
|
|
200
|
+
"content_hash_at_link": ch,
|
|
201
|
+
"spec_lock_hash_at_link": lock_hash,
|
|
202
|
+
"affirmations": [{"content_hash": ch, "affirmed_at": now, "affirmed_by": "human"}],
|
|
203
|
+
})
|
|
204
|
+
with open(ipath, "w", encoding="utf-8") as fh:
|
|
205
|
+
json.dump(doc, fh, indent=2)
|
|
206
|
+
fh.write("\n")
|
|
207
|
+
print("linked: " + sid + " -> " + rid + " at " + ch[:12])
|
|
208
|
+
PYEOF
|
|
209
|
+
return $?
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# affirm: re-affirm an intent against the requirement's CURRENT hash.
|
|
214
|
+
#
|
|
215
|
+
# WHY THIS EXISTS AND IS NOT OPTIONAL. Permanent idempotence is right for the
|
|
216
|
+
# assumption ledger and wrong here: without re-affirmation the first legitimate
|
|
217
|
+
# spec edit makes a statement STALE forever, the gate stays red, and a run grinds
|
|
218
|
+
# to max iterations against a finding no action can clear. That is the exact
|
|
219
|
+
# failure spec-interrogation.sh's own header documents for unresolved
|
|
220
|
+
# contradictions.
|
|
221
|
+
#
|
|
222
|
+
# It APPENDS rather than overwrites, so "this intent was re-affirmed across three
|
|
223
|
+
# successive versions of R" stays visible. The history is itself the useful fact.
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
intent_do_affirm() {
|
|
226
|
+
local sid="${1:-}"
|
|
227
|
+
[ -n "$sid" ] || { echo "Usage: loki intent affirm <statement_id>" >&2; return 2; }
|
|
228
|
+
local file; file="$(_intent_file)"
|
|
229
|
+
local lock; lock="$(_intent_lock)"
|
|
230
|
+
[ -f "$file" ] || { echo "no intent record" >&2; return 2; }
|
|
231
|
+
[ -f "$lock" ] || { echo "no spec.lock" >&2; return 2; }
|
|
232
|
+
|
|
233
|
+
python3 - "$file" "$lock" "$sid" "$(_intent_now)" <<'PYEOF'
|
|
234
|
+
import json, sys
|
|
235
|
+
ipath, lpath, sid, now = sys.argv[1:5]
|
|
236
|
+
with open(ipath, "r", encoding="utf-8") as fh:
|
|
237
|
+
doc = json.load(fh)
|
|
238
|
+
with open(lpath, "r", encoding="utf-8") as fh:
|
|
239
|
+
lock = json.load(fh)
|
|
240
|
+
by_id = {r.get("id"): r for r in lock.get("requirements", [])}
|
|
241
|
+
|
|
242
|
+
n = 0
|
|
243
|
+
for s in doc.get("statements", []):
|
|
244
|
+
if s.get("id") != sid:
|
|
245
|
+
continue
|
|
246
|
+
for l in s.get("links", []):
|
|
247
|
+
req = by_id.get(l.get("requirement_id"))
|
|
248
|
+
if req is None:
|
|
249
|
+
continue
|
|
250
|
+
ch = req.get("content_hash", "")
|
|
251
|
+
l.setdefault("affirmations", []).append(
|
|
252
|
+
{"content_hash": ch, "affirmed_at": now, "affirmed_by": "human"})
|
|
253
|
+
n += 1
|
|
254
|
+
if n == 0:
|
|
255
|
+
sys.stderr.write("nothing to affirm for: " + sid + "\n")
|
|
256
|
+
sys.exit(2)
|
|
257
|
+
with open(ipath, "w", encoding="utf-8") as fh:
|
|
258
|
+
json.dump(doc, fh, indent=2)
|
|
259
|
+
fh.write("\n")
|
|
260
|
+
print("affirmed " + str(n) + " link(s) for " + sid)
|
|
261
|
+
PYEOF
|
|
262
|
+
return $?
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
# status: the divergence report. Every verdict is a fact or a named refusal.
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
intent_do_status() {
|
|
269
|
+
local as_json=0
|
|
270
|
+
[ "${1:-}" = "--json" ] && as_json=1
|
|
271
|
+
local file; file="$(_intent_file)"
|
|
272
|
+
local lock; lock="$(_intent_lock)"
|
|
273
|
+
|
|
274
|
+
if [ ! -f "$file" ]; then
|
|
275
|
+
if [ "$as_json" = "1" ]; then
|
|
276
|
+
printf '{"schema_version":"%s","status":"UNKNOWN","reason":"no_intent_record","detail":"%s"}\n' \
|
|
277
|
+
"$_INTENT_SCHEMA_VERSION" "$(_intent_unknown_reason no_intent_record)"
|
|
278
|
+
else
|
|
279
|
+
echo "Intent Ledger: UNKNOWN"
|
|
280
|
+
echo " $(_intent_unknown_reason no_intent_record)"
|
|
281
|
+
echo ""
|
|
282
|
+
echo " Intent is not the spec. A spec you wrote yourself is your"
|
|
283
|
+
echo " INTERPRETATION already; recording intent separately is what makes"
|
|
284
|
+
echo " drift between the two measurable at all."
|
|
285
|
+
fi
|
|
286
|
+
return 0
|
|
287
|
+
fi
|
|
288
|
+
if [ ! -f "$lock" ]; then
|
|
289
|
+
if [ "$as_json" = "1" ]; then
|
|
290
|
+
printf '{"schema_version":"%s","status":"UNKNOWN","reason":"no_spec_lock","detail":"%s"}\n' \
|
|
291
|
+
"$_INTENT_SCHEMA_VERSION" "$(_intent_unknown_reason no_spec_lock)"
|
|
292
|
+
else
|
|
293
|
+
echo "Intent Ledger: UNKNOWN"
|
|
294
|
+
echo " $(_intent_unknown_reason no_spec_lock)"
|
|
295
|
+
fi
|
|
296
|
+
return 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
python3 - "$file" "$lock" "$as_json" "$_INTENT_SCHEMA_VERSION" <<'PYEOF'
|
|
300
|
+
import json, sys
|
|
301
|
+
ipath, lpath, as_json, schema = sys.argv[1:5]
|
|
302
|
+
as_json = as_json == "1"
|
|
303
|
+
with open(ipath, "r", encoding="utf-8") as fh:
|
|
304
|
+
doc = json.load(fh)
|
|
305
|
+
with open(lpath, "r", encoding="utf-8") as fh:
|
|
306
|
+
lock = json.load(fh)
|
|
307
|
+
by_id = {r.get("id"): r for r in lock.get("requirements", [])}
|
|
308
|
+
|
|
309
|
+
rows = []
|
|
310
|
+
for s in doc.get("statements", []):
|
|
311
|
+
links = s.get("links") or []
|
|
312
|
+
if not links:
|
|
313
|
+
rows.append({"statement_id": s.get("id"), "verdict": "UNLINKED",
|
|
314
|
+
"detail": "recorded but never linked to a requirement"})
|
|
315
|
+
continue
|
|
316
|
+
for l in links:
|
|
317
|
+
rid = l.get("requirement_id")
|
|
318
|
+
row = {"statement_id": s.get("id"), "requirement_id": rid}
|
|
319
|
+
if rid not in by_id:
|
|
320
|
+
row["verdict"] = "LINKED-REMOVED"
|
|
321
|
+
row["detail"] = "the linked requirement is no longer in the spec"
|
|
322
|
+
rows.append(row); continue
|
|
323
|
+
affs = l.get("affirmations") or []
|
|
324
|
+
last = affs[-1].get("content_hash") if affs else l.get("content_hash_at_link")
|
|
325
|
+
if not last:
|
|
326
|
+
row["verdict"] = "UNKNOWN"
|
|
327
|
+
row["reason"] = "link_missing_content_hash"
|
|
328
|
+
rows.append(row); continue
|
|
329
|
+
current = by_id[rid].get("content_hash", "")
|
|
330
|
+
if current == last:
|
|
331
|
+
row["verdict"] = "LINKED-CURRENT"
|
|
332
|
+
row["affirmations"] = len(affs)
|
|
333
|
+
else:
|
|
334
|
+
row["verdict"] = "LINKED-STALE"
|
|
335
|
+
row["detail"] = ("affirmed at " + last[:12] + ", requirement is now "
|
|
336
|
+
+ current[:12] + " and nobody re-affirmed")
|
|
337
|
+
row["affirmations"] = len(affs)
|
|
338
|
+
rows.append(row)
|
|
339
|
+
|
|
340
|
+
stale = [r for r in rows if r["verdict"] in ("LINKED-STALE", "LINKED-REMOVED")]
|
|
341
|
+
summary = {
|
|
342
|
+
"statements": len(doc.get("statements", [])),
|
|
343
|
+
"linked_current": len([r for r in rows if r["verdict"] == "LINKED-CURRENT"]),
|
|
344
|
+
"linked_stale": len([r for r in rows if r["verdict"] == "LINKED-STALE"]),
|
|
345
|
+
"linked_removed": len([r for r in rows if r["verdict"] == "LINKED-REMOVED"]),
|
|
346
|
+
"unlinked": len([r for r in rows if r["verdict"] == "UNLINKED"]),
|
|
347
|
+
"unknown": len([r for r in rows if r["verdict"] == "UNKNOWN"]),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if as_json:
|
|
351
|
+
print(json.dumps({"schema_version": schema, "summary": summary,
|
|
352
|
+
"rows": rows}, indent=2))
|
|
353
|
+
else:
|
|
354
|
+
print("Intent Ledger -- does the spec still say what was actually wanted?")
|
|
355
|
+
print("")
|
|
356
|
+
for r in rows:
|
|
357
|
+
line = " " + str(r.get("statement_id", "?"))[:14].ljust(16)
|
|
358
|
+
line += r["verdict"].ljust(16)
|
|
359
|
+
if r.get("requirement_id"):
|
|
360
|
+
line += str(r["requirement_id"])[:24].ljust(26)
|
|
361
|
+
if r.get("detail"):
|
|
362
|
+
line += " " + r["detail"]
|
|
363
|
+
print(line)
|
|
364
|
+
print("")
|
|
365
|
+
print(" statements " + str(summary["statements"])
|
|
366
|
+
+ " current " + str(summary["linked_current"])
|
|
367
|
+
+ " stale " + str(summary["linked_stale"])
|
|
368
|
+
+ " removed " + str(summary["linked_removed"])
|
|
369
|
+
+ " unlinked " + str(summary["unlinked"]))
|
|
370
|
+
if stale:
|
|
371
|
+
print("")
|
|
372
|
+
print(" A stale link is the failure a verification gate cannot see: the")
|
|
373
|
+
print(" code still matches the spec, and the spec moved away from what")
|
|
374
|
+
print(" was wanted. Re-affirm once you agree with the change:")
|
|
375
|
+
print(" loki intent affirm <statement_id>")
|
|
376
|
+
|
|
377
|
+
sys.exit(1 if stale else 0)
|
|
378
|
+
PYEOF
|
|
379
|
+
return $?
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
intent_help() {
|
|
383
|
+
echo "loki intent - does the spec still say what was actually wanted?"
|
|
384
|
+
echo ""
|
|
385
|
+
echo "Usage: loki intent <subcommand>"
|
|
386
|
+
echo ""
|
|
387
|
+
echo " record [--statement \"...\"] record intent as a first-class artifact"
|
|
388
|
+
echo " link <stmt_id> <req_id> bind intent to a requirement at its current hash"
|
|
389
|
+
echo " affirm <stmt_id> re-affirm after an agreed spec change"
|
|
390
|
+
echo " status [--json] divergence report"
|
|
391
|
+
echo ""
|
|
392
|
+
echo "Verification proves code matches spec. It cannot prove the spec was"
|
|
393
|
+
echo "right. This measures the other half, deterministically: an intent"
|
|
394
|
+
echo "affirmed against requirement R at hash H reads LINKED-STALE once R"
|
|
395
|
+
echo "changes and nobody re-affirms. No model judges anything here, and there"
|
|
396
|
+
echo "is deliberately no similarity score."
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
intent_main() {
|
|
400
|
+
local sub="${1:-}"
|
|
401
|
+
[ $# -gt 0 ] && shift
|
|
402
|
+
case "$sub" in
|
|
403
|
+
record) intent_do_record "$@" ;;
|
|
404
|
+
link) intent_do_link "$@" ;;
|
|
405
|
+
affirm) intent_do_affirm "$@" ;;
|
|
406
|
+
status) intent_do_status "$@" ;;
|
|
407
|
+
""|--help|-h|help) intent_help ;;
|
|
408
|
+
*) echo "unknown subcommand: $sub" >&2; intent_help >&2; return 2 ;;
|
|
409
|
+
esac
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
413
|
+
intent_main "$@"
|
|
414
|
+
fi
|
|
@@ -380,6 +380,29 @@ repo = data.get('repo', '')
|
|
|
380
380
|
|
|
381
381
|
labels_str = ', '.join(labels) if labels else ''
|
|
382
382
|
|
|
383
|
+
_ac_text = (title + ' ' + body).lower()
|
|
384
|
+
_ac_rules = [
|
|
385
|
+
(['save', 'persist', 'store', 'databas', 'crud'], 'PERSIST',
|
|
386
|
+
'Data the change writes survives a restart (a real store, not in-memory state).'),
|
|
387
|
+
(['auth', 'login', 'sign in', 'session', 'permission'], 'AUTH',
|
|
388
|
+
'The auth path is exercised end to end including the denied case (401/403), not only the happy path.'),
|
|
389
|
+
(['api', 'endpoint', 'rest', 'graphql', 'route'], 'API',
|
|
390
|
+
'Each affected endpoint returns the documented status codes and is callable without a browser.'),
|
|
391
|
+
(['payment', 'stripe', 'billing', 'invoice', 'subscription'], 'PAY',
|
|
392
|
+
'The payment path runs against provider test mode; no mocked charge stands in for the integration.'),
|
|
393
|
+
(['bug', 'fix', 'regression', 'broken', 'crash', 'error'], 'REPRO',
|
|
394
|
+
'A test reproduces the reported failure and FAILS before the fix, then passes after it.'),
|
|
395
|
+
(['perf', 'slow', 'latency', 'timeout', 'memory leak'], 'PERF',
|
|
396
|
+
'The improvement is measured before and after, and the numbers appear in the change.'),
|
|
397
|
+
(['security', 'vulnerab', 'injection', 'xss', 'csrf'], 'SEC',
|
|
398
|
+
'A test demonstrates the vulnerable behavior is refused after the change.'),
|
|
399
|
+
|
|
400
|
+
]
|
|
401
|
+
# Stable, content-derived IDs: the axis comes from WHICH rule fired, so adding
|
|
402
|
+
# a rule never renumbers an existing criterion and a receipt can cite one.
|
|
403
|
+
_hits = [(ax, c) for kws, ax, c in _ac_rules if any(k in _ac_text for k in kws)]
|
|
404
|
+
derived_ac = ('\n'.join(f'- AC-{ax}-001: {c}' for ax, c in _hits) + '\n') if _hits else ''
|
|
405
|
+
|
|
383
406
|
prd = f'''# PRD: {title}
|
|
384
407
|
|
|
385
408
|
**Source:** {provider.replace('_', ' ').title()} Issue [{number}]({url})
|
|
@@ -408,6 +431,7 @@ Based on the issue description, implement the following:
|
|
|
408
431
|
2. Ensure backward compatibility (unless explicitly breaking changes are requested)
|
|
409
432
|
3. Add appropriate tests for new functionality
|
|
410
433
|
4. Update documentation as needed
|
|
434
|
+
{derived_ac}
|
|
411
435
|
|
|
412
436
|
---
|
|
413
437
|
|