@agentplaneorg/core 0.1.2 → 0.1.4
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 +78 -2
- package/dist/task-store.d.ts.map +1 -1
- package/dist/task-store.js +153 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# @agentplaneorg/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@agentplaneorg/core)
|
|
4
|
+
[](https://github.com/basilisk-labs/agentplane/blob/main/LICENSE)
|
|
5
|
+
[](https://github.com/basilisk-labs/agentplane/blob/main/docs/user/prerequisites.mdx)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Core utilities and models used by the `agentplane` CLI. This package exposes the reusable building blocks
|
|
8
|
+
for project discovery, config handling, task readme parsing, and task export/linting.
|
|
9
|
+
|
|
10
|
+
If you are an end-user, install the CLI instead: https://www.npmjs.com/package/agentplane
|
|
11
|
+
|
|
12
|
+
## 5-minute start (CLI)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g agentplane
|
|
16
|
+
agentplane init
|
|
17
|
+
agentplane quickstart
|
|
18
|
+
agentplane task new --title "First task" --description "Describe the change" --priority med --owner ORCHESTRATOR --tag docs
|
|
19
|
+
agentplane verify <task-id>
|
|
20
|
+
agentplane finish <task-id>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## For library usage
|
|
24
|
+
|
|
25
|
+
### Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @agentplaneorg/core
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Requirements
|
|
32
|
+
|
|
33
|
+
- Node.js >= 20
|
|
34
|
+
- ESM-only (`type: module`)
|
|
35
|
+
|
|
36
|
+
### Usage
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
resolveProject,
|
|
41
|
+
loadConfig,
|
|
42
|
+
listTasks,
|
|
43
|
+
readTask,
|
|
44
|
+
buildTasksExportSnapshot,
|
|
45
|
+
} from "@agentplaneorg/core";
|
|
46
|
+
|
|
47
|
+
const project = await resolveProject(process.cwd());
|
|
48
|
+
const config = await loadConfig(project.root);
|
|
49
|
+
const tasks = await listTasks(project.root);
|
|
50
|
+
const task = await readTask(project.root, tasks[0]?.id ?? "");
|
|
51
|
+
const snapshot = await buildTasksExportSnapshot(project.root);
|
|
52
|
+
|
|
53
|
+
console.log(config.data.workflow_mode, task?.id, snapshot.meta.version);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Exported Modules
|
|
57
|
+
|
|
58
|
+
| Area | Highlights |
|
|
59
|
+
| ----------------- | -------------------------------------------------------------- |
|
|
60
|
+
| Project discovery | `resolveProject`, `findGitRoot` |
|
|
61
|
+
| Config | `loadConfig`, `saveConfig`, `setByDottedKey`, `validateConfig` |
|
|
62
|
+
| Task README | `parseTaskReadme`, `renderTaskReadme` |
|
|
63
|
+
| Task store | `createTask`, `listTasks`, `readTask`, `setTaskDocSection` |
|
|
64
|
+
| Exports | `buildTasksExportSnapshot`, `writeTasksExport`, checksums |
|
|
65
|
+
| Linting | `lintTasksFile`, `lintTasksSnapshot` |
|
|
66
|
+
| Git | `getStagedFiles`, `getUnstagedFiles`, base branch helpers |
|
|
67
|
+
| Commit policy | `validateCommitSubject`, `extractTaskSuffix` |
|
|
68
|
+
|
|
69
|
+
## Stability
|
|
70
|
+
|
|
71
|
+
This package is versioned alongside the CLI and is primarily used by `agentplane`. The API is stable for
|
|
72
|
+
current use cases, but expect changes as the CLI evolves.
|
|
73
|
+
|
|
74
|
+
## Docs
|
|
75
|
+
|
|
76
|
+
- Repository: https://github.com/basilisk-labs/agentplane
|
|
77
|
+
- Developer docs: https://github.com/basilisk-labs/agentplane/tree/main/docs
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/task-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,WAAW,EAAE,CAAC,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,eAAe,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"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"}
|
package/dist/task-store.js
CHANGED
|
@@ -6,6 +6,9 @@ import { parseTaskReadme, renderTaskReadme } from "./task-readme.js";
|
|
|
6
6
|
function nowIso() {
|
|
7
7
|
return new Date().toISOString();
|
|
8
8
|
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
9
12
|
export function validateTaskDocMetadata(frontmatter) {
|
|
10
13
|
const errors = [];
|
|
11
14
|
if (frontmatter.doc_version !== 2)
|
|
@@ -107,6 +110,152 @@ function setMarkdownSection(body, section, text) {
|
|
|
107
110
|
const out = [...lines.slice(0, start + 1), ...replacement, ...lines.slice(nextHeading)];
|
|
108
111
|
return `${out.join("\n")}\n`;
|
|
109
112
|
}
|
|
113
|
+
function normalizeDocSectionName(section) {
|
|
114
|
+
return section.trim().replaceAll(/\s+/g, " ").toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
function getLastCommentAuthor(frontmatter) {
|
|
117
|
+
const comments = frontmatter.comments;
|
|
118
|
+
if (!Array.isArray(comments))
|
|
119
|
+
return null;
|
|
120
|
+
const entries = comments;
|
|
121
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
122
|
+
const entry = entries[i];
|
|
123
|
+
if (!isRecord(entry))
|
|
124
|
+
continue;
|
|
125
|
+
const author = entry.author;
|
|
126
|
+
if (typeof author === "string") {
|
|
127
|
+
const trimmed = author.trim();
|
|
128
|
+
if (trimmed)
|
|
129
|
+
return trimmed;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function resolveDocUpdatedBy(frontmatter, updatedBy) {
|
|
135
|
+
if (updatedBy != null) {
|
|
136
|
+
const explicit = updatedBy.trim();
|
|
137
|
+
if (explicit.length === 0) {
|
|
138
|
+
throw new Error("doc_updated_by must be a non-empty string");
|
|
139
|
+
}
|
|
140
|
+
return explicit;
|
|
141
|
+
}
|
|
142
|
+
const lastAuthor = getLastCommentAuthor(frontmatter);
|
|
143
|
+
if (lastAuthor)
|
|
144
|
+
return lastAuthor;
|
|
145
|
+
const existing = frontmatter.doc_updated_by;
|
|
146
|
+
if (typeof existing === "string") {
|
|
147
|
+
const trimmed = existing.trim();
|
|
148
|
+
if (trimmed && trimmed.toLowerCase() !== "agentplane")
|
|
149
|
+
return trimmed;
|
|
150
|
+
}
|
|
151
|
+
const owner = frontmatter.owner;
|
|
152
|
+
if (typeof owner === "string") {
|
|
153
|
+
const trimmed = owner.trim();
|
|
154
|
+
if (trimmed)
|
|
155
|
+
return trimmed;
|
|
156
|
+
}
|
|
157
|
+
return "agentplane";
|
|
158
|
+
}
|
|
159
|
+
function splitCombinedHeadingLines(doc) {
|
|
160
|
+
const lines = doc.replaceAll("\r\n", "\n").split("\n");
|
|
161
|
+
const out = [];
|
|
162
|
+
let inFence = false;
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
const trimmed = line.trimStart();
|
|
165
|
+
if (trimmed.startsWith("```")) {
|
|
166
|
+
inFence = !inFence;
|
|
167
|
+
out.push(line);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!inFence && line.includes("## ")) {
|
|
171
|
+
const matches = [...line.matchAll(/##\s+/g)];
|
|
172
|
+
if (matches.length > 1 && matches[0]?.index === 0) {
|
|
173
|
+
let start = 0;
|
|
174
|
+
for (let i = 1; i < matches.length; i += 1) {
|
|
175
|
+
const idx = matches[i]?.index ?? 0;
|
|
176
|
+
const chunk = line.slice(start, idx).trimEnd();
|
|
177
|
+
if (chunk)
|
|
178
|
+
out.push(chunk);
|
|
179
|
+
start = idx;
|
|
180
|
+
}
|
|
181
|
+
const last = line.slice(start).trimEnd();
|
|
182
|
+
if (last)
|
|
183
|
+
out.push(last);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
out.push(line);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
function normalizeDocSections(doc, required) {
|
|
192
|
+
const lines = splitCombinedHeadingLines(doc);
|
|
193
|
+
const sections = new Map();
|
|
194
|
+
const order = [];
|
|
195
|
+
const pendingSeparator = new Set();
|
|
196
|
+
let currentKey = null;
|
|
197
|
+
for (const line of lines) {
|
|
198
|
+
const match = /^##\s+(.*)$/.exec(line.trim());
|
|
199
|
+
if (match) {
|
|
200
|
+
const title = match[1]?.trim() ?? "";
|
|
201
|
+
const key = normalizeDocSectionName(title);
|
|
202
|
+
if (key) {
|
|
203
|
+
const existing = sections.get(key);
|
|
204
|
+
if (existing) {
|
|
205
|
+
if (existing.lines.some((entry) => entry.trim() !== "")) {
|
|
206
|
+
pendingSeparator.add(key);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
sections.set(key, { title, lines: [] });
|
|
211
|
+
order.push(key);
|
|
212
|
+
}
|
|
213
|
+
currentKey = key;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (currentKey) {
|
|
218
|
+
const entry = sections.get(currentKey);
|
|
219
|
+
if (!entry)
|
|
220
|
+
continue;
|
|
221
|
+
if (pendingSeparator.has(currentKey) && line.trim() !== "") {
|
|
222
|
+
entry.lines.push("");
|
|
223
|
+
pendingSeparator.delete(currentKey);
|
|
224
|
+
}
|
|
225
|
+
entry.lines.push(line);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const out = [];
|
|
229
|
+
const seen = new Set(order);
|
|
230
|
+
for (const key of order) {
|
|
231
|
+
const section = sections.get(key);
|
|
232
|
+
if (!section)
|
|
233
|
+
continue;
|
|
234
|
+
out.push(`## ${section.title}`);
|
|
235
|
+
if (section.lines.length > 0) {
|
|
236
|
+
out.push(...section.lines);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
out.push("");
|
|
240
|
+
}
|
|
241
|
+
out.push("");
|
|
242
|
+
}
|
|
243
|
+
for (const requiredSection of required) {
|
|
244
|
+
const requiredKey = normalizeDocSectionName(requiredSection);
|
|
245
|
+
if (seen.has(requiredKey))
|
|
246
|
+
continue;
|
|
247
|
+
out.push(`## ${requiredSection}`, "", "");
|
|
248
|
+
}
|
|
249
|
+
return `${out.join("\n").trimEnd()}\n`;
|
|
250
|
+
}
|
|
251
|
+
function ensureDocSections(doc, required) {
|
|
252
|
+
const trimmed = doc.trim();
|
|
253
|
+
if (!trimmed) {
|
|
254
|
+
const blocks = required.map((section) => `## ${section}\n`);
|
|
255
|
+
return `${blocks.join("\n").trimEnd()}\n`;
|
|
256
|
+
}
|
|
257
|
+
return normalizeDocSections(doc, required);
|
|
258
|
+
}
|
|
110
259
|
export async function createTask(opts) {
|
|
111
260
|
const { tasksDir, idSuffixLengthDefault } = await getTasksDir({
|
|
112
261
|
cwd: opts.cwd,
|
|
@@ -132,7 +281,7 @@ export async function createTask(opts) {
|
|
|
132
281
|
comments: [],
|
|
133
282
|
doc_version: 2,
|
|
134
283
|
doc_updated_at: nowIso(),
|
|
135
|
-
doc_updated_by:
|
|
284
|
+
doc_updated_by: opts.owner,
|
|
136
285
|
description: opts.description,
|
|
137
286
|
};
|
|
138
287
|
const body = defaultTaskBody();
|
|
@@ -154,16 +303,15 @@ export async function setTaskDocSection(opts) {
|
|
|
154
303
|
const readmePath = taskReadmePath(tasksDir, opts.taskId);
|
|
155
304
|
const original = await readFile(readmePath, "utf8");
|
|
156
305
|
const parsed = parseTaskReadme(original);
|
|
157
|
-
const updatedBy = (opts.updatedBy
|
|
158
|
-
if (updatedBy.length === 0)
|
|
159
|
-
throw new Error("doc_updated_by must be a non-empty string");
|
|
306
|
+
const updatedBy = resolveDocUpdatedBy(parsed.frontmatter, opts.updatedBy);
|
|
160
307
|
const nextFrontmatter = {
|
|
161
308
|
...parsed.frontmatter,
|
|
162
309
|
doc_version: 2,
|
|
163
310
|
doc_updated_at: nowIso(),
|
|
164
311
|
doc_updated_by: updatedBy,
|
|
165
312
|
};
|
|
166
|
-
const
|
|
313
|
+
const baseDoc = ensureDocSections(parsed.body, loaded.config.tasks.doc.required_sections);
|
|
314
|
+
const nextBody = ensureDocSections(setMarkdownSection(baseDoc, opts.section, opts.text), loaded.config.tasks.doc.required_sections);
|
|
167
315
|
const nextText = renderTaskReadme(nextFrontmatter, nextBody);
|
|
168
316
|
await writeFile(readmePath, nextText, "utf8");
|
|
169
317
|
return { readmePath };
|