@peterxiaoyang/superspec 0.1.14 → 0.1.16-alpha
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 +37 -36
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +301 -0
- package/dist/format.d.ts +29 -0
- package/dist/format.js +141 -0
- package/dist/install.d.ts +18 -0
- package/dist/install.js +161 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +161 -0
- package/dist/openspec.d.ts +9 -0
- package/dist/openspec.js +41 -0
- package/dist/record.d.ts +20 -0
- package/dist/record.js +266 -0
- package/dist/skill_loop.d.ts +23 -0
- package/dist/skill_loop.js +58 -0
- package/dist/store.d.ts +31 -0
- package/dist/store.js +213 -0
- package/dist/sync.d.ts +3 -0
- package/dist/sync.js +135 -0
- package/dist/task.d.ts +7 -0
- package/dist/task.js +44 -0
- package/dist/transition.d.ts +34 -0
- package/dist/transition.js +400 -0
- package/dist/types.d.ts +141 -0
- package/dist/types.js +3 -0
- package/package.json +8 -53
- package/templates/workflow/agents/architect.toml +13 -0
- package/{adapters/codex → templates/workflow}/agents/code-reviewer.toml +2 -2
- package/templates/workflow/agents/critic.toml +13 -0
- package/{adapters/codex → templates/workflow}/agents/executor.toml +2 -2
- package/{adapters/codex → templates/workflow}/agents/explore.toml +3 -3
- package/templates/workflow/agents/final-audit.toml +13 -0
- package/templates/workflow/agents/proposal-auditor.toml +13 -0
- package/{adapters/codex → templates/workflow}/agents/test-engineer.toml +4 -4
- package/{adapters/codex → templates/workflow}/agents/test-runner.toml +2 -2
- package/{adapters/codex → templates/workflow}/agents/verifier.toml +2 -2
- package/templates/workflow/prompts/architect.md +16 -0
- package/templates/workflow/prompts/critic.md +16 -0
- package/templates/workflow/prompts/explore.md +1 -1
- package/templates/workflow/prompts/final-audit.md +32 -0
- package/templates/workflow/prompts/proposal-auditor.md +34 -0
- package/templates/workflow/prompts/test-engineer.md +16 -0
- package/templates/workflow/skills/superspec-apply/SKILL.md +39 -121
- package/templates/workflow/skills/superspec-archive/SKILL.md +17 -49
- package/templates/workflow/skills/superspec-explore/SKILL.md +34 -70
- package/templates/workflow/skills/superspec-propose/SKILL.md +57 -60
- package/templates/workflow/skills/superspec-review/SKILL.md +18 -85
- package/adapters/codex/agents/architect.toml +0 -13
- package/adapters/codex/agents/critic.toml +0 -13
- package/adapters/codex/install-map.json +0 -116
- package/bin/launch.js +0 -37
- package/bin/superspec-check.js +0 -4
- package/bin/superspec-hook.js +0 -4
- package/bin/superspec-init.js +0 -4
- package/bin/superspec.js +0 -4
- package/dist/src/apply_worker_chain.d.ts +0 -60
- package/dist/src/apply_worker_chain.js +0 -1283
- package/dist/src/apply_worker_chain_lifecycle.d.ts +0 -21
- package/dist/src/apply_worker_chain_lifecycle.js +0 -409
- package/dist/src/archive.d.ts +0 -23
- package/dist/src/archive.js +0 -430
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +0 -33
- package/dist/src/cli_args.d.ts +0 -32
- package/dist/src/cli_args.js +0 -454
- package/dist/src/core.d.ts +0 -20
- package/dist/src/core.js +0 -394
- package/dist/src/disclosure.d.ts +0 -35
- package/dist/src/disclosure.js +0 -671
- package/dist/src/doctor.d.ts +0 -44
- package/dist/src/doctor.js +0 -230
- package/dist/src/evidence.d.ts +0 -29
- package/dist/src/evidence.js +0 -1107
- package/dist/src/gates.d.ts +0 -19
- package/dist/src/gates.js +0 -1631
- package/dist/src/git.d.ts +0 -8
- package/dist/src/git.js +0 -112
- package/dist/src/hooks/adapter.d.ts +0 -5
- package/dist/src/hooks/adapter.js +0 -347
- package/dist/src/hooks/guard_api.d.ts +0 -12
- package/dist/src/hooks/guard_api.js +0 -797
- package/dist/src/hooks/health.d.ts +0 -4
- package/dist/src/hooks/health.js +0 -86
- package/dist/src/hooks/policy_event.d.ts +0 -41
- package/dist/src/hooks/policy_event.js +0 -2634
- package/dist/src/hooks/types.d.ts +0 -72
- package/dist/src/hooks/types.js +0 -1
- package/dist/src/hooks/validation.d.ts +0 -3
- package/dist/src/hooks/validation.js +0 -70
- package/dist/src/i18n.d.ts +0 -21
- package/dist/src/i18n.js +0 -720
- package/dist/src/init_cli.d.ts +0 -28
- package/dist/src/init_cli.js +0 -404
- package/dist/src/install_engine.d.ts +0 -71
- package/dist/src/install_engine.js +0 -538
- package/dist/src/invariants.d.ts +0 -16
- package/dist/src/invariants.js +0 -363
- package/dist/src/openspec.d.ts +0 -37
- package/dist/src/openspec.js +0 -274
- package/dist/src/packet_render.d.ts +0 -5
- package/dist/src/packet_render.js +0 -1767
- package/dist/src/packet_schema.d.ts +0 -59
- package/dist/src/packet_schema.js +0 -1
- package/dist/src/paths.d.ts +0 -22
- package/dist/src/paths.js +0 -203
- package/dist/src/project_init.d.ts +0 -33
- package/dist/src/project_init.js +0 -191
- package/dist/src/self_update.d.ts +0 -14
- package/dist/src/self_update.js +0 -56
- package/dist/src/state.d.ts +0 -37
- package/dist/src/state.js +0 -464
- package/dist/src/tasks.d.ts +0 -38
- package/dist/src/tasks.js +0 -373
- package/dist/src/util.d.ts +0 -163
- package/dist/src/util.js +0 -792
- package/dist/superspec.d.ts +0 -6
- package/dist/superspec.js +0 -97
- package/dist/superspec_guard.d.ts +0 -4
- package/dist/superspec_guard.js +0 -19
- package/dist/superspec_hook.d.ts +0 -4
- package/dist/superspec_hook.js +0 -35
- package/dist/superspec_init.d.ts +0 -2
- package/dist/superspec_init.js +0 -17
- package/schemas/hook-event.schema.json +0 -27
- package/schemas/install-manifest.schema.json +0 -97
- package/templates/hooks/codex-hooks.json +0 -35
- package/templates/sidecar/archive-preservation.json +0 -11
- package/templates/sidecar/business-invariants.md +0 -38
- package/templates/sidecar/config.yaml +0 -13
- package/templates/sidecar/discovery.md +0 -33
- package/templates/sidecar/test-contract.md +0 -27
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// SuperSpec 流程引擎 — transition:提交协议 + 所有 transition 处理器
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
4
|
+
import { ensureChangeLayout, readEvents, appendEvent, makeEvent, writeSnapshot, snapshotDigest, withLock, idempotencyKey, stagingDir, sha256File, sha256Text, } from "./store.js";
|
|
5
|
+
import { rebuildSnapshot } from "./sync.js";
|
|
6
|
+
import { validateDiscovery, findTaskInLines, parseTasksMd, tasksStructureDigest } from "./format.js";
|
|
7
|
+
let transitionSeq = 0;
|
|
8
|
+
function newTransitionId() { return `T-${Date.now()}-${++transitionSeq}`; }
|
|
9
|
+
let jobSeq = 0;
|
|
10
|
+
function newJobId(change, role) { return `JOB-${change.slice(0, 8)}-${role.slice(0, 4)}-${Date.now()}-${++jobSeq}`; }
|
|
11
|
+
const TRANSITION_REQUIREMENTS = {
|
|
12
|
+
"propose-ready": {
|
|
13
|
+
minimal: [],
|
|
14
|
+
normal: ["proposal-auditor"],
|
|
15
|
+
strict: ["proposal-auditor", "critic", "architect", "test-engineer"],
|
|
16
|
+
},
|
|
17
|
+
"explore": {
|
|
18
|
+
minimal: [],
|
|
19
|
+
normal: [],
|
|
20
|
+
strict: ["critic"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* 通用 job 检查/创建逻辑——任何 transition 都能调用。
|
|
25
|
+
* 检查 requiredRoles 是否有 fresh accepted job;缺则创建新 job。
|
|
26
|
+
* 返回 null = 全部满足;返回 Decision = 需要创建 job(状态不变)。
|
|
27
|
+
*/
|
|
28
|
+
function checkOrCreateReviewJobs(snapshot, requiredRoles, changeRoot, change, transitionName, bindDocPaths) {
|
|
29
|
+
const staleRoles = [];
|
|
30
|
+
for (const role of requiredRoles) {
|
|
31
|
+
const openForRole = snapshot.open_jobs.find(j => j.role === role);
|
|
32
|
+
if (openForRole)
|
|
33
|
+
return { fromState: snapshot.state, toState: snapshot.state, outcome: "advanced", reason: `工作项 ${role} 已存在(${openForRole.job_id}),请先完成它` };
|
|
34
|
+
const fresh = snapshot.accepted_jobs.find(j => j.role === role);
|
|
35
|
+
if (!fresh) {
|
|
36
|
+
staleRoles.push({ role, reason: `需求 ${role} 无已接受的工作项` });
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const bf of fresh.boundFiles) {
|
|
40
|
+
const currentSha = sha256File(join(changeRoot, bf.path)) ?? "sha256:missing";
|
|
41
|
+
if (currentSha !== bf.sha) {
|
|
42
|
+
staleRoles.push({ role, reason: `${role} 绑定文件 ${bf.path} 已变化` });
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (staleRoles.length > 0) {
|
|
49
|
+
const newJobs = staleRoles.map(({ role }) => {
|
|
50
|
+
const boundFiles = bindDocPaths.filter(p => existsSync(join(changeRoot, p))).map(p => ({ path: p, sha: sha256File(join(changeRoot, p)) ?? "sha256:missing" }));
|
|
51
|
+
return { job_id: newJobId(change, role), role, state: "requested", boundFiles, packet_digest: sha256Text(JSON.stringify({ role, boundFiles })), created_from_transition: transitionName, created_at: new Date().toISOString() };
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
fromState: snapshot.state, toState: snapshot.state, outcome: "job_created",
|
|
55
|
+
newJobs, reason: staleRoles.map(s => s.reason).join("; "),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return null; // 全部满足
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 已迁移到 format.ts:findTaskInLines / parseTasksMd / tasksStructureDigest
|
|
62
|
+
* 以下保留 findTaskLine 作为兼容 wrapper(内部调用 format.ts)
|
|
63
|
+
*/
|
|
64
|
+
function findTaskLine(lines, taskId) {
|
|
65
|
+
return findTaskInLines(lines, taskId);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 统一 transition 提交协议——所有校验在锁内。
|
|
69
|
+
*/
|
|
70
|
+
export function commitTransition(projectRoot, change, changeRoot, opts) {
|
|
71
|
+
const { name } = opts;
|
|
72
|
+
return withLock(projectRoot, change, () => {
|
|
73
|
+
ensureChangeLayout(projectRoot, change);
|
|
74
|
+
const snapshot = rebuildSnapshot(projectRoot, change, changeRoot);
|
|
75
|
+
const worldDigest = snapshotDigest(snapshot);
|
|
76
|
+
const idemKey = idempotencyKey(name, opts.idempotencyInputs ?? {}, worldDigest);
|
|
77
|
+
const events = readEvents(projectRoot, change);
|
|
78
|
+
const existing = events.find(e => e.idempotency_key === idemKey && e.event_type === "transition_commit");
|
|
79
|
+
if (existing) {
|
|
80
|
+
const p = existing.payload;
|
|
81
|
+
return {
|
|
82
|
+
transition: name, outcome: p.outcome,
|
|
83
|
+
from_state: p.from_state, to_state: p.to_state,
|
|
84
|
+
created_jobs: p.created_job_ids ?? [], message: "幂等返回", events_written: 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const decision = opts.decide(snapshot);
|
|
88
|
+
if ("skip" in decision) {
|
|
89
|
+
return {
|
|
90
|
+
transition: name, outcome: "advanced",
|
|
91
|
+
from_state: snapshot.state, to_state: snapshot.state,
|
|
92
|
+
created_jobs: [], message: decision.message, events_written: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const { fromState, toState, outcome, newJobs = [], reason, extraEvents = [] } = decision;
|
|
96
|
+
if (fromState !== snapshot.state) {
|
|
97
|
+
return {
|
|
98
|
+
transition: name, outcome: "advanced",
|
|
99
|
+
from_state: snapshot.state, to_state: snapshot.state,
|
|
100
|
+
created_jobs: [], message: `from_state 不匹配:期望 ${fromState},实际 ${snapshot.state}`,
|
|
101
|
+
events_written: 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const transitionId = newTransitionId();
|
|
105
|
+
// prepare
|
|
106
|
+
appendEvent(projectRoot, change, makeEvent(change, "transition_prepare", {
|
|
107
|
+
transition: name, transition_id: transitionId, from_state: fromState, to_state: toState, reason,
|
|
108
|
+
}, { transitionId, idempotencyKey: idemKey, prevSnapshotDigest: worldDigest }));
|
|
109
|
+
// BLOCKER-1 修复:postCommit 在 commit 写入前执行。
|
|
110
|
+
// 如果 postCommit 抛错 → 只有 prepare(无 commit)→ 幂等重试会跳过 prepare 重新执行。
|
|
111
|
+
// 如果 postCommit 成功 → commit + extraEvents 写入 → 一致。
|
|
112
|
+
if (decision.postCommit) {
|
|
113
|
+
decision.postCommit(projectRoot, change, changeRoot);
|
|
114
|
+
}
|
|
115
|
+
// commit(postCommit 成功后才写)
|
|
116
|
+
appendEvent(projectRoot, change, makeEvent(change, "transition_commit", {
|
|
117
|
+
transition: name, from_state: fromState, to_state: toState,
|
|
118
|
+
outcome, created_job_ids: newJobs.map(j => j.job_id), new_jobs: newJobs, reason,
|
|
119
|
+
}, { transitionId, idempotencyKey: idemKey, prevSnapshotDigest: worldDigest }));
|
|
120
|
+
// extra events (task_started, task_completed, etc.)
|
|
121
|
+
for (const ex of extraEvents) {
|
|
122
|
+
appendEvent(projectRoot, change, makeEvent(change, ex.type, ex.payload, { transitionId }));
|
|
123
|
+
}
|
|
124
|
+
writeSnapshot(projectRoot, change, rebuildSnapshot(projectRoot, change, changeRoot));
|
|
125
|
+
return {
|
|
126
|
+
transition: name, outcome, from_state: fromState, to_state: toState,
|
|
127
|
+
created_jobs: newJobs.map(j => j.job_id),
|
|
128
|
+
message: outcome === "advanced" ? `状态推进:${fromState} → ${toState}` : `状态不变(${fromState}),创建了 ${newJobs.length} 个工作项`,
|
|
129
|
+
events_written: 1 + extraEvents.length,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// ===== propose-ready =====
|
|
134
|
+
export function proposeReady(projectRoot, change, changeRoot, risk = "normal") {
|
|
135
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
136
|
+
name: "propose-ready", idempotencyInputs: { risk },
|
|
137
|
+
decide: (snapshot) => {
|
|
138
|
+
if (snapshot.state !== "propose")
|
|
139
|
+
return { skip: true, message: `当前状态 ${snapshot.state},不能 propose-ready` };
|
|
140
|
+
const tasksPath = join(changeRoot, "tasks.md");
|
|
141
|
+
if (!existsSync(tasksPath))
|
|
142
|
+
return { skip: true, message: "tasks.md 不存在" };
|
|
143
|
+
const tasksContent = readFileSync(tasksPath, "utf8");
|
|
144
|
+
if (!tasksContent.includes("# Tasks") && !tasksContent.includes("- [ ]"))
|
|
145
|
+
return { skip: true, message: "tasks.md 内容不像任务计划文档" };
|
|
146
|
+
if (risk !== "minimal") {
|
|
147
|
+
const artifactsDir = join(changeRoot, ".superspec", "artifacts");
|
|
148
|
+
for (const doc of ["discovery.md", "business-invariants.md", "test-contract.md"]) {
|
|
149
|
+
if (!existsSync(join(artifactsDir, doc)))
|
|
150
|
+
return { skip: true, message: `基础职责缺失:${doc} 不存在(risk=${risk} 需要)` };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// 通用 job 检查(用提取的 helper)
|
|
154
|
+
const requiredRoles = TRANSITION_REQUIREMENTS["propose-ready"]?.[risk] ?? [];
|
|
155
|
+
const reviewResult = checkOrCreateReviewJobs(snapshot, requiredRoles, changeRoot, change, "propose-ready", ["proposal.md", "tasks.md", "design.md", ".superspec/artifacts/discovery.md", ".superspec/artifacts/business-invariants.md", ".superspec/artifacts/test-contract.md"]);
|
|
156
|
+
if (reviewResult)
|
|
157
|
+
return reviewResult;
|
|
158
|
+
return { fromState: "propose", toState: "propose_ready", outcome: "advanced", reason: `risk=${risk},所有需求已满足` };
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// ===== init =====
|
|
163
|
+
export function transitionInit(projectRoot, change, changeRoot) {
|
|
164
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
165
|
+
name: "init", idempotencyInputs: { phase: "init" },
|
|
166
|
+
decide: () => {
|
|
167
|
+
const events = readEvents(projectRoot, change);
|
|
168
|
+
if (events.length > 0)
|
|
169
|
+
return { skip: true, message: "change 已初始化" };
|
|
170
|
+
return { fromState: "init", toState: "init", outcome: "advanced", reason: "引擎初始化" };
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// ===== explore =====
|
|
175
|
+
export function transitionExplore(projectRoot, change, changeRoot, risk = "normal") {
|
|
176
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
177
|
+
name: "explore", idempotencyInputs: { phase: "explore", risk },
|
|
178
|
+
decide: (snapshot) => {
|
|
179
|
+
if (snapshot.state === "init")
|
|
180
|
+
return { fromState: "init", toState: "explore", outcome: "advanced", reason: "进入探索阶段" };
|
|
181
|
+
if (snapshot.state === "explore") {
|
|
182
|
+
const discoveryPath = join(changeRoot, ".superspec", "artifacts", "discovery.md");
|
|
183
|
+
if (!existsSync(discoveryPath))
|
|
184
|
+
return { skip: true, message: "discovery.md 不存在" };
|
|
185
|
+
// explore→propose:校验 discovery + strict 模式下的 critic 审查
|
|
186
|
+
const discoveryCheck = validateDiscovery(changeRoot);
|
|
187
|
+
if (!discoveryCheck.ok)
|
|
188
|
+
return { skip: true, message: discoveryCheck.message };
|
|
189
|
+
// 通用 job 审查(和 propose-ready 同一个 helper)
|
|
190
|
+
const requiredRoles = TRANSITION_REQUIREMENTS["explore"]?.[risk] ?? [];
|
|
191
|
+
const reviewResult = checkOrCreateReviewJobs(snapshot, requiredRoles, changeRoot, change, "explore", [".superspec/artifacts/discovery.md"]);
|
|
192
|
+
if (reviewResult)
|
|
193
|
+
return reviewResult;
|
|
194
|
+
return { fromState: "explore", toState: "propose", outcome: "advanced", reason: "探索完成" };
|
|
195
|
+
}
|
|
196
|
+
return { skip: true, message: `当前状态 ${snapshot.state},explore 不适用` };
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// ===== start-apply =====
|
|
201
|
+
export function startApply(projectRoot, change, changeRoot) {
|
|
202
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
203
|
+
name: "start-apply", idempotencyInputs: { phase: "start-apply" },
|
|
204
|
+
decide: (snapshot) => {
|
|
205
|
+
if (snapshot.state !== "propose_ready")
|
|
206
|
+
return { skip: true, message: `当前状态 ${snapshot.state},需要 propose_ready` };
|
|
207
|
+
return { fromState: "propose_ready", toState: "apply", outcome: "advanced", reason: "进入执行阶段" };
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// ===== task-start =====
|
|
212
|
+
let attemptSeq = 0;
|
|
213
|
+
export function taskStart(projectRoot, change, changeRoot, taskId) {
|
|
214
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
215
|
+
name: "task-start", idempotencyInputs: { task: taskId },
|
|
216
|
+
decide: (snapshot) => {
|
|
217
|
+
if (snapshot.state !== "apply")
|
|
218
|
+
return { skip: true, message: `当前状态 ${snapshot.state},需要 apply` };
|
|
219
|
+
const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
|
|
220
|
+
const lines = tasksContent.split("\n");
|
|
221
|
+
const taskLineIdx = findTaskLine(lines, taskId);
|
|
222
|
+
if (taskLineIdx < 0)
|
|
223
|
+
return { skip: true, message: `任务 ${taskId} 不存在` };
|
|
224
|
+
if (lines[taskLineIdx].match(/- \[x\]/))
|
|
225
|
+
return { skip: true, message: `任务 ${taskId} 已完成` };
|
|
226
|
+
const existing = snapshot.active_task_attempts?.find(a => a.task_id === taskId && a.state === "active");
|
|
227
|
+
if (existing)
|
|
228
|
+
return { skip: true, message: `任务 ${taskId} 已有活跃尝试` };
|
|
229
|
+
const structureDigest = sha256Text(tasksContent.replace(/- \[[xX]\]/g, "- [ ]"));
|
|
230
|
+
const attempt = {
|
|
231
|
+
attempt_id: `ATT-${taskId}-${Date.now()}-${++attemptSeq}`,
|
|
232
|
+
task_id: taskId, state: "active",
|
|
233
|
+
task_structure_digest: structureDigest,
|
|
234
|
+
declared_write_scope: [], pre_edit_source_fingerprint: null,
|
|
235
|
+
pre_edit_red_ref: null, executor_packet_digest: null,
|
|
236
|
+
executor_result_ref: null, post_edit_green_ref: null,
|
|
237
|
+
created_at: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
fromState: "apply", toState: "apply", outcome: "advanced",
|
|
241
|
+
reason: `创建任务 ${taskId} 执行尝试`,
|
|
242
|
+
extraEvents: [{ type: "task_started", payload: attempt }],
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// ===== review-ready =====
|
|
248
|
+
export function reviewReady(projectRoot, change, changeRoot, risk = "normal") {
|
|
249
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
250
|
+
name: "review-ready", idempotencyInputs: { phase: "review-ready", risk },
|
|
251
|
+
decide: (snapshot) => {
|
|
252
|
+
// 检查是否所有任务已完成
|
|
253
|
+
const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
|
|
254
|
+
const allDone = !tasksContent.split("\n").some(l => l.includes("- [ ]"));
|
|
255
|
+
if (!allDone)
|
|
256
|
+
return { skip: true, message: "尚有未完成任务" };
|
|
257
|
+
// 如果当前是 apply,先推进到 apply_done
|
|
258
|
+
if (snapshot.state === "apply") {
|
|
259
|
+
return { fromState: "apply", toState: "apply_done", outcome: "advanced", reason: "所有任务完成" };
|
|
260
|
+
}
|
|
261
|
+
if (snapshot.state === "apply_done") {
|
|
262
|
+
// 检查是否有 final-audit job 需求
|
|
263
|
+
const finalAuditOpen = snapshot.open_jobs.find(j => j.role === "final-audit");
|
|
264
|
+
if (finalAuditOpen)
|
|
265
|
+
return { skip: true, message: `有待完成的最终审查工作项 ${finalAuditOpen.job_id}` };
|
|
266
|
+
const finalAuditAccepted = snapshot.accepted_jobs.find(j => j.role === "final-audit");
|
|
267
|
+
// minimal 直接推进;normal/strict 需要 final-audit
|
|
268
|
+
if (risk !== "minimal" && !finalAuditAccepted) {
|
|
269
|
+
// 创建 final-audit job
|
|
270
|
+
const docPaths = ["proposal.md", "tasks.md", "design.md", ".superspec/artifacts/discovery.md", ".superspec/artifacts/business-invariants.md", ".superspec/artifacts/test-contract.md"];
|
|
271
|
+
const boundFiles = docPaths.filter(p => existsSync(join(changeRoot, p))).map(p => ({ path: p, sha: sha256File(join(changeRoot, p)) ?? "sha256:missing" }));
|
|
272
|
+
const job = {
|
|
273
|
+
job_id: newJobId(change, "final-audit"), role: "final-audit", state: "requested",
|
|
274
|
+
boundFiles, packet_digest: sha256Text(JSON.stringify({ role: "final-audit", boundFiles })),
|
|
275
|
+
created_from_transition: "review-ready", created_at: new Date().toISOString(),
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
fromState: "apply_done", toState: "apply_done", outcome: "job_created",
|
|
279
|
+
newJobs: [job], reason: "创建最终审查工作项",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return { fromState: "apply_done", toState: "review", outcome: "advanced", reason: "最终审查已接受,进入审查阶段" };
|
|
283
|
+
}
|
|
284
|
+
return { skip: true, message: `当前状态 ${snapshot.state},review-ready 不适用` };
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// ===== accept =====
|
|
289
|
+
export function accept(projectRoot, change, changeRoot) {
|
|
290
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
291
|
+
name: "accept", idempotencyInputs: { phase: "accept" },
|
|
292
|
+
decide: (snapshot) => {
|
|
293
|
+
if (snapshot.state !== "review")
|
|
294
|
+
return { skip: true, message: `当前状态 ${snapshot.state},需要 review` };
|
|
295
|
+
return { fromState: "review", toState: "accepted", outcome: "advanced", reason: "审查通过" };
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// ===== archive =====
|
|
300
|
+
export function archive(projectRoot, change, changeRoot) {
|
|
301
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
302
|
+
name: "archive", idempotencyInputs: { phase: "archive" },
|
|
303
|
+
decide: (snapshot) => {
|
|
304
|
+
if (snapshot.state !== "accepted")
|
|
305
|
+
return { skip: true, message: `当前状态 ${snapshot.state},需要 accepted` };
|
|
306
|
+
// 构建保全清单(Phase 4 简化版:记录文档指纹 + specs/)
|
|
307
|
+
const manifest = {};
|
|
308
|
+
const docPaths = ["proposal.md", "tasks.md", "design.md", ".superspec/artifacts/discovery.md", ".superspec/artifacts/business-invariants.md", ".superspec/artifacts/test-contract.md"];
|
|
309
|
+
for (const p of docPaths) {
|
|
310
|
+
manifest[p] = sha256File(join(changeRoot, p)) ?? "sha256:missing";
|
|
311
|
+
}
|
|
312
|
+
// specs/ 目录
|
|
313
|
+
const specsDir = join(changeRoot, "specs");
|
|
314
|
+
if (existsSync(specsDir)) {
|
|
315
|
+
for (const f of readdirSync(specsDir)) {
|
|
316
|
+
if (f.endsWith(".md"))
|
|
317
|
+
manifest[`specs/${f}`] = sha256File(join(specsDir, f)) ?? "sha256:missing";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
fromState: "accepted", toState: "archive", outcome: "advanced",
|
|
322
|
+
reason: "归档完成",
|
|
323
|
+
extraEvents: [{ type: "artifact_recorded", payload: { kind: "archive_preservation_manifest", manifest } }],
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
// ===== task-complete =====
|
|
329
|
+
export function taskComplete(projectRoot, change, changeRoot, taskId) {
|
|
330
|
+
return commitTransition(projectRoot, change, changeRoot, {
|
|
331
|
+
name: "task-complete", idempotencyInputs: { task: taskId, phase: "complete" },
|
|
332
|
+
decide: (snapshot) => {
|
|
333
|
+
if (snapshot.state !== "apply")
|
|
334
|
+
return { skip: true, message: `当前状态 ${snapshot.state},需要 apply` };
|
|
335
|
+
const attempt = snapshot.active_task_attempts?.find(a => a.task_id === taskId && a.state === "active");
|
|
336
|
+
if (!attempt)
|
|
337
|
+
return { skip: true, message: `任务 ${taskId} 无活跃执行尝试` };
|
|
338
|
+
const tasksContent = readFileSync(join(changeRoot, "tasks.md"), "utf8");
|
|
339
|
+
const currentDigest = sha256Text(tasksContent.replace(/- \[[xX]\]/g, "- [ ]"));
|
|
340
|
+
if (currentDigest !== attempt.task_structure_digest)
|
|
341
|
+
return { skip: true, message: "任务结构指纹不匹配" };
|
|
342
|
+
// 解析任务属性(委托给 format.ts 的 parseTasksMd)
|
|
343
|
+
const tasks = parseTasksMd(tasksContent);
|
|
344
|
+
const taskInfo = tasks.find(t => t.taskId === taskId);
|
|
345
|
+
const isTdd = taskInfo?.tddRequired ?? true;
|
|
346
|
+
const hasNoTddReason = taskInfo?.noTddReason != null;
|
|
347
|
+
// RED/GREEN 检查(HIGH-2 修复:按 attempt_id 匹配,避免多任务 digest 碰撞)
|
|
348
|
+
const events = readEvents(projectRoot, change);
|
|
349
|
+
let hasRed = false, hasGreen = false;
|
|
350
|
+
for (const ev of events) {
|
|
351
|
+
if (ev.event_type === "test_run_recorded") {
|
|
352
|
+
const tr = ev.payload;
|
|
353
|
+
// 匹配当前 attempt(优先按 attempt_id,回退到 digest)
|
|
354
|
+
const matches = tr.attempt_id === attempt.attempt_id ||
|
|
355
|
+
(!tr.attempt_id && tr.task_structure_digest === attempt.task_structure_digest);
|
|
356
|
+
if (matches) {
|
|
357
|
+
if (tr.semantic_status === "expected_failure" || tr.semantic_status === "characterization_pass")
|
|
358
|
+
hasRed = true;
|
|
359
|
+
if (tr.semantic_status === "expected_success")
|
|
360
|
+
hasGreen = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (isTdd) {
|
|
365
|
+
if (!hasRed)
|
|
366
|
+
return { skip: true, message: `TDD 任务 ${taskId} 缺少 RED 证据` };
|
|
367
|
+
if (!hasGreen)
|
|
368
|
+
return { skip: true, message: `TDD 任务 ${taskId} 缺少 GREEN 证据` };
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
if (!hasNoTddReason)
|
|
372
|
+
return { skip: true, message: `非 TDD 任务 ${taskId} 缺少 no_tdd_reason` };
|
|
373
|
+
}
|
|
374
|
+
// B1 修复:checkbox 写入移到 postCommit(commit 事件写入后执行)
|
|
375
|
+
return {
|
|
376
|
+
fromState: "apply", toState: "apply", outcome: "advanced",
|
|
377
|
+
reason: `任务 ${taskId} 完成`,
|
|
378
|
+
extraEvents: [{ type: "task_completed", payload: { task_id: taskId, attempt_id: attempt.attempt_id } }],
|
|
379
|
+
postCommit: (_pr, _ch, cr) => {
|
|
380
|
+
const lines = readFileSync(join(cr, "tasks.md"), "utf8").split("\n");
|
|
381
|
+
const idx = findTaskLine(lines, taskId);
|
|
382
|
+
if (idx < 0 || !lines[idx].match(/- \[ \]/))
|
|
383
|
+
throw new Error(`找不到 ${taskId} 的未完成复选框`);
|
|
384
|
+
// H2 修复:应用前验证结构指纹
|
|
385
|
+
const beforeDigest = sha256Text(lines.join("\n").replace(/- \[[xX]\]/g, "- [ ]"));
|
|
386
|
+
lines[idx] = lines[idx].replace(/- \[ \]/, "- [x]");
|
|
387
|
+
writeFileSync(join(cr, "tasks.md"), lines.join("\n"));
|
|
388
|
+
// H2 修复:应用后验证只有目标变了
|
|
389
|
+
const afterLines = readFileSync(join(cr, "tasks.md"), "utf8").split("\n");
|
|
390
|
+
const afterDigest = sha256Text(afterLines.join("\n").replace(/- \[[xX]\]/g, "- [ ]"));
|
|
391
|
+
if (afterDigest !== beforeDigest) {
|
|
392
|
+
afterLines[idx] = afterLines[idx].replace(/- \[x\]/, "- [ ]");
|
|
393
|
+
writeFileSync(join(cr, "tasks.md"), afterLines.join("\n"));
|
|
394
|
+
throw new Error(`复选框补丁导致结构变化:${taskId}`);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type State = "init" | "explore" | "propose" | "propose_ready" | "apply" | "apply_done" | "review" | "accepted" | "archive" | "abandoned";
|
|
2
|
+
export declare const PHASE1_STATES: ReadonlySet<State>;
|
|
3
|
+
export type Ref = {
|
|
4
|
+
path: string;
|
|
5
|
+
sha: string;
|
|
6
|
+
};
|
|
7
|
+
export type JobState = "requested" | "accepted" | "rejected";
|
|
8
|
+
export type JobRole = "proposal-auditor" | "critic" | "architect" | "test-engineer" | "executor" | "test-run" | "final-audit";
|
|
9
|
+
export interface Job {
|
|
10
|
+
job_id: string;
|
|
11
|
+
role: JobRole;
|
|
12
|
+
state: JobState;
|
|
13
|
+
boundFiles: Ref[];
|
|
14
|
+
packet_digest: string;
|
|
15
|
+
created_from_transition: string;
|
|
16
|
+
created_at: string;
|
|
17
|
+
}
|
|
18
|
+
export interface JobPacket {
|
|
19
|
+
job_id: string;
|
|
20
|
+
role: JobRole;
|
|
21
|
+
recommended_agent?: string;
|
|
22
|
+
boundFiles: Ref[];
|
|
23
|
+
packet_digest: string;
|
|
24
|
+
required_output_kind: string;
|
|
25
|
+
output_contract_fields?: string[];
|
|
26
|
+
output_contract_optional_fields?: string[];
|
|
27
|
+
stop_conditions: string[];
|
|
28
|
+
created_from_transition: string;
|
|
29
|
+
}
|
|
30
|
+
export type EventType = "transition_prepare" | "transition_commit" | "job_requested" | "job_invalidated" | "reopen" | "abandon" | "task_started" | "task_completed" | "task_abandoned" | "job_accepted" | "job_rejected" | "user_decision_recorded" | "test_run_recorded" | "task_activation_recorded" | "artifact_recorded";
|
|
31
|
+
export interface Event {
|
|
32
|
+
event_id: string;
|
|
33
|
+
event_type: EventType;
|
|
34
|
+
change_id: string;
|
|
35
|
+
transition_id: string | null;
|
|
36
|
+
idempotency_key: string | null;
|
|
37
|
+
created_at: string;
|
|
38
|
+
actor: string;
|
|
39
|
+
prev_snapshot_digest: string | null;
|
|
40
|
+
input_refs: Ref[];
|
|
41
|
+
output_refs: Ref[];
|
|
42
|
+
payload: Record<string, unknown>;
|
|
43
|
+
event_digest: string;
|
|
44
|
+
}
|
|
45
|
+
export interface TransitionCommitPayload {
|
|
46
|
+
transition: string;
|
|
47
|
+
from_state: State;
|
|
48
|
+
to_state: State;
|
|
49
|
+
outcome: "advanced" | "job_created";
|
|
50
|
+
created_job_ids: string[];
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
53
|
+
export interface Snapshot {
|
|
54
|
+
change_id: string;
|
|
55
|
+
state: State;
|
|
56
|
+
openspec_status_digest: string;
|
|
57
|
+
events_digest: string;
|
|
58
|
+
document_digests: Record<string, string>;
|
|
59
|
+
tasks_structure_digest: string | null;
|
|
60
|
+
task_statuses: Record<string, "todo" | "doing" | "done">;
|
|
61
|
+
open_jobs: Job[];
|
|
62
|
+
accepted_jobs: Job[];
|
|
63
|
+
active_task_attempts: TaskAttempt[];
|
|
64
|
+
pending_user_decisions: AskUser[];
|
|
65
|
+
last_transition: string | null;
|
|
66
|
+
computed_at: string;
|
|
67
|
+
}
|
|
68
|
+
export type AttemptState = "active" | "closed" | "abandoned";
|
|
69
|
+
export interface TaskAttempt {
|
|
70
|
+
attempt_id: string;
|
|
71
|
+
task_id: string;
|
|
72
|
+
state: AttemptState;
|
|
73
|
+
task_structure_digest: string;
|
|
74
|
+
declared_write_scope: string[];
|
|
75
|
+
pre_edit_source_fingerprint: string | null;
|
|
76
|
+
pre_edit_red_ref: string | null;
|
|
77
|
+
executor_packet_digest: string | null;
|
|
78
|
+
executor_result_ref: string | null;
|
|
79
|
+
post_edit_green_ref: string | null;
|
|
80
|
+
created_at: string;
|
|
81
|
+
}
|
|
82
|
+
export interface TestRun {
|
|
83
|
+
test_id: string;
|
|
84
|
+
attempt_id?: string | null;
|
|
85
|
+
task_structure_digest: string;
|
|
86
|
+
command: string;
|
|
87
|
+
cwd: string;
|
|
88
|
+
exit_code: number;
|
|
89
|
+
semantic_status: "expected_failure" | "expected_success" | "characterization_pass" | "unknown";
|
|
90
|
+
target_fingerprint: string | null;
|
|
91
|
+
raw_log_ref: string | null;
|
|
92
|
+
created_at: string;
|
|
93
|
+
}
|
|
94
|
+
export interface MissingInput {
|
|
95
|
+
field: string;
|
|
96
|
+
expected: string;
|
|
97
|
+
command_to_fix: string;
|
|
98
|
+
}
|
|
99
|
+
export interface AskUser {
|
|
100
|
+
question: string;
|
|
101
|
+
allowed_answers: string[];
|
|
102
|
+
scope: string;
|
|
103
|
+
}
|
|
104
|
+
export type NextOutput = {
|
|
105
|
+
state: State;
|
|
106
|
+
} & ({
|
|
107
|
+
path: "next_command";
|
|
108
|
+
next_command: string;
|
|
109
|
+
reason: string;
|
|
110
|
+
missing_inputs: MissingInput[];
|
|
111
|
+
} | {
|
|
112
|
+
path: "required_job";
|
|
113
|
+
required_jobs: {
|
|
114
|
+
job_id: string;
|
|
115
|
+
role: JobRole;
|
|
116
|
+
packet_command: string;
|
|
117
|
+
}[];
|
|
118
|
+
reason: string;
|
|
119
|
+
} | {
|
|
120
|
+
path: "ask_user";
|
|
121
|
+
ask_user: AskUser;
|
|
122
|
+
reason: string;
|
|
123
|
+
} | {
|
|
124
|
+
path: "done";
|
|
125
|
+
reason: string;
|
|
126
|
+
});
|
|
127
|
+
export interface TransitionResult {
|
|
128
|
+
transition: string;
|
|
129
|
+
outcome: "advanced" | "job_created";
|
|
130
|
+
from_state: State;
|
|
131
|
+
to_state: State;
|
|
132
|
+
created_jobs: string[];
|
|
133
|
+
message: string;
|
|
134
|
+
events_written: number;
|
|
135
|
+
}
|
|
136
|
+
export interface RecordResult {
|
|
137
|
+
event_type: EventType;
|
|
138
|
+
accepted: boolean;
|
|
139
|
+
message: string;
|
|
140
|
+
job_state?: JobState;
|
|
141
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,65 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peterxiaoyang/superspec",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "SuperSpec
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/PeterYaoYang/SuperSpec.git"
|
|
8
|
-
},
|
|
9
|
-
"bugs": {
|
|
10
|
-
"url": "https://github.com/PeterYaoYang/SuperSpec/issues"
|
|
11
|
-
},
|
|
12
|
-
"homepage": "https://github.com/PeterYaoYang/SuperSpec#readme",
|
|
13
|
-
"private": false,
|
|
3
|
+
"version": "0.1.16-alpha",
|
|
4
|
+
"description": "SuperSpec 流程引擎 — transition engine with lightweight fact-sync",
|
|
14
5
|
"type": "module",
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
"engines": {
|
|
19
|
-
"node": ">=20.19.0"
|
|
20
|
-
},
|
|
21
|
-
"bin": {
|
|
22
|
-
"superspec": "bin/superspec.js",
|
|
23
|
-
"superspec-check": "bin/superspec-check.js",
|
|
24
|
-
"superspec-hook": "bin/superspec-hook.js",
|
|
25
|
-
"superspec-init": "bin/superspec-init.js"
|
|
26
|
-
},
|
|
6
|
+
"engines": { "node": ">=20.19.0" },
|
|
7
|
+
"bin": { "superspec": "dist/cli.js" },
|
|
27
8
|
"exports": {
|
|
28
|
-
".": {
|
|
29
|
-
"types": "./dist/superspec.d.ts",
|
|
30
|
-
"default": "./dist/superspec.js"
|
|
31
|
-
},
|
|
32
|
-
"./superspec_guard": {
|
|
33
|
-
"types": "./dist/superspec_guard.d.ts",
|
|
34
|
-
"default": "./dist/superspec_guard.js"
|
|
35
|
-
},
|
|
36
|
-
"./superspec_hook": {
|
|
37
|
-
"types": "./dist/superspec_hook.d.ts",
|
|
38
|
-
"default": "./dist/superspec_hook.js"
|
|
39
|
-
},
|
|
40
|
-
"./superspec_init": {
|
|
41
|
-
"types": "./dist/superspec_init.d.ts",
|
|
42
|
-
"default": "./dist/superspec_init.js"
|
|
43
|
-
},
|
|
44
|
-
"./templates/*": "./templates/*",
|
|
45
|
-
"./adapters/*": "./adapters/*",
|
|
46
|
-
"./schemas/*": "./schemas/*"
|
|
9
|
+
".": { "types": "./dist/types.d.ts", "default": "./dist/cli.js" }
|
|
47
10
|
},
|
|
48
|
-
"files": [
|
|
49
|
-
"README.md",
|
|
50
|
-
"bin",
|
|
51
|
-
"dist",
|
|
52
|
-
"templates",
|
|
53
|
-
"adapters",
|
|
54
|
-
"schemas"
|
|
55
|
-
],
|
|
11
|
+
"files": ["README.md", "dist", "templates"],
|
|
56
12
|
"scripts": {
|
|
57
13
|
"build": "node build.js",
|
|
58
14
|
"typecheck": "tsc --noEmit",
|
|
59
|
-
"test": "node --test tests
|
|
15
|
+
"test": "node --test tests/*.ts",
|
|
60
16
|
"prepack": "npm run build",
|
|
61
|
-
"prepublishOnly": "npm run build"
|
|
62
|
-
"pack:dry-run": "npm pack --dry-run"
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
63
18
|
},
|
|
64
19
|
"devDependencies": {
|
|
65
20
|
"@types/node": "^20.19.0",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SuperSpec Codex agent: architect
|
|
2
|
+
name = "architect"
|
|
3
|
+
description = "System design, boundaries, interfaces, long-horizon tradeoffs"
|
|
4
|
+
model_reasoning_effort = "high"
|
|
5
|
+
developer_instructions = """
|
|
6
|
+
Role: Architect. Review system boundaries, interface contracts, data flow, maintenance risk, rollback risk, and design tradeoffs.
|
|
7
|
+
|
|
8
|
+
Prompt binding: load `.codex/prompts/architect.md` first, then read the provided `review-packet`, job packet, or `prompt_ref`. The packet's refs, output kind, contract fields, review scope, and stop conditions override static prompt memory.
|
|
9
|
+
|
|
10
|
+
Boundary: read-only. Do not edit files or judge materials you have not opened. Report missing context upward instead of guessing.
|
|
11
|
+
|
|
12
|
+
Output: concise Simplified Chinese. For `job_report_json`, submit JSON with `role:"architect"`, `verdict`, and `findings`. Otherwise put the conclusion first, cite file:line evidence, and write `无阻塞问题` when no blocking issue is found.
|
|
13
|
+
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SuperSpec Codex agent: code-reviewer
|
|
2
2
|
name = "code-reviewer"
|
|
3
3
|
description = "Comprehensive review across all concerns"
|
|
4
|
-
model_reasoning_effort = "
|
|
4
|
+
model_reasoning_effort = "high"
|
|
5
5
|
developer_instructions = """
|
|
6
6
|
Role: Code Reviewer. Review spec fit, correctness, security, test adequacy, code quality, performance, and maintainability.
|
|
7
7
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SuperSpec Codex agent: critic
|
|
2
|
+
name = "critic"
|
|
3
|
+
description = "Plan/design critical challenge and review"
|
|
4
|
+
model_reasoning_effort = "high"
|
|
5
|
+
developer_instructions = """
|
|
6
|
+
Role: Critic. Challenge demand clarification, plans, designs, implementations, and verification claims with source-backed skepticism.
|
|
7
|
+
|
|
8
|
+
Prompt binding: load `.codex/prompts/critic.md` first, then read the provided `review-packet`, job packet, or `prompt_ref`. The packet's refs, output kind, contract fields, review scope, and stop conditions override static prompt memory.
|
|
9
|
+
|
|
10
|
+
Boundary: read-only by default. Do not edit files, invent issues, or widen scope silently. Report missing source refs or claim gaps upward.
|
|
11
|
+
|
|
12
|
+
Output: concise Simplified Chinese. For `job_report_json`, submit JSON with `role:"critic"`, `verdict`, and `findings`. Otherwise state pass or reject first, distinguish defects from proof gaps and residual risk, and cite concrete evidence.
|
|
13
|
+
"""
|