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.
- package/README.md +51 -30
- package/dist/agents/agents-template.d.ts.map +1 -0
- package/dist/{agents-template.js → agents/agents-template.js} +2 -2
- package/dist/{task-backend.d.ts → backends/task-backend.d.ts} +5 -3
- package/dist/backends/task-backend.d.ts.map +1 -0
- package/dist/{task-backend.js → backends/task-backend.js} +139 -156
- package/dist/backends/task-index.d.ts +16 -0
- package/dist/backends/task-index.d.ts.map +1 -0
- package/dist/backends/task-index.js +84 -0
- package/dist/cli/archive.d.ts +16 -0
- package/dist/cli/archive.d.ts.map +1 -0
- package/dist/cli/archive.js +149 -0
- package/dist/cli/checksum.d.ts +3 -0
- package/dist/cli/checksum.d.ts.map +1 -0
- package/dist/cli/checksum.js +12 -0
- package/dist/cli/command-guide.d.ts.map +1 -0
- package/dist/cli/error-map.d.ts +4 -0
- package/dist/cli/error-map.d.ts.map +1 -0
- package/dist/cli/error-map.js +42 -0
- package/dist/cli/exit-codes.d.ts +3 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +12 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/{help.js → cli/help.js} +1 -1
- package/dist/cli/http.d.ts +4 -0
- package/dist/cli/http.d.ts.map +1 -0
- package/dist/cli/http.js +95 -0
- package/dist/cli/output.d.ts +16 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +47 -0
- package/dist/cli/recipes-bundled.js +2 -2
- package/dist/cli/run-cli.d.ts.map +1 -0
- package/dist/cli/run-cli.js +2681 -0
- package/dist/{run-cli.test-helpers.d.ts → cli/run-cli.test-helpers.d.ts} +6 -0
- package/dist/cli/run-cli.test-helpers.d.ts.map +1 -0
- package/dist/{run-cli.test-helpers.js → cli/run-cli.test-helpers.js} +67 -0
- package/dist/cli/update-check.d.ts +31 -0
- package/dist/cli/update-check.d.ts.map +1 -0
- package/dist/cli/update-check.js +86 -0
- package/dist/cli.js +1 -1
- package/dist/commands/backend.d.ts +15 -0
- package/dist/commands/backend.d.ts.map +1 -0
- package/dist/commands/backend.js +211 -0
- package/dist/commands/recipes.d.ts +13 -0
- package/dist/commands/recipes.d.ts.map +1 -0
- package/dist/commands/recipes.js +1919 -0
- package/dist/commands/upgrade.d.ts +6 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +291 -0
- package/dist/commands/workflow.d.ts +367 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +4619 -0
- package/dist/meta/version.d.ts.map +1 -0
- package/dist/meta/version.js +20 -0
- package/dist/recipes/bundled-recipes.d.ts.map +1 -0
- package/dist/shared/comment-format.d.ts.map +1 -0
- package/dist/shared/env.d.ts.map +1 -0
- package/dist/{errors.d.ts → shared/errors.d.ts} +1 -3
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/{errors.js → shared/errors.js} +1 -3
- package/package.json +6 -2
- package/dist/agents-template.d.ts.map +0 -1
- package/dist/bundled-recipes.d.ts.map +0 -1
- package/dist/command-guide.d.ts.map +0 -1
- package/dist/comment-format.d.ts.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/help.d.ts.map +0 -1
- package/dist/run-cli.d.ts.map +0 -1
- package/dist/run-cli.js +0 -9431
- package/dist/run-cli.test-helpers.d.ts.map +0 -1
- package/dist/task-backend.d.ts.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -3
- /package/dist/{agents-template.d.ts → agents/agents-template.d.ts} +0 -0
- /package/dist/{command-guide.d.ts → cli/command-guide.d.ts} +0 -0
- /package/dist/{command-guide.js → cli/command-guide.js} +0 -0
- /package/dist/{help.d.ts → cli/help.d.ts} +0 -0
- /package/dist/{run-cli.d.ts → cli/run-cli.d.ts} +0 -0
- /package/dist/{version.d.ts → meta/version.d.ts} +0 -0
- /package/dist/{bundled-recipes.d.ts → recipes/bundled-recipes.d.ts} +0 -0
- /package/dist/{bundled-recipes.js → recipes/bundled-recipes.js} +0 -0
- /package/dist/{comment-format.d.ts → shared/comment-format.d.ts} +0 -0
- /package/dist/{comment-format.js → shared/comment-format.js} +0 -0
- /package/dist/{env.d.ts → shared/env.d.ts} +0 -0
- /package/dist/{env.js → shared/env.js} +0 -0
package/README.md
CHANGED
|
@@ -5,53 +5,83 @@
|
|
|
5
5
|
[](https://github.com/basilisk-labs/agentplane/blob/main/LICENSE)
|
|
6
6
|
[](https://github.com/basilisk-labs/agentplane/blob/main/docs/user/prerequisites.mdx)
|
|
7
7
|
|
|
8
|
-
Agent Plane is
|
|
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
|
-
##
|
|
10
|
+
## Why Agent Plane
|
|
12
11
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
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
|
-
##
|
|
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
|
-
|
|
27
|
+
Create your first task and run the workflow:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
|
|
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
|
-
|
|
35
|
+
Prefer `npx` instead of a global install?
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
```bash
|
|
38
|
+
npx agentplane init
|
|
39
|
+
npx agentplane quickstart
|
|
40
|
+
```
|
|
34
41
|
|
|
35
|
-
##
|
|
42
|
+
## What gets installed automatically
|
|
36
43
|
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
npm install -g agentplane
|
|
47
73
|
```
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
Or run without installing:
|
|
50
76
|
|
|
51
77
|
```bash
|
|
52
|
-
npx agentplane
|
|
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("
|
|
5
|
-
const AGENTS_DIR_URL = new 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
|
-
|
|
3
|
-
|
|
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
|
|
2
|
-
import { mkdir, readdir, readFile,
|
|
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 "
|
|
6
|
-
|
|
7
|
-
const TASK_ID_RE = new RegExp(String.raw `^\d{12}-[${
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
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
|
-
|
|
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
|
|
73
|
-
|
|
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 (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
509
|
-
const envApiKey = firstNonEmptyString(process.env.
|
|
510
|
-
const envProjectId = firstNonEmptyString(process.env.
|
|
511
|
-
const envAssignee = (process.env.
|
|
512
|
-
const envOwner = firstNonEmptyString(process.env.
|
|
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(
|
|
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
|
|
1288
|
-
const backendId =
|
|
1289
|
-
const 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"}
|