agentplane 0.2.4 → 0.2.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/assets/AGENTS.md +1 -1
- package/dist/cli/command-guide.js +2 -2
- package/dist/commands/branch/work-start.command.d.ts.map +1 -1
- package/dist/commands/branch/work-start.command.js +3 -2
- package/dist/commands/branch/work-start.d.ts.map +1 -1
- package/dist/commands/branch/work-start.js +95 -2
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +24 -0
- package/package.json +2 -2
package/assets/AGENTS.md
CHANGED
|
@@ -455,7 +455,7 @@ Always follow `workflow_mode` from `.agentplane/config.json`.
|
|
|
455
455
|
Rules:
|
|
456
456
|
|
|
457
457
|
- Do all work in the current checkout.
|
|
458
|
-
-
|
|
458
|
+
- In `direct` (single working directory), agentplane uses a single-stream workflow in the current checkout. `agentplane work start <task-id> --agent <ROLE> --slug <slug>` records the active task and keeps the current branch (no task branches).
|
|
459
459
|
- Do not use worktrees in `direct`. `agentplane work start ... --worktree` is `branch_pr`-only.
|
|
460
460
|
- If you only need artifacts/docs without switching branches, prefer `agentplane task scaffold <task-id>`.
|
|
461
461
|
|
|
@@ -89,7 +89,7 @@ const ROLE_GUIDES = [
|
|
|
89
89
|
{
|
|
90
90
|
role: "CODER",
|
|
91
91
|
lines: [
|
|
92
|
-
"- direct mode:
|
|
92
|
+
"- direct mode: single-stream in the current checkout; `agentplane work start <task-id> --agent <ROLE> --slug <slug>` records the active task and keeps the current branch (no task branches). Use `agentplane task scaffold <task-id>` for docs without switching context.",
|
|
93
93
|
"- branch_pr: `agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
94
94
|
'- Status updates: `agentplane start <task-id> --author <ROLE> --body "Start: ..."` / `agentplane block <task-id> --author <ROLE> --body "Blocked: ..."`',
|
|
95
95
|
"- Verify Steps: `agentplane task verify-show <task-id>` (use as the verification contract before recording results).",
|
|
@@ -101,7 +101,7 @@ const ROLE_GUIDES = [
|
|
|
101
101
|
{
|
|
102
102
|
role: "TESTER",
|
|
103
103
|
lines: [
|
|
104
|
-
"- direct mode:
|
|
104
|
+
"- direct mode: single-stream in the current checkout; `agentplane work start <task-id> --agent <ROLE> --slug <slug>` records the active task and keeps the current branch (no task branches). Use `agentplane task scaffold <task-id>` for docs without switching context.",
|
|
105
105
|
"- branch_pr: `agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
106
106
|
'- Status updates: `agentplane start <task-id> --author <ROLE> --body "Start: ..."` / `agentplane block <task-id> --author <ROLE> --body "Blocked: ..."`',
|
|
107
107
|
"- Verify Steps: `agentplane task verify-show <task-id>` (treat as the verification contract).",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-start.command.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAc,MAAM,wBAAwB,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,eAAe,
|
|
1
|
+
{"version":3,"file":"work-start.command.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAc,MAAM,wBAAwB,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,eAAe,CAkDtD,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,CAAC,sBAAsB,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,GAClE,cAAc,CAAC,eAAe,CAAC,CA4BjC"}
|
|
@@ -3,7 +3,7 @@ import { cmdWorkStart } from "./index.js";
|
|
|
3
3
|
export const workStartSpec = {
|
|
4
4
|
id: ["work", "start"],
|
|
5
5
|
group: "Work",
|
|
6
|
-
summary: "
|
|
6
|
+
summary: "Prepare the workspace for a task (direct: single-stream on current branch; branch_pr: task branch/worktree).",
|
|
7
7
|
args: [
|
|
8
8
|
{ name: "task-id", required: true, valueHint: "<task-id>", description: "Existing task id." },
|
|
9
9
|
],
|
|
@@ -36,10 +36,11 @@ export const workStartSpec = {
|
|
|
36
36
|
examples: [
|
|
37
37
|
{
|
|
38
38
|
cmd: "agentplane work start 202602030608-F1Q8AB --agent CODER --slug cli",
|
|
39
|
-
why: "Start work
|
|
39
|
+
why: "Start work (direct mode records the active task; branch_pr uses a dedicated task branch/worktree).",
|
|
40
40
|
},
|
|
41
41
|
],
|
|
42
42
|
notes: [
|
|
43
|
+
"When workflow_mode=direct, agentplane does not create task branches; it records a single active task for the workspace.",
|
|
43
44
|
"When workflow_mode=branch_pr, --worktree is required and the command must be run on the base branch.",
|
|
44
45
|
],
|
|
45
46
|
parse: (raw) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-start.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"work-start.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.ts"],"names":[],"mappings":"AAaA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAuFnC,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkJlB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveBaseBranch } from "@agentplaneorg/core";
|
|
4
4
|
import { mapBackendError } from "../../cli/error-map.js";
|
|
5
5
|
import { fileExists } from "../../cli/fs-utils.js";
|
|
6
|
+
import { exitCodeForError } from "../../cli/exit-codes.js";
|
|
6
7
|
import { successMessage } from "../../cli/output.js";
|
|
7
8
|
import { CliError } from "../../shared/errors.js";
|
|
8
9
|
import { execFileAsync, gitEnv } from "../shared/git.js";
|
|
@@ -11,6 +12,76 @@ import { isPathWithin } from "../shared/path.js";
|
|
|
11
12
|
import { loadBackendTask, loadCommandContext, } from "../shared/task-backend.js";
|
|
12
13
|
import { ensurePlanApprovedIfRequired } from "../task/shared.js";
|
|
13
14
|
import { validateWorkAgent, validateWorkSlug } from "./internal/work-validate.js";
|
|
15
|
+
function directWorkLockPath(agentplaneDir) {
|
|
16
|
+
// Intentionally under cache/ so it stays out of git by default.
|
|
17
|
+
return path.join(agentplaneDir, "cache", "direct-work.json");
|
|
18
|
+
}
|
|
19
|
+
async function readDirectWorkLock(agentplaneDir) {
|
|
20
|
+
try {
|
|
21
|
+
const text = await readFile(directWorkLockPath(agentplaneDir), "utf8");
|
|
22
|
+
const parsed = JSON.parse(text);
|
|
23
|
+
if (!parsed || typeof parsed !== "object")
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof parsed.task_id !== "string" ||
|
|
26
|
+
typeof parsed.agent !== "string" ||
|
|
27
|
+
typeof parsed.slug !== "string" ||
|
|
28
|
+
typeof parsed.branch !== "string" ||
|
|
29
|
+
typeof parsed.started_at !== "string") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function writeDirectWorkLock(agentplaneDir, lock) {
|
|
39
|
+
const dir = path.dirname(directWorkLockPath(agentplaneDir));
|
|
40
|
+
await mkdir(dir, { recursive: true });
|
|
41
|
+
await writeFile(directWorkLockPath(agentplaneDir), JSON.stringify(lock, null, 2) + "\n", "utf8");
|
|
42
|
+
}
|
|
43
|
+
async function ensureGitClean(gitRoot) {
|
|
44
|
+
const { stdout } = await execFileAsync("git", ["status", "--porcelain"], {
|
|
45
|
+
cwd: gitRoot,
|
|
46
|
+
env: gitEnv(),
|
|
47
|
+
});
|
|
48
|
+
const lines = stdout
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map((line) => line.trimEnd())
|
|
51
|
+
.filter((line) => line.trim().length > 0);
|
|
52
|
+
if (lines.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
// Allow task workflow artifacts to be dirty. In direct mode we want a single-stream
|
|
55
|
+
// workflow without task branches, but we still expect task docs to change.
|
|
56
|
+
const allowedPrefixes = [
|
|
57
|
+
".agentplane/tasks/",
|
|
58
|
+
".agentplane/tasks.json",
|
|
59
|
+
".agentplane/cache/",
|
|
60
|
+
".agentplane/.upgrade/",
|
|
61
|
+
".agentplane/upgrade/",
|
|
62
|
+
];
|
|
63
|
+
const isAllowed = (p) => allowedPrefixes.some((prefix) => p.startsWith(prefix));
|
|
64
|
+
const dirty = lines
|
|
65
|
+
.map((line) => {
|
|
66
|
+
// Format: XY <path> (we only need the path-ish tail).
|
|
67
|
+
const rest = line.slice(2).trim();
|
|
68
|
+
if (!rest)
|
|
69
|
+
return "";
|
|
70
|
+
// Rename/copy format: "old -> new"
|
|
71
|
+
const arrow = rest.lastIndexOf(" -> ");
|
|
72
|
+
if (arrow === -1)
|
|
73
|
+
return rest;
|
|
74
|
+
return rest.slice(arrow + 4).trim();
|
|
75
|
+
})
|
|
76
|
+
.filter((p) => p.length > 0 && !isAllowed(p));
|
|
77
|
+
if (dirty.length === 0)
|
|
78
|
+
return;
|
|
79
|
+
throw new CliError({
|
|
80
|
+
exitCode: exitCodeForError("E_GIT"),
|
|
81
|
+
code: "E_GIT",
|
|
82
|
+
message: "Working tree has non-task changes. In workflow_mode=direct, agentplane runs tasks in a single stream on the current branch; commit/stash your changes before starting a different task.",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
14
85
|
export async function cmdWorkStart(opts) {
|
|
15
86
|
try {
|
|
16
87
|
validateWorkAgent(opts.agent);
|
|
@@ -42,6 +113,28 @@ export async function cmdWorkStart(opts) {
|
|
|
42
113
|
});
|
|
43
114
|
ensurePlanApprovedIfRequired(task, config);
|
|
44
115
|
const currentBranch = await gitCurrentBranch(resolved.gitRoot);
|
|
116
|
+
// direct mode: single-stream, no task branches.
|
|
117
|
+
if (mode === "direct") {
|
|
118
|
+
await ensureGitClean(resolved.gitRoot);
|
|
119
|
+
const existingLock = await readDirectWorkLock(resolved.agentplaneDir);
|
|
120
|
+
if (existingLock && existingLock.task_id !== opts.taskId) {
|
|
121
|
+
throw new CliError({
|
|
122
|
+
exitCode: 2,
|
|
123
|
+
code: "E_USAGE",
|
|
124
|
+
message: `Another task is already active in this workspace (workflow_mode=direct): ${existingLock.task_id}. ` +
|
|
125
|
+
`Finish it first, or delete ${path.relative(resolved.gitRoot, directWorkLockPath(resolved.agentplaneDir))} to override.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
await writeDirectWorkLock(resolved.agentplaneDir, {
|
|
129
|
+
task_id: opts.taskId,
|
|
130
|
+
agent: opts.agent,
|
|
131
|
+
slug: opts.slug.trim(),
|
|
132
|
+
branch: currentBranch,
|
|
133
|
+
started_at: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
process.stdout.write(`${successMessage("work start", opts.taskId, `mode=direct branch=${currentBranch}`)}\n`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
45
138
|
let baseRef = currentBranch;
|
|
46
139
|
if (mode === "branch_pr") {
|
|
47
140
|
const baseBranch = await resolveBaseBranch({
|
|
@@ -59,7 +152,7 @@ export async function cmdWorkStart(opts) {
|
|
|
59
152
|
}
|
|
60
153
|
if (currentBranch !== baseBranch) {
|
|
61
154
|
throw new CliError({
|
|
62
|
-
exitCode:
|
|
155
|
+
exitCode: exitCodeForError("E_GIT"),
|
|
63
156
|
code: "E_GIT",
|
|
64
157
|
message: `work start must be run on base branch ${baseBranch} (current: ${currentBranch})`,
|
|
65
158
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA+BnC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,OAAO,CAAC;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgMlB"}
|
|
@@ -2,10 +2,28 @@ import { mapBackendError } from "../../cli/error-map.js";
|
|
|
2
2
|
import { successMessage } from "../../cli/output.js";
|
|
3
3
|
import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
|
|
4
4
|
import { CliError } from "../../shared/errors.js";
|
|
5
|
+
import { readFile, rm } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
5
7
|
import { commitFromComment } from "../guard/index.js";
|
|
6
8
|
import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
|
|
7
9
|
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
8
10
|
import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, ensureVerificationSatisfiedIfRequired, nowIso, readCommitInfo, readHeadCommit, requireStructuredComment, } from "./shared.js";
|
|
11
|
+
async function clearDirectWorkLockIfMatches(opts) {
|
|
12
|
+
const lockPath = path.join(opts.agentplaneDir, "cache", "direct-work.json");
|
|
13
|
+
try {
|
|
14
|
+
const text = await readFile(lockPath, "utf8");
|
|
15
|
+
const parsed = JSON.parse(text);
|
|
16
|
+
const lockTaskId = parsed && typeof parsed.task_id === "string" ? parsed.task_id : null;
|
|
17
|
+
if (!lockTaskId)
|
|
18
|
+
return;
|
|
19
|
+
if (!opts.taskIds.includes(lockTaskId))
|
|
20
|
+
return;
|
|
21
|
+
await rm(lockPath, { force: true });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// best-effort
|
|
25
|
+
}
|
|
26
|
+
}
|
|
9
27
|
export async function cmdFinish(opts) {
|
|
10
28
|
try {
|
|
11
29
|
const ctx = opts.ctx ??
|
|
@@ -162,6 +180,12 @@ export async function cmdFinish(opts) {
|
|
|
162
180
|
config: ctx.config,
|
|
163
181
|
});
|
|
164
182
|
}
|
|
183
|
+
if (ctx.config.workflow_mode === "direct") {
|
|
184
|
+
await clearDirectWorkLockIfMatches({
|
|
185
|
+
agentplaneDir: ctx.resolvedProject.agentplaneDir,
|
|
186
|
+
taskIds: opts.taskIds,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
165
189
|
if (!opts.quiet) {
|
|
166
190
|
process.stdout.write(`${successMessage("finished")}\n`);
|
|
167
191
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentplane",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Agent Plane CLI for task workflows, recipes, and project automation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentplane",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"prepublishOnly": "npm run prepack"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@agentplaneorg/core": "0.2.
|
|
57
|
+
"@agentplaneorg/core": "0.2.5",
|
|
58
58
|
"yauzl": "^2.10.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|