@peterxiaoyang/superspec 0.1.4 → 0.1.6
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/adapters/codex/agents/architect.toml +4 -148
- package/adapters/codex/agents/code-reviewer.toml +4 -166
- package/adapters/codex/agents/critic.toml +5 -106
- package/adapters/codex/agents/executor.toml +13 -0
- package/adapters/codex/agents/test-engineer.toml +4 -154
- package/adapters/codex/agents/test-runner.toml +13 -0
- package/adapters/codex/agents/verifier.toml +4 -110
- package/adapters/codex/install-map.json +20 -0
- package/dist/src/apply_worker_chain.d.ts +57 -0
- package/dist/src/apply_worker_chain.js +1188 -0
- package/dist/src/cli.js +13 -0
- package/dist/src/cli_args.d.ts +13 -1
- package/dist/src/cli_args.js +237 -12
- package/dist/src/core.d.ts +1 -0
- package/dist/src/core.js +1 -0
- package/dist/src/evidence.js +152 -0
- package/dist/src/gates.d.ts +2 -1
- package/dist/src/gates.js +275 -21
- package/dist/src/i18n.js +4 -3
- package/dist/src/install_engine.d.ts +17 -0
- package/dist/src/install_engine.js +125 -2
- package/dist/src/packet_measure.d.ts +43 -0
- package/dist/src/packet_measure.js +417 -0
- package/dist/src/packet_render.d.ts +4 -0
- package/dist/src/packet_render.js +1623 -0
- package/dist/src/packet_schema.d.ts +56 -0
- package/dist/src/packet_schema.js +1 -0
- package/dist/src/project_init.js +7 -49
- package/dist/src/tasks.d.ts +10 -0
- package/dist/src/tasks.js +86 -0
- package/dist/src/util.d.ts +11 -3
- package/dist/src/util.js +27 -6
- package/package.json +2 -2
- package/schemas/install-manifest.schema.json +17 -0
- package/templates/workflow/prompts/architect.md +16 -109
- package/templates/workflow/prompts/code-reviewer.md +20 -134
- package/templates/workflow/prompts/critic.md +18 -75
- package/templates/workflow/prompts/executor.md +32 -0
- package/templates/workflow/prompts/test-engineer.md +16 -126
- package/templates/workflow/prompts/test-runner.md +33 -0
- package/templates/workflow/prompts/verifier.md +20 -77
- package/templates/workflow/skills/superspec-apply/SKILL.md +102 -78
- package/templates/workflow/skills/superspec-archive/SKILL.md +41 -37
- package/templates/workflow/skills/superspec-explore/SKILL.md +63 -77
- package/templates/workflow/skills/superspec-propose/SKILL.md +64 -85
- package/templates/workflow/skills/superspec-review/SKILL.md +76 -233
package/dist/src/cli.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { block, dispatch, GuardError, printDecision, reason } from "./core.js";
|
|
2
2
|
import { emitArgparsePreamble, parse_argv } from "./cli_args.js";
|
|
3
|
+
import { dispatch_packet, is_packet_command } from "./packet_render.js";
|
|
4
|
+
import { printPacket, printPacketError } from "./util.js";
|
|
3
5
|
export function main(argv = process.argv.slice(2)) {
|
|
4
6
|
let args = null;
|
|
7
|
+
const rawCommand = argv[0] ?? "";
|
|
5
8
|
try {
|
|
6
9
|
const argparseExit = emitArgparsePreamble(argv);
|
|
7
10
|
if (argparseExit !== null)
|
|
8
11
|
return argparseExit;
|
|
9
12
|
args = parse_argv(argv);
|
|
13
|
+
if (is_packet_command(args.command)) {
|
|
14
|
+
const result = dispatch_packet(args);
|
|
15
|
+
printPacket(result.payload, { format: result.output_format });
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
10
18
|
const [decision] = dispatch(args);
|
|
11
19
|
printDecision(decision, { command: args.command, format: args.format });
|
|
12
20
|
return decision.allowed ? 0 : 1;
|
|
13
21
|
}
|
|
14
22
|
catch (err) {
|
|
23
|
+
if ((args && is_packet_command(args.command)) || is_packet_command(rawCommand)) {
|
|
24
|
+
const errCode = err instanceof GuardError ? "guard_error" : "guard_internal_error";
|
|
25
|
+
printPacketError(errCode, `${err.message}`);
|
|
26
|
+
return 2;
|
|
27
|
+
}
|
|
15
28
|
const change = args?.change ?? "?";
|
|
16
29
|
const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
|
|
17
30
|
printDecision(block(change, "guard_error", [errReason]), { command: args?.command, format: args?.format });
|
package/dist/src/cli_args.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import { type DecisionOutputFormat } from "./util.ts";
|
|
1
|
+
import { type DecisionOutputFormat, type PacketOutputFormat } from "./util.ts";
|
|
2
2
|
export type ParsedArgs = {
|
|
3
3
|
command: string;
|
|
4
4
|
change: string;
|
|
5
5
|
format?: DecisionOutputFormat;
|
|
6
|
+
packet_format?: PacketOutputFormat;
|
|
6
7
|
artifact?: string;
|
|
7
8
|
gate?: string;
|
|
8
9
|
task_id?: string;
|
|
10
|
+
test_id?: string;
|
|
11
|
+
phase?: "red" | "characterization" | "green";
|
|
12
|
+
role?: string;
|
|
13
|
+
evidence_kind?: string;
|
|
14
|
+
round?: number;
|
|
15
|
+
executor_report_refs?: string[];
|
|
16
|
+
task_code_review_report_refs?: string[];
|
|
17
|
+
apply_worker_chain_refs?: string[];
|
|
18
|
+
green_test_run_evidence_refs?: string[];
|
|
19
|
+
red_test_run_evidence_refs?: string[];
|
|
20
|
+
characterization_test_run_evidence_refs?: string[];
|
|
9
21
|
create?: boolean;
|
|
10
22
|
force_unlock?: boolean;
|
|
11
23
|
rebuild_corrupt?: boolean;
|
package/dist/src/cli_args.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { command_zh } from "./i18n.js";
|
|
2
|
-
import { GuardError, parseDecisionOutputFormat } from "./util.js";
|
|
2
|
+
import { GuardError, parseDecisionOutputFormat, parsePacketOutputFormat } from "./util.js";
|
|
3
|
+
import { normalize_gate } from "./openspec.js";
|
|
3
4
|
const SIMPLE_COMMANDS = [
|
|
4
5
|
"status",
|
|
5
6
|
"recompute",
|
|
@@ -19,9 +20,25 @@ const COMMANDS = [
|
|
|
19
20
|
"check-task-reopen",
|
|
20
21
|
"check-task-edit",
|
|
21
22
|
"check-task-complete",
|
|
23
|
+
"workflow-packet",
|
|
24
|
+
"review-packet",
|
|
25
|
+
"apply-test-packet",
|
|
26
|
+
"apply-executor-packet",
|
|
27
|
+
"apply-code-review-packet",
|
|
28
|
+
"apply-verify-packet",
|
|
29
|
+
"ledger-render",
|
|
22
30
|
];
|
|
23
31
|
const COMMAND_LIST = COMMANDS.join(",");
|
|
24
32
|
const COMMAND_CHOICES = COMMANDS.map((item) => `'${item}'`).join(", ");
|
|
33
|
+
function isPacketCommand(command) {
|
|
34
|
+
return command === "workflow-packet"
|
|
35
|
+
|| command === "review-packet"
|
|
36
|
+
|| command === "apply-test-packet"
|
|
37
|
+
|| command === "apply-executor-packet"
|
|
38
|
+
|| command === "apply-code-review-packet"
|
|
39
|
+
|| command === "apply-verify-packet"
|
|
40
|
+
|| command === "ledger-render";
|
|
41
|
+
}
|
|
25
42
|
function requiredValueFlags(command) {
|
|
26
43
|
const flags = ["--change"];
|
|
27
44
|
if (command === "check-artifact")
|
|
@@ -30,14 +47,66 @@ function requiredValueFlags(command) {
|
|
|
30
47
|
flags.push("--gate");
|
|
31
48
|
if (command === "check-task-reopen" || command === "check-task-edit" || command === "check-task-complete")
|
|
32
49
|
flags.push("--task-id");
|
|
50
|
+
if (command === "workflow-packet")
|
|
51
|
+
flags.push("--gate");
|
|
52
|
+
if (command === "review-packet")
|
|
53
|
+
flags.push("--gate", "--role", "--round");
|
|
54
|
+
if (command === "apply-test-packet")
|
|
55
|
+
flags.push("--task-id", "--test-id", "--phase");
|
|
56
|
+
if (command === "apply-executor-packet")
|
|
57
|
+
flags.push("--task-id");
|
|
58
|
+
if (command === "apply-code-review-packet")
|
|
59
|
+
flags.push("--task-id", "--executor-report-ref");
|
|
60
|
+
if (command === "apply-verify-packet")
|
|
61
|
+
flags.push("--task-id", "--executor-report-ref", "--task-code-review-report-ref", "--green-test-run-evidence-ref");
|
|
62
|
+
if (command === "ledger-render")
|
|
63
|
+
flags.push("--gate");
|
|
33
64
|
return flags;
|
|
34
65
|
}
|
|
35
66
|
function requiredBooleanFlags(command) {
|
|
36
67
|
return command === "init" ? ["--create"] : [];
|
|
37
68
|
}
|
|
38
69
|
function optionalBooleanFlags(command) {
|
|
70
|
+
if (isPacketCommand(command))
|
|
71
|
+
return [];
|
|
39
72
|
return ["--user-facing", ...(command === "recompute" ? ["--force-unlock", "--rebuild-corrupt"] : [])];
|
|
40
73
|
}
|
|
74
|
+
function optionalValueFlags(command) {
|
|
75
|
+
if (command === "workflow-packet")
|
|
76
|
+
return ["--task-id"];
|
|
77
|
+
if (command === "review-packet")
|
|
78
|
+
return ["--kind"];
|
|
79
|
+
if (command === "apply-test-packet")
|
|
80
|
+
return ["--task-code-review-report-ref"];
|
|
81
|
+
if (command === "apply-executor-packet")
|
|
82
|
+
return ["--apply-worker-chain-ref"];
|
|
83
|
+
if (command === "apply-verify-packet")
|
|
84
|
+
return ["--red-test-run-evidence-ref", "--characterization-test-run-evidence-ref"];
|
|
85
|
+
if (command === "ledger-render")
|
|
86
|
+
return ["--round"];
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
function formatUsage(command) {
|
|
90
|
+
if (command === "workflow-packet")
|
|
91
|
+
return "--format {agent}";
|
|
92
|
+
if (command === "review-packet"
|
|
93
|
+
|| command === "apply-test-packet"
|
|
94
|
+
|| command === "apply-executor-packet"
|
|
95
|
+
|| command === "apply-code-review-packet"
|
|
96
|
+
|| command === "apply-verify-packet")
|
|
97
|
+
return "--format {agent,prompt}";
|
|
98
|
+
if (command === "ledger-render")
|
|
99
|
+
return null;
|
|
100
|
+
return "[--format {json,agent,user}]";
|
|
101
|
+
}
|
|
102
|
+
function requiresFormat(command) {
|
|
103
|
+
return command === "workflow-packet"
|
|
104
|
+
|| command === "review-packet"
|
|
105
|
+
|| command === "apply-test-packet"
|
|
106
|
+
|| command === "apply-executor-packet"
|
|
107
|
+
|| command === "apply-code-review-packet"
|
|
108
|
+
|| command === "apply-verify-packet";
|
|
109
|
+
}
|
|
41
110
|
function rootUsage() {
|
|
42
111
|
return `usage: superspec_guard [-h]\n {${COMMAND_LIST}}\n ...\n`;
|
|
43
112
|
}
|
|
@@ -52,7 +121,8 @@ function commandUsage(command) {
|
|
|
52
121
|
const usageFlags = [
|
|
53
122
|
"[-h]",
|
|
54
123
|
...requiredValueFlags(command).map((flag) => `${flag} ${flag.slice(2).replace(/-/g, "_").toUpperCase()}`),
|
|
55
|
-
|
|
124
|
+
...(formatUsage(command) ? [formatUsage(command)] : []),
|
|
125
|
+
...optionalValueFlags(command).map((flag) => `[${flag} ${flag.slice(2).replace(/-/g, "_").toUpperCase()}]`),
|
|
56
126
|
...requiredBooleanFlags(command),
|
|
57
127
|
...optionalBooleanFlags(command),
|
|
58
128
|
];
|
|
@@ -68,7 +138,13 @@ function commandHelp(command) {
|
|
|
68
138
|
for (const flag of requiredBooleanFlags(command)) {
|
|
69
139
|
lines.push(` ${flag}\n`);
|
|
70
140
|
}
|
|
71
|
-
|
|
141
|
+
const formatToken = formatUsage(command);
|
|
142
|
+
if (formatToken)
|
|
143
|
+
lines.push(` ${formatToken}\n`);
|
|
144
|
+
for (const flag of optionalValueFlags(command)) {
|
|
145
|
+
const metavariable = flag.slice(2).replace(/-/g, "_").toUpperCase();
|
|
146
|
+
lines.push(` ${flag} ${metavariable}\n`);
|
|
147
|
+
}
|
|
72
148
|
for (const flag of optionalBooleanFlags(command)) {
|
|
73
149
|
lines.push(` ${flag}\n`);
|
|
74
150
|
}
|
|
@@ -78,7 +154,10 @@ function hasFlag(argv, flag) {
|
|
|
78
154
|
return argv.includes(flag);
|
|
79
155
|
}
|
|
80
156
|
function missingRequiredFlags(command, args) {
|
|
81
|
-
|
|
157
|
+
const missing = [...requiredValueFlags(command), ...requiredBooleanFlags(command)].filter((flag) => !hasFlag(args, flag));
|
|
158
|
+
if (requiresFormat(command) && !hasFlag(args, "--format"))
|
|
159
|
+
missing.push("--format");
|
|
160
|
+
return missing;
|
|
82
161
|
}
|
|
83
162
|
export function emitArgparsePreamble(argv) {
|
|
84
163
|
if (argv.length === 0) {
|
|
@@ -99,10 +178,12 @@ export function emitArgparsePreamble(argv) {
|
|
|
99
178
|
process.stdout.write(commandHelp(command));
|
|
100
179
|
return 0;
|
|
101
180
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
181
|
+
if (!isPacketCommand(command)) {
|
|
182
|
+
const missing = missingRequiredFlags(command, args);
|
|
183
|
+
if (missing.length > 0) {
|
|
184
|
+
process.stderr.write(`${commandUsage(command)}superspec_guard ${command}:错误:缺少必填参数:${missing.join(", ")}\n`);
|
|
185
|
+
return 2;
|
|
186
|
+
}
|
|
106
187
|
}
|
|
107
188
|
return null;
|
|
108
189
|
}
|
|
@@ -125,15 +206,31 @@ export function parse_argv(argv) {
|
|
|
125
206
|
};
|
|
126
207
|
const getValue = (flag) => getValues(flag)[0];
|
|
127
208
|
const formatValues = getValues("--format");
|
|
128
|
-
|
|
129
|
-
|
|
209
|
+
const isPacketOutputCommand = command === "workflow-packet"
|
|
210
|
+
|| command === "review-packet"
|
|
211
|
+
|| command === "apply-test-packet"
|
|
212
|
+
|| command === "apply-executor-packet"
|
|
213
|
+
|| command === "apply-code-review-packet"
|
|
214
|
+
|| command === "apply-verify-packet";
|
|
215
|
+
const isPacketCommand = isPacketOutputCommand || command === "ledger-render";
|
|
216
|
+
const selectedPacketFormat = formatValues.length > 0 ? formatValues[formatValues.length - 1] : undefined;
|
|
217
|
+
if (isPacketOutputCommand) {
|
|
218
|
+
if (!selectedPacketFormat)
|
|
219
|
+
throw new GuardError("缺少必填参数 --format");
|
|
220
|
+
for (const value of formatValues)
|
|
221
|
+
parsePacketOutputFormat(value, { allowPrompt: command !== "workflow-packet" });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
for (const value of formatValues)
|
|
225
|
+
parseDecisionOutputFormat(value);
|
|
226
|
+
}
|
|
130
227
|
const selectedFormat = hasFlag(args, "--user-facing")
|
|
131
228
|
? "user"
|
|
132
229
|
: (formatValues.length > 0 ? formatValues[formatValues.length - 1] : "json");
|
|
133
|
-
const format = parseDecisionOutputFormat(selectedFormat);
|
|
230
|
+
const format = isPacketCommand ? undefined : parseDecisionOutputFormat(selectedFormat);
|
|
134
231
|
const change = getValue("--change");
|
|
135
232
|
if (!change)
|
|
136
|
-
throw new Error("缺少必填参数 --change");
|
|
233
|
+
throw new (isPacketCommand ? GuardError : Error)("缺少必填参数 --change");
|
|
137
234
|
if (command === "init") {
|
|
138
235
|
if (!hasFlag(args, "--create"))
|
|
139
236
|
throw new Error("缺少必填参数 --create");
|
|
@@ -151,6 +248,134 @@ export function parse_argv(argv) {
|
|
|
151
248
|
throw new Error("缺少必填参数 --gate");
|
|
152
249
|
return { command, change, format, gate };
|
|
153
250
|
}
|
|
251
|
+
if (command === "workflow-packet") {
|
|
252
|
+
const gate = getValue("--gate");
|
|
253
|
+
if (!gate)
|
|
254
|
+
throw new GuardError("缺少必填参数 --gate");
|
|
255
|
+
const normalizedGate = normalize_gate(gate);
|
|
256
|
+
const taskId = getValue("--task-id");
|
|
257
|
+
if ((normalizedGate === "task_edit" || normalizedGate === "task_complete" || normalizedGate === "task_reopen") && !taskId) {
|
|
258
|
+
throw new GuardError("workflow-packet 缺少必填参数 --task-id");
|
|
259
|
+
}
|
|
260
|
+
return { command, change, gate, task_id: taskId, packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: false }) };
|
|
261
|
+
}
|
|
262
|
+
if (command === "review-packet") {
|
|
263
|
+
const gate = getValue("--gate");
|
|
264
|
+
const role = getValue("--role");
|
|
265
|
+
const roundValue = getValue("--round");
|
|
266
|
+
const evidenceKind = getValue("--kind");
|
|
267
|
+
if (!gate)
|
|
268
|
+
throw new GuardError("缺少必填参数 --gate");
|
|
269
|
+
if (!role)
|
|
270
|
+
throw new GuardError("缺少必填参数 --role");
|
|
271
|
+
if (!roundValue)
|
|
272
|
+
throw new GuardError("缺少必填参数 --round");
|
|
273
|
+
if (evidenceKind !== undefined && evidenceKind !== "source_guidance" && evidenceKind !== "verification_review") {
|
|
274
|
+
throw new GuardError("--kind 只允许 source_guidance 或 verification_review");
|
|
275
|
+
}
|
|
276
|
+
const round = Number.parseInt(roundValue, 10);
|
|
277
|
+
if (!Number.isInteger(round) || round < 1)
|
|
278
|
+
throw new GuardError("--round 必须是大于等于 1 的整数");
|
|
279
|
+
return {
|
|
280
|
+
command,
|
|
281
|
+
change,
|
|
282
|
+
gate,
|
|
283
|
+
role,
|
|
284
|
+
evidence_kind: evidenceKind,
|
|
285
|
+
round,
|
|
286
|
+
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (command === "apply-test-packet") {
|
|
290
|
+
const taskId = getValue("--task-id");
|
|
291
|
+
const testId = getValue("--test-id");
|
|
292
|
+
const phase = getValue("--phase");
|
|
293
|
+
if (!taskId)
|
|
294
|
+
throw new GuardError("apply-test-packet 缺少必填参数 --task-id");
|
|
295
|
+
if (!testId)
|
|
296
|
+
throw new GuardError("apply-test-packet 缺少必填参数 --test-id");
|
|
297
|
+
if (phase !== "red" && phase !== "characterization" && phase !== "green") {
|
|
298
|
+
throw new GuardError("--phase 只允许 red、characterization 或 green");
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
command,
|
|
302
|
+
change,
|
|
303
|
+
task_id: taskId,
|
|
304
|
+
test_id: testId,
|
|
305
|
+
phase,
|
|
306
|
+
task_code_review_report_refs: getValues("--task-code-review-report-ref"),
|
|
307
|
+
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (command === "apply-executor-packet") {
|
|
311
|
+
const taskId = getValue("--task-id");
|
|
312
|
+
if (!taskId)
|
|
313
|
+
throw new GuardError("apply-executor-packet 缺少必填参数 --task-id");
|
|
314
|
+
return {
|
|
315
|
+
command,
|
|
316
|
+
change,
|
|
317
|
+
task_id: taskId,
|
|
318
|
+
apply_worker_chain_refs: getValues("--apply-worker-chain-ref"),
|
|
319
|
+
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (command === "apply-code-review-packet") {
|
|
323
|
+
const taskId = getValue("--task-id");
|
|
324
|
+
const executorRefs = getValues("--executor-report-ref");
|
|
325
|
+
if (!taskId)
|
|
326
|
+
throw new GuardError("apply-code-review-packet 缺少必填参数 --task-id");
|
|
327
|
+
if (executorRefs.length === 0)
|
|
328
|
+
throw new GuardError("apply-code-review-packet 缺少必填参数 --executor-report-ref");
|
|
329
|
+
return {
|
|
330
|
+
command,
|
|
331
|
+
change,
|
|
332
|
+
task_id: taskId,
|
|
333
|
+
executor_report_refs: executorRefs,
|
|
334
|
+
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (command === "apply-verify-packet") {
|
|
338
|
+
const taskId = getValue("--task-id");
|
|
339
|
+
const executorRefs = getValues("--executor-report-ref");
|
|
340
|
+
const codeReviewRefs = getValues("--task-code-review-report-ref");
|
|
341
|
+
const greenRefs = getValues("--green-test-run-evidence-ref");
|
|
342
|
+
const redRefs = getValues("--red-test-run-evidence-ref");
|
|
343
|
+
const characterizationRefs = getValues("--characterization-test-run-evidence-ref");
|
|
344
|
+
if (!taskId)
|
|
345
|
+
throw new GuardError("apply-verify-packet 缺少必填参数 --task-id");
|
|
346
|
+
if (executorRefs.length === 0)
|
|
347
|
+
throw new GuardError("apply-verify-packet 缺少必填参数 --executor-report-ref");
|
|
348
|
+
if (codeReviewRefs.length === 0)
|
|
349
|
+
throw new GuardError("apply-verify-packet 缺少必填参数 --task-code-review-report-ref");
|
|
350
|
+
if (greenRefs.length === 0)
|
|
351
|
+
throw new GuardError("apply-verify-packet 缺少必填参数 --green-test-run-evidence-ref");
|
|
352
|
+
if (redRefs.length === 0 && characterizationRefs.length === 0) {
|
|
353
|
+
throw new GuardError("apply-verify-packet requires --red-test-run-evidence-ref or --characterization-test-run-evidence-ref");
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
command,
|
|
357
|
+
change,
|
|
358
|
+
task_id: taskId,
|
|
359
|
+
executor_report_refs: executorRefs,
|
|
360
|
+
task_code_review_report_refs: codeReviewRefs,
|
|
361
|
+
green_test_run_evidence_refs: greenRefs,
|
|
362
|
+
red_test_run_evidence_refs: redRefs,
|
|
363
|
+
characterization_test_run_evidence_refs: characterizationRefs,
|
|
364
|
+
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (command === "ledger-render") {
|
|
368
|
+
const gate = getValue("--gate");
|
|
369
|
+
const roundValue = getValue("--round");
|
|
370
|
+
if (!gate)
|
|
371
|
+
throw new GuardError("缺少必填参数 --gate");
|
|
372
|
+
if (roundValue === undefined)
|
|
373
|
+
return { command, change, gate };
|
|
374
|
+
const round = Number.parseInt(roundValue, 10);
|
|
375
|
+
if (!Number.isInteger(round) || round < 1)
|
|
376
|
+
throw new GuardError("--round 必须是大于等于 1 的整数");
|
|
377
|
+
return { command, change, gate, round };
|
|
378
|
+
}
|
|
154
379
|
if (command === "check-task-reopen" || command === "check-task-edit" || command === "check-task-complete") {
|
|
155
380
|
const taskId = getValue("--task-id");
|
|
156
381
|
if (!taskId)
|
package/dist/src/core.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./invariants.ts";
|
|
|
9
9
|
export * from "./git.ts";
|
|
10
10
|
export * from "./state.ts";
|
|
11
11
|
export * from "./archive.ts";
|
|
12
|
+
export * from "./apply_worker_chain.ts";
|
|
12
13
|
export * from "./gates.ts";
|
|
13
14
|
export * from "./install_engine.ts";
|
|
14
15
|
import type { ParsedArgs } from "./cli_args.ts";
|
package/dist/src/core.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./invariants.js";
|
|
|
9
9
|
export * from "./git.js";
|
|
10
10
|
export * from "./state.js";
|
|
11
11
|
export * from "./archive.js";
|
|
12
|
+
export * from "./apply_worker_chain.js";
|
|
12
13
|
export * from "./gates.js";
|
|
13
14
|
export * from "./install_engine.js";
|
|
14
15
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
package/dist/src/evidence.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CLAIM_ADJUDICATION_DECISIONS, EVIDENCE_KINDS, EVIDENCE_STATUSES, FINAL_
|
|
|
4
4
|
import { normalize_gate } from "./openspec.js";
|
|
5
5
|
import { superspec_dir } from "./paths.js";
|
|
6
6
|
import { findings_schema_reasons, review_digest_schema_reasons, standing_authorization_schema_reasons, user_decision_schema_reasons, } from "./disclosure.js";
|
|
7
|
+
import { pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, worker_test_run_reasons, } from "./apply_worker_chain.js";
|
|
7
8
|
function file_ref_reasons(baseRoot, ev, field, code) {
|
|
8
9
|
const problems = [];
|
|
9
10
|
const raw = ev[field];
|
|
@@ -29,6 +30,46 @@ function file_ref_reasons(baseRoot, ev, field, code) {
|
|
|
29
30
|
}
|
|
30
31
|
return problems;
|
|
31
32
|
}
|
|
33
|
+
function contractField(line) {
|
|
34
|
+
const match = line.match(/^\s*-\s+`?([A-Za-z0-9_-]+)`?\s*:\s*(.*)$/);
|
|
35
|
+
if (!match)
|
|
36
|
+
return null;
|
|
37
|
+
return { key: match[1], value: match[2].trim() };
|
|
38
|
+
}
|
|
39
|
+
function test_contract_section_lines(changeRoot, testId) {
|
|
40
|
+
const pathValue = join(changeRoot, ".superspec", "artifacts", "test-contract.md");
|
|
41
|
+
if (!existsSync(pathValue) || !statSync(pathValue).isFile())
|
|
42
|
+
return [];
|
|
43
|
+
const lines = readFileSync(pathValue, "utf8").split(/\r?\n/);
|
|
44
|
+
const out = [];
|
|
45
|
+
let inSection = false;
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const heading = line.match(/^###\s+(.+?)\s*$/);
|
|
48
|
+
if (heading) {
|
|
49
|
+
if (inSection)
|
|
50
|
+
break;
|
|
51
|
+
inSection = heading[1].trim() === testId;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (inSection)
|
|
55
|
+
out.push(line);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function expected_failure_contract(changeRoot, testId) {
|
|
60
|
+
const fields = new Map();
|
|
61
|
+
for (const line of test_contract_section_lines(changeRoot, testId)) {
|
|
62
|
+
const field = contractField(line);
|
|
63
|
+
if (!field)
|
|
64
|
+
continue;
|
|
65
|
+
const values = fields.get(field.key) ?? [];
|
|
66
|
+
values.push(field.value);
|
|
67
|
+
fields.set(field.key, values);
|
|
68
|
+
}
|
|
69
|
+
const signature = (fields.get("expected_failure_signature") ?? [])[0];
|
|
70
|
+
const classifier = (fields.get("expected_failure_classifier") ?? [])[0];
|
|
71
|
+
return { signature, classifier };
|
|
72
|
+
}
|
|
32
73
|
function pinned_ref_list_reasons(ev, field, baseRoot, staleCode, opts = {}) {
|
|
33
74
|
const problems = [];
|
|
34
75
|
const raw = ev[field];
|
|
@@ -94,6 +135,30 @@ function pinned_ref_reasons(ev, field, baseRoot, staleCode) {
|
|
|
94
135
|
}
|
|
95
136
|
return problems;
|
|
96
137
|
}
|
|
138
|
+
function string_list_field_reasons(ev, field, code, opts = {}) {
|
|
139
|
+
const raw = ev[field];
|
|
140
|
+
if (!Array.isArray(raw) || (!opts.allowEmpty && raw.length === 0) || !raw.every((item) => typeof item === "string" && item.length > 0)) {
|
|
141
|
+
return [reason(code, `${ev._path}: ${field} must be a non-empty string list`)];
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
function fingerprint_field_reasons(ev, field, code) {
|
|
146
|
+
const raw = ev[field];
|
|
147
|
+
if (typeof raw === "string") {
|
|
148
|
+
return raw.startsWith("sha256:") ? [] : [reason(code, `${ev._path}: ${field} must be a sha256 fingerprint`)];
|
|
149
|
+
}
|
|
150
|
+
if (isObject(raw) && typeof raw.fingerprint_digest === "string" && raw.fingerprint_digest.startsWith("sha256:"))
|
|
151
|
+
return [];
|
|
152
|
+
return [reason(code, `${ev._path}: ${field} must be a sha256 fingerprint or fingerprint object`)];
|
|
153
|
+
}
|
|
154
|
+
function pinned_artifact_ref_item_reasons(refItem, field, changeRoot, expected = {}) {
|
|
155
|
+
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, field, {
|
|
156
|
+
kind: expected.kind,
|
|
157
|
+
role: expected.role,
|
|
158
|
+
taskId: expected.task_id,
|
|
159
|
+
chainId: expected.chain_id,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
97
162
|
export function role_target_ref_reasons(ev, targetRoot) {
|
|
98
163
|
return pinned_ref_reasons(ev, "target_refs", targetRoot, "stale_review");
|
|
99
164
|
}
|
|
@@ -328,6 +393,26 @@ function test_run_reasons(ev, changeRoot) {
|
|
|
328
393
|
if (typeof ev.result_summary !== "string" || !ev.result_summary.trim()) {
|
|
329
394
|
problems.push(reason("test_run_summary_missing", `${ev._path}: test_run requires non-empty result_summary`));
|
|
330
395
|
}
|
|
396
|
+
const runnerOrigin = ev.runner_origin;
|
|
397
|
+
const executorWorkerRun = ev.apply_execution_chain === "executor_worker";
|
|
398
|
+
if (runnerOrigin !== undefined && runnerOrigin !== "main-thread" && runnerOrigin !== "test-runner") {
|
|
399
|
+
problems.push(reason("test_run_runner_origin_invalid", `${ev._path}: runner_origin must be main-thread or test-runner`));
|
|
400
|
+
}
|
|
401
|
+
if (runnerOrigin === "test-runner" && (typeof ev.phase !== "string" || !ev.phase)) {
|
|
402
|
+
problems.push(reason("test_run_runner_origin_invalid", `${ev._path}: runner_origin=test-runner requires phase`));
|
|
403
|
+
}
|
|
404
|
+
if (runnerOrigin === "test-runner" && ev.gate === "task_complete" && ev.semantic_status === "expected_success" && !executorWorkerRun) {
|
|
405
|
+
problems.push(reason("test_run_runner_origin_invalid", `${ev._path}: test-runner GREEN evidence must belong to apply_execution_chain=executor_worker`));
|
|
406
|
+
}
|
|
407
|
+
if (executorWorkerRun && runnerOrigin !== "test-runner") {
|
|
408
|
+
problems.push(reason("test_run_runner_origin_invalid", `${ev._path}: apply_execution_chain=executor_worker requires runner_origin=test-runner`));
|
|
409
|
+
}
|
|
410
|
+
if (executorWorkerRun && (typeof ev.apply_worker_chain_id !== "string" || !ev.apply_worker_chain_id)) {
|
|
411
|
+
problems.push(reason("test_run_worker_ref_missing", `${ev._path}: apply_execution_chain=executor_worker requires apply_worker_chain_id`));
|
|
412
|
+
}
|
|
413
|
+
if (runnerOrigin === "test-runner" || executorWorkerRun) {
|
|
414
|
+
problems.push(...worker_test_run_reasons(changeRoot, ev, typeof ev.task_id === "string" ? ev.task_id : "", executorWorkerRun && typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : null));
|
|
415
|
+
}
|
|
331
416
|
if (logTexts.length > 0) {
|
|
332
417
|
const claimed = [
|
|
333
418
|
...(typeof ev.test_id === "string" && ev.test_id.trim() ? [ev.test_id.trim()] : []),
|
|
@@ -339,6 +424,71 @@ function test_run_reasons(ev, changeRoot) {
|
|
|
339
424
|
}
|
|
340
425
|
}
|
|
341
426
|
}
|
|
427
|
+
if (ev.semantic_status === "expected_failure" && typeof ev.test_id === "string" && ev.test_id.trim()) {
|
|
428
|
+
const expected = expected_failure_contract(changeRoot, ev.test_id.trim());
|
|
429
|
+
if (expected.signature) {
|
|
430
|
+
if (ev.expected_failure_signature !== expected.signature) {
|
|
431
|
+
problems.push(reason("test_run_wrong_failure_reason", `${ev._path}: RED evidence expected_failure_signature must match test contract for ${ev.test_id}`, [ev.test_id]));
|
|
432
|
+
}
|
|
433
|
+
if (!logTexts.some((text) => text.includes(String(expected.signature)))) {
|
|
434
|
+
problems.push(reason("test_run_wrong_failure_reason", `${ev._path}: RED raw_log_refs do not contain expected_failure_signature for ${ev.test_id}`, [ev.test_id]));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (expected.classifier && ev.expected_failure_classifier !== expected.classifier) {
|
|
438
|
+
problems.push(reason("test_run_wrong_failure_reason", `${ev._path}: RED evidence expected_failure_classifier must match test contract for ${ev.test_id}`, [ev.test_id]));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return problems;
|
|
442
|
+
}
|
|
443
|
+
function apply_worker_chain_reasons(ev, changeRoot) {
|
|
444
|
+
const problems = [];
|
|
445
|
+
const state = String(ev.chain_state ?? "");
|
|
446
|
+
if (!["active", "closed", "abandoned"].includes(state)) {
|
|
447
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: apply_worker_chain chain_state must be active, closed, or abandoned`));
|
|
448
|
+
}
|
|
449
|
+
for (const field of ["task_id", "apply_worker_chain_id"]) {
|
|
450
|
+
if (typeof ev[field] !== "string" || !ev[field])
|
|
451
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: apply_worker_chain requires ${field}`));
|
|
452
|
+
}
|
|
453
|
+
if (state === "active") {
|
|
454
|
+
problems.push(...fingerprint_field_reasons(ev, "executor_packet_fingerprint", "apply_worker_chain_invalid"));
|
|
455
|
+
problems.push(...fingerprint_field_reasons(ev, "source_implementation_fingerprint", "apply_worker_chain_invalid"));
|
|
456
|
+
problems.push(...string_list_field_reasons(ev, "declared_task_write_scope", "apply_worker_chain_invalid"));
|
|
457
|
+
problems.push(...string_list_field_reasons(ev, "pre_edit_evidence_refs", "apply_worker_chain_invalid"));
|
|
458
|
+
}
|
|
459
|
+
if (state === "closed") {
|
|
460
|
+
const taskId = typeof ev.task_id === "string" ? ev.task_id : undefined;
|
|
461
|
+
const chainId = typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : undefined;
|
|
462
|
+
const refProblems = [
|
|
463
|
+
...pinned_artifact_ref_item_reasons(ev.executor_report_ref, "executor_report_ref", changeRoot, { kind: "worker_report", role: "executor", task_id: taskId, chain_id: chainId }),
|
|
464
|
+
...pinned_artifact_ref_item_reasons(ev.task_code_review_report_ref, "task_code_review_report_ref", changeRoot, { kind: "worker_report", role: "code-reviewer", task_id: taskId, chain_id: chainId }),
|
|
465
|
+
...pinned_artifact_ref_item_reasons(ev.verifier_report_ref, "verifier_report_ref", changeRoot, { kind: "worker_report", role: "verifier", task_id: taskId, chain_id: chainId }),
|
|
466
|
+
];
|
|
467
|
+
problems.push(...refProblems);
|
|
468
|
+
if (refProblems.length > 0)
|
|
469
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: closed apply_worker_chain report refs must be pinned same-chain worker reports`));
|
|
470
|
+
if (!isObject(ev.green_test_run_evidence_ref) && typeof ev.green_test_run_evidence_ref !== "string") {
|
|
471
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: closed apply_worker_chain requires green_test_run_evidence_ref`));
|
|
472
|
+
}
|
|
473
|
+
problems.push(...fingerprint_field_reasons(ev, "observed_freshness_fingerprint", "apply_worker_chain_invalid"));
|
|
474
|
+
}
|
|
475
|
+
if (state === "abandoned") {
|
|
476
|
+
const taskId = typeof ev.task_id === "string" ? ev.task_id : undefined;
|
|
477
|
+
const chainId = typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : undefined;
|
|
478
|
+
if ("restored_implementation_fingerprint" in ev) {
|
|
479
|
+
problems.push(...fingerprint_field_reasons(ev, "restored_implementation_fingerprint", "apply_worker_chain_invalid"));
|
|
480
|
+
}
|
|
481
|
+
if (!("restored_implementation_fingerprint" in ev) && !("serial_takeover_baseline_ref" in ev)) {
|
|
482
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain requires restored_implementation_fingerprint or serial_takeover_baseline_ref`));
|
|
483
|
+
}
|
|
484
|
+
if ("serial_takeover_baseline_ref" in ev) {
|
|
485
|
+
const baselineProblems = pinned_artifact_ref_item_reasons(ev.serial_takeover_baseline_ref, "serial_takeover_baseline_ref", changeRoot, { kind: "status_report", role: "verifier", task_id: taskId, chain_id: chainId });
|
|
486
|
+
problems.push(...baselineProblems);
|
|
487
|
+
if (baselineProblems.length > 0)
|
|
488
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain serial_takeover_baseline_ref must be a pinned same-chain status_report`));
|
|
489
|
+
problems.push(...string_list_field_reasons(ev, "successor_green_evidence_refs", "apply_worker_chain_invalid"));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
342
492
|
return problems;
|
|
343
493
|
}
|
|
344
494
|
export function validate_evidence_schema(ev, change, changeRoot, repoRoot) {
|
|
@@ -359,6 +509,8 @@ export function validate_evidence_schema(ev, change, changeRoot, repoRoot) {
|
|
|
359
509
|
problems.push(...human_confirmation_reasons(ev));
|
|
360
510
|
if (ev.kind === "test_run")
|
|
361
511
|
problems.push(...test_run_reasons(ev, changeRoot));
|
|
512
|
+
if (ev.kind === "apply_worker_chain")
|
|
513
|
+
problems.push(...apply_worker_chain_reasons(ev, changeRoot));
|
|
362
514
|
// DISC Phase 1: disclosure evidence kinds and reviewer findings[] are schema-checked fail-closed.
|
|
363
515
|
if (ev.kind === "main_review_digest")
|
|
364
516
|
problems.push(...review_digest_schema_reasons(ev));
|
package/dist/src/gates.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Decision, JsonMap, Reason } from "./util.ts";
|
|
2
|
-
export declare function openspec_init_reasons(
|
|
2
|
+
export declare function openspec_init_reasons(_repoRoot: string): Reason[];
|
|
3
3
|
export declare function superspec_workflow_skill_reasons(repoRoot: string): Reason[];
|
|
4
4
|
export declare function superspec_agent_reasons(repoRoot: string): Reason[];
|
|
5
5
|
export declare function openspec_cli_capability_reasons(): Reason[];
|
|
@@ -10,6 +10,7 @@ export declare function check_artifact(change: string, status: JsonMap, changeRo
|
|
|
10
10
|
export declare function check_task_reopen(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], taskId: string): Decision;
|
|
11
11
|
export declare function check_apply_ready(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[]): Decision;
|
|
12
12
|
export declare function check_task_edit(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], taskId: string): Decision;
|
|
13
|
+
export declare function apply_worker_chain_lifecycle_reasons(repoRoot: string, changeRoot: string, evidences: JsonMap[], taskId: string): Reason[];
|
|
13
14
|
export declare function check_task_complete(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], taskId: string): Decision;
|
|
14
15
|
export declare function check_review_ready(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[]): Decision;
|
|
15
16
|
export declare function check_review_complete(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[]): Decision;
|