agentplane 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 +96 -2
- package/assets/AGENTS.md +3 -0
- package/dist/command-guide.d.ts.map +1 -1
- package/dist/command-guide.js +1 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +6 -5
- package/dist/run-cli.d.ts.map +1 -1
- package/dist/run-cli.js +287 -32
- package/dist/run-cli.test-helpers.d.ts.map +1 -1
- package/dist/run-cli.test-helpers.js +8 -0
- package/dist/task-backend.d.ts.map +1 -1
- package/dist/task-backend.js +211 -10
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
# agentplane
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/agentplane)
|
|
4
|
+
[](https://www.npmjs.com/package/agentplane)
|
|
5
|
+
[](https://github.com/basilisk-labs/agentplane/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/basilisk-labs/agentplane/blob/main/docs/user/prerequisites.mdx)
|
|
7
|
+
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Why Agent Plane
|
|
11
|
+
|
|
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.
|
|
16
|
+
|
|
17
|
+
## 5-minute start
|
|
18
|
+
|
|
19
|
+
Install and initialize the CLI:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g agentplane
|
|
23
|
+
agentplane init
|
|
24
|
+
agentplane quickstart
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Create your first task and run the workflow:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
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>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Prefer `npx` instead of a global install?
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx agentplane init
|
|
39
|
+
npx agentplane quickstart
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What gets installed automatically
|
|
43
|
+
|
|
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 ...`.
|
|
48
|
+
|
|
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
|
|
59
|
+
```
|
|
60
|
+
|
|
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.
|
|
4
68
|
|
|
5
69
|
## Install
|
|
6
70
|
|
|
@@ -8,12 +72,42 @@ AgentPlane is an offline-first CLI for managing agent workflows (tasks, guardrai
|
|
|
8
72
|
npm install -g agentplane
|
|
9
73
|
```
|
|
10
74
|
|
|
75
|
+
Or run without installing:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx agentplane --help
|
|
79
|
+
```
|
|
80
|
+
|
|
11
81
|
## Requirements
|
|
12
82
|
|
|
13
83
|
- Node.js >= 20
|
|
14
84
|
|
|
15
|
-
##
|
|
85
|
+
## Common Commands
|
|
16
86
|
|
|
17
87
|
```bash
|
|
18
88
|
agentplane --help
|
|
89
|
+
agentplane quickstart
|
|
90
|
+
agentplane config show
|
|
91
|
+
agentplane task list
|
|
92
|
+
agentplane task new --title "..." --description "..." --priority med --owner ORCHESTRATOR --tag docs
|
|
93
|
+
agentplane verify <task-id>
|
|
94
|
+
agentplane finish <task-id>
|
|
95
|
+
agentplane recipes list
|
|
19
96
|
```
|
|
97
|
+
|
|
98
|
+
## Docs and Guides
|
|
99
|
+
|
|
100
|
+
- Documentation index: https://github.com/basilisk-labs/agentplane/tree/main/docs
|
|
101
|
+
- Workflow overview: https://github.com/basilisk-labs/agentplane/blob/main/docs/user/workflow.mdx
|
|
102
|
+
- CLI commands: https://github.com/basilisk-labs/agentplane/blob/main/docs/user/commands.mdx
|
|
103
|
+
- Project layout: https://github.com/basilisk-labs/agentplane/blob/main/docs/developer/project-layout.mdx
|
|
104
|
+
- Recipes: https://github.com/basilisk-labs/agentplane/tree/main/agentplane-recipes
|
|
105
|
+
|
|
106
|
+
## Support
|
|
107
|
+
|
|
108
|
+
- Issues: https://github.com/basilisk-labs/agentplane/issues
|
|
109
|
+
- Contributing: https://github.com/basilisk-labs/agentplane/blob/main/CONTRIBUTING.md
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
package/assets/AGENTS.md
CHANGED
|
@@ -163,6 +163,7 @@ Every task must have these sections in its README or task doc:
|
|
|
163
163
|
|
|
164
164
|
## Updating task docs
|
|
165
165
|
|
|
166
|
+
- Workflow/task artifacts (task READMEs, PR artifacts, task exports) must be updated via `agentplane` commands, not manual edits.
|
|
166
167
|
- Task README updates must be done via `agentplane task doc set ...`
|
|
167
168
|
- Manual edits to `.agentplane/tasks/<task-id>/README.md` are prohibited.
|
|
168
169
|
|
|
@@ -170,6 +171,8 @@ Every task must have these sections in its README or task doc:
|
|
|
170
171
|
|
|
171
172
|
# COMMIT WORKFLOW
|
|
172
173
|
|
|
174
|
+
- Commits and pushes must go through `agentplane` commands (no direct `git commit`/`git push`). If a push is needed but no `agentplane` command exists, ask for guidance.
|
|
175
|
+
|
|
173
176
|
## Commit message semantics (canonical)
|
|
174
177
|
|
|
175
178
|
There are two supported modes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-guide.d.ts","sourceRoot":"","sources":["../src/command-guide.ts"],"names":[],"mappings":"AA4KA,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAEpC;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"command-guide.d.ts","sourceRoot":"","sources":["../src/command-guide.ts"],"names":[],"mappings":"AA4KA,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAEpC;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CA4EzC"}
|
package/dist/command-guide.js
CHANGED
|
@@ -228,6 +228,7 @@ export function renderQuickstart() {
|
|
|
228
228
|
"- `--json`: emit JSON-formatted errors",
|
|
229
229
|
"- `--help` / `-h`: show help",
|
|
230
230
|
"- `--version`: show version",
|
|
231
|
+
"- `--no-update-check`: skip checking npm for a newer CLI version",
|
|
231
232
|
"",
|
|
232
233
|
"Notes:",
|
|
233
234
|
"- `.env` at the repo root is loaded automatically (without overwriting existing environment variables).",
|
package/dist/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,IAAI,MAAM,CA4HnC"}
|
package/dist/help.js
CHANGED
|
@@ -3,15 +3,16 @@ export function renderHelp() {
|
|
|
3
3
|
"agentplane (v1 prototype)",
|
|
4
4
|
"",
|
|
5
5
|
"Usage:",
|
|
6
|
-
" agentplane [--root <path>] [--json] <namespace> <command> [options]",
|
|
7
|
-
" agentplane [--root <path>] [--json] <command> [options]",
|
|
6
|
+
" agentplane [--root <path>] [--json] [--no-update-check] <namespace> <command> [options]",
|
|
7
|
+
" agentplane [--root <path>] [--json] [--no-update-check] <command> [options]",
|
|
8
8
|
"",
|
|
9
9
|
"Core namespaces (implemented in this prototype): config, mode, task, ide, recipes, backend",
|
|
10
10
|
"",
|
|
11
11
|
"Flags:",
|
|
12
|
-
" --help, -h
|
|
13
|
-
" --version
|
|
14
|
-
" --root <path>
|
|
12
|
+
" --help, -h Show help",
|
|
13
|
+
" --version Show version",
|
|
14
|
+
" --root <path> Treat <path> as project root",
|
|
15
|
+
" --no-update-check Skip checking npm for a newer CLI version",
|
|
15
16
|
"",
|
|
16
17
|
"Quickstart commands:",
|
|
17
18
|
" agentplane quickstart",
|
package/dist/run-cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../src/run-cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../src/run-cli.ts"],"names":[],"mappings":"AA69QA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4/D5D"}
|
package/dist/run-cli.js
CHANGED
|
@@ -30,6 +30,7 @@ function gitEnv() {
|
|
|
30
30
|
function parseGlobalArgs(argv) {
|
|
31
31
|
let help = false;
|
|
32
32
|
let version = false;
|
|
33
|
+
let noUpdateCheck = false;
|
|
33
34
|
let jsonErrors = false;
|
|
34
35
|
let root;
|
|
35
36
|
const rest = [];
|
|
@@ -45,6 +46,10 @@ function parseGlobalArgs(argv) {
|
|
|
45
46
|
version = true;
|
|
46
47
|
continue;
|
|
47
48
|
}
|
|
49
|
+
if (arg === "--no-update-check") {
|
|
50
|
+
noUpdateCheck = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
48
53
|
if (arg === "--json") {
|
|
49
54
|
jsonErrors = true;
|
|
50
55
|
continue;
|
|
@@ -63,7 +68,7 @@ function parseGlobalArgs(argv) {
|
|
|
63
68
|
}
|
|
64
69
|
rest.push(arg);
|
|
65
70
|
}
|
|
66
|
-
return { globals: { help, version, root, jsonErrors }, rest };
|
|
71
|
+
return { globals: { help, version, noUpdateCheck, root, jsonErrors }, rest };
|
|
67
72
|
}
|
|
68
73
|
function writeError(err, jsonErrors) {
|
|
69
74
|
const hint = renderErrorHint(err);
|
|
@@ -157,6 +162,79 @@ function usageMessage(usage, example) {
|
|
|
157
162
|
function backendNotSupportedMessage(feature) {
|
|
158
163
|
return `Backend does not support ${feature}`;
|
|
159
164
|
}
|
|
165
|
+
const UPDATE_CHECK_PACKAGE = "agentplane";
|
|
166
|
+
const UPDATE_CHECK_URL = `https://registry.npmjs.org/${UPDATE_CHECK_PACKAGE}/latest`;
|
|
167
|
+
const UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
168
|
+
function parseVersionParts(version) {
|
|
169
|
+
const cleaned = version.trim().replace(/^v/i, "").split("+")[0] ?? "";
|
|
170
|
+
const [mainRaw, prereleaseRaw] = cleaned.split("-", 2);
|
|
171
|
+
const main = (mainRaw ?? "")
|
|
172
|
+
.split(".")
|
|
173
|
+
.filter((part) => part.length > 0)
|
|
174
|
+
.map((part) => {
|
|
175
|
+
const parsed = Number.parseInt(part, 10);
|
|
176
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
177
|
+
});
|
|
178
|
+
return { main, prerelease: prereleaseRaw ? prereleaseRaw.trim() : null };
|
|
179
|
+
}
|
|
180
|
+
function compareVersions(left, right) {
|
|
181
|
+
const a = parseVersionParts(left);
|
|
182
|
+
const b = parseVersionParts(right);
|
|
183
|
+
const length = Math.max(a.main.length, b.main.length);
|
|
184
|
+
for (let i = 0; i < length; i++) {
|
|
185
|
+
const partA = a.main[i] ?? 0;
|
|
186
|
+
const partB = b.main[i] ?? 0;
|
|
187
|
+
if (partA !== partB)
|
|
188
|
+
return partA > partB ? 1 : -1;
|
|
189
|
+
}
|
|
190
|
+
if (a.prerelease === b.prerelease)
|
|
191
|
+
return 0;
|
|
192
|
+
if (a.prerelease === null)
|
|
193
|
+
return 1;
|
|
194
|
+
if (b.prerelease === null)
|
|
195
|
+
return -1;
|
|
196
|
+
return a.prerelease.localeCompare(b.prerelease);
|
|
197
|
+
}
|
|
198
|
+
function isTruthyEnv(value) {
|
|
199
|
+
if (!value)
|
|
200
|
+
return false;
|
|
201
|
+
const normalized = value.trim().toLowerCase();
|
|
202
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
203
|
+
}
|
|
204
|
+
async function fetchLatestNpmVersion() {
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
const timeout = setTimeout(() => controller.abort(), UPDATE_CHECK_TIMEOUT_MS);
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch(UPDATE_CHECK_URL, {
|
|
209
|
+
headers: { "User-Agent": "agentplane" },
|
|
210
|
+
signal: controller.signal,
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok)
|
|
213
|
+
return null;
|
|
214
|
+
const data = (await res.json());
|
|
215
|
+
const version = typeof data.version === "string" ? data.version.trim() : "";
|
|
216
|
+
return version.length > 0 ? version : null;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function maybeWarnOnUpdate(opts) {
|
|
226
|
+
if (opts.skip || opts.jsonErrors)
|
|
227
|
+
return;
|
|
228
|
+
if (isTruthyEnv(process.env.AGENTPLANE_NO_UPDATE_CHECK))
|
|
229
|
+
return;
|
|
230
|
+
const latest = await fetchLatestNpmVersion();
|
|
231
|
+
if (!latest)
|
|
232
|
+
return;
|
|
233
|
+
if (compareVersions(latest, opts.currentVersion) <= 0)
|
|
234
|
+
return;
|
|
235
|
+
const message = `Update available: ${UPDATE_CHECK_PACKAGE} ${opts.currentVersion} → ${latest}. Run: npm i -g ${UPDATE_CHECK_PACKAGE}@latest`;
|
|
236
|
+
process.stderr.write(`${warnMessage(message)}\n`);
|
|
237
|
+
}
|
|
160
238
|
function workflowModeMessage(actual, expected) {
|
|
161
239
|
return `Invalid workflow_mode: ${actual ?? "unknown"} (expected ${expected})`;
|
|
162
240
|
}
|
|
@@ -359,7 +437,7 @@ const BRANCH_STATUS_USAGE_EXAMPLE = "agentplane branch status --base main";
|
|
|
359
437
|
const BRANCH_REMOVE_USAGE = "Usage: agentplane branch remove [--branch <name>] [--worktree <path>] [--force] [--quiet]";
|
|
360
438
|
const BRANCH_REMOVE_USAGE_EXAMPLE = "agentplane branch remove --branch task/20260203-F1Q8AB --worktree .agentplane/worktrees/task";
|
|
361
439
|
const UPGRADE_USAGE = "Usage: agentplane upgrade [--tag <tag>] [--dry-run] [--no-backup] [--source <repo-url>] [--bundle <path|url>] [--checksum <path|url>]";
|
|
362
|
-
const UPGRADE_USAGE_EXAMPLE = "agentplane upgrade --tag v0.1.
|
|
440
|
+
const UPGRADE_USAGE_EXAMPLE = "agentplane upgrade --tag v0.1.4 --dry-run";
|
|
363
441
|
const INIT_USAGE = "Usage: agentplane init --ide <...> --workflow <...> --hooks <...> --require-plan-approval <...> --require-network-approval <...> [--recipes <...>] [--yes] [--force|--backup]";
|
|
364
442
|
const INIT_USAGE_EXAMPLE = "agentplane init --ide codex --workflow direct --hooks false --require-plan-approval true --require-network-approval true --yes";
|
|
365
443
|
const CONFIG_SET_USAGE = "Usage: agentplane config set <key> <value>";
|
|
@@ -1485,6 +1563,7 @@ async function cmdInit(opts) {
|
|
|
1485
1563
|
let recipes = flags.recipes ?? defaults.recipes;
|
|
1486
1564
|
let requirePlanApproval = flags.requirePlanApproval ?? defaults.requirePlanApproval;
|
|
1487
1565
|
let requireNetworkApproval = flags.requireNetworkApproval ?? defaults.requireNetworkApproval;
|
|
1566
|
+
const isInteractive = process.stdin.isTTY && !flags.yes;
|
|
1488
1567
|
if (!process.stdin.isTTY &&
|
|
1489
1568
|
!flags.yes &&
|
|
1490
1569
|
(!flags.workflow ||
|
|
@@ -1497,7 +1576,7 @@ async function cmdInit(opts) {
|
|
|
1497
1576
|
message: usageMessage(INIT_USAGE, INIT_USAGE_EXAMPLE),
|
|
1498
1577
|
});
|
|
1499
1578
|
}
|
|
1500
|
-
if (
|
|
1579
|
+
if (isInteractive) {
|
|
1501
1580
|
ide = flags.ide ?? defaults.ide;
|
|
1502
1581
|
if (!flags.workflow) {
|
|
1503
1582
|
const choice = await promptChoice("Select workflow mode", ["direct", "branch_pr"], workflow);
|
|
@@ -1534,7 +1613,9 @@ async function cmdInit(opts) {
|
|
|
1534
1613
|
validateBundledRecipesSelection(recipes);
|
|
1535
1614
|
try {
|
|
1536
1615
|
const initRoot = path.resolve(opts.rootOverride ?? opts.cwd);
|
|
1537
|
-
|
|
1616
|
+
const existingGitRoot = await findGitRoot(initRoot);
|
|
1617
|
+
const gitRootExisted = Boolean(existingGitRoot);
|
|
1618
|
+
let gitRoot = existingGitRoot;
|
|
1538
1619
|
const baseBranchFallback = defaultConfig().base_branch;
|
|
1539
1620
|
if (!gitRoot) {
|
|
1540
1621
|
await gitInitRepo(initRoot, baseBranchFallback);
|
|
@@ -1544,7 +1625,13 @@ async function cmdInit(opts) {
|
|
|
1544
1625
|
cwd: gitRoot,
|
|
1545
1626
|
rootOverride: gitRoot,
|
|
1546
1627
|
});
|
|
1547
|
-
|
|
1628
|
+
let initBaseBranch = await resolveInitBaseBranch(resolved.gitRoot, baseBranchFallback);
|
|
1629
|
+
if (isInteractive && workflow === "branch_pr" && gitRootExisted) {
|
|
1630
|
+
initBaseBranch = await promptInitBaseBranch({
|
|
1631
|
+
gitRoot: resolved.gitRoot,
|
|
1632
|
+
fallback: initBaseBranch,
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1548
1635
|
const configPath = path.join(resolved.agentplaneDir, "config.json");
|
|
1549
1636
|
const backendPath = path.join(resolved.agentplaneDir, "backends", "local", "backend.json");
|
|
1550
1637
|
const initDirs = [
|
|
@@ -1658,6 +1745,7 @@ async function cmdInit(opts) {
|
|
|
1658
1745
|
baseBranch: initBaseBranch,
|
|
1659
1746
|
installPaths,
|
|
1660
1747
|
version: getVersion(),
|
|
1748
|
+
skipHooks: hooks,
|
|
1661
1749
|
});
|
|
1662
1750
|
process.stdout.write(`${path.relative(resolved.gitRoot, resolved.agentplaneDir)}\n`);
|
|
1663
1751
|
return 0;
|
|
@@ -2928,7 +3016,7 @@ async function cmdTaskNew(opts) {
|
|
|
2928
3016
|
comments: [],
|
|
2929
3017
|
doc_version: 2,
|
|
2930
3018
|
doc_updated_at: nowIso(),
|
|
2931
|
-
doc_updated_by:
|
|
3019
|
+
doc_updated_by: flags.owner,
|
|
2932
3020
|
id_source: "generated",
|
|
2933
3021
|
};
|
|
2934
3022
|
if (requiresVerify(flags.tags, config.tasks.verify.required_tags) &&
|
|
@@ -3061,6 +3149,7 @@ async function cmdTaskAdd(opts) {
|
|
|
3061
3149
|
const tags = dedupeStrings(flags.tags);
|
|
3062
3150
|
const dependsOn = dedupeStrings(flags.dependsOn);
|
|
3063
3151
|
const verify = dedupeStrings(flags.verify);
|
|
3152
|
+
const docUpdatedBy = (flags.commentAuthor ?? "").trim() || flags.owner;
|
|
3064
3153
|
if (requiresVerify(tags, config.tasks.verify.required_tags) && verify.length === 0) {
|
|
3065
3154
|
throw new CliError({
|
|
3066
3155
|
exitCode: 2,
|
|
@@ -3083,7 +3172,7 @@ async function cmdTaskAdd(opts) {
|
|
|
3083
3172
|
: [],
|
|
3084
3173
|
doc_version: 2,
|
|
3085
3174
|
doc_updated_at: nowIso(),
|
|
3086
|
-
doc_updated_by:
|
|
3175
|
+
doc_updated_by: docUpdatedBy,
|
|
3087
3176
|
id_source: "explicit",
|
|
3088
3177
|
}));
|
|
3089
3178
|
if (backend.writeTasks) {
|
|
@@ -3756,10 +3845,15 @@ async function cmdTaskScaffold(opts) {
|
|
|
3756
3845
|
comments: [],
|
|
3757
3846
|
doc_version: 2,
|
|
3758
3847
|
doc_updated_at: nowIso(),
|
|
3759
|
-
doc_updated_by: "
|
|
3848
|
+
doc_updated_by: "UNKNOWN",
|
|
3760
3849
|
};
|
|
3761
3850
|
if (flags.title)
|
|
3762
3851
|
baseTask.title = flags.title;
|
|
3852
|
+
if (typeof baseTask.doc_updated_by !== "string" ||
|
|
3853
|
+
baseTask.doc_updated_by.trim().length === 0 ||
|
|
3854
|
+
baseTask.doc_updated_by.trim().toLowerCase() === "agentplane") {
|
|
3855
|
+
baseTask.doc_updated_by = baseTask.owner?.trim() ? baseTask.owner : "UNKNOWN";
|
|
3856
|
+
}
|
|
3763
3857
|
const frontmatter = taskDataToFrontmatter(baseTask);
|
|
3764
3858
|
const body = ensureDocSections("", config.tasks.doc.required_sections);
|
|
3765
3859
|
const text = renderTaskReadme(frontmatter, body);
|
|
@@ -3895,7 +3989,7 @@ async function cmdTaskComment(opts) {
|
|
|
3895
3989
|
comments: [...existing, { author: opts.author, body: opts.body }],
|
|
3896
3990
|
doc_version: 2,
|
|
3897
3991
|
doc_updated_at: nowIso(),
|
|
3898
|
-
doc_updated_by:
|
|
3992
|
+
doc_updated_by: opts.author,
|
|
3899
3993
|
};
|
|
3900
3994
|
await backend.writeTask(next);
|
|
3901
3995
|
process.stdout.write(`${successMessage("commented", opts.taskId)}\n`);
|
|
@@ -3998,7 +4092,7 @@ async function cmdTaskSetStatus(opts) {
|
|
|
3998
4092
|
comments,
|
|
3999
4093
|
doc_version: 2,
|
|
4000
4094
|
doc_updated_at: nowIso(),
|
|
4001
|
-
doc_updated_by:
|
|
4095
|
+
doc_updated_by: resolveDocUpdatedBy(task, opts.author),
|
|
4002
4096
|
};
|
|
4003
4097
|
if (opts.commit) {
|
|
4004
4098
|
const commitInfo = await readCommitInfo(resolved.gitRoot, opts.commit);
|
|
@@ -4472,8 +4566,12 @@ async function gitAddPaths(cwd, paths) {
|
|
|
4472
4566
|
return;
|
|
4473
4567
|
await execFileAsync("git", ["add", "--", ...paths], { cwd, env: gitEnv() });
|
|
4474
4568
|
}
|
|
4475
|
-
async function gitCommit(cwd, message) {
|
|
4476
|
-
|
|
4569
|
+
async function gitCommit(cwd, message, opts) {
|
|
4570
|
+
const args = ["commit", "-m", message];
|
|
4571
|
+
if (opts?.skipHooks)
|
|
4572
|
+
args.push("--no-verify");
|
|
4573
|
+
const env = opts?.env ? { ...gitEnv(), ...opts.env } : gitEnv();
|
|
4574
|
+
await execFileAsync("git", args, { cwd, env });
|
|
4477
4575
|
}
|
|
4478
4576
|
async function resolveInitBaseBranch(gitRoot, fallback) {
|
|
4479
4577
|
let current = null;
|
|
@@ -4495,6 +4593,47 @@ async function resolveInitBaseBranch(gitRoot, fallback) {
|
|
|
4495
4593
|
}
|
|
4496
4594
|
return fallback;
|
|
4497
4595
|
}
|
|
4596
|
+
async function promptInitBaseBranch(opts) {
|
|
4597
|
+
const branches = await gitListBranches(opts.gitRoot);
|
|
4598
|
+
let current = null;
|
|
4599
|
+
try {
|
|
4600
|
+
current = await gitCurrentBranch(opts.gitRoot);
|
|
4601
|
+
}
|
|
4602
|
+
catch {
|
|
4603
|
+
current = null;
|
|
4604
|
+
}
|
|
4605
|
+
const promptNewBranch = async (hasBranches) => {
|
|
4606
|
+
const raw = await promptInput(`Enter new base branch name (default ${opts.fallback}): `);
|
|
4607
|
+
const candidate = raw.trim() || opts.fallback;
|
|
4608
|
+
if (!candidate) {
|
|
4609
|
+
throw new CliError({
|
|
4610
|
+
exitCode: 2,
|
|
4611
|
+
code: "E_USAGE",
|
|
4612
|
+
message: "Base branch name cannot be empty",
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
if (await gitBranchExists(opts.gitRoot, candidate))
|
|
4616
|
+
return candidate;
|
|
4617
|
+
try {
|
|
4618
|
+
await execFileAsync("git", hasBranches ? ["branch", candidate] : ["checkout", "-q", "-b", candidate], { cwd: opts.gitRoot, env: gitEnv() });
|
|
4619
|
+
}
|
|
4620
|
+
catch (err) {
|
|
4621
|
+
const message = err instanceof Error ? err.message : `Failed to create branch ${candidate}`;
|
|
4622
|
+
throw new CliError({ exitCode: 5, code: "E_GIT", message });
|
|
4623
|
+
}
|
|
4624
|
+
return candidate;
|
|
4625
|
+
};
|
|
4626
|
+
if (branches.length === 0) {
|
|
4627
|
+
return await promptNewBranch(false);
|
|
4628
|
+
}
|
|
4629
|
+
const createLabel = "Create new branch";
|
|
4630
|
+
const defaultChoice = current && branches.includes(current) ? current : (branches[0] ?? opts.fallback);
|
|
4631
|
+
const choice = await promptChoice("Select base branch", [...branches, createLabel], defaultChoice);
|
|
4632
|
+
if (choice === createLabel) {
|
|
4633
|
+
return await promptNewBranch(true);
|
|
4634
|
+
}
|
|
4635
|
+
return choice;
|
|
4636
|
+
}
|
|
4498
4637
|
async function ensureInitCommit(opts) {
|
|
4499
4638
|
const stagedBefore = await gitStagedPaths(opts.gitRoot);
|
|
4500
4639
|
if (stagedBefore.length > 0) {
|
|
@@ -4515,7 +4654,7 @@ async function ensureInitCommit(opts) {
|
|
|
4515
4654
|
if (staged.length === 0)
|
|
4516
4655
|
return;
|
|
4517
4656
|
const message = `chore: install agentplane ${opts.version}`;
|
|
4518
|
-
await gitCommit(opts.gitRoot, message);
|
|
4657
|
+
await gitCommit(opts.gitRoot, message, { skipHooks: opts.skipHooks });
|
|
4519
4658
|
}
|
|
4520
4659
|
function toGitPath(filePath) {
|
|
4521
4660
|
return filePath.split(path.sep).join("/");
|
|
@@ -5232,7 +5371,7 @@ async function cmdStart(opts) {
|
|
|
5232
5371
|
comments: commentsValue,
|
|
5233
5372
|
doc_version: 2,
|
|
5234
5373
|
doc_updated_at: nowIso(),
|
|
5235
|
-
doc_updated_by:
|
|
5374
|
+
doc_updated_by: opts.author,
|
|
5236
5375
|
};
|
|
5237
5376
|
await backend.writeTask(nextTask);
|
|
5238
5377
|
let commitInfo = null;
|
|
@@ -5308,7 +5447,7 @@ async function cmdBlock(opts) {
|
|
|
5308
5447
|
comments: commentsValue,
|
|
5309
5448
|
doc_version: 2,
|
|
5310
5449
|
doc_updated_at: nowIso(),
|
|
5311
|
-
doc_updated_by:
|
|
5450
|
+
doc_updated_by: opts.author,
|
|
5312
5451
|
};
|
|
5313
5452
|
await backend.writeTask(nextTask);
|
|
5314
5453
|
let commitInfo = null;
|
|
@@ -5409,7 +5548,7 @@ async function cmdFinish(opts) {
|
|
|
5409
5548
|
comments: commentsValue,
|
|
5410
5549
|
doc_version: 2,
|
|
5411
5550
|
doc_updated_at: nowIso(),
|
|
5412
|
-
doc_updated_by:
|
|
5551
|
+
doc_updated_by: opts.author,
|
|
5413
5552
|
};
|
|
5414
5553
|
await backend.writeTask(nextTask);
|
|
5415
5554
|
}
|
|
@@ -6935,26 +7074,128 @@ function setMarkdownSection(body, section, text) {
|
|
|
6935
7074
|
const out = [...lines.slice(0, start + 1), ...replacement, ...lines.slice(nextHeading)];
|
|
6936
7075
|
return `${out.join("\n")}\n`;
|
|
6937
7076
|
}
|
|
7077
|
+
function normalizeDocSectionName(section) {
|
|
7078
|
+
return section.trim().replaceAll(/\s+/g, " ").toLowerCase();
|
|
7079
|
+
}
|
|
7080
|
+
function normalizeDocUpdatedBy(value) {
|
|
7081
|
+
const trimmed = value?.trim() ?? "";
|
|
7082
|
+
if (!trimmed)
|
|
7083
|
+
return "";
|
|
7084
|
+
if (trimmed.toLowerCase() === "agentplane")
|
|
7085
|
+
return "";
|
|
7086
|
+
return trimmed;
|
|
7087
|
+
}
|
|
7088
|
+
function resolveDocUpdatedBy(task, author) {
|
|
7089
|
+
const fromAuthor = normalizeDocUpdatedBy(author);
|
|
7090
|
+
if (fromAuthor)
|
|
7091
|
+
return fromAuthor;
|
|
7092
|
+
const fromTask = normalizeDocUpdatedBy(typeof task.doc_updated_by === "string" ? task.doc_updated_by : undefined);
|
|
7093
|
+
if (fromTask)
|
|
7094
|
+
return fromTask;
|
|
7095
|
+
return normalizeDocUpdatedBy(typeof task.owner === "string" ? task.owner : undefined);
|
|
7096
|
+
}
|
|
7097
|
+
function splitCombinedHeadingLines(doc) {
|
|
7098
|
+
const lines = doc.replaceAll("\r\n", "\n").split("\n");
|
|
7099
|
+
const out = [];
|
|
7100
|
+
let inFence = false;
|
|
7101
|
+
for (const line of lines) {
|
|
7102
|
+
const trimmed = line.trimStart();
|
|
7103
|
+
if (trimmed.startsWith("```")) {
|
|
7104
|
+
inFence = !inFence;
|
|
7105
|
+
out.push(line);
|
|
7106
|
+
continue;
|
|
7107
|
+
}
|
|
7108
|
+
if (!inFence && line.includes("## ")) {
|
|
7109
|
+
const matches = [...line.matchAll(/##\s+/g)];
|
|
7110
|
+
if (matches.length > 1 && matches[0]?.index === 0) {
|
|
7111
|
+
let start = 0;
|
|
7112
|
+
for (let i = 1; i < matches.length; i += 1) {
|
|
7113
|
+
const idx = matches[i]?.index ?? 0;
|
|
7114
|
+
const chunk = line.slice(start, idx).trimEnd();
|
|
7115
|
+
if (chunk)
|
|
7116
|
+
out.push(chunk);
|
|
7117
|
+
start = idx;
|
|
7118
|
+
}
|
|
7119
|
+
const last = line.slice(start).trimEnd();
|
|
7120
|
+
if (last)
|
|
7121
|
+
out.push(last);
|
|
7122
|
+
continue;
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
out.push(line);
|
|
7126
|
+
}
|
|
7127
|
+
return out;
|
|
7128
|
+
}
|
|
7129
|
+
function normalizeDocSections(doc, required) {
|
|
7130
|
+
const lines = splitCombinedHeadingLines(doc);
|
|
7131
|
+
const sections = new Map();
|
|
7132
|
+
const order = [];
|
|
7133
|
+
const pendingSeparator = new Set();
|
|
7134
|
+
let currentKey = null;
|
|
7135
|
+
for (const line of lines) {
|
|
7136
|
+
const match = /^##\s+(.*)$/.exec(line.trim());
|
|
7137
|
+
if (match) {
|
|
7138
|
+
const title = match[1]?.trim() ?? "";
|
|
7139
|
+
const key = normalizeDocSectionName(title);
|
|
7140
|
+
if (key) {
|
|
7141
|
+
const existing = sections.get(key);
|
|
7142
|
+
if (existing) {
|
|
7143
|
+
if (existing.lines.some((entry) => entry.trim() !== "")) {
|
|
7144
|
+
pendingSeparator.add(key);
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
else {
|
|
7148
|
+
sections.set(key, { title, lines: [] });
|
|
7149
|
+
order.push(key);
|
|
7150
|
+
}
|
|
7151
|
+
currentKey = key;
|
|
7152
|
+
continue;
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
if (currentKey) {
|
|
7156
|
+
const entry = sections.get(currentKey);
|
|
7157
|
+
if (!entry)
|
|
7158
|
+
continue;
|
|
7159
|
+
if (pendingSeparator.has(currentKey) && line.trim() !== "") {
|
|
7160
|
+
entry.lines.push("");
|
|
7161
|
+
pendingSeparator.delete(currentKey);
|
|
7162
|
+
}
|
|
7163
|
+
entry.lines.push(line);
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
const out = [];
|
|
7167
|
+
const seen = new Set(order);
|
|
7168
|
+
for (const key of order) {
|
|
7169
|
+
const section = sections.get(key);
|
|
7170
|
+
if (!section)
|
|
7171
|
+
continue;
|
|
7172
|
+
out.push(`## ${section.title}`);
|
|
7173
|
+
if (section.lines.length > 0) {
|
|
7174
|
+
out.push(...section.lines);
|
|
7175
|
+
}
|
|
7176
|
+
else {
|
|
7177
|
+
out.push("");
|
|
7178
|
+
}
|
|
7179
|
+
out.push("");
|
|
7180
|
+
}
|
|
7181
|
+
for (const requiredSection of required) {
|
|
7182
|
+
const requiredKey = normalizeDocSectionName(requiredSection);
|
|
7183
|
+
if (seen.has(requiredKey))
|
|
7184
|
+
continue;
|
|
7185
|
+
out.push(`## ${requiredSection}`, "", "");
|
|
7186
|
+
}
|
|
7187
|
+
return `${out.join("\n").trimEnd()}\n`;
|
|
7188
|
+
}
|
|
6938
7189
|
function ensureDocSections(doc, required) {
|
|
6939
7190
|
const trimmed = doc.trim();
|
|
6940
7191
|
if (!trimmed) {
|
|
6941
7192
|
const blocks = required.map((section) => `## ${section}\n`);
|
|
6942
7193
|
return `${blocks.join("\n").trimEnd()}\n`;
|
|
6943
7194
|
}
|
|
6944
|
-
|
|
6945
|
-
for (const section of required) {
|
|
6946
|
-
const needle = `## ${section}`;
|
|
6947
|
-
if (!out.split("\n").some((line) => line.trim() === needle)) {
|
|
6948
|
-
out = `${out.trimEnd()}\n\n${needle}\n`;
|
|
6949
|
-
}
|
|
6950
|
-
}
|
|
6951
|
-
return out.endsWith("\n") ? out : `${out}\n`;
|
|
6952
|
-
}
|
|
6953
|
-
function normalizeDocSectionName(section) {
|
|
6954
|
-
return section.trim().replaceAll(/\s+/g, " ").toLowerCase();
|
|
7195
|
+
return normalizeDocSections(doc, required);
|
|
6955
7196
|
}
|
|
6956
7197
|
function parseDocSections(doc) {
|
|
6957
|
-
const lines = doc
|
|
7198
|
+
const lines = splitCombinedHeadingLines(doc);
|
|
6958
7199
|
const sections = new Map();
|
|
6959
7200
|
const order = [];
|
|
6960
7201
|
let currentKey = null;
|
|
@@ -7098,9 +7339,17 @@ async function cmdTaskDocSet(opts) {
|
|
|
7098
7339
|
message: usageMessage(TASK_DOC_SET_USAGE, TASK_DOC_SET_USAGE_EXAMPLE),
|
|
7099
7340
|
});
|
|
7100
7341
|
}
|
|
7101
|
-
|
|
7102
|
-
if (updatedBy
|
|
7103
|
-
|
|
7342
|
+
let updatedBy;
|
|
7343
|
+
if (flags.updatedBy !== undefined) {
|
|
7344
|
+
const trimmed = flags.updatedBy.trim();
|
|
7345
|
+
if (trimmed.length === 0) {
|
|
7346
|
+
throw new CliError({
|
|
7347
|
+
exitCode: 2,
|
|
7348
|
+
code: "E_USAGE",
|
|
7349
|
+
message: "--updated-by must be non-empty",
|
|
7350
|
+
});
|
|
7351
|
+
}
|
|
7352
|
+
updatedBy = trimmed;
|
|
7104
7353
|
}
|
|
7105
7354
|
let text = flags.text ?? "";
|
|
7106
7355
|
if (hasFile) {
|
|
@@ -7164,7 +7413,8 @@ async function cmdTaskDocSet(opts) {
|
|
|
7164
7413
|
}
|
|
7165
7414
|
}
|
|
7166
7415
|
const nextDoc = setMarkdownSection(baseDoc, flags.section, nextText);
|
|
7167
|
-
|
|
7416
|
+
const normalized = ensureDocSections(nextDoc, config.tasks.doc.required_sections);
|
|
7417
|
+
await backend.setTaskDoc(opts.taskId, normalized, updatedBy);
|
|
7168
7418
|
}
|
|
7169
7419
|
const tasksDir = path.join(resolved.gitRoot, config.paths.workflow_dir);
|
|
7170
7420
|
process.stdout.write(`${path.join(tasksDir, opts.taskId, "README.md")}\n`);
|
|
@@ -7234,6 +7484,11 @@ export async function runCli(argv) {
|
|
|
7234
7484
|
return 0;
|
|
7235
7485
|
}
|
|
7236
7486
|
await maybeLoadDotEnv({ cwd: process.cwd(), rootOverride: globals.root });
|
|
7487
|
+
await maybeWarnOnUpdate({
|
|
7488
|
+
currentVersion: getVersion(),
|
|
7489
|
+
skip: globals.noUpdateCheck,
|
|
7490
|
+
jsonErrors: globals.jsonErrors,
|
|
7491
|
+
});
|
|
7237
7492
|
const [namespace, command, ...args] = rest;
|
|
7238
7493
|
if (namespace === "init") {
|
|
7239
7494
|
const initArgs = command ? [command, ...args] : [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-cli.test-helpers.d.ts","sourceRoot":"","sources":["../src/run-cli.test-helpers.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-cli.test-helpers.d.ts","sourceRoot":"","sources":["../src/run-cli.test-helpers.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAkCpD,wBAAgB,sBAAsB,IAAI,IAAI,CAsB7C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,YAAY;;;;EAgC3B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAKrD;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAEjD;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpE;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,CAAC,EAAE;IAC/C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CA+FtE;AAED,wBAAsB,+BAA+B,CAAC,IAAI,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlB;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAiBD;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE;AAED,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,CAS/C;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYpF;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE"}
|
|
@@ -9,6 +9,7 @@ import { defaultConfig } from "@agentplaneorg/core";
|
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
let agentplaneHome = null;
|
|
11
11
|
const originalAgentplaneHome = process.env.AGENTPLANE_HOME;
|
|
12
|
+
const originalNoUpdateCheck = process.env.AGENTPLANE_NO_UPDATE_CHECK;
|
|
12
13
|
const recipeArchiveCache = new Map();
|
|
13
14
|
let gitTemplateRoot = null;
|
|
14
15
|
let gitTemplatePromise = null;
|
|
@@ -31,6 +32,7 @@ export function registerAgentplaneHome() {
|
|
|
31
32
|
beforeAll(async () => {
|
|
32
33
|
agentplaneHome = await mkdtemp(path.join(os.tmpdir(), "agentplane-home-"));
|
|
33
34
|
process.env.AGENTPLANE_HOME = agentplaneHome;
|
|
35
|
+
process.env.AGENTPLANE_NO_UPDATE_CHECK = "1";
|
|
34
36
|
});
|
|
35
37
|
afterAll(async () => {
|
|
36
38
|
if (agentplaneHome) {
|
|
@@ -42,6 +44,12 @@ export function registerAgentplaneHome() {
|
|
|
42
44
|
else {
|
|
43
45
|
process.env.AGENTPLANE_HOME = originalAgentplaneHome;
|
|
44
46
|
}
|
|
47
|
+
if (originalNoUpdateCheck === undefined) {
|
|
48
|
+
delete process.env.AGENTPLANE_NO_UPDATE_CHECK;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
process.env.AGENTPLANE_NO_UPDATE_CHECK = originalNoUpdateCheck;
|
|
52
|
+
}
|
|
45
53
|
});
|
|
46
54
|
}
|
|
47
55
|
export function getAgentplaneHome() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-backend.d.ts","sourceRoot":"","sources":["../src/task-backend.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"task-backend.d.ts","sourceRoot":"","sources":["../src/task-backend.ts"],"names":[],"mappings":"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"}
|
package/dist/task-backend.js
CHANGED
|
@@ -6,6 +6,7 @@ import { loadDotEnv } from "./env.js";
|
|
|
6
6
|
const ID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
7
7
|
const TASK_ID_RE = new RegExp(String.raw `^\d{12}-[${ID_ALPHABET}]{4,}$`);
|
|
8
8
|
const DOC_SECTION_HEADER = "## Summary";
|
|
9
|
+
const DOC_SECTION_HEADER_RE = /^##\s+Summary(?:\s|$|#)/;
|
|
9
10
|
const AUTO_SUMMARY_HEADER = "## Changes Summary (auto)";
|
|
10
11
|
const DEFAULT_DOC_UPDATED_BY = "agentplane";
|
|
11
12
|
const DOC_VERSION = 2;
|
|
@@ -41,13 +42,209 @@ function normalizeDoc(text) {
|
|
|
41
42
|
.join("\n")
|
|
42
43
|
.trim();
|
|
43
44
|
}
|
|
45
|
+
function normalizeDocSectionName(section) {
|
|
46
|
+
return section.trim().replaceAll(/\s+/g, " ").toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
function normalizeSectionLines(lines) {
|
|
49
|
+
const trimmedLines = [...lines];
|
|
50
|
+
while (trimmedLines.length > 0 && trimmedLines[0]?.trim() === "")
|
|
51
|
+
trimmedLines.shift();
|
|
52
|
+
while (trimmedLines.length > 0 && trimmedLines.at(-1)?.trim() === "")
|
|
53
|
+
trimmedLines.pop();
|
|
54
|
+
const out = [];
|
|
55
|
+
let inFence = false;
|
|
56
|
+
let pendingBlank = false;
|
|
57
|
+
for (const line of trimmedLines) {
|
|
58
|
+
const fenceCheck = line.trimStart();
|
|
59
|
+
if (fenceCheck.startsWith("```")) {
|
|
60
|
+
if (pendingBlank) {
|
|
61
|
+
out.push("");
|
|
62
|
+
pendingBlank = false;
|
|
63
|
+
}
|
|
64
|
+
out.push(line);
|
|
65
|
+
inFence = !inFence;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (inFence) {
|
|
69
|
+
out.push(line);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (line.trim() === "") {
|
|
73
|
+
pendingBlank = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (pendingBlank) {
|
|
77
|
+
out.push("");
|
|
78
|
+
pendingBlank = false;
|
|
79
|
+
}
|
|
80
|
+
out.push(line);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function splitCombinedHeadingLines(doc) {
|
|
85
|
+
const lines = doc.replaceAll("\r\n", "\n").split("\n");
|
|
86
|
+
const out = [];
|
|
87
|
+
let inFence = false;
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
const trimmed = line.trimStart();
|
|
90
|
+
if (trimmed.startsWith("```")) {
|
|
91
|
+
inFence = !inFence;
|
|
92
|
+
out.push(line);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!inFence && line.includes("## ")) {
|
|
96
|
+
const matches = [...line.matchAll(/##\s+/g)];
|
|
97
|
+
if (matches.length > 1 && matches[0]?.index === 0) {
|
|
98
|
+
let start = 0;
|
|
99
|
+
for (let i = 1; i < matches.length; i += 1) {
|
|
100
|
+
const idx = matches[i]?.index ?? 0;
|
|
101
|
+
const chunk = line.slice(start, idx).trimEnd();
|
|
102
|
+
if (chunk)
|
|
103
|
+
out.push(chunk);
|
|
104
|
+
start = idx;
|
|
105
|
+
}
|
|
106
|
+
const last = line.slice(start).trimEnd();
|
|
107
|
+
if (last)
|
|
108
|
+
out.push(last);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
out.push(line);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function normalizeTaskDoc(doc) {
|
|
117
|
+
const normalized = doc.replaceAll("\r\n", "\n");
|
|
118
|
+
const trimmed = normalized.replaceAll(/^\n+|\n+$/g, "");
|
|
119
|
+
if (!trimmed)
|
|
120
|
+
return "";
|
|
121
|
+
const lines = splitCombinedHeadingLines(trimmed);
|
|
122
|
+
const sections = new Map();
|
|
123
|
+
const order = [];
|
|
124
|
+
const pendingSeparator = new Set();
|
|
125
|
+
let currentKey = null;
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const match = /^##\s+(.*)$/.exec(line.trim());
|
|
128
|
+
if (match) {
|
|
129
|
+
const title = match[1]?.trim() ?? "";
|
|
130
|
+
const key = normalizeDocSectionName(title);
|
|
131
|
+
if (key) {
|
|
132
|
+
const existing = sections.get(key);
|
|
133
|
+
if (existing) {
|
|
134
|
+
if (existing.lines.some((entry) => entry.trim() !== "")) {
|
|
135
|
+
pendingSeparator.add(key);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
sections.set(key, { title, lines: [] });
|
|
140
|
+
order.push(key);
|
|
141
|
+
}
|
|
142
|
+
currentKey = key;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (currentKey) {
|
|
147
|
+
const entry = sections.get(currentKey);
|
|
148
|
+
if (!entry)
|
|
149
|
+
continue;
|
|
150
|
+
if (pendingSeparator.has(currentKey) && line.trim() !== "") {
|
|
151
|
+
entry.lines.push("");
|
|
152
|
+
pendingSeparator.delete(currentKey);
|
|
153
|
+
}
|
|
154
|
+
entry.lines.push(line);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (order.length === 0)
|
|
158
|
+
return trimmed;
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const key of order) {
|
|
161
|
+
const section = sections.get(key);
|
|
162
|
+
if (!section)
|
|
163
|
+
continue;
|
|
164
|
+
const normalizedLines = normalizeSectionLines(section.lines);
|
|
165
|
+
if (normalizedLines.length > 0) {
|
|
166
|
+
out.push(`## ${section.title}`, "", ...normalizedLines, "");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
out.push(`## ${section.title}`, "", "");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out.join("\n").trimEnd();
|
|
173
|
+
}
|
|
44
174
|
function docChanged(existing, updated) {
|
|
45
175
|
return normalizeDoc(existing) !== normalizeDoc(updated);
|
|
46
176
|
}
|
|
177
|
+
function normalizeUpdatedBy(value) {
|
|
178
|
+
if (typeof value !== "string")
|
|
179
|
+
return "";
|
|
180
|
+
const trimmed = value.trim();
|
|
181
|
+
if (!trimmed)
|
|
182
|
+
return "";
|
|
183
|
+
if (trimmed.toLowerCase() === DEFAULT_DOC_UPDATED_BY.toLowerCase())
|
|
184
|
+
return "";
|
|
185
|
+
return trimmed;
|
|
186
|
+
}
|
|
47
187
|
function ensureDocMetadata(task, updatedBy) {
|
|
48
188
|
task.doc_version = DOC_VERSION;
|
|
49
189
|
task.doc_updated_at = nowIso();
|
|
50
|
-
|
|
190
|
+
const explicit = normalizeUpdatedBy(updatedBy);
|
|
191
|
+
if (updatedBy !== undefined) {
|
|
192
|
+
task.doc_updated_by =
|
|
193
|
+
explicit || resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
task.doc_updated_by = resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY);
|
|
197
|
+
}
|
|
198
|
+
function lastCommentAuthor(comments) {
|
|
199
|
+
if (!Array.isArray(comments))
|
|
200
|
+
return null;
|
|
201
|
+
const entries = comments;
|
|
202
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
203
|
+
const entry = entries[i];
|
|
204
|
+
if (!isRecord(entry))
|
|
205
|
+
continue;
|
|
206
|
+
const author = entry.author;
|
|
207
|
+
if (typeof author === "string") {
|
|
208
|
+
const trimmed = author.trim();
|
|
209
|
+
if (trimmed)
|
|
210
|
+
return trimmed;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
function resolveDocUpdatedByFromFrontmatter(frontmatter, updatedBy, fallback) {
|
|
216
|
+
if (updatedBy !== undefined) {
|
|
217
|
+
const explicit = normalizeUpdatedBy(updatedBy);
|
|
218
|
+
if (explicit)
|
|
219
|
+
return explicit;
|
|
220
|
+
}
|
|
221
|
+
const author = lastCommentAuthor(frontmatter.comments);
|
|
222
|
+
if (author)
|
|
223
|
+
return author;
|
|
224
|
+
const existing = normalizeUpdatedBy(frontmatter.doc_updated_by);
|
|
225
|
+
if (existing)
|
|
226
|
+
return existing;
|
|
227
|
+
const owner = normalizeUpdatedBy(frontmatter.owner);
|
|
228
|
+
if (owner)
|
|
229
|
+
return owner;
|
|
230
|
+
const fallbackValue = normalizeUpdatedBy(fallback);
|
|
231
|
+
return fallbackValue || fallback;
|
|
232
|
+
}
|
|
233
|
+
function resolveDocUpdatedByFromTask(task, fallback) {
|
|
234
|
+
const author = lastCommentAuthor(task.comments);
|
|
235
|
+
if (author)
|
|
236
|
+
return author;
|
|
237
|
+
const existing = normalizeUpdatedBy(task.doc_updated_by);
|
|
238
|
+
if (existing)
|
|
239
|
+
return existing;
|
|
240
|
+
const owner = normalizeUpdatedBy(task.owner);
|
|
241
|
+
if (owner)
|
|
242
|
+
return owner;
|
|
243
|
+
const fallbackValue = normalizeUpdatedBy(fallback);
|
|
244
|
+
return fallbackValue || fallback;
|
|
245
|
+
}
|
|
246
|
+
function isDocSectionHeader(line) {
|
|
247
|
+
return DOC_SECTION_HEADER_RE.test(line.trim());
|
|
51
248
|
}
|
|
52
249
|
export function extractTaskDoc(body) {
|
|
53
250
|
if (!body)
|
|
@@ -55,13 +252,16 @@ export function extractTaskDoc(body) {
|
|
|
55
252
|
const lines = body.split("\n");
|
|
56
253
|
let startIdx = null;
|
|
57
254
|
for (const [idx, line] of lines.entries()) {
|
|
58
|
-
if (line
|
|
255
|
+
if (isDocSectionHeader(line)) {
|
|
59
256
|
startIdx = idx;
|
|
60
257
|
break;
|
|
61
258
|
}
|
|
62
259
|
}
|
|
63
260
|
if (startIdx === null)
|
|
64
261
|
return "";
|
|
262
|
+
if (lines[startIdx]?.trim() !== DOC_SECTION_HEADER) {
|
|
263
|
+
lines[startIdx] = DOC_SECTION_HEADER;
|
|
264
|
+
}
|
|
65
265
|
let endIdx = lines.length;
|
|
66
266
|
for (let idx = startIdx + 1; idx < lines.length; idx++) {
|
|
67
267
|
if (lines[idx]?.trim() === AUTO_SUMMARY_HEADER) {
|
|
@@ -69,15 +269,16 @@ export function extractTaskDoc(body) {
|
|
|
69
269
|
break;
|
|
70
270
|
}
|
|
71
271
|
}
|
|
72
|
-
|
|
272
|
+
const doc = lines.slice(startIdx, endIdx).join("\n").trimEnd();
|
|
273
|
+
return normalizeTaskDoc(doc);
|
|
73
274
|
}
|
|
74
275
|
export function mergeTaskDoc(body, doc) {
|
|
75
|
-
const docText = String(doc ?? "")
|
|
276
|
+
const docText = normalizeTaskDoc(String(doc ?? ""));
|
|
76
277
|
if (docText) {
|
|
77
278
|
const lines = body ? body.split("\n") : [];
|
|
78
279
|
let prefixIdx = null;
|
|
79
280
|
for (const [idx, line] of lines.entries()) {
|
|
80
|
-
if (line
|
|
281
|
+
if (isDocSectionHeader(line)) {
|
|
81
282
|
prefixIdx = idx;
|
|
82
283
|
break;
|
|
83
284
|
}
|
|
@@ -211,7 +412,7 @@ function taskDataToExport(task) {
|
|
|
211
412
|
: [],
|
|
212
413
|
doc_version: task.doc_version ?? DOC_VERSION,
|
|
213
414
|
doc_updated_at: task.doc_updated_at ?? "",
|
|
214
|
-
doc_updated_by: task
|
|
415
|
+
doc_updated_by: resolveDocUpdatedByFromTask(task, DEFAULT_DOC_UPDATED_BY),
|
|
215
416
|
dirty: Boolean(task.dirty),
|
|
216
417
|
id_source: task.id_source ?? "generated",
|
|
217
418
|
};
|
|
@@ -383,7 +584,7 @@ export class LocalBackend {
|
|
|
383
584
|
if (docChanged(existingDoc, docText)) {
|
|
384
585
|
payload.doc_version = DOC_VERSION;
|
|
385
586
|
payload.doc_updated_at = nowIso();
|
|
386
|
-
payload.doc_updated_by = this.updatedBy;
|
|
587
|
+
payload.doc_updated_by = resolveDocUpdatedByFromTask(task, this.updatedBy);
|
|
387
588
|
}
|
|
388
589
|
}
|
|
389
590
|
if (payload.doc_version !== DOC_VERSION) {
|
|
@@ -393,7 +594,7 @@ export class LocalBackend {
|
|
|
393
594
|
payload.doc_updated_at = nowIso();
|
|
394
595
|
}
|
|
395
596
|
if (payload.doc_updated_by === undefined || payload.doc_updated_by === "") {
|
|
396
|
-
payload.doc_updated_by = this.updatedBy;
|
|
597
|
+
payload.doc_updated_by = resolveDocUpdatedByFromTask(task, this.updatedBy);
|
|
397
598
|
}
|
|
398
599
|
await mkdir(path.dirname(readme), { recursive: true });
|
|
399
600
|
const text = renderTaskReadme(payload, body || "");
|
|
@@ -409,7 +610,7 @@ export class LocalBackend {
|
|
|
409
610
|
if (docChanged(extractTaskDoc(parsed.body), docText) || !frontmatter.doc_updated_at) {
|
|
410
611
|
frontmatter.doc_version = DOC_VERSION;
|
|
411
612
|
frontmatter.doc_updated_at = nowIso();
|
|
412
|
-
frontmatter.doc_updated_by = updatedBy
|
|
613
|
+
frontmatter.doc_updated_by = resolveDocUpdatedByFromFrontmatter(frontmatter, updatedBy, this.updatedBy);
|
|
413
614
|
}
|
|
414
615
|
if (frontmatter.doc_version !== DOC_VERSION) {
|
|
415
616
|
frontmatter.doc_version = DOC_VERSION;
|
|
@@ -424,7 +625,7 @@ export class LocalBackend {
|
|
|
424
625
|
const frontmatter = { ...parsed.frontmatter };
|
|
425
626
|
frontmatter.doc_version = DOC_VERSION;
|
|
426
627
|
frontmatter.doc_updated_at = nowIso();
|
|
427
|
-
frontmatter.doc_updated_by = updatedBy
|
|
628
|
+
frontmatter.doc_updated_by = resolveDocUpdatedByFromFrontmatter(frontmatter, updatedBy, this.updatedBy);
|
|
428
629
|
const next = renderTaskReadme(frontmatter, parsed.body || "");
|
|
429
630
|
await writeFile(readme, next.endsWith("\n") ? next : `${next}\n`, "utf8");
|
|
430
631
|
}
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentplane",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Agent Plane CLI for task workflows, recipes, and project automation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentplane",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"prepublishOnly": "npm run prepack"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@agentplaneorg/core": "0.1.
|
|
57
|
+
"@agentplaneorg/core": "0.1.4"
|
|
58
58
|
}
|
|
59
59
|
}
|