cli-five 0.1.4 → 0.1.6
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/package.json +3 -2
- package/src/commands/init.mjs +5 -0
- package/src/steps/interview.mjs +136 -18
- package/src/steps/scaffold.mjs +1 -0
- package/src/steps/skills.mjs +183 -13
- package/templates/.github/agents/coder.agent.md +4 -0
- package/templates/.github/copilot-instructions.md.tmpl +12 -0
- package/templates/PROJECT.md.tmpl +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-five",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Code Like I'm Five — scaffold a 5-agent VS Code Copilot team into any repo.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"kleur": "^4.1.5",
|
|
33
|
-
"prompts": "^2.4.2"
|
|
33
|
+
"prompts": "^2.4.2",
|
|
34
|
+
"skills": "^1.5.0"
|
|
34
35
|
}
|
|
35
36
|
}
|
package/src/commands/init.mjs
CHANGED
|
@@ -49,6 +49,11 @@ export async function init(args) {
|
|
|
49
49
|
answers.costMode = args.costMode;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
if (answers.presetId && answers.presetId !== 'custom') {
|
|
53
|
+
log.info(`Preset: ${answers.presetId}`);
|
|
54
|
+
}
|
|
55
|
+
if (answers.frameworks.length) log.info(`Stack: ${answers.stack.join(', ')} + ${answers.frameworks.join(', ')}`);
|
|
56
|
+
|
|
52
57
|
// 5. Scaffold
|
|
53
58
|
log.step('4/6 Scaffold');
|
|
54
59
|
const written = scaffold({ cwd, answers, args });
|
package/src/steps/interview.mjs
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
|
|
3
|
+
// ── Preset stacks ─────────────────────────────────────────────────────
|
|
4
|
+
// Chosen for: LLM familiarity, low setup friction, minimal config,
|
|
5
|
+
// copy-paste quickstart, broad hosting support.
|
|
6
|
+
// Research via Context7: Next.js, Vite+React, Express, Hono all score
|
|
7
|
+
// 80+ benchmark with high snippet counts and well-indexed docs.
|
|
8
|
+
const STACK_PRESETS = [
|
|
9
|
+
{
|
|
10
|
+
title: 'Next.js (TypeScript)',
|
|
11
|
+
value: 'nextjs',
|
|
12
|
+
description: 'Full-stack React. App Router, TypeScript, Tailwind. `npx create-next-app`',
|
|
13
|
+
stack: ['Node + TypeScript'],
|
|
14
|
+
frameworks: ['Next.js', 'React', 'Tailwind CSS'],
|
|
15
|
+
quickstart: 'npx create-next-app@latest . --yes && npm run dev',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: 'Vite + React (TypeScript)',
|
|
19
|
+
value: 'vite-react',
|
|
20
|
+
description: 'SPA frontend. Lightning-fast HMR. `npm create vite`',
|
|
21
|
+
stack: ['Node + TypeScript'],
|
|
22
|
+
frameworks: ['Vite', 'React', 'TypeScript'],
|
|
23
|
+
quickstart: 'npm create vite@latest . -- --template react-ts && npm i && npm run dev',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: 'Express API (TypeScript)',
|
|
27
|
+
value: 'express',
|
|
28
|
+
description: 'REST API server. Minimal, well-known, huge ecosystem.',
|
|
29
|
+
stack: ['Node + TypeScript'],
|
|
30
|
+
frameworks: ['Express', 'TypeScript'],
|
|
31
|
+
quickstart: 'npm init -y && npm i express typescript tsx @types/express && npx tsx src/index.ts',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'Hono API (TypeScript)',
|
|
35
|
+
value: 'hono',
|
|
36
|
+
description: 'Ultrafast, Web Standards. Works on Node, Bun, Deno, Cloudflare.',
|
|
37
|
+
stack: ['Node + TypeScript'],
|
|
38
|
+
frameworks: ['Hono', 'TypeScript'],
|
|
39
|
+
quickstart: 'npm create hono@latest . && npm i && npm run dev',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Vite + Vue (TypeScript)',
|
|
43
|
+
value: 'vite-vue',
|
|
44
|
+
description: 'SPA frontend with Vue 3 + Composition API.',
|
|
45
|
+
stack: ['Node + TypeScript'],
|
|
46
|
+
frameworks: ['Vite', 'Vue', 'TypeScript'],
|
|
47
|
+
quickstart: 'npm create vite@latest . -- --template vue-ts && npm i && npm run dev',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Python + FastAPI',
|
|
51
|
+
value: 'fastapi',
|
|
52
|
+
description: 'Modern Python API with type hints and auto-docs.',
|
|
53
|
+
stack: ['Python'],
|
|
54
|
+
frameworks: ['FastAPI', 'Pydantic', 'Uvicorn'],
|
|
55
|
+
quickstart: 'pip install fastapi uvicorn && uvicorn main:app --reload',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: 'Custom (freeform)',
|
|
59
|
+
value: 'custom',
|
|
60
|
+
description: 'Enter your own stack and frameworks manually.',
|
|
61
|
+
stack: [],
|
|
62
|
+
frameworks: [],
|
|
63
|
+
quickstart: '',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
3
67
|
const COST_MODES = [
|
|
4
68
|
{ title: 'Premium (default)', value: 'premium', description: 'Sonnet/Opus/Codex. Best quality, real cost.' },
|
|
5
69
|
{ title: 'Cheap (0x)', value: 'cheap', description: 'GPT-4.1 / GPT-4o / GPT-5 mini. Free tier-friendly.' },
|
|
@@ -13,7 +77,8 @@ export async function interview(detected, args) {
|
|
|
13
77
|
throw new Error('Interview cancelled. Nothing was written.');
|
|
14
78
|
};
|
|
15
79
|
|
|
16
|
-
|
|
80
|
+
// ── Basic info ────────────────────────────────────────────────────
|
|
81
|
+
const basic = await prompts(
|
|
17
82
|
[
|
|
18
83
|
{
|
|
19
84
|
type: 'text',
|
|
@@ -27,20 +92,61 @@ export async function interview(detected, args) {
|
|
|
27
92
|
message: 'One-line description (becomes PROJECT.md vision)',
|
|
28
93
|
initial: '',
|
|
29
94
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
95
|
+
],
|
|
96
|
+
{ onCancel },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// ── Stack preset or freeform ──────────────────────────────────────
|
|
100
|
+
// If detection already found a stack, show it as initial hint
|
|
101
|
+
const detectedLabel = detected.stacks.map((s) => s.label).join(', ');
|
|
102
|
+
|
|
103
|
+
const { preset } = await prompts(
|
|
104
|
+
{
|
|
105
|
+
type: 'select',
|
|
106
|
+
name: 'preset',
|
|
107
|
+
message: detectedLabel
|
|
108
|
+
? `Stack preset (detected: ${detectedLabel})`
|
|
109
|
+
: 'Stack preset — pick one or go freeform',
|
|
110
|
+
choices: STACK_PRESETS,
|
|
111
|
+
initial: 0,
|
|
112
|
+
},
|
|
113
|
+
{ onCancel },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
let stackAnswers;
|
|
117
|
+
const chosenPreset = STACK_PRESETS.find((p) => p.value === preset);
|
|
118
|
+
|
|
119
|
+
if (preset === 'custom') {
|
|
120
|
+
// Freeform: user types whatever they want
|
|
121
|
+
stackAnswers = await prompts(
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
type: 'list',
|
|
125
|
+
name: 'stack',
|
|
126
|
+
message: 'Tech stack (comma separated)',
|
|
127
|
+
initial: detectedLabel,
|
|
128
|
+
separator: ',',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'frameworks',
|
|
133
|
+
message: 'Frameworks / key libraries (comma separated)',
|
|
134
|
+
initial: '',
|
|
135
|
+
separator: ',',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
{ onCancel },
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
stackAnswers = {
|
|
142
|
+
stack: chosenPreset.stack,
|
|
143
|
+
frameworks: chosenPreset.frameworks,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Remaining questions ───────────────────────────────────────────
|
|
148
|
+
const rest = await prompts(
|
|
149
|
+
[
|
|
44
150
|
{
|
|
45
151
|
type: 'text',
|
|
46
152
|
name: 'goals',
|
|
@@ -68,19 +174,31 @@ export async function interview(detected, args) {
|
|
|
68
174
|
{ onCancel },
|
|
69
175
|
);
|
|
70
176
|
|
|
71
|
-
return normalize({
|
|
177
|
+
return normalize({
|
|
178
|
+
...defaults(detected),
|
|
179
|
+
...basic,
|
|
180
|
+
...stackAnswers,
|
|
181
|
+
...rest,
|
|
182
|
+
presetId: preset,
|
|
183
|
+
quickstart: chosenPreset?.quickstart || '',
|
|
184
|
+
});
|
|
72
185
|
}
|
|
73
186
|
|
|
187
|
+
/** Default stack is Next.js + TypeScript when nothing detected and --yes. */
|
|
74
188
|
function defaults(detected) {
|
|
189
|
+
const hasDetected = detected.stacks.length > 0;
|
|
190
|
+
const fallback = STACK_PRESETS[0]; // Next.js (TypeScript)
|
|
75
191
|
return {
|
|
76
192
|
projectName: detected.projectName,
|
|
77
193
|
oneLiner: '',
|
|
78
|
-
stack: detected.stacks.map((s) => s.label),
|
|
79
|
-
frameworks: [],
|
|
194
|
+
stack: hasDetected ? detected.stacks.map((s) => s.label) : fallback.stack,
|
|
195
|
+
frameworks: hasDetected ? [] : fallback.frameworks,
|
|
80
196
|
goals: '',
|
|
81
197
|
constraints: '',
|
|
82
198
|
costMode: 'premium',
|
|
83
199
|
snark: true,
|
|
200
|
+
presetId: hasDetected ? 'custom' : fallback.value,
|
|
201
|
+
quickstart: hasDetected ? '' : fallback.quickstart,
|
|
84
202
|
};
|
|
85
203
|
}
|
|
86
204
|
|
package/src/steps/scaffold.mjs
CHANGED
|
@@ -94,6 +94,7 @@ function buildVars(a) {
|
|
|
94
94
|
FRAMEWORKS: a.frameworks.length ? a.frameworks.join(', ') : 'None declared.',
|
|
95
95
|
GOALS: a.goals || 'TODO — declare the primary goal.',
|
|
96
96
|
CONSTRAINTS: a.constraints || 'None declared.',
|
|
97
|
+
QUICKSTART: a.quickstart || 'TODO — add install + run commands here.',
|
|
97
98
|
COST_MODE: a.costMode,
|
|
98
99
|
DATE: new Date().toISOString().slice(0, 10),
|
|
99
100
|
PERSONA_BLOCK: a.snark ? PERSONA_BLOCK : '',
|
package/src/steps/skills.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import { spawn, execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
3
6
|
import { log } from '../util/log.mjs';
|
|
4
7
|
|
|
5
8
|
// Well-known skill repos matched by stack term → repo + suggested skill names
|
|
@@ -13,6 +16,83 @@ const SKILL_CATALOG = [
|
|
|
13
16
|
{ terms: ['rust'], repo: 'anthropics/skills', skills: ['code-review'] },
|
|
14
17
|
];
|
|
15
18
|
|
|
19
|
+
// ── Environment detection ─────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function resolveSkillsBin() {
|
|
22
|
+
try {
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const pkgPath = require.resolve('skills/package.json');
|
|
25
|
+
const bin = join(dirname(pkgPath), 'bin', 'cli.mjs');
|
|
26
|
+
return existsSync(bin) ? bin : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function whichVersion(cmd) {
|
|
33
|
+
try {
|
|
34
|
+
return execFileSync(cmd, ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
35
|
+
.toString()
|
|
36
|
+
.trim()
|
|
37
|
+
.split('\n')[0];
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Detect whether the target workspace itself uses pnpm. */
|
|
44
|
+
function projectUsesPnpm(cwd) {
|
|
45
|
+
return existsSync(join(cwd, 'pnpm-lock.yaml')) || existsSync(join(cwd, 'pnpm-workspace.yaml'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detect the runtime environment.
|
|
50
|
+
* Returns { runner, label } where runner is a function (skillsArgs, cwd) → {cmd, args}.
|
|
51
|
+
*/
|
|
52
|
+
function detectEnv(cwd) {
|
|
53
|
+
const bundledBin = resolveSkillsBin();
|
|
54
|
+
const pnpmVer = whichVersion('pnpm');
|
|
55
|
+
const npxVer = whichVersion('npx');
|
|
56
|
+
const projectPnpm = projectUsesPnpm(cwd);
|
|
57
|
+
|
|
58
|
+
// ── Terse status lines ────────────────────────────────────────────
|
|
59
|
+
log.info(`skills CLI (bundled) ${bundledBin ? '✓' : '✗ not resolved'}`);
|
|
60
|
+
log.info(`pnpm ${pnpmVer ? `✓ ${pnpmVer}` : '✗ not found'}${projectPnpm ? ' (project uses pnpm)' : ''}`);
|
|
61
|
+
log.info(`npx ${npxVer ? `✓ ${npxVer}` : '✗ not found'}`);
|
|
62
|
+
|
|
63
|
+
// ── Priority: bundled > pnpm (if project or system) > npx ─────────
|
|
64
|
+
if (bundledBin) {
|
|
65
|
+
return {
|
|
66
|
+
label: 'bundled skills CLI',
|
|
67
|
+
runner: (skillsArgs) => ({ cmd: process.execPath, args: [bundledBin, ...skillsArgs] }),
|
|
68
|
+
pnpmVer,
|
|
69
|
+
npxVer,
|
|
70
|
+
projectPnpm,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (pnpmVer) {
|
|
74
|
+
return {
|
|
75
|
+
label: 'pnpm dlx',
|
|
76
|
+
runner: (skillsArgs) => ({ cmd: 'pnpm', args: ['dlx', 'skills', ...skillsArgs] }),
|
|
77
|
+
pnpmVer,
|
|
78
|
+
npxVer,
|
|
79
|
+
projectPnpm,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (npxVer) {
|
|
83
|
+
return {
|
|
84
|
+
label: 'npx',
|
|
85
|
+
runner: (skillsArgs) => ({ cmd: 'npx', args: ['-y', 'skills', ...skillsArgs] }),
|
|
86
|
+
pnpmVer,
|
|
87
|
+
npxVer,
|
|
88
|
+
projectPnpm,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Main entry ────────────────────────────────────────────────────────
|
|
95
|
+
|
|
16
96
|
export async function skillDiscovery({ cwd, answers, args }) {
|
|
17
97
|
if (!args.skills) {
|
|
18
98
|
log.dim('Skipping skills discovery (--no-skills).');
|
|
@@ -23,15 +103,43 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
23
103
|
return;
|
|
24
104
|
}
|
|
25
105
|
|
|
106
|
+
// ── Environment checks ──────────────────────────────────────────────
|
|
107
|
+
const env = detectEnv(cwd);
|
|
108
|
+
|
|
109
|
+
if (!env) {
|
|
110
|
+
log.warn('Cannot run skills CLI: no bundled binary, pnpm, or npx found.');
|
|
111
|
+
log.warn('Install Node.js (includes npx) or pnpm, then re-run.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Offer pnpm if the user doesn't have it
|
|
116
|
+
if (!env.pnpmVer) {
|
|
117
|
+
const { wantPnpm } = await prompts({
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'wantPnpm',
|
|
120
|
+
message: 'pnpm not found. Install it? (faster, stricter deps, saves disk)',
|
|
121
|
+
initial: false,
|
|
122
|
+
});
|
|
123
|
+
if (wantPnpm) {
|
|
124
|
+
try {
|
|
125
|
+
execFileSync('npm', ['install', '-g', 'pnpm'], { stdio: 'inherit' });
|
|
126
|
+
log.ok('pnpm installed.');
|
|
127
|
+
} catch (err) {
|
|
128
|
+
log.warn(`pnpm install failed: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
log.ok(`Running skills via ${env.label}`);
|
|
26
134
|
log.raw('');
|
|
27
|
-
|
|
135
|
+
|
|
136
|
+
// ── Skill discovery ─────────────────────────────────────────────────
|
|
28
137
|
|
|
29
138
|
// 1. Build recommendations from catalog based on stack
|
|
30
139
|
const recs = buildRecommendations(answers);
|
|
31
140
|
if (recs.length === 0) {
|
|
32
141
|
log.dim('No stack-specific recommendations found.');
|
|
33
142
|
} else {
|
|
34
|
-
log.raw('');
|
|
35
143
|
log.raw(` ${pad('Skill', 36)} ${pad('Repo', 36)}`);
|
|
36
144
|
log.raw(` ${'─'.repeat(36)} ${'─'.repeat(36)}`);
|
|
37
145
|
for (const r of recs) {
|
|
@@ -47,7 +155,6 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
47
155
|
log.raw('');
|
|
48
156
|
}
|
|
49
157
|
|
|
50
|
-
// 3. Offer to launch interactive find for each term
|
|
51
158
|
for (const term of terms) {
|
|
52
159
|
const { go } = await prompts({
|
|
53
160
|
type: 'confirm',
|
|
@@ -56,11 +163,11 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
56
163
|
initial: true,
|
|
57
164
|
});
|
|
58
165
|
if (go) {
|
|
59
|
-
await
|
|
166
|
+
await runSkills(env, ['find', term], cwd);
|
|
60
167
|
}
|
|
61
168
|
}
|
|
62
169
|
|
|
63
|
-
//
|
|
170
|
+
// 3. Offer to install from well-known repos
|
|
64
171
|
if (recs.length > 0) {
|
|
65
172
|
const { install } = await prompts({
|
|
66
173
|
type: 'multiselect',
|
|
@@ -75,7 +182,6 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
75
182
|
});
|
|
76
183
|
|
|
77
184
|
if (install && install.length > 0) {
|
|
78
|
-
// Group by repo
|
|
79
185
|
const byRepo = new Map();
|
|
80
186
|
for (const r of install) {
|
|
81
187
|
if (!byRepo.has(r.repo)) byRepo.set(r.repo, []);
|
|
@@ -84,16 +190,14 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
84
190
|
for (const [repo, skills] of byRepo) {
|
|
85
191
|
const skillArgs = skills.flatMap((s) => ['--skill', s]);
|
|
86
192
|
log.info(`Installing from ${repo}: ${skills.join(', ')}`);
|
|
87
|
-
await
|
|
88
|
-
'npx',
|
|
89
|
-
['-y', 'skills', 'add', repo, ...skillArgs, '-a', 'github-copilot'],
|
|
90
|
-
cwd,
|
|
91
|
-
);
|
|
193
|
+
await runSkills(env, ['add', repo, ...skillArgs, '-a', 'github-copilot'], cwd);
|
|
92
194
|
}
|
|
195
|
+
|
|
196
|
+
await relocateSkills(cwd);
|
|
93
197
|
}
|
|
94
198
|
}
|
|
95
199
|
|
|
96
|
-
//
|
|
200
|
+
// 4. Offer freeform catch-all
|
|
97
201
|
const { freeform } = await prompts({
|
|
98
202
|
type: 'confirm',
|
|
99
203
|
name: 'freeform',
|
|
@@ -101,12 +205,78 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
101
205
|
initial: false,
|
|
102
206
|
});
|
|
103
207
|
if (freeform) {
|
|
104
|
-
await
|
|
208
|
+
await runSkills(env, ['find'], cwd);
|
|
209
|
+
await relocateSkills(cwd);
|
|
105
210
|
}
|
|
106
211
|
|
|
107
212
|
log.dim('Done. Run `npx skills find` anytime to discover more.');
|
|
108
213
|
}
|
|
109
214
|
|
|
215
|
+
// ── Relocation ─────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
async function relocateSkills(cwd) {
|
|
218
|
+
const src = join(cwd, '.agents', 'skills');
|
|
219
|
+
const dest = join(cwd, '.github', 'skills');
|
|
220
|
+
|
|
221
|
+
if (!existsSync(src)) return;
|
|
222
|
+
|
|
223
|
+
const candidates = [];
|
|
224
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
225
|
+
if (!entry.isDirectory()) continue;
|
|
226
|
+
if (!existsSync(join(src, entry.name, 'SKILL.md'))) continue;
|
|
227
|
+
if (existsSync(join(dest, entry.name))) continue;
|
|
228
|
+
candidates.push(entry.name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (candidates.length === 0) return;
|
|
232
|
+
|
|
233
|
+
log.raw('');
|
|
234
|
+
log.info(`${candidates.length} skill${candidates.length > 1 ? 's' : ''} in .agents/skills/: ${candidates.join(', ')}`);
|
|
235
|
+
log.info('cli-five prefers .github/skills/ (co-located with agents & instructions).');
|
|
236
|
+
log.info('Both locations work — Copilot discovers either.');
|
|
237
|
+
|
|
238
|
+
const { move } = await prompts({
|
|
239
|
+
type: 'confirm',
|
|
240
|
+
name: 'move',
|
|
241
|
+
message: 'Move to .github/skills/?',
|
|
242
|
+
initial: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!move) {
|
|
246
|
+
log.dim('Keeping skills in .agents/skills/.');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let moved = 0;
|
|
251
|
+
for (const name of candidates) {
|
|
252
|
+
mkdirSync(dest, { recursive: true });
|
|
253
|
+
renameSync(join(src, name), join(dest, name));
|
|
254
|
+
moved++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
log.ok(`Moved ${moved} skill${moved > 1 ? 's' : ''} → .github/skills/`);
|
|
258
|
+
|
|
259
|
+
// Clean up empty dirs
|
|
260
|
+
try {
|
|
261
|
+
if (readdirSync(src).length === 0) {
|
|
262
|
+
rmSync(src, { recursive: true });
|
|
263
|
+
const agentsDir = join(cwd, '.agents');
|
|
264
|
+
if (existsSync(agentsDir) && readdirSync(agentsDir).length === 0) {
|
|
265
|
+
rmSync(agentsDir, { recursive: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
/* best-effort */
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Runner ─────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
function runSkills(env, skillsArgs, cwd) {
|
|
276
|
+
const { cmd, args } = env.runner(skillsArgs);
|
|
277
|
+
return runInteractive(cmd, args, cwd);
|
|
278
|
+
}
|
|
279
|
+
|
|
110
280
|
function buildRecommendations(a) {
|
|
111
281
|
const stackLower = (a.stack || []).map((s) => s.toLowerCase());
|
|
112
282
|
const fwLower = (a.frameworks || []).map((f) => f.toLowerCase());
|
|
@@ -54,6 +54,10 @@ Before writing code, read (if they exist):
|
|
|
54
54
|
|
|
55
55
|
Before finishing, if any implementation choice was made (library selection, pattern choice, API approach), append an entry to `decisions.md` using the format in that file. Skip silently if no decisions were made.
|
|
56
56
|
|
|
57
|
+
## README.md (MANDATORY)
|
|
58
|
+
|
|
59
|
+
After any session that adds, changes, or removes user-facing functionality, update `README.md` at the project root. The README must contain at minimum: project name & one-liner, **copy-paste quickstart commands** (install deps + run), usage notes, and tech stack. If `README.md` does not exist, create it as the FIRST file before any other work. A new developer must go from clone → running app in < 2 minutes.
|
|
60
|
+
|
|
57
61
|
## History (MANDATORY)
|
|
58
62
|
|
|
59
63
|
Before finishing, append at least one bullet to `histories/coder.md` below the `<!-- Append entries below this line -->` marker. Record: build quirks, API gotchas, pattern preferences, file structure observations, test insights. Format: `- YYYY-MM-DD: <learning>`. Skip only if the session had zero meaningful work.
|
|
@@ -23,6 +23,18 @@
|
|
|
23
23
|
- Read `PROJECT.md`, `STATE.md`, and `decisions.md` on entry.
|
|
24
24
|
- Before finishing: update `decisions.md` if any choice was made, update your `histories/<agent>.md` with at least one learning.
|
|
25
25
|
|
|
26
|
+
# README.md (MANDATORY)
|
|
27
|
+
- The project MUST have a `README.md` at the root at all times.
|
|
28
|
+
- After any session that adds, changes, or removes user-facing functionality, update `README.md`.
|
|
29
|
+
- The README must always contain at minimum:
|
|
30
|
+
1. **Project name & one-liner** — what this is.
|
|
31
|
+
2. **Quickstart** — exact commands to install dependencies and run the project locally (copy-paste ready, no placeholders).
|
|
32
|
+
3. **Usage** — how to use the main features.
|
|
33
|
+
4. **Tech stack** — languages, frameworks, key libraries.
|
|
34
|
+
- Keep it concise and user-friendly. A new developer should go from clone → running app in under 2 minutes.
|
|
35
|
+
- If the README does not exist yet, create it as the FIRST file before any other work.
|
|
36
|
+
- If unsure about run commands, inspect `package.json` scripts, `Makefile`, `Dockerfile`, or equivalent.
|
|
37
|
+
|
|
26
38
|
At the end of every agent session, before stopping, append a terse session summary to `agent-diary.md`
|
|
27
39
|
at the workspace root as a markdown section with format `## YYYY-MM-DD HH:MM` (24-hour time).
|
|
28
40
|
Include: 2-5 sentence summary of what was accomplished, include model used.
|