@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,19 @@
1
+ /**
2
+ * Rule: calendar-uuid-missing.
3
+ *
4
+ * Detection: re-read the calendar markdown from disk and find rows
5
+ * whose UUID cell is empty or missing. The in-memory parser auto-
6
+ * backfills missing UUIDs, so the in-context calendar always has ids;
7
+ * we have to look at the on-disk bytes directly to see what hasn't
8
+ * been persisted yet.
9
+ *
10
+ * Repair: a single calendar write flushes the in-memory backfilled
11
+ * ids to disk. We read the calendar via `readCalendar` (which assigns
12
+ * UUIDs in-memory) and call `writeCalendar` to persist them.
13
+ *
14
+ * Sibling-relative imports per the project convention.
15
+ */
16
+ import type { DoctorRule } from '../types.ts';
17
+ declare const rule: DoctorRule;
18
+ export default rule;
19
+ //# sourceMappingURL=calendar-uuid-missing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-uuid-missing.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/calendar-uuid-missing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAmFrB,QAAA,MAAM,IAAI,EAAE,UA8FX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Rule: calendar-uuid-missing.
3
+ *
4
+ * Detection: re-read the calendar markdown from disk and find rows
5
+ * whose UUID cell is empty or missing. The in-memory parser auto-
6
+ * backfills missing UUIDs, so the in-context calendar always has ids;
7
+ * we have to look at the on-disk bytes directly to see what hasn't
8
+ * been persisted yet.
9
+ *
10
+ * Repair: a single calendar write flushes the in-memory backfilled
11
+ * ids to disk. We read the calendar via `readCalendar` (which assigns
12
+ * UUIDs in-memory) and call `writeCalendar` to persist them.
13
+ *
14
+ * Sibling-relative imports per the project convention.
15
+ */
16
+ import { readFileSync, existsSync } from 'node:fs';
17
+ import { resolveCalendarPath } from "../../paths.js";
18
+ import { readCalendar, writeCalendar } from "../../calendar.js";
19
+ const RULE_ID = 'calendar-uuid-missing';
20
+ const STAGE_HEADER_RE = /^##\s+(.+)\s*$/;
21
+ /**
22
+ * Walk the calendar markdown line-by-line, find stage tables, and
23
+ * report rows whose UUID column is empty/missing.
24
+ *
25
+ * Tolerant of column ordering: we identify the UUID column index from
26
+ * the table header (case-insensitive), then check each data row's
27
+ * cell at that index.
28
+ */
29
+ function scanRowsMissingUuid(markdown) {
30
+ const lines = markdown.split('\n');
31
+ const rows = [];
32
+ let currentStage = null;
33
+ let i = 0;
34
+ while (i < lines.length) {
35
+ const line = lines[i];
36
+ const stageMatch = line.match(STAGE_HEADER_RE);
37
+ if (stageMatch) {
38
+ const name = stageMatch[1].trim();
39
+ // Only the lifecycle stage tables; Distribution / Shortform
40
+ // sections aren't entry rows so we skip their tables here.
41
+ if (name === 'Distribution' || name === 'Shortform Copy') {
42
+ currentStage = null;
43
+ }
44
+ else {
45
+ currentStage = name;
46
+ }
47
+ i++;
48
+ continue;
49
+ }
50
+ if (line.startsWith('|') && currentStage) {
51
+ // Table found — first row is the header.
52
+ const headerCells = parseRow(line);
53
+ const uuidIdx = headerCells.findIndex((c) => c.trim().toLowerCase() === 'uuid' || c.trim().toLowerCase() === 'id');
54
+ const slugIdx = headerCells.findIndex((c) => c.trim().toLowerCase() === 'slug');
55
+ i++;
56
+ // Optional separator row.
57
+ if (i < lines.length && /^\|[\s:-]+\|/.test(lines[i]))
58
+ i++;
59
+ while (i < lines.length && lines[i].startsWith('|')) {
60
+ const cells = parseRow(lines[i]);
61
+ const slug = slugIdx >= 0 ? (cells[slugIdx] ?? '').trim() : '';
62
+ if (slug) {
63
+ const uuidCell = uuidIdx >= 0 ? (cells[uuidIdx] ?? '').trim() : '';
64
+ if (!uuidCell) {
65
+ rows.push({
66
+ slug,
67
+ stage: currentStage,
68
+ line: i + 1,
69
+ });
70
+ }
71
+ }
72
+ i++;
73
+ }
74
+ continue;
75
+ }
76
+ i++;
77
+ }
78
+ return rows;
79
+ }
80
+ function parseRow(line) {
81
+ return line.split('|').slice(1, -1).map((c) => c.trim());
82
+ }
83
+ const rule = {
84
+ id: RULE_ID,
85
+ label: 'Calendar rows with missing UUIDs (not yet persisted)',
86
+ async audit(ctx) {
87
+ const calendarPath = resolveCalendarPath(ctx.projectRoot, ctx.config, ctx.site);
88
+ if (!existsSync(calendarPath))
89
+ return [];
90
+ let raw;
91
+ try {
92
+ raw = readFileSync(calendarPath, 'utf-8');
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ const rows = scanRowsMissingUuid(raw);
98
+ if (rows.length === 0)
99
+ return [];
100
+ return [
101
+ {
102
+ ruleId: RULE_ID,
103
+ site: ctx.site,
104
+ severity: 'warning',
105
+ message: `${rows.length} calendar row(s) have no UUID on disk; in-memory parser will backfill on next write`,
106
+ details: {
107
+ calendarPath,
108
+ rows: rows.map((r) => ({ slug: r.slug, stage: r.stage, line: r.line })),
109
+ },
110
+ },
111
+ ];
112
+ },
113
+ async plan(ctx, finding) {
114
+ const calendarPath = String(finding.details.calendarPath ?? '');
115
+ if (!calendarPath) {
116
+ return {
117
+ kind: 'report-only',
118
+ finding,
119
+ reason: 'finding missing calendarPath — re-run audit',
120
+ };
121
+ }
122
+ return {
123
+ kind: 'apply',
124
+ finding,
125
+ summary: `re-write ${calendarPath} so the in-memory backfilled UUIDs land on disk`,
126
+ payload: { calendarPath, site: ctx.site },
127
+ };
128
+ },
129
+ async apply(ctx, plan) {
130
+ if (plan.kind !== 'apply') {
131
+ return {
132
+ finding: plan.finding,
133
+ applied: false,
134
+ message: 'plan is not directly appliable; runner should resolve prompt first',
135
+ skipReason: 'apply-failed',
136
+ };
137
+ }
138
+ const calendarPath = String(plan.payload.calendarPath ?? '');
139
+ if (!calendarPath) {
140
+ return {
141
+ finding: plan.finding,
142
+ applied: false,
143
+ message: 'apply payload missing calendarPath',
144
+ skipReason: 'apply-failed',
145
+ };
146
+ }
147
+ try {
148
+ // readCalendar populates missing UUIDs in-memory; writeCalendar
149
+ // flushes the populated calendar back to disk. One round-trip
150
+ // migrates every row — also true at the call site here.
151
+ const cal = readCalendar(calendarPath);
152
+ writeCalendar(calendarPath, cal);
153
+ }
154
+ catch (err) {
155
+ const reason = err instanceof Error ? err.message : String(err);
156
+ return {
157
+ finding: plan.finding,
158
+ applied: false,
159
+ message: `failed to re-write calendar: ${reason}`,
160
+ skipReason: 'apply-failed',
161
+ };
162
+ }
163
+ // Re-read what's now on disk to update the runner's view of
164
+ // ctx.calendar — though strictly the runner doesn't depend on
165
+ // it past this point.
166
+ void ctx;
167
+ return {
168
+ finding: plan.finding,
169
+ applied: true,
170
+ message: `re-wrote ${calendarPath} with UUIDs populated`,
171
+ details: { calendarPath },
172
+ };
173
+ },
174
+ };
175
+ export default rule;
176
+ //# sourceMappingURL=calendar-uuid-missing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-uuid-missing.js","sourceRoot":"","sources":["../../../src/doctor/rules/calendar-uuid-missing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAShE,MAAM,OAAO,GAAG,uBAAuB,CAAC;AAWxC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEzC;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAAC,QAAgB;IAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAC/C,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClC,4DAA4D;YAC5D,2DAA2D;YAC3D,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;gBACzD,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,EAAE,CAAC;YACzC,yCAAyC;YACzC,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,WAAW,CAAC,SAAS,CACnC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,IAAI,CAC5E,CAAC;YACF,MAAM,OAAO,GAAG,WAAW,CAAC,SAAS,CACnC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CACzC,CAAC;YACF,CAAC,EAAE,CAAC;YACJ,0BAA0B;YAC1B,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACjC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/D,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACnE,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,IAAI,CAAC,IAAI,CAAC;4BACR,IAAI;4BACJ,KAAK,EAAE,YAAY;4BACnB,IAAI,EAAE,CAAC,GAAG,CAAC;yBACZ,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;gBACD,CAAC,EAAE,CAAC;YACN,CAAC;YACD,SAAS;QACX,CAAC;QACD,CAAC,EAAE,CAAC;IACN,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,IAAI,GAAe;IACvB,EAAE,EAAE,OAAO;IACX,KAAK,EAAE,sDAAsD;IAE7D,KAAK,CAAC,KAAK,CAAC,GAAkB;QAC5B,MAAM,YAAY,GAAG,mBAAmB,CACtC,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,IAAI,CACT,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QACzC,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,OAAO;YACL;gBACE,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,qFAAqF;gBAC5G,OAAO,EAAE;oBACP,YAAY;oBACZ,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;iBACxE;aACF;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAkB,EAAE,OAAgB;QAC7C,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO;gBACP,MAAM,EAAE,6CAA6C;aACtD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO;YACP,OAAO,EAAE,YAAY,YAAY,iDAAiD;YAClF,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;SAC1C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAkB,EAAE,IAAgB;QAC9C,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,oEAAoE;gBAC7E,UAAU,EAAE,cAAc;aAC3B,CAAC;QACJ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,oCAAoC;gBAC7C,UAAU,EAAE,cAAc;aAC3B,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,gEAAgE;YAChE,8DAA8D;YAC9D,wDAAwD;YACxD,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;YACvC,aAAa,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,gCAAgC,MAAM,EAAE;gBACjD,UAAU,EAAE,cAAc;aAC3B,CAAC;QACJ,CAAC;QACD,4DAA4D;QAC5D,8DAA8D;QAC9D,sBAAsB;QACtB,KAAK,GAAG,CAAC;QACT,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,YAAY,YAAY,uBAAuB;YACxD,OAAO,EAAE,EAAE,YAAY,EAAE;SAC1B,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Rule: duplicate-id.
3
+ *
4
+ * Audit: more than one file under contentDir claims the same frontmatter
5
+ * id. The content index reports the byId map keeping only the first
6
+ * encountered, but byPath records every file. We re-walk byPath grouped
7
+ * by id and flag any group with > 1 entry.
8
+ *
9
+ * Repair: prompt the operator to pick a canonical file; clear the id
10
+ * from the others. With `--yes`, skip — picking a canonical file is
11
+ * an editorial decision, not something doctor should default.
12
+ */
13
+ import type { DoctorContext, DoctorRule } from '../types.ts';
14
+ interface DuplicateGroup {
15
+ id: string;
16
+ /** Absolute paths of every file claiming `id`. */
17
+ files: string[];
18
+ }
19
+ /**
20
+ * Group `index.byPath` (relPath → uuid) by uuid, return only groups
21
+ * with more than one file. Caller resolves relative paths against
22
+ * the site's contentDir to get absolute paths for repair.
23
+ */
24
+ export declare function findDuplicateGroups(ctx: DoctorContext): DuplicateGroup[];
25
+ declare const rule: DoctorRule;
26
+ export default rule;
27
+ //# sourceMappingURL=duplicate-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EAIX,MAAM,aAAa,CAAC;AAyBrB,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,GACjB,cAAc,EAAE,CAgBlB;AAED,QAAA,MAAM,IAAI,EAAE,UA2FX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Rule: duplicate-id.
3
+ *
4
+ * Audit: more than one file under contentDir claims the same frontmatter
5
+ * id. The content index reports the byId map keeping only the first
6
+ * encountered, but byPath records every file. We re-walk byPath grouped
7
+ * by id and flag any group with > 1 entry.
8
+ *
9
+ * Repair: prompt the operator to pick a canonical file; clear the id
10
+ * from the others. With `--yes`, skip — picking a canonical file is
11
+ * an editorial decision, not something doctor should default.
12
+ */
13
+ import { readFileSync, writeFileSync } from 'node:fs';
14
+ import { join, relative } from 'node:path';
15
+ import { parseFrontmatter, removeFrontmatterPaths } from "../../frontmatter.js";
16
+ import { resolveContentDir } from "../../paths.js";
17
+ const RULE_ID = 'duplicate-id';
18
+ /**
19
+ * Clear the `deskwork.id` field from a markdown file. Returns true when
20
+ * the field was present and cleared. Issue #38: scoped to the
21
+ * namespaced key — top-level `id:` belongs to the operator and is left
22
+ * alone.
23
+ */
24
+ function clearFrontmatterId(absPath) {
25
+ const raw = readFileSync(absPath, 'utf-8');
26
+ const { data } = parseFrontmatter(raw);
27
+ const block = data.deskwork;
28
+ if (block === undefined || block === null)
29
+ return false;
30
+ if (typeof block !== 'object' || Array.isArray(block))
31
+ return false;
32
+ const blockObj = block;
33
+ if (!('id' in blockObj))
34
+ return false;
35
+ const updated = removeFrontmatterPaths(raw, [['deskwork', 'id']]);
36
+ if (updated === raw)
37
+ return false;
38
+ writeFileSync(absPath, updated, 'utf-8');
39
+ return true;
40
+ }
41
+ /**
42
+ * Group `index.byPath` (relPath → uuid) by uuid, return only groups
43
+ * with more than one file. Caller resolves relative paths against
44
+ * the site's contentDir to get absolute paths for repair.
45
+ */
46
+ export function findDuplicateGroups(ctx) {
47
+ const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
48
+ const byUuid = new Map();
49
+ for (const [relPath, uuid] of ctx.index.byPath) {
50
+ const abs = join(contentDir, relPath);
51
+ const list = byUuid.get(uuid);
52
+ if (list)
53
+ list.push(abs);
54
+ else
55
+ byUuid.set(uuid, [abs]);
56
+ }
57
+ const groups = [];
58
+ for (const [uuid, files] of byUuid) {
59
+ if (files.length > 1) {
60
+ groups.push({ id: uuid, files: files.slice().sort() });
61
+ }
62
+ }
63
+ return groups;
64
+ }
65
+ const rule = {
66
+ id: RULE_ID,
67
+ label: 'Multiple files share the same frontmatter id',
68
+ async audit(ctx) {
69
+ const groups = findDuplicateGroups(ctx);
70
+ return groups.map((g) => ({
71
+ ruleId: RULE_ID,
72
+ site: ctx.site,
73
+ severity: 'error',
74
+ message: `id ${g.id} appears in ${g.files.length} files`,
75
+ details: { entryId: g.id, files: g.files },
76
+ }));
77
+ },
78
+ async plan(ctx, finding) {
79
+ const rawFiles = finding.details.files;
80
+ const files = Array.isArray(rawFiles)
81
+ ? rawFiles.filter((x) => typeof x === 'string')
82
+ : [];
83
+ if (files.length < 2) {
84
+ return {
85
+ kind: 'report-only',
86
+ finding,
87
+ reason: 'duplicate group has fewer than 2 files — re-run audit',
88
+ };
89
+ }
90
+ return {
91
+ kind: 'prompt',
92
+ finding,
93
+ question: `Multiple files claim id ${finding.details.entryId}. Pick the canonical file; the id will be cleared from the others.`,
94
+ choices: files.map((abs) => ({
95
+ id: abs,
96
+ label: relative(ctx.projectRoot, abs),
97
+ payload: { canonical: abs, others: files.filter((f) => f !== abs) },
98
+ })),
99
+ };
100
+ },
101
+ async apply(ctx, plan) {
102
+ if (plan.kind !== 'apply') {
103
+ return {
104
+ finding: plan.finding,
105
+ applied: false,
106
+ message: 'plan is not directly appliable; runner should resolve prompt first',
107
+ skipReason: 'apply-failed',
108
+ };
109
+ }
110
+ const canonical = String(plan.payload.canonical ?? '');
111
+ const othersRaw = plan.payload.others;
112
+ const others = Array.isArray(othersRaw)
113
+ ? (othersRaw.filter((x) => typeof x === 'string'))
114
+ : [];
115
+ if (!canonical || others.length === 0) {
116
+ return {
117
+ finding: plan.finding,
118
+ applied: false,
119
+ message: 'apply payload missing canonical or others',
120
+ skipReason: 'apply-failed',
121
+ };
122
+ }
123
+ const cleared = [];
124
+ const failed = [];
125
+ for (const abs of others) {
126
+ try {
127
+ const changed = clearFrontmatterId(abs);
128
+ if (changed)
129
+ cleared.push(abs);
130
+ }
131
+ catch {
132
+ failed.push(abs);
133
+ }
134
+ }
135
+ if (failed.length > 0) {
136
+ const partial = cleared.length > 0;
137
+ return {
138
+ finding: plan.finding,
139
+ applied: partial,
140
+ message: `cleared id from ${cleared.length} file(s); failed on ${failed.length}: ${failed.map((p) => relative(ctx.projectRoot, p)).join(', ')}`,
141
+ // When we cleared at least one file but some failed, treat as
142
+ // partial success — the `apply-failed` skip reason only fires
143
+ // when nothing landed on disk.
144
+ ...(partial ? {} : { skipReason: 'apply-failed' }),
145
+ details: { canonical, cleared, failed },
146
+ };
147
+ }
148
+ return {
149
+ finding: plan.finding,
150
+ applied: true,
151
+ message: `cleared id from ${cleared.length} file(s); canonical: ${relative(ctx.projectRoot, canonical)}`,
152
+ details: { canonical, cleared },
153
+ };
154
+ },
155
+ };
156
+ export default rule;
157
+ //# sourceMappingURL=duplicate-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
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 type { DoctorRule } from '../types.ts';
29
+ /**
30
+ * Apply the migration to a single file:
31
+ * 1. Add `deskwork.id` with the old value.
32
+ * 2. Remove the top-level `id:`.
33
+ *
34
+ * Both writes use the round-trip-preserving frontmatter API so every
35
+ * other byte stays put.
36
+ */
37
+ export declare function migrateLegacyTopLevelId(absPath: string, legacyId: string): void;
38
+ declare const rule: DoctorRule;
39
+ export default rule;
40
+ //# sourceMappingURL=legacy-top-level-id-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-top-level-id-migration.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/legacy-top-level-id-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAsGrB;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAM/E;AAED,QAAA,MAAM,IAAI,EAAE,UAqGX,CAAC;AAEF,eAAe,IAAI,CAAC"}