@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.0",
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
- return Object.freeze({
8
- action: "complete",
9
- phase: "PLAN",
10
- progress: "Planning complete — tasks written",
11
- } satisfies DispatchResult);
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");