@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
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { templateSourcePath, writeIfAbsent } from '../../core/templates/template-seed.js';
4
+ import { renderTemplateString } from '../../core/templates/template-renderer.js';
5
+ import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
6
+
7
+ export function seedDesignArtifacts(featureName, cwd = process.cwd()) {
8
+ const mockupsTemplate = readFileSync(templateSourcePath('ui-mockups', cwd), 'utf8');
9
+ const mockups = writeIfAbsent(
10
+ getAbsoluteOutputPath(cwd, featureName, 'uiMockups'),
11
+ renderTemplateString(mockupsTemplate, { FEATURE_NAME: featureName, DATE: new Date().toISOString().slice(0, 10) }),
12
+ );
13
+ const designSystem = writeIfAbsent(
14
+ join(cwd, '.morph', 'context', 'design-system.md'),
15
+ readFileSync(templateSourcePath('context-design-system', cwd), 'utf8'),
16
+ );
17
+ return { mockups, designSystem };
18
+ }
@@ -0,0 +1,44 @@
1
+ const FRONTMATTER_RE = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/;
2
+
3
+ /**
4
+ * Set (or replace) a single top-level frontmatter field, preserving every
5
+ * other line, the original EOL style, and anything after the closing `---`.
6
+ * A document with no frontmatter block is returned unchanged.
7
+ *
8
+ * @param {string} content
9
+ * @param {string} key
10
+ * @param {string} value - written as-is; quote it first when it is free text
11
+ * @returns {string}
12
+ */
13
+ export function setFrontmatterField(content, key, value) {
14
+ const m = FRONTMATTER_RE.exec(content);
15
+ if (!m || m.index !== 0) return content;
16
+
17
+ const [full, open, body, close] = m;
18
+ const eol = full.includes('\r\n') ? '\r\n' : '\n';
19
+ const fieldLine = new RegExp(`^${key}:[ \\t]*.*$`, 'm');
20
+ // Function replacer: a value carrying `$&` or `$1` is text, not a pattern.
21
+ const next = fieldLine.test(body)
22
+ ? body.replace(fieldLine, () => `${key}: ${value}`)
23
+ : `${body}${eol}${key}: ${value}`;
24
+
25
+ return open + next + close + content.slice(full.length);
26
+ }
27
+
28
+ /**
29
+ * Read a single top-level frontmatter field; `null` when absent or there is no
30
+ * frontmatter block. A double-quoted value is read as the JSON string it is
31
+ * written as; a single-quoted one as YAML reads it (`''` is a quote).
32
+ */
33
+ export function readFrontmatterField(content, key) {
34
+ const m = FRONTMATTER_RE.exec(content);
35
+ if (!m || m.index !== 0) return null;
36
+ const line = new RegExp(`^${key}:[ \\t]*(.+?)[ \\t]*$`, 'm').exec(m[2]);
37
+ if (!line) return null;
38
+ const raw = line[1].trim();
39
+ if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
40
+ try { return JSON.parse(raw); } catch { return raw; }
41
+ }
42
+ if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/''/g, "'");
43
+ return raw;
44
+ }
@@ -0,0 +1,17 @@
1
+ /** A markdown table separator row: `|---|:--:|`, with or without the outer pipes. */
2
+ export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
3
+
4
+ /** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
5
+ export const isTableLine = (line) => /(?<!\\)\|/.test(line);
6
+
7
+ /**
8
+ * Split a table row on unescaped pipes, outer pipes optional; `\|` inside a
9
+ * cell is a literal pipe.
10
+ *
11
+ * @param {string} row
12
+ * @returns {string[]} trimmed cells
13
+ */
14
+ export function tableCells(row) {
15
+ const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
16
+ return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
17
+ }
@@ -0,0 +1,68 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'fs';
2
+ import { join, resolve } from 'path';
3
+ import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
4
+ import { parseWorktreeList } from '../../commands/project/worktree.js';
5
+
6
+ const FINISHED = new Set(['completed', 'abandoned']);
7
+ const PREVIOUS_LINE = '8.47';
8
+
9
+ export function isOldFormatFeature(featureDir) {
10
+ const plan = join(featureDir, OUTPUT_SCHEMA.plan.phaseDir, OUTPUT_SCHEMA.plan.filename);
11
+ if (existsSync(plan)) return false;
12
+ return existsSync(join(featureDir, '2-plan', 'tasks.json')) || existsSync(join(featureDir, '0-proposal'));
13
+ }
14
+
15
+ function featureStatus(featureDir) {
16
+ try {
17
+ return JSON.parse(readFileSync(join(featureDir, 'feature.json'), 'utf8'))?.status ?? null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function featureDirs(tree) {
24
+ const base = join(tree, '.morph', 'features');
25
+ try {
26
+ return readdirSync(base, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => [e.name, join(base, e.name)]);
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Features still in flight, in the root and in every git worktree, written in
34
+ * the layout this CLI no longer drives. Archived ones live outside `.morph/features/`.
35
+ *
36
+ * @param {string} root
37
+ * @param {{ listTrees?: (cwd: string) => Array<{ path: string }> }} [deps]
38
+ * @returns {Array<{ feature: string, trees: string[] }>}
39
+ */
40
+ export function findOldFormatFeatures(root, { listTrees = parseWorktreeList } = {}) {
41
+ const trees = [resolve(root)];
42
+ for (const wt of listTrees(root)) {
43
+ const p = resolve(wt.path);
44
+ if (!trees.some((t) => t.toLowerCase() === p.toLowerCase())) trees.push(p);
45
+ }
46
+ const found = new Map();
47
+ for (const tree of trees) {
48
+ for (const [name, dir] of featureDirs(tree)) {
49
+ if (FINISHED.has(featureStatus(dir)) || !isOldFormatFeature(dir)) continue;
50
+ if (!found.has(name)) found.set(name, []);
51
+ found.get(name).push(tree);
52
+ }
53
+ }
54
+ return [...found.entries()].map(([feature, where]) => ({ feature, trees: where }));
55
+ }
56
+
57
+ export function oldFormatGuardLines(found, root) {
58
+ const lines = [
59
+ `Recusado: ${found.length} feature(s) em voo no formato anterior ao plan.md (proposal/spec/tasks.json), que esta versão não conduz:`,
60
+ ];
61
+ for (const { feature, trees } of found) {
62
+ const where = trees.map((t) => (resolve(t) === resolve(root) ? 'raiz' : t)).join(', ');
63
+ lines.push(` - ${feature} (${where})`);
64
+ }
65
+ lines.push(` Termine cada uma na ${PREVIOUS_LINE}: \`npx @polymorphism-tech/morph-spec@${PREVIOUS_LINE} <comando>\` (por exemplo \`finish <feature> --pr\`),`);
66
+ lines.push(' ou arquive sem integrar: `morph-spec archive <feature> --force` (o Gate 3 dela não foi aprovado). Depois rode o `update` de novo.');
67
+ return lines;
68
+ }
@@ -1,156 +1,98 @@
1
1
  /**
2
- * The plan's declaration of what a feature supersedes in the project's acervo:
3
- * the "UCs e ADRs que esta feature altera ou revoga" section of `spec.md`.
4
- *
5
- * ## UCs e ADRs que esta feature altera ou revoga
6
- *
7
- * | Id | Mudança | Substituto | Escopo em que o antigo ainda vale |
8
- * |---|---|---|---|
9
- * | UC-{nn} | parcial | UC-{nn} | {onde o antigo ainda vale} |
10
- * | {feature}/ADR-{nnn} | total | — | — |
11
- *
12
- * Columns are positional, so the header wording is free. Parsing is pure: the
13
- * finish applies the entries (`acervo-lifecycle.js`), and `dag`/`usecase
14
- * check` report the problems while `spec.md` can still be edited — it freezes
15
- * at Gate 2, so a bad row found at close time can no longer be fixed there.
16
- *
17
- * Nothing ambiguous is applied: a row with any problem, and every row of an id
18
- * declared twice, stays out of `entries`. A table inside a code fence is an
19
- * example, never a declaration.
2
+ * The ADRs of the project index that a plan revokes: the table under
3
+ * `### Conflitos com o acervo` whose first header cell is `ADR`, with columns
4
+ * `{feature}/ADR-{nnn}`, `total|parcial` and the scope where the old one still holds.
5
+ * plan-check reports the problems while the plan can still change (it freezes
6
+ * at the plan gate); nothing ambiguous reaches the finish.
20
7
  */
21
8
 
22
- import { SEPARATOR_ROW_RE, UC_ID_RE, isTableLine, tableCells } from './acervo-format.js';
23
-
24
- export const REVOCATIONS_TITLE = 'UCs e ADRs que esta feature altera ou revoga';
9
+ import { SEPARATOR_ROW_RE, isTableLine, tableCells } from './gfm-table.js';
25
10
 
26
11
  /** ADR numbers restart in every feature, so only `{feature}/ADR-{nnn}` is unambiguous. */
27
12
  export const ADR_REF_RE = /^[a-z0-9][a-z0-9-]*\/ADR-\d+$/;
28
13
 
29
- const HEADING_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
30
14
  const FENCE_RE = /^\s*(```|~~~)/;
31
- const ID_TOKEN_RE = /\b(?:UC|ADR)-\d+\b/;
32
- /** A list item: the shape a revocation takes when the author skips the table. */
15
+ const REF_TOKEN_RE = /\b[a-z0-9][a-z0-9-]*\/ADR-\d+\b/;
33
16
  const LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s/;
34
-
35
17
  const CHANGES = new Map([['total', 'total'], ['parcial', 'parcial'], ['partial', 'parcial']]);
36
18
 
37
- /** `—`, `–`, `-` and an empty cell all mean "none". */
38
19
  const isNone = (cell) => /^(?:—|–|-)?$/.test(cell);
39
-
40
- /** Id-like cells tolerate the typography an author reaches for: `code` and **bold**. */
41
20
  const bare = (cell) => cell.replace(/^[`*]+|[`*]+$/g, '').trim();
42
21
 
43
- /**
44
- * The problems of one data row; `[]` = the row is applicable.
45
- * @returns {{ entry: object|null, problems: Array<{ id: string|null, reason: string, message: string }> }}
46
- */
47
22
  function readRow(line) {
48
23
  const problems = [];
49
24
  const problem = (id, reason, message) => problems.push({ id, reason, message });
50
25
 
51
26
  const row = tableCells(line);
52
- if (row.length < 4) {
53
- problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado 4 (Id | Mudança | Substituto | Escopo): ${line.trim()}`);
27
+ if (row.length < 3) {
28
+ problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado 3 (ADR | Mudança | Escopo): ${line.trim()}`);
54
29
  return { entry: null, problems };
55
30
  }
56
31
  const id = bare(row[0]);
57
- const changeCell = bare(row[1]).toLowerCase();
58
- const substitute = isNone(bare(row[2])) ? null : bare(row[2]);
59
- const scope = isNone(row[3]) ? null : row[3];
32
+ const change = CHANGES.get(bare(row[1]).toLowerCase());
33
+ const scope = isNone(row[2]) ? null : row[2];
60
34
 
61
- const kind = UC_ID_RE.test(id) ? 'uc' : ADR_REF_RE.test(id) ? 'adr' : null;
62
- if (!kind) {
63
- problem(id || null, 'malformed-id', `"${id}" não é um UC-{nn} nem um {feature}/ADR-{nnn}`);
35
+ if (!ADR_REF_RE.test(id)) {
36
+ problem(id || null, 'malformed-id', `"${id}" não é um {feature}/ADR-{nnn}`);
64
37
  return { entry: null, problems };
65
38
  }
66
-
67
- const change = CHANGES.get(changeCell);
68
39
  if (!change) problem(id, 'malformed-change', `${id}: mudança "${bare(row[1])}" — use total ou parcial`);
69
-
70
- if (kind === 'uc' && !substitute) {
71
- problem(id, 'missing-substitute', `${id}: UC revogado precisa do substituto (UC-{nn}) — é o superseded_by do acervo`);
72
- } else if (substitute && !(kind === 'uc' ? UC_ID_RE : ADR_REF_RE).test(substitute)) {
73
- problem(id, 'malformed-substitute',
74
- `${id}: substituto "${substitute}" — esperado ${kind === 'uc' ? 'UC-{nn}' : '{feature}/ADR-{nnn}'}`);
75
- } else if (substitute === id) {
76
- problem(id, 'self-substitute', `${id}: o substituto é o próprio ${id}`);
77
- }
78
-
79
40
  if (change === 'parcial' && !scope) {
80
41
  problem(id, 'missing-scope', `${id}: revogação parcial precisa dizer onde o antigo ainda vale (coluna Escopo)`);
81
42
  } else if (change === 'total' && scope) {
82
43
  problem(id, 'scope-on-total', `${id}: revogação total com escopo "${scope}" — se o antigo ainda vale em algum lugar, a mudança é parcial`);
83
44
  }
84
45
 
85
- return { entry: problems.length ? { id } : { kind, id, change, substitute, scope }, problems };
46
+ return { entry: problems.length ? { id } : { id, change, scope }, problems };
86
47
  }
87
48
 
88
49
  /**
89
- * @param {string} content - the whole `spec.md`
50
+ * @param {string|null} conflicts - the text of `### Conflitos com o acervo` (`parsePlan(...).conflicts.text`)
90
51
  * @returns {{
91
- * declared: boolean,
92
- * entries: Array<{ kind: 'uc'|'adr', id: string, change: 'total'|'parcial', substitute: string|null, scope: string|null }>,
52
+ * entries: Array<{ id: string, change: 'total'|'parcial', scope: string|null }>,
93
53
  * problems: Array<{ id: string|null, reason: string, message: string }>
94
54
  * }}
95
- * `declared: false` — no section at all (a plan written before it existed).
96
55
  */
97
- export function parseRevocations(content) {
98
- const lines = String(content ?? '').split(/\r?\n/);
99
- const title = REVOCATIONS_TITLE.toLowerCase();
100
-
101
- let start = -1;
102
- let level = 0;
103
- for (let i = 0; i < lines.length; i++) {
104
- const h = HEADING_RE.exec(lines[i]);
105
- if (h && h[2].toLowerCase().startsWith(title)) { start = i + 1; level = h[1].length; break; }
106
- }
107
- if (start === -1) return { declared: false, entries: [], problems: [] };
108
-
109
- let end = lines.length;
110
- for (let i = start; i < lines.length; i++) {
111
- const h = HEADING_RE.exec(lines[i]);
112
- if (h && h[1].length <= level) { end = i; break; }
113
- }
114
-
56
+ export function parseRevocations(conflicts) {
115
57
  const problems = [];
116
- const rows = []; // { id, entry } in order, applicable or not
58
+ const rows = [];
117
59
  let fenced = false;
118
- let separatorSeen = false;
119
- let tableLines = 0;
60
+ let table = null;
120
61
 
121
- for (const line of lines.slice(start, end)) {
122
- if (FENCE_RE.test(line)) { fenced = !fenced; continue; }
62
+ for (const line of String(conflicts ?? '').split(/\r?\n/)) {
63
+ if (FENCE_RE.test(line)) { fenced = !fenced; table = null; continue; }
123
64
  if (fenced) continue;
124
65
 
125
66
  if (!isTableLine(line)) {
126
- const stray = LIST_ITEM_RE.test(line) ? ID_TOKEN_RE.exec(line) : null;
67
+ table = null;
68
+ const stray = LIST_ITEM_RE.test(line) ? REF_TOKEN_RE.exec(line) : null;
127
69
  if (stray) {
128
70
  problems.push({ id: stray[0], reason: 'outside-table',
129
- message: `${stray[0]} aparece fora da tabela — só as linhas da tabela são aplicadas no finish` });
71
+ message: `${stray[0]} aparece fora da tabela ADR | Mudança | Escopo — só as linhas da tabela são aplicadas no finish` });
130
72
  }
131
73
  continue;
132
74
  }
133
- tableLines++;
134
- if (SEPARATOR_ROW_RE.test(line)) { separatorSeen = true; continue; }
135
- if (!separatorSeen) continue; // the header row: its wording is free
136
-
75
+ if (!table) {
76
+ table = { revocations: bare(tableCells(line)[0] ?? '').toLowerCase() === 'adr', separatorSeen: false };
77
+ continue;
78
+ }
79
+ if (!table.revocations) continue;
80
+ if (!table.separatorSeen) {
81
+ if (SEPARATOR_ROW_RE.test(line)) { table.separatorSeen = true; continue; }
82
+ problems.push({ id: null, reason: 'unreadable-table',
83
+ message: 'tabela de revogação ilegível — falta a linha separadora (|---|---|---|) depois do cabeçalho; nenhuma linha foi lida' });
84
+ table.revocations = false;
85
+ continue;
86
+ }
137
87
  const { entry, problems: rowProblems } = readRow(line);
138
88
  problems.push(...rowProblems);
139
89
  if (entry) rows.push(entry);
140
90
  }
141
91
 
142
- if (tableLines > 0 && !separatorSeen) {
143
- problems.push({ id: null, reason: 'unreadable-table',
144
- message: 'tabela ilegível — falta a linha separadora (|---|---|---|---) depois do cabeçalho; nenhuma linha foi lida' });
145
- }
146
-
147
- // An id declared twice is ambiguous as a whole: none of its rows applies.
148
92
  const count = new Map();
149
93
  for (const r of rows) count.set(r.id, (count.get(r.id) ?? 0) + 1);
150
94
  for (const [id, n] of count) {
151
95
  if (n > 1) problems.push({ id, reason: 'duplicate-id', message: `${id} aparece em ${n} linhas — nenhuma foi aplicada; deixe uma só` });
152
96
  }
153
- const entries = rows.filter((r) => r.kind && count.get(r.id) === 1);
154
-
155
- return { declared: true, entries, problems };
97
+ return { entries: rows.filter((r) => r.change && count.get(r.id) === 1), problems };
156
98
  }
@@ -3,14 +3,14 @@
3
3
  * dados precisa (volume, paginação, ordenação, filtro, os três estados,
4
4
  * mobile, abas) escritas por tela, na fase uiux.
5
5
  *
6
- * Existe porque o que o design não pergunta não vira `doneCriteria`, e o que
7
- * não é critério ninguém entrega: telas de admin saíam listando tudo, sem
6
+ * Existe porque o que o design não pergunta não vira cenário, e o que
7
+ * não é cenário ninguém entrega: telas de admin saíam listando tudo, sem
8
8
  * paginar nem ordenar, com o design aprovado. Os leitores do MESMO parser:
9
9
  *
10
10
  * - `gate-check uiux` pausa com os problemas nomeados;
11
11
  * - `approve`/`advance` com assinatura `auto` recusam a mesma pausa sem uma
12
12
  * decisão gravada (`uiuxAutoApprovalBlock`) — a regra é a do risk-detector;
13
- * - `gate-check review` exige que o avaliador registre a conferência de cada
13
+ * - `gate-check review` exige que o verificador registre a conferência de cada
14
14
  * item decidido contra a tela entregue (`checklistCoverage`).
15
15
  *
16
16
  * Mockup no formato ANTERIOR ao checklist (telas `## S001: …`, sem nenhuma
@@ -27,7 +27,7 @@ import { existsSync, readFileSync } from 'fs';
27
27
  import { join } from 'path';
28
28
  import { fileURLToPath } from 'url';
29
29
  import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
30
- import { evaluatorScreenChecklist } from '../evaluator-report.js';
30
+ import { verifierScreenChecklist } from '../review-reports.js';
31
31
  import { LEGACY_MOCKUP_EXEMPTION, uiuxChecklistPause } from '../risk-detector.js';
32
32
 
33
33
  const PACKAGE_AGENTS = fileURLToPath(new URL('../../../framework/agents.json', import.meta.url));
@@ -52,7 +52,7 @@ export const SCREEN_CHECKLIST_ITEMS = [
52
52
  ];
53
53
 
54
54
  /**
55
- * Itens que o avaliador não confere contra a tela: a superfície decide QUAIS
55
+ * Itens que o verificador não confere contra a tela: a superfície decide QUAIS
56
56
  * standards a tela lê, não um comportamento que a tela entregue exiba.
57
57
  */
58
58
  const NOT_VERIFIED_AT_REVIEW = new Set(['superficie']);
@@ -180,8 +180,8 @@ const labelOf = (id) => SCREEN_CHECKLIST_ITEMS.find((item) => item.id === id)?.l
180
180
 
181
181
  /**
182
182
  * As superfícies que a persona `ui-designer` declara no `agents.json` — o
183
- * vocabulário válido do item `Superfície`, que o `morph-plan` §5a usa para
184
- * somar os standards da tela em `tasks[].standards`. Lê a cópia instalada do
183
+ * vocabulário válido do item `Superfície`, que o `morph-plan` usa para
184
+ * somar os standards da tela no `standards:` da fatia. Lê a cópia instalada do
185
185
  * projeto; sem ela (este repositório), a do pacote. Persona sem `surfaces`
186
186
  * (instalação anterior ao campo) devolve null: o valor não é validado.
187
187
  *
@@ -189,14 +189,15 @@ const labelOf = (id) => SCREEN_CHECKLIST_ITEMS.find((item) => item.id === id)?.l
189
189
  * @returns {string[]|null}
190
190
  */
191
191
  export function knownSurfaces(cwd) {
192
+ const surfaces = readAgentsJson(cwd)?.agents?.['ui-designer']?.surfaces;
193
+ return surfaces && typeof surfaces === 'object' && !Array.isArray(surfaces) ? Object.keys(surfaces) : null;
194
+ }
195
+
196
+ /** The project's installed `agents.json`, else the package's; null when unreadable. */
197
+ export function readAgentsJson(cwd) {
192
198
  for (const path of [join(cwd, '.morph', 'framework', 'agents.json'), PACKAGE_AGENTS]) {
193
199
  if (!existsSync(path)) continue;
194
- try {
195
- const surfaces = JSON.parse(readFileSync(path, 'utf8'))?.agents?.['ui-designer']?.surfaces;
196
- return surfaces && typeof surfaces === 'object' && !Array.isArray(surfaces) ? Object.keys(surfaces) : null;
197
- } catch {
198
- return null;
199
- }
200
+ try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
200
201
  }
201
202
  return null;
202
203
  }
@@ -257,7 +258,7 @@ export function auditScreenChecklist(markdown, { surfaces = null } = {}) {
257
258
  *
258
259
  * `mockups: false` é "a feature não tem mockup" — nada a cobrar. No review, o
259
260
  * que se cobra é a conferência (`uncovered`: itens decididos sem linha na
260
- * seção do avaliador), não o design, que já passou pelo Gate uiux.
261
+ * seção do verificador), não o design, que já passou pelo Gate uiux.
261
262
  *
262
263
  * @param {string} cwd
263
264
  * @param {string} featureName
@@ -272,7 +273,7 @@ export function collectScreenChecklist(cwd, featureName, gateName) {
272
273
  const signal = { mockups: true, legacy: audit.legacy, screens: audit.screens.map((s) => s.name), problems: audit.problems };
273
274
  if (audit.legacy) signal.exempt = LEGACY_MOCKUP_EXEMPTION;
274
275
  if (gateName === 'review') {
275
- const report = evaluatorScreenChecklist(cwd, featureName);
276
+ const report = verifierScreenChecklist(cwd, featureName);
276
277
  signal.section = report.section;
277
278
  signal.uncovered = checklistCoverage(mockups, report.section ? report.body : '');
278
279
  }
@@ -300,13 +301,13 @@ export function uiuxAutoApprovalBlock(cwd, featureName, feature) {
300
301
  }
301
302
 
302
303
  /**
303
- * O que o avaliador deixou de conferir: por tela do mockup, os itens com
304
+ * O que o verificador deixou de conferir: por tela do mockup, os itens com
304
305
  * decisão (nem "não se aplica", nem a superfície) sem linha na seção
305
306
  * `## Checklist de tela` do relatório. Uma linha que o avaliador marca como
306
307
  * não verificável conta como conferida — o que pausa é a AUSÊNCIA da linha.
307
308
  *
308
309
  * @param {string} designMarkdown - o `1-design/mockups.md`
309
- * @param {string} reportSection - o corpo da seção do `evaluator-report.md`
310
+ * @param {string} reportSection - o corpo da seção do `verifier.md`
310
311
  * @returns {string[]} `"<tela>: <item>, <item>"` por tela com lacuna
311
312
  */
312
313
  export function checklistCoverage(designMarkdown, reportSection) {
@@ -0,0 +1,47 @@
1
+ import { readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { ensureDotnetBuildIgnores } from '../../utils/file-copier.js';
4
+ import { getProjectTags } from '../stack-filter.js';
5
+ import { readMorphConfig } from '../tasks/test-runner.js';
6
+ import { readPlanChecks } from '../plan/checks.js';
7
+ import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
8
+
9
+ export const DOTNET_IMPLEMENTERS = ['dotnet-senior', 'ef-modeler', 'maf-expert'];
10
+
11
+ const SKIPPED_DIRS = new Set([...WORKTREE_DIR_NAMES, 'node_modules', 'bin', 'obj']);
12
+ const DOTNET_PROJECT_FILE = /\.(?:csproj|slnx?)$/i;
13
+ const MAX_DEPTH = 6;
14
+
15
+ export function hasDotnetProjectFile(root, depth = 0) {
16
+ let entries;
17
+ try { entries = readdirSync(root, { withFileTypes: true }); } catch { return false; }
18
+ if (entries.some((e) => e.isFile() && DOTNET_PROJECT_FILE.test(e.name))) return true;
19
+ if (depth >= MAX_DEPTH) return false;
20
+ return entries.some((e) => e.isDirectory() && !e.name.startsWith('.') && !SKIPPED_DIRS.has(e.name)
21
+ && hasDotnetProjectFile(join(root, e.name), depth + 1));
22
+ }
23
+
24
+ function planHasDotnetSlice(tree, featureName) {
25
+ if (!featureName) return false;
26
+ const { checks } = readPlanChecks(tree, featureName);
27
+ return (checks?.slices || []).some((slice) => DOTNET_IMPLEMENTERS.includes(String(slice.persona || '').replace(/^morph-/, '')));
28
+ }
29
+
30
+ /**
31
+ * "Is this a .NET project?" from what is known before the first code commit:
32
+ * the declared `project.stack`, a .NET implementer in the plan's slices, or a
33
+ * .csproj/.sln/.slnx already in the tree. Greenfield has only the second.
34
+ */
35
+ export function isDotnetProject(tree, { featureName, configRoot = tree } = {}) {
36
+ if (getProjectTags(readMorphConfig(configRoot)).includes('dotnet')) return true;
37
+ return planHasDotnetSlice(tree, featureName) || hasDotnetProjectFile(tree);
38
+ }
39
+
40
+ /** @returns {string[]} the `.gitignore` entries added to `tree`; `[]` when not .NET or on any error */
41
+ export function seedDotnetBuildIgnores(tree, options = {}) {
42
+ try {
43
+ return isDotnetProject(tree, options) ? ensureDotnetBuildIgnores(tree) : [];
44
+ } catch {
45
+ return [];
46
+ }
47
+ }
@@ -19,7 +19,7 @@ const NO_VALIDATOR_STACKS = new Set(['nodejs-cli', 'node-cli', 'cli']);
19
19
  const PROFILES = {
20
20
  dotnet: {
21
21
  isDotnet: true, isNextjs: false,
22
- defaultValidators: ['architecture', 'packages'],
22
+ defaultValidators: ['packages'],
23
23
  buildCommand: 'dotnet build --no-restore',
24
24
  },
25
25
  nextjs: {