@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.
- package/dist/body-state.d.ts +27 -0
- package/dist/body-state.d.ts.map +1 -0
- package/dist/body-state.js +62 -0
- package/dist/body-state.js.map +1 -0
- package/dist/calendar-mutations.d.ts +124 -0
- package/dist/calendar-mutations.d.ts.map +1 -0
- package/dist/calendar-mutations.js +305 -0
- package/dist/calendar-mutations.js.map +1 -0
- package/dist/calendar.d.ts +54 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +430 -0
- package/dist/calendar.js.map +1 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +91 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +216 -0
- package/dist/config.js.map +1 -0
- package/dist/content-index.d.ts +74 -0
- package/dist/content-index.d.ts.map +1 -0
- package/dist/content-index.js +205 -0
- package/dist/content-index.js.map +1 -0
- package/dist/content-tree-fs-walk.d.ts +54 -0
- package/dist/content-tree-fs-walk.d.ts.map +1 -0
- package/dist/content-tree-fs-walk.js +112 -0
- package/dist/content-tree-fs-walk.js.map +1 -0
- package/dist/content-tree-helpers.d.ts +52 -0
- package/dist/content-tree-helpers.d.ts.map +1 -0
- package/dist/content-tree-helpers.js +116 -0
- package/dist/content-tree-helpers.js.map +1 -0
- package/dist/content-tree-types.d.ts +175 -0
- package/dist/content-tree-types.d.ts.map +1 -0
- package/dist/content-tree-types.js +10 -0
- package/dist/content-tree-types.js.map +1 -0
- package/dist/content-tree.d.ts +93 -0
- package/dist/content-tree.d.ts.map +1 -0
- package/dist/content-tree.js +385 -0
- package/dist/content-tree.js.map +1 -0
- package/dist/doctor/index.d.ts +11 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +10 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/project-rules.d.ts +59 -0
- package/dist/doctor/project-rules.d.ts.map +1 -0
- package/dist/doctor/project-rules.js +143 -0
- package/dist/doctor/project-rules.js.map +1 -0
- package/dist/doctor/rules/calendar-uuid-missing.d.ts +19 -0
- package/dist/doctor/rules/calendar-uuid-missing.d.ts.map +1 -0
- package/dist/doctor/rules/calendar-uuid-missing.js +176 -0
- package/dist/doctor/rules/calendar-uuid-missing.js.map +1 -0
- package/dist/doctor/rules/duplicate-id.d.ts +27 -0
- package/dist/doctor/rules/duplicate-id.d.ts.map +1 -0
- package/dist/doctor/rules/duplicate-id.js +157 -0
- package/dist/doctor/rules/duplicate-id.js.map +1 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts +40 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.js +232 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -0
- package/dist/doctor/rules/missing-frontmatter-id.d.ts +45 -0
- package/dist/doctor/rules/missing-frontmatter-id.d.ts.map +1 -0
- package/dist/doctor/rules/missing-frontmatter-id.js +283 -0
- package/dist/doctor/rules/missing-frontmatter-id.js.map +1 -0
- package/dist/doctor/rules/orphan-frontmatter-id.d.ts +18 -0
- package/dist/doctor/rules/orphan-frontmatter-id.d.ts.map +1 -0
- package/dist/doctor/rules/orphan-frontmatter-id.js +154 -0
- package/dist/doctor/rules/orphan-frontmatter-id.js.map +1 -0
- package/dist/doctor/rules/schema-rejected.d.ts +20 -0
- package/dist/doctor/rules/schema-rejected.d.ts.map +1 -0
- package/dist/doctor/rules/schema-rejected.js +44 -0
- package/dist/doctor/rules/schema-rejected.js.map +1 -0
- package/dist/doctor/rules/slug-collision.d.ts +18 -0
- package/dist/doctor/rules/slug-collision.d.ts.map +1 -0
- package/dist/doctor/rules/slug-collision.js +65 -0
- package/dist/doctor/rules/slug-collision.js.map +1 -0
- package/dist/doctor/rules/workflow-stale.d.ts +20 -0
- package/dist/doctor/rules/workflow-stale.d.ts.map +1 -0
- package/dist/doctor/rules/workflow-stale.js +136 -0
- package/dist/doctor/rules/workflow-stale.js.map +1 -0
- package/dist/doctor/runner.d.ts +75 -0
- package/dist/doctor/runner.d.ts.map +1 -0
- package/dist/doctor/runner.js +289 -0
- package/dist/doctor/runner.js.map +1 -0
- package/dist/doctor/schema-patch.d.ts +21 -0
- package/dist/doctor/schema-patch.d.ts.map +1 -0
- package/dist/doctor/schema-patch.js +92 -0
- package/dist/doctor/schema-patch.js.map +1 -0
- package/dist/doctor/types.d.ts +185 -0
- package/dist/doctor/types.d.ts.map +1 -0
- package/dist/doctor/types.js +13 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/frontmatter.d.ts +103 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +306 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-derive.d.ts +79 -0
- package/dist/ingest-derive.d.ts.map +1 -0
- package/dist/ingest-derive.js +299 -0
- package/dist/ingest-derive.js.map +1 -0
- package/dist/ingest-paths.d.ts +37 -0
- package/dist/ingest-paths.d.ts.map +1 -0
- package/dist/ingest-paths.js +176 -0
- package/dist/ingest-paths.js.map +1 -0
- package/dist/ingest.d.ts +162 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +269 -0
- package/dist/ingest.js.map +1 -0
- package/dist/journal.d.ts +49 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +113 -0
- package/dist/journal.js.map +1 -0
- package/dist/outline-split.d.ts +38 -0
- package/dist/outline-split.d.ts.map +1 -0
- package/dist/outline-split.js +84 -0
- package/dist/outline-split.js.map +1 -0
- package/dist/overrides.d.ts +83 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +88 -0
- package/dist/overrides.js.map +1 -0
- package/dist/paths.d.ts +183 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +266 -0
- package/dist/paths.js.map +1 -0
- package/dist/remark-image-figure.mjs +77 -0
- package/dist/remark-strip-first-h1.mjs +26 -0
- package/dist/remark-strip-outline.mjs +44 -0
- package/dist/rename-slug.d.ts +49 -0
- package/dist/rename-slug.d.ts.map +1 -0
- package/dist/rename-slug.js +161 -0
- package/dist/rename-slug.js.map +1 -0
- package/dist/review/handlers.d.ts +55 -0
- package/dist/review/handlers.d.ts.map +1 -0
- package/dist/review/handlers.js +307 -0
- package/dist/review/handlers.js.map +1 -0
- package/dist/review/index.d.ts +14 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/index.js +13 -0
- package/dist/review/index.js.map +1 -0
- package/dist/review/journal-mappers.d.ts +35 -0
- package/dist/review/journal-mappers.d.ts.map +1 -0
- package/dist/review/journal-mappers.js +48 -0
- package/dist/review/journal-mappers.js.map +1 -0
- package/dist/review/pipeline.d.ts +79 -0
- package/dist/review/pipeline.d.ts.map +1 -0
- package/dist/review/pipeline.js +234 -0
- package/dist/review/pipeline.js.map +1 -0
- package/dist/review/render.d.ts +27 -0
- package/dist/review/render.d.ts.map +1 -0
- package/dist/review/render.js +42 -0
- package/dist/review/render.js.map +1 -0
- package/dist/review/report.d.ts +50 -0
- package/dist/review/report.d.ts.map +1 -0
- package/dist/review/report.js +164 -0
- package/dist/review/report.js.map +1 -0
- package/dist/review/result.d.ts +12 -0
- package/dist/review/result.d.ts.map +1 -0
- package/dist/review/result.js +12 -0
- package/dist/review/result.js.map +1 -0
- package/dist/review/start-handlers.d.ts +62 -0
- package/dist/review/start-handlers.d.ts.map +1 -0
- package/dist/review/start-handlers.js +223 -0
- package/dist/review/start-handlers.js.map +1 -0
- package/dist/review/types.d.ts +169 -0
- package/dist/review/types.d.ts.map +1 -0
- package/dist/review/types.js +26 -0
- package/dist/review/types.js.map +1 -0
- package/dist/review/workflow-paths.d.ts +68 -0
- package/dist/review/workflow-paths.d.ts.map +1 -0
- package/dist/review/workflow-paths.js +112 -0
- package/dist/review/workflow-paths.js.map +1 -0
- package/dist/scaffold.d.ts +67 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +122 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/scrapbook.d.ts +229 -0
- package/dist/scrapbook.d.ts.map +1 -0
- package/dist/scrapbook.js +500 -0
- package/dist/scrapbook.js.map +1 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +120 -0
- package/dist/types.js.map +1 -0
- 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"}
|