@deskwork/core 0.9.5

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 (188) hide show
  1. package/dist/body-state.d.ts +27 -0
  2. package/dist/body-state.d.ts.map +1 -0
  3. package/dist/body-state.js +62 -0
  4. package/dist/body-state.js.map +1 -0
  5. package/dist/calendar-mutations.d.ts +124 -0
  6. package/dist/calendar-mutations.d.ts.map +1 -0
  7. package/dist/calendar-mutations.js +305 -0
  8. package/dist/calendar-mutations.js.map +1 -0
  9. package/dist/calendar.d.ts +54 -0
  10. package/dist/calendar.d.ts.map +1 -0
  11. package/dist/calendar.js +430 -0
  12. package/dist/calendar.js.map +1 -0
  13. package/dist/cli.d.ts +38 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +72 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/config.d.ts +91 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +216 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/content-index.d.ts +74 -0
  22. package/dist/content-index.d.ts.map +1 -0
  23. package/dist/content-index.js +205 -0
  24. package/dist/content-index.js.map +1 -0
  25. package/dist/content-tree-fs-walk.d.ts +54 -0
  26. package/dist/content-tree-fs-walk.d.ts.map +1 -0
  27. package/dist/content-tree-fs-walk.js +112 -0
  28. package/dist/content-tree-fs-walk.js.map +1 -0
  29. package/dist/content-tree-helpers.d.ts +52 -0
  30. package/dist/content-tree-helpers.d.ts.map +1 -0
  31. package/dist/content-tree-helpers.js +116 -0
  32. package/dist/content-tree-helpers.js.map +1 -0
  33. package/dist/content-tree-types.d.ts +175 -0
  34. package/dist/content-tree-types.d.ts.map +1 -0
  35. package/dist/content-tree-types.js +10 -0
  36. package/dist/content-tree-types.js.map +1 -0
  37. package/dist/content-tree.d.ts +93 -0
  38. package/dist/content-tree.d.ts.map +1 -0
  39. package/dist/content-tree.js +385 -0
  40. package/dist/content-tree.js.map +1 -0
  41. package/dist/doctor/index.d.ts +11 -0
  42. package/dist/doctor/index.d.ts.map +1 -0
  43. package/dist/doctor/index.js +10 -0
  44. package/dist/doctor/index.js.map +1 -0
  45. package/dist/doctor/project-rules.d.ts +59 -0
  46. package/dist/doctor/project-rules.d.ts.map +1 -0
  47. package/dist/doctor/project-rules.js +143 -0
  48. package/dist/doctor/project-rules.js.map +1 -0
  49. package/dist/doctor/rules/calendar-uuid-missing.d.ts +19 -0
  50. package/dist/doctor/rules/calendar-uuid-missing.d.ts.map +1 -0
  51. package/dist/doctor/rules/calendar-uuid-missing.js +176 -0
  52. package/dist/doctor/rules/calendar-uuid-missing.js.map +1 -0
  53. package/dist/doctor/rules/duplicate-id.d.ts +27 -0
  54. package/dist/doctor/rules/duplicate-id.d.ts.map +1 -0
  55. package/dist/doctor/rules/duplicate-id.js +157 -0
  56. package/dist/doctor/rules/duplicate-id.js.map +1 -0
  57. package/dist/doctor/rules/legacy-top-level-id-migration.d.ts +40 -0
  58. package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -0
  59. package/dist/doctor/rules/legacy-top-level-id-migration.js +232 -0
  60. package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -0
  61. package/dist/doctor/rules/missing-frontmatter-id.d.ts +45 -0
  62. package/dist/doctor/rules/missing-frontmatter-id.d.ts.map +1 -0
  63. package/dist/doctor/rules/missing-frontmatter-id.js +283 -0
  64. package/dist/doctor/rules/missing-frontmatter-id.js.map +1 -0
  65. package/dist/doctor/rules/orphan-frontmatter-id.d.ts +18 -0
  66. package/dist/doctor/rules/orphan-frontmatter-id.d.ts.map +1 -0
  67. package/dist/doctor/rules/orphan-frontmatter-id.js +154 -0
  68. package/dist/doctor/rules/orphan-frontmatter-id.js.map +1 -0
  69. package/dist/doctor/rules/schema-rejected.d.ts +20 -0
  70. package/dist/doctor/rules/schema-rejected.d.ts.map +1 -0
  71. package/dist/doctor/rules/schema-rejected.js +44 -0
  72. package/dist/doctor/rules/schema-rejected.js.map +1 -0
  73. package/dist/doctor/rules/slug-collision.d.ts +18 -0
  74. package/dist/doctor/rules/slug-collision.d.ts.map +1 -0
  75. package/dist/doctor/rules/slug-collision.js +65 -0
  76. package/dist/doctor/rules/slug-collision.js.map +1 -0
  77. package/dist/doctor/rules/workflow-stale.d.ts +20 -0
  78. package/dist/doctor/rules/workflow-stale.d.ts.map +1 -0
  79. package/dist/doctor/rules/workflow-stale.js +136 -0
  80. package/dist/doctor/rules/workflow-stale.js.map +1 -0
  81. package/dist/doctor/runner.d.ts +75 -0
  82. package/dist/doctor/runner.d.ts.map +1 -0
  83. package/dist/doctor/runner.js +289 -0
  84. package/dist/doctor/runner.js.map +1 -0
  85. package/dist/doctor/schema-patch.d.ts +21 -0
  86. package/dist/doctor/schema-patch.d.ts.map +1 -0
  87. package/dist/doctor/schema-patch.js +92 -0
  88. package/dist/doctor/schema-patch.js.map +1 -0
  89. package/dist/doctor/types.d.ts +185 -0
  90. package/dist/doctor/types.d.ts.map +1 -0
  91. package/dist/doctor/types.js +13 -0
  92. package/dist/doctor/types.js.map +1 -0
  93. package/dist/frontmatter.d.ts +103 -0
  94. package/dist/frontmatter.d.ts.map +1 -0
  95. package/dist/frontmatter.js +306 -0
  96. package/dist/frontmatter.js.map +1 -0
  97. package/dist/index.d.ts +27 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +27 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/ingest-derive.d.ts +79 -0
  102. package/dist/ingest-derive.d.ts.map +1 -0
  103. package/dist/ingest-derive.js +299 -0
  104. package/dist/ingest-derive.js.map +1 -0
  105. package/dist/ingest-paths.d.ts +37 -0
  106. package/dist/ingest-paths.d.ts.map +1 -0
  107. package/dist/ingest-paths.js +176 -0
  108. package/dist/ingest-paths.js.map +1 -0
  109. package/dist/ingest.d.ts +162 -0
  110. package/dist/ingest.d.ts.map +1 -0
  111. package/dist/ingest.js +269 -0
  112. package/dist/ingest.js.map +1 -0
  113. package/dist/journal.d.ts +49 -0
  114. package/dist/journal.d.ts.map +1 -0
  115. package/dist/journal.js +113 -0
  116. package/dist/journal.js.map +1 -0
  117. package/dist/outline-split.d.ts +38 -0
  118. package/dist/outline-split.d.ts.map +1 -0
  119. package/dist/outline-split.js +84 -0
  120. package/dist/outline-split.js.map +1 -0
  121. package/dist/overrides.d.ts +83 -0
  122. package/dist/overrides.d.ts.map +1 -0
  123. package/dist/overrides.js +88 -0
  124. package/dist/overrides.js.map +1 -0
  125. package/dist/paths.d.ts +183 -0
  126. package/dist/paths.d.ts.map +1 -0
  127. package/dist/paths.js +266 -0
  128. package/dist/paths.js.map +1 -0
  129. package/dist/remark-image-figure.mjs +77 -0
  130. package/dist/remark-strip-first-h1.mjs +26 -0
  131. package/dist/remark-strip-outline.mjs +44 -0
  132. package/dist/rename-slug.d.ts +49 -0
  133. package/dist/rename-slug.d.ts.map +1 -0
  134. package/dist/rename-slug.js +161 -0
  135. package/dist/rename-slug.js.map +1 -0
  136. package/dist/review/handlers.d.ts +55 -0
  137. package/dist/review/handlers.d.ts.map +1 -0
  138. package/dist/review/handlers.js +307 -0
  139. package/dist/review/handlers.js.map +1 -0
  140. package/dist/review/index.d.ts +14 -0
  141. package/dist/review/index.d.ts.map +1 -0
  142. package/dist/review/index.js +13 -0
  143. package/dist/review/index.js.map +1 -0
  144. package/dist/review/journal-mappers.d.ts +35 -0
  145. package/dist/review/journal-mappers.d.ts.map +1 -0
  146. package/dist/review/journal-mappers.js +48 -0
  147. package/dist/review/journal-mappers.js.map +1 -0
  148. package/dist/review/pipeline.d.ts +79 -0
  149. package/dist/review/pipeline.d.ts.map +1 -0
  150. package/dist/review/pipeline.js +234 -0
  151. package/dist/review/pipeline.js.map +1 -0
  152. package/dist/review/render.d.ts +27 -0
  153. package/dist/review/render.d.ts.map +1 -0
  154. package/dist/review/render.js +42 -0
  155. package/dist/review/render.js.map +1 -0
  156. package/dist/review/report.d.ts +50 -0
  157. package/dist/review/report.d.ts.map +1 -0
  158. package/dist/review/report.js +164 -0
  159. package/dist/review/report.js.map +1 -0
  160. package/dist/review/result.d.ts +12 -0
  161. package/dist/review/result.d.ts.map +1 -0
  162. package/dist/review/result.js +12 -0
  163. package/dist/review/result.js.map +1 -0
  164. package/dist/review/start-handlers.d.ts +62 -0
  165. package/dist/review/start-handlers.d.ts.map +1 -0
  166. package/dist/review/start-handlers.js +223 -0
  167. package/dist/review/start-handlers.js.map +1 -0
  168. package/dist/review/types.d.ts +169 -0
  169. package/dist/review/types.d.ts.map +1 -0
  170. package/dist/review/types.js +26 -0
  171. package/dist/review/types.js.map +1 -0
  172. package/dist/review/workflow-paths.d.ts +68 -0
  173. package/dist/review/workflow-paths.d.ts.map +1 -0
  174. package/dist/review/workflow-paths.js +112 -0
  175. package/dist/review/workflow-paths.js.map +1 -0
  176. package/dist/scaffold.d.ts +67 -0
  177. package/dist/scaffold.d.ts.map +1 -0
  178. package/dist/scaffold.js +122 -0
  179. package/dist/scaffold.js.map +1 -0
  180. package/dist/scrapbook.d.ts +229 -0
  181. package/dist/scrapbook.d.ts.map +1 -0
  182. package/dist/scrapbook.js +500 -0
  183. package/dist/scrapbook.js.map +1 -0
  184. package/dist/types.d.ts +197 -0
  185. package/dist/types.d.ts.map +1 -0
  186. package/dist/types.js +120 -0
  187. package/dist/types.js.map +1 -0
  188. package/package.json +160 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Doctor runner — orchestrates rule execution.
3
+ *
4
+ * The runner owns the per-site setup (read calendar, build content
5
+ * index, scope workflows) and walks the registered rules in a stable
6
+ * order. `runAudit` only calls `audit()`; `runRepair` chains audit →
7
+ * plan → (interaction) → apply.
8
+ *
9
+ * Sibling-relative imports per the project convention.
10
+ */
11
+ import { readCalendar } from "../calendar.js";
12
+ import { buildContentIndex } from "../content-index.js";
13
+ import { resolveCalendarPath } from "../paths.js";
14
+ import { readWorkflows } from "../review/pipeline.js";
15
+ import missingFrontmatterId from "./rules/missing-frontmatter-id.js";
16
+ import orphanFrontmatterId from "./rules/orphan-frontmatter-id.js";
17
+ import duplicateId from "./rules/duplicate-id.js";
18
+ import slugCollision from "./rules/slug-collision.js";
19
+ import schemaRejected from "./rules/schema-rejected.js";
20
+ import workflowStale from "./rules/workflow-stale.js";
21
+ import calendarUuidMissing from "./rules/calendar-uuid-missing.js";
22
+ import legacyTopLevelIdMigration from "./rules/legacy-top-level-id-migration.js";
23
+ import { loadProjectRules, mergeRules } from "./project-rules.js";
24
+ /**
25
+ * Registry of all rules in the order they run. The order matters: we
26
+ * detect calendar-uuid-missing first (to flush UUIDs), then run the
27
+ * frontmatter-id rules (which depend on UUIDs being persisted on
28
+ * disk to be useful in long-lived data).
29
+ *
30
+ * `legacy-top-level-id-migration` (Issue #38) runs BEFORE
31
+ * `missing-frontmatter-id` so that v0.7.0/v0.7.1-shaped files migrate
32
+ * to the namespaced form first; on the same run, the
33
+ * missing-frontmatter-id rule then sees the migrated files as bound
34
+ * (via `deskwork.id`) and doesn't re-report them.
35
+ */
36
+ export const RULES = [
37
+ calendarUuidMissing,
38
+ legacyTopLevelIdMigration,
39
+ missingFrontmatterId,
40
+ orphanFrontmatterId,
41
+ duplicateId,
42
+ slugCollision,
43
+ workflowStale,
44
+ schemaRejected,
45
+ ];
46
+ const RULE_BY_ID = new Map(RULES.map((r) => [r.id, r]));
47
+ /**
48
+ * Resolve a CSV/comma-separated `--fix=` argument to rule ids.
49
+ *
50
+ * Returns the full list of built-in rule ids for `''` and `'all'`.
51
+ * Unknown built-in id strings are rejected (exit 2 in the CLI).
52
+ *
53
+ * Project rules registered via `<projectRoot>/.deskwork/doctor/*.ts`
54
+ * (Phase 23f) are selected by passing `'all'`; the runner picks them
55
+ * up from the merged rule list. Selecting an individual project rule
56
+ * by id via `--fix=<id>` is not yet supported — file an issue if the
57
+ * usage emerges.
58
+ */
59
+ export function parseFixArgument(arg) {
60
+ const trimmed = arg.trim();
61
+ if (trimmed === '' || trimmed === 'all') {
62
+ return RULES.map((r) => r.id);
63
+ }
64
+ const ids = trimmed
65
+ .split(',')
66
+ .map((s) => s.trim())
67
+ .filter((s) => s.length > 0);
68
+ for (const id of ids) {
69
+ if (!RULE_BY_ID.has(id)) {
70
+ throw new Error(`Unknown doctor rule: "${id}". Known: ${RULES.map((r) => r.id).join(', ')}, all`);
71
+ }
72
+ }
73
+ return ids;
74
+ }
75
+ /** Build the per-site context once for a run. */
76
+ function buildContext(opts, site, interaction) {
77
+ const calendarPath = resolveCalendarPath(opts.projectRoot, opts.config, site);
78
+ const calendar = readCalendar(calendarPath);
79
+ const index = buildContentIndex(opts.projectRoot, opts.config, site);
80
+ const allWorkflows = readWorkflows(opts.projectRoot, opts.config);
81
+ const workflows = allWorkflows.filter((w) => w.site === site);
82
+ return {
83
+ projectRoot: opts.projectRoot,
84
+ config: opts.config,
85
+ site,
86
+ calendar,
87
+ index,
88
+ workflows,
89
+ interaction,
90
+ };
91
+ }
92
+ function selectSites(opts) {
93
+ if (opts.site !== undefined) {
94
+ if (!(opts.site in opts.config.sites)) {
95
+ throw new Error(`Unknown site "${opts.site}". Configured sites: ${Object.keys(opts.config.sites).join(', ')}`);
96
+ }
97
+ return [opts.site];
98
+ }
99
+ return Object.keys(opts.config.sites);
100
+ }
101
+ function selectRules(available, ruleIds) {
102
+ if (ruleIds === undefined)
103
+ return [...available];
104
+ const byId = new Map(available.map((r) => [r.id, r]));
105
+ const out = [];
106
+ for (const id of ruleIds) {
107
+ const rule = byId.get(id);
108
+ if (!rule) {
109
+ throw new Error(`Unknown doctor rule: "${id}". Known: ${available.map((r) => r.id).join(', ')}, all`);
110
+ }
111
+ out.push(rule);
112
+ }
113
+ return out;
114
+ }
115
+ /**
116
+ * Phase 23f: build the effective rule set for an audit or repair run.
117
+ * Built-in rules merged with project rules from
118
+ * `<projectRoot>/.deskwork/doctor/*.ts`. Project rules with a basename
119
+ * matching a built-in's basename REPLACE that built-in (override
120
+ * semantics); new basenames append.
121
+ *
122
+ * Loaded once per run — not per finding — so disk i/o for
123
+ * `readdirSync` + N dynamic imports happens at the start of an audit.
124
+ */
125
+ async function buildEffectiveRules(projectRoot) {
126
+ const projectRules = await loadProjectRules(projectRoot);
127
+ return mergeRules(RULES, projectRules);
128
+ }
129
+ /**
130
+ * Audit: collect findings without mutating the world. Returns a fully-
131
+ * built report with empty `repairs`. Suitable for pre-commit hooks
132
+ * that just want a non-zero exit code on any finding.
133
+ */
134
+ export async function runAudit(opts, interaction) {
135
+ const sites = selectSites(opts);
136
+ const available = await buildEffectiveRules(opts.projectRoot);
137
+ const rules = selectRules(available, opts.ruleIds);
138
+ const findings = [];
139
+ for (const site of sites) {
140
+ const ctx = buildContext(opts, site, interaction);
141
+ for (const rule of rules) {
142
+ const out = await rule.audit(ctx);
143
+ findings.push(...out);
144
+ }
145
+ }
146
+ return { findings, repairs: [], sites };
147
+ }
148
+ /**
149
+ * Repair: run audit → plan → (consult interaction) → apply. Returns
150
+ * the audit findings AND the repair results so callers can render
151
+ * a single report covering both phases.
152
+ *
153
+ * For `prompt` plans the runner consults `interaction.pickChoice` to
154
+ * resolve to an apply payload; for `apply` plans it consults
155
+ * `interaction.confirmApply`. `report-only` plans never apply.
156
+ */
157
+ export async function runRepair(opts, interaction) {
158
+ const sites = selectSites(opts);
159
+ const available = await buildEffectiveRules(opts.projectRoot);
160
+ const rules = selectRules(available, opts.ruleIds);
161
+ const findings = [];
162
+ const repairs = [];
163
+ for (const site of sites) {
164
+ const ctx = buildContext(opts, site, interaction);
165
+ for (const rule of rules) {
166
+ const ruleFindings = await rule.audit(ctx);
167
+ findings.push(...ruleFindings);
168
+ for (const finding of ruleFindings) {
169
+ const plan = await rule.plan(ctx, finding);
170
+ const result = await resolveAndApply(rule, ctx, plan, interaction);
171
+ repairs.push(result);
172
+ }
173
+ }
174
+ }
175
+ return { findings, repairs, sites };
176
+ }
177
+ /**
178
+ * Resolve a `prompt` plan via the interaction adapter and apply it.
179
+ * `apply` plans go through `confirmApply` first; `report-only` plans
180
+ * record a non-applied result.
181
+ */
182
+ async function resolveAndApply(rule, ctx, plan, interaction) {
183
+ if (plan.kind === 'report-only') {
184
+ // `report-only` is the rule's signal that the finding can't be
185
+ // auto-repaired in this run. The granularity (prerequisite vs
186
+ // editorial-decision vs schema-rejected) is rule-specific; the
187
+ // rule sets `skipReason` via the report-only finding's details
188
+ // — see `reportOnlySkipReason()` below.
189
+ return {
190
+ finding: plan.finding,
191
+ applied: false,
192
+ message: plan.reason,
193
+ skipReason: reportOnlySkipReason(rule, plan.finding),
194
+ };
195
+ }
196
+ if (plan.kind === 'prompt') {
197
+ const choiceId = await interaction.pickChoice(plan);
198
+ if (choiceId === undefined) {
199
+ return {
200
+ finding: plan.finding,
201
+ applied: false,
202
+ message: 'skipped (operator declined or --yes mode encountered ambiguity)',
203
+ skipReason: 'ambiguous',
204
+ };
205
+ }
206
+ const choice = plan.choices.find((c) => c.id === choiceId);
207
+ if (!choice) {
208
+ return {
209
+ finding: plan.finding,
210
+ applied: false,
211
+ message: `unknown choice id: ${choiceId}`,
212
+ skipReason: 'apply-failed',
213
+ };
214
+ }
215
+ const applyPlan = {
216
+ kind: 'apply',
217
+ finding: plan.finding,
218
+ summary: choice.label,
219
+ payload: choice.payload,
220
+ };
221
+ return rule.apply(ctx, applyPlan);
222
+ }
223
+ // apply
224
+ const ok = await interaction.confirmApply(plan);
225
+ if (!ok) {
226
+ return {
227
+ finding: plan.finding,
228
+ applied: false,
229
+ message: 'skipped (operator declined)',
230
+ skipReason: 'operator-declined',
231
+ };
232
+ }
233
+ return rule.apply(ctx, plan);
234
+ }
235
+ /**
236
+ * Map a rule's `report-only` plan to a `SkipReason`. The mapping is by
237
+ * rule id because the existing rules don't carry an explicit skip-
238
+ * reason field on their `report-only` plans — adding one to every
239
+ * rule would be churn for a 1:1 relationship that's already implicit
240
+ * in the rule's purpose.
241
+ */
242
+ function reportOnlySkipReason(rule, _finding) {
243
+ switch (rule.id) {
244
+ case 'missing-frontmatter-id':
245
+ // Always "no candidate file found" — the operator hasn't
246
+ // scaffolded the body file yet (run /deskwork:outline).
247
+ return 'prerequisite-missing';
248
+ case 'slug-collision':
249
+ // Editorial: which slug "owns" the public URL.
250
+ return 'editorial-decision';
251
+ case 'schema-rejected':
252
+ // Patch instructions only; nothing to apply automatically.
253
+ return 'schema-rejected';
254
+ default:
255
+ // Conservative fallback — treat unfamiliar rules as needing
256
+ // operator follow-up.
257
+ return 'editorial-decision';
258
+ }
259
+ }
260
+ /**
261
+ * Pre-built interaction: always confirm `apply` plans, skip `prompt`
262
+ * plans (no way to choose without a UI). Used by `--yes` mode.
263
+ *
264
+ * Exposed for the CLI command to construct.
265
+ */
266
+ export const yesInteraction = {
267
+ async pickChoice(_plan) {
268
+ // `--yes` skips ambiguous cases by design — the workplan calls
269
+ // out missing-frontmatter-id with multiple candidates as the
270
+ // canonical example.
271
+ return undefined;
272
+ },
273
+ async confirmApply(_plan) {
274
+ return true;
275
+ },
276
+ };
277
+ /**
278
+ * Pre-built interaction: never apply anything. Used to dry-run a
279
+ * repair pipeline (prompt resolution + apply both no-op).
280
+ */
281
+ export const declineInteraction = {
282
+ async pickChoice(_plan) {
283
+ return undefined;
284
+ },
285
+ async confirmApply(_plan) {
286
+ return false;
287
+ },
288
+ };
289
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Schema-patch instructions for host content collections that reject
3
+ * the deskwork frontmatter binding.
4
+ *
5
+ * Astro's content collection schemas are strict by default — a
6
+ * `z.object({ ... })` schema without `.passthrough()` (or an explicit
7
+ * entry for the deskwork namespace) refuses files whose frontmatter
8
+ * carries unknown keys. Phase 19 introduced the binding key in
9
+ * frontmatter; v0.7.2 (Issue #38) moved it under a `deskwork:`
10
+ * namespace so deskwork doesn't claim the global top-level keyspace.
11
+ *
12
+ * The `schema-rejected` doctor rule and the scaffolder both surface
13
+ * this text when an actual schema rejection is observed at write time.
14
+ */
15
+ /**
16
+ * Return the operator-facing schema-patch instructions. The optional
17
+ * `collection` argument is reserved for future per-collection scoping
18
+ * (the current implementation returns the same text regardless).
19
+ */
20
+ export declare function printSchemaPatchInstructions(collection?: string): string;
21
+ //# sourceMappingURL=schema-patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-patch.d.ts","sourceRoot":"","sources":["../../src/doctor/schema-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAqEH;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAKxE"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Schema-patch instructions for host content collections that reject
3
+ * the deskwork frontmatter binding.
4
+ *
5
+ * Astro's content collection schemas are strict by default — a
6
+ * `z.object({ ... })` schema without `.passthrough()` (or an explicit
7
+ * entry for the deskwork namespace) refuses files whose frontmatter
8
+ * carries unknown keys. Phase 19 introduced the binding key in
9
+ * frontmatter; v0.7.2 (Issue #38) moved it under a `deskwork:`
10
+ * namespace so deskwork doesn't claim the global top-level keyspace.
11
+ *
12
+ * The `schema-rejected` doctor rule and the scaffolder both surface
13
+ * this text when an actual schema rejection is observed at write time.
14
+ */
15
+ const TEMPLATE = `# Host content schema must permit the \`deskwork\` namespace in frontmatter
16
+
17
+ Deskwork binds calendar entries to markdown files via a UUID written
18
+ under a \`deskwork:\` mapping in frontmatter (\`deskwork.id\`, UUID v4).
19
+ For Astro projects with strict content collection schemas, this means
20
+ the site's \`src/content/config.ts\` must allow the namespace to pass
21
+ through.
22
+
23
+ Note: top-level \`id:\` is NOT what to add — that field belongs to the
24
+ operator's keyspace. Deskwork no longer claims it.
25
+
26
+ Pick one of the following patches and apply it to your collection
27
+ schema. Re-run the failing command after the patch.
28
+
29
+ ## Option 1 — explicit \`deskwork\` namespace (recommended)
30
+
31
+ \`\`\`ts
32
+ import { defineCollection, z } from 'astro:content';
33
+
34
+ const blog = defineCollection({
35
+ type: 'content',
36
+ schema: z.object({
37
+ deskwork: z.object({ id: z.string().uuid() }).passthrough().optional(),
38
+ title: z.string(),
39
+ description: z.string().optional(),
40
+ // ...your existing fields
41
+ }),
42
+ });
43
+
44
+ export const collections = { blog };
45
+ \`\`\`
46
+
47
+ The \`deskwork\` block is optional so legacy files without it keep
48
+ validating; deskwork sets it on every new scaffold and via
49
+ \`deskwork doctor --fix=missing-frontmatter-id\` /
50
+ \`--fix=legacy-top-level-id-migration\`. The inner \`.passthrough()\`
51
+ leaves room for additional deskwork-scoped fields without forcing a
52
+ schema change every release.
53
+
54
+ ## Option 2 — passthrough unknown keys at the top level
55
+
56
+ \`\`\`ts
57
+ const blog = defineCollection({
58
+ type: 'content',
59
+ schema: z.object({
60
+ title: z.string(),
61
+ // ...your existing fields
62
+ }).passthrough(),
63
+ });
64
+ \`\`\`
65
+
66
+ Less precise but a smaller diff. Top-level \`.passthrough()\` accepts
67
+ any extra keys — including the entire \`deskwork:\` namespace — without
68
+ complaint. Use this when the schema is wide and you don't want to
69
+ enumerate every deskwork-internal field.
70
+
71
+ ## Hugo / Jekyll / Eleventy / plain markdown
72
+
73
+ These engines don't validate frontmatter against a schema; the
74
+ \`deskwork:\` mapping already passes through untouched. No patch
75
+ needed.
76
+
77
+ After patching, re-run the original deskwork command. The
78
+ \`schema-rejected\` rule's findings will clear on the next
79
+ \`deskwork doctor\` audit.
80
+ `;
81
+ /**
82
+ * Return the operator-facing schema-patch instructions. The optional
83
+ * `collection` argument is reserved for future per-collection scoping
84
+ * (the current implementation returns the same text regardless).
85
+ */
86
+ export function printSchemaPatchInstructions(collection) {
87
+ if (collection !== undefined && collection.length > 0) {
88
+ return `${TEMPLATE}\n(Reported for collection: ${collection})\n`;
89
+ }
90
+ return TEMPLATE;
91
+ }
92
+ //# sourceMappingURL=schema-patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-patch.js","sourceRoot":"","sources":["../../src/doctor/schema-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEhB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,UAAmB;IAC9D,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,OAAO,GAAG,QAAQ,+BAA+B,UAAU,KAAK,CAAC;IACnE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Doctor — type definitions.
3
+ *
4
+ * `deskwork doctor` walks calendar + content tree + workflow store and
5
+ * produces structured reports. Each rule audits, optionally proposes a
6
+ * repair plan, and (with operator consent) applies the plan. The runner
7
+ * orchestrates rule execution; this module owns the data shapes.
8
+ *
9
+ * Sibling-relative imports per the project convention — `@/` doesn't
10
+ * resolve under tsx at runtime in this package's `src/`, only in tests.
11
+ */
12
+ import type { DeskworkConfig } from '../config.ts';
13
+ import type { EditorialCalendar } from '../types.ts';
14
+ import type { ContentIndex } from '../content-index.ts';
15
+ import type { DraftWorkflowItem } from '../review/types.ts';
16
+ /** Severity for a finding — used only to color text-mode output. */
17
+ export type FindingSeverity = 'error' | 'warning' | 'info';
18
+ /**
19
+ * A single audit finding produced by a rule. The `details` map is rule-
20
+ * specific and meant for both human display and for `plan()` to reuse
21
+ * without re-walking the world.
22
+ */
23
+ export interface Finding {
24
+ /** Stable rule id (matches `DoctorRule.id`). */
25
+ ruleId: string;
26
+ /** Site slug the finding belongs to (multi-site projects). */
27
+ site: string;
28
+ /** Severity bucket. */
29
+ severity: FindingSeverity;
30
+ /** Short human-readable label, suitable for one-line text output. */
31
+ message: string;
32
+ /** Rule-defined payload (entry id, file paths, etc.). */
33
+ details: Readonly<Record<string, unknown>>;
34
+ }
35
+ /**
36
+ * What a rule would do if applied. `kind` discriminates: rules either
37
+ * have a concrete repair (`apply`), need operator input (`prompt`), or
38
+ * can only be reported on (`report-only`).
39
+ *
40
+ * The runner uses `kind` to decide whether to ask the operator (in
41
+ * interactive mode) or skip with a clear message (in `--yes` mode).
42
+ */
43
+ export type RepairPlan = {
44
+ kind: 'apply';
45
+ /** The finding this plan addresses. */
46
+ finding: Finding;
47
+ /** One-line summary the runner shows before applying. */
48
+ summary: string;
49
+ /** Rule-defined payload — passed verbatim to `apply()`. */
50
+ payload: Readonly<Record<string, unknown>>;
51
+ } | {
52
+ kind: 'prompt';
53
+ finding: Finding;
54
+ /** Operator-facing question. */
55
+ question: string;
56
+ /** Possible answers — first is the default. */
57
+ choices: ReadonlyArray<RepairChoice>;
58
+ } | {
59
+ kind: 'report-only';
60
+ finding: Finding;
61
+ /** Why no repair is offered (e.g. "operator must decide manually"). */
62
+ reason: string;
63
+ };
64
+ /** A single choice the operator can pick when a plan is `prompt`. */
65
+ export interface RepairChoice {
66
+ /** Stable id — what the runner records when the operator picks this. */
67
+ id: string;
68
+ /** Operator-facing label. */
69
+ label: string;
70
+ /** Rule-defined payload to pass to `apply()` if chosen. */
71
+ payload: Readonly<Record<string, unknown>>;
72
+ }
73
+ /**
74
+ * Reason a `RepairResult` was not applied — the granularity matters for
75
+ * the exit-code logic in `--fix` mode (Issue #44, Phase 22).
76
+ *
77
+ * - `prerequisite-missing`: the rule could not run because something
78
+ * outside doctor's scope hasn't happened yet (e.g. the entry has no
79
+ * body file because `outline` hasn't been called). Operator action
80
+ * is required, but it's not a doctor failure — `--fix` should still
81
+ * exit 0 once every applicable rule has been applied.
82
+ * - `ambiguous`: the rule has multiple valid resolutions and refuses
83
+ * to pick one without operator input. `--yes` mode skips these; the
84
+ * operator has to re-run interactively. `--fix` exits non-zero.
85
+ * - `editorial-decision`: the rule has a single resolution path but
86
+ * that path requires a human judgment call (e.g. picking which slug
87
+ * "owns" a public URL when two collide). `--fix` exits non-zero.
88
+ * - `schema-rejected`: the host's content-collection schema refused
89
+ * the write. `--fix` exits non-zero — the operator must patch the
90
+ * schema before retrying.
91
+ * - `operator-declined`: interactive mode and the operator said no.
92
+ * `--fix` exits non-zero (the operator chose not to proceed).
93
+ * - `apply-failed`: the rule's `apply()` raised on disk. `--fix` exits
94
+ * non-zero — a real failure to track down.
95
+ * - `no-action-needed`: the rule's plan resolved to a no-op (e.g. the
96
+ * operator chose "leave as-is" in an orphan-id prompt). `--fix`
97
+ * treats this as a successful skip.
98
+ */
99
+ export type SkipReason = 'prerequisite-missing' | 'ambiguous' | 'editorial-decision' | 'schema-rejected' | 'operator-declined' | 'apply-failed' | 'no-action-needed';
100
+ /** Outcome of applying a repair plan. */
101
+ export interface RepairResult {
102
+ finding: Finding;
103
+ /** True when the repair landed on disk. */
104
+ applied: boolean;
105
+ /** Human-readable summary of what happened (or why it was skipped). */
106
+ message: string;
107
+ /**
108
+ * When `applied` is false, why. Used by the CLI command to decide
109
+ * whether the skip is a real follow-up (exit non-zero) or a no-op
110
+ * waiting on operator action outside doctor's scope (exit zero).
111
+ * Optional for backward compatibility — older rule implementations
112
+ * may not set it; the CLI defaults to "treat as a real follow-up".
113
+ */
114
+ skipReason?: SkipReason;
115
+ /** Optional rule-defined details — e.g. paths written. */
116
+ details?: Readonly<Record<string, unknown>>;
117
+ }
118
+ /**
119
+ * Operator interaction adapter. Rules don't depend on the runner's UI;
120
+ * they declare prompts in their `plan()` and the runner provides an
121
+ * interaction implementation (interactive readline, `--yes` auto-pick,
122
+ * or a test stub).
123
+ */
124
+ export interface DoctorInteraction {
125
+ /**
126
+ * Resolve a `prompt` plan to a single choice id. Returns `undefined`
127
+ * to skip without applying — `--yes` mode does this for ambiguous
128
+ * cases; interactive mode does it when the operator declines.
129
+ */
130
+ pickChoice(plan: Extract<RepairPlan, {
131
+ kind: 'prompt';
132
+ }>): Promise<string | undefined>;
133
+ /**
134
+ * Confirm an `apply` plan. Returns `true` to apply, `false` to skip.
135
+ * `--yes` mode always returns `true`; interactive mode shows the
136
+ * summary and asks.
137
+ */
138
+ confirmApply(plan: Extract<RepairPlan, {
139
+ kind: 'apply';
140
+ }>): Promise<boolean>;
141
+ }
142
+ /**
143
+ * Carrier of every per-site input a rule needs. Rules receive this and
144
+ * return findings without re-walking the calendar or re-reading config.
145
+ */
146
+ export interface DoctorContext {
147
+ projectRoot: string;
148
+ config: DeskworkConfig;
149
+ /** Site slug the runner is currently auditing. */
150
+ site: string;
151
+ /** Calendar for `site` — already parsed. */
152
+ calendar: EditorialCalendar;
153
+ /** Content index for `site` — already built. */
154
+ index: ContentIndex;
155
+ /** Workflows from the review store, scoped to `site`. */
156
+ workflows: ReadonlyArray<DraftWorkflowItem>;
157
+ /** Operator interaction — used by the runner during repair. */
158
+ interaction: DoctorInteraction;
159
+ }
160
+ /**
161
+ * The contract every rule implements.
162
+ *
163
+ * `audit` is a pure read of the world. `plan` decides what (if anything)
164
+ * the rule would do for a finding. `apply` executes the plan. The runner
165
+ * decides whether to chain these — audit-only mode stops after `audit`.
166
+ */
167
+ export interface DoctorRule {
168
+ /** Stable identifier — appears in `--fix=<id>`. Use kebab-case. */
169
+ readonly id: string;
170
+ /** One-line operator-facing label. */
171
+ readonly label: string;
172
+ audit(ctx: DoctorContext): Promise<Finding[]>;
173
+ plan(ctx: DoctorContext, finding: Finding): Promise<RepairPlan>;
174
+ apply(ctx: DoctorContext, plan: RepairPlan): Promise<RepairResult>;
175
+ }
176
+ /** Aggregate report returned by the runner. */
177
+ export interface DoctorReport {
178
+ /** Per-site findings, in rule iteration order. */
179
+ findings: Finding[];
180
+ /** Per-site repair results — empty in audit-only mode. */
181
+ repairs: RepairResult[];
182
+ /** Sites the runner exercised, in iteration order. */
183
+ sites: string[];
184
+ }
185
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/doctor/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Doctor — type definitions.
3
+ *
4
+ * `deskwork doctor` walks calendar + content tree + workflow store and
5
+ * produces structured reports. Each rule audits, optionally proposes a
6
+ * repair plan, and (with operator consent) applies the plan. The runner
7
+ * orchestrates rule execution; this module owns the data shapes.
8
+ *
9
+ * Sibling-relative imports per the project convention — `@/` doesn't
10
+ * resolve under tsx at runtime in this package's `src/`, only in tests.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/doctor/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}