@peterxiaoyang/superspec 0.1.46 → 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.
- package/dist/cli.js +2 -1
- package/dist/code_review.d.ts +11 -1
- package/dist/code_review.js +40 -0
- package/dist/explore_round.d.ts +23 -0
- package/dist/explore_round.js +94 -0
- package/dist/format.d.ts +51 -2
- package/dist/format.js +106 -12
- package/dist/openspec.js +26 -5
- package/dist/phase_confirmation.js +71 -2
- package/dist/phase_plan.d.ts +3 -0
- package/dist/phase_plan.js +56 -12
- package/dist/record.js +191 -57
- package/dist/review.js +2 -0
- package/dist/task_evidence.js +5 -3
- package/dist/transition.d.ts +1 -0
- package/dist/transition.js +218 -28
- package/dist/types.d.ts +28 -0
- package/package.json +1 -1
- package/templates/workflow/AGENTS.md +15 -5
- package/templates/workflow/prompts/architect.md +2 -2
- package/templates/workflow/prompts/code-reviewer.md +3 -3
- package/templates/workflow/prompts/critic.md +2 -2
- package/templates/workflow/prompts/executor.md +5 -5
- package/templates/workflow/prompts/explore.md +2 -2
- package/templates/workflow/prompts/test-engineer.md +5 -5
- package/templates/workflow/prompts/test-runner.md +5 -5
- package/templates/workflow/prompts/verifier.md +4 -4
- package/templates/workflow/skills/superspec-apply/SKILL.md +20 -10
- package/templates/workflow/skills/superspec-explore/SKILL.md +19 -5
- package/templates/workflow/skills/superspec-propose/SKILL.md +21 -16
- package/templates/workflow/skills/superspec-review/SKILL.md +9 -9
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);
|
package/dist/code_review.d.ts
CHANGED
|
@@ -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;
|
package/dist/code_review.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 校验 discovery.md 的可解析结构。未确认问题不是格式错误:next 会把第一个问题
|
|
53
|
+
* 作为当前用户决策返回;只有缺文档、空文档或已声明链路的结构错误才在此阻断。
|
|
54
|
+
*/
|
|
13
55
|
export declare function validateDiscovery(changeRoot: string): {
|
|
14
56
|
ok: boolean;
|
|
15
57
|
message: string;
|
|
@@ -66,6 +108,13 @@ export declare function adoptedContractForTask(content: string, taskId: string,
|
|
|
66
108
|
parsed: ParsedExecutionRequirement | null;
|
|
67
109
|
contract: ExecutionContract | null;
|
|
68
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;保留给旧扩展和历史调用。 */
|
|
69
118
|
export declare function isReviewFixTaskId(taskId: string): boolean;
|
|
70
119
|
export declare function isCharacterizationTask(task: ParsedTask): boolean;
|
|
71
120
|
export declare function parseTestContractEntries(content: string): TestContractParseResult;
|
package/dist/format.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// 生成与语义判断,不得自行充当格式校验器或在其它地方重复解析。
|
|
5
5
|
import { readFileSync, existsSync, realpathSync } from "node:fs";
|
|
6
6
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { sha256Text } from "./store.js";
|
|
7
8
|
import { GREEN_ONLY_NO_TDD_REASON } from "./types.js";
|
|
8
9
|
// ===== discovery.md =====
|
|
9
10
|
//
|
|
@@ -24,7 +25,7 @@ function countOpenChecklistItemsInSection(content, headings) {
|
|
|
24
25
|
const matches = sectionBody.match(/^\s*-\s+\[ \]/gm);
|
|
25
26
|
return matches ? matches.length : 0;
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
+
function sectionRangeByHeadings(content, headings) {
|
|
28
29
|
const headingPattern = headings.map(escapeRegex).join("|");
|
|
29
30
|
const sectionMatch = new RegExp(`^#{1,6}\\s*(?:${headingPattern})\\s*$`, "im").exec(content);
|
|
30
31
|
if (!sectionMatch)
|
|
@@ -33,11 +34,90 @@ function sectionBodyByHeadings(content, headings) {
|
|
|
33
34
|
// 截取到下一个标题或文件末尾
|
|
34
35
|
const restContent = content.slice(sectionStart);
|
|
35
36
|
const nextHeadingMatch = restContent.match(/^#{1,6}\s+/m);
|
|
36
|
-
return
|
|
37
|
+
return {
|
|
38
|
+
start: sectionStart,
|
|
39
|
+
end: nextHeadingMatch ? sectionStart + nextHeadingMatch.index : content.length,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function sectionBodyByHeadings(content, headings) {
|
|
43
|
+
const range = sectionRangeByHeadings(content, headings);
|
|
44
|
+
return range ? content.slice(range.start, range.end) : null;
|
|
45
|
+
}
|
|
46
|
+
const DISCOVERY_QUESTION_HEADINGS = ["待确认问题", "Open Questions", "Pending Questions"];
|
|
47
|
+
export const EXPLORE_OPEN_QUESTION_SCOPE_PREFIX = "explore_open_question:";
|
|
48
|
+
/**
|
|
49
|
+
* 当前文档中同一确认事项的稳定键。结合 Q-ID、顺序和原文,避免把本轮开始前
|
|
50
|
+
* 已经确认的旧事项误认为新答复。
|
|
51
|
+
*/
|
|
52
|
+
export function discoveryQuestionKey(question) {
|
|
53
|
+
return sha256Text(`${question.id}\n${question.ordinal}\n${question.text}`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 计算某一确认事项之外的 Discovery 决策上下文。回写时该事项本身会从问题改为
|
|
57
|
+
* 结论,因此只归一化这一行;其余事实、证据和其它待确认项的改动都会使指纹失效。
|
|
58
|
+
*/
|
|
59
|
+
export function discoveryQuestionContextFingerprint(content, question) {
|
|
60
|
+
const range = sectionRangeByHeadings(content, DISCOVERY_QUESTION_HEADINGS);
|
|
61
|
+
if (!range)
|
|
62
|
+
return null;
|
|
63
|
+
const sectionBody = content.slice(range.start, range.end);
|
|
64
|
+
const checklist = /^\s*-\s+\[([ xX])\]\s+(.*?)\s*$/gm;
|
|
65
|
+
let ordinal = 0;
|
|
66
|
+
for (const match of sectionBody.matchAll(checklist)) {
|
|
67
|
+
ordinal += 1;
|
|
68
|
+
if (ordinal !== question.ordinal)
|
|
69
|
+
continue;
|
|
70
|
+
const lineStart = range.start + match.index;
|
|
71
|
+
const lineEnd = lineStart + match[0].length;
|
|
72
|
+
const placeholder = `- [ ] <discovery-question:${question.id}:${question.ordinal}>`;
|
|
73
|
+
return sha256Text(`${content.slice(0, lineStart)}${placeholder}${content.slice(lineEnd)}`);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/** 按文档顺序提取 discovery.md 的全部确认事项。 */
|
|
78
|
+
export function parseDiscoveryQuestions(content) {
|
|
79
|
+
const sectionBody = sectionBodyByHeadings(content, DISCOVERY_QUESTION_HEADINGS);
|
|
80
|
+
if (sectionBody == null)
|
|
81
|
+
return [];
|
|
82
|
+
const documentFingerprint = sha256Text(content);
|
|
83
|
+
const questions = [];
|
|
84
|
+
const checklist = /^\s*-\s+\[([ xX])\]\s+(.*?)\s*$/gm;
|
|
85
|
+
let ordinal = 0;
|
|
86
|
+
for (const match of sectionBody.matchAll(checklist)) {
|
|
87
|
+
ordinal += 1;
|
|
88
|
+
const text = match[2];
|
|
89
|
+
const idMatch = /^\s*(Q-[A-Za-z0-9][A-Za-z0-9_-]*)\b/.exec(text);
|
|
90
|
+
questions.push({
|
|
91
|
+
id: idMatch?.[1] ?? `item-${ordinal}`,
|
|
92
|
+
ordinal,
|
|
93
|
+
text,
|
|
94
|
+
raw: match[0],
|
|
95
|
+
documentFingerprint,
|
|
96
|
+
status: match[1] === " " ? "open" : "closed",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return questions;
|
|
100
|
+
}
|
|
101
|
+
/** 按文档顺序提取 discovery.md 中尚未确认的问题。 */
|
|
102
|
+
export function parseDiscoveryOpenQuestions(content) {
|
|
103
|
+
return parseDiscoveryQuestions(content)
|
|
104
|
+
.filter(question => question.status === "open")
|
|
105
|
+
.map(({ status: _status, ...question }) => question);
|
|
106
|
+
}
|
|
107
|
+
export function discoveryOpenQuestionScope(question, exploreRoundId) {
|
|
108
|
+
const fingerprint = sha256Text(`${exploreRoundId}\n${question.documentFingerprint}`);
|
|
109
|
+
return `${EXPLORE_OPEN_QUESTION_SCOPE_PREFIX}${fingerprint}:${question.id}`;
|
|
110
|
+
}
|
|
111
|
+
/** 面向用户展示时隐藏 Q-xxx 这一内部编号;历史无编号问题保持原文。 */
|
|
112
|
+
export function discoveryOpenQuestionDisplayText(question) {
|
|
113
|
+
if (question.id.startsWith("Q-") && question.text.startsWith(question.id)) {
|
|
114
|
+
return question.text.slice(question.id.length).replace(/^[\s::—–-]+/, "").trim();
|
|
115
|
+
}
|
|
116
|
+
return question.text.trim();
|
|
37
117
|
}
|
|
38
|
-
/** 从 discovery.md
|
|
118
|
+
/** 从 discovery.md 提取“待确认问题”段内的未确认项数量。 */
|
|
39
119
|
export function countDiscoveryOpenQuestions(content) {
|
|
40
|
-
return
|
|
120
|
+
return parseDiscoveryOpenQuestions(content).length;
|
|
41
121
|
}
|
|
42
122
|
const DISCOVERY_CHAIN_HEADINGS = ["链路五要素"];
|
|
43
123
|
const DISCOVERY_CHAIN_REQUIRED_COLUMNS = [
|
|
@@ -105,7 +185,10 @@ export function validateDiscoveryChainCoverage(content) {
|
|
|
105
185
|
}
|
|
106
186
|
return { ok: true, message: "链路五要素就绪", present: true };
|
|
107
187
|
}
|
|
108
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* 校验 discovery.md 的可解析结构。未确认问题不是格式错误:next 会把第一个问题
|
|
190
|
+
* 作为当前用户决策返回;只有缺文档、空文档或已声明链路的结构错误才在此阻断。
|
|
191
|
+
*/
|
|
109
192
|
export function validateDiscovery(changeRoot) {
|
|
110
193
|
const path = join(changeRoot, ".superspec", "artifacts", "discovery.md");
|
|
111
194
|
if (!existsSync(path))
|
|
@@ -117,9 +200,11 @@ export function validateDiscovery(changeRoot) {
|
|
|
117
200
|
if (!chainCoverage.ok)
|
|
118
201
|
return { ok: false, message: chainCoverage.message, openCount: -1 };
|
|
119
202
|
const openCount = countDiscoveryOpenQuestions(content);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
message: openCount > 0 ? `discovery.md 结构有效,有 ${openCount} 个待确认问题` : "discovery.md 就绪",
|
|
206
|
+
openCount,
|
|
207
|
+
};
|
|
123
208
|
}
|
|
124
209
|
const PROPOSE_CONFIRMATION_DOCS = [
|
|
125
210
|
"proposal.md",
|
|
@@ -327,8 +412,17 @@ export function adoptedContractForTask(content, taskId, contractMode) {
|
|
|
327
412
|
const parsed = executionRequirementForTask(content, taskId);
|
|
328
413
|
return { parsed, contract: contractMode ? parsed?.contract ?? null : null };
|
|
329
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Fix task 由状态机从已批准的实现范围派生;它没有 proposal 阶段执行依据块。
|
|
417
|
+
* REVIEW-FIX-* 是发布前已有的持久化 task ID,FIX-SELFTEST-* 是当前引擎生成的
|
|
418
|
+
* 自测修复 task。不能把通用 FIX-* 前缀保留为内部命名空间。
|
|
419
|
+
*/
|
|
420
|
+
export function isFixTaskId(taskId) {
|
|
421
|
+
return taskId.startsWith("REVIEW-FIX-") || taskId.startsWith("FIX-SELFTEST-");
|
|
422
|
+
}
|
|
423
|
+
/** @deprecated 新代码使用 isFixTaskId;保留给旧扩展和历史调用。 */
|
|
330
424
|
export function isReviewFixTaskId(taskId) {
|
|
331
|
-
return taskId
|
|
425
|
+
return isFixTaskId(taskId);
|
|
332
426
|
}
|
|
333
427
|
export function isCharacterizationTask(task) {
|
|
334
428
|
return task.tddRequired === false && task.noTddReason === "characterization";
|
|
@@ -510,7 +604,7 @@ export function validateExecutionRequirements(content, testContractContent, exec
|
|
|
510
604
|
const hasLegacyGreenOnlyTask = tasks.some(task => task.noTddReason === GREEN_ONLY_NO_TDD_REASON);
|
|
511
605
|
// v2 不允许“所有任务都没有执行依据”这一静默回退:新 Propose 的每个普通
|
|
512
606
|
// task 都必须显式声明五字段。v1 的缺失版本仍保留旧的按需契约语义。
|
|
513
|
-
const hasV2OrdinaryTask = executionRequirementVersion === 2 && tasks.some(task => !
|
|
607
|
+
const hasV2OrdinaryTask = executionRequirementVersion === 2 && tasks.some(task => !isFixTaskId(task.taskId));
|
|
514
608
|
const mode = hasTaskBoundExecutionRequirements(content) || hasLegacyGreenOnlyTask || hasV2OrdinaryTask;
|
|
515
609
|
const contracts = parseExecutionRequirements(content);
|
|
516
610
|
// 孤儿检测必须在 mode=false 的 early return 之前:全部块都悬空时 mode=false,
|
|
@@ -532,11 +626,11 @@ export function validateExecutionRequirements(content, testContractContent, exec
|
|
|
532
626
|
}
|
|
533
627
|
// v2 是本次改造后的新计划:每个普通 task 必须声明完整五字段,
|
|
534
628
|
// `测试:` 可显式为空以表达非行为任务。旧计划只保持原 TDD 契约要求。
|
|
535
|
-
if (executionRequirementVersion === 2 && !
|
|
629
|
+
if (executionRequirementVersion === 2 && !isFixTaskId(task.taskId) && !contract) {
|
|
536
630
|
errors.push(`${task.taskId} 缺少执行依据`);
|
|
537
631
|
continue;
|
|
538
632
|
}
|
|
539
|
-
if (executionRequirementVersion === 1 && task.tddRequired && !
|
|
633
|
+
if (executionRequirementVersion === 1 && task.tddRequired && !isFixTaskId(task.taskId) && !contract) {
|
|
540
634
|
errors.push(`${task.taskId} 缺少执行依据`);
|
|
541
635
|
continue;
|
|
542
636
|
}
|
package/dist/openspec.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
// SuperSpec 流程引擎 — OpenSpec 探测
|
|
2
|
-
import {
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
+
function isTestMode() {
|
|
7
|
+
return process.env.SUPERSPEC_TEST_MODE === "1" || process.env.NODE_ENV === "test";
|
|
8
|
+
}
|
|
9
|
+
function runtimePlatform() {
|
|
10
|
+
return isTestMode() ? process.env.SUPERSPEC_TEST_PLATFORM ?? process.platform : process.platform;
|
|
11
|
+
}
|
|
12
|
+
function windowsCommandHost() {
|
|
13
|
+
return process.env.ComSpec ?? "cmd.exe";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 在 Windows 上,npm 安装的 CLI 是 .cmd 启动器,不能被 execFileSync 直接执行。
|
|
17
|
+
* 保留数组参数传递;只有启动器这一层通过 cmd.exe 运行。
|
|
18
|
+
*/
|
|
19
|
+
function execOpenSpec(args, options) {
|
|
20
|
+
if (runtimePlatform() !== "win32")
|
|
21
|
+
return execFileSync("openspec", args, options);
|
|
22
|
+
return execFileSync(windowsCommandHost(), ["/d", "/s", "/c", "openspec.cmd", ...args], options);
|
|
23
|
+
}
|
|
6
24
|
/** B2 修复:校验 change 字符集,防 shell 注入 */
|
|
7
25
|
function validateChange(change) {
|
|
8
26
|
if (!/^[A-Za-z0-9._-]+$/.test(change)) {
|
|
@@ -11,7 +29,10 @@ function validateChange(change) {
|
|
|
11
29
|
}
|
|
12
30
|
export function probeOpenSpec(projectRoot, change) {
|
|
13
31
|
try {
|
|
14
|
-
const version =
|
|
32
|
+
const version = execOpenSpec(["--version"], {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
35
|
+
}).trim();
|
|
15
36
|
let changeExists = false;
|
|
16
37
|
if (change) {
|
|
17
38
|
validateChange(change);
|
|
@@ -26,8 +47,8 @@ export function probeOpenSpec(projectRoot, change) {
|
|
|
26
47
|
export function openspecStatus(projectRoot, change) {
|
|
27
48
|
validateChange(change);
|
|
28
49
|
try {
|
|
29
|
-
// B2
|
|
30
|
-
const result =
|
|
50
|
+
// B2 修复:参数始终走数组;Windows 仅通过 cmd.exe 运行 .cmd 启动器。
|
|
51
|
+
const result = execOpenSpec(["status", "--change", change, "--json"], {
|
|
31
52
|
encoding: "utf8", cwd: projectRoot, stdio: ["pipe", "pipe", "ignore"],
|
|
32
53
|
});
|
|
33
54
|
return "sha256:" + createHash("sha256").update(result).digest("hex");
|
|
@@ -45,7 +66,7 @@ export function openspecStatus(projectRoot, change) {
|
|
|
45
66
|
export function validateOpenSpecChange(projectRoot, change) {
|
|
46
67
|
validateChange(change);
|
|
47
68
|
try {
|
|
48
|
-
|
|
69
|
+
execOpenSpec(["validate", change, "--type", "change", "--strict", "--no-interactive"], {
|
|
49
70
|
cwd: projectRoot,
|
|
50
71
|
encoding: "utf8",
|
|
51
72
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1,10 +1,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { currentGitHead, dirtyCodeFiles } from "./git_state.js";
|
|
2
4
|
import { historicalProposeReadyRoles, reviewEvidenceDigest, reviewGateRoleResolution } from "./review.js";
|
|
3
5
|
import { EXPLORE_DISCOVERY_REVIEW_GATE, PROPOSE_FINAL_REVIEW_GATE } from "./review_job_gates.js";
|
|
4
6
|
import { changeRoot as openspecChangeRoot } from "./openspec.js";
|
|
5
7
|
import { findLatestEvent, sha256Text } from "./store.js";
|
|
8
|
+
import { parseExecutionRequirements, parseTasksMd, parseTestContractEntries } from "./format.js";
|
|
6
9
|
import { hasFrozenWorkflowModeForProposeRound, workflowRiskForProject, workflowRiskForState, } from "./workflow_config.js";
|
|
7
10
|
export const PHASE_CONFIRMATION_SCOPE_PREFIX = "phase_confirmation:";
|
|
11
|
+
function taskTitle(content, task) {
|
|
12
|
+
const line = content.split("\n")[task.lineIdx] ?? "";
|
|
13
|
+
return line.replace(/^-\s+\[[ xX]\]\s+\S+\s*/, "").trim() || task.taskId;
|
|
14
|
+
}
|
|
15
|
+
function taskSummaryField(lines, task, labels) {
|
|
16
|
+
for (let index = task.lineIdx + 1; index < lines.length; index++) {
|
|
17
|
+
const line = lines[index];
|
|
18
|
+
if (/^-\s+\[[ xX]\]\s+\S+/.test(line) || /^#{1,6}\s+/.test(line))
|
|
19
|
+
break;
|
|
20
|
+
const match = /^\s*-\s*([^::]+)\s*[::]\s*(.+?)\s*$/.exec(line);
|
|
21
|
+
if (match && labels.includes(match[1].trim()))
|
|
22
|
+
return match[2].trim();
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 这是阶段确认的临时阅读面,不是新的计划格式或 gate。优先读取 task 已声明的
|
|
28
|
+
* 交付/依赖/验收;缺省项如实标为未声明,避免将任务顺序臆造成真实依赖。
|
|
29
|
+
*/
|
|
30
|
+
function proposeTaskDeliverySummary(changeRoot) {
|
|
31
|
+
const tasksPath = join(changeRoot, "tasks.md");
|
|
32
|
+
if (!existsSync(tasksPath))
|
|
33
|
+
return null;
|
|
34
|
+
const tasksContent = readFileSync(tasksPath, "utf8");
|
|
35
|
+
const tasks = parseTasksMd(tasksContent);
|
|
36
|
+
if (tasks.length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
const contracts = new Map(parseExecutionRequirements(tasksContent).map(item => [item.taskId, item.contract]));
|
|
39
|
+
const testContractPath = join(changeRoot, ".superspec", "artifacts", "test-contract.md");
|
|
40
|
+
const parsedTests = existsSync(testContractPath)
|
|
41
|
+
? parseTestContractEntries(readFileSync(testContractPath, "utf8"))
|
|
42
|
+
: null;
|
|
43
|
+
const scenarios = parsedTests?.ok
|
|
44
|
+
? new Map(parsedTests.entries.map(entry => [entry.test_id, entry.scenario]))
|
|
45
|
+
: new Map();
|
|
46
|
+
const lines = tasksContent.split("\n");
|
|
47
|
+
// Propose 展示的是当前计划,不是旧 Apply 轮的执行历史。重新打开同一 task 时,
|
|
48
|
+
// 当前 tasks.md 中的未勾选状态明确表示它已重新纳入本轮待实施范围。
|
|
49
|
+
const pendingTasks = tasks.filter(task => !task.done);
|
|
50
|
+
const completedCount = tasks.length - pendingTasks.length;
|
|
51
|
+
const items = pendingTasks.map((task, index) => {
|
|
52
|
+
const contract = contracts.get(task.taskId);
|
|
53
|
+
const explicitDelivery = taskSummaryField(lines, task, ["交付", "Delivery"]);
|
|
54
|
+
const explicitDependency = taskSummaryField(lines, task, ["依赖", "Dependencies", "Depends On"]);
|
|
55
|
+
const firstTest = contract?.tests[0];
|
|
56
|
+
const testScenario = firstTest ? scenarios.get(firstTest) : null;
|
|
57
|
+
const delivery = explicitDelivery ?? contract?.acceptance ?? taskTitle(tasksContent, task);
|
|
58
|
+
const dependency = explicitDependency ?? "无明确前置交付";
|
|
59
|
+
const acceptance = contract?.acceptance
|
|
60
|
+
?? (testScenario ? `${firstTest}:${testScenario}` : "计划未单独声明验收");
|
|
61
|
+
return [
|
|
62
|
+
`${index + 1}. ${task.taskId} ${taskTitle(tasksContent, task)}`,
|
|
63
|
+
` - 交付:${delivery}`,
|
|
64
|
+
` - 依赖:${dependency}`,
|
|
65
|
+
` - 验收:${acceptance}`,
|
|
66
|
+
].join("\n");
|
|
67
|
+
});
|
|
68
|
+
const status = completedCount === 0
|
|
69
|
+
? ""
|
|
70
|
+
: `已完成 ${completedCount} 项;${pendingTasks.length === 0 ? "当前没有待实施任务。" : `以下 ${pendingTasks.length} 项仍待实施或调整。`}\n\n`;
|
|
71
|
+
return `执行计划概览\n\n${status}${items.join("\n\n")}`;
|
|
72
|
+
}
|
|
8
73
|
function latestTransition(events, predicate) {
|
|
9
74
|
return findLatestEvent(events, "transition_commit", event => predicate(event.payload));
|
|
10
75
|
}
|
|
@@ -187,7 +252,11 @@ export function phaseConfirmationForBoundary(projectRoot, events, snapshot, boun
|
|
|
187
252
|
const epochEventId = epoch?.event_id ?? `legacy-${spec.state}`;
|
|
188
253
|
const digest = materialDigest(projectRoot, events, snapshot, boundary, risk);
|
|
189
254
|
const scope = `${spec.scopePrefix}:${epochEventId}:${digest}`;
|
|
190
|
-
const
|
|
255
|
+
const summary = boundary === "propose_to_apply"
|
|
256
|
+
? proposeTaskDeliverySummary(openspecChangeRoot(projectRoot, snapshot.change_id))
|
|
257
|
+
: null;
|
|
258
|
+
const question = summary ? `${summary}\n\n${spec.question}` : spec.question;
|
|
259
|
+
const actions = buildActions(snapshot.change_id, boundary, scope, question, spec.actions, risk);
|
|
191
260
|
return {
|
|
192
261
|
boundary,
|
|
193
262
|
epoch_event_id: epochEventId,
|
|
@@ -195,7 +264,7 @@ export function phaseConfirmationForBoundary(projectRoot, events, snapshot, boun
|
|
|
195
264
|
scope,
|
|
196
265
|
actions,
|
|
197
266
|
ask: {
|
|
198
|
-
question
|
|
267
|
+
question,
|
|
199
268
|
allowed_answers: actions.map(action => action.label),
|
|
200
269
|
scope,
|
|
201
270
|
actions,
|
package/dist/phase_plan.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReviewGateRule } from "./review_job_gates.ts";
|
|
2
|
+
import { exploreAnswerRegistrationPayload } from "./explore_round.ts";
|
|
2
3
|
import type { AcceptedMaterialFollowupContinuation, AskUser, Event, ExecutionPolicy, Job, JobRole, PlanningValidationProfile, State } from "./types.ts";
|
|
3
4
|
import type { Snapshot } from "./types.ts";
|
|
4
5
|
import type { ReviewRisk } from "./review.ts";
|
|
@@ -87,6 +88,8 @@ export declare function executionPolicyForRisk(risk: ReviewRisk): ExecutionPolic
|
|
|
87
88
|
export declare function executionPolicyForCurrentRound(events: Event[]): ExecutionPolicy;
|
|
88
89
|
export declare function proposalDocsBaseline(changeRoot: string): Record<string, string>;
|
|
89
90
|
export declare function discoveryDocsBaseline(changeRoot: string): Record<string, string>;
|
|
91
|
+
/** 新 Explore 轮次冻结已有已确认事项,避免把历史答复当作本轮遗漏。 */
|
|
92
|
+
export declare function exploreAnswerRegistrationPayloadForChange(changeRoot: string): ReturnType<typeof exploreAnswerRegistrationPayload>;
|
|
90
93
|
export declare function latestAcceptedProposalBaseline(events: Event[]): Record<string, string> | null;
|
|
91
94
|
export declare function latestReopenProposeBaseline(events: Event[]): Record<string, string> | null;
|
|
92
95
|
export declare function latestReopenExploreBaseline(events: Event[]): Record<string, string> | null;
|