@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,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: orphan-frontmatter-id.
|
|
3
|
+
*
|
|
4
|
+
* Audit: every entry in the content index whose id has no matching
|
|
5
|
+
* calendar entry. The file is bound to "something", but the calendar
|
|
6
|
+
* doesn't know about it.
|
|
7
|
+
*
|
|
8
|
+
* Repair: there are three plausible operator intents — (a) add a
|
|
9
|
+
* calendar row for the file, (b) clear the orphan id from the file
|
|
10
|
+
* (un-bind), or (c) leave it alone. Without a way to gather the
|
|
11
|
+
* intent, the rule reports findings and presents a prompt; with
|
|
12
|
+
* `--yes`, the safest action is "do nothing" — auto-creating
|
|
13
|
+
* calendar rows or auto-deleting frontmatter is destructive.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { relative } from 'node:path';
|
|
17
|
+
import { parseFrontmatter, removeFrontmatterPaths } from "../../frontmatter.js";
|
|
18
|
+
const RULE_ID = 'orphan-frontmatter-id';
|
|
19
|
+
/**
|
|
20
|
+
* Clear the `deskwork.id` field from a markdown file's frontmatter.
|
|
21
|
+
* Returns true when the field was present and cleared; false when there
|
|
22
|
+
* was nothing to clear. Issue #38: scoped to the namespaced key — top-
|
|
23
|
+
* level `id:` belongs to the operator and is left alone.
|
|
24
|
+
*
|
|
25
|
+
* Uses the round-trip-preserving emitter so untouched keys keep their
|
|
26
|
+
* exact bytes (quoting, comments, ordering). Empty `deskwork:` blocks
|
|
27
|
+
* are pruned via `removeFrontmatterPaths`.
|
|
28
|
+
*/
|
|
29
|
+
function clearFrontmatterId(absPath) {
|
|
30
|
+
const raw = readFileSync(absPath, 'utf-8');
|
|
31
|
+
const { data } = parseFrontmatter(raw);
|
|
32
|
+
const block = data.deskwork;
|
|
33
|
+
if (block === undefined || block === null)
|
|
34
|
+
return false;
|
|
35
|
+
if (typeof block !== 'object' || Array.isArray(block))
|
|
36
|
+
return false;
|
|
37
|
+
const blockObj = block;
|
|
38
|
+
if (!('id' in blockObj))
|
|
39
|
+
return false;
|
|
40
|
+
const updated = removeFrontmatterPaths(raw, [['deskwork', 'id']]);
|
|
41
|
+
if (updated === raw)
|
|
42
|
+
return false;
|
|
43
|
+
writeFileSync(absPath, updated, 'utf-8');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const rule = {
|
|
47
|
+
id: RULE_ID,
|
|
48
|
+
label: 'Files with frontmatter ids that are not in the calendar',
|
|
49
|
+
async audit(ctx) {
|
|
50
|
+
const findings = [];
|
|
51
|
+
const calendarIds = new Set();
|
|
52
|
+
for (const e of ctx.calendar.entries) {
|
|
53
|
+
if (e.id)
|
|
54
|
+
calendarIds.add(e.id);
|
|
55
|
+
}
|
|
56
|
+
for (const [id, absPath] of ctx.index.byId) {
|
|
57
|
+
if (calendarIds.has(id))
|
|
58
|
+
continue;
|
|
59
|
+
findings.push({
|
|
60
|
+
ruleId: RULE_ID,
|
|
61
|
+
site: ctx.site,
|
|
62
|
+
severity: 'warning',
|
|
63
|
+
message: `File ${relative(ctx.projectRoot, absPath)} carries id ${id}, which is not in the calendar`,
|
|
64
|
+
details: { absolutePath: absPath, entryId: id },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return findings;
|
|
68
|
+
},
|
|
69
|
+
async plan(_ctx, finding) {
|
|
70
|
+
const absPath = String(finding.details.absolutePath ?? '');
|
|
71
|
+
const entryId = String(finding.details.entryId ?? '');
|
|
72
|
+
return {
|
|
73
|
+
kind: 'prompt',
|
|
74
|
+
finding,
|
|
75
|
+
question: `File ${absPath} has id ${entryId} but no calendar entry matches. Pick an action:`,
|
|
76
|
+
choices: [
|
|
77
|
+
{
|
|
78
|
+
id: 'none',
|
|
79
|
+
label: 'leave as-is (default; review manually)',
|
|
80
|
+
payload: { action: 'none' },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'clear-id',
|
|
84
|
+
label: `clear the id from ${absPath} (un-bind the file)`,
|
|
85
|
+
payload: { action: 'clear-id', absolutePath: absPath },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
async apply(ctx, plan) {
|
|
91
|
+
if (plan.kind !== 'apply') {
|
|
92
|
+
return {
|
|
93
|
+
finding: plan.finding,
|
|
94
|
+
applied: false,
|
|
95
|
+
message: 'plan is not directly appliable; runner should resolve prompt first',
|
|
96
|
+
skipReason: 'apply-failed',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const action = String(plan.payload.action ?? '');
|
|
100
|
+
if (action === 'none') {
|
|
101
|
+
return {
|
|
102
|
+
finding: plan.finding,
|
|
103
|
+
applied: false,
|
|
104
|
+
message: 'left file unchanged per operator choice',
|
|
105
|
+
skipReason: 'no-action-needed',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (action === 'clear-id') {
|
|
109
|
+
const absPath = String(plan.payload.absolutePath ?? '');
|
|
110
|
+
if (!absPath) {
|
|
111
|
+
return {
|
|
112
|
+
finding: plan.finding,
|
|
113
|
+
applied: false,
|
|
114
|
+
message: 'clear-id apply payload missing absolutePath',
|
|
115
|
+
skipReason: 'apply-failed',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const changed = clearFrontmatterId(absPath);
|
|
120
|
+
if (!changed) {
|
|
121
|
+
return {
|
|
122
|
+
finding: plan.finding,
|
|
123
|
+
applied: false,
|
|
124
|
+
message: `no id field in ${relative(ctx.projectRoot, absPath)} to clear`,
|
|
125
|
+
skipReason: 'no-action-needed',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
131
|
+
return {
|
|
132
|
+
finding: plan.finding,
|
|
133
|
+
applied: false,
|
|
134
|
+
message: `failed to clear frontmatter id: ${reason}`,
|
|
135
|
+
skipReason: 'apply-failed',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
finding: plan.finding,
|
|
140
|
+
applied: true,
|
|
141
|
+
message: `cleared id from ${relative(ctx.projectRoot, absPath)}`,
|
|
142
|
+
details: { absolutePath: absPath },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
finding: plan.finding,
|
|
147
|
+
applied: false,
|
|
148
|
+
message: `unknown apply action: ${action}`,
|
|
149
|
+
skipReason: 'apply-failed',
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
export default rule;
|
|
154
|
+
//# sourceMappingURL=orphan-frontmatter-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-frontmatter-id.js","sourceRoot":"","sources":["../../../src/doctor/rules/orphan-frontmatter-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: schema-rejected.
|
|
3
|
+
*
|
|
4
|
+
* The host's content collection schema may reject the `id` frontmatter
|
|
5
|
+
* field that deskwork relies on for the calendar/file binding. We
|
|
6
|
+
* detect this at *write time* in other code paths (scaffolder, the
|
|
7
|
+
* other rules' apply()), not by an active probe — running an actual
|
|
8
|
+
* Astro build inside doctor would be slow and project-specific.
|
|
9
|
+
*
|
|
10
|
+
* This rule's audit always returns empty for that reason. The
|
|
11
|
+
* `printSchemaPatchInstructions` helper is the user-facing surface;
|
|
12
|
+
* other rules (and the CLI command) call it when they observe an
|
|
13
|
+
* actual schema rejection. Phase 19b-followup may add an active
|
|
14
|
+
* probe (write a tmpfile to contentDir, attempt astro check, etc.)
|
|
15
|
+
* once the integration cost is justified.
|
|
16
|
+
*/
|
|
17
|
+
import type { DoctorRule } from '../types.ts';
|
|
18
|
+
declare const rule: DoctorRule;
|
|
19
|
+
export default rule;
|
|
20
|
+
//# sourceMappingURL=schema-rejected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-rejected.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/schema-rejected.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAIrB,QAAA,MAAM,IAAI,EAAE,UA2BX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: schema-rejected.
|
|
3
|
+
*
|
|
4
|
+
* The host's content collection schema may reject the `id` frontmatter
|
|
5
|
+
* field that deskwork relies on for the calendar/file binding. We
|
|
6
|
+
* detect this at *write time* in other code paths (scaffolder, the
|
|
7
|
+
* other rules' apply()), not by an active probe — running an actual
|
|
8
|
+
* Astro build inside doctor would be slow and project-specific.
|
|
9
|
+
*
|
|
10
|
+
* This rule's audit always returns empty for that reason. The
|
|
11
|
+
* `printSchemaPatchInstructions` helper is the user-facing surface;
|
|
12
|
+
* other rules (and the CLI command) call it when they observe an
|
|
13
|
+
* actual schema rejection. Phase 19b-followup may add an active
|
|
14
|
+
* probe (write a tmpfile to contentDir, attempt astro check, etc.)
|
|
15
|
+
* once the integration cost is justified.
|
|
16
|
+
*/
|
|
17
|
+
import { printSchemaPatchInstructions } from "../schema-patch.js";
|
|
18
|
+
const RULE_ID = 'schema-rejected';
|
|
19
|
+
const rule = {
|
|
20
|
+
id: RULE_ID,
|
|
21
|
+
label: "Host's content schema rejects the `id` frontmatter field",
|
|
22
|
+
async audit(_ctx) {
|
|
23
|
+
// Passive — see file header. Other code paths surface schema-patch
|
|
24
|
+
// instructions when they observe an actual rejection.
|
|
25
|
+
return [];
|
|
26
|
+
},
|
|
27
|
+
async plan(_ctx, finding) {
|
|
28
|
+
return {
|
|
29
|
+
kind: 'report-only',
|
|
30
|
+
finding,
|
|
31
|
+
reason: printSchemaPatchInstructions(),
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
async apply(_ctx, plan) {
|
|
35
|
+
return {
|
|
36
|
+
finding: plan.finding,
|
|
37
|
+
applied: false,
|
|
38
|
+
message: 'schema-rejected has no automatic repair — operator must patch the host content schema',
|
|
39
|
+
skipReason: 'schema-rejected',
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export default rule;
|
|
44
|
+
//# sourceMappingURL=schema-rejected.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-rejected.js","sourceRoot":"","sources":["../../../src/doctor/rules/schema-rejected.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AASlE,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAElC,MAAM,IAAI,GAAe;IACvB,EAAE,EAAE,OAAO;IACX,KAAK,EAAE,0DAA0D;IAEjE,KAAK,CAAC,KAAK,CAAC,IAAmB;QAC7B,mEAAmE;QACnE,sDAAsD;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAmB,EAAE,OAAgB;QAC9C,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,OAAO;YACP,MAAM,EAAE,4BAA4B,EAAE;SACvC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAmB,EAAE,IAAgB;QAC/C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,KAAK;YACd,OAAO,EACL,uFAAuF;YACzF,UAAU,EAAE,iBAAiB;SAC9B,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: slug-collision.
|
|
3
|
+
*
|
|
4
|
+
* Audit: two or more calendar entries share the same slug. With UUID
|
|
5
|
+
* identity this is no longer a hard error (joins go through id), but
|
|
6
|
+
* it's still a bug — the host renderer maps URLs by slug, so two
|
|
7
|
+
* entries claiming the same slug produces duplicate or hidden public
|
|
8
|
+
* URLs.
|
|
9
|
+
*
|
|
10
|
+
* Repair: rename one slug. Doctor doesn't pick which one — that's an
|
|
11
|
+
* editorial decision (which entry "owns" the public URL). The rule
|
|
12
|
+
* reports findings; with no interactive UI it returns `report-only`.
|
|
13
|
+
* `--yes` mode skips.
|
|
14
|
+
*/
|
|
15
|
+
import type { DoctorRule } from '../types.ts';
|
|
16
|
+
declare const rule: DoctorRule;
|
|
17
|
+
export default rule;
|
|
18
|
+
//# sourceMappingURL=slug-collision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug-collision.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/slug-collision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AAyBrB,QAAA,MAAM,IAAI,EAAE,UAiCX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: slug-collision.
|
|
3
|
+
*
|
|
4
|
+
* Audit: two or more calendar entries share the same slug. With UUID
|
|
5
|
+
* identity this is no longer a hard error (joins go through id), but
|
|
6
|
+
* it's still a bug — the host renderer maps URLs by slug, so two
|
|
7
|
+
* entries claiming the same slug produces duplicate or hidden public
|
|
8
|
+
* URLs.
|
|
9
|
+
*
|
|
10
|
+
* Repair: rename one slug. Doctor doesn't pick which one — that's an
|
|
11
|
+
* editorial decision (which entry "owns" the public URL). The rule
|
|
12
|
+
* reports findings; with no interactive UI it returns `report-only`.
|
|
13
|
+
* `--yes` mode skips.
|
|
14
|
+
*/
|
|
15
|
+
const RULE_ID = 'slug-collision';
|
|
16
|
+
function findCollisions(ctx) {
|
|
17
|
+
const bySlug = new Map();
|
|
18
|
+
for (const e of ctx.calendar.entries) {
|
|
19
|
+
if (!e.slug)
|
|
20
|
+
continue;
|
|
21
|
+
const list = bySlug.get(e.slug);
|
|
22
|
+
if (list)
|
|
23
|
+
list.push(e.id ?? '');
|
|
24
|
+
else
|
|
25
|
+
bySlug.set(e.slug, [e.id ?? '']);
|
|
26
|
+
}
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const [slug, ids] of bySlug) {
|
|
29
|
+
if (ids.length > 1)
|
|
30
|
+
out.push({ slug, entryIds: ids });
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
const rule = {
|
|
35
|
+
id: RULE_ID,
|
|
36
|
+
label: 'Duplicate slugs in the calendar',
|
|
37
|
+
async audit(ctx) {
|
|
38
|
+
return findCollisions(ctx).map((g) => ({
|
|
39
|
+
ruleId: RULE_ID,
|
|
40
|
+
site: ctx.site,
|
|
41
|
+
severity: 'error',
|
|
42
|
+
message: `slug "${g.slug}" is shared by ${g.entryIds.length} calendar entries`,
|
|
43
|
+
details: { slug: g.slug, entryIds: g.entryIds },
|
|
44
|
+
}));
|
|
45
|
+
},
|
|
46
|
+
async plan(_ctx, finding) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'report-only',
|
|
49
|
+
finding,
|
|
50
|
+
reason: 'pick which entry owns the slug and rename the others via `deskwork rename-slug` ' +
|
|
51
|
+
'(or hand-edit the calendar). Doctor refuses to choose automatically — slug is ' +
|
|
52
|
+
'host-public-URL, an editorial decision.',
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
async apply(_ctx, plan) {
|
|
56
|
+
return {
|
|
57
|
+
finding: plan.finding,
|
|
58
|
+
applied: false,
|
|
59
|
+
message: 'slug-collision has no automatic repair (operator must rename)',
|
|
60
|
+
skipReason: 'editorial-decision',
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export default rule;
|
|
65
|
+
//# sourceMappingURL=slug-collision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug-collision.js","sourceRoot":"","sources":["../../../src/doctor/rules/slug-collision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,MAAM,OAAO,GAAG,gBAAgB,CAAC;AAQjC,SAAS,cAAc,CAAC,GAAkB;IACxC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,CAAC,IAAI;YAAE,SAAS;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAe;IACvB,EAAE,EAAE,OAAO;IACX,KAAK,EAAE,iCAAiC;IAExC,KAAK,CAAC,KAAK,CAAC,GAAkB;QAC5B,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACrC,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,QAAQ,CAAC,MAAM,mBAAmB;YAC9E,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE;SAChD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAmB,EAAE,OAAgB;QAC9C,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,OAAO;YACP,MAAM,EACJ,kFAAkF;gBAClF,gFAAgF;gBAChF,yCAAyC;SAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAmB,EAAE,IAAgB;QAC/C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,+DAA+D;YACxE,UAAU,EAAE,oBAAoB;SACjC,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: workflow-stale.
|
|
3
|
+
*
|
|
4
|
+
* Audit: a `DraftWorkflowItem` whose `(site, slug)` no longer resolves
|
|
5
|
+
* to a calendar entry on the workflow's site. Two failure modes:
|
|
6
|
+
* - The entry was deleted from the calendar (truly stale).
|
|
7
|
+
* - The entry's slug was renamed and the workflow predates the rename.
|
|
8
|
+
*
|
|
9
|
+
* Detecting the slug-rename case requires `entryId` on the workflow —
|
|
10
|
+
* not yet present on legacy records. For now the rule reports only
|
|
11
|
+
* "no entry found by site+slug" findings and ignores the rename case.
|
|
12
|
+
*
|
|
13
|
+
* Repair: clear the stale workflow record from the pipeline journal.
|
|
14
|
+
* The history journal is append-only and stays untouched (provenance).
|
|
15
|
+
* `--yes` applies; interactive prompts before deletion.
|
|
16
|
+
*/
|
|
17
|
+
import type { DoctorRule } from '../types.ts';
|
|
18
|
+
declare const rule: DoctorRule;
|
|
19
|
+
export default rule;
|
|
20
|
+
//# sourceMappingURL=workflow-stale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-stale.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAEV,UAAU,EAIX,MAAM,aAAa,CAAC;AA6CrB,QAAA,MAAM,IAAI,EAAE,UA+EX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: workflow-stale.
|
|
3
|
+
*
|
|
4
|
+
* Audit: a `DraftWorkflowItem` whose `(site, slug)` no longer resolves
|
|
5
|
+
* to a calendar entry on the workflow's site. Two failure modes:
|
|
6
|
+
* - The entry was deleted from the calendar (truly stale).
|
|
7
|
+
* - The entry's slug was renamed and the workflow predates the rename.
|
|
8
|
+
*
|
|
9
|
+
* Detecting the slug-rename case requires `entryId` on the workflow —
|
|
10
|
+
* not yet present on legacy records. For now the rule reports only
|
|
11
|
+
* "no entry found by site+slug" findings and ignores the rename case.
|
|
12
|
+
*
|
|
13
|
+
* Repair: clear the stale workflow record from the pipeline journal.
|
|
14
|
+
* The history journal is append-only and stays untouched (provenance).
|
|
15
|
+
* `--yes` applies; interactive prompts before deletion.
|
|
16
|
+
*/
|
|
17
|
+
import { unlinkSync, readdirSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { pipelinePath } from "../../review/pipeline.js";
|
|
20
|
+
const RULE_ID = 'workflow-stale';
|
|
21
|
+
function isStale(workflow, ctx) {
|
|
22
|
+
if (workflow.site !== ctx.site)
|
|
23
|
+
return false;
|
|
24
|
+
if (workflow.state === 'applied' || workflow.state === 'cancelled') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// Prefer entryId match when both sides have it. If the workflow has
|
|
28
|
+
// an entryId and the calendar doesn't carry that id, the workflow is
|
|
29
|
+
// stale regardless of slug. If it doesn't, fall back to slug match.
|
|
30
|
+
if (workflow.entryId) {
|
|
31
|
+
return !ctx.calendar.entries.some((e) => e.id === workflow.entryId);
|
|
32
|
+
}
|
|
33
|
+
return !ctx.calendar.entries.some((e) => e.slug === workflow.slug);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Find the pipeline-journal file backing a workflow id. Files are
|
|
37
|
+
* named `<normalizedTimestamp>-<id>.json`; we suffix-match on
|
|
38
|
+
* `-<id>.json`.
|
|
39
|
+
*/
|
|
40
|
+
function findWorkflowFile(projectRoot, config, workflowId) {
|
|
41
|
+
const dir = pipelinePath(projectRoot, config);
|
|
42
|
+
let names;
|
|
43
|
+
try {
|
|
44
|
+
names = readdirSync(dir);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const suffix = `-${workflowId}.json`;
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
if (name.endsWith(suffix))
|
|
52
|
+
return join(dir, name);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const rule = {
|
|
57
|
+
id: RULE_ID,
|
|
58
|
+
label: 'Workflow records that no longer match a calendar entry',
|
|
59
|
+
async audit(ctx) {
|
|
60
|
+
const findings = [];
|
|
61
|
+
for (const w of ctx.workflows) {
|
|
62
|
+
if (!isStale(w, ctx))
|
|
63
|
+
continue;
|
|
64
|
+
findings.push({
|
|
65
|
+
ruleId: RULE_ID,
|
|
66
|
+
site: ctx.site,
|
|
67
|
+
severity: 'warning',
|
|
68
|
+
message: `Workflow ${w.id} (slug "${w.slug}", state ${w.state}) has no matching calendar entry`,
|
|
69
|
+
details: {
|
|
70
|
+
workflowId: w.id,
|
|
71
|
+
slug: w.slug,
|
|
72
|
+
state: w.state,
|
|
73
|
+
entryId: w.entryId ?? null,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return findings;
|
|
78
|
+
},
|
|
79
|
+
async plan(_ctx, finding) {
|
|
80
|
+
const workflowId = String(finding.details.workflowId ?? '');
|
|
81
|
+
return {
|
|
82
|
+
kind: 'apply',
|
|
83
|
+
finding,
|
|
84
|
+
summary: `delete pipeline journal entry for workflow ${workflowId} (history journal preserved)`,
|
|
85
|
+
payload: { workflowId },
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
async apply(ctx, plan) {
|
|
89
|
+
if (plan.kind !== 'apply') {
|
|
90
|
+
return {
|
|
91
|
+
finding: plan.finding,
|
|
92
|
+
applied: false,
|
|
93
|
+
message: 'plan is not directly appliable; runner should resolve prompt first',
|
|
94
|
+
skipReason: 'apply-failed',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const workflowId = String(plan.payload.workflowId ?? '');
|
|
98
|
+
if (!workflowId) {
|
|
99
|
+
return {
|
|
100
|
+
finding: plan.finding,
|
|
101
|
+
applied: false,
|
|
102
|
+
message: 'apply payload missing workflowId',
|
|
103
|
+
skipReason: 'apply-failed',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const file = findWorkflowFile(ctx.projectRoot, ctx.config, workflowId);
|
|
107
|
+
if (!file) {
|
|
108
|
+
return {
|
|
109
|
+
finding: plan.finding,
|
|
110
|
+
applied: false,
|
|
111
|
+
message: `no pipeline file found for workflow ${workflowId}`,
|
|
112
|
+
skipReason: 'apply-failed',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(file);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
120
|
+
return {
|
|
121
|
+
finding: plan.finding,
|
|
122
|
+
applied: false,
|
|
123
|
+
message: `failed to delete ${file}: ${reason}`,
|
|
124
|
+
skipReason: 'apply-failed',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
finding: plan.finding,
|
|
129
|
+
applied: true,
|
|
130
|
+
message: `deleted pipeline entry for workflow ${workflowId}`,
|
|
131
|
+
details: { file, workflowId },
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
export default rule;
|
|
136
|
+
//# sourceMappingURL=workflow-stale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-stale.js","sourceRoot":"","sources":["../../../src/doctor/rules/workflow-stale.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor runner — orchestrates rule execution.
|
|
3
|
+
*
|
|
4
|
+
* The runner owns the per-site setup (read calendar, build content
|
|
5
|
+
* index, scope workflows) and walks the registered rules in a stable
|
|
6
|
+
* order. `runAudit` only calls `audit()`; `runRepair` chains audit →
|
|
7
|
+
* plan → (interaction) → apply.
|
|
8
|
+
*
|
|
9
|
+
* Sibling-relative imports per the project convention.
|
|
10
|
+
*/
|
|
11
|
+
import type { DeskworkConfig } from '../config.ts';
|
|
12
|
+
import type { DoctorInteraction, DoctorReport, DoctorRule } from './types.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Registry of all rules in the order they run. The order matters: we
|
|
15
|
+
* detect calendar-uuid-missing first (to flush UUIDs), then run the
|
|
16
|
+
* frontmatter-id rules (which depend on UUIDs being persisted on
|
|
17
|
+
* disk to be useful in long-lived data).
|
|
18
|
+
*
|
|
19
|
+
* `legacy-top-level-id-migration` (Issue #38) runs BEFORE
|
|
20
|
+
* `missing-frontmatter-id` so that v0.7.0/v0.7.1-shaped files migrate
|
|
21
|
+
* to the namespaced form first; on the same run, the
|
|
22
|
+
* missing-frontmatter-id rule then sees the migrated files as bound
|
|
23
|
+
* (via `deskwork.id`) and doesn't re-report them.
|
|
24
|
+
*/
|
|
25
|
+
export declare const RULES: ReadonlyArray<DoctorRule>;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a CSV/comma-separated `--fix=` argument to rule ids.
|
|
28
|
+
*
|
|
29
|
+
* Returns the full list of built-in rule ids for `''` and `'all'`.
|
|
30
|
+
* Unknown built-in id strings are rejected (exit 2 in the CLI).
|
|
31
|
+
*
|
|
32
|
+
* Project rules registered via `<projectRoot>/.deskwork/doctor/*.ts`
|
|
33
|
+
* (Phase 23f) are selected by passing `'all'`; the runner picks them
|
|
34
|
+
* up from the merged rule list. Selecting an individual project rule
|
|
35
|
+
* by id via `--fix=<id>` is not yet supported — file an issue if the
|
|
36
|
+
* usage emerges.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseFixArgument(arg: string): string[];
|
|
39
|
+
export interface DoctorRunOptions {
|
|
40
|
+
projectRoot: string;
|
|
41
|
+
config: DeskworkConfig;
|
|
42
|
+
/** Restrict to one site; undefined = run for every site in config. */
|
|
43
|
+
site?: string;
|
|
44
|
+
/** Restrict the rule set; undefined = all rules. */
|
|
45
|
+
ruleIds?: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Audit: collect findings without mutating the world. Returns a fully-
|
|
49
|
+
* built report with empty `repairs`. Suitable for pre-commit hooks
|
|
50
|
+
* that just want a non-zero exit code on any finding.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runAudit(opts: DoctorRunOptions, interaction: DoctorInteraction): Promise<DoctorReport>;
|
|
53
|
+
/**
|
|
54
|
+
* Repair: run audit → plan → (consult interaction) → apply. Returns
|
|
55
|
+
* the audit findings AND the repair results so callers can render
|
|
56
|
+
* a single report covering both phases.
|
|
57
|
+
*
|
|
58
|
+
* For `prompt` plans the runner consults `interaction.pickChoice` to
|
|
59
|
+
* resolve to an apply payload; for `apply` plans it consults
|
|
60
|
+
* `interaction.confirmApply`. `report-only` plans never apply.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runRepair(opts: DoctorRunOptions, interaction: DoctorInteraction): Promise<DoctorReport>;
|
|
63
|
+
/**
|
|
64
|
+
* Pre-built interaction: always confirm `apply` plans, skip `prompt`
|
|
65
|
+
* plans (no way to choose without a UI). Used by `--yes` mode.
|
|
66
|
+
*
|
|
67
|
+
* Exposed for the CLI command to construct.
|
|
68
|
+
*/
|
|
69
|
+
export declare const yesInteraction: DoctorInteraction;
|
|
70
|
+
/**
|
|
71
|
+
* Pre-built interaction: never apply anything. Used to dry-run a
|
|
72
|
+
* repair pipeline (prompt resolution + apply both no-op).
|
|
73
|
+
*/
|
|
74
|
+
export declare const declineInteraction: DoctorInteraction;
|
|
75
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/doctor/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAUnD,OAAO,KAAK,EAEV,iBAAiB,EACjB,YAAY,EACZ,UAAU,EAIX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,UAAU,CAS3C,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtD;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAwED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAavB;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAoBvB;AAmGD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,iBAU5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAOhC,CAAC"}
|