singleton-pipeline 0.4.0-beta.11 → 0.4.0-beta.13
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/CHANGELOG.md +49 -0
- package/README.md +170 -129
- package/docs/reference.md +63 -18
- package/package.json +2 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/new.js +455 -109
- package/packages/cli/src/commands/repl.js +86 -89
- package/packages/cli/src/executor/debug-loop.js +587 -0
- package/packages/cli/src/executor/inputs.js +202 -0
- package/packages/cli/src/executor/outputs.js +140 -0
- package/packages/cli/src/executor/preflight.js +459 -0
- package/packages/cli/src/executor/replay-loop.js +172 -0
- package/packages/cli/src/executor/run-report.js +189 -0
- package/packages/cli/src/executor/run-setup.js +93 -0
- package/packages/cli/src/executor/security-review.js +108 -0
- package/packages/cli/src/executor/snapshot-manager.js +335 -0
- package/packages/cli/src/executor/step-runner.js +266 -0
- package/packages/cli/src/executor.js +233 -2233
- package/packages/cli/src/index.js +1 -1
- package/packages/cli/src/runners/copilot.js +20 -22
- package/packages/cli/src/shell.js +244 -54
- package/packages/cli/src/timeline.js +54 -20
- package/packages/server/package.json +1 -1
- package/packages/web/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { createShell,
|
|
6
|
+
import { createShell, S } from '../shell.js';
|
|
5
7
|
import { scanAgents } from '../scanner.js';
|
|
6
8
|
import { runPipeline } from '../executor.js';
|
|
7
9
|
import { newAgentShellCommand } from './new.js';
|
|
@@ -13,18 +15,18 @@ const HELP = [
|
|
|
13
15
|
'',
|
|
14
16
|
`{bold}Commands{/}`,
|
|
15
17
|
'',
|
|
16
|
-
` {${
|
|
17
|
-
` {${
|
|
18
|
-
` {${
|
|
19
|
-
` {${
|
|
20
|
-
` {${
|
|
21
|
-
` {${
|
|
22
|
-
` {${
|
|
23
|
-
` {${
|
|
24
|
-
` {${
|
|
25
|
-
` {${
|
|
26
|
-
` {${
|
|
27
|
-
` {${
|
|
18
|
+
` {${S.accent}-fg}{bold}/run <name>{/} run a pipeline`,
|
|
19
|
+
` {${S.accent}-fg}{bold}/run <name> --dry{/} dry-run (plan without API calls)`,
|
|
20
|
+
` {${S.accent}-fg}{bold}/run <name> --verbose{/} show prompts and outputs`,
|
|
21
|
+
` {${S.accent}-fg}{bold}/run <name> --debug{/} pause before each step`,
|
|
22
|
+
` {${S.accent}-fg}{bold}/scan{/} scan .md agents`,
|
|
23
|
+
` {${S.accent}-fg}{bold}/new{/} create a new agent`,
|
|
24
|
+
` {${S.accent}-fg}{bold}/serve{/} start the web server`,
|
|
25
|
+
` {${S.accent}-fg}{bold}/stop{/} stop the web server`,
|
|
26
|
+
` {${S.accent}-fg}{bold}/commit-last{/} commit deliverables from the last run`,
|
|
27
|
+
` {${S.accent}-fg}{bold}/ls{/} list pipelines`,
|
|
28
|
+
` {${S.accent}-fg}{bold}/help{/} show help`,
|
|
29
|
+
` {${S.accent}-fg}{bold}/quit{/} quit {${S.muted}-fg}(or Ctrl+C){/}`,
|
|
28
30
|
'',
|
|
29
31
|
].join('\n');
|
|
30
32
|
|
|
@@ -179,30 +181,22 @@ async function countAgents(root) {
|
|
|
179
181
|
} catch { return 0; }
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
184
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
185
|
+
const LOGO_PATH = path.join(__dirname, '..', 'assets', 'singleton-logo.txt');
|
|
186
|
+
function loadLogoLines() {
|
|
187
|
+
try {
|
|
188
|
+
const raw = fsSync.readFileSync(LOGO_PATH, 'utf8');
|
|
189
|
+
return raw
|
|
190
|
+
.replace(/\x1b\[\?25[lh]/g, '')
|
|
191
|
+
.split('\n')
|
|
192
|
+
.filter((line) => line.length > 0);
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const SINGLETON_RAW = [
|
|
199
|
-
'▄█████ ▄▄ ▄▄ ▄▄ ▄▄▄▄ ▄▄ ▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ',
|
|
200
|
-
'▀▀▀▄▄▄ ██ ███▄██ ██ ▄▄ ██ ▄▄██ ██ ██▀██ ███▄██ ',
|
|
201
|
-
'█████▀ ██ ██ ▀██ ▀███▀ ██▄▄▄ ▄▄▄██ ██ ▀███▀ ██ ▀██ ',
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
const ART_WIDTH = Math.max(...SINGLETON_RAW.map((l) => l.length));
|
|
205
|
-
const APP_VERSION = 'v0.4.0-beta.11';
|
|
198
|
+
const SINGLETON_LOGO = loadLogoLines();
|
|
199
|
+
const APP_VERSION = 'v0.4.0-beta.12';
|
|
206
200
|
|
|
207
201
|
async function showWelcome(root, shell) {
|
|
208
202
|
const now = new Date();
|
|
@@ -220,27 +214,27 @@ async function showWelcome(root, shell) {
|
|
|
220
214
|
const headerLines = [
|
|
221
215
|
'',
|
|
222
216
|
' '.repeat(tw('Welcome back')),
|
|
223
|
-
`${dateStr} ${timeStr} {${
|
|
217
|
+
`${dateStr} ${timeStr} {${S.muted}-fg}${APP_VERSION}{/}`,
|
|
224
218
|
'',
|
|
225
219
|
`${pipelines.length} pipeline${pipelines.length !== 1 ? 's' : ''}`,
|
|
226
220
|
`${agentCount} agent${agentCount !== 1 ? 's' : ''}`,
|
|
227
221
|
'',
|
|
228
|
-
`{${
|
|
229
|
-
`{${
|
|
230
|
-
`{${
|
|
231
|
-
`{${
|
|
232
|
-
`{${
|
|
233
|
-
`{${
|
|
222
|
+
`{${S.warning}-fg}{bold}New{/} {${S.text}-fg}redesigned /new agent flow{/}`,
|
|
223
|
+
`{${S.subtle}-fg}·{/} {${S.keyword}-fg}sectioned form{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}field autocomplete{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}review + :back{/}`,
|
|
224
|
+
`{${S.warning}-fg}{bold}New{/} {${S.text}-fg}safer replay and prompt inputs{/}`,
|
|
225
|
+
`{${S.subtle}-fg}·{/} {${S.keyword}-fg}snapshot restore{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}escaped user XML{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}loud rollback limits{/}`,
|
|
226
|
+
`{${S.warning}-fg}{bold}New{/} {${S.text}-fg}cleaner run UI and output{/}`,
|
|
227
|
+
`{${S.subtle}-fg}·{/} {${S.keyword}-fg}framed logs{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}step reviews{/} {${S.subtle}-fg}·{/} {${S.keyword}-fg}final Copilot output{/}`,
|
|
234
228
|
'',
|
|
235
229
|
];
|
|
236
230
|
const TAGLINE = 'one to rule them all';
|
|
237
231
|
const CREDIT = 'Developed by Romain LENTZ';
|
|
238
|
-
const bottomBlockHeight = 3 +
|
|
232
|
+
const bottomBlockHeight = 3 + SINGLETON_LOGO.length;
|
|
239
233
|
const spacerLines = Math.max(0, contentHeight - headerLines.length - bottomBlockHeight);
|
|
240
234
|
|
|
241
235
|
// Track shimmer positions.
|
|
242
236
|
const welcomeRow = CONTENT_PAD_TOP + 2;
|
|
243
|
-
const creditRow = CONTENT_PAD_TOP + headerLines.length + spacerLines;
|
|
237
|
+
const creditRow = CONTENT_PAD_TOP + headerLines.length + spacerLines + SINGLETON_LOGO.length;
|
|
244
238
|
const taglineRow = creditRow + 1;
|
|
245
239
|
|
|
246
240
|
for (const line of headerLines) {
|
|
@@ -250,12 +244,13 @@ async function showWelcome(root, shell) {
|
|
|
250
244
|
shell.log('');
|
|
251
245
|
}
|
|
252
246
|
|
|
253
|
-
|
|
247
|
+
for (const logoLine of SINGLETON_LOGO) {
|
|
248
|
+
shell.log(logoLine);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
shell.log(`{${S.muted}-fg}${CREDIT}{/}`);
|
|
254
252
|
shell.log(' '.repeat(TAGLINE.length));
|
|
255
253
|
shell.log('');
|
|
256
|
-
for (const line of SINGLETON_RAW) {
|
|
257
|
-
shell.log(plainBrightLine(line));
|
|
258
|
-
}
|
|
259
254
|
|
|
260
255
|
const stopWelcome = shell.createShimmer('Welcome back', welcomeRow, CONTENT_PAD_LEFT);
|
|
261
256
|
const stopTagline = shell.createShimmer(TAGLINE, taglineRow, CONTENT_PAD_LEFT);
|
|
@@ -318,14 +313,16 @@ export async function replCommand(opts) {
|
|
|
318
313
|
await closeServer(serveState.server);
|
|
319
314
|
serveState.clear();
|
|
320
315
|
}
|
|
321
|
-
shell.log(
|
|
316
|
+
shell.log(`{${S.muted}-fg}See you soon.{/}`);
|
|
322
317
|
setTimeout(() => { shell.destroy(); process.exit(0); }, 300);
|
|
323
318
|
return;
|
|
324
319
|
default:
|
|
325
|
-
shell.log(`{${
|
|
320
|
+
shell.log(`{${S.warning}-fg}!{/} Unknown command: {bold}${cmd}{/} — type /help`);
|
|
326
321
|
}
|
|
327
322
|
} catch (err) {
|
|
328
|
-
|
|
323
|
+
// Safety: if a command (typically /run) threw before resetting the border, reflect the error.
|
|
324
|
+
shell.setMode?.('error');
|
|
325
|
+
shell.log(`{${S.error}-fg}✕{/} ${err.message}`);
|
|
329
326
|
}
|
|
330
327
|
shell.enableInput();
|
|
331
328
|
});
|
|
@@ -340,19 +337,19 @@ async function cmdRun(args, root, shell) {
|
|
|
340
337
|
if (!name) {
|
|
341
338
|
const pipelines = await listPipelines(root);
|
|
342
339
|
if (pipelines.length === 0) {
|
|
343
|
-
shell.log(`{${
|
|
340
|
+
shell.log(`{${S.warning}-fg}!{/} No pipelines found.`);
|
|
344
341
|
return;
|
|
345
342
|
}
|
|
346
|
-
shell.log(`{${
|
|
347
|
-
shell.log(`{${
|
|
343
|
+
shell.log(`{${S.muted}-fg} Pipelines: ${pipelines.join(', ')}{/}`);
|
|
344
|
+
shell.log(`{${S.muted}-fg} Usage: /run <name> [--dry] [--verbose] [--debug]{/}`);
|
|
348
345
|
return;
|
|
349
346
|
}
|
|
350
347
|
|
|
351
348
|
const filePath = await resolvePipelinePath(name, root);
|
|
352
349
|
if (!filePath) {
|
|
353
|
-
shell.log(`{${
|
|
350
|
+
shell.log(`{${S.error}-fg}✕{/} Pipeline "{bold}${name}{/}" not found.`);
|
|
354
351
|
const pipelines = await listPipelines(root);
|
|
355
|
-
if (pipelines.length) shell.log(`{${
|
|
352
|
+
if (pipelines.length) shell.log(`{${S.muted}-fg} Available: ${pipelines.join(', ')}{/}`);
|
|
356
353
|
return;
|
|
357
354
|
}
|
|
358
355
|
|
|
@@ -362,12 +359,12 @@ async function cmdRun(args, root, shell) {
|
|
|
362
359
|
async function cmdLs(root, shell) {
|
|
363
360
|
const pipelines = await listPipelines(root);
|
|
364
361
|
if (pipelines.length === 0) {
|
|
365
|
-
shell.log(
|
|
362
|
+
shell.log(`{${S.warning}-fg}!{/} No pipelines found.`);
|
|
366
363
|
return;
|
|
367
364
|
}
|
|
368
365
|
shell.log(`{bold}Pipelines (${pipelines.length}){/}`);
|
|
369
366
|
shell.log('');
|
|
370
|
-
for (const p of pipelines) shell.log(` {${
|
|
367
|
+
for (const p of pipelines) shell.log(` {${S.subtle}-fg}·{/} {${S.string}-fg}${p}{/}`);
|
|
371
368
|
shell.log('');
|
|
372
369
|
}
|
|
373
370
|
|
|
@@ -381,26 +378,26 @@ function groupAgentsByProvider(agents) {
|
|
|
381
378
|
}
|
|
382
379
|
|
|
383
380
|
async function cmdScan(root, shell) {
|
|
384
|
-
shell.log(`{${
|
|
381
|
+
shell.log(`{${S.muted}-fg}Scanning ${root}…{/}`);
|
|
385
382
|
const agents = await scanAgents(root);
|
|
386
383
|
if (agents.length === 0) {
|
|
387
|
-
shell.log(`{${
|
|
384
|
+
shell.log(`{${S.warning}-fg}!{/} No agents found (no .md files with ## Config).`);
|
|
388
385
|
return;
|
|
389
386
|
}
|
|
390
387
|
shell.log(`{bold}Agents (${agents.length}){/}`);
|
|
391
388
|
shell.log('');
|
|
392
389
|
const groups = groupAgentsByProvider(agents);
|
|
393
390
|
[...groups.entries()].forEach(([provider, providerAgents]) => {
|
|
394
|
-
shell.log(` {${
|
|
395
|
-
shell.log(` {bold}${provider}{/} {${
|
|
396
|
-
shell.log(` {${
|
|
391
|
+
shell.log(` {${S.muted}-fg}════════════════════════════════════════{/}`);
|
|
392
|
+
shell.log(` {bold}${provider}{/} {${S.muted}-fg}(${providerAgents.length}){/}`);
|
|
393
|
+
shell.log(` {${S.muted}-fg}════════════════════════════════════════{/}`);
|
|
397
394
|
shell.log('');
|
|
398
395
|
|
|
399
396
|
providerAgents.forEach((a, index) => {
|
|
400
|
-
shell.log(` {${
|
|
401
|
-
shell.log(` {${
|
|
402
|
-
shell.log(` {${
|
|
403
|
-
if (index < providerAgents.length - 1) shell.log(` {${
|
|
397
|
+
shell.log(` {${S.accent}-fg}{bold}${a.id}{/} {${S.muted}-fg}${a.description || '(no description)'}{/}`);
|
|
398
|
+
shell.log(` {${S.keyword}-fg}{bold}source{/}: {${S.text}-fg}${a.source || 'repo'}{/}${a.permission_mode ? ` {${S.keyword}-fg}{bold}permission{/}: {${S.text}-fg}${a.permission_mode}{/}` : ''}`);
|
|
399
|
+
shell.log(` {${S.keyword}-fg}{bold}in{/}: {${S.text}-fg}${a.inputs.join(', ') || '—'}{/} {${S.keyword}-fg}{bold}out{/}: {${S.text}-fg}${a.outputs.join(', ') || '—'}{/}`);
|
|
400
|
+
if (index < providerAgents.length - 1) shell.log(` {${S.muted}-fg}──────────────────────────────────────{/}`);
|
|
404
401
|
});
|
|
405
402
|
shell.log('');
|
|
406
403
|
});
|
|
@@ -408,7 +405,7 @@ async function cmdScan(root, shell) {
|
|
|
408
405
|
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
409
406
|
await fs.writeFile(outPath, JSON.stringify({ scannedAt: new Date().toISOString(), root, agents }, null, 2));
|
|
410
407
|
shell.log('');
|
|
411
|
-
shell.log(`{${
|
|
408
|
+
shell.log(`{${S.success}-fg}✓{/} Cache → {${S.string}-fg}.singleton/agents.json{/}`);
|
|
412
409
|
}
|
|
413
410
|
|
|
414
411
|
async function cmdNew(root, shell) {
|
|
@@ -429,12 +426,12 @@ function closeServer(server) {
|
|
|
429
426
|
|
|
430
427
|
async function cmdServe(root, shell, serveState) {
|
|
431
428
|
if (serveState.server) {
|
|
432
|
-
shell.log(`{${
|
|
429
|
+
shell.log(`{${S.warning}-fg}!{/} The server is already running on {${S.string}-fg}${serveState.url}{/}.`);
|
|
433
430
|
return;
|
|
434
431
|
}
|
|
435
432
|
const { startServer } = await import('../../../server/src/index.js');
|
|
436
433
|
const serverUrl = 'http://localhost:4317';
|
|
437
|
-
shell.log(
|
|
434
|
+
shell.log(`{${S.muted}-fg}Starting server… (/stop to stop){/}`);
|
|
438
435
|
shell.enableInput();
|
|
439
436
|
const server = await startServer({
|
|
440
437
|
port: 4317,
|
|
@@ -445,10 +442,10 @@ async function cmdServe(root, shell, serveState) {
|
|
|
445
442
|
const url = urlMatch[0];
|
|
446
443
|
const prefix = message.slice(0, urlMatch.index);
|
|
447
444
|
const suffix = message.slice(urlMatch.index + url.length);
|
|
448
|
-
shell.log(`{
|
|
445
|
+
shell.log(`{${S.text}-fg}{bold}${prefix}{/}{${S.string}-fg}${url}{/}{${S.muted}-fg}${suffix}{/}`);
|
|
449
446
|
return;
|
|
450
447
|
}
|
|
451
|
-
shell.log(`{${
|
|
448
|
+
shell.log(`{${S.muted}-fg}${message}{/}`);
|
|
452
449
|
},
|
|
453
450
|
});
|
|
454
451
|
serveState.server = server;
|
|
@@ -456,16 +453,16 @@ async function cmdServe(root, shell, serveState) {
|
|
|
456
453
|
server.on('close', () => {
|
|
457
454
|
const shouldLog = !serveState.suppressCloseLog;
|
|
458
455
|
serveState.clear();
|
|
459
|
-
if (shouldLog) shell.log(`{${
|
|
456
|
+
if (shouldLog) shell.log(`{${S.muted}-fg}Serve stopped.{/}`);
|
|
460
457
|
});
|
|
461
458
|
shell.setFooterCenter(
|
|
462
|
-
`{${
|
|
459
|
+
`{${S.muted}-fg}serve running{/} {${S.string}-fg}${serverUrl}{/}`
|
|
463
460
|
);
|
|
464
461
|
}
|
|
465
462
|
|
|
466
463
|
async function cmdStop(shell, serveState) {
|
|
467
464
|
if (!serveState.server) {
|
|
468
|
-
shell.log(`{${
|
|
465
|
+
shell.log(`{${S.warning}-fg}!{/} No running server.`);
|
|
469
466
|
return;
|
|
470
467
|
}
|
|
471
468
|
const url = serveState.url;
|
|
@@ -475,7 +472,7 @@ async function cmdStop(shell, serveState) {
|
|
|
475
472
|
serveState.url = '';
|
|
476
473
|
shell.setFooterCenter('');
|
|
477
474
|
await closeServer(server);
|
|
478
|
-
shell.log(`{${
|
|
475
|
+
shell.log(`{${S.success}-fg}✓{/} Serve stopped {${S.string}-fg}${url}{/}`);
|
|
479
476
|
}
|
|
480
477
|
|
|
481
478
|
async function cmdCommitLast(root, shell) {
|
|
@@ -483,7 +480,7 @@ async function cmdCommitLast(root, shell) {
|
|
|
483
480
|
try {
|
|
484
481
|
manifest = await loadLastRunManifest(root);
|
|
485
482
|
} catch {
|
|
486
|
-
shell.log(`{${
|
|
483
|
+
shell.log(`{${S.warning}-fg}!{/} No usable latest run found in .singleton/runs/latest.`);
|
|
487
484
|
return;
|
|
488
485
|
}
|
|
489
486
|
|
|
@@ -491,7 +488,7 @@ async function cmdCommitLast(root, shell) {
|
|
|
491
488
|
try {
|
|
492
489
|
securityConfig = await loadProjectSecurityConfig(root);
|
|
493
490
|
} catch (err) {
|
|
494
|
-
shell.log(`{${
|
|
491
|
+
shell.log(`{${S.error}-fg}✕{/} ${err.message}`);
|
|
495
492
|
return;
|
|
496
493
|
}
|
|
497
494
|
|
|
@@ -505,9 +502,9 @@ async function cmdCommitLast(root, shell) {
|
|
|
505
502
|
return true;
|
|
506
503
|
});
|
|
507
504
|
if (files.length === 0) {
|
|
508
|
-
shell.log(`{${
|
|
505
|
+
shell.log(`{${S.warning}-fg}!{/} The last run produced no deliverables to commit.`);
|
|
509
506
|
if (excluded.length) {
|
|
510
|
-
shell.log(`{${
|
|
507
|
+
shell.log(`{${S.muted}-fg}All deliverables were excluded by .singleton/security.json commit rules.{/}`);
|
|
511
508
|
}
|
|
512
509
|
return;
|
|
513
510
|
}
|
|
@@ -515,27 +512,27 @@ async function cmdCommitLast(root, shell) {
|
|
|
515
512
|
try {
|
|
516
513
|
await runCommand('git', ['rev-parse', '--show-toplevel'], { cwd: root });
|
|
517
514
|
} catch {
|
|
518
|
-
shell.log(`{${
|
|
515
|
+
shell.log(`{${S.error}-fg}✕{/} This project is not inside a Git repository.`);
|
|
519
516
|
return;
|
|
520
517
|
}
|
|
521
518
|
|
|
522
|
-
shell.log(`{bold}Commit last preview{/} {${
|
|
519
|
+
shell.log(`{bold}Commit last preview{/} {${S.muted}-fg}${manifest.pipeline || 'unknown pipeline'}{/}`);
|
|
523
520
|
shell.log('');
|
|
524
|
-
shell.log(`{${
|
|
525
|
-
for (const file of files) shell.log(` {${
|
|
521
|
+
shell.log(`{${S.keyword}-fg}Files to stage{/}`);
|
|
522
|
+
for (const file of files) shell.log(` {${S.subtle}-fg}·{/} {${S.string}-fg}${file.path}{/}`);
|
|
526
523
|
if (excluded.length) {
|
|
527
524
|
shell.log('');
|
|
528
|
-
shell.log(`{${
|
|
529
|
-
for (const { file, excludedBy } of excluded) shell.log(` {${
|
|
525
|
+
shell.log(`{${S.warning}-fg}Excluded by security config{/}`);
|
|
526
|
+
for (const { file, excludedBy } of excluded) shell.log(` {${S.subtle}-fg}·{/} {${S.string}-fg}${file.path}{/} {${S.muted}-fg}(${excludedBy}){/}`);
|
|
530
527
|
}
|
|
531
528
|
shell.log('');
|
|
532
|
-
shell.log(`{${
|
|
529
|
+
shell.log(`{${S.muted}-fg}.singleton artifacts are not committed by /commit-last.{/}`);
|
|
533
530
|
shell.log('');
|
|
534
531
|
|
|
535
532
|
if (securityConfig.commit.requireConfirmation) {
|
|
536
533
|
const confirmation = (await shell.prompt('Stage and commit these files? (y/N)')).trim().toLowerCase();
|
|
537
534
|
if (confirmation !== 'y' && confirmation !== 'yes') {
|
|
538
|
-
shell.log(`{${
|
|
535
|
+
shell.log(`{${S.warning}-fg}!{/} Commit cancelled.`);
|
|
539
536
|
return;
|
|
540
537
|
}
|
|
541
538
|
}
|
|
@@ -547,5 +544,5 @@ async function cmdCommitLast(root, shell) {
|
|
|
547
544
|
await runCommand('git', ['add', '--', ...relFiles], { cwd: root });
|
|
548
545
|
await runCommand('git', ['commit', '-m', message], { cwd: root });
|
|
549
546
|
|
|
550
|
-
shell.log(`{${
|
|
547
|
+
shell.log(`{${S.success}-fg}✓{/} Commit created: {${S.string}-fg}${message}{/}`);
|
|
551
548
|
}
|