@deskwork/core 0.45.2 → 0.46.0
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/config.d.ts +14 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -12
- package/dist/config.js.map +1 -1
- package/dist/content-index.d.ts +25 -0
- package/dist/content-index.d.ts.map +1 -1
- package/dist/content-index.js +142 -15
- package/dist/content-index.js.map +1 -1
- package/dist/doctor/index.d.ts +3 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +3 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/legacy-config.d.ts +98 -0
- package/dist/doctor/legacy-config.d.ts.map +1 -0
- package/dist/doctor/legacy-config.js +174 -0
- package/dist/doctor/legacy-config.js.map +1 -0
- package/dist/doctor/project-scope-gate.d.ts +12 -21
- package/dist/doctor/project-scope-gate.d.ts.map +1 -1
- package/dist/doctor/project-scope-gate.js +13 -25
- package/dist/doctor/project-scope-gate.js.map +1 -1
- package/dist/doctor/repair.d.ts +11 -0
- package/dist/doctor/repair.d.ts.map +1 -1
- package/dist/doctor/repair.js +10 -89
- package/dist/doctor/repair.js.map +1 -1
- package/dist/doctor/rules/duplicate-id.d.ts +7 -2
- package/dist/doctor/rules/duplicate-id.d.ts.map +1 -1
- package/dist/doctor/rules/duplicate-id.js +8 -5
- package/dist/doctor/rules/duplicate-id.js.map +1 -1
- package/dist/doctor/rules/duplicate-id.ts +8 -5
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -1
- package/dist/doctor/rules/legacy-top-level-id-migration.js +11 -8
- package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -1
- package/dist/doctor/rules/legacy-top-level-id-migration.ts +10 -11
- package/dist/doctor/rules/sites-to-lanes-migration.d.ts +39 -0
- package/dist/doctor/rules/sites-to-lanes-migration.d.ts.map +1 -0
- package/dist/doctor/rules/sites-to-lanes-migration.js +395 -0
- package/dist/doctor/rules/sites-to-lanes-migration.js.map +1 -0
- package/dist/doctor/rules/sites-to-lanes-migration.ts +449 -0
- package/dist/doctor/rules/workflow-stale.d.ts.map +1 -1
- package/dist/doctor/rules/workflow-stale.js +5 -2
- package/dist/doctor/rules/workflow-stale.js.map +1 -1
- package/dist/doctor/rules/workflow-stale.ts +5 -1
- package/dist/doctor/runner.d.ts +15 -1
- package/dist/doctor/runner.d.ts.map +1 -1
- package/dist/doctor/runner.js +48 -41
- package/dist/doctor/runner.js.map +1 -1
- package/dist/doctor/sites-migration-backfill.d.ts +71 -0
- package/dist/doctor/sites-migration-backfill.d.ts.map +1 -0
- package/dist/doctor/sites-migration-backfill.js +164 -0
- package/dist/doctor/sites-migration-backfill.js.map +1 -0
- package/dist/doctor/validate.d.ts.map +1 -1
- package/dist/doctor/validate.js +34 -73
- package/dist/doctor/validate.js.map +1 -1
- package/dist/entry/resolve-artifact.d.ts +80 -0
- package/dist/entry/resolve-artifact.d.ts.map +1 -0
- package/dist/entry/resolve-artifact.js +102 -0
- package/dist/entry/resolve-artifact.js.map +1 -0
- package/dist/entry/shortform-path.d.ts +34 -0
- package/dist/entry/shortform-path.d.ts.map +1 -0
- package/dist/entry/shortform-path.js +50 -0
- package/dist/entry/shortform-path.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/iterate/iterate.d.ts.map +1 -1
- package/dist/iterate/iterate.js +21 -26
- package/dist/iterate/iterate.js.map +1 -1
- package/dist/lanes/bootstrap.d.ts +11 -7
- package/dist/lanes/bootstrap.d.ts.map +1 -1
- package/dist/lanes/bootstrap.js +42 -17
- package/dist/lanes/bootstrap.js.map +1 -1
- package/dist/lanes/index.d.ts +1 -0
- package/dist/lanes/index.d.ts.map +1 -1
- package/dist/lanes/index.js +6 -0
- package/dist/lanes/index.js.map +1 -1
- package/dist/lanes/loader.d.ts +33 -6
- package/dist/lanes/loader.d.ts.map +1 -1
- package/dist/lanes/loader.js +44 -11
- package/dist/lanes/loader.js.map +1 -1
- package/dist/lanes/operations/create.d.ts +11 -2
- package/dist/lanes/operations/create.d.ts.map +1 -1
- package/dist/lanes/operations/create.js +15 -4
- package/dist/lanes/operations/create.js.map +1 -1
- package/dist/lanes/operations/list.d.ts +3 -2
- package/dist/lanes/operations/list.d.ts.map +1 -1
- package/dist/lanes/operations/list.js +3 -2
- package/dist/lanes/operations/list.js.map +1 -1
- package/dist/lanes/operations/move.d.ts +29 -25
- package/dist/lanes/operations/move.d.ts.map +1 -1
- package/dist/lanes/operations/move.js +45 -242
- package/dist/lanes/operations/move.js.map +1 -1
- package/dist/lanes/operations/update.d.ts +15 -5
- package/dist/lanes/operations/update.d.ts.map +1 -1
- package/dist/lanes/operations/update.js +32 -12
- package/dist/lanes/operations/update.js.map +1 -1
- package/dist/lanes/scaffold-path.d.ts +88 -0
- package/dist/lanes/scaffold-path.d.ts.map +1 -0
- package/dist/lanes/scaffold-path.js +122 -0
- package/dist/lanes/scaffold-path.js.map +1 -0
- package/dist/lanes/types.d.ts +54 -31
- package/dist/lanes/types.d.ts.map +1 -1
- package/dist/lanes/types.js +60 -21
- package/dist/lanes/types.js.map +1 -1
- package/dist/outline-split.d.ts +2 -3
- package/dist/outline-split.d.ts.map +1 -1
- package/dist/outline-split.js +2 -3
- package/dist/outline-split.js.map +1 -1
- package/dist/paths.d.ts +14 -87
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +21 -131
- package/dist/paths.js.map +1 -1
- package/dist/remark-strip-outline.mjs +2 -3
- package/dist/rename-slug.d.ts +3 -3
- package/dist/rename-slug.d.ts.map +1 -1
- package/dist/rename-slug.js +137 -44
- package/dist/rename-slug.js.map +1 -1
- package/dist/review/handlers.d.ts +3 -3
- package/dist/review/handlers.d.ts.map +1 -1
- package/dist/review/handlers.js +18 -14
- package/dist/review/handlers.js.map +1 -1
- package/dist/review/report.d.ts +13 -2
- package/dist/review/report.d.ts.map +1 -1
- package/dist/review/report.js +48 -18
- package/dist/review/report.js.map +1 -1
- package/dist/review/start-handlers.d.ts +4 -4
- package/dist/review/start-handlers.d.ts.map +1 -1
- package/dist/review/start-handlers.js +25 -25
- package/dist/review/start-handlers.js.map +1 -1
- package/dist/review/workflow-paths.d.ts +24 -26
- package/dist/review/workflow-paths.d.ts.map +1 -1
- package/dist/review/workflow-paths.js +70 -60
- package/dist/review/workflow-paths.js.map +1 -1
- package/dist/schema/draft-annotation.d.ts +8 -8
- package/dist/schema/entry.d.ts +6 -6
- package/dist/schema/journal-events.d.ts +56 -42
- package/dist/schema/journal-events.d.ts.map +1 -1
- package/dist/schema/journal-events.js +23 -9
- package/dist/schema/journal-events.js.map +1 -1
- package/dist/sidecar/read.d.ts +8 -0
- package/dist/sidecar/read.d.ts.map +1 -1
- package/dist/sidecar/read.js +36 -9
- package/dist/sidecar/read.js.map +1 -1
- package/dist/sidecar/write.d.ts +14 -0
- package/dist/sidecar/write.d.ts.map +1 -1
- package/dist/sidecar/write.js +25 -0
- package/dist/sidecar/write.js.map +1 -1
- package/package.json +9 -9
- package/dist/body-state.d.ts +0 -27
- package/dist/body-state.d.ts.map +0 -1
- package/dist/body-state.js +0 -62
- package/dist/body-state.js.map +0 -1
- package/dist/scaffold.d.ts +0 -67
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js +0 -122
- package/dist/scaffold.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration-only tolerant reader for the legacy `config.sites` block
|
|
3
|
+
* (Phase 39b; finalized in 39c as the sole home for `SiteConfig`).
|
|
4
|
+
*
|
|
5
|
+
* Per the sites→lanes retirement spec §"Migration":
|
|
6
|
+
*
|
|
7
|
+
* > `sites` reads are tolerated *only* inside this migration. After it
|
|
8
|
+
* > runs, nothing reads `sites`.
|
|
9
|
+
*
|
|
10
|
+
* This module is that tolerated read surface. It parses the on-disk
|
|
11
|
+
* `.deskwork/config.json` directly and extracts just the fields the
|
|
12
|
+
* migration needs (`sites.<id>.{contentDir, calendarPath, host}`),
|
|
13
|
+
* WITHOUT going through `parseConfig`. `parseConfig` strips the legacy
|
|
14
|
+
* fields the migration consumes (it surfaces only the typed `SiteConfig`
|
|
15
|
+
* subset and, post-migration, tolerates an absent `sites` by normalizing
|
|
16
|
+
* it to `{}` — AUDIT-20260603-11), so the migration reads the raw JSON
|
|
17
|
+
* directly to see the legacy block verbatim. It must read both shapes:
|
|
18
|
+
*
|
|
19
|
+
* - pre-migration (`sites` present) → returns the legacy sites map.
|
|
20
|
+
* - post-migration (`sites` absent) → returns an EMPTY map, the
|
|
21
|
+
* "nothing to migrate" signal the rule's idempotency relies on.
|
|
22
|
+
*
|
|
23
|
+
* 39b keeps the field present in the live schema; this reader exists so
|
|
24
|
+
* the migration does not depend on the live schema's required-`sites`
|
|
25
|
+
* invariant. 39c moves `SiteConfig` here and removes the live-schema
|
|
26
|
+
* `sites` field; until then this is a focused, additive read helper.
|
|
27
|
+
*
|
|
28
|
+
* Sibling-relative imports per the doctor convention (`@/` does not
|
|
29
|
+
* resolve under tsx at runtime in this package's `src/`).
|
|
30
|
+
*/
|
|
31
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { configPath } from "../config.js";
|
|
33
|
+
/** Narrow an unknown to a string-keyed record (object, not null/array). */
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
/** Read a non-empty string property from a record (undefined otherwise). */
|
|
38
|
+
function readString(obj, key) {
|
|
39
|
+
const v = obj[key];
|
|
40
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read the legacy `sites` block from `<projectRoot>/.deskwork/config.json`
|
|
44
|
+
* tolerantly.
|
|
45
|
+
*
|
|
46
|
+
* - Config file ABSENT (ENOENT — never installed, or a bare fixture) →
|
|
47
|
+
* returns an empty map. A project with no config has no legacy sites
|
|
48
|
+
* to migrate; the rule no-ops. This mirrors `bootstrap.ts`'s
|
|
49
|
+
* "no-config → nothing to do" branch and keeps the migration rule
|
|
50
|
+
* safe to run unconditionally across the doctor's per-site loop.
|
|
51
|
+
* - Config PRESENT but unreadable (non-ENOENT I/O) / not valid JSON →
|
|
52
|
+
* throws. A config that exists but is broken should surface loudly
|
|
53
|
+
* where the operator can fix it (AUDIT-20260530-10 precedent;
|
|
54
|
+
* silently treating it as "no sites" would mask a real config-side
|
|
55
|
+
* problem — the project's "no fallbacks" rule).
|
|
56
|
+
* - Config present but no `sites` block (the post-migration shape) →
|
|
57
|
+
* returns an empty map. This is the idempotency signal: nothing to
|
|
58
|
+
* migrate.
|
|
59
|
+
* - `sites` present → returns each site's `{ contentDir, calendarPath,
|
|
60
|
+
* host, redirectsPath }`. A site missing a non-empty `contentDir`
|
|
61
|
+
* throws (the migration cannot derive `scaffoldDefaults` without it).
|
|
62
|
+
*
|
|
63
|
+
* @throws when a PRESENT config is unreadable / not JSON, or a declared
|
|
64
|
+
* site lacks a non-empty `contentDir`.
|
|
65
|
+
*/
|
|
66
|
+
export function readLegacySites(projectRoot) {
|
|
67
|
+
const path = configPath(projectRoot);
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = readFileSync(path, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
74
|
+
// No config on disk — no legacy sites. Nothing to migrate.
|
|
75
|
+
return { sites: new Map() };
|
|
76
|
+
}
|
|
77
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
78
|
+
throw new Error(`sites-to-lanes migration: could not read ${path}: ${reason}. ` +
|
|
79
|
+
`The migration needs the config to read the legacy "sites" block.`);
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(raw);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
87
|
+
throw new Error(`sites-to-lanes migration: invalid JSON in ${path}: ${reason}.`);
|
|
88
|
+
}
|
|
89
|
+
if (!isRecord(parsed)) {
|
|
90
|
+
throw new Error(`sites-to-lanes migration: ${path} is not a JSON object.`);
|
|
91
|
+
}
|
|
92
|
+
const obj = parsed;
|
|
93
|
+
const sites = new Map();
|
|
94
|
+
const sitesValue = obj.sites;
|
|
95
|
+
if (!isRecord(sitesValue)) {
|
|
96
|
+
// No `sites` block (post-migration shape) — nothing to migrate.
|
|
97
|
+
return { sites };
|
|
98
|
+
}
|
|
99
|
+
for (const slug of Object.keys(sitesValue)) {
|
|
100
|
+
const siteObj = sitesValue[slug];
|
|
101
|
+
if (!isRecord(siteObj)) {
|
|
102
|
+
throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} is not an object.`);
|
|
103
|
+
}
|
|
104
|
+
const contentDir = readString(siteObj, 'contentDir');
|
|
105
|
+
if (contentDir === undefined) {
|
|
106
|
+
throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} is missing a ` +
|
|
107
|
+
`non-empty "contentDir"; the migration cannot derive scaffoldDefaults ` +
|
|
108
|
+
`without it. Repair the config and re-run.`);
|
|
109
|
+
}
|
|
110
|
+
const calendarPath = readString(siteObj, 'calendarPath');
|
|
111
|
+
const host = readString(siteObj, 'host');
|
|
112
|
+
// AUDIT-20260604-11: a present-but-invalid `redirectsPath` (non-string
|
|
113
|
+
// or empty) must NOT be silently treated as absent — `readString` would
|
|
114
|
+
// omit it, and the migration's later `dropSitesBlock` would then discard
|
|
115
|
+
// a malformed-but-present value with no signal. Reject it loudly, mirroring
|
|
116
|
+
// the live `parseSiteConfig` rule (config.ts), so the operator repairs it.
|
|
117
|
+
// (Single read — `readString` is pure; reuse the result for the guard and
|
|
118
|
+
// the assignment.)
|
|
119
|
+
const redirectsPath = readString(siteObj, 'redirectsPath');
|
|
120
|
+
if ('redirectsPath' in siteObj && redirectsPath === undefined) {
|
|
121
|
+
throw new Error(`sites-to-lanes migration: site "${slug}" in ${path} has an invalid ` +
|
|
122
|
+
`"redirectsPath" (must be a non-empty string when set). Repair the ` +
|
|
123
|
+
`config and re-run.`);
|
|
124
|
+
}
|
|
125
|
+
const site = {
|
|
126
|
+
contentDir,
|
|
127
|
+
...(calendarPath !== undefined ? { calendarPath } : {}),
|
|
128
|
+
...(host !== undefined ? { host } : {}),
|
|
129
|
+
...(redirectsPath !== undefined ? { redirectsPath } : {}),
|
|
130
|
+
};
|
|
131
|
+
sites.set(slug, site);
|
|
132
|
+
}
|
|
133
|
+
return { sites };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Rewrite `<projectRoot>/.deskwork/config.json` with the `sites` and
|
|
137
|
+
* `defaultSite` keys removed (migration step 3 — "drop sites"). Every
|
|
138
|
+
* other top-level key is preserved verbatim. A no-op when neither key
|
|
139
|
+
* is present (idempotent).
|
|
140
|
+
*
|
|
141
|
+
* Returns `true` when the file was rewritten (a key was dropped),
|
|
142
|
+
* `false` when there was nothing to drop.
|
|
143
|
+
*
|
|
144
|
+
* Note: `parseConfig` tolerates the resulting `sites`-less config
|
|
145
|
+
* (AUDIT-20260603-11 — an absent/empty `sites` normalizes to `{}`), so a
|
|
146
|
+
* migrated project still loads through `readConfig` and every
|
|
147
|
+
* config-reading command keeps working between 39b and 39c. 39c removes
|
|
148
|
+
* the `sites` field from the schema entirely; until then the migration's
|
|
149
|
+
* own `sites` reads go through `readLegacySites` (this module), which
|
|
150
|
+
* does not depend on the live schema's shape.
|
|
151
|
+
*/
|
|
152
|
+
export function dropSitesBlock(projectRoot) {
|
|
153
|
+
const path = configPath(projectRoot);
|
|
154
|
+
const raw = readFileSync(path, 'utf8');
|
|
155
|
+
const parsed = JSON.parse(raw);
|
|
156
|
+
if (!isRecord(parsed)) {
|
|
157
|
+
throw new Error(`sites-to-lanes migration: ${path} is not a JSON object; cannot drop sites.`);
|
|
158
|
+
}
|
|
159
|
+
const obj = parsed;
|
|
160
|
+
const hadSites = Object.prototype.hasOwnProperty.call(obj, 'sites');
|
|
161
|
+
const hadDefault = Object.prototype.hasOwnProperty.call(obj, 'defaultSite');
|
|
162
|
+
if (!hadSites && !hadDefault) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const next = {};
|
|
166
|
+
for (const key of Object.keys(obj)) {
|
|
167
|
+
if (key === 'sites' || key === 'defaultSite')
|
|
168
|
+
continue;
|
|
169
|
+
next[key] = obj[key];
|
|
170
|
+
}
|
|
171
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=legacy-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-config.js","sourceRoot":"","sources":["../../src/doctor/legacy-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project-scope gate for doctor rules.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* their only site; multi-site projects trip it on the first site listed
|
|
12
|
-
* in the config and skip the remainder.
|
|
13
|
-
*
|
|
14
|
-
* The alternative — a dedicated project-scope abstraction in the runner
|
|
15
|
-
* — would let project-scoped rules opt out of the per-site loop
|
|
16
|
-
* entirely. Until that abstraction lands, this helper is the agreed
|
|
17
|
-
* shape; extracted here so multiple rules consuming the pattern share a
|
|
18
|
-
* single named definition rather than duplicating the body.
|
|
4
|
+
* Phase 39c (sites→lanes retirement): the doctor runner no longer loops
|
|
5
|
+
* per configured site — it runs a SINGLE project-scoped pass (see
|
|
6
|
+
* `runner.ts` `PROJECT_SCOPE`). Project-scoped rules (lane configs,
|
|
7
|
+
* sidecars, the journal) therefore already run exactly once. This gate
|
|
8
|
+
* is now a constant `true`; it is retained (rather than ripped out of
|
|
9
|
+
* every rule body) so the rules that consume it keep their shape and
|
|
10
|
+
* the single-pass guarantee is documented in one place.
|
|
19
11
|
*
|
|
20
12
|
* Sibling-relative imports per the project convention.
|
|
21
13
|
*/
|
|
22
14
|
import type { DoctorContext } from './types.ts';
|
|
23
15
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* single-pass case so the rule still runs).
|
|
16
|
+
* Phase 39c: the runner makes a single project pass, so a project-scoped
|
|
17
|
+
* rule always runs exactly once. The legacy "first site in
|
|
18
|
+
* `Object.keys(config.sites)`" check is retired with the per-site loop;
|
|
19
|
+
* this now unconditionally admits the single pass.
|
|
29
20
|
*/
|
|
30
|
-
export declare function isFirstSite(
|
|
21
|
+
export declare function isFirstSite(_ctx: DoctorContext): boolean;
|
|
31
22
|
//# sourceMappingURL=project-scope-gate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-scope-gate.d.ts","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"project-scope-gate.d.ts","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAExD"}
|
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project-scope gate for doctor rules.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* their only site; multi-site projects trip it on the first site listed
|
|
12
|
-
* in the config and skip the remainder.
|
|
13
|
-
*
|
|
14
|
-
* The alternative — a dedicated project-scope abstraction in the runner
|
|
15
|
-
* — would let project-scoped rules opt out of the per-site loop
|
|
16
|
-
* entirely. Until that abstraction lands, this helper is the agreed
|
|
17
|
-
* shape; extracted here so multiple rules consuming the pattern share a
|
|
18
|
-
* single named definition rather than duplicating the body.
|
|
4
|
+
* Phase 39c (sites→lanes retirement): the doctor runner no longer loops
|
|
5
|
+
* per configured site — it runs a SINGLE project-scoped pass (see
|
|
6
|
+
* `runner.ts` `PROJECT_SCOPE`). Project-scoped rules (lane configs,
|
|
7
|
+
* sidecars, the journal) therefore already run exactly once. This gate
|
|
8
|
+
* is now a constant `true`; it is retained (rather than ripped out of
|
|
9
|
+
* every rule body) so the rules that consume it keep their shape and
|
|
10
|
+
* the single-pass guarantee is documented in one place.
|
|
19
11
|
*
|
|
20
12
|
* Sibling-relative imports per the project convention.
|
|
21
13
|
*/
|
|
22
14
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* single-pass case so the rule still runs).
|
|
15
|
+
* Phase 39c: the runner makes a single project pass, so a project-scoped
|
|
16
|
+
* rule always runs exactly once. The legacy "first site in
|
|
17
|
+
* `Object.keys(config.sites)`" check is retired with the per-site loop;
|
|
18
|
+
* this now unconditionally admits the single pass.
|
|
28
19
|
*/
|
|
29
|
-
export function isFirstSite(
|
|
30
|
-
|
|
31
|
-
if (siteIds.length === 0)
|
|
32
|
-
return true;
|
|
33
|
-
return siteIds[0] === ctx.site;
|
|
20
|
+
export function isFirstSite(_ctx) {
|
|
21
|
+
return true;
|
|
34
22
|
}
|
|
35
23
|
//# sourceMappingURL=project-scope-gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-scope-gate.js","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"project-scope-gate.js","sourceRoot":"","sources":["../../src/doctor/project-scope-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAmB;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/doctor/repair.d.ts
CHANGED
|
@@ -5,5 +5,16 @@ export interface RepairResult {
|
|
|
5
5
|
applied: string[];
|
|
6
6
|
pendingDestructive: string[];
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Regenerate `calendar.md` from sidecars (the SSOT).
|
|
10
|
+
*
|
|
11
|
+
* Phase 39d (sites→lanes retirement): the runtime `artifactPath`
|
|
12
|
+
* backfiller that used to live here is REMOVED. Backfilling missing
|
|
13
|
+
* `artifactPath` is owned exclusively by the 39b migration
|
|
14
|
+
* (`sites-migration-backfill.ts`), which enumerates candidates across
|
|
15
|
+
* legacy content dirs and halts on ambiguity. `repair.ts` no longer
|
|
16
|
+
* derives a path from the slug+stage heuristic — it reads stored paths
|
|
17
|
+
* only and regenerates the calendar projection.
|
|
18
|
+
*/
|
|
8
19
|
export declare function repairAll(projectRoot: string, opts: RepairOptions): Promise<RepairResult>;
|
|
9
20
|
//# sourceMappingURL=repair.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8B/F"}
|
package/dist/doctor/repair.js
CHANGED
|
@@ -1,76 +1,21 @@
|
|
|
1
|
-
import { readdir, readFile, writeFile, mkdir
|
|
2
|
-
import { join, dirname
|
|
1
|
+
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
3
|
import { sidecarsDir } from "../sidecar/paths.js";
|
|
4
4
|
import { EntrySchema } from "../schema/entry.js";
|
|
5
5
|
import { readConfig } from "../config.js";
|
|
6
6
|
import { resolveCalendarPath } from "../paths.js";
|
|
7
7
|
import { renderCalendar } from "../calendar/render.js";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* validate.ts (kept here to avoid a cross-import; both files reach for
|
|
11
|
-
* the same canonical mapping). Returns null for stages with no on-disk
|
|
12
|
-
* artifact, including stages outside the editorial pipeline (Phase 3
|
|
13
|
-
* / Phase 4 — lane-aware path conventions land in lane code).
|
|
14
|
-
*/
|
|
15
|
-
function artifactPathForStage(projectRoot, slug, stage) {
|
|
16
|
-
switch (stage) {
|
|
17
|
-
case 'Ideas':
|
|
18
|
-
return join(projectRoot, 'docs', slug, 'scrapbook', 'idea.md');
|
|
19
|
-
case 'Planned':
|
|
20
|
-
return join(projectRoot, 'docs', slug, 'scrapbook', 'plan.md');
|
|
21
|
-
case 'Outlining':
|
|
22
|
-
return join(projectRoot, 'docs', slug, 'scrapbook', 'outline.md');
|
|
23
|
-
case 'Drafting':
|
|
24
|
-
case 'Final':
|
|
25
|
-
case 'Published':
|
|
26
|
-
return join(projectRoot, 'docs', slug, 'index.md');
|
|
27
|
-
case 'Blocked':
|
|
28
|
-
case 'Cancelled':
|
|
29
|
-
return null;
|
|
30
|
-
default:
|
|
31
|
-
// Lane-specific or unrecognized stage; no editorial-default path
|
|
32
|
-
// applies. Phase 4 introduces template-driven path resolution.
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function fileExists(path) {
|
|
37
|
-
try {
|
|
38
|
-
await stat(path);
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* #182 Phase 34 ship-pass — backfill `artifactPath` on sidecars that
|
|
47
|
-
* lack the field but where the slug+stage heuristic resolves to a real
|
|
48
|
-
* file. The validator's `missing-artifact-path` rule surfaces the gap;
|
|
49
|
-
* this writer fixes it.
|
|
9
|
+
* Regenerate `calendar.md` from sidecars (the SSOT).
|
|
50
10
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
11
|
+
* Phase 39d (sites→lanes retirement): the runtime `artifactPath`
|
|
12
|
+
* backfiller that used to live here is REMOVED. Backfilling missing
|
|
13
|
+
* `artifactPath` is owned exclusively by the 39b migration
|
|
14
|
+
* (`sites-migration-backfill.ts`), which enumerates candidates across
|
|
15
|
+
* legacy content dirs and halts on ambiguity. `repair.ts` no longer
|
|
16
|
+
* derives a path from the slug+stage heuristic — it reads stored paths
|
|
17
|
+
* only and regenerates the calendar projection.
|
|
53
18
|
*/
|
|
54
|
-
async function backfillArtifactPaths(projectRoot, entries, sidecarPaths) {
|
|
55
|
-
const changed = [];
|
|
56
|
-
for (const entry of entries) {
|
|
57
|
-
if (entry.artifactPath !== undefined && entry.artifactPath !== '')
|
|
58
|
-
continue;
|
|
59
|
-
const heuristicAbs = artifactPathForStage(projectRoot, entry.slug, entry.currentStage);
|
|
60
|
-
if (!heuristicAbs)
|
|
61
|
-
continue;
|
|
62
|
-
if (!(await fileExists(heuristicAbs)))
|
|
63
|
-
continue;
|
|
64
|
-
const sidecarPath = sidecarPaths.get(entry.uuid);
|
|
65
|
-
if (!sidecarPath)
|
|
66
|
-
continue;
|
|
67
|
-
const relPath = relative(projectRoot, heuristicAbs);
|
|
68
|
-
const updated = { ...entry, artifactPath: relPath };
|
|
69
|
-
await writeFile(sidecarPath, JSON.stringify(updated, null, 2));
|
|
70
|
-
changed.push(entry.uuid);
|
|
71
|
-
}
|
|
72
|
-
return changed;
|
|
73
|
-
}
|
|
74
19
|
export async function repairAll(projectRoot, opts) {
|
|
75
20
|
void opts;
|
|
76
21
|
const result = { applied: [], pendingDestructive: [] };
|
|
@@ -84,7 +29,6 @@ export async function repairAll(projectRoot, opts) {
|
|
|
84
29
|
return result;
|
|
85
30
|
}
|
|
86
31
|
const entries = [];
|
|
87
|
-
const sidecarPaths = new Map();
|
|
88
32
|
for (const name of names.filter(n => n.endsWith('.json'))) {
|
|
89
33
|
const sidecarPath = join(dir, name);
|
|
90
34
|
const raw = await readFile(sidecarPath, 'utf8');
|
|
@@ -92,33 +36,10 @@ export async function repairAll(projectRoot, opts) {
|
|
|
92
36
|
const parsed = EntrySchema.safeParse(JSON.parse(raw));
|
|
93
37
|
if (parsed.success) {
|
|
94
38
|
entries.push(parsed.data);
|
|
95
|
-
sidecarPaths.set(parsed.data.uuid, sidecarPath);
|
|
96
39
|
}
|
|
97
40
|
}
|
|
98
41
|
catch { /* skip malformed */ }
|
|
99
42
|
}
|
|
100
|
-
// #182 — backfill missing artifactPath BEFORE regenerating calendar
|
|
101
|
-
// so the calendar render uses the freshly-stamped paths if it
|
|
102
|
-
// consumes them downstream.
|
|
103
|
-
const backfilled = await backfillArtifactPaths(projectRoot, entries, sidecarPaths);
|
|
104
|
-
if (backfilled.length > 0) {
|
|
105
|
-
// Re-load the entries so the calendar render sees the stamped
|
|
106
|
-
// paths (the in-memory `entries` array is now stale for those
|
|
107
|
-
// UUIDs).
|
|
108
|
-
for (let i = 0; i < entries.length; i++) {
|
|
109
|
-
const e = entries[i];
|
|
110
|
-
if (!e || !backfilled.includes(e.uuid))
|
|
111
|
-
continue;
|
|
112
|
-
const sidecarPath = sidecarPaths.get(e.uuid);
|
|
113
|
-
if (!sidecarPath)
|
|
114
|
-
continue;
|
|
115
|
-
const raw = await readFile(sidecarPath, 'utf8');
|
|
116
|
-
const parsed = EntrySchema.safeParse(JSON.parse(raw));
|
|
117
|
-
if (parsed.success)
|
|
118
|
-
entries[i] = parsed.data;
|
|
119
|
-
}
|
|
120
|
-
result.applied.push(`artifact-path-backfilled (${backfilled.length} entrie(s))`);
|
|
121
|
-
}
|
|
122
43
|
const md = renderCalendar(entries);
|
|
123
44
|
// #232: honor the configured per-site calendarPath (default site) rather
|
|
124
45
|
// than the hardcoded `.deskwork/calendar.md`, consistent with regenerateCalendar.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/doctor/repair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAc,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAWvD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAmB,EAAE,IAAmB;IACtE,KAAK,IAAI,CAAC;IACV,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC;IAErE,uCAAuC;IACvC,MAAM,GAAG,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC;QAAC,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,MAAM,CAAC;IAAC,CAAC;IAE5D,MAAM,OAAO,GAAY,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACnC,yEAAyE;IACzE,kFAAkF;IAClF,MAAM,YAAY,GAAG,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAClC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAE5C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -18,8 +18,13 @@ interface DuplicateGroup {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Group `index.byPath` (relPath → uuid) by uuid, return only groups
|
|
21
|
-
* with more than one file.
|
|
22
|
-
*
|
|
21
|
+
* with more than one file.
|
|
22
|
+
*
|
|
23
|
+
* Phase 39c (sites→lanes retirement): the doctor's content index is now
|
|
24
|
+
* built by `buildContentIndexFromSidecars`, whose `byPath` keys are
|
|
25
|
+
* PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
|
|
26
|
+
* contentDir-relative. So we reconstruct absolute paths by joining
|
|
27
|
+
* against `projectRoot` — there is no per-site `contentDir` axis.
|
|
23
28
|
*/
|
|
24
29
|
export declare function findDuplicateGroups(ctx: DoctorContext): DuplicateGroup[];
|
|
25
30
|
declare const rule: DoctorRule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplicate-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"duplicate-id.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,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;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,GACjB,cAAc,EAAE,CAelB;AAED,QAAA,MAAM,IAAI,EAAE,UA2FX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { join, relative } from 'node:path';
|
|
15
15
|
import { parseFrontmatter, removeFrontmatterPaths } from "../../frontmatter.js";
|
|
16
|
-
import { resolveContentDir } from "../../paths.js";
|
|
17
16
|
const RULE_ID = 'duplicate-id';
|
|
18
17
|
/**
|
|
19
18
|
* Clear the `deskwork.id` field from a markdown file. Returns true when
|
|
@@ -40,14 +39,18 @@ function clearFrontmatterId(absPath) {
|
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
42
41
|
* Group `index.byPath` (relPath → uuid) by uuid, return only groups
|
|
43
|
-
* with more than one file.
|
|
44
|
-
*
|
|
42
|
+
* with more than one file.
|
|
43
|
+
*
|
|
44
|
+
* Phase 39c (sites→lanes retirement): the doctor's content index is now
|
|
45
|
+
* built by `buildContentIndexFromSidecars`, whose `byPath` keys are
|
|
46
|
+
* PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
|
|
47
|
+
* contentDir-relative. So we reconstruct absolute paths by joining
|
|
48
|
+
* against `projectRoot` — there is no per-site `contentDir` axis.
|
|
45
49
|
*/
|
|
46
50
|
export function findDuplicateGroups(ctx) {
|
|
47
|
-
const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
|
|
48
51
|
const byUuid = new Map();
|
|
49
52
|
for (const [relPath, uuid] of ctx.index.byPath) {
|
|
50
|
-
const abs = join(
|
|
53
|
+
const abs = join(ctx.projectRoot, relPath);
|
|
51
54
|
const list = byUuid.get(uuid);
|
|
52
55
|
if (list)
|
|
53
56
|
list.push(abs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplicate-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"duplicate-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/duplicate-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
15
15
|
import { join, relative } from 'node:path';
|
|
16
16
|
import { parseFrontmatter, removeFrontmatterPaths } from '../../frontmatter.ts';
|
|
17
|
-
import { resolveContentDir } from '../../paths.ts';
|
|
18
17
|
import type {
|
|
19
18
|
DoctorContext,
|
|
20
19
|
DoctorRule,
|
|
@@ -54,16 +53,20 @@ interface DuplicateGroup {
|
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
55
|
* Group `index.byPath` (relPath → uuid) by uuid, return only groups
|
|
57
|
-
* with more than one file.
|
|
58
|
-
*
|
|
56
|
+
* with more than one file.
|
|
57
|
+
*
|
|
58
|
+
* Phase 39c (sites→lanes retirement): the doctor's content index is now
|
|
59
|
+
* built by `buildContentIndexFromSidecars`, whose `byPath` keys are
|
|
60
|
+
* PROJECT-ROOT-relative (matching `entry.artifactPath`'s base), not
|
|
61
|
+
* contentDir-relative. So we reconstruct absolute paths by joining
|
|
62
|
+
* against `projectRoot` — there is no per-site `contentDir` axis.
|
|
59
63
|
*/
|
|
60
64
|
export function findDuplicateGroups(
|
|
61
65
|
ctx: DoctorContext,
|
|
62
66
|
): DuplicateGroup[] {
|
|
63
|
-
const contentDir = resolveContentDir(ctx.projectRoot, ctx.config, ctx.site);
|
|
64
67
|
const byUuid = new Map<string, string[]>();
|
|
65
68
|
for (const [relPath, uuid] of ctx.index.byPath) {
|
|
66
|
-
const abs = join(
|
|
69
|
+
const abs = join(ctx.projectRoot, relPath);
|
|
67
70
|
const list = byUuid.get(uuid);
|
|
68
71
|
if (list) list.push(abs);
|
|
69
72
|
else byUuid.set(uuid, [abs]);
|
|
@@ -1 +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,
|
|
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,UAoGX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
29
29
|
import { extname, join, relative } from 'node:path';
|
|
30
|
-
import {
|
|
30
|
+
import { collectSidecarArtifactDirs } from "../../content-index.js";
|
|
31
31
|
import { parseFrontmatter, removeFrontmatterPaths, updateFrontmatter, } from "../../frontmatter.js";
|
|
32
32
|
const RULE_ID = 'legacy-top-level-id-migration';
|
|
33
33
|
const MARKDOWN_EXTENSIONS = new Set([
|
|
@@ -139,19 +139,22 @@ const rule = {
|
|
|
139
139
|
id: RULE_ID,
|
|
140
140
|
label: 'Frontmatter id at top level should be under `deskwork:` namespace',
|
|
141
141
|
async audit(ctx) {
|
|
142
|
-
|
|
142
|
+
// Phase 39c (sites→lanes retirement): discovery is sidecar-driven —
|
|
143
|
+
// walk each entry's resolved artifact directory rather than a
|
|
144
|
+
// configured site `contentDir`. The sidecar is the SSOT; legacy
|
|
145
|
+
// top-level-id files live alongside the entries that reference them.
|
|
146
|
+
const roots = await collectSidecarArtifactDirs(ctx.projectRoot);
|
|
143
147
|
const calendarIds = new Set();
|
|
144
148
|
for (const e of ctx.calendar.entries) {
|
|
145
149
|
if (e.id)
|
|
146
150
|
calendarIds.add(e.id);
|
|
147
151
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
catch {
|
|
153
|
-
return [];
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
for (const root of roots) {
|
|
154
|
+
for (const abs of collectMarkdownFiles(root))
|
|
155
|
+
seen.add(abs);
|
|
154
156
|
}
|
|
157
|
+
const files = [...seen].sort();
|
|
155
158
|
const findings = [];
|
|
156
159
|
for (const abs of files) {
|
|
157
160
|
const hit = classify(abs, calendarIds);
|