agentic-flow-kit 1.8.2 → 2.0.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 +113 -148
- package/bin/flow-kit.mjs +5 -2
- package/locales/en/installer.json +25 -0
- package/locales/en/overrides/.agents/commands/deploy.md +52 -0
- package/locales/en/overrides/.agents/commands/maintain.md +76 -0
- package/locales/en/overrides/.agents/commands/new-task.md +89 -0
- package/locales/en/overrides/.agents/commands/pipeline-run.md +56 -0
- package/locales/en/overrides/.agents/commands/pr-review.md +30 -0
- package/locales/en/overrides/.agents/commands/review.md +93 -0
- package/locales/en/overrides/.agents/commands/stages.md +256 -0
- package/locales/en/overrides/.agents/notes/pr-review-cost-discipline.md +48 -0
- package/locales/en/overrides/.agents/notes/process-order-rule.md +21 -0
- package/locales/en/overrides/.agents/notes/runtime-env.local.example.md +21 -0
- package/locales/en/overrides/.agents/pipelines/_TEMPLATE.md +75 -0
- package/locales/en/overrides/.agents/pipelines/pipeline-closing.md +28 -0
- package/locales/en/overrides/.agents/roles/implementer.md +28 -0
- package/locales/en/overrides/.agents/roles/independent-reviewer.md +39 -0
- package/locales/en/overrides/.agents/roles/pr-adjudicator.md +27 -0
- package/locales/en/overrides/.agents/roles/pr-finder.md +51 -0
- package/locales/en/overrides/.agents/roles/pr-verifier.md +44 -0
- package/locales/en/overrides/.agents/roles/ui-verifier.md +22 -0
- package/locales/en/overrides/.agents/roles/wiki-compiler.md +47 -0
- package/locales/en/overrides/.agents/skills/arena/SKILL.md +50 -0
- package/locales/en/overrides/.agents/skills/autonomous-run/SKILL.md +45 -0
- package/locales/en/overrides/.agents/skills/blast-radius/SKILL.md +53 -0
- package/locales/en/overrides/.agents/skills/csharp-quality-gates/SKILL.md +109 -0
- package/locales/en/overrides/.agents/skills/dev-tour/SKILL.md +98 -0
- package/locales/en/overrides/.agents/skills/dream/SKILL.md +63 -0
- package/locales/en/overrides/.agents/skills/eval/SKILL.md +42 -0
- package/locales/en/overrides/.agents/skills/interrogate/SKILL.md +49 -0
- package/locales/en/overrides/.agents/skills/number-vetting/SKILL.md +35 -0
- package/locales/en/overrides/.agents/skills/page-refactor/SKILL.md +192 -0
- package/locales/en/overrides/.agents/skills/papercut-fix/SKILL.md +92 -0
- package/locales/en/overrides/.agents/skills/pause-safely/SKILL.md +31 -0
- package/locales/en/overrides/.agents/skills/perf/SKILL.md +42 -0
- package/locales/en/overrides/.agents/skills/review-method/SKILL.md +92 -0
- package/locales/en/overrides/.agents/skills/session-resume/SKILL.md +69 -0
- package/locales/en/overrides/.agents/skills/shipyard-workflow-closeout/SKILL.md +109 -0
- package/locales/en/overrides/.agents/skills/show-me-your-work/SKILL.md +57 -0
- package/locales/en/overrides/.agents/skills/tfs-pr/SKILL.md +95 -0
- package/locales/en/overrides/.agents/skills/verify-ui/SKILL.md +116 -0
- package/locales/en/overrides/.agents/skills/visual-parity/SKILL.md +35 -0
- package/locales/en/overrides/.agents/skills/why/SKILL.md +56 -0
- package/locales/en/overrides/.agents/skills/wiki/SKILL.md +53 -0
- package/locales/en/overrides/.claude/agents/implementer.md +9 -0
- package/locales/en/overrides/.claude/agents/independent-reviewer.md +10 -0
- package/locales/en/overrides/.claude/agents/pr-adjudicator.md +10 -0
- package/locales/en/overrides/.claude/agents/pr-finder.md +10 -0
- package/locales/en/overrides/.claude/agents/pr-verifier.md +10 -0
- package/locales/en/overrides/.claude/agents/ui-verifier.md +10 -0
- package/locales/en/overrides/.claude/agents/wiki-compiler.md +10 -0
- package/locales/en/overrides/.claude/commands/wf-deploy.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-maintain.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-new-task.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-pipeline-run.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-pr-review.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-review.md +8 -0
- package/locales/en/overrides/.claude/commands/wf-stages.md +8 -0
- package/locales/en/overrides/.cline/agents/implementer.yml +13 -0
- package/locales/en/overrides/.cline/agents/independent-reviewer.yml +11 -0
- package/locales/en/overrides/.cline/agents/pr-adjudicator.yml +11 -0
- package/locales/en/overrides/.cline/agents/pr-finder.yml +11 -0
- package/locales/en/overrides/.cline/agents/pr-verifier.yml +11 -0
- package/locales/en/overrides/.cline/agents/ui-verifier.yml +8 -0
- package/locales/en/overrides/.cline/agents/wiki-compiler.yml +14 -0
- package/locales/en/overrides/.cline/workflows/wf-deploy.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-maintain.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-new-task.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-pipeline-run.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-pr-review.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-review.md +8 -0
- package/locales/en/overrides/.cline/workflows/wf-stages.md +8 -0
- package/locales/en/overrides/.codebuddy/CODEBUDDY.md +7 -0
- package/locales/en/overrides/.codebuddy/agents/implementer.md +9 -0
- package/locales/en/overrides/.codebuddy/agents/independent-reviewer.md +10 -0
- package/locales/en/overrides/.codebuddy/agents/pr-adjudicator.md +10 -0
- package/locales/en/overrides/.codebuddy/agents/pr-finder.md +10 -0
- package/locales/en/overrides/.codebuddy/agents/pr-verifier.md +10 -0
- package/locales/en/overrides/.codebuddy/agents/ui-verifier.md +11 -0
- package/locales/en/overrides/.codebuddy/agents/wiki-compiler.md +10 -0
- package/locales/en/overrides/.codebuddy/commands/wf-deploy.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-maintain.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-new-task.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-pipeline-run.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-pr-review.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-review.md +8 -0
- package/locales/en/overrides/.codebuddy/commands/wf-stages.md +8 -0
- package/locales/en/overrides/.codebuddy/rules/backend-scope.md +5 -0
- package/locales/en/overrides/.codebuddy/rules/frontend-scope.md +5 -0
- package/locales/en/overrides/.codebuddy/skills/tfs-pr/SKILL.md +6 -0
- package/locales/en/overrides/.commandcode/agents/implementer.md +10 -0
- package/locales/en/overrides/.commandcode/agents/independent-reviewer.md +9 -0
- package/locales/en/overrides/.commandcode/agents/pr-adjudicator.md +9 -0
- package/locales/en/overrides/.commandcode/agents/pr-finder.md +9 -0
- package/locales/en/overrides/.commandcode/agents/pr-verifier.md +9 -0
- package/locales/en/overrides/.commandcode/agents/ui-verifier.md +10 -0
- package/locales/en/overrides/.commandcode/agents/wiki-compiler.md +10 -0
- package/locales/en/overrides/.commandcode/commands/wf-deploy.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-maintain.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-new-task.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-pipeline-run.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-pr-review.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-review.md +7 -0
- package/locales/en/overrides/.commandcode/commands/wf-stages.md +7 -0
- package/locales/en/overrides/.opencode/agents/implementer.md +51 -0
- package/locales/en/overrides/.opencode/agents/independent-reviewer.md +18 -0
- package/locales/en/overrides/.opencode/agents/pr-adjudicator.md +18 -0
- package/locales/en/overrides/.opencode/agents/pr-finder.md +18 -0
- package/locales/en/overrides/.opencode/agents/pr-verifier.md +18 -0
- package/locales/en/overrides/.opencode/agents/ui-verifier.md +45 -0
- package/locales/en/overrides/.opencode/agents/wiki-compiler.md +51 -0
- package/locales/en/overrides/.opencode/commands/wf-deploy.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-maintain.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-new-task.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-pipeline-run.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-pr-review.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-review.md +7 -0
- package/locales/en/overrides/.opencode/commands/wf-stages.md +7 -0
- package/locales/en/overrides/.pi/agents/implementer.md +11 -0
- package/locales/en/overrides/.pi/agents/independent-reviewer.md +12 -0
- package/locales/en/overrides/.pi/agents/pr-adjudicator.md +12 -0
- package/locales/en/overrides/.pi/agents/pr-finder.md +11 -0
- package/locales/en/overrides/.pi/agents/pr-verifier.md +11 -0
- package/locales/en/overrides/.pi/agents/ui-verifier.md +11 -0
- package/locales/en/overrides/.pi/agents/wiki-compiler.md +11 -0
- package/locales/en/overrides/.qoder/agents/implementer.md +9 -0
- package/locales/en/overrides/.qoder/agents/independent-reviewer.md +10 -0
- package/locales/en/overrides/.qoder/agents/pr-adjudicator.md +10 -0
- package/locales/en/overrides/.qoder/agents/pr-finder.md +10 -0
- package/locales/en/overrides/.qoder/agents/pr-verifier.md +10 -0
- package/locales/en/overrides/.qoder/agents/ui-verifier.md +10 -0
- package/locales/en/overrides/.qoder/agents/wiki-compiler.md +10 -0
- package/locales/en/overrides/.qoder/commands/wf-deploy.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-maintain.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-new-task.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-pipeline-run.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-pr-review.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-review.md +8 -0
- package/locales/en/overrides/.qoder/commands/wf-stages.md +8 -0
- package/locales/en/overrides/.qwen/agents/implementer.md +10 -0
- package/locales/en/overrides/.qwen/agents/independent-reviewer.md +10 -0
- package/locales/en/overrides/.qwen/agents/pr-adjudicator.md +10 -0
- package/locales/en/overrides/.qwen/agents/pr-finder.md +10 -0
- package/locales/en/overrides/.qwen/agents/pr-verifier.md +10 -0
- package/locales/en/overrides/.qwen/agents/ui-verifier.md +10 -0
- package/locales/en/overrides/.qwen/agents/wiki-compiler.md +13 -0
- package/locales/en/overrides/.qwen/commands/wf-deploy.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-maintain.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-new-task.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-pipeline-run.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-pr-review.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-review.md +7 -0
- package/locales/en/overrides/.qwen/commands/wf-stages.md +7 -0
- package/locales/en/overrides/.trae/agents/implementer.md +9 -0
- package/locales/en/overrides/.trae/agents/independent-reviewer.md +10 -0
- package/locales/en/overrides/.trae/agents/pr-adjudicator.md +10 -0
- package/locales/en/overrides/.trae/agents/pr-finder.md +10 -0
- package/locales/en/overrides/.trae/agents/pr-verifier.md +10 -0
- package/locales/en/overrides/.trae/agents/ui-verifier.md +10 -0
- package/locales/en/overrides/.trae/agents/wiki-compiler.md +10 -0
- package/locales/en/overrides/.trae/commands/wf-deploy.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-maintain.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-new-task.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-pipeline-run.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-pr-review.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-review.md +8 -0
- package/locales/en/overrides/.trae/commands/wf-stages.md +8 -0
- package/locales/en/overrides/.trae/rules/backend-scope.md +5 -0
- package/locales/en/overrides/.trae/rules/frontend-scope.md +5 -0
- package/locales/en/overrides/.zcode/agents/implementer.md +8 -0
- package/locales/en/overrides/.zcode/agents/independent-reviewer.md +8 -0
- package/locales/en/overrides/.zcode/agents/ui-verifier.md +8 -0
- package/locales/en/overrides/.zcode/agents/wiki-compiler.md +8 -0
- package/locales/en/overrides/AGENTS.md +52 -0
- package/locales/en/overrides/CLAUDE.md +20 -0
- package/locales/en/overrides/wiki/INDEX.md +33 -0
- package/locales/en/overrides/wiki/drafts-archive/README.md +6 -0
- package/locales/en/overrides/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +22 -0
- package/locales/en/overrides/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +230 -0
- package/locales/en/overrides/workflow/INDEX.md +13 -0
- package/locales/en/overrides/workflow/README.md +67 -0
- package/locales/en/overrides/workflow/archive/README.md +3 -0
- package/locales/en/overrides/workflow/delegations.md +4 -0
- package/locales/en/overrides/workflow/effective-policy.md +4 -0
- package/locales/en/overrides/workflow/gate-cost.md +4 -0
- package/locales/en/overrides/workflow/incidents/_TEMPLATE.md +49 -0
- package/locales/en/overrides/workflow/intents/_TEMPLATE.md +43 -0
- package/locales/en/overrides/workflow/metrics.md +4 -0
- package/locales/en/overrides/workflow/papercuts.md +3 -0
- package/locales/en/overrides/workflow/plans/_TEMPLATE.md +47 -0
- package/locales/en/overrides/workflow/regression-checklist.md +3 -0
- package/locales/en/overrides/workflow/specs/_TEMPLATE.md +86 -0
- package/locales/en/overrides/workflow/verify-evidence.md +4 -0
- package/locales/zh-CN/installer.json +25 -0
- package/package.json +23 -36
- package/schemas/install-manifest.schema.json +24 -0
- package/schemas/project-profile.schema.json +28 -0
- package/src/cli/args.mjs +41 -0
- package/src/cli/main.mjs +72 -0
- package/src/commands/add-host.mjs +29 -0
- package/src/commands/doctor.mjs +80 -0
- package/src/commands/install.mjs +149 -0
- package/src/commands/upgrade.mjs +30 -0
- package/src/git/config.mjs +61 -0
- package/src/hosts/registry.mjs +26 -0
- package/src/i18n.mjs +32 -0
- package/src/install/apply.mjs +124 -0
- package/src/install/manifest.mjs +40 -0
- package/src/install/plan.mjs +83 -0
- package/src/render/tokens.mjs +58 -0
- package/templates/engine/.agents/board/dev-tour.html +1940 -0
- package/templates/engine/.agents/board/index.html +835 -0
- package/templates/engine/.agents/commands/deploy.md +52 -0
- package/templates/engine/.agents/commands/maintain.md +76 -0
- package/templates/engine/.agents/commands/new-task.md +89 -0
- package/templates/engine/.agents/commands/pipeline-run.md +56 -0
- package/templates/engine/.agents/commands/pr-review.md +30 -0
- package/templates/engine/.agents/commands/review.md +93 -0
- package/templates/engine/.agents/commands/stages.md +256 -0
- package/templates/engine/.agents/config/commit-msg-rules.json +7 -0
- package/templates/engine/.agents/config/engine.json +11 -0
- package/templates/engine/.agents/config/gate-budgets.json +5 -0
- package/templates/engine/.agents/config/mechanism-registry.md +62 -0
- package/templates/engine/.agents/config/model-roles.json +26 -0
- package/templates/engine/.agents/config/model-roles.local.example.json +7 -0
- package/templates/engine/.agents/config/pr-review.json +67 -0
- package/templates/engine/.agents/config/principles.md +32 -0
- package/templates/engine/.agents/config/workflow-enums.txt +25 -0
- package/templates/engine/.agents/hooks/check-architecture.mjs +168 -0
- package/templates/engine/.agents/hooks/check-audit-fields.mjs +56 -0
- package/templates/engine/.agents/hooks/check-commit-plan-ref.mjs +84 -0
- package/templates/engine/.agents/hooks/check-confirm-order.mjs +281 -0
- package/templates/engine/.agents/hooks/check-delete-guard.mjs +63 -0
- package/templates/engine/.agents/hooks/check-notify-guard.mjs +55 -0
- package/templates/engine/.agents/hooks/commit-check.cjs +208 -0
- package/templates/engine/.agents/hooks/gates.json +33 -0
- package/templates/engine/.agents/hooks/local-pre-commit.mjs +27 -0
- package/templates/engine/.agents/hooks/pre-shell-check.cjs +361 -0
- package/templates/engine/.agents/hooks/pre-write-check.cjs +220 -0
- package/templates/engine/.agents/notes/gate-diet.md +75 -0
- package/templates/engine/.agents/notes/pr-review-cost-discipline.md +48 -0
- package/templates/engine/.agents/notes/process-order-rule.md +21 -0
- package/templates/engine/.agents/notes/runtime-env.local.example.md +21 -0
- package/templates/engine/.agents/pipelines/_TEMPLATE.md +75 -0
- package/templates/engine/.agents/pipelines/pipeline-closing.md +28 -0
- package/templates/engine/.agents/pipelines//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +16 -0
- package/templates/engine/.agents/roles/implementer.md +28 -0
- package/templates/engine/.agents/roles/independent-reviewer.md +39 -0
- package/templates/engine/.agents/roles/pr-adjudicator.md +27 -0
- package/templates/engine/.agents/roles/pr-finder.md +51 -0
- package/templates/engine/.agents/roles/pr-verifier.md +44 -0
- package/templates/engine/.agents/roles/ui-verifier.md +22 -0
- package/templates/engine/.agents/roles/wiki-compiler.md +47 -0
- package/templates/engine/.agents/scripts/agg-delegations.mjs +439 -0
- package/templates/engine/.agents/scripts/agg-judgments.mjs +301 -0
- package/templates/engine/.agents/scripts/agg-kb-usage.mjs +196 -0
- package/templates/engine/.agents/scripts/append-compensation.mjs +109 -0
- package/templates/engine/.agents/scripts/archive-sweep.mjs +229 -0
- package/templates/engine/.agents/scripts/auto-tour.mjs +77 -0
- package/templates/engine/.agents/scripts/board-port.mjs +17 -0
- package/templates/engine/.agents/scripts/check-doc-body-drift.mjs +102 -0
- package/templates/engine/.agents/scripts/check-engine-artifacts.mjs +54 -0
- package/templates/engine/.agents/scripts/check-hook-wiring.mjs +75 -0
- package/templates/engine/.agents/scripts/check-lane-surface.mjs +393 -0
- package/templates/engine/.agents/scripts/check-ledger-doc-presence.mjs +71 -0
- package/templates/engine/.agents/scripts/check-ledger-invariant.mjs +873 -0
- package/templates/engine/.agents/scripts/check-local-gate-config.mjs +95 -0
- package/templates/engine/.agents/scripts/check-local-gates.mjs +248 -0
- package/templates/engine/.agents/scripts/check-loop-kit.mjs +550 -0
- package/templates/engine/.agents/scripts/check-loop-rev-semantics.mjs +59 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s01-pairing.mjs +116 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s03x-regr-evidence.mjs +38 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s08-acceptance.mjs +130 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s15-fingerprint.mjs +152 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s23-convergence-audit.mjs +182 -0
- package/templates/engine/.agents/scripts/check-loop-sections/s29-regr-enforcer.mjs +69 -0
- package/templates/engine/.agents/scripts/check-loop.mjs +131 -0
- package/templates/engine/.agents/scripts/check-regression-consistency.mjs +252 -0
- package/templates/engine/.agents/scripts/check-schema-residue.mjs +144 -0
- package/templates/engine/.agents/scripts/check-scope-range.mjs +156 -0
- package/templates/engine/.agents/scripts/check-scope.mjs +121 -0
- package/templates/engine/.agents/scripts/check-scripts-syntax.mjs +138 -0
- package/templates/engine/.agents/scripts/check-secrets-lite.mjs +239 -0
- package/templates/engine/.agents/scripts/check-secrets.mjs +104 -0
- package/templates/engine/.agents/scripts/check-test-impact-map.mjs +107 -0
- package/templates/engine/.agents/scripts/ci-assert-frontend-deps.mjs +42 -0
- package/templates/engine/.agents/scripts/ci-commit-hygiene.mjs +86 -0
- package/templates/engine/.agents/scripts/ci-install-gitleaks.mjs +75 -0
- package/templates/engine/.agents/scripts/ci-push-before.mjs +49 -0
- package/templates/engine/.agents/scripts/closeout.mjs +127 -0
- package/templates/engine/.agents/scripts/compact-ledgers.mjs +70 -0
- package/templates/engine/.agents/scripts/confirm-doc.mjs +737 -0
- package/templates/engine/.agents/scripts/delegation-presence.mjs +38 -0
- package/templates/engine/.agents/scripts/dev-tour-model.mjs +1398 -0
- package/templates/engine/.agents/scripts/dream-run.mjs +283 -0
- package/templates/engine/.agents/scripts/ensure-board.mjs +187 -0
- package/templates/engine/.agents/scripts/failure-attribution.mjs +11 -0
- package/templates/engine/.agents/scripts/fill-intent.mjs +60 -0
- package/templates/engine/.agents/scripts/fill-plan.mjs +105 -0
- package/templates/engine/.agents/scripts/fill-spec.mjs +72 -0
- package/templates/engine/.agents/scripts/gen-dev-tour.mjs +191 -0
- package/templates/engine/.agents/scripts/gen-effective-policy.mjs +113 -0
- package/templates/engine/.agents/scripts/gen-engine-map.mjs +292 -0
- package/templates/engine/.agents/scripts/gen-narrative-claims.mjs +275 -0
- package/templates/engine/.agents/scripts/gen-verify-evidence.mjs +115 -0
- package/templates/engine/.agents/scripts/gen-wiki-board.mjs +221 -0
- package/templates/engine/.agents/scripts/gen-workflow-index.mjs +201 -0
- package/templates/engine/.agents/scripts/gen-workflow-metrics.mjs +692 -0
- package/templates/engine/.agents/scripts/git-merge-jsonl.mjs +62 -0
- package/templates/engine/.agents/scripts/judgment-ledger.mjs +68 -0
- package/templates/engine/.agents/scripts/kb-cache-evict.mjs +50 -0
- package/templates/engine/.agents/scripts/kb-search.mjs +423 -0
- package/templates/engine/.agents/scripts/lane-surface-audit.mjs +145 -0
- package/templates/engine/.agents/scripts/ledger-loader.mjs +77 -0
- package/templates/engine/.agents/scripts/ledger-shards.mjs +346 -0
- package/templates/engine/.agents/scripts/metrics-core.mjs +131 -0
- package/templates/engine/.agents/scripts/pipeline-run.mjs +964 -0
- package/templates/engine/.agents/scripts/policy.mjs +250 -0
- package/templates/engine/.agents/scripts/pr-create.mjs +54 -0
- package/templates/engine/.agents/scripts/pr-review.mjs +1551 -0
- package/templates/engine/.agents/scripts/quote-grounding.mjs +233 -0
- package/templates/engine/.agents/scripts/regen-ledgers.mjs +59 -0
- package/templates/engine/.agents/scripts/rewrite-file.mjs +135 -0
- package/templates/engine/.agents/scripts/rule-budget.mjs +246 -0
- package/templates/engine/.agents/scripts/stage-gates.mjs +812 -0
- package/templates/engine/.agents/scripts/test-impact-lib.mjs +451 -0
- package/templates/engine/.agents/scripts/test-impact.mjs +105 -0
- package/templates/engine/.agents/scripts/trust-mode.mjs +130 -0
- package/templates/engine/.agents/scripts/verify-scope.mjs +163 -0
- package/templates/engine/.agents/scripts/verify-wiki-consistency.mjs +264 -0
- package/templates/engine/.agents/scripts/verify.mjs +847 -0
- package/templates/engine/.agents/scripts/warn-format.mjs +57 -0
- package/templates/engine/.agents/scripts/wf-journal.mjs +230 -0
- package/templates/engine/.agents/scripts/workflow-board-server.mjs +696 -0
- package/templates/engine/.agents/scripts/workflow-enums.mjs +70 -0
- package/templates/engine/.agents/scripts/workflows-check.mjs +260 -0
- package/templates/engine/.agents/settings.json +33 -0
- package/templates/engine/.agents/skills/arena/SKILL.md +50 -0
- package/templates/engine/.agents/skills/autonomous-run/SKILL.md +45 -0
- package/templates/engine/.agents/skills/blast-radius/SKILL.md +53 -0
- package/templates/engine/.agents/skills/csharp-quality-gates/SKILL.md +109 -0
- package/templates/engine/.agents/skills/csharp-quality-gates/scripts/quality-gates.mjs +430 -0
- package/templates/engine/.agents/skills/dev-tour/SKILL.md +98 -0
- package/templates/engine/.agents/skills/dream/SKILL.md +63 -0
- package/templates/engine/.agents/skills/eval/SKILL.md +42 -0
- package/templates/engine/.agents/skills/eval/scripts/eval-score.mjs +59 -0
- package/templates/engine/.agents/skills/interrogate/SKILL.md +49 -0
- package/templates/engine/.agents/skills/interrogate/scripts/interrogate-plan.mjs +72 -0
- package/templates/engine/.agents/skills/number-vetting/SKILL.md +35 -0
- package/templates/engine/.agents/skills/page-refactor/SKILL.md +192 -0
- package/templates/engine/.agents/skills/page-refactor/references/templates.md +989 -0
- package/templates/engine/.agents/skills/papercut-fix/SKILL.md +92 -0
- package/templates/engine/.agents/skills/papercut-fix/references/response-template.md +81 -0
- package/templates/engine/.agents/skills/pause-safely/SKILL.md +31 -0
- package/templates/engine/.agents/skills/perf/SKILL.md +42 -0
- package/templates/engine/.agents/skills/review-method/SKILL.md +92 -0
- package/templates/engine/.agents/skills/review-method/golden-set.md +61 -0
- package/templates/engine/.agents/skills/session-resume/SKILL.md +69 -0
- package/templates/engine/.agents/skills/session-resume/scripts/worktree-audit.mjs +110 -0
- package/templates/engine/.agents/skills/shipyard-workflow-closeout/SKILL.md +109 -0
- package/templates/engine/.agents/skills/show-me-your-work/SKILL.md +57 -0
- package/templates/engine/.agents/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/templates/engine/.agents/skills/show-me-your-work/scripts/decision-log.mjs +24 -0
- package/templates/engine/.agents/skills/tfs-pr/SKILL.md +95 -0
- package/templates/engine/.agents/skills/tfs-pr/scripts/tfs-pr.mjs +947 -0
- package/templates/engine/.agents/skills/verify-ui/SKILL.md +116 -0
- package/templates/engine/.agents/skills/verify-ui/features/.gitkeep +0 -0
- package/templates/engine/.agents/skills/verify-ui/scripts/gen-verify-feature.mjs +114 -0
- package/templates/engine/.agents/skills/visual-parity/SKILL.md +35 -0
- package/templates/engine/.agents/skills/why/SKILL.md +56 -0
- package/templates/engine/.agents/skills/why/scripts/why-evidence.mjs +74 -0
- package/templates/engine/.agents/skills/wiki/SKILL.md +53 -0
- package/templates/engine/.agents/tests/auto-tour.test.mjs +82 -0
- package/templates/engine/.agents/tests/dev-tour-model.test.mjs +452 -0
- package/templates/engine/.agents/tests/fill-plan-lite.test.mjs +46 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-bare-mid-path.md +14 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-bare-mid-path.model.json +179 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-fake-path-guard.md +21 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-fake-path-guard.model.json +194 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-h3-batches.md +20 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-h3-batches.model.json +207 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-issues.md +24 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-issues.model.json +225 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-l1-single-file.md +15 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-l1-single-file.model.json +160 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layer-word-unique.md +29 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layer-word-unique.model.json +266 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layers-fallback.md +17 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layers-fallback.model.json +207 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-numbered-steps.md +39 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-numbered-steps.model.json +359 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-path-guess.md +16 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-path-guess.model.json +131 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-table-rows.md +29 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-table-rows.model.json +190 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-task-checkboxes.md +39 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-task-checkboxes.model.json +362 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-union-label.md +24 -0
- package/templates/engine/.agents/tests/fixtures/dev-tour/plan-union-label.model.json +224 -0
- package/templates/engine/.agents/tests/tools/gen-synthetic-plan.mjs +108 -0
- package/templates/git/.gitattributes +22 -0
- package/templates/git/.githooks/commit-msg +72 -0
- package/templates/git/.githooks/post-commit +62 -0
- package/templates/git/.githooks/pre-commit +197 -0
- package/templates/git/.githooks/pre-merge-commit +28 -0
- package/templates/git/.githooks/pre-push +201 -0
- package/templates/git/.gitignore.engine +11 -0
- package/templates/hosts/claude/CLAUDE.md +55 -0
- package/templates/hosts/claude/agents/implementer.md +7 -0
- package/templates/hosts/claude/agents/independent-reviewer.md +8 -0
- package/templates/hosts/claude/agents/pr-adjudicator.md +8 -0
- package/templates/hosts/claude/agents/pr-finder.md +8 -0
- package/templates/hosts/claude/agents/pr-verifier.md +8 -0
- package/templates/hosts/claude/agents/ui-verifier.md +8 -0
- package/templates/hosts/claude/agents/wiki-compiler.md +8 -0
- package/templates/hosts/claude/commands/wf-deploy.md +10 -0
- package/templates/hosts/claude/commands/wf-maintain.md +10 -0
- package/templates/hosts/claude/commands/wf-new-task.md +10 -0
- package/templates/hosts/claude/commands/wf-pipeline-run.md +10 -0
- package/templates/hosts/claude/commands/wf-pr-review.md +10 -0
- package/templates/hosts/claude/commands/wf-review.md +10 -0
- package/templates/hosts/claude/commands/wf-stages.md +10 -0
- package/templates/hosts/claude/settings.json +57 -0
- package/templates/hosts/cline/agents/implementer.yml +15 -0
- package/templates/hosts/cline/agents/independent-reviewer.yml +20 -0
- package/templates/hosts/cline/agents/pr-adjudicator.yml +18 -0
- package/templates/hosts/cline/agents/pr-finder.yml +19 -0
- package/templates/hosts/cline/agents/pr-verifier.yml +18 -0
- package/templates/hosts/cline/agents/ui-verifier.yml +17 -0
- package/templates/hosts/cline/agents/wiki-compiler.yml +20 -0
- package/templates/hosts/cline/hooks/PreToolUse.cjs +260 -0
- package/templates/hosts/cline/workflows/wf-deploy.md +8 -0
- package/templates/hosts/cline/workflows/wf-maintain.md +8 -0
- package/templates/hosts/cline/workflows/wf-new-task.md +8 -0
- package/templates/hosts/cline/workflows/wf-pipeline-run.md +8 -0
- package/templates/hosts/cline/workflows/wf-pr-review.md +8 -0
- package/templates/hosts/cline/workflows/wf-review.md +8 -0
- package/templates/hosts/cline/workflows/wf-stages.md +8 -0
- package/templates/hosts/codebuddy/CODEBUDDY.md +7 -0
- package/templates/hosts/codebuddy/agents/implementer.md +9 -0
- package/templates/hosts/codebuddy/agents/independent-reviewer.md +10 -0
- package/templates/hosts/codebuddy/agents/pr-adjudicator.md +10 -0
- package/templates/hosts/codebuddy/agents/pr-finder.md +10 -0
- package/templates/hosts/codebuddy/agents/pr-verifier.md +10 -0
- package/templates/hosts/codebuddy/agents/ui-verifier.md +11 -0
- package/templates/hosts/codebuddy/agents/wiki-compiler.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-deploy.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-maintain.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-new-task.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-pipeline-run.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-pr-review.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-review.md +10 -0
- package/templates/hosts/codebuddy/commands/wf-stages.md +10 -0
- package/templates/hosts/codebuddy/rules/backend-scope.md +5 -0
- package/templates/hosts/codebuddy/rules/frontend-scope.md +5 -0
- package/templates/hosts/codebuddy/settings.json +31 -0
- package/templates/hosts/codebuddy/skills/tfs-pr/SKILL.md +6 -0
- package/templates/hosts/commandcode/agents/implementer.md +10 -0
- package/templates/hosts/commandcode/agents/independent-reviewer.md +9 -0
- package/templates/hosts/commandcode/agents/pr-adjudicator.md +9 -0
- package/templates/hosts/commandcode/agents/pr-finder.md +9 -0
- package/templates/hosts/commandcode/agents/pr-verifier.md +9 -0
- package/templates/hosts/commandcode/agents/ui-verifier.md +11 -0
- package/templates/hosts/commandcode/agents/wiki-compiler.md +10 -0
- package/templates/hosts/commandcode/commands/wf-deploy.md +9 -0
- package/templates/hosts/commandcode/commands/wf-maintain.md +9 -0
- package/templates/hosts/commandcode/commands/wf-new-task.md +9 -0
- package/templates/hosts/commandcode/commands/wf-pipeline-run.md +9 -0
- package/templates/hosts/commandcode/commands/wf-pr-review.md +9 -0
- package/templates/hosts/commandcode/commands/wf-review.md +9 -0
- package/templates/hosts/commandcode/commands/wf-stages.md +9 -0
- package/templates/hosts/commandcode/settings.json +59 -0
- package/templates/hosts/opencode/agents/implementer.md +51 -0
- package/templates/hosts/opencode/agents/independent-reviewer.md +18 -0
- package/templates/hosts/opencode/agents/pr-adjudicator.md +18 -0
- package/templates/hosts/opencode/agents/pr-finder.md +18 -0
- package/templates/hosts/opencode/agents/pr-verifier.md +18 -0
- package/templates/hosts/opencode/agents/ui-verifier.md +45 -0
- package/templates/hosts/opencode/agents/wiki-compiler.md +51 -0
- package/templates/hosts/opencode/commands/wf-deploy.md +7 -0
- package/templates/hosts/opencode/commands/wf-maintain.md +7 -0
- package/templates/hosts/opencode/commands/wf-new-task.md +7 -0
- package/templates/hosts/opencode/commands/wf-pipeline-run.md +7 -0
- package/templates/hosts/opencode/commands/wf-pr-review.md +7 -0
- package/templates/hosts/opencode/commands/wf-review.md +7 -0
- package/templates/hosts/opencode/commands/wf-stages.md +7 -0
- package/templates/hosts/pi/agents/implementer.md +10 -0
- package/templates/hosts/pi/agents/independent-reviewer.md +11 -0
- package/templates/hosts/pi/agents/pr-adjudicator.md +11 -0
- package/templates/hosts/pi/agents/pr-finder.md +10 -0
- package/templates/hosts/pi/agents/pr-verifier.md +10 -0
- package/templates/hosts/pi/agents/ui-verifier.md +10 -0
- package/templates/hosts/pi/agents/wiki-compiler.md +10 -0
- package/templates/hosts/pi/extensions/lib/gates-core.cjs +196 -0
- package/templates/hosts/pi/extensions/shipyard-gates.ts +95 -0
- package/templates/hosts/pi/settings.json +5 -0
- package/templates/hosts/qoder/agents/implementer.md +9 -0
- package/templates/hosts/qoder/agents/independent-reviewer.md +10 -0
- package/templates/hosts/qoder/agents/pr-adjudicator.md +10 -0
- package/templates/hosts/qoder/agents/pr-finder.md +10 -0
- package/templates/hosts/qoder/agents/pr-verifier.md +10 -0
- package/templates/hosts/qoder/agents/ui-verifier.md +10 -0
- package/templates/hosts/qoder/agents/wiki-compiler.md +10 -0
- package/templates/hosts/qoder/commands/wf-deploy.md +8 -0
- package/templates/hosts/qoder/commands/wf-maintain.md +8 -0
- package/templates/hosts/qoder/commands/wf-new-task.md +8 -0
- package/templates/hosts/qoder/commands/wf-pipeline-run.md +8 -0
- package/templates/hosts/qoder/commands/wf-pr-review.md +8 -0
- package/templates/hosts/qoder/commands/wf-review.md +8 -0
- package/templates/hosts/qoder/commands/wf-stages.md +8 -0
- package/templates/hosts/qoder/settings.json +63 -0
- package/templates/hosts/qwen/agents/implementer.md +8 -0
- package/templates/hosts/qwen/agents/independent-reviewer.md +8 -0
- package/templates/hosts/qwen/agents/pr-adjudicator.md +8 -0
- package/templates/hosts/qwen/agents/pr-finder.md +8 -0
- package/templates/hosts/qwen/agents/pr-verifier.md +8 -0
- package/templates/hosts/qwen/agents/ui-verifier.md +8 -0
- package/templates/hosts/qwen/agents/wiki-compiler.md +11 -0
- package/templates/hosts/qwen/commands/wf-deploy.md +9 -0
- package/templates/hosts/qwen/commands/wf-maintain.md +9 -0
- package/templates/hosts/qwen/commands/wf-new-task.md +9 -0
- package/templates/hosts/qwen/commands/wf-pipeline-run.md +9 -0
- package/templates/hosts/qwen/commands/wf-pr-review.md +9 -0
- package/templates/hosts/qwen/commands/wf-review.md +9 -0
- package/templates/hosts/qwen/commands/wf-stages.md +9 -0
- package/templates/hosts/qwen/settings.json +59 -0
- package/templates/hosts/trae/agents/implementer.md +9 -0
- package/templates/hosts/trae/agents/independent-reviewer.md +10 -0
- package/templates/hosts/trae/agents/pr-adjudicator.md +10 -0
- package/templates/hosts/trae/agents/pr-finder.md +10 -0
- package/templates/hosts/trae/agents/pr-verifier.md +10 -0
- package/templates/hosts/trae/agents/ui-verifier.md +10 -0
- package/templates/hosts/trae/agents/wiki-compiler.md +10 -0
- package/templates/hosts/trae/commands/wf-deploy.md +8 -0
- package/templates/hosts/trae/commands/wf-maintain.md +8 -0
- package/templates/hosts/trae/commands/wf-new-task.md +8 -0
- package/templates/hosts/trae/commands/wf-pipeline-run.md +8 -0
- package/templates/hosts/trae/commands/wf-pr-review.md +8 -0
- package/templates/hosts/trae/commands/wf-review.md +8 -0
- package/templates/hosts/trae/commands/wf-stages.md +8 -0
- package/templates/hosts/trae/hooks.json +17 -0
- package/templates/hosts/trae/rules/backend-scope.md +5 -0
- package/templates/hosts/trae/rules/frontend-scope.md +5 -0
- package/templates/hosts/zcode/agents/implementer.md +8 -0
- package/templates/hosts/zcode/agents/independent-reviewer.md +8 -0
- package/templates/hosts/zcode/agents/ui-verifier.md +8 -0
- package/templates/hosts/zcode/agents/wiki-compiler.md +8 -0
- package/templates/hosts/zcode/config.json +31 -0
- package/templates/hosts/zcode/workflows/code-review.dwf.ts +428 -0
- package/templates/hosts/zcode/workflows/fullchain-flow-test.dwf.ts +898 -0
- package/templates/profiles/dotnet/.agents/config/lane-surfaces.txt +33 -0
- package/templates/profiles/dotnet/.agents/config/rule-budgets.txt +84 -0
- package/templates/profiles/dotnet/.agents/config/test-impact-map.json +590 -0
- package/templates/profiles/dotnet/.agents/config/workflow-modules.txt +18 -0
- package/templates/profiles/dotnet/.agents/hooks/commit-check.config.json +18 -0
- package/templates/profiles/dotnet/.agents/hooks/delete-guard-allowlist.txt +28 -0
- package/templates/profiles/dotnet/.agents/notes/runtime-env.md +178 -0
- package/templates/profiles/dotnet/AGENTS.md +52 -0
- package/templates/profiles/empty/.agents/config/lane-surfaces.txt +12 -0
- package/templates/profiles/empty/.agents/config/rule-budgets.txt +3 -0
- package/templates/profiles/empty/.agents/config/test-impact-map.json +8 -0
- package/templates/profiles/empty/.agents/config/workflow-modules.txt +2 -0
- package/templates/profiles/empty/.agents/hooks/commit-check.config.json +5 -0
- package/templates/profiles/empty/.agents/hooks/delete-guard-allowlist.txt +2 -0
- package/templates/profiles/empty/.agents/notes/runtime-env.md +44 -0
- package/templates/profiles/empty/AGENTS.md +52 -0
- package/templates/profiles/node/.agents/config/lane-surfaces.txt +12 -0
- package/templates/profiles/node/.agents/config/rule-budgets.txt +3 -0
- package/templates/profiles/node/.agents/config/test-impact-map.json +8 -0
- package/templates/profiles/node/.agents/config/workflow-modules.txt +2 -0
- package/templates/profiles/node/.agents/hooks/commit-check.config.json +5 -0
- package/templates/profiles/node/.agents/hooks/delete-guard-allowlist.txt +2 -0
- package/templates/profiles/node/.agents/notes/runtime-env.md +44 -0
- package/templates/profiles/node/AGENTS.md +52 -0
- package/templates/scaffold/wiki/INDEX.md +33 -0
- package/templates/scaffold/wiki/drafts-archive/README.md +7 -0
- package/templates/scaffold/wiki//344/272/247/345/223/201/351/234/200/346/261/202/.gitkeep +0 -0
- package/templates/scaffold/wiki//345/274/200/345/217/221/346/226/271/346/241/210/.gitkeep +0 -0
- package/templates/scaffold/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +22 -0
- package/templates/scaffold/wiki//346/265/213/350/257/225/346/212/245/345/221/212/.gitkeep +0 -0
- package/templates/scaffold/wiki//347/224/250/346/210/267/345/212/237/350/203/275/.gitkeep +0 -0
- package/templates/scaffold/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +230 -0
- package/templates/scaffold/wiki//351/241/271/347/233/256/350/247/204/350/214/203/.gitkeep +0 -0
- package/templates/scaffold/workflow/INDEX.md +13 -0
- package/templates/scaffold/workflow/README.md +67 -0
- package/templates/scaffold/workflow/archive/README.md +3 -0
- package/templates/scaffold/workflow/delegations.d/.gitkeep +0 -0
- package/templates/scaffold/workflow/delegations.md +4 -0
- package/templates/scaffold/workflow/effective-policy.md +4 -0
- package/templates/scaffold/workflow/evidence/.gitkeep +0 -0
- package/templates/scaffold/workflow/gate-cost.md +4 -0
- package/templates/scaffold/workflow/gate-metrics.d/.gitkeep +0 -0
- package/templates/scaffold/workflow/incidents/_TEMPLATE.md +49 -0
- package/templates/scaffold/workflow/intents/_TEMPLATE.md +43 -0
- package/templates/scaffold/workflow/metrics.md +4 -0
- package/templates/scaffold/workflow/papercuts.d/.gitkeep +0 -0
- package/templates/scaffold/workflow/papercuts.md +3 -0
- package/templates/scaffold/workflow/plans/_TEMPLATE.md +47 -0
- package/templates/scaffold/workflow/regression-checklist.d/.gitkeep +0 -0
- package/templates/scaffold/workflow/regression-checklist.md +3 -0
- package/templates/scaffold/workflow/research/.gitkeep +0 -0
- package/templates/scaffold/workflow/reviews.d/.gitkeep +0 -0
- package/templates/scaffold/workflow/specs/_TEMPLATE.md +87 -0
- package/templates/scaffold/workflow/verify-evidence/.gitkeep +0 -0
- package/templates/scaffold/workflow/verify-evidence.md +4 -0
- package/LICENSE +0 -21
- package/modules/gates/README.md +0 -25
- package/modules/gates/dotnet-ca/README.md +0 -24
- package/modules/gates/dotnet-ca/check-architecture.sh +0 -150
- package/modules/gates/dotnet-ca/local-pre-commit.line +0 -1
- package/modules/gates/generated-readonly/README.md +0 -13
- package/modules/gates/generated-readonly/check-generated.mjs +0 -61
- package/modules/gates/generated-readonly/local-pre-commit.line +0 -1
- package/modules/gates/node-layer/README.md +0 -9
- package/modules/gates/node-layer/check-node-layer.mjs +0 -29
- package/modules/gates/node-layer/local-pre-commit.line +0 -1
- package/modules/gates/py-import/README.md +0 -9
- package/modules/gates/py-import/check-py-import.mjs +0 -30
- package/modules/gates/py-import/local-pre-commit.line +0 -1
- package/modules/gates/test-protect/README.md +0 -23
- package/modules/gates/test-protect/check-test-protect.mjs +0 -64
- package/modules/gates/test-protect/local-pre-commit.line +0 -1
- package/modules/hosts/README.md +0 -57
- package/modules/hosts/claude/agents/implementer.md +0 -51
- package/modules/hosts/claude/agents/independent-reviewer.md +0 -49
- package/modules/hosts/claude/agents/ui-verifier.md +0 -48
- package/modules/hosts/claude/commands/wf-build.md +0 -69
- package/modules/hosts/claude/commands/wf-deploy.md +0 -58
- package/modules/hosts/claude/commands/wf-design.md +0 -49
- package/modules/hosts/claude/commands/wf-gate-checklist.md +0 -77
- package/modules/hosts/claude/commands/wf-maintain.md +0 -76
- package/modules/hosts/claude/commands/wf-new-task.md +0 -102
- package/modules/hosts/claude/commands/wf-pipeline-run.md +0 -35
- package/modules/hosts/claude/commands/wf-plan.md +0 -40
- package/modules/hosts/claude/commands/wf-review.md +0 -49
- package/modules/hosts/claude/commands/wf-sync-hosts.md +0 -64
- package/modules/hosts/claude/commands/wf-test.md +0 -68
- package/modules/hosts/codex/agents/implementer.md +0 -51
- package/modules/hosts/codex/agents/independent-reviewer.md +0 -49
- package/modules/hosts/codex/agents/ui-verifier.md +0 -48
- package/modules/hosts/codex/commands/wf-build.md +0 -69
- package/modules/hosts/codex/commands/wf-deploy.md +0 -58
- package/modules/hosts/codex/commands/wf-design.md +0 -49
- package/modules/hosts/codex/commands/wf-gate-checklist.md +0 -77
- package/modules/hosts/codex/commands/wf-maintain.md +0 -76
- package/modules/hosts/codex/commands/wf-new-task.md +0 -102
- package/modules/hosts/codex/commands/wf-pipeline-run.md +0 -35
- package/modules/hosts/codex/commands/wf-plan.md +0 -40
- package/modules/hosts/codex/commands/wf-review.md +0 -49
- package/modules/hosts/codex/commands/wf-sync-hosts.md +0 -64
- package/modules/hosts/codex/commands/wf-test.md +0 -68
- package/modules/hosts/codex/skills/flow-kit/SKILL.md +0 -14
- package/modules/hosts/cursor/agents/implementer.md +0 -51
- package/modules/hosts/cursor/agents/independent-reviewer.md +0 -49
- package/modules/hosts/cursor/agents/ui-verifier.md +0 -48
- package/modules/hosts/cursor/commands/wf-build.md +0 -69
- package/modules/hosts/cursor/commands/wf-deploy.md +0 -58
- package/modules/hosts/cursor/commands/wf-design.md +0 -49
- package/modules/hosts/cursor/commands/wf-gate-checklist.md +0 -77
- package/modules/hosts/cursor/commands/wf-maintain.md +0 -76
- package/modules/hosts/cursor/commands/wf-new-task.md +0 -102
- package/modules/hosts/cursor/commands/wf-pipeline-run.md +0 -35
- package/modules/hosts/cursor/commands/wf-plan.md +0 -40
- package/modules/hosts/cursor/commands/wf-review.md +0 -49
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +0 -64
- package/modules/hosts/cursor/commands/wf-test.md +0 -68
- package/modules/hosts/omp/agents/implementer.md +0 -52
- package/modules/hosts/omp/agents/independent-reviewer.md +0 -50
- package/modules/hosts/omp/agents/ui-verifier.md +0 -49
- package/modules/hosts/opencode/agents/implementer.md +0 -59
- package/modules/hosts/opencode/agents/independent-reviewer.md +0 -53
- package/modules/hosts/opencode/agents/ui-verifier.md +0 -52
- package/modules/hosts/opencode/commands/wf-build.md +0 -69
- package/modules/hosts/opencode/commands/wf-deploy.md +0 -58
- package/modules/hosts/opencode/commands/wf-design.md +0 -49
- package/modules/hosts/opencode/commands/wf-gate-checklist.md +0 -77
- package/modules/hosts/opencode/commands/wf-maintain.md +0 -76
- package/modules/hosts/opencode/commands/wf-new-task.md +0 -102
- package/modules/hosts/opencode/commands/wf-pipeline-run.md +0 -35
- package/modules/hosts/opencode/commands/wf-plan.md +0 -40
- package/modules/hosts/opencode/commands/wf-review.md +0 -49
- package/modules/hosts/opencode/commands/wf-sync-hosts.md +0 -64
- package/modules/hosts/opencode/commands/wf-test.md +0 -68
- package/modules/hosts/trae/agents/implementer.md +0 -52
- package/modules/hosts/trae/agents/independent-reviewer.md +0 -51
- package/modules/hosts/trae/agents/ui-verifier.md +0 -50
- package/modules/hosts/trae/commands/wf-build.md +0 -70
- package/modules/hosts/trae/commands/wf-deploy.md +0 -59
- package/modules/hosts/trae/commands/wf-design.md +0 -50
- package/modules/hosts/trae/commands/wf-gate-checklist.md +0 -78
- package/modules/hosts/trae/commands/wf-maintain.md +0 -77
- package/modules/hosts/trae/commands/wf-new-task.md +0 -103
- package/modules/hosts/trae/commands/wf-pipeline-run.md +0 -36
- package/modules/hosts/trae/commands/wf-plan.md +0 -41
- package/modules/hosts/trae/commands/wf-review.md +0 -50
- package/modules/hosts/trae/commands/wf-sync-hosts.md +0 -65
- package/modules/hosts/trae/commands/wf-test.md +0 -69
- package/modules/hosts/trae/hooks/post-edit-check.cjs +0 -98
- package/modules/hosts/trae/hooks/pre-shell-check.cjs +0 -192
- package/modules/hosts/trae/hooks/resolve-shell.cjs +0 -30
- package/modules/hosts/trae/hooks.json +0 -29
- package/modules/hosts/trae/rules/beautify-input-chinese.md +0 -6
- package/modules/hosts/trae/rules/git-commit-message.md +0 -10
- package/modules/hosts/trae/rules/project-workflow.md +0 -14
- package/modules/hosts/zcode/agents/implementer.md +0 -51
- package/modules/hosts/zcode/agents/independent-reviewer.md +0 -49
- package/modules/hosts/zcode/agents/ui-verifier.md +0 -48
- package/src/add-gate.mjs +0 -116
- package/src/add-gate.test.mjs +0 -103
- package/src/add-host.mjs +0 -68
- package/src/cli.mjs +0 -169
- package/src/doctor.mjs +0 -629
- package/src/executor.mjs +0 -262
- package/src/executor.test.mjs +0 -295
- package/src/fresh-init.test.mjs +0 -64
- package/src/gate-dotnet-ca.test.mjs +0 -124
- package/src/gates.test.mjs +0 -143
- package/src/init.mjs +0 -382
- package/src/init.test.mjs +0 -163
- package/src/instance-ref.test.mjs +0 -62
- package/src/jscpd-drift.test.mjs +0 -144
- package/src/pack.test.mjs +0 -24
- package/src/profiles.mjs +0 -183
- package/src/render.mjs +0 -132
- package/src/rule-budget.test.mjs +0 -103
- package/src/run-tests.mjs +0 -56
- package/src/stack-profile.test.mjs +0 -95
- package/src/sync-hosts.mjs +0 -257
- package/src/sync-hosts.test.mjs +0 -291
- package/src/sync.mjs +0 -228
- package/src/sync.test.mjs +0 -452
- package/src/trae-hooks.test.mjs +0 -127
- package/src/validate.mjs +0 -77
- package/src/validate.test.mjs +0 -95
- package/templates/.gitattributes +0 -17
- package/templates/AGENTS.md +0 -57
- package/templates/_agents/board/index.html +0 -676
- package/templates/_agents/commands/build.md +0 -84
- package/templates/_agents/commands/deploy.md +0 -66
- package/templates/_agents/commands/design.md +0 -63
- package/templates/_agents/commands/gate-checklist.md +0 -91
- package/templates/_agents/commands/maintain.md +0 -90
- package/templates/_agents/commands/new-task.md +0 -110
- package/templates/_agents/commands/pipeline-run.md +0 -43
- package/templates/_agents/commands/plan.md +0 -48
- package/templates/_agents/commands/review.md +0 -62
- package/templates/_agents/commands/sync-hosts.md +0 -77
- package/templates/_agents/commands/test.md +0 -87
- package/templates/_agents/control-surfaces.txt +0 -53
- package/templates/_agents/engine-lock.json +0 -342
- package/templates/_agents/hooks/check-index-incremental.mjs +0 -48
- package/templates/_agents/hooks/check-pairing-incremental.mjs +0 -66
- package/templates/_agents/hooks/check-wiki-ledger.mjs +0 -30
- package/templates/_agents/hooks/commit-check.cjs +0 -193
- package/templates/_agents/hooks/local-pre-commit +0 -6
- package/templates/_agents/lane-surfaces.txt +0 -17
- package/templates/_agents/level-hints.txt +0 -30
- package/templates/_agents/loop/papercut-debt/DESIGN.md +0 -112
- package/templates/_agents/mechanism-registry.md +0 -27
- package/templates/_agents/metric-claims.txt +0 -81
- package/templates/_agents/notes/engine-internals.md +0 -17
- package/templates/_agents/notes/runtime-env.md +0 -18
- package/templates/_agents/roles/implementer.md +0 -47
- package/templates/_agents/roles/independent-reviewer.md +0 -45
- package/templates/_agents/roles/ui-verifier.md +0 -44
- package/templates/_agents/rule-budgets.txt +0 -18
- package/templates/_agents/scripts/agg-delegations.cjs +0 -302
- package/templates/_agents/scripts/agg-delegations.test.mjs +0 -282
- package/templates/_agents/scripts/agg-gate-stats.mjs +0 -113
- package/templates/_agents/scripts/agg-gate-stats.test.mjs +0 -95
- package/templates/_agents/scripts/agg-judgments.mjs +0 -156
- package/templates/_agents/scripts/agg-judgments.test.mjs +0 -93
- package/templates/_agents/scripts/archive-sweep.mjs +0 -235
- package/templates/_agents/scripts/archive-sweep.test.mjs +0 -129
- package/templates/_agents/scripts/check-attribution.mjs +0 -37
- package/templates/_agents/scripts/check-convergence.mjs +0 -124
- package/templates/_agents/scripts/check-engine-integrity.mjs +0 -220
- package/templates/_agents/scripts/check-engine-integrity.test.mjs +0 -215
- package/templates/_agents/scripts/check-funnel.mjs +0 -56
- package/templates/_agents/scripts/check-hygiene.mjs +0 -167
- package/templates/_agents/scripts/check-hygiene.test.mjs +0 -182
- package/templates/_agents/scripts/check-index-incremental.test.mjs +0 -73
- package/templates/_agents/scripts/check-lane-surface.mjs +0 -238
- package/templates/_agents/scripts/check-lane-surface.test.mjs +0 -302
- package/templates/_agents/scripts/check-ledger-invariant.mjs +0 -527
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +0 -639
- package/templates/_agents/scripts/check-loop-rev.test.mjs +0 -336
- package/templates/_agents/scripts/check-loop-unit.test.mjs +0 -103
- package/templates/_agents/scripts/check-loop.mjs +0 -1958
- package/templates/_agents/scripts/check-loop.sh +0 -6
- package/templates/_agents/scripts/check-loop.test.mjs +0 -3875
- package/templates/_agents/scripts/check-metric-claims.mjs +0 -277
- package/templates/_agents/scripts/check-prejudgment.mjs +0 -64
- package/templates/_agents/scripts/check-registry.mjs +0 -45
- package/templates/_agents/scripts/check-scope.mjs +0 -96
- package/templates/_agents/scripts/check-scope.test.mjs +0 -96
- package/templates/_agents/scripts/check-scripts-syntax.mjs +0 -61
- package/templates/_agents/scripts/check-scripts-syntax.test.mjs +0 -72
- package/templates/_agents/scripts/check-spec-verif.mjs +0 -47
- package/templates/_agents/scripts/check-stage-index.mjs +0 -40
- package/templates/_agents/scripts/check-stage-index.test.mjs +0 -77
- package/templates/_agents/scripts/closeout-l1.mjs +0 -217
- package/templates/_agents/scripts/closeout-l1.test.mjs +0 -105
- package/templates/_agents/scripts/commit-check-trigger.test.mjs +0 -205
- package/templates/_agents/scripts/compact-ledgers.mjs +0 -97
- package/templates/_agents/scripts/compact-ledgers.test.mjs +0 -162
- package/templates/_agents/scripts/confirm-doc.mjs +0 -603
- package/templates/_agents/scripts/confirm-doc.test.mjs +0 -1103
- package/templates/_agents/scripts/doctor.test.mjs +0 -770
- package/templates/_agents/scripts/draft-sediment.mjs +0 -178
- package/templates/_agents/scripts/draft-sediment.test.mjs +0 -272
- package/templates/_agents/scripts/edit-face-check.mjs +0 -109
- package/templates/_agents/scripts/edit-face-check.test.mjs +0 -99
- package/templates/_agents/scripts/ensure-board.mjs +0 -185
- package/templates/_agents/scripts/fill-intent.mjs +0 -91
- package/templates/_agents/scripts/fill-intent.test.mjs +0 -113
- package/templates/_agents/scripts/fill-plan.mjs +0 -79
- package/templates/_agents/scripts/fill-plan.test.mjs +0 -224
- package/templates/_agents/scripts/fill-spec.mjs +0 -70
- package/templates/_agents/scripts/fill-spec.test.mjs +0 -266
- package/templates/_agents/scripts/fixture-scripts.mjs +0 -153
- package/templates/_agents/scripts/fixture-scripts.test.mjs +0 -112
- package/templates/_agents/scripts/gate-checklist.mjs +0 -209
- package/templates/_agents/scripts/gate-checklist.test.mjs +0 -201
- package/templates/_agents/scripts/gate-seg.mjs +0 -54
- package/templates/_agents/scripts/gate-seg.test.mjs +0 -112
- package/templates/_agents/scripts/gen-effective-policy.mjs +0 -107
- package/templates/_agents/scripts/gen-effective-policy.test.mjs +0 -100
- package/templates/_agents/scripts/gen-verify-evidence.mjs +0 -107
- package/templates/_agents/scripts/gen-verify-evidence.test.mjs +0 -84
- package/templates/_agents/scripts/gen-wiki-board.mjs +0 -276
- package/templates/_agents/scripts/gen-wiki-board.test.mjs +0 -249
- package/templates/_agents/scripts/gen-workflow-dashboard.mjs +0 -251
- package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +0 -188
- package/templates/_agents/scripts/gen-workflow-index.mjs +0 -201
- package/templates/_agents/scripts/gen-workflow-index.test.mjs +0 -152
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +0 -334
- package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +0 -224
- package/templates/_agents/scripts/git-merge-jsonl.mjs +0 -63
- package/templates/_agents/scripts/git-merge-jsonl.test.mjs +0 -125
- package/templates/_agents/scripts/judgment-ledger.mjs +0 -65
- package/templates/_agents/scripts/judgment-ledger.test.mjs +0 -129
- package/templates/_agents/scripts/kb-cache-evict.mjs +0 -50
- package/templates/_agents/scripts/kb-cache-evict.test.mjs +0 -104
- package/templates/_agents/scripts/kb-search.mjs +0 -413
- package/templates/_agents/scripts/kb-search.test.mjs +0 -299
- package/templates/_agents/scripts/lane-surface-audit.mjs +0 -143
- package/templates/_agents/scripts/lane-surface-audit.test.mjs +0 -93
- package/templates/_agents/scripts/ledger-doc-chain.test.mjs +0 -166
- package/templates/_agents/scripts/ledger-loader.mjs +0 -108
- package/templates/_agents/scripts/ledger-loader.test.mjs +0 -131
- package/templates/_agents/scripts/ledger-prune.mjs +0 -96
- package/templates/_agents/scripts/ledger-prune.test.mjs +0 -120
- package/templates/_agents/scripts/ledger-shards.mjs +0 -344
- package/templates/_agents/scripts/ledger-shards.test.mjs +0 -356
- package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +0 -135
- package/templates/_agents/scripts/level-criteria.mjs +0 -231
- package/templates/_agents/scripts/level-criteria.test.mjs +0 -281
- package/templates/_agents/scripts/mechanism-retire-watch.mjs +0 -183
- package/templates/_agents/scripts/mechanism-retire-watch.test.mjs +0 -114
- package/templates/_agents/scripts/papercut-debt-controller.mjs +0 -141
- package/templates/_agents/scripts/papercut-debt-controller.test.mjs +0 -127
- package/templates/_agents/scripts/papercut-debt-sensor.mjs +0 -170
- package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +0 -146
- package/templates/_agents/scripts/pipeline-run-docs.mjs +0 -76
- package/templates/_agents/scripts/pipeline-run-run.mjs +0 -63
- package/templates/_agents/scripts/pipeline-run.mjs +0 -867
- package/templates/_agents/scripts/pipeline-run.test.mjs +0 -517
- package/templates/_agents/scripts/policy.mjs +0 -281
- package/templates/_agents/scripts/pre-push.test.mjs +0 -105
- package/templates/_agents/scripts/resolve-merge-conflicts.mjs +0 -110
- package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +0 -87
- package/templates/_agents/scripts/rewrite-file.mjs +0 -133
- package/templates/_agents/scripts/rewrite-file.test.mjs +0 -148
- package/templates/_agents/scripts/rule-budget.mjs +0 -96
- package/templates/_agents/scripts/rule-budget.test.mjs +0 -118
- package/templates/_agents/scripts/run-tests.mjs +0 -99
- package/templates/_agents/scripts/run-tests.test.mjs +0 -105
- package/templates/_agents/scripts/solidify-task.mjs +0 -107
- package/templates/_agents/scripts/solidify-task.test.mjs +0 -129
- package/templates/_agents/scripts/stage-gates.mjs +0 -343
- package/templates/_agents/scripts/stage-gates.test.mjs +0 -105
- package/templates/_agents/scripts/trust-mode.mjs +0 -67
- package/templates/_agents/scripts/trust-mode.test.mjs +0 -110
- package/templates/_agents/scripts/verify-wiki-consistency.mjs +0 -253
- package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +0 -123
- package/templates/_agents/scripts/verify.mjs +0 -210
- package/templates/_agents/scripts/verify.test.mjs +0 -358
- package/templates/_agents/scripts/wf-journal.mjs +0 -231
- package/templates/_agents/scripts/wf-journal.test.mjs +0 -216
- package/templates/_agents/scripts/wiki-search.mjs +0 -19
- package/templates/_agents/scripts/wiki-search.test.mjs +0 -57
- package/templates/_agents/scripts/workflow-board-server.mjs +0 -393
- package/templates/_agents/scripts/workflow-board-server.test.mjs +0 -201
- package/templates/_agents/scripts/workflow-enums.mjs +0 -70
- package/templates/_agents/scripts/workflow-enums.test.mjs +0 -118
- package/templates/_agents/scripts/workflows-check.mjs +0 -260
- package/templates/_agents/scripts/workflows-check.test.mjs +0 -278
- package/templates/_agents/scripts-test-exempt.txt +0 -16
- package/templates/_agents/skills/papercut-fix/SKILL.md +0 -60
- package/templates/_agents/skills/papercut-fix/references/response-template.md +0 -81
- package/templates/_agents/skills/review-method/SKILL.md +0 -55
- package/templates/_agents/skills/verify-ui/SKILL.md +0 -61
- package/templates/_agents/skills/wiki/SKILL.md +0 -106
- package/templates/_agents/workflow-enums.txt +0 -26
- package/templates/_agents/workflow-modules.txt +0 -11
- package/templates/_agents/workflows/_TEMPLATE.md +0 -75
- package/templates/_agents/workflows/pipeline-closing.md +0 -28
- package/templates/_agents/workflows//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +0 -16
- package/templates/_githooks/commit-msg +0 -17
- package/templates/_githooks/post-commit +0 -10
- package/templates/_githooks/pre-commit +0 -126
- package/templates/_githooks/pre-merge-commit +0 -29
- package/templates/_githooks/pre-push +0 -136
- package/templates/_github/workflows/kit-ci.yml +0 -49
- package/templates/wiki/INDEX.md +0 -73
- package/templates/wiki/drafts-archive/README.md +0 -3
- package/templates/wiki//344/272/247/345/223/201/351/234/200/346/261/202/README.md +0 -3
- package/templates/wiki//345/274/200/345/217/221/346/226/271/346/241/210/README.md +0 -3
- package/templates/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +0 -3
- package/templates/wiki//346/265/213/350/257/225/346/212/245/345/221/212/README.md +0 -3
- package/templates/wiki//347/224/250/346/210/267/345/212/237/350/203/275/README.md +0 -3
- package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +0 -77
- package/templates/wiki//351/241/271/347/233/256/350/247/204/350/214/203/README.md +0 -3
- package/templates/wiki//351/241/271/347/233/256/350/256/241/345/210/222/README.md +0 -3
- package/templates/workflow/README.md +0 -90
- package/templates/workflow/delegations.md +0 -29
- package/templates/workflow/incidents/_TEMPLATE.md +0 -51
- package/templates/workflow/intents/_TEMPLATE.md +0 -46
- package/templates/workflow/papercuts.md +0 -9
- package/templates/workflow/plans/_TEMPLATE.md +0 -47
- package/templates/workflow/regression-checklist.md +0 -23
- package/templates/workflow/specs/_TEMPLATE.md +0 -80
- /package/templates/{_agents → engine/.agents}/board/marked.min.js +0 -0
- /package/templates/{_agents/workflows → engine/.agents/pipelines}/steps//347/244/272/344/276/213-/351/203/250/347/275/262/351/252/214/350/257/201.md" +0 -0
- /package/templates/{_agents → engine/.agents}/scripts/git-core.mjs +0 -0
|
@@ -0,0 +1,1398 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// dev-tour-model.mjs — 开发全景图数据模型(spec/plan/todo → 步骤 × 文件 × 时序)
|
|
3
|
+
// 单源:workflow 文档正文(步骤 / 任务拆解 / 改动方案 / 系统改动)+ git 工作树(--base 对比)。
|
|
4
|
+
// 零依赖;供 gen-dev-tour.mjs(静态快照)与 workflow-board-server.mjs(/api/tour 实时视图)共用。
|
|
5
|
+
//
|
|
6
|
+
// 设计口径:
|
|
7
|
+
// 1. 文档是「计划面」:步骤顺序、每步做什么、预计改哪些文件,全部从正文解析,不另造一份台账。
|
|
8
|
+
// 2. git 是「实际面」:--base 之后的新增/修改/删除行数、真实文件清单,用于发现「文档外改动」。
|
|
9
|
+
// 3. 两者按文件路径(含相对路径后缀 / basename 兜底)合并;解析不到步骤的文件进「未分步」,
|
|
10
|
+
// 解析不到文档的实际改动进「文档外改动」,不静默丢弃。
|
|
11
|
+
// 4. 时序图不猜业务消息:按每步涉及文件的架构层(前端→API→应用→领域→基础设施→DB)连箭头;
|
|
12
|
+
// 步骤正文若显式写「A → B」仍以文件层为准,保证图与改动面一致。
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { execFileSync } from 'node:child_process';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
export const DEFAULT_ROOT = path.resolve(SCRIPT_DIR, '../..');
|
|
20
|
+
|
|
21
|
+
const CODE_ROOTS = ['backend/', 'frontend/', 'tests/', 'db/', 'database/', 'sql/', 'scripts/', 'tools/', 'integration/'];
|
|
22
|
+
const DOC_ROOTS = ['workflow/', 'wiki/', '.agents/'];
|
|
23
|
+
const IGNORE_DIRS = ['node_modules/', 'bin/', 'obj/', '.git/', '.vs/', 'dist/', 'coverage/', '.agents/cache/', '.agents/tmp/'];
|
|
24
|
+
const CODE_EXT = /\.(cs|ts|tsx|js|jsx|mjs|cjs|vue|sql|json|html|css|scss|csproj|sln|yml|yaml|xml|config|props|targets)$/i;
|
|
25
|
+
|
|
26
|
+
const LAYERS = [
|
|
27
|
+
{ id: 'frontend', label: '前端', rank: 0, test: '前端' },
|
|
28
|
+
{ id: 'api', label: 'API', rank: 1, test: 'API/控制器' },
|
|
29
|
+
{ id: 'app', label: '应用层', rank: 2, test: '应用层' },
|
|
30
|
+
{ id: 'domain', label: '领域层', rank: 3, test: '领域层' },
|
|
31
|
+
{ id: 'infra', label: '基础设施', rank: 4, test: '基础设施' },
|
|
32
|
+
{ id: 'db', label: '数据库', rank: 5, test: '数据库' },
|
|
33
|
+
{ id: 'test', label: '测试', rank: 6, test: '测试' },
|
|
34
|
+
{ id: 'doc', label: '文档', rank: 7, test: '文档' },
|
|
35
|
+
{ id: 'other', label: '其他', rank: 8, test: '其他' },
|
|
36
|
+
];
|
|
37
|
+
const LAYER_BY_ID = new Map(LAYERS.map((l) => [l.id, l]));
|
|
38
|
+
|
|
39
|
+
const STATUS_LABEL = { new: '新增', modified: '修改', deleted: '删除', renamed: '重命名', planned: '计划', unchanged: '未变' };
|
|
40
|
+
const TYPE_LABEL = { intents: 'INTENT', specs: 'SPEC', plans: 'PLAN', incidents: 'INCIDENT' };
|
|
41
|
+
|
|
42
|
+
// ---------------- 通用小工具 ----------------
|
|
43
|
+
export function normPath(p) {
|
|
44
|
+
return String(p || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+/g, '/');
|
|
45
|
+
}
|
|
46
|
+
function stripLineNo(p) {
|
|
47
|
+
return p.replace(/:\d+(?:-\d+)?$/, '').replace(/[,。;、,;]+$/, '');
|
|
48
|
+
}
|
|
49
|
+
function uniq(a) { return [...new Set(a)]; }
|
|
50
|
+
function clamp(s, n) { s = String(s || ''); return s.length > n ? s.slice(0, n - 1) + '…' : s; }
|
|
51
|
+
function rel(root, abs) { return normPath(path.relative(root, abs)); }
|
|
52
|
+
function firstLine(s) { return String(s || '').split(/\r?\n/).map((x) => x.trim()).find(Boolean) || ''; }
|
|
53
|
+
|
|
54
|
+
function isIgnored(p) {
|
|
55
|
+
const s = '/' + normPath(p);
|
|
56
|
+
return IGNORE_DIRS.some((d) => s.includes('/' + d)) || /(^|\/)\.tour\.html$/.test(p);
|
|
57
|
+
}
|
|
58
|
+
function inCodeScope(p) {
|
|
59
|
+
return CODE_ROOTS.some((r) => p.startsWith(r)) && !isIgnored(p);
|
|
60
|
+
}
|
|
61
|
+
function isDocPath(p) {
|
|
62
|
+
return DOC_ROOTS.some((r) => p.startsWith(r)) || /\.md$/i.test(p);
|
|
63
|
+
}
|
|
64
|
+
function looksLikePath(s) {
|
|
65
|
+
if (!s) return false;
|
|
66
|
+
s = stripLineNo(normPath(s));
|
|
67
|
+
if (!s || /\s/.test(s)) return false;
|
|
68
|
+
if (/^[a-z]+:\/\//i.test(s)) return false;
|
|
69
|
+
if (/^(node:|System\.|Microsoft\.)/i.test(s)) return false;
|
|
70
|
+
if (s.includes('*') || s.includes('<') || s.includes('>') || s.startsWith('/')) return false;
|
|
71
|
+
if (s.includes('=') || s.startsWith('-')) return false;
|
|
72
|
+
if (/^[A-Za-z]:\//.test(s)) return false;
|
|
73
|
+
if (isIgnored(s)) return false;
|
|
74
|
+
if (!s.includes('/') && !CODE_EXT.test(s) && !/\.(md|cs|ts|vue|sql)$/i.test(s)) return false;
|
|
75
|
+
if (s.includes('/') && !CODE_EXT.test(s) && !CODE_ROOTS.some((r) => s.startsWith(r)) && !DOC_ROOTS.some((r) => s.startsWith(r))) {
|
|
76
|
+
// 允许 Application/Services/Foo.cs 这类相对路径
|
|
77
|
+
if (!/\/[^/]+\.[A-Za-z0-9]+$/.test(s)) return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------- 文档解析 ----------------
|
|
83
|
+
export function parseFrontmatter(text) {
|
|
84
|
+
const out = {};
|
|
85
|
+
const src = String(text || '').replace(/^\uFEFF/, '');
|
|
86
|
+
if (!src.startsWith('---')) return out;
|
|
87
|
+
const end = src.indexOf('\n---', 3);
|
|
88
|
+
if (end < 0) return out;
|
|
89
|
+
const head = src.slice(3, end).split(/\r?\n/);
|
|
90
|
+
for (const line of head) {
|
|
91
|
+
const m = line.replace(/\r$/, '').match(/^([^:#]+):\s*(.*)$/);
|
|
92
|
+
if (m) out[m[1].trim()] = m[2].trim();
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseSections(text) {
|
|
98
|
+
const lines = String(text || '').replace(/^\uFEFF/, '').split(/\r?\n/);
|
|
99
|
+
// 跳过 frontmatter 后再按标题切;正文标题只认 ^## / ^###
|
|
100
|
+
const sections = [];
|
|
101
|
+
let cur = null;
|
|
102
|
+
let fence = false;
|
|
103
|
+
// body 做 trim 的同时记录被剥掉的前导空行数:行号 = start + lead + j + 2(j 为 trim 后 body 行下标)
|
|
104
|
+
const sliceBody = (from, to) => {
|
|
105
|
+
const raw = lines.slice(from, to);
|
|
106
|
+
const lead = raw.findIndex((x) => x.trim() !== '');
|
|
107
|
+
return { body: raw.join('\n').trim(), lead: lead < 0 ? raw.length : lead };
|
|
108
|
+
};
|
|
109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
110
|
+
const line = lines[i];
|
|
111
|
+
if (/^\s*```/.test(line)) fence = !fence;
|
|
112
|
+
const m = !fence && line.match(/^(#{2,4})\s+(.+?)\s*$/);
|
|
113
|
+
if (m) {
|
|
114
|
+
if (cur) { cur.end = i; Object.assign(cur, sliceBody(cur.start + 1, i)); sections.push(cur); }
|
|
115
|
+
cur = { level: m[1].length, title: m[2].trim(), start: i, end: lines.length, body: '', lead: 0 };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (cur) { cur.end = lines.length; Object.assign(cur, sliceBody(cur.start + 1, lines.length)); sections.push(cur); }
|
|
119
|
+
// frontmatter 内的 `---` 不是标题;上面只匹配 # 开头,安全。
|
|
120
|
+
return sections;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function extractTitle(text) {
|
|
124
|
+
const m = String(text || '').match(/^#\s+(.+?)\s*$/m);
|
|
125
|
+
if (!m) return '';
|
|
126
|
+
return m[1].replace(/^(PLAN|SPEC|INTENT|INCIDENT)\s*[—\-–]\s*/i, '').trim();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sectionByTitle(sections, names) {
|
|
130
|
+
const wanted = names.map((n) => n.replace(/[((].*$/, '').trim());
|
|
131
|
+
return sections.find((s) => {
|
|
132
|
+
const t = s.title.replace(/[((].*$/, '').trim();
|
|
133
|
+
return wanted.includes(t) || wanted.some((w) => s.title.startsWith(w));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function childSections(sections, parent) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const sec of sections) {
|
|
139
|
+
if (sec.start <= parent.start) continue;
|
|
140
|
+
if (sec.level <= parent.level) break;
|
|
141
|
+
if (sec.level === parent.level + 1) out.push(sec);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function extractPaths(text, repo = null) {
|
|
147
|
+
const out = new Set();
|
|
148
|
+
const add = (raw) => {
|
|
149
|
+
let s = stripLineNo(String(raw || '').trim());
|
|
150
|
+
s = s.replace(/^[`'"“”‘’((\s]+|[`'"“”‘’())\s]+$/g, '');
|
|
151
|
+
s = normPath(s);
|
|
152
|
+
if (looksLikePath(s)) out.add(s);
|
|
153
|
+
};
|
|
154
|
+
const src = String(text || '');
|
|
155
|
+
// 1) 反引号:文档里最主要的路径书写方式
|
|
156
|
+
for (const m of src.matchAll(/`([^`\n]{2,240})`/g)) {
|
|
157
|
+
add(m[1]);
|
|
158
|
+
}
|
|
159
|
+
// 2) 已知根目录的裸路径(含中文标点边界)
|
|
160
|
+
for (const m of src.matchAll(/(?:^|[\s((::,,;;])((?:backend|frontend|tests|db|database|sql|scripts|tools|integration|wiki|workflow|\.agents)\/[^\s`'"“”‘’,。;、))]+)/g)) {
|
|
161
|
+
add(m[1]);
|
|
162
|
+
}
|
|
163
|
+
// 3) 未反引号的中段路径(如步骤标题里的 pml/index.vue):含 / + 已知代码扩展名 + 能消解到仓库真实文件,
|
|
164
|
+
// 三条件同时满足才收——否则静默丢弃(防 if/else 类杂型 token 噪音,上轮实测 719 个)。
|
|
165
|
+
// 消解复用 resolvePath 但不给上下文(确定性后缀/目录反推/basename 唯一,不打分不猜测)。
|
|
166
|
+
if (repo) {
|
|
167
|
+
for (const m of src.matchAll(/[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+/g)) {
|
|
168
|
+
const cand = normPath(m[0].replace(/\.+$/, ''));
|
|
169
|
+
if (out.has(cand) || !cand.includes('/') || /\/\//.test(cand) || !CODE_EXT.test(cand)) continue;
|
|
170
|
+
if (!repo.set.has(resolvePath(cand, new Set(), repo, []))) continue;
|
|
171
|
+
out.add(cand);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return [...out];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function labelOf(raw) {
|
|
178
|
+
const s = String(raw || '').trim();
|
|
179
|
+
const m = s.match(/^(?:批次\s*)?([A-Za-z]{1,4}[-_]?\d+[A-Za-z]?|[0-9]+[A-Za-z]?(?:\.[0-9]+)?|批次\s*[0-9A-Za-z]+)\s*[::、.\-—·•]?\s*(.*)$/);
|
|
180
|
+
if (!m) return { id: '', title: s };
|
|
181
|
+
return { id: m[1].replace(/\s+/g, ''), title: m[2].trim() || m[1] };
|
|
182
|
+
}
|
|
183
|
+
function sameLabel(a, b) {
|
|
184
|
+
const x = String(a || '').replace(/\s+/g, '').toLowerCase();
|
|
185
|
+
const y = String(b || '').replace(/\s+/g, '').toLowerCase();
|
|
186
|
+
return x && y && (x === y || x === 't' + y || y === 't' + x);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 步骤可带并集标签成员集(任务行 `1. S1+S2:…` → ids=['S1','S2']);块↔步骤按成员交集命中
|
|
190
|
+
function stepLabels(st) {
|
|
191
|
+
return st.ids && st.ids.length ? st.ids : [st.id];
|
|
192
|
+
}
|
|
193
|
+
function blockMatchesStep(b, st) {
|
|
194
|
+
const bids = b.ids && b.ids.length ? b.ids : (b.id ? [b.id] : []);
|
|
195
|
+
return (bids.length && stepLabels(st).some((sid) => bids.some((bid) => sameLabel(bid, sid)))) ||
|
|
196
|
+
(b.title && (b.title.includes(st.title) || st.title.includes(b.title)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function newStep(o) {
|
|
200
|
+
return {
|
|
201
|
+
id: '', index: 0, title: '', summary: '', detail: [], detailPos: [],
|
|
202
|
+
files: [], status: 'todo', source: 'doc', raw: '',
|
|
203
|
+
line: 0,
|
|
204
|
+
...o,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 从 `## 步骤` 的编号列表提取
|
|
209
|
+
function stepsFromNumberedSection(sec) {
|
|
210
|
+
const steps = [];
|
|
211
|
+
let cur = null;
|
|
212
|
+
for (const [j, line] of sec.body.split(/\r?\n/).entries()) {
|
|
213
|
+
const m = line.match(/^\s*(\d+)\.\s+(.*)$/);
|
|
214
|
+
if (m) {
|
|
215
|
+
if (cur) steps.push(cur);
|
|
216
|
+
let body = m[2].trim();
|
|
217
|
+
let title = body;
|
|
218
|
+
let summary = '';
|
|
219
|
+
const bold = body.match(/^\*\*(.+?)\*\*\s*[::]?\s*(.*)$/);
|
|
220
|
+
if (bold) { title = bold[1].trim(); summary = bold[2].trim(); }
|
|
221
|
+
else {
|
|
222
|
+
const c = body.indexOf(':');
|
|
223
|
+
if (c >= 0) { title = body.slice(0, c).trim(); summary = body.slice(c + 1).trim(); }
|
|
224
|
+
}
|
|
225
|
+
const lb = labelOf(title);
|
|
226
|
+
cur = newStep({
|
|
227
|
+
id: lb.id || `S${m[1]}`,
|
|
228
|
+
title: lb.title || title,
|
|
229
|
+
summary,
|
|
230
|
+
raw: line,
|
|
231
|
+
source: 'steps',
|
|
232
|
+
line: sec.start + sec.lead + j + 2,
|
|
233
|
+
});
|
|
234
|
+
if (lb.id && title.startsWith(lb.id)) cur.title = lb.title;
|
|
235
|
+
} else if (cur) {
|
|
236
|
+
const t = line.trim();
|
|
237
|
+
if (t) { cur.detail.push(t); cur.detailPos.push(sec.start + sec.lead + j + 2); }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (cur) steps.push(cur);
|
|
241
|
+
return steps;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 从 `## 任务拆解` 的 checkbox 列表提取
|
|
245
|
+
function stepsFromTaskSection(sec) {
|
|
246
|
+
const steps = [];
|
|
247
|
+
let cur = null;
|
|
248
|
+
for (const [j, line] of sec.body.split(/\r?\n/).entries()) {
|
|
249
|
+
const m = line.match(/^\s*-\s*\[([ xX])\]\s*(?:(\d+)[.、]\s*)?(.*)$/);
|
|
250
|
+
if (m) {
|
|
251
|
+
if (cur) steps.push(cur);
|
|
252
|
+
const done = m[1].toLowerCase() === 'x';
|
|
253
|
+
const raw = m[3].trim();
|
|
254
|
+
// 任务行标签:并集式(`S1+S2:标题` / `S8+S9 收口`)拆成员集;单个(`T1 标题` / `S7:标题`)走 labelOf
|
|
255
|
+
// ——仅认显式编号标签(并集/单个),粗体无编号锚与词面模糊匹配不做:宁可不绑,留失约可见
|
|
256
|
+
const um = raw.match(/^([A-Za-z]{1,4}[-_]?\d+[A-Za-z]?(?:\s*\+\s*[A-Za-z]{1,4}[-_]?\d+[A-Za-z]?)+)(?:\s|[::]\s*)(.*)$/);
|
|
257
|
+
const lb = /^[A-Za-z]{1,4}[-_]?\d+[A-Za-z]?(?:\s|[::])/.test(raw) ? labelOf(raw) : { id: '', title: raw };
|
|
258
|
+
const ids = um ? um[1].replace(/\s+/g, '').split('+') : null;
|
|
259
|
+
cur = newStep({
|
|
260
|
+
id: um ? ids[0] : (lb.id || (m[2] ? 'T' + m[2] : `S${steps.length + 1}`)),
|
|
261
|
+
ids,
|
|
262
|
+
title: um ? um[2].trim() : (lb.title || raw),
|
|
263
|
+
summary: '',
|
|
264
|
+
status: done ? 'done' : 'todo',
|
|
265
|
+
raw: line,
|
|
266
|
+
source: 'tasks',
|
|
267
|
+
line: sec.start + sec.lead + j + 2,
|
|
268
|
+
});
|
|
269
|
+
} else if (cur) {
|
|
270
|
+
const t = line.trim();
|
|
271
|
+
if (t) {
|
|
272
|
+
if (/^-\s*判据[::]/.test(t)) cur.summary = t.replace(/^-\s*判据[::]\s*/, '');
|
|
273
|
+
else { cur.detail.push(t); cur.detailPos.push(sec.start + sec.lead + j + 2); }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (cur) steps.push(cur);
|
|
278
|
+
return steps;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 把 `## 改动方案` / `## 改动面` / `## 系统改动清单` 拆成块
|
|
282
|
+
function blocksFromChangeSection(sec, splitFiles = false) {
|
|
283
|
+
const blocks = [];
|
|
284
|
+
let cur = null;
|
|
285
|
+
let last = null; // 最近落盘的块
|
|
286
|
+
let openParent = null; // 连续缩进锚共享的无锚父文件行
|
|
287
|
+
const flush = () => { if (cur) { blocks.push(cur); last = cur; cur = null; } };
|
|
288
|
+
for (const [j, line] of sec.body.split(/\r?\n/).entries()) {
|
|
289
|
+
const h = line.match(/^###\s+(.+)$/);
|
|
290
|
+
const b = line.match(/^\s*-\s+\*\*(.+?)\*\*\s*[::]?\s*(.*)$/);
|
|
291
|
+
const plain = line.match(/^\s*-\s+(.+)$/);
|
|
292
|
+
if (h || b) {
|
|
293
|
+
flush();
|
|
294
|
+
const raw = (h ? h[1] : b[1] + (b[2] ? ':' + b[2] : '')).trim();
|
|
295
|
+
const lb = labelOf(raw);
|
|
296
|
+
const indented = /^\s/.test(line);
|
|
297
|
+
cur = { id: lb.id, title: lb.title || raw, raw, lines: [], line: sec.start + sec.lead + j + 2 };
|
|
298
|
+
// 缩进锚(` - **S1**`)继承无锚父文件行:子锚描述的就是对父文件的改动,全部连续子锚都继承;
|
|
299
|
+
// 父块自身无标签,单独成块只会被重复归集进「改动面(未分步)」
|
|
300
|
+
if (indented && (openParent || (last && !last.id && last.isParentFile))) {
|
|
301
|
+
const parent = openParent || last;
|
|
302
|
+
cur.lines.unshift(...parent.lines);
|
|
303
|
+
parent.dead = true;
|
|
304
|
+
openParent = parent;
|
|
305
|
+
} else {
|
|
306
|
+
openParent = null;
|
|
307
|
+
}
|
|
308
|
+
} else if (plain) {
|
|
309
|
+
const txt = plain[1].trim();
|
|
310
|
+
const paths = extractPaths(txt);
|
|
311
|
+
const topLevel = !/^\s+/.test(line);
|
|
312
|
+
const startsWithPath = /^`[^`]+`/.test(txt);
|
|
313
|
+
// 顶层括注编号标签(`- \`path\`(**S7**):…`)也是块锚;须带路径才提升,纯散文括注不认
|
|
314
|
+
const pm = topLevel && paths.length ? txt.match(/[((]\*\*([A-Za-z]{1,4}[-_]?\d+[A-Za-z]?(?:\s*\+\s*[A-Za-z]{1,4}[-_]?\d+[A-Za-z]?)*)\*\*[))]/) : null;
|
|
315
|
+
if (pm || (splitFiles && topLevel && startsWithPath && cur && cur.lines.length)) flush();
|
|
316
|
+
if (topLevel) openParent = null;
|
|
317
|
+
if (pm) {
|
|
318
|
+
const first = paths[0] || '';
|
|
319
|
+
const ids = pm[1].replace(/\s+/g, '').split('+');
|
|
320
|
+
cur = { id: ids[0], ids: ids.length > 1 ? ids : undefined, title: first ? first.split('/').pop() : '', raw: txt, lines: [txt], line: sec.start + sec.lead + j + 2 };
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!cur) {
|
|
324
|
+
const first = paths[0] || '';
|
|
325
|
+
cur = { id: '', title: first ? first.split('/').pop() : '', raw: txt, lines: [], line: sec.start + sec.lead + j + 2, isParentFile: topLevel && startsWithPath };
|
|
326
|
+
}
|
|
327
|
+
cur.lines.push(txt);
|
|
328
|
+
} else if (/^\s*\|/.test(line) && line.trim()) {
|
|
329
|
+
// 表格行(`| \`path\` | 说明 |`):路径归入当前块上下文;无块上下文时自成无锚块 → 未分步可见
|
|
330
|
+
const txt = line.trim();
|
|
331
|
+
const paths = extractPaths(txt);
|
|
332
|
+
if (!cur) {
|
|
333
|
+
const first = paths[0] || '';
|
|
334
|
+
cur = { id: '', title: first ? first.split('/').pop() : '', raw: txt, lines: [], line: sec.start + sec.lead + j + 2 };
|
|
335
|
+
}
|
|
336
|
+
cur.lines.push(txt);
|
|
337
|
+
} else if (cur && line.trim()) {
|
|
338
|
+
cur.lines.push(line.trim());
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
flush();
|
|
342
|
+
// 只保留有文件或标题的块;dead = 已被子锚继承的无锚父块
|
|
343
|
+
return blocks.filter((b) => !b.dead && (b.lines.length || b.title));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function inferLayer(p) {
|
|
347
|
+
const s = normPath(p);
|
|
348
|
+
if (s.startsWith('tests/') || /\.(test|spec)\.[tj]sx?$/i.test(s)) return 'test';
|
|
349
|
+
if (s.startsWith('frontend/') || /\.(vue|css|scss)$/i.test(s)) return 'frontend';
|
|
350
|
+
if (/\/Controllers?\//i.test(s) || s.startsWith('backend/') && /\.API\//i.test(s)) return 'api';
|
|
351
|
+
if (/\/Domain\//i.test(s) || /\/Entities?\//i.test(s)) return 'domain';
|
|
352
|
+
if (/\/Infrastructure\//i.test(s) || /DbContext/i.test(s) || /\/Repositories?\//i.test(s)) return 'infra';
|
|
353
|
+
if (s.startsWith('db/') || s.startsWith('database/') || s.startsWith('sql/') || /\.sql$/i.test(s)) return 'db';
|
|
354
|
+
if (s.startsWith('backend/') || /\/Application\//i.test(s) || /\/Services?\//i.test(s) || /\/Dtos?\//i.test(s)) return 'app';
|
|
355
|
+
if (isDocPath(s) || s.startsWith('.agents/')) return 'doc';
|
|
356
|
+
return 'other';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function inferLayersFromText(text) {
|
|
360
|
+
const s = String(text || '');
|
|
361
|
+
const out = [];
|
|
362
|
+
if (/前端|页面|按钮|表单|弹窗|抽屉|vue|store|路由/i.test(s)) out.push('frontend');
|
|
363
|
+
if (/API|控制器|Controller|端点|接口\s*\/|路由参数/i.test(s)) out.push('api');
|
|
364
|
+
if (/应用层|服务|Service|映射|编排|DTO|用例/i.test(s)) out.push('app');
|
|
365
|
+
if (/领域|Domain|实体|Entity|值对象/i.test(s)) out.push('domain');
|
|
366
|
+
if (/仓储|Repository|EF|DbContext|基础设施|Infrastructure/i.test(s)) out.push('infra');
|
|
367
|
+
if (/数据库|SQL|表结构|schema|迁移|字段/i.test(s)) out.push('db');
|
|
368
|
+
if (/测试|用例|单测|集成测试|Test/i.test(s)) out.push('test');
|
|
369
|
+
return [...new Set(out)];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function fileRecord(p, o = {}) {
|
|
373
|
+
return {
|
|
374
|
+
path: normPath(p), status: 'planned', badge: STATUS_LABEL.planned,
|
|
375
|
+
additions: 0, deletions: 0, binary: false, test: false, layer: inferLayer(p),
|
|
376
|
+
planned: false, actual: false, steps: [], diff: '', oldPath: '',
|
|
377
|
+
pathGuess: false,
|
|
378
|
+
...o,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const REPO_CACHE = new Map();
|
|
383
|
+
function trackedIndex(root) {
|
|
384
|
+
const hit = REPO_CACHE.get(root);
|
|
385
|
+
if (hit && Date.now() - hit.at < 30000) return hit.index;
|
|
386
|
+
const out = git(['ls-files'], root);
|
|
387
|
+
const files = out ? out.split(/\r?\n/).map((x) => normPath(x.trim())).filter(Boolean) : [];
|
|
388
|
+
const set = new Set(files);
|
|
389
|
+
const byBase = new Map();
|
|
390
|
+
const dirs = new Set();
|
|
391
|
+
for (const p of files) {
|
|
392
|
+
const base = p.split('/').pop();
|
|
393
|
+
if (!byBase.has(base)) byBase.set(base, []);
|
|
394
|
+
byBase.get(base).push(p);
|
|
395
|
+
const parts = p.split('/');
|
|
396
|
+
for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join('/'));
|
|
397
|
+
}
|
|
398
|
+
const index = { files, set, byBase, dirs: [...dirs] };
|
|
399
|
+
REPO_CACHE.set(root, { at: Date.now(), index });
|
|
400
|
+
return index;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function contextTokens(doc, step) {
|
|
404
|
+
const raw = [
|
|
405
|
+
doc.title, doc.module, step?.title, step?.summary,
|
|
406
|
+
...(step?.detail || []),
|
|
407
|
+
].join(' ');
|
|
408
|
+
const tokens = raw.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
409
|
+
// 同一批文件里已解析出的目录名也是强上下文(如 api/materialCode.ts → materialcode)
|
|
410
|
+
for (const p of step?.files || []) {
|
|
411
|
+
for (const seg of p.split('/')) {
|
|
412
|
+
const t = seg.replace(/\.[A-Za-z0-9]+$/, '').toLowerCase();
|
|
413
|
+
if (t.length >= 4) tokens.push(t);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return [...new Set(tokens)];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function resolvePath(p, universe, repo, context = [], guesses = null) {
|
|
420
|
+
const s = normPath(p);
|
|
421
|
+
if (!s) return s;
|
|
422
|
+
// 1) 真实仓库文件优先:相对路径先按后缀命中,避免被「文档里的原样路径」抢先
|
|
423
|
+
if (repo?.set.has(s)) return s;
|
|
424
|
+
const rsuffix = s.includes('/') ? [...(repo?.set || [])].find((x) => x.endsWith('/' + s)) : null;
|
|
425
|
+
if (rsuffix) return rsuffix;
|
|
426
|
+
// 2) 绝对根路径才直接信文档原样;相对路径继续找真实前缀
|
|
427
|
+
const rooted = CODE_ROOTS.some((r) => s.startsWith(r)) || DOC_ROOTS.some((r) => s.startsWith(r));
|
|
428
|
+
if (rooted && universe.has(s)) return s;
|
|
429
|
+
if (rooted && s.includes('/')) {
|
|
430
|
+
const us = [...universe].find((x) => x.endsWith('/' + s));
|
|
431
|
+
if (us) return us;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const base = s.split('/').pop();
|
|
435
|
+
const dir = s.includes('/') ? s.split('/').slice(0, -1).join('/') : '';
|
|
436
|
+
if (dir && repo) {
|
|
437
|
+
// 3a) 目录本身是已存在目录(候选必须是真实跟踪文件才可返回——反推不验证存在 = 制造假路径,宁可原样未解析)
|
|
438
|
+
const cands = repo.dirs.filter((d) => d === dir || d.endsWith('/' + dir));
|
|
439
|
+
if (cands.length === 1 && repo.set.has(cands[0] + '/' + base)) return cands[0] + '/' + base;
|
|
440
|
+
// 3b) 目录不是完整目录(如 Dtos 下只有子目录):用任意已跟踪文件的路径前缀反推(同样须验证候选真实存在)
|
|
441
|
+
const marker = dir + '/';
|
|
442
|
+
const hits = repo.files.filter((f) => { const i = f.indexOf(marker); return i >= 0 && (i === 0 || f[i - 1] === '/' || f[i - 1] === '.'); });
|
|
443
|
+
if (hits.length) {
|
|
444
|
+
const prefixes = uniq(hits.map((f) => f.slice(0, f.indexOf(marker))));
|
|
445
|
+
if (prefixes.length === 1 && repo.set.has(prefixes[0] + dir + '/' + base)) return prefixes[0] + dir + '/' + base;
|
|
446
|
+
if (prefixes.length > 1 && context.length) {
|
|
447
|
+
const ranked = prefixes.map((p) => ({ p, sc: context.reduce((n, t) => n + (p.toLowerCase().includes(t) ? 1 : 0), 0) }))
|
|
448
|
+
.sort((a, b) => b.sc - a.sc);
|
|
449
|
+
const cand = ranked[0].p + dir + '/' + base;
|
|
450
|
+
if (ranked[0].sc > 0 && ranked[0].sc > (ranked[1]?.sc || 0) && repo.set.has(cand)) {
|
|
451
|
+
// 多候选上下文打分 = 路径猜测(basename 唯一命中不算)——guesses 供 parseHealth.pathGuesses 计量
|
|
452
|
+
if (guesses) guesses.push({ path: s, candidates: prefixes.length });
|
|
453
|
+
return cand;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// 3c) 从最长后缀逐段回退(同样须验证候选真实存在,否则继续回退直至原样)
|
|
458
|
+
const segs = dir.split('/');
|
|
459
|
+
for (let i = 1; i < segs.length; i++) {
|
|
460
|
+
const tail = segs.slice(i).join('/');
|
|
461
|
+
const c = repo.dirs.filter((d) => d === tail || d.endsWith('/' + tail));
|
|
462
|
+
if (c.length === 1 && repo.set.has(c[0] + '/' + base)) return c[0] + '/' + base;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// 4) 已合并集合(实际改动 / 文档路径);裸文件名继续走 basename 解析
|
|
466
|
+
const isBare = !s.includes('/');
|
|
467
|
+
if (!isBare && universe.has(s)) return s;
|
|
468
|
+
const usuffix2 = [...universe].find((x) => x.endsWith('/' + s));
|
|
469
|
+
if (usuffix2) return usuffix2;
|
|
470
|
+
// 5) basename 唯一 → 直接命中
|
|
471
|
+
if (!isBare) {
|
|
472
|
+
const uBase = [...universe].filter((x) => x.split('/').pop() === base);
|
|
473
|
+
if (uBase.length === 1) return uBase[0];
|
|
474
|
+
}
|
|
475
|
+
const rBase = repo?.byBase.get(base) || [];
|
|
476
|
+
if (rBase.length === 1) {
|
|
477
|
+
// basename 唯一命中(裸文件名的 P1 机制);带目录的 raw 仅当其目录在仓库里确实存在才兜底,
|
|
478
|
+
// 否则该目录是笔误/未落库路径(如 tmp/perf/),原样返回保持可见,不跨目录冒名顶替
|
|
479
|
+
if (isBare || repo.dirs.some((d) => d === dir || d.endsWith('/' + dir))) return rBase[0];
|
|
480
|
+
}
|
|
481
|
+
// 6) basename 多候选 → 用文档/步骤上下文打分(materialCode 页面下的 detail.vue 等)
|
|
482
|
+
if (rBase.length > 1 && context.length) {
|
|
483
|
+
let best = null; let bestScore = 0; let tie = false;
|
|
484
|
+
for (const c of rBase) {
|
|
485
|
+
const lc = c.toLowerCase();
|
|
486
|
+
let sc = 0;
|
|
487
|
+
for (const t of context) if (lc.includes(t)) sc += t.length >= 8 ? 2 : 1;
|
|
488
|
+
if (sc > bestScore) { best = c; bestScore = sc; tie = false; }
|
|
489
|
+
else if (sc === bestScore && sc > 0) tie = true;
|
|
490
|
+
}
|
|
491
|
+
if (best && !tie) {
|
|
492
|
+
// basename 多候选打分 = 路径猜测;guesses 由 buildTour 聚合进 parseHealth.pathGuesses
|
|
493
|
+
if (guesses) guesses.push({ path: s, candidates: rBase.length });
|
|
494
|
+
return best;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return s;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ---------------- git 实际面 ----------------
|
|
501
|
+
function git(args, root) {
|
|
502
|
+
try {
|
|
503
|
+
return execFileSync('git', args, {
|
|
504
|
+
cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, windowsHide: true,
|
|
505
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
506
|
+
});
|
|
507
|
+
} catch {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export function collectGit({ root = DEFAULT_ROOT, base = 'HEAD', includeAll = false, includeDocs = false, withDiff = true, maxDiffLines = 160 } = {}) {
|
|
513
|
+
const info = { available: false, base, files: new Map(), basePaths: new Set(), note: '' };
|
|
514
|
+
const top = git(['rev-parse', '--show-toplevel'], root);
|
|
515
|
+
if (!top || normPath(top.trim()) !== normPath(root)) {
|
|
516
|
+
info.note = '不在 git 仓库根或 git 不可用';
|
|
517
|
+
return info;
|
|
518
|
+
}
|
|
519
|
+
const resolved = git(['rev-parse', '--verify', `${base}^{commit}`], root);
|
|
520
|
+
if (!resolved) { info.note = `base 不存在:${base}`; return info; }
|
|
521
|
+
info.available = true;
|
|
522
|
+
info.baseSha = resolved.trim().slice(0, 12);
|
|
523
|
+
|
|
524
|
+
// 合并进行中(MERGE_HEAD 存在):工作树 vs HEAD 的差异 = 被合入分支的整批变更,不是「文档外实际改动」。
|
|
525
|
+
// 2026-11-10 实证:merge 期间 pre-commit 的 dev-tour「默认路径回归」恒多出一层 git 步,合并提交被阻断
|
|
526
|
+
//(多文件合并无法自证——测试读的是活工作树,不是夹具)。合并完成后的下一次采集照常。
|
|
527
|
+
if (git(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], root)) {
|
|
528
|
+
info.note = '合并进行中(MERGE_HEAD):跳过实际改动面采集';
|
|
529
|
+
return info;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const tree = git(['ls-tree', '-r', '--name-only', base], root);
|
|
533
|
+
if (tree) for (const l of tree.split(/\r?\n/)) if (l.trim()) info.basePaths.add(normPath(l.trim()));
|
|
534
|
+
|
|
535
|
+
const nameStatus = git(['diff', '--name-status', '-M', base, '--'], root);
|
|
536
|
+
if (nameStatus) {
|
|
537
|
+
for (const l of nameStatus.split(/\r?\n/)) {
|
|
538
|
+
if (!l.trim()) continue;
|
|
539
|
+
const parts = l.split('\t');
|
|
540
|
+
const code = parts[0][0];
|
|
541
|
+
const oldP = normPath(parts[1] || '');
|
|
542
|
+
const newP = normPath(parts[2] || parts[1] || '');
|
|
543
|
+
const p = newP;
|
|
544
|
+
if (!p || isIgnored(p)) continue;
|
|
545
|
+
const status = code === 'A' ? 'new' : code === 'D' ? 'deleted' : code === 'R' ? 'renamed' : 'modified';
|
|
546
|
+
const rec = fileRecord(p, { status, badge: STATUS_LABEL[status], actual: true, oldPath: code === 'R' ? oldP : '' });
|
|
547
|
+
info.files.set(p, rec);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const numstat = git(['diff', '--numstat', '-M', base, '--'], root);
|
|
552
|
+
if (numstat) {
|
|
553
|
+
for (const l of numstat.split(/\r?\n/)) {
|
|
554
|
+
if (!l.trim()) continue;
|
|
555
|
+
const parts = l.split('\t');
|
|
556
|
+
if (parts.length < 3) continue;
|
|
557
|
+
const add = parts[0] === '-' ? 0 : Number(parts[0]) || 0;
|
|
558
|
+
const del = parts[1] === '-' ? 0 : Number(parts[1]) || 0;
|
|
559
|
+
let p = parts.slice(2).join('\t');
|
|
560
|
+
if (p.includes('=>')) p = p.split('=>').pop().trim();
|
|
561
|
+
p = normPath(p);
|
|
562
|
+
const rec = info.files.get(p);
|
|
563
|
+
if (rec) { rec.additions = add; rec.deletions = del; rec.binary = parts[0] === '-' && parts[1] === '-'; }
|
|
564
|
+
else if (!isIgnored(p)) info.files.set(p, fileRecord(p, { status: 'modified', badge: STATUS_LABEL.modified, actual: true, additions: add, deletions: del }));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// untracked:git diff 看不到,单独枚举
|
|
569
|
+
const untracked = git(['ls-files', '--others', '--exclude-standard'], root);
|
|
570
|
+
if (untracked) {
|
|
571
|
+
for (const l of untracked.split(/\r?\n/)) {
|
|
572
|
+
const p = normPath(l.trim());
|
|
573
|
+
if (!p || isIgnored(p)) continue;
|
|
574
|
+
if (!info.files.has(p)) {
|
|
575
|
+
let lines = 0;
|
|
576
|
+
try { lines = fs.readFileSync(path.join(root, p), 'utf8').split(/\r?\n/).length; } catch { /* 读不到按 0 */ }
|
|
577
|
+
info.files.set(p, fileRecord(p, { status: 'new', badge: STATUS_LABEL.new, actual: true, additions: lines }));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// 代码面过滤:默认只留产品路径 + 文档显式登记的文件;--all 放开
|
|
583
|
+
for (const [p, rec] of [...info.files]) {
|
|
584
|
+
if (includeAll) continue;
|
|
585
|
+
if (!inCodeScope(p) && !includeDocs) info.files.delete(p);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (withDiff && info.files.size) {
|
|
589
|
+
let budget = 220 * 1024;
|
|
590
|
+
for (const [p, rec] of info.files) {
|
|
591
|
+
if (rec.status === 'deleted' || budget <= 0) continue;
|
|
592
|
+
let d = git(['diff', '--no-color', '--unified=2', base, '--', p], root) || '';
|
|
593
|
+
if (!d && rec.status === 'new') {
|
|
594
|
+
try {
|
|
595
|
+
const body = fs.readFileSync(path.join(root, p), 'utf8');
|
|
596
|
+
const lines = body.split(/\r?\n/).slice(0, maxDiffLines);
|
|
597
|
+
d = [`diff --git a/${p} b/${p}`, 'new file mode 100644', '--- /dev/null', `+++ b/${p}`, `@@ -0,0 +1,${lines.length} @@`, ...lines.map((x) => '+' + x)].join('\n');
|
|
598
|
+
} catch { d = ''; }
|
|
599
|
+
}
|
|
600
|
+
if (!d) continue;
|
|
601
|
+
const arr = d.split(/\r?\n/);
|
|
602
|
+
const clipped = arr.length > maxDiffLines;
|
|
603
|
+
d = arr.slice(0, maxDiffLines).join('\n') + (clipped ? `\n… 已截断(原 ${arr.length} 行,用 git diff ${base} -- ${p} 查看完整)` : '');
|
|
604
|
+
rec.diff = d;
|
|
605
|
+
budget -= d.length;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return info;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ---------------- 数据流 ----------------
|
|
612
|
+
// 从 diff 新增行 + 步骤正文静态派生「数据对象 × 文件 × 架构层 × 步骤」:
|
|
613
|
+
// 定义模式识别对象(entity/dto/table)→ 「diff 新增行出现」关联文件、「正文出现 / 文件归属」关联步骤。
|
|
614
|
+
const DF_DEF_PATTERNS = [
|
|
615
|
+
{ re: /\b(?:class|record|interface|type)\s+([A-Z]\w{3,})/g, table: false }, // C#/TS 类型定义
|
|
616
|
+
{ re: /\bDbSet<([A-Z]\w{3,})>/g, table: false },
|
|
617
|
+
{ re: /\bToTable\(\s*["'`]?(\w{3,})/g, table: true },
|
|
618
|
+
{ re: /\bCREATE\s+TABLE\s+(?:\[?\w+\]?\s*\.\s*)?\[?(\w{3,})\]?/gi, table: true },
|
|
619
|
+
];
|
|
620
|
+
const DF_DTO_RE = /(Dto|Request|Response|Payload|ViewModel|Input|Result|Query)$/;
|
|
621
|
+
const DF_NOISE_RE = /(Controller|Service|Handler|Validator|Mapper|Profile|Extension|Configuration|Options|Settings|Exception|Middleware|Test|Tests|Fixture|Helper|Utility|Extensions|Constants|Enum|Client|Repository|Factory|Provider|Fetcher|Composer)$/;
|
|
622
|
+
const DF_NOISE_NAMES = new Set(['Program', 'App', 'Startup']);
|
|
623
|
+
const DF_MAX_OBJECTS = 12;
|
|
624
|
+
|
|
625
|
+
function dfKind(name, table) {
|
|
626
|
+
if (DF_DTO_RE.test(name)) return 'dto';
|
|
627
|
+
return table ? 'table' : 'entity';
|
|
628
|
+
}
|
|
629
|
+
// diff 里「+」开头的新增行(+++ 文件头不算);删除行不参与出现判定
|
|
630
|
+
function dfAddedText(diff) {
|
|
631
|
+
return String(diff || '').split(/\r?\n/).filter((l) => l.startsWith('+') && !l.startsWith('+++')).join('\n');
|
|
632
|
+
}
|
|
633
|
+
function dfDefNames(text) {
|
|
634
|
+
const out = new Set();
|
|
635
|
+
for (const p of DF_DEF_PATTERNS) {
|
|
636
|
+
for (const m of text.matchAll(p.re)) out.add(m[1]);
|
|
637
|
+
}
|
|
638
|
+
return out;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function deriveDataflow(files, steps) {
|
|
642
|
+
const addedByPath = new Map(files.map((f) => [f.path, dfAddedText(f.diff)]));
|
|
643
|
+
const defNamesByPath = new Map(files.map((f) => [f.path, dfDefNames(addedByPath.get(f.path))]));
|
|
644
|
+
const stepBody = new Map(steps.map((s) => [s.id, [s.title, s.summary, ...s.detail].join('\n')]));
|
|
645
|
+
|
|
646
|
+
// 1) 识别:定义模式扫 diff 新增行与步骤正文;噪音名丢弃(kind 后缀判定优先于来源)
|
|
647
|
+
const kindByName = new Map();
|
|
648
|
+
const scanDef = (text) => {
|
|
649
|
+
for (const p of DF_DEF_PATTERNS) {
|
|
650
|
+
for (const m of text.matchAll(p.re)) {
|
|
651
|
+
const name = m[1];
|
|
652
|
+
if (DF_NOISE_RE.test(name) || DF_NOISE_NAMES.has(name)) continue;
|
|
653
|
+
if (!kindByName.has(name)) kindByName.set(name, dfKind(name, p.table));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
for (const f of files) scanDef(addedByPath.get(f.path));
|
|
658
|
+
for (const s of steps) scanDef(stepBody.get(s.id));
|
|
659
|
+
|
|
660
|
+
// 2) 关联:diff 新增行命中 → 文件(≥2 个才保留);文件归属 / 正文命中 → 步骤(保步骤顺序)
|
|
661
|
+
const candidates = [];
|
|
662
|
+
for (const [name, kind] of kindByName) {
|
|
663
|
+
const nameRe = new RegExp('\\b' + name + '\\b');
|
|
664
|
+
const hitFiles = files.filter((f) => nameRe.test(addedByPath.get(f.path)));
|
|
665
|
+
if (hitFiles.length < 2) continue;
|
|
666
|
+
hitFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
667
|
+
const layers = uniq(hitFiles.map((f) => f.layer).filter((l) => LAYER_BY_ID.has(l)))
|
|
668
|
+
.sort((a, b) => LAYER_BY_ID.get(a).rank - LAYER_BY_ID.get(b).rank);
|
|
669
|
+
const stepIds = [];
|
|
670
|
+
for (const s of steps) {
|
|
671
|
+
const viaFile = hitFiles.some((f) => f.steps.includes(s.id));
|
|
672
|
+
if (viaFile || nameRe.test(stepBody.get(s.id))) stepIds.push(s.id);
|
|
673
|
+
}
|
|
674
|
+
// defStep:优先「diff 新增行命中定义模式」的第一个文件所属的首个步骤;都没有则取首个涉及步骤
|
|
675
|
+
const defFile = hitFiles.find((f) => defNamesByPath.get(f.path).has(name));
|
|
676
|
+
const defStep = defFile ? (defFile.steps[0] || '') : (stepIds[0] || '');
|
|
677
|
+
candidates.push({ name, kind, files: hitFiles.map((f) => f.path), layers, steps: stepIds, defStep });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// 3) 截取:跨文件数降序、跨层数降序,最多 12 个
|
|
681
|
+
candidates.sort((a, b) => b.files.length - a.files.length || b.layers.length - a.layers.length);
|
|
682
|
+
const objects = candidates.slice(0, DF_MAX_OBJECTS);
|
|
683
|
+
|
|
684
|
+
// 4) 传递边:defStep → 其余涉及步骤
|
|
685
|
+
const flows = [];
|
|
686
|
+
for (const o of objects) {
|
|
687
|
+
if (!o.defStep) continue;
|
|
688
|
+
for (const s of o.steps) {
|
|
689
|
+
if (s === o.defStep) continue;
|
|
690
|
+
flows.push({ object: o.name, from: o.defStep, to: s });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return { objects, flows };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ---------------- 依赖解析(`## 执行顺序` 子句 + 任务行内「依赖:」) ----------------
|
|
697
|
+
// 只收「声明的依赖边」:执行顺序箭头链本身由看板主链表达,不进 edges;
|
|
698
|
+
// 子句边在自身集合内做传递归约(A→B、B→C 都在时去掉 A→C),弧更少且不失真。
|
|
699
|
+
function deriveDeps(sections, steps) {
|
|
700
|
+
const empty = { edges: [], source: '', unresolved: 0, unresolvedItems: [] };
|
|
701
|
+
const docSteps = steps.filter((s) => s.source !== 'git' && s.source !== 'fallback');
|
|
702
|
+
if (docSteps.length < 2) return empty;
|
|
703
|
+
|
|
704
|
+
// 引用归一:裸数字(按 index)/ Tn / Sn / 标签 id → step.id
|
|
705
|
+
const ref = new Map();
|
|
706
|
+
const reg = (k, id) => { if (k && !ref.has(k)) ref.set(k, id); };
|
|
707
|
+
for (const s of docSteps) {
|
|
708
|
+
reg(String(s.index), s.id);
|
|
709
|
+
reg(String(s.id).toLowerCase(), s.id);
|
|
710
|
+
reg(String(s.id).replace(/^[A-Za-z]+/, ''), s.id);
|
|
711
|
+
}
|
|
712
|
+
const resolveRef = (tok) => {
|
|
713
|
+
const t = String(tok || '').trim().replace(/^[((\s]+|[))\s,。;、,;.::]+$/g, '');
|
|
714
|
+
if (!t) return '';
|
|
715
|
+
return ref.get(t) || ref.get(t.toLowerCase()) || ref.get(t.replace(/^[A-Za-z]+/, '')) || '';
|
|
716
|
+
};
|
|
717
|
+
// token 展开:4/5/6、4~7、T2、全部(= allIds)
|
|
718
|
+
const expand = (expr, allIds) => {
|
|
719
|
+
const e = String(expr || '').replace(/([^)]*)|\([^)]*\)/g, '');
|
|
720
|
+
const out = [];
|
|
721
|
+
for (const raw of e.split(/[\/、,,\s]+|与|和/g)) {
|
|
722
|
+
const p = raw.trim();
|
|
723
|
+
if (!p || /^(?:依赖|独立|先于|必须|见|UI|ui)$/.test(p)) continue;
|
|
724
|
+
if (/^全部|^所有/.test(p)) { out.push(...allIds); continue; }
|
|
725
|
+
const rm = p.match(/^([A-Za-z]{0,4})(\d+)[~\-—–][A-Za-z]{0,4}(\d+)$/);
|
|
726
|
+
if (rm) {
|
|
727
|
+
for (let i = +rm[2]; i <= +rm[3]; i++) { const id = resolveRef(rm[1] + i); if (id) out.push(id); }
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
const id = resolveRef(p);
|
|
731
|
+
if (id) out.push(id);
|
|
732
|
+
}
|
|
733
|
+
return [...new Set(out)];
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const edges = new Map();
|
|
737
|
+
const addEdge = (from, to) => { if (from && to && from !== to) edges.set(from + '>' + to, { from, to }); };
|
|
738
|
+
let unresolved = 0;
|
|
739
|
+
const unresolvedItems = []; // 2026-11-10:unresolved 计数保留,另留 {line,title} 定位(parseHealth.issues 消费)
|
|
740
|
+
let source = '';
|
|
741
|
+
const noteUnresolved = (line, title) => {
|
|
742
|
+
unresolved++;
|
|
743
|
+
unresolvedItems.push({ line: line || 0, title: String(title || '').trim() });
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
const parseClauses = (text, lhsFixed, lineNo) => {
|
|
747
|
+
for (const clause of String(text || '').split(/[;;]/)) {
|
|
748
|
+
const c = clause.trim();
|
|
749
|
+
if (!c) continue;
|
|
750
|
+
const m = c.match(/^(.+?)\s*依赖\s*(.+)$/);
|
|
751
|
+
if (!m) {
|
|
752
|
+
// 「T5 先于 T7/T8」= T5 是被依赖方( prose 先于不可解析时静默跳过,不计 unresolved)
|
|
753
|
+
const m2 = c.match(/^(.+?)\s*(?:必须|须)?先于\s*(.+)$/);
|
|
754
|
+
if (!m2) continue; // 「9 独立」等无边子句
|
|
755
|
+
const lhs = expand(m2[1], []);
|
|
756
|
+
const rhs = expand(m2[2], []);
|
|
757
|
+
for (const b of rhs) for (const a of lhs) addEdge(a, b);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
const lhs = lhsFixed ? [lhsFixed] : expand(m[1], []);
|
|
761
|
+
// 「依赖全部」= 依赖 LHS 之前的全部步骤(按 index,排除自身与后至步骤)
|
|
762
|
+
const lhsMin = Math.min(...lhs.map((id) => (docSteps.find((s) => s.id === id) || {}).index || Infinity));
|
|
763
|
+
const rhs = expand(m[2], docSteps.filter((s) => !lhs.includes(s.id) && s.index < lhsMin).map((s) => s.id));
|
|
764
|
+
if (!lhs.length || !rhs.length) { noteUnresolved(lineNo, c); continue; }
|
|
765
|
+
for (const b of rhs) for (const a of lhs) addEdge(b, a);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// a) `## 执行顺序` 节里的「依赖:」行 / 含依赖子句的括注行(箭头链不产边)
|
|
770
|
+
const orderSec = sectionByTitle(sections, ['执行顺序', '实施顺序']);
|
|
771
|
+
if (orderSec) {
|
|
772
|
+
for (const [j, rawLine] of orderSec.body.split(/\r?\n/).entries()) {
|
|
773
|
+
const line = rawLine.trim().replace(/^[-*>\s]+/, '');
|
|
774
|
+
// 箭头链行(含批注里的「依赖」字样)不产子句边——链本身由看板主链表达
|
|
775
|
+
if (!line || line.includes('→') || line.includes('->') || !line.includes('依赖')) continue;
|
|
776
|
+
if (!source) source = 'exec-order';
|
|
777
|
+
parseClauses(line.replace(/^(?:执行)?依赖\s*[::]\s*/, ''), false, orderSec.start + orderSec.lead + j + 2);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// b) 任务行内 `- 依赖:T1、T2`(= 本步骤依赖列出的前序)
|
|
781
|
+
for (const s of docSteps) {
|
|
782
|
+
(s.detail || []).forEach((d, i) => {
|
|
783
|
+
const m = String(d).match(/^-?\s*依赖\s*[::]\s*(.+)$/);
|
|
784
|
+
if (!m) return;
|
|
785
|
+
if (!source) source = 'task-rows';
|
|
786
|
+
const rhs = expand(m[1], []);
|
|
787
|
+
if (!rhs.length) { noteUnresolved((s.detailPos || [])[i] || s.line, d); return; }
|
|
788
|
+
for (const b of rhs) addEdge(b, s.id);
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (!edges.size) return { edges: [], source, unresolved, unresolvedItems };
|
|
792
|
+
|
|
793
|
+
// 传递归约(仅在声明边集合内):去掉可被其他声明路径推出的边
|
|
794
|
+
const adj = new Map();
|
|
795
|
+
for (const e of edges.values()) {
|
|
796
|
+
if (!adj.has(e.from)) adj.set(e.from, []);
|
|
797
|
+
adj.get(e.from).push(e.to);
|
|
798
|
+
}
|
|
799
|
+
const reduced = [];
|
|
800
|
+
for (const e of edges.values()) {
|
|
801
|
+
const seen = new Set([e.from]);
|
|
802
|
+
const stack = [e.from];
|
|
803
|
+
let reachable = false;
|
|
804
|
+
while (stack.length && !reachable) {
|
|
805
|
+
const cur = stack.pop();
|
|
806
|
+
for (const nxt of adj.get(cur) || []) {
|
|
807
|
+
if (cur === e.from && nxt === e.to) continue; // 跳过被检边本身
|
|
808
|
+
if (nxt === e.to) { reachable = true; break; }
|
|
809
|
+
if (!seen.has(nxt)) { seen.add(nxt); stack.push(nxt); }
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (!reachable) reduced.push(e);
|
|
813
|
+
}
|
|
814
|
+
return { edges: reduced, source, unresolved, unresolvedItems };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// 验证证据(2026-11-10):本机台账 .agents/verifications.jsonl 优先,
|
|
818
|
+
// 仓库凭证 workflow/verify-evidence.md 兜底合并(ts+head 去重)。
|
|
819
|
+
// 时间窗 = 文档日期起——关联语义是「任务时间窗内的验证凭证」,非逐条绑定。
|
|
820
|
+
function collectEvidence(root, windowStart) {
|
|
821
|
+
const rows = [];
|
|
822
|
+
const seen = new Set();
|
|
823
|
+
const push = (r) => {
|
|
824
|
+
const key = r.ts + '|' + (r.headShort || '');
|
|
825
|
+
if (!r.ts || seen.has(key)) return;
|
|
826
|
+
seen.add(key);
|
|
827
|
+
rows.push(r);
|
|
828
|
+
};
|
|
829
|
+
try {
|
|
830
|
+
const p = path.join(root, '.agents', 'verifications.jsonl');
|
|
831
|
+
if (fs.existsSync(p)) {
|
|
832
|
+
for (const line of fs.readFileSync(p, 'utf8').split(/\r?\n/)) {
|
|
833
|
+
if (!line.trim()) continue;
|
|
834
|
+
try {
|
|
835
|
+
const j = JSON.parse(line);
|
|
836
|
+
push({
|
|
837
|
+
ts: j.ts, suite: j.suite || '', gates: j.gates ?? null, exitCode: j.exitCode ?? null,
|
|
838
|
+
headShort: j.headShort || '', skipped: (j.scope && j.scope.skipped) || [],
|
|
839
|
+
envSkips: j.envSkips || [], source: 'ledger',
|
|
840
|
+
});
|
|
841
|
+
} catch { /* 坏行跳过 */ }
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
} catch { /* 台账读不动不影响主模型 */ }
|
|
845
|
+
try {
|
|
846
|
+
const p = path.join(root, 'workflow', 'verify-evidence.md');
|
|
847
|
+
if (fs.existsSync(p)) {
|
|
848
|
+
for (const line of fs.readFileSync(p, 'utf8').split(/\r?\n/)) {
|
|
849
|
+
const m = line.match(/^\|\s*`([0-9a-f]+)`\s*\|\s*([\dT:.Z+-]+)\s*\|\s*(\d+)\s*\|/);
|
|
850
|
+
if (m) push({ ts: m[2], suite: 'all-gates', gates: +m[3], exitCode: 0, headShort: m[1], skipped: [], envSkips: [], source: 'evidence' });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
} catch { /* 凭证读不动不影响主模型 */ }
|
|
854
|
+
const w0 = windowStart ? Date.parse(windowStart) : NaN;
|
|
855
|
+
const inWindow = isNaN(w0) ? rows : rows.filter((r) => Date.parse(r.ts) >= w0);
|
|
856
|
+
inWindow.sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
|
|
857
|
+
return { windowStart: windowStart || '', rows: inWindow.slice(0, 10), totalInWindow: inWindow.length };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ---------------- 组装模型 ----------------
|
|
861
|
+
// 测试缝合(2026-11-10):把注入 payload 归一成 collectGit 返回形状(files 条目缺省走 fileRecord)。
|
|
862
|
+
// 仅供 .agents/tests/dev-tour-model.test.mjs 注入确定性 git 面;buildTour 不传 opts.git 时
|
|
863
|
+
// 仍走原 collectGit 路径,行为不变。
|
|
864
|
+
function injectGitInfo(g = {}) {
|
|
865
|
+
const files = new Map();
|
|
866
|
+
const src = g.files instanceof Map ? g.files : Object.entries(g.files || {});
|
|
867
|
+
for (const [p, v] of src) files.set(normPath(p), fileRecord(p, v || {}));
|
|
868
|
+
return {
|
|
869
|
+
available: g.available !== false, base: g.base || 'HEAD',
|
|
870
|
+
files, basePaths: new Set(g.basePaths || []),
|
|
871
|
+
note: g.note || '', baseSha: g.baseSha || '',
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
export function buildTour(opts = {}) {
|
|
876
|
+
const root = path.resolve(opts.root || DEFAULT_ROOT);
|
|
877
|
+
const docAbs = path.isAbsolute(opts.docPath) ? opts.docPath : path.join(root, opts.docPath);
|
|
878
|
+
const docPath = rel(root, docAbs);
|
|
879
|
+
if (!fs.existsSync(docAbs)) throw new Error(`文档不存在:${docPath}`);
|
|
880
|
+
const text = fs.readFileSync(docAbs, 'utf8');
|
|
881
|
+
const fm = parseFrontmatter(text);
|
|
882
|
+
const sections = parseSections(text);
|
|
883
|
+
const title = (extractTitle(text) || path.basename(docPath, '.md'))
|
|
884
|
+
.replace(/^\d{4}-\d{2}-\d{2}[-—–\s]+/, '')
|
|
885
|
+
.trim() || path.basename(docPath, '.md');
|
|
886
|
+
const typeKey = docPath.split('/')[1] || '';
|
|
887
|
+
const type = TYPE_LABEL[typeKey] || 'DOC';
|
|
888
|
+
|
|
889
|
+
// 仓库跟踪索引提前建好(30s 缓存):步骤标题/正文/表格行的未反引号中段路径需按真实文件消解采集
|
|
890
|
+
const repo = trackedIndex(root);
|
|
891
|
+
|
|
892
|
+
// 1) 步骤:步骤 → 任务拆解 → h3 子标题 → 改动块 → 文件分组
|
|
893
|
+
let steps = [];
|
|
894
|
+
const secTasks = sectionByTitle(sections, ['任务拆解', '任务清单', 'TODO', '待办', '步骤', '实施步骤', '执行步骤']);
|
|
895
|
+
if (secTasks) steps = /-\s*\[[ xX]\]/.test(secTasks.body) ? stepsFromTaskSection(secTasks) : stepsFromNumberedSection(secTasks);
|
|
896
|
+
|
|
897
|
+
const changeSec = sectionByTitle(sections, ['改动方案', '改动面', '系统改动清单', '系统改动', '改动清单', '涉及文件', '改动文件', '文件清单']);
|
|
898
|
+
const changeChildren = changeSec ? childSections(sections, changeSec) : [];
|
|
899
|
+
let blocks = changeSec ? blocksFromChangeSection(changeSec, changeChildren.length === 0) : [];
|
|
900
|
+
// h3 批次子节的表格行:块解析只扫 `##` 节自身正文,够不到 `###` 子节——表格路径归入批次块,
|
|
901
|
+
// 与任务号/批次标签交集命中;无表格行的子节不产块(避免与标题步骤重复归集)
|
|
902
|
+
for (const csec of changeChildren) {
|
|
903
|
+
const rows = csec.body.split(/\r?\n/).map((l) => l.trim()).filter((l) => /^\|/.test(l) && !/^\|[\s:|-]+\|?$/.test(l));
|
|
904
|
+
if (!rows.length) continue;
|
|
905
|
+
const lb = labelOf(csec.title);
|
|
906
|
+
blocks.push({ id: lb.id, title: lb.title || csec.title, raw: csec.title, lines: rows, line: csec.start + 1 });
|
|
907
|
+
}
|
|
908
|
+
// h3 子标题当步骤(若尚未拿到):改动方案的 `### 批次` / 任务拆解的 `### T1 · 标题`
|
|
909
|
+
// (parseSections 把 h3 切成独立节、父节 body 为空,故从 childSections 取)
|
|
910
|
+
if (!steps.length) {
|
|
911
|
+
const h3Parent = secTasks && childSections(sections, secTasks).length ? secTasks : changeSec;
|
|
912
|
+
const h3 = h3Parent ? childSections(sections, h3Parent) : [];
|
|
913
|
+
if (h3.length) {
|
|
914
|
+
steps = h3.map((s, i) => {
|
|
915
|
+
const lb = labelOf(s.title);
|
|
916
|
+
return newStep({
|
|
917
|
+
id: lb.id || `S${i + 1}`, title: lb.title || s.title,
|
|
918
|
+
summary: firstLine(s.body.replace(/[`*_#>]/g, '')).slice(0, 160),
|
|
919
|
+
detail: s.body.split(/\r?\n/).map((x) => x.trim()).filter(Boolean).slice(0, 8),
|
|
920
|
+
source: 'headings',
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// 步骤还没有文件:从改动方案块按标签合并
|
|
927
|
+
for (const st of steps) {
|
|
928
|
+
const matches = blocks.filter((b) => blockMatchesStep(b, st));
|
|
929
|
+
for (const b of matches) {
|
|
930
|
+
for (const line of b.lines) {
|
|
931
|
+
st.detail.push(line);
|
|
932
|
+
for (const p of extractPaths(line, repo)) if (!st.files.includes(p)) st.files.push(p);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// 未被步骤认领的块 → 新步骤;完全无步骤 → 按块/文件生成
|
|
938
|
+
const usedBlocks = new Set();
|
|
939
|
+
for (const st of steps) {
|
|
940
|
+
for (const b of blocks) {
|
|
941
|
+
if (blockMatchesStep(b, st)) usedBlocks.add(b);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const orphan = [];
|
|
945
|
+
const hadSteps = steps.length > 0;
|
|
946
|
+
for (const b of blocks) {
|
|
947
|
+
if (usedBlocks.has(b)) continue;
|
|
948
|
+
const files = uniq(b.lines.flatMap((l) => extractPaths(l, repo)));
|
|
949
|
+
if (!b.id && !files.length) continue;
|
|
950
|
+
if (hadSteps) { orphan.push(b); continue; }
|
|
951
|
+
const st = newStep({
|
|
952
|
+
id: b.id || `S${steps.length + 1}`,
|
|
953
|
+
title: b.title || (files.length === 1 ? files[0].split('/').pop() : `改动块 ${steps.length + 1}`),
|
|
954
|
+
summary: firstLine(b.lines.join('\n')).slice(0, 160),
|
|
955
|
+
detail: b.lines.slice(0, 12),
|
|
956
|
+
files,
|
|
957
|
+
source: 'blocks',
|
|
958
|
+
line: b.line || 0,
|
|
959
|
+
});
|
|
960
|
+
steps.push(st);
|
|
961
|
+
}
|
|
962
|
+
if (steps.length && orphan.length) {
|
|
963
|
+
const files = uniq(orphan.flatMap((b) => b.lines.flatMap((l) => extractPaths(l, repo))));
|
|
964
|
+
steps.push(newStep({
|
|
965
|
+
id: 'A', title: '改动面(未分步)',
|
|
966
|
+
summary: '任务拆解未覆盖的文件清单,按文档原样归集',
|
|
967
|
+
detail: orphan.flatMap((b) => b.lines).slice(0, 20),
|
|
968
|
+
files, source: 'blocks',
|
|
969
|
+
}));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (!steps.length) {
|
|
973
|
+
// 最后兜底:按文件所属架构层分组,避免 100 个文件 100 个站
|
|
974
|
+
const all = extractPaths(text).filter((p) => !isDocPath(p) || p.startsWith(docPath.split('/')[0] + '/'));
|
|
975
|
+
const groups = new Map();
|
|
976
|
+
for (const p of all) {
|
|
977
|
+
const layer = inferLayer(p);
|
|
978
|
+
if (!groups.has(layer)) groups.set(layer, []);
|
|
979
|
+
groups.get(layer).push(p);
|
|
980
|
+
}
|
|
981
|
+
for (const [layer, files] of groups) {
|
|
982
|
+
steps.push(newStep({
|
|
983
|
+
id: `S${steps.length + 1}`,
|
|
984
|
+
title: LAYER_BY_ID.get(layer)?.label || '改动',
|
|
985
|
+
summary: `${files.length} 个文件`,
|
|
986
|
+
files,
|
|
987
|
+
source: 'layers',
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// 步骤无文件:从正文路径补;仍无则保留(时序图按文字推断层)
|
|
993
|
+
for (const st of steps) {
|
|
994
|
+
if (!st.files.length) {
|
|
995
|
+
const body = [st.title, st.summary, ...st.detail].join('\n');
|
|
996
|
+
st.files = extractPaths(body, repo);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// 2) 文档引用路径(计划面)
|
|
1001
|
+
const allDocPaths = extractPaths(text);
|
|
1002
|
+
const changePaths = changeSec ? blocks.flatMap((b) => b.lines.flatMap((l) => extractPaths(l))) : [];
|
|
1003
|
+
const docPaths = uniq(allDocPaths.filter((p) => {
|
|
1004
|
+
if (p === docPath) return false;
|
|
1005
|
+
if (!CODE_EXT.test(p)) return false; // 目录/项目名只做步骤锚点,不进文件树
|
|
1006
|
+
if (inCodeScope(p)) return true;
|
|
1007
|
+
if (changePaths.includes(p)) return true;
|
|
1008
|
+
// 文档/台账只在显式出现在改动方案里时纳入,避免把检索命令、模板路径当改动
|
|
1009
|
+
return false;
|
|
1010
|
+
}));
|
|
1011
|
+
|
|
1012
|
+
// 3) git 实际面(测试缝合:opts.git 提供时跳过 collectGit,直接用注入的 collectGit 同形状 payload)
|
|
1013
|
+
const gitInfo = opts.git ? injectGitInfo(opts.git) : collectGit({
|
|
1014
|
+
root, base: opts.base || 'HEAD', includeAll: !!opts.includeAll,
|
|
1015
|
+
includeDocs: !!opts.includeDocs, withDiff: opts.withDiff !== false,
|
|
1016
|
+
maxDiffLines: opts.maxDiffLines || 160,
|
|
1017
|
+
});
|
|
1018
|
+
const docMeta = { title, module: fm['模块'] || '' };
|
|
1019
|
+
|
|
1020
|
+
// 验证计划里的测试文件补到「测试/验证」步骤:步骤正文常只写判据,测试路径在验证计划里
|
|
1021
|
+
const verifySecEarly = sectionByTitle(sections, ['验证计划', '测试计划']);
|
|
1022
|
+
if (verifySecEarly) {
|
|
1023
|
+
const testPaths = extractPaths(verifySecEarly.body).filter((p) => /(^|\/)tests?\//.test(p) || /\.(test|spec)\.[tj]sx?$/i.test(p));
|
|
1024
|
+
const testStep = steps.find((s) => /测试|验证|Test/i.test(s.title));
|
|
1025
|
+
if (testStep) {
|
|
1026
|
+
for (const p of testPaths) if (!testStep.files.includes(p)) testStep.files.push(p);
|
|
1027
|
+
if (testPaths.length && !testStep.summary) testStep.summary = `${testPaths.length} 个测试文件 / 验证命令`;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const universe = new Set([...docPaths, ...gitInfo.files.keys()]);
|
|
1032
|
+
const files = new Map();
|
|
1033
|
+
const docContext = contextTokens(docMeta);
|
|
1034
|
+
for (const p of docPaths) {
|
|
1035
|
+
const rp = resolvePath(p, universe, repo, docContext);
|
|
1036
|
+
if (!files.has(rp)) files.set(rp, fileRecord(rp, { planned: true, layer: inferLayer(rp) }));
|
|
1037
|
+
}
|
|
1038
|
+
for (const [p, rec] of gitInfo.files) {
|
|
1039
|
+
const rp = resolvePath(p, universe, repo, docContext);
|
|
1040
|
+
const prev = files.get(rp);
|
|
1041
|
+
if (prev) Object.assign(prev, rec, { planned: prev.planned, path: rp });
|
|
1042
|
+
else files.set(rp, fileRecord(rp, { ...rec, path: rp }));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// 计划面文件若在 git 实际面里存在但状态 planned,补状态
|
|
1046
|
+
for (const rec of files.values()) {
|
|
1047
|
+
rec.test = rec.layer === 'test' || /(^|\/)tests?\//.test(rec.path) || /\.(test|spec)\.[tj]sx?$/i.test(rec.path);
|
|
1048
|
+
if (rec.actual) rec.badge = STATUS_LABEL[rec.status] || '修改';
|
|
1049
|
+
else if (rec.planned) rec.badge = '计划';
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// 4) 步骤 ↔ 文件双向绑定
|
|
1053
|
+
const stepFiles = new Map(steps.map((s) => [s.id, new Set()]));
|
|
1054
|
+
const resolvedAway = new Set();
|
|
1055
|
+
const guessed = []; // 路径猜测(多候选打分)→ parseHealth.pathGuesses;basename 唯一命中不算
|
|
1056
|
+
for (const st of steps) {
|
|
1057
|
+
const ctx = contextTokens(docMeta, st);
|
|
1058
|
+
const anchors = extractPaths([st.summary, ...st.detail].join('\n'))
|
|
1059
|
+
.filter((a) => !CODE_EXT.test(a));
|
|
1060
|
+
for (const raw of st.files) {
|
|
1061
|
+
const rawGuesses = [];
|
|
1062
|
+
let rp = resolvePath(raw, universe, repo, ctx, rawGuesses);
|
|
1063
|
+
// 裸文件名(MaterialCodePushService.cs)若仓库里只有一个同名文件,优先落到本步骤其他文件的共同目录
|
|
1064
|
+
if (!raw.includes('/') && (repo.byBase.get(raw) || []).length === 1) {
|
|
1065
|
+
const dirs = st.files.filter((x) => x !== raw).map((x) => x.split('/').slice(0, -1).join('/')).filter(Boolean);
|
|
1066
|
+
const freq = new Map();
|
|
1067
|
+
for (const d of dirs) freq.set(d, (freq.get(d) || 0) + 1);
|
|
1068
|
+
const dom = [...freq.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
1069
|
+
if (dom && !repo.set.has(dom + '/' + raw) && !files.has(dom + '/' + raw)) {
|
|
1070
|
+
rp = resolvePath(dom + '/' + raw, universe, repo, ctx);
|
|
1071
|
+
if (rp === dom + '/' + raw && CODE_EXT.test(raw)) rp = dom + '/' + raw;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// 相对新文件(如 Guoyu/GuoyuOptions.cs)落在步骤正文声明的项目目录下:
|
|
1075
|
+
// 仅当拼接结果确实是仓库真实文件才生效;raw 本身已 tracked(rp===raw 即真实命中)不二次拼接,
|
|
1076
|
+
// 否则产生 wiki/.../wiki/.../00-preflight.sql 型翻倍伪路径与跨目录冒名拼接
|
|
1077
|
+
if (rp === raw && !repo.set.has(raw) && !raw.startsWith('.') && !CODE_ROOTS.some((r) => raw.startsWith(r))) {
|
|
1078
|
+
for (const a of anchors) {
|
|
1079
|
+
const cand = a.replace(/\/$/, '') + '/' + raw;
|
|
1080
|
+
if (repo.set.has(cand)) { rp = cand; break; }
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const rec = files.get(rp) || (gitInfo.files.has(rp) ? files.get(rp) : null);
|
|
1084
|
+
if (rec) {
|
|
1085
|
+
rec.steps.push(st.id);
|
|
1086
|
+
stepFiles.get(st.id).add(rp);
|
|
1087
|
+
// 同一文件先以相对路径进了计划面,这里解析到真实路径:删掉无主的相对重复项
|
|
1088
|
+
if (rp !== raw) {
|
|
1089
|
+
resolvedAway.add(raw);
|
|
1090
|
+
const old = files.get(raw);
|
|
1091
|
+
if (old && old.planned && !old.actual && !old.steps.length) files.delete(raw);
|
|
1092
|
+
}
|
|
1093
|
+
if (rawGuesses.length) {
|
|
1094
|
+
rec.pathGuess = true;
|
|
1095
|
+
guessed.push({ path: rp, step: st.id, candidates: rawGuesses[rawGuesses.length - 1].candidates });
|
|
1096
|
+
}
|
|
1097
|
+
} else if (CODE_EXT.test(rp) || repo.set.has(rp)) {
|
|
1098
|
+
const r = fileRecord(rp, { planned: true, layer: inferLayer(rp) });
|
|
1099
|
+
r.steps.push(st.id);
|
|
1100
|
+
files.set(rp, r);
|
|
1101
|
+
stepFiles.get(st.id).add(rp);
|
|
1102
|
+
if (rawGuesses.length) {
|
|
1103
|
+
r.pathGuess = true;
|
|
1104
|
+
guessed.push({ path: rp, step: st.id, candidates: rawGuesses[rawGuesses.length - 1].candidates });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
st.files = [...stepFiles.get(st.id)];
|
|
1109
|
+
}
|
|
1110
|
+
// 相对路径已解析到真实文件:清理无主的原样重复项(否则会出现在「未分步文件」里)
|
|
1111
|
+
for (const raw of resolvedAway) {
|
|
1112
|
+
const old = files.get(raw);
|
|
1113
|
+
if (old && old.planned && !old.actual) {
|
|
1114
|
+
for (const set of stepFiles.values()) set.delete(raw);
|
|
1115
|
+
files.delete(raw);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
// 全局清理:文档里以相对路径出现、但步骤已解析到真实文件的重复项
|
|
1119
|
+
const resolvedSet = new Set([...stepFiles.values()].flatMap((set) => [...set]));
|
|
1120
|
+
for (const [p, rec] of [...files]) {
|
|
1121
|
+
if (rec.actual || rec.steps.length || resolvedSet.has(p) || !rec.planned) continue;
|
|
1122
|
+
const base = p.split('/').pop();
|
|
1123
|
+
const twin = [...resolvedSet].find((x) => x.endsWith('/' + p)) ||
|
|
1124
|
+
([...resolvedSet].filter((x) => x.endsWith('/' + base)).length === 1 ? [...resolvedSet].find((x) => x.endsWith('/' + base)) : null);
|
|
1125
|
+
if (twin) files.delete(p);
|
|
1126
|
+
}
|
|
1127
|
+
// 测试文件优先归到「测试/验证」步骤(验证计划里的路径常只到测试项目目录)
|
|
1128
|
+
const testStep = steps.find((x) => /测试|验证|Test/i.test(x.title));
|
|
1129
|
+
if (testStep) {
|
|
1130
|
+
for (const rec of files.values()) {
|
|
1131
|
+
if (rec.test && !rec.steps.length) {
|
|
1132
|
+
rec.steps.push(testStep.id);
|
|
1133
|
+
stepFiles.get(testStep.id).add(rec.path);
|
|
1134
|
+
if (!testStep.files.includes(rec.path)) testStep.files.push(rec.path);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
// 未绑定步骤的文件:按 basename / 层词 尝试匹配步骤正文。两臂都是启发式 → 打 pathGuess 标(猜测可见)。
|
|
1139
|
+
// basename 臂:正文提到文件名即绑第一个命中(既有语义不动)。
|
|
1140
|
+
// 层词臂:仅当恰一个步骤正文含该层 label 才绑;0 个或 ≥2 个都不绑,留「未分步」可见——
|
|
1141
|
+
// 静默挂到第一个命中步骤(旧行为)比不绑更难发现,故收紧为恰一。
|
|
1142
|
+
for (const rec of files.values()) {
|
|
1143
|
+
if (rec.steps.length) continue;
|
|
1144
|
+
const base = rec.path.split('/').pop();
|
|
1145
|
+
const byBase = steps.find((s) => [s.title, s.summary, ...s.detail].join(' ').includes(base));
|
|
1146
|
+
let hit = byBase;
|
|
1147
|
+
if (!hit && rec.layer !== 'other') {
|
|
1148
|
+
const label = LAYER_BY_ID.get(rec.layer)?.label || '';
|
|
1149
|
+
const hits = label ? steps.filter((s) => [s.title, s.summary, ...s.detail].join(' ').includes(label)) : [];
|
|
1150
|
+
if (hits.length === 1) hit = hits[0];
|
|
1151
|
+
}
|
|
1152
|
+
if (hit) {
|
|
1153
|
+
rec.steps.push(hit.id);
|
|
1154
|
+
stepFiles.get(hit.id).add(rec.path);
|
|
1155
|
+
rec.pathGuess = true;
|
|
1156
|
+
guessed.push({ path: rec.path, step: hit.id, candidates: 1 });
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
// 仍未绑定:进入「未分步」步骤;有实际改动但文档没提:进入「文档外改动」
|
|
1160
|
+
const unassigned = [...files.values()].filter((f) => !f.steps.length);
|
|
1161
|
+
const undocumented = unassigned.filter((f) => f.actual);
|
|
1162
|
+
const rest = unassigned.filter((f) => !f.actual);
|
|
1163
|
+
if (undocumented.length) {
|
|
1164
|
+
const st = newStep({
|
|
1165
|
+
id: 'X', title: '文档外实际改动', summary: 'git 工作树里有、plan 改动面未登记——优先核对 scope',
|
|
1166
|
+
detail: ['这些文件不在文档改动清单内,可能是范围漂移,也可能是文档该回填。'],
|
|
1167
|
+
files: undocumented.map((f) => f.path), source: 'git', status: 'warn',
|
|
1168
|
+
});
|
|
1169
|
+
steps.push(st);
|
|
1170
|
+
for (const f of undocumented) { f.steps.push(st.id); stepFiles.set(st.id, (stepFiles.get(st.id) || new Set()).add(f.path)); }
|
|
1171
|
+
}
|
|
1172
|
+
if (rest.length) {
|
|
1173
|
+
const st = newStep({
|
|
1174
|
+
id: 'U', title: '未分步文件', summary: '文档提到了,但无法归入具体步骤',
|
|
1175
|
+
files: rest.map((f) => f.path), source: 'fallback',
|
|
1176
|
+
});
|
|
1177
|
+
steps.push(st);
|
|
1178
|
+
for (const f of rest) { f.steps.push(st.id); stepFiles.set(st.id, (stepFiles.get(st.id) || new Set()).add(f.path)); }
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
for (const st of steps) st.files = uniq([...(stepFiles.get(st.id) || [])]);
|
|
1182
|
+
steps = steps.map((s, i) => ({ ...s, index: i + 1, files: uniq(s.files) }));
|
|
1183
|
+
|
|
1184
|
+
// 解析健康度(2026-11-09):直认(steps/tasks/headings = 标题约定命中)vs 兜底
|
|
1185
|
+
// (blocks/layers/fallback 归集)——文档标题跑偏时静默降级的可观测面;
|
|
1186
|
+
// gen-dev-tour --strict 与模板警告条消费本字段。git 步(文档外实际改动)是实际面
|
|
1187
|
+
// 对账产物,不计入文档解析分母。
|
|
1188
|
+
const DIRECT_SOURCES = new Set(['steps', 'tasks', 'headings']);
|
|
1189
|
+
const parseSrc = steps.filter((s) => s.source !== 'git');
|
|
1190
|
+
const bySource = {};
|
|
1191
|
+
for (const s of parseSrc) bySource[s.source] = (bySource[s.source] || 0) + 1;
|
|
1192
|
+
const guessSeen = new Set();
|
|
1193
|
+
const guessItems = [];
|
|
1194
|
+
for (const g of guessed) {
|
|
1195
|
+
const k = g.path + '>' + g.step;
|
|
1196
|
+
if (guessSeen.has(k)) continue;
|
|
1197
|
+
guessSeen.add(k);
|
|
1198
|
+
guessItems.push(g);
|
|
1199
|
+
}
|
|
1200
|
+
const parseHealth = {
|
|
1201
|
+
total: parseSrc.length,
|
|
1202
|
+
direct: parseSrc.filter((s) => DIRECT_SOURCES.has(s.source)).length,
|
|
1203
|
+
bySource,
|
|
1204
|
+
zeroFileSteps: parseSrc.filter((s) => !s.files.length).length,
|
|
1205
|
+
layersFallback: !parseSrc.some((s) => DIRECT_SOURCES.has(s.source)) && parseSrc.some((s) => s.source === 'layers'),
|
|
1206
|
+
hitRate: 0,
|
|
1207
|
+
pathGuesses: { total: guessItems.length, items: guessItems },
|
|
1208
|
+
};
|
|
1209
|
+
parseHealth.hitRate = parseHealth.total ? parseHealth.direct / parseHealth.total : 0;
|
|
1210
|
+
|
|
1211
|
+
// 5) 统计(产品面为主,文档/工具另计)
|
|
1212
|
+
const fileArr = [...files.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
1213
|
+
const isProduct = (f) => inCodeScope(f.path) || f.layer === 'test' || (!isDocPath(f.path) && ['frontend', 'api', 'app', 'domain', 'infra', 'db', 'test'].includes(f.layer));
|
|
1214
|
+
const product = fileArr.filter(isProduct);
|
|
1215
|
+
const stat = (arr, k) => arr.filter((f) => f.status === k).length;
|
|
1216
|
+
const stats = {
|
|
1217
|
+
files: product.length,
|
|
1218
|
+
new: stat(product, 'new'),
|
|
1219
|
+
modified: stat(product, 'modified'),
|
|
1220
|
+
deleted: stat(product, 'deleted'),
|
|
1221
|
+
renamed: stat(product, 'renamed'),
|
|
1222
|
+
planned: product.filter((f) => f.planned && !f.actual).length,
|
|
1223
|
+
tests: product.filter((f) => f.test).length,
|
|
1224
|
+
additions: product.reduce((n, f) => n + (f.additions || 0), 0),
|
|
1225
|
+
deletions: product.reduce((n, f) => n + (f.deletions || 0), 0),
|
|
1226
|
+
docs: fileArr.length - product.length,
|
|
1227
|
+
steps: steps.length,
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// 6) 时序图:每步文件层 → 箭头
|
|
1231
|
+
const participants = LAYERS.filter((l) => fileArr.some((f) => f.layer === l.id) || steps.some((s) => inferLayersFromText(s.title + ' ' + s.summary).includes(l.id)))
|
|
1232
|
+
.filter((l) => l.id !== 'doc' && l.id !== 'other' || fileArr.some((f) => f.layer === l.id));
|
|
1233
|
+
const messages = [];
|
|
1234
|
+
for (const st of steps) {
|
|
1235
|
+
let layers = uniq(st.files.map((p) => files.get(p)?.layer || inferLayer(p)));
|
|
1236
|
+
if (!layers.length) layers = inferLayersFromText(st.title + ' ' + st.summary + ' ' + st.detail.join(' '));
|
|
1237
|
+
layers = layers.filter((l) => LAYER_BY_ID.has(l) && l !== 'doc' && l !== 'other').sort((a, b) => LAYER_BY_ID.get(a).rank - LAYER_BY_ID.get(b).rank);
|
|
1238
|
+
if (!layers.length) layers = ['app'];
|
|
1239
|
+
if (layers.length === 1) {
|
|
1240
|
+
messages.push({ stepId: st.id, from: layers[0], to: layers[0], label: clamp(st.title, 26), self: true });
|
|
1241
|
+
} else {
|
|
1242
|
+
for (let i = 0; i < layers.length - 1; i++) {
|
|
1243
|
+
messages.push({ stepId: st.id, from: layers[i], to: layers[i + 1], label: clamp(st.title, 26) });
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (layers.includes('test') && layers.length > 1 && layers[0] !== 'test') {
|
|
1247
|
+
messages.push({ stepId: st.id, from: 'test', to: layers[0], label: '验证 ' + clamp(st.title, 20), dashed: true });
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// 6b) 数据流:数据对象 × 文件 × 架构层 × 步骤(从 diff 新增行 + 步骤正文静态派生)
|
|
1252
|
+
const dataflow = deriveDataflow(fileArr, steps);
|
|
1253
|
+
|
|
1254
|
+
// 7) 验证计划(流程图末端门)
|
|
1255
|
+
const verifySec = sectionByTitle(sections, ['验证计划', '测试计划']);
|
|
1256
|
+
const gates = verifySec ? verifySec.body.split(/\r?\n/).map((l) => l.trim()).filter((l) => /^-\s+/.test(l)).slice(0, 6).map((l) => l.replace(/^-\s+/, '')) : [];
|
|
1257
|
+
|
|
1258
|
+
// 7b) 声明依赖边(执行顺序「依赖:」子句 / 任务行内依赖;流程图旁路弧数据源)
|
|
1259
|
+
const deps = deriveDeps(sections, steps);
|
|
1260
|
+
|
|
1261
|
+
// 解析失约定位(2026-11-10):issues 从总数下钻到行——advisory,不参与 --strict 拒产判定。
|
|
1262
|
+
const issues = [];
|
|
1263
|
+
for (const st of parseSrc) {
|
|
1264
|
+
if (!st.files.length) issues.push({ line: st.line || 0, title: st.title, kind: 'step-no-files' });
|
|
1265
|
+
}
|
|
1266
|
+
for (const b of orphan) {
|
|
1267
|
+
issues.push({ line: b.line || 0, title: b.raw || b.title, kind: 'block-unmatched' });
|
|
1268
|
+
}
|
|
1269
|
+
for (const u of deps.unresolvedItems || []) {
|
|
1270
|
+
issues.push({ line: u.line, title: u.title, kind: 'dep-unresolved' });
|
|
1271
|
+
}
|
|
1272
|
+
if (parseHealth.layersFallback) {
|
|
1273
|
+
issues.push({ line: 1, title: '未发现结构化步骤标题(## 步骤 / ## 任务拆解 / ### 批次 N)', kind: 'layers-fallback' });
|
|
1274
|
+
}
|
|
1275
|
+
issues.sort((a, b) => a.line - b.line);
|
|
1276
|
+
parseHealth.issues = issues;
|
|
1277
|
+
|
|
1278
|
+
// 8) 关联文档
|
|
1279
|
+
const links = [];
|
|
1280
|
+
// 2026-11-11:`对应入口/spec` 允许中间空格;`../intents|specs/*.md` 是相对链接,
|
|
1281
|
+
// 不在 extractPaths 的仓库根白名单内,需单独兜底,否则「需求」tab 永远不出现。
|
|
1282
|
+
for (const m of text.matchAll(/对应\s*(?:入口|spec|规范|事故)[::]\s*(.+)/gi)) {
|
|
1283
|
+
const line = m[1].trim();
|
|
1284
|
+
for (const p of extractPaths(line)) {
|
|
1285
|
+
if (!links.some((l) => l.path === p)) links.push({ label: p.split('/').pop(), path: p });
|
|
1286
|
+
}
|
|
1287
|
+
const raw = normPath(line.replace(/^[`'"“”‘’((\s]+|[`'"“”‘’())\s]+$/g, '').split(/[\s,。;,;]+/)[0]);
|
|
1288
|
+
if (/\.md$/i.test(raw) && !links.some((l) => l.path === raw)) {
|
|
1289
|
+
links.push({ label: raw.split('/').pop(), path: raw });
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const purpose = (() => {
|
|
1294
|
+
const entry = sectionByTitle(sections, ['入口']);
|
|
1295
|
+
const m = entry && entry.body.match(/目标[::]\s*(.+)/);
|
|
1296
|
+
if (m) return m[1].trim();
|
|
1297
|
+
const behavior = sectionByTitle(sections, ['功能行为']);
|
|
1298
|
+
if (behavior) return firstLine(behavior.body.replace(/^[#>*\-\s]+/, ''));
|
|
1299
|
+
return '';
|
|
1300
|
+
})();
|
|
1301
|
+
|
|
1302
|
+
// 9) 需求链路(2026-11-09):跟随「对应入口/spec」链接读上游文档(一层,不递归),
|
|
1303
|
+
// 提取目标与行为/验收要点——看板「需求」视图数据源,补齐 why / what 叙事。
|
|
1304
|
+
const UP_SEC = { INTENT: ['目标', '背景', '需求'], SPEC: ['功能行为', '验收标准', '验收'], INCIDENT: ['现象', '影响', '根因'] };
|
|
1305
|
+
const upstreamType = (p) => (/\/intents?\//.test(p) ? 'INTENT' : /\/specs?\//.test(p) ? 'SPEC' : /\/incidents?\//.test(p) ? 'INCIDENT' : type);
|
|
1306
|
+
const upstream = [];
|
|
1307
|
+
for (const lk of links.slice(0, 3)) {
|
|
1308
|
+
if (!/\.md$/i.test(lk.path)) continue;
|
|
1309
|
+
let abs = path.resolve(path.dirname(docAbs), lk.path);
|
|
1310
|
+
if (!fs.existsSync(abs)) abs = path.resolve(root, lk.path);
|
|
1311
|
+
if (!fs.existsSync(abs)) continue;
|
|
1312
|
+
try {
|
|
1313
|
+
const upText = fs.readFileSync(abs, 'utf8');
|
|
1314
|
+
const upSecs = parseSections(upText);
|
|
1315
|
+
const ut = upstreamType(lk.path);
|
|
1316
|
+
const upPurpose = (() => {
|
|
1317
|
+
const entry = sectionByTitle(upSecs, ['入口']);
|
|
1318
|
+
const m = entry && entry.body.match(/目标[::]\s*(.+)/);
|
|
1319
|
+
if (m) return m[1].trim();
|
|
1320
|
+
// SPEC 的「功能行为」正文常为空,真正的行为在 ### 子节里;用首个子节标题当一句话目标,
|
|
1321
|
+
// 避免 firstLine 落到「验收判据」表格头(| 判据 | 证据 | 状态 |)。
|
|
1322
|
+
if (ut === 'SPEC') {
|
|
1323
|
+
const behavior = sectionByTitle(upSecs, ['功能行为']);
|
|
1324
|
+
const kids = behavior ? childSections(upSecs, behavior) : [];
|
|
1325
|
+
if (kids.length) return clamp(kids[0].title, 120);
|
|
1326
|
+
}
|
|
1327
|
+
for (const name of UP_SEC[ut] || []) {
|
|
1328
|
+
const sec = sectionByTitle(upSecs, [name]);
|
|
1329
|
+
if (sec) {
|
|
1330
|
+
const line = firstLine(sec.body.replace(/^[#>*\-\s]+/, ''));
|
|
1331
|
+
if (line) return clamp(line, 120);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return clamp(extractTitle(upText) || lk.label, 120);
|
|
1335
|
+
})();
|
|
1336
|
+
const points = [];
|
|
1337
|
+
if (ut === 'SPEC') {
|
|
1338
|
+
// 功能行为子节标题(S1/S2/…)比父节空正文更能说明“要做什么”
|
|
1339
|
+
const behavior = sectionByTitle(upSecs, ['功能行为']);
|
|
1340
|
+
const kids = behavior ? childSections(upSecs, behavior) : [];
|
|
1341
|
+
for (const k of kids) { if (points.length >= 5) break; points.push(k.title); }
|
|
1342
|
+
if (!points.length) {
|
|
1343
|
+
const acc = sectionByTitle(upSecs, ['验收判据', '验收标准', '验收']);
|
|
1344
|
+
for (const raw of String(acc && acc.body || '').split(/\r?\n/)) {
|
|
1345
|
+
const mm = raw.match(/^\|\s*([^|]+?)\s*\|/);
|
|
1346
|
+
const t = mm && mm[1].trim();
|
|
1347
|
+
if (t && !/^-+$/.test(t) && !/^(判据|证据|状态|锚)$/.test(t)) points.push(t);
|
|
1348
|
+
if (points.length >= 5) break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (!points.length) {
|
|
1353
|
+
for (const name of UP_SEC[ut] || ['功能行为', '目标']) {
|
|
1354
|
+
const sec = sectionByTitle(upSecs, [name]);
|
|
1355
|
+
if (!sec) continue;
|
|
1356
|
+
for (const raw of sec.body.split(/\r?\n/)) {
|
|
1357
|
+
const t = raw.trim();
|
|
1358
|
+
if (/^[-*\d]/.test(t) && t.length > 2) points.push(t.replace(/^[-*]\s*|^\d+[.、]\s*/, ''));
|
|
1359
|
+
if (points.length >= 5) break;
|
|
1360
|
+
}
|
|
1361
|
+
if (points.length) break;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
upstream.push({ path: rel(root, abs), type: ut, title: extractTitle(upText) || lk.label, purpose: upPurpose, points: points.filter((p) => !p.trim().startsWith(upPurpose.replace(/…$/, '').trim())).slice(0, 5) });
|
|
1365
|
+
} catch { /* 上游读不动不影响主模型 */ }
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return {
|
|
1369
|
+
schema: 2,
|
|
1370
|
+
generatedAt: new Date().toISOString(),
|
|
1371
|
+
rootLabel: path.basename(root),
|
|
1372
|
+
doc: {
|
|
1373
|
+
path: docPath, type, title, status: fm['状态'] || '', level: fm['级别'] || '',
|
|
1374
|
+
module: fm['模块'] || '', date: fm['日期'] || '', purpose,
|
|
1375
|
+
links,
|
|
1376
|
+
upstream,
|
|
1377
|
+
},
|
|
1378
|
+
evidence: collectEvidence(root, fm['日期'] || ''),
|
|
1379
|
+
base: { ref: opts.base || 'HEAD', sha: gitInfo.baseSha || '', available: gitInfo.available, note: gitInfo.note || '' },
|
|
1380
|
+
stats,
|
|
1381
|
+
parseHealth,
|
|
1382
|
+
steps: steps.map((s) => ({
|
|
1383
|
+
id: s.id, index: s.index, title: s.title, summary: s.summary,
|
|
1384
|
+
detail: s.detail.slice(0, 30), files: s.files,
|
|
1385
|
+
status: s.status || 'todo', source: s.source,
|
|
1386
|
+
})),
|
|
1387
|
+
files: fileArr,
|
|
1388
|
+
sequence: { participants: participants.map((p) => ({ id: p.id, label: p.label })), messages },
|
|
1389
|
+
dataflow,
|
|
1390
|
+
deps,
|
|
1391
|
+
gates,
|
|
1392
|
+
note: gitInfo.available ? '' : (gitInfo.note || 'git 数据不可用,仅展示文档计划面'),
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
export function renderModelJson(model) {
|
|
1397
|
+
return JSON.stringify(model, null, 2);
|
|
1398
|
+
}
|