loki-mode 9.12.5 → 9.16.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/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.12.5'
78
+ __version__ = '9.16.0'
@@ -147,6 +147,31 @@ def _load_real_fastmcp():
147
147
  for _k in ("mcp", "mcp.server"):
148
148
  if _k in _saved_local:
149
149
  sys.modules[_k] = _saved_local[_k]
150
+ elif _k in sys.modules:
151
+ # The SDK claimed a name the local package had NOT yet loaded,
152
+ # so there is no local module to re-pin and the old code left
153
+ # the SDK's module cached under it PERMANENTLY, process-wide.
154
+ #
155
+ # Measured (issue #190, python3.12 + SDK, from the repo root):
156
+ # any caller that reaches the loader before our own
157
+ # mcp/server.py is imported -- mcp/lsp_proxy.py, or a test that
158
+ # imports mcp._sdk_loader directly -- leaves the SDK's
159
+ # site-packages/mcp/server/__init__.py under "mcp.server". Every
160
+ # later `from mcp import server` in that process then silently
161
+ # gets the SDK instead of ours. That is how
162
+ # tests/test_mcp_agent_metrics_honesty.py skipped for its full
163
+ # life: it passed 7/7 alone but hit its capability guard in a
164
+ # full run, because tests/mcp/* ran first and poisoned the name.
165
+ #
166
+ # Deleting is correct where re-pinning is impossible: it leaves
167
+ # the name UNCLAIMED, so the next `import mcp.server` resolves
168
+ # through the local package exactly as it would have. Verified
169
+ # this does not disturb the SDK's own runtime subtree --
170
+ # mcp.types, mcp.server.lowlevel and mcp.server.fastmcp.* stay
171
+ # cached and FastMCP still instantiates, because FastMCP holds
172
+ # direct references to its dependencies rather than re-resolving
173
+ # "mcp.server" by name.
174
+ del sys.modules[_k]
150
175
  for _k, _v in _saved_local.items():
151
176
  # Restore any other purely-local submodules that the SDK import did
152
177
  # not legitimately replace (e.g. mcp.magic_tools, mcp.tools).
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": "9.12.5",
4
+ "version": "9.16.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": "9.12.5",
5
+ "version": "9.16.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",
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """loop-harness-v1: what the verifiers in a run actually recorded.
3
+
4
+ READ-ONLY. Opens .loki/events.jsonl and nothing else. Writes nothing, spawns
5
+ nothing, and touches no runtime path. Rollback is deleting this file.
6
+
7
+ WHY THIS REPORTS MOSTLY UNKNOWN, AND WHY THAT IS THE POINT. A loop-harness
8
+ manifest wants, per verifier invocation: eligibility, the deterministic
9
+ criterion, retry and timeout caps, latency, tokens, cash cost, the verdict,
10
+ whether it changed the terminal outcome, false-positive review, and a rollback
11
+ switch.
12
+
13
+ Measured at v9.12.5, the runtime emits almost none of that:
14
+
15
+ code_review_complete review_id, source, iteration
16
+ review_verification_failed reason, iteration, implementation_retry
17
+ _evidence_gate_and_surface nothing structured
18
+ _invariant_gate_and_surface nothing structured
19
+ _semantic_gate_and_surface nothing structured
20
+
21
+ So this reader CANNOT derive most columns. It reports them UNKNOWN rather than
22
+ defaulting them, because a manifest that fills a cost column with 0.0 or an
23
+ eligibility column with "yes" is asserting a measurement nobody took -- and a
24
+ fabricated manifest about verification is worse than no manifest at all.
25
+
26
+ The UNKNOWNs are the deliverable. They say exactly which instrumentation is
27
+ missing, so a later decision to add it is evidenced rather than assumed.
28
+
29
+ Exit codes follow this repo's convention:
30
+ 0 verifier records were found and reported
31
+ 2 the trace could not be read
32
+ 3 nothing to check -- no verifier events in this workspace
33
+ 64 usage error
34
+ """
35
+
36
+ import argparse
37
+ import json
38
+ import os
39
+ import sys
40
+
41
+ sys.dont_write_bytecode = True
42
+
43
+ UNKNOWN = "UNKNOWN"
44
+
45
+ # Event types that represent a verifier doing something. Derived from
46
+ # autonomy/run.sh's emit_event_json call sites, not invented here.
47
+ _VERIFIER_EVENTS = {
48
+ "code_review_start",
49
+ "code_review_complete",
50
+ "code_review_council_complete",
51
+ "review_verification_failed",
52
+ "managed_review_council_ok",
53
+ "gate_stuck",
54
+ "policy_denied",
55
+ "task_completion_claim",
56
+ }
57
+
58
+ # Fields a loop-harness manifest wants, and where each one comes from today.
59
+ # "" means no emitter records it, so the column reads UNKNOWN for every row.
60
+ _FIELD_SOURCES = {
61
+ "verifier": "event type",
62
+ "iteration": "iteration",
63
+ "verdict": "partial: only the failure path records a reason",
64
+ "eligible": "",
65
+ "criterion": "",
66
+ "retry_cap": "",
67
+ "timeout_cap": "",
68
+ "latency_ms": "",
69
+ "tokens": "",
70
+ "cost_usd": "",
71
+ "changed_terminal_outcome": "",
72
+ "false_positive_reviewed": "",
73
+ "rollback_switch": "",
74
+ }
75
+
76
+
77
+ class _Parser(argparse.ArgumentParser):
78
+ """Usage errors exit 64, not argparse's default 2.
79
+
80
+ In this repo 2 means "could NOT be checked" -- a real answer about the
81
+ subject. A mistyped flag is not that.
82
+ """
83
+
84
+ def error(self, message):
85
+ self.print_usage(sys.stderr)
86
+ sys.stderr.write("%s: error: %s\n" % (self.prog, message))
87
+ raise SystemExit(64)
88
+
89
+
90
+ def _events(path):
91
+ """Every well-formed record, oldest first. A torn line is skipped.
92
+
93
+ events.jsonl is appended to by concurrent shell writers, so a partial
94
+ final line is normal operation and must not blank out the history behind
95
+ it.
96
+ """
97
+ out = []
98
+ try:
99
+ with open(path, "r", encoding="utf-8", errors="replace") as fh:
100
+ for line in fh:
101
+ line = line.strip()
102
+ if not line:
103
+ continue
104
+ try:
105
+ rec = json.loads(line)
106
+ except (ValueError, json.JSONDecodeError):
107
+ continue
108
+ if isinstance(rec, dict):
109
+ out.append(rec)
110
+ except OSError:
111
+ return None
112
+ return out
113
+
114
+
115
+ def _row(rec):
116
+ """One verifier invocation, with every underivable field UNKNOWN."""
117
+ data = rec.get("data") if isinstance(rec.get("data"), dict) else {}
118
+ etype = rec.get("type") or UNKNOWN
119
+
120
+ verdict = UNKNOWN
121
+ if etype in ("code_review_complete", "managed_review_council_ok"):
122
+ # Completion is not a verdict: these fire when the council FINISHED,
123
+ # not when it approved. Recording "pass" here would invent an outcome.
124
+ verdict = UNKNOWN
125
+ elif etype == "review_verification_failed":
126
+ verdict = data.get("reason") or "failed"
127
+ elif etype == "policy_denied":
128
+ verdict = "denied"
129
+
130
+ row = {
131
+ "verifier": etype,
132
+ "timestamp": rec.get("timestamp") or UNKNOWN,
133
+ "iteration": data.get("iteration", UNKNOWN),
134
+ "verdict": verdict,
135
+ }
136
+ for field, source in _FIELD_SOURCES.items():
137
+ if field in row:
138
+ continue
139
+ row[field] = UNKNOWN if not source else data.get(field, UNKNOWN)
140
+ return row
141
+
142
+
143
+ def report(loki_dir):
144
+ """The manifest envelope. Empty results always carry a reason."""
145
+ path = os.path.join(loki_dir, "events.jsonl")
146
+ env = {
147
+ "report": "loop-harness-v1",
148
+ "source": [path],
149
+ "rows": [],
150
+ "count": 0,
151
+ "unmeasured_fields": sorted(f for f, s in _FIELD_SOURCES.items()
152
+ if not s),
153
+ "reason": None,
154
+ }
155
+ if not os.path.isdir(loki_dir):
156
+ env["reason"] = "no .loki directory at %s" % loki_dir
157
+ return env, 3
158
+ if not os.path.isfile(path):
159
+ env["reason"] = ("%s does not exist, so no verifier ever recorded "
160
+ "anything here" % path)
161
+ return env, 3
162
+
163
+ records = _events(path)
164
+ if records is None:
165
+ env["reason"] = "%s could not be read" % path
166
+ return env, 2
167
+
168
+ rows = [_row(r) for r in records if r.get("type") in _VERIFIER_EVENTS]
169
+ if not rows:
170
+ # NOT an empty table. "no verifier events" and "the verifiers all
171
+ # passed" are different claims, and only one of them is supported.
172
+ env["reason"] = (
173
+ "no verifier events among %d records in this trace (the run "
174
+ "predates verifier tracing, or no verifier ran)" % len(records))
175
+ return env, 3
176
+
177
+ env["rows"] = rows
178
+ env["count"] = len(rows)
179
+ return env, 0
180
+
181
+
182
+ def main(argv=None):
183
+ parser = _Parser(description="loop-harness-v1 verifier manifest (read-only)")
184
+ parser.add_argument("--loki-dir", default=os.environ.get("LOKI_DIR")
185
+ or os.path.join(os.getcwd(), ".loki"))
186
+ parser.add_argument("--json", action="store_true")
187
+ args = parser.parse_args(argv)
188
+
189
+ env, code = report(args.loki_dir)
190
+
191
+ if args.json:
192
+ print(json.dumps(env, indent=2))
193
+ return code
194
+
195
+ print("loop-harness-v1 source: %s" % ", ".join(env["source"]))
196
+ if not env["rows"]:
197
+ print(" nothing to check: %s" % env["reason"])
198
+ else:
199
+ print(" %-30s %-10s %s" % ("VERIFIER", "ITERATION", "VERDICT"))
200
+ for r in env["rows"]:
201
+ print(" %-30s %-10s %s"
202
+ % (r["verifier"], r["iteration"], r["verdict"]))
203
+ print(" %d verifier records" % env["count"])
204
+ print("")
205
+ print(" NOT RECORDED BY THE RUNTIME (every row reads UNKNOWN):")
206
+ for f in env["unmeasured_fields"]:
207
+ print(" - %s" % f)
208
+ print(" These are the fields a loop-harness manifest needs and the")
209
+ print(" runtime does not emit. They are reported UNKNOWN rather than")
210
+ print(" defaulted, because a fabricated verification manifest is worse")
211
+ print(" than an absent one.")
212
+ return code
213
+
214
+
215
+ if __name__ == "__main__":
216
+ raise SystemExit(main())