@guanlin4924/devflow-pi 0.1.94 → 0.1.96
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 +34 -4
- package/node_modules/@playwright/test/LICENSE +202 -0
- package/node_modules/@playwright/test/NOTICE +5 -0
- package/node_modules/@playwright/test/README.md +318 -0
- package/node_modules/@playwright/test/cli.js +19 -0
- package/node_modules/@playwright/test/index.d.ts +18 -0
- package/node_modules/@playwright/test/index.js +17 -0
- package/node_modules/@playwright/test/index.mjs +18 -0
- package/node_modules/@playwright/test/package.json +35 -0
- package/node_modules/@playwright/test/reporter.d.ts +17 -0
- package/node_modules/@playwright/test/reporter.js +17 -0
- package/node_modules/@playwright/test/reporter.mjs +17 -0
- package/node_modules/playwright/LICENSE +202 -0
- package/node_modules/playwright/NOTICE +5 -0
- package/node_modules/playwright/README.md +318 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +14 -0
- package/node_modules/playwright/cli.js +19 -0
- package/node_modules/playwright/index.d.ts +17 -0
- package/node_modules/playwright/index.js +17 -0
- package/node_modules/playwright/index.mjs +18 -0
- package/node_modules/playwright/jsx-runtime.js +42 -0
- package/node_modules/playwright/jsx-runtime.mjs +21 -0
- package/node_modules/playwright/lib/agents/agentParser.js +89 -0
- package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +34 -0
- package/node_modules/playwright/lib/agents/generateAgents.js +395 -0
- package/node_modules/playwright/lib/agents/playwright-test-coverage.prompt.md +31 -0
- package/node_modules/playwright/lib/agents/playwright-test-generate.prompt.md +8 -0
- package/node_modules/playwright/lib/agents/playwright-test-generator.agent.md +88 -0
- package/node_modules/playwright/lib/agents/playwright-test-heal.prompt.md +6 -0
- package/node_modules/playwright/lib/agents/playwright-test-healer.agent.md +56 -0
- package/node_modules/playwright/lib/agents/playwright-test-plan.prompt.md +9 -0
- package/node_modules/playwright/lib/agents/playwright-test-planner.agent.md +74 -0
- package/node_modules/playwright/lib/cli/reportActions.js +78 -0
- package/node_modules/playwright/lib/cli/testActions.js +214 -0
- package/node_modules/playwright/lib/common/index.js +2923 -0
- package/node_modules/playwright/lib/common/index.js.txt +34 -0
- package/node_modules/playwright/lib/errorContext.js +129 -0
- package/node_modules/playwright/lib/globals.js +58 -0
- package/node_modules/playwright/lib/index.js +815 -0
- package/node_modules/playwright/lib/isomorphic.js +260 -0
- package/node_modules/playwright/lib/isomorphic.js.txt +9 -0
- package/node_modules/playwright/lib/loader/loaderProcessEntry.js +34 -0
- package/node_modules/playwright/lib/loader/loaderProcessEntry.js.txt +9 -0
- package/node_modules/playwright/lib/matchers/expect.js +13490 -0
- package/node_modules/playwright/lib/matchers/expect.js.LICENSE +640 -0
- package/node_modules/playwright/lib/matchers/expect.js.txt +67 -0
- package/node_modules/playwright/lib/mcp/test/browserBackend.js +135 -0
- package/node_modules/playwright/lib/mcp/test/generatorTools.js +122 -0
- package/node_modules/playwright/lib/mcp/test/plannerTools.js +150 -0
- package/node_modules/playwright/lib/mcp/test/seed.js +82 -0
- package/node_modules/playwright/lib/mcp/test/streams.js +44 -0
- package/node_modules/playwright/lib/mcp/test/testBackend.js +99 -0
- package/node_modules/playwright/lib/mcp/test/testContext.js +306 -0
- package/node_modules/playwright/lib/mcp/test/testTool.js +30 -0
- package/node_modules/playwright/lib/mcp/test/testTools.js +98 -0
- package/node_modules/playwright/lib/package.js +47 -0
- package/node_modules/playwright/lib/program.js +241 -0
- package/node_modules/playwright/lib/runner/index.js +8527 -0
- package/node_modules/playwright/lib/runner/index.js.txt +61 -0
- package/node_modules/playwright/lib/transform/babelBundle.js +72274 -0
- package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +2384 -0
- package/node_modules/playwright/lib/transform/babelBundle.js.txt +325 -0
- package/node_modules/playwright/lib/transform/esmLoader.js +8054 -0
- package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +355 -0
- package/node_modules/playwright/lib/transform/esmLoader.js.txt +60 -0
- package/node_modules/playwright/lib/util.js +373 -0
- package/node_modules/playwright/lib/worker/workerProcessEntry.js +1861 -0
- package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +19 -0
- package/node_modules/playwright/package.json +55 -0
- package/node_modules/playwright/test.d.ts +18 -0
- package/node_modules/playwright/test.js +24 -0
- package/node_modules/playwright/test.mjs +35 -0
- package/node_modules/playwright/types/test.d.ts +11074 -0
- package/node_modules/playwright/types/testReporter.d.ts +950 -0
- package/node_modules/playwright-core/LICENSE +202 -0
- package/node_modules/playwright-core/NOTICE +5 -0
- package/node_modules/playwright-core/README.md +3 -0
- package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
- package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
- package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/browsers.json +52 -0
- package/node_modules/playwright-core/cli.js +21 -0
- package/node_modules/playwright-core/index.d.ts +17 -0
- package/node_modules/playwright-core/index.js +17 -0
- package/node_modules/playwright-core/index.mjs +28 -0
- package/node_modules/playwright-core/lib/bootstrap.js +88 -0
- package/node_modules/playwright-core/lib/coreBundle.js +75813 -0
- package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
- package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
- package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
- package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
- package/node_modules/playwright-core/lib/package.js +50 -0
- package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/server/electron/loader.js +116 -0
- package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
- package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
- package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
- package/node_modules/playwright-core/lib/tools/cli-client/cli.js +6 -0
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +721 -0
- package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
- package/node_modules/playwright-core/lib/tools/cli-client/output.js +351 -0
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +418 -0
- package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +259 -0
- package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +425 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +136 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +89 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +69 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/typing.md +173 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +77 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +174 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
- package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
- package/node_modules/playwright-core/lib/utilsBundle.js +85357 -0
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CVQWJZAA.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-hrlLqLtq.js +129 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BbkfBe3n.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Ds6CBOo0.js +182 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-DywYcAf8.js +7 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.Bqk9acqE.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B4cLoZK3.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CU5KtEkS.js +5 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/node_modules/playwright-core/lib/xdg-open +1267 -0
- package/node_modules/playwright-core/package.json +34 -0
- package/node_modules/playwright-core/types/protocol.d.ts +24645 -0
- package/node_modules/playwright-core/types/structs.d.ts +52 -0
- package/node_modules/playwright-core/types/types.d.ts +26632 -0
- package/package.json +13 -2
- package/plugins/devflow-core/extensions/devflow-core.ts +432 -7
- package/plugins/devflow-core/runtime/hooks/hook_common.py +514 -0
- package/plugins/devflow-core/runtime/hooks/index_gate.py +47 -16
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +488 -71
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +51 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_pins.py +433 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_store.py +137 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_coverage.py +87 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_draft.py +53 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_v1.py +318 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/environment_profile.py +134 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +1 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_surface.py +138 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/playwright_adapter.py +515 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tc_hash.py +132 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +102 -8
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +13 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +13 -34
- package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +3 -1
- package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +68 -140
- package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization_database.py +1 -1
- package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +3 -1
- package/plugins/devflow-intel/extensions/devflow-intel.ts +1 -1
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +23 -4
- package/plugins/devflow-quality/runtime/playwright/devflow-test-dsl-v1.schema.json +872 -0
- package/plugins/devflow-quality/runtime/playwright/playwright.config.mjs +46 -0
- package/plugins/devflow-quality/runtime/playwright/reporter.mjs +311 -0
- package/plugins/devflow-quality/runtime/playwright/runner.spec.mjs +1308 -0
- package/plugins/devflow-quality/runtime/playwright/runtime-manifest.json +11 -0
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +71 -58
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +20 -15
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +3 -1
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +115 -192
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +27 -23
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +78 -153
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +36 -83
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +497 -556
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/export_legacy_testagent.py +647 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +10 -3
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +165 -275
- package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +63 -0
- package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +4 -4
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +17 -9
- package/src/approval-store.ts +340 -0
- package/src/cli.ts +10 -0
- package/src/constants.ts +1 -1
- package/src/doctor.ts +268 -2
- package/src/initialization-store.ts +48 -1
- package/src/mcp/auth.ts +103 -0
- package/src/mcp/core.ts +102 -0
- package/src/mcp/server.ts +193 -0
- package/src/mcp/types.ts +5 -0
- package/src/paths.ts +12 -1
- package/src/project-context.ts +24 -1
- package/templates/omp/hook-config.json +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanlin4924/devflow-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -14,13 +14,18 @@
|
|
|
14
14
|
"test:devflow-pi": "bun test tests/devflow-pi",
|
|
15
15
|
"test": "bun test tests/devflow-pi && python3 -m unittest discover -s tests -p 'test_*.py'",
|
|
16
16
|
"check": "bun test tests/devflow-pi && python3 -m unittest discover -s tests -p 'test_*.py'",
|
|
17
|
+
"build:runtime": "bun scripts/build-runtime.ts",
|
|
18
|
+
"check:runtime": "bun scripts/build-runtime.ts --check",
|
|
17
19
|
"release": "bash scripts/release-npm.sh"
|
|
18
20
|
},
|
|
19
21
|
"engines": {
|
|
20
|
-
"bun": ">=1.3.14"
|
|
22
|
+
"bun": ">=1.3.14",
|
|
23
|
+
"node": ">=20"
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
23
27
|
"@oh-my-pi/pi-coding-agent": "17.2.7",
|
|
28
|
+
"@playwright/test": "1.63.0",
|
|
24
29
|
"@plurnk/plurnk-mimetypes-grammar-cpp": "1.16.1",
|
|
25
30
|
"@plurnk/plurnk-mimetypes-grammar-kotlin": "1.16.1",
|
|
26
31
|
"@plurnk/plurnk-mimetypes-grammar-ruby": "1.16.1",
|
|
@@ -29,6 +34,9 @@
|
|
|
29
34
|
"tree-sitter-graphql-grammar-wasm": "1.1.0",
|
|
30
35
|
"web-tree-sitter": "0.25.10"
|
|
31
36
|
},
|
|
37
|
+
"bundleDependencies": [
|
|
38
|
+
"@playwright/test"
|
|
39
|
+
],
|
|
32
40
|
"devDependencies": {
|
|
33
41
|
"@types/bun": "^1.3.14",
|
|
34
42
|
"typescript": "^5.9.3"
|
|
@@ -40,6 +48,9 @@
|
|
|
40
48
|
"!plugins/**/__pycache__",
|
|
41
49
|
"!plugins/**/*.pyc",
|
|
42
50
|
"!plugins/**/tests",
|
|
51
|
+
"!plugins/devflow-quality/runtime/playwright/spike",
|
|
52
|
+
"!plugins/devflow-quality/runtime/playwright/src",
|
|
53
|
+
"!plugins/devflow-quality/runtime/playwright/SPIKE.md",
|
|
43
54
|
"templates",
|
|
44
55
|
"README.md"
|
|
45
56
|
],
|
|
@@ -17,6 +17,20 @@ import { projectControlDatabasePath } from "../../../src/paths.ts";
|
|
|
17
17
|
import { generateProjectDocuments } from "../../../src/project-documents.ts";
|
|
18
18
|
import { exportInitializationSnapshot, importLegacyInitialization, switchInitializationAuthority } from "../../../src/project-migration.ts";
|
|
19
19
|
import { migrateLegacyProjectLayout } from "../../../src/project-layout.ts";
|
|
20
|
+
import {
|
|
21
|
+
APPROVAL_KINDS,
|
|
22
|
+
confirmApproval,
|
|
23
|
+
databaseAuthority,
|
|
24
|
+
listApprovals,
|
|
25
|
+
listProposals,
|
|
26
|
+
proposalIsExpired,
|
|
27
|
+
proposeApprovals,
|
|
28
|
+
rejectProposal,
|
|
29
|
+
type ApprovalKind,
|
|
30
|
+
type CurrentPins,
|
|
31
|
+
type HumanResultStatus,
|
|
32
|
+
type ProposalRecord,
|
|
33
|
+
} from "../../../src/approval-store.ts";
|
|
20
34
|
|
|
21
35
|
|
|
22
36
|
export type DevflowIntent = "idle" | "explain-state" | "project-init" | "requirement-draft" | "workflow-execute";
|
|
@@ -210,6 +224,28 @@ const STATE_MUTATION_TOOLS = new Set([
|
|
|
210
224
|
"devflow_run_acceptance_tests",
|
|
211
225
|
]);
|
|
212
226
|
|
|
227
|
+
/** Maps devflow_run_acceptance_tests parameters to acceptance_test_runner.py argv; gc_runs is a dry-run unless write=true. */
|
|
228
|
+
export function acceptanceRunnerArgs(params: {
|
|
229
|
+
action: "validate" | "validate_human" | "run" | "validate_results" | "gc_runs";
|
|
230
|
+
feature: string;
|
|
231
|
+
write?: boolean;
|
|
232
|
+
retentionDays?: number;
|
|
233
|
+
unreferencedTtlDays?: number;
|
|
234
|
+
}): string[] {
|
|
235
|
+
const scriptAction = params.action === "validate_results"
|
|
236
|
+
? "validate-results"
|
|
237
|
+
: params.action === "validate_human" ? "validate-human"
|
|
238
|
+
: params.action === "gc_runs" ? "gc-runs" : params.action;
|
|
239
|
+
const args = [scriptAction, "--feature", params.feature, "--json"];
|
|
240
|
+
if (params.action === "run") args.push("--write");
|
|
241
|
+
if (params.action === "gc_runs") {
|
|
242
|
+
if (params.write === true) args.push("--write");
|
|
243
|
+
if (params.retentionDays !== undefined) args.push("--retention-days", String(params.retentionDays));
|
|
244
|
+
if (params.unreferencedTtlDays !== undefined) args.push("--unreferenced-ttl-days", String(params.unreferencedTtlDays));
|
|
245
|
+
}
|
|
246
|
+
return args;
|
|
247
|
+
}
|
|
248
|
+
|
|
213
249
|
export function initialControlState(): SessionControlState {
|
|
214
250
|
return { intent: "idle", phase: "idle", label: "对话(不自动执行)" };
|
|
215
251
|
}
|
|
@@ -561,6 +597,7 @@ const REQUIRED_EXTENSION_TOOLS = [
|
|
|
561
597
|
"devflow_context_cursor",
|
|
562
598
|
"devflow_design_rule",
|
|
563
599
|
"devflow_mainflow",
|
|
600
|
+
"devflow_approval",
|
|
564
601
|
];
|
|
565
602
|
|
|
566
603
|
const REQUIRED_SKILLS = [
|
|
@@ -584,6 +621,7 @@ const REQUIRED_SKILLS = [
|
|
|
584
621
|
"workflow-frontend-tech-spec",
|
|
585
622
|
"workflow-gen-test-guide",
|
|
586
623
|
"workflow-generate-mainflow-cases",
|
|
624
|
+
"workflow-human-acceptance",
|
|
587
625
|
"workflow-integration-verify",
|
|
588
626
|
"workflow-orchestrator",
|
|
589
627
|
"workflow-pipeline",
|
|
@@ -807,6 +845,7 @@ function isCoreOwnedGovernancePath(filePath: string): boolean {
|
|
|
807
845
|
// 规范需求图谱只能由 Core 的 devflow_requirement_graph writer 写入;the
|
|
808
846
|
// same ownership extends to its views, machine evidence, and audit records.
|
|
809
847
|
return isCanonicalRequirementGraphPath(filePath)
|
|
848
|
+
|| /^\.devflow\/planning\/test-runs\/[^/\\\x00-\x1F\x7F]+\/acceptance-results(?:\.meta)?\.json$/u.test(filePath)
|
|
810
849
|
|| /^\.devflow\/planning\/evidence\/[^/\\\x00-\x1F\x7F]+\/[^/\\]+\.machine\.json$/u.test(filePath)
|
|
811
850
|
|| /^\.devflow\/planning\/contracts\/[^/\\\x00-\x1F\x7F]+\/(?:EC-[A-Fa-f0-9]+\.json|control-events\.jsonl)$/u.test(filePath)
|
|
812
851
|
|| filePath.startsWith(".devflow/planning/review-runs/")
|
|
@@ -1292,7 +1331,9 @@ function stepScopedMutationOwner(toolName: string, input: unknown): string | nul
|
|
|
1292
1331
|
if (normalized === "devflow_api_test_matrix") return action === "validate" ? undefined : "acceptance-test-design";
|
|
1293
1332
|
if (normalized === "devflow_tdd_evidence") return action === "inspect" ? undefined : "execute";
|
|
1294
1333
|
if (normalized === "devflow_run_acceptance_tests") return action === "run" ? "run-acceptance-tests" : undefined;
|
|
1295
|
-
|
|
1334
|
+
// Mainflow generation is an optional developer handoff inside the explicit
|
|
1335
|
+
// human-acceptance step, not a standalone workflow step.
|
|
1336
|
+
if (normalized === "devflow_mainflow") return "human-acceptance";
|
|
1296
1337
|
if (normalized === "devflow_wireframe" || normalized === "devflow_design_rule") return "ui-spec";
|
|
1297
1338
|
if (normalized.startsWith("devflow_architecture_") || normalized === "devflow_workload_constraint") {
|
|
1298
1339
|
return action === "inspect" ? undefined : "architecture-feasibility";
|
|
@@ -1349,6 +1390,198 @@ export function stepScopedMutationBlockReason(
|
|
|
1349
1390
|
return undefined;
|
|
1350
1391
|
}
|
|
1351
1392
|
|
|
1393
|
+
// ---------------------------------------------------------------------------
|
|
1394
|
+
// Core human approvals (change list P03/P04). The agent may only propose or
|
|
1395
|
+
// list; confirmation exists solely in the /devflow control panel callback,
|
|
1396
|
+
// which writes control.db directly from this extension process.
|
|
1397
|
+
// ---------------------------------------------------------------------------
|
|
1398
|
+
|
|
1399
|
+
export const APPROVAL_TOOL_ACTIONS = ["propose", "list"] as const;
|
|
1400
|
+
|
|
1401
|
+
/** Parameter schema of `devflow_approval`; there is deliberately no confirm action. */
|
|
1402
|
+
export function approvalToolParameters(z: any) {
|
|
1403
|
+
const item = z.object({
|
|
1404
|
+
subject: z.string().min(1).max(128),
|
|
1405
|
+
dsl_file: z.string().min(1).max(512).optional(),
|
|
1406
|
+
evidence_files: z.array(z.string().min(1).max(512)).max(50).optional(),
|
|
1407
|
+
note: z.string().max(4000).optional(),
|
|
1408
|
+
review_issues: z.array(z.string().min(1).max(2000)).max(100).optional(),
|
|
1409
|
+
}).strict();
|
|
1410
|
+
return z.object({
|
|
1411
|
+
action: z.enum(APPROVAL_TOOL_ACTIONS),
|
|
1412
|
+
feature: z.string().min(1).max(200).optional(),
|
|
1413
|
+
kind: z.enum(APPROVAL_KINDS).optional(),
|
|
1414
|
+
items: z.array(item).min(1).max(200).optional(),
|
|
1415
|
+
status: z.enum(["proposed", "consumed", "rejected", "expired"]).optional(),
|
|
1416
|
+
}).strict();
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
export type ApprovalProposeItem = {
|
|
1420
|
+
subject: string;
|
|
1421
|
+
dsl_file?: string;
|
|
1422
|
+
evidence_files?: string[];
|
|
1423
|
+
note?: string;
|
|
1424
|
+
review_issues?: string[];
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Splits one agent item into the pin-computation request (re-used verbatim for
|
|
1429
|
+
* the confirm-time CAS) and agent-supplied context that is only displayed.
|
|
1430
|
+
*/
|
|
1431
|
+
export function approvalProposeRequest(kind: ApprovalKind, item: ApprovalProposeItem): { request: Record<string, unknown>; agent: Record<string, unknown> } {
|
|
1432
|
+
if (item.dsl_file !== undefined && kind !== "execution_plan") throw new Error(`dsl_file is only valid for execution_plan proposals`);
|
|
1433
|
+
if (item.review_issues !== undefined && kind !== "execution_plan") throw new Error(`review_issues are only valid for execution_plan proposals`);
|
|
1434
|
+
if (item.evidence_files !== undefined && kind !== "human_result") throw new Error(`evidence_files are only valid for human_result proposals`);
|
|
1435
|
+
const request: Record<string, unknown> = {};
|
|
1436
|
+
if (kind === "execution_plan" && item.dsl_file) request.dsl_file = item.dsl_file;
|
|
1437
|
+
if (kind === "human_result") request.evidence_files = item.evidence_files ?? [];
|
|
1438
|
+
const agent: Record<string, unknown> = {};
|
|
1439
|
+
if (item.note?.trim()) agent.note = item.note.trim();
|
|
1440
|
+
if (item.review_issues?.length) agent.review_issues = item.review_issues;
|
|
1441
|
+
return { request, agent };
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
export function approvalPinsScript(): string {
|
|
1445
|
+
// Always the packaged helper next to this extension: never a project-local
|
|
1446
|
+
// `.skills` copy the agent could edit to forge the confirm-time CAS.
|
|
1447
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../skills/workflow-orchestrator/scripts/approval_pins.py");
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
export function approvalPinsArgv(root: string, feature: string, kind: ApprovalKind, subject: string, request: Record<string, unknown>): string[] {
|
|
1451
|
+
return [
|
|
1452
|
+
"python3", "-I", approvalPinsScript(), "compute",
|
|
1453
|
+
"--root", root, "--feature", feature, "--kind", kind, "--subject", subject,
|
|
1454
|
+
"--payload-json", JSON.stringify(request), "--json",
|
|
1455
|
+
];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
export type ApprovalPinsResult = { ok: true; pins: Record<string, unknown>; stateRevision: number; display: Record<string, unknown> } | { ok: false; errors: string[] };
|
|
1459
|
+
|
|
1460
|
+
export function parseApprovalPinsOutput(stdout: string, stderr = ""): ApprovalPinsResult {
|
|
1461
|
+
let parsed: Record<string, unknown> | null = null;
|
|
1462
|
+
try { parsed = JSON.parse(stdout) as Record<string, unknown>; } catch { parsed = null; }
|
|
1463
|
+
if (!parsed || typeof parsed !== "object") return { ok: false, errors: [`approval pin helper returned invalid JSON: ${(stderr || stdout).slice(0, 500)}`] };
|
|
1464
|
+
if (parsed.ok !== true) {
|
|
1465
|
+
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(String) : ["approval pin helper failed"];
|
|
1466
|
+
return { ok: false, errors };
|
|
1467
|
+
}
|
|
1468
|
+
if (!parsed.pins || typeof parsed.pins !== "object" || !Number.isInteger(parsed.state_revision)) {
|
|
1469
|
+
return { ok: false, errors: ["approval pin helper returned no pins"] };
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
ok: true,
|
|
1473
|
+
pins: parsed.pins as Record<string, unknown>,
|
|
1474
|
+
stateRevision: Number(parsed.state_revision),
|
|
1475
|
+
display: parsed.display && typeof parsed.display === "object" ? parsed.display as Record<string, unknown> : {},
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/** Synchronous recompute used inside the confirm transaction. */
|
|
1480
|
+
export function recomputeApprovalPinsSync(root: string, proposal: ProposalRecord): CurrentPins {
|
|
1481
|
+
const request = proposal.payload.request && typeof proposal.payload.request === "object" ? proposal.payload.request as Record<string, unknown> : {};
|
|
1482
|
+
const child = Bun.spawnSync(approvalPinsArgv(root, proposal.feature, proposal.kind, proposal.subjectKey, request), {
|
|
1483
|
+
cwd: root, stdout: "pipe", stderr: "pipe", env: { ...process.env, OMP_PROJECT_DIR: root },
|
|
1484
|
+
});
|
|
1485
|
+
const result = parseApprovalPinsOutput(child.stdout.toString(), child.stderr.toString());
|
|
1486
|
+
if (!result.ok) throw new Error(result.errors.join("; "));
|
|
1487
|
+
return { pins: result.pins, stateRevision: result.stateRevision };
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
export const APPROVAL_KIND_LABELS: Record<ApprovalKind, string> = {
|
|
1491
|
+
execution_plan: "执行计划",
|
|
1492
|
+
environment_profile: "环境配置",
|
|
1493
|
+
mode_override: "降级为人工",
|
|
1494
|
+
human_result: "人工验收结果",
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
export const HUMAN_RESULT_CHOICES: Array<{ label: string; status: HumanResultStatus }> = [
|
|
1498
|
+
{ label: "passed(通过)", status: "passed" },
|
|
1499
|
+
{ label: "failed(失败)", status: "failed" },
|
|
1500
|
+
{ label: "blocked(阻塞)", status: "blocked" },
|
|
1501
|
+
];
|
|
1502
|
+
|
|
1503
|
+
/** Builds a human_result decision only from the user's own selection and input. */
|
|
1504
|
+
export function humanResultDecision(choice: string | undefined, actualResult: string | undefined): { status: HumanResultStatus; actual_result: string } | null {
|
|
1505
|
+
const status = HUMAN_RESULT_CHOICES.find(item => item.label === choice)?.status;
|
|
1506
|
+
const actual = actualResult?.trim();
|
|
1507
|
+
if (!status || !actual) return null;
|
|
1508
|
+
return { status, actual_result: actual };
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
export function pendingApprovalProposals(proposals: ProposalRecord[], now = new Date()): ProposalRecord[] {
|
|
1512
|
+
return proposals.filter(item => item.status === "proposed" && !proposalIsExpired(item, now));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
export function approvalProposalLabel(proposal: ProposalRecord): string {
|
|
1516
|
+
return `${APPROVAL_KIND_LABELS[proposal.kind] ?? proposal.kind} · ${proposal.feature} · ${proposal.subjectKey} · ${proposal.createdAt.slice(0, 16).replace("T", " ")} · ${proposal.id.slice(0, 8)}`;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function approvalText(value: unknown, limit = 600): string {
|
|
1520
|
+
const text = typeof value === "string" ? value : value === undefined || value === null ? "(无)" : JSON.stringify(value);
|
|
1521
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function approvalList(values: unknown, empty = "(无)"): string[] {
|
|
1525
|
+
if (!Array.isArray(values) || values.length === 0) return [` ${empty}`];
|
|
1526
|
+
return values.map((value, index) => ` ${index + 1}. ${approvalText(value, 400)}`);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/** Pure display assembly for one pending proposal (tested without a UI). */
|
|
1530
|
+
export function approvalReviewText(proposal: ProposalRecord): string {
|
|
1531
|
+
const display = proposal.payload.display && typeof proposal.payload.display === "object" ? proposal.payload.display as Record<string, any> : {};
|
|
1532
|
+
const agent = proposal.payload.agent && typeof proposal.payload.agent === "object" ? proposal.payload.agent as Record<string, any> : {};
|
|
1533
|
+
const lines = [
|
|
1534
|
+
`类型:${APPROVAL_KIND_LABELS[proposal.kind] ?? proposal.kind}`,
|
|
1535
|
+
`功能:${proposal.feature}`,
|
|
1536
|
+
`对象:${proposal.subjectKey}`,
|
|
1537
|
+
`提案:${proposal.id}(${proposal.createdAt},状态版本 ${proposal.stateRevision})`,
|
|
1538
|
+
"",
|
|
1539
|
+
];
|
|
1540
|
+
const tcLines = () => [
|
|
1541
|
+
`TC 目标:${approvalText(display.target)}`,
|
|
1542
|
+
`Oracle 原文:${approvalText(display.oracle, 2000)}`,
|
|
1543
|
+
"前置条件:", ...approvalList(display.preconditions),
|
|
1544
|
+
"步骤:", ...approvalList(display.steps),
|
|
1545
|
+
];
|
|
1546
|
+
if (proposal.kind === "execution_plan") {
|
|
1547
|
+
lines.push(...tcLines());
|
|
1548
|
+
lines.push(`DSL:${approvalText(display.dsl_file)} sha256=${approvalText(proposal.pins.dsl_sha256)}`);
|
|
1549
|
+
lines.push("覆盖 oracle 的断言:", ...approvalList(display.oracle_assertions, "(无 — 请勿批准)"));
|
|
1550
|
+
const uncovered = [
|
|
1551
|
+
...(Array.isArray(display.uncovered_preconditions) && display.uncovered_preconditions.length ? [`前置条件 ${display.uncovered_preconditions.join(", ")}`] : []),
|
|
1552
|
+
...(Array.isArray(display.uncovered_steps) && display.uncovered_steps.length ? [`步骤 ${display.uncovered_steps.join(", ")}`] : []),
|
|
1553
|
+
];
|
|
1554
|
+
lines.push(`未被覆盖:${uncovered.length ? uncovered.join(";") : "无"}`);
|
|
1555
|
+
if (display.structural_validation === "unavailable") lines.push("注意:Core 结构覆盖校验器不可用,本次只展示覆盖声明。");
|
|
1556
|
+
lines.push("AI 审查问题(建议性):", ...approvalList(agent.review_issues, "(未提供独立 AI 审查结果)"));
|
|
1557
|
+
} else if (proposal.kind === "environment_profile") {
|
|
1558
|
+
lines.push(`Profile:${approvalText(display.profile)}(${approvalText(display.path)})`);
|
|
1559
|
+
lines.push(`base_url:${approvalText(display.base_url)}`);
|
|
1560
|
+
lines.push("允许的页面导航 origin:", ...approvalList(display.navigation_origins));
|
|
1561
|
+
lines.push("允许的 API origin:", ...approvalList(display.api_origins));
|
|
1562
|
+
lines.push(`子资源策略:${approvalText(display.subresources)}`);
|
|
1563
|
+
const secrets = display.secret_env && typeof display.secret_env === "object" ? Object.entries(display.secret_env as Record<string, unknown>) : [];
|
|
1564
|
+
lines.push("密钥环境变量(仅名称):", ...(secrets.length ? secrets.map(([alias, name]) => ` ${alias} ← ${String(name)}`) : [" (无)"]));
|
|
1565
|
+
lines.push(`trace:${approvalText(display.trace)}`);
|
|
1566
|
+
lines.push(`profile sha256:${approvalText(proposal.pins.profile_sha256)}`);
|
|
1567
|
+
} else if (proposal.kind === "mode_override") {
|
|
1568
|
+
lines.push(...tcLines());
|
|
1569
|
+
lines.push(`降级:${approvalText(display.from)} → ${approvalText(display.to)}`);
|
|
1570
|
+
lines.push(`绑定条目:${approvalText(display.binding_entry, 1000)}`);
|
|
1571
|
+
} else if (proposal.kind === "human_result") {
|
|
1572
|
+
lines.push(...tcLines());
|
|
1573
|
+
const evidence = Array.isArray(display.evidence) ? display.evidence as Array<Record<string, unknown>> : [];
|
|
1574
|
+
lines.push("证据文件(批准时固定哈希):", ...(evidence.length ? evidence.map(item => ` ${String(item.path)} sha256=${String(item.sha256)}`) : [" (无证据文件;无法选择 passed)"]));
|
|
1575
|
+
lines.push("结论未预填:请根据你自己的执行结果选择 passed / failed / blocked,并填写实际结果。");
|
|
1576
|
+
}
|
|
1577
|
+
if (agent.note && proposal.kind !== "execution_plan") {
|
|
1578
|
+
lines.push("", `Agent 说明(仅供参考,不是结论):${approvalText(agent.note, 2000)}`);
|
|
1579
|
+
} else if (agent.note) {
|
|
1580
|
+
lines.push("", `Agent 说明:${approvalText(agent.note, 2000)}`);
|
|
1581
|
+
}
|
|
1582
|
+
return lines.join("\n");
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1352
1585
|
export default function devflowCore(pi: ExtensionAPI) {
|
|
1353
1586
|
const { z } = pi.zod;
|
|
1354
1587
|
pi.registerTool({
|
|
@@ -2326,6 +2559,115 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2326
2559
|
ctx.ui.notify(`功能“${feature}”已取消;现有工件已保留。`, "info");
|
|
2327
2560
|
}
|
|
2328
2561
|
|
|
2562
|
+
async function pendingApprovalCount(cwd: string): Promise<number | null> {
|
|
2563
|
+
try {
|
|
2564
|
+
const project = await loadProjectContext(cwd);
|
|
2565
|
+
const store = await InitializationStore.open(project);
|
|
2566
|
+
try {
|
|
2567
|
+
if (databaseAuthority(store.db, project.manifest.projectId) !== "database") return null;
|
|
2568
|
+
return pendingApprovalProposals(listProposals(store.db, project.manifest.projectId, { status: "proposed" })).length;
|
|
2569
|
+
} finally { store.close(); }
|
|
2570
|
+
} catch {
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* The only confirmation path for Core approvals. It runs in this extension
|
|
2577
|
+
* process, re-computes pins inside the control.db transaction and never
|
|
2578
|
+
* shells out to a script the agent could also call to confirm.
|
|
2579
|
+
*/
|
|
2580
|
+
async function reviewPendingApprovalsFromPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
2581
|
+
let project: Awaited<ReturnType<typeof loadProjectContext>>;
|
|
2582
|
+
try {
|
|
2583
|
+
project = await loadProjectContext(ctx.cwd);
|
|
2584
|
+
} catch (error) {
|
|
2585
|
+
ctx.ui.notify(`无法读取项目上下文:${error instanceof Error ? error.message : String(error)}`, "error");
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
const projectId = project.manifest.projectId;
|
|
2589
|
+
for (;;) {
|
|
2590
|
+
const store = await InitializationStore.open(project);
|
|
2591
|
+
try {
|
|
2592
|
+
if (databaseAuthority(store.db, projectId) !== "database") {
|
|
2593
|
+
ctx.ui.notify("approval_requires_database_authority:只有 database authority 模式支持人工批准。", "error");
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
const pending = pendingApprovalProposals(listProposals(store.db, projectId, { status: "proposed" }));
|
|
2597
|
+
if (pending.length === 0) {
|
|
2598
|
+
ctx.ui.notify("当前没有待审批的提案。", "info");
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
const labels = pending.map(approvalProposalLabel);
|
|
2602
|
+
const selected = await ctx.ui.select("待审批", [...labels, "返回"]);
|
|
2603
|
+
if (!selected || selected === "返回") return;
|
|
2604
|
+
const proposal = pending[labels.indexOf(selected)];
|
|
2605
|
+
if (!proposal) return;
|
|
2606
|
+
const review = approvalReviewText(proposal);
|
|
2607
|
+
let decision: Record<string, unknown> | null = null;
|
|
2608
|
+
if (proposal.kind === "human_result") {
|
|
2609
|
+
ctx.ui.notify(review.slice(0, 10000), "info");
|
|
2610
|
+
const choice = await ctx.ui.select(`人工验收结论(${proposal.subjectKey})`, [...HUMAN_RESULT_CHOICES.map(item => item.label), "拒绝该提案", "返回"]);
|
|
2611
|
+
if (!choice || choice === "返回") continue;
|
|
2612
|
+
if (choice === "拒绝该提案") {
|
|
2613
|
+
if (await ctx.ui.confirm("拒绝提案", `${approvalProposalLabel(proposal)}
|
|
2614
|
+
|
|
2615
|
+
拒绝后该提案不能再被确认。继续吗?`)) {
|
|
2616
|
+
rejectProposal(store.db, projectId, proposal.id);
|
|
2617
|
+
ctx.ui.notify("已拒绝该提案。", "info");
|
|
2618
|
+
}
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
const actual = await ctx.ui.input(`实际结果(${proposal.subjectKey})`, "请填写你实际观察到的结果");
|
|
2622
|
+
decision = humanResultDecision(choice, actual);
|
|
2623
|
+
if (!decision) {
|
|
2624
|
+
ctx.ui.notify("必须选择结论并填写实际结果;未写入任何批准。", "error");
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
const confirmed = await ctx.ui.confirm(
|
|
2628
|
+
"确认人工验收结果",
|
|
2629
|
+
`${proposal.subjectKey}
|
|
2630
|
+
结论:${String(decision.status)}
|
|
2631
|
+
实际结果:${String(decision.actual_result)}
|
|
2632
|
+
|
|
2633
|
+
确认后 Core 将固定当前 TC 与证据哈希并记录该结果。继续吗?`,
|
|
2634
|
+
);
|
|
2635
|
+
if (!confirmed) continue;
|
|
2636
|
+
} else {
|
|
2637
|
+
const action = await ctx.ui.select(`审查:${approvalProposalLabel(proposal)}`, ["查看详情并批准…", "拒绝该提案", "返回"]);
|
|
2638
|
+
if (!action || action === "返回") continue;
|
|
2639
|
+
if (action === "拒绝该提案") {
|
|
2640
|
+
if (await ctx.ui.confirm("拒绝提案", `${approvalProposalLabel(proposal)}
|
|
2641
|
+
|
|
2642
|
+
拒绝后该提案不能再被确认。继续吗?`)) {
|
|
2643
|
+
rejectProposal(store.db, projectId, proposal.id);
|
|
2644
|
+
ctx.ui.notify("已拒绝该提案。", "info");
|
|
2645
|
+
}
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
const confirmed = await ctx.ui.confirm(`批准${APPROVAL_KIND_LABELS[proposal.kind]}`, `${review.slice(0, 12000)}
|
|
2649
|
+
|
|
2650
|
+
确认批准吗?`);
|
|
2651
|
+
if (!confirmed) continue;
|
|
2652
|
+
decision = { approved: true };
|
|
2653
|
+
}
|
|
2654
|
+
const result = confirmApproval(store.db, {
|
|
2655
|
+
projectId,
|
|
2656
|
+
proposalId: proposal.id,
|
|
2657
|
+
decision,
|
|
2658
|
+
recompute: current => recomputeApprovalPinsSync(project.root, current),
|
|
2659
|
+
});
|
|
2660
|
+
if (result.ok) {
|
|
2661
|
+
ctx.ui.notify(`已批准:${approvalProposalLabel(proposal)}${result.supersededIds.length ? `(替代 ${result.supersededIds.length} 条旧批准)` : ""}`, "info");
|
|
2662
|
+
} else {
|
|
2663
|
+
ctx.ui.notify(`批准失败(${result.code}):${result.message}`, "error");
|
|
2664
|
+
}
|
|
2665
|
+
} finally {
|
|
2666
|
+
store.close();
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2329
2671
|
async function openProjectControlPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
2330
2672
|
for (;;) {
|
|
2331
2673
|
// The control panel is a read-only projection. Each action below reads a
|
|
@@ -2341,6 +2683,9 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2341
2683
|
if (snapshot.action === "await_user_confirmation") options.push("审查并批准推进…");
|
|
2342
2684
|
if (hasSafeRecoveryPlan(snapshot)) options.push("执行安全恢复…");
|
|
2343
2685
|
if (typeof snapshot.current_feature === "string" && !["completed", "cancelled", "failed"].includes(String(snapshot.status))) options.push("取消当前功能…");
|
|
2686
|
+
const approvalCount = await pendingApprovalCount(ctx.cwd);
|
|
2687
|
+
const approvalOption = approvalCount === null ? null : `待审批(${approvalCount})…`;
|
|
2688
|
+
if (approvalOption) options.push(approvalOption);
|
|
2344
2689
|
options.push("关闭");
|
|
2345
2690
|
|
|
2346
2691
|
const selected = await ctx.ui.select("项目控制操作", options);
|
|
@@ -2371,6 +2716,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2371
2716
|
await cancelFeatureFromPanel(ctx);
|
|
2372
2717
|
return;
|
|
2373
2718
|
}
|
|
2719
|
+
if (approvalOption && selected === approvalOption) {
|
|
2720
|
+
await reviewPendingApprovalsFromPanel(ctx);
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2374
2723
|
}
|
|
2375
2724
|
}
|
|
2376
2725
|
|
|
@@ -3429,16 +3778,23 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3429
3778
|
pi.registerTool({
|
|
3430
3779
|
name: "devflow_run_acceptance_tests",
|
|
3431
3780
|
label: "Devflow Run Acceptance Tests",
|
|
3432
|
-
description:
|
|
3781
|
+
description: [
|
|
3782
|
+
"Validate or execute the adapters bound to the frozen TC acceptance suite: command, portable HTTP, and devflow_playwright (the Core-owned Playwright Runtime driven by a pinned DSL).",
|
|
3783
|
+
"Actions: validate (bindings and suite), validate_human (manual TCs), run (executes every automated TC and writes the canonical acceptance-results.json; Core is the only writer), validate_results (re-checks recorded results against current approvals and hashes), gc_runs (prunes .devflow/runs/<feature> execution directories per retention rules; dry-run by default, pass write=true to delete; optional retentionDays / unreferencedTtlDays).",
|
|
3784
|
+
"Failure analysis reads only the Core-produced result.json summary; trace files and screenshots are not readable by Agent tools and are for the user to inspect locally with Playwright in a terminal.",
|
|
3785
|
+
"devflow_playwright bindings require CURRENT human approvals created with devflow_approval and confirmed by the user in /devflow control: an execution_plan approval per TC (pins the DSL, TC and suite) and an environment_profile approval for the target profile; manual or downgraded TCs need a human_result / mode_override approval. You cannot confirm approvals yourself.",
|
|
3786
|
+
"Hard gate: a missing, superseded or expired approval, or a DSL/profile/suite/evidence hash that changed after the approval or the run, makes run and validate_results return ok=false and blocks run-acceptance-tests from completing; there is no soft override and results must never be written by hand.",
|
|
3787
|
+
].join(" "),
|
|
3433
3788
|
parameters: z.object({
|
|
3434
|
-
action: z.enum(["validate", "run", "validate_results"]),
|
|
3789
|
+
action: z.enum(["validate", "validate_human", "run", "validate_results", "gc_runs"]),
|
|
3435
3790
|
feature: z.string().min(1),
|
|
3791
|
+
write: z.boolean().optional().describe("gc_runs only: actually delete expired run directories. Omit for a dry-run."),
|
|
3792
|
+
retentionDays: z.number().int().min(0).optional().describe("gc_runs only: days referenced runs are kept after release-closeout."),
|
|
3793
|
+
unreferencedTtlDays: z.number().int().min(0).optional().describe("gc_runs only: days unreferenced runs are kept."),
|
|
3436
3794
|
}),
|
|
3437
|
-
approval: args => args.action === "run" ? "write" : "read",
|
|
3795
|
+
approval: args => args.action === "run" || (args.action === "gc_runs" && args.write === true) ? "write" : "read",
|
|
3438
3796
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3439
|
-
const
|
|
3440
|
-
const args = [scriptAction, "--feature", params.feature, "--json"];
|
|
3441
|
-
if (params.action === "run") args.push("--write");
|
|
3797
|
+
const args = acceptanceRunnerArgs(params);
|
|
3442
3798
|
const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_runner.py", args);
|
|
3443
3799
|
const payload = parseJsonResult(process);
|
|
3444
3800
|
const failed = payload && typeof payload === "object" && (payload as { ok?: unknown }).ok === false;
|
|
@@ -3449,6 +3805,75 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3449
3805
|
},
|
|
3450
3806
|
});
|
|
3451
3807
|
|
|
3808
|
+
pi.registerTool({
|
|
3809
|
+
name: "devflow_approval",
|
|
3810
|
+
label: "Devflow Approval",
|
|
3811
|
+
description: "Propose or list Core human approvals (execution_plan, environment_profile, mode_override, human_result). Core computes and pins the current TC/DSL/binding/profile/evidence hashes. Only the user can confirm, in /devflow control → 待审批; this tool has no confirm action. human_result proposals carry evidence only, never a verdict. Requires database authority.",
|
|
3812
|
+
parameters: approvalToolParameters(z),
|
|
3813
|
+
approval: args => (args.action === "list" ? "read" : "write"),
|
|
3814
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3815
|
+
const fail = (message: string, details: Record<string, unknown> = {}) => ({
|
|
3816
|
+
content: [{ type: "text" as const, text: message }],
|
|
3817
|
+
details: { action: params.action, error: message, ...details },
|
|
3818
|
+
isError: true,
|
|
3819
|
+
});
|
|
3820
|
+
try {
|
|
3821
|
+
if (!(APPROVAL_TOOL_ACTIONS as readonly string[]).includes(params.action)) {
|
|
3822
|
+
return fail(`devflow_approval only supports ${APPROVAL_TOOL_ACTIONS.join("|")}; approvals are confirmed by the user in /devflow control`);
|
|
3823
|
+
}
|
|
3824
|
+
const project = await loadProjectContext(ctx.cwd);
|
|
3825
|
+
const projectId = project.manifest.projectId;
|
|
3826
|
+
const store = await InitializationStore.open(project);
|
|
3827
|
+
try {
|
|
3828
|
+
if (databaseAuthority(store.db, projectId) !== "database") {
|
|
3829
|
+
return fail("approval_requires_database_authority: approvals are only supported when the project uses database authority", { code: "approval_requires_database_authority" });
|
|
3830
|
+
}
|
|
3831
|
+
if (params.action === "list") {
|
|
3832
|
+
const proposals = listProposals(store.db, projectId, { feature: params.feature, kind: params.kind, status: params.status ?? "proposed" })
|
|
3833
|
+
.map(item => ({ proposalId: item.id, feature: item.feature, kind: item.kind, subject: item.subjectKey, status: item.status, createdAt: item.createdAt, resolvedAt: item.resolvedAt, expired: item.status === "proposed" && proposalIsExpired(item) }));
|
|
3834
|
+
const approvals = listApprovals(store.db, projectId, { feature: params.feature, kind: params.kind })
|
|
3835
|
+
.map(item => ({ approvalId: item.id, proposalId: item.proposalId, feature: item.feature, kind: item.kind, subject: item.subjectKey, pins: item.pins, decision: item.decision, approvedAt: item.approvedAt }));
|
|
3836
|
+
const value = { proposals, approvals, note: "Approvals listed here are current records; validity is re-checked against current pins by the reader." };
|
|
3837
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }], details: value };
|
|
3838
|
+
}
|
|
3839
|
+
if (!params.feature || !params.kind || !params.items?.length) return fail("propose requires feature, kind and at least one item");
|
|
3840
|
+
const kind = params.kind as ApprovalKind;
|
|
3841
|
+
const subjects = new Set<string>();
|
|
3842
|
+
const prepared: Array<{ subject: string; request: Record<string, unknown>; agent: Record<string, unknown>; pins: Record<string, unknown>; stateRevision: number; display: Record<string, unknown> }> = [];
|
|
3843
|
+
const errors: string[] = [];
|
|
3844
|
+
for (const item of params.items as ApprovalProposeItem[]) {
|
|
3845
|
+
if (subjects.has(item.subject)) { errors.push(`${item.subject}: duplicate subject`); continue; }
|
|
3846
|
+
subjects.add(item.subject);
|
|
3847
|
+
let split: { request: Record<string, unknown>; agent: Record<string, unknown> };
|
|
3848
|
+
try { split = approvalProposeRequest(kind, item); } catch (error) { errors.push(`${item.subject}: ${error instanceof Error ? error.message : String(error)}`); continue; }
|
|
3849
|
+
const result = await runProcess(approvalPinsArgv(project.root, params.feature, kind, item.subject, split.request), project.root, { OMP_PROJECT_DIR: project.root });
|
|
3850
|
+
const pins = parseApprovalPinsOutput(result.stdout, result.stderr);
|
|
3851
|
+
if (!pins.ok) { errors.push(`${item.subject}: ${pins.errors.join("; ")}`); continue; }
|
|
3852
|
+
prepared.push({ subject: item.subject, ...split, pins: pins.pins, stateRevision: pins.stateRevision, display: pins.display });
|
|
3853
|
+
}
|
|
3854
|
+
// All-or-nothing: a partially recorded execution plan would be misleading to review.
|
|
3855
|
+
if (errors.length) return fail(`No proposal was recorded:\n${errors.join("\n")}`, { errors });
|
|
3856
|
+
// One immediate transaction: any store-level error rolls back every insert and every "expire older proposal" update.
|
|
3857
|
+
const recorded = proposeApprovals(store.db, prepared.map(item => ({
|
|
3858
|
+
projectId, feature: params.feature!, kind, subjectKey: item.subject,
|
|
3859
|
+
payload: { request: item.request, agent: item.agent, display: item.display },
|
|
3860
|
+
pins: item.pins, stateRevision: item.stateRevision,
|
|
3861
|
+
})));
|
|
3862
|
+
const created = prepared.map((item, index) => ({
|
|
3863
|
+
proposalId: recorded[index]!.proposalId, subject: item.subject, pins: item.pins, replacedProposals: recorded[index]!.expiredIds,
|
|
3864
|
+
}));
|
|
3865
|
+
const value = {
|
|
3866
|
+
kind, feature: params.feature, proposals: created,
|
|
3867
|
+
next: "Ask the user to review and decide in /devflow control → 待审批. You cannot confirm approvals; do not write verdicts or approval records yourself.",
|
|
3868
|
+
};
|
|
3869
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }], details: value };
|
|
3870
|
+
} finally { store.close(); }
|
|
3871
|
+
} catch (error) {
|
|
3872
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
3873
|
+
}
|
|
3874
|
+
},
|
|
3875
|
+
});
|
|
3876
|
+
|
|
3452
3877
|
pi.registerTool({
|
|
3453
3878
|
name: "devflow_runtime_context",
|
|
3454
3879
|
label: "Devflow Runtime Context",
|