@polymorphism-tech/morph-spec 8.46.0 → 9.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.
Files changed (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -74,8 +74,7 @@ function resolveGitCommonDir(cwd) {
74
74
  * treating those as different paths would make every stack look foreign.
75
75
  *
76
76
  * Exported as the ONE copy of that rule: `doctor.js` compares a declared test
77
- * node `cwd` with a discovered `package.json` folder through it (#87 F5), and
78
- * `test-scope.js` takes only the CASE of a canonical path through it (#87 N3).
77
+ * node `cwd` with a discovered `package.json` folder through it.
79
78
  */
80
79
  export const samePathCase = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
81
80
  const norm = (p) => samePathCase(resolve(String(p)).replace(/\\/g, '/'));
@@ -6,7 +6,7 @@
6
6
  * operator loses track of "what is each session doing, what is waiting on me".
7
7
  * This scanner answers that from the primary root's vantage point: one entry
8
8
  * per active feature, worktree or root-resident, with the operational facts
9
- * (phase, pending gates, task counts, port block, handoff freshness, last
9
+ * (phase, pending gates, slice counts, port block, handoff freshness, last
10
10
  * commit, dirtiness) resolved per entry.
11
11
  *
12
12
  * Read-only and fail-open per FIELD: a corrupt feature.json or a git hiccup
@@ -24,52 +24,39 @@ import {
24
24
  getFeature,
25
25
  derivePhaseForFeature,
26
26
  derivePhaseFrom,
27
+ fileSetArtifactProbe,
27
28
  getPendingGates,
28
29
  pendingGatesFrom,
29
30
  } from '../../../framework/hooks/shared/state-reader.js';
30
31
  import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
31
32
  import { buildPrIndex } from '../git/pr-index.js';
32
33
  import { pendingPrFeatures } from '../git/pending-prs.js';
34
+ import { sliceProgress, sliceProgressFrom } from '../../../framework/hooks/shared/slice-progress.js';
33
35
 
34
- function countFrom(raw) {
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
- }
36
+ const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
39
37
 
40
38
  /**
41
- * Task progress for one feature.
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.
39
+ * Slice progress for one feature, read from the copy that holds the work: a
40
+ * root-resident feature's plan checks and reports are committed on its own
41
+ * `morph/{f}` branch, while the root working tree only holds the checked-out
42
+ * branch's (pre-work) copy.
51
43
  *
52
44
  * @param {string} featureDir - the feature dir inside `path`
53
45
  * @param {{ root: string, feature: string, featureBranch: string|null }} ctx
54
46
  */
55
- function taskCounts(featureDir, { root, feature, featureBranch } = {}) {
47
+ function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
56
48
  if (featureBranch && root && feature) {
57
- try {
58
- const raw = execFileSync(
59
- 'git',
60
- ['show', `${featureBranch}:.morph/features/${feature}/2-plan/tasks.json`],
61
- { cwd: root, stdio: 'pipe' },
62
- ).toString();
63
- const counted = countFrom(raw);
64
- if (counted) return counted;
65
- } catch { /* no such branch/blob — fall through to the working tree */ }
66
- }
67
-
68
- try {
69
- return countFrom(readFileSync(join(featureDir, '2-plan', 'tasks.json'), 'utf8'));
70
- } catch {
71
- return null;
49
+ const show = (rel) => {
50
+ try {
51
+ return execFileSync('git', ['show', `${featureBranch}:.morph/features/${feature}/${rel}`], { cwd: root, stdio: 'pipe' }).toString();
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+ const onBranch = sliceProgressFrom(show('2-plan/checks.json'), (id) => show(`3-implement/reports/${id}.md`));
57
+ if (onBranch) return counts(onBranch);
72
58
  }
59
+ return counts(sliceProgress(featureDir));
73
60
  }
74
61
 
75
62
  function handoffInfo(featureDir) {
@@ -164,15 +151,15 @@ function buildRemote(feature, branch, heads, prs) {
164
151
  function buildEntry(feature, { location, path, branch, root, allocations, blockSize, heads, prs, claims }) {
165
152
  let workType = null;
166
153
  let status = 'unknown';
167
- let phase = 'proposal';
154
+ let phase = 'plan';
168
155
  let pendingGates = [];
169
156
 
170
- // Resolved once and shared with taskCounts — see branchToReadFrom.
157
+ // Resolved once and shared with sliceCounts — see branchToReadFrom.
171
158
  const featureBranch = branchToReadFrom(root, feature, location, branch);
172
159
 
173
160
  try {
174
161
  // 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
162
+ // progress. They read `.morph/features/{f}/` through the working tree, so a
176
163
  // root parked on the default reported the PRE-WORK phase for a feature
177
164
  // whose branch was already at implement — and re-asked for a gate the
178
165
  // feature branch had already approved.
@@ -180,7 +167,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
180
167
  if (view) {
181
168
  workType = view.fj?.workType || null;
182
169
  status = view.fj?.status || 'unknown';
183
- phase = derivePhaseFrom(view.fj, (rel) => view.files.has(rel));
170
+ phase = derivePhaseFrom(view.fj, fileSetArtifactProbe(view.files));
184
171
  pendingGates = pendingGatesFrom(view.fj);
185
172
  } else {
186
173
  const fj = getFeature(feature, path);
@@ -202,7 +189,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
202
189
  location,
203
190
  path,
204
191
  branch,
205
- tasks: taskCounts(featureDir, { root, feature, featureBranch }),
192
+ slices: sliceCounts(featureDir, { root, feature, featureBranch }),
206
193
  env: alloc && Number.isInteger(alloc.base)
207
194
  ? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
208
195
  : null,
@@ -316,7 +303,7 @@ export function scanFleet(cwd = process.cwd()) {
316
303
  location: 'pr-pending',
317
304
  path: null,
318
305
  branch: p.branch,
319
- tasks: null,
306
+ slices: null,
320
307
  env: null,
321
308
  handoff: { exists: false, generatedAt: null },
322
309
  lastCommit: null,
@@ -13,7 +13,7 @@
13
13
  import { join, dirname, resolve } from 'path';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { execSync } from 'child_process';
16
- import { readFileSync, readdirSync } from 'fs';
16
+ import { readFileSync, readdirSync, existsSync } from 'fs';
17
17
  import {
18
18
  copyDirectory,
19
19
  copyFile,
@@ -32,6 +32,7 @@ import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-set
32
32
  import { installSkills } from '../utils/skills-installer.js';
33
33
  import { installAgents, installDomainAgents } from '../utils/agents-installer.js';
34
34
  import { resolvePrimaryRoot } from '../commands/project/worktree.js';
35
+ import { findOldFormatFeatures, oldFormatGuardLines } from '../lib/specs/old-format.js';
35
36
  import { reconcileLegacyTemplatesCopy } from '../lib/installers/templates-reconcile.js';
36
37
  import { realEffects } from '../lib/installers/effects.js';
37
38
 
@@ -89,7 +90,27 @@ export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = real
89
90
  }
90
91
 
91
92
  /**
92
- * Re-install only stack-aware assets (rules, skills, agents) using current config.json.
93
+ * Slash commands filtered by `stacks:` frontmatter. Clean-slate like the
94
+ * skills: removes only a framework command that the stack filtered out, never
95
+ * a command the user wrote into `.claude/commands/`.
96
+ */
97
+ export function installCommandsForStack(commandsSrc, commandsDest, projectTags, fx = realEffects()) {
98
+ const sourceFiles = readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
99
+ let installed = 0;
100
+ for (const file of sourceFiles) {
101
+ const dest = join(commandsDest, file);
102
+ if (shouldInstall(parseStacks(readFileSync(join(commandsSrc, file), 'utf-8')), projectTags)) {
103
+ fx.copyFile(join(commandsSrc, file), dest);
104
+ installed++;
105
+ } else if (!fx.dryRun && existsSync(dest)) {
106
+ fx.remove(dest);
107
+ }
108
+ }
109
+ return { installed };
110
+ }
111
+
112
+ /**
113
+ * Re-install only stack-aware assets (commands, rules, skills, agents) using current config.json.
93
114
  * Skips infrastructure steps (dirs, framework copy, hooks).
94
115
  *
95
116
  * @param {string} targetPath - Absolute path to the target project directory
@@ -109,8 +130,16 @@ async function installStackAwareAssets(targetPath, log) {
109
130
  } catch { /* ignore malformed config */ }
110
131
  }
111
132
 
112
- log('Installing stack-filtered rules...');
113
133
  const claudeDest = join(targetPath, '.claude');
134
+ log('Installing stack-filtered commands...');
135
+ const commandsSrc = join(FRAMEWORK_DIR, 'commands');
136
+ const commandsDest = join(claudeDest, 'commands');
137
+ if (await pathExists(commandsSrc)) {
138
+ await ensureDir(commandsDest);
139
+ installCommandsForStack(commandsSrc, commandsDest, projectTags);
140
+ }
141
+
142
+ log('Installing stack-filtered rules...');
114
143
  const rulesSrc = join(FRAMEWORK_DIR, 'rules');
115
144
  const rulesDest = join(claudeDest, 'rules');
116
145
  if (await pathExists(rulesSrc)) {
@@ -213,6 +242,14 @@ export async function setupInfra(targetPath, {
213
242
  }
214
243
  }
215
244
 
245
+ // Same refusal as `update`, and neither `force` nor `reinstall` waives it:
246
+ // `init --force` and `setup-infra` overwrite `.morph/` without passing through it.
247
+ const oldFormat = findOldFormatFeatures(targetPath);
248
+ if (oldFormat.length) {
249
+ for (const line of oldFormatGuardLines(oldFormat, targetPath)) log(line);
250
+ return { refused: true, reason: 'old-format-features', features: oldFormat };
251
+ }
252
+
216
253
  // --- Reinstall mode: only re-install stack-aware assets ---
217
254
  if (reinstall) {
218
255
  log('setup-infra (reinstall): re-installing stack-aware assets...');
@@ -336,18 +373,8 @@ export async function setupInfra(targetPath, {
336
373
  await copyDirectory(hooksSrc, hooksDest);
337
374
  }
338
375
 
339
- // --- 7b. Copy framework/rubrics → .morph/framework/rubrics ---
340
- log('Step 7b: Copying rubrics...');
341
- const rubricsSrc = join(FRAMEWORK_DIR, 'rubrics');
342
- const rubricsDest = join(frameworkDestDir, 'rubrics');
343
- if (await pathExists(rubricsSrc)) {
344
- await copyDirectory(rubricsSrc, rubricsDest);
345
- }
346
-
347
376
  // --- 7c. Copy framework/evals → .morph/framework/evals ---
348
- // Rubricas .md (bandas 0-10) + trace-schema.md — referenciadas por
349
- // morph-review/agents.json via .morph/framework/evals/.
350
- log('Step 7c: Copying evals (rubrics .md + trace schema)...');
377
+ log('Step 7c: Copying evals (review checklist + trace schema)...');
351
378
  const evalsSrc = join(FRAMEWORK_DIR, 'evals');
352
379
  const evalsDest = join(frameworkDestDir, 'evals');
353
380
  if (await pathExists(evalsSrc)) {
@@ -396,16 +423,9 @@ export async function setupInfra(targetPath, {
396
423
  await copyDirectory(schemasSrc, schemasDest);
397
424
  }
398
425
 
399
- // --- 9. Copy framework/commands → .claude/commands ---
400
- log('Step 9: Setting up Claude Code integration...');
401
426
  const claudeDest = join(targetPath, '.claude');
402
- const commandsSrc = join(FRAMEWORK_DIR, 'commands');
403
- const commandsDest = join(claudeDest, 'commands');
404
- if (await pathExists(commandsSrc)) {
405
- await copyDirectory(commandsSrc, commandsDest);
406
- }
407
427
 
408
- // --- 10-13. Install stack-aware assets (rules, skills, agents) ---
428
+ // --- 9-13. Install stack-aware assets (commands, rules, skills, agents) ---
409
429
  log('Steps 10-13: Installing stack-aware assets...');
410
430
  const { agentCounts, domainCounts, projectTags } = await installStackAwareAssets(targetPath, log);
411
431
 
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * In v5 the harness has 8 personas (no tiers) across two axes — domain
17
17
  * (dotnet-senior, ef-modeler, maf-expert, nextjs-expert, ui-designer,
18
- * infra-engineer, evaluator) and stage (scout, read-only diagnosis before
18
+ * infra-engineer, verifier, reviewer, security) and stage (scout, read-only diagnosis before
19
19
  * implementation). Each persona is a subagent the Specialist (Claude in
20
20
  * implementer role) can dispatch. The 3 roles (Orchestrator/Specialist/
21
21
  * Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
@@ -294,7 +294,7 @@ export function loadKnownMcpIds(frameworkDir) {
294
294
  * with NO known stack tags (greenfield / install before config.json exists)
295
295
  * EVERY persona is generated — a project that dispatches `morph-{id}` must
296
296
  * find the subagent registered, and an install that only lands
297
- * morph-evaluator forces every domain dispatch into the general-purpose
297
+ * morph-verifier forces every domain dispatch into the general-purpose
298
298
  * fallback. Stack filtering only applies when the project's stack is KNOWN
299
299
  * (tags non-empty): the fallback stays reserved for stacks the project
300
300
  * genuinely does not have. Note this deliberately differs from rules/skills
@@ -431,10 +431,8 @@ function buildFrontmatter(agent, description, knownMcpIds, maxTurns) {
431
431
  // Canonical 1:1 naming: frontmatter name === filename === subagent_type.
432
432
  `name: morph-${agent.id}`,
433
433
  `description: ${description}`,
434
- // model is dimensioned per stage/criticality in agents.json (v5.2.0):
435
- // evaluator (quality multiplier) → opus, domain implementers → sonnet,
436
- // scout → sonnet (measured: same cost as haiku, fewer rejected maps). Falls back to `inherit` only for
437
- // personas that omit the field.
434
+ // The verifier is sonnet here; the dispatch routes it to opus when the risk
435
+ // detector flags the diff. A persona without the field inherits the session model.
438
436
  `model: ${agent.model ?? 'inherit'}`,
439
437
  `tools: ${buildToolsLine(agent, knownMcpIds)}`,
440
438
  // Per persona in agents.json (ADR-004); absent → DEFAULT_MAX_TURNS. The
@@ -455,6 +453,63 @@ function buildDescription(agent) {
455
453
  return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
456
454
  }
457
455
 
456
+ /**
457
+ * The fixed half of every implementer dispatch. The orchestrator paraphrases
458
+ * a prompt template in the field; the generated body is the only text a
459
+ * dispatch is guaranteed to carry, so the contract lives here and the
460
+ * dispatch carries only what changes per slice.
461
+ */
462
+ const NEON_CAUTION = 'NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável ' +
463
+ '(prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — ' +
464
+ 'nunca chame tool destrutiva sem confirmação humana explícita.';
465
+
466
+ const STANDARDS_HINT = 'Digest de cada um: `morph-spec standards --find <id>`; só a seção que importa: ' +
467
+ '`morph-spec standards --show <id>#<âncora>` (`--show <id>` lista as âncoras).';
468
+
469
+ export const DISPATCH_CONTRACT = [
470
+ '## Contrato de dispatch',
471
+ '',
472
+ 'O dispatch traz só o que muda por fatia: id e título, `cobre` com o texto de cada cenário, `arquivos`, `standards`, ' +
473
+ 'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
474
+ '',
475
+ '1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
476
+ 'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final. `SendMessage` não é substituto do relatório e não deve ser chamado.',
477
+ '2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json`, `state.json` nem no board de sessão — todo estado é gravado exclusivamente pelo orquestrador.',
478
+ '3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
479
+ 'um por pós-condição, um de regressão por bug, variantes numa tabela (`[Theory]`/`it.each`). NÃO rode teste a cada marco nem faça ciclo red→green: ' +
480
+ 'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório; se falhar, corrija e rode só os que falharam. ' +
481
+ 'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
482
+ 'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
483
+ '4. ESCOPO: uma fatia, ~4 arquivos (nunca mais de 8). Escopo maior falha e volta — fatie antes, não depois.',
484
+ '5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `wip(F{n})` a cada marco (um arquivo ou uma fatia coerente escrita), não só ao fim — ' +
485
+ 'e só os seus arquivos (`git add <paths>`, nunca `-A` nem diretório). Sem commit, a retomada recomeça do zero.',
486
+ '6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as sessões). Encerre dev server pelo PID que você mesmo subiu. ' +
487
+ 'Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano. Exit 3 é fila cheia depois da espera da própria fila: ' +
488
+ 'não repita nem espere em laço — diga no relatório o que não rodou.',
489
+ '7. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
490
+ '`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
491
+ 'Se criar mesmo assim, diga por que em uma linha no relatório.',
492
+ '8. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar (`sed`, `mv`, `git show HEAD:… >`, `git stash`, ' +
493
+ '`git checkout --`). A prova de que o teste morde é do verificador (Gate 3).',
494
+ '9. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash — heredoc, redirecionamento, `sed -i` ou script python/node que grava (`cat > X.cs <<EOF`, `sed -i … X.cs`): ' +
495
+ 'os guards de escrita só enxergam Write/Edit.',
496
+ '',
497
+ 'Formato do relatório (o `report-check` lê estas marcas):',
498
+ '',
499
+ '```',
500
+ '# F1 — <título>',
501
+ 'O que foi feito, em 2-3 frases: o comportamento entregue e onde ele mora, sem repetir a lista de arquivos.',
502
+ '**Arquivos:** `src/a.cs`, `src/a.test.cs`',
503
+ '**Checks:**',
504
+ '- C1 — provado: `a.test.cs` › nome do teste (ou `arquivo:linha`)',
505
+ '- C2 — não provado: por quê',
506
+ '```',
507
+ '',
508
+ '`**Arquivos:**` lista TODO arquivo criado ou modificado, inclusive artefato auxiliar fora do plano (factory, seed, config gerada): ' +
509
+ 'arquivo em produção sem dono é falha de protocolo que o Gate 3 aponta. Todo cenário que a fatia `cobre` aparece em `**Checks:**`. ' +
510
+ 'Problemas e como foram resolvidos vão em prosa depois.',
511
+ ];
512
+
458
513
  function buildBody(agent, maxTurns) {
459
514
  const lines = [
460
515
  `# ${agent.title ?? agent.id}`,
@@ -463,12 +518,20 @@ function buildBody(agent, maxTurns) {
463
518
  '',
464
519
  ];
465
520
 
466
- if (agent.domains?.length) {
521
+ // The 5.3.0 shape (no effort, no surfaces, implementer) renders byte-for-byte as before.
522
+ const legacyShape = !agent.effort && !agent.surfaces && isImplementer(agent);
523
+
524
+ if (legacyShape && agent.domains?.length) {
467
525
  lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
468
526
  }
469
527
 
528
+ if (!legacyShape && agent.mcp?.includes('neon')) {
529
+ lines.push(NEON_CAUTION, '');
530
+ }
531
+
470
532
  if (agent.standards?.length) {
471
533
  lines.push('## Standards to consult on demand');
534
+ if (!legacyShape) lines.push(STANDARDS_HINT);
472
535
  for (const s of agent.standards) lines.push(`- ${s}`);
473
536
  lines.push('');
474
537
  }
@@ -487,13 +550,29 @@ function buildBody(agent, maxTurns) {
487
550
  }
488
551
  }
489
552
 
553
+ if (agent.effort && isImplementer(agent)) {
554
+ lines.push(...DISPATCH_CONTRACT, '');
555
+ } else if (isImplementer(agent)) {
556
+ lines.push(
557
+ '## How you operate',
558
+ '',
559
+ 'You are a domain persona dispatched per task. Read the spec/tasks/standards passed to you, do the work, and report each doneCriteria with its evidence. Refer to MORPH.md section 6 for the dispatch contract.',
560
+ '',
561
+ );
562
+ } else {
563
+ // A read-only persona has no slice to implement nor scenario to report.
564
+ lines.push(
565
+ '## Como você opera',
566
+ '',
567
+ 'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
568
+ 'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final (no scout, o mapa).',
569
+ '',
570
+ );
571
+ }
572
+
490
573
  lines.push(
491
- '## How you operate',
492
- '',
493
- 'You are a domain persona dispatched per task. Read the spec/tasks/standards passed to you, do the work, and report each doneCriteria with its evidence. Refer to MORPH.md section 6 for the dispatch contract.',
494
- '',
495
574
  // `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps an
496
- // evaluator partial from carrying a Verdict the gate would read as final.
575
+ // review partial from carrying a Verdict the gate would read as final.
497
576
  '## Turn budget',
498
577
  '',
499
578
  // Pasted into EVERY dispatch of every persona — kept to the minimum that
@@ -517,7 +596,7 @@ function buildBody(agent, maxTurns) {
517
596
  'Use Read/Grep/Glob, never grep/sed/cat/find via Bash. Check a library API in the `context7` MCP before guessing ' +
518
597
  '(load a deferred `mcp__` tool with ToolSearch). Write the tests the budget asks for, then run only your own ' +
519
598
  'filter ONCE before the report — no red→green cycles, no `morph-spec verify` (the orchestrator proves it). ' +
520
- 'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 evaluator.',
599
+ 'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 verifier.',
521
600
  );
522
601
  }
523
602
 
@@ -24,41 +24,6 @@ export { installAgents } from './agents-installer.js';
24
24
  // Extended settings management
25
25
  // ============================================================================
26
26
 
27
- /**
28
- * Install permission rules into .claude/settings.local.json
29
- * @param {string} targetPath - Project root directory
30
- * @param {Array<string>} permissions - Permission patterns to add
31
- * @returns {Promise<Object>} Summary of changes
32
- */
33
- export async function installPermissions(targetPath, permissions) {
34
- const settings = await loadSettings(targetPath);
35
-
36
- settings.permissions = settings.permissions || [];
37
-
38
- const added = [];
39
- for (const perm of permissions) {
40
- if (!settings.permissions.includes(perm)) {
41
- settings.permissions.push(perm);
42
- added.push(perm);
43
- }
44
- }
45
-
46
- // Track in _morph_managed
47
- settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
48
- settings[MORPH_MANAGED_KEY].permissions_added =
49
- settings[MORPH_MANAGED_KEY].permissions_added || [];
50
- for (const perm of added) {
51
- if (!settings[MORPH_MANAGED_KEY].permissions_added.includes(perm)) {
52
- settings[MORPH_MANAGED_KEY].permissions_added.push(perm);
53
- }
54
- }
55
- settings[MORPH_MANAGED_KEY].generated_at = new Date().toISOString();
56
-
57
- await saveSettings(targetPath, settings);
58
-
59
- return { added, total: settings.permissions.length };
60
- }
61
-
62
27
  /**
63
28
  * Install environment variables into .claude/settings.local.json
64
29
  * @param {string} targetPath - Project root directory
@@ -156,7 +156,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
156
156
  '.morph/memory/',
157
157
  '.morph/plans/',
158
158
  '.morph/analytics/',
159
- '.morph/traces/',
160
159
  // Per-instance activity/session logs (activity-logger.js). Each worktree
161
160
  // writes its own — committing this guarantees a merge conflict between
162
161
  // feature branches.
@@ -199,10 +198,12 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
199
198
  content = await readFile(gitignorePath);
200
199
  hasMorphSection = content.includes('# MORPH-SPEC');
201
200
  }
201
+ const original = content;
202
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
202
203
 
203
204
  // If MORPH section already exists, check if rules are up to date
204
205
  if (hasMorphSection) {
205
- const lines = content.split('\n');
206
+ const lines = content.split(/\r?\n/);
206
207
  const morphStartIndex = lines.findIndex(line => line.trim() === '# MORPH-SPEC');
207
208
 
208
209
  // Find the end of MORPH section (next empty line or section header)
@@ -225,17 +226,44 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
225
226
  if (missingRules.length > 0) {
226
227
  // Update the section with all rules
227
228
  lines.splice(morphStartIndex, morphEndIndex - morphStartIndex, ...morphRules.slice(1, -1));
228
- content = lines.join('\n');
229
+ content = lines.join(eol);
229
230
  }
230
231
  } else {
231
232
  // Add MORPH section
232
233
  if (content && !content.endsWith('\n')) {
233
- content += '\n';
234
+ content += eol;
234
235
  }
235
- content += morphRules.join('\n');
236
+ content += morphRules.join(eol);
236
237
  }
237
238
 
238
- fx.writeFile(gitignorePath, content);
239
+ if (content !== original) fx.writeFile(gitignorePath, content);
240
+ }
241
+
242
+ const DOTNET_BUILD_DIRS = ['bin', 'obj'];
243
+
244
+ const ignoredDir = (line) => line.trim()
245
+ .replace(/^\/|^\*\*\//, '')
246
+ .replace(/\/(\*\*)?$/, '')
247
+ .replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
248
+ .toLowerCase();
249
+
250
+ /**
251
+ * Appends `bin/` and `obj/` to the project `.gitignore` when no line already
252
+ * ignores them. Never rewrites an existing line.
253
+ *
254
+ * @returns {string[]} the entries added
255
+ */
256
+ export function ensureDotnetBuildIgnores(projectPath) {
257
+ const gitignorePath = join(projectPath, '.gitignore');
258
+ const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
259
+ const present = new Set(content.split(/\r?\n/).filter((l) => !l.trim().startsWith('#')).map(ignoredDir));
260
+ const missing = DOTNET_BUILD_DIRS.filter((dir) => !present.has(dir)).map((dir) => `${dir}/`);
261
+ if (!missing.length) return [];
262
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
263
+ const lead = content && !content.endsWith('\n') ? eol : '';
264
+ const block = [...(content ? [''] : []), '# .NET build output', ...missing, ''].join(eol);
265
+ fs.writeFileSync(gitignorePath, content + lead + block);
266
+ return missing;
239
267
  }
240
268
 
241
269
  /**
@@ -55,8 +55,11 @@ import { realEffects } from '../lib/installers/effects.js';
55
55
  *
56
56
  * 3.12.0 adds heavy-gate to the Bash PreToolUse chain and heavy-release to the
57
57
  * PostToolUse and PostToolUseFailure chains: the opt-in `heavy.gateBash`.
58
+ *
59
+ * 3.13.0 adds warn-bash-source-write to the Bash PreToolUse chain: a warning,
60
+ * never a block, when a command writes project source through a redirect.
58
61
  */
59
- const HOOKS_VERSION = '3.12.0';
62
+ const HOOKS_VERSION = '3.13.0';
60
63
 
61
64
  /** Marker for old dispatch.js (v1) */
62
65
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -93,21 +96,24 @@ const MORPH_DENY = [
93
96
  *
94
97
  * Pre-authorizes operations the agent legitimately performs during the
95
98
  * morph-spec flow, so the user does not get permission prompts on every
96
- * proposal.md / spec.md / tasks.json write.
99
+ * plan.md / slice report write.
97
100
  *
98
101
  * `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
99
102
  * covers Write, and a `Write(path)` rule is inert noise.
100
103
  */
101
104
  const MORPH_ALLOW = [
102
- // Feature artifacts — the agent's working directory during proposal/plan/implement/review
105
+ // Feature artifacts — the agent's working directory during plan/uiux/implement/review
103
106
  'Edit(.morph/features/**)',
104
107
  // state.json — fallback path when state-sync hook can't cover a case
105
108
  'Edit(.morph/state.json)',
106
- // Long-lived architectural context — agent may reconcile during brainstorming
109
+ // Long-lived architectural context — agent may reconcile during planning
107
110
  'Edit(.morph/context/**)',
108
111
  // Folder creation under .morph
109
112
  'Bash(mkdir:.morph/**)',
110
113
  'Bash(ls:.morph/**)',
114
+ // Gate 3 mutation round: restores bytes itself and has --restore; an auto-mode deny pauses the gate
115
+ 'Bash(morph-spec mutate:*)',
116
+ 'Bash(npx morph-spec mutate:*)',
111
117
  ];
112
118
 
113
119
  // Backwards-compat alias used by older tests
@@ -168,6 +174,8 @@ const MORPH_HOOKS = [
168
174
  // on someone having read MORPH.md. block-raw-worktree-add is its way-IN
169
175
  // counterpart (a raw `git worktree add` builds a tree with no junctions, no
170
176
  // ports, no identity) — static, so it costs nothing after the first two.
177
+ // warn-bash-source-write only adds context (source written by redirect skips
178
+ // the Write/Edit guards); after the guards, so a blocked command is not warned.
171
179
  // heavy-gate comes after every guard — it takes a slot, and a command a guard
172
180
  // blocks must not hold one — and before the rewriter.
173
181
  // worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
@@ -179,7 +187,7 @@ const MORPH_HOOKS = [
179
187
  hooks: [{
180
188
  type: 'command',
181
189
  command: 'node framework/hooks/claude-code/core/chain.js',
182
- args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'heavy-gate', 'worktree-env-inject']
190
+ args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
183
191
  }]
184
192
  },
185
193
 
@@ -214,15 +222,14 @@ const MORPH_HOOKS = [
214
222
  }]
215
223
  },
216
224
 
217
- // === PostToolUse: Write|Edit — tasks.json schema guard, trace auto-generation,
218
- // comment-guard LAST: it blocks, and a block ends the chain. ===
225
+ // === PostToolUse: Write|Edit — comment-guard, solo: it blocks the edit that
226
+ // adds a forbidden comment. ===
219
227
  {
220
228
  event: 'PostToolUse',
221
229
  matcher: 'Write|Edit',
222
230
  hooks: [{
223
231
  type: 'command',
224
- command: 'node framework/hooks/claude-code/core/chain.js',
225
- args: ['tasks-json-guard', 'trace-autogen', 'comment-guard']
232
+ command: 'node framework/hooks/claude-code/core/comment-guard.js'
226
233
  }]
227
234
  },
228
235
 
@@ -3,8 +3,7 @@
3
3
  *
4
4
  * Copies framework skills to .claude/skills/ during morph-spec init.
5
5
  * Installs only the `core/` skill set — the canonical NLH skills referenced by
6
- * MORPH.md (morph-brainstorming, morph-stack-scan, morph-plan, morph-implement,
7
- * morph-review, morph-standards). The 3-tier system was collapsed
6
+ * MORPH.md (morph-plan, morph-uiux, morph-apply, morph-review, morph-standards…). The 3-tier system was collapsed
8
7
  * into a single source of truth in framework/skills/core/.
9
8
  *
10
9
  * Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md