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.
@@ -44,9 +44,9 @@ export async function runTaskExecution(input) {
44
44
  const prefs = loadRunnerPreferences(input.rootPath);
45
45
  const dryRun = Boolean(input.dryRun);
46
46
  const task = getTaskPacket(input.rootPath, input.taskId);
47
- const runner = input.runner ?? task.tracking.assigned_runner ?? prefs.default_runner;
48
- const agentRole = input.agentRole ?? task.tracking.assigned_agent_role ?? prefs.default_agent_role;
49
- const worktree = (task.tracking.worktree ?? '').trim();
47
+ const runner = input.runner ?? prefs.default_runner;
48
+ const agentRole = input.agentRole ?? prefs.default_agent_role;
49
+ const worktree = '';
50
50
  let cwd;
51
51
  if (worktree.length > 0) {
52
52
  if (!fs.existsSync(worktree)) {
@@ -74,10 +74,10 @@ export async function runTaskExecution(input) {
74
74
  }
75
75
  const logPath = path.resolve(path.join(input.rootPath, '.sidecar', 'runs', 'logs', `${compiled.run_id}.log`));
76
76
  const adapter = getRunnerAdapter(runner);
77
- saveTaskPacket(input.rootPath, { ...task, status: 'running' });
77
+ saveTaskPacket(input.rootPath, { ...task, status: 'active' });
78
78
  updateRunRecordEntry(input.rootPath, compiled.run_id, {
79
79
  status: 'running',
80
- branch: task.tracking.branch,
80
+ branch: '',
81
81
  worktree: cwd,
82
82
  });
83
83
  const prepared = adapter.prepare({
@@ -107,13 +107,8 @@ export async function runTaskExecution(input) {
107
107
  changedFiles = await captureFilesChangedSince(cwd, preRunSnapshot);
108
108
  }
109
109
  if (!dryRun && collected.executed && ok) {
110
- const configured = task.execution?.commands?.validation ?? [];
111
- const steps = [];
112
- for (const entry of configured) {
113
- const normalized = normalizeValidationStep(entry);
114
- if (normalized)
115
- steps.push(normalized);
116
- }
110
+ const normalized = normalizeValidationStep(task.validation_command);
111
+ const steps = normalized ? [normalized] : [];
117
112
  if (steps.length > 0) {
118
113
  validationAttempted = true;
119
114
  const results = await runValidationCommands(cwd, steps, logPath);
@@ -130,7 +125,7 @@ export async function runTaskExecution(input) {
130
125
  }
131
126
  }
132
127
  const finishedStatus = ok ? 'completed' : 'failed';
133
- const nextTaskStatus = ok ? 'review' : 'blocked';
128
+ const nextTaskStatus = ok ? 'done' : 'blocked';
134
129
  // Auto-approve on all-green is opt-in via preferences. We only auto-approve when at least
135
130
  // one validation step actually ran and every step passed — a runner-only success with no
136
131
  // validation configured still requires a human click.
@@ -4,14 +4,14 @@ import { getRunRecord, listRunRecords, updateRunRecordEntry } from '../runs/run-
4
4
  import { createTaskPacketRecord, getTaskPacket, saveTaskPacket } from '../tasks/task-service.js';
5
5
  function taskStatusForReview(state) {
6
6
  if (state === 'approved')
7
- return 'review';
7
+ return 'active';
8
8
  if (state === 'needs_changes')
9
- return 'ready';
9
+ return 'active';
10
10
  if (state === 'blocked')
11
11
  return 'blocked';
12
12
  if (state === 'merged')
13
13
  return 'done';
14
- return 'review';
14
+ return 'active';
15
15
  }
16
16
  export function reviewRun(rootPath, runId, state, options) {
17
17
  const run = getRunRecord(rootPath, runId);
@@ -38,22 +38,21 @@ export function createFollowupTaskFromRun(rootPath, runId) {
38
38
  const run = getRunRecord(rootPath, runId);
39
39
  const sourceTask = getTaskPacket(rootPath, run.task_id);
40
40
  const suggestions = run.follow_ups.length > 0 ? run.follow_ups : ['Investigate run issues and apply required changes'];
41
+ const entryPoints = sourceTask.entry_points.length > 0
42
+ ? sourceTask.entry_points.slice(0, 3)
43
+ : run.changed_files.slice(0, 3).length > 0
44
+ ? run.changed_files.slice(0, 3)
45
+ : ['src/cli.ts'];
41
46
  const created = createTaskPacketRecord(rootPath, {
42
47
  title: `Follow-up: ${sourceTask.title}`,
43
48
  summary: run.review_note || run.summary || 'Follow-up work from reviewed run',
44
- goal: suggestions.join('; '),
45
- type: sourceTask.type,
46
- status: 'draft',
49
+ status: 'active',
47
50
  priority: sourceTask.priority,
48
- dependencies: [sourceTask.task_id],
49
- tags: Array.from(new Set([...sourceTask.tags, 'follow-up'])),
50
- target_areas: sourceTask.target_areas,
51
- files_to_read: sourceTask.implementation.files_to_read,
52
- files_to_avoid: sourceTask.implementation.files_to_avoid,
53
- technical_constraints: sourceTask.constraints.technical,
54
- design_constraints: sourceTask.constraints.design,
55
- validation_commands: sourceTask.execution.commands.validation.map((v) => ({ ...v })),
56
- definition_of_done: [...sourceTask.definition_of_done, ...suggestions],
51
+ trigger_condition: `After ${sourceTask.task_id} is done and this follow-up is explicitly scheduled`,
52
+ trigger_depends_on: [sourceTask.task_id],
53
+ entry_points: entryPoints,
54
+ done_condition: suggestions.join('; '),
55
+ validation_command: sourceTask.validation_command,
57
56
  });
58
57
  return {
59
58
  source_run_id: run.run_id,
@@ -1,24 +1,15 @@
1
- import { nowIso } from '../lib/format.js';
1
+ import { spawnSync } from 'node:child_process';
2
2
  import { getTaskPacket, listTaskPackets, saveTaskPacket } from '../tasks/task-service.js';
3
3
  function hasUiSignal(task) {
4
- const joined = [
5
- ...task.tags,
6
- ...task.target_areas,
7
- ...task.implementation.files_to_read,
8
- ...task.implementation.files_to_avoid,
9
- ]
10
- .join(' ')
11
- .toLowerCase();
4
+ const joined = [task.title, task.summary, ...task.entry_points].join(' ').toLowerCase();
12
5
  return /(ui|frontend|css|html|react|view|component)/.test(joined);
13
6
  }
14
7
  function pickRole(task) {
15
- if (task.type === 'research')
16
- return { role: 'planner', reason: 'task type is research' };
17
- if (task.tags.some((t) => t.toLowerCase() === 'test') || task.target_areas.some((a) => /test/i.test(a))) {
18
- return { role: 'tester', reason: 'tags/target_areas indicate testing' };
8
+ if (/test|qa|verification|validate/i.test(task.title) || /test|qa|verification|validate/i.test(task.summary)) {
9
+ return { role: 'tester', reason: 'testing signal in title/summary' };
19
10
  }
20
- if (task.tags.some((t) => /review/i.test(t)) || task.type === 'bug') {
21
- return { role: 'reviewer', reason: 'bug/review signal present' };
11
+ if (/review|audit|risk|regression/i.test(task.title) || /review|audit|risk|regression/i.test(task.summary)) {
12
+ return { role: 'reviewer', reason: 'review signal in title/summary' };
22
13
  }
23
14
  if (hasUiSignal(task))
24
15
  return { role: 'builder-ui', reason: 'ui/frontend signal detected' };
@@ -30,26 +21,43 @@ function defaultRunnerForRole(role) {
30
21
  return 'codex';
31
22
  }
32
23
  export function dependenciesMet(task, tasksById) {
33
- const missing = task.dependencies.filter((depId) => tasksById.get(depId)?.status !== 'done');
24
+ const deps = task.trigger.depends_on ?? [];
25
+ const missing = deps.filter((depId) => tasksById.get(depId)?.status !== 'done');
34
26
  return { ok: missing.length === 0, missing };
35
27
  }
28
+ function triggerCommandSatisfied(rootPath, command) {
29
+ const proc = spawnSync(command, {
30
+ cwd: rootPath,
31
+ shell: true,
32
+ stdio: 'pipe',
33
+ encoding: 'utf8',
34
+ });
35
+ const ok = proc.status === 0;
36
+ const stderr = (proc.stderr || '').trim();
37
+ const stdout = (proc.stdout || '').trim();
38
+ if (ok)
39
+ return { ok: true, reason: `trigger check passed: ${command}` };
40
+ const details = stderr || stdout || `exit ${String(proc.status ?? '1')}`;
41
+ return { ok: false, reason: `trigger check failed: ${command} (${details})` };
42
+ }
43
+ export function evaluateTrigger(rootPath, task, tasksById) {
44
+ const dep = dependenciesMet(task, tasksById);
45
+ if (!dep.ok)
46
+ return { ok: false, reason: `dependencies not done: ${dep.missing.join(', ')}` };
47
+ const checkCommand = task.trigger.check_command?.trim();
48
+ if (checkCommand)
49
+ return triggerCommandSatisfied(rootPath, checkCommand);
50
+ if (task.trigger.depends_on.length > 0) {
51
+ return { ok: true, reason: 'dependency trigger satisfied' };
52
+ }
53
+ return { ok: false, reason: 'trigger requires user confirmation (no check command)' };
54
+ }
36
55
  export function assignTask(rootPath, taskId, override) {
37
56
  const task = getTaskPacket(rootPath, taskId);
38
57
  const auto = pickRole(task);
39
58
  const role = override?.role ?? auto.role;
40
59
  const runner = override?.runner ?? defaultRunnerForRole(role);
41
60
  const reason = override?.role || override?.runner ? 'manual override' : auto.reason;
42
- const updated = {
43
- ...task,
44
- tracking: {
45
- ...task.tracking,
46
- assigned_agent_role: role,
47
- assigned_runner: runner,
48
- assignment_reason: reason,
49
- assigned_at: nowIso(),
50
- },
51
- };
52
- saveTaskPacket(rootPath, updated);
53
61
  return { task_id: task.task_id, agent_role: role, runner, reason };
54
62
  }
55
63
  export function queueReadyTasks(rootPath) {
@@ -57,38 +65,17 @@ export function queueReadyTasks(rootPath) {
57
65
  const byId = new Map(tasks.map((t) => [t.task_id, t]));
58
66
  const decisions = [];
59
67
  for (const task of tasks) {
60
- if (task.status !== 'ready')
68
+ if (task.status !== 'active')
61
69
  continue;
62
- const dep = dependenciesMet(task, byId);
63
- if (!dep.ok) {
64
- saveTaskPacket(rootPath, { ...task, status: 'blocked' });
65
- decisions.push({ task_id: task.task_id, queued: false, reason: `blocked by dependencies: ${dep.missing.join(', ')}` });
70
+ const trigger = evaluateTrigger(rootPath, task, byId);
71
+ if (!trigger.ok) {
72
+ if (task.trigger.depends_on.length > 0) {
73
+ saveTaskPacket(rootPath, { ...task, status: 'blocked' });
74
+ }
75
+ decisions.push({ task_id: task.task_id, queued: false, reason: trigger.reason });
66
76
  continue;
67
77
  }
68
- const assignment = task.tracking.assigned_agent_role && task.tracking.assigned_runner
69
- ? {
70
- role: task.tracking.assigned_agent_role,
71
- runner: task.tracking.assigned_runner,
72
- }
73
- : (() => {
74
- const decided = assignTask(rootPath, task.task_id);
75
- return { role: decided.agent_role, runner: decided.runner };
76
- })();
77
- const latest = getTaskPacket(rootPath, task.task_id);
78
- saveTaskPacket(rootPath, {
79
- ...latest,
80
- status: 'queued',
81
- tracking: {
82
- ...latest.tracking,
83
- assigned_agent_role: assignment.role,
84
- assigned_runner: assignment.runner,
85
- },
86
- });
87
- decisions.push({
88
- task_id: task.task_id,
89
- queued: true,
90
- reason: `queued for ${assignment.role} via ${assignment.runner}`,
91
- });
78
+ decisions.push({ task_id: task.task_id, queued: true, reason: trigger.reason });
92
79
  }
93
80
  return decisions;
94
81
  }
@@ -1,13 +1,9 @@
1
1
  import { SidecarError } from '../lib/errors.js';
2
2
  import { getTaskPacket, saveTaskPacket } from '../tasks/task-service.js';
3
3
  const TASK_STATUS_TRANSITIONS = {
4
- draft: ['ready', 'blocked', 'done'],
5
- ready: ['draft', 'queued', 'blocked', 'done'],
6
- queued: ['ready', 'running', 'blocked'],
7
- running: ['ready', 'review', 'blocked'],
8
- review: ['ready', 'blocked', 'done'],
9
- blocked: ['ready', 'done'],
10
- done: ['review'],
4
+ active: ['blocked', 'done'],
5
+ blocked: ['active', 'done'],
6
+ done: ['active'],
11
7
  };
12
8
  export function allowedTaskStatusTransitions(fromStatus) {
13
9
  return TASK_STATUS_TRANSITIONS[fromStatus];
@@ -1,148 +1,179 @@
1
1
  import { z } from 'zod';
2
- export const TASK_PACKET_VERSION = '1.0';
2
+ import { nowIso } from '../lib/format.js';
3
+ export const TASK_PACKET_VERSION = '2.0';
3
4
  const taskIdSchema = z.string().regex(/^T-\d{3,}$/, 'Task id must look like T-001');
4
- export const taskPacketStatusSchema = z.enum(['draft', 'ready', 'queued', 'running', 'review', 'blocked', 'done']);
5
+ export const taskPacketStatusSchema = z.enum(['active', 'blocked', 'done']);
5
6
  export const taskPacketPrioritySchema = z.enum(['low', 'medium', 'high']);
6
- export const taskPacketTypeSchema = z.enum(['feature', 'bug', 'chore', 'research']);
7
- export const taskAgentRoleSchema = z.enum(['planner', 'builder-ui', 'builder-app', 'reviewer', 'tester']);
8
- export const taskRunnerSchema = z.enum(['codex', 'claude']);
9
- export const validationKindSchema = z.enum(['typecheck', 'lint', 'test', 'build', 'custom']);
10
- // Accept string entries ("npm test") or object entries ({kind,command,...}). String entries
11
- // are promoted to { kind: 'custom', command }. This preserves the v1 packet shape while
12
- // giving new packets first-class typed validation steps.
13
- export const validationStepSchema = z.preprocess((raw) => {
14
- if (typeof raw === 'string') {
15
- return { kind: 'custom', command: raw };
16
- }
17
- return raw;
18
- }, z
7
+ export const taskTriggerSchema = z
19
8
  .object({
20
- kind: validationKindSchema.default('custom'),
21
- command: z.string().min(1, 'validation command is required'),
22
- name: z.string().optional(),
23
- timeout_ms: z.number().int().positive().optional(),
9
+ condition: z.string().min(1, 'trigger condition is required'),
10
+ check_command: z.string().min(1).optional(),
11
+ depends_on: z.array(taskIdSchema).default([]),
24
12
  })
25
- .strict());
26
- export const taskPacketSchema = z
13
+ .strict();
14
+ export const taskResultSchema = z
15
+ .object({
16
+ summary: z.string().default(''),
17
+ changed_files: z.array(z.string()).default([]),
18
+ validation_output: z.string().default(''),
19
+ validated_at: z.string().datetime({ offset: true }).nullable().default(null),
20
+ })
21
+ .strict();
22
+ function normalizeLegacyStatus(value) {
23
+ if (value === 'open')
24
+ return 'active';
25
+ if (value === 'in_progress')
26
+ return 'active';
27
+ if (value === 'draft' || value === 'ready' || value === 'queued' || value === 'running' || value === 'review')
28
+ return 'active';
29
+ return value;
30
+ }
31
+ function normalizeLegacyPacket(raw) {
32
+ if (!raw || typeof raw !== 'object')
33
+ return raw;
34
+ const packet = raw;
35
+ const entryFromLegacy = (() => {
36
+ const implementation = packet.implementation;
37
+ const files = implementation?.files_to_read;
38
+ if (Array.isArray(files)) {
39
+ return files
40
+ .map((v) => (typeof v === 'string' ? v.trim() : ''))
41
+ .filter((v) => v.length > 0)
42
+ .slice(0, 3);
43
+ }
44
+ return [];
45
+ })();
46
+ const doneFromLegacy = (() => {
47
+ const dod = packet.definition_of_done;
48
+ if (Array.isArray(dod) && dod.length > 0) {
49
+ const first = dod.find((v) => typeof v === 'string' && v.trim().length > 0);
50
+ if (typeof first === 'string')
51
+ return first.trim();
52
+ }
53
+ const goal = typeof packet.goal === 'string' ? packet.goal.trim() : '';
54
+ return goal;
55
+ })();
56
+ const validationFromLegacy = (() => {
57
+ const execution = packet.execution;
58
+ const commands = execution?.commands;
59
+ const validation = commands?.validation;
60
+ if (Array.isArray(validation) && validation.length > 0) {
61
+ const first = validation[0];
62
+ if (typeof first === 'string')
63
+ return first.trim();
64
+ if (first && typeof first === 'object') {
65
+ const command = first.command;
66
+ if (typeof command === 'string')
67
+ return command.trim();
68
+ }
69
+ }
70
+ return '';
71
+ })();
72
+ const dependenciesFromLegacy = (() => {
73
+ const deps = packet.dependencies;
74
+ if (Array.isArray(deps)) {
75
+ return deps.filter((v) => typeof v === 'string' && /^T-\d{3,}$/.test(v));
76
+ }
77
+ return [];
78
+ })();
79
+ const baseSummary = typeof packet.summary === 'string' ? packet.summary.trim() : '';
80
+ const baseGoal = typeof packet.goal === 'string' ? packet.goal.trim() : '';
81
+ const baseTitle = typeof packet.title === 'string' ? packet.title.trim() : '';
82
+ const resultLegacy = (() => {
83
+ const result = packet.result;
84
+ if (!result || typeof result !== 'object')
85
+ return undefined;
86
+ const validationResults = Array.isArray(result.validation_results)
87
+ ? result.validation_results.filter((v) => typeof v === 'string')
88
+ : [];
89
+ return {
90
+ summary: typeof result.summary === 'string' ? result.summary : '',
91
+ changed_files: Array.isArray(result.changed_files)
92
+ ? result.changed_files.filter((v) => typeof v === 'string')
93
+ : [],
94
+ validation_output: validationResults.join('\n'),
95
+ validated_at: null,
96
+ };
97
+ })();
98
+ return {
99
+ version: typeof packet.version === 'string' ? packet.version : TASK_PACKET_VERSION,
100
+ task_id: packet.task_id,
101
+ title: baseTitle,
102
+ summary: baseSummary || baseGoal || baseTitle,
103
+ priority: packet.priority,
104
+ status: normalizeLegacyStatus(packet.status),
105
+ created_at: packet.created_at,
106
+ updated_at: packet.updated_at,
107
+ trigger: packet.trigger ?? {
108
+ condition: dependenciesFromLegacy.length > 0
109
+ ? `After dependencies are done: ${dependenciesFromLegacy.join(', ')}`
110
+ : 'Set explicit trigger before execution.',
111
+ depends_on: dependenciesFromLegacy,
112
+ },
113
+ entry_points: packet.entry_points ?? entryFromLegacy,
114
+ done_condition: packet.done_condition ?? doneFromLegacy,
115
+ validation_command: packet.validation_command ?? validationFromLegacy,
116
+ ...(resultLegacy ? { result: resultLegacy } : {}),
117
+ };
118
+ }
119
+ const taskPacketShapeSchema = z
27
120
  .object({
28
121
  version: z.string().default(TASK_PACKET_VERSION),
29
122
  task_id: taskIdSchema,
30
123
  title: z.string().min(1, 'title is required'),
31
- type: taskPacketTypeSchema.default('chore'),
32
- status: z
33
- .preprocess((value) => {
34
- if (value === 'open')
35
- return 'draft';
36
- if (value === 'in_progress')
37
- return 'running';
38
- return value;
39
- }, taskPacketStatusSchema)
40
- .default('draft'),
41
- priority: taskPacketPrioritySchema.default('medium'),
42
124
  summary: z.string().min(1, 'summary is required'),
43
- goal: z.string().min(1, 'goal is required'),
44
- scope: z.object({
45
- in_scope: z.array(z.string()).default([]),
46
- out_of_scope: z.array(z.string()).default([]),
47
- }),
48
- context: z.object({
49
- related_decisions: z.array(z.string()).default([]),
50
- related_notes: z.array(z.string()).default([]),
51
- }),
52
- implementation: z.object({
53
- files_to_read: z.array(z.string()).default([]),
54
- files_to_avoid: z.array(z.string()).default([]),
55
- }),
56
- constraints: z.object({
57
- technical: z.array(z.string()).default([]),
58
- design: z.array(z.string()).default([]),
59
- }),
60
- execution: z.object({
61
- commands: z.object({
62
- validation: z.array(validationStepSchema).default([]),
63
- }),
64
- }),
65
- dependencies: z.array(taskIdSchema).default([]),
66
- tags: z.array(z.string()).default([]),
67
- target_areas: z.array(z.string()).default([]),
68
- definition_of_done: z.array(z.string()).default([]),
69
- tracking: z.object({
70
- branch: z.string().default(''),
71
- worktree: z.string().default(''),
72
- assigned_agent_role: taskAgentRoleSchema.nullable().default(null),
73
- assigned_runner: taskRunnerSchema.nullable().default(null),
74
- assignment_reason: z.string().default(''),
75
- assigned_at: z.string().datetime({ offset: true }).nullable().default(null),
76
- }),
77
- result: z.object({
78
- summary: z.string().default(''),
79
- changed_files: z.array(z.string()).default([]),
80
- validation_results: z.array(z.string()).default([]),
125
+ priority: taskPacketPrioritySchema.default('medium'),
126
+ status: z.preprocess(normalizeLegacyStatus, taskPacketStatusSchema).default('active'),
127
+ created_at: z.string().datetime({ offset: true }).default(() => nowIso()),
128
+ updated_at: z.string().datetime({ offset: true }).default(() => nowIso()),
129
+ trigger: taskTriggerSchema,
130
+ entry_points: z
131
+ .array(z.string().min(1))
132
+ .min(1, 'at least one entry point is required')
133
+ .max(3, 'entry points must be 1-3 files'),
134
+ done_condition: z.string().min(1, 'done condition is required'),
135
+ validation_command: z.string().min(1, 'validation command is required'),
136
+ result: taskResultSchema.default({
137
+ summary: '',
138
+ changed_files: [],
139
+ validation_output: '',
140
+ validated_at: null,
81
141
  }),
82
142
  })
83
143
  .strict();
84
- export const taskPacketInputSchema = taskPacketSchema.omit({ task_id: true }).partial({
144
+ export const taskPacketSchema = z.preprocess(normalizeLegacyPacket, taskPacketShapeSchema);
145
+ export const taskPacketInputSchema = taskPacketShapeSchema.omit({ task_id: true }).partial({
85
146
  version: true,
86
- type: true,
87
- status: true,
88
147
  priority: true,
89
- scope: true,
90
- context: true,
91
- implementation: true,
92
- constraints: true,
93
- execution: true,
94
- dependencies: true,
95
- tags: true,
96
- target_areas: true,
97
- definition_of_done: true,
98
- tracking: true,
148
+ status: true,
149
+ created_at: true,
150
+ updated_at: true,
99
151
  result: true,
100
152
  });
101
153
  export function createTaskPacket(taskId, input) {
154
+ const now = nowIso();
102
155
  const normalized = {
103
- ...input,
104
156
  version: input.version ?? TASK_PACKET_VERSION,
105
157
  task_id: taskId,
106
- type: input.type ?? 'chore',
107
- status: input.status ?? 'draft',
158
+ title: input.title,
159
+ summary: input.summary,
108
160
  priority: input.priority ?? 'medium',
109
- scope: {
110
- in_scope: input.scope?.in_scope ?? [],
111
- out_of_scope: input.scope?.out_of_scope ?? [],
112
- },
113
- context: {
114
- related_decisions: input.context?.related_decisions ?? [],
115
- related_notes: input.context?.related_notes ?? [],
116
- },
117
- implementation: {
118
- files_to_read: input.implementation?.files_to_read ?? [],
119
- files_to_avoid: input.implementation?.files_to_avoid ?? [],
120
- },
121
- constraints: {
122
- technical: input.constraints?.technical ?? [],
123
- design: input.constraints?.design ?? [],
124
- },
125
- execution: {
126
- commands: {
127
- validation: input.execution?.commands?.validation ?? [],
128
- },
129
- },
130
- dependencies: input.dependencies ?? [],
131
- tags: input.tags ?? [],
132
- target_areas: input.target_areas ?? [],
133
- definition_of_done: input.definition_of_done ?? [],
134
- tracking: {
135
- branch: input.tracking?.branch ?? '',
136
- worktree: input.tracking?.worktree ?? '',
137
- assigned_agent_role: input.tracking?.assigned_agent_role ?? null,
138
- assigned_runner: input.tracking?.assigned_runner ?? null,
139
- assignment_reason: input.tracking?.assignment_reason ?? '',
140
- assigned_at: input.tracking?.assigned_at ?? null,
161
+ status: input.status ?? 'active',
162
+ created_at: input.created_at ?? now,
163
+ updated_at: input.updated_at ?? now,
164
+ trigger: {
165
+ condition: input.trigger?.condition ?? '',
166
+ ...(input.trigger?.check_command ? { check_command: input.trigger.check_command } : {}),
167
+ depends_on: input.trigger?.depends_on ?? [],
141
168
  },
169
+ entry_points: input.entry_points ?? [],
170
+ done_condition: input.done_condition ?? '',
171
+ validation_command: input.validation_command ?? '',
142
172
  result: {
143
173
  summary: input.result?.summary ?? '',
144
174
  changed_files: input.result?.changed_files ?? [],
145
- validation_results: input.result?.validation_results ?? [],
175
+ validation_output: input.result?.validation_output ?? '',
176
+ validated_at: input.result?.validated_at ?? null,
146
177
  },
147
178
  };
148
179
  return taskPacketSchema.parse(normalized);