@guanlin4924/devflow-pi 0.1.0
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 +164 -0
- package/docs/OMP-NATIVE-ARCHITECTURE.md +104 -0
- package/package.json +40 -0
- package/plugins/devflow-core/extensions/devflow-core.ts +1642 -0
- package/plugins/devflow-core/package.json +14 -0
- package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +174 -0
- package/plugins/devflow-core/runtime/hooks/hook_common.py +696 -0
- package/plugins/devflow-core/runtime/hooks/index_gate.py +63 -0
- package/plugins/devflow-core/runtime/hooks/injection_code_specification.py +7 -0
- package/plugins/devflow-core/runtime/hooks/post_tool.py +44 -0
- package/plugins/devflow-core/runtime/hooks/start.py +288 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +265 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/agents/openai.yaml +4 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +520 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +42 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +138 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +121 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +553 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/manage_lessons.rb +219 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +168 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/update_triage_route.rb +110 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +565 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +783 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/uat-remediation.yaml +143 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/workflows-README.md +10 -0
- package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +117 -0
- package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +167 -0
- package/plugins/devflow-core/skills/workflow-pipeline/references/slice-safety-guide.md +125 -0
- package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +819 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/agents-md-template.md +25 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-backend-spec-template.md +68 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-frontend-spec-template.md +64 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-navigation-template.md +168 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/context-domain-index-template.md +43 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/context-spec-index-template.md +45 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/execution-contract-template.md +57 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/navigation-index-template.md +40 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/project-initialization-template.md +109 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/rules-index-template.md +80 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/run-manifest-template.json +40 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/directory-structure.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/engine.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/errors.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/locator.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/model.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/repository.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/router.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/scheduler.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/schema.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/service.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/testing.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/api-client.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/case-editor.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/components.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/directory-structure.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/forms.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/routing.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/state.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/testing.md +46 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/stack-spec-template.md +106 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/state-template.md +77 -0
- package/plugins/devflow-core/skills/workflow-project-context/assets/toolchain-template.md +37 -0
- package/plugins/devflow-core/skills/workflow-project-context/references/navigation-guide.md +262 -0
- package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization.py +545 -0
- package/plugins/devflow-design/agents/contract-auditor.md +18 -0
- package/plugins/devflow-design/extensions/devflow-design.ts +47 -0
- package/plugins/devflow-design/package.json +14 -0
- package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +142 -0
- package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +150 -0
- package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +121 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +257 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/adr-template.md +57 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/backend-spec-template.md +205 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/affected-module-exploration.md +59 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/backend-conventions.md +30 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/code-explore-guide.md +40 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/project-rule-contract.md +66 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +91 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/index.md +71 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/accessibility.md +31 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/component-library.md +31 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/design-systems.md +47 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/responsive-grid.md +29 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/stack-guidelines.md +31 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/ux-patterns.md +40 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tokens/project-tokens.md +19 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/SKILL.md +377 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/charts.csv +26 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/colors.csv +97 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/icons.csv +101 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/landing.csv +31 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/products.csv +97 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/styles.csv +68 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/typography.csv +58 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/core.py +253 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/search.py +114 -0
- package/plugins/devflow-design/skills/workflow-design-rule-pack/scripts/install.py +67 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +370 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/admin-system-template.md +57 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/api-service-template.md +42 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/c-end-system-template.md +25 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/output-template.md +139 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/references/bug-rules.md +12 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/references/exception-checklist.md +31 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/references/trace-integrity-guide.md +153 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/references/writing-guide.md +92 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +223 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/assets/frontend-spec-template.md +204 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/affected-module-exploration.md +58 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/component-patterns.md +42 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-conventions.md +28 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/common-web.md +53 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/next.md +40 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/react.md +41 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/vue.md +41 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/project-rule-contract.md +95 -0
- package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +172 -0
- package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +302 -0
- package/plugins/devflow-design/skills/workflow-ui-spec/assets/ui-spec-template.md +244 -0
- package/plugins/devflow-design/skills/workflow-ui-spec/references/affected-module-exploration.md +55 -0
- package/plugins/devflow-implementation/agents/implementation-worker.md +20 -0
- package/plugins/devflow-implementation/package.json +6 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +124 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/INDEX.md +13 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/adversarial-review.md +35 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/first-principles.md +26 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/lesson-format.md +28 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/root-cause-ladder.md +51 -0
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/seam-analysis.md +30 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +160 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/code-conventions.md +33 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/ddl-guidelines.md +60 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +217 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/project-rule-contract.md +55 -0
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +271 -0
- package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +1251 -0
- package/plugins/devflow-implementation/skills/workflow-execute-phase/references/ui-brand.md +160 -0
- package/plugins/devflow-intel/agents/project-scout.md +27 -0
- package/plugins/devflow-intel/extensions/devflow-intel.ts +104 -0
- package/plugins/devflow-intel/package.json +14 -0
- package/plugins/devflow-intel/skills/code-index-query/SKILL.md +37 -0
- package/plugins/devflow-intel/skills/code-index-query/references/query-patterns.md +27 -0
- package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +68 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/adapter-contract.md +38 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/audit-checklist.md +10 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/confidence-rules.md +43 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/data-model.md +74 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/discovery.md +24 -0
- package/plugins/devflow-intel/skills/workflow-code-index/references/query-guide.md +20 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/__init__.py +1 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/base.py +30 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/generic_text.py +287 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/registry.py +17 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +437 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/__init__.py +1 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/discovery.py +139 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/hashing.py +11 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/scanner.py +115 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/schema.py +123 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/__init__.py +1 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/project_binding_check.py +66 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/relation_check.py +36 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/schema_validate.py +11 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/stale_check.py +42 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/__init__.py +1 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/jsonl_writer.py +25 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/markdown_writer.py +106 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/sqlite_writer.py +143 -0
- package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +381 -0
- package/plugins/devflow-intel/skills/workflow-context-sync/agents/openai.yaml +4 -0
- package/plugins/devflow-intel/skills/workflow-context-sync/scripts/context_sync_cursor.py +241 -0
- package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +186 -0
- package/plugins/devflow-intel/skills/workflow-update-navigation/references/incremental-update-guide.md +47 -0
- package/plugins/devflow-quality/agents/code-reviewer.md +18 -0
- package/plugins/devflow-quality/agents/verification-runner.md +21 -0
- package/plugins/devflow-quality/extensions/devflow-quality.ts +59 -0
- package/plugins/devflow-quality/package.json +14 -0
- package/plugins/devflow-quality/skills/code-review/SKILL.md +288 -0
- package/plugins/devflow-quality/skills/code-review/references/cross-layer-checklist.md +70 -0
- package/plugins/devflow-quality/skills/code-review/references/dao-checklist.md +70 -0
- package/plugins/devflow-quality/skills/code-review/references/handler-checklist.md +48 -0
- package/plugins/devflow-quality/skills/code-review/references/repository-checklist.md +44 -0
- package/plugins/devflow-quality/skills/code-review/references/service-checklist.md +57 -0
- package/plugins/devflow-quality/skills/code-review/references/spec-checklist.md +34 -0
- package/plugins/devflow-quality/skills/code-review/references/vo-checklist.md +26 -0
- package/plugins/devflow-quality/skills/workflow-audit-milestone/SKILL.md +31 -0
- package/plugins/devflow-quality/skills/workflow-audit-milestone/references/audit-milestone.md +328 -0
- package/plugins/devflow-quality/skills/workflow-complete-milestone/SKILL.md +132 -0
- package/plugins/devflow-quality/skills/workflow-complete-milestone/assets/milestone-archive.md +123 -0
- package/plugins/devflow-quality/skills/workflow-complete-milestone/references/complete-milestone.md +775 -0
- package/plugins/devflow-quality/skills/workflow-complete-milestone/references/milestone-archive.md +123 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +126 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +27 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +54 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +244 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +36 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +190 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +110 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +583 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/discover_codebases.py +199 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +177 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +370 -0
- package/plugins/devflow-quality/skills/workflow-verify-work/SKILL.md +106 -0
- package/plugins/devflow-quality/skills/workflow-verify-work/assets/UAT.md +265 -0
- package/plugins/devflow-quality/skills/workflow-verify-work/references/verify-work.md +759 -0
- package/plugins/devflow-requirements/agents/requirements-auditor.md +20 -0
- package/plugins/devflow-requirements/package.json +6 -0
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +219 -0
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/agents/openai.yaml +4 -0
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/references/review-method.md +257 -0
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/references/review-template.md +80 -0
- package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +325 -0
- package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +163 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +748 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/assets/baseline-template.md +281 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/antipatterns.md +69 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/challenge-review-guide.md +158 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/decomposition-guide.md +115 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/project-context-guide.md +119 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/questioning.md +76 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/requirement-review-checklist.md +115 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +204 -0
- package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +183 -0
- package/src/cli.ts +76 -0
- package/src/constants.ts +15 -0
- package/src/doctor.ts +66 -0
- package/src/paths.ts +37 -0
- package/src/platform-extension.ts +46 -0
- package/src/project-context.ts +165 -0
- package/src/runtime.ts +167 -0
- package/src/usage-stats.ts +101 -0
- package/templates/commands/start.md +58 -0
- package/templates/omp/AGENTS.md +36 -0
- package/templates/omp/config.yml +112 -0
- package/templates/omp/hook-config.json +18 -0
- package/templates/omp/index-policy.json +57 -0
|
@@ -0,0 +1,1642 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export type DevflowIntent = "idle" | "explain-state" | "project-init" | "requirement-draft" | "workflow-execute";
|
|
10
|
+
|
|
11
|
+
export type ControlPhase = "idle" | "planning" | "awaiting-approval" | "waiting-user" | "executing" | "blocked";
|
|
12
|
+
|
|
13
|
+
export type SessionControlState = {
|
|
14
|
+
intent: DevflowIntent;
|
|
15
|
+
phase: ControlPhase;
|
|
16
|
+
label: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ActiveExecutionContract = {
|
|
20
|
+
contractId: string;
|
|
21
|
+
feature: string;
|
|
22
|
+
step: string;
|
|
23
|
+
allowedPaths: string[];
|
|
24
|
+
allowedAgents: string[];
|
|
25
|
+
acceptance: string[];
|
|
26
|
+
onUnknown: "block_and_escalate";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const READ_ONLY_TOOLS = new Set([
|
|
30
|
+
"read",
|
|
31
|
+
"grep",
|
|
32
|
+
"find",
|
|
33
|
+
"glob",
|
|
34
|
+
"lsp",
|
|
35
|
+
"search",
|
|
36
|
+
"devflow_state",
|
|
37
|
+
"devflow_doctor",
|
|
38
|
+
"devflow_code_index",
|
|
39
|
+
"devflow_context_cursor",
|
|
40
|
+
"devflow_usage_stats",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const PLANNING_AGENTS = new Set(["project-scout", "requirements-auditor", "contract-auditor"]);
|
|
44
|
+
|
|
45
|
+
const STATE_MUTATION_TOOLS = new Set([
|
|
46
|
+
"devflow_bootstrap",
|
|
47
|
+
"devflow_transition",
|
|
48
|
+
"devflow_artifact",
|
|
49
|
+
"devflow_context_sync",
|
|
50
|
+
"devflow_evidence",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
export function initialControlState(): SessionControlState {
|
|
54
|
+
return { intent: "idle", phase: "idle", label: "对话(不自动执行)" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function classifyNaturalLanguageIntent(input: string): DevflowIntent | null {
|
|
58
|
+
const text = input.trim();
|
|
59
|
+
if (!text) return null;
|
|
60
|
+
if (/^(?:查看|看|解释|说明).{0,12}(?:状态|进度)|^(?:当前)?状态(?:是什么|如何|\?|?)?$/.test(text)) {
|
|
61
|
+
return "explain-state";
|
|
62
|
+
}
|
|
63
|
+
if (/^(?:初始化|新建).{0,8}(?:项目|工程)|^(?:项目|工程)初始化$/.test(text)) {
|
|
64
|
+
return "project-init";
|
|
65
|
+
}
|
|
66
|
+
if (/^(?:开始|新增|录入)?(?:需求)\s*[::]/.test(text) || /^(?:我有个)?需求[::]/.test(text)) {
|
|
67
|
+
return "requirement-draft";
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function planningState(intent: "project-init" | "requirement-draft"): SessionControlState {
|
|
73
|
+
return {
|
|
74
|
+
intent,
|
|
75
|
+
phase: "planning",
|
|
76
|
+
label: intent === "project-init" ? "项目初始化规划(只读)" : "需求梳理(只读)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function executingState(label = "当前工作流步骤执行"): SessionControlState {
|
|
81
|
+
return { intent: "workflow-execute", phase: "executing", label };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function waitingUserState(label = "等待用户确认"): SessionControlState {
|
|
85
|
+
return { intent: "workflow-execute", phase: "waiting-user", label };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function statusText(state: SessionControlState): string {
|
|
89
|
+
return `意图:${state.intent} | 模式:${state.label}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isPlanningToolAllowed(toolName: string, taskAgents: string[] = []): boolean {
|
|
93
|
+
const normalized = toolName.toLowerCase();
|
|
94
|
+
if (READ_ONLY_TOOLS.has(normalized)) return true;
|
|
95
|
+
if (normalized !== "task") return false;
|
|
96
|
+
return taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isStateMutationTool(toolName: string): boolean {
|
|
100
|
+
return STATE_MUTATION_TOOLS.has(toolName.toLowerCase());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function planningToolBlockReason(toolName: string): string {
|
|
104
|
+
return `Devflow ${toolName} blocked: current intent is planning. Planning is read-only; present an Evidence Pack or plan and wait for explicit approval before writing or executing.`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const DEVFLOW_CHINESE_LANGUAGE_POLICY = `
|
|
108
|
+
Devflow 语言策略(强制):
|
|
109
|
+
- 默认使用简体中文与用户沟通。
|
|
110
|
+
- 需求、计划、Evidence Pack、Execution Contract、审查报告、验证报告和阻塞原因必须使用简体中文。
|
|
111
|
+
- 文件名、类名、函数名、接口路径、命令、代码、日志原文和模型/Agent 名称保持原样,不翻译标识符。
|
|
112
|
+
- 推理时先区分“事实、推断、未知、待用户确认”,不要用英文长篇解释替代证据。
|
|
113
|
+
- 如果引用英文源码或工具输出,保留原文并紧跟中文解释。
|
|
114
|
+
- 不要因为语言切换而扩大任务范围;未知项用中文明确写出并停止。
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
type ProcessResult = {
|
|
118
|
+
ok: boolean;
|
|
119
|
+
exitCode: number;
|
|
120
|
+
stdout: string;
|
|
121
|
+
stderr: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type DoctorStatus = "pass" | "warn" | "fail";
|
|
125
|
+
|
|
126
|
+
type DoctorCheck = {
|
|
127
|
+
id: string;
|
|
128
|
+
status: DoctorStatus;
|
|
129
|
+
message: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type DoctorReport = {
|
|
133
|
+
ok: boolean;
|
|
134
|
+
checks: DoctorCheck[];
|
|
135
|
+
models: Record<string, string>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type ModelLike = {
|
|
139
|
+
provider?: unknown;
|
|
140
|
+
id?: unknown;
|
|
141
|
+
name?: unknown;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
type DoctorContext = {
|
|
145
|
+
cwd: string;
|
|
146
|
+
models: {
|
|
147
|
+
resolve(spec: string): ModelLike | undefined;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const CONTROL_PATHS = [
|
|
152
|
+
".planning/STATE.md",
|
|
153
|
+
".planning/.next-intent",
|
|
154
|
+
".planning/.devflow.lock",
|
|
155
|
+
".planning/artifacts/index.yaml",
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const CONTROL_PREFIXES = [".planning/workflows/", ".planning/contracts/"];
|
|
159
|
+
|
|
160
|
+
const REQUIRED_EXTENSION_TOOLS = [
|
|
161
|
+
"devflow_doctor",
|
|
162
|
+
"devflow_state",
|
|
163
|
+
"devflow_bootstrap",
|
|
164
|
+
"devflow_transition",
|
|
165
|
+
"devflow_artifact",
|
|
166
|
+
"devflow_skill_script",
|
|
167
|
+
"devflow_context_sync",
|
|
168
|
+
"devflow_code_index",
|
|
169
|
+
"devflow_context_cursor",
|
|
170
|
+
"devflow_design_rule",
|
|
171
|
+
"devflow_mainflow",
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const REQUIRED_SKILLS = [
|
|
175
|
+
"code-index-query",
|
|
176
|
+
"code-review",
|
|
177
|
+
"requirement-feasibility-review",
|
|
178
|
+
"workflow-api-call-guide",
|
|
179
|
+
"workflow-api-contract",
|
|
180
|
+
"workflow-api-supplement",
|
|
181
|
+
"workflow-audit-milestone",
|
|
182
|
+
"workflow-backend-tech-spec",
|
|
183
|
+
"workflow-bug-fix",
|
|
184
|
+
"workflow-change-plan",
|
|
185
|
+
"workflow-code-index",
|
|
186
|
+
"workflow-complete-milestone",
|
|
187
|
+
"workflow-context-sync",
|
|
188
|
+
"workflow-demand-clarify",
|
|
189
|
+
"workflow-design-rule-pack",
|
|
190
|
+
"workflow-domain-modeling",
|
|
191
|
+
"workflow-execute-phase",
|
|
192
|
+
"workflow-feature-breakdown",
|
|
193
|
+
"workflow-frontend-tech-spec",
|
|
194
|
+
"workflow-generate-mainflow-cases",
|
|
195
|
+
"workflow-integration-verify",
|
|
196
|
+
"workflow-orchestrator",
|
|
197
|
+
"workflow-pipeline",
|
|
198
|
+
"workflow-project-context",
|
|
199
|
+
"workflow-requirement-baseline",
|
|
200
|
+
"workflow-requirement-triage",
|
|
201
|
+
"workflow-ui-spec",
|
|
202
|
+
"workflow-update-navigation",
|
|
203
|
+
"workflow-verify-work",
|
|
204
|
+
"workflow-wayfinder",
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const PLUGIN_NAMES = [
|
|
208
|
+
"devflow-core",
|
|
209
|
+
"devflow-intel",
|
|
210
|
+
"devflow-requirements",
|
|
211
|
+
"devflow-design",
|
|
212
|
+
"devflow-implementation",
|
|
213
|
+
"devflow-quality",
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const REQUIRED_AGENTS: Record<string, { file: string; role: string }> = {
|
|
217
|
+
"project-scout": { file: "project-scout.md", role: "@smol" },
|
|
218
|
+
"requirements-auditor": { file: "requirements-auditor.md", role: "@slow" },
|
|
219
|
+
"contract-auditor": { file: "contract-auditor.md", role: "@slow" },
|
|
220
|
+
"implementation-worker": { file: "implementation-worker.md", role: "@task" },
|
|
221
|
+
"devflow-code-reviewer": { file: "code-reviewer.md", role: "@slow" },
|
|
222
|
+
"verification-runner": { file: "verification-runner.md", role: "@task" },
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const REQUIRED_MODEL_ROLES = ["@smol", "@task", "@slow"];
|
|
226
|
+
const REQUIRED_AGENT_NAMES = new Set(Object.keys(REQUIRED_AGENTS));
|
|
227
|
+
|
|
228
|
+
const REQUIRED_SCRIPTS = [
|
|
229
|
+
"workflow-orchestrator/scripts/init_workflow.rb",
|
|
230
|
+
"workflow-orchestrator/scripts/init_state.rb",
|
|
231
|
+
"workflow-orchestrator/scripts/inspect_state.rb",
|
|
232
|
+
"workflow-orchestrator/scripts/advance_state.rb",
|
|
233
|
+
"workflow-orchestrator/scripts/sync_artifacts.rb",
|
|
234
|
+
"workflow-code-index/scripts/code_index.py",
|
|
235
|
+
"workflow-context-sync/scripts/context_sync_cursor.py",
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
function resolvePluginRoot(): string {
|
|
239
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
240
|
+
return path.resolve(process.env.DEVFLOW_PLUGIN_ROOT || path.resolve(moduleDirectory, "../.."));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isEmbeddedRuntime(): boolean {
|
|
244
|
+
return process.env.DEVFLOW_RUNTIME_MODE === "embedded";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Embedded Devflow Pi never executes a target project's legacy `.skills`
|
|
249
|
+
* copy. Legacy OMP installation mode intentionally retains its old lookup
|
|
250
|
+
* order for backwards compatibility.
|
|
251
|
+
*/
|
|
252
|
+
function resolveKitRoot(cwd: string): string {
|
|
253
|
+
if (isEmbeddedRuntime()) {
|
|
254
|
+
const packaged = path.join(resolvePluginRoot(), "devflow-core", "skills");
|
|
255
|
+
if (existsSync(path.join(packaged, "workflow-orchestrator", "scripts", "inspect_state.rb"))) {
|
|
256
|
+
return packaged;
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`Embedded Devflow Pi core skills are missing: ${packaged}`);
|
|
259
|
+
}
|
|
260
|
+
const configured = process.env.DEVFLOW_KIT_ROOT;
|
|
261
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
262
|
+
const pluginSkills = path.resolve(moduleDirectory, "../skills");
|
|
263
|
+
const repositoryRoot = path.resolve(moduleDirectory, "../../..");
|
|
264
|
+
const candidates = [configured, path.join(cwd, ".skills"), pluginSkills, repositoryRoot].filter(
|
|
265
|
+
Boolean,
|
|
266
|
+
) as string[];
|
|
267
|
+
for (const candidate of candidates) {
|
|
268
|
+
if (existsSync(path.join(candidate, "workflow-orchestrator", "scripts", "inspect_state.rb"))) {
|
|
269
|
+
return path.resolve(candidate);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
throw new Error(
|
|
273
|
+
"Devflow skill root not found. Keep the kit at <project>/.skills or set DEVFLOW_KIT_ROOT.",
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function devflowSkillRoots(cwd: string): string[] {
|
|
278
|
+
const pluginRoot = resolvePluginRoot();
|
|
279
|
+
const roots = isEmbeddedRuntime() ? [] : [path.join(cwd, ".skills")];
|
|
280
|
+
roots.push(...PLUGIN_NAMES.map(plugin => path.join(pluginRoot, plugin, "skills")));
|
|
281
|
+
return [...new Set(roots.map(root => path.resolve(root)))];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function assertRuntimeProjectBoundary(cwd: string): void {
|
|
285
|
+
const resolvedCwd = realpathSync(cwd);
|
|
286
|
+
const configured = process.env.DEVFLOW_PROJECT_ROOT;
|
|
287
|
+
if (configured && realpathSync(configured) !== resolvedCwd) {
|
|
288
|
+
throw new Error(`Devflow project-root mismatch: session=${resolvedCwd}, configured=${configured}`);
|
|
289
|
+
}
|
|
290
|
+
const planningRoot = path.join(resolvedCwd, ".planning");
|
|
291
|
+
if (existsSync(planningRoot) && lstatSync(planningRoot).isSymbolicLink()) {
|
|
292
|
+
throw new Error(`Refusing symlinked Devflow planning root: ${planningRoot}`);
|
|
293
|
+
}
|
|
294
|
+
for (const relative of [
|
|
295
|
+
".planning/STATE.md",
|
|
296
|
+
".planning/.devflow.lock",
|
|
297
|
+
".planning/workflows",
|
|
298
|
+
".planning/artifacts",
|
|
299
|
+
]) {
|
|
300
|
+
const target = path.join(resolvedCwd, relative);
|
|
301
|
+
try {
|
|
302
|
+
if (lstatSync(target).isSymbolicLink()) {
|
|
303
|
+
throw new Error(`Refusing symlinked Devflow control path: ${target}`);
|
|
304
|
+
}
|
|
305
|
+
const canonical = realpathSync(target);
|
|
306
|
+
if (!canonical.startsWith(`${resolvedCwd}${path.sep}`)) {
|
|
307
|
+
throw new Error(`Devflow control path escapes project boundary: ${canonical}`);
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function safeControlPath(cwd: string, raw: string | undefined, kind: "state" | "workflow"): string | undefined {
|
|
316
|
+
if (raw === undefined) return undefined;
|
|
317
|
+
if (path.isAbsolute(raw)) throw new Error(`${kind} path must be project-relative`);
|
|
318
|
+
const relative = normalizedProjectPath(raw, cwd);
|
|
319
|
+
const allowed =
|
|
320
|
+
kind === "state"
|
|
321
|
+
? relative === ".planning/STATE.md"
|
|
322
|
+
: Boolean(relative?.startsWith(".planning/workflows/") && relative.endsWith(".yaml"));
|
|
323
|
+
if (!relative || !allowed) throw new Error(`Invalid ${kind} control path: ${raw}`);
|
|
324
|
+
return relative;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveIntelSkillRoot(cwd: string): string | undefined {
|
|
328
|
+
if (isEmbeddedRuntime()) {
|
|
329
|
+
const packaged = path.join(resolvePluginRoot(), "devflow-intel", "skills");
|
|
330
|
+
return existsSync(path.join(packaged, "workflow-code-index/scripts/code_index.py")) ? packaged : undefined;
|
|
331
|
+
}
|
|
332
|
+
const candidates = [
|
|
333
|
+
path.join(cwd, ".skills"),
|
|
334
|
+
path.join(cwd, ".omp/plugins/node_modules/@devflow/omp-intel/skills"),
|
|
335
|
+
path.join(resolvePluginRoot(), "devflow-intel", "skills"),
|
|
336
|
+
];
|
|
337
|
+
return candidates.find(candidate =>
|
|
338
|
+
existsSync(path.join(candidate, "workflow-code-index/scripts/code_index.py")),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function requestedTaskAgents(input: unknown): { names: string[]; missing: boolean } {
|
|
343
|
+
if (!input || typeof input !== "object") return { names: [], missing: true };
|
|
344
|
+
const record = input as Record<string, unknown>;
|
|
345
|
+
const values: unknown[] = [];
|
|
346
|
+
if (Array.isArray(record.tasks)) {
|
|
347
|
+
values.push(...record.tasks.map(task => (task && typeof task === "object" ? (task as Record<string, unknown>).agent : undefined)));
|
|
348
|
+
} else {
|
|
349
|
+
values.push(record.agent);
|
|
350
|
+
}
|
|
351
|
+
const names = values.filter((value): value is string => typeof value === "string" && value.trim() !== "").map(value => value.trim());
|
|
352
|
+
return { names, missing: values.length === 0 || names.length !== values.length };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function agentShadowIssue(cwd: string, name: string): string | undefined {
|
|
356
|
+
const definition = REQUIRED_AGENTS[name];
|
|
357
|
+
if (!definition) return `agent ${name} is outside the Devflow allowlist`;
|
|
358
|
+
const packaged = PLUGIN_NAMES.map(plugin => path.join(resolvePluginRoot(), plugin, "agents", definition.file)).find(candidate => existsSync(candidate));
|
|
359
|
+
if (!packaged) return `packaged agent definition is missing for ${name}`;
|
|
360
|
+
const expected = readFileSync(packaged, "utf8");
|
|
361
|
+
const roots = [
|
|
362
|
+
path.join(cwd, ".omp", "agents"),
|
|
363
|
+
process.env.PI_CODING_AGENT_DIR ? path.join(process.env.PI_CODING_AGENT_DIR, "agents") : undefined,
|
|
364
|
+
].filter(Boolean) as string[];
|
|
365
|
+
for (const root of roots) {
|
|
366
|
+
if (!existsSync(root)) continue;
|
|
367
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
368
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
369
|
+
const candidate = path.join(root, entry.name);
|
|
370
|
+
const content = readFileSync(candidate, "utf8");
|
|
371
|
+
if (!new RegExp(`^name:\\s*["']?${name}["']?\\s*$`, "m").test(content)) continue;
|
|
372
|
+
if (content !== expected) return `${candidate} shadows packaged agent ${name} with different content`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function runProcess(
|
|
379
|
+
command: string[],
|
|
380
|
+
cwd: string,
|
|
381
|
+
env: Record<string, string | undefined> = {},
|
|
382
|
+
stdin?: string,
|
|
383
|
+
): Promise<ProcessResult> {
|
|
384
|
+
const processHandle = Bun.spawn(command, {
|
|
385
|
+
cwd,
|
|
386
|
+
env: { ...process.env, ...env },
|
|
387
|
+
stdin: stdin === undefined ? undefined : "pipe",
|
|
388
|
+
stdout: "pipe",
|
|
389
|
+
stderr: "pipe",
|
|
390
|
+
});
|
|
391
|
+
if (stdin !== undefined && processHandle.stdin) {
|
|
392
|
+
processHandle.stdin.write(stdin);
|
|
393
|
+
processHandle.stdin.end();
|
|
394
|
+
}
|
|
395
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
396
|
+
processHandle.exited,
|
|
397
|
+
new Response(processHandle.stdout).text(),
|
|
398
|
+
new Response(processHandle.stderr).text(),
|
|
399
|
+
]);
|
|
400
|
+
return {
|
|
401
|
+
ok: exitCode === 0,
|
|
402
|
+
exitCode,
|
|
403
|
+
stdout: stdout.trim(),
|
|
404
|
+
stderr: stderr.trim(),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function renderResult(result: ProcessResult, details: Record<string, unknown> = {}) {
|
|
409
|
+
const text = result.stdout || result.stderr || `Process exited with ${result.exitCode}`;
|
|
410
|
+
return {
|
|
411
|
+
content: [{ type: "text" as const, text }],
|
|
412
|
+
details: { ...details, exitCode: result.exitCode, stderr: result.stderr || undefined },
|
|
413
|
+
isError: !result.ok,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function registeredNames(value: unknown): Set<string> {
|
|
418
|
+
if (Array.isArray(value)) {
|
|
419
|
+
return new Set(
|
|
420
|
+
value
|
|
421
|
+
.map(item => {
|
|
422
|
+
if (typeof item === "string") return item;
|
|
423
|
+
if (!item || typeof item !== "object") return "";
|
|
424
|
+
const record = item as Record<string, unknown>;
|
|
425
|
+
return String(record.name ?? record.command ?? record.id ?? "");
|
|
426
|
+
})
|
|
427
|
+
.filter(Boolean),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (value && typeof value === "object") return new Set(Object.keys(value as Record<string, unknown>));
|
|
431
|
+
return new Set();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function modelIdentity(model: ModelLike): string | null {
|
|
435
|
+
const provider = typeof model.provider === "string" ? model.provider : "";
|
|
436
|
+
const id = typeof model.id === "string" ? model.id : "";
|
|
437
|
+
if (provider && id) return `${provider}/${id}`;
|
|
438
|
+
if (id) return id;
|
|
439
|
+
return typeof model.name === "string" && model.name ? model.name : null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function formatDoctorReport(report: DoctorReport): string {
|
|
443
|
+
const icon: Record<DoctorStatus, string> = { pass: "PASS", warn: "WARN", fail: "FAIL" };
|
|
444
|
+
const lines = [`Devflow doctor: ${report.ok ? "PASS" : "FAIL"}`];
|
|
445
|
+
for (const check of report.checks) {
|
|
446
|
+
lines.push(`[${icon[check.status]}] ${check.id}: ${check.message}`);
|
|
447
|
+
}
|
|
448
|
+
const modelEntries = Object.entries(report.models);
|
|
449
|
+
if (modelEntries.length > 0) {
|
|
450
|
+
lines.push(`models: ${modelEntries.map(([role, model]) => `${role}=${model}`).join(", ")}`);
|
|
451
|
+
}
|
|
452
|
+
return lines.join("\n");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function parseJsonResult(result: ProcessResult): Record<string, unknown> | null {
|
|
456
|
+
if (!result.ok || !result.stdout) return null;
|
|
457
|
+
try {
|
|
458
|
+
const parsed = JSON.parse(result.stdout);
|
|
459
|
+
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null;
|
|
460
|
+
} catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function compactStep(value: unknown): Record<string, unknown> | null {
|
|
466
|
+
if (!value || typeof value !== "object") return null;
|
|
467
|
+
const step = value as Record<string, unknown>;
|
|
468
|
+
return {
|
|
469
|
+
id: step.id,
|
|
470
|
+
label: step.label,
|
|
471
|
+
skill: step.skill,
|
|
472
|
+
action: step.action,
|
|
473
|
+
inputs: step.inputs,
|
|
474
|
+
outputs: Array.isArray(step.outputs)
|
|
475
|
+
? step.outputs.map(output => {
|
|
476
|
+
if (!output || typeof output !== "object") return output;
|
|
477
|
+
const record = output as Record<string, unknown>;
|
|
478
|
+
return {
|
|
479
|
+
path: record.path ?? record.resolved_path,
|
|
480
|
+
required: record.required,
|
|
481
|
+
exists: record.exists,
|
|
482
|
+
artifact_status: record.artifact_status,
|
|
483
|
+
};
|
|
484
|
+
})
|
|
485
|
+
: undefined,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "resume" | "continue"): string {
|
|
490
|
+
const selectedStep = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
|
|
491
|
+
const envelope = {
|
|
492
|
+
source,
|
|
493
|
+
action: snapshot.action,
|
|
494
|
+
revision: snapshot.revision,
|
|
495
|
+
feature: snapshot.current_feature,
|
|
496
|
+
phase: snapshot.current_phase,
|
|
497
|
+
checkpoint: snapshot.checkpoint,
|
|
498
|
+
step: selectedStep,
|
|
499
|
+
parallel_window: snapshot.parallel_window,
|
|
500
|
+
failure_branch: snapshot.failure_branch,
|
|
501
|
+
reason: snapshot.reason,
|
|
502
|
+
recovery: snapshot.recovery,
|
|
503
|
+
};
|
|
504
|
+
return [
|
|
505
|
+
"<devflow-runtime-directive>",
|
|
506
|
+
"Devflow 已通过启动预检。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
|
|
507
|
+
"先调用 devflow_state(action=\"inspect\") 重新确认 revision,再由 workflow-orchestrator 路由当前步骤。",
|
|
508
|
+
"只按 inspector 返回的当前 step 加载所需 Skill;不得调用 devflow_transition、不得推进下一步骤、不得替用户通过人工 checkpoint。",
|
|
509
|
+
"完成本步骤的真实产物后,报告证据、未决项和验证结果并停止,等待用户执行 /devflow advance。",
|
|
510
|
+
"当前步骤信封:",
|
|
511
|
+
JSON.stringify(envelope, null, 2),
|
|
512
|
+
"</devflow-runtime-directive>",
|
|
513
|
+
].join("\n");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function explainWorkflowSnapshot(snapshot: Record<string, unknown> | null): string {
|
|
517
|
+
if (!snapshot) return "Devflow 当前没有可解释的工作流状态。可使用 /devflow start <feature> 创建新 Feature。";
|
|
518
|
+
const step = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
|
|
519
|
+
const lines = [
|
|
520
|
+
`Feature:${String(snapshot.current_feature ?? "未开始")}`,
|
|
521
|
+
`状态:${String(snapshot.status ?? "unknown")}`,
|
|
522
|
+
`动作:${String(snapshot.action ?? "unknown")}`,
|
|
523
|
+
`Revision:${String(snapshot.revision ?? "unknown")}`,
|
|
524
|
+
];
|
|
525
|
+
if (step?.id) lines.push(`当前/下一步骤:${String(step.id)}${step.label ? `(${String(step.label)})` : ""}`);
|
|
526
|
+
if (snapshot.checkpoint) lines.push(`Checkpoint:${String(snapshot.checkpoint)}`);
|
|
527
|
+
if (snapshot.action === "workflow_complete") {
|
|
528
|
+
lines.push("结论:上一 Feature 已完成;可以使用 /devflow start <新功能名> 创建下一 Feature。");
|
|
529
|
+
} else {
|
|
530
|
+
lines.push("结论:这是只读解释;未执行任何状态迁移或文件写入。");
|
|
531
|
+
}
|
|
532
|
+
return lines.join("\n");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function normalizedProjectPath(raw: unknown, cwd: string): string | null {
|
|
536
|
+
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
537
|
+
const absolute = path.resolve(cwd, raw);
|
|
538
|
+
const relative = path.relative(cwd, absolute).replaceAll("\\", "/");
|
|
539
|
+
if (relative === ".." || relative.startsWith("../")) return null;
|
|
540
|
+
return relative.replace(/^\.\//, "");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function inputPaths(input: unknown, cwd: string): string[] {
|
|
544
|
+
if (!input || typeof input !== "object") return [];
|
|
545
|
+
const record = input as Record<string, unknown>;
|
|
546
|
+
const values = [record.file_path, record.filePath, record.path, record.target];
|
|
547
|
+
if (Array.isArray(record.paths)) values.push(...record.paths);
|
|
548
|
+
return values
|
|
549
|
+
.map(value => normalizedProjectPath(value, cwd))
|
|
550
|
+
.filter((value): value is string => value !== null);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isControlPath(filePath: string): boolean {
|
|
554
|
+
return CONTROL_PATHS.includes(filePath) || CONTROL_PREFIXES.some(prefix => filePath.startsWith(prefix));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function isDirectControlMutation(toolName: string, input: unknown, cwd: string): boolean {
|
|
558
|
+
if (!["write", "edit", "apply_patch", "notebook_edit"].includes(toolName)) return false;
|
|
559
|
+
return inputPaths(input, cwd).some(isControlPath);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function isShellControlMutation(toolName: string, input: unknown): boolean {
|
|
563
|
+
if (!["bash", "shell", "exec_command"].includes(toolName) || !input || typeof input !== "object") {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
const command = String((input as Record<string, unknown>).command ?? (input as Record<string, unknown>).cmd ?? "");
|
|
567
|
+
if (!command) return false;
|
|
568
|
+
const invokesWriter =
|
|
569
|
+
/workflow-orchestrator\/scripts\/(?:advance_state|init_state|init_workflow|sync_artifacts)\.rb[^\n]*--write/.test(
|
|
570
|
+
command,
|
|
571
|
+
);
|
|
572
|
+
const namesControlPath = [...CONTROL_PATHS, ...CONTROL_PREFIXES].some(item => command.includes(item));
|
|
573
|
+
const looksLikeWrite = /(?:^|[;&|]\s*)(?:rm|mv|cp|install|truncate)\b|(?:^|\s)(?:tee|sed\s+-i|perl\s+-i)\b|>{1,2}/.test(
|
|
574
|
+
command,
|
|
575
|
+
);
|
|
576
|
+
return invokesWriter || (namesControlPath && looksLikeWrite);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function shellMutationPaths(command: string, cwd: string): { mutating: boolean; paths: string[] } {
|
|
580
|
+
const mutating = /(?:\bsed\s+-i\b|\bperl\s+-i\b|\b(?:cp|mv|rm|install|truncate|tee)\b|(?:^|\s)>{1,2}\s*\S|\b(?:python3?|node|ruby)\b[^\n]*(?:writeFile|write_text|open\([^\n]*['\"]w|unlink|rename)\b)/i.test(command);
|
|
581
|
+
if (!mutating) return { mutating: false, paths: [] };
|
|
582
|
+
const candidates = command.match(/(?:^|\s|>)([A-Za-z0-9_./~-]+)(?=\s|$|2?>)/g) ?? [];
|
|
583
|
+
const paths = candidates
|
|
584
|
+
.map(value => value.replace(/^\s|^>/g, "").trim())
|
|
585
|
+
.filter(value => value && !["-i", "-f", "-r", "-p", "sudo"].includes(value))
|
|
586
|
+
.map(value => normalizedProjectPath(value, cwd))
|
|
587
|
+
.filter((value): value is string => value !== null && value !== "");
|
|
588
|
+
return { mutating: true, paths };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export default function devflowCore(pi: ExtensionAPI) {
|
|
592
|
+
const { z } = pi.zod;
|
|
593
|
+
let sessionId = randomUUID();
|
|
594
|
+
let controlState: SessionControlState = initialControlState();
|
|
595
|
+
let activeContract: ActiveExecutionContract | undefined;
|
|
596
|
+
let toolCalls = 0;
|
|
597
|
+
const toolCallBudget = 40;
|
|
598
|
+
|
|
599
|
+
function setControlState(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }, state: SessionControlState) {
|
|
600
|
+
if (state.phase === "executing" && controlState.phase !== "executing") toolCalls = 0;
|
|
601
|
+
controlState = state;
|
|
602
|
+
ctx.ui.setStatus("devflow-control", statusText(state));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function normalizeContractSegment(value: string, label: string): string {
|
|
606
|
+
const normalized = value.trim();
|
|
607
|
+
if (!/^[\p{L}\p{N}][\p{L}\p{N}._-]{0,119}$/u.test(normalized)) {
|
|
608
|
+
throw new Error(`${label} must be 1-120 letters, numbers, dot, underscore, or hyphen without path separators`);
|
|
609
|
+
}
|
|
610
|
+
return normalized;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function contractAllowsPath(contract: ActiveExecutionContract, filePath: string): boolean {
|
|
614
|
+
return contract.allowedPaths.some(root => filePath === root || filePath.startsWith(`${root}/`));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function persistContract(cwd: string, contract: ActiveExecutionContract): Promise<string> {
|
|
618
|
+
assertRuntimeProjectBoundary(cwd);
|
|
619
|
+
const feature = normalizeContractSegment(contract.feature, "feature");
|
|
620
|
+
const contractId = normalizeContractSegment(contract.contractId, "contractId");
|
|
621
|
+
const root = path.join(cwd, ".planning", "contracts", feature);
|
|
622
|
+
const destination = path.join(root, `${contractId}.json`);
|
|
623
|
+
await mkdir(root, { recursive: true });
|
|
624
|
+
const payload = JSON.stringify({ schemaVersion: 1, activatedAt: new Date().toISOString(), ...contract }, null, 2) + "\n";
|
|
625
|
+
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
626
|
+
await writeFile(temporary, payload, "utf8");
|
|
627
|
+
await rename(temporary, destination);
|
|
628
|
+
return path.relative(cwd, destination).replaceAll("\\", "/");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async function collectProjectInitialization(ctx: {
|
|
632
|
+
ui: {
|
|
633
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
634
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
635
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
636
|
+
};
|
|
637
|
+
}): Promise<string | null> {
|
|
638
|
+
const projectName = await ctx.ui.input("Devflow 项目初始化", "项目名称(留空则使用当前目录)");
|
|
639
|
+
const frontendPath = await ctx.ui.input("前端边界", "前端目录;没有请填写 none");
|
|
640
|
+
if (frontendPath === undefined) return null;
|
|
641
|
+
const backendPath = await ctx.ui.input("后端边界", "后端目录;没有请填写 none");
|
|
642
|
+
if (backendPath === undefined) return null;
|
|
643
|
+
const buildIndex = await ctx.ui.input("代码索引", "建立索引?输入 yes 或 no");
|
|
644
|
+
if (buildIndex === undefined) return null;
|
|
645
|
+
const summary = [
|
|
646
|
+
`项目名:${projectName?.trim() || "当前目录"}`,
|
|
647
|
+
`前端:${frontendPath.trim() || "未提供"}`,
|
|
648
|
+
`后端:${backendPath.trim() || "未提供"}`,
|
|
649
|
+
`建立代码索引:${buildIndex.trim() || "未提供"}`,
|
|
650
|
+
].join("\n");
|
|
651
|
+
const confirmed = await ctx.ui.confirm("确认项目初始化范围", `${summary}\n\n确认后只进入只读规划,不会创建工作流或写入项目文件。`);
|
|
652
|
+
if (!confirmed) {
|
|
653
|
+
ctx.ui.notify("项目初始化已取消;未写入任何文件。", "info");
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
return summary;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function runRuby(cwd: string, relativeScript: string, args: string[]) {
|
|
660
|
+
assertRuntimeProjectBoundary(cwd);
|
|
661
|
+
const kitRoot = resolveKitRoot(cwd);
|
|
662
|
+
return runProcess(["ruby", path.join(kitRoot, relativeScript), ...args], cwd, {
|
|
663
|
+
DEVFLOW_KIT_ROOT: kitRoot,
|
|
664
|
+
OMP_PROJECT_DIR: cwd,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function runHook(
|
|
669
|
+
cwd: string,
|
|
670
|
+
scriptName: string,
|
|
671
|
+
event: Record<string, unknown>,
|
|
672
|
+
args: string[] = [],
|
|
673
|
+
) {
|
|
674
|
+
assertRuntimeProjectBoundary(cwd);
|
|
675
|
+
const kitRoot = resolveKitRoot(cwd);
|
|
676
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
677
|
+
const candidates = [
|
|
678
|
+
path.resolve(moduleDirectory, "../runtime/hooks", scriptName),
|
|
679
|
+
path.join(kitRoot, "templates", "hooks", scriptName),
|
|
680
|
+
path.resolve(kitRoot, "../runtime/hooks", scriptName),
|
|
681
|
+
];
|
|
682
|
+
const script = candidates.find(candidate => existsSync(candidate));
|
|
683
|
+
if (!script) throw new Error(`Devflow hook not found: ${scriptName}`);
|
|
684
|
+
return runProcess(
|
|
685
|
+
["python3", script, ...args],
|
|
686
|
+
cwd,
|
|
687
|
+
{
|
|
688
|
+
DEVFLOW_KIT_ROOT: kitRoot,
|
|
689
|
+
DEVFLOW_INTEL_SKILL_ROOT: resolveIntelSkillRoot(cwd),
|
|
690
|
+
OMP_PROJECT_DIR: cwd,
|
|
691
|
+
},
|
|
692
|
+
JSON.stringify(event),
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function runDoctor(ctx: DoctorContext): Promise<DoctorReport> {
|
|
697
|
+
const checks: DoctorCheck[] = [];
|
|
698
|
+
const models: Record<string, string> = {};
|
|
699
|
+
const add = (id: string, status: DoctorStatus, message: string) => {
|
|
700
|
+
checks.push({ id, status, message });
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
let kitRoot: string | null = null;
|
|
704
|
+
try {
|
|
705
|
+
kitRoot = resolveKitRoot(ctx.cwd);
|
|
706
|
+
add("kit", "pass", `skill root resolved at ${kitRoot}`);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
add("kit", "fail", error instanceof Error ? error.message : String(error));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const toolNames = registeredNames(pi.getAllTools());
|
|
712
|
+
const missingTools = REQUIRED_EXTENSION_TOOLS.filter(name => !toolNames.has(name));
|
|
713
|
+
add(
|
|
714
|
+
"extensions",
|
|
715
|
+
missingTools.length === 0 ? "pass" : "fail",
|
|
716
|
+
missingTools.length === 0
|
|
717
|
+
? "all four runtime extensions registered their required tools"
|
|
718
|
+
: `missing runtime tools: ${missingTools.join(", ")}; run the full installer and restart OMP`,
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
assertRuntimeProjectBoundary(ctx.cwd);
|
|
723
|
+
add("project-boundary", "pass", `control plane is contained in ${realpathSync(ctx.cwd)}`);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
add("project-boundary", "fail", error instanceof Error ? error.message : String(error));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const configPath = path.join(ctx.cwd, ".omp", "config.yml");
|
|
729
|
+
if (isEmbeddedRuntime()) {
|
|
730
|
+
add("config", "pass", "embedded Devflow Pi owns the managed runtime profile");
|
|
731
|
+
} else if (!existsSync(configPath)) {
|
|
732
|
+
add("config", "fail", "missing .omp/config.yml; run bash .skills/install.sh --apply");
|
|
733
|
+
} else {
|
|
734
|
+
const config = readFileSync(configPath, "utf8");
|
|
735
|
+
const requiredConfigPatterns: Array<[string, RegExp]> = [
|
|
736
|
+
["project role storage", /modelRoleStorage:\s*project/],
|
|
737
|
+
["manual skill diagnostics", /enableSkillCommands:\s*true/],
|
|
738
|
+
["task model overrides", /agentModelOverrides:/],
|
|
739
|
+
...Object.entries(REQUIRED_AGENTS).map(
|
|
740
|
+
([agent, definition]) =>
|
|
741
|
+
[`${agent} -> ${definition.role}`, new RegExp(`${agent}:\\s*["']?${definition.role}`)] as [string, RegExp],
|
|
742
|
+
),
|
|
743
|
+
...PLUGIN_NAMES.map(
|
|
744
|
+
plugin =>
|
|
745
|
+
[
|
|
746
|
+
`${plugin} skill source`,
|
|
747
|
+
new RegExp(`-\\s+\\.skills/plugins/${plugin}/skills`),
|
|
748
|
+
] as [string, RegExp],
|
|
749
|
+
),
|
|
750
|
+
];
|
|
751
|
+
const missingConfig = requiredConfigPatterns
|
|
752
|
+
.filter(([, pattern]) => !pattern.test(config))
|
|
753
|
+
.map(([label]) => label);
|
|
754
|
+
add(
|
|
755
|
+
"config",
|
|
756
|
+
missingConfig.length === 0 ? "pass" : "fail",
|
|
757
|
+
missingConfig.length === 0
|
|
758
|
+
? "project profile contains Devflow skill sources and agent model overrides"
|
|
759
|
+
: `missing required project settings: ${missingConfig.join(", ")}; review then run installer with --force-config`,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (kitRoot) {
|
|
764
|
+
const skillRoots = devflowSkillRoots(ctx.cwd);
|
|
765
|
+
const missingSkills = REQUIRED_SKILLS.filter(skill => {
|
|
766
|
+
const candidates = skillRoots.map(root => path.join(root, skill, "SKILL.md"));
|
|
767
|
+
return !candidates.some(candidate => existsSync(candidate));
|
|
768
|
+
});
|
|
769
|
+
add(
|
|
770
|
+
"skills",
|
|
771
|
+
missingSkills.length === 0 ? "pass" : "fail",
|
|
772
|
+
missingSkills.length === 0
|
|
773
|
+
? `${REQUIRED_SKILLS.length} required skills are present`
|
|
774
|
+
: `missing skills: ${missingSkills.join(", ")}`,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
const missingScripts = REQUIRED_SCRIPTS.filter(
|
|
778
|
+
script => !skillRoots.some(root => existsSync(path.join(root, script))),
|
|
779
|
+
);
|
|
780
|
+
add(
|
|
781
|
+
"scripts",
|
|
782
|
+
missingScripts.length === 0 ? "pass" : "fail",
|
|
783
|
+
missingScripts.length === 0 ? "deterministic workflow scripts are present" : `missing scripts: ${missingScripts.join(", ")}`,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const embeddedAgentRoots = PLUGIN_NAMES.map(plugin => path.join(resolvePluginRoot(), plugin, "agents"));
|
|
788
|
+
const agentRoots = isEmbeddedRuntime() ? embeddedAgentRoots : [path.join(ctx.cwd, ".omp", "agents")];
|
|
789
|
+
const invalidAgents = Object.entries(REQUIRED_AGENTS).flatMap(([name, definition]) => {
|
|
790
|
+
const agentPath = agentRoots.map(root => path.join(root, definition.file)).find(candidate => existsSync(candidate));
|
|
791
|
+
if (!agentPath) return [`${name}: missing ${definition.file}`];
|
|
792
|
+
const content = readFileSync(agentPath, "utf8");
|
|
793
|
+
const hasName = new RegExp(`^name:\\s*["']?${name}["']?\\s*$`, "m").test(content);
|
|
794
|
+
const hasRole = new RegExp(`^model:\\s*["']?${definition.role}["']?\\s*$`, "m").test(content);
|
|
795
|
+
if (!hasName || !hasRole) return [`${name}: expected model ${definition.role}`];
|
|
796
|
+
const shadow = isEmbeddedRuntime() ? agentShadowIssue(ctx.cwd, name) : undefined;
|
|
797
|
+
return shadow ? [shadow] : [];
|
|
798
|
+
});
|
|
799
|
+
add(
|
|
800
|
+
"agents",
|
|
801
|
+
invalidAgents.length === 0 ? "pass" : "fail",
|
|
802
|
+
invalidAgents.length === 0 ? "all task agents are installed with the expected role aliases" : invalidAgents.join(", "),
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const resolvedModels: string[] = [];
|
|
806
|
+
for (const role of REQUIRED_MODEL_ROLES) {
|
|
807
|
+
let model: ModelLike | undefined;
|
|
808
|
+
try {
|
|
809
|
+
model = ctx.models.resolve(role);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
add(`model:${role}`, "fail", error instanceof Error ? error.message : String(error));
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const identity = model && modelIdentity(model);
|
|
815
|
+
if (!identity) {
|
|
816
|
+
add(
|
|
817
|
+
`model:${role}`,
|
|
818
|
+
"fail",
|
|
819
|
+
`role is unresolved; assign a concrete authenticated model to ${role} in the project model roles`,
|
|
820
|
+
);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
models[role] = identity;
|
|
824
|
+
resolvedModels.push(identity);
|
|
825
|
+
add(`model:${role}`, "pass", identity);
|
|
826
|
+
}
|
|
827
|
+
const uniqueModels = new Set(resolvedModels);
|
|
828
|
+
add(
|
|
829
|
+
"model-separation",
|
|
830
|
+
resolvedModels.length === REQUIRED_MODEL_ROLES.length && uniqueModels.size === REQUIRED_MODEL_ROLES.length
|
|
831
|
+
? "pass"
|
|
832
|
+
: "fail",
|
|
833
|
+
resolvedModels.length === REQUIRED_MODEL_ROLES.length && uniqueModels.size === REQUIRED_MODEL_ROLES.length
|
|
834
|
+
? "scout, implementation/verification, and adversarial review resolve to different models"
|
|
835
|
+
: "@smol, @task, and @slow must resolve to three different concrete models; silent parent-model fallback is not allowed",
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
const commandNames = registeredNames(pi.getCommands());
|
|
839
|
+
const skillCommands = REQUIRED_SKILLS.map(name => `skill:${name}`);
|
|
840
|
+
const missingCommands = skillCommands.filter(name => !commandNames.has(name));
|
|
841
|
+
add(
|
|
842
|
+
"skills-runtime",
|
|
843
|
+
missingCommands.length === 0 ? "pass" : "fail",
|
|
844
|
+
missingCommands.length === 0
|
|
845
|
+
? `${REQUIRED_SKILLS.length} skills are registered in the active OMP runtime`
|
|
846
|
+
: `active OMP runtime is missing skill commands: ${missingCommands.join(", ")}; do not start with --no-skills`,
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
return { ok: checks.every(check => check.status !== "fail"), checks, models };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function inspectWorkflow(cwd: string): Promise<{ result: ProcessResult; snapshot: Record<string, unknown> | null }> {
|
|
853
|
+
const result = await runRuby(cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]);
|
|
854
|
+
return { result, snapshot: parseJsonResult(result) };
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "resume" | "continue") {
|
|
858
|
+
await pi.sendUserMessage(workflowDirective(snapshot, source));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
pi.setLabel("Devflow Project Runtime");
|
|
862
|
+
|
|
863
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
864
|
+
sessionId = randomUUID();
|
|
865
|
+
const result = await runHook(ctx.cwd, "start.py", {
|
|
866
|
+
session_id: sessionId,
|
|
867
|
+
cwd: ctx.cwd,
|
|
868
|
+
source: "startup",
|
|
869
|
+
hook_event_name: "SessionStart",
|
|
870
|
+
});
|
|
871
|
+
if (!result.ok) {
|
|
872
|
+
ctx.ui.notify(`Devflow bootstrap failed: ${result.stderr || result.stdout}`, "error");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
ctx.ui.setStatus("devflow", "Devflow ready");
|
|
876
|
+
setControlState(ctx, initialControlState());
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
pi.on("before_agent_start", async event => ({
|
|
880
|
+
systemPrompt: [...event.systemPrompt, DEVFLOW_CHINESE_LANGUAGE_POLICY],
|
|
881
|
+
}));
|
|
882
|
+
|
|
883
|
+
pi.on("input", async (event, ctx) => {
|
|
884
|
+
if (event.source !== "interactive") return;
|
|
885
|
+
const intent = classifyNaturalLanguageIntent(event.text);
|
|
886
|
+
if (intent === "explain-state") {
|
|
887
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
888
|
+
ctx.ui.notify(
|
|
889
|
+
inspected.result.ok ? explainWorkflowSnapshot(inspected.snapshot) : `状态读取失败:${inspected.result.stderr || inspected.result.stdout}`,
|
|
890
|
+
inspected.result.ok ? "info" : "error",
|
|
891
|
+
);
|
|
892
|
+
setControlState(ctx, initialControlState());
|
|
893
|
+
return { handled: true };
|
|
894
|
+
}
|
|
895
|
+
if (intent === "project-init") {
|
|
896
|
+
const summary = await collectProjectInitialization(ctx);
|
|
897
|
+
if (!summary) return { handled: true };
|
|
898
|
+
const state = planningState("project-init");
|
|
899
|
+
setControlState(ctx, state);
|
|
900
|
+
return {
|
|
901
|
+
text: [
|
|
902
|
+
"<devflow-planner-directive>",
|
|
903
|
+
"用户已确认项目初始化的输入。你现在是 Planner,只允许只读分析和派发 project-scout / requirements-auditor / contract-auditor。",
|
|
904
|
+
"不得创建 .planning 文件、不得调用状态迁移、不得执行实现。",
|
|
905
|
+
"先输出:已知事实、待验证点、最少侦察任务和必须由用户决定的问题。没有证据时写 unknown。",
|
|
906
|
+
summary,
|
|
907
|
+
"</devflow-planner-directive>",
|
|
908
|
+
].join("\n"),
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
if (intent === "requirement-draft") {
|
|
912
|
+
const state = planningState("requirement-draft");
|
|
913
|
+
setControlState(ctx, state);
|
|
914
|
+
return {
|
|
915
|
+
text: [
|
|
916
|
+
"<devflow-planner-directive>",
|
|
917
|
+
"当前是需求梳理(只读)阶段。只允许澄清、收集证据、派发只读审查代理和输出计划草案。",
|
|
918
|
+
"不得开始工作流、不得写入产物、不得调用实现代理或状态迁移。",
|
|
919
|
+
"把用户目标、约束、未知项、反例和下一步最少问题分开列出;缺少证据时不得猜测。",
|
|
920
|
+
event.text,
|
|
921
|
+
"</devflow-planner-directive>",
|
|
922
|
+
].join("\n"),
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
928
|
+
toolCalls += 1;
|
|
929
|
+
if (toolCalls > toolCallBudget && controlState.phase === "executing") {
|
|
930
|
+
return {
|
|
931
|
+
block: true,
|
|
932
|
+
reason: `本步骤已达到工具调用上限(${toolCallBudget})。已自动停止,请检查证据后使用 /devflow advance 或重新 /devflow continue。`,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
const toolName = String(event.toolName).toLowerCase();
|
|
936
|
+
if (toolName === "task") {
|
|
937
|
+
const requested = requestedTaskAgents(event.input);
|
|
938
|
+
if (requested.missing) {
|
|
939
|
+
return {
|
|
940
|
+
block: true,
|
|
941
|
+
reason: "Devflow task calls must name an explicit role agent; parent-model/default-agent fallback is forbidden.",
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const disallowed = requested.names.filter(name => !REQUIRED_AGENT_NAMES.has(name));
|
|
945
|
+
if (disallowed.length > 0) {
|
|
946
|
+
return {
|
|
947
|
+
block: true,
|
|
948
|
+
reason: `Devflow task agent is outside the product allowlist: ${disallowed.join(", ")}`,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const shadows = requested.names.map(name => agentShadowIssue(ctx.cwd, name)).filter(Boolean);
|
|
952
|
+
if (shadows.length > 0) {
|
|
953
|
+
return { block: true, reason: `Devflow task agent shadow detected: ${shadows.join("; ")}` };
|
|
954
|
+
}
|
|
955
|
+
if (controlState.phase === "planning" && !isPlanningToolAllowed(toolName, requested.names)) {
|
|
956
|
+
return { block: true, reason: planningToolBlockReason(toolName) };
|
|
957
|
+
}
|
|
958
|
+
if (
|
|
959
|
+
activeContract &&
|
|
960
|
+
controlState.phase === "executing" &&
|
|
961
|
+
!requested.names.every(agent => activeContract?.allowedAgents.includes(agent))
|
|
962
|
+
) {
|
|
963
|
+
return { block: true, reason: `Active contract ${activeContract.contractId} only allows agents: ${activeContract.allowedAgents.join(", ")}` };
|
|
964
|
+
}
|
|
965
|
+
} else if (
|
|
966
|
+
controlState.phase === "planning" &&
|
|
967
|
+
toolName !== "devflow_contract" &&
|
|
968
|
+
!(toolName === "devflow_evidence" && String((event.input as Record<string, unknown> | undefined)?.action) !== "create") &&
|
|
969
|
+
!isPlanningToolAllowed(toolName)
|
|
970
|
+
) {
|
|
971
|
+
return { block: true, reason: planningToolBlockReason(toolName) };
|
|
972
|
+
}
|
|
973
|
+
if (isStateMutationTool(toolName)) {
|
|
974
|
+
return {
|
|
975
|
+
block: true,
|
|
976
|
+
reason:
|
|
977
|
+
"Devflow state transitions are user-command-only. Use /devflow start, /devflow advance, or /devflow checkpoint; an LLM may not advance delivery state directly.",
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
if (toolName.startsWith("devflow_")) return;
|
|
981
|
+
if (
|
|
982
|
+
activeContract &&
|
|
983
|
+
controlState.phase === "executing" &&
|
|
984
|
+
["write", "edit", "apply_patch", "notebook_edit"].includes(toolName)
|
|
985
|
+
) {
|
|
986
|
+
const paths = inputPaths(event.input, ctx.cwd);
|
|
987
|
+
if (paths.length === 0 || paths.some(filePath => !contractAllowsPath(activeContract!, filePath))) {
|
|
988
|
+
return {
|
|
989
|
+
block: true,
|
|
990
|
+
reason: `Active contract ${activeContract.contractId} only allows edits under: ${activeContract.allowedPaths.join(", ")}`,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (activeContract && controlState.phase === "executing" && ["bash", "shell", "exec_command"].includes(toolName)) {
|
|
995
|
+
const command = String((event.input as Record<string, unknown> | undefined)?.command ?? (event.input as Record<string, unknown> | undefined)?.cmd ?? "");
|
|
996
|
+
const shell = shellMutationPaths(command, ctx.cwd);
|
|
997
|
+
if (shell.mutating && (shell.paths.length === 0 || shell.paths.some(filePath => !contractAllowsPath(activeContract!, filePath)))) {
|
|
998
|
+
return {
|
|
999
|
+
block: true,
|
|
1000
|
+
reason: `Active contract ${activeContract.contractId} 不允许通过 Bash/脚本修改合同外路径;请使用合同允许路径,或先更新合同。`,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (isDirectControlMutation(toolName, event.input, ctx.cwd) || isShellControlMutation(toolName, event.input)) {
|
|
1005
|
+
return {
|
|
1006
|
+
block: true,
|
|
1007
|
+
reason:
|
|
1008
|
+
"Devflow control-plane files are single-writer resources. Use devflow_bootstrap or devflow_transition instead of editing them directly.",
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const gate = await runHook(ctx.cwd, "index_gate.py", {
|
|
1012
|
+
session_id: sessionId,
|
|
1013
|
+
cwd: ctx.cwd,
|
|
1014
|
+
hook_event_name: "PreToolUse",
|
|
1015
|
+
tool_name: event.toolName,
|
|
1016
|
+
tool_input: event.input ?? {},
|
|
1017
|
+
});
|
|
1018
|
+
if (!gate.ok) {
|
|
1019
|
+
return {
|
|
1020
|
+
block: true,
|
|
1021
|
+
reason: `Devflow index gate failed closed: ${gate.stderr || gate.stdout || `exit ${gate.exitCode}`}`,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
if (gate.stdout) {
|
|
1025
|
+
try {
|
|
1026
|
+
const payload = JSON.parse(gate.stdout) as {
|
|
1027
|
+
hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string };
|
|
1028
|
+
};
|
|
1029
|
+
if (payload.hookSpecificOutput?.permissionDecision === "deny") {
|
|
1030
|
+
return {
|
|
1031
|
+
block: true,
|
|
1032
|
+
reason: payload.hookSpecificOutput.permissionDecisionReason || "Devflow index-first gate denied the tool call.",
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
} catch {
|
|
1036
|
+
return {
|
|
1037
|
+
block: true,
|
|
1038
|
+
reason: `Devflow index gate returned invalid JSON: ${gate.stdout.slice(0, 1000)}`,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
1045
|
+
if (event.isError) return;
|
|
1046
|
+
await runHook(ctx.cwd, "post_tool.py", {
|
|
1047
|
+
session_id: sessionId,
|
|
1048
|
+
cwd: ctx.cwd,
|
|
1049
|
+
hook_event_name: "PostToolUse",
|
|
1050
|
+
tool_name: event.toolName,
|
|
1051
|
+
tool_input: event.input ?? {},
|
|
1052
|
+
});
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
pi.on("session_stop", async (_event, ctx) => {
|
|
1056
|
+
const result = await runHook(ctx.cwd, "context_sync_gate.py", {
|
|
1057
|
+
session_id: sessionId,
|
|
1058
|
+
cwd: ctx.cwd,
|
|
1059
|
+
hook_event_name: "Stop",
|
|
1060
|
+
});
|
|
1061
|
+
if (!result.ok || !result.stdout) return;
|
|
1062
|
+
try {
|
|
1063
|
+
const decision = JSON.parse(result.stdout) as { decision?: string; reason?: string };
|
|
1064
|
+
if (decision.decision === "block" && decision.reason) {
|
|
1065
|
+
return {
|
|
1066
|
+
continue: true,
|
|
1067
|
+
additionalContext:
|
|
1068
|
+
`${decision.reason}\nUse skill://workflow-context-sync for the reconciliation procedure, then call ` +
|
|
1069
|
+
"devflow_context_sync(action=\"ack\", level=<reported-level>).",
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
} catch {
|
|
1073
|
+
ctx.ui.notify(`Devflow context-sync returned invalid JSON: ${result.stdout}`, "error");
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
pi.registerTool({
|
|
1078
|
+
name: "devflow_evidence",
|
|
1079
|
+
label: "Devflow Evidence Pack",
|
|
1080
|
+
description: "Create, inspect, and validate structured evidence packs used to prevent unsupported workflow progression.",
|
|
1081
|
+
parameters: z.object({
|
|
1082
|
+
action: z.enum(["create", "inspect", "validate"]),
|
|
1083
|
+
feature: z.string().min(1),
|
|
1084
|
+
taskId: z.string().min(1),
|
|
1085
|
+
pack: z.record(z.string(), z.unknown()).optional(),
|
|
1086
|
+
}),
|
|
1087
|
+
approval: (args) => args.action === "create" ? "write" : "read",
|
|
1088
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1089
|
+
const feature = normalizeContractSegment(params.feature, "feature");
|
|
1090
|
+
const taskId = normalizeContractSegment(params.taskId, "taskId");
|
|
1091
|
+
const root = path.join(ctx.cwd, ".planning", "evidence", feature);
|
|
1092
|
+
const destination = path.join(root, `${taskId}.json`);
|
|
1093
|
+
const validatePack = (pack: unknown): string[] => {
|
|
1094
|
+
if (!pack || typeof pack !== "object" || Array.isArray(pack)) return ["pack must be an object"];
|
|
1095
|
+
const record = pack as Record<string, unknown>;
|
|
1096
|
+
const errors: string[] = [];
|
|
1097
|
+
if (record.schemaVersion !== 1) errors.push("schemaVersion must equal 1");
|
|
1098
|
+
for (const key of ["facts", "unknowns", "risks"]) {
|
|
1099
|
+
if (!Array.isArray(record[key])) errors.push(`${key} must be an array`);
|
|
1100
|
+
}
|
|
1101
|
+
if (typeof record.taskId !== "string" || record.taskId !== taskId) errors.push("taskId must match the requested taskId");
|
|
1102
|
+
return errors;
|
|
1103
|
+
};
|
|
1104
|
+
if (params.action === "inspect" || params.action === "validate") {
|
|
1105
|
+
if (!existsSync(destination)) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: `Evidence Pack not found: ${path.relative(ctx.cwd, destination)}` }, { action: params.action });
|
|
1106
|
+
let parsed: unknown;
|
|
1107
|
+
try { parsed = JSON.parse(readFileSync(destination, "utf8")); } catch (error) {
|
|
1108
|
+
return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: `Invalid Evidence Pack JSON: ${String(error)}` }, { action: params.action });
|
|
1109
|
+
}
|
|
1110
|
+
const errors = validatePack(parsed);
|
|
1111
|
+
if (params.action === "validate") return renderResult({ ok: errors.length === 0, exitCode: errors.length === 0 ? 0 : 1, stdout: errors.length === 0 ? "Evidence Pack valid" : "", stderr: errors.join("; ") }, { action: params.action, errors });
|
|
1112
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(parsed, null, 2) }], details: { action: params.action, path: path.relative(ctx.cwd, destination), errors } };
|
|
1113
|
+
}
|
|
1114
|
+
if (!params.pack) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "pack is required for create" }, { action: params.action });
|
|
1115
|
+
const pack = { schemaVersion: 1, taskId, createdAt: new Date().toISOString(), ...params.pack };
|
|
1116
|
+
const errors = validatePack(pack);
|
|
1117
|
+
if (errors.length > 0) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: errors.join("; ") }, { action: params.action, errors });
|
|
1118
|
+
await mkdir(root, { recursive: true });
|
|
1119
|
+
await writeFile(destination, JSON.stringify(pack, null, 2) + "\n", "utf8");
|
|
1120
|
+
return { content: [{ type: "text" as const, text: `Evidence Pack created: ${path.relative(ctx.cwd, destination)}` }], details: { action: params.action, path: path.relative(ctx.cwd, destination) } };
|
|
1121
|
+
},
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
pi.registerTool({
|
|
1125
|
+
name: "devflow_contract",
|
|
1126
|
+
label: "Devflow Execution Contract",
|
|
1127
|
+
description: "Show or activate a user-confirmed, path-bounded execution contract. Only an active contract may authorize low-cost implementation agents.",
|
|
1128
|
+
parameters: z.object({
|
|
1129
|
+
action: z.enum(["status", "activate"]),
|
|
1130
|
+
contractId: z.string().min(1).optional(),
|
|
1131
|
+
feature: z.string().min(1).optional(),
|
|
1132
|
+
step: z.string().min(1).optional(),
|
|
1133
|
+
allowedPaths: z.array(z.string().min(1)).default([]),
|
|
1134
|
+
allowedAgents: z.array(z.enum(["implementation-worker", "verification-runner"])).default([]),
|
|
1135
|
+
acceptance: z.array(z.string().min(1)).default([]),
|
|
1136
|
+
}),
|
|
1137
|
+
approval: "write",
|
|
1138
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1139
|
+
const input = params as {
|
|
1140
|
+
action: "status" | "activate";
|
|
1141
|
+
contractId?: string;
|
|
1142
|
+
feature?: string;
|
|
1143
|
+
step?: string;
|
|
1144
|
+
allowedPaths: string[];
|
|
1145
|
+
allowedAgents: string[];
|
|
1146
|
+
acceptance: string[];
|
|
1147
|
+
};
|
|
1148
|
+
if (input.action === "status") {
|
|
1149
|
+
return {
|
|
1150
|
+
content: [{ type: "text" as const, text: activeContract ? JSON.stringify(activeContract, null, 2) : "No active execution contract in this session." }],
|
|
1151
|
+
details: { activeContract },
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
if (controlState.phase !== "planning") {
|
|
1155
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "Contracts may only be activated from the planning phase." });
|
|
1156
|
+
}
|
|
1157
|
+
if (!input.contractId || !input.feature || !input.step || input.allowedPaths.length === 0 || input.allowedAgents.length === 0 || input.acceptance.length === 0) {
|
|
1158
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "contractId, feature, step, allowedPaths, allowedAgents, and acceptance are required." });
|
|
1159
|
+
}
|
|
1160
|
+
let allowedPaths: string[];
|
|
1161
|
+
try {
|
|
1162
|
+
allowedPaths = input.allowedPaths.map(candidate => {
|
|
1163
|
+
const relative = normalizedProjectPath(candidate, ctx.cwd);
|
|
1164
|
+
if (!relative || relative.startsWith(".planning/")) throw new Error(`Invalid contract path: ${candidate}`);
|
|
1165
|
+
return relative.replace(/\/$/, "");
|
|
1166
|
+
});
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: error instanceof Error ? error.message : String(error) });
|
|
1169
|
+
}
|
|
1170
|
+
const contract: ActiveExecutionContract = {
|
|
1171
|
+
contractId: input.contractId,
|
|
1172
|
+
feature: input.feature,
|
|
1173
|
+
step: input.step,
|
|
1174
|
+
allowedPaths: [...new Set(allowedPaths)],
|
|
1175
|
+
allowedAgents: [...new Set(input.allowedAgents)],
|
|
1176
|
+
acceptance: [...new Set(input.acceptance)],
|
|
1177
|
+
onUnknown: "block_and_escalate",
|
|
1178
|
+
};
|
|
1179
|
+
const confirmed = await ctx.ui.confirm(
|
|
1180
|
+
"激活 Devflow 执行合同",
|
|
1181
|
+
[
|
|
1182
|
+
`合同:${contract.contractId}`,
|
|
1183
|
+
`Feature:${contract.feature}`,
|
|
1184
|
+
`步骤:${contract.step}`,
|
|
1185
|
+
`可写路径:${contract.allowedPaths.join(", ")}`,
|
|
1186
|
+
`允许 Agent:${contract.allowedAgents.join(", ")}`,
|
|
1187
|
+
`验收:${contract.acceptance.join(";")}`,
|
|
1188
|
+
"确认后低成本模型只能在以上范围执行;未知项必须阻塞升级。",
|
|
1189
|
+
].join("\n"),
|
|
1190
|
+
);
|
|
1191
|
+
if (!confirmed) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "User declined execution contract activation." });
|
|
1192
|
+
try {
|
|
1193
|
+
const destination = await persistContract(ctx.cwd, contract);
|
|
1194
|
+
activeContract = contract;
|
|
1195
|
+
setControlState(ctx, executingState(`合同执行:${contract.contractId}`));
|
|
1196
|
+
return {
|
|
1197
|
+
content: [{ type: "text" as const, text: `Execution contract activated: ${destination}` }],
|
|
1198
|
+
details: { contract, destination },
|
|
1199
|
+
};
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) });
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
pi.registerTool({
|
|
1207
|
+
name: "devflow_state",
|
|
1208
|
+
label: "Devflow State",
|
|
1209
|
+
description: "Inspect or validate the project delivery state without mutating it.",
|
|
1210
|
+
parameters: z.object({
|
|
1211
|
+
action: z.enum(["inspect", "validate"]),
|
|
1212
|
+
state: z.string().optional(),
|
|
1213
|
+
}),
|
|
1214
|
+
approval: "read",
|
|
1215
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1216
|
+
const script =
|
|
1217
|
+
params.action === "inspect"
|
|
1218
|
+
? "workflow-orchestrator/scripts/inspect_state.rb"
|
|
1219
|
+
: "workflow-orchestrator/scripts/validate_state.rb";
|
|
1220
|
+
const args = ["--json"];
|
|
1221
|
+
const state = safeControlPath(ctx.cwd, params.state, "state");
|
|
1222
|
+
if (state) args.push("--state", state);
|
|
1223
|
+
return renderResult(await runRuby(ctx.cwd, script, args), { action: params.action });
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
pi.registerTool({
|
|
1228
|
+
name: "devflow_doctor",
|
|
1229
|
+
label: "Devflow Doctor",
|
|
1230
|
+
description: "Verify the complete Devflow runtime, project profile, skills, agents, scripts, and distinct task-model bindings.",
|
|
1231
|
+
parameters: z.object({}),
|
|
1232
|
+
approval: "read",
|
|
1233
|
+
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
1234
|
+
const report = await runDoctor(ctx);
|
|
1235
|
+
return {
|
|
1236
|
+
content: [{ type: "text" as const, text: formatDoctorReport(report) }],
|
|
1237
|
+
details: report,
|
|
1238
|
+
isError: !report.ok,
|
|
1239
|
+
};
|
|
1240
|
+
},
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
pi.registerTool({
|
|
1244
|
+
name: "devflow_bootstrap",
|
|
1245
|
+
label: "Devflow Bootstrap",
|
|
1246
|
+
description: "Initialize workflow definitions, STATE.md, and the artifact registry for a project feature.",
|
|
1247
|
+
parameters: z.object({
|
|
1248
|
+
feature: z.string().min(1),
|
|
1249
|
+
phase: z.string().min(1).default("delivery"),
|
|
1250
|
+
flow: z.string().min(1).default("feature-development"),
|
|
1251
|
+
force: z.boolean().default(false),
|
|
1252
|
+
}),
|
|
1253
|
+
approval: "write",
|
|
1254
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1255
|
+
const force = params.force ? ["--force"] : [];
|
|
1256
|
+
const workflow = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
1257
|
+
"--write",
|
|
1258
|
+
"--json",
|
|
1259
|
+
...force,
|
|
1260
|
+
]);
|
|
1261
|
+
if (!workflow.ok) return renderResult(workflow, { stage: "workflow" });
|
|
1262
|
+
const state = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_state.rb", [
|
|
1263
|
+
"--feature",
|
|
1264
|
+
params.feature,
|
|
1265
|
+
"--phase",
|
|
1266
|
+
params.phase,
|
|
1267
|
+
"--flow",
|
|
1268
|
+
params.flow,
|
|
1269
|
+
"--write",
|
|
1270
|
+
"--json",
|
|
1271
|
+
...force,
|
|
1272
|
+
]);
|
|
1273
|
+
if (!state.ok) return renderResult(state, { stage: "state" });
|
|
1274
|
+
const artifacts = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", [
|
|
1275
|
+
"--write",
|
|
1276
|
+
"--json",
|
|
1277
|
+
]);
|
|
1278
|
+
return renderResult(artifacts, {
|
|
1279
|
+
stage: "complete",
|
|
1280
|
+
workflow: workflow.stdout,
|
|
1281
|
+
state: state.stdout,
|
|
1282
|
+
});
|
|
1283
|
+
},
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
pi.registerTool({
|
|
1287
|
+
name: "devflow_transition",
|
|
1288
|
+
label: "Devflow Transition",
|
|
1289
|
+
description: "Apply one validated delivery-state transition. This is the only supported STATE.md mutation path.",
|
|
1290
|
+
parameters: z.object({
|
|
1291
|
+
action: z.enum(["complete-step", "confirm-checkpoint", "checkpoint-decision", "enter-failure-branch"]),
|
|
1292
|
+
step: z.string().optional(),
|
|
1293
|
+
decision: z.string().optional(),
|
|
1294
|
+
state: z.string().optional(),
|
|
1295
|
+
expectedRevision: z.number().int().min(0),
|
|
1296
|
+
}),
|
|
1297
|
+
approval: "write",
|
|
1298
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1299
|
+
const args = ["--write", "--json"];
|
|
1300
|
+
const state = safeControlPath(ctx.cwd, params.state, "state");
|
|
1301
|
+
if (state) args.push("--state", state);
|
|
1302
|
+
args.push("--expected-revision", String(params.expectedRevision));
|
|
1303
|
+
if (params.action === "complete-step") {
|
|
1304
|
+
if (!params.step) {
|
|
1305
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "step is required" });
|
|
1306
|
+
}
|
|
1307
|
+
args.push("--complete-step", params.step);
|
|
1308
|
+
} else if (params.action === "confirm-checkpoint") {
|
|
1309
|
+
args.push("--confirm-checkpoint");
|
|
1310
|
+
} else if (params.action === "checkpoint-decision") {
|
|
1311
|
+
if (!params.decision) {
|
|
1312
|
+
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "decision is required" });
|
|
1313
|
+
}
|
|
1314
|
+
args.push("--checkpoint-decision", params.decision);
|
|
1315
|
+
} else {
|
|
1316
|
+
args.push("--enter-failure-branch");
|
|
1317
|
+
}
|
|
1318
|
+
return renderResult(
|
|
1319
|
+
await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", args),
|
|
1320
|
+
{ action: params.action },
|
|
1321
|
+
);
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
pi.registerTool({
|
|
1326
|
+
name: "devflow_artifact",
|
|
1327
|
+
label: "Devflow Artifact Registry",
|
|
1328
|
+
description: "Synchronize the workflow artifact registry from validated state and workflow definitions.",
|
|
1329
|
+
parameters: z.object({
|
|
1330
|
+
action: z.literal("sync"),
|
|
1331
|
+
state: z.string().optional(),
|
|
1332
|
+
workflow: z.string().optional(),
|
|
1333
|
+
preserve: z.boolean().default(true),
|
|
1334
|
+
}),
|
|
1335
|
+
approval: "write",
|
|
1336
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1337
|
+
const args = ["--write", "--json"];
|
|
1338
|
+
const state = safeControlPath(ctx.cwd, params.state, "state");
|
|
1339
|
+
const workflow = safeControlPath(ctx.cwd, params.workflow, "workflow");
|
|
1340
|
+
if (state) args.push("--state", state);
|
|
1341
|
+
if (workflow) args.push("--workflow", workflow);
|
|
1342
|
+
if (!params.preserve) args.push("--no-preserve");
|
|
1343
|
+
return renderResult(
|
|
1344
|
+
await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", args),
|
|
1345
|
+
{ action: params.action },
|
|
1346
|
+
);
|
|
1347
|
+
},
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
pi.registerTool({
|
|
1351
|
+
name: "devflow_skill_script",
|
|
1352
|
+
label: "Devflow Skill Script",
|
|
1353
|
+
description: "Run an allowlisted deterministic script bundled with a Devflow skill without relying on a .skills filesystem path.",
|
|
1354
|
+
parameters: z.object({
|
|
1355
|
+
script: z.enum([
|
|
1356
|
+
"project-context-validate",
|
|
1357
|
+
"orchestrator-manage-lessons",
|
|
1358
|
+
"orchestrator-update-triage",
|
|
1359
|
+
]),
|
|
1360
|
+
args: z.array(z.string()).default([]),
|
|
1361
|
+
}),
|
|
1362
|
+
approval: "exec",
|
|
1363
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1364
|
+
const kitRoot = resolveKitRoot(ctx.cwd);
|
|
1365
|
+
const scripts: Record<string, [string, string]> = {
|
|
1366
|
+
"project-context-validate": ["python3", "workflow-project-context/scripts/validate_initialization.py"],
|
|
1367
|
+
"orchestrator-manage-lessons": ["ruby", "workflow-orchestrator/scripts/manage_lessons.rb"],
|
|
1368
|
+
"orchestrator-update-triage": ["ruby", "workflow-orchestrator/scripts/update_triage_route.rb"],
|
|
1369
|
+
};
|
|
1370
|
+
const [runtime, relativeScript] = scripts[params.script];
|
|
1371
|
+
const result = await runProcess(
|
|
1372
|
+
[runtime, path.join(kitRoot, relativeScript), ...params.args],
|
|
1373
|
+
ctx.cwd,
|
|
1374
|
+
{ DEVFLOW_KIT_ROOT: kitRoot, OMP_PROJECT_DIR: ctx.cwd },
|
|
1375
|
+
);
|
|
1376
|
+
return renderResult(result, { script: params.script });
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
pi.registerTool({
|
|
1381
|
+
name: "devflow_context_sync",
|
|
1382
|
+
label: "Devflow Context Sync",
|
|
1383
|
+
description: "Acknowledge the exact context-sync change set after the required reconciliation and validation completed.",
|
|
1384
|
+
parameters: z.object({
|
|
1385
|
+
action: z.literal("ack"),
|
|
1386
|
+
level: z.enum(["Light", "Medium", "Full"]),
|
|
1387
|
+
}),
|
|
1388
|
+
approval: "write",
|
|
1389
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1390
|
+
const result = await runHook(
|
|
1391
|
+
ctx.cwd,
|
|
1392
|
+
"context_sync_gate.py",
|
|
1393
|
+
{ session_id: sessionId, cwd: ctx.cwd },
|
|
1394
|
+
["mark-synced", "--session-id", sessionId, "--level", params.level],
|
|
1395
|
+
);
|
|
1396
|
+
return renderResult(result, { action: params.action, level: params.level });
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
pi.registerCommand("devflow", {
|
|
1401
|
+
description: "Devflow runtime: inspect, initialize, plan, run one step, or explicitly advance the controlled delivery pipeline",
|
|
1402
|
+
handler: async (args, ctx) => {
|
|
1403
|
+
const trimmed = args.trim();
|
|
1404
|
+
if (trimmed === "" || trimmed === "status") {
|
|
1405
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]);
|
|
1406
|
+
ctx.ui.notify((result.stdout || result.stderr).slice(0, 6000), result.ok ? "info" : "error");
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (trimmed === "explain-state") {
|
|
1410
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1411
|
+
ctx.ui.notify(
|
|
1412
|
+
inspected.result.ok ? explainWorkflowSnapshot(inspected.snapshot) : `状态读取失败:${inspected.result.stderr || inspected.result.stdout}`,
|
|
1413
|
+
inspected.result.ok ? "info" : "error",
|
|
1414
|
+
);
|
|
1415
|
+
setControlState(ctx, initialControlState());
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (trimmed === "project-init") {
|
|
1419
|
+
const summary = await collectProjectInitialization(ctx);
|
|
1420
|
+
if (!summary) return;
|
|
1421
|
+
setControlState(ctx, planningState("project-init"));
|
|
1422
|
+
await pi.sendUserMessage(
|
|
1423
|
+
[
|
|
1424
|
+
"<devflow-planner-directive>",
|
|
1425
|
+
"用户已确认项目初始化的输入。你现在是 Planner,只允许只读分析和派发 project-scout / requirements-auditor / contract-auditor。",
|
|
1426
|
+
"不得创建 .planning 文件、不得调用状态迁移、不得执行实现。",
|
|
1427
|
+
"先输出:已知事实、待验证点、最少侦察任务和必须由用户决定的问题。没有证据时写 unknown。",
|
|
1428
|
+
summary,
|
|
1429
|
+
"</devflow-planner-directive>",
|
|
1430
|
+
].join("\n"),
|
|
1431
|
+
);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (trimmed === "doctor") {
|
|
1435
|
+
const report = await runDoctor(ctx);
|
|
1436
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), report.ok ? "info" : "error");
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (trimmed === "resume") {
|
|
1440
|
+
const report = await runDoctor(ctx);
|
|
1441
|
+
if (!report.ok) {
|
|
1442
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1446
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1447
|
+
ctx.ui.notify(
|
|
1448
|
+
`Devflow resume failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`,
|
|
1449
|
+
"error",
|
|
1450
|
+
);
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
if (inspected.snapshot.action === "workflow_complete") {
|
|
1454
|
+
ctx.ui.notify("Devflow workflow is already complete.", "info");
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
setControlState(ctx, waitingUserState("等待用户审查步骤产物"));
|
|
1458
|
+
ctx.ui.notify(
|
|
1459
|
+
`Devflow 已恢复到 ${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "当前步骤")};未自动执行。请先查看状态或使用 /devflow continue 执行一个步骤。`,
|
|
1460
|
+
"info",
|
|
1461
|
+
);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (trimmed === "continue") {
|
|
1465
|
+
const report = await runDoctor(ctx);
|
|
1466
|
+
if (!report.ok) {
|
|
1467
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1471
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1472
|
+
ctx.ui.notify(`Devflow continue failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (inspected.snapshot.action === "workflow_complete") {
|
|
1476
|
+
ctx.ui.notify("Devflow workflow is already complete.", "info");
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (inspected.snapshot.status === "waiting_checkpoint") {
|
|
1480
|
+
setControlState(ctx, { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" });
|
|
1481
|
+
ctx.ui.notify("当前步骤正在等待 checkpoint;请审查产物后使用 /devflow checkpoint。", "warning");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
setControlState(ctx, executingState("执行当前工作流步骤(完成后停止)"));
|
|
1485
|
+
ctx.ui.notify("仅启动当前步骤;模型被禁止迁移状态或继续下一步骤。", "info");
|
|
1486
|
+
await triggerWorkflow(inspected.snapshot, "continue");
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (trimmed === "advance") {
|
|
1490
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1491
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1492
|
+
ctx.ui.notify(`Devflow advance failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (inspected.snapshot.status === "waiting_checkpoint") {
|
|
1496
|
+
ctx.ui.notify("当前状态等待 checkpoint;请使用 /devflow checkpoint。", "warning");
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const step = compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step);
|
|
1500
|
+
const stepId = typeof step?.id === "string" ? step.id : undefined;
|
|
1501
|
+
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1502
|
+
if (!stepId || revision === undefined) {
|
|
1503
|
+
ctx.ui.notify("当前状态没有可推进的步骤或 revision;请先执行 /devflow explain-state。", "error");
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1507
|
+
"--write",
|
|
1508
|
+
"--json",
|
|
1509
|
+
"--expected-revision",
|
|
1510
|
+
String(revision),
|
|
1511
|
+
"--complete-step",
|
|
1512
|
+
stepId,
|
|
1513
|
+
]);
|
|
1514
|
+
if (!result.ok) {
|
|
1515
|
+
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
|
|
1516
|
+
ctx.ui.notify(`步骤未推进:${result.stderr || result.stdout}`, "error");
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const after = await inspectWorkflow(ctx.cwd);
|
|
1520
|
+
setControlState(
|
|
1521
|
+
ctx,
|
|
1522
|
+
after.snapshot?.status === "waiting_checkpoint"
|
|
1523
|
+
? { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" }
|
|
1524
|
+
: waitingUserState("等待用户审查步骤产物"),
|
|
1525
|
+
);
|
|
1526
|
+
ctx.ui.notify(`已完成步骤 ${stepId} 的真实产物校验并推进状态。${after.snapshot?.status === "waiting_checkpoint" ? "下一步需要 /devflow checkpoint。" : "下一步请审查后使用 /devflow continue。"}`, "info");
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (trimmed === "checkpoint") {
|
|
1530
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1531
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1532
|
+
ctx.ui.notify(`Devflow checkpoint failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (inspected.snapshot.status !== "waiting_checkpoint") {
|
|
1536
|
+
ctx.ui.notify("当前并不等待 checkpoint;请使用 /devflow explain-state 查看原因。", "warning");
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1540
|
+
if (revision === undefined) {
|
|
1541
|
+
ctx.ui.notify("当前状态缺少 revision,拒绝确认 checkpoint。", "error");
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
const confirmed = await ctx.ui.confirm("确认 Devflow checkpoint", "你已审查当前步骤产物并同意推进到下一步骤吗?");
|
|
1545
|
+
if (!confirmed) {
|
|
1546
|
+
ctx.ui.notify("checkpoint 未确认;状态保持不变。", "info");
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1550
|
+
"--write",
|
|
1551
|
+
"--json",
|
|
1552
|
+
"--expected-revision",
|
|
1553
|
+
String(revision),
|
|
1554
|
+
"--confirm-checkpoint",
|
|
1555
|
+
]);
|
|
1556
|
+
if (!result.ok) {
|
|
1557
|
+
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "checkpoint 校验失败" });
|
|
1558
|
+
ctx.ui.notify(`checkpoint 未推进:${result.stderr || result.stdout}`, "error");
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
setControlState(ctx, waitingUserState("等待用户确认下一步骤"));
|
|
1562
|
+
ctx.ui.notify("checkpoint 已由用户确认;下一步骤尚未执行,请使用 /devflow continue。", "info");
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (trimmed.startsWith("start ")) {
|
|
1566
|
+
const [feature, phase = "delivery"] = trimmed.slice(6).split("|", 2).map(value => value.trim());
|
|
1567
|
+
if (!feature) {
|
|
1568
|
+
ctx.ui.notify("Usage: /devflow start <feature>|<phase>", "error");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const report = await runDoctor(ctx);
|
|
1572
|
+
if (!report.ok) {
|
|
1573
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const existing = await inspectWorkflow(ctx.cwd);
|
|
1577
|
+
const existingFeature = existing.snapshot?.current_feature;
|
|
1578
|
+
const previousWorkflowComplete = existing.snapshot?.action === "workflow_complete";
|
|
1579
|
+
if (
|
|
1580
|
+
!previousWorkflowComplete &&
|
|
1581
|
+
typeof existingFeature === "string" &&
|
|
1582
|
+
existingFeature !== feature
|
|
1583
|
+
) {
|
|
1584
|
+
ctx.ui.notify(
|
|
1585
|
+
`An unfinished Devflow state belongs to feature ${existingFeature}. Use /devflow resume and complete that workflow before starting ${feature}.`,
|
|
1586
|
+
"error",
|
|
1587
|
+
);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const workflow = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
1591
|
+
"--write",
|
|
1592
|
+
"--json",
|
|
1593
|
+
]);
|
|
1594
|
+
if (!workflow.ok) {
|
|
1595
|
+
ctx.ui.notify(workflow.stderr || workflow.stdout, "error");
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const state = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_state.rb", [
|
|
1599
|
+
"--feature",
|
|
1600
|
+
feature,
|
|
1601
|
+
"--phase",
|
|
1602
|
+
phase,
|
|
1603
|
+
"--flow",
|
|
1604
|
+
"feature-development",
|
|
1605
|
+
"--write",
|
|
1606
|
+
"--json",
|
|
1607
|
+
...(previousWorkflowComplete ? ["--force"] : []),
|
|
1608
|
+
]);
|
|
1609
|
+
if (!state.ok) {
|
|
1610
|
+
ctx.ui.notify((state.stderr || state.stdout).slice(0, 6000), "error");
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
const artifacts = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", [
|
|
1614
|
+
"--write",
|
|
1615
|
+
"--json",
|
|
1616
|
+
]);
|
|
1617
|
+
if (!artifacts.ok) {
|
|
1618
|
+
ctx.ui.notify((artifacts.stderr || artifacts.stdout).slice(0, 6000), "error");
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1622
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1623
|
+
ctx.ui.notify(
|
|
1624
|
+
`Devflow initialized but inspector failed: ${inspected.result.stderr || inspected.result.stdout || "invalid JSON"}`,
|
|
1625
|
+
"error",
|
|
1626
|
+
);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
setControlState(ctx, planningState("requirement-draft"));
|
|
1630
|
+
ctx.ui.notify(
|
|
1631
|
+
`Devflow 已创建 Feature ${feature},但没有自动执行。当前步骤:${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "requirement-triage")}。请补充需求后使用 /devflow continue。`,
|
|
1632
|
+
"info",
|
|
1633
|
+
);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
ctx.ui.notify(
|
|
1637
|
+
"Usage: /devflow doctor | status | explain-state | project-init | start <feature>|<phase> | resume | continue | advance | checkpoint",
|
|
1638
|
+
"warning",
|
|
1639
|
+
);
|
|
1640
|
+
},
|
|
1641
|
+
});
|
|
1642
|
+
}
|