@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,232 @@
1
+ /**
2
+ * Rule: legacy-top-level-id-migration.
3
+ *
4
+ * Issue #38: v0.7.0 / v0.7.1 of deskwork wrote (and read) the calendar
5
+ * binding key as a top-level `id:` field in frontmatter. Starting in
6
+ * v0.7.2 the canonical location is `deskwork.id`, scoping the binding
7
+ * to a `deskwork:` namespace so deskwork doesn't claim the operator's
8
+ * global keyspace.
9
+ *
10
+ * Audit: walk every markdown file under `<contentDir>` and find files
11
+ * where:
12
+ * 1. top-level `id:` is present AND its value is a UUID matching a
13
+ * calendar entry, AND
14
+ * 2. `deskwork.id` is NOT present.
15
+ *
16
+ * The (1)+(2) conjunction makes the rule idempotent: once a file has
17
+ * migrated to the namespaced form, it is no longer reported. Files
18
+ * with a top-level `id:` whose value is NOT a calendar UUID belong to
19
+ * the operator and are left alone.
20
+ *
21
+ * Repair: round-trip-preserving rewrite — add `deskwork.id` with the
22
+ * old value, remove the top-level `id:`, leave every other byte
23
+ * untouched. Safe for `--yes` / `--fix=all` mode (clear-and-move with
24
+ * no editorial decision required).
25
+ *
26
+ * Sibling-relative imports per the project convention.
27
+ */
28
+ import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
29
+ import { extname, join, relative } from 'node:path';
30
+ import { resolveContentDir } from "../../paths.js";
31
+ import { parseFrontmatter, removeFrontmatterPaths, updateFrontmatter, } from "../../frontmatter.js";
32
+ const RULE_ID = 'legacy-top-level-id-migration';
33
+ const MARKDOWN_EXTENSIONS = new Set([
34
+ '.md',
35
+ '.mdx',
36
+ '.markdown',
37
+ ]);
38
+ const SKIP_DIRS = new Set([
39
+ 'scrapbook',
40
+ 'node_modules',
41
+ 'dist',
42
+ '.git',
43
+ ]);
44
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
45
+ function shouldSkipDir(name) {
46
+ if (name.startsWith('.'))
47
+ return true;
48
+ return SKIP_DIRS.has(name.toLowerCase());
49
+ }
50
+ function collectMarkdownFiles(dir) {
51
+ const out = [];
52
+ visit(dir);
53
+ out.sort();
54
+ return out;
55
+ function visit(currentDir) {
56
+ let names;
57
+ try {
58
+ names = readdirSync(currentDir);
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ for (const name of names) {
64
+ const abs = join(currentDir, name);
65
+ let st;
66
+ try {
67
+ st = statSync(abs);
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ if (st.isDirectory()) {
73
+ if (shouldSkipDir(name))
74
+ continue;
75
+ visit(abs);
76
+ continue;
77
+ }
78
+ if (!st.isFile())
79
+ continue;
80
+ if (MARKDOWN_EXTENSIONS.has(extname(name).toLowerCase())) {
81
+ out.push(abs);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Inspect a single file's frontmatter and decide if it qualifies for
88
+ * migration. The conjunction is strict on purpose:
89
+ * - top-level `id:` must be a string AND match the UUID shape
90
+ * - that UUID must be present in the calendar (otherwise it isn't
91
+ * deskwork's id; it's the operator's)
92
+ * - `deskwork.id` must NOT exist (otherwise the file is already on
93
+ * the new shape; ignoring leaves the rule idempotent across runs)
94
+ */
95
+ function classify(absPath, calendarIds) {
96
+ let parsed;
97
+ try {
98
+ parsed = parseFrontmatter(readFileSync(absPath, 'utf-8'));
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ const topLevelId = parsed.data.id;
104
+ if (typeof topLevelId !== 'string')
105
+ return null;
106
+ const trimmed = topLevelId.trim();
107
+ if (trimmed === '' || !UUID_RE.test(trimmed))
108
+ return null;
109
+ if (!calendarIds.has(trimmed))
110
+ return null;
111
+ const block = parsed.data.deskwork;
112
+ if (block !== undefined && block !== null) {
113
+ if (typeof block === 'object' && !Array.isArray(block)) {
114
+ const nestedId = block.id;
115
+ if (typeof nestedId === 'string' && nestedId.trim() !== '') {
116
+ return null;
117
+ }
118
+ }
119
+ }
120
+ return { absolutePath: absPath, legacyId: trimmed };
121
+ }
122
+ /**
123
+ * Apply the migration to a single file:
124
+ * 1. Add `deskwork.id` with the old value.
125
+ * 2. Remove the top-level `id:`.
126
+ *
127
+ * Both writes use the round-trip-preserving frontmatter API so every
128
+ * other byte stays put.
129
+ */
130
+ export function migrateLegacyTopLevelId(absPath, legacyId) {
131
+ const raw = readFileSync(absPath, 'utf-8');
132
+ const withDeskwork = updateFrontmatter(raw, { deskwork: { id: legacyId } });
133
+ const withoutTopLevel = removeFrontmatterPaths(withDeskwork, [['id']]);
134
+ if (withoutTopLevel === raw)
135
+ return;
136
+ writeFileSync(absPath, withoutTopLevel, 'utf-8');
137
+ }
138
+ const rule = {
139
+ id: RULE_ID,
140
+ label: 'Frontmatter id at top level should be under `deskwork:` namespace',
141
+ async audit(ctx) {
142
+ const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
143
+ const calendarIds = new Set();
144
+ for (const e of ctx.calendar.entries) {
145
+ if (e.id)
146
+ calendarIds.add(e.id);
147
+ }
148
+ let files;
149
+ try {
150
+ files = collectMarkdownFiles(contentDir);
151
+ }
152
+ catch {
153
+ return [];
154
+ }
155
+ const findings = [];
156
+ for (const abs of files) {
157
+ const hit = classify(abs, calendarIds);
158
+ if (hit === null)
159
+ continue;
160
+ findings.push({
161
+ ruleId: RULE_ID,
162
+ site: ctx.site,
163
+ severity: 'warning',
164
+ message: `File ${relative(ctx.projectRoot, abs)} has a top-level \`id:\` ` +
165
+ `that should be migrated under \`deskwork.id\``,
166
+ details: {
167
+ absolutePath: abs,
168
+ legacyId: hit.legacyId,
169
+ },
170
+ });
171
+ }
172
+ return findings;
173
+ },
174
+ async plan(_ctx, finding) {
175
+ const absPath = String(finding.details.absolutePath ?? '');
176
+ const legacyId = String(finding.details.legacyId ?? '');
177
+ if (!absPath || !legacyId) {
178
+ return {
179
+ kind: 'report-only',
180
+ finding,
181
+ reason: 'finding missing absolutePath or legacyId — re-run audit',
182
+ };
183
+ }
184
+ return {
185
+ kind: 'apply',
186
+ finding,
187
+ summary: `move top-level id ${legacyId} to deskwork.id in ${absPath}`,
188
+ payload: { absolutePath: absPath, legacyId },
189
+ };
190
+ },
191
+ async apply(ctx, plan) {
192
+ if (plan.kind !== 'apply') {
193
+ return {
194
+ finding: plan.finding,
195
+ applied: false,
196
+ message: 'plan is not directly appliable; runner should resolve prompt first',
197
+ skipReason: 'apply-failed',
198
+ };
199
+ }
200
+ const absPath = String(plan.payload.absolutePath ?? '');
201
+ const legacyId = String(plan.payload.legacyId ?? '');
202
+ if (!absPath || !legacyId) {
203
+ return {
204
+ finding: plan.finding,
205
+ applied: false,
206
+ message: 'apply payload missing absolutePath or legacyId',
207
+ skipReason: 'apply-failed',
208
+ };
209
+ }
210
+ try {
211
+ migrateLegacyTopLevelId(absPath, legacyId);
212
+ }
213
+ catch (err) {
214
+ const reason = err instanceof Error ? err.message : String(err);
215
+ return {
216
+ finding: plan.finding,
217
+ applied: false,
218
+ message: `failed to migrate frontmatter id: ${reason}`,
219
+ skipReason: 'apply-failed',
220
+ };
221
+ }
222
+ return {
223
+ finding: plan.finding,
224
+ applied: true,
225
+ message: `migrated id ${legacyId} from top-level to deskwork.id in ` +
226
+ relative(ctx.projectRoot, absPath),
227
+ details: { absolutePath: absPath, legacyId },
228
+ };
229
+ },
230
+ };
231
+ export default rule;
232
+ //# sourceMappingURL=legacy-top-level-id-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-top-level-id-migration.js","sourceRoot":"","sources":["../../../src/doctor/rules/legacy-top-level-id-migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Rule: missing-frontmatter-id.
3
+ *
4
+ * Audit: every calendar entry whose `id` is not a key in `index.byId`
5
+ * is a finding. The file binding for that entry isn't there yet.
6
+ *
7
+ * Repair: search the content tree for candidate files. Three searches,
8
+ * widening:
9
+ * 1. The file at the slug-template path (today's behavior — pre-Phase-19
10
+ * scaffolds and most Astro flat layouts).
11
+ * 2. Any file whose frontmatter `title` matches the entry title.
12
+ * 3. Any file whose basename (without extension) matches the slug.
13
+ *
14
+ * If exactly one candidate emerges, the plan is "write `id: <entry.id>`
15
+ * into <path>". Multiple candidates produce a prompt the operator
16
+ * resolves; `--yes` mode skips. Zero candidates is reported and skipped
17
+ * (nothing to repair without operator input — the file may not exist
18
+ * at all yet).
19
+ *
20
+ * Sibling-relative imports per the project convention.
21
+ */
22
+ import type { CalendarEntry } from '../../types.ts';
23
+ import type { DoctorContext, DoctorRule } from '../types.ts';
24
+ interface CandidateFile {
25
+ /** Absolute path to the candidate. */
26
+ absolutePath: string;
27
+ /** Why this file was considered (for operator-facing labels). */
28
+ matchReason: 'template-path' | 'title-match' | 'basename-match';
29
+ }
30
+ /**
31
+ * Search the content tree for files that could be bound to `entry`.
32
+ * Excludes files already claiming a (different) id — those are not
33
+ * candidates here; the duplicate-id and orphan rules handle them.
34
+ */
35
+ export declare function findCandidatesForEntry(projectRoot: string, config: DoctorContext['config'], site: string, entry: CalendarEntry): CandidateFile[];
36
+ /**
37
+ * Write `deskwork.id: <entryId>` into the markdown file's frontmatter.
38
+ * Idempotent when the file already carries the same id (no-op write
39
+ * avoided). Issue #38: writes under the `deskwork:` namespace, not the
40
+ * global top-level `id:`.
41
+ */
42
+ export declare function bindFrontmatterId(absPath: string, entryId: string): void;
43
+ declare const rule: DoctorRule;
44
+ export default rule;
45
+ //# sourceMappingURL=missing-frontmatter-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-frontmatter-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/missing-frontmatter-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EAIX,MAAM,aAAa,CAAC;AAOrB,UAAU,aAAa;IACrB,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,WAAW,EAAE,eAAe,GAAG,aAAa,GAAG,gBAAgB,CAAC;CACjE;AAuDD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,GACnB,aAAa,EAAE,CAyDjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAKxE;AAkBD,QAAA,MAAM,IAAI,EAAE,UAmHX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Rule: missing-frontmatter-id.
3
+ *
4
+ * Audit: every calendar entry whose `id` is not a key in `index.byId`
5
+ * is a finding. The file binding for that entry isn't there yet.
6
+ *
7
+ * Repair: search the content tree for candidate files. Three searches,
8
+ * widening:
9
+ * 1. The file at the slug-template path (today's behavior — pre-Phase-19
10
+ * scaffolds and most Astro flat layouts).
11
+ * 2. Any file whose frontmatter `title` matches the entry title.
12
+ * 3. Any file whose basename (without extension) matches the slug.
13
+ *
14
+ * If exactly one candidate emerges, the plan is "write `id: <entry.id>`
15
+ * into <path>". Multiple candidates produce a prompt the operator
16
+ * resolves; `--yes` mode skips. Zero candidates is reported and skipped
17
+ * (nothing to repair without operator input — the file may not exist
18
+ * at all yet).
19
+ *
20
+ * Sibling-relative imports per the project convention.
21
+ */
22
+ import { existsSync, readdirSync, statSync } from 'node:fs';
23
+ import { basename, extname, join, relative } from 'node:path';
24
+ import { resolveBlogFilePath, resolveContentDir } from "../../paths.js";
25
+ import { readFrontmatter, updateFrontmatter } from "../../frontmatter.js";
26
+ import { readFileSync, writeFileSync } from 'node:fs';
27
+ const RULE_ID = 'missing-frontmatter-id';
28
+ const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx', '.markdown']);
29
+ const SKIP_DIRS = new Set(['scrapbook', 'node_modules', 'dist', '.git']);
30
+ function shouldSkipDir(name) {
31
+ if (name.startsWith('.'))
32
+ return true;
33
+ return SKIP_DIRS.has(name.toLowerCase());
34
+ }
35
+ function collectMarkdownFiles(dir) {
36
+ const out = [];
37
+ visit(dir);
38
+ out.sort();
39
+ return out;
40
+ function visit(currentDir) {
41
+ let names;
42
+ try {
43
+ names = readdirSync(currentDir);
44
+ }
45
+ catch {
46
+ return;
47
+ }
48
+ for (const name of names) {
49
+ const abs = join(currentDir, name);
50
+ let st;
51
+ try {
52
+ st = statSync(abs);
53
+ }
54
+ catch {
55
+ continue;
56
+ }
57
+ if (st.isDirectory()) {
58
+ if (shouldSkipDir(name))
59
+ continue;
60
+ visit(abs);
61
+ continue;
62
+ }
63
+ if (!st.isFile())
64
+ continue;
65
+ if (MARKDOWN_EXTENSIONS.has(extname(name).toLowerCase())) {
66
+ out.push(abs);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ function readTitle(absPath) {
72
+ try {
73
+ const parsed = readFrontmatter(absPath);
74
+ const t = parsed.data.title;
75
+ return typeof t === 'string' ? t : undefined;
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ function basenameNoExt(p) {
82
+ return basename(p, extname(p));
83
+ }
84
+ /**
85
+ * Search the content tree for files that could be bound to `entry`.
86
+ * Excludes files already claiming a (different) id — those are not
87
+ * candidates here; the duplicate-id and orphan rules handle them.
88
+ */
89
+ export function findCandidatesForEntry(projectRoot, config, site, entry) {
90
+ const candidates = [];
91
+ const seen = new Set();
92
+ const contentDir = resolveContentDir(projectRoot, config, site);
93
+ function consider(abs, reason) {
94
+ if (!existsSync(abs))
95
+ return;
96
+ if (seen.has(abs))
97
+ return;
98
+ // Skip files whose frontmatter already has a deskwork.id (they
99
+ // belong to another entry or are duplicates — out of this rule's
100
+ // scope). Top-level `id:` is the operator's keyspace per Issue #38
101
+ // and doesn't disqualify a file as a candidate.
102
+ try {
103
+ const parsed = readFrontmatter(abs);
104
+ const existingId = readDeskworkId(parsed.data);
105
+ if (existingId !== undefined) {
106
+ return;
107
+ }
108
+ }
109
+ catch {
110
+ // Unreadable frontmatter — skip; the index already reports it.
111
+ return;
112
+ }
113
+ seen.add(abs);
114
+ candidates.push({ absolutePath: abs, matchReason: reason });
115
+ }
116
+ // 1. Slug-template path.
117
+ const templatePath = resolveBlogFilePath(projectRoot, config, site, entry.slug);
118
+ consider(templatePath, 'template-path');
119
+ // 2. + 3. Walk content tree once, match by title and basename.
120
+ let files;
121
+ try {
122
+ files = collectMarkdownFiles(contentDir);
123
+ }
124
+ catch {
125
+ return candidates;
126
+ }
127
+ const slugBasename = entry.slug.split('/').pop() ?? entry.slug;
128
+ for (const abs of files) {
129
+ if (seen.has(abs))
130
+ continue;
131
+ const title = readTitle(abs);
132
+ if (title !== undefined && title.trim() === entry.title.trim()) {
133
+ consider(abs, 'title-match');
134
+ continue;
135
+ }
136
+ const bn = basenameNoExt(abs);
137
+ if (bn === slugBasename) {
138
+ consider(abs, 'basename-match');
139
+ }
140
+ }
141
+ return candidates;
142
+ }
143
+ /**
144
+ * Write `deskwork.id: <entryId>` into the markdown file's frontmatter.
145
+ * Idempotent when the file already carries the same id (no-op write
146
+ * avoided). Issue #38: writes under the `deskwork:` namespace, not the
147
+ * global top-level `id:`.
148
+ */
149
+ export function bindFrontmatterId(absPath, entryId) {
150
+ const raw = readFileSync(absPath, 'utf-8');
151
+ const updated = updateFrontmatter(raw, { deskwork: { id: entryId } });
152
+ if (updated === raw)
153
+ return;
154
+ writeFileSync(absPath, updated, 'utf-8');
155
+ }
156
+ /**
157
+ * Read `deskwork.id` from frontmatter data. Returns `undefined` when
158
+ * either the `deskwork` block or the nested `id` is missing or
159
+ * malformed. Used by candidate filters and the migration rule to keep
160
+ * the namespaced-key reads consistent across rules.
161
+ */
162
+ function readDeskworkId(data) {
163
+ const block = data.deskwork;
164
+ if (block === undefined || block === null)
165
+ return undefined;
166
+ if (typeof block !== 'object' || Array.isArray(block))
167
+ return undefined;
168
+ const id = block.id;
169
+ if (typeof id !== 'string')
170
+ return undefined;
171
+ const trimmed = id.trim();
172
+ return trimmed === '' ? undefined : trimmed;
173
+ }
174
+ const rule = {
175
+ id: RULE_ID,
176
+ label: 'Calendar entries with no matching frontmatter id',
177
+ async audit(ctx) {
178
+ const findings = [];
179
+ for (const entry of ctx.calendar.entries) {
180
+ if (!entry.id) {
181
+ // Calendar entries without an id are reported by
182
+ // calendar-uuid-missing instead — keep concerns separate.
183
+ continue;
184
+ }
185
+ if (ctx.index.byId.has(entry.id))
186
+ continue;
187
+ findings.push({
188
+ ruleId: RULE_ID,
189
+ site: ctx.site,
190
+ severity: 'warning',
191
+ message: `Entry "${entry.slug}" (id ${entry.id}) is not bound to any file via frontmatter id`,
192
+ details: {
193
+ entryId: entry.id,
194
+ slug: entry.slug,
195
+ title: entry.title,
196
+ stage: entry.stage,
197
+ },
198
+ });
199
+ }
200
+ return findings;
201
+ },
202
+ async plan(ctx, finding) {
203
+ const entryId = String(finding.details.entryId ?? '');
204
+ const entry = ctx.calendar.entries.find((e) => e.id === entryId);
205
+ if (!entry) {
206
+ return {
207
+ kind: 'report-only',
208
+ finding,
209
+ reason: `entry id ${entryId} no longer present in calendar — re-run audit`,
210
+ };
211
+ }
212
+ const candidates = findCandidatesForEntry(ctx.projectRoot, ctx.config, ctx.site, entry);
213
+ const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
214
+ if (candidates.length === 0) {
215
+ return {
216
+ kind: 'report-only',
217
+ finding,
218
+ reason: `no candidate file found under ${contentDir} for slug "${entry.slug}". ` +
219
+ 'Create the file (e.g. via `deskwork outline`) or move an existing one ' +
220
+ 'into place, then re-run.',
221
+ };
222
+ }
223
+ if (candidates.length === 1) {
224
+ const c = candidates[0];
225
+ return {
226
+ kind: 'apply',
227
+ finding,
228
+ summary: `write \`id: ${entry.id}\` into ${relative(ctx.projectRoot, c.absolutePath)} (${c.matchReason})`,
229
+ payload: { absolutePath: c.absolutePath, entryId: entry.id },
230
+ };
231
+ }
232
+ return {
233
+ kind: 'prompt',
234
+ finding,
235
+ question: `Multiple candidate files for entry "${entry.slug}" (id ${entry.id}). Which file should carry the id?`,
236
+ choices: candidates.map((c) => ({
237
+ id: c.absolutePath,
238
+ label: `${relative(ctx.projectRoot, c.absolutePath)} (${c.matchReason})`,
239
+ payload: { absolutePath: c.absolutePath, entryId: entry.id },
240
+ })),
241
+ };
242
+ },
243
+ async apply(ctx, plan) {
244
+ if (plan.kind !== 'apply') {
245
+ return {
246
+ finding: plan.finding,
247
+ applied: false,
248
+ message: 'plan is not directly appliable; runner should resolve prompt first',
249
+ skipReason: 'apply-failed',
250
+ };
251
+ }
252
+ const absPath = String(plan.payload.absolutePath ?? '');
253
+ const entryId = String(plan.payload.entryId ?? '');
254
+ if (!absPath || !entryId) {
255
+ return {
256
+ finding: plan.finding,
257
+ applied: false,
258
+ message: 'apply payload missing absolutePath or entryId',
259
+ skipReason: 'apply-failed',
260
+ };
261
+ }
262
+ try {
263
+ bindFrontmatterId(absPath, entryId);
264
+ }
265
+ catch (err) {
266
+ const reason = err instanceof Error ? err.message : String(err);
267
+ return {
268
+ finding: plan.finding,
269
+ applied: false,
270
+ message: `failed to write frontmatter id: ${reason}`,
271
+ skipReason: 'apply-failed',
272
+ };
273
+ }
274
+ return {
275
+ finding: plan.finding,
276
+ applied: true,
277
+ message: `wrote id ${entryId} to ${relative(ctx.projectRoot, absPath)}`,
278
+ details: { absolutePath: absPath, entryId },
279
+ };
280
+ },
281
+ };
282
+ export default rule;
283
+ //# sourceMappingURL=missing-frontmatter-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-frontmatter-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/missing-frontmatter-id.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Rule: orphan-frontmatter-id.
3
+ *
4
+ * Audit: every entry in the content index whose id has no matching
5
+ * calendar entry. The file is bound to "something", but the calendar
6
+ * doesn't know about it.
7
+ *
8
+ * Repair: there are three plausible operator intents — (a) add a
9
+ * calendar row for the file, (b) clear the orphan id from the file
10
+ * (un-bind), or (c) leave it alone. Without a way to gather the
11
+ * intent, the rule reports findings and presents a prompt; with
12
+ * `--yes`, the safest action is "do nothing" — auto-creating
13
+ * calendar rows or auto-deleting frontmatter is destructive.
14
+ */
15
+ import type { DoctorRule } from '../types.ts';
16
+ declare const rule: DoctorRule;
17
+ export default rule;
18
+ //# sourceMappingURL=orphan-frontmatter-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphan-frontmatter-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/orphan-frontmatter-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AA6BrB,QAAA,MAAM,IAAI,EAAE,UA0GX,CAAC;AAEF,eAAe,IAAI,CAAC"}