strikethroo 3.16.0 → 3.17.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.
Files changed (86) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.js +37 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +43 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/serve/root.d.ts +27 -11
  8. package/dist/serve/root.d.ts.map +1 -1
  9. package/dist/serve/root.js +36 -16
  10. package/dist/serve/root.js.map +1 -1
  11. package/dist/validation/graph-checks.d.ts +45 -0
  12. package/dist/validation/graph-checks.d.ts.map +1 -0
  13. package/dist/validation/graph-checks.js +388 -0
  14. package/dist/validation/graph-checks.js.map +1 -0
  15. package/dist/validation/metadata-gate.d.ts +26 -0
  16. package/dist/validation/metadata-gate.d.ts.map +1 -0
  17. package/dist/validation/metadata-gate.js +123 -0
  18. package/dist/validation/metadata-gate.js.map +1 -0
  19. package/dist/validation/strict-pass.d.ts +38 -0
  20. package/dist/validation/strict-pass.d.ts.map +1 -0
  21. package/dist/validation/strict-pass.js +338 -0
  22. package/dist/validation/strict-pass.js.map +1 -0
  23. package/dist/validation/types.d.ts +30 -0
  24. package/dist/validation/types.d.ts.map +1 -0
  25. package/dist/validation/types.js +12 -0
  26. package/dist/validation/types.js.map +1 -0
  27. package/dist/validation/workspace.d.ts +31 -0
  28. package/dist/validation/workspace.d.ts.map +1 -0
  29. package/dist/validation/workspace.js +49 -0
  30. package/dist/validation/workspace.js.map +1 -0
  31. package/dist-web/assets/{arc-DMHUv2NY.js → arc-CtIXTnp-.js} +1 -1
  32. package/dist-web/assets/{architectureDiagram-3BPJPVTR-CeAAqVS4.js → architectureDiagram-3BPJPVTR-DjLBuYL_.js} +1 -1
  33. package/dist-web/assets/{blockDiagram-GPEHLZMM-Hns_u_jz.js → blockDiagram-GPEHLZMM-CyUvqZi1.js} +1 -1
  34. package/dist-web/assets/{c4Diagram-AAUBKEIU-BH3RLVAi.js → c4Diagram-AAUBKEIU-DbT3qzdM.js} +1 -1
  35. package/dist-web/assets/channel-ejrFqLJP.js +1 -0
  36. package/dist-web/assets/{chunk-2J33WTMH-h6Ws9VXu.js → chunk-2J33WTMH-w1zteFnE.js} +1 -1
  37. package/dist-web/assets/{chunk-4BX2VUAB-BSBeNNnS.js → chunk-4BX2VUAB-AXaQBlkr.js} +1 -1
  38. package/dist-web/assets/{chunk-55IACEB6-BmUY060b.js → chunk-55IACEB6-YxIJIoS8.js} +1 -1
  39. package/dist-web/assets/{chunk-727SXJPM-CmemMVvQ.js → chunk-727SXJPM-Ec_YNRXy.js} +1 -1
  40. package/dist-web/assets/{chunk-AQP2D5EJ-LQpUb_FL.js → chunk-AQP2D5EJ-Bq3XSb33.js} +1 -1
  41. package/dist-web/assets/{chunk-FMBD7UC4-wk7OYcdf.js → chunk-FMBD7UC4-D3hDHEbQ.js} +1 -1
  42. package/dist-web/assets/{chunk-ND2GUHAM-O3K3rkHW.js → chunk-ND2GUHAM-D7qdmfIe.js} +1 -1
  43. package/dist-web/assets/{chunk-QZHKN3VN-CvppUbp9.js → chunk-QZHKN3VN-CnaHYcq_.js} +1 -1
  44. package/dist-web/assets/classDiagram-4FO5ZUOK-D4kGOS-J.js +1 -0
  45. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-D4kGOS-J.js +1 -0
  46. package/dist-web/assets/{cose-bilkent-S5V4N54A-Cf1nQ4V_.js → cose-bilkent-S5V4N54A-B-C7BPM4.js} +1 -1
  47. package/dist-web/assets/{dagre-BM42HDAG-YyP7mwPP.js → dagre-BM42HDAG-Ce21GDpz.js} +1 -1
  48. package/dist-web/assets/{diagram-2AECGRRQ-BmAtTKkQ.js → diagram-2AECGRRQ-DHwtwYvI.js} +1 -1
  49. package/dist-web/assets/{diagram-5GNKFQAL-Bem8_DUK.js → diagram-5GNKFQAL-Cz3oNPoV.js} +1 -1
  50. package/dist-web/assets/{diagram-KO2AKTUF-0gNyY9-f.js → diagram-KO2AKTUF-DvTrw4fW.js} +1 -1
  51. package/dist-web/assets/{diagram-LMA3HP47-CJJh_IUU.js → diagram-LMA3HP47-BkoqJsup.js} +1 -1
  52. package/dist-web/assets/{diagram-OG6HWLK6-DJcCho7F.js → diagram-OG6HWLK6-D-g29Bkd.js} +1 -1
  53. package/dist-web/assets/{erDiagram-TEJ5UH35-D-TI_EbX.js → erDiagram-TEJ5UH35-CTjmf6Xm.js} +1 -1
  54. package/dist-web/assets/{flowDiagram-I6XJVG4X-BUrB4EYN.js → flowDiagram-I6XJVG4X-Bw77m94u.js} +1 -1
  55. package/dist-web/assets/{ganttDiagram-6RSMTGT7-C9_ItheP.js → ganttDiagram-6RSMTGT7-gzA4FOSg.js} +1 -1
  56. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-DYQEmll0.js → gitGraphDiagram-PVQCEYII-BRaUUXaJ.js} +1 -1
  57. package/dist-web/assets/{index-mEUl0sqe.js → index-B_YjLzSw.js} +1 -1
  58. package/dist-web/assets/{index-X-a-o-AT.js → index-DJpZGE86.js} +1 -1
  59. package/dist-web/assets/{index-DULKNhw0.js → index-YT2EMDff.js} +4 -4
  60. package/dist-web/assets/{infoDiagram-5YYISTIA-B06Tp7cb.js → infoDiagram-5YYISTIA-h6n8eqGu.js} +1 -1
  61. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-DY9iP3P6.js → ishikawaDiagram-YF4QCWOH-BiJ3tbak.js} +1 -1
  62. package/dist-web/assets/{journeyDiagram-JHISSGLW-Bk4MV49m.js → journeyDiagram-JHISSGLW-Q31kk_hH.js} +1 -1
  63. package/dist-web/assets/{kanban-definition-UN3LZRKU-B9-wsNt9.js → kanban-definition-UN3LZRKU-ZV5ks__v.js} +1 -1
  64. package/dist-web/assets/{linear-DEY9HwQB.js → linear-F-j1CAaU.js} +1 -1
  65. package/dist-web/assets/{mermaid.core-Cn0e4l6V.js → mermaid.core-C6KsMc78.js} +4 -4
  66. package/dist-web/assets/{mindmap-definition-RKZ34NQL-pN4Q2WNG.js → mindmap-definition-RKZ34NQL-D-qRccYa.js} +1 -1
  67. package/dist-web/assets/{pieDiagram-4H26LBE5-DTkPv6S2.js → pieDiagram-4H26LBE5-y1b8Tbaa.js} +1 -1
  68. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-BS9LhTv0.js → quadrantDiagram-W4KKPZXB-BMoept3P.js} +1 -1
  69. package/dist-web/assets/{requirementDiagram-4Y6WPE33-BlSOLsQe.js → requirementDiagram-4Y6WPE33-BL-2jNsJ.js} +1 -1
  70. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-B1xl-B1U.js → sankeyDiagram-5OEKKPKP-dfD7Fow2.js} +1 -1
  71. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-BoiYrW_0.js → sequenceDiagram-3UESZ5HK-DEkvMHGP.js} +1 -1
  72. package/dist-web/assets/{stateDiagram-AJRCARHV-BxvfZYDK.js → stateDiagram-AJRCARHV-4t9PvMom.js} +1 -1
  73. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-5xE8WrG_.js +1 -0
  74. package/dist-web/assets/{timeline-definition-PNZ67QCA-CUEbrRkc.js → timeline-definition-PNZ67QCA-V1D7tV43.js} +1 -1
  75. package/dist-web/assets/{vennDiagram-CIIHVFJN-CgQiBucH.js → vennDiagram-CIIHVFJN-Tw5zLG0T.js} +1 -1
  76. package/dist-web/assets/{wardley-L42UT6IY-BsGW6T55.js → wardley-L42UT6IY-CrYt_lTh.js} +1 -1
  77. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-Cq3RMp6B.js → wardleyDiagram-YWT4CUSO-CDe_cIHY.js} +1 -1
  78. package/dist-web/assets/{xychartDiagram-2RQKCTM6-WaoV-V3f.js → xychartDiagram-2RQKCTM6-DNoNd0tl.js} +1 -1
  79. package/dist-web/index.html +1 -1
  80. package/package.json +1 -1
  81. package/templates/strikethroo/gitignore +30 -0
  82. package/dist-web/assets/channel-Dk0dEQIT.js +0 -1
  83. package/dist-web/assets/classDiagram-4FO5ZUOK-gqDJqcDV.js +0 -1
  84. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-gqDJqcDV.js +0 -1
  85. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-XGZY1tGg.js +0 -1
  86. package/templates/strikethroo/runtime/.gitignore +0 -2
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ /**
3
+ * Structural checks over the task dependency graph and the execution blueprint:
4
+ * dangling dependency references, dependency cycles, blueprint/task consistency
5
+ * in both directions, agreement among the three coexisting task-id notions, and
6
+ * task-id uniqueness within a plan.
7
+ *
8
+ * Scope is `plans/` only; `archive/` participates solely in plan-id uniqueness.
9
+ * Plan clarification 1 settled that: an archived plan is immutable history, so a
10
+ * content finding against it cannot be acted on and is therefore noise. Plan ids
11
+ * are the exception because continuous numbering across active and archived
12
+ * plans is exactly what stops an archived plan from colliding with a new one.
13
+ *
14
+ * Cycle detection exists nowhere else in this repository. The acyclicity rule is
15
+ * written down only as instructions to a language model
16
+ * (`src/skill-prompts/sections/validation-checklist.md`, the
17
+ * `POST_TASK_GENERATION_ALL` hook). Today a cycle is a display defect in the
18
+ * viewer plus a permanent, undiagnosed "phase not ready" stall from
19
+ * `collectTaskReadinessIssues` — which is why naming the participating task ids
20
+ * in the message is the point of the check, not a nicety.
21
+ *
22
+ * No blueprint parser is defined here. The repository already carries
23
+ * `parseBlueprintPhases` twice (`src/serve/derivation.ts` and
24
+ * `src/skill-scripts/shared/blueprint-parse.ts`), quietly diverging; a third
25
+ * copy is the specific failure mode this file must not add. The serve copy is
26
+ * reused because it is inside the CLI's `tsc` domain and already returns the
27
+ * `Phase` shape. Deduplicating the existing two is out of scope.
28
+ *
29
+ * Known limitation, deliberately not fixed here: that parser's `TASK_REF_RE`
30
+ * matches the first `Task NN` in *any* bulleted line, so ordinary prose in a
31
+ * blueprint bullet is indistinguishable from a phase assignment and can yield a
32
+ * phantom reference. Hardening it belongs to whoever deduplicates the two
33
+ * parsers. The `blueprint/reference-unresolved` message therefore spells the
34
+ * false-positive case out, so a human meets it as a known limitation instead of
35
+ * a mystery.
36
+ *
37
+ * `findStrikethrooRoot` is never imported: it terminates the process on a
38
+ * schema-version mismatch. The root arrives already resolved.
39
+ */
40
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ var desc = Object.getOwnPropertyDescriptor(m, k);
43
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
44
+ desc = { enumerable: true, get: function() { return m[k]; } };
45
+ }
46
+ Object.defineProperty(o, k2, desc);
47
+ }) : (function(o, m, k, k2) {
48
+ if (k2 === undefined) k2 = k;
49
+ o[k2] = m[k];
50
+ }));
51
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
52
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
53
+ }) : function(o, v) {
54
+ o["default"] = v;
55
+ });
56
+ var __importStar = (this && this.__importStar) || (function () {
57
+ var ownKeys = function(o) {
58
+ ownKeys = Object.getOwnPropertyNames || function (o) {
59
+ var ar = [];
60
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
61
+ return ar;
62
+ };
63
+ return ownKeys(o);
64
+ };
65
+ return function (mod) {
66
+ if (mod && mod.__esModule) return mod;
67
+ var result = {};
68
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
69
+ __setModuleDefault(result, mod);
70
+ return result;
71
+ };
72
+ })();
73
+ Object.defineProperty(exports, "__esModule", { value: true });
74
+ exports.graphChecks = graphChecks;
75
+ const fs = __importStar(require("fs"));
76
+ const path = __importStar(require("path"));
77
+ const markdown_1 = require("../serve/markdown");
78
+ const derivation_1 = require("../serve/derivation");
79
+ const plan_scan_1 = require("../skill-scripts/shared/plan-scan");
80
+ /** Leading `NN--` numeric prefix of a task filename — the id notion `findTaskFile` uses. */
81
+ const FILENAME_PREFIX_RE = /^(\d+)--/;
82
+ /** Ascending numeric sort, for stable id lists in messages. */
83
+ const byNumber = (a, b) => a - b;
84
+ /** Sorted directory names under `<root>/<area>`. Missing or unreadable dir -> []. */
85
+ const listPlanDirNames = (root, area) => {
86
+ try {
87
+ return fs
88
+ .readdirSync(path.join(root, area), { withFileTypes: true })
89
+ .filter(entry => entry.isDirectory())
90
+ .map(entry => entry.name)
91
+ .sort((a, b) => a.localeCompare(b));
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ };
97
+ /**
98
+ * Picks the plan document inside a plan directory: the first `plan-*.md` in
99
+ * sorted order, falling back to the first `*.md`. Sorting keeps the choice
100
+ * independent of `readdirSync` order.
101
+ */
102
+ const findPlanFile = (planDir) => {
103
+ let names;
104
+ try {
105
+ names = fs
106
+ .readdirSync(planDir, { withFileTypes: true })
107
+ .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
108
+ .map(entry => entry.name)
109
+ .sort((a, b) => a.localeCompare(b));
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ return names.find(name => name.startsWith('plan-')) ?? names[0];
115
+ };
116
+ /** Builds the per-plan context for every directory under `plans/`. Never throws. */
117
+ const collectPlans = (root) => listPlanDirNames(root, 'plans').map(name => {
118
+ const planDir = path.join(root, 'plans', name);
119
+ const planFile = findPlanFile(planDir);
120
+ let body = '';
121
+ if (planFile !== undefined) {
122
+ try {
123
+ body = (0, markdown_1.extractBody)(fs.readFileSync(path.join(planDir, planFile), 'utf8'));
124
+ }
125
+ catch {
126
+ body = '';
127
+ }
128
+ }
129
+ return {
130
+ name,
131
+ relDir: path.join('plans', name),
132
+ relPlanFile: planFile === undefined ? undefined : path.join('plans', name, planFile),
133
+ body,
134
+ tasks: (0, derivation_1.scanTasks)(planDir),
135
+ };
136
+ });
137
+ /** The `NN` prefix of a task filename, or undefined when the name has none. */
138
+ const filenameId = (file) => {
139
+ const match = file.match(FILENAME_PREFIX_RE);
140
+ if (!match || match[1] === undefined)
141
+ return undefined;
142
+ const parsed = parseInt(match[1], 10);
143
+ return Number.isNaN(parsed) ? undefined : parsed;
144
+ };
145
+ /**
146
+ * The id a task answers to. Frontmatter wins because it is the notion the viewer
147
+ * and the dependency graph use; the filename prefix is the fallback so a task
148
+ * with unparsable frontmatter still participates instead of vanishing.
149
+ */
150
+ const effectiveId = (task) => task.id ?? filenameId(task.file);
151
+ /** Every id one task can be addressed by, across notions; deduplicated. */
152
+ const notionIds = (task) => {
153
+ const ids = [];
154
+ if (typeof task.id === 'number')
155
+ ids.push(task.id);
156
+ const fromName = filenameId(task.file);
157
+ if (fromName !== undefined && !ids.includes(fromName))
158
+ ids.push(fromName);
159
+ return ids;
160
+ };
161
+ /**
162
+ * Every id by which a task in this plan can be addressed, across all notions.
163
+ *
164
+ * A reference resolves if *any* notion matches. That keeps a single id
165
+ * disagreement from being reported three times: `identity/task-id-mismatch`
166
+ * already names the root cause, so the dependency and blueprint checks stay
167
+ * quiet about the same file.
168
+ */
169
+ const resolvableIds = (tasks) => new Set(tasks.flatMap(notionIds));
170
+ /**
171
+ * Canonical key for a cycle, invariant under rotation: rotate so the smallest id
172
+ * leads. Two DFS entry points reaching the same cycle then produce one finding.
173
+ */
174
+ const cycleKey = (cycle) => {
175
+ let pivot = 0;
176
+ for (let i = 1; i < cycle.length; i++) {
177
+ if (cycle[i] < cycle[pivot])
178
+ pivot = i;
179
+ }
180
+ return [...cycle.slice(pivot), ...cycle.slice(0, pivot)].join('>');
181
+ };
182
+ /**
183
+ * Finds every distinct dependency cycle by depth-first search with a recursion
184
+ * stack (white/grey/black colouring). Graphs here hold a handful of tasks, so
185
+ * recursion depth is a non-issue. The stack slice is what supplies the member
186
+ * list the finding message must name.
187
+ *
188
+ * @param edges - task id -> ids it depends on, restricted to ids in this plan.
189
+ */
190
+ const findCycles = (edges) => {
191
+ const GREY = 1;
192
+ const BLACK = 2;
193
+ const colour = new Map();
194
+ const stack = [];
195
+ const cycles = [];
196
+ const seen = new Set();
197
+ const visit = (id) => {
198
+ colour.set(id, GREY);
199
+ stack.push(id);
200
+ for (const next of edges.get(id) ?? []) {
201
+ const state = colour.get(next);
202
+ if (state === undefined) {
203
+ visit(next);
204
+ }
205
+ else if (state === GREY) {
206
+ const start = stack.indexOf(next);
207
+ if (start !== -1) {
208
+ const cycle = stack.slice(start);
209
+ const key = cycleKey(cycle);
210
+ if (!seen.has(key)) {
211
+ seen.add(key);
212
+ cycles.push(cycle);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ stack.pop();
218
+ colour.set(id, BLACK);
219
+ };
220
+ for (const id of [...edges.keys()].sort(byNumber)) {
221
+ if (colour.get(id) === undefined)
222
+ visit(id);
223
+ }
224
+ return cycles;
225
+ };
226
+ /** Cycles and dangling dependency references for one plan. */
227
+ const dependencyFindings = (plan) => {
228
+ const findings = [];
229
+ const known = resolvableIds(plan.tasks);
230
+ for (const task of plan.tasks) {
231
+ const id = effectiveId(task);
232
+ const label = id === undefined ? task.file : `task ${id} (${task.file})`;
233
+ for (const dep of [...new Set(task.dependencies)].sort(byNumber)) {
234
+ if (known.has(dep))
235
+ continue;
236
+ findings.push({
237
+ check: 'graph/dangling-dependency',
238
+ path: path.join(plan.relDir, 'tasks', task.file),
239
+ message: `Plan "${plan.name}": ${label} declares a dependency on task ${dep}, but no task file in that plan carries that id under any notion (frontmatter id or filename prefix). Remove the dependency or add the missing task.`,
240
+ });
241
+ }
242
+ }
243
+ // The graph is built over frontmatter ids only: that is the notion the viewer
244
+ // and `inferPhases` traverse, so it is the notion in which a cycle actually
245
+ // stalls execution.
246
+ const graphIds = new Set(plan.tasks.map(task => task.id).filter((id) => typeof id === 'number'));
247
+ const edges = new Map();
248
+ for (const task of plan.tasks) {
249
+ if (typeof task.id !== 'number')
250
+ continue;
251
+ const outgoing = [...new Set(task.dependencies)]
252
+ .filter(dep => graphIds.has(dep))
253
+ .sort(byNumber);
254
+ edges.set(task.id, [...(edges.get(task.id) ?? []), ...outgoing]);
255
+ }
256
+ for (const cycle of findCycles(edges)) {
257
+ const members = [...new Set(cycle)].sort(byNumber);
258
+ const trail = [...cycle, cycle[0]].join(' -> ');
259
+ const detail = members.length === 1
260
+ ? `task ${members[0]} depends on itself`
261
+ : `tasks ${members.join(', ')} (${trail})`;
262
+ findings.push({
263
+ check: 'graph/dependency-cycle',
264
+ path: plan.relDir,
265
+ message: `Plan "${plan.name}" has a dependency cycle: ${detail}. No phase ordering satisfies it, so these tasks can never become ready. Break the cycle by dropping one of the dependencies.`,
266
+ });
267
+ }
268
+ return findings;
269
+ };
270
+ /** Blueprint <-> task consistency, in both directions, for one plan. */
271
+ const blueprintFindings = (plan) => {
272
+ const phases = (0, derivation_1.parseBlueprintPhases)(plan.body);
273
+ // No blueprint section (or one with no phase headings) is a legitimate state
274
+ // for a drafted plan, not a defect. Nothing to reconcile against.
275
+ if (!phases || phases.length === 0)
276
+ return [];
277
+ const findings = [];
278
+ const known = resolvableIds(plan.tasks);
279
+ const scheduled = new Set();
280
+ const planFileLabel = plan.relPlanFile ?? plan.relDir;
281
+ for (const phase of phases) {
282
+ const phaseLabel = phase.name === undefined ? `phase ${phase.index}` : `phase ${phase.index} ("${phase.name}")`;
283
+ for (const id of phase.taskIds) {
284
+ scheduled.add(id);
285
+ if (known.has(id))
286
+ continue;
287
+ const finding = {
288
+ check: 'blueprint/reference-unresolved',
289
+ message: `Plan "${plan.name}": the Execution Blueprint's ${phaseLabel} references task ${id}, but no file in ${path.join(plan.relDir, 'tasks')} carries that id. Either the task file is missing, or this is a false positive: the shared blueprint parser treats the first "Task NN" in any bulleted line as a phase assignment, so a bullet whose prose merely mentions "Task ${id}" reads as a reference — reword that bullet if so.`,
290
+ };
291
+ if (plan.relPlanFile !== undefined)
292
+ finding.path = plan.relPlanFile;
293
+ findings.push(finding);
294
+ }
295
+ }
296
+ for (const task of plan.tasks) {
297
+ const id = effectiveId(task);
298
+ if (id === undefined)
299
+ continue;
300
+ // Symmetric with the incoming direction above, which resolves through
301
+ // `resolvableIds`: a task counts as scheduled when *any* of its notions is
302
+ // named by a phase. Keying this sweep on `effectiveId` alone would report a
303
+ // file whose frontmatter id and filename prefix disagree as unscheduled
304
+ // even though a phase names it by filename — a second finding for a root
305
+ // cause `identity/task-id-mismatch` already reports, carrying remediation
306
+ // ("Add it to a phase") that is wrong for that cause.
307
+ if (notionIds(task).some(notion => scheduled.has(notion)))
308
+ continue;
309
+ findings.push({
310
+ check: 'blueprint/task-in-no-phase',
311
+ path: path.join(plan.relDir, 'tasks', task.file),
312
+ message: `Plan "${plan.name}": task ${id} (${task.file}) appears in no phase of the Execution Blueprint in ${planFileLabel}, so nothing will ever execute it. Add it to a phase or remove the file.`,
313
+ });
314
+ }
315
+ return findings;
316
+ };
317
+ /** Task-id agreement across notions, plus per-plan task-id uniqueness. */
318
+ const taskIdentityFindings = (plan) => {
319
+ const findings = [];
320
+ for (const task of plan.tasks) {
321
+ const fromName = filenameId(task.file);
322
+ if (typeof task.id !== 'number' || fromName === undefined)
323
+ continue;
324
+ if (task.id === fromName)
325
+ continue;
326
+ findings.push({
327
+ check: 'identity/task-id-mismatch',
328
+ path: path.join(plan.relDir, 'tasks', task.file),
329
+ message: `Plan "${plan.name}": ${task.file} declares frontmatter id ${task.id} but its filename prefix is ${fromName}. The viewer and the dependency graph resolve tasks by frontmatter id while the execution path (\`findTaskFile\`) resolves by filename prefix, so the two consumers would pick different files. Make them agree.`,
330
+ });
331
+ }
332
+ // Task ids are unique *within a plan* only: `config/STRIKETHROO.md` specifies
333
+ // auto-incremental ids that restart at 01 per plan, so a global check would
334
+ // flag every healthy multi-plan workspace.
335
+ const byId = new Map();
336
+ for (const task of plan.tasks) {
337
+ if (typeof task.id !== 'number')
338
+ continue;
339
+ byId.set(task.id, [...(byId.get(task.id) ?? []), task.file]);
340
+ }
341
+ for (const id of [...byId.keys()].sort(byNumber)) {
342
+ const files = [...(byId.get(id) ?? [])].sort((a, b) => a.localeCompare(b));
343
+ if (files.length < 2)
344
+ continue;
345
+ findings.push({
346
+ check: 'identity/duplicate-task-id',
347
+ path: plan.relDir,
348
+ message: `Plan "${plan.name}" has ${files.length} task files declaring frontmatter id ${id}: ${files.join(', ')}. Task ids must be unique within a plan; dependencies on task ${id} are ambiguous until this is fixed.`,
349
+ });
350
+ }
351
+ return findings;
352
+ };
353
+ /**
354
+ * Plan-id uniqueness across `plans/` and `archive/` together — the only check
355
+ * that reads `archive/`. Continuous numbering across both trees is what keeps a
356
+ * newly created plan from colliding with an archived one.
357
+ */
358
+ const planIdFindings = (root) => {
359
+ const byId = new Map();
360
+ for (const entry of (0, plan_scan_1.getAllPlans)(root)) {
361
+ byId.set(entry.id, [...(byId.get(entry.id) ?? []), path.relative(root, entry.file)]);
362
+ }
363
+ const findings = [];
364
+ for (const id of [...byId.keys()].sort(byNumber)) {
365
+ const files = [...(byId.get(id) ?? [])].sort((a, b) => a.localeCompare(b));
366
+ if (files.length < 2)
367
+ continue;
368
+ findings.push({
369
+ check: 'identity/duplicate-plan-id',
370
+ message: `Plan id ${id} is claimed by ${files.length} plans across plans/ and archive/: ${files.join(', ')}. Plan ids must be unique across both, since numbering continues when a plan is archived.`,
371
+ });
372
+ }
373
+ return findings;
374
+ };
375
+ /**
376
+ * Runs the graph and identity checks against an already-resolved absolute
377
+ * workspace root. Pure: reads only, never writes, never exits.
378
+ */
379
+ function graphChecks(root) {
380
+ const plans = collectPlans(root);
381
+ return [
382
+ ...plans.flatMap(dependencyFindings),
383
+ ...plans.flatMap(blueprintFindings),
384
+ ...plans.flatMap(taskIdentityFindings),
385
+ ...planIdFindings(root),
386
+ ];
387
+ }
388
+ //# sourceMappingURL=graph-checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-checks.js","sourceRoot":"","sources":["../../src/validation/graph-checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Metadata gate: workspace-shape checks derived from `<root>/.init-metadata.json`.
3
+ *
4
+ * Four findings come out of this file — an unreadable or unparsable metadata
5
+ * file, an absent `files` map, a `workspaceSchemaVersion` other than the current
6
+ * constant, and any path recorded in `files` that is no longer on disk.
7
+ *
8
+ * Hash drift is deliberately NOT reported. `src/conflict-detector.ts` documents
9
+ * that a hash mismatch is how the tool detects *user modification* — a
10
+ * first-class, protected state that the whole hash-tracking mechanism exists to
11
+ * preserve. Reporting it would fire on every customized or profiled workspace.
12
+ * Only deletions are reported.
13
+ *
14
+ * `isFileDeleted` from `src/conflict-detector.ts` is not reused: it evaluates
15
+ * `relativePath in metadata.files`, which throws when `files` is `undefined`.
16
+ * Any workspace initialized before the hash map was recorded is in that state,
17
+ * so the absent-map finding short-circuits the deletion scan below.
18
+ */
19
+ import { Finding } from './types';
20
+ /**
21
+ * Runs the metadata gate against an already-resolved absolute workspace root.
22
+ *
23
+ * Pure: reads only, never writes, never exits.
24
+ */
25
+ export declare function metadataGate(root: string): Finding[];
26
+ //# sourceMappingURL=metadata-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-gate.d.ts","sourceRoot":"","sources":["../../src/validation/metadata-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA0BlC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA6CpD"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * Metadata gate: workspace-shape checks derived from `<root>/.init-metadata.json`.
4
+ *
5
+ * Four findings come out of this file — an unreadable or unparsable metadata
6
+ * file, an absent `files` map, a `workspaceSchemaVersion` other than the current
7
+ * constant, and any path recorded in `files` that is no longer on disk.
8
+ *
9
+ * Hash drift is deliberately NOT reported. `src/conflict-detector.ts` documents
10
+ * that a hash mismatch is how the tool detects *user modification* — a
11
+ * first-class, protected state that the whole hash-tracking mechanism exists to
12
+ * preserve. Reporting it would fire on every customized or profiled workspace.
13
+ * Only deletions are reported.
14
+ *
15
+ * `isFileDeleted` from `src/conflict-detector.ts` is not reused: it evaluates
16
+ * `relativePath in metadata.files`, which throws when `files` is `undefined`.
17
+ * Any workspace initialized before the hash map was recorded is in that state,
18
+ * so the absent-map finding short-circuits the deletion scan below.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.metadataGate = metadataGate;
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const metadata_1 = require("../metadata");
58
+ const METADATA_FILENAME = '.init-metadata.json';
59
+ /**
60
+ * Reads and parses `<root>/.init-metadata.json`.
61
+ *
62
+ * Returns `null` when the file is missing, unreadable, unparsable, or not a
63
+ * JSON object. Never throws: an unreadable metadata file is a finding, not a
64
+ * crash.
65
+ */
66
+ const readMetadata = (root) => {
67
+ try {
68
+ const parsed = JSON.parse(fs.readFileSync(path.join(root, METADATA_FILENAME), 'utf-8'));
69
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
70
+ return null;
71
+ return parsed;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ };
77
+ /**
78
+ * Runs the metadata gate against an already-resolved absolute workspace root.
79
+ *
80
+ * Pure: reads only, never writes, never exits.
81
+ */
82
+ function metadataGate(root) {
83
+ const findings = [];
84
+ const metadata = readMetadata(root);
85
+ if (metadata === null) {
86
+ findings.push({
87
+ check: 'metadata/unreadable',
88
+ path: METADATA_FILENAME,
89
+ message: `${METADATA_FILENAME} is missing, unreadable, or not a JSON object. Run \`npx strikethroo init\` to initialize the workspace.`,
90
+ });
91
+ return findings;
92
+ }
93
+ const recordedVersion = metadata.workspaceSchemaVersion;
94
+ if (recordedVersion !== metadata_1.CURRENT_WORKSPACE_SCHEMA_VERSION) {
95
+ const shown = recordedVersion === undefined ? 'absent' : JSON.stringify(recordedVersion);
96
+ findings.push({
97
+ check: 'metadata/schema-version-skew',
98
+ path: METADATA_FILENAME,
99
+ message: `workspaceSchemaVersion is ${shown}, but this build expects ${metadata_1.CURRENT_WORKSPACE_SCHEMA_VERSION}. Re-run \`npx strikethroo init\` to bring the workspace to current shape.`,
100
+ });
101
+ }
102
+ const files = metadata.files;
103
+ if (typeof files !== 'object' || files === null || Array.isArray(files)) {
104
+ findings.push({
105
+ check: 'metadata/files-map-absent',
106
+ path: METADATA_FILENAME,
107
+ message: `${METADATA_FILENAME} has no \`files\` map, so tracked-file deletions cannot be detected. Re-run \`npx strikethroo init\` to record it.`,
108
+ });
109
+ // Short-circuit: without a map there is nothing to scan for deletions.
110
+ return findings;
111
+ }
112
+ for (const relativePath of Object.keys(files)) {
113
+ if (!fs.existsSync(path.join(root, relativePath))) {
114
+ findings.push({
115
+ check: 'metadata/file-deleted',
116
+ path: relativePath,
117
+ message: `${relativePath} is recorded in .init-metadata.json but no longer exists on disk. Restore it or re-run \`npx strikethroo init\`.`,
118
+ });
119
+ }
120
+ }
121
+ return findings;
122
+ }
123
+ //# sourceMappingURL=metadata-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-gate.js","sourceRoot":"","sources":["../../src/validation/metadata-gate.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCH,oCA6CC;AA/ED,uCAAyB;AACzB,2CAA6B;AAC7B,0CAA+D;AAG/D,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAKhD;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,CAAC,IAAY,EAAsB,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAChC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAC7D,CAAC;QACF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QACxF,OAAO,MAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,SAAgB,YAAY,CAAC,IAAY;IACvC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEpC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,qBAAqB;YAC5B,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,GAAG,iBAAiB,0GAA0G;SACxI,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,eAAe,GAAG,QAAQ,CAAC,sBAAsB,CAAC;IACxD,IAAI,eAAe,KAAK,2CAAgC,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QACzF,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,8BAA8B;YACrC,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,6BAA6B,KAAK,4BAA4B,2CAAgC,4EAA4E;SACpL,CAAC,CAAC;IACL,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,2BAA2B;YAClC,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,GAAG,iBAAiB,oHAAoH;SAClJ,CAAC,CAAC;QACH,uEAAuE;QACvE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,uBAAuB;gBAC9B,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,GAAG,YAAY,kHAAkH;aAC3I,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Strict frontmatter pass over `plans/<plan>/plan-<name>.md` and
3
+ * `plans/<plan>/tasks/<name>.md`.
4
+ *
5
+ * Deliberately independent of the viewer's frontmatter reader in
6
+ * `src/serve/markdown.ts`: that one is lenient by design and cannot distinguish
7
+ * a missing field from a malformed one, which is precisely the distinction a
8
+ * validator must report. It runs `parseInt` and leaves the field `undefined` on
9
+ * failure, so `id: abc` and a file with no `id:` line at all produce
10
+ * byte-identical results. The two parsers coexist with different tolerance
11
+ * contracts, and the viewer's is not to be "fixed" — leniency is correct for
12
+ * rendering.
13
+ *
14
+ * Enumeration is likewise hand-rolled rather than routed through the shared
15
+ * plan-scan helpers in `src/skill-scripts/shared/`, which drop any plan whose
16
+ * `id` will not parse and are therefore structurally incapable of reporting the
17
+ * plans most likely to be broken.
18
+ *
19
+ * Scope is exactly `plans/`. Archived plans are immutable history, so a finding
20
+ * against one is unfixable noise; the shipped task template carries literal
21
+ * placeholders (`status: "[STATUS]"`), so a workspace-wide Markdown sweep would
22
+ * make the workspace report its own templates as broken.
23
+ *
24
+ * Required plan *sections* are deliberately not checked: no machine-readable
25
+ * list of them exists, and the templates are user-editable and wholesale
26
+ * replaceable by a strikethroo profile.
27
+ */
28
+ import { Finding } from './types';
29
+ /**
30
+ * Runs the strict frontmatter pass against an already-resolved absolute
31
+ * workspace root. Pure: reads only, never writes, never exits.
32
+ *
33
+ * @param root - Absolute path to the `.ai/strikethroo` directory.
34
+ * @returns One finding per proven frontmatter defect, each carrying a
35
+ * workspace-relative `path`.
36
+ */
37
+ export declare function strictPass(root: string): Finding[];
38
+ //# sourceMappingURL=strict-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strict-pass.d.ts","sourceRoot":"","sources":["../../src/validation/strict-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA8UlC;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAgClD"}