@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,240 @@
1
+ /** What the engine itself needs of its host. A host's own adapter set extends
2
+ * it with what its steps need (the app's wizard adds fetch, a process runner,
3
+ * paths…); a step that needs only this runs on any host's checklist. */
4
+ export interface ChecklistAdapters {
5
+ clock: () => Date;
6
+ log?: (line: string) => void;
7
+ }
8
+ export interface CheckResult {
9
+ name: string;
10
+ ok: boolean;
11
+ detail: string;
12
+ }
13
+ /** What run() and verify() both return. `data` (run only) is persisted onto
14
+ * the step's checklist record and handed back as ctx.data on later calls. */
15
+ export interface StepReport {
16
+ ok: boolean;
17
+ checks: CheckResult[];
18
+ data?: Record<string, unknown>;
19
+ }
20
+ /** What detect() returns: already true (`found` says what was found, as the
21
+ * person reads it), or not (`missing` says why, with the fix when it has
22
+ * one). */
23
+ export type Detection = {
24
+ found: string;
25
+ } | {
26
+ missing: string;
27
+ };
28
+ export interface StepContext<A extends ChecklistAdapters = ChecklistAdapters> {
29
+ adapters: A;
30
+ /** The inputs of the call: the person's answer to a step that asks one
31
+ * (`{}` for a step that declares no `parameters`, and always on verify()
32
+ * and detect()). A step that needs an answer and does not get one must
33
+ * return a refusal naming the options, never a guess. */
34
+ input: Readonly<Record<string, unknown>>;
35
+ /** Recorded choices (topology, auth route, captured uid, …). */
36
+ choices: Readonly<Record<string, unknown>>;
37
+ /** This step's persisted data from a previous run ({} on first run). */
38
+ data: Readonly<Record<string, unknown>>;
39
+ /** Record a choice / derived fact for later steps (persisted with the step
40
+ * result). */
41
+ recordChoice(key: string, value: unknown): void;
42
+ }
43
+ /** A step id: tool-name-safe, because a host may register each step as a
44
+ * tool of its own (the wizard's `onboard_<id>`). */
45
+ export declare const STEP_ID_RE: RegExp;
46
+ /**
47
+ * One setup step; the interface is the extension point:
48
+ * - `id` matches STEP_ID_RE and is unique on its registry;
49
+ * - `explain` is the teach-text a guide relays before running the step;
50
+ * - `enabled` gates on recorded choices; absent ⇒ always enabled;
51
+ * - `detect`, `run` and `verify` as the module header says.
52
+ */
53
+ export interface SetupStep<A extends ChecklistAdapters = ChecklistAdapters> {
54
+ id: string;
55
+ title: string;
56
+ explain: string;
57
+ /** Ascending display/registration order. */
58
+ order: number;
59
+ /** JSON-schema object the step's tool declares, for steps that need the
60
+ * person's answer (a choice, a hostname). Absent ⇒ the step takes no
61
+ * input, which is the common case. */
62
+ parameters?: unknown;
63
+ enabled?(choices: Readonly<Record<string, unknown>>): boolean;
64
+ /** The checklist completes without it: it still runs and verifies like any
65
+ * other, but finish never waits on it. */
66
+ optional?: boolean;
67
+ detect?(ctx: StepContext<A>): Promise<Detection>;
68
+ run(ctx: StepContext<A>): Promise<StepReport>;
69
+ verify(ctx: StepContext<A>): Promise<StepReport>;
70
+ }
71
+ export declare function failures(checks: readonly CheckResult[]): string[];
72
+ /** The steps one checklist walks, in `order` order. Each host keeps its own:
73
+ * the plugin's /qcr-setup builds one of the plugin's steps, and the app
74
+ * builds one of those steps plus its own, so a step the app registers never
75
+ * reaches a plain /qcr-setup. */
76
+ export declare class StepRegistry<A extends ChecklistAdapters = ChecklistAdapters> {
77
+ private readonly entries;
78
+ constructor(steps?: Iterable<SetupStep<A>>);
79
+ /** Add steps. An id that is not tool-name-safe, or one already registered,
80
+ * throws: two steps under one id would share one checklist record. */
81
+ register(...steps: SetupStep<A>[]): this;
82
+ /** The registered steps in ascending `order` (registration order breaks a
83
+ * tie). */
84
+ steps(): SetupStep<A>[];
85
+ }
86
+ export declare const CHECKLIST_VERSION = 1;
87
+ export interface StepRecord {
88
+ status: "pending" | "done";
89
+ completed_at: string | null;
90
+ /** Last successful re-verification (resume / finish / detect). */
91
+ verified_at: string | null;
92
+ /** Why a claimed-done step was demoted, why detect found it missing, or
93
+ * other engine notes. */
94
+ note: string | null;
95
+ /** What detect() found when it marked the step done without a run; absent
96
+ * once a run did it. */
97
+ found?: string;
98
+ data: Record<string, unknown>;
99
+ }
100
+ export interface ChecklistFile {
101
+ version: number;
102
+ created_at: string;
103
+ updated_at: string;
104
+ /** Set exactly once, by finish() when every enabled step verifies green.
105
+ * History: when the checklist first finished; it never moves and never
106
+ * clears. */
107
+ completed_at: string | null;
108
+ /** When a completed checklist stopped verifying (a demotion, a failed run,
109
+ * a refused finish, a newly-enabled unfinished step, a step run again:
110
+ * a run changes what the full verification checked); null/absent while the
111
+ * completion still holds. A green finish() clears it. Whether the checklist
112
+ * is complete now is `completed_at` set and this unset. */
113
+ reopened_at?: string | null;
114
+ choices: Record<string, unknown>;
115
+ steps: Record<string, StepRecord>;
116
+ }
117
+ /** Read a checklist file; null when absent or unreadable (both mean
118
+ * "fresh"). */
119
+ export declare function readChecklistFile(path: string): ChecklistFile | null;
120
+ /** Is the checklist complete NOW: finished once, and not reopened since. The
121
+ * one reading of the two fields, for a file on disk and the engine's copy. */
122
+ export declare function checklistCompleteNow(file: Pick<ChecklistFile, "completed_at" | "reopened_at">): boolean;
123
+ /** tmp+rename atomic write: a reader never sees a half-written file, and an
124
+ * interrupted write leaves the previous file whole. The checklist's writer,
125
+ * and the one the app's setup steps write their files through. */
126
+ export declare function writeAtomic(path: string, content: string): void;
127
+ /** A file's text, or null when it cannot be read for any reason. For a read
128
+ * where only ABSENCE may be null, read it yourself. */
129
+ export declare function readTextOrNull(path: string): string | null;
130
+ export interface StepStatusLine {
131
+ id: string;
132
+ title: string;
133
+ explain: string;
134
+ status: "pending" | "done" | "disabled";
135
+ note: string | null;
136
+ /** What detect() found, when that is how the step got done. */
137
+ found?: string;
138
+ /** The step's `optional` flag: the checklist completes without it. */
139
+ optional?: boolean;
140
+ }
141
+ export interface ReconcileReport {
142
+ /** Claimed-done steps whose verify() held. */
143
+ verified: string[];
144
+ /** Claimed-done steps whose verify() failed: demoted to pending. */
145
+ demoted: Array<{
146
+ id: string;
147
+ failures: string[];
148
+ }>;
149
+ }
150
+ export interface DetectReport {
151
+ /** One entry per enabled, not-yet-done step that has a detect(). */
152
+ results: Array<{
153
+ id: string;
154
+ found: string;
155
+ } | {
156
+ id: string;
157
+ missing: string;
158
+ }>;
159
+ }
160
+ export interface StepRunResult {
161
+ id: string;
162
+ ok: boolean;
163
+ checks: CheckResult[];
164
+ failures: string[];
165
+ }
166
+ export interface VerifyAllResult {
167
+ ok: boolean;
168
+ /** `optional` results are reported but never hold `ok` false. */
169
+ results: Array<{
170
+ id: string;
171
+ ok: boolean;
172
+ failures: string[];
173
+ optional?: boolean;
174
+ }>;
175
+ }
176
+ export type ChecklistFinish = {
177
+ ok: true;
178
+ completed_at: string;
179
+ } | {
180
+ ok: false;
181
+ results: VerifyAllResult["results"];
182
+ };
183
+ export interface ChecklistOptions {
184
+ /** Where the checklist persists; null keeps it in memory only. */
185
+ file: string | null;
186
+ }
187
+ export declare class SetupChecklist<A extends ChecklistAdapters = ChecklistAdapters> {
188
+ protected readonly adapters: A;
189
+ private readonly registry;
190
+ private checklist;
191
+ private readonly file;
192
+ constructor(steps: StepRegistry<A> | readonly SetupStep<A>[], adapters: A, o: ChecklistOptions);
193
+ private fresh;
194
+ private save;
195
+ private record;
196
+ private ctxFor;
197
+ /** The registered steps, in order (a host's tools register off this). */
198
+ stepDefs(): readonly SetupStep<A>[];
199
+ private enabled;
200
+ choices(): Readonly<Record<string, unknown>>;
201
+ isComplete(): boolean;
202
+ /** A completed checklist no longer verifies: withdraw the current-completion
203
+ * claim without touching the history. */
204
+ private reopen;
205
+ list(): StepStatusLine[];
206
+ /**
207
+ * Detect: ask every enabled, not-yet-done step that has a detect() whether
208
+ * it is already true. A step that is is marked done with what was found,
209
+ * and never runs; one that is not stays pending with why in its note. A
210
+ * throwing detect is a "missing", never a throw. Persists once at the end.
211
+ */
212
+ detect(): Promise<DetectReport>;
213
+ /**
214
+ * Resume: re-verify every enabled claimed-done step against reality. A step
215
+ * whose verify() fails is demoted to pending with the failures in its
216
+ * note: the file's claim is never trusted alone. A fully-done checklist
217
+ * therefore re-runs as verify-only. Persists once at the end.
218
+ */
219
+ reconcile(): Promise<ReconcileReport>;
220
+ /** Run one step, optionally with the person's answer (`input`, the tool's
221
+ * params). Unknown/disabled ids are refusals-as-answers, never throws (the
222
+ * caller may be an LLM tool belt). Persists after the step. */
223
+ runStep(id: string, input?: Record<string, unknown>): Promise<StepRunResult>;
224
+ private safeVerify;
225
+ /**
226
+ * Verify every enabled step, done or not. A pending step whose verify()
227
+ * holds is promoted to done (its outcome exists in reality, which is the
228
+ * only truth the checklist mirrors); a done step whose verify() fails
229
+ * reports red here (finish refuses) but demotion is reconcile()'s job.
230
+ */
231
+ verifyAll(): Promise<VerifyAllResult>;
232
+ /**
233
+ * Re-run the full verification. Anything red ⇒ refuse with the failures.
234
+ * All green ⇒ mark the checklist complete; `completed_at` is set once and
235
+ * never moves. A host that marks its own first finish (the app's first-run
236
+ * marker) does so on top of this.
237
+ */
238
+ finish(): Promise<ChecklistFinish>;
239
+ }
240
+ //# sourceMappingURL=setupchecklist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupchecklist.d.ts","sourceRoot":"","sources":["../../src/setupchecklist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,407 @@
1
+ // The setup checklist: a step registry and the engine that walks it. Ordered
2
+ // steps, each with detect, run and verify, recorded in a checklist. ADR 0001
3
+ // makes this a public seam: the plugin's /qcr-setup is the checklist of the
4
+ // plugin's own steps (src/plugin/setup.ts), and the app's setup is that
5
+ // checklist with more steps registered onto it (the onboarding wizard,
6
+ // src/onboard/ in the app, and the home's first-run steps). The app's steps
7
+ // live on the app's registry only, so plain /qcr-setup never shows them.
8
+ //
9
+ // Nothing here touches the network, a process or pi: a step reaches the
10
+ // outside world through the adapters its host hands in (or the facts its own
11
+ // factory closed over), and the engine itself is fully drivable from tests.
12
+ //
13
+ // A step's three verbs:
14
+ // - detect: is it already true? `{ found }` (what was found, for the
15
+ // person: "@user · via gh") marks the step done without running it;
16
+ // `{ missing }` says why not. A step without detect is never skipped.
17
+ // - run: performs the step, with the person's answer as input.
18
+ // - verify: re-checks its outcome against reality, safe to call any number
19
+ // of times.
20
+ //
21
+ // Resumability contract (for a checklist kept in a file):
22
+ // - checklist state persists after every step, tmp+rename atomic;
23
+ // - a resume (reconcile) re-verifies every claimed-done step with its
24
+ // verify(), never trusting the file alone;
25
+ // - a fully-done checklist re-runs as verify-only (the checklist doubles as
26
+ // a diagnostic);
27
+ // - a corrupt/unreadable checklist degrades to a fresh one (the resume
28
+ // re-verifies everything anyway), never a crash.
29
+ // A checklist kept in memory (`file: null`, /qcr-setup's) has the same
30
+ // behaviour for its lifetime and writes nothing.
31
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
32
+ import { basename, dirname, join } from "node:path";
33
+ /** A step id: tool-name-safe, because a host may register each step as a
34
+ * tool of its own (the wizard's `onboard_<id>`). */
35
+ export const STEP_ID_RE = /^[a-z][a-z0-9_]*$/;
36
+ export function failures(checks) {
37
+ return checks.filter((c) => !c.ok).map((c) => `${c.name}: ${c.detail}`);
38
+ }
39
+ // ---- the registry -----------------------------------------------------------
40
+ /** The steps one checklist walks, in `order` order. Each host keeps its own:
41
+ * the plugin's /qcr-setup builds one of the plugin's steps, and the app
42
+ * builds one of those steps plus its own, so a step the app registers never
43
+ * reaches a plain /qcr-setup. */
44
+ export class StepRegistry {
45
+ entries = [];
46
+ constructor(steps = []) {
47
+ for (const step of steps)
48
+ this.register(step);
49
+ }
50
+ /** Add steps. An id that is not tool-name-safe, or one already registered,
51
+ * throws: two steps under one id would share one checklist record. */
52
+ register(...steps) {
53
+ for (const step of steps) {
54
+ if (!STEP_ID_RE.test(step.id))
55
+ throw new Error(`setup step id ${JSON.stringify(step.id)} is not [a-z][a-z0-9_]*`);
56
+ if (this.entries.some((s) => s.id === step.id))
57
+ throw new Error(`setup step "${step.id}" is registered twice`);
58
+ this.entries.push(step);
59
+ }
60
+ return this;
61
+ }
62
+ /** The registered steps in ascending `order` (registration order breaks a
63
+ * tie). */
64
+ steps() {
65
+ return [...this.entries].sort((a, b) => a.order - b.order);
66
+ }
67
+ }
68
+ // ---- checklist file ---------------------------------------------------------
69
+ export const CHECKLIST_VERSION = 1;
70
+ function isPlainObject(v) {
71
+ return typeof v === "object" && v !== null && !Array.isArray(v);
72
+ }
73
+ /** Read a checklist file; null when absent or unreadable (both mean
74
+ * "fresh"). */
75
+ export function readChecklistFile(path) {
76
+ if (!existsSync(path))
77
+ return null;
78
+ try {
79
+ const raw = JSON.parse(readFileSync(path, "utf8"));
80
+ if (typeof raw !== "object" || raw === null)
81
+ return null;
82
+ const file = raw;
83
+ // Every field the engine dereferences is checked here: a file that parses
84
+ // but lacks one (hand-edited, foreign) would otherwise throw out of a
85
+ // step predicate or record() on every call: a wedge, not a fresh start.
86
+ if (typeof file.version !== "number")
87
+ return null;
88
+ if (!isPlainObject(file.choices))
89
+ return null;
90
+ if (file.completed_at !== null && typeof file.completed_at !== "string")
91
+ return null;
92
+ if (!isPlainObject(file.steps))
93
+ return null;
94
+ for (const rec of Object.values(file.steps)) {
95
+ if (!isPlainObject(rec) || !isPlainObject(rec.data))
96
+ return null;
97
+ }
98
+ return file;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ /** Is the checklist complete NOW: finished once, and not reopened since. The
105
+ * one reading of the two fields, for a file on disk and the engine's copy. */
106
+ export function checklistCompleteNow(file) {
107
+ return typeof file.completed_at === "string" && !file.reopened_at;
108
+ }
109
+ /** tmp+rename atomic write: a reader never sees a half-written file, and an
110
+ * interrupted write leaves the previous file whole. The checklist's writer,
111
+ * and the one the app's setup steps write their files through. */
112
+ export function writeAtomic(path, content) {
113
+ mkdirSync(dirname(path), { recursive: true });
114
+ const tmp = join(dirname(path), `.${basename(path)}.onboard.tmp`);
115
+ writeFileSync(tmp, content);
116
+ renameSync(tmp, path);
117
+ }
118
+ /** A file's text, or null when it cannot be read for any reason. For a read
119
+ * where only ABSENCE may be null, read it yourself. */
120
+ export function readTextOrNull(path) {
121
+ try {
122
+ return readFileSync(path, "utf8");
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ export class SetupChecklist {
129
+ adapters;
130
+ registry;
131
+ checklist;
132
+ file;
133
+ constructor(steps, adapters, o) {
134
+ this.adapters = adapters;
135
+ this.registry = (steps instanceof StepRegistry ? steps : new StepRegistry(steps)).steps();
136
+ this.file = o.file;
137
+ const loaded = o.file === null ? null : readChecklistFile(o.file);
138
+ if (loaded === null && o.file !== null && existsSync(o.file)) {
139
+ adapters.log?.("setup checklist: the checklist file is unreadable, starting fresh (recorded choices and step progress were reset)");
140
+ }
141
+ this.checklist = loaded ?? this.fresh();
142
+ }
143
+ fresh() {
144
+ const now = this.adapters.clock().toISOString();
145
+ return {
146
+ version: CHECKLIST_VERSION,
147
+ created_at: now,
148
+ updated_at: now,
149
+ completed_at: null,
150
+ choices: {},
151
+ steps: {},
152
+ };
153
+ }
154
+ save() {
155
+ this.checklist.updated_at = this.adapters.clock().toISOString();
156
+ if (this.file !== null)
157
+ writeAtomic(this.file, `${JSON.stringify(this.checklist, null, 2)}\n`);
158
+ }
159
+ record(id) {
160
+ const existing = this.checklist.steps[id];
161
+ if (existing)
162
+ return existing;
163
+ const created = {
164
+ status: "pending",
165
+ completed_at: null,
166
+ verified_at: null,
167
+ note: null,
168
+ data: {},
169
+ };
170
+ this.checklist.steps[id] = created;
171
+ return created;
172
+ }
173
+ ctxFor(id, input = {}) {
174
+ return {
175
+ adapters: this.adapters,
176
+ input,
177
+ choices: this.checklist.choices,
178
+ data: this.record(id).data,
179
+ recordChoice: (key, value) => {
180
+ this.checklist.choices[key] = value;
181
+ },
182
+ };
183
+ }
184
+ /** The registered steps, in order (a host's tools register off this). */
185
+ stepDefs() {
186
+ return this.registry;
187
+ }
188
+ enabled(step) {
189
+ return step.enabled ? step.enabled(this.checklist.choices) : true;
190
+ }
191
+ choices() {
192
+ return this.checklist.choices;
193
+ }
194
+ isComplete() {
195
+ return checklistCompleteNow(this.checklist);
196
+ }
197
+ /** A completed checklist no longer verifies: withdraw the current-completion
198
+ * claim without touching the history. */
199
+ reopen() {
200
+ if (this.checklist.completed_at === null || this.checklist.reopened_at)
201
+ return;
202
+ this.checklist.reopened_at = this.adapters.clock().toISOString();
203
+ }
204
+ list() {
205
+ return this.registry.map((step) => {
206
+ const rec = this.checklist.steps[step.id];
207
+ return {
208
+ id: step.id,
209
+ title: step.title,
210
+ explain: step.explain,
211
+ status: this.enabled(step) ? (rec?.status ?? "pending") : "disabled",
212
+ note: rec?.note ?? null,
213
+ ...(rec?.found !== undefined ? { found: rec.found } : {}),
214
+ ...(step.optional ? { optional: true } : {}),
215
+ };
216
+ });
217
+ }
218
+ /**
219
+ * Detect: ask every enabled, not-yet-done step that has a detect() whether
220
+ * it is already true. A step that is is marked done with what was found,
221
+ * and never runs; one that is not stays pending with why in its note. A
222
+ * throwing detect is a "missing", never a throw. Persists once at the end.
223
+ */
224
+ async detect() {
225
+ const report = { results: [] };
226
+ for (const step of this.registry) {
227
+ if (!step.detect || !this.enabled(step))
228
+ continue;
229
+ const rec = this.record(step.id);
230
+ if (rec.status === "done")
231
+ continue;
232
+ let found;
233
+ try {
234
+ found = await step.detect(this.ctxFor(step.id));
235
+ }
236
+ catch (err) {
237
+ found = { missing: `detect threw: ${String(err)}` };
238
+ }
239
+ if ("found" in found) {
240
+ const now = this.adapters.clock().toISOString();
241
+ rec.status = "done";
242
+ rec.completed_at = now;
243
+ rec.verified_at = now;
244
+ rec.note = null;
245
+ rec.found = found.found;
246
+ report.results.push({ id: step.id, found: found.found });
247
+ }
248
+ else {
249
+ rec.note = found.missing;
250
+ this.reopen();
251
+ report.results.push({ id: step.id, missing: found.missing });
252
+ }
253
+ }
254
+ this.save();
255
+ return report;
256
+ }
257
+ /**
258
+ * Resume: re-verify every enabled claimed-done step against reality. A step
259
+ * whose verify() fails is demoted to pending with the failures in its
260
+ * note: the file's claim is never trusted alone. A fully-done checklist
261
+ * therefore re-runs as verify-only. Persists once at the end.
262
+ */
263
+ async reconcile() {
264
+ const report = { verified: [], demoted: [] };
265
+ for (const step of this.registry) {
266
+ if (!this.enabled(step))
267
+ continue;
268
+ const rec = this.checklist.steps[step.id];
269
+ if (!rec || rec.status !== "done") {
270
+ this.reopen();
271
+ continue;
272
+ }
273
+ const outcome = await this.safeVerify(step);
274
+ if (outcome.ok) {
275
+ rec.verified_at = this.adapters.clock().toISOString();
276
+ report.verified.push(step.id);
277
+ }
278
+ else {
279
+ const fails = failures(outcome.checks);
280
+ rec.status = "pending";
281
+ rec.note = `re-verification failed: ${fails.join("; ") || "unknown"}`;
282
+ this.reopen();
283
+ report.demoted.push({ id: step.id, failures: fails });
284
+ }
285
+ }
286
+ this.save();
287
+ return report;
288
+ }
289
+ /** Run one step, optionally with the person's answer (`input`, the tool's
290
+ * params). Unknown/disabled ids are refusals-as-answers, never throws (the
291
+ * caller may be an LLM tool belt). Persists after the step. */
292
+ async runStep(id, input = {}) {
293
+ const step = this.registry.find((s) => s.id === id);
294
+ if (!step) {
295
+ return {
296
+ id,
297
+ ok: false,
298
+ checks: [],
299
+ failures: [`unknown step "${id}" — known: ${this.registry.map((s) => s.id).join(", ")}`],
300
+ };
301
+ }
302
+ if (!this.enabled(step)) {
303
+ return {
304
+ id,
305
+ ok: false,
306
+ checks: [],
307
+ failures: [`step "${id}" is disabled by the recorded choices`],
308
+ };
309
+ }
310
+ const rec = this.record(id);
311
+ rec.status = "pending";
312
+ delete rec.found;
313
+ this.reopen();
314
+ this.save();
315
+ let report;
316
+ try {
317
+ report = await step.run(this.ctxFor(id, input));
318
+ }
319
+ catch (err) {
320
+ report = {
321
+ ok: false,
322
+ checks: [{ name: id, ok: false, detail: `step threw: ${String(err)}` }],
323
+ };
324
+ }
325
+ if (report.data)
326
+ rec.data = { ...rec.data, ...report.data };
327
+ if (report.ok) {
328
+ rec.status = "done";
329
+ rec.completed_at = this.adapters.clock().toISOString();
330
+ rec.verified_at = null;
331
+ rec.note = null;
332
+ // A run acts on the installation (choices, config, deploys) after the
333
+ // full verification that backed the completion claim: only the next
334
+ // green finish() may make that claim again.
335
+ this.reopen();
336
+ }
337
+ else {
338
+ rec.status = "pending";
339
+ rec.note = `last run failed: ${failures(report.checks).join("; ") || "unknown"}`;
340
+ this.reopen();
341
+ }
342
+ this.save();
343
+ return { id, ok: report.ok, checks: report.checks, failures: failures(report.checks) };
344
+ }
345
+ async safeVerify(step) {
346
+ try {
347
+ return await step.verify(this.ctxFor(step.id));
348
+ }
349
+ catch (err) {
350
+ return {
351
+ ok: false,
352
+ checks: [{ name: step.id, ok: false, detail: `verify threw: ${String(err)}` }],
353
+ };
354
+ }
355
+ }
356
+ /**
357
+ * Verify every enabled step, done or not. A pending step whose verify()
358
+ * holds is promoted to done (its outcome exists in reality, which is the
359
+ * only truth the checklist mirrors); a done step whose verify() fails
360
+ * reports red here (finish refuses) but demotion is reconcile()'s job.
361
+ */
362
+ async verifyAll() {
363
+ const results = [];
364
+ for (const step of this.registry) {
365
+ if (!this.enabled(step))
366
+ continue;
367
+ const outcome = await this.safeVerify(step);
368
+ const rec = this.record(step.id);
369
+ if (outcome.ok) {
370
+ rec.verified_at = this.adapters.clock().toISOString();
371
+ if (rec.status !== "done") {
372
+ rec.status = "done";
373
+ rec.completed_at = rec.completed_at ?? rec.verified_at;
374
+ rec.note = "verified green without a recorded run";
375
+ }
376
+ }
377
+ results.push({
378
+ id: step.id,
379
+ ok: outcome.ok,
380
+ failures: failures(outcome.checks),
381
+ ...(step.optional ? { optional: true } : {}),
382
+ });
383
+ }
384
+ this.save();
385
+ return { ok: results.every((r) => r.ok || r.optional === true), results };
386
+ }
387
+ /**
388
+ * Re-run the full verification. Anything red ⇒ refuse with the failures.
389
+ * All green ⇒ mark the checklist complete; `completed_at` is set once and
390
+ * never moves. A host that marks its own first finish (the app's first-run
391
+ * marker) does so on top of this.
392
+ */
393
+ async finish() {
394
+ const all = await this.verifyAll();
395
+ if (!all.ok) {
396
+ this.reopen();
397
+ this.save();
398
+ return { ok: false, results: all.results };
399
+ }
400
+ const completedAt = this.checklist.completed_at ?? this.adapters.clock().toISOString();
401
+ this.checklist.completed_at = completedAt;
402
+ this.checklist.reopened_at = null;
403
+ this.save();
404
+ return { ok: true, completed_at: completedAt };
405
+ }
406
+ }
407
+ //# sourceMappingURL=setupchecklist.js.map