@guanlin4924/devflow-pi 0.1.19 → 0.1.21

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.
Files changed (36) hide show
  1. package/README.md +19 -0
  2. package/package.json +3 -3
  3. package/plugins/devflow-core/extensions/devflow-core.ts +152 -3
  4. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +394 -0
  5. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_suite.rb +86 -0
  6. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +192 -1
  7. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/api_test_matrix.py +829 -0
  8. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +7 -5
  9. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +11 -1
  10. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +5 -1
  11. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +39 -3
  12. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +309 -23
  13. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tdd_evidence.py +311 -0
  14. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +53 -0
  15. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_task_binding_check.py +254 -0
  16. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +109 -4
  17. package/plugins/devflow-core/skills/workflow-orchestrator/templates/uat-remediation.yaml +21 -1
  18. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +17 -0
  19. package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +38 -0
  20. package/plugins/devflow-implementation/agents/implementation-worker.md +3 -0
  21. package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +7 -0
  22. package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +2 -0
  23. package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +3 -3
  24. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +63 -0
  25. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/agents/openai.yaml +4 -0
  26. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +7 -6
  27. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +3 -0
  28. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +49 -3
  29. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +48 -0
  30. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/agents/openai.yaml +4 -0
  31. package/plugins/devflow-quality/skills/workflow-verify-work/SKILL.md +9 -3
  32. package/src/constants.ts +1 -1
  33. package/src/doctor.ts +6 -2
  34. package/src/project-context.ts +11 -1
  35. package/src/runtime.ts +7 -0
  36. package/src/usage-stats.ts +3 -3
package/README.md CHANGED
@@ -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.19",
3
+ "version": "0.1.21",
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 && python -m pytest",
15
- "check": "bun test tests/devflow-pi && python -m pytest"
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 {
@@ -335,6 +339,10 @@ const REQUIRED_EXTENSION_TOOLS = [
335
339
  "devflow_artifact",
336
340
  "devflow_machine_evidence",
337
341
  "devflow_requirement_graph",
342
+ "devflow_acceptance_tests",
343
+ "devflow_api_test_matrix",
344
+ "devflow_tdd_evidence",
345
+ "devflow_run_acceptance_tests",
338
346
  "devflow_evidence",
339
347
  "devflow_contract",
340
348
  "devflow_recovery",
@@ -353,6 +361,7 @@ const REQUIRED_SKILLS = [
353
361
  "workflow-api-call-guide",
354
362
  "workflow-api-contract",
355
363
  "workflow-api-supplement",
364
+ "workflow-acceptance-test-design",
356
365
  "workflow-audit-milestone",
357
366
  "workflow-backend-tech-spec",
358
367
  "workflow-bug-fix",
@@ -371,6 +380,7 @@ const REQUIRED_SKILLS = [
371
380
  "workflow-orchestrator",
372
381
  "workflow-pipeline",
373
382
  "workflow-project-context",
383
+ "workflow-run-acceptance-tests",
374
384
  "workflow-requirement-baseline",
375
385
  "workflow-requirement-triage",
376
386
  "workflow-ui-spec",
@@ -408,6 +418,12 @@ const REQUIRED_SCRIPTS = [
408
418
  "workflow-orchestrator/scripts/advance_state.rb",
409
419
  "workflow-orchestrator/scripts/sync_artifacts.rb",
410
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",
411
427
  "workflow-orchestrator/scripts/machine_evidence.rb",
412
428
  "workflow-orchestrator/scripts/execution_contract.rb",
413
429
  "workflow-code-index/scripts/code_index.py",
@@ -496,12 +512,21 @@ function assertRuntimeProjectBoundary(cwd: string): void {
496
512
  if (!canonical.startsWith(`${resolvedCwd}${path.sep}`)) {
497
513
  throw new Error(`Devflow control path escapes project boundary: ${canonical}`);
498
514
  }
515
+ if (lstatSync(target).isDirectory()) assertNoSymlinkDescendantsSync(target);
499
516
  } catch (error) {
500
517
  if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
501
518
  }
502
519
  }
503
520
  }
504
521
 
522
+ function assertNoSymlinkDescendantsSync(target: string): void {
523
+ for (const entry of readdirSync(target, { withFileTypes: true })) {
524
+ const child = path.join(target, entry.name);
525
+ if (entry.isSymbolicLink()) throw new Error(`Refusing symlinked Devflow control path: ${child}`);
526
+ if (entry.isDirectory()) assertNoSymlinkDescendantsSync(child);
527
+ }
528
+ }
529
+
505
530
  function safeControlPath(cwd: string, raw: string | undefined, kind: "state" | "workflow"): string | undefined {
506
531
  if (raw === undefined) return undefined;
507
532
  if (path.isAbsolute(raw)) throw new Error(`${kind} path must be project-relative`);
@@ -515,7 +540,7 @@ function safeControlPath(cwd: string, raw: string | undefined, kind: "state" | "
515
540
  }
516
541
 
517
542
  function isCanonicalRequirementGraphPath(filePath: string): boolean {
518
- return /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:governance|evidence|definitions|behavior-graph|requirement-graph|slices|acceptance|traceability|graph\.meta)\.json$/u.test(filePath)
543
+ return /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:governance|evidence|definitions|behavior-graph|requirement-graph|slices|acceptance|tests|traceability|graph\.meta)\.json$/u.test(filePath)
519
544
  || /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:context\/[^/\\]+\.json|control-events\.jsonl)$/u.test(filePath);
520
545
  }
521
546
 
@@ -967,6 +992,15 @@ export default function devflowCore(pi: ExtensionAPI) {
967
992
  });
968
993
  }
969
994
 
995
+ async function runPython(cwd: string, relativeScript: string, args: string[]) {
996
+ assertRuntimeProjectBoundary(cwd);
997
+ const kitRoot = resolveKitRoot(cwd);
998
+ return runProcess(["python3", path.join(kitRoot, relativeScript), ...args], cwd, {
999
+ DEVFLOW_KIT_ROOT: kitRoot,
1000
+ OMP_PROJECT_DIR: cwd,
1001
+ });
1002
+ }
1003
+
970
1004
  async function runHook(
971
1005
  cwd: string,
972
1006
  scriptName: string,
@@ -1520,7 +1554,7 @@ export default function devflowCore(pi: ExtensionAPI) {
1520
1554
  read: z.boolean().optional(),
1521
1555
  delta: z.object({
1522
1556
  op: z.enum(["add", "link", "update"]),
1523
- kind: z.enum(["source", "code", "term", "object", "behavior", "requirement", "rule", "boundary", "slice", "risk", "decision", "acceptance", "api", "workload_shape", "workload_constraint"]).optional(),
1557
+ kind: z.enum(["source", "code", "term", "object", "behavior", "requirement", "rule", "boundary", "slice", "risk", "decision", "acceptance", "api", "workload_shape", "workload_constraint", "test_case"]).optional(),
1524
1558
  id: z.string().min(1).optional(),
1525
1559
  data: z.record(z.string(), z.unknown()).optional(),
1526
1560
  from: z.string().min(1).optional(),
@@ -1542,6 +1576,14 @@ export default function devflowCore(pi: ExtensionAPI) {
1542
1576
  if (params.expectedRevision === undefined || !params.delta) {
1543
1577
  return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "propose requires expectedRevision and delta" });
1544
1578
  }
1579
+ if (params.delta.kind === "test_case" && activeContract?.step !== "acceptance-test-design") {
1580
+ return renderResult({
1581
+ ok: false,
1582
+ exitCode: 1,
1583
+ stdout: "",
1584
+ stderr: "TC candidates are accepted only during the active acceptance-test-design contract.",
1585
+ });
1586
+ }
1545
1587
  args.push("--write", "--expected-revision", String(params.expectedRevision), "--delta-json", JSON.stringify(params.delta));
1546
1588
  if (activeContract?.allowedGraphDelta?.length) args.push("--allowed-kinds", activeContract.allowedGraphDelta.join(","));
1547
1589
  }
@@ -1588,6 +1630,113 @@ export default function devflowCore(pi: ExtensionAPI) {
1588
1630
  },
1589
1631
  });
1590
1632
 
1633
+ pi.registerTool({
1634
+ name: "devflow_acceptance_tests",
1635
+ label: "Devflow Acceptance Tests",
1636
+ description: "Inspect the Core-owned frozen TC acceptance suite. CP-3, not an agent, freezes the suite.",
1637
+ parameters: z.object({
1638
+ feature: z.string().min(1),
1639
+ }),
1640
+ approval: "read",
1641
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1642
+ return renderResult(
1643
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_suite.rb", ["inspect", "--feature", params.feature, "--json"]),
1644
+ { feature: params.feature },
1645
+ );
1646
+ },
1647
+ });
1648
+
1649
+ pi.registerTool({
1650
+ name: "devflow_api_test_matrix",
1651
+ label: "Devflow API Test Matrix",
1652
+ description: "Compile complete API field-boundary, pairwise and error-code TC candidates; import them only during acceptance-test design.",
1653
+ parameters: z.object({
1654
+ action: z.enum(["compile", "import", "validate"]),
1655
+ feature: z.string().min(1),
1656
+ }),
1657
+ approval: args => args.action === "validate" ? "read" : "write",
1658
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1659
+ if (params.action !== "validate" && activeContract?.step !== "acceptance-test-design") {
1660
+ return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "API test matrix compilation/import is allowed only during the active acceptance-test-design contract." });
1661
+ }
1662
+ if (params.action === "validate") {
1663
+ const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["validate", "--feature", params.feature, "--json"]);
1664
+ const payload = parseJsonResult(process);
1665
+ const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
1666
+ return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, { action: params.action, feature: params.feature });
1667
+ }
1668
+ const compiled = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["compile", "--feature", params.feature, "--write", "--json"]);
1669
+ const compiledPayload = parseJsonResult(compiled);
1670
+ if (!compiled.ok || !compiledPayload || typeof compiledPayload !== "object" || (compiledPayload as { ok?: unknown }).ok !== true) {
1671
+ return renderResult({ ...compiled, ok: false, exitCode: compiled.exitCode || 1 }, { action: params.action, feature: params.feature });
1672
+ }
1673
+ if (params.action === "compile") return renderResult(compiled, { action: params.action, feature: params.feature });
1674
+
1675
+ const inspected = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["inspect", "--feature", params.feature, "--json"]));
1676
+ const revision = inspected && typeof inspected === "object" && typeof (inspected as { graph?: { revision?: unknown } }).graph?.revision === "number"
1677
+ ? (inspected as { graph: { revision: number } }).graph.revision
1678
+ : undefined;
1679
+ if (revision === undefined) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "Could not read the current requirement-graph revision before importing API TC candidates." });
1680
+ const imported = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["import-test-cases", "--feature", params.feature, "--write", "--expected-revision", String(revision), "--json"]);
1681
+ const importedPayload = parseJsonResult(imported);
1682
+ const failed = importedPayload && typeof importedPayload === "object" && (importedPayload as { ok?: unknown }).ok === false;
1683
+ return renderResult(failed ? { ...imported, ok: false, exitCode: imported.exitCode || 1 } : imported, { action: params.action, feature: params.feature });
1684
+ },
1685
+ });
1686
+
1687
+ pi.registerTool({
1688
+ name: "devflow_tdd_evidence",
1689
+ label: "Devflow TDD Evidence",
1690
+ description: "Core-owned preparation and RED/GREEN/REGRESSION evidence for a frozen tdd_required task binding.",
1691
+ parameters: z.object({
1692
+ action: z.enum(["prepare", "record", "inspect"]),
1693
+ feature: z.string().min(1),
1694
+ taskId: z.string().min(1),
1695
+ stage: z.enum(["red", "green", "regression"]).optional(),
1696
+ }),
1697
+ approval: args => args.action === "inspect" ? "read" : "write",
1698
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1699
+ if (params.action === "record" && !params.stage) {
1700
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "record requires stage" });
1701
+ }
1702
+ const args = [params.action, "--feature", params.feature, "--task-id", params.taskId, "--json"];
1703
+ if (params.action !== "inspect") args.push("--write");
1704
+ if (params.stage) args.push("--stage", params.stage);
1705
+ const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/tdd_evidence.py", args);
1706
+ const payload = parseJsonResult(process);
1707
+ const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
1708
+ return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, {
1709
+ action: params.action,
1710
+ feature: params.feature,
1711
+ taskId: params.taskId,
1712
+ stage: params.stage,
1713
+ });
1714
+ },
1715
+ });
1716
+
1717
+ pi.registerTool({
1718
+ name: "devflow_run_acceptance_tests",
1719
+ label: "Devflow Run Acceptance Tests",
1720
+ description: "Validate or execute only command or portable HTTP adapters bound to the frozen TC acceptance suite.",
1721
+ parameters: z.object({
1722
+ action: z.enum(["validate", "run", "validate_results"]),
1723
+ feature: z.string().min(1),
1724
+ }),
1725
+ approval: args => args.action === "run" ? "write" : "read",
1726
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1727
+ const scriptAction = params.action === "validate_results" ? "validate-results" : params.action;
1728
+ const args = [scriptAction, "--feature", params.feature, "--json"];
1729
+ if (params.action === "run") args.push("--write");
1730
+ const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_runner.py", args);
1731
+ const payload = parseJsonResult(process);
1732
+ const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
1733
+ return renderResult(failed ? { ...process, ok: false, exitCode: process.exitCode || 1 } : process, {
1734
+ action: params.action,
1735
+ feature: params.feature,
1736
+ });
1737
+ },
1738
+ });
1739
+
1591
1740
  pi.registerTool({
1592
1741
  name: "devflow_runtime_context",
1593
1742
  label: "Devflow Runtime Context",
@@ -2131,7 +2280,7 @@ export default function devflowCore(pi: ExtensionAPI) {
2131
2280
  pi.registerTool({
2132
2281
  name: "devflow_skill_script",
2133
2282
  label: "Devflow Skill Script",
2134
- description: "Run an allowlisted deterministic script bundled with a Devflow skill without relying on a .skills filesystem path.",
2283
+ description: "Run an allowlisted deterministic script bundled with a Devflow skill. project-context-validate is a final initialization gate: call it only after all manifest outputs and current-run markers are written.",
2135
2284
  parameters: z.object({
2136
2285
  script: z.enum([
2137
2286
  "project-context-validate",