@polymorphism-tech/morph-spec 8.46.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -1,10 +1,8 @@
1
1
  /**
2
- * `morph-spec cost [feature] [--by task|phase] [--all-trees] [--metrics [--top n]] [--json]`
2
+ * `morph-spec cost [feature] [--by phase] [--all-trees] [--metrics [--top n]] [--json]`
3
3
  *
4
4
  * A cost-facing view over the same harness event stream `telemetry` reads
5
- * (.morph/logs/events.jsonl): how much wall-clock time was spent inside
6
- * measured tasks (TaskStart→TaskEnd), how many context bytes were
7
- * re-ingested (grouped by kind: rubric/standard/skill/agents/plan-artifact/
5
+ * (.morph/logs/events.jsonl): how many context bytes were re-ingested (grouped by kind: rubric/standard/skill/agents/plan-artifact/
8
6
  * code), and how many sub-agent dispatches happened (with total prompt
9
7
  * chars). When the stream has `SessionUsage` events (real, hook-observed
10
8
  * token counts), those are summed and shown too — see the sessionUsage
@@ -22,7 +20,7 @@
22
20
  *
23
21
  * `--metrics` measures the loop instead: turns, tool calls by orchestrator and
24
22
  * sub-agent, time per command class, identical reruns and why, rereads, how
25
- * many feature-scope verifies each feature ran, git calls per task and whether
23
+ * many verifies each feature ran, git calls and whether
26
24
  * the reuse validator ran in root and worktree (see lib/telemetry/metrics.js).
27
25
  *
28
26
  * Read-only, always exits 0 on a normal (even empty) run — fail-open, an
@@ -37,7 +35,7 @@ import { computeMetrics } from '../../lib/telemetry/metrics.js';
37
35
  const BYTES_CAVEAT =
38
36
  'Bytes re-ingeridos são um PROXY determinístico de custo (o que as ferramentas leram do disco) — NÃO são tokens reais. Hooks não enxergam o uso real de tokens do modelo.';
39
37
 
40
- const BY_MODES = ['task', 'phase'];
38
+ const BY_MODES = ['phase'];
41
39
 
42
40
  function fmtBytes(n) {
43
41
  if (!n) return '0 B';
@@ -65,21 +63,6 @@ function printBytesByKind(bytesByKind) {
65
63
  }
66
64
  }
67
65
 
68
- function printTasks(tasks) {
69
- if (!tasks.length) {
70
- console.log(chalk.gray('\nNenhuma task com TaskStart/TaskEnd capturada.'));
71
- return;
72
- }
73
- for (const t of tasks) {
74
- const status = t.open ? chalk.yellow('[em aberto]') : (t.status || '-');
75
- console.log('\n' + chalk.white.bold(`Task ${t.task}`) + chalk.gray(` · ${t.feature ?? '(sem feature)'}`) + ` · ${status}`);
76
- console.log(` duração: ${fmtDuration(t.durationSeconds)}`);
77
- console.log(` dispatches: ${t.dispatches.count} (${t.dispatches.promptChars} chars de prompt)`);
78
- console.log(' bytes re-ingeridos por kind:');
79
- printBytesByKind(t.bytesByKind);
80
- }
81
- }
82
-
83
66
  function printPhases(phases) {
84
67
  if (!phases.length) {
85
68
  console.log(chalk.gray('\nNenhum byte de Read com phase capturado.'));
@@ -118,8 +101,7 @@ function printSessionUsage(summary) {
118
101
  }
119
102
 
120
103
  function printSummary(summary) {
121
- console.log(`\n duração dentro de tasks medidas: ${fmtDuration(summary.taskDurationSeconds)}`);
122
- console.log(` dispatches: ${summary.dispatches.count} (${summary.dispatches.promptChars} chars de prompt)`);
104
+ console.log(`\n dispatches: ${summary.dispatches.count} (${summary.dispatches.promptChars} chars de prompt)`);
123
105
 
124
106
  console.log('\n' + chalk.white.bold('Bytes re-ingeridos por kind') + ` (${fmtBytes(summary.bytesTotal)} total)`);
125
107
  printBytesByKind(summary.bytesByKind);
@@ -140,7 +122,7 @@ function split(counts) {
140
122
  return `${counts.total} (orq ${counts.orchestrator} · sub ${counts.subagent}${counts.unknown ? ` · sem agente ${counts.unknown}` : ''})`;
141
123
  }
142
124
 
143
- function printMetricsBody(m, { by }) {
125
+ function printMetricsBody(m) {
144
126
  const legacyShare = m.window.events ? m.coverage.legacy / m.window.events : 0;
145
127
  console.log(chalk.gray(`janela: ${m.window.from ?? '-'} → ${m.window.to ?? '-'} · ${m.window.events} eventos`
146
128
  + ` · ${pct(1 - legacyShare)} no esquema v2`));
@@ -149,16 +131,8 @@ function printMetricsBody(m, { by }) {
149
131
  }
150
132
 
151
133
  console.log('\n' + chalk.white.bold('Turnos e chamadas'));
152
- console.log(` turnos: ${m.turns ? split(m.turns) : 'desconhecido (nenhum PostToolBatch na janela)'}`
153
- + (m.turnsPerTaskMedian !== null ? chalk.gray(` · mediana por task ${m.turnsPerTaskMedian}`) : ''));
134
+ console.log(` turnos: ${m.turns ? split(m.turns) : 'desconhecido (nenhum PostToolBatch na janela)'}`);
154
135
  console.log(` chamadas de ferramenta: ${split(m.toolCalls)}`);
155
- const perTask = m.perTaskByFeature;
156
- if (perTask.toolCalls !== null) {
157
- console.log(` por task (total da feature ÷ tasks dela, mediana): ${perTask.toolCalls} chamadas`
158
- + (perTask.turns !== null ? ` · ${perTask.turns} turnos` : '') + ` · ${perTask.git} git status+diff`
159
- + chalk.gray(` · as janelas TaskStart→TaskEnd têm ${pct(m.taskWindowCoverage)} das chamadas`));
160
- }
161
-
162
136
  console.log('\n' + chalk.white.bold('Tempo por classe de comando'));
163
137
  for (const [klass, t] of Object.entries(m.timeByClass).sort((a, b) => b[1].measuredMs - a[1].measuredMs)) {
164
138
  const unmeasured = t.unmeasured ? chalk.gray(` (${t.unmeasured} sem duração)`) : '';
@@ -183,9 +157,9 @@ function printMetricsBody(m, { by }) {
183
157
  }
184
158
 
185
159
  console.log('\n' + chalk.white.bold('verify, git e reuso'));
186
- const perFeature = Object.entries(m.verify.byFeature).sort((a, b) => b[1].feature - a[1].feature);
187
- console.log(` verify de feature por feature: mediana ${m.verify.featureScopeMedian ?? '-'}`
188
- + (perFeature.length ? chalk.gray(` (${perFeature.slice(0, 5).map(([f, v]) => `${f} ${v.feature}`).join(', ')})`) : ''));
160
+ const perFeature = Object.entries(m.verify.byFeature).sort((a, b) => b[1] - a[1]);
161
+ console.log(` verify por feature: mediana ${m.verify.median ?? '-'}`
162
+ + (perFeature.length ? chalk.gray(` (${perFeature.slice(0, 5).map(([f, n]) => `${f} ${n}`).join(', ')})`) : ''));
189
163
  const repeats = Object.entries(m.verify.unchangedRepeats);
190
164
  if (repeats.length) {
191
165
  console.log(` verify de feature sobre a mesma árvore do anterior: ${repeats.reduce((n, [, v]) => n + v, 0)}`
@@ -196,24 +170,12 @@ function printMetricsBody(m, { by }) {
196
170
  console.log(` suíte .NET inteira que recompila (sem --no-build): ${m.dotnetFullSuite.withBuild}/${m.dotnetFullSuite.runs}`
197
171
  + chalk.gray(` (${phases})`));
198
172
  }
199
- console.log(` git status / diff por task: mediana ${m.gitPerTaskMedian.status ?? '-'} / ${m.gitPerTaskMedian.diff ?? '-'}`
200
- + chalk.gray(` · total ${m.git.status} / ${m.git.diff}`));
173
+ console.log(` git status / diff: ${m.git.status} / ${m.git.diff}`);
201
174
  const trees = Object.entries(m.reuse.byTree);
202
175
  console.log(` reuse-validator: ${trees.length
203
176
  ? trees.map(([tree, s]) => `${tree} rodou ${s.ran}, pulou ${s.skipped}`).join(' · ')
204
177
  : 'nenhum evento Verify na janela'}`);
205
178
  for (const [reason, n] of Object.entries(m.reuse.skipReasons)) console.log(chalk.gray(` pulou ${n}×: ${reason}`));
206
-
207
- if (by === 'task') {
208
- for (const t of m.tasks) {
209
- const status = t.open ? chalk.yellow('[em aberto]') : (t.status || '-');
210
- console.log('\n' + chalk.white.bold(`Task ${t.task}`) + chalk.gray(` · ${t.feature ?? '(sem feature)'}`) + ` · ${status}`);
211
- console.log(` turnos ${t.turns ? split(t.turns) : '-'} · chamadas ${split(t.toolCalls)}`);
212
- const time = Object.entries(t.timeByClass).map(([k, v]) => `${k} ${fmtDuration(v.measuredMs / 1000)}`).join(' · ');
213
- if (time) console.log(` tempo: ${time}`);
214
- console.log(` rerun ${pct(t.reruns.rate)} (${t.reruns.reruns}/${t.reruns.runs}) · releitura ${pct(t.rereads.rate)} · git status/diff ${t.git.status}/${t.git.diff}`);
215
- }
216
- }
217
179
  }
218
180
 
219
181
  /**
@@ -237,17 +199,17 @@ function printMetrics(feature, options, cwd) {
237
199
  }
238
200
  const scope = feature ? chalk.cyan(` · feature ${feature}`) : '';
239
201
  console.log(chalk.white.bold(`Medições do loop${options.allTrees ? ' — todas as árvores + finish-backup' : ''}`) + scope);
240
- printMetricsBody(metrics, options);
202
+ printMetricsBody(metrics);
241
203
  }
242
204
 
243
205
  /**
244
206
  * `--all-trees` rendering: a short line per tree (so a fleet operator can
245
207
  * spot which tree is expensive) followed by the COMBINED aggregate — full
246
- * detail (task/phase breakdown honors `--by` here too, same as the
208
+ * detail (the phase breakdown honors `--by` here too, same as the
247
209
  * single-tree path; a typeable flag combination is never a silent no-op).
248
210
  *
249
211
  * @param {string} [feature]
250
- * @param {{ json?: boolean, by?: 'task'|'phase' }} options
212
+ * @param {{ json?: boolean, by?: 'phase' }} options
251
213
  * @param {string} cwd
252
214
  */
253
215
  function printAllTrees(feature, options, cwd) {
@@ -277,7 +239,7 @@ function printAllTrees(feature, options, cwd) {
277
239
 
278
240
  for (const t of perTree) {
279
241
  console.log('\n' + chalk.cyan.bold(`── Árvore: ${t.tree}`) + chalk.gray(` — ${t.path}`));
280
- console.log(` ${t.total} eventos · duração em tasks: ${fmtDuration(t.taskDurationSeconds)} · `
242
+ console.log(` ${t.total} eventos · `
281
243
  + `bytes: ${fmtBytes(t.bytesTotal)} · dispatches: ${t.dispatches.count}`);
282
244
  }
283
245
 
@@ -285,9 +247,7 @@ function printAllTrees(feature, options, cwd) {
285
247
  if (combined.features.length) {
286
248
  console.log(chalk.gray(`Features: ${combined.features.join(', ')}`));
287
249
  }
288
- if (options.by === 'task') {
289
- printTasks(combined.tasks);
290
- } else if (options.by === 'phase') {
250
+ if (options.by === 'phase') {
291
251
  printPhases(combined.phases);
292
252
  } else {
293
253
  printSummary(combined);
@@ -296,7 +256,7 @@ function printAllTrees(feature, options, cwd) {
296
256
 
297
257
  /**
298
258
  * @param {string} [feature] - narrow to a single feature
299
- * @param {{ json?: boolean, by?: 'task'|'phase', allTrees?: boolean, cwd?: string }} [options]
259
+ * @param {{ json?: boolean, by?: 'phase', allTrees?: boolean, cwd?: string }} [options]
300
260
  */
301
261
  export function costCommand(feature, options = {}) {
302
262
  if (options.by && !BY_MODES.includes(options.by)) {
@@ -345,9 +305,7 @@ export function costCommand(feature, options = {}) {
345
305
  console.log(chalk.gray(`Features: ${summary.features.join(', ')}`));
346
306
  }
347
307
 
348
- if (options.by === 'task') {
349
- printTasks(summary.tasks);
350
- } else if (options.by === 'phase') {
308
+ if (options.by === 'phase') {
351
309
  printPhases(summary.phases);
352
310
  } else {
353
311
  printSummary(summary);
@@ -12,12 +12,10 @@
12
12
  import chalk from 'chalk';
13
13
  import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
14
14
 
15
- // The knowledge-base count reflects EXPLICIT reads only. The core standards
16
- // load via @-refs in rules (Layer 0/1 auto-load) — those are injected by
17
- // Claude Code's rules system, not via the Read tool, so no hook ever sees them.
18
- // A low count here means "few on-demand reads", NOT "standards barely used".
15
+ // A rule injects its standards' digests through Claude Code's rules system,
16
+ // not the Read tool, so no hook sees them: a low count means "few full-text reads".
19
17
  const STANDARDS_CAVEAT =
20
- 'Só reads explícitos (via Read) são observáveis; standards auto-carregados por @-ref nas rules (Layer 0/1) não passam por tool call e ficam invisíveis aqui.';
18
+ 'Só reads explícitos (via Read) são observáveis; o resumo que uma rule injeta ao tocar arquivo casado por `paths:` não passa por tool call: não observável aqui.';
21
19
 
22
20
  function section(title, rows, fmt) {
23
21
  if (!rows.length) return;
@@ -134,7 +134,7 @@ export async function doctorProcsCommand(targetPath, options = {}) {
134
134
  const { afterHeavyRun } = await import('../../lib/heavy/cleanup.js');
135
135
  report.prune = report.procs.status === 'ok' ? pruneOrphans(report.procs.rows, options.pruneDeps) : [];
136
136
  report.buildServer = afterHeavyRun({
137
- projectPath: targetPath, config: readHeavyConfig(targetPath), scope: 'prune', deps: options.cleanupDeps,
137
+ projectPath: targetPath, config: readHeavyConfig(targetPath), deps: options.cleanupDeps,
138
138
  });
139
139
  }
140
140
 
@@ -242,10 +242,12 @@ const FRAMEWORK_STANDARDS = [
242
242
  'framework/standards/integration/mcp/mcp-tools.md'
243
243
  ];
244
244
 
245
- // required personas expected in agents.json (v5 — the universal, stack-agnostic
246
- // personas: evaluator (domain axis) and scout (stage axis))
245
+ // required personas expected in agents.json — the universal, stack-agnostic
246
+ // ones: the three review roles and scout (stage axis)
247
247
  const REQUIRED_AGENTS = [
248
- 'evaluator',
248
+ 'verifier',
249
+ 'reviewer',
250
+ 'security',
249
251
  'scout'
250
252
  ];
251
253
 
@@ -1289,7 +1291,7 @@ export async function doctorCommand(options = {}) {
1289
1291
  // Check for missing agents based on stack tags. Empty tags (no config
1290
1292
  // yet) is NOT a skip: personas install-all by default, so with an
1291
1293
  // unknown stack ALL personas are expected (agentEligible semantics) —
1292
- // the exact field failure where only morph-evaluator landed and every
1294
+ // the exact field failure where only morph-verifier landed and every
1293
1295
  // domain dispatch fell back to general-purpose.
1294
1296
  try {
1295
1297
  let projectTags = [];
@@ -1509,10 +1511,8 @@ export async function doctorCommand(options = {}) {
1509
1511
  // Check key templates exist (in framework package, not project copy)
1510
1512
  const frameworkTemplatesRoot = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
1511
1513
  const keyTemplates = [
1512
- { name: 'proposal.md', path: join(frameworkTemplatesRoot, 'docs', 'proposal.md') },
1513
- { name: 'spec.md', path: join(frameworkTemplatesRoot, 'docs', 'spec.md') },
1514
- { name: 'tasks.template.json', path: join(frameworkTemplatesRoot, 'tasks.template.json') },
1515
- { name: 'decisions.md', path: join(frameworkTemplatesRoot, 'feature', 'decisions.md') }
1514
+ { name: 'plan.md', path: join(frameworkTemplatesRoot, 'docs', 'plan.md') },
1515
+ { name: 'ui-mockups.md', path: join(frameworkTemplatesRoot, 'docs', 'ui-mockups.md') }
1516
1516
  ];
1517
1517
  const missingTemplates = [];
1518
1518
  for (const template of keyTemplates) {
@@ -1560,7 +1560,7 @@ export async function doctorCommand(options = {}) {
1560
1560
  }
1561
1561
 
1562
1562
  // Per-feature detail for the lifecycle inconsistencies (item: doctor lists
1563
- // what's already wrong in the repo, e.g. plan approved with proposal pending).
1563
+ // what's already wrong in the repo, e.g. review approved with plan pending).
1564
1564
  if (lifecycleIssues.length) {
1565
1565
  logger.blank();
1566
1566
  console.log(chalk.yellow(' Feature lifecycle inconsistencies:'));
@@ -2,7 +2,7 @@
2
2
  * `morph-spec fleet [--json]` — the fleet view.
3
3
  *
4
4
  * One line per active feature across the primary root and every morph/*
5
- * worktree: phase, pending gates, task progress, port block, handoff
5
+ * worktree: phase, pending gates, slice progress, port block, handoff
6
6
  * freshness, last commit and dirtiness. This is the terminal surface the
7
7
  * operator scans between sessions ("what is each instance doing, what is
8
8
  * waiting on me") — `worktree list` remains the lean per-worktree view.
@@ -59,12 +59,12 @@ export function fleetCommand(options = {}) {
59
59
  // fleet-scanner.js) is 16 chars — 1 wider than the col width `list.js`
60
60
  // already uses (17) for the identical string. A narrower column here
61
61
  // would misalign every column after it just for that one row.
62
- col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('TASKS', 8)
62
+ col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('FATIAS', 8)
63
63
  + col('GATES PEND.', 20) + col('WHERE', 24) + col('REMOTO', 16) + col('CLAIM', 16)
64
64
  + col('PORTS', 12) + col('HANDOFF', 9) + 'ATIVIDADE',
65
65
  ));
66
66
  for (const e of fleet.entries) {
67
- const tasks = e.tasks ? `${e.tasks.done}/${e.tasks.total}` : '-';
67
+ const slices = e.slices ? `${e.slices.done}/${e.slices.total}` : '-';
68
68
  const gates = e.pendingGates.length ? e.pendingGates.join(',') : '-';
69
69
  // pr-pending has no worktree and isn't root-resident either — its branch
70
70
  // is the only place it exists, so show that instead of the misleading
@@ -80,7 +80,7 @@ export function fleetCommand(options = {}) {
80
80
  // length and append the padding by hand.
81
81
  const claimPlain = e.claim ? `${e.claim.live ? 'sessão' : 'morta'} (${claimAge(e.claim)})` : '-';
82
82
  console.log(
83
- col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(tasks, 8)
83
+ col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(slices, 8)
84
84
  + col(gates, 20) + col(where, 24) + col(remoteCell(e.remote), 16)
85
85
  + claimCell(e.claim) + ' '.repeat(Math.max(1, 16 - claimPlain.length))
86
86
  + col(ports, 12) + col(handoff, 9) + activity,
@@ -4,9 +4,9 @@ import { logger } from '../../utils/logger.js';
4
4
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
5
5
  import { createHeavyGate, HeavyBusyError } from '../../lib/heavy/gate.js';
6
6
  import { describeHolder, heavyDir, heavyStatus } from '../../../framework/hooks/shared/heavy-slots.js';
7
+ import { resolveSpawn } from '../../../framework/hooks/shared/spawn-command.js';
7
8
 
8
9
  const EXIT_BUSY = 3;
9
- const NEEDS_CMD_SHIM = /^(npm|npx|pnpm|yarn)$/;
10
10
 
11
11
  export function heavyStatusCommand(options = {}) {
12
12
  const config = readHeavyConfig(process.cwd());
@@ -49,8 +49,8 @@ export function heavyRunCommand(command, options = {}) {
49
49
  process.exit(EXIT_BUSY);
50
50
  }
51
51
 
52
- const exe = process.platform === 'win32' && NEEDS_CMD_SHIM.test(file) ? `${file}.cmd` : file;
53
- const res = spawnSync(exe, args, { stdio: 'inherit', shell: exe.endsWith('.cmd') });
52
+ const spawn = resolveSpawn(file, args);
53
+ const res = spawnSync(spawn.file, spawn.args, { stdio: 'inherit', shell: spawn.shell });
54
54
  gate.release();
55
55
 
56
56
  if (res.error) {
@@ -28,7 +28,7 @@ import {
28
28
  setupStitch,
29
29
  loadMcpRegistry
30
30
  } from '../../lib/installers/mcp-installer.js';
31
- import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
31
+ import { setupInfra, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
32
32
  import { installSkills } from '../../utils/skills-installer.js';
33
33
  import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
34
34
  import { getProjectTags } from '../../lib/stack-filter.js';
@@ -111,6 +111,10 @@ export async function initCommand(options) {
111
111
  // ARE the primary root, or the caller explicitly passed --force), so it
112
112
  // must not be redundantly re-applied inside setupInfra().
113
113
  const infraResult = await setupInfra(targetPath, { force: true });
114
+ if (infraResult?.refused) {
115
+ process.exitCode = 1;
116
+ return infraResult;
117
+ }
114
118
  spinner.start('Continuing...');
115
119
 
116
120
  // Derive display variables from what setupInfra installed
@@ -263,6 +267,7 @@ export async function initCommand(options) {
263
267
  const rulesSrc = join(fwSrcDir, 'rules');
264
268
  const rulesDest = join(targetPath, '.claude', 'rules');
265
269
  installRulesForStack(rulesSrc, rulesDest, stackTags);
270
+ installCommandsForStack(join(fwSrcDir, 'commands'), join(targetPath, '.claude', 'commands'), stackTags);
266
271
 
267
272
  // Skills
268
273
  await installSkills(targetPath, { projectTags: stackTags });
@@ -4,7 +4,8 @@ import { logger } from '../../utils/logger.js';
4
4
  export async function setupInfraCommand(options) {
5
5
  const targetPath = options.path || process.cwd();
6
6
  try {
7
- await setupInfra(targetPath, { reinstall: options.reinstall ?? false });
7
+ const result = await setupInfra(targetPath, { reinstall: options.reinstall ?? false });
8
+ if (result?.refused) process.exitCode = 1;
8
9
  } catch (err) {
9
10
  logger.error(`setup-infra failed: ${err.message}`);
10
11
  process.exit(1);
@@ -0,0 +1,58 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { getFeatureDir } from '../../core/paths/output-schema.js';
5
+ import { readMorphConfig } from '../../lib/tasks/test-runner.js';
6
+ import { notAuditedReason, scanStack, renderStackScan } from '../../lib/stack/stack-scan.js';
7
+ import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
8
+
9
+ export const STACK_SCAN_FILE = 'stack-scan.md';
10
+
11
+ /** Scan the tree at `cwd`; with a feature, write `2-plan/stack-scan.md`. */
12
+ export function runStackScan(featureName, cwd = process.cwd()) {
13
+ const result = scanStack(cwd, readMorphConfig(cwd));
14
+ const markdown = renderStackScan(result, featureName ?? 'projeto');
15
+ if (!featureName) return { ...result, markdown, written: null, exit: 0 };
16
+
17
+ const featureDir = join(cwd, getFeatureDir(featureName));
18
+ if (!existsSync(featureDir)) return { ...result, markdown, written: null, exit: 2, error: 'feature-not-found' };
19
+ const planDir = join(featureDir, '2-plan');
20
+ mkdirSync(planDir, { recursive: true });
21
+ const written = join(planDir, STACK_SCAN_FILE);
22
+ writeFileSync(written, markdown, 'utf8');
23
+ return { ...result, markdown, written, exit: 0 };
24
+ }
25
+
26
+ export function stackScanCommand(featureName, options = {}) {
27
+ if (featureName) {
28
+ const owner = assertOwnerTree(featureName, process.cwd());
29
+ if (owner.blocked) {
30
+ for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec stack-scan ${featureName}`)) {
31
+ console.error(chalk[tone](text));
32
+ }
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ const result = runStackScan(featureName);
38
+ if (options.json) {
39
+ const { markdown, ...rest } = result;
40
+ console.log(JSON.stringify(rest, null, 2));
41
+ process.exit(result.exit);
42
+ }
43
+ if (result.error === 'feature-not-found') {
44
+ console.error(chalk.red(`stack-scan: a feature "${featureName}" não existe — rode morph-spec create ${featureName} antes`));
45
+ process.exit(2);
46
+ }
47
+ if (result.written) {
48
+ const high = result.deviations.filter((d) => d.severity === 'Bloqueante' || d.severity === 'Alto').length;
49
+ console.log(chalk.green(`✓ stack-scan: ${join(getFeatureDir(featureName), '2-plan', STACK_SCAN_FILE)} gravado`));
50
+ const summary = result.greenfield ? 'greenfield'
51
+ : !result.audited ? notAuditedReason(result)
52
+ : `${result.deviations.length} desvio(s), ${high} Bloqueante/Alto`;
53
+ console.log(chalk.gray(` ${summary}`));
54
+ } else {
55
+ process.stdout.write(result.markdown);
56
+ }
57
+ process.exit(result.exit);
58
+ }
@@ -1,26 +1,20 @@
1
1
  /**
2
- * `morph-spec standards --find "<query>" [--category <c>] [--tags <a,b>] [--json]`
2
+ * `morph-spec standards [--find "<query>"] [--show <id>[#<anchor>]] [--category <c>] [--tags <a,b>] [--json]`
3
3
  *
4
- * Server-side replacement for "read the whole STANDARDS.json to pick 1-3
5
- * standards" (R2 of the performance plan — token cut per task). Today
6
- * morph-implement loads the full 65 KB / 60-entry registry (with a `digest`
7
- * per entry) just to choose a couple of relevant standards: ~16k tokens per
8
- * task. This command searches server-side and returns only the TOP 3 hits —
9
- * `id · path · digest` — where the digest is the FULL text: it is what
10
- * substitutes for actually reading the standard's .md file.
4
+ * `--find` scores the registry server-side and returns the TOP 3 hits with
5
+ * digest and anchor keys, so nobody reads the whole registry to pick a
6
+ * standard. `--show <id>#<anchor>` prints only that section of the .md;
7
+ * `--show <id>` prints the digest and the anchors. Bare call: categories + counts.
11
8
  *
12
- * Without `--find` (a bare call): lists categories + counts — a cheap
13
- * orientation view, no scoring.
14
- *
15
- * A query with no hit exits 0 (this is advisory tooling, not a gate) with a
16
- * human-readable warning + category suggestions on stderr, and — under
17
- * `--json` — a stable `{ query, results: [], suggestion }` shape on stdout so
18
- * a JSON consumer never has to special-case "no results" as a parse error.
9
+ * A query with no hit exits 0 (advisory, not a gate); under `--json` the
10
+ * shape stays `{ query, results: [], suggestion }`.
19
11
  */
20
12
 
21
13
  import { existsSync, readFileSync } from 'fs';
22
- import { join } from 'path';
14
+ import { dirname, join } from 'path';
23
15
  import chalk from 'chalk';
16
+ import { loadStandardsRegistry } from '../../lib/standards/registry.js';
17
+ import { markdownHeadings, sectionBySlug } from '../../lib/standards/sections.js';
24
18
 
25
19
  /**
26
20
  * Resolve STANDARDS.json the same way doctor.js resolves the standards root:
@@ -41,10 +35,67 @@ export function resolveStandardsJsonPath(targetPath) {
41
35
  return null;
42
36
  }
43
37
 
44
- /** Load + parse STANDARDS.json. Throws on missing/invalid file — caller decides how to report it. */
45
- function loadRegistry(standardsJsonPath) {
46
- const raw = readFileSync(standardsJsonPath, 'utf8');
47
- return JSON.parse(raw);
38
+ /**
39
+ * The one section a `--show` target names. `anchor` is an anchor key of the
40
+ * registry entry (`regra`) or any heading slug of the .md (`#regra-principal`).
41
+ * @returns {{ status: 'ok', section: string, slug: string }
42
+ * | { status: 'unknown-anchor', anchors: Record<string, string>, headings: string[] }
43
+ * | { status: 'unreadable' }}
44
+ */
45
+ export function resolveStandardSection(standard, mdText, anchor) {
46
+ if (mdText === null) return { status: 'unreadable' };
47
+ const anchors = standard.anchors || {};
48
+ const slug = String(anchors[anchor] ?? anchor).replace(/^#/, '');
49
+ const section = sectionBySlug(mdText, slug);
50
+ if (section) return { status: 'ok', section, slug };
51
+ return { status: 'unknown-anchor', anchors, headings: markdownHeadings(mdText).map((h) => h.slug) };
52
+ }
53
+
54
+ function readStandardMd(standardsJsonPath, standard) {
55
+ try {
56
+ return readFileSync(join(dirname(standardsJsonPath), standard.path), 'utf8');
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ function showStandard(standards, standardsJsonPath, target, json) {
63
+ const hash = String(target).indexOf('#');
64
+ const id = hash === -1 ? String(target) : String(target).slice(0, hash);
65
+ const anchor = hash === -1 ? '' : String(target).slice(hash + 1).replace(/^#+/, '');
66
+ const standard = standards.find((s) => s.id === id);
67
+ if (!standard) {
68
+ const near = findStandards(standards, id.replace(/-/g, ' ')).map((s) => s.id);
69
+ if (json) console.log(JSON.stringify({ id, error: 'unknown-id', suggestions: near }));
70
+ else console.error(chalk.red(`\n❌ Standard "${id}" não existe.`) + (near.length ? chalk.gray(` Talvez: ${near.join(', ')}`) : '') + '\n');
71
+ process.exit(1);
72
+ }
73
+ const anchors = standard.anchors || {};
74
+
75
+ if (!anchor) {
76
+ if (json) {
77
+ console.log(JSON.stringify({ id: standard.id, path: standard.path, digest: standard.digest, anchors }));
78
+ process.exit(0);
79
+ }
80
+ console.log(chalk.cyan(`\n${standard.id}`) + chalk.gray(` (${standard.path})`));
81
+ console.log(`\n${standard.digest || '(sem digest)'}\n`);
82
+ console.log(chalk.bold('Âncoras') + chalk.gray(` — morph-spec standards --show ${standard.id}#<âncora>`));
83
+ for (const [key, slug] of Object.entries(anchors)) console.log(` ${key.padEnd(16)}${slug}`);
84
+ console.log('');
85
+ process.exit(0);
86
+ }
87
+
88
+ const result = resolveStandardSection(standard, readStandardMd(standardsJsonPath, standard), anchor);
89
+ if (result.status === 'ok') {
90
+ if (json) console.log(JSON.stringify({ id: standard.id, anchor, slug: result.slug, section: result.section }));
91
+ else process.stdout.write(result.section);
92
+ process.exit(0);
93
+ }
94
+ if (json) console.log(JSON.stringify({ id: standard.id, anchor, error: result.status, anchors }));
95
+ else if (result.status === 'unreadable') console.error(chalk.red(`\n❌ ${standard.path} não pôde ser lido.\n`));
96
+ else console.error(chalk.red(`\n❌ "${anchor}" não é âncora nem heading de ${standard.id}.`)
97
+ + chalk.gray(` Âncoras: ${Object.keys(anchors).join(', ') || '(nenhuma)'}\n`));
98
+ process.exit(1);
48
99
  }
49
100
 
50
101
  /**
@@ -145,7 +196,7 @@ function parseTagsOption(raw) {
145
196
  }
146
197
 
147
198
  /**
148
- * @param {{ find?: string, category?: string, tags?: string, json?: boolean, cwd?: string }} [options]
199
+ * @param {{ find?: string, show?: string, category?: string, tags?: string, json?: boolean, cwd?: string }} [options]
149
200
  */
150
201
  export function standardsCommand(options = {}) {
151
202
  const cwd = options.cwd || process.cwd();
@@ -159,13 +210,14 @@ export function standardsCommand(options = {}) {
159
210
 
160
211
  let registry;
161
212
  try {
162
- registry = loadRegistry(standardsJsonPath);
213
+ registry = loadStandardsRegistry(standardsJsonPath);
163
214
  } catch (err) {
164
215
  console.error(chalk.red(`\n❌ STANDARDS.json inválido em ${standardsJsonPath}: ${err.message}\n`));
165
216
  process.exit(1);
166
217
  }
167
218
 
168
- const standards = Array.isArray(registry.standards) ? registry.standards : [];
219
+ const standards = registry.standards;
220
+ if (options.show) showStandard(standards, standardsJsonPath, options.show, options.json);
169
221
  const query = (options.find || '').trim();
170
222
 
171
223
  // ── Bare call (no --find): categories + counts, cheap orientation ────────
@@ -180,7 +232,7 @@ export function standardsCommand(options = {}) {
180
232
  for (const { category, count } of counts) {
181
233
  console.log(` ${col(category, 20)}${count}`);
182
234
  }
183
- console.log(chalk.gray('\nUse --find "<query>" para buscar (top 3, com digest completo).\n'));
235
+ console.log(chalk.gray('\nUse --find "<query>" para buscar (top 3, com digest e âncoras) e --show <id>#<âncora> para ler uma seção.\n'));
184
236
  process.exit(0);
185
237
  }
186
238
 
@@ -201,7 +253,8 @@ export function standardsCommand(options = {}) {
201
253
  }
202
254
 
203
255
  const shaped = results.map((r) => ({
204
- id: r.id, name: r.name, path: r.path, category: r.category, digest: r.digest, score: r.score,
256
+ id: r.id, name: r.name, path: r.path, category: r.category, digest: r.digest,
257
+ anchors: Object.keys(r.anchors || {}), score: r.score,
205
258
  }));
206
259
 
207
260
  if (options.json) {
@@ -214,6 +267,7 @@ export function standardsCommand(options = {}) {
214
267
  console.log(chalk.cyan(`${i + 1}. ${r.id}`) + chalk.gray(` (${r.category})`));
215
268
  console.log(` ${r.path}`);
216
269
  console.log(chalk.gray(` ${r.digest}`));
270
+ if (r.anchors.length) console.log(` âncoras: ${r.anchors.join(', ')}` + chalk.gray(` → morph-spec standards --show ${r.id}#<âncora>`));
217
271
  console.log('');
218
272
  });
219
273
  process.exit(0);