opencastle 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/cli/adapters/claude-code.d.ts +2 -2
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +31 -4
- package/dist/cli/adapters/claude-code.js.map +1 -1
- package/dist/cli/adapters/cursor.d.ts +2 -2
- package/dist/cli/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/adapters/cursor.js +28 -4
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/opencode.d.ts +20 -0
- package/dist/cli/adapters/opencode.d.ts.map +1 -0
- package/dist/cli/adapters/opencode.js +265 -0
- package/dist/cli/adapters/opencode.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts +2 -2
- package/dist/cli/adapters/vscode.d.ts.map +1 -1
- package/dist/cli/adapters/vscode.js +38 -7
- package/dist/cli/adapters/vscode.js.map +1 -1
- package/dist/cli/copy.d.ts +12 -0
- package/dist/cli/copy.d.ts.map +1 -1
- package/dist/cli/copy.js +27 -0
- package/dist/cli/copy.js.map +1 -1
- package/dist/cli/detect.d.ts +18 -0
- package/dist/cli/detect.d.ts.map +1 -0
- package/dist/cli/detect.js +434 -0
- package/dist/cli/detect.js.map +1 -0
- package/dist/cli/gitignore.d.ts.map +1 -1
- package/dist/cli/gitignore.js +0 -2
- package/dist/cli/gitignore.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +154 -91
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +2 -1
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -6
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +105 -34
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/prompt.d.ts +22 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +239 -0
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/stack-config.d.ts +26 -3
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +140 -125
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +46 -10
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +26 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +66 -19
- package/dist/cli/update.js.map +1 -1
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.js +48 -0
- package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
- package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
- package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/convex/config.js +32 -0
- package/dist/orchestrator/plugins/convex/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts +28 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -0
- package/dist/orchestrator/plugins/index.js +63 -0
- package/dist/orchestrator/plugins/index.js.map +1 -0
- package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
- package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/jira/config.js +29 -0
- package/dist/orchestrator/plugins/jira/config.js.map +1 -0
- package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
- package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/linear/config.js +33 -0
- package/dist/orchestrator/plugins/linear/config.js.map +1 -0
- package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
- package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/nx/config.js +28 -0
- package/dist/orchestrator/plugins/nx/config.js.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.js +43 -0
- package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
- package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
- package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/slack/config.js +34 -0
- package/dist/orchestrator/plugins/slack/config.js.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.js +40 -0
- package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.js +33 -0
- package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
- package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
- package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/teams/config.js +43 -0
- package/dist/orchestrator/plugins/teams/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +61 -0
- package/dist/orchestrator/plugins/types.d.ts.map +1 -0
- package/dist/orchestrator/plugins/types.js +2 -0
- package/dist/orchestrator/plugins/types.js.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.js +32 -0
- package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/adapters/claude-code.ts +40 -6
- package/src/cli/adapters/cursor.ts +46 -6
- package/src/cli/adapters/opencode.ts +320 -0
- package/src/cli/adapters/vscode.ts +43 -9
- package/src/cli/copy.ts +32 -0
- package/src/cli/detect.ts +483 -0
- package/src/cli/gitignore.ts +0 -3
- package/src/cli/init.ts +169 -96
- package/src/cli/manifest.ts +2 -1
- package/src/cli/mcp.ts +131 -51
- package/src/cli/prompt.ts +299 -0
- package/src/cli/stack-config.ts +187 -145
- package/src/cli/types.ts +60 -9
- package/src/cli/update.ts +78 -20
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/agent-workflows/README.md +1 -1
- package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
- package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
- package/src/orchestrator/agent-workflows/database-migration.md +11 -11
- package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
- package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
- package/src/orchestrator/agent-workflows/refactoring.md +10 -10
- package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
- package/src/orchestrator/agent-workflows/security-audit.md +12 -12
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
- package/src/orchestrator/agents/api-designer.agent.md +2 -2
- package/src/orchestrator/agents/architect.agent.md +2 -2
- package/src/orchestrator/agents/content-engineer.agent.md +4 -4
- package/src/orchestrator/agents/copywriter.agent.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +6 -6
- package/src/orchestrator/agents/database-engineer.agent.md +4 -4
- package/src/orchestrator/agents/developer.agent.md +5 -5
- package/src/orchestrator/agents/devops-expert.agent.md +5 -5
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +3 -3
- package/src/orchestrator/agents/release-manager.agent.md +4 -4
- package/src/orchestrator/agents/researcher.agent.md +19 -3
- package/src/orchestrator/agents/reviewer.agent.md +2 -4
- package/src/orchestrator/agents/security-expert.agent.md +4 -4
- package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
- package/src/orchestrator/agents/team-lead.agent.md +97 -101
- package/src/orchestrator/agents/testing-expert.agent.md +5 -5
- package/src/orchestrator/agents/ui-ux-expert.agent.md +7 -7
- package/src/orchestrator/copilot-instructions.md +1 -1
- package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
- package/src/orchestrator/customizations/DISPUTES.md +5 -5
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
- package/src/orchestrator/customizations/README.md +5 -2
- package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
- package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
- package/src/orchestrator/customizations/logs/README.md +1 -1
- package/src/orchestrator/customizations/project/decisions.md +31 -0
- package/src/orchestrator/customizations/project/docs-structure.md +16 -5
- package/src/orchestrator/customizations/project/roadmap.md +24 -0
- package/src/orchestrator/customizations/project/tracker-config.md +1 -1
- package/src/orchestrator/customizations/stack/cms-config.md +1 -1
- package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
- package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
- package/src/orchestrator/instructions/general.instructions.md +102 -40
- package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
- package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
- package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
- package/src/orchestrator/plugins/contentful/config.ts +49 -0
- package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
- package/src/orchestrator/plugins/convex/config.ts +33 -0
- package/src/orchestrator/plugins/index.ts +85 -0
- package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
- package/src/orchestrator/plugins/jira/config.ts +30 -0
- package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
- package/src/orchestrator/plugins/linear/config.ts +34 -0
- package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
- package/src/orchestrator/plugins/nx/config.ts +29 -0
- package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
- package/src/orchestrator/plugins/sanity/config.ts +44 -0
- package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
- package/src/orchestrator/plugins/slack/config.ts +35 -0
- package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
- package/src/orchestrator/plugins/strapi/config.ts +41 -0
- package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
- package/src/orchestrator/plugins/supabase/config.ts +34 -0
- package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
- package/src/orchestrator/plugins/teams/config.ts +44 -0
- package/src/orchestrator/plugins/types.ts +79 -0
- package/src/orchestrator/plugins/vercel/config.ts +33 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +59 -12
- package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
- package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
- package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
- package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
- package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
- package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
- package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
- package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/SKILL.md +4 -4
- package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
- package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
- package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
- package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
- package/src/orchestrator/skills/react-development/SKILL.md +3 -3
- package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
- package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
- package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
- package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
- package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
- package/src/orchestrator/mcp.json +0 -61
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { readFile, readdir, access } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import type { RepoInfo, StackConfig } from './types.js';
|
|
5
|
+
|
|
6
|
+
// ── Detection rules ───────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
interface DetectionRule {
|
|
9
|
+
/** Human-readable label stored in repoInfo */
|
|
10
|
+
label: string;
|
|
11
|
+
/** File patterns to check (relative to project root) */
|
|
12
|
+
files: string[];
|
|
13
|
+
/** Optional: glob-style directory check */
|
|
14
|
+
dirs?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PACKAGE_MANAGERS: DetectionRule[] = [
|
|
18
|
+
{ label: 'pnpm', files: ['pnpm-lock.yaml', 'pnpm-workspace.yaml'] },
|
|
19
|
+
{ label: 'yarn', files: ['yarn.lock'] },
|
|
20
|
+
{ label: 'bun', files: ['bun.lockb', 'bun.lock'] },
|
|
21
|
+
{ label: 'npm', files: ['package-lock.json'] },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const MONOREPO_TOOLS: DetectionRule[] = [
|
|
25
|
+
{ label: 'nx', files: ['nx.json'] },
|
|
26
|
+
{ label: 'turborepo', files: ['turbo.json'] },
|
|
27
|
+
{ label: 'lerna', files: ['lerna.json'] },
|
|
28
|
+
{ label: 'pnpm-workspaces', files: ['pnpm-workspace.yaml'] },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const FRAMEWORKS: DetectionRule[] = [
|
|
32
|
+
{ label: 'next', files: ['next.config.js', 'next.config.mjs', 'next.config.ts'] },
|
|
33
|
+
{ label: 'nuxt', files: ['nuxt.config.js', 'nuxt.config.ts'] },
|
|
34
|
+
{ label: 'astro', files: ['astro.config.mjs', 'astro.config.ts', 'astro.config.js'] },
|
|
35
|
+
{ label: 'remix', files: ['remix.config.js', 'remix.config.ts'] },
|
|
36
|
+
{ label: 'sveltekit', files: ['svelte.config.js', 'svelte.config.ts'] },
|
|
37
|
+
{ label: 'vite', files: ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'] },
|
|
38
|
+
{ label: 'angular', files: ['angular.json'] },
|
|
39
|
+
{ label: 'gatsby', files: ['gatsby-config.js', 'gatsby-config.ts'] },
|
|
40
|
+
{ label: 'express', files: [] }, // detected via package.json
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const DATABASES: DetectionRule[] = [
|
|
44
|
+
{ label: 'supabase', files: ['supabase/config.toml'], dirs: ['supabase/'] },
|
|
45
|
+
{ label: 'prisma', files: ['prisma/schema.prisma'] },
|
|
46
|
+
{ label: 'drizzle', files: ['drizzle.config.ts', 'drizzle.config.js'] },
|
|
47
|
+
{ label: 'convex', files: ['convex/_generated'], dirs: ['convex/'] },
|
|
48
|
+
{ label: 'mongoose', files: [] }, // detected via package.json
|
|
49
|
+
{ label: 'typeorm', files: [] }, // detected via package.json
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const CMS_PLATFORMS: DetectionRule[] = [
|
|
53
|
+
{ label: 'sanity', files: ['sanity.config.ts', 'sanity.config.js', 'sanity.config.mjs'] },
|
|
54
|
+
{ label: 'contentful', files: ['.contentful.json', 'contentful.config.js'] },
|
|
55
|
+
{ label: 'strapi', files: [] }, // detected via package.json
|
|
56
|
+
{ label: 'payload', files: ['payload.config.ts', 'payload.config.js'] },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const DEPLOYMENT: DetectionRule[] = [
|
|
60
|
+
{ label: 'vercel', files: ['vercel.json'] },
|
|
61
|
+
{ label: 'netlify', files: ['netlify.toml'] },
|
|
62
|
+
{ label: 'docker', files: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'] },
|
|
63
|
+
{ label: 'railway', files: ['railway.json', 'railway.toml'] },
|
|
64
|
+
{ label: 'fly', files: ['fly.toml'] },
|
|
65
|
+
{ label: 'render', files: ['render.yaml'] },
|
|
66
|
+
{ label: 'aws-cdk', files: ['cdk.json'] },
|
|
67
|
+
{ label: 'terraform', files: [] , dirs: ['terraform/'] },
|
|
68
|
+
{ label: 'pulumi', files: ['Pulumi.yaml'] },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const TESTING: DetectionRule[] = [
|
|
72
|
+
{ label: 'jest', files: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'] },
|
|
73
|
+
{ label: 'vitest', files: ['vitest.config.ts', 'vitest.config.js', 'vitest.config.mjs'] },
|
|
74
|
+
{ label: 'playwright', files: ['playwright.config.ts', 'playwright.config.js'] },
|
|
75
|
+
{ label: 'cypress', files: ['cypress.config.ts', 'cypress.config.js'], dirs: ['cypress/'] },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const CICD: DetectionRule[] = [
|
|
79
|
+
{ label: 'github-actions', files: [], dirs: ['.github/workflows/'] },
|
|
80
|
+
{ label: 'gitlab-ci', files: ['.gitlab-ci.yml'] },
|
|
81
|
+
{ label: 'circleci', files: ['.circleci/config.yml'] },
|
|
82
|
+
{ label: 'jenkins', files: ['Jenkinsfile'] },
|
|
83
|
+
{ label: 'travis', files: ['.travis.yml'] },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const STYLING: DetectionRule[] = [
|
|
87
|
+
{ label: 'tailwind', files: ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.mjs'] },
|
|
88
|
+
{ label: 'sass', files: [] }, // detected via package.json
|
|
89
|
+
{ label: 'styled-components', files: [] }, // detected via package.json
|
|
90
|
+
{ label: 'emotion', files: [] }, // detected via package.json
|
|
91
|
+
{ label: 'css-modules', files: [] }, // detected via file extensions
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const AUTH: DetectionRule[] = [
|
|
95
|
+
{ label: 'next-auth', files: [] }, // detected via package.json
|
|
96
|
+
{ label: 'clerk', files: [] }, // detected via package.json
|
|
97
|
+
{ label: 'auth0', files: [] }, // detected via package.json
|
|
98
|
+
{ label: 'supabase-auth', files: [] }, // detected via supabase presence
|
|
99
|
+
{ label: 'lucia', files: [] }, // detected via package.json
|
|
100
|
+
{ label: 'passport', files: [] }, // detected via package.json
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
// Mapping of npm package names to detection labels
|
|
104
|
+
const PACKAGE_DETECTIONS: Record<string, { category: string; label: string }> = {
|
|
105
|
+
'next': { category: 'frameworks', label: 'next' },
|
|
106
|
+
'nuxt': { category: 'frameworks', label: 'nuxt' },
|
|
107
|
+
'astro': { category: 'frameworks', label: 'astro' },
|
|
108
|
+
'@remix-run/node': { category: 'frameworks', label: 'remix' },
|
|
109
|
+
'@sveltejs/kit': { category: 'frameworks', label: 'sveltekit' },
|
|
110
|
+
'express': { category: 'frameworks', label: 'express' },
|
|
111
|
+
'fastify': { category: 'frameworks', label: 'fastify' },
|
|
112
|
+
'hono': { category: 'frameworks', label: 'hono' },
|
|
113
|
+
'mongoose': { category: 'databases', label: 'mongoose' },
|
|
114
|
+
'typeorm': { category: 'databases', label: 'typeorm' },
|
|
115
|
+
'@supabase/supabase-js': { category: 'databases', label: 'supabase' },
|
|
116
|
+
'@prisma/client': { category: 'databases', label: 'prisma' },
|
|
117
|
+
'drizzle-orm': { category: 'databases', label: 'drizzle' },
|
|
118
|
+
'convex': { category: 'databases', label: 'convex' },
|
|
119
|
+
'sanity': { category: 'cms', label: 'sanity' },
|
|
120
|
+
'contentful': { category: 'cms', label: 'contentful' },
|
|
121
|
+
'@strapi/strapi': { category: 'cms', label: 'strapi' },
|
|
122
|
+
'payload': { category: 'cms', label: 'payload' },
|
|
123
|
+
'next-auth': { category: 'auth', label: 'next-auth' },
|
|
124
|
+
'@auth/core': { category: 'auth', label: 'next-auth' },
|
|
125
|
+
'@clerk/nextjs': { category: 'auth', label: 'clerk' },
|
|
126
|
+
'@clerk/clerk-sdk-node': { category: 'auth', label: 'clerk' },
|
|
127
|
+
'@auth0/nextjs-auth0': { category: 'auth', label: 'auth0' },
|
|
128
|
+
'auth0': { category: 'auth', label: 'auth0' },
|
|
129
|
+
'lucia': { category: 'auth', label: 'lucia' },
|
|
130
|
+
'passport': { category: 'auth', label: 'passport' },
|
|
131
|
+
'sass': { category: 'styling', label: 'sass' },
|
|
132
|
+
'styled-components': { category: 'styling', label: 'styled-components' },
|
|
133
|
+
'@emotion/react': { category: 'styling', label: 'emotion' },
|
|
134
|
+
'@emotion/styled': { category: 'styling', label: 'emotion' },
|
|
135
|
+
'tailwindcss': { category: 'styling', label: 'tailwind' },
|
|
136
|
+
'jest': { category: 'testing', label: 'jest' },
|
|
137
|
+
'vitest': { category: 'testing', label: 'vitest' },
|
|
138
|
+
'@playwright/test': { category: 'testing', label: 'playwright' },
|
|
139
|
+
'cypress': { category: 'testing', label: 'cypress' },
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
145
|
+
try {
|
|
146
|
+
await access(path);
|
|
147
|
+
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function dirExists(path: string): Promise<boolean> {
|
|
154
|
+
try {
|
|
155
|
+
await access(path);
|
|
156
|
+
const entries = await readdir(path);
|
|
157
|
+
return entries.length >= 0; // exists as a directory
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function addUnique(arr: string[], value: string): void {
|
|
164
|
+
if (!arr.includes(value)) arr.push(value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Main detect function ──────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/** Internal type with required arrays for detection phase. */
|
|
170
|
+
interface RepoInfoInternal {
|
|
171
|
+
packageManager?: string;
|
|
172
|
+
monorepo?: string;
|
|
173
|
+
language?: string;
|
|
174
|
+
frameworks: string[];
|
|
175
|
+
databases: string[];
|
|
176
|
+
cms: string[];
|
|
177
|
+
deployment: string[];
|
|
178
|
+
testing: string[];
|
|
179
|
+
cicd: string[];
|
|
180
|
+
styling: string[];
|
|
181
|
+
auth: string[];
|
|
182
|
+
mcpConfig?: boolean;
|
|
183
|
+
configFiles: string[];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Perform repo research: scan the project root for config files,
|
|
188
|
+
* package.json dependencies, and directory structures to detect
|
|
189
|
+
* the project's tooling and tech stack.
|
|
190
|
+
*/
|
|
191
|
+
export async function detectRepoInfo(projectRoot: string): Promise<RepoInfo> {
|
|
192
|
+
const info: RepoInfoInternal = {
|
|
193
|
+
frameworks: [],
|
|
194
|
+
databases: [],
|
|
195
|
+
cms: [],
|
|
196
|
+
deployment: [],
|
|
197
|
+
testing: [],
|
|
198
|
+
cicd: [],
|
|
199
|
+
styling: [],
|
|
200
|
+
auth: [],
|
|
201
|
+
configFiles: [],
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// ── 1. Detect package manager ───────────────────────────────
|
|
205
|
+
for (const pm of PACKAGE_MANAGERS) {
|
|
206
|
+
const found = await checkFiles(projectRoot, pm.files);
|
|
207
|
+
if (found.length > 0) {
|
|
208
|
+
info.packageManager = pm.label;
|
|
209
|
+
info.configFiles.push(...found);
|
|
210
|
+
break; // first match wins (order = priority)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── 2. Detect monorepo tool ─────────────────────────────────
|
|
215
|
+
for (const tool of MONOREPO_TOOLS) {
|
|
216
|
+
const found = await checkFiles(projectRoot, tool.files);
|
|
217
|
+
if (found.length > 0) {
|
|
218
|
+
info.monorepo = tool.label;
|
|
219
|
+
info.configFiles.push(...found);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── 3. Detect by config files ───────────────────────────────
|
|
225
|
+
await detectCategory(projectRoot, FRAMEWORKS, info, 'frameworks');
|
|
226
|
+
await detectCategory(projectRoot, DATABASES, info, 'databases');
|
|
227
|
+
await detectCategory(projectRoot, CMS_PLATFORMS, info, 'cms');
|
|
228
|
+
await detectCategory(projectRoot, DEPLOYMENT, info, 'deployment');
|
|
229
|
+
await detectCategory(projectRoot, TESTING, info, 'testing');
|
|
230
|
+
await detectCategory(projectRoot, CICD, info, 'cicd');
|
|
231
|
+
await detectCategory(projectRoot, STYLING, info, 'styling');
|
|
232
|
+
await detectCategory(projectRoot, AUTH, info, 'auth');
|
|
233
|
+
|
|
234
|
+
// ── 4. Detect from package.json deps ────────────────────────
|
|
235
|
+
await detectFromPackageJson(projectRoot, info);
|
|
236
|
+
|
|
237
|
+
// ── 5. Detect MCP config ────────────────────────────────────
|
|
238
|
+
const mcpPaths = [
|
|
239
|
+
'.vscode/mcp.json',
|
|
240
|
+
'.cursor/mcp.json',
|
|
241
|
+
'.claude/mcp.json',
|
|
242
|
+
'mcp.json',
|
|
243
|
+
];
|
|
244
|
+
for (const p of mcpPaths) {
|
|
245
|
+
if (await fileExists(resolve(projectRoot, p))) {
|
|
246
|
+
info.mcpConfig = true;
|
|
247
|
+
info.configFiles.push(p);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── 6. Check for TypeScript ─────────────────────────────────
|
|
252
|
+
const tsConfigPath = resolve(projectRoot, 'tsconfig.json');
|
|
253
|
+
if (await fileExists(tsConfigPath)) {
|
|
254
|
+
info.language = 'typescript';
|
|
255
|
+
info.configFiles.push('tsconfig.json');
|
|
256
|
+
} else {
|
|
257
|
+
const jsConfigPath = resolve(projectRoot, 'jsconfig.json');
|
|
258
|
+
if (await fileExists(jsConfigPath)) {
|
|
259
|
+
info.language = 'javascript';
|
|
260
|
+
info.configFiles.push('jsconfig.json');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── 7. Detect CSS modules via src scan ──────────────────────
|
|
265
|
+
if (!info.styling.includes('css-modules')) {
|
|
266
|
+
const hasCssModules = await scanForPattern(projectRoot, /\.module\.(css|scss|sass)$/);
|
|
267
|
+
if (hasCssModules) {
|
|
268
|
+
addUnique(info.styling, 'css-modules');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── 8. Detect supabase-auth if supabase is present ──────────
|
|
273
|
+
if (info.databases.includes('supabase') && !info.auth.includes('supabase-auth')) {
|
|
274
|
+
addUnique(info.auth, 'supabase-auth');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Deduplicate configFiles
|
|
278
|
+
info.configFiles = [...new Set(info.configFiles)];
|
|
279
|
+
|
|
280
|
+
// Sort arrays for stable output
|
|
281
|
+
for (const key of ['frameworks', 'databases', 'cms', 'deployment', 'testing', 'cicd', 'styling', 'auth', 'configFiles'] as const) {
|
|
282
|
+
info[key].sort();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Strip empty arrays for cleaner JSON and return as RepoInfo
|
|
286
|
+
return cleanEmpty(info);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Internal helpers ──────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
async function checkFiles(root: string, files: string[]): Promise<string[]> {
|
|
292
|
+
const found: string[] = [];
|
|
293
|
+
for (const f of files) {
|
|
294
|
+
if (await fileExists(resolve(root, f))) {
|
|
295
|
+
found.push(f);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return found;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
type CategoryKey = 'frameworks' | 'databases' | 'cms' | 'deployment' | 'testing' | 'cicd' | 'styling' | 'auth';
|
|
302
|
+
|
|
303
|
+
async function detectCategory(
|
|
304
|
+
root: string,
|
|
305
|
+
rules: DetectionRule[],
|
|
306
|
+
info: RepoInfoInternal,
|
|
307
|
+
category: CategoryKey,
|
|
308
|
+
): Promise<void> {
|
|
309
|
+
for (const rule of rules) {
|
|
310
|
+
if (rule.files.length === 0 && !rule.dirs?.length) continue; // package.json-only detection
|
|
311
|
+
|
|
312
|
+
const foundFiles = await checkFiles(root, rule.files);
|
|
313
|
+
let foundDir = false;
|
|
314
|
+
if (rule.dirs) {
|
|
315
|
+
for (const d of rule.dirs) {
|
|
316
|
+
if (await dirExists(resolve(root, d))) {
|
|
317
|
+
foundDir = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (foundFiles.length > 0 || foundDir) {
|
|
324
|
+
addUnique(info[category], rule.label);
|
|
325
|
+
info.configFiles.push(...foundFiles);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function detectFromPackageJson(root: string, info: RepoInfoInternal): Promise<void> {
|
|
331
|
+
const pkgPath = resolve(root, 'package.json');
|
|
332
|
+
if (!await fileExists(pkgPath)) return;
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const content = await readFile(pkgPath, 'utf8');
|
|
336
|
+
const pkg = JSON.parse(content) as {
|
|
337
|
+
dependencies?: Record<string, string>;
|
|
338
|
+
devDependencies?: Record<string, string>;
|
|
339
|
+
packageManager?: string;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Detect from dependencies
|
|
343
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
344
|
+
for (const [pkgName, detection] of Object.entries(PACKAGE_DETECTIONS)) {
|
|
345
|
+
if (pkgName in allDeps) {
|
|
346
|
+
addUnique(info[detection.category as CategoryKey], detection.label);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Detect package manager from packageManager field (corepack)
|
|
351
|
+
if (pkg.packageManager && !info.packageManager) {
|
|
352
|
+
const pm = pkg.packageManager.split('@')[0];
|
|
353
|
+
if (['pnpm', 'yarn', 'bun', 'npm'].includes(pm)) {
|
|
354
|
+
info.packageManager = pm;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Track package.json itself
|
|
359
|
+
if (!info.configFiles.includes('package.json')) {
|
|
360
|
+
info.configFiles.push('package.json');
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// Malformed package.json — skip silently
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Quick scan of src/ directory (1 level deep) for file name patterns.
|
|
369
|
+
* Used to detect CSS modules without walking the entire tree.
|
|
370
|
+
*/
|
|
371
|
+
async function scanForPattern(root: string, pattern: RegExp): Promise<boolean> {
|
|
372
|
+
const srcDir = resolve(root, 'src');
|
|
373
|
+
if (!existsSync(srcDir)) return false;
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const queue = [srcDir];
|
|
377
|
+
let depth = 0;
|
|
378
|
+
const maxDepth = 3;
|
|
379
|
+
|
|
380
|
+
while (queue.length > 0 && depth < maxDepth) {
|
|
381
|
+
const nextQueue: string[] = [];
|
|
382
|
+
for (const dir of queue) {
|
|
383
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
384
|
+
for (const entry of entries) {
|
|
385
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
386
|
+
if (entry.isFile() && pattern.test(entry.name)) return true;
|
|
387
|
+
if (entry.isDirectory()) nextQueue.push(resolve(dir, entry.name));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
queue.length = 0;
|
|
391
|
+
queue.push(...nextQueue);
|
|
392
|
+
depth++;
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
// Permission or read error — skip
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Remove empty arrays and undefined values, returning a clean RepoInfo.
|
|
403
|
+
*/
|
|
404
|
+
function cleanEmpty(info: RepoInfoInternal): RepoInfo {
|
|
405
|
+
const result: RepoInfo = {};
|
|
406
|
+
|
|
407
|
+
if (info.packageManager) result.packageManager = info.packageManager;
|
|
408
|
+
if (info.monorepo) result.monorepo = info.monorepo;
|
|
409
|
+
if (info.language) result.language = info.language;
|
|
410
|
+
if (info.mcpConfig) result.mcpConfig = info.mcpConfig;
|
|
411
|
+
if (info.frameworks.length > 0) result.frameworks = info.frameworks;
|
|
412
|
+
if (info.databases.length > 0) result.databases = info.databases;
|
|
413
|
+
if (info.cms.length > 0) result.cms = info.cms;
|
|
414
|
+
if (info.deployment.length > 0) result.deployment = info.deployment;
|
|
415
|
+
if (info.testing.length > 0) result.testing = info.testing;
|
|
416
|
+
if (info.cicd.length > 0) result.cicd = info.cicd;
|
|
417
|
+
if (info.styling.length > 0) result.styling = info.styling;
|
|
418
|
+
if (info.auth.length > 0) result.auth = info.auth;
|
|
419
|
+
if (info.configFiles.length > 0) result.configFiles = info.configFiles;
|
|
420
|
+
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Merge user-declared stack choices into the auto-detected repoInfo.
|
|
426
|
+
* Adds tech tools and team tools from the questionnaire so
|
|
427
|
+
* repoInfo becomes the single combined source of truth.
|
|
428
|
+
*/
|
|
429
|
+
export function mergeStackIntoRepoInfo(info: RepoInfo, stack: StackConfig): RepoInfo {
|
|
430
|
+
const merged = { ...info };
|
|
431
|
+
|
|
432
|
+
for (const tool of stack.techTools) {
|
|
433
|
+
if (['sanity', 'contentful', 'strapi'].includes(tool)) {
|
|
434
|
+
merged.cms = addUniqueToArray(merged.cms, tool);
|
|
435
|
+
} else if (['supabase', 'convex'].includes(tool)) {
|
|
436
|
+
merged.databases = addUniqueToArray(merged.databases, tool);
|
|
437
|
+
} else if (tool === 'vercel') {
|
|
438
|
+
merged.deployment = addUniqueToArray(merged.deployment, tool);
|
|
439
|
+
} else if (tool === 'nx') {
|
|
440
|
+
merged.monorepo = merged.monorepo ?? 'nx';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
for (const tool of stack.teamTools) {
|
|
445
|
+
if (['linear', 'jira'].includes(tool)) {
|
|
446
|
+
merged.pm = addUniqueToArray(merged.pm, tool);
|
|
447
|
+
} else if (['slack', 'teams'].includes(tool)) {
|
|
448
|
+
merged.notifications = addUniqueToArray(merged.notifications, tool);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return merged;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function addUniqueToArray(arr: string[] | undefined, value: string): string[] {
|
|
456
|
+
const result = arr ? [...arr] : [];
|
|
457
|
+
if (!result.includes(value)) result.push(value);
|
|
458
|
+
return result.sort();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Format the detected repo info for console display.
|
|
463
|
+
*/
|
|
464
|
+
export function formatRepoInfo(info: RepoInfo): string {
|
|
465
|
+
const lines: string[] = [];
|
|
466
|
+
|
|
467
|
+
if (info.packageManager) lines.push(`Package manager: ${info.packageManager}`);
|
|
468
|
+
if (info.monorepo) lines.push(`Monorepo: ${info.monorepo}`);
|
|
469
|
+
if (info.language) lines.push(`Language: ${info.language}`);
|
|
470
|
+
if (info.frameworks?.length) lines.push(`Frameworks: ${info.frameworks.join(', ')}`);
|
|
471
|
+
if (info.databases?.length) lines.push(`Databases: ${info.databases.join(', ')}`);
|
|
472
|
+
if (info.cms?.length) lines.push(`CMS: ${info.cms.join(', ')}`);
|
|
473
|
+
if (info.auth?.length) lines.push(`Auth: ${info.auth.join(', ')}`);
|
|
474
|
+
if (info.pm?.length) lines.push(`Project management: ${info.pm.join(', ')}`);
|
|
475
|
+
if (info.notifications?.length) lines.push(`Notifications: ${info.notifications.join(', ')}`);
|
|
476
|
+
if (info.deployment?.length) lines.push(`Deployment: ${info.deployment.join(', ')}`);
|
|
477
|
+
if (info.testing?.length) lines.push(`Testing: ${info.testing.join(', ')}`);
|
|
478
|
+
if (info.cicd?.length) lines.push(`CI/CD: ${info.cicd.join(', ')}`);
|
|
479
|
+
if (info.styling?.length) lines.push(`Styling: ${info.styling.join(', ')}`);
|
|
480
|
+
if (info.mcpConfig) lines.push(`MCP config: found`);
|
|
481
|
+
|
|
482
|
+
return lines.map(l => ` ${l}`).join('\n');
|
|
483
|
+
}
|
package/src/cli/gitignore.ts
CHANGED