@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.
- package/DISCLOSURE +1 -0
- package/README.md +118 -2
- package/bundles/expo-mobile/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-mobile/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-mobile/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-mobile/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/README.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-mobile/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-mobile/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-mobile/.claude/settings.json +38 -0
- package/bundles/expo-mobile/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-mobile/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-mobile/.cursor/mcp.json +34 -0
- package/bundles/expo-mobile/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-mobile/.cursor/rules.md +94 -0
- package/bundles/expo-mobile/.env.example +19 -0
- package/bundles/expo-mobile/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-mobile/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-mobile/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-mobile/.github/copilot-instructions.md +37 -0
- package/bundles/expo-mobile/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-mobile/.github/workflows/ci.yml +26 -0
- package/bundles/expo-mobile/.gitmessage +24 -0
- package/bundles/expo-mobile/.lattice/config.json +9 -0
- package/bundles/expo-mobile/.lattice/dependency-security.json +91 -0
- package/bundles/expo-mobile/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-mobile/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-mobile/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-mobile/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-mobile/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-mobile/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-mobile/.lattice/release-workflow.json +55 -0
- package/bundles/expo-mobile/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-mobile/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-mobile/.mcp.json +35 -0
- package/bundles/expo-mobile/AGENTS.md +58 -0
- package/bundles/expo-mobile/CLAUDE.md +75 -0
- package/bundles/expo-mobile/PROJECT_GUIDE.md +299 -0
- package/bundles/expo-mobile/README.md +39 -0
- package/bundles/expo-mobile/__tests__/home.test.tsx +22 -0
- package/bundles/expo-mobile/app/_layout.tsx +20 -0
- package/bundles/expo-mobile/app/index.tsx +72 -0
- package/bundles/expo-mobile/app.json +33 -0
- package/bundles/expo-mobile/components/ui/Button.tsx +75 -0
- package/bundles/expo-mobile/components/ui/Input.tsx +50 -0
- package/bundles/expo-mobile/components/ui/index.ts +2 -0
- package/bundles/expo-mobile/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-mobile/docs/rules/codex.md +62 -0
- package/bundles/expo-mobile/eslint.config.js +7 -0
- package/bundles/expo-mobile/lib/supabase/client.ts +22 -0
- package/bundles/expo-mobile/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-mobile/lib/supabase/storage.ts +6 -0
- package/bundles/expo-mobile/package.json +65 -0
- package/bundles/expo-mobile/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-mobile/prompts/01-feature-implementation.md +48 -0
- package/bundles/expo-mobile/prompts/02-bug-fix.md +43 -0
- package/bundles/expo-mobile/prompts/03-safe-refactor.md +38 -0
- package/bundles/expo-mobile/prompts/04-test-first.md +41 -0
- package/bundles/expo-mobile/prompts/05-ui-polish.md +44 -0
- package/bundles/expo-mobile/prompts/06-security-review.md +39 -0
- package/bundles/expo-mobile/prompts/quality.md +143 -0
- package/bundles/expo-mobile/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-mobile/tsconfig.json +21 -0
- package/bundles/expo-mobile/types/database.types.ts +76 -0
- package/bundles/expo-mobile/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/expo-supabase/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-supabase/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-supabase/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-supabase/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/README.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-supabase/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-supabase/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-supabase/.claude/settings.json +38 -0
- package/bundles/expo-supabase/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-supabase/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-supabase/.cursor/mcp.json +34 -0
- package/bundles/expo-supabase/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-supabase/.cursor/rules.md +94 -0
- package/bundles/expo-supabase/.env.example +12 -0
- package/bundles/expo-supabase/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-supabase/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-supabase/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-supabase/.github/copilot-instructions.md +37 -0
- package/bundles/expo-supabase/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-supabase/.github/workflows/ci.yml +26 -0
- package/bundles/expo-supabase/.gitmessage +24 -0
- package/bundles/expo-supabase/.lattice/config.json +9 -0
- package/bundles/expo-supabase/.lattice/dependency-security.json +91 -0
- package/bundles/expo-supabase/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-supabase/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-supabase/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-supabase/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-supabase/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-supabase/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-supabase/.lattice/release-workflow.json +55 -0
- package/bundles/expo-supabase/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-supabase/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-supabase/.mcp.json +35 -0
- package/bundles/expo-supabase/AGENTS.md +58 -0
- package/bundles/expo-supabase/CLAUDE.md +75 -0
- package/bundles/expo-supabase/PROJECT_GUIDE.md +201 -0
- package/bundles/expo-supabase/README.md +38 -0
- package/bundles/expo-supabase/__tests__/home.test.tsx +22 -0
- package/bundles/expo-supabase/app/_layout.tsx +14 -0
- package/bundles/expo-supabase/app/index.tsx +72 -0
- package/bundles/expo-supabase/app.json +33 -0
- package/bundles/expo-supabase/components/ui/Button.tsx +75 -0
- package/bundles/expo-supabase/components/ui/Input.tsx +50 -0
- package/bundles/expo-supabase/components/ui/index.ts +2 -0
- package/bundles/expo-supabase/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-supabase/docs/rules/codex.md +62 -0
- package/bundles/expo-supabase/eslint.config.js +7 -0
- package/bundles/expo-supabase/lib/supabase/client.ts +22 -0
- package/bundles/expo-supabase/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-supabase/lib/supabase/storage.ts +6 -0
- package/bundles/expo-supabase/package.json +60 -0
- package/bundles/expo-supabase/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-supabase/prompts/01-feature-implementation.md +8 -0
- package/bundles/expo-supabase/prompts/02-bug-fix.md +9 -0
- package/bundles/expo-supabase/prompts/03-safe-refactor.md +8 -0
- package/bundles/expo-supabase/prompts/04-test-first.md +7 -0
- package/bundles/expo-supabase/prompts/05-ui-polish.md +7 -0
- package/bundles/expo-supabase/prompts/06-security-review.md +7 -0
- package/bundles/expo-supabase/prompts/quality.md +143 -0
- package/bundles/expo-supabase/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-supabase/tsconfig.json +21 -0
- package/bundles/expo-supabase/types/database.types.ts +76 -0
- package/bundles/expo-supabase/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-landing/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-landing/.claude/settings.json +38 -0
- package/bundles/nextjs-landing/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-landing/.cursor/mcp.json +34 -0
- package/bundles/nextjs-landing/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-landing/.cursor/rules.md +95 -0
- package/bundles/nextjs-landing/.env.example +24 -0
- package/bundles/nextjs-landing/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-landing/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-landing/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-landing/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-landing/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-landing/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-landing/.gitmessage +24 -0
- package/bundles/nextjs-landing/.lattice/config.json +9 -0
- package/bundles/nextjs-landing/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-landing/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-landing/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-landing/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-landing/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-landing/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-landing/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-landing/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-landing/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-landing/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-landing/.mcp.json +35 -0
- package/bundles/nextjs-landing/AGENTS.md +58 -0
- package/bundles/nextjs-landing/CLAUDE.md +76 -0
- package/bundles/nextjs-landing/PROJECT_GUIDE.md +216 -0
- package/bundles/nextjs-landing/README.md +39 -0
- package/bundles/nextjs-landing/app/api/example/[id]/route.ts +34 -0
- package/bundles/nextjs-landing/app/api/example/route.ts +24 -0
- package/bundles/nextjs-landing/app/api/health/route.ts +8 -0
- package/bundles/nextjs-landing/app/global-error.tsx +17 -0
- package/bundles/nextjs-landing/app/globals.css +55 -0
- package/bundles/nextjs-landing/app/layout.tsx +22 -0
- package/bundles/nextjs-landing/app/page.tsx +42 -0
- package/bundles/nextjs-landing/components/ui/button.tsx +39 -0
- package/bundles/nextjs-landing/components/ui/card.tsx +53 -0
- package/bundles/nextjs-landing/components/ui/index.ts +4 -0
- package/bundles/nextjs-landing/components/ui/input.tsx +23 -0
- package/bundles/nextjs-landing/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-landing/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-landing/docs/rules/codex.md +62 -0
- package/bundles/nextjs-landing/eslint.config.mjs +16 -0
- package/bundles/nextjs-landing/instrumentation-client.ts +30 -0
- package/bundles/nextjs-landing/instrumentation.ts +12 -0
- package/bundles/nextjs-landing/jest.config.mjs +16 -0
- package/bundles/nextjs-landing/jest.setup.js +1 -0
- package/bundles/nextjs-landing/lib/utils.ts +6 -0
- package/bundles/nextjs-landing/next-env.d.ts +5 -0
- package/bundles/nextjs-landing/next.config.ts +15 -0
- package/bundles/nextjs-landing/package.json +48 -0
- package/bundles/nextjs-landing/postcss.config.mjs +7 -0
- package/bundles/nextjs-landing/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-landing/prompts/02-bug-fix.md +10 -0
- package/bundles/nextjs-landing/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-landing/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-landing/prompts/05-ui-polish.md +8 -0
- package/bundles/nextjs-landing/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-landing/prompts/quality.md +269 -0
- package/bundles/nextjs-landing/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-landing/sentry.server.config.ts +15 -0
- package/bundles/nextjs-landing/tsconfig.json +42 -0
- package/bundles/nextjs-landing/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-saas/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-saas/.claude/settings.json +38 -0
- package/bundles/nextjs-saas/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-saas/.cursor/mcp.json +34 -0
- package/bundles/nextjs-saas/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-saas/.cursor/rules.md +95 -0
- package/bundles/nextjs-saas/.env.example +48 -0
- package/bundles/nextjs-saas/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-saas/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-saas/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-saas/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-saas/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-saas/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-saas/.gitmessage +24 -0
- package/bundles/nextjs-saas/.lattice/config.json +9 -0
- package/bundles/nextjs-saas/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-saas/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-saas/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-saas/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-saas/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-saas/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/quality.md +269 -0
- package/bundles/nextjs-saas/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-saas/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-saas/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-saas/.mcp.json +35 -0
- package/bundles/nextjs-saas/AGENTS.md +58 -0
- package/bundles/nextjs-saas/CLAUDE.md +76 -0
- package/bundles/nextjs-saas/PROJECT_GUIDE.md +279 -0
- package/bundles/nextjs-saas/README.md +42 -0
- package/bundles/nextjs-saas/app/(auth)/forgot-password/page.tsx +101 -0
- package/bundles/nextjs-saas/app/(auth)/layout.tsx +7 -0
- package/bundles/nextjs-saas/app/(auth)/login/page.tsx +108 -0
- package/bundles/nextjs-saas/app/(auth)/reset-password/page.tsx +135 -0
- package/bundles/nextjs-saas/app/(auth)/signup/page.tsx +144 -0
- package/bundles/nextjs-saas/app/(dashboard)/dashboard/page.tsx +80 -0
- package/bundles/nextjs-saas/app/(dashboard)/layout.tsx +49 -0
- package/bundles/nextjs-saas/app/api/billing/checkout/route.ts +47 -0
- package/bundles/nextjs-saas/app/api/billing/portal/route.ts +29 -0
- package/bundles/nextjs-saas/app/api/example/[id]/route.ts +93 -0
- package/bundles/nextjs-saas/app/api/example/route.ts +61 -0
- package/bundles/nextjs-saas/app/api/health/route.ts +8 -0
- package/bundles/nextjs-saas/app/api/stripe/webhook/route.ts +57 -0
- package/bundles/nextjs-saas/app/api/user/route.ts +49 -0
- package/bundles/nextjs-saas/app/auth/callback/route.ts +16 -0
- package/bundles/nextjs-saas/app/auth/signout/route.ts +10 -0
- package/bundles/nextjs-saas/app/global-error.tsx +17 -0
- package/bundles/nextjs-saas/app/globals.css +55 -0
- package/bundles/nextjs-saas/app/layout.tsx +22 -0
- package/bundles/nextjs-saas/app/page.tsx +84 -0
- package/bundles/nextjs-saas/components/ui/button.tsx +39 -0
- package/bundles/nextjs-saas/components/ui/card.tsx +53 -0
- package/bundles/nextjs-saas/components/ui/index.ts +4 -0
- package/bundles/nextjs-saas/components/ui/input.tsx +23 -0
- package/bundles/nextjs-saas/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-saas/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-saas/docs/rules/codex.md +62 -0
- package/bundles/nextjs-saas/eslint.config.mjs +16 -0
- package/bundles/nextjs-saas/instrumentation-client.ts +30 -0
- package/bundles/nextjs-saas/instrumentation.ts +12 -0
- package/bundles/nextjs-saas/jest.config.mjs +16 -0
- package/bundles/nextjs-saas/jest.setup.js +1 -0
- package/bundles/nextjs-saas/lib/auth/password-recovery.ts +94 -0
- package/bundles/nextjs-saas/lib/stripe.ts +10 -0
- package/bundles/nextjs-saas/lib/supabase/admin.ts +14 -0
- package/bundles/nextjs-saas/lib/supabase/client.ts +8 -0
- package/bundles/nextjs-saas/lib/supabase/server.ts +27 -0
- package/bundles/nextjs-saas/lib/utils.ts +6 -0
- package/bundles/nextjs-saas/next-env.d.ts +5 -0
- package/bundles/nextjs-saas/next.config.ts +15 -0
- package/bundles/nextjs-saas/package.json +50 -0
- package/bundles/nextjs-saas/postcss.config.mjs +7 -0
- package/bundles/nextjs-saas/prompts/01-feature-implementation.md +92 -0
- package/bundles/nextjs-saas/prompts/02-bug-fix.md +78 -0
- package/bundles/nextjs-saas/prompts/03-safe-refactor.md +82 -0
- package/bundles/nextjs-saas/prompts/04-test-first.md +80 -0
- package/bundles/nextjs-saas/prompts/05-ui-polish.md +82 -0
- package/bundles/nextjs-saas/prompts/06-security-review.md +74 -0
- package/bundles/nextjs-saas/proxy.ts +61 -0
- package/bundles/nextjs-saas/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-saas/sentry.server.config.ts +15 -0
- package/bundles/nextjs-saas/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/nextjs-saas/supabase/migrations/002_billing.sql +17 -0
- package/bundles/nextjs-saas/tsconfig.json +42 -0
- package/bundles/nextjs-saas/types/database.types.ts +76 -0
- package/bundles/nextjs-saas/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-trpc/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-trpc/.claude/settings.json +38 -0
- package/bundles/nextjs-trpc/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-trpc/.cursor/mcp.json +34 -0
- package/bundles/nextjs-trpc/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-trpc/.cursor/rules.md +95 -0
- package/bundles/nextjs-trpc/.env.example +21 -0
- package/bundles/nextjs-trpc/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-trpc/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-trpc/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-trpc/.gitmessage +24 -0
- package/bundles/nextjs-trpc/.lattice/config.json +9 -0
- package/bundles/nextjs-trpc/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-trpc/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-trpc/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-trpc/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-trpc/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-trpc/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-trpc/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-trpc/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-trpc/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-trpc/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-trpc/.mcp.json +35 -0
- package/bundles/nextjs-trpc/AGENTS.md +58 -0
- package/bundles/nextjs-trpc/CLAUDE.md +76 -0
- package/bundles/nextjs-trpc/PROJECT_GUIDE.md +187 -0
- package/bundles/nextjs-trpc/README.md +49 -0
- package/bundles/nextjs-trpc/app/api/trpc/[trpc]/route.ts +12 -0
- package/bundles/nextjs-trpc/app/global-error.tsx +17 -0
- package/bundles/nextjs-trpc/app/globals.css +55 -0
- package/bundles/nextjs-trpc/app/layout.tsx +19 -0
- package/bundles/nextjs-trpc/app/page.tsx +16 -0
- package/bundles/nextjs-trpc/app/providers.tsx +24 -0
- package/bundles/nextjs-trpc/components/ui/button.tsx +39 -0
- package/bundles/nextjs-trpc/components/ui/card.tsx +53 -0
- package/bundles/nextjs-trpc/components/ui/index.ts +4 -0
- package/bundles/nextjs-trpc/components/ui/input.tsx +23 -0
- package/bundles/nextjs-trpc/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-trpc/components/user-list.tsx +17 -0
- package/bundles/nextjs-trpc/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-trpc/docs/rules/codex.md +62 -0
- package/bundles/nextjs-trpc/eslint.config.mjs +16 -0
- package/bundles/nextjs-trpc/instrumentation-client.ts +20 -0
- package/bundles/nextjs-trpc/instrumentation.ts +12 -0
- package/bundles/nextjs-trpc/jest.config.mjs +16 -0
- package/bundles/nextjs-trpc/jest.setup.js +1 -0
- package/bundles/nextjs-trpc/lib/db.ts +17 -0
- package/bundles/nextjs-trpc/lib/trpc/react.tsx +7 -0
- package/bundles/nextjs-trpc/lib/utils.ts +6 -0
- package/bundles/nextjs-trpc/next-env.d.ts +5 -0
- package/bundles/nextjs-trpc/next.config.ts +15 -0
- package/bundles/nextjs-trpc/package.json +62 -0
- package/bundles/nextjs-trpc/postcss.config.mjs +7 -0
- package/bundles/nextjs-trpc/prisma/schema.prisma +41 -0
- package/bundles/nextjs-trpc/prisma.config.ts +10 -0
- package/bundles/nextjs-trpc/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-trpc/prompts/02-bug-fix.md +9 -0
- package/bundles/nextjs-trpc/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-trpc/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-trpc/prompts/05-ui-polish.md +7 -0
- package/bundles/nextjs-trpc/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-trpc/prompts/quality.md +269 -0
- package/bundles/nextjs-trpc/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-trpc/sentry.server.config.ts +15 -0
- package/bundles/nextjs-trpc/server/routers/_app.test.ts +60 -0
- package/bundles/nextjs-trpc/server/routers/_app.ts +25 -0
- package/bundles/nextjs-trpc/server/trpc.ts +12 -0
- package/bundles/nextjs-trpc/tsconfig.json +42 -0
- package/bundles/nextjs-trpc/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/dist/agent-install-session.d.ts +17 -0
- package/dist/agent-install-session.d.ts.map +1 -0
- package/dist/agent-install-session.js +106 -0
- package/dist/agent-install-session.js.map +1 -0
- package/dist/agent-install-session.test.d.ts +2 -0
- package/dist/agent-install-session.test.d.ts.map +1 -0
- package/dist/agent-install-session.test.js +62 -0
- package/dist/agent-install-session.test.js.map +1 -0
- package/dist/agent-install.d.ts +41 -0
- package/dist/agent-install.d.ts.map +1 -0
- package/dist/agent-install.js +42 -0
- package/dist/agent-install.js.map +1 -0
- package/dist/agent-install.test.d.ts +2 -0
- package/dist/agent-install.test.d.ts.map +1 -0
- package/dist/agent-install.test.js +58 -0
- package/dist/agent-install.test.js.map +1 -0
- package/dist/apply-merge.test.d.ts +2 -0
- package/dist/apply-merge.test.d.ts.map +1 -0
- package/dist/apply-merge.test.js +123 -0
- package/dist/apply-merge.test.js.map +1 -0
- package/dist/bundle-download.d.ts +23 -0
- package/dist/bundle-download.d.ts.map +1 -0
- package/dist/bundle-download.js +180 -0
- package/dist/bundle-download.js.map +1 -0
- package/dist/bundle-download.test.d.ts +2 -0
- package/dist/bundle-download.test.d.ts.map +1 -0
- package/dist/bundle-download.test.js +200 -0
- package/dist/bundle-download.test.js.map +1 -0
- package/dist/bundle-rollback.test.d.ts +2 -0
- package/dist/bundle-rollback.test.d.ts.map +1 -0
- package/dist/bundle-rollback.test.js +127 -0
- package/dist/bundle-rollback.test.js.map +1 -0
- package/dist/bundle-scaffold.test.d.ts +2 -0
- package/dist/bundle-scaffold.test.d.ts.map +1 -0
- package/dist/bundle-scaffold.test.js +105 -0
- package/dist/bundle-scaffold.test.js.map +1 -0
- package/dist/commands.d.ts +132 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +2012 -0
- package/dist/commands.js.map +1 -0
- package/dist/credential-store.d.ts +27 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +48 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/credential-store.test.d.ts +2 -0
- package/dist/credential-store.test.d.ts.map +1 -0
- package/dist/credential-store.test.js +80 -0
- package/dist/credential-store.test.js.map +1 -0
- package/dist/delivered-project-files.d.ts +31 -0
- package/dist/delivered-project-files.d.ts.map +1 -0
- package/dist/delivered-project-files.js +158 -0
- package/dist/delivered-project-files.js.map +1 -0
- package/dist/delivered-project-files.test.d.ts +2 -0
- package/dist/delivered-project-files.test.d.ts.map +1 -0
- package/dist/delivered-project-files.test.js +107 -0
- package/dist/delivered-project-files.test.js.map +1 -0
- package/dist/doctor-agents.test.d.ts +2 -0
- package/dist/doctor-agents.test.d.ts.map +1 -0
- package/dist/doctor-agents.test.js +65 -0
- package/dist/doctor-agents.test.js.map +1 -0
- package/dist/hermes-install.d.ts +102 -0
- package/dist/hermes-install.d.ts.map +1 -0
- package/dist/hermes-install.js +641 -0
- package/dist/hermes-install.js.map +1 -0
- package/dist/hermes-install.test.d.ts +2 -0
- package/dist/hermes-install.test.d.ts.map +1 -0
- package/dist/hermes-install.test.js +866 -0
- package/dist/hermes-install.test.js.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5311 -0
- package/dist/index.js.map +1 -0
- package/dist/openclaw-install.d.ts +67 -0
- package/dist/openclaw-install.d.ts.map +1 -0
- package/dist/openclaw-install.js +358 -0
- package/dist/openclaw-install.js.map +1 -0
- package/dist/openclaw-install.test.d.ts +2 -0
- package/dist/openclaw-install.test.d.ts.map +1 -0
- package/dist/openclaw-install.test.js +296 -0
- package/dist/openclaw-install.test.js.map +1 -0
- package/dist/password-recovery-bundle.test.d.ts +2 -0
- package/dist/password-recovery-bundle.test.d.ts.map +1 -0
- package/dist/password-recovery-bundle.test.js +132 -0
- package/dist/password-recovery-bundle.test.js.map +1 -0
- package/dist/project-bundle-env.d.ts +19 -0
- package/dist/project-bundle-env.d.ts.map +1 -0
- package/dist/project-bundle-env.js +111 -0
- package/dist/project-bundle-env.js.map +1 -0
- package/dist/project-bundle-env.test.d.ts +2 -0
- package/dist/project-bundle-env.test.d.ts.map +1 -0
- package/dist/project-bundle-env.test.js +93 -0
- package/dist/project-bundle-env.test.js.map +1 -0
- package/dist/project-path-confinement.test.d.ts +2 -0
- package/dist/project-path-confinement.test.d.ts.map +1 -0
- package/dist/project-path-confinement.test.js +222 -0
- package/dist/project-path-confinement.test.js.map +1 -0
- package/dist/security/api.d.ts +18 -0
- package/dist/security/api.d.ts.map +1 -0
- package/dist/security/api.js +238 -0
- package/dist/security/api.js.map +1 -0
- package/dist/security/api.test.d.ts +2 -0
- package/dist/security/api.test.d.ts.map +1 -0
- package/dist/security/api.test.js +220 -0
- package/dist/security/api.test.js.map +1 -0
- package/dist/security/artifact.d.ts +10 -0
- package/dist/security/artifact.d.ts.map +1 -0
- package/dist/security/artifact.js +283 -0
- package/dist/security/artifact.js.map +1 -0
- package/dist/security/artifact.test.d.ts +2 -0
- package/dist/security/artifact.test.d.ts.map +1 -0
- package/dist/security/artifact.test.js +229 -0
- package/dist/security/artifact.test.js.map +1 -0
- package/dist/security/commands.d.ts +42 -0
- package/dist/security/commands.d.ts.map +1 -0
- package/dist/security/commands.js +825 -0
- package/dist/security/commands.js.map +1 -0
- package/dist/security/commands.test.d.ts +2 -0
- package/dist/security/commands.test.d.ts.map +1 -0
- package/dist/security/commands.test.js +1051 -0
- package/dist/security/commands.test.js.map +1 -0
- package/dist/security/connection.d.ts +19 -0
- package/dist/security/connection.d.ts.map +1 -0
- package/dist/security/connection.js +145 -0
- package/dist/security/connection.js.map +1 -0
- package/dist/security/connection.test.d.ts +2 -0
- package/dist/security/connection.test.d.ts.map +1 -0
- package/dist/security/connection.test.js +62 -0
- package/dist/security/connection.test.js.map +1 -0
- package/dist/security/index-routing.test.d.ts +2 -0
- package/dist/security/index-routing.test.d.ts.map +1 -0
- package/dist/security/index-routing.test.js +169 -0
- package/dist/security/index-routing.test.js.map +1 -0
- package/dist/security/journal.d.ts +26 -0
- package/dist/security/journal.d.ts.map +1 -0
- package/dist/security/journal.js +441 -0
- package/dist/security/journal.js.map +1 -0
- package/dist/security/journal.test.d.ts +2 -0
- package/dist/security/journal.test.d.ts.map +1 -0
- package/dist/security/journal.test.js +226 -0
- package/dist/security/journal.test.js.map +1 -0
- package/dist/security/package-verifier.d.ts +15 -0
- package/dist/security/package-verifier.d.ts.map +1 -0
- package/dist/security/package-verifier.js +65 -0
- package/dist/security/package-verifier.js.map +1 -0
- package/dist/security/package-verifier.test.d.ts +2 -0
- package/dist/security/package-verifier.test.d.ts.map +1 -0
- package/dist/security/package-verifier.test.js +57 -0
- package/dist/security/package-verifier.test.js.map +1 -0
- package/dist/security/runtime.d.ts +43 -0
- package/dist/security/runtime.d.ts.map +1 -0
- package/dist/security/runtime.js +81 -0
- package/dist/security/runtime.js.map +1 -0
- package/dist/security/runtime.test.d.ts +2 -0
- package/dist/security/runtime.test.d.ts.map +1 -0
- package/dist/security/runtime.test.js +88 -0
- package/dist/security/runtime.test.js.map +1 -0
- package/dist/security/types.d.ts +169 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +3 -0
- package/dist/security/types.js.map +1 -0
- package/dist/security-runtime/LICENSE +202 -0
- package/dist/security-runtime/NOTICE +16 -0
- package/dist/security-runtime/artifact-manifest.json +960 -0
- package/dist/security-runtime/bin/vsg.mjs +5 -0
- package/dist/security-runtime/lattice-catalog.json +176585 -0
- package/dist/security-runtime/lattice-runtime.mjs +90 -0
- package/dist/security-runtime/licensing/candidates/apache-source-license-evidence.json +478 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-generic-api-key.json +76 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-jwt.json +74 -0
- package/dist/security-runtime/licensing/candidates/blackberry-additional-source-declaration-evidence.json +276 -0
- package/dist/security-runtime/licensing/candidates/blackberry-source-declaration-evidence.json +369 -0
- package/dist/security-runtime/licensing/candidates/cado-source-declaration-evidence.json +283 -0
- package/dist/security-runtime/licensing/candidates/commercial-rule-candidate.json +280 -0
- package/dist/security-runtime/licensing/candidates/commercial-runtime-release.json +389 -0
- package/dist/security-runtime/licensing/candidates/data-feed-evidence.json +259 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-rule-identities.json +178 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-evidence.json +1824 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-notice-bundle.json +45 -0
- package/dist/security-runtime/licensing/candidates/malpedia-source-license-evidence.json +41 -0
- package/dist/security-runtime/licensing/candidates/rule-license-evidence.json +376 -0
- package/dist/security-runtime/licensing/candidates/runner-image-composition.json +1500 -0
- package/dist/security-runtime/licensing/candidates/selected-runtime-coverage.json +91 -0
- package/dist/security-runtime/licensing/candidates/yara-forge-20260927.json +58502 -0
- package/dist/security-runtime/licensing/commercial-components.json +379 -0
- package/dist/security-runtime/licensing/notices/betterleaks-1.9.0-MIT.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5.txt +428 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/33acf42afdd03163d75fe856cb431e70710e0b5deede3362e6d1c4697cfe1c6a.txt +28 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986.txt +674 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/4fecb8fcaf80fd9a6315bca5225ee43377015b46a0459c2925495be885cab19a.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/811515f8723aa4007de2fbb75a364127fdec51cda534b87a7ea8f2fef2f96dd7.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/814442c3987a2c0411171ad8ad43118e8c08f85c9c5d1dcdecc9787c7ca8689b.txt +664 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/8a959b2413d0c83ca1b3ce9153076c649abf2e4bbfebbb493fb24c2074a1eb8a.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/995182e185856186fbbe49f150409ae026aa4cda7e94f83c6ed737cbb77fa9c7.txt +23 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/9b8e1305e46d1dd4011b6ed933c9f6223e08c9c4625001c5fd75a34fa930f616.txt +202 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a01f1a28424bfe7f49a23d339a01847cfa9ace3864d5e7295051772a4bae2271.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5681bf9b05db14d86776930017c647ad9e6e56ff6bbcfdf21e5848288dfaf1b.txt +165 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5b8bd9ae96c630bbf229d61c5c5def20274e00be39abfd2fab4d7879ceab462.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ada32b3747ce108d5e8bab7918af94abbd03f2fe2a46304f74c6333bbfd914ed.txt +8 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt +201 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c738ae413cf561f174e34f6961f8ca458aae2369a73640dda6234c629b98bcc4.txt +2732 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ce41c9a2c986277f7f9ab146a2195c1888a5d8da062e22d7c9c88666bb7c38cd.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/d5f4c36a0528e01cfdd9e4fc60879201d3af49b41c520200643d321fe53da5f9.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e08c368ccd455f85c7fb9e04830e730f26e9dbb5c0f77bc88e09a5b0cb37df5b.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e9efb2f7daf11bb6379c06c16b870dddcecc4a10f8704bb8aad807529c8b3c97.txt +8 -0
- package/dist/security-runtime/licensing/notices/gitleaks-generic-origin-782f3104-MIT.txt +21 -0
- package/dist/security-runtime/package.json +17 -0
- package/dist/security-runtime/playbook/README.md +76 -0
- package/dist/security-runtime/playbook/access-control.md +56 -0
- package/dist/security-runtime/playbook/ai-features.md +53 -0
- package/dist/security-runtime/playbook/desktop-python.md +59 -0
- package/dist/security-runtime/playbook/input-handling.md +59 -0
- package/dist/security-runtime/playbook/mobile-expo.md +59 -0
- package/dist/security-runtime/playbook/payments.md +53 -0
- package/dist/security-runtime/playbook/privacy-account-recovery.md +59 -0
- package/dist/security-runtime/playbook/secrets-config.md +47 -0
- package/dist/security-runtime/playbook/supabase.md +48 -0
- package/dist/security-runtime/playbook/supply-chain.md +62 -0
- package/dist/security-runtime/playbook/webhooks-integrations.md +52 -0
- package/dist/security-runtime/rules/vsg-ai-key-in-client.yaml +41 -0
- package/dist/security-runtime/rules/vsg-ai-output-raw-html.yaml +35 -0
- package/dist/security-runtime/rules/vsg-angular-sanitizer-bypass.yaml +23 -0
- package/dist/security-runtime/rules/vsg-expo-token-in-asyncstorage.yaml +22 -0
- package/dist/security-runtime/rules/vsg-flask-public-bind.yaml +32 -0
- package/dist/security-runtime/rules/vsg-nextjs-action-write-no-auth.yaml +144 -0
- package/dist/security-runtime/rules/vsg-nextjs-inline-action-write-no-auth.yaml +161 -0
- package/dist/security-runtime/rules/vsg-nextjs-route-write-no-auth.yaml +193 -0
- package/dist/security-runtime/rules/vsg-nosql-where-injection.yaml +36 -0
- package/dist/security-runtime/rules/vsg-python-pickle-load.yaml +26 -0
- package/dist/security-runtime/rules/vsg-python-requests-verify-false.yaml +34 -0
- package/dist/security-runtime/rules/vsg-python-subprocess-shell.yaml +23 -0
- package/dist/security-runtime/rules/vsg-stripe-client-price.yaml +51 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-no-signature.yaml +31 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-pages-no-signature.yaml +51 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-in-client.yaml +27 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-public-env.yaml +24 -0
- package/dist/security-runtime/rules/vsg-xml-external-entities.yaml +19 -0
- package/dist/security-runtime/src/agent/brief.mjs +95 -0
- package/dist/security-runtime/src/agent/discover.mjs +108 -0
- package/dist/security-runtime/src/agent/exec.mjs +64 -0
- package/dist/security-runtime/src/agent/ingest.mjs +213 -0
- package/dist/security-runtime/src/agent/report.mjs +94 -0
- package/dist/security-runtime/src/agent/schema.mjs +62 -0
- package/dist/security-runtime/src/agent/tokenize.mjs +38 -0
- package/dist/security-runtime/src/checks/README.md +8 -0
- package/dist/security-runtime/src/checks/_lib/gitfiles.mjs +35 -0
- package/dist/security-runtime/src/checks/_lib/lockfiles.mjs +135 -0
- package/dist/security-runtime/src/checks/_lib/osv.mjs +6 -0
- package/dist/security-runtime/src/checks/_lib/secrets.mjs +381 -0
- package/dist/security-runtime/src/checks/_lib/targets.mjs +79 -0
- package/dist/security-runtime/src/checks/agent-review/index.mjs +52 -0
- package/dist/security-runtime/src/checks/agent-surface/config.mjs +223 -0
- package/dist/security-runtime/src/checks/agent-surface/index.mjs +113 -0
- package/dist/security-runtime/src/checks/agent-surface/instructions.mjs +87 -0
- package/dist/security-runtime/src/checks/agent-surface/patterns.mjs +68 -0
- package/dist/security-runtime/src/checks/agent-surface/unicode.mjs +88 -0
- package/dist/security-runtime/src/checks/code/auth-helpers.mjs +659 -0
- package/dist/security-runtime/src/checks/code/index.mjs +617 -0
- package/dist/security-runtime/src/checks/code/opengrep-container.mjs +85 -0
- package/dist/security-runtime/src/checks/code/opengrep-runner.mjs +86 -0
- package/dist/security-runtime/src/checks/code/selected-opengrep-runner.mjs +112 -0
- package/dist/security-runtime/src/checks/dep-change/guarddog.mjs +142 -0
- package/dist/security-runtime/src/checks/dep-change/index.mjs +316 -0
- package/dist/security-runtime/src/checks/dep-change/registries.mjs +199 -0
- package/dist/security-runtime/src/checks/deps-vuln/index.mjs +305 -0
- package/dist/security-runtime/src/checks/desktop-config/index.mjs +112 -0
- package/dist/security-runtime/src/checks/dns-mail/index.mjs +71 -0
- package/dist/security-runtime/src/checks/iac/index.mjs +83 -0
- package/dist/security-runtime/src/checks/install-posture/index.mjs +123 -0
- package/dist/security-runtime/src/checks/licenses/index.mjs +143 -0
- package/dist/security-runtime/src/checks/live-basic/index.mjs +77 -0
- package/dist/security-runtime/src/checks/live-js-secrets/index.mjs +100 -0
- package/dist/security-runtime/src/checks/malware/clamav.mjs +17 -0
- package/dist/security-runtime/src/checks/malware/defender.mjs +43 -0
- package/dist/security-runtime/src/checks/malware/index.mjs +186 -0
- package/dist/security-runtime/src/checks/malware/targets.mjs +147 -0
- package/dist/security-runtime/src/checks/malware/yara-runner.mjs +201 -0
- package/dist/security-runtime/src/checks/malware/yara.mjs +124 -0
- package/dist/security-runtime/src/checks/mobile-config/index.mjs +173 -0
- package/dist/security-runtime/src/checks/mobile-config/mobsf.mjs +105 -0
- package/dist/security-runtime/src/checks/posture/index.mjs +133 -0
- package/dist/security-runtime/src/checks/project-scripts/index.mjs +187 -0
- package/dist/security-runtime/src/checks/sbom/index.mjs +98 -0
- package/dist/security-runtime/src/checks/secrets-build/index.mjs +63 -0
- package/dist/security-runtime/src/checks/secrets-history/index.mjs +112 -0
- package/dist/security-runtime/src/checks/secrets-tree/env.mjs +120 -0
- package/dist/security-runtime/src/checks/secrets-tree/index.mjs +73 -0
- package/dist/security-runtime/src/checks/supabase-static/index.mjs +240 -0
- package/dist/security-runtime/src/checks/supabase-static/sql.mjs +557 -0
- package/dist/security-runtime/src/checks/workflows/index.mjs +93 -0
- package/dist/security-runtime/src/cli/deep.mjs +13 -0
- package/dist/security-runtime/src/cli/doctor.mjs +82 -0
- package/dist/security-runtime/src/cli/index.mjs +60 -0
- package/dist/security-runtime/src/cli/live-common.mjs +27 -0
- package/dist/security-runtime/src/cli/parse.mjs +56 -0
- package/dist/security-runtime/src/cli/portfolio.mjs +34 -0
- package/dist/security-runtime/src/cli/release.mjs +28 -0
- package/dist/security-runtime/src/cli/report.mjs +18 -0
- package/dist/security-runtime/src/cli/run-common.mjs +54 -0
- package/dist/security-runtime/src/cli/scan.mjs +12 -0
- package/dist/security-runtime/src/cli/setup.mjs +59 -0
- package/dist/security-runtime/src/cli/url.mjs +38 -0
- package/dist/security-runtime/src/core/accept.mjs +141 -0
- package/dist/security-runtime/src/core/archive.mjs +114 -0
- package/dist/security-runtime/src/core/base-ref.mjs +43 -0
- package/dist/security-runtime/src/core/checks.mjs +39 -0
- package/dist/security-runtime/src/core/detect.mjs +90 -0
- package/dist/security-runtime/src/core/errors.mjs +5 -0
- package/dist/security-runtime/src/core/fetch.mjs +139 -0
- package/dist/security-runtime/src/core/findings.mjs +177 -0
- package/dist/security-runtime/src/core/gate.mjs +131 -0
- package/dist/security-runtime/src/core/installer.mjs +255 -0
- package/dist/security-runtime/src/core/lock.mjs +38 -0
- package/dist/security-runtime/src/core/nested.mjs +107 -0
- package/dist/security-runtime/src/core/paths.mjs +20 -0
- package/dist/security-runtime/src/core/pipeline.mjs +196 -0
- package/dist/security-runtime/src/core/registry.mjs +81 -0
- package/dist/security-runtime/src/core/report.mjs +128 -0
- package/dist/security-runtime/src/core/runner.mjs +86 -0
- package/dist/security-runtime/src/core/sarif.mjs +141 -0
- package/dist/security-runtime/src/lattice/artifact.mjs +1201 -0
- package/dist/security-runtime/src/lattice/execution.mjs +45 -0
- package/dist/security-runtime/src/lattice/export.mjs +215 -0
- package/dist/security-runtime/src/lattice/provenance.mjs +70 -0
- package/dist/security-runtime/src/lattice/runtime.mjs +158 -0
- package/dist/security-runtime/src/lattice/selected-runtime.mjs +189 -0
- package/dist/security-runtime/src/lattice/source-acquirer.mjs +51 -0
- package/dist/security-runtime/src/lattice/source-fetcher.mjs +88 -0
- package/dist/security-runtime/src/lattice/transport.mjs +232 -0
- package/dist/security-runtime/src/live/analyze.mjs +101 -0
- package/dist/security-runtime/src/live/exposure.mjs +79 -0
- package/dist/security-runtime/src/live/secrets.mjs +31 -0
- package/dist/security-runtime/src/live/session.mjs +98 -0
- package/dist/security-runtime/src/live/targets.mjs +81 -0
- package/dist/security-runtime/src/portfolio/index.mjs +116 -0
- package/dist/security-runtime/tools/registry/betterleaks.json +29 -0
- package/dist/security-runtime/tools/registry/guarddog.json +9 -0
- package/dist/security-runtime/tools/registry/mobsf.json +10 -0
- package/dist/security-runtime/tools/registry/opengrep.json +28 -0
- package/dist/security-runtime/tools/registry/osv-scanner.json +14 -0
- package/dist/security-runtime/tools/registry/semgrep-rules.json +21 -0
- package/dist/security-runtime/tools/registry/trivy.json +30 -0
- package/dist/security-runtime/tools/registry/yara-forge-exclusions.json +14 -0
- package/dist/security-runtime/tools/registry/yara-forge.json +22 -0
- package/dist/security-runtime/tools/registry/yara-runner.json +10 -0
- package/dist/security-runtime/tools/registry/yara-x.json +24 -0
- package/dist/security-runtime/tools/registry/zizmor.json +30 -0
- package/dist/spinner.d.ts +16 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +72 -0
- package/dist/spinner.js.map +1 -0
- package/dist/verify.test.d.ts +2 -0
- package/dist/verify.test.d.ts.map +1 -0
- package/dist/verify.test.js +98 -0
- package/dist/verify.test.js.map +1 -0
- package/package.json +73 -4
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { platformFor, resolveTool, resolveVerifiedArchive, verifyTool } from '../core/installer.mjs';
|
|
5
|
+
import { platformKey, toolsDir } from '../core/paths.mjs';
|
|
6
|
+
import { loadRegistry } from '../core/registry.mjs';
|
|
7
|
+
import { runProcess } from '../core/runner.mjs';
|
|
8
|
+
|
|
9
|
+
const GIT_NEEDED_FOR = 'secrets history, posture, and the dependency-change base diff';
|
|
10
|
+
const firstLine = (r) => `${r.stdout}\n${r.stderr}`.trim().split('\n')[0].trim();
|
|
11
|
+
const probe = async (command, args) => {
|
|
12
|
+
const r = await runProcess({ command, args, timeoutMs: 15_000 });
|
|
13
|
+
return r.status === 'ran' ? r : null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function findDefender() {
|
|
17
|
+
if (process.platform !== 'win32') return null;
|
|
18
|
+
const candidates = [path.join(process.env.ProgramFiles ?? 'C:\\Program Files', 'Windows Defender', 'MpCmdRun.exe')];
|
|
19
|
+
const platformDir = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'Microsoft', 'Windows Defender', 'Platform');
|
|
20
|
+
try { for (const v of fs.readdirSync(platformDir).sort().reverse()) candidates.unshift(path.join(platformDir, v, 'MpCmdRun.exe')); } catch { /* no platform folder */ }
|
|
21
|
+
return candidates.find((c) => fs.existsSync(c)) ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
name: 'doctor',
|
|
26
|
+
summary: 'Report which tools, Docker and antivirus engines are available',
|
|
27
|
+
usage: '',
|
|
28
|
+
maxPositionals: 0,
|
|
29
|
+
purpose: 'Lists each registry tool with its installed version, and reports Node, git, Docker, Microsoft Defender and ClamAV status. Read-only; it changes nothing.',
|
|
30
|
+
options: [],
|
|
31
|
+
examples: ['vsg doctor', 'vsg doctor --json'],
|
|
32
|
+
async run({ flags }, io) {
|
|
33
|
+
const registry = loadRegistry(io.registryDir);
|
|
34
|
+
const tools = [];
|
|
35
|
+
for (const tool of registry.values()) {
|
|
36
|
+
if (tool.image) { tools.push({ name: tool.name, version: tool.version, status: 'image', detail: `${tool.image} (pulled by digest when used)` }); continue; }
|
|
37
|
+
if (process.platform === 'win32' && tool.name === 'semgrep-rules') {
|
|
38
|
+
const archive = resolveVerifiedArchive(tool);
|
|
39
|
+
tools.push({ name: tool.name, version: tool.version, status: archive ? 'installed' : 'not installed', detail: archive ? `${archive.path} (verified archive; Linux runner)` : 'run vsg setup --repair' });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const resolved = resolveTool(tool);
|
|
43
|
+
if (!resolved) { tools.push({ name: tool.name, version: tool.version, status: tool.platforms[platformFor(tool)] ? 'not installed' : `no build for ${platformKey()}`, detail: 'run vsg setup' }); continue; }
|
|
44
|
+
const check = await verifyTool(tool, resolved);
|
|
45
|
+
const linuxRunner = check.ok && process.platform === 'win32' && tool.name === 'opengrep' ? resolveTool(tool, 'linux-x64') : null;
|
|
46
|
+
if (check.ok && process.platform === 'win32' && tool.name === 'opengrep' && !linuxRunner) {
|
|
47
|
+
tools.push({ name: tool.name, version: tool.version, status: 'broken', detail: 'Linux runner binary missing; run vsg setup --repair' });
|
|
48
|
+
} else {
|
|
49
|
+
tools.push({ name: tool.name, version: tool.version, status: check.ok ? 'installed' : 'broken', detail: check.ok ? `${resolved.path}${linuxRunner ? `; Linux runner: ${linuxRunner.path}` : ''}` : `version check failed: ${check.output}; run vsg setup --repair` });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const git = await probe('git', ['--version']);
|
|
53
|
+
const dockerClient = await probe('docker', ['version', '--format', '{{.Client.Version}}']);
|
|
54
|
+
const dockerServer = dockerClient?.exitCode === 0 ? await probe('docker', ['version', '--format', '{{.Server.Version}}']) : null;
|
|
55
|
+
const clam = await probe('clamscan', ['--version']);
|
|
56
|
+
const defender = findDefender();
|
|
57
|
+
const env = {
|
|
58
|
+
platform: platformKey(), node: process.version, toolsDir: toolsDir(), os: `${os.type()} ${os.release()}`,
|
|
59
|
+
git: git?.exitCode === 0 ? firstLine(git) : 'not found',
|
|
60
|
+
gitNeededFor: GIT_NEEDED_FOR,
|
|
61
|
+
docker: !dockerClient || dockerClient.exitCode !== 0 ? 'not found' : dockerServer?.exitCode === 0 ? `client ${firstLine(dockerClient)}, engine ${firstLine(dockerServer)}` : `client ${firstLine(dockerClient)}, engine not running`,
|
|
62
|
+
defender: process.platform === 'win32' ? (defender ?? 'MpCmdRun.exe not found') : 'not applicable on this platform',
|
|
63
|
+
clamav: clam?.exitCode === 0 ? firstLine(clam) : 'not found',
|
|
64
|
+
};
|
|
65
|
+
const missing = tools.filter((t) => t.status !== 'installed' && t.status !== 'image');
|
|
66
|
+
const headline = !tools.length ? 'READY: the registry has no tools yet'
|
|
67
|
+
: missing.length ? `NOT READY: ${missing.length} of ${tools.length} tools missing. Run vsg setup.`
|
|
68
|
+
: `READY: all ${tools.length} tools installed`;
|
|
69
|
+
if (flags.json) io.stdout(JSON.stringify({ headline, ...env, tools }));
|
|
70
|
+
else {
|
|
71
|
+
io.stdout(headline);
|
|
72
|
+
io.stdout(`Platform: ${env.platform} (${env.os}), Node ${env.node}`);
|
|
73
|
+
io.stdout(`Tools cache: ${env.toolsDir}`);
|
|
74
|
+
for (const t of tools) io.stdout(` ${t.name} ${t.version}: ${t.status}${t.detail ? ` (${t.detail})` : ''}`);
|
|
75
|
+
io.stdout(env.git === 'not found' ? `git: NOT FOUND. These checks cannot run without it: ${GIT_NEEDED_FOR}. Install git.` : `git: ${env.git} (needed for ${GIT_NEEDED_FOR})`);
|
|
76
|
+
io.stdout(`Docker: ${env.docker}`);
|
|
77
|
+
io.stdout(`Microsoft Defender: ${env.defender}`);
|
|
78
|
+
io.stdout(`ClamAV: ${env.clamav}`);
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Command dispatch. main() returns the exit code (0 PASS, 1 BLOCKED, 2 INCOMPLETE, 3 usage, configuration, authorization or lock error).
|
|
2
|
+
import { UsageError } from '../core/errors.mjs';
|
|
3
|
+
import { assertStandaloneExecution } from '../lattice/execution.mjs';
|
|
4
|
+
import { vsgVersion } from '../core/pipeline.mjs';
|
|
5
|
+
import { commandHelp, EXIT_TEXT, parseArgs } from './parse.mjs';
|
|
6
|
+
import deep from './deep.mjs';
|
|
7
|
+
import doctor from './doctor.mjs';
|
|
8
|
+
import portfolio from './portfolio.mjs';
|
|
9
|
+
import report from './report.mjs';
|
|
10
|
+
import release from './release.mjs';
|
|
11
|
+
import scan from './scan.mjs';
|
|
12
|
+
import setup from './setup.mjs';
|
|
13
|
+
import url from './url.mjs';
|
|
14
|
+
|
|
15
|
+
const byName = new Map([setup, doctor, scan, deep, report, url, release, portfolio].map((c) => [c.name, c]));
|
|
16
|
+
const QUALIFICATION_COMMANDS = new Set(['scan', 'deep', 'report', 'release']);
|
|
17
|
+
export const isQualificationCommand = (name) => QUALIFICATION_COMMANDS.has(name);
|
|
18
|
+
export const COMMANDS = [...byName.values()].map((c) => [c.name, c.summary]);
|
|
19
|
+
export const EXIT = { PASS: 0, BLOCKED: 1, INCOMPLETE: 2, USAGE: 3 };
|
|
20
|
+
|
|
21
|
+
export function helpText() {
|
|
22
|
+
const width = Math.max(...COMMANDS.map(([name]) => name.length));
|
|
23
|
+
return [
|
|
24
|
+
'Usage: vsg <command> [options]',
|
|
25
|
+
'',
|
|
26
|
+
'Commands:',
|
|
27
|
+
...COMMANDS.map(([name, summary]) => ` ${name.padEnd(width)} ${summary}`),
|
|
28
|
+
'',
|
|
29
|
+
'Run vsg <command> --help for a command\'s options and examples.',
|
|
30
|
+
'Common options: --out <dir> (output directory), --json (one JSON summary on stdout), --help; vsg --version prints the version.',
|
|
31
|
+
EXIT_TEXT,
|
|
32
|
+
].join('\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultIo = () => ({
|
|
36
|
+
stdout: (l) => process.stdout.write(`${l}\n`),
|
|
37
|
+
stderr: (l) => process.stderr.write(`${l}\n`),
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
env: process.env,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export async function main(argv, ioOverrides = {}) {
|
|
43
|
+
const io = { ...defaultIo(), ...ioOverrides };
|
|
44
|
+
const [name, ...rest] = argv;
|
|
45
|
+
if (name === '--version' || name === '-v') { io.stdout(`vsg ${vsgVersion()}`); return EXIT.PASS; }
|
|
46
|
+
if (!name || name === '--help' || name === '-h' || name === 'help') { io.stdout(helpText()); return EXIT.PASS; }
|
|
47
|
+
const cmd = byName.get(name);
|
|
48
|
+
if (!cmd) { io.stderr(`Unknown command "${name}". Run vsg --help.`); return EXIT.USAGE; }
|
|
49
|
+
try {
|
|
50
|
+
const args = parseArgs(name, rest, cmd.options);
|
|
51
|
+
if (args.flags.help) { io.stdout(commandHelp(cmd)); return EXIT.PASS; }
|
|
52
|
+
if (args.positionals.length > cmd.maxPositionals) throw new UsageError(`Too many arguments for vsg ${name}. Run vsg ${name} --help.`);
|
|
53
|
+
assertStandaloneExecution({ allowQualification: isQualificationCommand(name) });
|
|
54
|
+
return await cmd.run(args, io);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e instanceof UsageError) { io.stderr(e.message); return EXIT.USAGE; }
|
|
57
|
+
io.stderr(`vsg ${name} failed: ${e.message}`);
|
|
58
|
+
return EXIT.INCOMPLETE; // an unexpected internal error is never a PASS
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Shared by vsg url and vsg release: authorize the target before any request, then build the rate-limited live session.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { loadConfig } from '../core/accept.mjs';
|
|
5
|
+
import { UsageError } from '../core/errors.mjs';
|
|
6
|
+
import { vsgHome } from '../core/paths.mjs';
|
|
7
|
+
import { createSession } from '../live/session.mjs';
|
|
8
|
+
import { authorize, resolveRate } from '../live/targets.mjs';
|
|
9
|
+
|
|
10
|
+
export const LIVE_OPTIONS = [
|
|
11
|
+
{ flag: '--rate', type: 'string', arg: 'n', help: 'Requests per second (default 2; more only for a loopback origin registered as local in ~/.vsg/targets.json)' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Returns { session, info: { target, authorization }, flags } for run flags. Throws UsageError (exit 3) for an unregistered origin; sends nothing. */
|
|
15
|
+
export function prepareLive(urlText, { io, repo, rateFlag }) {
|
|
16
|
+
const home = io.env?.VSG_HOME || vsgHome();
|
|
17
|
+
const repoConfig = repo && fs.existsSync(path.join(repo, 'vsg.config.json')) ? loadConfig(repo) : null;
|
|
18
|
+
const auth = authorize(urlText, { home, repoConfig, repoPath: repo });
|
|
19
|
+
const rps = resolveRate(rateFlag, auth);
|
|
20
|
+
for (const n of auth.notes) io.stderr(`Note: ${n}`);
|
|
21
|
+
const session = createSession({ origin: auth.origin, url: auth.url, displayUrl: auth.displayUrl, environment: auth.environment, rps, fetchImpl: io.fetchImpl });
|
|
22
|
+
return { session, auth, info: { target: auth.displayUrl, authorization: auth.description }, flags: { live: session } };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function requireDirectory(dir, label = 'repository') {
|
|
26
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) throw new UsageError(`"${dir}" is not a directory. Pass the ${label} path.`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Minimal argument parser driven by each command's option table. Unknown flags are a usage error (exit 3).
|
|
2
|
+
import { UsageError } from '../core/errors.mjs';
|
|
3
|
+
|
|
4
|
+
export const COMMON_OPTIONS = [
|
|
5
|
+
{ flag: '--json', type: 'bool', help: 'Print one JSON summary to stdout instead of text' },
|
|
6
|
+
{ flag: '--help', alias: '-h', type: 'bool', help: 'Show this help' },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export function parseArgs(command, argv, options) {
|
|
10
|
+
const all = [...options, ...COMMON_OPTIONS];
|
|
11
|
+
const flags = {};
|
|
12
|
+
const positionals = [];
|
|
13
|
+
const find = (token) => all.find((o) => o.flag === token || o.alias === token);
|
|
14
|
+
for (let i = 0; i < argv.length; i++) {
|
|
15
|
+
const token = argv[i];
|
|
16
|
+
if (token === '--') { positionals.push(...argv.slice(i + 1)); break; }
|
|
17
|
+
if (!token.startsWith('-') || token === '-') { positionals.push(token); continue; }
|
|
18
|
+
const eq = token.indexOf('=');
|
|
19
|
+
const name = token.startsWith('--') && eq > 0 ? token.slice(0, eq) : token;
|
|
20
|
+
const opt = find(name);
|
|
21
|
+
if (!opt) throw new UsageError(`Unknown option "${name}" for vsg ${command}. Run vsg ${command} --help.`);
|
|
22
|
+
const key = opt.flag.slice(2);
|
|
23
|
+
if (opt.type === 'bool') {
|
|
24
|
+
if (eq > 0) throw new UsageError(`Option ${opt.flag} does not take a value. Run vsg ${command} --help.`);
|
|
25
|
+
flags[key] = true;
|
|
26
|
+
} else {
|
|
27
|
+
const value = eq > 0 ? token.slice(eq + 1) : argv[++i];
|
|
28
|
+
if (value === undefined || value.startsWith('--')) throw new UsageError(`Option ${opt.flag} needs a value. Run vsg ${command} --help.`);
|
|
29
|
+
flags[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { flags, positionals };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const EXIT_TEXT = 'Exit codes: 0 PASS, 1 BLOCKED, 2 INCOMPLETE, 3 usage, configuration, authorization or lock error';
|
|
36
|
+
|
|
37
|
+
export function commandHelp(cmd) {
|
|
38
|
+
const opts = [...cmd.options, ...COMMON_OPTIONS];
|
|
39
|
+
const label = (o) => `${o.alias ? `${o.alias}, ` : ''}${o.flag}${o.type === 'string' ? ` <${o.arg ?? 'value'}>` : ''}`;
|
|
40
|
+
const width = Math.max(...opts.map((o) => label(o).length));
|
|
41
|
+
return [
|
|
42
|
+
`vsg ${cmd.name}: ${cmd.summary}`,
|
|
43
|
+
'',
|
|
44
|
+
`Usage: vsg ${cmd.name}${cmd.usage ? ` ${cmd.usage}` : ''} [options]`,
|
|
45
|
+
'',
|
|
46
|
+
cmd.purpose,
|
|
47
|
+
'',
|
|
48
|
+
'Options:',
|
|
49
|
+
...opts.map((o) => ` ${label(o).padEnd(width)} ${o.help}`),
|
|
50
|
+
'',
|
|
51
|
+
'Examples:',
|
|
52
|
+
...cmd.examples.map((e) => ` ${e}`),
|
|
53
|
+
'',
|
|
54
|
+
EXIT_TEXT,
|
|
55
|
+
].join('\n');
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { discoverChecks } from '../core/checks.mjs';
|
|
3
|
+
import { defaultChecksDir, vsgHome } from '../core/paths.mjs';
|
|
4
|
+
import { loadRegistry } from '../core/registry.mjs';
|
|
5
|
+
import { loadPortfolio, runPortfolio } from '../portfolio/index.mjs';
|
|
6
|
+
import { prepareLive } from './live-common.mjs';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'portfolio',
|
|
10
|
+
summary: 'Run across every product in ~/.vsg/portfolio.json',
|
|
11
|
+
usage: '',
|
|
12
|
+
maxPositionals: 0,
|
|
13
|
+
purpose: 'Runs each registered product through scan or deep, one after another, and writes the outputs plus summary.md and summary.json to ~/.vsg/portfolio/<date>/ (never inside a product). A product with a registered "url" also gets the passive live checks. A product that fails is recorded and the run continues. The exit code is the worst verdict. Dated folders older than 90 days are removed. Schedule it with Windows Task Scheduler (see README); vsg never creates scheduled tasks.',
|
|
14
|
+
options: [{ flag: '--config', type: 'string', arg: 'file', help: 'Portfolio file (default: ~/.vsg/portfolio.json)' }],
|
|
15
|
+
examples: ['vsg portfolio', 'vsg portfolio --config ./portfolio.json --json'],
|
|
16
|
+
async run({ flags }, io) {
|
|
17
|
+
const home = io.env?.VSG_HOME || vsgHome();
|
|
18
|
+
const products = loadPortfolio(flags.config ? path.resolve(io.cwd, flags.config) : path.join(home, 'portfolio.json'));
|
|
19
|
+
const dirs = [defaultChecksDir, ...(io.env.VSG_EXTRA_CHECKS_DIR ? [io.env.VSG_EXTRA_CHECKS_DIR] : [])];
|
|
20
|
+
const checks = io.checks ?? await discoverChecks(dirs);
|
|
21
|
+
const registry = loadRegistry(io.registryDir);
|
|
22
|
+
const { summary, dir, pruned, exitCode } = await runPortfolio(products, {
|
|
23
|
+
home, checks, registry, now: io.now, progress: (l) => io.stderr(l),
|
|
24
|
+
prepareLive: (url, repo) => prepareLive(url, { io, repo }),
|
|
25
|
+
});
|
|
26
|
+
if (flags.json) io.stdout(JSON.stringify({ ...summary, outputs: { dir, summary: path.join(dir, 'summary.md') }, pruned }));
|
|
27
|
+
else {
|
|
28
|
+
io.stdout(`Portfolio ${summary.verdict}: ${summary.products.length} product(s)`);
|
|
29
|
+
for (const p of summary.products) io.stdout(` ${p.name}: ${p.verdict}, ${p.mustFix} must fix, ${p.error ? `run failed: ${p.error}` : `${p.notRun.length} check(s) did not run`}`);
|
|
30
|
+
io.stdout(`Summary: ${path.join(dir, 'summary.md')}`);
|
|
31
|
+
}
|
|
32
|
+
return exitCode;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { LIVE_OPTIONS, prepareLive, requireDirectory } from './live-common.mjs';
|
|
3
|
+
import { RUN_OPTIONS, runMode } from './run-common.mjs';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'release',
|
|
7
|
+
summary: 'Deep audit plus passive live checks against a registered preview URL, with the SBOM',
|
|
8
|
+
usage: '[path]',
|
|
9
|
+
maxPositionals: 1,
|
|
10
|
+
purpose: 'Runs the deep audit in release mode (project scripts and SBOM included), then the passive live checks against the registered preview URL, and writes one combined set of outputs and one verdict. The URL must be registered (see vsg url --help). Without --url the live checks are reported as did-not-run, so the verdict is INCOMPLETE rather than PASS. vsg never starts the app.',
|
|
11
|
+
options: [
|
|
12
|
+
...RUN_OPTIONS,
|
|
13
|
+
{ flag: '--url', type: 'string', arg: 'url', help: 'Registered preview URL to check (required for a PASS)' },
|
|
14
|
+
{ flag: '--no-validate', type: 'bool', help: 'Do not validate found credentials against their issuers' },
|
|
15
|
+
...LIVE_OPTIONS,
|
|
16
|
+
],
|
|
17
|
+
examples: ['vsg release --url https://preview.example.com', 'vsg release ../my-app --url http://127.0.0.1:3000 --json'],
|
|
18
|
+
async run(args, io) {
|
|
19
|
+
const repo = path.resolve(io.cwd, args.positionals[0] ?? '.');
|
|
20
|
+
requireDirectory(repo);
|
|
21
|
+
if (!args.flags.url) {
|
|
22
|
+
io.stderr('No --url was given, so the live checks will not run and the verdict cannot be PASS.');
|
|
23
|
+
return runMode('release', args, io);
|
|
24
|
+
}
|
|
25
|
+
const live = prepareLive(args.flags.url, { io, repo, rateFlag: args.flags.rate });
|
|
26
|
+
return runMode('release', args, io, { repo, flags: live.flags, info: live.info });
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { reportWithAgentFindings } from '../agent/report.mjs';
|
|
3
|
+
import { printResult } from './run-common.mjs';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'report',
|
|
7
|
+
summary: 'Merge agent findings and recompute the verdict',
|
|
8
|
+
usage: '[path]',
|
|
9
|
+
maxPositionals: 1,
|
|
10
|
+
purpose: 'Reads the previous deep run in the output directory plus <out>/agent/findings.json. It validates the file, rejects findings that cite missing files, runs each reproduction command (at most 20, 120 seconds each, no shell), and rewrites the report and verdict. It does not rescan.',
|
|
11
|
+
options: [{ flag: '--out', type: 'string', arg: 'dir', help: 'Output directory of the deep run (default: <path>/.vsg)' }],
|
|
12
|
+
examples: ['vsg report', 'vsg report ../my-app --out ../scan-output', 'vsg report --json'],
|
|
13
|
+
async run({ flags, positionals }, io) {
|
|
14
|
+
const repo = path.resolve(io.cwd, positionals[0] ?? '.');
|
|
15
|
+
const result = await reportWithAgentFindings({ repo, out: flags.out ? path.resolve(io.cwd, flags.out) : undefined, now: io.now, progress: (l) => io.stderr(l) });
|
|
16
|
+
return printResult('deep', result, flags, io);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Shared run path for scan and deep (and later url and release): load checks and registry, run the pipeline, print the result.
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { discoverChecks } from '../core/checks.mjs';
|
|
4
|
+
import { EXIT_CODES, headline } from '../core/gate.mjs';
|
|
5
|
+
import { scrubText, scrubUrl } from '../core/findings.mjs';
|
|
6
|
+
import { defaultChecksDir } from '../core/paths.mjs';
|
|
7
|
+
import { runPipeline } from '../core/pipeline.mjs';
|
|
8
|
+
import { loadRegistry } from '../core/registry.mjs';
|
|
9
|
+
|
|
10
|
+
export const RUN_OPTIONS = [
|
|
11
|
+
{ flag: '--out', type: 'string', arg: 'dir', help: 'Output directory (default: <path>/.vsg)' },
|
|
12
|
+
{ flag: '--base', type: 'string', arg: 'ref', help: 'Git base ref for acceptance and policy change review (default: vsg.config.json baseRef, then the merge-base with the remote default branch)' },
|
|
13
|
+
{ flag: '--compare', type: 'string', arg: 'findings.json', help: 'Earlier findings.json; findings absent from it are marked new' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* extra (used by url and release): { repo, out (default), flags (merged into pipeline flags), info: { target, authorization } }.
|
|
18
|
+
*/
|
|
19
|
+
export async function runMode(mode, { flags, positionals }, io, extra = {}) {
|
|
20
|
+
const repo = extra.repo ?? path.resolve(io.cwd, positionals[0] ?? '.');
|
|
21
|
+
const dirs = [defaultChecksDir, ...(io.env.VSG_EXTRA_CHECKS_DIR ? [io.env.VSG_EXTRA_CHECKS_DIR] : [])];
|
|
22
|
+
const checks = io.checks ?? await discoverChecks(dirs);
|
|
23
|
+
const registry = loadRegistry(io.registryDir);
|
|
24
|
+
const result = await runPipeline({
|
|
25
|
+
mode, repo, out: flags.out ? path.resolve(io.cwd, flags.out) : extra.out, checks, registry,
|
|
26
|
+
flags: { base: flags.base, noValidate: !!flags['no-validate'], compare: flags.compare ? path.resolve(io.cwd, flags.compare) : undefined,
|
|
27
|
+
...(extra.flags ?? {}), ...(extra.info ? { target: extra.info.target, authorization: extra.info.authorization } : {}) },
|
|
28
|
+
now: io.now, progress: (line) => io.stderr(line),
|
|
29
|
+
});
|
|
30
|
+
return printResult(mode, result, flags, io, extra);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Prints a run's result (text or --json) and returns the exit code. Shared by scan, deep and report. */
|
|
34
|
+
export function printResult(mode, result, flags, io, extra = {}) {
|
|
35
|
+
const line = headline(result.verdict, result.counts, result.coverage);
|
|
36
|
+
const exitCode = EXIT_CODES[result.verdict];
|
|
37
|
+
if (flags.json) {
|
|
38
|
+
io.stdout(JSON.stringify({
|
|
39
|
+
verdict: result.verdict, headline: line, mode, exitCode, counts: result.counts,
|
|
40
|
+
notRun: result.coverage.filter((c) => c.status === 'not_run').map((c) => ({ id: c.id, required: c.required, reason: c.reason })),
|
|
41
|
+
outputs: result.outputs,
|
|
42
|
+
...(extra.info ? { target: scrubUrl(extra.info.target), authorizedBy: scrubText(extra.info.authorization) } : {}),
|
|
43
|
+
}));
|
|
44
|
+
} else {
|
|
45
|
+
io.stdout(line);
|
|
46
|
+
if (extra.info) io.stdout(`Target: ${scrubUrl(extra.info.target)} (authorized by ${scrubText(extra.info.authorization)})`);
|
|
47
|
+
for (const f of result.findings.filter((x) => x.blocking).slice(0, 10)) {
|
|
48
|
+
io.stdout(` Must fix: ${f.severity}: ${f.title}${f.location.file ? ` (${f.location.file}${f.location.line ? `:${f.location.line}` : ''})` : ''}`);
|
|
49
|
+
}
|
|
50
|
+
for (const c of result.coverage.filter((x) => x.status === 'not_run')) io.stdout(` Did not run: ${c.id}${c.required ? ' (required)' : ''}: ${c.reason}`);
|
|
51
|
+
io.stdout(`Report: ${result.outputs.report}`);
|
|
52
|
+
}
|
|
53
|
+
return exitCode;
|
|
54
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RUN_OPTIONS, runMode } from './run-common.mjs';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: 'scan',
|
|
5
|
+
summary: 'Fast repository scan; never runs repository code',
|
|
6
|
+
usage: '[path]',
|
|
7
|
+
maxPositionals: 1,
|
|
8
|
+
purpose: 'Runs the fast checks (dependencies, working-tree secrets, code, agent surface, workflows) on a repository and gives a PASS, BLOCKED or INCOMPLETE verdict. It never installs packages or runs repository code, and it writes only to its output directory.',
|
|
9
|
+
options: RUN_OPTIONS,
|
|
10
|
+
examples: ['vsg scan', 'vsg scan ../my-app --out ../scan-output', 'vsg scan --json'],
|
|
11
|
+
run: (args, io) => runMode('scan', args, io),
|
|
12
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FetchError, InstallError, UsageError } from '../core/errors.mjs';
|
|
2
|
+
import { installTool, installVerifiedArchive } from '../core/installer.mjs';
|
|
3
|
+
import { toolsDir } from '../core/paths.mjs';
|
|
4
|
+
import { loadRegistry } from '../core/registry.mjs';
|
|
5
|
+
import { runProcess } from '../core/runner.mjs';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: 'setup',
|
|
9
|
+
summary: 'Download and verify the pinned scanner tools',
|
|
10
|
+
usage: '[tool...]',
|
|
11
|
+
maxPositionals: 99,
|
|
12
|
+
purpose: 'Installs every tool in the registry for this platform into the tools cache. Each download is checked against its pinned SHA-256 before anything is extracted. PATH and system settings are never changed.',
|
|
13
|
+
options: [{ flag: '--repair', type: 'bool', help: 'Reinstall tools even if they look installed' }],
|
|
14
|
+
examples: ['vsg setup', 'vsg setup osv-scanner', 'vsg setup --repair'],
|
|
15
|
+
async run({ flags, positionals }, io) {
|
|
16
|
+
const registry = loadRegistry(io.registryDir);
|
|
17
|
+
const names = positionals.length ? positionals : [...registry.keys()];
|
|
18
|
+
for (const n of names) if (!registry.has(n)) throw new UsageError(`Unknown tool "${n}". Run vsg doctor to list registry tools.`);
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const name of names) {
|
|
21
|
+
const tool = registry.get(name);
|
|
22
|
+
try {
|
|
23
|
+
if (tool.image) {
|
|
24
|
+
const docker = await runProcess({ command: 'docker', args: ['pull', tool.image], timeoutMs: 600_000 });
|
|
25
|
+
if (docker.status !== 'ran' || docker.exitCode !== 0) throw new InstallError(`Docker could not pull ${tool.image}. Start Docker Desktop and rerun vsg setup.`);
|
|
26
|
+
results.push({ name, status: 'installed', detail: tool.image });
|
|
27
|
+
} else {
|
|
28
|
+
const options = { force: !!flags.repair, log: (l) => io.stderr(l) };
|
|
29
|
+
if (process.platform === 'win32' && name === 'semgrep-rules') {
|
|
30
|
+
const r = await installVerifiedArchive(tool, options);
|
|
31
|
+
results.push({ name, status: r.status, detail: `${r.path} (verified archive; extracted only in Linux)` });
|
|
32
|
+
} else {
|
|
33
|
+
const r = await installTool(tool, options);
|
|
34
|
+
if (process.platform === 'win32' && name === 'opengrep') {
|
|
35
|
+
const linux = await installTool(tool, { ...options, platform: 'linux-x64' });
|
|
36
|
+
results.push({ name, status: r.status === 'already' && linux.status === 'already' ? 'already' : 'installed', detail: `${r.path}; Linux runner: ${linux.path}` });
|
|
37
|
+
} else {
|
|
38
|
+
results.push({ name, status: r.status, detail: r.path });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (!(e instanceof InstallError) && !(e instanceof FetchError)) throw e;
|
|
44
|
+
results.push({ name, status: 'failed', detail: e.message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
48
|
+
const headline = !results.length ? 'SETUP: the registry has no tools yet'
|
|
49
|
+
: failed.length ? `SETUP FAILED: ${failed.length} of ${results.length} tools were not installed`
|
|
50
|
+
: `SETUP OK: ${results.length} of ${results.length} tools ready`;
|
|
51
|
+
if (flags.json) io.stdout(JSON.stringify({ headline, toolsDir: toolsDir(), results }));
|
|
52
|
+
else {
|
|
53
|
+
io.stdout(headline);
|
|
54
|
+
for (const r of results) io.stdout(` ${r.name}: ${r.status}${r.detail ? `: ${r.detail}` : ''}`);
|
|
55
|
+
if (failed.length) io.stdout('Fix the problem above and rerun vsg setup. Nothing was installed from a failed download.');
|
|
56
|
+
}
|
|
57
|
+
return failed.length ? 2 : 0;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { UsageError } from '../core/errors.mjs';
|
|
5
|
+
import { vsgHome } from '../core/paths.mjs';
|
|
6
|
+
import { LIVE_OPTIONS, prepareLive, requireDirectory } from './live-common.mjs';
|
|
7
|
+
import { runMode } from './run-common.mjs';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: 'url',
|
|
11
|
+
summary: 'Passive live checks of a registered target (headers, cookies, CORS, exposed files, served JavaScript)',
|
|
12
|
+
usage: '<url>',
|
|
13
|
+
maxPositionals: 1,
|
|
14
|
+
purpose: 'Reads pages and files of a registered target with ordinary GET requests (2 per second or less) and reports headers, cookie flags, CORS, exposed sensitive files, source maps, secrets in served JavaScript and mail DNS records. The origin must be listed in ~/.vsg/targets.json or in the "urls" of the repository\'s vsg.config.json; otherwise it exits 3 before any request. Nothing is written to the target.',
|
|
15
|
+
options: [
|
|
16
|
+
{ flag: '--out', type: 'string', arg: 'dir', help: 'Output directory (default: ~/.vsg/url/<host>)' },
|
|
17
|
+
{ flag: '--repo', type: 'string', arg: 'dir', help: 'Product repository whose vsg.config.json urls and vsg.accept.json apply (default: the current directory when it has a vsg.config.json)' },
|
|
18
|
+
...LIVE_OPTIONS,
|
|
19
|
+
],
|
|
20
|
+
examples: ['vsg url https://staging.example.com', 'vsg url http://127.0.0.1:3000 --json', 'vsg url https://preview.example.com --repo ../my-app'],
|
|
21
|
+
async run(args, io) {
|
|
22
|
+
const urlText = args.positionals[0];
|
|
23
|
+
if (!urlText) throw new UsageError('vsg url needs the URL to check. Run vsg url --help.');
|
|
24
|
+
let repo = args.flags.repo ? path.resolve(io.cwd, args.flags.repo) : null;
|
|
25
|
+
if (repo) requireDirectory(repo);
|
|
26
|
+
else if (fs.existsSync(path.join(io.cwd, 'vsg.config.json'))) repo = io.cwd;
|
|
27
|
+
const live = prepareLive(urlText, { io, repo, rateFlag: args.flags.rate });
|
|
28
|
+
const home = io.env?.VSG_HOME || vsgHome();
|
|
29
|
+
const out = path.join(home, 'url', live.auth.host.replace(/[^A-Za-z0-9.-]/g, '_'));
|
|
30
|
+
// The pipeline wants a repository directory for config and acceptances. With none, an empty scratch directory stands in.
|
|
31
|
+
const scratch = repo ? null : fs.mkdtempSync(path.join(os.tmpdir(), 'vsg-url-'));
|
|
32
|
+
try {
|
|
33
|
+
return await runMode('url', args, io, { repo: repo ?? scratch, out, flags: { ...live.flags, noReview: !repo }, info: live.info });
|
|
34
|
+
} finally {
|
|
35
|
+
if (scratch) fs.rmSync(scratch, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Acceptances (vsg.accept.json), product config (vsg.config.json), and the human-review listing against a base ref.
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { resolveBase } from './base-ref.mjs';
|
|
6
|
+
import { UsageError } from './errors.mjs';
|
|
7
|
+
|
|
8
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
9
|
+
const dateMs = (s, endOfDay = false) => {
|
|
10
|
+
if (typeof s !== 'string') return NaN;
|
|
11
|
+
return Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(s) ? `${s}T${endOfDay ? '23:59:59' : '00:00:00'}Z` : s);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function readJson(file, label) {
|
|
15
|
+
let text;
|
|
16
|
+
try { text = fs.readFileSync(file, 'utf8'); } catch { return undefined; }
|
|
17
|
+
try { return JSON.parse(text); } catch (e) { throw new UsageError(`${label} is not valid JSON (${e.message}). Fix it and rerun.`); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Checks one acceptance entry. Returns an error string, or null when it is well formed. A day of clock skew is allowed for approvedAt. */
|
|
21
|
+
export function acceptanceProblem(a, now = Date.now()) {
|
|
22
|
+
if (!a || typeof a !== 'object') return 'not an object';
|
|
23
|
+
if (!/^[0-9a-f]{64}$/.test(a.fingerprint ?? '')) return 'fingerprint must be 64 hex characters';
|
|
24
|
+
for (const k of ['reason', 'approvedBy']) if (typeof a[k] !== 'string' || !a[k].trim()) return `${k} is required`;
|
|
25
|
+
const approved = dateMs(a.approvedAt);
|
|
26
|
+
const expires = dateMs(a.expires, true);
|
|
27
|
+
if (Number.isNaN(approved)) return 'approvedAt must be a date';
|
|
28
|
+
if (Number.isNaN(expires)) return 'expires is required and must be a date';
|
|
29
|
+
if (approved > now + DAY) return 'approvedAt is in the future';
|
|
30
|
+
if (expires <= approved) return 'expires must be after approvedAt';
|
|
31
|
+
if (expires - approved > 90 * DAY + DAY) return 'expires must be within 90 days of approvedAt';
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Loads vsg.accept.json (an array, or { acceptances: [...] }). Returns { valid, invalid }. Invalid or expired entries suppress nothing. */
|
|
36
|
+
export function loadAcceptances(repo, { now = Date.now() } = {}) {
|
|
37
|
+
const data = readJson(path.join(repo, 'vsg.accept.json'), 'vsg.accept.json');
|
|
38
|
+
const list = Array.isArray(data) ? data : data?.acceptances ?? [];
|
|
39
|
+
if (data !== undefined && !Array.isArray(list)) throw new UsageError('vsg.accept.json must be an array or { "acceptances": [] }.');
|
|
40
|
+
const valid = [];
|
|
41
|
+
const invalid = [];
|
|
42
|
+
for (const a of list) {
|
|
43
|
+
const problem = acceptanceProblem(a, now);
|
|
44
|
+
if (problem) { invalid.push({ entry: a, problem }); continue; }
|
|
45
|
+
valid.push({ ...a, expired: dateMs(a.expires, true) < now });
|
|
46
|
+
}
|
|
47
|
+
return { valid, invalid };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Marks findings accepted (valid, unexpired entry) or expired-acceptance. Everything else is left open. */
|
|
51
|
+
export function applyAcceptances(findings, { valid }) {
|
|
52
|
+
for (const f of findings) {
|
|
53
|
+
const mine = valid.filter((a) => a.fingerprint === f.fingerprint);
|
|
54
|
+
if (mine.some((a) => !a.expired)) f.status = 'accepted';
|
|
55
|
+
else if (mine.length) f.status = 'expired-acceptance';
|
|
56
|
+
}
|
|
57
|
+
return findings;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const strArray = (v, name) => {
|
|
61
|
+
if (v === undefined) return [];
|
|
62
|
+
if (!Array.isArray(v) || !v.every((x) => typeof x === 'string')) throw new UsageError(`vsg.config.json: ${name} must be an array of strings.`);
|
|
63
|
+
return v;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** authHelpers: function names the code check accepts as a caller check. They go into a regex, so only plain identifiers are allowed. */
|
|
67
|
+
const authHelperNames = (v) => {
|
|
68
|
+
const names = strArray(v, 'authHelpers');
|
|
69
|
+
const bad = names.find((n) => !/^[A-Za-z_$][\w$]*$/.test(n));
|
|
70
|
+
if (bad !== undefined) throw new UsageError(`vsg.config.json: authHelpers entries must be plain function names (letters, digits, _ or $), not "${bad}".`);
|
|
71
|
+
return names;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Loads and validates vsg.config.json (optional). */
|
|
75
|
+
export function loadConfig(repo) {
|
|
76
|
+
const data = readJson(path.join(repo, 'vsg.config.json'), 'vsg.config.json') ?? {};
|
|
77
|
+
if (typeof data !== 'object' || Array.isArray(data)) throw new UsageError('vsg.config.json must be a JSON object.');
|
|
78
|
+
const remove = data.requiredChecks?.remove ?? [];
|
|
79
|
+
if (!Array.isArray(remove) || remove.some((r) => typeof r?.check !== 'string' || typeof r?.reason !== 'string' || !r.reason.trim())) {
|
|
80
|
+
throw new UsageError('vsg.config.json: requiredChecks.remove entries need { "check", "reason" } with a reason.');
|
|
81
|
+
}
|
|
82
|
+
if (data.baseRef !== undefined && typeof data.baseRef !== 'string') throw new UsageError('vsg.config.json: baseRef must be a string.');
|
|
83
|
+
return {
|
|
84
|
+
baseRef: data.baseRef ?? null,
|
|
85
|
+
urls: strArray(data.urls, 'urls'),
|
|
86
|
+
supabase: { projectRef: data.supabase?.projectRef ?? null },
|
|
87
|
+
publicTables: strArray(data.publicTables, 'publicTables'),
|
|
88
|
+
authHelpers: authHelperNames(data.authHelpers),
|
|
89
|
+
artifacts: strArray(data.artifacts, 'artifacts'),
|
|
90
|
+
licenseAllow: strArray(data.licenseAllow, 'licenseAllow'),
|
|
91
|
+
agentReview: { command: data.agentReview?.command ?? null },
|
|
92
|
+
mobile: { apk: typeof data.mobile?.apk === 'string' && data.mobile.apk ? data.mobile.apk : null },
|
|
93
|
+
requiredChecks: { remove },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const git = (repo, args) => {
|
|
98
|
+
try {
|
|
99
|
+
return execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 20_000 }).trim();
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** The base commit (a SHA): --base flag, then config.baseRef, then the merge-base with the remote default branch (see base-ref.mjs). Null when git can't tell. */
|
|
106
|
+
export function resolveBaseRef(repo, opts = {}) {
|
|
107
|
+
return resolveBase(repo, opts).sha;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parseJson = (text) => { try { return JSON.parse(text); } catch { return undefined; } };
|
|
111
|
+
|
|
112
|
+
/** Lists acceptance and policy changes against the base ref for human review. `why` says why there is no base (see resolveBase). Returns [string]. */
|
|
113
|
+
export function reviewItems(repo, baseRef, why) {
|
|
114
|
+
if (!baseRef) return [`No base ref is available (${why ?? 'no git history or remote default branch'}), so acceptance and policy changes could not be compared. Review vsg.accept.json and vsg.config.json by hand.`];
|
|
115
|
+
const items = [];
|
|
116
|
+
const baseOf = (file) => { const t = git(repo, ['show', `${baseRef}:./${file}`]); return t === null ? undefined : parseJson(t); };
|
|
117
|
+
const nowOf = (file) => parseJson((() => { try { return fs.readFileSync(path.join(repo, file), 'utf8'); } catch { return 'undefined'; } })());
|
|
118
|
+
|
|
119
|
+
const accList = (d) => (Array.isArray(d) ? d : d?.acceptances ?? []);
|
|
120
|
+
const before = new Map(accList(baseOf('vsg.accept.json')).map((a) => [a?.fingerprint, a]));
|
|
121
|
+
const after = new Map(accList(nowOf('vsg.accept.json')).map((a) => [a?.fingerprint, a]));
|
|
122
|
+
for (const [fp, a] of after) {
|
|
123
|
+
const short = String(fp).slice(0, 12);
|
|
124
|
+
if (!before.has(fp)) items.push(`Acceptance added for ${short}: ${a.reason ?? '(no reason)'} (approved by ${a.approvedBy ?? 'nobody'}, expires ${a.expires ?? 'never'}).`);
|
|
125
|
+
else if (JSON.stringify(before.get(fp)) !== JSON.stringify(a)) items.push(`Acceptance changed for ${short}: now expires ${a.expires ?? 'never'}, approved by ${a.approvedBy ?? 'nobody'}.`);
|
|
126
|
+
}
|
|
127
|
+
for (const fp of before.keys()) if (!after.has(fp)) items.push(`Acceptance removed for ${String(fp).slice(0, 12)}.`);
|
|
128
|
+
|
|
129
|
+
const b = baseOf('vsg.config.json') ?? {};
|
|
130
|
+
const n = nowOf('vsg.config.json') ?? {};
|
|
131
|
+
const diff = (label, was, is) => {
|
|
132
|
+
const w = new Set(was.map((x) => JSON.stringify(x)));
|
|
133
|
+
const i = new Set(is.map((x) => JSON.stringify(x)));
|
|
134
|
+
for (const x of i) if (!w.has(x)) items.push(`${label} added: ${x}`);
|
|
135
|
+
for (const x of w) if (!i.has(x)) items.push(`${label} removed: ${x}`);
|
|
136
|
+
};
|
|
137
|
+
diff('publicTables entry', b.publicTables ?? [], n.publicTables ?? []);
|
|
138
|
+
diff('authHelpers entry', b.authHelpers ?? [], n.authHelpers ?? []);
|
|
139
|
+
diff('requiredChecks.remove entry', b.requiredChecks?.remove ?? [], n.requiredChecks?.remove ?? []);
|
|
140
|
+
return items;
|
|
141
|
+
}
|