sidecar-cli 0.1.6-beta.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 create --title "<title>" --summary "<summary>" --goal "<goal>" [--type feature|bug|chore|research] [--status draft|ready|queued|running|review|blocked|done] [--priority low|medium|high] [--json]`
204
- - `sidecar task set-status <task-id> --to draft|ready|queued|running|review|blocked|done --reason "<text>" [--by human|agent] [--session <id>] [--json]`
205
- - `sidecar task list [--status draft|ready|queued|running|review|blocked|done|all] [--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 create --title "Add integration tests" --summary "Add integration coverage for init flow" --goal "Ensure init flow has regression coverage" --priority medium
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
  ```
package/dist/cli.js CHANGED
@@ -32,7 +32,7 @@ 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';
@@ -44,19 +44,15 @@ import { buildReviewSummary, createFollowupTaskFromRun, reviewRun } from './serv
44
44
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
45
45
  const actorSchema = z.enum(['human', 'agent']);
46
46
  const artifactKindSchema = z.enum(['file', 'doc', 'screenshot', 'other']);
47
- const taskListStatusSchema = z.enum(['draft', 'ready', 'queued', 'running', 'review', 'blocked', 'done', 'all']);
47
+ const taskListStatusSchema = z.enum(['active', 'blocked', 'done', 'all']);
48
48
  const runListStatusSchema = runStatusSchema.or(z.literal('all'));
49
49
  const agentRoleSchema = z.enum(['planner', 'builder-ui', 'builder-app', 'reviewer', 'tester']);
50
50
  const exportFormatSchema = z.enum(['json', 'jsonl']);
51
51
  const NOT_INITIALIZED_MSG = 'Sidecar is not initialized in this directory or any parent directory';
52
52
  function formatStatus(value) {
53
53
  const v = value.toLowerCase();
54
- if (v === 'ready' || v === 'draft')
54
+ if (v === 'active')
55
55
  return c.cyan(value);
56
- if (v === 'running' || v === 'queued')
57
- return c.yellow(value);
58
- if (v === 'review')
59
- return c.magenta(value);
60
56
  if (v === 'blocked')
61
57
  return c.red(value);
62
58
  if (v === 'done' || v === 'merged' || v === 'approved')
@@ -545,15 +541,12 @@ program
545
541
  const created = createTaskPacketRecord(demoRoot, {
546
542
  title: 'Add welcome banner',
547
543
  summary: 'Show a friendly greeting on first launch so new users know the tool is working.',
548
- goal: 'Render a configurable banner at the top of the home page.',
549
- type: 'feature',
550
- status: 'ready',
544
+ status: 'active',
551
545
  priority: 'medium',
552
- scope_in_scope: ['Render banner component', 'Wire to /home route'],
553
- scope_out_of_scope: ['Dismissal persistence'],
554
- files_to_read: ['src/pages/home.tsx'],
555
- definition_of_done: ['Banner visible on load', 'No layout shift'],
556
- 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',
557
550
  });
558
551
  log(c.green(' ✓ ') + `${created.task.task_id} — ${created.task.title}`);
559
552
  log(c.dim(` packet: ${path.relative(demoRoot, created.path)}`));
@@ -1000,76 +993,70 @@ worklog
1000
993
  const task = program.command('task').description('Task commands');
1001
994
  task
1002
995
  .command('create')
1003
- .description('Create a structured task packet')
996
+ .description('Create an agent-ready queue task')
1004
997
  .option('--title <title>', 'Task title')
1005
- .option('--type <type>', 'feature|bug|chore|research', 'chore')
1006
- .option('--status <status>', 'draft|ready|queued|running|review|blocked|done', 'draft')
998
+ .option('--status <status>', 'active|blocked|done', 'active')
1007
999
  .option('--priority <priority>', 'low|medium|high', 'medium')
1008
1000
  .option('--summary <summary>', 'Task summary')
1009
- .option('--goal <goal>', 'Task goal')
1010
- .option('--dependencies <task-ids>', 'Comma-separated dependency task IDs')
1011
- .option('--tags <tags>', 'Comma-separated tags')
1012
- .option('--target-areas <areas>', 'Comma-separated target areas')
1013
- .option('--scope-in <items>', 'Comma-separated in-scope items')
1014
- .option('--scope-out <items>', 'Comma-separated out-of-scope items')
1015
- .option('--related-decisions <items>', 'Comma-separated related decision IDs/titles')
1016
- .option('--related-notes <items>', 'Comma-separated related notes')
1017
- .option('--files-read <paths>', 'Comma-separated files to read')
1018
- .option('--files-avoid <paths>', 'Comma-separated files to avoid')
1019
- .option('--constraint-tech <items>', 'Comma-separated technical constraints')
1020
- .option('--constraint-design <items>', 'Comma-separated design constraints')
1021
- .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".')
1022
- .option('--dod <items>', 'Comma-separated definition-of-done checks')
1023
- .option('--branch <name>', 'Branch name')
1024
- .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')
1025
1007
  .option('--json', 'Print machine-readable JSON output')
1026
- .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"')
1027
1016
  .action(async (opts) => {
1028
1017
  const command = 'task create';
1029
1018
  try {
1030
1019
  const rootPath = resolveProjectRoot();
1031
1020
  let title = opts.title?.trim() ?? '';
1032
1021
  let summary = opts.summary?.trim() ?? '';
1033
- let goal = opts.goal?.trim() ?? '';
1034
- 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) {
1035
1027
  if (!process.stdin.isTTY) {
1036
- 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.');
1037
1029
  }
1038
1030
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1039
1031
  try {
1040
1032
  title = title || (await askWithDefault(rl, 'Title'));
1041
1033
  summary = summary || (await askWithDefault(rl, 'Summary', title));
1042
- 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'));
1043
1038
  }
1044
1039
  finally {
1045
1040
  rl.close();
1046
1041
  }
1047
1042
  }
1048
- const type = taskPacketTypeSchema.parse(opts.type);
1049
1043
  const status = taskPacketStatusSchema.parse(opts.status);
1050
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
+ }
1051
1049
  const created = createTaskPacketRecord(rootPath, {
1052
1050
  title,
1053
1051
  summary,
1054
- goal,
1055
- type,
1056
1052
  status,
1057
1053
  priority,
1058
- scope_in_scope: parseCsvOption(opts.scopeIn),
1059
- scope_out_of_scope: parseCsvOption(opts.scopeOut),
1060
- related_decisions: parseCsvOption(opts.relatedDecisions),
1061
- related_notes: parseCsvOption(opts.relatedNotes),
1062
- files_to_read: parseCsvOption(opts.filesRead),
1063
- files_to_avoid: parseCsvOption(opts.filesAvoid),
1064
- technical_constraints: parseCsvOption(opts.constraintTech),
1065
- design_constraints: parseCsvOption(opts.constraintDesign),
1066
- validation_commands: parseCsvOption(opts.validateCmds),
1067
- dependencies: parseCsvOption(opts.dependencies).map((v) => v.toUpperCase()),
1068
- tags: parseCsvOption(opts.tags),
1069
- target_areas: parseCsvOption(opts.targetAreas),
1070
- definition_of_done: parseCsvOption(opts.dod),
1071
- branch: opts.branch?.trim(),
1072
- 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,
1073
1060
  });
1074
1061
  respondSuccess(command, Boolean(opts.json), { task: created.task, path: created.path }, [`Created task ${created.task.task_id}.`, `Path: ${created.path}`]);
1075
1062
  }
@@ -1099,9 +1086,9 @@ task
1099
1086
  task
1100
1087
  .command('list')
1101
1088
  .description('List task packets')
1102
- .option('--status <status>', 'draft|ready|queued|running|review|blocked|done|all', 'all')
1089
+ .option('--status <status>', 'active|blocked|done|all', 'all')
1103
1090
  .option('--json', 'Print machine-readable JSON output')
1104
- .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status draft\n $ sidecar task list --json')
1091
+ .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status active\n $ sidecar task list --json')
1105
1092
  .action((opts) => {
1106
1093
  const command = 'task list';
1107
1094
  try {
@@ -1162,12 +1149,12 @@ task
1162
1149
  task
1163
1150
  .command('set-status <task-id>')
1164
1151
  .description('Transition task packet status with validation and an audit note')
1165
- .requiredOption('--to <status>', 'draft|ready|queued|running|review|blocked|done')
1152
+ .requiredOption('--to <status>', 'active|blocked|done')
1166
1153
  .requiredOption('--reason <text>', 'Why this manual transition is needed')
1167
1154
  .option('--by <actor>', 'human|agent', 'human')
1168
1155
  .option('--session <session-id>', 'Session id override')
1169
1156
  .option('--json', 'Print machine-readable JSON output')
1170
- .addHelpText('after', '\nExamples:\n $ sidecar task set-status T-001 --to ready --reason "Ready to queue"\n $ sidecar task set-status T-001 --to done --reason "Administrative cleanup" --by agent --json')
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')
1171
1158
  .action((taskIdText, opts) => {
1172
1159
  const command = 'task set-status';
1173
1160
  try {
@@ -1463,7 +1450,7 @@ const run = program
1463
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');
1464
1451
  run
1465
1452
  .command('queue')
1466
- .description('Queue all ready tasks with satisfied dependencies')
1453
+ .description('Evaluate active tasks and mark dependency-blocked tasks')
1467
1454
  .option('--json', 'Print machine-readable JSON output')
1468
1455
  .addHelpText('after', '\nExamples:\n $ sidecar run queue\n $ sidecar run queue --json')
1469
1456
  .action((opts) => {
@@ -1472,7 +1459,7 @@ run
1472
1459
  const rootPath = resolveProjectRoot();
1473
1460
  const decisions = queueReadyTasks(rootPath);
1474
1461
  respondSuccess(command, Boolean(opts.json), { decisions }, [
1475
- `Processed ${decisions.length} ready task(s).`,
1462
+ `Processed ${decisions.length} active task(s).`,
1476
1463
  ...decisions.map((d) => `- ${d.task_id}: ${d.reason}`),
1477
1464
  ]);
1478
1465
  }
@@ -1482,7 +1469,7 @@ run
1482
1469
  });
1483
1470
  run
1484
1471
  .command('start-ready')
1485
- .description('Queue and start all runnable ready tasks')
1472
+ .description('Evaluate active tasks and run the ones with satisfied triggers')
1486
1473
  .option('--dry-run', 'Prepare and compile only without executing external runners')
1487
1474
  .option('--json', 'Print machine-readable JSON output')
1488
1475
  .addHelpText('after', '\nExamples:\n $ sidecar run start-ready\n $ sidecar run start-ready --dry-run --json')
@@ -1491,9 +1478,13 @@ run
1491
1478
  try {
1492
1479
  const rootPath = resolveProjectRoot();
1493
1480
  const queueDecisions = queueReadyTasks(rootPath);
1494
- 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');
1495
1486
  const results = [];
1496
- for (const task of queuedTasks) {
1487
+ for (const task of runnableTasks) {
1497
1488
  const result = await runTaskExecution({
1498
1489
  rootPath,
1499
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');
@@ -9,8 +9,8 @@ export function compileTaskPrompt(input) {
9
9
  runner_type: input.runner,
10
10
  agent_role: input.agentRole,
11
11
  status: 'preparing',
12
- branch: task.tracking.branch,
13
- worktree: task.tracking.worktree,
12
+ branch: '',
13
+ worktree: '',
14
14
  ...(input.parentRunId ? { parent_run_id: input.parentRunId } : {}),
15
15
  ...(input.replayReason ? { replay_reason: input.replayReason } : {}),
16
16
  ...(input.pipelineId ? { pipeline_id: input.pipelineId } : {}),
@@ -173,31 +173,20 @@ export function getCapabilitiesManifest(version) {
173
173
  subcommands: [
174
174
  {
175
175
  name: 'create',
176
- description: 'Create a structured task packet',
176
+ description: 'Create an agent-ready queue task',
177
177
  json_output: true,
178
178
  arguments: [],
179
179
  options: [
180
180
  '--title <title>',
181
181
  '--summary <summary>',
182
- '--goal <goal>',
183
- '--type feature|bug|chore|research',
184
- '--status draft|ready|queued|running|review|blocked|done',
182
+ '--status active|blocked|done',
185
183
  '--priority low|medium|high',
186
- '--dependencies <task-ids>',
187
- '--tags <tags>',
188
- '--target-areas <areas>',
189
- '--scope-in <items>',
190
- '--scope-out <items>',
191
- '--related-decisions <items>',
192
- '--related-notes <items>',
193
- '--files-read <paths>',
194
- '--files-avoid <paths>',
195
- '--constraint-tech <items>',
196
- '--constraint-design <items>',
197
- '--validate-cmds <commands>',
198
- '--dod <items>',
199
- '--branch <name>',
200
- '--worktree <path>',
184
+ '--trigger <condition>',
185
+ '--trigger-check <command>',
186
+ '--depends-on <task-ids>',
187
+ '--entry-points <paths>',
188
+ '--done-condition <text>',
189
+ '--validate-cmd <command>',
201
190
  '--json',
202
191
  ],
203
192
  },
@@ -213,7 +202,7 @@ export function getCapabilitiesManifest(version) {
213
202
  description: 'List task packets',
214
203
  json_output: true,
215
204
  arguments: [],
216
- options: ['--status draft|ready|queued|running|review|blocked|done|all', '--json'],
205
+ options: ['--status active|blocked|done|all', '--json'],
217
206
  },
218
207
  {
219
208
  name: 'assign',
@@ -227,7 +216,7 @@ export function getCapabilitiesManifest(version) {
227
216
  description: 'Transition task packet status with validation',
228
217
  json_output: true,
229
218
  arguments: ['<task-id>'],
230
- options: ['--to draft|ready|queued|running|review|blocked|done', '--reason <text>', '--by human|agent', '--session <id>', '--json'],
219
+ options: ['--to active|blocked|done', '--reason <text>', '--by human|agent', '--session <id>', '--json'],
231
220
  },
232
221
  {
233
222
  name: 'create-followup',
@@ -263,14 +252,14 @@ export function getCapabilitiesManifest(version) {
263
252
  subcommands: [
264
253
  {
265
254
  name: 'queue',
266
- description: 'Queue ready tasks with satisfied dependencies',
255
+ description: 'Evaluate active tasks and mark dependency-blocked tasks',
267
256
  json_output: true,
268
257
  arguments: [],
269
258
  options: ['--json'],
270
259
  },
271
260
  {
272
261
  name: 'start-ready',
273
- description: 'Queue and start all runnable ready tasks',
262
+ description: 'Evaluate active tasks and run the ones with satisfied triggers',
274
263
  json_output: true,
275
264
  arguments: [],
276
265
  options: ['--dry-run', '--json'],