@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 CHANGED
@@ -1,5 +1,81 @@
1
1
  # @agentplaneorg/core
2
2
 
3
- Internal core library used by the `agentplane` CLI.
3
+ [![npm](https://img.shields.io/npm/v/@agentplaneorg/core.svg)](https://www.npmjs.com/package/@agentplaneorg/core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/basilisk-labs/agentplane/blob/main/LICENSE)
5
+ [![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)
4
6
 
5
- This package is intended to be published alongside `agentplane` to satisfy runtime dependencies.
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
@@ -1 +1 @@
1
- {"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"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"}
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;AAUF,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAgBtF;AA8BD,wBAAsB,WAAW,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC,CASD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvE;AA+MD,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAuC9C;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BlC;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,UAAU,CAAC,CActB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA0BxB"}
@@ -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: "agentplane",
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 ?? "agentplane").trim();
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 nextBody = setMarkdownSection(parsed.body, opts.section, opts.text);
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentplaneorg/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Core utilities and models for the Agent Plane CLI.",
5
5
  "keywords": [
6
6
  "agentplane",