@curdx/flow 7.1.5 → 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 +87 -0
- package/README.md +14 -0
- package/dist/analyze-FX2PCSL6.mjs +1956 -0
- package/dist/check-TJPGCG3Z.mjs +222 -0
- package/dist/index.mjs +112 -107
- 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
|
@@ -851,110 +851,61 @@ var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flo
|
|
|
851
851
|
function claudeMdPath() {
|
|
852
852
|
return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
|
|
853
853
|
}
|
|
854
|
-
function isZh() {
|
|
855
|
-
return getLang() === "zh";
|
|
856
|
-
}
|
|
857
854
|
function buildCombinationPatterns(ids) {
|
|
858
855
|
const has = (k) => ids.has(k);
|
|
859
856
|
const out = [
|
|
860
|
-
|
|
857
|
+
"Combine tools by capability. Keep slash commands, MCP tools, and plugin skills distinct.",
|
|
861
858
|
""
|
|
862
859
|
];
|
|
863
|
-
if (has("
|
|
864
|
-
out.push(
|
|
860
|
+
if (has("context7") || has("curdx-flow") || has("claude-mem")) {
|
|
861
|
+
out.push("- **Starting a new feature**");
|
|
865
862
|
let step = 1;
|
|
866
|
-
if (has("claude-mem")) {
|
|
867
|
-
out.push(
|
|
868
|
-
isZh() ? ` ${step++}. \u5148\u7528 \`/claude-mem:mem-search\` \u67E5\u5386\u53F2\uFF0C\u786E\u8BA4\u4E4B\u524D\u6709\u6CA1\u6709\u7C7B\u4F3C\u5B9E\u73B0\u3001\u8E29\u5751\u6216\u51B3\u7B56\u3002` : ` ${step++}. Start with \`/claude-mem:mem-search\` to check whether this work, decision, or pitfall already exists in memory.`
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
863
|
if (has("context7")) {
|
|
872
|
-
out.push(
|
|
873
|
-
isZh() ? ` ${step++}. \u6D89\u53CA\u5916\u90E8\u5E93\u3001SDK\u3001\u6846\u67B6\u6216 API \u65F6\uFF0C\u4F7F\u7528 Context7 MCP \u62C9\u5B98\u65B9\u6700\u65B0\u6587\u6863\u3002` : ` ${step++}. If external libraries, SDKs, frameworks, or APIs are involved, use the Context7 MCP to pull current official docs.`
|
|
874
|
-
);
|
|
864
|
+
out.push(` ${step++}. If external libraries, SDKs, frameworks, or APIs are involved, use the Context7 MCP to pull current official docs.`);
|
|
875
865
|
}
|
|
876
866
|
const planners = [];
|
|
877
|
-
if (has("claude-mem"))
|
|
878
|
-
|
|
879
|
-
isZh() ? "`/claude-mem:make-plan` \u4EA7\u51FA\u5206\u9636\u6BB5\u8BA1\u5212" : "`/claude-mem:make-plan` for a phased plan"
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
if (has("curdx-flow")) {
|
|
883
|
-
planners.push(
|
|
884
|
-
isZh() ? "`/curdx-flow:new` \u6216\u76F8\u5173 spec flow \u8D77\u5B8C\u6574\u89C4\u683C" : "`/curdx-flow:new` or the spec flow for a full specification"
|
|
885
|
-
);
|
|
886
|
-
}
|
|
867
|
+
if (has("claude-mem")) planners.push("`/claude-mem:make-plan` for a phased plan");
|
|
868
|
+
if (has("curdx-flow")) planners.push("`/curdx-flow:new` or the spec flow for a full specification");
|
|
887
869
|
if (planners.length > 0) {
|
|
888
|
-
out.push(
|
|
889
|
-
isZh() ? ` ${step++}. \u591A\u6B65\u9AA4\u3001\u9AD8\u4E0D\u786E\u5B9A\u6027\u3001\u8DE8\u6A21\u5757\u65F6\uFF0C\u518D\u8FDB\u5165 ${planners.join("\uFF0C\u6216 ")}\u3002` : ` ${step++}. Only move into ${planners.join(" or ")} when the work is multi-step, cross-cutting, or uncertain.`
|
|
890
|
-
);
|
|
870
|
+
out.push(` ${step++}. Only move into ${planners.join(" or ")} when the work is multi-step, cross-cutting, or uncertain.`);
|
|
891
871
|
}
|
|
892
|
-
out.push(
|
|
893
|
-
isZh() ? ` ${step++}. \u7B80\u5355\u3001\u8303\u56F4\u660E\u786E\u7684\u4E00\u6B21\u6027\u6539\u52A8\uFF0C\u76F4\u63A5\u505A\uFF0C\u4E0D\u8981\u5148\u628A\u6D41\u7A0B\u62C9\u6EE1\u3002` : ` ${step++}. For small, clear one-shot changes, implement directly instead of forcing the full workflow.`
|
|
894
|
-
);
|
|
872
|
+
out.push(` ${step++}. For small, clear one-shot changes, implement directly instead of forcing the full workflow.`);
|
|
895
873
|
out.push("");
|
|
896
874
|
}
|
|
897
875
|
const stuckLines = [];
|
|
898
876
|
let s = 1;
|
|
899
|
-
if (has("claude-mem")) {
|
|
900
|
-
stuckLines.push(
|
|
901
|
-
isZh() ? ` ${s++}. \u5148\u7528 \`/claude-mem:mem-search\` \u67E5\u8BB0\u5FC6\uFF0C\u786E\u8BA4\u662F\u4E0D\u662F\u4EE5\u524D\u89E3\u8FC7\u540C\u7C7B bug\u3002` : ` ${s++}. Check \`/claude-mem:mem-search\` first to see whether the same bug was solved before.`
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
877
|
if (has("chrome-devtools-mcp")) {
|
|
905
|
-
stuckLines.push(
|
|
906
|
-
isZh() ? ` ${s++}. \u6D4F\u89C8\u5668\u4FA7\u95EE\u9898\u4F7F\u7528 Chrome DevTools MCP\uFF1A\u770B network\u3001console\u3001performance\u3001DOM snapshot\u3002` : ` ${s++}. For browser-side issues, use the Chrome DevTools MCP for network, console, performance, and DOM snapshots.`
|
|
907
|
-
);
|
|
878
|
+
stuckLines.push(` ${s++}. For browser-side issues, use the Chrome DevTools MCP for network, console, performance, and DOM snapshots.`);
|
|
908
879
|
}
|
|
909
880
|
if (has("context7")) {
|
|
910
|
-
stuckLines.push(
|
|
911
|
-
isZh() ? ` ${s++}. \u5982\u679C\u6000\u7591\u662F\u5E93 / API \u884C\u4E3A\u53D8\u5316\uFF0C\u4F7F\u7528 Context7 MCP \u67E5\u5B98\u65B9\u6587\u6863\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u3002` : ` ${s++}. If the issue may come from library or API behavior, use the Context7 MCP instead of relying on memory.`
|
|
912
|
-
);
|
|
881
|
+
stuckLines.push(` ${s++}. If the issue may come from library or API behavior, use the Context7 MCP instead of relying on memory.`);
|
|
913
882
|
}
|
|
914
883
|
const stillStuck = [];
|
|
915
|
-
if (has("sequential-thinking"))
|
|
916
|
-
|
|
917
|
-
isZh() ? "\u4F7F\u7528 sequential-thinking MCP \u62C6\u5047\u8BBE" : "use the sequential-thinking MCP to break down hypotheses"
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
if (has("pua")) {
|
|
921
|
-
stillStuck.push(
|
|
922
|
-
isZh() ? "\u518D\u8FDB\u5165 `/pua:pua-loop` \u8FED\u4EE3" : "then enter `/pua:pua-loop` for structured retries"
|
|
923
|
-
);
|
|
924
|
-
}
|
|
884
|
+
if (has("sequential-thinking")) stillStuck.push("switch to the sequential-thinking MCP to break down hypotheses");
|
|
885
|
+
if (has("pua")) stillStuck.push("enter `/pua:pua-loop` for structured retries");
|
|
925
886
|
if (stillStuck.length > 0) {
|
|
926
|
-
stuckLines.push(
|
|
927
|
-
isZh() ? ` ${s++}. \u4E24\u8F6E\u4EE5\u4E0A\u4ECD\u5361\u4F4F\uFF0C\u518D ${stillStuck.join("\uFF0C\u6216 ")}\u3002` : ` ${s++}. If you are still stuck after multiple attempts, ${stillStuck.join(" or ")}.`
|
|
928
|
-
);
|
|
887
|
+
stuckLines.push(` ${s++}. If you are still stuck after multiple attempts, ${stillStuck.join(" or ")}.`);
|
|
929
888
|
}
|
|
930
889
|
if (stuckLines.length > 0) {
|
|
931
|
-
out.push(
|
|
890
|
+
out.push("- **Debugging and repeated failures**", ...stuckLines, "");
|
|
932
891
|
}
|
|
933
892
|
if (has("frontend-design") || has("chrome-devtools-mcp")) {
|
|
934
|
-
out.push(
|
|
893
|
+
out.push("- **UI and frontend work**");
|
|
935
894
|
if (has("frontend-design")) {
|
|
936
|
-
out.push(
|
|
937
|
-
isZh() ? " - \u4F18\u5148\u4F7F\u7528 `frontend-design` \u63D2\u4EF6\u7684 UI skills\uFF1B\u82E5\u672A\u81EA\u52A8\u89E6\u53D1\uFF0C\u518D\u663E\u5F0F\u8C03\u7528\u5BF9\u5E94 skill\u3002" : " - Prioritize the `frontend-design` plugin skills for UI work; if they do not trigger automatically, invoke the relevant skill explicitly."
|
|
938
|
-
);
|
|
895
|
+
out.push(" - Prioritize the `frontend-design` plugin skills for UI work; if they do not trigger automatically, invoke the relevant skill explicitly.");
|
|
939
896
|
}
|
|
940
897
|
if (has("chrome-devtools-mcp")) {
|
|
941
|
-
out.push(
|
|
942
|
-
isZh() ? " - \u6E32\u67D3\u5F02\u5E38\u3001\u4EA4\u4E92\u95EE\u9898\u6216\u89C6\u89C9\u56DE\u5F52\uFF0C\u4F7F\u7528 Chrome DevTools MCP \u9A8C\u8BC1\uFF0C\u4E0D\u8981\u53EA\u9760\u8089\u773C\u731C\u3002" : " - For rendering issues, interaction bugs, or visual regressions, verify with the Chrome DevTools MCP instead of relying on visual guesswork alone."
|
|
943
|
-
);
|
|
898
|
+
out.push(" - For rendering issues, interaction bugs, or visual regressions, verify with the Chrome DevTools MCP instead of relying on visual guesswork alone.");
|
|
944
899
|
}
|
|
945
900
|
out.push("");
|
|
946
901
|
}
|
|
947
902
|
if (has("pua") || has("curdx-flow")) {
|
|
948
|
-
out.push(
|
|
903
|
+
out.push("- **Large, cross-cutting, or multi-agent work**");
|
|
949
904
|
if (has("pua")) {
|
|
950
|
-
out.push(
|
|
951
|
-
isZh() ? " - \u9700\u8981\u5E76\u884C\u62C6\u89E3\u4E0E\u56E2\u961F\u534F\u4F5C\u65F6\uFF0C\u7528 `/pua:p9`\uFF1B\u66F4\u9AD8\u5C42\u6218\u7565\u89C4\u5212\u518D\u8003\u8651 `/pua:p10`\u3002" : " - Use `/pua:p9` for parallel task decomposition and team coordination; reserve `/pua:p10` for higher-level strategy work."
|
|
952
|
-
);
|
|
905
|
+
out.push(" - Use `/pua:p9` for parallel task decomposition and team coordination; reserve `/pua:p10` for higher-level strategy work.");
|
|
953
906
|
}
|
|
954
907
|
if (has("curdx-flow")) {
|
|
955
|
-
out.push(
|
|
956
|
-
isZh() ? " - \u4E00\u4E2A\u5927\u529F\u80FD\u9700\u8981\u62C6\u6210\u591A\u4E2A\u76F8\u4E92\u4F9D\u8D56\u7684\u89C4\u683C\u65F6\uFF0C\u4F7F\u7528 `/curdx-flow:triage`\u3002" : " - Use `/curdx-flow:triage` when one large feature needs to be split into multiple dependent specs."
|
|
957
|
-
);
|
|
908
|
+
out.push(" - Use `/curdx-flow:triage` when one large feature needs to be split into multiple dependent specs.");
|
|
958
909
|
}
|
|
959
910
|
}
|
|
960
911
|
while (out.length > 0 && out[out.length - 1] === "") out.pop();
|
|
@@ -963,46 +914,32 @@ function buildCombinationPatterns(ids) {
|
|
|
963
914
|
function buildSkipRules(ids) {
|
|
964
915
|
const has = (k) => ids.has(k);
|
|
965
916
|
const out = [];
|
|
966
|
-
out.push(
|
|
967
|
-
isZh() ? "- \u4E00\u884C\u6539\u52A8\u3001typo\u3001\u7EAF\u91CD\u547D\u540D\u53D8\u91CF\uFF1A\u4E0D\u8981\u5148 plan\uFF0C\u4E0D\u8981\u5148\u5F00 spec\uFF0C\u76F4\u63A5\u6539\u3002" : "- For one-line changes, typos, or pure renames, skip planning and spec flow. Just make the edit."
|
|
968
|
-
);
|
|
917
|
+
out.push("- For one-line changes, typos, or pure renames, skip planning and spec flow. Just make the edit.");
|
|
969
918
|
const skips = [];
|
|
970
|
-
if (has("pua")) skips.push("`/pua:pua`");
|
|
919
|
+
if (has("pua")) skips.push("`/pua:pua-loop`");
|
|
971
920
|
if (has("sequential-thinking")) skips.push("`sequential-thinking`");
|
|
972
921
|
if (skips.length > 0) {
|
|
973
|
-
out.push(
|
|
974
|
-
isZh() ? `- \u5DF2\u77E5\u786E\u5B9A\u7684 fix\uFF1A\u4E0D\u8981\u5148\u4E0A ${skips.join("\u3001")}\u3002` : `- For a known, deterministic fix, do not reach for ${skips.join(" or ")} first.`
|
|
975
|
-
);
|
|
922
|
+
out.push(`- For a known, deterministic fix, do not reach for ${skips.join(" or ")} first.`);
|
|
976
923
|
}
|
|
977
|
-
out.push(
|
|
978
|
-
isZh() ? "- \u7EAF\u6982\u5FF5\u89E3\u91CA\u9898\u53EF\u4EE5\u76F4\u63A5\u7B54\uFF1B\u5982\u679C\u662F\u5728\u95EE\u5F53\u524D\u4ED3\u5E93\u91CC\u7684\u4EE3\u7801\u542B\u4E49\uFF0C\u5148\u8BFB\u76F8\u5173\u6587\u4EF6\u518D\u89E3\u91CA\u3002" : "- Answer pure conceptual explanation questions directly. If the question is about code in this repository, read the relevant files first."
|
|
979
|
-
);
|
|
924
|
+
out.push("- Answer pure conceptual explanation questions directly. If the question is about code in this repository, read the relevant files first.");
|
|
980
925
|
if (has("curdx-flow")) {
|
|
981
|
-
out.push(
|
|
982
|
-
isZh() ? "- \u5355\u6587\u4EF6\u5C40\u90E8\u91CD\u6784\u6216\u5F88\u5C0F\u8303\u56F4\u7684\u6574\u7406\uFF1A\u901A\u5E38\u4E0D\u8981\u8FDB\u5165 curdx-flow spec \u6D41\u3002" : "- For a single-file refactor or a very local cleanup, usually do not enter the curdx-flow spec workflow."
|
|
983
|
-
);
|
|
926
|
+
out.push("- For a single-file refactor or a very local cleanup, usually do not enter the curdx-flow spec workflow.");
|
|
984
927
|
}
|
|
985
928
|
return out;
|
|
986
929
|
}
|
|
987
930
|
function buildDecisionTree(ids) {
|
|
988
931
|
const has = (k) => ids.has(k);
|
|
989
932
|
const out = [];
|
|
990
|
-
out.push(
|
|
991
|
-
out.push(
|
|
992
|
-
isZh() ? "2. \u591A\u6B65\u9AA4\u4F46\u8DEF\u5F84\u6E05\u6670\uFF1F\u2192 \u62C6\u6210\u7B80\u77ED\u4EFB\u52A1\u5217\u8868\u9010\u4E2A\u63A8\u8FDB\uFF0C\u4E0D\u8981\u9ED8\u8BA4\u8FDB\u5165\u5B8C\u6574 spec \u6D41\u3002" : "2. Is it multi-step but still clear? -> Break it into a short task list and execute without defaulting to the full spec flow."
|
|
993
|
-
);
|
|
933
|
+
out.push("1. Can it be finished in 1-2 steps? -> Do it directly.");
|
|
934
|
+
out.push("2. Is it multi-step but still clear? -> Break it into a short task list and execute without defaulting to the full spec flow.");
|
|
994
935
|
const planners = [];
|
|
995
936
|
if (has("curdx-flow")) planners.push("`/curdx-flow:new`");
|
|
996
937
|
if (has("claude-mem")) planners.push("`/claude-mem:make-plan`");
|
|
997
938
|
if (planners.length > 0) {
|
|
998
|
-
out.push(
|
|
999
|
-
isZh() ? `3. \u9700\u6C42\u6A21\u7CCA\u3001\u8DE8\u6A21\u5757\u3001\u9700\u8981\u5206\u9636\u6BB5\u4EA4\u4ED8\uFF1F\u2192 ${planners.join(" \u6216 ")}\u3002` : `3. Is the request ambiguous, cross-cutting, or phase-based? -> ${planners.join(" or ")}.`
|
|
1000
|
-
);
|
|
939
|
+
out.push(`3. Is the request ambiguous, cross-cutting, or phase-based? -> ${planners.join(" or ")}.`);
|
|
1001
940
|
}
|
|
1002
941
|
if (has("claude-mem")) {
|
|
1003
|
-
out.push(
|
|
1004
|
-
isZh() ? "4. \u8FD9\u7C7B\u6D3B\u4EE5\u524D\u53EF\u80FD\u505A\u8FC7\uFF1F\u2192 \u5148 `/claude-mem:mem-search`\u3002" : "4. Might this work have been done before? -> Start with `/claude-mem:mem-search`."
|
|
1005
|
-
);
|
|
942
|
+
out.push("4. Might this work have been done before? -> Start with `/claude-mem:mem-search`.");
|
|
1006
943
|
}
|
|
1007
944
|
return out;
|
|
1008
945
|
}
|
|
@@ -1016,23 +953,17 @@ function buildLanguagePolicy() {
|
|
|
1016
953
|
function renderBlock(items) {
|
|
1017
954
|
const installedIds = new Set(items.map((i) => i.id));
|
|
1018
955
|
const sections = [
|
|
1019
|
-
["## Language Policy
|
|
1020
|
-
[
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
],
|
|
1024
|
-
[isZh() ? "## Skip Rules\uFF08\u9632\u8FC7\u5EA6\u5DE5\u5177\u5316\uFF09" : "## Skip Rules", buildSkipRules(installedIds)],
|
|
1025
|
-
[isZh() ? "## Decision Tree\uFF08\u9047\u5230\u6A21\u7CCA\u8BF7\u6C42\u65F6\uFF09" : "## Decision Tree", buildDecisionTree(installedIds)]
|
|
956
|
+
["## Language Policy", buildLanguagePolicy()],
|
|
957
|
+
["## Tool Combination Patterns", buildCombinationPatterns(installedIds)],
|
|
958
|
+
["## Skip Rules", buildSkipRules(installedIds)],
|
|
959
|
+
["## Decision Tree", buildDecisionTree(installedIds)]
|
|
1026
960
|
];
|
|
1027
961
|
const lines = [BEGIN_MARKER];
|
|
1028
962
|
for (const [heading, body] of sections) {
|
|
1029
963
|
if (body.length === 0) continue;
|
|
1030
964
|
lines.push(heading, "", ...body, "");
|
|
1031
965
|
}
|
|
1032
|
-
lines.push(
|
|
1033
|
-
isZh() ? "\u8FD0\u884C `npx @curdx/flow` \u4EE5 install / update / uninstall\u3002" : "Run `npx @curdx/flow` to install / update / uninstall.",
|
|
1034
|
-
END_MARKER
|
|
1035
|
-
);
|
|
966
|
+
lines.push(END_MARKER);
|
|
1036
967
|
return lines.join("\n");
|
|
1037
968
|
}
|
|
1038
969
|
function withEol(s, eol) {
|
|
@@ -1695,17 +1626,49 @@ var analyzeCmd = defineCommand({
|
|
|
1695
1626
|
"include-prompts": {
|
|
1696
1627
|
type: "boolean",
|
|
1697
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)"
|
|
1698
1653
|
}
|
|
1699
1654
|
},
|
|
1700
1655
|
async run({ args }) {
|
|
1701
1656
|
const limitRaw = args.limit;
|
|
1702
1657
|
const limit = typeof limitRaw === "string" && limitRaw.length > 0 ? Number(limitRaw) : void 0;
|
|
1703
|
-
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");
|
|
1704
1661
|
await runAnalyze({
|
|
1705
1662
|
out: typeof args.out === "string" ? args.out : void 0,
|
|
1706
1663
|
json: Boolean(args.json),
|
|
1707
1664
|
limit: Number.isFinite(limit) ? limit : void 0,
|
|
1708
|
-
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
|
|
1709
1672
|
});
|
|
1710
1673
|
}
|
|
1711
1674
|
});
|
|
@@ -1839,6 +1802,17 @@ var updateCmd = defineCommand2({
|
|
|
1839
1802
|
p10.outro(t("app.outro"));
|
|
1840
1803
|
}
|
|
1841
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
|
+
});
|
|
1842
1816
|
var statusCmd = defineCommand2({
|
|
1843
1817
|
meta: { name: "status", description: "Show install status" },
|
|
1844
1818
|
args: {
|
|
@@ -1852,7 +1826,27 @@ var statusCmd = defineCommand2({
|
|
|
1852
1826
|
if (!args.json) p10.outro(t("app.outro"));
|
|
1853
1827
|
}
|
|
1854
1828
|
});
|
|
1855
|
-
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
|
+
`;
|
|
1856
1850
|
var root = defineCommand2({
|
|
1857
1851
|
meta: {
|
|
1858
1852
|
name: "@curdx/flow",
|
|
@@ -1865,7 +1859,8 @@ var root = defineCommand2({
|
|
|
1865
1859
|
uninstall: uninstallCmd,
|
|
1866
1860
|
update: updateCmd,
|
|
1867
1861
|
status: statusCmd,
|
|
1868
|
-
analyze: analyze_default
|
|
1862
|
+
analyze: analyze_default,
|
|
1863
|
+
check: checkCmd
|
|
1869
1864
|
}
|
|
1870
1865
|
// No root run() — citty 0.1.6 calls parent.run AFTER a matching subcommand,
|
|
1871
1866
|
// which would render the menu after a subcommand finishes. We dispatch the
|
|
@@ -1899,6 +1894,16 @@ async function runInteractive(argv2) {
|
|
|
1899
1894
|
var argv = process.argv.slice(2);
|
|
1900
1895
|
var first = firstNonFlag(argv);
|
|
1901
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
|
+
}
|
|
1902
1907
|
if (first === void 0 || first !== void 0 && !SUBCOMMANDS.has(first) && first !== "--help" && first !== "-h") {
|
|
1903
1908
|
if (first === void 0) {
|
|
1904
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"
|