@peterxiaoyang/superspec 0.1.40 → 0.1.42
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/README.md +8 -14
- package/dist/cli.js +9 -13
- package/dist/code_review.d.ts +2 -6
- package/dist/install.d.ts +1 -1
- package/dist/install.js +5 -2
- package/dist/next.js +6 -9
- package/dist/phase_confirmation.d.ts +31 -0
- package/dist/phase_confirmation.js +218 -0
- package/dist/phase_plan.d.ts +5 -6
- package/dist/phase_plan.js +155 -15
- package/dist/record.js +280 -76
- package/dist/record_input.d.ts +9 -0
- package/dist/record_input.js +34 -0
- package/dist/review.d.ts +3 -1
- package/dist/review.js +103 -1
- package/dist/review_job_gates.d.ts +2 -1
- package/dist/review_job_gates.js +8 -5
- package/dist/task.js +9 -1
- package/dist/transition.d.ts +0 -1
- package/dist/transition.js +108 -42
- package/dist/types.d.ts +60 -4
- package/package.json +1 -1
- package/templates/workflow/AGENTS.md +2 -0
- package/templates/workflow/prompts/architect.md +16 -15
- package/templates/workflow/prompts/code-reviewer.md +1 -1
- package/templates/workflow/prompts/critic.md +22 -16
- package/templates/workflow/prompts/executor.md +1 -1
- package/templates/workflow/prompts/test-engineer.md +11 -7
- package/templates/workflow/prompts/test-runner.md +1 -1
- package/templates/workflow/skills/superspec-propose/SKILL.md +92 -23
- package/templates/workflow/skills/superspec-review/SKILL.md +9 -6
- package/templates/workflow/skills/superspec-archive/SKILL.md +0 -38
package/dist/phase_plan.js
CHANGED
|
@@ -5,11 +5,23 @@ import { collectProposeOpenQuestions, countDiscoveryOpenQuestions, parseTasksMd,
|
|
|
5
5
|
import { currentGitHead } from "./git_state.js";
|
|
6
6
|
import { docRef, sha256File } from "./store.js";
|
|
7
7
|
import { isReviewReadyVerifier, isFreshReviewVerifier, readReviewPolicyFromEvents, reviewEvidenceDigest, } from "./review.js";
|
|
8
|
-
import { CODE_REVIEW_DECISION_ANSWER_LABELS, CODE_REVIEW_REPAIR_SCOPE_PREFIX, codeReviewDecisionScope, collectCodeReviewGateFacts, latestCodeReviewFailedStatus, requiresFinalVerifierForCurrentReview, } from "./code_review.js";
|
|
8
|
+
import { CODE_REVIEW_DECISION_ANSWER_LABELS, CODE_REVIEW_REPAIR_SCOPE_PREFIX, codeReviewDecisionScope, codeReviewJobStaleReason, collectCodeReviewGateFacts, currentCodeReviewWorkingPaths, latestCodeReviewFailedStatus, requiresFinalVerifierForCurrentReview, scanCodeChangesForReview, } from "./code_review.js";
|
|
9
|
+
import { isPhaseAdvanceAuthorized, latestAcceptedPhaseDecision, phaseConfirmationCommitPayload, phaseConfirmationForBoundary, phaseConfirmationMissingMessage, } from "./phase_confirmation.js";
|
|
9
10
|
import { taskEvidenceReadiness } from "./task_evidence.js";
|
|
10
11
|
function requiredJobs(state, jobs, reason) {
|
|
11
12
|
return { kind: "required_jobs", state, jobs, reason };
|
|
12
13
|
}
|
|
14
|
+
function phaseConfirmationStep(context, boundary, reason) {
|
|
15
|
+
const confirmation = phaseConfirmationForBoundary(context.projectRoot, context.events, context.snapshot, boundary, context.mode.risk);
|
|
16
|
+
if (!confirmation || isPhaseAdvanceAuthorized(context.events, confirmation))
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
kind: "ask_user",
|
|
20
|
+
state: context.snapshot.state,
|
|
21
|
+
ask: confirmation.ask,
|
|
22
|
+
reason,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
13
25
|
function reviewGatePlan(snapshot, gate, requiredRoles) {
|
|
14
26
|
const missingRoles = [];
|
|
15
27
|
for (const role of requiredRoles) {
|
|
@@ -63,14 +75,36 @@ function missingBaseArtifact(changeRoot, risk) {
|
|
|
63
75
|
return null;
|
|
64
76
|
}
|
|
65
77
|
export function proposalDocsBaseline(changeRoot) {
|
|
66
|
-
// specs/
|
|
67
|
-
const docs =
|
|
78
|
+
// 与 Propose gate 的可修改审查目标保持同一来源:specs/ 用目录聚合指纹,避免 reopen 基线漏掉任一个可修改的计划材料。
|
|
79
|
+
const docs = PROPOSE_FINAL_REVIEW_GATE.reviewTargets;
|
|
68
80
|
const baseline = {};
|
|
69
81
|
for (const doc of docs) {
|
|
70
82
|
baseline[doc] = docRef(changeRoot, doc).sha;
|
|
71
83
|
}
|
|
72
84
|
return baseline;
|
|
73
85
|
}
|
|
86
|
+
function isDigestMap(value) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
88
|
+
return false;
|
|
89
|
+
const entries = Object.entries(value);
|
|
90
|
+
return entries.length > 0 && entries.every(([, digest]) => typeof digest === "string");
|
|
91
|
+
}
|
|
92
|
+
export function latestAcceptedProposalBaseline(events) {
|
|
93
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
94
|
+
const ev = events[i];
|
|
95
|
+
if (ev.event_type !== "transition_commit")
|
|
96
|
+
continue;
|
|
97
|
+
const payload = ev.payload;
|
|
98
|
+
if (payload.transition !== "accept" ||
|
|
99
|
+
payload.from_state !== "review" ||
|
|
100
|
+
payload.to_state !== "accepted")
|
|
101
|
+
continue;
|
|
102
|
+
return isDigestMap(payload.accepted_baseline_docs)
|
|
103
|
+
? payload.accepted_baseline_docs
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
74
108
|
export function latestReopenProposeBaseline(events) {
|
|
75
109
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
76
110
|
const ev = events[i];
|
|
@@ -185,8 +219,42 @@ export function pendingTaskStatusForApply(changeRoot, events) {
|
|
|
185
219
|
export function formatPendingTaskMessage(ids, action) {
|
|
186
220
|
return `尚有未完成任务:${ids.join(", ")};${action}`;
|
|
187
221
|
}
|
|
222
|
+
function nextArgv(change, risk) {
|
|
223
|
+
return [
|
|
224
|
+
"superspec",
|
|
225
|
+
"transition",
|
|
226
|
+
"next",
|
|
227
|
+
"--change",
|
|
228
|
+
change,
|
|
229
|
+
...(risk === "strict" ? [] : ["--risk", risk]),
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
function acceptedMaterialFollowup(change, risk, planDocsChangedSinceAccept) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "accepted_material_followup",
|
|
235
|
+
trigger: "material_user_followup",
|
|
236
|
+
reason_source: "summarize_user_input",
|
|
237
|
+
reopen_argv_template: [
|
|
238
|
+
"superspec",
|
|
239
|
+
"transition",
|
|
240
|
+
"reopen",
|
|
241
|
+
"--change",
|
|
242
|
+
change,
|
|
243
|
+
"--to",
|
|
244
|
+
"propose",
|
|
245
|
+
"--reason",
|
|
246
|
+
"{{reason}}",
|
|
247
|
+
],
|
|
248
|
+
resume: {
|
|
249
|
+
kind: "continue_current_phase",
|
|
250
|
+
instruction: "根据用户补充更新 proposal/specs/design/tasks/test-contract;完成后重新执行 next。",
|
|
251
|
+
next_argv_after_completion: nextArgv(change, risk),
|
|
252
|
+
},
|
|
253
|
+
plan_docs_changed_since_accept: planDocsChangedSinceAccept,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
188
256
|
export function planNextStep(context) {
|
|
189
|
-
const { changeRoot, mode, snapshot } = context;
|
|
257
|
+
const { change, changeRoot, events, mode, snapshot } = context;
|
|
190
258
|
switch (snapshot.state) {
|
|
191
259
|
case "init":
|
|
192
260
|
if (snapshot.open_jobs.length > 0) {
|
|
@@ -217,6 +285,12 @@ export function planNextStep(context) {
|
|
|
217
285
|
};
|
|
218
286
|
return { kind: "ask_user", state: "explore", ask, reason: `有 ${openQs} 个未确认问题` };
|
|
219
287
|
}
|
|
288
|
+
const requiredRoles = EXPLORE_DISCOVERY_REVIEW_GATE.requiredRolesForRisk(mode.risk);
|
|
289
|
+
if (!reviewGatePlan(snapshot, EXPLORE_DISCOVERY_REVIEW_GATE, requiredRoles)) {
|
|
290
|
+
const confirmation = phaseConfirmationStep(context, "explore_to_propose", "探索完成,等待用户确认进入计划阶段");
|
|
291
|
+
if (confirmation)
|
|
292
|
+
return confirmation;
|
|
293
|
+
}
|
|
220
294
|
return {
|
|
221
295
|
kind: "run_transition",
|
|
222
296
|
state: "explore",
|
|
@@ -253,6 +327,12 @@ export function planNextStep(context) {
|
|
|
253
327
|
if (proposalReviewJobs.length > 0) {
|
|
254
328
|
return requiredJobs("propose_ready", proposalReviewJobs, `有 ${proposalReviewJobs.length} 个待完成 proposal 审查工作项`);
|
|
255
329
|
}
|
|
330
|
+
const startApplyPlan = planStartApplyTransition(context, false);
|
|
331
|
+
if (startApplyPlan.kind === "advance") {
|
|
332
|
+
const confirmation = phaseConfirmationStep(context, "propose_to_apply", "计划阶段完成,等待用户确认开始实现");
|
|
333
|
+
if (confirmation)
|
|
334
|
+
return confirmation;
|
|
335
|
+
}
|
|
256
336
|
return { kind: "run_transition", state: "propose_ready", transition: "start-apply", reason: "计划就绪,开始执行" };
|
|
257
337
|
}
|
|
258
338
|
case "apply":
|
|
@@ -263,14 +343,28 @@ export function planNextStep(context) {
|
|
|
263
343
|
return planReviewNext(context);
|
|
264
344
|
case "accepted":
|
|
265
345
|
if (snapshot.open_jobs.length > 0) {
|
|
266
|
-
return requiredJobs("accepted", snapshot.open_jobs, `有 ${snapshot.open_jobs.length}
|
|
346
|
+
return requiredJobs("accepted", snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项,暂不结束流程`);
|
|
347
|
+
}
|
|
348
|
+
{
|
|
349
|
+
const acceptedBaseline = latestAcceptedProposalBaseline(events);
|
|
350
|
+
const planDocsChanged = acceptedBaseline
|
|
351
|
+
? proposalDocsChangedSinceBaseline(changeRoot, acceptedBaseline)
|
|
352
|
+
: null;
|
|
353
|
+
const driftNote = planDocsChanged === true
|
|
354
|
+
? "检测到 accepted 后计划材料已变化;当前完成结论仍对应 accepted 时冻结的版本。"
|
|
355
|
+
: "";
|
|
356
|
+
return {
|
|
357
|
+
kind: "done",
|
|
358
|
+
state: "accepted",
|
|
359
|
+
reason: `${driftNote}审查已接受,流程完成;后续若使用者补充或修改需求、方案、验收或实现约束,按 continuation 自动回到 propose 后继续,不得要求使用者执行工作流命令`,
|
|
360
|
+
continuation: acceptedMaterialFollowup(change, mode.risk, planDocsChanged),
|
|
361
|
+
};
|
|
267
362
|
}
|
|
268
|
-
return { kind: "ask_archive_confirmation", state: "accepted", reason: "审查通过,等待用户确认归档" };
|
|
269
363
|
case "archive":
|
|
270
364
|
if (snapshot.open_jobs.length > 0) {
|
|
271
|
-
return requiredJobs("archive", snapshot.open_jobs,
|
|
365
|
+
return requiredJobs("archive", snapshot.open_jobs, `历史 archive 状态仍有 ${snapshot.open_jobs.length} 个待完成工作项`);
|
|
272
366
|
}
|
|
273
|
-
return { kind: "done", state: "archive", reason: "
|
|
367
|
+
return { kind: "done", state: "archive", reason: "历史 archive 状态,流程已结束。" };
|
|
274
368
|
case "abandoned":
|
|
275
369
|
return { kind: "done", state: "abandoned", reason: "变更已放弃,流程终止。" };
|
|
276
370
|
default:
|
|
@@ -320,6 +414,14 @@ function planApplyNext(context) {
|
|
|
320
414
|
reason: "所有任务完成,进入审查",
|
|
321
415
|
};
|
|
322
416
|
}
|
|
417
|
+
export function blockingJobsForApplyDone(projectRoot, events, snapshot) {
|
|
418
|
+
const facts = collectCodeReviewGateFacts(events);
|
|
419
|
+
const currentWorkingPaths = currentCodeReviewWorkingPaths(projectRoot, events);
|
|
420
|
+
const freshCodeReviewJobIds = new Set(facts.openJobs
|
|
421
|
+
.filter(job => codeReviewJobStaleReason(projectRoot, job, currentWorkingPaths) == null)
|
|
422
|
+
.map(job => job.job_id));
|
|
423
|
+
return snapshot.open_jobs.filter(job => job.role !== "code-reviewer" || freshCodeReviewJobIds.has(job.job_id));
|
|
424
|
+
}
|
|
323
425
|
function planApplyDoneNext(context) {
|
|
324
426
|
const { change, changeRoot, events, mode, snapshot } = context;
|
|
325
427
|
const pendingTasks = pendingTaskStatusForApply(changeRoot, events).pending;
|
|
@@ -332,10 +434,12 @@ function planApplyDoneNext(context) {
|
|
|
332
434
|
reason: `发现未完成任务 ${pendingTasks[0]},回到执行阶段`,
|
|
333
435
|
};
|
|
334
436
|
}
|
|
335
|
-
if (snapshot.open_jobs.length > 0) {
|
|
336
|
-
return requiredJobs("apply_done", snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项`);
|
|
337
|
-
}
|
|
338
437
|
const facts = collectCodeReviewGateFacts(events);
|
|
438
|
+
const currentWorkingPaths = currentCodeReviewWorkingPaths(context.projectRoot, events);
|
|
439
|
+
const relevantOpenJobs = blockingJobsForApplyDone(context.projectRoot, events, snapshot);
|
|
440
|
+
if (relevantOpenJobs.length > 0) {
|
|
441
|
+
return requiredJobs("apply_done", relevantOpenJobs, `有 ${relevantOpenJobs.length} 个待完成工作项`);
|
|
442
|
+
}
|
|
339
443
|
const latest = facts.latestTerminal;
|
|
340
444
|
if (latest?.state === "rejected" && latest.result_kind === "review_failed") {
|
|
341
445
|
const status = latestCodeReviewFailedStatus(events);
|
|
@@ -423,6 +527,19 @@ function planApplyDoneNext(context) {
|
|
|
423
527
|
};
|
|
424
528
|
return { kind: "ask_user", state: "apply_done", ask, reason: "代码审查报告连续不符合要求或没有可处理问题" };
|
|
425
529
|
}
|
|
530
|
+
const codeScan = scanCodeChangesForReview(context.projectRoot, events);
|
|
531
|
+
const acceptedScope = latest?.state === "accepted"
|
|
532
|
+
? latest.job.packet_context?.code_review_scope
|
|
533
|
+
: undefined;
|
|
534
|
+
const acceptedCurrentHead = acceptedScope?.current_head;
|
|
535
|
+
const acceptedReviewReady = latest?.state === "accepted" &&
|
|
536
|
+
codeReviewJobStaleReason(context.projectRoot, latest.job, currentWorkingPaths) == null && (acceptedCurrentHead === null ||
|
|
537
|
+
(typeof acceptedCurrentHead === "string" && acceptedCurrentHead.trim() !== ""));
|
|
538
|
+
if (!codeScan.hasCodeChanges || acceptedReviewReady) {
|
|
539
|
+
const confirmation = phaseConfirmationStep(context, "apply_to_review", "Apply 与代码审查完成,等待用户确认进入最终审查");
|
|
540
|
+
if (confirmation)
|
|
541
|
+
return confirmation;
|
|
542
|
+
}
|
|
426
543
|
return {
|
|
427
544
|
kind: "run_transition",
|
|
428
545
|
state: "apply_done",
|
|
@@ -485,7 +602,7 @@ export function planTransition(name, context) {
|
|
|
485
602
|
}
|
|
486
603
|
}
|
|
487
604
|
function planExploreTransition(context) {
|
|
488
|
-
const { changeRoot, mode, snapshot } = context;
|
|
605
|
+
const { changeRoot, events, mode, projectRoot, snapshot } = context;
|
|
489
606
|
if (snapshot.state === "init") {
|
|
490
607
|
return { kind: "advance", fromState: "init", toState: "explore", reason: "进入探索阶段" };
|
|
491
608
|
}
|
|
@@ -499,7 +616,18 @@ function planExploreTransition(context) {
|
|
|
499
616
|
const gatePlan = reviewGatePlan(snapshot, EXPLORE_DISCOVERY_REVIEW_GATE, requiredRoles);
|
|
500
617
|
if (gatePlan)
|
|
501
618
|
return gatePlan;
|
|
502
|
-
|
|
619
|
+
const confirmation = phaseConfirmationForBoundary(projectRoot, events, snapshot, "explore_to_propose");
|
|
620
|
+
const decision = confirmation ? latestAcceptedPhaseDecision(events, confirmation) : null;
|
|
621
|
+
if (!confirmation || decision?.decision !== "advance") {
|
|
622
|
+
return { kind: "skip", message: confirmation ? phaseConfirmationMissingMessage(confirmation) : "无法建立 Explore 阶段确认范围" };
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
kind: "advance",
|
|
626
|
+
fromState: "explore",
|
|
627
|
+
toState: "propose",
|
|
628
|
+
reason: "探索完成",
|
|
629
|
+
payload: phaseConfirmationCommitPayload(confirmation, decision),
|
|
630
|
+
};
|
|
503
631
|
}
|
|
504
632
|
function planProposeReadyTransition(context) {
|
|
505
633
|
const { changeRoot, mode, snapshot } = context;
|
|
@@ -526,7 +654,7 @@ function planProposeReadyTransition(context) {
|
|
|
526
654
|
return gatePlan;
|
|
527
655
|
return { kind: "advance", fromState: "propose", toState: "propose_ready", reason: `risk=${risk},所有需求已满足` };
|
|
528
656
|
}
|
|
529
|
-
function planStartApplyTransition(context) {
|
|
657
|
+
function planStartApplyTransition(context, enforceConfirmation = true) {
|
|
530
658
|
const { changeRoot, events, projectRoot, snapshot } = context;
|
|
531
659
|
if (snapshot.state !== "propose_ready")
|
|
532
660
|
return { kind: "skip", message: `当前状态 ${snapshot.state},需要 propose_ready` };
|
|
@@ -548,6 +676,11 @@ function planStartApplyTransition(context) {
|
|
|
548
676
|
const executionRequirementPlan = validateExecutionRequirementPlan(changeRoot);
|
|
549
677
|
if (!executionRequirementPlan.ok)
|
|
550
678
|
return { kind: "skip", message: executionRequirementPlan.message };
|
|
679
|
+
const confirmation = phaseConfirmationForBoundary(projectRoot, events, snapshot, "propose_to_apply");
|
|
680
|
+
const decision = confirmation ? latestAcceptedPhaseDecision(events, confirmation) : null;
|
|
681
|
+
if (enforceConfirmation && (!confirmation || decision?.decision !== "advance")) {
|
|
682
|
+
return { kind: "skip", message: confirmation ? phaseConfirmationMissingMessage(confirmation) : "无法建立 Propose 阶段确认范围" };
|
|
683
|
+
}
|
|
551
684
|
const gitHead = currentGitHead(projectRoot);
|
|
552
685
|
return {
|
|
553
686
|
kind: "advance",
|
|
@@ -558,6 +691,7 @@ function planStartApplyTransition(context) {
|
|
|
558
691
|
apply_start_head: gitHead.head,
|
|
559
692
|
apply_start_head_reason: gitHead.reason,
|
|
560
693
|
apply_contract_mode: executionRequirementPlan.mode,
|
|
694
|
+
...(confirmation && decision?.decision === "advance" ? phaseConfirmationCommitPayload(confirmation, decision) : {}),
|
|
561
695
|
},
|
|
562
696
|
};
|
|
563
697
|
}
|
|
@@ -578,5 +712,11 @@ function planAcceptTransition(context) {
|
|
|
578
712
|
if (!verifierAccepted)
|
|
579
713
|
return { kind: "skip", message: "缺少仍然匹配当前证据的最终验证,请先运行 review-ready" };
|
|
580
714
|
}
|
|
581
|
-
return {
|
|
715
|
+
return {
|
|
716
|
+
kind: "advance",
|
|
717
|
+
fromState: "review",
|
|
718
|
+
toState: "accepted",
|
|
719
|
+
reason: "审查通过",
|
|
720
|
+
payload: { accepted_baseline_docs: proposalDocsBaseline(changeRoot) },
|
|
721
|
+
};
|
|
582
722
|
}
|