@polymorphism-tech/morph-spec 8.47.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* fleet-scanner.js — aggregate view of every active feature across the fleet.
|
|
3
|
-
*
|
|
4
|
-
* A fleet is N Claude Code instances, one per feature worktree, plus whatever
|
|
5
|
-
* is active in the primary root. Each instance only sees its own cwd; the
|
|
6
|
-
* operator loses track of "what is each session doing, what is waiting on me".
|
|
7
|
-
* This scanner answers that from the primary root's vantage point: one entry
|
|
8
|
-
* per active feature, worktree or root-resident, with the operational facts
|
|
9
|
-
* (phase, pending gates, task counts, port block, handoff freshness, last
|
|
10
|
-
* commit, dirtiness) resolved per entry.
|
|
11
|
-
*
|
|
12
|
-
* Read-only and fail-open per FIELD: a corrupt feature.json or a git hiccup
|
|
13
|
-
* nulls that field, never the scan.
|
|
14
|
-
*/
|
|
1
|
+
// Fail-open per FIELD: a corrupt feature.json or a git hiccup nulls that field, never the scan.
|
|
15
2
|
|
|
16
3
|
import { execSync, execFileSync } from 'child_process';
|
|
17
4
|
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
@@ -24,52 +11,31 @@ import {
|
|
|
24
11
|
getFeature,
|
|
25
12
|
derivePhaseForFeature,
|
|
26
13
|
derivePhaseFrom,
|
|
14
|
+
fileSetArtifactProbe,
|
|
27
15
|
getPendingGates,
|
|
28
16
|
pendingGatesFrom,
|
|
29
17
|
} from '../../../framework/hooks/shared/state-reader.js';
|
|
30
18
|
import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
|
|
31
19
|
import { buildPrIndex } from '../git/pr-index.js';
|
|
32
20
|
import { pendingPrFeatures } from '../git/pending-prs.js';
|
|
21
|
+
import { sliceProgress, sliceProgressFrom } from '../../../framework/hooks/shared/slice-progress.js';
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
const data = JSON.parse(raw);
|
|
36
|
-
if (!Array.isArray(data.tasks)) return null;
|
|
37
|
-
return { done: data.tasks.filter((t) => t.status === 'done').length, total: data.tasks.length };
|
|
38
|
-
}
|
|
23
|
+
const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
|
|
39
24
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
* DIVERGENCE from the bug report, which blamed "a cached state.json/feature.json
|
|
44
|
-
* snapshot": this never read either — it always read `2-plan/tasks.json`
|
|
45
|
-
* directly. The real mechanism is which COPY of that file is on disk. Since
|
|
46
|
-
* v8.4 every feature is born on `morph/{f}` and its tasks.json is COMMITTED
|
|
47
|
-
* there; the primary root's working tree only ever holds the copy of the
|
|
48
|
-
* CHECKED-OUT branch. An operator sitting on the default therefore read the
|
|
49
|
-
* pre-work copy — the reported `2/2` for a feature whose branch already had
|
|
50
|
-
* T1..T3 all done.
|
|
51
|
-
*
|
|
52
|
-
* @param {string} featureDir - the feature dir inside `path`
|
|
53
|
-
* @param {{ root: string, feature: string, featureBranch: string|null }} ctx
|
|
54
|
-
*/
|
|
55
|
-
function taskCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
25
|
+
// A root-resident feature's reports live on `morph/{f}`; the root tree only has the pre-work copy.
|
|
26
|
+
function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
56
27
|
if (featureBranch && root && feature) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'git',
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return countFrom(readFileSync(join(featureDir, '2-plan', 'tasks.json'), 'utf8'));
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
28
|
+
const show = (rel) => {
|
|
29
|
+
try {
|
|
30
|
+
return execFileSync('git', ['show', `${featureBranch}:.morph/features/${feature}/${rel}`], { cwd: root, stdio: 'pipe' }).toString();
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const onBranch = sliceProgressFrom(show('2-plan/checks.json'), (id) => show(`3-implement/reports/${id}.md`));
|
|
36
|
+
if (onBranch) return counts(onBranch);
|
|
72
37
|
}
|
|
38
|
+
return counts(sliceProgress(featureDir));
|
|
73
39
|
}
|
|
74
40
|
|
|
75
41
|
function handoffInfo(featureDir) {
|
|
@@ -101,20 +67,7 @@ function dirtyCount(worktreePath) {
|
|
|
101
67
|
}
|
|
102
68
|
}
|
|
103
69
|
|
|
104
|
-
|
|
105
|
-
* One `git ls-remote --heads origin` for the WHOLE scan → Map<branch, sha>.
|
|
106
|
-
*
|
|
107
|
-
* The fleet used to read only local state, so a feature whose branch (and PR)
|
|
108
|
-
* already lived on origin still showed up as "pending, nothing pushed" — that
|
|
109
|
-
* reading is what made a second session redo finished work. Resolved once per
|
|
110
|
-
* scan, never per entry.
|
|
111
|
-
*
|
|
112
|
-
* Returns `null` when there is no origin, or the probe fails/times out: the
|
|
113
|
-
* entries then carry `remote: null` ("unknown"), never a false `pushed: false`.
|
|
114
|
-
*
|
|
115
|
-
* @param {string} root
|
|
116
|
-
* @returns {Map<string,string>|null}
|
|
117
|
-
*/
|
|
70
|
+
// null = unknown (no origin, or the probe failed): entries get `remote: null`, never a false `pushed: false`.
|
|
118
71
|
function remoteHeads(root) {
|
|
119
72
|
if (!hasOriginRemote(root)) return null;
|
|
120
73
|
try {
|
|
@@ -132,28 +85,11 @@ function remoteHeads(root) {
|
|
|
132
85
|
}
|
|
133
86
|
}
|
|
134
87
|
|
|
135
|
-
/**
|
|
136
|
-
* One `gh pr list` for the whole scan → Map<headRefName, {number, state}>.
|
|
137
|
-
*
|
|
138
|
-
* Delegates to the shared `buildPrIndex` so `fleet`, `branch prune` and
|
|
139
|
-
* `doctor --worktrees` cannot answer "does this branch have a PR" differently
|
|
140
|
-
* — the destructive two read it to decide what NOT to delete.
|
|
141
|
-
*
|
|
142
|
-
* Fail-open: `null` (no `gh`, not authenticated, not GitHub, timeout) just
|
|
143
|
-
* leaves the PR half of the column empty while `pushed` still works.
|
|
144
|
-
*
|
|
145
|
-
* @param {string} root
|
|
146
|
-
* @returns {Map<string,{number:number,state:string}>|null}
|
|
147
|
-
*/
|
|
148
88
|
function remotePrs(root) {
|
|
149
89
|
return buildPrIndex(root, { limit: 100 });
|
|
150
90
|
}
|
|
151
91
|
|
|
152
|
-
|
|
153
|
-
* The remote fact for one feature. Worktree entries know their real branch
|
|
154
|
-
* (which may carry a `--fresh` date suffix); root-resident features fall back
|
|
155
|
-
* to the canonical `morph/{feature}` every feature is born on.
|
|
156
|
-
*/
|
|
92
|
+
// A worktree's real branch may carry a `--fresh` date suffix; only root entries fall back to `morph/{f}`.
|
|
157
93
|
function buildRemote(feature, branch, heads, prs) {
|
|
158
94
|
if (!heads) return null;
|
|
159
95
|
const remoteBranch = branch || `morph/${feature}`;
|
|
@@ -164,23 +100,17 @@ function buildRemote(feature, branch, heads, prs) {
|
|
|
164
100
|
function buildEntry(feature, { location, path, branch, root, allocations, blockSize, heads, prs, claims }) {
|
|
165
101
|
let workType = null;
|
|
166
102
|
let status = 'unknown';
|
|
167
|
-
let phase = '
|
|
103
|
+
let phase = 'plan';
|
|
168
104
|
let pendingGates = [];
|
|
169
105
|
|
|
170
|
-
// Resolved once and shared with taskCounts — see branchToReadFrom.
|
|
171
106
|
const featureBranch = branchToReadFrom(root, feature, location, branch);
|
|
172
107
|
|
|
173
108
|
try {
|
|
174
|
-
// B3b: phase and gates suffered the same branch-staleness B3 fixed for
|
|
175
|
-
// tasks. They read `.morph/features/{f}/` through the working tree, so a
|
|
176
|
-
// root parked on the default reported the PRE-WORK phase for a feature
|
|
177
|
-
// whose branch was already at implement — and re-asked for a gate the
|
|
178
|
-
// feature branch had already approved.
|
|
179
109
|
const view = featureBranch ? branchView(root, feature, featureBranch) : null;
|
|
180
110
|
if (view) {
|
|
181
111
|
workType = view.fj?.workType || null;
|
|
182
112
|
status = view.fj?.status || 'unknown';
|
|
183
|
-
phase = derivePhaseFrom(view.fj, (
|
|
113
|
+
phase = derivePhaseFrom(view.fj, fileSetArtifactProbe(view.files));
|
|
184
114
|
pendingGates = pendingGatesFrom(view.fj);
|
|
185
115
|
} else {
|
|
186
116
|
const fj = getFeature(feature, path);
|
|
@@ -189,7 +119,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
189
119
|
phase = derivePhaseForFeature(feature, path);
|
|
190
120
|
pendingGates = getPendingGates(feature, path);
|
|
191
121
|
}
|
|
192
|
-
} catch { /*
|
|
122
|
+
} catch { /* fail-open */ }
|
|
193
123
|
|
|
194
124
|
const featureDir = join(path, '.morph', 'features', feature);
|
|
195
125
|
const alloc = allocations[feature];
|
|
@@ -202,7 +132,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
202
132
|
location,
|
|
203
133
|
path,
|
|
204
134
|
branch,
|
|
205
|
-
|
|
135
|
+
slices: sliceCounts(featureDir, { root, feature, featureBranch }),
|
|
206
136
|
env: alloc && Number.isInteger(alloc.base)
|
|
207
137
|
? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
|
|
208
138
|
: null,
|
|
@@ -210,20 +140,11 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
210
140
|
lastCommit: location === 'worktree' && branch ? lastCommitRelative(root, branch) : null,
|
|
211
141
|
dirty: location === 'worktree' ? dirtyCount(path) : null,
|
|
212
142
|
remote: buildRemote(feature, branch, heads, prs),
|
|
213
|
-
//
|
|
214
|
-
// `live` is what matters for the operator — a stale claim is reported as
|
|
215
|
-
// stale rather than hidden, so "nobody is here" and "somebody died here"
|
|
216
|
-
// stay distinguishable.
|
|
143
|
+
// A stale claim is reported, not hidden: "nobody is here" ≠ "somebody died here".
|
|
217
144
|
claim: claims?.[feature] || null,
|
|
218
145
|
};
|
|
219
146
|
}
|
|
220
147
|
|
|
221
|
-
/**
|
|
222
|
-
* Scan the fleet from anywhere inside the repo.
|
|
223
|
-
*
|
|
224
|
-
* @param {string} [cwd]
|
|
225
|
-
* @returns {{ root: string, entries: object[] }}
|
|
226
|
-
*/
|
|
227
148
|
export function scanFleet(cwd = process.cwd()) {
|
|
228
149
|
if (!isGitRepo(cwd)) {
|
|
229
150
|
return { root: resolve(cwd).replace(/\\/g, '/'), entries: [] };
|
|
@@ -239,13 +160,12 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
239
160
|
try {
|
|
240
161
|
const reg = JSON.parse(readFileSync(registryPath(gitCommonDir), 'utf8'));
|
|
241
162
|
if (reg && typeof reg.allocations === 'object' && reg.allocations !== null) allocations = reg.allocations;
|
|
242
|
-
} catch { /*
|
|
163
|
+
} catch { /* env stays null per entry */ }
|
|
243
164
|
for (const c of listClaims(gitCommonDir)) claims[c.feature] = c;
|
|
244
165
|
}
|
|
245
|
-
} catch { /*
|
|
166
|
+
} catch { /* env and claim stay null per entry */ }
|
|
246
167
|
const { blockSize } = resolveFleetConfig(root);
|
|
247
168
|
|
|
248
|
-
// Remote awareness, resolved ONCE for the whole scan (see remoteHeads/remotePrs).
|
|
249
169
|
const heads = remoteHeads(root);
|
|
250
170
|
const prs = heads ? remotePrs(root) : null;
|
|
251
171
|
|
|
@@ -254,7 +174,7 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
254
174
|
|
|
255
175
|
for (const e of parseWorktreeList(root)) {
|
|
256
176
|
const m = e.branch && e.branch.match(/^refs\/heads\/morph\/(.+)$/);
|
|
257
|
-
if (!m) continue;
|
|
177
|
+
if (!m) continue;
|
|
258
178
|
inWorktree.add(m[1]);
|
|
259
179
|
entries.push(buildEntry(m[1], {
|
|
260
180
|
location: 'worktree',
|
|
@@ -269,7 +189,6 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
269
189
|
}));
|
|
270
190
|
}
|
|
271
191
|
|
|
272
|
-
// Root-resident features (no live worktree): whatever has a feature.json.
|
|
273
192
|
try {
|
|
274
193
|
const featuresDir = join(root, '.morph', 'features');
|
|
275
194
|
if (existsSync(featuresDir)) {
|
|
@@ -285,23 +204,9 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
285
204
|
}
|
|
286
205
|
} catch { /* fail-open */ }
|
|
287
206
|
|
|
288
|
-
// Features that exist
|
|
289
|
-
//
|
|
290
|
-
// `
|
|
291
|
-
// "nothing in flight" about work that is under review on GitHub.
|
|
292
|
-
//
|
|
293
|
-
// `prs` was attempted above whenever `heads` is truthy (`remotePrs` runs —
|
|
294
|
-
// see `const prs = heads ? remotePrs(root) : null;` — regardless of
|
|
295
|
-
// whether that `gh` call itself succeeded). Gate on the SAME condition
|
|
296
|
-
// `remotePrs` used, not on `prs !== null`: `remotePrs`/`buildPrIndex` can
|
|
297
|
-
// fail (timeout, not authenticated, malformed JSON) and legitimately
|
|
298
|
-
// return `null` even after really invoking `gh`. Passing that `null`
|
|
299
|
-
// through lets `pendingPrFeatures` short-circuit on its own documented
|
|
300
|
-
// contract (explicit `null` index = known unknown, never a fallback build)
|
|
301
|
-
// instead of retrying `gh` a second time in the same scan. Only when
|
|
302
|
-
// `remotePrs` was never attempted at all (`heads` falsy) does
|
|
303
|
-
// `pendingPrFeatures` get no index, so it makes the first (and only) `gh`
|
|
304
|
-
// call itself.
|
|
207
|
+
// Features that exist only as an open PR (what `finish --pr` leaves). Gate on `heads`, not
|
|
208
|
+
// `prs !== null`: a `gh` that already ran and failed passes an explicit `null` index, so
|
|
209
|
+
// `pendingPrFeatures` does not call `gh` a second time.
|
|
305
210
|
try {
|
|
306
211
|
const seen = new Set(entries.map((e) => e.feature));
|
|
307
212
|
const pendOpts = heads ? { index: prs } : {};
|
|
@@ -316,7 +221,7 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
316
221
|
location: 'pr-pending',
|
|
317
222
|
path: null,
|
|
318
223
|
branch: p.branch,
|
|
319
|
-
|
|
224
|
+
slices: null,
|
|
320
225
|
env: null,
|
|
321
226
|
handoff: { exists: false, generatedAt: null },
|
|
322
227
|
lastCommit: null,
|
|
@@ -325,7 +230,7 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
325
230
|
claim: null,
|
|
326
231
|
});
|
|
327
232
|
}
|
|
328
|
-
} catch { /* fail-open
|
|
233
|
+
} catch { /* fail-open */ }
|
|
329
234
|
|
|
330
235
|
return { root, entries };
|
|
331
236
|
}
|
|
@@ -1,77 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* WHY THIS EXISTS: `git worktree remove` deletes gitignored files outright and
|
|
6
|
-
* the clean-check that gates it (`git status --porcelain`) omits them by
|
|
7
|
-
* design, so a user's `.env` dies silently while the command reports success.
|
|
8
|
-
* The first fix for that refused the removal whenever ANY unrecognised
|
|
9
|
-
* gitignored path was present — correct, but blind: in the observed session a
|
|
10
|
-
* worktree carried `.env` and `.env.e2e` that were BYTE-IDENTICAL to the
|
|
11
|
-
* primary root's, alongside a `keys/key-*.xml` that existed nowhere else. The
|
|
12
|
-
* refusal treated them alike, and the only escape it offered
|
|
13
|
-
* (`--discard-ignored`) destroyed the unique one together with the replicas.
|
|
14
|
-
*
|
|
15
|
-
* The triage below replaces "recognised vs not" with the question that actually
|
|
16
|
-
* decides whether bytes are lost:
|
|
17
|
-
*
|
|
18
|
-
* REPLICABLE — the primary root has the same relative path with identical
|
|
19
|
-
* content. Deleting the worktree's copy loses nothing; the
|
|
20
|
-
* original stays where it always was.
|
|
21
|
-
* DISPOSABLE — a well-known build artifact or cache (`bin/`, `obj/`,
|
|
22
|
-
* `node_modules/`, `.next/`, `TestResults/`, `graphify-out/`…,
|
|
23
|
-
* see DISPOSABLE_DIR_NAMES). Reproducible from source, which is a
|
|
24
|
-
* STRONGER guarantee than backed up.
|
|
25
|
-
* EXCLUSIVE — everything else: no counterpart in the root, or a counterpart
|
|
26
|
-
* whose content differs. These are the only bytes at stake, and
|
|
27
|
-
* the caller copies them to `finish-backup` before proceeding.
|
|
28
|
-
*
|
|
29
|
-
* Fail-CLOSED on every uncertainty: an unreadable file, a hash that cannot be
|
|
30
|
-
* computed, a directory too large to compare, a counterpart that is a link
|
|
31
|
-
* rather than a real file — all classify as EXCLUSIVE. Being wrong in that
|
|
32
|
-
* direction costs a backup copy; being wrong the other way costs the file.
|
|
33
|
-
*/
|
|
1
|
+
// `git worktree remove` deletes gitignored files and `git status` never shows them.
|
|
2
|
+
// Every uncertainty classifies as EXCLUSIVE (backed up): being wrong that way costs a
|
|
3
|
+
// copy, the other way costs the file.
|
|
34
4
|
|
|
35
5
|
import { createHash } from 'crypto';
|
|
36
6
|
import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs';
|
|
37
7
|
import { join } from 'path';
|
|
38
8
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* `build/`, `coverage/`); anything unlisted still reaches the EXCLUSIVE bucket,
|
|
43
|
-
* where it is backed up rather than assumed disposable. Each name here is a
|
|
44
|
-
* directory its own tool rewrites from scratch: `.next/` (next build/dev),
|
|
45
|
-
* `.turbo/`, `.pytest_cache/`, `TestResults/` (VSTest), `graphify-out/`
|
|
46
|
-
* (`morph-spec graph build`). `.next/` is also the one that makes a backup
|
|
47
|
-
* weigh hundreds of megabytes; `graphify-out/` is seeded into every worktree
|
|
48
|
-
* and is past the replica size cap (18 MB in a real project), so without its
|
|
49
|
-
* entry every `finish` would back it up.
|
|
50
|
-
*
|
|
51
|
-
* `.auth/` (Playwright storage state) belongs here for a reason worth stating:
|
|
52
|
-
* it holds a captured session, so it LOOKS like precious local state — but it
|
|
53
|
-
* is regenerated by the auth setup project on the next run, and it expires on
|
|
54
|
-
* its own. Keeping it out of the backup avoids copying credentials around.
|
|
55
|
-
*/
|
|
9
|
+
// Only dirs their own tool rewrites from scratch. Never add `dist/`, `build/` or `coverage/`:
|
|
10
|
+
// projects hand-write those. `.auth/` (Playwright session) is regenerated by the auth setup,
|
|
11
|
+
// and keeping it out of the backup avoids copying credentials around.
|
|
56
12
|
const DISPOSABLE_DIR_NAMES = new Set([
|
|
57
13
|
'bin', 'obj', 'node_modules', '__pycache__', 'test-results', '.auth',
|
|
58
14
|
'.next', '.turbo', '.pytest_cache', 'TestResults', 'graphify-out',
|
|
59
15
|
]);
|
|
60
16
|
const DISPOSABLE_FILE_RE = /\.tsbuildinfo$/;
|
|
61
17
|
|
|
62
|
-
/**
|
|
63
|
-
* Comparing a directory recursively is only worth doing while it stays small:
|
|
64
|
-
* the point is to spare `.env`-sized replicas from a needless refusal, not to
|
|
65
|
-
* checksum a data set. Past either cap the answer becomes EXCLUSIVE (the safe
|
|
66
|
-
* direction), and the caller backs it up.
|
|
67
|
-
*/
|
|
68
18
|
const REPLICABLE_MAX_FILES = 500;
|
|
69
19
|
const REPLICABLE_MAX_BYTES = 8 * 1024 * 1024;
|
|
70
20
|
|
|
71
|
-
/**
|
|
72
|
-
* @param {string} p - forward-slash relative path
|
|
73
|
-
* @returns {boolean}
|
|
74
|
-
*/
|
|
21
|
+
/** @param {string} p - forward-slash relative path */
|
|
75
22
|
export function isDisposableBuildArtifact(p) {
|
|
76
23
|
if (DISPOSABLE_FILE_RE.test(p)) return true;
|
|
77
24
|
return p.split('/').filter(Boolean).some((seg) => DISPOSABLE_DIR_NAMES.has(seg));
|
|
@@ -85,11 +32,7 @@ function sha256OfFile(path) {
|
|
|
85
32
|
}
|
|
86
33
|
}
|
|
87
34
|
|
|
88
|
-
|
|
89
|
-
* Real file (not a link, not a directory)? A reparse point must never be read
|
|
90
|
-
* THROUGH — following one is the exact failure mode the whole teardown path
|
|
91
|
-
* exists to prevent — so a link on either side makes the pair incomparable.
|
|
92
|
-
*/
|
|
35
|
+
// lstat, never stat: reading THROUGH a junction is the failure the teardown path exists to prevent.
|
|
93
36
|
function isRealFile(path) {
|
|
94
37
|
try {
|
|
95
38
|
return lstatSync(path).isFile();
|
|
@@ -106,11 +49,7 @@ function isRealDir(path) {
|
|
|
106
49
|
}
|
|
107
50
|
}
|
|
108
51
|
|
|
109
|
-
|
|
110
|
-
* Every real file under `dir`, relative to it. Returns null when the walk hits
|
|
111
|
-
* a link, an unreadable entry, or either cap — "cannot enumerate" and "too big
|
|
112
|
-
* to compare" both mean "do not claim this is replicable".
|
|
113
|
-
*/
|
|
52
|
+
// null on a link, an unreadable entry or either cap: none of them may be claimed replicable.
|
|
114
53
|
function walkFiles(dir, budget) {
|
|
115
54
|
const out = [];
|
|
116
55
|
const stack = [''];
|
|
@@ -125,9 +64,9 @@ function walkFiles(dir, budget) {
|
|
|
125
64
|
for (const e of entries) {
|
|
126
65
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
127
66
|
const childAbs = join(dir, childRel);
|
|
128
|
-
if (e.isSymbolicLink()) return null;
|
|
67
|
+
if (e.isSymbolicLink()) return null;
|
|
129
68
|
if (e.isDirectory()) { stack.push(childRel); continue; }
|
|
130
|
-
if (!e.isFile()) return null;
|
|
69
|
+
if (!e.isFile()) return null;
|
|
131
70
|
let size;
|
|
132
71
|
try { size = lstatSync(childAbs).size; } catch { return null; }
|
|
133
72
|
budget.files += 1;
|
|
@@ -140,14 +79,8 @@ function walkFiles(dir, budget) {
|
|
|
140
79
|
}
|
|
141
80
|
|
|
142
81
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* Files compare by SHA-256; directories compare by having the SAME set of
|
|
146
|
-
* files, each with an identical hash — a root copy that is merely a SUPERSET
|
|
147
|
-
* is not a replica of the worktree's, because the worktree's own extra files
|
|
148
|
-
* would still be lost.
|
|
149
|
-
*
|
|
150
|
-
* @returns {boolean} false on ANY uncertainty
|
|
82
|
+
* False on any uncertainty. A directory needs the SAME file set: a root superset is
|
|
83
|
+
* not a replica, because the worktree's own extra files would still be lost.
|
|
151
84
|
*/
|
|
152
85
|
export function isReplicatedInRoot(worktreePath, rootPath, rel) {
|
|
153
86
|
const src = join(worktreePath, rel);
|
|
@@ -178,19 +111,10 @@ export function isReplicatedInRoot(worktreePath, rootPath, rel) {
|
|
|
178
111
|
return true;
|
|
179
112
|
}
|
|
180
113
|
|
|
181
|
-
return false;
|
|
114
|
+
return false;
|
|
182
115
|
}
|
|
183
116
|
|
|
184
|
-
|
|
185
|
-
* Split gitignored worktree paths into the three buckets above.
|
|
186
|
-
*
|
|
187
|
-
* ORDER IS DELIBERATE: disposable is checked FIRST, so a `bin/` that happens to
|
|
188
|
-
* be byte-identical to the root's is discarded rather than hashed, and a
|
|
189
|
-
* `node_modules` never enters the comparison budget at all.
|
|
190
|
-
*
|
|
191
|
-
* @param {{ worktreePath: string, rootPath: string, paths: string[] }} args
|
|
192
|
-
* @returns {{ replicable: string[], disposable: string[], exclusive: string[] }}
|
|
193
|
-
*/
|
|
117
|
+
// Disposable is checked first so `node_modules` never enters the hashing budget.
|
|
194
118
|
export function triageIgnoredPaths({ worktreePath, rootPath, paths }) {
|
|
195
119
|
const replicable = [];
|
|
196
120
|
const disposable = [];
|
|
@@ -63,9 +63,8 @@ function snippetFor(wanted, present) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Read-only
|
|
67
|
-
*
|
|
68
|
-
* commas or a tracked file's cleanliness.
|
|
66
|
+
* Read-only. `write` carries the full new text; `suggest` carries the snippet to paste, because
|
|
67
|
+
* rewriting would drop comments, trailing commas or a tracked file's cleanliness.
|
|
69
68
|
*/
|
|
70
69
|
export function planEditorSettings(root, { config = readProjectConfig(root), env = process.env } = {}) {
|
|
71
70
|
const file = join(root, SETTINGS_REL);
|