@kodrunhq/opencode-autopilot 1.14.0 → 1.14.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodrunhq/opencode-autopilot",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -1,14 +1,119 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isEnoentError } from "../../utils/fs-helpers";
|
|
1
3
|
import { getArtifactRef } from "../artifacts";
|
|
4
|
+
import { taskSchema } from "../schemas";
|
|
5
|
+
import type { Task } from "../types";
|
|
2
6
|
import type { DispatchResult, PhaseHandler } from "./types";
|
|
3
7
|
import { AGENT_NAMES } from "./types";
|
|
4
8
|
|
|
9
|
+
const EXPECTED_COLUMN_COUNT = 6;
|
|
10
|
+
const taskIdPattern = /^W(\d+)-T(\d+)$/i;
|
|
11
|
+
const separatorCellPattern = /^:?-{3,}:?$/;
|
|
12
|
+
|
|
13
|
+
function parseTableColumns(line: string): readonly string[] | null {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (!trimmed.includes("|")) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const withoutLeadingBoundary = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
20
|
+
const normalized = withoutLeadingBoundary.endsWith("|")
|
|
21
|
+
? withoutLeadingBoundary.slice(0, -1)
|
|
22
|
+
: withoutLeadingBoundary;
|
|
23
|
+
|
|
24
|
+
return normalized.split("|").map((col) => col.trim());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isSeparatorRow(columns: readonly string[]): boolean {
|
|
28
|
+
return columns.length > 0 && columns.every((col) => separatorCellPattern.test(col));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse tasks from markdown table in tasks.md.
|
|
33
|
+
* Expected format: | Task ID | Title | Description | Files | Wave | Criteria |
|
|
34
|
+
* Returns array of Task objects.
|
|
35
|
+
*/
|
|
36
|
+
async function loadTasksFromMarkdown(tasksPath: string): Promise<Task[]> {
|
|
37
|
+
const content = await readFile(tasksPath, "utf-8");
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
|
|
40
|
+
const tasks: Task[] = [];
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const columns = parseTableColumns(line);
|
|
43
|
+
if (columns === null || columns.length < EXPECTED_COLUMN_COUNT || isSeparatorRow(columns)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (columns[0].toLowerCase() === "task id") {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const idMatch = taskIdPattern.exec(columns[0]);
|
|
52
|
+
if (idMatch === null) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const waveFromId = Number.parseInt(idMatch[1], 10);
|
|
57
|
+
const title = columns[1];
|
|
58
|
+
const waveFromColumn = Number.parseInt(columns[4], 10);
|
|
59
|
+
|
|
60
|
+
if (!title || Number.isNaN(waveFromId) || Number.isNaN(waveFromColumn)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (waveFromId !== waveFromColumn) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
tasks.push(
|
|
69
|
+
taskSchema.parse({
|
|
70
|
+
id: tasks.length + 1,
|
|
71
|
+
title,
|
|
72
|
+
status: "PENDING",
|
|
73
|
+
wave: waveFromColumn,
|
|
74
|
+
depends_on: [],
|
|
75
|
+
attempt: 0,
|
|
76
|
+
strike: 0,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (tasks.length === 0) {
|
|
82
|
+
throw new Error("No valid task rows found in PLAN tasks.md");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return tasks;
|
|
86
|
+
}
|
|
87
|
+
|
|
5
88
|
export const handlePlan: PhaseHandler = async (_state, artifactDir, result?) => {
|
|
89
|
+
// When result is provided, the planner has completed writing tasks
|
|
90
|
+
// Load them from tasks.md and populate state.tasks
|
|
6
91
|
if (result) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
92
|
+
const tasksPath = getArtifactRef(artifactDir, "PLAN", "tasks.md");
|
|
93
|
+
try {
|
|
94
|
+
const loadedTasks = await loadTasksFromMarkdown(tasksPath);
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
action: "complete",
|
|
97
|
+
phase: "PLAN",
|
|
98
|
+
progress: `Planning complete — loaded ${loadedTasks.length} task(s)`,
|
|
99
|
+
_stateUpdates: {
|
|
100
|
+
tasks: loadedTasks,
|
|
101
|
+
},
|
|
102
|
+
} satisfies DispatchResult);
|
|
103
|
+
} catch (error: unknown) {
|
|
104
|
+
const reason = isEnoentError(error)
|
|
105
|
+
? "tasks.md not found after planner completion"
|
|
106
|
+
: error instanceof Error
|
|
107
|
+
? error.message
|
|
108
|
+
: "Unknown parsing error";
|
|
109
|
+
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
action: "error",
|
|
112
|
+
phase: "PLAN",
|
|
113
|
+
message: `Failed to load PLAN tasks: ${reason}`,
|
|
114
|
+
progress: "Planning failed — task extraction error",
|
|
115
|
+
} satisfies DispatchResult);
|
|
116
|
+
}
|
|
12
117
|
}
|
|
13
118
|
|
|
14
119
|
const architectRef = getArtifactRef(artifactDir, "ARCHITECT", "design.md");
|