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.
- package/assets/AGENTS.md +39 -42
- package/bin/agentplane.js +109 -1
- package/dist/cli/critical/cli-runner.d.ts +2 -0
- package/dist/cli/critical/cli-runner.d.ts.map +1 -0
- package/dist/cli/critical/cli-runner.js +11 -0
- package/dist/cli/critical/harness.d.ts +22 -0
- package/dist/cli/critical/harness.d.ts.map +1 -0
- package/dist/cli/critical/harness.js +164 -0
- package/dist/cli/run-cli/commands/init/git.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/git.js +9 -7
- package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init.js +2 -6
- package/dist/cli/run-cli.d.ts.map +1 -1
- package/dist/cli/run-cli.js +4 -1
- package/dist/commands/guard/impl/comment-commit.d.ts +1 -0
- package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
- package/dist/commands/guard/impl/comment-commit.js +5 -1
- package/dist/commands/guard/impl/env.d.ts +2 -0
- package/dist/commands/guard/impl/env.d.ts.map +1 -1
- package/dist/commands/guard/impl/env.js +2 -0
- package/dist/commands/hooks/index.d.ts.map +1 -1
- package/dist/commands/hooks/index.js +42 -6
- package/dist/commands/recipes/impl/project.d.ts.map +1 -1
- package/dist/commands/recipes/impl/project.js +5 -3
- package/dist/commands/shared/git-ops.d.ts.map +1 -1
- package/dist/commands/shared/git-ops.js +7 -2
- package/dist/commands/task/block.d.ts.map +1 -1
- package/dist/commands/task/block.js +21 -3
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +29 -1
- package/dist/commands/task/shared.d.ts +1 -0
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +5 -0
- package/dist/commands/task/start.d.ts.map +1 -1
- package/dist/commands/task/start.js +21 -3
- package/dist/policy/rules/commit-subject.d.ts.map +1 -1
- package/dist/policy/rules/commit-subject.js +1 -13
- package/dist/shared/agent-emoji.d.ts +5 -0
- package/dist/shared/agent-emoji.d.ts.map +1 -0
- package/dist/shared/agent-emoji.js +50 -0
- package/dist/shared/direct-work-lock.d.ts +10 -0
- package/dist/shared/direct-work-lock.d.ts.map +1 -0
- package/dist/shared/direct-work-lock.js +24 -0
- 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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
98
|
+
### Outside-repo
|
|
101
99
|
|
|
102
|
-
Outside-
|
|
100
|
+
Outside-repo reading/writing is **always prohibited** unless the user explicitly approves it (regardless of `require_network`).
|
|
103
101
|
|
|
104
|
-
Outside-
|
|
102
|
+
Outside-repo includes (non-exhaustive):
|
|
105
103
|
|
|
106
|
-
- reading/writing outside the
|
|
104
|
+
- reading/writing outside the repo (`~`, `/etc`, global configs)
|
|
107
105
|
- modifying keychains, ssh keys, credential stores
|
|
108
|
-
- any tool that mutates outside-
|
|
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.
|
|
162
|
-
2.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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 "
|
|
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 @@
|
|
|
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":"
|
|
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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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":"
|
|
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 =
|
|
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":"
|
|
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"}
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -298,7 +298,10 @@ async function maybeResolveProject(opts) {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
catch (err) {
|
|
301
|
-
if (err instanceof Error &&
|
|
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":"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;
|
|
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.
|
|
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 +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,
|
|
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":"
|
|
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 {
|
|
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
|
|
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":"
|
|
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
|
|
2
|
+
function isNoProjectError(err) {
|
|
3
3
|
if (err instanceof Error) {
|
|
4
|
-
return err.message.startsWith("Not
|
|
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 (
|
|
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,
|
|
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
|
|
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;
|
|
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 {
|
|
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 ??
|
|
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;
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAK9C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAK9C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAW5D,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAI/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,eAAO,MAAM,aAAa,+BAAsB,CAAC;AAEjD,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAE3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKvE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAczD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAKtD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAI9E;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB1F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAW7E;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAc3F;AAED,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAiBN;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAqBpF;AAgBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,MAAM,CAgBjF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAgB7F;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5F;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,IAAI,CAqBP;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5C;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,eAAe,CA8EjB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAiBnD"}
|
|
@@ -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;
|
|
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 {
|
|
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,
|
|
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 @@
|
|
|
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.
|
|
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.
|
|
57
|
+
"@agentplaneorg/core": "0.2.6",
|
|
58
58
|
"yauzl": "^2.10.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|