@peterxiaoyang/superspec 0.1.44 → 0.1.46
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 +13 -1
- package/dist/cli.js +23 -24
- package/dist/code_review.js +7 -2
- package/dist/format.d.ts +20 -2
- package/dist/format.js +217 -26
- package/dist/git_state.d.ts +12 -1
- package/dist/git_state.js +45 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +12 -0
- package/dist/next.d.ts +1 -1
- package/dist/next.js +3 -8
- package/dist/openspec.d.ts +13 -0
- package/dist/openspec.js +28 -0
- package/dist/phase_confirmation.d.ts +6 -0
- package/dist/phase_confirmation.js +22 -6
- package/dist/phase_plan.d.ts +10 -1
- package/dist/phase_plan.js +250 -37
- package/dist/record.d.ts +1 -1
- package/dist/record.js +38 -30
- package/dist/review.js +18 -2
- package/dist/sync.js +13 -4
- package/dist/task.js +15 -2
- package/dist/task_evidence.d.ts +1 -1
- package/dist/task_evidence.js +85 -10
- package/dist/transition.d.ts +4 -3
- package/dist/transition.js +166 -29
- package/dist/types.d.ts +38 -1
- package/dist/types.js +1 -0
- package/dist/workflow_config.d.ts +24 -0
- package/dist/workflow_config.js +127 -0
- package/package.json +1 -1
- package/templates/workflow/AGENTS.md +1 -1
- package/templates/workflow/agents/architect.toml +1 -1
- package/templates/workflow/agents/code-reviewer.toml +1 -1
- package/templates/workflow/agents/critic.toml +1 -1
- package/templates/workflow/agents/executor.toml +1 -1
- package/templates/workflow/agents/explore.toml +1 -1
- package/templates/workflow/agents/test-engineer.toml +1 -1
- package/templates/workflow/agents/test-runner.toml +1 -1
- package/templates/workflow/agents/verifier.toml +1 -1
- package/templates/workflow/prompts/architect.md +25 -33
- package/templates/workflow/prompts/code-reviewer.md +19 -67
- package/templates/workflow/prompts/critic.md +36 -87
- package/templates/workflow/prompts/executor.md +17 -19
- package/templates/workflow/prompts/explore.md +12 -46
- package/templates/workflow/prompts/test-engineer.md +22 -35
- package/templates/workflow/prompts/test-runner.md +11 -21
- package/templates/workflow/prompts/verifier.md +13 -37
- package/templates/workflow/skills/superspec-apply/SKILL.md +17 -85
- package/templates/workflow/skills/superspec-explore/SKILL.md +57 -66
- package/templates/workflow/skills/superspec-propose/SKILL.md +76 -129
- package/templates/workflow/skills/superspec-review/SKILL.md +14 -73
package/dist/install.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { SUPERSPEC_VERSION } from "./version.js";
|
|
4
|
+
import { DEFAULT_WORKFLOW_RISK, WORKFLOW_CONFIG_PATH } from "./workflow_config.js";
|
|
4
5
|
export const WORKFLOW_SKILLS = [
|
|
5
6
|
"superspec-explore",
|
|
6
7
|
"superspec-propose",
|
|
@@ -247,6 +248,16 @@ function ensureOpenSpecChineseContext(projectRoot) {
|
|
|
247
248
|
writeFileSync(configPath, next);
|
|
248
249
|
return OPENSPEC_CONFIG_PATH;
|
|
249
250
|
}
|
|
251
|
+
function ensureWorkflowConfig(projectRoot) {
|
|
252
|
+
const configPath = join(projectRoot, WORKFLOW_CONFIG_PATH);
|
|
253
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
254
|
+
if (!existsSync(configPath)) {
|
|
255
|
+
// install/update 是显式迁移动作:为以后各轮写入 normal 默认值。
|
|
256
|
+
// 未执行安装的旧项目仍由 workflowRiskForProject 保守回放 strict。
|
|
257
|
+
writeFileSync(configPath, JSON.stringify({ workflow: { mode: DEFAULT_WORKFLOW_RISK } }, null, 2) + "\n");
|
|
258
|
+
}
|
|
259
|
+
return WORKFLOW_CONFIG_PATH;
|
|
260
|
+
}
|
|
250
261
|
const AGENTS_MD_PATH = "AGENTS.md";
|
|
251
262
|
const SUPERSPEC_AGENTS_START = "<!-- SUPERSPEC:AGENTS:START -->";
|
|
252
263
|
const SUPERSPEC_AGENTS_END = "<!-- SUPERSPEC:AGENTS:END -->";
|
|
@@ -304,6 +315,7 @@ export function installProject(projectRoot, options = {}) {
|
|
|
304
315
|
prompts: copyPrompts(templateRoot, projectRoot),
|
|
305
316
|
agents: copyAgents(templateRoot, projectRoot),
|
|
306
317
|
config: ensureCodexConfig(projectRoot),
|
|
318
|
+
workflow_config: ensureWorkflowConfig(projectRoot),
|
|
307
319
|
agents_md: ensureAgentsMd(projectRoot, agentsMdTemplate),
|
|
308
320
|
openspec_config: ensureOpenSpecChineseContext(projectRoot),
|
|
309
321
|
},
|
package/dist/next.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { NextOutput } from "./types.ts";
|
|
2
2
|
/** next 命令:读 snapshot,返回唯一可执行路径 */
|
|
3
|
-
export declare function next(projectRoot: string, change: string, changeRoot: string, defaultRisk?: "
|
|
3
|
+
export declare function next(projectRoot: string, change: string, changeRoot: string, defaultRisk?: import("./review.ts").ReviewRisk): NextOutput;
|
package/dist/next.js
CHANGED
|
@@ -3,6 +3,7 @@ import { rebuildSnapshot } from "./sync.js";
|
|
|
3
3
|
import { readEvents } from "./store.js";
|
|
4
4
|
import { requiredJobActions } from "./job_action.js";
|
|
5
5
|
import { planNextStep } from "./phase_plan.js";
|
|
6
|
+
import { workflowRiskForProject } from "./workflow_config.js";
|
|
6
7
|
function requiredJobsOutput(state, change, jobs, reason) {
|
|
7
8
|
return {
|
|
8
9
|
state,
|
|
@@ -14,12 +15,6 @@ function requiredJobsOutput(state, change, jobs, reason) {
|
|
|
14
15
|
function transitionCommand(change, name, extra = "") {
|
|
15
16
|
return `superspec transition ${name} --change "${change}"${extra ? " " + extra : ""}`;
|
|
16
17
|
}
|
|
17
|
-
function riskFlag(risk) {
|
|
18
|
-
return risk === "strict" ? "" : `--risk ${risk}`;
|
|
19
|
-
}
|
|
20
|
-
function riskArg(risk) {
|
|
21
|
-
return risk ? riskFlag(risk) : "";
|
|
22
|
-
}
|
|
23
18
|
function formatTransitionArgs(plan) {
|
|
24
19
|
if (plan.taskId)
|
|
25
20
|
return `--task ${plan.taskId}`;
|
|
@@ -32,7 +27,7 @@ function formatTransitionArgs(plan) {
|
|
|
32
27
|
if (plan.reopen?.reason === "review_finding") {
|
|
33
28
|
return `--to propose --review-finding ${plan.reopen.jobId}#${plan.reopen.findingId} --reason "根据代码审查问题 ${plan.reopen.findingId} 回到计划阶段"`;
|
|
34
29
|
}
|
|
35
|
-
return
|
|
30
|
+
return "";
|
|
36
31
|
}
|
|
37
32
|
function toNextOutput(change, plan) {
|
|
38
33
|
switch (plan.kind) {
|
|
@@ -58,7 +53,7 @@ function toNextOutput(change, plan) {
|
|
|
58
53
|
}
|
|
59
54
|
}
|
|
60
55
|
/** next 命令:读 snapshot,返回唯一可执行路径 */
|
|
61
|
-
export function next(projectRoot, change, changeRoot, defaultRisk =
|
|
56
|
+
export function next(projectRoot, change, changeRoot, defaultRisk = workflowRiskForProject(projectRoot)) {
|
|
62
57
|
const snapshot = rebuildSnapshot(projectRoot, change, changeRoot);
|
|
63
58
|
const events = readEvents(projectRoot, change);
|
|
64
59
|
const plannedNextStep = planNextStep({ projectRoot, change, changeRoot, events, snapshot, mode: { kind: "risk", risk: defaultRisk } });
|
package/dist/openspec.d.ts
CHANGED
|
@@ -4,6 +4,19 @@ export interface OpenSpecProbe {
|
|
|
4
4
|
changeExists: boolean;
|
|
5
5
|
error?: string;
|
|
6
6
|
}
|
|
7
|
+
export interface OpenSpecStrictValidation {
|
|
8
|
+
/** 调用方已经按冻结 profile 决定是否执行 strict validation。 */
|
|
9
|
+
checked: boolean;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
7
13
|
export declare function probeOpenSpec(projectRoot: string, change?: string): OpenSpecProbe;
|
|
8
14
|
export declare function openspecStatus(projectRoot: string, change: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* 计划阶段的原生 OpenSpec 结构 gate。
|
|
17
|
+
*
|
|
18
|
+
* 调用方已通过冻结的 planning profile 确认应执行 strict validation。这里不再
|
|
19
|
+
* 读取实时 config.yaml,避免计划就绪后环境变化导致准入标准漂移。
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateOpenSpecChange(projectRoot: string, change: string): OpenSpecStrictValidation;
|
|
9
22
|
export declare function changeRoot(projectRoot: string, change: string): string;
|
package/dist/openspec.js
CHANGED
|
@@ -36,6 +36,34 @@ export function openspecStatus(projectRoot, change) {
|
|
|
36
36
|
return "sha256:unknown";
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 计划阶段的原生 OpenSpec 结构 gate。
|
|
41
|
+
*
|
|
42
|
+
* 调用方已通过冻结的 planning profile 确认应执行 strict validation。这里不再
|
|
43
|
+
* 读取实时 config.yaml,避免计划就绪后环境变化导致准入标准漂移。
|
|
44
|
+
*/
|
|
45
|
+
export function validateOpenSpecChange(projectRoot, change) {
|
|
46
|
+
validateChange(change);
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("openspec", ["validate", change, "--type", "change", "--strict", "--no-interactive"], {
|
|
49
|
+
cwd: projectRoot,
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
52
|
+
});
|
|
53
|
+
return { checked: true, ok: true, message: "OpenSpec 原生结构校验通过" };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const failure = error;
|
|
57
|
+
const stdout = typeof failure.stdout === "string" ? failure.stdout : failure.stdout?.toString("utf8") ?? "";
|
|
58
|
+
const stderr = typeof failure.stderr === "string" ? failure.stderr : failure.stderr?.toString("utf8") ?? "";
|
|
59
|
+
const detail = [stdout, stderr].map(value => value.trim()).filter(Boolean).join(";");
|
|
60
|
+
return {
|
|
61
|
+
checked: true,
|
|
62
|
+
ok: false,
|
|
63
|
+
message: `OpenSpec strict 校验失败${detail ? `:${detail}` : ";请确认 openspec CLI 可用并修复 proposal/specs 结构"}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
39
67
|
export function changeRoot(projectRoot, change) {
|
|
40
68
|
return join(projectRoot, "openspec", "changes", change);
|
|
41
69
|
}
|
|
@@ -20,7 +20,13 @@ export interface PhaseConfirmationDecision {
|
|
|
20
20
|
scope: string;
|
|
21
21
|
answer: string;
|
|
22
22
|
decision: PhaseDecision;
|
|
23
|
+
review_risk: ReviewRisk;
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 阶段确认不是 mode 的输入。它只读取当前 planning/apply round 的冻结快照;
|
|
27
|
+
* 仍处于 Explore/Propose 时才从项目配置获取候选 mode。
|
|
28
|
+
*/
|
|
29
|
+
export declare function workflowRiskForPhaseConfirmation(projectRoot: string, events: Event[], snapshot: Snapshot): ReviewRisk;
|
|
24
30
|
export declare function isPhaseConfirmationScope(value: unknown): value is string;
|
|
25
31
|
export declare function phaseConfirmationForBoundary(projectRoot: string, events: Event[], snapshot: Snapshot, boundary: PhaseBoundary, risk?: ReviewRisk): PhaseConfirmation | null;
|
|
26
32
|
export declare function phaseConfirmationForCurrentState(projectRoot: string, events: Event[], snapshot: Snapshot, risk?: ReviewRisk): PhaseConfirmation | null;
|
|
@@ -3,6 +3,7 @@ import { historicalProposeReadyRoles, reviewEvidenceDigest, reviewGateRoleResolu
|
|
|
3
3
|
import { EXPLORE_DISCOVERY_REVIEW_GATE, PROPOSE_FINAL_REVIEW_GATE } from "./review_job_gates.js";
|
|
4
4
|
import { changeRoot as openspecChangeRoot } from "./openspec.js";
|
|
5
5
|
import { findLatestEvent, sha256Text } from "./store.js";
|
|
6
|
+
import { hasFrozenWorkflowModeForProposeRound, workflowRiskForProject, workflowRiskForState, } from "./workflow_config.js";
|
|
6
7
|
export const PHASE_CONFIRMATION_SCOPE_PREFIX = "phase_confirmation:";
|
|
7
8
|
function latestTransition(events, predicate) {
|
|
8
9
|
return findLatestEvent(events, "transition_commit", event => predicate(event.payload));
|
|
@@ -25,7 +26,7 @@ const SPECS = {
|
|
|
25
26
|
},
|
|
26
27
|
],
|
|
27
28
|
scopePrefix: `${PHASE_CONFIRMATION_SCOPE_PREFIX}explore_to_propose`,
|
|
28
|
-
epoch: events => latestTransition(events, payload => payload.
|
|
29
|
+
epoch: events => latestTransition(events, payload => payload.to_state === "explore" && payload.from_state !== "explore"),
|
|
29
30
|
},
|
|
30
31
|
propose_to_apply: {
|
|
31
32
|
state: "propose_ready",
|
|
@@ -66,9 +67,18 @@ function boundaryForState(state) {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
function ordinaryReviewRolesForBoundary(events, boundary, gate, risk) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// 历史 plan 没有冻结 mode,当时的 gate 只能按实际创建过的角色回放。
|
|
71
|
+
if (boundary === "propose_to_apply" && !hasFrozenWorkflowModeForProposeRound(events)) {
|
|
72
|
+
return historicalProposeReadyRoles(events);
|
|
73
|
+
}
|
|
74
|
+
return gate.requiredRolesForRisk(risk);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 阶段确认不是 mode 的输入。它只读取当前 planning/apply round 的冻结快照;
|
|
78
|
+
* 仍处于 Explore/Propose 时才从项目配置获取候选 mode。
|
|
79
|
+
*/
|
|
80
|
+
export function workflowRiskForPhaseConfirmation(projectRoot, events, snapshot) {
|
|
81
|
+
return workflowRiskForState(events, snapshot.state, workflowRiskForProject(projectRoot));
|
|
72
82
|
}
|
|
73
83
|
function materialDigest(projectRoot, events, snapshot, boundary, risk) {
|
|
74
84
|
const head = currentGitHead(projectRoot);
|
|
@@ -105,6 +115,7 @@ function materialDigest(projectRoot, events, snapshot, boundary, risk) {
|
|
|
105
115
|
.sort((left, right) => `${left.gate_id}\u0000${left.role}\u0000${left.job_id}`.localeCompare(`${right.gate_id}\u0000${right.role}\u0000${right.job_id}`))
|
|
106
116
|
: [];
|
|
107
117
|
return sha256Text(JSON.stringify({
|
|
118
|
+
review_risk: risk,
|
|
108
119
|
documents,
|
|
109
120
|
tasks_structure_digest: snapshot.tasks_structure_digest,
|
|
110
121
|
accepted_jobs: acceptedJobs,
|
|
@@ -121,14 +132,13 @@ function materialDigest(projectRoot, events, snapshot, boundary, risk) {
|
|
|
121
132
|
function phaseRecordArgv(change) {
|
|
122
133
|
return ["superspec", "record", "user-decision", "--change", change, "--input", "-"];
|
|
123
134
|
}
|
|
124
|
-
function nextArgv(change,
|
|
135
|
+
function nextArgv(change, _risk) {
|
|
125
136
|
return [
|
|
126
137
|
"superspec",
|
|
127
138
|
"transition",
|
|
128
139
|
"next",
|
|
129
140
|
"--change",
|
|
130
141
|
change,
|
|
131
|
-
...(risk === "strict" ? [] : ["--risk", risk]),
|
|
132
142
|
];
|
|
133
143
|
}
|
|
134
144
|
function buildActions(change, boundary, scope, question, specs, risk) {
|
|
@@ -220,6 +230,11 @@ export function latestAcceptedPhaseDecision(events, confirmation) {
|
|
|
220
230
|
scope: confirmation.scope,
|
|
221
231
|
answer: payload.answer,
|
|
222
232
|
decision: payload.phase_confirmation.decision,
|
|
233
|
+
review_risk: payload.phase_confirmation.review_risk === "minimal" ||
|
|
234
|
+
payload.phase_confirmation.review_risk === "normal" ||
|
|
235
|
+
payload.phase_confirmation.review_risk === "strict"
|
|
236
|
+
? payload.phase_confirmation.review_risk
|
|
237
|
+
: "strict",
|
|
223
238
|
};
|
|
224
239
|
}
|
|
225
240
|
export function isPhaseAdvanceAuthorized(events, confirmation) {
|
|
@@ -237,6 +252,7 @@ export function phaseConfirmationCommitPayload(confirmation, decision) {
|
|
|
237
252
|
material_digest: confirmation.material_digest,
|
|
238
253
|
scope: confirmation.scope,
|
|
239
254
|
decision_event_id: decision.event.event_id,
|
|
255
|
+
review_risk: decision.review_risk,
|
|
240
256
|
},
|
|
241
257
|
};
|
|
242
258
|
}
|
package/dist/phase_plan.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReviewGateRule } from "./review_job_gates.ts";
|
|
2
|
-
import type { AcceptedMaterialFollowupContinuation, AskUser, Event, Job, JobRole, State } from "./types.ts";
|
|
2
|
+
import type { AcceptedMaterialFollowupContinuation, AskUser, Event, ExecutionPolicy, Job, JobRole, PlanningValidationProfile, State } from "./types.ts";
|
|
3
3
|
import type { Snapshot } from "./types.ts";
|
|
4
4
|
import type { ReviewRisk } from "./review.ts";
|
|
5
5
|
export type TransitionName = "explore" | "propose-ready" | "start-apply" | "task-start" | "task-complete" | "review-ready" | "reopen" | "accept";
|
|
@@ -83,11 +83,20 @@ export interface ApplyPendingTaskStatus {
|
|
|
83
83
|
needsCompletionEvent: string[];
|
|
84
84
|
completedByEvent: string[];
|
|
85
85
|
}
|
|
86
|
+
export declare function executionPolicyForRisk(risk: ReviewRisk): ExecutionPolicy;
|
|
87
|
+
export declare function executionPolicyForCurrentRound(events: Event[]): ExecutionPolicy;
|
|
86
88
|
export declare function proposalDocsBaseline(changeRoot: string): Record<string, string>;
|
|
89
|
+
export declare function discoveryDocsBaseline(changeRoot: string): Record<string, string>;
|
|
87
90
|
export declare function latestAcceptedProposalBaseline(events: Event[]): Record<string, string> | null;
|
|
88
91
|
export declare function latestReopenProposeBaseline(events: Event[]): Record<string, string> | null;
|
|
92
|
+
export declare function latestReopenExploreBaseline(events: Event[]): Record<string, string> | null;
|
|
89
93
|
export declare function proposalDocsChangedSinceBaseline(changeRoot: string, baseline: Record<string, string>): boolean;
|
|
94
|
+
export declare function discoveryDocsChangedSinceBaseline(changeRoot: string, baseline: Record<string, string>): boolean;
|
|
90
95
|
export declare function pendingTaskIds(changeRoot: string): string[];
|
|
96
|
+
/** 当前 Apply round 的规则版本;缺失版本的历史 event 保持 v1 回放。 */
|
|
97
|
+
export declare function executionRequirementVersionForCurrentRound(events: Event[]): 1 | 2;
|
|
98
|
+
/** 新 planning round 在进入 propose 时冻结当前 OpenSpec 校验契约。 */
|
|
99
|
+
export declare function planningValidationProfileForNewRound(projectRoot: string): PlanningValidationProfile;
|
|
91
100
|
export declare function applyRequirementModeForCurrentRound(events: Event[]): boolean;
|
|
92
101
|
export declare function pendingTaskStatusForApply(changeRoot: string, events: Event[]): ApplyPendingTaskStatus;
|
|
93
102
|
export declare function formatPendingTaskMessage(ids: string[], action: string): string;
|