@deskwork/core 0.45.2 → 0.46.0

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 (156) hide show
  1. package/dist/config.d.ts +14 -4
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +43 -12
  4. package/dist/config.js.map +1 -1
  5. package/dist/content-index.d.ts +25 -0
  6. package/dist/content-index.d.ts.map +1 -1
  7. package/dist/content-index.js +142 -15
  8. package/dist/content-index.js.map +1 -1
  9. package/dist/doctor/index.d.ts +3 -0
  10. package/dist/doctor/index.d.ts.map +1 -1
  11. package/dist/doctor/index.js +3 -0
  12. package/dist/doctor/index.js.map +1 -1
  13. package/dist/doctor/legacy-config.d.ts +98 -0
  14. package/dist/doctor/legacy-config.d.ts.map +1 -0
  15. package/dist/doctor/legacy-config.js +174 -0
  16. package/dist/doctor/legacy-config.js.map +1 -0
  17. package/dist/doctor/project-scope-gate.d.ts +12 -21
  18. package/dist/doctor/project-scope-gate.d.ts.map +1 -1
  19. package/dist/doctor/project-scope-gate.js +13 -25
  20. package/dist/doctor/project-scope-gate.js.map +1 -1
  21. package/dist/doctor/repair.d.ts +11 -0
  22. package/dist/doctor/repair.d.ts.map +1 -1
  23. package/dist/doctor/repair.js +10 -89
  24. package/dist/doctor/repair.js.map +1 -1
  25. package/dist/doctor/rules/duplicate-id.d.ts +7 -2
  26. package/dist/doctor/rules/duplicate-id.d.ts.map +1 -1
  27. package/dist/doctor/rules/duplicate-id.js +8 -5
  28. package/dist/doctor/rules/duplicate-id.js.map +1 -1
  29. package/dist/doctor/rules/duplicate-id.ts +8 -5
  30. package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -1
  31. package/dist/doctor/rules/legacy-top-level-id-migration.js +11 -8
  32. package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -1
  33. package/dist/doctor/rules/legacy-top-level-id-migration.ts +10 -11
  34. package/dist/doctor/rules/sites-to-lanes-migration.d.ts +39 -0
  35. package/dist/doctor/rules/sites-to-lanes-migration.d.ts.map +1 -0
  36. package/dist/doctor/rules/sites-to-lanes-migration.js +395 -0
  37. package/dist/doctor/rules/sites-to-lanes-migration.js.map +1 -0
  38. package/dist/doctor/rules/sites-to-lanes-migration.ts +449 -0
  39. package/dist/doctor/rules/workflow-stale.d.ts.map +1 -1
  40. package/dist/doctor/rules/workflow-stale.js +5 -2
  41. package/dist/doctor/rules/workflow-stale.js.map +1 -1
  42. package/dist/doctor/rules/workflow-stale.ts +5 -1
  43. package/dist/doctor/runner.d.ts +15 -1
  44. package/dist/doctor/runner.d.ts.map +1 -1
  45. package/dist/doctor/runner.js +48 -41
  46. package/dist/doctor/runner.js.map +1 -1
  47. package/dist/doctor/sites-migration-backfill.d.ts +71 -0
  48. package/dist/doctor/sites-migration-backfill.d.ts.map +1 -0
  49. package/dist/doctor/sites-migration-backfill.js +164 -0
  50. package/dist/doctor/sites-migration-backfill.js.map +1 -0
  51. package/dist/doctor/validate.d.ts.map +1 -1
  52. package/dist/doctor/validate.js +34 -73
  53. package/dist/doctor/validate.js.map +1 -1
  54. package/dist/entry/resolve-artifact.d.ts +80 -0
  55. package/dist/entry/resolve-artifact.d.ts.map +1 -0
  56. package/dist/entry/resolve-artifact.js +102 -0
  57. package/dist/entry/resolve-artifact.js.map +1 -0
  58. package/dist/entry/shortform-path.d.ts +34 -0
  59. package/dist/entry/shortform-path.d.ts.map +1 -0
  60. package/dist/entry/shortform-path.js +50 -0
  61. package/dist/entry/shortform-path.js.map +1 -0
  62. package/dist/index.d.ts +1 -2
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -2
  65. package/dist/index.js.map +1 -1
  66. package/dist/iterate/iterate.d.ts.map +1 -1
  67. package/dist/iterate/iterate.js +21 -26
  68. package/dist/iterate/iterate.js.map +1 -1
  69. package/dist/lanes/bootstrap.d.ts +11 -7
  70. package/dist/lanes/bootstrap.d.ts.map +1 -1
  71. package/dist/lanes/bootstrap.js +42 -17
  72. package/dist/lanes/bootstrap.js.map +1 -1
  73. package/dist/lanes/index.d.ts +1 -0
  74. package/dist/lanes/index.d.ts.map +1 -1
  75. package/dist/lanes/index.js +6 -0
  76. package/dist/lanes/index.js.map +1 -1
  77. package/dist/lanes/loader.d.ts +33 -6
  78. package/dist/lanes/loader.d.ts.map +1 -1
  79. package/dist/lanes/loader.js +44 -11
  80. package/dist/lanes/loader.js.map +1 -1
  81. package/dist/lanes/operations/create.d.ts +11 -2
  82. package/dist/lanes/operations/create.d.ts.map +1 -1
  83. package/dist/lanes/operations/create.js +15 -4
  84. package/dist/lanes/operations/create.js.map +1 -1
  85. package/dist/lanes/operations/list.d.ts +3 -2
  86. package/dist/lanes/operations/list.d.ts.map +1 -1
  87. package/dist/lanes/operations/list.js +3 -2
  88. package/dist/lanes/operations/list.js.map +1 -1
  89. package/dist/lanes/operations/move.d.ts +29 -25
  90. package/dist/lanes/operations/move.d.ts.map +1 -1
  91. package/dist/lanes/operations/move.js +45 -242
  92. package/dist/lanes/operations/move.js.map +1 -1
  93. package/dist/lanes/operations/update.d.ts +15 -5
  94. package/dist/lanes/operations/update.d.ts.map +1 -1
  95. package/dist/lanes/operations/update.js +32 -12
  96. package/dist/lanes/operations/update.js.map +1 -1
  97. package/dist/lanes/scaffold-path.d.ts +88 -0
  98. package/dist/lanes/scaffold-path.d.ts.map +1 -0
  99. package/dist/lanes/scaffold-path.js +122 -0
  100. package/dist/lanes/scaffold-path.js.map +1 -0
  101. package/dist/lanes/types.d.ts +54 -31
  102. package/dist/lanes/types.d.ts.map +1 -1
  103. package/dist/lanes/types.js +60 -21
  104. package/dist/lanes/types.js.map +1 -1
  105. package/dist/outline-split.d.ts +2 -3
  106. package/dist/outline-split.d.ts.map +1 -1
  107. package/dist/outline-split.js +2 -3
  108. package/dist/outline-split.js.map +1 -1
  109. package/dist/paths.d.ts +14 -87
  110. package/dist/paths.d.ts.map +1 -1
  111. package/dist/paths.js +21 -131
  112. package/dist/paths.js.map +1 -1
  113. package/dist/remark-strip-outline.mjs +2 -3
  114. package/dist/rename-slug.d.ts +3 -3
  115. package/dist/rename-slug.d.ts.map +1 -1
  116. package/dist/rename-slug.js +137 -44
  117. package/dist/rename-slug.js.map +1 -1
  118. package/dist/review/handlers.d.ts +3 -3
  119. package/dist/review/handlers.d.ts.map +1 -1
  120. package/dist/review/handlers.js +18 -14
  121. package/dist/review/handlers.js.map +1 -1
  122. package/dist/review/report.d.ts +13 -2
  123. package/dist/review/report.d.ts.map +1 -1
  124. package/dist/review/report.js +48 -18
  125. package/dist/review/report.js.map +1 -1
  126. package/dist/review/start-handlers.d.ts +4 -4
  127. package/dist/review/start-handlers.d.ts.map +1 -1
  128. package/dist/review/start-handlers.js +25 -25
  129. package/dist/review/start-handlers.js.map +1 -1
  130. package/dist/review/workflow-paths.d.ts +24 -26
  131. package/dist/review/workflow-paths.d.ts.map +1 -1
  132. package/dist/review/workflow-paths.js +70 -60
  133. package/dist/review/workflow-paths.js.map +1 -1
  134. package/dist/schema/draft-annotation.d.ts +8 -8
  135. package/dist/schema/entry.d.ts +6 -6
  136. package/dist/schema/journal-events.d.ts +56 -42
  137. package/dist/schema/journal-events.d.ts.map +1 -1
  138. package/dist/schema/journal-events.js +23 -9
  139. package/dist/schema/journal-events.js.map +1 -1
  140. package/dist/sidecar/read.d.ts +8 -0
  141. package/dist/sidecar/read.d.ts.map +1 -1
  142. package/dist/sidecar/read.js +36 -9
  143. package/dist/sidecar/read.js.map +1 -1
  144. package/dist/sidecar/write.d.ts +14 -0
  145. package/dist/sidecar/write.d.ts.map +1 -1
  146. package/dist/sidecar/write.js +25 -0
  147. package/dist/sidecar/write.js.map +1 -1
  148. package/package.json +9 -9
  149. package/dist/body-state.d.ts +0 -27
  150. package/dist/body-state.d.ts.map +0 -1
  151. package/dist/body-state.js +0 -62
  152. package/dist/body-state.js.map +0 -1
  153. package/dist/scaffold.d.ts +0 -67
  154. package/dist/scaffold.d.ts.map +0 -1
  155. package/dist/scaffold.js +0 -122
  156. package/dist/scaffold.js.map +0 -1
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Rule: sites-to-lanes-migration (Phase 39b).
3
+ *
4
+ * Detects the pre-migration shape and migrates it under `--fix`. Per the
5
+ * sites→lanes retirement spec (`docs/superpowers/specs/2026-06-02-sites-
6
+ * to-lanes-retirement-design.md` §"Migration"):
7
+ *
8
+ * Detect (audit): `config.sites` is present (legacy shape) OR any
9
+ * artifact-bearing entry lacks `artifactPath`. The rule emits a
10
+ * detection finding describing what `--fix` will do, PLUS one
11
+ * `migration-ambiguous` finding per entry whose slug+stage resolves to
12
+ * more than one candidate file (AUDIT-20260602-03 — see the backfiller).
13
+ *
14
+ * Fix (apply): three steps, in order:
15
+ * 1. Lanes from sites — per legacy `site`, create/ensure a lane
16
+ * whose `id` is the site slug, `pipelineTemplate: editorial`,
17
+ * `host ← site.host` (when present), and `scaffoldDefaults` keyed
18
+ * at minimum by `markdown → site.contentDir`.
19
+ * 2. Backfill `artifactPath` — stamp every UNAMBIGUOUS artifact-
20
+ * bearing entry from its single on-disk candidate. Ambiguous
21
+ * entries are REFUSED (the backfiller's ambiguity-halt).
22
+ * 3. Drop `sites` — remove the `sites` / `defaultSite` block from the
23
+ * on-disk config.
24
+ *
25
+ * Project-scoped despite the runner's per-site loop: the runner calls
26
+ * each rule once per `Object.keys(config.sites)`. This rule is a
27
+ * whole-project operation (it rewrites the single config + every
28
+ * sidecar), so it acts ONLY on the runner's FIRST site pass and no-ops
29
+ * on the rest — guarded by `isLeadSite`. Its source of truth is the
30
+ * on-disk config read through the migration-only tolerant reader
31
+ * (`legacy-config.ts`), NOT `ctx.config`, so once `sites` is dropped a
32
+ * re-run sees nothing to migrate (idempotency).
33
+ *
34
+ * Sibling-relative imports per the doctor convention.
35
+ */
36
+
37
+ import { existsSync, mkdirSync } from 'node:fs';
38
+ import { appendJournalEvent } from '../../journal/append.ts';
39
+ import { commitLaneConfig } from '../../lanes/operations/commit.ts';
40
+ import { laneConfigPath, lanesDir, loadLaneConfig } from '../../lanes/loader.ts';
41
+ import { LaneConfigSchema, type LaneConfig } from '../../lanes/types.ts';
42
+ import {
43
+ dropSitesBlock,
44
+ readLegacySites,
45
+ type LegacySite,
46
+ } from '../legacy-config.ts';
47
+ import {
48
+ backfillFromLegacySites,
49
+ detectAmbiguousBackfills,
50
+ type AmbiguousBackfill,
51
+ type LaneBase,
52
+ } from '../sites-migration-backfill.ts';
53
+ import { readAllSidecars } from '../../sidecar/read-all.ts';
54
+ import type {
55
+ DoctorContext,
56
+ DoctorRule,
57
+ Finding,
58
+ RepairPlan,
59
+ RepairResult,
60
+ } from '../types.ts';
61
+
62
+ const RULE_ID = 'sites-to-lanes-migration';
63
+ const AMBIGUOUS_RULE_ID = 'migration-ambiguous';
64
+ const MIGRATION_PIPELINE_TEMPLATE = 'editorial';
65
+
66
+ /**
67
+ * The migration is whole-project. Phase 39c collapsed the runner's
68
+ * per-site loop into a single project pass (`runner.ts` `PROJECT_SCOPE`),
69
+ * so this rule now fires exactly once per run regardless. The guard is
70
+ * retained as a constant `true` (its source of truth is the on-disk
71
+ * config via `readLegacySites`, not `ctx.config`, so idempotency is
72
+ * unaffected).
73
+ */
74
+ function isLeadSite(_ctx: DoctorContext): boolean {
75
+ return true;
76
+ }
77
+
78
+ /**
79
+ * Collect the legacy bases (one per legacy site) — each site's contentDir
80
+ * paired with the lane id it migrates to (the site slug). These are the
81
+ * bases the backfiller searches; the lane id lets it stamp the migrated
82
+ * entry's `lane` (AUDIT-20260603-12). Returns an empty array when there
83
+ * are no legacy sites (post-migration).
84
+ */
85
+ function legacyLaneBases(sites: ReadonlyMap<string, LegacySite>): LaneBase[] {
86
+ return [...sites.entries()].map(([slug, s]) => ({
87
+ laneId: slug,
88
+ contentDir: s.contentDir,
89
+ }));
90
+ }
91
+
92
+ /**
93
+ * Whether any artifact-bearing entry still lacks `artifactPath`.
94
+ *
95
+ * Does NOT swallow read errors (AUDIT-20260603-14). A corrupt sidecar
96
+ * makes `readAllSidecars` throw; previously this was caught and turned
97
+ * into `false`, so `audit()` silently under-reported ("nothing missing")
98
+ * on the exact on-disk state that `apply()` rejects — an audit/apply
99
+ * asymmetry the project's "no swallowed exceptions" rule names as a bug
100
+ * factory. The throw now propagates to `audit()`, which converts it into
101
+ * an `error` finding (so audit and apply agree on corrupt input, and the
102
+ * run still completes — AUDIT-20260603-13).
103
+ */
104
+ async function anyEntryMissingArtifactPath(projectRoot: string): Promise<boolean> {
105
+ const entries = await readAllSidecars(projectRoot);
106
+ return entries.some(
107
+ (e) => e.artifactPath === undefined || e.artifactPath === '',
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Build the per-site lane config (id = slug). Carries the site's
113
+ * website-publishing metadata forward when present: `host` (Decision #2)
114
+ * and `redirectsPath` (Decision #23 / c4) re-home onto the lane as
115
+ * optional siblings; each is omitted entirely when the legacy site did
116
+ * not declare it (no empty-string writes). `scaffoldDefaults.markdown`
117
+ * captures the legacy contentDir as the add-time default — never
118
+ * identity or resolution.
119
+ */
120
+ function laneFromSite(slug: string, site: LegacySite): LaneConfig {
121
+ const lane: LaneConfig = {
122
+ id: slug,
123
+ name: slug,
124
+ pipelineTemplate: MIGRATION_PIPELINE_TEMPLATE,
125
+ // Per Phase 39c the lane carries no `contentDir` — the legacy site's
126
+ // content directory becomes the lane's add-time `scaffoldDefaults`
127
+ // (the `markdown` kind, the editorial pipeline's artifact kind).
128
+ // Location info is the ENTRY's `artifactPath`, never the lane.
129
+ scaffoldDefaults: { markdown: site.contentDir },
130
+ ...(site.host !== undefined ? { host: site.host } : {}),
131
+ ...(site.redirectsPath !== undefined
132
+ ? { redirectsPath: site.redirectsPath }
133
+ : {}),
134
+ };
135
+ return lane;
136
+ }
137
+
138
+ /**
139
+ * Collect the migration's detection + ambiguity findings. Extracted from
140
+ * `audit()` so the latter can wrap it in a single try/catch that converts
141
+ * any read throw into an `error` finding (AUDIT-20260603-13/-14) without
142
+ * the detection logic itself growing a defensive nest. May throw —
143
+ * `audit()` is its only caller and handles the throw.
144
+ */
145
+ async function collectFindings(ctx: DoctorContext): Promise<Finding[]> {
146
+ const { sites } = readLegacySites(ctx.projectRoot);
147
+ const sitesPresent = sites.size > 0;
148
+ const missingArtifactPath = await anyEntryMissingArtifactPath(ctx.projectRoot);
149
+
150
+ const findings: Finding[] = [];
151
+
152
+ if (sitesPresent || missingArtifactPath) {
153
+ const slugs = [...sites.keys()];
154
+ findings.push({
155
+ ruleId: RULE_ID,
156
+ site: ctx.site,
157
+ severity: 'warning',
158
+ message:
159
+ `Project uses the legacy "sites" model` +
160
+ (sitesPresent ? ` (${slugs.length} site(s): ${slugs.join(', ')})` : '') +
161
+ (missingArtifactPath ? ' and has entries missing artifactPath' : '') +
162
+ `. --fix migrates each site to a lane (host + scaffoldDefaults), ` +
163
+ `backfills each unambiguous entry's artifactPath, and drops the ` +
164
+ `sites block. Slug-collision entries (resolving to >1 file) are ` +
165
+ `refused and reported as migration-ambiguous.`,
166
+ details: {
167
+ siteSlugs: slugs,
168
+ sitesPresent,
169
+ missingArtifactPath,
170
+ },
171
+ });
172
+ }
173
+
174
+ // Surface ambiguity collisions as their own findings BEFORE any fix
175
+ // runs (AUDIT-20260602-03). These are report-only — the migration
176
+ // refuses to stamp them.
177
+ const bases = legacyLaneBases(sites);
178
+ if (bases.length > 0) {
179
+ const ambiguous = await detectAmbiguousBackfills(ctx.projectRoot, bases);
180
+ for (const amb of ambiguous) {
181
+ findings.push(ambiguousFinding(ctx.site, amb));
182
+ }
183
+ }
184
+
185
+ return findings;
186
+ }
187
+
188
+ const rule: DoctorRule = {
189
+ id: RULE_ID,
190
+ label: 'Migrate legacy sites to lanes (Phase 39 sites→lanes retirement)',
191
+
192
+ async audit(ctx: DoctorContext): Promise<Finding[]> {
193
+ if (!isLeadSite(ctx)) return [];
194
+
195
+ // The migration's reads can throw on a broken-but-present config (a
196
+ // legacy site missing `contentDir` — `readLegacySites`) or a corrupt
197
+ // sidecar (`readAllSidecars` via `anyEntryMissingArtifactPath` /
198
+ // `detectAmbiguousBackfills`). An uncaught throw here aborts the WHOLE
199
+ // doctor run, denying the operator every other rule's output
200
+ // (AUDIT-20260603-13). The runner does not guard `rule.audit()`, so
201
+ // the rule guards itself: any throw becomes an `error`-severity
202
+ // finding naming the problem, and the run continues. This also keeps
203
+ // audit and apply consistent on corrupt sidecars (AUDIT-20260603-14):
204
+ // both surface the corruption loudly rather than audit silently
205
+ // reporting clean while apply throws.
206
+ try {
207
+ return await collectFindings(ctx);
208
+ } catch (err) {
209
+ const reason = err instanceof Error ? err.message : String(err);
210
+ return [
211
+ {
212
+ ruleId: RULE_ID,
213
+ site: ctx.site,
214
+ severity: 'error',
215
+ message:
216
+ `sites-to-lanes migration could not inspect the project: ${reason}. ` +
217
+ `Repair the offending config or sidecar and re-run doctor.`,
218
+ details: { error: reason },
219
+ },
220
+ ];
221
+ }
222
+ },
223
+
224
+ async plan(_ctx: DoctorContext, finding: Finding): Promise<RepairPlan> {
225
+ if (finding.ruleId === AMBIGUOUS_RULE_ID) {
226
+ const rawCandidates = finding.details.candidates;
227
+ const candidates = Array.isArray(rawCandidates)
228
+ ? rawCandidates.map((c) => String(c))
229
+ : [];
230
+ return {
231
+ kind: 'report-only',
232
+ finding,
233
+ reason:
234
+ `Entry ${String(finding.details.entryUuid)} (slug=${String(finding.details.slug)}) ` +
235
+ `resolves to ${candidates.length} candidate files across legacy site ` +
236
+ `contentDirs: ${candidates.join(', ')}. The migration refuses to stamp ` +
237
+ `an ambiguous guess (it would launder a #394-class collision into ` +
238
+ `permanent data). Disambiguate manually — set the entry's artifactPath ` +
239
+ `explicitly, or remove the duplicate file — then re-run doctor --fix.`,
240
+ };
241
+ }
242
+ return {
243
+ kind: 'apply',
244
+ finding,
245
+ summary:
246
+ 'migrate legacy sites → lanes: create lanes (host + scaffoldDefaults), ' +
247
+ 'backfill unambiguous entry artifactPaths, drop the sites block',
248
+ payload: {},
249
+ };
250
+ },
251
+
252
+ async apply(ctx: DoctorContext, plan: RepairPlan): Promise<RepairResult> {
253
+ if (plan.kind !== 'apply') {
254
+ return {
255
+ finding: plan.finding,
256
+ applied: false,
257
+ message: 'plan is not directly appliable',
258
+ skipReason: 'apply-failed',
259
+ };
260
+ }
261
+
262
+ try {
263
+ const { sites } = readLegacySites(ctx.projectRoot);
264
+ const bases = legacyLaneBases(sites);
265
+
266
+ // No legacy sites to migrate, but the detection still fired — that
267
+ // means it fired on `missingArtifactPath` alone (AUDIT-20260603-15).
268
+ // Every repair action keys off legacy sites: lane creation iterates
269
+ // `sites`, and the backfiller only searches each legacy
270
+ // `site.contentDir`. With no bases there is nothing this migration
271
+ // can stamp, so claiming `applied: true` would advertise a
272
+ // successful fix for a run that changed nothing and did not converge
273
+ // (the next audit re-fires the same finding). Report honestly: the
274
+ // entries need the lane-native back-fill, not the sites→lanes path.
275
+ if (bases.length === 0) {
276
+ const stillMissing = await anyEntryMissingArtifactPath(ctx.projectRoot);
277
+ if (stillMissing) {
278
+ return {
279
+ finding: plan.finding,
280
+ applied: false,
281
+ message:
282
+ 'sites-to-lanes: no legacy sites to migrate, but entries still ' +
283
+ 'lack artifactPath. This migration only back-fills from legacy ' +
284
+ 'site contentDirs, of which there are none — it cannot stamp ' +
285
+ 'these entries. Back-fill via the lane-native path ' +
286
+ '(`migrateLaneMembership` / `/deskwork:lane move`) instead.',
287
+ skipReason: 'prerequisite-missing',
288
+ };
289
+ }
290
+ // No sites AND nothing missing → genuinely nothing to do.
291
+ return {
292
+ finding: plan.finding,
293
+ applied: false,
294
+ message:
295
+ 'sites-to-lanes: no legacy sites and no entries missing ' +
296
+ 'artifactPath — nothing to migrate.',
297
+ skipReason: 'no-action-needed',
298
+ };
299
+ }
300
+
301
+ // Step 1 — lanes from sites (idempotent: skip an existing lane file).
302
+ const lanesCreated: string[] = [];
303
+ if (sites.size > 0) {
304
+ mkdirSync(lanesDir(ctx.projectRoot), { recursive: true });
305
+ }
306
+ for (const [slug, site] of sites) {
307
+ const target = laneConfigPath(ctx.projectRoot, slug);
308
+ if (existsSync(target)) {
309
+ // AUDIT-20260604-10: a pre-existing lane file (re-run / partial
310
+ // migration) was previously skipped wholesale — then Step 3's
311
+ // dropSitesBlock removed the only remaining copy of the legacy
312
+ // `redirectsPath`, silently discarding it. Merge the legacy
313
+ // redirectsPath onto the existing lane when the lane lacks it.
314
+ //
315
+ // Faithfulness (AUDIT-20260604-08-followup / claude-02): reading
316
+ // via loadLaneConfig is faithful here because LaneConfigSchema is
317
+ // `.strict()` with NO `.default()` fields — a successfully-loaded
318
+ // lane carries exactly its on-disk valid fields ($rationale
319
+ // included), and an unknown key would have THROWN at load (strict),
320
+ // not been silently stripped. So `{ ...existing, redirectsPath }`
321
+ // adds one field and re-writes the rest verbatim; commitLaneConfig
322
+ // re-validates via the same LaneConfigSchema.safeParse gate the
323
+ // create branch uses below. Only the c4-introduced redirectsPath is
324
+ // reconciled; other fields are not re-derived (the lane already
325
+ // exists by operator/earlier intent).
326
+ if (site.redirectsPath !== undefined) {
327
+ const existing = loadLaneConfig(slug, ctx.projectRoot);
328
+ if (existing.redirectsPath === undefined) {
329
+ commitLaneConfig(
330
+ ctx.projectRoot,
331
+ slug,
332
+ { ...existing, redirectsPath: site.redirectsPath },
333
+ 'merge-legacy-redirectsPath',
334
+ );
335
+ await appendJournalEvent(ctx.projectRoot, {
336
+ kind: 'lane-migration',
337
+ at: new Date().toISOString(),
338
+ migration: 'merge-redirectsPath-into-existing-lane',
339
+ source: `sites.${slug}`,
340
+ target: `lanes.${slug}`,
341
+ details: { legacySiteId: slug, redirectsPath: site.redirectsPath },
342
+ });
343
+ }
344
+ }
345
+ continue;
346
+ }
347
+ const lane = laneFromSite(slug, site);
348
+ const validated = LaneConfigSchema.safeParse(lane);
349
+ if (!validated.success) {
350
+ return {
351
+ finding: plan.finding,
352
+ applied: false,
353
+ message:
354
+ `sites-to-lanes: lane for site "${slug}" failed schema validation: ` +
355
+ validated.error.message,
356
+ skipReason: 'apply-failed',
357
+ };
358
+ }
359
+ commitLaneConfig(ctx.projectRoot, slug, validated.data, 'create');
360
+ await appendJournalEvent(ctx.projectRoot, {
361
+ kind: 'lane-migration',
362
+ at: new Date().toISOString(),
363
+ migration: 'lane-from-legacy-site',
364
+ source: `sites.${slug}`,
365
+ target: `lanes.${slug}`,
366
+ details: {
367
+ legacySiteId: slug,
368
+ scaffoldMarkdown: site.contentDir,
369
+ ...(site.host !== undefined ? { host: site.host } : {}),
370
+ ...(site.redirectsPath !== undefined
371
+ ? { redirectsPath: site.redirectsPath }
372
+ : {}),
373
+ },
374
+ });
375
+ lanesCreated.push(slug);
376
+ }
377
+
378
+ // Step 2 — backfill unambiguous entry artifactPaths (ambiguity-halt).
379
+ const backfill = await backfillFromLegacySites(ctx.projectRoot, bases);
380
+
381
+ // Step 3 — drop the sites block from the config.
382
+ const dropped = dropSitesBlock(ctx.projectRoot);
383
+ if (dropped) {
384
+ await appendJournalEvent(ctx.projectRoot, {
385
+ kind: 'lane-migration',
386
+ at: new Date().toISOString(),
387
+ migration: 'drop-sites-block',
388
+ source: '.deskwork/config.json#sites',
389
+ target: '.deskwork/config.json',
390
+ details: {
391
+ lanesCreated: lanesCreated.length,
392
+ entriesStamped: backfill.stamped.length,
393
+ entriesAmbiguous: backfill.ambiguous.length,
394
+ },
395
+ });
396
+ }
397
+
398
+ const ambiguousNote =
399
+ backfill.ambiguous.length > 0
400
+ ? ` ${backfill.ambiguous.length} entry(ies) refused as ambiguous ` +
401
+ `(reported separately as migration-ambiguous).`
402
+ : '';
403
+
404
+ return {
405
+ finding: plan.finding,
406
+ applied: true,
407
+ message:
408
+ `migrated: ${lanesCreated.length} lane(s) created, ` +
409
+ `${backfill.stamped.length} entry artifactPath(s) backfilled, ` +
410
+ `sites block ${dropped ? 'dropped' : 'already absent'}.${ambiguousNote}`,
411
+ details: {
412
+ lanesCreated,
413
+ entriesStamped: backfill.stamped,
414
+ entriesAmbiguous: backfill.ambiguous.map((a) => a.entryUuid),
415
+ sitesDropped: dropped,
416
+ },
417
+ };
418
+ } catch (err) {
419
+ const reason = err instanceof Error ? err.message : String(err);
420
+ return {
421
+ finding: plan.finding,
422
+ applied: false,
423
+ message: `sites-to-lanes migration failed: ${reason}`,
424
+ skipReason: 'apply-failed',
425
+ };
426
+ }
427
+ },
428
+ };
429
+
430
+ function ambiguousFinding(site: string, amb: AmbiguousBackfill): Finding {
431
+ return {
432
+ ruleId: AMBIGUOUS_RULE_ID,
433
+ site,
434
+ severity: 'error',
435
+ message:
436
+ `Entry ${amb.entryUuid} (slug=${amb.slug}, stage=${amb.stage}) resolves to ` +
437
+ `${amb.candidates.length} candidate files across legacy site contentDirs ` +
438
+ `(${amb.candidates.join(', ')}). The migration refuses to stamp an ` +
439
+ `ambiguous artifactPath — disambiguate manually and re-run.`,
440
+ details: {
441
+ entryUuid: amb.entryUuid,
442
+ slug: amb.slug,
443
+ stage: amb.stage,
444
+ candidates: amb.candidates,
445
+ },
446
+ };
447
+ }
448
+
449
+ export default rule;
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-stale.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AA6CrB,QAAA,MAAM,IAAI,EAAE,UA+EX,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"workflow-stale.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAiDrB,QAAA,MAAM,IAAI,EAAE,UA+EX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -19,8 +19,11 @@ import { join } from 'node:path';
19
19
  import { pipelinePath } from "../../review/pipeline.js";
20
20
  const RULE_ID = 'workflow-stale';
21
21
  function isStale(workflow, ctx) {
22
- if (workflow.site !== ctx.site)
23
- return false;
22
+ // Phase 39c (sites→lanes retirement): the doctor runs a single
23
+ // project pass and reconciles every workflow against the single
24
+ // project calendar. The legacy per-site filter (`workflow.site !==
25
+ // ctx.site`) is retired — `ctx.site` is now the `PROJECT_SCOPE`
26
+ // sentinel, never a real site slug.
24
27
  if (workflow.state === 'applied' || workflow.state === 'cancelled') {
25
28
  return false;
26
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-stale.js","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workflow-stale.js","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,11 @@ function isStale(
33
33
  workflow: DraftWorkflowItem,
34
34
  ctx: DoctorContext,
35
35
  ): boolean {
36
- if (workflow.site !== ctx.site) return false;
36
+ // Phase 39c (sites→lanes retirement): the doctor runs a single
37
+ // project pass and reconciles every workflow against the single
38
+ // project calendar. The legacy per-site filter (`workflow.site !==
39
+ // ctx.site`) is retired — `ctx.site` is now the `PROJECT_SCOPE`
40
+ // sentinel, never a real site slug.
37
41
  if (workflow.state === 'applied' || workflow.state === 'cancelled') {
38
42
  return false;
39
43
  }
@@ -38,11 +38,25 @@ export declare function parseFixArgument(arg: string): string[];
38
38
  export interface DoctorRunOptions {
39
39
  projectRoot: string;
40
40
  config: DeskworkConfig;
41
- /** Restrict to one site; undefined = run for every site in config. */
41
+ /**
42
+ * Phase 39c (sites→lanes retirement): the doctor runs a SINGLE
43
+ * project-scoped pass. `site` is retained on the options shape (the
44
+ * `--site` CLI flag still parses) but it no longer scopes the run —
45
+ * the doctor operates on the project's sidecar set, calendar, and
46
+ * workflow store as a whole.
47
+ */
42
48
  site?: string;
43
49
  /** Restrict the rule set; undefined = all rules. */
44
50
  ruleIds?: string[];
45
51
  }
52
+ /**
53
+ * The single project scope label. Phase 39c collapsed the doctor's
54
+ * per-site loop (location is no longer the identifying axis — the
55
+ * sidecar set is the source of truth) into one project pass. Findings
56
+ * carry this as their `site` label so existing consumers that group by
57
+ * `finding.site` still see a stable key.
58
+ */
59
+ export declare const PROJECT_SCOPE = "project";
46
60
  /**
47
61
  * Audit: collect findings without mutating the world. Returns a fully-
48
62
  * built report with empty `repairs`. Suitable for pre-commit hooks
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAcnD,OAAO,KAAK,EAEV,iBAAiB,EACjB,YAAY,EACZ,UAAU,EAIX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,UAAU,CAa3C,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtD;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAwED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAavB;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAoBvB;AA+FD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,iBAU5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAOhC,CAAC"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAenD,OAAO,KAAK,EAEV,iBAAiB,EACjB,YAAY,EACZ,UAAU,EAIX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,UAAU,CAc3C,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtD;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,YAAY,CAAC;AA+EvC;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAQvB;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAcvB;AA+FD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,iBAU5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAOhC,CAAC"}