@ariacode/cli 0.2.2 → 0.2.3
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 +60 -7
- package/dist/actions/db-ask.d.ts +2 -0
- package/dist/actions/db-ask.js +23 -6
- package/dist/actions/db-ask.js.map +1 -1
- package/dist/actions/db-explain.d.ts +2 -0
- package/dist/actions/db-explain.js +23 -5
- package/dist/actions/db-explain.js.map +1 -1
- package/dist/actions/db-schema.d.ts +2 -0
- package/dist/actions/db-schema.js +15 -2
- package/dist/actions/db-schema.js.map +1 -1
- package/dist/actions/upgrade-deps.d.ts +2 -0
- package/dist/actions/upgrade-deps.js +18 -0
- package/dist/actions/upgrade-deps.js.map +1 -1
- package/dist/actions.d.ts +86 -71
- package/dist/actions.js +382 -182
- package/dist/actions.js.map +1 -1
- package/dist/agent.d.ts +0 -1
- package/dist/agent.js +1 -2
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +3 -3
- package/dist/app.js +3 -3
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +15 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/output/schemas.d.ts +92 -0
- package/dist/output/schemas.js +142 -0
- package/dist/output/schemas.js.map +1 -0
- package/dist/parser.d.ts +8 -3
- package/dist/parser.js +51 -5
- package/dist/parser.js.map +1 -1
- package/dist/repo.d.ts +0 -1
- package/dist/repo.js +6 -9
- package/dist/repo.js.map +1 -1
- package/dist/safety.d.ts +0 -4
- package/dist/safety.js +0 -4
- package/dist/safety.js.map +1 -1
- package/dist/storage/queries.d.ts +39 -0
- package/dist/storage/queries.js +211 -0
- package/dist/storage/queries.js.map +1 -0
- package/dist/tools.d.ts +6 -7
- package/dist/tools.js +20 -7
- package/dist/tools.js.map +1 -1
- package/dist/ui/diff-renderer.d.ts +28 -0
- package/dist/ui/diff-renderer.js +388 -0
- package/dist/ui/diff-renderer.js.map +1 -0
- package/dist/ui/highlight.d.ts +25 -0
- package/dist/ui/highlight.js +239 -0
- package/dist/ui/highlight.js.map +1 -0
- package/dist/ui.d.ts +3 -16
- package/dist/ui.js +7 -28
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/actions.js
CHANGED
|
@@ -19,12 +19,15 @@ import { getConfig } from "./config.js";
|
|
|
19
19
|
import { detectProjectType } from "./repo.js";
|
|
20
20
|
import { createProvider, ProviderError } from "./provider.js";
|
|
21
21
|
import { initializeDatabase, createSession, updateSessionStatus, getSession, logMessage, listSessions, } from "./storage.js";
|
|
22
|
-
import {
|
|
22
|
+
import { searchSessions, filterSessions, exportSessionMarkdown, } from "./storage/queries.js";
|
|
23
|
+
import { readFileTool, listDirectoryTool, searchCodeTool, readPackageJsonTool, readPrismaSchemaTool, proposeDiffTool, applyDiffTool, setDiffRenderOptions, } from "./tools.js";
|
|
23
24
|
import { agentLoop, UserCancelledError } from "./agent.js";
|
|
24
25
|
import prompts from "prompts";
|
|
25
|
-
import { initUI, info, print, error as uiError, bold, yellow, green, dim, cyan, red, renderTable, generateAndRenderDiff, confirm, ConfirmCancelledError, } from "./ui.js";
|
|
26
|
+
import { initUI, info, print, error as uiError, bold, yellow, green, dim, cyan, red, stripAnsi, renderTable, generateAndRenderDiff, confirm, ConfirmCancelledError, } from "./ui.js";
|
|
27
|
+
import { renderDiff as renderDiffEnhanced } from "./ui/diff-renderer.js";
|
|
28
|
+
import { formatOutput, AskOutputSchema, PlanOutputSchema, ReviewOutputSchema, ExploreOutputSchema, HistoryOutputSchema, DoctorOutputSchema, } from "./output/schemas.js";
|
|
26
29
|
import { loadConfig, validateConfig } from "./config.js";
|
|
27
|
-
import { getShellRcPath } from "./fs-helpers.js";
|
|
30
|
+
import { getShellRcPath, writeFileAtomic } from "./fs-helpers.js";
|
|
28
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
32
|
// ---------------------------------------------------------------------------
|
|
30
33
|
// Provider resolution with interactive setup
|
|
@@ -212,7 +215,6 @@ function saveProviderChoice(config) {
|
|
|
212
215
|
// ---------------------------------------------------------------------------
|
|
213
216
|
/**
|
|
214
217
|
* The five read-only tools exposed to the ask command.
|
|
215
|
-
* Requirements: 9.4
|
|
216
218
|
*/
|
|
217
219
|
const READ_ONLY_TOOLS = [
|
|
218
220
|
readFileTool,
|
|
@@ -340,19 +342,18 @@ function buildSystemPrompt(templateName, ctx, extraVars = {}) {
|
|
|
340
342
|
* Execute the ask command.
|
|
341
343
|
*
|
|
342
344
|
* Flow:
|
|
343
|
-
* 1. Load configuration and detect project type
|
|
344
|
-
* 2. Create or resume session with mode: "plan"
|
|
345
|
-
* 3. Build system prompt from ask.md template
|
|
346
|
-
* 4. Expose only read-only tools
|
|
347
|
-
* 5. Execute agent loop
|
|
348
|
-
* 6. Render response to terminal
|
|
349
|
-
* 7. Persist session to database
|
|
345
|
+
* 1. Load configuration and detect project type
|
|
346
|
+
* 2. Create or resume session with mode: "plan"
|
|
347
|
+
* 3. Build system prompt from ask.md template
|
|
348
|
+
* 4. Expose only read-only tools
|
|
349
|
+
* 5. Execute agent loop
|
|
350
|
+
* 6. Render response to terminal
|
|
351
|
+
* 7. Persist session to database
|
|
350
352
|
*
|
|
351
|
-
* Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9
|
|
352
353
|
*/
|
|
353
354
|
export async function runAsk(options) {
|
|
354
355
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
355
|
-
// 1. Load configuration
|
|
356
|
+
// 1. Load configuration
|
|
356
357
|
const config = getConfig(projectRoot, {
|
|
357
358
|
quiet: options.quiet,
|
|
358
359
|
maxTokens: options.maxTokens,
|
|
@@ -360,15 +361,15 @@ export async function runAsk(options) {
|
|
|
360
361
|
model: options.model,
|
|
361
362
|
});
|
|
362
363
|
// Initialize UI with config settings
|
|
363
|
-
initUI(config.ui.color, config.ui.quiet);
|
|
364
|
-
// Detect project type early to fail fast if package.json is missing
|
|
364
|
+
initUI(config.ui.color, config.ui.quiet || (options.format === 'json' || options.format === 'ndjson'));
|
|
365
|
+
// Detect project type early to fail fast if package.json is missing
|
|
365
366
|
detectProjectType(projectRoot);
|
|
366
|
-
// 2. Initialize database and create/resume session
|
|
367
|
+
// 2. Initialize database and create/resume session
|
|
367
368
|
const db = initializeDatabase();
|
|
368
369
|
let sessionId;
|
|
369
370
|
let resumedMessages = [];
|
|
370
371
|
if (options.session) {
|
|
371
|
-
// Resume existing session
|
|
372
|
+
// Resume existing session
|
|
372
373
|
const existing = getSession(db, options.session);
|
|
373
374
|
if (!existing) {
|
|
374
375
|
uiError(`Session not found: ${options.session}`);
|
|
@@ -383,7 +384,7 @@ export async function runAsk(options) {
|
|
|
383
384
|
resumedMessages = rows;
|
|
384
385
|
}
|
|
385
386
|
else {
|
|
386
|
-
// Create new session
|
|
387
|
+
// Create new session
|
|
387
388
|
sessionId = randomUUID();
|
|
388
389
|
createSession(db, {
|
|
389
390
|
id: sessionId,
|
|
@@ -408,7 +409,7 @@ export async function runAsk(options) {
|
|
|
408
409
|
updateSessionStatus(db, sessionId, "failed", String(err));
|
|
409
410
|
process.exit(4);
|
|
410
411
|
}
|
|
411
|
-
// 4. Build execution context with mode: "plan"
|
|
412
|
+
// 4. Build execution context with mode: "plan"
|
|
412
413
|
const ctx = {
|
|
413
414
|
projectRoot,
|
|
414
415
|
sessionId,
|
|
@@ -424,7 +425,7 @@ export async function runAsk(options) {
|
|
|
424
425
|
if (options.maxTokens !== undefined) {
|
|
425
426
|
config.provider.maxTokens = options.maxTokens;
|
|
426
427
|
}
|
|
427
|
-
// 5. Build system prompt from ask.md template
|
|
428
|
+
// 5. Build system prompt from ask.md template
|
|
428
429
|
const systemPrompt = buildSystemPrompt("ask", ctx);
|
|
429
430
|
// Log system prompt as a system message
|
|
430
431
|
logMessage(db, sessionId, "system", systemPrompt);
|
|
@@ -441,15 +442,34 @@ export async function runAsk(options) {
|
|
|
441
442
|
.join("\n\n");
|
|
442
443
|
userRequest = `[Resumed session context]\n${priorContext}\n\n[New question]: ${options.question}`;
|
|
443
444
|
}
|
|
444
|
-
// 6. Execute agent loop
|
|
445
|
-
// agentLoop streams the response to stdout as it arrives
|
|
445
|
+
// 6. Execute agent loop
|
|
446
|
+
// agentLoop streams the response to stdout as it arrives
|
|
446
447
|
try {
|
|
447
|
-
await agentLoop(ctx, userRequest, READ_ONLY_TOOLS, provider, config, "ask", db, systemPrompt);
|
|
448
|
-
//
|
|
448
|
+
const answer = await agentLoop(ctx, userRequest, READ_ONLY_TOOLS, provider, config, "ask", db, systemPrompt);
|
|
449
|
+
// v0.2.3: structured output
|
|
450
|
+
if (options.format === 'json' || options.format === 'ndjson') {
|
|
451
|
+
process.stdout.write(formatOutput({ version: '1', answer, sessionId }, options.format, AskOutputSchema));
|
|
452
|
+
}
|
|
453
|
+
else if (options.format === 'plain') {
|
|
454
|
+
// Strip ANSI codes and write plain text
|
|
455
|
+
process.stdout.write(stripAnsi(answer) + '\n');
|
|
456
|
+
}
|
|
457
|
+
// 7. Mark session as completed
|
|
449
458
|
updateSessionStatus(db, sessionId, "completed");
|
|
450
459
|
}
|
|
451
460
|
catch (err) {
|
|
452
461
|
const message = err instanceof Error ? err.message : String(err);
|
|
462
|
+
// v0.2.3: structured error output
|
|
463
|
+
if (options.format === 'json') {
|
|
464
|
+
process.stderr.write(JSON.stringify({ version: '1', error: message, exitCode: 1 }) + '\n');
|
|
465
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
else if (options.format === 'ndjson') {
|
|
469
|
+
process.stderr.write(JSON.stringify({ version: '1', event: 'error', error: message }) + '\n');
|
|
470
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
453
473
|
uiError(message);
|
|
454
474
|
updateSessionStatus(db, sessionId, "failed", message);
|
|
455
475
|
process.exit(1);
|
|
@@ -459,35 +479,34 @@ export async function runAsk(options) {
|
|
|
459
479
|
* Execute the plan command.
|
|
460
480
|
*
|
|
461
481
|
* Flow:
|
|
462
|
-
* 1. Load configuration and detect project type
|
|
463
|
-
* 2. Create or resume session with mode: "plan"
|
|
464
|
-
* 3. Build system prompt from plan.md template
|
|
465
|
-
* 4. Expose only read-only tools
|
|
466
|
-
* 5. Execute agent loop
|
|
467
|
-
* 6. Render structured plan to terminal
|
|
468
|
-
* 7. Save to file if --output flag provided
|
|
469
|
-
* 8. Persist session to database
|
|
482
|
+
* 1. Load configuration and detect project type
|
|
483
|
+
* 2. Create or resume session with mode: "plan"
|
|
484
|
+
* 3. Build system prompt from plan.md template
|
|
485
|
+
* 4. Expose only read-only tools
|
|
486
|
+
* 5. Execute agent loop
|
|
487
|
+
* 6. Render structured plan to terminal
|
|
488
|
+
* 7. Save to file if --output flag provided
|
|
489
|
+
* 8. Persist session to database
|
|
470
490
|
*
|
|
471
|
-
* Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8
|
|
472
491
|
*/
|
|
473
492
|
export async function runPlan(options) {
|
|
474
493
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
475
|
-
// 1. Load configuration
|
|
494
|
+
// 1. Load configuration
|
|
476
495
|
const config = getConfig(projectRoot, {
|
|
477
496
|
quiet: options.quiet,
|
|
478
497
|
provider: options.provider,
|
|
479
498
|
model: options.model,
|
|
480
499
|
});
|
|
481
500
|
// Initialize UI with config settings
|
|
482
|
-
initUI(config.ui.color, config.ui.quiet);
|
|
483
|
-
// Detect project type early to fail fast if package.json is missing
|
|
501
|
+
initUI(config.ui.color, config.ui.quiet || (options.format === 'json' || options.format === 'ndjson'));
|
|
502
|
+
// Detect project type early to fail fast if package.json is missing
|
|
484
503
|
detectProjectType(projectRoot);
|
|
485
|
-
// 2. Initialize database and create/resume session
|
|
504
|
+
// 2. Initialize database and create/resume session
|
|
486
505
|
const db = initializeDatabase();
|
|
487
506
|
let sessionId;
|
|
488
507
|
let resumedMessages = [];
|
|
489
508
|
if (options.session) {
|
|
490
|
-
// Resume existing session
|
|
509
|
+
// Resume existing session
|
|
491
510
|
const existing = getSession(db, options.session);
|
|
492
511
|
if (!existing) {
|
|
493
512
|
uiError(`Session not found: ${options.session}`);
|
|
@@ -502,7 +521,7 @@ export async function runPlan(options) {
|
|
|
502
521
|
resumedMessages = rows;
|
|
503
522
|
}
|
|
504
523
|
else {
|
|
505
|
-
// Create new session
|
|
524
|
+
// Create new session
|
|
506
525
|
sessionId = randomUUID();
|
|
507
526
|
createSession(db, {
|
|
508
527
|
id: sessionId,
|
|
@@ -527,7 +546,7 @@ export async function runPlan(options) {
|
|
|
527
546
|
updateSessionStatus(db, sessionId, "failed", String(err));
|
|
528
547
|
process.exit(4);
|
|
529
548
|
}
|
|
530
|
-
// 4. Build execution context with mode: "plan"
|
|
549
|
+
// 4. Build execution context with mode: "plan"
|
|
531
550
|
const ctx = {
|
|
532
551
|
projectRoot,
|
|
533
552
|
sessionId,
|
|
@@ -539,7 +558,7 @@ export async function runPlan(options) {
|
|
|
539
558
|
maxIterations: config.agent.maxIterations,
|
|
540
559
|
timeoutSeconds: config.agent.timeoutSeconds,
|
|
541
560
|
};
|
|
542
|
-
// 5. Build system prompt from plan.md template
|
|
561
|
+
// 5. Build system prompt from plan.md template
|
|
543
562
|
const systemPrompt = buildSystemPrompt("plan", ctx, { userGoal: options.goal });
|
|
544
563
|
// Log system prompt as a system message
|
|
545
564
|
logMessage(db, sessionId, "system", systemPrompt);
|
|
@@ -553,17 +572,21 @@ export async function runPlan(options) {
|
|
|
553
572
|
.join("\n\n");
|
|
554
573
|
userRequest = `[Resumed session context]\n${priorContext}\n\n[New goal]: ${options.goal}`;
|
|
555
574
|
}
|
|
556
|
-
// 6. Execute agent loop — streams response to stdout
|
|
575
|
+
// 6. Execute agent loop — streams response to stdout
|
|
557
576
|
try {
|
|
558
577
|
const planContent = await agentLoop(ctx, userRequest, READ_ONLY_TOOLS, provider, config, "plan", db, systemPrompt);
|
|
559
|
-
//
|
|
578
|
+
// v0.2.3: structured output
|
|
579
|
+
if (options.format === 'json' || options.format === 'ndjson') {
|
|
580
|
+
process.stdout.write(formatOutput({ version: '1', plan: planContent, sessionId, outputPath: options.output }, options.format, PlanOutputSchema));
|
|
581
|
+
}
|
|
582
|
+
// 7. Save to file if --output flag provided
|
|
560
583
|
if (options.output) {
|
|
561
584
|
const outputPath = path.resolve(options.output);
|
|
562
585
|
const outputDir = path.dirname(outputPath);
|
|
563
586
|
if (!existsSync(outputDir)) {
|
|
564
587
|
mkdirSync(outputDir, { recursive: true });
|
|
565
588
|
}
|
|
566
|
-
|
|
589
|
+
writeFileAtomic(outputPath, planContent);
|
|
567
590
|
info(`Plan saved to ${options.output}`);
|
|
568
591
|
}
|
|
569
592
|
// 8. Mark session as completed (Req 10.8)
|
|
@@ -571,6 +594,17 @@ export async function runPlan(options) {
|
|
|
571
594
|
}
|
|
572
595
|
catch (err) {
|
|
573
596
|
const message = err instanceof Error ? err.message : String(err);
|
|
597
|
+
// v0.2.3: structured error output
|
|
598
|
+
if (options.format === 'json') {
|
|
599
|
+
process.stderr.write(JSON.stringify({ version: '1', error: message, exitCode: 1 }) + '\n');
|
|
600
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
else if (options.format === 'ndjson') {
|
|
604
|
+
process.stderr.write(JSON.stringify({ version: '1', event: 'error', error: message }) + '\n');
|
|
605
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
574
608
|
uiError(message);
|
|
575
609
|
updateSessionStatus(db, sessionId, "failed", message);
|
|
576
610
|
process.exit(1);
|
|
@@ -580,25 +614,24 @@ export async function runPlan(options) {
|
|
|
580
614
|
* Execute the patch command.
|
|
581
615
|
*
|
|
582
616
|
* Flow:
|
|
583
|
-
* 1. Load configuration and detect project type
|
|
584
|
-
* 2. Create session with mode: "build"
|
|
585
|
-
* 3. Build system prompt from patch.md template
|
|
586
|
-
* 4. Expose read-only + mutation tools
|
|
587
|
-
* 5. Execute agent loop — agent calls propose_diff
|
|
588
|
-
* 6. Render diff preview with syntax highlighting
|
|
589
|
-
* 7. Render mutation summary
|
|
590
|
-
* 8. If --dry-run, exit with code 0
|
|
591
|
-
* 9. If not --yes, prompt for confirmation
|
|
592
|
-
* 10. Agent calls apply_diff atomically
|
|
593
|
-
* 11. Log mutation to database
|
|
594
|
-
* 12. Display rollback hints
|
|
595
|
-
* 13. Persist session to database
|
|
617
|
+
* 1. Load configuration and detect project type
|
|
618
|
+
* 2. Create session with mode: "build"
|
|
619
|
+
* 3. Build system prompt from patch.md template
|
|
620
|
+
* 4. Expose read-only + mutation tools
|
|
621
|
+
* 5. Execute agent loop — agent calls propose_diff
|
|
622
|
+
* 6. Render diff preview with syntax highlighting
|
|
623
|
+
* 7. Render mutation summary
|
|
624
|
+
* 8. If --dry-run, exit with code 0
|
|
625
|
+
* 9. If not --yes, prompt for confirmation
|
|
626
|
+
* 10. Agent calls apply_diff atomically
|
|
627
|
+
* 11. Log mutation to database
|
|
628
|
+
* 12. Display rollback hints
|
|
629
|
+
* 13. Persist session to database
|
|
596
630
|
*
|
|
597
|
-
* Requirements: 11.1–11.13, 17.1–17.9
|
|
598
631
|
*/
|
|
599
632
|
export async function runPatch(options) {
|
|
600
633
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
601
|
-
// 1. Load configuration
|
|
634
|
+
// 1. Load configuration
|
|
602
635
|
const config = getConfig(projectRoot, {
|
|
603
636
|
quiet: options.quiet,
|
|
604
637
|
provider: options.provider,
|
|
@@ -609,9 +642,14 @@ export async function runPatch(options) {
|
|
|
609
642
|
config.agent.mode = "build"; // keep build mode, dryRun handled via ctx
|
|
610
643
|
// Initialize UI with config settings
|
|
611
644
|
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet));
|
|
612
|
-
//
|
|
645
|
+
// Plain format guard: require --yes for mutation commands
|
|
646
|
+
if (options.format === 'plain' && !options.yes) {
|
|
647
|
+
process.stdout.write('plain format requires --yes for mutation commands\n');
|
|
648
|
+
process.exit(2);
|
|
649
|
+
}
|
|
650
|
+
// Detect project type early to fail fast
|
|
613
651
|
detectProjectType(projectRoot);
|
|
614
|
-
// 2. Initialize database and create session
|
|
652
|
+
// 2. Initialize database and create session
|
|
615
653
|
const db = initializeDatabase();
|
|
616
654
|
let sessionId;
|
|
617
655
|
if (options.session) {
|
|
@@ -648,7 +686,7 @@ export async function runPatch(options) {
|
|
|
648
686
|
updateSessionStatus(db, sessionId, "failed", String(err));
|
|
649
687
|
process.exit(4);
|
|
650
688
|
}
|
|
651
|
-
// 4. Build execution context with mode: "build"
|
|
689
|
+
// 4. Build execution context with mode: "build"
|
|
652
690
|
const ctx = {
|
|
653
691
|
projectRoot,
|
|
654
692
|
sessionId,
|
|
@@ -660,10 +698,10 @@ export async function runPatch(options) {
|
|
|
660
698
|
maxIterations: config.agent.maxIterations,
|
|
661
699
|
timeoutSeconds: config.agent.timeoutSeconds,
|
|
662
700
|
};
|
|
663
|
-
// 5. Build system prompt from patch.md template
|
|
701
|
+
// 5. Build system prompt from patch.md template
|
|
664
702
|
const systemPrompt = buildSystemPrompt("patch", ctx);
|
|
665
703
|
logMessage(db, sessionId, "system", systemPrompt);
|
|
666
|
-
// Expose read-only tools + mutation tools
|
|
704
|
+
// Expose read-only tools + mutation tools
|
|
667
705
|
const patchTools = [
|
|
668
706
|
readFileTool,
|
|
669
707
|
listDirectoryTool,
|
|
@@ -676,15 +714,25 @@ export async function runPatch(options) {
|
|
|
676
714
|
if (options.dryRun) {
|
|
677
715
|
info(bold("Dry-run mode — changes will be previewed but not applied."));
|
|
678
716
|
}
|
|
679
|
-
//
|
|
717
|
+
// Configure enhanced diff renderer
|
|
718
|
+
const terminalWidth = process.stdout.columns ?? 80;
|
|
719
|
+
const diffRenderOptions = {
|
|
720
|
+
split: options.split ?? false,
|
|
721
|
+
lineNumbers: options.format !== 'plain',
|
|
722
|
+
collapseThreshold: 5,
|
|
723
|
+
terminalWidth,
|
|
724
|
+
...(options.format === 'plain' ? { language: undefined } : {}),
|
|
725
|
+
};
|
|
726
|
+
setDiffRenderOptions((diffText) => renderDiffEnhanced(diffText, diffRenderOptions));
|
|
727
|
+
// 6. Execute agent loop
|
|
680
728
|
// The agent loop handles:
|
|
681
|
-
// - propose_diff: generates diff + MutationSummary
|
|
682
|
-
// - dry-run enforcement: skips apply_diff
|
|
683
|
-
// - confirmation prompt before apply_diff
|
|
684
|
-
// - atomic application via apply_diff
|
|
729
|
+
// - propose_diff: generates diff + MutationSummary
|
|
730
|
+
// - dry-run enforcement: skips apply_diff
|
|
731
|
+
// - confirmation prompt before apply_diff
|
|
732
|
+
// - atomic application via apply_diff
|
|
685
733
|
try {
|
|
686
734
|
await agentLoop(ctx, options.description, patchTools, provider, config, "patch", db, systemPrompt);
|
|
687
|
-
// 12. Display rollback hints after successful application
|
|
735
|
+
// 12. Display rollback hints after successful application
|
|
688
736
|
// The agent loop streams the response which includes rollback hints from
|
|
689
737
|
// the apply_diff result. We add a final summary line here.
|
|
690
738
|
if (!options.dryRun) {
|
|
@@ -696,22 +744,37 @@ export async function runPatch(options) {
|
|
|
696
744
|
info("");
|
|
697
745
|
info(yellow("Dry-run complete — no files were modified."));
|
|
698
746
|
}
|
|
699
|
-
// 13. Mark session as completed
|
|
747
|
+
// 13. Mark session as completed
|
|
700
748
|
updateSessionStatus(db, sessionId, "completed");
|
|
701
749
|
}
|
|
702
750
|
catch (err) {
|
|
703
751
|
const message = err instanceof Error ? err.message : String(err);
|
|
704
|
-
// Handle user cancellation
|
|
752
|
+
// Handle user cancellation
|
|
705
753
|
if (err instanceof UserCancelledError || err instanceof ConfirmCancelledError) {
|
|
706
754
|
info("");
|
|
707
755
|
info(yellow("Operation cancelled."));
|
|
708
756
|
updateSessionStatus(db, sessionId, "cancelled");
|
|
709
757
|
process.exit(130);
|
|
710
758
|
}
|
|
759
|
+
// v0.2.3: structured error output
|
|
760
|
+
if (options.format === 'json') {
|
|
761
|
+
process.stderr.write(JSON.stringify({ version: '1', error: message, exitCode: 1 }) + '\n');
|
|
762
|
+
updateSessionStatus(db, sessionId, "failed", message);
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
else if (options.format === 'ndjson') {
|
|
766
|
+
process.stderr.write(JSON.stringify({ version: '1', event: 'error', error: message }) + '\n');
|
|
767
|
+
updateSessionStatus(db, sessionId, "failed", message);
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
711
770
|
uiError(message);
|
|
712
771
|
updateSessionStatus(db, sessionId, "failed", message);
|
|
713
772
|
process.exit(1);
|
|
714
773
|
}
|
|
774
|
+
finally {
|
|
775
|
+
// Clear diff render options after patch completes
|
|
776
|
+
setDiffRenderOptions(null);
|
|
777
|
+
}
|
|
715
778
|
}
|
|
716
779
|
/**
|
|
717
780
|
* Read git diff based on the provided options.
|
|
@@ -720,21 +783,20 @@ export async function runPatch(options) {
|
|
|
720
783
|
* - --unstaged: unstaged changes (`git diff`)
|
|
721
784
|
* - --branch <base>: compare to base branch (`git diff <base>...HEAD`)
|
|
722
785
|
*
|
|
723
|
-
* Requirements: 12.3, 12.4, 12.5
|
|
724
786
|
*/
|
|
725
787
|
function readGitDiff(options, projectRoot) {
|
|
726
788
|
try {
|
|
727
789
|
let args;
|
|
728
790
|
if (options.branch) {
|
|
729
|
-
// Compare current branch to specified base
|
|
791
|
+
// Compare current branch to specified base
|
|
730
792
|
args = ["diff", `${options.branch}...HEAD`];
|
|
731
793
|
}
|
|
732
794
|
else if (options.unstaged) {
|
|
733
|
-
// Unstaged changes
|
|
795
|
+
// Unstaged changes
|
|
734
796
|
args = ["diff"];
|
|
735
797
|
}
|
|
736
798
|
else {
|
|
737
|
-
// Staged changes — default
|
|
799
|
+
// Staged changes — default
|
|
738
800
|
args = ["diff", "--cached"];
|
|
739
801
|
}
|
|
740
802
|
const output = execFileSync("git", args, {
|
|
@@ -754,7 +816,6 @@ function readGitDiff(options, projectRoot) {
|
|
|
754
816
|
* Extracts summary, issues (with severity), and suggestions from the
|
|
755
817
|
* "# Code Review" markdown format defined in review.md.
|
|
756
818
|
*
|
|
757
|
-
* Requirements: 12.7
|
|
758
819
|
*/
|
|
759
820
|
function parseReviewResponse(content) {
|
|
760
821
|
const result = {
|
|
@@ -805,7 +866,6 @@ function parseReviewResponse(content) {
|
|
|
805
866
|
/**
|
|
806
867
|
* Render the structured review to the terminal in readable format.
|
|
807
868
|
*
|
|
808
|
-
* Requirements: 12.8
|
|
809
869
|
*/
|
|
810
870
|
function renderReview(review) {
|
|
811
871
|
info("");
|
|
@@ -844,32 +904,31 @@ function renderReview(review) {
|
|
|
844
904
|
* Execute the review command.
|
|
845
905
|
*
|
|
846
906
|
* Flow:
|
|
847
|
-
* 1. Parse flags and load configuration
|
|
848
|
-
* 2. Detect project type
|
|
849
|
-
* 3. Create session with mode: "plan"
|
|
850
|
-
* 4. Read git diff (staged / unstaged / branch)
|
|
851
|
-
* 5. Build system prompt from review.md template
|
|
852
|
-
* 6. Send diff + project context to provider
|
|
853
|
-
* 7. Parse structured review (summary, issues, suggestions)
|
|
854
|
-
* 8. Render review to terminal
|
|
855
|
-
* 9. Output JSON if --format json
|
|
856
|
-
* 10. Persist session to database
|
|
907
|
+
* 1. Parse flags and load configuration
|
|
908
|
+
* 2. Detect project type
|
|
909
|
+
* 3. Create session with mode: "plan"
|
|
910
|
+
* 4. Read git diff (staged / unstaged / branch)
|
|
911
|
+
* 5. Build system prompt from review.md template
|
|
912
|
+
* 6. Send diff + project context to provider
|
|
913
|
+
* 7. Parse structured review (summary, issues, suggestions)
|
|
914
|
+
* 8. Render review to terminal
|
|
915
|
+
* 9. Output JSON if --format json
|
|
916
|
+
* 10. Persist session to database
|
|
857
917
|
*
|
|
858
|
-
* Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9, 12.10
|
|
859
918
|
*/
|
|
860
919
|
export async function runReview(options) {
|
|
861
920
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
862
|
-
// 1. Load configuration
|
|
921
|
+
// 1. Load configuration
|
|
863
922
|
const config = getConfig(projectRoot, {
|
|
864
923
|
quiet: options.quiet,
|
|
865
924
|
provider: options.provider,
|
|
866
925
|
model: options.model,
|
|
867
926
|
});
|
|
868
927
|
// Initialize UI with config settings
|
|
869
|
-
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet));
|
|
870
|
-
// 2. Detect project type early to fail fast
|
|
928
|
+
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet) || (options.format === 'json' || options.format === 'ndjson'));
|
|
929
|
+
// 2. Detect project type early to fail fast
|
|
871
930
|
detectProjectType(projectRoot);
|
|
872
|
-
// 3. Initialize database and create session
|
|
931
|
+
// 3. Initialize database and create session
|
|
873
932
|
const db = initializeDatabase();
|
|
874
933
|
const sessionId = randomUUID();
|
|
875
934
|
createSession(db, {
|
|
@@ -894,7 +953,7 @@ export async function runReview(options) {
|
|
|
894
953
|
updateSessionStatus(db, sessionId, "failed", String(err));
|
|
895
954
|
process.exit(4);
|
|
896
955
|
}
|
|
897
|
-
// Build execution context with mode: "plan"
|
|
956
|
+
// Build execution context with mode: "plan"
|
|
898
957
|
const ctx = {
|
|
899
958
|
projectRoot,
|
|
900
959
|
sessionId,
|
|
@@ -906,7 +965,7 @@ export async function runReview(options) {
|
|
|
906
965
|
maxIterations: config.agent.maxIterations,
|
|
907
966
|
timeoutSeconds: config.agent.timeoutSeconds,
|
|
908
967
|
};
|
|
909
|
-
// 4. Read git diff
|
|
968
|
+
// 4. Read git diff
|
|
910
969
|
let diff;
|
|
911
970
|
try {
|
|
912
971
|
diff = readGitDiff(options, projectRoot);
|
|
@@ -927,10 +986,10 @@ export async function runReview(options) {
|
|
|
927
986
|
updateSessionStatus(db, sessionId, "completed");
|
|
928
987
|
return;
|
|
929
988
|
}
|
|
930
|
-
// 5. Build system prompt from review.md template
|
|
989
|
+
// 5. Build system prompt from review.md template
|
|
931
990
|
const systemPrompt = buildSystemPrompt("review", ctx);
|
|
932
991
|
logMessage(db, sessionId, "system", systemPrompt);
|
|
933
|
-
// 6. Build user request: diff + project context
|
|
992
|
+
// 6. Build user request: diff + project context
|
|
934
993
|
const project = detectProjectType(projectRoot);
|
|
935
994
|
const diffSource = options.branch
|
|
936
995
|
? `branch diff (current vs ${options.branch})`
|
|
@@ -950,23 +1009,28 @@ export async function runReview(options) {
|
|
|
950
1009
|
]
|
|
951
1010
|
.filter((l) => l !== null)
|
|
952
1011
|
.join("\n");
|
|
953
|
-
// Execute agent loop — streams response to stdout
|
|
1012
|
+
// Execute agent loop — streams response to stdout
|
|
954
1013
|
try {
|
|
955
1014
|
const reviewContent = await agentLoop(ctx, userRequest, READ_ONLY_TOOLS, provider, config, "review", db, systemPrompt);
|
|
956
|
-
// 7. Parse structured review
|
|
1015
|
+
// 7. Parse structured review
|
|
957
1016
|
const review = parseReviewResponse(reviewContent);
|
|
958
|
-
// 8 & 9. Render or output
|
|
959
|
-
if (options.format ===
|
|
960
|
-
//
|
|
961
|
-
process.stdout.write(
|
|
1017
|
+
// 8 & 9. Render or output
|
|
1018
|
+
if (options.format === 'json' || options.format === 'ndjson') {
|
|
1019
|
+
// v0.2.3: structured output via formatOutput
|
|
1020
|
+
process.stdout.write(formatOutput({ version: '1', review: reviewContent, sessionId, branch: options.branch }, options.format, ReviewOutputSchema));
|
|
1021
|
+
}
|
|
1022
|
+
else if (options.format === 'plain') {
|
|
1023
|
+
// Strip ANSI codes for plain output
|
|
1024
|
+
const plainReview = stripAnsi(reviewContent);
|
|
1025
|
+
process.stdout.write(plainReview + '\n');
|
|
962
1026
|
}
|
|
963
1027
|
else {
|
|
964
|
-
// Render to terminal in readable format
|
|
1028
|
+
// Render to terminal in readable format
|
|
965
1029
|
// Note: agentLoop already streamed the raw response; renderReview
|
|
966
1030
|
// provides a structured re-render for clarity.
|
|
967
1031
|
renderReview(review);
|
|
968
1032
|
}
|
|
969
|
-
// 10. Mark session as completed
|
|
1033
|
+
// 10. Mark session as completed
|
|
970
1034
|
updateSessionStatus(db, sessionId, "completed");
|
|
971
1035
|
}
|
|
972
1036
|
catch (err) {
|
|
@@ -978,6 +1042,17 @@ export async function runReview(options) {
|
|
|
978
1042
|
updateSessionStatus(db, sessionId, "cancelled");
|
|
979
1043
|
process.exit(130);
|
|
980
1044
|
}
|
|
1045
|
+
// v0.2.3: structured error output
|
|
1046
|
+
if (options.format === 'json') {
|
|
1047
|
+
process.stderr.write(JSON.stringify({ version: '1', error: message, exitCode: 1 }) + '\n');
|
|
1048
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
else if (options.format === 'ndjson') {
|
|
1052
|
+
process.stderr.write(JSON.stringify({ version: '1', event: 'error', error: message }) + '\n');
|
|
1053
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
}
|
|
981
1056
|
uiError(message);
|
|
982
1057
|
updateSessionStatus(db, sessionId, "failed", message);
|
|
983
1058
|
process.exit(1);
|
|
@@ -987,33 +1062,32 @@ export async function runReview(options) {
|
|
|
987
1062
|
* Execute the explore command.
|
|
988
1063
|
*
|
|
989
1064
|
* Flow:
|
|
990
|
-
* 1. Parse flags and load configuration
|
|
991
|
-
* 2. Detect project type
|
|
992
|
-
* 3. Create session with mode: "plan"
|
|
993
|
-
* 4. Scan repository structure respecting .gitignore
|
|
994
|
-
* 5. Detect frameworks and key configuration files
|
|
995
|
-
* 6. Identify entry points based on project type
|
|
996
|
-
* 7. Build system prompt from explore.md template
|
|
997
|
-
* 8. Execute agent loop to summarize structure/patterns
|
|
998
|
-
* 9. Render exploration summary to terminal
|
|
999
|
-
* 10. Save to ./.aria/explore.md if --save flag
|
|
1000
|
-
* 11. Persist session to database
|
|
1065
|
+
* 1. Parse flags and load configuration
|
|
1066
|
+
* 2. Detect project type
|
|
1067
|
+
* 3. Create session with mode: "plan"
|
|
1068
|
+
* 4. Scan repository structure respecting .gitignore
|
|
1069
|
+
* 5. Detect frameworks and key configuration files
|
|
1070
|
+
* 6. Identify entry points based on project type
|
|
1071
|
+
* 7. Build system prompt from explore.md template
|
|
1072
|
+
* 8. Execute agent loop to summarize structure/patterns
|
|
1073
|
+
* 9. Render exploration summary to terminal
|
|
1074
|
+
* 10. Save to ./.aria/explore.md if --save flag
|
|
1075
|
+
* 11. Persist session to database
|
|
1001
1076
|
*
|
|
1002
|
-
* Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6, 13.7, 13.8, 13.9, 13.10
|
|
1003
1077
|
*/
|
|
1004
1078
|
export async function runExplore(options) {
|
|
1005
1079
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
1006
|
-
// 1. Load configuration
|
|
1080
|
+
// 1. Load configuration
|
|
1007
1081
|
const config = getConfig(projectRoot, {
|
|
1008
1082
|
quiet: options.quiet,
|
|
1009
1083
|
provider: options.provider,
|
|
1010
1084
|
model: options.model,
|
|
1011
1085
|
});
|
|
1012
1086
|
// Initialize UI with config settings
|
|
1013
|
-
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet));
|
|
1014
|
-
// Detect project type early to fail fast
|
|
1087
|
+
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet) || (options.format === 'json' || options.format === 'ndjson'));
|
|
1088
|
+
// Detect project type early to fail fast
|
|
1015
1089
|
const project = detectProjectType(projectRoot);
|
|
1016
|
-
// 2. Initialize database and create session
|
|
1090
|
+
// 2. Initialize database and create session
|
|
1017
1091
|
const db = initializeDatabase();
|
|
1018
1092
|
const sessionId = randomUUID();
|
|
1019
1093
|
createSession(db, {
|
|
@@ -1038,7 +1112,7 @@ export async function runExplore(options) {
|
|
|
1038
1112
|
updateSessionStatus(db, sessionId, "failed", String(err));
|
|
1039
1113
|
process.exit(4);
|
|
1040
1114
|
}
|
|
1041
|
-
// 4. Build execution context with mode: "plan"
|
|
1115
|
+
// 4. Build execution context with mode: "plan"
|
|
1042
1116
|
const ctx = {
|
|
1043
1117
|
projectRoot,
|
|
1044
1118
|
sessionId,
|
|
@@ -1050,10 +1124,10 @@ export async function runExplore(options) {
|
|
|
1050
1124
|
maxIterations: config.agent.maxIterations,
|
|
1051
1125
|
timeoutSeconds: config.agent.timeoutSeconds,
|
|
1052
1126
|
};
|
|
1053
|
-
// 5. Build system prompt from explore.md template
|
|
1127
|
+
// 5. Build system prompt from explore.md template
|
|
1054
1128
|
const systemPrompt = buildSystemPrompt("explore", ctx);
|
|
1055
1129
|
logMessage(db, sessionId, "system", systemPrompt);
|
|
1056
|
-
// 6. Build user request with project context and depth hint
|
|
1130
|
+
// 6. Build user request with project context and depth hint
|
|
1057
1131
|
const frameworkInfo = project.framework
|
|
1058
1132
|
? `${project.framework.name}${project.framework.version ? ` ${project.framework.version}` : ""}${project.framework.router ? ` (${project.framework.router} router)` : ""}`
|
|
1059
1133
|
: "none";
|
|
@@ -1077,20 +1151,24 @@ export async function runExplore(options) {
|
|
|
1077
1151
|
]
|
|
1078
1152
|
.filter((l) => l !== null)
|
|
1079
1153
|
.join("\n");
|
|
1080
|
-
// 7. Execute agent loop — streams response to stdout
|
|
1154
|
+
// 7. Execute agent loop — streams response to stdout
|
|
1081
1155
|
try {
|
|
1082
1156
|
const exploreContent = await agentLoop(ctx, userRequest, READ_ONLY_TOOLS, provider, config, "explore", db, systemPrompt);
|
|
1083
|
-
//
|
|
1157
|
+
// v0.2.3: structured output
|
|
1158
|
+
if (options.format === 'json' || options.format === 'ndjson') {
|
|
1159
|
+
process.stdout.write(formatOutput({ version: '1', summary: exploreContent, sessionId }, options.format, ExploreOutputSchema));
|
|
1160
|
+
}
|
|
1161
|
+
// 8. Save to ./.aria/explore.md if --save flag provided
|
|
1084
1162
|
if (options.save) {
|
|
1085
1163
|
const ariaDir = path.join(projectRoot, ".aria");
|
|
1086
1164
|
const savePath = path.join(ariaDir, "explore.md");
|
|
1087
1165
|
if (!existsSync(ariaDir)) {
|
|
1088
1166
|
mkdirSync(ariaDir, { recursive: true });
|
|
1089
1167
|
}
|
|
1090
|
-
|
|
1168
|
+
writeFileAtomic(savePath, exploreContent);
|
|
1091
1169
|
info(`Exploration summary saved to .aria/explore.md`);
|
|
1092
1170
|
}
|
|
1093
|
-
// 9. Mark session as completed
|
|
1171
|
+
// 9. Mark session as completed
|
|
1094
1172
|
updateSessionStatus(db, sessionId, "completed");
|
|
1095
1173
|
}
|
|
1096
1174
|
catch (err) {
|
|
@@ -1101,6 +1179,17 @@ export async function runExplore(options) {
|
|
|
1101
1179
|
updateSessionStatus(db, sessionId, "cancelled");
|
|
1102
1180
|
process.exit(130);
|
|
1103
1181
|
}
|
|
1182
|
+
// v0.2.3: structured error output
|
|
1183
|
+
if (options.format === 'json') {
|
|
1184
|
+
process.stderr.write(JSON.stringify({ version: '1', error: message, exitCode: 1 }) + '\n');
|
|
1185
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
else if (options.format === 'ndjson') {
|
|
1189
|
+
process.stderr.write(JSON.stringify({ version: '1', event: 'error', error: message }) + '\n');
|
|
1190
|
+
updateSessionStatus(db, sessionId, 'failed', message);
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
}
|
|
1104
1193
|
uiError(message);
|
|
1105
1194
|
updateSessionStatus(db, sessionId, "failed", message);
|
|
1106
1195
|
process.exit(1);
|
|
@@ -1110,7 +1199,6 @@ export async function runExplore(options) {
|
|
|
1110
1199
|
* Format a SQLite timestamp string into a human-readable relative time.
|
|
1111
1200
|
* e.g. "2 hours ago", "3 days ago", "just now"
|
|
1112
1201
|
*
|
|
1113
|
-
* Requirements: 14.7
|
|
1114
1202
|
*/
|
|
1115
1203
|
function formatTimestamp(timestamp) {
|
|
1116
1204
|
// SQLite CURRENT_TIMESTAMP returns "YYYY-MM-DD HH:MM:SS" (space, no T, no Z).
|
|
@@ -1162,8 +1250,20 @@ function colorizeStatus(status) {
|
|
|
1162
1250
|
* Render a tool execution tree for a session.
|
|
1163
1251
|
* Shows tool calls in chronological order with input/output summaries.
|
|
1164
1252
|
*
|
|
1165
|
-
* Requirements: 14.6
|
|
1166
1253
|
*/
|
|
1254
|
+
// Tool type icon sets
|
|
1255
|
+
const READ_TOOLS = new Set(['read_file', 'read_package_json', 'read_prisma_schema']);
|
|
1256
|
+
const SEARCH_TOOLS = new Set(['search_code', 'list_directory']);
|
|
1257
|
+
const MUTATION_TOOLS = new Set(['propose_diff', 'apply_diff', 'apply_schema_change']);
|
|
1258
|
+
function toolIcon(toolName) {
|
|
1259
|
+
if (READ_TOOLS.has(toolName))
|
|
1260
|
+
return '[R]';
|
|
1261
|
+
if (SEARCH_TOOLS.has(toolName))
|
|
1262
|
+
return '[S]';
|
|
1263
|
+
if (MUTATION_TOOLS.has(toolName))
|
|
1264
|
+
return '[W]';
|
|
1265
|
+
return '[.]';
|
|
1266
|
+
}
|
|
1167
1267
|
function renderToolTree(db, sessionId) {
|
|
1168
1268
|
const executions = db
|
|
1169
1269
|
.prepare(`SELECT tool_name, input, output, error, created_at
|
|
@@ -1181,7 +1281,8 @@ function renderToolTree(db, sessionId) {
|
|
|
1181
1281
|
const prefix = isLast ? "└─" : "├─";
|
|
1182
1282
|
const childPrefix = isLast ? " " : "│ ";
|
|
1183
1283
|
const statusIcon = exec.error ? red("✗") : green("✓");
|
|
1184
|
-
|
|
1284
|
+
const typeIcon = toolIcon(exec.tool_name);
|
|
1285
|
+
info(` ${prefix} ${typeIcon} ${statusIcon} ${bold(exec.tool_name)} ${dim(formatTimestamp(exec.created_at))}`);
|
|
1185
1286
|
// Show a brief summary of the input
|
|
1186
1287
|
try {
|
|
1187
1288
|
const inputObj = JSON.parse(exec.input);
|
|
@@ -1214,23 +1315,88 @@ function renderToolTree(db, sessionId) {
|
|
|
1214
1315
|
* Execute the history command.
|
|
1215
1316
|
*
|
|
1216
1317
|
* Flow:
|
|
1217
|
-
* 1. If no --session flag: list recent sessions in a table
|
|
1218
|
-
* 2. If --session flag: display full session log
|
|
1219
|
-
* 3. If --tree flag: render tool execution tree
|
|
1220
|
-
* 4. Format timestamps in human-readable format
|
|
1221
|
-
* 5. Support pagination for large result sets
|
|
1318
|
+
* 1. If no --session flag: list recent sessions in a table
|
|
1319
|
+
* 2. If --session flag: display full session log
|
|
1320
|
+
* 3. If --tree flag: render tool execution tree
|
|
1321
|
+
* 4. Format timestamps in human-readable format
|
|
1322
|
+
* 5. Support pagination for large result sets
|
|
1222
1323
|
*
|
|
1223
|
-
* Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7, 14.8
|
|
1224
1324
|
*/
|
|
1225
1325
|
export async function runHistory(options) {
|
|
1226
1326
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
1227
1327
|
// Load configuration and initialize UI
|
|
1228
1328
|
const config = getConfig(projectRoot, { quiet: options.quiet });
|
|
1229
|
-
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet));
|
|
1329
|
+
initUI(config.ui.color, config.ui.quiet || Boolean(options.quiet) || (options.format === 'json' || options.format === 'ndjson'));
|
|
1230
1330
|
// Initialize database
|
|
1231
1331
|
const db = initializeDatabase();
|
|
1232
1332
|
// ---------------------------------------------------------------------------
|
|
1233
|
-
//
|
|
1333
|
+
// v0.2.3: Structured output for json/ndjson/plain — applies to session listing
|
|
1334
|
+
// ---------------------------------------------------------------------------
|
|
1335
|
+
const isStructuredFormat = options.format === 'json' || options.format === 'ndjson';
|
|
1336
|
+
const isPlainFormat = options.format === 'plain';
|
|
1337
|
+
// ---------------------------------------------------------------------------
|
|
1338
|
+
// v0.2.3: Search path — full-text search across session content
|
|
1339
|
+
// ---------------------------------------------------------------------------
|
|
1340
|
+
if (options.search) {
|
|
1341
|
+
const results = searchSessions(db, options.search, { limit: options.limit });
|
|
1342
|
+
if (results.length === 0) {
|
|
1343
|
+
info("No sessions found matching query.");
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const rows = results.map(({ session, matchedInMessages }) => [
|
|
1347
|
+
dim(session.id.slice(0, 8)),
|
|
1348
|
+
cyan(session.command),
|
|
1349
|
+
colorizeStatus(session.status),
|
|
1350
|
+
formatTimestamp(session.createdAt),
|
|
1351
|
+
matchedInMessages ? dim("message") : dim("metadata"),
|
|
1352
|
+
]);
|
|
1353
|
+
const table = renderTable({ head: ["ID", "Command", "Status", "Started", "Match"], colWidths: [12, 12, 12, 20, 12] }, rows);
|
|
1354
|
+
info(table);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
// ---------------------------------------------------------------------------
|
|
1358
|
+
// v0.2.3: Filter path — filter by command, since, status
|
|
1359
|
+
// ---------------------------------------------------------------------------
|
|
1360
|
+
if (options.command || options.since || options.status) {
|
|
1361
|
+
const sessions = filterSessions(db, {
|
|
1362
|
+
command: options.command,
|
|
1363
|
+
since: options.since,
|
|
1364
|
+
status: options.status,
|
|
1365
|
+
limit: options.limit,
|
|
1366
|
+
});
|
|
1367
|
+
if (sessions.length === 0) {
|
|
1368
|
+
info("No sessions found.");
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const rows = sessions.map((s) => [
|
|
1372
|
+
dim(s.id.slice(0, 8)),
|
|
1373
|
+
cyan(s.command),
|
|
1374
|
+
formatTimestamp(s.createdAt),
|
|
1375
|
+
colorizeStatus(s.status),
|
|
1376
|
+
]);
|
|
1377
|
+
const table = renderTable({ head: ["ID", "Command", "When", "Status"], colWidths: [12, 12, 20, 12] }, rows);
|
|
1378
|
+
info(table);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
// ---------------------------------------------------------------------------
|
|
1382
|
+
// v0.2.3: Export path — export session transcript to markdown
|
|
1383
|
+
// ---------------------------------------------------------------------------
|
|
1384
|
+
if (options.export && options.session) {
|
|
1385
|
+
// Validate export path is within project root to prevent path traversal
|
|
1386
|
+
try {
|
|
1387
|
+
const { validatePath } = await import('./safety.js');
|
|
1388
|
+
validatePath(path.resolve(options.export), projectRoot);
|
|
1389
|
+
}
|
|
1390
|
+
catch {
|
|
1391
|
+
uiError(`Export path must be within the project root: ${options.export}`);
|
|
1392
|
+
process.exit(2);
|
|
1393
|
+
}
|
|
1394
|
+
exportSessionMarkdown(db, options.session, options.export);
|
|
1395
|
+
info(`Session exported to ${path.resolve(options.export)}`);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
// ---------------------------------------------------------------------------
|
|
1399
|
+
// Case 1: --session flag — show full session log
|
|
1234
1400
|
// ---------------------------------------------------------------------------
|
|
1235
1401
|
if (options.session) {
|
|
1236
1402
|
const session = getSession(db, options.session);
|
|
@@ -1288,7 +1454,7 @@ export async function runHistory(options) {
|
|
|
1288
1454
|
if (toolCount > 0) {
|
|
1289
1455
|
info(bold(`Tool Executions (${toolCount}):`));
|
|
1290
1456
|
if (options.tree) {
|
|
1291
|
-
// Render as tree
|
|
1457
|
+
// Render as tree
|
|
1292
1458
|
renderToolTree(db, options.session);
|
|
1293
1459
|
}
|
|
1294
1460
|
else {
|
|
@@ -1309,30 +1475,55 @@ export async function runHistory(options) {
|
|
|
1309
1475
|
return;
|
|
1310
1476
|
}
|
|
1311
1477
|
// ---------------------------------------------------------------------------
|
|
1312
|
-
// Case 2: No --session flag — list recent sessions
|
|
1478
|
+
// Case 2: No --session flag — list recent sessions
|
|
1313
1479
|
// ---------------------------------------------------------------------------
|
|
1314
1480
|
const PAGE_SIZE = 20;
|
|
1315
1481
|
const limit = options.limit ?? PAGE_SIZE;
|
|
1316
|
-
// Fetch sessions with pagination support
|
|
1482
|
+
// Fetch sessions with pagination support
|
|
1317
1483
|
const sessions = listSessions(db, { limit });
|
|
1318
1484
|
if (sessions.length === 0) {
|
|
1319
|
-
|
|
1485
|
+
if (!isStructuredFormat) {
|
|
1486
|
+
info("No sessions found. Run a command to create your first session.");
|
|
1487
|
+
}
|
|
1488
|
+
if (isStructuredFormat) {
|
|
1489
|
+
process.stdout.write(formatOutput({ version: '1', sessions: [], total: 0 }, options.format, HistoryOutputSchema));
|
|
1490
|
+
}
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
// v0.2.3: structured output
|
|
1494
|
+
if (isStructuredFormat) {
|
|
1495
|
+
const outputSessions = sessions.map((s) => ({
|
|
1496
|
+
id: s.id,
|
|
1497
|
+
command: s.command,
|
|
1498
|
+
status: s.status,
|
|
1499
|
+
createdAt: s.createdAt,
|
|
1500
|
+
completedAt: s.completedAt ?? null,
|
|
1501
|
+
}));
|
|
1502
|
+
process.stdout.write(formatOutput({ version: '1', sessions: outputSessions, total: sessions.length }, options.format, HistoryOutputSchema));
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
// v0.2.3: plain format — tab-separated, no cli-table3 borders
|
|
1506
|
+
if (isPlainFormat) {
|
|
1507
|
+
process.stdout.write('ID\tCommand\tWhen\tStatus\n');
|
|
1508
|
+
for (const s of sessions) {
|
|
1509
|
+
process.stdout.write(`${s.id.slice(0, 8)}\t${s.command}\t${s.createdAt}\t${s.status}\n`);
|
|
1510
|
+
}
|
|
1320
1511
|
return;
|
|
1321
1512
|
}
|
|
1322
|
-
// Build table rows
|
|
1513
|
+
// Build table rows
|
|
1323
1514
|
const rows = sessions.map((s) => [
|
|
1324
1515
|
dim(s.id.slice(0, 8)), // abbreviated ID
|
|
1325
1516
|
cyan(s.command),
|
|
1326
|
-
formatTimestamp(s.createdAt), // human-readable timestamp
|
|
1517
|
+
formatTimestamp(s.createdAt), // human-readable timestamp
|
|
1327
1518
|
colorizeStatus(s.status),
|
|
1328
1519
|
]);
|
|
1329
|
-
// Render table with cli-table3
|
|
1520
|
+
// Render table with cli-table3
|
|
1330
1521
|
const table = renderTable({
|
|
1331
1522
|
head: ["ID", "Command", "When", "Status"],
|
|
1332
1523
|
colWidths: [12, 12, 20, 12],
|
|
1333
1524
|
}, rows);
|
|
1334
1525
|
info(table);
|
|
1335
|
-
// Show pagination hint if there may be more results
|
|
1526
|
+
// Show pagination hint if there may be more results
|
|
1336
1527
|
if (sessions.length === limit && !options.limit) {
|
|
1337
1528
|
info(dim(`\nShowing ${limit} most recent sessions. Use --limit <n> to see more.`));
|
|
1338
1529
|
}
|
|
@@ -1423,7 +1614,6 @@ function parseConfigValue(value) {
|
|
|
1423
1614
|
}
|
|
1424
1615
|
/**
|
|
1425
1616
|
* Display the effective configuration with precedence sources.
|
|
1426
|
-
* Requirements: 15.2
|
|
1427
1617
|
*/
|
|
1428
1618
|
function displayEffectiveConfig(projectRoot, config) {
|
|
1429
1619
|
const userConfigPath = path.join(os.homedir(), ".aria", "config.toml");
|
|
@@ -1464,13 +1654,12 @@ function displayEffectiveConfig(projectRoot, config) {
|
|
|
1464
1654
|
* Execute the config command.
|
|
1465
1655
|
*
|
|
1466
1656
|
* Subcommands:
|
|
1467
|
-
* - (none): Display effective configuration with precedence sources
|
|
1468
|
-
* - get <key>: Display value for specified key
|
|
1469
|
-
* - set <key> <value>: Write key-value to ~/.aria/config.toml
|
|
1470
|
-
* - path: Display configuration file resolution paths
|
|
1471
|
-
* - init: Create ./.aria.toml with default values
|
|
1657
|
+
* - (none): Display effective configuration with precedence sources
|
|
1658
|
+
* - get <key>: Display value for specified key
|
|
1659
|
+
* - set <key> <value>: Write key-value to ~/.aria/config.toml
|
|
1660
|
+
* - path: Display configuration file resolution paths
|
|
1661
|
+
* - init: Create ./.aria.toml with default values
|
|
1472
1662
|
*
|
|
1473
|
-
* Requirements: 15.1–15.10
|
|
1474
1663
|
*/
|
|
1475
1664
|
export async function runConfig(options) {
|
|
1476
1665
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
@@ -1480,14 +1669,14 @@ export async function runConfig(options) {
|
|
|
1480
1669
|
const userConfigPath = path.join(os.homedir(), ".aria", "config.toml");
|
|
1481
1670
|
const projectConfigPath = path.join(projectRoot, ".aria.toml");
|
|
1482
1671
|
// ---------------------------------------------------------------------------
|
|
1483
|
-
// No subcommand: display effective configuration
|
|
1672
|
+
// No subcommand: display effective configuration
|
|
1484
1673
|
// ---------------------------------------------------------------------------
|
|
1485
1674
|
if (!options.subcommand) {
|
|
1486
1675
|
displayEffectiveConfig(projectRoot, config);
|
|
1487
1676
|
return;
|
|
1488
1677
|
}
|
|
1489
1678
|
// ---------------------------------------------------------------------------
|
|
1490
|
-
// config path: display config file resolution paths
|
|
1679
|
+
// config path: display config file resolution paths
|
|
1491
1680
|
// ---------------------------------------------------------------------------
|
|
1492
1681
|
if (options.subcommand === "path") {
|
|
1493
1682
|
info(bold("Configuration file paths:"));
|
|
@@ -1500,7 +1689,7 @@ export async function runConfig(options) {
|
|
|
1500
1689
|
return;
|
|
1501
1690
|
}
|
|
1502
1691
|
// ---------------------------------------------------------------------------
|
|
1503
|
-
// config get <key>: display value for key
|
|
1692
|
+
// config get <key>: display value for key
|
|
1504
1693
|
// ---------------------------------------------------------------------------
|
|
1505
1694
|
if (options.subcommand === "get") {
|
|
1506
1695
|
const key = options.key;
|
|
@@ -1513,14 +1702,14 @@ export async function runConfig(options) {
|
|
|
1513
1702
|
return;
|
|
1514
1703
|
}
|
|
1515
1704
|
// ---------------------------------------------------------------------------
|
|
1516
|
-
// config set <key> <value>: write to user config
|
|
1705
|
+
// config set <key> <value>: write to user config
|
|
1517
1706
|
// ---------------------------------------------------------------------------
|
|
1518
1707
|
if (options.subcommand === "set") {
|
|
1519
1708
|
const key = options.key;
|
|
1520
1709
|
const rawValue = options.value;
|
|
1521
1710
|
// Parse the value to the appropriate type
|
|
1522
1711
|
const parsedValue = parseConfigValue(rawValue);
|
|
1523
|
-
// Validate by applying to current config and re-validating
|
|
1712
|
+
// Validate by applying to current config and re-validating
|
|
1524
1713
|
const currentMerged = loadConfig(projectRoot);
|
|
1525
1714
|
const updatedMerged = setNestedValue(currentMerged, key, parsedValue);
|
|
1526
1715
|
let validatedConfig;
|
|
@@ -1536,16 +1725,16 @@ export async function runConfig(options) {
|
|
|
1536
1725
|
? readFileSync(userConfigPath, "utf-8")
|
|
1537
1726
|
: "";
|
|
1538
1727
|
const newContent = serializeConfigToToml(validatedConfig);
|
|
1539
|
-
// Preview the diff
|
|
1728
|
+
// Preview the diff
|
|
1540
1729
|
const diffOutput = generateAndRenderDiff(userConfigPath, oldContent, newContent);
|
|
1541
1730
|
info(bold("Preview:"));
|
|
1542
1731
|
info(diffOutput);
|
|
1543
|
-
// If --dry-run, exit without writing
|
|
1732
|
+
// If --dry-run, exit without writing
|
|
1544
1733
|
if (options.dryRun) {
|
|
1545
1734
|
info(yellow("Dry-run mode — no changes written."));
|
|
1546
1735
|
return;
|
|
1547
1736
|
}
|
|
1548
|
-
// If not --yes, prompt for confirmation
|
|
1737
|
+
// If not --yes, prompt for confirmation
|
|
1549
1738
|
if (!options.yes) {
|
|
1550
1739
|
let confirmed;
|
|
1551
1740
|
try {
|
|
@@ -1563,7 +1752,7 @@ export async function runConfig(options) {
|
|
|
1563
1752
|
process.exit(130);
|
|
1564
1753
|
}
|
|
1565
1754
|
}
|
|
1566
|
-
// Write to ~/.aria/config.toml
|
|
1755
|
+
// Write to ~/.aria/config.toml
|
|
1567
1756
|
const ariaDir = path.join(os.homedir(), ".aria");
|
|
1568
1757
|
if (!existsSync(ariaDir)) {
|
|
1569
1758
|
mkdirSync(ariaDir, { recursive: true });
|
|
@@ -1573,7 +1762,7 @@ export async function runConfig(options) {
|
|
|
1573
1762
|
return;
|
|
1574
1763
|
}
|
|
1575
1764
|
// ---------------------------------------------------------------------------
|
|
1576
|
-
// config init: create ./.aria.toml with defaults
|
|
1765
|
+
// config init: create ./.aria.toml with defaults
|
|
1577
1766
|
// ---------------------------------------------------------------------------
|
|
1578
1767
|
if (options.subcommand === "init") {
|
|
1579
1768
|
// Generate default config content
|
|
@@ -1586,12 +1775,12 @@ export async function runConfig(options) {
|
|
|
1586
1775
|
const diffOutput = generateAndRenderDiff(projectConfigPath, oldContent, defaultContent);
|
|
1587
1776
|
info(bold("Preview (.aria.toml):"));
|
|
1588
1777
|
info(diffOutput);
|
|
1589
|
-
// If --dry-run, exit without writing
|
|
1778
|
+
// If --dry-run, exit without writing
|
|
1590
1779
|
if (options.dryRun) {
|
|
1591
1780
|
info(yellow("Dry-run mode — no file created."));
|
|
1592
1781
|
return;
|
|
1593
1782
|
}
|
|
1594
|
-
// If not --yes, prompt for confirmation
|
|
1783
|
+
// If not --yes, prompt for confirmation
|
|
1595
1784
|
if (!options.yes) {
|
|
1596
1785
|
let confirmed;
|
|
1597
1786
|
try {
|
|
@@ -1609,7 +1798,7 @@ export async function runConfig(options) {
|
|
|
1609
1798
|
process.exit(130);
|
|
1610
1799
|
}
|
|
1611
1800
|
}
|
|
1612
|
-
// Write ./.aria.toml
|
|
1801
|
+
// Write ./.aria.toml
|
|
1613
1802
|
writeFileSync(projectConfigPath, defaultContent, "utf-8");
|
|
1614
1803
|
info(green(`✓ Created ${projectConfigPath}`));
|
|
1615
1804
|
return;
|
|
@@ -1621,7 +1810,6 @@ export async function runConfig(options) {
|
|
|
1621
1810
|
* Runs a series of environment diagnostic checks and reports results.
|
|
1622
1811
|
* Exits with code 1 if any critical check fails.
|
|
1623
1812
|
*
|
|
1624
|
-
* Requirements: 16.1–16.13
|
|
1625
1813
|
*/
|
|
1626
1814
|
export async function runDoctor(options = {}) {
|
|
1627
1815
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
@@ -1636,7 +1824,7 @@ export async function runDoctor(options = {}) {
|
|
|
1636
1824
|
}
|
|
1637
1825
|
const checks = [];
|
|
1638
1826
|
// -------------------------------------------------------------------------
|
|
1639
|
-
// 1. Node.js version >= 20
|
|
1827
|
+
// 1. Node.js version >= 20
|
|
1640
1828
|
// -------------------------------------------------------------------------
|
|
1641
1829
|
{
|
|
1642
1830
|
const nodeVersion = process.version; // e.g. "v20.11.0"
|
|
@@ -1653,7 +1841,7 @@ export async function runDoctor(options = {}) {
|
|
|
1653
1841
|
}
|
|
1654
1842
|
}
|
|
1655
1843
|
// -------------------------------------------------------------------------
|
|
1656
|
-
// 2. git availability
|
|
1844
|
+
// 2. git availability — WARN only
|
|
1657
1845
|
// -------------------------------------------------------------------------
|
|
1658
1846
|
{
|
|
1659
1847
|
try {
|
|
@@ -1665,7 +1853,7 @@ export async function runDoctor(options = {}) {
|
|
|
1665
1853
|
}
|
|
1666
1854
|
}
|
|
1667
1855
|
// -------------------------------------------------------------------------
|
|
1668
|
-
// 3. ripgrep (rg) availability
|
|
1856
|
+
// 3. ripgrep (rg) availability — WARN only
|
|
1669
1857
|
// -------------------------------------------------------------------------
|
|
1670
1858
|
{
|
|
1671
1859
|
try {
|
|
@@ -1677,7 +1865,7 @@ export async function runDoctor(options = {}) {
|
|
|
1677
1865
|
}
|
|
1678
1866
|
}
|
|
1679
1867
|
// -------------------------------------------------------------------------
|
|
1680
|
-
// 4. Config file syntax and schema validation
|
|
1868
|
+
// 4. Config file syntax and schema validation — CRITICAL
|
|
1681
1869
|
// -------------------------------------------------------------------------
|
|
1682
1870
|
{
|
|
1683
1871
|
const userConfigPath = path.join(os.homedir(), ".aria", "config.toml");
|
|
@@ -1704,7 +1892,7 @@ export async function runDoctor(options = {}) {
|
|
|
1704
1892
|
}
|
|
1705
1893
|
}
|
|
1706
1894
|
// -------------------------------------------------------------------------
|
|
1707
|
-
// 5. History DB accessibility and schema version
|
|
1895
|
+
// 5. History DB accessibility and schema version — CRITICAL
|
|
1708
1896
|
// -------------------------------------------------------------------------
|
|
1709
1897
|
{
|
|
1710
1898
|
try {
|
|
@@ -1722,7 +1910,7 @@ export async function runDoctor(options = {}) {
|
|
|
1722
1910
|
}
|
|
1723
1911
|
}
|
|
1724
1912
|
// -------------------------------------------------------------------------
|
|
1725
|
-
// 6. Provider readiness — API key presence
|
|
1913
|
+
// 6. Provider readiness — API key presence — CRITICAL for default
|
|
1726
1914
|
// v0.2.2: report all configured providers, fail only if default key is missing
|
|
1727
1915
|
// -------------------------------------------------------------------------
|
|
1728
1916
|
{
|
|
@@ -1759,7 +1947,7 @@ export async function runDoctor(options = {}) {
|
|
|
1759
1947
|
}
|
|
1760
1948
|
}
|
|
1761
1949
|
// -------------------------------------------------------------------------
|
|
1762
|
-
// 7. Project type detection
|
|
1950
|
+
// 7. Project type detection
|
|
1763
1951
|
// -------------------------------------------------------------------------
|
|
1764
1952
|
{
|
|
1765
1953
|
try {
|
|
@@ -1778,7 +1966,7 @@ export async function runDoctor(options = {}) {
|
|
|
1778
1966
|
}
|
|
1779
1967
|
}
|
|
1780
1968
|
// -------------------------------------------------------------------------
|
|
1781
|
-
// 8. Prisma schema existence and model count
|
|
1969
|
+
// 8. Prisma schema existence and model count
|
|
1782
1970
|
// -------------------------------------------------------------------------
|
|
1783
1971
|
{
|
|
1784
1972
|
try {
|
|
@@ -1818,7 +2006,7 @@ export async function runDoctor(options = {}) {
|
|
|
1818
2006
|
}
|
|
1819
2007
|
}
|
|
1820
2008
|
// -------------------------------------------------------------------------
|
|
1821
|
-
// 9. Ollama reachability if Ollama provider selected
|
|
2009
|
+
// 9. Ollama reachability if Ollama provider selected — WARN
|
|
1822
2010
|
// -------------------------------------------------------------------------
|
|
1823
2011
|
{
|
|
1824
2012
|
const provider = config?.provider.default ?? "anthropic";
|
|
@@ -1841,10 +2029,22 @@ export async function runDoctor(options = {}) {
|
|
|
1841
2029
|
// -------------------------------------------------------------------------
|
|
1842
2030
|
const criticalNames = new Set(["nodejs", "config", "history_db", "provider"]);
|
|
1843
2031
|
const hasCriticalFailure = checks.some((c) => c.status === "fail" && criticalNames.has(c.name));
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2032
|
+
const allPassed = !checks.some((c) => c.status === "fail");
|
|
2033
|
+
if (options.format === 'json' || options.format === 'ndjson') {
|
|
2034
|
+
// v0.2.3: structured output via formatOutput
|
|
2035
|
+
const doctorChecks = checks.map((c) => ({
|
|
2036
|
+
name: c.name,
|
|
2037
|
+
passed: c.status !== 'fail',
|
|
2038
|
+
message: c.message,
|
|
2039
|
+
}));
|
|
2040
|
+
process.stdout.write(formatOutput({ version: '1', checks: doctorChecks, allPassed }, options.format, DoctorOutputSchema));
|
|
2041
|
+
}
|
|
2042
|
+
else if (options.format === 'plain') {
|
|
2043
|
+
// Plain text — no ANSI codes, tab-separated
|
|
2044
|
+
for (const check of checks) {
|
|
2045
|
+
const icon = check.status === 'pass' ? 'OK' : check.status === 'warn' ? 'WARN' : 'FAIL';
|
|
2046
|
+
process.stdout.write(`${icon}\t${check.name}\t${check.message}\n`);
|
|
2047
|
+
}
|
|
1848
2048
|
}
|
|
1849
2049
|
else {
|
|
1850
2050
|
// Text output (Req 16.11)
|
|
@@ -1870,7 +2070,7 @@ export async function runDoctor(options = {}) {
|
|
|
1870
2070
|
info(green("All critical checks passed."));
|
|
1871
2071
|
}
|
|
1872
2072
|
}
|
|
1873
|
-
// Exit with code 1 if any critical check fails
|
|
2073
|
+
// Exit with code 1 if any critical check fails
|
|
1874
2074
|
if (hasCriticalFailure) {
|
|
1875
2075
|
process.exit(1);
|
|
1876
2076
|
}
|