@guanlin4924/devflow-pi 0.1.18 → 0.1.20
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 +20 -1
- package/package.json +3 -3
- package/plugins/devflow-core/extensions/devflow-core.ts +339 -20
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +394 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_suite.rb +86 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +192 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/api_test_matrix.py +829 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +7 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +11 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +5 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +39 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +309 -23
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tdd_evidence.py +311 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +53 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_task_binding_check.py +254 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +109 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/uat-remediation.yaml +21 -1
- package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +38 -0
- package/plugins/devflow-implementation/agents/implementation-worker.md +3 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +7 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +2 -0
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +3 -3
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +63 -0
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/agents/openai.yaml +4 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +7 -6
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +3 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +49 -3
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +48 -0
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/agents/openai.yaml +4 -0
- package/plugins/devflow-quality/skills/workflow-verify-work/SKILL.md +9 -3
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ devflow-pi
|
|
|
34
34
|
/usage-stats
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`/devflow project-init`
|
|
37
|
+
`/devflow project-init` 会先读取当前项目已有的 `.planning/PROJECT.md` 与 `.planning/navigation/INDEX.md`:仅回填仍存在于项目根目录内的已登记前后端路径,删除或越界的历史路径会被忽略。随后可选择复用已有边界、复用边界并重建索引,或手动覆盖。初始化模式使用选择器提供“只读规划(默认)”和“写入初始化”;选择后者并确认,只会创建 Devflow 的 `.planning/` 与 `.context/` 初始化产物,不会修改业务源码、依赖或项目配置。
|
|
38
38
|
|
|
39
39
|
用量统计默认只读取当前项目的隔离会话:`/usage-stats`。需要明确查看历史 OMP 会话时,使用 `/usage-stats omp`,或加筛选条件:`/usage-stats omp --from 2026-08-01 --to 2026-08-10`。历史统计保留真实会话路径,不再把不同项目的长会话文件合并成 `main`;统计只使用 assistant message 的原始 usage,不虚构工具调用成本分摊。
|
|
40
40
|
|
|
@@ -119,6 +119,25 @@ bash .skills/install.sh --apply --force-config
|
|
|
119
119
|
|
|
120
120
|
`/devflow start` 会先进入安全的需求梳理:可澄清并只读检查,不写入项目。用户随后直接补充需求,原文会被带入入口分诊并启动当前步骤。每个步骤结束后,系统展示产物和证据;用户明确回复“批准执行”才会推进并运行下一步,回复修改意见或“拒绝执行”则保持原步骤。普通需求描述绝不会自动确认 checkpoint。`/devflow explain-state` 发现控制面和制品 registry 漂移时会给出只读恢复计划;用户审查后可用 `/devflow recover` 确认仅修复可由文件系统证明的 registry 字段,它绝不推进步骤或自动通过 checkpoint。恢复已有流程使用 `/devflow resume`,它同样只恢复状态而不自动执行。
|
|
121
121
|
|
|
122
|
+
### 需求验收测试与 TDD 证据链
|
|
123
|
+
|
|
124
|
+
新建功能在 `integration-verify` 后会先生成独立的 `TC-*` 验收测试设计,再生成变更清单:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
已确认需求/契约/架构事实 → TC-* 候选 → 变更清单(任务 ↔ 候选 TC 绑定)
|
|
128
|
+
↓
|
|
129
|
+
CP-3:校验候选绑定 → 冻结 TC → 写入 Core attestation
|
|
130
|
+
↓
|
|
131
|
+
RED / GREEN / REGRESSION 证据 → 按冻结 TC 执行 API / 页面 / 专项测试 → UAT → CP-4
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- `TC-*` 的步骤和 oracle 在代码出现前冻结;CP-3 在同一次确认中先校验候选绑定,再冻结 TC 并固定绑定哈希。需求或契约变化会使它 stale,不能继续以旧测试集宣称通过。
|
|
135
|
+
- `tdd_required` 任务必须先由 Core 记录准备快照,再记录 RED(命中预期失败)、同一目标的 GREEN 和回归;缺失或环境失败不算完成。
|
|
136
|
+
- API、跨数据域、边界、迁移、工作负载与架构风险会派生相应的 `api`、`permission`、`boundary`、`migration`、`performance`、`resilience` TC 义务。
|
|
137
|
+
- 代码完成后,`workflow-run-acceptance-tests` 只能运行绑定到冻结 TC、且已在初始化 `.planning/toolchain.md` 发现的测试运行器命令。任何失败、未执行、人工待执行或环境阻塞用例都会阻止 CP-4。
|
|
138
|
+
|
|
139
|
+
正常使用不需要记忆底层命令:在 Devflow 工作流中按步骤继续即可。需要排查时,可用 `devflow_acceptance_tests` 查看冻结套件,或由工作流调用 `devflow_tdd_evidence` 与 `devflow_run_acceptance_tests` 生成 Core-owned 证据;不要手写“已测试”来替代结果文件。
|
|
140
|
+
|
|
122
141
|
## Marketplace 能力包
|
|
123
142
|
|
|
124
143
|
仓库也可作为 OMP marketplace 使用,但 Marketplace 安装只用于发现和安装 Skills、Agents、命令等能力内容,不代表完整 Devflow Runtime 已启用。OMP 不会从 Marketplace 安装加载 `package.json` 中的 `omp.extensions`,因此下面的命令不能替代 `bash .skills/install.sh --apply`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanlin4924/devflow-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dev": "bun run src/cli.ts",
|
|
12
12
|
"doctor": "bun run src/cli.ts doctor",
|
|
13
13
|
"test:devflow-pi": "bun test tests/devflow-pi",
|
|
14
|
-
"test": "bun test tests/devflow-pi &&
|
|
15
|
-
"check": "bun test tests/devflow-pi &&
|
|
14
|
+
"test": "bun test tests/devflow-pi && python3 -m unittest discover -s tests -p 'test_*.py'",
|
|
15
|
+
"check": "bun test tests/devflow-pi && python3 -m unittest discover -s tests -p 'test_*.py'"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
18
|
"bun": ">=1.3.14"
|
|
@@ -52,6 +52,7 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
52
52
|
"devflow_doctor",
|
|
53
53
|
"devflow_code_index",
|
|
54
54
|
"devflow_context_cursor",
|
|
55
|
+
"devflow_acceptance_tests",
|
|
55
56
|
"devflow_usage_stats",
|
|
56
57
|
"usage_stats",
|
|
57
58
|
]);
|
|
@@ -68,7 +69,10 @@ const STATE_MUTATION_TOOLS = new Set([
|
|
|
68
69
|
"devflow_bootstrap",
|
|
69
70
|
"devflow_transition",
|
|
70
71
|
"devflow_artifact",
|
|
72
|
+
"devflow_api_test_matrix",
|
|
71
73
|
"devflow_evidence",
|
|
74
|
+
"devflow_tdd_evidence",
|
|
75
|
+
"devflow_run_acceptance_tests",
|
|
72
76
|
]);
|
|
73
77
|
|
|
74
78
|
export function initialControlState(): SessionControlState {
|
|
@@ -98,11 +102,116 @@ export function planningState(intent: "project-init" | "requirement-draft"): Ses
|
|
|
98
102
|
};
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
export type ExistingProjectInitialization = {
|
|
106
|
+
projectName?: string;
|
|
107
|
+
frontendPaths: string[];
|
|
108
|
+
backendPaths: string[];
|
|
109
|
+
codeIndexStatus?: string;
|
|
110
|
+
sourcePath: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type DocumentedProjectLocations = {
|
|
114
|
+
frontendPaths: string[];
|
|
115
|
+
backendPaths: string[];
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function markdownTableCells(line: string): string[] | null {
|
|
119
|
+
if (!line.trim().startsWith("|")) return null;
|
|
120
|
+
const cells = line.trim().split("|").slice(1, -1).map(cell => cell.trim());
|
|
121
|
+
return cells.length > 0 ? cells : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function documentedProjectPath(raw: string, cwd: string): string | null {
|
|
125
|
+
const cleaned = raw
|
|
126
|
+
.replaceAll("`", "")
|
|
127
|
+
.replaceAll("~", "")
|
|
128
|
+
.replaceAll("*", "")
|
|
129
|
+
.replace(/\s*[((].*$/, "")
|
|
130
|
+
.trim();
|
|
131
|
+
if (!cleaned) return null;
|
|
132
|
+
const absolute = path.resolve(cwd, cleaned);
|
|
133
|
+
const relative = path.relative(cwd, absolute).replaceAll("\\", "/");
|
|
134
|
+
if (!relative || relative === ".." || relative.startsWith("../") || path.isAbsolute(relative)) return null;
|
|
135
|
+
if (!existsSync(absolute)) return null;
|
|
136
|
+
return relative;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function documentedLocations(markdown: string, cwd: string): DocumentedProjectLocations {
|
|
140
|
+
const frontendPaths: string[] = [];
|
|
141
|
+
const backendPaths: string[] = [];
|
|
142
|
+
const lines = markdown.split(/\r?\n/);
|
|
143
|
+
for (let index = 0; index < lines.length - 2; index += 1) {
|
|
144
|
+
const header = markdownTableCells(lines[index]);
|
|
145
|
+
const divider = markdownTableCells(lines[index + 1]);
|
|
146
|
+
if (!header || !divider || !divider.every(cell => /^:?-{3,}:?$/.test(cell))) continue;
|
|
147
|
+
const pathColumn = header.findIndex(cell => /^(?:path|路径)$/i.test(cell));
|
|
148
|
+
const typeColumn = header.findIndex(cell => /^(?:type|类型)$/i.test(cell));
|
|
149
|
+
if (pathColumn < 0 || typeColumn < 0) continue;
|
|
150
|
+
for (let rowIndex = index + 2; rowIndex < lines.length; rowIndex += 1) {
|
|
151
|
+
const row = markdownTableCells(lines[rowIndex]);
|
|
152
|
+
if (!row || row.length !== header.length) break;
|
|
153
|
+
const projectPath = documentedProjectPath(row[pathColumn], cwd);
|
|
154
|
+
if (!projectPath) continue;
|
|
155
|
+
const type = row[typeColumn];
|
|
156
|
+
if (/(?:前端|frontend|\bh5\b|小程序|uniapp)/i.test(type)) frontendPaths.push(projectPath);
|
|
157
|
+
if (/(?:后端|backend|服务|\bapi\b)/i.test(type)) backendPaths.push(projectPath);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
frontendPaths: [...new Set(frontendPaths)],
|
|
162
|
+
backendPaths: [...new Set(backendPaths)],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function documentedProjectName(markdown: string): string | undefined {
|
|
167
|
+
const match = markdown.match(/^\s*-\s*\*\*Name\*\*:\s*(.+?)\s*$/mi);
|
|
168
|
+
return match?.[1]?.trim() || undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function documentedCodeIndexStatus(markdown: string): string | undefined {
|
|
172
|
+
const comment = markdown.match(/code_index_status:\s*([^;\-\n]+)/i);
|
|
173
|
+
if (comment?.[1]?.trim()) return comment[1].trim();
|
|
174
|
+
const field = markdown.match(/^\s*-\s*\*\*Code Index Status\*\*:\s*([^\n]+)/mi);
|
|
175
|
+
return field?.[1]?.trim() || undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Reuses only existing, project-local initialization records. Markdown is
|
|
180
|
+
* evidence, not authority by itself: every documented path must still exist
|
|
181
|
+
* beneath the current project root before it can be offered back to the user.
|
|
182
|
+
*/
|
|
183
|
+
export function discoverExistingProjectInitialization(cwd: string): ExistingProjectInitialization | null {
|
|
184
|
+
const sources = [".planning/PROJECT.md", ".planning/navigation/INDEX.md"];
|
|
185
|
+
const records: Array<ExistingProjectInitialization> = [];
|
|
186
|
+
for (const sourcePath of sources) {
|
|
187
|
+
const absolute = path.join(cwd, sourcePath);
|
|
188
|
+
if (!existsSync(absolute)) continue;
|
|
189
|
+
const markdown = readFileSync(absolute, "utf8");
|
|
190
|
+
const locations = documentedLocations(markdown, cwd);
|
|
191
|
+
if (locations.frontendPaths.length === 0 && locations.backendPaths.length === 0) continue;
|
|
192
|
+
records.push({
|
|
193
|
+
projectName: documentedProjectName(markdown),
|
|
194
|
+
frontendPaths: locations.frontendPaths,
|
|
195
|
+
backendPaths: locations.backendPaths,
|
|
196
|
+
codeIndexStatus: documentedCodeIndexStatus(markdown),
|
|
197
|
+
sourcePath,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const preferred = records[0];
|
|
201
|
+
if (!preferred) return null;
|
|
202
|
+
const fallback = records.find(record => record !== preferred);
|
|
203
|
+
const useFallbackFrontend = preferred.frontendPaths.length === 0 && Boolean(fallback?.frontendPaths.length);
|
|
204
|
+
const useFallbackBackend = preferred.backendPaths.length === 0 && Boolean(fallback?.backendPaths.length);
|
|
205
|
+
return {
|
|
206
|
+
projectName: preferred.projectName ?? fallback?.projectName,
|
|
207
|
+
frontendPaths: preferred.frontendPaths.length > 0 ? preferred.frontendPaths : (fallback?.frontendPaths ?? []),
|
|
208
|
+
backendPaths: preferred.backendPaths.length > 0 ? preferred.backendPaths : (fallback?.backendPaths ?? []),
|
|
209
|
+
codeIndexStatus: preferred.codeIndexStatus ?? fallback?.codeIndexStatus,
|
|
210
|
+
sourcePath: [
|
|
211
|
+
preferred.sourcePath,
|
|
212
|
+
...((useFallbackFrontend || useFallbackBackend) && fallback ? [fallback.sourcePath] : []),
|
|
213
|
+
].join(", "),
|
|
214
|
+
};
|
|
106
215
|
}
|
|
107
216
|
|
|
108
217
|
export function executingState(label = "当前工作流步骤执行"): SessionControlState {
|
|
@@ -230,6 +339,10 @@ const REQUIRED_EXTENSION_TOOLS = [
|
|
|
230
339
|
"devflow_artifact",
|
|
231
340
|
"devflow_machine_evidence",
|
|
232
341
|
"devflow_requirement_graph",
|
|
342
|
+
"devflow_acceptance_tests",
|
|
343
|
+
"devflow_api_test_matrix",
|
|
344
|
+
"devflow_tdd_evidence",
|
|
345
|
+
"devflow_run_acceptance_tests",
|
|
233
346
|
"devflow_evidence",
|
|
234
347
|
"devflow_contract",
|
|
235
348
|
"devflow_recovery",
|
|
@@ -248,6 +361,7 @@ const REQUIRED_SKILLS = [
|
|
|
248
361
|
"workflow-api-call-guide",
|
|
249
362
|
"workflow-api-contract",
|
|
250
363
|
"workflow-api-supplement",
|
|
364
|
+
"workflow-acceptance-test-design",
|
|
251
365
|
"workflow-audit-milestone",
|
|
252
366
|
"workflow-backend-tech-spec",
|
|
253
367
|
"workflow-bug-fix",
|
|
@@ -266,6 +380,7 @@ const REQUIRED_SKILLS = [
|
|
|
266
380
|
"workflow-orchestrator",
|
|
267
381
|
"workflow-pipeline",
|
|
268
382
|
"workflow-project-context",
|
|
383
|
+
"workflow-run-acceptance-tests",
|
|
269
384
|
"workflow-requirement-baseline",
|
|
270
385
|
"workflow-requirement-triage",
|
|
271
386
|
"workflow-ui-spec",
|
|
@@ -303,6 +418,12 @@ const REQUIRED_SCRIPTS = [
|
|
|
303
418
|
"workflow-orchestrator/scripts/advance_state.rb",
|
|
304
419
|
"workflow-orchestrator/scripts/sync_artifacts.rb",
|
|
305
420
|
"workflow-orchestrator/scripts/requirement_graph.rb",
|
|
421
|
+
"workflow-orchestrator/scripts/acceptance_test_suite.rb",
|
|
422
|
+
"workflow-orchestrator/scripts/api_test_matrix.py",
|
|
423
|
+
"workflow-orchestrator/scripts/test_task_binding_check.py",
|
|
424
|
+
"workflow-orchestrator/scripts/test_command_policy.py",
|
|
425
|
+
"workflow-orchestrator/scripts/tdd_evidence.py",
|
|
426
|
+
"workflow-orchestrator/scripts/acceptance_test_runner.py",
|
|
306
427
|
"workflow-orchestrator/scripts/machine_evidence.rb",
|
|
307
428
|
"workflow-orchestrator/scripts/execution_contract.rb",
|
|
308
429
|
"workflow-code-index/scripts/code_index.py",
|
|
@@ -410,7 +531,7 @@ function safeControlPath(cwd: string, raw: string | undefined, kind: "state" | "
|
|
|
410
531
|
}
|
|
411
532
|
|
|
412
533
|
function isCanonicalRequirementGraphPath(filePath: string): boolean {
|
|
413
|
-
return /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:governance|evidence|definitions|behavior-graph|requirement-graph|slices|acceptance|traceability|graph\.meta)\.json$/u.test(filePath)
|
|
534
|
+
return /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:governance|evidence|definitions|behavior-graph|requirement-graph|slices|acceptance|tests|traceability|graph\.meta)\.json$/u.test(filePath)
|
|
414
535
|
|| /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:context\/[^/\\]+\.json|control-events\.jsonl)$/u.test(filePath);
|
|
415
536
|
}
|
|
416
537
|
|
|
@@ -740,32 +861,106 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
740
861
|
}
|
|
741
862
|
|
|
742
863
|
async function collectProjectInitialization(ctx: {
|
|
864
|
+
cwd: string;
|
|
743
865
|
ui: {
|
|
866
|
+
select(title: string, options: Array<{ label: string; description?: string }>): Promise<string | undefined>;
|
|
744
867
|
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
745
868
|
confirm(title: string, message: string): Promise<boolean>;
|
|
746
869
|
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
747
870
|
};
|
|
748
871
|
}): Promise<ProjectInitializationRequest | null> {
|
|
749
|
-
const
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
872
|
+
const existing = discoverExistingProjectInitialization(ctx.cwd);
|
|
873
|
+
const reuseExisting = "使用已记录的项目结构(推荐)";
|
|
874
|
+
const rebuildExisting = "使用已记录的项目结构,并重新扫描代码";
|
|
875
|
+
const manualEntry = "我需要手动指定项目目录";
|
|
876
|
+
|
|
877
|
+
let projectName: string | undefined;
|
|
878
|
+
let frontendPath: string | undefined;
|
|
879
|
+
let backendPath: string | undefined;
|
|
880
|
+
let buildIndex: string | undefined;
|
|
881
|
+
let boundarySource: string | undefined;
|
|
882
|
+
|
|
883
|
+
if (existing) {
|
|
884
|
+
const frontend = existing.frontendPaths.length > 0 ? existing.frontendPaths.join(", ") : "未记录(unknown)";
|
|
885
|
+
const backend = existing.backendPaths.length > 0 ? existing.backendPaths.join(", ") : "未记录(unknown)";
|
|
886
|
+
const selected = await ctx.ui.select("检测到已有项目记录:这次初始化怎么处理?", [
|
|
887
|
+
{
|
|
888
|
+
label: reuseExisting,
|
|
889
|
+
description: `直接复用下面已核验、仍存在的目录,不重新扫描代码。\n前端:${frontend}\n后端:${backend}\n记录来源:${existing.sourcePath}${existing.codeIndexStatus ? `;索引状态:${existing.codeIndexStatus}` : ""}`,
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
label: rebuildExisting,
|
|
893
|
+
description: `沿用已登记目录,但重新扫描代码并更新索引。\n前端:${frontend}\n后端:${backend}`,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
label: manualEntry,
|
|
897
|
+
description: "不采用上面的记录。我会依次询问项目名、前端目录和后端目录;目录均相对当前项目根目录。",
|
|
898
|
+
},
|
|
899
|
+
]);
|
|
900
|
+
if (!selected) return null;
|
|
901
|
+
if (selected === reuseExisting || selected === rebuildExisting) {
|
|
902
|
+
projectName = existing.projectName;
|
|
903
|
+
frontendPath = existing.frontendPaths.join(", ") || "unknown";
|
|
904
|
+
backendPath = existing.backendPaths.join(", ") || "unknown";
|
|
905
|
+
buildIndex = selected === reuseExisting
|
|
906
|
+
? `复用已有索引${existing.codeIndexStatus ? `(${existing.codeIndexStatus})` : ""}`
|
|
907
|
+
: "yes";
|
|
908
|
+
boundarySource = `${existing.sourcePath}(已校验目录仍存在)`;
|
|
909
|
+
}
|
|
762
910
|
}
|
|
911
|
+
|
|
912
|
+
if (!frontendPath || !backendPath || !buildIndex) {
|
|
913
|
+
projectName = await ctx.ui.input(
|
|
914
|
+
"项目显示名称(可留空)",
|
|
915
|
+
"用于 Devflow 文档标题;留空时使用当前目录名。例如:商城运营平台",
|
|
916
|
+
);
|
|
917
|
+
if (projectName === undefined) return null;
|
|
918
|
+
frontendPath = await ctx.ui.input(
|
|
919
|
+
"前端代码目录(相对当前项目根目录)",
|
|
920
|
+
"可填写多个目录,用英文逗号分隔。例:apps/admin, apps/h5;不确定请填 unknown",
|
|
921
|
+
);
|
|
922
|
+
if (frontendPath === undefined) return null;
|
|
923
|
+
backendPath = await ctx.ui.input(
|
|
924
|
+
"后端代码目录(相对当前项目根目录)",
|
|
925
|
+
"例:services/api 或 server;项目没有后端请填 none;不确定请填 unknown",
|
|
926
|
+
);
|
|
927
|
+
if (backendPath === undefined) return null;
|
|
928
|
+
const fullIndex = "建立完整代码索引(推荐)";
|
|
929
|
+
const skipIndex = "暂不建立代码索引";
|
|
930
|
+
const selectedIndex = await ctx.ui.select("是否扫描代码并建立索引?", [
|
|
931
|
+
{
|
|
932
|
+
label: fullIndex,
|
|
933
|
+
description: "扫描已确认的代码目录,建立调用关系和符号索引;后续需求与架构评估可直接复用。",
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
label: skipIndex,
|
|
937
|
+
description: "只建立项目说明与规则入口;后续需要源码级分析时再补建索引。",
|
|
938
|
+
},
|
|
939
|
+
]);
|
|
940
|
+
if (!selectedIndex) return null;
|
|
941
|
+
buildIndex = selectedIndex === fullIndex ? "yes" : "no";
|
|
942
|
+
}
|
|
943
|
+
const planMode = "只读规划(不写任何项目文件)";
|
|
944
|
+
const applyMode = "写入初始化";
|
|
945
|
+
const selectedMode = await ctx.ui.select("确认这次初始化是否写入项目文件?", [
|
|
946
|
+
{
|
|
947
|
+
label: planMode,
|
|
948
|
+
description: "只读取项目和已有初始化记录,输出规划与待确认项;不写入任何项目文件。",
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
label: applyMode,
|
|
952
|
+
description: "确认范围后创建或更新 Devflow 初始化产物;不修改业务源码、依赖或项目配置。",
|
|
953
|
+
},
|
|
954
|
+
]);
|
|
955
|
+
if (!selectedMode) return null;
|
|
956
|
+
const mode: ProjectInitializationMode = selectedMode === applyMode ? "apply" : "plan";
|
|
763
957
|
const modeSummary = mode === "apply" ? "写入初始化" : "只读规划";
|
|
764
958
|
const summary = [
|
|
765
959
|
`项目名:${projectName?.trim() || "当前目录"}`,
|
|
766
960
|
`前端:${frontendPath.trim() || "未提供"}`,
|
|
767
961
|
`后端:${backendPath.trim() || "未提供"}`,
|
|
768
962
|
`建立代码索引:${buildIndex.trim() || "未提供"}`,
|
|
963
|
+
...(boundarySource ? [`边界来源:${boundarySource}`] : []),
|
|
769
964
|
`初始化方式:${modeSummary}`,
|
|
770
965
|
].join("\n");
|
|
771
966
|
const consequence = mode === "apply"
|
|
@@ -788,6 +983,15 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
788
983
|
});
|
|
789
984
|
}
|
|
790
985
|
|
|
986
|
+
async function runPython(cwd: string, relativeScript: string, args: string[]) {
|
|
987
|
+
assertRuntimeProjectBoundary(cwd);
|
|
988
|
+
const kitRoot = resolveKitRoot(cwd);
|
|
989
|
+
return runProcess(["python3", path.join(kitRoot, relativeScript), ...args], cwd, {
|
|
990
|
+
DEVFLOW_KIT_ROOT: kitRoot,
|
|
991
|
+
OMP_PROJECT_DIR: cwd,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
791
995
|
async function runHook(
|
|
792
996
|
cwd: string,
|
|
793
997
|
scriptName: string,
|
|
@@ -1341,7 +1545,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1341
1545
|
read: z.boolean().optional(),
|
|
1342
1546
|
delta: z.object({
|
|
1343
1547
|
op: z.enum(["add", "link", "update"]),
|
|
1344
|
-
kind: z.enum(["source", "code", "term", "object", "behavior", "requirement", "rule", "boundary", "slice", "risk", "decision", "acceptance", "api", "workload_shape", "workload_constraint"]).optional(),
|
|
1548
|
+
kind: z.enum(["source", "code", "term", "object", "behavior", "requirement", "rule", "boundary", "slice", "risk", "decision", "acceptance", "api", "workload_shape", "workload_constraint", "test_case"]).optional(),
|
|
1345
1549
|
id: z.string().min(1).optional(),
|
|
1346
1550
|
data: z.record(z.string(), z.unknown()).optional(),
|
|
1347
1551
|
from: z.string().min(1).optional(),
|
|
@@ -1363,6 +1567,14 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1363
1567
|
if (params.expectedRevision === undefined || !params.delta) {
|
|
1364
1568
|
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "propose requires expectedRevision and delta" });
|
|
1365
1569
|
}
|
|
1570
|
+
if (params.delta.kind === "test_case" && activeContract?.step !== "acceptance-test-design") {
|
|
1571
|
+
return renderResult({
|
|
1572
|
+
ok: false,
|
|
1573
|
+
exitCode: 1,
|
|
1574
|
+
stdout: "",
|
|
1575
|
+
stderr: "TC candidates are accepted only during the active acceptance-test-design contract.",
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1366
1578
|
args.push("--write", "--expected-revision", String(params.expectedRevision), "--delta-json", JSON.stringify(params.delta));
|
|
1367
1579
|
if (activeContract?.allowedGraphDelta?.length) args.push("--allowed-kinds", activeContract.allowedGraphDelta.join(","));
|
|
1368
1580
|
}
|
|
@@ -1409,6 +1621,113 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1409
1621
|
},
|
|
1410
1622
|
});
|
|
1411
1623
|
|
|
1624
|
+
pi.registerTool({
|
|
1625
|
+
name: "devflow_acceptance_tests",
|
|
1626
|
+
label: "Devflow Acceptance Tests",
|
|
1627
|
+
description: "Inspect the Core-owned frozen TC acceptance suite. CP-3, not an agent, freezes the suite.",
|
|
1628
|
+
parameters: z.object({
|
|
1629
|
+
feature: z.string().min(1),
|
|
1630
|
+
}),
|
|
1631
|
+
approval: "read",
|
|
1632
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1633
|
+
return renderResult(
|
|
1634
|
+
await runRuby(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_suite.rb", ["inspect", "--feature", params.feature, "--json"]),
|
|
1635
|
+
{ feature: params.feature },
|
|
1636
|
+
);
|
|
1637
|
+
},
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
pi.registerTool({
|
|
1641
|
+
name: "devflow_api_test_matrix",
|
|
1642
|
+
label: "Devflow API Test Matrix",
|
|
1643
|
+
description: "Compile complete API field-boundary, pairwise and error-code TC candidates; import them only during acceptance-test design.",
|
|
1644
|
+
parameters: z.object({
|
|
1645
|
+
action: z.enum(["compile", "import", "validate"]),
|
|
1646
|
+
feature: z.string().min(1),
|
|
1647
|
+
}),
|
|
1648
|
+
approval: args => args.action === "validate" ? "read" : "write",
|
|
1649
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1650
|
+
if (params.action !== "validate" && activeContract?.step !== "acceptance-test-design") {
|
|
1651
|
+
return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "API test matrix compilation/import is allowed only during the active acceptance-test-design contract." });
|
|
1652
|
+
}
|
|
1653
|
+
if (params.action === "validate") {
|
|
1654
|
+
const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["validate", "--feature", params.feature, "--json"]);
|
|
1655
|
+
const payload = parseJsonResult(process);
|
|
1656
|
+
const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
|
|
1657
|
+
return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, { action: params.action, feature: params.feature });
|
|
1658
|
+
}
|
|
1659
|
+
const compiled = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["compile", "--feature", params.feature, "--write", "--json"]);
|
|
1660
|
+
const compiledPayload = parseJsonResult(compiled);
|
|
1661
|
+
if (!compiled.ok || !compiledPayload || typeof compiledPayload !== "object" || (compiledPayload as { ok?: unknown }).ok !== true) {
|
|
1662
|
+
return renderResult({ ...compiled, ok: false, exitCode: compiled.exitCode || 1 }, { action: params.action, feature: params.feature });
|
|
1663
|
+
}
|
|
1664
|
+
if (params.action === "compile") return renderResult(compiled, { action: params.action, feature: params.feature });
|
|
1665
|
+
|
|
1666
|
+
const inspected = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["inspect", "--feature", params.feature, "--json"]));
|
|
1667
|
+
const revision = inspected && typeof inspected === "object" && typeof (inspected as { graph?: { revision?: unknown } }).graph?.revision === "number"
|
|
1668
|
+
? (inspected as { graph: { revision: number } }).graph.revision
|
|
1669
|
+
: undefined;
|
|
1670
|
+
if (revision === undefined) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "Could not read the current requirement-graph revision before importing API TC candidates." });
|
|
1671
|
+
const imported = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["import-test-cases", "--feature", params.feature, "--write", "--expected-revision", String(revision), "--json"]);
|
|
1672
|
+
const importedPayload = parseJsonResult(imported);
|
|
1673
|
+
const failed = importedPayload && typeof importedPayload === "object" && (importedPayload as { ok?: unknown }).ok === false;
|
|
1674
|
+
return renderResult(failed ? { ...imported, ok: false, exitCode: imported.exitCode || 1 } : imported, { action: params.action, feature: params.feature });
|
|
1675
|
+
},
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
pi.registerTool({
|
|
1679
|
+
name: "devflow_tdd_evidence",
|
|
1680
|
+
label: "Devflow TDD Evidence",
|
|
1681
|
+
description: "Core-owned preparation and RED/GREEN/REGRESSION evidence for a frozen tdd_required task binding.",
|
|
1682
|
+
parameters: z.object({
|
|
1683
|
+
action: z.enum(["prepare", "record", "inspect"]),
|
|
1684
|
+
feature: z.string().min(1),
|
|
1685
|
+
taskId: z.string().min(1),
|
|
1686
|
+
stage: z.enum(["red", "green", "regression"]).optional(),
|
|
1687
|
+
}),
|
|
1688
|
+
approval: args => args.action === "inspect" ? "read" : "write",
|
|
1689
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1690
|
+
if (params.action === "record" && !params.stage) {
|
|
1691
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "record requires stage" });
|
|
1692
|
+
}
|
|
1693
|
+
const args = [params.action, "--feature", params.feature, "--task-id", params.taskId, "--json"];
|
|
1694
|
+
if (params.action !== "inspect") args.push("--write");
|
|
1695
|
+
if (params.stage) args.push("--stage", params.stage);
|
|
1696
|
+
const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/tdd_evidence.py", args);
|
|
1697
|
+
const payload = parseJsonResult(process);
|
|
1698
|
+
const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
|
|
1699
|
+
return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, {
|
|
1700
|
+
action: params.action,
|
|
1701
|
+
feature: params.feature,
|
|
1702
|
+
taskId: params.taskId,
|
|
1703
|
+
stage: params.stage,
|
|
1704
|
+
});
|
|
1705
|
+
},
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
pi.registerTool({
|
|
1709
|
+
name: "devflow_run_acceptance_tests",
|
|
1710
|
+
label: "Devflow Run Acceptance Tests",
|
|
1711
|
+
description: "Validate or execute only command or portable HTTP adapters bound to the frozen TC acceptance suite.",
|
|
1712
|
+
parameters: z.object({
|
|
1713
|
+
action: z.enum(["validate", "run", "validate_results"]),
|
|
1714
|
+
feature: z.string().min(1),
|
|
1715
|
+
}),
|
|
1716
|
+
approval: args => args.action === "run" ? "write" : "read",
|
|
1717
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1718
|
+
const scriptAction = params.action === "validate_results" ? "validate-results" : params.action;
|
|
1719
|
+
const args = [scriptAction, "--feature", params.feature, "--json"];
|
|
1720
|
+
if (params.action === "run") args.push("--write");
|
|
1721
|
+
const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_runner.py", args);
|
|
1722
|
+
const payload = parseJsonResult(process);
|
|
1723
|
+
const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
|
|
1724
|
+
return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, {
|
|
1725
|
+
action: params.action,
|
|
1726
|
+
feature: params.feature,
|
|
1727
|
+
});
|
|
1728
|
+
},
|
|
1729
|
+
});
|
|
1730
|
+
|
|
1412
1731
|
pi.registerTool({
|
|
1413
1732
|
name: "devflow_runtime_context",
|
|
1414
1733
|
label: "Devflow Runtime Context",
|