@deskwork/core 0.45.1 → 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
package/dist/doctor/runner.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Sibling-relative imports per the project convention.
|
|
10
10
|
*/
|
|
11
11
|
import { readCalendar } from "../calendar.js";
|
|
12
|
-
import {
|
|
12
|
+
import { buildContentIndexFromSidecars } from "../content-index.js";
|
|
13
13
|
import { resolveCalendarPath } from "../paths.js";
|
|
14
14
|
import { readWorkflows } from "../review/pipeline.js";
|
|
15
15
|
import orphanFrontmatterId from "./rules/orphan-frontmatter-id.js";
|
|
@@ -21,6 +21,7 @@ import calendarUuidMissing from "./rules/calendar-uuid-missing.js";
|
|
|
21
21
|
import legacyTopLevelIdMigration from "./rules/legacy-top-level-id-migration.js";
|
|
22
22
|
import legacyStageArtifactPath from "./rules/legacy-stage-artifact-path.js";
|
|
23
23
|
import laneConfigMissingTemplate from "./rules/lane-config-missing-template.js";
|
|
24
|
+
import sitesToLanesMigration from "./rules/sites-to-lanes-migration.js";
|
|
24
25
|
import entryLaneMissing from "./rules/entry-lane-missing.js";
|
|
25
26
|
import entryAnchorShape from "./rules/entry-anchor-shape.js";
|
|
26
27
|
import entryAddressReasonMissing from "./rules/entry-address-reason-missing.js";
|
|
@@ -40,6 +41,7 @@ export const RULES = [
|
|
|
40
41
|
calendarUuidMissing,
|
|
41
42
|
legacyTopLevelIdMigration,
|
|
42
43
|
legacyStageArtifactPath,
|
|
44
|
+
sitesToLanesMigration,
|
|
43
45
|
laneConfigMissingTemplate,
|
|
44
46
|
entryLaneMissing,
|
|
45
47
|
entryAnchorShape,
|
|
@@ -79,32 +81,35 @@ export function parseFixArgument(arg) {
|
|
|
79
81
|
}
|
|
80
82
|
return ids;
|
|
81
83
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The single project scope label. Phase 39c collapsed the doctor's
|
|
86
|
+
* per-site loop (location is no longer the identifying axis — the
|
|
87
|
+
* sidecar set is the source of truth) into one project pass. Findings
|
|
88
|
+
* carry this as their `site` label so existing consumers that group by
|
|
89
|
+
* `finding.site` still see a stable key.
|
|
90
|
+
*/
|
|
91
|
+
export const PROJECT_SCOPE = 'project';
|
|
92
|
+
/** Build the single project-scoped context for a run. */
|
|
93
|
+
async function buildContext(opts, interaction) {
|
|
94
|
+
// Phase 39c: the calendar is the single project-level
|
|
95
|
+
// `.deskwork/calendar.md`; the content index is driven by the sidecar
|
|
96
|
+
// set (the SSOT), not a configured site `contentDir`. Workflows are no
|
|
97
|
+
// longer site-filtered — the runner exposes the whole workflow store
|
|
98
|
+
// and the workflow-stale rule reconciles against the single calendar.
|
|
99
|
+
const calendarPath = resolveCalendarPath(opts.projectRoot, opts.config);
|
|
85
100
|
const calendar = readCalendar(calendarPath);
|
|
86
|
-
const index =
|
|
87
|
-
const
|
|
88
|
-
const workflows = allWorkflows.filter((w) => w.site === site);
|
|
101
|
+
const index = await buildContentIndexFromSidecars(opts.projectRoot);
|
|
102
|
+
const workflows = readWorkflows(opts.projectRoot, opts.config);
|
|
89
103
|
return {
|
|
90
104
|
projectRoot: opts.projectRoot,
|
|
91
105
|
config: opts.config,
|
|
92
|
-
site,
|
|
106
|
+
site: PROJECT_SCOPE,
|
|
93
107
|
calendar,
|
|
94
108
|
index,
|
|
95
109
|
workflows,
|
|
96
110
|
interaction,
|
|
97
111
|
};
|
|
98
112
|
}
|
|
99
|
-
function selectSites(opts) {
|
|
100
|
-
if (opts.site !== undefined) {
|
|
101
|
-
if (!(opts.site in opts.config.sites)) {
|
|
102
|
-
throw new Error(`Unknown site "${opts.site}". Configured sites: ${Object.keys(opts.config.sites).join(', ')}`);
|
|
103
|
-
}
|
|
104
|
-
return [opts.site];
|
|
105
|
-
}
|
|
106
|
-
return Object.keys(opts.config.sites);
|
|
107
|
-
}
|
|
108
113
|
function selectRules(available, ruleIds) {
|
|
109
114
|
if (ruleIds === undefined)
|
|
110
115
|
return [...available];
|
|
@@ -133,24 +138,31 @@ async function buildEffectiveRules(projectRoot) {
|
|
|
133
138
|
const projectRules = await loadProjectRules(projectRoot);
|
|
134
139
|
return mergeRules(RULES, projectRules);
|
|
135
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Shared setup for both doctor entry points: resolve the effective rule
|
|
143
|
+
* set (built-ins + project overrides, filtered by `ruleIds`) and build the
|
|
144
|
+
* run context. `runAudit` and `runRepair` differ only in what they DO with
|
|
145
|
+
* the rules + ctx, not in how they obtain them.
|
|
146
|
+
*/
|
|
147
|
+
async function prepareRun(opts, interaction) {
|
|
148
|
+
const available = await buildEffectiveRules(opts.projectRoot);
|
|
149
|
+
const rules = selectRules(available, opts.ruleIds);
|
|
150
|
+
const ctx = await buildContext(opts, interaction);
|
|
151
|
+
return { rules, ctx };
|
|
152
|
+
}
|
|
136
153
|
/**
|
|
137
154
|
* Audit: collect findings without mutating the world. Returns a fully-
|
|
138
155
|
* built report with empty `repairs`. Suitable for pre-commit hooks
|
|
139
156
|
* that just want a non-zero exit code on any finding.
|
|
140
157
|
*/
|
|
141
158
|
export async function runAudit(opts, interaction) {
|
|
142
|
-
const
|
|
143
|
-
const available = await buildEffectiveRules(opts.projectRoot);
|
|
144
|
-
const rules = selectRules(available, opts.ruleIds);
|
|
159
|
+
const { rules, ctx } = await prepareRun(opts, interaction);
|
|
145
160
|
const findings = [];
|
|
146
|
-
for (const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const out = await rule.audit(ctx);
|
|
150
|
-
findings.push(...out);
|
|
151
|
-
}
|
|
161
|
+
for (const rule of rules) {
|
|
162
|
+
const out = await rule.audit(ctx);
|
|
163
|
+
findings.push(...out);
|
|
152
164
|
}
|
|
153
|
-
return { findings, repairs: [], sites };
|
|
165
|
+
return { findings, repairs: [], sites: [PROJECT_SCOPE] };
|
|
154
166
|
}
|
|
155
167
|
/**
|
|
156
168
|
* Repair: run audit → plan → (consult interaction) → apply. Returns
|
|
@@ -162,24 +174,19 @@ export async function runAudit(opts, interaction) {
|
|
|
162
174
|
* `interaction.confirmApply`. `report-only` plans never apply.
|
|
163
175
|
*/
|
|
164
176
|
export async function runRepair(opts, interaction) {
|
|
165
|
-
const
|
|
166
|
-
const available = await buildEffectiveRules(opts.projectRoot);
|
|
167
|
-
const rules = selectRules(available, opts.ruleIds);
|
|
177
|
+
const { rules, ctx } = await prepareRun(opts, interaction);
|
|
168
178
|
const findings = [];
|
|
169
179
|
const repairs = [];
|
|
170
|
-
for (const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const result = await resolveAndApply(rule, ctx, plan, interaction);
|
|
178
|
-
repairs.push(result);
|
|
179
|
-
}
|
|
180
|
+
for (const rule of rules) {
|
|
181
|
+
const ruleFindings = await rule.audit(ctx);
|
|
182
|
+
findings.push(...ruleFindings);
|
|
183
|
+
for (const finding of ruleFindings) {
|
|
184
|
+
const plan = await rule.plan(ctx, finding);
|
|
185
|
+
const result = await resolveAndApply(rule, ctx, plan, interaction);
|
|
186
|
+
repairs.push(result);
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
|
-
return { findings, repairs, sites };
|
|
189
|
+
return { findings, repairs, sites: [PROJECT_SCOPE] };
|
|
183
190
|
}
|
|
184
191
|
/**
|
|
185
192
|
* Resolve a `prompt` plan via the interaction adapter and apply it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sites-to-lanes migration backfiller (Phase 39b).
|
|
3
|
+
*
|
|
4
|
+
* This is the LAST legitimate use of the slug+stage heuristic (per the
|
|
5
|
+
* sites→lanes retirement spec §"Migration" step 2). For every entry
|
|
6
|
+
* lacking `artifactPath`, it derives the artifact location ONCE from the
|
|
7
|
+
* current resolved location and stamps it onto the sidecar.
|
|
8
|
+
*
|
|
9
|
+
* AMBIGUITY-HALT (AUDIT-20260602-03 — the critical guard):
|
|
10
|
+
*
|
|
11
|
+
* The heuristic is the SAME slug+stage search that causes the #394
|
|
12
|
+
* multi-site false-positive. When a slug exists under more than one
|
|
13
|
+
* legacy `site.contentDir` (or on more than one filesystem), the
|
|
14
|
+
* heuristic resolves to MORE THAN ONE candidate file. The migration
|
|
15
|
+
* MUST NOT silently stamp one of them — doing so would launder a
|
|
16
|
+
* known-ambiguous guess into permanent, trusted `artifactPath` data
|
|
17
|
+
* and make the bug undetectable afterward (no more search to flag it).
|
|
18
|
+
*
|
|
19
|
+
* So this backfiller enumerates ALL candidate files across EVERY
|
|
20
|
+
* legacy `site.contentDir` for the entry's slug+stage. Only an
|
|
21
|
+
* entry with EXACTLY ONE candidate is stamped. An entry with two or
|
|
22
|
+
* more candidates is REFUSED: it is reported on `ambiguous[]` with the
|
|
23
|
+
* colliding paths, requiring operator disambiguation. An entry with
|
|
24
|
+
* zero candidates is reported on `noCandidate[]` (nothing on disk to
|
|
25
|
+
* stamp from). Never guess.
|
|
26
|
+
*
|
|
27
|
+
* The pre-existing `repair.ts:backfillArtifactPaths` checked exactly one
|
|
28
|
+
* heuristic path (a single base dir). This widens that to a
|
|
29
|
+
* multi-candidate search across every legacy contentDir precisely so it
|
|
30
|
+
* can DETECT the collision it must refuse to launder.
|
|
31
|
+
*
|
|
32
|
+
* Sibling-relative imports per the doctor convention.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* One legacy content base the backfiller searches, paired with the lane
|
|
36
|
+
* id the migration assigns to entries resolving under it. `laneId` is the
|
|
37
|
+
* legacy site slug (= the lane id `laneFromSite` creates). Threading the
|
|
38
|
+
* lane id through the search lets the backfiller stamp BOTH `artifactPath`
|
|
39
|
+
* AND `lane` on an unambiguously-resolved entry (AUDIT-20260603-12), so a
|
|
40
|
+
* migrated entry is not left lane-less for `entry-lane-missing` to flag.
|
|
41
|
+
*/
|
|
42
|
+
export interface LaneBase {
|
|
43
|
+
readonly laneId: string;
|
|
44
|
+
readonly contentDir: string;
|
|
45
|
+
}
|
|
46
|
+
/** One entry the migration could not stamp because the slug+stage search
|
|
47
|
+
* resolved to more than one candidate (the #394 collision). */
|
|
48
|
+
export interface AmbiguousBackfill {
|
|
49
|
+
readonly entryUuid: string;
|
|
50
|
+
readonly slug: string;
|
|
51
|
+
readonly stage: string;
|
|
52
|
+
/** Project-relative candidate paths — operator must disambiguate. */
|
|
53
|
+
readonly candidates: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** Result of the backfill pass. */
|
|
56
|
+
export interface BackfillResult {
|
|
57
|
+
/** UUIDs whose sidecar was stamped with a single unambiguous path. */
|
|
58
|
+
readonly stamped: readonly string[];
|
|
59
|
+
/** Entries refused because >1 candidate existed (ambiguity-halt). */
|
|
60
|
+
readonly ambiguous: readonly AmbiguousBackfill[];
|
|
61
|
+
}
|
|
62
|
+
export declare function backfillFromLegacySites(projectRoot: string, bases: readonly LaneBase[]): Promise<BackfillResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Audit-only variant: enumerate the ambiguity collisions WITHOUT writing
|
|
65
|
+
* anything. Used by the doctor rule's `audit()` to surface
|
|
66
|
+
* `migration-ambiguous` findings before any `--fix` mutation. Shares
|
|
67
|
+
* `planBackfills` with the apply path so the audit and the apply agree on
|
|
68
|
+
* what is ambiguous by construction.
|
|
69
|
+
*/
|
|
70
|
+
export declare function detectAmbiguousBackfills(projectRoot: string, bases: readonly LaneBase[]): Promise<AmbiguousBackfill[]>;
|
|
71
|
+
//# sourceMappingURL=sites-migration-backfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites-migration-backfill.d.ts","sourceRoot":"","sources":["../../src/doctor/sites-migration-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAmDH;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAkCD;gEACgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAClD;AAsDD,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,QAAQ,EAAE,GACzB,OAAO,CAAC,cAAc,CAAC,CAmBzB;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,QAAQ,EAAE,GACzB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAS9B"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sites-to-lanes migration backfiller (Phase 39b).
|
|
3
|
+
*
|
|
4
|
+
* This is the LAST legitimate use of the slug+stage heuristic (per the
|
|
5
|
+
* sites→lanes retirement spec §"Migration" step 2). For every entry
|
|
6
|
+
* lacking `artifactPath`, it derives the artifact location ONCE from the
|
|
7
|
+
* current resolved location and stamps it onto the sidecar.
|
|
8
|
+
*
|
|
9
|
+
* AMBIGUITY-HALT (AUDIT-20260602-03 — the critical guard):
|
|
10
|
+
*
|
|
11
|
+
* The heuristic is the SAME slug+stage search that causes the #394
|
|
12
|
+
* multi-site false-positive. When a slug exists under more than one
|
|
13
|
+
* legacy `site.contentDir` (or on more than one filesystem), the
|
|
14
|
+
* heuristic resolves to MORE THAN ONE candidate file. The migration
|
|
15
|
+
* MUST NOT silently stamp one of them — doing so would launder a
|
|
16
|
+
* known-ambiguous guess into permanent, trusted `artifactPath` data
|
|
17
|
+
* and make the bug undetectable afterward (no more search to flag it).
|
|
18
|
+
*
|
|
19
|
+
* So this backfiller enumerates ALL candidate files across EVERY
|
|
20
|
+
* legacy `site.contentDir` for the entry's slug+stage. Only an
|
|
21
|
+
* entry with EXACTLY ONE candidate is stamped. An entry with two or
|
|
22
|
+
* more candidates is REFUSED: it is reported on `ambiguous[]` with the
|
|
23
|
+
* colliding paths, requiring operator disambiguation. An entry with
|
|
24
|
+
* zero candidates is reported on `noCandidate[]` (nothing on disk to
|
|
25
|
+
* stamp from). Never guess.
|
|
26
|
+
*
|
|
27
|
+
* The pre-existing `repair.ts:backfillArtifactPaths` checked exactly one
|
|
28
|
+
* heuristic path (a single base dir). This widens that to a
|
|
29
|
+
* multi-candidate search across every legacy contentDir precisely so it
|
|
30
|
+
* can DETECT the collision it must refuse to launder.
|
|
31
|
+
*
|
|
32
|
+
* Sibling-relative imports per the doctor convention.
|
|
33
|
+
*/
|
|
34
|
+
import { stat } from 'node:fs/promises';
|
|
35
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
36
|
+
import { sidecarsDir } from "../sidecar/paths.js";
|
|
37
|
+
import { writeSidecar } from "../sidecar/write.js";
|
|
38
|
+
import { readAllSidecars } from "../sidecar/read-all.js";
|
|
39
|
+
/**
|
|
40
|
+
* Stage-conventional artifact leaf path RELATIVE to a content base dir.
|
|
41
|
+
* Mirrors the heuristic in `repair.ts` / `validate.ts` but parameterized
|
|
42
|
+
* over the base dir (each legacy `site.contentDir`) instead of hardcoding
|
|
43
|
+
* `docs/`. Returns null for stages with no on-disk artifact.
|
|
44
|
+
*
|
|
45
|
+
* Path shape per slug+stage:
|
|
46
|
+
* - Ideas → <slug>/scrapbook/idea.md
|
|
47
|
+
* - Planned → <slug>/scrapbook/plan.md
|
|
48
|
+
* - Outlining → <slug>/scrapbook/outline.md
|
|
49
|
+
* - Drafting / Final / Published → <slug>/index.md
|
|
50
|
+
* - Blocked / Cancelled / other → null (no editorial-default path)
|
|
51
|
+
*/
|
|
52
|
+
function stageRelativeLeaf(slug, stage) {
|
|
53
|
+
switch (stage) {
|
|
54
|
+
case 'Ideas':
|
|
55
|
+
return join(slug, 'scrapbook', 'idea.md');
|
|
56
|
+
case 'Planned':
|
|
57
|
+
return join(slug, 'scrapbook', 'plan.md');
|
|
58
|
+
case 'Outlining':
|
|
59
|
+
return join(slug, 'scrapbook', 'outline.md');
|
|
60
|
+
case 'Drafting':
|
|
61
|
+
case 'Final':
|
|
62
|
+
case 'Published':
|
|
63
|
+
return join(slug, 'index.md');
|
|
64
|
+
default:
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function fileExists(absPath) {
|
|
69
|
+
return stat(absPath).then((s) => s.isFile(), () => false);
|
|
70
|
+
}
|
|
71
|
+
/** Resolve a legacy contentDir to an absolute path under the project. */
|
|
72
|
+
function contentDirAbs(projectRoot, contentDir) {
|
|
73
|
+
return isAbsolute(contentDir) ? contentDir : join(projectRoot, contentDir);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Enumerate EVERY on-disk candidate for an entry's slug+stage across all
|
|
77
|
+
* legacy content bases. Returns project-relative paths (the form stored on
|
|
78
|
+
* the sidecar) paired with the owning lane id, de-duplicated by path and
|
|
79
|
+
* in stable order.
|
|
80
|
+
*/
|
|
81
|
+
async function enumerateCandidates(projectRoot, bases, entry) {
|
|
82
|
+
const leaf = stageRelativeLeaf(entry.slug, entry.currentStage);
|
|
83
|
+
if (leaf === null)
|
|
84
|
+
return [];
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const base of bases) {
|
|
88
|
+
const abs = join(contentDirAbs(projectRoot, base.contentDir), leaf);
|
|
89
|
+
if (!(await fileExists(abs)))
|
|
90
|
+
continue;
|
|
91
|
+
const rel = relative(projectRoot, abs);
|
|
92
|
+
if (seen.has(rel))
|
|
93
|
+
continue;
|
|
94
|
+
seen.add(rel);
|
|
95
|
+
out.push({ path: rel, laneId: base.laneId });
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/** The shared backfill plan — what would be stamped + what is ambiguous.
|
|
100
|
+
* Pure read: enumerates candidates, classifies each entry, writes nothing.
|
|
101
|
+
* Both `backfillFromLegacySites` (apply) and `detectAmbiguousBackfills`
|
|
102
|
+
* (audit) derive from this, so audit and apply agree by construction. */
|
|
103
|
+
async function planBackfills(projectRoot, bases) {
|
|
104
|
+
const entries = await readAllSidecars(projectRoot);
|
|
105
|
+
const toStamp = [];
|
|
106
|
+
const ambiguous = [];
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.artifactPath !== undefined && entry.artifactPath !== '')
|
|
109
|
+
continue;
|
|
110
|
+
const candidates = await enumerateCandidates(projectRoot, bases, entry);
|
|
111
|
+
if (candidates.length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
if (candidates.length > 1) {
|
|
114
|
+
ambiguous.push({
|
|
115
|
+
entryUuid: entry.uuid,
|
|
116
|
+
slug: entry.slug,
|
|
117
|
+
stage: entry.currentStage,
|
|
118
|
+
candidates: candidates.map((c) => c.path),
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
toStamp.push({ entry, path: candidates[0].path, laneId: candidates[0].laneId });
|
|
123
|
+
}
|
|
124
|
+
return { toStamp, ambiguous };
|
|
125
|
+
}
|
|
126
|
+
export async function backfillFromLegacySites(projectRoot, bases) {
|
|
127
|
+
const { toStamp, ambiguous } = await planBackfills(projectRoot, bases);
|
|
128
|
+
const stamped = [];
|
|
129
|
+
for (const { entry, path, laneId } of toStamp) {
|
|
130
|
+
const updated = {
|
|
131
|
+
...entry,
|
|
132
|
+
artifactPath: path,
|
|
133
|
+
// Assign the entry to the lane the migration created for its owning
|
|
134
|
+
// site (AUDIT-20260603-12). Without this, the migrated entry stays
|
|
135
|
+
// lane-less and `entry-lane-missing` flags it as an error in the
|
|
136
|
+
// very same `--fix=all` run. Only set when the entry has no lane;
|
|
137
|
+
// an entry already on a lane keeps its membership.
|
|
138
|
+
...(entry.lane === undefined ? { lane: laneId } : {}),
|
|
139
|
+
updatedAt: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
await writeSidecar(projectRoot, updated);
|
|
142
|
+
stamped.push(entry.uuid);
|
|
143
|
+
}
|
|
144
|
+
return { stamped, ambiguous };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Audit-only variant: enumerate the ambiguity collisions WITHOUT writing
|
|
148
|
+
* anything. Used by the doctor rule's `audit()` to surface
|
|
149
|
+
* `migration-ambiguous` findings before any `--fix` mutation. Shares
|
|
150
|
+
* `planBackfills` with the apply path so the audit and the apply agree on
|
|
151
|
+
* what is ambiguous by construction.
|
|
152
|
+
*/
|
|
153
|
+
export async function detectAmbiguousBackfills(projectRoot, bases) {
|
|
154
|
+
// The sidecars dir may not exist yet; treat absence as "no entries".
|
|
155
|
+
try {
|
|
156
|
+
await stat(sidecarsDir(projectRoot));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const { ambiguous } = await planBackfills(projectRoot, bases);
|
|
162
|
+
return ambiguous;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=sites-migration-backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites-migration-backfill.js","sourceRoot":"","sources":["../../src/doctor/sites-migration-backfill.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/doctor/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/doctor/validate.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EACJ,QAAQ,GACR,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,mBAAmB,GACnB,eAAe,GACf,kBAAkB,GAClB,aAAa,GACb,WAAW,GACX,uBAAuB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAqcD,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAYhF"}
|
package/dist/doctor/validate.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { EntrySchema, isLinearPipelineStage, isOffPipelineStage, } from "../schema/entry.js";
|
|
4
4
|
import { extractEntriesForMigration } from "../calendar/parse.js";
|
|
5
5
|
import { readJournalEvents } from "../journal/read.js";
|
|
6
|
+
import { resolveStoredArtifactPath } from "../entry/resolve-artifact.js";
|
|
6
7
|
async function validateSchema(projectRoot) {
|
|
7
8
|
const failures = [];
|
|
8
9
|
const dir = join(projectRoot, '.deskwork', 'entries');
|
|
@@ -48,15 +49,11 @@ async function readSidecarUuids(projectRoot) {
|
|
|
48
49
|
}
|
|
49
50
|
async function validateCalendarSidecar(projectRoot) {
|
|
50
51
|
const failures = [];
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// deciding whether the entry-centric calendar and the per-site
|
|
57
|
-
// calendarPath are one surface or two (entangled with #234); see #357.
|
|
58
|
-
// For the default config (calendarPath == .deskwork/calendar.md) the two
|
|
59
|
-
// coincide and there is no gap.
|
|
52
|
+
// This entry-centric check reads the single project calendar at
|
|
53
|
+
// `.deskwork/calendar.md`. Per the sites→lanes retirement spec §"Calendar"
|
|
54
|
+
// the calendar is a single derived projection (per-site `calendarPath` is
|
|
55
|
+
// retired); the de-parameterization of regenerate/repair lands in 39c
|
|
56
|
+
// (#223/#234/#357). This read-side target already matches that endpoint.
|
|
60
57
|
const calendarPath = join(projectRoot, '.deskwork', 'calendar.md');
|
|
61
58
|
let md;
|
|
62
59
|
try {
|
|
@@ -138,51 +135,19 @@ async function loadSidecars(projectRoot) {
|
|
|
138
135
|
return out;
|
|
139
136
|
}
|
|
140
137
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* outside the editorial pipeline's eight known values (per Phase 3 / Phase
|
|
144
|
-
* 4 — lane-aware path conventions land in the lane code, not in this
|
|
145
|
-
* editorial-specific heuristic).
|
|
138
|
+
* Resolve the on-disk artifact path for an entry — STORED PATH ONLY
|
|
139
|
+
* (Phase 39d, sites→lanes retirement).
|
|
146
140
|
*
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return join(projectRoot, 'docs', slug, 'scrapbook', 'plan.md');
|
|
155
|
-
case 'Outlining':
|
|
156
|
-
return join(projectRoot, 'docs', slug, 'scrapbook', 'outline.md');
|
|
157
|
-
case 'Drafting':
|
|
158
|
-
case 'Final':
|
|
159
|
-
case 'Published':
|
|
160
|
-
return join(projectRoot, 'docs', slug, 'index.md');
|
|
161
|
-
case 'Blocked':
|
|
162
|
-
case 'Cancelled':
|
|
163
|
-
return null;
|
|
164
|
-
default:
|
|
165
|
-
// Lane-specific or unrecognized stage; no editorial-default path
|
|
166
|
-
// applies. Phase 4 introduces template-driven path resolution.
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Resolve the on-disk artifact path for an entry.
|
|
172
|
-
*
|
|
173
|
-
* Precedence:
|
|
174
|
-
* 1. entry.artifactPath (when set — see #140 / migration)
|
|
175
|
-
* 2. slug+stage heuristic (`artifactPathForStage`) for entries without
|
|
176
|
-
* an explicit path. Off-pipeline stages (Blocked / Cancelled) still
|
|
177
|
-
* return null.
|
|
178
|
-
*
|
|
179
|
-
* Returns null when no artifact is expected for this stage.
|
|
141
|
+
* Per the spec §"Resolution": `entry.artifactPath` → the file, full
|
|
142
|
+
* stop. There is no base-searching and no slug+stage heuristic, so the
|
|
143
|
+
* #394-class multi-site ambiguity cannot recur. An entry that lacks
|
|
144
|
+
* `artifactPath` resolves to `null` here (callers treat null as "no
|
|
145
|
+
* artifact to check"); the `missing-artifact-path` rule surfaces the
|
|
146
|
+
* gap and the 39b migration backfiller (`sites-migration-backfill.ts`)
|
|
147
|
+
* owns stamping it — the runtime resolver never guesses.
|
|
180
148
|
*/
|
|
181
149
|
function resolveArtifactPath(projectRoot, entry) {
|
|
182
|
-
|
|
183
|
-
return join(projectRoot, entry.artifactPath);
|
|
184
|
-
}
|
|
185
|
-
return artifactPathForStage(projectRoot, entry.slug, entry.currentStage);
|
|
150
|
+
return resolveStoredArtifactPath(entry, projectRoot);
|
|
186
151
|
}
|
|
187
152
|
/**
|
|
188
153
|
* Minimal frontmatter `deskwork.stage` extractor.
|
|
@@ -326,24 +291,22 @@ async function validateIterationHistory(projectRoot) {
|
|
|
326
291
|
return failures;
|
|
327
292
|
}
|
|
328
293
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
294
|
+
* Surface entries that lack `artifactPath` on a stage that expects an
|
|
295
|
+
* on-disk artifact.
|
|
331
296
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
297
|
+
* Phase 39d (sites→lanes retirement): resolution reads the stored path
|
|
298
|
+
* ONLY — the runtime no longer derives a path from the slug+stage
|
|
299
|
+
* heuristic, so this rule no longer SUGGESTS a heuristic location.
|
|
300
|
+
* It reports the missing field and points the operator at the migration
|
|
301
|
+
* backfiller, which is the spec-sanctioned LAST use of the heuristic
|
|
302
|
+
* (`sites-migration-backfill.ts`, 39b). The migration enumerates
|
|
303
|
+
* candidates across legacy content dirs, halts on ambiguity, and stamps
|
|
304
|
+
* the unambiguous ones.
|
|
337
305
|
*
|
|
338
306
|
* Returns nothing for entries that:
|
|
339
|
-
* - Already have artifactPath
|
|
340
|
-
* -
|
|
341
|
-
*
|
|
342
|
-
* handles those — backfilling a non-existent path would be
|
|
343
|
-
* misleading).
|
|
344
|
-
*
|
|
345
|
-
* Repair: write `artifactPath = <heuristic-relative-path>` into the
|
|
346
|
-
* sidecar.
|
|
307
|
+
* - Already have a non-empty `artifactPath` (no gap).
|
|
308
|
+
* - Are at an off-pipeline stage with no on-disk artifact
|
|
309
|
+
* (Blocked / Cancelled) — those legitimately carry no path.
|
|
347
310
|
*/
|
|
348
311
|
async function validateMissingArtifactPath(projectRoot) {
|
|
349
312
|
const failures = [];
|
|
@@ -351,16 +314,14 @@ async function validateMissingArtifactPath(projectRoot) {
|
|
|
351
314
|
for (const { entry, path } of sidecars) {
|
|
352
315
|
if (entry.artifactPath !== undefined && entry.artifactPath !== '')
|
|
353
316
|
continue;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (!(await fileExists(heuristic)))
|
|
317
|
+
// Off-pipeline stages (Blocked / Cancelled) carry no on-disk
|
|
318
|
+
// artifact, so a missing artifactPath is expected, not a gap.
|
|
319
|
+
if (isOffPipelineStage(entry.currentStage))
|
|
358
320
|
continue;
|
|
359
|
-
// Sidecar lacks artifactPath; the heuristic resolves and the file
|
|
360
|
-
// exists — this is a backfillable case.
|
|
361
321
|
failures.push({
|
|
362
322
|
category: 'missing-artifact-path',
|
|
363
|
-
message: `sidecar lacks artifactPath
|
|
323
|
+
message: `sidecar lacks artifactPath (currentStage=${entry.currentStage}); ` +
|
|
324
|
+
`run \`deskwork doctor --fix\` to backfill it from the sites→lanes migration`,
|
|
364
325
|
entryId: entry.uuid,
|
|
365
326
|
path,
|
|
366
327
|
});
|