@polymorphism-tech/morph-spec 8.37.1 → 8.38.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 (128) hide show
  1. package/CLAUDE.md +19 -6
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +16 -7
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +3 -3
  8. package/framework/MORPH.md +25 -22
  9. package/framework/agents.json +76 -27
  10. package/framework/docs/CLI.md +54 -17
  11. package/framework/evals/rubrics/code-quality.md +5 -0
  12. package/framework/evals/rubrics/condensed.md +6 -2
  13. package/framework/evals/rubrics/contracts.md +20 -0
  14. package/framework/evals/rubrics/test-coverage.md +17 -6
  15. package/framework/hooks/README.md +9 -3
  16. package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
  17. package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
  18. package/framework/hooks/claude-code/core/validate-completion.js +16 -90
  19. package/framework/hooks/claude-code/core/worktree-context.js +10 -0
  20. package/framework/hooks/shared/command-tokens.js +96 -0
  21. package/framework/hooks/shared/git-exclude.js +44 -0
  22. package/framework/hooks/shared/git-paths.js +52 -0
  23. package/framework/hooks/shared/graph-freshness.js +380 -0
  24. package/framework/hooks/shared/graph-refresh.js +416 -0
  25. package/framework/hooks/shared/morph-cli.js +17 -5
  26. package/framework/hooks/shared/redact-secrets.js +55 -0
  27. package/framework/hooks/shared/telemetry-logger.js +25 -2
  28. package/framework/hooks/shared/transcript-usage.js +80 -0
  29. package/framework/rubrics/backend.json +2 -2
  30. package/framework/rubrics/default.json +2 -2
  31. package/framework/rubrics/frontend.json +2 -2
  32. package/framework/rules/csharp-standards.md +1 -0
  33. package/framework/rules/morph-workflow.md +1 -0
  34. package/framework/rules/nextjs-standards.md +1 -0
  35. package/framework/schemas/config.schema.json +41 -1
  36. package/framework/schemas/feature.schema.json +5 -1
  37. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  38. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  39. package/framework/skills/core/morph-apply/SKILL.md +36 -11
  40. package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
  41. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  42. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  43. package/framework/skills/core/morph-implement/SKILL.md +39 -10
  44. package/framework/skills/core/morph-media/SKILL.md +1 -1
  45. package/framework/skills/core/morph-plan/SKILL.md +72 -15
  46. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  47. package/framework/skills/core/morph-review/SKILL.md +51 -13
  48. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  49. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  50. package/framework/skills/core/morph-uiux/SKILL.md +34 -9
  51. package/framework/standards/STANDARDS.json +187 -12
  52. package/framework/standards/ai-agents/testing-ai.md +2 -1
  53. package/framework/standards/architecture/code-comments.md +64 -0
  54. package/framework/standards/backend/dotnet/testing.md +22 -0
  55. package/framework/standards/frontend/admin-app/data-table.md +562 -0
  56. package/framework/standards/frontend/admin-app/feedback.md +236 -0
  57. package/framework/standards/frontend/admin-app/forms.md +232 -0
  58. package/framework/standards/frontend/admin-app/page-structure.md +203 -0
  59. package/framework/standards/frontend/nextjs/components.md +37 -58
  60. package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
  61. package/framework/standards/frontend/nextjs/forms.md +31 -30
  62. package/framework/standards/frontend/nextjs/state-management.md +2 -1
  63. package/framework/standards/frontend/nextjs/testing.md +18 -0
  64. package/framework/templates/context/design-system.md +3 -3
  65. package/framework/templates/docs/proposal.md +11 -0
  66. package/framework/templates/docs/spec.md +15 -0
  67. package/framework/templates/docs/ui-mockups.md +115 -98
  68. package/framework/templates/feature/decisions.md +124 -122
  69. package/package.json +1 -1
  70. package/src/commands/graph/affected.js +4 -3
  71. package/src/commands/graph/build.js +109 -18
  72. package/src/commands/graph/explain.js +8 -5
  73. package/src/commands/graph/status.js +14 -8
  74. package/src/commands/observability/cost.js +148 -18
  75. package/src/commands/project/doctor.js +82 -10
  76. package/src/commands/project/update.js +25 -0
  77. package/src/commands/project/worktree.js +73 -90
  78. package/src/commands/state/advance.js +18 -0
  79. package/src/commands/state/approve.js +15 -0
  80. package/src/commands/state/archive.js +62 -9
  81. package/src/commands/state/finish.js +62 -14
  82. package/src/commands/state/gate-check.js +8 -1
  83. package/src/commands/state/gate-decision.js +8 -4
  84. package/src/commands/state/report-check.js +90 -9
  85. package/src/commands/state/usecase.js +66 -0
  86. package/src/commands/tasks/dag.js +17 -2
  87. package/src/commands/validation/validate-feature.js +6 -1
  88. package/src/commands/validation/verify.js +20 -1
  89. package/src/dashboard/scanner/agents-scanner.js +5 -1
  90. package/src/dashboard/scanner/hooks-scanner.js +8 -1
  91. package/src/dashboard/scanner/standards-scanner.js +2 -1
  92. package/src/lib/ai/comment-stripper.js +101 -4
  93. package/src/lib/evaluator-report.js +25 -0
  94. package/src/lib/generators/handoff-generator.js +4 -18
  95. package/src/lib/git/default-branch.js +52 -0
  96. package/src/lib/git/tree-fingerprint.js +58 -0
  97. package/src/lib/git/tree-state.js +167 -0
  98. package/src/lib/graph/god-nodes.js +12 -3
  99. package/src/lib/graph/graph-health.js +48 -2
  100. package/src/lib/graph/graph-loader.js +99 -25
  101. package/src/lib/graph/graphifyignore.js +145 -28
  102. package/src/lib/graph/sanitize.js +117 -11
  103. package/src/lib/risk-detector.js +90 -0
  104. package/src/lib/specs/acervo-format.js +70 -0
  105. package/src/lib/specs/acervo-lifecycle.js +264 -0
  106. package/src/lib/specs/adr-index.js +169 -0
  107. package/src/lib/specs/revocations.js +156 -0
  108. package/src/lib/specs/screen-checklist.js +357 -0
  109. package/src/lib/specs/usecase-check.js +20 -16
  110. package/src/lib/specs/usecase-promoter.js +103 -38
  111. package/src/lib/specs/usecase-registry.js +7 -7
  112. package/src/lib/tasks/tasks-json-parser.js +2 -3
  113. package/src/lib/telemetry/metrics.js +386 -0
  114. package/src/lib/telemetry/telemetry-reader.js +161 -51
  115. package/src/lib/telemetry/verify-event.js +79 -0
  116. package/src/lib/validators/code-budget/code-budget-validator.js +172 -0
  117. package/src/lib/validators/code-budget/comment-scanner.js +75 -0
  118. package/src/lib/validators/code-budget/index.js +9 -0
  119. package/src/lib/validators/reuse/reuse-validator.js +4 -2
  120. package/src/lib/validators/reuse/symbol-scanner.js +27 -22
  121. package/src/lib/validators/validation-runner.js +21 -5
  122. package/src/lib/validators/verify-runner.js +11 -0
  123. package/src/lib/worktree/finish-backup.js +739 -0
  124. package/src/lib/worktree/ignored-triage.js +15 -8
  125. package/src/lib/worktree/worktree-audit.js +5 -0
  126. package/src/scripts/setup-infra.js +29 -18
  127. package/src/utils/agents-installer.js +281 -12
  128. package/src/utils/hooks-installer.js +75 -15
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Extração determinística de declarações de tipo a partir do TEXTO do arquivo.
3
3
  *
4
- * Por que não ler as declarações do grafo: o grafo é reconstruído em background
5
- * pelo hook post-commit do graphify, então o código que acabou de ser escrito
6
- * (o caso que este validador existe para pegar) ainda NÃO está nele. O grafo
7
- * responde "o que já existia"; o disco responde "o que esta feature acabou de
8
- * declarar". Inverter os dois papéis produziria um validador cego justamente na
9
- * janela em que ele precisa morder — ver graph-loader.js `getFreshness`.
4
+ * Por que não ler as declarações do grafo: o grafo só é reconstruído depois de
5
+ * um commit (o refresh em background de framework/hooks/shared/graph-refresh.js),
6
+ * então o código que acabou de ser escrito — o caso que este validador existe
7
+ * para pegar — ainda NÃO está nele. O grafo responde "o que já existia"; o
8
+ * disco responde "o que esta feature acabou de declarar". Inverter os dois
9
+ * papéis produziria um validador cego justamente na janela em que ele precisa
10
+ * morder — ver graph-loader.js `getFreshness`.
10
11
  *
11
12
  * A varredura é por linha, com regex ancorada no início da declaração. Mesma
12
13
  * limitação (e mesmo grau de tolerância) do design-system-validator: uma
@@ -16,20 +17,11 @@
16
17
  * @module reuse-symbol-scanner
17
18
  */
18
19
 
19
- import { languageOf } from '../../graph/sanitize.js';
20
+ import { isTestPath, languageOf } from '../../graph/sanitize.js';
20
21
 
21
22
  /** Extensões que este scanner sabe ler. Fora daqui, o arquivo é ignorado. */
22
23
  const SCANNABLE = /\.(cs|ts|tsx|js|jsx|mjs|cjs)$/i;
23
24
 
24
- /**
25
- * Arquivo de teste, em qualquer das convenções que os dois stacks usam.
26
- * Duplicar um stub entre três arquivos de teste (`ResizeObserverStub` no
27
- * ProspectPRO) é ruído conhecido: teste é código descartável por natureza e a
28
- * regra de reuso não se aplica a ele. Cobre `__tests__/`, `.test.`, `.spec.`,
29
- * e os projetos `*.Tests/` do .NET.
30
- */
31
- const TEST_FILE = /(^|\/)__tests__\/|\.(test|spec)\.[jt]sx?$|(^|\/)(?:[Tt]ests?|[^/]*\.[Tt]ests?|[^/]*[a-z0-9_]Tests?)\//;
32
-
33
25
  /**
34
26
  * Diretórios que contêm arquivos com extensão de código mas que não são o
35
27
  * código-fonte do projeto. `.morph/` é o caso que apareceu de verdade: o
@@ -71,17 +63,30 @@ const WEB_DECL =
71
63
  /** Linha que é claramente comentário — não vale a pena parsear. */
72
64
  const COMMENT_LINE = /^\s*(?:\/\/|\/\*|\*|<!--)/;
73
65
 
66
+ /**
67
+ * @param {string} sourceFile
68
+ * @returns {boolean} código do projeto numa extensão lida, fora de `.d.ts` e de
69
+ * pasta que não é fonte — teste incluído
70
+ */
71
+ export function isProjectSource(sourceFile) {
72
+ const f = String(sourceFile || '').replace(/\\/g, '/');
73
+ return SCANNABLE.test(f) && !DECLARATION_FILE.test(f) && !NOT_SOURCE.test(f);
74
+ }
75
+
76
+ /**
77
+ * @param {string} sourceFile
78
+ * @returns {boolean} arquivo de teste, pelo mesmo predicado do grafo
79
+ */
80
+ export function isTestFile(sourceFile) {
81
+ return isTestPath(String(sourceFile || '').replace(/\\/g, '/'));
82
+ }
83
+
74
84
  /**
75
85
  * @param {string} sourceFile
76
86
  * @returns {boolean} o arquivo entra na varredura de reuso
77
87
  */
78
88
  export function isScannableFile(sourceFile) {
79
- const f = String(sourceFile || '').replace(/\\/g, '/');
80
- if (!SCANNABLE.test(f)) return false;
81
- if (DECLARATION_FILE.test(f)) return false;
82
- if (TEST_FILE.test(f)) return false;
83
- if (NOT_SOURCE.test(f)) return false;
84
- return true;
89
+ return isProjectSource(sourceFile) && !isTestFile(sourceFile);
85
90
  }
86
91
 
87
92
  /**
@@ -24,7 +24,7 @@ import { aggregateScores, loadRubric } from './score-aggregator.js';
24
24
  */
25
25
  const HEAVY_VALIDATORS = new Set([
26
26
  'architecture', 'packages', 'contract-compliance',
27
- 'css', 'design-system', 'reuse'
27
+ 'css', 'design-system', 'reuse', 'code-budget'
28
28
  ]);
29
29
 
30
30
  /**
@@ -89,6 +89,11 @@ export async function runValidation(projectPath, featureName, options = {}) {
89
89
  if (!options.skipReuse && mode !== 'light') {
90
90
  validatorIds.push('reuse');
91
91
  }
92
+ // Mesma política, pelo mesmo motivo: comentário e orçamento de teste valem
93
+ // em qualquer stack, e a medida é da feature, não de um checkpoint.
94
+ if (mode !== 'light') {
95
+ validatorIds.push('code-budget');
96
+ }
92
97
 
93
98
  if (validatorIds.length === 0) {
94
99
  // Say which of the two it is. On a pure `dotnet` project every default
@@ -135,6 +140,7 @@ export async function runValidation(projectPath, featureName, options = {}) {
135
140
  // projeto sem grafo). Um validador assim não acha nada — e "não achou nada"
136
141
  // é indistinguível de "verificou e está limpo" se ninguém contar.
137
142
  const selfSkipped = [];
143
+ result.skipped = selfSkipped;
138
144
 
139
145
  // Run each validator
140
146
  for (const validatorId of uniqueValidators) {
@@ -149,15 +155,20 @@ export async function runValidation(projectPath, featureName, options = {}) {
149
155
 
150
156
  if (validatorResult) {
151
157
  if (validatorResult._skipReason) {
152
- selfSkipped.push(`${validatorId}: ${validatorResult._skipReason}`);
158
+ selfSkipped.push({
159
+ validator: validatorId,
160
+ reason: validatorResult._skipReason,
161
+ unavailable: validatorResult._skipUnavailable === true,
162
+ });
153
163
  }
154
164
  const { errors, warnings, issues } = normalizeValidatorResult(validatorId, validatorResult);
155
165
  result.errors += errors;
156
166
  result.warnings += warnings;
157
167
  result.issues.push(...issues);
158
168
 
159
- // Store per-validator results for hooks
160
- result.results[validatorId] = { passed: errors === 0, issues };
169
+ // A validator that skipped itself checked nothing: in `results` it would
170
+ // be scored as a clean 10 and dilute the ones that ran.
171
+ if (!validatorResult._skipReason) result.results[validatorId] = { passed: errors === 0, issues };
161
172
  }
162
173
  } catch (err) {
163
174
  // A validator that throws is a FAILED validation, not a silent skip.
@@ -187,7 +198,7 @@ export async function runValidation(projectPath, featureName, options = {}) {
187
198
  // `validators: pass` do `morph-spec verify` tendo verificado exatamente nada
188
199
  // — o modo de falha que este repositório já pagou caro (guardrail inerte).
189
200
  if (selfSkipped.length > 0 && selfSkipped.length === uniqueValidators.length) {
190
- result._skipReason = `No validators ran — ${selfSkipped.join('; ')}`;
201
+ result._skipReason = `No validators ran — ${selfSkipped.map((s) => `${s.validator}: ${s.reason}`).join('; ')}`;
191
202
  result.summary = result._skipReason;
192
203
  return result;
193
204
  }
@@ -325,6 +336,11 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
325
336
  return await validateReuse(projectPath, featureName, options);
326
337
  }
327
338
 
339
+ case 'code-budget': {
340
+ const { validateCodeBudget } = await import('./code-budget/code-budget-validator.js');
341
+ return await validateCodeBudget(projectPath, featureName, options);
342
+ }
343
+
328
344
  case 'nextjs-component': {
329
345
  const { validateNextComponentFiles } = await import('./nextjs/next-component-validator.js');
330
346
  return await validateNextComponentFiles(projectPath, options);
@@ -975,12 +975,15 @@ async function runValidatorsNode({ projectPath, featureName, rubricId, _runValid
975
975
 
976
976
  const durationMs = Date.now() - start;
977
977
 
978
+ const skipped = validationResult?.skipped ?? [];
979
+
978
980
  if (validationResult && validationResult._skipReason) {
979
981
  return {
980
982
  status: 'skip',
981
983
  score: null,
982
984
  action: null,
983
985
  ran: [],
986
+ skipped,
984
987
  reason: validationResult._skipReason,
985
988
  priorityFixes: [],
986
989
  durationMs,
@@ -995,6 +998,7 @@ async function runValidatorsNode({ projectPath, featureName, rubricId, _runValid
995
998
  score: scoreResult.score,
996
999
  action: scoreResult.action,
997
1000
  ran: Object.keys(validationResult.results || {}),
1001
+ skipped,
998
1002
  reason: null,
999
1003
  priorityFixes: scoreResult.priorityFixes || [],
1000
1004
  durationMs,
@@ -1002,6 +1006,13 @@ async function runValidatorsNode({ projectPath, featureName, rubricId, _runValid
1002
1006
  }
1003
1007
 
1004
1008
  function appendValidatorsFeedback(feedback, validatorsNode) {
1009
+ // A validator that could not check (reuse without a graph) is invisible in a
1010
+ // green score; when the whole node skipped, its own line already says why.
1011
+ if (validatorsNode.status !== 'skip') {
1012
+ for (const s of validatorsNode.skipped || []) {
1013
+ if (s.unavailable) feedback.push(`[validators/${s.validator}] não rodou: ${s.reason}`);
1014
+ }
1015
+ }
1005
1016
  for (const fix of validatorsNode.priorityFixes || []) {
1006
1017
  const topIssue = fix.issues && fix.issues[0];
1007
1018
  if (!topIssue) continue;