@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,196 @@
1
+ // The shared run path: detect, select checks, run them, normalize, dedupe, accept, gate, write outputs atomically.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { AsyncLocalStorage } from 'node:async_hooks';
6
+ import { applyAcceptances, loadAcceptances, loadConfig, reviewItems } from './accept.mjs';
7
+ import { resolveBase } from './base-ref.mjs';
8
+ import { UsageError } from './errors.mjs';
9
+ import { cachedGet, vfetch } from './fetch.mjs';
10
+ import { dedupe, makeFinding, scrubText } from './findings.mjs';
11
+ import { applyPolicy, countsOf, requiredFor, verdictFor } from './gate.mjs';
12
+ import { resolveTool } from './installer.mjs';
13
+ import { acquireLock } from './lock.mjs';
14
+ import { packageRoot } from './paths.mjs';
15
+ import { detect } from './detect.mjs';
16
+ import { nestedSummary } from './nested.mjs';
17
+ import { writeOutputs } from './report.mjs';
18
+ import { runProcess } from './runner.mjs';
19
+ import { reviewIdentity } from '../agent/ingest.mjs';
20
+ import { execFileSync } from 'node:child_process';
21
+ import { captureRuntimeProvenance } from '../lattice/provenance.mjs';
22
+ import { assertStandaloneExecution } from '../lattice/execution.mjs';
23
+ import { loadSelectedRuntime } from '../lattice/selected-runtime.mjs';
24
+
25
+ // The running check's AbortController. Processes a check starts through ctx.runProcess / ctx.runTool are killed (whole tree) when it
26
+ // aborts: on timeout, when the check ends, and when the pipeline finishes.
27
+ const currentCheck = new AsyncLocalStorage();
28
+ const MAX_CONCURRENT = 3;
29
+ const DEFAULT_CHECK_TIMEOUT_SEC = 1800;
30
+ const withTimeout = (promise, ms, label) => new Promise((resolve, reject) => {
31
+ const t = setTimeout(() => reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`)), ms);
32
+ promise.then((v) => { clearTimeout(t); resolve(v); }, (e) => { clearTimeout(t); reject(e); });
33
+ });
34
+
35
+ async function pool(items, limit, fn) {
36
+ const queue = [...items];
37
+ await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, async () => {
38
+ while (queue.length) await fn(queue.shift());
39
+ }));
40
+ }
41
+
42
+ export function vsgVersion() {
43
+ try { return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version; } catch { return '0.0.0'; }
44
+ }
45
+
46
+ /** Captures the same content identity used by agent review, plus bounded Git state for publication evidence. */
47
+ export function captureReviewContext(repo, runId, out) {
48
+ const identity = reviewIdentity(repo, runId, out);
49
+ let commitSha = null;
50
+ let dirty = true;
51
+ try {
52
+ const options = { encoding: 'utf8', timeout: 10_000, stdio: ['ignore', 'pipe', 'pipe'] };
53
+ const inside = execFileSync('git', ['-C', repo, 'rev-parse', '--is-inside-work-tree'], options).trim();
54
+ if (inside === 'true') {
55
+ const commit = execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], options).trim();
56
+ commitSha = /^[0-9a-f]{40}$/.test(commit) ? commit : null;
57
+ dirty = execFileSync('git', ['-C', repo, 'status', '--porcelain=v1', '--untracked-files=all'], { ...options, maxBuffer: 16 * 1024 * 1024 }).trim().length > 0;
58
+ }
59
+ } catch { /* Non-Git inputs retain a null commit and dirty=true. */ }
60
+ return { ...identity, commitSha, dirty };
61
+ }
62
+
63
+ /**
64
+ * Runs one mode over a repository and writes the outputs. Returns { verdict, counts, outputs, ... }.
65
+ * opts: { mode, repo, out, checks, registry, flags: { base, compare }, now, progress }
66
+ */
67
+ export async function runPipeline({ mode, repo, out, checks, registry = new Map(), flags = {}, now = () => new Date(), progress = () => {} }) {
68
+ assertStandaloneExecution({ allowQualification: true });
69
+ const selectedRuntime = loadSelectedRuntime({ runtimeRoot: packageRoot });
70
+ repo = path.resolve(repo);
71
+ if (!fs.existsSync(repo) || !fs.statSync(repo).isDirectory()) throw new UsageError(`"${repo}" is not a directory. Pass the repository path.`);
72
+ out = path.resolve(out ?? path.join(repo, '.vsg'));
73
+ let previous = null;
74
+ if (flags.compare) {
75
+ try { previous = JSON.parse(fs.readFileSync(flags.compare, 'utf8')); } catch (e) { throw new UsageError(`Cannot read --compare file "${flags.compare}": ${e.message}.`); }
76
+ if (!Array.isArray(previous?.findings)) throw new UsageError(`--compare file "${flags.compare}" is not a vsg findings.json.`);
77
+ }
78
+ const lock = acquireLock(out);
79
+ const controllers = new Set(); // one per running check
80
+ try {
81
+ const startedAt = now().toISOString();
82
+ const runId = crypto.randomUUID();
83
+ const sourceStart = captureReviewContext(repo, runId, out);
84
+ const runtimeStart = captureRuntimeProvenance({ runtimeRoot: packageRoot });
85
+ const log = [];
86
+ const note = (line) => log.push(`${new Date().toISOString()} ${line}`);
87
+ note(`vsg ${vsgVersion()} ${mode} ${repo}`);
88
+
89
+ const config = loadConfig(repo);
90
+ const acceptances = loadAcceptances(repo, { now: now().getTime() });
91
+ const detection = detect(repo);
92
+ note(`detected: ${detection.stacks.join(', ') || 'nothing specific'}; lockfiles: ${detection.lockfiles.length}`);
93
+ if (detection.nestedRepos.length) note(nestedSummary(detection.nestedRepos));
94
+ const base = resolveBase(repo, { flag: flags.base, config });
95
+ const baseRef = base.sha;
96
+ const review = flags.noReview ? [] : reviewItems(repo, baseRef, base.why); // a live run with no repository has nothing to compare
97
+
98
+ const { required, removed } = requiredFor(mode, detection, { config });
99
+ const selected = checks.filter((c) => c.modes.includes(mode));
100
+ const requiredSet = new Set([...required, ...selected.filter((c) => c.requiredIn.includes(mode) && !removed.some((r) => r.id === c.id)).map((c) => c.id)]);
101
+ const raw = {};
102
+ const coverage = [];
103
+ const rawFindings = [];
104
+
105
+ const ctx = {
106
+ repo, out, mode, detection, nestedRepos: detection.nestedRepos, config, registry, selectedRuntime, baseRef, flags, live: flags.live ?? null, runId, log: note, fetch: vfetch, cachedGet, makeFinding, vsgVersion: vsgVersion(),
107
+ /** This run's findings so far (normalized, deduped, policy applied). Only meaningful to checks that set `last`, which run after the others. */
108
+ priorFindings() {
109
+ const snapshot = structuredClone(dedupe(rawFindings));
110
+ applyAcceptances(snapshot, acceptances);
111
+ return applyPolicy(snapshot);
112
+ },
113
+ runProcess: (o) => runProcess({ log: note, signal: currentCheck.getStore()?.signal, ...o }),
114
+ /** Runs a pinned, installed registry tool. Returns the runner result, or not_run with a setup hint. */
115
+ async runTool(name, args, o = {}) {
116
+ const tool = registry.get(name);
117
+ if (!tool) return { status: 'not_run', reason: `Tool "${name}" is not in the registry.` };
118
+ const resolved = resolveTool(tool);
119
+ if (!resolved) return { status: 'not_run', reason: `${tool.name} is not installed. Run vsg setup.` };
120
+ return runProcess({ command: resolved.path, args, cwd: o.cwd ?? repo, timeoutMs: (o.timeoutSec ?? tool.timeoutSec ?? 600) * 1000, envNames: [...(tool.env ?? []), ...(o.envNames ?? [])], extraEnv: o.extraEnv, input: o.input, log: note, signal: currentCheck.getStore()?.signal });
121
+ },
122
+ };
123
+
124
+ const runCheck = async (check) => {
125
+ const started = Date.now();
126
+ const controller = new AbortController();
127
+ controllers.add(controller);
128
+ const entry = { id: check.id, status: 'not_run', reason: '', required: requiredSet.has(check.id), toolVersion: null, dataVersions: {}, durationMs: 0 };
129
+ try {
130
+ const verdict = await check.applies(ctx);
131
+ const applies = typeof verdict === 'object' && verdict !== null ? verdict.applies : verdict;
132
+ if (!applies) {
133
+ entry.status = 'not_applicable';
134
+ entry.reason = (typeof verdict === 'object' && verdict?.reason) || 'nothing to check in this repository';
135
+ } else {
136
+ // Optional checks become required when their input exists. This keeps a clean repository not_applicable while failing closed
137
+ // if an artifact was produced but its scanner cannot complete.
138
+ entry.required = entry.required || check.requiredIfApplied === true;
139
+ progress(`Running ${check.id}`);
140
+ const res = await withTimeout(Promise.resolve(currentCheck.run(controller, () => check.run(ctx))), (check.timeoutSec ?? DEFAULT_CHECK_TIMEOUT_SEC) * 1000, check.id);
141
+ if (!['ran', 'not_run', 'not_applicable'].includes(res?.status)) throw new Error('returned an invalid status');
142
+ entry.status = res.status;
143
+ entry.reason = res.reason ?? '';
144
+ entry.toolVersion = res.toolVersion ?? null;
145
+ entry.dataVersions = res.dataVersions ?? {};
146
+ if (res.status === 'ran') for (const f of res.findings ?? []) rawFindings.push(makeFinding(f, check.id));
147
+ if (res.evidence && typeof res.evidence === 'object') raw[check.id] = res.evidence;
148
+ }
149
+ } catch (e) {
150
+ entry.status = 'not_run';
151
+ entry.reason = scrubText(e.message);
152
+ // a check that failed halfway contributes no findings
153
+ for (let i = rawFindings.length - 1; i >= 0; i--) if (rawFindings[i].check === check.id) rawFindings.splice(i, 1);
154
+ }
155
+ controller.abort(); // a timed-out check's scanner processes must not outlive it
156
+ controllers.delete(controller);
157
+ entry.durationMs = Date.now() - started;
158
+ note(`check ${check.id}: ${entry.status}${entry.reason ? ` (${entry.reason})` : ''} in ${entry.durationMs}ms`);
159
+ coverage.push(entry);
160
+ };
161
+ // Explicit numeric phases provide the one ordering barrier needed by post-build artifact checks. There is no general dependency graph:
162
+ // checks in a phase remain concurrent, and checks without a phase keep the original phase-0 behavior.
163
+ const phaseOf = (c) => c.last ? Number.MAX_SAFE_INTEGER : (c.phase ?? 0);
164
+ const phases = [...new Set(selected.map(phaseOf))].sort((a, b) => a - b);
165
+ for (const phase of phases) await pool(selected.filter((c) => phaseOf(c) === phase), MAX_CONCURRENT, runCheck);
166
+
167
+ // a required check with no implementation is not_run: the gate fails closed
168
+ for (const id of requiredSet) {
169
+ if (!coverage.some((c) => c.id === id)) {
170
+ coverage.push({ id, status: 'not_run', reason: `Check "${id}" is not implemented in this build.`, required: true, toolVersion: null, dataVersions: {}, durationMs: 0 });
171
+ }
172
+ }
173
+ coverage.sort((a, b) => a.id.localeCompare(b.id));
174
+
175
+ const findings = dedupe(rawFindings);
176
+ applyAcceptances(findings, acceptances);
177
+ applyPolicy(findings);
178
+ if (previous) {
179
+ const known = new Set(previous.findings.map((f) => f.fingerprint));
180
+ for (const f of findings) f.isNew = !known.has(f.fingerprint);
181
+ }
182
+ const counts = countsOf(findings);
183
+ const verdict = verdictFor(findings, coverage);
184
+ const sourceEnd = captureReviewContext(repo, runId, out);
185
+ const runtimeEnd = captureRuntimeProvenance({ runtimeRoot: packageRoot });
186
+ const finishedAt = now().toISOString();
187
+ note(`verdict ${verdict}`);
188
+
189
+ const run = { mode, target: flags.target ?? repo, authorization: flags.authorization ?? null, vsgVersion: vsgVersion(), startedAt, finishedAt, verdict, counts, coverage, findings, detection, acceptances, review, removedChecks: removed, raw, log, sourceSnapshot: { schemaVersion: 1, start: sourceStart, end: sourceEnd }, runtimeSnapshot: { schemaVersion: 1, start: runtimeStart, end: runtimeEnd } };
190
+ const outputs = writeOutputs(out, run);
191
+ return { verdict, counts, coverage, findings, outputs, review };
192
+ } finally {
193
+ for (const c of controllers) c.abort();
194
+ lock.release();
195
+ }
196
+ }
@@ -0,0 +1,81 @@
1
+ // Tool registry: one JSON file per pinned tool in tools/registry/. Declared as data, validated on load.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { UsageError } from './errors.mjs';
5
+ import { defaultRegistryDir } from './paths.mjs';
6
+
7
+ const HEX64 = /^[0-9a-f]{64}$/;
8
+ const IMAGE = /^[a-z0-9][a-z0-9._\/-]*(:[\w.-]+)?@sha256:[0-9a-f]{64}$/;
9
+ const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
10
+
11
+ function httpsUrl(value) {
12
+ try {
13
+ const u = new URL(value);
14
+ return u.protocol === 'https:' || (u.protocol === 'http:' && LOCAL_HOSTS.has(u.hostname));
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function safeRelative(p) {
21
+ if (typeof p !== 'string' || !p || path.isAbsolute(p) || /^[A-Za-z]:/.test(p) || p.includes('\0')) return false;
22
+ return !p.replace(/\\/g, '/').split('/').includes('..');
23
+ }
24
+
25
+ export function validateTool(t) {
26
+ const errors = [];
27
+ const need = (cond, msg) => { if (!cond) errors.push(msg); };
28
+ if (!t || typeof t !== 'object') return ['not a JSON object'];
29
+ need(typeof t.name === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(t.name), 'name must be lowercase letters, digits and dashes');
30
+ need(typeof t.version === 'string' && t.version.length > 0, 'version is required');
31
+ need(typeof t.license === 'string' && t.license.length > 0, 'license is required');
32
+ need(httpsUrl(t.homepage), 'homepage must be an https URL');
33
+ need(httpsUrl(t.releaseUrl), 'releaseUrl must be an https URL');
34
+ need(t.checksumsUrl === undefined || httpsUrl(t.checksumsUrl), 'checksumsUrl must be an https URL');
35
+ need(t.timeoutSec === undefined || (Number.isFinite(t.timeoutSec) && t.timeoutSec > 0), 'timeoutSec must be a positive number');
36
+ need(t.env === undefined || (Array.isArray(t.env) && t.env.every((n) => typeof n === 'string')), 'env must be an array of variable names');
37
+ need(!!t.platforms !== !!t.image, 'exactly one of platforms or image is required');
38
+ need(t.containerPlatform === undefined || (typeof t.containerPlatform === 'string' && !!t.platforms?.[t.containerPlatform]), 'containerPlatform must name one of the platforms');
39
+ if (t.image !== undefined) need(typeof t.image === 'string' && IMAGE.test(t.image), 'image must be <name>@sha256:<64 hex>');
40
+ if (t.platforms !== undefined) {
41
+ need(t.platforms && typeof t.platforms === 'object' && Object.keys(t.platforms).length > 0, 'platforms must not be empty');
42
+ for (const [key, p] of Object.entries(t.platforms ?? {})) {
43
+ need(/^[a-z0-9]+-[a-z0-9]+$/.test(key), `platform key "${key}" must look like win32-x64`);
44
+ need(httpsUrl(p?.url), `${key}: url must be an https URL`);
45
+ need(HEX64.test(p?.sha256 ?? ''), `${key}: sha256 must be 64 lowercase hex characters`);
46
+ need(['none', 'zip', 'tar.gz'].includes(p?.archive), `${key}: archive must be none, zip or tar.gz`);
47
+ need(safeRelative(p?.bin), `${key}: bin must be a relative path without ..`);
48
+ }
49
+ }
50
+ if (t.verify !== undefined) {
51
+ need(Array.isArray(t.verify?.args) && t.verify.args.every((a) => typeof a === 'string'), 'verify.args must be an array of strings');
52
+ need(typeof t.verify?.expect === 'string', 'verify.expect must be a string');
53
+ }
54
+ return errors;
55
+ }
56
+
57
+ /** Loads and validates every registry file. Throws UsageError listing all problems (configuration error). */
58
+ export function loadRegistry(dir = defaultRegistryDir) {
59
+ const tools = new Map();
60
+ const problems = [];
61
+ let files = [];
62
+ try {
63
+ // <tool>-exclusions.json files are data for a check (for example the YARA Forge exclusion list), not tools
64
+ files = fs.readdirSync(dir).filter((f) => f.endsWith('.json') && !f.endsWith('-exclusions.json')).sort();
65
+ } catch { /* no registry directory: empty registry */ }
66
+ for (const file of files) {
67
+ let data;
68
+ try {
69
+ data = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
70
+ } catch (e) {
71
+ problems.push(`${file}: invalid JSON (${e.message})`);
72
+ continue;
73
+ }
74
+ const errors = validateTool(data);
75
+ if (data?.name && file !== `${data.name}.json`) errors.push(`file name must be ${data.name}.json`);
76
+ if (errors.length) problems.push(`${file}: ${errors.join('; ')}`);
77
+ else tools.set(data.name, data);
78
+ }
79
+ if (problems.length) throw new UsageError(`Invalid tool registry: ${problems.join(' | ')}. Fix the registry file.`);
80
+ return tools;
81
+ }
@@ -0,0 +1,128 @@
1
+ // Output writer (REQ-006): report.md, findings.json, results.sarif, coverage.json, run.log, raw/.
2
+ // Files are staged inside the output directory and moved into place at the end, so a failed run never leaves half-written outputs.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { headline } from './gate.mjs';
6
+ import { scrubText, scrubUrl } from './findings.mjs';
7
+ import { nestedSummary } from './nested.mjs';
8
+ import { toSarif } from './sarif.mjs';
9
+
10
+ const SEV = { critical: 'Critical', high: 'High', medium: 'Medium', low: 'Low', info: 'Info' };
11
+ const OUTPUTS = ['report.md', 'findings.json', 'results.sarif', 'coverage.json', 'run.log', 'raw'];
12
+
13
+ function retainedSnapshot(outDir, key) {
14
+ try {
15
+ const findings = JSON.parse(fs.readFileSync(path.join(outDir, 'findings.json'), 'utf8'))[key];
16
+ const coverage = JSON.parse(fs.readFileSync(path.join(outDir, 'coverage.json'), 'utf8'))[key];
17
+ return findings && JSON.stringify(findings) === JSON.stringify(coverage) ? findings : null;
18
+ } catch { return null; }
19
+ }
20
+
21
+ function findingBlock(f, n, withReason) {
22
+ const where = f.location.file ? `${f.location.file}${f.location.line ? `:${f.location.line}` : ''}`
23
+ : f.location.url ?? (f.location.package ? `${f.location.package}${f.location.version ? `@${f.location.version}` : ''}` : '');
24
+ const lines = [`${n}. **${SEV[f.severity]}**: ${f.title} (${f.id})`];
25
+ if (where) lines.push(` - Where: ${where}`);
26
+ if (withReason && f.blockReason) lines.push(` - Why it blocks: ${f.blockReason}`);
27
+ if (f.description) lines.push(` - What: ${f.description}`);
28
+ if (f.exploited.kev) lines.push(' - Known to be exploited (CISA KEV).');
29
+ if (f.exploited.epss != null) lines.push(` - EPSS score: ${f.exploited.epss}`);
30
+ if (f.evidence) lines.push(` - Evidence: ${f.evidence.replace(/\s+/g, ' ').slice(0, 300)}`);
31
+ if (f.fix) lines.push(` - Fix: ${f.fix}`);
32
+ lines.push(` - Found by: ${f.sources.map((s) => (s.ruleId ? `${s.tool} (${s.ruleId})` : s.tool)).join(', ')}`);
33
+ if (f.isNew === true) lines.push(' - New since the comparison run.');
34
+ return lines.join('\n');
35
+ }
36
+
37
+ export function renderReport(run) {
38
+ const { verdict, counts, coverage, findings, mode, target, review, acceptances } = run;
39
+ const shownTarget = /^https?:\/\//i.test(String(target)) ? scrubUrl(target) : String(target);
40
+ const out = [`# ${headline(verdict, counts, coverage)}`, '', `Mode: ${mode}. Target: ${shownTarget}. Finished: ${run.finishedAt}.`, ...(run.authorization ? [`Authorized by: ${scrubText(run.authorization)}`] : []), ...(run.detection.nestedRepos?.length ? ['', nestedSummary(run.detection.nestedRepos)] : []), ''];
41
+
42
+ out.push('## Must fix', '');
43
+ const must = findings.filter((f) => f.blocking);
44
+ out.push(...(must.length ? must.map((f, i) => findingBlock(f, i + 1, true) + '\n') : ['Nothing blocks this release.', '']));
45
+
46
+ out.push('## Not tested', '');
47
+ const notRun = coverage.filter((c) => c.status === 'not_run');
48
+ const notApplicable = coverage.filter((c) => c.status === 'not_applicable');
49
+ if (!notRun.length && !notApplicable.length) out.push('Every selected check ran.', '');
50
+ notRun.forEach((c, i) => out.push(`${i + 1}. ${c.id}: did not run${c.required ? ' (required, so the verdict cannot be PASS)' : ''}. ${c.reason}`));
51
+ if (notRun.length) out.push('');
52
+ if (notApplicable.length) out.push(`Not applicable to this repository: ${notApplicable.map((c) => `${c.id}${c.reason ? ` (${c.reason})` : ''}`).join(', ')}.`, '');
53
+
54
+ // A check that ran but could not cover everything (files its tool could not parse, stale data, skipped directories) names what it missed.
55
+ const partial = coverage.filter((c) => c.status === 'ran' && /^degraded:/.test(c.reason ?? ''));
56
+ if (partial.length) {
57
+ out.push('## Not fully tested', '');
58
+ partial.forEach((c, i) => out.push(`${i + 1}. ${c.id}: ran, but ${String(c.reason).replace(/^degraded:\s*/, 'with reduced coverage: ')}`));
59
+ out.push('');
60
+ }
61
+
62
+ out.push('## Other findings', '');
63
+ const others = findings.filter((f) => !f.blocking);
64
+ if (!others.length) out.push('None.', '');
65
+ const cats = [...new Set(others.map((f) => f.category))].sort();
66
+ for (const cat of cats) {
67
+ out.push(`### ${cat}`, '');
68
+ others.filter((f) => f.category === cat).forEach((f, i) => out.push(findingBlock(f, i + 1, false) + '\n'));
69
+ }
70
+
71
+ out.push('## Acceptances and review items', '');
72
+ const accLines = [
73
+ ...findings.filter((f) => f.status === 'accepted').map((f) => `Accepted: ${f.title} (${f.id}).`),
74
+ ...findings.filter((f) => f.status === 'expired-acceptance').map((f) => `Acceptance expired, finding is open again: ${f.title} (${f.id}).`),
75
+ ...acceptances.invalid.map((a) => `Ignored invalid acceptance (${a.problem}); it suppresses nothing.`),
76
+ ...(run.removedChecks ?? []).map((r) => `Required check "${r.id}" was removed by vsg.config.json: ${r.reason}`),
77
+ ...review.map((r) => `For human review: ${r}`),
78
+ ];
79
+ out.push(...(accLines.length ? accLines.map((l, i) => `${i + 1}. ${l}`) : ['None.']), '');
80
+
81
+ out.push('## Tools and data versions', '');
82
+ out.push(`vsg ${run.vsgVersion}.`);
83
+ for (const c of coverage) {
84
+ const bits = [c.status, `${c.durationMs} ms`];
85
+ if (c.toolVersion) bits.push(`tool ${c.toolVersion}`);
86
+ if (c.dataVersions && Object.keys(c.dataVersions).length) bits.push(`data ${JSON.stringify(c.dataVersions)}`);
87
+ out.push(`- ${c.id}: ${bits.join(', ')}`);
88
+ }
89
+ if (run.detection.stacks.length) out.push(`- Detected: ${run.detection.stacks.join(', ')}`);
90
+ return `${out.join('\n')}\n`;
91
+ }
92
+
93
+ /** Writes all outputs. `raw` maps check id to { filename: text }. Returns the output paths. */
94
+ export function writeOutputs(outDir, run) {
95
+ const sourceSnapshot = run.sourceSnapshot ?? retainedSnapshot(outDir, 'sourceSnapshot');
96
+ const runtimeSnapshot = run.runtimeSnapshot ?? retainedSnapshot(outDir, 'runtimeSnapshot');
97
+ const staging = path.join(outDir, `.staging-${process.pid}`);
98
+ fs.rmSync(staging, { recursive: true, force: true });
99
+ fs.mkdirSync(path.join(staging, 'raw'), { recursive: true });
100
+ const json = (x) => `${JSON.stringify(x, null, 2)}\n`;
101
+ const findingsDoc = {
102
+ schemaVersion: 1, vsgVersion: run.vsgVersion, mode: run.mode, target: /^https?:\/\//i.test(String(run.target)) ? scrubUrl(run.target) : run.target, authorization: run.authorization ? scrubText(run.authorization) : null, startedAt: run.startedAt, finishedAt: run.finishedAt,
103
+ verdict: run.verdict, counts: run.counts, checks: run.coverage, findings: run.findings,
104
+ acceptances: { valid: run.acceptances.valid.length, invalid: run.acceptances.invalid.length }, review: run.review,
105
+ ...(sourceSnapshot ? { sourceSnapshot } : {}), ...(runtimeSnapshot ? { runtimeSnapshot } : {}),
106
+ };
107
+ fs.writeFileSync(path.join(staging, 'findings.json'), json(findingsDoc));
108
+ fs.writeFileSync(path.join(staging, 'results.sarif'), json(toSarif(run.findings, { vsgVersion: run.vsgVersion })));
109
+ fs.writeFileSync(path.join(staging, 'coverage.json'), json({ schemaVersion: 1, mode: run.mode, detection: run.detection, skippedNestedRepos: run.detection.nestedRepos?.length ? { count: run.detection.nestedRepos.length, summary: nestedSummary(run.detection.nestedRepos), repos: run.detection.nestedRepos } : null, checks: run.coverage, ...(sourceSnapshot ? { sourceSnapshot } : {}), ...(runtimeSnapshot ? { runtimeSnapshot } : {}) }));
110
+ fs.writeFileSync(path.join(staging, 'report.md'), renderReport(run));
111
+ fs.writeFileSync(path.join(staging, 'run.log'), `${run.log.map((l) => scrubText(l)).join('\n')}\n`);
112
+ for (const [checkId, files] of Object.entries(run.raw)) {
113
+ for (const [name, content] of Object.entries(files)) {
114
+ const dir = path.join(staging, 'raw', path.basename(checkId));
115
+ fs.mkdirSync(dir, { recursive: true });
116
+ fs.writeFileSync(path.join(dir, path.basename(name)), scrubText(Buffer.isBuffer(content) ? content.toString('utf8') : typeof content === 'string' ? content : JSON.stringify(content, null, 2)));
117
+ }
118
+ }
119
+ for (const name of OUTPUTS) {
120
+ fs.rmSync(path.join(outDir, name), { recursive: true, force: true });
121
+ fs.renameSync(path.join(staging, name), path.join(outDir, name));
122
+ }
123
+ fs.rmSync(staging, { recursive: true, force: true });
124
+ return {
125
+ dir: outDir, report: path.join(outDir, 'report.md'), findings: path.join(outDir, 'findings.json'), sarif: path.join(outDir, 'results.sarif'),
126
+ coverage: path.join(outDir, 'coverage.json'), log: path.join(outDir, 'run.log'), raw: path.join(outDir, 'raw'),
127
+ };
128
+ }
@@ -0,0 +1,86 @@
1
+ // Safe process execution: argument arrays only, environment allowlist, timeout with process-tree kill.
2
+ import { spawn, spawnSync } from 'node:child_process';
3
+ import { scrubText } from './findings.mjs';
4
+
5
+ // Names every scanner may inherit. Anything else (tokens, cloud keys, proxies with credentials) must be named per tool.
6
+ const BASE_ENV = new Set([
7
+ 'PATH', 'PATHEXT', 'SYSTEMROOT', 'SYSTEMDRIVE', 'WINDIR', 'COMSPEC', 'TEMP', 'TMP', 'TMPDIR', 'HOME', 'HOMEDRIVE', 'HOMEPATH',
8
+ 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)', 'PROGRAMW6432',
9
+ 'NUMBER_OF_PROCESSORS', 'PROCESSOR_ARCHITECTURE', 'OS', 'LANG', 'LC_ALL', 'TERM', 'TZ', 'NO_COLOR', 'CI',
10
+ 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
11
+ ]);
12
+
13
+ export function buildEnv(envNames = [], extra = {}) {
14
+ const allowed = new Set([...BASE_ENV, ...envNames.map((n) => n.toUpperCase())]);
15
+ const env = {};
16
+ for (const [k, v] of Object.entries(process.env)) if (allowed.has(k.toUpperCase())) env[k] = v;
17
+ return { ...env, ...extra };
18
+ }
19
+
20
+ function killTree(child) {
21
+ if (!child.pid) return;
22
+ if (process.platform === 'win32') {
23
+ spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
24
+ } else {
25
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child.kill('SIGKILL'); } catch { /* already gone */ } }
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Runs a command. Resolves { status: 'ran'|'not_run', exitCode, stdout, stderr, durationMs, reason }.
31
+ * 'ran' means the process started and exited (the caller interprets exitCode). 'not_run' means it could not start, timed out, was
32
+ * cancelled through `signal` (an AbortSignal; the whole process tree is killed), or wrote more than maxOutputBytes (the tree is
33
+ * killed then too: an incomplete output is never read as a result).
34
+ */
35
+ export function runProcess({ command, args = [], cwd, envNames = [], extraEnv = {}, timeoutMs = 600_000, maxOutputBytes = 32 * 1024 * 1024, input, log, signal }) {
36
+ return new Promise((resolve) => {
37
+ const started = Date.now();
38
+ const chunks = { out: [], err: [] };
39
+ let size = 0;
40
+ let truncated = false;
41
+ let timedOut = false;
42
+ let aborted = false;
43
+ let settled = false;
44
+ let child;
45
+ let timer; // declared before finish() can run: spawn() may throw synchronously (seen in sandboxed agent shells)
46
+ const onAbort = () => { aborted = true; if (child) killTree(child); };
47
+ const finish = (result) => {
48
+ if (settled) return;
49
+ settled = true;
50
+ clearTimeout(timer);
51
+ signal?.removeEventListener('abort', onAbort);
52
+ const durationMs = Date.now() - started;
53
+ const full = { stdout: Buffer.concat(chunks.out).toString('utf8'), stderr: Buffer.concat(chunks.err).toString('utf8'), durationMs, truncated, ...result };
54
+ log?.(`$ ${[command, ...args].map((a) => scrubText(a)).join(' ')} -> ${full.status}${full.exitCode !== undefined ? ` exit ${full.exitCode}` : ''}${full.reason ? ` (${full.reason})` : ''} in ${durationMs}ms`);
55
+ resolve(full);
56
+ };
57
+ if (signal?.aborted) { finish({ status: 'not_run', reason: 'cancelled before it started' }); return; }
58
+ try {
59
+ child = spawn(command, args, { cwd, env: buildEnv(envNames, extraEnv), shell: false, windowsHide: true, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'] });
60
+ } catch (e) {
61
+ finish({ status: 'not_run', reason: `could not start ${command}: ${e.message}` });
62
+ return;
63
+ }
64
+ signal?.addEventListener('abort', onAbort, { once: true });
65
+ timer = setTimeout(() => { timedOut = true; killTree(child); }, timeoutMs);
66
+ const collect = (key) => (d) => {
67
+ size += d.length;
68
+ if (size > maxOutputBytes) {
69
+ if (!truncated) { truncated = true; killTree(child); }
70
+ return;
71
+ }
72
+ chunks[key].push(d);
73
+ };
74
+ child.stdout.on('data', collect('out'));
75
+ child.stderr.on('data', collect('err'));
76
+ child.on('error', (e) => finish({ status: 'not_run', reason: `could not start ${command}: ${e.message}` }));
77
+ child.on('close', (code, signalName) => {
78
+ if (truncated) finish({ status: 'not_run', reason: `output exceeded ${Math.round((maxOutputBytes / 1048576) * 100) / 100} MB and the process was stopped` });
79
+ else if (aborted) finish({ status: 'not_run', reason: 'cancelled and stopped' });
80
+ else if (timedOut) finish({ status: 'not_run', reason: `timed out after ${timeoutMs / 1000}s and was stopped` });
81
+ else finish({ status: 'ran', exitCode: code ?? -1, signal: signalName });
82
+ });
83
+ child.stdin.on('error', () => {});
84
+ child.stdin.end(input);
85
+ });
86
+ }
@@ -0,0 +1,141 @@
1
+ // SARIF 2.1.0 in (normalizer) and out (results.sarif), plus the CWE to OWASP Top 10:2025 map.
2
+ import crypto from 'node:crypto';
3
+ import { scrubText, redact } from './findings.mjs';
4
+
5
+ const A = {
6
+ A01: 'A01:2025 Broken Access Control',
7
+ A02: 'A02:2025 Security Misconfiguration',
8
+ A03: 'A03:2025 Software Supply Chain Failures',
9
+ A04: 'A04:2025 Cryptographic Failures',
10
+ A05: 'A05:2025 Injection',
11
+ A06: 'A06:2025 Insecure Design',
12
+ A07: 'A07:2025 Authentication Failures',
13
+ A08: 'A08:2025 Software or Data Integrity Failures',
14
+ A09: 'A09:2025 Security Logging and Alerting Failures',
15
+ A10: 'A10:2025 Mishandling of Exceptional Conditions',
16
+ };
17
+ const GROUPS = {
18
+ A01: [22, 23, 36, 73, 200, 201, 219, 284, 285, 352, 359, 402, 425, 441, 497, 538, 540, 548, 552, 566, 601, 639, 668, 706, 862, 863, 918, 922, 1275],
19
+ A02: [2, 11, 13, 15, 16, 260, 315, 489, 547, 611, 614, 756, 776, 942, 1004, 1032, 1174],
20
+ A03: [829, 937, 1035, 1104, 1357, 1395],
21
+ A04: [261, 296, 310, 311, 312, 319, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 335, 336, 337, 338, 340, 347, 523, 757, 759, 760, 780, 818, 916],
22
+ A05: [20, 74, 75, 77, 78, 79, 80, 83, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 99, 113, 116, 138, 184, 470, 471, 564, 610, 643, 644, 652, 917, 943, 1236, 1336],
23
+ A06: [73, 183, 256, 266, 269, 311, 419, 430, 434, 444, 451, 463, 501, 522, 525, 539, 579, 598, 602, 642, 646, 653, 656, 657, 799, 807, 840, 841, 927, 1021, 1173],
24
+ A07: [255, 259, 287, 288, 290, 294, 295, 297, 300, 302, 304, 306, 307, 346, 384, 521, 613, 620, 640, 798, 940, 941, 1216],
25
+ A08: [345, 353, 426, 494, 502, 565, 784, 830, 915],
26
+ A09: [117, 223, 532, 778],
27
+ A10: [209, 215, 234, 248, 252, 274, 280, 369, 390, 391, 394, 396, 397, 431, 456, 476, 636, 703, 754, 755, 833],
28
+ };
29
+ // first group listed wins when a CWE appears in several groups
30
+ export const CWE_TO_OWASP = {};
31
+ for (const [group, cwes] of Object.entries(GROUPS)) for (const n of cwes) CWE_TO_OWASP[n] ??= A[group];
32
+
33
+ export function owaspForCwes(cwes) {
34
+ const out = new Set();
35
+ for (const c of cwes) {
36
+ const n = Number(String(c).replace(/\D/g, ''));
37
+ if (CWE_TO_OWASP[n]) out.add(CWE_TO_OWASP[n]);
38
+ }
39
+ return [...out];
40
+ }
41
+
42
+ /** security-severity 9+ critical, 7+ high, 4+ medium, above 0 low; otherwise level error/warning/note -> high/medium/low. */
43
+ export function severityFromSarif(result, rule) {
44
+ const raw = result?.properties?.['security-severity'] ?? rule?.properties?.['security-severity'];
45
+ const score = raw === undefined ? NaN : Number(raw);
46
+ if (Number.isFinite(score) && score > 0) return score >= 9 ? 'critical' : score >= 7 ? 'high' : score >= 4 ? 'medium' : 'low';
47
+ const level = result?.level ?? rule?.defaultConfiguration?.level ?? 'warning';
48
+ return { error: 'high', warning: 'medium', note: 'low' }[level] ?? 'info';
49
+ }
50
+
51
+ const PRECISION = { 'very-high': 'high', high: 'high', medium: 'medium', low: 'low' };
52
+
53
+ function cwesOf(rule, result) {
54
+ const found = new Set();
55
+ const sources = [...(rule?.properties?.tags ?? []), ...(result?.properties?.tags ?? []), rule?.properties?.cwe, ...(rule?.relationships ?? []).map((r) => r?.target?.id)].flat();
56
+ for (const s of sources) {
57
+ const m = typeof s === 'string' ? /cwe[-/ _]?(\d+)/i.exec(s) : null;
58
+ if (m) found.add(`CWE-${m[1]}`);
59
+ }
60
+ return [...found];
61
+ }
62
+
63
+ function uriToPath(uri, repo) {
64
+ let p = String(uri ?? '');
65
+ if (p.startsWith('file:///')) p = p.slice(8);
66
+ else if (p.startsWith('file://')) p = p.slice(7);
67
+ try { p = decodeURIComponent(p); } catch { /* keep raw */ }
68
+ p = p.replace(/\\/g, '/');
69
+ if (repo) {
70
+ const root = repo.replace(/\\/g, '/').replace(/\/$/, '');
71
+ if (p.toLowerCase().startsWith(`${root.toLowerCase()}/`)) p = p.slice(root.length + 1);
72
+ }
73
+ return p.replace(/^\.\//, '');
74
+ }
75
+
76
+ /**
77
+ * Normalizes SARIF 2.1.0 to raw findings (input for makeFinding).
78
+ * opts: { tool, category, repo, secrets (redact snippets), fix, confidence }
79
+ */
80
+ export function normalizeSarif(sarif, opts = {}) {
81
+ const out = [];
82
+ for (const run of sarif?.runs ?? []) {
83
+ const driver = run.tool?.driver ?? {};
84
+ const rules = driver.rules ?? [];
85
+ const byId = new Map(rules.map((r) => [r.id, r]));
86
+ for (const result of run.results ?? []) {
87
+ const rule = byId.get(result.ruleId) ?? rules[result.ruleIndex];
88
+ const ruleId = result.ruleId ?? rule?.id ?? 'unknown';
89
+ const phys = result.locations?.[0]?.physicalLocation;
90
+ const file = phys?.artifactLocation?.uri ? uriToPath(phys.artifactLocation.uri, opts.repo) : undefined;
91
+ const line = phys?.region?.startLine;
92
+ const snippet = phys?.region?.snippet?.text ?? phys?.contextRegion?.snippet?.text ?? '';
93
+ const evidence = snippet ? (opts.secrets ? redact(snippet.trim()) : scrubText(snippet.trim().slice(0, 500))) : '';
94
+ const cwe = cwesOf(rule, result);
95
+ out.push({
96
+ tool: opts.tool ?? driver.name,
97
+ ruleId,
98
+ key: `${ruleId}:${crypto.createHash('sha256').update(snippet.trim() || String(line ?? '')).digest('hex').slice(0, 12)}`,
99
+ category: opts.category ?? 'other',
100
+ severity: severityFromSarif(result, rule),
101
+ confidence: opts.confidence ?? PRECISION[rule?.properties?.precision] ?? 'medium',
102
+ title: rule?.shortDescription?.text ?? rule?.name ?? ruleId,
103
+ description: result.message?.text ?? rule?.fullDescription?.text ?? '',
104
+ location: { file, line },
105
+ evidence,
106
+ refs: [rule?.helpUri].filter(Boolean),
107
+ fix: opts.fix ?? '',
108
+ cwe,
109
+ });
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+
115
+ const LEVEL = { critical: 'error', high: 'error', medium: 'warning', low: 'note', info: 'note' };
116
+ const SCORE = { critical: 9.5, high: 8, medium: 5, low: 2 };
117
+
118
+ /** Writes our findings as SARIF 2.1.0 (results.sarif). */
119
+ export function toSarif(findings, { vsgVersion = '0.0.0' } = {}) {
120
+ const rules = new Map();
121
+ const results = findings.map((f) => {
122
+ const ruleId = `${f.check}/${f.sources[0]?.ruleId ?? f.id}`;
123
+ if (!rules.has(ruleId)) rules.set(ruleId, { id: ruleId, name: ruleId, shortDescription: { text: f.title } });
124
+ const result = {
125
+ ruleId,
126
+ level: LEVEL[f.severity],
127
+ message: { text: f.description ? `${f.title}: ${f.description}` : f.title },
128
+ partialFingerprints: { vsgFingerprint: f.fingerprint },
129
+ properties: { ...(SCORE[f.severity] ? { 'security-severity': String(SCORE[f.severity]) } : {}), severity: f.severity, blocking: f.blocking, status: f.status },
130
+ };
131
+ if (f.location.file) {
132
+ result.locations = [{ physicalLocation: { artifactLocation: { uri: f.location.file }, ...(f.location.line ? { region: { startLine: f.location.line } } : {}) } }];
133
+ }
134
+ return result;
135
+ });
136
+ return {
137
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
138
+ version: '2.1.0',
139
+ runs: [{ tool: { driver: { name: 'vsg', version: vsgVersion, rules: [...rules.values()] } }, results }],
140
+ };
141
+ }