jdi-cli 0.1.2 → 0.1.4
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/README.md +21 -0
- package/bin/jdi.js +29 -26
- package/bin/lib/ui.js +234 -14
- package/core/commands/jdi-add-phase.md +171 -0
- package/core/commands/jdi-remove-phase.md +228 -0
- package/package.json +1 -1
- package/runtimes/antigravity/agents.md +4 -0
- package/runtimes/antigravity/skills/jdi-add-phase/SKILL.md +171 -0
- package/runtimes/antigravity/skills/jdi-remove-phase/SKILL.md +228 -0
- package/runtimes/claude/CLAUDE.md +4 -0
- package/runtimes/claude/commands/jdi-add-phase.md +171 -0
- package/runtimes/claude/commands/jdi-remove-phase.md +228 -0
- package/runtimes/copilot/copilot-instructions.md +4 -0
- package/runtimes/copilot/prompts/jdi-add-phase.prompt.md +171 -0
- package/runtimes/copilot/prompts/jdi-remove-phase.prompt.md +228 -0
- package/runtimes/opencode/AGENTS.md +4 -0
- package/runtimes/opencode/commands/jdi-add-phase.md +171 -0
- package/runtimes/opencode/commands/jdi-remove-phase.md +228 -0
package/README.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# JDI — Just Do It
|
|
2
2
|
|
|
3
|
+
```
|
|
4
|
+
██╗██████╗ ██╗
|
|
5
|
+
██║██╔══██╗██║
|
|
6
|
+
██║██║ ██║██║
|
|
7
|
+
██ ██║██║ ██║██║
|
|
8
|
+
╚█████╔╝██████╔╝██║
|
|
9
|
+
╚════╝ ╚═════╝ ╚═╝
|
|
10
|
+
|
|
11
|
+
◄══════════════════════════════════════════════|=|◉|=|/////|==
|
|
12
|
+
◄══════════════════════════════════════════════|=|◉|=|/////|==
|
|
13
|
+
◄══════════════════════════════════════════════|=|◉|=|/////|==
|
|
14
|
+
|
|
15
|
+
Cut through the chaos. Ship the work. [Just do it]
|
|
16
|
+
```
|
|
17
|
+
|
|
3
18
|
Lean workflow toolkit for solo dev + AI assistant. Adaptive loop, atomic commits, file-based state, fresh context per agent, wave-based parallelism. Per-project specialists that already know your stack.
|
|
4
19
|
|
|
5
20
|
## Why
|
|
@@ -27,6 +42,10 @@ Full-blown AI workflows (33+ agents, 60+ commands, 100+ subworkflows) burn token
|
|
|
27
42
|
/jdi-do <N> <- execute via doer specialist (SUMMARY.md)
|
|
28
43
|
/jdi-verify <N> <- gates via reviewer (REVIEW.md)
|
|
29
44
|
/jdi-ship <N> <- update ROADMAP, advance phase
|
|
45
|
+
|
|
46
|
+
# Roadmap mutation (run anytime)
|
|
47
|
+
/jdi-add-phase "<name>" <- append (or --at <pos>) a new phase
|
|
48
|
+
/jdi-remove-phase <N> <- remove a future/pending phase
|
|
30
49
|
```
|
|
31
50
|
|
|
32
51
|
### Brownfield (existing project)
|
|
@@ -527,6 +546,8 @@ See [AGENTS.md](AGENTS.md) for full details.
|
|
|
527
546
|
| `/jdi-do <N>` | phase number | `--sequential` (force sequential execution even if waves permit parallel) | Execute tasks via doer specialist(s) → SUMMARY.md |
|
|
528
547
|
| `/jdi-verify <N>` | phase number | — | Run reviewer specialist gates → REVIEW.md (verdict APPROVED / APPROVED_WITH_WARNINGS / BLOCKED) |
|
|
529
548
|
| `/jdi-ship <N>` | phase number | — | Update ROADMAP, advance phase. Gates: verdict must not be BLOCKED |
|
|
549
|
+
| `/jdi-add-phase "<name>"` | phase name (required) | `--goal "<text>"`, `--at <pos>`, `--reason "<text>"` | Append (or insert at position) a new phase in ROADMAP.md. Bumps `total_phases`. Cannot insert at or before `current_phase`. |
|
|
550
|
+
| `/jdi-remove-phase <N>` | phase number (required) | `--force` (required if artifacts exist) | Remove a future or pending phase. Refuses for `done`, current, or past phases. Archives existing artifacts to `.jdi/archive/removed-<NN-slug>/`. Numbers are NOT renumbered (preserves history). |
|
|
530
551
|
|
|
531
552
|
**Brownfield entry (1):**
|
|
532
553
|
|
package/bin/jdi.js
CHANGED
|
@@ -116,14 +116,14 @@ function ensureScope(scope) {
|
|
|
116
116
|
// Commands
|
|
117
117
|
// =================================================================
|
|
118
118
|
|
|
119
|
-
function cmdInstall({ positional, flags }) {
|
|
119
|
+
async function cmdInstall({ positional, flags }) {
|
|
120
120
|
const runtime = positional[0];
|
|
121
121
|
const scope = flags.scope || 'project';
|
|
122
122
|
|
|
123
123
|
ensureRuntime(runtime);
|
|
124
124
|
ensureScope(scope);
|
|
125
125
|
|
|
126
|
-
ui.
|
|
126
|
+
await ui.bannerAnimated();
|
|
127
127
|
|
|
128
128
|
ui.header(`Instalando JDI para ${c.bold}${runtime}${c.reset}`);
|
|
129
129
|
ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -163,8 +163,8 @@ function cmdInstall({ positional, flags }) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
function cmdBuild({ flags }) {
|
|
167
|
-
ui.
|
|
166
|
+
async function cmdBuild({ flags }) {
|
|
167
|
+
await ui.bannerAnimated();
|
|
168
168
|
|
|
169
169
|
ui.header('Building JDI runtimes');
|
|
170
170
|
ui.info(`Source: ${c.dim}${PKG_ROOT}/core/${c.reset}`);
|
|
@@ -188,8 +188,8 @@ function cmdBuild({ flags }) {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function cmdUpdate({ flags }) {
|
|
192
|
-
ui.
|
|
191
|
+
async function cmdUpdate({ flags }) {
|
|
192
|
+
await ui.bannerAnimated();
|
|
193
193
|
|
|
194
194
|
ui.header('JDI Update');
|
|
195
195
|
ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -213,8 +213,8 @@ function cmdUpdate({ flags }) {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
function cmdUninstall({ positional, flags }) {
|
|
217
|
-
ui.
|
|
216
|
+
async function cmdUninstall({ positional, flags }) {
|
|
217
|
+
await ui.bannerAnimated();
|
|
218
218
|
|
|
219
219
|
ui.header('JDI Uninstall');
|
|
220
220
|
ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -245,8 +245,8 @@ function cmdUninstall({ positional, flags }) {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function cmdInstallPlaywright({ flags }) {
|
|
249
|
-
ui.
|
|
248
|
+
async function cmdInstallPlaywright({ flags }) {
|
|
249
|
+
await ui.bannerAnimated();
|
|
250
250
|
|
|
251
251
|
ui.header('JDI: Install Playwright + MCP');
|
|
252
252
|
ui.info(`Directory: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -282,8 +282,8 @@ function cmdInstallPlaywright({ flags }) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
function cmdInstallCaveman({ flags }) {
|
|
286
|
-
ui.
|
|
285
|
+
async function cmdInstallCaveman({ flags }) {
|
|
286
|
+
await ui.bannerAnimated();
|
|
287
287
|
|
|
288
288
|
ui.header('JDI: Install Caveman plugin');
|
|
289
289
|
ui.info(`Directory: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -317,8 +317,8 @@ function cmdInstallCaveman({ flags }) {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
function cmdDoctor({ flags }) {
|
|
321
|
-
ui.
|
|
320
|
+
async function cmdDoctor({ flags }) {
|
|
321
|
+
await ui.bannerAnimated();
|
|
322
322
|
|
|
323
323
|
ui.header('JDI Doctor');
|
|
324
324
|
ui.info(`Diretorio atual: ${c.dim}${process.cwd()}${c.reset}`);
|
|
@@ -332,8 +332,8 @@ function cmdDoctor({ flags }) {
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
function cmdHelp() {
|
|
336
|
-
ui.
|
|
335
|
+
async function cmdHelp() {
|
|
336
|
+
await ui.bannerAnimated();
|
|
337
337
|
|
|
338
338
|
console.log(`${c.bold}Uso:${c.reset}`);
|
|
339
339
|
console.log(` ${c.cyan}npx jdi-cli <comando> [opcoes]${c.reset}`);
|
|
@@ -403,7 +403,7 @@ function cmdVersion() {
|
|
|
403
403
|
// Main dispatcher
|
|
404
404
|
// =================================================================
|
|
405
405
|
|
|
406
|
-
function main() {
|
|
406
|
+
async function main() {
|
|
407
407
|
const parsed = parseArgs(process.argv);
|
|
408
408
|
|
|
409
409
|
if (parsed.flags && parsed.flags.noColor) {
|
|
@@ -417,34 +417,34 @@ function main() {
|
|
|
417
417
|
|
|
418
418
|
switch (parsed.cmd) {
|
|
419
419
|
case 'install':
|
|
420
|
-
cmdInstall(parsed);
|
|
420
|
+
await cmdInstall(parsed);
|
|
421
421
|
break;
|
|
422
422
|
case 'update':
|
|
423
423
|
case 'upgrade':
|
|
424
|
-
cmdUpdate(parsed);
|
|
424
|
+
await cmdUpdate(parsed);
|
|
425
425
|
break;
|
|
426
426
|
case 'uninstall':
|
|
427
427
|
case 'remove':
|
|
428
|
-
cmdUninstall(parsed);
|
|
428
|
+
await cmdUninstall(parsed);
|
|
429
429
|
break;
|
|
430
430
|
case 'build':
|
|
431
|
-
cmdBuild(parsed);
|
|
431
|
+
await cmdBuild(parsed);
|
|
432
432
|
break;
|
|
433
433
|
case 'install-playwright':
|
|
434
434
|
case 'playwright':
|
|
435
|
-
cmdInstallPlaywright(parsed);
|
|
435
|
+
await cmdInstallPlaywright(parsed);
|
|
436
436
|
break;
|
|
437
437
|
case 'install-caveman':
|
|
438
438
|
case 'caveman':
|
|
439
|
-
cmdInstallCaveman(parsed);
|
|
439
|
+
await cmdInstallCaveman(parsed);
|
|
440
440
|
break;
|
|
441
441
|
case 'doctor':
|
|
442
|
-
cmdDoctor(parsed);
|
|
442
|
+
await cmdDoctor(parsed);
|
|
443
443
|
break;
|
|
444
444
|
case 'help':
|
|
445
445
|
case '--help':
|
|
446
446
|
case '-h':
|
|
447
|
-
cmdHelp();
|
|
447
|
+
await cmdHelp();
|
|
448
448
|
break;
|
|
449
449
|
case '--version':
|
|
450
450
|
case '-V':
|
|
@@ -457,4 +457,7 @@ function main() {
|
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
main()
|
|
460
|
+
main().catch((err) => {
|
|
461
|
+
ui.fail(err && err.message ? err.message : String(err));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
});
|
package/bin/lib/ui.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
// ANSI escape codes — sem deps externas
|
|
4
4
|
const isTTY = process.stdout.isTTY;
|
|
5
5
|
const supportsColor = isTTY && !process.env.NO_COLOR;
|
|
6
|
+
const supportsTrueColor =
|
|
7
|
+
supportsColor &&
|
|
8
|
+
(process.env.COLORTERM === 'truecolor' ||
|
|
9
|
+
process.env.COLORTERM === '24bit' ||
|
|
10
|
+
process.env.TERM_PROGRAM === 'vscode' ||
|
|
11
|
+
process.env.WT_SESSION); // Windows Terminal
|
|
6
12
|
|
|
7
13
|
const c = {
|
|
8
14
|
reset: supportsColor ? '\x1b[0m' : '',
|
|
@@ -25,22 +31,234 @@ const c = {
|
|
|
25
31
|
bgGreen: supportsColor ? '\x1b[42m' : '',
|
|
26
32
|
};
|
|
27
33
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
''
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
// Synthwave palette (truecolor with 256-color fallback)
|
|
35
|
+
function rgb(r, g, b, fallback256) {
|
|
36
|
+
if (!supportsColor) return '';
|
|
37
|
+
if (supportsTrueColor) return `\x1b[38;2;${r};${g};${b}m`;
|
|
38
|
+
return `\x1b[38;5;${fallback256}m`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const synthwave = {
|
|
42
|
+
hotPink: rgb(255, 27, 141, 199),
|
|
43
|
+
neonYellow: rgb(244, 241, 66, 227),
|
|
44
|
+
cyanNeon: rgb(0, 255, 224, 51),
|
|
45
|
+
magenta: rgb(255, 0, 200, 201),
|
|
46
|
+
purple: rgb(185, 103, 255, 141),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ASCII letters J D I in ANSI Shadow font (full glyphs, including J's hook)
|
|
50
|
+
const JDI_LETTERS = [
|
|
51
|
+
' ██╗██████╗ ██╗',
|
|
52
|
+
' ██║██╔══██╗██║',
|
|
53
|
+
' ██║██║ ██║██║',
|
|
54
|
+
'██ ██║██║ ██║██║',
|
|
55
|
+
'╚█████╔╝██████╔╝██║',
|
|
56
|
+
' ╚════╝ ╚═════╝ ╚═╝',
|
|
40
57
|
];
|
|
41
58
|
|
|
59
|
+
// 3 lightsabers stacked, right-anchored hilts, blades ignite leftward.
|
|
60
|
+
// Each saber: [blade plasma extending left]|=|◉|=|/////|==
|
|
61
|
+
// ^^^ ^ ^^^ ^^^^^ ^^
|
|
62
|
+
// emt btn grd grip pommel
|
|
63
|
+
const HILT_STR = '|=|◉|=|/////|=='; // composed hilt, right side
|
|
64
|
+
const HILT_LEN = 15; // visible length of HILT_STR
|
|
65
|
+
const BLADE_CHAR = '═';
|
|
66
|
+
const BLADE_MAX = 47; // blade extends this many chars left
|
|
67
|
+
const BLADE_TIP = '◄'; // tip when fully ignited
|
|
68
|
+
const SABER_INDENTS = [2, 2, 2]; // aligned indents — hilts in same column
|
|
69
|
+
// Jedi blade palette — blue, green, purple
|
|
70
|
+
const SABER_COLORS_TRUE = [
|
|
71
|
+
[46, 170, 251], // Obi-Wan / Luke OG blue
|
|
72
|
+
[55, 251, 58], // Yoda / Luke ROTJ green
|
|
73
|
+
[163, 72, 251], // Mace Windu purple
|
|
74
|
+
];
|
|
75
|
+
const SABER_COLORS_256 = [33, 46, 129];
|
|
76
|
+
function saberColor(idx) {
|
|
77
|
+
if (!supportsColor) return '';
|
|
78
|
+
if (supportsTrueColor) {
|
|
79
|
+
const [r, g, b] = SABER_COLORS_TRUE[idx];
|
|
80
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
81
|
+
}
|
|
82
|
+
return `\x1b[38;5;${SABER_COLORS_256[idx]}m`;
|
|
83
|
+
}
|
|
84
|
+
const TAGLINE = 'Cut through the chaos. Ship the work. [Just do it]';
|
|
85
|
+
|
|
86
|
+
// Build one saber line, blade ignited from right (hilt) extending left to bladeChars.
|
|
87
|
+
// Layout (right-anchored): [spaces if blade incomplete][tip?][blade chars][hilt]
|
|
88
|
+
function buildSaberLine(saberIdx, bladeChars) {
|
|
89
|
+
const yellow = synthwave.neonYellow;
|
|
90
|
+
const red = supportsColor ? '\x1b[38;2;255;40;40m' : '';
|
|
91
|
+
const bladeCol = saberColor(saberIdx);
|
|
92
|
+
const r = c.reset;
|
|
93
|
+
const hiltColor = `${yellow}${c.bold}`;
|
|
94
|
+
const buttonColor = bladeChars > 0 ? `${red}${c.bold}` : hiltColor;
|
|
95
|
+
|
|
96
|
+
// Indent (stagger) + leading spaces to keep hilt at fixed right column
|
|
97
|
+
const indent = ' '.repeat(SABER_INDENTS[saberIdx]);
|
|
98
|
+
const leftPad = ' '.repeat(Math.max(0, BLADE_MAX - bladeChars));
|
|
99
|
+
|
|
100
|
+
let bladeSection = '';
|
|
101
|
+
if (bladeChars > 0) {
|
|
102
|
+
const tip = bladeChars >= BLADE_MAX ? `${bladeCol}${c.bold}${BLADE_TIP}${r}` : '';
|
|
103
|
+
const bladeBody = bladeChars >= BLADE_MAX ? bladeChars - 1 : bladeChars;
|
|
104
|
+
bladeSection = `${tip}${bladeCol}${c.bold}${BLADE_CHAR.repeat(bladeBody)}${r}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// HILT_STR has '◉' as 4th char (index 3). Recolor it red when blade active.
|
|
108
|
+
// Render hilt in 3 segments to keep button distinct.
|
|
109
|
+
const before = HILT_STR.slice(0, 3); // |=|
|
|
110
|
+
const button = HILT_STR.slice(3, 4); // ◉
|
|
111
|
+
const after = HILT_STR.slice(4); // |=|/////|==
|
|
112
|
+
const hilt =
|
|
113
|
+
`${hiltColor}${before}${r}` +
|
|
114
|
+
`${buttonColor}${button}${r}` +
|
|
115
|
+
`${hiltColor}${after}${r}`;
|
|
116
|
+
|
|
117
|
+
return `${indent}${leftPad}${bladeSection}${hilt}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildFrame({ showHilt, sabersBlade, showTagline }) {
|
|
121
|
+
const pink = synthwave.hotPink;
|
|
122
|
+
const purple = synthwave.purple;
|
|
123
|
+
const r = c.reset;
|
|
124
|
+
|
|
125
|
+
const lines = [''];
|
|
126
|
+
|
|
127
|
+
// 6 rows of JDI letters in hot pink
|
|
128
|
+
for (const row of JDI_LETTERS) {
|
|
129
|
+
lines.push(` ${pink}${c.bold}${row}${r}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
lines.push('');
|
|
133
|
+
|
|
134
|
+
// 3 stacked sabers
|
|
135
|
+
if (!showHilt) {
|
|
136
|
+
lines.push('');
|
|
137
|
+
lines.push('');
|
|
138
|
+
lines.push('');
|
|
139
|
+
} else {
|
|
140
|
+
for (let i = 0; i < 3; i++) {
|
|
141
|
+
const blade = (sabersBlade && sabersBlade[i] != null) ? sabersBlade[i] : 0;
|
|
142
|
+
lines.push(buildSaberLine(i, blade));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
lines.push('');
|
|
147
|
+
|
|
148
|
+
if (showTagline) {
|
|
149
|
+
lines.push(` ${purple}${c.italic}${TAGLINE}${r}`);
|
|
150
|
+
} else {
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function moveCursorUp(n) {
|
|
159
|
+
if (n > 0) process.stdout.write(`\x1b[${n}A`);
|
|
160
|
+
}
|
|
161
|
+
function clearLine() {
|
|
162
|
+
process.stdout.write('\x1b[2K');
|
|
163
|
+
}
|
|
164
|
+
function sleep(ms) {
|
|
165
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeFrame(lines, isFirst) {
|
|
169
|
+
if (!isFirst) {
|
|
170
|
+
moveCursorUp(lines.length);
|
|
171
|
+
}
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
clearLine();
|
|
174
|
+
process.stdout.write(`\r${line}\n`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const wantsAnim =
|
|
179
|
+
isTTY &&
|
|
180
|
+
!process.env.NO_COLOR &&
|
|
181
|
+
!process.env.CI &&
|
|
182
|
+
process.env.JDI_ANIMATE !== 'off';
|
|
183
|
+
|
|
184
|
+
async function bannerAnimated() {
|
|
185
|
+
// Non-TTY / CI / NO_COLOR: print static
|
|
186
|
+
if (!wantsAnim) {
|
|
187
|
+
banner();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Cascade ignition: 3 sabers, each ignites with 80ms stagger.
|
|
192
|
+
// Saber 0 (blue) at t=0, saber 1 (green) at t=80ms, saber 2 (purple) at t=160ms.
|
|
193
|
+
// Each blade extends in 5 steps (~400ms per saber).
|
|
194
|
+
const stepChars = [0, 12, 24, 36, 44, BLADE_MAX];
|
|
195
|
+
|
|
196
|
+
function snapshot(t) {
|
|
197
|
+
const result = [];
|
|
198
|
+
for (let i = 0; i < 3; i++) {
|
|
199
|
+
const elapsed = t - i * 80;
|
|
200
|
+
if (elapsed <= 0) {
|
|
201
|
+
result.push(0);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const stepIdx = Math.min(stepChars.length - 1, Math.floor(elapsed / 80));
|
|
205
|
+
result.push(stepChars[stepIdx]);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const frames = [];
|
|
211
|
+
// t=0: letters only, no sabers
|
|
212
|
+
frames.push({ showHilt: false, sabersBlade: [0, 0, 0], showTagline: false, delay: 80 });
|
|
213
|
+
// t=80: hilts appear (no blade)
|
|
214
|
+
frames.push({ showHilt: true, sabersBlade: [0, 0, 0], showTagline: false, delay: 80 });
|
|
215
|
+
// Cascade ignition (~480ms)
|
|
216
|
+
for (let t = 80; t <= 560; t += 80) {
|
|
217
|
+
frames.push({ showHilt: true, sabersBlade: snapshot(t), showTagline: false, delay: 80 });
|
|
218
|
+
}
|
|
219
|
+
// Final: all blades full + tagline
|
|
220
|
+
frames.push({
|
|
221
|
+
showHilt: true,
|
|
222
|
+
sabersBlade: [BLADE_MAX, BLADE_MAX, BLADE_MAX],
|
|
223
|
+
showTagline: true,
|
|
224
|
+
delay: 0,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
let isFirst = true;
|
|
228
|
+
for (const f of frames) {
|
|
229
|
+
const lines = buildFrame(f);
|
|
230
|
+
writeFrame(lines, isFirst);
|
|
231
|
+
isFirst = false;
|
|
232
|
+
if (f.delay > 0) await sleep(f.delay);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Hold the banner for HOLD_MS so the user can see it before the command continues.
|
|
236
|
+
// Use a small spinner under the tagline to signal that the CLI is alive and not frozen.
|
|
237
|
+
const HOLD_MS = 5000;
|
|
238
|
+
const SPIN_INTERVAL = 100;
|
|
239
|
+
const SPIN_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
240
|
+
const purple = synthwave.purple;
|
|
241
|
+
const r = c.reset;
|
|
242
|
+
const start = Date.now();
|
|
243
|
+
let spinIdx = 0;
|
|
244
|
+
while (Date.now() - start < HOLD_MS) {
|
|
245
|
+
const frame = SPIN_FRAMES[spinIdx % SPIN_FRAMES.length];
|
|
246
|
+
process.stdout.write(`\r ${purple}${frame}${r} ${c.dim}igniting...${r} `);
|
|
247
|
+
spinIdx++;
|
|
248
|
+
await sleep(SPIN_INTERVAL);
|
|
249
|
+
}
|
|
250
|
+
// Clear the spinner line so command output starts cleanly
|
|
251
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
252
|
+
}
|
|
253
|
+
|
|
42
254
|
function banner() {
|
|
43
|
-
|
|
255
|
+
// Static one-shot — final state: all 3 sabers ignited + tagline
|
|
256
|
+
const lines = buildFrame({
|
|
257
|
+
showHilt: true,
|
|
258
|
+
sabersBlade: [BLADE_MAX, BLADE_MAX, BLADE_MAX],
|
|
259
|
+
showTagline: true,
|
|
260
|
+
});
|
|
261
|
+
console.log(lines.join('\n'));
|
|
44
262
|
}
|
|
45
263
|
|
|
46
264
|
// Box drawing
|
|
@@ -120,7 +338,7 @@ function spinner(msg) {
|
|
|
120
338
|
if (!isTTY) {
|
|
121
339
|
console.log(`${sym.arrow} ${msg}...`);
|
|
122
340
|
return {
|
|
123
|
-
stop: () => {},
|
|
341
|
+
stop: () => { },
|
|
124
342
|
success: (s) => ok(s || msg),
|
|
125
343
|
fail: (s) => fail(s || msg),
|
|
126
344
|
};
|
|
@@ -179,7 +397,9 @@ function nextSteps(steps) {
|
|
|
179
397
|
module.exports = {
|
|
180
398
|
c,
|
|
181
399
|
sym,
|
|
400
|
+
synthwave,
|
|
182
401
|
banner,
|
|
402
|
+
bannerAnimated,
|
|
183
403
|
box,
|
|
184
404
|
step,
|
|
185
405
|
ok,
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jdi-add-phase
|
|
3
|
+
description: Adds a new phase to ROADMAP.md. Append at end (default) or insert at a specific position. Bumps total_phases. Atomic commit.
|
|
4
|
+
argument_hint: "\"<phase name>\" [--goal \"<goal>\"] [--at <position>]"
|
|
5
|
+
runtime_intent:
|
|
6
|
+
invokes_agent: none
|
|
7
|
+
runtime_overrides:
|
|
8
|
+
claude:
|
|
9
|
+
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion]
|
|
10
|
+
copilot:
|
|
11
|
+
tools: [read, write, edit, grep, glob, terminal]
|
|
12
|
+
opencode:
|
|
13
|
+
subtask: true
|
|
14
|
+
antigravity:
|
|
15
|
+
triggers:
|
|
16
|
+
- "/jdi-add-phase"
|
|
17
|
+
- "add new phase"
|
|
18
|
+
- "create phase"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<objective>
|
|
22
|
+
Adds a new phase to the project's roadmap. Edits `.jdi/ROADMAP.md`. Does not start the phase — only registers it. The user advances via `/jdi-discuss <N>` when ready.
|
|
23
|
+
</objective>
|
|
24
|
+
|
|
25
|
+
<arguments>
|
|
26
|
+
- `name` (required) — short phase name. Quote if it contains spaces.
|
|
27
|
+
- `--goal "<text>"` (optional) — 1-line description of what the phase delivers. If missing, AskUserQuestion will prompt.
|
|
28
|
+
- `--at <position>` (optional) — insert at this position instead of appending. Existing phases at and after `<position>` shift down by 1 (renumbered in ROADMAP only — slugs stay the same in any existing `.jdi/phases/` folders). Default: append.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
- `/jdi-add-phase "User authentication" --goal "Login + signup + JWT"`
|
|
32
|
+
- `/jdi-add-phase "Performance pass" --at 3`
|
|
33
|
+
- `/jdi-add-phase "Hotfix N+1 query"`
|
|
34
|
+
</arguments>
|
|
35
|
+
|
|
36
|
+
<process>
|
|
37
|
+
|
|
38
|
+
### Step 1: Validation
|
|
39
|
+
```bash
|
|
40
|
+
test -d .jdi/ || { echo "Not a JDI project. /jdi-new first."; exit 1; }
|
|
41
|
+
test -f .jdi/ROADMAP.md || { echo "ROADMAP.md missing."; exit 1; }
|
|
42
|
+
test -f .jdi/STATE.md || { echo "STATE.md missing."; exit 1; }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If `<name>` argument missing: AskUserQuestion "Phase name?" (free text). Required.
|
|
46
|
+
|
|
47
|
+
If `--goal` flag missing: AskUserQuestion "Phase goal (1 line)?" (free text). Required.
|
|
48
|
+
|
|
49
|
+
### Step 2: Compute phase number + slug
|
|
50
|
+
|
|
51
|
+
Read `total_phases` from `.jdi/ROADMAP.md`:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
TOTAL=$(grep -oE 'total_phases:\s*[0-9]+' .jdi/ROADMAP.md | grep -oE '[0-9]+')
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
PowerShell:
|
|
58
|
+
```powershell
|
|
59
|
+
$total = (Select-String -Path .jdi/ROADMAP.md -Pattern 'total_phases:\s*([0-9]+)').Matches[0].Groups[1].Value -as [int]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If `--at <pos>` provided, validate `1 <= pos <= total + 1`. Otherwise `pos = total + 1` (append).
|
|
63
|
+
|
|
64
|
+
If `pos <= current_phase` (read from STATE.md), abort with error: "Cannot insert before current phase {current}. Use --at {current+1} or higher."
|
|
65
|
+
|
|
66
|
+
Generate `slug` from `name`:
|
|
67
|
+
- Lowercase
|
|
68
|
+
- Replace accents (à → a, é → e, ç → c, ñ → n)
|
|
69
|
+
- Non-alphanumeric → `-`
|
|
70
|
+
- Collapse repeated `-`, strip leading/trailing `-`
|
|
71
|
+
- Truncate to 40 chars
|
|
72
|
+
|
|
73
|
+
Phase identifier prefix is the 2-digit position (`NN`): `01`, `02`, ..., zero-padded.
|
|
74
|
+
|
|
75
|
+
### Step 3: Renumber prefix on shift (only if --at)
|
|
76
|
+
|
|
77
|
+
If `--at <pos>` shifts existing phases:
|
|
78
|
+
|
|
79
|
+
Edit `.jdi/ROADMAP.md` — for every existing `### Phase K:` where `K >= pos`, change to `### Phase K+1:` and bump its `**Slug:**` prefix from `KK-...` to `(K+1)(K+1)-...`.
|
|
80
|
+
|
|
81
|
+
**WARNING (printed):**
|
|
82
|
+
> "Renumbering ROADMAP phases. Any existing `.jdi/phases/<NN-slug>/` folders are NOT renamed — they keep their original slugs and become "history". New work in renumbered phases creates new folders. Commits referencing old `phase {K}` remain valid as history."
|
|
83
|
+
|
|
84
|
+
This is intentional. Do not rename `.jdi/phases/` folders — would break commit history and cross-references in DECISIONS.md.
|
|
85
|
+
|
|
86
|
+
If `--at` not provided (append), skip Step 3.
|
|
87
|
+
|
|
88
|
+
### Step 4: Append phase section to ROADMAP.md
|
|
89
|
+
|
|
90
|
+
If appending (`pos == total + 1`), append at the bottom of `## Phases`:
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
|
|
94
|
+
### Phase {pos}: {name}
|
|
95
|
+
- **Slug:** {NN}-{slug}
|
|
96
|
+
- **Status:** pending
|
|
97
|
+
- **Goal:** {goal}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If inserting (`pos <= total`), insert the new section BEFORE the section that previously had number `pos` (now renumbered to `pos+1`).
|
|
101
|
+
|
|
102
|
+
### Step 5: Bump total_phases
|
|
103
|
+
|
|
104
|
+
Edit `.jdi/ROADMAP.md`:
|
|
105
|
+
```
|
|
106
|
+
total_phases: {TOTAL + 1}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Step 6: Audit trail in DECISIONS.md (optional, only if user passes --reason)
|
|
110
|
+
|
|
111
|
+
Not required. If user wants to record why this phase was added mid-project, they pass `--reason "<text>"`. When present, append to `.jdi/DECISIONS.md`:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
D-{N+1} ({date}, phase {pos}): Phase added mid-project. Reason: {reason}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
(N = current max D-X.)
|
|
118
|
+
|
|
119
|
+
### Step 7: Commit
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git add .jdi/ROADMAP.md .jdi/DECISIONS.md 2>/dev/null
|
|
123
|
+
git commit -m "chore(jdi): add phase {pos} {NN-slug}"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
PowerShell:
|
|
127
|
+
```powershell
|
|
128
|
+
git add .jdi/ROADMAP.md .jdi/DECISIONS.md 2>$null
|
|
129
|
+
git commit -m "chore(jdi): add phase $pos $NN-$slug"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Step 8: Confirm
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Phase {pos}: {name} added.
|
|
136
|
+
Slug: {NN}-{slug}
|
|
137
|
+
Goal: {goal}
|
|
138
|
+
Status: pending
|
|
139
|
+
total_phases: {TOTAL + 1}
|
|
140
|
+
|
|
141
|
+
Next: /jdi-discuss {pos} (when ready to start this phase)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
</process>
|
|
145
|
+
|
|
146
|
+
<gates>
|
|
147
|
+
- pre: `.jdi/ROADMAP.md` + `.jdi/STATE.md` exist
|
|
148
|
+
- pre: `--at <pos>` (if used) is greater than `current_phase` in STATE.md
|
|
149
|
+
- post: ROADMAP.md has new phase section + bumped total_phases + atomic commit
|
|
150
|
+
</gates>
|
|
151
|
+
|
|
152
|
+
<errors>
|
|
153
|
+
- `.jdi/` missing -> "Run /jdi-new first"
|
|
154
|
+
- `--at <pos>` <= current_phase -> "Cannot insert before current phase. Use --at {current+1} or higher."
|
|
155
|
+
- `--at <pos>` < 1 or > total+1 -> "Position out of range. Valid: 1..{total+1}"
|
|
156
|
+
- Phase name empty -> AskUserQuestion to fill
|
|
157
|
+
- Goal empty -> AskUserQuestion to fill
|
|
158
|
+
</errors>
|
|
159
|
+
|
|
160
|
+
<runtime_notes>
|
|
161
|
+
|
|
162
|
+
**Claude Code:**
|
|
163
|
+
- AskUserQuestion handles missing args interactively.
|
|
164
|
+
|
|
165
|
+
**Copilot:**
|
|
166
|
+
- AskUserQuestion not always available — read missing args from prompt body or fail with clear error.
|
|
167
|
+
|
|
168
|
+
**OpenCode/Antigravity:**
|
|
169
|
+
- Same interactive flow as Claude when supported. Otherwise fail with clear error message asking the user to re-invoke with all args.
|
|
170
|
+
|
|
171
|
+
</runtime_notes>
|