agentplane 0.1.3 → 0.1.5

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.
Files changed (86) hide show
  1. package/README.md +51 -30
  2. package/dist/agents/agents-template.d.ts.map +1 -0
  3. package/dist/{agents-template.js → agents/agents-template.js} +2 -2
  4. package/dist/{task-backend.d.ts → backends/task-backend.d.ts} +5 -3
  5. package/dist/backends/task-backend.d.ts.map +1 -0
  6. package/dist/{task-backend.js → backends/task-backend.js} +139 -156
  7. package/dist/backends/task-index.d.ts +16 -0
  8. package/dist/backends/task-index.d.ts.map +1 -0
  9. package/dist/backends/task-index.js +84 -0
  10. package/dist/cli/archive.d.ts +16 -0
  11. package/dist/cli/archive.d.ts.map +1 -0
  12. package/dist/cli/archive.js +149 -0
  13. package/dist/cli/checksum.d.ts +3 -0
  14. package/dist/cli/checksum.d.ts.map +1 -0
  15. package/dist/cli/checksum.js +12 -0
  16. package/dist/cli/command-guide.d.ts.map +1 -0
  17. package/dist/cli/error-map.d.ts +4 -0
  18. package/dist/cli/error-map.d.ts.map +1 -0
  19. package/dist/cli/error-map.js +42 -0
  20. package/dist/cli/exit-codes.d.ts +3 -0
  21. package/dist/cli/exit-codes.d.ts.map +1 -0
  22. package/dist/cli/exit-codes.js +12 -0
  23. package/dist/cli/help.d.ts.map +1 -0
  24. package/dist/{help.js → cli/help.js} +1 -1
  25. package/dist/cli/http.d.ts +4 -0
  26. package/dist/cli/http.d.ts.map +1 -0
  27. package/dist/cli/http.js +95 -0
  28. package/dist/cli/output.d.ts +16 -0
  29. package/dist/cli/output.d.ts.map +1 -0
  30. package/dist/cli/output.js +47 -0
  31. package/dist/cli/recipes-bundled.js +2 -2
  32. package/dist/cli/run-cli.d.ts.map +1 -0
  33. package/dist/cli/run-cli.js +2681 -0
  34. package/dist/{run-cli.test-helpers.d.ts → cli/run-cli.test-helpers.d.ts} +6 -0
  35. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -0
  36. package/dist/{run-cli.test-helpers.js → cli/run-cli.test-helpers.js} +67 -0
  37. package/dist/cli/update-check.d.ts +31 -0
  38. package/dist/cli/update-check.d.ts.map +1 -0
  39. package/dist/cli/update-check.js +86 -0
  40. package/dist/cli.js +1 -1
  41. package/dist/commands/backend.d.ts +15 -0
  42. package/dist/commands/backend.d.ts.map +1 -0
  43. package/dist/commands/backend.js +211 -0
  44. package/dist/commands/recipes.d.ts +13 -0
  45. package/dist/commands/recipes.d.ts.map +1 -0
  46. package/dist/commands/recipes.js +1919 -0
  47. package/dist/commands/upgrade.d.ts +6 -0
  48. package/dist/commands/upgrade.d.ts.map +1 -0
  49. package/dist/commands/upgrade.js +291 -0
  50. package/dist/commands/workflow.d.ts +367 -0
  51. package/dist/commands/workflow.d.ts.map +1 -0
  52. package/dist/commands/workflow.js +4619 -0
  53. package/dist/meta/version.d.ts.map +1 -0
  54. package/dist/meta/version.js +20 -0
  55. package/dist/recipes/bundled-recipes.d.ts.map +1 -0
  56. package/dist/shared/comment-format.d.ts.map +1 -0
  57. package/dist/shared/env.d.ts.map +1 -0
  58. package/dist/{errors.d.ts → shared/errors.d.ts} +1 -3
  59. package/dist/shared/errors.d.ts.map +1 -0
  60. package/dist/{errors.js → shared/errors.js} +1 -3
  61. package/package.json +6 -2
  62. package/dist/agents-template.d.ts.map +0 -1
  63. package/dist/bundled-recipes.d.ts.map +0 -1
  64. package/dist/command-guide.d.ts.map +0 -1
  65. package/dist/comment-format.d.ts.map +0 -1
  66. package/dist/env.d.ts.map +0 -1
  67. package/dist/errors.d.ts.map +0 -1
  68. package/dist/help.d.ts.map +0 -1
  69. package/dist/run-cli.d.ts.map +0 -1
  70. package/dist/run-cli.js +0 -9431
  71. package/dist/run-cli.test-helpers.d.ts.map +0 -1
  72. package/dist/task-backend.d.ts.map +0 -1
  73. package/dist/version.d.ts.map +0 -1
  74. package/dist/version.js +0 -3
  75. /package/dist/{agents-template.d.ts → agents/agents-template.d.ts} +0 -0
  76. /package/dist/{command-guide.d.ts → cli/command-guide.d.ts} +0 -0
  77. /package/dist/{command-guide.js → cli/command-guide.js} +0 -0
  78. /package/dist/{help.d.ts → cli/help.d.ts} +0 -0
  79. /package/dist/{run-cli.d.ts → cli/run-cli.d.ts} +0 -0
  80. /package/dist/{version.d.ts → meta/version.d.ts} +0 -0
  81. /package/dist/{bundled-recipes.d.ts → recipes/bundled-recipes.d.ts} +0 -0
  82. /package/dist/{bundled-recipes.js → recipes/bundled-recipes.js} +0 -0
  83. /package/dist/{comment-format.d.ts → shared/comment-format.d.ts} +0 -0
  84. /package/dist/{comment-format.js → shared/comment-format.js} +0 -0
  85. /package/dist/{env.d.ts → shared/env.d.ts} +0 -0
  86. /package/dist/{env.js → shared/env.js} +0 -0
package/README.md CHANGED
@@ -5,53 +5,83 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/basilisk-labs/agentplane/blob/main/LICENSE)
6
6
  [![Node.js 20+](https://img.shields.io/badge/Node.js-20%2B-3c873a.svg)](https://github.com/basilisk-labs/agentplane/blob/main/docs/user/prerequisites.mdx)
7
7
 
8
- Agent Plane is an offline-first CLI for running policy-driven agent workflows inside real repositories.
9
- It turns "AI magic" into a predictable process with approvals, role boundaries, and audit-friendly artifacts.
8
+ Agent Plane is a policy-driven framework for running LLM agents inside real repositories. It turns "AI magic" into an engineering process: explicit approvals, role boundaries, and a reproducible execution pipeline. The goal is simple: make agents boring, safe, and auditable.
10
9
 
11
- ## Features
10
+ ## Why Agent Plane
12
11
 
13
- - Policy-first execution with explicit approvals and guardrails.
14
- - Role-based workflows for teams: ORCHESTRATOR, PLANNER, CREATOR, INTEGRATOR, etc.
15
- - Two workflow modes: `direct` (single checkout) and `branch_pr` (worktrees + integration).
16
- - Task tracking, verification, and exports baked in.
17
- - Recipes for repeatable setup and automation.
12
+ - You want agents that behave predictably inside real repos.
13
+ - You need human approvals, clear roles, and traceable artifacts.
14
+ - You want guardrails by default, not optional add-ons.
15
+ - You want an offline-first CLI that keeps state local and inspectable.
18
16
 
19
- ## Install
17
+ ## 5-minute start
18
+
19
+ Install and initialize the CLI:
20
20
 
21
21
  ```bash
22
22
  npm install -g agentplane
23
+ agentplane init
24
+ agentplane quickstart
23
25
  ```
24
26
 
25
- Or run without installing:
27
+ Create your first task and run the workflow:
26
28
 
27
29
  ```bash
28
- npx agentplane --help
30
+ agentplane task new --title "First task" --description "Describe the change" --priority med --owner ORCHESTRATOR --tag docs
31
+ agentplane verify <task-id>
32
+ agentplane finish <task-id>
29
33
  ```
30
34
 
31
- ## Requirements
35
+ Prefer `npx` instead of a global install?
32
36
 
33
- - Node.js >= 20
37
+ ```bash
38
+ npx agentplane init
39
+ npx agentplane quickstart
40
+ ```
34
41
 
35
- ## Quickstart
42
+ ## What gets installed automatically
36
43
 
37
- Initialize a repository:
44
+ - `.agentplane/` is created with config, tasks, agents, and caches.
45
+ - `AGENTS.md` is created if missing and defines the policy/guardrails.
46
+ - Built-in agent definitions are copied into `.agentplane/agents/`.
47
+ - Optional recipes can install additional agents when you run `agentplane recipes install ...`.
38
48
 
39
- ```bash
40
- npx agentplane init
49
+ ## Guardrails and artifacts
50
+
51
+ - Approval gates for plans and network access (configured in `.agentplane/config.json`).
52
+ - Role boundaries (ORCHESTRATOR, PLANNER, CODER, INTEGRATOR, etc.).
53
+ - Agent definitions in `.agentplane/agents/`.
54
+ - Task records in `.agentplane/tasks/` with a snapshot export in `.agentplane/tasks.json`.
55
+ - A visible, reproducible pipeline:
56
+
57
+ ```text
58
+ Preflight -> Plan -> Approval -> Tasks -> Verify -> Finish -> Export
41
59
  ```
42
60
 
43
- See the built-in quickstart guide:
61
+ ## Features
62
+
63
+ - Policy-first execution with explicit approvals and guardrails.
64
+ - Role-based workflows for teams: ORCHESTRATOR, PLANNER, CREATOR, INTEGRATOR, etc.
65
+ - Two workflow modes: `direct` (single checkout) and `branch_pr` (worktrees + integration).
66
+ - Task tracking, verification, and exports baked in.
67
+ - Recipes for repeatable setup and automation.
68
+
69
+ ## Install
44
70
 
45
71
  ```bash
46
- npx agentplane quickstart
72
+ npm install -g agentplane
47
73
  ```
48
74
 
49
- Switch workflow mode if you need a structured team flow:
75
+ Or run without installing:
50
76
 
51
77
  ```bash
52
- npx agentplane config set workflow_mode branch_pr
78
+ npx agentplane --help
53
79
  ```
54
80
 
81
+ ## Requirements
82
+
83
+ - Node.js >= 20
84
+
55
85
  ## Common Commands
56
86
 
57
87
  ```bash
@@ -73,15 +103,6 @@ agentplane recipes list
73
103
  - Project layout: https://github.com/basilisk-labs/agentplane/blob/main/docs/developer/project-layout.mdx
74
104
  - Recipes: https://github.com/basilisk-labs/agentplane/tree/main/agentplane-recipes
75
105
 
76
- ## How it works
77
-
78
- Agent Plane expects a repository policy file (`AGENTS.md`) plus a project config (`.agentplane/config.json`).
79
- Together, they define role boundaries, approval gates, and the execution pipeline:
80
-
81
- ```text
82
- Preflight -> Plan -> Approval -> Tasks -> Verify -> Finish -> Export
83
- ```
84
-
85
106
  ## Support
86
107
 
87
108
  - Issues: https://github.com/basilisk-labs/agentplane/issues
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../../src/agents/agents-template.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElD,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAwC5D,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAG1D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAanE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CASvF"}
@@ -1,8 +1,8 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- const AGENTS_TEMPLATE_URL = new URL("../assets/AGENTS.md", import.meta.url);
5
- const AGENTS_DIR_URL = new URL("../assets/agents/", import.meta.url);
4
+ const AGENTS_TEMPLATE_URL = new URL("../../assets/AGENTS.md", import.meta.url);
5
+ const AGENTS_DIR_URL = new URL("../../assets/agents/", import.meta.url);
6
6
  const HEADING_RE = /^(#+)\s+(.*)$/;
7
7
  function ensureTrailingNewline(text) {
8
8
  return text.endsWith("\n") ? text : `${text}\n`;
@@ -1,6 +1,9 @@
1
1
  import { type AgentplaneConfig, type ResolvedProject, type TaskRecord } from "@agentplaneorg/core";
2
- export declare function extractTaskDoc(body: string): string;
3
- export declare function mergeTaskDoc(body: string, doc: string): string;
2
+ type ExtractTaskDoc = (body: string) => string;
3
+ type MergeTaskDoc = (body: string, doc: string) => string;
4
+ declare const extractTaskDoc: ExtractTaskDoc;
5
+ declare const mergeTaskDoc: MergeTaskDoc;
6
+ export { extractTaskDoc, mergeTaskDoc };
4
7
  export type TaskData = {
5
8
  id: string;
6
9
  title: string;
@@ -171,5 +174,4 @@ export declare function loadTaskBackend(opts: {
171
174
  config: AgentplaneConfig;
172
175
  backendConfigPath: string;
173
176
  }>;
174
- export {};
175
177
  //# sourceMappingURL=task-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-backend.d.ts","sourceRoot":"","sources":["../../src/backends/task-backend.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,16 @@
1
- import { createHash, randomInt } from "node:crypto";
2
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readdir, readFile, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { canonicalizeJson, loadConfig, parseTaskReadme, renderTaskReadme, resolveProject, taskReadmePath, } from "@agentplaneorg/core";
5
- import { loadDotEnv } from "./env.js";
6
- const ID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
7
- const TASK_ID_RE = new RegExp(String.raw `^\d{12}-[${ID_ALPHABET}]{4,}$`);
8
- const DOC_SECTION_HEADER = "## Summary";
9
- const DOC_SECTION_HEADER_RE = /^##\s+Summary(?:\s|$|#)/;
10
- const AUTO_SUMMARY_HEADER = "## Changes Summary (auto)";
4
+ import { atomicWriteFile as atomicWriteFileCore, canonicalizeJson, docChanged, extractTaskDoc as extractTaskDocCore, generateTaskId as generateTaskIdCore, loadConfig, mergeTaskDoc as mergeTaskDocCore, parseTaskReadme, renderTaskReadme, resolveProject, TASK_ID_ALPHABET, taskReadmePath, } from "@agentplaneorg/core";
5
+ import { loadDotEnv } from "../shared/env.js";
6
+ import { buildTaskIndexEntry, loadTaskIndex, resolveTaskIndexPath, saveTaskIndex, } from "./task-index.js";
7
+ const TASK_ID_RE = new RegExp(String.raw `^\d{12}-[${TASK_ID_ALPHABET}]{4,}$`);
11
8
  const DEFAULT_DOC_UPDATED_BY = "agentplane";
12
9
  const DOC_VERSION = 2;
10
+ const atomicWriteFile = atomicWriteFileCore;
11
+ const extractTaskDoc = extractTaskDocCore;
12
+ const mergeTaskDoc = mergeTaskDocCore;
13
+ const generateTaskId = generateTaskIdCore;
13
14
  function nowIso() {
14
15
  return new Date().toISOString();
15
16
  }
@@ -35,20 +36,26 @@ function firstNonEmptyString(...values) {
35
36
  function sleep(ms) {
36
37
  return new Promise((resolve) => setTimeout(resolve, ms));
37
38
  }
38
- function normalizeDoc(text) {
39
- return (text ?? "")
40
- .split("\n")
41
- .map((line) => line.replaceAll(/\s+$/gu, ""))
42
- .join("\n")
43
- .trim();
44
- }
45
- function docChanged(existing, updated) {
46
- return normalizeDoc(existing) !== normalizeDoc(updated);
39
+ function normalizeUpdatedBy(value) {
40
+ if (typeof value !== "string")
41
+ return "";
42
+ const trimmed = value.trim();
43
+ if (!trimmed)
44
+ return "";
45
+ if (trimmed.toLowerCase() === DEFAULT_DOC_UPDATED_BY.toLowerCase())
46
+ return "";
47
+ return trimmed;
47
48
  }
48
49
  function ensureDocMetadata(task, updatedBy) {
49
50
  task.doc_version = DOC_VERSION;
50
51
  task.doc_updated_at = nowIso();
51
- task.doc_updated_by = updatedBy ?? DEFAULT_DOC_UPDATED_BY;
52
+ const explicit = normalizeUpdatedBy(updatedBy);
53
+ if (updatedBy !== undefined) {
54
+ task.doc_updated_by =
55
+ explicit || resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY);
56
+ return;
57
+ }
58
+ task.doc_updated_by = resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY);
52
59
  }
53
60
  function lastCommentAuthor(comments) {
54
61
  if (!Array.isArray(comments))
@@ -69,92 +76,36 @@ function lastCommentAuthor(comments) {
69
76
  }
70
77
  function resolveDocUpdatedByFromFrontmatter(frontmatter, updatedBy, fallback) {
71
78
  if (updatedBy !== undefined) {
72
- const trimmed = updatedBy.trim();
73
- return trimmed.length > 0 ? trimmed : fallback;
79
+ const explicit = normalizeUpdatedBy(updatedBy);
80
+ if (explicit)
81
+ return explicit;
74
82
  }
75
83
  const author = lastCommentAuthor(frontmatter.comments);
76
84
  if (author)
77
85
  return author;
78
- const existing = frontmatter.doc_updated_by;
79
- if (typeof existing === "string") {
80
- const trimmed = existing.trim();
81
- if (trimmed)
82
- return trimmed;
83
- }
84
- return fallback;
86
+ const existing = normalizeUpdatedBy(frontmatter.doc_updated_by);
87
+ if (existing)
88
+ return existing;
89
+ const owner = normalizeUpdatedBy(frontmatter.owner);
90
+ if (owner)
91
+ return owner;
92
+ const fallbackValue = normalizeUpdatedBy(fallback);
93
+ return fallbackValue || fallback;
85
94
  }
86
95
  function resolveDocUpdatedByFromTask(task, fallback) {
87
96
  const author = lastCommentAuthor(task.comments);
88
97
  if (author)
89
98
  return author;
90
- const existing = task.doc_updated_by;
91
- if (typeof existing === "string") {
92
- const trimmed = existing.trim();
93
- if (trimmed)
94
- return trimmed;
95
- }
96
- return fallback;
97
- }
98
- function isDocSectionHeader(line) {
99
- return DOC_SECTION_HEADER_RE.test(line.trim());
100
- }
101
- export function extractTaskDoc(body) {
102
- if (!body)
103
- return "";
104
- const lines = body.split("\n");
105
- let startIdx = null;
106
- for (const [idx, line] of lines.entries()) {
107
- if (isDocSectionHeader(line)) {
108
- startIdx = idx;
109
- break;
110
- }
111
- }
112
- if (startIdx === null)
113
- return "";
114
- if (lines[startIdx]?.trim() !== DOC_SECTION_HEADER) {
115
- lines[startIdx] = DOC_SECTION_HEADER;
116
- }
117
- let endIdx = lines.length;
118
- for (let idx = startIdx + 1; idx < lines.length; idx++) {
119
- if (lines[idx]?.trim() === AUTO_SUMMARY_HEADER) {
120
- endIdx = idx;
121
- break;
122
- }
123
- }
124
- return lines.slice(startIdx, endIdx).join("\n").trimEnd();
125
- }
126
- export function mergeTaskDoc(body, doc) {
127
- const docText = String(doc ?? "").replaceAll(/^\n+|\n+$/g, "");
128
- if (docText) {
129
- const lines = body ? body.split("\n") : [];
130
- let prefixIdx = null;
131
- for (const [idx, line] of lines.entries()) {
132
- if (isDocSectionHeader(line)) {
133
- prefixIdx = idx;
134
- break;
135
- }
136
- }
137
- const prefixText = prefixIdx === null ? "" : lines.slice(0, prefixIdx).join("\n").trimEnd();
138
- let autoIdx = null;
139
- for (const [idx, line] of lines.entries()) {
140
- if (line.trim() === AUTO_SUMMARY_HEADER) {
141
- autoIdx = idx;
142
- break;
143
- }
144
- }
145
- const autoBlock = autoIdx === null ? "" : lines.slice(autoIdx).join("\n").trimEnd();
146
- const parts = [];
147
- if (prefixText) {
148
- parts.push(prefixText, "");
149
- }
150
- parts.push(docText.trimEnd());
151
- if (autoBlock) {
152
- parts.push("", autoBlock);
153
- }
154
- return `${parts.join("\n").trimEnd()}\n`;
155
- }
156
- return body;
99
+ const existing = normalizeUpdatedBy(task.doc_updated_by);
100
+ if (existing)
101
+ return existing;
102
+ const owner = normalizeUpdatedBy(task.owner);
103
+ if (owner)
104
+ return owner;
105
+ const fallbackValue = normalizeUpdatedBy(fallback);
106
+ return fallbackValue || fallback;
157
107
  }
108
+ export { extractTaskDoc, mergeTaskDoc };
158
109
  function validateTaskId(taskId) {
159
110
  if (TASK_ID_RE.test(taskId))
160
111
  return;
@@ -192,6 +143,14 @@ function toStringArray(value) {
192
143
  return [];
193
144
  return value.filter((v) => typeof v === "string");
194
145
  }
146
+ function normalizeDependsOn(value) {
147
+ if (Array.isArray(value)) {
148
+ return value.filter((v) => typeof v === "string" && v.trim() !== "[]");
149
+ }
150
+ if (typeof value === "string" && value.trim() === "[]")
151
+ return [];
152
+ return [];
153
+ }
195
154
  function normalizePriority(value) {
196
155
  const raw = toStringSafe(value).trim().toLowerCase();
197
156
  if (!raw)
@@ -229,7 +188,7 @@ export function taskRecordToData(record) {
229
188
  status: typeof fm.status === "string" ? fm.status : "TODO",
230
189
  priority: typeof fm.priority === "string" || typeof fm.priority === "number" ? fm.priority : "",
231
190
  owner: typeof fm.owner === "string" ? fm.owner : "",
232
- depends_on: toStringArray(fm.depends_on),
191
+ depends_on: normalizeDependsOn(fm.depends_on),
233
192
  tags: toStringArray(fm.tags),
234
193
  verify: toStringArray(fm.verify),
235
194
  commit,
@@ -263,7 +222,7 @@ function taskDataToExport(task) {
263
222
  : [],
264
223
  doc_version: task.doc_version ?? DOC_VERSION,
265
224
  doc_updated_at: task.doc_updated_at ?? "",
266
- doc_updated_by: task.doc_updated_by ?? DEFAULT_DOC_UPDATED_BY,
225
+ doc_updated_by: resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY),
267
226
  dirty: Boolean(task.dirty),
268
227
  id_source: task.id_source ?? "generated",
269
228
  };
@@ -286,7 +245,7 @@ export function buildTasksExportSnapshotFromTasks(tasks) {
286
245
  export async function writeTasksExportFromTasks(opts) {
287
246
  const snapshot = buildTasksExportSnapshotFromTasks(opts.tasks);
288
247
  await mkdir(path.dirname(opts.outputPath), { recursive: true });
289
- await writeFile(opts.outputPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
248
+ await atomicWriteFile(opts.outputPath, `${JSON.stringify(snapshot, null, 2)}\n`);
290
249
  }
291
250
  export class LocalBackend {
292
251
  id = "local";
@@ -301,40 +260,64 @@ export class LocalBackend {
301
260
  if (length < 4)
302
261
  throw new Error(invalidLengthMessage(length, 4));
303
262
  const attempts = Math.max(1, opts.attempts);
304
- for (let i = 0; i < attempts; i++) {
305
- const now = new Date();
306
- const yyyy = String(now.getUTCFullYear()).padStart(4, "0");
307
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
308
- const dd = String(now.getUTCDate()).padStart(2, "0");
309
- const hh = String(now.getUTCHours()).padStart(2, "0");
310
- const min = String(now.getUTCMinutes()).padStart(2, "0");
311
- let suffix = "";
312
- for (let j = 0; j < length; j++) {
313
- suffix += ID_ALPHABET[randomInt(0, ID_ALPHABET.length)];
314
- }
315
- const taskId = `${yyyy}${mm}${dd}${hh}${min}-${suffix}`;
316
- const readmePath = taskReadmePath(this.root, taskId);
317
- try {
318
- await readFile(readmePath, "utf8");
319
- continue;
320
- }
321
- catch (err) {
322
- const code = err?.code;
323
- if (code === "ENOENT")
324
- return taskId;
325
- throw err;
326
- }
327
- }
328
- throw new Error("Failed to generate a unique task id (exhausted attempts)");
263
+ return await generateTaskId({
264
+ length,
265
+ attempts,
266
+ isAvailable: async (taskId) => {
267
+ const readmePath = taskReadmePath(this.root, taskId);
268
+ try {
269
+ await readFile(readmePath, "utf8");
270
+ return false;
271
+ }
272
+ catch (err) {
273
+ const code = err?.code;
274
+ if (code === "ENOENT")
275
+ return true;
276
+ throw err;
277
+ }
278
+ },
279
+ });
329
280
  }
330
281
  async listTasks() {
331
282
  const tasks = [];
332
283
  const entries = await readdir(this.root, { withFileTypes: true }).catch(() => []);
284
+ const indexPath = resolveTaskIndexPath(this.root);
285
+ const cachedIndex = await loadTaskIndex(indexPath);
286
+ const cachedByPath = new Map();
287
+ if (cachedIndex) {
288
+ for (const entry of cachedIndex.tasks) {
289
+ cachedByPath.set(entry.readmePath, entry);
290
+ }
291
+ }
292
+ const nextIndex = [];
333
293
  const seen = new Set();
334
294
  for (const entry of entries) {
335
295
  if (!entry.isDirectory())
336
296
  continue;
337
297
  const readme = path.join(this.root, entry.name, "README.md");
298
+ let stats;
299
+ try {
300
+ stats = await stat(readme);
301
+ }
302
+ catch {
303
+ continue;
304
+ }
305
+ if (!stats.isFile())
306
+ continue;
307
+ const cached = cachedByPath.get(readme);
308
+ if (cached?.mtimeMs === stats.mtimeMs) {
309
+ const taskId = cached.task.id.trim();
310
+ if (taskId) {
311
+ validateTaskId(taskId);
312
+ if (seen.has(taskId)) {
313
+ throw new Error(`Duplicate task id in local backend: ${taskId}`);
314
+ }
315
+ seen.add(taskId);
316
+ }
317
+ tasks.push(cached.task);
318
+ nextIndex.push(cached);
319
+ continue;
320
+ }
338
321
  let text = "";
339
322
  try {
340
323
  text = await readFile(readme, "utf8");
@@ -367,6 +350,13 @@ export class LocalBackend {
367
350
  readmePath: readme,
368
351
  });
369
352
  tasks.push(task);
353
+ nextIndex.push(buildTaskIndexEntry(task, readme, stats.mtimeMs));
354
+ }
355
+ try {
356
+ await saveTaskIndex(indexPath, { schema_version: 1, tasks: nextIndex });
357
+ }
358
+ catch {
359
+ // Best-effort cache; ignore failures.
370
360
  }
371
361
  return tasks;
372
362
  }
@@ -445,11 +435,11 @@ export class LocalBackend {
445
435
  payload.doc_updated_at = nowIso();
446
436
  }
447
437
  if (payload.doc_updated_by === undefined || payload.doc_updated_by === "") {
448
- payload.doc_updated_by = this.updatedBy;
438
+ payload.doc_updated_by = resolveDocUpdatedByFromTask(task, this.updatedBy);
449
439
  }
450
440
  await mkdir(path.dirname(readme), { recursive: true });
451
441
  const text = renderTaskReadme(payload, body || "");
452
- await writeFile(readme, text.endsWith("\n") ? text : `${text}\n`, "utf8");
442
+ await atomicWriteFile(readme, text.endsWith("\n") ? text : `${text}\n`);
453
443
  }
454
444
  async setTaskDoc(taskId, doc, updatedBy) {
455
445
  const readme = taskReadmePath(this.root, taskId);
@@ -467,7 +457,7 @@ export class LocalBackend {
467
457
  frontmatter.doc_version = DOC_VERSION;
468
458
  }
469
459
  const next = renderTaskReadme(frontmatter, body);
470
- await writeFile(readme, next.endsWith("\n") ? next : `${next}\n`, "utf8");
460
+ await atomicWriteFile(readme, next.endsWith("\n") ? next : `${next}\n`);
471
461
  }
472
462
  async touchTaskDocMetadata(taskId, updatedBy) {
473
463
  const readme = taskReadmePath(this.root, taskId);
@@ -478,7 +468,7 @@ export class LocalBackend {
478
468
  frontmatter.doc_updated_at = nowIso();
479
469
  frontmatter.doc_updated_by = resolveDocUpdatedByFromFrontmatter(frontmatter, updatedBy, this.updatedBy);
480
470
  const next = renderTaskReadme(frontmatter, parsed.body || "");
481
- await writeFile(readme, next.endsWith("\n") ? next : `${next}\n`, "utf8");
471
+ await atomicWriteFile(readme, next.endsWith("\n") ? next : `${next}\n`);
482
472
  }
483
473
  async writeTasks(tasks) {
484
474
  for (const task of tasks) {
@@ -505,11 +495,11 @@ export class RedmineBackend {
505
495
  issueCache = new Map();
506
496
  reverseStatus = new Map();
507
497
  constructor(settings, opts) {
508
- const envUrl = firstNonEmptyString(process.env.CODEXSWARM_REDMINE_URL);
509
- const envApiKey = firstNonEmptyString(process.env.CODEXSWARM_REDMINE_API_KEY);
510
- const envProjectId = firstNonEmptyString(process.env.CODEXSWARM_REDMINE_PROJECT_ID);
511
- const envAssignee = (process.env.CODEXSWARM_REDMINE_ASSIGNEE_ID ?? "").trim();
512
- const envOwner = firstNonEmptyString(process.env.CODEXSWARM_REDMINE_OWNER, process.env.CODEXSWARM_REDMINE_OWNER_AGENT);
498
+ const envUrl = firstNonEmptyString(process.env.AGENTPLANE_REDMINE_URL);
499
+ const envApiKey = firstNonEmptyString(process.env.AGENTPLANE_REDMINE_API_KEY);
500
+ const envProjectId = firstNonEmptyString(process.env.AGENTPLANE_REDMINE_PROJECT_ID);
501
+ const envAssignee = (process.env.AGENTPLANE_REDMINE_ASSIGNEE_ID ?? "").trim();
502
+ const envOwner = firstNonEmptyString(process.env.AGENTPLANE_REDMINE_OWNER, process.env.AGENTPLANE_REDMINE_OWNER_AGENT);
513
503
  this.baseUrl = firstNonEmptyString(envUrl, settings.url).replaceAll(/\/+$/gu, "");
514
504
  this.apiKey = firstNonEmptyString(envApiKey, settings.api_key);
515
505
  this.projectId = firstNonEmptyString(envProjectId, settings.project_id);
@@ -547,7 +537,11 @@ export class RedmineBackend {
547
537
  const cached = await this.cache.listTasks();
548
538
  existingIds = new Set(cached.map((task) => toStringSafe(task.id)).filter(Boolean));
549
539
  }
550
- return generateTaskId(existingIds, length, attempts);
540
+ return await generateTaskId({
541
+ length,
542
+ attempts,
543
+ isAvailable: (taskId) => !existingIds.has(taskId),
544
+ });
551
545
  }
552
546
  async listTasks() {
553
547
  try {
@@ -1239,26 +1233,6 @@ export class RedmineBackend {
1239
1233
  throw lastError instanceof Error ? lastError : new RedmineUnavailable("Redmine unavailable");
1240
1234
  }
1241
1235
  }
1242
- function generateTaskId(existingIds, length, attempts) {
1243
- if (length < 4)
1244
- throw new Error(invalidLengthMessage(length, 4));
1245
- for (let i = 0; i < attempts; i++) {
1246
- const now = new Date();
1247
- const yyyy = String(now.getUTCFullYear()).padStart(4, "0");
1248
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
1249
- const dd = String(now.getUTCDate()).padStart(2, "0");
1250
- const hh = String(now.getUTCHours()).padStart(2, "0");
1251
- const min = String(now.getUTCMinutes()).padStart(2, "0");
1252
- let suffix = "";
1253
- for (let j = 0; j < length; j++) {
1254
- suffix += ID_ALPHABET[randomInt(0, ID_ALPHABET.length)];
1255
- }
1256
- const candidate = `${yyyy}${mm}${dd}${hh}${min}-${suffix}`;
1257
- if (!existingIds.has(candidate))
1258
- return candidate;
1259
- }
1260
- throw new Error("Failed to generate a unique task id (exhausted attempts)");
1261
- }
1262
1236
  async function loadBackendConfig(configPath) {
1263
1237
  try {
1264
1238
  const raw = JSON.parse(await readFile(configPath, "utf8"));
@@ -1279,14 +1253,23 @@ function resolveMaybeRelative(root, input) {
1279
1253
  return null;
1280
1254
  return path.isAbsolute(raw) ? raw : path.join(root, raw);
1281
1255
  }
1256
+ function normalizeBackendConfig(raw) {
1257
+ if (!isRecord(raw)) {
1258
+ return { id: "local", version: 1, settings: {} };
1259
+ }
1260
+ const id = toStringSafe(raw.id).trim() || "local";
1261
+ const version = typeof raw.version === "number" ? raw.version : 1;
1262
+ const settings = isRecord(raw.settings) ? raw.settings : {};
1263
+ return { id, version, settings };
1264
+ }
1282
1265
  export async function loadTaskBackend(opts) {
1283
1266
  const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
1284
1267
  const loaded = await loadConfig(resolved.agentplaneDir);
1285
1268
  const backendConfigPath = path.join(resolved.gitRoot, loaded.config.tasks_backend.config_path);
1286
1269
  const backendConfig = await loadBackendConfig(backendConfigPath);
1287
- const backendIdRaw = toStringSafe(backendConfig?.id).trim();
1288
- const backendId = backendIdRaw.length > 0 ? backendIdRaw : "local";
1289
- const settings = isRecord(backendConfig?.settings) ? backendConfig?.settings : {};
1270
+ const normalized = normalizeBackendConfig(backendConfig);
1271
+ const backendId = normalized.id;
1272
+ const settings = normalized.settings;
1290
1273
  if (backendId === "redmine") {
1291
1274
  await loadDotEnv(resolved.gitRoot);
1292
1275
  const cacheDirRaw = resolveMaybeRelative(resolved.gitRoot, settings.cache_dir);
@@ -0,0 +1,16 @@
1
+ import type { TaskData } from "./task-backend.js";
2
+ export declare const TASK_INDEX_SCHEMA_VERSION = 1;
3
+ export type TaskIndexEntry = {
4
+ task: TaskData;
5
+ readmePath: string;
6
+ mtimeMs: number;
7
+ };
8
+ export type TaskIndexFile = {
9
+ schema_version: 1;
10
+ tasks: TaskIndexEntry[];
11
+ };
12
+ export declare function resolveTaskIndexPath(tasksDir: string): string;
13
+ export declare function loadTaskIndex(indexPath: string): Promise<TaskIndexFile | null>;
14
+ export declare function saveTaskIndex(indexPath: string, index: TaskIndexFile): Promise<void>;
15
+ export declare function buildTaskIndexEntry(task: TaskData, readmePath: string, mtimeMs: number): TaskIndexEntry;
16
+ //# sourceMappingURL=task-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-index.d.ts","sourceRoot":"","sources":["../../src/backends/task-index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAG3C,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,cAAc,EAAE,CAAC,CAAC;IAClB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU7D;AAuBD,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAkBpF;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1F;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,cAAc,CAMhB"}