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 +12 -38
- package/dist/cli.js +57 -66
- package/dist/prompts/packet-sections.js +41 -103
- package/dist/prompts/prompt-compiler.js +4 -4
- package/dist/prompts/prompt-service.js +2 -2
- package/dist/services/capabilities-service.js +12 -23
- package/dist/services/run-orchestrator-service.js +8 -13
- package/dist/services/run-review-service.js +14 -15
- package/dist/services/task-orchestration-service.js +43 -56
- package/dist/services/task-status-service.js +3 -7
- package/dist/tasks/task-packet.js +150 -119
- package/dist/tasks/task-repository.js +64 -26
- package/dist/tasks/task-service.js +12 -46
- package/dist/templates/agents.js +118 -52
- package/package.json +1 -1
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>" --
|
|
204
|
-
- `sidecar task set-status <task-id> --to
|
|
205
|
-
- `sidecar task list [--status
|
|
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
|
|
220
|
+
## Validation and auto-approve
|
|
221
221
|
|
|
222
|
-
|
|
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 "..."
|
|
242
|
-
--
|
|
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" --
|
|
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
|
|
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(['
|
|
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 === '
|
|
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
|
-
|
|
549
|
-
type: 'feature',
|
|
550
|
-
status: 'ready',
|
|
544
|
+
status: 'active',
|
|
551
545
|
priority: 'medium',
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
|
996
|
+
.description('Create an agent-ready queue task')
|
|
1004
997
|
.option('--title <title>', 'Task title')
|
|
1005
|
-
.option('--
|
|
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('--
|
|
1010
|
-
.option('--
|
|
1011
|
-
.option('--
|
|
1012
|
-
.option('--
|
|
1013
|
-
.option('--
|
|
1014
|
-
.option('--
|
|
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
|
|
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
|
|
1034
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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>', '
|
|
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
|
|
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>', '
|
|
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
|
|
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('
|
|
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}
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
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
|
-
?
|
|
64
|
-
:
|
|
51
|
+
? relatedDecisions.slice(0, 3)
|
|
52
|
+
: relatedDecisions.slice(0, 1);
|
|
65
53
|
const notes = mode === 'full'
|
|
66
54
|
? relatedNotes
|
|
67
55
|
: mode === 'trim'
|
|
68
|
-
?
|
|
69
|
-
:
|
|
70
|
-
if (decisions.length
|
|
71
|
-
lines.push('
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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 ??
|
|
146
|
-
const relatedNotes = linkedContext?.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.
|
|
155
|
-
textSection('
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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 ??
|
|
16
|
-
const relatedNotes = adapterInput.linkedContext?.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 ??
|
|
31
|
-
const notes = adapterInput.linkedContext?.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:
|
|
13
|
-
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
|
|
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
|
-
'--
|
|
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
|
-
'--
|
|
187
|
-
'--
|
|
188
|
-
'--
|
|
189
|
-
'--
|
|
190
|
-
'--
|
|
191
|
-
'--
|
|
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
|
|
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
|
|
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: '
|
|
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: '
|
|
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'],
|