agentic-flow-kit 1.8.7 → 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 -145
- 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 -58
- 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 -35
- 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 -20
- 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 -586
- 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 -1987
- package/templates/_agents/scripts/check-loop.sh +0 -6
- package/templates/_agents/scripts/check-loop.test.mjs +0 -3912
- 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 -99
- package/templates/_agents/scripts/check-scripts-syntax.mjs +0 -84
- 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 -610
- package/templates/_agents/scripts/confirm-doc.test.mjs +0 -1119
- 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 -466
- package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +0 -343
- 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 -109
- 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 -345
- package/templates/_agents/scripts/verify.test.mjs +0 -373
- 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 -58
- 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 -91
- package/templates/workflow/delegations.md +0 -29
- package/templates/workflow/incidents/_TEMPLATE.md +0 -51
- package/templates/workflow/intents/_TEMPLATE.md +0 -51
- 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,1940 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>开发全景图</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>◈</text></svg>">
|
|
8
|
+
<style>
|
|
9
|
+
/* ============================================================
|
|
10
|
+
DEV TOUR — 开发全景图 dossier(模板单源,脚本三锚点由生成器 / 看板服务注入)
|
|
11
|
+
方向:暖纸 · 发丝线 · 极简 —— 令牌层与看板 index.html 同源,两页可同步
|
|
12
|
+
暖纸底 + 暖墨字;朱红是全页唯一强调色,只给选中左缘 / tab 指示线 /
|
|
13
|
+
时序箭头等必须被看见的位置;状态色只上小徽标、圆点与细线,禁止大色块。
|
|
14
|
+
等宽用本机栈(JetBrains Mono→Cascadia→Consolas),正文中文雅黑,零外部请求。
|
|
15
|
+
============================================================ */
|
|
16
|
+
|
|
17
|
+
:root {
|
|
18
|
+
--bg: #f2f0e9; /* 暖纸 */
|
|
19
|
+
--surface: #f7f5ee; /* 顶栏 / 左右轨 */
|
|
20
|
+
--card: #fdfcf8; /* 卡片近白 */
|
|
21
|
+
--card-hover: #ffffff;
|
|
22
|
+
--border: #ddd7c8; /* 发丝线 */
|
|
23
|
+
--border-strong: #b5ad99;
|
|
24
|
+
--text: #26241d; /* 暖墨 */
|
|
25
|
+
--muted: #6c675a;
|
|
26
|
+
--faint: #99937f; /* 仅装饰:滚动条 / 占位符 */
|
|
27
|
+
--accent: #bc4327; /* 朱 — 唯一强调色 */
|
|
28
|
+
--intent: #3f6288; --incident: #a04b35; --spec: #6f5a88; --plan: #3e7157;
|
|
29
|
+
--ok: #3e7157; --warn: #7d6224; --bad: #a04b35;
|
|
30
|
+
--shadow-1: 0 1px 2px rgba(38,36,29,.05), 0 3px 10px rgba(38,36,29,.07);
|
|
31
|
+
--shadow-2: -16px 0 40px rgba(38,36,29,.16);
|
|
32
|
+
--ease: cubic-bezier(.16, 1, .3, 1);
|
|
33
|
+
--mono: "JetBrains Mono", "Cascadia Code", Consolas, "Courier New", monospace;
|
|
34
|
+
--sans: "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif;
|
|
35
|
+
}
|
|
36
|
+
* { box-sizing: border-box; margin: 0; }
|
|
37
|
+
::selection { background: rgba(188,67,39,.18); }
|
|
38
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
39
|
+
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; border: 2px solid var(--bg); }
|
|
40
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
41
|
+
html, body { height: 100%; }
|
|
42
|
+
body { margin: 0; overflow: hidden; background: var(--bg); color: var(--text); font: 14px/1.7 "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif; letter-spacing: .01em; }
|
|
43
|
+
/* 纸张颗粒:5% 噪点叠层让纸面有物理质感;pointer-events 关闭不挡交互 */
|
|
44
|
+
body::before { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .05;
|
|
45
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
|
|
46
|
+
#app { height: 100%; display: flex; flex-direction: column; position: relative; z-index: 1; }
|
|
47
|
+
button, input { font: inherit; color: inherit; }
|
|
48
|
+
button { cursor: pointer; }
|
|
49
|
+
a { color: var(--text); text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 3px; }
|
|
50
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
51
|
+
|
|
52
|
+
/* ---- 顶栏:surface 纸条 + 墨字标题 + 朱方块记忆点 ---- */
|
|
53
|
+
#topbar {
|
|
54
|
+
flex: 0 0 58px; display: flex; align-items: center; gap: 14px; padding: 0 22px;
|
|
55
|
+
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
56
|
+
position: relative; z-index: 5;
|
|
57
|
+
}
|
|
58
|
+
.brand { display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; }
|
|
59
|
+
.brand h1 { font: 700 15px/1 var(--mono); letter-spacing: .02em; max-width: 560px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
60
|
+
.brand h1::after { content: ""; display: inline-block; width: 7px; height: 7px; background: var(--accent); margin-left: 9px; }
|
|
61
|
+
.brand .sub { max-width: 560px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font: 10.5px/1.3 var(--mono); letter-spacing: .02em; }
|
|
62
|
+
.stat { display: inline-flex; align-items: baseline; gap: 3px; font: 500 11px/1 var(--mono); color: var(--muted); background: transparent; border: 1px solid var(--border); border-radius: 3px; padding: 4px 8px; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
63
|
+
.stat b { color: var(--text); font-weight: 700; font-size: 12px; }
|
|
64
|
+
.stat.add b { color: var(--ok); }
|
|
65
|
+
.stat.del b { color: var(--bad); }
|
|
66
|
+
.stats { display: flex; align-items: center; gap: 6px; margin-left: auto; }
|
|
67
|
+
.actions { display: flex; align-items: center; gap: 8px; }
|
|
68
|
+
.btn { background: var(--card); border: 1px solid var(--border); color: var(--muted); border-radius: 3px; padding: 6px 10px; font: 12px/1.4 var(--mono); white-space: nowrap; transition: border-color .15s ease-out, color .15s ease-out, background .15s ease-out; }
|
|
69
|
+
.btn:hover { border-color: var(--border-strong); color: var(--text); background: var(--card-hover); }
|
|
70
|
+
.btn.on { background: var(--text); color: var(--bg); border-color: var(--text); }
|
|
71
|
+
kbd { font: 9px/1 var(--mono); padding: 1px 3px; margin-left: 4px; border: 1px solid var(--border-strong); border-radius: 2px; opacity: .75; }
|
|
72
|
+
#live { display: inline-flex; align-items: center; gap: 6px; font: 11px/1 var(--mono); color: var(--muted); white-space: nowrap; }
|
|
73
|
+
#live i { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
|
|
74
|
+
#live.off i { background: var(--warn); }
|
|
75
|
+
|
|
76
|
+
/* ---- 三栏 ---- */
|
|
77
|
+
#main { flex: 1; min-height: 0; display: grid; grid-template-columns: 300px minmax(0, 1fr) 420px; grid-template-rows: minmax(0, 1fr); }
|
|
78
|
+
|
|
79
|
+
/* ---- 左轨 ---- */
|
|
80
|
+
#sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); }
|
|
81
|
+
.side-head { padding: 14px 16px 12px; border-bottom: 1px solid var(--border); }
|
|
82
|
+
.side-head .eyebrow { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
|
|
83
|
+
.side-head .proj { display: flex; align-items: center; gap: 6px; margin-top: 5px; font-size: 14px; font-weight: 600; }
|
|
84
|
+
.side-head .proj span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
|
+
.side-tools { padding: 10px 12px; display: flex; gap: 6px; border-bottom: 1px solid var(--border); }
|
|
86
|
+
#search { flex: 1; min-width: 0; background: var(--card); border: 1px solid var(--border); border-radius: 3px; color: var(--text); padding: 5px 9px; font: 12px/1.4 var(--mono); outline: none; transition: border-color .15s ease-out; }
|
|
87
|
+
#search::placeholder { color: var(--faint); }
|
|
88
|
+
#search:focus { border-color: var(--border-strong); }
|
|
89
|
+
.mini { background: var(--card); border: 1px solid var(--border); border-radius: 3px; color: var(--muted); padding: 5px 9px; font: 11px/1.4 var(--mono); white-space: nowrap; transition: border-color .15s ease-out, color .15s ease-out; }
|
|
90
|
+
.mini:hover { border-color: var(--border-strong); color: var(--text); }
|
|
91
|
+
.side-sec { display: flex; align-items: center; justify-content: space-between; padding: 13px 16px 6px; }
|
|
92
|
+
.side-sec b { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
|
|
93
|
+
.side-sec span { font: 10px/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
94
|
+
#step-nav { max-height: 30%; overflow: auto; padding: 0 8px 8px; border-bottom: 1px solid var(--border); }
|
|
95
|
+
.nav-step {
|
|
96
|
+
display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
|
|
97
|
+
border: 0; border-left: 2px solid transparent;
|
|
98
|
+
background: transparent; padding: 6px 7px 6px 8px; font-size: 12px; color: var(--muted);
|
|
99
|
+
transition: background .15s ease-out, border-color .15s ease-out, color .15s ease-out;
|
|
100
|
+
}
|
|
101
|
+
.nav-step:hover { background: var(--bg); }
|
|
102
|
+
.nav-step.on { background: rgba(188,67,39,.08); border-left-color: var(--accent); color: var(--text); }
|
|
103
|
+
.nav-step .n {
|
|
104
|
+
flex: 0 0 20px; width: 20px; height: 20px; display: grid; place-items: center;
|
|
105
|
+
border: 1px solid var(--border-strong); border-radius: 2px; background: transparent;
|
|
106
|
+
color: var(--muted); font: 700 10px/1 var(--mono); font-variant-numeric: tabular-nums;
|
|
107
|
+
}
|
|
108
|
+
.nav-step.on .n { background: var(--accent); border-color: var(--accent); color: var(--bg); }
|
|
109
|
+
.nav-step.appendix .n { background: transparent; border-color: var(--border-strong); color: var(--muted); }
|
|
110
|
+
.nav-step.appendix.on .n { background: var(--accent); border-color: var(--accent); color: var(--bg); }
|
|
111
|
+
.nav-step.appendix .t { color: var(--muted); }
|
|
112
|
+
.nav-step.appendix.on .t { color: var(--text); }
|
|
113
|
+
.nav-step .t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
114
|
+
.nav-step .c { font: 10.5px var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
115
|
+
#file-tree { flex: 1; overflow: auto; padding: 4px 8px 34px; }
|
|
116
|
+
.tree-dir { user-select: none; }
|
|
117
|
+
.tree-row {
|
|
118
|
+
display: flex; align-items: center; gap: 6px; padding: 5px; border-radius: 3px;
|
|
119
|
+
font: 11.5px/1.5 var(--mono); color: var(--muted); cursor: pointer; white-space: nowrap;
|
|
120
|
+
transition: background .15s ease-out, color .15s ease-out;
|
|
121
|
+
}
|
|
122
|
+
.tree-row:hover { background: var(--bg); color: var(--text); }
|
|
123
|
+
.tree-row .chev { width: 10px; flex: 0 0 10px; color: var(--faint); font-size: 9px; transition: transform .15s ease-out; }
|
|
124
|
+
.tree-dir.closed > .tree-row .chev { transform: rotate(-90deg); }
|
|
125
|
+
.tree-dir.closed > .tree-children { display: none; }
|
|
126
|
+
.tree-row .folder { color: var(--muted); }
|
|
127
|
+
.tree-file {
|
|
128
|
+
display: flex; align-items: center; gap: 6px; padding: 5px 5px 5px 20px;
|
|
129
|
+
border-left: 2px solid transparent; border-radius: 0 3px 3px 0;
|
|
130
|
+
font: 11.5px/1.5 var(--mono); cursor: pointer; white-space: nowrap;
|
|
131
|
+
transition: background .15s ease-out, border-color .15s ease-out;
|
|
132
|
+
}
|
|
133
|
+
.tree-file:hover { background: var(--bg); }
|
|
134
|
+
.tree-file.on { background: rgba(188,67,39,.08); border-left-color: var(--accent); }
|
|
135
|
+
.tree-file .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
|
|
136
|
+
.tree-file .nm { overflow: hidden; text-overflow: ellipsis; }
|
|
137
|
+
.tree-file .bd { margin-left: auto; padding: 0 3px; border: 1px solid currentColor; border-radius: 2px; font: 9px/1.5 var(--mono); }
|
|
138
|
+
.side-foot { flex: 0 0 auto; border-top: 1px solid var(--border); padding: 9px 14px; font: 10px/1.75 var(--mono); color: var(--muted); }
|
|
139
|
+
.side-foot b { color: var(--text); font-weight: 700; }
|
|
140
|
+
|
|
141
|
+
/* ---- 画布 ---- */
|
|
142
|
+
#canvas-wrap { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: transparent; position: relative; }
|
|
143
|
+
#view-tabs {
|
|
144
|
+
flex: 0 0 42px; display: flex; align-items: stretch; padding: 0 16px;
|
|
145
|
+
background: transparent; border-bottom: 1px solid var(--border); overflow-x: auto; z-index: 3;
|
|
146
|
+
}
|
|
147
|
+
#view-tabs .tab {
|
|
148
|
+
position: relative; border: 0; background: transparent; color: var(--muted);
|
|
149
|
+
font-family: inherit; font-size: 13px; letter-spacing: .02em; padding: 0 13px;
|
|
150
|
+
white-space: nowrap; flex: 0 0 auto; transition: color .15s ease-out;
|
|
151
|
+
}
|
|
152
|
+
#view-tabs .tab::after { content: ""; position: absolute; left: 13px; right: 13px; bottom: 0; height: 2px; background: var(--accent); transform: scaleX(0); transition: transform .2s var(--ease); }
|
|
153
|
+
#view-tabs .tab:hover { color: var(--text); }
|
|
154
|
+
#view-tabs .tab.on { color: var(--text); font-weight: 600; }
|
|
155
|
+
#view-tabs .tab.on::after { transform: scaleX(1); }
|
|
156
|
+
#view-tabs .spacer { flex: 1; }
|
|
157
|
+
.layout-btn {
|
|
158
|
+
display: none; align-self: center; margin: 0 0 0 8px; padding: 5px 9px;
|
|
159
|
+
background: var(--card); border: 1px solid var(--border); border-radius: 3px;
|
|
160
|
+
color: var(--muted); font: 11px/1.4 var(--mono); white-space: nowrap;
|
|
161
|
+
transition: border-color .15s ease-out, color .15s ease-out;
|
|
162
|
+
}
|
|
163
|
+
.layout-btn:hover { border-color: var(--border-strong); color: var(--text); }
|
|
164
|
+
body.focus #sidebar, body.focus #detail { display: none; }
|
|
165
|
+
body.focus #main { grid-template-columns: minmax(0, 1fr); }
|
|
166
|
+
#pan-hint { display: none; position: absolute; right: 18px; bottom: 14px; z-index: 2; padding: 4px 8px; border: 1px solid var(--border); border-radius: 3px; background: var(--card); font: 10px/1.6 var(--mono); color: var(--muted); white-space: nowrap; }
|
|
167
|
+
.zoombar { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
|
|
168
|
+
.zoombar button {
|
|
169
|
+
width: 29px; height: 26px; background: var(--card); border: 1px solid var(--border); border-radius: 3px;
|
|
170
|
+
color: var(--muted); font: 12px/1 var(--mono);
|
|
171
|
+
transition: border-color .15s ease-out, color .15s ease-out;
|
|
172
|
+
}
|
|
173
|
+
.zoombar button:hover { border-color: var(--border-strong); color: var(--text); }
|
|
174
|
+
.zoombar .wide { width: auto; padding: 0 9px; font-size: 11px; }
|
|
175
|
+
#zoom-label { min-width: 38px; text-align: center; font: 10px var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
176
|
+
#canvas-scroll { flex: 1; min-height: 0; overflow: auto; position: relative; background: transparent; }
|
|
177
|
+
#canvas { min-width: 100%; min-height: 0; transform-origin: top left; }
|
|
178
|
+
#canvas.zoomed { transition: transform .15s ease-out; }
|
|
179
|
+
|
|
180
|
+
/* 全景图:网格总览(默认) */
|
|
181
|
+
#canvas.view-panorama.layout-grid { display: block; width: auto; padding: 26px 26px 64px; }
|
|
182
|
+
.station-grid {
|
|
183
|
+
display: grid;
|
|
184
|
+
grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
|
|
185
|
+
gap: 24px; align-items: start; max-width: 1180px; margin: 0 auto;
|
|
186
|
+
}
|
|
187
|
+
#canvas.layout-grid .station { width: auto; flex: none; }
|
|
188
|
+
#canvas.layout-grid .station::after,
|
|
189
|
+
#canvas.layout-grid .station::before { display: none; }
|
|
190
|
+
#canvas.layout-grid .st-card { padding: 16px 15px 14px; }
|
|
191
|
+
#canvas.layout-grid .st-no { width: 34px; height: 34px; flex-basis: 34px; }
|
|
192
|
+
#canvas.layout-grid .st-title { font-size: 16px; }
|
|
193
|
+
#canvas.layout-grid .st-summary { font-size: 13px; max-height: none; }
|
|
194
|
+
|
|
195
|
+
/* 全景图:站点串(时间线布局,站点间朱色细虚线衔接) */
|
|
196
|
+
#canvas.view-panorama { display: flex; align-items: flex-start; gap: 56px; padding: 34px 34px 80px; width: max-content; }
|
|
197
|
+
.station { width: 268px; min-width: 0; flex: 0 0 268px; position: relative; }
|
|
198
|
+
.station::after {
|
|
199
|
+
content: ""; position: absolute; right: -47px; top: 30px; width: 38px;
|
|
200
|
+
border-top: 1px dashed var(--accent); opacity: .7;
|
|
201
|
+
}
|
|
202
|
+
.station::before {
|
|
203
|
+
content: ""; position: absolute; right: -48px; top: 25px; width: 0; height: 0;
|
|
204
|
+
border-left: 7px solid var(--accent); border-top: 4px solid transparent; border-bottom: 4px solid transparent;
|
|
205
|
+
opacity: .9;
|
|
206
|
+
}
|
|
207
|
+
.station:last-child::after, .station:last-child::before { display: none; }
|
|
208
|
+
.st-top { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
|
|
209
|
+
.st-no { width: 30px; height: 30px; flex: 0 0 30px; display: grid; place-items: center; border-radius: 3px; background: var(--text); color: var(--bg); font: 700 12px/1 var(--mono); font-variant-numeric: tabular-nums; }
|
|
210
|
+
.station.warn .st-no { background: var(--warn); }
|
|
211
|
+
.st-headtext { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
212
|
+
.st-label { font: 700 10px/1 var(--mono); letter-spacing: .12em; color: var(--accent); }
|
|
213
|
+
.station.warn .st-label { color: var(--warn); }
|
|
214
|
+
.station:hover .st-label::after { content: " · 点击看全文"; letter-spacing: 0; font-family: var(--sans); font-weight: 400; color: var(--faint); }
|
|
215
|
+
/* 卡片是索引,不是全文:标题/摘要各收 3 行,全文在右侧「变更说明」承接 */
|
|
216
|
+
.st-title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: 15px; font-weight: 700; line-height: 1.4; color: var(--text); overflow-wrap: anywhere; }
|
|
217
|
+
.st-card {
|
|
218
|
+
background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
|
|
219
|
+
border-radius: 4px; padding: 12px 14px; cursor: pointer;
|
|
220
|
+
transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
|
|
221
|
+
}
|
|
222
|
+
.station:hover .st-card { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
|
|
223
|
+
.station.on .st-card { border-left-color: var(--accent); }
|
|
224
|
+
.st-summary { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: 12.5px; line-height: 1.65; color: var(--muted); margin: 0 0 10px; }
|
|
225
|
+
.st-summary .st-k { display: inline-block; margin-right: 5px; padding: 1px 3px; border: 1px solid rgba(62,113,87,.5); border-radius: 2px; vertical-align: 1px; color: var(--ok); font: 700 9px/1 var(--mono); letter-spacing: .08em; }
|
|
226
|
+
.st-files { display: flex; flex-direction: column; gap: 0; }
|
|
227
|
+
.st-file {
|
|
228
|
+
display: flex; align-items: center; gap: 6px; padding: 5px 0;
|
|
229
|
+
border-bottom: 1px dashed var(--border); background: transparent;
|
|
230
|
+
font: 11px/1.5 var(--mono); color: var(--text); white-space: nowrap; overflow: hidden;
|
|
231
|
+
}
|
|
232
|
+
.st-file:last-child { border-bottom: 0; }
|
|
233
|
+
.st-file .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; }
|
|
234
|
+
.st-file .nm { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; overflow: hidden; }
|
|
235
|
+
.st-file .nm .bn { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
236
|
+
.st-file .nm .dir { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--faint); font-size: 10px; }
|
|
237
|
+
.st-file > span:last-child { flex: 0 0 auto; font-size: 10px !important; }
|
|
238
|
+
.st-more { padding: 8px 0 0; font: 10.5px var(--mono); color: var(--accent); cursor: pointer; }
|
|
239
|
+
.st-more:hover { text-decoration: underline; }
|
|
240
|
+
.st-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); font-style: normal; }
|
|
241
|
+
.st-empty .lnk { color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
|
|
242
|
+
.station.appendix .st-no { background: var(--muted); }
|
|
243
|
+
.station.appendix .st-label { color: var(--muted); letter-spacing: .08em; }
|
|
244
|
+
.st-objs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border); }
|
|
245
|
+
|
|
246
|
+
/* 数据对象徽标:dto/entity/table 各一枚低饱和标签色,仅文字 + 1px 边 */
|
|
247
|
+
.kbadge { font: 700 9.5px/1.5 var(--mono); border: 1px solid var(--border-strong); border-radius: 2px; padding: 1px 5px; color: var(--muted); white-space: nowrap; }
|
|
248
|
+
.kbadge.dto { color: var(--plan); border-color: var(--plan); }
|
|
249
|
+
.kbadge.entity { color: var(--intent); border-color: var(--intent); }
|
|
250
|
+
.kbadge.table { color: var(--spec); border-color: var(--spec); }
|
|
251
|
+
.kbadge.on { color: var(--accent); border-color: var(--accent); }
|
|
252
|
+
|
|
253
|
+
/* 时序图 */
|
|
254
|
+
#canvas.view-sequence { padding: 26px 30px 70px; }
|
|
255
|
+
.seq-wrap { min-width: 560px; max-width: 1200px; margin: 0 auto; }
|
|
256
|
+
.seq-titlebar { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 0 2px 12px; }
|
|
257
|
+
.seq-titlebar b { font-size: 13px; font-weight: 600; }
|
|
258
|
+
.seq-titlebar span { font: 11px var(--mono); color: var(--muted); }
|
|
259
|
+
.seq-head { display: grid; grid-template-columns: repeat(var(--n, 5), minmax(0, 1fr)); gap: 8px; padding: 6px 0 0; }
|
|
260
|
+
.seq-part { text-align: center; }
|
|
261
|
+
.seq-part .box {
|
|
262
|
+
display: inline-block; max-width: 100%; padding: 5px 12px;
|
|
263
|
+
border: 1px solid var(--border-strong); border-radius: 3px;
|
|
264
|
+
background: var(--card); color: var(--text); font: 500 12px/1.6 var(--mono);
|
|
265
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
266
|
+
}
|
|
267
|
+
.seq-body { position: relative; margin: 14px 0 0; }
|
|
268
|
+
.seq-lifelines { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(var(--n, 5), minmax(0, 1fr)); pointer-events: none; }
|
|
269
|
+
.seq-lifelines i { justify-self: center; width: 0; border-left: 1px dashed var(--border-strong); }
|
|
270
|
+
.seq-msg { position: relative; height: 58px; cursor: pointer; transition: background .15s ease-out; }
|
|
271
|
+
.seq-msg:hover { background: rgba(188,67,39,.06); }
|
|
272
|
+
.seq-msg.on { background: rgba(188,67,39,.08); }
|
|
273
|
+
.seq-msg svg { position: absolute; inset: 0; width: 100%; height: 58px; overflow: visible; }
|
|
274
|
+
.seq-msg .lbl {
|
|
275
|
+
position: absolute; top: 7px; transform: translateX(-50%); max-width: 300px; padding: 3px 8px;
|
|
276
|
+
border: 1px solid var(--border); border-radius: 3px; background: var(--card);
|
|
277
|
+
font: 500 10.5px/1.4 var(--mono); color: var(--muted);
|
|
278
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
279
|
+
}
|
|
280
|
+
.seq-msg.on .lbl { border-color: var(--accent); color: var(--text); font-weight: 600; }
|
|
281
|
+
.seq-msg .lbl b { margin-right: 5px; color: var(--accent); font: 700 9px var(--mono); }
|
|
282
|
+
.seq-empty { padding: 50px; text-align: center; color: var(--faint); font: 12px var(--mono); }
|
|
283
|
+
|
|
284
|
+
/* 流程图 */
|
|
285
|
+
#canvas.view-flow { padding: 30px 24px 80px; }
|
|
286
|
+
.flow-wrap { max-width: 780px; margin: 0 auto; }
|
|
287
|
+
.flow-node {
|
|
288
|
+
background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
|
|
289
|
+
border-radius: 4px; padding: 12px 14px; cursor: pointer;
|
|
290
|
+
transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
|
|
291
|
+
}
|
|
292
|
+
.flow-node:hover { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
|
|
293
|
+
.flow-node.on { border-left-color: var(--accent); }
|
|
294
|
+
.flow-node .fn-head { display: flex; align-items: center; gap: 9px; }
|
|
295
|
+
.flow-node .fn-no { padding: 2px 5px; border: 1px solid var(--border-strong); border-radius: 2px; color: var(--muted); font: 700 10px/1.4 var(--mono); font-variant-numeric: tabular-nums; }
|
|
296
|
+
.flow-node .fn-title { font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
|
|
297
|
+
.flow-node .fn-sum { font-size: 12px; line-height: 1.65; color: var(--muted); margin: 5px 0 0 33px; }
|
|
298
|
+
.flow-node .fn-files { margin: 8px 0 0 33px; display: flex; flex-wrap: wrap; gap: 5px; }
|
|
299
|
+
.flow-node .fn-files span { padding: 1px 6px; border: 1px solid var(--border); border-radius: 2px; background: transparent; font: 10px/1.6 var(--mono); color: var(--muted); }
|
|
300
|
+
.flow-arrow { height: 32px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--accent); }
|
|
301
|
+
.flow-arrow i { width: 0; height: 18px; border-left: 1px dashed var(--accent); }
|
|
302
|
+
.flow-arrow b { width: 0; height: 0; margin-top: -2px; border-top: 5px solid var(--accent); border-left: 4px solid transparent; border-right: 4px solid transparent; }
|
|
303
|
+
.flow-pill {
|
|
304
|
+
display: flex; align-items: center; justify-content: center; gap: 8px; padding: 9px 16px;
|
|
305
|
+
border: 1px solid var(--border-strong); border-radius: 3px; background: var(--card);
|
|
306
|
+
font: 11px/1.6 var(--mono); color: var(--muted);
|
|
307
|
+
}
|
|
308
|
+
.flow-pill.end { color: var(--ok); border-color: var(--ok); }
|
|
309
|
+
.flow-gate {
|
|
310
|
+
padding: 12px 14px; border: 1px solid var(--border); border-left: 3px solid var(--warn);
|
|
311
|
+
border-radius: 4px; background: rgba(125,98,36,.08);
|
|
312
|
+
}
|
|
313
|
+
.flow-gate h4 { margin: 0 0 6px; font: 600 11px/1.6 var(--mono); letter-spacing: .06em; color: var(--warn); }
|
|
314
|
+
.flow-gate ul { margin: 0; padding-left: 18px; font: 11.5px/1.7 var(--mono); color: var(--muted); }
|
|
315
|
+
/* 需求视图:本档目标 + 上游 intent/spec/incident 叙事卡 */
|
|
316
|
+
.reqs-wrap { max-width: 860px; padding: 26px 30px 60px; margin: 0 auto; }
|
|
317
|
+
.req-card { position: relative; background: var(--card); border: 1px solid var(--border); border-radius: 5px; padding: 16px 18px; margin-bottom: 14px; box-shadow: var(--shadow-1); }
|
|
318
|
+
.req-card.self { border-left: 3px solid var(--accent); }
|
|
319
|
+
.req-eyebrow { display: flex; align-items: center; gap: 8px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
|
|
320
|
+
.req-badge { display: inline-block; padding: 2px 7px; border-radius: 2px; font: 700 9.5px/1.5 var(--mono); letter-spacing: .06em; color: var(--bg); background: var(--muted); }
|
|
321
|
+
.req-badge[data-type="INTENT"] { background: var(--intent); }
|
|
322
|
+
.req-badge[data-type="SPEC"] { background: var(--spec); }
|
|
323
|
+
.req-badge[data-type="PLAN"] { background: var(--plan); }
|
|
324
|
+
.req-badge[data-type="INCIDENT"] { background: var(--incident); }
|
|
325
|
+
.req-card h3 { margin: 8px 0 4px; font-size: 14.5px; font-weight: 600; }
|
|
326
|
+
.req-card .req-path { font: 10.5px/1.4 var(--mono); color: var(--faint); }
|
|
327
|
+
.req-card .req-why { margin: 9px 0 0; font-size: 13px; line-height: 1.75; }
|
|
328
|
+
.req-card .req-why b { color: var(--accent); font: 600 10px/1 var(--mono); letter-spacing: .1em; margin-right: 7px; }
|
|
329
|
+
.req-points { margin: 10px 0 0; padding: 0; list-style: none; }
|
|
330
|
+
.req-points li { position: relative; padding: 5px 0 5px 16px; font-size: 12.5px; line-height: 1.7; color: var(--text); border-top: 1px dashed var(--border); }
|
|
331
|
+
.req-points li::before { content: "·"; position: absolute; left: 3px; color: var(--accent); font-weight: 700; }
|
|
332
|
+
.req-points code, .req-why code { font: .92em var(--mono); background: var(--bg); border: 1px solid var(--border); border-radius: 2px; padding: 0 4px; }
|
|
333
|
+
/* 验证视图:左计划清单 + 右实际凭证表,状态条在顶 */
|
|
334
|
+
.vfy-status { display: flex; align-items: center; gap: 8px; max-width: 860px; margin: 0 auto; padding: 18px 30px 0; font: 12px/1.6 var(--mono); color: var(--muted); }
|
|
335
|
+
.vfy-status i { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--ok); }
|
|
336
|
+
.vfy-status.bad i { background: var(--bad); }
|
|
337
|
+
.vfy-status b { color: var(--text); font-weight: 700; }
|
|
338
|
+
.vfy-cols { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 14px; max-width: 1160px; margin: 0 auto; padding: 14px 30px 60px; align-items: start; }
|
|
339
|
+
.vfy-plan h4, .vfy-ev h4 { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); margin-bottom: 8px; }
|
|
340
|
+
.vfy-plan ul { margin: 0; padding: 0; list-style: none; background: var(--card); border: 1px solid var(--border); border-radius: 5px; box-shadow: var(--shadow-1); }
|
|
341
|
+
.vfy-plan li { padding: 9px 14px; font-size: 12.5px; line-height: 1.65; border-bottom: 1px dashed var(--border); }
|
|
342
|
+
.vfy-plan li:last-child { border-bottom: 0; }
|
|
343
|
+
.vfy-table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--border); border-radius: 5px; overflow: hidden; box-shadow: var(--shadow-1); font: 11.5px/1.5 var(--mono); }
|
|
344
|
+
.vfy-table th { text-align: left; padding: 8px 10px; font: 600 9.5px/1 var(--mono); letter-spacing: .1em; color: var(--muted); border-bottom: 1px solid var(--border-strong); background: var(--surface); }
|
|
345
|
+
.vfy-table td { padding: 7px 10px; border-bottom: 1px dashed var(--border); color: var(--text); font-variant-numeric: tabular-nums; }
|
|
346
|
+
.vfy-table tr:last-child td { border-bottom: 0; }
|
|
347
|
+
.vfy-exit { font-weight: 700; }
|
|
348
|
+
.vfy-exit.ok { color: var(--ok); }
|
|
349
|
+
.vfy-exit.bad { color: var(--bad); }
|
|
350
|
+
.vfy-src { display: inline-block; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font-size: 9.5px; color: var(--muted); }
|
|
351
|
+
.vfy-skip { color: var(--faint); font-size: 10px; }
|
|
352
|
+
.vfy-note { max-width: 860px; margin: 0 auto; padding: 0 30px 40px; font: 10.5px/1.6 var(--mono); color: var(--faint); }
|
|
353
|
+
/* 验证 tab 门禁化(2026-11-11 P2):套件卡 + 可展开凭证明细 + 失败日志尾部 */
|
|
354
|
+
.vfy-status { max-width: 1160px; }
|
|
355
|
+
.vfy-status-main { display: flex; flex-direction: column; gap: 2px; }
|
|
356
|
+
.vfy-green { color: var(--ok); }
|
|
357
|
+
.vfy-sum { max-width: 1160px; margin: 6px auto 0; padding: 0 30px; font: 10.5px/1.7 var(--mono); color: var(--faint); }
|
|
358
|
+
.vfy-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; max-width: 1160px; margin: 0 auto; padding: 12px 30px 0; }
|
|
359
|
+
.vfy-card { background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong); border-radius: 5px; padding: 10px 12px; box-shadow: var(--shadow-1); }
|
|
360
|
+
.vfy-card.ok { border-left-color: var(--ok); }
|
|
361
|
+
.vfy-card.bad { border-left-color: var(--bad); }
|
|
362
|
+
.vfy-card-h { display: flex; align-items: baseline; gap: 8px; }
|
|
363
|
+
.vfy-card-h b { font: 600 12.5px/1.5 var(--mono); }
|
|
364
|
+
.vfy-card-h time { margin-left: auto; font: 10.5px/1.5 var(--mono); color: var(--faint); }
|
|
365
|
+
.vfy-state { flex: 0 0 auto; padding: 1px 6px; border: 1px solid currentColor; border-radius: 2px; font: 600 10px/1.6 var(--mono); }
|
|
366
|
+
.vfy-card.ok .vfy-state { color: var(--ok); }
|
|
367
|
+
.vfy-card.bad .vfy-state { color: var(--bad); }
|
|
368
|
+
.vfy-card-meta { margin-top: 4px; font: 10.5px/1.6 var(--mono); color: var(--muted); }
|
|
369
|
+
.vfy-card-note { margin-top: 5px; font: 11px/1.6 var(--sans); color: var(--muted); overflow-wrap: anywhere; }
|
|
370
|
+
.vfy-copy { margin-left: 6px; border: 1px solid var(--border); background: transparent; color: var(--accent); border-radius: 2px; padding: 0 5px; font: 10px/1.6 var(--mono); cursor: pointer; }
|
|
371
|
+
.vfy-copy:hover { border-color: var(--accent); }
|
|
372
|
+
.vfy-skips { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 6px; }
|
|
373
|
+
.vfy-skips span { padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font: 9.5px/1.6 var(--mono); color: var(--muted); }
|
|
374
|
+
.vfy-log { margin-top: 7px; }
|
|
375
|
+
.vfy-log summary { cursor: pointer; font: 10.5px/1.7 var(--mono); color: var(--accent); }
|
|
376
|
+
.vfy-log pre { max-height: 220px; overflow: auto; margin: 5px 0 0; padding: 8px; background: #26241d; color: #d8d4c8; border-radius: 3px; font: 10px/1.55 var(--mono); white-space: pre-wrap; }
|
|
377
|
+
.vfy-gates { margin-top: 7px; }
|
|
378
|
+
.vfy-gates summary { cursor: pointer; font: 10.5px/1.7 var(--mono); color: var(--accent); }
|
|
379
|
+
.vfy-gate-grid { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 6px; }
|
|
380
|
+
.vfy-gate-grid span { display: inline-flex; align-items: baseline; gap: 4px; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font: 9.5px/1.6 var(--mono); color: var(--muted); }
|
|
381
|
+
.vfy-gate-grid span.pass { border-color: rgba(62,113,87,.45); color: var(--ok); }
|
|
382
|
+
.vfy-gate-grid span.skip { color: var(--faint); border-style: dashed; }
|
|
383
|
+
.vfy-gate-grid b { font-weight: 600; }
|
|
384
|
+
.vfy-row { background: var(--card); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 7px; box-shadow: var(--shadow-1); }
|
|
385
|
+
.vfy-row summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding: 8px 10px; cursor: pointer; font: 10.5px/1.6 var(--mono); color: var(--muted); }
|
|
386
|
+
.vfy-row[open] summary { border-bottom: 1px solid var(--border); }
|
|
387
|
+
.vfy-row summary::-webkit-details-marker { display: none; }
|
|
388
|
+
.vfy-row summary::before { content: "▸"; color: var(--faint); }
|
|
389
|
+
.vfy-row[open] summary::before { content: "▾"; }
|
|
390
|
+
.vfy-rs { color: var(--text); font-weight: 600; }
|
|
391
|
+
.vfy-re.ok { color: var(--ok); font-weight: 700; }
|
|
392
|
+
.vfy-re.bad { color: var(--bad); font-weight: 700; }
|
|
393
|
+
.vfy-rg, .vfy-rk, .vfy-rh { color: var(--faint); }
|
|
394
|
+
.vfy-row-body { padding: 9px 12px; font: 11.5px/1.7 var(--sans); color: var(--muted); }
|
|
395
|
+
.vfy-row-body > div + div { margin-top: 4px; }
|
|
396
|
+
.vfy-row-meta { font: 10px/1.6 var(--mono); color: var(--faint); }
|
|
397
|
+
.vfy-gate-ev { margin-top: 8px; padding-top: 7px; border-top: 1px dashed var(--border); font: 10px/1.5 var(--mono); color: var(--muted); }
|
|
398
|
+
.vfy-gate-ev b { color: var(--ok); font-weight: 700; }
|
|
399
|
+
.vfy-gate-ev.bad b { color: var(--bad); }
|
|
400
|
+
.flow-fail { margin: 8px 0 0 27px; padding-left: 10px; border-left: 2px dashed var(--bad); font: 10.5px/1.6 var(--mono); color: var(--bad); }
|
|
401
|
+
/* 依赖旁路弧:声明的跨步依赖(主链相邻边不重复画),左侧细轨,hover 步骤高亮其上下游 */
|
|
402
|
+
.flow-wrap.has-deps { position: relative; padding-left: 46px; }
|
|
403
|
+
.flow-deps { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
|
|
404
|
+
.flow-deps .dep { fill: none; stroke: var(--border-strong); stroke-width: 1.1; opacity: .7; transition: stroke .15s ease-out, opacity .15s ease-out; }
|
|
405
|
+
.flow-deps .dep.back { stroke: var(--bad); stroke-dasharray: 3 3; }
|
|
406
|
+
.flow-deps .dep.on { stroke: var(--accent); stroke-width: 1.6; opacity: 1; }
|
|
407
|
+
.flow-deps .dep-head { fill: var(--border-strong); opacity: .7; transition: fill .15s ease-out, opacity .15s ease-out; }
|
|
408
|
+
.flow-deps .dep-head.on { fill: var(--accent); opacity: 1; }
|
|
409
|
+
.flow-node.rel { border-color: var(--accent); }
|
|
410
|
+
|
|
411
|
+
/* 数据流 */
|
|
412
|
+
#canvas.view-dataflow { padding: 26px 30px 70px; }
|
|
413
|
+
.df-wrap { max-width: 980px; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }
|
|
414
|
+
.df-sum { font: 11px/1.6 var(--mono); color: var(--muted); padding: 0 2px 4px; }
|
|
415
|
+
.df-sec { font: 600 11px/1 var(--mono); letter-spacing: .12em; color: var(--muted); padding: 14px 2px 2px; }
|
|
416
|
+
.df-obj {
|
|
417
|
+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
418
|
+
background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
|
|
419
|
+
border-radius: 4px; padding: 12px 14px; cursor: pointer;
|
|
420
|
+
transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
|
|
421
|
+
}
|
|
422
|
+
.df-obj:hover { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
|
|
423
|
+
.df-obj.on { border-left-color: var(--accent); }
|
|
424
|
+
.df-name { font: 600 12.5px/1.6 var(--mono); }
|
|
425
|
+
.df-nfiles { font: 10px/1.6 var(--mono); color: var(--faint); }
|
|
426
|
+
.df-lane { margin-left: auto; display: flex; align-items: flex-start; }
|
|
427
|
+
.df-slot { display: flex; flex-direction: column; align-items: center; gap: 5px; }
|
|
428
|
+
.df-slot .lyr { padding: 3px 10px; border: 1px solid var(--border-strong); border-radius: 3px; background: var(--card); font: 500 11px/1.6 var(--mono); color: var(--text); white-space: nowrap; }
|
|
429
|
+
.df-slot .fs { max-width: 150px; text-align: center; font: 9.5px/1.5 var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
430
|
+
.df-link { width: 24px; height: 24px; align-self: center; position: relative; flex: 0 0 24px; }
|
|
431
|
+
.df-link::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; border-top: 1px solid var(--border-strong); }
|
|
432
|
+
.df-link::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 0; height: 0; border-left: 5px solid var(--accent); border-top: 3px solid transparent; border-bottom: 3px solid transparent; }
|
|
433
|
+
.df-flow { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 8px 12px; background: var(--card); border: 1px solid var(--border); border-radius: 3px; font: 11.5px/1.6 var(--mono); color: var(--muted); }
|
|
434
|
+
.df-flow .arr { color: var(--faint); }
|
|
435
|
+
.df-flow .kbadge { font-size: 9px; }
|
|
436
|
+
.df-step { border: 0; background: none; padding: 0; font: 600 11.5px/1.6 var(--mono); color: var(--text); }
|
|
437
|
+
.df-step:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
|
|
438
|
+
.df-step b { color: var(--accent); font-weight: 700; margin-right: 4px; }
|
|
439
|
+
|
|
440
|
+
/* ---------- 右轨:变更 dossier ---------- */
|
|
441
|
+
#detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--border); }
|
|
442
|
+
.detail-head { flex: 0 0 auto; padding: 14px 16px 11px; border-bottom: 1px solid var(--border); }
|
|
443
|
+
.detail-head .dh { display: flex; align-items: center; gap: 9px; }
|
|
444
|
+
.detail-head h2 { font-size: 15px; font-weight: 700; }
|
|
445
|
+
#detail-sub { font: 10.5px var(--mono); color: var(--faint); }
|
|
446
|
+
.dback { margin-left: auto; border: 1px solid var(--border); background: var(--card); color: var(--muted); border-radius: 3px; padding: 1px 8px; font: 10.5px/1.7 var(--mono); cursor: pointer; }
|
|
447
|
+
.dback:hover { color: var(--text); border-color: var(--accent); }
|
|
448
|
+
.dback[hidden] { display: none; }
|
|
449
|
+
/* 右侧总览:范围 / 未归属 / 验证 / 解析风险;选中步骤时再下钻全文,避免复述中间全景 */
|
|
450
|
+
.ov { display: flex; flex-direction: column; gap: 13px; }
|
|
451
|
+
.ov-sec { padding-bottom: 12px; border-bottom: 1px dashed var(--border); }
|
|
452
|
+
.ov-sec:last-child { padding-bottom: 0; border-bottom: 0; }
|
|
453
|
+
.ov-eyebrow { margin-bottom: 7px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
|
|
454
|
+
.ov-eyebrow b { margin-left: 3px; font-variant-numeric: tabular-nums; }
|
|
455
|
+
.ov-kpis { font: 12.5px/1.7 var(--sans); color: var(--text); }
|
|
456
|
+
.ov-kpis b { font: 600 14px/1 var(--mono); }
|
|
457
|
+
.ov-sub { margin-top: 3px; color: var(--muted); font: 11.5px/1.7 var(--sans); }
|
|
458
|
+
.ov-sub code { font: 11px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
|
|
459
|
+
.ov-add { color: var(--ok); font-family: var(--mono); }
|
|
460
|
+
.ov-del { color: var(--bad); font-family: var(--mono); }
|
|
461
|
+
.ov-link { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; padding: 0; border: 0; background: transparent; color: var(--accent); font: 11.5px/1.7 var(--mono); cursor: pointer; }
|
|
462
|
+
.ov-link:hover { text-decoration: underline; }
|
|
463
|
+
.ov-status { display: flex; align-items: baseline; gap: 6px; font: 11.5px/1.7 var(--mono); color: var(--muted); overflow-wrap: anywhere; }
|
|
464
|
+
.ov-status i { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
|
|
465
|
+
.ov-status.ok { color: var(--ok); } .ov-status.ok i { background: var(--ok); }
|
|
466
|
+
.ov-status.bad { color: var(--bad); } .ov-status.bad i { background: var(--bad); }
|
|
467
|
+
#filter-tabs { display: flex; margin-top: 9px; overflow-x: auto; padding-bottom: 1px; }
|
|
468
|
+
#filter-tabs button {
|
|
469
|
+
border: 0; border-bottom: 2px solid transparent; background: transparent;
|
|
470
|
+
color: var(--muted); padding: 6px 9px; font: 11.5px/1.6 var(--mono); white-space: nowrap;
|
|
471
|
+
transition: color .15s ease-out, border-color .15s ease-out;
|
|
472
|
+
}
|
|
473
|
+
#filter-tabs button:hover { color: var(--text); }
|
|
474
|
+
#filter-tabs button.on { color: var(--text); border-bottom-color: var(--accent); font-weight: 700; }
|
|
475
|
+
#filter-tabs button .n { margin-left: 4px; color: var(--faint); font: 10.5px var(--mono); font-weight: 400; }
|
|
476
|
+
#detail-body { flex: 1; overflow: auto; padding: 13px 14px 54px; }
|
|
477
|
+
.step-card {
|
|
478
|
+
background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
|
|
479
|
+
border-radius: 4px; margin-bottom: 12px;
|
|
480
|
+
transition: border-color .15s ease-out;
|
|
481
|
+
}
|
|
482
|
+
.step-card.on { border-color: var(--border-strong); border-left-color: var(--accent); }
|
|
483
|
+
.step-card.warn { border-left-color: var(--warn); }
|
|
484
|
+
.sc-head {
|
|
485
|
+
display: flex; align-items: flex-start; gap: 9px; padding: 10px 12px;
|
|
486
|
+
border-bottom: 1px solid var(--border); cursor: pointer;
|
|
487
|
+
}
|
|
488
|
+
.sc-head .sc-no { flex: 0 0 auto; padding: 2px 5px; border: 1px solid var(--border-strong); border-radius: 2px; color: var(--muted); font: 700 10px/1.4 var(--mono); font-variant-numeric: tabular-nums; }
|
|
489
|
+
.step-card.appendix .sc-no { border-style: dashed; color: var(--faint); }
|
|
490
|
+
.step-card.warn .sc-head .sc-no { color: var(--warn); border-color: var(--warn); }
|
|
491
|
+
.sc-titlewrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
492
|
+
.sc-eyebrow { font: 600 10px/1.2 var(--mono); letter-spacing: .1em; color: var(--muted); }
|
|
493
|
+
.sc-eyebrow .done { margin-left: 6px; color: var(--ok); }
|
|
494
|
+
.sc-head h3 { flex: none; font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
495
|
+
.sc-head .meta { font: 10.5px var(--mono); color: var(--faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
496
|
+
.sc-body { padding: 9px 12px 11px; }
|
|
497
|
+
.sc-summary { margin: 0 0 6px; font-size: 13px; line-height: 1.65; }
|
|
498
|
+
.sc-detail { margin: 0 0 7px; padding-left: 16px; font-size: 12.5px; line-height: 1.65; color: var(--muted); }
|
|
499
|
+
.sc-detail li { margin: 2px 0; }
|
|
500
|
+
.sc-files { display: flex; flex-direction: column; gap: 0; margin-top: 0; }
|
|
501
|
+
.sc-sec-h { margin: 9px 0 3px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
|
|
502
|
+
.sc-sec-h b { color: var(--text); font-variant-numeric: tabular-nums; }
|
|
503
|
+
.sc-sec { margin-top: 11px; }
|
|
504
|
+
.sc-sec-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); }
|
|
505
|
+
.sc-req { margin-bottom: 6px; }
|
|
506
|
+
.sc-req-title { margin-left: 6px; font: 12px/1.6 var(--sans); color: var(--text); }
|
|
507
|
+
.sc-req-why { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 2px; font: 12px/1.65 var(--sans); color: var(--muted); }
|
|
508
|
+
.sc-req-why code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
|
|
509
|
+
.sc-proof-verify { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px 7px; margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border); font: 11.5px/1.7 var(--sans); color: var(--muted); }
|
|
510
|
+
.sc-proof-verify b { font: 600 10px/1 var(--mono); letter-spacing: .08em; }
|
|
511
|
+
.sc-proof-verify .ok { color: var(--ok); }
|
|
512
|
+
.sc-proof-verify .bad { color: var(--bad); }
|
|
513
|
+
.sc-proof { margin-top: 0; padding: 8px 10px; border: 1px solid var(--border); border-left: 3px solid var(--ok); border-radius: 3px; background: rgba(62,113,87,.05); }
|
|
514
|
+
.sc-proof.empty { border-left-color: var(--border-strong); background: transparent; }
|
|
515
|
+
.sc-proof-h { margin-bottom: 5px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--ok); }
|
|
516
|
+
.sc-proof.empty .sc-proof-h { color: var(--muted); }
|
|
517
|
+
.sc-accept { margin: 0; padding-left: 16px; font: 12.5px/1.7 var(--sans); color: var(--text); }
|
|
518
|
+
.sc-accept li { margin: 2px 0; }
|
|
519
|
+
.sc-accept code, .sc-proof-meta code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
|
|
520
|
+
.sc-proof-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); }
|
|
521
|
+
.sc-proof-meta { margin-top: 5px; font: 11.5px/1.7 var(--sans); color: var(--muted); }
|
|
522
|
+
.sc-proof-meta b { margin-right: 5px; font: 600 10px/1 var(--mono); letter-spacing: .08em; color: var(--muted); }
|
|
523
|
+
.file-row {
|
|
524
|
+
display: flex; align-items: center; gap: 6px; padding: 6px 4px; border-radius: 3px;
|
|
525
|
+
border-bottom: 1px dashed var(--border); cursor: pointer;
|
|
526
|
+
transition: background .15s ease-out;
|
|
527
|
+
}
|
|
528
|
+
.file-row:hover { background: var(--bg); }
|
|
529
|
+
.file-row.on { background: rgba(188,67,39,.08); }
|
|
530
|
+
.file-row .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; }
|
|
531
|
+
.file-row .path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 11.5px/1.5 var(--mono); direction: rtl; text-align: left; }
|
|
532
|
+
.file-row .num { font: 10px var(--mono); color: var(--ok); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
533
|
+
.file-row .num del { margin-left: 3px; color: var(--bad); text-decoration: none; }
|
|
534
|
+
.badge {
|
|
535
|
+
padding: 1px 4px; border: 1px solid currentColor; border-radius: 2px;
|
|
536
|
+
font: 700 9.5px/1.5 var(--mono); white-space: nowrap;
|
|
537
|
+
}
|
|
538
|
+
.badge.new { color: var(--ok); }
|
|
539
|
+
.badge.modified { color: var(--accent); }
|
|
540
|
+
.badge.deleted { color: var(--bad); }
|
|
541
|
+
.badge.renamed { color: var(--muted); border-color: var(--border-strong); }
|
|
542
|
+
.badge.planned { color: var(--warn); }
|
|
543
|
+
.badge.test { color: var(--intent); }
|
|
544
|
+
.badge.doc { color: var(--muted); border-color: var(--border-strong); }
|
|
545
|
+
.diff-btn {
|
|
546
|
+
border: 1px solid var(--border); border-radius: 2px; background: transparent;
|
|
547
|
+
padding: 0 4px; font: 9px/1.6 var(--mono); color: var(--muted);
|
|
548
|
+
}
|
|
549
|
+
.diff-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
550
|
+
.diff {
|
|
551
|
+
display: none; margin: 3px 0 8px; padding: 9px 10px;
|
|
552
|
+
border-left: 3px solid var(--accent); border-radius: 0 2px 2px 0;
|
|
553
|
+
background: #26241d; color: #d8d4c8;
|
|
554
|
+
font: 11px/1.65 var(--mono); overflow: auto; max-height: 320px; white-space: pre;
|
|
555
|
+
}
|
|
556
|
+
.diff.open { display: block; }
|
|
557
|
+
.diff .add { color: #8fbf9c; }
|
|
558
|
+
.diff .del { color: #e0a498; }
|
|
559
|
+
.diff .hunk { color: #d8b56a; }
|
|
560
|
+
.diff .meta { color: #8a8474; }
|
|
561
|
+
.none { padding: 38px 16px; text-align: center; color: var(--faint); font: 12px var(--mono); }
|
|
562
|
+
/* 解析告警条:折叠摘要 + 可滚动逐条清单(2026-11-11 版面/字号修订)
|
|
563
|
+
旧版用 <br> 拼长串、11px 等宽且不换行,条数一多就横向溢出并吃掉整页;现改为
|
|
564
|
+
标题行常驻(计数 + 行号预览)、明细默认折叠且限高滚动,正文换行、路径走 code。 */
|
|
565
|
+
.warnbox { margin: 0 0 11px; padding: 0; border: 1px solid var(--border); border-left: 3px solid var(--warn); border-radius: 4px; background: rgba(125,98,36,.07); font: 13px/1.7 var(--sans); color: var(--text); overflow: hidden; }
|
|
566
|
+
#parse-warn { flex: 0 0 auto; margin: 8px 12px 0; }
|
|
567
|
+
.warn-head { display: flex; align-items: center; gap: 8px; padding: 7px 10px; }
|
|
568
|
+
.warn-head-main { display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0; }
|
|
569
|
+
.warn-icon { flex: 0 0 auto; color: var(--warn); font-size: 13px; }
|
|
570
|
+
.warn-head b { flex: 0 0 auto; font-weight: 600; color: var(--warn); }
|
|
571
|
+
.warn-count { flex: 0 0 auto; font: 600 11px/1 var(--mono); color: var(--warn); background: rgba(125,98,36,.14); border-radius: 9px; padding: 3px 7px; font-variant-numeric: tabular-nums; }
|
|
572
|
+
.warn-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
|
|
573
|
+
.warn-toggle { flex: 0 0 auto; border: 1px solid var(--border-strong); background: var(--card); color: var(--muted); border-radius: 3px; padding: 2px 9px; font: 11.5px/1.7 var(--mono); }
|
|
574
|
+
.warn-toggle:hover { color: var(--text); border-color: var(--accent); }
|
|
575
|
+
.warn-body { max-height: min(32vh, 260px); overflow: auto; padding: 4px 10px 8px; border-top: 1px solid var(--border); }
|
|
576
|
+
.warn-note { margin: 4px 0; color: var(--warn); font-size: 12.5px; }
|
|
577
|
+
.warn-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 1px 8px; padding: 4px 6px; border-radius: 3px; }
|
|
578
|
+
.warn-row + .warn-row { border-top: 1px dashed rgba(125,98,36,.16); }
|
|
579
|
+
.warn-row:hover { background: rgba(125,98,36,.07); }
|
|
580
|
+
.warn-line { flex: 0 0 auto; font: 600 11.5px/1.7 var(--mono); color: var(--warn); font-variant-numeric: tabular-nums; }
|
|
581
|
+
.warn-text { flex: 1 1 420px; min-width: 0; overflow-wrap: anywhere; }
|
|
582
|
+
.warn-text code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
|
|
583
|
+
.warn-kind { flex: 0 0 auto; font: 10.5px/1.7 var(--mono); color: var(--warn); border: 1px solid rgba(125,98,36,.5); border-radius: 2px; padding: 0 4px; white-space: nowrap; }
|
|
584
|
+
.warn-kind.bad { color: var(--bad); border-color: rgba(160,75,53,.5); }
|
|
585
|
+
.warn-kind.verify { color: var(--ok); border-color: rgba(62,113,87,.5); }
|
|
586
|
+
.warn-group { border-top: 1px dashed rgba(125,98,36,.16); }
|
|
587
|
+
.warn-group:first-of-type { border-top: 0; }
|
|
588
|
+
.warn-group-head { display: flex; align-items: baseline; gap: 7px; width: 100%; padding: 5px 6px; border: 0; border-radius: 3px; background: transparent; text-align: left; color: var(--text); font: 13px/1.7 var(--sans); cursor: pointer; }
|
|
589
|
+
.warn-group-head:hover { background: rgba(125,98,36,.07); }
|
|
590
|
+
.wg-chev { flex: 0 0 auto; width: 10px; color: var(--faint); font-size: 10px; }
|
|
591
|
+
.wg-name { flex: 0 0 auto; font-weight: 600; color: var(--warn); }
|
|
592
|
+
.wg-hint { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
|
|
593
|
+
.wg-n { flex: 0 0 auto; font: 10.5px/1.7 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
594
|
+
.warn-group-body { padding: 0 0 3px; }
|
|
595
|
+
|
|
596
|
+
/* ---------- 入场编排 ---------- */
|
|
597
|
+
@keyframes rise {
|
|
598
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
599
|
+
to { opacity: 1; transform: none; }
|
|
600
|
+
}
|
|
601
|
+
#topbar { animation: rise 280ms var(--ease) both; }
|
|
602
|
+
#sidebar { animation: rise 280ms var(--ease) 30ms both; }
|
|
603
|
+
#canvas-wrap { animation: rise 280ms var(--ease) 60ms both; }
|
|
604
|
+
#detail { animation: rise 280ms var(--ease) 90ms both; }
|
|
605
|
+
#app.booting .station { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
|
|
606
|
+
#app.booting .flow-node { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
|
|
607
|
+
#app.booting .df-obj { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
|
|
608
|
+
#app.booting .step-card { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
|
|
609
|
+
|
|
610
|
+
@media (prefers-reduced-motion: reduce) {
|
|
611
|
+
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@media (max-width: 1280px) {
|
|
615
|
+
#main { grid-template-columns: 280px minmax(0, 1fr) 380px; }
|
|
616
|
+
.brand h1, .brand .sub { max-width: 340px; }
|
|
617
|
+
.stat:nth-child(n+7) { display: none; }
|
|
618
|
+
}
|
|
619
|
+
@media (max-width: 1180px) {
|
|
620
|
+
#main { grid-template-columns: 260px minmax(0, 1fr) 350px; }
|
|
621
|
+
.stat:nth-child(n+5) { display: none; }
|
|
622
|
+
.vfy-cols { grid-template-columns: 1fr; }
|
|
623
|
+
}
|
|
624
|
+
@media (max-width: 900px) {
|
|
625
|
+
#main { grid-template-columns: 240px minmax(0, 1fr); }
|
|
626
|
+
.vfy-cols { grid-template-columns: 1fr; padding-left: 20px; padding-right: 20px; }
|
|
627
|
+
.vfy-status, .vfy-sum, .vfy-cards { padding-left: 20px; padding-right: 20px; }
|
|
628
|
+
#detail { display: none; }
|
|
629
|
+
.stats { display: none; }
|
|
630
|
+
.brand h1, .brand .sub { max-width: 280px; }
|
|
631
|
+
}
|
|
632
|
+
@media (max-width: 760px) {
|
|
633
|
+
#topbar { flex: 0 0 auto; min-height: 58px; flex-wrap: wrap; padding: 10px 14px; gap: 8px; }
|
|
634
|
+
.brand { flex: 1 1 100%; }
|
|
635
|
+
.brand h1, .brand .sub { max-width: 100%; }
|
|
636
|
+
.stats { display: none; }
|
|
637
|
+
.actions { width: 100%; overflow-x: auto; padding-bottom: 2px; }
|
|
638
|
+
.actions .btn { flex: 0 0 auto; }
|
|
639
|
+
#main { grid-template-columns: minmax(0, 1fr); }
|
|
640
|
+
#sidebar { display: none; }
|
|
641
|
+
#detail { display: none; }
|
|
642
|
+
#canvas.view-panorama { padding: 22px 16px 68px; gap: 38px; }
|
|
643
|
+
.station { width: 236px; flex-basis: 236px; }
|
|
644
|
+
#view-tabs { padding: 0 10px; }
|
|
645
|
+
#pan-hint { right: 10px; bottom: 10px; }
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
</style>
|
|
649
|
+
</head>
|
|
650
|
+
<body>
|
|
651
|
+
<div id="app" class="booting">
|
|
652
|
+
<header id="topbar">
|
|
653
|
+
<div class="brand">
|
|
654
|
+
<h1 id="title">开发全景图</h1>
|
|
655
|
+
<div class="sub" id="subtitle">加载中…</div>
|
|
656
|
+
</div>
|
|
657
|
+
<div class="stats" id="stats"></div>
|
|
658
|
+
<div class="actions">
|
|
659
|
+
<button class="btn" id="btn-follow" title="按步骤自动播放">▶ 跟着流程走</button>
|
|
660
|
+
<button class="btn" id="btn-clear" title="清除步骤/文件联动">清除联动 <kbd>Esc</kbd></button>
|
|
661
|
+
<button class="btn" id="btn-copy" title="复制当前文档路径">复制路径</button>
|
|
662
|
+
<span id="live"><i></i><span>实时</span></span>
|
|
663
|
+
</div>
|
|
664
|
+
</header>
|
|
665
|
+
|
|
666
|
+
<div id="parse-warn" class="warnbox" hidden></div>
|
|
667
|
+
|
|
668
|
+
<main id="main">
|
|
669
|
+
<aside id="sidebar" aria-label="步骤与文件">
|
|
670
|
+
<div class="side-head">
|
|
671
|
+
<div class="eyebrow">PROJECT</div>
|
|
672
|
+
<div class="proj"><span id="proj-name">{{projectName}}</span></div>
|
|
673
|
+
</div>
|
|
674
|
+
<div class="side-tools">
|
|
675
|
+
<input id="search" placeholder="搜索文件 / 步骤…" autocomplete="off" aria-label="搜索文件或步骤">
|
|
676
|
+
<button class="mini" id="expand-all" title="展开全部">展开</button>
|
|
677
|
+
<button class="mini" id="collapse-all" title="折叠全部">折叠</button>
|
|
678
|
+
</div>
|
|
679
|
+
<div class="side-sec"><b>01 / 步骤</b><span id="step-count"></span></div>
|
|
680
|
+
<div id="step-nav"></div>
|
|
681
|
+
<div class="side-sec"><b>02 / 文件</b><span id="file-count"></span></div>
|
|
682
|
+
<div id="file-tree"></div>
|
|
683
|
+
<div class="side-foot" id="side-foot"></div>
|
|
684
|
+
</aside>
|
|
685
|
+
|
|
686
|
+
<section id="canvas-wrap" aria-label="开发全景画布">
|
|
687
|
+
<div id="view-tabs">
|
|
688
|
+
<button class="tab on" data-view="panorama">全景图</button>
|
|
689
|
+
<button class="tab" data-view="sequence">时序图</button>
|
|
690
|
+
<button class="tab" data-view="flow">流程图</button>
|
|
691
|
+
<button class="tab" data-view="reqs" style="display:none">需求</button>
|
|
692
|
+
<button class="tab" data-view="verify" style="display:none">验证</button>
|
|
693
|
+
<button class="tab" data-view="dataflow" style="display:none">数据流</button>
|
|
694
|
+
<button id="layout-toggle" class="layout-btn" title="切换全景布局(网格 / 时间线)">布局:网格</button>
|
|
695
|
+
<button id="btn-focus" class="layout-btn" title="隐藏左右栏,专注全景">专注全景</button>
|
|
696
|
+
<span class="spacer"></span>
|
|
697
|
+
<div class="zoombar">
|
|
698
|
+
<button class="wide" id="fit" title="缩放适配">适配</button>
|
|
699
|
+
<button id="zoom-out" title="缩小">−</button>
|
|
700
|
+
<span id="zoom-label">100%</span>
|
|
701
|
+
<button id="zoom-in" title="放大">+</button>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
<div id="canvas-scroll"><div id="canvas"></div></div>
|
|
705
|
+
<span id="pan-hint">← 左右滚动查看全部 →</span>
|
|
706
|
+
</section>
|
|
707
|
+
|
|
708
|
+
<aside id="detail" aria-label="变更说明">
|
|
709
|
+
<div class="detail-head">
|
|
710
|
+
<div class="dh"><h2>变更说明</h2><span id="detail-sub"></span><button id="detail-back" class="dback" type="button" hidden>← 总览</button></div>
|
|
711
|
+
<div id="filter-tabs"></div>
|
|
712
|
+
</div>
|
|
713
|
+
<div id="detail-body"></div>
|
|
714
|
+
</aside>
|
|
715
|
+
</main>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<script>
|
|
719
|
+
const EMBEDDED = __TOUR_DATA__;
|
|
720
|
+
const DOC_FILE = __TOUR_FILE__;
|
|
721
|
+
const MODE = __TOUR_MODE__;
|
|
722
|
+
</script>
|
|
723
|
+
<script>
|
|
724
|
+
(() => {
|
|
725
|
+
'use strict';
|
|
726
|
+
const $ = (s, el = document) => el.querySelector(s);
|
|
727
|
+
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
|
|
728
|
+
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
729
|
+
// 行内 markdown(需求叙事用):反引号 → code,**x** → b;先转义再替换
|
|
730
|
+
const mdInline = (s) => esc(s)
|
|
731
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
732
|
+
.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
|
|
733
|
+
const fmt = (n) => (n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k' : String(n));
|
|
734
|
+
|
|
735
|
+
let DATA = null;
|
|
736
|
+
let FILE_BY_PATH = new Map();
|
|
737
|
+
let liveTimer = null;
|
|
738
|
+
let sse = null;
|
|
739
|
+
let followTimer = null;
|
|
740
|
+
let lastRefresh = 0;
|
|
741
|
+
let lastSig = '';
|
|
742
|
+
let lastDetailKey = '';
|
|
743
|
+
|
|
744
|
+
const state = {
|
|
745
|
+
view: 'panorama',
|
|
746
|
+
layout: (() => { try { return localStorage.getItem('devTourLayout') || 'grid'; } catch { return 'grid'; } })(),
|
|
747
|
+
step: null,
|
|
748
|
+
file: null,
|
|
749
|
+
dataflowObj: null,
|
|
750
|
+
dfPrevStep: null,
|
|
751
|
+
dfPrevFile: null,
|
|
752
|
+
filter: 'all',
|
|
753
|
+
zoom: 1,
|
|
754
|
+
query: '',
|
|
755
|
+
collapsed: new Set(),
|
|
756
|
+
follow: false,
|
|
757
|
+
warnOpen: null, // 解析告警明细开合:null=按条数自动(>3 折叠),用户点击后保持
|
|
758
|
+
warnGroups: new Set(), // 告警分组展开态(按根因 kind)
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const FILTERS = [
|
|
762
|
+
['all', '全部'], ['new', '新增'], ['modified', '修改'], ['deleted', '删除'],
|
|
763
|
+
['planned', '计划'], ['test', '测试'],
|
|
764
|
+
];
|
|
765
|
+
const LAYER_LABEL = { frontend: '前端', api: '接口', app: '应用', domain: '领域', infra: '基础设施', db: '数据库', test: '测试' };
|
|
766
|
+
|
|
767
|
+
function hashState() {
|
|
768
|
+
const p = new URLSearchParams();
|
|
769
|
+
if (state.step) p.set('step', state.step);
|
|
770
|
+
if (state.file) p.set('file', state.file);
|
|
771
|
+
if (state.view !== 'panorama') p.set('view', state.view);
|
|
772
|
+
return p.toString();
|
|
773
|
+
}
|
|
774
|
+
function syncHash() {
|
|
775
|
+
if (MODE === 'snapshot') return;
|
|
776
|
+
const h = hashState();
|
|
777
|
+
history.replaceState(null, '', h ? '#' + h : location.pathname + location.search);
|
|
778
|
+
}
|
|
779
|
+
function readHash() {
|
|
780
|
+
const p = new URLSearchParams(location.hash.slice(1));
|
|
781
|
+
if (p.get('step')) state.step = p.get('step');
|
|
782
|
+
if (p.get('file')) state.file = p.get('file');
|
|
783
|
+
if (p.get('view')) state.view = p.get('view');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function fileOf(path) { return FILE_BY_PATH.get(path) || null; }
|
|
787
|
+
function stepOf(id) { return DATA ? DATA.steps.find((s) => s.id === id) : null; }
|
|
788
|
+
// 附录步(改动面/未分步)不是任务:不编号、不参与“N 步”计数,避免和真实任务抢序位。
|
|
789
|
+
const APPENDIX_ID = 'A';
|
|
790
|
+
const isAppendix = (s) => !!s && s.id === APPENDIX_ID;
|
|
791
|
+
const stepNo = (s) => isAppendix(s) ? '附' : String(s && s.index != null ? s.index : '').padStart(2, '0');
|
|
792
|
+
const stepName = (s) => isAppendix(s) ? '附录' : `步骤 ${stepNo(s)}`;
|
|
793
|
+
const stepTitle = (s) => isAppendix(s) ? `附:改动清单(${(s.files || []).length} 个文件)` : (s ? s.title : '');
|
|
794
|
+
const stepAria = (s) => isAppendix(s) ? `附录:改动清单(${(s.files || []).length} 个文件)` : `${stepName(s)}:${s ? s.title : ''}`;
|
|
795
|
+
const SOURCE_LABEL = { tasks: '任务拆解', steps: '步骤', headings: '步骤', blocks: '改动块', layers: '层归集', git: '实际改动', doc: '文档' };
|
|
796
|
+
const sourceLabel = (s) => SOURCE_LABEL[s && s.source] || '步骤';
|
|
797
|
+
// 验证步骤(单测 / UI 实测 / 静态门)天然没有代码文件;用于区分“解析失败”和“本来就没有文件”。
|
|
798
|
+
const VERIFY_STEP_RE = /(单测|集成测试|测试收口|冒烟|UI 实测|静态门|门禁|验证步骤)/;
|
|
799
|
+
const isVerificationStep = (s) => !!s && VERIFY_STEP_RE.test(s.title || '');
|
|
800
|
+
// 文件筛选口径单源:计数与列表都走这里,避免“状态 planned + planned 标记”重复计数。
|
|
801
|
+
const matchesFilter = (f, filter) => filter === 'all' ? true
|
|
802
|
+
: filter === 'test' ? !!f.test
|
|
803
|
+
: filter === 'planned' ? !!(f.planned && !f.actual)
|
|
804
|
+
: f.status === filter;
|
|
805
|
+
function stepsFor(filter = state.filter) {
|
|
806
|
+
return DATA.steps.filter((s) => {
|
|
807
|
+
if (filter === 'all') return true;
|
|
808
|
+
return s.files.some((p) => {
|
|
809
|
+
const f = fileOf(p);
|
|
810
|
+
return !!f && matchesFilter(f, filter);
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function filesForStep(id) { return (stepOf(id)?.files || []).map(fileOf).filter(Boolean); }
|
|
815
|
+
|
|
816
|
+
// —— 数据流视图:模型缺 dataflow 字段(旧缓存)时整视图降级隐藏 ——
|
|
817
|
+
function dfObjects() { return (DATA && DATA.dataflow && Array.isArray(DATA.dataflow.objects)) ? DATA.dataflow.objects : []; }
|
|
818
|
+
function hasDataflow() { return dfObjects().length > 0; }
|
|
819
|
+
function dataflowObj() {
|
|
820
|
+
if (!state.dataflowObj) return null;
|
|
821
|
+
return dfObjects().find((o) => o.name === state.dataflowObj) || null;
|
|
822
|
+
}
|
|
823
|
+
function relatedSteps() {
|
|
824
|
+
const o = dataflowObj();
|
|
825
|
+
return o ? new Set(o.steps || []) : null;
|
|
826
|
+
}
|
|
827
|
+
// 退出对象联动:restore 时还原进入联动前的步骤/文件现场,防止选中期间点过的步骤/文件残留
|
|
828
|
+
function clearDataflowSel(restore) {
|
|
829
|
+
if (restore) { state.step = state.dfPrevStep; state.file = state.dfPrevFile; }
|
|
830
|
+
state.dfPrevStep = null; state.dfPrevFile = null;
|
|
831
|
+
state.dataflowObj = null;
|
|
832
|
+
}
|
|
833
|
+
function stepObjects(stepId) {
|
|
834
|
+
if (!hasDataflow()) return [];
|
|
835
|
+
return dfObjects().filter((o) => (o.steps || []).includes(stepId));
|
|
836
|
+
}
|
|
837
|
+
function syncDataflowTab() {
|
|
838
|
+
const tab = $('.tab[data-view="dataflow"]');
|
|
839
|
+
if (tab) tab.style.display = hasDataflow() ? '' : 'none';
|
|
840
|
+
if (state.view === 'dataflow' && !hasDataflow()) {
|
|
841
|
+
state.view = 'panorama';
|
|
842
|
+
$$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
// —— 需求视图:本档目标 + 上游 intent/spec/incident 叙事(模型 doc.upstream)——
|
|
846
|
+
function hasReqs() {
|
|
847
|
+
return !!(DATA.doc.purpose || (DATA.doc.upstream && DATA.doc.upstream.length));
|
|
848
|
+
}
|
|
849
|
+
function syncReqsTab() {
|
|
850
|
+
const tab = $('.tab[data-view="reqs"]');
|
|
851
|
+
if (tab) tab.style.display = hasReqs() ? '' : 'none';
|
|
852
|
+
if (state.view === 'reqs' && !hasReqs()) {
|
|
853
|
+
state.view = 'panorama';
|
|
854
|
+
$$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function reqCard(up, self) {
|
|
858
|
+
const points = (up.points || []).map((p) => `<li>${mdInline(p)}</li>`).join('');
|
|
859
|
+
const purposeLabel = self ? '目标' : (up.type === 'SPEC' ? '范围' : up.type === 'INCIDENT' ? '现象' : '目标');
|
|
860
|
+
return `<div class="req-card${self ? ' self' : ''}">` +
|
|
861
|
+
`<div class="req-eyebrow"><span class="req-badge" data-type="${esc(up.type)}">${esc(up.type)}</span>` +
|
|
862
|
+
`<span>${self ? '本档目标' : '上游 ' + up.type.toLowerCase() + ' · 为什么改'}</span>` +
|
|
863
|
+
(self ? '' : `<span style="margin-left:auto">${esc(up.path)}</span>`) + `</div>` +
|
|
864
|
+
`<h3>${esc(up.title)}</h3>` +
|
|
865
|
+
(up.purpose ? `<p class="req-why"><b>${purposeLabel}</b>${mdInline(up.purpose)}</p>` : '') +
|
|
866
|
+
(points ? `<ul class="req-points">${points}</ul>` : '') +
|
|
867
|
+
`</div>`;
|
|
868
|
+
}
|
|
869
|
+
function reqsHtml() {
|
|
870
|
+
const d = DATA.doc;
|
|
871
|
+
const ups = d.upstream || [];
|
|
872
|
+
if (!d.purpose && !ups.length) return '<div class="none">文档未登记目标或关联需求</div>';
|
|
873
|
+
return '<div class="reqs-wrap">' +
|
|
874
|
+
(d.purpose ? reqCard({ type: d.type, title: d.title, purpose: d.purpose, points: [] }, true) : '') +
|
|
875
|
+
ups.map((u) => reqCard(u, false)).join('') +
|
|
876
|
+
(ups.length ? `<p class="req-path" style="margin:4px 2px">实现拆解:${ups.length} 篇上游需求 → ${DATA.steps.length} 步 · ${DATA.stats.files} 个文件(见全景图 / 流程图)</p>` : '') +
|
|
877
|
+
`</div>`;
|
|
878
|
+
}
|
|
879
|
+
// —— 验证视图:计划清单(plan 验证计划) vs 实际凭证(verify 台账/仓库凭证)——
|
|
880
|
+
function hasEvidence() {
|
|
881
|
+
return !!(DATA.evidence && DATA.evidence.rows && DATA.evidence.rows.length);
|
|
882
|
+
}
|
|
883
|
+
function latestEvidence() {
|
|
884
|
+
return hasEvidence() ? DATA.evidence.rows[0] : null;
|
|
885
|
+
}
|
|
886
|
+
function syncEvidenceTab() {
|
|
887
|
+
const tab = $('.tab[data-view="verify"]');
|
|
888
|
+
if (tab) tab.style.display = hasEvidence() ? '' : 'none';
|
|
889
|
+
if (state.view === 'verify' && !hasEvidence()) {
|
|
890
|
+
state.view = 'panorama';
|
|
891
|
+
$$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function fmtMs(ms) {
|
|
895
|
+
const n = Number(ms);
|
|
896
|
+
if (!isFinite(n)) return '—';
|
|
897
|
+
return n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 's' : Math.round(n) + 'ms';
|
|
898
|
+
}
|
|
899
|
+
function fmtTs(ts) {
|
|
900
|
+
const d = new Date(ts);
|
|
901
|
+
if (isNaN(d)) return ts || '—';
|
|
902
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
903
|
+
return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
904
|
+
}
|
|
905
|
+
function verifyHtml() {
|
|
906
|
+
const ev = DATA.evidence || { rows: [] };
|
|
907
|
+
const rows = ev.rows || [];
|
|
908
|
+
if (!rows.length) return '<div class="none">时间窗内无验证凭证</div>';
|
|
909
|
+
const GATE_LABEL = {
|
|
910
|
+
'backend-build': '后端构建', 'backend-test': '后端测试', 'frontend-lint': '前端 lint',
|
|
911
|
+
'frontend-unit-test': '前端单测', 'frontend-build': '前端构建',
|
|
912
|
+
'code-architecture': '架构约束', 'code-audit-fields': '审计字段',
|
|
913
|
+
'code-delete-guard': '删除守卫', 'code-notify-guard': '通知守卫',
|
|
914
|
+
'check-scope': 'scope 对账', 'verify': 'verify 全量',
|
|
915
|
+
'wiki-consistency': 'wiki 一致性', 'check-loop': '闭环校验', 'all-gates': '全量门',
|
|
916
|
+
'scripts-syntax': '脚本语法', 'workflows-check': '工作流校验', 'local-gate-config': '本地门配置',
|
|
917
|
+
'rule-budget': '规则预算', 'engine-artifacts': '引擎产物', 'ledger-doc-presence': '台账文档',
|
|
918
|
+
'lane-surface-ci': '泳道面 CI', 'secrets-scan': '密钥扫描', 'ledger-invariant': '台账不变量',
|
|
919
|
+
};
|
|
920
|
+
const gateLabel = (id) => GATE_LABEL[id] || id;
|
|
921
|
+
const latest = rows[0];
|
|
922
|
+
const latestSkips = [...new Set([...(latest.skipped || []), ...(latest.envSkips || [])])];
|
|
923
|
+
const latestOk = latest.exitCode === 0;
|
|
924
|
+
const latestGreen = rows.find((r) => r.exitCode === 0);
|
|
925
|
+
const failed = rows.filter((r) => r.exitCode !== 0).length;
|
|
926
|
+
const status = `<div class="vfy-status${latestOk ? '' : ' bad'}"><i></i><div class="vfy-status-main">` +
|
|
927
|
+
`<span>${latestOk ? '最近一条 通过' + (latestSkips.length ? `(跳过 ${latestSkips.length})` : '') : `⚠ 最近一条 exit=${latest.exitCode}`}(${fmtTs(latest.ts)} · ${esc(latest.suite || '?')})</span>` +
|
|
928
|
+
(latestGreen
|
|
929
|
+
? `<span class="vfy-green">最近绿行 ${fmtTs(latestGreen.ts)}${latestGreen.gates != null ? ` · ${latestGreen.gates} 门执行` : ''}${(latestGreen.skipped || []).length ? ` · 跳过 ${latestGreen.skipped.length}` : ''}</span>`
|
|
930
|
+
: '<span>窗内暂无绿行</span>') +
|
|
931
|
+
`</div></div>`;
|
|
932
|
+
|
|
933
|
+
// 套件级门禁卡:同 suite 取最新一条;失败优先、其余按时间倒序。
|
|
934
|
+
const bySuite = new Map();
|
|
935
|
+
for (const r of rows) {
|
|
936
|
+
const k = r.suite || '—';
|
|
937
|
+
if (!bySuite.has(k)) bySuite.set(k, r);
|
|
938
|
+
}
|
|
939
|
+
const suites = [...bySuite.values()].sort((a, b) => {
|
|
940
|
+
const fa = a.exitCode === 0 ? 1 : 0;
|
|
941
|
+
const fb = b.exitCode === 0 ? 1 : 0;
|
|
942
|
+
return fa - fb || Date.parse(b.ts) - Date.parse(a.ts);
|
|
943
|
+
});
|
|
944
|
+
const cards = suites.map((r) => {
|
|
945
|
+
const skips = [...new Set([...(r.skipped || []), ...(r.envSkips || [])])];
|
|
946
|
+
const ok = r.exitCode === 0;
|
|
947
|
+
const stateText = ok ? '通过' : (r.exitCode == null ? '未知' : `失败 exit=${r.exitCode}`);
|
|
948
|
+
const note = r.note || (r.exitCode !== 0 ? `台账未记录失败原因(仅 exit=${r.exitCode})` : '');
|
|
949
|
+
const copy = r.logPath ? `<button class="vfy-copy" type="button" data-copy="${esc(r.logPath)}">复制日志路径</button>` : '';
|
|
950
|
+
const tail = r.logTail ? `<details class="vfy-log"><summary>失败日志尾部</summary><pre>${esc(r.logTail)}</pre></details>` : '';
|
|
951
|
+
const gateEntries = (r.durations && r.exitCode === 0) ? Object.entries(r.durations) : [];
|
|
952
|
+
const gateGrid = gateEntries.length
|
|
953
|
+
? `<details class="vfy-gates"><summary>门禁明细 · 执行 ${gateEntries.length} 门(耗时)</summary><div class="vfy-gate-grid">` +
|
|
954
|
+
gateEntries.map(([id, ms]) => {
|
|
955
|
+
const skip = skips.includes(id);
|
|
956
|
+
return `<span class="${skip ? 'skip' : 'pass'}" title="${esc(id)} · ${skip ? '跳过(本地缺环境)' : '通过'}${ms != null ? ' · ' + fmtMs(ms) : ''}">${esc(gateLabel(id))}<b>${skip ? '跳过' : fmtMs(ms)}</b></span>`;
|
|
957
|
+
}).join('') + `</div></details>`
|
|
958
|
+
: '';
|
|
959
|
+
return `<article class="vfy-card ${ok ? 'ok' : 'bad'}">` +
|
|
960
|
+
`<div class="vfy-card-h"><b>${esc(r.suite || '—')}</b><span class="vfy-state">${esc(stateText)}</span><time>${fmtTs(r.ts)}</time></div>` +
|
|
961
|
+
`<div class="vfy-card-meta">${r.gates != null ? `${r.gates} 门执行` : '门数未记'}${skips.length ? ` · 跳过 ${skips.length}` : ''}${r.headShort ? ` · head ${esc(r.headShort)}` : ''}${r.source ? ` · ${r.source === 'ledger' ? '台账' : '凭证'}` : ''}</div>` +
|
|
962
|
+
(note ? `<div class="vfy-card-note">${esc(note)}${copy}</div>` : '') +
|
|
963
|
+
(skips.length ? `<div class="vfy-skips">${skips.map((x) => `<span title="${esc(x)}">${esc(gateLabel(x))}</span>`).join('')}</div>` : '') +
|
|
964
|
+
gateGrid +
|
|
965
|
+
tail +
|
|
966
|
+
`</article>`;
|
|
967
|
+
}).join('');
|
|
968
|
+
|
|
969
|
+
const gates = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
|
|
970
|
+
const planCol = `<div class="vfy-plan"><h4>计划清单(${gates.length})· 未逐条绑定</h4><ul>` +
|
|
971
|
+
gates.map((g) => `<li>${mdInline(g)}</li>`).join('') + `</ul></div>`;
|
|
972
|
+
|
|
973
|
+
const body = rows.map((r) => {
|
|
974
|
+
const skips = [...new Set([...(r.skipped || []), ...(r.envSkips || [])])];
|
|
975
|
+
const ok = r.exitCode === 0;
|
|
976
|
+
const stateText = ok ? '通过' : (r.exitCode == null ? '未知' : `失败 exit=${r.exitCode}`);
|
|
977
|
+
const note = r.note || (r.exitCode !== 0 ? `台账未记录失败原因(仅 exit=${r.exitCode})` : '');
|
|
978
|
+
const copy = r.logPath ? `<button class="vfy-copy" type="button" data-copy="${esc(r.logPath)}">复制日志路径</button>` : '';
|
|
979
|
+
const tail = r.logTail ? `<details class="vfy-log"><summary>日志尾部</summary><pre>${esc(r.logTail)}</pre></details>` : '';
|
|
980
|
+
return `<details class="vfy-row">` +
|
|
981
|
+
`<summary><span>${fmtTs(r.ts)}</span><span class="vfy-rs">${esc(r.suite || '—')}</span>` +
|
|
982
|
+
`<span class="vfy-re ${ok ? 'ok' : 'bad'}">${esc(stateText)}</span>` +
|
|
983
|
+
`<span class="vfy-rg">${r.gates != null ? r.gates + ' 门执行' : '—'}</span>` +
|
|
984
|
+
`<span class="vfy-rk">${skips.length ? '跳过 ' + skips.length : ''}</span>` +
|
|
985
|
+
`<span class="vfy-rh">${r.headShort ? 'head ' + esc(r.headShort) : ''}</span>` +
|
|
986
|
+
`<span>${r.source === 'ledger' ? '台账' : '凭证'}</span></summary>` +
|
|
987
|
+
`<div class="vfy-row-body">` +
|
|
988
|
+
(note ? `<div>${esc(note)}${copy}</div>` : '') +
|
|
989
|
+
(skips.length ? `<div class="vfy-skips">${skips.map((x) => `<span title="${esc(x)}">${esc(gateLabel(x))}</span>`).join('')}</div>` : '') +
|
|
990
|
+
(r.attribution ? `<div>归因:${esc(r.attribution)}</div>` : '') +
|
|
991
|
+
tail +
|
|
992
|
+
`</div></details>`;
|
|
993
|
+
}).join('');
|
|
994
|
+
|
|
995
|
+
const summary = `<div class="vfy-sum">窗内 ${ev.totalInWindow ?? rows.length} 条凭证 · ${suites.length} 个套件 · 失败 ${failed} 条 · 计划门禁 ${gates.length} 项(未逐条绑定)</div>`;
|
|
996
|
+
const evCol = `<div class="vfy-ev"><h4>凭证明细(窗内 ${ev.totalInWindow ?? rows.length} 条,示最近 ${rows.length})</h4>${body}</div>`;
|
|
997
|
+
return status + summary +
|
|
998
|
+
`<div class="vfy-cards">${cards}</div>` +
|
|
999
|
+
`<div class="vfy-cols">${planCol}${evCol}</div>` +
|
|
1000
|
+
`<p class="vfy-note">窗起点 ${esc(ev.windowStart || '—')}(文档日期);关联语义为「任务时间窗内的验证凭证」,非逐条绑定到本档。台账 = 本机 .agents/verifications.jsonl;失败原因取台账 note,并附本地日志尾部。</p>`;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function renderHeader() {
|
|
1004
|
+
const d = DATA.doc;
|
|
1005
|
+
document.title = d.title + ' · 开发全景图';
|
|
1006
|
+
$('#title').textContent = d.title;
|
|
1007
|
+
const bits = [d.type, d.level, d.status, d.module, d.date].filter(Boolean);
|
|
1008
|
+
$('#subtitle').textContent = (bits.join(' · ') || d.path) + (d.purpose ? ' — ' + d.purpose : '');
|
|
1009
|
+
$('#proj-name').textContent = DATA.rootLabel || '{{projectName}}';
|
|
1010
|
+
const s = DATA.stats;
|
|
1011
|
+
const realStepCount = DATA.steps.filter((x) => !isAppendix(x)).length;
|
|
1012
|
+
const hasAppendix = DATA.steps.some(isAppendix);
|
|
1013
|
+
$('#stats').innerHTML =
|
|
1014
|
+
`<span class="stat" title="真实任务步骤数;附录(改动面/未分步)不计入"><b>${realStepCount}</b>步${hasAppendix ? ' + 附' : ''}</span>` +
|
|
1015
|
+
`<span class="stat" title="代码面文件(backend / frontend / tests 等);文档/工具脚本单列,不含在代码计数内"><b>${s.files}</b>代码${s.docs ? ` · <b>${s.docs}</b>文档` : ''}</span>` +
|
|
1016
|
+
`<span class="stat"><b>${s.new}</b>新增</span>` +
|
|
1017
|
+
`<span class="stat"><b>${s.modified}</b>修改</span>` +
|
|
1018
|
+
(s.deleted ? `<span class="stat"><b>${s.deleted}</b>删除</span>` : '') +
|
|
1019
|
+
(s.planned ? `<span class="stat" title="代码面中尚未实际改动的文件(不含文档/工具)"><b>${s.planned}</b>计划中</span>` : '') +
|
|
1020
|
+
`<span class="stat add"><b>+${fmt(s.additions)}</b>行</span>` +
|
|
1021
|
+
`<span class="stat del"><b>-${fmt(s.deletions)}</b>行</span>`;
|
|
1022
|
+
const live = $('#live');
|
|
1023
|
+
if (MODE === 'live') {
|
|
1024
|
+
live.classList.remove('off');
|
|
1025
|
+
live.querySelector('span').textContent = '实时';
|
|
1026
|
+
} else {
|
|
1027
|
+
live.classList.add('off');
|
|
1028
|
+
live.querySelector('span').textContent = '快照';
|
|
1029
|
+
}
|
|
1030
|
+
$('#side-foot').innerHTML =
|
|
1031
|
+
`<div><b>基线</b> ${esc(DATA.base.ref)}${DATA.base.sha ? ' · ' + esc(DATA.base.sha) : ''}</div>` +
|
|
1032
|
+
`<div><b>生成</b> ${esc(new Date(DATA.generatedAt).toLocaleString())}</div>` +
|
|
1033
|
+
(DATA.note ? `<div style="color:var(--warn)">${esc(DATA.note)}</div>` : '') +
|
|
1034
|
+
`<div style="margin-top:3px">● 新增 ● 修改 ● 计划</div>`;
|
|
1035
|
+
// 解析健康度警告条(2026-11-09):层兜底或直认率 <50% 时提示文档标题失约——
|
|
1036
|
+
// 与 gen-dev-tour 的「解析命中」行同源(parseHealth,实时/快照同一模型自动生效)。
|
|
1037
|
+
// 2026-11-10 起同条列出 parseHealth.issues 逐条定位(第 N 行),文案与 gen 打印等价。
|
|
1038
|
+
// 2026-11-11 版面修订:标题行常驻(计数/行号预览),明细默认折叠 + 限高滚动 + 自动换行。
|
|
1039
|
+
const phw = DATA.parseHealth;
|
|
1040
|
+
const warnEl = $('#parse-warn');
|
|
1041
|
+
const ISSUE_LABEL = {
|
|
1042
|
+
'step-no-files': '步骤无对齐文件块(改动方案缺该步的文件条目)',
|
|
1043
|
+
'block-unmatched': '文件块锚点未对齐任何步骤(编号/标题与任务拆解不一致)',
|
|
1044
|
+
'dep-unresolved': '依赖引用未解析到步骤(编号与步骤序号/Tn 不一致)',
|
|
1045
|
+
'layers-fallback': '全文无结构化步骤标题,按架构层兜底分组',
|
|
1046
|
+
};
|
|
1047
|
+
const ISSUE_SHORT = {
|
|
1048
|
+
'step-no-files': '步骤缺文件',
|
|
1049
|
+
'block-unmatched': '文件块未对齐',
|
|
1050
|
+
'dep-unresolved': '依赖未解析',
|
|
1051
|
+
'layers-fallback': '层兜底',
|
|
1052
|
+
};
|
|
1053
|
+
const issues = (phw && phw.issues) || [];
|
|
1054
|
+
const lowHit = !!(phw && (phw.layersFallback || (phw.total && phw.hitRate < 0.5)));
|
|
1055
|
+
if (!lowHit && !issues.length) {
|
|
1056
|
+
warnEl.hidden = true;
|
|
1057
|
+
} else {
|
|
1058
|
+
// 同根因归并(2026-11-11):10 条 block-unmatched 是“改动面没按任务归组”一条信息,
|
|
1059
|
+
// 不再逐行铺满;按 kind 分组,组内明细默认收起。
|
|
1060
|
+
const GROUP_ORDER = ['block-unmatched', 'step-no-files', 'dep-unresolved', 'layers-fallback'];
|
|
1061
|
+
const GROUP_HINT = {
|
|
1062
|
+
'block-unmatched': '改动面/文件块没有按任务归组,解析器无法归属到步骤',
|
|
1063
|
+
'step-no-files': '实现步骤缺「- 文件:」条目;纯验证步骤可忽略',
|
|
1064
|
+
'dep-unresolved': '执行顺序 / 依赖引用与任务编号对不上',
|
|
1065
|
+
'layers-fallback': '全文没有结构化步骤标题,只能按架构层兜底',
|
|
1066
|
+
};
|
|
1067
|
+
const groups = [...new Set([...GROUP_ORDER, ...issues.map((it) => it.kind)])]
|
|
1068
|
+
.map((kind) => ({ kind, items: issues.filter((it) => it.kind === kind) }))
|
|
1069
|
+
.filter((g) => g.items.length);
|
|
1070
|
+
const open = state.warnOpen == null ? issues.length <= 3 : state.warnOpen;
|
|
1071
|
+
const groupHtml = groups.map((g) => {
|
|
1072
|
+
const isOpen = state.warnGroups.has(g.kind);
|
|
1073
|
+
const verifyCount = g.kind === 'step-no-files'
|
|
1074
|
+
? g.items.filter((it) => isVerificationStep({ title: it.title })).length : 0;
|
|
1075
|
+
const fixedCount = g.items.length - verifyCount;
|
|
1076
|
+
const rows = g.items.map((it) => {
|
|
1077
|
+
const verify = g.kind === 'step-no-files' && isVerificationStep({ title: it.title });
|
|
1078
|
+
return `<div class="warn-row" title="${esc(ISSUE_LABEL[it.kind] || it.kind)}">` +
|
|
1079
|
+
`<span class="warn-line">第 ${esc(it.line)} 行</span>` +
|
|
1080
|
+
`<span class="warn-text">${mdInline(it.title)}</span>` +
|
|
1081
|
+
(verify ? '<span class="warn-kind verify">验证步骤</span>' : '') +
|
|
1082
|
+
`</div>`;
|
|
1083
|
+
}).join('');
|
|
1084
|
+
const countText = verifyCount ? `${fixedCount} 项待修 / ${verifyCount} 项验证` : `${g.items.length} 项`;
|
|
1085
|
+
return `<div class="warn-group">` +
|
|
1086
|
+
`<button class="warn-group-head" type="button" data-wg="${esc(g.kind)}" aria-expanded="${isOpen}">` +
|
|
1087
|
+
`<span class="wg-chev">${isOpen ? '▾' : '▸'}</span>` +
|
|
1088
|
+
`<span class="wg-name">${esc(ISSUE_SHORT[g.kind] || g.kind)}</span>` +
|
|
1089
|
+
`<span class="wg-hint">${esc(GROUP_HINT[g.kind] || '')}</span>` +
|
|
1090
|
+
`<span class="wg-n">${esc(countText)}</span>` +
|
|
1091
|
+
`</button>` +
|
|
1092
|
+
`<div class="warn-group-body"${isOpen ? '' : ' hidden'}>${rows}</div>` +
|
|
1093
|
+
`</div>`;
|
|
1094
|
+
}).join('');
|
|
1095
|
+
const headLines = issues.slice(0, 6).map((it) => it.line);
|
|
1096
|
+
const preview = headLines.length
|
|
1097
|
+
? `第 ${headLines.join('、')} 行${issues.length > headLines.length ? '…' : ''}`
|
|
1098
|
+
: '';
|
|
1099
|
+
warnEl.hidden = false;
|
|
1100
|
+
warnEl.innerHTML =
|
|
1101
|
+
`<div class="warn-head">` +
|
|
1102
|
+
`<span class="warn-icon">⚠</span>` +
|
|
1103
|
+
`<div class="warn-head-main">` +
|
|
1104
|
+
`<b>解析告警</b>` +
|
|
1105
|
+
(issues.length ? `<span class="warn-count">${issues.length} 项 / ${groups.length} 类</span>` : '') +
|
|
1106
|
+
`<span class="warn-sub">${lowHit ? `文档步骤未被完整识别(直认 ${phw.direct}/${phw.total})` : '文档与图示对齐存疑'}${preview ? ' · ' + preview : ''}</span>` +
|
|
1107
|
+
`</div>` +
|
|
1108
|
+
`<button class="warn-toggle" type="button" aria-expanded="${open}">${open ? '收起' : '展开'}</button>` +
|
|
1109
|
+
`</div>` +
|
|
1110
|
+
`<div class="warn-body"${open ? '' : ' hidden'}>` +
|
|
1111
|
+
(lowHit ? `<div class="warn-note">⚠ 文档步骤未被完整识别(直认 ${phw.direct}/${phw.total}),图示含兜底归集近似——规范 ## 任务拆解 / ## 改动方案 标题后重新生成更准</div>` : '') +
|
|
1112
|
+
groupHtml +
|
|
1113
|
+
`</div>`;
|
|
1114
|
+
const toggle = $('.warn-toggle', warnEl);
|
|
1115
|
+
const body = $('.warn-body', warnEl);
|
|
1116
|
+
toggle.onclick = () => {
|
|
1117
|
+
const next = body.hidden;
|
|
1118
|
+
body.hidden = !next;
|
|
1119
|
+
toggle.textContent = next ? '收起' : '展开';
|
|
1120
|
+
toggle.setAttribute('aria-expanded', String(next));
|
|
1121
|
+
state.warnOpen = next;
|
|
1122
|
+
};
|
|
1123
|
+
$$('.warn-group-head', warnEl).forEach((btn) => {
|
|
1124
|
+
btn.onclick = () => {
|
|
1125
|
+
const gb = btn.nextElementSibling;
|
|
1126
|
+
const next = gb.hidden;
|
|
1127
|
+
gb.hidden = !next;
|
|
1128
|
+
btn.setAttribute('aria-expanded', String(next));
|
|
1129
|
+
const chev = $('.wg-chev', btn);
|
|
1130
|
+
if (chev) chev.textContent = next ? '▾' : '▸';
|
|
1131
|
+
if (next) state.warnGroups.add(btn.dataset.wg); else state.warnGroups.delete(btn.dataset.wg);
|
|
1132
|
+
};
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
syncDataflowTab();
|
|
1136
|
+
syncReqsTab();
|
|
1137
|
+
syncEvidenceTab();
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function renderStepNav() {
|
|
1141
|
+
const q = state.query.trim().toLowerCase();
|
|
1142
|
+
const related = relatedSteps();
|
|
1143
|
+
const list = DATA.steps.filter((s) => !q || s.title.toLowerCase().includes(q) || s.id.toLowerCase().includes(q) || s.files.some((p) => p.toLowerCase().includes(q)));
|
|
1144
|
+
const realTotal = DATA.steps.filter((s) => !isAppendix(s)).length;
|
|
1145
|
+
const realList = list.filter((s) => !isAppendix(s)).length;
|
|
1146
|
+
const hasAppendix = list.some(isAppendix);
|
|
1147
|
+
$('#step-count').textContent = `${realList} / ${realTotal}${hasAppendix ? ' + 附' : ''}`;
|
|
1148
|
+
$('#step-nav').innerHTML = list.map((s) => {
|
|
1149
|
+
const n = s.files.length;
|
|
1150
|
+
const on = state.step === s.id || (related && related.has(s.id));
|
|
1151
|
+
return `<button class="nav-step ${on ? 'on' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" title="${esc(stepTitle(s) + (s.summary ? ' — ' + s.summary : ''))}">` +
|
|
1152
|
+
`<span class="n">${stepNo(s)}</span><span class="t">${esc(isAppendix(s) ? '附:改动清单' : s.title)}</span><span class="c">${n}</span></button>`;
|
|
1153
|
+
}).join('') || '<div class="none">无匹配步骤</div>';
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function buildTree(files) {
|
|
1157
|
+
const root = { name: '', path: '', dirs: new Map(), files: [] };
|
|
1158
|
+
for (const f of files) {
|
|
1159
|
+
const parts = f.path.split('/');
|
|
1160
|
+
let node = root;
|
|
1161
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1162
|
+
const name = parts[i];
|
|
1163
|
+
if (!node.dirs.has(name)) node.dirs.set(name, { name, path: parts.slice(0, i + 1).join('/'), dirs: new Map(), files: [] });
|
|
1164
|
+
node = node.dirs.get(name);
|
|
1165
|
+
}
|
|
1166
|
+
node.files.push(f);
|
|
1167
|
+
}
|
|
1168
|
+
return root;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function statusDot(f) {
|
|
1172
|
+
if (f.status === 'new') return 'var(--ok)';
|
|
1173
|
+
if (f.status === 'modified') return 'var(--accent)';
|
|
1174
|
+
if (f.status === 'deleted') return 'var(--bad)';
|
|
1175
|
+
if (f.status === 'renamed') return 'var(--muted)';
|
|
1176
|
+
return 'var(--warn)';
|
|
1177
|
+
}
|
|
1178
|
+
function badgeOf(f) {
|
|
1179
|
+
let b = '';
|
|
1180
|
+
if (f.test) b += `<span class="badge test">测试</span>`;
|
|
1181
|
+
else if (f.badge) b += `<span class="badge ${esc(f.status)}">${esc(f.badge)}</span>`;
|
|
1182
|
+
if (f.pathGuess) b += `<span class="badge planned" title="路径按同名多候选推断,建议在文档中写全路径">猜?</span>`;
|
|
1183
|
+
return b;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function renderTree() {
|
|
1187
|
+
const q = state.query.trim().toLowerCase();
|
|
1188
|
+
const dfo = dataflowObj();
|
|
1189
|
+
let files = DATA.files.filter((f) => !q || f.path.toLowerCase().includes(q) || (stepOf(state.step)?.title || '').toLowerCase().includes(q));
|
|
1190
|
+
if (dfo) {
|
|
1191
|
+
const set = new Set(dfo.files || []);
|
|
1192
|
+
files = files.filter((f) => set.has(f.path));
|
|
1193
|
+
} else if (state.step) {
|
|
1194
|
+
const set = new Set(stepOf(state.step)?.files || []);
|
|
1195
|
+
files = files.filter((f) => set.has(f.path));
|
|
1196
|
+
}
|
|
1197
|
+
$('#file-count').textContent = files.length + ' / ' + DATA.files.length;
|
|
1198
|
+
const tree = buildTree(files);
|
|
1199
|
+
const dirs = [...tree.dirs.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1200
|
+
const rootFiles = tree.files.slice().sort((a, b) => a.path.localeCompare(b.path));
|
|
1201
|
+
const html = [];
|
|
1202
|
+
for (const d of dirs) html.push(renderDir(d, 0));
|
|
1203
|
+
for (const f of rootFiles) html.push(renderFile(f, 0));
|
|
1204
|
+
$('#file-tree').innerHTML = html.join('') || '<div class="none">无文件</div>';
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function renderDir(node, depth) {
|
|
1208
|
+
const collapsed = state.collapsed.has(node.path);
|
|
1209
|
+
const dirs = [...node.dirs.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1210
|
+
const files = node.files.slice().sort((a, b) => a.path.localeCompare(b.path));
|
|
1211
|
+
const n = countFiles(node);
|
|
1212
|
+
return `<div class="tree-dir ${collapsed ? 'closed' : ''}" data-dir="${esc(node.path)}">` +
|
|
1213
|
+
`<div class="tree-row" style="padding-left:${4 + depth * 12}px" role="button" tabindex="0" aria-label="目录 ${esc(node.path)}"><span class="chev">▼</span><span class="folder">▸</span><span>${esc(node.name)}</span><span style="margin-left:auto;font:9px var(--mono);color:var(--faint)">${n}</span></div>` +
|
|
1214
|
+
`<div class="tree-children">` +
|
|
1215
|
+
dirs.map((d) => renderDir(d, depth + 1)).join('') +
|
|
1216
|
+
files.map((f) => renderFile(f, depth + 1)).join('') +
|
|
1217
|
+
`</div></div>`;
|
|
1218
|
+
}
|
|
1219
|
+
function countFiles(node) {
|
|
1220
|
+
let n = node.files.length;
|
|
1221
|
+
for (const d of node.dirs.values()) n += countFiles(d);
|
|
1222
|
+
return n;
|
|
1223
|
+
}
|
|
1224
|
+
function renderFile(f, depth) {
|
|
1225
|
+
return `<div class="tree-file ${state.file === f.path ? 'on' : ''}" data-file="${esc(f.path)}" style="padding-left:${20 + depth * 12}px" title="${esc(f.path)}" role="button" tabindex="0" aria-label="文件 ${esc(f.path)}">` +
|
|
1226
|
+
`<span class="dot" style="background:${statusDot(f)}"></span><span class="nm">${esc(f.path.split('/').pop())}</span>` +
|
|
1227
|
+
`${f.test ? '<span class="bd" style="color:var(--intent)">测</span>' : (f.badge ? `<span class="bd" style="color:${statusDot(f)}">${esc(f.badge)}</span>` : '')}` +
|
|
1228
|
+
`${f.pathGuess ? '<span class="bd" style="color:var(--warn)" title="路径按同名多候选推断,建议在文档中写全路径">猜?</span>' : ''}</div>`;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function renderCanvas() {
|
|
1232
|
+
const c = $('#canvas');
|
|
1233
|
+
const grid = state.view === 'panorama' && state.layout === 'grid';
|
|
1234
|
+
c.className = 'view-' + state.view + (grid ? ' layout-grid' : '');
|
|
1235
|
+
if (state.view === 'sequence') c.innerHTML = sequenceHtml();
|
|
1236
|
+
else if (state.view === 'flow') { c.innerHTML = flowHtml(); requestAnimationFrame(drawFlowDeps); }
|
|
1237
|
+
else if (state.view === 'reqs') c.innerHTML = reqsHtml();
|
|
1238
|
+
else if (state.view === 'verify') c.innerHTML = verifyHtml();
|
|
1239
|
+
else if (state.view === 'dataflow') c.innerHTML = dataflowHtml();
|
|
1240
|
+
else c.innerHTML = panoramaHtml();
|
|
1241
|
+
c.style.transform = state.zoom === 1 ? '' : `scale(${state.zoom})`;
|
|
1242
|
+
$('#zoom-label').textContent = Math.round(state.zoom * 100) + '%';
|
|
1243
|
+
const lt = $('#layout-toggle');
|
|
1244
|
+
if (lt) {
|
|
1245
|
+
lt.style.display = state.view === 'panorama' ? 'inline-block' : 'none';
|
|
1246
|
+
lt.textContent = '布局:' + (state.layout === 'grid' ? '网格' : '时间线');
|
|
1247
|
+
}
|
|
1248
|
+
const fb = $('#btn-focus');
|
|
1249
|
+
if (fb) {
|
|
1250
|
+
fb.style.display = state.view === 'panorama' ? 'inline-block' : 'none';
|
|
1251
|
+
fb.textContent = document.body.classList.contains('focus') ? '退出专注' : '专注全景';
|
|
1252
|
+
}
|
|
1253
|
+
const hint = $('#pan-hint');
|
|
1254
|
+
if (hint) {
|
|
1255
|
+
const sc = $('#canvas-scroll');
|
|
1256
|
+
requestAnimationFrame(() => {
|
|
1257
|
+
hint.style.display = (state.view === 'panorama' && state.layout === 'timeline' && state.zoom === 1 && c.scrollWidth > sc.clientWidth + 4) ? 'block' : 'none';
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function panoramaHtml() {
|
|
1263
|
+
const list = stepsFor(state.filter);
|
|
1264
|
+
if (!list.length) return '<div class="none">当前筛选无步骤</div>';
|
|
1265
|
+
const related = relatedSteps();
|
|
1266
|
+
const hasDf = hasDataflow();
|
|
1267
|
+
const stations = list.map((s, i) => {
|
|
1268
|
+
const files = filesForStep(s.id);
|
|
1269
|
+
const shown = files.slice(0, 5);
|
|
1270
|
+
const objs = stepObjects(s.id);
|
|
1271
|
+
const objBadges = hasDf && objs.length
|
|
1272
|
+
? `<div class="st-objs">` +
|
|
1273
|
+
objs.slice(0, 3).map((o) => `<span class="kbadge ${esc(o.kind || '')} ${state.dataflowObj === o.name ? 'on' : ''}" data-df="${esc(o.name)}" title="数据对象 ${esc(o.name)}(点击联动相关步骤与文件)">${esc(o.name)}</span>`).join('') +
|
|
1274
|
+
(objs.length > 3 ? `<span class="kbadge">+${objs.length - 3}</span>` : '') +
|
|
1275
|
+
`</div>`
|
|
1276
|
+
: '';
|
|
1277
|
+
const app = isAppendix(s);
|
|
1278
|
+
const hasAppendix = DATA.steps.some(isAppendix);
|
|
1279
|
+
const emptyLabel = isVerificationStep(s)
|
|
1280
|
+
? '验证步骤 · 无代码文件'
|
|
1281
|
+
: `该步未列文件${hasAppendix ? ' · <span class="lnk" data-step="A">查看附录改动清单</span>' : ''}`;
|
|
1282
|
+
return `<div class="station ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${s.status === 'warn' ? 'warn' : ''} ${app ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}">` +
|
|
1283
|
+
`<div class="st-top"><span class="st-no">${stepNo(s)}</span><div class="st-headtext"><span class="st-label">${app ? '附录 · 改动清单' : stepName(s)}</span><span class="st-title" title="${esc(s.title)}">${esc(stepTitle(s))}</span></div></div>` +
|
|
1284
|
+
`<div class="st-card">` +
|
|
1285
|
+
(s.summary ? `<div class="st-summary" title="${esc(s.summary)}">${s.source === 'tasks' ? '<span class="st-k">判据</span>' : ''}${esc(s.summary)}</div>` : '') +
|
|
1286
|
+
`<div class="st-files">` +
|
|
1287
|
+
(shown.length ? shown.map((f) => {
|
|
1288
|
+
const base = f.path.split('/').pop();
|
|
1289
|
+
const dir = f.path.split('/').slice(-2, -1)[0] || '';
|
|
1290
|
+
return `<div class="st-file" data-file="${esc(f.path)}" title="${esc(f.path)}"><span class="dot" style="background:${statusDot(f)}"></span><span class="nm"><span class="bn">${esc(base)}</span>${dir ? `<span class="dir">${esc(dir)}</span>` : ''}</span><span style="margin-left:auto;color:${statusDot(f)};font-size:10px">${esc(f.badge || '')}</span></div>`;
|
|
1291
|
+
}).join('') : `<div class="st-empty">${emptyLabel}</div>`) +
|
|
1292
|
+
(files.length > shown.length ? `<div class="st-more" title="在右侧查看全部 ${files.length} 个文件">查看全部 ${files.length} 个文件 →</div>` : '') +
|
|
1293
|
+
`</div>` + objBadges + `</div></div>`;
|
|
1294
|
+
}).join('');
|
|
1295
|
+
return state.layout === 'grid' ? `<div class="station-grid">${stations}</div>` : stations;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function sequenceHtml() {
|
|
1299
|
+
const seq = DATA.sequence;
|
|
1300
|
+
if (!seq || !seq.participants.length || !seq.messages.length) return '<div class="seq-empty">暂无可推导的时序(步骤未关联到架构层文件)</div>';
|
|
1301
|
+
const parts = seq.participants;
|
|
1302
|
+
const idx = new Map(parts.map((p, i) => [p.id, i]));
|
|
1303
|
+
const n = parts.length;
|
|
1304
|
+
const width = 1000;
|
|
1305
|
+
const head = parts.map((p) => `<div class="seq-part" data-layer="${esc(p.id)}" title="架构层:${esc(LAYER_LABEL[p.id] || p.id)}"><span class="box">${esc(p.label)}</span></div>`).join('');
|
|
1306
|
+
const lines = parts.map(() => '<i></i>').join('');
|
|
1307
|
+
const msgs = seq.messages.map((m) => {
|
|
1308
|
+
const a = idx.get(m.from);
|
|
1309
|
+
const b = idx.get(m.to);
|
|
1310
|
+
if (a == null || b == null) return '';
|
|
1311
|
+
const x1 = (a + 0.5) / n * width;
|
|
1312
|
+
const x2 = (b + 0.5) / n * width;
|
|
1313
|
+
const st = stepOf(m.stepId);
|
|
1314
|
+
const color = m.dashed ? '#7d6224' : '#bc4327';
|
|
1315
|
+
let svg = '';
|
|
1316
|
+
if (a === b) {
|
|
1317
|
+
const x = x1;
|
|
1318
|
+
svg = `<path d="M ${x - 7} 30 L ${x - 7} 19 L ${x + 7} 19 L ${x + 7} 27" fill="none" stroke="${color}" stroke-width="1" ${m.dashed ? 'stroke-dasharray="4 3"' : ''}/><polygon points="${x + 3},27 ${x + 11},27 ${x + 7},33" fill="${color}"/>`;
|
|
1319
|
+
} else {
|
|
1320
|
+
const dir = x2 > x1 ? 1 : -1;
|
|
1321
|
+
svg = `<line x1="${x1}" y1="31" x2="${x2 - dir * 6}" y2="31" stroke="${color}" stroke-width="1" ${m.dashed ? 'stroke-dasharray="4 3"' : ''}/>` +
|
|
1322
|
+
`<polygon points="${x2 - dir * 12},27 ${x2 - dir * 4},31 ${x2 - dir * 12},35" fill="${color}"/>`;
|
|
1323
|
+
}
|
|
1324
|
+
const mx = a === b ? x1 : (x1 + x2) / 2;
|
|
1325
|
+
return `<div class="seq-msg ${state.step === m.stepId ? 'on' : ''} ${isAppendix(st) ? 'appendix' : ''}" data-step="${esc(m.stepId)}" title="${esc(st ? stepTitle(st) : m.stepId)}">` +
|
|
1326
|
+
`<svg viewBox="0 0 ${width} 50" preserveAspectRatio="none">${svg}</svg>` +
|
|
1327
|
+
`<span class="lbl" style="left:${(mx / width * 100).toFixed(2)}%"><b>${esc(isAppendix(st) ? '附' : m.stepId)}</b>${esc(m.label)}</span></div>`;
|
|
1328
|
+
}).join('');
|
|
1329
|
+
return `<div class="seq-wrap" style="--n:${n}">` +
|
|
1330
|
+
`<div class="seq-titlebar"><b>调用时序</b><span>${parts.length} 个参与层 · ${seq.messages.length} 条步骤消息 · 点击消息联动</span></div>` +
|
|
1331
|
+
`<div class="seq-head">${head}</div>` +
|
|
1332
|
+
`<div class="seq-body"><div class="seq-lifelines">${lines}</div>${msgs}</div></div>`;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// —— 依赖旁路弧:模型 deps.edges(声明依赖,已传递归约),主链相邻边不重复画 ——
|
|
1336
|
+
function depEdges(list) {
|
|
1337
|
+
const raw = (DATA && DATA.deps && Array.isArray(DATA.deps.edges)) ? DATA.deps.edges : [];
|
|
1338
|
+
if (!raw.length) return [];
|
|
1339
|
+
const pos = new Map(list.map((s, i) => [s.id, i]));
|
|
1340
|
+
return raw.filter((e) => pos.has(e.from) && pos.has(e.to))
|
|
1341
|
+
.map((e) => ({ from: e.from, to: e.to, a: pos.get(e.from), b: pos.get(e.to) }))
|
|
1342
|
+
.filter((e) => e.a !== e.b && Math.abs(e.b - e.a) > 1);
|
|
1343
|
+
}
|
|
1344
|
+
function drawFlowDeps() {
|
|
1345
|
+
const svg = $('#canvas .flow-deps');
|
|
1346
|
+
if (!svg) return;
|
|
1347
|
+
const wrap = $('#canvas .flow-wrap');
|
|
1348
|
+
const nodes = [...wrap.querySelectorAll('.flow-node')];
|
|
1349
|
+
const edges = depEdges(stepsFor(state.filter));
|
|
1350
|
+
if (!nodes.length || !edges.length) return;
|
|
1351
|
+
// offsetTop/offsetHeight 取布局坐标(canvas 可能整体 scale,getBoundingClientRect 会失真)
|
|
1352
|
+
const ys = nodes.map((el) => ({ top: el.offsetTop, bot: el.offsetTop + el.offsetHeight, mid: el.offsetTop + el.offsetHeight / 2 }));
|
|
1353
|
+
const H = wrap.offsetHeight;
|
|
1354
|
+
svg.setAttribute('width', '40');
|
|
1355
|
+
svg.setAttribute('height', String(H));
|
|
1356
|
+
svg.setAttribute('viewBox', `0 0 40 ${H}`);
|
|
1357
|
+
// 车道分配:跨度大的占外道,减少弧间相交
|
|
1358
|
+
const laneOf = new Map();
|
|
1359
|
+
edges.slice().sort((p, q) => Math.abs(q.b - q.a) - Math.abs(p.b - p.a))
|
|
1360
|
+
.forEach((e, i) => laneOf.set(e.from + '>' + e.to, i));
|
|
1361
|
+
const X0 = 38; // 节点左缘(wrap padding-left 46 - 8)
|
|
1362
|
+
const parts = [];
|
|
1363
|
+
for (const e of edges) {
|
|
1364
|
+
const lane = laneOf.get(e.from + '>' + e.to) % 6;
|
|
1365
|
+
const x = 30 - lane * 4.2;
|
|
1366
|
+
const back = e.b < e.a;
|
|
1367
|
+
const y1 = back ? ys[e.a].top + 4 : ys[e.a].bot - 5;
|
|
1368
|
+
const y2 = back ? ys[e.b].bot - 5 : ys[e.b].top + 4;
|
|
1369
|
+
const k = e.from + '>' + e.to;
|
|
1370
|
+
parts.push(`<path class="dep${back ? ' back' : ''}" data-dep="${esc(k)}" d="M ${X0} ${y1} C ${x} ${y1}, ${x} ${y2}, ${X0} ${y2}"/>`);
|
|
1371
|
+
parts.push(`<path class="dep-head" data-dep="${esc(k)}" d="M ${X0 + 1} ${y2} l -6.5 -3.2 v 6.4 z" transform="rotate(${back ? 180 : 0} ${X0} ${y2})"/>`);
|
|
1372
|
+
}
|
|
1373
|
+
svg.innerHTML = parts.join('');
|
|
1374
|
+
}
|
|
1375
|
+
function flowDepHover(stepId, on) {
|
|
1376
|
+
const svg = $('#canvas .flow-deps');
|
|
1377
|
+
if (!svg) return;
|
|
1378
|
+
const wrap = $('#canvas .flow-wrap');
|
|
1379
|
+
for (const e of depEdges(stepsFor(state.filter))) {
|
|
1380
|
+
if (e.from !== stepId && e.to !== stepId) continue;
|
|
1381
|
+
const k = e.from + '>' + e.to;
|
|
1382
|
+
svg.querySelectorAll(`[data-dep="${cssEsc(k)}"]`).forEach((el) => el.classList.toggle('on', on));
|
|
1383
|
+
const other = e.from === stepId ? e.to : e.from;
|
|
1384
|
+
const el = wrap.querySelector(`.flow-node[data-step="${cssEsc(other)}"]`);
|
|
1385
|
+
if (el) el.classList.toggle('rel', on);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function flowHtml() {
|
|
1390
|
+
const list = stepsFor(state.filter);
|
|
1391
|
+
if (!list.length) return '<div class="none">当前筛选无步骤</div>';
|
|
1392
|
+
const related = relatedSteps();
|
|
1393
|
+
const deps = depEdges(list);
|
|
1394
|
+
const gates = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
|
|
1395
|
+
const stepHtml = list.map((s, i) => {
|
|
1396
|
+
const files = filesForStep(s.id);
|
|
1397
|
+
const chips = files.slice(0, 4).map((f) => `<span title="${esc(f.path)}">${esc(f.path.split('/').pop())}</span>`).join('') +
|
|
1398
|
+
(files.length > 4 ? `<span>+${files.length - 4}</span>` : '');
|
|
1399
|
+
return `<div class="flow-node ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}">` +
|
|
1400
|
+
`<div class="fn-head"><span class="fn-no">${stepNo(s)}</span><span class="fn-title">${esc(stepTitle(s))}</span><span style="margin-left:auto;font:9px var(--mono);color:var(--faint)">${files.length} 文件</span></div>` +
|
|
1401
|
+
(s.summary ? `<div class="fn-sum">${esc(s.summary)}</div>` : '') +
|
|
1402
|
+
(chips ? `<div class="fn-files">${chips}</div>` : '') +
|
|
1403
|
+
`</div>` + (i < list.length - 1 ? '<div class="flow-arrow"><i></i><b></b></div>' : '');
|
|
1404
|
+
}).join('');
|
|
1405
|
+
return `<div class="flow-wrap${deps.length ? ' has-deps' : ''}">` +
|
|
1406
|
+
(deps.length ? '<svg class="flow-deps" aria-hidden="true"></svg>' : '') +
|
|
1407
|
+
`<div class="flow-pill">◉ 计划确认 · ${esc(DATA.doc.type)} ${esc(DATA.doc.status || 'approved')}</div>` +
|
|
1408
|
+
`<div class="flow-arrow"><i></i><b></b></div>${stepHtml}` +
|
|
1409
|
+
`<div class="flow-arrow"><i></i><b></b></div>` +
|
|
1410
|
+
`<div class="flow-gate"><h4>验证门(全部通过才算完成)</h4><ul>${gates.map((g) => `<li>${esc(g)}</li>`).join('')}</ul>` +
|
|
1411
|
+
(() => { const le = latestEvidence(); return le ? `<div class="vfy-gate-ev${le.exitCode === 0 ? '' : ' bad'}">窗内最近凭证:<b>${le.exitCode === 0 ? '✓ ' + fmtTs(le.ts) + ' · ' + (le.gates ?? '?') + ' 门全过' : '✗ ' + fmtTs(le.ts) + ' · exit ' + le.exitCode}</b></div>` : ''; })() +
|
|
1412
|
+
`<div class="flow-fail">失败 → 回修当前步骤,不越过门禁</div></div>` +
|
|
1413
|
+
`<div class="flow-arrow"><i></i><b></b></div>` +
|
|
1414
|
+
`<div class="flow-pill end">✓ 关单 / 提交</div>` +
|
|
1415
|
+
`</div>`;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
function dataflowHtml() {
|
|
1419
|
+
const objs = dfObjects();
|
|
1420
|
+
if (!objs.length) return '<div class="none">未识别到跨文件数据对象(需 diff 或文档正文含类型定义)</div>';
|
|
1421
|
+
const flows = (DATA.dataflow && Array.isArray(DATA.dataflow.flows)) ? DATA.dataflow.flows : [];
|
|
1422
|
+
const cross = objs.reduce((n, o) => n + Math.max(0, ((o.layers || []).length - 1)), 0);
|
|
1423
|
+
const kindOf = (name) => (objs.find((o) => o.name === name) || {}).kind || '';
|
|
1424
|
+
const objRow = (o, i) => {
|
|
1425
|
+
const layers = o.layers || [];
|
|
1426
|
+
const slots = layers.map((ly) => {
|
|
1427
|
+
const names = (o.files || []).filter((p) => (fileOf(p)?.layer) === ly).map((p) => p.split('/').pop());
|
|
1428
|
+
const show = names.slice(0, 2).join('、') + (names.length > 2 ? ` +${names.length - 2}` : '');
|
|
1429
|
+
return `<div class="df-slot"><span class="lyr">${esc(LAYER_LABEL[ly] || ly)}</span><span class="fs" title="${esc(names.join(' · '))}">${esc(show || '—')}</span></div>`;
|
|
1430
|
+
});
|
|
1431
|
+
const lane = slots.map((s, j) => (j ? '<span class="df-link"></span>' : '') + s).join('');
|
|
1432
|
+
return `<div class="df-obj ${state.dataflowObj === o.name ? 'on' : ''}" data-df="${esc(o.name)}" style="--i:${i}" role="button" tabindex="0" aria-label="数据对象 ${esc(o.name)}" title="点击联动相关步骤与文件(再次点击取消)">` +
|
|
1433
|
+
`<span class="df-name">${esc(o.name)}</span>` +
|
|
1434
|
+
`<span class="kbadge ${esc(o.kind || '')}">${esc(o.kind || '?')}</span>` +
|
|
1435
|
+
`<span class="df-nfiles">${(o.files || []).length} 文件</span>` +
|
|
1436
|
+
(lane ? `<span class="df-lane">${lane}</span>` : '') +
|
|
1437
|
+
`</div>`;
|
|
1438
|
+
};
|
|
1439
|
+
const stepLbl = (id) => {
|
|
1440
|
+
const s = stepOf(id);
|
|
1441
|
+
return s
|
|
1442
|
+
? `<button class="df-step" data-step="${esc(id)}" title="联动步骤 ${esc(s.title)}"><b>${esc(id)}</b>${esc(s.title)}</button>`
|
|
1443
|
+
: `<span>${esc(id)}</span>`;
|
|
1444
|
+
};
|
|
1445
|
+
return `<div class="df-wrap">` +
|
|
1446
|
+
`<div class="df-sum">${objs.length} 个数据对象 · 跨层 ${cross} 处 · ${flows.length} 条步骤间传递</div>` +
|
|
1447
|
+
objs.map(objRow).join('') +
|
|
1448
|
+
(flows.length ? `<div class="df-sec">步骤间传递</div>` + flows.map((f) =>
|
|
1449
|
+
`<div class="df-flow">${stepLbl(f.from)}<span class="arr">→</span>` +
|
|
1450
|
+
`<span class="kbadge ${esc(kindOf(f.object))}" title="数据对象 ${esc(f.object)}">${esc(f.object)}</span>` +
|
|
1451
|
+
`<span class="arr">→</span>${stepLbl(f.to)}</div>`).join('') : '') +
|
|
1452
|
+
`</div>`;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// 右侧总览:只放“结论/缺口/门禁”,步骤全文在选中后下钻;不再复述中间 9 张卡片。
|
|
1456
|
+
function overviewHtml() {
|
|
1457
|
+
const s = DATA.stats || {};
|
|
1458
|
+
const realSteps = DATA.steps.filter((x) => !isAppendix(x)).length;
|
|
1459
|
+
const app = DATA.steps.find(isAppendix);
|
|
1460
|
+
const appFiles = app ? filesForStep(app.id) : [];
|
|
1461
|
+
const issues = (DATA.parseHealth && DATA.parseHealth.issues) || [];
|
|
1462
|
+
const issueKinds = new Set(issues.map((it) => it.kind)).size;
|
|
1463
|
+
const ph = DATA.parseHealth || {};
|
|
1464
|
+
const lowHit = !!(ph.layersFallback || (ph.total && ph.hitRate < 0.5));
|
|
1465
|
+
const ev = latestEvidence();
|
|
1466
|
+
const evOk = ev && ev.exitCode === 0;
|
|
1467
|
+
const evText = ev
|
|
1468
|
+
? (evOk
|
|
1469
|
+
? `最近绿行 ${fmtTs(ev.ts)} · ${ev.gates ?? '?'} 门全过`
|
|
1470
|
+
: `最近 exit=${ev.exitCode}(${fmtTs(ev.ts)} · ${ev.suite || '?'})未达全绿`)
|
|
1471
|
+
: '时间窗内无验证凭证';
|
|
1472
|
+
const skipped = ev && (ev.skipped || []).length;
|
|
1473
|
+
const gateList = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
|
|
1474
|
+
const gateText = gateList.map((g) => String(g).replace(/`/g, '').split(/[::((]/)[0].trim()).join(' · ');
|
|
1475
|
+
const gateFull = gateList.map((g) => String(g).replace(/`/g, '')).join(' | ');
|
|
1476
|
+
const risks = {};
|
|
1477
|
+
for (const st of DATA.steps) {
|
|
1478
|
+
for (const d of (st.detail || [])) {
|
|
1479
|
+
const m = String(d).match(/^-\s*风险[::]\s*(高|中|低)/);
|
|
1480
|
+
if (m) risks[m[1]] = (risks[m[1]] || 0) + 1;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const riskText = ['高', '中', '低'].filter((k) => risks[k]).map((k) => `${k} ${risks[k]}`).join(' · ') || '未登记';
|
|
1484
|
+
const hotSteps = DATA.steps.filter((st) => (st.detail || []).some((d) => /^-\s*风险[::]\s*(高|中)/.test(String(d)))).map((st) => st.id);
|
|
1485
|
+
const unresolved = (DATA.deps && DATA.deps.unresolved) || 0;
|
|
1486
|
+
const ups = DATA.doc.upstream || [];
|
|
1487
|
+
const reqSource = ups.length
|
|
1488
|
+
? ups.map((u) => `<div class="ov-sub"><b>${esc(u.type)}</b> ${esc(u.title)}${u.purpose ? `<br><span class="ov-req-purpose" title="${esc(u.purpose)}">${mdInline(u.purpose)}</span>` : ''}</div>`).join('')
|
|
1489
|
+
: (DATA.doc.purpose
|
|
1490
|
+
? `<div class="ov-sub">${mdInline(DATA.doc.purpose)}</div>`
|
|
1491
|
+
: '<div class="ov-sub">未登记上游需求;在 plan 写「对应入口 / 对应 spec」后自动出现。</div>');
|
|
1492
|
+
return `<div class="ov">` +
|
|
1493
|
+
`<section class="ov-sec">` +
|
|
1494
|
+
`<div class="ov-eyebrow">需求来源</div>` +
|
|
1495
|
+
reqSource +
|
|
1496
|
+
(hasReqs() ? `<button class="ov-link" type="button" data-view="reqs">查看需求与验收 →</button>` : '') +
|
|
1497
|
+
`</section>` +
|
|
1498
|
+
`<section class="ov-sec">` +
|
|
1499
|
+
`<div class="ov-eyebrow">范围</div>` +
|
|
1500
|
+
`<div class="ov-kpis"><b>${realSteps}</b> 步 · <b>${s.files || 0}</b> 代码${s.docs ? ` · <b>${s.docs}</b> 文档` : ''}</div>` +
|
|
1501
|
+
`<div class="ov-sub">修改 ${s.modified || 0} · 计划中 ${s.planned || 0} · <span class="ov-add">+${fmt(s.additions || 0)}</span> / <span class="ov-del">−${fmt(s.deletions || 0)}</span></div>` +
|
|
1502
|
+
`</section>` +
|
|
1503
|
+
`<section class="ov-sec">` +
|
|
1504
|
+
`<div class="ov-eyebrow">未归属文件${app ? ` <b>${appFiles.length}</b>` : ''}</div>` +
|
|
1505
|
+
(app
|
|
1506
|
+
? `<button class="ov-link" type="button" data-step="${esc(app.id)}">附:改动清单(${appFiles.length} 个文件)→</button>` +
|
|
1507
|
+
`<div class="ov-sub">任务拆解未覆盖;补 <code>- 文件:</code> 后会自动归位。</div>`
|
|
1508
|
+
: `<div class="ov-sub">无未归属文件。</div>`) +
|
|
1509
|
+
`</section>` +
|
|
1510
|
+
`<section class="ov-sec">` +
|
|
1511
|
+
`<div class="ov-eyebrow">验证 / 门禁</div>` +
|
|
1512
|
+
`<div class="ov-status ${ev ? (evOk ? 'ok' : 'bad') : ''}"><i></i><span>${esc(evText)}</span></div>` +
|
|
1513
|
+
(skipped ? `<div class="ov-sub">本地跳过 ${skipped} 项(缺环境),未计入失败。</div>` : '') +
|
|
1514
|
+
`<div class="ov-sub" title="${esc(gateFull)}">计划门禁 ${gateList.length} 项:${esc(gateText)}</div>` +
|
|
1515
|
+
(hasEvidence() ? `<button class="ov-link" type="button" data-view="verify">查看验证 →</button>` : "") +
|
|
1516
|
+
`</section>` +
|
|
1517
|
+
`<section class="ov-sec">` +
|
|
1518
|
+
`<div class="ov-eyebrow">解析与风险</div>` +
|
|
1519
|
+
`<div class="ov-sub">${issues.length ? `解析告警 ${issues.length} 项 / ${issueKinds} 类` : '解析正常'}${lowHit ? ` · 直认 ${ph.direct ?? '?'}/${ph.total ?? '?'}` : ''} · 依赖未解析 ${unresolved} 处</div>` +
|
|
1520
|
+
`<div class="ov-sub">风险:${riskText}${hotSteps.length ? `(关注 ${hotSteps.join('/')})` : ''}</div>` +
|
|
1521
|
+
`</section>` +
|
|
1522
|
+
`</div>`;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// 选中步骤的「需求来源」:只放 INTENT/SPEC 的一句话,详细要点去需求 tab。
|
|
1526
|
+
function stepReqHtml() {
|
|
1527
|
+
const ups = DATA.doc.upstream || [];
|
|
1528
|
+
if (!ups.length && !DATA.doc.purpose) {
|
|
1529
|
+
return `<div class="sc-sec"><div class="sc-sec-h">需求来源</div><div class="sc-sec-empty">未登记上游需求;在 plan 写「对应入口 / 对应 spec」。</div></div>`;
|
|
1530
|
+
}
|
|
1531
|
+
const items = ups.length
|
|
1532
|
+
? ups.map((u) => `<div class="sc-req"><span class="req-badge" data-type="${esc(u.type)}">${esc(u.type)}</span><span class="sc-req-title">${esc(u.title)}</span>${u.purpose ? `<div class="sc-req-why" title="${esc(u.purpose)}">${mdInline(u.purpose)}</div>` : ''}</div>`).join('')
|
|
1533
|
+
: `<div class="sc-req-why">${mdInline(DATA.doc.purpose)}</div>`;
|
|
1534
|
+
return `<div class="sc-sec"><div class="sc-sec-h">需求来源</div>${items}${hasReqs() ? '<button class="ov-link" type="button" data-view="reqs">查看需求与验收 →</button>' : ''}</div>`;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function renderDetail() {
|
|
1538
|
+
const curStep = state.step ? stepOf(state.step) : null;
|
|
1539
|
+
const detailKey = curStep ? curStep.id : '__overview__';
|
|
1540
|
+
const resetScroll = detailKey !== lastDetailKey;
|
|
1541
|
+
lastDetailKey = detailKey;
|
|
1542
|
+
const back = $('#detail-back');
|
|
1543
|
+
if (back) back.hidden = !curStep;
|
|
1544
|
+
const heading = $('.detail-head h2');
|
|
1545
|
+
if (heading) heading.textContent = curStep ? '变更说明' : '总览';
|
|
1546
|
+
const filterTabs = $('#filter-tabs');
|
|
1547
|
+
if (!curStep) {
|
|
1548
|
+
if (filterTabs) { filterTabs.style.display = 'none'; filterTabs.innerHTML = ''; }
|
|
1549
|
+
$('#detail-sub').textContent = '范围 · 验证 · 未归属';
|
|
1550
|
+
$('#detail-body').innerHTML = overviewHtml();
|
|
1551
|
+
if (resetScroll) $('#detail-body').scrollTop = 0;
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
if (filterTabs) filterTabs.style.display = '';
|
|
1555
|
+
|
|
1556
|
+
// 计数与筛选共用 matchesFilter,修掉 planned 状态与 planned 标记重复计数(12 → 6)。
|
|
1557
|
+
const counts = {};
|
|
1558
|
+
for (const [k] of FILTERS) counts[k] = DATA.files.filter((f) => matchesFilter(f, k)).length;
|
|
1559
|
+
filterTabs.innerHTML = FILTERS.map(([k, label]) =>
|
|
1560
|
+
`<button data-filter="${k}" title="${k === 'planned' ? '计划中:文档已登记、工作树尚未实际改动(含文档/工具)' : '文件筛选(含文档/工具)'}" class="${state.filter === k ? 'on' : ''}">${label}<span class="n">${counts[k]}</span></button>`).join('');
|
|
1561
|
+
$('#detail-sub').textContent = isAppendix(curStep) ? '附录 · 未分步文件' : `第 ${curStep.index} 步`;
|
|
1562
|
+
const list = [curStep];
|
|
1563
|
+
|
|
1564
|
+
const dfo = dataflowObj();
|
|
1565
|
+
const related = relatedSteps();
|
|
1566
|
+
const html = list.map((s, i) => {
|
|
1567
|
+
let files = filesForStep(s.id);
|
|
1568
|
+
const totalFiles = files.length;
|
|
1569
|
+
if (state.filter !== 'all') files = files.filter((f) => matchesFilter(f, state.filter));
|
|
1570
|
+
const rawDetails = (s.detail || []).map((d) => d.replace(/^[-*]\s*/, '').trim()).filter(Boolean);
|
|
1571
|
+
const acceptance = (s.source === 'tasks' && s.summary) ? [s.summary] : rawDetails
|
|
1572
|
+
.filter((d) => /^判据[::]/.test(d))
|
|
1573
|
+
.map((d) => d.replace(/^判据[::]\s*/, ''))
|
|
1574
|
+
.filter(Boolean);
|
|
1575
|
+
const proof = rawDetails
|
|
1576
|
+
.filter((d) => /^留痕[::]/.test(d))
|
|
1577
|
+
.map((d) => d.replace(/^留痕[::]\s*/, ''))
|
|
1578
|
+
.filter(Boolean);
|
|
1579
|
+
const details = rawDetails
|
|
1580
|
+
.filter((d) => !/^(判据|留痕)[::]/.test(d) && !/^(留痕|风险|依赖|判据)[::]?$/.test(d))
|
|
1581
|
+
.slice(0, 8);
|
|
1582
|
+
const ev = latestEvidence();
|
|
1583
|
+
const evOk = ev && ev.exitCode === 0;
|
|
1584
|
+
const evText = ev
|
|
1585
|
+
? (evOk
|
|
1586
|
+
? `最近绿行 ${fmtTs(ev.ts)} · ${ev.gates ?? '?'} 门全过`
|
|
1587
|
+
: `最近 exit=${ev.exitCode}(${fmtTs(ev.ts)} · ${ev.suite || '?'})未达全绿`)
|
|
1588
|
+
: '时间窗内无验证凭证';
|
|
1589
|
+
const verifyLine = `<div class="sc-proof-verify"><b>计划级验证</b><span class="${evOk ? 'ok' : ev ? 'bad' : ''}">${esc(evText)}</span>${hasEvidence() ? '<button class="ov-link" type="button" data-view="verify">查看验证 →</button>' : ''}</div>`;
|
|
1590
|
+
const proofHtml = isAppendix(s) ? '' :
|
|
1591
|
+
`<div class="sc-proof${acceptance.length || proof.length || ev ? '' : ' empty'}">` +
|
|
1592
|
+
`<div class="sc-proof-h">怎么证明 · 验收判据</div>` +
|
|
1593
|
+
(acceptance.length
|
|
1594
|
+
? `<ul class="sc-accept">${acceptance.map((a) => `<li>${mdInline(a)}</li>`).join('')}</ul>`
|
|
1595
|
+
: `<div class="sc-proof-empty">未登记验收判据</div>`) +
|
|
1596
|
+
(proof.length ? `<div class="sc-proof-meta"><b>留痕</b>${proof.map((p) => mdInline(p)).join(';')}</div>` : '') +
|
|
1597
|
+
verifyLine +
|
|
1598
|
+
`</div>`;
|
|
1599
|
+
const emptyText = isVerificationStep(s) ? '验证步骤(无代码文件)' : (totalFiles ? '当前筛选无文件' : '该步未列文件');
|
|
1600
|
+
const statusTag = s.status === 'done' ? '<span class="done">已完成</span>' : s.status === 'warn' ? '<span class="done" style="color:var(--warn)">待复核</span>' : '';
|
|
1601
|
+
const eyebrow = isAppendix(s) ? '附录 · 改动清单' : `做什么 · ${sourceLabel(s)}${statusTag}`;
|
|
1602
|
+
return `<div class="step-card ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${s.status === 'warn' ? 'warn' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}">` +
|
|
1603
|
+
`<div class="sc-head" data-step="${esc(s.id)}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}"><span class="sc-no">${stepNo(s)}</span><div class="sc-titlewrap"><div class="sc-eyebrow">${eyebrow}</div><h3>${esc(stepTitle(s))}</h3></div><span class="meta">${files.length} 文件</span></div>` +
|
|
1604
|
+
`<div class="sc-body">` +
|
|
1605
|
+
(s.summary && !(s.source === 'tasks') ? `<p class="sc-summary">${esc(s.summary)}</p>` : '') +
|
|
1606
|
+
(!isAppendix(s) ? stepReqHtml() : '') +
|
|
1607
|
+
(!isAppendix(s) ? `<div class="sc-sec"><div class="sc-sec-h">怎么做 · 实现要点 / 依赖 / 风险</div>${details.length ? `<ul class="sc-detail">${details.map((d) => `<li>${mdInline(d)}</li>`).join('')}</ul>` : '<div class="sc-sec-empty">任务拆解未写实现要点 / 依赖 / 风险</div>'}</div>` : '') +
|
|
1608
|
+
`<div class="sc-sec"><div class="sc-sec-h">改动文件 <b>${files.length}</b></div>` +
|
|
1609
|
+
`<div class="sc-files">${files.map((f) => fileRow(f, dfo)).join('') || `<div class="none" style="padding:8px">${emptyText}</div>`}</div></div>` +
|
|
1610
|
+
(proofHtml ? `<div class="sc-sec">${proofHtml}</div>` : '') +
|
|
1611
|
+
`</div></div>`;
|
|
1612
|
+
}).join('');
|
|
1613
|
+
$('#detail-body').innerHTML = html || '<div class="none">无步骤</div>';
|
|
1614
|
+
if (resetScroll) $('#detail-body').scrollTop = 0;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function fileRow(f, dfo) {
|
|
1618
|
+
const diffBtn = f.diff ? `<button class="diff-btn" data-diff="${esc(f.path)}">diff</button>` : '';
|
|
1619
|
+
const nums = (f.additions || f.deletions) ? `<span class="num">+${f.additions || 0}${f.deletions ? `<del>-${f.deletions}</del>` : ''}</span>` : '';
|
|
1620
|
+
const active = state.file === f.path || (dfo && (dfo.files || []).includes(f.path)) ? 'on' : '';
|
|
1621
|
+
return `<div class="file-row ${active}" data-file="${esc(f.path)}" title="${esc(f.path)}" role="button" tabindex="0" aria-label="文件 ${esc(f.path)}">` +
|
|
1622
|
+
`<span class="dot" style="background:${statusDot(f)}"></span>` +
|
|
1623
|
+
`<span class="path">${esc(f.path)}</span>${nums}${badgeOf(f)}${diffBtn}</div>` +
|
|
1624
|
+
(f.diff ? `<pre class="diff ${state.file === f.path ? 'open' : ''}" data-diff-for="${esc(f.path)}">${diffHtml(f.diff)}</pre>` : '');
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function diffHtml(diff) {
|
|
1628
|
+
const lines = String(diff).split(/\r?\n/);
|
|
1629
|
+
return lines.map((l) => {
|
|
1630
|
+
let cls = '';
|
|
1631
|
+
if (/^diff --git|^index |^--- |^\+\+\+ |^new file|^deleted file|^similarity|^rename /.test(l)) cls = 'meta';
|
|
1632
|
+
else if (/^@@/.test(l)) cls = 'hunk';
|
|
1633
|
+
else if (/^\+/.test(l)) cls = 'add';
|
|
1634
|
+
else if (/^-/.test(l)) cls = 'del';
|
|
1635
|
+
return cls ? `<span class="${cls}">${esc(l)}</span>` : esc(l);
|
|
1636
|
+
}).join('\n');
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function renderAll(opts = {}) {
|
|
1640
|
+
if (!DATA) return;
|
|
1641
|
+
const scroll = {
|
|
1642
|
+
tree: $('#file-tree').scrollTop,
|
|
1643
|
+
detail: $('#detail-body').scrollTop,
|
|
1644
|
+
nav: $('#step-nav').scrollTop,
|
|
1645
|
+
canvas: $('#canvas-scroll').scrollLeft,
|
|
1646
|
+
};
|
|
1647
|
+
const detailKeyBefore = lastDetailKey;
|
|
1648
|
+
renderHeader();
|
|
1649
|
+
renderStepNav();
|
|
1650
|
+
renderTree();
|
|
1651
|
+
renderCanvas();
|
|
1652
|
+
renderDetail();
|
|
1653
|
+
$('#file-tree').scrollTop = scroll.tree;
|
|
1654
|
+
$('#detail-body').scrollTop = lastDetailKey === detailKeyBefore ? scroll.detail : 0;
|
|
1655
|
+
$('#step-nav').scrollTop = scroll.nav;
|
|
1656
|
+
if (opts.keepCanvas) $('#canvas-scroll').scrollLeft = scroll.canvas;
|
|
1657
|
+
else $('#canvas-scroll').scrollLeft = 0;
|
|
1658
|
+
if (opts.scrollFile) {
|
|
1659
|
+
const el = $(`.file-row[data-file="${cssEsc(state.file)}"]`);
|
|
1660
|
+
if (el) el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1661
|
+
}
|
|
1662
|
+
if (opts.scrollStep) {
|
|
1663
|
+
const el = $(`.station[data-step="${cssEsc(state.step)}"], .flow-node[data-step="${cssEsc(state.step)}"]`);
|
|
1664
|
+
if (el) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' });
|
|
1665
|
+
}
|
|
1666
|
+
syncHash();
|
|
1667
|
+
}
|
|
1668
|
+
function cssEsc(s) { return String(s).replace(/["\\]/g, '\\$&'); }
|
|
1669
|
+
|
|
1670
|
+
function selectStep(id) {
|
|
1671
|
+
state.step = state.step === id ? null : id;
|
|
1672
|
+
state.file = null;
|
|
1673
|
+
renderAll({ scrollStep: true });
|
|
1674
|
+
}
|
|
1675
|
+
function selectFile(p) {
|
|
1676
|
+
const f = fileOf(p);
|
|
1677
|
+
if (!f) return;
|
|
1678
|
+
state.file = p;
|
|
1679
|
+
state.step = (f.steps && f.steps[0]) || state.step;
|
|
1680
|
+
renderAll({ scrollFile: true });
|
|
1681
|
+
}
|
|
1682
|
+
function toggleDataflowObj(name) {
|
|
1683
|
+
if (state.dataflowObj === name) {
|
|
1684
|
+
clearDataflowSel(true);
|
|
1685
|
+
} else {
|
|
1686
|
+
if (!state.dataflowObj) { state.dfPrevStep = state.step; state.dfPrevFile = state.file; }
|
|
1687
|
+
state.dataflowObj = name;
|
|
1688
|
+
}
|
|
1689
|
+
renderAll();
|
|
1690
|
+
}
|
|
1691
|
+
function clearLink() {
|
|
1692
|
+
state.step = null; state.file = null;
|
|
1693
|
+
clearDataflowSel(false);
|
|
1694
|
+
state.filter = 'all';
|
|
1695
|
+
stopFollow();
|
|
1696
|
+
renderAll();
|
|
1697
|
+
}
|
|
1698
|
+
function setView(v) {
|
|
1699
|
+
if (v === 'dataflow' && !hasDataflow()) v = 'panorama';
|
|
1700
|
+
if (v === 'reqs' && !hasReqs()) v = 'panorama';
|
|
1701
|
+
if (v === 'verify' && !hasEvidence()) v = 'panorama';
|
|
1702
|
+
state.view = v;
|
|
1703
|
+
state.zoom = 1;
|
|
1704
|
+
$$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === v));
|
|
1705
|
+
renderAll();
|
|
1706
|
+
// 视图用量埋点(2026-11-11 tour-usage):fire-and-forget,失败静默不影响页面;
|
|
1707
|
+
// 静态 file:// 快照无服务时自然跳过。数据只落本机 .agents/cache/tour-usage.jsonl(口径见 dev-tour SKILL 边界节)。
|
|
1708
|
+
if (DATA && DATA.doc && DATA.doc.path && typeof fetch === 'function') {
|
|
1709
|
+
try {
|
|
1710
|
+
fetch('/api/tour/usage', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1711
|
+
body: JSON.stringify({ doc: DATA.doc.path, view: v, ts: new Date().toISOString() }) }).catch(() => {});
|
|
1712
|
+
} catch { /* file:// 下同步抛错也不影响页面 */ }
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
function setFilter(f) { state.filter = f; renderAll(); }
|
|
1716
|
+
|
|
1717
|
+
function fitZoom() {
|
|
1718
|
+
if (state.view === 'panorama' && state.layout === 'grid') { state.zoom = 1; renderCanvas(); return; }
|
|
1719
|
+
const wrap = $('#canvas');
|
|
1720
|
+
const scroller = $('#canvas-scroll');
|
|
1721
|
+
const contentW = wrap.scrollWidth;
|
|
1722
|
+
const avail = scroller.clientWidth - 36;
|
|
1723
|
+
const z = contentW > avail ? Math.max(0.25, avail / contentW) : 1;
|
|
1724
|
+
state.zoom = Math.round(z * 100) / 100;
|
|
1725
|
+
renderCanvas();
|
|
1726
|
+
}
|
|
1727
|
+
function zoomBy(d) {
|
|
1728
|
+
state.zoom = Math.max(0.25, Math.min(1.6, Math.round((state.zoom + d) * 10) / 10));
|
|
1729
|
+
renderCanvas();
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function startFollow() {
|
|
1733
|
+
state.follow = true;
|
|
1734
|
+
$('#btn-follow').classList.add('on');
|
|
1735
|
+
$('#btn-follow').textContent = '❚❚ 暂停跟走';
|
|
1736
|
+
const order = DATA.steps.map((s) => s.id);
|
|
1737
|
+
const cur = state.step ? order.indexOf(state.step) : -1;
|
|
1738
|
+
let i = cur < 0 ? 0 : cur;
|
|
1739
|
+
state.step = order[i];
|
|
1740
|
+
renderAll({ scrollStep: true });
|
|
1741
|
+
followTimer = setInterval(() => {
|
|
1742
|
+
i++;
|
|
1743
|
+
if (i >= order.length) { stopFollow(); return; }
|
|
1744
|
+
state.step = order[i];
|
|
1745
|
+
state.file = null;
|
|
1746
|
+
renderAll({ scrollStep: true });
|
|
1747
|
+
}, 4200);
|
|
1748
|
+
}
|
|
1749
|
+
function stopFollow() {
|
|
1750
|
+
state.follow = false;
|
|
1751
|
+
clearInterval(followTimer); followTimer = null;
|
|
1752
|
+
$('#btn-follow').classList.remove('on');
|
|
1753
|
+
$('#btn-follow').textContent = '▶ 跟着流程走';
|
|
1754
|
+
}
|
|
1755
|
+
function toggleFollow() { state.follow ? stopFollow() : startFollow(); }
|
|
1756
|
+
|
|
1757
|
+
function bind() {
|
|
1758
|
+
document.addEventListener('click', (e) => {
|
|
1759
|
+
const dir = e.target.closest('.tree-row');
|
|
1760
|
+
if (dir) {
|
|
1761
|
+
const box = dir.closest('.tree-dir');
|
|
1762
|
+
const p = box.dataset.dir;
|
|
1763
|
+
if (state.collapsed.has(p)) state.collapsed.delete(p); else state.collapsed.add(p);
|
|
1764
|
+
renderTree();
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
const cp = e.target.closest('[data-copy]');
|
|
1768
|
+
if (cp) {
|
|
1769
|
+
e.stopPropagation();
|
|
1770
|
+
const text = cp.dataset.copy || '';
|
|
1771
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1772
|
+
const old = cp.textContent;
|
|
1773
|
+
navigator.clipboard.writeText(text).then(() => { cp.textContent = '已复制'; setTimeout(() => { cp.textContent = old; }, 1200); }).catch(() => {});
|
|
1774
|
+
}
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
const df = e.target.closest('[data-diff]');
|
|
1778
|
+
if (df) {
|
|
1779
|
+
e.stopPropagation();
|
|
1780
|
+
const pre = $(`[data-diff-for="${cssEsc(df.dataset.diff)}"]`);
|
|
1781
|
+
if (pre) pre.classList.toggle('open');
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
const dfc = e.target.closest('[data-df]');
|
|
1785
|
+
if (dfc) { e.stopPropagation(); toggleDataflowObj(dfc.dataset.df); return; }
|
|
1786
|
+
const file = e.target.closest('[data-file]');
|
|
1787
|
+
if (file) { e.stopPropagation(); selectFile(file.dataset.file); return; }
|
|
1788
|
+
const step = e.target.closest('[data-step]');
|
|
1789
|
+
if (step) { selectStep(step.dataset.step); return; }
|
|
1790
|
+
const view = e.target.closest('[data-view]');
|
|
1791
|
+
if (view) { setView(view.dataset.view); return; }
|
|
1792
|
+
const filter = e.target.closest('[data-filter]');
|
|
1793
|
+
if (filter) { setFilter(filter.dataset.filter); return; }
|
|
1794
|
+
});
|
|
1795
|
+
document.addEventListener('mouseover', (e) => {
|
|
1796
|
+
const n = e.target.closest('.flow-node');
|
|
1797
|
+
if (n && n.dataset.step) flowDepHover(n.dataset.step, true);
|
|
1798
|
+
});
|
|
1799
|
+
document.addEventListener('mouseout', (e) => {
|
|
1800
|
+
const n = e.target.closest('.flow-node');
|
|
1801
|
+
if (n && n.dataset.step) flowDepHover(n.dataset.step, false);
|
|
1802
|
+
});
|
|
1803
|
+
window.addEventListener('resize', () => { if (state.view === 'flow') drawFlowDeps(); });
|
|
1804
|
+
$('#search').addEventListener('input', (e) => { state.query = e.target.value; renderStepNav(); renderTree(); });
|
|
1805
|
+
$('#btn-clear').addEventListener('click', clearLink);
|
|
1806
|
+
$('#detail-back').addEventListener('click', clearLink);
|
|
1807
|
+
$('#btn-follow').addEventListener('click', toggleFollow);
|
|
1808
|
+
$('#btn-copy').addEventListener('click', async () => {
|
|
1809
|
+
try { await navigator.clipboard.writeText(DATA.doc.path); $('#btn-copy').textContent = '已复制'; setTimeout(() => $('#btn-copy').textContent = '复制路径', 1200); } catch { /* 忽略 */ }
|
|
1810
|
+
});
|
|
1811
|
+
$('#expand-all').addEventListener('click', () => { state.collapsed.clear(); renderTree(); });
|
|
1812
|
+
$('#collapse-all').addEventListener('click', () => {
|
|
1813
|
+
state.collapsed.clear();
|
|
1814
|
+
const walk = (node) => { for (const d of node.dirs.values()) { state.collapsed.add(d.path); walk(d); } };
|
|
1815
|
+
walk(buildTree(DATA.files));
|
|
1816
|
+
renderTree();
|
|
1817
|
+
});
|
|
1818
|
+
$('#btn-focus').addEventListener('click', () => {
|
|
1819
|
+
document.body.classList.toggle('focus');
|
|
1820
|
+
renderAll();
|
|
1821
|
+
});
|
|
1822
|
+
$('#layout-toggle').addEventListener('click', () => {
|
|
1823
|
+
state.layout = state.layout === 'grid' ? 'timeline' : 'grid';
|
|
1824
|
+
try { localStorage.setItem('devTourLayout', state.layout); } catch { /* 本地禁用时忽略 */ }
|
|
1825
|
+
state.zoom = 1;
|
|
1826
|
+
renderAll();
|
|
1827
|
+
});
|
|
1828
|
+
$('#fit').addEventListener('click', fitZoom);
|
|
1829
|
+
$('#zoom-in').addEventListener('click', () => zoomBy(0.1));
|
|
1830
|
+
$('#zoom-out').addEventListener('click', () => zoomBy(-0.1));
|
|
1831
|
+
document.addEventListener('keydown', (e) => {
|
|
1832
|
+
if (e.key === 'Escape') { document.body.classList.remove('focus'); clearLink(); return; }
|
|
1833
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
1834
|
+
if ((e.key === 'Enter' || e.key === ' ') && e.target.tagName !== 'BUTTON') {
|
|
1835
|
+
const hit = e.target.closest('[data-step],[data-file],[data-diff],[data-df]');
|
|
1836
|
+
if (hit) { e.preventDefault(); hit.click(); return; }
|
|
1837
|
+
}
|
|
1838
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
|
|
1839
|
+
const order = DATA.steps.map((s) => s.id);
|
|
1840
|
+
const i = state.step ? order.indexOf(state.step) : -1;
|
|
1841
|
+
const next = e.key === 'ArrowRight' ? Math.min(order.length - 1, i + 1) : Math.max(0, i < 0 ? 0 : i - 1);
|
|
1842
|
+
state.step = order[next]; state.file = null;
|
|
1843
|
+
renderAll({ scrollStep: true });
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
window.addEventListener('hashchange', () => {
|
|
1847
|
+
const p = new URLSearchParams(location.hash.slice(1));
|
|
1848
|
+
state.step = p.get('step') || null;
|
|
1849
|
+
state.file = p.get('file') || null;
|
|
1850
|
+
if (p.get('view')) setView(p.get('view')); else renderAll();
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
async function fetchModel() {
|
|
1855
|
+
const file = DOC_FILE || new URLSearchParams(location.search).get('file');
|
|
1856
|
+
if (!file) throw new Error('缺少 file 参数');
|
|
1857
|
+
const r = await fetch('/api/tour?file=' + encodeURIComponent(file), { cache: 'no-store' });
|
|
1858
|
+
const j = await r.json();
|
|
1859
|
+
if (!r.ok || j.error) throw new Error(j.error || ('HTTP ' + r.status));
|
|
1860
|
+
if (j.doc && !j.doc.path) j.doc.path = file;
|
|
1861
|
+
return j;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function applyModel(model) {
|
|
1865
|
+
const dfo = (model.dataflow && Array.isArray(model.dataflow.objects)) ? model.dataflow.objects : [];
|
|
1866
|
+
const dfl = (model.dataflow && Array.isArray(model.dataflow.flows)) ? model.dataflow.flows : [];
|
|
1867
|
+
const dpe = (model.deps && Array.isArray(model.deps.edges)) ? model.deps.edges : [];
|
|
1868
|
+
const sig = model.files.length + '|' + model.stats.additions + '/' + model.stats.deletions + '|' + model.steps.map((s) => s.id + s.files.length).join(',') + '|' + model.files.map((f) => f.path + f.status + f.additions + f.deletions + (f.diff ? f.diff.length : 0)).join(',') +
|
|
1869
|
+
'|df:' + dfo.length + ':' + dfo.map((o) => o.name + ':' + (o.kind || '') + ':' + (o.files || []).length + ':' + (o.steps || []).join('+')).join(';') + '|' + dfl.map((f) => f.object + '>' + f.from + '>' + f.to).join(';') +
|
|
1870
|
+
'|deps:' + dpe.map((e) => e.from + '>' + e.to).join(';') +
|
|
1871
|
+
'|reqs:' + ((model.doc.upstream || []).map((u) => u.path + u.purpose + (u.points || []).length).join(';')) +
|
|
1872
|
+
'|ev:' + ((model.evidence && model.evidence.rows || []).map((r) => r.ts + r.exitCode + ':' + (r.note || '').length + ':' + (r.logTail || '').length + ':' + Object.keys(r.durations || {}).length).join(';')) +
|
|
1873
|
+
'|ph:' + JSON.stringify(model.parseHealth || null);
|
|
1874
|
+
if (sig === lastSig) return false;
|
|
1875
|
+
lastSig = sig;
|
|
1876
|
+
DATA = model;
|
|
1877
|
+
FILE_BY_PATH = new Map(DATA.files.map((f) => [f.path, f]));
|
|
1878
|
+
if (state.step && !DATA.steps.some((s) => s.id === state.step)) state.step = null;
|
|
1879
|
+
if (state.file && !FILE_BY_PATH.has(state.file)) state.file = null;
|
|
1880
|
+
if (state.dataflowObj && !dfo.some((o) => o.name === state.dataflowObj)) clearDataflowSel(true);
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
async function refresh() {
|
|
1885
|
+
if (MODE !== 'live' || document.visibilityState === 'hidden') return;
|
|
1886
|
+
if (Date.now() - lastRefresh < 1200) return;
|
|
1887
|
+
lastRefresh = Date.now();
|
|
1888
|
+
try {
|
|
1889
|
+
const m = await fetchModel();
|
|
1890
|
+
if (applyModel(m)) renderAll({ keepCanvas: true });
|
|
1891
|
+
$('#live').querySelector('span').textContent = '实时';
|
|
1892
|
+
} catch (e) {
|
|
1893
|
+
$('#live').classList.add('off');
|
|
1894
|
+
$('#live').querySelector('span').textContent = '断线';
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
async function init() {
|
|
1899
|
+
try {
|
|
1900
|
+
if (EMBEDDED) applyModel(EMBEDDED);
|
|
1901
|
+
else applyModel(await fetchModel());
|
|
1902
|
+
} catch (e) {
|
|
1903
|
+
$('#title').textContent = '开发全景图加载失败';
|
|
1904
|
+
$('#subtitle').textContent = e.message;
|
|
1905
|
+
$('#canvas').innerHTML = `<div class="none">${esc(e.message)}<br><br>实时视图需先启动 <code>node .agents/scripts/ensure-board.mjs</code></div>`;
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
readHash();
|
|
1909
|
+
if (!['panorama', 'sequence', 'flow', 'dataflow', 'reqs', 'verify'].includes(state.view)) state.view = 'panorama';
|
|
1910
|
+
if (state.view === 'dataflow' && !hasDataflow()) state.view = 'panorama';
|
|
1911
|
+
if (state.view === 'reqs' && !hasReqs()) state.view = 'panorama';
|
|
1912
|
+
if (state.view === 'verify' && !hasEvidence()) state.view = 'panorama';
|
|
1913
|
+
$$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
|
|
1914
|
+
bind();
|
|
1915
|
+
renderAll();
|
|
1916
|
+
setTimeout(() => document.getElementById('app').classList.remove('booting'), 1700);
|
|
1917
|
+
if (state.file) {
|
|
1918
|
+
const el = $(`.file-row[data-file="${cssEsc(state.file)}"]`);
|
|
1919
|
+
if (el) el.scrollIntoView({ block: 'center' });
|
|
1920
|
+
}
|
|
1921
|
+
if (MODE === 'live') {
|
|
1922
|
+
try {
|
|
1923
|
+
sse = new EventSource('/api/events');
|
|
1924
|
+
sse.addEventListener('changed', () => setTimeout(refresh, 400));
|
|
1925
|
+
sse.addEventListener('watchdead', () => {
|
|
1926
|
+
$('#live').classList.add('off');
|
|
1927
|
+
$('#live').querySelector('span').textContent = '轮询';
|
|
1928
|
+
});
|
|
1929
|
+
sse.onerror = () => { $('#live').classList.add('off'); $('#live').querySelector('span').textContent = '轮询'; };
|
|
1930
|
+
} catch { /* 无 SSE 时靠轮询 */ }
|
|
1931
|
+
liveTimer = setInterval(refresh, 5000);
|
|
1932
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') refresh(); });
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
init();
|
|
1937
|
+
})();
|
|
1938
|
+
</script>
|
|
1939
|
+
</body>
|
|
1940
|
+
</html>
|