@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,112 @@
1
+ /**
2
+ * Content-tree filesystem walk — pure recursive directory scan.
3
+ *
4
+ * Extracted from `content-tree.ts` (Phase 19c) to keep that file under
5
+ * the project's 500-line guideline. The walk is read-only and produces
6
+ * one `FsWalkEntry` per directory beneath `<contentDir>` — the input
7
+ * to the tree assembly's fs-primary inversion.
8
+ *
9
+ * No knowledge of the calendar or content index lives here.
10
+ */
11
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { resolveContentDir } from "./paths.js";
14
+ import { parseFrontmatter } from "./frontmatter.js";
15
+ /** Match the index/README basenames the studio recognizes as a node marker. */
16
+ export const INDEX_BASENAMES = new Set([
17
+ 'index.md', 'index.mdx', 'index.markdown',
18
+ ]);
19
+ const README_BASENAMES = new Set([
20
+ 'readme.md', 'readme.mdx', 'readme.markdown',
21
+ ]);
22
+ /**
23
+ * Index file basenames the host's default template (`<path>/index.md`)
24
+ * recognises. Used to decide `hasOwnIndex` for tracked entries when the
25
+ * content index doesn't bind them to a specific file (pre-doctor state).
26
+ */
27
+ export const TEMPLATE_INDEX_BASENAMES = [
28
+ 'index.md',
29
+ 'index.mdx',
30
+ 'index.markdown',
31
+ ];
32
+ function readTitleFromMarkdown(absPath) {
33
+ try {
34
+ const raw = readFileSync(absPath, 'utf-8');
35
+ const parsed = parseFrontmatter(raw);
36
+ const t = parsed.data.title;
37
+ if (typeof t === 'string' && t.trim().length > 0)
38
+ return t.trim();
39
+ }
40
+ catch {
41
+ // Unreadable / unparseable — fall through.
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Default filesystem walk — recursively scan a site's contentDir for
47
+ * directories. Returns one `FsWalkEntry` per directory beneath
48
+ * contentDir (not contentDir itself). Skips dotfiles and the
49
+ * conventional non-content names (`scrapbook`, `node_modules`, etc.).
50
+ *
51
+ * Per-directory the walk records whether an `index.md` / `README.md`
52
+ * is present and (when present) reads the frontmatter `title`.
53
+ */
54
+ export function defaultFsWalk(projectRoot, config, site) {
55
+ const root = resolveContentDir(projectRoot, config, site);
56
+ if (!existsSync(root))
57
+ return [];
58
+ const out = [];
59
+ const SKIP = new Set(['scrapbook', 'node_modules', 'dist', '.git']);
60
+ const visit = (dirAbs, pathSoFar) => {
61
+ let names;
62
+ try {
63
+ names = readdirSync(dirAbs);
64
+ }
65
+ catch {
66
+ return;
67
+ }
68
+ let hasIndex = false;
69
+ let hasReadme = false;
70
+ let titleSource = null;
71
+ for (const name of names) {
72
+ const lower = name.toLowerCase();
73
+ if (INDEX_BASENAMES.has(lower)) {
74
+ hasIndex = true;
75
+ if (titleSource === null)
76
+ titleSource = join(dirAbs, name);
77
+ }
78
+ else if (README_BASENAMES.has(lower)) {
79
+ hasReadme = true;
80
+ // Prefer index.md as the title source when both exist; only
81
+ // fall back to README when there is no index.
82
+ if (titleSource === null && !hasIndex)
83
+ titleSource = join(dirAbs, name);
84
+ }
85
+ }
86
+ if (pathSoFar !== '') {
87
+ const title = titleSource ? readTitleFromMarkdown(titleSource) : null;
88
+ out.push({ slug: pathSoFar, hasIndex, hasReadme, title });
89
+ }
90
+ for (const name of names) {
91
+ if (name.startsWith('.'))
92
+ continue;
93
+ if (SKIP.has(name.toLowerCase()))
94
+ continue;
95
+ const childAbs = join(dirAbs, name);
96
+ let childStat;
97
+ try {
98
+ childStat = statSync(childAbs);
99
+ }
100
+ catch {
101
+ continue;
102
+ }
103
+ if (!childStat.isDirectory())
104
+ continue;
105
+ const childPath = pathSoFar === '' ? name : `${pathSoFar}/${name}`;
106
+ visit(childAbs, childPath);
107
+ }
108
+ };
109
+ visit(root, '');
110
+ return out;
111
+ }
112
+ //# sourceMappingURL=content-tree-fs-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree-fs-walk.js","sourceRoot":"","sources":["../src/content-tree-fs-walk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Internal helpers for `content-tree.ts`. Path-shape utilities, id-binding
3
+ * lookups, and the `hasOwnIndex` decision. Extracted to keep the main
4
+ * tree-assembly module under the project's 500-line guideline.
5
+ *
6
+ * No public API here — the tree builder re-exports what callers need.
7
+ */
8
+ import type { CalendarEntry } from './types.ts';
9
+ import type { ContentIndex } from './content-index.ts';
10
+ /** Last segment of a `/`-separated path. */
11
+ export declare function leafOfPath(path: string): string;
12
+ /** Every prefix path of a `/`-separated path, excluding the path itself. */
13
+ export declare function ancestorsOf(path: string): string[];
14
+ /** First segment of a `/`-separated path. */
15
+ export declare function rootSegment(path: string): string;
16
+ /**
17
+ * Decide `hasOwnIndex` for a tracked entry.
18
+ *
19
+ * Order:
20
+ * 1. The content index binds the entry to a real file → `true`.
21
+ * 2. The fs walk found an index/README at this path → `true`.
22
+ * 3. A template-path file actually exists on disk → `true` (covers
23
+ * the pre-bind case where the file is at `<path>/index.md` but
24
+ * the walk didn't surface this directory because we synthesized
25
+ * it from the calendar — should be rare post-19c).
26
+ * 4. Calendar-only entry (no fs node, no id binding): default to
27
+ * `true` because the host renderer's template implies a file at
28
+ * `<path>/index.md`. Pre-19c behavior for ghost entries.
29
+ */
30
+ export declare function entryHasOwnIndex(contentDir: string, entryPath: string, fsHasIndex: boolean, fsHasReadme: boolean, boundFile: string | undefined, hasFsDir: boolean): boolean;
31
+ /** Return the most-recent ISO mtime across two values, or null when both are null. */
32
+ export declare function pickLatestMtime(a: string | null, b: string | null): string | null;
33
+ /**
34
+ * If the relative path ends with a recognized index file basename
35
+ * (`index.md`, `index.mdx`, `index.markdown`), return the parent
36
+ * directory path. Otherwise return the path unchanged. Used to map
37
+ * `projects/the-outbound/index.md` (the file) → `projects/the-outbound`
38
+ * (the tree node).
39
+ *
40
+ * Files at flat paths (e.g. `my-flat-post.md`) keep their full path
41
+ * minus the extension as the node key. This matches today's
42
+ * audiocontrol shape where the slug is the basename.
43
+ */
44
+ export declare function stripIndexBasename(relPath: string): string;
45
+ /**
46
+ * Find the fs path that the content index binds to this entry's id, or
47
+ * null when the entry has no id or its id isn't in the index.
48
+ */
49
+ export declare function findIdBoundPath(entry: CalendarEntry, index: ContentIndex): string | null;
50
+ /** Absolute path to the file the index binds to this entry, or undefined. */
51
+ export declare function idBoundFile(entry: CalendarEntry, index: ContentIndex): string | undefined;
52
+ //# sourceMappingURL=content-tree-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree-helpers.d.ts","sourceRoot":"","sources":["../src/content-tree-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAMvD,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOlD;AAED,6CAA6C;AAC7C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,OAAO,EACnB,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,OAAO,GAChB,OAAO,CAYT;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,CAAC,EAAE,MAAM,GAAG,IAAI,EAChB,CAAC,EAAE,MAAM,GAAG,IAAI,GACf,MAAM,GAAG,IAAI,CAIf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,IAAI,CAQf;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,SAAS,CAGpB"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Internal helpers for `content-tree.ts`. Path-shape utilities, id-binding
3
+ * lookups, and the `hasOwnIndex` decision. Extracted to keep the main
4
+ * tree-assembly module under the project's 500-line guideline.
5
+ *
6
+ * No public API here — the tree builder re-exports what callers need.
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { INDEX_BASENAMES, TEMPLATE_INDEX_BASENAMES, } from "./content-tree-fs-walk.js";
11
+ /** Last segment of a `/`-separated path. */
12
+ export function leafOfPath(path) {
13
+ const idx = path.lastIndexOf('/');
14
+ return idx < 0 ? path : path.slice(idx + 1);
15
+ }
16
+ /** Every prefix path of a `/`-separated path, excluding the path itself. */
17
+ export function ancestorsOf(path) {
18
+ const segments = path.split('/');
19
+ const out = [];
20
+ for (let i = 1; i < segments.length; i++) {
21
+ out.push(segments.slice(0, i).join('/'));
22
+ }
23
+ return out;
24
+ }
25
+ /** First segment of a `/`-separated path. */
26
+ export function rootSegment(path) {
27
+ const idx = path.indexOf('/');
28
+ return idx < 0 ? path : path.slice(0, idx);
29
+ }
30
+ /**
31
+ * Decide `hasOwnIndex` for a tracked entry.
32
+ *
33
+ * Order:
34
+ * 1. The content index binds the entry to a real file → `true`.
35
+ * 2. The fs walk found an index/README at this path → `true`.
36
+ * 3. A template-path file actually exists on disk → `true` (covers
37
+ * the pre-bind case where the file is at `<path>/index.md` but
38
+ * the walk didn't surface this directory because we synthesized
39
+ * it from the calendar — should be rare post-19c).
40
+ * 4. Calendar-only entry (no fs node, no id binding): default to
41
+ * `true` because the host renderer's template implies a file at
42
+ * `<path>/index.md`. Pre-19c behavior for ghost entries.
43
+ */
44
+ export function entryHasOwnIndex(contentDir, entryPath, fsHasIndex, fsHasReadme, boundFile, hasFsDir) {
45
+ if (boundFile !== undefined)
46
+ return true;
47
+ if (fsHasIndex)
48
+ return true;
49
+ if (fsHasReadme)
50
+ return true;
51
+ for (const basename of TEMPLATE_INDEX_BASENAMES) {
52
+ if (existsSync(join(contentDir, entryPath, basename)))
53
+ return true;
54
+ }
55
+ // No fs evidence at all — calendar-only ghost. Assume the host
56
+ // template (`<path>/index.md`) so detail-panel paths remain
57
+ // discoverable for entries that haven't been scaffolded yet.
58
+ if (!hasFsDir)
59
+ return true;
60
+ return false;
61
+ }
62
+ /** Return the most-recent ISO mtime across two values, or null when both are null. */
63
+ export function pickLatestMtime(a, b) {
64
+ if (a === null)
65
+ return b;
66
+ if (b === null)
67
+ return a;
68
+ return a > b ? a : b;
69
+ }
70
+ /**
71
+ * If the relative path ends with a recognized index file basename
72
+ * (`index.md`, `index.mdx`, `index.markdown`), return the parent
73
+ * directory path. Otherwise return the path unchanged. Used to map
74
+ * `projects/the-outbound/index.md` (the file) → `projects/the-outbound`
75
+ * (the tree node).
76
+ *
77
+ * Files at flat paths (e.g. `my-flat-post.md`) keep their full path
78
+ * minus the extension as the node key. This matches today's
79
+ * audiocontrol shape where the slug is the basename.
80
+ */
81
+ export function stripIndexBasename(relPath) {
82
+ const segments = relPath.split('/');
83
+ const last = segments[segments.length - 1].toLowerCase();
84
+ if (INDEX_BASENAMES.has(last)) {
85
+ return segments.slice(0, -1).join('/');
86
+ }
87
+ const dotIdx = last.lastIndexOf('.');
88
+ if (dotIdx > 0) {
89
+ const stripped = segments.slice(0, -1);
90
+ stripped.push(last.slice(0, dotIdx));
91
+ return stripped.join('/');
92
+ }
93
+ return relPath;
94
+ }
95
+ /**
96
+ * Find the fs path that the content index binds to this entry's id, or
97
+ * null when the entry has no id or its id isn't in the index.
98
+ */
99
+ export function findIdBoundPath(entry, index) {
100
+ if (typeof entry.id !== 'string' || entry.id === '')
101
+ return null;
102
+ // Reverse-lookup byPath for value === entry.id. Keeps the index
103
+ // single-source-of-truth without us needing a third map.
104
+ for (const [path, id] of index.byPath.entries()) {
105
+ if (id === entry.id)
106
+ return stripIndexBasename(path);
107
+ }
108
+ return null;
109
+ }
110
+ /** Absolute path to the file the index binds to this entry, or undefined. */
111
+ export function idBoundFile(entry, index) {
112
+ if (typeof entry.id !== 'string' || entry.id === '')
113
+ return undefined;
114
+ return index.byId.get(entry.id);
115
+ }
116
+ //# sourceMappingURL=content-tree-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree-helpers.js","sourceRoot":"","sources":["../src/content-tree-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Public types for the bird's-eye content tree (Phase 16d / 19c).
3
+ *
4
+ * Extracted from `content-tree.ts` to keep the assembly module under
5
+ * the project's 500-line guideline. The tree builder re-exports every
6
+ * symbol from this module so callers continue importing from
7
+ * `@deskwork/core/content-tree`.
8
+ */
9
+ import type { CalendarEntry, Stage } from './types.ts';
10
+ import type { ContentIndex } from './content-index.ts';
11
+ import type { FsWalkEntry } from './content-tree-fs-walk.ts';
12
+ /** A node in the content tree. */
13
+ export interface ContentNode {
14
+ /** Site slug — passed through from the input. */
15
+ site: string;
16
+ /**
17
+ * Fs-relative path under contentDir (e.g. `projects/the-outbound`,
18
+ * `essays/whats-in-a-name`). The structural key for the tree —
19
+ * parent/child wiring, URL construction, and node lookup all go
20
+ * through this field.
21
+ *
22
+ * Renamed from `slug` in Phase 19c (#33). For audiocontrol-shaped
23
+ * flat blogs, this is identical to today's slug (`my-flat-post`);
24
+ * for hierarchical content collections, this captures the actual
25
+ * fs hierarchy independent of any host-derived public URL.
26
+ */
27
+ path: string;
28
+ /**
29
+ * Host-owned public URL slug — populated only when a calendar entry
30
+ * is overlaid on this node. The studio uses this for the "public
31
+ * URL: /blog/<slug>" hover hint. Not used internally by deskwork
32
+ * for routing or path resolution. `undefined` for organizational
33
+ * nodes and for fs nodes that have no calendar entry.
34
+ */
35
+ slug?: string;
36
+ /**
37
+ * Absolute path of the markdown file backing this node, when one
38
+ * is known. Populated from the content index's id-binding (Issue #70)
39
+ * — the actual on-disk file, not a slug-template ghost. The studio's
40
+ * "file path" hint reads this to display the real path; renderers
41
+ * that previously reconstructed `<path>/index.md` from `path` were
42
+ * showing a path that didn't necessarily exist.
43
+ *
44
+ * `undefined` for ghost calendar entries (no fs binding yet) and for
45
+ * organizational nodes without a tracked calendar entry. Callers
46
+ * needing a fallback should check `hasOwnIndex` and fall back to
47
+ * a path constructed from `path`.
48
+ */
49
+ filePath?: string;
50
+ /** Display title. Resolution order: calendar entry title → README/index frontmatter title → leaf path segment. */
51
+ title: string;
52
+ /**
53
+ * Lifecycle stage when the node corresponds to a tracked calendar
54
+ * entry. `null` for organizational nodes (filesystem-only — no
55
+ * calendar entry).
56
+ */
57
+ lane: Stage | null;
58
+ /**
59
+ * Underlying calendar entry, when present. `null` for organizational
60
+ * filesystem nodes. Distinguishes "tracked / has lane" from
61
+ * "structural only / no lane".
62
+ */
63
+ entry: CalendarEntry | null;
64
+ /**
65
+ * True when the node has its own `index.md` / `README.md` on disk.
66
+ * Used by the UI to pick branch / leaf icons and (for organizational
67
+ * nodes) to decide whether the detail panel can show a README excerpt.
68
+ */
69
+ hasOwnIndex: boolean;
70
+ /**
71
+ * True when the node was discovered by the filesystem walk
72
+ * (independent of whether it has a calendar entry). Pure-calendar
73
+ * nodes (entry exists, no fs directory) report false here. Used by
74
+ * the studio detail panel to decide whether organizational README
75
+ * content is fetchable.
76
+ */
77
+ hasFsDir: boolean;
78
+ /** Items at `<contentDir>/<path>/scrapbook/` (public + secret). */
79
+ scrapbookCount: number;
80
+ /**
81
+ * Most recent mtime across the node's scrapbook items (ISO8601), or
82
+ * `null` when the scrapbook is empty / absent.
83
+ */
84
+ scrapbookMostRecentMtime: string | null;
85
+ /** Direct children — already sorted in path order. */
86
+ children: ContentNode[];
87
+ }
88
+ /**
89
+ * Project-level tree summary returned by `buildContentTree`. Operators
90
+ * use this both for the per-project drilldown and for the top-level
91
+ * site cards (counts + lanes derive from the same shape).
92
+ */
93
+ export interface ContentProject {
94
+ site: string;
95
+ /**
96
+ * First path segment — the project root (e.g. `projects` for
97
+ * writingcontrol's hierarchical layout, or `my-flat-post` for an
98
+ * audiocontrol-shaped flat blog). Pre-19c this was "first slug
99
+ * segment"; post-19c the semantics shift to fs-driven. For flat
100
+ * layouts the value is unchanged; for hierarchical layouts it now
101
+ * captures the operator's organizational top-level (e.g. `projects`,
102
+ * `essays`) rather than a per-entry slug.
103
+ */
104
+ rootSlug: string;
105
+ /**
106
+ * Display name for the project. When a tracked entry exists at the
107
+ * root path, its `title` is used; otherwise the README frontmatter
108
+ * title or the rootSlug verbatim.
109
+ */
110
+ title: string;
111
+ /** Total tracked entries beneath this project (recursive). */
112
+ trackedCount: number;
113
+ /** Total tree nodes including organizational nodes (recursive). */
114
+ totalNodes: number;
115
+ /** Maximum depth (1 = single root node, 2 = root + leaves, …). */
116
+ maxDepth: number;
117
+ /** Sum of scrapbookCount across every node beneath this project. */
118
+ scrapbookCount: number;
119
+ /**
120
+ * Predominant lane across tracked nodes — the most-frequent stage,
121
+ * tie-broken by the lane order (`STAGES`). `null` when the project
122
+ * has no tracked entries.
123
+ */
124
+ predominantLane: Stage | null;
125
+ /** The project root node (its descendants live in `.children`). */
126
+ root: ContentNode;
127
+ }
128
+ /** Build-time options for `buildContentTree`. Test-friendly injection points. */
129
+ export interface BuildOptions {
130
+ /**
131
+ * Override scrapbook lookups — useful for tests that don't want to
132
+ * depend on the real filesystem layout. Defaults to `listScrapbook`
133
+ * from `@deskwork/core/scrapbook`.
134
+ */
135
+ scrapbookLookup?: (site: string, path: string) => {
136
+ items: {
137
+ mtime: string;
138
+ }[];
139
+ secretItems: {
140
+ mtime: string;
141
+ }[];
142
+ };
143
+ /**
144
+ * Override the filesystem walk — used by tests to inject a synthetic
145
+ * directory shape without writing to disk. Defaults to
146
+ * `defaultFsWalk` (recursive walk under `resolveContentDir`).
147
+ */
148
+ fsWalk?: (site: string) => readonly FsWalkEntry[];
149
+ /**
150
+ * Override the content index — used by tests + the studio's
151
+ * per-request memoization. Defaults to `buildContentIndex` per call.
152
+ * The index drives the entry-id → fs-path overlay; without it,
153
+ * calendar entries fall through to the legacy slug-fallback path.
154
+ */
155
+ contentIndex?: ContentIndex;
156
+ /**
157
+ * Optional logger for the legacy slug-fallback warning. Tests inject
158
+ * a spy here; production code lets it default to `console.warn`.
159
+ * Receives one warning per calendar entry whose id wasn't found in
160
+ * the content index but whose slug matched an fs node.
161
+ */
162
+ warn?: (message: string) => void;
163
+ }
164
+ /**
165
+ * One row of the depth-first flatten — used by the studio's tree
166
+ * renderer. `depth` starts at 0 for the project root; `isLast` is true
167
+ * when the node is the last child of its parent (the UI uses it to
168
+ * truncate tree connector lines).
169
+ */
170
+ export interface FlatNode {
171
+ node: ContentNode;
172
+ depth: number;
173
+ isLast: boolean;
174
+ }
175
+ //# sourceMappingURL=content-tree-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree-types.d.ts","sourceRoot":"","sources":["../src/content-tree-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kHAAkH;IAClH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,sDAAsD;IACtD,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;OAQG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,eAAe,EAAE,KAAK,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,KACT;QAAE,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,WAAW,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,WAAW,EAAE,CAAC;IAClD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public types for the bird's-eye content tree (Phase 16d / 19c).
3
+ *
4
+ * Extracted from `content-tree.ts` to keep the assembly module under
5
+ * the project's 500-line guideline. The tree builder re-exports every
6
+ * symbol from this module so callers continue importing from
7
+ * `@deskwork/core/content-tree`.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=content-tree-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree-types.js","sourceRoot":"","sources":["../src/content-tree-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,93 @@
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 type { CalendarEntry } from './types.ts';
49
+ import type { DeskworkConfig } from './config.ts';
50
+ import type { BuildOptions, ContentNode, ContentProject, FlatNode } from './content-tree-types.ts';
51
+ export { defaultFsWalk } from './content-tree-fs-walk.ts';
52
+ export type { FsWalkEntry } from './content-tree-fs-walk.ts';
53
+ export type { BuildOptions, ContentNode, ContentProject, FlatNode, } from './content-tree-types.ts';
54
+ /**
55
+ * Test-only: clear the legacy slug-fallback warning de-dup set so tests
56
+ * that exercise warning behavior can do so independently. Not part of the
57
+ * public API — exposed only because process-level state is awkward to
58
+ * reset from inside tests without a hook.
59
+ */
60
+ export declare function __resetLegacyFallbackWarnings(): void;
61
+ /**
62
+ * Build the content-tree projects for one site. Pure data — no HTML,
63
+ * no path-style decisions.
64
+ *
65
+ * Tree assembly (Phase 19c):
66
+ * 1. Walk the fs (`fsWalk`) to enumerate every directory under
67
+ * contentDir. Each fs entry contributes a candidate node keyed by
68
+ * its fs-relative path.
69
+ * 2. Build the content index (`buildContentIndex`) to map
70
+ * `relPath → entryId` based on frontmatter `id:`.
71
+ * 3. For each fs node with a markdown index/README, look up the
72
+ * bound entry id via `index.byPath`, then resolve the calendar
73
+ * entry by id. If found → overlay state (lane, title from entry,
74
+ * slug as a display attribute).
75
+ * 4. For calendar entries whose id is NOT in the index (their file
76
+ * isn't bound to frontmatter yet — pre-doctor state), do
77
+ * legacy slug fallback: look for an fs node whose path equals
78
+ * the entry's slug. If found → overlay with a one-time warning.
79
+ * If not found → place as a ghost node (today's behavior,
80
+ * preserved for backward compat).
81
+ */
82
+ export declare function buildContentTree(site: string, entries: readonly CalendarEntry[], config: DeskworkConfig, projectRoot: string, options?: BuildOptions): ContentProject[];
83
+ /**
84
+ * Helper: find the node with the given path under a project tree, or
85
+ * return null. Used by the studio's node-detail panel.
86
+ */
87
+ export declare function findNode(project: ContentProject, path: string): ContentNode | null;
88
+ /**
89
+ * Flatten the tree into a depth-first ordered list. See `FlatNode` in
90
+ * `content-tree-types.ts` for the row shape.
91
+ */
92
+ export declare function flattenForRender(root: ContentNode): FlatNode[];
93
+ //# sourceMappingURL=content-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-tree.d.ts","sourceRoot":"","sources":["../src/content-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAS,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAclD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACT,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,GACT,MAAM,yBAAyB,CAAC;AAkBjC;;;;;GAKG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,YAAiB,GACzB,cAAc,EAAE,CAsOlB;AAgDD;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GACX,WAAW,GAAG,IAAI,CAUpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,EAAE,CAW9D"}