@polymorphism-tech/morph-spec 8.47.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 (222) hide show
  1. package/CLAUDE.md +48 -39
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +37 -51
  4. package/bin/validate.js +3 -111
  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 +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-proposal.md +2 -50
  17. package/framework/commands/morph-status.md +3 -3
  18. package/framework/docs/CLI.md +585 -286
  19. package/framework/evals/review-checklist.md +82 -41
  20. package/framework/hooks/README.md +10 -11
  21. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +33 -3
  23. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  24. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  25. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  26. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  27. package/framework/hooks/claude-code/core/state-sync.js +7 -21
  28. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  29. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  30. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  31. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  32. package/framework/hooks/claude-code/statusline.py +55 -41
  33. package/framework/hooks/package.json +3 -0
  34. package/framework/hooks/shared/activity-logger.js +1 -1
  35. package/framework/hooks/shared/build-check.js +10 -6
  36. package/framework/hooks/shared/command-tokens.js +169 -0
  37. package/framework/hooks/shared/comment-rules.js +1 -1
  38. package/framework/hooks/shared/comment-scanner.js +10 -23
  39. package/framework/hooks/shared/comment-stripper.js +1 -1
  40. package/framework/hooks/shared/e2e-check.js +64 -27
  41. package/framework/hooks/shared/errata.js +72 -0
  42. package/framework/hooks/shared/feature-locator.js +1 -1
  43. package/framework/hooks/shared/frontend-locations.js +6 -0
  44. package/framework/hooks/shared/gate-question.js +18 -0
  45. package/framework/hooks/shared/phase-utils.js +15 -32
  46. package/framework/hooks/shared/slice-progress.js +118 -0
  47. package/framework/hooks/shared/spawn-command.js +23 -0
  48. package/framework/hooks/shared/state-reader.js +61 -87
  49. package/framework/hooks/shared/state-writer.js +2 -3
  50. package/framework/mcp/registry.json +3 -4
  51. package/framework/phases.json +23 -39
  52. package/framework/schemas/config.schema.json +14 -9
  53. package/framework/schemas/feature.schema.json +15 -50
  54. package/framework/skills/README.md +3 -4
  55. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  56. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  57. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  58. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  59. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  60. package/framework/skills/core/morph-media/SKILL.md +1 -1
  61. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  62. package/framework/skills/core/morph-review/SKILL.md +111 -136
  63. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  64. package/framework/skills/core/morph-uiux/SKILL.md +77 -152
  65. package/framework/standards/STANDARDS.json +86 -1218
  66. package/framework/standards/STANDARDS.meta.json +1305 -0
  67. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  68. package/framework/standards/ai-agents/setup.md +1 -1
  69. package/framework/standards/architecture/code-comments.md +6 -6
  70. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  71. package/framework/templates/README.md +2 -3
  72. package/framework/templates/REGISTRY.json +6 -170
  73. package/framework/templates/docs/plan.md +72 -0
  74. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  75. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  76. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  77. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  78. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  79. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  80. package/package.json +1 -1
  81. package/src/commands/e2e/e2e.js +2 -2
  82. package/src/commands/observability/cost.js +18 -60
  83. package/src/commands/project/doctor-procs.js +1 -1
  84. package/src/commands/project/doctor.js +9 -9
  85. package/src/commands/project/fleet.js +4 -4
  86. package/src/commands/project/heavy.js +3 -3
  87. package/src/commands/project/init.js +4 -0
  88. package/src/commands/project/setup-infra-cmd.js +2 -1
  89. package/src/commands/project/stack-scan.js +58 -0
  90. package/src/commands/project/standards.js +79 -25
  91. package/src/commands/project/status.js +29 -69
  92. package/src/commands/project/update.js +17 -1
  93. package/src/commands/project/worktree.js +1 -1
  94. package/src/commands/state/advance.js +62 -46
  95. package/src/commands/state/approve.js +81 -141
  96. package/src/commands/state/archive.js +20 -84
  97. package/src/commands/state/auto-approval.js +68 -0
  98. package/src/commands/state/create.js +28 -1
  99. package/src/commands/state/delete.js +1 -1
  100. package/src/commands/state/finish.js +11 -23
  101. package/src/commands/state/gate-check.js +85 -84
  102. package/src/commands/state/gate-decision.js +6 -4
  103. package/src/commands/state/handoff.js +1 -1
  104. package/src/commands/state/list.js +9 -9
  105. package/src/commands/state/report-check.js +157 -311
  106. package/src/commands/state/retype.js +9 -3
  107. package/src/commands/validation/mutate.js +77 -14
  108. package/src/commands/validation/plan-check.js +81 -0
  109. package/src/commands/validation/validate-feature.js +60 -135
  110. package/src/commands/validation/verify.js +40 -72
  111. package/src/core/paths/output-schema.js +19 -66
  112. package/src/core/state/gate-order.js +5 -6
  113. package/src/core/state/lifecycle-audit.js +2 -2
  114. package/src/core/state/state-manager.js +17 -98
  115. package/src/core/templates/template-registry.js +2 -2
  116. package/src/core/templates/template-seed.js +22 -0
  117. package/src/core/work-types.js +10 -10
  118. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  119. package/src/dashboard/app/components/CommandPalette.jsx +5 -5
  120. package/src/dashboard/app/components/EntityLink.jsx +1 -1
  121. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  122. package/src/dashboard/app/lib/phases.js +2 -4
  123. package/src/dashboard/app/pages/Activity.jsx +1 -1
  124. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  125. package/src/dashboard/app/pages/Features.jsx +9 -9
  126. package/src/dashboard/app/pages/Guide.jsx +2 -2
  127. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  128. package/src/dashboard/app/pages/Standards.jsx +1 -1
  129. package/src/dashboard/scanner/activity-scanner.js +5 -15
  130. package/src/dashboard/scanner/agents-scanner.js +1 -1
  131. package/src/dashboard/scanner/docs-scanner.js +2 -2
  132. package/src/dashboard/scanner/features-scanner.js +95 -60
  133. package/src/dashboard/scanner/project-api.js +1 -1
  134. package/src/dashboard/scanner/standards-scanner.js +2 -1
  135. package/src/lib/ci/ci-verify.js +17 -1
  136. package/src/lib/ci/github-ci.js +1 -1
  137. package/src/lib/detectors/index.js +3 -8
  138. package/src/lib/generators/handoff-generator.js +34 -65
  139. package/src/lib/generators/settings-generator.js +11 -89
  140. package/src/lib/git/evidence-staleness.js +1 -1
  141. package/src/lib/git/tree-fingerprint.js +19 -7
  142. package/src/lib/git/tree-state.js +2 -2
  143. package/src/lib/heavy/cleanup.js +1 -5
  144. package/src/lib/installers/memory-installer.js +6 -8
  145. package/src/lib/mutation/commands.js +3 -2
  146. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  147. package/src/lib/mutation/plan.js +14 -6
  148. package/src/lib/mutation/runner.js +1 -1
  149. package/src/lib/mutation/subset.js +46 -9
  150. package/src/lib/plan/artifact-vocabulary.js +60 -0
  151. package/src/lib/plan/checks.js +234 -0
  152. package/src/lib/plan/plan-parser.js +209 -0
  153. package/src/lib/plan/plan-template.js +18 -0
  154. package/src/lib/review-reports.js +222 -0
  155. package/src/lib/risk-detector.js +131 -140
  156. package/src/lib/specs/adr-index.js +50 -69
  157. package/src/lib/specs/design-seed.js +18 -0
  158. package/src/lib/specs/frontmatter.js +44 -0
  159. package/src/lib/specs/gfm-table.js +17 -0
  160. package/src/lib/specs/old-format.js +68 -0
  161. package/src/lib/specs/revocations.js +37 -95
  162. package/src/lib/specs/screen-checklist.js +18 -17
  163. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  164. package/src/lib/stack/stack-scan.js +267 -0
  165. package/src/lib/standards/registry.js +43 -0
  166. package/src/lib/standards/sections.js +58 -0
  167. package/src/lib/tasks/eval-node.js +4 -12
  168. package/src/lib/tasks/test-nodes.js +1 -74
  169. package/src/lib/tasks/test-runner.js +75 -117
  170. package/src/lib/telemetry/metrics.js +8 -69
  171. package/src/lib/telemetry/telemetry-reader.js +5 -118
  172. package/src/lib/telemetry/verify-event.js +5 -7
  173. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  174. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  175. package/src/lib/validators/reuse/reuse-validator.js +22 -45
  176. package/src/lib/validators/traceability-check.js +85 -129
  177. package/src/lib/validators/verify-runner.js +118 -235
  178. package/src/lib/worktree/docker-audit.js +1 -2
  179. package/src/lib/worktree/fleet-scanner.js +25 -38
  180. package/src/scripts/setup-infra.js +9 -0
  181. package/src/utils/agents-installer.js +76 -9
  182. package/src/utils/claude-settings-manager.js +0 -35
  183. package/src/utils/file-copier.js +27 -1
  184. package/src/utils/hooks-installer.js +16 -9
  185. package/src/utils/skills-installer.js +1 -2
  186. package/framework/evals/trace-schema.md +0 -67
  187. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  188. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  189. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  190. package/framework/hooks/shared/tasks-validator.js +0 -111
  191. package/framework/rules/morph-task-tracking.md +0 -54
  192. package/framework/rules/morph-workflow.md +0 -244
  193. package/framework/schemas/tasks.schema.json +0 -79
  194. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  195. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  196. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  197. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  198. package/framework/templates/docs/proposal.md +0 -192
  199. package/framework/templates/docs/spec.md +0 -57
  200. package/framework/templates/docs/ui-components.md +0 -52
  201. package/framework/templates/docs/ui-design-system.md +0 -76
  202. package/framework/templates/docs/ui-flows.md +0 -167
  203. package/framework/templates/examples/spec-examples.md +0 -91
  204. package/framework/templates/feature/decisions.md +0 -124
  205. package/framework/templates/feature/recap.md +0 -145
  206. package/framework/templates/tasks.template.json +0 -17
  207. package/src/commands/state/usecase.js +0 -143
  208. package/src/commands/tasks/dag.js +0 -181
  209. package/src/lib/agents/output-collector.js +0 -76
  210. package/src/lib/evaluator-report.js +0 -104
  211. package/src/lib/specs/acervo-format.js +0 -70
  212. package/src/lib/specs/acervo-lifecycle.js +0 -264
  213. package/src/lib/specs/done-criteria-check.js +0 -200
  214. package/src/lib/specs/usecase-check.js +0 -301
  215. package/src/lib/specs/usecase-promoter.js +0 -301
  216. package/src/lib/specs/usecase-registry.js +0 -237
  217. package/src/lib/tasks/dispatch-planner.js +0 -319
  218. package/src/lib/tasks/mermaid-renderer.js +0 -132
  219. package/src/lib/tasks/task-classifier.js +0 -30
  220. package/src/lib/tasks/task-parser.js +0 -209
  221. package/src/lib/tasks/tasks-json-parser.js +0 -296
  222. package/src/lib/tasks/test-scope.js +0 -597
@@ -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);
@@ -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) {
@@ -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
@@ -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);