acstack 0.0.0-stage → 0.47.1
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 +334 -0
- package/assets/terminal-workspace/setup.sh +4404 -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 +1780 -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,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ACStack handoff reload (consume-once, change-aware).
|
|
3
|
+
#
|
|
4
|
+
# When a session resumes after compaction (SessionStart source == "compact"),
|
|
5
|
+
# re-inject the handoff anchor written by /acstack:compact-and-continue, then
|
|
6
|
+
# retire it so it can never be injected twice.
|
|
7
|
+
#
|
|
8
|
+
# The compaction summary carries the conversation narrative; this anchor carries
|
|
9
|
+
# the load-bearing state the summary may have compressed away (next action, task
|
|
10
|
+
# ledger, agent-team file ownership, blockers).
|
|
11
|
+
#
|
|
12
|
+
# Active (pending) handoff lives at the fixed path acstack/HANDOFF.md, so this
|
|
13
|
+
# hook reads one known file with no CLI call. The file's frontmatter records the
|
|
14
|
+
# change it belongs to. On consume, the file is MOVED into that change's own
|
|
15
|
+
# folder, accumulating a per-change history:
|
|
16
|
+
#
|
|
17
|
+
# acstack/changes/<change>/handoffs/<timestamp>.md (when the change dir exists)
|
|
18
|
+
# acstack/handoffs/<change>/<timestamp>.md (ad-hoc, or change archived)
|
|
19
|
+
#
|
|
20
|
+
# Storing consumed handoffs inside the change dir means they travel with the
|
|
21
|
+
# change when /acstack:archive moves it. Names are timestamped with a numeric
|
|
22
|
+
# collision suffix (-2, -3, ...) so many handoffs accumulate without overwrite.
|
|
23
|
+
#
|
|
24
|
+
# No-op on a fresh startup, on --resume, on /clear, when no anchor exists, when
|
|
25
|
+
# the anchor is empty, or when the file lacks the acstack_handoff marker (so a
|
|
26
|
+
# user's own unrelated HANDOFF.md is never touched).
|
|
27
|
+
#
|
|
28
|
+
# Runs alongside session-start.sh as a second SessionStart hook. Claude Code
|
|
29
|
+
# aggregates additionalContext from every matching hook, so this adds to the
|
|
30
|
+
# orchestrator instruction injection rather than replacing it.
|
|
31
|
+
|
|
32
|
+
set -u
|
|
33
|
+
|
|
34
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
35
|
+
|
|
36
|
+
# Capture the hook payload from stdin before running python. The heredoc below
|
|
37
|
+
# becomes python's stdin (the program source), so the payload must travel via an
|
|
38
|
+
# env var, not sys.stdin.
|
|
39
|
+
ACSTACK_HOOK_INPUT="$(cat)"
|
|
40
|
+
export ACSTACK_HOOK_INPUT
|
|
41
|
+
|
|
42
|
+
python3 <<'PY'
|
|
43
|
+
import json, os, re, shutil, sys
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
data = json.loads(os.environ.get("ACSTACK_HOOK_INPUT", ""))
|
|
47
|
+
except Exception:
|
|
48
|
+
sys.exit(0)
|
|
49
|
+
|
|
50
|
+
if not isinstance(data, dict):
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
if data.get("source") != "compact":
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
active = os.path.join("acstack", "HANDOFF.md")
|
|
57
|
+
try:
|
|
58
|
+
with open(active, encoding="utf-8") as f:
|
|
59
|
+
raw = f.read()
|
|
60
|
+
except OSError:
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
if not raw.strip():
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_frontmatter(text):
|
|
68
|
+
"""Return (meta dict, body) for a simple --- key: value --- header.
|
|
69
|
+
Returns (None, text) when there is no recognizable frontmatter."""
|
|
70
|
+
if not text.startswith("---"):
|
|
71
|
+
return None, text
|
|
72
|
+
lines = text.split("\n")
|
|
73
|
+
end = None
|
|
74
|
+
for i in range(1, len(lines)):
|
|
75
|
+
if lines[i].strip() == "---":
|
|
76
|
+
end = i
|
|
77
|
+
break
|
|
78
|
+
if end is None:
|
|
79
|
+
return None, text
|
|
80
|
+
meta = {}
|
|
81
|
+
for line in lines[1:end]:
|
|
82
|
+
if ":" in line:
|
|
83
|
+
key, _, value = line.partition(":")
|
|
84
|
+
meta[key.strip()] = value.strip()
|
|
85
|
+
body = "\n".join(lines[end + 1:]).lstrip("\n")
|
|
86
|
+
return meta, body
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
meta, body = parse_frontmatter(raw)
|
|
90
|
+
|
|
91
|
+
# Only consume files this plugin wrote. A bare HANDOFF.md without the marker is
|
|
92
|
+
# left untouched.
|
|
93
|
+
if not meta or "acstack_handoff" not in meta:
|
|
94
|
+
sys.exit(0)
|
|
95
|
+
|
|
96
|
+
if not body.strip():
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
|
|
99
|
+
# "map" is the current key; "change" is accepted for anchors written by
|
|
100
|
+
# pre-map versions of the handoff skill. The value feeds os.path.join, so
|
|
101
|
+
# sanitize it the same way as the stamp – a name with "/" or ".." must not
|
|
102
|
+
# route the archived handoff outside acstack/.
|
|
103
|
+
change = meta.get("map") or meta.get("change") or "ad-hoc"
|
|
104
|
+
change = re.sub(r"[^A-Za-z0-9._-]", "-", str(change))
|
|
105
|
+
if not change.strip("."):
|
|
106
|
+
change = "ad-hoc"
|
|
107
|
+
created = meta.get("created") or ""
|
|
108
|
+
|
|
109
|
+
# Build a filesystem-safe stamp for the archived filename.
|
|
110
|
+
stamp = re.sub(r"[^A-Za-z0-9._-]", "-", created) if created else ""
|
|
111
|
+
if not stamp:
|
|
112
|
+
from datetime import datetime, timezone
|
|
113
|
+
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%SZ")
|
|
114
|
+
|
|
115
|
+
# Route to the change's own folder when that change dir exists, so consumed
|
|
116
|
+
# handoffs travel with the change on /acstack:archive. Otherwise fall back to a
|
|
117
|
+
# neutral top-level handoffs dir (ad-hoc work, or a change already archived).
|
|
118
|
+
change_dir = os.path.join("acstack", "changes", change)
|
|
119
|
+
if change != "ad-hoc" and os.path.isdir(change_dir):
|
|
120
|
+
archive_dir = os.path.join(change_dir, "handoffs")
|
|
121
|
+
else:
|
|
122
|
+
archive_dir = os.path.join("acstack", "handoffs", change)
|
|
123
|
+
|
|
124
|
+
# Find a non-colliding destination: <stamp>.md, then <stamp>-2.md, -3.md, ...
|
|
125
|
+
dest = None
|
|
126
|
+
try:
|
|
127
|
+
os.makedirs(archive_dir, exist_ok=True)
|
|
128
|
+
candidate = os.path.join(archive_dir, stamp + ".md")
|
|
129
|
+
n = 2
|
|
130
|
+
while os.path.exists(candidate):
|
|
131
|
+
candidate = os.path.join(archive_dir, "{}-{}.md".format(stamp, n))
|
|
132
|
+
n += 1
|
|
133
|
+
dest = candidate
|
|
134
|
+
except OSError:
|
|
135
|
+
dest = None
|
|
136
|
+
|
|
137
|
+
# Consume: move the active handoff into the archive. Best-effort - even if the
|
|
138
|
+
# move fails, we still inject below so the resume is not lost.
|
|
139
|
+
if dest:
|
|
140
|
+
try:
|
|
141
|
+
shutil.move(active, dest)
|
|
142
|
+
except OSError:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
preamble = (
|
|
146
|
+
"The session was just compacted. The following ACStack handoff captures "
|
|
147
|
+
"load-bearing work state the summary may have compressed. Treat it as "
|
|
148
|
+
"authoritative for what to do next.\n\n"
|
|
149
|
+
"Resume now: when the user's next message is \"continue\" (or any short "
|
|
150
|
+
"nudge), pick up the work immediately from the Next action below - do not "
|
|
151
|
+
"wait for further instruction. If anything in this handoff or the "
|
|
152
|
+
"compaction summary is ambiguous or underspecified, ask the user "
|
|
153
|
+
"to resolve it before acting rather than guessing. Ask liberally: a "
|
|
154
|
+
"wrong assumption taken after compaction is expensive to unwind.\n\n"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
print(json.dumps({
|
|
158
|
+
"hookSpecificOutput": {
|
|
159
|
+
"hookEventName": "SessionStart",
|
|
160
|
+
"additionalContext": preamble + body,
|
|
161
|
+
}
|
|
162
|
+
}))
|
|
163
|
+
PY
|
|
164
|
+
|
|
165
|
+
exit 0
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ACStack SessionStart hook.
|
|
3
|
+
#
|
|
4
|
+
# Two symlinks get refreshed on every session start:
|
|
5
|
+
#
|
|
6
|
+
# 1. ~/.claude/acstack/bin/acstack – the **agent-facing** invariant path. Every
|
|
7
|
+
# skill and agent in this plugin invokes the CLI through this exact path.
|
|
8
|
+
# It is referenced by absolute path (no PATH, no env-var propagation, no
|
|
9
|
+
# shell-rc setup), and the hook re-resolves it to the current plugin cache
|
|
10
|
+
# version on every session start. Survives version bumps; survives stale
|
|
11
|
+
# caches; survives subshell PATH inheritance issues.
|
|
12
|
+
#
|
|
13
|
+
# 2. ~/.local/bin/acstack – the **human convenience** symlink. Lets users type
|
|
14
|
+
# `acstack` at a terminal if ~/.local/bin is on PATH. Not load-bearing for
|
|
15
|
+
# agents – if it breaks, nothing in the plugin breaks.
|
|
16
|
+
#
|
|
17
|
+
# Both symlinks point at ${CLAUDE_PLUGIN_ROOT}/cli/bin/acstack.js, which is a
|
|
18
|
+
# tiny node wrapper that loads ../dist/index.js.
|
|
19
|
+
#
|
|
20
|
+
# When a project is initialized (acstack/project.yaml exists), the hook also
|
|
21
|
+
# injects the plugin's orchestrator instructions as additionalContext so installs
|
|
22
|
+
# stay version-agnostic. Otherwise prints a one-line reminder.
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
plugin_root="${CLAUDE_PLUGIN_ROOT:-}"
|
|
27
|
+
troubleshoot="${plugin_root}/TROUBLESHOOTING.md"
|
|
28
|
+
cli_wrapper="${plugin_root}/cli/bin/acstack.js"
|
|
29
|
+
cli_dist="${plugin_root}/cli/dist/index.js"
|
|
30
|
+
|
|
31
|
+
# Symlink $link to point at $src. Reports linked / updated / unchanged.
|
|
32
|
+
# Args: $1 = source target, $2 = link path, $3 = human label for the log message.
|
|
33
|
+
# Echoes a one-line status to stderr when a change happens or a warning fires.
|
|
34
|
+
link_cli() {
|
|
35
|
+
local src="$1"
|
|
36
|
+
local link="$2"
|
|
37
|
+
local label="$3"
|
|
38
|
+
local dir
|
|
39
|
+
dir=$(dirname "$link")
|
|
40
|
+
|
|
41
|
+
if [ ! -e "$link" ] && [ ! -L "$link" ]; then
|
|
42
|
+
mkdir -p "$dir" 2>/dev/null
|
|
43
|
+
if ln -s "$src" "$link" 2>/dev/null; then
|
|
44
|
+
echo "acstack: linked ${label} at ${link} -> ${src}" >&2
|
|
45
|
+
else
|
|
46
|
+
echo "acstack: failed to create symlink at ${link} – see ${troubleshoot} (section: command not found)." >&2
|
|
47
|
+
fi
|
|
48
|
+
elif [ -L "$link" ]; then
|
|
49
|
+
local current
|
|
50
|
+
current=$(readlink "$link")
|
|
51
|
+
if [ "$current" != "$src" ]; then
|
|
52
|
+
if ln -sfn "$src" "$link" 2>/dev/null; then
|
|
53
|
+
echo "acstack: updated ${label} at ${link} -> ${src}" >&2
|
|
54
|
+
else
|
|
55
|
+
echo "acstack: failed to update stale symlink at ${link} (was: ${current}) – see ${troubleshoot} (section: stale cache)." >&2
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
elif [ -e "$link" ]; then
|
|
59
|
+
echo "acstack: ${link} exists but is not a symlink – not overwriting. See ${troubleshoot} (section: command not found)." >&2
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if [ -n "$plugin_root" ] && [ -f "$cli_wrapper" ]; then
|
|
64
|
+
if [ ! -f "$cli_dist" ]; then
|
|
65
|
+
echo "acstack: CLI is missing its built output (${cli_dist})." >&2
|
|
66
|
+
echo "acstack: this should not happen for a published version – see ${troubleshoot} (section: ERR_MODULE_NOT_FOUND)." >&2
|
|
67
|
+
else
|
|
68
|
+
# Agent-facing invariant path. Skills/agents invoke this directly.
|
|
69
|
+
link_cli "$cli_wrapper" "${HOME}/.claude/acstack/bin/acstack" "agent CLI"
|
|
70
|
+
|
|
71
|
+
# Human-convenience path. Only useful when ~/.local/bin is on PATH.
|
|
72
|
+
#
|
|
73
|
+
# Defer to any other acstack that already owns the name. The usual one is a
|
|
74
|
+
# global `npm install -g acstack-cli`, which people install to drive a
|
|
75
|
+
# project from outside Claude Code. Two managers pointing one command name
|
|
76
|
+
# at different copies is a silent PATH race: whichever directory sorts
|
|
77
|
+
# first wins, and it can differ per machine. Better to let the one the user
|
|
78
|
+
# installed deliberately win, and say so.
|
|
79
|
+
#
|
|
80
|
+
# The agent path above is never affected. It is absolute, so it always
|
|
81
|
+
# reaches this plugin's own CLI no matter what is on PATH.
|
|
82
|
+
human_link="${HOME}/.local/bin/acstack"
|
|
83
|
+
on_path=$(command -v acstack 2>/dev/null || true)
|
|
84
|
+
if [ -n "$on_path" ] && [ "$on_path" != "$human_link" ] &&
|
|
85
|
+
[ "$(readlink -f "$on_path" 2>/dev/null)" != "$(readlink -f "$human_link" 2>/dev/null)" ]; then
|
|
86
|
+
echo "acstack: \`acstack\` on PATH comes from ${on_path} – leaving it alone." >&2
|
|
87
|
+
echo "acstack: agents and skills are unaffected – they use ~/.claude/acstack/bin/acstack directly." >&2
|
|
88
|
+
else
|
|
89
|
+
link_cli "$cli_wrapper" "$human_link" "human CLI"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [ -L "$human_link" ]; then
|
|
93
|
+
case ":${PATH}:" in
|
|
94
|
+
*":${HOME}/.local/bin:"*) ;;
|
|
95
|
+
*)
|
|
96
|
+
# The human symlink exists but PATH won't pick it up. This only affects
|
|
97
|
+
# humans typing `acstack` at a terminal – agents are unaffected because
|
|
98
|
+
# they use the absolute ~/.claude/acstack/bin/acstack path.
|
|
99
|
+
rc_hint="~/.profile"
|
|
100
|
+
case "${SHELL:-}" in
|
|
101
|
+
*/zsh) rc_hint="~/.zshrc" ;;
|
|
102
|
+
*/bash) rc_hint="~/.bashrc (Linux) or ~/.bash_profile (macOS)" ;;
|
|
103
|
+
*/fish) rc_hint="~/.config/fish/config.fish" ;;
|
|
104
|
+
esac
|
|
105
|
+
echo "acstack: ~/.local/bin is not on PATH – typed \`acstack\` won't work. Add this to ${rc_hint} and restart the shell:" >&2
|
|
106
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" >&2
|
|
107
|
+
echo "acstack: agents and skills are unaffected – they use ~/.claude/acstack/bin/acstack directly. See ${troubleshoot} for details." >&2
|
|
108
|
+
;;
|
|
109
|
+
esac
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# --- Codex subagents: keep ~/.codex/agents current with the plugin ---
|
|
115
|
+
# Codex cannot ship agent definitions inside a plugin - its plugin manifest has
|
|
116
|
+
# no agent field - so they have to be installed as files. This reconciles them
|
|
117
|
+
# on every session, the same way the CLI symlink above is reconciled, so a
|
|
118
|
+
# plugin upgrade heals them instead of leaving stale copies behind.
|
|
119
|
+
#
|
|
120
|
+
# Copies rather than symlinks: Codex is known to refuse symlinked skill
|
|
121
|
+
# directories and manifests, and the agent loader's behaviour there is
|
|
122
|
+
# unverified. A copy is boring and works.
|
|
123
|
+
#
|
|
124
|
+
# Only ever overwrites files this generator wrote. A file without the generated
|
|
125
|
+
# marker is someone's own agent that happens to share a name, and clobbering it
|
|
126
|
+
# would be unforgivable.
|
|
127
|
+
if [ -n "$plugin_root" ] && [ -d "${HOME}/.codex" ]; then
|
|
128
|
+
codex_src="${plugin_root}/agents/codex"
|
|
129
|
+
codex_dst="${HOME}/.codex/agents"
|
|
130
|
+
marker="by scripts/gen-codex-agents.mjs"
|
|
131
|
+
|
|
132
|
+
if [ -d "$codex_src" ]; then
|
|
133
|
+
mkdir -p "$codex_dst" 2>/dev/null
|
|
134
|
+
for src in "$codex_src"/*.toml; do
|
|
135
|
+
[ -f "$src" ] || continue
|
|
136
|
+
dst="${codex_dst}/$(basename "$src")"
|
|
137
|
+
|
|
138
|
+
if [ -e "$dst" ] && ! grep -qF "$marker" "$dst" 2>/dev/null; then
|
|
139
|
+
echo "acstack: ${dst} was not written by acstack - leaving it alone." >&2
|
|
140
|
+
continue
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
if ! cmp -s "$src" "$dst" 2>/dev/null; then
|
|
144
|
+
if cp "$src" "$dst" 2>/dev/null; then
|
|
145
|
+
echo "acstack: installed Codex agent $(basename "$src" .toml)" >&2
|
|
146
|
+
else
|
|
147
|
+
echo "acstack: could not write ${dst}" >&2
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
done
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# --- Playwriter bridge: keep deployed scripts current with the plugin cache ---
|
|
155
|
+
# The bridge runs as OS scheduled tasks (Windows) and an on-demand helper (VM)
|
|
156
|
+
# that live outside the plugin, so a plugin update alone never reaches them.
|
|
157
|
+
# Reconcile them here, the same way the CLI symlink above is reconciled. Both
|
|
158
|
+
# branches no-op unless the bridge is actually present on this machine.
|
|
159
|
+
if [ -n "$plugin_root" ]; then
|
|
160
|
+
case "$(uname -s 2>/dev/null)" in
|
|
161
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
162
|
+
# Windows: copy changed bridge scripts out of the cache and restart only
|
|
163
|
+
# the affected tasks. sync.ps1 self-gates on "bridge installed".
|
|
164
|
+
sync_ps1="${plugin_root}/assets/playwriter-bridge/sync.ps1"
|
|
165
|
+
if [ -f "$sync_ps1" ] && command -v powershell.exe >/dev/null 2>&1; then
|
|
166
|
+
win_root=$(cygpath -w "$plugin_root" 2>/dev/null || printf '%s' "$plugin_root")
|
|
167
|
+
win_sync=$(cygpath -w "$sync_ps1" 2>/dev/null || printf '%s' "$sync_ps1")
|
|
168
|
+
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$win_sync" -PluginRoot "$win_root" >&2 2>&1 || true
|
|
169
|
+
fi
|
|
170
|
+
;;
|
|
171
|
+
Linux|Darwin)
|
|
172
|
+
# VM / macOS: vm-free-port.sh is invoked on demand, so a symlink to the
|
|
173
|
+
# cache copy keeps it current with no copy and no restart - mirrors the
|
|
174
|
+
# agent-facing CLI symlink. Skips silently if the helper is not shipped.
|
|
175
|
+
vmfp_src="${plugin_root}/assets/playwriter-bridge/vm-free-port.sh"
|
|
176
|
+
vmfp_link="${HOME}/.claude/acstack/bin/vm-free-port.sh"
|
|
177
|
+
if [ -f "$vmfp_src" ]; then
|
|
178
|
+
link_cli "$vmfp_src" "$vmfp_link" "vm-free-port.sh"
|
|
179
|
+
fi
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if [ ! -f acstack/project.yaml ]; then
|
|
185
|
+
echo 'acstack: no project config found. Run /acstack:setup to set up.' >&2
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
claude_md="${plugin_root}/orchestrator/AGENTS.md"
|
|
190
|
+
|
|
191
|
+
if [ -z "$plugin_root" ] || [ ! -r "$claude_md" ]; then
|
|
192
|
+
echo "acstack: could not read orchestrator/AGENTS.md (CLAUDE_PLUGIN_ROOT=${plugin_root:-unset})" >&2
|
|
193
|
+
exit 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
197
|
+
echo 'acstack: python3 not found; skipping orchestrator injection' >&2
|
|
198
|
+
exit 0
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
python3 - "$claude_md" <<'PY'
|
|
202
|
+
import json, sys
|
|
203
|
+
path = sys.argv[1]
|
|
204
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
205
|
+
content = f.read()
|
|
206
|
+
print(json.dumps({
|
|
207
|
+
"hookSpecificOutput": {
|
|
208
|
+
"hookEventName": "SessionStart",
|
|
209
|
+
"additionalContext": content,
|
|
210
|
+
}
|
|
211
|
+
}))
|
|
212
|
+
PY
|
|
213
|
+
|
|
214
|
+
exit 0
|