@peterxiaoyang/superspec 0.1.47 → 0.1.48
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 +52 -8
- package/dist/explore_round.d.ts +3 -1
- package/dist/explore_round.js +48 -3
- package/dist/format.d.ts +28 -1
- package/dist/format.js +121 -4
- package/dist/next.d.ts +1 -1
- package/dist/next.js +97 -12
- package/dist/phase_confirmation.js +3 -1
- package/dist/phase_plan.d.ts +21 -1
- package/dist/phase_plan.js +151 -35
- package/dist/propose_round.d.ts +15 -0
- package/dist/propose_round.js +137 -0
- package/dist/record.js +133 -11
- package/dist/review_job_gates.d.ts +1 -1
- package/dist/review_job_gates.js +12 -4
- package/dist/skill_loop.js +20 -0
- package/dist/transition.js +31 -4
- package/dist/types.d.ts +49 -1
- package/dist/workflow_profile.js +1 -1
- package/package.json +7 -1
- package/templates/workflow/AGENTS.md +2 -0
- package/templates/workflow/prompts/architect.md +2 -0
- package/templates/workflow/prompts/critic.md +11 -2
- package/templates/workflow/prompts/test-engineer.md +1 -0
- package/templates/workflow/skills/superspec-apply/SKILL.md +7 -3
- package/templates/workflow/skills/superspec-explore/SKILL.md +7 -3
- package/templates/workflow/skills/superspec-propose/SKILL.md +16 -4
package/dist/record.js
CHANGED
|
@@ -12,7 +12,8 @@ import { REVIEW_DOC_PATHS, REVIEW_REJECTION_OVERRIDE_SCOPE_PREFIX, REVIEW_REJECT
|
|
|
12
12
|
import { EXPLORE_DISCOVERY_REVIEW_GATE, PROPOSE_FINAL_REVIEW_GATE } from "./review_job_gates.js";
|
|
13
13
|
import { RecordInputDecodingError, readRecordInputFile } from "./record_input.js";
|
|
14
14
|
import { currentExploreRoundId } from "./explore_round.js";
|
|
15
|
-
import {
|
|
15
|
+
import { currentProposeOpenQuestion, currentProposeQuestionContent, currentProposeRoundId, } from "./propose_round.js";
|
|
16
|
+
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";
|
|
16
17
|
const REVIEW_REPORT_REQUIRED_FIELDS = ["role", "verdict", "findings"];
|
|
17
18
|
const REVIEW_REPORT_OPTIONAL_FIELDS = ["summary", "evidence_refs", "risks", "open_questions"];
|
|
18
19
|
const REVIEWER_KINDS = new Set(["codex-subagent", "human", "external-agent"]);
|
|
@@ -73,7 +74,7 @@ function previousRejectionInstruction(job) {
|
|
|
73
74
|
const identityRule = job.role === "code-reviewer"
|
|
74
75
|
? "同一问题仍存在时复用原 finding ID;legacy finding 没有 ID 时沿用原始语义并补一个稳定 ID;"
|
|
75
76
|
: "已解决或已由等价证据闭环的问题不要重复报告,不得通过更换标题或措辞重复同一问题;";
|
|
76
|
-
return `${reason}本轮是修复复核:逐项判断本工作项附带的上一次同角色 finding 是否仍成立。Finding 中的 recommendation 只是非绑定建议,不是需求或验收标准;先独立核对 underlying problem
|
|
77
|
+
return `${reason}本轮是修复复核:逐项判断本工作项附带的上一次同角色 finding 是否仍成立。Finding 中的 recommendation 只是非绑定建议,不是需求或验收标准;先独立核对 underlying problem、直接证据和本次验收,不得因原建议指定了某种架构就要求照做。修正不得通过缩小已确认范围、改写用户决定或删除验收来让 finding 字面消失;这类偏离属于本次修正直接引入的回归。${identityRule}默认只复核历史 finding;新 blocker 仅允许是本次修正直接引入的回归,并必须说明“修正动作 → 新问题”的因果链,不得展开无关的故障模型、消费者或架构议题。`;
|
|
77
78
|
}
|
|
78
79
|
function reviewScopeForJob(job) {
|
|
79
80
|
if (job.review_targets !== undefined || job.read_only_refs !== undefined) {
|
|
@@ -132,12 +133,21 @@ function currentDiscoveryOpenQuestion(projectRoot, change) {
|
|
|
132
133
|
return null;
|
|
133
134
|
return parseDiscoveryOpenQuestions(readFileSync(discoveryPath, "utf8"))[0] ?? null;
|
|
134
135
|
}
|
|
135
|
-
function latestAcceptedExploreOpenQuestionDecision(events,
|
|
136
|
+
function latestAcceptedExploreOpenQuestionDecision(events, scopes, identity) {
|
|
136
137
|
return [...events].reverse().find(event => {
|
|
137
138
|
if (event.event_type !== "user_decision_recorded")
|
|
138
139
|
return false;
|
|
139
140
|
const payload = event.payload;
|
|
140
|
-
|
|
141
|
+
if (payload.accepted === false)
|
|
142
|
+
return false;
|
|
143
|
+
const recorded = payload.explore_open_question;
|
|
144
|
+
if (recorded && typeof recorded.decision_basis_digest === "string") {
|
|
145
|
+
return recorded.round_id === identity.roundId &&
|
|
146
|
+
recorded.question_id === identity.questionId &&
|
|
147
|
+
(!identity.questionId.startsWith("item-") || recorded.question_ordinal === identity.questionOrdinal) &&
|
|
148
|
+
recorded.decision_basis_digest === identity.decisionBasisDigest;
|
|
149
|
+
}
|
|
150
|
+
return typeof payload.scope === "string" && scopes.includes(payload.scope);
|
|
141
151
|
}) ?? null;
|
|
142
152
|
}
|
|
143
153
|
function isExploreOpenQuestionScope(scope) {
|
|
@@ -172,6 +182,52 @@ function invalidExploreOpenQuestionResult(projectRoot, change, inputDigest, exis
|
|
|
172
182
|
: "需要确认的事项已变化或已完成,请重新执行 next 获取当前事项",
|
|
173
183
|
};
|
|
174
184
|
}
|
|
185
|
+
function latestAcceptedProposeOpenQuestionDecision(events, scopes, identity) {
|
|
186
|
+
return [...events].reverse().find(event => {
|
|
187
|
+
if (event.event_type !== "user_decision_recorded")
|
|
188
|
+
return false;
|
|
189
|
+
const payload = event.payload;
|
|
190
|
+
if (payload.accepted === false)
|
|
191
|
+
return false;
|
|
192
|
+
const recorded = payload.propose_open_question;
|
|
193
|
+
if (recorded && typeof recorded.decision_basis_digest === "string") {
|
|
194
|
+
return recorded.round_id === identity.roundId &&
|
|
195
|
+
recorded.path === identity.path &&
|
|
196
|
+
recorded.question_id === identity.questionId &&
|
|
197
|
+
(!identity.questionId.startsWith("item-") || recorded.question_ordinal === identity.questionOrdinal) &&
|
|
198
|
+
recorded.decision_basis_digest === identity.decisionBasisDigest;
|
|
199
|
+
}
|
|
200
|
+
return typeof payload.scope === "string" && scopes.includes(payload.scope);
|
|
201
|
+
}) ?? null;
|
|
202
|
+
}
|
|
203
|
+
function isProposeOpenQuestionScope(scope) {
|
|
204
|
+
return scope.startsWith(PROPOSE_OPEN_QUESTION_SCOPE_PREFIX);
|
|
205
|
+
}
|
|
206
|
+
function isWellFormedProposeOpenQuestionScope(scope) {
|
|
207
|
+
return /^propose_open_question:sha256:[a-f0-9]{64}:(?:DEC-[A-Za-z0-9][A-Za-z0-9_-]*|item-[1-9]\d*)$/.test(scope);
|
|
208
|
+
}
|
|
209
|
+
function invalidProposeOpenQuestionResult(projectRoot, change, inputDigest, existing, decision, reason) {
|
|
210
|
+
const existingPayload = existing?.payload;
|
|
211
|
+
if (existingPayload?.accepted === false && existingPayload.reason === reason) {
|
|
212
|
+
return { event_type: "user_decision_recorded", accepted: false, message: "幂等返回:同一无效设计决定答复已登记" };
|
|
213
|
+
}
|
|
214
|
+
appendEvent(projectRoot, change, makeEvent(change, "user_decision_recorded", {
|
|
215
|
+
accepted: false,
|
|
216
|
+
scope: decision.scope,
|
|
217
|
+
answer: decision.answer,
|
|
218
|
+
reason,
|
|
219
|
+
input_digest: inputDigest,
|
|
220
|
+
}));
|
|
221
|
+
return {
|
|
222
|
+
event_type: "user_decision_recorded",
|
|
223
|
+
accepted: false,
|
|
224
|
+
message: reason === "invalid_propose_open_question_scope"
|
|
225
|
+
? "设计决定的内部标识无效,请重新执行 next 获取当前事项"
|
|
226
|
+
: reason === "propose_open_question_already_recorded"
|
|
227
|
+
? "这项设计决定已有答复,请先回写计划材料后重新执行 next"
|
|
228
|
+
: "需要确认的设计决定已变化或已完成,请重新执行 next 获取当前事项",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
175
231
|
function invalidCodeReviewDecisionResult(projectRoot, change, inputDigest, existing, decision, reason, message) {
|
|
176
232
|
const existingPayload = existing?.payload;
|
|
177
233
|
if (existingPayload?.accepted === false && existingPayload.reason === reason) {
|
|
@@ -704,20 +760,31 @@ function recordUserDecisionLoaded(projectRoot, change, events, content, inputDig
|
|
|
704
760
|
}
|
|
705
761
|
// Explore 的用户答复只能绑定当前文档顺序中的第一项。这里不判断答案是否
|
|
706
762
|
// “正确”,只机械校验当前项、Discovery 决策上下文和 Explore 轮次仍与 next
|
|
707
|
-
//
|
|
763
|
+
// 返回时一致。决定身份绑定问题项中明确写出的 basis;其它文档内容不参与当前
|
|
764
|
+
// scope,避免无关材料编辑触发重复提问。
|
|
708
765
|
let exploreOpenQuestion = null;
|
|
709
766
|
let exploreOpenQuestionRoundId = null;
|
|
710
767
|
let exploreOpenQuestionContextFingerprint = null;
|
|
768
|
+
let exploreOpenQuestionBasisDigest = null;
|
|
711
769
|
if (isExploreOpenQuestionScope(decision.scope)) {
|
|
712
770
|
if (!isWellFormedExploreOpenQuestionScope(decision.scope)) {
|
|
713
771
|
return invalidExploreOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "invalid_explore_open_question_scope");
|
|
714
772
|
}
|
|
715
773
|
const current = currentDiscoveryOpenQuestion(projectRoot, change);
|
|
716
|
-
const
|
|
717
|
-
|
|
774
|
+
const exploreRoundId = currentExploreRoundId(events);
|
|
775
|
+
const expectedScopes = current
|
|
776
|
+
? [discoveryOpenQuestionScope(current, exploreRoundId), legacyDiscoveryOpenQuestionScope(current, exploreRoundId)]
|
|
777
|
+
: [];
|
|
778
|
+
if (!expectedScopes.includes(decision.scope)) {
|
|
718
779
|
return invalidExploreOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "stale_explore_open_question_scope");
|
|
719
780
|
}
|
|
720
|
-
const
|
|
781
|
+
const currentBasisDigest = current ? discoveryQuestionDecisionBasisDigest(current) : "";
|
|
782
|
+
const acceptedForCurrentScope = latestAcceptedExploreOpenQuestionDecision(events, expectedScopes, {
|
|
783
|
+
roundId: exploreRoundId,
|
|
784
|
+
questionId: current.id,
|
|
785
|
+
questionOrdinal: current.ordinal,
|
|
786
|
+
decisionBasisDigest: currentBasisDigest,
|
|
787
|
+
});
|
|
721
788
|
if (acceptedForCurrentScope) {
|
|
722
789
|
const previousAnswer = acceptedForCurrentScope.payload.answer;
|
|
723
790
|
if (previousAnswer === decision.answer) {
|
|
@@ -730,11 +797,52 @@ function recordUserDecisionLoaded(projectRoot, change, events, content, inputDig
|
|
|
730
797
|
return invalidExploreOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "explore_open_question_already_recorded");
|
|
731
798
|
}
|
|
732
799
|
exploreOpenQuestion = current;
|
|
733
|
-
exploreOpenQuestionRoundId =
|
|
800
|
+
exploreOpenQuestionRoundId = exploreRoundId;
|
|
734
801
|
const discoveryPath = join(openspecChangeRoot(projectRoot, change), ".superspec", "artifacts", "discovery.md");
|
|
735
802
|
exploreOpenQuestionContextFingerprint = current
|
|
736
803
|
? discoveryQuestionContextFingerprint(readFileSync(discoveryPath, "utf8"), current)
|
|
737
804
|
: null;
|
|
805
|
+
exploreOpenQuestionBasisDigest = currentBasisDigest;
|
|
806
|
+
}
|
|
807
|
+
let proposeOpenQuestion = null;
|
|
808
|
+
let proposeOpenQuestionRoundId = null;
|
|
809
|
+
let proposeOpenQuestionContextFingerprint = null;
|
|
810
|
+
let proposeOpenQuestionBasisDigest = null;
|
|
811
|
+
if (isProposeOpenQuestionScope(decision.scope)) {
|
|
812
|
+
if (!isWellFormedProposeOpenQuestionScope(decision.scope)) {
|
|
813
|
+
return invalidProposeOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "invalid_propose_open_question_scope");
|
|
814
|
+
}
|
|
815
|
+
const changeRoot = openspecChangeRoot(projectRoot, change);
|
|
816
|
+
const current = currentProposeOpenQuestion(changeRoot);
|
|
817
|
+
const proposeRoundId = currentProposeRoundId(events);
|
|
818
|
+
const expectedScopes = current
|
|
819
|
+
? [proposeOpenQuestionScope(current, proposeRoundId), legacyProposeOpenQuestionScope(current, proposeRoundId)]
|
|
820
|
+
: [];
|
|
821
|
+
if (!expectedScopes.includes(decision.scope)) {
|
|
822
|
+
return invalidProposeOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "stale_propose_open_question_scope");
|
|
823
|
+
}
|
|
824
|
+
const currentBasisDigest = current ? proposeQuestionDecisionBasisDigest(current) : "";
|
|
825
|
+
const acceptedForCurrentScope = latestAcceptedProposeOpenQuestionDecision(events, expectedScopes, {
|
|
826
|
+
roundId: proposeRoundId,
|
|
827
|
+
path: current.path,
|
|
828
|
+
questionId: current.id,
|
|
829
|
+
questionOrdinal: current.ordinal,
|
|
830
|
+
decisionBasisDigest: currentBasisDigest,
|
|
831
|
+
});
|
|
832
|
+
if (acceptedForCurrentScope) {
|
|
833
|
+
const previousAnswer = acceptedForCurrentScope.payload.answer;
|
|
834
|
+
if (previousAnswer === decision.answer) {
|
|
835
|
+
return { event_type: "user_decision_recorded", accepted: true, message: "幂等返回:同一用户决策已登记" };
|
|
836
|
+
}
|
|
837
|
+
return invalidProposeOpenQuestionResult(projectRoot, change, inputDigest, existing, { scope: decision.scope, answer: decision.answer }, "propose_open_question_already_recorded");
|
|
838
|
+
}
|
|
839
|
+
const content = current ? currentProposeQuestionContent(changeRoot, current) : null;
|
|
840
|
+
proposeOpenQuestion = current;
|
|
841
|
+
proposeOpenQuestionRoundId = proposeRoundId;
|
|
842
|
+
proposeOpenQuestionContextFingerprint = current && content
|
|
843
|
+
? proposeQuestionContextFingerprint(content, current)
|
|
844
|
+
: null;
|
|
845
|
+
proposeOpenQuestionBasisDigest = currentBasisDigest;
|
|
738
846
|
}
|
|
739
847
|
let phaseConfirmation = null;
|
|
740
848
|
let phaseAction = null;
|
|
@@ -915,7 +1023,9 @@ function recordUserDecisionLoaded(projectRoot, change, events, content, inputDig
|
|
|
915
1023
|
? phaseConfirmation.ask.question
|
|
916
1024
|
: exploreOpenQuestion
|
|
917
1025
|
? discoveryOpenQuestionDisplayText(exploreOpenQuestion)
|
|
918
|
-
:
|
|
1026
|
+
: proposeOpenQuestion
|
|
1027
|
+
? proposeOpenQuestionDisplayText(proposeOpenQuestion)
|
|
1028
|
+
: typeof decision.question === "string" ? decision.question : "",
|
|
919
1029
|
answer: phaseAction
|
|
920
1030
|
? phaseAction.label
|
|
921
1031
|
: normalizedAnswer ? codeReviewDecisionAnswerLabel(normalizedAnswer) : decision.answer,
|
|
@@ -923,13 +1033,25 @@ function recordUserDecisionLoaded(projectRoot, change, events, content, inputDig
|
|
|
923
1033
|
...(reviewRejectionDecisionSource ? { decision_source: reviewRejectionDecisionSource } : {}),
|
|
924
1034
|
...(reviewRejectionOverride ? { review_rejection_override: reviewRejectionOverride } : {}),
|
|
925
1035
|
...(codeReviewDecisionReference ? { code_review_decision: codeReviewDecisionReference } : {}),
|
|
926
|
-
...(exploreOpenQuestion && exploreOpenQuestionRoundId && exploreOpenQuestionContextFingerprint ? {
|
|
1036
|
+
...(exploreOpenQuestion && exploreOpenQuestionRoundId && exploreOpenQuestionContextFingerprint && exploreOpenQuestionBasisDigest ? {
|
|
927
1037
|
explore_open_question: {
|
|
928
1038
|
round_id: exploreOpenQuestionRoundId,
|
|
929
1039
|
question_id: exploreOpenQuestion.id,
|
|
930
1040
|
question_ordinal: exploreOpenQuestion.ordinal,
|
|
931
1041
|
document_fingerprint: exploreOpenQuestion.documentFingerprint,
|
|
932
1042
|
context_fingerprint: exploreOpenQuestionContextFingerprint,
|
|
1043
|
+
decision_basis_digest: exploreOpenQuestionBasisDigest,
|
|
1044
|
+
},
|
|
1045
|
+
} : {}),
|
|
1046
|
+
...(proposeOpenQuestion && proposeOpenQuestionRoundId && proposeOpenQuestionContextFingerprint && proposeOpenQuestionBasisDigest ? {
|
|
1047
|
+
propose_open_question: {
|
|
1048
|
+
round_id: proposeOpenQuestionRoundId,
|
|
1049
|
+
path: proposeOpenQuestion.path,
|
|
1050
|
+
question_id: proposeOpenQuestion.id,
|
|
1051
|
+
question_ordinal: proposeOpenQuestion.ordinal,
|
|
1052
|
+
document_fingerprint: proposeOpenQuestion.documentFingerprint,
|
|
1053
|
+
context_fingerprint: proposeOpenQuestionContextFingerprint,
|
|
1054
|
+
decision_basis_digest: proposeOpenQuestionBasisDigest,
|
|
933
1055
|
},
|
|
934
1056
|
} : {}),
|
|
935
1057
|
...(phaseAction ? {
|
|
@@ -15,7 +15,7 @@ export declare const EXPLORE_DISCOVERY_REVIEW_GATE_ID: "explore.discovery_review
|
|
|
15
15
|
export declare const PROPOSE_FINAL_REVIEW_GATE_ID: "propose.final_review";
|
|
16
16
|
export declare const REVIEW_CODE_REVIEW_GATE_ID: "review.code_review";
|
|
17
17
|
export declare const REVIEW_FINAL_VERIFIER_GATE_ID: "review.final_verifier";
|
|
18
|
-
export declare function reviewScopeForGateRole(gate: ReviewGateRule, role: JobRole): {
|
|
18
|
+
export declare function reviewScopeForGateRole(gate: ReviewGateRule, role: JobRole, requiredRoles?: readonly JobRole[]): {
|
|
19
19
|
reviewTargets: string[];
|
|
20
20
|
readOnlyRefs: string[];
|
|
21
21
|
boundPaths: string[];
|
package/dist/review_job_gates.js
CHANGED
|
@@ -26,14 +26,22 @@ function defaultReviewScope(gate) {
|
|
|
26
26
|
boundPaths: [...new Set([...gate.reviewTargets, ...gate.readOnlyRefs])],
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
export function reviewScopeForGateRole(gate, role) {
|
|
29
|
+
export function reviewScopeForGateRole(gate, role, requiredRoles = gate.allowedRoles) {
|
|
30
30
|
if (gate.gate_id !== PROPOSE_FINAL_REVIEW_GATE_ID)
|
|
31
31
|
return defaultReviewScope(gate);
|
|
32
32
|
if (role === "critic") {
|
|
33
|
-
const
|
|
33
|
+
const ownsDesign = !requiredRoles.includes("architect");
|
|
34
|
+
const ownsTestContract = !requiredRoles.includes("test-engineer");
|
|
35
|
+
const reviewTargets = [
|
|
36
|
+
"proposal.md",
|
|
37
|
+
"specs/",
|
|
38
|
+
...(ownsDesign ? ["design.md"] : []),
|
|
39
|
+
"tasks.md",
|
|
40
|
+
...(ownsTestContract ? [".superspec/artifacts/test-contract.md"] : []),
|
|
41
|
+
];
|
|
34
42
|
const readOnlyRefs = [
|
|
35
|
-
"design.md",
|
|
36
|
-
".superspec/artifacts/test-contract.md",
|
|
43
|
+
...(!ownsDesign ? ["design.md"] : []),
|
|
44
|
+
...(!ownsTestContract ? [".superspec/artifacts/test-contract.md"] : []),
|
|
37
45
|
".superspec/artifacts/discovery.md",
|
|
38
46
|
];
|
|
39
47
|
return {
|
package/dist/skill_loop.js
CHANGED
|
@@ -47,6 +47,26 @@ export function simulateLoop(nextFn, executeFn, maxSteps = 20) {
|
|
|
47
47
|
return { completed: false, steps, finalState: output.state, message: `工作项执行失败` };
|
|
48
48
|
}
|
|
49
49
|
break;
|
|
50
|
+
case "artifact_required":
|
|
51
|
+
step.action = "artifact";
|
|
52
|
+
step.detail = output.artifact.path;
|
|
53
|
+
steps.push(step);
|
|
54
|
+
return {
|
|
55
|
+
completed: false,
|
|
56
|
+
steps,
|
|
57
|
+
finalState: output.state,
|
|
58
|
+
message: `需要创建或更新工作流产物:${output.artifact.path}`,
|
|
59
|
+
};
|
|
60
|
+
case "material_update_required":
|
|
61
|
+
step.action = "material_update";
|
|
62
|
+
step.detail = output.errors.join(";");
|
|
63
|
+
steps.push(step);
|
|
64
|
+
return {
|
|
65
|
+
completed: false,
|
|
66
|
+
steps,
|
|
67
|
+
finalState: output.state,
|
|
68
|
+
message: `需要修正工作流材料:${output.errors.join(";")}`,
|
|
69
|
+
};
|
|
50
70
|
case "ask_user":
|
|
51
71
|
step.action = "ask";
|
|
52
72
|
step.detail = output.ask_user.question;
|
package/dist/transition.js
CHANGED
|
@@ -9,7 +9,7 @@ import { REVIEW_CODE_REVIEW_GATE_ID, REVIEW_FINAL_VERIFIER_GATE, REVIEW_FINAL_VE
|
|
|
9
9
|
import { codeReviewBoundFiles, codeReviewDecisionScope, codeReviewJobStaleReason, codeReviewPacketContext, codeReviewPacketDigest, collectCodeReviewGateFacts, computeCodeStateCheck, currentCodeReviewWorkingPaths, dismissedCodeReviewSummary, effectiveCoverageExemptionRefsFromEvents, latestCodeReviewGateEvidence, latestCodeReviewDecision, latestCodeReviewFailedStatus, missingCoverageExemptionTestIds, requiresFinalVerifierForCurrentReview, scanCodeChangesForReview, taskExecutionIndexForReview, } from "./code_review.js";
|
|
10
10
|
import { taskEvidenceReadiness } from "./task_evidence.js";
|
|
11
11
|
import { adoptedContractForTask, findTaskInLines, isFixTaskId, parseTasksMd, parseTestContractEntries, } from "./format.js";
|
|
12
|
-
import { applyRequirementModeForCurrentRound, executionRequirementVersionForCurrentRound, blockingJobsForApplyDone, executionPolicyForCurrentRound, formatPendingTaskMessage, latestAcceptedProposalBaseline, pendingTaskStatusForApply, planningValidationProfileForNewRound, planTransition, discoveryDocsBaseline, exploreAnswerRegistrationPayloadForChange, proposalDocsBaseline, } from "./phase_plan.js";
|
|
12
|
+
import { applyRequirementModeForCurrentRound, executionRequirementVersionForCurrentRound, blockingJobsForApplyDone, executionPolicyForCurrentRound, formatPendingTaskMessage, latestAcceptedProposalBaseline, pendingTaskStatusForApply, planningValidationProfileForNewRound, planTransition, discoveryDocsBaseline, exploreAnswerRegistrationPayloadForChange, proposeAnswerRegistrationPayloadForChange, proposalDocsBaseline, } from "./phase_plan.js";
|
|
13
13
|
import { latestAcceptedPhaseDecision, phaseConfirmationCommitPayload, phaseConfirmationForBoundary, phaseConfirmationMissingMessage, } from "./phase_confirmation.js";
|
|
14
14
|
import { currentGitHead, dirtyCodeFiles, stageProductionJavaFilesSince } from "./git_state.js";
|
|
15
15
|
import { workflowRiskForProject } from "./workflow_config.js";
|
|
@@ -17,9 +17,9 @@ let transitionSeq = 0;
|
|
|
17
17
|
function newTransitionId() { return `T-${Date.now()}-${++transitionSeq}`; }
|
|
18
18
|
let jobSeq = 0;
|
|
19
19
|
function newJobId(change, role) { return `JOB-${change.slice(0, 8)}-${role.slice(0, 4)}-${Date.now()}-${++jobSeq}`; }
|
|
20
|
-
function createReviewJobsForGate(state, gate, roles, changeRoot, change, reason, events) {
|
|
20
|
+
function createReviewJobsForGate(state, gate, roles, requiredRoles, changeRoot, change, reason, events) {
|
|
21
21
|
const newJobs = roles.map(role => {
|
|
22
|
-
const scope = reviewScopeForGateRole(gate, role);
|
|
22
|
+
const scope = reviewScopeForGateRole(gate, role, requiredRoles);
|
|
23
23
|
// 角色职责目标和显式 freshness 路径绑定时点指纹:单文件缺失使用 sha256:missing,目录缺失使用稳定空指纹。
|
|
24
24
|
const boundPaths = [...new Set(scope.boundPaths)];
|
|
25
25
|
const boundFiles = boundPaths
|
|
@@ -209,6 +209,28 @@ function compileRequiredEvidence(executionPolicy, testIds, requiresVerificationW
|
|
|
209
209
|
accepted_green_statuses: ["expected_success"],
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
|
+
function evidenceActionsForAttempt(change, attemptId, required) {
|
|
213
|
+
const testIds = required.test_ids.length > 0 ? required.test_ids : [undefined];
|
|
214
|
+
const statuses = [];
|
|
215
|
+
if (required.red_required)
|
|
216
|
+
statuses.push("expected_failure");
|
|
217
|
+
if (required.green_required)
|
|
218
|
+
statuses.push(required.accepted_green_statuses[0] ?? "expected_success");
|
|
219
|
+
return testIds.flatMap(testId => statuses.map(semanticStatus => ({
|
|
220
|
+
kind: "test_run",
|
|
221
|
+
...(testId ? { test_id: testId } : {}),
|
|
222
|
+
record_argv: ["superspec", "record", "test-run", "--change", change, "--input", "-"],
|
|
223
|
+
record_input: {
|
|
224
|
+
...(testId ? { test_id: testId } : {}),
|
|
225
|
+
attempt_id: attemptId,
|
|
226
|
+
command: null,
|
|
227
|
+
cwd: null,
|
|
228
|
+
exit_code: null,
|
|
229
|
+
semantic_status: semanticStatus,
|
|
230
|
+
},
|
|
231
|
+
required_fields: ["command", "cwd", "exit_code"],
|
|
232
|
+
})));
|
|
233
|
+
}
|
|
212
234
|
function hasRejectedReviewReadyVerifier(events) {
|
|
213
235
|
const reviewReadyVerifierIds = new Set();
|
|
214
236
|
for (const ev of events) {
|
|
@@ -627,7 +649,7 @@ function transitionPlanToDecision(snapshot, changeRoot, change, plan, events) {
|
|
|
627
649
|
case "blocked":
|
|
628
650
|
return { blocked: true, reason: plan.reason, jobs: plan.jobs, ...(plan.details ? { details: plan.details } : {}) };
|
|
629
651
|
case "create_gate_jobs":
|
|
630
|
-
return createReviewJobsForGate(snapshot.state, plan.gate, plan.roles, changeRoot, change, plan.reason, events);
|
|
652
|
+
return createReviewJobsForGate(snapshot.state, plan.gate, plan.roles, plan.requiredRoles, changeRoot, change, plan.reason, events);
|
|
631
653
|
case "advance":
|
|
632
654
|
return {
|
|
633
655
|
fromState: plan.fromState,
|
|
@@ -867,6 +889,9 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
|
867
889
|
...attempt,
|
|
868
890
|
...boundarySnapshotPayload(projectRoot),
|
|
869
891
|
};
|
|
892
|
+
const evidenceActions = requiredEvidence
|
|
893
|
+
? evidenceActionsForAttempt(change, attempt.attempt_id, requiredEvidence)
|
|
894
|
+
: null;
|
|
870
895
|
return {
|
|
871
896
|
fromState: "apply", toState: "apply", outcome: "advanced",
|
|
872
897
|
reason: `创建任务 ${taskId} 执行尝试`,
|
|
@@ -878,6 +903,7 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
|
878
903
|
contract: adopted.contract,
|
|
879
904
|
...(fix ? { fix } : {}),
|
|
880
905
|
...(requiredEvidence ? { required_evidence: requiredEvidence } : {}),
|
|
906
|
+
...(evidenceActions ? { evidence_actions: evidenceActions } : {}),
|
|
881
907
|
// legacy 轮统一标注历史模式(无论有无执行依据文本);契约轮无块时标 false(如 Fix task)
|
|
882
908
|
...(adopted.contract ? {} : { legacy_contract: !contractMode }),
|
|
883
909
|
},
|
|
@@ -969,6 +995,7 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
969
995
|
baseline_docs: proposalDocsBaseline(changeRoot),
|
|
970
996
|
planning_validation_version: 2,
|
|
971
997
|
planning_validation_profile: planningValidationProfileForNewRound(projectRoot),
|
|
998
|
+
...proposeAnswerRegistrationPayloadForChange(changeRoot),
|
|
972
999
|
},
|
|
973
1000
|
};
|
|
974
1001
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -168,7 +168,7 @@ export interface RequiredJobAction {
|
|
|
168
168
|
packet_command: string;
|
|
169
169
|
packet_argv: string[];
|
|
170
170
|
}
|
|
171
|
-
export type EventType = "transition_prepare" | "transition_commit" | "job_requested" | "job_invalidated" | "reopen" | "abandon" | "task_started" | "task_completed" | "task_abandoned" | "job_accepted" | "job_rejected" | "user_decision_recorded" | "test_run_recorded" | "task_activation_recorded" | "artifact_recorded";
|
|
171
|
+
export type EventType = "transition_prepare" | "transition_commit" | "job_requested" | "job_invalidated" | "reopen" | "abandon" | "task_started" | "task_completed" | "task_abandoned" | "job_accepted" | "job_rejected" | "user_question_presented" | "user_decision_recorded" | "test_run_recorded" | "task_activation_recorded" | "artifact_recorded";
|
|
172
172
|
export interface Event {
|
|
173
173
|
event_id: string;
|
|
174
174
|
event_type: EventType;
|
|
@@ -192,6 +192,9 @@ export type OpenSpecValidationProfile = {
|
|
|
192
192
|
export interface PlanningValidationProfile {
|
|
193
193
|
version: 2;
|
|
194
194
|
openspec: OpenSpecValidationProfile;
|
|
195
|
+
design?: {
|
|
196
|
+
schema_version: 1;
|
|
197
|
+
};
|
|
195
198
|
}
|
|
196
199
|
export interface TransitionCommitPayload {
|
|
197
200
|
transition: string;
|
|
@@ -318,6 +321,14 @@ export interface AskUser {
|
|
|
318
321
|
allowed_answers: string[];
|
|
319
322
|
scope: string;
|
|
320
323
|
actions?: AskUserAction[];
|
|
324
|
+
/** 自由文本问题的直接登记入口;固定选项继续使用 actions。 */
|
|
325
|
+
record_argv?: string[];
|
|
326
|
+
record_input?: {
|
|
327
|
+
scope: string;
|
|
328
|
+
question: string;
|
|
329
|
+
answer: null;
|
|
330
|
+
};
|
|
331
|
+
required_fields?: Array<"answer">;
|
|
321
332
|
}
|
|
322
333
|
export interface AcceptedMaterialFollowupContinuation {
|
|
323
334
|
kind: "accepted_material_followup";
|
|
@@ -331,6 +342,33 @@ export interface AcceptedMaterialFollowupContinuation {
|
|
|
331
342
|
};
|
|
332
343
|
plan_docs_changed_since_accept: boolean | null;
|
|
333
344
|
}
|
|
345
|
+
export type WorkflowArtifactKind = "discovery" | "test_contract";
|
|
346
|
+
export interface RequiredWorkflowArtifact {
|
|
347
|
+
kind: WorkflowArtifactKind;
|
|
348
|
+
/** Repository-relative canonical path owned by the workflow engine. */
|
|
349
|
+
path: string;
|
|
350
|
+
operation: "create_or_update";
|
|
351
|
+
}
|
|
352
|
+
export interface ArtifactRequiredResume {
|
|
353
|
+
argv: string[];
|
|
354
|
+
}
|
|
355
|
+
export interface MaterialUpdateRequiredResume {
|
|
356
|
+
argv: string[];
|
|
357
|
+
}
|
|
358
|
+
export interface TestEvidenceAction {
|
|
359
|
+
kind: "test_run";
|
|
360
|
+
test_id?: string;
|
|
361
|
+
record_argv: string[];
|
|
362
|
+
record_input: {
|
|
363
|
+
test_id?: string;
|
|
364
|
+
attempt_id: string;
|
|
365
|
+
command: null;
|
|
366
|
+
cwd: null;
|
|
367
|
+
exit_code: null;
|
|
368
|
+
semantic_status: "expected_failure" | "expected_success" | "characterization_pass";
|
|
369
|
+
};
|
|
370
|
+
required_fields: Array<"command" | "cwd" | "exit_code">;
|
|
371
|
+
}
|
|
334
372
|
export type NextOutput = {
|
|
335
373
|
state: State;
|
|
336
374
|
} & ({
|
|
@@ -342,6 +380,16 @@ export type NextOutput = {
|
|
|
342
380
|
path: "required_job";
|
|
343
381
|
required_jobs: RequiredJobAction[];
|
|
344
382
|
reason: string;
|
|
383
|
+
} | {
|
|
384
|
+
path: "artifact_required";
|
|
385
|
+
artifact: RequiredWorkflowArtifact;
|
|
386
|
+
resume: ArtifactRequiredResume;
|
|
387
|
+
reason: string;
|
|
388
|
+
} | {
|
|
389
|
+
path: "material_update_required";
|
|
390
|
+
errors: string[];
|
|
391
|
+
resume: MaterialUpdateRequiredResume;
|
|
392
|
+
reason: string;
|
|
345
393
|
} | {
|
|
346
394
|
path: "ask_user";
|
|
347
395
|
ask_user: AskUser;
|
package/dist/workflow_profile.js
CHANGED
|
@@ -6,7 +6,7 @@ const CURRENT_GATE_ROLES_BY_RISK = {
|
|
|
6
6
|
"review.final_verifier": ["verifier"],
|
|
7
7
|
},
|
|
8
8
|
normal: {
|
|
9
|
-
"explore.discovery_review": [],
|
|
9
|
+
"explore.discovery_review": ["critic"],
|
|
10
10
|
"propose.final_review": ["critic"],
|
|
11
11
|
"review.code_review": ["code-reviewer"],
|
|
12
12
|
"review.final_verifier": ["verifier"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peterxiaoyang/superspec",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"build": "node build.js",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"test": "node --test tests/*.ts",
|
|
27
|
+
"eval:probe": "node evals/probe.mjs",
|
|
28
|
+
"eval:dynamic": "node evals/probe.mjs --scenario evals/scenarios/probe-dynamic-accepted.json",
|
|
29
|
+
"eval:m2": "node evals/m2.mjs",
|
|
30
|
+
"eval:m2:validate": "node evals/m2.mjs --validate-faults",
|
|
31
|
+
"eval:arena": "node evals/arena.mjs",
|
|
32
|
+
"eval:delegation": "node evals/delegation-probe.mjs",
|
|
27
33
|
"prepack": "npm run build",
|
|
28
34
|
"prepublishOnly": "npm run build"
|
|
29
35
|
},
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
每完成 `next` 返回的当前事项(材料更新、用户答复回写、实现、验证、审查或修复时),立即再次运行 `superspec transition next --change "<change>"` 并继续处理。完成单个事项不等于完成整个 change;只有工作流明确需要用户决定、当前独立工作项尚未返回结果、遇到真实阻塞或整个 change 已完成时才暂停。
|
|
7
7
|
|
|
8
|
+
工作流要求用户决定时,只能登记当前对话中用户针对当前问题作出的明确答复。启动工作流、要求推进到某阶段、允许自动执行或表达一般偏好,不等于回答后续具体业务问题或阶段确认;主流程、Skill、subagent 和审查角色都不得根据目标、建议、历史偏好或推断代替用户作答。答复与当前问题不对应或仍有实质歧义时,保持待确认,不得登记为用户决定。
|
|
9
|
+
|
|
8
10
|
当用户显式调用 `$superspec-explore`,或明确要求继续处于 Explore 的已有 change 时,视为已明确授权启动 `explore` subagent 做只读深扫;其他 `$superspec-*` 阶段仅在工作流引擎创建独立工作项时,视为授权启动对应 subagent。
|
|
9
11
|
|
|
10
12
|
Explore 中需要用户决定业务、验收、范围或关键取舍时,先简要说明当前理解、影响和建议,再一次只请用户决定一件事;收到明确答复后,更新相关 discovery 结论,再继续工作流。其他阶段要求用户确认、选择处理方向或补齐材料时,按当前工作流返回的要求登记结论或更新相应材料;不得把这类答复默认写入 discovery。
|
|
@@ -22,6 +22,7 @@ argument-hint: "本次架构审查说明"
|
|
|
22
22
|
- 方案必须说明在何处承担责任,以及本次数据、接口、状态或控制流如何改变;实现者不能据此落地时阻塞。
|
|
23
23
|
- 需求、规格和 discovery 中影响实现的事实必须在设计中得到一致安排。共享数据、接口、状态或优先级规则不能在不同方案中得到冲突解释。
|
|
24
24
|
- 复用既有机制时,核对接入点、本次差异和保持语义;只审查本次接入是否破坏现有契约,不要求重建或重新证明底层基础设施。
|
|
25
|
+
- 参考实现证明的是可复用能力和候选机制,不自动决定本次的接口、资源、数据模型或模块形态。新增 Controller、API、实体、表、公共类型、独立模块或跨仓库改动时,检查其是否承担不可由现有边界表达的独立责任;不要规定数量,但应挑战无必要依据的平行结构和机械复制。
|
|
25
26
|
- 只有本次确实改变的兼容、并发、恢复、数据语义或发布顺序才需要明确设计;未改变的既有风险和理论故障不是 blocker。
|
|
26
27
|
- 设计取舍受用户决定和明确非目标约束。报告无法满足的结果或事实冲突,不把未经采纳的架构方案写成 required fix。
|
|
27
28
|
|
|
@@ -31,6 +32,7 @@ argument-hint: "本次架构审查说明"
|
|
|
31
32
|
- 多个功能点共享字段组、接口语义、状态转换、优先级或一致性规则时,检查是否有一个一致的权威契约;局部方案互相矛盾时阻塞。
|
|
32
33
|
- 改变既有规则变形、持久化语义、调用顺序或事务边界时,确认该变化已被声明为目标,并有与影响相称的保护边界;无理由地重复上游变形、双重兜底或扩大数据语义应报告。
|
|
33
34
|
- 运行时输入或 producer-to-consumer 契约变化时,设计应分开说明输入如何完整到达 consumer、consumer 如何处理;不要用单一算法描述掩盖输入链路缺口。
|
|
35
|
+
- 跨仓库、外部模块或不同运行时参与方案时,区分已经核实的契约、尚待核实的假设和必须先完成的外部前置变更。方案不能把当前证据范围外的实现当作确定事实,也不能让 Apply 隐含承担未登记的外部设计工作。
|
|
34
36
|
- 任务应能从实现方案和边界约束自然推出。仅当多个独立行为、入口或系统边界确实不能共享一个验证边界时,才要求拆分;不要为了形式化而增加层级或任务。
|
|
35
37
|
|
|
36
38
|
### 停止边界
|
|
@@ -12,7 +12,7 @@ argument-hint: "本次反方审查说明"
|
|
|
12
12
|
## 工作边界
|
|
13
13
|
|
|
14
14
|
- 先读任务说明和被引用材料;只审查本次 change 的既定范围。材料不足时报告证据缺口,不猜测或静默扩大范围。
|
|
15
|
-
-
|
|
15
|
+
- 修复复核优先判断原问题是否仍成立;不得通过缩小已确认范围、改写用户决定或删除验收来让 finding 字面消失。新 blocker 只能来自修复直接引入的回归,并说明因果链。
|
|
16
16
|
- 不要因标题、字段、表格、编号、勾选或引用写法提出 finding;只判断材料表达的事实、范围和可执行性。
|
|
17
17
|
|
|
18
18
|
## 反方判断
|
|
@@ -21,7 +21,7 @@ argument-hint: "本次反方审查说明"
|
|
|
21
21
|
|
|
22
22
|
Discovery 阶段,检查用户目标、当前差异和完成口径是否清楚;事实、推断和未知是否被诚实区分;影响范围、数据来源和下游影响是否由实际链路支撑。数据或跨边界输入发生变化时,确认调查能证明相关 producer-to-consumer 契约;局部行为且不改变数据传递时不要求虚构全链路。
|
|
23
23
|
|
|
24
|
-
计划阶段,检查 proposal、specs、design、tasks
|
|
24
|
+
计划阶段,检查 proposal、specs、design、tasks 与测试契约是否围绕同一已确认目标:能力是否被可观察行为、可落地设计、独立验证边界和证据路径支撑;已确认决策与需求变化是否得到一致回写;是否把调查记录、实现日志、技术偏好或未证实消费者伪装成需求。Architect 和 Test Engineer 存在时分别承担技术路线与测试证明力的深入审查;Critic 仍需识别会让整个计划无法实施或无法证明的跨材料缺口。
|
|
25
25
|
|
|
26
26
|
### Discovery 反方口径
|
|
27
27
|
|
|
@@ -29,6 +29,11 @@ Discovery 阶段,检查用户目标、当前差异和完成口径是否清楚
|
|
|
29
29
|
- 影响范围和风险应由当前调用、数据流、现有契约或用户可观察行为支撑。消费者类别只是搜索线索,不是无证据的覆盖配额。
|
|
30
30
|
- 对共享数据、跨边界输入或下游可观察行为,检查是否追到足以判断影响的真实链路,以及是否说明已排除的相邻消费者。对局部改动,直接锚点和具体不适用理由可以构成闭环。
|
|
31
31
|
- 已确认结论、未知和排除理由必须互相一致。会改变范围或验收的未知不能被伪装成非阻塞;可由继续调查解决的问题不应升级给用户。
|
|
32
|
+
- 区分证据支持的事实、基于事实提出的建议和需要用户选择的产品决定。会实质收窄范围、定义兼容或接口语义、选择行为主体或改变验收结果的结论,没有明确需求源、系统不变量或用户针对该问题的答复时,不得视为已确认。
|
|
33
|
+
|
|
34
|
+
Discovery 准备结束时,从本次变更及已有证据出发,反向检查是否仍有会影响范围、验收、兼容、数据语义或可落地性的未闭合分支。结合实际链路和边界自由判断,不套固定问题清单,也不为了形式完整而穷举可能性;正文已经暴露却没有得到明确处理的关键问题仍属于缺口。
|
|
35
|
+
|
|
36
|
+
能够通过代码、依赖、配置、测试或需求源继续核实的事实,应要求补足调查;只有证据无法裁决且确实需要选择的事项才交给用户。没有直接证据或不影响本次结果的可能性不应扩展为审查义务。
|
|
32
37
|
|
|
33
38
|
### 计划反方口径
|
|
34
39
|
|
|
@@ -36,6 +41,10 @@ Discovery 阶段,检查用户目标、当前差异和完成口径是否清楚
|
|
|
36
41
|
- `Impact` 应说明受影响原因和可观察影响,而不是文件清单;已证实会变化的影响没有登记且会使验收或实现判断失真时才阻塞。
|
|
37
42
|
- design 应表达方案关系、责任边界和约束,而不是 discovery 调查过程、规格复述、测试步骤或实现日志。共享事实需要一个可定位的权威定义,避免多处含义漂移。
|
|
38
43
|
- task 的来源、设计依据、验收和边界应能让执行者判断是否越界;这些材料与 task 实质无关、空泛或互相矛盾时才报告。不要检查字段、ID 或引用写法本身。
|
|
44
|
+
- 参考实现只证明已有能力和候选机制,不自动证明其接口数量、资源拆分、数据模型或模块边界适合本次 change。新增公共表面或跨系统改动缺少独立责任与必要性依据,或者明显存在可复用、合并、缩减空间并影响实施边界时,应要求计划补足判断,而不是规定具体数量或替代方案。
|
|
45
|
+
- 计划通过前,执行者应能在不重新决定产品语义或重做架构设计的前提下开始 Apply。会改变数据归属、调用路径、一致性或发布顺序的候选路线不得留给 Apply 临时选择。跨越可独立发布、失败或验证边界的 task,未经核实却被当成既定事实的外部依赖,以及无法证明已声明行为或设计直接风险的测试契约,都会削弱这一条件。
|
|
46
|
+
- 检查计划自己声明的关键不变量是否在迁移、兼容、回退和失败路径下仍成立;新旧实现同时存在且可能承担同一写入责任时,计划应明确权威写入边界,避免实现阶段重新决定所有权。
|
|
47
|
+
- 需求语义未闭合的问题属于 Explore;需求结果已经明确、但不同可行路线会改变迁移、兼容、数据归属、发布、成本或长期责任边界时,计划应让使用者明确选择。只有内部实现不同且不改变这些结果时,不得要求新增用户决定。
|
|
39
48
|
|
|
40
49
|
建议只能说明需补足的事实、范围或闭环,不能把个人技术偏好、新基础设施或额外测试升级为强制要求。已声明行为及其直接边界有充分证据时停止。
|
|
41
50
|
|
|
@@ -22,6 +22,7 @@ argument-hint: "本次测试审查说明"
|
|
|
22
22
|
- 测试应证明用户或调用方可观察的结果;预期值应来自规格、示例或独立计算,而不是复刻被测实现。
|
|
23
23
|
- 复用基础设施或既有测试时,只补本次接入的最小证明;不因理论上的长期风险重测未改变机制。
|
|
24
24
|
- 数据传递、字段形态或 producer-to-consumer 契约变化时,测试或其他可信证据必须证明目标输入按新契约抵达 consumer;只证明 consumer 算法不足以证明链路。
|
|
25
|
+
- 从已采纳方案实际引入的责任边界和失败方式推导测试义务。例如只有异步投影、一致性窗口、批量范围变更、租户路由、缓存陈旧或跨运行时版本差确实由本次方案产生并影响验收时,才要求相应证明;不要把这些例子当作所有 change 的固定矩阵。
|
|
25
26
|
- 已采纳的方案和验收约束决定测试义务。技术偏好、未采纳架构、通用故障矩阵和审查建议不能自行升级为必须新增的测试。
|
|
26
27
|
|
|
27
28
|
### 证明力审查
|
|
@@ -16,10 +16,12 @@ metadata:
|
|
|
16
16
|
|
|
17
17
|
每个 task 使用同一循环:
|
|
18
18
|
|
|
19
|
-
1.
|
|
19
|
+
1. 开始 task 前先检查当前工作区变化,并沿 task 引用链核对发生变化的需求源或计划材料;确认要实现的行为、边界和相关测试仍与当前计划一致。新变化使已批准行为、验收、边界或方案失效时,不按旧计划继续,停止实现并交回 Propose。
|
|
20
20
|
2. 在授权范围内实现最小改动;不要提前修改计划材料或扩大范围。
|
|
21
21
|
3. 完成当前 task 要求的验证,如实报告测试、环境或覆盖不足的结果。
|
|
22
|
-
4.
|
|
22
|
+
4. 当前 task 完成后立即继续工作流并处理下一事项;不要总结交付或等待用户再次要求继续。
|
|
23
|
+
|
|
24
|
+
验证和登记所需的执行顺序、固定语义与提交方式以工作流当前返回为准;执行者只补充真实命令、工作目录和退出结果。不要通过阅读 CLI 或引擎源码猜测证据格式。
|
|
23
25
|
|
|
24
26
|
不要伪造完成结果、验证材料或审查结论。
|
|
25
27
|
|
|
@@ -30,7 +32,9 @@ metadata:
|
|
|
30
32
|
- 优先沿用仓库已有的验证边界,证明用户或调用方可观察的结果。
|
|
31
33
|
- 不为方便测试改变生产设计,也不把测试偏好升级为额外的开发步骤或测试义务。
|
|
32
34
|
|
|
33
|
-
##
|
|
35
|
+
## 连续执行与停止
|
|
36
|
+
|
|
37
|
+
完成单个 task、测试通过或文件修改完成都不是暂停条件。只有工作流明确需要用户决定、当前独立工作项仍在执行、遇到无法在当前范围内解决的真实阻塞,或整个 change 已完成时才暂停。
|
|
34
38
|
|
|
35
39
|
当前 task 尚未完成时,自测发现仍属于该 task 已批准行为和边界的实现问题,直接修正并完成要求的验证;不要为同一实现缺陷新增 task 或回 propose。
|
|
36
40
|
|