@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  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 +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,15 +1,3 @@
1
- /**
2
- * Template Registry - Discovery and metadata API for all framework templates.
3
- *
4
- * Provides programmatic access to REGISTRY.json for:
5
- * - Template discovery by category, phase, stack
6
- * - Template metadata lookup
7
- * - Path resolution with fallback logic
8
- * - Validation of template existence
9
- *
10
- * @module template-registry
11
- */
12
-
13
1
  import { readFileSync, existsSync } from 'fs';
14
2
  import { join, dirname } from 'path';
15
3
  import { fileURLToPath } from 'url';
@@ -17,21 +5,10 @@ import { fileURLToPath } from 'url';
17
5
  const __filename = fileURLToPath(import.meta.url);
18
6
  const __dirname = dirname(__filename);
19
7
 
20
- // Parsed registries cached by resolved REGISTRY.json path. Keyed on the path —
21
- // NOT a single global slot — so a process that loads the framework registry
22
- // and a project's registry gets each one correctly, instead of whichever
23
- // loaded first.
8
+ // Keyed by REGISTRY.json path, so the framework and a project registry never shadow each other.
24
9
  const _registryCache = new Map();
25
10
 
26
- /**
27
- * Load the template registry from REGISTRY.json.
28
- *
29
- * @param {string} [projectPath] - Optional project path (defaults to framework root)
30
- * @returns {Object} Parsed registry object
31
- * @throws {Error} If registry file not found or invalid JSON
32
- */
33
11
  export function loadTemplateRegistry(projectPath = null) {
34
- // Resolve the path first — it is the cache key.
35
12
  const registryPath = projectPath
36
13
  ? join(projectPath, 'framework/templates/REGISTRY.json')
37
14
  : join(__dirname, '../../../framework/templates/REGISTRY.json');
@@ -55,92 +32,39 @@ export function loadTemplateRegistry(projectPath = null) {
55
32
  }
56
33
  }
57
34
 
58
- /**
59
- * Clear all cached registries (useful for testing).
60
- */
61
35
  export function clearRegistryCache() {
62
36
  _registryCache.clear();
63
37
  }
64
38
 
65
- /**
66
- * Get all templates from registry.
67
- *
68
- * @param {string} [projectPath] - Optional project path
69
- * @returns {Array} Array of template objects
70
- */
71
39
  export function getAllTemplates(projectPath = null) {
72
40
  const registry = loadTemplateRegistry(projectPath);
73
41
  return registry.templates || [];
74
42
  }
75
43
 
76
- /**
77
- * Get template by ID.
78
- *
79
- * @param {string} templateId - Template identifier (e.g., 'plan')
80
- * @param {string} [projectPath] - Optional project path
81
- * @returns {Object|null} Template object or null if not found
82
- */
83
44
  export function getTemplateById(templateId, projectPath = null) {
84
45
  const templates = getAllTemplates(projectPath);
85
46
  return templates.find(t => t.id === templateId) || null;
86
47
  }
87
48
 
88
- /**
89
- * Get templates by category.
90
- *
91
- * @param {string} category - Category ('documentation', 'code', 'infrastructure', 'context', 'examples')
92
- * @param {string} [projectPath] - Optional project path
93
- * @returns {Array} Filtered template array
94
- */
95
49
  export function getTemplatesByCategory(category, projectPath = null) {
96
50
  const templates = getAllTemplates(projectPath);
97
51
  return templates.filter(t => t.category === category);
98
52
  }
99
53
 
100
- /**
101
- * Get templates by phase.
102
- *
103
- * @param {string} phase - Phase name (e.g., 'plan', 'uiux', 'implement')
104
- * @param {string} [projectPath] - Optional project path
105
- * @returns {Array} Filtered template array
106
- */
107
- export function getTemplatesByPhase(phase, projectPath = null) {
108
- const templates = getAllTemplates(projectPath);
109
- return templates.filter(t => t.phase === phase);
110
- }
111
-
112
- /**
113
- * Get templates by stack.
114
- *
115
- * @param {string} stackId - Stack identifier ('blazor-azure', 'nextjs-neon')
116
- * @param {string} [projectPath] - Optional project path
117
- * @returns {Array} Filtered template array (includes universal + stack-specific)
118
- */
119
54
  export function getTemplatesByStack(stackId, projectPath = null) {
120
55
  const templates = getAllTemplates(projectPath);
121
56
  return templates.filter(t => {
122
- // Include universal templates (stackSpecific: false)
123
57
  if (!t.stackSpecific) return true;
124
58
 
125
- // Include stack-specific if matches
126
59
  return t.stacks && t.stacks.includes(stackId);
127
60
  });
128
61
  }
129
62
 
130
- /**
131
- * Get required templates only.
132
- *
133
- * @param {string} [stackId] - Optional stack filter
134
- * @param {string} [projectPath] - Optional project path
135
- * @returns {Array} Filtered template array
136
- */
137
63
  export function getRequiredTemplates(stackId = null, projectPath = null) {
138
64
  let templates = getAllTemplates(projectPath);
139
65
 
140
- // Filter by required flag
141
66
  templates = templates.filter(t => t.required === true);
142
67
 
143
- // Optional: filter by stack
144
68
  if (stackId) {
145
69
  templates = templates.filter(t => {
146
70
  if (!t.stackSpecific) return true;
@@ -151,13 +75,6 @@ export function getRequiredTemplates(stackId = null, projectPath = null) {
151
75
  return templates;
152
76
  }
153
77
 
154
- /**
155
- * Search templates by keyword (searches id, name, description).
156
- *
157
- * @param {string} keyword - Search term
158
- * @param {string} [projectPath] - Optional project path
159
- * @returns {Array} Matching templates
160
- */
161
78
  export function searchTemplates(keyword, projectPath = null) {
162
79
  const templates = getAllTemplates(projectPath);
163
80
  const lowerKeyword = keyword.toLowerCase();
@@ -171,21 +88,9 @@ export function searchTemplates(keyword, projectPath = null) {
171
88
  });
172
89
  }
173
90
 
174
- /** Absolute path of this repo's own framework/templates/ directory. */
175
91
  const FRAMEWORK_TEMPLATES_DIR = join(__dirname, '../../../framework/templates');
176
92
 
177
- /**
178
- * Resolve a template id to its absolute path inside THIS framework repo's
179
- * `framework/templates/` directory — for generators running against the
180
- * framework itself (scaffold-generator, context-generator), not against an
181
- * installed `.morph/`. Use architecture-scanner.js's `resolveTemplatePath()`
182
- * when a project root and its project-local overrides are in play.
183
- *
184
- * @param {string} templateId - Template identifier (REGISTRY.json `id`)
185
- * @returns {string} Absolute path to the template file
186
- * @throws {Error} If the id is not in REGISTRY.json or the file is missing —
187
- * a missing template is a framework bug, not a recoverable condition.
188
- */
93
+ /** Path inside this package's `framework/templates/`, not a project's `.morph/`; throws when missing. */
189
94
  export function resolveFrameworkTemplatePath(templateId) {
190
95
  const template = getTemplateById(templateId);
191
96
  if (!template) {
@@ -1,22 +1,7 @@
1
- /**
2
- * Template Renderer - Shared Handlebars-based template engine.
3
- *
4
- * Provides unified template rendering with custom helpers for:
5
- * - Case transformations (pascalCase, camelCase, snakeCase, etc.)
6
- * - Conditional logic (eq, ne, and, or, not)
7
- * - Loops (each, unless)
8
- *
9
- * @module template-renderer
10
- */
11
-
12
1
  import Handlebars from 'handlebars';
13
2
  import { readFileSync, existsSync } from 'fs';
14
3
 
15
- /**
16
- * Register all custom Handlebars helpers
17
- */
18
4
  function registerHelpers() {
19
- // Case transformation helpers
20
5
  Handlebars.registerHelper('pascalCase', function (str) {
21
6
  if (typeof str !== 'string') return '';
22
7
  return str
@@ -60,7 +45,6 @@ function registerHelpers() {
60
45
  return str.toLowerCase().replace(/_/g, '-');
61
46
  });
62
47
 
63
- // Comparison helpers
64
48
  Handlebars.registerHelper('eq', function (a, b) {
65
49
  return a === b;
66
50
  });
@@ -85,14 +69,14 @@ function registerHelpers() {
85
69
  return a >= b;
86
70
  });
87
71
 
88
- // Logical helpers
89
72
  Handlebars.registerHelper('and', function (...args) {
90
- // Remove Handlebars options object (last argument)
73
+ // Handlebars passes its options object as the last argument.
91
74
  const values = args.slice(0, -1);
92
75
  return values.every(Boolean);
93
76
  });
94
77
 
95
78
  Handlebars.registerHelper('or', function (...args) {
79
+ // Handlebars passes its options object as the last argument.
96
80
  const values = args.slice(0, -1);
97
81
  return values.some(Boolean);
98
82
  });
@@ -101,7 +85,6 @@ function registerHelpers() {
101
85
  return !value;
102
86
  });
103
87
 
104
- // String helpers
105
88
  Handlebars.registerHelper('uppercase', function (str) {
106
89
  return typeof str === 'string' ? str.toUpperCase() : '';
107
90
  });
@@ -115,7 +98,6 @@ function registerHelpers() {
115
98
  return str.charAt(0).toUpperCase() + str.slice(1);
116
99
  });
117
100
 
118
- // Utility helpers
119
101
  Handlebars.registerHelper('default', function (value, defaultValue) {
120
102
  return value != null && value !== '' ? value : defaultValue;
121
103
  });
@@ -133,31 +115,30 @@ function registerHelpers() {
133
115
  } else if (format === 'time') {
134
116
  return date.toTimeString().split(' ')[0];
135
117
  }
136
- return date.toISOString().split('T')[0]; // Default: YYYY-MM-DD
118
+ return date.toISOString().split('T')[0];
137
119
  });
138
120
 
139
121
  Handlebars.registerHelper('year', function () {
140
122
  return new Date().getFullYear();
141
123
  });
142
124
 
143
- // Date formatting helpers
144
125
  Handlebars.registerHelper('formatDate', function (date, format) {
145
126
  if (!date) return '';
146
127
 
147
128
  const d = new Date(date);
148
- if (isNaN(d.getTime())) return date; // Return original if invalid
129
+ if (isNaN(d.getTime())) return date;
149
130
 
150
131
  switch (format) {
151
132
  case 'iso':
152
133
  return d.toISOString();
153
134
  case 'date':
154
- return d.toISOString().split('T')[0]; // YYYY-MM-DD
135
+ return d.toISOString().split('T')[0];
155
136
  case 'time':
156
- return d.toTimeString().split(' ')[0]; // HH:MM:SS
137
+ return d.toTimeString().split(' ')[0];
157
138
  case 'datetime':
158
- return d.toISOString().replace('T', ' ').split('.')[0]; // YYYY-MM-DD HH:MM:SS
139
+ return d.toISOString().replace('T', ' ').split('.')[0];
159
140
  case 'short':
160
- return d.toLocaleDateString(); // Locale-specific short date
141
+ return d.toLocaleDateString();
161
142
  case 'long':
162
143
  return d.toLocaleDateString('en-US', {
163
144
  weekday: 'long',
@@ -166,15 +147,13 @@ function registerHelpers() {
166
147
  day: 'numeric'
167
148
  });
168
149
  default:
169
- return d.toISOString().split('T')[0]; // Default: YYYY-MM-DD
150
+ return d.toISOString().split('T')[0];
170
151
  }
171
152
  });
172
153
 
173
- // Pluralization helpers (basic English rules)
174
154
  Handlebars.registerHelper('pluralize', function (str) {
175
155
  if (typeof str !== 'string') return '';
176
156
 
177
- // Common irregular plurals
178
157
  const irregulars = {
179
158
  'person': 'people',
180
159
  'child': 'children',
@@ -191,7 +170,6 @@ function registerHelpers() {
191
170
  : irregulars[lower];
192
171
  }
193
172
 
194
- // Regular pluralization rules
195
173
  if (str.endsWith('s') || str.endsWith('x') || str.endsWith('z') ||
196
174
  str.endsWith('ch') || str.endsWith('sh')) {
197
175
  return str + 'es';
@@ -209,7 +187,6 @@ function registerHelpers() {
209
187
  Handlebars.registerHelper('singularize', function (str) {
210
188
  if (typeof str !== 'string') return '';
211
189
 
212
- // Common irregular singulars
213
190
  const irregulars = {
214
191
  'people': 'person',
215
192
  'children': 'child',
@@ -226,7 +203,6 @@ function registerHelpers() {
226
203
  : irregulars[lower];
227
204
  }
228
205
 
229
- // Regular singularization rules
230
206
  if (str.endsWith('ies')) {
231
207
  return str.slice(0, -3) + 'y';
232
208
  } else if (str.endsWith('ves')) {
@@ -240,7 +216,6 @@ function registerHelpers() {
240
216
  }
241
217
  });
242
218
 
243
- // String manipulation helpers
244
219
  Handlebars.registerHelper('trim', function (str) {
245
220
  return typeof str === 'string' ? str.trim() : '';
246
221
  });
@@ -251,7 +226,7 @@ function registerHelpers() {
251
226
  });
252
227
 
253
228
  Handlebars.registerHelper('concat', function (...args) {
254
- // Remove Handlebars options object (last argument)
229
+ // Handlebars passes its options object as the last argument.
255
230
  const strings = args.slice(0, -1);
256
231
  return strings.join('');
257
232
  });
@@ -283,7 +258,6 @@ function registerHelpers() {
283
258
  return 0;
284
259
  });
285
260
 
286
- // Math helpers
287
261
  Handlebars.registerHelper('add', function (a, b) {
288
262
  return Number(a) + Number(b);
289
263
  });
@@ -309,7 +283,6 @@ function registerHelpers() {
309
283
  return Math.round(Number(num) * multiplier) / multiplier;
310
284
  });
311
285
 
312
- // Array helpers
313
286
  Handlebars.registerHelper('join', function (array, separator = ', ') {
314
287
  if (!Array.isArray(array)) return '';
315
288
  return array.join(separator);
@@ -331,19 +304,8 @@ function registerHelpers() {
331
304
  });
332
305
  }
333
306
 
334
- // Auto-register helpers on module load
335
307
  registerHelpers();
336
308
 
337
- /**
338
- * Render a template file with Handlebars.
339
- *
340
- * @param {string} templatePath - Absolute path to template file
341
- * @param {Object} variables - Variables to pass to template context
342
- * @param {Object} [options] - Rendering options
343
- * @param {boolean} [options.noEscape] - Disable HTML escaping (default: true for markdown)
344
- * @returns {string} Rendered template content
345
- * @throws {Error} If template file doesn't exist or compilation fails
346
- */
347
309
  export function renderTemplate(templatePath, variables, options = {}) {
348
310
  if (!existsSync(templatePath)) {
349
311
  throw new Error(`Template file not found: ${templatePath}`);
@@ -352,28 +314,18 @@ export function renderTemplate(templatePath, variables, options = {}) {
352
314
  try {
353
315
  const templateContent = readFileSync(templatePath, 'utf-8');
354
316
 
355
- // Compile template
356
317
  const template = Handlebars.compile(templateContent, {
357
- noEscape: options.noEscape !== undefined ? options.noEscape : true, // Don't escape by default
358
- strict: false, // Allow undefined variables
359
- preventIndent: true, // Preserve indentation
318
+ noEscape: options.noEscape !== undefined ? options.noEscape : true,
319
+ strict: false,
320
+ preventIndent: true,
360
321
  });
361
322
 
362
- // Render with variables
363
323
  return template(variables);
364
324
  } catch (error) {
365
325
  throw new Error(`Failed to render template ${templatePath}: ${error.message}`);
366
326
  }
367
327
  }
368
328
 
369
- /**
370
- * Render a template string (not from file) with Handlebars.
371
- *
372
- * @param {string} templateString - Template content as string
373
- * @param {Object} variables - Variables to pass to template context
374
- * @param {Object} [options] - Rendering options
375
- * @returns {string} Rendered content
376
- */
377
329
  export function renderTemplateString(templateString, variables, options = {}) {
378
330
  try {
379
331
  const template = Handlebars.compile(templateString, {
@@ -388,12 +340,6 @@ export function renderTemplateString(templateString, variables, options = {}) {
388
340
  }
389
341
  }
390
342
 
391
- /**
392
- * Get list of placeholders used in a template file.
393
- *
394
- * @param {string} templatePath - Absolute path to template file
395
- * @returns {string[]} Array of placeholder names found
396
- */
397
343
  export function getTemplatePlaceholders(templatePath) {
398
344
  if (!existsSync(templatePath)) {
399
345
  throw new Error(`Template file not found: ${templatePath}`);
@@ -403,44 +349,26 @@ export function getTemplatePlaceholders(templatePath) {
403
349
  return getTemplatePlaceholdersFromString(templateContent);
404
350
  }
405
351
 
406
- /**
407
- * Get list of placeholders from a template string.
408
- *
409
- * @param {string} templateString - Template content
410
- * @returns {string[]} Array of placeholder names found
411
- */
412
352
  export function getTemplatePlaceholdersFromString(templateString) {
413
353
  const placeholders = new Set();
414
354
 
415
- // Match {{PLACEHOLDER}} and {{helper PLACEHOLDER}}
416
355
  const regex = /\{\{(?:#|\/)?\s*([A-Za-z_][A-Za-z0-9_]*)/g;
417
356
  let match;
418
357
 
419
358
  while ((match = regex.exec(templateString)) !== null) {
420
359
  const placeholder = match[1];
421
360
 
422
- // Skip Handlebars built-in helpers and custom helpers
423
361
  const skipHelpers = [
424
- // Built-in Handlebars
425
362
  'if', 'unless', 'each', 'with', 'lookup', 'log', 'else',
426
- // Comparison
427
363
  'eq', 'ne', 'lt', 'gt', 'lte', 'gte',
428
- // Logical
429
364
  'and', 'or', 'not',
430
- // Case transformations
431
365
  'pascalCase', 'camelCase', 'titleCase', 'upperSnakeCase', 'snakeCase', 'kebabCase',
432
- // String manipulation
433
366
  'uppercase', 'lowercase', 'capitalize', 'trim', 'replace', 'concat', 'substr',
434
367
  'startsWith', 'endsWith', 'contains', 'length',
435
- // Date/Time
436
368
  'formatDate', 'now', 'year',
437
- // Pluralization
438
369
  'pluralize', 'singularize',
439
- // Math
440
370
  'add', 'subtract', 'multiply', 'divide', 'mod', 'round',
441
- // Array
442
371
  'join', 'first', 'last', 'slice',
443
- // Utility
444
372
  'default', 'json'
445
373
  ];
446
374
 
@@ -452,14 +380,6 @@ export function getTemplatePlaceholdersFromString(templateString) {
452
380
  return Array.from(placeholders).sort();
453
381
  }
454
382
 
455
- /**
456
- * Validate that all required placeholders are provided in variables.
457
- *
458
- * @param {string} templatePath - Absolute path to template file
459
- * @param {Object} variables - Variables object
460
- * @param {string[]} [optionalPlaceholders] - Placeholders that are optional
461
- * @returns {{ valid: boolean, missing: string[] }} Validation result
462
- */
463
383
  export function validateTemplatePlaceholders(templatePath, variables, optionalPlaceholders = []) {
464
384
  const placeholders = getTemplatePlaceholders(templatePath);
465
385
  const providedKeys = Object.keys(variables);
@@ -9,7 +9,6 @@ export function templateSourcePath(templateId, cwd = process.cwd()) {
9
9
  return local && existsSync(local) ? local : resolveFrameworkTemplatePath(templateId);
10
10
  }
11
11
 
12
- /** @returns {{ path: string, written: boolean }} `written: false` when the file already existed. */
13
12
  export function writeIfAbsent(path, content) {
14
13
  mkdirSync(dirname(path), { recursive: true });
15
14
  try {
@@ -1,26 +1,3 @@
1
- /**
2
- * Work Types — Single Source of Truth (v8)
3
- *
4
- * Routes work by TYPE (feature/bug/chore/hotfix), each with a dimensioned
5
- * pipeline. `derivePhase` is already skip-tolerant via approved gates
6
- * (`gateImpliedPhase`), so "skipping phases" means stamping gates by policy
7
- * at birth (`approvedBy: "policy:{type}"` — see POLICY_APPROVER_PREFIX).
8
- *
9
- * Edit ONLY this file to add, rename, or retune a work type. Consumed by
10
- * `create`/`retype`/`risk-detector` in later tasks — this module is pure
11
- * and has no filesystem/CLI side effects.
12
- *
13
- * Fields per type:
14
- * label - Human-readable name
15
- * scaffold - Phase folders created at feature birth (subset of
16
- * 2-plan/1-design/3-implement/4-review)
17
- * skippedGates - Gates stamped `policy:{type}`-approved at birth
18
- * instead of waiting for human approval
19
- * forcedPauseGates - Gates that ALWAYS pause for a human, regardless of
20
- * trust mode or skippedGates (e.g. hotfix.review)
21
- * entrySkill - The skill that kicks off work of this type
22
- */
23
-
24
1
  export const DEFAULT_WORK_TYPE = 'feature';
25
2
  export const POLICY_APPROVER_PREFIX = 'policy:';
26
3
 
@@ -55,24 +32,12 @@ export const WORK_TYPES = {
55
32
  },
56
33
  };
57
34
 
58
- /**
59
- * @param {string} t - candidate work type key
60
- * @returns {boolean} true iff `t` is a known key of WORK_TYPES
61
- */
62
35
  export function isValidWorkType(t) {
63
36
  return Object.prototype.hasOwnProperty.call(WORK_TYPES, t);
64
37
  }
65
38
 
66
- /**
67
- * Resolve a feature's work-type config, defaulting to `feature` when the
68
- * feature has no `workType` or an unrecognized one.
69
- *
70
- * @param {object} feature - feature.json contents (uses `workType`)
71
- * @returns {object} the matching WORK_TYPES entry
72
- */
73
39
  export function getWorkType(feature) {
74
- // Guard against inherited property names (e.g. workType: "toString") — a bare
75
- // WORK_TYPES[key] lookup would resolve those to Object.prototype members.
40
+ // A bare WORK_TYPES[key] would resolve inherited names like "toString".
76
41
  return isValidWorkType(feature?.workType)
77
42
  ? WORK_TYPES[feature.workType]
78
43
  : WORK_TYPES[DEFAULT_WORK_TYPE];
@@ -17,11 +17,6 @@ import Guide from './pages/Guide.jsx';
17
17
  import { useNow } from './hooks/usePref.js';
18
18
  import { useProjects, useCurrentProject, projectHref } from './lib/project.js';
19
19
 
20
- /**
21
- * What the sidebar and the tab title say at a glance, from the portfolio:
22
- * per project, how many features are active, waiting on you and being worked
23
- * on right now.
24
- */
25
20
  function usePulse(portfolio) {
26
21
  const now = useNow();
27
22
  const byProject = {};
@@ -49,7 +44,6 @@ export default function App() {
49
44
  const current = useCurrentProject(portfolio);
50
45
  const pulse = usePulse(portfolio);
51
46
  const [palette, setPalette] = useState(false);
52
- // Ctrl+K / Cmd+K opens the search from anywhere.
53
47
  useEffect(() => {
54
48
  const onKey = (e) => {
55
49
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setPalette((o) => !o); }
@@ -65,8 +59,7 @@ export default function App() {
65
59
  <CommandPalette open={palette} onClose={() => setPalette(false)} portfolio={portfolio} project={current.id} />
66
60
  <main className="relative z-10 flex-1 overflow-auto">
67
61
  <MobileNav {...nav} />
68
- {/* Inside <main>, keyed by route: a page that throws leaves the
69
- sidebar alive, and navigating away remounts a clean boundary. */}
62
+ {/* Keyed by route: a boundary that caught an error stays in fallback until it remounts. */}
70
63
  <ErrorBoundary key={pathname} page={pathname}>
71
64
  <Routes>
72
65
  <Route path="/" element={<Portfolio />} />
@@ -65,7 +65,6 @@ function Line({ item, showFeature, project }) {
65
65
  );
66
66
  }
67
67
 
68
- /** Group consecutive items by calendar day, newest day first. */
69
68
  function byDay(items) {
70
69
  const groups = [];
71
70
  for (const it of items) {
@@ -128,10 +127,6 @@ export function FeedView({ items, kinds, onToggleKind, showFeature, onMore, hasM
128
127
  );
129
128
  }
130
129
 
131
- /**
132
- * Live activity feed. The first page follows the server's `morph:changed`
133
- * pushes; older pages are fetched on demand and appended.
134
- */
135
130
  export default function ActivityFeed({ project, feature, showFeature = false }) {
136
131
  const [kinds, setKinds] = useState(DEFAULT);
137
132
  const [older, setOlder] = useState([]);
@@ -17,7 +17,6 @@ const PAGES = [
17
17
  ['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
18
18
  ];
19
19
 
20
- /** Everything the framework knows that has a page — built once. */
21
20
  function knowledgeIndex() {
22
21
  const out = PAGES.map(([title, to]) => ({ kind: 'page', title, to, hay: title }));
23
22
  for (const s of morphData.skills || []) out.push({ kind: 'skill', title: s.id, hint: s.description, to: entityHref('skill', s.id), hay: `${s.id} ${s.description}` });
@@ -42,10 +41,6 @@ function projectIndex(portfolio) {
42
41
  return (portfolio?.projects || []).map((p) => ({ kind: 'project', title: p.name, hint: p.dir, to: projectHref(p.id), hay: `${p.name} ${p.dir}` }));
43
42
  }
44
43
 
45
- /**
46
- * Features and their slices. `projectName` is set for another project's
47
- * features, so a hit says where it lives.
48
- */
49
44
  function featureIndex(project, features, projectName = null) {
50
45
  const out = [];
51
46
  const where = (text) => (projectName ? (text ? `${projectName}: ${text}` : projectName) : text);
@@ -58,14 +53,12 @@ function featureIndex(project, features, projectName = null) {
58
53
  return out;
59
54
  }
60
55
 
61
- /** The active features of the other projects, from the portfolio's summaries. */
62
56
  function otherProjectsIndex(portfolio, project) {
63
57
  return (portfolio?.projects || [])
64
58
  .filter((p) => p.id !== project && p.summary)
65
59
  .flatMap((p) => featureIndex(p.id, p.summary.activeFeatures, p.name));
66
60
  }
67
61
 
68
- /** Every query word must appear; a title that starts with the query ranks first. */
69
62
  export function searchIndex(index, query, limit = 40) {
70
63
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
71
64
  if (!words.length) return [];
@@ -1,11 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { X } from '@phosphor-icons/react';
3
3
 
4
- /**
5
- * The reading drawer of the knowledge pages. Wide enough for rendered markdown,
6
- * closes on Escape, and sits over the graph without a backdrop so the graph
7
- * stays visible beside it.
8
- */
4
+ /** No backdrop: the graph stays visible beside the drawer. */
9
5
  export default function DetailPanel({ open, title, subtitle, onClose, children }) {
10
6
  useEffect(() => {
11
7
  if (!open) return undefined;
@@ -1,11 +1,6 @@
1
1
  import { Link, useSearchParams } from 'react-router-dom';
2
2
  import { useCallback } from 'react';
3
3
 
4
- /**
5
- * Where each kind of thing lives in the app. The ONE map of entity → URL, so a
6
- * persona, standard or skill reads as a link everywhere it is mentioned.
7
- * A feature belongs to a project: its links come from `featureHref` (lib/project.js).
8
- */
9
4
  export function entityHref(kind, id) {
10
5
  const q = (page) => `/${page}?sel=${encodeURIComponent(id)}`;
11
6
  switch (kind) {
@@ -20,7 +15,6 @@ export function entityHref(kind, id) {
20
15
  }
21
16
  }
22
17
 
23
- // `standards/backend/dotnet/testing.md` (as a plan may cite it) → the registry `path`.
24
18
  export function standardKey(ref) {
25
19
  return String(ref).replace(/^\.?\/?(\.morph\/framework\/)?standards\//, '');
26
20
  }
@@ -34,12 +28,6 @@ export default function EntityLink({ kind, id, children, className = '', chip =
34
28
  return <Link to={entityHref(kind, id)} className={cls} title={title}>{children ?? id}</Link>;
35
29
  }
36
30
 
37
- /**
38
- * The selected item of a knowledge page, kept in `?sel=` so a panel can be
39
- * linked, bookmarked and reached from anywhere with EntityLink.
40
- *
41
- * @returns {[string|null, (id: string|null) => void]}
42
- */
43
31
  export function useSelection() {
44
32
  const [params, setParams] = useSearchParams();
45
33
  const sel = params.get('sel');
@@ -1,15 +1,8 @@
1
1
  import { Component } from 'react';
2
2
 
3
3
  /**
4
- * Catches a page that throws while rendering and shows the error in place of
5
- * the page, so the rest of the layout (the sidebar) keeps working.
6
- *
7
- * Without it one broken page unmounts the whole tree: 8.38.0 opened on
8
- * /agents, which threw a ReferenceError, and the dashboard was a black screen.
9
- *
10
4
  * Mount it with `key={location.pathname}`: a boundary that already caught an
11
- * error stays in fallback until it remounts, and the key remounts it when the
12
- * sidebar navigates to another page.
5
+ * error stays in fallback until it remounts.
13
6
  */
14
7
  export default class ErrorBoundary extends Component {
15
8
  constructor(props) {