sidecar-cli 0.1.5-rc.1 → 0.1.6-beta.2

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 CHANGED
@@ -200,9 +200,9 @@ Notes, decisions, worklogs:
200
200
 
201
201
  Tasks:
202
202
 
203
- - `sidecar task add "<title>" [--description <text>] [--priority low|medium|high] [--by human|agent] [--json]`
204
- - `sidecar task done <task-id> [--by human|agent] [--json]`
205
- - `sidecar task list [--status open|done|all] [--format table|json] [--json]`
203
+ - `sidecar task create --title "<title>" --summary "<summary>" --trigger "<condition>" --entry-points <path1,path2> --done-condition "<done>" --validate-cmd "<cmd>" [--trigger-check <command>] [--depends-on <task-ids>] [--status active|blocked|done] [--priority low|medium|high] [--json]`
204
+ - `sidecar task set-status <task-id> --to active|blocked|done --reason "<text>" [--by human|agent] [--session <id>] [--json]`
205
+ - `sidecar task list [--status active|blocked|done|all] [--json]`
206
206
 
207
207
  Sessions:
208
208
 
@@ -217,47 +217,21 @@ Artifacts:
217
217
  - `sidecar artifact add <path> [--kind file|doc|screenshot|other] [--note <text>] [--json]`
218
218
  - `sidecar artifact list [--json]`
219
219
 
220
- ## Validation kinds and auto-approve
220
+ ## Validation and auto-approve
221
221
 
222
- Task packets describe post-run validation commands under `execution.commands.validation`. Each entry is tagged with a **kind** so Sidecar can route the result intelligently, apply a sensible default timeout, and surface the outcome in the UI and CLI.
223
-
224
- ### Kinds
225
-
226
- | Kind | Default timeout | Intended for |
227
- | --- | --- | --- |
228
- | `typecheck` | 3 min | `tsc --noEmit`, `mypy`, `pyright` |
229
- | `lint` | 3 min | `eslint`, `ruff`, `golangci-lint` |
230
- | `test` | 10 min | unit/integration suites |
231
- | `build` | 10 min | bundlers, compilers, image builds |
232
- | `custom` | 5 min | anything else (the legacy default) |
233
-
234
- ### Authoring
235
-
236
- On the CLI, prefix a command with `kind:`. Entries without a prefix default to `custom`.
222
+ Queue tasks now store one required `validation_command` that agents run to
223
+ verify the done condition.
237
224
 
238
225
  ```bash
239
226
  sidecar task create \
240
227
  --title "Add import flow" \
241
- --summary "..." --goal "..." \
242
- --validate-cmds "typecheck:tsc --noEmit,lint:eslint .,test:npm test"
228
+ --summary "..." \
229
+ --trigger "After T-012 lands" --depends-on T-012 \
230
+ --entry-points src/cli.ts,src/tasks/task-packet.ts \
231
+ --done-condition "Task packets persist in active/blocked/done folders" \
232
+ --validate-cmd "npm run build"
243
233
  ```
244
234
 
245
- In a task packet JSON file, use the object form:
246
-
247
- ```json
248
- "execution": {
249
- "commands": {
250
- "validation": [
251
- { "kind": "typecheck", "command": "tsc --noEmit" },
252
- { "kind": "test", "command": "npm test", "timeout_ms": 900000 },
253
- "bash scripts/smoke.sh"
254
- ]
255
- }
256
- }
257
- ```
258
-
259
- String entries are accepted for back-compat (promoted to `{ kind: "custom", command }` on load).
260
-
261
235
  ### Auto-approve on all-green
262
236
 
263
237
  When every validation step passes for a run, Sidecar can auto-approve the run so you don't have to click through the review queue for a strictly-green outcome. It's opt-in:
@@ -434,7 +408,7 @@ sidecar context --format markdown
434
408
  sidecar session start --actor agent --name codex
435
409
  sidecar decision record --title "Use SQLite" --summary "Local-first persistence"
436
410
  sidecar worklog record --goal "init flow" --done "Implemented schema and command surface" --files src/cli.ts,src/db/schema.ts
437
- sidecar task add "Add integration tests" --priority medium --by agent
411
+ sidecar task create --title "Add integration tests" --summary "Add integration coverage for init flow" --trigger "When test matrix is finalized" --entry-points src/cli.ts --done-condition "Integration suite covers init flow" --validate-cmd "npm test" --priority medium
438
412
  sidecar summary refresh
439
413
  sidecar session end --summary "Initialization and recording flow implemented"
440
414
  ```
@@ -449,7 +423,7 @@ Required minimum for any code change:
449
423
  1. `sidecar context --format markdown`
450
424
  2. `sidecar worklog record --done "<what changed>" --files <paths> --by agent`
451
425
  3. if behavior/design changed: `sidecar decision record ...`
452
- 4. if follow-up exists: `sidecar task add ...`
426
+ 4. if follow-up exists: `sidecar task create ...`
453
427
  5. `sidecar summary refresh`
454
428
 
455
429
  Optional local enforcement:
@@ -489,7 +463,7 @@ When changes are made in this repo, document them in Sidecar:
489
463
  1. `sidecar context --format markdown`
490
464
  2. `sidecar worklog record --done "<what changed>" --files <paths> --by human|agent`
491
465
  3. `sidecar decision record ...` when behavior/design changes
492
- 4. `sidecar task add ...` for follow-up work
466
+ 4. `sidecar task create ...` for follow-up work
493
467
  5. `sidecar summary refresh`
494
468
 
495
469
  ## Local storage details
@@ -579,7 +553,7 @@ Standard JSON envelope:
579
553
  {
580
554
  "ok": true,
581
555
  "version": "1.0",
582
- "command": "task add",
556
+ "command": "task create",
583
557
  "data": {},
584
558
  "errors": []
585
559
  }
@@ -591,7 +565,7 @@ Failure envelope:
591
565
  {
592
566
  "ok": false,
593
567
  "version": "1.0",
594
- "command": "task add",
568
+ "command": "task create",
595
569
  "data": null,
596
570
  "errors": ["..."]
597
571
  }
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import { refreshSummaryFile } from './services/summary-service.js';
22
22
  import { buildContext } from './services/context-service.js';
23
23
  import { getCapabilitiesManifest } from './services/capabilities-service.js';
24
24
  import { addArtifact, listArtifacts } from './services/artifact-service.js';
25
- import { addDecision, addNote, addWorklog, getActiveSessionId, listRecentEvents } from './services/event-service.js';
25
+ import { addDecision, addNote, addWorklog, createEvent, getActiveSessionId, listRecentEvents } from './services/event-service.js';
26
26
  import { currentSession, endSession, startSession, verifySessionHygiene } from './services/session-service.js';
27
27
  import { HOOK_EVENTS, handleHookEvent, hookEventSchema, hookPayloadSchema } from './services/hook-service.js';
28
28
  import { loadPromptSpec } from './prompts/prompt-spec.js';
@@ -32,30 +32,27 @@ import { renderClaudeCodeHooksJson } from './templates/hooks.js';
32
32
  import { eventIngestSchema, ingestEvent } from './services/event-ingest-service.js';
33
33
  import { buildExportJson, buildExportJsonlEvents, writeOutputFile } from './services/export-service.js';
34
34
  import { createTaskPacketRecord, getTaskPacket, listTaskPackets } from './tasks/task-service.js';
35
- import { taskPacketPrioritySchema, taskPacketStatusSchema, taskPacketTypeSchema } from './tasks/task-packet.js';
35
+ import { taskPacketPrioritySchema, taskPacketStatusSchema } from './tasks/task-packet.js';
36
36
  import { getRunRecord, listRunRecords, listRunRecordsForTask } from './runs/run-service.js';
37
37
  import { runStatusSchema, runnerTypeSchema } from './runs/run-record.js';
38
38
  import { compileTaskPrompt } from './prompts/prompt-service.js';
39
39
  import { runPipelineExecution, runTaskExecution } from './services/run-orchestrator-service.js';
40
40
  import { loadRunnerPreferences } from './runners/config.js';
41
41
  import { assignTask, queueReadyTasks } from './services/task-orchestration-service.js';
42
+ import { transitionTaskStatus } from './services/task-status-service.js';
42
43
  import { buildReviewSummary, createFollowupTaskFromRun, reviewRun } from './services/run-review-service.js';
43
44
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
44
45
  const actorSchema = z.enum(['human', 'agent']);
45
46
  const artifactKindSchema = z.enum(['file', 'doc', 'screenshot', 'other']);
46
- const taskListStatusSchema = z.enum(['draft', 'ready', 'queued', 'running', 'review', 'blocked', 'done', 'all']);
47
+ const taskListStatusSchema = z.enum(['active', 'blocked', 'done', 'all']);
47
48
  const runListStatusSchema = runStatusSchema.or(z.literal('all'));
48
49
  const agentRoleSchema = z.enum(['planner', 'builder-ui', 'builder-app', 'reviewer', 'tester']);
49
50
  const exportFormatSchema = z.enum(['json', 'jsonl']);
50
51
  const NOT_INITIALIZED_MSG = 'Sidecar is not initialized in this directory or any parent directory';
51
52
  function formatStatus(value) {
52
53
  const v = value.toLowerCase();
53
- if (v === 'ready' || v === 'draft')
54
+ if (v === 'active')
54
55
  return c.cyan(value);
55
- if (v === 'running' || v === 'queued')
56
- return c.yellow(value);
57
- if (v === 'review')
58
- return c.magenta(value);
59
56
  if (v === 'blocked')
60
57
  return c.red(value);
61
58
  if (v === 'done' || v === 'merged' || v === 'approved')
@@ -544,15 +541,12 @@ program
544
541
  const created = createTaskPacketRecord(demoRoot, {
545
542
  title: 'Add welcome banner',
546
543
  summary: 'Show a friendly greeting on first launch so new users know the tool is working.',
547
- goal: 'Render a configurable banner at the top of the home page.',
548
- type: 'feature',
549
- status: 'ready',
544
+ status: 'active',
550
545
  priority: 'medium',
551
- scope_in_scope: ['Render banner component', 'Wire to /home route'],
552
- scope_out_of_scope: ['Dismissal persistence'],
553
- files_to_read: ['src/pages/home.tsx'],
554
- definition_of_done: ['Banner visible on load', 'No layout shift'],
555
- validation_commands: ['typecheck@30s:tsc --noEmit', 'test@2m:npm test'],
546
+ trigger_condition: 'PM approved onboarding polish for next release',
547
+ entry_points: ['src/pages/home.tsx', 'src/components/Banner.tsx'],
548
+ done_condition: 'Banner is visible on first launch with no layout shift.',
549
+ validation_command: 'npm run build',
556
550
  });
557
551
  log(c.green(' ✓ ') + `${created.task.task_id} — ${created.task.title}`);
558
552
  log(c.dim(` packet: ${path.relative(demoRoot, created.path)}`));
@@ -999,76 +993,70 @@ worklog
999
993
  const task = program.command('task').description('Task commands');
1000
994
  task
1001
995
  .command('create')
1002
- .description('Create a structured task packet')
996
+ .description('Create an agent-ready queue task')
1003
997
  .option('--title <title>', 'Task title')
1004
- .option('--type <type>', 'feature|bug|chore|research', 'chore')
1005
- .option('--status <status>', 'draft|ready|queued|running|review|blocked|done', 'draft')
998
+ .option('--status <status>', 'active|blocked|done', 'active')
1006
999
  .option('--priority <priority>', 'low|medium|high', 'medium')
1007
1000
  .option('--summary <summary>', 'Task summary')
1008
- .option('--goal <goal>', 'Task goal')
1009
- .option('--dependencies <task-ids>', 'Comma-separated dependency task IDs')
1010
- .option('--tags <tags>', 'Comma-separated tags')
1011
- .option('--target-areas <areas>', 'Comma-separated target areas')
1012
- .option('--scope-in <items>', 'Comma-separated in-scope items')
1013
- .option('--scope-out <items>', 'Comma-separated out-of-scope items')
1014
- .option('--related-decisions <items>', 'Comma-separated related decision IDs/titles')
1015
- .option('--related-notes <items>', 'Comma-separated related notes')
1016
- .option('--files-read <paths>', 'Comma-separated files to read')
1017
- .option('--files-avoid <paths>', 'Comma-separated files to avoid')
1018
- .option('--constraint-tech <items>', 'Comma-separated technical constraints')
1019
- .option('--constraint-design <items>', 'Comma-separated design constraints')
1020
- .option('--validate-cmds <commands>', 'Comma-separated validation commands. Use "kind:command" to tag (typecheck|lint|test|build|custom), e.g. "typecheck:tsc --noEmit,test:npm test". Append "@30s" / "@2m" / "@1500ms" to the kind to override the timeout, e.g. "test@2m:npm test".')
1021
- .option('--dod <items>', 'Comma-separated definition-of-done checks')
1022
- .option('--branch <name>', 'Branch name')
1023
- .option('--worktree <path>', 'Worktree path')
1001
+ .option('--trigger <condition>', 'Concrete trigger condition that makes this task ready')
1002
+ .option('--trigger-check <command>', 'Command that returns 0 when the trigger is satisfied')
1003
+ .option('--depends-on <task-ids>', 'Comma-separated dependency task IDs for trigger gating')
1004
+ .option('--entry-points <paths>', 'Comma-separated 1-3 files to open first')
1005
+ .option('--done-condition <text>', 'Observable condition that proves this task is done')
1006
+ .option('--validate-cmd <command>', 'Command to validate the done condition')
1024
1007
  .option('--json', 'Print machine-readable JSON output')
1025
- .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"')
1008
+ .addHelpText('after', '\nExamples:\n' +
1009
+ ' $ sidecar task create --title "Ship docs update" --summary "Update README hero"\n' +
1010
+ ' --trigger "When >=6 hubs are published" --trigger-check "node scripts/check-hubs.mjs --min 6"\n' +
1011
+ ' --entry-points README.md,POSITIONING.md --done-condition "README hero matches retrieval positioning"\n' +
1012
+ ' --validate-cmd "npm run build"\n' +
1013
+ ' $ sidecar task create --title "Follow-up patch" --summary "Apply merged run feedback"\n' +
1014
+ ' --trigger "After T-012 is done" --depends-on T-012 --entry-points src/cli.ts\n' +
1015
+ ' --done-condition "Regression no longer reproduces" --validate-cmd "npm test"')
1026
1016
  .action(async (opts) => {
1027
1017
  const command = 'task create';
1028
1018
  try {
1029
1019
  const rootPath = resolveProjectRoot();
1030
1020
  let title = opts.title?.trim() ?? '';
1031
1021
  let summary = opts.summary?.trim() ?? '';
1032
- let goal = opts.goal?.trim() ?? '';
1033
- if (!title || !summary || !goal) {
1022
+ let triggerCondition = opts.trigger?.trim() ?? '';
1023
+ let entryPointsRaw = opts.entryPoints?.trim() ?? '';
1024
+ let doneCondition = opts.doneCondition?.trim() ?? '';
1025
+ let validationCommand = opts.validateCmd?.trim() ?? '';
1026
+ if (!title || !summary || !triggerCondition || !entryPointsRaw || !doneCondition || !validationCommand) {
1034
1027
  if (!process.stdin.isTTY) {
1035
- fail('Missing required fields. Provide --title, --summary, and --goal when not running interactively.');
1028
+ fail('Missing required fields. Provide --title, --summary, --trigger, --entry-points, --done-condition, and --validate-cmd when not running interactively.');
1036
1029
  }
1037
1030
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1038
1031
  try {
1039
1032
  title = title || (await askWithDefault(rl, 'Title'));
1040
1033
  summary = summary || (await askWithDefault(rl, 'Summary', title));
1041
- goal = goal || (await askWithDefault(rl, 'Goal', `Complete: ${title}`));
1034
+ triggerCondition = triggerCondition || (await askWithDefault(rl, 'Trigger condition'));
1035
+ entryPointsRaw = entryPointsRaw || (await askWithDefault(rl, 'Entry points (comma-separated files)'));
1036
+ doneCondition = doneCondition || (await askWithDefault(rl, 'Done condition', `Complete: ${title}`));
1037
+ validationCommand = validationCommand || (await askWithDefault(rl, 'Validation command', 'npm run build'));
1042
1038
  }
1043
1039
  finally {
1044
1040
  rl.close();
1045
1041
  }
1046
1042
  }
1047
- const type = taskPacketTypeSchema.parse(opts.type);
1048
1043
  const status = taskPacketStatusSchema.parse(opts.status);
1049
1044
  const priority = taskPacketPrioritySchema.parse(opts.priority);
1045
+ const entryPoints = parseCsvOption(entryPointsRaw);
1046
+ if (entryPoints.length === 0 || entryPoints.length > 3) {
1047
+ fail('Entry points must include between 1 and 3 paths');
1048
+ }
1050
1049
  const created = createTaskPacketRecord(rootPath, {
1051
1050
  title,
1052
1051
  summary,
1053
- goal,
1054
- type,
1055
1052
  status,
1056
1053
  priority,
1057
- scope_in_scope: parseCsvOption(opts.scopeIn),
1058
- scope_out_of_scope: parseCsvOption(opts.scopeOut),
1059
- related_decisions: parseCsvOption(opts.relatedDecisions),
1060
- related_notes: parseCsvOption(opts.relatedNotes),
1061
- files_to_read: parseCsvOption(opts.filesRead),
1062
- files_to_avoid: parseCsvOption(opts.filesAvoid),
1063
- technical_constraints: parseCsvOption(opts.constraintTech),
1064
- design_constraints: parseCsvOption(opts.constraintDesign),
1065
- validation_commands: parseCsvOption(opts.validateCmds),
1066
- dependencies: parseCsvOption(opts.dependencies).map((v) => v.toUpperCase()),
1067
- tags: parseCsvOption(opts.tags),
1068
- target_areas: parseCsvOption(opts.targetAreas),
1069
- definition_of_done: parseCsvOption(opts.dod),
1070
- branch: opts.branch?.trim(),
1071
- worktree: opts.worktree?.trim(),
1054
+ trigger_condition: triggerCondition,
1055
+ trigger_check_command: opts.triggerCheck?.trim() || undefined,
1056
+ trigger_depends_on: parseCsvOption(opts.dependsOn).map((v) => v.toUpperCase()),
1057
+ entry_points: entryPoints,
1058
+ done_condition: doneCondition,
1059
+ validation_command: validationCommand,
1072
1060
  });
1073
1061
  respondSuccess(command, Boolean(opts.json), { task: created.task, path: created.path }, [`Created task ${created.task.task_id}.`, `Path: ${created.path}`]);
1074
1062
  }
@@ -1098,9 +1086,9 @@ task
1098
1086
  task
1099
1087
  .command('list')
1100
1088
  .description('List task packets')
1101
- .option('--status <status>', 'draft|ready|queued|running|review|blocked|done|all', 'all')
1089
+ .option('--status <status>', 'active|blocked|done|all', 'all')
1102
1090
  .option('--json', 'Print machine-readable JSON output')
1103
- .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status open\n $ sidecar task list --json')
1091
+ .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status active\n $ sidecar task list --json')
1104
1092
  .action((opts) => {
1105
1093
  const command = 'task list';
1106
1094
  try {
@@ -1158,6 +1146,64 @@ task
1158
1146
  handleCommandError(command, Boolean(opts.json), err);
1159
1147
  }
1160
1148
  });
1149
+ task
1150
+ .command('set-status <task-id>')
1151
+ .description('Transition task packet status with validation and an audit note')
1152
+ .requiredOption('--to <status>', 'active|blocked|done')
1153
+ .requiredOption('--reason <text>', 'Why this manual transition is needed')
1154
+ .option('--by <actor>', 'human|agent', 'human')
1155
+ .option('--session <session-id>', 'Session id override')
1156
+ .option('--json', 'Print machine-readable JSON output')
1157
+ .addHelpText('after', '\nExamples:\n $ sidecar task set-status T-001 --to active --reason "Trigger is now satisfied"\n $ sidecar task set-status T-001 --to done --reason "Administrative cleanup" --by agent --json')
1158
+ .action((taskIdText, opts) => {
1159
+ const command = 'task set-status';
1160
+ try {
1161
+ const taskId = taskIdText.trim().toUpperCase();
1162
+ const toStatus = taskPacketStatusSchema.parse(opts.to);
1163
+ const reason = String(opts.reason ?? '').trim();
1164
+ if (!reason)
1165
+ fail('Reason is required');
1166
+ const by = actorSchema.parse(opts.by);
1167
+ const { db, projectId } = requireInitialized();
1168
+ const sessionId = maybeSessionId(db, projectId, opts.session);
1169
+ const result = transitionTaskStatus(resolveProjectRoot(), taskId, toStatus);
1170
+ const eventId = createEvent(db, {
1171
+ projectId,
1172
+ type: 'note',
1173
+ title: `Task ${result.task_id} status changed`,
1174
+ summary: `${result.from_status} -> ${result.to_status}: ${reason}`,
1175
+ details: {
1176
+ task_id: result.task_id,
1177
+ from_status: result.from_status,
1178
+ to_status: result.to_status,
1179
+ reason,
1180
+ command: 'task set-status',
1181
+ },
1182
+ createdBy: by,
1183
+ sessionId,
1184
+ });
1185
+ db.close();
1186
+ respondSuccess(command, Boolean(opts.json), {
1187
+ task: result,
1188
+ event: {
1189
+ id: eventId,
1190
+ type: 'note',
1191
+ title: `Task ${result.task_id} status changed`,
1192
+ summary: `${result.from_status} -> ${result.to_status}: ${reason}`,
1193
+ created_by: by,
1194
+ session_id: sessionId,
1195
+ created_at: nowIso(),
1196
+ },
1197
+ }, [
1198
+ `Updated ${result.task_id}: ${result.from_status} -> ${result.to_status}.`,
1199
+ `Reason: ${reason}`,
1200
+ `Recorded note event #${eventId}.`,
1201
+ ]);
1202
+ }
1203
+ catch (err) {
1204
+ handleCommandError(command, Boolean(opts.json), err);
1205
+ }
1206
+ });
1161
1207
  task
1162
1208
  .command('create-followup <run-id>')
1163
1209
  .description('Create a follow-up task packet from a run report')
@@ -1404,7 +1450,7 @@ const run = program
1404
1450
  .addHelpText('after', '\nExamples:\n $ sidecar run T-001 --dry-run\n $ sidecar run T-001 --runner claude --agent-role reviewer\n $ sidecar run replay R-010 --edit-prompt\n $ sidecar run replay R-010 --runner claude --reason "second opinion"\n $ sidecar run queue\n $ sidecar run start-ready --dry-run\n $ sidecar run list --task T-001\n $ sidecar run show R-001');
1405
1451
  run
1406
1452
  .command('queue')
1407
- .description('Queue all ready tasks with satisfied dependencies')
1453
+ .description('Evaluate active tasks and mark dependency-blocked tasks')
1408
1454
  .option('--json', 'Print machine-readable JSON output')
1409
1455
  .addHelpText('after', '\nExamples:\n $ sidecar run queue\n $ sidecar run queue --json')
1410
1456
  .action((opts) => {
@@ -1413,7 +1459,7 @@ run
1413
1459
  const rootPath = resolveProjectRoot();
1414
1460
  const decisions = queueReadyTasks(rootPath);
1415
1461
  respondSuccess(command, Boolean(opts.json), { decisions }, [
1416
- `Processed ${decisions.length} ready task(s).`,
1462
+ `Processed ${decisions.length} active task(s).`,
1417
1463
  ...decisions.map((d) => `- ${d.task_id}: ${d.reason}`),
1418
1464
  ]);
1419
1465
  }
@@ -1423,7 +1469,7 @@ run
1423
1469
  });
1424
1470
  run
1425
1471
  .command('start-ready')
1426
- .description('Queue and start all runnable ready tasks')
1472
+ .description('Evaluate active tasks and run the ones with satisfied triggers')
1427
1473
  .option('--dry-run', 'Prepare and compile only without executing external runners')
1428
1474
  .option('--json', 'Print machine-readable JSON output')
1429
1475
  .addHelpText('after', '\nExamples:\n $ sidecar run start-ready\n $ sidecar run start-ready --dry-run --json')
@@ -1432,9 +1478,13 @@ run
1432
1478
  try {
1433
1479
  const rootPath = resolveProjectRoot();
1434
1480
  const queueDecisions = queueReadyTasks(rootPath);
1435
- const queuedTasks = listTaskPackets(rootPath).filter((task) => task.status === 'queued');
1481
+ const runnableTasks = queueDecisions
1482
+ .filter((d) => d.queued)
1483
+ .map((d) => d.task_id)
1484
+ .map((taskId) => getTaskPacket(rootPath, taskId))
1485
+ .filter((task) => task.status === 'active');
1436
1486
  const results = [];
1437
- for (const task of queuedTasks) {
1487
+ for (const task of runnableTasks) {
1438
1488
  const result = await runTaskExecution({
1439
1489
  rootPath,
1440
1490
  taskId: task.task_id,
@@ -1,14 +1,24 @@
1
- // Adapter from TaskPacket → CompileSectionsInput. Mirrors the legacy packet
2
- // layout exactly so `sidecar run <task-id>` produces byte-identical prompts
3
- // after the compiler refactor. Snapshot-guarded in `prompts.compat.test`.
4
1
  import { nowIso } from '../lib/format.js';
5
2
  import { PROMPT_PREFERENCE_DEFAULTS } from '../runners/config.js';
3
+ function textSection(id, title, content) {
4
+ return { id, title, kind: 'text', content, trim: 'keep' };
5
+ }
6
+ function listSection(id, title, items, options) {
7
+ return {
8
+ id,
9
+ title,
10
+ kind: 'list',
11
+ items,
12
+ ...(options?.empty_placeholder ? { empty_placeholder: options.empty_placeholder } : {}),
13
+ ...(options?.trim ? { trim: options.trim } : { trim: { policy: 'keep' } }),
14
+ };
15
+ }
6
16
  function finalResponseFormat(runner) {
7
17
  if (runner === 'codex') {
8
18
  return [
9
19
  '- Start with a one-line outcome summary.',
10
20
  '- List files changed with concise reasons.',
11
- '- Include validation commands run and their results.',
21
+ '- Include validation command output.',
12
22
  '- Note risks, blockers, or follow-up tasks.',
13
23
  ];
14
24
  }
@@ -33,59 +43,32 @@ function runnerGuidance(runner) {
33
43
  'Provide a clear summary with validation and follow-up notes at the end.',
34
44
  ];
35
45
  }
36
- function textSection(id, title, content) {
37
- return { id, title, kind: 'text', content, trim: 'keep' };
38
- }
39
- function listSection(id, title, items, options) {
40
- return {
41
- id,
42
- title,
43
- kind: 'list',
44
- items,
45
- ...(options?.empty_placeholder ? { empty_placeholder: options.empty_placeholder } : {}),
46
- ...(options?.trim ? { trim: options.trim } : { trim: { policy: 'keep' } }),
47
- };
48
- }
49
- function validationLine(v) {
50
- const label = v.name ? `${v.kind}:${v.name}` : v.kind;
51
- return v.kind === 'custom' ? v.command : `${label} — \`${v.command}\``;
52
- }
53
- // Linked context uses two sub-lists (decisions + notes) under one heading. The
54
- // legacy layout merged them so we render as a single `text` section whose
55
- // content is the pre-formatted bullet list, and trim by hand before handing it
56
- // to the core. That keeps byte-identical output without teaching the core
57
- // about multi-list sections.
58
46
  function renderLinkedContext(relatedDecisions, relatedNotes, mode) {
59
47
  const lines = [];
60
48
  const decisions = mode === 'full'
61
49
  ? relatedDecisions
62
50
  : mode === 'trim'
63
- ? sliceWithOverflow(relatedDecisions, 3, 'decisions')
64
- : sliceWithOverflow(relatedDecisions, 1, 'decisions');
51
+ ? relatedDecisions.slice(0, 3)
52
+ : relatedDecisions.slice(0, 1);
65
53
  const notes = mode === 'full'
66
54
  ? relatedNotes
67
55
  : mode === 'trim'
68
- ? sliceWithOverflow(relatedNotes, 2, 'notes')
69
- : sliceWithOverflow(relatedNotes, 0, 'notes');
70
- if (decisions.length === 0)
71
- lines.push('- no related decisions');
72
- else
56
+ ? relatedNotes.slice(0, 2)
57
+ : [];
58
+ if (decisions.length > 0) {
59
+ lines.push('Decisions:');
73
60
  for (const d of decisions)
74
61
  lines.push(`- ${d}`);
75
- if (notes.length === 0)
76
- lines.push('- no related notes');
77
- else
62
+ }
63
+ if (notes.length > 0) {
64
+ lines.push('Notes:');
78
65
  for (const n of notes)
79
66
  lines.push(`- ${n}`);
67
+ }
68
+ if (lines.length === 0)
69
+ lines.push('- no linked context');
80
70
  return lines;
81
71
  }
82
- function sliceWithOverflow(items, limit, label) {
83
- if (items.length <= limit)
84
- return items;
85
- const kept = items.slice(0, limit);
86
- kept.push(`+ ${items.length - limit} more ${label} (see task packet for full list)`);
87
- return kept;
88
- }
89
72
  function renderPreviousRuns(runs) {
90
73
  const lines = [];
91
74
  runs.forEach((prev, idx) => {
@@ -102,34 +85,12 @@ function renderPreviousRuns(runs) {
102
85
  lines.push(`- Changed files (${prev.changed_files.length}):`);
103
86
  for (const f of limited)
104
87
  lines.push(` - ${f}`);
105
- if (prev.changed_files.length > limited.length) {
106
- lines.push(` - + ${prev.changed_files.length - limited.length} more (see run record)`);
107
- }
108
- }
109
- if (prev.log_tail) {
110
- lines.push('- Log tail:');
111
- lines.push('```');
112
- for (const line of prev.log_tail.split('\n'))
113
- lines.push(line);
114
- lines.push('```');
88
+ if (prev.changed_files.length > limited.length)
89
+ lines.push(` - + ${prev.changed_files.length - limited.length} more`);
115
90
  }
116
91
  });
117
92
  return lines;
118
93
  }
119
- function renderConstraints(technical, design) {
120
- const lines = [];
121
- if (technical.length === 0)
122
- lines.push('- no technical constraints');
123
- else
124
- for (const t of technical)
125
- lines.push(`- ${t}`);
126
- if (design.length === 0)
127
- lines.push('- no design constraints');
128
- else
129
- for (const d of design)
130
- lines.push(`- ${d}`);
131
- return lines;
132
- }
133
94
  export function packetToCompileInput(input) {
134
95
  const { task, run, runner, agentRole, linkedContext, budget } = input;
135
96
  const pref = budget ?? PROMPT_PREFERENCE_DEFAULTS;
@@ -142,38 +103,30 @@ export function packetToCompileInput(input) {
142
103
  `Task id: ${task.task_id}`,
143
104
  `Compiled at: ${nowIso()}`,
144
105
  ];
145
- const relatedDecisions = linkedContext?.related_decisions ?? task.context.related_decisions;
146
- const relatedNotes = linkedContext?.related_notes ?? task.context.related_notes;
106
+ const relatedDecisions = linkedContext?.related_decisions ?? [];
107
+ const relatedNotes = linkedContext?.related_notes ?? [];
147
108
  const sections = [
148
109
  textSection('task', 'Task', [
149
110
  `- ${task.title}`,
150
- `- Type: ${task.type}`,
151
111
  `- Priority: ${task.priority}`,
152
112
  `- Status: ${task.status}`,
113
+ `- Created: ${task.created_at}`,
153
114
  ]),
154
- textSection('objective', 'Objective', [task.goal]),
155
- textSection('why', 'Why this matters', [task.summary]),
156
- listSection('in_scope', 'In scope', task.scope.in_scope, {
157
- trim: { policy: 'trim-last', limit: 8, limit_strict: 8, overflow_label: 'in-scope items' },
158
- }),
159
- listSection('out_of_scope', 'Out of scope', task.scope.out_of_scope, {
160
- trim: { policy: 'trim-last', limit: 5, limit_strict: 3, overflow_label: 'out-of-scope items' },
161
- }),
162
- listSection('files_to_read', 'Read these first', task.implementation.files_to_read, {
163
- trim: { policy: 'trim-last', limit: 10, limit_strict: 10, overflow_label: 'read-first files' },
164
- }),
165
- listSection('files_to_avoid', 'Avoid changing', task.implementation.files_to_avoid, {
166
- trim: { policy: 'trim-last', limit: 5, limit_strict: 3, overflow_label: 'avoid files' },
115
+ textSection('objective', 'Objective', [task.summary]),
116
+ textSection('trigger', 'Trigger', [
117
+ `- Condition: ${task.trigger.condition}`,
118
+ ...(task.trigger.check_command ? [`- Verify: \`${task.trigger.check_command}\``] : []),
119
+ ...(task.trigger.depends_on.length > 0 ? [`- Depends on: ${task.trigger.depends_on.join(', ')}`] : []),
120
+ ]),
121
+ listSection('entry_points', 'Entry points', task.entry_points, {
122
+ trim: { policy: 'trim-last', limit: 3, limit_strict: 3, overflow_label: 'entry points' },
167
123
  }),
168
- // Linked context stays a text section so the "no related X" placeholders stay where they were.
124
+ textSection('definition_of_done', 'Definition of done', [task.done_condition]),
125
+ textSection('validation', 'Validation command', [`\`${task.validation_command}\``]),
169
126
  textSection('linked_context', 'Linked context', renderLinkedContext(relatedDecisions, relatedNotes, 'full')),
170
- // Previous runner context — only when this run is a later step in a pipeline.
171
127
  ...(linkedContext?.previous_runs && linkedContext.previous_runs.length > 0
172
128
  ? [textSection('previous_runs', 'Previous runner context', renderPreviousRuns(linkedContext.previous_runs))]
173
129
  : []),
174
- textSection('constraints', 'Constraints', renderConstraints(task.constraints.technical, task.constraints.design)),
175
- listSection('validation', 'Validation', task.execution.commands.validation.map(validationLine)),
176
- listSection('definition_of_done', 'Definition of done', task.definition_of_done),
177
130
  textSection('runner_guidance', 'Runner guidance', runnerGuidance(runner)),
178
131
  textSection('final_response_format', 'Final response format', finalResponseFormat(runner)),
179
132
  ];
@@ -183,21 +136,6 @@ export function packetToCompileInput(input) {
183
136
  budget: { target: pref.budget_target, max: pref.budget_max },
184
137
  };
185
138
  }
186
- // Legacy metadata expects `trimmed_sections: string[]` using the historical names.
187
- // Map the new core metadata back for back-compat.
188
- export const LEGACY_TRIM_IDS = [
189
- 'in_scope',
190
- 'out_of_scope',
191
- 'files_to_read',
192
- 'files_to_avoid',
193
- 'related_decisions',
194
- 'related_notes',
195
- ];
196
- // Rebuild linked_context lines under a trim mode. The core compileSections()
197
- // can't partially trim a text section, so we run the full pipeline twice in
198
- // prompt-compiler.ts: first with full linked_context, and again with trimmed
199
- // linked_context if the baseline is over budget. See prompt-compiler.ts for
200
- // the wrapper that orchestrates this.
201
139
  export function linkedContextForMode(relatedDecisions, relatedNotes, mode) {
202
140
  return renderLinkedContext(relatedDecisions, relatedNotes, mode);
203
141
  }
@@ -12,8 +12,8 @@ function packetInputForMode(adapterInput, mode) {
12
12
  const baseline = packetToCompileInput(adapterInput);
13
13
  if (mode === 'full')
14
14
  return baseline;
15
- const relatedDecisions = adapterInput.linkedContext?.related_decisions ?? adapterInput.task.context.related_decisions;
16
- const relatedNotes = adapterInput.linkedContext?.related_notes ?? adapterInput.task.context.related_notes;
15
+ const relatedDecisions = adapterInput.linkedContext?.related_decisions ?? [];
16
+ const relatedNotes = adapterInput.linkedContext?.related_notes ?? [];
17
17
  const linkedLines = linkedContextForMode(relatedDecisions, relatedNotes, mode);
18
18
  const sections = baseline.sections.map((section) => {
19
19
  if (section.id !== 'linked_context')
@@ -27,8 +27,8 @@ function packetInputForMode(adapterInput, mode) {
27
27
  function buildLegacyTrimmed(adapterInput, listTrimmed, mode) {
28
28
  const out = new Set(listTrimmed);
29
29
  if (mode !== 'full') {
30
- const decisions = adapterInput.linkedContext?.related_decisions ?? adapterInput.task.context.related_decisions;
31
- const notes = adapterInput.linkedContext?.related_notes ?? adapterInput.task.context.related_notes;
30
+ const decisions = adapterInput.linkedContext?.related_decisions ?? [];
31
+ const notes = adapterInput.linkedContext?.related_notes ?? [];
32
32
  if (mode === 'trim') {
33
33
  if (decisions.length > 3)
34
34
  out.add('related_decisions');