@peterxiaoyang/superspec 0.1.47 → 0.1.49
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 +23 -1
- package/dist/phase_plan.js +190 -39
- 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 +86 -7
- package/dist/types.d.ts +51 -1
- package/dist/workflow_profile.js +1 -1
- package/package.json +7 -1
- package/templates/workflow/AGENTS.md +6 -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 +18 -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, applyPlanningDocsChangedSinceBaseline, 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, fallbackTestId, required) {
|
|
213
|
+
const testIds = required.test_ids.length > 0 ? required.test_ids : [fallbackTestId];
|
|
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
|
+
test_id: testId,
|
|
222
|
+
record_argv: ["superspec", "record", "test-run", "--change", change, "--input", "-"],
|
|
223
|
+
record_input: {
|
|
224
|
+
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,
|
|
@@ -798,6 +820,9 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
|
798
820
|
if (snapshot.state !== "apply")
|
|
799
821
|
return { skip: true, message: `当前状态 ${snapshot.state},需要 apply` };
|
|
800
822
|
const events = readEvents(projectRoot, change);
|
|
823
|
+
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
824
|
+
return { skip: true, message: "Apply 期间计划材料已变化;请回到 Propose 核对并重新批准计划后再继续任务" };
|
|
825
|
+
}
|
|
801
826
|
const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
|
|
802
827
|
const lines = tasksContent.split("\n");
|
|
803
828
|
const taskLineIdx = findTaskLine(lines, taskId);
|
|
@@ -867,6 +892,9 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
|
867
892
|
...attempt,
|
|
868
893
|
...boundarySnapshotPayload(projectRoot),
|
|
869
894
|
};
|
|
895
|
+
const evidenceActions = requiredEvidence
|
|
896
|
+
? evidenceActionsForAttempt(change, attempt.attempt_id, taskId, requiredEvidence)
|
|
897
|
+
: null;
|
|
870
898
|
return {
|
|
871
899
|
fromState: "apply", toState: "apply", outcome: "advanced",
|
|
872
900
|
reason: `创建任务 ${taskId} 执行尝试`,
|
|
@@ -878,6 +906,7 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
|
878
906
|
contract: adopted.contract,
|
|
879
907
|
...(fix ? { fix } : {}),
|
|
880
908
|
...(requiredEvidence ? { required_evidence: requiredEvidence } : {}),
|
|
909
|
+
...(evidenceActions ? { evidence_actions: evidenceActions } : {}),
|
|
881
910
|
// legacy 轮统一标注历史模式(无论有无执行依据文本);契约轮无块时标 false(如 Fix task)
|
|
882
911
|
...(adopted.contract ? {} : { legacy_contract: !contractMode }),
|
|
883
912
|
},
|
|
@@ -908,6 +937,36 @@ function invalidateOpenJobs(snapshot, to, reason) {
|
|
|
908
937
|
},
|
|
909
938
|
}));
|
|
910
939
|
}
|
|
940
|
+
function latestApplyPlanningBaseline(events) {
|
|
941
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
942
|
+
const event = events[index];
|
|
943
|
+
if (event.event_type !== "transition_commit")
|
|
944
|
+
continue;
|
|
945
|
+
const payload = event.payload;
|
|
946
|
+
if (payload.transition !== "start-apply")
|
|
947
|
+
continue;
|
|
948
|
+
const baseline = payload.apply_planning_baseline;
|
|
949
|
+
if (!baseline || typeof baseline !== "object" || Array.isArray(baseline))
|
|
950
|
+
return null;
|
|
951
|
+
const entries = Object.entries(baseline);
|
|
952
|
+
return entries.every(([, digest]) => typeof digest === "string")
|
|
953
|
+
? Object.fromEntries(entries)
|
|
954
|
+
: null;
|
|
955
|
+
}
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
function applyPlanningMaterialsChanged(changeRoot, events) {
|
|
959
|
+
const baseline = latestApplyPlanningBaseline(events);
|
|
960
|
+
return baseline != null && applyPlanningDocsChangedSinceBaseline(changeRoot, baseline);
|
|
961
|
+
}
|
|
962
|
+
function proposalReopenBaseline(changeRoot, events, source) {
|
|
963
|
+
const applyBaseline = ["apply", "apply_done", "review"].includes(source)
|
|
964
|
+
? latestApplyPlanningBaseline(events)
|
|
965
|
+
: null;
|
|
966
|
+
return applyBaseline
|
|
967
|
+
? { baseline: { ...proposalDocsBaseline(changeRoot), ...applyBaseline }, source: "apply" }
|
|
968
|
+
: { baseline: proposalDocsBaseline(changeRoot), source: "reopen_fallback" };
|
|
969
|
+
}
|
|
911
970
|
function planningReopenExtraEvents(snapshot, to, reason) {
|
|
912
971
|
return [
|
|
913
972
|
...invalidateOpenJobs(snapshot, to, reason),
|
|
@@ -969,6 +1028,7 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
969
1028
|
baseline_docs: proposalDocsBaseline(changeRoot),
|
|
970
1029
|
planning_validation_version: 2,
|
|
971
1030
|
planning_validation_profile: planningValidationProfileForNewRound(projectRoot),
|
|
1031
|
+
...proposeAnswerRegistrationPayloadForChange(changeRoot),
|
|
972
1032
|
},
|
|
973
1033
|
};
|
|
974
1034
|
}
|
|
@@ -977,6 +1037,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
977
1037
|
return { skip: true, message: "--review-fix 只能用于回到实现阶段(reopen --to apply)" };
|
|
978
1038
|
if (snapshot.state !== "apply_done")
|
|
979
1039
|
return { skip: true, message: `当前状态 ${snapshot.state},不能通过代码审查修复回到实现阶段` };
|
|
1040
|
+
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
1041
|
+
return { skip: true, message: "计划材料已变化,不能作为纯实现问题回到 Apply;请 reopen --to propose" };
|
|
1042
|
+
}
|
|
980
1043
|
const ref = parseCodeReviewFindingRef(opts.reviewFix);
|
|
981
1044
|
if (!ref)
|
|
982
1045
|
return { skip: true, message: "--review-fix 必须是 <job_id>#<finding_id>" };
|
|
@@ -1018,6 +1081,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1018
1081
|
return { skip: true, message: `当前状态 ${snapshot.state},不能通过自测问题回到实现阶段` };
|
|
1019
1082
|
}
|
|
1020
1083
|
const parentTaskId = opts.selfTestFix.trim();
|
|
1084
|
+
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
1085
|
+
return { skip: true, message: "计划材料已变化,不能作为纯实现问题创建 self-test 修复;请 reopen --to propose" };
|
|
1086
|
+
}
|
|
1021
1087
|
const parentTask = parseTasksMd(readFileSync(join(changeRoot, "tasks.md"), "utf8"))
|
|
1022
1088
|
.find(task => task.taskId === parentTaskId);
|
|
1023
1089
|
if (!parentTask)
|
|
@@ -1091,12 +1157,13 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1091
1157
|
// 的摘要,并用 reopen 当刻的摘要补齐缺项,确保本轮之后对任一审查目标的修改都能被检测。
|
|
1092
1158
|
const baselineNeedsBackfill = acceptedBaseline !== null && Object.keys(currentBaseline)
|
|
1093
1159
|
.some(path => !Object.prototype.hasOwnProperty.call(acceptedBaseline, path));
|
|
1160
|
+
const applyReopenBaseline = acceptedBaseline ? null : proposalReopenBaseline(changeRoot, events, snapshot.state);
|
|
1094
1161
|
const baselineDocs = acceptedBaseline
|
|
1095
1162
|
? Object.fromEntries(Object.entries(currentBaseline).map(([path, digest]) => [
|
|
1096
1163
|
path,
|
|
1097
1164
|
Object.prototype.hasOwnProperty.call(acceptedBaseline, path) ? acceptedBaseline[path] : digest,
|
|
1098
1165
|
]))
|
|
1099
|
-
:
|
|
1166
|
+
: applyReopenBaseline.baseline;
|
|
1100
1167
|
return {
|
|
1101
1168
|
fromState: snapshot.state,
|
|
1102
1169
|
toState: "propose",
|
|
@@ -1105,7 +1172,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1105
1172
|
commitPayload: {
|
|
1106
1173
|
reopen_target: "propose",
|
|
1107
1174
|
reopen_source: snapshot.state,
|
|
1108
|
-
baseline_source: acceptedBaseline
|
|
1175
|
+
baseline_source: acceptedBaseline
|
|
1176
|
+
? (baselineNeedsBackfill ? "accepted_backfill" : "accepted")
|
|
1177
|
+
: applyReopenBaseline.source,
|
|
1109
1178
|
baseline_docs: baselineDocs,
|
|
1110
1179
|
planning_validation_version: 2,
|
|
1111
1180
|
planning_validation_profile: planningValidationProfileForNewRound(projectRoot),
|
|
@@ -1118,6 +1187,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
|
|
|
1118
1187
|
if (snapshot.state !== "apply_done" && snapshot.state !== "review") {
|
|
1119
1188
|
return { skip: true, message: `当前状态 ${snapshot.state},不能 reopen 到 apply` };
|
|
1120
1189
|
}
|
|
1190
|
+
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
1191
|
+
return { skip: true, message: "计划材料已变化,不能直接回到 Apply;请 reopen --to propose" };
|
|
1192
|
+
}
|
|
1121
1193
|
const pending = pendingTaskStatusForApply(changeRoot, events).pending;
|
|
1122
1194
|
if (pending.length === 0)
|
|
1123
1195
|
return { skip: true, message: "没有未完成任务,不能 reopen 到 apply" };
|
|
@@ -1140,6 +1212,9 @@ export function reviewReady(projectRoot, change, changeRoot, risk = workflowRisk
|
|
|
1140
1212
|
const policy = storedPolicy ?? reviewPolicyForRisk(risk);
|
|
1141
1213
|
const policyPayload = storedPolicy ? {} : { review_policy: policy };
|
|
1142
1214
|
const currentEvidenceDigest = reviewEvidenceDigest(events);
|
|
1215
|
+
if (snapshot.state === "apply" && applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
1216
|
+
return { skip: true, message: "Apply 期间计划材料已变化,不能进入 Review;请回到 Propose 核对并重新批准计划" };
|
|
1217
|
+
}
|
|
1143
1218
|
// 检查是否所有任务已完成
|
|
1144
1219
|
const pending = pendingTaskStatusForApply(changeRoot, events).pending;
|
|
1145
1220
|
if (pending.length > 0)
|
|
@@ -1240,10 +1315,14 @@ export function taskComplete(projectRoot, change, changeRoot, taskId, inputConte
|
|
|
1240
1315
|
const attempt = snapshot.active_task_attempts?.find(a => a.task_id === taskId && a.state === "active");
|
|
1241
1316
|
if (!attempt)
|
|
1242
1317
|
return { skip: true, message: `任务 ${taskId} 无活跃执行尝试` };
|
|
1318
|
+
const events = readEvents(projectRoot, change);
|
|
1319
|
+
if (applyPlanningMaterialsChanged(changeRoot, events)) {
|
|
1320
|
+
return { skip: true, message: "Apply 期间计划材料已变化,不能完成当前任务;请回到 Propose 核对并重新批准计划" };
|
|
1321
|
+
}
|
|
1243
1322
|
const readiness = taskEvidenceReadiness(projectRoot, change, changeRoot, attempt);
|
|
1244
1323
|
if (!readiness.ready)
|
|
1245
1324
|
return { skip: true, message: `任务 ${taskId} 无法完成:${readiness.reason}` };
|
|
1246
|
-
const taskStartBoundary = boundarySnapshotForTaskAttempt(
|
|
1325
|
+
const taskStartBoundary = boundarySnapshotForTaskAttempt(events, attempt.attempt_id);
|
|
1247
1326
|
const completedPayload = {
|
|
1248
1327
|
task_id: taskId,
|
|
1249
1328
|
attempt_id: attempt.attempt_id,
|
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;
|
|
@@ -223,6 +226,8 @@ export interface TransitionCommitPayload {
|
|
|
223
226
|
review_risk?: "minimal" | "normal" | "strict";
|
|
224
227
|
};
|
|
225
228
|
accepted_baseline_docs?: Record<string, string>;
|
|
229
|
+
/** Apply 开始时冻结的计划材料摘要;tasks.md 由状态机维护,不参与冻结。 */
|
|
230
|
+
apply_planning_baseline?: Record<string, string>;
|
|
226
231
|
/** Propose-ready / start-apply 写入的本轮 workflow mode,后续阶段只读该快照。 */
|
|
227
232
|
workflow_mode?: "minimal" | "normal" | "strict";
|
|
228
233
|
/** v2 起所有普通任务必须有五字段执行依据;缺失表示旧 change,沿用旧规则回放。 */
|
|
@@ -318,6 +323,14 @@ export interface AskUser {
|
|
|
318
323
|
allowed_answers: string[];
|
|
319
324
|
scope: string;
|
|
320
325
|
actions?: AskUserAction[];
|
|
326
|
+
/** 自由文本问题的直接登记入口;固定选项继续使用 actions。 */
|
|
327
|
+
record_argv?: string[];
|
|
328
|
+
record_input?: {
|
|
329
|
+
scope: string;
|
|
330
|
+
question: string;
|
|
331
|
+
answer: null;
|
|
332
|
+
};
|
|
333
|
+
required_fields?: Array<"answer">;
|
|
321
334
|
}
|
|
322
335
|
export interface AcceptedMaterialFollowupContinuation {
|
|
323
336
|
kind: "accepted_material_followup";
|
|
@@ -331,6 +344,33 @@ export interface AcceptedMaterialFollowupContinuation {
|
|
|
331
344
|
};
|
|
332
345
|
plan_docs_changed_since_accept: boolean | null;
|
|
333
346
|
}
|
|
347
|
+
export type WorkflowArtifactKind = "discovery" | "test_contract" | "tasks";
|
|
348
|
+
export interface RequiredWorkflowArtifact {
|
|
349
|
+
kind: WorkflowArtifactKind;
|
|
350
|
+
/** Repository-relative canonical path owned by the workflow engine. */
|
|
351
|
+
path: string;
|
|
352
|
+
operation: "create_or_update";
|
|
353
|
+
}
|
|
354
|
+
export interface ArtifactRequiredResume {
|
|
355
|
+
argv: string[];
|
|
356
|
+
}
|
|
357
|
+
export interface MaterialUpdateRequiredResume {
|
|
358
|
+
argv: string[];
|
|
359
|
+
}
|
|
360
|
+
export interface TestEvidenceAction {
|
|
361
|
+
kind: "test_run";
|
|
362
|
+
test_id: string;
|
|
363
|
+
record_argv: string[];
|
|
364
|
+
record_input: {
|
|
365
|
+
test_id: string;
|
|
366
|
+
attempt_id: string;
|
|
367
|
+
command: null;
|
|
368
|
+
cwd: null;
|
|
369
|
+
exit_code: null;
|
|
370
|
+
semantic_status: "expected_failure" | "expected_success" | "characterization_pass";
|
|
371
|
+
};
|
|
372
|
+
required_fields: Array<"command" | "cwd" | "exit_code">;
|
|
373
|
+
}
|
|
334
374
|
export type NextOutput = {
|
|
335
375
|
state: State;
|
|
336
376
|
} & ({
|
|
@@ -342,6 +382,16 @@ export type NextOutput = {
|
|
|
342
382
|
path: "required_job";
|
|
343
383
|
required_jobs: RequiredJobAction[];
|
|
344
384
|
reason: string;
|
|
385
|
+
} | {
|
|
386
|
+
path: "artifact_required";
|
|
387
|
+
artifact: RequiredWorkflowArtifact;
|
|
388
|
+
resume: ArtifactRequiredResume;
|
|
389
|
+
reason: string;
|
|
390
|
+
} | {
|
|
391
|
+
path: "material_update_required";
|
|
392
|
+
errors: string[];
|
|
393
|
+
resume: MaterialUpdateRequiredResume;
|
|
394
|
+
reason: string;
|
|
345
395
|
} | {
|
|
346
396
|
path: "ask_user";
|
|
347
397
|
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.49",
|
|
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
|
},
|