ai-team 1.0.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/.agents/manifest.json +170 -0
- package/.agents/skills/agent-creator/SKILL.md +28 -0
- package/.agents/skills/ai-integration/SKILL.md +69 -0
- package/.agents/skills/back-end-development/SKILL.md +44 -0
- package/.agents/skills/database-management/SKILL.md +111 -0
- package/.agents/skills/front-end-development/SKILL.md +39 -0
- package/.agents/skills/front-end-development/references/component-architecture.md +213 -0
- package/.agents/skills/front-end-development/references/data-fetching-pattern.md +111 -0
- package/.agents/skills/front-end-development/references/state-management.md +223 -0
- package/.agents/skills/front-end-development/references/styling.md +226 -0
- package/.agents/skills/self-improvement/SKILL.md +32 -0
- package/.agents/skills/self-improvement/templates/handoff-template.md +35 -0
- package/.agents/skills/self-improvement/templates/plan-template.md +55 -0
- package/.agents/skills/visual-testing/SKILL.md +40 -0
- package/.agents/teams/web-product/README.md +65 -0
- package/.agents/teams/web-product/workflows/feature-lifecycle.md +101 -0
- package/.github/agents/agent-creator.agent.md +66 -0
- package/.github/agents/cli-dev.agent.md +57 -0
- package/.github/agents/code-reviewer.agent.md +66 -0
- package/.github/agents/documentation-writer.agent.md +62 -0
- package/.github/agents/planning-agent.agent.md +70 -0
- package/.github/agents/product-team-orchestrator.agent.md +82 -0
- package/.github/agents/requirement-analyst.agent.md +65 -0
- package/.vscode/mcp.json +15 -0
- package/README.md +121 -0
- package/docs/agents/README.md +36 -0
- package/docs/cli.md +65 -0
- package/docs/plan.md +95 -0
- package/package.json +30 -0
- package/src/agents/definitions/agent-creator.yaml +68 -0
- package/src/agents/definitions/backend-dev.yaml +69 -0
- package/src/agents/definitions/cli-dev.yaml +59 -0
- package/src/agents/definitions/code-reviewer.yaml +67 -0
- package/src/agents/definitions/documentation-writer.yaml +74 -0
- package/src/agents/definitions/frontend-dev.yaml +68 -0
- package/src/agents/definitions/planning-agent.yaml +72 -0
- package/src/agents/definitions/product-team-orchestrator.yaml +83 -0
- package/src/agents/definitions/requirement-analyst.yaml +67 -0
- package/src/agents/definitions/tester.yaml +81 -0
- package/src/agents/generate.js +213 -0
- package/src/cli.js +398 -0
- package/src/lib/adapters.js +41 -0
- package/src/lib/fs-utils.js +60 -0
- package/src/lib/hash.js +9 -0
- package/src/lib/manifest.js +50 -0
- package/src/lib/migration.js +60 -0
- package/src/lib/prompts.js +104 -0
- package/src/lib/sync-engine.js +319 -0
- package/src/lib/template-registry.js +107 -0
- package/templates/bootstrap/base/.agents/skills/agent-creator/SKILL.md +28 -0
- package/templates/bootstrap/base/.agents/skills/ai-integration/SKILL.md +69 -0
- package/templates/bootstrap/base/.agents/skills/back-end-development/SKILL.md +44 -0
- package/templates/bootstrap/base/.agents/skills/database-management/SKILL.md +111 -0
- package/templates/bootstrap/base/.agents/skills/front-end-development/SKILL.md +39 -0
- package/templates/bootstrap/base/.agents/skills/front-end-development/references/component-architecture.md +213 -0
- package/templates/bootstrap/base/.agents/skills/front-end-development/references/data-fetching-pattern.md +111 -0
- package/templates/bootstrap/base/.agents/skills/front-end-development/references/state-management.md +223 -0
- package/templates/bootstrap/base/.agents/skills/front-end-development/references/styling.md +226 -0
- package/templates/bootstrap/base/.agents/skills/self-improvement/SKILL.md +32 -0
- package/templates/bootstrap/base/.agents/skills/self-improvement/templates/handoff-template.md +35 -0
- package/templates/bootstrap/base/.agents/skills/self-improvement/templates/plan-template.md +55 -0
- package/templates/bootstrap/base/.agents/skills/visual-testing/SKILL.md +40 -0
- package/templates/bootstrap/base/.agents/teams/web-product/README.md +65 -0
- package/templates/bootstrap/base/.agents/teams/web-product/workflows/feature-lifecycle.md +101 -0
- package/templates/bootstrap/manifest.json +32 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/agent-creator.md +55 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/backend-dev.md +48 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/cli-dev.md +47 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/code-reviewer.md +52 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/documentation-writer.md +56 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/frontend-dev.md +47 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/planning-agent.md +51 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/product-team-orchestrator.md +51 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/requirement-analyst.md +54 -0
- package/templates/bootstrap/overlays/claude-code/.claude/agents/tester.md +58 -0
- package/templates/bootstrap/overlays/claude-code/.mcp.json +3 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/agent-creator.agent.md +66 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/backend-dev.agent.md +67 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/cli-dev.agent.md +57 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/code-reviewer.agent.md +64 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/documentation-writer.agent.md +72 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/frontend-dev.agent.md +66 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/planning-agent.agent.md +70 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/product-team-orchestrator.agent.md +82 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/requirement-analyst.agent.md +65 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/tester-agent.agent.md +69 -0
- package/templates/bootstrap/overlays/vscode-copilot/.github/agents/tester.agent.md +80 -0
- package/templates/bootstrap/overlays/vscode-copilot/.vscode/mcp.json +12 -0
- package/tests/cli.integration.test.js +63 -0
- package/tests/hash.test.js +9 -0
- package/tests/sync-engine.test.js +77 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { promises as fs } from "node:fs";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
import { collectInteractiveProfile } from "./lib/prompts.js";
|
|
10
|
+
import { getAdapter } from "./lib/adapters.js";
|
|
11
|
+
import { planSync, applySync, doctor } from "./lib/sync-engine.js";
|
|
12
|
+
|
|
13
|
+
function printUsage() {
|
|
14
|
+
console.log(`${chalk.bold("ai-team")} <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
init Install templates and create .agents/manifest.json
|
|
18
|
+
update Update templates using manifest drift detection
|
|
19
|
+
plan Dry-run preview of changes
|
|
20
|
+
diff Alias of plan
|
|
21
|
+
doctor Validate install health and report drift
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--ide <vscode|claude-code>
|
|
25
|
+
--team <web-product>
|
|
26
|
+
--target <path> Default: current directory
|
|
27
|
+
--include-mcp Include .vscode/mcp.json merge
|
|
28
|
+
--no-include-mcp Skip MCP config
|
|
29
|
+
--force Overwrite locally modified copy-tracked files
|
|
30
|
+
--yes Non-interactive defaults
|
|
31
|
+
--migrate Apply legacy migration moves when safe
|
|
32
|
+
--json Emit machine-readable output
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createSpinner(values, text) {
|
|
37
|
+
const canUseSpinner = !values.json && process.stdout.isTTY;
|
|
38
|
+
return ora({ text, isEnabled: canUseSpinner }).start();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function canResolveProfileWithoutPrompt(values, command) {
|
|
42
|
+
if (values.yes || command === "doctor" || process.env.CI === "true") {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const hasIncludeMcpOption =
|
|
47
|
+
values["include-mcp"] !== undefined ||
|
|
48
|
+
values["no-include-mcp"] !== undefined;
|
|
49
|
+
|
|
50
|
+
return Boolean(values.ide && values.team && hasIncludeMcpOption);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseBooleanOption(values, key, fallback) {
|
|
54
|
+
if (values[key] === undefined) {
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Boolean(values[key]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function summarizePlan(plan) {
|
|
62
|
+
const counts = { create: 0, overwrite: 0, merge: 0, unchanged: 0, skip: 0 };
|
|
63
|
+
for (const action of plan.actions) {
|
|
64
|
+
counts[action.action] = (counts[action.action] ?? 0) + 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
templateVersion: plan.templateVersion,
|
|
69
|
+
actions: counts,
|
|
70
|
+
conflicts: plan.conflicts.length,
|
|
71
|
+
migrationMoves: plan.legacyMoves.length,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseFrontmatterDescription(text) {
|
|
76
|
+
const normalized = text.replace(/^```[^\n]*\r?\n/, "");
|
|
77
|
+
const match = normalized.match(/^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n/);
|
|
78
|
+
if (!match) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const frontmatter = YAML.parse(match[1]);
|
|
84
|
+
if (typeof frontmatter?.description === "string") {
|
|
85
|
+
return frontmatter.description.trim();
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function getInstalledAgents(manifest, targetDir, writtenPaths = null) {
|
|
95
|
+
const ide = manifest?.profile?.ide ?? "vscode";
|
|
96
|
+
const isClaudeCode = ide === "claude-code" || ide === "claude";
|
|
97
|
+
const agentsPrefix = isClaudeCode ? ".claude/agents/" : ".github/agents/";
|
|
98
|
+
const agentsSuffix = isClaudeCode ? ".md" : ".agent.md";
|
|
99
|
+
|
|
100
|
+
const agentPaths = Object.keys(manifest?.files ?? {})
|
|
101
|
+
.filter(
|
|
102
|
+
(targetPath) =>
|
|
103
|
+
targetPath.startsWith(agentsPrefix) &&
|
|
104
|
+
targetPath.endsWith(agentsSuffix) &&
|
|
105
|
+
(writtenPaths === null || writtenPaths.has(targetPath)),
|
|
106
|
+
)
|
|
107
|
+
.sort();
|
|
108
|
+
|
|
109
|
+
const agents = [];
|
|
110
|
+
for (const agentPath of agentPaths) {
|
|
111
|
+
const agentName = path.basename(agentPath, agentsSuffix);
|
|
112
|
+
let description = null;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const content = await fs.readFile(
|
|
116
|
+
path.join(targetDir, agentPath),
|
|
117
|
+
"utf8",
|
|
118
|
+
);
|
|
119
|
+
description = parseFrontmatterDescription(content);
|
|
120
|
+
} catch {
|
|
121
|
+
description = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
agents.push({ name: agentName, description });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return agents;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function wrapText(text, { indent = "", maxWidth = 100 } = {}) {
|
|
131
|
+
if (!text) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
136
|
+
const lines = [];
|
|
137
|
+
let current = "";
|
|
138
|
+
|
|
139
|
+
for (const word of words) {
|
|
140
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
141
|
+
if (candidate.length > maxWidth && current) {
|
|
142
|
+
lines.push(`${indent}${current}`);
|
|
143
|
+
current = word;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
current = candidate;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (current) {
|
|
151
|
+
lines.push(`${indent}${current}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function printInitAgentUsage(result, targetDir) {
|
|
158
|
+
const { manifest } = result;
|
|
159
|
+
const writtenPaths = new Set(result.written.map((w) => w.targetPath));
|
|
160
|
+
const agents = await getInstalledAgents(manifest, targetDir, writtenPaths);
|
|
161
|
+
if (agents.length === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const columns = process.stdout.columns ?? 120;
|
|
166
|
+
const wrapWidth = Math.max(60, columns - 8);
|
|
167
|
+
|
|
168
|
+
console.log("");
|
|
169
|
+
console.log(chalk.bold.cyan("Installed agents"));
|
|
170
|
+
for (const agent of agents) {
|
|
171
|
+
console.log(`- ${chalk.greenBright(`@${agent.name}`)}`);
|
|
172
|
+
|
|
173
|
+
if (!agent.description) {
|
|
174
|
+
console.log("");
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const wrappedDescription = wrapText(agent.description, {
|
|
179
|
+
indent: " ",
|
|
180
|
+
maxWidth: wrapWidth,
|
|
181
|
+
});
|
|
182
|
+
for (const line of wrappedDescription) {
|
|
183
|
+
console.log(chalk.italic.white(line));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log("");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log("");
|
|
190
|
+
|
|
191
|
+
const printWrappedBullet = (text) => {
|
|
192
|
+
const wrapped = wrapText(text, { maxWidth: wrapWidth - 2 });
|
|
193
|
+
if (wrapped.length === 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`- ${wrapped[0].trim()}`);
|
|
198
|
+
for (const continuation of wrapped.slice(1)) {
|
|
199
|
+
console.log(` ${continuation.trim()}`);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const ide = manifest?.profile?.ide ?? "vscode";
|
|
204
|
+
const isClaudeCode = ide === "claude-code" || ide === "claude";
|
|
205
|
+
|
|
206
|
+
if (isClaudeCode) {
|
|
207
|
+
console.log(chalk.bold.cyan("Quick start in Claude Code"));
|
|
208
|
+
printWrappedBullet("Run /agents to view and manage installed subagents.");
|
|
209
|
+
printWrappedBullet(
|
|
210
|
+
"Ask Claude to delegate: use the product-team-orchestrator to plan and coordinate <feature>.",
|
|
211
|
+
);
|
|
212
|
+
printWrappedBullet(
|
|
213
|
+
"Continue by naming specialist agents directly or letting the orchestrator route to them.",
|
|
214
|
+
);
|
|
215
|
+
} else {
|
|
216
|
+
console.log(chalk.bold.cyan("Quick start in VS Code Chat"));
|
|
217
|
+
printWrappedBullet(
|
|
218
|
+
"Select the product-team-orchestrator agent from the chat session and describe your feature.",
|
|
219
|
+
);
|
|
220
|
+
printWrappedBullet(
|
|
221
|
+
"The orchestrator can delegate to specialist agents automatically or you can call them directly by changing the agent selector.",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function printPlan(plan) {
|
|
227
|
+
const summary = summarizePlan(plan);
|
|
228
|
+
console.log(chalk.cyan(`Template version: ${summary.templateVersion}`));
|
|
229
|
+
console.log(
|
|
230
|
+
`Actions: create=${summary.actions.create} overwrite=${summary.actions.overwrite} merge=${summary.actions.merge} unchanged=${summary.actions.unchanged} skip=${summary.actions.skip}`,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (plan.conflicts.length > 0) {
|
|
234
|
+
console.log(chalk.yellow("Conflicts:"));
|
|
235
|
+
for (const conflict of plan.conflicts) {
|
|
236
|
+
console.log(`- ${conflict.targetPath} (${conflict.reason})`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (plan.legacyMoves.length > 0) {
|
|
241
|
+
console.log(chalk.yellow("Legacy migration preview:"));
|
|
242
|
+
for (const move of plan.legacyMoves) {
|
|
243
|
+
console.log(`- ${move.from} -> ${move.to}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function resolveProfile(values, command) {
|
|
249
|
+
const includeMcp = values["include-mcp"];
|
|
250
|
+
const noIncludeMcp = values["no-include-mcp"];
|
|
251
|
+
const includeMcpValue = includeMcp ? true : noIncludeMcp ? false : undefined;
|
|
252
|
+
|
|
253
|
+
const profile = await collectInteractiveProfile({
|
|
254
|
+
ide: values.ide,
|
|
255
|
+
team: values.team,
|
|
256
|
+
includeMcp: includeMcpValue,
|
|
257
|
+
yes: values.yes || command === "doctor",
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
getAdapter(profile.ide);
|
|
261
|
+
return profile;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function asJson(values, payload) {
|
|
265
|
+
if (values.json) {
|
|
266
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function run() {
|
|
274
|
+
const [, , commandArg] = process.argv;
|
|
275
|
+
const command = commandArg === "diff" ? "plan" : commandArg;
|
|
276
|
+
|
|
277
|
+
if (!command || ["-h", "--help", "help"].includes(command)) {
|
|
278
|
+
printUsage();
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!["init", "update", "plan", "doctor"].includes(command)) {
|
|
283
|
+
console.error(`Unsupported command: ${command}`);
|
|
284
|
+
printUsage();
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const { values } = parseArgs({
|
|
289
|
+
options: {
|
|
290
|
+
ide: { type: "string" },
|
|
291
|
+
team: { type: "string" },
|
|
292
|
+
target: { type: "string" },
|
|
293
|
+
force: { type: "boolean", default: false },
|
|
294
|
+
yes: { type: "boolean", default: false },
|
|
295
|
+
json: { type: "boolean", default: false },
|
|
296
|
+
migrate: { type: "boolean", default: false },
|
|
297
|
+
"include-mcp": { type: "boolean" },
|
|
298
|
+
"no-include-mcp": { type: "boolean" },
|
|
299
|
+
},
|
|
300
|
+
allowPositionals: true,
|
|
301
|
+
strict: true,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const targetDir = path.resolve(values.target ?? process.cwd());
|
|
305
|
+
|
|
306
|
+
if (command === "doctor") {
|
|
307
|
+
const spinner = createSpinner(values, "Running health checks");
|
|
308
|
+
const report = await doctor({ targetDir });
|
|
309
|
+
spinner.stop();
|
|
310
|
+
|
|
311
|
+
if (asJson(values, report)) {
|
|
312
|
+
process.exit(report.ok ? 0 : 1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (report.issues.length === 0) {
|
|
316
|
+
console.log(chalk.green("Doctor: OK"));
|
|
317
|
+
} else {
|
|
318
|
+
console.log(chalk.red("Doctor issues:"));
|
|
319
|
+
for (const issue of report.issues) {
|
|
320
|
+
console.log(`- [${issue.level}] ${issue.code}: ${issue.message}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (report.legacyMoves.length > 0) {
|
|
325
|
+
console.log("Legacy migration preview:");
|
|
326
|
+
for (const move of report.legacyMoves) {
|
|
327
|
+
console.log(`- ${move.from} -> ${move.to}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
process.exit(report.ok ? 0 : 1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let profile;
|
|
335
|
+
if (canResolveProfileWithoutPrompt(values, command)) {
|
|
336
|
+
const profileSpinner = createSpinner(values, "Resolving profile");
|
|
337
|
+
profile = await resolveProfile(values, command);
|
|
338
|
+
profileSpinner.succeed("Profile resolved");
|
|
339
|
+
} else {
|
|
340
|
+
profile = await resolveProfile(values, command);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const force = parseBooleanOption(values, "force", false);
|
|
344
|
+
const planSpinner = createSpinner(values, "Computing sync plan");
|
|
345
|
+
const plan = await planSync({ targetDir, profile, force, command });
|
|
346
|
+
planSpinner.succeed("Sync plan ready");
|
|
347
|
+
|
|
348
|
+
if (command === "plan") {
|
|
349
|
+
if (asJson(values, plan)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
printPlan(plan);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const applySpinner = createSpinner(values, "Applying changes");
|
|
358
|
+
const result = await applySync(plan, { applyMigration: values.migrate });
|
|
359
|
+
applySpinner.succeed("Changes applied");
|
|
360
|
+
|
|
361
|
+
if (asJson(values, result)) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const summary = summarizePlan(plan);
|
|
366
|
+
console.log(chalk.green(`${command} completed on ${targetDir}`));
|
|
367
|
+
console.log(chalk.cyan(`Template version: ${summary.templateVersion}`));
|
|
368
|
+
console.log(
|
|
369
|
+
`Written files: ${result.written.length}, skipped: ${result.skipped.length}, conflicts: ${result.conflicts.length}`,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (result.conflicts.length > 0) {
|
|
373
|
+
console.log(
|
|
374
|
+
chalk.yellow(
|
|
375
|
+
"Conflicts (rerun with --force to overwrite copy-tracked files):",
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
for (const conflict of result.conflicts) {
|
|
379
|
+
console.log(`- ${conflict.targetPath} (${conflict.reason})`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (result.migration.length > 0) {
|
|
384
|
+
console.log(chalk.yellow("Migration results:"));
|
|
385
|
+
for (const move of result.migration) {
|
|
386
|
+
console.log(`- ${move.from} -> ${move.to} (${move.status})`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (command === "init") {
|
|
391
|
+
await printInitAgentUsage(result, targetDir);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
run().catch((error) => {
|
|
396
|
+
console.error(error.message);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function createVscodeAdapter() {
|
|
2
|
+
return {
|
|
3
|
+
id: "vscode-copilot",
|
|
4
|
+
applyOverlay() {
|
|
5
|
+
return "vscode-copilot";
|
|
6
|
+
},
|
|
7
|
+
pathMap(relativePath) {
|
|
8
|
+
return relativePath;
|
|
9
|
+
},
|
|
10
|
+
postInstall() {
|
|
11
|
+
return [];
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createClaudeCodeAdapter() {
|
|
17
|
+
return {
|
|
18
|
+
id: "claude-code",
|
|
19
|
+
applyOverlay() {
|
|
20
|
+
return "claude-code";
|
|
21
|
+
},
|
|
22
|
+
pathMap(relativePath) {
|
|
23
|
+
return relativePath;
|
|
24
|
+
},
|
|
25
|
+
postInstall() {
|
|
26
|
+
return [];
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getAdapter(ide) {
|
|
32
|
+
if (ide === "vscode" || ide === "copilot") {
|
|
33
|
+
return createVscodeAdapter();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (ide === "claude-code" || ide === "claude") {
|
|
37
|
+
return createClaudeCodeAdapter();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error(`Unsupported IDE adapter: ${ide}`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export async function pathExists(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(filePath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function ensureDirForFile(filePath) {
|
|
14
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function readTextIfExists(filePath) {
|
|
18
|
+
if (!(await pathExists(filePath))) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return fs.readFile(filePath, "utf8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function readJsonIfExists(filePath) {
|
|
26
|
+
const raw = await readTextIfExists(filePath);
|
|
27
|
+
if (raw === null) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function writeText(filePath, text) {
|
|
35
|
+
await ensureDirForFile(filePath);
|
|
36
|
+
await fs.writeFile(filePath, text, "utf8");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function writeJson(filePath, data) {
|
|
40
|
+
await writeText(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function listFilesRecursive(rootPath) {
|
|
44
|
+
const results = [];
|
|
45
|
+
|
|
46
|
+
async function walk(currentPath) {
|
|
47
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const absolute = path.join(currentPath, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
await walk(absolute);
|
|
52
|
+
} else if (entry.isFile()) {
|
|
53
|
+
results.push(absolute);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await walk(rootPath);
|
|
59
|
+
return results;
|
|
60
|
+
}
|
package/src/lib/hash.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readJsonIfExists, writeJson } from "./fs-utils.js";
|
|
3
|
+
|
|
4
|
+
export function getManifestPath(targetDir) {
|
|
5
|
+
return path.join(targetDir, ".agents/manifest.json");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function loadInstalledManifest(targetDir) {
|
|
9
|
+
const manifestPath = getManifestPath(targetDir);
|
|
10
|
+
return readJsonIfExists(manifestPath);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function writeInstalledManifest(targetDir, manifest) {
|
|
14
|
+
const manifestPath = getManifestPath(targetDir);
|
|
15
|
+
await writeJson(manifestPath, manifest);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createManifest({
|
|
19
|
+
templateVersion,
|
|
20
|
+
schemaVersion,
|
|
21
|
+
profile,
|
|
22
|
+
files,
|
|
23
|
+
}) {
|
|
24
|
+
const now = new Date().toISOString();
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion,
|
|
28
|
+
templateVersion,
|
|
29
|
+
installedAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
profile,
|
|
32
|
+
files,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function bumpManifest(
|
|
37
|
+
manifest,
|
|
38
|
+
{ templateVersion, schemaVersion, profile, files },
|
|
39
|
+
) {
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...manifest,
|
|
44
|
+
schemaVersion,
|
|
45
|
+
templateVersion,
|
|
46
|
+
updatedAt: now,
|
|
47
|
+
profile,
|
|
48
|
+
files,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { pathExists } from "./fs-utils.js";
|
|
4
|
+
|
|
5
|
+
export async function detectLegacyLayout(targetDir) {
|
|
6
|
+
const moves = [];
|
|
7
|
+
const legacyTeamDirs = ["web-product", "gympt"];
|
|
8
|
+
|
|
9
|
+
for (const teamDir of legacyTeamDirs) {
|
|
10
|
+
const legacyAgentsDir = path.join(
|
|
11
|
+
targetDir,
|
|
12
|
+
`.agents/teams/${teamDir}/agents`,
|
|
13
|
+
);
|
|
14
|
+
const exists = await pathExists(legacyAgentsDir);
|
|
15
|
+
|
|
16
|
+
if (!exists) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const entries = await fs.readdir(legacyAgentsDir, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (!entry.isFile() || !entry.name.endsWith(".agent.md")) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
moves.push({
|
|
28
|
+
from: `.agents/teams/${teamDir}/agents/${entry.name}`,
|
|
29
|
+
to: `.github/agents/${entry.name}`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return moves;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function applyLegacyMigration(targetDir, moves) {
|
|
38
|
+
const results = [];
|
|
39
|
+
|
|
40
|
+
for (const move of moves) {
|
|
41
|
+
const fromAbs = path.join(targetDir, move.from);
|
|
42
|
+
const toAbs = path.join(targetDir, move.to);
|
|
43
|
+
|
|
44
|
+
if (!(await pathExists(fromAbs))) {
|
|
45
|
+
results.push({ ...move, status: "missing-source" });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (await pathExists(toAbs)) {
|
|
50
|
+
results.push({ ...move, status: "destination-exists" });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await fs.mkdir(path.dirname(toAbs), { recursive: true });
|
|
55
|
+
await fs.rename(fromAbs, toAbs);
|
|
56
|
+
results.push({ ...move, status: "moved" });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return results;
|
|
60
|
+
}
|