@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,18 +1,3 @@
1
- /**
2
- * Skills Installer
3
- *
4
- * Copies framework skills to .claude/skills/ during morph-spec init.
5
- * Installs only the `core/` skill set — the canonical NLH skills referenced by
6
- * MORPH.md (morph-plan, morph-uiux, morph-apply, morph-review, morph-standards…). The 3-tier system was collapsed
7
- * into a single source of truth in framework/skills/core/.
8
- *
9
- * Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
10
- * (plus optional scripts/, references/, assets/ subdirectories).
11
- *
12
- * Directory-based skills have their entire directory copied so Claude Code can
13
- * execute scripts and load references on demand.
14
- */
15
-
16
1
  import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
17
2
  import { basename, join } from 'path';
18
3
  import { fileURLToPath } from 'url';
@@ -22,59 +7,16 @@ import { realEffects } from '../lib/installers/effects.js';
22
7
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
23
8
  const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
24
9
 
25
- /**
26
- * Skill levels to install in .claude/skills/.
27
- * Only `core/` survives — single source of truth aligned with MORPH.md.
28
- */
29
10
  const SKILL_LEVELS_TO_INSTALL = ['core'];
30
11
 
31
- /**
32
- * Check if a skill should be installed based on its stacks: frontmatter.
33
- * @param {string} skillContent - Raw SKILL.md content
34
- * @param {string[]} projectTags - Stack tags from config
35
- * @returns {boolean}
36
- */
37
12
  export function shouldInstallSkill(skillContent, projectTags) {
38
13
  return shouldInstall(parseStacks(skillContent), projectTags);
39
14
  }
40
15
 
41
- /**
42
- * Copy a skill directory tree from src to dest, skipping README.md files.
43
- *
44
- * UM efeito por skill (não um por arquivo): é assim que o instalador pensa
45
- * ("instalar a skill X"), e é o que o relatório do `--dry-run` precisa dizer.
46
- *
47
- * @param {string} src - Source directory path
48
- * @param {string} dest - Destination directory path
49
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} fx
50
- */
51
16
  function copySkillDirectory(src, dest, fx) {
52
17
  fx.copyDir(src, dest, { filter: (from) => basename(from) !== 'README.md' });
53
18
  }
54
19
 
55
- /**
56
- * Walk srcDir and install skills into destDir.
57
- *
58
- * Handles two source formats:
59
- * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
60
- * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
61
- * → destDir/{skill-name}/
62
- *
63
- * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
64
- * → destDir/{skill-name}/SKILL.md
65
- *
66
- * The `core/` level directory is traversed recursively. A directory is
67
- * recognized as a skill directory when it contains a SKILL.md file.
68
- *
69
- * @param {string} srcDir - Source directory to walk
70
- * @param {string} destDir - Destination base directory (.claude/skills/)
71
- * @param {string[]} projectTags - Stack tags from config
72
- * @param {Set<string>} installedNow - Accumulator: skill names this call
73
- * actually (re)installed. NOT derived by re-scanning destDir afterwards —
74
- * a disk scan can't distinguish "installed this run" from "leftover from a
75
- * previous run with different projectTags", which is exactly the stale
76
- * entry a stack change needs cleaned up.
77
- */
78
20
  function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
79
21
  const entries = readdirSync(srcDir);
80
22
  for (const entry of entries) {
@@ -84,20 +26,15 @@ function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
84
26
  if (stat.isDirectory()) {
85
27
  const skillMdPath = join(srcPath, 'SKILL.md');
86
28
  if (existsSync(skillMdPath)) {
87
- // Directory-based skill — check stack filter before copying.
88
- // O filtro é PURO e continua sendo o único: previsão e execução
89
- // percorrem esta mesma linha.
90
29
  const content = readFileSync(skillMdPath, 'utf-8');
91
30
  if (!shouldInstallSkill(content, projectTags)) continue;
92
31
  const skillDestDir = join(destDir, entry);
93
32
  copySkillDirectory(srcPath, skillDestDir, fx);
94
33
  installedNow.add(entry);
95
34
  } else {
96
- // Level/category directory — recurse
97
35
  installSkillsFromDir(srcPath, destDir, projectTags, installedNow, fx);
98
36
  }
99
37
  } else if (entry.endsWith('.md') && entry !== 'README.md') {
100
- // Legacy flat .md skill — check stack filter before copying
101
38
  const content = readFileSync(srcPath, 'utf-8');
102
39
  if (!shouldInstallSkill(content, projectTags)) continue;
103
40
  const skillName = entry.slice(0, -3);
@@ -131,32 +68,12 @@ function collectNamesFromDir(dir, names) {
131
68
  }
132
69
  }
133
70
 
134
- /**
135
- * Install morph framework skills to .claude/skills/ in the target project.
136
- * Skills are copied into subdirectories as SKILL.md files so Claude Code can
137
- * discover them natively via /<skill-name> syntax (e.g., /phase-design,
138
- * /nextjs-expert). Directory-based skills also get their scripts/,
139
- * references/, and assets/ subdirectories copied for on-demand loading.
140
- *
141
- * Stack filtering: if projectTags is provided, only skills whose `stacks:`
142
- * frontmatter matches the project tags are installed. Skills without a
143
- * `stacks:` field are always installed (universal).
144
- *
145
- * @param {string} projectDir - Target project root directory
146
- * @param {Object} [options]
147
- * @param {string[]} [options.projectTags=[]] - Stack tags from config
148
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
149
- * Coletor de efeitos; o default executa e registra numa lista descartada.
150
- */
151
71
  export async function installSkills(projectDir, { projectTags = [], fx = realEffects() } = {}) {
152
72
  const claudeSkillsDir = join(projectDir, '.claude', 'skills');
153
73
  fx.ensureDir(claudeSkillsDir);
154
74
 
155
- // Skill names this call actually (re)installs, given projectTags. Tracked
156
- // as we go (NOT by re-scanning claudeSkillsDir afterwards) — a disk scan
157
- // can't tell "installed this run" apart from "leftover from a previous
158
- // run with different projectTags", which is exactly the stale entry a
159
- // stack change (or a re-run that narrows the stack) needs cleaned up.
75
+ // Tracked as we go: a disk scan can't tell this run's installs from leftovers
76
+ // of a run with other projectTags, which a stack change must clean up.
160
77
  const installedNow = new Set();
161
78
  for (const level of SKILL_LEVELS_TO_INSTALL) {
162
79
  const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
@@ -164,22 +81,8 @@ export async function installSkills(projectDir, { projectTags = [], fx = realEff
164
81
  installSkillsFromDir(levelDir, claudeSkillsDir, projectTags, installedNow, fx);
165
82
  }
166
83
 
167
- // Clean-slate: remove any skill dir that is a KNOWN framework skill (still
168
- // present in framework/skills/core/) but did NOT get (re)installed this
169
- // run — i.e. it was filtered out by a `stacks:` mismatch (project.stack
170
- // changed, or a re-run narrowed the tags). Previously scoped to `morph-`
171
- // prefixed names only, which silently exempted every non-`morph-` skill
172
- // (the Umbraco pair) from ever being cleaned up: install once with
173
- // `umbraco` in the stack, drop the stack later, and they'd survive every
174
- // reinstall/update forever. Names outside allSourceSkills (user-added,
175
- // non-framework skills) are never touched.
176
- //
177
- // Em `dryRun` este laço NÃO emite nada, e a razão está na D6: numa execução
178
- // real do `update` ele sempre vê um diretório VAZIO, porque
179
- // `cleanFrameworkDirs()` acabou de apagar `.claude/skills/` inteiro. Num dry
180
- // ingênuo nada foi apagado, ele veria o diretório POPULADO, e o relatório
181
- // preveria remoções que a execução real nunca faz — errado na direção que
182
- // assusta o usuário. O clean subsome este laço, provadamente.
84
+ // Removes known framework skills not reinstalled this run; user skills are never touched.
85
+ // Silent in dryRun: the real update's cleanFrameworkDirs() already emptied the dir.
183
86
  if (!fx.dryRun) {
184
87
  const allSourceSkills = collectSourceSkillNames();
185
88
  for (const entry of readdirSync(claudeSkillsDir)) {
@@ -6,10 +6,6 @@ import { realEffects } from '../lib/installers/effects.js';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
9
- /**
10
- * Get the current CLI version from package.json
11
- * @returns {string} Version string (e.g., "1.0.4")
12
- */
13
9
  export function getInstalledCLIVersion() {
14
10
  try {
15
11
  const pkgPath = join(__dirname, '..', '..', 'package.json');
@@ -20,11 +16,6 @@ export function getInstalledCLIVersion() {
20
16
  }
21
17
  }
22
18
 
23
- /**
24
- * Get the latest version from npm registry
25
- * @param {string} packageName - The package name (default: @polymorphism-tech/morph-spec)
26
- * @returns {Promise<string|null>} Latest version or null if failed
27
- */
28
19
  export async function getLatestNPMVersion(packageName = '@polymorphism-tech/morph-spec') {
29
20
  try {
30
21
  const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
@@ -36,11 +27,6 @@ export async function getLatestNPMVersion(packageName = '@polymorphism-tech/morp
36
27
  }
37
28
  }
38
29
 
39
- /**
40
- * Get the MORPH version installed in the project
41
- * @param {string} projectPath - Path to the project
42
- * @returns {Promise<object|null>} Version info object or null if not found
43
- */
44
30
  export async function getProjectMorphVersion(projectPath) {
45
31
  try {
46
32
  const versionPath = join(projectPath, '.morph', '.morphversion');
@@ -53,12 +39,6 @@ export async function getProjectMorphVersion(projectPath) {
53
39
  }
54
40
  }
55
41
 
56
- /**
57
- * Save the MORPH version to the project
58
- * @param {string} projectPath - Path to the project
59
- * @param {string} version - Version to save
60
- * @returns {Promise<void>}
61
- */
62
42
  export async function saveProjectMorphVersion(projectPath, version, fx = realEffects()) {
63
43
  const versionPath = join(projectPath, '.morph', '.morphversion');
64
44
  const versionInfo = {
@@ -69,12 +49,7 @@ export async function saveProjectMorphVersion(projectPath, version, fx = realEff
69
49
  fx.writeJson(versionPath, versionInfo);
70
50
  }
71
51
 
72
- /**
73
- * Compare two semver versions
74
- * @param {string} v1 - First version
75
- * @param {string} v2 - Second version
76
- * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
77
- */
52
+ /** @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2 */
78
53
  export function compareVersions(v1, v2) {
79
54
  if (!v1 || !v2) return 0;
80
55
 
@@ -92,49 +67,17 @@ export function compareVersions(v1, v2) {
92
67
  return 0;
93
68
  }
94
69
 
95
- /**
96
- * Major de uma versão, ou `null` quando a string não permite decidir.
97
- *
98
- * Vive aqui, ao lado de `compareVersions`, de propósito: uma segunda
99
- * aritmética de versão noutro arquivo é exatamente como duas verdades sobre a
100
- * mesma versão nascem.
101
- *
102
- * @param {string|null|undefined} version
103
- * @returns {number|null}
104
- */
70
+ /** @returns {number|null} `null` when the string does not determine a major */
105
71
  export function majorOf(version) {
106
72
  if (typeof version !== 'string') return null;
107
73
  const m = /^\s*v?(\d+)\./.exec(version) ?? /^\s*v?(\d+)\s*$/.exec(version);
108
74
  return m ? Number(m[1]) : null;
109
75
  }
110
76
 
111
- /**
112
- * Limiar do freio: um salto de DOIS majors ou mais recusa sem `--force`.
113
- *
114
- * O limiar não é "quantos majors dá para pular com segurança" — é "a partir de
115
- * onde o usuário merece ler o relatório antes". Medido: um projeto em 7.x indo
116
- * para 9.x recusa; um em 8.x atualiza sem atrito.
117
- */
118
77
  export const MAJOR_JUMP_LIMIT = 2;
119
78
 
120
- /**
121
- * Avalia o salto entre a versão instalada no projeto e a versão de destino.
122
- *
123
- * Três resultados, e a diferença entre eles é o que a mensagem tem de dizer:
124
- *
125
- * - `reason: 'unknown-installed'` — a versão instalada é indeterminável.
126
- * Tratada como salto grande, com motivo PRÓPRIO: "não sei quão velho isto é"
127
- * é precisamente quando sobrescrever às cegas é mais perigoso, e tratá-la
128
- * como zero seria a única leitura que libera o pior caso.
129
- * - `reason: 'major-jump'` — salto medido de `jump` majors, >= ao limiar.
130
- * - `reason: null` — sem freio (mesmo major, um só major de diferença, ou
131
- * destino não superior à instalada).
132
- *
133
- * @param {string|null} installedVersion
134
- * @param {string|null} targetVersion
135
- * @param {{ limit?: number }} [options]
136
- * @returns {{ refuse: boolean, reason: 'unknown-installed'|'major-jump'|null, jump: number|null }}
137
- */
79
+ // An unreadable installed version refuses with its own reason: not knowing how old
80
+ // the project is is when a blind overwrite is most dangerous.
138
81
  export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_LIMIT } = {}) {
139
82
  const installedMajor = majorOf(installedVersion);
140
83
  const targetMajor = majorOf(targetVersion);
@@ -143,8 +86,6 @@ export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_
143
86
  return { refuse: true, reason: 'unknown-installed', jump: null };
144
87
  }
145
88
  if (targetMajor === null) {
146
- // Sem destino legível não há salto a medir — e inventar um seria decidir
147
- // por um número que ninguém mantém.
148
89
  return { refuse: false, reason: null, jump: null };
149
90
  }
150
91
  if (compareVersions(installedVersion, targetVersion) >= 0) {
@@ -156,17 +97,11 @@ export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_
156
97
  return { refuse: false, reason: null, jump };
157
98
  }
158
99
 
159
- /**
160
- * Detect how the CLI was installed
161
- * @returns {'global'|'npx'|'unknown'}
162
- */
163
100
  export function detectInstallMethod() {
164
- // Check if running via npx
165
101
  if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) {
166
102
  return 'npx';
167
103
  }
168
104
 
169
- // Check if installed globally
170
105
  const isGlobal = process.env.npm_config_global === 'true' ||
171
106
  process.execPath.includes('node_modules');
172
107
 
@@ -177,11 +112,6 @@ export function detectInstallMethod() {
177
112
  return 'unknown';
178
113
  }
179
114
 
180
- /**
181
- * Get update instructions based on install method
182
- * @param {string} method - Install method ('global', 'npx', 'unknown')
183
- * @returns {string[]} Array of instruction strings
184
- */
185
115
  export function getUpdateInstructions(method) {
186
116
  switch (method) {
187
117
  case 'global':
@@ -198,10 +128,6 @@ export function getUpdateInstructions(method) {
198
128
  }
199
129
  }
200
130
 
201
- /**
202
- * Check if CLI version is outdated
203
- * @returns {Promise<{isOutdated: boolean, current: string, latest: string|null}>}
204
- */
205
131
  export async function checkCLIOutdated() {
206
132
  const current = getInstalledCLIVersion();
207
133
  const latest = await getLatestNPMVersion();
@@ -215,11 +141,6 @@ export async function checkCLIOutdated() {
215
141
  return { isOutdated, current, latest };
216
142
  }
217
143
 
218
- /**
219
- * Check if project MORPH version is outdated
220
- * @param {string} projectPath - Path to the project
221
- * @returns {Promise<{isOutdated: boolean, current: string|null, cliVersion: string}>}
222
- */
223
144
  export async function checkProjectOutdated(projectPath) {
224
145
  const cliVersion = getInstalledCLIVersion();
225
146
  const projectVersion = await getProjectMorphVersion(projectPath);
@@ -1,9 +0,0 @@
1
- ---
2
- description: Inicia ou retoma uma feature MORPH-SPEC — entrevista, plan.md, plan-check e gate do plano.
3
- argument-hint: <feature-name>
4
- allowed-tools: Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion, Skill
5
- ---
6
-
7
- Invoque `Skill(morph-plan)` com o argumento abaixo e siga a skill.
8
-
9
- **Feature:** $ARGUMENTS
@@ -1,3 +0,0 @@
1
- /**
2
- * Utility Commands — no active commands after CLI simplification
3
- */
@@ -1,188 +0,0 @@
1
- /**
2
- * @fileoverview Design System Detector
3
- *
4
- * Detects if a design system exists for the project or feature.
5
- * Checks multiple locations in priority order.
6
- *
7
- * @module design-system-detector
8
- */
9
-
10
- import { existsSync, readFileSync, readdirSync } from 'fs';
11
- import { join } from 'path';
12
-
13
- const SKIP_DIRS = new Set(['node_modules', '.git', '.morph', 'dist', 'build', '.next', 'obj', 'bin', '.vs']);
14
- const CSS_SCAN_MAX_DEPTH = 6;
15
-
16
- /**
17
- * Recursively walk a directory collecting files with given extensions.
18
- * @param {string} dir - Directory to walk
19
- * @param {string[]} extensions - File extensions to collect (e.g. ['.css', '.scss'])
20
- * @param {string[]} files - Accumulator array (mutated in-place)
21
- * @param {number} depth - Current recursion depth
22
- * @param {number} maxDepth - Maximum recursion depth
23
- */
24
- function walkDirForCss(dir, extensions, files, depth, maxDepth) {
25
- if (depth > maxDepth) return;
26
- let entries;
27
- try {
28
- entries = readdirSync(dir, { withFileTypes: true });
29
- } catch {
30
- return;
31
- }
32
- for (const entry of entries) {
33
- if (entry.isDirectory()) {
34
- if (!SKIP_DIRS.has(entry.name)) {
35
- walkDirForCss(join(dir, entry.name), extensions, files, depth + 1, maxDepth);
36
- }
37
- } else if (entry.isFile()) {
38
- const lower = entry.name.toLowerCase();
39
- if (extensions.some(ext => lower.endsWith(ext))) {
40
- files.push(join(dir, entry.name));
41
- }
42
- }
43
- }
44
- }
45
-
46
- /**
47
- * Detect design system for project or feature
48
- * @param {string} projectPath - Root path of the project
49
- * @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
50
- * @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
51
- */
52
- export function detectDesignSystem(projectPath, featureName = null) {
53
- const result = {
54
- hasDesignSystem: false,
55
- path: null,
56
- isProjectLevel: false,
57
- isFeatureLevel: false,
58
- source: null
59
- };
60
-
61
- // Priority 1: Feature-level design system (if feature specified)
62
- if (featureName) {
63
- const featureDesignSystemPath = join(
64
- projectPath,
65
- '.morph/features',
66
- featureName,
67
- '1-design',
68
- 'design-system.md'
69
- );
70
-
71
- if (existsSync(featureDesignSystemPath)) {
72
- return {
73
- hasDesignSystem: true,
74
- path: featureDesignSystemPath,
75
- isProjectLevel: false,
76
- isFeatureLevel: true,
77
- source: 'feature-output'
78
- };
79
- }
80
- }
81
-
82
- // Priority 2: Project-level design system (markdown)
83
- const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
84
- if (existsSync(projectDesignSystemPath)) {
85
- return {
86
- hasDesignSystem: true,
87
- path: projectDesignSystemPath,
88
- isProjectLevel: true,
89
- isFeatureLevel: false,
90
- source: 'project-markdown'
91
- };
92
- }
93
-
94
- // Priority 3: Generated CSS design system
95
- const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
96
- if (existsSync(cssDesignSystemPath)) {
97
- return {
98
- hasDesignSystem: true,
99
- path: cssDesignSystemPath,
100
- isProjectLevel: true,
101
- isFeatureLevel: false,
102
- source: 'generated-css'
103
- };
104
- }
105
-
106
- // Priority 4: Recursively scan frontend root directories for CSS/SCSS with design tokens
107
- const frontendRoots = ['wwwroot', 'styles', 'src/styles', 'src', 'public'];
108
- const extensions = ['.css', '.scss'];
109
- const collected = [];
110
-
111
- for (const root of frontendRoots) {
112
- const rootPath = join(projectPath, root);
113
- if (existsSync(rootPath)) {
114
- walkDirForCss(rootPath, extensions, collected, 0, CSS_SCAN_MAX_DEPTH);
115
- }
116
- }
117
-
118
- // Deduplicate by resolved path
119
- const seen = new Set();
120
- const cssFiles = [];
121
- for (const file of collected) {
122
- if (!seen.has(file)) {
123
- seen.add(file);
124
- cssFiles.push(file);
125
- }
126
- }
127
-
128
- // Sort for deterministic first-match across platforms
129
- cssFiles.sort();
130
-
131
- for (const cssFile of cssFiles) {
132
- try {
133
- const content = readFileSync(cssFile, 'utf8');
134
- const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
135
- if (hasDesignTokens) {
136
- return {
137
- hasDesignSystem: true,
138
- path: cssFile,
139
- isProjectLevel: true,
140
- isFeatureLevel: false,
141
- source: 'existing-css-variables'
142
- };
143
- }
144
- } catch {
145
- // Continue checking other files
146
- }
147
- }
148
-
149
- // No design system found
150
- return result;
151
- }
152
-
153
- /**
154
- * Get recommended design system locations based on project type
155
- * @param {string} projectPath - Root path of the project
156
- * @returns {Object} { markdown, css, description }
157
- */
158
- export function getRecommendedLocations(projectPath) {
159
- try {
160
- const configPath = join(projectPath, '.morph/config/config.json');
161
-
162
- let projectType = 'unknown';
163
- if (existsSync(configPath)) {
164
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
165
- projectType = config.project?.type || 'unknown';
166
- }
167
-
168
- // Default locations
169
- const locations = {
170
- markdown: '.morph/context/design-system.md',
171
- css: 'public/design-system.css',
172
- description: 'Project-level design system (shared across features)'
173
- };
174
-
175
- // Adjust based on project type
176
- if (projectType === 'nextjs' || projectType.includes('nextjs')) {
177
- locations.css = 'styles/design-system.css';
178
- }
179
-
180
- return locations;
181
- } catch {
182
- return {
183
- markdown: '.morph/context/design-system.md',
184
- css: 'wwwroot/css/design-system.css',
185
- description: 'Project-level design system (shared across features)'
186
- };
187
- }
188
- }