@peterxiaoyang/superspec 0.1.45 → 0.1.47

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.
Files changed (40) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/code_review.d.ts +11 -1
  3. package/dist/code_review.js +40 -0
  4. package/dist/explore_round.d.ts +23 -0
  5. package/dist/explore_round.js +94 -0
  6. package/dist/format.d.ts +67 -2
  7. package/dist/format.js +273 -22
  8. package/dist/openspec.d.ts +13 -0
  9. package/dist/openspec.js +53 -4
  10. package/dist/phase_confirmation.js +71 -2
  11. package/dist/phase_plan.d.ts +6 -1
  12. package/dist/phase_plan.js +180 -32
  13. package/dist/record.js +191 -57
  14. package/dist/review.js +2 -0
  15. package/dist/task_evidence.js +5 -3
  16. package/dist/transition.d.ts +1 -0
  17. package/dist/transition.js +222 -28
  18. package/dist/types.d.ts +42 -0
  19. package/package.json +1 -1
  20. package/templates/workflow/AGENTS.md +15 -5
  21. package/templates/workflow/agents/architect.toml +1 -1
  22. package/templates/workflow/agents/code-reviewer.toml +1 -1
  23. package/templates/workflow/agents/critic.toml +1 -1
  24. package/templates/workflow/agents/executor.toml +1 -1
  25. package/templates/workflow/agents/explore.toml +1 -1
  26. package/templates/workflow/agents/test-engineer.toml +1 -1
  27. package/templates/workflow/agents/test-runner.toml +1 -1
  28. package/templates/workflow/agents/verifier.toml +1 -1
  29. package/templates/workflow/prompts/architect.md +25 -33
  30. package/templates/workflow/prompts/code-reviewer.md +19 -67
  31. package/templates/workflow/prompts/critic.md +36 -86
  32. package/templates/workflow/prompts/executor.md +17 -19
  33. package/templates/workflow/prompts/explore.md +12 -46
  34. package/templates/workflow/prompts/test-engineer.md +22 -34
  35. package/templates/workflow/prompts/test-runner.md +11 -21
  36. package/templates/workflow/prompts/verifier.md +13 -37
  37. package/templates/workflow/skills/superspec-apply/SKILL.md +26 -26
  38. package/templates/workflow/skills/superspec-explore/SKILL.md +69 -60
  39. package/templates/workflow/skills/superspec-propose/SKILL.md +85 -133
  40. package/templates/workflow/skills/superspec-review/SKILL.md +14 -44
package/dist/cli.js CHANGED
@@ -514,7 +514,7 @@ transition 子命令:
514
514
  init / explore / sync / next / propose-ready / start-apply
515
515
  task-start --task <T> / task-complete --task <T> [--input -]
516
516
  reopen --to explore|propose|apply --reason <TEXT>
517
- reopen --to apply --reason <TEXT> [--review-fix <JOB#FINDING>]
517
+ reopen --to apply --reason <TEXT> [--review-fix <JOB#FINDING>|--self-test-fix <TASK>]
518
518
  reopen --to propose --reason <TEXT> [--review-finding <JOB#FINDING>]
519
519
  review-ready / accept
520
520
 
@@ -735,6 +735,7 @@ jobs 子命令:
735
735
  const result = reopen(projectRoot, change, cr, to, reason, {
736
736
  ...(opts["review-fix"] ? { reviewFix: opts["review-fix"] } : {}),
737
737
  ...(opts["review-finding"] ? { reviewFinding: opts["review-finding"] } : {}),
738
+ ...(opts["self-test-fix"] ? { selfTestFix: opts["self-test-fix"] } : {}),
738
739
  });
739
740
  console.log(JSON.stringify(result, null, 2));
740
741
  return transitionExitCode(result);
@@ -1,4 +1,4 @@
1
- import type { CodeReviewResultKind, CodeReviewScope, CodeStateCheck, CoverageExemptionRef, Event, Job, JobPacketContext, Ref, ReviewPreviousRejection } from "./types.ts";
1
+ import type { CodeReviewGateEvidence, CodeReviewResultKind, CodeReviewScope, CodeStateCheck, CoverageExemptionRef, Event, Job, JobPacketContext, Ref, ReviewPreviousRejection, TaskExecutionIndexEntry } from "./types.ts";
2
2
  export declare const CODE_REVIEW_REPAIR_SCOPE_PREFIX = "code_reviewer_report_repair:";
3
3
  export declare const CODE_REVIEW_DECISION_SCOPE_PREFIX = "code_review_decision:";
4
4
  export declare const TEST_COVERAGE_EXEMPTION_SCOPE_PREFIX = "test_coverage_exemption:";
@@ -68,7 +68,15 @@ export declare function codeReviewPacketDigest(input: {
68
68
  export declare function codeReviewPacketContext(changeRoot: string, projectRoot: string, scope: CodeReviewScope, events: Event[]): JobPacketContext;
69
69
  export declare function effectiveCoverageExemptionRefsFromEvents(events: Event[]): CoverageExemptionRef[];
70
70
  export declare function missingCoverageExemptionTestIds(changeRoot: string, events: Event[]): string[];
71
+ /** Read-only execution evidence projected for code review and final verification. */
72
+ export declare function taskExecutionIndexForReview(projectRoot: string, events: Event[]): TaskExecutionIndexEntry[];
71
73
  export declare function codeReviewDecisionScope(jobId: string, findingId: string): string;
74
+ export interface CodeReviewDecisionScopeRef {
75
+ jobId: string;
76
+ findingId: string;
77
+ }
78
+ /** 只接受状态机生成的 <job>#<finding> 决策范围。 */
79
+ export declare function parseCodeReviewDecisionScope(scope: string): CodeReviewDecisionScopeRef | null;
72
80
  export declare function isCodeReviewDecisionAnswer(value: unknown): value is CodeReviewDecisionAnswer;
73
81
  export declare function codeReviewDecisionAnswerLabel(answer: CodeReviewDecisionAnswer): string;
74
82
  export declare function normalizeCodeReviewDecisionAnswer(value: unknown): CodeReviewDecisionAnswer | null;
@@ -82,5 +90,7 @@ export declare function latestApplyDoneToReviewGate(events: Event[]): {
82
90
  job_id?: string;
83
91
  reason?: string;
84
92
  } | null;
93
+ /** Latest code-review gate fact for the current review cycle, frozen into verifier packets. */
94
+ export declare function latestCodeReviewGateEvidence(events: Event[]): CodeReviewGateEvidence | null;
85
95
  export declare function requiresFinalVerifierForCurrentReview(events: Event[]): boolean;
86
96
  export declare function computeCodeStateCheck(projectRoot: string, events: Event[], ignoredCodePaths?: string[]): CodeStateCheck;
@@ -418,6 +418,7 @@ function taskExecutionIndexFromEvents(projectRoot, events) {
418
418
  entries.push({
419
419
  task_id: payload.task_id,
420
420
  attempt_id: payload.attempt_id,
421
+ ...(attempt?.fix ? { fix: attempt.fix } : {}),
421
422
  execution_policy: attempt?.execution_policy ?? "tdd",
422
423
  changed_paths: changedResult ? changedResult.paths : null,
423
424
  ...(changedResult?.partial_reason ? { changed_paths_partial_reason: changedResult.partial_reason } : {}),
@@ -435,6 +436,10 @@ function taskExecutionIndexFromEvents(projectRoot, events) {
435
436
  entries.sort((a, b) => a.task_id.localeCompare(b.task_id) || a.attempt_id.localeCompare(b.attempt_id));
436
437
  return entries;
437
438
  }
439
+ /** Read-only execution evidence projected for code review and final verification. */
440
+ export function taskExecutionIndexForReview(projectRoot, events) {
441
+ return taskExecutionIndexFromEvents(projectRoot, events);
442
+ }
438
443
  function isCodeReviewerJob(job) {
439
444
  return job.role === "code-reviewer" && REVIEW_CODE_REVIEW_GATE.isJobForGate(job);
440
445
  }
@@ -446,6 +451,18 @@ function codeReviewResultKind(value) {
446
451
  export function codeReviewDecisionScope(jobId, findingId) {
447
452
  return `${CODE_REVIEW_DECISION_SCOPE_PREFIX}${jobId}#${findingId}`;
448
453
  }
454
+ /** 只接受状态机生成的 <job>#<finding> 决策范围。 */
455
+ export function parseCodeReviewDecisionScope(scope) {
456
+ if (!scope.startsWith(CODE_REVIEW_DECISION_SCOPE_PREFIX))
457
+ return null;
458
+ const value = scope.slice(CODE_REVIEW_DECISION_SCOPE_PREFIX.length);
459
+ const separator = value.indexOf("#");
460
+ if (separator <= 0 || separator === value.length - 1)
461
+ return null;
462
+ const jobId = value.slice(0, separator);
463
+ const findingId = value.slice(separator + 1);
464
+ return jobId.trim() !== "" && findingId.trim() !== "" ? { jobId, findingId } : null;
465
+ }
449
466
  export function isCodeReviewDecisionAnswer(value) {
450
467
  return value === "reopen_propose" || value === "reopen_apply" || value === "dismiss";
451
468
  }
@@ -622,6 +639,29 @@ export function latestApplyDoneToReviewGate(events) {
622
639
  }
623
640
  return null;
624
641
  }
642
+ /** Latest code-review gate fact for the current review cycle, frozen into verifier packets. */
643
+ export function latestCodeReviewGateEvidence(events) {
644
+ for (let i = events.length - 1; i >= 0; i--) {
645
+ const event = events[i];
646
+ if (event.event_type !== "transition_commit")
647
+ continue;
648
+ const payload = event.payload;
649
+ if (payload.transition !== "review-ready" || payload.from_state !== "apply_done" || payload.to_state !== "review")
650
+ continue;
651
+ const gate = payload.code_review_gate;
652
+ if (!gate || (gate.decision !== "passed" && gate.decision !== "skipped"))
653
+ return null;
654
+ return {
655
+ decision: gate.decision,
656
+ job_id: typeof gate.job_id === "string" ? gate.job_id : null,
657
+ packet_digest: typeof gate.packet_digest === "string" ? gate.packet_digest : null,
658
+ ...(gate.reason === "no_code_changes" ? { reason: gate.reason } : {}),
659
+ event_id: event.event_id,
660
+ event_digest: event.event_digest,
661
+ };
662
+ }
663
+ return null;
664
+ }
625
665
  export function requiresFinalVerifierForCurrentReview(events) {
626
666
  return latestApplyDoneToReviewGate(events) != null;
627
667
  }
@@ -0,0 +1,23 @@
1
+ import { type DiscoveryQuestion } from "./format.ts";
2
+ import type { Event } from "./types.ts";
3
+ /**
4
+ * 当前 Explore 轮次的稳定标识。
5
+ *
6
+ * 同一轮内补充调查或创建审查工作项不会使当前确认事项失效;只有首次进入
7
+ * Explore 或从后续阶段重新打开 Explore 时,才开始新的确认轮次。
8
+ */
9
+ export declare function currentExploreRoundId(events: readonly Event[]): string;
10
+ /** 在进入新 Explore 轮次时冻结已有已确认事项,避免升级时追溯历史答复。 */
11
+ export declare function exploreAnswerRegistrationPayload(discoveryContent: string | null): {
12
+ explore_answer_registration: {
13
+ version: number;
14
+ baseline_closed_question_keys: string[];
15
+ };
16
+ };
17
+ /** 当前确认事项已通过本轮主流程登记过答复。 */
18
+ export declare function exploreAnswerWasRecorded(events: readonly Event[], roundId: string, question: Pick<DiscoveryQuestion, "id" | "ordinal">, discoveryContent: string): boolean;
19
+ /**
20
+ * 新协议轮次中,被回写为已确认的事项必须有对应答复记录。
21
+ * 进入本轮前已经关闭的事项作为基线保留,历史 change 因缺少协议标记整体兼容。
22
+ */
23
+ export declare function unregisteredClosedExploreQuestions(events: readonly Event[], discoveryContent: string): DiscoveryQuestion[];
@@ -0,0 +1,94 @@
1
+ import { discoveryQuestionContextFingerprint, discoveryQuestionKey, parseDiscoveryQuestions, } from "./format.js";
2
+ const EXPLORE_ANSWER_REGISTRATION_VERSION = 1;
3
+ function isExploreRoundEntry(event) {
4
+ if (event.event_type !== "transition_commit")
5
+ return false;
6
+ const payload = event.payload;
7
+ return payload.transition === "explore" && payload.from_state === "init" && payload.to_state === "explore" ||
8
+ payload.transition === "reopen" && payload.reopen_target === "explore";
9
+ }
10
+ function currentExploreRoundEntry(events) {
11
+ for (let index = events.length - 1; index >= 0; index--) {
12
+ const event = events[index];
13
+ if (isExploreRoundEntry(event))
14
+ return { event, roundId: event.event_id };
15
+ }
16
+ return null;
17
+ }
18
+ /**
19
+ * 当前 Explore 轮次的稳定标识。
20
+ *
21
+ * 同一轮内补充调查或创建审查工作项不会使当前确认事项失效;只有首次进入
22
+ * Explore 或从后续阶段重新打开 Explore 时,才开始新的确认轮次。
23
+ */
24
+ export function currentExploreRoundId(events) {
25
+ return currentExploreRoundEntry(events)?.roundId ?? "legacy-explore-round";
26
+ }
27
+ /** 在进入新 Explore 轮次时冻结已有已确认事项,避免升级时追溯历史答复。 */
28
+ export function exploreAnswerRegistrationPayload(discoveryContent) {
29
+ const baselineClosedQuestionKeys = discoveryContent == null
30
+ ? []
31
+ : parseDiscoveryQuestions(discoveryContent)
32
+ .filter(question => question.status === "closed")
33
+ .map(discoveryQuestionKey)
34
+ .sort();
35
+ return {
36
+ explore_answer_registration: {
37
+ version: EXPLORE_ANSWER_REGISTRATION_VERSION,
38
+ baseline_closed_question_keys: baselineClosedQuestionKeys,
39
+ },
40
+ };
41
+ }
42
+ function currentExploreAnswerRegistration(events) {
43
+ const entry = currentExploreRoundEntry(events);
44
+ if (!entry) {
45
+ return {
46
+ enabled: false,
47
+ roundId: "legacy-explore-round",
48
+ baselineClosedQuestionKeys: new Set(),
49
+ };
50
+ }
51
+ const payload = entry.event.payload;
52
+ const registration = payload.explore_answer_registration;
53
+ if (registration?.version !== EXPLORE_ANSWER_REGISTRATION_VERSION || !Array.isArray(registration.baseline_closed_question_keys)) {
54
+ return {
55
+ enabled: false,
56
+ roundId: entry.roundId,
57
+ baselineClosedQuestionKeys: new Set(),
58
+ };
59
+ }
60
+ return {
61
+ enabled: true,
62
+ roundId: entry.roundId,
63
+ baselineClosedQuestionKeys: new Set(registration.baseline_closed_question_keys.filter((key) => typeof key === "string")),
64
+ };
65
+ }
66
+ /** 当前确认事项已通过本轮主流程登记过答复。 */
67
+ export function exploreAnswerWasRecorded(events, roundId, question, discoveryContent) {
68
+ const currentContextFingerprint = discoveryQuestionContextFingerprint(discoveryContent, question);
69
+ if (!currentContextFingerprint)
70
+ return false;
71
+ return events.some(event => {
72
+ if (event.event_type !== "user_decision_recorded")
73
+ return false;
74
+ const payload = event.payload;
75
+ const recorded = payload.explore_open_question;
76
+ return payload.accepted === true &&
77
+ recorded?.round_id === roundId &&
78
+ recorded.question_id === question.id &&
79
+ recorded.question_ordinal === question.ordinal &&
80
+ recorded.context_fingerprint === currentContextFingerprint;
81
+ });
82
+ }
83
+ /**
84
+ * 新协议轮次中,被回写为已确认的事项必须有对应答复记录。
85
+ * 进入本轮前已经关闭的事项作为基线保留,历史 change 因缺少协议标记整体兼容。
86
+ */
87
+ export function unregisteredClosedExploreQuestions(events, discoveryContent) {
88
+ const registration = currentExploreAnswerRegistration(events);
89
+ if (!registration.enabled)
90
+ return [];
91
+ return parseDiscoveryQuestions(discoveryContent).filter(question => question.status === "closed" &&
92
+ !registration.baselineClosedQuestionKeys.has(discoveryQuestionKey(question)) &&
93
+ !exploreAnswerWasRecorded(events, registration.roundId, question, discoveryContent));
94
+ }
package/dist/format.d.ts CHANGED
@@ -1,5 +1,44 @@
1
1
  import { type ExecutionContract, type ExecutionPolicy } from "./types.ts";
2
- /** discovery.md 提取"待确认问题"段内的未确认项数量 */
2
+ export declare const EXPLORE_OPEN_QUESTION_SCOPE_PREFIX = "explore_open_question:";
3
+ /**
4
+ * Discovery 的未确认项是用户决策的唯一候选来源。这里保留原有的“仅指定段落内
5
+ * checkbox 有效”语义;ID 缺失的历史文档按所有 checklist 在该段落中的稳定顺序
6
+ * 使用 item-N,避免升级时要求迁移历史 change。
7
+ */
8
+ export interface DiscoveryOpenQuestion {
9
+ /** Q-xxx;历史材料没有 ID 时为 item-N。 */
10
+ id: string;
11
+ /** 在“待确认问题”段落中所有 checklist 的 1-based 顺序。 */
12
+ ordinal: number;
13
+ /** 去掉 markdown checkbox 后的原始内容,用于留痕与内部匹配。 */
14
+ text: string;
15
+ /** 包含 checkbox 的原始 Markdown 行,供诊断和测试使用。 */
16
+ raw: string;
17
+ /** 当前完整 discovery.md 的内容指纹,避免只改决策依据时沿用旧答复。 */
18
+ documentFingerprint: string;
19
+ }
20
+ /** Discovery 待确认段中的一项;状态机需要同时识别待答复和已回写的项。 */
21
+ export interface DiscoveryQuestion extends DiscoveryOpenQuestion {
22
+ status: "open" | "closed";
23
+ }
24
+ /**
25
+ * 当前文档中同一确认事项的稳定键。结合 Q-ID、顺序和原文,避免把本轮开始前
26
+ * 已经确认的旧事项误认为新答复。
27
+ */
28
+ export declare function discoveryQuestionKey(question: Pick<DiscoveryQuestion, "id" | "ordinal" | "text">): string;
29
+ /**
30
+ * 计算某一确认事项之外的 Discovery 决策上下文。回写时该事项本身会从问题改为
31
+ * 结论,因此只归一化这一行;其余事实、证据和其它待确认项的改动都会使指纹失效。
32
+ */
33
+ export declare function discoveryQuestionContextFingerprint(content: string, question: Pick<DiscoveryQuestion, "id" | "ordinal">): string | null;
34
+ /** 按文档顺序提取 discovery.md 的全部确认事项。 */
35
+ export declare function parseDiscoveryQuestions(content: string): DiscoveryQuestion[];
36
+ /** 按文档顺序提取 discovery.md 中尚未确认的问题。 */
37
+ export declare function parseDiscoveryOpenQuestions(content: string): DiscoveryOpenQuestion[];
38
+ export declare function discoveryOpenQuestionScope(question: Pick<DiscoveryOpenQuestion, "id" | "documentFingerprint">, exploreRoundId: string): string;
39
+ /** 面向用户展示时隐藏 Q-xxx 这一内部编号;历史无编号问题保持原文。 */
40
+ export declare function discoveryOpenQuestionDisplayText(question: Pick<DiscoveryOpenQuestion, "id" | "text">): string;
41
+ /** 从 discovery.md 提取“待确认问题”段内的未确认项数量。 */
3
42
  export declare function countDiscoveryOpenQuestions(content: string): number;
4
43
  export interface DiscoveryChainCoverageCheck {
5
44
  ok: boolean;
@@ -9,7 +48,10 @@ export interface DiscoveryChainCoverageCheck {
9
48
  export declare function splitMarkdownTableRow(line: string): string[];
10
49
  /** 轻量校验 discovery.md 的链路五要素段。只校验结构和阻塞未知,不判断业务真假。 */
11
50
  export declare function validateDiscoveryChainCoverage(content: string): DiscoveryChainCoverageCheck;
12
- /** 完整校验 discovery.md:存在 + 非空 + 无未确认问题 */
51
+ /**
52
+ * 校验 discovery.md 的可解析结构。未确认问题不是格式错误:next 会把第一个问题
53
+ * 作为当前用户决策返回;只有缺文档、空文档或已声明链路的结构错误才在此阻断。
54
+ */
13
55
  export declare function validateDiscovery(changeRoot: string): {
14
56
  ok: boolean;
15
57
  message: string;
@@ -53,6 +95,11 @@ export type TestContractParseResult = {
53
95
  };
54
96
  /** 解析 tasks.md 的全部任务行 */
55
97
  export declare function parseTasksMd(content: string): ParsedTask[];
98
+ /**
99
+ * tasks.md 的机械结构校验。任务是否拆分合理、顺序是否符合真实依赖仍由
100
+ * Critic/Architect 判断;这里仅拒绝引擎无法可靠驱动的格式。
101
+ */
102
+ export declare function validateTasksDocument(content: string): string[];
56
103
  export declare function hasTaskBoundExecutionRequirements(content: string): boolean;
57
104
  export declare function parseExecutionRequirements(content: string): ParsedExecutionRequirement[];
58
105
  export declare function orphanExecutionRequirementErrors(content: string): string[];
@@ -61,15 +108,33 @@ export declare function adoptedContractForTask(content: string, taskId: string,
61
108
  parsed: ParsedExecutionRequirement | null;
62
109
  contract: ExecutionContract | null;
63
110
  };
111
+ /**
112
+ * Fix task 由状态机从已批准的实现范围派生;它没有 proposal 阶段执行依据块。
113
+ * REVIEW-FIX-* 是发布前已有的持久化 task ID,FIX-SELFTEST-* 是当前引擎生成的
114
+ * 自测修复 task。不能把通用 FIX-* 前缀保留为内部命名空间。
115
+ */
116
+ export declare function isFixTaskId(taskId: string): boolean;
117
+ /** @deprecated 新代码使用 isFixTaskId;保留给旧扩展和历史调用。 */
64
118
  export declare function isReviewFixTaskId(taskId: string): boolean;
65
119
  export declare function isCharacterizationTask(task: ParsedTask): boolean;
66
120
  export declare function parseTestContractEntries(content: string): TestContractParseResult;
121
+ export interface ProposalImpactValidation {
122
+ ok: boolean;
123
+ message: string;
124
+ }
125
+ /** OpenSpec 项目的 proposal 采用固定 Impact 表格,供状态机进行纯结构校验。 */
126
+ export declare function validateProposalImpact(content: string): ProposalImpactValidation;
67
127
  export interface ExecutionRequirementValidation {
68
128
  ok: boolean;
69
129
  mode: boolean;
70
130
  contracts: ParsedExecutionRequirement[];
71
131
  errors: string[];
72
132
  }
133
+ /**
134
+ * 在已初始化的当前工作流中,把执行依据的文件/锚点可解析性作为状态机协议。
135
+ * “该材料是否足以支撑 task”仍然是 Critic/Architect 的语义判断。
136
+ */
137
+ export declare function validateExecutionRequirementDocumentReferences(changeRoot: string, contracts: readonly ParsedExecutionRequirement[]): string[];
73
138
  export declare function validateExecutionRequirements(content: string, testContractContent: string | null, executionPolicy?: ExecutionPolicy, executionRequirementVersion?: 1 | 2): ExecutionRequirementValidation;
74
139
  /** 返回未完成任务 */
75
140
  export declare function pendingTasksInContent(content: string): ParsedTask[];