archgen-skill 0.0.1

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.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // verify-plan.mjs — the VERIFIER gate as an executable check.
3
+ // Usage: node verify-plan.mjs <tasks.yaml> --plan <plans-dir>
4
+ // stdout: JSON {verdict: "APPROVE"|"ISSUES", issues:[...]}
5
+ // Exit: 0 APPROVE · 1 ISSUES. Structural checks only (the agent-side verifier
6
+ // adds judgment on top; this script is the deterministic floor).
7
+ import { readFileSync } from 'node:fs';
8
+ import { parseYaml } from './lib/yaml.mjs';
9
+ import { buildGraph, findCycle, computeWaves, findOwnershipConflict, GraphError } from './lib/graph.mjs';
10
+ import { checkPlanCoverage } from './lib/plan-coverage.mjs';
11
+
12
+ const [, , file, ...rest] = process.argv;
13
+ const planIdx = rest.indexOf('--plan');
14
+ if (!file || planIdx < 0) {
15
+ console.error('usage: verify-plan.mjs <tasks.yaml> --plan <plans-dir>');
16
+ process.exit(2);
17
+ }
18
+ const planDir = rest[planIdx + 1];
19
+ const issues = [];
20
+
21
+ let data;
22
+ try {
23
+ ({ data } = parseYaml(readFileSync(file, 'utf8'), { filename: file }));
24
+ } catch (e) {
25
+ console.log(JSON.stringify({ verdict: 'ISSUES', issues: [`unparseable tasks.yaml: ${e.message}`] }, null, 2));
26
+ process.exit(1);
27
+ }
28
+ const tasks = data?.tasks;
29
+ if (!Array.isArray(tasks)) { issues.push('missing tasks: sequence'); }
30
+
31
+ if (issues.length === 0) {
32
+ try {
33
+ const { byId, prerequisites } = buildGraph(tasks);
34
+ const cycle = findCycle(prerequisites);
35
+ if (cycle.length) issues.push(`dependency cycle: ${cycle.join(' -> ')}`);
36
+
37
+ // Every task must carry acceptance criteria — the verifier refuses work
38
+ // that cannot be objectively checked later.
39
+ for (const t of tasks) {
40
+ if (!Array.isArray(t.acceptance) || t.acceptance.length === 0) {
41
+ issues.push(`task '${t.id}' has no acceptance criteria`);
42
+ }
43
+ if (!Array.isArray(t.file_ownership) || t.file_ownership.length === 0) {
44
+ issues.push(`task '${t.id}' has empty file_ownership`);
45
+ }
46
+ }
47
+
48
+ if (!issues.length) {
49
+ const { waves } = computeWaves(byId, prerequisites);
50
+ for (const wave of waves) {
51
+ const conflict = findOwnershipConflict(wave);
52
+ if (conflict) issues.push(`same-wave ownership overlap: '${conflict.glob}' on '${conflict.a}' and '${conflict.b}'`);
53
+ }
54
+ }
55
+
56
+ issues.push(...checkPlanCoverage(planDir, tasks));
57
+ } catch (e) {
58
+ if (e instanceof GraphError) issues.push(e.message);
59
+ else throw e;
60
+ }
61
+ }
62
+
63
+ const verdict = issues.length ? 'ISSUES' : 'APPROVE';
64
+ console.log(JSON.stringify({ verdict, issues }, null, 2));
65
+ process.exit(verdict === 'APPROVE' ? 0 : 1);