@imdeadpool/guardex 7.0.38 → 7.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/package.json +1 -1
- package/src/cli/main.js +4 -3
- package/src/context.js +3 -3
- package/src/doctor/index.js +1 -1
- package/src/sandbox/index.js +3 -2
- package/templates/AGENTS.multiagent-safety.md +394 -36
- package/templates/scripts/agent-branch-finish.sh +3 -3
- package/templates/scripts/agent-branch-merge.sh +1 -1
- package/templates/scripts/agent-branch-start.sh +140 -2
- package/templates/vscode/guardex-active-agents/extension.js +111 -1
- package/templates/vscode/guardex-active-agents/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
and human teammates working the same codebase at the same time.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman. See [about_description.txt](./about_description.txt).
|
|
13
12
|
|
|
14
13
|
<p align="center">
|
|
15
14
|
<a href="https://www.npmjs.com/package/@imdeadpool/guardex"><img alt="npm version" src="https://img.shields.io/npm/v/%40imdeadpool%2Fguardex?label=npm&style=flat-square&color=cb3837&logo=npm&logoColor=white"></a>
|
|
@@ -157,7 +156,7 @@ $ gx
|
|
|
157
156
|
● oh-my-codex active
|
|
158
157
|
● oh-my-claude-sisyphus active
|
|
159
158
|
● @fission-ai/openspec active
|
|
160
|
-
●
|
|
159
|
+
● colony active
|
|
161
160
|
● cavekit optional · not installed
|
|
162
161
|
● gh authenticated
|
|
163
162
|
|
|
@@ -266,6 +265,27 @@ Being honest about where this still has issues:
|
|
|
266
265
|
<details open>
|
|
267
266
|
<summary><strong>v7.x</strong></summary>
|
|
268
267
|
|
|
268
|
+
### v7.0.41
|
|
269
|
+
- Bumped `@imdeadpool/guardex` from `7.0.40` to `7.0.41` so the current
|
|
270
|
+
`main` payload can publish under a fresh npm version after the `v7.0.40`
|
|
271
|
+
GitHub release landed without a matching npm registry package.
|
|
272
|
+
- Ships the Colony companion setup as the default global companion surface:
|
|
273
|
+
`colony` maps to `@imdeadpool/colony-cli`, README setup documents
|
|
274
|
+
`colony install --ide ...`, and status tests/images expect Colony instead
|
|
275
|
+
of cavemem.
|
|
276
|
+
|
|
277
|
+
### v7.0.40
|
|
278
|
+
- Bumped `@imdeadpool/guardex` from `7.0.39` to `7.0.40` so the current
|
|
279
|
+
`main` payload can publish under a fresh npm version after `7.0.39` reached
|
|
280
|
+
the registry.
|
|
281
|
+
- No new CLI command behavior is introduced in this release lane.
|
|
282
|
+
|
|
283
|
+
### v7.0.39
|
|
284
|
+
- Bumped `@imdeadpool/guardex` from `7.0.38` to `7.0.39` so the current
|
|
285
|
+
`main` payload can publish under a fresh npm version after `7.0.38` reached
|
|
286
|
+
the registry.
|
|
287
|
+
- No new CLI command behavior is introduced in this release lane.
|
|
288
|
+
|
|
269
289
|
### v7.0.38
|
|
270
290
|
- Bumped `@imdeadpool/guardex` from `7.0.37` to `7.0.38` so the current
|
|
271
291
|
`main` payload can publish under a fresh npm version after `7.0.37` reached
|
|
@@ -309,7 +329,7 @@ Install repo skills with `npx skills add recodee/gitguardex`; `npx skills add re
|
|
|
309
329
|
| [**oh-my-codex**](https://github.com/Yeachan-Heo/oh-my-codex) — `npm i -g oh-my-codex` | Codex config + skills framework. Merged into every agent worktree so each spawned Codex starts with the same tuned config. | [](https://github.com/Yeachan-Heo/oh-my-codex) |
|
|
310
330
|
| [**oh-my-claudecode**](https://github.com/Yeachan-Heo/oh-my-claudecode) — `npm i -g oh-my-claude-sisyphus@latest` | Claude-side mirror of oh-my-codex. Skills, commands, and defaults for every Claude Code session. | [](https://github.com/Yeachan-Heo/oh-my-claudecode) |
|
|
311
331
|
| [**OpenSpec**](https://github.com/Fission-AI/OpenSpec) — `npm i -g @fission-ai/openspec` | Structured plan / change / apply / archive flow so long agent runs don't drift off-task. | [](https://github.com/Fission-AI/OpenSpec) |
|
|
312
|
-
| [**
|
|
332
|
+
| [**Colony**](https://github.com/recodeee/colony) — `npm i -g @imdeadpool/colony-cli` | Multi-agent task coordination and handoff routing. After install, register runtimes with `colony install --ide codex`, `colony install --ide claude-code`, `colony install --ide cursor`, `colony install --ide gemini-cli`, or `colony install --ide opencode`, then verify with `colony status`. | [](https://github.com/recodeee/colony) |
|
|
313
333
|
| [**cavekit**](https://github.com/JuliusBrussee/cavekit) — `npx skills add JuliusBrussee/cavekit` | Spec-driven build loop with `spec`, `build`, `check`, `caveman`, `backprop` skills bundled in. | [](https://github.com/JuliusBrussee/cavekit) |
|
|
314
334
|
| [**caveman**](https://github.com/JuliusBrussee/caveman) — `npx skills add JuliusBrussee/caveman` | Ultra-compressed response mode for Claude / Codex. Less output-token churn on long reviews and debug loops. | [](https://github.com/JuliusBrussee/caveman) |
|
|
315
335
|
| [**codex-account-switcher**](https://github.com/recodeecom/codex-account-switcher-cli) — `npm i -g @imdeadpool/codex-account-switcher` | Multi-identity Codex account switcher. Auto-registers accounts on `codex login`; switch with one command. | [](https://github.com/recodeecom/codex-account-switcher-cli) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.41",
|
|
4
4
|
"description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"preferGlobal": true,
|
package/src/cli/main.js
CHANGED
|
@@ -408,8 +408,9 @@ function runSetupBootstrapInternal(options) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
function extractAgentBranchStartMetadata(output) {
|
|
411
|
-
const
|
|
412
|
-
const
|
|
411
|
+
const outputText = String(output || '');
|
|
412
|
+
const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
|
|
413
|
+
const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
|
|
413
414
|
return {
|
|
414
415
|
branch: branchMatch ? branchMatch[1].trim() : '',
|
|
415
416
|
worktreePath: worktreeMatch ? worktreeMatch[1].trim() : '',
|
|
@@ -3466,7 +3467,7 @@ function pivot(rawArgs) {
|
|
|
3466
3467
|
}
|
|
3467
3468
|
const stdoutText = String(result.stdout || '');
|
|
3468
3469
|
const wtMatch = stdoutText.match(/^\[agent-branch-start\] Worktree:\s+(.+)$/m);
|
|
3469
|
-
const branchMatch = stdoutText.match(/^\[agent-branch-start\] Created branch:\s+(.+)$/m);
|
|
3470
|
+
const branchMatch = stdoutText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch):\s+(.+)$/m);
|
|
3470
3471
|
if (wtMatch) {
|
|
3471
3472
|
const wtPath = wtMatch[1].trim();
|
|
3472
3473
|
process.stdout.write('\n');
|
package/src/context.js
CHANGED
|
@@ -21,7 +21,7 @@ const GLOBAL_INSTALL_COMMAND = `npm i -g ${packageJson.name}`;
|
|
|
21
21
|
const OPENSPEC_PACKAGE = '@fission-ai/openspec';
|
|
22
22
|
const OMC_PACKAGE = 'oh-my-claude-sisyphus';
|
|
23
23
|
const OMC_REPO_URL = 'https://github.com/Yeachan-Heo/oh-my-claudecode';
|
|
24
|
-
const
|
|
24
|
+
const COLONY_PACKAGE = '@imdeadpool/colony-cli';
|
|
25
25
|
const NPX_BIN = process.env.GUARDEX_NPX_BIN || 'npx';
|
|
26
26
|
const GUARDEX_HOME_DIR = path.resolve(process.env.GUARDEX_HOME_DIR || os.homedir());
|
|
27
27
|
const GLOBAL_TOOLCHAIN_SERVICES = [
|
|
@@ -32,7 +32,7 @@ const GLOBAL_TOOLCHAIN_SERVICES = [
|
|
|
32
32
|
dependencyUrl: OMC_REPO_URL,
|
|
33
33
|
},
|
|
34
34
|
{ name: OPENSPEC_PACKAGE, packageName: OPENSPEC_PACKAGE },
|
|
35
|
-
{ name:
|
|
35
|
+
{ name: 'colony', packageName: COLONY_PACKAGE },
|
|
36
36
|
{
|
|
37
37
|
name: '@imdeadpool/codex-account-switcher',
|
|
38
38
|
packageName: '@imdeadpool/codex-account-switcher',
|
|
@@ -674,7 +674,7 @@ module.exports = {
|
|
|
674
674
|
OPENSPEC_PACKAGE,
|
|
675
675
|
OMC_PACKAGE,
|
|
676
676
|
OMC_REPO_URL,
|
|
677
|
-
|
|
677
|
+
COLONY_PACKAGE,
|
|
678
678
|
NPX_BIN,
|
|
679
679
|
GUARDEX_HOME_DIR,
|
|
680
680
|
GLOBAL_TOOLCHAIN_SERVICES,
|
package/src/doctor/index.js
CHANGED
|
@@ -310,7 +310,7 @@ function extractAgentBranchFinishPrUrl(output) {
|
|
|
310
310
|
function doctorFinishFlowIsPending(output) {
|
|
311
311
|
return (
|
|
312
312
|
/\[agent-branch-finish\] PR merge not completed yet; leaving PR open\./.test(output) ||
|
|
313
|
-
/\[agent-branch-finish\]
|
|
313
|
+
/\[agent-branch-finish\] PR pending review\/check policy\./.test(output) ||
|
|
314
314
|
/\[agent-branch-finish\] PR auto-merge enabled; waiting for required checks\/reviews\./.test(output)
|
|
315
315
|
);
|
|
316
316
|
}
|
package/src/sandbox/index.js
CHANGED
|
@@ -65,8 +65,9 @@ function assertProtectedMainWriteAllowed(options, commandName) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function extractAgentBranchStartMetadata(output) {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
68
|
+
const outputText = String(output || '');
|
|
69
|
+
const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
|
|
70
|
+
const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
|
|
70
71
|
return {
|
|
71
72
|
branch: branchMatch ? branchMatch[1].trim() : '',
|
|
72
73
|
worktreePath: worktreeMatch ? worktreeMatch[1].trim() : '',
|
|
@@ -1,63 +1,421 @@
|
|
|
1
1
|
<!-- multiagent-safety:START -->
|
|
2
|
-
## Multi-Agent Execution Contract
|
|
2
|
+
## Multi-Agent Execution Contract: Guardex + Colony
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
### Repo toggle
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
`GUARDEX_ON=0` disables Guardex for that repo.
|
|
8
|
-
`GUARDEX_ON=1` explicitly enables Guardex for that repo again.
|
|
6
|
+
Guardex is enabled by default.
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
Promote to OMX orchestration only when the task is medium/large: multi-file behavior changes, API/schema work, refactors, migrations, architecture, cross-cutting scope, or long prompts. Heavy OMX modes (`ralph`, `autopilot`, `team`, `ultrawork`, `swarm`, `ralplan`) are for that larger scope. If the task grows while working, upgrade then.
|
|
8
|
+
If the repo-root `.env` sets any of these values, treat this entire Guardex contract as disabled for the repo:
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
```text
|
|
11
|
+
GUARDEX_ON=0
|
|
12
|
+
GUARDEX_ON=false
|
|
13
|
+
GUARDEX_ON=no
|
|
14
|
+
GUARDEX_ON=off
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
When disabled, do not require Guardex worktrees, lock claims, completion flow, or OpenSpec workflow until `GUARDEX_ON` is re-enabled.
|
|
18
|
+
|
|
19
|
+
To explicitly enable:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
GUARDEX_ON=1
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Core rules
|
|
26
|
+
|
|
27
|
+
- Work from an `agent/*` branch and worktree, never directly on the protected base branch.
|
|
28
|
+
- Claim files before edits.
|
|
29
|
+
- Use Colony for coordination before falling back to OMX state/notepad.
|
|
30
|
+
- Use OpenSpec for durable behavior contracts and change-driven work.
|
|
31
|
+
- Keep outputs compact: less word, same proof.
|
|
32
|
+
- Commit, push, and open/update a PR for completed work unless the user explicitly says to keep it local.
|
|
33
|
+
- Do not embed stale memory dumps, generated status snapshots, PR transcripts, session history, or long logs in this file.
|
|
34
|
+
|
|
35
|
+
### Task-size routing
|
|
36
|
+
|
|
37
|
+
Small tasks stay direct and caveman-only.
|
|
38
|
+
|
|
39
|
+
For typos, single-file tweaks, one-liners, version bumps, comment-only changes, or similarly bounded asks, solve directly and do not escalate into heavy orchestration just because a keyword appears.
|
|
40
|
+
|
|
41
|
+
Treat these prefixes as explicit lightweight escape hatches:
|
|
42
|
+
|
|
43
|
+
- `quick:`
|
|
44
|
+
- `simple:`
|
|
45
|
+
- `tiny:`
|
|
46
|
+
- `minor:`
|
|
47
|
+
- `small:`
|
|
48
|
+
- `just:`
|
|
49
|
+
- `only:`
|
|
50
|
+
|
|
51
|
+
Promote to full Guardex / OMX orchestration only when scope grows into:
|
|
52
|
+
|
|
53
|
+
- multi-file behavior change
|
|
54
|
+
- API/schema work
|
|
55
|
+
- refactor
|
|
56
|
+
- migration
|
|
57
|
+
- architecture
|
|
58
|
+
- cross-cutting scope
|
|
59
|
+
- long prompt
|
|
60
|
+
- multi-agent execution
|
|
61
|
+
|
|
62
|
+
### Colony coordination loop
|
|
63
|
+
|
|
64
|
+
Use Colony as the primary coordination surface.
|
|
65
|
+
|
|
66
|
+
On every startup, resume, follow-up, or "continue" request, run this order:
|
|
67
|
+
|
|
68
|
+
1. `mcp__colony__hivemind_context`
|
|
69
|
+
2. `mcp__colony__attention_inbox`
|
|
70
|
+
3. `mcp__colony__task_ready_for_agent`
|
|
71
|
+
4. `mcp__colony__search` only when prior decisions, earlier lanes, file history, or error context matter.
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
|
|
75
|
+
- Use `task_ready_for_agent` to choose work.
|
|
76
|
+
- Use `task_list` only for browsing/debugging. Do not use `task_list` as the normal work picker.
|
|
77
|
+
- If an agent reaches for `task_list` repeatedly while choosing work, stop and call `task_ready_for_agent` instead. `task_list` is an inventory tool, not a scheduler.
|
|
78
|
+
- Before editing files on an active task, call `task_claim_file` for each touched file.
|
|
79
|
+
- Use `task_post` for task-thread notes, decisions, blockers, and working-state updates.
|
|
80
|
+
- Use `task_message` / `task_messages` for directed agent-to-agent communication.
|
|
81
|
+
- Use `get_observations` only after compact Colony tools return IDs worth hydrating.
|
|
82
|
+
|
|
83
|
+
Fallback:
|
|
84
|
+
|
|
85
|
+
- Colony is considered unavailable only when the MCP namespace is missing, the tool call fails, or the installed Colony server does not expose the required tool.
|
|
86
|
+
- If `attention_inbox` or `task_ready_for_agent` is missing, fall back to `hivemind_context`, then `task_list`, then hydrate only the relevant task IDs.
|
|
87
|
+
- Do not skip Colony just because OMX state exists. OMX is fallback, not the first coordination source.
|
|
88
|
+
- Read `.omx/state` and `.omx/notepad.md` only when Colony is unavailable, missing the needed state, or the task explicitly depends on legacy OMX state.
|
|
89
|
+
- Keep `.omx/notepad.md` lean: live handoffs only.
|
|
90
|
+
|
|
91
|
+
### Working-state notes
|
|
92
|
+
|
|
93
|
+
Colony is preferred over generic notepad state.
|
|
94
|
+
|
|
95
|
+
A working-state note should be task-scoped, searchable, and useful to another agent resuming the lane.
|
|
96
|
+
|
|
97
|
+
When saving progress, use a task-scoped Colony note when possible:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
task_post kind=note
|
|
101
|
+
content="branch=<branch>; task=<task>; blocker=<blocker>; next=<next>; evidence=<path|command|PR|spec>"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use exactly these fields for handoff-style notes:
|
|
105
|
+
|
|
106
|
+
- `branch`
|
|
107
|
+
- `task`
|
|
108
|
+
- `blocker`
|
|
109
|
+
- `next`
|
|
110
|
+
- `evidence`
|
|
111
|
+
|
|
112
|
+
Do not store long proof dumps, stale narrative, or full logs in notepads. Put bulky proof in OpenSpec artifacts, PRs, or command output.
|
|
113
|
+
|
|
114
|
+
### Token / context budget
|
|
14
115
|
|
|
15
116
|
Default: less word, same proof.
|
|
16
117
|
|
|
17
|
-
- For prompts about `token inefficiency`, `reviewer mode`, `minimal token overhead`, or session waste patterns, switch into low-overhead mode
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
118
|
+
- For prompts about `token inefficiency`, `reviewer mode`, `minimal token overhead`, or session waste patterns, switch into low-overhead mode.
|
|
119
|
+
- Plan in at most 4 bullets.
|
|
120
|
+
- Execute by phase.
|
|
121
|
+
- Batch related reads and commands.
|
|
122
|
+
- Avoid duplicate reads and interactive loops.
|
|
123
|
+
- Keep outputs compact.
|
|
124
|
+
- Verify once per phase.
|
|
125
|
+
- Low output alone is not a defect. A bounded run that finishes in roughly <=10 steps is usually fine.
|
|
126
|
+
- Low output spread across 20+ steps with rising per-turn input is fragmentation and should be treated as context growth first.
|
|
127
|
+
- Startup / resume summaries stay tiny: `branch`, `task`, `blocker`, `next`, and `evidence`.
|
|
128
|
+
- Front-load scaffold/path discovery into one grouped inspection pass. Avoid serial `ls` / `find` / `rg` / `cat` retries that rediscover the same path state.
|
|
129
|
+
- Treat repeated `write_stdin`, repeated `sed` / `cat` peeks, and tiny diagnostic follow-up checks as strong negative signals.
|
|
130
|
+
- If a session turns fragmented, collapse back to inspect once, patch once, verify once, and summarize once.
|
|
23
131
|
- Tool / hook summaries stay tiny: command, status, last meaningful lines only. Drop routine hook boilerplate.
|
|
24
132
|
- Keep raw terminal interaction out of long-lived context. For `write_stdin` or interactive babysitting, retain only process, action sent, current result, and next action.
|
|
25
133
|
- Keep execution log separate from reasoning context: full commands/stdout belong in logs, while prompt context keeps only the latest 1-2 checkpoints plus the newest tool-result summary.
|
|
26
|
-
- Treat local edit/commit, remote publish/PR, CI diagnosis, and cleanup as bounded phases.
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
134
|
+
- Treat local edit/commit, remote publish/PR, CI diagnosis, and cleanup as bounded phases.
|
|
135
|
+
- Do not spend fresh narration or approval turns on obvious safe follow-ons inside an already authorized phase unless the risk changes.
|
|
136
|
+
|
|
137
|
+
### Caveman style
|
|
138
|
+
|
|
139
|
+
Commentary and progress updates use smart-caveman `ultra` by default:
|
|
140
|
+
|
|
141
|
+
- Answer order stays fixed: answer first, cause next, fix or next step last.
|
|
142
|
+
- drop filler
|
|
143
|
+
- use fragments when clear
|
|
144
|
+
- answer first
|
|
145
|
+
- cause next
|
|
146
|
+
- fix or next step last
|
|
147
|
+
|
|
148
|
+
Keep exact literals unchanged:
|
|
149
|
+
|
|
150
|
+
- code
|
|
151
|
+
- commands
|
|
152
|
+
- file paths
|
|
153
|
+
- flags
|
|
154
|
+
- env vars
|
|
155
|
+
- URLs
|
|
156
|
+
- numbers
|
|
157
|
+
- timestamps
|
|
158
|
+
- error text
|
|
159
|
+
|
|
160
|
+
Switch back to `lite` or normal wording for:
|
|
30
161
|
|
|
31
|
-
|
|
162
|
+
- security warnings
|
|
163
|
+
- irreversible actions
|
|
164
|
+
- privacy/compliance notes
|
|
165
|
+
- ordered instructions where fragments may confuse
|
|
166
|
+
- confused users
|
|
167
|
+
- commits
|
|
168
|
+
- PR text
|
|
169
|
+
- specs
|
|
170
|
+
- logs
|
|
171
|
+
- blocker evidence
|
|
32
172
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
173
|
+
Never caveman-compress commands, file paths, specs, logs, or blocker evidence.
|
|
174
|
+
|
|
175
|
+
### Isolation
|
|
176
|
+
|
|
177
|
+
Every task runs on a dedicated `agent/*` branch and worktree.
|
|
178
|
+
|
|
179
|
+
Start with:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
gx branch start "<task>" "<agent-name>"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Treat the base branch (`main` / `dev`) as read-only while an agent branch is active.
|
|
38
186
|
|
|
39
|
-
**Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `gx branch start "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions and auto-stashes a dirty tree before reverting - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
|
|
40
187
|
For every new task, including follow-up work in the same chat/session, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch instead of creating a fresh lane unless the user explicitly redirects scope.
|
|
41
|
-
Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
42
188
|
|
|
43
|
-
|
|
189
|
+
Never implement directly on the local/base branch checkout. Keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
190
|
+
|
|
191
|
+
### Primary-tree lock
|
|
192
|
+
|
|
193
|
+
On the primary checkout, do not run:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git checkout <ref>
|
|
197
|
+
git switch <ref>
|
|
198
|
+
git switch -c ...
|
|
199
|
+
git checkout -b ...
|
|
200
|
+
git worktree add <path> <existing-agent-branch>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Allowed on primary:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
git fetch
|
|
207
|
+
git pull --ff-only
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
To work on any `agent/*` branch, run `gx branch start ...` first, then `cd` into the printed worktree path and run every subsequent git command from inside that worktree.
|
|
211
|
+
|
|
212
|
+
If you are about to type `git checkout agent/...` or `git switch agent/...` from the primary checkout, stop. That is the mistake that flips primary onto an agent branch.
|
|
213
|
+
|
|
214
|
+
### Dirty-tree rule
|
|
215
|
+
|
|
216
|
+
Finish or stash edits inside the worktree they belong to before any branch switch on primary.
|
|
217
|
+
|
|
218
|
+
The post-checkout guard may auto-stash a dirty primary tree as:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
guardex-auto-revert <ts> <prev>-><new>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
That is a safety net, not a workflow. Do not rely on it routinely.
|
|
225
|
+
|
|
226
|
+
Recover stashed changes with:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
git stash list | grep 'guardex-auto-revert'
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Ownership
|
|
44
233
|
|
|
45
|
-
|
|
234
|
+
Before editing, claim files.
|
|
46
235
|
|
|
47
|
-
|
|
236
|
+
Preferred Colony path when on an active task:
|
|
48
237
|
|
|
49
|
-
|
|
238
|
+
```text
|
|
239
|
+
mcp__colony__task_claim_file
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Guardex lock path:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
gx locks claim --branch "<agent-branch>" <file...>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Before deleting, confirm the path is in your claim.
|
|
249
|
+
|
|
250
|
+
Do not edit outside your scope unless reassigned.
|
|
251
|
+
|
|
252
|
+
If another agent owns or recently touched nearby code:
|
|
253
|
+
|
|
254
|
+
1. read latest Colony context
|
|
255
|
+
2. post a handoff or question
|
|
256
|
+
3. avoid reverting unrelated changes
|
|
257
|
+
4. report conflicts instead of overwriting
|
|
258
|
+
|
|
259
|
+
### Handoff gate
|
|
260
|
+
|
|
261
|
+
Before editing, post a one-line handoff note through Colony `task_post` when a task is active.
|
|
262
|
+
|
|
263
|
+
Use `.omx/notepad.md` only when Colony is unavailable or the lane explicitly depends on legacy OMX state.
|
|
264
|
+
|
|
265
|
+
Handoff shape:
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
branch=<branch>; task=<task>; blocker=<blocker>; next=<next>; evidence=<path|command|PR|spec>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Re-read latest Colony context before replacing another agent's code.
|
|
272
|
+
|
|
273
|
+
### Completion
|
|
274
|
+
|
|
275
|
+
Finish with:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
gx branch finish --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
or:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
gx finish --all
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Task is complete only when:
|
|
288
|
+
|
|
289
|
+
1. changes are committed
|
|
290
|
+
2. branch is pushed
|
|
291
|
+
3. PR URL is recorded
|
|
292
|
+
4. PR state is `MERGED`
|
|
293
|
+
5. sandbox worktree is pruned
|
|
294
|
+
6. final handoff records proof
|
|
295
|
+
|
|
296
|
+
If anything blocks, append a `BLOCKED:` note and stop. Do not half-finish.
|
|
50
297
|
|
|
51
|
-
**Completion.** Finish with `gx branch finish --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup` (or `gx finish --all`). Task is only complete when: commit pushed, PR URL recorded, state = `MERGED`, sandbox worktree pruned. If anything blocks, append a `BLOCKED:` note and stop - don't half-finish.
|
|
52
298
|
OMX completion policy: when a task is done, the agent must commit the task changes, push the agent branch, and create/update a PR before considering the branch complete.
|
|
53
299
|
|
|
54
|
-
|
|
300
|
+
### Parallel safety
|
|
301
|
+
|
|
302
|
+
Assume other agents edit nearby.
|
|
303
|
+
|
|
304
|
+
- Never revert unrelated changes.
|
|
305
|
+
- Never simplify or delete critical shared paths without explicit request and regression coverage.
|
|
306
|
+
- Report conflicts in the handoff.
|
|
307
|
+
- Prefer compatibility-preserving changes over endpoint-specific rewrites when other agents may be changing adjacent systems.
|
|
308
|
+
|
|
309
|
+
### Reporting
|
|
310
|
+
|
|
311
|
+
Every completion handoff includes:
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
branch
|
|
315
|
+
task
|
|
316
|
+
files changed
|
|
317
|
+
behavior touched
|
|
318
|
+
verification commands/results
|
|
319
|
+
PR URL
|
|
320
|
+
merge state
|
|
321
|
+
sandbox cleanup state
|
|
322
|
+
risks/follow-ups
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
If blocked, use:
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
BLOCKED:
|
|
329
|
+
branch=<branch>
|
|
330
|
+
task=<task>
|
|
331
|
+
blocker=<blocker>
|
|
332
|
+
next=<next>
|
|
333
|
+
evidence=<path|command|PR|spec>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Open questions
|
|
337
|
+
|
|
338
|
+
If Codex/Claude hits an unresolved question, branching decision, or blocker that should survive chat, record it in:
|
|
339
|
+
|
|
340
|
+
```text
|
|
341
|
+
openspec/plan/<plan-slug>/open-questions.md
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
as an unchecked item:
|
|
345
|
+
|
|
346
|
+
```md
|
|
347
|
+
- [ ] Question or blocker...
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Resolve it in-place when answered instead of burying it in chat-only notes.
|
|
351
|
+
|
|
352
|
+
### OpenSpec
|
|
353
|
+
|
|
354
|
+
OpenSpec is the source of truth for change-driven repo work.
|
|
355
|
+
|
|
356
|
+
For change-driven tasks, keep:
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
openspec/changes/<slug>/tasks.md
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
current during work, not batched at the end.
|
|
363
|
+
|
|
364
|
+
Task scaffolds and manual task edits must include a final completion/cleanup section that ends with PR merge + sandbox cleanup and records PR URL + final `MERGED` evidence.
|
|
365
|
+
|
|
366
|
+
Validate specs before archive:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
openspec validate --specs
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Never archive unverified work.
|
|
373
|
+
|
|
374
|
+
For `T0` / small `T1` lanes, use the compact Colony spec path when available. One Colony handoff plus `colony-spec.md` is enough. Do not create proposal/spec/tasks unless the task grows.
|
|
375
|
+
|
|
376
|
+
For `T2` / `T3` lanes, keep proposal, spec, design, and tasks live while implementing.
|
|
377
|
+
|
|
378
|
+
### Version bumps
|
|
379
|
+
|
|
380
|
+
If a change bumps a published version, the same PR records release notes in the appropriate OpenSpec artifact or release-note mechanism for the repo.
|
|
381
|
+
|
|
382
|
+
Do not edit `CHANGELOG.md` directly unless the repo explicitly requires manual changelog edits.
|
|
383
|
+
|
|
384
|
+
### Verification gates
|
|
385
|
+
|
|
386
|
+
Before claiming completion, run the narrowest meaningful verification for the touched area.
|
|
387
|
+
|
|
388
|
+
Examples:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
pnpm test
|
|
392
|
+
pnpm typecheck
|
|
393
|
+
pnpm lint
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
If a command cannot run, record:
|
|
397
|
+
|
|
398
|
+
```text
|
|
399
|
+
command
|
|
400
|
+
reason it could not run
|
|
401
|
+
risk
|
|
402
|
+
next
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Do not claim green verification without command output evidence.
|
|
406
|
+
|
|
407
|
+
### What not to put in this file
|
|
55
408
|
|
|
56
|
-
|
|
409
|
+
Do not embed:
|
|
57
410
|
|
|
58
|
-
|
|
411
|
+
- stale memory dumps
|
|
412
|
+
- PR transcripts
|
|
413
|
+
- long logs
|
|
414
|
+
- generated status snapshots
|
|
415
|
+
- session history
|
|
416
|
+
- full OpenSpec examples
|
|
417
|
+
- repeated copies of long workflow docs
|
|
59
418
|
|
|
60
|
-
|
|
419
|
+
Keep this section as the hard multi-agent contract. Put long examples and recovery docs in repo-specific workflow docs.
|
|
61
420
|
|
|
62
|
-
**Version bumps.** If a change bumps a published version, the same PR updates release notes/changelog.
|
|
63
421
|
<!-- multiagent-safety:END -->
|
|
@@ -508,7 +508,7 @@ is_local_branch_delete_error() {
|
|
|
508
508
|
|
|
509
509
|
is_remote_branch_missing_error() {
|
|
510
510
|
local output="$1"
|
|
511
|
-
if [[ "$output" == *"remote ref does not exist"* ]]
|
|
511
|
+
if [[ "$output" == *"remote ref does not exist"* ]]; then
|
|
512
512
|
return 0
|
|
513
513
|
fi
|
|
514
514
|
return 1
|
|
@@ -831,7 +831,7 @@ if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
|
831
831
|
echo "[agent-branch-finish] Merge did not complete within wait window; keeping branch open." >&2
|
|
832
832
|
exit 1
|
|
833
833
|
fi
|
|
834
|
-
echo "[agent-branch-finish]
|
|
834
|
+
echo "[agent-branch-finish] PR pending review/check policy. Worktree retained for now; the autofinish watcher (or 'gx worktree prune --include-pr-merged --delete-branches') will prune it after merge. Verify with 'git worktree list' before claiming the worktree is still on disk." >&2
|
|
835
835
|
exit 0
|
|
836
836
|
fi
|
|
837
837
|
echo "[agent-branch-finish] PR flow failed." >&2
|
|
@@ -893,8 +893,8 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
893
893
|
if is_remote_branch_missing_error "$remote_delete_output"; then
|
|
894
894
|
echo "[agent-branch-finish] Remote branch '${SOURCE_BRANCH}' was already deleted; continuing cleanup." >&2
|
|
895
895
|
else
|
|
896
|
+
echo "[agent-branch-finish] Warning: remote branch cleanup failed for '${SOURCE_BRANCH}' after merge; continuing local cleanup." >&2
|
|
896
897
|
echo "$remote_delete_output" >&2
|
|
897
|
-
exit 1
|
|
898
898
|
fi
|
|
899
899
|
fi
|
|
900
900
|
fi
|
|
@@ -288,7 +288,7 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
288
288
|
fi
|
|
289
289
|
|
|
290
290
|
printf '%s\n' "$start_output"
|
|
291
|
-
TARGET_BRANCH="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Created branch: //p' | head -n 1)"
|
|
291
|
+
TARGET_BRANCH="$(printf '%s\n' "$start_output" | sed -n -E 's/^\[agent-branch-start\] (Created branch|Reusing existing branch): //p' | head -n 1)"
|
|
292
292
|
target_worktree="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | head -n 1)"
|
|
293
293
|
if [[ -z "$TARGET_BRANCH" || -z "$target_worktree" ]]; then
|
|
294
294
|
echo "[agent-branch-merge] Unable to parse target branch/worktree from agent-branch-start output." >&2
|
|
@@ -15,6 +15,7 @@ OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
|
15
15
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
16
16
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
17
17
|
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
|
|
18
|
+
REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
|
|
18
19
|
PRINT_NAME_ONLY=0
|
|
19
20
|
POSITIONAL_ARGS=()
|
|
20
21
|
|
|
@@ -58,6 +59,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
58
59
|
OPENSPEC_TIER_RAW="${2:-$OPENSPEC_TIER_RAW}"
|
|
59
60
|
shift 2
|
|
60
61
|
;;
|
|
62
|
+
--reuse-existing|--reuse)
|
|
63
|
+
REUSE_EXISTING_RAW="true"
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
--new|--no-reuse|--no-reuse-existing)
|
|
67
|
+
REUSE_EXISTING_RAW="false"
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
61
70
|
--in-place|--allow-in-place)
|
|
62
71
|
echo "[agent-branch-start] In-place branch mode is disabled." >&2
|
|
63
72
|
echo "[agent-branch-start] This command always creates an isolated worktree to keep your active checkout unchanged." >&2
|
|
@@ -78,7 +87,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
78
87
|
;;
|
|
79
88
|
-*)
|
|
80
89
|
echo "[agent-branch-start] Unknown option: $1" >&2
|
|
81
|
-
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--print-name-only]" >&2
|
|
90
|
+
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--reuse-existing|--new] [--print-name-only]" >&2
|
|
82
91
|
exit 1
|
|
83
92
|
;;
|
|
84
93
|
*)
|
|
@@ -90,7 +99,7 @@ done
|
|
|
90
99
|
|
|
91
100
|
if [[ "${#POSITIONAL_ARGS[@]}" -gt 3 ]]; then
|
|
92
101
|
echo "[agent-branch-start] Too many positional arguments." >&2
|
|
93
|
-
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>]" >&2
|
|
102
|
+
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--reuse-existing|--new]" >&2
|
|
94
103
|
exit 1
|
|
95
104
|
fi
|
|
96
105
|
|
|
@@ -254,6 +263,7 @@ normalize_bool() {
|
|
|
254
263
|
}
|
|
255
264
|
|
|
256
265
|
OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
|
|
266
|
+
REUSE_EXISTING_WORKTREE="$(normalize_bool "$REUSE_EXISTING_RAW" "1")"
|
|
257
267
|
|
|
258
268
|
normalize_tier() {
|
|
259
269
|
local raw="${1:-}"
|
|
@@ -370,6 +380,22 @@ resolve_worktree_leaf() {
|
|
|
370
380
|
printf '%s' "${branch_name//\//__}"
|
|
371
381
|
}
|
|
372
382
|
|
|
383
|
+
print_reused_agent_worktree() {
|
|
384
|
+
local branch_name="$1"
|
|
385
|
+
local worktree_path="$2"
|
|
386
|
+
|
|
387
|
+
echo "[agent-branch-start] Reusing existing branch: ${branch_name}"
|
|
388
|
+
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
389
|
+
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
390
|
+
echo "[agent-branch-start] OpenSpec change: existing worktree"
|
|
391
|
+
echo "[agent-branch-start] OpenSpec plan: existing worktree"
|
|
392
|
+
echo "[agent-branch-start] Next steps:"
|
|
393
|
+
echo " cd \"${worktree_path}\""
|
|
394
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
395
|
+
echo " # continue work in this existing sandbox"
|
|
396
|
+
echo " gx branch finish --branch \"${branch_name}\" --via-pr --wait-for-merge"
|
|
397
|
+
}
|
|
398
|
+
|
|
373
399
|
has_local_changes() {
|
|
374
400
|
local root="$1"
|
|
375
401
|
if ! git -C "$root" diff --quiet; then
|
|
@@ -384,6 +410,101 @@ has_local_changes() {
|
|
|
384
410
|
return 1
|
|
385
411
|
}
|
|
386
412
|
|
|
413
|
+
meaningful_slug_tokens() {
|
|
414
|
+
local raw="$1"
|
|
415
|
+
printf '%s' "$raw" \
|
|
416
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
417
|
+
| tr '/_' '--' \
|
|
418
|
+
| tr '-' '\n' \
|
|
419
|
+
| awk '
|
|
420
|
+
length($0) < 4 { next }
|
|
421
|
+
$0 ~ /^[0-9]+$/ { next }
|
|
422
|
+
$0 ~ /^(agent|agents|branch|codex|claude|continue|dirty|existing|fix|from|implement|make|matching|reuse|start|task|that|this|update|with|worktree|worktrees)$/ { next }
|
|
423
|
+
!seen[$0]++ { print }
|
|
424
|
+
'
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
token_match_score() {
|
|
428
|
+
local task_slug="$1"
|
|
429
|
+
local branch_descriptor="$2"
|
|
430
|
+
local task_tokens branch_tokens token score
|
|
431
|
+
task_tokens="$(meaningful_slug_tokens "$task_slug")"
|
|
432
|
+
branch_tokens="$(meaningful_slug_tokens "$branch_descriptor")"
|
|
433
|
+
score=0
|
|
434
|
+
|
|
435
|
+
if [[ -z "$task_tokens" ]] || [[ -z "$branch_tokens" ]]; then
|
|
436
|
+
printf '0'
|
|
437
|
+
return 0
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
while IFS= read -r token; do
|
|
441
|
+
if grep -Fxq "$token" <<<"$branch_tokens"; then
|
|
442
|
+
score=$((score + 1))
|
|
443
|
+
fi
|
|
444
|
+
done <<<"$task_tokens"
|
|
445
|
+
|
|
446
|
+
printf '%s' "$score"
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
managed_worktree_roots() {
|
|
450
|
+
local repo="$1"
|
|
451
|
+
local explicit_root="$2"
|
|
452
|
+
local root
|
|
453
|
+
local seen_roots=$'\n'
|
|
454
|
+
|
|
455
|
+
for root in \
|
|
456
|
+
"${repo}/${explicit_root}" \
|
|
457
|
+
"${repo}/.omx/agent-worktrees" \
|
|
458
|
+
"${repo}/.omc/agent-worktrees"; do
|
|
459
|
+
if [[ -n "$root" && "$seen_roots" != *$'\n'"$root"$'\n'* ]]; then
|
|
460
|
+
seen_roots+="${root}"$'\n'
|
|
461
|
+
printf '%s\n' "$root"
|
|
462
|
+
fi
|
|
463
|
+
done
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
find_matching_dirty_agent_worktree() {
|
|
467
|
+
local repo="$1"
|
|
468
|
+
local worktree_root_rel="$2"
|
|
469
|
+
local task_slug="$3"
|
|
470
|
+
local agent_slug="$4"
|
|
471
|
+
local best_score=0
|
|
472
|
+
local best_branch=""
|
|
473
|
+
local best_worktree=""
|
|
474
|
+
local best_count=0
|
|
475
|
+
local root entry branch descriptor score
|
|
476
|
+
|
|
477
|
+
while IFS= read -r root; do
|
|
478
|
+
[[ -d "$root" ]] || continue
|
|
479
|
+
while IFS= read -r entry; do
|
|
480
|
+
[[ -d "$entry" ]] || continue
|
|
481
|
+
if ! branch="$(git -C "$entry" rev-parse --abbrev-ref HEAD 2>/dev/null)"; then
|
|
482
|
+
continue
|
|
483
|
+
fi
|
|
484
|
+
[[ "$branch" == "agent/${agent_slug}/"* ]] || continue
|
|
485
|
+
has_local_changes "$entry" || continue
|
|
486
|
+
|
|
487
|
+
descriptor="${branch#agent/${agent_slug}/}"
|
|
488
|
+
score="$(token_match_score "$task_slug" "$descriptor")"
|
|
489
|
+
[[ "$score" =~ ^[0-9]+$ ]] || score=0
|
|
490
|
+
[[ "$score" -gt 0 ]] || continue
|
|
491
|
+
|
|
492
|
+
if [[ "$score" -gt "$best_score" ]]; then
|
|
493
|
+
best_score="$score"
|
|
494
|
+
best_branch="$branch"
|
|
495
|
+
best_worktree="$entry"
|
|
496
|
+
best_count=1
|
|
497
|
+
elif [[ "$score" -eq "$best_score" ]]; then
|
|
498
|
+
best_count=$((best_count + 1))
|
|
499
|
+
fi
|
|
500
|
+
done < <(find "$root" -mindepth 1 -maxdepth 1 -type d -print 2>/dev/null | sort)
|
|
501
|
+
done < <(managed_worktree_roots "$repo" "$worktree_root_rel")
|
|
502
|
+
|
|
503
|
+
if [[ "$best_score" -gt 0 && "$best_count" -eq 1 && -n "$best_branch" && -n "$best_worktree" ]]; then
|
|
504
|
+
printf '%s\t%s\n' "$best_branch" "$best_worktree"
|
|
505
|
+
fi
|
|
506
|
+
}
|
|
507
|
+
|
|
387
508
|
resolve_stash_ref_by_message() {
|
|
388
509
|
local root="$1"
|
|
389
510
|
local message="$2"
|
|
@@ -550,6 +671,12 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
|
|
|
550
671
|
exit 1
|
|
551
672
|
fi
|
|
552
673
|
|
|
674
|
+
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
675
|
+
if [[ "$REUSE_EXISTING_WORKTREE" -eq 1 && "$current_branch" == agent/* ]]; then
|
|
676
|
+
print_reused_agent_worktree "$current_branch" "$repo_root"
|
|
677
|
+
exit 0
|
|
678
|
+
fi
|
|
679
|
+
|
|
553
680
|
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
554
681
|
agent_slug="$(normalize_role "$AGENT_NAME")"
|
|
555
682
|
if [[ "$WORKTREE_ROOT_EXPLICIT" -eq 0 ]]; then
|
|
@@ -565,6 +692,16 @@ if [[ "$PRINT_NAME_ONLY" -eq 1 ]]; then
|
|
|
565
692
|
exit 0
|
|
566
693
|
fi
|
|
567
694
|
|
|
695
|
+
if [[ "$REUSE_EXISTING_WORKTREE" -eq 1 ]]; then
|
|
696
|
+
matching_dirty_worktree="$(find_matching_dirty_agent_worktree "$repo_root" "$WORKTREE_ROOT_REL" "$task_slug" "$agent_slug")"
|
|
697
|
+
if [[ -n "$matching_dirty_worktree" ]]; then
|
|
698
|
+
IFS=$'\t' read -r reused_branch reused_worktree <<<"$matching_dirty_worktree"
|
|
699
|
+
echo "[agent-branch-start] Matched dirty managed worktree for requested task."
|
|
700
|
+
print_reused_agent_worktree "$reused_branch" "$reused_worktree"
|
|
701
|
+
exit 0
|
|
702
|
+
fi
|
|
703
|
+
fi
|
|
704
|
+
|
|
568
705
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
|
|
569
706
|
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
570
707
|
protected_branches_raw="$(resolve_protected_branches "$repo_root")"
|
|
@@ -681,6 +818,7 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
|
681
818
|
fi
|
|
682
819
|
fi
|
|
683
820
|
|
|
821
|
+
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" ".venv"
|
|
684
822
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
|
|
685
823
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
|
|
686
824
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
|
|
@@ -2395,6 +2395,74 @@ function isPathWithin(parentPath, targetPath) {
|
|
|
2395
2395
|
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
2396
2396
|
}
|
|
2397
2397
|
|
|
2398
|
+
function normalizeAbsolutePath(value) {
|
|
2399
|
+
return typeof value === 'string' && value.trim() ? path.resolve(value) : '';
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function isManagedWorktreePath(worktreePath) {
|
|
2403
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2404
|
+
if (!normalizedWorktreePath) {
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
return MANAGED_WORKTREE_RELATIVE_ROOTS.some((relativeRoot) => {
|
|
2409
|
+
const normalizedRelativeRoot = path.normalize(relativeRoot);
|
|
2410
|
+
const marker = `${path.sep}${normalizedRelativeRoot}${path.sep}`;
|
|
2411
|
+
return normalizedWorktreePath.includes(marker);
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
function removeDeletedWorktreeWorkspaceFolder(worktreePath) {
|
|
2416
|
+
if (typeof vscode.workspace.updateWorkspaceFolders !== 'function') {
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2421
|
+
if (!normalizedWorktreePath) {
|
|
2422
|
+
return false;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
const workspaceFolders = vscode.workspace.workspaceFolders || [];
|
|
2426
|
+
const folderIndex = workspaceFolders.findIndex((folder) => (
|
|
2427
|
+
normalizeAbsolutePath(folder?.uri?.fsPath) === normalizedWorktreePath
|
|
2428
|
+
));
|
|
2429
|
+
if (folderIndex < 0) {
|
|
2430
|
+
return false;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
try {
|
|
2434
|
+
return vscode.workspace.updateWorkspaceFolders(folderIndex, 1) === true;
|
|
2435
|
+
} catch (_error) {
|
|
2436
|
+
return false;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
async function closeDeletedWorktreeRepository(worktreePath) {
|
|
2441
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2442
|
+
if (!normalizedWorktreePath || fs.existsSync(normalizedWorktreePath)) {
|
|
2443
|
+
return false;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
try {
|
|
2447
|
+
await vscode.commands.executeCommand('git.close', vscode.Uri.file(normalizedWorktreePath));
|
|
2448
|
+
} catch (_error) {
|
|
2449
|
+
// The Git extension may have already removed this repository.
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
removeDeletedWorktreeWorkspaceFolder(normalizedWorktreePath);
|
|
2453
|
+
return true;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
function findDeletedManagedWorkspaceFolders() {
|
|
2457
|
+
return (vscode.workspace.workspaceFolders || [])
|
|
2458
|
+
.map((folder) => normalizeAbsolutePath(folder?.uri?.fsPath))
|
|
2459
|
+
.filter((workspacePath) => (
|
|
2460
|
+
workspacePath
|
|
2461
|
+
&& !fs.existsSync(workspacePath)
|
|
2462
|
+
&& isManagedWorktreePath(workspacePath)
|
|
2463
|
+
));
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2398
2466
|
function localizeChangeForSession(session, change) {
|
|
2399
2467
|
if (!change?.absolutePath || !isPathWithin(session.worktreePath, change.absolutePath)) {
|
|
2400
2468
|
return null;
|
|
@@ -3434,6 +3502,8 @@ class ActiveAgentsRefreshController {
|
|
|
3434
3502
|
this.inspectPanelManager = inspectPanelManager;
|
|
3435
3503
|
this.refreshTimer = null;
|
|
3436
3504
|
this.sessionWatchers = new Map();
|
|
3505
|
+
this.closedMissingWorktreeRepositories = new Set();
|
|
3506
|
+
this.observedWorktreePaths = new Set();
|
|
3437
3507
|
}
|
|
3438
3508
|
|
|
3439
3509
|
scheduleRefresh() {
|
|
@@ -3456,8 +3526,23 @@ class ActiveAgentsRefreshController {
|
|
|
3456
3526
|
const repoEntries = await findRepoSessionEntries();
|
|
3457
3527
|
const liveSessionKeys = new Set();
|
|
3458
3528
|
|
|
3529
|
+
for (const workspacePath of findDeletedManagedWorkspaceFolders()) {
|
|
3530
|
+
await this.closeMissingWorktreeRepository(workspacePath);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3459
3533
|
for (const entry of repoEntries) {
|
|
3460
3534
|
for (const session of entry.sessions) {
|
|
3535
|
+
const worktreePath = sessionWorktreePath(session);
|
|
3536
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
3537
|
+
if (normalizedWorktreePath && !fs.existsSync(normalizedWorktreePath)) {
|
|
3538
|
+
await this.closeMissingWorktreeRepository(normalizedWorktreePath);
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
if (normalizedWorktreePath) {
|
|
3542
|
+
this.closedMissingWorktreeRepositories.delete(normalizedWorktreePath);
|
|
3543
|
+
this.observedWorktreePaths.add(normalizedWorktreePath);
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3461
3546
|
const sessionKey = resolveSessionWatcherKey(session);
|
|
3462
3547
|
liveSessionKeys.add(sessionKey);
|
|
3463
3548
|
if (this.sessionWatchers.has(sessionKey)) {
|
|
@@ -3468,8 +3553,20 @@ class ActiveAgentsRefreshController {
|
|
|
3468
3553
|
resolveSessionGitIndexPath(session.worktreePath),
|
|
3469
3554
|
);
|
|
3470
3555
|
const disposables = bindRefreshWatcher(watcher, () => this.scheduleRefresh());
|
|
3471
|
-
this.sessionWatchers.set(sessionKey, {
|
|
3556
|
+
this.sessionWatchers.set(sessionKey, {
|
|
3557
|
+
watcher,
|
|
3558
|
+
disposables,
|
|
3559
|
+
worktreePath: normalizedWorktreePath,
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
for (const observedWorktreePath of this.observedWorktreePaths) {
|
|
3565
|
+
if (fs.existsSync(observedWorktreePath)) {
|
|
3566
|
+
this.closedMissingWorktreeRepositories.delete(observedWorktreePath);
|
|
3567
|
+
continue;
|
|
3472
3568
|
}
|
|
3569
|
+
await this.closeMissingWorktreeRepository(observedWorktreePath);
|
|
3473
3570
|
}
|
|
3474
3571
|
|
|
3475
3572
|
for (const [sessionKey, entry] of this.sessionWatchers) {
|
|
@@ -3477,12 +3574,25 @@ class ActiveAgentsRefreshController {
|
|
|
3477
3574
|
continue;
|
|
3478
3575
|
}
|
|
3479
3576
|
|
|
3577
|
+
if (entry.worktreePath && !fs.existsSync(entry.worktreePath)) {
|
|
3578
|
+
await this.closeMissingWorktreeRepository(entry.worktreePath);
|
|
3579
|
+
}
|
|
3480
3580
|
disposeAll(entry.disposables);
|
|
3481
3581
|
entry.watcher.dispose();
|
|
3482
3582
|
this.sessionWatchers.delete(sessionKey);
|
|
3483
3583
|
}
|
|
3484
3584
|
}
|
|
3485
3585
|
|
|
3586
|
+
async closeMissingWorktreeRepository(worktreePath) {
|
|
3587
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
3588
|
+
if (!normalizedWorktreePath || this.closedMissingWorktreeRepositories.has(normalizedWorktreePath)) {
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
this.closedMissingWorktreeRepositories.add(normalizedWorktreePath);
|
|
3593
|
+
await closeDeletedWorktreeRepository(normalizedWorktreePath);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3486
3596
|
dispose() {
|
|
3487
3597
|
if (this.refreshTimer) {
|
|
3488
3598
|
clearTimeout(this.refreshTimer);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "GitGuardex Active Agents",
|
|
4
4
|
"description": "Shows live Guardex sandbox sessions and repo changes in a dedicated VS Code Active Agents sidebar.",
|
|
5
5
|
"publisher": "Recodee",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.21",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"icon": "icon.png",
|
|
9
9
|
"engines": {
|