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.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +389 -2
  4. package/THIRD_PARTY_NOTICES +14 -0
  5. package/TROUBLESHOOTING.md +225 -0
  6. package/agents/codex/frontend-verifier.toml +352 -0
  7. package/agents/codex/task-checker.toml +345 -0
  8. package/agents/codex/task-executor.toml +157 -0
  9. package/agents/codex/test-writer.toml +149 -0
  10. package/agents/frontend-verifier.md +351 -0
  11. package/agents/task-checker.md +344 -0
  12. package/agents/task-executor.md +156 -0
  13. package/agents/test-writer.md +148 -0
  14. package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
  15. package/assets/playwriter-bridge/doctor.ps1 +450 -0
  16. package/assets/playwriter-bridge/launcher-src.cs +131 -0
  17. package/assets/playwriter-bridge/opener.js +330 -0
  18. package/assets/playwriter-bridge/opener.ps1 +63 -0
  19. package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
  20. package/assets/playwriter-bridge/relay.ps1 +50 -0
  21. package/assets/playwriter-bridge/stop.ps1 +125 -0
  22. package/assets/playwriter-bridge/sync.ps1 +271 -0
  23. package/assets/playwriter-bridge/tunnel.ps1 +325 -0
  24. package/assets/playwriter-bridge/vm-free-port.sh +166 -0
  25. package/assets/terminal-workspace/client-keybindings.md +87 -0
  26. package/assets/terminal-workspace/config.example.yaml +329 -0
  27. package/assets/terminal-workspace/setup.sh +4317 -0
  28. package/cli/README.md +47 -0
  29. package/cli/bin/acstack.js +3 -0
  30. package/cli/dist/index.js +25984 -0
  31. package/cli/dist/index.js.map +7 -0
  32. package/cli/package.json +59 -0
  33. package/docs/adr/0001-ste-writing-standard.md +16 -0
  34. package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
  35. package/docs/adr/0003-handoff-ack-command.md +19 -0
  36. package/docs/adr/0004-dev-setup-per-host.md +13 -0
  37. package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
  38. package/docs/adr/0006-document-opener-service.md +89 -0
  39. package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
  40. package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
  41. package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
  42. package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
  43. package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
  44. package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
  45. package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
  46. package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
  47. package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
  48. package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
  49. package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
  50. package/docs/adr/index.md +17 -0
  51. package/docs/config-reference.md +572 -0
  52. package/docs/dev-setups.md +164 -0
  53. package/docs/migrate-0.8.md +28 -0
  54. package/docs/playwriter-bridge-windows.md +15 -0
  55. package/docs/terminal-workspace-keys.md +137 -0
  56. package/docs/terminal-workspace.md +1774 -0
  57. package/docs/workflows.md +309 -0
  58. package/hooks/ac-writing-style-reminder.sh +86 -0
  59. package/hooks/context-pressure.sh +583 -0
  60. package/hooks/hooks.json +50 -0
  61. package/hooks/session-start-handoff.sh +165 -0
  62. package/hooks/session-start.sh +214 -0
  63. package/orchestrator/AGENTS.md +532 -0
  64. package/output-styles/ac-writing-style.md +286 -0
  65. package/package.json +50 -4
  66. package/pi/agents/frontend-verifier.md +357 -0
  67. package/pi/agents/task-checker.md +348 -0
  68. package/pi/agents/task-executor.md +160 -0
  69. package/pi/agents/test-writer.md +154 -0
  70. package/pi/extension.ts +723 -0
  71. package/profiles/fullstack-python.yaml +29 -0
  72. package/profiles/node-typescript.yaml +23 -0
  73. package/profiles/python.yaml +24 -0
  74. package/skills/archive/SKILL.md +160 -0
  75. package/skills/bootstrap/SKILL.md +109 -0
  76. package/skills/bridge/HANDOFF.md +93 -0
  77. package/skills/bridge/SKILL-VM.md +292 -0
  78. package/skills/bridge/SKILL-WINDOWS.md +526 -0
  79. package/skills/bridge/SKILL.md +121 -0
  80. package/skills/browser-verify/SKILL.md +229 -0
  81. package/skills/code-review/SKILL.md +44 -0
  82. package/skills/commit/SKILL.md +106 -0
  83. package/skills/compact-and-continue/SKILL.md +241 -0
  84. package/skills/explore/SKILL.md +149 -0
  85. package/skills/grilling/SKILL.md +134 -0
  86. package/skills/handoff/SKILL.md +250 -0
  87. package/skills/handoff/launch-herdr.md +197 -0
  88. package/skills/handoff/launch-without-herdr.md +30 -0
  89. package/skills/handoff/lead-check.md +59 -0
  90. package/skills/handoff/outcomes.md +162 -0
  91. package/skills/handoff/running-agent.md +50 -0
  92. package/skills/handoff/watch.md +115 -0
  93. package/skills/host/SKILL.md +247 -0
  94. package/skills/host/detect-host.sh +297 -0
  95. package/skills/host/herdr.md +79 -0
  96. package/skills/host/install-herdr-block.sh +467 -0
  97. package/skills/host/install-orca.sh +512 -0
  98. package/skills/host/notes.md +97 -0
  99. package/skills/host/orca.md +160 -0
  100. package/skills/host/questions.md +163 -0
  101. package/skills/host/report.md +42 -0
  102. package/skills/implement/SKILL.md +230 -0
  103. package/skills/implement/decision-log.md +62 -0
  104. package/skills/implement/design-pass.md +32 -0
  105. package/skills/implement/relaxed-tdd.md +28 -0
  106. package/skills/map/SKILL.md +250 -0
  107. package/skills/map/frontend.md +227 -0
  108. package/skills/map/trackers/files.md +74 -0
  109. package/skills/map/trackers/github.md +180 -0
  110. package/skills/map/visuals.md +387 -0
  111. package/skills/new-worktree/SKILL.md +155 -0
  112. package/skills/new-worktree/detect-orchestrator.sh +328 -0
  113. package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
  114. package/skills/new-worktree/herdr.md +84 -0
  115. package/skills/new-worktree/orca.md +101 -0
  116. package/skills/pre-commit-review/SKILL.md +242 -0
  117. package/skills/pre-commit-review/auto-fix.md +19 -0
  118. package/skills/pre-commit-review/interactive.md +35 -0
  119. package/skills/quick-fix/SKILL.md +125 -0
  120. package/skills/record-context/SKILL.md +106 -0
  121. package/skills/setup/SKILL.md +250 -0
  122. package/skills/setup/browser.md +123 -0
  123. package/skills/setup/design-review.md +97 -0
  124. package/skills/setup/first-run.md +114 -0
  125. package/skills/setup/report.md +68 -0
  126. package/skills/setup/row-fixes.md +196 -0
  127. package/skills/setup/terminal-workspace.md +153 -0
  128. package/skills/sync/SKILL.md +171 -0
  129. package/skills/teardown-worktree/SKILL.md +245 -0
  130. package/skills/teardown-worktree/detector.md +39 -0
  131. package/skills/teardown-worktree/herdr.md +113 -0
  132. package/skills/teardown-worktree/orca.md +61 -0
  133. package/skills/terminal-workspace/SKILL.md +249 -0
  134. package/skills/terminal-workspace/components.md +36 -0
  135. package/skills/terminal-workspace/config-choices.md +69 -0
  136. package/skills/terminal-workspace/emulator-keys.md +48 -0
  137. package/skills/terminal-workspace/headless-clipboard.md +175 -0
  138. package/skills/terminal-workspace/turn-off-and-update.md +104 -0
  139. package/skills/test-conventions/SKILL.md +59 -0
  140. package/skills/visual-docs/SKILL.md +246 -0
  141. package/skills/writing/SKILL.md +231 -0
  142. package/skills/writing/examples.md +153 -0
  143. 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