@quickchatai/pi-quickreview 1.2.0 → 1.3.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.
Files changed (231) hide show
  1. package/README.md +79 -27
  2. package/assets/config.example.toml +25 -7
  3. package/assets/prompts/onboarding.md +8 -7
  4. package/assets/schemas/review-result.v1.json +60 -0
  5. package/dist/extensions/quickreview.d.ts +17 -5
  6. package/dist/extensions/quickreview.d.ts.map +1 -1
  7. package/dist/extensions/quickreview.js +79 -26
  8. package/dist/extensions/quickreview.js.map +1 -1
  9. package/dist/src/engine/auth.d.ts +4 -0
  10. package/dist/src/engine/auth.d.ts.map +1 -1
  11. package/dist/src/engine/auth.js +11 -2
  12. package/dist/src/engine/auth.js.map +1 -1
  13. package/dist/src/engine/lens/activity.d.ts +45 -0
  14. package/dist/src/engine/lens/activity.d.ts.map +1 -0
  15. package/dist/src/engine/lens/activity.js +118 -0
  16. package/dist/src/engine/lens/activity.js.map +1 -0
  17. package/dist/src/engine/lens/runlens.d.ts +7 -0
  18. package/dist/src/engine/lens/runlens.d.ts.map +1 -1
  19. package/dist/src/engine/lens/runlens.js +30 -1
  20. package/dist/src/engine/lens/runlens.js.map +1 -1
  21. package/dist/src/engine/lens/tool.d.ts +7 -0
  22. package/dist/src/engine/lens/tool.d.ts.map +1 -1
  23. package/dist/src/engine/lens/tool.js +101 -18
  24. package/dist/src/engine/lens/tool.js.map +1 -1
  25. package/dist/src/engine/lens/wave.d.ts +6 -0
  26. package/dist/src/engine/lens/wave.d.ts.map +1 -1
  27. package/dist/src/engine/lens/wave.js +16 -0
  28. package/dist/src/engine/lens/wave.js.map +1 -1
  29. package/dist/src/homes.js +1 -1
  30. package/dist/src/homes.js.map +1 -1
  31. package/dist/src/live/events.d.ts +3 -0
  32. package/dist/src/live/events.d.ts.map +1 -1
  33. package/dist/src/live/events.js.map +1 -1
  34. package/dist/src/live/lensline.d.ts +20 -0
  35. package/dist/src/live/lensline.d.ts.map +1 -0
  36. package/dist/src/live/lensline.js +70 -0
  37. package/dist/src/live/lensline.js.map +1 -0
  38. package/dist/src/live/projection.d.ts +55 -0
  39. package/dist/src/live/projection.d.ts.map +1 -0
  40. package/dist/src/live/projection.js +465 -0
  41. package/dist/src/live/projection.js.map +1 -0
  42. package/dist/src/live/read.d.ts +24 -0
  43. package/dist/src/live/read.d.ts.map +1 -0
  44. package/dist/src/live/read.js +60 -0
  45. package/dist/src/live/read.js.map +1 -0
  46. package/dist/src/live/writer.d.ts +3 -0
  47. package/dist/src/live/writer.d.ts.map +1 -1
  48. package/dist/src/live/writer.js +6 -1
  49. package/dist/src/live/writer.js.map +1 -1
  50. package/dist/src/localreview.d.ts +18 -8
  51. package/dist/src/localreview.d.ts.map +1 -1
  52. package/dist/src/localreview.js +103 -43
  53. package/dist/src/localreview.js.map +1 -1
  54. package/dist/src/modelroute.d.ts +4 -0
  55. package/dist/src/modelroute.d.ts.map +1 -1
  56. package/dist/src/modelroute.js +21 -0
  57. package/dist/src/modelroute.js.map +1 -1
  58. package/dist/src/plugin/args.d.ts +38 -7
  59. package/dist/src/plugin/args.d.ts.map +1 -1
  60. package/dist/src/plugin/args.js +134 -37
  61. package/dist/src/plugin/args.js.map +1 -1
  62. package/dist/src/plugin/index.d.ts.map +1 -1
  63. package/dist/src/plugin/index.js +5 -1
  64. package/dist/src/plugin/index.js.map +1 -1
  65. package/dist/src/plugin/login.d.ts +8 -4
  66. package/dist/src/plugin/login.d.ts.map +1 -1
  67. package/dist/src/plugin/login.js +25 -32
  68. package/dist/src/plugin/login.js.map +1 -1
  69. package/dist/src/plugin/messages.d.ts +7 -0
  70. package/dist/src/plugin/messages.d.ts.map +1 -0
  71. package/dist/src/plugin/messages.js +13 -0
  72. package/dist/src/plugin/messages.js.map +1 -0
  73. package/dist/src/plugin/peers.d.ts +17 -0
  74. package/dist/src/plugin/peers.d.ts.map +1 -0
  75. package/dist/src/plugin/peers.js +55 -0
  76. package/dist/src/plugin/peers.js.map +1 -0
  77. package/dist/src/plugin/review.d.ts +52 -6
  78. package/dist/src/plugin/review.d.ts.map +1 -1
  79. package/dist/src/plugin/review.js +261 -95
  80. package/dist/src/plugin/review.js.map +1 -1
  81. package/dist/src/plugin/scan.d.ts +80 -44
  82. package/dist/src/plugin/scan.d.ts.map +1 -1
  83. package/dist/src/plugin/scan.js +450 -233
  84. package/dist/src/plugin/scan.js.map +1 -1
  85. package/dist/src/plugin/scancases.d.ts +33 -0
  86. package/dist/src/plugin/scancases.d.ts.map +1 -0
  87. package/dist/src/plugin/scancases.js +153 -0
  88. package/dist/src/plugin/scancases.js.map +1 -0
  89. package/dist/src/plugin/settings.d.ts +18 -0
  90. package/dist/src/plugin/settings.d.ts.map +1 -0
  91. package/dist/src/plugin/settings.js +47 -0
  92. package/dist/src/plugin/settings.js.map +1 -0
  93. package/dist/src/plugin/setup.d.ts +35 -12
  94. package/dist/src/plugin/setup.d.ts.map +1 -1
  95. package/dist/src/plugin/setup.js +258 -118
  96. package/dist/src/plugin/setup.js.map +1 -1
  97. package/dist/src/plugin/versiongate.d.ts +3 -0
  98. package/dist/src/plugin/versiongate.d.ts.map +1 -1
  99. package/dist/src/plugin/versiongate.js +9 -4
  100. package/dist/src/plugin/versiongate.js.map +1 -1
  101. package/dist/src/profileoverrides.d.ts +3 -0
  102. package/dist/src/profileoverrides.d.ts.map +1 -1
  103. package/dist/src/profileoverrides.js +9 -2
  104. package/dist/src/profileoverrides.js.map +1 -1
  105. package/dist/src/reviewapi.d.ts +22 -31
  106. package/dist/src/reviewapi.d.ts.map +1 -1
  107. package/dist/src/reviewapi.js +90 -66
  108. package/dist/src/reviewapi.js.map +1 -1
  109. package/dist/src/reviewdest.d.ts +1 -1
  110. package/dist/src/reviewdest.d.ts.map +1 -1
  111. package/dist/src/reviewdest.js +4 -3
  112. package/dist/src/reviewdest.js.map +1 -1
  113. package/dist/src/reviewtarget.d.ts +36 -0
  114. package/dist/src/reviewtarget.d.ts.map +1 -0
  115. package/dist/src/reviewtarget.js +6 -0
  116. package/dist/src/reviewtarget.js.map +1 -0
  117. package/dist/src/round-progress.d.ts +5 -0
  118. package/dist/src/round-progress.d.ts.map +1 -1
  119. package/dist/src/round-progress.js.map +1 -1
  120. package/dist/src/round-stage-deadlines.d.ts +26 -1
  121. package/dist/src/round-stage-deadlines.d.ts.map +1 -1
  122. package/dist/src/round-stage-deadlines.js +45 -2
  123. package/dist/src/round-stage-deadlines.js.map +1 -1
  124. package/dist/src/savedpost.d.ts +65 -0
  125. package/dist/src/savedpost.d.ts.map +1 -0
  126. package/dist/src/savedpost.js +131 -0
  127. package/dist/src/savedpost.js.map +1 -0
  128. package/dist/src/scan/caseread.d.ts +84 -0
  129. package/dist/src/scan/caseread.d.ts.map +1 -0
  130. package/dist/src/scan/caseread.js +263 -0
  131. package/dist/src/scan/caseread.js.map +1 -0
  132. package/dist/src/scan/channelfs.d.ts +6 -0
  133. package/dist/src/scan/channelfs.d.ts.map +1 -0
  134. package/dist/src/scan/channelfs.js +27 -0
  135. package/dist/src/scan/channelfs.js.map +1 -0
  136. package/dist/src/scan/cutrender.d.ts +13 -0
  137. package/dist/src/scan/cutrender.d.ts.map +1 -0
  138. package/dist/src/scan/cutrender.js +51 -0
  139. package/dist/src/scan/cutrender.js.map +1 -0
  140. package/dist/src/scan/facts.d.ts +72 -0
  141. package/dist/src/scan/facts.d.ts.map +1 -0
  142. package/dist/src/scan/facts.js +593 -0
  143. package/dist/src/scan/facts.js.map +1 -0
  144. package/dist/src/scan/findings.d.ts +320 -0
  145. package/dist/src/scan/findings.d.ts.map +1 -0
  146. package/dist/src/scan/findings.js +948 -0
  147. package/dist/src/scan/findings.js.map +1 -0
  148. package/dist/src/scan/ledger.d.ts +116 -0
  149. package/dist/src/scan/ledger.d.ts.map +1 -0
  150. package/dist/src/scan/ledger.js +207 -0
  151. package/dist/src/scan/ledger.js.map +1 -0
  152. package/dist/src/scan/presentation.d.ts +19 -0
  153. package/dist/src/scan/presentation.d.ts.map +1 -0
  154. package/dist/src/scan/presentation.js +9 -0
  155. package/dist/src/scan/presentation.js.map +1 -0
  156. package/dist/src/scan/scan.d.ts +354 -0
  157. package/dist/src/scan/scan.d.ts.map +1 -0
  158. package/dist/src/scan/scan.js +725 -0
  159. package/dist/src/scan/scan.js.map +1 -0
  160. package/dist/src/scan/scanagents.d.ts +23 -0
  161. package/dist/src/scan/scanagents.d.ts.map +1 -0
  162. package/dist/src/scan/scanagents.js +99 -0
  163. package/dist/src/scan/scanagents.js.map +1 -0
  164. package/dist/src/scan/scanassemble.d.ts +65 -0
  165. package/dist/src/scan/scanassemble.d.ts.map +1 -0
  166. package/dist/src/scan/scanassemble.js +93 -0
  167. package/dist/src/scan/scanassemble.js.map +1 -0
  168. package/dist/src/scan/scanfs.d.ts +26 -0
  169. package/dist/src/scan/scanfs.d.ts.map +1 -0
  170. package/dist/src/scan/scanfs.js +229 -0
  171. package/dist/src/scan/scanfs.js.map +1 -0
  172. package/dist/src/scan/scanlease.d.ts +50 -0
  173. package/dist/src/scan/scanlease.d.ts.map +1 -0
  174. package/dist/src/scan/scanlease.js +251 -0
  175. package/dist/src/scan/scanlease.js.map +1 -0
  176. package/dist/src/scan/scanleaves.d.ts +55 -0
  177. package/dist/src/scan/scanleaves.d.ts.map +1 -0
  178. package/dist/src/scan/scanleaves.js +258 -0
  179. package/dist/src/scan/scanleaves.js.map +1 -0
  180. package/dist/src/scan/scanports.d.ts +79 -0
  181. package/dist/src/scan/scanports.d.ts.map +1 -0
  182. package/dist/src/scan/scanports.js +8 -0
  183. package/dist/src/scan/scanports.js.map +1 -0
  184. package/dist/src/scan/scanrender.d.ts +128 -0
  185. package/dist/src/scan/scanrender.d.ts.map +1 -0
  186. package/dist/src/scan/scanrender.js +527 -0
  187. package/dist/src/scan/scanrender.js.map +1 -0
  188. package/dist/src/scan/scanreport.d.ts +126 -0
  189. package/dist/src/scan/scanreport.d.ts.map +1 -0
  190. package/dist/src/scan/scanreport.js +289 -0
  191. package/dist/src/scan/scanreport.js.map +1 -0
  192. package/dist/src/scan/scanrun.d.ts +472 -0
  193. package/dist/src/scan/scanrun.d.ts.map +1 -0
  194. package/dist/src/scan/scanrun.js +1733 -0
  195. package/dist/src/scan/scanrun.js.map +1 -0
  196. package/dist/src/scan/scantree.d.ts +21 -0
  197. package/dist/src/scan/scantree.d.ts.map +1 -0
  198. package/dist/src/scan/scantree.js +297 -0
  199. package/dist/src/scan/scantree.js.map +1 -0
  200. package/dist/src/scan/scanverbs.d.ts +208 -0
  201. package/dist/src/scan/scanverbs.d.ts.map +1 -0
  202. package/dist/src/scan/scanverbs.js +689 -0
  203. package/dist/src/scan/scanverbs.js.map +1 -0
  204. package/dist/src/scan/stage2.d.ts +221 -0
  205. package/dist/src/scan/stage2.d.ts.map +1 -0
  206. package/dist/src/scan/stage2.js +617 -0
  207. package/dist/src/scan/stage2.js.map +1 -0
  208. package/dist/src/scan/stage2request.d.ts +125 -0
  209. package/dist/src/scan/stage2request.d.ts.map +1 -0
  210. package/dist/src/scan/stage2request.js +259 -0
  211. package/dist/src/scan/stage2request.js.map +1 -0
  212. package/dist/src/scan/textsniff.d.ts +14 -0
  213. package/dist/src/scan/textsniff.d.ts.map +1 -0
  214. package/dist/src/scan/textsniff.js +88 -0
  215. package/dist/src/scan/textsniff.js.map +1 -0
  216. package/dist/src/scan/unitcut.d.ts +135 -0
  217. package/dist/src/scan/unitcut.d.ts.map +1 -0
  218. package/dist/src/scan/unitcut.js +437 -0
  219. package/dist/src/scan/unitcut.js.map +1 -0
  220. package/dist/src/scan/unitcutstore.d.ts +155 -0
  221. package/dist/src/scan/unitcutstore.d.ts.map +1 -0
  222. package/dist/src/scan/unitcutstore.js +381 -0
  223. package/dist/src/scan/unitcutstore.js.map +1 -0
  224. package/dist/src/setupchecklist.d.ts +240 -0
  225. package/dist/src/setupchecklist.d.ts.map +1 -0
  226. package/dist/src/setupchecklist.js +407 -0
  227. package/dist/src/setupchecklist.js.map +1 -0
  228. package/package.json +7 -3
  229. package/skills/qcr-review/SKILL.md +24 -0
  230. package/skills/qcr-scan/SKILL.md +23 -0
  231. package/skills/qcr-setup/SKILL.md +18 -0
@@ -0,0 +1,689 @@
1
+ // The scan's verbs — preview, start, status, stop, findings, the match
2
+ // question, suggestions, stage 2, dismiss, the unit cut, and the sandbox's
3
+ // own doctor, login and rebuild — over the engine and a host's ports, each
4
+ // rendered twice: the human default, worded for the surface it answers on
5
+ // (`ScanVerbs`), and a `--json` object on one line for scripts.
6
+ //
7
+ // Two surfaces run them: the app's `qcr scan` (src/scancli.ts), whose start
8
+ // launches a detached driver (in the scan sandbox, or a host process), and the
9
+ // pi plugin's /qcr-scan, whose start runs in the person's own pi. The promises
10
+ // are the runner's own and hold on either: `start` must quote the key
11
+ // `preview` handed out (a plan, sha, price or setting that moved since is
12
+ // refused), and a run stops hard at the estimate it showed.
13
+ //
14
+ // Exit codes: 0 done; 1 refused (or a waited-for run that did not end
15
+ // complete); 2 usage; 3 `status --wait` gave up at its `--timeout`.
16
+ import { readFileSync } from "node:fs";
17
+ import { SCAN_SCHEMA_VERSION, withSchemaVersion } from "../contract.js";
18
+ import { CUT_CLI_VERBS as CUT_QCR_VERBS, renderCut, renderDraft, renderStatus as renderCutStatus, } from "./cutrender.js";
19
+ import { dismissedLine } from "./presentation.js";
20
+ import { CLI_VERBS as QCR_SCAN_VERBS, FINDINGS_LIMIT, startedDetails, answerMatch, findingsFilter, listFindings, previewDetails, proposalDetails, renderFindings, renderPreview, renderProposal, renderQuestion, renderStatus, renderSuggestions, stage2StartedLine, startedLine, stopScan, } from "./scanrender.js";
21
+ import { dismissedLookalike, validityOf } from "./scanreport.js";
22
+ import { lensTiers, stage2Lenses, MAX_STAGE2_ROWS } from "./stage2.js";
23
+ export const SCAN_USAGE = "usage: qcr scan preview [--sha REF] [--delta] | start --key K [--sha REF] [--delta] [--foreground] | " +
24
+ "status [--wait [--timeout S]] | stop | " +
25
+ "findings [--state S] [--lens L] [--unit U] [--severity P] [--validity V] [--doubted|--not-doubted] [--limit N] | " +
26
+ "matches | match --key K [OLD=NEW…] | suggestions | " +
27
+ "stage2 propose [--rows FILE|-] [--budget USD] [--deep|--standard] [--sha REF] | " +
28
+ "stage2 start --key K [--rows ID,ID…] [--foreground] | dismiss FP… --reason TEXT | " +
29
+ "units show | units propose [--file F|-] | units adjust --file F|- | units save | login [--provider P] | doctor | rebuild (every subcommand takes --json, and --sandbox host|docker over [scan] sandbox)";
30
+ /** How often `status --wait` and `stop` look again. */
31
+ export const POLL_MS = 2000;
32
+ /** A usage error: the usage line, and why when there is one. */
33
+ export const usage = (why) => new Error(why ? `${SCAN_USAGE}\n(${why})` : SCAN_USAGE);
34
+ /** `qcr scan`'s arguments (`--json` taken off before). An unknown token is
35
+ * refused, so a mistyped flag never runs something else. */
36
+ export function parseScanArgs(rest) {
37
+ const [first, ...more] = rest;
38
+ let sub = first;
39
+ let args = more;
40
+ if (first === "stage2" || first === "units") {
41
+ sub = `${first}-${more[0] ?? ""}`;
42
+ args = more.slice(1);
43
+ }
44
+ const flags = new Map();
45
+ const positional = [];
46
+ const VALUED = new Set([
47
+ "--sha",
48
+ "--key",
49
+ "--timeout",
50
+ "--state",
51
+ "--lens",
52
+ "--unit",
53
+ "--severity",
54
+ "--validity",
55
+ "--limit",
56
+ "--rows",
57
+ "--budget",
58
+ "--reason",
59
+ "--file",
60
+ "--provider",
61
+ ]);
62
+ const BARE = new Set([
63
+ "--delta",
64
+ "--foreground",
65
+ "--wait",
66
+ "--doubted",
67
+ "--not-doubted",
68
+ "--deep",
69
+ "--standard",
70
+ ]);
71
+ for (let i = 0; i < args.length; i++) {
72
+ const a = args[i];
73
+ if (VALUED.has(a)) {
74
+ const v = args[++i];
75
+ if (v === undefined || v.startsWith("--"))
76
+ throw usage();
77
+ flags.set(a, v);
78
+ }
79
+ else if (BARE.has(a))
80
+ flags.set(a, true);
81
+ else if (a.startsWith("--"))
82
+ throw usage();
83
+ else
84
+ positional.push(a);
85
+ }
86
+ const allowed = (...names) => {
87
+ for (const k of flags.keys())
88
+ if (!names.includes(k))
89
+ throw usage();
90
+ };
91
+ const noPositional = () => {
92
+ if (positional.length > 0)
93
+ throw usage();
94
+ };
95
+ const str = (k) => {
96
+ const v = flags.get(k);
97
+ return typeof v === "string" ? v : undefined;
98
+ };
99
+ const num = (k, ok) => {
100
+ const v = str(k);
101
+ if (v === undefined)
102
+ return undefined;
103
+ const n = Number(v);
104
+ if (!Number.isFinite(n) || !ok(n))
105
+ throw usage();
106
+ return n;
107
+ };
108
+ const sha = () => (str("--sha") !== undefined ? { sha: str("--sha") } : {});
109
+ switch (sub) {
110
+ case "preview":
111
+ allowed("--sha", "--delta");
112
+ noPositional();
113
+ return { verb: "preview", ...sha(), delta: flags.has("--delta") };
114
+ case "start": {
115
+ allowed("--key", "--sha", "--delta", "--foreground");
116
+ noPositional();
117
+ const key = str("--key");
118
+ if (key === undefined)
119
+ throw usage("start needs --key — the key a preview showed");
120
+ return {
121
+ verb: "start",
122
+ key,
123
+ ...sha(),
124
+ delta: flags.has("--delta"),
125
+ foreground: flags.has("--foreground"),
126
+ };
127
+ }
128
+ case "status": {
129
+ allowed("--wait", "--timeout");
130
+ noPositional();
131
+ const timeout = num("--timeout", (n) => n > 0);
132
+ if (timeout !== undefined && !flags.has("--wait"))
133
+ throw usage("--timeout needs --wait");
134
+ return {
135
+ verb: "status",
136
+ wait: flags.has("--wait"),
137
+ ...(timeout !== undefined ? { timeout } : {}),
138
+ };
139
+ }
140
+ case "stop":
141
+ allowed();
142
+ noPositional();
143
+ return { verb: "stop" };
144
+ case "findings": {
145
+ allowed("--state", "--lens", "--unit", "--severity", "--validity", "--doubted", "--not-doubted", "--limit");
146
+ noPositional();
147
+ if (flags.has("--doubted") && flags.has("--not-doubted"))
148
+ throw usage();
149
+ const query = {};
150
+ for (const k of ["state", "lens", "unit", "severity", "validity"]) {
151
+ const v = str(`--${k}`);
152
+ if (v !== undefined)
153
+ query[k] = v;
154
+ }
155
+ if (flags.has("--doubted"))
156
+ query.doubted = true;
157
+ if (flags.has("--not-doubted"))
158
+ query.doubted = false;
159
+ const limit = num("--limit", (n) => Number.isInteger(n) && n > 0);
160
+ if (limit !== undefined)
161
+ query.limit = limit;
162
+ return { verb: "findings", query };
163
+ }
164
+ case "matches":
165
+ allowed();
166
+ noPositional();
167
+ return { verb: "matches" };
168
+ case "match": {
169
+ allowed("--key");
170
+ const key = str("--key");
171
+ if (key === undefined)
172
+ throw usage("match needs --key — the key the match question showed");
173
+ const pairs = positional.map((p) => {
174
+ const m = /^([0-9a-f]+)=([0-9a-f]+)$/i.exec(p);
175
+ if (!m)
176
+ throw usage(`a pair is OLD=NEW (two fingerprints), not ${JSON.stringify(p)}`);
177
+ return { old: m[1], new: m[2] };
178
+ });
179
+ return { verb: "match", key, pairs };
180
+ }
181
+ case "suggestions":
182
+ allowed();
183
+ noPositional();
184
+ return { verb: "suggestions" };
185
+ case "stage2-propose": {
186
+ allowed("--rows", "--budget", "--deep", "--standard", "--sha");
187
+ noPositional();
188
+ if (flags.has("--deep") && flags.has("--standard"))
189
+ throw usage();
190
+ const budget = num("--budget", (n) => n > 0);
191
+ return {
192
+ verb: "stage2-propose",
193
+ ...(str("--rows") !== undefined ? { rows: str("--rows") } : {}),
194
+ ...(budget !== undefined ? { budget } : {}),
195
+ ...(flags.has("--deep") ? { deep: true } : flags.has("--standard") ? { deep: false } : {}),
196
+ ...sha(),
197
+ };
198
+ }
199
+ case "stage2-start": {
200
+ allowed("--key", "--rows", "--foreground");
201
+ noPositional();
202
+ const key = str("--key");
203
+ if (key === undefined)
204
+ throw usage("stage2 start needs --key — the key the stage 2 proposal showed");
205
+ const rows = str("--rows");
206
+ return {
207
+ verb: "stage2-start",
208
+ key,
209
+ ...(rows !== undefined ? { rows: rows.split(",").map((r) => r.trim()) } : {}),
210
+ foreground: flags.has("--foreground"),
211
+ };
212
+ }
213
+ case "dismiss": {
214
+ allowed("--reason");
215
+ const reason = str("--reason");
216
+ if (reason === undefined)
217
+ throw usage("dismiss needs --reason — it is what PR rounds read");
218
+ if (positional.length === 0)
219
+ throw usage("dismiss needs at least one fingerprint");
220
+ return { verb: "dismiss", fingerprints: positional, reason };
221
+ }
222
+ case "login": {
223
+ allowed("--provider");
224
+ noPositional();
225
+ const provider = str("--provider");
226
+ return { verb: "login", ...(provider !== undefined ? { provider } : {}) };
227
+ }
228
+ case "units-show":
229
+ case "units-save":
230
+ case "doctor":
231
+ case "rebuild":
232
+ allowed();
233
+ noPositional();
234
+ return { verb: sub };
235
+ case "units-propose": {
236
+ allowed("--file");
237
+ noPositional();
238
+ const file = str("--file");
239
+ return { verb: "units-propose", ...(file !== undefined ? { file } : {}) };
240
+ }
241
+ case "units-adjust": {
242
+ allowed("--file");
243
+ noPositional();
244
+ const file = str("--file");
245
+ if (file === undefined)
246
+ throw usage("units adjust needs --file — a JSON array of adjust ops (or - for stdin)");
247
+ return { verb: "units-adjust", file };
248
+ }
249
+ default:
250
+ throw usage();
251
+ }
252
+ }
253
+ export function findingJson(f) {
254
+ const look = dismissedLookalike(f);
255
+ return {
256
+ fingerprint: f.fingerprint,
257
+ file: f.path,
258
+ range: { start: f.start, end: f.end },
259
+ lens: f.lens,
260
+ lenses: f.lenses ?? [f.lens],
261
+ unit: f.unit,
262
+ kind: f.kind,
263
+ severity: f.severity,
264
+ validity: validityOf(f),
265
+ state: f.state,
266
+ claim: f.claim,
267
+ doubted: f.doubt !== null,
268
+ doubt: f.doubt
269
+ ? { word: f.doubt.word, reason: f.doubt.reason, class: f.doubt.class ?? null }
270
+ : null,
271
+ looks_like_dismissed: look ? { fingerprint: look.fingerprint, reason: look.reason } : null,
272
+ dismissal_reason: f.dismissal?.reason ?? null,
273
+ ticket: f.ticket?.ref ?? null,
274
+ first_seen: { run: f.first_seen.run, sha: f.first_seen.sha },
275
+ last_seen: { run: f.last_seen.run, sha: f.last_seen.sha },
276
+ };
277
+ }
278
+ /** A run as `status --json` and a foreground start carry it. */
279
+ export function runJson(run) {
280
+ const n = (k) => run.cases.reduce((t, c) => t + c[k], 0);
281
+ return {
282
+ id: run.id,
283
+ mode: run.mode,
284
+ sha: run.sha,
285
+ state: run.state,
286
+ display: run.display ?? run.state,
287
+ depth: run.settings.depth ?? "standard",
288
+ started_at: run.started_at,
289
+ finished_at: run.finished_at,
290
+ cap_usd: run.cap_usd,
291
+ estimate_usd: run.estimate_usd,
292
+ spent_usd: run.spent_usd,
293
+ unpriced_cases: run.unpriced_cases,
294
+ cases_total: run.cases.length,
295
+ cases_done: run.cases.filter((c) => c.status === "done").length,
296
+ cases_failed: run.cases.filter((c) => c.status === "failed").length,
297
+ findings: n("findings"),
298
+ doubted: n("doubted"),
299
+ matching: run.matching ?? null,
300
+ tickets_closed: run.tickets_closed ?? 0,
301
+ problems: run.problems,
302
+ cases: run.cases.map((c) => ({
303
+ id: c.id,
304
+ unit: c.unit,
305
+ status: c.status,
306
+ findings: c.findings,
307
+ doubted: c.doubted,
308
+ cost_usd: c.cost_usd,
309
+ error: c.error,
310
+ ...(c.lens !== undefined ? { lens: c.lens, tier: c.tier ?? null } : {}),
311
+ })),
312
+ };
313
+ }
314
+ function proposalJson(p) {
315
+ return {
316
+ ...proposalDetails(p),
317
+ budget_usd: p.budget_usd,
318
+ over_budget_usd: p.over_budget_usd,
319
+ validator: p.validator,
320
+ row_list: p.rows.map((r) => ({
321
+ id: r.id,
322
+ lens: r.lens,
323
+ tier: r.tier,
324
+ units: r.units,
325
+ files: r.files.map((f) => f.path),
326
+ tests: r.tests.map((f) => f.path),
327
+ lines: r.lines,
328
+ estimate_usd: r.estimate_usd,
329
+ status: r.status,
330
+ floor: r.floor === true,
331
+ reason: r.reason,
332
+ })),
333
+ };
334
+ }
335
+ /** Run one scan subcommand (`qcr scan …`, `/qcr-scan …`); resolves its exit code. */
336
+ export async function runScanVerb(rest, json, deps) {
337
+ const CLI_VERBS = deps.verbs ?? QCR_SCAN_VERBS;
338
+ const CUT_CLI_VERBS = deps.cutVerbs ?? CUT_QCR_VERBS;
339
+ const emit = (code, human, data) => {
340
+ deps.out(json ? JSON.stringify(withSchemaVersion(SCAN_SCHEMA_VERSION, data)) : human);
341
+ return code;
342
+ };
343
+ const refusal = (what, errors, code = 1) => emit(code, `${what} refused:\n${errors.map((e) => `✗ ${e}`).join("\n")}`, {
344
+ ok: false,
345
+ errors,
346
+ });
347
+ let cmd;
348
+ try {
349
+ cmd = parseScanArgs(rest);
350
+ }
351
+ catch (err) {
352
+ const msg = err instanceof Error ? err.message : String(err);
353
+ return emit(2, msg, { ok: false, errors: [msg] });
354
+ }
355
+ const { scan } = deps;
356
+ switch (cmd.verb) {
357
+ case "preview": {
358
+ const r = await scan.preview({ ...(cmd.sha ? { sha: cmd.sha } : {}), delta: cmd.delta });
359
+ if (!r.ok)
360
+ return refusal("Scan preview", r.errors);
361
+ const p = r.preview;
362
+ const start = p.blockers.length > 0
363
+ ? null
364
+ : ["--key", p.key, "--sha", p.sha, ...(p.mode === "delta" ? ["--delta"] : [])];
365
+ return emit(0, renderPreview(p, CLI_VERBS), {
366
+ ok: true,
367
+ preview: previewDetails(p),
368
+ start,
369
+ });
370
+ }
371
+ case "start":
372
+ case "stage2-start": {
373
+ const stage2 = cmd.verb === "stage2-start";
374
+ const argv = cmd.verb === "start"
375
+ ? [
376
+ "start",
377
+ "--key",
378
+ cmd.key,
379
+ ...(cmd.sha ? ["--sha", cmd.sha] : []),
380
+ ...(cmd.delta ? ["--delta"] : []),
381
+ ]
382
+ : [
383
+ "stage2",
384
+ "start",
385
+ "--key",
386
+ cmd.key,
387
+ ...(cmd.rows ? ["--rows", cmd.rows.join(",")] : []),
388
+ ];
389
+ const what = stage2 ? "Stage 2 start" : "Scan start";
390
+ const line = (run) => stage2
391
+ ? stage2StartedLine(run, scan.stage2Proposal().proposal?.runnable ?? null, cmd.verb === "stage2-start" && cmd.rows !== undefined, CLI_VERBS)
392
+ : startedLine(run, CLI_VERBS);
393
+ if (!cmd.foreground) {
394
+ const d = await deps.detach(argv);
395
+ if (!d.ok)
396
+ return refusal(what, d.errors);
397
+ return emit(0, `${line(d.run)}\nDriver output: ${d.log}${d.container ? `\nContainer: ${d.container}` : ""}`, {
398
+ ok: true,
399
+ run: startedDetails(d.run),
400
+ log: d.log,
401
+ ...(d.container ? { container: d.container } : {}),
402
+ });
403
+ }
404
+ const r = cmd.verb === "start"
405
+ ? await scan.start({
406
+ key: cmd.key,
407
+ ...(cmd.sha ? { sha: cmd.sha } : {}),
408
+ delta: cmd.delta,
409
+ })
410
+ : await scan.startStage2({ key: cmd.key, ...(cmd.rows ? { rows: cmd.rows } : {}) });
411
+ if (!r.ok) {
412
+ deps.onStart?.({ ok: false, errors: r.errors });
413
+ return refusal(what, r.errors);
414
+ }
415
+ deps.onStart?.({ ok: true, run: r.run, log: deps.driverLog });
416
+ if (!json)
417
+ deps.out(line(r.run));
418
+ // A pending promise alone keeps no process alive: hold it open until
419
+ // the run ends.
420
+ const keep = setInterval(() => { }, 60_000);
421
+ const unwatch = deps.watchStops?.(scan) ?? null;
422
+ try {
423
+ await scan.idle();
424
+ }
425
+ finally {
426
+ unwatch?.();
427
+ clearInterval(keep);
428
+ }
429
+ const s = scan.status();
430
+ const complete = s.run?.display === "complete";
431
+ return emit(complete ? 0 : 1, renderStatus(s, CLI_VERBS), {
432
+ ok: complete,
433
+ run: s.run ? runJson(s.run) : null,
434
+ });
435
+ }
436
+ case "status": {
437
+ let s = scan.status();
438
+ const t0 = deps.now();
439
+ while (cmd.wait && s.run?.display === "running") {
440
+ if (cmd.timeout !== undefined && deps.now() - t0 >= cmd.timeout * 1000) {
441
+ return emit(3, `${renderStatus(s, CLI_VERBS)}\nstill running after ${cmd.timeout}s`, {
442
+ ...statusJson(s),
443
+ timed_out: true,
444
+ });
445
+ }
446
+ await deps.sleep(POLL_MS);
447
+ s = scan.status();
448
+ }
449
+ const code = cmd.wait
450
+ ? s.run === null || s.run.display === "complete"
451
+ ? 0
452
+ : 1
453
+ : s.ok
454
+ ? 0
455
+ : 1;
456
+ return emit(code, renderStatus(s, CLI_VERBS), statusJson(s));
457
+ }
458
+ case "stop":
459
+ return stop(deps, emit, refusal);
460
+ case "findings": {
461
+ const f = findingsFilter(cmd.query);
462
+ if (!f.ok)
463
+ return emit(2, f.error, { ok: false, errors: [f.error] });
464
+ const listed = listFindings(deps.findings, f.filter);
465
+ if (!listed.ok)
466
+ return refusal("Findings", [listed.error]);
467
+ const all = listed.findings;
468
+ const shown = all.slice(0, cmd.query.limit ?? (json ? all.length : FINDINGS_LIMIT));
469
+ return emit(0, renderFindings(shown, all.length), {
470
+ ok: true,
471
+ total: all.length,
472
+ validity: listed.validity,
473
+ findings: shown.map(findingJson),
474
+ });
475
+ }
476
+ case "matches": {
477
+ const { question, error } = deps.findings.question();
478
+ if (error !== null)
479
+ return refusal("Match question", [error]);
480
+ return emit(0, renderQuestion(question, CLI_VERBS), {
481
+ ok: true,
482
+ question: question
483
+ ? {
484
+ key: question.key,
485
+ run: question.run,
486
+ sha: question.sha,
487
+ unreported: question.unreported.map(findingJson),
488
+ fresh: question.fresh.map(findingJson),
489
+ }
490
+ : null,
491
+ });
492
+ }
493
+ case "match": {
494
+ const r = await answerMatch(deps.findings, { key: cmd.key, pairs: cmd.pairs }, deps.afterMatch);
495
+ if (!r.ok)
496
+ return refusal("Match", r.errors);
497
+ const { tickets, lines, ...result } = r;
498
+ return emit(0, lines.join("\n"), {
499
+ ...result,
500
+ tickets_closed: tickets?.closed.length ?? 0,
501
+ });
502
+ }
503
+ case "suggestions": {
504
+ const { suggestions, error } = scan.suggestions();
505
+ if (error !== null)
506
+ return refusal("Suggestions", [error]);
507
+ const s = deps.settingsOf();
508
+ return emit(0, renderSuggestions(suggestions, s), {
509
+ ok: true,
510
+ suggestions,
511
+ stage2: s
512
+ ? {
513
+ lenses: stage2Lenses(s.lenses).map((lens) => ({
514
+ lens,
515
+ tiers: lensTiers(s.lenses, lens, s.riskPasses ?? []),
516
+ })),
517
+ depth: s.scan.depth,
518
+ deep_lenses: s.scan.deep_lenses,
519
+ case_max_files: s.scan.case_max_files,
520
+ case_max_lines: s.scan.case_max_lines,
521
+ max_rows: MAX_STAGE2_ROWS,
522
+ }
523
+ : null,
524
+ });
525
+ }
526
+ case "stage2-propose": {
527
+ let rows = [];
528
+ if (cmd.rows !== undefined) {
529
+ try {
530
+ rows = JSON.parse(readFileSync(cmd.rows === "-" ? 0 : cmd.rows, "utf8"));
531
+ }
532
+ catch (err) {
533
+ return refusal("Stage 2 proposal", [
534
+ `the rows (${cmd.rows}) cannot be read as JSON: ${err instanceof Error ? err.message : String(err)}`,
535
+ ]);
536
+ }
537
+ }
538
+ const r = await scan.proposeStage2({
539
+ rows,
540
+ ...(cmd.budget !== undefined ? { budget_usd: cmd.budget } : {}),
541
+ ...(cmd.sha ? { sha: cmd.sha } : {}),
542
+ ...(cmd.deep !== undefined ? { deep: cmd.deep } : {}),
543
+ });
544
+ if (!r.ok)
545
+ return refusal("Stage 2 proposal", r.errors);
546
+ return emit(0, renderProposal(r.proposal, CLI_VERBS), {
547
+ ok: true,
548
+ proposal: proposalJson(r.proposal),
549
+ });
550
+ }
551
+ case "dismiss": {
552
+ if (deps.triage === null)
553
+ return refusal("Dismiss", ["no triage in this assembly"]);
554
+ const dismissed = [];
555
+ const errors = [];
556
+ const lines = [];
557
+ for (const fp of cmd.fingerprints) {
558
+ const r = await deps.triage.dismiss({ fingerprint: fp, reason: cmd.reason });
559
+ if (!r.ok) {
560
+ errors.push(`${fp}: ${r.errors.join("; ")}`);
561
+ lines.push(`✗ ${fp}: ${r.errors.join("; ")}`);
562
+ continue;
563
+ }
564
+ dismissed.push({
565
+ fingerprint: r.finding.fingerprint,
566
+ again: r.again,
567
+ memory: r.memory === "written"
568
+ ? { written: true }
569
+ : { written: false, problem: r.memory.problem },
570
+ });
571
+ lines.push(dismissedLine(r));
572
+ }
573
+ return emit(errors.length === 0 ? 0 : 1, lines.join("\n"), {
574
+ ok: errors.length === 0,
575
+ dismissed,
576
+ errors,
577
+ });
578
+ }
579
+ case "units-show":
580
+ case "units-propose":
581
+ case "units-adjust":
582
+ case "units-save":
583
+ return units(cmd, deps.units ?? null, emit, refusal, CUT_CLI_VERBS);
584
+ case "doctor": {
585
+ if (!deps.doctor)
586
+ return refusal("Doctor", ["no doctor in this assembly"]);
587
+ const r = await deps.doctor();
588
+ return emit(r.ok ? 0 : 1, r.human, r.data);
589
+ }
590
+ case "login":
591
+ if (json)
592
+ return refusal("Login", ["login is interactive; run qcr scan login without --json"]);
593
+ if (!deps.login)
594
+ return refusal("Login", ["no login in this assembly"]);
595
+ return deps.login(cmd.provider);
596
+ case "rebuild": {
597
+ if (!deps.rebuild)
598
+ return refusal("Scan image rebuild", ["no rebuild in this assembly"]);
599
+ const r = await deps.rebuild();
600
+ if (!r.ok)
601
+ return refusal("Scan image rebuild", r.errors);
602
+ return emit(0, `Scan image ${r.image} built (stamp ${r.stamp}); no container was stopped or replaced. ` +
603
+ "A companion on the same image keeps its container until `qcr companion rebuild`.", { ok: true, image: r.image, stamp: r.stamp });
604
+ }
605
+ }
606
+ }
607
+ /** `qcr scan units …`: the setup belt's unit-cut tools, over the same
608
+ * engine and rendering, worded for the CLI. */
609
+ function units(cmd, engine, emit, refusal, CUT_CLI_VERBS) {
610
+ if (engine === null)
611
+ return refusal("Unit cut", ["no setup engine in this assembly"]);
612
+ const readJson = (file) => {
613
+ try {
614
+ return { ok: true, value: JSON.parse(readFileSync(file === "-" ? 0 : file, "utf8")) };
615
+ }
616
+ catch (err) {
617
+ return {
618
+ ok: false,
619
+ error: `${file === "-" ? "stdin" : file} cannot be read as JSON: ${err instanceof Error ? err.message : String(err)}`,
620
+ };
621
+ }
622
+ };
623
+ switch (cmd.verb) {
624
+ case "units-show": {
625
+ const s = engine.status();
626
+ return emit(0, renderCutStatus(s), { ok: true, status: s });
627
+ }
628
+ case "units-propose": {
629
+ let input;
630
+ if (cmd.file !== undefined) {
631
+ const j = readJson(cmd.file);
632
+ if (!j.ok)
633
+ return refusal("Proposal", [j.error]);
634
+ input = j.value;
635
+ }
636
+ const r = engine.propose(input);
637
+ if (!r.ok)
638
+ return refusal("Proposal", r.errors);
639
+ return emit(0, renderDraft("Draft stored", r.draft, CUT_CLI_VERBS), {
640
+ ok: true,
641
+ draft: r.draft,
642
+ });
643
+ }
644
+ case "units-adjust": {
645
+ const j = readJson(cmd.file);
646
+ if (!j.ok)
647
+ return refusal("Adjustment", [j.error]);
648
+ // The tool's own parameter shape, `{ops: […]}`, or the ops alone.
649
+ const v = j.value;
650
+ const ops = typeof v === "object" && v !== null && !Array.isArray(v) && "ops" in v ? v.ops : v;
651
+ const r = engine.adjust(ops);
652
+ if (!r.ok)
653
+ return refusal("Adjustment", r.errors);
654
+ return emit(0, renderDraft("Draft adjusted", r.draft, CUT_CLI_VERBS), {
655
+ ok: true,
656
+ draft: r.draft,
657
+ });
658
+ }
659
+ case "units-save": {
660
+ const r = engine.save();
661
+ if (!r.ok)
662
+ return refusal("Save", r.errors);
663
+ return emit(0, renderCut("Unit cut saved", r.saved), { ok: true, saved: r.saved });
664
+ }
665
+ }
666
+ }
667
+ function statusJson(s) {
668
+ return {
669
+ ok: s.ok,
670
+ problems: s.problems,
671
+ run: s.run ? runJson(s.run) : null,
672
+ active: s.active,
673
+ history: s.history,
674
+ ledger: s.ledger,
675
+ };
676
+ }
677
+ /** `qcr scan stop`: the shared stop (`stopScan`), rendered. */
678
+ async function stop(deps, emit, refusal) {
679
+ const r = await stopScan(deps.scan, {
680
+ home: deps.home,
681
+ by: "cli",
682
+ sleep: deps.sleep,
683
+ now: deps.now,
684
+ });
685
+ if (!r.ok)
686
+ return refusal("Scan stop", r.errors);
687
+ return emit(0, r.answer, { ok: true, run: r.run, answer: r.answer });
688
+ }
689
+ //# sourceMappingURL=scanverbs.js.map