agentplane 0.2.5 → 0.2.6

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.
Files changed (44) hide show
  1. package/assets/AGENTS.md +39 -42
  2. package/bin/agentplane.js +109 -1
  3. package/dist/cli/critical/cli-runner.d.ts +2 -0
  4. package/dist/cli/critical/cli-runner.d.ts.map +1 -0
  5. package/dist/cli/critical/cli-runner.js +11 -0
  6. package/dist/cli/critical/harness.d.ts +22 -0
  7. package/dist/cli/critical/harness.d.ts.map +1 -0
  8. package/dist/cli/critical/harness.js +164 -0
  9. package/dist/cli/run-cli/commands/init/git.d.ts.map +1 -1
  10. package/dist/cli/run-cli/commands/init/git.js +9 -7
  11. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  12. package/dist/cli/run-cli/commands/init.js +2 -6
  13. package/dist/cli/run-cli.d.ts.map +1 -1
  14. package/dist/cli/run-cli.js +4 -1
  15. package/dist/commands/guard/impl/comment-commit.d.ts +1 -0
  16. package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
  17. package/dist/commands/guard/impl/comment-commit.js +5 -1
  18. package/dist/commands/guard/impl/env.d.ts +2 -0
  19. package/dist/commands/guard/impl/env.d.ts.map +1 -1
  20. package/dist/commands/guard/impl/env.js +2 -0
  21. package/dist/commands/hooks/index.d.ts.map +1 -1
  22. package/dist/commands/hooks/index.js +42 -6
  23. package/dist/commands/recipes/impl/project.d.ts.map +1 -1
  24. package/dist/commands/recipes/impl/project.js +5 -3
  25. package/dist/commands/shared/git-ops.d.ts.map +1 -1
  26. package/dist/commands/shared/git-ops.js +7 -2
  27. package/dist/commands/task/block.d.ts.map +1 -1
  28. package/dist/commands/task/block.js +21 -3
  29. package/dist/commands/task/finish.d.ts.map +1 -1
  30. package/dist/commands/task/finish.js +29 -1
  31. package/dist/commands/task/shared.d.ts +1 -0
  32. package/dist/commands/task/shared.d.ts.map +1 -1
  33. package/dist/commands/task/shared.js +5 -0
  34. package/dist/commands/task/start.d.ts.map +1 -1
  35. package/dist/commands/task/start.js +21 -3
  36. package/dist/policy/rules/commit-subject.d.ts.map +1 -1
  37. package/dist/policy/rules/commit-subject.js +1 -13
  38. package/dist/shared/agent-emoji.d.ts +5 -0
  39. package/dist/shared/agent-emoji.d.ts.map +1 -0
  40. package/dist/shared/agent-emoji.js +50 -0
  41. package/dist/shared/direct-work-lock.d.ts +10 -0
  42. package/dist/shared/direct-work-lock.d.ts.map +1 -0
  43. package/dist/shared/direct-work-lock.js +24 -0
  44. package/package.json +2 -2
package/assets/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ default_initiator: ORCHESTRATOR
6
6
 
7
7
  # PURPOSE
8
8
 
9
- This document defines the **behavioral policy** for agents operating in an agentplane-managed development workspace.
9
+ This document defines the **behavioral policy** for Codex-style agents operating in this repository (CLI + VS Code extension).
10
10
  Goal: **deterministic execution**, **tight guardrails**, and **minimum accidental changes** by enforcing a strict, inspectable pipeline.
11
11
 
12
12
  This policy is designed to be the single, authoritative instruction set the agent follows when invoked in a folder containing this file.
@@ -28,11 +28,9 @@ If two sources conflict, prefer the higher-priority source.
28
28
 
29
29
  All commands in this policy are written as `agentplane ...` and MUST use the `agentplane` CLI available on `PATH`.
30
30
 
31
- Do not use repository-relative entrypoints (for example `node .../bin/agentplane.js`) in instructions or automation.
32
-
33
31
  ## Scope boundary
34
32
 
35
- - All operations must remain within the workspace unless explicitly approved (see Approval Gates + Overrides).
33
+ - All operations must remain within the repository unless explicitly approved (see Approval Gates + Overrides).
36
34
  - Do not read/write global user files (`~`, `/etc`, keychains, ssh keys, global git config) unless explicitly approved and necessary.
37
35
 
38
36
  ## Agent roles (authority boundaries)
@@ -62,16 +60,16 @@ Execution agents are defined by JSON files under `.agentplane/agents/*.json`. Th
62
60
 
63
61
  ## Definitions (remove ambiguity)
64
62
 
65
- - **Read-only inspection**: commands that may read workspace state but must not change tracked files or commit history.
63
+ - **Read-only inspection**: commands that may read repo state but must not change tracked files or commit history.
66
64
  Examples: `agentplane config show`, `agentplane task list`, `agentplane task show`, `git status`, `git diff`, `cat`, `grep`.
67
- - **Mutating action**: anything that can change tracked files, task state, commits, branches, or outside-workspace state.
65
+ - **Mutating action**: anything that can change tracked files, task state, commits, branches, or outside-repo state.
68
66
  Examples: `agentplane task new/update/doc set/plan set/start/finish/verify`, `git commit`, `git checkout`, `bun install`.
69
67
 
70
68
  If unsure whether an action mutates state, treat it as mutating.
71
69
 
72
70
  ## Truthfulness & safety (hard invariants)
73
71
 
74
- - Never invent facts about workspace state. Prefer inspection over guessing.
72
+ - Never invent facts about repo state. Prefer inspection over guessing.
75
73
  - Never modify `.agentplane/tasks.json` manually. It is an **export-only snapshot** generated via `agentplane task export`.
76
74
  - Never expose raw internal chain-of-thought. Use structured artifacts instead (see OUTPUT CONTRACTS).
77
75
 
@@ -82,7 +80,7 @@ If unsure whether an action mutates state, treat it as mutating.
82
80
  - “Clean” means: **no tracked changes** (`git status --short --untracked-files=no` is empty).
83
81
  - If untracked files interfere with verify/guardrails or fall inside the task scope paths, surface them as a risk and request approval before acting.
84
82
 
85
- ## Approval gates (network vs outside-workspace)
83
+ ## Approval gates (network vs outside-repo)
86
84
 
87
85
  ### Network
88
86
 
@@ -97,20 +95,15 @@ Network use includes (non-exhaustive):
97
95
  - `git fetch`, `git pull`
98
96
  - calling external HTTP APIs or remote services
99
97
 
100
- ### Outside-workspace
98
+ ### Outside-repo
101
99
 
102
- Outside-workspace reading/writing is **always prohibited** unless the user explicitly approves it (regardless of `require_network`).
100
+ Outside-repo reading/writing is **always prohibited** unless the user explicitly approves it (regardless of `require_network`).
103
101
 
104
- Outside-workspace includes (non-exhaustive):
102
+ Outside-repo includes (non-exhaustive):
105
103
 
106
- - reading/writing outside the workspace (`~`, `/etc`, global configs)
104
+ - reading/writing outside the repo (`~`, `/etc`, global configs)
107
105
  - modifying keychains, ssh keys, credential stores
108
- - any tool that mutates outside-workspace state
109
-
110
- ### Interactive vs non-interactive runs (approvals mechanics)
111
-
112
- - Interactive: the user can approve prompts (for example network use) during the run.
113
- - Non-interactive (CI, scripted runs): approvals MUST be expressed via flags/config up front (for example `--yes`). If an approval is required and not granted, stop and request explicit user instruction.
106
+ - any tool that mutates outside-repo state
114
107
 
115
108
  ---
116
109
 
@@ -156,18 +149,13 @@ This is the required substitute for raw chain-of-thought.
156
149
 
157
150
  Preflight is **read-only inspection**. It is allowed before user approval.
158
151
 
159
- Before any planning or execution, ORCHESTRATOR must:
152
+ Before any planning or execution, ORCHESTRATOR must run:
160
153
 
161
- 1. Determine whether the current directory is an initialized agentplane workspace (e.g. `.agentplane/config.json` exists).
162
- 2. Attempt git inspection:
163
- - `git status --short --untracked-files=no`
164
- - `git rev-parse --abbrev-ref HEAD`
165
- 3. If the workspace is initialized, also run:
166
- - `agentplane config show`
167
- - `agentplane quickstart` (CLI instructions)
168
- - `agentplane task list`
169
-
170
- If a command fails because the workspace is not initialized or not a git repo, record that fact in the Preflight Summary instead of guessing or proceeding with mutating actions.
154
+ 1. `agentplane config show`
155
+ 2. `agentplane quickstart` (CLI instructions)
156
+ 3. `agentplane task list`
157
+ 4. `git status --short --untracked-files=no`
158
+ 5. `git rev-parse --abbrev-ref HEAD`
171
159
 
172
160
  Then report a **Preflight Summary** (do not dump full config or quickstart text).
173
161
 
@@ -178,8 +166,6 @@ You MUST explicitly state:
178
166
  - Config loaded: yes/no
179
167
  - CLI instructions loaded: yes/no
180
168
  - Task list loaded: yes/no
181
- - Workspace initialized: yes/no
182
- - Git repository detected: yes/no
183
169
  - Working tree clean (tracked-only): yes/no
184
170
  - Current git branch: `<name>`
185
171
  - `workflow_mode`: `direct` / `branch_pr` / unknown
@@ -187,7 +173,7 @@ You MUST explicitly state:
187
173
  - `require_plan`: true/false/unknown
188
174
  - `require_verify`: true/false/unknown
189
175
  - `require_network`: true/false/unknown
190
- - Outside-workspace: not needed / needed (if needed, requires explicit user approval)
176
+ - Outside-repo: not needed / needed (if needed, requires explicit user approval)
191
177
 
192
178
  Do not output the full contents of config or quickstart unless the user explicitly asks.
193
179
 
@@ -199,7 +185,7 @@ Do not output the full contents of config or quickstart unless the user explicit
199
185
  - ORCHESTRATOR starts by producing a top-level plan + task decomposition.
200
186
  - **Before explicit user approval, do not perform mutating actions.**
201
187
  - Allowed: read-only inspection (including preflight).
202
- - Prohibited: creating/updating tasks, editing files, starting/finishing tasks, commits, branching, verify runs that mutate task state, network use, outside-workspace access.
188
+ - Prohibited: creating/updating tasks, editing files, starting/finishing tasks, commits, branching, verify runs that mutate task state, network use, outside-repo access.
203
189
 
204
190
  ---
205
191
 
@@ -219,7 +205,7 @@ ORCHESTRATOR MUST produce:
219
205
  - **Decomposition**
220
206
  - Atomic tasks assignable to existing agents
221
207
  - **Approvals**
222
- - Whether network and/or outside-workspace actions will be needed
208
+ - Whether network and/or outside-repo actions will be needed
223
209
  - Any requested overrides (see Override Protocol)
224
210
  - **Verification criteria**
225
211
  - What will be considered "done" + checks to run
@@ -234,7 +220,7 @@ ORCHESTRATOR MUST produce:
234
220
  - PLANNER creates any additional tasks from the approved decomposition.
235
221
  - Task IDs are referenced in comments/notes for traceability.
236
222
 
237
- **Task tracking is mandatory** for any work that changes workspace state. Exceptions require explicit user approval (Override Protocol).
223
+ **Task tracking is mandatory** for any work that changes repo state. Exceptions require explicit user approval (Override Protocol).
238
224
 
239
225
  ---
240
226
 
@@ -244,7 +230,7 @@ Overrides exist to let the user intentionally relax guardrails **in a controlled
244
230
 
245
231
  ## Hard invariants (cannot be overridden)
246
232
 
247
- - No fabricated workspace facts.
233
+ - No fabricated repo facts.
248
234
  - No raw chain-of-thought.
249
235
  - No manual editing of `.agentplane/tasks.json` (exports are generated, not edited).
250
236
 
@@ -253,7 +239,7 @@ Overrides exist to let the user intentionally relax guardrails **in a controlled
253
239
  Common overridable guardrails:
254
240
 
255
241
  - **Network**: allow network access even when `require_network=true`.
256
- - **Outside-workspace**: allow reading/writing outside the workspace (scoped).
242
+ - **Outside-repo**: allow reading/writing outside the repo (scoped).
257
243
  - **Pipeline**: skip/relax steps (e.g., skip task tracking for analysis-only; skip exports).
258
244
  - **Tooling**: allow direct `git` operations when no agentplane command exists (commit/push).
259
245
  - **Force flags**: allow `--force` status transitions / dependency bypass.
@@ -282,7 +268,7 @@ Any approved override MUST be recorded:
282
268
 
283
269
  ## Golden rule
284
270
 
285
- If an agent changes workspace state, that work must be traceable to a task ID and a filled task README.
271
+ If an agent changes repo state, that work must be traceable to a task ID and a filled task README.
286
272
 
287
273
  ## Scaffold is mandatory
288
274
 
@@ -396,9 +382,7 @@ If config sets `agents.approvals.require_plan=true`:
396
382
 
397
383
  # COMMIT WORKFLOW
398
384
 
399
- Default: commits and pushes should go through `agentplane` commands (instead of direct `git commit`/`git push`) to enforce policy and allowlists.
400
-
401
- Override: direct git operations are allowed only with explicit user approval, and must be logged under the task `## Notes` → `### Approvals / Overrides`.
385
+ - Commits and pushes must go through `agentplane` commands (no direct `git commit`/`git push`) unless explicitly overridden.
402
386
 
403
387
  ## Commit message semantics (canonical)
404
388
 
@@ -431,12 +415,25 @@ In this mode:
431
415
 
432
416
  `<emoji> <suffix> <scope>: <summary>`
433
417
 
418
+ `<suffix>` rules:
419
+
420
+ - Task commits: `<suffix>` must equal the task id suffix (e.g. task `202601010101-ABCDEF` -> `ABCDEF`).
421
+ - Non-task commits: `<suffix>` may be omitted. Preferred: `<emoji> <scope>: <summary>`.
422
+ - Optional explicit non-task suffix: `DEV` is allowed as `<emoji> DEV <scope>: <summary>`.
423
+
434
424
  Recommended action/status emojis:
435
425
 
436
426
  - `🚧` start / DOING
437
427
  - `⛔` blocked / BLOCKED
438
428
  - `✅` finish / DONE
439
429
 
430
+ Executor agent emoji policy (status/comment-driven commits):
431
+
432
+ - In `workflow_mode=direct`, status/comment-driven commits prefer the active `work start` lock (`.agentplane/cache/direct-work.json`) when present.
433
+ - The emoji for status/comment-driven commits is derived from the executor agent id (recorded by `agentplane work start ... --agent <ID>`).
434
+ - Users may override the emoji per agent by adding `commit_emoji` to `.agentplane/agents/<ID>.json`.
435
+ - Finish commits MUST use `✅` (enforced by CLI and by the `commit-msg` hook for agentplane-generated commits).
436
+
440
437
  Agents must not reinterpret `-m` as "body-only" or "comment-only". `-m` is a commit message.
441
438
 
442
439
  ## Allowlist staging (guardrails)
@@ -509,7 +506,7 @@ Re-approval is required if any of the following becomes true:
509
506
 
510
507
  - Scope expands beyond the approved in-scope paths/artifacts.
511
508
  - New tasks are needed that were not in the approved decomposition.
512
- - Any network or outside-workspace access becomes necessary (and was not approved).
509
+ - Any network or outside-repo access becomes necessary (and was not approved).
513
510
  - Verification criteria change materially.
514
511
  - Plan changes materially for an in-flight task (update plan -> plan approval returns to pending).
515
512
  - Guardrails require `--force` to proceed.
package/bin/agentplane.js CHANGED
@@ -1,2 +1,110 @@
1
1
  #!/usr/bin/env node
2
- import "../dist/cli.js";
2
+ import path from "node:path";
3
+ import { readdir, stat } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ async function exists(p) {
7
+ try {
8
+ await stat(p);
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function isTestLikePath(absPath) {
16
+ // The repo build does not emit test files to dist. If we treat test mtimes as
17
+ // "src is newer than dist", we can block normal commits that only change tests.
18
+ const normalized = absPath.replaceAll("\\", "/");
19
+ if (normalized.includes("/__snapshots__/")) return true;
20
+ if (normalized.endsWith(".snap")) return true;
21
+ if (/\.(unit\.)?test\.[cm]?ts$/.test(normalized)) return true;
22
+ if (/\.(unit\.)?test\.tsx$/.test(normalized)) return true;
23
+ return false;
24
+ }
25
+
26
+ // Keep this file dependency-free and simple: rely on directory mtime scans below.
27
+ async function newestMtimeMsInDir(dir) {
28
+ let newest = 0;
29
+ const stack = [dir];
30
+ while (stack.length > 0) {
31
+ const current = stack.pop();
32
+ if (!current) continue;
33
+ let entries;
34
+ try {
35
+ entries = await readdir(current, { withFileTypes: true });
36
+ } catch {
37
+ continue;
38
+ }
39
+ for (const entry of entries) {
40
+ const abs = path.join(current, entry.name);
41
+ if (entry.isDirectory()) {
42
+ stack.push(abs);
43
+ continue;
44
+ }
45
+ if (!entry.isFile()) continue;
46
+ if (isTestLikePath(abs)) continue;
47
+ try {
48
+ const s = await stat(abs);
49
+ if (s.mtimeMs > newest) newest = s.mtimeMs;
50
+ } catch {
51
+ // ignore
52
+ }
53
+ }
54
+ }
55
+ return newest;
56
+ }
57
+
58
+ async function assertDistUpToDate() {
59
+ const here = path.dirname(fileURLToPath(import.meta.url));
60
+ const agentplaneRoot = path.resolve(here, "..");
61
+ const inRepo = await exists(path.join(agentplaneRoot, "src", "cli.ts"));
62
+ if (!inRepo) return true;
63
+
64
+ const allowStale = (process.env.AGENTPLANE_DEV_ALLOW_STALE_DIST ?? "").trim() === "1";
65
+ const agentplaneDistDir = path.join(agentplaneRoot, "dist");
66
+ if (!(await exists(agentplaneDistDir))) {
67
+ process.stderr.write(
68
+ "error: agentplane dist is missing for this repo checkout.\n" +
69
+ "Fix:\n" +
70
+ " bun run --filter=@agentplaneorg/core build\n" +
71
+ " bun run --filter=agentplane build\n",
72
+ );
73
+ process.exitCode = 2;
74
+ return false;
75
+ }
76
+
77
+ const agentplaneSrcDir = path.join(agentplaneRoot, "src");
78
+ const agentplaneSrcNewest = await newestMtimeMsInDir(agentplaneSrcDir);
79
+ const agentplaneDistNewest = await newestMtimeMsInDir(agentplaneDistDir);
80
+ const isStaleAgentplane = agentplaneSrcNewest > agentplaneDistNewest;
81
+
82
+ // If we're in the monorepo, also check core dist because the CLI imports it.
83
+ const repoRoot = path.resolve(agentplaneRoot, "..", "..");
84
+ const coreRoot = path.join(repoRoot, "packages", "core");
85
+ const coreSrcDir = path.join(coreRoot, "src");
86
+ const coreDistDir = path.join(coreRoot, "dist");
87
+ let isStaleCore = false;
88
+ if ((await exists(coreSrcDir)) && (await exists(coreDistDir))) {
89
+ const coreSrcNewest = await newestMtimeMsInDir(coreSrcDir);
90
+ const coreDistNewest = await newestMtimeMsInDir(coreDistDir);
91
+ isStaleCore = coreSrcNewest > coreDistNewest;
92
+ }
93
+
94
+ if ((isStaleAgentplane || isStaleCore) && !allowStale) {
95
+ process.stderr.write(
96
+ "error: refusing to run a stale repo build (dist is older than src).\n" +
97
+ "Fix:\n" +
98
+ " bun run --filter=@agentplaneorg/core build\n" +
99
+ " bun run --filter=agentplane build\n" +
100
+ "Override (not recommended): set AGENTPLANE_DEV_ALLOW_STALE_DIST=1\n",
101
+ );
102
+ process.exitCode = 2;
103
+ return false;
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ const ok = await assertDistUpToDate();
110
+ if (ok) await import("../dist/cli.js");
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-runner.d.ts","sourceRoot":"","sources":["../../../src/cli/critical/cli-runner.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { runCli } from "../run-cli.js";
2
+ async function main() {
3
+ const argv = process.argv.slice(2);
4
+ const code = await runCli(argv);
5
+ process.exitCode = code;
6
+ }
7
+ main().catch((err) => {
8
+ // Last-resort fallback. The contract for tests is "exit non-zero and surface the error".
9
+ console.error(err);
10
+ process.exitCode = 1;
11
+ });
@@ -0,0 +1,22 @@
1
+ export type RunCliResult = {
2
+ code: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ };
6
+ export declare function makeTempDir(prefix?: string): Promise<string>;
7
+ export declare function ensureDir(dir: string): Promise<void>;
8
+ export declare function writeText(filePath: string, content: string): Promise<void>;
9
+ export declare function readText(filePath: string): Promise<string>;
10
+ export declare function real(p: string): Promise<string>;
11
+ export declare function pathExists(filePath: string): Promise<boolean>;
12
+ export declare function listDirRecursive(root: string): Promise<string[]>;
13
+ export declare function gitInit(repoRoot: string, branch?: string): Promise<void>;
14
+ export declare function gitCommitAll(repoRoot: string, message: string): Promise<void>;
15
+ export declare function gitHead(repoRoot: string): Promise<string>;
16
+ export declare function runCli(args: string[], opts: {
17
+ cwd: string;
18
+ extraEnv?: Record<string, string>;
19
+ }): Promise<RunCliResult>;
20
+ export declare function expectCliError(result: RunCliResult, code: number, errCode: string): void;
21
+ export declare function cleanGitEnv(): NodeJS.ProcessEnv;
22
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../src/cli/critical/harness.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAgBF,wBAAsB,WAAW,CAAC,MAAM,SAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAElF;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF;AAED,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhE;AAED,wBAAsB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAErD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAatE;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB9E;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnF;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/D;AAED,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACvD,OAAO,CAAC,YAAY,CAAC,CA8CvB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAexF;AAED,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,CAqB/C"}
@@ -0,0 +1,164 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, mkdtemp, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ const execFileAsync = promisify(execFile);
7
+ function renderExecField(v) {
8
+ if (v == null)
9
+ return "";
10
+ if (typeof v === "string")
11
+ return v;
12
+ if (Buffer.isBuffer(v))
13
+ return v.toString("utf8");
14
+ try {
15
+ return JSON.stringify(v);
16
+ }
17
+ catch {
18
+ return "[unserializable]";
19
+ }
20
+ }
21
+ export async function makeTempDir(prefix = "agentplane-critical-") {
22
+ return await mkdtemp(path.join(os.tmpdir(), prefix));
23
+ }
24
+ export async function ensureDir(dir) {
25
+ await mkdir(dir, { recursive: true });
26
+ }
27
+ export async function writeText(filePath, content) {
28
+ await ensureDir(path.dirname(filePath));
29
+ await writeFile(filePath, content, "utf8");
30
+ }
31
+ export async function readText(filePath) {
32
+ return await readFile(filePath, "utf8");
33
+ }
34
+ export async function real(p) {
35
+ return await realpath(p);
36
+ }
37
+ export async function pathExists(filePath) {
38
+ try {
39
+ await stat(filePath);
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ export async function listDirRecursive(root) {
47
+ const out = [];
48
+ async function walk(dir) {
49
+ const entries = await readdir(dir, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ const abs = path.join(dir, entry.name);
52
+ const rel = path.relative(root, abs);
53
+ out.push(rel);
54
+ if (entry.isDirectory())
55
+ await walk(abs);
56
+ }
57
+ }
58
+ if (await pathExists(root))
59
+ await walk(root);
60
+ return out.toSorted((a, b) => a.localeCompare(b));
61
+ }
62
+ export async function gitInit(repoRoot, branch = "main") {
63
+ await ensureDir(repoRoot);
64
+ // Use real git, but avoid reading developer global/system configs.
65
+ await execFileAsync("git", ["init", "-q", "-b", branch], {
66
+ cwd: repoRoot,
67
+ env: cleanGitEnv(),
68
+ }).catch(async () => {
69
+ // Older git: init without -b, then create branch.
70
+ await execFileAsync("git", ["init", "-q"], { cwd: repoRoot, env: cleanGitEnv() });
71
+ await execFileAsync("git", ["checkout", "-q", "-b", branch], {
72
+ cwd: repoRoot,
73
+ env: cleanGitEnv(),
74
+ });
75
+ });
76
+ // Repo-local identity to avoid relying on global git config.
77
+ await execFileAsync("git", ["config", "user.email", "agentplane-test@example.com"], {
78
+ cwd: repoRoot,
79
+ env: cleanGitEnv(),
80
+ });
81
+ await execFileAsync("git", ["config", "user.name", "Agentplane Test"], {
82
+ cwd: repoRoot,
83
+ env: cleanGitEnv(),
84
+ });
85
+ }
86
+ export async function gitCommitAll(repoRoot, message) {
87
+ await execFileAsync("git", ["add", "-A"], { cwd: repoRoot, env: cleanGitEnv() });
88
+ await execFileAsync("git", ["commit", "-m", message], { cwd: repoRoot, env: cleanGitEnv() });
89
+ }
90
+ export async function gitHead(repoRoot) {
91
+ const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
92
+ cwd: repoRoot,
93
+ env: cleanGitEnv(),
94
+ });
95
+ return String(stdout).trim();
96
+ }
97
+ export async function runCli(args, opts) {
98
+ // Run as a separate process using bun to execute the TS runner.
99
+ const runnerPath = path.join(process.cwd(), "packages", "agentplane", "src", "cli", "critical", "cli-runner.ts");
100
+ const isolatedHome = await makeTempDir("agentplane-critical-home-");
101
+ const isolatedAgentplaneHome = await makeTempDir("agentplane-critical-ap-home-");
102
+ const env = {
103
+ ...process.env,
104
+ ...cleanGitEnv(),
105
+ HOME: isolatedHome,
106
+ XDG_CONFIG_HOME: path.join(isolatedHome, ".config"),
107
+ AGENTPLANE_HOME: isolatedAgentplaneHome,
108
+ AGENTPLANE_NO_UPDATE_CHECK: "1",
109
+ ...opts.extraEnv,
110
+ };
111
+ // execFileAsync throws on non-zero exit. Normalize into a stable `{code,stdout,stderr}` shape.
112
+ let code = 0;
113
+ let stdout = "";
114
+ let stderr = "";
115
+ try {
116
+ const ok = (await execFileAsync("bun", [runnerPath, ...args], {
117
+ cwd: opts.cwd,
118
+ env,
119
+ encoding: "utf8",
120
+ maxBuffer: 10 * 1024 * 1024,
121
+ }));
122
+ stdout = String(ok.stdout ?? "");
123
+ stderr = String(ok.stderr ?? "");
124
+ }
125
+ catch (err) {
126
+ const e = err;
127
+ stdout = renderExecField(e.stdout);
128
+ stderr = renderExecField(e.stderr);
129
+ code = typeof e.code === "number" ? e.code : 1;
130
+ }
131
+ return { code, stdout, stderr };
132
+ }
133
+ export function expectCliError(result, code, errCode) {
134
+ if (result.code !== code) {
135
+ throw new Error(`Expected exit code ${code} but got ${result.code}\n` +
136
+ `stdout:\n${result.stdout}\n` +
137
+ `stderr:\n${result.stderr}\n`);
138
+ }
139
+ if (!result.stderr.includes(`error [${errCode}]`)) {
140
+ throw new Error(`Expected stderr to include error [${errCode}]\n` +
141
+ `stdout:\n${result.stdout}\n` +
142
+ `stderr:\n${result.stderr}\n`);
143
+ }
144
+ }
145
+ export function cleanGitEnv() {
146
+ const env = { ...process.env };
147
+ // Prevent git from inheriting caller-specific repository bindings.
148
+ delete env.GIT_DIR;
149
+ delete env.GIT_WORK_TREE;
150
+ delete env.GIT_COMMON_DIR;
151
+ delete env.GIT_INDEX_FILE;
152
+ delete env.GIT_OBJECT_DIRECTORY;
153
+ delete env.GIT_ALTERNATE_OBJECT_DIRECTORIES;
154
+ // Disable global/system configs to avoid developer machine drift.
155
+ env.GIT_CONFIG_GLOBAL = "/dev/null";
156
+ env.GIT_CONFIG_SYSTEM = "/dev/null";
157
+ env.GIT_TERMINAL_PROMPT = "0";
158
+ // Provide identity for commits when commands rely on it.
159
+ env.GIT_AUTHOR_NAME = env.GIT_AUTHOR_NAME ?? "Agentplane Test";
160
+ env.GIT_AUTHOR_EMAIL = env.GIT_AUTHOR_EMAIL ?? "agentplane-test@example.com";
161
+ env.GIT_COMMITTER_NAME = env.GIT_COMMITTER_NAME ?? "Agentplane Test";
162
+ env.GIT_COMMITTER_EMAIL = env.GIT_COMMITTER_EMAIL ?? "agentplane-test@example.com";
163
+ return env;
164
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../../../src/cli/run-cli/commands/init/git.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC,CASxD"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../../../src/cli/run-cli/commands/init/git.ts"],"names":[],"mappings":"AAKA,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC,CAUxD"}
@@ -1,12 +1,14 @@
1
- import { findGitRoot } from "@agentplaneorg/core";
1
+ import path from "node:path";
2
2
  import { gitInitRepo } from "../../../../commands/workflow.js";
3
+ import { getPathKind } from "../../../fs-utils.js";
3
4
  export async function ensureGitRoot(opts) {
4
- const existingGitRoot = await findGitRoot(opts.initRoot);
5
- const gitRootExisted = Boolean(existingGitRoot);
6
- let gitRoot = existingGitRoot;
7
- if (!gitRoot) {
5
+ // Init is intentionally scoped to initRoot only. We do not search parent directories
6
+ // for an existing git repository, to avoid accidentally initializing the wrong workspace.
7
+ const dotGit = path.join(opts.initRoot, ".git");
8
+ const kind = await getPathKind(dotGit);
9
+ const gitRootExisted = kind === "dir";
10
+ if (!gitRootExisted) {
8
11
  await gitInitRepo(opts.initRoot, opts.baseBranchFallback);
9
- gitRoot = opts.initRoot;
10
12
  }
11
- return { gitRoot, gitRootExisted };
13
+ return { gitRoot: opts.initRoot, gitRootExisted };
12
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/run-cli/commands/init.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAatE,KAAK,SAAS,GAAG;IACf,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAsBF,KAAK,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,UAAU,CA0I5C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,cAAc,CAAC,UAAU,CACmB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/run-cli/commands/init.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAatE,KAAK,SAAS,GAAG;IACf,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAsBF,KAAK,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,UAAU,CA0I5C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,cAAc,CAAC,UAAU,CACmB,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { resolveProject } from "@agentplaneorg/core";
3
2
  import { mapCoreError } from "../../error-map.js";
4
3
  import { promptChoice, promptInput, promptYesNo } from "../../prompts.js";
5
4
  import { invalidValueForFlag } from "../../output.js";
@@ -40,7 +39,7 @@ export const initSpec = {
40
39
  id: ["init"],
41
40
  group: "Setup",
42
41
  summary: "Initialize agentplane project files under .agentplane/.",
43
- description: "Creates .agentplane/ config, backend stubs, and agent templates. In interactive mode it prompts for missing inputs; use --yes for non-interactive mode.",
42
+ description: "Creates .agentplane/ config, backend stubs, and agent templates in the target directory. If the target directory is not a git repository, it initializes one and writes an initial install commit. In interactive mode it prompts for missing inputs; use --yes for non-interactive mode.",
44
43
  options: [
45
44
  {
46
45
  kind: "string",
@@ -253,10 +252,7 @@ async function cmdInit(opts) {
253
252
  const initRoot = path.resolve(opts.rootOverride ?? opts.cwd);
254
253
  const baseBranchFallback = "main";
255
254
  const { gitRoot, gitRootExisted } = await ensureGitRoot({ initRoot, baseBranchFallback });
256
- const resolved = await resolveProject({
257
- cwd: gitRoot,
258
- rootOverride: gitRoot,
259
- });
255
+ const resolved = { gitRoot, agentplaneDir: path.join(gitRoot, ".agentplane") };
260
256
  const initBaseBranch = await resolveInitBaseBranchForInit({
261
257
  gitRoot: resolved.gitRoot,
262
258
  baseBranchFallback,
@@ -1 +1 @@
1
- {"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.ts"],"names":[],"mappings":"AAmVA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoI5D"}
1
+ {"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.ts"],"names":[],"mappings":"AAwVA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoI5D"}
@@ -298,7 +298,10 @@ async function maybeResolveProject(opts) {
298
298
  });
299
299
  }
300
300
  catch (err) {
301
- if (err instanceof Error && err.message.startsWith("Not a git repository")) {
301
+ if (err instanceof Error &&
302
+ (err.message.startsWith("Not an agentplane project") ||
303
+ err.message.startsWith("Not a git repository") ||
304
+ err.message.startsWith("Agentplane project root is not a git repository"))) {
302
305
  return null;
303
306
  }
304
307
  throw err;
@@ -5,6 +5,7 @@ export declare function commitFromComment(opts: {
5
5
  cwd: string;
6
6
  rootOverride?: string;
7
7
  taskId: string;
8
+ executorAgent?: string;
8
9
  author?: string;
9
10
  statusFrom?: string;
10
11
  statusTo?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAoEvF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4F/D"}
1
+ {"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAsEvF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA+F/D"}
@@ -47,7 +47,8 @@ function deriveCommitMessageFromComment(opts) {
47
47
  function deriveCommitBodyFromComment(opts) {
48
48
  const lines = [
49
49
  `Task: ${opts.taskId}`,
50
- ...(opts.author ? [`Agent: ${opts.author}`] : []),
50
+ ...(opts.executorAgent ? [`Agent: ${opts.executorAgent}`] : []),
51
+ ...(opts.author ? [`Author: ${opts.author}`] : []),
51
52
  ...(opts.statusFrom && opts.statusTo ? [`Status: ${opts.statusFrom} -> ${opts.statusTo}`] : []),
52
53
  `Comment: ${normalizeCommentBodyForCommit(opts.formattedComment || opts.commentBody)}`,
53
54
  ];
@@ -95,6 +96,7 @@ export async function commitFromComment(opts) {
95
96
  const formattedComment = opts.formattedComment ?? formatCommentBodyForCommit(opts.commentBody, opts.config);
96
97
  const body = deriveCommitBodyFromComment({
97
98
  taskId: opts.taskId,
99
+ executorAgent: opts.executorAgent,
98
100
  author: opts.author,
99
101
  statusFrom: opts.statusFrom,
100
102
  statusTo: opts.statusTo,
@@ -121,6 +123,8 @@ export async function commitFromComment(opts) {
121
123
  // Base overrides must be explicit via the `commit` command's --allow-base flag.
122
124
  const env = buildGitCommitEnv({
123
125
  taskId: opts.taskId,
126
+ agentId: opts.executorAgent,
127
+ statusTo: opts.statusTo,
124
128
  allowTasks: opts.allowTasks,
125
129
  allowBase: false,
126
130
  allowPolicy: false,
@@ -1,5 +1,7 @@
1
1
  export declare function buildGitCommitEnv(opts: {
2
2
  taskId: string;
3
+ agentId?: string;
4
+ statusTo?: string;
3
5
  allowTasks: boolean;
4
6
  allowBase: boolean;
5
7
  allowPolicy: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/env.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,CAAC,UAAU,CAWpB"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/env.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,CAAC,UAAU,CAapB"}
@@ -2,6 +2,8 @@ export function buildGitCommitEnv(opts) {
2
2
  return {
3
3
  ...process.env,
4
4
  AGENTPLANE_TASK_ID: opts.taskId,
5
+ ...(opts.agentId ? { AGENTPLANE_AGENT_ID: opts.agentId } : null),
6
+ ...(opts.statusTo ? { AGENTPLANE_STATUS_TO: opts.statusTo } : null),
5
7
  AGENTPLANE_ALLOW_TASKS: opts.allowTasks ? "1" : "0",
6
8
  AGENTPLANE_ALLOW_BASE: opts.allowBase ? "1" : "0",
7
9
  AGENTPLANE_ALLOW_POLICY: opts.allowPolicy ? "1" : "0",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,UAAU,mDAAoD,CAAC;AAgG5E,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2FlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,UAAU,mDAAoD,CAAC;AAgG5E,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HlB"}
@@ -1,14 +1,17 @@
1
1
  import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { getStagedFiles, loadConfig, resolveBaseBranch, resolveProject } from "@agentplaneorg/core";
3
+ import { loadConfig, resolveBaseBranch, resolveProject } from "@agentplaneorg/core";
4
4
  import { evaluatePolicy } from "../../policy/evaluate.js";
5
5
  import { mapBackendError, mapCoreError } from "../../cli/error-map.js";
6
6
  import { fileExists } from "../../cli/fs-utils.js";
7
7
  import { infoMessage, successMessage } from "../../cli/output.js";
8
+ import { resolveCommitEmojiForAgent } from "../../shared/agent-emoji.js";
8
9
  import { CliError } from "../../shared/errors.js";
10
+ import { GitContext } from "../shared/git-context.js";
9
11
  import { throwIfPolicyDenied } from "../shared/policy-deny.js";
10
12
  import { gitCurrentBranch, gitRevParse } from "../shared/git-ops.js";
11
13
  import { isPathWithin } from "../shared/path.js";
14
+ import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
12
15
  const HOOK_MARKER = "agentplane-hook";
13
16
  const SHIM_MARKER = "agentplane-hook-shim";
14
17
  export const HOOK_NAMES = ["commit-msg", "pre-commit", "pre-push"];
@@ -183,6 +186,42 @@ export async function cmdHooksRun(opts) {
183
186
  });
184
187
  const loaded = await loadConfig(resolved.agentplaneDir);
185
188
  const taskId = (process.env.AGENTPLANE_TASK_ID ?? "").trim();
189
+ const statusTo = (process.env.AGENTPLANE_STATUS_TO ?? "").trim().toUpperCase();
190
+ const agentsDirAbs = path.join(resolved.gitRoot, loaded.config.paths.agents_dir);
191
+ let agentId = (process.env.AGENTPLANE_AGENT_ID ?? "").trim();
192
+ if (!agentId && loaded.config.workflow_mode === "direct" && taskId) {
193
+ const lock = await readDirectWorkLock(resolved.agentplaneDir);
194
+ const lockAgent = lock?.agent?.trim() ?? "";
195
+ if (lock?.task_id === taskId && lockAgent)
196
+ agentId = lockAgent;
197
+ }
198
+ const emoji = subject.split(/\s+/).find(Boolean) ?? "";
199
+ if (taskId) {
200
+ if (statusTo === "DONE") {
201
+ if (emoji !== "✅") {
202
+ throw new CliError({
203
+ exitCode: 5,
204
+ code: "E_GIT",
205
+ message: "Finish commits must use a checkmark emoji.\n" +
206
+ "Expected:\n" +
207
+ " ✅ <TASK_SUFFIX> <scope>: <summary>",
208
+ });
209
+ }
210
+ }
211
+ else if (agentId) {
212
+ const expectedEmoji = await resolveCommitEmojiForAgent({ agentsDirAbs, agentId });
213
+ if (emoji !== expectedEmoji) {
214
+ throw new CliError({
215
+ exitCode: 5,
216
+ code: "E_GIT",
217
+ message: "Commit emoji does not match the executor agent policy.\n" +
218
+ `executor_agent=${agentId}\n` +
219
+ "Expected:\n" +
220
+ ` ${expectedEmoji} <TASK_SUFFIX> <scope>: <summary>`,
221
+ });
222
+ }
223
+ }
224
+ }
186
225
  const res = evaluatePolicy({
187
226
  action: "hook_commit_msg",
188
227
  config: loaded.config,
@@ -194,10 +233,11 @@ export async function cmdHooksRun(opts) {
194
233
  return 0;
195
234
  }
196
235
  if (opts.hook === "pre-commit") {
197
- const staged = await getStagedFiles({
236
+ const resolved = await resolveProject({
198
237
  cwd: opts.cwd,
199
238
  rootOverride: opts.rootOverride ?? null,
200
239
  });
240
+ const staged = await new GitContext({ gitRoot: resolved.gitRoot }).statusStagedPaths();
201
241
  if (staged.length === 0)
202
242
  return 0;
203
243
  const allowTasks = (process.env.AGENTPLANE_ALLOW_TASKS ?? "").trim() === "1";
@@ -206,10 +246,6 @@ export async function cmdHooksRun(opts) {
206
246
  const allowConfig = (process.env.AGENTPLANE_ALLOW_CONFIG ?? "").trim() === "1";
207
247
  const allowHooks = (process.env.AGENTPLANE_ALLOW_HOOKS ?? "").trim() === "1";
208
248
  const allowCI = (process.env.AGENTPLANE_ALLOW_CI ?? "").trim() === "1";
209
- const resolved = await resolveProject({
210
- cwd: opts.cwd,
211
- rootOverride: opts.rootOverride ?? null,
212
- });
213
249
  const loaded = await loadConfig(resolved.agentplaneDir);
214
250
  const inBranchPr = loaded.config.workflow_mode === "branch_pr";
215
251
  const baseBranch = inBranchPr
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../../src/commands/recipes/impl/project.ts"],"names":[],"mappings":"AASA,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAa7D"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../../src/commands/recipes/impl/project.ts"],"names":[],"mappings":"AAaA,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAa7D"}
@@ -1,7 +1,9 @@
1
1
  import { resolveProject } from "@agentplaneorg/core";
2
- function isNotGitRepoError(err) {
2
+ function isNoProjectError(err) {
3
3
  if (err instanceof Error) {
4
- return err.message.startsWith("Not a git repository");
4
+ return (err.message.startsWith("Not an agentplane project") ||
5
+ err.message.startsWith("Not a git repository") ||
6
+ err.message.startsWith("Agentplane project root is not a git repository"));
5
7
  }
6
8
  return false;
7
9
  }
@@ -13,7 +15,7 @@ export async function maybeResolveProject(opts) {
13
15
  });
14
16
  }
15
17
  catch (err) {
16
- if (isNotGitRepoError(err)) {
18
+ if (isNoProjectError(err)) {
17
19
  if (opts.rootOverride)
18
20
  throw err;
19
21
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"git-ops.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git-ops.ts"],"names":[],"mappings":"AAMA,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9E;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBnE;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnF;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASpE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASnE;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAe9F;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBhB"}
1
+ {"version":3,"file":"git-ops.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git-ops.ts"],"names":[],"mappings":"AAMA,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9E;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBnE;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnF;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASpE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASnE;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAe9F;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBhB"}
@@ -28,8 +28,13 @@ export async function gitCurrentBranch(cwd) {
28
28
  env: gitEnv(),
29
29
  });
30
30
  const trimmed = stdout.trim();
31
- if (!trimmed || trimmed === "HEAD")
32
- throw new Error("Failed to resolve git branch");
31
+ if (!trimmed || trimmed === "HEAD") {
32
+ throw new CliError({
33
+ code: "E_GIT",
34
+ exitCode: exitCodeForError("E_GIT"),
35
+ message: "Detached HEAD: failed to resolve current branch",
36
+ });
37
+ }
33
38
  return trimmed;
34
39
  }
35
40
  export async function gitBranchExists(cwd, branch) {
@@ -1 +1 @@
1
- {"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAYnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiGlB"}
1
+ {"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAcnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuHlB"}
@@ -1,11 +1,12 @@
1
1
  import { mapBackendError } from "../../cli/error-map.js";
2
- import { successMessage } from "../../cli/output.js";
2
+ import { invalidValueMessage, successMessage } from "../../cli/output.js";
3
3
  import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
4
4
  import { CliError } from "../../shared/errors.js";
5
5
  import { commitFromComment } from "../guard/index.js";
6
6
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
7
7
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
8
- import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, isTransitionAllowed, nowIso, requireStructuredComment, } from "./shared.js";
8
+ import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
9
+ import { appendTaskEvent, defaultCommitEmojiForAgentId, enforceStatusCommitPolicy, isTransitionAllowed, nowIso, requireStructuredComment, } from "./shared.js";
9
10
  export async function cmdBlock(opts) {
10
11
  try {
11
12
  const ctx = opts.ctx ??
@@ -63,17 +64,34 @@ export async function cmdBlock(opts) {
63
64
  : ctx.taskBackend.writeTask(nextTask));
64
65
  let commitInfo = null;
65
66
  if (opts.commitFromComment) {
67
+ const mode = ctx.config.workflow_mode;
68
+ let executorAgent = opts.author;
69
+ if (mode === "direct") {
70
+ const lock = await readDirectWorkLock(ctx.resolvedProject.agentplaneDir);
71
+ const lockAgent = lock?.task_id === opts.taskId ? (lock.agent?.trim() ?? "") : "";
72
+ if (lockAgent)
73
+ executorAgent = lockAgent;
74
+ }
75
+ const expectedEmoji = await defaultCommitEmojiForAgentId(ctx, executorAgent);
76
+ if (typeof opts.commitEmoji === "string" && opts.commitEmoji.trim() !== expectedEmoji) {
77
+ throw new CliError({
78
+ exitCode: 2,
79
+ code: "E_USAGE",
80
+ message: invalidValueMessage("--commit-emoji", opts.commitEmoji, `${expectedEmoji} (executor agent=${executorAgent})`),
81
+ });
82
+ }
66
83
  commitInfo = await commitFromComment({
67
84
  ctx,
68
85
  cwd: opts.cwd,
69
86
  rootOverride: opts.rootOverride,
70
87
  taskId: opts.taskId,
88
+ executorAgent,
71
89
  author: opts.author,
72
90
  statusFrom: currentStatus,
73
91
  statusTo: "BLOCKED",
74
92
  commentBody: opts.body,
75
93
  formattedComment,
76
- emoji: opts.commitEmoji ?? defaultCommitEmojiForStatus("BLOCKED"),
94
+ emoji: opts.commitEmoji ?? expectedEmoji,
77
95
  allow: opts.commitAllow,
78
96
  autoAllow: opts.commitAutoAllow || opts.commitAllow.length === 0,
79
97
  allowTasks: opts.commitAllowTasks,
@@ -1 +1 @@
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"}
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;AAiCnC,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,CAmOlB"}
@@ -1,5 +1,5 @@
1
1
  import { mapBackendError } from "../../cli/error-map.js";
2
- import { successMessage } from "../../cli/output.js";
2
+ import { invalidValueMessage, successMessage } from "../../cli/output.js";
3
3
  import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
4
4
  import { CliError } from "../../shared/errors.js";
5
5
  import { readFile, rm } from "node:fs/promises";
@@ -7,6 +7,7 @@ import path from "node:path";
7
7
  import { commitFromComment } from "../guard/index.js";
8
8
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
9
9
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
10
+ import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
10
11
  import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, ensureVerificationSatisfiedIfRequired, nowIso, readCommitInfo, readHeadCommit, requireStructuredComment, } from "./shared.js";
11
12
  async function clearDirectWorkLockIfMatches(opts) {
12
13
  const lockPath = path.join(opts.agentplaneDir, "cache", "direct-work.json");
@@ -140,12 +141,31 @@ export async function cmdFinish(opts) {
140
141
  : ctx.taskBackend.writeTask(nextTask));
141
142
  }
142
143
  // tasks.json is export-only; generated via `agentplane task export`.
144
+ let executorAgent = null;
145
+ if (opts.commitFromComment || opts.statusCommit) {
146
+ const mode = ctx.config.workflow_mode;
147
+ executorAgent = opts.author;
148
+ if (mode === "direct") {
149
+ const lock = await readDirectWorkLock(ctx.resolvedProject.agentplaneDir);
150
+ const lockAgent = lock?.task_id === primaryTaskId ? (lock.agent?.trim() ?? "") : "";
151
+ if (lockAgent)
152
+ executorAgent = lockAgent;
153
+ }
154
+ }
143
155
  if (opts.commitFromComment) {
156
+ if (typeof opts.commitEmoji === "string" && opts.commitEmoji.trim() !== "✅") {
157
+ throw new CliError({
158
+ exitCode: 2,
159
+ code: "E_USAGE",
160
+ message: invalidValueMessage("--commit-emoji", opts.commitEmoji, "✅ (finish commits must use a checkmark)"),
161
+ });
162
+ }
144
163
  await commitFromComment({
145
164
  ctx,
146
165
  cwd: opts.cwd,
147
166
  rootOverride: opts.rootOverride,
148
167
  taskId: primaryTaskId,
168
+ executorAgent: executorAgent ?? undefined,
149
169
  author: opts.author,
150
170
  statusFrom: primaryStatusFrom ?? undefined,
151
171
  statusTo: "DONE",
@@ -161,11 +181,19 @@ export async function cmdFinish(opts) {
161
181
  });
162
182
  }
163
183
  if (opts.statusCommit) {
184
+ if (typeof opts.statusCommitEmoji === "string" && opts.statusCommitEmoji.trim() !== "✅") {
185
+ throw new CliError({
186
+ exitCode: 2,
187
+ code: "E_USAGE",
188
+ message: invalidValueMessage("--status-commit-emoji", opts.statusCommitEmoji, "✅ (finish commits must use a checkmark)"),
189
+ });
190
+ }
164
191
  await commitFromComment({
165
192
  ctx,
166
193
  cwd: opts.cwd,
167
194
  rootOverride: opts.rootOverride,
168
195
  taskId: primaryTaskId,
196
+ executorAgent: executorAgent ?? undefined,
169
197
  author: opts.author,
170
198
  statusFrom: primaryStatusFrom ?? undefined,
171
199
  statusTo: "DONE",
@@ -40,6 +40,7 @@ export declare function readCommitInfo(cwd: string, rev: string): Promise<{
40
40
  message: string;
41
41
  }>;
42
42
  export declare function defaultCommitEmojiForStatus(status: string): string;
43
+ export declare function defaultCommitEmojiForAgentId(ctx: CommandContext, agentId: string): Promise<string>;
43
44
  export type TaskListFilters = {
44
45
  status: string[];
45
46
  owner: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { readdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { resolveCommitEmojiForAgent } from "../../shared/agent-emoji.js";
5
6
  import { invalidValueForFlag, invalidValueMessage, missingValueMessage, warnMessage, } from "../../cli/output.js";
6
7
  import { fileExists } from "../../cli/fs-utils.js";
7
8
  import { exitCodeForError } from "../../cli/exit-codes.js";
@@ -256,6 +257,10 @@ export function defaultCommitEmojiForStatus(status) {
256
257
  return "⛔";
257
258
  return "🧩";
258
259
  }
260
+ export async function defaultCommitEmojiForAgentId(ctx, agentId) {
261
+ const agentsDir = path.join(ctx.resolvedProject.gitRoot, ctx.config.paths.agents_dir);
262
+ return await resolveCommitEmojiForAgent({ agentsDirAbs: agentsDir, agentId });
263
+ }
259
264
  export function parseTaskListFilters(args, opts) {
260
265
  const out = { status: [], owner: [], tag: [], quiet: false };
261
266
  for (let i = 0; i < args.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAiBnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqKlB"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAoBnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2LlB"}
@@ -1,11 +1,12 @@
1
1
  import { mapBackendError } from "../../cli/error-map.js";
2
- import { successMessage, warnMessage } from "../../cli/output.js";
2
+ import { invalidValueMessage, successMessage, warnMessage } from "../../cli/output.js";
3
3
  import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
4
4
  import { CliError } from "../../shared/errors.js";
5
5
  import { commitFromComment } from "../guard/index.js";
6
6
  import { listTasksMemo, loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
7
7
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
8
- import { appendTaskEvent, buildDependencyState, ensurePlanApprovedIfRequired, enforceStatusCommitPolicy, extractDocSection, isVerifyStepsFilled, isTransitionAllowed, nowIso, requiresVerify, requireStructuredComment, toStringArray, } from "./shared.js";
8
+ import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
9
+ import { appendTaskEvent, buildDependencyState, ensurePlanApprovedIfRequired, enforceStatusCommitPolicy, defaultCommitEmojiForAgentId, extractDocSection, isVerifyStepsFilled, isTransitionAllowed, nowIso, requiresVerify, requireStructuredComment, toStringArray, } from "./shared.js";
9
10
  export async function cmdStart(opts) {
10
11
  try {
11
12
  const ctx = opts.ctx ??
@@ -120,17 +121,34 @@ export async function cmdStart(opts) {
120
121
  : ctx.taskBackend.writeTask(nextTask));
121
122
  let commitInfo = null;
122
123
  if (opts.commitFromComment) {
124
+ const mode = ctx.config.workflow_mode;
125
+ let executorAgent = opts.author;
126
+ if (mode === "direct") {
127
+ const lock = await readDirectWorkLock(ctx.resolvedProject.agentplaneDir);
128
+ const lockAgent = lock?.task_id === opts.taskId ? (lock.agent?.trim() ?? "") : "";
129
+ if (lockAgent)
130
+ executorAgent = lockAgent;
131
+ }
132
+ const expectedEmoji = await defaultCommitEmojiForAgentId(ctx, executorAgent);
133
+ if (typeof opts.commitEmoji === "string" && opts.commitEmoji.trim() !== expectedEmoji) {
134
+ throw new CliError({
135
+ exitCode: 2,
136
+ code: "E_USAGE",
137
+ message: invalidValueMessage("--commit-emoji", opts.commitEmoji, `${expectedEmoji} (executor agent=${executorAgent})`),
138
+ });
139
+ }
123
140
  commitInfo = await commitFromComment({
124
141
  ctx,
125
142
  cwd: opts.cwd,
126
143
  rootOverride: opts.rootOverride,
127
144
  taskId: opts.taskId,
145
+ executorAgent,
128
146
  author: opts.author,
129
147
  statusFrom: currentStatus,
130
148
  statusTo: "DOING",
131
149
  commentBody: opts.body,
132
150
  formattedComment,
133
- emoji: opts.commitEmoji ?? "🚧",
151
+ emoji: opts.commitEmoji ?? expectedEmoji,
134
152
  allow: opts.commitAllow,
135
153
  autoAllow: opts.commitAutoAllow || opts.commitAllow.length === 0,
136
154
  allowTasks: opts.commitAllowTasks,
@@ -1 +1 @@
1
- {"version":3,"file":"commit-subject.d.ts","sourceRoot":"","sources":["../../../src/policy/rules/commit-subject.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAkClE"}
1
+ {"version":3,"file":"commit-subject.d.ts","sourceRoot":"","sources":["../../../src/policy/rules/commit-subject.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAmBlE"}
@@ -5,21 +5,9 @@ export function commitSubjectRule(ctx) {
5
5
  if (!subject) {
6
6
  return { ok: false, errors: [gitError("Commit message subject is empty")], warnings: [] };
7
7
  }
8
- const taskId = (ctx.taskId ?? "").trim();
9
- if (!taskId) {
10
- return {
11
- ok: false,
12
- errors: [
13
- gitError(ctx.action === "hook_commit_msg"
14
- ? "AGENTPLANE_TASK_ID is required (use `agentplane commit ...`)"
15
- : "Task id is required for commit subject validation"),
16
- ],
17
- warnings: [],
18
- };
19
- }
20
8
  const policy = validateCommitSubject({
21
9
  subject,
22
- taskId,
10
+ taskId: (ctx.taskId ?? "").trim() || undefined,
23
11
  genericTokens: ctx.config.commit.generic_tokens,
24
12
  });
25
13
  if (!policy.ok) {
@@ -0,0 +1,5 @@
1
+ export declare function resolveCommitEmojiForAgent(opts: {
2
+ agentsDirAbs: string;
3
+ agentId: string;
4
+ }): Promise<string>;
5
+ //# sourceMappingURL=agent-emoji.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-emoji.d.ts","sourceRoot":"","sources":["../../src/shared/agent-emoji.ts"],"names":[],"mappings":"AAmCA,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlB"}
@@ -0,0 +1,50 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileExists } from "../cli/fs-utils.js";
4
+ const FALLBACK_EMOJIS = ["🧭", "🧠", "🏗️", "🧪", "🛠️", "🔧", "🧩", "🔍", "📦"];
5
+ const WELL_KNOWN_AGENT_EMOJI = {
6
+ ORCHESTRATOR: "🧭",
7
+ PLANNER: "🧠",
8
+ CREATOR: "🏗️",
9
+ INTEGRATOR: "🧩",
10
+ TESTER: "🧪",
11
+ CODER: "🛠️",
12
+ };
13
+ function stableHash32(input) {
14
+ // Simple, stable FNV-1a 32-bit hash (good enough for deterministic emoji selection).
15
+ // FNV-1a offset basis / prime in decimal to avoid numeric-separator style lint quirks.
16
+ let h = 2_166_136_261;
17
+ for (const ch of input) {
18
+ const cp = ch.codePointAt(0) ?? 0;
19
+ h ^= cp;
20
+ h = Math.imul(h, 16_777_619);
21
+ }
22
+ return h >>> 0;
23
+ }
24
+ function fallbackEmojiForAgentId(agentId) {
25
+ const wellKnown = WELL_KNOWN_AGENT_EMOJI[agentId];
26
+ if (wellKnown)
27
+ return wellKnown;
28
+ const idx = stableHash32(agentId) % FALLBACK_EMOJIS.length;
29
+ return FALLBACK_EMOJIS[idx] ?? "🧩";
30
+ }
31
+ export async function resolveCommitEmojiForAgent(opts) {
32
+ const agentId = opts.agentId.trim();
33
+ if (!agentId)
34
+ return "🧩";
35
+ // Allow users to override the emoji per agent by adding `commit_emoji` to the agent json file.
36
+ const agentPath = path.join(opts.agentsDirAbs, `${agentId}.json`);
37
+ if (await fileExists(agentPath)) {
38
+ try {
39
+ const text = await readFile(agentPath, "utf8");
40
+ const parsed = JSON.parse(text);
41
+ const emoji = parsed && typeof parsed.commit_emoji === "string" ? parsed.commit_emoji.trim() : "";
42
+ if (emoji)
43
+ return emoji;
44
+ }
45
+ catch {
46
+ // ignore; fallback below
47
+ }
48
+ }
49
+ return fallbackEmojiForAgentId(agentId);
50
+ }
@@ -0,0 +1,10 @@
1
+ export type DirectWorkLock = {
2
+ task_id: string;
3
+ agent: string;
4
+ slug: string;
5
+ branch: string;
6
+ started_at: string;
7
+ };
8
+ export declare function directWorkLockPath(agentplaneDir: string): string;
9
+ export declare function readDirectWorkLock(agentplaneDir: string): Promise<DirectWorkLock | null>;
10
+ //# sourceMappingURL=direct-work-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-work-lock.d.ts","sourceRoot":"","sources":["../../src/shared/direct-work-lock.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkB9F"}
@@ -0,0 +1,24 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export function directWorkLockPath(agentplaneDir) {
4
+ return path.join(agentplaneDir, "cache", "direct-work.json");
5
+ }
6
+ export async function readDirectWorkLock(agentplaneDir) {
7
+ try {
8
+ const text = await readFile(directWorkLockPath(agentplaneDir), "utf8");
9
+ const parsed = JSON.parse(text);
10
+ if (!parsed || typeof parsed !== "object")
11
+ return null;
12
+ if (typeof parsed.task_id !== "string" ||
13
+ typeof parsed.agent !== "string" ||
14
+ typeof parsed.slug !== "string" ||
15
+ typeof parsed.branch !== "string" ||
16
+ typeof parsed.started_at !== "string") {
17
+ return null;
18
+ }
19
+ return parsed;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentplane",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
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.5",
57
+ "@agentplaneorg/core": "0.2.6",
58
58
  "yauzl": "^2.10.0"
59
59
  },
60
60
  "devDependencies": {