@curdx/flow 7.1.6 → 7.1.7
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/CHANGELOG.md +73 -0
- package/README.md +14 -0
- package/dist/analyze-FX2PCSL6.mjs +1956 -0
- package/dist/check-TJPGCG3Z.mjs +222 -0
- package/dist/index.mjs +78 -4
- package/package.json +2 -2
- package/dist/analyze-4DE3HVCA.mjs +0 -794
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/hooks/lib/check-verification-blocks.ts
|
|
4
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var VERIFICATION_PHASES = [
|
|
7
|
+
"research",
|
|
8
|
+
"requirements",
|
|
9
|
+
"design",
|
|
10
|
+
"tasks",
|
|
11
|
+
"execution"
|
|
12
|
+
];
|
|
13
|
+
async function runVerificationCheck(opts = {}) {
|
|
14
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
15
|
+
const env = opts.env ?? process.env;
|
|
16
|
+
const specsDir = path.join(repoRoot, "specs");
|
|
17
|
+
const specDir = resolveActiveSpecDir(specsDir);
|
|
18
|
+
if (!specDir) {
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
code: 0,
|
|
22
|
+
skipped: true,
|
|
23
|
+
message: "check-verification-blocks: no active spec found, skipping.\n"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (env.CURDX_VERIFY_SKIP_BLOCKS === "1") {
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
code: 0,
|
|
30
|
+
skipped: true,
|
|
31
|
+
specDir,
|
|
32
|
+
message: "[check-verification-blocks] CURDX_VERIFY_SKIP_BLOCKS=1 \u2014 skipping gate.\n"
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const stateFile = path.join(specDir, ".curdx-state.json");
|
|
36
|
+
let state;
|
|
37
|
+
try {
|
|
38
|
+
state = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
code: 2,
|
|
44
|
+
specDir,
|
|
45
|
+
message: `\u2717 failed to read ${path.relative(repoRoot, stateFile)}: ${msg}
|
|
46
|
+
`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (typeof state !== "object" || state === null || !("verificationBlocks" in state)) {
|
|
50
|
+
const rel2 = path.relative(repoRoot, specDir);
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
code: 0,
|
|
54
|
+
skipped: true,
|
|
55
|
+
specDir,
|
|
56
|
+
message: `[check-verification-blocks] No verificationBlocks defined \u2014 skipping (treat as initial state)
|
|
57
|
+
Active spec: ${rel2}
|
|
58
|
+
`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const blocks = state.verificationBlocks;
|
|
62
|
+
const blocksObj = blocks && typeof blocks === "object" && !Array.isArray(blocks) ? blocks : null;
|
|
63
|
+
const presentPhases = blocksObj ? Object.keys(blocksObj).filter(
|
|
64
|
+
(p) => blocksObj[p] !== void 0 && blocksObj[p] !== null
|
|
65
|
+
) : [];
|
|
66
|
+
if (!blocksObj || presentPhases.length === 0) {
|
|
67
|
+
const rel2 = path.relative(repoRoot, specDir);
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
code: 2,
|
|
71
|
+
specDir,
|
|
72
|
+
message: `\u2717 No verificationBlocks found. Run the appropriate phase verification command.
|
|
73
|
+
Active spec: ${rel2}
|
|
74
|
+
Hint: each phase must record an entry in .curdx-state.json::verificationBlocks
|
|
75
|
+
(see plugins/curdx-flow/references/iron-law-verification.md).
|
|
76
|
+
`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const failures = [];
|
|
80
|
+
for (const phase of presentPhases) {
|
|
81
|
+
if (!VERIFICATION_PHASES.includes(phase)) {
|
|
82
|
+
failures.push({
|
|
83
|
+
phase,
|
|
84
|
+
reason: `unknown phase key "${phase}"`,
|
|
85
|
+
command: "(remove from state)"
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const raw = blocksObj[phase];
|
|
90
|
+
if (typeof raw !== "object" || raw === null) {
|
|
91
|
+
failures.push({
|
|
92
|
+
phase,
|
|
93
|
+
reason: "block is not an object",
|
|
94
|
+
command: "(rewrite block)"
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const block = raw;
|
|
99
|
+
const command = typeof block.command === "string" ? block.command : "(unknown command)";
|
|
100
|
+
const exitCode = block.exitCode;
|
|
101
|
+
const timestamp = block.timestamp;
|
|
102
|
+
const srcMtime = block.srcMtime;
|
|
103
|
+
const failedReason = block.failedReason;
|
|
104
|
+
if (exitCode !== 0) {
|
|
105
|
+
failures.push({
|
|
106
|
+
phase,
|
|
107
|
+
reason: typeof failedReason === "string" && failedReason.length > 0 ? `verification failed: ${failedReason} (exitCode=${String(exitCode)})` : `verification failed (exitCode=${String(exitCode)})`,
|
|
108
|
+
command
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const ts = typeof timestamp === "string" ? Date.parse(timestamp) : NaN;
|
|
113
|
+
if (Number.isNaN(ts)) {
|
|
114
|
+
failures.push({
|
|
115
|
+
phase,
|
|
116
|
+
reason: `invalid timestamp "${String(timestamp)}"`,
|
|
117
|
+
command
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (typeof srcMtime !== "number" || !Number.isFinite(srcMtime) || srcMtime < 0) {
|
|
122
|
+
failures.push({
|
|
123
|
+
phase,
|
|
124
|
+
reason: `invalid srcMtime ${String(srcMtime)}`,
|
|
125
|
+
command
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (ts < srcMtime) {
|
|
130
|
+
const srcIso = new Date(srcMtime).toISOString();
|
|
131
|
+
failures.push({
|
|
132
|
+
phase,
|
|
133
|
+
reason: `stale evidence: src changed at ${srcIso}, last verified at ${String(timestamp)}`,
|
|
134
|
+
command
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (failures.length > 0) {
|
|
139
|
+
const rel2 = path.relative(repoRoot, specDir);
|
|
140
|
+
let message = "\u2717 verificationBlocks gate failed:\n";
|
|
141
|
+
message += ` Active spec: ${rel2}
|
|
142
|
+
`;
|
|
143
|
+
for (const f of failures) {
|
|
144
|
+
message += ` - phase "${f.phase}": ${f.reason}
|
|
145
|
+
`;
|
|
146
|
+
message += ` Re-run: ${f.command}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
message += "\n";
|
|
150
|
+
message += "See plugins/curdx-flow/references/iron-law-verification.md for the full checklist.\n";
|
|
151
|
+
return { ok: false, code: 2, specDir, message };
|
|
152
|
+
}
|
|
153
|
+
const rel = path.relative(repoRoot, specDir);
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
code: 0,
|
|
157
|
+
specDir,
|
|
158
|
+
message: `All verificationBlocks valid.
|
|
159
|
+
Active spec: ${rel}
|
|
160
|
+
Phases verified: ${presentPhases.join(", ")}
|
|
161
|
+
`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function resolveActiveSpecDir(specsDir) {
|
|
165
|
+
const pointer = path.join(specsDir, ".current-spec");
|
|
166
|
+
if (existsSync(pointer)) {
|
|
167
|
+
try {
|
|
168
|
+
const name = readFileSync(pointer, "utf8").trim();
|
|
169
|
+
if (name) {
|
|
170
|
+
const dir = path.join(specsDir, name);
|
|
171
|
+
if (existsSync(path.join(dir, ".curdx-state.json"))) return dir;
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!existsSync(specsDir)) return null;
|
|
177
|
+
let entries;
|
|
178
|
+
try {
|
|
179
|
+
entries = readdirSync(specsDir, { withFileTypes: true });
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
let latest = null;
|
|
184
|
+
let latestMtime = 0;
|
|
185
|
+
for (const e of entries) {
|
|
186
|
+
if (!e.isDirectory()) continue;
|
|
187
|
+
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
188
|
+
const stateFile = path.join(specsDir, e.name, ".curdx-state.json");
|
|
189
|
+
if (!existsSync(stateFile)) continue;
|
|
190
|
+
try {
|
|
191
|
+
const st = statSync(stateFile);
|
|
192
|
+
if (st.mtimeMs > latestMtime) {
|
|
193
|
+
latestMtime = st.mtimeMs;
|
|
194
|
+
latest = path.join(specsDir, e.name);
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return latest;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/cli/commands/check.ts
|
|
203
|
+
async function runCheckCommand(args) {
|
|
204
|
+
void args;
|
|
205
|
+
const result = await runVerificationCheck();
|
|
206
|
+
if (result.ok) {
|
|
207
|
+
process.stdout.write("All verificationBlocks valid.\n");
|
|
208
|
+
if (result.specDir !== void 0) {
|
|
209
|
+
const rest = result.message.replace(
|
|
210
|
+
/^All verificationBlocks valid\.\n/,
|
|
211
|
+
""
|
|
212
|
+
);
|
|
213
|
+
if (rest.length > 0) process.stderr.write(rest);
|
|
214
|
+
}
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
process.stderr.write(result.message);
|
|
218
|
+
process.exit(2);
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
runCheckCommand
|
|
222
|
+
};
|
package/dist/index.mjs
CHANGED
|
@@ -1626,17 +1626,49 @@ var analyzeCmd = defineCommand({
|
|
|
1626
1626
|
"include-prompts": {
|
|
1627
1627
|
type: "boolean",
|
|
1628
1628
|
description: "Skip prompt redaction (D-9 white-list passthrough disabled \u2014 local debugging only)"
|
|
1629
|
+
},
|
|
1630
|
+
session: {
|
|
1631
|
+
type: "string",
|
|
1632
|
+
description: "Filter to single session UUID (matches <uuid>.jsonl in encoded project dir)"
|
|
1633
|
+
},
|
|
1634
|
+
"cost-summary": {
|
|
1635
|
+
type: "boolean",
|
|
1636
|
+
description: "Emit OB-3 cost analytics: totalCost.usd top-level + ## Cost Summary markdown (opt-in, default false)"
|
|
1637
|
+
},
|
|
1638
|
+
"by-spec": {
|
|
1639
|
+
type: "boolean",
|
|
1640
|
+
description: "OB-3 R1: aggregate cost by spec dimension (opt-in, default false; with --cost-summary alone all 3 dims emit)"
|
|
1641
|
+
},
|
|
1642
|
+
"by-phase": {
|
|
1643
|
+
type: "boolean",
|
|
1644
|
+
description: "OB-3 R2: aggregate cost by phase dimension (opt-in, default false; with --cost-summary alone all 3 dims emit)"
|
|
1645
|
+
},
|
|
1646
|
+
"by-task": {
|
|
1647
|
+
type: "boolean",
|
|
1648
|
+
description: "OB-3 R7: aggregate cost by task (correlationId) dimension, top-N truncated by --top (opt-in, default false)"
|
|
1649
|
+
},
|
|
1650
|
+
top: {
|
|
1651
|
+
type: "string",
|
|
1652
|
+
description: "OB-3 R7 top-N truncation for --by-task buckets (default: 10)"
|
|
1629
1653
|
}
|
|
1630
1654
|
},
|
|
1631
1655
|
async run({ args }) {
|
|
1632
1656
|
const limitRaw = args.limit;
|
|
1633
1657
|
const limit = typeof limitRaw === "string" && limitRaw.length > 0 ? Number(limitRaw) : void 0;
|
|
1634
|
-
const
|
|
1658
|
+
const topRaw = args.top;
|
|
1659
|
+
const top = typeof topRaw === "string" && topRaw.length > 0 ? Number(topRaw) : void 0;
|
|
1660
|
+
const { runAnalyze } = await import("./analyze-FX2PCSL6.mjs");
|
|
1635
1661
|
await runAnalyze({
|
|
1636
1662
|
out: typeof args.out === "string" ? args.out : void 0,
|
|
1637
1663
|
json: Boolean(args.json),
|
|
1638
1664
|
limit: Number.isFinite(limit) ? limit : void 0,
|
|
1639
|
-
includePrompts: Boolean(args["include-prompts"])
|
|
1665
|
+
includePrompts: Boolean(args["include-prompts"]),
|
|
1666
|
+
session: typeof args.session === "string" && args.session.length > 0 ? args.session : void 0,
|
|
1667
|
+
costSummary: Boolean(args["cost-summary"]),
|
|
1668
|
+
bySpec: Boolean(args["by-spec"]),
|
|
1669
|
+
byPhase: Boolean(args["by-phase"]),
|
|
1670
|
+
byTask: Boolean(args["by-task"]),
|
|
1671
|
+
top: Number.isFinite(top) ? top : void 0
|
|
1640
1672
|
});
|
|
1641
1673
|
}
|
|
1642
1674
|
});
|
|
@@ -1770,6 +1802,17 @@ var updateCmd = defineCommand2({
|
|
|
1770
1802
|
p10.outro(t("app.outro"));
|
|
1771
1803
|
}
|
|
1772
1804
|
});
|
|
1805
|
+
var checkCmd = defineCommand2({
|
|
1806
|
+
meta: {
|
|
1807
|
+
name: "check",
|
|
1808
|
+
description: "Verify active spec verificationBlocks (iron-law gate)"
|
|
1809
|
+
},
|
|
1810
|
+
args: {},
|
|
1811
|
+
async run() {
|
|
1812
|
+
const { runCheckCommand } = await import("./check-TJPGCG3Z.mjs");
|
|
1813
|
+
await runCheckCommand([]);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1773
1816
|
var statusCmd = defineCommand2({
|
|
1774
1817
|
meta: { name: "status", description: "Show install status" },
|
|
1775
1818
|
args: {
|
|
@@ -1783,7 +1826,27 @@ var statusCmd = defineCommand2({
|
|
|
1783
1826
|
if (!args.json) p10.outro(t("app.outro"));
|
|
1784
1827
|
}
|
|
1785
1828
|
});
|
|
1786
|
-
var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "status", "analyze"]);
|
|
1829
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "status", "analyze", "check"]);
|
|
1830
|
+
var CHECK_HELP = `USAGE \`@curdx/flow check\`
|
|
1831
|
+
|
|
1832
|
+
DESCRIPTION
|
|
1833
|
+
Verify the active spec's verificationBlocks (iron-law gate) \u2014 the same
|
|
1834
|
+
check enforced by the Stop hook and \`npm run verify\` release gate.
|
|
1835
|
+
|
|
1836
|
+
OPTIONS
|
|
1837
|
+
--help, -h Show this help message and exit.
|
|
1838
|
+
|
|
1839
|
+
EXIT CODES
|
|
1840
|
+
0 All verificationBlocks valid (or no spec is active \u2014 graceful no-op).
|
|
1841
|
+
2 At least one phase block is missing, stale, or failed.
|
|
1842
|
+
|
|
1843
|
+
ENV
|
|
1844
|
+
CURDX_VERIFY_SKIP_BLOCKS=1 Skip the gate (human escape hatch; never set
|
|
1845
|
+
in CI / release).
|
|
1846
|
+
|
|
1847
|
+
SEE ALSO
|
|
1848
|
+
plugins/curdx-flow/references/iron-law-verification.md
|
|
1849
|
+
`;
|
|
1787
1850
|
var root = defineCommand2({
|
|
1788
1851
|
meta: {
|
|
1789
1852
|
name: "@curdx/flow",
|
|
@@ -1796,7 +1859,8 @@ var root = defineCommand2({
|
|
|
1796
1859
|
uninstall: uninstallCmd,
|
|
1797
1860
|
update: updateCmd,
|
|
1798
1861
|
status: statusCmd,
|
|
1799
|
-
analyze: analyze_default
|
|
1862
|
+
analyze: analyze_default,
|
|
1863
|
+
check: checkCmd
|
|
1800
1864
|
}
|
|
1801
1865
|
// No root run() — citty 0.1.6 calls parent.run AFTER a matching subcommand,
|
|
1802
1866
|
// which would render the menu after a subcommand finishes. We dispatch the
|
|
@@ -1830,6 +1894,16 @@ async function runInteractive(argv2) {
|
|
|
1830
1894
|
var argv = process.argv.slice(2);
|
|
1831
1895
|
var first = firstNonFlag(argv);
|
|
1832
1896
|
assertFreshLocalBuild();
|
|
1897
|
+
if (process.argv[2] === "check") {
|
|
1898
|
+
const rest = process.argv.slice(3);
|
|
1899
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
1900
|
+
process.stdout.write(CHECK_HELP);
|
|
1901
|
+
process.exit(0);
|
|
1902
|
+
}
|
|
1903
|
+
const { runCheckCommand } = await import("./check-TJPGCG3Z.mjs");
|
|
1904
|
+
await runCheckCommand(rest);
|
|
1905
|
+
process.exit(0);
|
|
1906
|
+
}
|
|
1833
1907
|
if (first === void 0 || first !== void 0 && !SUBCOMMANDS.has(first) && first !== "--help" && first !== "-h") {
|
|
1834
1908
|
if (first === void 0) {
|
|
1835
1909
|
runInteractive(argv).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curdx/flow",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.7",
|
|
4
4
|
"description": "Interactive installer for Claude Code plugins and MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/index.mjs",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"test:analyze": "vitest run tests/analyze",
|
|
22
22
|
"start": "node ./dist/index.mjs",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"verify": "npm run typecheck && npm run check-versions && npm run check:hooks-fresh && npm run build && npm run check:bundle && npm run test:hooks && npm run test:analyze",
|
|
24
|
+
"verify": "npm run typecheck && npm run check-versions && npm run check:hooks-fresh && npm run build && npm run check:bundle && npm run test:hooks && npm run test:analyze && node scripts/check-verification-blocks.mjs",
|
|
25
25
|
"check-versions": "node scripts/check-versions.mjs",
|
|
26
26
|
"bump-version": "node scripts/bump-version.mjs",
|
|
27
27
|
"prepublishOnly": "node scripts/check-versions.mjs && npm run typecheck && npm run check:hooks-fresh && npm run build"
|