cli-five 0.1.0 → 0.1.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-five",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": {
@@ -6,6 +6,7 @@ import { confirmOverwriteIfNeeded } from '../steps/confirm.mjs';
6
6
  import { interview } from '../steps/interview.mjs';
7
7
  import { scaffold, summarize } from '../steps/scaffold.mjs';
8
8
  import { skillDiscovery } from '../steps/skills.mjs';
9
+ import { instructionGeneration } from '../steps/instructions.mjs';
9
10
  import { isGitRepo, gitInit } from '../util/git.mjs';
10
11
 
11
12
  export async function init(args) {
@@ -13,7 +14,7 @@ export async function init(args) {
13
14
  log.raw(kleur.bold().magenta('\ncli-five init') + kleur.gray(` ${cwd}`));
14
15
 
15
16
  // 1. Detect
16
- log.step('1/5 Detect workspace');
17
+ log.step('1/6 Detect workspace');
17
18
  const detected = detect(cwd);
18
19
  log.info(`Project: ${kleur.bold(detected.projectName)}`);
19
20
  log.info(`Mode: ${detected.isBrownfield ? kleur.yellow('brownfield') : kleur.green('greenfield')}`);
@@ -31,7 +32,7 @@ export async function init(args) {
31
32
  }
32
33
 
33
34
  // 3. Overwrite gate
34
- log.step('2/5 Confirm overwrites');
35
+ log.step('2/6 Confirm overwrites');
35
36
  const ok = await confirmOverwriteIfNeeded(detected, args);
36
37
  if (!ok) {
37
38
  log.warn('Aborted. Nothing written.');
@@ -40,7 +41,7 @@ export async function init(args) {
40
41
  if (!detected.hasAgents && !detected.hasCopilotInstructions) log.dim('No collisions.');
41
42
 
42
43
  // 4. Interview
43
- log.step('3/5 Interview');
44
+ log.step('3/6 Interview');
44
45
  const answers = await interview(detected, args);
45
46
 
46
47
  // CLI --cost-mode override
@@ -49,17 +50,28 @@ export async function init(args) {
49
50
  }
50
51
 
51
52
  // 5. Scaffold
52
- log.step('4/5 Scaffold');
53
+ log.step('4/6 Scaffold');
53
54
  const written = scaffold({ cwd, answers, args });
54
55
  if (args.dryRun) log.warn('--dry-run: no files written. Plan:');
55
56
  log.raw(summarize(written, cwd));
56
57
  if (!args.dryRun) log.ok(`Wrote ${written.length} files.`);
57
58
 
58
59
  // 6. Skill discovery
59
- log.step('5/5 Skill discovery');
60
+ log.step('5/6 Skill discovery');
60
61
  await skillDiscovery({ cwd, answers, args });
61
62
 
62
- // 7. Next steps
63
+ // 7. Custom instructions
64
+ log.step('6/6 Custom instructions');
65
+ const instrWritten = await instructionGeneration({ cwd, answers, args });
66
+ if (instrWritten && instrWritten.length > 0) {
67
+ if (args.dryRun) log.warn('--dry-run: instruction plan:');
68
+ for (const w of instrWritten) {
69
+ log.raw(` ${w.written ? '+' : '~'} ${w.path.replace(cwd + '/', '')}`);
70
+ }
71
+ if (!args.dryRun) log.ok(`Wrote ${instrWritten.length} instruction file${instrWritten.length > 1 ? 's' : ''}.`);
72
+ }
73
+
74
+ // 8. Next steps
63
75
  printNextSteps(answers);
64
76
  }
65
77
 
@@ -78,9 +90,9 @@ function printNextSteps(answers) {
78
90
  log.raw(` 3. Open Copilot Chat and verify the 5 agents appear (@ menu).`);
79
91
  log.raw(` 4. Ask the Orchestrator to do something:`);
80
92
  log.raw(kleur.gray(` @Orchestrator read PROJECT.md and propose Phase 1.`));
81
- log.raw(` 5. Generate stack-specific instructions:`);
82
- log.raw(kleur.gray(` Run /agent-customization in chat — it will read PROJECT.md`));
83
- log.raw(kleur.gray(` and produce .github/instructions/*.instructions.md files.`));
93
+ log.raw(` 5. Review generated instruction files in .github/instructions/.`);
94
+ log.raw(kleur.gray(` Edit applyTo globs and guidelines to fit your project.`));
95
+ log.raw(kleur.gray(` Installed skills are in .agents/skills/ (from skills.sh).`));
84
96
  log.raw('');
85
97
  log.raw(kleur.dim('Edit cost mode anytime by changing `model:` in .github/agents/*.agent.md.'));
86
98
  log.raw('');
@@ -0,0 +1,333 @@
1
+ import prompts from 'prompts';
2
+ import { join } from 'node:path';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { log } from '../util/log.mjs';
5
+ import { writeFile } from '../util/fs.mjs';
6
+
7
+ /**
8
+ * Maps detected stacks to instruction file templates.
9
+ * `skillHints` keys are skill names — only referenced if the skill is actually installed.
10
+ */
11
+ const INSTRUCTION_CATALOG = [
12
+ {
13
+ id: 'typescript',
14
+ stackIds: ['node-ts', 'node'],
15
+ filename: 'typescript.instructions.md',
16
+ applyTo: '**/*.ts,**/*.tsx,**/*.js,**/*.jsx',
17
+ label: 'TypeScript / JavaScript',
18
+ guidelines: [
19
+ 'Prefer strict TypeScript (`strict: true`). Avoid `any`; use `unknown` with type guards.',
20
+ 'Use named exports over default exports for better discoverability.',
21
+ 'Prefer `interface` over `type` for object shapes that may be extended.',
22
+ 'Keep files under 300 lines. Extract helpers into co-located modules.',
23
+ ],
24
+ skillHints: {
25
+ 'vercel-react-best-practices':
26
+ 'Use the **vercel-react-best-practices** skill when writing or reviewing React / Next.js components for performance.',
27
+ 'vercel-composition-patterns':
28
+ 'Use the **vercel-composition-patterns** skill when refactoring components with boolean-prop proliferation or designing reusable component APIs.',
29
+ 'frontend-design':
30
+ 'Use the **frontend-design** skill when building new UI components, pages, or layouts.',
31
+ },
32
+ },
33
+ {
34
+ id: 'python',
35
+ stackIds: ['python'],
36
+ filename: 'python.instructions.md',
37
+ applyTo: '**/*.py',
38
+ label: 'Python',
39
+ guidelines: [
40
+ 'Use type hints on all public functions and methods.',
41
+ 'Prefer `pathlib.Path` over `os.path`.',
42
+ 'Use `dataclasses` or `pydantic` for structured data.',
43
+ 'Follow PEP 8. Keep line length ≤ 100 chars.',
44
+ ],
45
+ skillHints: {
46
+ 'python-best-practices':
47
+ 'Use the **python-best-practices** skill for idiomatic Python patterns and code review.',
48
+ },
49
+ },
50
+ {
51
+ id: 'kotlin',
52
+ stackIds: ['kotlin-android', 'gradle'],
53
+ filename: 'kotlin.instructions.md',
54
+ applyTo: '**/*.kt,**/*.kts',
55
+ label: 'Kotlin',
56
+ guidelines: [
57
+ 'Use `data class` for value objects.',
58
+ 'Prefer `val` over `var`; favour immutability.',
59
+ 'Use sealed classes / interfaces for restricted hierarchies.',
60
+ 'Follow Kotlin coding conventions (kotlinlang.org/docs/coding-conventions.html).',
61
+ ],
62
+ skillHints: {},
63
+ },
64
+ {
65
+ id: 'dotnet',
66
+ stackIds: ['dotnet'],
67
+ filename: 'dotnet.instructions.md',
68
+ applyTo: '**/*.cs,**/*.fs',
69
+ label: '.NET / C#',
70
+ guidelines: [
71
+ 'Use `record` types for immutable DTOs.',
72
+ 'Prefer `async/await` throughout; avoid `.Result` or `.Wait()`.',
73
+ 'Follow .NET naming conventions (PascalCase for public, _camelCase for private fields).',
74
+ 'Target nullable reference types (`<Nullable>enable</Nullable>`).',
75
+ ],
76
+ skillHints: {
77
+ 'microsoft-foundry':
78
+ 'Use the **microsoft-foundry** skill for Azure / Foundry deployment patterns.',
79
+ },
80
+ },
81
+ {
82
+ id: 'rust',
83
+ stackIds: ['rust'],
84
+ filename: 'rust.instructions.md',
85
+ applyTo: '**/*.rs',
86
+ label: 'Rust',
87
+ guidelines: [
88
+ 'Prefer owned types in public APIs; borrow internally.',
89
+ 'Use `thiserror` for library errors, `anyhow` for application errors.',
90
+ 'Run `cargo clippy -- -D warnings` before committing.',
91
+ 'Keep `unsafe` blocks minimal, documented, and audited.',
92
+ ],
93
+ skillHints: {
94
+ 'code-review': 'Use the **code-review** skill for thorough Rust code review.',
95
+ },
96
+ },
97
+ {
98
+ id: 'go',
99
+ stackIds: ['go'],
100
+ filename: 'go.instructions.md',
101
+ applyTo: '**/*.go',
102
+ label: 'Go',
103
+ guidelines: [
104
+ 'Follow Effective Go and the Go Code Review Comments guide.',
105
+ 'Return errors; do not panic in library code.',
106
+ 'Use `context.Context` as the first parameter for cancellable operations.',
107
+ 'Run `go vet` and `staticcheck` before committing.',
108
+ ],
109
+ skillHints: {},
110
+ },
111
+ {
112
+ id: 'ruby',
113
+ stackIds: ['ruby'],
114
+ filename: 'ruby.instructions.md',
115
+ applyTo: '**/*.rb,**/*.rake',
116
+ label: 'Ruby',
117
+ guidelines: [
118
+ 'Follow the Ruby Style Guide.',
119
+ 'Prefer `frozen_string_literal: true` at the top of every file.',
120
+ 'Use keyword arguments for methods with 3+ parameters.',
121
+ 'Keep controller actions thin; push logic into models or service objects.',
122
+ ],
123
+ skillHints: {},
124
+ },
125
+ {
126
+ id: 'php',
127
+ stackIds: ['php'],
128
+ filename: 'php.instructions.md',
129
+ applyTo: '**/*.php',
130
+ label: 'PHP',
131
+ guidelines: [
132
+ 'Use strict types (`declare(strict_types=1)`).',
133
+ 'Follow PSR-12 coding style.',
134
+ 'Use type declarations for parameters and return types.',
135
+ 'Prefer dependency injection over static calls.',
136
+ ],
137
+ skillHints: {},
138
+ },
139
+ {
140
+ id: 'java',
141
+ stackIds: ['maven', 'gradle'],
142
+ filename: 'java.instructions.md',
143
+ applyTo: '**/*.java',
144
+ label: 'Java',
145
+ guidelines: [
146
+ 'Use `record` types (Java 16+) for immutable value objects.',
147
+ 'Prefer `var` for local variables when the type is obvious.',
148
+ 'Follow Google Java Style Guide.',
149
+ 'Use `Optional` for nullable return types; never pass `null` as a parameter.',
150
+ ],
151
+ skillHints: {},
152
+ },
153
+ {
154
+ id: 'css',
155
+ stackIds: ['node-ts', 'node'],
156
+ filename: 'css.instructions.md',
157
+ applyTo: '**/*.css,**/*.scss,**/*.less',
158
+ label: 'CSS / Styling',
159
+ guidelines: [
160
+ 'Prefer CSS custom properties (variables) over hard-coded values.',
161
+ 'Use logical properties (`inline-size`, `block-size`) for internationalization.',
162
+ 'Keep specificity low; prefer class selectors over IDs or element selectors.',
163
+ ],
164
+ skillHints: {
165
+ 'frontend-design':
166
+ 'Use the **frontend-design** skill when designing distinctive, high-quality UI.',
167
+ 'web-design-guidelines':
168
+ 'Use the **web-design-guidelines** skill to audit UI against Web Interface Guidelines.',
169
+ },
170
+ },
171
+ ];
172
+
173
+ export async function instructionGeneration({ cwd, answers, args }) {
174
+ if (args.yes) {
175
+ log.dim('Non-interactive mode — generating all matching instructions.');
176
+ return generateAll(cwd, answers, args);
177
+ }
178
+
179
+ const installedSkills = detectInstalledSkills(cwd);
180
+ const candidates = matchCandidates(answers, installedSkills);
181
+
182
+ if (candidates.length === 0) {
183
+ log.dim('No stack-specific instruction templates matched.');
184
+ return [];
185
+ }
186
+
187
+ log.raw('');
188
+ log.raw(' Matched instruction templates:');
189
+ log.raw('');
190
+ for (const c of candidates) {
191
+ const skillCount = c.matchedSkills.length;
192
+ const suffix = skillCount ? ` (${skillCount} skill${skillCount > 1 ? 's' : ''} linked)` : '';
193
+ log.raw(` ${c.label.padEnd(28)} → .github/instructions/${c.filename}${suffix}`);
194
+ }
195
+ log.raw('');
196
+
197
+ const { selected } = await prompts({
198
+ type: 'multiselect',
199
+ name: 'selected',
200
+ message: 'Generate instruction files?',
201
+ choices: candidates.map((c) => ({
202
+ title: `${c.label} (${c.applyTo})`,
203
+ value: c,
204
+ selected: true,
205
+ })),
206
+ hint: 'Space to toggle, Enter to confirm',
207
+ });
208
+
209
+ if (!selected || selected.length === 0) {
210
+ log.dim('No instructions generated.');
211
+ return [];
212
+ }
213
+
214
+ return writeInstructions(cwd, selected, args);
215
+ }
216
+
217
+ function generateAll(cwd, answers, args) {
218
+ const installedSkills = detectInstalledSkills(cwd);
219
+ const candidates = matchCandidates(answers, installedSkills);
220
+ return writeInstructions(cwd, candidates, args);
221
+ }
222
+
223
+ function matchCandidates(answers, installedSkills) {
224
+ const detectedIds = new Set((answers.stack || []).map(stackLabelToId));
225
+ const out = [];
226
+ const seen = new Set();
227
+
228
+ for (const entry of INSTRUCTION_CATALOG) {
229
+ if (seen.has(entry.id)) continue;
230
+ const matches = entry.stackIds.some((sid) => detectedIds.has(sid));
231
+ if (!matches) continue;
232
+ seen.add(entry.id);
233
+
234
+ const matchedSkills = Object.keys(entry.skillHints).filter((s) => installedSkills.has(s));
235
+ out.push({ ...entry, matchedSkills });
236
+ }
237
+
238
+ return out;
239
+ }
240
+
241
+ function writeInstructions(cwd, candidates, args) {
242
+ const written = [];
243
+ for (const c of candidates) {
244
+ const content = renderInstruction(c);
245
+ const target = join(cwd, '.github', 'instructions', c.filename);
246
+ written.push(writeFile(target, content, args));
247
+ }
248
+ return written;
249
+ }
250
+
251
+ function renderInstruction(entry) {
252
+ const lines = [];
253
+ lines.push('---');
254
+ lines.push(`applyTo: "${entry.applyTo}"`);
255
+ lines.push('---');
256
+ lines.push('');
257
+ lines.push(`# ${entry.label}`);
258
+ lines.push('');
259
+
260
+ for (const g of entry.guidelines) {
261
+ lines.push(`- ${g}`);
262
+ }
263
+
264
+ const activeHints = entry.matchedSkills
265
+ .map((s) => entry.skillHints[s])
266
+ .filter(Boolean);
267
+
268
+ if (activeHints.length > 0) {
269
+ lines.push('');
270
+ lines.push('## Skills');
271
+ lines.push('');
272
+ for (const h of activeHints) {
273
+ lines.push(`- ${h}`);
274
+ }
275
+ }
276
+
277
+ lines.push('');
278
+ return lines.join('\n');
279
+ }
280
+
281
+ /**
282
+ * Scan .agents/skills/ and .github/skills/ for installed skill directories.
283
+ */
284
+ function detectInstalledSkills(cwd) {
285
+ const skills = new Set();
286
+ for (const dir of [join(cwd, '.agents', 'skills'), join(cwd, '.github', 'skills')]) {
287
+ if (!existsSync(dir)) continue;
288
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
289
+ if (entry.isDirectory() && existsSync(join(dir, entry.name, 'SKILL.md'))) {
290
+ skills.add(entry.name);
291
+ }
292
+ }
293
+ }
294
+
295
+ // Also check skills-lock.json
296
+ const lockPath = join(cwd, 'skills-lock.json');
297
+ if (existsSync(lockPath)) {
298
+ try {
299
+ const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
300
+ if (lock.skills) {
301
+ for (const name of Object.keys(lock.skills)) {
302
+ skills.add(name);
303
+ }
304
+ }
305
+ } catch {
306
+ /* ignore */
307
+ }
308
+ }
309
+
310
+ return skills;
311
+ }
312
+
313
+ /**
314
+ * Best-effort reverse mapping from interview label → STACK_SIGNATURES id.
315
+ * Falls through to lowercase comparison.
316
+ */
317
+ function stackLabelToId(label) {
318
+ const LABEL_MAP = {
319
+ 'node + typescript': 'node-ts',
320
+ 'node': 'node',
321
+ 'python': 'python',
322
+ '.net': 'dotnet',
323
+ 'rust': 'rust',
324
+ 'go': 'go',
325
+ 'kotlin / android': 'kotlin-android',
326
+ 'gradle (jvm)': 'gradle',
327
+ 'maven (jvm)': 'maven',
328
+ 'ruby': 'ruby',
329
+ 'php': 'php',
330
+ };
331
+ const lower = (label || '').toLowerCase().trim();
332
+ return LABEL_MAP[lower] || lower;
333
+ }
@@ -8,7 +8,7 @@ Each file should:
8
8
  - include an `applyTo` glob (e.g. `**/*.ts`) OR a `description` for on-demand loading
9
9
  - be terse — instructions burn context every time they load
10
10
 
11
- Generate stack-appropriate instructions by running `/agent-customization` in Copilot Chat
12
- after `npx cli-five init`. It will read `PROJECT.md` and propose files based on your stack.
11
+ Stack-specific instructions are generated during `npx cli-five init` (the instructions step).
12
+ Re-run `npx cli-five init` to regenerate, or create them manually.
13
13
 
14
14
  Reference: https://code.visualstudio.com/docs/copilot/copilot-customization
@@ -1,14 +1,21 @@
1
- # .github/skills/
1
+ # Skills
2
2
 
3
- Drop project-local skills here as `<skill-name>/SKILL.md` directories.
3
+ VS Code Copilot loads skills from **two** directories:
4
+
5
+ | Location | Purpose |
6
+ |----------|---------|
7
+ | `.agents/skills/<name>/SKILL.md` | Default for `npx skills add -a github-copilot` (CLI-installed) |
8
+ | `.github/skills/<name>/SKILL.md` | Hand-crafted or project-specific skills |
9
+
10
+ Both are first-class. The `skills` CLI writes to `.agents/skills/` for `github-copilot`.
11
+ Either directory works — Copilot discovers both.
4
12
 
5
- Skills are on-demand workflows with bundled assets (scripts, templates, references).
6
13
  The `description` field IS the discovery surface — without trigger phrases in it, no agent
7
14
  will ever load the skill.
8
15
 
9
- Discover community skills:
16
+ ## Discover & install community skills
10
17
 
11
- npx skills search <term>
12
- npx skills add <skill-id>
18
+ npx skills find <term>
19
+ npx skills add <repo> -a github-copilot
13
20
 
14
21
  Sources: https://skills.sh, https://github.com/anthropics/skills, https://github.com/github/awesome-copilot