@polymorphism-tech/morph-spec 8.29.0 → 8.32.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.
Files changed (183) hide show
  1. package/CLAUDE.md +11 -1
  2. package/README.md +13 -2
  3. package/bin/morph-spec.js +67 -1
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +4 -1
  9. package/framework/MORPH.md +4 -3
  10. package/framework/agents.json +6 -6
  11. package/framework/commands/morph-status.md +1 -1
  12. package/framework/docs/CLI.md +68 -45
  13. package/framework/evals/rubrics/code-quality.md +17 -0
  14. package/framework/evals/rubrics/condensed.md +3 -1
  15. package/framework/hooks/claude-code/statusline.sh +11 -11
  16. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  17. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  18. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  19. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  20. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  21. package/framework/hooks/shared/phase-utils.js +2 -0
  22. package/framework/hooks/shared/stdin-reader.js +26 -26
  23. package/framework/mcp/registry.json +39 -4
  24. package/framework/rubrics/backend.json +2 -2
  25. package/framework/rubrics/default.json +2 -2
  26. package/framework/rubrics/frontend.json +2 -2
  27. package/framework/schemas/tasks.schema.json +25 -0
  28. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  29. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  30. package/framework/skills/core/morph-apply/SKILL.md +14 -4
  31. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  32. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  33. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  34. package/framework/skills/core/morph-implement/SKILL.md +12 -2
  35. package/framework/skills/core/morph-plan/SKILL.md +45 -3
  36. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  37. package/framework/skills/core/morph-review/SKILL.md +12 -4
  38. package/framework/skills/core/morph-stack-scan/SKILL.md +13 -1
  39. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  40. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  41. package/framework/standards/ai-agents/agent-session.md +160 -160
  42. package/framework/standards/ai-agents/batching.md +193 -193
  43. package/framework/standards/ai-agents/context-providers.md +188 -188
  44. package/framework/standards/ai-agents/mcp-server.md +250 -250
  45. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  46. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  47. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  48. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  49. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  50. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  51. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  52. package/framework/standards/ai-agents/production.md +231 -231
  53. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  54. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  55. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  56. package/framework/standards/ai-agents/structured-output.md +249 -249
  57. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  58. package/framework/templates/.idea/morph-templates.xml +92 -0
  59. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  60. package/framework/templates/IDE-SNIPPETS.md +262 -262
  61. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  62. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  63. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  64. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  65. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  66. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  67. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  68. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  69. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  70. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  71. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  72. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  73. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  74. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  75. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  76. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  77. package/framework/templates/code/typescript/contracts.ts +167 -167
  78. package/framework/templates/context/ARCHITECTURE.md +20 -0
  79. package/framework/templates/docs/clarifications.md +253 -253
  80. package/framework/templates/docs/schema-analysis.md +119 -119
  81. package/framework/templates/docs/ui-components.md +52 -52
  82. package/framework/templates/docs/ui-design-system.md +76 -76
  83. package/framework/templates/docs/ui-flows.md +167 -167
  84. package/framework/templates/docs/ui-mockups.md +98 -98
  85. package/framework/templates/feature/decisions.md +122 -122
  86. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  87. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  88. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  89. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  90. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  91. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  92. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  93. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  94. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  95. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  96. package/framework/templates/infrastructure/github/README.md +56 -56
  97. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  98. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  99. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  100. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  101. package/framework/templates/integrations/asaas-client.cs +387 -387
  102. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  103. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  104. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  105. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  106. package/framework/templates/project/validate.js +120 -120
  107. package/framework/templates/state.template.json +17 -17
  108. package/framework/templates/ui/design-system.css +226 -226
  109. package/package.json +1 -1
  110. package/src/commands/dashboard/dashboard.js +53 -53
  111. package/src/commands/graph/affected.js +89 -0
  112. package/src/commands/graph/build.js +94 -0
  113. package/src/commands/graph/explain.js +96 -0
  114. package/src/commands/graph/status.js +93 -0
  115. package/src/commands/index.js +13 -13
  116. package/src/commands/project/branch.js +170 -0
  117. package/src/commands/project/checkpoint.js +209 -209
  118. package/src/commands/project/doctor.js +148 -9
  119. package/src/commands/project/fleet.js +10 -3
  120. package/src/commands/project/worktree.js +73 -50
  121. package/src/commands/state/finish.js +41 -26
  122. package/src/commands/state/list.js +79 -6
  123. package/src/core/paths/output-schema.js +7 -0
  124. package/src/core/state/lifecycle-audit.js +127 -0
  125. package/src/core/state/phase-state-machine.js +197 -197
  126. package/src/core/templates/template-renderer.js +477 -477
  127. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  128. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  129. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  130. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  131. package/src/dashboard/app/main.jsx +13 -13
  132. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  133. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  134. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  135. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  136. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  137. package/src/dashboard/app/pages/Standards.jsx +332 -332
  138. package/src/dashboard/app/styles/index.css +47 -47
  139. package/src/dashboard/index.html +12 -12
  140. package/src/dashboard/package.json +24 -24
  141. package/src/dashboard/scanner/phases-scanner.js +15 -15
  142. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  143. package/src/dashboard/scanner/rules-scanner.js +37 -37
  144. package/src/dashboard/scanner/skills-scanner.js +43 -43
  145. package/src/dashboard/scanner/squads-scanner.js +13 -13
  146. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  147. package/src/dashboard/vite.config.js +24 -24
  148. package/src/lib/detectors/design-system-detector.js +188 -188
  149. package/src/lib/detectors/index.js +217 -217
  150. package/src/lib/generators/architecture-scanner.js +45 -4
  151. package/src/lib/generators/design-system-generator.js +508 -508
  152. package/src/lib/generators/settings-generator.js +209 -209
  153. package/src/lib/git/branch-classifier.js +177 -0
  154. package/src/lib/git/gh.js +30 -0
  155. package/src/lib/git/pending-prs.js +89 -0
  156. package/src/lib/git/pr-index.js +70 -0
  157. package/src/lib/graph/god-nodes.js +75 -0
  158. package/src/lib/graph/graph-health.js +61 -0
  159. package/src/lib/graph/graph-loader.js +86 -0
  160. package/src/lib/graph/graphify-runtime.js +100 -0
  161. package/src/lib/graph/graphifyignore.js +86 -0
  162. package/src/lib/graph/sanitize.js +280 -0
  163. package/src/lib/hooks/hook-executor.js +167 -167
  164. package/src/lib/installers/mcp-installer.js +719 -719
  165. package/src/lib/tasks/task-classifier.js +30 -30
  166. package/src/lib/tasks/task-parser.js +209 -209
  167. package/src/lib/tasks/tasks-json-parser.js +26 -0
  168. package/src/lib/validators/checkpoint-persistence.js +39 -39
  169. package/src/lib/validators/css/css-validator.js +340 -340
  170. package/src/lib/validators/reuse/index.js +10 -0
  171. package/src/lib/validators/reuse/reuse-validator.js +324 -0
  172. package/src/lib/validators/reuse/symbol-scanner.js +144 -0
  173. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  174. package/src/lib/validators/shared/issue-counter.js +14 -14
  175. package/src/lib/validators/validation-runner.js +40 -1
  176. package/src/lib/worktree/fleet-scanner.js +51 -21
  177. package/src/lib/worktree/ignored-triage.js +197 -0
  178. package/src/lib/worktree/worktree-audit.js +200 -35
  179. package/src/scripts/setup-infra.js +60 -1
  180. package/src/utils/agent-id-mapper.js +11 -11
  181. package/src/utils/claude-settings-manager.js +300 -300
  182. package/src/utils/file-copier.js +40 -0
  183. package/src/utils/skills-installer.js +187 -187
package/CLAUDE.md CHANGED
@@ -37,7 +37,7 @@ No build step — pure ESM JavaScript, runs directly via Node.js 18+.
37
37
  - **`src/commands/`** — CLI command handlers organized by domain (project/, state/, validation/, scaffold/, mcp/, context/, dashboard/).
38
38
  - **`src/core/`** — Core domain logic:
39
39
  - `state/state-manager.js` — Atomic state I/O with process-ID temp files
40
- - `paths/output-schema.js` — **Single source of truth** for the 13 v5 output types
40
+ - `paths/output-schema.js` — **Single source of truth** for the 14 v5 output types
41
41
  - `templates/` — Handlebars template registry and renderer
42
42
  - `workflows/` — Workflow type detector
43
43
  - **`src/lib/`** — Libraries and subsystems:
@@ -114,7 +114,17 @@ When modifying framework components, verify the full scope of related files.
114
114
  | `framework/templates/**` | REGISTRY.json (template registered), valid Handlebars helpers |
115
115
  | `src/core/paths/output-schema.js`| Run `npm run generate` to update phase-utils.js |
116
116
  | `framework/MORPH.md` | All core skills (cross-references), CLAUDE.md (slash command list) |
117
+ | `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish.test.js` que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
118
+ | `src/lib/git/pr-index.js` | os QUATRO leitores do mesmo fato "esta branch tem PR": `fleet-scanner.js` (coluna PR), `branch-classifier.js` (o que NÃO apagar), `worktree-audit.js` (o que NÃO sugerir remover) e `pending-prs.js` (a síntese "aguardando merge"). `null` é UNKNOWN, nunca "sem PR" |
119
+ | `src/lib/git/branch-classifier.js` | `src/commands/project/branch.js`, `worktree-audit.js` (`unintegratedWork` reusa `classifyBranch` — o `doctor` e o `branch prune` não podem discordar sobre a mesma branch), `test/commands/branch-prune.test.js`, e `lifecycle-audit.js` (`abandoned-scaffold` conta commit próprio pelo mesmo `resolveBaseRef` + `commitsAhead`) |
120
+ | `src/lib/git/pending-prs.js` | os TRÊS consumidores da mesma síntese "aguardando merge": `list.js` (linha sintetizada), `fleet-scanner.js` (entrada `pr-pending`) e `doctor.js` (`auditPullRequests`). Uma feature visível numa superfície e invisível na outra é o bug que este módulo existe para impedir |
121
+ | `src/lib/git/gh.js` | `pr-index.js` e `finish.js` — as DUAS formas de invocar o `gh` viraram uma; uma segunda cópia de `resolveGh` volta a tornar o caminho de sucesso do índice intestável |
117
122
  | `src/lib/worktree/feature-truth.js` | The four leaders reading the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, and the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`) |
123
+ | `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b |
124
+ | `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `framework/rubrics/*.json` (o id `reuse` mapeado numa categoria, senão o achado não pesa no score), `test/fixtures/graph/prospectpro-reuse.graph.json` (recorte do grafo real — regerar com `build-reuse-fixture.mjs`, nunca editar à mão) |
125
+ | o ciclo de reuso (`reuse-map.md`) | `output-schema.js` (+ `npm run generate`), `framework/schemas/tasks.schema.json` + `tasks-json-parser.js` (campo `reuse`), `morph-plan` §1c/§5b, template de dispatch de `morph-implement`/`morph-apply`, personas de `agents.json`, `evals/rubrics/code-quality.md` + `condensed.md`, `morph-review` (insumo do avaliador), `test/framework/graph-fallback.test.js` |
126
+ | `framework/mcp/registry.json` | mcp-installer.js, claude-config-detector.js, agents-installer.js (personas' `mcp` field), `scripts/validate-framework.js` Check 8, `test/framework/mcp-registry.test.js` |
127
+ | Any `framework/` file that calls `morph-spec graph` | The fallback to grep must be declared in the SAME file — `test/framework/graph-fallback.test.js` enforces it (ADR-002: a skill that requires the graph with no fallback is a bug) |
118
128
 
119
129
  ### Test Coverage
120
130
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 8.29.0
6
+ **Version:** 8.32.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -203,6 +203,17 @@ morph-spec worktree list [--json] # List active feature worktrees with ph
203
203
  # Close a worktree feature with `morph-spec finish {feature} --pr|--merge` (see Feature workflow) — it absorbed `worktree finish`.
204
204
  ```
205
205
 
206
+ **Codebase graph** — optional, fail-open. `init` installs [graphify](https://pypi.org/project/graphifyy/) (pinned 0.9.50) when a Python toolchain is available and writes a stack-aware `.graphifyignore`; without one, it records the fact and moves on. Everything degrades to grep.
207
+
208
+ ```bash
209
+ morph-spec graph build # Extract the graph (local AST, zero tokens)
210
+ morph-spec graph status # Size, freshness, sanitizer health
211
+ morph-spec graph affected {symbol} --uses-only # Who actually uses it (not who implements it)
212
+ morph-spec graph explain {symbol} # Neighbourhood, cited file:line
213
+ ```
214
+
215
+ The gain is **precision, not recall**: the graph finds nothing grep would miss, but it drops 58% of the noise and tells you *what kind* of relationship each hit is — the difference between "20 types implement this interface" and "2 actually use it". The graph is never consumed raw: a sanitizing layer fixes the C# namespace-scope resolution gap that would otherwise report **zero** consumers where two exist.
216
+
206
217
  ---
207
218
 
208
219
  ## Agent model
@@ -368,4 +379,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
368
379
 
369
380
  ---
370
381
 
371
- *morph-spec v8.29.0 by [Polymorphism Tech](https://polymorphism.tech)*
382
+ *morph-spec v8.32.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -24,6 +24,7 @@ import { updateCommand } from '../src/commands/project/update.js';
24
24
  import { doctorCommand } from '../src/commands/project/doctor.js';
25
25
  import { statusCommand } from '../src/commands/project/status.js';
26
26
  import { worktreeSetupCommand, worktreeLinkCommand, worktreeListCommand, worktreeRemoveCommand } from '../src/commands/project/worktree.js';
27
+ import { branchPruneCommand } from '../src/commands/project/branch.js';
27
28
  import { fleetCommand } from '../src/commands/project/fleet.js';
28
29
  import { claimCommand } from '../src/commands/project/claim.js';
29
30
  import { standardsCommand } from '../src/commands/project/standards.js';
@@ -47,6 +48,12 @@ import { usecaseReserveCommand } from '../src/commands/state/usecase.js';
47
48
  // === Tasks ===
48
49
  import { dagCommand } from '../src/commands/tasks/dag.js';
49
50
 
51
+ // === Codebase graph ===
52
+ import { graphStatusCommand } from '../src/commands/graph/status.js';
53
+ import { graphBuildCommand } from '../src/commands/graph/build.js';
54
+ import { graphAffectedCommand } from '../src/commands/graph/affected.js';
55
+ import { graphExplainCommand } from '../src/commands/graph/explain.js';
56
+
50
57
  // === Validation ===
51
58
  import { validateCommand } from './validate.js';
52
59
  import { validateFeatureCommand } from '../src/commands/validation/validate-feature.js';
@@ -285,6 +292,48 @@ program
285
292
  .option('--mermaid', 'Emit the tasks.json DAG as a Mermaid flowchart (flowchart TD)')
286
293
  .action((feature, options) => dagCommand(feature, options));
287
294
 
295
+ // ─── Codebase graph ───────────────────────────────────────────────────────
296
+ const graphCommand = program
297
+ .command('graph')
298
+ .description('Grafo de codebase (graphify) com saneamento determinístico — fail-open: sem grafo, tudo segue por grep');
299
+
300
+ graphCommand
301
+ .command('status')
302
+ .description('Existência, tamanho, frescor e saúde do saneamento. Grafo ausente não é erro (exit 0)')
303
+ .option('-p, --path <path>', 'Target path (default: current directory)')
304
+ .option('--json', 'Output as JSON')
305
+ .action((options) => graphStatusCommand(options));
306
+
307
+ graphCommand
308
+ .command('build')
309
+ .description('Extração completa (graphify extract --code-only); gera o .graphifyignore stack-aware antes')
310
+ .option('-p, --path <path>', 'Target path (default: current directory)')
311
+ .option('--json', 'Output as JSON')
312
+ .action((options) => graphBuildCommand(options));
313
+
314
+ graphCommand
315
+ .command('refresh')
316
+ .description('Atualização incremental (graphify update) — 2 a 3x mais rápida que o build')
317
+ .option('-p, --path <path>', 'Target path (default: current directory)')
318
+ .option('--json', 'Output as JSON')
319
+ .action((options) => graphBuildCommand({ ...options, refresh: true }));
320
+
321
+ graphCommand
322
+ .command('affected <symbol>')
323
+ .description('Blast radius saneado: quem depende do símbolo. Em frontend responde "quem importa", não "quem chama"')
324
+ .option('-p, --path <path>', 'Target path (default: current directory)')
325
+ .option('--prod', 'Descarta consumidores em projeto de teste')
326
+ .option('--uses-only', 'Só uso real: exclui inherits/implements/extends')
327
+ .option('--json', 'Output as JSON')
328
+ .action((symbol, options) => graphAffectedCommand(symbol, options));
329
+
330
+ graphCommand
331
+ .command('explain <symbol>')
332
+ .description('Vizinhança do símbolo com citação arquivo:linha — preferido a query, que trunca por budget')
333
+ .option('-p, --path <path>', 'Target path (default: current directory)')
334
+ .option('--json', 'Output as JSON')
335
+ .action((symbol, options) => graphExplainCommand(symbol, options));
336
+
288
337
  // ─── Validation ───────────────────────────────────────────────────────────
289
338
  program
290
339
  .command('validate [validator]')
@@ -424,7 +473,7 @@ worktreeCommand
424
473
  .command('remove [feature]')
425
474
  .description('Safely tear down a worktree (drops infra junctions first, PROVE-or-DECLINE) — does NOT close the feature; that is `finish`')
426
475
  .option('--target <path>', 'Remove the worktree at this path (for worktrees with no feature registration)')
427
- .option('--discard-ignored', 'Allow removing even though it holds gitignored files that exist nowhere else (e.g. .env) — they are destroyed')
476
+ .option('--discard-ignored', 'Skip the backup: gitignored files unique to the worktree are deleted instead of copied to .git/morph-spec/finish-backup (by default they are backed up and the removal proceeds anyway)')
428
477
  .option('--delete-branch', 'Also delete the branch (default: kept — unintegrated commits live there)')
429
478
  .action((feature, options) => worktreeRemoveCommand(feature, options));
430
479
 
@@ -434,6 +483,23 @@ worktreeCommand
434
483
  .option('--json', 'Output as JSON')
435
484
  .action((options) => worktreeListCommand(options));
436
485
 
486
+ // ─── Branch housekeeping ──────────────────────────────────────────────────
487
+ // `git branch --merged` cannot see a squash-merge, so the only command left for
488
+ // a squash-merged branch is `git branch -D` — indistinguishable from discarding
489
+ // real work. `prune` makes the distinction itself and deletes only what it
490
+ // proved is already integrated.
491
+ const branchCommand = program
492
+ .command('branch')
493
+ .description('Local branch housekeeping');
494
+
495
+ branchCommand
496
+ .command('prune')
497
+ .description('Delete local branches whose work is already in the default branch — including squash-merged ones git cannot see. Never touches a branch with an OPEN PR, one in use by a worktree, or one whose integration could not be proven')
498
+ .option('--remote', 'Also delete origin/<branch> for each pruned branch (default: remote branches are left alone)')
499
+ .option('--dry-run', 'Classify and report without deleting anything')
500
+ .option('--json', 'Output as JSON')
501
+ .action((options) => branchPruneCommand(options));
502
+
437
503
  // ─── Fleet ────────────────────────────────────────────────────────────────
438
504
  program
439
505
  .command('fleet')