agentplane 0.2.4 → 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 (49) hide show
  1. package/assets/AGENTS.md +40 -43
  2. package/bin/agentplane.js +109 -1
  3. package/dist/cli/command-guide.js +2 -2
  4. package/dist/cli/critical/cli-runner.d.ts +2 -0
  5. package/dist/cli/critical/cli-runner.d.ts.map +1 -0
  6. package/dist/cli/critical/cli-runner.js +11 -0
  7. package/dist/cli/critical/harness.d.ts +22 -0
  8. package/dist/cli/critical/harness.d.ts.map +1 -0
  9. package/dist/cli/critical/harness.js +164 -0
  10. package/dist/cli/run-cli/commands/init/git.d.ts.map +1 -1
  11. package/dist/cli/run-cli/commands/init/git.js +9 -7
  12. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  13. package/dist/cli/run-cli/commands/init.js +2 -6
  14. package/dist/cli/run-cli.d.ts.map +1 -1
  15. package/dist/cli/run-cli.js +4 -1
  16. package/dist/commands/branch/work-start.command.d.ts.map +1 -1
  17. package/dist/commands/branch/work-start.command.js +3 -2
  18. package/dist/commands/branch/work-start.d.ts.map +1 -1
  19. package/dist/commands/branch/work-start.js +95 -2
  20. package/dist/commands/guard/impl/comment-commit.d.ts +1 -0
  21. package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
  22. package/dist/commands/guard/impl/comment-commit.js +5 -1
  23. package/dist/commands/guard/impl/env.d.ts +2 -0
  24. package/dist/commands/guard/impl/env.d.ts.map +1 -1
  25. package/dist/commands/guard/impl/env.js +2 -0
  26. package/dist/commands/hooks/index.d.ts.map +1 -1
  27. package/dist/commands/hooks/index.js +42 -6
  28. package/dist/commands/recipes/impl/project.d.ts.map +1 -1
  29. package/dist/commands/recipes/impl/project.js +5 -3
  30. package/dist/commands/shared/git-ops.d.ts.map +1 -1
  31. package/dist/commands/shared/git-ops.js +7 -2
  32. package/dist/commands/task/block.d.ts.map +1 -1
  33. package/dist/commands/task/block.js +21 -3
  34. package/dist/commands/task/finish.d.ts.map +1 -1
  35. package/dist/commands/task/finish.js +53 -1
  36. package/dist/commands/task/shared.d.ts +1 -0
  37. package/dist/commands/task/shared.d.ts.map +1 -1
  38. package/dist/commands/task/shared.js +5 -0
  39. package/dist/commands/task/start.d.ts.map +1 -1
  40. package/dist/commands/task/start.js +21 -3
  41. package/dist/policy/rules/commit-subject.d.ts.map +1 -1
  42. package/dist/policy/rules/commit-subject.js +1 -13
  43. package/dist/shared/agent-emoji.d.ts +5 -0
  44. package/dist/shared/agent-emoji.d.ts.map +1 -0
  45. package/dist/shared/agent-emoji.js +50 -0
  46. package/dist/shared/direct-work-lock.d.ts +10 -0
  47. package/dist/shared/direct-work-lock.d.ts.map +1 -0
  48. package/dist/shared/direct-work-lock.js +24 -0
  49. 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)
@@ -455,7 +452,7 @@ Always follow `workflow_mode` from `.agentplane/config.json`.
455
452
  Rules:
456
453
 
457
454
  - Do all work in the current checkout.
458
- - Task branches are allowed in `direct` (single working directory). Note: `agentplane work start <task-id> --agent <ROLE> --slug <slug>` will create/checkout `task/<task-id>/<slug>` in-place.
455
+ - 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
456
  - Do not use worktrees in `direct`. `agentplane work start ... --worktree` is `branch_pr`-only.
460
457
  - If you only need artifacts/docs without switching branches, prefer `agentplane task scaffold <task-id>`.
461
458
 
@@ -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");
@@ -89,7 +89,7 @@ const ROLE_GUIDES = [
89
89
  {
90
90
  role: "CODER",
91
91
  lines: [
92
- "- direct mode: work in the current checkout; `agentplane work start <task-id> --agent <ROLE> --slug <slug>` creates/checks out `task/<task-id>/<slug>` in-place (no worktree). Use `agentplane task scaffold <task-id>` for docs without switching branches.",
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: work in the current checkout; `agentplane work start <task-id> --agent <ROLE> --slug <slug>` creates/checks out `task/<task-id>/<slug>` in-place (no worktree). Use `agentplane task scaffold <task-id>` for docs without switching branches.",
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).",
@@ -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;
@@ -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,CAgDtD,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,CAAC,sBAAsB,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,GAClE,cAAc,CAAC,eAAe,CAAC,CA4BjC"}
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"}