aped-method 1.0.0 → 1.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/package.json +1 -1
- package/src/index.js +308 -60
- package/src/templates/commands.js +10 -0
- package/src/templates/config.js +38 -1
- package/src/templates/guardrail.js +9 -1
- package/src/templates/skills.js +106 -19
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync as writeFS } from 'node:fs';
|
|
2
3
|
import { stdin, stdout } from 'node:process';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
import { scaffold } from './scaffold.js';
|
|
4
7
|
|
|
8
|
+
// ── CLI version (read from package.json) ──
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
11
|
+
|
|
5
12
|
const DEFAULTS = {
|
|
6
13
|
apedDir: '.aped',
|
|
7
14
|
outputDir: 'docs/aped',
|
|
@@ -10,8 +17,13 @@ const DEFAULTS = {
|
|
|
10
17
|
projectName: '',
|
|
11
18
|
communicationLang: 'english',
|
|
12
19
|
documentLang: 'english',
|
|
20
|
+
ticketSystem: 'none',
|
|
21
|
+
gitProvider: 'github',
|
|
13
22
|
};
|
|
14
23
|
|
|
24
|
+
const TICKET_OPTIONS = ['none', 'jira', 'linear', 'github-issues', 'gitlab-issues'];
|
|
25
|
+
const GIT_OPTIONS = ['github', 'gitlab', 'bitbucket'];
|
|
26
|
+
|
|
15
27
|
// ── ANSI helpers ──
|
|
16
28
|
const ESC = '\x1b';
|
|
17
29
|
const a = {
|
|
@@ -27,24 +39,16 @@ const a = {
|
|
|
27
39
|
magenta: `${ESC}[35m`,
|
|
28
40
|
cyan: `${ESC}[36m`,
|
|
29
41
|
white: `${ESC}[37m`,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
spring: `${ESC}[38;5;82m`, // spring green
|
|
42
|
+
lime: `${ESC}[38;5;118m`,
|
|
43
|
+
emerald: `${ESC}[38;5;42m`,
|
|
44
|
+
mint: `${ESC}[38;5;48m`,
|
|
45
|
+
forest: `${ESC}[38;5;34m`,
|
|
46
|
+
spring: `${ESC}[38;5;82m`,
|
|
36
47
|
};
|
|
37
48
|
|
|
38
49
|
const bold = (s) => `${a.bold}${s}${a.reset}`;
|
|
39
50
|
const dim = (s) => `${a.dim}${s}${a.reset}`;
|
|
40
|
-
const green = (s) => `${a.green}${s}${a.reset}`;
|
|
41
|
-
const lime = (s) => `${a.lime}${s}${a.reset}`;
|
|
42
|
-
const emerald = (s) => `${a.emerald}${s}${a.reset}`;
|
|
43
|
-
const mint = (s) => `${a.mint}${s}${a.reset}`;
|
|
44
51
|
const yellow = (s) => `${a.yellow}${s}${a.reset}`;
|
|
45
|
-
const magenta = (s) => `${a.magenta}${s}${a.reset}`;
|
|
46
|
-
const red = (s) => `${a.red}${s}${a.reset}`;
|
|
47
|
-
const cyan = (s) => `${a.cyan}${s}${a.reset}`;
|
|
48
52
|
|
|
49
53
|
// ── ASCII Art Logo ──
|
|
50
54
|
const LOGO = `
|
|
@@ -69,7 +73,7 @@ function createSpinner(text) {
|
|
|
69
73
|
let interval;
|
|
70
74
|
return {
|
|
71
75
|
start() {
|
|
72
|
-
process.stdout.write('\x1b[?25l');
|
|
76
|
+
process.stdout.write('\x1b[?25l');
|
|
73
77
|
interval = setInterval(() => {
|
|
74
78
|
const frame = SPINNER_FRAMES[i % SPINNER_FRAMES.length];
|
|
75
79
|
process.stdout.write(`\r ${a.emerald}${frame}${a.reset} ${text}`);
|
|
@@ -79,7 +83,7 @@ function createSpinner(text) {
|
|
|
79
83
|
stop(finalText) {
|
|
80
84
|
clearInterval(interval);
|
|
81
85
|
process.stdout.write(`\r ${a.lime}${a.bold}✓${a.reset} ${finalText}\x1b[K\n`);
|
|
82
|
-
process.stdout.write('\x1b[?25h');
|
|
86
|
+
process.stdout.write('\x1b[?25h');
|
|
83
87
|
},
|
|
84
88
|
fail(finalText) {
|
|
85
89
|
clearInterval(interval);
|
|
@@ -93,7 +97,6 @@ function sleep(ms) {
|
|
|
93
97
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
// ── Section display ──
|
|
97
100
|
function sectionHeader(title) {
|
|
98
101
|
console.log('');
|
|
99
102
|
console.log(` ${a.emerald}${a.bold}┌─${a.reset} ${a.bold}${title}${a.reset}`);
|
|
@@ -104,12 +107,55 @@ function sectionEnd() {
|
|
|
104
107
|
console.log(` ${a.emerald}${a.bold}└──────────────────────────────────${a.reset}`);
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
// ── Semver compare (1=a>b, -1=a<b, 0=equal) ──
|
|
111
|
+
function semverCompare(va, vb) {
|
|
112
|
+
const pa = va.split('.').map(Number);
|
|
113
|
+
const pb = vb.split('.').map(Number);
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
116
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Detect existing install & read config ──
|
|
122
|
+
function detectExisting(apedDir) {
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
const configPath = join(cwd, apedDir, 'config.yaml');
|
|
125
|
+
if (!existsSync(configPath)) return null;
|
|
126
|
+
|
|
127
|
+
// Parse existing config.yaml (simple key: value)
|
|
128
|
+
const existing = {};
|
|
129
|
+
try {
|
|
130
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
131
|
+
for (const line of content.split('\n')) {
|
|
132
|
+
const match = line.match(/^(\w[\w_]*)\s*:\s*(.+)$/);
|
|
133
|
+
if (match) existing[match[1]] = match[2].trim().replace(/^["']|["']$/g, '');
|
|
134
|
+
}
|
|
135
|
+
} catch { /* ignore */ }
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
projectName: existing.project_name || '',
|
|
139
|
+
authorName: existing.user_name || '',
|
|
140
|
+
communicationLang: existing.communication_language || DEFAULTS.communicationLang,
|
|
141
|
+
documentLang: existing.document_output_language || DEFAULTS.documentLang,
|
|
142
|
+
apedDir: existing.aped_path || apedDir,
|
|
143
|
+
outputDir: existing.output_path || DEFAULTS.outputDir,
|
|
144
|
+
ticketSystem: existing.ticket_system || DEFAULTS.ticketSystem,
|
|
145
|
+
gitProvider: existing.git_provider || DEFAULTS.gitProvider,
|
|
146
|
+
installedVersion: existing.aped_version || '0.0.0',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
// ── Args ──
|
|
108
151
|
function parseArgs(argv) {
|
|
109
152
|
const args = {};
|
|
110
153
|
for (let i = 2; i < argv.length; i++) {
|
|
111
154
|
const arg = argv[i];
|
|
112
155
|
if (arg === '--yes' || arg === '-y') { args.yes = true; continue; }
|
|
156
|
+
if (arg === '--update' || arg === '-u') { args.mode = 'update'; continue; }
|
|
157
|
+
if (arg === '--fresh' || arg === '--force') { args.mode = 'fresh'; continue; }
|
|
158
|
+
if (arg === '--version' || arg === '-v') { args.version = true; continue; }
|
|
113
159
|
const match = arg.match(/^--(\w[\w-]*)=(.+)$/);
|
|
114
160
|
if (match) {
|
|
115
161
|
const key = match[1].replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
|
|
@@ -147,33 +193,65 @@ export async function run() {
|
|
|
147
193
|
|
|
148
194
|
let detectedProject = '';
|
|
149
195
|
try {
|
|
150
|
-
const { readFileSync } = await import('node:fs');
|
|
151
196
|
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
|
|
152
197
|
detectedProject = pkg.name || '';
|
|
153
198
|
} catch {
|
|
154
199
|
detectedProject = process.cwd().split('/').pop();
|
|
155
200
|
}
|
|
156
201
|
|
|
202
|
+
// ── Version flag ──
|
|
203
|
+
if (args.version) {
|
|
204
|
+
console.log(`aped-method v${CLI_VERSION}`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
157
208
|
// ── Banner ──
|
|
158
209
|
console.log(LOGO);
|
|
210
|
+
console.log(`${a.dim} v${CLI_VERSION}${a.reset}`);
|
|
211
|
+
console.log('');
|
|
159
212
|
console.log(PIPELINE);
|
|
160
213
|
console.log('');
|
|
161
214
|
|
|
215
|
+
// ── Detect existing installation ──
|
|
216
|
+
const apedDir = args.aped || args.apedDir || DEFAULTS.apedDir;
|
|
217
|
+
const existing = detectExisting(apedDir);
|
|
218
|
+
|
|
219
|
+
// ── Version upgrade detection ──
|
|
220
|
+
if (existing && existing.installedVersion !== '0.0.0') {
|
|
221
|
+
if (existing.installedVersion === CLI_VERSION) {
|
|
222
|
+
console.log(` ${a.lime}${a.bold}✓${a.reset} ${dim(`Installed: v${existing.installedVersion} — up to date`)}`);
|
|
223
|
+
} else if (semverCompare(CLI_VERSION, existing.installedVersion) > 0) {
|
|
224
|
+
console.log(` ${a.yellow}${a.bold}↑${a.reset} ${bold(`Upgrade available: v${existing.installedVersion} → v${CLI_VERSION}`)}`);
|
|
225
|
+
console.log(` ${dim(' Run with --update to upgrade engine files')}`);
|
|
226
|
+
} else {
|
|
227
|
+
console.log(` ${a.yellow}${a.bold}!${a.reset} ${dim(`Installed v${existing.installedVersion} is newer than CLI v${CLI_VERSION}`)}`);
|
|
228
|
+
}
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
|
|
162
232
|
if (args.yes) {
|
|
233
|
+
// Non-interactive mode
|
|
234
|
+
let mode = args.mode || (existing ? 'update' : 'install');
|
|
235
|
+
|
|
236
|
+
const defaults = existing || DEFAULTS;
|
|
163
237
|
const config = {
|
|
164
|
-
projectName: args.project || args.projectName || detectedProject || 'my-project',
|
|
165
|
-
authorName: args.author || args.authorName || '',
|
|
166
|
-
communicationLang: args.lang || args.communicationLang ||
|
|
167
|
-
documentLang: args.docLang || args.documentLang ||
|
|
168
|
-
apedDir: args.aped || args.apedDir || DEFAULTS.apedDir,
|
|
169
|
-
outputDir: args.output || args.outputDir || DEFAULTS.outputDir,
|
|
238
|
+
projectName: args.project || args.projectName || defaults.projectName || detectedProject || 'my-project',
|
|
239
|
+
authorName: args.author || args.authorName || defaults.authorName || '',
|
|
240
|
+
communicationLang: args.lang || args.communicationLang || defaults.communicationLang,
|
|
241
|
+
documentLang: args.docLang || args.documentLang || defaults.documentLang,
|
|
242
|
+
apedDir: args.aped || args.apedDir || defaults.apedDir || DEFAULTS.apedDir,
|
|
243
|
+
outputDir: args.output || args.outputDir || defaults.outputDir || DEFAULTS.outputDir,
|
|
170
244
|
commandsDir: args.commands || args.commandsDir || DEFAULTS.commandsDir,
|
|
245
|
+
ticketSystem: args.tickets || args.ticketSystem || defaults.ticketSystem || DEFAULTS.ticketSystem,
|
|
246
|
+
gitProvider: args.git || args.gitProvider || defaults.gitProvider || DEFAULTS.gitProvider,
|
|
247
|
+
cliVersion: CLI_VERSION,
|
|
171
248
|
};
|
|
172
249
|
|
|
173
|
-
await runScaffold(config);
|
|
250
|
+
await runScaffold(config, mode);
|
|
174
251
|
return;
|
|
175
252
|
}
|
|
176
253
|
|
|
254
|
+
// ── Interactive mode ──
|
|
177
255
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
178
256
|
|
|
179
257
|
const prompt = stdinLines
|
|
@@ -186,20 +264,58 @@ export async function run() {
|
|
|
186
264
|
: (question, def) => ask(rl, question, def);
|
|
187
265
|
|
|
188
266
|
try {
|
|
267
|
+
let mode = 'install';
|
|
268
|
+
|
|
269
|
+
// ── Existing install detected ──
|
|
270
|
+
if (existing) {
|
|
271
|
+
const vInfo = existing.installedVersion !== '0.0.0'
|
|
272
|
+
? ` | v${existing.installedVersion}`
|
|
273
|
+
: '';
|
|
274
|
+
console.log(` ${a.yellow}${a.bold}⚠${a.reset} ${bold('Existing APED installation detected')}`);
|
|
275
|
+
console.log(` ${dim(` Project: ${existing.projectName}${vInfo}`)}`);
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log(` ${a.emerald}│${a.reset} ${bold('1')} ${dim('Update engine')} ${dim('— upgrade skills, scripts, hooks. Preserve config & artifacts')}`);
|
|
278
|
+
console.log(` ${a.emerald}│${a.reset} ${bold('2')} ${dim('Fresh install')} ${dim('— delete everything and start from zero')}`);
|
|
279
|
+
console.log(` ${a.emerald}│${a.reset} ${bold('3')} ${dim('Cancel')}`);
|
|
280
|
+
console.log(` ${a.emerald}│${a.reset}`);
|
|
281
|
+
|
|
282
|
+
const choice = await prompt(`${bold('Choice')}`, '1');
|
|
283
|
+
|
|
284
|
+
if (choice === '3' || choice.toLowerCase() === 'n') {
|
|
285
|
+
console.log(`\n ${yellow('Cancelled.')}\n`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
mode = choice === '2' ? 'fresh' : 'update';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Config prompts ──
|
|
293
|
+
// For update: pre-fill with existing values
|
|
294
|
+
const defaults = (mode === 'update' && existing) ? existing : DEFAULTS;
|
|
295
|
+
|
|
189
296
|
sectionHeader('Configuration');
|
|
190
297
|
|
|
191
298
|
const config = {};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
299
|
+
if (mode === 'update') {
|
|
300
|
+
// In update mode, show current config and only ask what to change
|
|
301
|
+
console.log(` ${a.emerald}│${a.reset} ${dim('Current config loaded. Press Enter to keep, or type new value.')}`);
|
|
302
|
+
console.log(` ${a.emerald}│${a.reset}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
config.projectName = await prompt(`${bold('Project name')}`, defaults.projectName || detectedProject);
|
|
306
|
+
config.authorName = await prompt(`${bold('Author')}`, defaults.authorName);
|
|
307
|
+
config.communicationLang = await prompt(`${bold('Communication language')}`, defaults.communicationLang);
|
|
308
|
+
config.documentLang = await prompt(`${bold('Document language')}`, defaults.documentLang);
|
|
309
|
+
config.apedDir = await prompt(`${bold('APED dir')} ${dim('(engine)')}`, defaults.apedDir || DEFAULTS.apedDir);
|
|
310
|
+
config.outputDir = await prompt(`${bold('Output dir')} ${dim('(artifacts)')}`, defaults.outputDir || DEFAULTS.outputDir);
|
|
198
311
|
config.commandsDir = await prompt(`${bold('Commands dir')}`, DEFAULTS.commandsDir);
|
|
312
|
+
config.ticketSystem = await prompt(`${bold('Ticket system')} ${dim(`(${TICKET_OPTIONS.join('/')})`)}`, defaults.ticketSystem || DEFAULTS.ticketSystem);
|
|
313
|
+
config.gitProvider = await prompt(`${bold('Git provider')} ${dim(`(${GIT_OPTIONS.join('/')})`)}`, defaults.gitProvider || DEFAULTS.gitProvider);
|
|
314
|
+
config.cliVersion = CLI_VERSION;
|
|
199
315
|
|
|
200
316
|
sectionEnd();
|
|
201
317
|
console.log('');
|
|
202
|
-
printConfig(config);
|
|
318
|
+
printConfig(config, mode);
|
|
203
319
|
console.log('');
|
|
204
320
|
|
|
205
321
|
const confirm = await prompt(`${bold('Proceed?')}`, 'Y');
|
|
@@ -208,50 +324,78 @@ export async function run() {
|
|
|
208
324
|
return;
|
|
209
325
|
}
|
|
210
326
|
|
|
211
|
-
await runScaffold(config);
|
|
327
|
+
await runScaffold(config, mode);
|
|
212
328
|
} finally {
|
|
213
329
|
rl.close();
|
|
214
330
|
}
|
|
215
331
|
}
|
|
216
332
|
|
|
217
|
-
async function runScaffold(config) {
|
|
218
|
-
|
|
333
|
+
async function runScaffold(config, mode) {
|
|
334
|
+
const modeLabel = mode === 'update' ? 'Updating' : mode === 'fresh' ? 'Fresh installing' : 'Installing';
|
|
335
|
+
const modeTag = mode === 'update'
|
|
336
|
+
? `${a.yellow}${a.bold}UPDATE${a.reset}`
|
|
337
|
+
: mode === 'fresh'
|
|
338
|
+
? `${a.red}${a.bold}FRESH${a.reset}`
|
|
339
|
+
: `${a.lime}${a.bold}INSTALL${a.reset}`;
|
|
340
|
+
|
|
341
|
+
console.log(` ${modeTag} ${dim(modeLabel + ' APED Method...')}`);
|
|
342
|
+
console.log('');
|
|
343
|
+
|
|
344
|
+
// ── Phase 1: Clean if fresh ──
|
|
345
|
+
if (mode === 'fresh') {
|
|
346
|
+
const s0 = createSpinner('Removing existing installation...');
|
|
347
|
+
s0.start();
|
|
348
|
+
const { rmSync } = await import('node:fs');
|
|
349
|
+
const cwd = process.cwd();
|
|
350
|
+
try { rmSync(join(cwd, config.apedDir), { recursive: true, force: true }); } catch { /* ok */ }
|
|
351
|
+
try { rmSync(join(cwd, config.outputDir), { recursive: true, force: true }); } catch { /* ok */ }
|
|
352
|
+
// Don't delete commands dir — may have non-aped commands
|
|
353
|
+
// Delete only aped-* command files
|
|
354
|
+
const { readdirSync } = await import('node:fs');
|
|
355
|
+
try {
|
|
356
|
+
const cmdDir = join(cwd, config.commandsDir);
|
|
357
|
+
for (const f of readdirSync(cmdDir)) {
|
|
358
|
+
if (f.startsWith('aped-')) {
|
|
359
|
+
rmSync(join(cmdDir, f), { force: true });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch { /* ok */ }
|
|
363
|
+
await sleep(300);
|
|
364
|
+
s0.stop('Previous installation removed');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── Phase 2: Validate ──
|
|
219
368
|
const s1 = createSpinner('Validating configuration...');
|
|
220
369
|
s1.start();
|
|
221
370
|
await sleep(400);
|
|
222
371
|
s1.stop('Configuration validated');
|
|
223
372
|
|
|
224
|
-
// ── Phase
|
|
225
|
-
|
|
226
|
-
s2.start();
|
|
227
|
-
await sleep(300);
|
|
228
|
-
s2.stop('Directory structure ready');
|
|
229
|
-
|
|
230
|
-
// ── Phase 3: Scaffolding ──
|
|
231
|
-
sectionHeader('Scaffolding Pipeline');
|
|
373
|
+
// ── Phase 3: Scaffold ──
|
|
374
|
+
sectionHeader(`Scaffolding Pipeline ${dim(`(${mode})`)}`);
|
|
232
375
|
console.log(` ${a.emerald}│${a.reset}`);
|
|
233
376
|
|
|
234
|
-
const
|
|
377
|
+
const { created, updated, skipped } = await scaffoldWithCheckpoints(config, mode);
|
|
235
378
|
|
|
236
379
|
sectionEnd();
|
|
237
380
|
|
|
238
|
-
// ── Phase 4:
|
|
381
|
+
// ── Phase 4: Hooks ──
|
|
239
382
|
const s3 = createSpinner('Installing guardrail hook...');
|
|
240
383
|
s3.start();
|
|
241
384
|
await sleep(350);
|
|
242
385
|
s3.stop('Guardrail hook installed');
|
|
243
386
|
|
|
244
|
-
// ── Phase 5:
|
|
387
|
+
// ── Phase 5: Verify ──
|
|
388
|
+
const total = created + updated;
|
|
245
389
|
const s4 = createSpinner('Verifying installation...');
|
|
246
390
|
s4.start();
|
|
247
391
|
await sleep(300);
|
|
248
|
-
s4.stop(`
|
|
392
|
+
s4.stop(`Verified — ${bold(String(total))} files`);
|
|
249
393
|
|
|
250
394
|
// ── Done ──
|
|
251
|
-
printDone(
|
|
395
|
+
printDone(created, updated, skipped, mode);
|
|
252
396
|
}
|
|
253
397
|
|
|
254
|
-
async function scaffoldWithCheckpoints(config) {
|
|
398
|
+
async function scaffoldWithCheckpoints(config, mode) {
|
|
255
399
|
const { getTemplates } = await import('./templates/index.js');
|
|
256
400
|
const { mkdirSync, writeFileSync, chmodSync, existsSync } = await import('node:fs');
|
|
257
401
|
const { join, dirname } = await import('node:path');
|
|
@@ -280,7 +424,14 @@ async function scaffoldWithCheckpoints(config) {
|
|
|
280
424
|
else groups.config.items.push(tpl);
|
|
281
425
|
}
|
|
282
426
|
|
|
283
|
-
|
|
427
|
+
// Paths to preserve during update (user data, not engine)
|
|
428
|
+
const preserveOnUpdate = new Set([
|
|
429
|
+
join(config.outputDir, 'state.yaml'),
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
let created = 0;
|
|
433
|
+
let updated = 0;
|
|
434
|
+
let skipped = 0;
|
|
284
435
|
|
|
285
436
|
for (const [, group] of Object.entries(groups)) {
|
|
286
437
|
if (group.items.length === 0) continue;
|
|
@@ -289,31 +440,108 @@ async function scaffoldWithCheckpoints(config) {
|
|
|
289
440
|
sp.start();
|
|
290
441
|
await sleep(200);
|
|
291
442
|
|
|
292
|
-
let
|
|
443
|
+
let groupCreated = 0;
|
|
444
|
+
let groupUpdated = 0;
|
|
445
|
+
let groupSkipped = 0;
|
|
446
|
+
|
|
293
447
|
for (const tpl of group.items) {
|
|
294
448
|
const fullPath = join(cwd, tpl.path);
|
|
449
|
+
const fileExists = existsSync(fullPath);
|
|
450
|
+
|
|
295
451
|
mkdirSync(dirname(fullPath), { recursive: true });
|
|
296
|
-
|
|
452
|
+
|
|
453
|
+
if (!fileExists) {
|
|
454
|
+
// New file — always create
|
|
297
455
|
writeFileSync(fullPath, tpl.content, 'utf-8');
|
|
298
456
|
if (tpl.executable) chmodSync(fullPath, 0o755);
|
|
299
|
-
|
|
300
|
-
|
|
457
|
+
groupCreated++;
|
|
458
|
+
created++;
|
|
459
|
+
} else if (mode === 'update') {
|
|
460
|
+
// File exists + update mode
|
|
461
|
+
if (preserveOnUpdate.has(tpl.path)) {
|
|
462
|
+
// Preserve user artifacts
|
|
463
|
+
groupSkipped++;
|
|
464
|
+
skipped++;
|
|
465
|
+
} else if (tpl.path.endsWith('settings.local.json')) {
|
|
466
|
+
// Merge settings instead of overwrite
|
|
467
|
+
mergeSettings(fullPath, tpl.content);
|
|
468
|
+
groupUpdated++;
|
|
469
|
+
updated++;
|
|
470
|
+
} else {
|
|
471
|
+
// Engine file — overwrite with new version
|
|
472
|
+
writeFileSync(fullPath, tpl.content, 'utf-8');
|
|
473
|
+
if (tpl.executable) chmodSync(fullPath, 0o755);
|
|
474
|
+
groupUpdated++;
|
|
475
|
+
updated++;
|
|
476
|
+
}
|
|
477
|
+
} else if (mode === 'fresh') {
|
|
478
|
+
// Fresh mode — overwrite everything
|
|
479
|
+
writeFileSync(fullPath, tpl.content, 'utf-8');
|
|
480
|
+
if (tpl.executable) chmodSync(fullPath, 0o755);
|
|
481
|
+
groupCreated++;
|
|
482
|
+
created++;
|
|
483
|
+
} else {
|
|
484
|
+
// Install mode — skip existing
|
|
485
|
+
groupSkipped++;
|
|
486
|
+
skipped++;
|
|
301
487
|
}
|
|
302
488
|
}
|
|
303
489
|
|
|
304
|
-
|
|
490
|
+
const parts = [];
|
|
491
|
+
if (groupCreated > 0) parts.push(`${a.lime}+${groupCreated}${a.reset}`);
|
|
492
|
+
if (groupUpdated > 0) parts.push(`${a.yellow}↑${groupUpdated}${a.reset}`);
|
|
493
|
+
if (groupSkipped > 0) parts.push(`${a.dim}=${groupSkipped}${a.reset}`);
|
|
494
|
+
const stats = parts.length > 0 ? parts.join(' ') : `${a.dim}0${a.reset}`;
|
|
495
|
+
|
|
496
|
+
sp.stop(`${group.icon} ${group.label} ${dim('(')}${stats}${dim(')')}`);
|
|
305
497
|
}
|
|
306
498
|
|
|
307
|
-
return
|
|
499
|
+
return { created, updated, skipped };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function mergeSettings(filePath, newContent) {
|
|
503
|
+
try {
|
|
504
|
+
const existing = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
505
|
+
const incoming = JSON.parse(newContent);
|
|
506
|
+
|
|
507
|
+
// Merge hooks: add guardrail if not already present
|
|
508
|
+
if (incoming.hooks) {
|
|
509
|
+
if (!existing.hooks) existing.hooks = {};
|
|
510
|
+
for (const [event, handlers] of Object.entries(incoming.hooks)) {
|
|
511
|
+
if (!existing.hooks[event]) {
|
|
512
|
+
existing.hooks[event] = handlers;
|
|
513
|
+
} else {
|
|
514
|
+
// Check if guardrail hook already exists
|
|
515
|
+
for (const handler of handlers) {
|
|
516
|
+
const hookCmds = handler.hooks || [];
|
|
517
|
+
for (const hook of hookCmds) {
|
|
518
|
+
const alreadyExists = existing.hooks[event].some((h) =>
|
|
519
|
+
(h.hooks || []).some((hk) => hk.command === hook.command)
|
|
520
|
+
);
|
|
521
|
+
if (!alreadyExists) {
|
|
522
|
+
existing.hooks[event].push(handler);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
writeFS(filePath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
|
|
531
|
+
} catch {
|
|
532
|
+
// If can't parse existing, overwrite
|
|
533
|
+
writeFS(filePath, newContent, 'utf-8');
|
|
534
|
+
}
|
|
308
535
|
}
|
|
309
536
|
|
|
310
|
-
function printConfig(config) {
|
|
537
|
+
function printConfig(config, mode) {
|
|
311
538
|
const box = (label, value, extra) => {
|
|
312
539
|
const e = extra ? ` ${dim(extra)}` : '';
|
|
313
540
|
console.log(` ${a.emerald}│${a.reset} ${dim(label.padEnd(16))}${bold(value)}${e}`);
|
|
314
541
|
};
|
|
315
542
|
|
|
316
|
-
|
|
543
|
+
const modeTag = mode === 'update' ? ` ${a.yellow}${a.bold}UPDATE${a.reset}` : mode === 'fresh' ? ` ${a.red}${a.bold}FRESH${a.reset}` : '';
|
|
544
|
+
console.log(` ${a.emerald}${a.bold}┌─${a.reset} ${bold('Summary')}${modeTag}`);
|
|
317
545
|
console.log(` ${a.emerald}│${a.reset}`);
|
|
318
546
|
box('Project', config.projectName);
|
|
319
547
|
box('Author', config.authorName || dim('(not set)'));
|
|
@@ -322,15 +550,33 @@ function printConfig(config) {
|
|
|
322
550
|
box('APED', config.apedDir + '/', 'engine');
|
|
323
551
|
box('Output', config.outputDir + '/', 'artifacts');
|
|
324
552
|
box('Commands', config.commandsDir + '/');
|
|
553
|
+
box('Tickets', config.ticketSystem, config.ticketSystem === 'none' ? '' : 'integrated');
|
|
554
|
+
box('Git', config.gitProvider);
|
|
555
|
+
|
|
556
|
+
if (mode === 'update') {
|
|
557
|
+
console.log(` ${a.emerald}│${a.reset}`);
|
|
558
|
+
console.log(` ${a.emerald}│${a.reset} ${a.yellow}${a.bold}↑${a.reset} ${dim('Engine files will be overwritten')}`);
|
|
559
|
+
console.log(` ${a.emerald}│${a.reset} ${a.lime}${a.bold}=${a.reset} ${dim('Config, state & artifacts preserved')}`);
|
|
560
|
+
} else if (mode === 'fresh') {
|
|
561
|
+
console.log(` ${a.emerald}│${a.reset}`);
|
|
562
|
+
console.log(` ${a.emerald}│${a.reset} ${a.red}${a.bold}!${a.reset} ${dim('All existing files will be deleted and recreated')}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
325
565
|
console.log(` ${a.emerald}│${a.reset}`);
|
|
326
566
|
console.log(` ${a.emerald}${a.bold}└──────────────────────────────────${a.reset}`);
|
|
327
567
|
}
|
|
328
568
|
|
|
329
|
-
function printDone(
|
|
569
|
+
function printDone(created, updated, skipped, mode) {
|
|
570
|
+
const total = created + updated;
|
|
330
571
|
console.log('');
|
|
331
572
|
console.log(` ${a.emerald}${a.bold}╔══════════════════════════════════════╗${a.reset}`);
|
|
332
|
-
|
|
333
|
-
|
|
573
|
+
if (mode === 'update') {
|
|
574
|
+
console.log(` ${a.emerald}${a.bold}║${a.reset} ${a.lime}${a.bold}✓${a.reset} ${bold('Update complete')} ${a.emerald}${a.bold}║${a.reset}`);
|
|
575
|
+
console.log(` ${a.emerald}${a.bold}║${a.reset} ${dim(` +${created} created ↑${updated} updated =${skipped} kept`)} ${a.emerald}${a.bold}║${a.reset}`);
|
|
576
|
+
} else {
|
|
577
|
+
console.log(` ${a.emerald}${a.bold}║${a.reset} ${a.lime}${a.bold}✓${a.reset} ${bold(`${total} files scaffolded`)} ${a.emerald}${a.bold}║${a.reset}`);
|
|
578
|
+
console.log(` ${a.emerald}${a.bold}║${a.reset} ${dim('Pipeline ready to use')} ${a.emerald}${a.bold}║${a.reset}`);
|
|
579
|
+
}
|
|
334
580
|
console.log(` ${a.emerald}${a.bold}╚══════════════════════════════════════╝${a.reset}`);
|
|
335
581
|
console.log('');
|
|
336
582
|
|
|
@@ -341,6 +587,8 @@ function printDone(count) {
|
|
|
341
587
|
console.log(` ${a.yellow}${a.bold}/aped-e${a.reset} ${dim('Epics — requirements decomposition')}`);
|
|
342
588
|
console.log(` ${a.lime}${a.bold}/aped-d${a.reset} ${dim('Dev — TDD story implementation')}`);
|
|
343
589
|
console.log(` ${a.red}${a.bold}/aped-r${a.reset} ${dim('Review — adversarial code review')}`);
|
|
590
|
+
console.log('');
|
|
591
|
+
console.log(` ${a.spring}${a.bold}/aped-quick${a.reset} ${dim('Quick fix/feature — bypass full pipeline')}`);
|
|
344
592
|
console.log(` ${a.spring}${a.bold}/aped-all${a.reset} ${dim('Full pipeline A→P→E→D→R')}`);
|
|
345
593
|
console.log('');
|
|
346
594
|
console.log(` ${dim('Guardrail hook active — pipeline coherence enforced')}`);
|
|
@@ -49,6 +49,16 @@ description: 'Adversarial code review for completed story. Use when user says "r
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
51
|
Read and follow the SKILL.md at $PROJECT_ROOT/${a}/aped-r/SKILL.md
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
path: `${c.commandsDir}/aped-quick.md`,
|
|
56
|
+
content: `---
|
|
57
|
+
name: aped-quick
|
|
58
|
+
description: 'Quick fix/feature bypassing full pipeline. Use when user says "quick fix", "quick feature", "aped quick", or "hotfix"'
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
Read and follow the SKILL.md at $PROJECT_ROOT/${a}/aped-quick/SKILL.md
|
|
52
62
|
`,
|
|
53
63
|
},
|
|
54
64
|
{
|
package/src/templates/config.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export function configFiles(c) {
|
|
2
2
|
const a = c.apedDir;
|
|
3
3
|
const o = c.outputDir;
|
|
4
|
+
const ts = c.ticketSystem || 'none';
|
|
5
|
+
const gp = c.gitProvider || 'github';
|
|
6
|
+
const ver = c.cliVersion || '0.0.0';
|
|
4
7
|
return [
|
|
5
8
|
{
|
|
6
9
|
path: `${a}/config.yaml`,
|
|
@@ -11,10 +14,15 @@ communication_language: ${c.communicationLang}
|
|
|
11
14
|
document_output_language: ${c.documentLang}
|
|
12
15
|
aped_path: ${a}
|
|
13
16
|
output_path: ${o}
|
|
17
|
+
aped_version: ${ver}
|
|
18
|
+
|
|
19
|
+
# Integrations
|
|
20
|
+
ticket_system: ${ts}
|
|
21
|
+
git_provider: ${gp}
|
|
14
22
|
`,
|
|
15
23
|
},
|
|
16
24
|
{
|
|
17
|
-
path: `${
|
|
25
|
+
path: `${o}/state.yaml`,
|
|
18
26
|
content: `# APED Pipeline State
|
|
19
27
|
pipeline:
|
|
20
28
|
current_phase: "none"
|
|
@@ -224,6 +232,35 @@ sprint:
|
|
|
224
232
|
### Completion Notes
|
|
225
233
|
|
|
226
234
|
### File List
|
|
235
|
+
`,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
path: `${a}/templates/quick-spec.md`,
|
|
239
|
+
content: `# Quick Spec: {{title}}
|
|
240
|
+
|
|
241
|
+
**Date:** {{date}}
|
|
242
|
+
**Author:** {{user_name}}
|
|
243
|
+
**Type:** {{fix|feature|refactor}}
|
|
244
|
+
|
|
245
|
+
## What
|
|
246
|
+
|
|
247
|
+
<!-- 1-2 sentences: what needs to change -->
|
|
248
|
+
|
|
249
|
+
## Why
|
|
250
|
+
|
|
251
|
+
<!-- 1 sentence: why this change matters now -->
|
|
252
|
+
|
|
253
|
+
## Acceptance Criteria
|
|
254
|
+
|
|
255
|
+
- [ ] {{criterion}}
|
|
256
|
+
|
|
257
|
+
## Files to Change
|
|
258
|
+
|
|
259
|
+
- {{file_path}} — {{what to change}}
|
|
260
|
+
|
|
261
|
+
## Test Plan
|
|
262
|
+
|
|
263
|
+
- {{test description}}
|
|
227
264
|
`,
|
|
228
265
|
},
|
|
229
266
|
];
|
|
@@ -13,7 +13,7 @@ export function guardrail(c) {
|
|
|
13
13
|
set -euo pipefail
|
|
14
14
|
|
|
15
15
|
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
16
|
-
STATE_FILE="$PROJECT_ROOT/${
|
|
16
|
+
STATE_FILE="$PROJECT_ROOT/${o}/state.yaml"
|
|
17
17
|
CONFIG_FILE="$PROJECT_ROOT/${a}/config.yaml"
|
|
18
18
|
OUTPUT_DIR="$PROJECT_ROOT/${o}"
|
|
19
19
|
|
|
@@ -56,6 +56,9 @@ WANTS_CODE=false
|
|
|
56
56
|
[[ "$PROMPT_LOWER" =~ (aped-d|/aped-d|dev|implement|code|build|create.*component|create.*service) ]] && WANTS_DEV=true
|
|
57
57
|
[[ "$PROMPT_LOWER" =~ (aped-r|/aped-r|review|audit) ]] && WANTS_REVIEW=true
|
|
58
58
|
[[ "$PROMPT_LOWER" =~ (aped-all|/aped-all|full.pipeline|start.from.scratch) ]] && WANTS_ALL=true
|
|
59
|
+
WANTS_QUICK=false
|
|
60
|
+
|
|
61
|
+
[[ "$PROMPT_LOWER" =~ (aped-quick|/aped-quick|quick.fix|quick.feature|hotfix) ]] && WANTS_QUICK=true
|
|
59
62
|
[[ "$PROMPT_LOWER" =~ (code|implement|write.*function|create.*file|add.*feature|fix.*bug|refactor) ]] && WANTS_CODE=true
|
|
60
63
|
|
|
61
64
|
# ── Check artifact existence ──
|
|
@@ -84,6 +87,11 @@ phase_index() {
|
|
|
84
87
|
|
|
85
88
|
CURRENT_IDX=$(phase_index "$CURRENT_PHASE")
|
|
86
89
|
|
|
90
|
+
# Rule 0: Quick mode bypasses pipeline checks
|
|
91
|
+
if [[ "$WANTS_QUICK" == "true" ]]; then
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
87
95
|
# Rule 1: Trying to code without epics/stories
|
|
88
96
|
if [[ "$WANTS_CODE" == "true" || "$WANTS_DEV" == "true" ]] && [[ "$CURRENT_PHASE" != "dev" && "$CURRENT_PHASE" != "review" ]]; then
|
|
89
97
|
if [[ "$HAS_EPICS" == "false" ]]; then
|
package/src/templates/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function skills(c) {
|
|
2
2
|
const a = c.apedDir; // .aped (engine: skills, config, templates)
|
|
3
|
-
const o = c.outputDir; // docs/aped (output: generated artifacts)
|
|
3
|
+
const o = c.outputDir; // docs/aped (output: generated artifacts + state)
|
|
4
4
|
return [
|
|
5
5
|
// ── aped-a ──────────────────────────────────────────────
|
|
6
6
|
{
|
|
@@ -14,8 +14,8 @@ description: 'Analyze project idea through parallel market, domain, and technica
|
|
|
14
14
|
|
|
15
15
|
## Setup
|
|
16
16
|
|
|
17
|
-
1. Read \`${a}/config.yaml\` — extract \`user_name\`, \`communication_language\`
|
|
18
|
-
2. Read \`${
|
|
17
|
+
1. Read \`${a}/config.yaml\` — extract \`user_name\`, \`communication_language\`, \`ticket_system\`, \`git_provider\`
|
|
18
|
+
2. Read \`${o}/state.yaml\` — check \`pipeline.phases.analyze\`
|
|
19
19
|
- If status is \`done\`: ask user — redo analysis or skip to next phase?
|
|
20
20
|
- If user skips: invoke Skill tool with \`skill: "aped-p"\` and stop
|
|
21
21
|
|
|
@@ -80,7 +80,7 @@ If validation fails: fix missing sections and re-validate.
|
|
|
80
80
|
|
|
81
81
|
## State Update
|
|
82
82
|
|
|
83
|
-
Update \`${
|
|
83
|
+
Update \`${o}/state.yaml\`:
|
|
84
84
|
\`\`\`yaml
|
|
85
85
|
pipeline:
|
|
86
86
|
current_phase: "analyze"
|
|
@@ -108,7 +108,7 @@ description: 'Generate PRD autonomously from product brief. Use when user says "
|
|
|
108
108
|
## Setup
|
|
109
109
|
|
|
110
110
|
1. Read \`${a}/config.yaml\` — extract \`user_name\`, \`communication_language\`, \`document_output_language\`
|
|
111
|
-
2. Read \`${
|
|
111
|
+
2. Read \`${o}/state.yaml\` — check pipeline state
|
|
112
112
|
- If \`pipeline.phases.prd.status\` is \`done\`: ask user — redo PRD or skip?
|
|
113
113
|
- If user skips: invoke Skill tool with \`skill: "aped-e"\` and stop
|
|
114
114
|
|
|
@@ -162,7 +162,7 @@ bash ${a}/aped-p/scripts/validate-prd.sh ${o}/prd.md
|
|
|
162
162
|
## Output & State
|
|
163
163
|
|
|
164
164
|
1. Write PRD to \`${o}/prd.md\`
|
|
165
|
-
2. Update \`${
|
|
165
|
+
2. Update \`${o}/state.yaml\`:
|
|
166
166
|
\`\`\`yaml
|
|
167
167
|
pipeline:
|
|
168
168
|
current_phase: "prd"
|
|
@@ -189,8 +189,8 @@ description: 'Create epics and stories from PRD with full FR coverage. Use when
|
|
|
189
189
|
|
|
190
190
|
## Setup
|
|
191
191
|
|
|
192
|
-
1. Read \`${a}/config.yaml\` — extract config
|
|
193
|
-
2. Read \`${
|
|
192
|
+
1. Read \`${a}/config.yaml\` — extract config including \`ticket_system\`
|
|
193
|
+
2. Read \`${o}/state.yaml\` — check pipeline state
|
|
194
194
|
- If \`pipeline.phases.epics.status\` is \`done\`: ask user — redo or skip?
|
|
195
195
|
- If user skips: invoke Skill tool with \`skill: "aped-d"\` and stop
|
|
196
196
|
|
|
@@ -225,6 +225,16 @@ Story files: \`${o}/stories/{story-key}.md\`
|
|
|
225
225
|
- ACs in **Given/When/Then** format
|
|
226
226
|
- Tasks as checkboxes: \`- [ ] task [AC: AC#]\`
|
|
227
227
|
|
|
228
|
+
## Ticket System Integration
|
|
229
|
+
|
|
230
|
+
Read \`ticket_system\` from config. If not \`none\`:
|
|
231
|
+
- Add ticket reference in each story header: \`**Ticket:** {{ticket_id}}\`
|
|
232
|
+
- If \`jira\`: format as \`PROJ-###\` placeholder
|
|
233
|
+
- If \`linear\`: format as \`TEAM-###\` placeholder
|
|
234
|
+
- If \`github-issues\`: format as \`#issue_number\` placeholder
|
|
235
|
+
- If \`gitlab-issues\`: format as \`#issue_number\` placeholder
|
|
236
|
+
- Note: actual ticket creation is manual — these are reference placeholders
|
|
237
|
+
|
|
228
238
|
## FR Coverage Map
|
|
229
239
|
|
|
230
240
|
Every FR from PRD mapped to exactly one epic. No orphans, no phantoms.
|
|
@@ -243,7 +253,7 @@ mkdir -p ${o}/stories
|
|
|
243
253
|
|
|
244
254
|
1. Write epics to \`${o}/epics.md\`
|
|
245
255
|
2. Create story files in \`${o}/stories/\` using \`${a}/templates/story.md\`
|
|
246
|
-
3. Update \`${
|
|
256
|
+
3. Update \`${o}/state.yaml\` with sprint section and pipeline phase
|
|
247
257
|
|
|
248
258
|
## Chain
|
|
249
259
|
|
|
@@ -262,8 +272,8 @@ description: 'Dev sprint - implement next story with TDD red-green-refactor. Use
|
|
|
262
272
|
|
|
263
273
|
## Setup
|
|
264
274
|
|
|
265
|
-
1. Read \`${a}/config.yaml\` — extract config
|
|
266
|
-
2. Read \`${
|
|
275
|
+
1. Read \`${a}/config.yaml\` — extract config including \`ticket_system\`, \`git_provider\`
|
|
276
|
+
2. Read \`${o}/state.yaml\` — find next story
|
|
267
277
|
|
|
268
278
|
## Story Selection
|
|
269
279
|
|
|
@@ -278,7 +288,7 @@ If story has \`[AI-Review]\` items: address them BEFORE regular tasks.
|
|
|
278
288
|
|
|
279
289
|
## State Update (start)
|
|
280
290
|
|
|
281
|
-
Update \`${
|
|
291
|
+
Update \`${o}/state.yaml\`: story — \`in-progress\`, epic — \`in-progress\` if first story.
|
|
282
292
|
|
|
283
293
|
## Context Gathering
|
|
284
294
|
|
|
@@ -308,10 +318,16 @@ Mark \`[x]\` ONLY when: tests exist, pass 100%, implementation matches, ACs sati
|
|
|
308
318
|
|
|
309
319
|
**STOP and ask user if:** new dependency, 3 consecutive failures, missing config, ambiguity.
|
|
310
320
|
|
|
321
|
+
## Git Commit Convention
|
|
322
|
+
|
|
323
|
+
Read \`git_provider\` and \`ticket_system\` from config:
|
|
324
|
+
- Commit message format: \`type(scope): description\`
|
|
325
|
+
- If ticket system configured, append ticket ref: \`type(scope): description [TICKET-ID]\`
|
|
326
|
+
|
|
311
327
|
## Completion
|
|
312
328
|
|
|
313
329
|
1. Update story: mark tasks \`[x]\`, fill Dev Agent Record
|
|
314
|
-
2. Update \`${
|
|
330
|
+
2. Update \`${o}/state.yaml\`: story — \`review\`
|
|
315
331
|
3. Chain to \`/aped-r\`
|
|
316
332
|
`,
|
|
317
333
|
},
|
|
@@ -327,8 +343,8 @@ description: 'Adversarial code review for completed stories. Use when user says
|
|
|
327
343
|
|
|
328
344
|
## Setup
|
|
329
345
|
|
|
330
|
-
1. Read \`${a}/config.yaml\` — extract config
|
|
331
|
-
2. Read \`${
|
|
346
|
+
1. Read \`${a}/config.yaml\` — extract config including \`git_provider\`
|
|
347
|
+
2. Read \`${o}/state.yaml\` — find first story with status \`review\`
|
|
332
348
|
- If none: report "No stories pending review" and stop
|
|
333
349
|
|
|
334
350
|
## Load Story
|
|
@@ -367,7 +383,78 @@ Severity: CRITICAL > HIGH > MEDIUM > LOW. Format: \`[Severity] Description [file
|
|
|
367
383
|
|
|
368
384
|
## State Update
|
|
369
385
|
|
|
370
|
-
Update \`${
|
|
386
|
+
Update \`${o}/state.yaml\`. If more stories — chain to \`/aped-d\`. If all done — report completion.
|
|
387
|
+
`,
|
|
388
|
+
},
|
|
389
|
+
// ── aped-quick ────────────────────────────────────────────
|
|
390
|
+
{
|
|
391
|
+
path: `${a}/aped-quick/SKILL.md`,
|
|
392
|
+
content: `---
|
|
393
|
+
name: aped-quick
|
|
394
|
+
description: 'Quick feature/fix implementation bypassing full pipeline. Use when user says "quick fix", "quick feature", "aped quick", or invokes /aped-quick.'
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
# APED Quick — Fast Track for Small Changes
|
|
398
|
+
|
|
399
|
+
Use this for isolated fixes, small features, or refactors that don't warrant the full A→P→E→D→R pipeline.
|
|
400
|
+
|
|
401
|
+
## Setup
|
|
402
|
+
|
|
403
|
+
1. Read \`${a}/config.yaml\` — extract config
|
|
404
|
+
2. Read \`${o}/state.yaml\` — note current phase for context
|
|
405
|
+
|
|
406
|
+
## Scope Check
|
|
407
|
+
|
|
408
|
+
This mode is for changes that:
|
|
409
|
+
- Touch **5 files or fewer**
|
|
410
|
+
- Can be completed in **1 session**
|
|
411
|
+
- Don't introduce **new architectural patterns**
|
|
412
|
+
- Don't require **new dependencies**
|
|
413
|
+
|
|
414
|
+
If any of these are violated, recommend the full pipeline instead.
|
|
415
|
+
|
|
416
|
+
## Quick Spec (2 minutes)
|
|
417
|
+
|
|
418
|
+
Ask the user:
|
|
419
|
+
1. **What?** — What needs to change (1-2 sentences)
|
|
420
|
+
2. **Why?** — Why now, what breaks without it
|
|
421
|
+
3. **Type?** — fix | feature | refactor
|
|
422
|
+
|
|
423
|
+
Generate a quick spec using \`${a}/templates/quick-spec.md\`:
|
|
424
|
+
- Fill: title, type, what, why, acceptance criteria, files to change, test plan
|
|
425
|
+
- Write to \`${o}/quick-specs/{date}-{slug}.md\`
|
|
426
|
+
|
|
427
|
+
## Implementation (TDD)
|
|
428
|
+
|
|
429
|
+
Same TDD cycle as aped-d but compressed:
|
|
430
|
+
|
|
431
|
+
1. **RED** — Write test for the expected behavior
|
|
432
|
+
2. **GREEN** — Minimal implementation to pass
|
|
433
|
+
3. **REFACTOR** — Clean up while green
|
|
434
|
+
|
|
435
|
+
Run tests: \`bash ${a}/aped-d/scripts/run-tests.sh\`
|
|
436
|
+
|
|
437
|
+
## Self-Review (30 seconds)
|
|
438
|
+
|
|
439
|
+
Quick checklist — no full adversarial review:
|
|
440
|
+
- [ ] Tests pass
|
|
441
|
+
- [ ] No security issues introduced
|
|
442
|
+
- [ ] No regressions in existing tests
|
|
443
|
+
- [ ] AC from quick spec satisfied
|
|
444
|
+
|
|
445
|
+
## Git Commit
|
|
446
|
+
|
|
447
|
+
Read \`ticket_system\` and \`git_provider\` from config.
|
|
448
|
+
- Format: \`type(scope): description\`
|
|
449
|
+
- Append ticket ref if configured
|
|
450
|
+
- If \`git_provider\` is \`github\`: suggest PR creation with \`gh pr create\`
|
|
451
|
+
- If \`git_provider\` is \`gitlab\`: suggest MR creation with \`glab mr create\`
|
|
452
|
+
|
|
453
|
+
## Output
|
|
454
|
+
|
|
455
|
+
1. Write quick spec to \`${o}/quick-specs/\` (create dir if needed)
|
|
456
|
+
2. No state.yaml update — quick specs don't affect pipeline phase
|
|
457
|
+
3. Report: files changed, tests added, quick spec path
|
|
371
458
|
`,
|
|
372
459
|
},
|
|
373
460
|
// ── aped-all ─────────────────────────────────────────────
|
|
@@ -382,7 +469,7 @@ description: 'Run full APED pipeline from Analyze through Review. Use when user
|
|
|
382
469
|
|
|
383
470
|
## Resume Logic
|
|
384
471
|
|
|
385
|
-
1. Read \`${
|
|
472
|
+
1. Read \`${o}/state.yaml\`
|
|
386
473
|
2. Determine resume point:
|
|
387
474
|
|
|
388
475
|
| State | Action |
|
|
@@ -397,11 +484,11 @@ description: 'Run full APED pipeline from Analyze through Review. Use when user
|
|
|
397
484
|
## Execution
|
|
398
485
|
|
|
399
486
|
Use the Skill tool to invoke each phase: aped-a, aped-p, aped-e, aped-d, aped-r.
|
|
400
|
-
Each phase updates \`${
|
|
487
|
+
Each phase updates \`${o}/state.yaml\` and chains automatically.
|
|
401
488
|
|
|
402
489
|
## Interruption Handling
|
|
403
490
|
|
|
404
|
-
State persists in \`${
|
|
491
|
+
State persists in \`${o}/state.yaml\`. Next \`/aped-all\` resumes from last incomplete phase.
|
|
405
492
|
|
|
406
493
|
## Completion Report
|
|
407
494
|
|