@peterxiaoyang/superspec 0.1.24 → 0.1.26
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 +10 -2
- package/dist/cli.js +75 -17
- package/dist/install.d.ts +1 -0
- package/dist/install.js +37 -1
- package/dist/job_action.d.ts +6 -0
- package/dist/job_action.js +21 -0
- package/dist/next.js +37 -48
- package/dist/record.d.ts +4 -0
- package/dist/record.js +180 -152
- package/dist/task.d.ts +5 -0
- package/dist/task.js +38 -28
- package/dist/transition.d.ts +11 -4
- package/dist/transition.js +19 -2
- package/dist/types.d.ts +13 -6
- package/package.json +1 -1
- package/templates/workflow/AGENTS.md +11 -0
- package/templates/workflow/prompts/architect.md +1 -1
- package/templates/workflow/prompts/critic.md +1 -1
- package/templates/workflow/prompts/test-engineer.md +1 -1
- package/templates/workflow/skills/superspec-apply/SKILL.md +4 -2
- package/templates/workflow/skills/superspec-archive/SKILL.md +2 -0
- package/templates/workflow/skills/superspec-explore/SKILL.md +12 -10
- package/templates/workflow/skills/superspec-propose/SKILL.md +7 -3
- package/templates/workflow/skills/superspec-review/SKILL.md +3 -1
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ superspec install
|
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
这条命令的意思是:把 SuperSpec 当前可用的工作流入口安装到项目里。
|
|
84
|
-
当前 beta 会安装 `.superspec/` 引擎目录、`.codex/skills/superspec-*` 阶段入口、`.codex/prompts/*.md` 角色 prompt、`.codex/agents/*.toml`
|
|
84
|
+
当前 beta 会安装 `.superspec/` 引擎目录、`.codex/skills/superspec-*` 阶段入口、`.codex/prompts/*.md` 角色 prompt、`.codex/agents/*.toml` 子智能体配置,补齐 `.codex/config.toml` 的多 agent 开关,并在项目根 `AGENTS.md` 中维护 SuperSpec 轻量门禁片段。`superspec init --scope project` 仍作为兼容别名可用。
|
|
85
85
|
|
|
86
86
|
Windows PowerShell 如果拦截 npm 的 `.ps1` 脚本,请改用:
|
|
87
87
|
|
|
@@ -160,6 +160,14 @@ openspec/changes/<变更ID>/.superspec/
|
|
|
160
160
|
`.superspec/` 要不要提交到 git,由你的团队决定。
|
|
161
161
|
如果不提交,删掉后就没有 git 历史可以恢复。
|
|
162
162
|
|
|
163
|
+
## 流程门禁
|
|
164
|
+
|
|
165
|
+
SuperSpec 的阶段入口由 `superspec transition next --change <变更ID>` 驱动。
|
|
166
|
+
|
|
167
|
+
当当前阶段还有未完成的用户确认、审查、验证或工作项时,`next` 会先返回这些事项,不会把下一阶段命令作为推荐路径。重复运行会创建审查工作项的 transition 时,如果同阶段工作项已经存在,CLI 会返回正常的门禁结果,不会写入新事件,也不会把它当作程序错误。
|
|
168
|
+
|
|
169
|
+
这仍然是轻量流程控制,不是写入拦截。它约束按 SuperSpec 正常入口执行时的下一步建议和状态提交结果,不承诺阻止绕过流程的手动编辑。
|
|
170
|
+
|
|
163
171
|
## Hook 会做什么
|
|
164
172
|
|
|
165
173
|
SuperSpec 默认安装的 hook 只在子智能体启动和停止时运行:
|
|
@@ -238,7 +246,7 @@ superspec status
|
|
|
238
246
|
superspec update
|
|
239
247
|
```
|
|
240
248
|
|
|
241
|
-
这条命令会先检查 npm 上的 latest 版本;如果有新版,会自动执行全局升级并用新版 CLI 重新同步项目入口。同步内容包括补齐 `.superspec/changes`
|
|
249
|
+
这条命令会先检查 npm 上的 latest 版本;如果有新版,会自动执行全局升级并用新版 CLI 重新同步项目入口。同步内容包括补齐 `.superspec/changes` 运行时目录,把当前 CLI 内置的 `.codex/skills/superspec-*`、`.codex/prompts/*.md`、`.codex/agents/*.toml` 同步到项目里,并更新 `AGENTS.md` 中 marker 包裹的 SuperSpec 轻量门禁片段。
|
|
242
250
|
|
|
243
251
|
## 进阶信息
|
|
244
252
|
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SuperSpec 流程引擎 — CLI 入口
|
|
3
|
-
import { join } from "node:path";
|
|
4
3
|
import { execFileSync } from "node:child_process";
|
|
5
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
6
|
import { installProject } from "./install.js";
|
|
7
7
|
import { writeSnapshot } from "./store.js";
|
|
8
8
|
import { rebuildSnapshot } from "./sync.js";
|
|
9
9
|
import { next as nextCmd } from "./next.js";
|
|
10
10
|
import { proposeReady, commitTransition, transitionInit, transitionExplore, startApply, taskStart, taskComplete, reopen, reviewReady, accept, archive } from "./transition.js";
|
|
11
|
-
import { recordJobSubmit, recordUserDecision, jobsList, jobsPacket } from "./record.js";
|
|
12
|
-
import { recordTestRun } from "./task.js";
|
|
11
|
+
import { recordJobSubmit, recordJobSubmitContent, recordUserDecision, recordUserDecisionContent, jobsList, jobsPacket } from "./record.js";
|
|
12
|
+
import { recordTestRun, recordTestRunContent } from "./task.js";
|
|
13
13
|
import { probeOpenSpec, openspecStatus, changeRoot } from "./openspec.js";
|
|
14
14
|
import { SUPERSPEC_VERSION } from "./version.js";
|
|
15
15
|
const PACKAGE_NAME = "@peterxiaoyang/superspec";
|
|
@@ -42,6 +42,28 @@ function parseFlags(args) {
|
|
|
42
42
|
}
|
|
43
43
|
return opts;
|
|
44
44
|
}
|
|
45
|
+
class StdinRecordInputError extends Error {
|
|
46
|
+
constructor(flag) {
|
|
47
|
+
super(`${flag} - 需要通过 pipe 或重定向提供 JSON;不方便时请使用文件路径。`);
|
|
48
|
+
this.name = "StdinRecordInputError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function readStdinRecordContent(flag) {
|
|
52
|
+
if (process.stdin.isTTY === true) {
|
|
53
|
+
throw new StdinRecordInputError(flag);
|
|
54
|
+
}
|
|
55
|
+
return readFileSync(0, "utf8");
|
|
56
|
+
}
|
|
57
|
+
function transitionExitCode(result) {
|
|
58
|
+
if (result.outcome === "blocked")
|
|
59
|
+
return 0;
|
|
60
|
+
return result.events_written === 0 ? 1 : 0;
|
|
61
|
+
}
|
|
62
|
+
function proposeReadyExitCode(result) {
|
|
63
|
+
if (result.outcome === "blocked")
|
|
64
|
+
return 0;
|
|
65
|
+
return result.events_written === 0 && result.message.includes("不能") ? 1 : 0;
|
|
66
|
+
}
|
|
45
67
|
function parseVersion(version) {
|
|
46
68
|
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
47
69
|
if (!match)
|
|
@@ -342,9 +364,9 @@ transition 子命令:
|
|
|
342
364
|
review-ready / accept / archive
|
|
343
365
|
|
|
344
366
|
record 子命令:
|
|
345
|
-
job-submit --job <J> --report <F
|
|
346
|
-
user-decision --input <F
|
|
347
|
-
test-run --input <F
|
|
367
|
+
job-submit --job <J> --report <F|->
|
|
368
|
+
user-decision --input <F|->
|
|
369
|
+
test-run --input <F|->
|
|
348
370
|
|
|
349
371
|
jobs 子命令:
|
|
350
372
|
list / packet --job <J>
|
|
@@ -482,12 +504,12 @@ jobs 子命令:
|
|
|
482
504
|
const risk = opts.risk ?? "strict";
|
|
483
505
|
const result = proposeReady(projectRoot, change, cr, risk);
|
|
484
506
|
console.log(JSON.stringify(result, null, 2));
|
|
485
|
-
return result
|
|
507
|
+
return proposeReadyExitCode(result);
|
|
486
508
|
}
|
|
487
509
|
case "start-apply": {
|
|
488
510
|
const result = startApply(projectRoot, change, cr);
|
|
489
511
|
console.log(JSON.stringify(result, null, 2));
|
|
490
|
-
return result
|
|
512
|
+
return transitionExitCode(result);
|
|
491
513
|
}
|
|
492
514
|
case "task-start": {
|
|
493
515
|
const taskId = opts.task;
|
|
@@ -497,7 +519,7 @@ jobs 子命令:
|
|
|
497
519
|
}
|
|
498
520
|
const result = taskStart(projectRoot, change, cr, taskId);
|
|
499
521
|
console.log(JSON.stringify(result, null, 2));
|
|
500
|
-
return result
|
|
522
|
+
return transitionExitCode(result);
|
|
501
523
|
}
|
|
502
524
|
case "task-complete": {
|
|
503
525
|
const taskId = opts.task;
|
|
@@ -507,7 +529,7 @@ jobs 子命令:
|
|
|
507
529
|
}
|
|
508
530
|
const result = taskComplete(projectRoot, change, cr, taskId);
|
|
509
531
|
console.log(JSON.stringify(result, null, 2));
|
|
510
|
-
return result
|
|
532
|
+
return transitionExitCode(result);
|
|
511
533
|
}
|
|
512
534
|
case "reopen": {
|
|
513
535
|
const to = opts.to;
|
|
@@ -522,23 +544,23 @@ jobs 子命令:
|
|
|
522
544
|
}
|
|
523
545
|
const result = reopen(projectRoot, change, cr, to, reason);
|
|
524
546
|
console.log(JSON.stringify(result, null, 2));
|
|
525
|
-
return result
|
|
547
|
+
return transitionExitCode(result);
|
|
526
548
|
}
|
|
527
549
|
case "review-ready": {
|
|
528
550
|
const risk = opts.risk ?? "strict";
|
|
529
551
|
const result = reviewReady(projectRoot, change, cr, risk);
|
|
530
552
|
console.log(JSON.stringify(result, null, 2));
|
|
531
|
-
return result
|
|
553
|
+
return transitionExitCode(result);
|
|
532
554
|
}
|
|
533
555
|
case "accept": {
|
|
534
556
|
const result = accept(projectRoot, change, cr);
|
|
535
557
|
console.log(JSON.stringify(result, null, 2));
|
|
536
|
-
return result
|
|
558
|
+
return transitionExitCode(result);
|
|
537
559
|
}
|
|
538
560
|
case "archive": {
|
|
539
561
|
const result = archive(projectRoot, change, cr);
|
|
540
562
|
console.log(JSON.stringify(result, null, 2));
|
|
541
|
-
return result
|
|
563
|
+
return transitionExitCode(result);
|
|
542
564
|
}
|
|
543
565
|
default:
|
|
544
566
|
console.error(`未知的 transition 子命令:${subcommand}`);
|
|
@@ -555,7 +577,19 @@ jobs 子命令:
|
|
|
555
577
|
console.error("record job-submit 需要 --job 和 --report");
|
|
556
578
|
return 1;
|
|
557
579
|
}
|
|
558
|
-
|
|
580
|
+
let result;
|
|
581
|
+
try {
|
|
582
|
+
result = report === "-"
|
|
583
|
+
? recordJobSubmitContent(projectRoot, change, cr, jobId, readStdinRecordContent("--report"))
|
|
584
|
+
: recordJobSubmit(projectRoot, change, cr, jobId, report);
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
if (err instanceof StdinRecordInputError) {
|
|
588
|
+
console.error(err.message);
|
|
589
|
+
return 1;
|
|
590
|
+
}
|
|
591
|
+
throw err;
|
|
592
|
+
}
|
|
559
593
|
console.log(JSON.stringify(result, null, 2));
|
|
560
594
|
return result.accepted ? 0 : 1;
|
|
561
595
|
}
|
|
@@ -565,7 +599,19 @@ jobs 子命令:
|
|
|
565
599
|
console.error("record user-decision 需要 --input");
|
|
566
600
|
return 1;
|
|
567
601
|
}
|
|
568
|
-
|
|
602
|
+
let result;
|
|
603
|
+
try {
|
|
604
|
+
result = inputFile === "-"
|
|
605
|
+
? recordUserDecisionContent(projectRoot, change, readStdinRecordContent("--input"))
|
|
606
|
+
: recordUserDecision(projectRoot, change, inputFile);
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
if (err instanceof StdinRecordInputError) {
|
|
610
|
+
console.error(err.message);
|
|
611
|
+
return 1;
|
|
612
|
+
}
|
|
613
|
+
throw err;
|
|
614
|
+
}
|
|
569
615
|
console.log(JSON.stringify(result, null, 2));
|
|
570
616
|
return result.accepted ? 0 : 1;
|
|
571
617
|
}
|
|
@@ -575,7 +621,19 @@ jobs 子命令:
|
|
|
575
621
|
console.error("record test-run 需要 --input");
|
|
576
622
|
return 1;
|
|
577
623
|
}
|
|
578
|
-
|
|
624
|
+
let result;
|
|
625
|
+
try {
|
|
626
|
+
result = inputFile === "-"
|
|
627
|
+
? recordTestRunContent(projectRoot, change, readStdinRecordContent("--input"))
|
|
628
|
+
: recordTestRun(projectRoot, change, inputFile);
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
if (err instanceof StdinRecordInputError) {
|
|
632
|
+
console.error(err.message);
|
|
633
|
+
return 1;
|
|
634
|
+
}
|
|
635
|
+
throw err;
|
|
636
|
+
}
|
|
579
637
|
console.log(JSON.stringify(result, null, 2));
|
|
580
638
|
return result.accepted ? 0 : 1;
|
|
581
639
|
}
|
package/dist/install.d.ts
CHANGED
package/dist/install.js
CHANGED
|
@@ -55,6 +55,9 @@ function assertWorkflowTemplates(templateRoot) {
|
|
|
55
55
|
if (!existsSync(file))
|
|
56
56
|
missing.push(file);
|
|
57
57
|
}
|
|
58
|
+
const agentsMdTemplate = join(templateRoot, "AGENTS.md");
|
|
59
|
+
if (!existsSync(agentsMdTemplate))
|
|
60
|
+
missing.push(agentsMdTemplate);
|
|
58
61
|
if (missing.length > 0) {
|
|
59
62
|
throw new Error(`workflow templates missing: ${missing.join(", ")}`);
|
|
60
63
|
}
|
|
@@ -66,7 +69,6 @@ function writeBundledFile(src, dest) {
|
|
|
66
69
|
const current = readFileSync(dest, "utf8");
|
|
67
70
|
if (current === next)
|
|
68
71
|
return;
|
|
69
|
-
writeFileSync(`${dest}.bak`, current);
|
|
70
72
|
}
|
|
71
73
|
writeFileSync(dest, next);
|
|
72
74
|
}
|
|
@@ -242,6 +244,38 @@ function ensureOpenSpecChineseContext(projectRoot) {
|
|
|
242
244
|
writeFileSync(configPath, next);
|
|
243
245
|
return OPENSPEC_CONFIG_PATH;
|
|
244
246
|
}
|
|
247
|
+
const AGENTS_MD_PATH = "AGENTS.md";
|
|
248
|
+
const SUPERSPEC_AGENTS_START = "<!-- SUPERSPEC:AGENTS:START -->";
|
|
249
|
+
const SUPERSPEC_AGENTS_END = "<!-- SUPERSPEC:AGENTS:END -->";
|
|
250
|
+
function readAgentsMdTemplate(templateRoot) {
|
|
251
|
+
const template = readFileSync(join(templateRoot, AGENTS_MD_PATH), "utf8").trimEnd();
|
|
252
|
+
if (!template.includes(SUPERSPEC_AGENTS_START) || !template.includes(SUPERSPEC_AGENTS_END)) {
|
|
253
|
+
throw new Error(`workflow AGENTS.md template missing SuperSpec markers: ${join(templateRoot, AGENTS_MD_PATH)}`);
|
|
254
|
+
}
|
|
255
|
+
return template;
|
|
256
|
+
}
|
|
257
|
+
function replaceMarkerBlock(content, block) {
|
|
258
|
+
const start = content.indexOf(SUPERSPEC_AGENTS_START);
|
|
259
|
+
const end = content.indexOf(SUPERSPEC_AGENTS_END);
|
|
260
|
+
if (start < 0 || end < 0 || end < start)
|
|
261
|
+
return null;
|
|
262
|
+
const afterEnd = end + SUPERSPEC_AGENTS_END.length;
|
|
263
|
+
return `${content.slice(0, start)}${block}${content.slice(afterEnd)}`;
|
|
264
|
+
}
|
|
265
|
+
function ensureAgentsMd(projectRoot, block) {
|
|
266
|
+
const agentsPath = join(projectRoot, AGENTS_MD_PATH);
|
|
267
|
+
if (!existsSync(agentsPath)) {
|
|
268
|
+
writeFileSync(agentsPath, `${block}\n`);
|
|
269
|
+
return AGENTS_MD_PATH;
|
|
270
|
+
}
|
|
271
|
+
const current = readFileSync(agentsPath, "utf8");
|
|
272
|
+
const replaced = replaceMarkerBlock(current, block);
|
|
273
|
+
const next = replaced ?? `${current.trimEnd()}\n\n${block}\n`;
|
|
274
|
+
if (next === current)
|
|
275
|
+
return AGENTS_MD_PATH;
|
|
276
|
+
writeFileSync(agentsPath, next);
|
|
277
|
+
return AGENTS_MD_PATH;
|
|
278
|
+
}
|
|
245
279
|
export function installProject(projectRoot, options = {}) {
|
|
246
280
|
if (!options.allowLegacyState && legacyStateFound(projectRoot)) {
|
|
247
281
|
throw new Error("检测到老版 SuperSpec (0.x) 的状态文件。\n" +
|
|
@@ -251,6 +285,7 @@ export function installProject(projectRoot, options = {}) {
|
|
|
251
285
|
}
|
|
252
286
|
const templateRoot = options.templateRoot ?? defaultTemplateRoot();
|
|
253
287
|
assertWorkflowTemplates(templateRoot);
|
|
288
|
+
const agentsMdTemplate = readAgentsMdTemplate(templateRoot);
|
|
254
289
|
const engineDir = join(projectRoot, ".superspec");
|
|
255
290
|
mkdirSync(join(engineDir, "changes"), { recursive: true });
|
|
256
291
|
const gitignorePath = join(engineDir, ".gitignore");
|
|
@@ -266,6 +301,7 @@ export function installProject(projectRoot, options = {}) {
|
|
|
266
301
|
prompts: copyPrompts(templateRoot, projectRoot),
|
|
267
302
|
agents: copyAgents(templateRoot, projectRoot),
|
|
268
303
|
config: ensureCodexConfig(projectRoot),
|
|
304
|
+
agents_md: ensureAgentsMd(projectRoot, agentsMdTemplate),
|
|
269
305
|
openspec_config: ensureOpenSpecChineseContext(projectRoot),
|
|
270
306
|
},
|
|
271
307
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Job, RequiredJobAction } from "./types.ts";
|
|
2
|
+
export declare function jobPacketCommand(change: string, jobId: string): string;
|
|
3
|
+
export declare function jobPacketArgv(change: string, jobId: string): string[];
|
|
4
|
+
export declare function requiredJobAction(change: string, job: Job): RequiredJobAction;
|
|
5
|
+
export declare function requiredJobActions(change: string, jobs: Job[]): RequiredJobAction[];
|
|
6
|
+
export declare function jobSubmitArgv(change: string, jobId: string): string[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SuperSpec workflow engine - shared job action protocol helpers.
|
|
2
|
+
export function jobPacketCommand(change, jobId) {
|
|
3
|
+
return `superspec jobs packet --change "${change}" --job "${jobId}"`;
|
|
4
|
+
}
|
|
5
|
+
export function jobPacketArgv(change, jobId) {
|
|
6
|
+
return ["superspec", "jobs", "packet", "--change", change, "--job", jobId];
|
|
7
|
+
}
|
|
8
|
+
export function requiredJobAction(change, job) {
|
|
9
|
+
return {
|
|
10
|
+
job_id: job.job_id,
|
|
11
|
+
role: job.role,
|
|
12
|
+
packet_command: jobPacketCommand(change, job.job_id),
|
|
13
|
+
packet_argv: jobPacketArgv(change, job.job_id),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function requiredJobActions(change, jobs) {
|
|
17
|
+
return jobs.map(job => requiredJobAction(change, job));
|
|
18
|
+
}
|
|
19
|
+
export function jobSubmitArgv(change, jobId) {
|
|
20
|
+
return ["superspec", "record", "job-submit", "--change", change, "--job", jobId, "--report", "-"];
|
|
21
|
+
}
|
package/dist/next.js
CHANGED
|
@@ -4,13 +4,22 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { readEvents, sha256Text } from "./store.js";
|
|
6
6
|
import { isFreshReviewVerifier, isReviewReadyVerifier, readReviewPolicyFromEvents, reviewEvidenceDigest } from "./review.js";
|
|
7
|
+
import { requiredJobActions } from "./job_action.js";
|
|
7
8
|
import { validateDiscovery, countDiscoveryOpenQuestions, collectProposeOpenQuestions, parseTasksMd, pendingTasksInContent } from "./format.js";
|
|
8
9
|
const ACTIVE_PROPOSAL_REVIEW_ROLES = new Set(["critic", "architect", "test-engineer"]);
|
|
9
10
|
function isActiveProposalReviewJob(job) {
|
|
10
11
|
return job.created_from_transition === "propose-ready" && ACTIVE_PROPOSAL_REVIEW_ROLES.has(job.role);
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
13
|
+
function isExploreReviewJob(job) {
|
|
14
|
+
return job.created_from_transition === "explore" && job.role === "critic";
|
|
15
|
+
}
|
|
16
|
+
function requiredJobsOutput(state, change, jobs, reason) {
|
|
17
|
+
return {
|
|
18
|
+
state,
|
|
19
|
+
path: "required_job",
|
|
20
|
+
required_jobs: requiredJobActions(change, jobs),
|
|
21
|
+
reason,
|
|
22
|
+
};
|
|
14
23
|
}
|
|
15
24
|
function transitionCommand(change, name, extra = "") {
|
|
16
25
|
return `superspec transition ${name} --change "${change}"${extra ? " " + extra : ""}`;
|
|
@@ -67,6 +76,9 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
67
76
|
const snapshot = rebuildSnapshot(projectRoot, change, changeRoot);
|
|
68
77
|
switch (snapshot.state) {
|
|
69
78
|
case "init":
|
|
79
|
+
if (snapshot.open_jobs.length > 0) {
|
|
80
|
+
return requiredJobsOutput("init", change, snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项`);
|
|
81
|
+
}
|
|
70
82
|
return {
|
|
71
83
|
state: "init",
|
|
72
84
|
path: "next_command",
|
|
@@ -75,6 +87,10 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
75
87
|
missing_inputs: [],
|
|
76
88
|
};
|
|
77
89
|
case "explore": {
|
|
90
|
+
const exploreReviewJobs = snapshot.open_jobs.filter(isExploreReviewJob);
|
|
91
|
+
if (exploreReviewJobs.length > 0) {
|
|
92
|
+
return requiredJobsOutput("explore", change, exploreReviewJobs, `有 ${exploreReviewJobs.length} 个待完成探索审查工作项`);
|
|
93
|
+
}
|
|
78
94
|
// Phase 2:检查 discovery.md
|
|
79
95
|
const discoveryCheck = validateDiscovery(changeRoot);
|
|
80
96
|
if (!discoveryCheck.ok) {
|
|
@@ -117,17 +133,7 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
117
133
|
}
|
|
118
134
|
const proposalReviewJobs = snapshot.open_jobs.filter(isActiveProposalReviewJob);
|
|
119
135
|
if (proposalReviewJobs.length > 0) {
|
|
120
|
-
|
|
121
|
-
job_id: j.job_id,
|
|
122
|
-
role: j.role,
|
|
123
|
-
packet_command: packetCommand(change, j.job_id),
|
|
124
|
-
}));
|
|
125
|
-
return {
|
|
126
|
-
state: "propose",
|
|
127
|
-
path: "required_job",
|
|
128
|
-
required_jobs: jobs,
|
|
129
|
-
reason: `有 ${jobs.length} 个待完成工作项`,
|
|
130
|
-
};
|
|
136
|
+
return requiredJobsOutput("propose", change, proposalReviewJobs, `有 ${proposalReviewJobs.length} 个待完成 proposal 审查工作项`);
|
|
131
137
|
}
|
|
132
138
|
return {
|
|
133
139
|
state: "propose",
|
|
@@ -140,16 +146,7 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
140
146
|
case "propose_ready": {
|
|
141
147
|
const proposalReviewJobs = snapshot.open_jobs.filter(isActiveProposalReviewJob);
|
|
142
148
|
if (proposalReviewJobs.length > 0) {
|
|
143
|
-
return {
|
|
144
|
-
state: "propose_ready",
|
|
145
|
-
path: "required_job",
|
|
146
|
-
required_jobs: proposalReviewJobs.map(j => ({
|
|
147
|
-
job_id: j.job_id,
|
|
148
|
-
role: j.role,
|
|
149
|
-
packet_command: packetCommand(change, j.job_id),
|
|
150
|
-
})),
|
|
151
|
-
reason: `有 ${proposalReviewJobs.length} 个待完成 proposal 审查工作项`,
|
|
152
|
-
};
|
|
149
|
+
return requiredJobsOutput("propose_ready", change, proposalReviewJobs, `有 ${proposalReviewJobs.length} 个待完成 proposal 审查工作项`);
|
|
153
150
|
}
|
|
154
151
|
return {
|
|
155
152
|
state: "propose_ready",
|
|
@@ -160,6 +157,9 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
160
157
|
};
|
|
161
158
|
}
|
|
162
159
|
case "apply": {
|
|
160
|
+
if (snapshot.open_jobs.length > 0) {
|
|
161
|
+
return requiredJobsOutput("apply", change, snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项`);
|
|
162
|
+
}
|
|
163
163
|
// 检查是否所有任务已完成
|
|
164
164
|
const pending = pendingTaskIds(changeRoot);
|
|
165
165
|
if (pending.length > 0) {
|
|
@@ -190,15 +190,6 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
190
190
|
missing_inputs: [],
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
|
-
// 有 open job → 做
|
|
194
|
-
if (snapshot.open_jobs.length > 0) {
|
|
195
|
-
return {
|
|
196
|
-
state: "apply",
|
|
197
|
-
path: "required_job",
|
|
198
|
-
required_jobs: snapshot.open_jobs.map(j => ({ job_id: j.job_id, role: j.role, packet_command: packetCommand(change, j.job_id) })),
|
|
199
|
-
reason: `有 ${snapshot.open_jobs.length} 个待完成工作项`,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
193
|
return {
|
|
203
194
|
state: "apply",
|
|
204
195
|
path: "next_command",
|
|
@@ -219,12 +210,7 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
219
210
|
};
|
|
220
211
|
}
|
|
221
212
|
if (snapshot.open_jobs.length > 0) {
|
|
222
|
-
return {
|
|
223
|
-
state: "apply_done",
|
|
224
|
-
path: "required_job",
|
|
225
|
-
required_jobs: snapshot.open_jobs.map(j => ({ job_id: j.job_id, role: j.role, packet_command: packetCommand(change, j.job_id) })),
|
|
226
|
-
reason: `有 ${snapshot.open_jobs.length} 个待完成工作项`,
|
|
227
|
-
};
|
|
213
|
+
return requiredJobsOutput("apply_done", change, snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项`);
|
|
228
214
|
}
|
|
229
215
|
return {
|
|
230
216
|
state: "apply_done",
|
|
@@ -247,16 +233,7 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
247
233
|
}
|
|
248
234
|
const reviewVerifierJobs = snapshot.open_jobs.filter(isReviewReadyVerifier);
|
|
249
235
|
if (reviewVerifierJobs.length > 0) {
|
|
250
|
-
return {
|
|
251
|
-
state: "review",
|
|
252
|
-
path: "required_job",
|
|
253
|
-
required_jobs: reviewVerifierJobs.map(j => ({
|
|
254
|
-
job_id: j.job_id,
|
|
255
|
-
role: j.role,
|
|
256
|
-
packet_command: packetCommand(change, j.job_id),
|
|
257
|
-
})),
|
|
258
|
-
reason: `有 ${reviewVerifierJobs.length} 个待完成最终验证工作项`,
|
|
259
|
-
};
|
|
236
|
+
return requiredJobsOutput("review", change, reviewVerifierJobs, `有 ${reviewVerifierJobs.length} 个待完成最终验证工作项`);
|
|
260
237
|
}
|
|
261
238
|
const events = readEvents(projectRoot, change);
|
|
262
239
|
const policy = readReviewPolicyFromEvents(events);
|
|
@@ -291,6 +268,9 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
291
268
|
};
|
|
292
269
|
}
|
|
293
270
|
case "accepted":
|
|
271
|
+
if (snapshot.open_jobs.length > 0) {
|
|
272
|
+
return requiredJobsOutput("accepted", change, snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项,暂不归档`);
|
|
273
|
+
}
|
|
294
274
|
return {
|
|
295
275
|
state: "accepted",
|
|
296
276
|
path: "next_command",
|
|
@@ -299,11 +279,20 @@ export function next(projectRoot, change, changeRoot, defaultRisk = "strict") {
|
|
|
299
279
|
missing_inputs: [],
|
|
300
280
|
};
|
|
301
281
|
case "archive":
|
|
282
|
+
if (snapshot.open_jobs.length > 0) {
|
|
283
|
+
return requiredJobsOutput("archive", change, snapshot.open_jobs, `有 ${snapshot.open_jobs.length} 个待完成工作项,暂不结束`);
|
|
284
|
+
}
|
|
302
285
|
return {
|
|
303
286
|
state: "archive",
|
|
304
287
|
path: "done",
|
|
305
288
|
reason: "已归档,流程完成。",
|
|
306
289
|
};
|
|
290
|
+
case "abandoned":
|
|
291
|
+
return {
|
|
292
|
+
state: "abandoned",
|
|
293
|
+
path: "done",
|
|
294
|
+
reason: "变更已放弃,流程终止。",
|
|
295
|
+
};
|
|
307
296
|
default:
|
|
308
297
|
return {
|
|
309
298
|
state: snapshot.state,
|
package/dist/record.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { RecordResult, Job } from "./types.ts";
|
|
2
2
|
/** record job-submit:登记工作项结果 */
|
|
3
3
|
export declare function recordJobSubmit(projectRoot: string, change: string, changeRoot: string, jobId: string, reportFile: string): RecordResult;
|
|
4
|
+
/** record job-submit:从 JSON 内容登记工作项结果 */
|
|
5
|
+
export declare function recordJobSubmitContent(projectRoot: string, change: string, changeRoot: string, jobId: string, reportContent: string): RecordResult;
|
|
4
6
|
/** record user-decision:登记用户决策 */
|
|
5
7
|
export declare function recordUserDecision(projectRoot: string, change: string, inputFile: string): RecordResult;
|
|
8
|
+
/** record user-decision:从 JSON 内容登记用户决策 */
|
|
9
|
+
export declare function recordUserDecisionContent(projectRoot: string, change: string, content: string): RecordResult;
|
|
6
10
|
/** jobs list(HIGH-1 修复:从 transition_commit.new_jobs 提取,不再依赖已删除的 job_requested 事件) */
|
|
7
11
|
export declare function jobsList(projectRoot: string, change: string): {
|
|
8
12
|
open: Job[];
|