@peterxiaoyang/superspec 0.1.58 → 0.1.59
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/dist/cli.js +24 -3
- package/dist/code_review.d.ts +5 -0
- package/dist/code_review.js +21 -0
- package/dist/record.d.ts +21 -1
- package/dist/record.js +51 -29
- package/dist/report_contract.d.ts +70 -0
- package/dist/report_contract.js +161 -0
- package/dist/transition.js +10 -1
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
- package/templates/workflow/agents/architect.toml +1 -1
- package/templates/workflow/agents/code-reviewer.toml +1 -1
- package/templates/workflow/agents/critic.toml +1 -1
- package/templates/workflow/agents/test-engineer.toml +1 -1
- package/templates/workflow/agents/test-runner.toml +1 -1
- package/templates/workflow/agents/verifier.toml +1 -1
- package/templates/workflow/agents-md/architect.md +1 -1
- package/templates/workflow/agents-md/code-reviewer.md +2 -2
- package/templates/workflow/agents-md/critic.md +1 -1
- package/templates/workflow/agents-md/test-engineer.md +1 -1
- package/templates/workflow/agents-md/test-runner.md +1 -1
- package/templates/workflow/agents-md/verifier.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { writeSnapshot } from "./store.js";
|
|
|
8
8
|
import { rebuildSnapshot } from "./sync.js";
|
|
9
9
|
import { next as nextCmd } from "./next.js";
|
|
10
10
|
import { proposeReady, commitTransition, transitionInit, transitionExplore, startApply, taskStart, taskComplete, reopen, reviewReady, accept } from "./transition.js";
|
|
11
|
-
import { recordJobSubmit, recordJobSubmitContent, recordUserDecision, recordUserDecisionContent, jobsList, jobsPacket } from "./record.js";
|
|
11
|
+
import { recordJobSubmit, recordJobSubmitContent, recordUserDecision, recordUserDecisionContent, jobsList, jobsPacket, jobsContract } from "./record.js";
|
|
12
12
|
import { recordTestRun, recordTestRunContent } from "./task.js";
|
|
13
13
|
import { RecordInputDecodingError, decodeRecordInput } from "./record_input.js";
|
|
14
14
|
import { probeOpenSpec, openspecStatus, changeRoot } from "./openspec.js";
|
|
@@ -573,10 +573,20 @@ record 子命令:
|
|
|
573
573
|
test-run --input <F|->
|
|
574
574
|
|
|
575
575
|
jobs 子命令:
|
|
576
|
-
list / packet --job <J>
|
|
576
|
+
list / packet --job <J> / contract --job <J> [--skeleton]
|
|
577
577
|
`;
|
|
578
578
|
}
|
|
579
579
|
function commandHelp(command, subcommand) {
|
|
580
|
+
if (command === "jobs" && subcommand === "contract") {
|
|
581
|
+
return `用法:superspec jobs contract --change <C> --job <J> [--skeleton]
|
|
582
|
+
|
|
583
|
+
输出该工作项的报告契约(默认)或可直接填写的报告骨架(--skeleton)。
|
|
584
|
+
契约与 packet 顶层的 report_skeleton 同源;审查角色应在产出报告前先取骨架,逐字段填写。
|
|
585
|
+
|
|
586
|
+
示例:
|
|
587
|
+
superspec jobs contract --change <C> --job <J> --skeleton
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
580
590
|
if (command === "record" && subcommand === "user-decision") {
|
|
581
591
|
return `用法:superspec record user-decision --change <C> --input <F|->
|
|
582
592
|
|
|
@@ -590,7 +600,7 @@ function commandHelp(command, subcommand) {
|
|
|
590
600
|
if (command === "record" && subcommand === "job-submit") {
|
|
591
601
|
return `用法:superspec record job-submit --change <C> --job <J> --report <F|->
|
|
592
602
|
|
|
593
|
-
提交 reviewer JSON 报告;--report - 表示从 stdin
|
|
603
|
+
提交 reviewer JSON 报告;--report - 表示从 stdin 读取。报告契约(report_schema)见 superspec jobs contract --change <C> --job <J>;packet 顶层给出预填骨架(report_skeleton)。
|
|
594
604
|
需要落盘时写到 packet 的 report_file_path(.superspec/changes/<C>/jobs/<J>.report.json),不要放进 openspec/changes 计划材料目录。
|
|
595
605
|
|
|
596
606
|
示例:
|
|
@@ -958,6 +968,17 @@ async function main(argv) {
|
|
|
958
968
|
console.log(JSON.stringify(result, null, 2));
|
|
959
969
|
return result.found ? 0 : 1;
|
|
960
970
|
}
|
|
971
|
+
case "contract": {
|
|
972
|
+
const jobId = opts.job;
|
|
973
|
+
if (!jobId) {
|
|
974
|
+
console.error("jobs contract 需要 --job");
|
|
975
|
+
return 1;
|
|
976
|
+
}
|
|
977
|
+
const skeletonOnly = opts.skeleton === "true";
|
|
978
|
+
const result = jobsContract(projectRoot, change, jobId, { skeleton: skeletonOnly });
|
|
979
|
+
console.log(JSON.stringify(skeletonOnly && result.found ? result.report_skeleton : result, null, 2));
|
|
980
|
+
return result.found ? 0 : 1;
|
|
981
|
+
}
|
|
961
982
|
default:
|
|
962
983
|
console.error(`未知的 jobs 子命令:${subcommand}`);
|
|
963
984
|
return 1;
|
package/dist/code_review.d.ts
CHANGED
|
@@ -105,4 +105,9 @@ export declare function latestApplyDoneToReviewGate(events: Event[]): {
|
|
|
105
105
|
export declare function latestCodeReviewGateEvidence(events: Event[]): CodeReviewGateEvidence | null;
|
|
106
106
|
export declare function requiresFinalVerifierForCurrentReview(events: Event[]): boolean;
|
|
107
107
|
export declare function computeCodeStateCheck(projectRoot: string, events: Event[], ignoredCodePaths?: string[]): CodeStateCheck;
|
|
108
|
+
/** 已接受代码审查工作项在事件流里留下的范围外未检查摘要(pass 的置信边界)。 */
|
|
109
|
+
export declare function outOfScopeUncheckedForJob(events: Event[], jobId: string): {
|
|
110
|
+
path: string;
|
|
111
|
+
reason: string;
|
|
112
|
+
}[];
|
|
108
113
|
export {};
|
package/dist/code_review.js
CHANGED
|
@@ -867,6 +867,9 @@ export function latestCodeReviewGateEvidence(events) {
|
|
|
867
867
|
job_id: typeof gate.job_id === "string" ? gate.job_id : null,
|
|
868
868
|
packet_digest: typeof gate.packet_digest === "string" ? gate.packet_digest : null,
|
|
869
869
|
...(gate.reason === "no_code_changes" ? { reason: gate.reason } : {}),
|
|
870
|
+
...(Array.isArray(gate.out_of_scope_unchecked) && gate.out_of_scope_unchecked.length > 0
|
|
871
|
+
? { out_of_scope_unchecked: gate.out_of_scope_unchecked }
|
|
872
|
+
: {}),
|
|
870
873
|
event_id: event.event_id,
|
|
871
874
|
event_digest: event.event_digest,
|
|
872
875
|
};
|
|
@@ -969,3 +972,21 @@ export function computeCodeStateCheck(projectRoot, events, ignoredCodePaths = []
|
|
|
969
972
|
scope_reason: scopeReason,
|
|
970
973
|
};
|
|
971
974
|
}
|
|
975
|
+
/** 已接受代码审查工作项在事件流里留下的范围外未检查摘要(pass 的置信边界)。 */
|
|
976
|
+
export function outOfScopeUncheckedForJob(events, jobId) {
|
|
977
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
978
|
+
const event = events[i];
|
|
979
|
+
if (event.event_type !== "job_accepted")
|
|
980
|
+
continue;
|
|
981
|
+
const payload = event.payload;
|
|
982
|
+
if (payload.job_id !== jobId)
|
|
983
|
+
continue;
|
|
984
|
+
if (!Array.isArray(payload.out_of_scope_unchecked))
|
|
985
|
+
return [];
|
|
986
|
+
return payload.out_of_scope_unchecked.filter((item) => {
|
|
987
|
+
const candidate = item;
|
|
988
|
+
return typeof candidate?.path === "string" && typeof candidate?.reason === "string";
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
return [];
|
|
992
|
+
}
|
package/dist/record.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { RecordResult, Job, JobPacket } from "./types.ts";
|
|
1
|
+
import type { RecordResult, Job, JobPacket, JobRole } from "./types.ts";
|
|
2
|
+
import { type ReportSchemaContract } from "./report_contract.ts";
|
|
2
3
|
/** 报告文件的推荐落盘位置:工作流记录目录,不进入计划材料。 */
|
|
3
4
|
export declare function jobReportFilePath(change: string, jobId: string): string;
|
|
4
5
|
/** record job-submit:登记工作项结果 */
|
|
@@ -24,3 +25,22 @@ export declare function jobsPacket(projectRoot: string, change: string, jobId: s
|
|
|
24
25
|
packet?: JobPacket;
|
|
25
26
|
message: string;
|
|
26
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* jobs contract:单独取报告契约(默认)或可填骨架(--skeleton)。
|
|
30
|
+
*
|
|
31
|
+
* 与 packet 顶层的 report_skeleton 同源,供审查角色在产出报告前自查结构,
|
|
32
|
+
* 不必从 packet 的长段落里提取。
|
|
33
|
+
*/
|
|
34
|
+
export declare function jobsContract(projectRoot: string, change: string, jobId: string, opts?: {
|
|
35
|
+
skeleton?: boolean;
|
|
36
|
+
}): {
|
|
37
|
+
found: boolean;
|
|
38
|
+
job_id?: string;
|
|
39
|
+
role?: JobRole;
|
|
40
|
+
report_schema?: ReportSchemaContract;
|
|
41
|
+
report_skeleton?: Record<string, unknown>;
|
|
42
|
+
fill_items?: string[];
|
|
43
|
+
contract_command?: string;
|
|
44
|
+
skeleton_command?: string;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
package/dist/record.js
CHANGED
|
@@ -15,20 +15,8 @@ import { RecordInputDecodingError, readRecordInputFile } from "./record_input.js
|
|
|
15
15
|
import { currentExploreRoundId } from "./explore_round.js";
|
|
16
16
|
import { currentProposeOpenQuestion, currentProposeQuestionContent, currentProposeRoundId, } from "./propose_round.js";
|
|
17
17
|
import { discoveryOpenQuestionDisplayText, discoveryQuestionContextFingerprint, discoveryQuestionDecisionBasisDigest, discoveryOpenQuestionScope, legacyDiscoveryOpenQuestionScope, EXPLORE_OPEN_QUESTION_SCOPE_PREFIX, parseDiscoveryOpenQuestions, proposeOpenQuestionDisplayText, proposeOpenQuestionScope, proposeQuestionContextFingerprint, proposeQuestionDecisionBasisDigest, legacyProposeOpenQuestionScope, PROPOSE_OPEN_QUESTION_SCOPE_PREFIX, } from "./format.js";
|
|
18
|
-
|
|
19
|
-
const REVIEW_REPORT_OPTIONAL_FIELDS = ["summary", "evidence_refs", "risks", "open_questions"];
|
|
20
|
-
const REVIEWER_KINDS = new Set(["subagent", "codex-subagent", "human", "external-agent"]);
|
|
18
|
+
import { COVERAGE_MESSAGES, REVIEWER_KINDS, outOfScopeUncheckedFromReport, REVIEW_REPORT_OPTIONAL_FIELDS, REVIEW_REPORT_REQUIRED_FIELDS, isReviewRole, requiresReviewer, requiresReviewScope, reportSchemaForJob, reportSkeletonFillItems, reportSkeletonForJob, } from "./report_contract.js";
|
|
21
19
|
const CODE_REVIEW_FINDING_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
22
|
-
function isReviewRole(role) {
|
|
23
|
-
return role === "critic" || role === "architect" || role === "test-engineer" || role === "code-reviewer" || role === "verifier";
|
|
24
|
-
}
|
|
25
|
-
function requiresReviewer(role) {
|
|
26
|
-
return role === "critic" || role === "architect" || role === "test-engineer" || role === "code-reviewer";
|
|
27
|
-
}
|
|
28
|
-
/** All review reports with bound material acknowledge full file coverage. */
|
|
29
|
-
function requiresReviewScope(job) {
|
|
30
|
-
return job.boundFiles.length > 0 && isReviewRole(job.role);
|
|
31
|
-
}
|
|
32
20
|
function projectLocalInvalidReportMustTerminate(job) {
|
|
33
21
|
return job.role === "code-reviewer" || job.packet_context?.code_state_check !== undefined;
|
|
34
22
|
}
|
|
@@ -256,7 +244,7 @@ function uncheckedCodeReviewPaths(value, checks) {
|
|
|
256
244
|
for (const item of value) {
|
|
257
245
|
const obj = asObject(item);
|
|
258
246
|
if (!obj) {
|
|
259
|
-
checks.push(
|
|
247
|
+
checks.push(COVERAGE_MESSAGES.uncheckedItemNotObject);
|
|
260
248
|
continue;
|
|
261
249
|
}
|
|
262
250
|
if (!nonEmptyString(obj.path)) {
|
|
@@ -284,8 +272,8 @@ function validateReviewer(obj, checks) {
|
|
|
284
272
|
checks.push("报告 reviewer 必须是包含 kind/id 的对象");
|
|
285
273
|
}
|
|
286
274
|
else {
|
|
287
|
-
if (typeof reviewer.kind !== "string" || !REVIEWER_KINDS.
|
|
288
|
-
checks.push(`报告 reviewer.kind 必须是 ${
|
|
275
|
+
if (typeof reviewer.kind !== "string" || !REVIEWER_KINDS.includes(reviewer.kind)) {
|
|
276
|
+
checks.push(`报告 reviewer.kind 必须是 ${REVIEWER_KINDS.join("|")} 之一`);
|
|
289
277
|
}
|
|
290
278
|
if (typeof reviewer.id !== "string" || reviewer.id.trim() === "") {
|
|
291
279
|
checks.push("报告 reviewer.id 必须是非空字符串");
|
|
@@ -295,7 +283,7 @@ function validateReviewer(obj, checks) {
|
|
|
295
283
|
function validateCodeReviewScope(obj, job, checks) {
|
|
296
284
|
const scope = asObject(obj.review_scope);
|
|
297
285
|
if (!scope) {
|
|
298
|
-
checks.push(
|
|
286
|
+
checks.push(COVERAGE_MESSAGES.codeReviewScopeMissing);
|
|
299
287
|
return;
|
|
300
288
|
}
|
|
301
289
|
if (scope.job_id !== job.job_id) {
|
|
@@ -305,11 +293,11 @@ function validateCodeReviewScope(obj, job, checks) {
|
|
|
305
293
|
checks.push("代码审查覆盖范围里的 packet_digest 与工作项不匹配,请使用当前工作项说明重新生成报告");
|
|
306
294
|
}
|
|
307
295
|
if (!stringArray(scope.checked_paths))
|
|
308
|
-
checks.push(
|
|
296
|
+
checks.push(COVERAGE_MESSAGES.checkedPathsNotStringArray);
|
|
309
297
|
if (!stringArray(scope.checked_docs))
|
|
310
|
-
checks.push(
|
|
298
|
+
checks.push(COVERAGE_MESSAGES.checkedDocsNotStringArray);
|
|
311
299
|
if (!Array.isArray(scope.unchecked))
|
|
312
|
-
checks.push(
|
|
300
|
+
checks.push(COVERAGE_MESSAGES.uncheckedNotArray);
|
|
313
301
|
if (stringArray(scope.checked_paths) && Array.isArray(scope.unchecked)) {
|
|
314
302
|
const checkedPaths = new Set(scope.checked_paths);
|
|
315
303
|
const uncheckedPaths = uncheckedCodeReviewPaths(scope.unchecked, checks);
|
|
@@ -318,19 +306,21 @@ function validateCodeReviewScope(obj, job, checks) {
|
|
|
318
306
|
checks.push(`代码审查报告未说明是否检查了 ${bound.path}`);
|
|
319
307
|
}
|
|
320
308
|
}
|
|
321
|
-
|
|
322
|
-
|
|
309
|
+
// 只有绑定文件被列为未检查时才阻塞 pass:范围外观察(未跑构建、无关文件)写进
|
|
310
|
+
// unchecked 是诚实记录,不应让 pass 结论无法提交。
|
|
311
|
+
if (obj.verdict === "pass" && job.boundFiles.some(bound => uncheckedPaths.has(bound.path))) {
|
|
312
|
+
checks.push(COVERAGE_MESSAGES.passWithUncheckedBoundFile);
|
|
323
313
|
}
|
|
324
314
|
}
|
|
325
315
|
}
|
|
326
316
|
function validateReviewScope(obj, job, checks) {
|
|
327
317
|
const scope = asObject(obj.review_scope);
|
|
328
318
|
if (!scope) {
|
|
329
|
-
checks.push(
|
|
319
|
+
checks.push(COVERAGE_MESSAGES.reviewScopeMissing);
|
|
330
320
|
return;
|
|
331
321
|
}
|
|
332
322
|
if (!stringArray(scope.checked_paths)) {
|
|
333
|
-
checks.push(
|
|
323
|
+
checks.push(COVERAGE_MESSAGES.reviewCheckedPathsNotStringArray);
|
|
334
324
|
return;
|
|
335
325
|
}
|
|
336
326
|
const checkedPaths = new Set(scope.checked_paths);
|
|
@@ -667,11 +657,15 @@ function recordJobSubmitLoaded(projectRoot, change, changeRoot, jobId, job, even
|
|
|
667
657
|
}
|
|
668
658
|
}
|
|
669
659
|
const rawRef = appendRawRecord(projectRoot, change, "review-reports", parsedReport);
|
|
660
|
+
// pass 结论下的范围外未检查项不进门禁,但必须留在决策面:只翻 raw 才能看到
|
|
661
|
+
// 会让「pass 但未跑构建 / 未覆盖范围外文件」这一事实在流程里消失。
|
|
662
|
+
const outOfScopeUnchecked = job.role === "code-reviewer" ? outOfScopeUncheckedFromReport(parsedReport, job.boundFiles) : [];
|
|
670
663
|
const acceptEvent = makeEvent(change, "job_accepted", {
|
|
671
664
|
job_id: jobId,
|
|
672
665
|
role: job.role,
|
|
673
666
|
report_digest: reportDigest,
|
|
674
667
|
accepted_at: new Date().toISOString(),
|
|
668
|
+
...(outOfScopeUnchecked.length > 0 ? { out_of_scope_unchecked: outOfScopeUnchecked } : {}),
|
|
675
669
|
...(reportPath ? { report_path: reportPath } : {}),
|
|
676
670
|
...rawRef,
|
|
677
671
|
});
|
|
@@ -1199,6 +1193,8 @@ function packetFieldDescriptions() {
|
|
|
1199
1193
|
unknown_attribution_tasks: "因为缺少边界快照或提交段 diff 失败而无法完整计算改动归属的任务(task)。",
|
|
1200
1194
|
coverage_exemption_refs: "测试覆盖豁免引用:说明某个 TEST 为什么没有绑定到任务(task)。",
|
|
1201
1195
|
report_file_path: "报告需要落盘时的文件位置(项目相对路径),位于工作流记录目录;报告内容登记后由引擎存入 raw 记录,不属于计划材料。",
|
|
1196
|
+
report_skeleton: "按本工作项预填的报告骨架(job_id / packet_digest / 空数组);逐字段填写即可,不要自行设计结构。",
|
|
1197
|
+
report_schema: "报告契约(字段形状、取值、条件、提示消息);由 `superspec jobs contract --change <C> --job <J>` 输出,提交校验引用同一份定义。",
|
|
1202
1198
|
code_review_gate: "最终验证读取的代码审查门禁事实:passed 指向已接受的代码审查工作项,skipped 表示本轮没有代码类改动。",
|
|
1203
1199
|
code_state_check: "代码状态检查:最终验证时用于判断代码审查后代码是否又发生变化。",
|
|
1204
1200
|
event_id: "事件 ID,用于追溯证据来源。",
|
|
@@ -1260,8 +1256,9 @@ export function jobsPacket(projectRoot, change, jobId) {
|
|
|
1260
1256
|
...(hasReviewScope ? ["review_scope"] : []),
|
|
1261
1257
|
],
|
|
1262
1258
|
output_contract_optional_fields: [...REVIEW_REPORT_OPTIONAL_FIELDS],
|
|
1259
|
+
report_skeleton: reportSkeletonForJob(job),
|
|
1263
1260
|
字段说明: packetFieldDescriptions(),
|
|
1264
|
-
output_instructions: `${roleDescription(job.role)}
|
|
1261
|
+
output_instructions: `${roleDescription(job.role)}。本工作项的报告结构以 packet 顶层 report_skeleton 为准(完整契约:superspec jobs contract --change "${change}" --job "${job.job_id}"):按骨架逐字段填写,job_id / packet_digest 已按本工作项预填,不要改写,也不要用上一轮报告里的值。` +
|
|
1265
1262
|
(isReviewer ? reviewScopeInstruction(job, reviewTargets, readOnlyRefs) : "") +
|
|
1266
1263
|
(isReviewer ? migrationEvidenceInstruction(job) : "") +
|
|
1267
1264
|
(job.review_evidence_digest ? `本工作项对应的执行证据版本为 ${job.review_evidence_digest},` : "") +
|
|
@@ -1269,19 +1266,19 @@ export function jobsPacket(projectRoot, change, jobId) {
|
|
|
1269
1266
|
(requiresReviewer(job.role) ? `必须由独立 ${recommendedAgentForRole(job.role)} 审查角色执行,并在审查者来源字段(reviewer.kind/id)中记录来源,` : "") +
|
|
1270
1267
|
`产出 JSON 报告内容并优先通过 --report - 从 stdin 登记;需要落盘时写到 report_file_path,不要写进 openspec/changes 或 .superspec/artifacts 等计划材料目录。${recordInputInstruction(job)}协议字段含义见 packet 顶层“字段说明”,普通对话不要原样复述 JSON。` +
|
|
1271
1268
|
(isCodeReviewer
|
|
1272
|
-
?
|
|
1269
|
+
? `格式骨架:${JSON.stringify(reportSkeletonForJob(job))}。提交前按真实审查结果填写数组;不得从 boundFiles 自动复制 checked_paths。verdict 只能为 pass 或 fail;审查覆盖范围(review_scope)用来说明本次审查覆盖了哪些文件和文档,已检查路径(checked_paths)与未检查项(unchecked)必须合起来覆盖全部绑定文件(boundFiles),unchecked 条目格式为 {"path":"<path>","reason":"<reason>"};pass 不允许仍有未检查的绑定文件。`
|
|
1273
1270
|
+ `报告结论为 fail 时,问题列表(findings)至少包含一个可处理、可追溯的阻塞问题,字段为 {"id":"<stable-id>","blocking":true,"type":"implementation|spec|mixed","claim_kind":"missing_approved|breaks_existing|unjustified_addition","approved_refs":["TEST-001"],"description":"<what>","evidence":"<why>","source_refs":["<path:line>"],"impact":"<impact>","suggested_action":"apply|propose"}。问题类型(type)中 implementation 表示纯代码实现问题,spec 表示方案/需求文档问题,mixed 表示需要使用者判断的混合问题;claim_kind 与 approved_refs 见字段说明。`
|
|
1274
1271
|
+ (packetContext?.task_execution_index
|
|
1275
1272
|
? `本工作项带任务执行索引(task_execution_index):按 task 对照其执行依据快照(contract)审查——实现路线对照 design 引用原文、累计 diff 对照 guard 边界、测试断言对照 tests 声明的 scenario;每项的 required_evidence 是 task-start 冻结的证据口径,red_required/green_required 分别说明是否需要 RED/GREEN;fix 非空表示状态机创建的实现修复,source、parent_task_id 和 reason 说明其归属,code_review 来源还需核对 review_finding;scope_note 既可能解释必要的范围扩大,也可能说明代码审查修复为何保留原实现,均需结合 Diff、调用链和验证证据独立判断;changed_paths 是归属线索不是结论(null 表示未知);unattributed_paths 中的无主改动和 added_code_paths 中的新建代码文件,均需判断是否服务已批准行为:放行其中任何计划外文件,都必须写明它服务于哪条已批准锚点、为何无法避免,说不出依据的按 unjustified_addition 收缩;coverage_exemption_refs 解释未绑定 task 的 TEST 豁免。当前 packet 的 boundFiles 是本轮冻结的审查范围;若它来自前一轮审查后的增量,只复核本轮变化及其直接影响链路,不要求重复审查未变化文件,但仍要判断批准行为是否完整闭合。`
|
|
1276
1273
|
: "")
|
|
1277
1274
|
: job.role === "verifier"
|
|
1278
|
-
?
|
|
1275
|
+
? `最小格式:${JSON.stringify(reportSkeletonForJob(job))}。verdict 只能为 pass 或 fail;核对代码审查记录(code_review_gate):passed 必须能追溯到已接受的代码审查工作项,skipped 必须能证明本次没有代码类改动。核对修复闭环:task_execution_index.fix.source=code_review 时必须核对 review_finding 对应问题是否关闭;source=self_test 时必须核对 parent_task_id、记录的自测原因、本次 attempt 验证和最新代码审查是否共同闭环。方案/混合问题必须有用户决策或后续修复证据。按 task_execution_index 的 required_evidence 核对测试证据:red_required 时需要同一 TEST 的 RED(expected_failure)后 GREEN;green_required 时每个声明 TEST 都需要允许的 GREEN 语义状态;测试运行证据应包含测试 ID(test_id)、命令(command)、工作目录(cwd)、退出码(exit_code)、语义状态(semantic_status)。修复 task 的回归测试运行可用回归覆盖任务列表(covers_task_ids)说明覆盖了哪些已完成任务;缺少任务尝试 ID(attempt_id)的旧证据只能弱引用。` +
|
|
1279
1276
|
(packetContext?.code_state_check
|
|
1280
1277
|
? `本工作项带代码状态检查(code_state_check),它是创建 packet 时的快照:验证期间若代码状态已变化,不要提交该报告;主流程会通过 next 创建携带最新事实的验证工作项。`
|
|
1281
1278
|
: "")
|
|
1282
1279
|
: isReviewer
|
|
1283
|
-
?
|
|
1284
|
-
:
|
|
1280
|
+
? `最小格式:${JSON.stringify(reportSkeletonForJob(job))}。verdict 只能为 pass 或 fail。`
|
|
1281
|
+
: `最小格式:${JSON.stringify(reportSkeletonForJob(job))}。verdict 只能为 pass 或 fail。`),
|
|
1285
1282
|
stop_conditions: isReviewer
|
|
1286
1283
|
? ["完成审查后提交报告,不要修改文档"]
|
|
1287
1284
|
: job.role === "executor"
|
|
@@ -1292,3 +1289,28 @@ export function jobsPacket(projectRoot, change, jobId) {
|
|
|
1292
1289
|
message: `工作项 ${jobId} 的执行说明`,
|
|
1293
1290
|
};
|
|
1294
1291
|
}
|
|
1292
|
+
/**
|
|
1293
|
+
* jobs contract:单独取报告契约(默认)或可填骨架(--skeleton)。
|
|
1294
|
+
*
|
|
1295
|
+
* 与 packet 顶层的 report_skeleton 同源,供审查角色在产出报告前自查结构,
|
|
1296
|
+
* 不必从 packet 的长段落里提取。
|
|
1297
|
+
*/
|
|
1298
|
+
export function jobsContract(projectRoot, change, jobId, opts = {}) {
|
|
1299
|
+
const job = findJob(readEvents(projectRoot, change), jobId);
|
|
1300
|
+
if (!job)
|
|
1301
|
+
return { found: false, message: `工作项 ${jobId} 不存在` };
|
|
1302
|
+
const base = ["superspec", "jobs", "contract", "--change", change, "--job", job.job_id];
|
|
1303
|
+
return {
|
|
1304
|
+
found: true,
|
|
1305
|
+
job_id: job.job_id,
|
|
1306
|
+
role: job.role,
|
|
1307
|
+
report_schema: reportSchemaForJob(job),
|
|
1308
|
+
report_skeleton: reportSkeletonForJob(job),
|
|
1309
|
+
fill_items: reportSkeletonFillItems(job),
|
|
1310
|
+
contract_command: base.join(" "),
|
|
1311
|
+
skeleton_command: [...base, "--skeleton"].join(" "),
|
|
1312
|
+
message: opts.skeleton
|
|
1313
|
+
? `工作项 ${job.job_id} 的报告骨架`
|
|
1314
|
+
: `工作项 ${job.job_id} 的报告契约`,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 报告契约单源:字段形状与取值、覆盖类提示消息、骨架渲染。
|
|
3
|
+
*
|
|
4
|
+
* 范围止于「形状」:字段名/类型/枚举/必填与否 + 跨字段条件 + 可直接填写的骨架。
|
|
5
|
+
* 面向模型的说明文字仍由 packet 的 output_instructions 承载,判据仍由 record.ts 的
|
|
6
|
+
* 校验函数执行(交叉规则与跨文件解析不声明化)。这样契约表不是第二份散文,
|
|
7
|
+
* 也不与校验器争谁说了算。
|
|
8
|
+
*
|
|
9
|
+
* packet 的 report_skeleton、`superspec jobs contract` 与提交校验共用这里的定义。
|
|
10
|
+
*/
|
|
11
|
+
import type { Job, JobRole } from "./types.ts";
|
|
12
|
+
export declare const REVIEW_REPORT_REQUIRED_FIELDS: readonly ["role", "verdict", "findings"];
|
|
13
|
+
export declare const REVIEW_REPORT_OPTIONAL_FIELDS: readonly ["summary", "evidence_refs", "risks", "open_questions"];
|
|
14
|
+
/** reviewer.kind 允许取值:契约、packet 与提交校验共用。 */
|
|
15
|
+
export declare const REVIEWER_KINDS: readonly ["subagent", "codex-subagent", "human", "external-agent"];
|
|
16
|
+
/** 审查/验证类角色:报告需要 review_scope 覆盖回执。 */
|
|
17
|
+
export declare function isReviewRole(role: JobRole): boolean;
|
|
18
|
+
/** 需要 reviewer.kind/id 的角色。 */
|
|
19
|
+
export declare function requiresReviewer(role: JobRole): boolean;
|
|
20
|
+
/** 需要 review_scope 覆盖回执的工作项。 */
|
|
21
|
+
export declare function requiresReviewScope(job: Job): boolean;
|
|
22
|
+
/** 提交校验与契约共用的覆盖类提示消息:改这里即同时影响两侧。 */
|
|
23
|
+
export declare const COVERAGE_MESSAGES: {
|
|
24
|
+
readonly codeReviewScopeMissing: "代码审查报告缺少覆盖范围字段 review_scope";
|
|
25
|
+
readonly reviewScopeMissing: "审查报告缺少覆盖范围字段 review_scope";
|
|
26
|
+
readonly checkedPathsNotStringArray: "代码审查覆盖范围里的 checked_paths 必须是字符串数组";
|
|
27
|
+
readonly reviewCheckedPathsNotStringArray: "审查报告覆盖范围里的 checked_paths 必须是字符串数组";
|
|
28
|
+
readonly checkedDocsNotStringArray: "代码审查覆盖范围里的 checked_docs 必须是字符串数组";
|
|
29
|
+
readonly uncheckedNotArray: "代码审查覆盖范围里的 unchecked 必须是数组";
|
|
30
|
+
readonly uncheckedItemNotObject: "代码审查覆盖范围里的未检查项必须是包含 path/reason 的对象";
|
|
31
|
+
readonly passWithUncheckedBoundFile: "代码审查结论为 pass 时不能包含未检查的绑定文件";
|
|
32
|
+
};
|
|
33
|
+
export interface ReportFieldContract {
|
|
34
|
+
type: "string" | "array" | "object";
|
|
35
|
+
required?: boolean;
|
|
36
|
+
values?: readonly string[];
|
|
37
|
+
pattern?: string;
|
|
38
|
+
item?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ReportSchemaContract {
|
|
41
|
+
role: JobRole;
|
|
42
|
+
required_fields: string[];
|
|
43
|
+
optional_fields: string[];
|
|
44
|
+
/** 顶层字段与 review_scope / findings 子字段的形状,键用点路径表示。 */
|
|
45
|
+
fields: Record<string, ReportFieldContract>;
|
|
46
|
+
/** 跨字段条件;校验器拒绝时的措辞与此保持一致。 */
|
|
47
|
+
conditions: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 工作项报告契约:只描述形状与取值。模型需要的解释性文字见 packet 的
|
|
51
|
+
* `output_instructions` 与 `字段说明`;判据由 record.ts 执行。
|
|
52
|
+
*/
|
|
53
|
+
export declare function reportSchemaForJob(job: Job): ReportSchemaContract;
|
|
54
|
+
/**
|
|
55
|
+
* 可直接填写的报告骨架。
|
|
56
|
+
*
|
|
57
|
+
* 只预填工作项常量(job_id / packet_digest)与空数组:code-reviewer 的 checked_paths
|
|
58
|
+
* 必须由审查者按实际浏览填写,预填会架空覆盖回执的意义。
|
|
59
|
+
* 普通 reviewer / verifier 的 checked_paths 仍按既有协议预填全部绑定文件。
|
|
60
|
+
*/
|
|
61
|
+
export declare function reportSkeletonForJob(job: Job): Record<string, unknown>;
|
|
62
|
+
/** 骨架中必须由审查者替换的空值,供 packet 与 CLI 给出同一份填写提示。 */
|
|
63
|
+
export declare function reportSkeletonFillItems(job: Job): string[];
|
|
64
|
+
/** 报告里声明"未检查"且不属于绑定文件的条目(pass 的置信边界,供事件流与门禁透出)。 */
|
|
65
|
+
export declare function outOfScopeUncheckedFromReport(report: Record<string, unknown> | null, boundFiles: {
|
|
66
|
+
path: string;
|
|
67
|
+
}[]): {
|
|
68
|
+
path: string;
|
|
69
|
+
reason: string;
|
|
70
|
+
}[];
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export const REVIEW_REPORT_REQUIRED_FIELDS = ["role", "verdict", "findings"];
|
|
2
|
+
export const REVIEW_REPORT_OPTIONAL_FIELDS = ["summary", "evidence_refs", "risks", "open_questions"];
|
|
3
|
+
/** reviewer.kind 允许取值:契约、packet 与提交校验共用。 */
|
|
4
|
+
export const REVIEWER_KINDS = ["subagent", "codex-subagent", "human", "external-agent"];
|
|
5
|
+
/** 需要 reviewer 来源字段的角色。 */
|
|
6
|
+
const REVIEWER_ROLE = {
|
|
7
|
+
critic: true,
|
|
8
|
+
architect: true,
|
|
9
|
+
"test-engineer": true,
|
|
10
|
+
"code-reviewer": true,
|
|
11
|
+
};
|
|
12
|
+
/** 需要 review_scope 覆盖回执的角色。 */
|
|
13
|
+
const REVIEW_SCOPE_ROLE = {
|
|
14
|
+
...REVIEWER_ROLE,
|
|
15
|
+
verifier: true,
|
|
16
|
+
};
|
|
17
|
+
/** 审查/验证类角色:报告需要 review_scope 覆盖回执。 */
|
|
18
|
+
export function isReviewRole(role) {
|
|
19
|
+
return REVIEW_SCOPE_ROLE[role] === true;
|
|
20
|
+
}
|
|
21
|
+
/** 需要 reviewer.kind/id 的角色。 */
|
|
22
|
+
export function requiresReviewer(role) {
|
|
23
|
+
return REVIEWER_ROLE[role] === true;
|
|
24
|
+
}
|
|
25
|
+
/** 需要 review_scope 覆盖回执的工作项。 */
|
|
26
|
+
export function requiresReviewScope(job) {
|
|
27
|
+
return job.boundFiles.length > 0 && isReviewRole(job.role);
|
|
28
|
+
}
|
|
29
|
+
/** 提交校验与契约共用的覆盖类提示消息:改这里即同时影响两侧。 */
|
|
30
|
+
export const COVERAGE_MESSAGES = {
|
|
31
|
+
codeReviewScopeMissing: "代码审查报告缺少覆盖范围字段 review_scope",
|
|
32
|
+
reviewScopeMissing: "审查报告缺少覆盖范围字段 review_scope",
|
|
33
|
+
checkedPathsNotStringArray: "代码审查覆盖范围里的 checked_paths 必须是字符串数组",
|
|
34
|
+
reviewCheckedPathsNotStringArray: "审查报告覆盖范围里的 checked_paths 必须是字符串数组",
|
|
35
|
+
checkedDocsNotStringArray: "代码审查覆盖范围里的 checked_docs 必须是字符串数组",
|
|
36
|
+
uncheckedNotArray: "代码审查覆盖范围里的 unchecked 必须是数组",
|
|
37
|
+
uncheckedItemNotObject: "代码审查覆盖范围里的未检查项必须是包含 path/reason 的对象",
|
|
38
|
+
passWithUncheckedBoundFile: "代码审查结论为 pass 时不能包含未检查的绑定文件",
|
|
39
|
+
};
|
|
40
|
+
const FINDING_TYPES = ["implementation", "spec", "mixed"];
|
|
41
|
+
const CLAIM_KINDS = ["missing_approved", "breaks_existing", "unjustified_addition"];
|
|
42
|
+
const SUGGESTED_ACTIONS = ["apply", "propose"];
|
|
43
|
+
/**
|
|
44
|
+
* 工作项报告契约:只描述形状与取值。模型需要的解释性文字见 packet 的
|
|
45
|
+
* `output_instructions` 与 `字段说明`;判据由 record.ts 执行。
|
|
46
|
+
*/
|
|
47
|
+
export function reportSchemaForJob(job) {
|
|
48
|
+
const isCodeReviewer = job.role === "code-reviewer";
|
|
49
|
+
const hasScope = requiresReviewScope(job);
|
|
50
|
+
const fields = {
|
|
51
|
+
role: { type: "string", required: true, values: [job.role] },
|
|
52
|
+
verdict: { type: "string", required: true, values: ["pass", "fail"] },
|
|
53
|
+
findings: { type: "array", required: true },
|
|
54
|
+
summary: { type: "string" },
|
|
55
|
+
evidence_refs: { type: "array" },
|
|
56
|
+
risks: { type: "array" },
|
|
57
|
+
open_questions: { type: "array" },
|
|
58
|
+
};
|
|
59
|
+
const conditions = ["verdict 只能是 pass 或 fail。"];
|
|
60
|
+
if (requiresReviewer(job.role)) {
|
|
61
|
+
fields.reviewer = { type: "object", required: true };
|
|
62
|
+
fields["reviewer.kind"] = { type: "string", required: true, values: REVIEWER_KINDS };
|
|
63
|
+
fields["reviewer.id"] = { type: "string", required: true };
|
|
64
|
+
}
|
|
65
|
+
if (isCodeReviewer) {
|
|
66
|
+
fields.review_scope = { type: "object", required: true };
|
|
67
|
+
fields["review_scope.job_id"] = { type: "string", required: true, values: [job.job_id] };
|
|
68
|
+
fields["review_scope.packet_digest"] = { type: "string", required: true, values: [job.packet_digest] };
|
|
69
|
+
fields["review_scope.checked_paths"] = { type: "array", required: true, item: "path" };
|
|
70
|
+
fields["review_scope.checked_docs"] = { type: "array", required: true, item: "path" };
|
|
71
|
+
fields["review_scope.unchecked"] = { type: "array", required: true, item: "{path, reason}" };
|
|
72
|
+
fields["findings[blocking=true].id"] = { type: "string", required: true, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$" };
|
|
73
|
+
fields["findings[blocking=true].type"] = { type: "string", required: true, values: FINDING_TYPES };
|
|
74
|
+
fields["findings[blocking=true].claim_kind"] = { type: "string", required: true, values: CLAIM_KINDS };
|
|
75
|
+
fields["findings[blocking=true].suggested_action"] = { type: "string", required: true, values: SUGGESTED_ACTIONS };
|
|
76
|
+
fields["findings[blocking=true].source_refs"] = { type: "array", required: true, item: "path:line" };
|
|
77
|
+
fields["findings[blocking=true].approved_refs"] = { type: "array", item: "已批准锚点" };
|
|
78
|
+
for (const field of ["description", "evidence", "impact"]) {
|
|
79
|
+
fields[`findings[blocking=true].${field}`] = { type: "string", required: true };
|
|
80
|
+
}
|
|
81
|
+
conditions.push("verdict=pass 时不得存在 blocking:true 的问题。", "verdict=pass 时 unchecked 中不得包含绑定文件;范围外观察写 unchecked 或 risks 都不会导致拒收。", "checked_paths 与 unchecked[].path 合起来必须覆盖全部绑定文件。", "verdict=fail 时必须给出至少一个字段完整的 blocking 问题,字段缺失会被判为不可处理报告。", "claim_kind=missing_approved 且 suggested_action=apply 时,approved_refs 必须含可解析的 TEST 或 spec Requirement;缺口属于计划或验收本身时,改用 type=mixed 且 suggested_action=propose。", "提交时引擎会比对冻结范围与当前代码状态:绑定文件已变化会被判为过期报告,需等 next 重建工作项,不要补写指纹。");
|
|
82
|
+
}
|
|
83
|
+
else if (hasScope) {
|
|
84
|
+
fields.review_scope = { type: "object", required: true };
|
|
85
|
+
fields["review_scope.checked_paths"] = { type: "array", required: true, item: "path" };
|
|
86
|
+
conditions.push("checked_paths 必须覆盖全部绑定文件。");
|
|
87
|
+
}
|
|
88
|
+
if (job.role !== "code-reviewer" && job.role !== "verifier") {
|
|
89
|
+
conditions.push("verdict=fail 时 findings 至少包含一个问题。");
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
role: job.role,
|
|
93
|
+
required_fields: isCodeReviewer
|
|
94
|
+
? [...REVIEW_REPORT_REQUIRED_FIELDS, "reviewer", "review_scope"]
|
|
95
|
+
: [
|
|
96
|
+
...REVIEW_REPORT_REQUIRED_FIELDS,
|
|
97
|
+
...(requiresReviewer(job.role) ? ["reviewer"] : []),
|
|
98
|
+
...(hasScope ? ["review_scope"] : []),
|
|
99
|
+
],
|
|
100
|
+
optional_fields: [...REVIEW_REPORT_OPTIONAL_FIELDS],
|
|
101
|
+
fields,
|
|
102
|
+
conditions,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 可直接填写的报告骨架。
|
|
107
|
+
*
|
|
108
|
+
* 只预填工作项常量(job_id / packet_digest)与空数组:code-reviewer 的 checked_paths
|
|
109
|
+
* 必须由审查者按实际浏览填写,预填会架空覆盖回执的意义。
|
|
110
|
+
* 普通 reviewer / verifier 的 checked_paths 仍按既有协议预填全部绑定文件。
|
|
111
|
+
*/
|
|
112
|
+
export function reportSkeletonForJob(job) {
|
|
113
|
+
const skeleton = {
|
|
114
|
+
role: job.role,
|
|
115
|
+
verdict: "pass",
|
|
116
|
+
findings: [],
|
|
117
|
+
};
|
|
118
|
+
if (requiresReviewer(job.role)) {
|
|
119
|
+
skeleton.reviewer = { kind: "subagent", id: "<agent-id>" };
|
|
120
|
+
}
|
|
121
|
+
if (job.role === "code-reviewer") {
|
|
122
|
+
skeleton.review_scope = {
|
|
123
|
+
job_id: job.job_id,
|
|
124
|
+
packet_digest: job.packet_digest,
|
|
125
|
+
checked_paths: [],
|
|
126
|
+
checked_docs: [],
|
|
127
|
+
unchecked: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
else if (requiresReviewScope(job)) {
|
|
131
|
+
skeleton.review_scope = { checked_paths: job.boundFiles.map(file => file.path) };
|
|
132
|
+
}
|
|
133
|
+
return skeleton;
|
|
134
|
+
}
|
|
135
|
+
/** 骨架中必须由审查者替换的空值,供 packet 与 CLI 给出同一份填写提示。 */
|
|
136
|
+
export function reportSkeletonFillItems(job) {
|
|
137
|
+
const items = ["verdict", "findings"];
|
|
138
|
+
if (requiresReviewer(job.role))
|
|
139
|
+
items.push("reviewer.id");
|
|
140
|
+
if (job.role === "code-reviewer") {
|
|
141
|
+
items.push("review_scope.checked_paths", "review_scope.checked_docs", "review_scope.unchecked");
|
|
142
|
+
}
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
/** 报告里声明"未检查"且不属于绑定文件的条目(pass 的置信边界,供事件流与门禁透出)。 */
|
|
146
|
+
export function outOfScopeUncheckedFromReport(report, boundFiles) {
|
|
147
|
+
const scope = report?.review_scope;
|
|
148
|
+
if (!scope || !Array.isArray(scope.unchecked))
|
|
149
|
+
return [];
|
|
150
|
+
const bound = new Set(boundFiles.map(file => file.path));
|
|
151
|
+
const items = [];
|
|
152
|
+
for (const raw of scope.unchecked) {
|
|
153
|
+
const item = raw;
|
|
154
|
+
if (typeof item?.path !== "string" || typeof item?.reason !== "string")
|
|
155
|
+
continue;
|
|
156
|
+
if (bound.has(item.path))
|
|
157
|
+
continue;
|
|
158
|
+
items.push({ path: item.path, reason: item.reason });
|
|
159
|
+
}
|
|
160
|
+
return items;
|
|
161
|
+
}
|
package/dist/transition.js
CHANGED
|
@@ -6,7 +6,7 @@ import { rebuildSnapshot } from "./sync.js";
|
|
|
6
6
|
import { requiredJobActions } from "./job_action.js";
|
|
7
7
|
import { assertCommitPayloadExtension, isFreshReviewVerifier, isReviewReadyVerifier, latestReviewHistoryForGateRole, readReviewPolicyFromEvents, reviewBoundFiles, reviewEvidenceDigest, reviewPolicyForRisk, REVIEW_DOC_PATHS, } from "./review.js";
|
|
8
8
|
import { REVIEW_CODE_REVIEW_GATE_ID, REVIEW_FINAL_VERIFIER_GATE, REVIEW_FINAL_VERIFIER_GATE_ID, reviewScopeForGateRole, } from "./review_job_gates.js";
|
|
9
|
-
import { codeReviewBoundFiles, codeReviewDecisionScope, codeReviewJobStaleReason, codeReviewPacketContext, codeReviewPacketDigest, collectCodeReviewGateFacts, computeCodeStateCheck, currentCodeReviewWorkingPaths, dismissedCodeReviewSummary, effectiveCoverageExemptionRefsFromEvents, latestCodeReviewGateEvidence, latestCodeReviewDecision, latestCodeReviewFailedStatus, missingCoverageExemptionTestIds, requiresFinalVerifierForCurrentReview, scanCodeChangesForReview, taskExecutionIndexForReview, codeReviewFindingNeedsUserDecision, isReviewFixCapReached, } from "./code_review.js";
|
|
9
|
+
import { codeReviewBoundFiles, codeReviewDecisionScope, codeReviewJobStaleReason, outOfScopeUncheckedForJob, codeReviewPacketContext, codeReviewPacketDigest, collectCodeReviewGateFacts, computeCodeStateCheck, currentCodeReviewWorkingPaths, dismissedCodeReviewSummary, effectiveCoverageExemptionRefsFromEvents, latestCodeReviewGateEvidence, latestCodeReviewDecision, latestCodeReviewFailedStatus, missingCoverageExemptionTestIds, requiresFinalVerifierForCurrentReview, scanCodeChangesForReview, taskExecutionIndexForReview, codeReviewFindingNeedsUserDecision, isReviewFixCapReached, } from "./code_review.js";
|
|
10
10
|
import { taskEvidenceReadiness } from "./task_evidence.js";
|
|
11
11
|
import { adoptedContractForTask, findTaskInLines, isFixTaskId, parseTasksMd, parseTestContractEntries, } from "./format.js";
|
|
12
12
|
import { isCodeReviewClaimKind, reviewFixReason } from "./approved_ref.js";
|
|
@@ -552,6 +552,10 @@ function evaluateApplyDoneCodeReviewGate(input) {
|
|
|
552
552
|
job_id: latest.job.job_id,
|
|
553
553
|
packet_digest: latest.job.packet_digest,
|
|
554
554
|
current_head: acceptedScope.current_head,
|
|
555
|
+
// pass 放行了范围外未检查项;把置信边界写进门禁事实,供最终验证与使用方读取。
|
|
556
|
+
...(outOfScopeUncheckedForJob(input.events, latest.job.job_id).length > 0
|
|
557
|
+
? { out_of_scope_unchecked: outOfScopeUncheckedForJob(input.events, latest.job.job_id) }
|
|
558
|
+
: {}),
|
|
555
559
|
},
|
|
556
560
|
},
|
|
557
561
|
};
|
|
@@ -1125,6 +1129,8 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1125
1129
|
if (opts.reviewFix) {
|
|
1126
1130
|
if (to !== "apply")
|
|
1127
1131
|
return { skip: true, message: "--review-fix 只能用于回到实现阶段(reopen --to apply)" };
|
|
1132
|
+
// 无需 invalidateOpenJobs:--review-fix 的引用来自已终结的代码审查问题,此时 open_jobs
|
|
1133
|
+
// 通常为空;即使残留过期的工作项,也会被 apply_done 侧的新鲜度过滤与重建消化,不构成死锁。
|
|
1128
1134
|
if (snapshot.state !== "apply_done")
|
|
1129
1135
|
return { skip: true, message: `当前状态 ${snapshot.state},不能通过代码审查修复回到实现阶段` };
|
|
1130
1136
|
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
@@ -1290,6 +1296,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1290
1296
|
toState: "apply",
|
|
1291
1297
|
outcome: "advanced",
|
|
1292
1298
|
reason: `${reason.trim()}(pending tasks: ${pending.join(", ")})`,
|
|
1299
|
+
// 回到 Apply 意味着代码会被改动:仍在等待报告的审查工作项依据已失效,
|
|
1300
|
+
// 在此显式作废,避免审查者继续投入后才发现报告过期。
|
|
1301
|
+
extraEvents: invalidateOpenJobs(snapshot, "apply", reason.trim()),
|
|
1293
1302
|
};
|
|
1294
1303
|
},
|
|
1295
1304
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -102,6 +102,11 @@ export interface CodeReviewGateEvidence {
|
|
|
102
102
|
job_id: string | null;
|
|
103
103
|
packet_digest: string | null;
|
|
104
104
|
reason?: "no_code_changes";
|
|
105
|
+
/** pass 结论下的范围外未检查项:门禁事实的一部分,供最终验证与使用方判断置信边界。 */
|
|
106
|
+
out_of_scope_unchecked?: {
|
|
107
|
+
path: string;
|
|
108
|
+
reason: string;
|
|
109
|
+
}[];
|
|
105
110
|
event_id: string;
|
|
106
111
|
event_digest: string;
|
|
107
112
|
}
|
|
@@ -166,6 +171,8 @@ export interface JobPacket {
|
|
|
166
171
|
report_file_path?: string;
|
|
167
172
|
output_contract_fields?: string[];
|
|
168
173
|
output_contract_optional_fields?: string[];
|
|
174
|
+
/** 按本工作项预填的报告骨架;完整契约见 `superspec jobs contract`。 */
|
|
175
|
+
report_skeleton?: Record<string, unknown>;
|
|
169
176
|
字段说明?: Record<string, string>;
|
|
170
177
|
output_instructions?: string;
|
|
171
178
|
stop_conditions: string[];
|
package/package.json
CHANGED
|
@@ -10,5 +10,5 @@ Task binding: load `.codex/prompts/architect.md` first, then read the current jo
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Do not edit files or judge materials you have not opened. Report missing context upward instead of guessing.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise put the conclusion first, cite file:line evidence, and write `无阻塞问题` when no blocking issue is found.
|
|
14
14
|
"""
|
|
@@ -10,5 +10,5 @@ Task binding: load `.codex/prompts/code-reviewer.md` first, then read the curren
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Do not implement fixes, write evidence, mark tasks complete, decide GREEN, reopen, accept, or replace main-thread workflow decisions. Start from packet-provided materials and report missing context upward instead of guessing.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Blocking issues must be traceable and actionable. Write `无阻塞问题` when no blocking issue is found.
|
|
14
14
|
"""
|
|
@@ -10,5 +10,5 @@ Task binding: load `.codex/prompts/critic.md` first, then read the current job p
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only by default. Do not edit files, invent issues, or widen scope silently. Report missing source refs or claim gaps upward.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise state pass or reject first, distinguish defects from proof gaps and residual risk, and cite concrete evidence.
|
|
14
14
|
"""
|
|
@@ -10,5 +10,5 @@ Task binding: load `.codex/prompts/test-engineer.md` first, then read the curren
|
|
|
10
10
|
|
|
11
11
|
Boundary: review jobs are read-only. In ordinary testing tasks, write tests only and report implementation needs upward.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise list coverage gaps, suggested tests, fresh validation commands, unverifiable items, and residual risk.
|
|
14
14
|
"""
|
|
@@ -8,7 +8,7 @@ Role: Test Runner. Execute exactly one SuperSpec apply test phase from the curre
|
|
|
8
8
|
|
|
9
9
|
Task binding: load `.codex/prompts/test-runner.md` first, then read the current task instructions. Their task id, test id, phase, allowed command, expected semantic status, guard fingerprint, report policy, and stop conditions override static prompt memory.
|
|
10
10
|
|
|
11
|
-
Boundary: read-only by default. Do not edit production code, OpenSpec artifacts, `.superspec/**`, task checkboxes, evidence, review reports, or archives. Run only the allowed command from current task instructions and report blockers for missing command, unsafe side effects, or incomplete raw transcript refs.
|
|
11
|
+
Boundary: read-only by default. Do not edit production code, OpenSpec artifacts, `.superspec/**`, task checkboxes, evidence, review reports, or archives. Run only the allowed command from current task instructions and report blockers for missing command, unsafe side effects, or incomplete raw transcript refs. One run may back several declared TEST ids: register evidence per TEST id, and do not re-run the same command for a second registration.
|
|
12
12
|
|
|
13
13
|
Output: concise Simplified Chinese test report with command, cwd, phase, task/test id, exit status, semantic status candidate, result summary, raw transcript ref, repo head, dirty-state summary, guard fingerprint, and unverified items.
|
|
14
14
|
"""
|
|
@@ -10,5 +10,5 @@ Task binding: load `.codex/prompts/verifier.md` first, then read the current job
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Check commands, test output, artifacts, evidence refs, acceptance criteria, code-reviewer closure, and whether the verifier job still matches the packet-provided evidence version. Use diffs only as evidence references when the packet requires them. Do not edit files, write evidence, mark tasks complete, or add an extra code-diff blocker outside the packet contract.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. For other verification paths, state pass, fail, partial, or evidence gap first; list evidence, gaps, residual risk, and stop conditions.
|
|
14
14
|
"""
|
|
@@ -10,4 +10,4 @@ Task binding: read the current SuperSpec job packet and task instructions first.
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Do not edit files or judge materials you have not opened. Report missing context upward instead of guessing.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise put the conclusion first, cite file:line evidence, and write `无阻塞问题` when no blocking issue is found.
|
|
@@ -6,8 +6,8 @@ tools: read, grep, glob, bash
|
|
|
6
6
|
|
|
7
7
|
Role: Code Reviewer. Check that approved behaviors landed with minimal extra semantics. Report missing approved results or unjustified additions; do not mint required work outside the approved plan.
|
|
8
8
|
|
|
9
|
-
Task binding: read the current SuperSpec job packet and task instructions first. The job packet is the runtime contract; follow it over this prompt, including any previous rejection it asks you to correct.
|
|
9
|
+
Task binding: read the current SuperSpec job packet and task instructions first. The job packet is the runtime contract; follow it over this prompt, including any previous rejection it asks you to correct. Report structure comes from the packet's `report_skeleton` (full contract via `superspec jobs contract`); never invent structure or reuse a previous round's report.
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Do not implement fixes, write evidence, mark tasks complete, decide GREEN, reopen, accept, or replace main-thread workflow decisions. Start from packet-provided materials and report missing context upward instead of guessing.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Blocking issues must be traceable and actionable. Write `无阻塞问题` when no blocking issue is found.
|
|
@@ -10,4 +10,4 @@ Task binding: read the current SuperSpec job packet and task instructions first.
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only by default. Do not edit files, invent issues, or widen scope silently. Report missing source refs or claim gaps upward. Undeclared theoretical risks are residual, not blockers.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise state pass or reject first, distinguish defects from proof gaps and residual risk, and cite concrete evidence.
|
|
@@ -10,4 +10,4 @@ Task binding: read the current SuperSpec job packet and task instructions first.
|
|
|
10
10
|
|
|
11
11
|
Boundary: review jobs are read-only. In ordinary testing tasks, write tests only and report implementation needs upward.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. Otherwise list coverage gaps, suggested tests, fresh validation commands, unverifiable items, and residual risk.
|
|
@@ -8,6 +8,6 @@ Role: Test Runner. Execute exactly one SuperSpec apply test phase from the curre
|
|
|
8
8
|
|
|
9
9
|
Task binding: read the current SuperSpec task instructions first. Their task id, test id, phase, allowed command, expected semantic status, guard fingerprint, report policy, and stop conditions override this prompt.
|
|
10
10
|
|
|
11
|
-
Boundary: read-only by default. Do not edit production code, OpenSpec artifacts, `.superspec/**`, task checkboxes, evidence, review reports, or archives. Run only the allowed command from current task instructions and report blockers for missing command, unsafe side effects, or incomplete raw transcript refs.
|
|
11
|
+
Boundary: read-only by default. Do not edit production code, OpenSpec artifacts, `.superspec/**`, task checkboxes, evidence, review reports, or archives. Run only the allowed command from current task instructions and report blockers for missing command, unsafe side effects, or incomplete raw transcript refs. One run may back several declared TEST ids: register evidence per TEST id, and do not re-run the same command for a second registration.
|
|
12
12
|
|
|
13
13
|
Output: concise Simplified Chinese test report with command, cwd, phase, task/test id, exit status, semantic status candidate, result summary, raw transcript ref, repo head, dirty-state summary, guard fingerprint, and unverified items.
|
|
@@ -10,4 +10,4 @@ Task binding: read the current SuperSpec job packet and task instructions first.
|
|
|
10
10
|
|
|
11
11
|
Boundary: read-only. Check commands, test output, artifacts, evidence refs, acceptance criteria, code-reviewer closure, and whether the verifier job still matches the packet-provided evidence version. Use diffs only as evidence references when the packet requires them. Do not edit files, write evidence, mark tasks complete, or add an extra code-diff blocker outside the packet contract.
|
|
12
12
|
|
|
13
|
-
Output: concise Simplified Chinese. For JSON reports, follow the packet's
|
|
13
|
+
Output: concise Simplified Chinese. For JSON reports, follow the packet's `report_skeleton` (full contract via `superspec jobs contract`) exactly. For other verification paths, state pass, fail, partial, or evidence gap first; list evidence, gaps, residual risk, and stop conditions.
|