acstack 0.0.0-stage → 0.47.0
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/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +389 -2
- package/THIRD_PARTY_NOTICES +14 -0
- package/TROUBLESHOOTING.md +225 -0
- package/agents/codex/frontend-verifier.toml +352 -0
- package/agents/codex/task-checker.toml +345 -0
- package/agents/codex/task-executor.toml +157 -0
- package/agents/codex/test-writer.toml +149 -0
- package/agents/frontend-verifier.md +351 -0
- package/agents/task-checker.md +344 -0
- package/agents/task-executor.md +156 -0
- package/agents/test-writer.md +148 -0
- package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
- package/assets/playwriter-bridge/doctor.ps1 +450 -0
- package/assets/playwriter-bridge/launcher-src.cs +131 -0
- package/assets/playwriter-bridge/opener.js +330 -0
- package/assets/playwriter-bridge/opener.ps1 +63 -0
- package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
- package/assets/playwriter-bridge/relay.ps1 +50 -0
- package/assets/playwriter-bridge/stop.ps1 +125 -0
- package/assets/playwriter-bridge/sync.ps1 +271 -0
- package/assets/playwriter-bridge/tunnel.ps1 +325 -0
- package/assets/playwriter-bridge/vm-free-port.sh +166 -0
- package/assets/terminal-workspace/client-keybindings.md +87 -0
- package/assets/terminal-workspace/config.example.yaml +329 -0
- package/assets/terminal-workspace/setup.sh +4317 -0
- package/cli/README.md +47 -0
- package/cli/bin/acstack.js +3 -0
- package/cli/dist/index.js +25984 -0
- package/cli/dist/index.js.map +7 -0
- package/cli/package.json +59 -0
- package/docs/adr/0001-ste-writing-standard.md +16 -0
- package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
- package/docs/adr/0003-handoff-ack-command.md +19 -0
- package/docs/adr/0004-dev-setup-per-host.md +13 -0
- package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
- package/docs/adr/0006-document-opener-service.md +89 -0
- package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
- package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
- package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
- package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
- package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
- package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
- package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
- package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
- package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
- package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
- package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
- package/docs/adr/index.md +17 -0
- package/docs/config-reference.md +572 -0
- package/docs/dev-setups.md +164 -0
- package/docs/migrate-0.8.md +28 -0
- package/docs/playwriter-bridge-windows.md +15 -0
- package/docs/terminal-workspace-keys.md +137 -0
- package/docs/terminal-workspace.md +1774 -0
- package/docs/workflows.md +309 -0
- package/hooks/ac-writing-style-reminder.sh +86 -0
- package/hooks/context-pressure.sh +583 -0
- package/hooks/hooks.json +50 -0
- package/hooks/session-start-handoff.sh +165 -0
- package/hooks/session-start.sh +214 -0
- package/orchestrator/AGENTS.md +532 -0
- package/output-styles/ac-writing-style.md +286 -0
- package/package.json +50 -4
- package/pi/agents/frontend-verifier.md +357 -0
- package/pi/agents/task-checker.md +348 -0
- package/pi/agents/task-executor.md +160 -0
- package/pi/agents/test-writer.md +154 -0
- package/pi/extension.ts +723 -0
- package/profiles/fullstack-python.yaml +29 -0
- package/profiles/node-typescript.yaml +23 -0
- package/profiles/python.yaml +24 -0
- package/skills/archive/SKILL.md +160 -0
- package/skills/bootstrap/SKILL.md +109 -0
- package/skills/bridge/HANDOFF.md +93 -0
- package/skills/bridge/SKILL-VM.md +292 -0
- package/skills/bridge/SKILL-WINDOWS.md +526 -0
- package/skills/bridge/SKILL.md +121 -0
- package/skills/browser-verify/SKILL.md +229 -0
- package/skills/code-review/SKILL.md +44 -0
- package/skills/commit/SKILL.md +106 -0
- package/skills/compact-and-continue/SKILL.md +241 -0
- package/skills/explore/SKILL.md +149 -0
- package/skills/grilling/SKILL.md +134 -0
- package/skills/handoff/SKILL.md +250 -0
- package/skills/handoff/launch-herdr.md +197 -0
- package/skills/handoff/launch-without-herdr.md +30 -0
- package/skills/handoff/lead-check.md +59 -0
- package/skills/handoff/outcomes.md +162 -0
- package/skills/handoff/running-agent.md +50 -0
- package/skills/handoff/watch.md +115 -0
- package/skills/host/SKILL.md +247 -0
- package/skills/host/detect-host.sh +297 -0
- package/skills/host/herdr.md +79 -0
- package/skills/host/install-herdr-block.sh +467 -0
- package/skills/host/install-orca.sh +512 -0
- package/skills/host/notes.md +97 -0
- package/skills/host/orca.md +160 -0
- package/skills/host/questions.md +163 -0
- package/skills/host/report.md +42 -0
- package/skills/implement/SKILL.md +230 -0
- package/skills/implement/decision-log.md +62 -0
- package/skills/implement/design-pass.md +32 -0
- package/skills/implement/relaxed-tdd.md +28 -0
- package/skills/map/SKILL.md +250 -0
- package/skills/map/frontend.md +227 -0
- package/skills/map/trackers/files.md +74 -0
- package/skills/map/trackers/github.md +180 -0
- package/skills/map/visuals.md +387 -0
- package/skills/new-worktree/SKILL.md +155 -0
- package/skills/new-worktree/detect-orchestrator.sh +328 -0
- package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
- package/skills/new-worktree/herdr.md +84 -0
- package/skills/new-worktree/orca.md +101 -0
- package/skills/pre-commit-review/SKILL.md +242 -0
- package/skills/pre-commit-review/auto-fix.md +19 -0
- package/skills/pre-commit-review/interactive.md +35 -0
- package/skills/quick-fix/SKILL.md +125 -0
- package/skills/record-context/SKILL.md +106 -0
- package/skills/setup/SKILL.md +250 -0
- package/skills/setup/browser.md +123 -0
- package/skills/setup/design-review.md +97 -0
- package/skills/setup/first-run.md +114 -0
- package/skills/setup/report.md +68 -0
- package/skills/setup/row-fixes.md +196 -0
- package/skills/setup/terminal-workspace.md +153 -0
- package/skills/sync/SKILL.md +171 -0
- package/skills/teardown-worktree/SKILL.md +245 -0
- package/skills/teardown-worktree/detector.md +39 -0
- package/skills/teardown-worktree/herdr.md +113 -0
- package/skills/teardown-worktree/orca.md +61 -0
- package/skills/terminal-workspace/SKILL.md +249 -0
- package/skills/terminal-workspace/components.md +36 -0
- package/skills/terminal-workspace/config-choices.md +69 -0
- package/skills/terminal-workspace/emulator-keys.md +48 -0
- package/skills/terminal-workspace/headless-clipboard.md +175 -0
- package/skills/terminal-workspace/turn-off-and-update.md +104 -0
- package/skills/test-conventions/SKILL.md +59 -0
- package/skills/visual-docs/SKILL.md +246 -0
- package/skills/writing/SKILL.md +231 -0
- package/skills/writing/examples.md +153 -0
- package/skills/writing/vocabulary.md +96 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: new-worktree
|
|
3
|
+
description: "Create a git worktree branched off origin/dev (or origin/main) – through the orchestrator hosting this session, otherwise plain git under .claude/worktrees – then change cwd into it and continue the task there. Use whenever the user says \"create a worktree\", \"new worktree\", \"spin up a worktree\", \"make a worktree off dev\", \"branch off dev and cd into it\", or otherwise wants isolated work in a fresh worktree before doing something."
|
|
4
|
+
argument-hint: "[slug or branch name] [then <task>]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# New worktree
|
|
8
|
+
|
|
9
|
+
Set up an isolated git worktree. Move into it. Then carry on with the task the user attached to the request, as in "create a worktree, then <do X>".
|
|
10
|
+
|
|
11
|
+
The worktree setup is the skill. The follow-on task runs normally once cwd is inside it.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
Trigger on "create a worktree", "new worktree", "spin up a worktree", "make me a worktree off dev", "branch off dev and cd there". The request usually has a tail ("...then plan X", "...then fix Y", "...then give me a dev-server command") which you complete after setup.
|
|
16
|
+
|
|
17
|
+
## Before creating anything
|
|
18
|
+
|
|
19
|
+
- Confirm the task actually needs a worktree. If the cwd is already in a worktree, or uncommitted changes belong to this task, ask before branching. Do not silently start a second one.
|
|
20
|
+
- Resolve the MAIN repo root, never a nested worktree path. From anywhere in the repo:
|
|
21
|
+
- `dirname "$(git rev-parse --git-common-dir)"` gives the main repo root.
|
|
22
|
+
- Never create a worktree inside another worktree. Nested worktree paths have caused real breakage.
|
|
23
|
+
|
|
24
|
+
## Pick the inputs
|
|
25
|
+
|
|
26
|
+
- **Slug**: short kebab-case name for the directory, derived from the task (e.g. `roadmap-gantt`, `feedback-inbox`). If the user named it, use that.
|
|
27
|
+
- **Branch**: `<type>/<slug>` where type is `feat`, `fix`, `chore`, or `docs` to match the work. A bare slug is acceptable if the user gives one. Confirm with the user only if the type is genuinely ambiguous.
|
|
28
|
+
- **Base**: default `origin/dev`. Local `dev` is often behind. Fetch first, then branch off the remote ref.
|
|
29
|
+
|
|
30
|
+
Use `origin/main` for a hotfix, a dev to main promotion, when the user says so, or when the repo has no `origin/dev`. Branch off whichever integration branch the repo actually has. If unsure which, check the repo's CLAUDE.md or AGENTS.md / recent PRs before asking.
|
|
31
|
+
|
|
32
|
+
## Create it
|
|
33
|
+
|
|
34
|
+
Always fetch first, then branch off the remote ref.
|
|
35
|
+
|
|
36
|
+
### Which orchestrator hosts this session
|
|
37
|
+
|
|
38
|
+
A worktree orchestrator is the tool that owns worktrees and the panes working in them – herdr, Orca, or nothing at all. Which one is in play is not something to guess at from environment variables. A sibling script works it out:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ACSTACK_ROOT=$(cd -- "$(dirname -- "$(readlink -f "$HOME/.claude/acstack/bin/acstack")")/../.." && pwd)
|
|
42
|
+
"$ACSTACK_ROOT/skills/new-worktree/detect-orchestrator.sh"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That invariant CLI symlink is the only way to reach the plugin's own root. The SessionStart hook maintains it, and re-creates it every time an agent session starts. Do not shorten the path and do not reach for `$CLAUDE_PLUGIN_ROOT`; the plugin deliberately does not depend on env vars propagating into subshells.
|
|
46
|
+
|
|
47
|
+
The script always exits 0 and prints six lines:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
declared_herdr=<true|false|unset>
|
|
51
|
+
declared_orca=<true|false|unset>
|
|
52
|
+
detected=<herdr|orca|none>
|
|
53
|
+
active=<herdr|orca|none>
|
|
54
|
+
owner=<herdr|orca|none>
|
|
55
|
+
warning=<one line, empty when there is nothing to say>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The script takes one optional argument: the checkout to examine. A checkout is one git worktree directory. The script examines the current directory when you pass nothing.
|
|
59
|
+
|
|
60
|
+
- `declared_herdr` – whether the user has herdr installed on this host, recorded in the ACStack global config under `host.orchestrators.herdr`. `unset` means nobody has declared herdr on this host yet.
|
|
61
|
+
- `declared_orca` – the same yes-or-no answer for Orca, recorded under `host.orchestrators.orca`. The two are independent: a host can have both installed, one, or neither, so one answer says nothing about the other. Both `false` is how a host says it has no orchestrator at all.
|
|
62
|
+
- `detected` – which orchestrator is actually hosting this session, read from the environment markers an orchestrator injects into the terminals it opens.
|
|
63
|
+
- `active` – the branch to run.
|
|
64
|
+
- `owner` – which orchestrator owns the checkout the script examined. The path settles it.
|
|
65
|
+
|
|
66
|
+
A checkout under `~/orca/workspaces/` belongs to Orca. A checkout under herdr's worktree root belongs to herdr, and herdr's config names that root. Anything else is plain git, reported as `none`.
|
|
67
|
+
|
|
68
|
+
- `warning` – one line written for a human, empty when there is nothing to say.
|
|
69
|
+
|
|
70
|
+
Declared means installed. Detected means hosting.
|
|
71
|
+
|
|
72
|
+
Detected wins, so `active` always equals `detected`. So this skill never drives an orchestrator that the host declared but that does not host this session. A marker for an orchestrator the host never declared earns a warning, not a refusal.
|
|
73
|
+
|
|
74
|
+
Owning is not hosting. The session's host creates a worktree, so this skill branches on `active`. The owner decides who removes a checkout later, and the `teardown-worktree` skill acts on that.
|
|
75
|
+
|
|
76
|
+
Three rules follow:
|
|
77
|
+
|
|
78
|
+
- If `warning` is non-empty, repeat it to the user verbatim before going any further. It addresses a human reader.
|
|
79
|
+
- Branch on `active`. That is the section to run, and the only thing that decides it.
|
|
80
|
+
- Sometimes the script does not run at all – no output, and a non-zero exit from the invocation rather than from the script. Do not guess. There is no `active` to branch on.
|
|
81
|
+
|
|
82
|
+
Say so to the user, treat it as no orchestrator, and go no further than the plain git branch in `Orchestrator: none`. An installed plugin that predates the script looks like this. The file is missing or not executable, so the invocation fails before the script can report anything.
|
|
83
|
+
|
|
84
|
+
### Orchestrator: herdr
|
|
85
|
+
|
|
86
|
+
When `active` is `herdr`, read `herdr.md` and run all of it. It creates the worktree through herdr and moves this pane into it.
|
|
87
|
+
|
|
88
|
+
### Orchestrator: orca
|
|
89
|
+
|
|
90
|
+
When `active` is `orca`, read `orca.md` and run all of it. It creates the worktree through Orca and moves this session into it.
|
|
91
|
+
|
|
92
|
+
### Orchestrator: none
|
|
93
|
+
|
|
94
|
+
No orchestrator is driving this branch, so there is no pane to move and nothing above applies. The checkout goes under `.claude/worktrees` in the main repo. If the detector reported a warning, you already told the user why.
|
|
95
|
+
|
|
96
|
+
Use plain git, with `<base>` per the base rule above:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd <main-root> \
|
|
100
|
+
&& git fetch origin --quiet \
|
|
101
|
+
&& git worktree add .claude/worktrees/<slug> -b <branch> <base> \
|
|
102
|
+
&& git -C .claude/worktrees/<slug> log -1 --oneline
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The rest of this section is the same whichever branch ran. One exception: when Orca fails and you stop, Orca created nothing. There is nothing to move into, so stop here.
|
|
106
|
+
|
|
107
|
+
### Then move into it
|
|
108
|
+
|
|
109
|
+
Change cwd into the worktree before any edits:
|
|
110
|
+
|
|
111
|
+
- Prefer the `EnterWorktree` tool if available (it moves the session cwd cleanly).
|
|
112
|
+
- Otherwise target whatever path the orchestrator's create step reported, or `<main-root>/.claude/worktrees/<slug>` with no orchestrator, for all later work. Confirm with `pwd` and `git branch --show-current`.
|
|
113
|
+
|
|
114
|
+
When `active` is `herdr`, this is still a separate step. The pane move relocates the terminal in the sidebar. It does not change the session's working directory.
|
|
115
|
+
|
|
116
|
+
Confirm out loud which worktree, branch, and base commit you are now on.
|
|
117
|
+
|
|
118
|
+
## Then do the attached task
|
|
119
|
+
|
|
120
|
+
Continue with whatever followed the worktree request:
|
|
121
|
+
|
|
122
|
+
- "...then enter plan mode" -> enter plan mode now, from inside the worktree. On a host without plan mode, write the plan and wait for approval before you edit anything.
|
|
123
|
+
- "...then give me a command to start the dev server" -> hand back a copy-paste one-liner. Do not start it yourself unless the user asks.
|
|
124
|
+
|
|
125
|
+
Use the project's own dev script. Do not hand-set env vars. Confirm the right launch command from the repo's CLAUDE.md or AGENTS.md, package scripts, or `scripts/` rather than guessing.
|
|
126
|
+
|
|
127
|
+
- Otherwise just proceed with the task in the new worktree.
|
|
128
|
+
|
|
129
|
+
## Cleanup (later, not now)
|
|
130
|
+
|
|
131
|
+
When the branch merges, tear the worktree down. Run the `teardown-worktree` skill. Do not do the steps by hand, and do not repeat them here.
|
|
132
|
+
|
|
133
|
+
That skill moves this session out, removes the checkout, and deletes the merged local and remote branches. It picks up stale siblings in the same run. It runs the same detector as this skill, so both agree on the same host.
|
|
134
|
+
|
|
135
|
+
Removal branches on the checkout's `owner`, not on `active`. The session's host creates a worktree, and the checkout's owner removes it. `teardown-worktree` owns that rule and the reasons behind it.
|
|
136
|
+
|
|
137
|
+
Confirm the branch has merged or gone stale before the teardown starts. The `ship` skill can run the teardown as its final step.
|
|
138
|
+
|
|
139
|
+
## Never
|
|
140
|
+
|
|
141
|
+
- Create a worktree nested inside another worktree.
|
|
142
|
+
- Branch off local `dev`/`main` without fetching first.
|
|
143
|
+
- Hard-code an orchestrator, or branch on an environment variable directly. Run the detector and use `active`.
|
|
144
|
+
- Swallow the detector's `warning`. Say it to the user before doing anything.
|
|
145
|
+
- Run herdr or Orca commands when `active` is `none`.
|
|
146
|
+
- Quietly fall back to plain git when an Orca command fails. Show the command and the error, then stop.
|
|
147
|
+
- Pass a nested worktree path as `--cwd` to `herdr worktree create`, or as `--repo` to `orca worktree create`.
|
|
148
|
+
- Assume where a herdr worktree landed instead of reading the path from its output.
|
|
149
|
+
- Assume what Orca named the branch. Orca derives it, so read it from the create output.
|
|
150
|
+
- Leave the session in the old workspace after creating a herdr worktree. Move this pane in.
|
|
151
|
+
- Close the spare shell before the pane move lands. That closes the workspace with it.
|
|
152
|
+
- Leave the shell Orca spawned running after you move in. Teardown then refuses to remove that checkout.
|
|
153
|
+
- Start a long-running dev server unless the user asked. Hand back the command instead.
|
|
154
|
+
- Delete a worktree or branch before you confirm it has merged and move your cwd out of it.
|
|
155
|
+
- Leave a herdr workspace pointing at a checkout you removed with plain git.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Which worktree orchestrator should this session drive?
|
|
3
|
+
#
|
|
4
|
+
# Three facts and what they add up to, reported as six key=value lines on stdout:
|
|
5
|
+
#
|
|
6
|
+
# declared_herdr – whether this host says it has herdr installed, as recorded
|
|
7
|
+
# in the ACStack global config under host.orchestrators.herdr.
|
|
8
|
+
# One of true, false, unset.
|
|
9
|
+
# declared_orca – the same for host.orchestrators.orca.
|
|
10
|
+
# detected – which orchestrator is actually HOSTING this Claude session,
|
|
11
|
+
# read from the environment markers an orchestrator injects
|
|
12
|
+
# into the terminals it opens. One of herdr, orca, none.
|
|
13
|
+
# active – the orchestrator to drive. Always equals detected: installed
|
|
14
|
+
# is not the same as hosting, and hosting is what can be
|
|
15
|
+
# driven.
|
|
16
|
+
# owner – which orchestrator OWNS the checkout being looked at. A
|
|
17
|
+
# checkout is one git worktree directory. Its path names the
|
|
18
|
+
# owner: under $HOME/orca/workspaces/ it belongs to orca,
|
|
19
|
+
# under herdr's worktree root it belongs to herdr, anywhere
|
|
20
|
+
# else it is plain git. One of herdr, orca, none. The
|
|
21
|
+
# checkout is this session's working directory, or the path
|
|
22
|
+
# given as the first argument.
|
|
23
|
+
# warning – one plain-language sentence when something could not be read
|
|
24
|
+
# or the facts sit oddly together. Empty when there is
|
|
25
|
+
# nothing to say.
|
|
26
|
+
#
|
|
27
|
+
# The two declarations are independent booleans, one per orchestrator, so a host
|
|
28
|
+
# can have both installed, one, or neither. There is no single "the declared
|
|
29
|
+
# orchestrator" any more.
|
|
30
|
+
#
|
|
31
|
+
# Owning is not hosting. A herdr checkout opened in a plain terminal is owned by
|
|
32
|
+
# herdr and hosted by nobody, so owner=herdr and active=none. The owner only
|
|
33
|
+
# joins the reconciliation when both families of markers are present at once,
|
|
34
|
+
# where it settles the tie.
|
|
35
|
+
#
|
|
36
|
+
# Read-only: writes no files, runs no git command, changes no config.
|
|
37
|
+
# Always exits 0 – a caller that cannot read the report has nothing to fall
|
|
38
|
+
# back on, so every failure is reported in-band as a warning instead.
|
|
39
|
+
|
|
40
|
+
set -u
|
|
41
|
+
|
|
42
|
+
declared_herdr="unset"
|
|
43
|
+
declared_orca="unset"
|
|
44
|
+
detected="none"
|
|
45
|
+
warning=""
|
|
46
|
+
|
|
47
|
+
# The per-axis read problems, kept apart so the warning ladder further down can
|
|
48
|
+
# report herdr's before orca's without re-deriving anything.
|
|
49
|
+
warn_herdr=""
|
|
50
|
+
warn_orca=""
|
|
51
|
+
|
|
52
|
+
# --- declared -----------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
# The invariant absolute path the SessionStart hook maintains. Invoked by full
|
|
55
|
+
# path on purpose: PATH is not reliable inside a fresh agent subshell.
|
|
56
|
+
# Defaulted because set -u would abort on a stripped HOME, printing nothing.
|
|
57
|
+
cli="${HOME:-}/.claude/acstack/bin/acstack"
|
|
58
|
+
|
|
59
|
+
# Ask the CLI for one orchestrator key. Sets axis_value (true, false or unset)
|
|
60
|
+
# and axis_warning (empty when there was nothing wrong). $1 is the config key,
|
|
61
|
+
# $2 is the orchestrator's name for the sentence.
|
|
62
|
+
read_axis() {
|
|
63
|
+
local key="$1" name="$2"
|
|
64
|
+
local out code value
|
|
65
|
+
|
|
66
|
+
out="$("$cli" config get "$key" 2>/dev/null)"
|
|
67
|
+
code=$?
|
|
68
|
+
|
|
69
|
+
# Strip any newline first so a value can never break the one-line-per-key
|
|
70
|
+
# output contract, then trim surrounding whitespace.
|
|
71
|
+
value="$(printf '%s' "$out" | tr -d '\n' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
72
|
+
|
|
73
|
+
axis_value="unset"
|
|
74
|
+
axis_warning=""
|
|
75
|
+
|
|
76
|
+
if [ "$code" -eq 0 ]; then
|
|
77
|
+
case "$value" in
|
|
78
|
+
true|false) axis_value="$value" ;;
|
|
79
|
+
"")
|
|
80
|
+
# Success plus nothing said is not the same as the unset code below, so
|
|
81
|
+
# it gets its own sentence – quoting an empty value back at the reader
|
|
82
|
+
# would look like a bug report rather than an explanation.
|
|
83
|
+
axis_warning="The ACStack command line tool returned an empty answer when asked for ${key}, so whether this host has ${name} is not known."
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
axis_warning="The ACStack global config records ${key} as \"${value}\", which is neither true nor false, so whether this host has ${name} is not known."
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
elif [ "$code" -eq 2 ]; then
|
|
90
|
+
# Exit 2 is the CLI's "this key is unset" code. Not declaring an
|
|
91
|
+
# orchestrator is a normal state, so it is not worth a warning by itself.
|
|
92
|
+
:
|
|
93
|
+
else
|
|
94
|
+
axis_warning="The ACStack command line tool failed when asked for ${key}, so whether this host has ${name} could not be read."
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# A directory carries the execute bit as permission to traverse it, so -x alone
|
|
99
|
+
# would let one through to be run and fail as a generic tool failure. Requiring
|
|
100
|
+
# a regular file keeps every "not there in a runnable form" case on one branch.
|
|
101
|
+
if [ -z "${HOME:-}" ]; then
|
|
102
|
+
# A path quoted back from an empty HOME would start at the filesystem root
|
|
103
|
+
# and read as a bug, so this case names the missing home instead.
|
|
104
|
+
warning="The ACStack command line tool could not be looked for because this session has no home directory set, so this host's declared orchestrators could not be read; restarting Claude Code from a normal shell gives it one."
|
|
105
|
+
elif [ ! -f "$cli" ] || [ ! -x "$cli" ]; then
|
|
106
|
+
# Missing, not a file, or non-executable are the same problem to the user,
|
|
107
|
+
# and the same fix: the SessionStart hook re-creates the symlink on the next
|
|
108
|
+
# start.
|
|
109
|
+
#
|
|
110
|
+
# The path is built from HOME, which the caller controls, so a line break in
|
|
111
|
+
# it would split the warning across two lines and break the one-line-per-key
|
|
112
|
+
# output contract. Strip line breaks for the sentence only; the real $cli is
|
|
113
|
+
# what gets tested and run above.
|
|
114
|
+
cli_shown="$(printf '%s' "$cli" | tr -d '\n\r')"
|
|
115
|
+
warning="The ACStack command line tool could not be run at ${cli_shown}, so this host's declared orchestrators could not be read; restarting Claude Code re-creates it."
|
|
116
|
+
else
|
|
117
|
+
read_axis "host.orchestrators.herdr" "herdr"
|
|
118
|
+
declared_herdr="$axis_value"
|
|
119
|
+
warn_herdr="$axis_warning"
|
|
120
|
+
|
|
121
|
+
read_axis "host.orchestrators.orca" "orca"
|
|
122
|
+
declared_orca="$axis_value"
|
|
123
|
+
warn_orca="$axis_warning"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# --- owner ---------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
# The checkout to examine: the path given as the first argument, or this
|
|
129
|
+
# session's working directory when no argument is given.
|
|
130
|
+
examined="${1:-.}"
|
|
131
|
+
|
|
132
|
+
owner="none"
|
|
133
|
+
|
|
134
|
+
# The physical location of directory $1: symlinks followed and .. segments
|
|
135
|
+
# settled. Prints nothing when the path cannot be reached. cd and pwd are shell
|
|
136
|
+
# builtins running in a subshell, so this reads the filesystem and changes
|
|
137
|
+
# neither it nor this script's own directory.
|
|
138
|
+
physical_path() {
|
|
139
|
+
( cd -P -- "$1" 2>/dev/null && pwd -P ) 2>/dev/null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Whether path $1 sits strictly underneath root $2. Strictly, because the root
|
|
143
|
+
# is where checkouts live, not a checkout itself. The pattern is quoted, so the
|
|
144
|
+
# match is literal, and the slash stops a sibling whose name merely starts with
|
|
145
|
+
# the root's name – .herdr/worktrees-old, say – from counting.
|
|
146
|
+
is_under() {
|
|
147
|
+
local path="$1" root="$2"
|
|
148
|
+
[ -n "$root" ] || return 1
|
|
149
|
+
case "$path" in
|
|
150
|
+
"$root"/*) return 0 ;;
|
|
151
|
+
*) return 1 ;;
|
|
152
|
+
esac
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# herdr keeps its worktrees under one root. The root is $HOME/.herdr/worktrees
|
|
156
|
+
# unless the user moved it, which they do by setting directory in the
|
|
157
|
+
# [worktrees] section of $HOME/.config/herdr/config.toml. A leading ~/ there
|
|
158
|
+
# means the home directory.
|
|
159
|
+
herdr_root_configured() {
|
|
160
|
+
local cfg="$1" value
|
|
161
|
+
[ -f "$cfg" ] && [ -r "$cfg" ] || return 0
|
|
162
|
+
|
|
163
|
+
# One pass, first match wins: track which section each line sits in, and read
|
|
164
|
+
# directory only while inside [worktrees]. A quoted value gives up everything
|
|
165
|
+
# between its quotes, so a trailing comment is dropped with it.
|
|
166
|
+
value="$(awk '
|
|
167
|
+
/^[[:space:]]*\[/ {
|
|
168
|
+
s = $0
|
|
169
|
+
sub(/^[[:space:]]*/, "", s)
|
|
170
|
+
sub(/[[:space:]]*$/, "", s)
|
|
171
|
+
section = s
|
|
172
|
+
next
|
|
173
|
+
}
|
|
174
|
+
section == "[worktrees]" {
|
|
175
|
+
s = $0
|
|
176
|
+
sub(/^[[:space:]]*/, "", s)
|
|
177
|
+
if (s ~ /^directory[[:space:]]*=/) {
|
|
178
|
+
sub(/^directory[[:space:]]*=[[:space:]]*/, "", s)
|
|
179
|
+
sub(/[[:space:]]*$/, "", s)
|
|
180
|
+
if (s ~ /^"/) { sub(/^"/, "", s); sub(/".*$/, "", s) }
|
|
181
|
+
print s
|
|
182
|
+
exit
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
' "$cfg" 2>/dev/null | tr -d '\n')"
|
|
186
|
+
|
|
187
|
+
printf '%s' "$value"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# No home directory means neither root has a location, so nothing can be owned.
|
|
191
|
+
if [ -n "${HOME:-}" ]; then
|
|
192
|
+
orca_root="${HOME}/orca/workspaces"
|
|
193
|
+
|
|
194
|
+
herdr_root="$(herdr_root_configured "${HOME}/.config/herdr/config.toml")"
|
|
195
|
+
# shellcheck disable=SC2088 # the case below matches a literal "~/" prefix read from config.toml, then expands it
|
|
196
|
+
case "$herdr_root" in
|
|
197
|
+
"") herdr_root="${HOME}/.herdr/worktrees" ;;
|
|
198
|
+
"~") herdr_root="$HOME" ;;
|
|
199
|
+
"~/"*) herdr_root="${HOME}/${herdr_root#\~/}" ;;
|
|
200
|
+
esac
|
|
201
|
+
|
|
202
|
+
# Both sides are compared physically, so a symlink on either one cannot make a
|
|
203
|
+
# checkout look like it lives somewhere it does not. A root that does not
|
|
204
|
+
# exist keeps its written form: nothing can sit under it either way.
|
|
205
|
+
examined_phys="$(physical_path "$examined")"
|
|
206
|
+
orca_root_phys="$(physical_path "$orca_root")"
|
|
207
|
+
[ -n "$orca_root_phys" ] || orca_root_phys="$orca_root"
|
|
208
|
+
herdr_root_phys="$(physical_path "$herdr_root")"
|
|
209
|
+
[ -n "$herdr_root_phys" ] || herdr_root_phys="$herdr_root"
|
|
210
|
+
|
|
211
|
+
if [ -n "$examined_phys" ]; then
|
|
212
|
+
if is_under "$examined_phys" "$orca_root_phys" \
|
|
213
|
+
&& is_under "$examined_phys" "$herdr_root_phys"; then
|
|
214
|
+
# One root configured inside the other. The deeper root is the more
|
|
215
|
+
# specific answer, so it wins.
|
|
216
|
+
if [ "${#orca_root_phys}" -ge "${#herdr_root_phys}" ]; then
|
|
217
|
+
owner="orca"
|
|
218
|
+
else
|
|
219
|
+
owner="herdr"
|
|
220
|
+
fi
|
|
221
|
+
elif is_under "$examined_phys" "$orca_root_phys"; then
|
|
222
|
+
owner="orca"
|
|
223
|
+
elif is_under "$examined_phys" "$herdr_root_phys"; then
|
|
224
|
+
owner="herdr"
|
|
225
|
+
fi
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# --- detected -----------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
# An orchestrator marks the terminals it opens. An exported-but-empty variable
|
|
232
|
+
# is a leftover, not a marker, so both families test for non-empty. The markers
|
|
233
|
+
# are read straight from the environment, never via the CLI, so this still works
|
|
234
|
+
# when the CLI is unreadable.
|
|
235
|
+
herdr_marks="no"
|
|
236
|
+
orca_marks="no"
|
|
237
|
+
[ -n "${HERDR_ENV:-}" ] && herdr_marks="yes"
|
|
238
|
+
[ -n "${HERDR_PANE_ID:-}" ] && herdr_marks="yes"
|
|
239
|
+
[ -n "${ORCA_PANE_KEY:-}" ] && orca_marks="yes"
|
|
240
|
+
|
|
241
|
+
ambiguous="no"
|
|
242
|
+
|
|
243
|
+
if [ "$herdr_marks" = "yes" ] && [ "$orca_marks" = "yes" ]; then
|
|
244
|
+
# Both families of markers at once: one orchestrator was probably launched
|
|
245
|
+
# from inside the other.
|
|
246
|
+
#
|
|
247
|
+
# The checkout's owner settles this outright. Working inside a herdr checkout
|
|
248
|
+
# under herdr markers is herdr's session whatever else is exported, so there is
|
|
249
|
+
# nothing ambiguous left to warn about.
|
|
250
|
+
#
|
|
251
|
+
# Failing that, the declarations break the tie only when exactly one of the
|
|
252
|
+
# two is declared true, because that is the only reading with a single obvious
|
|
253
|
+
# answer. Anything else falls back to herdr and says so.
|
|
254
|
+
if [ "$owner" = "herdr" ] || [ "$owner" = "orca" ]; then
|
|
255
|
+
detected="$owner"
|
|
256
|
+
elif [ "$declared_herdr" = "true" ] && [ "$declared_orca" != "true" ]; then
|
|
257
|
+
detected="herdr"
|
|
258
|
+
elif [ "$declared_orca" = "true" ] && [ "$declared_herdr" != "true" ]; then
|
|
259
|
+
detected="orca"
|
|
260
|
+
else
|
|
261
|
+
detected="herdr"
|
|
262
|
+
ambiguous="yes"
|
|
263
|
+
fi
|
|
264
|
+
elif [ "$herdr_marks" = "yes" ]; then
|
|
265
|
+
detected="herdr"
|
|
266
|
+
elif [ "$orca_marks" = "yes" ]; then
|
|
267
|
+
detected="orca"
|
|
268
|
+
else
|
|
269
|
+
detected="none"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
active="$detected"
|
|
273
|
+
|
|
274
|
+
# --- warning ------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
# At most one warning is ever printed, so this is a first-match ladder.
|
|
277
|
+
|
|
278
|
+
# A CLI that cannot be run outranks everything, because it is the only case with
|
|
279
|
+
# an action the user can take right now.
|
|
280
|
+
if [ -z "$warning" ]; then
|
|
281
|
+
# A read problem on one axis next. herdr is checked before orca so the order
|
|
282
|
+
# is stable rather than whichever happened to fail.
|
|
283
|
+
if [ -n "$warn_herdr" ]; then
|
|
284
|
+
warning="$warn_herdr"
|
|
285
|
+
elif [ -n "$warn_orca" ]; then
|
|
286
|
+
warning="$warn_orca"
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
if [ -z "$warning" ] && [ "$ambiguous" = "yes" ]; then
|
|
291
|
+
warning="This session carries environment markers from both herdr and orca, so ${detected} was picked as the one hosting it."
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
if [ -z "$warning" ] && [ "$detected" != "none" ]; then
|
|
295
|
+
# Something is hosting this session that the host does not claim to have.
|
|
296
|
+
# Nothing is broken, but the config is behind reality and can be corrected.
|
|
297
|
+
# The mirror case is not worth a warning: an orchestrator declared true that
|
|
298
|
+
# is not hosting is just installed, and installed is not hosting.
|
|
299
|
+
#
|
|
300
|
+
# A declaration of false and a missing declaration are different situations,
|
|
301
|
+
# so they get different sentences. False means the user already answered in
|
|
302
|
+
# /acstack:host and said it is not installed, so telling them to declare it
|
|
303
|
+
# would send them back to a question they answered; what they need to hear is
|
|
304
|
+
# that their answer disagrees with what is hosting them. Unset means nothing
|
|
305
|
+
# has been answered yet.
|
|
306
|
+
if [ "$detected" = "herdr" ] && [ "$declared_herdr" != "true" ]; then
|
|
307
|
+
if [ "$declared_herdr" = "false" ]; then
|
|
308
|
+
warning="herdr is hosting this session even though this host declares herdr is not installed; the declaration looks wrong, so run /acstack:host to correct it."
|
|
309
|
+
else
|
|
310
|
+
warning="herdr is hosting this session but nothing has been declared for herdr on this host; run /acstack:host to declare it."
|
|
311
|
+
fi
|
|
312
|
+
elif [ "$detected" = "orca" ] && [ "$declared_orca" != "true" ]; then
|
|
313
|
+
if [ "$declared_orca" = "false" ]; then
|
|
314
|
+
warning="orca is hosting this session even though this host declares orca is not installed; the declaration looks wrong, so run /acstack:host to correct it."
|
|
315
|
+
else
|
|
316
|
+
warning="orca is hosting this session but nothing has been declared for orca on this host; run /acstack:host to declare it."
|
|
317
|
+
fi
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
printf 'declared_herdr=%s\n' "$declared_herdr"
|
|
322
|
+
printf 'declared_orca=%s\n' "$declared_orca"
|
|
323
|
+
printf 'detected=%s\n' "$detected"
|
|
324
|
+
printf 'active=%s\n' "$active"
|
|
325
|
+
printf 'owner=%s\n' "$owner"
|
|
326
|
+
printf 'warning=%s\n' "$warning"
|
|
327
|
+
|
|
328
|
+
exit 0
|