@polymorphism-tech/morph-spec 9.0.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.
Files changed (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  4. package/bin/validate.js +1 -39
  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 +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,19 +1,3 @@
1
- /**
2
- * Architecture Scanner - Generates rich architectural context from project filesystem.
3
- *
4
- * Performs fast (<5s) glob-based scanning to produce a snapshot of:
5
- * - .NET entities (sealed class/record)
6
- * - API endpoints (IApiEndpoint implementations)
7
- * - Next.js pages and components
8
- * - Integrations (from appsettings.json)
9
- * - Database schema (from EF migrations)
10
- * - Stack dependencies (.csproj + package.json)
11
- *
12
- * All scanning is regex-based (no AST parsing) for speed.
13
- *
14
- * @module architecture-scanner
15
- */
16
-
17
1
  import fs from 'fs/promises';
18
2
  import path from 'path';
19
3
  import { fileURLToPath } from 'url';
@@ -25,16 +9,6 @@ import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
25
9
  export const SKIP_DIRS = new Set(['node_modules', 'bin', 'obj', '.git', '.morph', '.next', 'dist', 'out', ...WORKTREE_DIR_NAMES]);
26
10
  const MAX_FILES_PER_SCANNER = 200;
27
11
 
28
- /**
29
- * Recursively walk a directory, returning files matching a filter.
30
- * Skips known heavy directories for performance.
31
- *
32
- * @param {string} dir - Directory to walk
33
- * @param {function} filter - (relativePath) => boolean
34
- * @param {string} [base] - Base path for relative calculation
35
- * @param {number} [limit] - Max files to return
36
- * @returns {Promise<Array<{abs: string, rel: string}>>}
37
- */
38
12
  async function walkDir(dir, filter, base = dir, limit = MAX_FILES_PER_SCANNER) {
39
13
  const results = [];
40
14
 
@@ -68,12 +42,6 @@ async function walkDir(dir, filter, base = dir, limit = MAX_FILES_PER_SCANNER) {
68
42
  return results;
69
43
  }
70
44
 
71
- /**
72
- * Scan .NET entities from Entities/ or Features/ directories.
73
- *
74
- * @param {string} projectPath
75
- * @returns {Promise<Array<{name: string, type: string, path: string}>>}
76
- */
77
45
  export async function scanEntities(projectPath) {
78
46
  try {
79
47
  const files = await walkDir(
@@ -91,7 +59,6 @@ export async function scanEntities(projectPath) {
91
59
  entityRegex.lastIndex = 0;
92
60
  while ((match = entityRegex.exec(content)) !== null) {
93
61
  const line = content.slice(Math.max(0, match.index - 80), match.index + match[0].length);
94
- // Only capture sealed/record types or classes in entity-like directories
95
62
  if (/sealed|record/.test(line) || file.rel.includes('Entities/')) {
96
63
  entities.push({
97
64
  name: match[2],
@@ -100,7 +67,7 @@ export async function scanEntities(projectPath) {
100
67
  });
101
68
  }
102
69
  }
103
- } catch { /* skip unreadable files */ }
70
+ } catch {}
104
71
  }
105
72
 
106
73
  return entities;
@@ -109,12 +76,6 @@ export async function scanEntities(projectPath) {
109
76
  }
110
77
  }
111
78
 
112
- /**
113
- * Scan API endpoints implementing IApiEndpoint.
114
- *
115
- * @param {string} projectPath
116
- * @returns {Promise<Array<{name: string, route: string, method: string, path: string}>>}
117
- */
118
79
  export async function scanEndpoints(projectPath) {
119
80
  try {
120
81
  const files = await walkDir(
@@ -142,7 +103,7 @@ export async function scanEndpoints(projectPath) {
142
103
  method: methodMatch ? (methodMatch[1] || methodMatch[2] || '').toUpperCase() : '',
143
104
  path: file.rel
144
105
  });
145
- } catch { /* skip */ }
106
+ } catch {}
146
107
  }
147
108
 
148
109
  return endpoints;
@@ -151,17 +112,10 @@ export async function scanEndpoints(projectPath) {
151
112
  }
152
113
  }
153
114
 
154
- /**
155
- * Scan Next.js pages and components.
156
- *
157
- * @param {string} projectPath
158
- * @returns {Promise<{pages: Array<{route: string, path: string}>, layouts: Array<{path: string}>, components: Array<{name: string, path: string}>}>}
159
- */
160
115
  export async function scanNextjsComponents(projectPath) {
161
116
  try {
162
117
  const result = { pages: [], layouts: [], components: [] };
163
118
 
164
- // Scan pages and layouts from app directory
165
119
  const appFiles = await walkDir(
166
120
  projectPath,
167
121
  (rel, name) =>
@@ -181,7 +135,6 @@ export async function scanNextjsComponents(projectPath) {
181
135
  }
182
136
  }
183
137
 
184
- // Scan components
185
138
  const componentFiles = await walkDir(
186
139
  projectPath,
187
140
  (rel, name) =>
@@ -191,7 +144,6 @@ export async function scanNextjsComponents(projectPath) {
191
144
 
192
145
  for (const file of componentFiles) {
193
146
  const name = path.basename(file.rel, path.extname(file.rel));
194
- // Skip index files and test files
195
147
  if (name === 'index' || name.includes('.test') || name.includes('.spec')) continue;
196
148
  result.components.push({ name, path: file.rel });
197
149
  }
@@ -202,12 +154,6 @@ export async function scanNextjsComponents(projectPath) {
202
154
  }
203
155
  }
204
156
 
205
- /**
206
- * Scan integrations from appsettings*.json files.
207
- *
208
- * @param {string} projectPath
209
- * @returns {Promise<Array<{name: string, type: string, configured: boolean}>>}
210
- */
211
157
  export async function scanIntegrations(projectPath) {
212
158
  try {
213
159
  const files = await walkDir(
@@ -217,7 +163,6 @@ export async function scanIntegrations(projectPath) {
217
163
  20
218
164
  );
219
165
 
220
- // Also check .env files for common integrations
221
166
  const envFiles = await walkDir(
222
167
  projectPath,
223
168
  (rel, name) => name === '.env' || name === '.env.local' || name === '.env.production',
@@ -242,7 +187,6 @@ export async function scanIntegrations(projectPath) {
242
187
 
243
188
  const detected = new Map();
244
189
 
245
- // Scan appsettings files
246
190
  for (const file of files) {
247
191
  try {
248
192
  const content = JSON.parse(await fs.readFile(file.abs, 'utf8'));
@@ -256,7 +200,6 @@ export async function scanIntegrations(projectPath) {
256
200
  }
257
201
  }
258
202
  }
259
- // Check ConnectionStrings specifically
260
203
  if (content.ConnectionStrings) {
261
204
  for (const [key, val] of Object.entries(content.ConnectionStrings)) {
262
205
  if (val && !String(val).startsWith('{{')) {
@@ -264,10 +207,9 @@ export async function scanIntegrations(projectPath) {
264
207
  }
265
208
  }
266
209
  }
267
- } catch { /* skip invalid JSON */ }
210
+ } catch {}
268
211
  }
269
212
 
270
- // Scan .env files for known prefixes
271
213
  const envPrefixes = {
272
214
  'NEON_AUTH_': 'NeonAuth',
273
215
  'DATABASE_URL': 'Neon',
@@ -289,7 +231,7 @@ export async function scanIntegrations(projectPath) {
289
231
  }
290
232
  }
291
233
  }
292
- } catch { /* skip */ }
234
+ } catch {}
293
235
  }
294
236
 
295
237
  return Array.from(detected.values());
@@ -298,12 +240,6 @@ export async function scanIntegrations(projectPath) {
298
240
  }
299
241
  }
300
242
 
301
- /**
302
- * Scan database schema from EF Core migrations.
303
- *
304
- * @param {string} projectPath
305
- * @returns {Promise<Array<{table: string, migration: string}>>}
306
- */
307
243
  export async function scanDatabaseSchema(projectPath) {
308
244
  try {
309
245
  const files = await walkDir(
@@ -331,10 +267,9 @@ export async function scanDatabaseSchema(projectPath) {
331
267
  }
332
268
  }
333
269
  }
334
- } catch { /* skip */ }
270
+ } catch {}
335
271
  }
336
272
 
337
- // Also scan database migrations (SQL)
338
273
  const sqlFiles = await walkDir(
339
274
  projectPath,
340
275
  (rel, name) => name.endsWith('.sql') && rel.includes('migrations/'),
@@ -355,7 +290,7 @@ export async function scanDatabaseSchema(projectPath) {
355
290
  tables.set(match[1], { table: match[1], migration: migrationName });
356
291
  }
357
292
  }
358
- } catch { /* skip */ }
293
+ } catch {}
359
294
  }
360
295
 
361
296
  return Array.from(tables.values());
@@ -364,17 +299,10 @@ export async function scanDatabaseSchema(projectPath) {
364
299
  }
365
300
  }
366
301
 
367
- /**
368
- * Detect stack from .csproj PackageReference and package.json dependencies.
369
- *
370
- * @param {string} projectPath
371
- * @returns {Promise<{dotnet: Array<{name: string, version: string}>, npm: Array<{name: string, version: string}>}>}
372
- */
373
302
  export async function scanStackDependencies(projectPath) {
374
303
  try {
375
304
  const result = { dotnet: [], npm: [] };
376
305
 
377
- // Scan .csproj files
378
306
  const csprojFiles = await walkDir(
379
307
  projectPath,
380
308
  (rel, name) => name.endsWith('.csproj'),
@@ -392,10 +320,9 @@ export async function scanStackDependencies(projectPath) {
392
320
  while ((match = packageRefRegex.exec(content)) !== null) {
393
321
  result.dotnet.push({ name: match[1], version: match[2] });
394
322
  }
395
- } catch { /* skip */ }
323
+ } catch {}
396
324
  }
397
325
 
398
- // Scan package.json (root only)
399
326
  const pkgPath = path.join(projectPath, 'package.json');
400
327
  if (existsSync(pkgPath)) {
401
328
  try {
@@ -404,10 +331,9 @@ export async function scanStackDependencies(projectPath) {
404
331
  for (const [name, version] of Object.entries(allDeps)) {
405
332
  result.npm.push({ name, version: String(version) });
406
333
  }
407
- } catch { /* skip */ }
334
+ } catch {}
408
335
  }
409
336
 
410
- // Also check web/ or frontend/ subdirectory package.json
411
337
  for (const subdir of ['web', 'frontend', 'client', 'src']) {
412
338
  const subPkgPath = path.join(projectPath, subdir, 'package.json');
413
339
  if (existsSync(subPkgPath)) {
@@ -415,12 +341,11 @@ export async function scanStackDependencies(projectPath) {
415
341
  const pkg = JSON.parse(await fs.readFile(subPkgPath, 'utf8'));
416
342
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
417
343
  for (const [name, version] of Object.entries(allDeps)) {
418
- // Avoid duplicates
419
344
  if (!result.npm.some(d => d.name === name)) {
420
345
  result.npm.push({ name, version: String(version) });
421
346
  }
422
347
  }
423
- } catch { /* skip */ }
348
+ } catch {}
424
349
  }
425
350
  }
426
351
 
@@ -430,12 +355,6 @@ export async function scanStackDependencies(projectPath) {
430
355
  }
431
356
  }
432
357
 
433
- /**
434
- * Master scan — runs all scanners in parallel.
435
- *
436
- * @param {string} projectPath
437
- * @returns {Promise<Object>} ArchitectureScanResult
438
- */
439
358
  export async function scanArchitecture(projectPath) {
440
359
  const [entities, endpoints, nextjs, integrations, database, stack, graph] = await Promise.all([
441
360
  scanEntities(projectPath),
@@ -444,9 +363,7 @@ export async function scanArchitecture(projectPath) {
444
363
  scanIntegrations(projectPath),
445
364
  scanDatabaseSchema(projectPath),
446
365
  scanStackDependencies(projectPath),
447
- // Aditivo: sem grafo devolve listas vazias e nada muda no README. O
448
- // inventário acima continua sendo a espinha do arquivo — ele tem
449
- // exaustividade que o grafo não tem.
366
+ // Aditivo: sem grafo devolve listas vazias; o inventário acima tem a exaustividade que o grafo não tem.
450
367
  scanGraph(projectPath)
451
368
  ]);
452
369
 
@@ -462,20 +379,11 @@ export async function scanArchitecture(projectPath) {
462
379
  };
463
380
  }
464
381
 
465
- /**
466
- * Resolve a template path: check .morph/framework/templates/ first, then framework/templates/,
467
- * then fall back to the CLI package's own framework/templates/.
468
- *
469
- * @param {string} projectPath
470
- * @param {string} templateRelPath
471
- * @returns {string|null}
472
- */
382
+ // Project-local `.morph/framework/templates/` override first, then the CLI package's own.
473
383
  function resolveTemplatePath(projectPath, templateRelPath) {
474
- // 1. Project-local override
475
384
  const projectLocal = path.join(projectPath, '.morph/framework/templates', templateRelPath);
476
385
  if (existsSync(projectLocal)) return projectLocal;
477
386
 
478
- // 2. CLI package framework/ (for running from source or during init)
479
387
  const cliFramework = path.join(
480
388
  cliRootDir(), 'framework', 'templates', templateRelPath
481
389
  );
@@ -484,103 +392,64 @@ function resolveTemplatePath(projectPath, templateRelPath) {
484
392
  return null;
485
393
  }
486
394
 
487
- /**
488
- * The CLI package root, as a real filesystem path.
489
- *
490
- * fileURLToPath, not `new URL(...).pathname`: the latter leaves the path
491
- * percent-encoded, so an install under a directory with a space resolved to
492
- * ".../Program%20Files/..." — a path that never exists. `resolveTemplatePath`
493
- * then returned null and the caller fell through to its plain-text fallback
494
- * WITHOUT any warning. The first lines of that fallback are identical to the
495
- * template's, which is why nobody noticed the tables were gone. On Windows a
496
- * global npm install lives under a spaced path by default, so this was the
497
- * common case, not the edge one.
498
- */
395
+ // fileURLToPath, not `new URL(...).pathname`: the latter stays percent-encoded, so a path with a space
396
+ // (the default global npm install on Windows) never resolves and the template is silently dropped.
499
397
  function cliRootDir() {
500
398
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
501
399
  }
502
400
 
503
- /**
504
- * Render architecture context to .morph/context/README.md
505
- *
506
- * QUEM LÊ ESTE ARQUIVO, E QUANDO: o passo 1c da skill `morph-plan` (mapa de
507
- * reuso), antes de decidir REUSAR/ESTENDER/CRIAR cada conceito da feature. É a
508
- * granularidade de CONCEITO — entidades, endpoints com rota, componentes com
509
- * path — que o grafo de codebase, que casa por símbolo, não dá; e é o único
510
- * inventário legível num projeto sem grafo (sem Python), antes do grep.
511
- *
512
- * Até 2026-08-27 este arquivo era gerado e NUNCA lido por skill nenhuma
513
- * (docs/specs/code-reuse-gap/analysis.md §2.5). Se um dia o consumidor acima
514
- * desaparecer do `morph-plan`, este gerador volta a queimar tempo por nada —
515
- * a decisão então é remover os dois, não deixar o arquivo órfão de novo.
516
- *
517
- * @param {string} projectPath
518
- * @param {Object} scanResult - Result from scanArchitecture()
519
- * @param {Object} [options]
520
- * @param {string} [options.projectName] - Override project name
521
- * @returns {Promise<string>} Path to written file
522
- */
401
+ // Read by `morph-plan` for the concept-level inventory the graph doesn't give. If that reader goes away,
402
+ // remove this generator too.
523
403
  export async function renderArchitectureContext(projectPath, scanResult, options = {}) {
524
404
  const templatePath = resolveTemplatePath(projectPath, 'context/ARCHITECTURE.md');
525
405
 
526
- // Determine project name
527
406
  let projectName = options.projectName;
528
407
  if (!projectName) {
529
- // Try config.json
530
408
  const configPath = path.join(projectPath, '.morph/config/config.json');
531
409
  if (existsSync(configPath)) {
532
410
  try {
533
411
  const config = JSON.parse(readFileSync(configPath, 'utf8'));
534
412
  projectName = config.project?.name;
535
- } catch { /* ignore */ }
413
+ } catch {}
536
414
  }
537
- // Fallback to directory name
538
415
  if (!projectName) {
539
416
  projectName = path.basename(projectPath);
540
417
  }
541
418
  }
542
419
 
543
- // Get morph-spec version
544
420
  let morphVersion = 'unknown';
545
421
  try {
546
422
  const pkgPath = path.join(cliRootDir(), 'package.json');
547
423
  if (existsSync(pkgPath)) {
548
424
  morphVersion = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
549
425
  }
550
- } catch { /* ignore */ }
426
+ } catch {}
551
427
 
552
428
  const variables = {
553
429
  projectName,
554
430
  timestamp: new Date().toISOString().replace('T', ' ').split('.')[0],
555
431
  morphVersion,
556
- // Entities
557
432
  hasDotnet: scanResult.entities.length > 0 || scanResult.endpoints.length > 0,
558
433
  entities: scanResult.entities,
559
434
  entityCount: scanResult.entities.length,
560
435
  endpoints: scanResult.endpoints,
561
436
  endpointCount: scanResult.endpoints.length,
562
- // Next.js
563
437
  hasNextjs: scanResult.nextjs.pages.length > 0 || scanResult.nextjs.components.length > 0,
564
438
  pages: scanResult.nextjs.pages,
565
439
  pageCount: scanResult.nextjs.pages.length,
566
440
  layouts: scanResult.nextjs.layouts,
567
441
  components: scanResult.nextjs.components,
568
442
  componentCount: scanResult.nextjs.components.length,
569
- // Integrations
570
443
  hasIntegrations: scanResult.integrations.length > 0,
571
444
  integrations: scanResult.integrations,
572
- // Database
573
445
  hasTables: scanResult.database.length > 0,
574
446
  tables: scanResult.database,
575
447
  tableCount: scanResult.database.length,
576
- // Stack
577
448
  hasDotnetPackages: scanResult.stack.dotnet.length > 0,
578
449
  dotnetPackages: scanResult.stack.dotnet,
579
450
  hasNpmPackages: scanResult.stack.npm.length > 0,
580
451
  npmPackages: scanResult.stack.npm,
581
- // Centralidade (grafo de codebase) — ACRESCENTA, nunca substitui.
582
- // Comunidade ainda com rótulo Community_N fica de fora: sem
583
- // `graphify label` ela é ruído puro (441 páginas assim no eval).
452
+ // Comunidade ainda rotulada Community_N fica de fora: sem `graphify label` é ruído puro.
584
453
  hasGraph: scanResult.graph?.available === true && (scanResult.graph.godNodes?.length ?? 0) > 0,
585
454
  godNodes: scanResult.graph?.godNodes ?? [],
586
455
  communities: (scanResult.graph?.communities ?? []).filter((c) => c.labeled),
@@ -591,11 +460,9 @@ export async function renderArchitectureContext(projectPath, scanResult, options
591
460
  if (templatePath) {
592
461
  content = renderTemplate(templatePath, variables);
593
462
  } else {
594
- // Fallback: generate minimal markdown without template
595
463
  content = generateFallbackMarkdown(variables);
596
464
  }
597
465
 
598
- // Write to .morph/context/README.md
599
466
  const contextDir = path.join(projectPath, '.morph/context');
600
467
  await fs.mkdir(contextDir, { recursive: true });
601
468
  const outputPath = path.join(contextDir, 'README.md');
@@ -604,11 +471,6 @@ export async function renderArchitectureContext(projectPath, scanResult, options
604
471
  return outputPath;
605
472
  }
606
473
 
607
- /**
608
- * Generate minimal markdown when template is not available.
609
- * @param {Object} vars
610
- * @returns {string}
611
- */
612
474
  function generateFallbackMarkdown(vars) {
613
475
  const lines = [`# ${vars.projectName} — Architecture Context\n`];
614
476
  lines.push(`> Auto-generated by morph-spec context refresh`);
@@ -1,20 +1,5 @@
1
- /**
2
- * handoff-generator.js — compressed session-handoff document (fleet).
3
- *
4
- * A fleet runs one Claude Code instance per feature worktree. What travels
5
- * between sessions must be a HANDOFF, not an export: a compact document that
6
- * points at the feature's own files instead of copying their contents, so a
7
- * fresh instance starts focused instead of inheriting a wall of stale context.
8
- *
9
- * Deterministic by construction — everything here is derived from feature.json,
10
- * the extracted plan checks, the slice reports and the worktree env manifest. The document is
11
- * therefore REGENERABLE (never hand-edited, never merged): `morph-spec handoff`
12
- * rebuilds it, and setup/create/advance/approve regenerate it at the moments a
13
- * new session is most likely to pick the feature up.
14
- *
15
- * Every section is fail-open: missing or corrupt plan checks degrade that
16
- * section, never the document.
17
- */
1
+ // Derived only from committed sources, so it is regenerated, never hand-edited or merged.
2
+ // Every section is fail-open.
18
3
 
19
4
  import { existsSync, readFileSync, writeFileSync } from 'fs';
20
5
  import { join } from 'path';
@@ -22,7 +7,6 @@ import { ensureCommonExclude } from '../../../framework/hooks/shared/git-exclude
22
7
  import { derivePhaseForFeature, getPendingGates } from '../../../framework/hooks/shared/state-reader.js';
23
8
  import { sliceProgress, readySlices } from '../../../framework/hooks/shared/slice-progress.js';
24
9
 
25
- /** Ordered pointer candidates — only existing paths make it into the doc. */
26
10
  const POINTER_CANDIDATES = [
27
11
  ['2-plan/plan.md', 'plano: problema, cenários com prova, decisões e fatias'],
28
12
  ['2-plan/checks.json', 'cenários e fatias extraídos pelo plan-check'],
@@ -68,7 +52,7 @@ function nextAction(feature, phase, slices, featureName) {
68
52
 
69
53
  switch (phase) {
70
54
  case 'plan':
71
- return `Escreva \`2-plan/plan.md\`, rode \`morph-spec plan-check ${featureName}\` até zero erro e aprove o gate do plano (\`morph-spec advance ${featureName}\`). Skill: /morph-proposal ${featureName}.`;
55
+ return `Escreva \`2-plan/plan.md\`, rode \`morph-spec plan-check ${featureName}\` até zero erro e aprove o gate do plano (\`morph-spec advance ${featureName}\`). Skill: /morph-plan ${featureName}.`;
72
56
  case 'uiux':
73
57
  return approved('uiux')
74
58
  ? `Gate de uiux aprovado — implemente as fatias do plano via /morph-apply ${featureName}.`
@@ -88,20 +72,8 @@ function nextAction(feature, phase, slices, featureName) {
88
72
  }
89
73
  }
90
74
 
91
- /**
92
- * Build the handoff document for a feature, resolved against `cwd` (primary
93
- * root or worktree — wherever the feature's `.morph/features/{f}` lives).
94
- *
95
- * @param {string} featureName
96
- * @param {string} [cwd]
97
- * @param {{ worktree?: {path:string, branch:string}|null }} [opts] - override
98
- * the `worktree` bookkeeping instead of reading `feature.worktree`. Needed
99
- * because that field is ROOT-OWNED and stripped from every branch-local
100
- * copy at birth (create.js) — a handoff built FROM the worktree's own copy
101
- * would otherwise always say "no worktree registered", even while sitting
102
- * inside one. Pass `undefined` (the default) to fall back to `feature.worktree`.
103
- * @returns {{ markdown: string, meta: { generatedAt: string, phase: string, slicesDone: number, slicesTotal: number } } | null}
104
- */
75
+ // `opts.worktree` overrides `feature.worktree`: that field is root-owned and stripped from branch-local
76
+ // copies, so a handoff built inside a worktree would otherwise say "no worktree registered".
105
77
  export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
106
78
  const featureDir = join(cwd, '.morph', 'features', featureName);
107
79
  if (!existsSync(join(featureDir, 'feature.json'))) return null;
@@ -118,7 +90,7 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
118
90
  try {
119
91
  phase = derivePhaseForFeature(featureName, cwd) || 'plan';
120
92
  pendingGates = getPendingGates(featureName, cwd) || [];
121
- } catch { /* fail-open — plan defaults keep the doc useful */ }
93
+ } catch {}
122
94
 
123
95
  const generatedAt = new Date().toISOString();
124
96
  const slices = sliceProgress(featureDir);
@@ -193,29 +165,13 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
193
165
  const HANDOFF_EXCLUDE_MARKER = '# morph-spec: derived handoff docs (managed — see handoff-generator)';
194
166
  const HANDOFF_EXCLUDE_PATTERN = '/.morph/**/handoff.md';
195
167
 
196
- /**
197
- * The handoff is DERIVED from committed sources, so it is never committed
198
- * itself — regenerate > synchronize, the same principle as the materialized
199
- * state.json. Left visible, every `worktree setup`/`advance` would drop an
200
- * untracked file into the very `git status` the finish gates read. The
201
- * common-dir `.git/info/exclude` hides it in every worktree of the repo
202
- * (including the primary root); `**` also covers a feature folder after
203
- * `archive` moves it. Idempotent and fail-open — a handoff that shows up as
204
- * untracked is noise, never data loss.
205
- */
168
+ // Never committed: left visible it lands in the `git status` the finish gates read. The common-dir
169
+ // exclude hides it in every worktree; `**` also covers a feature folder after `archive` moves it.
206
170
  function ensureHandoffExcluded(cwd) {
207
171
  ensureCommonExclude(cwd, [HANDOFF_EXCLUDE_PATTERN], HANDOFF_EXCLUDE_MARKER);
208
172
  }
209
173
 
210
- /**
211
- * Write `.morph/features/{f}/handoff.md`. Returns the written path, or null
212
- * when the feature does not exist (nothing is created).
213
- *
214
- * @param {string} featureName
215
- * @param {string} [cwd]
216
- * @param {{ worktree?: {path:string, branch:string}|null }} [opts] - see buildHandoff
217
- * @returns {string|null}
218
- */
174
+ /** The written path, or null when the feature does not exist (nothing is created). */
219
175
  export function writeHandoff(featureName, cwd = process.cwd(), opts = {}) {
220
176
  const result = buildHandoff(featureName, cwd, opts);
221
177
  if (!result) return null;