@cleocode/core 2026.8.8 → 2026.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.
Files changed (171) hide show
  1. package/dist/conduit/conduit-client.d.ts.map +1 -1
  2. package/dist/conduit/conduit-client.js +10 -2
  3. package/dist/conduit/conduit-client.js.map +1 -1
  4. package/dist/dispatch/mvi-projection.d.ts +38 -0
  5. package/dist/dispatch/mvi-projection.d.ts.map +1 -1
  6. package/dist/dispatch/mvi-projection.js +189 -11
  7. package/dist/dispatch/mvi-projection.js.map +1 -1
  8. package/dist/docs/export-document.js +41 -3
  9. package/dist/docs/export-document.js.map +2 -2
  10. package/dist/doctor/acceptance-drift.d.ts +258 -0
  11. package/dist/doctor/acceptance-drift.d.ts.map +1 -0
  12. package/dist/doctor/acceptance-drift.js +366 -0
  13. package/dist/doctor/acceptance-drift.js.map +1 -0
  14. package/dist/doctor/index.d.ts +4 -0
  15. package/dist/doctor/index.d.ts.map +1 -1
  16. package/dist/doctor/index.js +2 -0
  17. package/dist/doctor/index.js.map +1 -1
  18. package/dist/doctor/legacy-reaper.d.ts +105 -0
  19. package/dist/doctor/legacy-reaper.d.ts.map +1 -0
  20. package/dist/doctor/legacy-reaper.js +151 -0
  21. package/dist/doctor/legacy-reaper.js.map +1 -0
  22. package/dist/doctor/malformed-task-ids.d.ts +111 -0
  23. package/dist/doctor/malformed-task-ids.d.ts.map +1 -0
  24. package/dist/doctor/malformed-task-ids.js +139 -0
  25. package/dist/doctor/malformed-task-ids.js.map +1 -0
  26. package/dist/doctor/nexus-schema-residency.d.ts +115 -0
  27. package/dist/doctor/nexus-schema-residency.d.ts.map +1 -0
  28. package/dist/doctor/nexus-schema-residency.js +178 -0
  29. package/dist/doctor/nexus-schema-residency.js.map +1 -0
  30. package/dist/error-registry.d.ts.map +1 -1
  31. package/dist/error-registry.js +18 -0
  32. package/dist/error-registry.js.map +1 -1
  33. package/dist/internal.d.ts +1 -0
  34. package/dist/internal.d.ts.map +1 -1
  35. package/dist/internal.js +1 -0
  36. package/dist/internal.js.map +1 -1
  37. package/dist/llm/ai-sdk-warnings.d.ts +55 -0
  38. package/dist/llm/ai-sdk-warnings.d.ts.map +1 -0
  39. package/dist/llm/ai-sdk-warnings.js +83 -0
  40. package/dist/llm/ai-sdk-warnings.js.map +1 -0
  41. package/dist/llm/model-runner.d.ts +0 -8
  42. package/dist/llm/model-runner.d.ts.map +1 -1
  43. package/dist/llm/model-runner.js +8 -0
  44. package/dist/llm/model-runner.js.map +1 -1
  45. package/dist/llm/plugin-facade.js +73 -8
  46. package/dist/llm/plugin-facade.js.map +3 -3
  47. package/dist/memory/embedding-queue.d.ts +12 -0
  48. package/dist/memory/embedding-queue.d.ts.map +1 -1
  49. package/dist/memory/embedding-queue.js +39 -5
  50. package/dist/memory/embedding-queue.js.map +1 -1
  51. package/dist/orchestrate/worktree-complete.d.ts.map +1 -1
  52. package/dist/orchestrate/worktree-complete.js +26 -0
  53. package/dist/orchestrate/worktree-complete.js.map +1 -1
  54. package/dist/output.d.ts.map +1 -1
  55. package/dist/output.js +25 -0
  56. package/dist/output.js.map +1 -1
  57. package/dist/release/pr-evidence.d.ts +13 -0
  58. package/dist/release/pr-evidence.d.ts.map +1 -1
  59. package/dist/release/pr-evidence.js +54 -1
  60. package/dist/release/pr-evidence.js.map +1 -1
  61. package/dist/render/session/briefing.d.ts.map +1 -1
  62. package/dist/render/session/briefing.js +9 -0
  63. package/dist/render/session/briefing.js.map +1 -1
  64. package/dist/selfimprove/fix-gen.js +73 -8
  65. package/dist/selfimprove/fix-gen.js.map +3 -3
  66. package/dist/sessions/briefing.d.ts +13 -0
  67. package/dist/sessions/briefing.d.ts.map +1 -1
  68. package/dist/sessions/briefing.js +64 -7
  69. package/dist/sessions/briefing.js.map +1 -1
  70. package/dist/sessions/handoff.d.ts +32 -4
  71. package/dist/sessions/handoff.d.ts.map +1 -1
  72. package/dist/sessions/handoff.js +7 -7
  73. package/dist/sessions/handoff.js.map +1 -1
  74. package/dist/shutdown-deadline.d.ts +144 -0
  75. package/dist/shutdown-deadline.d.ts.map +1 -0
  76. package/dist/shutdown-deadline.js +187 -0
  77. package/dist/shutdown-deadline.js.map +1 -0
  78. package/dist/shutdown.d.ts +5 -2
  79. package/dist/shutdown.d.ts.map +1 -1
  80. package/dist/shutdown.js +19 -14
  81. package/dist/shutdown.js.map +1 -1
  82. package/dist/spawn/branch-lock.d.ts.map +1 -1
  83. package/dist/spawn/branch-lock.js +33 -0
  84. package/dist/spawn/branch-lock.js.map +1 -1
  85. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  86. package/dist/store/sqlite-data-accessor.js +14 -0
  87. package/dist/store/sqlite-data-accessor.js.map +1 -1
  88. package/dist/store/tasks-sqlite.d.ts.map +1 -1
  89. package/dist/store/tasks-sqlite.js +16 -1
  90. package/dist/store/tasks-sqlite.js.map +1 -1
  91. package/dist/tasks/ac-immutability.d.ts.map +1 -1
  92. package/dist/tasks/ac-immutability.js +36 -0
  93. package/dist/tasks/ac-immutability.js.map +1 -1
  94. package/dist/tasks/add.d.ts +18 -0
  95. package/dist/tasks/add.d.ts.map +1 -1
  96. package/dist/tasks/add.js +28 -5
  97. package/dist/tasks/add.js.map +1 -1
  98. package/dist/tasks/axis-filters.d.ts +53 -0
  99. package/dist/tasks/axis-filters.d.ts.map +1 -0
  100. package/dist/tasks/axis-filters.js +77 -0
  101. package/dist/tasks/axis-filters.js.map +1 -0
  102. package/dist/tasks/complete.d.ts.map +1 -1
  103. package/dist/tasks/complete.js +10 -1
  104. package/dist/tasks/complete.js.map +1 -1
  105. package/dist/tasks/duplicate-detector.d.ts +67 -14
  106. package/dist/tasks/duplicate-detector.d.ts.map +1 -1
  107. package/dist/tasks/duplicate-detector.js +197 -54
  108. package/dist/tasks/duplicate-detector.js.map +1 -1
  109. package/dist/tasks/evidence.d.ts +139 -0
  110. package/dist/tasks/evidence.d.ts.map +1 -1
  111. package/dist/tasks/evidence.js +294 -25
  112. package/dist/tasks/evidence.js.map +1 -1
  113. package/dist/tasks/find.d.ts.map +1 -1
  114. package/dist/tasks/find.js +9 -2
  115. package/dist/tasks/find.js.map +1 -1
  116. package/dist/tasks/gate-runner.d.ts.map +1 -1
  117. package/dist/tasks/gate-runner.js +25 -4
  118. package/dist/tasks/gate-runner.js.map +1 -1
  119. package/dist/tasks/heavy-tool-env.d.ts +20 -0
  120. package/dist/tasks/heavy-tool-env.d.ts.map +1 -1
  121. package/dist/tasks/heavy-tool-env.js +74 -4
  122. package/dist/tasks/heavy-tool-env.js.map +1 -1
  123. package/dist/tasks/heavy-tool-limit.d.ts +134 -0
  124. package/dist/tasks/heavy-tool-limit.d.ts.map +1 -0
  125. package/dist/tasks/heavy-tool-limit.js +196 -0
  126. package/dist/tasks/heavy-tool-limit.js.map +1 -0
  127. package/dist/tasks/index.d.ts +1 -1
  128. package/dist/tasks/index.d.ts.map +1 -1
  129. package/dist/tasks/index.js +1 -1
  130. package/dist/tasks/index.js.map +1 -1
  131. package/dist/tasks/infer-add-params.d.ts +28 -0
  132. package/dist/tasks/infer-add-params.d.ts.map +1 -1
  133. package/dist/tasks/infer-add-params.js +96 -6
  134. package/dist/tasks/infer-add-params.js.map +1 -1
  135. package/dist/tasks/list.d.ts +30 -1
  136. package/dist/tasks/list.d.ts.map +1 -1
  137. package/dist/tasks/list.js +14 -0
  138. package/dist/tasks/list.js.map +1 -1
  139. package/dist/tasks/ops.d.ts +4 -0
  140. package/dist/tasks/ops.d.ts.map +1 -1
  141. package/dist/tasks/ops.js +2 -0
  142. package/dist/tasks/ops.js.map +1 -1
  143. package/dist/tasks/session-scope.d.ts +51 -4
  144. package/dist/tasks/session-scope.d.ts.map +1 -1
  145. package/dist/tasks/session-scope.js +68 -10
  146. package/dist/tasks/session-scope.js.map +1 -1
  147. package/dist/tasks/tool-cache.d.ts +116 -9
  148. package/dist/tasks/tool-cache.d.ts.map +1 -1
  149. package/dist/tasks/tool-cache.js +127 -16
  150. package/dist/tasks/tool-cache.js.map +1 -1
  151. package/dist/tasks/tool-semaphore.d.ts +91 -0
  152. package/dist/tasks/tool-semaphore.d.ts.map +1 -1
  153. package/dist/tasks/tool-semaphore.js +203 -14
  154. package/dist/tasks/tool-semaphore.js.map +1 -1
  155. package/dist/validation/doctor/checks.d.ts +64 -0
  156. package/dist/validation/doctor/checks.d.ts.map +1 -1
  157. package/dist/validation/doctor/checks.js +249 -0
  158. package/dist/validation/doctor/checks.js.map +1 -1
  159. package/dist/validation/engine-ops.d.ts.map +1 -1
  160. package/dist/validation/engine-ops.js +41 -5
  161. package/dist/validation/engine-ops.js.map +1 -1
  162. package/package.json +13 -13
  163. package/templates/CLEO-INJECTION.md +28 -44
  164. package/dist/backfill/adr-backfill-walker.d.ts +0 -57
  165. package/dist/backfill/adr-backfill-walker.d.ts.map +0 -1
  166. package/dist/backfill/adr-backfill-walker.js +0 -631
  167. package/dist/backfill/adr-backfill-walker.js.map +0 -1
  168. package/dist/tasks/graph-rag.d.ts +0 -49
  169. package/dist/tasks/graph-rag.d.ts.map +0 -1
  170. package/dist/tasks/graph-rag.js +0 -328
  171. package/dist/tasks/graph-rag.js.map +0 -1
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Detect tasks whose acceptance criteria disagree between their two stores.
3
+ *
4
+ * ## The structural fact (gh#1290)
5
+ *
6
+ * Acceptance criteria live in **two places**, with a projection mechanism
7
+ * between them, and until now nothing asserted they agree:
8
+ *
9
+ * tasks_tasks.acceptance_json the JSON column — what `cleo show` reads
10
+ * tasks_task_acceptance_criteria typed rows, one per criterion, keyed by `kind`
11
+ * tasks_acceptance_projection_dirty/_state the projection mechanism
12
+ *
13
+ * Two representations of one fact, a mechanism to keep them in sync, and no
14
+ * check that it worked.
15
+ *
16
+ * ## The composition rule, measured rather than assumed
17
+ *
18
+ * It is tempting to state the rule as *"the JSON column holds **text** criteria;
19
+ * the rows table holds those **plus** `child_task` projections"* — i.e.
20
+ * `json == text`. That is what a sample of older containers suggests, and it is
21
+ * **wrong for the live convention**.
22
+ *
23
+ * The JSON column does carry child projections, serialised as text. `T001`'s
24
+ * only JSON entry is the string `"Complete child T9092: …"`, and 476 tasks
25
+ * carry at least one such string. Of 582 tasks with `child_task` rows, 446 have
26
+ * `json == text + child`.
27
+ *
28
+ * Measured across a 5,067-task store, the convention **changed during May 2026**
29
+ * and has been uniform since:
30
+ *
31
+ * month children IN json children NOT in json
32
+ * 2026-04 14 66
33
+ * 2026-05 363 38
34
+ * 2026-06+ 68 0
35
+ *
36
+ * tasks created >= 2026-06-01: 608, violating `json == text + child`: 1
37
+ * tasks created < 2026-06-01: 4459, violating `json == text + child`: 163
38
+ *
39
+ * So the live rule is `json == text + child`, honoured in 607 of 608 tasks
40
+ * created since the change. The pre-change rows are a legacy convention, not
41
+ * drift, and are reported separately so that a real regression is not buried
42
+ * under them.
43
+ *
44
+ * ## What this must NOT be built on
45
+ *
46
+ * `tasks_acceptance_projection_state` is the obvious place to ask "is the
47
+ * projection fresh?", and it lies. Measured 2026-09-12:
48
+ *
49
+ * projection_key status last_projected_at dirty rows
50
+ * task_acceptance fresh 2026-05-26 08:48:55 0
51
+ *
52
+ * The projection had not run in three and a half months, reported itself
53
+ * `fresh`, and had an empty dirty queue — while acceptance rows continued to be
54
+ * written the whole time by a different, inline path. A freshness marker that is
55
+ * *written* rather than *derived* is a claim, not a measurement. This scan
56
+ * therefore derives the answer from the data every time and never reads that
57
+ * table.
58
+ *
59
+ * ## This is an OPERATOR command, NOT a CI gate — deliberately
60
+ *
61
+ * The baseline is per-install and lives at `.cleo/acceptance-drift-baseline.json`,
62
+ * which `.cleo/.gitignore` excludes by design (line 25 is a blanket `*` with an
63
+ * explicit allow list, and this file is correctly not on it — see
64
+ * {@link ACCEPTANCE_DRIFT_BASELINE_FILE} for why it must not be tracked).
65
+ *
66
+ * So **in CI, or in any fresh clone, no baseline exists, every entry reports
67
+ * unbaselined, and this command exits non-zero.** That is not a bug to route
68
+ * around; it follows from the baseline being about rows in one store rather
69
+ * than about source files. Every other baselined check in this repo IS a CI
70
+ * gate whose baseline is tracked, so the next reader who sees
71
+ * `--update-baseline` and a `.cleo/*-baseline.json` will reasonably
72
+ * pattern-match this into `cleo check arch`. Do not.
73
+ *
74
+ * What CI *can* assert is the invariant that needs no per-install state: **no
75
+ * task created since the convention settled violates `json == text + child`.**
76
+ * That is a single query over the same data and it is clean in a fresh clone.
77
+ *
78
+ * ## Scope: cardinality, not content
79
+ *
80
+ * This compares COUNTS — how many criteria each store holds. It cannot see a
81
+ * task whose JSON has the right number of entries but the wrong text, which is
82
+ * what a renamed child produces, since JSON entries are literal
83
+ * `"Complete child T9092: <title>"` strings.
84
+ *
85
+ * That gap was measured rather than assumed. Comparing the sorted multiset of
86
+ * JSON strings against the row texts, restricted to tasks whose counts already
87
+ * agree: **601 current-era tasks, 0 content differences; 4,053 legacy tasks, 1**
88
+ * — `T11011`. So count identity is an excellent proxy in this store, and
89
+ * content comparison would earn one legacy row. Named as out of scope rather
90
+ * than left for a reader to discover.
91
+ *
92
+ * @task T12157
93
+ * @see ADR-088 — PM-Core V2 containers
94
+ * @see ADR-092 — failure geometries; "a status surface that reports a state it
95
+ * does not measure"
96
+ */
97
+ /**
98
+ * Where a project records the drift it has decided to live with.
99
+ *
100
+ * Per-INSTALL, not per-repo. The entries are task ids from this project's own
101
+ * store, so a baseline committed to the cleo repository would bake cleocode's
102
+ * task ids into a CLI that ships to everyone else.
103
+ */
104
+ export declare const ACCEPTANCE_DRIFT_BASELINE_FILE = "acceptance-drift-baseline.json";
105
+ /**
106
+ * The date from which `json == text + child` has been honoured uniformly.
107
+ *
108
+ * Not a guess: containers created in 2026-04 omitted child projections from the
109
+ * JSON column 66 times against 14 that included them; 2026-05 is the crossover
110
+ * (363 vs 38); from 2026-06 onward it is 68 vs 0. Across ALL tasks created on or
111
+ * after this date, exactly **one** violates the rule out of 608.
112
+ *
113
+ * Rows older than this are reported as `legacy` so a reader can see WHY an entry
114
+ * is in the baseline. It is a REPORTING attribute only and does not decide what
115
+ * fails — see {@link AcceptanceDriftScanResult.unbaselined} for why.
116
+ */
117
+ export declare const ACCEPTANCE_CONVENTION_SETTLED_AT = "2026-06-01";
118
+ /**
119
+ * How a task's two acceptance stores disagree, once the container/projection
120
+ * composition rule has been applied.
121
+ */
122
+ export type AcceptanceDriftKind =
123
+ /** JSON holds criteria; the rows table has none at all. */
124
+ 'json-never-projected'
125
+ /** Rows exist while the JSON column is empty — `cleo show` reads the JSON, so
126
+ * a read cannot see criteria that exist. The only data-losing shape. */
127
+ | 'rows-unreadable'
128
+ /** JSON holds exactly the TEXT criteria and omits the child projections —
129
+ * the pre-2026-06 convention, expected on older rows, a regression on new
130
+ * ones. */
131
+ | 'legacy-children-omitted'
132
+ /** Both populated and disagreeing by something other than the child
133
+ * projections. */
134
+ | 'count-mismatch';
135
+ /** One task whose acceptance stores disagree. */
136
+ export interface AcceptanceDriftEntry {
137
+ /** Task id, e.g. `T11176`. */
138
+ readonly taskId: string;
139
+ /** `epic` | `task` | `subtask` | `saga`. */
140
+ readonly type: string;
141
+ /** How the two stores disagree. */
142
+ readonly kind: AcceptanceDriftKind;
143
+ /** Number of entries in `acceptance_json`. */
144
+ readonly jsonCount: number;
145
+ /** Rows in `tasks_task_acceptance_criteria` with `kind='text'`. */
146
+ readonly textRowCount: number;
147
+ /** Rows with `kind='child_task'` — container projections, not text. */
148
+ readonly childRowCount: number;
149
+ /**
150
+ * Whether `created_at` carries the application's ISO-8601 `T` separator.
151
+ *
152
+ * `false` means the value came from SQLite's `CURRENT_TIMESTAMP` column
153
+ * default — the fingerprint of a write that bypassed the application layer.
154
+ * Measured across a 5,067-task store: the application path had **0 failures
155
+ * in 4,771 writes**, the bypass path 24 in 26. So this field usually answers
156
+ * "why" without further investigation.
157
+ */
158
+ readonly appWritten: boolean;
159
+ /** The task's `created_at`, verbatim. */
160
+ readonly createdAt: string | null;
161
+ /**
162
+ * `true` when the task predates {@link ACCEPTANCE_CONVENTION_SETTLED_AT}, so
163
+ * its shape reflects the older convention rather than a regression.
164
+ */
165
+ readonly legacyEra: boolean;
166
+ }
167
+ /** The result of a whole-store acceptance consistency scan. */
168
+ export interface AcceptanceDriftScanResult {
169
+ /** Absolute path of the store that was scanned. */
170
+ readonly storePath: string;
171
+ /** `false` when there is no `.cleo/cleo.db` to scan; everything else is 0. */
172
+ readonly storeExists: boolean;
173
+ /** Tasks examined. */
174
+ readonly tasksScanned: number;
175
+ /**
176
+ * Tasks that disagree on a RAW count comparison, before the composition rule.
177
+ * Reported only so the difference from `entries.length` is visible — a reader
178
+ * who has run the raw query themselves should be able to see why this number
179
+ * is larger.
180
+ */
181
+ readonly rawDisagreements: number;
182
+ /** The genuine residue, after applying the container/projection rule. */
183
+ readonly entries: readonly AcceptanceDriftEntry[];
184
+ /** One line per drift kind, for a summary line. */
185
+ readonly byKind: Readonly<Record<AcceptanceDriftKind, number>>;
186
+ /** Entries created on or after {@link ACCEPTANCE_CONVENTION_SETTLED_AT}. */
187
+ readonly currentEraDrift: number;
188
+ /**
189
+ * Entries whose task id is NOT in the baseline — the number that gates.
190
+ *
191
+ * Creation date deliberately does not appear in this calculation. Acceptance
192
+ * rows are written throughout a task's life (reparenting, child completion,
193
+ * an edited criterion), so drift is introduced by a WRITE while a birthday is
194
+ * fixed forever. Keying the gate on creation date exempts every old task from
195
+ * every future regression: measured on this store, **223 tasks created before
196
+ * the convention settled have been updated since**, and a date-keyed gate is
197
+ * blind to new drift across 4,459 of 5,068 rows — 88% of the store.
198
+ *
199
+ * A baseline of known ids fixes that. It is wrong about the past on purpose
200
+ * so it can be right about the future: the 163 historical rows stay quiet,
201
+ * and a pre-June task that drifts tomorrow is a net-add and fails.
202
+ */
203
+ readonly unbaselined: readonly AcceptanceDriftEntry[];
204
+ /** Baselined ids that no longer drift — safe to prune. */
205
+ readonly staleBaselineIds: readonly string[];
206
+ /** Absolute path of the baseline file, whether or not it exists. */
207
+ readonly baselinePath: string;
208
+ }
209
+ /**
210
+ * Read the per-install baseline of task ids whose drift is accepted.
211
+ *
212
+ * A missing or unreadable file yields an empty set, so a first run reports
213
+ * everything rather than silently passing. Failing open here would reproduce
214
+ * the defect this module exists to catch.
215
+ */
216
+ export declare function readAcceptanceDriftBaseline(baselinePath: string): ReadonlySet<string>;
217
+ /**
218
+ * Write the baseline from a scan, accepting every current entry.
219
+ *
220
+ * @param scan - a completed scan; its entries become the accepted set.
221
+ * @returns the number of ids recorded.
222
+ */
223
+ export declare function writeAcceptanceDriftBaseline(scan: AcceptanceDriftScanResult): number;
224
+ /**
225
+ * Classify one task, or `null` when its two stores are consistent.
226
+ *
227
+ * Exported for testing: the composition rule is the load-bearing part of this
228
+ * module and deserves direct coverage rather than coverage through a DB.
229
+ *
230
+ * @param row - one task's JSON count and per-kind row counts.
231
+ * @returns the drift kind, or `null` when the task is consistent.
232
+ */
233
+ export declare function classifyAcceptanceDrift(row: {
234
+ jsonCount: number;
235
+ textRowCount: number;
236
+ childRowCount: number;
237
+ }): AcceptanceDriftKind | null;
238
+ /**
239
+ * Scan a project's store for acceptance drift.
240
+ *
241
+ * Read-only: opens a snapshot handle through {@link openCleoDbSnapshot} rather
242
+ * than the live chokepoint, so a survey never registers a writable handle or
243
+ * competes for the WAL.
244
+ *
245
+ * @param projectRoot - absolute path to the project root.
246
+ * @returns the survey; `entries` is empty for a consistent store.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * const scan = scanAcceptanceDrift('/mnt/projects/cleocode');
251
+ * console.log(scan.entries.length, 'of', scan.rawDisagreements, 'raw');
252
+ * // 58 of 164 raw
253
+ * ```
254
+ *
255
+ * @task T12157
256
+ */
257
+ export declare function scanAcceptanceDrift(projectRoot: string): AcceptanceDriftScanResult;
258
+ //# sourceMappingURL=acceptance-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acceptance-drift.d.ts","sourceRoot":"","sources":["../../src/doctor/acceptance-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AAUH;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,mCAAmC,CAAC;AAE/E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gCAAgC,eAAe,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,mBAAmB;AAC7B,2DAA2D;AACzD,sBAAsB;AACxB;yEACyE;GACvE,iBAAiB;AACnB;;YAEY;GACV,yBAAyB;AAC3B;mBACmB;GACjB,gBAAgB,CAAC;AAErB,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,sBAAsB;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAClD,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CASrF;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,yBAAyB,GAAG,MAAM,CAmBpF;AA+ED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,mBAAmB,GAAG,IAAI,CAuB7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,yBAAyB,CAqGlF"}
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Detect tasks whose acceptance criteria disagree between their two stores.
3
+ *
4
+ * ## The structural fact (gh#1290)
5
+ *
6
+ * Acceptance criteria live in **two places**, with a projection mechanism
7
+ * between them, and until now nothing asserted they agree:
8
+ *
9
+ * tasks_tasks.acceptance_json the JSON column — what `cleo show` reads
10
+ * tasks_task_acceptance_criteria typed rows, one per criterion, keyed by `kind`
11
+ * tasks_acceptance_projection_dirty/_state the projection mechanism
12
+ *
13
+ * Two representations of one fact, a mechanism to keep them in sync, and no
14
+ * check that it worked.
15
+ *
16
+ * ## The composition rule, measured rather than assumed
17
+ *
18
+ * It is tempting to state the rule as *"the JSON column holds **text** criteria;
19
+ * the rows table holds those **plus** `child_task` projections"* — i.e.
20
+ * `json == text`. That is what a sample of older containers suggests, and it is
21
+ * **wrong for the live convention**.
22
+ *
23
+ * The JSON column does carry child projections, serialised as text. `T001`'s
24
+ * only JSON entry is the string `"Complete child T9092: …"`, and 476 tasks
25
+ * carry at least one such string. Of 582 tasks with `child_task` rows, 446 have
26
+ * `json == text + child`.
27
+ *
28
+ * Measured across a 5,067-task store, the convention **changed during May 2026**
29
+ * and has been uniform since:
30
+ *
31
+ * month children IN json children NOT in json
32
+ * 2026-04 14 66
33
+ * 2026-05 363 38
34
+ * 2026-06+ 68 0
35
+ *
36
+ * tasks created >= 2026-06-01: 608, violating `json == text + child`: 1
37
+ * tasks created < 2026-06-01: 4459, violating `json == text + child`: 163
38
+ *
39
+ * So the live rule is `json == text + child`, honoured in 607 of 608 tasks
40
+ * created since the change. The pre-change rows are a legacy convention, not
41
+ * drift, and are reported separately so that a real regression is not buried
42
+ * under them.
43
+ *
44
+ * ## What this must NOT be built on
45
+ *
46
+ * `tasks_acceptance_projection_state` is the obvious place to ask "is the
47
+ * projection fresh?", and it lies. Measured 2026-09-12:
48
+ *
49
+ * projection_key status last_projected_at dirty rows
50
+ * task_acceptance fresh 2026-05-26 08:48:55 0
51
+ *
52
+ * The projection had not run in three and a half months, reported itself
53
+ * `fresh`, and had an empty dirty queue — while acceptance rows continued to be
54
+ * written the whole time by a different, inline path. A freshness marker that is
55
+ * *written* rather than *derived* is a claim, not a measurement. This scan
56
+ * therefore derives the answer from the data every time and never reads that
57
+ * table.
58
+ *
59
+ * ## This is an OPERATOR command, NOT a CI gate — deliberately
60
+ *
61
+ * The baseline is per-install and lives at `.cleo/acceptance-drift-baseline.json`,
62
+ * which `.cleo/.gitignore` excludes by design (line 25 is a blanket `*` with an
63
+ * explicit allow list, and this file is correctly not on it — see
64
+ * {@link ACCEPTANCE_DRIFT_BASELINE_FILE} for why it must not be tracked).
65
+ *
66
+ * So **in CI, or in any fresh clone, no baseline exists, every entry reports
67
+ * unbaselined, and this command exits non-zero.** That is not a bug to route
68
+ * around; it follows from the baseline being about rows in one store rather
69
+ * than about source files. Every other baselined check in this repo IS a CI
70
+ * gate whose baseline is tracked, so the next reader who sees
71
+ * `--update-baseline` and a `.cleo/*-baseline.json` will reasonably
72
+ * pattern-match this into `cleo check arch`. Do not.
73
+ *
74
+ * What CI *can* assert is the invariant that needs no per-install state: **no
75
+ * task created since the convention settled violates `json == text + child`.**
76
+ * That is a single query over the same data and it is clean in a fresh clone.
77
+ *
78
+ * ## Scope: cardinality, not content
79
+ *
80
+ * This compares COUNTS — how many criteria each store holds. It cannot see a
81
+ * task whose JSON has the right number of entries but the wrong text, which is
82
+ * what a renamed child produces, since JSON entries are literal
83
+ * `"Complete child T9092: <title>"` strings.
84
+ *
85
+ * That gap was measured rather than assumed. Comparing the sorted multiset of
86
+ * JSON strings against the row texts, restricted to tasks whose counts already
87
+ * agree: **601 current-era tasks, 0 content differences; 4,053 legacy tasks, 1**
88
+ * — `T11011`. So count identity is an excellent proxy in this store, and
89
+ * content comparison would earn one legacy row. Named as out of scope rather
90
+ * than left for a reader to discover.
91
+ *
92
+ * @task T12157
93
+ * @see ADR-088 — PM-Core V2 containers
94
+ * @see ADR-092 — failure geometries; "a status surface that reports a state it
95
+ * does not measure"
96
+ */
97
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
98
+ import { join } from 'node:path';
99
+ import { openCleoDbSnapshot } from '../store/open-cleo-db.js';
100
+ /** The dual-scope store filename (ADR-068). */
101
+ const LIVE_STORE_FILENAME = 'cleo.db';
102
+ /**
103
+ * Where a project records the drift it has decided to live with.
104
+ *
105
+ * Per-INSTALL, not per-repo. The entries are task ids from this project's own
106
+ * store, so a baseline committed to the cleo repository would bake cleocode's
107
+ * task ids into a CLI that ships to everyone else.
108
+ */
109
+ export const ACCEPTANCE_DRIFT_BASELINE_FILE = 'acceptance-drift-baseline.json';
110
+ /**
111
+ * The date from which `json == text + child` has been honoured uniformly.
112
+ *
113
+ * Not a guess: containers created in 2026-04 omitted child projections from the
114
+ * JSON column 66 times against 14 that included them; 2026-05 is the crossover
115
+ * (363 vs 38); from 2026-06 onward it is 68 vs 0. Across ALL tasks created on or
116
+ * after this date, exactly **one** violates the rule out of 608.
117
+ *
118
+ * Rows older than this are reported as `legacy` so a reader can see WHY an entry
119
+ * is in the baseline. It is a REPORTING attribute only and does not decide what
120
+ * fails — see {@link AcceptanceDriftScanResult.unbaselined} for why.
121
+ */
122
+ export const ACCEPTANCE_CONVENTION_SETTLED_AT = '2026-06-01';
123
+ /**
124
+ * Read the per-install baseline of task ids whose drift is accepted.
125
+ *
126
+ * A missing or unreadable file yields an empty set, so a first run reports
127
+ * everything rather than silently passing. Failing open here would reproduce
128
+ * the defect this module exists to catch.
129
+ */
130
+ export function readAcceptanceDriftBaseline(baselinePath) {
131
+ if (!existsSync(baselinePath))
132
+ return new Set();
133
+ try {
134
+ const parsed = JSON.parse(readFileSync(baselinePath, 'utf-8'));
135
+ if (!Array.isArray(parsed.taskIds))
136
+ return new Set();
137
+ return new Set(parsed.taskIds.filter((id) => typeof id === 'string'));
138
+ }
139
+ catch {
140
+ return new Set();
141
+ }
142
+ }
143
+ /**
144
+ * Write the baseline from a scan, accepting every current entry.
145
+ *
146
+ * @param scan - a completed scan; its entries become the accepted set.
147
+ * @returns the number of ids recorded.
148
+ */
149
+ export function writeAcceptanceDriftBaseline(scan) {
150
+ const taskIds = [...scan.entries].map((e) => e.taskId).sort();
151
+ writeFileSync(scan.baselinePath, `${JSON.stringify({
152
+ generatedAt: new Date().toISOString(),
153
+ note: 'Acceptance drift accepted for these task ids (gh#1290). Any OTHER drifting task fails ' +
154
+ '`cleo doctor acceptance-drift`, regardless of when it was created. Regenerate with ' +
155
+ '`cleo doctor acceptance-drift --update-baseline`.',
156
+ taskIds,
157
+ }, null, 2)}\n`, 'utf-8');
158
+ return taskIds.length;
159
+ }
160
+ /** Narrow a column to `string`, or `null` when it is absent or another type. */
161
+ function columnAsStringOrNull(value) {
162
+ return typeof value === 'string' ? value : null;
163
+ }
164
+ /**
165
+ * Narrow a numeric column, tolerating the `bigint` SQLite may return.
166
+ *
167
+ * `COUNT`/`SUM` come back as `number` for small results and `bigint` for large
168
+ * ones, and `json_array_length` is an integer. Anything else is a column that
169
+ * is not what this module thinks it is, which {@link toScanRow} rejects.
170
+ */
171
+ function columnAsCount(value) {
172
+ if (typeof value === 'number')
173
+ return value;
174
+ if (typeof value === 'bigint')
175
+ return Number(value);
176
+ return null;
177
+ }
178
+ /**
179
+ * Convert one driver row into a {@link ScanRow}.
180
+ *
181
+ * Explicit rather than a cast. `node:sqlite` hands back
182
+ * `Record<string, SQLOutputValue>`, and asserting that into a shaped interface
183
+ * would compile while silently tolerating a column set that no longer matches —
184
+ * in a module whose entire purpose is detecting exactly that kind of drift.
185
+ *
186
+ * @throws when a column is missing or has an unexpected type, so a query that
187
+ * drifts from this shape fails loudly at the first row instead of producing
188
+ * a scan full of zeroes that reads like a clean store.
189
+ */
190
+ function toScanRow(record) {
191
+ const id = columnAsStringOrNull(record['id']);
192
+ const jsonN = columnAsCount(record['json_n']);
193
+ const textN = columnAsCount(record['text_n']);
194
+ const childN = columnAsCount(record['child_n']);
195
+ // Name the offending column. An error that says only "the shape is wrong"
196
+ // sends the reader to the SQL when the problem is one value in one row —
197
+ // the failure describing the wrong thing with full confidence (ADR-092).
198
+ const bad = [
199
+ ['id', id],
200
+ ['json_n', jsonN],
201
+ ['text_n', textN],
202
+ ['child_n', childN],
203
+ ].find(([, value]) => value === null)?.[0];
204
+ if (bad !== undefined || id === null || jsonN === null || textN === null || childN === null) {
205
+ throw new Error(`acceptance-drift: scan column "${String(bad)}" had an unexpected type ` +
206
+ `(got ${typeof record[String(bad)]}) for task ${String(record['id'] ?? '<unknown>')}. ` +
207
+ 'The SQL and ScanRow have drifted apart.');
208
+ }
209
+ return {
210
+ id,
211
+ // `type` is NULLABLE in practice: 174 of 5,068 rows in this store carry no
212
+ // type at all. It is a reporting field here, not an input to the drift
213
+ // classification, so a missing one must not abort a whole-store survey.
214
+ type: columnAsStringOrNull(record['type']) ?? 'unknown',
215
+ created_at: columnAsStringOrNull(record['created_at']),
216
+ json_n: jsonN,
217
+ text_n: textN,
218
+ child_n: childN,
219
+ };
220
+ }
221
+ /**
222
+ * Classify one task, or `null` when its two stores are consistent.
223
+ *
224
+ * Exported for testing: the composition rule is the load-bearing part of this
225
+ * module and deserves direct coverage rather than coverage through a DB.
226
+ *
227
+ * @param row - one task's JSON count and per-kind row counts.
228
+ * @returns the drift kind, or `null` when the task is consistent.
229
+ */
230
+ export function classifyAcceptanceDrift(row) {
231
+ const { jsonCount, textRowCount, childRowCount } = row;
232
+ const expected = textRowCount + childRowCount;
233
+ // Nothing recorded anywhere — consistent.
234
+ if (jsonCount === 0 && expected === 0)
235
+ return null;
236
+ // The live convention: the JSON column carries every criterion, text and
237
+ // child projection alike. Measured at 607/608 for tasks created since the
238
+ // convention settled in 2026-06.
239
+ if (jsonCount === expected)
240
+ return null;
241
+ if (jsonCount > 0 && expected === 0)
242
+ return 'json-never-projected';
243
+ // Rows exist and the JSON column is empty. This is the only shape where the
244
+ // read path loses criteria that exist, because `cleo show` reads the JSON.
245
+ if (jsonCount === 0)
246
+ return 'rows-unreadable';
247
+ // Both populated and disagreeing. When the shortfall is exactly the child
248
+ // projections, this is the pre-2026-06 convention rather than a regression —
249
+ // the caller separates the two by task age, which is a property of the row
250
+ // and not of the counts.
251
+ return jsonCount === textRowCount ? 'legacy-children-omitted' : 'count-mismatch';
252
+ }
253
+ /**
254
+ * Scan a project's store for acceptance drift.
255
+ *
256
+ * Read-only: opens a snapshot handle through {@link openCleoDbSnapshot} rather
257
+ * than the live chokepoint, so a survey never registers a writable handle or
258
+ * competes for the WAL.
259
+ *
260
+ * @param projectRoot - absolute path to the project root.
261
+ * @returns the survey; `entries` is empty for a consistent store.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * const scan = scanAcceptanceDrift('/mnt/projects/cleocode');
266
+ * console.log(scan.entries.length, 'of', scan.rawDisagreements, 'raw');
267
+ * // 58 of 164 raw
268
+ * ```
269
+ *
270
+ * @task T12157
271
+ */
272
+ export function scanAcceptanceDrift(projectRoot) {
273
+ const storePath = join(projectRoot, '.cleo', LIVE_STORE_FILENAME);
274
+ const baselinePath = join(projectRoot, '.cleo', ACCEPTANCE_DRIFT_BASELINE_FILE);
275
+ const baselined = readAcceptanceDriftBaseline(baselinePath);
276
+ const empty = {
277
+ 'json-never-projected': 0,
278
+ 'rows-unreadable': 0,
279
+ 'legacy-children-omitted': 0,
280
+ 'count-mismatch': 0,
281
+ };
282
+ if (!existsSync(storePath)) {
283
+ return {
284
+ storePath,
285
+ storeExists: false,
286
+ tasksScanned: 0,
287
+ rawDisagreements: 0,
288
+ entries: [],
289
+ byKind: empty,
290
+ currentEraDrift: 0,
291
+ unbaselined: [],
292
+ staleBaselineIds: [],
293
+ baselinePath,
294
+ };
295
+ }
296
+ const snapshot = openCleoDbSnapshot(storePath);
297
+ try {
298
+ // `main.` is explicit: both tables also exist in the GLOBAL cleo.db, which
299
+ // nexus ATTACHes onto project handles as `nexus_global`. A bare name would
300
+ // resolve by SQLite search order and could answer from the wrong file
301
+ // (gh#1283). A snapshot handle has one schema, but the qualifier is what
302
+ // keeps that true if this query is ever moved.
303
+ const rows = snapshot.db
304
+ .prepare(`SELECT t.id AS id,
305
+ t.type AS type,
306
+ t.created_at AS created_at,
307
+ CASE WHEN t.acceptance_json IS NULL
308
+ OR t.acceptance_json IN ('', '[]', 'null')
309
+ THEN 0
310
+ ELSE json_array_length(t.acceptance_json) END AS json_n,
311
+ COALESCE(SUM(CASE WHEN c.kind = 'text' THEN 1 ELSE 0 END), 0) AS text_n,
312
+ COALESCE(SUM(CASE WHEN c.kind = 'child_task' THEN 1 ELSE 0 END), 0) AS child_n
313
+ FROM main.tasks_tasks t
314
+ LEFT JOIN main.tasks_task_acceptance_criteria c ON c.task_id = t.id
315
+ GROUP BY t.id`)
316
+ .all()
317
+ .map(toScanRow);
318
+ const entries = [];
319
+ const byKind = { ...empty };
320
+ let rawDisagreements = 0;
321
+ for (const row of rows) {
322
+ const jsonCount = row.json_n;
323
+ const textRowCount = row.text_n;
324
+ const childRowCount = row.child_n;
325
+ const totalRows = textRowCount + childRowCount;
326
+ if (jsonCount !== totalRows)
327
+ rawDisagreements += 1;
328
+ const kind = classifyAcceptanceDrift({ jsonCount, textRowCount, childRowCount });
329
+ if (kind === null)
330
+ continue;
331
+ byKind[kind] += 1;
332
+ entries.push({
333
+ taskId: row.id,
334
+ type: row.type,
335
+ kind,
336
+ jsonCount,
337
+ textRowCount,
338
+ childRowCount,
339
+ appWritten: (row.created_at ?? '').includes('T'),
340
+ createdAt: row.created_at,
341
+ // Fail CLOSED on a missing date: an unknown creation date must not
342
+ // buy an exemption. Measured 0 null/empty in 5,068 rows, so this
343
+ // guards a direction rather than a known victim.
344
+ legacyEra: row.created_at !== null &&
345
+ row.created_at !== '' &&
346
+ row.created_at < ACCEPTANCE_CONVENTION_SETTLED_AT,
347
+ });
348
+ }
349
+ return {
350
+ storePath,
351
+ storeExists: true,
352
+ tasksScanned: rows.length,
353
+ rawDisagreements,
354
+ entries,
355
+ byKind,
356
+ currentEraDrift: entries.filter((e) => !e.legacyEra).length,
357
+ unbaselined: entries.filter((e) => !baselined.has(e.taskId)),
358
+ staleBaselineIds: [...baselined].filter((id) => !entries.some((e) => e.taskId === id)),
359
+ baselinePath,
360
+ };
361
+ }
362
+ finally {
363
+ snapshot.close();
364
+ }
365
+ }
366
+ //# sourceMappingURL=acceptance-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acceptance-drift.js","sourceRoot":"","sources":["../../src/doctor/acceptance-drift.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,10 @@ export { auditInvariantRegistry } from './invariant-audit.js';
22
22
  export { renderInvariantAuditLines } from './invariant-audit-render.js';
23
23
  export type { LegacyBackupPruneOptions, LegacyBackupScanOptions } from './legacy-backups.js';
24
24
  export { classifyLegacyBackup, DEFAULT_HARD_RETENTION_DAYS, DEFAULT_SOFT_RETENTION_DAYS, isLegacyBackupFilename, legacyBackupSearchRoots, pruneLegacyBackups, recommendForBackup, scanLegacyBackups, } from './legacy-backups.js';
25
+ export type { LegacyReaperReport, LegacyUnitState } from './legacy-reaper.js';
26
+ export { buildRecommendation, isUnitDangerous, scanLegacyReaper } from './legacy-reaper.js';
27
+ export type { MalformedTaskIdReport, MalformedTaskRow } from './malformed-task-ids.js';
28
+ export { scanMalformedTaskIds } from './malformed-task-ids.js';
25
29
  export type { PragmaSsot, PragmaSsotEntry } from './pragma-ssot.js';
26
30
  export { loadPragmaSsot, normalisePragmaValue, PRAGMA_VALUE_NORMALISERS, } from './pragma-ssot.js';
27
31
  export { auditSagaHierarchy } from './saga-audit.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAExE,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EACL,iCAAiC,EACjC,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAExE,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EACL,iCAAiC,EACjC,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC"}