sidecar-cli 0.1.2 → 0.1.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +446 -56
- package/dist/lib/paths.js +3 -0
- package/dist/lib/ui.js +4 -1
- package/dist/prompts/prompt-compiler.js +88 -0
- package/dist/prompts/prompt-service.js +35 -0
- package/dist/runners/claude-runner.js +38 -0
- package/dist/runners/codex-runner.js +38 -0
- package/dist/runners/config.js +39 -0
- package/dist/runners/factory.js +10 -0
- package/dist/runners/runner-adapter.js +1 -0
- package/dist/runs/run-record.js +97 -0
- package/dist/runs/run-repository.js +99 -0
- package/dist/runs/run-service.js +27 -0
- package/dist/services/capabilities-service.js +124 -9
- package/dist/services/run-orchestrator-service.js +59 -0
- package/dist/services/run-review-service.js +76 -0
- package/dist/services/task-orchestration-service.js +94 -0
- package/dist/tasks/task-packet.js +132 -0
- package/dist/tasks/task-repository.js +78 -0
- package/dist/tasks/task-service.js +79 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import readline from 'node:readline/promises';
|
|
4
5
|
import { Command } from 'commander';
|
|
5
6
|
import Database from 'better-sqlite3';
|
|
6
7
|
import { z } from 'zod';
|
|
@@ -19,15 +20,24 @@ import { buildContext } from './services/context-service.js';
|
|
|
19
20
|
import { getCapabilitiesManifest } from './services/capabilities-service.js';
|
|
20
21
|
import { addArtifact, listArtifacts } from './services/artifact-service.js';
|
|
21
22
|
import { addDecision, addNote, addWorklog, getActiveSessionId, listRecentEvents } from './services/event-service.js';
|
|
22
|
-
import { addTask, listTasks, markTaskDone } from './services/task-service.js';
|
|
23
23
|
import { currentSession, endSession, startSession, verifySessionHygiene } from './services/session-service.js';
|
|
24
24
|
import { eventIngestSchema, ingestEvent } from './services/event-ingest-service.js';
|
|
25
25
|
import { buildExportJson, buildExportJsonlEvents, writeOutputFile } from './services/export-service.js';
|
|
26
|
+
import { createTaskPacketRecord, getTaskPacket, listTaskPackets } from './tasks/task-service.js';
|
|
27
|
+
import { taskPacketPrioritySchema, taskPacketStatusSchema, taskPacketTypeSchema } from './tasks/task-packet.js';
|
|
28
|
+
import { getRunRecord, listRunRecords, listRunRecordsForTask } from './runs/run-service.js';
|
|
29
|
+
import { runStatusSchema, runnerTypeSchema } from './runs/run-record.js';
|
|
30
|
+
import { compileTaskPrompt } from './prompts/prompt-service.js';
|
|
31
|
+
import { runTaskExecution } from './services/run-orchestrator-service.js';
|
|
32
|
+
import { loadRunnerPreferences } from './runners/config.js';
|
|
33
|
+
import { assignTask, queueReadyTasks } from './services/task-orchestration-service.js';
|
|
34
|
+
import { buildReviewSummary, createFollowupTaskFromRun, reviewRun } from './services/run-review-service.js';
|
|
26
35
|
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
27
36
|
const actorSchema = z.enum(['human', 'agent']);
|
|
28
|
-
const taskPrioritySchema = z.enum(['low', 'medium', 'high']);
|
|
29
37
|
const artifactKindSchema = z.enum(['file', 'doc', 'screenshot', 'other']);
|
|
30
|
-
const
|
|
38
|
+
const taskListStatusSchema = z.enum(['draft', 'ready', 'queued', 'running', 'review', 'blocked', 'done', 'all']);
|
|
39
|
+
const runListStatusSchema = runStatusSchema.or(z.literal('all'));
|
|
40
|
+
const agentRoleSchema = z.enum(['planner', 'builder-ui', 'builder-app', 'reviewer', 'tester']);
|
|
31
41
|
const exportFormatSchema = z.enum(['json', 'jsonl']);
|
|
32
42
|
const NOT_INITIALIZED_MSG = 'Sidecar is not initialized in this directory or any parent directory';
|
|
33
43
|
function fail(message) {
|
|
@@ -185,6 +195,21 @@ function resolveProjectRoot(projectPath) {
|
|
|
185
195
|
}
|
|
186
196
|
return root;
|
|
187
197
|
}
|
|
198
|
+
function parseCsvOption(input) {
|
|
199
|
+
if (!input)
|
|
200
|
+
return [];
|
|
201
|
+
return input
|
|
202
|
+
.split(',')
|
|
203
|
+
.map((item) => item.trim())
|
|
204
|
+
.filter(Boolean);
|
|
205
|
+
}
|
|
206
|
+
async function askWithDefault(rl, question, fallback) {
|
|
207
|
+
const suffix = fallback ? ` [${fallback}]` : '';
|
|
208
|
+
const answer = (await rl.question(`${question}${suffix}: `)).trim();
|
|
209
|
+
if (answer.length > 0)
|
|
210
|
+
return answer;
|
|
211
|
+
return fallback ?? '';
|
|
212
|
+
}
|
|
188
213
|
const program = new Command();
|
|
189
214
|
program.name('sidecar').description('Local-first project memory and recording CLI').version(pkg.version);
|
|
190
215
|
program.option('--no-banner', 'Disable Sidecar banner output');
|
|
@@ -275,6 +300,9 @@ program
|
|
|
275
300
|
fail('Sidecar is already initialized in this project. Re-run with --force to recreate .sidecar files.');
|
|
276
301
|
}
|
|
277
302
|
const files = [
|
|
303
|
+
sidecar.tasksPath,
|
|
304
|
+
sidecar.runsPath,
|
|
305
|
+
sidecar.promptsPath,
|
|
278
306
|
sidecar.dbPath,
|
|
279
307
|
sidecar.configPath,
|
|
280
308
|
sidecar.preferencesPath,
|
|
@@ -297,9 +325,12 @@ program
|
|
|
297
325
|
sidecar.preferencesPath,
|
|
298
326
|
sidecar.agentsPath,
|
|
299
327
|
sidecar.summaryPath,
|
|
328
|
+
sidecar.tasksPath,
|
|
329
|
+
sidecar.runsPath,
|
|
330
|
+
sidecar.promptsPath,
|
|
300
331
|
]) {
|
|
301
332
|
if (fs.existsSync(file))
|
|
302
|
-
fs.rmSync(file);
|
|
333
|
+
fs.rmSync(file, { recursive: true, force: true });
|
|
303
334
|
}
|
|
304
335
|
}
|
|
305
336
|
const db = new Database(sidecar.dbPath);
|
|
@@ -315,9 +346,17 @@ program
|
|
|
315
346
|
settings: {},
|
|
316
347
|
};
|
|
317
348
|
fs.writeFileSync(sidecar.configPath, stringifyJson(config));
|
|
349
|
+
fs.mkdirSync(sidecar.tasksPath, { recursive: true });
|
|
350
|
+
fs.mkdirSync(sidecar.runsPath, { recursive: true });
|
|
351
|
+
fs.mkdirSync(sidecar.promptsPath, { recursive: true });
|
|
318
352
|
fs.writeFileSync(sidecar.preferencesPath, stringifyJson({
|
|
319
353
|
summary: { format: 'markdown', recentLimit: 8 },
|
|
320
354
|
output: { humanTime: true },
|
|
355
|
+
runner: {
|
|
356
|
+
defaultRunner: 'codex',
|
|
357
|
+
preferredRunners: ['codex', 'claude'],
|
|
358
|
+
defaultAgentRole: 'builder-app',
|
|
359
|
+
},
|
|
321
360
|
}));
|
|
322
361
|
fs.writeFileSync(sidecar.agentsPath, renderAgentsMarkdown(projectName));
|
|
323
362
|
if (shouldWriteRootAgents) {
|
|
@@ -705,46 +744,98 @@ worklog
|
|
|
705
744
|
});
|
|
706
745
|
const task = program.command('task').description('Task commands');
|
|
707
746
|
task
|
|
708
|
-
.command('
|
|
709
|
-
.description('Create
|
|
710
|
-
.option('--
|
|
747
|
+
.command('create')
|
|
748
|
+
.description('Create a structured task packet')
|
|
749
|
+
.option('--title <title>', 'Task title')
|
|
750
|
+
.option('--type <type>', 'feature|bug|chore|research', 'chore')
|
|
751
|
+
.option('--status <status>', 'draft|ready|queued|running|review|blocked|done', 'draft')
|
|
711
752
|
.option('--priority <priority>', 'low|medium|high', 'medium')
|
|
712
|
-
.option('--
|
|
753
|
+
.option('--summary <summary>', 'Task summary')
|
|
754
|
+
.option('--goal <goal>', 'Task goal')
|
|
755
|
+
.option('--dependencies <task-ids>', 'Comma-separated dependency task IDs')
|
|
756
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
757
|
+
.option('--target-areas <areas>', 'Comma-separated target areas')
|
|
758
|
+
.option('--scope-in <items>', 'Comma-separated in-scope items')
|
|
759
|
+
.option('--scope-out <items>', 'Comma-separated out-of-scope items')
|
|
760
|
+
.option('--related-decisions <items>', 'Comma-separated related decision IDs/titles')
|
|
761
|
+
.option('--related-notes <items>', 'Comma-separated related notes')
|
|
762
|
+
.option('--files-read <paths>', 'Comma-separated files to read')
|
|
763
|
+
.option('--files-avoid <paths>', 'Comma-separated files to avoid')
|
|
764
|
+
.option('--constraint-tech <items>', 'Comma-separated technical constraints')
|
|
765
|
+
.option('--constraint-design <items>', 'Comma-separated design constraints')
|
|
766
|
+
.option('--validate-cmds <commands>', 'Comma-separated validation commands')
|
|
767
|
+
.option('--dod <items>', 'Comma-separated definition-of-done checks')
|
|
768
|
+
.option('--branch <name>', 'Branch name')
|
|
769
|
+
.option('--worktree <path>', 'Worktree path')
|
|
713
770
|
.option('--json', 'Print machine-readable JSON output')
|
|
714
|
-
.addHelpText('after', '\nExamples:\n $ sidecar task
|
|
715
|
-
.action((
|
|
716
|
-
const command = 'task
|
|
771
|
+
.addHelpText('after', '\nExamples:\n $ sidecar task create\n $ sidecar task create --title "Add import support" --summary "Support JSON import" --goal "Enable scripted import flow" --priority high\n $ sidecar task create --title "Refactor parser" --files-read src/parser.ts,src/types.ts --dod "Tests pass,Docs updated"')
|
|
772
|
+
.action(async (opts) => {
|
|
773
|
+
const command = 'task create';
|
|
717
774
|
try {
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
775
|
+
const rootPath = resolveProjectRoot();
|
|
776
|
+
let title = opts.title?.trim() ?? '';
|
|
777
|
+
let summary = opts.summary?.trim() ?? '';
|
|
778
|
+
let goal = opts.goal?.trim() ?? '';
|
|
779
|
+
if (!title || !summary || !goal) {
|
|
780
|
+
if (!process.stdin.isTTY) {
|
|
781
|
+
fail('Missing required fields. Provide --title, --summary, and --goal when not running interactively.');
|
|
782
|
+
}
|
|
783
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
784
|
+
try {
|
|
785
|
+
title = title || (await askWithDefault(rl, 'Title'));
|
|
786
|
+
summary = summary || (await askWithDefault(rl, 'Summary', title));
|
|
787
|
+
goal = goal || (await askWithDefault(rl, 'Goal', `Complete: ${title}`));
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
rl.close();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const type = taskPacketTypeSchema.parse(opts.type);
|
|
794
|
+
const status = taskPacketStatusSchema.parse(opts.status);
|
|
795
|
+
const priority = taskPacketPrioritySchema.parse(opts.priority);
|
|
796
|
+
const created = createTaskPacketRecord(rootPath, {
|
|
797
|
+
title,
|
|
798
|
+
summary,
|
|
799
|
+
goal,
|
|
800
|
+
type,
|
|
801
|
+
status,
|
|
802
|
+
priority,
|
|
803
|
+
scope_in_scope: parseCsvOption(opts.scopeIn),
|
|
804
|
+
scope_out_of_scope: parseCsvOption(opts.scopeOut),
|
|
805
|
+
related_decisions: parseCsvOption(opts.relatedDecisions),
|
|
806
|
+
related_notes: parseCsvOption(opts.relatedNotes),
|
|
807
|
+
files_to_read: parseCsvOption(opts.filesRead),
|
|
808
|
+
files_to_avoid: parseCsvOption(opts.filesAvoid),
|
|
809
|
+
technical_constraints: parseCsvOption(opts.constraintTech),
|
|
810
|
+
design_constraints: parseCsvOption(opts.constraintDesign),
|
|
811
|
+
validation_commands: parseCsvOption(opts.validateCmds),
|
|
812
|
+
dependencies: parseCsvOption(opts.dependencies).map((v) => v.toUpperCase()),
|
|
813
|
+
tags: parseCsvOption(opts.tags),
|
|
814
|
+
target_areas: parseCsvOption(opts.targetAreas),
|
|
815
|
+
definition_of_done: parseCsvOption(opts.dod),
|
|
816
|
+
branch: opts.branch?.trim(),
|
|
817
|
+
worktree: opts.worktree?.trim(),
|
|
818
|
+
});
|
|
819
|
+
respondSuccess(command, Boolean(opts.json), { task: created.task, path: created.path }, [`Created task ${created.task.task_id}.`, `Path: ${created.path}`]);
|
|
724
820
|
}
|
|
725
821
|
catch (err) {
|
|
726
822
|
handleCommandError(command, Boolean(opts.json), err);
|
|
727
823
|
}
|
|
728
824
|
});
|
|
729
825
|
task
|
|
730
|
-
.command('
|
|
731
|
-
.description('
|
|
732
|
-
.option('--by <actor>', 'human|agent', 'human')
|
|
826
|
+
.command('show <task-id>')
|
|
827
|
+
.description('Show a task packet by id')
|
|
733
828
|
.option('--json', 'Print machine-readable JSON output')
|
|
734
|
-
.addHelpText('after', '\nExamples:\n $ sidecar task
|
|
829
|
+
.addHelpText('after', '\nExamples:\n $ sidecar task show T-001\n $ sidecar task show T-001 --json')
|
|
735
830
|
.action((taskIdText, opts) => {
|
|
736
|
-
const command = 'task
|
|
831
|
+
const command = 'task show';
|
|
737
832
|
try {
|
|
738
|
-
const
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
db.close();
|
|
745
|
-
if (!result.ok)
|
|
746
|
-
fail(result.reason);
|
|
747
|
-
respondSuccess(command, Boolean(opts.json), { task: { id: taskId, status: 'done' }, event: { id: result.eventId, type: 'task_completed' } }, [`Completed task #${taskId}.`]);
|
|
833
|
+
const task = getTaskPacket(resolveProjectRoot(), taskIdText.trim().toUpperCase());
|
|
834
|
+
if (opts.json) {
|
|
835
|
+
respondSuccess(command, true, { task }, []);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
console.log(stringifyJson(task));
|
|
748
839
|
}
|
|
749
840
|
catch (err) {
|
|
750
841
|
handleCommandError(command, Boolean(opts.json), err);
|
|
@@ -752,40 +843,327 @@ task
|
|
|
752
843
|
});
|
|
753
844
|
task
|
|
754
845
|
.command('list')
|
|
755
|
-
.description('List
|
|
756
|
-
.option('--status <status>', '
|
|
757
|
-
.option('--format <format>', 'table|json', 'table')
|
|
846
|
+
.description('List task packets')
|
|
847
|
+
.option('--status <status>', 'draft|ready|queued|running|review|blocked|done|all', 'all')
|
|
758
848
|
.option('--json', 'Print machine-readable JSON output')
|
|
759
|
-
.addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status
|
|
849
|
+
.addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status open\n $ sidecar task list --json')
|
|
760
850
|
.action((opts) => {
|
|
761
851
|
const command = 'task list';
|
|
762
852
|
try {
|
|
763
|
-
const status =
|
|
764
|
-
const
|
|
765
|
-
const rows =
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (opts.json)
|
|
769
|
-
printJsonEnvelope(jsonSuccess(command, { status, tasks: rows }));
|
|
770
|
-
else
|
|
771
|
-
console.log(stringifyJson(rows));
|
|
853
|
+
const status = taskListStatusSchema.parse(opts.status);
|
|
854
|
+
const tasks = listTaskPackets(resolveProjectRoot());
|
|
855
|
+
const rows = status === 'all' ? tasks : tasks.filter((task) => task.status === status);
|
|
856
|
+
if (opts.json) {
|
|
857
|
+
respondSuccess(command, true, { status, tasks: rows }, []);
|
|
772
858
|
return;
|
|
773
859
|
}
|
|
774
|
-
|
|
775
|
-
if (taskRows.length === 0) {
|
|
860
|
+
if (rows.length === 0) {
|
|
776
861
|
console.log('No tasks found.');
|
|
777
862
|
return;
|
|
778
863
|
}
|
|
779
|
-
const idWidth = Math.max(
|
|
780
|
-
const statusWidth = Math.max(
|
|
781
|
-
const priorityWidth = Math.max(8, ...
|
|
782
|
-
console.log(`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'PRIORITY'.padEnd(priorityWidth)} TITLE`);
|
|
783
|
-
for (const row of
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
864
|
+
const idWidth = Math.max(6, ...rows.map((r) => r.task_id.length));
|
|
865
|
+
const statusWidth = Math.max(11, ...rows.map((r) => r.status.length));
|
|
866
|
+
const priorityWidth = Math.max(8, ...rows.map((r) => r.priority.length));
|
|
867
|
+
console.log(`${'TASK ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'PRIORITY'.padEnd(priorityWidth)} TITLE`);
|
|
868
|
+
for (const row of rows) {
|
|
869
|
+
console.log(`${row.task_id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.priority.padEnd(priorityWidth)} ${row.title}`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
task
|
|
877
|
+
.command('assign <task-id>')
|
|
878
|
+
.description('Auto-assign agent role and runner for a task')
|
|
879
|
+
.option('--agent-role <role>', 'planner|builder-ui|builder-app|reviewer|tester')
|
|
880
|
+
.option('--runner <runner>', 'codex|claude')
|
|
881
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
882
|
+
.addHelpText('after', '\nExamples:\n $ sidecar task assign T-001\n $ sidecar task assign T-001 --agent-role builder-ui --runner codex\n $ sidecar task assign T-001 --json')
|
|
883
|
+
.action((taskIdText, opts) => {
|
|
884
|
+
const command = 'task assign';
|
|
885
|
+
try {
|
|
886
|
+
const rootPath = resolveProjectRoot();
|
|
887
|
+
const result = assignTask(rootPath, taskIdText.trim().toUpperCase(), {
|
|
888
|
+
role: opts.agentRole ? agentRoleSchema.parse(opts.agentRole) : undefined,
|
|
889
|
+
runner: opts.runner ? runnerTypeSchema.parse(opts.runner) : undefined,
|
|
890
|
+
});
|
|
891
|
+
respondSuccess(command, Boolean(opts.json), result, [
|
|
892
|
+
`Assigned ${result.task_id}.`,
|
|
893
|
+
`Role: ${result.agent_role}`,
|
|
894
|
+
`Runner: ${result.runner}`,
|
|
895
|
+
`Reason: ${result.reason}`,
|
|
896
|
+
]);
|
|
897
|
+
}
|
|
898
|
+
catch (err) {
|
|
899
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
task
|
|
903
|
+
.command('create-followup <run-id>')
|
|
904
|
+
.description('Create a follow-up task packet from a run report')
|
|
905
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
906
|
+
.addHelpText('after', '\nExamples:\n $ sidecar task create-followup R-010\n $ sidecar task create-followup R-010 --json')
|
|
907
|
+
.action((runIdText, opts) => {
|
|
908
|
+
const command = 'task create-followup';
|
|
909
|
+
try {
|
|
910
|
+
const result = createFollowupTaskFromRun(resolveProjectRoot(), runIdText.trim().toUpperCase());
|
|
911
|
+
respondSuccess(command, Boolean(opts.json), result, [
|
|
912
|
+
`Created follow-up task ${result.task_id}.`,
|
|
913
|
+
`Source run: ${result.source_run_id}`,
|
|
914
|
+
`Title: ${result.title}`,
|
|
915
|
+
]);
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
const prompt = program.command('prompt').description('Prompt compilation commands');
|
|
922
|
+
prompt
|
|
923
|
+
.command('compile <task-id>')
|
|
924
|
+
.description('Compile a markdown execution brief from a task packet')
|
|
925
|
+
.requiredOption('--runner <runner>', 'codex|claude')
|
|
926
|
+
.requiredOption('--agent-role <role>', 'Agent role, for example builder')
|
|
927
|
+
.option('--preview', 'Print compiled prompt content after writing file')
|
|
928
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
929
|
+
.addHelpText('after', '\nExamples:\n $ sidecar prompt compile T-001 --runner codex --agent-role builder\n $ sidecar prompt compile T-001 --runner claude --agent-role builder --preview\n $ sidecar prompt compile T-001 --runner codex --agent-role reviewer --json')
|
|
930
|
+
.action((taskIdText, opts) => {
|
|
931
|
+
const command = 'prompt compile';
|
|
932
|
+
try {
|
|
933
|
+
const rootPath = resolveProjectRoot();
|
|
934
|
+
const taskId = taskIdText.trim().toUpperCase();
|
|
935
|
+
const runner = runnerTypeSchema.parse(opts.runner);
|
|
936
|
+
const agentRole = String(opts.agentRole ?? '').trim();
|
|
937
|
+
if (!agentRole)
|
|
938
|
+
fail('Agent role is required');
|
|
939
|
+
const compiled = compileTaskPrompt({
|
|
940
|
+
rootPath,
|
|
941
|
+
taskId,
|
|
942
|
+
runner,
|
|
943
|
+
agentRole,
|
|
944
|
+
});
|
|
945
|
+
respondSuccess(command, Boolean(opts.json), {
|
|
946
|
+
run_id: compiled.run_id,
|
|
947
|
+
task_id: compiled.task_id,
|
|
948
|
+
runner_type: compiled.runner_type,
|
|
949
|
+
agent_role: compiled.agent_role,
|
|
950
|
+
prompt_path: compiled.prompt_path,
|
|
951
|
+
preview: opts.preview ? compiled.prompt_markdown : null,
|
|
952
|
+
}, [
|
|
953
|
+
`Compiled prompt for ${compiled.task_id}.`,
|
|
954
|
+
`Run: ${compiled.run_id}`,
|
|
955
|
+
`Path: ${compiled.prompt_path}`,
|
|
956
|
+
...(opts.preview ? ['', compiled.prompt_markdown] : []),
|
|
957
|
+
]);
|
|
958
|
+
}
|
|
959
|
+
catch (err) {
|
|
960
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
program
|
|
964
|
+
.command('run-exec <task-id>')
|
|
965
|
+
.description('Internal command backing `sidecar run <task-id>`')
|
|
966
|
+
.option('--runner <runner>', 'codex|claude')
|
|
967
|
+
.option('--agent-role <role>', 'planner|builder-ui|builder-app|reviewer|tester')
|
|
968
|
+
.option('--dry-run', 'Prepare and compile only without executing external runner')
|
|
969
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
970
|
+
.action((taskIdText, opts) => {
|
|
971
|
+
const command = 'run';
|
|
972
|
+
try {
|
|
973
|
+
const rootPath = resolveProjectRoot();
|
|
974
|
+
const defaults = loadRunnerPreferences(rootPath);
|
|
975
|
+
const selectedRunner = opts.runner ? runnerTypeSchema.parse(opts.runner) : defaults.default_runner;
|
|
976
|
+
const selectedAgentRole = opts.agentRole
|
|
977
|
+
? agentRoleSchema.parse(opts.agentRole)
|
|
978
|
+
: defaults.default_agent_role;
|
|
979
|
+
const result = runTaskExecution({
|
|
980
|
+
rootPath,
|
|
981
|
+
taskId: String(taskIdText).trim().toUpperCase(),
|
|
982
|
+
runner: selectedRunner,
|
|
983
|
+
agentRole: selectedAgentRole,
|
|
984
|
+
dryRun: Boolean(opts.dryRun),
|
|
985
|
+
});
|
|
986
|
+
respondSuccess(command, Boolean(opts.json), result, [
|
|
987
|
+
`Prepared run ${result.run_id} for ${result.task_id}.`,
|
|
988
|
+
`Runner: ${result.runner_type} (${result.agent_role})`,
|
|
989
|
+
`Prompt: ${result.prompt_path}`,
|
|
990
|
+
`Command: ${result.shell_command}`,
|
|
991
|
+
`Status: ${result.status}`,
|
|
992
|
+
`Summary: ${result.summary}`,
|
|
993
|
+
]);
|
|
994
|
+
}
|
|
995
|
+
catch (err) {
|
|
996
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
const run = program
|
|
1000
|
+
.command('run')
|
|
1001
|
+
.description('Run task execution or inspect run records')
|
|
1002
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run T-001 --dry-run\n $ sidecar run T-001 --runner claude --agent-role reviewer\n $ sidecar run queue\n $ sidecar run start-ready --dry-run\n $ sidecar run list --task T-001\n $ sidecar run show R-001');
|
|
1003
|
+
run
|
|
1004
|
+
.command('queue')
|
|
1005
|
+
.description('Queue all ready tasks with satisfied dependencies')
|
|
1006
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1007
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run queue\n $ sidecar run queue --json')
|
|
1008
|
+
.action((opts) => {
|
|
1009
|
+
const command = 'run queue';
|
|
1010
|
+
try {
|
|
1011
|
+
const rootPath = resolveProjectRoot();
|
|
1012
|
+
const decisions = queueReadyTasks(rootPath);
|
|
1013
|
+
respondSuccess(command, Boolean(opts.json), { decisions }, [
|
|
1014
|
+
`Processed ${decisions.length} ready task(s).`,
|
|
1015
|
+
...decisions.map((d) => `- ${d.task_id}: ${d.reason}`),
|
|
1016
|
+
]);
|
|
1017
|
+
}
|
|
1018
|
+
catch (err) {
|
|
1019
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
run
|
|
1023
|
+
.command('start-ready')
|
|
1024
|
+
.description('Queue and start all runnable ready tasks')
|
|
1025
|
+
.option('--dry-run', 'Prepare and compile only without executing external runners')
|
|
1026
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1027
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run start-ready\n $ sidecar run start-ready --dry-run --json')
|
|
1028
|
+
.action((opts) => {
|
|
1029
|
+
const command = 'run start-ready';
|
|
1030
|
+
try {
|
|
1031
|
+
const rootPath = resolveProjectRoot();
|
|
1032
|
+
const queueDecisions = queueReadyTasks(rootPath);
|
|
1033
|
+
const queuedTasks = listTaskPackets(rootPath).filter((task) => task.status === 'queued');
|
|
1034
|
+
const results = queuedTasks.map((task) => runTaskExecution({ rootPath, taskId: task.task_id, dryRun: Boolean(opts.dryRun) }));
|
|
1035
|
+
respondSuccess(command, Boolean(opts.json), { queued: queueDecisions, results }, [
|
|
1036
|
+
`Queued in this pass: ${queueDecisions.filter((d) => d.queued).length}`,
|
|
1037
|
+
`Started: ${results.length}`,
|
|
1038
|
+
...results.map((r) => `- ${r.task_id} -> ${r.run_id} (${r.status})`),
|
|
1039
|
+
]);
|
|
1040
|
+
}
|
|
1041
|
+
catch (err) {
|
|
1042
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
run
|
|
1046
|
+
.command('approve <run-id>')
|
|
1047
|
+
.description('Review a completed run as approved, needs changes, or merged')
|
|
1048
|
+
.option('--state <state>', 'approved|needs_changes|merged', 'approved')
|
|
1049
|
+
.option('--note <text>', 'Review note')
|
|
1050
|
+
.option('--by <name>', 'Reviewer name', 'human')
|
|
1051
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1052
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run approve R-010\n $ sidecar run approve R-010 --state needs_changes --note "Address test failures"\n $ sidecar run approve R-010 --state merged --json')
|
|
1053
|
+
.action((runIdText, opts) => {
|
|
1054
|
+
const command = 'run approve';
|
|
1055
|
+
try {
|
|
1056
|
+
const state = String(opts.state);
|
|
1057
|
+
if (state !== 'approved' && state !== 'needs_changes' && state !== 'merged') {
|
|
1058
|
+
fail('State must be one of: approved, needs_changes, merged');
|
|
1059
|
+
}
|
|
1060
|
+
const result = reviewRun(resolveProjectRoot(), runIdText.trim().toUpperCase(), state, {
|
|
1061
|
+
note: opts.note,
|
|
1062
|
+
by: opts.by,
|
|
1063
|
+
});
|
|
1064
|
+
respondSuccess(command, Boolean(opts.json), result, [
|
|
1065
|
+
`Run ${result.run_id} marked ${result.review_state}.`,
|
|
1066
|
+
`Task ${result.task_id} -> ${result.task_status}`,
|
|
1067
|
+
]);
|
|
1068
|
+
}
|
|
1069
|
+
catch (err) {
|
|
1070
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
run
|
|
1074
|
+
.command('block <run-id>')
|
|
1075
|
+
.description('Mark a completed run as blocked and set linked task blocked')
|
|
1076
|
+
.option('--note <text>', 'Blocking reason')
|
|
1077
|
+
.option('--by <name>', 'Reviewer name', 'human')
|
|
1078
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1079
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run block R-010 --note "Migration failed"\n $ sidecar run block R-010 --json')
|
|
1080
|
+
.action((runIdText, opts) => {
|
|
1081
|
+
const command = 'run block';
|
|
1082
|
+
try {
|
|
1083
|
+
const result = reviewRun(resolveProjectRoot(), runIdText.trim().toUpperCase(), 'blocked', {
|
|
1084
|
+
note: opts.note,
|
|
1085
|
+
by: opts.by,
|
|
1086
|
+
});
|
|
1087
|
+
respondSuccess(command, Boolean(opts.json), result, [
|
|
1088
|
+
`Run ${result.run_id} marked blocked.`,
|
|
1089
|
+
`Task ${result.task_id} -> ${result.task_status}`,
|
|
1090
|
+
]);
|
|
1091
|
+
}
|
|
1092
|
+
catch (err) {
|
|
1093
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
run
|
|
1097
|
+
.command('list')
|
|
1098
|
+
.description('List execution run records')
|
|
1099
|
+
.option('--task <task-id>', 'Filter by task id (for example T-001)')
|
|
1100
|
+
.option('--status <status>', 'queued|preparing|running|review|blocked|completed|failed|all', 'all')
|
|
1101
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1102
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run list\n $ sidecar run list --task T-001\n $ sidecar run list --status completed --json')
|
|
1103
|
+
.action((opts) => {
|
|
1104
|
+
const command = 'run list';
|
|
1105
|
+
try {
|
|
1106
|
+
const rootPath = resolveProjectRoot();
|
|
1107
|
+
const status = runListStatusSchema.parse(opts.status);
|
|
1108
|
+
const base = opts.task ? listRunRecordsForTask(rootPath, String(opts.task).trim().toUpperCase()) : listRunRecords(rootPath);
|
|
1109
|
+
const rows = status === 'all' ? base : base.filter((entry) => entry.status === status);
|
|
1110
|
+
if (opts.json) {
|
|
1111
|
+
respondSuccess(command, true, { status, task_id: opts.task ? String(opts.task).trim().toUpperCase() : null, runs: rows }, []);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (rows.length === 0) {
|
|
1115
|
+
console.log('No run records found.');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const idWidth = Math.max(6, ...rows.map((r) => r.run_id.length));
|
|
1119
|
+
const taskWidth = Math.max(7, ...rows.map((r) => r.task_id.length));
|
|
1120
|
+
const statusWidth = Math.max(10, ...rows.map((r) => r.status.length));
|
|
1121
|
+
console.log(`${'RUN ID'.padEnd(idWidth)} ${'TASK ID'.padEnd(taskWidth)} ${'STATUS'.padEnd(statusWidth)} STARTED`);
|
|
1122
|
+
for (const row of rows) {
|
|
1123
|
+
console.log(`${row.run_id.padEnd(idWidth)} ${row.task_id.padEnd(taskWidth)} ${row.status.padEnd(statusWidth)} ${humanTime(row.started_at)}`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
catch (err) {
|
|
1127
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
run
|
|
1131
|
+
.command('summary')
|
|
1132
|
+
.description('Show project-level run review summary')
|
|
1133
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1134
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run summary\n $ sidecar run summary --json')
|
|
1135
|
+
.action((opts) => {
|
|
1136
|
+
const command = 'run summary';
|
|
1137
|
+
try {
|
|
1138
|
+
const data = buildReviewSummary(resolveProjectRoot());
|
|
1139
|
+
respondSuccess(command, Boolean(opts.json), data, [
|
|
1140
|
+
`Completed runs: ${data.completed_runs}`,
|
|
1141
|
+
`Blocked runs: ${data.blocked_runs}`,
|
|
1142
|
+
`Suggested follow-ups: ${data.suggested_follow_ups}`,
|
|
1143
|
+
'Recently merged:',
|
|
1144
|
+
...(data.recently_merged.length
|
|
1145
|
+
? data.recently_merged.map((r) => `- ${r.run_id} (${r.task_id}) at ${humanTime(r.reviewed_at)}`)
|
|
1146
|
+
: ['- none']),
|
|
1147
|
+
]);
|
|
1148
|
+
}
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
handleCommandError(command, Boolean(opts.json), err);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
run
|
|
1154
|
+
.command('show <run-id>')
|
|
1155
|
+
.description('Show a run record by id')
|
|
1156
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1157
|
+
.addHelpText('after', '\nExamples:\n $ sidecar run show R-001\n $ sidecar run show R-001 --json')
|
|
1158
|
+
.action((runIdText, opts) => {
|
|
1159
|
+
const command = 'run show';
|
|
1160
|
+
try {
|
|
1161
|
+
const runRecord = getRunRecord(resolveProjectRoot(), runIdText.trim().toUpperCase());
|
|
1162
|
+
if (opts.json) {
|
|
1163
|
+
respondSuccess(command, true, { run: runRecord }, []);
|
|
1164
|
+
return;
|
|
788
1165
|
}
|
|
1166
|
+
console.log(stringifyJson(runRecord));
|
|
789
1167
|
}
|
|
790
1168
|
catch (err) {
|
|
791
1169
|
handleCommandError(command, Boolean(opts.json), err);
|
|
@@ -952,5 +1330,17 @@ if (process.argv.length === 2) {
|
|
|
952
1330
|
maybePrintUpdateNotice();
|
|
953
1331
|
process.exit(0);
|
|
954
1332
|
}
|
|
1333
|
+
if (process.argv[2] === 'run' &&
|
|
1334
|
+
process.argv[3] &&
|
|
1335
|
+
!process.argv[3].startsWith('-') &&
|
|
1336
|
+
process.argv[3] !== 'list' &&
|
|
1337
|
+
process.argv[3] !== 'show' &&
|
|
1338
|
+
process.argv[3] !== 'queue' &&
|
|
1339
|
+
process.argv[3] !== 'start-ready' &&
|
|
1340
|
+
process.argv[3] !== 'approve' &&
|
|
1341
|
+
process.argv[3] !== 'block' &&
|
|
1342
|
+
process.argv[3] !== 'summary') {
|
|
1343
|
+
process.argv.splice(2, 1, 'run-exec');
|
|
1344
|
+
}
|
|
955
1345
|
program.parse(process.argv);
|
|
956
1346
|
maybePrintUpdateNotice();
|
package/dist/lib/paths.js
CHANGED
|
@@ -6,6 +6,9 @@ export function getSidecarPaths(rootPath) {
|
|
|
6
6
|
return {
|
|
7
7
|
rootPath,
|
|
8
8
|
sidecarPath,
|
|
9
|
+
tasksPath: path.join(sidecarPath, 'tasks'),
|
|
10
|
+
runsPath: path.join(sidecarPath, 'runs'),
|
|
11
|
+
promptsPath: path.join(sidecarPath, 'prompts'),
|
|
9
12
|
rootAgentsPath: path.join(rootPath, 'AGENTS.md'),
|
|
10
13
|
rootClaudePath: path.join(rootPath, 'CLAUDE.md'),
|
|
11
14
|
dbPath: path.join(sidecarPath, 'sidecar.db'),
|
package/dist/lib/ui.js
CHANGED
|
@@ -84,7 +84,10 @@ export function launchUiServer(options) {
|
|
|
84
84
|
}
|
|
85
85
|
const child = spawn(process.execPath, [serverPath, '--project', options.projectPath, '--port', String(options.port)], {
|
|
86
86
|
stdio: 'inherit',
|
|
87
|
-
env: {
|
|
87
|
+
env: {
|
|
88
|
+
...process.env,
|
|
89
|
+
SIDECAR_CLI_JS: process.argv[1] || '',
|
|
90
|
+
},
|
|
88
91
|
});
|
|
89
92
|
const url = `http://localhost:${options.port}`;
|
|
90
93
|
if (options.openBrowser) {
|