@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,437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import contextlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sqlite3
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Iterable, List, Set, Tuple
|
|
14
|
+
|
|
15
|
+
import fcntl
|
|
16
|
+
|
|
17
|
+
sys.dont_write_bytecode = True
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
20
|
+
if str(SCRIPT_DIR) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
22
|
+
|
|
23
|
+
from adapters.generic_text import language_for_path
|
|
24
|
+
from adapters.registry import AdapterRegistry
|
|
25
|
+
from core.discovery import discover_codebases
|
|
26
|
+
from core.hashing import file_sha256
|
|
27
|
+
from core.scanner import scan_files
|
|
28
|
+
from validators.project_binding_check import audit_project_binding
|
|
29
|
+
from validators.relation_check import audit_relations
|
|
30
|
+
from validators.schema_validate import validate_index
|
|
31
|
+
from validators.stale_check import audit_stale
|
|
32
|
+
from writers.jsonl_writer import read_jsonl, write_jsonl
|
|
33
|
+
from writers.markdown_writer import write_audit, write_markdown
|
|
34
|
+
from writers.sqlite_writer import write_sqlite
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
INDEX_FILES = {
|
|
38
|
+
"files": "files.jsonl",
|
|
39
|
+
"symbols": "symbols.jsonl",
|
|
40
|
+
"relations": "relations.jsonl",
|
|
41
|
+
"diagnostics": "diagnostics.jsonl",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
MARKDOWN_FILES = ["INDEX.md", "SYMBOLS.md", "RELATIONS.md", "ENTRYPOINTS.md", "DATAFLOW.md"]
|
|
45
|
+
SNAPSHOT_FILE = "SNAPSHOT.json"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextlib.contextmanager
|
|
49
|
+
def _index_lock(output: Path, exclusive: bool):
|
|
50
|
+
"""Serialize index publication and keep readers off a partially published snapshot."""
|
|
51
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
lock_path = output / ".index.lock"
|
|
53
|
+
with lock_path.open("a+") as handle:
|
|
54
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)
|
|
55
|
+
try:
|
|
56
|
+
yield
|
|
57
|
+
finally:
|
|
58
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _publish_snapshot(staging: Path, output: Path, counts: Dict[str, int]) -> None:
|
|
62
|
+
generation = str(uuid.uuid4())
|
|
63
|
+
snapshot = {
|
|
64
|
+
"schemaVersion": 1,
|
|
65
|
+
"generation": generation,
|
|
66
|
+
"generatedAt": datetime.now(timezone.utc).isoformat(),
|
|
67
|
+
"counts": counts,
|
|
68
|
+
"artifacts": [*INDEX_FILES.values(), "index.sqlite", *MARKDOWN_FILES],
|
|
69
|
+
}
|
|
70
|
+
snapshot_path = staging / SNAPSHOT_FILE
|
|
71
|
+
snapshot_path.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
72
|
+
with snapshot_path.open("rb") as handle:
|
|
73
|
+
os.fsync(handle.fileno())
|
|
74
|
+
|
|
75
|
+
# Readers in this module hold the shared lock. SNAPSHOT.json is published
|
|
76
|
+
# last and is therefore the commit marker for external consumers.
|
|
77
|
+
for name in snapshot["artifacts"]:
|
|
78
|
+
os.replace(staging / name, output / name)
|
|
79
|
+
os.replace(snapshot_path, output / SNAPSHOT_FILE)
|
|
80
|
+
directory_fd = os.open(output, os.O_RDONLY)
|
|
81
|
+
try:
|
|
82
|
+
os.fsync(directory_fd)
|
|
83
|
+
finally:
|
|
84
|
+
os.close(directory_fd)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main(argv=None) -> int:
|
|
88
|
+
parser = argparse.ArgumentParser(description="Build and validate a generic code intelligence index.")
|
|
89
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
90
|
+
|
|
91
|
+
scan = sub.add_parser("scan", help="Build or refresh the code index.")
|
|
92
|
+
scan.add_argument("--root", default=".", help="Project root. Defaults to current directory.")
|
|
93
|
+
scan.add_argument("--output", default=".planning/code-index", help="Index output directory.")
|
|
94
|
+
scan.add_argument("--codebase", action="append", default=[], help="Explicit codebase: name:path[:type]. Can repeat.")
|
|
95
|
+
scan.add_argument("--changed-files", nargs="*", default=[], help="Optional changed files for incremental refresh.")
|
|
96
|
+
scan.add_argument("--exclude", action="append", default=[], help="Additional directory names to exclude.")
|
|
97
|
+
|
|
98
|
+
validate = sub.add_parser("validate", help="Validate JSONL schema.")
|
|
99
|
+
validate.add_argument("--index", default=".planning/code-index", help="Index directory.")
|
|
100
|
+
|
|
101
|
+
audit = sub.add_parser("audit", help="Audit stale files and relation integrity.")
|
|
102
|
+
audit.add_argument("--root", default=".", help="Project root.")
|
|
103
|
+
audit.add_argument("--index", default=".planning/code-index", help="Index directory.")
|
|
104
|
+
audit.add_argument("--skill-root", default=".skills", help="Optional skill root for project-binding checks.")
|
|
105
|
+
|
|
106
|
+
query = sub.add_parser("query", help="Search symbols using SQLite FTS or LIKE fallback.")
|
|
107
|
+
query.add_argument("--index", default=".planning/code-index", help="Index directory.")
|
|
108
|
+
query.add_argument("--term", required=True, help="Search term.")
|
|
109
|
+
query.add_argument("--limit", type=int, default=20)
|
|
110
|
+
|
|
111
|
+
args = parser.parse_args(argv)
|
|
112
|
+
if args.command == "scan":
|
|
113
|
+
return command_scan(args)
|
|
114
|
+
if args.command == "validate":
|
|
115
|
+
return command_validate(args)
|
|
116
|
+
if args.command == "audit":
|
|
117
|
+
return command_audit(args)
|
|
118
|
+
if args.command == "query":
|
|
119
|
+
return command_query(args)
|
|
120
|
+
return 2
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def command_scan(args) -> int:
|
|
124
|
+
root = Path(args.root).resolve()
|
|
125
|
+
output = (root / args.output).resolve() if not Path(args.output).is_absolute() else Path(args.output).resolve()
|
|
126
|
+
with _index_lock(output, exclusive=True):
|
|
127
|
+
codebases = discover_codebases(root, args.codebase)
|
|
128
|
+
registry = AdapterRegistry()
|
|
129
|
+
changed = _normalize_changed_files(root, args.changed_files)
|
|
130
|
+
existing = _load_existing(output)
|
|
131
|
+
secondary_changed = _incoming_evidence_files(existing, changed)
|
|
132
|
+
changed |= secondary_changed
|
|
133
|
+
|
|
134
|
+
files, symbols, relations, diagnostics = _existing_after_removal(existing, changed)
|
|
135
|
+
indexed_paths = {record["path"] for record in files}
|
|
136
|
+
|
|
137
|
+
for codebase in codebases:
|
|
138
|
+
if not codebase.root.exists():
|
|
139
|
+
diagnostics.append(_diagnostic(codebase.name, "", 1, "warning", "skipped_file", f"codebase root does not exist: {codebase.root}", "discovery@1"))
|
|
140
|
+
continue
|
|
141
|
+
all_files = scan_files(codebase.root, excludes=args.exclude)
|
|
142
|
+
if changed:
|
|
143
|
+
all_files = [path for path in all_files if _workspace_rel(root, path) in changed]
|
|
144
|
+
manifests = [path for path in all_files if path.name in {"package.json", "go.mod", "Cargo.toml", "pyproject.toml", "requirements.txt", "composer.json", "pubspec.yaml"}]
|
|
145
|
+
adapters = registry.adapters_for(codebase, all_files, manifests)
|
|
146
|
+
adapter = adapters[0]
|
|
147
|
+
for path in all_files:
|
|
148
|
+
rel_path = _workspace_rel(root, path)
|
|
149
|
+
if rel_path in indexed_paths:
|
|
150
|
+
continue
|
|
151
|
+
try:
|
|
152
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
153
|
+
content_hash = file_sha256(path)
|
|
154
|
+
file_record = {
|
|
155
|
+
"id": f"file:{codebase.name}:{rel_path}",
|
|
156
|
+
"codebase": codebase.name,
|
|
157
|
+
"path": rel_path,
|
|
158
|
+
"language": language_for_path(path),
|
|
159
|
+
"size_bytes": path.stat().st_size,
|
|
160
|
+
"content_hash": content_hash,
|
|
161
|
+
"indexed_at": datetime.now(timezone.utc).isoformat(),
|
|
162
|
+
"parser": adapter.parser_id,
|
|
163
|
+
}
|
|
164
|
+
result = adapter.parse_file(codebase, file_record, text)
|
|
165
|
+
files.append(file_record)
|
|
166
|
+
symbols.extend(result.symbols)
|
|
167
|
+
relations.extend(result.relations)
|
|
168
|
+
diagnostics.extend(result.diagnostics)
|
|
169
|
+
except Exception as exc: # keep indexing useful even when one file fails
|
|
170
|
+
diagnostics.append(_diagnostic(codebase.name, rel_path, 1, "error", "parse_failed", str(exc), adapter.parser_id))
|
|
171
|
+
|
|
172
|
+
relations = _resolve_relations(symbols, relations)
|
|
173
|
+
files, symbols, relations, diagnostics = _dedupe(files, symbols, relations, diagnostics)
|
|
174
|
+
errors = validate_index(files, symbols, relations, diagnostics)
|
|
175
|
+
if errors:
|
|
176
|
+
for error in errors[:200]:
|
|
177
|
+
print(error, file=sys.stderr)
|
|
178
|
+
print("Index validation failed; the previous snapshot was preserved.", file=sys.stderr)
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
staging = Path(tempfile.mkdtemp(prefix=f".{output.name}.build-", dir=str(output.parent)))
|
|
182
|
+
try:
|
|
183
|
+
write_jsonl(staging / INDEX_FILES["files"], files)
|
|
184
|
+
write_jsonl(staging / INDEX_FILES["symbols"], symbols)
|
|
185
|
+
write_jsonl(staging / INDEX_FILES["relations"], relations)
|
|
186
|
+
write_jsonl(staging / INDEX_FILES["diagnostics"], diagnostics)
|
|
187
|
+
write_sqlite(staging / "index.sqlite", files, symbols, relations, diagnostics)
|
|
188
|
+
write_markdown(staging, files, symbols, relations, diagnostics)
|
|
189
|
+
staged_errors = validate_index(*_load_tuple(staging))
|
|
190
|
+
if staged_errors:
|
|
191
|
+
for error in staged_errors[:200]:
|
|
192
|
+
print(error, file=sys.stderr)
|
|
193
|
+
print("Staged index validation failed; the previous snapshot was preserved.", file=sys.stderr)
|
|
194
|
+
return 1
|
|
195
|
+
_publish_snapshot(
|
|
196
|
+
staging,
|
|
197
|
+
output,
|
|
198
|
+
{
|
|
199
|
+
"files": len(files),
|
|
200
|
+
"symbols": len(symbols),
|
|
201
|
+
"relations": len(relations),
|
|
202
|
+
"diagnostics": len(diagnostics),
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
finally:
|
|
206
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
207
|
+
|
|
208
|
+
print(f"Indexed {len(files)} files, {len(symbols)} symbols, {len(relations)} relations, {len(diagnostics)} diagnostics.")
|
|
209
|
+
print(f"Output: {output}")
|
|
210
|
+
return 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def command_validate(args) -> int:
|
|
214
|
+
output = Path(args.index).resolve()
|
|
215
|
+
with _index_lock(output, exclusive=False):
|
|
216
|
+
files, symbols, relations, diagnostics = _load_tuple(output)
|
|
217
|
+
errors = validate_index(files, symbols, relations, diagnostics)
|
|
218
|
+
if errors:
|
|
219
|
+
for error in errors:
|
|
220
|
+
print(error)
|
|
221
|
+
return 1
|
|
222
|
+
print("Code index schema validation passed.")
|
|
223
|
+
return 0
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def command_audit(args) -> int:
|
|
227
|
+
root = Path(args.root).resolve()
|
|
228
|
+
output = Path(args.index).resolve()
|
|
229
|
+
with _index_lock(output, exclusive=True):
|
|
230
|
+
files, symbols, relations, diagnostics = _load_tuple(output)
|
|
231
|
+
findings = []
|
|
232
|
+
findings.extend(audit_stale(root, files))
|
|
233
|
+
findings.extend(audit_relations(symbols, relations))
|
|
234
|
+
skill_root = Path(args.skill_root)
|
|
235
|
+
if skill_root.exists():
|
|
236
|
+
findings.extend(audit_project_binding([skill_root / "workflow-code-index", skill_root / "code-index-query"]))
|
|
237
|
+
write_audit(output, findings)
|
|
238
|
+
if findings:
|
|
239
|
+
for finding in findings[:200]:
|
|
240
|
+
location = f" {finding.get('path', '')}:{finding.get('line', '')}".rstrip(":")
|
|
241
|
+
print(f"{finding.get('severity', 'warning')}: {finding.get('kind', 'unknown')}: {finding.get('message', '')}{location}")
|
|
242
|
+
return 1
|
|
243
|
+
print("Code index audit passed.")
|
|
244
|
+
return 0
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def command_query(args) -> int:
|
|
248
|
+
output = Path(args.index).resolve()
|
|
249
|
+
with _index_lock(output, exclusive=False):
|
|
250
|
+
index = output / "index.sqlite"
|
|
251
|
+
if not index.exists():
|
|
252
|
+
print(f"Missing index: {index}", file=sys.stderr)
|
|
253
|
+
return 1
|
|
254
|
+
conn = sqlite3.connect(str(index))
|
|
255
|
+
conn.row_factory = sqlite3.Row
|
|
256
|
+
try:
|
|
257
|
+
try:
|
|
258
|
+
rows = conn.execute(
|
|
259
|
+
"""
|
|
260
|
+
SELECT id, codebase, kind, name, qualified_name, path, line_start, line_end
|
|
261
|
+
FROM symbols_fts
|
|
262
|
+
JOIN symbols USING(id)
|
|
263
|
+
WHERE symbols_fts MATCH ?
|
|
264
|
+
LIMIT ?
|
|
265
|
+
""",
|
|
266
|
+
(args.term, args.limit),
|
|
267
|
+
).fetchall()
|
|
268
|
+
except sqlite3.OperationalError:
|
|
269
|
+
like = f"%{args.term}%"
|
|
270
|
+
rows = conn.execute(
|
|
271
|
+
"""
|
|
272
|
+
SELECT id, codebase, kind, name, qualified_name, path, line_start, line_end
|
|
273
|
+
FROM symbols
|
|
274
|
+
WHERE name LIKE ? OR qualified_name LIKE ? OR path LIKE ?
|
|
275
|
+
LIMIT ?
|
|
276
|
+
""",
|
|
277
|
+
(like, like, like, args.limit),
|
|
278
|
+
).fetchall()
|
|
279
|
+
for row in rows:
|
|
280
|
+
print(json.dumps(dict(row), ensure_ascii=False, sort_keys=True))
|
|
281
|
+
finally:
|
|
282
|
+
conn.close()
|
|
283
|
+
return 0
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _load_tuple(output: Path):
|
|
287
|
+
return (
|
|
288
|
+
read_jsonl(output / INDEX_FILES["files"]),
|
|
289
|
+
read_jsonl(output / INDEX_FILES["symbols"]),
|
|
290
|
+
read_jsonl(output / INDEX_FILES["relations"]),
|
|
291
|
+
read_jsonl(output / INDEX_FILES["diagnostics"]),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _load_existing(output: Path) -> Dict[str, List[dict]]:
|
|
296
|
+
return {
|
|
297
|
+
"files": read_jsonl(output / INDEX_FILES["files"]),
|
|
298
|
+
"symbols": read_jsonl(output / INDEX_FILES["symbols"]),
|
|
299
|
+
"relations": read_jsonl(output / INDEX_FILES["relations"]),
|
|
300
|
+
"diagnostics": read_jsonl(output / INDEX_FILES["diagnostics"]),
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _normalize_changed_files(root: Path, values: Iterable[str]) -> Set[str]:
|
|
305
|
+
result: Set[str] = set()
|
|
306
|
+
for value in values:
|
|
307
|
+
path = Path(value)
|
|
308
|
+
if not path.is_absolute():
|
|
309
|
+
path = root / path
|
|
310
|
+
if path.exists():
|
|
311
|
+
result.add(_workspace_rel(root, path))
|
|
312
|
+
else:
|
|
313
|
+
try:
|
|
314
|
+
result.add(str(path.resolve().relative_to(root)).replace(os.sep, "/"))
|
|
315
|
+
except ValueError:
|
|
316
|
+
result.add(value.replace(os.sep, "/"))
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _incoming_evidence_files(existing: Dict[str, List[dict]], changed: Set[str]) -> Set[str]:
|
|
321
|
+
if not changed:
|
|
322
|
+
return set()
|
|
323
|
+
changed_symbol_ids = {symbol["id"] for symbol in existing["symbols"] if symbol.get("path") in changed}
|
|
324
|
+
return {
|
|
325
|
+
relation["evidence_path"]
|
|
326
|
+
for relation in existing["relations"]
|
|
327
|
+
if relation.get("to_id") in changed_symbol_ids and relation.get("evidence_path")
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _existing_after_removal(existing: Dict[str, List[dict]], changed: Set[str]):
|
|
332
|
+
if not changed:
|
|
333
|
+
return [], [], [], []
|
|
334
|
+
changed_symbol_ids = {symbol["id"] for symbol in existing["symbols"] if symbol.get("path") in changed}
|
|
335
|
+
files = [record for record in existing["files"] if record.get("path") not in changed]
|
|
336
|
+
symbols = [record for record in existing["symbols"] if record.get("path") not in changed]
|
|
337
|
+
relations = [
|
|
338
|
+
record
|
|
339
|
+
for record in existing["relations"]
|
|
340
|
+
if record.get("evidence_path") not in changed
|
|
341
|
+
and record.get("from_id") not in changed_symbol_ids
|
|
342
|
+
and record.get("to_id") not in changed_symbol_ids
|
|
343
|
+
]
|
|
344
|
+
diagnostics = [record for record in existing["diagnostics"] if record.get("path") not in changed]
|
|
345
|
+
return files, symbols, relations, diagnostics
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _resolve_relations(symbols: List[dict], relations: List[dict]) -> List[dict]:
|
|
349
|
+
by_name: Dict[Tuple[str, str], List[dict]] = {}
|
|
350
|
+
by_id = {symbol["id"]: symbol for symbol in symbols}
|
|
351
|
+
for symbol in symbols:
|
|
352
|
+
by_name.setdefault((symbol["codebase"], symbol["name"]), []).append(symbol)
|
|
353
|
+
|
|
354
|
+
resolved = []
|
|
355
|
+
for relation in relations:
|
|
356
|
+
if relation.get("to_id") or not relation.get("to_external", "").startswith("call:"):
|
|
357
|
+
resolved.append(relation)
|
|
358
|
+
continue
|
|
359
|
+
source = by_id.get(relation["from_id"])
|
|
360
|
+
if not source:
|
|
361
|
+
resolved.append(relation)
|
|
362
|
+
continue
|
|
363
|
+
name = relation["to_external"][5:]
|
|
364
|
+
candidates = by_name.get((source["codebase"], name), [])
|
|
365
|
+
if len(candidates) == 1:
|
|
366
|
+
upgraded = dict(relation)
|
|
367
|
+
upgraded["to_id"] = candidates[0]["id"]
|
|
368
|
+
upgraded["confidence"] = "inferred" if upgraded["confidence"] == "heuristic" else upgraded["confidence"]
|
|
369
|
+
upgraded.setdefault("metadata", {})["resolution"] = "unique_symbol_name_in_codebase"
|
|
370
|
+
resolved.append(upgraded)
|
|
371
|
+
else:
|
|
372
|
+
resolved.append(relation)
|
|
373
|
+
return resolved
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _dedupe(files, symbols, relations, diagnostics):
|
|
377
|
+
files = _dedupe_by(files, lambda record: record["id"])
|
|
378
|
+
symbols = _dedupe_by(symbols, lambda record: record["id"])
|
|
379
|
+
relations = _dedupe_by(
|
|
380
|
+
relations,
|
|
381
|
+
lambda record: (
|
|
382
|
+
record.get("from_id"),
|
|
383
|
+
record.get("relation_type"),
|
|
384
|
+
record.get("to_id"),
|
|
385
|
+
record.get("to_external"),
|
|
386
|
+
record.get("evidence_path"),
|
|
387
|
+
record.get("evidence_line"),
|
|
388
|
+
record.get("confidence"),
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
diagnostics = _dedupe_by(
|
|
392
|
+
diagnostics,
|
|
393
|
+
lambda record: (
|
|
394
|
+
record.get("codebase"),
|
|
395
|
+
record.get("path"),
|
|
396
|
+
record.get("line"),
|
|
397
|
+
record.get("severity"),
|
|
398
|
+
record.get("kind"),
|
|
399
|
+
record.get("message"),
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
return files, symbols, relations, diagnostics
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _dedupe_by(records, key_fn):
|
|
406
|
+
seen = set()
|
|
407
|
+
result = []
|
|
408
|
+
for record in records:
|
|
409
|
+
key = key_fn(record)
|
|
410
|
+
if key in seen:
|
|
411
|
+
continue
|
|
412
|
+
seen.add(key)
|
|
413
|
+
result.append(record)
|
|
414
|
+
return result
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _workspace_rel(root: Path, path: Path) -> str:
|
|
418
|
+
try:
|
|
419
|
+
return str(path.resolve().relative_to(root)).replace(os.sep, "/")
|
|
420
|
+
except ValueError:
|
|
421
|
+
return os.path.relpath(str(path.resolve()), str(root)).replace(os.sep, "/")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _diagnostic(codebase, path, line, severity, kind, message, parser):
|
|
425
|
+
return {
|
|
426
|
+
"codebase": codebase or "workspace",
|
|
427
|
+
"path": path,
|
|
428
|
+
"line": line,
|
|
429
|
+
"severity": severity,
|
|
430
|
+
"kind": kind,
|
|
431
|
+
"message": message,
|
|
432
|
+
"parser": parser,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Iterable, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Codebase:
|
|
9
|
+
name: str
|
|
10
|
+
root: Path
|
|
11
|
+
type_hint: str = ""
|
|
12
|
+
stack_hint: str = ""
|
|
13
|
+
navigation_path: str = ""
|
|
14
|
+
spec_path: str = ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def discover_codebases(root: Path, explicit: Iterable[str] = ()) -> List[Codebase]:
|
|
18
|
+
root = root.resolve()
|
|
19
|
+
explicit_codebases = [_parse_explicit(root, value) for value in explicit]
|
|
20
|
+
explicit_codebases = [item for item in explicit_codebases if item is not None]
|
|
21
|
+
if explicit_codebases:
|
|
22
|
+
return explicit_codebases
|
|
23
|
+
|
|
24
|
+
nav_codebases = _from_navigation(root)
|
|
25
|
+
if nav_codebases:
|
|
26
|
+
return nav_codebases
|
|
27
|
+
|
|
28
|
+
spec_codebases = _from_spec_index(root)
|
|
29
|
+
if spec_codebases:
|
|
30
|
+
return spec_codebases
|
|
31
|
+
|
|
32
|
+
return [Codebase(name=root.name or "workspace", root=root)]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_explicit(root: Path, value: str) -> Optional[Codebase]:
|
|
36
|
+
parts = value.split(":", 2)
|
|
37
|
+
if len(parts) < 2:
|
|
38
|
+
return None
|
|
39
|
+
name, raw_path = parts[0].strip(), parts[1].strip()
|
|
40
|
+
type_hint = parts[2].strip() if len(parts) == 3 else ""
|
|
41
|
+
path = Path(raw_path)
|
|
42
|
+
if not path.is_absolute():
|
|
43
|
+
path = root / path
|
|
44
|
+
return Codebase(name=name, root=path.resolve(), type_hint=type_hint)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _from_navigation(root: Path) -> List[Codebase]:
|
|
48
|
+
index = root / ".planning/navigation/INDEX.md"
|
|
49
|
+
if not index.exists():
|
|
50
|
+
return []
|
|
51
|
+
rows = _read_markdown_table(index)
|
|
52
|
+
result: List[Codebase] = []
|
|
53
|
+
for row in rows:
|
|
54
|
+
name = row.get("Name") or row.get("Codebase") or row.get("name")
|
|
55
|
+
raw_path = row.get("Path") or row.get("Root") or row.get("path")
|
|
56
|
+
if not name or not raw_path:
|
|
57
|
+
continue
|
|
58
|
+
path = _clean_cell(raw_path)
|
|
59
|
+
codebase_root = Path(path)
|
|
60
|
+
if not codebase_root.is_absolute():
|
|
61
|
+
codebase_root = root / codebase_root
|
|
62
|
+
nav = _clean_cell(row.get("Navigation", ""))
|
|
63
|
+
result.append(
|
|
64
|
+
Codebase(
|
|
65
|
+
name=_clean_cell(name),
|
|
66
|
+
root=codebase_root.resolve(),
|
|
67
|
+
type_hint=_clean_cell(row.get("Type", "")),
|
|
68
|
+
navigation_path=nav,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
return [item for item in result if item.root.exists()]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _from_spec_index(root: Path) -> List[Codebase]:
|
|
75
|
+
index = root / ".context/spec/index.md"
|
|
76
|
+
if not index.exists():
|
|
77
|
+
return []
|
|
78
|
+
rows = _read_markdown_table(index)
|
|
79
|
+
result: List[Codebase] = []
|
|
80
|
+
for row in rows:
|
|
81
|
+
name = row.get("Codebase") or row.get("Name")
|
|
82
|
+
nav = row.get("Navigation", "")
|
|
83
|
+
spec = row.get("Codebase Spec", "")
|
|
84
|
+
stack = row.get("Stack Spec", "")
|
|
85
|
+
if not name:
|
|
86
|
+
continue
|
|
87
|
+
nav_path = root / _clean_cell(nav)
|
|
88
|
+
codebase_root = _root_from_navigation_file(root, nav_path)
|
|
89
|
+
if codebase_root:
|
|
90
|
+
result.append(
|
|
91
|
+
Codebase(
|
|
92
|
+
name=_clean_cell(name),
|
|
93
|
+
root=codebase_root,
|
|
94
|
+
stack_hint=_clean_cell(stack),
|
|
95
|
+
navigation_path=_clean_cell(nav),
|
|
96
|
+
spec_path=_clean_cell(spec),
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _root_from_navigation_file(project_root: Path, path: Path) -> Optional[Path]:
|
|
103
|
+
if not path.exists():
|
|
104
|
+
return None
|
|
105
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
106
|
+
match = re.search(r"\|\s*Root\s*\|\s*`?([^`|\n]+)`?\s*\|", text)
|
|
107
|
+
if not match:
|
|
108
|
+
return None
|
|
109
|
+
raw = match.group(1).strip()
|
|
110
|
+
root = Path(raw)
|
|
111
|
+
if not root.is_absolute():
|
|
112
|
+
root = project_root / root
|
|
113
|
+
return root.resolve() if root.exists() else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _read_markdown_table(path: Path) -> List[dict]:
|
|
117
|
+
rows: List[dict] = []
|
|
118
|
+
header = None
|
|
119
|
+
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
120
|
+
stripped = line.strip()
|
|
121
|
+
if not stripped.startswith("|") or not stripped.endswith("|"):
|
|
122
|
+
continue
|
|
123
|
+
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
|
|
124
|
+
if all(re.match(r"^:?-{3,}:?$", cell) for cell in cells):
|
|
125
|
+
continue
|
|
126
|
+
if header is None:
|
|
127
|
+
header = cells
|
|
128
|
+
continue
|
|
129
|
+
if len(cells) == len(header):
|
|
130
|
+
rows.append(dict(zip(header, cells)))
|
|
131
|
+
return rows
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _clean_cell(value: str) -> str:
|
|
135
|
+
value = value.strip()
|
|
136
|
+
if value.startswith("`") and value.endswith("`"):
|
|
137
|
+
value = value[1:-1]
|
|
138
|
+
return value.strip()
|
|
139
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def file_sha256(path: Path) -> str:
|
|
6
|
+
digest = hashlib.sha256()
|
|
7
|
+
with path.open("rb") as handle:
|
|
8
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
9
|
+
digest.update(chunk)
|
|
10
|
+
return "sha256:" + digest.hexdigest()
|
|
11
|
+
|