@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,385 @@
1
+ /**
2
+ * Bird's-eye content-tree builder (Phase 16d, fs-inverted in v0.6.0 / #24,
3
+ * fs-keyed in Phase 19c / #33).
4
+ *
5
+ * Derives a tree-of-nodes representation from the host project's
6
+ * filesystem, with the editorial calendar overlaid as a state layer.
7
+ *
8
+ * - The **filesystem walk** is the primary structure source. Every
9
+ * directory under `<contentDir>/` becomes a node, keyed by its
10
+ * fs-relative path (e.g. `projects/the-outbound`).
11
+ * - The **calendar** is the state overlay. A calendar entry whose
12
+ * `id` matches an fs node's frontmatter `id:` (via the content
13
+ * index) sets the node's lane (its lifecycle stage), display
14
+ * title, AND the `slug` display attribute (the entry's
15
+ * host-rendering-engine slug, used by the studio for the
16
+ * "public URL: /blog/<slug>" hover hint).
17
+ *
18
+ * Read-only — never mutates the calendar or the filesystem. Callers
19
+ * cache the result for the lifetime of a single request unless the
20
+ * caller knows the underlying data has changed.
21
+ *
22
+ * Inversion rationale (#33): pre-19c the tree keyed nodes by **slug**.
23
+ * This worked for audiocontrol's flat layout where slug == fs path,
24
+ * but broke for writingcontrol where slug `the-outbound` (the
25
+ * Astro-derived public URL) is unrelated to the file's path
26
+ * (`projects/the-outbound/index.md`). The ghost-root bug came from
27
+ * union-by-slug producing a calendar-only tree at slug
28
+ * `the-outbound` plus a separate untracked tree under `projects/`
29
+ * that never merged.
30
+ *
31
+ * Now: tree placement is filesystem-driven. Each fs node carries the
32
+ * relative path (e.g. `projects/the-outbound`). Calendar entries
33
+ * overlay state onto fs nodes by matching the entry's `id` against
34
+ * the file's frontmatter `id:` via `buildContentIndex`. Slug stops
35
+ * being load-bearing structurally and becomes a display attribute.
36
+ *
37
+ * Legacy slug-fallback (intentional, for pre-doctor entries): when a
38
+ * calendar entry's id isn't found in the content index (its file
39
+ * hasn't been bound to frontmatter yet), the assembly looks for an
40
+ * fs node whose path equals the entry's slug. If found → overlay
41
+ * with a one-time warning hinting at `deskwork doctor`. If not found
42
+ * → place the entry as a ghost node (preserving today's behavior
43
+ * for entries with neither id-binding nor a path-shaped slug).
44
+ * This is NOT a "fallback for missing functionality" in the project
45
+ * rule's sense — it's a deliberate transitional path that the doctor
46
+ * command resolves operator-side.
47
+ */
48
+ import { buildContentIndex } from "./content-index.js";
49
+ import { listScrapbook } from "./scrapbook.js";
50
+ import { resolveContentDir } from "./paths.js";
51
+ import { defaultFsWalk } from "./content-tree-fs-walk.js";
52
+ import { ancestorsOf, entryHasOwnIndex, findIdBoundPath, idBoundFile, leafOfPath, pickLatestMtime, rootSegment, } from "./content-tree-helpers.js";
53
+ // Re-export the fs-walk + types so external callers (tests, studio) keep
54
+ // importing from `content-tree.ts` as today — the split is internal.
55
+ export { defaultFsWalk } from "./content-tree-fs-walk.js";
56
+ // ---------------------------------------------------------------------------
57
+ // Legacy slug-fallback warning de-duplication
58
+ // ---------------------------------------------------------------------------
59
+ //
60
+ // `buildContentTree` is called on every studio render (the dashboard polls
61
+ // at ~10s intervals). Pre-doctor entries that legitimately fall through to
62
+ // slug-as-path matching would otherwise emit the same warning hundreds of
63
+ // times an hour. Track which (site, entryId|slug) pairs have already
64
+ // warned this process and skip subsequent warnings.
65
+ //
66
+ // The Set lives at module scope on purpose — it intentionally outlives any
67
+ // single request. Tests that need fresh warning behavior should call the
68
+ // `__resetLegacyFallbackWarnings()` helper below in `beforeEach`.
69
+ const WARNED_LEGACY_FALLBACK = new Set();
70
+ /**
71
+ * Test-only: clear the legacy slug-fallback warning de-dup set so tests
72
+ * that exercise warning behavior can do so independently. Not part of the
73
+ * public API — exposed only because process-level state is awkward to
74
+ * reset from inside tests without a hook.
75
+ */
76
+ export function __resetLegacyFallbackWarnings() {
77
+ WARNED_LEGACY_FALLBACK.clear();
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Build
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Build the content-tree projects for one site. Pure data — no HTML,
84
+ * no path-style decisions.
85
+ *
86
+ * Tree assembly (Phase 19c):
87
+ * 1. Walk the fs (`fsWalk`) to enumerate every directory under
88
+ * contentDir. Each fs entry contributes a candidate node keyed by
89
+ * its fs-relative path.
90
+ * 2. Build the content index (`buildContentIndex`) to map
91
+ * `relPath → entryId` based on frontmatter `id:`.
92
+ * 3. For each fs node with a markdown index/README, look up the
93
+ * bound entry id via `index.byPath`, then resolve the calendar
94
+ * entry by id. If found → overlay state (lane, title from entry,
95
+ * slug as a display attribute).
96
+ * 4. For calendar entries whose id is NOT in the index (their file
97
+ * isn't bound to frontmatter yet — pre-doctor state), do
98
+ * legacy slug fallback: look for an fs node whose path equals
99
+ * the entry's slug. If found → overlay with a one-time warning.
100
+ * If not found → place as a ghost node (today's behavior,
101
+ * preserved for backward compat).
102
+ */
103
+ export function buildContentTree(site, entries, config, projectRoot, options = {}) {
104
+ const lookup = options.scrapbookLookup ??
105
+ ((siteArg, path) => {
106
+ try {
107
+ return listScrapbook(projectRoot, config, siteArg, path);
108
+ }
109
+ catch {
110
+ return { items: [], secretItems: [] };
111
+ }
112
+ });
113
+ const fsWalk = options.fsWalk ?? ((siteArg) => defaultFsWalk(projectRoot, config, siteArg));
114
+ const fsEntries = fsWalk(site);
115
+ const fsEntryByPath = new Map();
116
+ for (const e of fsEntries)
117
+ fsEntryByPath.set(e.slug, e);
118
+ const contentIndex = options.contentIndex ?? buildContentIndex(projectRoot, config, site);
119
+ const warn = options.warn ?? ((msg) => console.warn(msg));
120
+ const contentDir = resolveContentDir(projectRoot, config, site);
121
+ // ---- Phase 1: assemble the union of paths that need nodes ----
122
+ // Calendar entries contribute through TWO routes:
123
+ // (a) id-based binding: index.byPath[relPath] === entry.id binds
124
+ // the entry to that fs path. The path becomes the node key.
125
+ // (b) legacy slug fallback: when (a) doesn't fire, treat the
126
+ // entry's slug as a candidate path. If the fs walk includes
127
+ // it, overlay there with a warning. Otherwise it's a ghost.
128
+ const allPaths = new Set();
129
+ // Track which calendar entries bind to which paths. The path is
130
+ // either the fs-bound path (id-driven) or the entry's slug (legacy
131
+ // fallback / ghost).
132
+ const overlayByPath = new Map();
133
+ // Filesystem-derived paths first — every walked dir contributes its
134
+ // own path AND its ancestors so the tree wires up cleanly.
135
+ for (const e of fsEntries) {
136
+ if (e.hasReadme || e.hasIndex) {
137
+ allPaths.add(e.slug);
138
+ for (const a of ancestorsOf(e.slug))
139
+ allPaths.add(a);
140
+ }
141
+ }
142
+ // Determine each entry's binding target (id-based, slug-fallback, ghost).
143
+ for (const entry of entries) {
144
+ const idBoundPath = findIdBoundPath(entry, contentIndex);
145
+ if (idBoundPath !== null) {
146
+ overlayByPath.set(idBoundPath, entry);
147
+ allPaths.add(idBoundPath);
148
+ for (const a of ancestorsOf(idBoundPath))
149
+ allPaths.add(a);
150
+ continue;
151
+ }
152
+ // Legacy slug-fallback: if the slug looks like an fs path AND
153
+ // matches a walked dir, overlay there. Otherwise ghost.
154
+ if (fsEntryByPath.has(entry.slug)) {
155
+ overlayByPath.set(entry.slug, entry);
156
+ allPaths.add(entry.slug);
157
+ for (const a of ancestorsOf(entry.slug))
158
+ allPaths.add(a);
159
+ // De-dup the warning per process — without this the studio's polled
160
+ // dashboard re-emits the same warning on every render. Key by
161
+ // (site, entryId | slug) so a renamed slug warns again.
162
+ const dedupKey = `${site}:${entry.id ?? entry.slug}`;
163
+ if (!WARNED_LEGACY_FALLBACK.has(dedupKey)) {
164
+ WARNED_LEGACY_FALLBACK.add(dedupKey);
165
+ warn(`[content-tree] Calendar entry "${entry.slug}" matched fs node by slug ` +
166
+ `(no frontmatter id binding). Run \`deskwork doctor --fix=missing-frontmatter-id\` ` +
167
+ `to make this binding refactor-proof.`);
168
+ }
169
+ continue;
170
+ }
171
+ // Ghost: entry exists but has no fs counterpart at slug-equals-path
172
+ // and no id binding. Surface it (the calendar is authoritative for
173
+ // "this entry exists") so the operator sees it in the tree.
174
+ allPaths.add(entry.slug);
175
+ for (const a of ancestorsOf(entry.slug))
176
+ allPaths.add(a);
177
+ overlayByPath.set(entry.slug, entry);
178
+ }
179
+ // ---- Phase 2: build nodes for every path in the union ----
180
+ const sortedPaths = [...allPaths].sort();
181
+ const nodeByPath = new Map();
182
+ for (const path of sortedPaths) {
183
+ const overlay = overlayByPath.get(path) ?? null;
184
+ const fsEntry = fsEntryByPath.get(path) ?? null;
185
+ const sb = lookup(site, path);
186
+ const items = [...sb.items, ...sb.secretItems];
187
+ const mostRecent = items.reduce((acc, it) => pickLatestMtime(acc, it.mtime), null);
188
+ // Title resolution: calendar wins, then fs frontmatter, then leaf.
189
+ const title = overlay?.title ??
190
+ (fsEntry?.title ?? null) ??
191
+ leafOfPath(path);
192
+ // hasOwnIndex resolution + on-disk file path resolution. Both
193
+ // depend on the content index's id-binding when an entry overlays
194
+ // — the bound file is the SSOT for both the existence check
195
+ // (`hasOwnIndex`) and the renderer's path display (Issue #70).
196
+ let hasOwnIndex = false;
197
+ let boundFile;
198
+ if (overlay !== null) {
199
+ boundFile = idBoundFile(overlay, contentIndex);
200
+ hasOwnIndex = entryHasOwnIndex(contentDir, path, fsEntry?.hasIndex ?? false, fsEntry?.hasReadme ?? false, boundFile, fsEntry !== null);
201
+ }
202
+ else if (fsEntry !== null) {
203
+ hasOwnIndex = fsEntry.hasIndex || fsEntry.hasReadme;
204
+ }
205
+ const node = {
206
+ site,
207
+ path,
208
+ title,
209
+ lane: overlay?.stage ?? null,
210
+ entry: overlay,
211
+ hasOwnIndex,
212
+ hasFsDir: fsEntry !== null,
213
+ scrapbookCount: items.length,
214
+ scrapbookMostRecentMtime: mostRecent,
215
+ children: [],
216
+ };
217
+ if (overlay?.slug !== undefined) {
218
+ // Slug only set when an entry overlays — used by the studio for
219
+ // the "public URL" hover hint. Honoring exactOptionalPropertyTypes:
220
+ // omit the field entirely rather than assigning undefined.
221
+ node.slug = overlay.slug;
222
+ }
223
+ if (boundFile !== undefined) {
224
+ // Issue #70: surface the actual on-disk path so the renderer
225
+ // doesn't have to reconstruct `<path>/index.md` (which is wrong
226
+ // for hierarchical / non-template layouts).
227
+ node.filePath = boundFile;
228
+ }
229
+ nodeByPath.set(path, node);
230
+ }
231
+ // Wire up parent → child links by path shape.
232
+ for (const path of sortedPaths) {
233
+ const parts = path.split('/');
234
+ if (parts.length === 1)
235
+ continue;
236
+ const parentPath = parts.slice(0, -1).join('/');
237
+ const parent = nodeByPath.get(parentPath);
238
+ const node = nodeByPath.get(path);
239
+ if (parent && node)
240
+ parent.children.push(node);
241
+ }
242
+ // Group root-level paths by project (their first segment).
243
+ const projectRootBy = new Map();
244
+ for (const path of sortedPaths) {
245
+ if (path.includes('/'))
246
+ continue;
247
+ const node = nodeByPath.get(path);
248
+ if (!node)
249
+ continue;
250
+ const arr = projectRootBy.get(path) ?? [];
251
+ arr.push(node);
252
+ projectRootBy.set(path, arr);
253
+ }
254
+ // Calendars with entries at deep paths and no top-level fs node
255
+ // need a synthetic project root at the first path segment.
256
+ const knownRoots = new Set(projectRootBy.keys());
257
+ for (const e of entries) {
258
+ // Compute the path the entry resolves to (id-bound or slug).
259
+ const idBoundPath = findIdBoundPath(e, contentIndex);
260
+ const entryPath = idBoundPath ?? e.slug;
261
+ const root = rootSegment(entryPath);
262
+ if (!knownRoots.has(root)) {
263
+ const sb = lookup(site, root);
264
+ const items = [...sb.items, ...sb.secretItems];
265
+ const mostRecent = items.reduce((acc, it) => pickLatestMtime(acc, it.mtime), null);
266
+ const synth = {
267
+ site,
268
+ path: root,
269
+ title: leafOfPath(root),
270
+ lane: null,
271
+ entry: null,
272
+ hasOwnIndex: false,
273
+ hasFsDir: fsEntryByPath.has(root),
274
+ scrapbookCount: items.length,
275
+ scrapbookMostRecentMtime: mostRecent,
276
+ children: [],
277
+ };
278
+ nodeByPath.set(root, synth);
279
+ // Re-attach orphans whose direct parent was a missing
280
+ // intermediate path we hadn't generated.
281
+ for (const path of sortedPaths) {
282
+ if (rootSegment(path) === root && path.includes('/')) {
283
+ const parentPath = path.split('/').slice(0, -1).join('/');
284
+ if (parentPath === root) {
285
+ const child = nodeByPath.get(path);
286
+ if (child && !synth.children.includes(child)) {
287
+ synth.children.push(child);
288
+ }
289
+ }
290
+ }
291
+ }
292
+ projectRootBy.set(root, [synth]);
293
+ knownRoots.add(root);
294
+ }
295
+ }
296
+ // Sort children deterministically.
297
+ for (const node of nodeByPath.values()) {
298
+ node.children.sort((a, b) => a.path.localeCompare(b.path));
299
+ }
300
+ // Fold each project root into a ContentProject summary.
301
+ const projects = [];
302
+ for (const [rootPath, roots] of projectRootBy.entries()) {
303
+ if (roots.length === 0)
304
+ continue;
305
+ const root = roots[0];
306
+ const summary = summarizeProject(site, rootPath, root);
307
+ projects.push(summary);
308
+ }
309
+ projects.sort((a, b) => a.rootSlug.localeCompare(b.rootSlug));
310
+ return projects;
311
+ }
312
+ function summarizeProject(site, rootPath, root) {
313
+ let trackedCount = 0;
314
+ let totalNodes = 0;
315
+ let maxDepth = 0;
316
+ let scrapbookCount = 0;
317
+ const laneCounts = new Map();
318
+ const visit = (node, depth) => {
319
+ totalNodes += 1;
320
+ maxDepth = Math.max(maxDepth, depth);
321
+ scrapbookCount += node.scrapbookCount;
322
+ if (node.entry !== null) {
323
+ trackedCount += 1;
324
+ const stage = node.entry.stage;
325
+ laneCounts.set(stage, (laneCounts.get(stage) ?? 0) + 1);
326
+ }
327
+ for (const c of node.children)
328
+ visit(c, depth + 1);
329
+ };
330
+ visit(root, 1);
331
+ let predominantLane = null;
332
+ let bestCount = 0;
333
+ for (const [stage, count] of laneCounts.entries()) {
334
+ if (count > bestCount) {
335
+ predominantLane = stage;
336
+ bestCount = count;
337
+ }
338
+ }
339
+ return {
340
+ site,
341
+ rootSlug: rootPath,
342
+ title: root.title,
343
+ trackedCount,
344
+ totalNodes,
345
+ maxDepth,
346
+ scrapbookCount,
347
+ predominantLane,
348
+ root,
349
+ };
350
+ }
351
+ /**
352
+ * Helper: find the node with the given path under a project tree, or
353
+ * return null. Used by the studio's node-detail panel.
354
+ */
355
+ export function findNode(project, path) {
356
+ if (project.root.path === path)
357
+ return project.root;
358
+ const queue = [...project.root.children];
359
+ while (queue.length > 0) {
360
+ const head = queue.shift();
361
+ if (!head)
362
+ continue;
363
+ if (head.path === path)
364
+ return head;
365
+ queue.push(...head.children);
366
+ }
367
+ return null;
368
+ }
369
+ /**
370
+ * Flatten the tree into a depth-first ordered list. See `FlatNode` in
371
+ * `content-tree-types.ts` for the row shape.
372
+ */
373
+ export function flattenForRender(root) {
374
+ const out = [];
375
+ const walk = (node, depth, isLast) => {
376
+ out.push({ node, depth, isLast });
377
+ const last = node.children.length - 1;
378
+ for (let i = 0; i < node.children.length; i++) {
379
+ walk(node.children[i], depth + 1, i === last);
380
+ }
381
+ };
382
+ walk(root, 0, true);
383
+ return out;
384
+ }
385
+ //# sourceMappingURL=content-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree.js","sourceRoot":"","sources":["../src/content-tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Doctor — public surface.
3
+ *
4
+ * Re-exports the rule registry, runner, types, and the schema-patch
5
+ * helper. Callers (the CLI command, the studio in a future phase, and
6
+ * tests) only import from here.
7
+ */
8
+ export type { DoctorContext, DoctorInteraction, DoctorReport, DoctorRule, Finding, FindingSeverity, RepairChoice, RepairPlan, RepairResult, SkipReason, } from './types.ts';
9
+ export { RULES, parseFixArgument, runAudit, runRepair, yesInteraction, declineInteraction, type DoctorRunOptions, } from './runner.ts';
10
+ export { printSchemaPatchInstructions } from './schema-patch.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,OAAO,EACP,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Doctor — public surface.
3
+ *
4
+ * Re-exports the rule registry, runner, types, and the schema-patch
5
+ * helper. Callers (the CLI command, the studio in a future phase, and
6
+ * tests) only import from here.
7
+ */
8
+ export { RULES, parseFixArgument, runAudit, runRepair, yesInteraction, declineInteraction, } from "./runner.js";
9
+ export { printSchemaPatchInstructions } from "./schema-patch.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,kBAAkB,GAEnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Phase 23f — project-level doctor rule loader.
3
+ *
4
+ * Operators can drop a `.ts` module in `<projectRoot>/.deskwork/doctor/`
5
+ * to register a custom rule. The runner merges those project rules
6
+ * with the built-in rules; basename collisions let the project rule
7
+ * override the built-in (e.g., a project-supplied
8
+ * `missing-frontmatter-id.ts` replaces the bundled one).
9
+ *
10
+ * Override resolution by basename, not by `rule.id`: the basename is
11
+ * what the operator types; mapping it to a rule object happens in the
12
+ * import. We chose basename-as-key so operators can author a rule
13
+ * whose internal `id` differs from the file name (e.g., a project's
14
+ * "tighter" version of an existing rule keeps the same id for `--fix`
15
+ * compatibility but lives at a basename-collision path).
16
+ *
17
+ * Discovery is sync — the runner builds the merged rule list once at
18
+ * the start of an audit/repair run, not per finding. We use
19
+ * `readdirSync` + `import()` (the latter is async; we await all
20
+ * project rules in `loadProjectRules`).
21
+ *
22
+ * Failure mode: a project rule that fails to import (bad TypeScript,
23
+ * wrong default export shape) throws. The runner surfaces the throw
24
+ * to the operator rather than silently dropping the rule.
25
+ */
26
+ import type { DoctorRule } from './types.ts';
27
+ /**
28
+ * One loaded project rule plus its source basename. The basename is
29
+ * what the runner uses to detect override collisions with built-in
30
+ * rules.
31
+ */
32
+ export interface LoadedProjectRule {
33
+ /** Filename without extension — e.g. `missing-frontmatter-id`. */
34
+ basename: string;
35
+ /** Absolute path of the source file. */
36
+ path: string;
37
+ /** Imported and type-narrowed rule. */
38
+ rule: DoctorRule;
39
+ }
40
+ /**
41
+ * Walk `<projectRoot>/.deskwork/doctor/` and return every rule found,
42
+ * in alphabetical order. Returns an empty list when the directory
43
+ * doesn't exist — no `.deskwork/doctor/` is the common case and must
44
+ * not throw.
45
+ */
46
+ export declare function loadProjectRules(projectRoot: string): Promise<LoadedProjectRule[]>;
47
+ /**
48
+ * Merge built-in rules with project rules. Project rules with a
49
+ * basename matching a built-in rule's basename REPLACE the built-in
50
+ * (override). Project rules with new basenames are APPENDED in their
51
+ * loaded order (alphabetical from `loadProjectRules`).
52
+ *
53
+ * The basename-of-built-in mapping uses each built-in rule's `id`,
54
+ * because every shipped rule is named after its id. If we ever ship
55
+ * a built-in whose file basename and id differ, this mapping will
56
+ * need an explicit table.
57
+ */
58
+ export declare function mergeRules(builtIns: ReadonlyArray<DoctorRule>, projectRules: ReadonlyArray<LoadedProjectRule>): DoctorRule[];
59
+ //# sourceMappingURL=project-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-rules.d.ts","sourceRoot":"","sources":["../../src/doctor/project-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA4D7C;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyB9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,EACnC,YAAY,EAAE,aAAa,CAAC,iBAAiB,CAAC,GAC7C,UAAU,EAAE,CAyBd"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Phase 23f — project-level doctor rule loader.
3
+ *
4
+ * Operators can drop a `.ts` module in `<projectRoot>/.deskwork/doctor/`
5
+ * to register a custom rule. The runner merges those project rules
6
+ * with the built-in rules; basename collisions let the project rule
7
+ * override the built-in (e.g., a project-supplied
8
+ * `missing-frontmatter-id.ts` replaces the bundled one).
9
+ *
10
+ * Override resolution by basename, not by `rule.id`: the basename is
11
+ * what the operator types; mapping it to a rule object happens in the
12
+ * import. We chose basename-as-key so operators can author a rule
13
+ * whose internal `id` differs from the file name (e.g., a project's
14
+ * "tighter" version of an existing rule keeps the same id for `--fix`
15
+ * compatibility but lives at a basename-collision path).
16
+ *
17
+ * Discovery is sync — the runner builds the merged rule list once at
18
+ * the start of an audit/repair run, not per finding. We use
19
+ * `readdirSync` + `import()` (the latter is async; we await all
20
+ * project rules in `loadProjectRules`).
21
+ *
22
+ * Failure mode: a project rule that fails to import (bad TypeScript,
23
+ * wrong default export shape) throws. The runner surfaces the throw
24
+ * to the operator rather than silently dropping the rule.
25
+ */
26
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
27
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
28
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
29
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
30
+ });
31
+ }
32
+ return path;
33
+ };
34
+ import { existsSync, readdirSync } from 'node:fs';
35
+ import { join } from 'node:path';
36
+ const RULE_FILE_SUFFIX = '.ts';
37
+ /**
38
+ * Narrow an unknown imported value to a `DoctorRule`. We require:
39
+ * - object with non-null id+label string fields,
40
+ * - audit / plan / apply functions.
41
+ *
42
+ * Failures throw a descriptive message — the operator's rule file is
43
+ * malformed and they need to know which file and which field.
44
+ */
45
+ function assertDoctorRule(value, source) {
46
+ if (typeof value !== 'object' || value === null) {
47
+ throw new Error(`project doctor rule ${source}: default export must be an object`);
48
+ }
49
+ const id = Reflect.get(value, 'id');
50
+ const label = Reflect.get(value, 'label');
51
+ const audit = Reflect.get(value, 'audit');
52
+ const plan = Reflect.get(value, 'plan');
53
+ const apply = Reflect.get(value, 'apply');
54
+ if (typeof id !== 'string' || id.length === 0) {
55
+ throw new Error(`project doctor rule ${source}: 'id' must be a non-empty string`);
56
+ }
57
+ if (typeof label !== 'string' || label.length === 0) {
58
+ throw new Error(`project doctor rule ${source}: 'label' must be a non-empty string`);
59
+ }
60
+ if (typeof audit !== 'function') {
61
+ throw new Error(`project doctor rule ${source}: 'audit' must be a function`);
62
+ }
63
+ if (typeof plan !== 'function') {
64
+ throw new Error(`project doctor rule ${source}: 'plan' must be a function`);
65
+ }
66
+ if (typeof apply !== 'function') {
67
+ throw new Error(`project doctor rule ${source}: 'apply' must be a function`);
68
+ }
69
+ // The shape checks above narrow `value` to a DoctorRule for
70
+ // practical purposes; we synthesize a plain rule reference rather
71
+ // than `as`-cast to avoid the lint rule about type assertions.
72
+ return {
73
+ id,
74
+ label,
75
+ audit: audit.bind(value),
76
+ plan: plan.bind(value),
77
+ apply: apply.bind(value),
78
+ };
79
+ }
80
+ /**
81
+ * Walk `<projectRoot>/.deskwork/doctor/` and return every rule found,
82
+ * in alphabetical order. Returns an empty list when the directory
83
+ * doesn't exist — no `.deskwork/doctor/` is the common case and must
84
+ * not throw.
85
+ */
86
+ export async function loadProjectRules(projectRoot) {
87
+ const dir = join(projectRoot, '.deskwork', 'doctor');
88
+ if (!existsSync(dir))
89
+ return [];
90
+ const entries = readdirSync(dir).filter((n) => n.endsWith(RULE_FILE_SUFFIX));
91
+ entries.sort();
92
+ const out = [];
93
+ for (const name of entries) {
94
+ const path = join(dir, name);
95
+ const basename = name.slice(0, -RULE_FILE_SUFFIX.length);
96
+ // Dynamic import via an absolute path — works under tsx (the
97
+ // runtime the CLI uses) and node when project rules are pre-
98
+ // compiled to JS adjacent paths in some future workflow.
99
+ const mod = await import(__rewriteRelativeImportExtension(path));
100
+ if (typeof mod !== 'object' || mod === null) {
101
+ throw new Error(`project doctor rule ${path}: import did not produce a module object`);
102
+ }
103
+ const def = Reflect.get(mod, 'default');
104
+ const rule = assertDoctorRule(def, path);
105
+ out.push({ basename, path, rule });
106
+ }
107
+ return out;
108
+ }
109
+ /**
110
+ * Merge built-in rules with project rules. Project rules with a
111
+ * basename matching a built-in rule's basename REPLACE the built-in
112
+ * (override). Project rules with new basenames are APPENDED in their
113
+ * loaded order (alphabetical from `loadProjectRules`).
114
+ *
115
+ * The basename-of-built-in mapping uses each built-in rule's `id`,
116
+ * because every shipped rule is named after its id. If we ever ship
117
+ * a built-in whose file basename and id differ, this mapping will
118
+ * need an explicit table.
119
+ */
120
+ export function mergeRules(builtIns, projectRules) {
121
+ // Map built-in rule id (== basename today) → index, so we can
122
+ // splice in the override at the same position.
123
+ const builtInIndexByBasename = new Map();
124
+ for (let i = 0; i < builtIns.length; i++) {
125
+ builtInIndexByBasename.set(builtIns[i].id, i);
126
+ }
127
+ const merged = builtIns.slice();
128
+ const overriddenBasenames = new Set();
129
+ for (const p of projectRules) {
130
+ const idx = builtInIndexByBasename.get(p.basename);
131
+ if (idx !== undefined) {
132
+ merged[idx] = p.rule;
133
+ overriddenBasenames.add(p.basename);
134
+ }
135
+ }
136
+ for (const p of projectRules) {
137
+ if (overriddenBasenames.has(p.basename))
138
+ continue;
139
+ merged.push(p.rule);
140
+ }
141
+ return merged;
142
+ }
143
+ //# sourceMappingURL=project-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-rules.js","sourceRoot":"","sources":["../../src/doctor/project-rules.ts"],"names":[],"mappings":"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"}