@grvmithas/create-aies-kit 1.3.0 → 2.3.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/CHANGELOG.md +96 -0
- package/README.md +82 -29
- package/bin/create-aies-kit.js +143 -41
- package/lib/audit.js +367 -0
- package/lib/kit-core/apply.js +143 -0
- package/lib/kit-core/embedded-corpus.js +79 -67
- package/lib/kit-core/evals.js +256 -0
- package/lib/kit-core/generate.js +984 -412
- package/lib/kit-core/install-api.js +154 -106
- package/lib/kit-core/mcp-catalog.js +95 -40
- package/lib/kit-core/options.js +20 -3
- package/lib/kit-core/repo-scan.js +158 -0
- package/package.json +12 -3
|
@@ -1,114 +1,164 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-safe kit helpers — no node:fs, safe for Next.js client components.
|
|
3
3
|
*
|
|
4
|
-
* Layout contract:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
4
|
+
* Layout contract (kit 2.x):
|
|
5
|
+
* - Cross-tool canon, always written: AGENTS.md, .agents/skills/<name>/SKILL.md, plans/, reviews/, hooks/
|
|
6
|
+
* - Primary-IDE native files: subagents, commands, hook wiring, MCP config — each in the exact path
|
|
7
|
+
* and format that IDE loads. Nothing is written to a path the target does not read.
|
|
8
|
+
* - Repo-root shared dirs: cookbook, guides, mcps, templates, policy (never nested under an IDE folder)
|
|
7
9
|
*/
|
|
8
|
-
import { SITE_URL } from './options.js';
|
|
10
|
+
import { SITE_URL, normalizeIde } from './options.js';
|
|
9
11
|
|
|
10
|
-
/** Shared repo-root paths — identical for every IDE. */
|
|
11
12
|
const SHARED = {
|
|
12
13
|
cookbookBase: 'cookbook',
|
|
13
14
|
guidesDir: 'guides',
|
|
14
15
|
mcpsDir: 'mcps',
|
|
15
16
|
templatesDir: 'templates',
|
|
16
17
|
policyFile: 'MODEL_POLICY.md',
|
|
17
|
-
|
|
18
|
+
skillsDir: '.agents/skills',
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Per-target emitter table. Formats are interpreted by generate.js renderers.
|
|
23
|
+
* @type {Record<string, object>}
|
|
24
|
+
*/
|
|
25
|
+
const TARGETS = {
|
|
26
|
+
cursor: {
|
|
27
|
+
root: '.cursor',
|
|
28
|
+
rulesFile: '.cursor/rules/aies.mdc',
|
|
29
|
+
commandsDir: '.cursor/commands',
|
|
30
|
+
commandFormat: 'plain-md',
|
|
31
|
+
agentsDir: '.cursor/agents',
|
|
32
|
+
agentFormat: 'cursor',
|
|
33
|
+
skillMirrorDir: null,
|
|
34
|
+
hooksFile: '.cursor/hooks.json',
|
|
35
|
+
hookFormat: 'cursor',
|
|
36
|
+
mcpFile: '.cursor/mcp.json',
|
|
37
|
+
mcpFormat: 'cursor',
|
|
38
|
+
},
|
|
39
|
+
'claude-code': {
|
|
40
|
+
root: '.claude',
|
|
41
|
+
rulesFile: 'CLAUDE.md',
|
|
42
|
+
commandsDir: '.claude/commands',
|
|
43
|
+
commandFormat: 'described-md',
|
|
44
|
+
agentsDir: '.claude/agents',
|
|
45
|
+
agentFormat: 'claude',
|
|
46
|
+
skillMirrorDir: '.claude/skills',
|
|
47
|
+
hooksFile: '.claude/settings.json',
|
|
48
|
+
hookFormat: 'claude',
|
|
49
|
+
mcpFile: '.mcp.json',
|
|
50
|
+
mcpFormat: 'portable',
|
|
51
|
+
},
|
|
52
|
+
copilot: {
|
|
53
|
+
root: '.github',
|
|
54
|
+
rulesFile: '.github/copilot-instructions.md',
|
|
55
|
+
commandsDir: '.github/prompts',
|
|
56
|
+
commandFormat: 'copilot-prompt',
|
|
57
|
+
agentsDir: '.github/agents',
|
|
58
|
+
agentFormat: 'copilot',
|
|
59
|
+
skillMirrorDir: null,
|
|
60
|
+
hooksFile: '.github/hooks/aies-guard.json',
|
|
61
|
+
hookFormat: 'copilot',
|
|
62
|
+
mcpFile: '.mcp.json',
|
|
63
|
+
mcpFormat: 'portable',
|
|
64
|
+
},
|
|
65
|
+
codex: {
|
|
66
|
+
root: '.codex',
|
|
67
|
+
rulesFile: null,
|
|
68
|
+
commandsDir: 'prompts',
|
|
69
|
+
commandFormat: 'plain-md',
|
|
70
|
+
agentsDir: '.codex/agents',
|
|
71
|
+
agentFormat: 'codex',
|
|
72
|
+
skillMirrorDir: null,
|
|
73
|
+
hooksFile: '.codex/hooks.json',
|
|
74
|
+
hookFormat: 'codex',
|
|
75
|
+
mcpFile: '.codex/config.toml',
|
|
76
|
+
mcpFormat: 'codex',
|
|
77
|
+
},
|
|
78
|
+
'gemini-cli': {
|
|
79
|
+
root: '.gemini',
|
|
80
|
+
rulesFile: null,
|
|
81
|
+
commandsDir: '.gemini/commands/aies',
|
|
82
|
+
commandFormat: 'gemini-toml',
|
|
83
|
+
agentsDir: '.gemini/agents',
|
|
84
|
+
agentFormat: 'gemini',
|
|
85
|
+
skillMirrorDir: null,
|
|
86
|
+
hooksFile: '.gemini/settings.json',
|
|
87
|
+
hookFormat: 'gemini',
|
|
88
|
+
mcpFile: '.gemini/settings.json',
|
|
89
|
+
mcpFormat: 'gemini',
|
|
90
|
+
},
|
|
91
|
+
kiro: {
|
|
92
|
+
root: '.kiro',
|
|
93
|
+
rulesFile: '.kiro/steering/aies.md',
|
|
94
|
+
commandsDir: '.kiro/steering',
|
|
95
|
+
commandFormat: 'kiro-steering',
|
|
96
|
+
agentsDir: null,
|
|
97
|
+
agentFormat: null,
|
|
98
|
+
skillMirrorDir: '.kiro/skills',
|
|
99
|
+
hooksFile: '.kiro/hooks/aies-guard.json',
|
|
100
|
+
hookFormat: 'kiro',
|
|
101
|
+
mcpFile: '.kiro/settings/mcp.json',
|
|
102
|
+
mcpFormat: 'cursor',
|
|
103
|
+
},
|
|
104
|
+
windsurf: {
|
|
105
|
+
root: '.devin',
|
|
106
|
+
rulesFile: '.devin/rules/aies.md',
|
|
107
|
+
commandsDir: '.devin/skills',
|
|
108
|
+
commandFormat: 'skill',
|
|
109
|
+
agentsDir: null,
|
|
110
|
+
agentFormat: null,
|
|
111
|
+
skillMirrorDir: null,
|
|
112
|
+
hooksFile: '.devin/hooks.v1.json',
|
|
113
|
+
hookFormat: 'devin',
|
|
114
|
+
mcpFile: '.devin/mcp_config.json',
|
|
115
|
+
mcpFormat: 'portable',
|
|
116
|
+
},
|
|
117
|
+
opencode: {
|
|
118
|
+
root: '.opencode',
|
|
119
|
+
rulesFile: null,
|
|
120
|
+
commandsDir: '.opencode/commands',
|
|
121
|
+
commandFormat: 'described-md',
|
|
122
|
+
agentsDir: '.opencode/agents',
|
|
123
|
+
agentFormat: 'opencode',
|
|
124
|
+
skillMirrorDir: null,
|
|
125
|
+
hooksFile: '.opencode/plugins/aies-guard.js',
|
|
126
|
+
hookFormat: 'opencode',
|
|
127
|
+
mcpFile: 'opencode.json',
|
|
128
|
+
mcpFormat: 'opencode',
|
|
129
|
+
},
|
|
130
|
+
continue: {
|
|
131
|
+
root: '.continue',
|
|
132
|
+
rulesFile: '.continue/rules/aies.md',
|
|
133
|
+
commandsDir: '.continue/prompts',
|
|
134
|
+
commandFormat: 'continue-prompt',
|
|
135
|
+
agentsDir: null,
|
|
136
|
+
agentFormat: null,
|
|
137
|
+
skillMirrorDir: null,
|
|
138
|
+
hooksFile: null,
|
|
139
|
+
hookFormat: null,
|
|
140
|
+
mcpFile: '.continue/mcpServers/aies.json',
|
|
141
|
+
mcpFormat: 'portable',
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const SUPPORTED_TARGETS = Object.keys(TARGETS);
|
|
146
|
+
|
|
20
147
|
/**
|
|
21
148
|
* @param {string} ide
|
|
22
149
|
* @param {{ cookbookId?: string | null }} [opts]
|
|
23
150
|
*/
|
|
24
151
|
export function layoutFor(ide, opts = {}) {
|
|
152
|
+
const id = normalizeIde(ide);
|
|
153
|
+
const target = TARGETS[id] ?? TARGETS.copilot;
|
|
25
154
|
const cookbookId = opts.cookbookId ?? null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cookbookDir,
|
|
34
|
-
root: '.cursor',
|
|
35
|
-
rulesFile: '.cursor/rules/aies.mdc',
|
|
36
|
-
skillsDir: '.cursor/skills',
|
|
37
|
-
skillStyle: 'cursor-skill',
|
|
38
|
-
promptsDir: '.cursor/prompts',
|
|
39
|
-
agentsDir: '.cursor/agents',
|
|
40
|
-
mcpFile: '.cursor/mcp.json',
|
|
41
|
-
hooksFile: '.cursor/aies-hooks.json',
|
|
42
|
-
};
|
|
43
|
-
case 'copilot':
|
|
44
|
-
return {
|
|
45
|
-
ide,
|
|
46
|
-
...SHARED,
|
|
47
|
-
cookbookDir,
|
|
48
|
-
root: '.github',
|
|
49
|
-
rulesFile: '.github/copilot-instructions.md',
|
|
50
|
-
skillsDir: '.github/skills',
|
|
51
|
-
skillStyle: 'flat',
|
|
52
|
-
promptsDir: '.github/prompts',
|
|
53
|
-
agentsDir: '.github/agents',
|
|
54
|
-
// Copilot discovery only under .github — MCP config at repo root
|
|
55
|
-
mcpFile: '.mcp.json',
|
|
56
|
-
};
|
|
57
|
-
case 'claude-code':
|
|
58
|
-
return {
|
|
59
|
-
ide,
|
|
60
|
-
...SHARED,
|
|
61
|
-
cookbookDir,
|
|
62
|
-
root: '.claude',
|
|
63
|
-
rulesFile: 'CLAUDE.md',
|
|
64
|
-
skillsDir: '.claude/skills',
|
|
65
|
-
skillStyle: 'cursor-skill',
|
|
66
|
-
promptsDir: '.claude/prompts',
|
|
67
|
-
agentsDir: '.claude/agents',
|
|
68
|
-
mcpFile: '.mcp.json',
|
|
69
|
-
};
|
|
70
|
-
case 'vscode':
|
|
71
|
-
return {
|
|
72
|
-
ide,
|
|
73
|
-
...SHARED,
|
|
74
|
-
cookbookDir,
|
|
75
|
-
root: '.vscode',
|
|
76
|
-
rulesFile: '.vscode/aies-instructions.md',
|
|
77
|
-
skillsDir: '.vscode/skills',
|
|
78
|
-
skillStyle: 'flat',
|
|
79
|
-
promptsDir: '.vscode/prompts',
|
|
80
|
-
agentsDir: '.vscode/agents',
|
|
81
|
-
mcpFile: '.vscode/mcp.json',
|
|
82
|
-
};
|
|
83
|
-
case 'windsurf':
|
|
84
|
-
return {
|
|
85
|
-
ide,
|
|
86
|
-
...SHARED,
|
|
87
|
-
cookbookDir,
|
|
88
|
-
root: '.windsurf',
|
|
89
|
-
rulesFile: '.windsurfrules',
|
|
90
|
-
skillsDir: '.windsurf/skills',
|
|
91
|
-
skillStyle: 'flat',
|
|
92
|
-
promptsDir: '.windsurf/prompts',
|
|
93
|
-
agentsDir: '.windsurf/agents',
|
|
94
|
-
mcpFile: '.windsurf/mcp.json',
|
|
95
|
-
};
|
|
96
|
-
case 'continue':
|
|
97
|
-
return {
|
|
98
|
-
ide,
|
|
99
|
-
...SHARED,
|
|
100
|
-
cookbookDir,
|
|
101
|
-
root: '.continue',
|
|
102
|
-
rulesFile: '.continue/aies-context.md',
|
|
103
|
-
skillsDir: '.continue/skills',
|
|
104
|
-
skillStyle: 'flat',
|
|
105
|
-
promptsDir: '.continue/prompts',
|
|
106
|
-
agentsDir: '.continue/agents',
|
|
107
|
-
mcpFile: '.continue/mcp.json',
|
|
108
|
-
};
|
|
109
|
-
default:
|
|
110
|
-
return layoutFor('vscode', opts);
|
|
111
|
-
}
|
|
155
|
+
return {
|
|
156
|
+
ide: TARGETS[id] ? id : 'copilot',
|
|
157
|
+
...SHARED,
|
|
158
|
+
...target,
|
|
159
|
+
promptsDir: target.commandsDir,
|
|
160
|
+
cookbookDir: cookbookId ? `${SHARED.cookbookBase}/${cookbookId}` : SHARED.cookbookBase,
|
|
161
|
+
};
|
|
112
162
|
}
|
|
113
163
|
|
|
114
164
|
export function describeInstallLayout(ide, opts = {}) {
|
|
@@ -116,34 +166,32 @@ export function describeInstallLayout(ide, opts = {}) {
|
|
|
116
166
|
return {
|
|
117
167
|
rootFiles: [
|
|
118
168
|
'AIES.md',
|
|
119
|
-
'implementation_plan.md',
|
|
120
|
-
'CLAUDE.md',
|
|
121
169
|
'AGENTS.md',
|
|
122
|
-
'
|
|
170
|
+
'implementation_plan.md',
|
|
123
171
|
'MODEL_POLICY.md',
|
|
124
|
-
'
|
|
172
|
+
...(L.ide === 'claude-code' ? ['CLAUDE.md'] : []),
|
|
125
173
|
],
|
|
126
174
|
ideRoot: L.root,
|
|
127
|
-
ideOnly: [L.
|
|
128
|
-
sharedRoot: [L.cookbookDir, L.guidesDir, L.mcpsDir, L.templatesDir],
|
|
129
|
-
rulesFile: L.rulesFile,
|
|
175
|
+
ideOnly: [L.commandsDir, L.agentsDir, L.skillMirrorDir].filter(Boolean),
|
|
176
|
+
sharedRoot: [L.skillsDir, L.cookbookDir, L.guidesDir, L.mcpsDir, L.templatesDir, 'plans', 'reviews', 'hooks'],
|
|
177
|
+
rulesFile: L.rulesFile ?? 'AGENTS.md',
|
|
130
178
|
skillsDir: L.skillsDir,
|
|
131
|
-
promptsDir: L.
|
|
179
|
+
promptsDir: L.commandsDir,
|
|
132
180
|
cookbookDir: L.cookbookDir,
|
|
133
181
|
guidesDir: L.guidesDir,
|
|
134
|
-
agentsDir: L.agentsDir,
|
|
182
|
+
agentsDir: L.agentsDir ?? '(AGENTS.md roles — target has no project subagents)',
|
|
135
183
|
mcpsDir: L.mcpsDir,
|
|
136
184
|
templatesDir: L.templatesDir,
|
|
137
185
|
mcpFile: L.mcpFile,
|
|
138
186
|
policyFile: L.policyFile,
|
|
139
|
-
hooksFile: L.hooksFile,
|
|
187
|
+
hooksFile: L.hooksFile ?? 'hooks/ (CI evidence gates — target has no native hooks)',
|
|
140
188
|
};
|
|
141
189
|
}
|
|
142
190
|
|
|
143
191
|
export function buildInstallQuery(profile) {
|
|
144
192
|
return new URLSearchParams({
|
|
145
193
|
stack: profile.stack,
|
|
146
|
-
ide: profile.ide,
|
|
194
|
+
ide: normalizeIde(profile.ide),
|
|
147
195
|
role: profile.role,
|
|
148
196
|
maturity: profile.maturity,
|
|
149
197
|
goal: profile.goal,
|
|
@@ -156,7 +204,7 @@ export function getInstallCommands(profile) {
|
|
|
156
204
|
const q = buildInstallQuery(profile);
|
|
157
205
|
const flags = [
|
|
158
206
|
`--stack ${profile.stack}`,
|
|
159
|
-
`--ide ${profile.ide}`,
|
|
207
|
+
`--ide ${normalizeIde(profile.ide)}`,
|
|
160
208
|
`--role ${profile.role}`,
|
|
161
209
|
`--maturity ${profile.maturity}`,
|
|
162
210
|
`--goal ${profile.goal}`,
|
|
@@ -165,8 +213,8 @@ export function getInstallCommands(profile) {
|
|
|
165
213
|
'--yes',
|
|
166
214
|
].join(' ');
|
|
167
215
|
return {
|
|
168
|
-
npx: `npx @grvmithas/create-aies-kit ${flags}`,
|
|
169
|
-
enhance: 'npx @grvmithas/create-aies-kit enhance',
|
|
216
|
+
npx: `npx @grvmithas/create-aies-kit@latest ${flags}`,
|
|
217
|
+
enhance: 'npx @grvmithas/create-aies-kit@latest enhance',
|
|
170
218
|
powershell: `irm "${SITE_URL}/api/kit?${q}&shell=ps1" | iex`,
|
|
171
219
|
bash: `curl -fsSL "${SITE_URL}/api/kit?${q}&shell=bash" | bash`,
|
|
172
220
|
};
|
|
@@ -31,54 +31,107 @@ export const MCP_SPECS = [
|
|
|
31
31
|
{ id: 'discord', category: 'communication', file: 'communication/discord.mcp.md', label: 'Discord' },
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Enable-ready server definitions (MCP 2026-07-28 era).
|
|
36
|
+
* Prefer the vendor-hosted Streamable HTTP endpoint with OAuth 2.1 — no long-lived token in the repo.
|
|
37
|
+
* stdio entries remain only where no vendor remote exists. Reference servers archived by the MCP
|
|
38
|
+
* project (server-github, server-postgres, server-redis, server-slack) are intentionally absent.
|
|
39
|
+
* Endpoints verified 2026-11-06; re-verify in the quarterly vendor review.
|
|
40
|
+
*/
|
|
41
|
+
export const MCP_SERVERS = {
|
|
36
42
|
github: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
filesystem: {
|
|
42
|
-
command: 'npx',
|
|
43
|
-
args: ['-y', '@modelcontextprotocol/server-filesystem', '${WORKSPACE_ROOT}'],
|
|
44
|
-
},
|
|
45
|
-
docker: {
|
|
46
|
-
command: 'npx',
|
|
47
|
-
args: ['-y', '@modelcontextprotocol/server-docker'],
|
|
48
|
-
},
|
|
49
|
-
postgresql: {
|
|
50
|
-
command: 'npx',
|
|
51
|
-
args: ['-y', '@modelcontextprotocol/server-postgres'],
|
|
52
|
-
env: { POSTGRES_CONNECTION_STRING: '${DATABASE_URL}' },
|
|
53
|
-
},
|
|
54
|
-
redis: {
|
|
55
|
-
command: 'npx',
|
|
56
|
-
args: ['-y', '@modelcontextprotocol/server-redis'],
|
|
57
|
-
env: { REDIS_URL: '${REDIS_URL}' },
|
|
58
|
-
},
|
|
59
|
-
playwright: {
|
|
60
|
-
command: 'npx',
|
|
61
|
-
args: ['-y', '@playwright/mcp@latest'],
|
|
43
|
+
transport: 'http',
|
|
44
|
+
url: 'https://api.githubcopilot.com/mcp/readonly',
|
|
45
|
+
auth: 'oauth',
|
|
46
|
+
note: 'Read-only endpoint. Drop /readonly only after approval; narrow toolsets with the X-MCP-Toolsets header.',
|
|
62
47
|
},
|
|
48
|
+
gitlab: { transport: 'http', url: 'https://gitlab.com/api/v4/mcp', auth: 'oauth' },
|
|
63
49
|
sentry: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
transport: 'http',
|
|
51
|
+
url: 'https://mcp.sentry.dev/mcp',
|
|
52
|
+
auth: 'oauth',
|
|
53
|
+
note: 'Scope by URL: /mcp/{org}/{project}.',
|
|
67
54
|
},
|
|
68
|
-
linear: {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
linear: { transport: 'http', url: 'https://mcp.linear.app/mcp/readonly', auth: 'oauth' },
|
|
56
|
+
notion: { transport: 'http', url: 'https://mcp.notion.com/mcp', auth: 'oauth' },
|
|
57
|
+
jira: {
|
|
58
|
+
transport: 'http',
|
|
59
|
+
url: 'https://mcp.atlassian.com/v2/mcp',
|
|
60
|
+
auth: 'oauth',
|
|
61
|
+
note: 'Atlassian Rovo MCP (Jira + Confluence); honors the signed-in user\'s permissions.',
|
|
72
62
|
},
|
|
73
|
-
|
|
63
|
+
asana: { transport: 'http', url: 'https://mcp.asana.com/v2/mcp', auth: 'oauth' },
|
|
64
|
+
supabase: {
|
|
65
|
+
transport: 'http',
|
|
66
|
+
url: 'https://mcp.supabase.com/mcp?read_only=true',
|
|
67
|
+
auth: 'oauth',
|
|
68
|
+
note: 'Add &project_ref=<ref> to pin one project.',
|
|
69
|
+
},
|
|
70
|
+
playwright: { transport: 'stdio', command: 'npx', args: ['-y', '@playwright/mcp@latest'] },
|
|
71
|
+
'chrome-devtools': { transport: 'stdio', command: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'] },
|
|
72
|
+
filesystem: {
|
|
73
|
+
transport: 'stdio',
|
|
74
74
|
command: 'npx',
|
|
75
|
-
args: ['-y', '@modelcontextprotocol/server-
|
|
76
|
-
|
|
75
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
|
76
|
+
note: 'Usually redundant: coding agents already have scoped file tools.',
|
|
77
77
|
},
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/** @deprecated kept for 1.x consumers; use MCP_SERVERS. */
|
|
81
|
+
export const MCP_SERVER_STUBS = MCP_SERVERS;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Render enabled servers for one client config format.
|
|
85
|
+
* @param {'portable'|'cursor'|'gemini'|'opencode'|'vscode'} format
|
|
86
|
+
* @param {string[]} ids
|
|
87
|
+
*/
|
|
88
|
+
export function renderMcpServers(format, ids) {
|
|
89
|
+
/** @type {Record<string, object>} */
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const id of ids) {
|
|
92
|
+
const s = MCP_SERVERS[id];
|
|
93
|
+
if (!s) continue;
|
|
94
|
+
if (s.transport === 'http') {
|
|
95
|
+
out[id] = {
|
|
96
|
+
portable: { type: 'http', url: s.url },
|
|
97
|
+
vscode: { type: 'http', url: s.url },
|
|
98
|
+
cursor: { url: s.url },
|
|
99
|
+
gemini: { httpUrl: s.url },
|
|
100
|
+
opencode: { type: 'remote', url: s.url, enabled: true },
|
|
101
|
+
}[format];
|
|
102
|
+
} else {
|
|
103
|
+
out[id] = {
|
|
104
|
+
portable: { type: 'stdio', command: s.command, args: s.args },
|
|
105
|
+
vscode: { type: 'stdio', command: s.command, args: s.args },
|
|
106
|
+
cursor: { command: s.command, args: s.args },
|
|
107
|
+
gemini: { command: s.command, args: s.args },
|
|
108
|
+
opencode: { type: 'local', command: [s.command, ...s.args], enabled: true },
|
|
109
|
+
}[format];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Codex `.codex/config.toml` — every server commented out until a human enables it. */
|
|
116
|
+
export function renderCodexMcpToml(ids) {
|
|
117
|
+
const lines = [];
|
|
118
|
+
for (const id of ids) {
|
|
119
|
+
const s = MCP_SERVERS[id];
|
|
120
|
+
if (!s) continue;
|
|
121
|
+
lines.push(`# [mcp_servers.${id.replace(/[^a-z0-9_]/gi, '_')}]`);
|
|
122
|
+
if (s.transport === 'http') {
|
|
123
|
+
lines.push(`# url = "${s.url}"`);
|
|
124
|
+
} else {
|
|
125
|
+
lines.push(`# command = "${s.command}"`);
|
|
126
|
+
lines.push(`# args = [${s.args.map(a => `"${a}"`).join(', ')}]`);
|
|
127
|
+
}
|
|
128
|
+
lines.push('');
|
|
129
|
+
}
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
80
133
|
export function stackMcpIds(stackId) {
|
|
81
|
-
const base = ['github'
|
|
134
|
+
const base = ['github'];
|
|
82
135
|
switch (stackId) {
|
|
83
136
|
case 'react':
|
|
84
137
|
case 'nextjs':
|
|
@@ -147,7 +200,9 @@ export function detectRepoSignals(cwd, fs) {
|
|
|
147
200
|
signals.postgres = signals.postgres || /psycopg|sqlalchemy|asyncpg/i.test(py);
|
|
148
201
|
}
|
|
149
202
|
|
|
150
|
-
const ids = new Set(['github'
|
|
203
|
+
const ids = new Set(['github']);
|
|
204
|
+
if (exists('.gitlab-ci.yml')) ids.add('gitlab');
|
|
205
|
+
if (exists('supabase')) ids.add('supabase');
|
|
151
206
|
if (signals.dockerfile) ids.add('docker');
|
|
152
207
|
if (signals.kubernetes) ids.add('kubernetes');
|
|
153
208
|
if (signals.playwright || signals.next) ids.add('playwright');
|
|
@@ -164,7 +219,7 @@ export function buildRecommendedMcpConfig(ids) {
|
|
|
164
219
|
/** @type {Record<string, object>} */
|
|
165
220
|
const recommended = {};
|
|
166
221
|
for (const id of ids) {
|
|
167
|
-
if (
|
|
222
|
+
if (MCP_SERVERS[id]) recommended[id] = MCP_SERVERS[id];
|
|
168
223
|
}
|
|
169
224
|
return recommended;
|
|
170
225
|
}
|
package/lib/kit-core/options.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const SITE_URL = 'https://www.ai-standards.dev';
|
|
2
2
|
|
|
3
|
+
export const KIT_VERSION = '2.3.0';
|
|
4
|
+
|
|
3
5
|
export const GOALS = [
|
|
4
6
|
{ id: 'new-product', label: 'Build a new product' },
|
|
5
7
|
{ id: 'existing-codebase', label: 'Join an existing codebase' },
|
|
@@ -30,13 +32,28 @@ export const MATURITIES = [
|
|
|
30
32
|
|
|
31
33
|
export const IDES = [
|
|
32
34
|
{ id: 'cursor', label: 'Cursor' },
|
|
33
|
-
{ id: 'copilot', label: 'GitHub Copilot' },
|
|
34
35
|
{ id: 'claude-code', label: 'Claude Code' },
|
|
35
|
-
{ id: '
|
|
36
|
-
{ id: '
|
|
36
|
+
{ id: 'copilot', label: 'GitHub Copilot (VS Code / CLI / cloud agent)' },
|
|
37
|
+
{ id: 'codex', label: 'OpenAI Codex (CLI / app / cloud)' },
|
|
38
|
+
{ id: 'gemini-cli', label: 'Gemini CLI' },
|
|
39
|
+
{ id: 'kiro', label: 'Kiro' },
|
|
40
|
+
{ id: 'windsurf', label: 'Devin Desktop (Windsurf)' },
|
|
41
|
+
{ id: 'opencode', label: 'opencode' },
|
|
37
42
|
{ id: 'continue', label: 'Continue' },
|
|
38
43
|
];
|
|
39
44
|
|
|
45
|
+
/** Legacy ids accepted on input and normalized before generation. */
|
|
46
|
+
export const IDE_ALIASES = {
|
|
47
|
+
vscode: 'copilot',
|
|
48
|
+
devin: 'windsurf',
|
|
49
|
+
gemini: 'gemini-cli',
|
|
50
|
+
claude: 'claude-code',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function normalizeIde(ide) {
|
|
54
|
+
return IDE_ALIASES[ide] ?? ide;
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
export const MODELS = [
|
|
41
58
|
{ id: 'claude', label: 'Claude (Anthropic)' },
|
|
42
59
|
{ id: 'gpt', label: 'GPT (OpenAI)' },
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect a repository's own verification commands so AGENTS.md states real gates, not placeholders.
|
|
3
|
+
* Read-only: inspects manifests and lockfiles, never executes project code.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const VERIFY_KEYS = ['lint', 'typecheck', 'test', 'build'];
|
|
8
|
+
|
|
9
|
+
const NPM_DEFAULT_TEST = /no test specified/i;
|
|
10
|
+
|
|
11
|
+
function nodeRunner(exists) {
|
|
12
|
+
if (exists('pnpm-lock.yaml')) return { name: 'pnpm', run: s => `pnpm ${s}`, test: 'pnpm test', exec: 'pnpm exec' };
|
|
13
|
+
if (exists('yarn.lock')) return { name: 'yarn', run: s => `yarn ${s}`, test: 'yarn test', exec: 'yarn' };
|
|
14
|
+
if (exists('bun.lock') || exists('bun.lockb')) return { name: 'bun', run: s => `bun run ${s}`, test: 'bun run test', exec: 'bunx' };
|
|
15
|
+
return { name: 'npm', run: s => `npm run ${s}`, test: 'npm test', exec: 'npx' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function firstScript(scripts, names) {
|
|
19
|
+
return names.find(n => typeof scripts[n] === 'string' && scripts[n].trim());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function detectNode(exists, read) {
|
|
23
|
+
let pkg;
|
|
24
|
+
try {
|
|
25
|
+
pkg = JSON.parse(read('package.json'));
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const scripts = pkg.scripts ?? {};
|
|
30
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
31
|
+
const r = nodeRunner(exists);
|
|
32
|
+
const out = {};
|
|
33
|
+
|
|
34
|
+
const lint = firstScript(scripts, ['lint', 'lint:check', 'eslint', 'biome:check', 'check:lint']);
|
|
35
|
+
if (lint) out.lint = r.run(lint);
|
|
36
|
+
else if (deps['@biomejs/biome']) out.lint = `${r.exec} biome check .`;
|
|
37
|
+
|
|
38
|
+
const typecheck = firstScript(scripts, ['typecheck', 'type-check', 'check-types', 'types:check', 'tsc']);
|
|
39
|
+
if (typecheck) out.typecheck = r.run(typecheck);
|
|
40
|
+
else if (deps.typescript && exists('tsconfig.json')) out.typecheck = `${r.exec} tsc --noEmit`;
|
|
41
|
+
|
|
42
|
+
if (scripts.test && !NPM_DEFAULT_TEST.test(scripts.test)) out.test = r.test;
|
|
43
|
+
else {
|
|
44
|
+
const alt = firstScript(scripts, ['test:unit', 'vitest', 'jest']);
|
|
45
|
+
if (alt) out.test = r.run(alt);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (scripts.build) out.build = r.run('build');
|
|
49
|
+
return { commands: out, source: `package.json scripts (${r.name})` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectPython(exists, read) {
|
|
53
|
+
const pyproject = read('pyproject.toml');
|
|
54
|
+
const reqs = read('requirements.txt') + read('requirements-dev.txt') + read('requirements/dev.txt');
|
|
55
|
+
if (!pyproject && !reqs && !exists('setup.py')) return null;
|
|
56
|
+
const all = `${pyproject}\n${reqs}`;
|
|
57
|
+
const prefix = exists('uv.lock') ? 'uv run ' : exists('poetry.lock') ? 'poetry run ' : exists('pdm.lock') ? 'pdm run ' : '';
|
|
58
|
+
const has = re => re.test(all);
|
|
59
|
+
const dep = name => new RegExp(`\\[tool\\.${name}\\b|(^|["'\\s,\\[])${name}([<>=~!\\[;\\s"',\\]]|$)`, 'm').test(all);
|
|
60
|
+
const out = {};
|
|
61
|
+
if (dep('ruff')) out.lint = `${prefix}ruff check .`;
|
|
62
|
+
else if (dep('flake8')) out.lint = `${prefix}flake8`;
|
|
63
|
+
if (dep('mypy')) out.typecheck = `${prefix}mypy .`;
|
|
64
|
+
else if (dep('pyright')) out.typecheck = `${prefix}pyright`;
|
|
65
|
+
if (dep('pytest') || has(/\[tool\.pytest/) || exists('pytest.ini') || exists('tests')) out.test = `${prefix}pytest`;
|
|
66
|
+
const tool = prefix ? prefix.trim().split(' ')[0] : 'pip';
|
|
67
|
+
return { commands: out, source: `Python project (${tool})` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function detectJvm(exists) {
|
|
71
|
+
if (exists('mvnw') || exists('pom.xml')) {
|
|
72
|
+
const mvn = exists('mvnw') ? './mvnw -B' : 'mvn -B';
|
|
73
|
+
return { commands: { typecheck: `${mvn} -q test-compile`, test: `${mvn} verify`, build: `${mvn} -DskipTests package` }, source: 'Maven' };
|
|
74
|
+
}
|
|
75
|
+
if (exists('gradlew') || exists('build.gradle') || exists('build.gradle.kts')) {
|
|
76
|
+
const g = exists('gradlew') ? './gradlew' : 'gradle';
|
|
77
|
+
return {
|
|
78
|
+
commands: { lint: `${g} check -x test`, typecheck: `${g} testClasses`, test: `${g} test`, build: `${g} build -x test` },
|
|
79
|
+
source: 'Gradle',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detectDotnet(listRoot) {
|
|
86
|
+
const entries = listRoot();
|
|
87
|
+
const target = entries.find(f => f.endsWith('.sln') || f.endsWith('.slnx')) ?? entries.find(f => f.endsWith('.csproj'));
|
|
88
|
+
if (!target) return null;
|
|
89
|
+
return {
|
|
90
|
+
commands: {
|
|
91
|
+
lint: `dotnet format ${target} --verify-no-changes`,
|
|
92
|
+
typecheck: `dotnet build ${target} -warnaserror`,
|
|
93
|
+
test: `dotnet test ${target}`,
|
|
94
|
+
},
|
|
95
|
+
source: '.NET',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function detectGo(exists) {
|
|
100
|
+
if (!exists('go.mod')) return null;
|
|
101
|
+
return { commands: { lint: 'go vet ./...', typecheck: 'go build ./...', test: 'go test ./...' }, source: 'Go modules' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function detectMake(read) {
|
|
105
|
+
const makefile = read('Makefile');
|
|
106
|
+
if (!makefile) return null;
|
|
107
|
+
const add = {};
|
|
108
|
+
for (const key of VERIFY_KEYS) {
|
|
109
|
+
if (new RegExp(`^${key}:`, 'm').test(makefile)) add[key] = `make ${key}`;
|
|
110
|
+
}
|
|
111
|
+
return Object.keys(add).length ? { commands: add, source: 'Makefile targets' } : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} cwd
|
|
116
|
+
* @param {{ existsSync: Function, readFileSync: Function, readdirSync?: Function }} fs
|
|
117
|
+
* @returns {{ commands: Partial<Record<'lint'|'typecheck'|'test'|'build', string>>, sources: string[] }}
|
|
118
|
+
*/
|
|
119
|
+
export function detectVerifyCommands(cwd, fs) {
|
|
120
|
+
const exists = rel => fs.existsSync(path.join(cwd, rel));
|
|
121
|
+
const read = rel => {
|
|
122
|
+
try {
|
|
123
|
+
return fs.readFileSync(path.join(cwd, rel), 'utf8');
|
|
124
|
+
} catch {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const listRoot = () => {
|
|
129
|
+
try {
|
|
130
|
+
return fs.readdirSync ? fs.readdirSync(cwd) : [];
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const commands = {};
|
|
137
|
+
const sources = [];
|
|
138
|
+
const merge = found => {
|
|
139
|
+
if (!found) return;
|
|
140
|
+
let used = false;
|
|
141
|
+
for (const [k, v] of Object.entries(found.commands)) {
|
|
142
|
+
if (!commands[k] && v) {
|
|
143
|
+
commands[k] = v;
|
|
144
|
+
used = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (used) sources.push(found.source);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Explicit project scripts first, then commands inferred from the toolchain.
|
|
151
|
+
if (exists('package.json')) merge(detectNode(exists, read));
|
|
152
|
+
merge(detectMake(read));
|
|
153
|
+
merge(detectPython(exists, read));
|
|
154
|
+
merge(detectJvm(exists));
|
|
155
|
+
merge(detectDotnet(listRoot));
|
|
156
|
+
merge(detectGo(exists));
|
|
157
|
+
return { commands, sources };
|
|
158
|
+
}
|