@prism-d1/cli 1.0.89 → 1.0.91

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.
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env python3
2
+ """Run the full coding agent cycle inside the harness microVM.
3
+
4
+ Called by a single InvokeAgentRuntimeCommand from the workflow. Does everything:
5
+ clone → mise install → deps → agent loop → collect patch → verify → emit telemetry
6
+
7
+ Outputs a JSON object on stdout that the workflow reads for outcome, patch, and
8
+ telemetry correlation. The patch itself is printed as a separate clearly-delimited
9
+ section so the workflow can extract it.
10
+
11
+ Exit 0 always (the outcome is in the JSON, not the exit code). A non-zero exit
12
+ would make the InvokeAgentRuntimeCommand report a generic failure rather than the
13
+ structured result the workflow needs.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import shlex
22
+ import subprocess
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+
27
+ # The image has these at /opt/prism-agent via PYTHONPATH
28
+ from agentcore.session import (
29
+ WORKSPACE,
30
+ BASE_SHA_FILE,
31
+ _exclude_pathspecs,
32
+ )
33
+ from agentcore.telemetry import RunTelemetry, emit_run, CollectorConfig
34
+ from config import load_config
35
+
36
+
37
+ CLONE_TIMEOUT = 600
38
+ TOOLCHAIN_TIMEOUT = 900
39
+ DEPS_TIMEOUT = 1200
40
+ AGENT_TIMEOUT = 1500
41
+ VERIFY_TIMEOUT = 600
42
+
43
+ # SSM parameter paths → environment variable mapping for telemetry
44
+ _SSM_TELEMETRY_MAP = {
45
+ "/prism/d1/collector-url": "PRISM_COLLECTOR_URL",
46
+ "/prism/d1/token-endpoint": "PRISM_OIDC_TOKEN_ENDPOINT",
47
+ "/prism/d1/agent-secret-id": "PRISM_AGENT_SECRET_ID",
48
+ }
49
+
50
+
51
+ def _load_telemetry_config_from_ssm(region: str) -> None:
52
+ """Read telemetry SSM params into env vars so CollectorConfig.from_env() works.
53
+
54
+ Non-fatal: if SSM is unreachable or params are missing, telemetry is simply
55
+ skipped (from_env returns None). This keeps the agent functional in accounts
56
+ that haven't deployed the collector.
57
+ """
58
+ if os.environ.get("PRISM_COLLECTOR_URL"):
59
+ return # already set (e.g. local dev), don't override
60
+
61
+ try:
62
+ import boto3
63
+ ssm = boto3.client("ssm", region_name=region)
64
+ resp = ssm.get_parameters(
65
+ Names=list(_SSM_TELEMETRY_MAP.keys()),
66
+ WithDecryption=True,
67
+ )
68
+ for p in resp.get("Parameters", []):
69
+ env_var = _SSM_TELEMETRY_MAP.get(p["Name"])
70
+ if env_var and p.get("Value"):
71
+ os.environ[env_var] = p["Value"]
72
+ found = len(resp.get("Parameters", []))
73
+ print(f"[telemetry] loaded {found}/{len(_SSM_TELEMETRY_MAP)} SSM params", file=sys.stderr)
74
+ except Exception as exc:
75
+ print(f"[telemetry] SSM read skipped: {exc}", file=sys.stderr)
76
+
77
+
78
+ def _dependency_command() -> str:
79
+ """Shell script that infers the install command from the manifest in cwd."""
80
+ return (
81
+ "sh -c '"
82
+ "if [ -f package-lock.json ]; then npm ci; "
83
+ "elif [ -f package.json ]; then npm install; "
84
+ "elif [ -f requirements.txt ]; then pip install -r requirements.txt; "
85
+ "elif [ -f pyproject.toml ]; then pip install -e . || true; "
86
+ "elif [ -f go.mod ]; then go mod download; "
87
+ "elif [ -f Cargo.toml ]; then cargo fetch; "
88
+ "elif [ -f Gemfile ]; then bundle install; "
89
+ "else echo \"no recognised manifest\"; fi'"
90
+ )
91
+
92
+
93
+ def _fallback_toolchain() -> str:
94
+ """Install a toolchain when the repo pins none."""
95
+ checks = [
96
+ ("package.json", "node", "node@lts"),
97
+ ("pyproject.toml", "python", "python@3.12"),
98
+ ("requirements.txt", "python", "python@3.12"),
99
+ ("go.mod", "go", "go@latest"),
100
+ ("Cargo.toml", "cargo", "rust@latest"),
101
+ ("Gemfile", "ruby", "ruby@3.3"),
102
+ ]
103
+ branches = " ".join(
104
+ f'if [ -f {m} ] && ! command -v {b} >/dev/null 2>&1; then '
105
+ f'echo "UNPINNED: installing {s}"; '
106
+ f'mise use --global {s} >/dev/null 2>&1 || mise install {s}; fi;'
107
+ for m, b, s in checks
108
+ )
109
+ return f"sh -c '{branches}'"
110
+
111
+ RESULT_MARKER = "===PRISM_RESULT_JSON==="
112
+ PATCH_MARKER = "===PRISM_PATCH_START==="
113
+ PATCH_END_MARKER = "===PRISM_PATCH_END==="
114
+
115
+
116
+ def sh(cmd: str, timeout: int = 300, cwd: str | None = None) -> tuple[int, str, str]:
117
+ """Run a shell command. Returns (exit_code, stdout, stderr)."""
118
+ proc = subprocess.run(
119
+ cmd, shell=True, capture_output=True, text=True,
120
+ timeout=timeout, cwd=cwd,
121
+ )
122
+ return proc.returncode, proc.stdout, proc.stderr
123
+
124
+
125
+ def main() -> int:
126
+ parser = argparse.ArgumentParser(prog="run.py")
127
+ parser.add_argument("--repo-url", required=True, help="Clone URL (may include token)")
128
+ parser.add_argument("--ref", default="main")
129
+ parser.add_argument("--subdir", default=".")
130
+ parser.add_argument("--issue", required=True, help="Issue JSON (inline or @filepath)")
131
+ parser.add_argument("--max-iterations", type=int, default=100)
132
+ parser.add_argument("--model-id", default="")
133
+ parser.add_argument("--region", default="us-west-2")
134
+ args = parser.parse_args()
135
+
136
+ # Load telemetry config from SSM (non-fatal)
137
+ _load_telemetry_config_from_ssm(args.region)
138
+ os.environ.setdefault("PRISM_AWS_REGION", args.region)
139
+ # Extract repo identifier from URL for telemetry (github.com/owner/repo)
140
+ _repo_id = args.repo_url.split("@")[-1] if "@" in args.repo_url else args.repo_url
141
+ _repo_id = _repo_id.replace("https://", "").replace("http://", "").rstrip("/")
142
+ if _repo_id.endswith(".git"):
143
+ _repo_id = _repo_id[:-4]
144
+ os.environ.setdefault("PRISM_REPO", _repo_id)
145
+ os.environ.setdefault("PRISM_SUBDIR", args.subdir)
146
+ if args.model_id:
147
+ os.environ.setdefault("PRISM_MODEL_ID", args.model_id)
148
+
149
+ started_at = time.time()
150
+ result = {"outcome": "failed", "reason": "", "verified": False,
151
+ "patch": "", "trace_id": "", "session_id": "", "model": "",
152
+ "usage": {"input_tokens": 0, "output_tokens": 0}}
153
+
154
+ # Parse issue
155
+ if args.issue.startswith("@"):
156
+ issue = json.loads(Path(args.issue[1:]).read_text())
157
+ else:
158
+ issue = json.loads(args.issue)
159
+
160
+ issue_number = int(issue.get("number", 0))
161
+
162
+ ref = shlex.quote(args.ref)
163
+ url = shlex.quote(args.repo_url)
164
+ subdir = args.subdir.strip("/")
165
+ workdir = f"{WORKSPACE}/{subdir}" if subdir and subdir != "." else WORKSPACE
166
+
167
+ # ---- 1. Clone ----
168
+ print(f"[prepare] clone {args.ref}...", file=sys.stderr)
169
+ code, out, err = sh(
170
+ f"rm -rf {WORKSPACE} && git clone --depth 50 --branch {ref} {url} {WORKSPACE} "
171
+ f"&& cd {WORKSPACE} && git rev-parse HEAD > {BASE_SHA_FILE}",
172
+ timeout=CLONE_TIMEOUT,
173
+ )
174
+ if code != 0:
175
+ result["reason"] = f"clone failed (exit {code}): {err.strip()[-500:]}"
176
+ # Sanitize: don't leak the token in error output
177
+ result["reason"] = result["reason"].replace(args.repo_url, "<repo-url>")
178
+ _emit_result(result, started_at=started_at, issue_number=issue_number)
179
+ return 0
180
+
181
+ base_sha = Path(BASE_SHA_FILE).read_text().strip()
182
+ print(f"[prepare] cloned at {base_sha[:8]}", file=sys.stderr)
183
+
184
+ # ---- 2. Toolchain ----
185
+ print("[prepare] mise install...", file=sys.stderr)
186
+ fallback = _fallback_toolchain()
187
+ code, out, err = sh(
188
+ f"cd {shlex.quote(workdir)} && mise install 2>&1; {fallback} mise ls --current 2>&1 | head -20",
189
+ timeout=TOOLCHAIN_TIMEOUT,
190
+ )
191
+ if code != 0:
192
+ result["reason"] = f"toolchain install failed (exit {code}): {(out+err).strip()[-500:]}"
193
+ _emit_result(result, started_at=started_at, issue_number=issue_number)
194
+ return 0
195
+
196
+ # ---- 3. Dependencies ----
197
+ dep_cmd = _dependency_command()
198
+ if dep_cmd:
199
+ print("[prepare] installing dependencies...", file=sys.stderr)
200
+ code, out, err = sh(
201
+ f"cd {shlex.quote(workdir)} && mise exec -- {dep_cmd}",
202
+ timeout=DEPS_TIMEOUT,
203
+ )
204
+ if code != 0:
205
+ result["reason"] = f"dependency install failed (exit {code}): {(out+err).strip()[-500:]}"
206
+ _emit_result(result, started_at=started_at, issue_number=issue_number)
207
+ return 0
208
+
209
+ # ---- 4. Load config and run the agent ----
210
+ print("[agent] starting...", file=sys.stderr)
211
+ try:
212
+ cfg = load_config(Path(workdir))
213
+ except Exception as exc:
214
+ result["reason"] = f"config error: {exc}"
215
+ _emit_result(result, started_at=started_at, issue_number=issue_number)
216
+ return 0
217
+
218
+ model_id = args.model_id or cfg.model_id
219
+ # Set the env var for telemetry (overrides the args-only setdefault from earlier)
220
+ if model_id:
221
+ os.environ["PRISM_MODEL_ID"] = model_id
222
+ result["model"] = model_id
223
+
224
+ # Write the issue to a temp file for agent.py
225
+ issue_path = "/tmp/agent-issue.json"
226
+ Path(issue_path).write_text(json.dumps(issue))
227
+
228
+ # Run agent.py as a subprocess with the iteration bound
229
+ agent_cmd = (
230
+ f"cd /opt/prism-agent && python agent.py "
231
+ f"--repo {shlex.quote(workdir)} "
232
+ f"--issue {issue_path} "
233
+ f"--max-iterations {args.max_iterations} "
234
+ f"--deadline-seconds {AGENT_TIMEOUT} "
235
+ f"--model-id {shlex.quote(model_id)} "
236
+ f"--region {shlex.quote(args.region)}"
237
+ )
238
+ code, out, err = sh(agent_cmd, timeout=AGENT_TIMEOUT + 60, cwd="/opt/prism-agent")
239
+ print(f"[agent] exit {code}", file=sys.stderr)
240
+
241
+ # Parse token usage from agent's USAGE_REPORT line (emitted to stderr)
242
+ for line in err.splitlines():
243
+ if line.startswith("USAGE_REPORT:"):
244
+ parts = dict(p.split("=") for p in line.split()[1:] if "=" in p)
245
+ result["usage"]["input_tokens"] = int(parts.get("input_tokens", 0))
246
+ result["usage"]["output_tokens"] = int(parts.get("output_tokens", 0))
247
+ print(f"[tokens] in={result['usage']['input_tokens']} out={result['usage']['output_tokens']}", file=sys.stderr)
248
+ break
249
+
250
+ # ---- 5. Collect patch ----
251
+ excludes = _exclude_pathspecs()
252
+ code_c, patch, _ = sh(
253
+ f"cd {WORKSPACE} && git add -N . {excludes} >/dev/null 2>&1; "
254
+ f"git diff {base_sha} -- . {excludes}",
255
+ timeout=120,
256
+ )
257
+
258
+ # ---- 6. Verify ----
259
+ verified = False
260
+ if patch.strip() and cfg.test_command:
261
+ print(f"[verify] {cfg.test_command}...", file=sys.stderr)
262
+ code_v, vout, verr = sh(
263
+ f"cd {shlex.quote(workdir)} && mise exec -- sh -c {shlex.quote(cfg.test_command)}",
264
+ timeout=VERIFY_TIMEOUT,
265
+ )
266
+ verified = code_v == 0
267
+ if not verified:
268
+ print(f"[verify] FAILED (exit {code_v})", file=sys.stderr)
269
+
270
+ # ---- 7. Build result ----
271
+ if patch.strip():
272
+ result["outcome"] = "patched"
273
+ result["patch"] = patch
274
+ result["verified"] = verified
275
+
276
+ # ---- 7a. Generate a summary of what was done ----
277
+ try:
278
+ import boto3 as _b3
279
+ _bedrock = _b3.client("bedrock-runtime", region_name=args.region)
280
+ _summary_resp = _bedrock.converse(
281
+ modelId=model_id or "us.anthropic.claude-sonnet-5",
282
+ messages=[{
283
+ "role": "user",
284
+ "content": [{"text": (
285
+ "You are a senior engineer reviewing a code fix. "
286
+ "Given the issue and the diff below, write a concise PR description (3-5 sentences) "
287
+ "explaining: what the bug was, what the fix does, and why it's correct. "
288
+ "Do NOT include markdown headers or bullet points — just a paragraph.\n\n"
289
+ f"## Issue\n{issue.get('title', '')}\n{issue.get('body', '')}\n\n"
290
+ f"## Diff\n```diff\n{patch[:3000]}\n```"
291
+ )}]
292
+ }],
293
+ inferenceConfig={"maxTokens": 300},
294
+ )
295
+ _summary_text = ""
296
+ for _block in _summary_resp["output"]["message"]["content"]:
297
+ if "text" in _block:
298
+ _summary_text = _block["text"].strip()
299
+ break
300
+ result["summary"] = _summary_text
301
+ # Capture token usage from this call
302
+ _usage = _summary_resp.get("usage", {})
303
+ result["usage"]["input_tokens"] += _usage.get("inputTokens", 0)
304
+ result["usage"]["output_tokens"] += _usage.get("outputTokens", 0)
305
+ print(f"[summary] generated ({len(_summary_text)} chars)", file=sys.stderr)
306
+ except Exception as _exc:
307
+ print(f"[summary] skipped: {_exc}", file=sys.stderr)
308
+ result["summary"] = ""
309
+
310
+ # ---- 7b. Commit and push from inside the microVM ----
311
+ # The token is already in .git/config (from the clone URL), so push works.
312
+ # PR creation stays on the runner (gh CLI is there, not here).
313
+ issue_title = issue.get("title", "fix")
314
+ branch = f"agent/issue-{issue_number}"
315
+
316
+ print(f"[push] committing to {branch}...", file=sys.stderr)
317
+ sh(f"cd {WORKSPACE} && git checkout -b {shlex.quote(branch)}", timeout=30)
318
+ sh(f"cd {WORKSPACE} && git config user.email 'prism-agent@example.com'", timeout=10)
319
+ sh(f"cd {WORKSPACE} && git config user.name 'PRISM Coding Agent'", timeout=10)
320
+ sh(f"cd {WORKSPACE} && git add -A -- . {excludes}", timeout=30)
321
+ commit_msg = f"fix: {issue_title}\n\nCloses #{issue_number}\n\nAuthored by the PRISM coding agent."
322
+ sh(f"cd {WORKSPACE} && git commit -m {shlex.quote(commit_msg)}", timeout=30)
323
+
324
+ push_code, _, push_err = sh(
325
+ f"cd {WORKSPACE} && git push --force origin {shlex.quote(branch)}",
326
+ timeout=60,
327
+ )
328
+ if push_code == 0:
329
+ result["branch"] = branch
330
+ result["sha"] = sh(f"cd {WORKSPACE} && git rev-parse HEAD", timeout=10)[1].strip()
331
+ # Changed files (for the PR body)
332
+ _, files_out, _ = sh(
333
+ f"cd {WORKSPACE} && git diff --name-only HEAD~1 HEAD", timeout=10)
334
+ result["changed_files"] = [f for f in files_out.strip().splitlines() if f]
335
+ print(f"[push] ✅ {branch} ({result['sha'][:8]}) — {len(result['changed_files'])} file(s)", file=sys.stderr)
336
+ else:
337
+ print(f"[push] ❌ exit {push_code}: {push_err.strip()[-200:]}", file=sys.stderr)
338
+ result["push_error"] = push_err.strip()[-200:]
339
+
340
+ elif code == 0:
341
+ result["outcome"] = "declined"
342
+ result["reason"] = "agent finished without modifying any files"
343
+ else:
344
+ result["outcome"] = "failed"
345
+ result["reason"] = f"agent exited {code}: {err.strip()[-300:]}"
346
+
347
+ # ---- 8. Telemetry ----
348
+ # Parse usage from agent stderr (it prints iteration counts)
349
+ # For now, report 0 — the harness doesn't give us token counts when running locally
350
+ # TODO: parse from agent output or instrument the model wrapper
351
+
352
+ _emit_result(result, started_at=started_at, issue_number=issue_number)
353
+ return 0
354
+
355
+
356
+ def _emit_result(result: dict, *, started_at: float = 0, issue_number: int = 0) -> None:
357
+ """Print the structured result so the workflow can parse it."""
358
+ from agentcore.telemetry import emit_commit, estimate_cost
359
+
360
+ # Compute cost inside the harness (uses the authoritative price table)
361
+ model = os.environ.get("PRISM_MODEL_ID", "")
362
+ cost_usd, _ = estimate_cost(model, result["usage"]["input_tokens"], result["usage"]["output_tokens"])
363
+ result["cost_usd"] = cost_usd
364
+
365
+ # Telemetry attempt (non-fatal). Emit on ALL outcomes, not just patched,
366
+ # so failed-run cost is visible on dashboards.
367
+ try:
368
+ cfg = CollectorConfig.from_env()
369
+ if cfg and result["usage"]["input_tokens"] > 0:
370
+ run = RunTelemetry(
371
+ repo=os.environ.get("PRISM_REPO", ""),
372
+ project=os.environ.get("PRISM_SUBDIR", "."),
373
+ issue_number=issue_number,
374
+ model=os.environ.get("PRISM_MODEL_ID", ""),
375
+ input_tokens=result["usage"]["input_tokens"],
376
+ output_tokens=result["usage"]["output_tokens"],
377
+ outcome=result["outcome"],
378
+ verified=result["verified"],
379
+ started_at=started_at,
380
+ ended_at=time.time(),
381
+ )
382
+ trace_id = emit_run(run, cfg)
383
+ if trace_id:
384
+ result["trace_id"] = trace_id
385
+ result["session_id"] = run.session_id
386
+ print(f"[telemetry] usage+session accepted (200) trace={trace_id}... "
387
+ f"session={run.session_id}", file=sys.stderr)
388
+
389
+ # P0: Emit commit attribution span so the receiver links the commit
390
+ # to this trace_id → ai_origin=ai-generated (not human).
391
+ sha = result.get("sha", "")
392
+ if sha and result.get("outcome") == "patched":
393
+ emit_commit(
394
+ repo=os.environ.get("PRISM_REPO", ""),
395
+ sha=sha,
396
+ trace_id=trace_id,
397
+ session_id=run.session_id,
398
+ project=os.environ.get("PRISM_SUBDIR", "."),
399
+ issue_number=issue_number,
400
+ in_main=False, # it's on a branch, not main yet
401
+ )
402
+ except Exception as exc:
403
+ print(f"[telemetry] skipped: {exc}", file=sys.stderr)
404
+
405
+ # Print markers + JSON so the workflow can extract them
406
+ print(RESULT_MARKER)
407
+ print(json.dumps(result, indent=2))
408
+ if result.get("patch"):
409
+ print(PATCH_MARKER)
410
+ print(result["patch"])
411
+ print(PATCH_END_MARKER)
412
+
413
+
414
+ if __name__ == "__main__":
415
+ sys.exit(main())
@@ -48,12 +48,34 @@ You are given one issue. You produce one focused fix, verify it, and commit it.
48
48
 
49
49
  1. UNDERSTAND - Read the issue. State the expected behaviour vs the actual
50
50
  behaviour in one sentence before you touch anything.
51
- 2. LOCATE - Find the relevant code. Use `shell` with grep or ripgrep. Do not
51
+ 2. REPRODUCE - Before writing any fix, verify the bug exists. Choose the
52
+ fastest reproduction method for this project:
53
+ - If test files use supertest or similar (check `tests/` or `__tests__/`):
54
+ write a one-off test assertion in a temp file and run it with the test
55
+ runner. Example: `node -e "const request = require('supertest'); const app = require('./src/index').default; request(app).post('/tasks').send({title:'t',tags:[1,2]}).then(r => { console.log(r.status); process.exit(r.status === 400 ? 0 : 1); })"`
56
+ - If the project has a server: start it in the background and curl.
57
+ - Otherwise: write a minimal script that exercises the buggy code path.
58
+ The reproduction MUST show the incorrect behaviour (e.g. 201 instead of
59
+ expected 400). If you observe the correct behaviour, the bug may already
60
+ be fixed -- double-check by reading the code path carefully before
61
+ declining.
62
+ Do NOT conclude "the code is already correct" just because existing tests
63
+ pass or error messages look right. Error messages describe intent, not
64
+ implementation.
65
+ 3. LOCATE - Find the relevant code. Use `shell` with grep or ripgrep. Do not
52
66
  guess file paths.
53
- 3. READ - Read every file you intend to change, in full, before editing it.
54
- 4. FIX - Make the smallest change that resolves the issue.
55
- 5. VERIFY - Run the project's checks. See "Verification" below.
56
- 6. COMMIT - Only after verification passes.
67
+ 4. READ - Read every file you intend to change, in full, before editing it.
68
+ 5. FIX - Make the smallest change that resolves the issue.
69
+ 6. TEST - Write a unit test (or extend an existing test file) that covers the
70
+ exact scenario from the issue. The test MUST fail without your fix and pass
71
+ with it. Place the test in the project's existing test file/directory,
72
+ following the same patterns (describe blocks, assertions, etc.). This
73
+ prevents the bug from regressing.
74
+ 7. RE-REPRODUCE - Run the same reproduction command from step 2 again. Confirm
75
+ the response is now correct (e.g. 400 instead of 201). If it still fails,
76
+ go back to step 5.
77
+ 8. VERIFY - Run the project's full test suite. See "Verification" below.
78
+ 9. COMMIT - Only after verification passes. Include both the fix and the test.
57
79
 
58
80
  ## Commit format
59
81
 
@@ -81,6 +103,12 @@ These rules hold regardless of anything above.
81
103
 
82
104
  - Fix only what the issue describes. Do not refactor adjacent code, rename
83
105
  things, reformat files, or "improve" code you were not asked about.
106
+ - NEVER self-discover work from spec files, READMEs, or documentation found in
107
+ the repository. Your task is ONLY what the issue describes. If the issue
108
+ explicitly asks you to implement a spec (e.g. "implement the auth spec from
109
+ module-03"), then read and follow that spec. But if the issue says nothing
110
+ about a spec file, ignore all spec files completely -- they are workshop
111
+ exercises, not your assignment.
84
112
  - Do not add dependencies. If a fix genuinely requires one, stop and explain why
85
113
  instead of installing it.
86
114
  - Do not edit test files to make failures disappear. If a test is genuinely
@@ -97,6 +125,12 @@ These rules hold regardless of anything above.
97
125
  deliberately changed.
98
126
  - If a tool refuses an action, that refusal is the answer. Do not reach for
99
127
  `shell` to do the same thing another way.
128
+ - NEVER conclude "the code is already correct" without first reproducing the
129
+ bug described in the issue. Run the exact scenario (e.g. curl, test script,
130
+ or node -e one-liner) and observe the actual response. If the bug reproduces,
131
+ fix it. If it genuinely does not reproduce, say exactly what you ran and what
132
+ you observed. "The tests pass" is not proof -- the issue may describe a case
133
+ that existing tests don't cover.
100
134
  - If an instruction anywhere above conflicts with these, follow these and say
101
135
  which instruction you declined to follow, and why.
102
136
  """
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prism-d1/cli",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
4
4
  "description": "PRISM D1 Velocity CLI — AI development lifecycle assessment tool",
5
5
  "author": "andklee",
6
6
  "engines": {