@m2-ai/lattice-cli 0.0.0-stage → 0.8.7

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 (743) hide show
  1. package/DISCLOSURE +1 -0
  2. package/README.md +118 -2
  3. package/bundles/expo-mobile/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
  4. package/bundles/expo-mobile/.agents/skills/lattice-release-manager/SKILL.md +29 -0
  5. package/bundles/expo-mobile/.agents/skills/lattice-verify/SKILL.md +27 -0
  6. package/bundles/expo-mobile/.claude/agents/lattice-feature-agent.md +10 -0
  7. package/bundles/expo-mobile/.claude/agents/lattice-release-manager.md +9 -0
  8. package/bundles/expo-mobile/.claude/hooks/README.md +9 -0
  9. package/bundles/expo-mobile/.claude/hooks/pre-commit-verify.mjs +10 -0
  10. package/bundles/expo-mobile/.claude/hooks/pre-tool-use.mjs +51 -0
  11. package/bundles/expo-mobile/.claude/rules/lattice-core.md +20 -0
  12. package/bundles/expo-mobile/.claude/settings.json +38 -0
  13. package/bundles/expo-mobile/.claude/skills/lattice-verify/SKILL.md +27 -0
  14. package/bundles/expo-mobile/.codex/agents/lattice-feature-agent.toml +9 -0
  15. package/bundles/expo-mobile/.codex/agents/lattice-release-manager.toml +9 -0
  16. package/bundles/expo-mobile/.cursor/mcp.json +34 -0
  17. package/bundles/expo-mobile/.cursor/rules/lattice-core.mdc +35 -0
  18. package/bundles/expo-mobile/.cursor/rules.md +94 -0
  19. package/bundles/expo-mobile/.env.example +19 -0
  20. package/bundles/expo-mobile/.github/PULL_REQUEST_TEMPLATE.md +42 -0
  21. package/bundles/expo-mobile/.github/agents/lattice-feature-agent.agent.md +7 -0
  22. package/bundles/expo-mobile/.github/agents/lattice-release-manager.agent.md +7 -0
  23. package/bundles/expo-mobile/.github/copilot-instructions.md +37 -0
  24. package/bundles/expo-mobile/.github/instructions/lattice-core.instructions.md +11 -0
  25. package/bundles/expo-mobile/.github/workflows/ci.yml +26 -0
  26. package/bundles/expo-mobile/.gitmessage +24 -0
  27. package/bundles/expo-mobile/.lattice/config.json +9 -0
  28. package/bundles/expo-mobile/.lattice/dependency-security.json +91 -0
  29. package/bundles/expo-mobile/.lattice/prompts/01-feature.md +36 -0
  30. package/bundles/expo-mobile/.lattice/prompts/02-bugfix.md +32 -0
  31. package/bundles/expo-mobile/.lattice/prompts/03-refactor.md +30 -0
  32. package/bundles/expo-mobile/.lattice/prompts/04-test.md +31 -0
  33. package/bundles/expo-mobile/.lattice/prompts/05-polish.md +38 -0
  34. package/bundles/expo-mobile/.lattice/prompts/06-security.md +32 -0
  35. package/bundles/expo-mobile/.lattice/release-workflow.json +55 -0
  36. package/bundles/expo-mobile/.lattice/schemas/feature-handoff.schema.json +264 -0
  37. package/bundles/expo-mobile/.lattice/templates/feature-handoff.json +52 -0
  38. package/bundles/expo-mobile/.mcp.json +35 -0
  39. package/bundles/expo-mobile/AGENTS.md +58 -0
  40. package/bundles/expo-mobile/CLAUDE.md +75 -0
  41. package/bundles/expo-mobile/PROJECT_GUIDE.md +299 -0
  42. package/bundles/expo-mobile/README.md +39 -0
  43. package/bundles/expo-mobile/__tests__/home.test.tsx +22 -0
  44. package/bundles/expo-mobile/app/_layout.tsx +20 -0
  45. package/bundles/expo-mobile/app/index.tsx +72 -0
  46. package/bundles/expo-mobile/app.json +33 -0
  47. package/bundles/expo-mobile/components/ui/Button.tsx +75 -0
  48. package/bundles/expo-mobile/components/ui/Input.tsx +50 -0
  49. package/bundles/expo-mobile/components/ui/index.ts +2 -0
  50. package/bundles/expo-mobile/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
  51. package/bundles/expo-mobile/docs/rules/codex.md +62 -0
  52. package/bundles/expo-mobile/eslint.config.js +7 -0
  53. package/bundles/expo-mobile/lib/supabase/client.ts +22 -0
  54. package/bundles/expo-mobile/lib/supabase/realtime.ts +10 -0
  55. package/bundles/expo-mobile/lib/supabase/storage.ts +6 -0
  56. package/bundles/expo-mobile/package.json +65 -0
  57. package/bundles/expo-mobile/plugins/with-lattice-commons-io.cjs +42 -0
  58. package/bundles/expo-mobile/prompts/01-feature-implementation.md +48 -0
  59. package/bundles/expo-mobile/prompts/02-bug-fix.md +43 -0
  60. package/bundles/expo-mobile/prompts/03-safe-refactor.md +38 -0
  61. package/bundles/expo-mobile/prompts/04-test-first.md +41 -0
  62. package/bundles/expo-mobile/prompts/05-ui-polish.md +44 -0
  63. package/bundles/expo-mobile/prompts/06-security-review.md +39 -0
  64. package/bundles/expo-mobile/prompts/quality.md +143 -0
  65. package/bundles/expo-mobile/supabase/migrations/001_initial_schema.sql +66 -0
  66. package/bundles/expo-mobile/tsconfig.json +21 -0
  67. package/bundles/expo-mobile/types/database.types.ts +76 -0
  68. package/bundles/expo-mobile/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
  69. package/bundles/expo-supabase/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
  70. package/bundles/expo-supabase/.agents/skills/lattice-release-manager/SKILL.md +29 -0
  71. package/bundles/expo-supabase/.agents/skills/lattice-verify/SKILL.md +27 -0
  72. package/bundles/expo-supabase/.claude/agents/lattice-feature-agent.md +10 -0
  73. package/bundles/expo-supabase/.claude/agents/lattice-release-manager.md +9 -0
  74. package/bundles/expo-supabase/.claude/hooks/README.md +9 -0
  75. package/bundles/expo-supabase/.claude/hooks/pre-commit-verify.mjs +10 -0
  76. package/bundles/expo-supabase/.claude/hooks/pre-tool-use.mjs +51 -0
  77. package/bundles/expo-supabase/.claude/rules/lattice-core.md +20 -0
  78. package/bundles/expo-supabase/.claude/settings.json +38 -0
  79. package/bundles/expo-supabase/.claude/skills/lattice-verify/SKILL.md +27 -0
  80. package/bundles/expo-supabase/.codex/agents/lattice-feature-agent.toml +9 -0
  81. package/bundles/expo-supabase/.codex/agents/lattice-release-manager.toml +9 -0
  82. package/bundles/expo-supabase/.cursor/mcp.json +34 -0
  83. package/bundles/expo-supabase/.cursor/rules/lattice-core.mdc +35 -0
  84. package/bundles/expo-supabase/.cursor/rules.md +94 -0
  85. package/bundles/expo-supabase/.env.example +12 -0
  86. package/bundles/expo-supabase/.github/PULL_REQUEST_TEMPLATE.md +42 -0
  87. package/bundles/expo-supabase/.github/agents/lattice-feature-agent.agent.md +7 -0
  88. package/bundles/expo-supabase/.github/agents/lattice-release-manager.agent.md +7 -0
  89. package/bundles/expo-supabase/.github/copilot-instructions.md +37 -0
  90. package/bundles/expo-supabase/.github/instructions/lattice-core.instructions.md +11 -0
  91. package/bundles/expo-supabase/.github/workflows/ci.yml +26 -0
  92. package/bundles/expo-supabase/.gitmessage +24 -0
  93. package/bundles/expo-supabase/.lattice/config.json +9 -0
  94. package/bundles/expo-supabase/.lattice/dependency-security.json +91 -0
  95. package/bundles/expo-supabase/.lattice/prompts/01-feature.md +36 -0
  96. package/bundles/expo-supabase/.lattice/prompts/02-bugfix.md +32 -0
  97. package/bundles/expo-supabase/.lattice/prompts/03-refactor.md +30 -0
  98. package/bundles/expo-supabase/.lattice/prompts/04-test.md +31 -0
  99. package/bundles/expo-supabase/.lattice/prompts/05-polish.md +38 -0
  100. package/bundles/expo-supabase/.lattice/prompts/06-security.md +32 -0
  101. package/bundles/expo-supabase/.lattice/release-workflow.json +55 -0
  102. package/bundles/expo-supabase/.lattice/schemas/feature-handoff.schema.json +264 -0
  103. package/bundles/expo-supabase/.lattice/templates/feature-handoff.json +52 -0
  104. package/bundles/expo-supabase/.mcp.json +35 -0
  105. package/bundles/expo-supabase/AGENTS.md +58 -0
  106. package/bundles/expo-supabase/CLAUDE.md +75 -0
  107. package/bundles/expo-supabase/PROJECT_GUIDE.md +201 -0
  108. package/bundles/expo-supabase/README.md +38 -0
  109. package/bundles/expo-supabase/__tests__/home.test.tsx +22 -0
  110. package/bundles/expo-supabase/app/_layout.tsx +14 -0
  111. package/bundles/expo-supabase/app/index.tsx +72 -0
  112. package/bundles/expo-supabase/app.json +33 -0
  113. package/bundles/expo-supabase/components/ui/Button.tsx +75 -0
  114. package/bundles/expo-supabase/components/ui/Input.tsx +50 -0
  115. package/bundles/expo-supabase/components/ui/index.ts +2 -0
  116. package/bundles/expo-supabase/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
  117. package/bundles/expo-supabase/docs/rules/codex.md +62 -0
  118. package/bundles/expo-supabase/eslint.config.js +7 -0
  119. package/bundles/expo-supabase/lib/supabase/client.ts +22 -0
  120. package/bundles/expo-supabase/lib/supabase/realtime.ts +10 -0
  121. package/bundles/expo-supabase/lib/supabase/storage.ts +6 -0
  122. package/bundles/expo-supabase/package.json +60 -0
  123. package/bundles/expo-supabase/plugins/with-lattice-commons-io.cjs +42 -0
  124. package/bundles/expo-supabase/prompts/01-feature-implementation.md +8 -0
  125. package/bundles/expo-supabase/prompts/02-bug-fix.md +9 -0
  126. package/bundles/expo-supabase/prompts/03-safe-refactor.md +8 -0
  127. package/bundles/expo-supabase/prompts/04-test-first.md +7 -0
  128. package/bundles/expo-supabase/prompts/05-ui-polish.md +7 -0
  129. package/bundles/expo-supabase/prompts/06-security-review.md +7 -0
  130. package/bundles/expo-supabase/prompts/quality.md +143 -0
  131. package/bundles/expo-supabase/supabase/migrations/001_initial_schema.sql +66 -0
  132. package/bundles/expo-supabase/tsconfig.json +21 -0
  133. package/bundles/expo-supabase/types/database.types.ts +76 -0
  134. package/bundles/expo-supabase/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
  135. package/bundles/nextjs-landing/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
  136. package/bundles/nextjs-landing/.agents/skills/lattice-release-manager/SKILL.md +29 -0
  137. package/bundles/nextjs-landing/.agents/skills/lattice-verify/SKILL.md +27 -0
  138. package/bundles/nextjs-landing/.claude/agents/lattice-feature-agent.md +10 -0
  139. package/bundles/nextjs-landing/.claude/agents/lattice-release-manager.md +9 -0
  140. package/bundles/nextjs-landing/.claude/hooks/README.md +9 -0
  141. package/bundles/nextjs-landing/.claude/hooks/pre-commit-verify.mjs +10 -0
  142. package/bundles/nextjs-landing/.claude/hooks/pre-tool-use.mjs +51 -0
  143. package/bundles/nextjs-landing/.claude/rules/lattice-core.md +20 -0
  144. package/bundles/nextjs-landing/.claude/settings.json +38 -0
  145. package/bundles/nextjs-landing/.claude/skills/lattice-verify/SKILL.md +27 -0
  146. package/bundles/nextjs-landing/.codex/agents/lattice-feature-agent.toml +9 -0
  147. package/bundles/nextjs-landing/.codex/agents/lattice-release-manager.toml +9 -0
  148. package/bundles/nextjs-landing/.cursor/mcp.json +34 -0
  149. package/bundles/nextjs-landing/.cursor/rules/lattice-core.mdc +35 -0
  150. package/bundles/nextjs-landing/.cursor/rules.md +95 -0
  151. package/bundles/nextjs-landing/.env.example +24 -0
  152. package/bundles/nextjs-landing/.github/PULL_REQUEST_TEMPLATE.md +42 -0
  153. package/bundles/nextjs-landing/.github/agents/lattice-feature-agent.agent.md +7 -0
  154. package/bundles/nextjs-landing/.github/agents/lattice-release-manager.agent.md +7 -0
  155. package/bundles/nextjs-landing/.github/copilot-instructions.md +37 -0
  156. package/bundles/nextjs-landing/.github/instructions/lattice-core.instructions.md +11 -0
  157. package/bundles/nextjs-landing/.github/workflows/ci.yml +26 -0
  158. package/bundles/nextjs-landing/.gitmessage +24 -0
  159. package/bundles/nextjs-landing/.lattice/config.json +9 -0
  160. package/bundles/nextjs-landing/.lattice/dependency-security.json +46 -0
  161. package/bundles/nextjs-landing/.lattice/prompts/01-feature.md +36 -0
  162. package/bundles/nextjs-landing/.lattice/prompts/02-bugfix.md +32 -0
  163. package/bundles/nextjs-landing/.lattice/prompts/03-refactor.md +30 -0
  164. package/bundles/nextjs-landing/.lattice/prompts/04-test.md +31 -0
  165. package/bundles/nextjs-landing/.lattice/prompts/05-polish.md +38 -0
  166. package/bundles/nextjs-landing/.lattice/prompts/06-security.md +32 -0
  167. package/bundles/nextjs-landing/.lattice/release-workflow.json +55 -0
  168. package/bundles/nextjs-landing/.lattice/schemas/feature-handoff.schema.json +264 -0
  169. package/bundles/nextjs-landing/.lattice/templates/feature-handoff.json +52 -0
  170. package/bundles/nextjs-landing/.mcp.json +35 -0
  171. package/bundles/nextjs-landing/AGENTS.md +58 -0
  172. package/bundles/nextjs-landing/CLAUDE.md +76 -0
  173. package/bundles/nextjs-landing/PROJECT_GUIDE.md +216 -0
  174. package/bundles/nextjs-landing/README.md +39 -0
  175. package/bundles/nextjs-landing/app/api/example/[id]/route.ts +34 -0
  176. package/bundles/nextjs-landing/app/api/example/route.ts +24 -0
  177. package/bundles/nextjs-landing/app/api/health/route.ts +8 -0
  178. package/bundles/nextjs-landing/app/global-error.tsx +17 -0
  179. package/bundles/nextjs-landing/app/globals.css +55 -0
  180. package/bundles/nextjs-landing/app/layout.tsx +22 -0
  181. package/bundles/nextjs-landing/app/page.tsx +42 -0
  182. package/bundles/nextjs-landing/components/ui/button.tsx +39 -0
  183. package/bundles/nextjs-landing/components/ui/card.tsx +53 -0
  184. package/bundles/nextjs-landing/components/ui/index.ts +4 -0
  185. package/bundles/nextjs-landing/components/ui/input.tsx +23 -0
  186. package/bundles/nextjs-landing/components/ui/spinner.tsx +22 -0
  187. package/bundles/nextjs-landing/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
  188. package/bundles/nextjs-landing/docs/rules/codex.md +62 -0
  189. package/bundles/nextjs-landing/eslint.config.mjs +16 -0
  190. package/bundles/nextjs-landing/instrumentation-client.ts +30 -0
  191. package/bundles/nextjs-landing/instrumentation.ts +12 -0
  192. package/bundles/nextjs-landing/jest.config.mjs +16 -0
  193. package/bundles/nextjs-landing/jest.setup.js +1 -0
  194. package/bundles/nextjs-landing/lib/utils.ts +6 -0
  195. package/bundles/nextjs-landing/next-env.d.ts +5 -0
  196. package/bundles/nextjs-landing/next.config.ts +15 -0
  197. package/bundles/nextjs-landing/package.json +48 -0
  198. package/bundles/nextjs-landing/postcss.config.mjs +7 -0
  199. package/bundles/nextjs-landing/prompts/01-feature-implementation.md +9 -0
  200. package/bundles/nextjs-landing/prompts/02-bug-fix.md +10 -0
  201. package/bundles/nextjs-landing/prompts/03-safe-refactor.md +8 -0
  202. package/bundles/nextjs-landing/prompts/04-test-first.md +8 -0
  203. package/bundles/nextjs-landing/prompts/05-ui-polish.md +8 -0
  204. package/bundles/nextjs-landing/prompts/06-security-review.md +7 -0
  205. package/bundles/nextjs-landing/prompts/quality.md +269 -0
  206. package/bundles/nextjs-landing/sentry.edge.config.ts +15 -0
  207. package/bundles/nextjs-landing/sentry.server.config.ts +15 -0
  208. package/bundles/nextjs-landing/tsconfig.json +42 -0
  209. package/bundles/nextjs-landing/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
  210. package/bundles/nextjs-saas/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
  211. package/bundles/nextjs-saas/.agents/skills/lattice-release-manager/SKILL.md +29 -0
  212. package/bundles/nextjs-saas/.agents/skills/lattice-verify/SKILL.md +27 -0
  213. package/bundles/nextjs-saas/.claude/agents/lattice-feature-agent.md +10 -0
  214. package/bundles/nextjs-saas/.claude/agents/lattice-release-manager.md +9 -0
  215. package/bundles/nextjs-saas/.claude/hooks/README.md +9 -0
  216. package/bundles/nextjs-saas/.claude/hooks/pre-commit-verify.mjs +10 -0
  217. package/bundles/nextjs-saas/.claude/hooks/pre-tool-use.mjs +51 -0
  218. package/bundles/nextjs-saas/.claude/rules/lattice-core.md +20 -0
  219. package/bundles/nextjs-saas/.claude/settings.json +38 -0
  220. package/bundles/nextjs-saas/.claude/skills/lattice-verify/SKILL.md +27 -0
  221. package/bundles/nextjs-saas/.codex/agents/lattice-feature-agent.toml +9 -0
  222. package/bundles/nextjs-saas/.codex/agents/lattice-release-manager.toml +9 -0
  223. package/bundles/nextjs-saas/.cursor/mcp.json +34 -0
  224. package/bundles/nextjs-saas/.cursor/rules/lattice-core.mdc +35 -0
  225. package/bundles/nextjs-saas/.cursor/rules.md +95 -0
  226. package/bundles/nextjs-saas/.env.example +48 -0
  227. package/bundles/nextjs-saas/.github/PULL_REQUEST_TEMPLATE.md +42 -0
  228. package/bundles/nextjs-saas/.github/agents/lattice-feature-agent.agent.md +7 -0
  229. package/bundles/nextjs-saas/.github/agents/lattice-release-manager.agent.md +7 -0
  230. package/bundles/nextjs-saas/.github/copilot-instructions.md +37 -0
  231. package/bundles/nextjs-saas/.github/instructions/lattice-core.instructions.md +11 -0
  232. package/bundles/nextjs-saas/.github/workflows/ci.yml +26 -0
  233. package/bundles/nextjs-saas/.gitmessage +24 -0
  234. package/bundles/nextjs-saas/.lattice/config.json +9 -0
  235. package/bundles/nextjs-saas/.lattice/dependency-security.json +46 -0
  236. package/bundles/nextjs-saas/.lattice/prompts/01-feature.md +36 -0
  237. package/bundles/nextjs-saas/.lattice/prompts/02-bugfix.md +32 -0
  238. package/bundles/nextjs-saas/.lattice/prompts/03-refactor.md +30 -0
  239. package/bundles/nextjs-saas/.lattice/prompts/04-test.md +31 -0
  240. package/bundles/nextjs-saas/.lattice/prompts/05-polish.md +38 -0
  241. package/bundles/nextjs-saas/.lattice/prompts/06-security.md +32 -0
  242. package/bundles/nextjs-saas/.lattice/prompts/quality.md +269 -0
  243. package/bundles/nextjs-saas/.lattice/release-workflow.json +55 -0
  244. package/bundles/nextjs-saas/.lattice/schemas/feature-handoff.schema.json +264 -0
  245. package/bundles/nextjs-saas/.lattice/templates/feature-handoff.json +52 -0
  246. package/bundles/nextjs-saas/.mcp.json +35 -0
  247. package/bundles/nextjs-saas/AGENTS.md +58 -0
  248. package/bundles/nextjs-saas/CLAUDE.md +76 -0
  249. package/bundles/nextjs-saas/PROJECT_GUIDE.md +279 -0
  250. package/bundles/nextjs-saas/README.md +42 -0
  251. package/bundles/nextjs-saas/app/(auth)/forgot-password/page.tsx +101 -0
  252. package/bundles/nextjs-saas/app/(auth)/layout.tsx +7 -0
  253. package/bundles/nextjs-saas/app/(auth)/login/page.tsx +108 -0
  254. package/bundles/nextjs-saas/app/(auth)/reset-password/page.tsx +135 -0
  255. package/bundles/nextjs-saas/app/(auth)/signup/page.tsx +144 -0
  256. package/bundles/nextjs-saas/app/(dashboard)/dashboard/page.tsx +80 -0
  257. package/bundles/nextjs-saas/app/(dashboard)/layout.tsx +49 -0
  258. package/bundles/nextjs-saas/app/api/billing/checkout/route.ts +47 -0
  259. package/bundles/nextjs-saas/app/api/billing/portal/route.ts +29 -0
  260. package/bundles/nextjs-saas/app/api/example/[id]/route.ts +93 -0
  261. package/bundles/nextjs-saas/app/api/example/route.ts +61 -0
  262. package/bundles/nextjs-saas/app/api/health/route.ts +8 -0
  263. package/bundles/nextjs-saas/app/api/stripe/webhook/route.ts +57 -0
  264. package/bundles/nextjs-saas/app/api/user/route.ts +49 -0
  265. package/bundles/nextjs-saas/app/auth/callback/route.ts +16 -0
  266. package/bundles/nextjs-saas/app/auth/signout/route.ts +10 -0
  267. package/bundles/nextjs-saas/app/global-error.tsx +17 -0
  268. package/bundles/nextjs-saas/app/globals.css +55 -0
  269. package/bundles/nextjs-saas/app/layout.tsx +22 -0
  270. package/bundles/nextjs-saas/app/page.tsx +84 -0
  271. package/bundles/nextjs-saas/components/ui/button.tsx +39 -0
  272. package/bundles/nextjs-saas/components/ui/card.tsx +53 -0
  273. package/bundles/nextjs-saas/components/ui/index.ts +4 -0
  274. package/bundles/nextjs-saas/components/ui/input.tsx +23 -0
  275. package/bundles/nextjs-saas/components/ui/spinner.tsx +22 -0
  276. package/bundles/nextjs-saas/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
  277. package/bundles/nextjs-saas/docs/rules/codex.md +62 -0
  278. package/bundles/nextjs-saas/eslint.config.mjs +16 -0
  279. package/bundles/nextjs-saas/instrumentation-client.ts +30 -0
  280. package/bundles/nextjs-saas/instrumentation.ts +12 -0
  281. package/bundles/nextjs-saas/jest.config.mjs +16 -0
  282. package/bundles/nextjs-saas/jest.setup.js +1 -0
  283. package/bundles/nextjs-saas/lib/auth/password-recovery.ts +94 -0
  284. package/bundles/nextjs-saas/lib/stripe.ts +10 -0
  285. package/bundles/nextjs-saas/lib/supabase/admin.ts +14 -0
  286. package/bundles/nextjs-saas/lib/supabase/client.ts +8 -0
  287. package/bundles/nextjs-saas/lib/supabase/server.ts +27 -0
  288. package/bundles/nextjs-saas/lib/utils.ts +6 -0
  289. package/bundles/nextjs-saas/next-env.d.ts +5 -0
  290. package/bundles/nextjs-saas/next.config.ts +15 -0
  291. package/bundles/nextjs-saas/package.json +50 -0
  292. package/bundles/nextjs-saas/postcss.config.mjs +7 -0
  293. package/bundles/nextjs-saas/prompts/01-feature-implementation.md +92 -0
  294. package/bundles/nextjs-saas/prompts/02-bug-fix.md +78 -0
  295. package/bundles/nextjs-saas/prompts/03-safe-refactor.md +82 -0
  296. package/bundles/nextjs-saas/prompts/04-test-first.md +80 -0
  297. package/bundles/nextjs-saas/prompts/05-ui-polish.md +82 -0
  298. package/bundles/nextjs-saas/prompts/06-security-review.md +74 -0
  299. package/bundles/nextjs-saas/proxy.ts +61 -0
  300. package/bundles/nextjs-saas/sentry.edge.config.ts +15 -0
  301. package/bundles/nextjs-saas/sentry.server.config.ts +15 -0
  302. package/bundles/nextjs-saas/supabase/migrations/001_initial_schema.sql +66 -0
  303. package/bundles/nextjs-saas/supabase/migrations/002_billing.sql +17 -0
  304. package/bundles/nextjs-saas/tsconfig.json +42 -0
  305. package/bundles/nextjs-saas/types/database.types.ts +76 -0
  306. package/bundles/nextjs-saas/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
  307. package/bundles/nextjs-trpc/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
  308. package/bundles/nextjs-trpc/.agents/skills/lattice-release-manager/SKILL.md +29 -0
  309. package/bundles/nextjs-trpc/.agents/skills/lattice-verify/SKILL.md +27 -0
  310. package/bundles/nextjs-trpc/.claude/agents/lattice-feature-agent.md +10 -0
  311. package/bundles/nextjs-trpc/.claude/agents/lattice-release-manager.md +9 -0
  312. package/bundles/nextjs-trpc/.claude/hooks/README.md +9 -0
  313. package/bundles/nextjs-trpc/.claude/hooks/pre-commit-verify.mjs +10 -0
  314. package/bundles/nextjs-trpc/.claude/hooks/pre-tool-use.mjs +51 -0
  315. package/bundles/nextjs-trpc/.claude/rules/lattice-core.md +20 -0
  316. package/bundles/nextjs-trpc/.claude/settings.json +38 -0
  317. package/bundles/nextjs-trpc/.claude/skills/lattice-verify/SKILL.md +27 -0
  318. package/bundles/nextjs-trpc/.codex/agents/lattice-feature-agent.toml +9 -0
  319. package/bundles/nextjs-trpc/.codex/agents/lattice-release-manager.toml +9 -0
  320. package/bundles/nextjs-trpc/.cursor/mcp.json +34 -0
  321. package/bundles/nextjs-trpc/.cursor/rules/lattice-core.mdc +35 -0
  322. package/bundles/nextjs-trpc/.cursor/rules.md +95 -0
  323. package/bundles/nextjs-trpc/.env.example +21 -0
  324. package/bundles/nextjs-trpc/.github/PULL_REQUEST_TEMPLATE.md +42 -0
  325. package/bundles/nextjs-trpc/.github/agents/lattice-feature-agent.agent.md +7 -0
  326. package/bundles/nextjs-trpc/.github/agents/lattice-release-manager.agent.md +7 -0
  327. package/bundles/nextjs-trpc/.github/copilot-instructions.md +37 -0
  328. package/bundles/nextjs-trpc/.github/instructions/lattice-core.instructions.md +11 -0
  329. package/bundles/nextjs-trpc/.github/workflows/ci.yml +26 -0
  330. package/bundles/nextjs-trpc/.gitmessage +24 -0
  331. package/bundles/nextjs-trpc/.lattice/config.json +9 -0
  332. package/bundles/nextjs-trpc/.lattice/dependency-security.json +46 -0
  333. package/bundles/nextjs-trpc/.lattice/prompts/01-feature.md +36 -0
  334. package/bundles/nextjs-trpc/.lattice/prompts/02-bugfix.md +32 -0
  335. package/bundles/nextjs-trpc/.lattice/prompts/03-refactor.md +30 -0
  336. package/bundles/nextjs-trpc/.lattice/prompts/04-test.md +31 -0
  337. package/bundles/nextjs-trpc/.lattice/prompts/05-polish.md +38 -0
  338. package/bundles/nextjs-trpc/.lattice/prompts/06-security.md +32 -0
  339. package/bundles/nextjs-trpc/.lattice/release-workflow.json +55 -0
  340. package/bundles/nextjs-trpc/.lattice/schemas/feature-handoff.schema.json +264 -0
  341. package/bundles/nextjs-trpc/.lattice/templates/feature-handoff.json +52 -0
  342. package/bundles/nextjs-trpc/.mcp.json +35 -0
  343. package/bundles/nextjs-trpc/AGENTS.md +58 -0
  344. package/bundles/nextjs-trpc/CLAUDE.md +76 -0
  345. package/bundles/nextjs-trpc/PROJECT_GUIDE.md +187 -0
  346. package/bundles/nextjs-trpc/README.md +49 -0
  347. package/bundles/nextjs-trpc/app/api/trpc/[trpc]/route.ts +12 -0
  348. package/bundles/nextjs-trpc/app/global-error.tsx +17 -0
  349. package/bundles/nextjs-trpc/app/globals.css +55 -0
  350. package/bundles/nextjs-trpc/app/layout.tsx +19 -0
  351. package/bundles/nextjs-trpc/app/page.tsx +16 -0
  352. package/bundles/nextjs-trpc/app/providers.tsx +24 -0
  353. package/bundles/nextjs-trpc/components/ui/button.tsx +39 -0
  354. package/bundles/nextjs-trpc/components/ui/card.tsx +53 -0
  355. package/bundles/nextjs-trpc/components/ui/index.ts +4 -0
  356. package/bundles/nextjs-trpc/components/ui/input.tsx +23 -0
  357. package/bundles/nextjs-trpc/components/ui/spinner.tsx +22 -0
  358. package/bundles/nextjs-trpc/components/user-list.tsx +17 -0
  359. package/bundles/nextjs-trpc/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
  360. package/bundles/nextjs-trpc/docs/rules/codex.md +62 -0
  361. package/bundles/nextjs-trpc/eslint.config.mjs +16 -0
  362. package/bundles/nextjs-trpc/instrumentation-client.ts +20 -0
  363. package/bundles/nextjs-trpc/instrumentation.ts +12 -0
  364. package/bundles/nextjs-trpc/jest.config.mjs +16 -0
  365. package/bundles/nextjs-trpc/jest.setup.js +1 -0
  366. package/bundles/nextjs-trpc/lib/db.ts +17 -0
  367. package/bundles/nextjs-trpc/lib/trpc/react.tsx +7 -0
  368. package/bundles/nextjs-trpc/lib/utils.ts +6 -0
  369. package/bundles/nextjs-trpc/next-env.d.ts +5 -0
  370. package/bundles/nextjs-trpc/next.config.ts +15 -0
  371. package/bundles/nextjs-trpc/package.json +62 -0
  372. package/bundles/nextjs-trpc/postcss.config.mjs +7 -0
  373. package/bundles/nextjs-trpc/prisma/schema.prisma +41 -0
  374. package/bundles/nextjs-trpc/prisma.config.ts +10 -0
  375. package/bundles/nextjs-trpc/prompts/01-feature-implementation.md +9 -0
  376. package/bundles/nextjs-trpc/prompts/02-bug-fix.md +9 -0
  377. package/bundles/nextjs-trpc/prompts/03-safe-refactor.md +8 -0
  378. package/bundles/nextjs-trpc/prompts/04-test-first.md +8 -0
  379. package/bundles/nextjs-trpc/prompts/05-ui-polish.md +7 -0
  380. package/bundles/nextjs-trpc/prompts/06-security-review.md +7 -0
  381. package/bundles/nextjs-trpc/prompts/quality.md +269 -0
  382. package/bundles/nextjs-trpc/sentry.edge.config.ts +15 -0
  383. package/bundles/nextjs-trpc/sentry.server.config.ts +15 -0
  384. package/bundles/nextjs-trpc/server/routers/_app.test.ts +60 -0
  385. package/bundles/nextjs-trpc/server/routers/_app.ts +25 -0
  386. package/bundles/nextjs-trpc/server/trpc.ts +12 -0
  387. package/bundles/nextjs-trpc/tsconfig.json +42 -0
  388. package/bundles/nextjs-trpc/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
  389. package/dist/agent-install-session.d.ts +17 -0
  390. package/dist/agent-install-session.d.ts.map +1 -0
  391. package/dist/agent-install-session.js +106 -0
  392. package/dist/agent-install-session.js.map +1 -0
  393. package/dist/agent-install-session.test.d.ts +2 -0
  394. package/dist/agent-install-session.test.d.ts.map +1 -0
  395. package/dist/agent-install-session.test.js +62 -0
  396. package/dist/agent-install-session.test.js.map +1 -0
  397. package/dist/agent-install.d.ts +41 -0
  398. package/dist/agent-install.d.ts.map +1 -0
  399. package/dist/agent-install.js +42 -0
  400. package/dist/agent-install.js.map +1 -0
  401. package/dist/agent-install.test.d.ts +2 -0
  402. package/dist/agent-install.test.d.ts.map +1 -0
  403. package/dist/agent-install.test.js +58 -0
  404. package/dist/agent-install.test.js.map +1 -0
  405. package/dist/apply-merge.test.d.ts +2 -0
  406. package/dist/apply-merge.test.d.ts.map +1 -0
  407. package/dist/apply-merge.test.js +123 -0
  408. package/dist/apply-merge.test.js.map +1 -0
  409. package/dist/bundle-download.d.ts +23 -0
  410. package/dist/bundle-download.d.ts.map +1 -0
  411. package/dist/bundle-download.js +180 -0
  412. package/dist/bundle-download.js.map +1 -0
  413. package/dist/bundle-download.test.d.ts +2 -0
  414. package/dist/bundle-download.test.d.ts.map +1 -0
  415. package/dist/bundle-download.test.js +200 -0
  416. package/dist/bundle-download.test.js.map +1 -0
  417. package/dist/bundle-rollback.test.d.ts +2 -0
  418. package/dist/bundle-rollback.test.d.ts.map +1 -0
  419. package/dist/bundle-rollback.test.js +127 -0
  420. package/dist/bundle-rollback.test.js.map +1 -0
  421. package/dist/bundle-scaffold.test.d.ts +2 -0
  422. package/dist/bundle-scaffold.test.d.ts.map +1 -0
  423. package/dist/bundle-scaffold.test.js +105 -0
  424. package/dist/bundle-scaffold.test.js.map +1 -0
  425. package/dist/commands.d.ts +132 -0
  426. package/dist/commands.d.ts.map +1 -0
  427. package/dist/commands.js +2012 -0
  428. package/dist/commands.js.map +1 -0
  429. package/dist/credential-store.d.ts +27 -0
  430. package/dist/credential-store.d.ts.map +1 -0
  431. package/dist/credential-store.js +48 -0
  432. package/dist/credential-store.js.map +1 -0
  433. package/dist/credential-store.test.d.ts +2 -0
  434. package/dist/credential-store.test.d.ts.map +1 -0
  435. package/dist/credential-store.test.js +80 -0
  436. package/dist/credential-store.test.js.map +1 -0
  437. package/dist/delivered-project-files.d.ts +31 -0
  438. package/dist/delivered-project-files.d.ts.map +1 -0
  439. package/dist/delivered-project-files.js +158 -0
  440. package/dist/delivered-project-files.js.map +1 -0
  441. package/dist/delivered-project-files.test.d.ts +2 -0
  442. package/dist/delivered-project-files.test.d.ts.map +1 -0
  443. package/dist/delivered-project-files.test.js +107 -0
  444. package/dist/delivered-project-files.test.js.map +1 -0
  445. package/dist/doctor-agents.test.d.ts +2 -0
  446. package/dist/doctor-agents.test.d.ts.map +1 -0
  447. package/dist/doctor-agents.test.js +65 -0
  448. package/dist/doctor-agents.test.js.map +1 -0
  449. package/dist/hermes-install.d.ts +102 -0
  450. package/dist/hermes-install.d.ts.map +1 -0
  451. package/dist/hermes-install.js +641 -0
  452. package/dist/hermes-install.js.map +1 -0
  453. package/dist/hermes-install.test.d.ts +2 -0
  454. package/dist/hermes-install.test.d.ts.map +1 -0
  455. package/dist/hermes-install.test.js +866 -0
  456. package/dist/hermes-install.test.js.map +1 -0
  457. package/dist/index.d.ts +115 -0
  458. package/dist/index.d.ts.map +1 -0
  459. package/dist/index.js +5311 -0
  460. package/dist/index.js.map +1 -0
  461. package/dist/openclaw-install.d.ts +67 -0
  462. package/dist/openclaw-install.d.ts.map +1 -0
  463. package/dist/openclaw-install.js +358 -0
  464. package/dist/openclaw-install.js.map +1 -0
  465. package/dist/openclaw-install.test.d.ts +2 -0
  466. package/dist/openclaw-install.test.d.ts.map +1 -0
  467. package/dist/openclaw-install.test.js +296 -0
  468. package/dist/openclaw-install.test.js.map +1 -0
  469. package/dist/password-recovery-bundle.test.d.ts +2 -0
  470. package/dist/password-recovery-bundle.test.d.ts.map +1 -0
  471. package/dist/password-recovery-bundle.test.js +132 -0
  472. package/dist/password-recovery-bundle.test.js.map +1 -0
  473. package/dist/project-bundle-env.d.ts +19 -0
  474. package/dist/project-bundle-env.d.ts.map +1 -0
  475. package/dist/project-bundle-env.js +111 -0
  476. package/dist/project-bundle-env.js.map +1 -0
  477. package/dist/project-bundle-env.test.d.ts +2 -0
  478. package/dist/project-bundle-env.test.d.ts.map +1 -0
  479. package/dist/project-bundle-env.test.js +93 -0
  480. package/dist/project-bundle-env.test.js.map +1 -0
  481. package/dist/project-path-confinement.test.d.ts +2 -0
  482. package/dist/project-path-confinement.test.d.ts.map +1 -0
  483. package/dist/project-path-confinement.test.js +222 -0
  484. package/dist/project-path-confinement.test.js.map +1 -0
  485. package/dist/security/api.d.ts +18 -0
  486. package/dist/security/api.d.ts.map +1 -0
  487. package/dist/security/api.js +238 -0
  488. package/dist/security/api.js.map +1 -0
  489. package/dist/security/api.test.d.ts +2 -0
  490. package/dist/security/api.test.d.ts.map +1 -0
  491. package/dist/security/api.test.js +220 -0
  492. package/dist/security/api.test.js.map +1 -0
  493. package/dist/security/artifact.d.ts +10 -0
  494. package/dist/security/artifact.d.ts.map +1 -0
  495. package/dist/security/artifact.js +283 -0
  496. package/dist/security/artifact.js.map +1 -0
  497. package/dist/security/artifact.test.d.ts +2 -0
  498. package/dist/security/artifact.test.d.ts.map +1 -0
  499. package/dist/security/artifact.test.js +229 -0
  500. package/dist/security/artifact.test.js.map +1 -0
  501. package/dist/security/commands.d.ts +42 -0
  502. package/dist/security/commands.d.ts.map +1 -0
  503. package/dist/security/commands.js +825 -0
  504. package/dist/security/commands.js.map +1 -0
  505. package/dist/security/commands.test.d.ts +2 -0
  506. package/dist/security/commands.test.d.ts.map +1 -0
  507. package/dist/security/commands.test.js +1051 -0
  508. package/dist/security/commands.test.js.map +1 -0
  509. package/dist/security/connection.d.ts +19 -0
  510. package/dist/security/connection.d.ts.map +1 -0
  511. package/dist/security/connection.js +145 -0
  512. package/dist/security/connection.js.map +1 -0
  513. package/dist/security/connection.test.d.ts +2 -0
  514. package/dist/security/connection.test.d.ts.map +1 -0
  515. package/dist/security/connection.test.js +62 -0
  516. package/dist/security/connection.test.js.map +1 -0
  517. package/dist/security/index-routing.test.d.ts +2 -0
  518. package/dist/security/index-routing.test.d.ts.map +1 -0
  519. package/dist/security/index-routing.test.js +169 -0
  520. package/dist/security/index-routing.test.js.map +1 -0
  521. package/dist/security/journal.d.ts +26 -0
  522. package/dist/security/journal.d.ts.map +1 -0
  523. package/dist/security/journal.js +441 -0
  524. package/dist/security/journal.js.map +1 -0
  525. package/dist/security/journal.test.d.ts +2 -0
  526. package/dist/security/journal.test.d.ts.map +1 -0
  527. package/dist/security/journal.test.js +226 -0
  528. package/dist/security/journal.test.js.map +1 -0
  529. package/dist/security/package-verifier.d.ts +15 -0
  530. package/dist/security/package-verifier.d.ts.map +1 -0
  531. package/dist/security/package-verifier.js +65 -0
  532. package/dist/security/package-verifier.js.map +1 -0
  533. package/dist/security/package-verifier.test.d.ts +2 -0
  534. package/dist/security/package-verifier.test.d.ts.map +1 -0
  535. package/dist/security/package-verifier.test.js +57 -0
  536. package/dist/security/package-verifier.test.js.map +1 -0
  537. package/dist/security/runtime.d.ts +43 -0
  538. package/dist/security/runtime.d.ts.map +1 -0
  539. package/dist/security/runtime.js +81 -0
  540. package/dist/security/runtime.js.map +1 -0
  541. package/dist/security/runtime.test.d.ts +2 -0
  542. package/dist/security/runtime.test.d.ts.map +1 -0
  543. package/dist/security/runtime.test.js +88 -0
  544. package/dist/security/runtime.test.js.map +1 -0
  545. package/dist/security/types.d.ts +169 -0
  546. package/dist/security/types.d.ts.map +1 -0
  547. package/dist/security/types.js +3 -0
  548. package/dist/security/types.js.map +1 -0
  549. package/dist/security-runtime/LICENSE +202 -0
  550. package/dist/security-runtime/NOTICE +16 -0
  551. package/dist/security-runtime/artifact-manifest.json +960 -0
  552. package/dist/security-runtime/bin/vsg.mjs +5 -0
  553. package/dist/security-runtime/lattice-catalog.json +176585 -0
  554. package/dist/security-runtime/lattice-runtime.mjs +90 -0
  555. package/dist/security-runtime/licensing/candidates/apache-source-license-evidence.json +478 -0
  556. package/dist/security-runtime/licensing/candidates/betterleaks-generic-api-key.json +76 -0
  557. package/dist/security-runtime/licensing/candidates/betterleaks-jwt.json +74 -0
  558. package/dist/security-runtime/licensing/candidates/blackberry-additional-source-declaration-evidence.json +276 -0
  559. package/dist/security-runtime/licensing/candidates/blackberry-source-declaration-evidence.json +369 -0
  560. package/dist/security-runtime/licensing/candidates/cado-source-declaration-evidence.json +283 -0
  561. package/dist/security-runtime/licensing/candidates/commercial-rule-candidate.json +280 -0
  562. package/dist/security-runtime/licensing/candidates/commercial-runtime-release.json +389 -0
  563. package/dist/security-runtime/licensing/candidates/data-feed-evidence.json +259 -0
  564. package/dist/security-runtime/licensing/candidates/gitlab-sast-rule-identities.json +178 -0
  565. package/dist/security-runtime/licensing/candidates/gitlab-sast-source-evidence.json +1824 -0
  566. package/dist/security-runtime/licensing/candidates/gitlab-sast-source-notice-bundle.json +45 -0
  567. package/dist/security-runtime/licensing/candidates/malpedia-source-license-evidence.json +41 -0
  568. package/dist/security-runtime/licensing/candidates/rule-license-evidence.json +376 -0
  569. package/dist/security-runtime/licensing/candidates/runner-image-composition.json +1500 -0
  570. package/dist/security-runtime/licensing/candidates/selected-runtime-coverage.json +91 -0
  571. package/dist/security-runtime/licensing/candidates/yara-forge-20260927.json +58502 -0
  572. package/dist/security-runtime/licensing/commercial-components.json +379 -0
  573. package/dist/security-runtime/licensing/notices/betterleaks-1.9.0-MIT.txt +21 -0
  574. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5.txt +428 -0
  575. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/33acf42afdd03163d75fe856cb431e70710e0b5deede3362e6d1c4697cfe1c6a.txt +28 -0
  576. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986.txt +674 -0
  577. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/4fecb8fcaf80fd9a6315bca5225ee43377015b46a0459c2925495be885cab19a.txt +21 -0
  578. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/811515f8723aa4007de2fbb75a364127fdec51cda534b87a7ea8f2fef2f96dd7.txt +24 -0
  579. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/814442c3987a2c0411171ad8ad43118e8c08f85c9c5d1dcdecc9787c7ca8689b.txt +664 -0
  580. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/8a959b2413d0c83ca1b3ce9153076c649abf2e4bbfebbb493fb24c2074a1eb8a.txt +38 -0
  581. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/995182e185856186fbbe49f150409ae026aa4cda7e94f83c6ed737cbb77fa9c7.txt +23 -0
  582. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/9b8e1305e46d1dd4011b6ed933c9f6223e08c9c4625001c5fd75a34fa930f616.txt +202 -0
  583. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a01f1a28424bfe7f49a23d339a01847cfa9ace3864d5e7295051772a4bae2271.txt +38 -0
  584. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5681bf9b05db14d86776930017c647ad9e6e56ff6bbcfdf21e5848288dfaf1b.txt +165 -0
  585. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5b8bd9ae96c630bbf229d61c5c5def20274e00be39abfd2fab4d7879ceab462.txt +24 -0
  586. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ada32b3747ce108d5e8bab7918af94abbd03f2fe2a46304f74c6333bbfd914ed.txt +8 -0
  587. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt +201 -0
  588. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c738ae413cf561f174e34f6961f8ca458aae2369a73640dda6234c629b98bcc4.txt +2732 -0
  589. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ce41c9a2c986277f7f9ab146a2195c1888a5d8da062e22d7c9c88666bb7c38cd.txt +17 -0
  590. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/d5f4c36a0528e01cfdd9e4fc60879201d3af49b41c520200643d321fe53da5f9.txt +17 -0
  591. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e08c368ccd455f85c7fb9e04830e730f26e9dbb5c0f77bc88e09a5b0cb37df5b.txt +38 -0
  592. package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e9efb2f7daf11bb6379c06c16b870dddcecc4a10f8704bb8aad807529c8b3c97.txt +8 -0
  593. package/dist/security-runtime/licensing/notices/gitleaks-generic-origin-782f3104-MIT.txt +21 -0
  594. package/dist/security-runtime/package.json +17 -0
  595. package/dist/security-runtime/playbook/README.md +76 -0
  596. package/dist/security-runtime/playbook/access-control.md +56 -0
  597. package/dist/security-runtime/playbook/ai-features.md +53 -0
  598. package/dist/security-runtime/playbook/desktop-python.md +59 -0
  599. package/dist/security-runtime/playbook/input-handling.md +59 -0
  600. package/dist/security-runtime/playbook/mobile-expo.md +59 -0
  601. package/dist/security-runtime/playbook/payments.md +53 -0
  602. package/dist/security-runtime/playbook/privacy-account-recovery.md +59 -0
  603. package/dist/security-runtime/playbook/secrets-config.md +47 -0
  604. package/dist/security-runtime/playbook/supabase.md +48 -0
  605. package/dist/security-runtime/playbook/supply-chain.md +62 -0
  606. package/dist/security-runtime/playbook/webhooks-integrations.md +52 -0
  607. package/dist/security-runtime/rules/vsg-ai-key-in-client.yaml +41 -0
  608. package/dist/security-runtime/rules/vsg-ai-output-raw-html.yaml +35 -0
  609. package/dist/security-runtime/rules/vsg-angular-sanitizer-bypass.yaml +23 -0
  610. package/dist/security-runtime/rules/vsg-expo-token-in-asyncstorage.yaml +22 -0
  611. package/dist/security-runtime/rules/vsg-flask-public-bind.yaml +32 -0
  612. package/dist/security-runtime/rules/vsg-nextjs-action-write-no-auth.yaml +144 -0
  613. package/dist/security-runtime/rules/vsg-nextjs-inline-action-write-no-auth.yaml +161 -0
  614. package/dist/security-runtime/rules/vsg-nextjs-route-write-no-auth.yaml +193 -0
  615. package/dist/security-runtime/rules/vsg-nosql-where-injection.yaml +36 -0
  616. package/dist/security-runtime/rules/vsg-python-pickle-load.yaml +26 -0
  617. package/dist/security-runtime/rules/vsg-python-requests-verify-false.yaml +34 -0
  618. package/dist/security-runtime/rules/vsg-python-subprocess-shell.yaml +23 -0
  619. package/dist/security-runtime/rules/vsg-stripe-client-price.yaml +51 -0
  620. package/dist/security-runtime/rules/vsg-stripe-webhook-no-signature.yaml +31 -0
  621. package/dist/security-runtime/rules/vsg-stripe-webhook-pages-no-signature.yaml +51 -0
  622. package/dist/security-runtime/rules/vsg-supabase-service-role-in-client.yaml +27 -0
  623. package/dist/security-runtime/rules/vsg-supabase-service-role-public-env.yaml +24 -0
  624. package/dist/security-runtime/rules/vsg-xml-external-entities.yaml +19 -0
  625. package/dist/security-runtime/src/agent/brief.mjs +95 -0
  626. package/dist/security-runtime/src/agent/discover.mjs +108 -0
  627. package/dist/security-runtime/src/agent/exec.mjs +64 -0
  628. package/dist/security-runtime/src/agent/ingest.mjs +213 -0
  629. package/dist/security-runtime/src/agent/report.mjs +94 -0
  630. package/dist/security-runtime/src/agent/schema.mjs +62 -0
  631. package/dist/security-runtime/src/agent/tokenize.mjs +38 -0
  632. package/dist/security-runtime/src/checks/README.md +8 -0
  633. package/dist/security-runtime/src/checks/_lib/gitfiles.mjs +35 -0
  634. package/dist/security-runtime/src/checks/_lib/lockfiles.mjs +135 -0
  635. package/dist/security-runtime/src/checks/_lib/osv.mjs +6 -0
  636. package/dist/security-runtime/src/checks/_lib/secrets.mjs +381 -0
  637. package/dist/security-runtime/src/checks/_lib/targets.mjs +79 -0
  638. package/dist/security-runtime/src/checks/agent-review/index.mjs +52 -0
  639. package/dist/security-runtime/src/checks/agent-surface/config.mjs +223 -0
  640. package/dist/security-runtime/src/checks/agent-surface/index.mjs +113 -0
  641. package/dist/security-runtime/src/checks/agent-surface/instructions.mjs +87 -0
  642. package/dist/security-runtime/src/checks/agent-surface/patterns.mjs +68 -0
  643. package/dist/security-runtime/src/checks/agent-surface/unicode.mjs +88 -0
  644. package/dist/security-runtime/src/checks/code/auth-helpers.mjs +659 -0
  645. package/dist/security-runtime/src/checks/code/index.mjs +617 -0
  646. package/dist/security-runtime/src/checks/code/opengrep-container.mjs +85 -0
  647. package/dist/security-runtime/src/checks/code/opengrep-runner.mjs +86 -0
  648. package/dist/security-runtime/src/checks/code/selected-opengrep-runner.mjs +112 -0
  649. package/dist/security-runtime/src/checks/dep-change/guarddog.mjs +142 -0
  650. package/dist/security-runtime/src/checks/dep-change/index.mjs +316 -0
  651. package/dist/security-runtime/src/checks/dep-change/registries.mjs +199 -0
  652. package/dist/security-runtime/src/checks/deps-vuln/index.mjs +305 -0
  653. package/dist/security-runtime/src/checks/desktop-config/index.mjs +112 -0
  654. package/dist/security-runtime/src/checks/dns-mail/index.mjs +71 -0
  655. package/dist/security-runtime/src/checks/iac/index.mjs +83 -0
  656. package/dist/security-runtime/src/checks/install-posture/index.mjs +123 -0
  657. package/dist/security-runtime/src/checks/licenses/index.mjs +143 -0
  658. package/dist/security-runtime/src/checks/live-basic/index.mjs +77 -0
  659. package/dist/security-runtime/src/checks/live-js-secrets/index.mjs +100 -0
  660. package/dist/security-runtime/src/checks/malware/clamav.mjs +17 -0
  661. package/dist/security-runtime/src/checks/malware/defender.mjs +43 -0
  662. package/dist/security-runtime/src/checks/malware/index.mjs +186 -0
  663. package/dist/security-runtime/src/checks/malware/targets.mjs +147 -0
  664. package/dist/security-runtime/src/checks/malware/yara-runner.mjs +201 -0
  665. package/dist/security-runtime/src/checks/malware/yara.mjs +124 -0
  666. package/dist/security-runtime/src/checks/mobile-config/index.mjs +173 -0
  667. package/dist/security-runtime/src/checks/mobile-config/mobsf.mjs +105 -0
  668. package/dist/security-runtime/src/checks/posture/index.mjs +133 -0
  669. package/dist/security-runtime/src/checks/project-scripts/index.mjs +187 -0
  670. package/dist/security-runtime/src/checks/sbom/index.mjs +98 -0
  671. package/dist/security-runtime/src/checks/secrets-build/index.mjs +63 -0
  672. package/dist/security-runtime/src/checks/secrets-history/index.mjs +112 -0
  673. package/dist/security-runtime/src/checks/secrets-tree/env.mjs +120 -0
  674. package/dist/security-runtime/src/checks/secrets-tree/index.mjs +73 -0
  675. package/dist/security-runtime/src/checks/supabase-static/index.mjs +240 -0
  676. package/dist/security-runtime/src/checks/supabase-static/sql.mjs +557 -0
  677. package/dist/security-runtime/src/checks/workflows/index.mjs +93 -0
  678. package/dist/security-runtime/src/cli/deep.mjs +13 -0
  679. package/dist/security-runtime/src/cli/doctor.mjs +82 -0
  680. package/dist/security-runtime/src/cli/index.mjs +60 -0
  681. package/dist/security-runtime/src/cli/live-common.mjs +27 -0
  682. package/dist/security-runtime/src/cli/parse.mjs +56 -0
  683. package/dist/security-runtime/src/cli/portfolio.mjs +34 -0
  684. package/dist/security-runtime/src/cli/release.mjs +28 -0
  685. package/dist/security-runtime/src/cli/report.mjs +18 -0
  686. package/dist/security-runtime/src/cli/run-common.mjs +54 -0
  687. package/dist/security-runtime/src/cli/scan.mjs +12 -0
  688. package/dist/security-runtime/src/cli/setup.mjs +59 -0
  689. package/dist/security-runtime/src/cli/url.mjs +38 -0
  690. package/dist/security-runtime/src/core/accept.mjs +141 -0
  691. package/dist/security-runtime/src/core/archive.mjs +114 -0
  692. package/dist/security-runtime/src/core/base-ref.mjs +43 -0
  693. package/dist/security-runtime/src/core/checks.mjs +39 -0
  694. package/dist/security-runtime/src/core/detect.mjs +90 -0
  695. package/dist/security-runtime/src/core/errors.mjs +5 -0
  696. package/dist/security-runtime/src/core/fetch.mjs +139 -0
  697. package/dist/security-runtime/src/core/findings.mjs +177 -0
  698. package/dist/security-runtime/src/core/gate.mjs +131 -0
  699. package/dist/security-runtime/src/core/installer.mjs +255 -0
  700. package/dist/security-runtime/src/core/lock.mjs +38 -0
  701. package/dist/security-runtime/src/core/nested.mjs +107 -0
  702. package/dist/security-runtime/src/core/paths.mjs +20 -0
  703. package/dist/security-runtime/src/core/pipeline.mjs +196 -0
  704. package/dist/security-runtime/src/core/registry.mjs +81 -0
  705. package/dist/security-runtime/src/core/report.mjs +128 -0
  706. package/dist/security-runtime/src/core/runner.mjs +86 -0
  707. package/dist/security-runtime/src/core/sarif.mjs +141 -0
  708. package/dist/security-runtime/src/lattice/artifact.mjs +1201 -0
  709. package/dist/security-runtime/src/lattice/execution.mjs +45 -0
  710. package/dist/security-runtime/src/lattice/export.mjs +215 -0
  711. package/dist/security-runtime/src/lattice/provenance.mjs +70 -0
  712. package/dist/security-runtime/src/lattice/runtime.mjs +158 -0
  713. package/dist/security-runtime/src/lattice/selected-runtime.mjs +189 -0
  714. package/dist/security-runtime/src/lattice/source-acquirer.mjs +51 -0
  715. package/dist/security-runtime/src/lattice/source-fetcher.mjs +88 -0
  716. package/dist/security-runtime/src/lattice/transport.mjs +232 -0
  717. package/dist/security-runtime/src/live/analyze.mjs +101 -0
  718. package/dist/security-runtime/src/live/exposure.mjs +79 -0
  719. package/dist/security-runtime/src/live/secrets.mjs +31 -0
  720. package/dist/security-runtime/src/live/session.mjs +98 -0
  721. package/dist/security-runtime/src/live/targets.mjs +81 -0
  722. package/dist/security-runtime/src/portfolio/index.mjs +116 -0
  723. package/dist/security-runtime/tools/registry/betterleaks.json +29 -0
  724. package/dist/security-runtime/tools/registry/guarddog.json +9 -0
  725. package/dist/security-runtime/tools/registry/mobsf.json +10 -0
  726. package/dist/security-runtime/tools/registry/opengrep.json +28 -0
  727. package/dist/security-runtime/tools/registry/osv-scanner.json +14 -0
  728. package/dist/security-runtime/tools/registry/semgrep-rules.json +21 -0
  729. package/dist/security-runtime/tools/registry/trivy.json +30 -0
  730. package/dist/security-runtime/tools/registry/yara-forge-exclusions.json +14 -0
  731. package/dist/security-runtime/tools/registry/yara-forge.json +22 -0
  732. package/dist/security-runtime/tools/registry/yara-runner.json +10 -0
  733. package/dist/security-runtime/tools/registry/yara-x.json +24 -0
  734. package/dist/security-runtime/tools/registry/zizmor.json +30 -0
  735. package/dist/spinner.d.ts +16 -0
  736. package/dist/spinner.d.ts.map +1 -0
  737. package/dist/spinner.js +72 -0
  738. package/dist/spinner.js.map +1 -0
  739. package/dist/verify.test.d.ts +2 -0
  740. package/dist/verify.test.d.ts.map +1 -0
  741. package/dist/verify.test.js +98 -0
  742. package/dist/verify.test.js.map +1 -0
  743. package/package.json +73 -4
@@ -0,0 +1,186 @@
1
+ // malware (REQ-018): antivirus plus YARA over the working tree, dependency folders, build output and configured artifacts.
2
+ // Engines: Microsoft Defender on Windows (MpCmdRun custom scan), ClamAV on Linux when installed, and YARA-X with the pinned YARA Forge
3
+ // core package everywhere (in a Linux container on Windows; see yara.mjs). An antivirus detection is a Critical, blocking finding.
4
+ // YARA severity (REQ-037): YARA Forge rules carry a `score` meta (0 to 100). A YARA-only detection scored below 70 is a heuristic: Medium
5
+ // and non-blocking. It stays Critical and blocking when the score is 70 or more (or missing), when an antivirus engine also flags the
6
+ // file, or when two distinct YARA rules match the same file.
7
+ // The check needs at least one engine to finish; a missing or failed engine is named in the reason and in evidence.
8
+ import path from 'node:path';
9
+ import { CLAMAV_TIMEOUT_MS, clamscanArgs, parseClamav } from './clamav.mjs';
10
+ import { DEFENDER_TIMEOUT_MS, defenderArgs, findDefender, parseDefender } from './defender.mjs';
11
+ import { avTargets, resolveArtifacts, selectForYara, walkTree, YARA_FILTER } from './targets.mjs';
12
+ import { runYara } from './yara.mjs';
13
+
14
+ export const id = 'malware';
15
+ export const title = 'Malware (antivirus and YARA)';
16
+ export const modes = ['deep', 'release'];
17
+ export const requiredIn = ['deep', 'release'];
18
+ // Run after project scripts so newly-created build output is included in the tree walk.
19
+ export const phase = 2;
20
+ export const timeoutSec = 2700;
21
+ export const applies = () => true;
22
+
23
+ const FIX = 'Do not ship this file. Remove it, or rebuild it from a trusted source, and find how it got into the tree or build. If the machine that built it may be compromised, rotate its secrets. If you have verified a false positive, Richard records it in vsg.accept.json.';
24
+ const HEURISTIC_FIX = 'This is a low-score YARA heuristic that no antivirus engine confirmed, so it does not block. Check the file: if it is ordinary code that matches a generic pattern, leave it; if you do not recognize it, treat it as malware. Richard can record a verified false positive in vsg.accept.json.';
25
+ const DOCKER_ENV = ['DOCKER_HOST', 'DOCKER_CONTEXT', 'DOCKER_CONFIG'];
26
+
27
+ const toRel = (repo, file) => {
28
+ const rel = path.relative(repo, file);
29
+ return rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel.split(path.sep).join('/') : file.split(path.sep).join('/');
30
+ };
31
+
32
+ export const YARA_BLOCKING_SCORE = 70;
33
+ /** The YARA Forge `score` meta as a number, or null when absent or not numeric. */
34
+ export const yaraScore = (meta) => { const n = Number(meta?.score); return meta?.score !== undefined && meta?.score !== '' && Number.isFinite(n) ? n : null; };
35
+
36
+ /** Supplied YARA author identification as single-line text, or null when absent. Author identities are never truncated. */
37
+ const yaraAuthor = (meta) => typeof meta?.author === 'string' && meta.author.trim()
38
+ ? meta.author.replace(/\s+/g, ' ').trim()
39
+ : null;
40
+
41
+ /**
42
+ * Severity for one YARA match. `others` is the number of distinct YARA rules matching the same file, `avAgrees` whether an antivirus
43
+ * engine flagged it. Returns { severity, heuristic, why }.
44
+ */
45
+ export function yaraSeverity(score, { others, avAgrees }) {
46
+ if (avAgrees) return { severity: 'critical', heuristic: false, why: 'an antivirus engine also detected this file' };
47
+ if (others >= 2) return { severity: 'critical', heuristic: false, why: `${others} distinct YARA rules matched this file` };
48
+ if (score === null) return { severity: 'critical', heuristic: false, why: 'the rule has no score, so it is treated as a signature' };
49
+ if (score >= YARA_BLOCKING_SCORE) return { severity: 'critical', heuristic: false, why: `rule score ${score} is ${YARA_BLOCKING_SCORE} or more` };
50
+ return { severity: 'medium', heuristic: true, why: `rule score ${score} is below ${YARA_BLOCKING_SCORE} and nothing else corroborates it` };
51
+ }
52
+
53
+ function rawFinding({ engine, signature, file, description, confidence, severity = 'critical', heuristic = false }) {
54
+ return {
55
+ category: 'malware', severity, confidence, tool: engine, ruleId: signature, key: `${engine}:${signature}`,
56
+ title: heuristic ? `Malware heuristic from ${engine}: ${signature}` : `Malware detected by ${engine}: ${signature}`,
57
+ description: description ?? `${engine} reported the signature or rule "${signature}" for this file.`,
58
+ location: { file }, evidence: `engine: ${engine}\nsignature: ${signature}\nfile: ${file}${heuristic ? '\nclassification: heuristic (does not block)' : ''}`, fix: heuristic ? HEURISTIC_FIX : FIX,
59
+ };
60
+ }
61
+
62
+ /** Microsoft Defender over each target. Returns { status: 'ran'|'failed', detections, durationMs, reason, targets }. */
63
+ async function runDefender(ctx, mp, targets) {
64
+ const started = Date.now();
65
+ const detections = [];
66
+ for (const target of targets) {
67
+ const left = DEFENDER_TIMEOUT_MS - (Date.now() - started);
68
+ if (left <= 0) return { status: 'failed', reason: 'Defender time budget (30 min) was used up', detections, targets: targets.length, durationMs: Date.now() - started };
69
+ const r = await ctx.runProcess({ command: mp, args: defenderArgs(target), timeoutMs: left });
70
+ if (r.status !== 'ran') return { status: 'failed', reason: `Defender ${r.reason}`, detections, targets: targets.length, durationMs: Date.now() - started };
71
+ const p = parseDefender(`${r.stdout}\n${r.stderr}`, r.exitCode, target);
72
+ if (p.status === 'error') return { status: 'failed', reason: `${p.reason} (${toRel(ctx.repo, target)})`, detections, targets: targets.length, durationMs: Date.now() - started };
73
+ for (const t of p.threats) for (const f of t.files.length ? t.files : [target]) detections.push({ signature: t.name, file: f });
74
+ }
75
+ return { status: 'ran', detections, targets: targets.length, durationMs: Date.now() - started };
76
+ }
77
+
78
+ async function runClamav(ctx, targets) {
79
+ const started = Date.now();
80
+ const r = await ctx.runProcess({ command: 'clamscan', args: clamscanArgs(targets), cwd: ctx.repo, timeoutMs: CLAMAV_TIMEOUT_MS });
81
+ if (r.status !== 'ran') return { status: 'failed', reason: `ClamAV ${r.reason}`, detections: [], targets: targets.length, durationMs: Date.now() - started };
82
+ const p = parseClamav(r.stdout, r.stderr, r.exitCode);
83
+ if (p.status === 'error') return { status: 'failed', reason: p.reason, detections: [], targets: targets.length, durationMs: Date.now() - started };
84
+ return { status: 'ran', detections: p.detections.map((d) => ({ signature: d.signature, file: d.file })), targets: targets.length, durationMs: Date.now() - started };
85
+ }
86
+
87
+ /** deps lets tests replace the engines: { platform, findDefender, runYara }. */
88
+ export async function run(ctx, deps = {}) {
89
+ const platform = deps.platform ?? process.platform;
90
+ const yara = deps.runYara ?? runYara;
91
+ const repo = ctx.repo;
92
+ const t0 = Date.now();
93
+
94
+ const outRel = ctx.out ? toRel(repo, ctx.out) : '.vsg';
95
+ const nested = (ctx.nestedRepos ?? []).map((n) => n.path); // other checkouts inside the tree: not part of this repository
96
+ const skip = ['.vsg', ...(outRel.startsWith('..') || path.isAbsolute(outRel) ? [] : [outRel])];
97
+ const artifacts = resolveArtifacts(repo, ctx.config?.artifacts ?? []);
98
+ const files = walkTree(repo, { skip: [...skip, ...nested] });
99
+ const sel = selectForYara(repo, files, artifacts.inside);
100
+ const walkMs = Date.now() - t0;
101
+
102
+ const engines = {};
103
+ const missing = [];
104
+ let avFailed = false; // an antivirus engine that started but did not finish: nothing can be said about a clean result
105
+ const findings = [];
106
+ const add = (f) => findings.push(rawFinding(f));
107
+ const avTargetList = [...avTargets(repo, { skip, nested }), ...artifacts.outside];
108
+
109
+ // antivirus
110
+ if (platform === 'win32') {
111
+ const mp = deps.findDefender ? deps.findDefender() : findDefender();
112
+ if (!mp) missing.push('Microsoft Defender (MpCmdRun.exe was not found)');
113
+ else {
114
+ const d = await runDefender(ctx, mp, avTargetList);
115
+ engines.defender = { status: d.status, targets: d.targets, detections: d.detections.length, durationMs: d.durationMs, ...(d.reason ? { reason: d.reason } : {}) };
116
+ if (d.status === 'failed') { avFailed = true; missing.push(`Microsoft Defender did not finish: ${d.reason}`); }
117
+ for (const x of d.detections) add({ engine: 'Microsoft Defender', signature: x.signature, file: toRel(repo, x.file), confidence: 'high' });
118
+ }
119
+ } else if (platform === 'linux') {
120
+ const probe = await ctx.runProcess({ command: 'clamscan', args: ['--version'], timeoutMs: 15_000 });
121
+ if (probe.status !== 'ran' || probe.exitCode !== 0) missing.push('ClamAV (clamscan is not installed; install it and run freshclam)');
122
+ else {
123
+ const c = await runClamav(ctx, avTargetList.map((t) => toRel(repo, t)));
124
+ engines.clamav = { status: c.status, targets: c.targets, detections: c.detections.length, durationMs: c.durationMs, version: probe.stdout.trim().split('\n')[0], ...(c.reason ? { reason: c.reason } : {}) };
125
+ if (c.status === 'failed') { avFailed = true; missing.push(`ClamAV did not finish: ${c.reason}`); }
126
+ for (const x of c.detections) add({ engine: 'ClamAV', signature: x.signature, file: toRel(repo, path.resolve(repo, x.file)), confidence: 'high' });
127
+ }
128
+ } else missing.push(`an antivirus engine (none is supported on ${platform})`);
129
+
130
+ // YARA
131
+ const y = await yara({ ...ctx, runProcess: (o) => ctx.runProcess({ envNames: DOCKER_ENV, ...o }) }, { root: repo, files: sel.list });
132
+ if (y.status === 'ran') {
133
+ engines.yara = { status: 'ran', mode: y.mode, filesScanned: y.scanned, scanErrors: y.scanErrors, matches: y.matches.length, forge: { total: y.forge.total, compiled: y.forge.compiled, excluded: y.forge.excluded.length, dropped: y.forge.dropped.length, cached: y.forge.cached }, durationMs: y.durationMs };
134
+ const avFiles = new Set(findings.map((f) => f.location.file.toLowerCase()));
135
+ const rulesByFile = new Map();
136
+ const yfile = (m) => toRel(repo, path.resolve(repo, m.file));
137
+ for (const m of y.matches) {
138
+ const k = yfile(m).toLowerCase();
139
+ if (!rulesByFile.has(k)) rulesByFile.set(k, new Set());
140
+ rulesByFile.get(k).add(`${m.namespace}:${m.rule}`);
141
+ }
142
+ let heuristics = 0;
143
+ for (const m of y.matches) {
144
+ const where = `${m.namespace ? `${m.namespace}:` : ''}${m.rule}`;
145
+ const file = yfile(m);
146
+ const score = yaraScore(m.meta);
147
+ const author = yaraAuthor(m.meta);
148
+ const sev = yaraSeverity(score, { others: rulesByFile.get(file.toLowerCase()).size, avAgrees: avFiles.has(file.toLowerCase()) });
149
+ if (sev.heuristic) heuristics++;
150
+ add({
151
+ engine: 'YARA', signature: where, file, confidence: 'medium', severity: sev.severity, heuristic: sev.heuristic,
152
+ description: `YARA rule "${m.rule}" matched${m.meta?.description ? `: ${String(m.meta.description).slice(0, 200)}` : ''}${author ? ` (author: ${author})` : ''}${m.tags?.length ? ` (tags: ${m.tags.join(', ')})` : ''}. Rule score ${score ?? 'unknown'}; ${sev.heuristic ? 'heuristic, does not block' : 'blocking'} because ${sev.why}.`,
153
+ });
154
+ }
155
+ engines.yara.heuristicOnly = heuristics;
156
+ engines.yara.severityPolicy = `YARA-only matches scored below ${YARA_BLOCKING_SCORE} are Medium and non-blocking; a score of ${YARA_BLOCKING_SCORE} or more, an antivirus detection on the same file, or two distinct YARA rules on one file stay Critical`;
157
+ } else {
158
+ engines.yara = { status: 'not_run', reason: y.reason };
159
+ missing.push(`YARA did not run: ${y.reason}`);
160
+ }
161
+
162
+ const evidence = {
163
+ engines, missingEngines: missing,
164
+ scope: {
165
+ filesWalked: files.length, avTargets: avTargetList.length, yaraFiles: sel.counts.selected, yaraArtifactFiles: sel.counts.artifacts, yaraSkippedLarge: sel.counts.skippedLarge,
166
+ artifactsConfigured: ctx.config?.artifacts ?? [], artifactsMissing: artifacts.missing, artifactsOutsideRepoAvOnly: artifacts.outside.length, walkMs, totalMs: Date.now() - t0,
167
+ },
168
+ yaraFilter: YARA_FILTER,
169
+ };
170
+ const finished = Object.values(engines).filter((e) => e.status === 'ran').length;
171
+ if (!findings.length && (finished === 0 || avFailed)) return { status: 'not_run', reason: `Scanning did not complete:${missing.join('; ')}`, findings: [], evidence };
172
+ const reason = missing.length ? `Reduced coverage, not run: ${missing.join('; ')}` : '';
173
+ const toolVersion = [ctx.registry?.get('yara-x') && `yara-x ${ctx.registry.get('yara-x').version}`, engines.clamav?.version].filter(Boolean).join(', ') || null;
174
+ return {
175
+ status: 'ran', reason, findings, evidence, toolVersion,
176
+ dataVersions: ctx.selectedRuntime?.kind === 'lattice-artifact'
177
+ ? {
178
+ selectedYara: ctx.selectedRuntime.selectedRuntime.yara.selectionDigest.slice(0, 12),
179
+ selectedYaraRules: String(ctx.selectedRuntime.selectedRuntime.yara.selectedRuleIds.length),
180
+ excludedYaraHolds: String(ctx.selectedRuntime.selectedRuntime.yara.excludedRules.length),
181
+ }
182
+ : { yaraForge: ctx.registry?.get('yara-forge')?.version ?? null },
183
+ };
184
+ }
185
+
186
+ export default { id, title, modes, requiredIn, phase, timeoutSec, applies, run };
@@ -0,0 +1,147 @@
1
+ // What the malware check scans: the working tree (.git only for hooks), dependency folders, build output and configured artifacts.
2
+ // Antivirus engines scan the whole tree. YARA scans a file-type subset (YARA_FILTER) plus every file in configured artifacts.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const DEP_DIRS = new Set(['node_modules', '.venv', 'venv', 'site-packages']);
7
+ const BUILD_DIRS = new Set(['.next', 'dist', 'build', 'out']);
8
+ const GENERATED = new Set([...DEP_DIRS, ...BUILD_DIRS]);
9
+ export const YARA_MAX_FILE_BYTES = 100 * 1024 * 1024;
10
+ const SCRIPT_BYTES_CAP = 5 * 1024 * 1024; // bundles and minified files above this are not worth rule-matching as scripts
11
+
12
+ const EXECUTABLE = 'exe dll sys scr com msi msp cpl ocx drv efi so dylib ko o obj lib a node pyd jar war apk aab ipa dex class elf bin dat'.split(' ');
13
+ const SCRIPT = 'bat cmd ps1 psm1 psd1 vbs vbe wsf wsh hta lnk pif sh bash zsh py pyw pl rb php php5 phtml jsp jspx asp aspx cgi lua'.split(' ');
14
+ const JS = ['js', 'mjs', 'cjs'];
15
+ const ARCHIVE = 'zip 7z rar gz tgz bz2 xz tar cab iso img dmg vhd vhdx rpm deb'.split(' ');
16
+ const OFFICE = 'doc docm dot dotm xls xlsm xlsb ppt pptm rtf chm'.split(' ');
17
+ const KNOWN = new Set([...EXECUTABLE, ...SCRIPT, ...JS, ...ARCHIVE, ...OFFICE]);
18
+ const SNIFF_EXT = new Set(['', 'bin', 'dat', 'tmp', 'out']);
19
+
20
+ /** Human-readable statement of the YARA file-type filter (recorded in every run's evidence). */
21
+ export const YARA_FILTER = 'YARA runs on executables and libraries, scripts, archives, Office/CHM files and extensionless or .bin/.dat/.tmp files whose first bytes are an executable, script or archive signature, plus every file in configured artifacts. JavaScript files (.js, .mjs, .cjs) are included outside dependency and build folders when 5 MB or smaller. Text, images, JSON, Markdown and source such as .ts are not scanned by YARA. Files over 100 MB are skipped.';
22
+
23
+ const posix = (p) => p.split(path.sep).join('/');
24
+ const extOf = (rel) => path.posix.extname(rel).slice(1).toLowerCase();
25
+
26
+ /** Walks a directory without following symlinks. Skips .git (but not .git/hooks) and the given relative directories. Returns [{ rel }]. */
27
+ export function walkTree(root, { skip = [], start = '' } = {}) {
28
+ const skipSet = new Set(skip);
29
+ const files = [];
30
+ const walk = (rel) => {
31
+ let entries;
32
+ try { entries = fs.readdirSync(path.join(root, rel), { withFileTypes: true }); } catch { return; }
33
+ for (const e of entries) {
34
+ if (e.isSymbolicLink()) continue;
35
+ const r = rel ? `${rel}/${e.name}` : e.name;
36
+ if (e.isDirectory()) {
37
+ if (skipSet.has(r)) continue;
38
+ if (r === '.git') { if (fs.existsSync(path.join(root, '.git', 'hooks'))) walk('.git/hooks'); continue; }
39
+ walk(r);
40
+ } else if (e.isFile()) files.push(r);
41
+ }
42
+ };
43
+ if (start) {
44
+ let st;
45
+ try { st = fs.lstatSync(path.join(root, start)); } catch { return files; }
46
+ if (st.isFile()) return [start];
47
+ if (st.isSymbolicLink()) return files;
48
+ }
49
+ walk(start);
50
+ return files;
51
+ }
52
+
53
+ /** True when the first bytes look like an executable, script or archive. */
54
+ function sniff(abs) {
55
+ let fd;
56
+ try {
57
+ fd = fs.openSync(abs, 'r');
58
+ const b = Buffer.alloc(8);
59
+ const n = fs.readSync(fd, b, 0, 8, 0);
60
+ if (n < 2) return false;
61
+ const hex = b.subarray(0, n).toString('hex');
62
+ return b[0] === 0x4d && b[1] === 0x5a // MZ
63
+ || hex.startsWith('7f454c46') // ELF
64
+ || hex.startsWith('2321') // #!
65
+ || /^(cafebabe|feedface|feedfacf|cefaedfe|cffaedfe)/.test(hex) // Mach-O, universal
66
+ || hex.startsWith('504b0304') || hex.startsWith('1f8b') || hex.startsWith('377abcaf') || hex.startsWith('526172');
67
+ } catch { return false; } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch { /* ignore */ } }
68
+ }
69
+
70
+ /**
71
+ * Chooses the files YARA scans. `files` are root-relative posix paths, `artifactFiles` the subset that are configured artifacts.
72
+ * Returns { list, counts: { total, selected, artifacts, skippedLarge } }.
73
+ */
74
+ export function selectForYara(root, files, artifactFiles = new Set()) {
75
+ const list = [];
76
+ const counts = { total: files.length, selected: 0, artifacts: 0, skippedLarge: 0 };
77
+ for (const rel of files) {
78
+ const isArtifact = artifactFiles.has(rel);
79
+ const ext = extOf(rel);
80
+ const generated = rel.split('/').some((seg) => GENERATED.has(seg));
81
+ let pick = isArtifact;
82
+ if (!pick) {
83
+ if (JS.includes(ext)) pick = !generated;
84
+ else if (KNOWN.has(ext) && !['bin', 'dat'].includes(ext)) pick = true;
85
+ else if (SNIFF_EXT.has(ext)) pick = sniff(path.join(root, rel));
86
+ }
87
+ if (!pick) continue;
88
+ let size = 0;
89
+ try { size = fs.statSync(path.join(root, rel)).size; } catch { continue; }
90
+ if (size > YARA_MAX_FILE_BYTES) { counts.skippedLarge++; continue; }
91
+ if (!isArtifact && JS.includes(ext) && size > SCRIPT_BYTES_CAP) continue;
92
+ list.push(rel);
93
+ counts.selected++;
94
+ if (isArtifact) counts.artifacts++;
95
+ }
96
+ return { list, counts };
97
+ }
98
+
99
+ /**
100
+ * Resolves vsg.config.json "artifacts" (files or directories, relative to the repository). Returns
101
+ * { inside: [root-relative files], insideEntries, outside: [absolute paths], missing: [configured value] }.
102
+ */
103
+ export function resolveArtifacts(repo, artifacts = []) {
104
+ const inside = new Set();
105
+ const insideEntries = [];
106
+ const outside = [];
107
+ const missing = [];
108
+ for (const a of artifacts) {
109
+ const abs = path.resolve(repo, a);
110
+ if (!fs.existsSync(abs)) { missing.push(a); continue; }
111
+ const rel = posix(path.relative(repo, abs));
112
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) { outside.push(abs); continue; }
113
+ insideEntries.push(rel);
114
+ for (const f of walkTree(repo, { start: rel })) inside.add(f);
115
+ }
116
+ return { inside, insideEntries, outside, missing };
117
+ }
118
+
119
+ /**
120
+ * Paths handed to antivirus engines (absolute). With a .git directory each top-level entry is scanned separately so .git is skipped
121
+ * (except .git/hooks); output and .vsg directories are skipped too. `skip` and `nested` are root-relative paths; engines scan a directory
122
+ * recursively, so a directory that holds a nested repository or worktree is replaced by its entries, level by level, until that path is left out.
123
+ */
124
+ export function avTargets(repo, { skip = [], nested = [] } = {}) {
125
+ const skipSet = new Set(['.vsg', ...skip, ...nested]);
126
+ const ancestors = new Set();
127
+ for (const p of nested) for (let i = p.indexOf('/'); i > 0; i = p.indexOf('/', i + 1)) ancestors.add(p.slice(0, i));
128
+ let entries;
129
+ try { entries = fs.readdirSync(repo, { withFileTypes: true }); } catch { return [repo]; }
130
+ if (!entries.some((e) => e.name === '.git') && !nested.length) return [repo];
131
+ const targets = [];
132
+ const expand = (rel, list) => {
133
+ for (const e of list) {
134
+ const r = rel ? `${rel}/${e.name}` : e.name;
135
+ if (e.isSymbolicLink() || skipSet.has(r) || (!rel && e.name === '.git')) continue;
136
+ if (e.isDirectory() && ancestors.has(r)) {
137
+ let inner;
138
+ try { inner = fs.readdirSync(path.join(repo, r), { withFileTypes: true }); } catch { continue; }
139
+ expand(r, inner);
140
+ } else targets.push(path.join(repo, r));
141
+ }
142
+ };
143
+ expand('', entries);
144
+ const hooks = path.join(repo, '.git', 'hooks');
145
+ if (fs.existsSync(hooks)) targets.push(hooks);
146
+ return targets;
147
+ }
@@ -0,0 +1,201 @@
1
+ // YARA runner for the malware check. Runs on Linux only: natively on a Linux host, or inside the pinned Linux container on Windows
2
+ // (so no compiled rules and no extracted rule source ever touch the Windows disk, where Defender quarantines them).
3
+ //
4
+ // node yara-runner.mjs --yr <yr binary> --forge <forge zip> --root <dir to scan> [--cache <dir>] [--extra <rules file>] [--work <dir>]
5
+ //
6
+ // stdin: JSON { files: [paths relative to --root], excludeRules: [{ rule, reason }], maxFileBytes, timeoutSec }
7
+ // stdout: one JSON line { ok, error?, forge: { total, compiled, excluded, dropped, cached }, matches, scanned, scanErrors, durationMs }.
8
+ //
9
+ // The YARA Forge package contains the EICAR test string in one rule. Any rule block that contains it is removed before the
10
+ // source is written anywhere, so the string never reaches a file. Everything is written to --work (a container /tmp) or --cache.
11
+ import { spawnSync } from 'node:child_process';
12
+ import crypto from 'node:crypto';
13
+ import fs from 'node:fs';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import { listZip } from '../../core/archive.mjs';
17
+
18
+ // the marker is assembled so this file does not contain the full test string, only its start
19
+ const EICAR_TEXT = new RegExp(`${'X5O!P'}%@AP`);
20
+ const EICAR_HEX = /58\s*35\s*4[fF]\s*21\s*50\s*25\s*40\s*41\s*50/;
21
+ const RULE_START = /^(?=(?:(?:private|global)\s+)*rule\s+\w+)/m;
22
+ const RULE_NAME = /^(?:(?:private|global)\s+)*rule\s+(\w+)/m;
23
+ const sha = (b) => crypto.createHash('sha256').update(b).digest('hex');
24
+ const CANDIDATE_DECISIONS = new Set(['candidate-attribution', 'candidate-attribution-sharealike', 'candidate-explicit-apache']);
25
+ const canonical = (value) => {
26
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
27
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
28
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(',')}}`;
29
+ };
30
+
31
+ function arg(name) {
32
+ const i = process.argv.indexOf(`--${name}`);
33
+ return i > 0 ? process.argv[i + 1] : undefined;
34
+ }
35
+ const readStdin = () => { try { return fs.readFileSync(0, 'utf8'); } catch { return ''; } };
36
+ const run = (cmd, args, o = {}) => spawnSync(cmd, args, { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, ...o });
37
+
38
+ /** Splits rule source into blocks and removes excluded ones. Returns { text, total, excluded: [{ rule, reason }] }. */
39
+ export function filterRules(source, excludeNames = new Map()) {
40
+ const blocks = source.split(RULE_START);
41
+ const excluded = [];
42
+ let total = 0;
43
+ const kept = blocks.filter((b) => {
44
+ const m = RULE_NAME.exec(b);
45
+ if (!m) return true; // header comments and imports
46
+ total++;
47
+ if (EICAR_TEXT.test(b) || EICAR_HEX.test(b)) { excluded.push({ rule: m[1], reason: 'contains the EICAR test string; excluded by vsg safety policy' }); return false; }
48
+ if (excludeNames.has(m[1])) { excluded.push({ rule: m[1], reason: excludeNames.get(m[1]) }); return false; }
49
+ return true;
50
+ });
51
+ return { text: kept.join(''), total, excluded };
52
+ }
53
+
54
+ /** Rules that `yr compile --ignore-invalid-rules` reports as ignored: " - Name: error: ...". */
55
+ export function ignoredRules(stderr) {
56
+ const out = [];
57
+ const at = stderr.indexOf('the following rules were ignored');
58
+ if (at < 0) return out;
59
+ for (const line of stderr.slice(at).split('\n').slice(1)) {
60
+ const m = /^\s+-\s+(\S+?):\s*(.*)$/.exec(line);
61
+ if (m) out.push({ rule: m[1], reason: m[2].trim().slice(0, 200) });
62
+ }
63
+ return out;
64
+ }
65
+
66
+ /** Maps `yr scan -o ndjson` lines to { file, rule, namespace, tags, meta }. */
67
+ export function parseNdjson(stdout) {
68
+ const matches = [];
69
+ for (const line of stdout.split('\n')) {
70
+ if (!line.trim()) continue;
71
+ let o;
72
+ try { o = JSON.parse(line); } catch { continue; }
73
+ for (const r of o.rules ?? []) {
74
+ const meta = Object.fromEntries(Array.isArray(r.meta) ? r.meta : Object.entries(r.meta ?? {}));
75
+ matches.push({ file: o.path, rule: r.identifier, namespace: r.namespace ?? '', tags: r.tags ?? [], meta });
76
+ }
77
+ }
78
+ return matches;
79
+ }
80
+
81
+ function normalizedRule(rule) {
82
+ return {
83
+ rule: rule.rule, blockSha256: rule.blockSha256, author: rule.author, sourceUrl: rule.sourceUrl,
84
+ candidateLicense: rule.candidateLicense, decision: rule.decision, licenseMetadata: rule.licenseMetadata,
85
+ };
86
+ }
87
+
88
+ /** Verifies every source block and derives the exact frozen selected/held identities inside Linux. */
89
+ export function verifySelectedInventory({ forgeZip, inventoryPath, expectedSelectionDigest, expectedHoldDigest }) {
90
+ const zip = fs.readFileSync(forgeZip);
91
+ const inventory = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'));
92
+ if (inventory.archiveVersion !== '20260927' || inventory.archiveSha256 !== sha(zip)
93
+ || inventory.totalRules !== 5110 || !Array.isArray(inventory.rules) || inventory.rules.length !== 5110) {
94
+ throw new Error('Selected YARA inventory identity does not match the mounted source archive.');
95
+ }
96
+ const blocks = new Map();
97
+ for (const entry of listZip(zip).filter((item) => !item.dir && /\.ya?ra?$/i.test(item.name))) {
98
+ for (const block of entry.data().toString('utf8').split(RULE_START)) {
99
+ const match = RULE_NAME.exec(block);
100
+ if (!match) continue;
101
+ if (blocks.has(match[1])) throw new Error(`Selected YARA source contains duplicate rule ${match[1]}.`);
102
+ blocks.set(match[1], block.replace(/\r\n/g, '\n'));
103
+ }
104
+ }
105
+ if (blocks.size !== inventory.rules.length) throw new Error('Selected YARA source rule count does not match the inventory.');
106
+ for (const rule of inventory.rules) {
107
+ const block = blocks.get(rule.rule);
108
+ if (!block || sha(Buffer.from(block, 'utf8')) !== rule.blockSha256) throw new Error(`Selected YARA source block changed for ${rule.rule}.`);
109
+ }
110
+ const selected = inventory.rules.filter((rule) => CANDIDATE_DECISIONS.has(rule.decision)).map(normalizedRule).sort((a, b) => a.rule.localeCompare(b.rule));
111
+ const held = inventory.rules.filter((rule) => !CANDIDATE_DECISIONS.has(rule.decision)).map(normalizedRule).sort((a, b) => a.rule.localeCompare(b.rule));
112
+ const selectionDigest = sha(Buffer.from(canonical(selected)));
113
+ const holdDigest = sha(Buffer.from(canonical(held)));
114
+ if (selected.length !== 4536 || held.length !== 574 || selectionDigest !== expectedSelectionDigest || holdDigest !== expectedHoldDigest) {
115
+ throw new Error('Selected YARA selection or hold identity changed.');
116
+ }
117
+ return {
118
+ selected: selected.map((rule) => rule.rule),
119
+ excluded: held.map((rule) => ({ rule: rule.rule, reason: `frozen selection hold: ${rule.decision}` })),
120
+ selectionDigest,
121
+ holdDigest,
122
+ };
123
+ }
124
+
125
+ function prepareForge({ yr, forgeZip, cacheDir, workDir, excludeNames, extra, strict = false }) {
126
+ const zip = fs.readFileSync(forgeZip);
127
+ const key = sha(Buffer.concat([zip, Buffer.from(JSON.stringify([...excludeNames])), Buffer.from(sha(fs.readFileSync(yr)))])).slice(0, 16);
128
+ const cachedRules = cacheDir && !extra ? path.join(cacheDir, `forge-${key}.yarc`) : null;
129
+ const cachedReport = cachedRules ? `${cachedRules}.json` : null;
130
+ if (cachedRules && fs.existsSync(cachedRules) && fs.existsSync(cachedReport)) {
131
+ return { rules: cachedRules, report: { ...JSON.parse(fs.readFileSync(cachedReport, 'utf8')), cached: true } };
132
+ }
133
+ const entries = listZip(zip).filter((e) => !e.dir && /\.ya?ra?$/i.test(e.name));
134
+ if (!entries.length) throw new Error('the YARA Forge package contains no .yar rule file');
135
+ const sources = [];
136
+ let total = 0;
137
+ const excluded = [];
138
+ entries.forEach((e, i) => {
139
+ const f = filterRules(e.data().toString('utf8'), excludeNames);
140
+ total += f.total;
141
+ excluded.push(...f.excluded);
142
+ const file = path.join(workDir, `forge-${i}.yar`);
143
+ fs.writeFileSync(file, f.text);
144
+ sources.push(`forge${i}:${file}`);
145
+ });
146
+ if (extra) sources.push(`custom:${extra}`);
147
+ const out = cachedRules ?? path.join(workDir, 'rules.yarc');
148
+ let c = run(yr, ['compile', '-w', '-o', out, ...sources]);
149
+ let dropped = [];
150
+ if (c.status !== 0) {
151
+ if (strict) throw new Error(`YARA-X could not strictly compile the selected rules: ${String(c.stderr).split('\n').find((l) => l.startsWith('error')) ?? `exit ${c.status}`}`);
152
+ c = run(yr, ['compile', '-w', '--ignore-invalid-rules', '-o', out, ...sources]);
153
+ if (c.status !== 0) throw new Error(`YARA-X could not compile the rules: ${String(c.stderr).split('\n').find((l) => l.startsWith('error')) ?? `exit ${c.status}`}`);
154
+ dropped = ignoredRules(c.stderr);
155
+ }
156
+ const report = { total, compiled: total - excluded.length - dropped.length, excluded, dropped, cached: false };
157
+ if (cachedReport) fs.writeFileSync(cachedReport, JSON.stringify(report));
158
+ return { rules: out, report };
159
+ }
160
+
161
+ function main() {
162
+ const started = Date.now();
163
+ try {
164
+ const yrIn = arg('yr');
165
+ const root = arg('root');
166
+ const input = JSON.parse(readStdin() || '{}');
167
+ const work = fs.mkdtempSync(path.join(arg('work') ?? os.tmpdir(), 'vsg-yara-'));
168
+ // a copy that is executable even when the source is a read-only bind mount from a Windows host
169
+ const yr = path.join(work, 'yr');
170
+ fs.copyFileSync(yrIn, yr);
171
+ fs.chmodSync(yr, 0o755);
172
+ const cache = arg('cache');
173
+ if (cache) fs.mkdirSync(cache, { recursive: true });
174
+ let exclusions = input.excludeRules ?? [];
175
+ let selection = null;
176
+ if (arg('inventory')) {
177
+ selection = verifySelectedInventory({
178
+ forgeZip: arg('forge'), inventoryPath: arg('inventory'),
179
+ expectedSelectionDigest: input.selectionDigest, expectedHoldDigest: input.holdDigest,
180
+ });
181
+ if (canonical(exclusions) !== canonical(selection.excluded)) throw new Error('Selected YARA exclusions differ from the frozen hold inventory.');
182
+ }
183
+ const excludeNames = new Map(exclusions.map((e) => [e.rule, e.reason]));
184
+ const { rules, report } = prepareForge({ yr, forgeZip: arg('forge'), cacheDir: cache, workDir: work, excludeNames, extra: arg('extra'), strict: Boolean(arg('inventory')) });
185
+ if (selection && (report.total !== 5110 || report.compiled !== 4536 || report.excluded.length !== 574 || report.dropped.length !== 0)) {
186
+ throw new Error('Selected YARA runtime counts differ from the frozen selection.');
187
+ }
188
+ const list = path.join(work, 'files.txt');
189
+ fs.writeFileSync(list, `${(input.files ?? []).join('\n')}\n`);
190
+ const scan = run(yr, ['scan', '--scan-list', '-C', '-o', 'ndjson', '-m', '-g',...(input.maxFileBytes ? ['-z', String(input.maxFileBytes)] : []), rules, list], { cwd: root, timeout: (input.timeoutSec ?? 1200) * 1000 });
191
+ if (scan.error || scan.status !== 0) throw new Error(`yr scan failed: ${scan.error?.message ?? `exit ${scan.status}: ${String(scan.stderr).slice(0, 300)}`}`);
192
+ const scanErrors = String(scan.stderr).split('\n').filter((l) => l.startsWith('error')).length;
193
+ fs.rmSync(work, { recursive: true, force: true });
194
+ process.stdout.write(`${JSON.stringify({ ok: true, forge: { ...report, ...(selection ? { selectionDigest: selection.selectionDigest, holdDigest: selection.holdDigest } : {}) }, matches: parseNdjson(scan.stdout), scanned: (input.files ?? []).length, scanErrors, durationMs: Date.now() - started })}\n`);
195
+ } catch (e) {
196
+ process.stdout.write(`${JSON.stringify({ ok: false, error: e.message, durationMs: Date.now() - started })}\n`);
197
+ }
198
+ }
199
+
200
+ import { fileURLToPath } from 'node:url';
201
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) main();