@paths.design/caws-cli 11.1.6 → 11.1.7
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 +1 -1
- package/dist/init/hook-packs/manifest-claude-code.d.ts +1 -1
- package/dist/init/hook-packs/manifest-claude-code.d.ts.map +1 -1
- package/dist/init/hook-packs/manifest-claude-code.js +59 -6
- package/dist/init/hook-packs/manifest-claude-code.js.map +1 -1
- package/dist/init/hook-packs/types.js +1 -1
- package/dist/init/hook-packs/types.js.map +1 -1
- package/dist/shell/commands/agents.d.ts +49 -0
- package/dist/shell/commands/agents.d.ts.map +1 -0
- package/dist/shell/commands/agents.js +577 -0
- package/dist/shell/commands/agents.js.map +1 -0
- package/dist/shell/commands/claim.d.ts.map +1 -1
- package/dist/shell/commands/claim.js +3 -4
- package/dist/shell/commands/claim.js.map +1 -1
- package/dist/shell/commands/status.d.ts +12 -0
- package/dist/shell/commands/status.d.ts.map +1 -1
- package/dist/shell/commands/status.js +236 -21
- package/dist/shell/commands/status.js.map +1 -1
- package/dist/shell/commands/worktree.d.ts +9 -0
- package/dist/shell/commands/worktree.d.ts.map +1 -1
- package/dist/shell/commands/worktree.js +302 -0
- package/dist/shell/commands/worktree.js.map +1 -1
- package/dist/shell/index.d.ts +4 -2
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +12 -1
- package/dist/shell/index.js.map +1 -1
- package/dist/shell/register.d.ts.map +1 -1
- package/dist/shell/register.js +150 -0
- package/dist/shell/register.js.map +1 -1
- package/dist/shell/render/status.d.ts +7 -1
- package/dist/shell/render/status.d.ts.map +1 -1
- package/dist/shell/render/status.js +72 -0
- package/dist/shell/render/status.js.map +1 -1
- package/dist/store/agents-store.d.ts.map +1 -1
- package/dist/store/agents-store.js +9 -0
- package/dist/store/agents-store.js.map +1 -1
- package/dist/store/apply-patch.d.ts.map +1 -1
- package/dist/store/apply-patch.js +15 -0
- package/dist/store/apply-patch.js.map +1 -1
- package/dist/store/doctor-snapshot.d.ts.map +1 -1
- package/dist/store/doctor-snapshot.js +143 -3
- package/dist/store/doctor-snapshot.js.map +1 -1
- package/dist/store/git-sparse-checkout.d.ts +25 -0
- package/dist/store/git-sparse-checkout.d.ts.map +1 -0
- package/dist/store/git-sparse-checkout.js +101 -0
- package/dist/store/git-sparse-checkout.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +10 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/leases-store.d.ts +89 -0
- package/dist/store/leases-store.d.ts.map +1 -0
- package/dist/store/leases-store.js +369 -0
- package/dist/store/leases-store.js.map +1 -0
- package/dist/store/lifecycle-transaction.d.ts.map +1 -1
- package/dist/store/lifecycle-transaction.js +34 -1
- package/dist/store/lifecycle-transaction.js.map +1 -1
- package/dist/store/rules.d.ts +21 -1
- package/dist/store/rules.d.ts.map +1 -1
- package/dist/store/rules.js +22 -0
- package/dist/store/rules.js.map +1 -1
- package/dist/store/types.d.ts +25 -1
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/worktrees-migration.d.ts +141 -0
- package/dist/store/worktrees-migration.d.ts.map +1 -0
- package/dist/store/worktrees-migration.js +356 -0
- package/dist/store/worktrees-migration.js.map +1 -0
- package/dist/store/worktrees-writer.d.ts.map +1 -1
- package/dist/store/worktrees-writer.js +37 -1
- package/dist/store/worktrees-writer.js.map +1 -1
- package/package.json +2 -2
- package/templates/hook-packs/claude-code/CLAUDE.md +5 -5
- package/templates/hook-packs/claude-code/agent-heartbeat.sh +131 -0
- package/templates/hook-packs/claude-code/agent-register.sh +62 -0
- package/templates/hook-packs/claude-code/agent-stop.sh +51 -0
- package/templates/hook-packs/claude-code/audit.sh +1 -1
- package/templates/hook-packs/claude-code/block-dangerous.sh +1 -1
- package/templates/hook-packs/claude-code/classify_command.py +1 -1
- package/templates/hook-packs/claude-code/dispatch/post_tool_use.sh +1 -1
- package/templates/hook-packs/claude-code/dispatch/pre_tool_use.sh +11 -2
- package/templates/hook-packs/claude-code/dispatch/session_start.sh +6 -2
- package/templates/hook-packs/claude-code/dispatch/stop.sh +7 -2
- package/templates/hook-packs/claude-code/guard-strikes.sh +1 -1
- package/templates/hook-packs/claude-code/lib/parse-input.sh +1 -1
- package/templates/hook-packs/claude-code/lib/run-handlers.sh +1 -1
- package/templates/hook-packs/claude-code/reset-danger-latch.sh +1 -1
- package/templates/hook-packs/claude-code/reset-strikes.sh +1 -1
- package/templates/hook-packs/claude-code/runtime-paths.sh +1 -1
- package/templates/hook-packs/claude-code/scope-guard.sh +1 -1
- package/templates/hook-packs/claude-code/session-caws-status.sh +7 -1
- package/templates/hook-packs/claude-code/session-log.sh +1 -1
- package/templates/hook-packs/claude-code/worktree-guard.sh +130 -4
- package/templates/hook-packs/claude-code/worktree-write-guard.sh +133 -18
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 5
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 19
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# PreToolUse handler — heartbeats the current session's lease and surfaces
|
|
10
|
+
# parallel-agent presence to the calling agent
|
|
11
|
+
# (MULTI-AGENT-ACTIVITY-REGISTRY-001).
|
|
12
|
+
#
|
|
13
|
+
# Sourcing: invoked by dispatch/pre_tool_use.sh (FIRST in the handler
|
|
14
|
+
# list) after parse-input.sh has populated HOOK_SESSION_ID. The dispatcher
|
|
15
|
+
# runs with --short-circuit-on-block; this handler must never block.
|
|
16
|
+
#
|
|
17
|
+
# Behavior:
|
|
18
|
+
# - Refuses on empty/unknown HOOK_SESSION_ID.
|
|
19
|
+
# - Invokes `caws agents heartbeat --session-id <id> --platform claude-code
|
|
20
|
+
# --throttle 30000 --reason pre_tool_use --json --include-active-summary`.
|
|
21
|
+
# - Parses CAWS-native JSON. When active_agent_count > 1, wraps the
|
|
22
|
+
# active_agents list into Claude Code's hookSpecificOutput.
|
|
23
|
+
# additionalContext envelope and emits it on stdout. When the count
|
|
24
|
+
# is 1 (self only), emits nothing — silent in the common case.
|
|
25
|
+
# - Throttled invocations still return an active_agents summary, so
|
|
26
|
+
# parallel-presence surfacing fires every tool call even when the
|
|
27
|
+
# write was skipped.
|
|
28
|
+
#
|
|
29
|
+
# IO BOUNDARY: this script is the ONLY surface that emits Claude Code's
|
|
30
|
+
# hookSpecificOutput.additionalContext envelope for lease state. The CLI
|
|
31
|
+
# emits CAWS-native JSON only. A Cursor or terminal integration would
|
|
32
|
+
# rewrite this script to emit its own protocol-specific output while
|
|
33
|
+
# reusing the same `caws agents heartbeat --json --include-active-summary`
|
|
34
|
+
# command verbatim.
|
|
35
|
+
#
|
|
36
|
+
# RUNTIME DEPENDENCIES: bash + node. node is already required by the CAWS
|
|
37
|
+
# CLI itself (which is a Node binary), so depending on it here adds no new
|
|
38
|
+
# runtime surface area. We do NOT depend on jq — jq is not guaranteed
|
|
39
|
+
# present on every install target (it is absent from many container base
|
|
40
|
+
# images and minimal CI runners), and a missing jq would silently drop
|
|
41
|
+
# every parallel-agent notice. The product goal is "agents see each
|
|
42
|
+
# other": that visibility cannot depend on a shell utility outside the
|
|
43
|
+
# CAWS toolchain.
|
|
44
|
+
#
|
|
45
|
+
# FAIL-CLOSED-NON-BLOCKING: if the CLI is absent, fails, or returns
|
|
46
|
+
# malformed JSON, this hook exits 0 silently. Heartbeat is observability
|
|
47
|
+
# and parallel-agent surfacing; a failure must never block the tool call.
|
|
48
|
+
|
|
49
|
+
set -uo pipefail
|
|
50
|
+
|
|
51
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
52
|
+
|
|
53
|
+
# shellcheck source=lib/parse-input.sh
|
|
54
|
+
source "$SCRIPT_DIR/lib/parse-input.sh" 2>/dev/null || exit 0
|
|
55
|
+
parse_hook_input || exit 0
|
|
56
|
+
|
|
57
|
+
if [[ -z "${HOOK_SESSION_ID:-}" || "$HOOK_SESSION_ID" == "unknown" ]]; then
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
CAWS_BIN="${CAWS_BIN:-caws}"
|
|
62
|
+
if ! command -v "$CAWS_BIN" >/dev/null 2>&1; then
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Capture both stdout (JSON) and stderr (diagnostics). On any CLI error,
|
|
67
|
+
# fall through to silent exit.
|
|
68
|
+
CLI_OUT="$(
|
|
69
|
+
"$CAWS_BIN" agents heartbeat \
|
|
70
|
+
--session-id "$HOOK_SESSION_ID" \
|
|
71
|
+
--platform claude-code \
|
|
72
|
+
--throttle 30000 \
|
|
73
|
+
--reason pre_tool_use \
|
|
74
|
+
--json \
|
|
75
|
+
--include-active-summary \
|
|
76
|
+
2>/dev/null
|
|
77
|
+
)" || exit 0
|
|
78
|
+
|
|
79
|
+
if [[ -z "$CLI_OUT" ]]; then
|
|
80
|
+
exit 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Parse the CAWS-native JSON and, when active_agent_count > 1, compose
|
|
84
|
+
# Claude Code's hookSpecificOutput.additionalContext envelope. A single
|
|
85
|
+
# node invocation does the whole pipeline: parse → filter peers → format
|
|
86
|
+
# bullet list → wrap envelope → emit. Malformed input, parse errors, or
|
|
87
|
+
# any thrown exception fall through to silent exit (fail-closed-non-
|
|
88
|
+
# blocking). Node is already a hard CAWS dependency — the CLI binary
|
|
89
|
+
# IS node — so this adds no new runtime surface vs. jq.
|
|
90
|
+
printf '%s' "$CLI_OUT" | node -e '
|
|
91
|
+
let raw = "";
|
|
92
|
+
process.stdin.setEncoding("utf8");
|
|
93
|
+
process.stdin.on("data", (chunk) => { raw += chunk; });
|
|
94
|
+
process.stdin.on("end", () => {
|
|
95
|
+
let parsed;
|
|
96
|
+
try { parsed = JSON.parse(raw); } catch { process.exit(0); }
|
|
97
|
+
const count = Number(parsed && parsed.active_agent_count);
|
|
98
|
+
if (!Number.isFinite(count) || count <= 1) process.exit(0);
|
|
99
|
+
const agents = Array.isArray(parsed.active_agents) ? parsed.active_agents : [];
|
|
100
|
+
const peers = agents.filter((a) => a && a.is_self !== true);
|
|
101
|
+
if (peers.length === 0) process.exit(0);
|
|
102
|
+
const bullets = peers.map((a) => {
|
|
103
|
+
const worktree = a.bound_worktree || "no worktree";
|
|
104
|
+
const spec = a.bound_spec_id ? " — spec " + a.bound_spec_id : "";
|
|
105
|
+
const kind = a.git_dir_kind || "unknown";
|
|
106
|
+
const branch = a.branch || "-";
|
|
107
|
+
const ageMs = Number(a.last_active_age_ms);
|
|
108
|
+
const ageSec = Number.isFinite(ageMs) ? Math.floor(ageMs / 1000) : 0;
|
|
109
|
+
return "• " + (a.session_id || "<unknown>") +
|
|
110
|
+
" (" + worktree + ")" + spec +
|
|
111
|
+
" — git_dir_kind=" + kind +
|
|
112
|
+
" — branch=" + branch +
|
|
113
|
+
" — last active " + ageSec + "s ago";
|
|
114
|
+
}).join("\n");
|
|
115
|
+
const ctx = "MULTI-AGENT NOTICE: " + count +
|
|
116
|
+
" agents active in this repo (including this session). Other active sessions:\n" +
|
|
117
|
+
bullets + "\n\n" +
|
|
118
|
+
"Coordinate via '\''caws agents list'\'' and '\''caws status'\'' before " +
|
|
119
|
+
"mutating shared state. Authority remains in .caws/worktrees.json " +
|
|
120
|
+
"(ownership) and .caws/specs/<id>.yaml (scope) — leases are " +
|
|
121
|
+
"visibility only.";
|
|
122
|
+
process.stdout.write(JSON.stringify({
|
|
123
|
+
hookSpecificOutput: {
|
|
124
|
+
hookEventName: "PreToolUse",
|
|
125
|
+
additionalContext: ctx,
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
});
|
|
129
|
+
' 2>/dev/null || exit 0
|
|
130
|
+
|
|
131
|
+
exit 0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 5
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 19
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# SessionStart handler — agent self-registration into the .caws/leases/
|
|
10
|
+
# liveness substrate (MULTI-AGENT-ACTIVITY-REGISTRY-001).
|
|
11
|
+
#
|
|
12
|
+
# Sourcing: invoked by dispatch/session_start.sh after parse-input.sh has
|
|
13
|
+
# populated HOOK_SESSION_ID. Reads HOOK_INPUT_JSON from stdin (unused for
|
|
14
|
+
# this handler beyond the parse-input contract).
|
|
15
|
+
#
|
|
16
|
+
# Behavior:
|
|
17
|
+
# - Refuses to run when HOOK_SESSION_ID is empty or "unknown" (the
|
|
18
|
+
# parse-input.sh fallback). A lease whose filename is "unknown.json"
|
|
19
|
+
# would collide across every session that hits the same fallback.
|
|
20
|
+
# - Invokes `caws agents register --session-id <id> --platform claude-code
|
|
21
|
+
# --reason session_start` to write the lease through the CLI.
|
|
22
|
+
# - Non-blocking. Any failure of the CLI surfaces as stderr only;
|
|
23
|
+
# SessionStart never fails on hook errors.
|
|
24
|
+
#
|
|
25
|
+
# IO boundary: this script is the only place that knows about Claude Code's
|
|
26
|
+
# SessionStart payload. The CLI receives explicit flags and returns
|
|
27
|
+
# CAWS-native JSON. The hook script does not produce additionalContext
|
|
28
|
+
# at SessionStart — the parallel-agent surfacing happens at PreToolUse
|
|
29
|
+
# via agent-heartbeat.sh.
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
|
|
35
|
+
# shellcheck source=lib/parse-input.sh
|
|
36
|
+
source "$SCRIPT_DIR/lib/parse-input.sh" 2>/dev/null || exit 0
|
|
37
|
+
parse_hook_input || exit 0
|
|
38
|
+
|
|
39
|
+
# Refuse on empty/unknown session id. Writing a lease at "unknown.json"
|
|
40
|
+
# would collide across every session that hits the parse-input fallback.
|
|
41
|
+
if [[ -z "${HOOK_SESSION_ID:-}" || "$HOOK_SESSION_ID" == "unknown" ]]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Locate caws binary. Prefer a project-local install (consistent with the
|
|
46
|
+
# repo's installed CLI version); fall back to PATH.
|
|
47
|
+
CAWS_BIN="${CAWS_BIN:-caws}"
|
|
48
|
+
if ! command -v "$CAWS_BIN" >/dev/null 2>&1; then
|
|
49
|
+
# No CAWS binary on PATH — silent skip. Liveness is best-effort.
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Invoke register. Send stderr to a buffer; we may want to attribute it
|
|
54
|
+
# in dispatcher output (prefixed by run-handlers).
|
|
55
|
+
"$CAWS_BIN" agents register \
|
|
56
|
+
--session-id "$HOOK_SESSION_ID" \
|
|
57
|
+
--platform claude-code \
|
|
58
|
+
--reason session_start \
|
|
59
|
+
>/dev/null 2>&1 || true
|
|
60
|
+
|
|
61
|
+
# Never block SessionStart.
|
|
62
|
+
exit 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 5
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 19
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# Stop handler — marks the current session's lease as stopped on clean
|
|
10
|
+
# session exit (MULTI-AGENT-ACTIVITY-REGISTRY-001).
|
|
11
|
+
#
|
|
12
|
+
# Sourcing: invoked by dispatch/stop.sh after parse-input.sh has populated
|
|
13
|
+
# HOOK_SESSION_ID.
|
|
14
|
+
#
|
|
15
|
+
# Behavior:
|
|
16
|
+
# - Refuses on empty/unknown HOOK_SESSION_ID.
|
|
17
|
+
# - Invokes `caws agents stop --session-id <id> --platform claude-code`
|
|
18
|
+
# which writes a mark_stopped LeasePatch (status: stopped, stopped_at
|
|
19
|
+
# timestamp). The lease file is preserved as evidence; hard deletion
|
|
20
|
+
# happens only via explicit `caws agents prune`.
|
|
21
|
+
# - Non-blocking. Stop semantics already require all handlers to be
|
|
22
|
+
# best-effort; a Stop failure is a warn, not a block.
|
|
23
|
+
#
|
|
24
|
+
# Note: Stop is NOT a guaranteed signal. A SIGKILL'd or crashed Claude
|
|
25
|
+
# Code session never reaches Stop. The primary liveness mechanism is
|
|
26
|
+
# heartbeat — Stop is the clean-exit optimization that lets observers
|
|
27
|
+
# distinguish "stopped cleanly" from "went stale and is presumed dead."
|
|
28
|
+
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
|
|
33
|
+
# shellcheck source=lib/parse-input.sh
|
|
34
|
+
source "$SCRIPT_DIR/lib/parse-input.sh" 2>/dev/null || exit 0
|
|
35
|
+
parse_hook_input || exit 0
|
|
36
|
+
|
|
37
|
+
if [[ -z "${HOOK_SESSION_ID:-}" || "$HOOK_SESSION_ID" == "unknown" ]]; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
CAWS_BIN="${CAWS_BIN:-caws}"
|
|
42
|
+
if ! command -v "$CAWS_BIN" >/dev/null 2>&1; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
"$CAWS_BIN" agents stop \
|
|
47
|
+
--session-id "$HOOK_SESSION_ID" \
|
|
48
|
+
--platform claude-code \
|
|
49
|
+
>/dev/null 2>&1 || true
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 5
|
|
5
5
|
# caws_min_major: 11
|
|
6
|
-
# lineage_refs: 8,11,17
|
|
6
|
+
# lineage_refs: 8,11,17,19
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
8
|
#
|
|
9
9
|
# PreToolUse dispatcher for Claude Code hooks.
|
|
@@ -40,7 +40,16 @@ source "$HOOKS_DIR/lib/run-handlers.sh" 2>/dev/null || exit 0
|
|
|
40
40
|
|
|
41
41
|
# Registered handlers in execution order. Each handler self-filters
|
|
42
42
|
# on $HOOK_TOOL_NAME; non-matching cases return exit 0 cheaply.
|
|
43
|
+
#
|
|
44
|
+
# MULTI-AGENT-ACTIVITY-REGISTRY-001: agent-heartbeat.sh runs FIRST so the
|
|
45
|
+
# lease is refreshed and parallel-agent presence is surfaced even when a
|
|
46
|
+
# later guard short-circuits the chain with exit 2 (block). Heartbeat is
|
|
47
|
+
# non-blocking and never produces a "block" decision — its stdout is a
|
|
48
|
+
# Claude-Code additionalContext envelope (priority 1), so it does not
|
|
49
|
+
# outrank a real block from scope-guard / worktree-guard. The dispatcher's
|
|
50
|
+
# stdout-priority logic ensures a block from a later handler still wins.
|
|
43
51
|
HANDLERS=(
|
|
52
|
+
agent-heartbeat.sh
|
|
44
53
|
block-dangerous.sh
|
|
45
54
|
worktree-guard.sh
|
|
46
55
|
scope-guard.sh
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 5
|
|
5
5
|
# caws_min_major: 11
|
|
6
|
-
# lineage_refs: 10,11
|
|
6
|
+
# lineage_refs: 10,11,19
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
8
|
# SessionStart dispatcher for Claude Code hooks.
|
|
9
9
|
#
|
|
@@ -36,6 +36,10 @@ HANDLERS=(
|
|
|
36
36
|
"audit.sh session-start"
|
|
37
37
|
# "session-caws-status.sh session-start"
|
|
38
38
|
"session-log.sh"
|
|
39
|
+
# MULTI-AGENT-ACTIVITY-REGISTRY-001: self-register into the leases
|
|
40
|
+
# substrate so other sessions can see this one. Non-blocking; refuses
|
|
41
|
+
# silently when HOOK_SESSION_ID is empty or "unknown".
|
|
42
|
+
"agent-register.sh"
|
|
39
43
|
)
|
|
40
44
|
|
|
41
45
|
run_handlers "${HANDLERS[@]}"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 5
|
|
5
5
|
# caws_min_major: 11
|
|
6
|
-
# lineage_refs: 10
|
|
6
|
+
# lineage_refs: 10,19
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
8
|
# Stop dispatcher for Claude Code hooks.
|
|
9
9
|
#
|
|
@@ -32,6 +32,11 @@ HANDLERS=(
|
|
|
32
32
|
# "stop-worktree-check.sh"
|
|
33
33
|
"plan-transcript-finalize.sh"
|
|
34
34
|
"session-log.sh"
|
|
35
|
+
# MULTI-AGENT-ACTIVITY-REGISTRY-001: mark our lease as stopped so other
|
|
36
|
+
# sessions can distinguish "stopped cleanly" from "went stale and is
|
|
37
|
+
# presumed dead." Non-blocking; refuses silently when HOOK_SESSION_ID
|
|
38
|
+
# is empty or "unknown".
|
|
39
|
+
"agent-stop.sh"
|
|
35
40
|
)
|
|
36
41
|
|
|
37
42
|
run_handlers "${HANDLERS[@]}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 5
|
|
5
5
|
# caws_min_major: 11
|
|
6
6
|
# lineage_refs: 4,11
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
@@ -109,6 +109,12 @@ if [ -f "$CAWS_ROOT/.caws/worktrees.json" ] && command -v node >/dev/null 2>&1;
|
|
|
109
109
|
echo " Worktree lifecycle commands (create/destroy/merge) return in"
|
|
110
110
|
echo " CAWS v11.1+; if you are on v11.0 they are not yet available."
|
|
111
111
|
echo ""
|
|
112
|
+
echo " CANONICAL-CHECKOUT-WORKTREE-GUARD-001 active:"
|
|
113
|
+
echo " Mutating git commands from this checkout (checkout, switch,"
|
|
114
|
+
echo " branch -f, reset non-hard) are now BLOCKED while worktrees"
|
|
115
|
+
echo " are active. Read-only commands remain allowed. To act on a"
|
|
116
|
+
echo " worktree's branch, enter the worktree first."
|
|
117
|
+
echo ""
|
|
112
118
|
else
|
|
113
119
|
echo ""
|
|
114
120
|
echo " You are on branch '$CURRENT_BRANCH' (worktree). Good."
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 5
|
|
5
5
|
# caws_min_major: 11
|
|
6
|
-
# lineage_refs: 4,6,11
|
|
6
|
+
# lineage_refs: 4,6,11,19
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
8
|
#
|
|
9
9
|
# CAWS Worktree Safety Guard for Claude Code (v11-shape).
|
|
@@ -50,11 +50,137 @@ if echo "$COMMAND" | grep -qE 'caws\s+(worktree\s+create|parallel\s+setup).*--sc
|
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
52
|
if echo "$COMMAND" | grep -qE '(^|;|&&|\|)\s*git\s+sparse-checkout'; then
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
# WORKTREE-SPEC-CANONICAL-ACCESS-GUARD-001 A3: blanket refusal stays.
|
|
54
|
+
# Agent-issued git sparse-checkout commands are refused regardless of
|
|
55
|
+
# subcommand (disable / set / init / reapply / list / add). Recovery
|
|
56
|
+
# of the canonical-spec-materialization invariant in a linked CAWS
|
|
57
|
+
# worktree is a CAWS worktree-repair concern routed through the CLI,
|
|
58
|
+
# not an agent-Bash git operation.
|
|
59
|
+
echo "BLOCKED: agent-issued git sparse-checkout is refused in CAWS projects." >&2
|
|
60
|
+
echo "" >&2
|
|
61
|
+
echo "Sparse-checkout in a CAWS linked worktree carries the mechanical guard" >&2
|
|
62
|
+
echo "against the v10.2 split-brain authority class: .caws/specs/ is excluded" >&2
|
|
63
|
+
echo "from the worktree by design, so canonical spec authority cannot be" >&2
|
|
64
|
+
echo "materialized inside the worktree as a divergent private copy. Disabling" >&2
|
|
65
|
+
echo "sparse-checkout (or any sparse-checkout reconfiguration via agent Bash)" >&2
|
|
66
|
+
echo "would re-open that class. Linked worktrees must not use worktree-local" >&2
|
|
67
|
+
echo ".caws/specs/ files as authority; CAWS resolves spec reads through the" >&2
|
|
68
|
+
echo "canonical control plane regardless of cwd." >&2
|
|
69
|
+
echo "" >&2
|
|
70
|
+
echo "To read a spec from any cwd (including this worktree), use:" >&2
|
|
71
|
+
echo " caws specs show <id>" >&2
|
|
72
|
+
echo "" >&2
|
|
73
|
+
echo "To check scope from any cwd, use:" >&2
|
|
74
|
+
echo " caws scope show <path>" >&2
|
|
75
|
+
echo " caws scope check <path>" >&2
|
|
76
|
+
echo "" >&2
|
|
77
|
+
echo "To restore the sparse-checkout invariant on a linked worktree (e.g.," >&2
|
|
78
|
+
echo "after a human-authorized sparse-checkout reconfiguration left the tree" >&2
|
|
79
|
+
echo "with materialized .caws/specs/ files), run from the canonical checkout:" >&2
|
|
80
|
+
echo " caws worktree repair-sparse <name>" >&2
|
|
81
|
+
echo "" >&2
|
|
82
|
+
echo "The repair command is non-destructive: it refuses dirty .caws/specs/" >&2
|
|
83
|
+
echo "rather than stashing, cleaning, or deleting work." >&2
|
|
55
84
|
exit 2
|
|
56
85
|
fi
|
|
57
86
|
|
|
87
|
+
# ─── CANONICAL-CHECKOUT-WORKTREE-GUARD-001 (Entry 19) ────────────────
|
|
88
|
+
# Block mutating git commands from the canonical checkout while at
|
|
89
|
+
# least one active CAWS worktree exists. Hook-layer enforcement only:
|
|
90
|
+
# authority remains in worktrees.json + specs. The guard's refusal
|
|
91
|
+
# predicate is conjunctive: canonical + worktrees-active + mutating
|
|
92
|
+
# command. Any one false MUST allow.
|
|
93
|
+
#
|
|
94
|
+
# Leases (.caws/leases/*.json) are NOT consulted by this decision —
|
|
95
|
+
# stale-lease-is-evidence-never-authority. The block decision uses
|
|
96
|
+
# worktrees.json's active entries only.
|
|
97
|
+
canonical_guard_emit_block() {
|
|
98
|
+
local action="$1"
|
|
99
|
+
local first_active="$2"
|
|
100
|
+
echo "BLOCKED: $action from the canonical checkout while CAWS worktrees are active." >&2
|
|
101
|
+
echo "Active worktree(s) detected (e.g. '$first_active' in .caws/worktrees.json)." >&2
|
|
102
|
+
echo "Switch into your worktree before mutating: cd .caws/worktrees/$first_active" >&2
|
|
103
|
+
echo "Or destroy any worktree that is genuinely abandoned: caws worktree destroy <name>" >&2
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Determine whether the session's cwd is the canonical checkout.
|
|
107
|
+
# git_dir == git_common_dir indicates canonical; a linked worktree has
|
|
108
|
+
# git_dir under git_common_dir/worktrees/<name>/.
|
|
109
|
+
CANONICAL_GUARD_CHECK_CWD="${HOOK_CWD:-$PROJECT_DIR}"
|
|
110
|
+
if command -v git >/dev/null 2>&1 && [[ -d "$CANONICAL_GUARD_CHECK_CWD" ]]; then
|
|
111
|
+
GIT_DIR_RESOLVED=$(cd "$CANONICAL_GUARD_CHECK_CWD" && git rev-parse --git-dir 2>/dev/null | head -1 || echo "")
|
|
112
|
+
GIT_COMMON_RESOLVED=$(cd "$CANONICAL_GUARD_CHECK_CWD" && git rev-parse --git-common-dir 2>/dev/null | head -1 || echo "")
|
|
113
|
+
if [[ -n "$GIT_DIR_RESOLVED" ]] && [[ -n "$GIT_COMMON_RESOLVED" ]]; then
|
|
114
|
+
# Normalize to absolute paths so equality is structural, not textual.
|
|
115
|
+
GIT_DIR_ABS=$(cd "$CANONICAL_GUARD_CHECK_CWD" && cd "$GIT_DIR_RESOLVED" 2>/dev/null && pwd || echo "$GIT_DIR_RESOLVED")
|
|
116
|
+
GIT_COMMON_ABS=$(cd "$CANONICAL_GUARD_CHECK_CWD" && cd "$GIT_COMMON_RESOLVED" 2>/dev/null && pwd || echo "$GIT_COMMON_RESOLVED")
|
|
117
|
+
if [[ "$GIT_DIR_ABS" == "$GIT_COMMON_ABS" ]]; then
|
|
118
|
+
# We are in the canonical checkout. Now check for active worktrees.
|
|
119
|
+
WORKTREES_JSON="$PROJECT_DIR/.caws/worktrees.json"
|
|
120
|
+
if [[ -f "$WORKTREES_JSON" ]] && command -v node >/dev/null 2>&1; then
|
|
121
|
+
FIRST_ACTIVE_WT=$(node -e "
|
|
122
|
+
try {
|
|
123
|
+
var reg = JSON.parse(require('fs').readFileSync('$WORKTREES_JSON', 'utf8'));
|
|
124
|
+
function entriesOf(r) {
|
|
125
|
+
if (!r || typeof r !== 'object') return [];
|
|
126
|
+
if (r.worktrees && typeof r.worktrees === 'object') {
|
|
127
|
+
return Object.entries(r.worktrees);
|
|
128
|
+
}
|
|
129
|
+
var out = [];
|
|
130
|
+
for (var k in r) {
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(r, k)) {
|
|
132
|
+
var v = r[k];
|
|
133
|
+
if (v && typeof v === 'object') out.push([k, v]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
var entries = entriesOf(reg);
|
|
139
|
+
// 'active' is the documented status; entries without an
|
|
140
|
+
// explicit status (legacy/in-flight registry shapes) are
|
|
141
|
+
// also treated as active because the CLI's createWorktree
|
|
142
|
+
// does not always emit a status field.
|
|
143
|
+
var active = entries.filter(function(e) {
|
|
144
|
+
var s = e[1] && e[1].status;
|
|
145
|
+
return s === 'active' || s === undefined || s === null || s === '';
|
|
146
|
+
});
|
|
147
|
+
if (active.length > 0) console.log(active[0][0]);
|
|
148
|
+
else console.log('');
|
|
149
|
+
} catch(e) { console.log(''); }
|
|
150
|
+
" 2>/dev/null || echo "")
|
|
151
|
+
if [[ -n "$FIRST_ACTIVE_WT" ]]; then
|
|
152
|
+
# Predicate (a) canonical + (b) at least one active worktree
|
|
153
|
+
# is satisfied. Now check (c) mutation command keywords.
|
|
154
|
+
# Read-only commands (status, log, diff, show, fetch w/o --prune,
|
|
155
|
+
# rev-parse, ls-files, branch -v, stash list) are NOT in this
|
|
156
|
+
# set; they fall through to the existing guard rules.
|
|
157
|
+
if echo "$COMMAND" | grep -qE '(^|[[:space:];&|])git\s+checkout\s+[^[:space:]-]'; then
|
|
158
|
+
canonical_guard_emit_block "git checkout (branch switch)" "$FIRST_ACTIVE_WT"
|
|
159
|
+
exit 2
|
|
160
|
+
fi
|
|
161
|
+
if echo "$COMMAND" | grep -qE '(^|[[:space:];&|])git\s+switch\s+[^[:space:]-]'; then
|
|
162
|
+
canonical_guard_emit_block "git switch (branch switch)" "$FIRST_ACTIVE_WT"
|
|
163
|
+
exit 2
|
|
164
|
+
fi
|
|
165
|
+
if echo "$COMMAND" | grep -qE '(^|[[:space:];&|])git\s+branch\s+(-f|--force)'; then
|
|
166
|
+
canonical_guard_emit_block "git branch -f (force branch update)" "$FIRST_ACTIVE_WT"
|
|
167
|
+
exit 2
|
|
168
|
+
fi
|
|
169
|
+
# git reset variants other than --hard (already covered later in
|
|
170
|
+
# this file) — --keep, --merge, --soft, --mixed, or with no
|
|
171
|
+
# mode flag — mutate the canonical's working tree/HEAD.
|
|
172
|
+
if echo "$COMMAND" | grep -qE '(^|[[:space:];&|])git\s+reset\b' \
|
|
173
|
+
&& ! echo "$COMMAND" | grep -qE 'git\s+reset\s+--hard'; then
|
|
174
|
+
canonical_guard_emit_block "git reset (HEAD mutation)" "$FIRST_ACTIVE_WT"
|
|
175
|
+
exit 2
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
# ─── /CANONICAL-CHECKOUT-WORKTREE-GUARD-001 ──────────────────────────
|
|
183
|
+
|
|
58
184
|
# Block cross-boundary file copies (worktree → main).
|
|
59
185
|
WORKTREE_BASE="$PROJECT_DIR/.caws/worktrees"
|
|
60
186
|
if [[ -d "$WORKTREE_BASE" ]]; then
|