@polymorphism-tech/morph-spec 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  4. package/bin/validate.js +1 -39
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,19 +1,6 @@
1
- /**
2
- * command-tokens.js — shell-ish tokenizer shared by the PreToolUse Bash guards.
3
- *
4
- * Lives in shared/ (not inside a hook) on purpose: every core hook ends in a
5
- * standalone tail that reads stdin and exits, so a hook importing a sibling
6
- * hook for a helper would fire THAT tail in its own process — consuming the
7
- * payload and exiting before the importer ever ran. block-raw-worktree-add
8
- * and block-destructive-worktree-removal both read paths off these tokens,
9
- * so they agree on what a quoted path is.
10
- */
1
+ // Lives in shared/: a hook importing a sibling hook would fire that hook's
2
+ // stdin-reading tail in its own process.
11
3
 
12
- /**
13
- * Split a shell-ish command into tokens, honoring single and double quotes.
14
- * @param {string} command
15
- * @returns {string[]}
16
- */
17
4
  export function tokenize(command) {
18
5
  const tokens = [];
19
6
  const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
@@ -22,7 +9,6 @@ export function tokenize(command) {
22
9
  return tokens;
23
10
  }
24
11
 
25
- /** Index of the first pipe (`|`, not `||` nor `>|`) outside quotes, or -1. */
26
12
  function firstUnquotedPipe(command) {
27
13
  const s = String(command || '');
28
14
  let quote = null;
@@ -43,24 +29,11 @@ function firstUnquotedPipe(command) {
43
29
  return -1;
44
30
  }
45
31
 
46
- /**
47
- * Does the command pipe one process into another outside quotes? Without
48
- * `pipefail` the shell reports the LAST command's status, so the exit code of
49
- * `dotnet test … | grep …` is grep's, not the test run's.
50
- * @param {string} command
51
- * @returns {boolean}
52
- */
53
32
  export function hasUnquotedPipe(command) {
54
33
  return firstUnquotedPipe(command) >= 0;
55
34
  }
56
35
 
57
- /**
58
- * The work a command does, without the filter that shapes its output:
59
- * `dotnet test 2>&1 | tail -25` and `dotnet test | tail -20` run the same
60
- * tests. Whitespace collapsed, stderr redirections dropped.
61
- * @param {string} command
62
- * @returns {string}
63
- */
36
+ /** The command without the pipe filter and stderr redirections that shape its output. */
64
37
  export function producerOf(command) {
65
38
  const s = String(command || '');
66
39
  const pipe = firstUnquotedPipe(s);
@@ -70,11 +43,6 @@ export function producerOf(command) {
70
43
  .trim();
71
44
  }
72
45
 
73
- /**
74
- * The command split at `;`, newline and background `&` outside quotes, plus
75
- * whether a background `&` was seen. `&&`, `||` and `>&`/`&>` redirections
76
- * stay inside their segment.
77
- */
78
46
  function topLevelSegments(command) {
79
47
  const s = String(command || '');
80
48
  const segments = [];
@@ -102,14 +70,7 @@ function topLevelSegments(command) {
102
70
 
103
71
  const REPORTER = /^(?:echo|printf|true|:|exit\s+0)\b/;
104
72
 
105
- /**
106
- * Is the exit code of this command the exit code of the work it ran? The
107
- * shell reports the last segment of a `;` sequence: `dotnet test > log;
108
- * echo "exit=$?"` always exits 0, and so does `dotnet test || true`. A pipe
109
- * without `pipefail` reports the filter, and a background `&` reports 0.
110
- * @param {string} command
111
- * @returns {boolean}
112
- */
73
+ /** True when the shell reports something other than the work's exit code (`; echo`, `|| true`, pipe, `&`). */
113
74
  export function exitCodeMasked(command) {
114
75
  const { segments, background } = topLevelSegments(command);
115
76
  if (background) return true;
@@ -253,14 +214,8 @@ function commandWriteTargets(head, body) {
253
214
  return targets;
254
215
  }
255
216
 
256
- /**
257
- * Files a command writes: stdout redirection (`>`, `>>`, `1>`, `&>`), `tee`, `sed -i`,
258
- * `perl -i`, the `cp`/`mv` destination, and a python/node `-c`/`-e`/heredoc script's writes.
259
- * The redirect scan stops at the line that opens a heredoc: the body is data, and an
260
- * apostrophe in it would derail the quote-aware split. Quoted operators are text.
261
- * @param {string} command
262
- * @returns {string[]}
263
- */
217
+ // The redirect scan stops at the heredoc line: the body is data, and an
218
+ // apostrophe in it would derail the quote-aware split.
264
219
  export function writeTargets(command) {
265
220
  const lines = String(command || '').split('\n');
266
221
  const heredocAt = lines.findIndex((line) => line.includes('<<'));
@@ -1,11 +1,5 @@
1
- /**
2
- * The comment shapes the `architecture-code-comments` standard forbids in
3
- * every case, judged only on the comments an edit ADDED. Whether a short
4
- * comment restates the code is not decidable here; that stays with the
5
- * Gate 3 reviewer and the `code-budget` density.
6
- *
7
- * @module comment-rules
8
- */
1
+ // Judges only the comments an edit ADDED, and only their shape: whether a
2
+ // comment restates the code is not decidable here.
9
3
 
10
4
  import { existsSync, readdirSync, readFileSync } from 'fs';
11
5
  import { dirname, join } from 'path';
@@ -58,13 +52,7 @@ function nearest(startDir, test) {
58
52
  }
59
53
  }
60
54
 
61
- /**
62
- * Doc-comments are allowed only on a package consumed outside the repo. When
63
- * that cannot be read from the nearest project file, the rule stays quiet.
64
- *
65
- * @param {string} file - absolute path of the edited file
66
- * @returns {boolean}
67
- */
55
+ /** Doc-comments are allowed only on a package consumed outside the repo; unreadable = quiet. */
68
56
  export function docCommentsForbidden(file) {
69
57
  const dir = dirname(file);
70
58
  if (/\.cs$/i.test(file)) {
@@ -85,7 +73,7 @@ export function docCommentsForbidden(file) {
85
73
  return JSON.parse(readFileSync(pkg, 'utf8')).private === true;
86
74
  }
87
75
 
88
- /** Lines of `after` whose comment is not one `before` already had (moved comments do not count). */
76
+ // Moved comments do not count as added.
89
77
  function addedCommentLines(before, after, file) {
90
78
  const remaining = new Map();
91
79
  for (const text of commentLines(before, file).values()) {
@@ -115,10 +103,7 @@ function addedRegionLines(before, after) {
115
103
  return out;
116
104
  }
117
105
 
118
- /**
119
- * @param {{ file: string, before: string, after: string, docCommentsForbidden?: boolean }} input
120
- * @returns {Array<{ line: number, rule: 'process'|'banner'|'doc-comment'|'long-block', text: string }>}
121
- */
106
+ /** @returns {Array<{ line: number, rule: 'process'|'banner'|'doc-comment'|'long-block', text: string }>} */
122
107
  export function commentFindings({ file, before, after, docCommentsForbidden: docForbidden = false }) {
123
108
  const isCs = /\.cs$/i.test(file);
124
109
  const all = commentLines(after, file);
@@ -1,20 +1,10 @@
1
- /**
2
- * Linhas de comentário de um arquivo e se elas contam história de processo.
3
- *
4
- * O comentário é o que `stripComments` apaga: o mesmo varredor que já separa
5
- * comentário de string para o `doctor --ai`, com a sintaxe de TS/JS ligada
6
- * fora do C#. Um regex por linha confundiria `https://` com comentário.
7
- *
8
- * @module comment-scanner
9
- */
1
+ // Comentário é o que `stripComments` apaga: um regex por linha confundiria
2
+ // `https://` com comentário.
10
3
 
11
4
  import { stripComments } from './comment-stripper.js';
12
5
 
13
- /**
14
- * Id de cenário (`C{n}`) e de fatia (`F{n}`) do plano. A mesma grafia também é
15
- * tecla de função, métrica, célula de planilha ou o modelo C-quatro: esses
16
- * vizinhos não contam (casos em comment-scanner.test.js).
17
- */
6
+ // A mesma grafia de `C{n}`/`F{n}` é tecla, métrica, célula ou o modelo C-quatro:
7
+ // esses vizinhos não contam.
18
8
  const PLAN_ID = new RegExp(String.raw`(?<!\b(?:key|tecla|press|aperte|cell|c[eé]lula|column|coluna)\s+)(?<![\w.#$/-])[CF]\d{1,2}(?![\w.(-]|\s+(?:key|tecla|score|model|modelo|diagram|diagrama)\b)`, 'i');
19
9
 
20
10
  const PROCESS_CITATION = [
@@ -27,19 +17,11 @@ const PROCESS_CITATION = [
27
17
  /\bmockups?\b/i,
28
18
  ];
29
19
 
30
- /**
31
- * @param {string} commentText
32
- * @returns {boolean} o comentário usa o vocabulário de processo acima
33
- */
34
20
  export function citesProcess(commentText) {
35
21
  return PROCESS_CITATION.some((re) => re.test(commentText)) || PLAN_ID.test(commentText);
36
22
  }
37
23
 
38
- /**
39
- * @param {string} content - conteúdo do arquivo
40
- * @param {string} file - path, só para escolher a sintaxe
41
- * @returns {Map<number, string>} nº da linha (1-based) → texto do comentário nela
42
- */
24
+ /** @returns {Map<number, string>} linha 1-based → texto do comentário; `file` só escolhe a sintaxe */
43
25
  export function commentLines(content, file) {
44
26
  const text = String(content || '');
45
27
  const stripped = stripComments(text, { js: !/\.cs$/i.test(file) });
@@ -53,8 +35,7 @@ export function commentLines(content, file) {
53
35
  if (first < 0) first = k;
54
36
  last = k;
55
37
  }
56
- // O espaço dentro do comentário não muda ao ser apagado; o trecho vai do
57
- // primeiro ao último caractere apagado.
38
+ // Espaço dentro do comentário não muda ao ser apagado.
58
39
  if (first >= 0) out.set(index + 1, row.slice(first, last + 1).trim());
59
40
  start += row.length + 1;
60
41
  });
@@ -1,55 +1,10 @@
1
- /**
2
- * comment-stripper.js — apagar comentários SEM apagar o que está dentro de uma
3
- * string, preservando comprimento e quebras de linha.
4
- *
5
- * Existe uma vez só, e serve a consumidores que fariam a MESMA coisa de
6
- * jeitos ligeiramente diferentes se cada um escrevesse o seu:
7
- *
8
- * - `code-audit.js` — não pode acusar `AgentThread` que só aparece num
9
- * comentário ou numa mensagem de log;
10
- * - `registry-audit.js` — precisa ler **JSONC**, a forma que o exemplar
11
- * canônico `model-registry.v2.json` usa e que o leitor C# do kit aceita de
12
- * propósito (`JsonCommentHandling.Skip`, `AllowTrailingCommas`);
13
- * - `mutation/dotnet-introspection.js` — lê o `global.json` por `jsoncToJson`;
14
- * - `comment-scanner.js` (ao lado; lido pelo `code-budget` e pelo hook
15
- * `comment-guard`) — acha as linhas de
16
- * comentário de `.cs`/`.ts`/`.js`, e é o único que liga a opção `js`.
17
- *
18
- * **Por que não um regex.** O exemplar tem `"http://localhost:11434/v1"` e uma
19
- * `provenance` com URL. Um `replace(/\/\/.*$/gm, '')` decapitaria as duas —
20
- * apagando dado e chamando isso de comentário. Um varredor que atravessa as
21
- * strings primeiro é a única forma correta, e é o que está aqui.
22
- *
23
- * O comprimento é preservado (os removidos viram espaço, `\n` e `\r` ficam) para
24
- * que índices e números de linha do texto original continuem válidos no texto
25
- * limpo. É o que permite `code-audit.js` reportar `arquivo.cs:34` sem um segundo
26
- * mapeamento.
27
- */
1
+ // Atravessa as strings antes: um regex apagaria o `//` de `"http://localhost"`.
2
+ // O comprimento é preservado para índices e números de linha continuarem válidos.
28
3
 
29
4
  /**
30
- * @param {string} text
31
- * @param {object} [options]
32
- * @param {boolean} [options.blankStrings=false]
33
- * Apaga também o CONTEÚDO das strings. **As aspas delimitadoras ficam.**
34
- *
35
- * Apagá-las junto era um defeito com consequência: um elemento-string virava
36
- * espaço puro, e o detector de vírgula sobrando de `jsoncToJson` passava a
37
- * enxergar `[ , , ]` — separadores REAIS entre delimitadores que sumiram —
38
- * e apagava uma vírgula que era sintaxe. Medido: `{"tags":["a","b"]}` saía
39
- * `{"tags":["a","b" ]}`, JSON inválido, e o registry voltava a ser
40
- * `○ não verificado`. O B1 reintroduzido pela correção do B1.
41
- * @param {{ unterminated?: string|null }} [options.report]
42
- * Se um objeto for passado, recebe `unterminated` = `'block-comment'`,
43
- * `'string'`, `'verbatim-string'`, `'raw-string'`, `'template'` ou `null`. É o canal de
44
- * diagnóstico que `jsoncToJson` usa para ficar tão estrito quanto o leitor
45
- * de produção, sem que este lexer precise lançar (ele também varre `.cs`,
46
- * onde um comentário aberto é problema do arquivo, não da varredura).
47
- * @param {boolean} [options.js=false]
48
- * Lê também template literal e regex literal de TS/JS. Opt-in porque só
49
- * quem varre `.ts`/`.js` precisa: sem template, `https://${h}` vira
50
- * comentário; sem regex, uma crase dentro de `/[`']/` abre um template que
51
- * engole o resto do arquivo.
52
- * @returns {string} texto do MESMO comprimento
5
+ * `blankStrings` apaga o conteúdo das strings, mas as ASPAS FICAM: sem elas um
6
+ * separador real vira vírgula sobrando. `report.unterminated` recebe o literal não
7
+ * fechado; `js` lê template e regex literal, sem os quais `https://${h}` vira comentário.
53
8
  */
54
9
  export function stripComments(text, { blankStrings = false, report = null, js = false } = {}) {
55
10
  const out = text.split('');
@@ -95,7 +50,7 @@ export function stripComments(text, { blankStrings = false, report = null, js =
95
50
  if (end !== -1) { i = end; continue; }
96
51
  }
97
52
 
98
- // Raw string literal do C#: """ ... """ — delimitadores de 3 aspas.
53
+ // C# raw string literal
99
54
  if (c === '"' && next === '"' && text[i + 2] === '"') {
100
55
  const end = text.indexOf('"""', i + 3);
101
56
  if (end === -1) flag('raw-string');
@@ -105,7 +60,7 @@ export function stripComments(text, { blankStrings = false, report = null, js =
105
60
  continue;
106
61
  }
107
62
 
108
- // Verbatim string do C#: @"..." — aspas duplicadas escapam
63
+ // C# verbatim string: doubled quotes escape
109
64
  if (c === '@' && next === '"') {
110
65
  let j = i + 2;
111
66
  let closed = false;
@@ -159,7 +114,6 @@ export function stripComments(text, { blankStrings = false, report = null, js =
159
114
  return out.join('');
160
115
  }
161
116
 
162
- /** Índice logo após a crase que fecha o template aberto em `start`, ou -1. */
163
117
  function templateEnd(text, start) {
164
118
  let j = start + 1;
165
119
  while (j < text.length) {
@@ -176,11 +130,7 @@ const REGEX_AFTER_WORD = new Set([
176
130
  'throw', 'instanceof', 'yield', 'await',
177
131
  ]);
178
132
 
179
- /**
180
- * Uma `/` abre regex, e não divisão, quando o que vem antes não é um valor: o
181
- * mesmo critério de contexto dos tokenizers de JS. `out` já tem os comentários
182
- * apagados, então o caractere anterior é código. Sem `<`: em TSX, `</b>` é tag.
183
- */
133
+ // `/` abre regex quando o que vem antes não é um valor. Sem `<`: em TSX, `</b>` é tag.
184
134
  function regexMayStart(out, i) {
185
135
  let k = i - 1;
186
136
  while (k >= 0 && /\s/.test(out[k])) k--;
@@ -192,7 +142,6 @@ function regexMayStart(out, i) {
192
142
  return REGEX_AFTER_WORD.has(out.slice(w, k + 1).join(''));
193
143
  }
194
144
 
195
- /** Índice logo após a `/` (e as flags) que fecha a regex aberta em `start`, ou -1 se a linha acaba antes. */
196
145
  function regexEnd(text, start) {
197
146
  let inClass = false;
198
147
  for (let j = start + 1; j < text.length; j++) {
@@ -210,7 +159,6 @@ function regexEnd(text, start) {
210
159
  return -1;
211
160
  }
212
161
 
213
- /** Índice logo após o `}` que fecha um `${`: o código do buraco aninha chaves, strings e templates. */
214
162
  function holeEnd(text, start) {
215
163
  let depth = 1;
216
164
  let j = start;
@@ -235,31 +183,9 @@ function holeEnd(text, start) {
235
183
  return text.length;
236
184
  }
237
185
 
238
- /**
239
- * Converte JSONC em JSON: apaga comentários e vírgulas sobrando antes de `}`/`]`.
240
- *
241
- * As duas tolerâncias são **exatamente** as que `ModelRegistry.FromJson` do
242
- * ai-kit declara (`CommentHandling = Skip`, `AllowTrailingCommas = true`). A
243
- * ferramenta que audita o registry tem de enxergar a mesma forma que o leitor
244
- * de produção enxerga — senão o framework passa a recomendar uma forma que a
245
- * sua própria auditoria não lê, e as checagens ficam inertes em silêncio.
246
- *
247
- * A vírgula sobrando é localizada num texto onde o conteúdo das strings também
248
- * foi apagado — **com as aspas preservadas**, senão um elemento-string vira
249
- * espaço puro e um separador REAL passa por vírgula sobrando (o defeito B4) —
250
- * e só então removida do texto real: uma vírgula dentro de `"a,}"` é dado.
251
- *
252
- * **Um comentário de bloco não fechado LANÇA**, e a razão é a mesma que fez
253
- * esta função existir: paridade com o leitor de produção. `JsonCommentHandling.Skip`
254
- * do .NET lança nesse caso, e ser mais frouxo que o kit é o defeito INVERSO do
255
- * B1 — auditar, e aprovar, um documento que a aplicação recusa carregar.
256
- * `readJsonSafe` captura e devolve `null`, que o relatório traduz em
257
- * "não verificado" com razão.
258
- *
259
- * @param {string} raw
260
- * @returns {string} JSON estrito, do mesmo comprimento do original
261
- * @throws {SyntaxError} comentário de bloco aberto e nunca fechado
262
- */
186
+ // Paridade com `ModelRegistry.FromJson` do ai-kit, nos dois sentidos: comentário de
187
+ // bloco não fechado LANÇA, como `JsonCommentHandling.Skip`, porque ser mais frouxo
188
+ // que o kit é aprovar um documento que a aplicação recusa carregar.
263
189
  export function jsoncToJson(raw) {
264
190
  const report = {};
265
191
  const noComments = stripComments(raw, { report }).split('');
Binary file
@@ -1,15 +1,10 @@
1
1
  export const ERRATA_HEADING = '## Errata';
2
2
 
3
- /** Strip BOM and CRLF — the one normalization every reader of a frozen markdown applies. */
3
+ /** The one normalization every reader of a frozen markdown applies. */
4
4
  export function normalizeMarkdown(text) {
5
5
  return String(text ?? '').replace(/^/, '').replace(/\r\n?/g, '\n');
6
6
  }
7
7
 
8
- /**
9
- * Lines with fenced blocks marked, so no heading is read inside ``` or ~~~.
10
- *
11
- * @returns {{ raw: string, n: number, fenced: boolean }[]}
12
- */
13
8
  export function markdownLines(text) {
14
9
  const out = [];
15
10
  let fence = null;
@@ -30,7 +25,7 @@ export function markdownLines(text) {
30
25
  const isErrataHeading = (line) => !line.fenced
31
26
  && /^##\s+errata\s*$/i.test(line.raw);
32
27
 
33
- /** Offset of the `## Errata` heading in the NORMALIZED text, or -1. */
28
+ /** Offset in the NORMALIZED text, or -1. */
34
29
  export function errataOffset(text) {
35
30
  let offset = 0;
36
31
  for (const line of markdownLines(text)) {
@@ -40,20 +35,12 @@ export function errataOffset(text) {
40
35
  return -1;
41
36
  }
42
37
 
43
- /** Everything before the errata block, normalized, without trailing blank. */
44
38
  export function frozenBody(text) {
45
39
  const norm = normalizeMarkdown(text);
46
40
  const at = errataOffset(norm);
47
41
  return (at === -1 ? norm : norm.slice(0, at)).trimEnd();
48
42
  }
49
43
 
50
- /**
51
- * Is `proposed` the approved text plus an append-only errata? The body stays
52
- * byte-identical, the result carries an errata block, and what the errata
53
- * already said is never rewritten.
54
- *
55
- * @returns {{ok: true} | {ok: false, why: string}}
56
- */
57
44
  export function errataViolation(original, proposed) {
58
45
  if (frozenBody(proposed) !== frozenBody(original)) {
59
46
  return { ok: false, why: 'o corpo aprovado mudou — errata só ACRESCENTA ao fim, nunca altera o texto aprovado' };
@@ -1,41 +1,11 @@
1
- /**
2
- * Locate which tree owns a feature, and which feature a gate question is about.
3
- *
4
- * `resolveMorphRoot` (payload-utils) answers this from a file path. Some hook
5
- * events carry no path at all — an `AskUserQuestion` gate approval is the one
6
- * that matters — and those were resolved by "the first in_progress feature of
7
- * whatever tree the session happens to sit in". That is how a Gate 2 approval
8
- * for one feature landed on `export-hub`, an unrelated feature that merely
9
- * happened to be active in the primary root.
10
- *
11
- * The honest signal for a pathless event is the question text itself: every
12
- * gate template in the core skills names its feature — "Gate 1 — Proposta de
13
- * {feature} aprovada?", "Gate 2 — Plano de {feature} aprovado?", "Gate 3 —
14
- * Review de {feature} aprovado?", "Gate 3 — Hotfix de {feature} pronto para
15
- * ship?". Rather than parse those sentences (which would break the moment a
16
- * template is reworded), match the question against the set of feature slugs
17
- * that actually exist on disk.
18
- *
19
- * When the text names no known feature, the caller must REFUSE TO GUESS.
20
- * Stamping a gate onto the wrong feature is close to unrecoverable — it inverts
21
- * gate order and invites a second stamp on the real owner; writing nothing
22
- * costs one `morph-spec approve`.
23
- *
24
- * Fail-open by contract: every function swallows errors and returns an empty
25
- * result rather than throwing.
26
- */
1
+ // A pathless gate question is matched against the slugs on disk, never parsed.
2
+ // When it names no known feature the caller must REFUSE TO GUESS: a gate on the
3
+ // wrong feature is close to unrecoverable, writing nothing costs one `approve`.
27
4
 
28
5
  import { execSync } from 'child_process';
29
6
  import { loadState } from './state-reader.js';
30
7
 
31
- /**
32
- * Feature slugs known to `root`.
33
- *
34
- * Deliberately `loadState` and not a `readdirSync` of `.morph/features/`: state
35
- * lives in BOTH shapes — the thin `.morph/state.json` index and the per-feature
36
- * `feature.json` — and `loadState` is the one place that already merges them.
37
- * Listing directories alone misses every feature carried only by the index.
38
- */
8
+ // `loadState`, not a directory listing: it also sees features only the index carries.
39
9
  export function featureNamesIn(root) {
40
10
  try {
41
11
  const state = loadState(root);
@@ -45,11 +15,6 @@ export function featureNamesIn(root) {
45
15
  }
46
16
  }
47
17
 
48
- /**
49
- * Every working tree of this repo — the primary root plus every registered
50
- * secondary worktree. Fail-open to `[sessionRoot]` when git is unavailable or
51
- * the directory is not a repo.
52
- */
53
18
  export function allWorkTrees(sessionRoot) {
54
19
  const roots = [sessionRoot];
55
20
  try {
@@ -64,26 +29,14 @@ export function allWorkTrees(sessionRoot) {
64
29
  if (path && !roots.includes(path)) roots.push(path);
65
30
  }
66
31
  } catch {
67
- /* fail-open — the session root alone */
68
32
  }
69
33
  return roots;
70
34
  }
71
35
 
72
36
  const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
73
37
 
74
- /**
75
- * The single feature slug named in `text`, or null.
76
- *
77
- * Slug boundaries are `[^a-z0-9-]` so a longer name never matches inside a
78
- * shorter one (`export` must not match the text "export-hub"). Zero matches
79
- * means the text named nothing; two or more means it named two features and is
80
- * ambiguous. Both are null — "I don't know" and "it's ambiguous" must lead to
81
- * the same refusal, not to a coin flip.
82
- *
83
- * @param {string} text
84
- * @param {string[]} names - candidate slugs
85
- * @returns {string|null}
86
- */
38
+ // `export` must not match inside "export-hub". Zero and ambiguous matches are
39
+ // both null: they must lead to the same refusal.
87
40
  export function featureNamedIn(text, names) {
88
41
  if (typeof text !== 'string' || !Array.isArray(names) || names.length === 0) return null;
89
42
  const haystack = text.toLowerCase();
@@ -93,28 +46,8 @@ export function featureNamedIn(text, names) {
93
46
  return hits.length === 1 ? hits[0] : null;
94
47
  }
95
48
 
96
- /**
97
- * A token in `text` shaped like a feature slug (kebab-case, at least one
98
- * hyphen — `morph-spec create`'s naming convention) that names none of
99
- * `names`. Signals "the text is talking about a specific feature I don't
100
- * know" as opposed to "the text names nothing in particular".
101
- *
102
- * This is what the single-feature fallback below was missing: a gate question
103
- * for a feature not yet registered (no `create`, no `plan.md` written
104
- * yet — a fresh hotfix, say) names NO known feature either, and used to fall
105
- * through to "the tree's one feature must be it". That is exactly how a Gate 3
106
- * for `backfill-bool-filter` landed on `export-hub`, the tree's only
107
- * registered feature at the time.
108
- *
109
- * Deliberately narrow (requires a hyphen) to match real feature slugs
110
- * (`export-hub`, `backfill-bool-filter`) without flagging ordinary prose —
111
- * the fixed portions of every gate template ("Proposta", "aprovado", "Sinal",
112
- * "pronto para ship"...) are single words with no internal hyphen.
113
- *
114
- * @param {string} text
115
- * @param {string[]} names - every feature slug known across all trees
116
- * @returns {string|null}
117
- */
49
+ // A slug-shaped token naming no known feature: the question is about a feature
50
+ // not yet registered. Requires a hyphen, so template prose never matches.
118
51
  export function unrecognizedSlugLike(text, names) {
119
52
  if (typeof text !== 'string') return null;
120
53
  const known = new Set((names || []).map((n) => String(n).toLowerCase()));
@@ -122,17 +55,8 @@ export function unrecognizedSlugLike(text, names) {
122
55
  return tokens.find((t) => !known.has(t)) ?? null;
123
56
  }
124
57
 
125
- /**
126
- * Resolve a gate question to `{ name, root }`.
127
- *
128
- * The session root is consulted first so the common case costs no git call, and
129
- * so a feature present in BOTH trees resolves to the tree the session is
130
- * actually in — the same tree `morph-spec approve` would write from that cwd.
131
- * Only when the session root knows no such feature do we pay for enumerating
132
- * the worktrees.
133
- *
134
- * @returns {{name: string, root: string}|null} null ⇒ caller must not guess
135
- */
58
+ // The session root wins a feature present in both trees: it is where
59
+ // `morph-spec approve` would write from that cwd.
136
60
  export function locateFeatureForQuestion(text, sessionRoot) {
137
61
  const trees = allWorkTrees(sessionRoot);
138
62
  const localNames = featureNamesIn(sessionRoot);
@@ -146,18 +70,10 @@ export function locateFeatureForQuestion(text, sessionRoot) {
146
70
  if (found) return { name: found, root };
147
71
  }
148
72
 
149
- // The text names a specific feature none of the trees know — refuse rather
150
- // than guess it meant the tree's one registered feature (see
151
- // `unrecognizedSlugLike` above).
152
73
  const allNames = trees.flatMap(featureNamesIn);
153
74
  if (unrecognizedSlugLike(text, allNames)) return null;
154
75
 
155
- // Unambiguous fallback: the session's tree holds exactly ONE feature, so
156
- // there is no other feature the answer could have been about. This is the
157
- // single-feature project — the common case — and refusing there would break
158
- // gate questions phrased without a name ("Gate 1 — Proposta aprovada?").
159
- // With two or more features the fallback is exactly the guess that put a
160
- // Gate 2 on `export-hub`, so it stops here.
76
+ // With exactly ONE feature in the tree there is nothing else the answer could be about.
161
77
  if (localNames.length === 1) return { name: localNames[0], root: sessionRoot };
162
78
 
163
79
  return null;
@@ -1,6 +1,4 @@
1
- // The single list of frontend package locations: `src/lib/detectors` re-exports it,
2
- // because a hook cannot import `src/`.
1
+ // `src/lib/detectors` re-exports this list, because a hook cannot import `src/`.
3
2
  export const PACKAGE_LOCATIONS = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
4
3
 
5
- // Where `verify`/`e2e` look for a Playwright setup when `project.frontendPath` is absent.
6
4
  export const E2E_SPEC_LOCATIONS = [...PACKAGE_LOCATIONS, 'web'];
@@ -1,10 +1,4 @@
1
- /**
2
- * The gate a gate question asks about, or null. Explicit gate names win over
3
- * words that may also appear in a feature slug ("plan", "design", "review").
4
- *
5
- * @param {string} question
6
- * @returns {'plan'|'uiux'|'review'|null}
7
- */
1
+ /** Explicit gate names win over words a feature slug may also carry ("plan", "design", "review"). */
8
2
  export function gateFromQuestion(question) {
9
3
  if (typeof question !== 'string') return null;
10
4
  if (/gate\s+uiux/i.test(question)) return 'uiux';
@@ -1,31 +1,14 @@
1
- /**
2
- * git-exclude.js — o escritor ÚNICO dos blocos que o morph-spec mantém no
3
- * `info/exclude` do git.
4
- *
5
- * O arquivo lido é o do COMMON dir (`git rev-parse --git-common-dir`), nunca o
6
- * `.git/worktrees/<id>/info/exclude`: o git não lê o deste para uma worktree
7
- * vinculada, e o do common dir vale para toda árvore do repositório — inclusive
8
- * a raiz primária. Cada chamador passa o seu marcador, e o bloco só ganha as
9
- * linhas que ainda faltam.
10
- *
11
- * Idempotente e fail-open: um exclude que não pôde ser escrito custa um
12
- * arquivo aparecendo como `??`, nunca um erro.
13
- */
1
+ // O exclude do COMMON dir: o git não lê `.git/worktrees/<id>/info/exclude` para
2
+ // uma worktree vinculada. Fail-open: falhar custa um `??`, nunca um erro.
14
3
 
15
4
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';
16
5
  import { join } from 'path';
17
6
  import { resolveGitCommonDir } from './git-paths.js';
18
7
 
19
- /**
20
- * @param {string} cwd - qualquer diretório dentro do repositório
21
- * @param {string[]} patterns - linhas prontas (já ancoradas, ex. `/graphify-out`)
22
- * @param {string} marker - comentário que identifica o bloco do chamador
23
- */
24
8
  export function ensureCommonExclude(cwd, patterns, marker) {
25
9
  if (!patterns.length) return;
26
10
  try {
27
- // Pelo disco, sem subprocesso: um `git` gerado aqui abria uma janela de
28
- // console no win32 quando o chamador era um processo destacado.
11
+ // Pelo disco: um `git` gerado por um processo destacado abre console no win32.
29
12
  const common = resolveGitCommonDir(cwd);
30
13
  if (!common) return;
31
14
  const infoDir = join(common, 'info');