@prism-d1/cli 1.9.0 → 1.9.1

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 ADDED
@@ -0,0 +1,48 @@
1
+ # prism-cli
2
+
3
+ The command line for [PRISM D1 Velocity](https://github.com/aws-samples/sample-prism-d1-velocity): measure, secure and review AI-assisted development from your own machine. Codewatch git hooks, a local dashboard, an MCP server for AI agents, AWS Security Agent scans, read-only AWS account checks, and the setup helpers for a team's PRISM deployment.
4
+
5
+ > Sample project, not production-ready. See [SECURITY.md](https://github.com/aws-samples/sample-prism-d1-velocity/blob/main/SECURITY.md).
6
+
7
+ ## Install
8
+
9
+ Node.js 22 or later:
10
+
11
+ ```bash
12
+ npm install -g @prism-d1/cli
13
+ prism-cli version # installed version, newer release on npm, other copies on PATH
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```bash
19
+ prism-cli enable codewatch # in a repo: secret scan on commit, AI review on push (report only)
20
+ prism-cli dashboard # local page on 127.0.0.1: results, AI usage, setup checklist
21
+ prism-cli aws check # read-only AWS account checks; then prism-cli aws recommend
22
+ ```
23
+
24
+ Send AI usage to your team's PRISM with `prism-cli enable codeburn --url <OtelCollectorUrl>` (the URL comes from your platform team). Every command takes `--help`.
25
+
26
+ ## Documentation
27
+
28
+ - [Using PRISM locally](https://github.com/aws-samples/sample-prism-d1-velocity/blob/main/USER_GUIDE.md#using-prism-locally): what each feature does and where it shows up (VS Code family editors, Kiro Crew, Claude Code)
29
+ - [Command reference](https://github.com/aws-samples/sample-prism-d1-velocity/blob/main/docs/CLI-REFERENCE.md): every command and flag
30
+ - [prism-cli Overview](https://github.com/aws-samples/sample-prism-d1-velocity/blob/main/docs/PRISM-CLI-OVERVIEW.md): what each command touches in AWS, its cost and its safety defaults
31
+ - [User Guide](https://github.com/aws-samples/sample-prism-d1-velocity/blob/main/USER_GUIDE.md): deploying PRISM, CI workflows and eval gates
32
+
33
+ ## Develop
34
+
35
+ From `cli/` in a clone:
36
+
37
+ ```bash
38
+ npm ci
39
+ npm run build # tsc -> dist/, plus the bundled assets
40
+ npm test # type-checks the tests, then the node:test suite
41
+ npm run docs # regenerate docs/CLI-REFERENCE.md after changing a command or flag
42
+ ```
43
+
44
+ Releases are published to npm by `.github/workflows/publish.yml` when a `v*` tag is pushed.
45
+
46
+ ## License
47
+
48
+ MIT
@@ -282,49 +282,85 @@ jobs:
282
282
  # on long content. Use a minimal CLI arg prompt and let kiro read files itself.
283
283
  git diff "${BASE_SHA}..${HEAD_SHA}" -- ${FILES} > /tmp/pr-diff.patch
284
284
 
285
- REVIEW_OUTPUT=$(kiro-cli chat --no-interactive \
286
- --trust-all-tools \
287
- --model auto \
288
- "Review /tmp/pr-diff.patch for code quality. Read it, then output ONLY JSON: {\"findings\":[{\"file\":\"path\",\"line\":1,\"severity\":\"high|medium|low\",\"category\":\"str\",\"message\":\"str\"}],\"score\":0.0,\"summary\":\"str\"}") || true
289
-
290
- # Strip ANSI escapes, then extract the LAST JSON object that parses and
291
- # has a numeric score and a findings array. kiro-cli writes reasoning
292
- # notes first, and they quote code with braces, so a greedy
293
- # first-brace-to-last-brace match is not the verdict.
294
- CLEANED=$(echo "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
295
- REVIEW_JSON=$(printf '%s' "${CLEANED}" | python3 -c 'if True:
296
- import json, math, sys
297
- text = sys.stdin.read()
298
- dec = json.JSONDecoder(strict=False)
299
- pos = text.rfind("{")
300
- while pos != -1:
301
- try:
302
- obj = dec.raw_decode(text, pos)[0]
303
- except ValueError:
304
- obj = None
305
- score = obj.get("score") if isinstance(obj, dict) else None
306
- if (isinstance(score, (int, float)) and not isinstance(score, bool)
307
- and math.isfinite(score) and isinstance(obj.get("findings"), list)):
308
- print(json.dumps(obj))
309
- sys.exit(0)
310
- pos = text.rfind("{", 0, pos)
311
- sys.exit(1)
312
- ' || true)
313
-
314
- if [ -z "${REVIEW_JSON}" ] || ! echo "${REVIEW_JSON}" | jq -e '.score' > /dev/null 2>&1; then
315
- # FAIL CLOSED: a gate that can't evaluate must not pass
316
- echo "::group::Debug: raw kiro-cli output (first 2000 chars)"
285
+ # One retry, only when the reply cannot be parsed. Attempt 2 re-runs the
286
+ # same review (same model, same diff file) with one sentence added that
287
+ # asks for a single JSON object. It does not hand the bad reply back
288
+ # for "repair": a broken reply is often truncated, and repairing it
289
+ # would make the model invent the missing findings. Each attempt reads
290
+ # the diff, so any accepted verdict comes from a real review. Nothing
291
+ # is repaired by regex; two unparseable replies still fail closed.
292
+ REVIEW_PROMPT="Review /tmp/pr-diff.patch for code quality. Read it, then output ONLY JSON: {\"findings\":[{\"file\":\"path\",\"line\":1,\"severity\":\"high|medium|low\",\"category\":\"str\",\"message\":\"str\"}],\"score\":0.0,\"summary\":\"str\"}"
293
+ RETRY_NOTE="Your previous reply was not valid JSON. Return exactly one JSON object matching that schema and nothing else: no prose, no code fences."
294
+ # Per-attempt cap. The retry gets the same budget as the first attempt.
295
+ REVIEW_TIMEOUT_SECS=600
296
+ REVIEW_JSON=""
297
+ REVIEW_ATTEMPT=""
298
+ for ATTEMPT in 1 2; do
299
+ PROMPT="${REVIEW_PROMPT}"
300
+ if [ "${ATTEMPT}" = "2" ]; then PROMPT="${REVIEW_PROMPT} ${RETRY_NOTE}"; fi
301
+ REVIEW_OUTPUT=$(timeout "${REVIEW_TIMEOUT_SECS}" kiro-cli chat --no-interactive \
302
+ --trust-all-tools \
303
+ --model auto \
304
+ "${PROMPT}") || true
305
+
306
+ # Strip ANSI escapes, then extract the LAST JSON object that parses and
307
+ # has a numeric score and a findings array. kiro-cli writes reasoning
308
+ # notes first, and they quote code with braces, so a greedy
309
+ # first-brace-to-last-brace match is not the verdict. Same rule as
310
+ # extractReviewJson in cli/src/codewatch/reviewers/types.ts; the copy
311
+ # in each eval-gate workflow is kept identical by cli/test/eval-gate-extract.test.ts.
312
+ CLEANED=$(echo "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
313
+ REVIEW_JSON=$(printf '%s' "${CLEANED}" | python3 -c 'if True:
314
+ import json, math, sys
315
+ text = sys.stdin.read()
316
+ dec = json.JSONDecoder(strict=False)
317
+ pos = text.rfind("{")
318
+ while pos != -1:
319
+ try:
320
+ obj = dec.raw_decode(text, pos)[0]
321
+ except ValueError:
322
+ obj = None
323
+ score = obj.get("score") if isinstance(obj, dict) else None
324
+ if (isinstance(score, (int, float)) and not isinstance(score, bool)
325
+ and math.isfinite(score) and isinstance(obj.get("findings"), list)):
326
+ print(json.dumps(obj))
327
+ sys.exit(0)
328
+ pos = text.rfind("{", 0, pos)
329
+ sys.exit(1)
330
+ ' || true)
331
+
332
+ if [ -n "${REVIEW_JSON}" ] && echo "${REVIEW_JSON}" | jq -e '.score' > /dev/null 2>&1; then
333
+ REVIEW_ATTEMPT="${ATTEMPT}"
334
+ break
335
+ fi
336
+ REVIEW_JSON=""
337
+
338
+ # The reply is untrusted model output. Workflow commands are paused
339
+ # while it is printed, so a "::" line in it is shown, not executed.
340
+ STOP_TOKEN=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')
341
+ echo "::group::Debug: attempt ${ATTEMPT} raw kiro-cli output (first 2000 chars)"
342
+ echo "::stop-commands::${STOP_TOKEN}"
317
343
  echo "${REVIEW_OUTPUT:-<empty>}" | head -c 2000
318
344
  echo ""
319
- echo "::endgroup::"
320
- echo "::group::Debug: cleaned output"
345
+ echo "--- cleaned ---"
321
346
  echo "${CLEANED:-<empty>}" | head -c 2000
322
347
  echo ""
348
+ echo "::${STOP_TOKEN}::"
323
349
  echo "::endgroup::"
324
- echo "::error::Could not parse kiro-cli review output -- failing closed"
350
+ if [ "${ATTEMPT}" = "1" ]; then
351
+ echo "::warning::Attempt 1: could not parse kiro-cli review output -- retrying the review once"
352
+ fi
353
+ done
354
+
355
+ if [ -z "${REVIEW_JSON}" ]; then
356
+ # FAIL CLOSED: a gate that can't evaluate must not pass
357
+ echo "::error::Could not parse kiro-cli review output on either attempt (both unparseable) -- failing closed"
325
358
  echo "result=ERROR" >> "$GITHUB_OUTPUT"
359
+ echo "Eval gate review: both attempts returned unparseable output -- failed closed." >> "$GITHUB_STEP_SUMMARY"
326
360
  exit 0
327
361
  fi
362
+ echo "Review verdict parsed on attempt ${REVIEW_ATTEMPT} of 2"
363
+ echo "attempt=${REVIEW_ATTEMPT}" >> "$GITHUB_OUTPUT"
328
364
 
329
365
  SCORE=$(echo "${REVIEW_JSON}" | jq -r '.score')
330
366
  FINDINGS=$(echo "${REVIEW_JSON}" | jq '.findings | length')
@@ -343,10 +379,13 @@ jobs:
343
379
  # Determine pass/fail (awk instead of bc -- no extra package needed)
344
380
  PASS=$(awk -v s="${SCORE}" 'BEGIN { print (s >= 0.82) ? "true" : "false" }')
345
381
  if [ "${HIGH}" -gt 0 ] || [ "${PASS}" != "true" ]; then
346
- echo "result=FAIL" >> "$GITHUB_OUTPUT"
382
+ RESULT=FAIL
347
383
  else
348
- echo "result=PASS" >> "$GITHUB_OUTPUT"
384
+ RESULT=PASS
349
385
  fi
386
+ echo "result=${RESULT}" >> "$GITHUB_OUTPUT"
387
+ # Only numbers and fixed words here; no model text reaches the summary.
388
+ echo "Eval gate review: ${RESULT} (score ${SCORE}, ${HIGH} high), verdict parsed on attempt ${REVIEW_ATTEMPT} of 2." >> "$GITHUB_STEP_SUMMARY"
350
389
 
351
390
  - name: Post PR comment
352
391
  if: always()
@@ -372,7 +411,7 @@ jobs:
372
411
  if (result === 'SKIPPED') {
373
412
  body = '⚠️ **PRISM Eval Gate** -- Review skipped (no source files changed or no API key on fork PRs).';
374
413
  } else if (result === 'ERROR') {
375
- body = '❌ **PRISM Eval Gate** -- Review output could not be parsed. Failing closed; re-run the workflow or review manually.';
414
+ body = '❌ **PRISM Eval Gate** -- Review output could not be parsed on either attempt (the review is retried once). Failing closed; re-run the workflow or review manually.';
376
415
  } else {
377
416
  const score = process.env.REVIEW_SCORE || '?';
378
417
  const findings = process.env.REVIEW_FINDINGS || '0';
@@ -217,41 +217,67 @@ prism-eval-gate:
217
217
  # into HTTP headers, so let it read the patch off disk itself.
218
218
  git diff "${BASE_SHA}..${HEAD_SHA}" -- ${FILES} > /tmp/mr-diff.patch
219
219
 
220
- set +e
221
- REVIEW_OUTPUT=$(kiro-cli chat --no-interactive --trust-all-tools --model auto \
222
- "Review /tmp/mr-diff.patch for code quality. Read it, then output ONLY JSON: {\"findings\":[{\"file\":\"path\",\"line\":1,\"severity\":\"high|medium|low\",\"category\":\"str\",\"message\":\"str\"}],\"score\":0.0,\"summary\":\"str\"}")
223
- set -e
220
+ # One retry, only when the reply cannot be parsed. Attempt 2 re-runs
221
+ # the same review (same model, same diff file) with one sentence
222
+ # added that asks for a single JSON object. It does not hand the bad
223
+ # reply back for "repair": a broken reply is often truncated, and
224
+ # repairing it would make the model invent the missing findings.
225
+ # Nothing is repaired by regex; two unparseable replies fail closed.
226
+ REVIEW_PROMPT="Review /tmp/mr-diff.patch for code quality. Read it, then output ONLY JSON: {\"findings\":[{\"file\":\"path\",\"line\":1,\"severity\":\"high|medium|low\",\"category\":\"str\",\"message\":\"str\"}],\"score\":0.0,\"summary\":\"str\"}"
227
+ RETRY_NOTE="Your previous reply was not valid JSON. Return exactly one JSON object matching that schema and nothing else: no prose, no code fences."
228
+ # Per-attempt cap. The retry gets the same budget as the first attempt.
229
+ REVIEW_TIMEOUT_SECS=600
230
+ REVIEW_JSON=""
231
+ REVIEW_ATTEMPT=""
232
+ for ATTEMPT in 1 2; do
233
+ PROMPT="${REVIEW_PROMPT}"
234
+ if [ "${ATTEMPT}" = "2" ]; then PROMPT="${REVIEW_PROMPT} ${RETRY_NOTE}"; fi
235
+ set +e
236
+ REVIEW_OUTPUT=$(timeout "${REVIEW_TIMEOUT_SECS}" kiro-cli chat --no-interactive --trust-all-tools --model auto "${PROMPT}")
237
+ set -e
224
238
 
225
- # Extract the LAST JSON object that parses and has a numeric score
226
- # and a findings array. kiro-cli writes reasoning notes first, and
227
- # they quote code with braces, so a greedy first-brace-to-last-brace
228
- # match is not the verdict.
229
- CLEANED=$(printf '%s' "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
230
- REVIEW_JSON=$(printf '%s' "${CLEANED}" | python3 -c 'if True:
231
- import json, math, sys
232
- text = sys.stdin.read()
233
- dec = json.JSONDecoder(strict=False)
234
- pos = text.rfind("{")
235
- while pos != -1:
236
- try:
237
- obj = dec.raw_decode(text, pos)[0]
238
- except ValueError:
239
- obj = None
240
- score = obj.get("score") if isinstance(obj, dict) else None
241
- if (isinstance(score, (int, float)) and not isinstance(score, bool)
242
- and math.isfinite(score) and isinstance(obj.get("findings"), list)):
243
- print(json.dumps(obj))
244
- sys.exit(0)
245
- pos = text.rfind("{", 0, pos)
246
- sys.exit(1)
247
- ' || true)
239
+ # Extract the LAST JSON object that parses and has a numeric score
240
+ # and a findings array. kiro-cli writes reasoning notes first, and
241
+ # they quote code with braces, so a greedy first-brace-to-last-brace
242
+ # match is not the verdict.
243
+ CLEANED=$(printf '%s' "${REVIEW_OUTPUT}" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r')
244
+ REVIEW_JSON=$(printf '%s' "${CLEANED}" | python3 -c 'if True:
245
+ import json, math, sys
246
+ text = sys.stdin.read()
247
+ dec = json.JSONDecoder(strict=False)
248
+ pos = text.rfind("{")
249
+ while pos != -1:
250
+ try:
251
+ obj = dec.raw_decode(text, pos)[0]
252
+ except ValueError:
253
+ obj = None
254
+ score = obj.get("score") if isinstance(obj, dict) else None
255
+ if (isinstance(score, (int, float)) and not isinstance(score, bool)
256
+ and math.isfinite(score) and isinstance(obj.get("findings"), list)):
257
+ print(json.dumps(obj))
258
+ sys.exit(0)
259
+ pos = text.rfind("{", 0, pos)
260
+ sys.exit(1)
261
+ ' || true)
248
262
 
249
- if [ -z "${REVIEW_JSON}" ] || ! printf '%s' "${REVIEW_JSON}" | jq -e '.score' >/dev/null 2>&1; then
250
- echo "❌ Could not parse kiro-cli review output — failing closed."
263
+ if [ -n "${REVIEW_JSON}" ] && printf '%s' "${REVIEW_JSON}" | jq -e '.score' >/dev/null 2>&1; then
264
+ REVIEW_ATTEMPT="${ATTEMPT}"
265
+ break
266
+ fi
267
+ REVIEW_JSON=""
268
+ echo "⚠ Attempt ${ATTEMPT}: could not parse kiro-cli review output. Raw output (first 2000 chars):"
251
269
  printf '%s' "${REVIEW_OUTPUT:-<empty>}" | head -c 2000
252
270
  echo ""
271
+ if [ "${ATTEMPT}" = "1" ]; then
272
+ echo "Retrying the review once."
273
+ fi
274
+ done
275
+
276
+ if [ -z "${REVIEW_JSON}" ]; then
277
+ echo "❌ Could not parse kiro-cli review output on either attempt (both unparseable) — failing closed."
253
278
  REVIEW_RESULT="ERROR"
254
279
  else
280
+ echo "Review verdict parsed on attempt ${REVIEW_ATTEMPT} of 2."
255
281
  printf '%s' "${REVIEW_JSON}" > /tmp/kiro-review.json
256
282
  REVIEW_SCORE=$(jq -r '.score' /tmp/kiro-review.json)
257
283
  REVIEW_HIGH=$(jq '[.findings[] | select(.severity=="high")] | length' /tmp/kiro-review.json)
@@ -1,4 +1,4 @@
1
- # PRISM app for Kiro Crew (v0.2)
1
+ # PRISM app for Kiro Crew
2
2
 
3
3
  A [Kiro Crew](https://github.com/kirodotdev/KiroCrew) app that brings your local PRISM data into
4
4
  Kiro Crew. It adds:
@@ -50,14 +50,13 @@ With prism-cli 1.9.0 or later, which bundles this app:
50
50
  npm i -g @prism-d1/cli && prism-cli enable kirocrew
51
51
  ```
52
52
 
53
- `prism-cli enable kirocrew` prints its plan (install, update or already current, with the
54
- versions) and asks before it changes anything. It copies the bundled app to
55
- `~/.prism/kirocrew-app`, then runs `kirocrew app install`, or `kirocrew app update prism --source`
56
- when the app is already installed (also from another folder, such as a clone), and
57
- `kirocrew app enable prism` on a first install. It never installs Kiro Crew. `--dry-run` prints
58
- the plan only, `--status` shows the installed and bundled versions, the source folder, the API
59
- grants and the digest cron, and `--uninstall` removes the app. After a prism-cli upgrade,
60
- `prism-cli version` says when the bundled app is newer than the installed one.
53
+ What the command does, its `--status`, `--dry-run` and `--uninstall` options and the two steps
54
+ that stay with you (trust the app, approve its API routes) are in the User Guide's
55
+ [Kiro Crew app](../USER_GUIDE.md#kiro-crew-app) section; every flag is in the
56
+ [command reference](../docs/CLI-REFERENCE.md#prism-cli-enable-kirocrew). It updates an app that
57
+ was installed from another folder, such as a clone, too. It never installs Kiro Crew.
58
+
59
+ ### Install from a folder
61
60
 
62
61
  From a local folder instead (a clone, for development):
63
62
 
@@ -129,6 +128,10 @@ is kept across app updates unless a later version of the app changes the schedul
129
128
 
130
129
  ## The page
131
130
 
131
+ ![PRISM page in Kiro Crew with demo data: spend cards, top models and projects, four repos with codewatch, one expanded with its findings](../assets/images/prism-cli/kirocrew-app-page-light.png)
132
+
133
+ *The PRISM page, rendered with made-up demo data.*
134
+
132
135
  The **PRISM** sidebar page shows the same data as the editor extension's machine mode:
133
136
 
134
137
  - spend today and this month (calls, sessions), sync mode, prism-cli version, and when the data
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "prism",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "displayName": "PRISM",
5
5
  "description": "Codewatch findings, AI spend and setup status from your local prism-cli for Kiro Crew agents, and a daily digest that speaks only when something changed.",
6
6
  "author": "aws-samples",
7
7
  "license": "MIT",
8
8
  "minKiroCrewVersion": "0.12.1",
9
9
  "iconPath": "assets/icon.png",
10
+ "screenshots": ["assets/screenshots/page-light.png", "assets/screenshots/report-light.png"],
11
+ "screenshotsDark": ["assets/screenshots/page-dark.png", "assets/screenshots/report-dark.png"],
10
12
  "tags": ["code-review", "developer-tools", "code-quality"],
11
13
  "highlights": [
12
14
  "PRISM page: spend, setup checklist, repos with codewatch newest commit first, their findings and a sandboxed scan report, all read-only",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prism-d1/cli",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "PRISM D1 Velocity CLI — AI development lifecycle assessment tool",
5
5
  "author": "andklee",
6
6
  "engines": {
@@ -24,6 +24,8 @@
24
24
  "scripts": {
25
25
  "build": "npx tsc && rm -rf dist/assets && mkdir -p dist/assets/infra dist/assets/coding-agent && cp -r ../bootstrapper/metric-hooks ../bootstrapper/eval-harness ../bootstrapper/github-workflows ../bootstrapper/gitlab-workflows dist/assets/ && cp -r ../infra/bin ../infra/lib ../infra/package.json ../infra/tsconfig.json ../infra/cdk.json dist/assets/infra/ && cp -r ../coding-agent/agent.py ../coding-agent/config.py ../coding-agent/system_prompt.py ../coding-agent/requirements.txt ../coding-agent/pyproject.toml ../coding-agent/README.md ../coding-agent/tools ../coding-agent/eval ../coding-agent/deploy dist/assets/coding-agent/ && mkdir -p dist/assets/coding-agent/fixtures && cp ../sample-app/.coding-agent/fixtures/*.json dist/assets/coding-agent/fixtures/ && find dist/assets/coding-agent -name __pycache__ -type d -prune -exec rm -rf {} + && cp -r src/commands/security/pentest-cdk dist/assets/pentest-cdk && cp -r ../kirocrew-app dist/assets/kirocrew-app && rm -rf dist/assets/kirocrew-app/tests dist/assets/kirocrew-app/data && find dist/assets/kirocrew-app \\( -name __pycache__ -o -name .pytest_cache \\) -type d -prune -exec rm -rf {} +",
26
26
  "test": "tsc -p tsconfig.test.json && node --import tsx --test test/*.test.ts",
27
+ "docs": "npm run build && node scripts/gen-cli-reference.mjs",
28
+ "docs:check": "node scripts/check-docs.mjs",
27
29
  "prepublishOnly": "npm run build",
28
30
  "start": "tsx bin/prism-cli.ts",
29
31
  "dev": "tsx bin/prism-cli.ts"