@monoes/monomindcli 2.15.6 → 2.16.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/helpers/build-skill-registry.cjs +23 -2
  2. package/.claude/helpers/handlers/route-handler.cjs +89 -3
  3. package/.claude/helpers/handlers/session-restore-handler.cjs +1 -1
  4. package/.claude/helpers/jev-picker.cjs +499 -0
  5. package/.claude/helpers/redact-secrets.cjs +69 -0
  6. package/.claude/skills/mastermind-agent-select/SKILL.md +28 -0
  7. package/dist/src/catalog/blueprints.d.ts +39 -0
  8. package/dist/src/catalog/blueprints.d.ts.map +1 -0
  9. package/dist/src/catalog/blueprints.js +78 -0
  10. package/dist/src/catalog/blueprints.js.map +1 -0
  11. package/dist/src/catalog/digest.d.ts +16 -0
  12. package/dist/src/catalog/digest.d.ts.map +1 -0
  13. package/dist/src/catalog/digest.js +62 -0
  14. package/dist/src/catalog/digest.js.map +1 -0
  15. package/dist/src/catalog/frontmatter.d.ts +23 -0
  16. package/dist/src/catalog/frontmatter.d.ts.map +1 -0
  17. package/dist/src/catalog/frontmatter.js +80 -0
  18. package/dist/src/catalog/frontmatter.js.map +1 -0
  19. package/dist/src/catalog/lifecycle.d.ts +31 -0
  20. package/dist/src/catalog/lifecycle.d.ts.map +1 -0
  21. package/dist/src/catalog/lifecycle.js +78 -0
  22. package/dist/src/catalog/lifecycle.js.map +1 -0
  23. package/dist/src/catalog/projection.d.ts +57 -0
  24. package/dist/src/catalog/projection.d.ts.map +1 -0
  25. package/dist/src/catalog/projection.js +272 -0
  26. package/dist/src/catalog/projection.js.map +1 -0
  27. package/dist/src/catalog/scan.d.ts +37 -0
  28. package/dist/src/catalog/scan.d.ts.map +1 -0
  29. package/dist/src/catalog/scan.js +214 -0
  30. package/dist/src/catalog/scan.js.map +1 -0
  31. package/dist/src/catalog/snapshot.d.ts +62 -0
  32. package/dist/src/catalog/snapshot.d.ts.map +1 -0
  33. package/dist/src/catalog/snapshot.js +183 -0
  34. package/dist/src/catalog/snapshot.js.map +1 -0
  35. package/dist/src/catalog/stage.d.ts +26 -0
  36. package/dist/src/catalog/stage.d.ts.map +1 -0
  37. package/dist/src/catalog/stage.js +316 -0
  38. package/dist/src/catalog/stage.js.map +1 -0
  39. package/dist/src/catalog/state.d.ts +23 -0
  40. package/dist/src/catalog/state.d.ts.map +1 -0
  41. package/dist/src/catalog/state.js +102 -0
  42. package/dist/src/catalog/state.js.map +1 -0
  43. package/dist/src/catalog/types.d.ts +289 -0
  44. package/dist/src/catalog/types.d.ts.map +1 -0
  45. package/dist/src/catalog/types.js +118 -0
  46. package/dist/src/catalog/types.js.map +1 -0
  47. package/dist/src/commands/catalog.d.ts +14 -0
  48. package/dist/src/commands/catalog.d.ts.map +1 -0
  49. package/dist/src/commands/catalog.js +392 -0
  50. package/dist/src/commands/catalog.js.map +1 -0
  51. package/dist/src/commands/doctor-catalog-checks.d.ts +3 -0
  52. package/dist/src/commands/doctor-catalog-checks.d.ts.map +1 -0
  53. package/dist/src/commands/doctor-catalog-checks.js +69 -0
  54. package/dist/src/commands/doctor-catalog-checks.js.map +1 -0
  55. package/dist/src/commands/doctor-decision-checks.d.ts +10 -0
  56. package/dist/src/commands/doctor-decision-checks.d.ts.map +1 -0
  57. package/dist/src/commands/doctor-decision-checks.js +82 -0
  58. package/dist/src/commands/doctor-decision-checks.js.map +1 -0
  59. package/dist/src/commands/doctor.d.ts.map +1 -1
  60. package/dist/src/commands/doctor.js +9 -1
  61. package/dist/src/commands/doctor.js.map +1 -1
  62. package/dist/src/commands/index.d.ts +2 -2
  63. package/dist/src/commands/index.d.ts.map +1 -1
  64. package/dist/src/commands/index.js +4 -1
  65. package/dist/src/commands/index.js.map +1 -1
  66. package/dist/src/commands/org-observe.d.ts.map +1 -1
  67. package/dist/src/commands/org-observe.js +7 -1
  68. package/dist/src/commands/org-observe.js.map +1 -1
  69. package/dist/src/commands/org-skills.d.ts.map +1 -1
  70. package/dist/src/commands/org-skills.js +14 -4
  71. package/dist/src/commands/org-skills.js.map +1 -1
  72. package/dist/src/commands/org.d.ts.map +1 -1
  73. package/dist/src/commands/org.js +8 -1
  74. package/dist/src/commands/org.js.map +1 -1
  75. package/dist/src/commands/pick.d.ts +4 -0
  76. package/dist/src/commands/pick.d.ts.map +1 -0
  77. package/dist/src/commands/pick.js +77 -0
  78. package/dist/src/commands/pick.js.map +1 -0
  79. package/dist/src/commands/route.js +1 -1
  80. package/dist/src/commands/route.js.map +1 -1
  81. package/dist/src/decision/catalogs.d.ts +20 -0
  82. package/dist/src/decision/catalogs.d.ts.map +1 -0
  83. package/dist/src/decision/catalogs.js +64 -0
  84. package/dist/src/decision/catalogs.js.map +1 -0
  85. package/dist/src/decision/jev.d.ts +85 -0
  86. package/dist/src/decision/jev.d.ts.map +1 -0
  87. package/dist/src/decision/jev.js +72 -0
  88. package/dist/src/decision/jev.js.map +1 -0
  89. package/dist/src/decision/picks.d.ts +50 -0
  90. package/dist/src/decision/picks.d.ts.map +1 -0
  91. package/dist/src/decision/picks.js +100 -0
  92. package/dist/src/decision/picks.js.map +1 -0
  93. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  94. package/dist/src/init/helpers-generator.js +7 -0
  95. package/dist/src/init/helpers-generator.js.map +1 -1
  96. package/dist/src/orgrt/checkpoint-ops.d.ts.map +1 -1
  97. package/dist/src/orgrt/checkpoint-ops.js +6 -1
  98. package/dist/src/orgrt/checkpoint-ops.js.map +1 -1
  99. package/dist/src/orgrt/completion-gate.d.ts +15 -0
  100. package/dist/src/orgrt/completion-gate.d.ts.map +1 -1
  101. package/dist/src/orgrt/completion-gate.js +59 -3
  102. package/dist/src/orgrt/completion-gate.js.map +1 -1
  103. package/dist/src/orgrt/daemon.d.ts +18 -1
  104. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  105. package/dist/src/orgrt/daemon.js +33 -3
  106. package/dist/src/orgrt/daemon.js.map +1 -1
  107. package/dist/src/orgrt/decisions.d.ts +24 -0
  108. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  109. package/dist/src/orgrt/decisions.js +164 -18
  110. package/dist/src/orgrt/decisions.js.map +1 -1
  111. package/dist/src/orgrt/review-packet.d.ts +1 -1
  112. package/dist/src/orgrt/review-packet.d.ts.map +1 -1
  113. package/dist/src/orgrt/review-packet.js +2 -2
  114. package/dist/src/orgrt/review-packet.js.map +1 -1
  115. package/dist/src/orgrt/session.d.ts +9 -0
  116. package/dist/src/orgrt/session.d.ts.map +1 -1
  117. package/dist/src/orgrt/session.js +24 -2
  118. package/dist/src/orgrt/session.js.map +1 -1
  119. package/dist/src/orgrt/skill-library.d.ts +10 -1
  120. package/dist/src/orgrt/skill-library.d.ts.map +1 -1
  121. package/dist/src/orgrt/skill-library.js +53 -4
  122. package/dist/src/orgrt/skill-library.js.map +1 -1
  123. package/dist/src/orgrt/task-dag.d.ts +3 -0
  124. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  125. package/dist/src/orgrt/task-dag.js +6 -0
  126. package/dist/src/orgrt/task-dag.js.map +1 -1
  127. package/dist/src/orgrt/types.d.ts +2 -0
  128. package/dist/src/orgrt/types.d.ts.map +1 -1
  129. package/dist/src/orgrt/types.js +4 -0
  130. package/dist/src/orgrt/types.js.map +1 -1
  131. package/dist/src/platform-adapters/mutation.d.ts +34 -0
  132. package/dist/src/platform-adapters/mutation.d.ts.map +1 -0
  133. package/dist/src/platform-adapters/mutation.js +188 -0
  134. package/dist/src/platform-adapters/mutation.js.map +1 -0
  135. package/dist/src/platform-adapters/operations.d.ts +7 -1
  136. package/dist/src/platform-adapters/operations.d.ts.map +1 -1
  137. package/dist/src/platform-adapters/operations.js +18 -76
  138. package/dist/src/platform-adapters/operations.js.map +1 -1
  139. package/dist/src/routing/jev-step.d.ts +17 -0
  140. package/dist/src/routing/jev-step.d.ts.map +1 -0
  141. package/dist/src/routing/jev-step.js +27 -0
  142. package/dist/src/routing/jev-step.js.map +1 -0
  143. package/dist/src/routing/route-layer-factory.d.ts.map +1 -1
  144. package/dist/src/routing/route-layer-factory.js +16 -1
  145. package/dist/src/routing/route-layer-factory.js.map +1 -1
  146. package/dist/src/utils/redaction.d.ts.map +1 -1
  147. package/dist/src/utils/redaction.js +19 -4
  148. package/dist/src/utils/redaction.js.map +1 -1
  149. package/dist/tsconfig.tsbuildinfo +1 -1
  150. package/package.json +4 -4
  151. package/.claude/skills/monodesign/SKILL.md +0 -257
  152. package/.claude/skills/monodesign/adapt.md +0 -53
  153. package/.claude/skills/monodesign/agents/monodesign-asset-producer.md +0 -100
  154. package/.claude/skills/monodesign/agents/monodesign-manual-edit-applier.md +0 -103
  155. package/.claude/skills/monodesign/animate.md +0 -65
  156. package/.claude/skills/monodesign/audit.md +0 -89
  157. package/.claude/skills/monodesign/bolder.md +0 -50
  158. package/.claude/skills/monodesign/clarify.md +0 -64
  159. package/.claude/skills/monodesign/colorize.md +0 -68
  160. package/.claude/skills/monodesign/craft.md +0 -51
  161. package/.claude/skills/monodesign/critique.md +0 -66
  162. package/.claude/skills/monodesign/delight.md +0 -47
  163. package/.claude/skills/monodesign/distill.md +0 -56
  164. package/.claude/skills/monodesign/document.md +0 -83
  165. package/.claude/skills/monodesign/extract.md +0 -74
  166. package/.claude/skills/monodesign/harden.md +0 -65
  167. package/.claude/skills/monodesign/init.md +0 -30
  168. package/.claude/skills/monodesign/layout.md +0 -36
  169. package/.claude/skills/monodesign/live.md +0 -34
  170. package/.claude/skills/monodesign/onboard.md +0 -50
  171. package/.claude/skills/monodesign/optimize.md +0 -64
  172. package/.claude/skills/monodesign/overdrive.md +0 -56
  173. package/.claude/skills/monodesign/polish.md +0 -68
  174. package/.claude/skills/monodesign/quieter.md +0 -57
  175. package/.claude/skills/monodesign/reference/adapt.md +0 -312
  176. package/.claude/skills/monodesign/reference/adapt.native.md +0 -58
  177. package/.claude/skills/monodesign/reference/android.md +0 -40
  178. package/.claude/skills/monodesign/reference/animate.md +0 -203
  179. package/.claude/skills/monodesign/reference/antipatterns-catalog.md +0 -410
  180. package/.claude/skills/monodesign/reference/audit.md +0 -135
  181. package/.claude/skills/monodesign/reference/audit.native.md +0 -139
  182. package/.claude/skills/monodesign/reference/bolder.md +0 -120
  183. package/.claude/skills/monodesign/reference/brand-workflow.md +0 -180
  184. package/.claude/skills/monodesign/reference/brand.md +0 -108
  185. package/.claude/skills/monodesign/reference/clarify.md +0 -288
  186. package/.claude/skills/monodesign/reference/codex.md +0 -105
  187. package/.claude/skills/monodesign/reference/cognitive-load.md +0 -106
  188. package/.claude/skills/monodesign/reference/color-and-contrast.md +0 -105
  189. package/.claude/skills/monodesign/reference/colorize.md +0 -257
  190. package/.claude/skills/monodesign/reference/component-specs.md +0 -260
  191. package/.claude/skills/monodesign/reference/component-states.md +0 -274
  192. package/.claude/skills/monodesign/reference/component-system.md +0 -358
  193. package/.claude/skills/monodesign/reference/copy-formulas.md +0 -160
  194. package/.claude/skills/monodesign/reference/craft.md +0 -123
  195. package/.claude/skills/monodesign/reference/critique.md +0 -780
  196. package/.claude/skills/monodesign/reference/delight.md +0 -302
  197. package/.claude/skills/monodesign/reference/design-principles.md +0 -246
  198. package/.claude/skills/monodesign/reference/distill.md +0 -111
  199. package/.claude/skills/monodesign/reference/document.md +0 -429
  200. package/.claude/skills/monodesign/reference/extract.md +0 -69
  201. package/.claude/skills/monodesign/reference/harden.md +0 -347
  202. package/.claude/skills/monodesign/reference/heuristics-scoring.md +0 -234
  203. package/.claude/skills/monodesign/reference/hooks.md +0 -92
  204. package/.claude/skills/monodesign/reference/image-prompts.md +0 -130
  205. package/.claude/skills/monodesign/reference/init.md +0 -221
  206. package/.claude/skills/monodesign/reference/interaction-design.md +0 -189
  207. package/.claude/skills/monodesign/reference/ios.md +0 -45
  208. package/.claude/skills/monodesign/reference/layout.md +0 -185
  209. package/.claude/skills/monodesign/reference/live.md +0 -710
  210. package/.claude/skills/monodesign/reference/motion-design.md +0 -109
  211. package/.claude/skills/monodesign/reference/onboard.md +0 -234
  212. package/.claude/skills/monodesign/reference/optimize.md +0 -258
  213. package/.claude/skills/monodesign/reference/overdrive.md +0 -130
  214. package/.claude/skills/monodesign/reference/personas.md +0 -179
  215. package/.claude/skills/monodesign/reference/polish.md +0 -241
  216. package/.claude/skills/monodesign/reference/pre-delivery-checklist.md +0 -108
  217. package/.claude/skills/monodesign/reference/product.md +0 -60
  218. package/.claude/skills/monodesign/reference/quieter.md +0 -99
  219. package/.claude/skills/monodesign/reference/responsive-design.md +0 -114
  220. package/.claude/skills/monodesign/reference/shape.md +0 -165
  221. package/.claude/skills/monodesign/reference/spatial-design.md +0 -100
  222. package/.claude/skills/monodesign/reference/teach.md +0 -10
  223. package/.claude/skills/monodesign/reference/token-architecture.md +0 -222
  224. package/.claude/skills/monodesign/reference/typeset.md +0 -301
  225. package/.claude/skills/monodesign/reference/typography.md +0 -159
  226. package/.claude/skills/monodesign/reference/ux-research.md +0 -143
  227. package/.claude/skills/monodesign/reference/ux-rules.md +0 -211
  228. package/.claude/skills/monodesign/reference/ux-writing.md +0 -107
  229. package/.claude/skills/monodesign/scripts/command-metadata.json +0 -94
  230. package/.claude/skills/monodesign/scripts/context-signals.mjs +0 -260
  231. package/.claude/skills/monodesign/scripts/context.mjs +0 -1065
  232. package/.claude/skills/monodesign/scripts/critique-storage.mjs +0 -416
  233. package/.claude/skills/monodesign/scripts/detect-csp.mjs +0 -193
  234. package/.claude/skills/monodesign/scripts/detect.mjs +0 -33
  235. package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +0 -1960
  236. package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +0 -321
  237. package/.claude/skills/monodesign/scripts/detector/design-system.mjs +0 -814
  238. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +0 -5628
  239. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns.mjs +0 -50
  240. package/.claude/skills/monodesign/scripts/detector/engines/browser/detect-url.mjs +0 -259
  241. package/.claude/skills/monodesign/scripts/detector/engines/browser/drivers.mjs +0 -339
  242. package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +0 -636
  243. package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +0 -1016
  244. package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +0 -253
  245. package/.claude/skills/monodesign/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -198
  246. package/.claude/skills/monodesign/scripts/detector/findings.mjs +0 -12
  247. package/.claude/skills/monodesign/scripts/detector/fix/css-regions.mjs +0 -181
  248. package/.claude/skills/monodesign/scripts/detector/fix/fixers.mjs +0 -373
  249. package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +0 -426
  250. package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +0 -198
  251. package/.claude/skills/monodesign/scripts/detector/profile/profiler.mjs +0 -166
  252. package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +0 -563
  253. package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +0 -3024
  254. package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +0 -124
  255. package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +0 -101
  256. package/.claude/skills/monodesign/scripts/detector/shared/fonts.mjs +0 -30
  257. package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +0 -148
  258. package/.claude/skills/monodesign/scripts/detector/shared/page.mjs +0 -7
  259. package/.claude/skills/monodesign/scripts/hook-admin.mjs +0 -694
  260. package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +0 -516
  261. package/.claude/skills/monodesign/scripts/hook-lib.mjs +0 -1785
  262. package/.claude/skills/monodesign/scripts/hook.mjs +0 -61
  263. package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +0 -809
  264. package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +0 -69
  265. package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +0 -640
  266. package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +0 -129
  267. package/.claude/skills/monodesign/scripts/lib/provider.mjs +0 -4
  268. package/.claude/skills/monodesign/scripts/lib/target-args.mjs +0 -42
  269. package/.claude/skills/monodesign/scripts/live/browser-script-parts.mjs +0 -50
  270. package/.claude/skills/monodesign/scripts/live/completion.mjs +0 -19
  271. package/.claude/skills/monodesign/scripts/live/event-validation.mjs +0 -137
  272. package/.claude/skills/monodesign/scripts/live/insert-ui.mjs +0 -458
  273. package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +0 -939
  274. package/.claude/skills/monodesign/scripts/live/manual-edit-routes.mjs +0 -357
  275. package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +0 -152
  276. package/.claude/skills/monodesign/scripts/live/session-store.mjs +0 -293
  277. package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +0 -826
  278. package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +0 -279
  279. package/.claude/skills/monodesign/scripts/live/ui-core.mjs +0 -180
  280. package/.claude/skills/monodesign/scripts/live/vocabulary.mjs +0 -36
  281. package/.claude/skills/monodesign/scripts/live-accept.mjs +0 -812
  282. package/.claude/skills/monodesign/scripts/live-browser-dom.js +0 -147
  283. package/.claude/skills/monodesign/scripts/live-browser-session.js +0 -124
  284. package/.claude/skills/monodesign/scripts/live-browser.js +0 -11213
  285. package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +0 -1241
  286. package/.claude/skills/monodesign/scripts/live-complete.mjs +0 -75
  287. package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +0 -683
  288. package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +0 -51
  289. package/.claude/skills/monodesign/scripts/live-inject.mjs +0 -596
  290. package/.claude/skills/monodesign/scripts/live-insert.mjs +0 -272
  291. package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +0 -363
  292. package/.claude/skills/monodesign/scripts/live-poll.mjs +0 -384
  293. package/.claude/skills/monodesign/scripts/live-resume.mjs +0 -94
  294. package/.claude/skills/monodesign/scripts/live-server.mjs +0 -1210
  295. package/.claude/skills/monodesign/scripts/live-status.mjs +0 -61
  296. package/.claude/skills/monodesign/scripts/live-target.mjs +0 -30
  297. package/.claude/skills/monodesign/scripts/live-wrap.mjs +0 -884
  298. package/.claude/skills/monodesign/scripts/live.mjs +0 -297
  299. package/.claude/skills/monodesign/scripts/modern-screenshot.umd.js +0 -14
  300. package/.claude/skills/monodesign/scripts/palette.mjs +0 -635
  301. package/.claude/skills/monodesign/scripts/pin.mjs +0 -221
  302. package/.claude/skills/monodesign/shape.md +0 -71
  303. package/.claude/skills/monodesign/teach.md +0 -18
  304. package/.claude/skills/monodesign/typeset.md +0 -59
@@ -1,516 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Monodesign design hook — Cursor preToolUse write gate.
4
- *
5
- * Cursor's stop hook is not consistently dispatched by the headless agent, so
6
- * this hook checks proposed Write/Edit content before it lands. It only denies
7
- * writes when the real detector finds an issue in the proposed UI content.
8
- *
9
- * Contract: never break a turn accidentally. On malformed input or internal
10
- * errors, allow the tool and exit 0.
11
- */
12
-
13
- import fs from 'node:fs';
14
- import os from 'node:os';
15
- import path from 'node:path';
16
-
17
- import {
18
- ALLOWED_EXTS,
19
- EDIT_COUNT_THRESHOLD,
20
- SENSITIVE_PATH,
21
- appendDesignSystemNote,
22
- designSystemOptions,
23
- filterFindings,
24
- isGeneratedPath,
25
- isNativePlatform,
26
- loadDetector,
27
- matchConfiguredExtension,
28
- matchesAnyGlob,
29
- persistCache,
30
- readCache,
31
- readConfig,
32
- renderTemplate,
33
- resolveCacheCwd,
34
- resolveProjectCwd,
35
- resolveProjectPlatform,
36
- truthy,
37
- writeAuditLog,
38
- } from './hook-lib.mjs';
39
-
40
- async function readStdin() {
41
- if (process.stdin.isTTY) return '';
42
- const chunks = [];
43
- for await (const chunk of process.stdin) chunks.push(chunk);
44
- return Buffer.concat(chunks).toString('utf-8');
45
- }
46
-
47
- function done(payload = null) {
48
- if (payload) process.stdout.write(JSON.stringify(payload));
49
- process.exit(0);
50
- }
51
-
52
- function allow(extra = {}, payload = {}) {
53
- writeAuditLog(process.env, {
54
- ts: new Date().toISOString(),
55
- event: 'preToolUse',
56
- ...extra,
57
- });
58
- return done({ permission: 'allow', ...payload });
59
- }
60
-
61
- function deny(message, audit) {
62
- writeAuditLog(process.env, {
63
- ts: new Date().toISOString(),
64
- event: 'preToolUse',
65
- blocked: true,
66
- ...audit,
67
- });
68
- return done({
69
- permission: 'deny',
70
- user_message: message,
71
- agent_message: message,
72
- });
73
- }
74
-
75
- function toolInput(event) {
76
- return event?.tool_input && typeof event.tool_input === 'object' ? event.tool_input : {};
77
- }
78
-
79
- function proposedFilePath(event, cwd) {
80
- const input = toolInput(event);
81
- const raw = input.file_path || input.path || input.target_file || event?.file_path;
82
- const candidate = typeof raw === 'string' && raw.trim()
83
- ? raw
84
- : shellWriteDestination(shellCommand(input));
85
- if (typeof candidate !== 'string' || !candidate.trim()) return '';
86
- return path.isAbsolute(candidate) ? candidate : path.resolve(cwd, candidate);
87
- }
88
-
89
- function proposedContent(event, cwd, filePath) {
90
- const input = toolInput(event);
91
- for (const key of ['content', 'streamContent', 'text']) {
92
- if (typeof input[key] === 'string') return input[key];
93
- }
94
-
95
- const editProjection = projectedEditContent(input, filePath, cwd);
96
- if (editProjection !== undefined) return editProjection;
97
-
98
- if (hasFragmentEditContent(input)) {
99
- return { skipped: 'fragment-only-edit' };
100
- }
101
-
102
- const command = shellCommand(input);
103
- const pythonContent = shellPythonWriteContent(command);
104
- if (pythonContent) return pythonContent;
105
- const shellContent = shellHereDocContent(command);
106
- if (shellContent) return shellContent;
107
- const copiedContent = shellCopiedFileContent(command, cwd);
108
- if (copiedContent) return copiedContent;
109
- return '';
110
- }
111
-
112
- function hasFragmentEditContent(input) {
113
- if (!input || typeof input !== 'object') return false;
114
- if (typeof input.new_string === 'string' || typeof input.newString === 'string' || typeof input.new_str === 'string' || typeof input.replacement === 'string') {
115
- return true;
116
- }
117
- return Array.isArray(input.edits) && input.edits.some((edit) => edit && typeof edit === 'object');
118
- }
119
-
120
- function projectedEditContent(input, filePath, cwd) {
121
- if (!filePath) return undefined;
122
- const singleOld = firstString(input, ['old_string', 'oldString', 'old_str', 'target']);
123
- const singleNew = firstString(input, ['new_string', 'newString', 'new_str', 'replacement']);
124
- if (singleOld !== undefined || singleNew !== undefined) {
125
- if (singleOld === undefined || singleNew === undefined) return { skipped: 'fragment-only-edit' };
126
- const original = readExistingProjectFile(filePath, cwd);
127
- if (original === null) return { skipped: 'edit-original-unreadable' };
128
- const projected = replaceOnce(original, singleOld, singleNew);
129
- return projected === null ? { skipped: 'edit-old-string-missing' } : projected;
130
- }
131
-
132
- if (!Array.isArray(input.edits)) return undefined;
133
- const original = readExistingProjectFile(filePath, cwd);
134
- if (original === null) return { skipped: 'edit-original-unreadable' };
135
-
136
- let projected = original;
137
- for (const edit of input.edits) {
138
- if (!edit || typeof edit !== 'object') return { skipped: 'fragment-only-edit' };
139
- const oldString = firstString(edit, ['old_string', 'oldString', 'old_str', 'target']);
140
- const newString = firstString(edit, ['new_string', 'newString', 'new_str', 'replacement']);
141
- if (oldString === undefined || newString === undefined) return { skipped: 'fragment-only-edit' };
142
- const next = replaceOnce(projected, oldString, newString);
143
- if (next === null) return { skipped: 'edit-old-string-missing' };
144
- projected = next;
145
- }
146
- return projected;
147
- }
148
-
149
- function firstString(obj, keys) {
150
- for (const key of keys) {
151
- if (typeof obj?.[key] === 'string') return obj[key];
152
- }
153
- return undefined;
154
- }
155
-
156
- function replaceOnce(original, oldString, newString) {
157
- if (oldString === '') return null;
158
- const index = original.indexOf(oldString);
159
- if (index === -1) return null;
160
- return `${original.slice(0, index)}${newString}${original.slice(index + oldString.length)}`;
161
- }
162
-
163
- function readExistingProjectFile(filePath, cwd) {
164
- if (!isInsideProject(filePath, cwd)) return null;
165
- if (SENSITIVE_PATH.test(filePath) || isGeneratedPath(filePath, cwd)) return null;
166
- try {
167
- const stat = fs.statSync(filePath);
168
- if (!stat.isFile() || stat.size > 1024 * 1024) return null;
169
- return fs.readFileSync(filePath, 'utf-8');
170
- } catch {
171
- return null;
172
- }
173
- }
174
-
175
- function shellCommand(input) {
176
- if (typeof input.command === 'string') return input.command;
177
- if (input.args && typeof input.args.command === 'string') return input.args.command;
178
- return '';
179
- }
180
-
181
- function shellRedirectPath(command) {
182
- if (!command || typeof command !== 'string') return '';
183
- const match = command.match(/(?:^|[\s;&|])(?:>>?|1>>?)\s*(?:"([^"]+)"|'([^']+)'|([^<>\s]+))/);
184
- return (match?.[1] || match?.[2] || match?.[3] || '').trim();
185
- }
186
-
187
- function shellWriteDestination(command) {
188
- return shellRedirectPath(command) || shellTeeDestination(command) || shellCopyPaths(command)?.dest || shellPythonWriteDestination(command) || '';
189
- }
190
-
191
- function shellPythonWriteDestination(command) {
192
- if (!/\bpython(?:3)?\b/.test(command || '')) return '';
193
- const directPath = firstMatch(command, /(?:^|[^\w.])(?:pathlib\.)?Path\(\s*(["'])(.*?)\1\s*\)\s*\.write_text\s*\(/);
194
- if (directPath) return directPath;
195
-
196
- const pathsByVar = new Map();
197
- const assignmentRe = /\b([A-Za-z_]\w*)\s*=\s*(?:pathlib\.)?Path\(\s*(["'])(.*?)\2\s*\)/g;
198
- let assignment;
199
- while ((assignment = assignmentRe.exec(command))) {
200
- pathsByVar.set(assignment[1], assignment[3]);
201
- }
202
-
203
- const writeVarRe = /\b([A-Za-z_]\w*)\.write_text\s*\(/g;
204
- let writeVar;
205
- while ((writeVar = writeVarRe.exec(command))) {
206
- const candidate = pathsByVar.get(writeVar[1]);
207
- if (candidate) return candidate;
208
- }
209
-
210
- return firstMatch(command, /\bopen\(\s*(["'])(.*?)\1\s*,\s*(["'])[wax](?:\+)?b?\3/);
211
- }
212
-
213
- function firstMatch(value, re) {
214
- const match = String(value || '').match(re);
215
- return (match?.[2] || '').trim();
216
- }
217
-
218
- function shellTeeDestination(command) {
219
- const words = shellWords(command);
220
- const teeIndex = words.findIndex((word) => path.basename(word) === 'tee');
221
- if (teeIndex === -1) return '';
222
- for (const word of words.slice(teeIndex + 1)) {
223
- if (['&&', '||', ';', '|'].includes(word)) break;
224
- if (word === '--') continue;
225
- if (word.startsWith('-')) continue;
226
- return word;
227
- }
228
- return '';
229
- }
230
-
231
- function shellCopiedFileContent(command, cwd) {
232
- const source = shellCopyPaths(command)?.source;
233
- if (!source) return '';
234
- const sourcePath = path.isAbsolute(source) ? source : path.resolve(cwd, source);
235
- if (!isInsideProject(sourcePath, cwd)) return '';
236
- if (SENSITIVE_PATH.test(sourcePath) || isGeneratedPath(sourcePath, cwd)) return '';
237
- try {
238
- const stat = fs.statSync(sourcePath);
239
- if (!stat.isFile() || stat.size > 1024 * 1024) return '';
240
- return fs.readFileSync(sourcePath, 'utf-8');
241
- } catch {
242
- return '';
243
- }
244
- }
245
-
246
- function shellCopyPaths(command) {
247
- const words = shellWords(command);
248
- if (words.length < 3 || path.basename(words[0]) !== 'cp') return null;
249
- const args = [];
250
- for (const word of words.slice(1)) {
251
- if (['&&', '||', ';', '|'].includes(word)) break;
252
- if (word === '--') continue;
253
- if (word.startsWith('-')) continue;
254
- args.push(word);
255
- }
256
- if (args.length < 2) return null;
257
- return { source: args[args.length - 2], dest: args[args.length - 1] };
258
- }
259
-
260
- function shellWords(command) {
261
- if (!command || typeof command !== 'string') return [];
262
- const words = [];
263
- const re = /"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'|([^\s]+)/g;
264
- let match;
265
- while ((match = re.exec(command))) {
266
- words.push((match[1] ?? match[2] ?? match[3] ?? '').replace(/\\(["'])/g, '$1'));
267
- }
268
- return words;
269
- }
270
-
271
- function shellHereDocContent(command) {
272
- if (!command || typeof command !== 'string') return '';
273
- const markerMatch = command.match(/<<-?\s*['"]?([A-Za-z0-9_.-]+)['"]?[^\r\n]*\r?\n/);
274
- if (!markerMatch) return '';
275
- const marker = markerMatch[1];
276
- const start = (markerMatch.index || 0) + markerMatch[0].length;
277
- const rest = command.slice(start);
278
- const endRe = new RegExp(`\\r?\\n${escapeRegExp(marker)}(?:\\r?\\n|$)`);
279
- const end = rest.search(endRe);
280
- return end >= 0 ? rest.slice(0, end) : '';
281
- }
282
-
283
- function shellPythonWriteContent(command) {
284
- if (!/\bpython(?:3)?\b/.test(command || '')) return '';
285
- const script = shellHereDocContent(command) || command;
286
- return pythonStringArg(script, /\.write_text\s*\(\s*/g) || pythonStringArg(script, /\.write\s*\(\s*/g);
287
- }
288
-
289
- function pythonStringArg(script, prefixRe) {
290
- let prefix;
291
- while ((prefix = prefixRe.exec(script))) {
292
- const start = prefixRe.lastIndex;
293
- const triple = script.slice(start, start + 3);
294
- if (triple === "'''" || triple === '"""') {
295
- const end = script.indexOf(triple, start + 3);
296
- if (end !== -1) return script.slice(start + 3, end);
297
- continue;
298
- }
299
- const quote = script[start];
300
- if (quote !== '"' && quote !== "'") continue;
301
- let out = '';
302
- for (let i = start + 1; i < script.length; i++) {
303
- const ch = script[i];
304
- if (ch === '\\') {
305
- out += script[i + 1] || '';
306
- i += 1;
307
- } else if (ch === quote) {
308
- return out;
309
- } else {
310
- out += ch;
311
- }
312
- }
313
- }
314
- return '';
315
- }
316
-
317
- function escapeRegExp(value) {
318
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
319
- }
320
-
321
- function relativePath(filePath, cwd) {
322
- try {
323
- const rel = path.relative(cwd, filePath).split(path.sep).join('/');
324
- if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return filePath;
325
- return rel.split(path.sep).join('/');
326
- } catch {
327
- return filePath;
328
- }
329
- }
330
-
331
- function isInsideProject(filePath, cwd) {
332
- try {
333
- const rel = path.relative(cwd, filePath).split(path.sep).join('/');
334
- return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
335
- } catch {
336
- return false;
337
- }
338
- }
339
-
340
- // The static HTML engine reads its input from disk, but preToolUse only has
341
- // the proposed content. Stage it in a temp file so html-engine targets get the
342
- // same DOM-structural rules pre-write that runHook applies post-edit.
343
- async function detectProposedHtml(detector, content, filePath, scanOptions) {
344
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'monodesign-pre-'));
345
- const tmpFile = path.join(dir, path.basename(filePath));
346
- try {
347
- fs.writeFileSync(tmpFile, content);
348
- const findings = await detector.detectHtml(tmpFile, scanOptions);
349
- // Findings carry the temp path; remap so file-scoped ignores still match.
350
- return (findings || []).map((f) => (f && typeof f === 'object' ? { ...f, file: filePath } : f));
351
- } finally {
352
- fs.rmSync(dir, { recursive: true, force: true });
353
- }
354
- }
355
-
356
- function cursorBlockMessage(findings, filePath, config, cwd) {
357
- const rendered = renderTemplate(findings, filePath, config, { cwd });
358
- const blocked = rendered.replace(
359
- '[monodesign@1] Design hook findings requiring review',
360
- '[monodesign@1] Monodesign design hook blocked this write before it landed. Design hook findings requiring review',
361
- );
362
- return blocked.length > 4000 ? `${blocked.slice(0, 3984)}\n...(truncated)` : blocked;
363
- }
364
-
365
- function findingSignature(findings) {
366
- return findings
367
- .map((finding) => `${finding.antipattern || 'unknown'}:${finding.line || 0}`)
368
- .sort()
369
- .join('|');
370
- }
371
-
372
- function bumpCursorDenial(cache, sessionId, filePath, findings) {
373
- const session = cache.sessions[sessionId] || { updatedAt: Date.now(), files: {} };
374
- cache.sessions[sessionId] = session;
375
- session.updatedAt = Date.now();
376
- const fileEntry = session.files[filePath] || { editCount: 0, findings: [] };
377
- session.files[filePath] = fileEntry;
378
- const key = findingSignature(findings);
379
- fileEntry.cursorDenials = fileEntry.cursorDenials && typeof fileEntry.cursorDenials === 'object'
380
- ? fileEntry.cursorDenials
381
- : {};
382
- fileEntry.cursorDenials[key] = (fileEntry.cursorDenials[key] || 0) + 1;
383
- return { key, count: fileEntry.cursorDenials[key] };
384
- }
385
-
386
- async function main() {
387
- if (truthy(process.env.MONODESIGN_HOOK_DISABLED)) {
388
- return allow({ skipped: 'env-disabled' });
389
- }
390
-
391
- let event = null;
392
- try {
393
- const raw = await readStdin();
394
- if (raw) event = JSON.parse(raw);
395
- } catch {
396
- return allow({ skipped: 'stdin-malformed' });
397
- }
398
-
399
- if (!event || typeof event !== 'object') {
400
- return allow({ skipped: 'stdin-empty' });
401
- }
402
-
403
- const sessionCwd = resolveProjectCwd(event);
404
- const started = Date.now();
405
- const filePath = proposedFilePath(event, sessionCwd);
406
- // Re-key config/cache to the edited file's project root when the session
407
- // was launched from a non-project umbrella directory (issue #305).
408
- const cwd = resolveCacheCwd(filePath, sessionCwd);
409
- const audit = {
410
- harness: 'cursor',
411
- cwd,
412
- tool: event.tool_name || null,
413
- file: filePath || null,
414
- };
415
-
416
- if (!filePath) return allow({ ...audit, skipped: 'no-file-path', durationMs: Date.now() - started });
417
- if (!isInsideProject(filePath, cwd)) return allow({ ...audit, skipped: 'outside-project', durationMs: Date.now() - started });
418
- if (SENSITIVE_PATH.test(filePath)) return allow({ ...audit, skipped: 'sensitive', durationMs: Date.now() - started });
419
- if (isGeneratedPath(filePath, cwd)) return allow({ ...audit, skipped: 'generated', durationMs: Date.now() - started });
420
-
421
- // Config is read before the extension gate so `detector.extensions` entries
422
- // (e.g. `.blade.php` template files, issue #316) can widen it.
423
- const config = readConfig(cwd);
424
- const ext = path.extname(filePath).toLowerCase();
425
- const configuredExt = matchConfiguredExtension(filePath, config.extensions);
426
- audit.ext = configuredExt ? configuredExt.ext : ext;
427
- if (!ALLOWED_EXTS.has(ext) && !configuredExt) return allow({ ...audit, skipped: 'extension', durationMs: Date.now() - started });
428
-
429
- const contentResult = proposedContent(event, cwd, filePath);
430
- if (contentResult && typeof contentResult === 'object' && contentResult.skipped) {
431
- return allow({ ...audit, skipped: contentResult.skipped, durationMs: Date.now() - started });
432
- }
433
- const content = typeof contentResult === 'string' ? contentResult : '';
434
- if (!content) return allow({ ...audit, skipped: 'no-proposed-content', durationMs: Date.now() - started });
435
-
436
- if (config.enabled === false) return allow({ ...audit, skipped: 'config-disabled', durationMs: Date.now() - started });
437
-
438
- // Web rule engine, native project: stand aside (see resolveProjectPlatform).
439
- const platform = resolveProjectPlatform(cwd);
440
- if (isNativePlatform(platform)) {
441
- return allow({ ...audit, skipped: 'native-platform', platform, durationMs: Date.now() - started });
442
- }
443
-
444
- const rel = relativePath(filePath, cwd);
445
- if (matchesAnyGlob(rel, config.ignoreFiles) || matchesAnyGlob(filePath, config.ignoreFiles)) {
446
- return allow({ ...audit, skipped: 'config-ignore-file', durationMs: Date.now() - started });
447
- }
448
-
449
- const detector = await loadDetector();
450
- if (!detector || typeof detector.detectText !== 'function') {
451
- return allow({ ...audit, skipped: 'detector-missing', durationMs: Date.now() - started });
452
- }
453
- const scanOptions = designSystemOptions(config, detector, cwd);
454
-
455
- // Mirror runHook's engine routing so template issues the HTML engine catches
456
- // post-edit cannot slip past the pre-write gate.
457
- const useHtmlEngine = configuredExt
458
- ? configuredExt.engine === 'html'
459
- : (ext === '.html' || ext === '.htm');
460
- let findings = [];
461
- try {
462
- findings = useHtmlEngine && typeof detector.detectHtml === 'function'
463
- ? await detectProposedHtml(detector, content, filePath, scanOptions)
464
- : await detector.detectText(content, filePath, scanOptions);
465
- } catch {
466
- return allow({ ...audit, error: 'detector-threw', durationMs: Date.now() - started });
467
- }
468
-
469
- const filtered = filterFindings(findings || [], content, ext, config);
470
- if (filtered.length === 0) {
471
- return allow({
472
- ...audit,
473
- findings: (findings || []).length,
474
- blockedFindings: 0,
475
- durationMs: Date.now() - started,
476
- });
477
- }
478
-
479
- const message = appendDesignSystemNote(cursorBlockMessage(filtered, filePath, config, cwd), scanOptions);
480
- const sessionId = event.session_id || event.conversation_id || 'unknown';
481
- const cache = readCache(cwd);
482
- const denial = bumpCursorDenial(cache, sessionId, filePath, filtered);
483
- persistCache(cwd, cache);
484
- if (denial.count > EDIT_COUNT_THRESHOLD) {
485
- const warning = `${message}\n\nThis is the ${denial.count}th repeated denial for the same file and finding signature, so Monodesign is allowing this write to avoid a loop. Reconsider the issue immediately after the tool runs.`;
486
- return allow({
487
- ...audit,
488
- findings: (findings || []).length,
489
- blockedFindings: filtered.length,
490
- cursorDenialKey: denial.key,
491
- cursorDenialCount: denial.count,
492
- downgraded: true,
493
- chars: warning.length,
494
- durationMs: Date.now() - started,
495
- }, {
496
- user_message: warning,
497
- agent_message: warning,
498
- });
499
- }
500
- return deny(message, {
501
- ...audit,
502
- findings: (findings || []).length,
503
- blockedFindings: filtered.length,
504
- cursorDenialKey: denial.key,
505
- cursorDenialCount: denial.count,
506
- chars: message.length,
507
- durationMs: Date.now() - started,
508
- });
509
- }
510
-
511
- main().catch((err) => {
512
- if (process.env.MONODESIGN_HOOK_DEBUG) {
513
- process.stderr.write(`[monodesign-hook-before-edit] ${err}\n`);
514
- }
515
- done({ permission: 'allow' });
516
- });