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
|
@@ -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 ??
|
|
48
|
-
const agentRole = input.agentRole ??
|
|
49
|
-
const worktree =
|
|
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: '
|
|
77
|
+
saveTaskPacket(input.rootPath, { ...task, status: 'active' });
|
|
78
78
|
updateRunRecordEntry(input.rootPath, compiled.run_id, {
|
|
79
79
|
status: 'running',
|
|
80
|
-
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
|
|
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 ? '
|
|
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 '
|
|
7
|
+
return 'active';
|
|
8
8
|
if (state === 'needs_changes')
|
|
9
|
-
return '
|
|
9
|
+
return 'active';
|
|
10
10
|
if (state === 'blocked')
|
|
11
11
|
return 'blocked';
|
|
12
12
|
if (state === 'merged')
|
|
13
13
|
return 'done';
|
|
14
|
-
return '
|
|
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
|
-
|
|
45
|
-
type: sourceTask.type,
|
|
46
|
-
status: 'draft',
|
|
49
|
+
status: 'active',
|
|
47
50
|
priority: sourceTask.priority,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 {
|
|
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.
|
|
16
|
-
return { role: '
|
|
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.
|
|
21
|
-
return { role: 'reviewer', reason: '
|
|
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
|
|
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 !== '
|
|
68
|
+
if (task.status !== 'active')
|
|
61
69
|
continue;
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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(['
|
|
5
|
+
export const taskPacketStatusSchema = z.enum(['active', 'blocked', 'done']);
|
|
5
6
|
export const taskPacketPrioritySchema = z.enum(['low', 'medium', 'high']);
|
|
6
|
-
export const
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
107
|
-
|
|
158
|
+
title: input.title,
|
|
159
|
+
summary: input.summary,
|
|
108
160
|
priority: input.priority ?? 'medium',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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);
|