@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,174 @@
1
+ /**
2
+ * Migration-only tolerant reader for the legacy `config.sites` block
3
+ * (Phase 39b; finalized in 39c as the sole home for `SiteConfig`).
4
+ *
5
+ * Per the sites→lanes retirement spec §"Migration":
6
+ *
7
+ * > `sites` reads are tolerated *only* inside this migration. After it
8
+ * > runs, nothing reads `sites`.
9
+ *
10
+ * This module is that tolerated read surface. It parses the on-disk
11
+ * `.deskwork/config.json` directly and extracts just the fields the
12
+ * migration needs (`sites.<id>.{contentDir, calendarPath, host}`),
13
+ * WITHOUT going through `parseConfig`. `parseConfig` strips the legacy
14
+ * fields the migration consumes (it surfaces only the typed `SiteConfig`
15
+ * subset and, post-migration, tolerates an absent `sites` by normalizing
16
+ * it to `{}` — AUDIT-20260603-11), so the migration reads the raw JSON
17
+ * directly to see the legacy block verbatim. It must read both shapes:
18
+ *
19
+ * - pre-migration (`sites` present) → returns the legacy sites map.
20
+ * - post-migration (`sites` absent) → returns an EMPTY map, the
21
+ * "nothing to migrate" signal the rule's idempotency relies on.
22
+ *
23
+ * 39b keeps the field present in the live schema; this reader exists so
24
+ * the migration does not depend on the live schema's required-`sites`
25
+ * invariant. 39c moves `SiteConfig` here and removes the live-schema
26
+ * `sites` field; until then this is a focused, additive read helper.
27
+ *
28
+ * Sibling-relative imports per the doctor convention (`@/` does not
29
+ * resolve under tsx at runtime in this package's `src/`).
30
+ */
31
+ import { readFileSync, writeFileSync } from 'node:fs';
32
+ import { configPath } from "../config.js";
33
+ /** Narrow an unknown to a string-keyed record (object, not null/array). */
34
+ function isRecord(value) {
35
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
36
+ }
37
+ /** Read a non-empty string property from a record (undefined otherwise). */
38
+ function readString(obj, key) {
39
+ const v = obj[key];
40
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
41
+ }
42
+ /**
43
+ * Read the legacy `sites` block from `<projectRoot>/.deskwork/config.json`
44
+ * tolerantly.
45
+ *
46
+ * - Config file ABSENT (ENOENT — never installed, or a bare fixture) →
47
+ * returns an empty map. A project with no config has no legacy sites
48
+ * to migrate; the rule no-ops. This mirrors `bootstrap.ts`'s
49
+ * "no-config → nothing to do" branch and keeps the migration rule
50
+ * safe to run unconditionally across the doctor's per-site loop.
51
+ * - Config PRESENT but unreadable (non-ENOENT I/O) / not valid JSON →
52
+ * throws. A config that exists but is broken should surface loudly
53
+ * where the operator can fix it (AUDIT-20260530-10 precedent;
54
+ * silently treating it as "no sites" would mask a real config-side
55
+ * problem — the project's "no fallbacks" rule).
56
+ * - Config present but no `sites` block (the post-migration shape) →
57
+ * returns an empty map. This is the idempotency signal: nothing to
58
+ * migrate.
59
+ * - `sites` present → returns each site's `{ contentDir, calendarPath,
60
+ * host, redirectsPath }`. A site missing a non-empty `contentDir`
61
+ * throws (the migration cannot derive `scaffoldDefaults` without it).
62
+ *
63
+ * @throws when a PRESENT config is unreadable / not JSON, or a declared
64
+ * site lacks a non-empty `contentDir`.
65
+ */
66
+ export function readLegacySites(projectRoot) {
67
+ const path = configPath(projectRoot);
68
+ let raw;
69
+ try {
70
+ raw = readFileSync(path, 'utf8');
71
+ }
72
+ catch (err) {
73
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
74
+ // No config on disk — no legacy sites. Nothing to migrate.
75
+ return { sites: new Map() };
76
+ }
77
+ const reason = err instanceof Error ? err.message : String(err);
78
+ throw new Error(`sites-to-lanes migration: could not read ${path}: ${reason}. ` +
79
+ `The migration needs the config to read the legacy "sites" block.`);
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(raw);
84
+ }
85
+ catch (err) {
86
+ const reason = err instanceof Error ? err.message : String(err);
87
+ throw new Error(`sites-to-lanes migration: invalid JSON in ${path}: ${reason}.`);
88
+ }
89
+ if (!isRecord(parsed)) {
90
+ throw new Error(`sites-to-lanes migration: ${path} is not a JSON object.`);
91
+ }
92
+ const obj = parsed;
93
+ const sites = new Map();
94
+ const sitesValue = obj.sites;
95
+ if (!isRecord(sitesValue)) {
96
+ // No `sites` block (post-migration shape) — nothing to migrate.
97
+ return { sites };
98
+ }
99
+ for (const slug of Object.keys(sitesValue)) {
100
+ const siteObj = sitesValue[slug];
101
+ if (!isRecord(siteObj)) {
102
+ throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} is not an object.`);
103
+ }
104
+ const contentDir = readString(siteObj, 'contentDir');
105
+ if (contentDir === undefined) {
106
+ throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} is missing a ` +
107
+ `non-empty "contentDir"; the migration cannot derive scaffoldDefaults ` +
108
+ `without it. Repair the config and re-run.`);
109
+ }
110
+ const calendarPath = readString(siteObj, 'calendarPath');
111
+ const host = readString(siteObj, 'host');
112
+ // AUDIT-20260604-11: a present-but-invalid `redirectsPath` (non-string
113
+ // or empty) must NOT be silently treated as absent — `readString` would
114
+ // omit it, and the migration's later `dropSitesBlock` would then discard
115
+ // a malformed-but-present value with no signal. Reject it loudly, mirroring
116
+ // the live `parseSiteConfig` rule (config.ts), so the operator repairs it.
117
+ // (Single read — `readString` is pure; reuse the result for the guard and
118
+ // the assignment.)
119
+ const redirectsPath = readString(siteObj, 'redirectsPath');
120
+ if ('redirectsPath' in siteObj && redirectsPath === undefined) {
121
+ throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} has an invalid ` +
122
+ `"redirectsPath" (must be a non-empty string when set). Repair the ` +
123
+ `config and re-run.`);
124
+ }
125
+ const site = {
126
+ contentDir,
127
+ ...(calendarPath !== undefined ? { calendarPath } : {}),
128
+ ...(host !== undefined ? { host } : {}),
129
+ ...(redirectsPath !== undefined ? { redirectsPath } : {}),
130
+ };
131
+ sites.set(slug, site);
132
+ }
133
+ return { sites };
134
+ }
135
+ /**
136
+ * Rewrite `<projectRoot>/.deskwork/config.json` with the `sites` and
137
+ * `defaultSite` keys removed (migration step 3 — "drop sites"). Every
138
+ * other top-level key is preserved verbatim. A no-op when neither key
139
+ * is present (idempotent).
140
+ *
141
+ * Returns `true` when the file was rewritten (a key was dropped),
142
+ * `false` when there was nothing to drop.
143
+ *
144
+ * Note: `parseConfig` tolerates the resulting `sites`-less config
145
+ * (AUDIT-20260603-11 — an absent/empty `sites` normalizes to `{}`), so a
146
+ * migrated project still loads through `readConfig` and every
147
+ * config-reading command keeps working between 39b and 39c. 39c removes
148
+ * the `sites` field from the schema entirely; until then the migration's
149
+ * own `sites` reads go through `readLegacySites` (this module), which
150
+ * does not depend on the live schema's shape.
151
+ */
152
+ export function dropSitesBlock(projectRoot) {
153
+ const path = configPath(projectRoot);
154
+ const raw = readFileSync(path, 'utf8');
155
+ const parsed = JSON.parse(raw);
156
+ if (!isRecord(parsed)) {
157
+ throw new Error(`sites-to-lanes migration: ${path} is not a JSON object; cannot drop sites.`);
158
+ }
159
+ const obj = parsed;
160
+ const hadSites = Object.prototype.hasOwnProperty.call(obj, 'sites');
161
+ const hadDefault = Object.prototype.hasOwnProperty.call(obj, 'defaultSite');
162
+ if (!hadSites && !hadDefault) {
163
+ return false;
164
+ }
165
+ const next = {};
166
+ for (const key of Object.keys(obj)) {
167
+ if (key === 'sites' || key === 'defaultSite')
168
+ continue;
169
+ next[key] = obj[key];
170
+ }
171
+ writeFileSync(path, JSON.stringify(next, null, 2) + '\n', 'utf8');
172
+ return true;
173
+ }
174
+ //# sourceMappingURL=legacy-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-config.js","sourceRoot":"","sources":["../../src/doctor/legacy-config.ts"],"names":[],"mappings":"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"}
@@ -1,31 +1,22 @@
1
1
  /**
2
2
  * Project-scope gate for doctor rules.
3
3
  *
4
- * The doctor runner invokes `audit()` once per configured site. Rules
5
- * whose target lives at the PROJECT scope (under `<projectRoot>/.deskwork/`
6
- * regardless of site count lane configs, sidecars, the journal) need
7
- * to emit findings once, not N times. The convention this module
8
- * captures: project-scoped rules early-return when the current site is
9
- * not the FIRST site in `ctx.config.sites` (Object.keys insertion order).
10
- * Single-site projects (the overwhelming majority) trip the guard on
11
- * their only site; multi-site projects trip it on the first site listed
12
- * in the config and skip the remainder.
13
- *
14
- * The alternative — a dedicated project-scope abstraction in the runner
15
- * — would let project-scoped rules opt out of the per-site loop
16
- * entirely. Until that abstraction lands, this helper is the agreed
17
- * shape; extracted here so multiple rules consuming the pattern share a
18
- * single named definition rather than duplicating the body.
4
+ * Phase 39c (sites→lanes retirement): the doctor runner no longer loops
5
+ * per configured site it runs a SINGLE project-scoped pass (see
6
+ * `runner.ts` `PROJECT_SCOPE`). Project-scoped rules (lane configs,
7
+ * sidecars, the journal) therefore already run exactly once. This gate
8
+ * is now a constant `true`; it is retained (rather than ripped out of
9
+ * every rule body) so the rules that consume it keep their shape and
10
+ * the single-pass guarantee is documented in one place.
19
11
  *
20
12
  * Sibling-relative imports per the project convention.
21
13
  */
22
14
  import type { DoctorContext } from './types.ts';
23
15
  /**
24
- * Returns `true` when the current site is the "first" site per the
25
- * config's `Object.keys` insertion order the conventional signal
26
- * that a project-scoped rule should run during the current per-site
27
- * iteration. Empty `sites` collection returns `true` (degenerate
28
- * single-pass case so the rule still runs).
16
+ * Phase 39c: the runner makes a single project pass, so a project-scoped
17
+ * rule always runs exactly once. The legacy "first site in
18
+ * `Object.keys(config.sites)`" check is retired with the per-site loop;
19
+ * this now unconditionally admits the single pass.
29
20
  */
30
- export declare function isFirstSite(ctx: DoctorContext): boolean;
21
+ export declare function isFirstSite(_ctx: DoctorContext): boolean;
31
22
  //# sourceMappingURL=project-scope-gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-scope-gate.d.ts","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAIvD"}
1
+ {"version":3,"file":"project-scope-gate.d.ts","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAExD"}
@@ -1,35 +1,23 @@
1
1
  /**
2
2
  * Project-scope gate for doctor rules.
3
3
  *
4
- * The doctor runner invokes `audit()` once per configured site. Rules
5
- * whose target lives at the PROJECT scope (under `<projectRoot>/.deskwork/`
6
- * regardless of site count lane configs, sidecars, the journal) need
7
- * to emit findings once, not N times. The convention this module
8
- * captures: project-scoped rules early-return when the current site is
9
- * not the FIRST site in `ctx.config.sites` (Object.keys insertion order).
10
- * Single-site projects (the overwhelming majority) trip the guard on
11
- * their only site; multi-site projects trip it on the first site listed
12
- * in the config and skip the remainder.
13
- *
14
- * The alternative — a dedicated project-scope abstraction in the runner
15
- * — would let project-scoped rules opt out of the per-site loop
16
- * entirely. Until that abstraction lands, this helper is the agreed
17
- * shape; extracted here so multiple rules consuming the pattern share a
18
- * single named definition rather than duplicating the body.
4
+ * Phase 39c (sites→lanes retirement): the doctor runner no longer loops
5
+ * per configured site it runs a SINGLE project-scoped pass (see
6
+ * `runner.ts` `PROJECT_SCOPE`). Project-scoped rules (lane configs,
7
+ * sidecars, the journal) therefore already run exactly once. This gate
8
+ * is now a constant `true`; it is retained (rather than ripped out of
9
+ * every rule body) so the rules that consume it keep their shape and
10
+ * the single-pass guarantee is documented in one place.
19
11
  *
20
12
  * Sibling-relative imports per the project convention.
21
13
  */
22
14
  /**
23
- * Returns `true` when the current site is the "first" site per the
24
- * config's `Object.keys` insertion order the conventional signal
25
- * that a project-scoped rule should run during the current per-site
26
- * iteration. Empty `sites` collection returns `true` (degenerate
27
- * single-pass case so the rule still runs).
15
+ * Phase 39c: the runner makes a single project pass, so a project-scoped
16
+ * rule always runs exactly once. The legacy "first site in
17
+ * `Object.keys(config.sites)`" check is retired with the per-site loop;
18
+ * this now unconditionally admits the single pass.
28
19
  */
29
- export function isFirstSite(ctx) {
30
- const siteIds = Object.keys(ctx.config.sites);
31
- if (siteIds.length === 0)
32
- return true;
33
- return siteIds[0] === ctx.site;
20
+ export function isFirstSite(_ctx) {
21
+ return true;
34
22
  }
35
23
  //# sourceMappingURL=project-scope-gate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-scope-gate.js","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAkB;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"project-scope-gate.js","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAmB;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -5,5 +5,16 @@ export interface RepairResult {
5
5
  applied: string[];
6
6
  pendingDestructive: string[];
7
7
  }
8
+ /**
9
+ * Regenerate `calendar.md` from sidecars (the SSOT).
10
+ *
11
+ * Phase 39d (sites→lanes retirement): the runtime `artifactPath`
12
+ * backfiller that used to live here is REMOVED. Backfilling missing
13
+ * `artifactPath` is owned exclusively by the 39b migration
14
+ * (`sites-migration-backfill.ts`), which enumerates candidates across
15
+ * legacy content dirs and halts on ambiguity. `repair.ts` no longer
16
+ * derives a path from the slug+stage heuristic — it reads stored paths
17
+ * only and regenerates the calendar projection.
18
+ */
8
19
  export declare function repairAll(projectRoot: string, opts: RepairOptions): Promise<RepairResult>;
9
20
  //# sourceMappingURL=repair.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAsED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAoD/F"}
1
+ {"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8B/F"}
@@ -1,76 +1,21 @@
1
- import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
2
- import { join, dirname, relative } from 'node:path';
1
+ import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
3
  import { sidecarsDir } from "../sidecar/paths.js";
4
4
  import { EntrySchema } from "../schema/entry.js";
5
5
  import { readConfig } from "../config.js";
6
6
  import { resolveCalendarPath } from "../paths.js";
7
7
  import { renderCalendar } from "../calendar/render.js";
8
8
  /**
9
- * Stage-conventional artifact path. Mirrors `artifactPathForStage` in
10
- * validate.ts (kept here to avoid a cross-import; both files reach for
11
- * the same canonical mapping). Returns null for stages with no on-disk
12
- * artifact, including stages outside the editorial pipeline (Phase 3
13
- * / Phase 4 — lane-aware path conventions land in lane code).
14
- */
15
- function artifactPathForStage(projectRoot, slug, stage) {
16
- switch (stage) {
17
- case 'Ideas':
18
- return join(projectRoot, 'docs', slug, 'scrapbook', 'idea.md');
19
- case 'Planned':
20
- return join(projectRoot, 'docs', slug, 'scrapbook', 'plan.md');
21
- case 'Outlining':
22
- return join(projectRoot, 'docs', slug, 'scrapbook', 'outline.md');
23
- case 'Drafting':
24
- case 'Final':
25
- case 'Published':
26
- return join(projectRoot, 'docs', slug, 'index.md');
27
- case 'Blocked':
28
- case 'Cancelled':
29
- return null;
30
- default:
31
- // Lane-specific or unrecognized stage; no editorial-default path
32
- // applies. Phase 4 introduces template-driven path resolution.
33
- return null;
34
- }
35
- }
36
- async function fileExists(path) {
37
- try {
38
- await stat(path);
39
- return true;
40
- }
41
- catch {
42
- return false;
43
- }
44
- }
45
- /**
46
- * #182 Phase 34 ship-pass — backfill `artifactPath` on sidecars that
47
- * lack the field but where the slug+stage heuristic resolves to a real
48
- * file. The validator's `missing-artifact-path` rule surfaces the gap;
49
- * this writer fixes it.
9
+ * Regenerate `calendar.md` from sidecars (the SSOT).
50
10
  *
51
- * Mutates each affected sidecar and returns a list of changed entry
52
- * UUIDs. Idempotent: re-running on already-fixed entries is a no-op.
11
+ * Phase 39d (sites→lanes retirement): the runtime `artifactPath`
12
+ * backfiller that used to live here is REMOVED. Backfilling missing
13
+ * `artifactPath` is owned exclusively by the 39b migration
14
+ * (`sites-migration-backfill.ts`), which enumerates candidates across
15
+ * legacy content dirs and halts on ambiguity. `repair.ts` no longer
16
+ * derives a path from the slug+stage heuristic — it reads stored paths
17
+ * only and regenerates the calendar projection.
53
18
  */
54
- async function backfillArtifactPaths(projectRoot, entries, sidecarPaths) {
55
- const changed = [];
56
- for (const entry of entries) {
57
- if (entry.artifactPath !== undefined && entry.artifactPath !== '')
58
- continue;
59
- const heuristicAbs = artifactPathForStage(projectRoot, entry.slug, entry.currentStage);
60
- if (!heuristicAbs)
61
- continue;
62
- if (!(await fileExists(heuristicAbs)))
63
- continue;
64
- const sidecarPath = sidecarPaths.get(entry.uuid);
65
- if (!sidecarPath)
66
- continue;
67
- const relPath = relative(projectRoot, heuristicAbs);
68
- const updated = { ...entry, artifactPath: relPath };
69
- await writeFile(sidecarPath, JSON.stringify(updated, null, 2));
70
- changed.push(entry.uuid);
71
- }
72
- return changed;
73
- }
74
19
  export async function repairAll(projectRoot, opts) {
75
20
  void opts;
76
21
  const result = { applied: [], pendingDestructive: [] };
@@ -84,7 +29,6 @@ export async function repairAll(projectRoot, opts) {
84
29
  return result;
85
30
  }
86
31
  const entries = [];
87
- const sidecarPaths = new Map();
88
32
  for (const name of names.filter(n => n.endsWith('.json'))) {
89
33
  const sidecarPath = join(dir, name);
90
34
  const raw = await readFile(sidecarPath, 'utf8');
@@ -92,33 +36,10 @@ export async function repairAll(projectRoot, opts) {
92
36
  const parsed = EntrySchema.safeParse(JSON.parse(raw));
93
37
  if (parsed.success) {
94
38
  entries.push(parsed.data);
95
- sidecarPaths.set(parsed.data.uuid, sidecarPath);
96
39
  }
97
40
  }
98
41
  catch { /* skip malformed */ }
99
42
  }
100
- // #182 — backfill missing artifactPath BEFORE regenerating calendar
101
- // so the calendar render uses the freshly-stamped paths if it
102
- // consumes them downstream.
103
- const backfilled = await backfillArtifactPaths(projectRoot, entries, sidecarPaths);
104
- if (backfilled.length > 0) {
105
- // Re-load the entries so the calendar render sees the stamped
106
- // paths (the in-memory `entries` array is now stale for those
107
- // UUIDs).
108
- for (let i = 0; i < entries.length; i++) {
109
- const e = entries[i];
110
- if (!e || !backfilled.includes(e.uuid))
111
- continue;
112
- const sidecarPath = sidecarPaths.get(e.uuid);
113
- if (!sidecarPath)
114
- continue;
115
- const raw = await readFile(sidecarPath, 'utf8');
116
- const parsed = EntrySchema.safeParse(JSON.parse(raw));
117
- if (parsed.success)
118
- entries[i] = parsed.data;
119
- }
120
- result.applied.push(`artifact-path-backfilled (${backfilled.length} entrie(s))`);
121
- }
122
43
  const md = renderCalendar(entries);
123
44
  // #232: honor the configured per-site calendarPath (default site) rather
124
45
  // than the hardcoded `.deskwork/calendar.md`, consistent with regenerateCalendar.
@@ -1 +1 @@
1
- {"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAc,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAWvD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAmB,EAAE,IAAmB;IACtE,KAAK,IAAI,CAAC;IACV,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC;IAErE,uCAAuC;IACvC,MAAM,GAAG,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC;QAAC,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,MAAM,CAAC;IAAC,CAAC;IAE5D,MAAM,OAAO,GAAY,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACnC,yEAAyE;IACzE,kFAAkF;IAClF,MAAM,YAAY,GAAG,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAClC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAE5C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -18,8 +18,13 @@ interface DuplicateGroup {
18
18
  }
19
19
  /**
20
20
  * Group `index.byPath` (relPath → uuid) by uuid, return only groups
21
- * with more than one file. Caller resolves relative paths against
22
- * the site's contentDir to get absolute paths for repair.
21
+ * with more than one file.
22
+ *
23
+ * Phase 39c (sites→lanes retirement): the doctor's content index is now
24
+ * built by `buildContentIndexFromSidecars`, whose `byPath` keys are
25
+ * PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
26
+ * contentDir-relative. So we reconstruct absolute paths by joining
27
+ * against `projectRoot` — there is no per-site `contentDir` axis.
23
28
  */
24
29
  export declare function findDuplicateGroups(ctx: DoctorContext): DuplicateGroup[];
25
30
  declare const rule: DoctorRule;
@@ -1 +1 @@
1
- {"version":3,"file":"duplicate-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EAIX,MAAM,aAAa,CAAC;AAyBrB,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,GACjB,cAAc,EAAE,CAgBlB;AAED,QAAA,MAAM,IAAI,EAAE,UA2FX,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"duplicate-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EAIX,MAAM,aAAa,CAAC;AAyBrB,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,GACjB,cAAc,EAAE,CAelB;AAED,QAAA,MAAM,IAAI,EAAE,UA2FX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -13,7 +13,6 @@
13
13
  import { readFileSync, writeFileSync } from 'node:fs';
14
14
  import { join, relative } from 'node:path';
15
15
  import { parseFrontmatter, removeFrontmatterPaths } from "../../frontmatter.js";
16
- import { resolveContentDir } from "../../paths.js";
17
16
  const RULE_ID = 'duplicate-id';
18
17
  /**
19
18
  * Clear the `deskwork.id` field from a markdown file. Returns true when
@@ -40,14 +39,18 @@ function clearFrontmatterId(absPath) {
40
39
  }
41
40
  /**
42
41
  * Group `index.byPath` (relPath → uuid) by uuid, return only groups
43
- * with more than one file. Caller resolves relative paths against
44
- * the site's contentDir to get absolute paths for repair.
42
+ * with more than one file.
43
+ *
44
+ * Phase 39c (sites→lanes retirement): the doctor's content index is now
45
+ * built by `buildContentIndexFromSidecars`, whose `byPath` keys are
46
+ * PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
47
+ * contentDir-relative. So we reconstruct absolute paths by joining
48
+ * against `projectRoot` — there is no per-site `contentDir` axis.
45
49
  */
46
50
  export function findDuplicateGroups(ctx) {
47
- const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
48
51
  const byUuid = new Map();
49
52
  for (const [relPath, uuid] of ctx.index.byPath) {
50
- const abs = join(contentDir, relPath);
53
+ const abs = join(ctx.projectRoot, relPath);
51
54
  const list = byUuid.get(uuid);
52
55
  if (list)
53
56
  list.push(abs);
@@ -1 +1 @@
1
- {"version":3,"file":"duplicate-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"duplicate-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,6 @@
14
14
  import { readFileSync, writeFileSync } from 'node:fs';
15
15
  import { join, relative } from 'node:path';
16
16
  import { parseFrontmatter, removeFrontmatterPaths } from '../../frontmatter.ts';
17
- import { resolveContentDir } from '../../paths.ts';
18
17
  import type {
19
18
  DoctorContext,
20
19
  DoctorRule,
@@ -54,16 +53,20 @@ interface DuplicateGroup {
54
53
 
55
54
  /**
56
55
  * Group `index.byPath` (relPath → uuid) by uuid, return only groups
57
- * with more than one file. Caller resolves relative paths against
58
- * the site's contentDir to get absolute paths for repair.
56
+ * with more than one file.
57
+ *
58
+ * Phase 39c (sites→lanes retirement): the doctor's content index is now
59
+ * built by `buildContentIndexFromSidecars`, whose `byPath` keys are
60
+ * PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
61
+ * contentDir-relative. So we reconstruct absolute paths by joining
62
+ * against `projectRoot` — there is no per-site `contentDir` axis.
59
63
  */
60
64
  export function findDuplicateGroups(
61
65
  ctx: DoctorContext,
62
66
  ): DuplicateGroup[] {
63
- const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
64
67
  const byUuid = new Map<string, string[]>();
65
68
  for (const [relPath, uuid] of ctx.index.byPath) {
66
- const abs = join(contentDir, relPath);
69
+ const abs = join(ctx.projectRoot, relPath);
67
70
  const list = byUuid.get(uuid);
68
71
  if (list) list.push(abs);
69
72
  else byUuid.set(uuid, [abs]);
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-top-level-id-migration.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/legacy-top-level-id-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAsGrB;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM/E;AAED,QAAA,MAAM,IAAI,EAAE,UAqGX,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"legacy-top-level-id-migration.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/legacy-top-level-id-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAsGrB;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM/E;AAED,QAAA,MAAM,IAAI,EAAE,UAoGX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -27,7 +27,7 @@
27
27
  */
28
28
  import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
29
29
  import { extname, join, relative } from 'node:path';
30
- import { resolveContentDir } from "../../paths.js";
30
+ import { collectSidecarArtifactDirs } from "../../content-index.js";
31
31
  import { parseFrontmatter, removeFrontmatterPaths, updateFrontmatter, } from "../../frontmatter.js";
32
32
  const RULE_ID = 'legacy-top-level-id-migration';
33
33
  const MARKDOWN_EXTENSIONS = new Set([
@@ -139,19 +139,22 @@ const rule = {
139
139
  id: RULE_ID,
140
140
  label: 'Frontmatter id at top level should be under `deskwork:` namespace',
141
141
  async audit(ctx) {
142
- const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
142
+ // Phase 39c (sites→lanes retirement): discovery is sidecar-driven —
143
+ // walk each entry's resolved artifact directory rather than a
144
+ // configured site `contentDir`. The sidecar is the SSOT; legacy
145
+ // top-level-id files live alongside the entries that reference them.
146
+ const roots = await collectSidecarArtifactDirs(ctx.projectRoot);
143
147
  const calendarIds = new Set();
144
148
  for (const e of ctx.calendar.entries) {
145
149
  if (e.id)
146
150
  calendarIds.add(e.id);
147
151
  }
148
- let files;
149
- try {
150
- files = collectMarkdownFiles(contentDir);
151
- }
152
- catch {
153
- return [];
152
+ const seen = new Set();
153
+ for (const root of roots) {
154
+ for (const abs of collectMarkdownFiles(root))
155
+ seen.add(abs);
154
156
  }
157
+ const files = [...seen].sort();
155
158
  const findings = [];
156
159
  for (const abs of files) {
157
160
  const hit = classify(abs, calendarIds);