@peterxiaoyang/superspec 0.1.48 → 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.
@@ -106,6 +106,8 @@ export interface ApplyPendingTaskStatus {
106
106
  export declare function executionPolicyForRisk(risk: ReviewRisk): ExecutionPolicy;
107
107
  export declare function executionPolicyForCurrentRound(events: Event[]): ExecutionPolicy;
108
108
  export declare function proposalDocsBaseline(changeRoot: string): Record<string, string>;
109
+ export declare function applyPlanningBaseline(changeRoot: string): Record<string, string>;
110
+ export declare function applyPlanningDocsChangedSinceBaseline(changeRoot: string, baseline: Record<string, string>): boolean;
109
111
  export declare function discoveryDocsBaseline(changeRoot: string): Record<string, string>;
110
112
  /** 新 Explore 轮次冻结已有已确认事项,避免把历史答复当作本轮遗漏。 */
111
113
  export declare function exploreAnswerRegistrationPayloadForChange(changeRoot: string): ReturnType<typeof exploreAnswerRegistrationPayload>;
@@ -25,14 +25,14 @@ function freeTextDecisionAsk(change, question, scope) {
25
25
  required_fields: ["answer"],
26
26
  };
27
27
  }
28
- function requiredArtifact(projectRoot, change, changeRoot, state, kind, fileName, risk) {
29
- const artifactPath = relative(projectRoot, join(changeRoot, ".superspec", "artifacts", fileName)).replaceAll("\\", "/");
28
+ function requiredArtifact(projectRoot, change, changeRoot, state, kind, canonicalPath, risk) {
29
+ const artifactPath = relative(projectRoot, join(changeRoot, canonicalPath)).replaceAll("\\", "/");
30
30
  return {
31
31
  kind: "artifact_required",
32
32
  state,
33
33
  artifact: { kind, path: artifactPath, operation: "create_or_update" },
34
34
  resume: { argv: nextArgv(change, risk) },
35
- reason: `${fileName} 不存在`,
35
+ reason: `${canonicalPath.split("/").at(-1)} 不存在`,
36
36
  };
37
37
  }
38
38
  function phaseConfirmationStep(context, boundary, reason) {
@@ -244,6 +244,15 @@ export function proposalDocsBaseline(changeRoot) {
244
244
  }
245
245
  return baseline;
246
246
  }
247
+ export function applyPlanningBaseline(changeRoot) {
248
+ const baseline = proposalDocsBaseline(changeRoot);
249
+ delete baseline["tasks.md"];
250
+ return baseline;
251
+ }
252
+ export function applyPlanningDocsChangedSinceBaseline(changeRoot, baseline) {
253
+ const current = applyPlanningBaseline(changeRoot);
254
+ return Object.entries(baseline).some(([path, digest]) => current[path] !== digest);
255
+ }
247
256
  export function discoveryDocsBaseline(changeRoot) {
248
257
  // 与 Explore gate 使用同一组审查目标。回退到 explore 后,至少要更新一项
249
258
  // discovery 材料,才允许重新进入 propose,避免把一次纯状态回退误当作新探索轮次。
@@ -476,6 +485,20 @@ export function pendingTaskStatusForApply(changeRoot, events) {
476
485
  completedByEvent,
477
486
  };
478
487
  }
488
+ function isPostApplyPlanOnlyRepair(changeRoot, events) {
489
+ // Propose 返工发生在新 Apply round 之前,旧 round 的 task_completed 不能覆盖
490
+ // 当前计划明确重新打开的 checkbox;这里以当前 tasks.md 为准。
491
+ if (pendingTaskIds(changeRoot).length > 0)
492
+ return false;
493
+ const roundId = currentProposeRoundId(events);
494
+ const roundEvent = events.find(event => event.event_id === roundId);
495
+ if (roundEvent?.event_type !== "transition_commit")
496
+ return false;
497
+ const payload = roundEvent.payload;
498
+ return payload.transition === "reopen"
499
+ && payload.reopen_target === "propose"
500
+ && ["apply", "apply_done", "review", "accepted"].includes(String(payload.reopen_source));
501
+ }
479
502
  export function formatPendingTaskMessage(ids, action) {
480
503
  return `尚有未完成任务:${ids.join(", ")};${action}`;
481
504
  }
@@ -532,7 +555,7 @@ export function planNextStep(context) {
532
555
  }
533
556
  const discoveryPath = join(changeRoot, ".superspec", "artifacts", "discovery.md");
534
557
  if (!existsSync(discoveryPath)) {
535
- return requiredArtifact(projectRoot, change, changeRoot, "explore", "discovery", "discovery.md", mode.risk);
558
+ return requiredArtifact(projectRoot, change, changeRoot, "explore", "discovery", ".superspec/artifacts/discovery.md", mode.risk);
536
559
  }
537
560
  const discoveryCheck = validateDiscovery(changeRoot);
538
561
  if (!discoveryCheck.ok) {
@@ -616,7 +639,10 @@ export function planNextStep(context) {
616
639
  }
617
640
  const testContractPath = join(changeRoot, ".superspec", "artifacts", "test-contract.md");
618
641
  if (!existsSync(testContractPath)) {
619
- return requiredArtifact(projectRoot, change, changeRoot, "propose", "test_contract", "test-contract.md", mode.risk);
642
+ return requiredArtifact(projectRoot, change, changeRoot, "propose", "test_contract", ".superspec/artifacts/test-contract.md", mode.risk);
643
+ }
644
+ if (!existsSync(join(changeRoot, "tasks.md"))) {
645
+ return requiredArtifact(projectRoot, change, changeRoot, "propose", "tasks", "tasks.md", mode.risk);
620
646
  }
621
647
  const planningProfile = planningValidationProfileForPendingProposeRound(events);
622
648
  const preflight = validatePlanningPreflight(projectRoot, change, changeRoot, mode.risk, executionPolicyForRisk(mode.risk), planningProfile);
@@ -646,7 +672,8 @@ export function planNextStep(context) {
646
672
  return requiredJobs("propose_ready", proposalReviewJobs, `有 ${proposalReviewJobs.length} 个待完成 proposal 审查工作项`);
647
673
  }
648
674
  const startApplyPlan = planStartApplyTransition(context, false);
649
- if (startApplyPlan.kind === "advance") {
675
+ const planOnlyRepair = isPostApplyPlanOnlyRepair(changeRoot, events);
676
+ if (startApplyPlan.kind === "advance" && !planOnlyRepair) {
650
677
  const confirmation = phaseConfirmationStep(context, "propose_to_apply", "计划阶段完成,等待用户确认开始实现");
651
678
  if (confirmation)
652
679
  return confirmation;
@@ -661,7 +688,12 @@ export function planNextStep(context) {
661
688
  };
662
689
  return { kind: "ask_user", state: "propose_ready", ask, reason: startApplyPlan.message };
663
690
  }
664
- return { kind: "run_transition", state: "propose_ready", transition: "start-apply", reason: "计划就绪,开始执行" };
691
+ return {
692
+ kind: "run_transition",
693
+ state: "propose_ready",
694
+ transition: "start-apply",
695
+ reason: planOnlyRepair ? "计划材料已修正且没有待实施任务,跳过重复的 Apply 确认" : "计划就绪,开始执行",
696
+ };
665
697
  }
666
698
  case "apply":
667
699
  return planApplyNext(context);
@@ -1061,10 +1093,12 @@ function planStartApplyTransition(context, enforceConfirmation = true) {
1061
1093
  reason: `进入执行阶段前需要重新完成计划文档审查:${gatePlan.reason}`,
1062
1094
  };
1063
1095
  }
1096
+ const planOnlyRepair = isPostApplyPlanOnlyRepair(changeRoot, events);
1064
1097
  const acceptedConfirmation = enforceConfirmation
1098
+ && !planOnlyRepair
1065
1099
  ? acceptedProposeToApplyConfirmation(context, risk)
1066
1100
  : null;
1067
- if (enforceConfirmation && !acceptedConfirmation) {
1101
+ if (enforceConfirmation && !acceptedConfirmation && !planOnlyRepair) {
1068
1102
  const confirmation = phaseConfirmationForBoundary(projectRoot, events, snapshot, "propose_to_apply", risk);
1069
1103
  return {
1070
1104
  kind: "skip",
@@ -1076,7 +1110,7 @@ function planStartApplyTransition(context, enforceConfirmation = true) {
1076
1110
  kind: "advance",
1077
1111
  fromState: "propose_ready",
1078
1112
  toState: "apply",
1079
- reason: "进入执行阶段",
1113
+ reason: planOnlyRepair ? "计划材料修正完成且没有待实施任务" : "进入执行阶段",
1080
1114
  payload: {
1081
1115
  apply_start_head: gitHead.head,
1082
1116
  apply_start_head_reason: gitHead.reason,
@@ -1088,6 +1122,7 @@ function planStartApplyTransition(context, enforceConfirmation = true) {
1088
1122
  review_risk: risk,
1089
1123
  requires_verifier: risk !== "minimal",
1090
1124
  },
1125
+ apply_planning_baseline: applyPlanningBaseline(changeRoot),
1091
1126
  ...(acceptedConfirmation ? phaseConfirmationCommitPayload(acceptedConfirmation.confirmation, acceptedConfirmation.decision) : {}),
1092
1127
  },
1093
1128
  };
@@ -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, proposeAnswerRegistrationPayloadForChange, 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";
@@ -209,8 +209,8 @@ 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];
212
+ function evidenceActionsForAttempt(change, attemptId, fallbackTestId, required) {
213
+ const testIds = required.test_ids.length > 0 ? required.test_ids : [fallbackTestId];
214
214
  const statuses = [];
215
215
  if (required.red_required)
216
216
  statuses.push("expected_failure");
@@ -218,10 +218,10 @@ function evidenceActionsForAttempt(change, attemptId, required) {
218
218
  statuses.push(required.accepted_green_statuses[0] ?? "expected_success");
219
219
  return testIds.flatMap(testId => statuses.map(semanticStatus => ({
220
220
  kind: "test_run",
221
- ...(testId ? { test_id: testId } : {}),
221
+ test_id: testId,
222
222
  record_argv: ["superspec", "record", "test-run", "--change", change, "--input", "-"],
223
223
  record_input: {
224
- ...(testId ? { test_id: testId } : {}),
224
+ test_id: testId,
225
225
  attempt_id: attemptId,
226
226
  command: null,
227
227
  cwd: null,
@@ -820,6 +820,9 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
820
820
  if (snapshot.state !== "apply")
821
821
  return { skip: true, message: `当前状态 ${snapshot.state},需要 apply` };
822
822
  const events = readEvents(projectRoot, change);
823
+ if (applyPlanningMaterialsChanged(changeRoot, events)) {
824
+ return { skip: true, message: "Apply 期间计划材料已变化;请回到 Propose 核对并重新批准计划后再继续任务" };
825
+ }
823
826
  const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
824
827
  const lines = tasksContent.split("\n");
825
828
  const taskLineIdx = findTaskLine(lines, taskId);
@@ -890,7 +893,7 @@ export function taskStart(projectRoot, change, changeRoot, taskId) {
890
893
  ...boundarySnapshotPayload(projectRoot),
891
894
  };
892
895
  const evidenceActions = requiredEvidence
893
- ? evidenceActionsForAttempt(change, attempt.attempt_id, requiredEvidence)
896
+ ? evidenceActionsForAttempt(change, attempt.attempt_id, taskId, requiredEvidence)
894
897
  : null;
895
898
  return {
896
899
  fromState: "apply", toState: "apply", outcome: "advanced",
@@ -934,6 +937,36 @@ function invalidateOpenJobs(snapshot, to, reason) {
934
937
  },
935
938
  }));
936
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
+ }
937
970
  function planningReopenExtraEvents(snapshot, to, reason) {
938
971
  return [
939
972
  ...invalidateOpenJobs(snapshot, to, reason),
@@ -1004,6 +1037,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
1004
1037
  return { skip: true, message: "--review-fix 只能用于回到实现阶段(reopen --to apply)" };
1005
1038
  if (snapshot.state !== "apply_done")
1006
1039
  return { skip: true, message: `当前状态 ${snapshot.state},不能通过代码审查修复回到实现阶段` };
1040
+ if (applyPlanningMaterialsChanged(changeRoot, events)) {
1041
+ return { skip: true, message: "计划材料已变化,不能作为纯实现问题回到 Apply;请 reopen --to propose" };
1042
+ }
1007
1043
  const ref = parseCodeReviewFindingRef(opts.reviewFix);
1008
1044
  if (!ref)
1009
1045
  return { skip: true, message: "--review-fix 必须是 <job_id>#<finding_id>" };
@@ -1045,6 +1081,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
1045
1081
  return { skip: true, message: `当前状态 ${snapshot.state},不能通过自测问题回到实现阶段` };
1046
1082
  }
1047
1083
  const parentTaskId = opts.selfTestFix.trim();
1084
+ if (applyPlanningMaterialsChanged(changeRoot, events)) {
1085
+ return { skip: true, message: "计划材料已变化,不能作为纯实现问题创建 self-test 修复;请 reopen --to propose" };
1086
+ }
1048
1087
  const parentTask = parseTasksMd(readFileSync(join(changeRoot, "tasks.md"), "utf8"))
1049
1088
  .find(task => task.taskId === parentTaskId);
1050
1089
  if (!parentTask)
@@ -1118,12 +1157,13 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
1118
1157
  // 的摘要,并用 reopen 当刻的摘要补齐缺项,确保本轮之后对任一审查目标的修改都能被检测。
1119
1158
  const baselineNeedsBackfill = acceptedBaseline !== null && Object.keys(currentBaseline)
1120
1159
  .some(path => !Object.prototype.hasOwnProperty.call(acceptedBaseline, path));
1160
+ const applyReopenBaseline = acceptedBaseline ? null : proposalReopenBaseline(changeRoot, events, snapshot.state);
1121
1161
  const baselineDocs = acceptedBaseline
1122
1162
  ? Object.fromEntries(Object.entries(currentBaseline).map(([path, digest]) => [
1123
1163
  path,
1124
1164
  Object.prototype.hasOwnProperty.call(acceptedBaseline, path) ? acceptedBaseline[path] : digest,
1125
1165
  ]))
1126
- : currentBaseline;
1166
+ : applyReopenBaseline.baseline;
1127
1167
  return {
1128
1168
  fromState: snapshot.state,
1129
1169
  toState: "propose",
@@ -1132,7 +1172,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
1132
1172
  commitPayload: {
1133
1173
  reopen_target: "propose",
1134
1174
  reopen_source: snapshot.state,
1135
- baseline_source: acceptedBaseline ? (baselineNeedsBackfill ? "accepted_backfill" : "accepted") : "reopen_fallback",
1175
+ baseline_source: acceptedBaseline
1176
+ ? (baselineNeedsBackfill ? "accepted_backfill" : "accepted")
1177
+ : applyReopenBaseline.source,
1136
1178
  baseline_docs: baselineDocs,
1137
1179
  planning_validation_version: 2,
1138
1180
  planning_validation_profile: planningValidationProfileForNewRound(projectRoot),
@@ -1145,6 +1187,9 @@ export function reopen(projectRoot, change, changeRoot, to, reason, opts = {}) {
1145
1187
  if (snapshot.state !== "apply_done" && snapshot.state !== "review") {
1146
1188
  return { skip: true, message: `当前状态 ${snapshot.state},不能 reopen 到 apply` };
1147
1189
  }
1190
+ if (applyPlanningMaterialsChanged(changeRoot, events)) {
1191
+ return { skip: true, message: "计划材料已变化,不能直接回到 Apply;请 reopen --to propose" };
1192
+ }
1148
1193
  const pending = pendingTaskStatusForApply(changeRoot, events).pending;
1149
1194
  if (pending.length === 0)
1150
1195
  return { skip: true, message: "没有未完成任务,不能 reopen 到 apply" };
@@ -1167,6 +1212,9 @@ export function reviewReady(projectRoot, change, changeRoot, risk = workflowRisk
1167
1212
  const policy = storedPolicy ?? reviewPolicyForRisk(risk);
1168
1213
  const policyPayload = storedPolicy ? {} : { review_policy: policy };
1169
1214
  const currentEvidenceDigest = reviewEvidenceDigest(events);
1215
+ if (snapshot.state === "apply" && applyPlanningMaterialsChanged(changeRoot, events)) {
1216
+ return { skip: true, message: "Apply 期间计划材料已变化,不能进入 Review;请回到 Propose 核对并重新批准计划" };
1217
+ }
1170
1218
  // 检查是否所有任务已完成
1171
1219
  const pending = pendingTaskStatusForApply(changeRoot, events).pending;
1172
1220
  if (pending.length > 0)
@@ -1267,10 +1315,14 @@ export function taskComplete(projectRoot, change, changeRoot, taskId, inputConte
1267
1315
  const attempt = snapshot.active_task_attempts?.find(a => a.task_id === taskId && a.state === "active");
1268
1316
  if (!attempt)
1269
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
+ }
1270
1322
  const readiness = taskEvidenceReadiness(projectRoot, change, changeRoot, attempt);
1271
1323
  if (!readiness.ready)
1272
1324
  return { skip: true, message: `任务 ${taskId} 无法完成:${readiness.reason}` };
1273
- const taskStartBoundary = boundarySnapshotForTaskAttempt(readEvents(projectRoot, change), attempt.attempt_id);
1325
+ const taskStartBoundary = boundarySnapshotForTaskAttempt(events, attempt.attempt_id);
1274
1326
  const completedPayload = {
1275
1327
  task_id: taskId,
1276
1328
  attempt_id: attempt.attempt_id,
package/dist/types.d.ts CHANGED
@@ -226,6 +226,8 @@ export interface TransitionCommitPayload {
226
226
  review_risk?: "minimal" | "normal" | "strict";
227
227
  };
228
228
  accepted_baseline_docs?: Record<string, string>;
229
+ /** Apply 开始时冻结的计划材料摘要;tasks.md 由状态机维护,不参与冻结。 */
230
+ apply_planning_baseline?: Record<string, string>;
229
231
  /** Propose-ready / start-apply 写入的本轮 workflow mode,后续阶段只读该快照。 */
230
232
  workflow_mode?: "minimal" | "normal" | "strict";
231
233
  /** v2 起所有普通任务必须有五字段执行依据;缺失表示旧 change,沿用旧规则回放。 */
@@ -342,7 +344,7 @@ export interface AcceptedMaterialFollowupContinuation {
342
344
  };
343
345
  plan_docs_changed_since_accept: boolean | null;
344
346
  }
345
- export type WorkflowArtifactKind = "discovery" | "test_contract";
347
+ export type WorkflowArtifactKind = "discovery" | "test_contract" | "tasks";
346
348
  export interface RequiredWorkflowArtifact {
347
349
  kind: WorkflowArtifactKind;
348
350
  /** Repository-relative canonical path owned by the workflow engine. */
@@ -357,10 +359,10 @@ export interface MaterialUpdateRequiredResume {
357
359
  }
358
360
  export interface TestEvidenceAction {
359
361
  kind: "test_run";
360
- test_id?: string;
362
+ test_id: string;
361
363
  record_argv: string[];
362
364
  record_input: {
363
- test_id?: string;
365
+ test_id: string;
364
366
  attempt_id: string;
365
367
  command: null;
366
368
  cwd: null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,6 +1,8 @@
1
1
  <!-- SUPERSPEC:AGENTS:START -->
2
2
  只有当用户显式调用 `superspec-*`,或明确要求继续处理某个已有 SuperSpec change 时,才进入或续转 SuperSpec 工作流。普通开发、修复、排查、测试或审查请求,即使项目已安装 SuperSpec,也不得自行启动工作流、创建 change、执行 `transition next`,或切换到某个 `superspec-*` 阶段。
3
3
 
4
+ 以下规则仅适用于已经显式启动或明确指定的 SuperSpec change。没有活跃 SuperSpec change 时,本区块除上述工作流激活边界外均不适用,按项目常规开发指令执行。
5
+
4
6
  一旦用户已显式启动工作流或明确指定 change,使用 `superspec-*` 工作流时一律以 `superspec transition next --change "<change>"` 返回的下一步推进;主流程执行内部命令,不要求用户手动运行工作流命令。流程完成前不得跳阶段、不得自称完成。
5
7
 
6
8
  每完成 `next` 返回的当前事项(材料更新、用户答复回写、实现、验证、审查或修复时),立即再次运行 `superspec transition next --change "<change>"` 并继续处理。完成单个事项不等于完成整个 change;只有工作流明确需要用户决定、当前独立工作项尚未返回结果、遇到真实阻塞或整个 change 已完成时才暂停。
@@ -13,6 +15,8 @@ Explore 中需要用户决定业务、验收、范围或关键取舍时,先简
13
15
 
14
16
  当前 change 的自测、联调或用户指出的问题若仍能由既有 task 的批准行为、边界和验收解释,就在同一 change 内处理:
15
17
 
18
+ 计划材料没有枚举某个类、继承关系、方法或局部实现细节,不等于计划遗漏。只要正确修法能够由既有 task、已批准行为和仓库事实唯一推导,仍属于 Apply;只有需要重新决定公共接口、数据归属、迁移兼容、实现路线、验收或 task 边界时才回 Propose。
19
+
16
20
  - 当前 task 尚未完成时,在其范围内直接修复;不要为同一实现问题新增 task 或回 propose。
17
21
  - 所有 task 已完成后,若问题仍能关联一个已完成 task、且不改变已批准行为和方案,主流程执行 `superspec transition reopen --change "<change>" --to apply --self-test-fix "<task>" --reason "<reason>"`,让工作流创建修复事项;随后继续 `next`,不得手改 tasks。
18
22
  - 无法关联既有 task,或需要改变行为、验收、接口、数据语义或实现路线时,才回 propose。
@@ -186,6 +186,8 @@ metadata:
186
186
 
187
187
  Propose 以已确认的 Discovery 为需求边界。范围、业务行为、验收、数据语义或安全仍不明确时,回同一 change 的 Explore 澄清,不静默采用默认业务语义。
188
188
 
189
+ 用户补充与已经核实的代码、运行行为、接口契约或外部系统事实冲突时,不通过改写事实材料来消除冲突。若用户明确要求改变真实行为,将相应实现、迁移和验证影响纳入计划;若是否改变真实行为仍不明确,保留冲突并交给用户确认。
190
+
189
191
  当需求结果已经明确,但多个可行技术路线会让使用者承担不同的迁移、兼容、数据归属、发布、成本或长期维护边界时,不替使用者静默选择。在 `design.md` 的 `## 待用户确认` 中保留当前高影响设计决定,说明已知事实、候选结果、推荐及依据和会受影响的交付;内部命名、文件组织、局部实现和不改变这些结果的技术选择由模型自主决定。没有这类取舍时不制造问答。
190
192
 
191
193
  每项使用 `DEC-xxx` 标识并只表达一个决定。工作流一次返回当前一项;用户答复后按工作流反馈处理,勾选时保留决定项原文,将最终选择与影响写入相邻正文及相关 design、specs、tasks 和测试契约,并按新方案重新判断后续事项。审查角色只能依据本次目标和直接证据指出缺失的决定,不能把个人偏好或更理想的架构升级为用户义务。