@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,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":"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"}
|
|
@@ -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"}
|