rhachet-roles-bhuild 0.1.3 → 0.4.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/dist/domain.operations/behavior/bind/flattenBranchName.cli.d.ts +2 -0
- package/dist/domain.operations/behavior/bind/flattenBranchName.cli.js +19 -0
- package/dist/domain.operations/behavior/bind/flattenBranchName.cli.js.map +1 -0
- package/dist/domain.operations/behavior/bind/flattenBranchName.d.ts +8 -0
- package/dist/domain.operations/behavior/bind/flattenBranchName.js +19 -0
- package/dist/domain.operations/behavior/bind/flattenBranchName.js.map +1 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.cli.d.ts +2 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.cli.js +18 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.cli.js.map +1 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.d.ts +11 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.js +51 -0
- package/dist/domain.operations/behavior/bind/getBoundBehaviorByBranch.js.map +1 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.cli.d.ts +2 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.cli.js +20 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.cli.js.map +1 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.d.ts +11 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.js +51 -0
- package/dist/domain.operations/behavior/bind/getLatestBlueprintByBehavior.js.map +1 -0
- package/dist/domain.operations/behavior/bind/index.d.ts +3 -0
- package/dist/domain.operations/behavior/bind/index.js +10 -0
- package/dist/domain.operations/behavior/bind/index.js.map +1 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.blueprint/rule.require.criteria-satisfied.md +37 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.blueprint/rule.require.determinism-declared.md +44 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.blueprint/rule.require.test-coverage-specified.md +42 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.blueprint/rule.require.test-patterns-specified.md +43 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.criteria/rule.prefer.dependency-order.md +43 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.criteria/rule.prefer.depth-groups.md +44 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.criteria/rule.prefer.usecase-groups.md +41 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.criteria/rule.require.bdd-format.md +25 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.roadmap/rule.prefer.clear-deliverables.md +46 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.roadmap/rule.prefer.dependency-order.md +59 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.wish/rule.prefer.bounded-scope.md +29 -0
- package/dist/domain.roles/behaver/briefs/practices/behavior.wish/rule.prefer.clear-desires.md +31 -0
- package/dist/domain.roles/behaver/inits/claude.hooks/sessionstart.boot-behavior.sh +109 -0
- package/dist/domain.roles/behaver/inits/init.claude.hooks.findsert.sh +226 -0
- package/dist/domain.roles/behaver/inits/init.claude.hooks.sh +34 -0
- package/dist/domain.roles/behaver/skills/bind.behavior.sh +236 -0
- package/dist/domain.roles/behaver/skills/init.behavior.sh +51 -0
- package/dist/domain.roles/behaver/skills/review.behavior.sh +396 -0
- package/dist/domain.roles/behaver/skills/review.behavior.test.utils.d.ts +12 -0
- package/dist/domain.roles/behaver/skills/review.behavior.test.utils.js +63 -0
- package/dist/domain.roles/behaver/skills/review.behavior.test.utils.js.map +1 -0
- package/dist/domain.roles/behaver/skills/review.deliverable.sh +344 -0
- package/package.json +10 -8
- package/readme.md +34 -0
package/dist/domain.roles/behaver/briefs/practices/behavior.roadmap/rule.prefer.dependency-order.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
.rule = prefer.dependency-order
|
|
2
|
+
|
|
3
|
+
.what = roadmap phases should be ordered by dependency
|
|
4
|
+
|
|
5
|
+
.why = dependency order enables correct execution sequence and parallel work identification; prerequisites must be built before dependents
|
|
6
|
+
|
|
7
|
+
.how = verify that phase prerequisites are listed before dependents; check for circular dependencies which are blockers
|
|
8
|
+
|
|
9
|
+
.examples:
|
|
10
|
+
.positive:
|
|
11
|
+
- |
|
|
12
|
+
## dependencies
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
phase.0 (scaffolding)
|
|
16
|
+
↓
|
|
17
|
+
phase.1 (rule briefs)
|
|
18
|
+
↓
|
|
19
|
+
phase.2 (composite skill)
|
|
20
|
+
↓
|
|
21
|
+
phase.3 (symlinks)
|
|
22
|
+
↓
|
|
23
|
+
phase.4 (validation)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## phase.0 = scaffolding
|
|
27
|
+
|
|
28
|
+
### dependencies
|
|
29
|
+
- none
|
|
30
|
+
|
|
31
|
+
## phase.1 = rule briefs
|
|
32
|
+
|
|
33
|
+
### dependencies
|
|
34
|
+
- [x] phase.0 complete (scaffolding exists)
|
|
35
|
+
.negative:
|
|
36
|
+
- |
|
|
37
|
+
## phase.1 = validation
|
|
38
|
+
|
|
39
|
+
run validation to check everything works.
|
|
40
|
+
|
|
41
|
+
## phase.2 = implementation
|
|
42
|
+
|
|
43
|
+
implement the features to validate.
|
|
44
|
+
|
|
45
|
+
(validation before implementation - wrong order)
|
|
46
|
+
- |
|
|
47
|
+
## phase.1 = skill
|
|
48
|
+
|
|
49
|
+
### dependencies
|
|
50
|
+
- phase.2 complete
|
|
51
|
+
|
|
52
|
+
## phase.2 = tests
|
|
53
|
+
|
|
54
|
+
### dependencies
|
|
55
|
+
- phase.1 complete
|
|
56
|
+
|
|
57
|
+
(circular dependency)
|
|
58
|
+
|
|
59
|
+
.severity = BLOCKER
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.rule = prefer.bounded-scope
|
|
2
|
+
|
|
3
|
+
.what = wishes should define boundaries of what is in scope and out of scope
|
|
4
|
+
|
|
5
|
+
.why = unbounded scope leads to scope creep and unclear completion criteria; explicit boundaries enable focused implementation
|
|
6
|
+
|
|
7
|
+
.how = check for explicit scope boundaries or constraints; verify the wish does not imply unlimited or vague scope
|
|
8
|
+
|
|
9
|
+
.examples:
|
|
10
|
+
.positive:
|
|
11
|
+
- |
|
|
12
|
+
# wish
|
|
13
|
+
|
|
14
|
+
wish = create rule briefs for behavior artifact review
|
|
15
|
+
|
|
16
|
+
## scope
|
|
17
|
+
- in: wish, criteria, blueprint, roadmap artifacts
|
|
18
|
+
- out: code review, test review, documentation review
|
|
19
|
+
.negative:
|
|
20
|
+
- |
|
|
21
|
+
# wish
|
|
22
|
+
|
|
23
|
+
wish = review all project artifacts comprehensively
|
|
24
|
+
- |
|
|
25
|
+
# wish
|
|
26
|
+
|
|
27
|
+
wish = make everything better
|
|
28
|
+
|
|
29
|
+
.severity = NITPICK
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.rule = prefer.clear-desires
|
|
2
|
+
|
|
3
|
+
.what = wishes must clearly state what is desired
|
|
4
|
+
|
|
5
|
+
.why = ambiguous wishes lead to misaligned implementations; clear desires enable accurate criteria, blueprints, and roadmaps
|
|
6
|
+
|
|
7
|
+
.how = check for explicit "wish =" statement with concrete, actionable desires; verify desires are specific enough to derive testable criteria
|
|
8
|
+
|
|
9
|
+
.examples:
|
|
10
|
+
.positive:
|
|
11
|
+
- |
|
|
12
|
+
# wish
|
|
13
|
+
|
|
14
|
+
wish = create a skill that reviews behavior artifacts against best practice rules
|
|
15
|
+
|
|
16
|
+
## desires
|
|
17
|
+
- review wish documents for clarity and scope
|
|
18
|
+
- review criteria documents for bdd format compliance
|
|
19
|
+
- review blueprints for test coverage specification
|
|
20
|
+
- review roadmaps for dependency order
|
|
21
|
+
.negative:
|
|
22
|
+
- |
|
|
23
|
+
# wish
|
|
24
|
+
|
|
25
|
+
make the system better at reviewing things
|
|
26
|
+
- |
|
|
27
|
+
# wish
|
|
28
|
+
|
|
29
|
+
wish = improve quality
|
|
30
|
+
|
|
31
|
+
.severity = BLOCKER
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = inject bound behavior context on session start
|
|
4
|
+
#
|
|
5
|
+
# .why = agent starts with full awareness of current behavior
|
|
6
|
+
#
|
|
7
|
+
# .note = SessionStart hooks run on both session start AND compaction
|
|
8
|
+
# this means behavior context is re-injected after context
|
|
9
|
+
# is summarized, keeping the agent aligned.
|
|
10
|
+
#
|
|
11
|
+
# guarantee:
|
|
12
|
+
# ✔ exits 0 silently if branch not bound (unbound = normal work)
|
|
13
|
+
# ✔ exits 0 with warning if multiple bindings (don't block session)
|
|
14
|
+
# ✔ never blocks session start (exit 0 always)
|
|
15
|
+
######################################################################
|
|
16
|
+
|
|
17
|
+
# note: no set -e because we must exit 0 always
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
|
|
20
|
+
# resolve script location and repo root
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
|
|
23
|
+
|
|
24
|
+
# ────────────────────────────────────────────────────────────────────
|
|
25
|
+
# helpers
|
|
26
|
+
# ────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
get_bound_behavior() {
|
|
29
|
+
npx tsx "$REPO_ROOT/src/domain.operations/behavior/bind/getBoundBehaviorByBranch.cli.ts" "$1" 2>/dev/null || echo '{"behaviorDir":null,"bindings":[]}'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get_latest_blueprint() {
|
|
33
|
+
npx tsx "$REPO_ROOT/src/domain.operations/behavior/bind/getLatestBlueprintByBehavior.cli.ts" "$1" 2>/dev/null || echo ""
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ────────────────────────────────────────────────────────────────────
|
|
37
|
+
# main
|
|
38
|
+
# ────────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
# get current branch
|
|
41
|
+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
42
|
+
if [[ -z "$CURRENT_BRANCH" ]]; then
|
|
43
|
+
exit 0 # not in a git repo, silently exit
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# check if bound
|
|
47
|
+
BINDING_RESULT=$(get_bound_behavior "$CURRENT_BRANCH")
|
|
48
|
+
BEHAVIOR_DIR=$(echo "$BINDING_RESULT" | jq -r '.behaviorDir // empty')
|
|
49
|
+
BINDINGS_COUNT=$(echo "$BINDING_RESULT" | jq -r '.bindings | length')
|
|
50
|
+
|
|
51
|
+
# if not bound, exit silently
|
|
52
|
+
if [[ -z "$BEHAVIOR_DIR" || "$BEHAVIOR_DIR" == "null" ]]; then
|
|
53
|
+
# check for multiple bindings (conflict)
|
|
54
|
+
if [[ "$BINDINGS_COUNT" -gt 1 ]]; then
|
|
55
|
+
echo "⚠️ warning: branch '$CURRENT_BRANCH' has conflicting bindings:" >&2
|
|
56
|
+
echo "$BINDING_RESULT" | jq -r '.bindings[]' | while read -r binding; do
|
|
57
|
+
echo " - $(basename "$binding")" >&2
|
|
58
|
+
done
|
|
59
|
+
echo "use 'bind.behavior.sh --del' to unbind and 'bind.behavior.sh --set' to rebind" >&2
|
|
60
|
+
fi
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# extract behavior name from path
|
|
65
|
+
BEHAVIOR_NAME=$(basename "$BEHAVIOR_DIR")
|
|
66
|
+
|
|
67
|
+
# ────────────────────────────────────────────────────────────────────
|
|
68
|
+
# output behavior context
|
|
69
|
+
# ────────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
echo "=================================================="
|
|
72
|
+
echo "BOUND BEHAVIOR: $BEHAVIOR_NAME"
|
|
73
|
+
echo "=================================================="
|
|
74
|
+
echo ""
|
|
75
|
+
|
|
76
|
+
# helper to output a behavior file
|
|
77
|
+
output_behavior_file() {
|
|
78
|
+
local tag="$1"
|
|
79
|
+
local filepath="$2"
|
|
80
|
+
local is_required="$3"
|
|
81
|
+
|
|
82
|
+
if [[ -f "$filepath" ]]; then
|
|
83
|
+
local relpath
|
|
84
|
+
relpath=$(realpath --relative-to="$PWD" "$filepath" 2>/dev/null || echo "$filepath")
|
|
85
|
+
echo "<behavior-$tag path=\"$relpath\">"
|
|
86
|
+
cat "$filepath"
|
|
87
|
+
echo "</behavior-$tag>"
|
|
88
|
+
echo ""
|
|
89
|
+
elif [[ "$is_required" == "true" ]]; then
|
|
90
|
+
echo "⚠️ missing required file: $filepath" >&2
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# output wish (required)
|
|
95
|
+
output_behavior_file "wish" "$BEHAVIOR_DIR/0.wish.md" "true"
|
|
96
|
+
|
|
97
|
+
# output vision (optional)
|
|
98
|
+
output_behavior_file "vision" "$BEHAVIOR_DIR/1.vision.md" "false"
|
|
99
|
+
|
|
100
|
+
# output criteria (optional)
|
|
101
|
+
output_behavior_file "criteria" "$BEHAVIOR_DIR/2.criteria.md" "false"
|
|
102
|
+
|
|
103
|
+
# output latest blueprint (optional)
|
|
104
|
+
LATEST_BLUEPRINT=$(get_latest_blueprint "$BEHAVIOR_DIR")
|
|
105
|
+
if [[ -n "$LATEST_BLUEPRINT" && -f "$LATEST_BLUEPRINT" ]]; then
|
|
106
|
+
output_behavior_file "blueprint" "$LATEST_BLUEPRINT" "false"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
echo "=================================================="
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = generic findsert utility for Claude hooks
|
|
4
|
+
#
|
|
5
|
+
# .why = centralizes the "find-or-insert" logic for binding hooks
|
|
6
|
+
# to .claude/settings.local.json, avoiding duplication across
|
|
7
|
+
# individual hook initializers.
|
|
8
|
+
#
|
|
9
|
+
# .how = takes hook configuration as arguments and uses jq to merge
|
|
10
|
+
# the hook into the settings file, creating structure if absent.
|
|
11
|
+
#
|
|
12
|
+
# usage:
|
|
13
|
+
# init.claude.hooks.findsert.sh \
|
|
14
|
+
# --hook-type SessionStart|PreToolUse \
|
|
15
|
+
# --matcher "*"|"Bash"|... \
|
|
16
|
+
# --command "command to run" \
|
|
17
|
+
# --name "hookname" \
|
|
18
|
+
# [--timeout 5] \
|
|
19
|
+
# [--position append|prepend]
|
|
20
|
+
#
|
|
21
|
+
# guarantee:
|
|
22
|
+
# ✔ creates .claude/settings.local.json if missing
|
|
23
|
+
# ✔ preserves existing settings (permissions, other hooks)
|
|
24
|
+
# ✔ idempotent: no-op if hook already present (at correct position)
|
|
25
|
+
# ✔ fail-fast on errors
|
|
26
|
+
######################################################################
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
# fail loud: print what failed
|
|
31
|
+
trap 'echo "❌ init.claude.hooks.findsert.sh failed at line $LINENO" >&2' ERR
|
|
32
|
+
|
|
33
|
+
# Defaults
|
|
34
|
+
HOOK_TYPE=""
|
|
35
|
+
MATCHER=""
|
|
36
|
+
HOOK_COMMAND=""
|
|
37
|
+
HOOK_NAME=""
|
|
38
|
+
TIMEOUT=5
|
|
39
|
+
POSITION="append"
|
|
40
|
+
AUTHOR="repo=bhuild/role=behaver"
|
|
41
|
+
|
|
42
|
+
# Parse arguments
|
|
43
|
+
while [[ $# -gt 0 ]]; do
|
|
44
|
+
case "$1" in
|
|
45
|
+
--hook-type)
|
|
46
|
+
HOOK_TYPE="$2"
|
|
47
|
+
shift 2
|
|
48
|
+
;;
|
|
49
|
+
--matcher)
|
|
50
|
+
MATCHER="$2"
|
|
51
|
+
shift 2
|
|
52
|
+
;;
|
|
53
|
+
--command)
|
|
54
|
+
HOOK_COMMAND="$2"
|
|
55
|
+
shift 2
|
|
56
|
+
;;
|
|
57
|
+
--name)
|
|
58
|
+
HOOK_NAME="$2"
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
--timeout)
|
|
62
|
+
TIMEOUT="$2"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
--position)
|
|
66
|
+
POSITION="$2"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
--author)
|
|
70
|
+
AUTHOR="$2"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
*)
|
|
74
|
+
echo "Unknown argument: $1" >&2
|
|
75
|
+
exit 1
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
# Validate required arguments
|
|
81
|
+
if [[ -z "$HOOK_TYPE" || -z "$MATCHER" || -z "$HOOK_COMMAND" || -z "$HOOK_NAME" ]]; then
|
|
82
|
+
echo "Usage: $0 --hook-type TYPE --matcher MATCHER --command CMD --name NAME [--timeout SECS] [--position append|prepend] [--author AUTHOR]" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
PROJECT_ROOT="$PWD"
|
|
87
|
+
SETTINGS_FILE="$PROJECT_ROOT/.claude/settings.local.json"
|
|
88
|
+
|
|
89
|
+
# Ensure .claude directory exists
|
|
90
|
+
mkdir -p "$(dirname "$SETTINGS_FILE")"
|
|
91
|
+
|
|
92
|
+
# Initialize settings file if it doesn't exist
|
|
93
|
+
if [[ ! -f "$SETTINGS_FILE" ]]; then
|
|
94
|
+
echo "{}" > "$SETTINGS_FILE"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Build the hook configuration JSON
|
|
98
|
+
HOOK_CONFIG=$(jq -n \
|
|
99
|
+
--arg hookType "$HOOK_TYPE" \
|
|
100
|
+
--arg matcher "$MATCHER" \
|
|
101
|
+
--arg command "$HOOK_COMMAND" \
|
|
102
|
+
--argjson timeout "$TIMEOUT" \
|
|
103
|
+
--arg author "$AUTHOR" \
|
|
104
|
+
'{
|
|
105
|
+
hooks: {
|
|
106
|
+
($hookType): [
|
|
107
|
+
{
|
|
108
|
+
matcher: $matcher,
|
|
109
|
+
hooks: [
|
|
110
|
+
{
|
|
111
|
+
type: "command",
|
|
112
|
+
command: $command,
|
|
113
|
+
timeout: $timeout,
|
|
114
|
+
author: $author
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}'
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Generate jq script based on position (append vs prepend)
|
|
124
|
+
if [[ "$POSITION" == "prepend" ]]; then
|
|
125
|
+
# Prepend: ensure hook is first in the array
|
|
126
|
+
JQ_SCRIPT=$(cat <<'JQEOF'
|
|
127
|
+
def hookType: $hookType;
|
|
128
|
+
def matcher: $matcher;
|
|
129
|
+
def targetCmd: $hook.hooks[hookType][0].hooks[0].command;
|
|
130
|
+
|
|
131
|
+
# Check if hook is already first in the matcher
|
|
132
|
+
def hookIsFirst:
|
|
133
|
+
(.hooks[hookType] // [])
|
|
134
|
+
| map(select(.matcher == matcher) | .hooks // [])
|
|
135
|
+
| flatten
|
|
136
|
+
| first
|
|
137
|
+
| .command == targetCmd;
|
|
138
|
+
|
|
139
|
+
# If hook is already first, return unchanged
|
|
140
|
+
if hookIsFirst then
|
|
141
|
+
.
|
|
142
|
+
else
|
|
143
|
+
# Ensure .hooks exists
|
|
144
|
+
.hooks //= {} |
|
|
145
|
+
|
|
146
|
+
# Ensure .hooks[hookType] exists
|
|
147
|
+
.hooks[hookType] //= [] |
|
|
148
|
+
|
|
149
|
+
# Check if our matcher already exists
|
|
150
|
+
if (.hooks[hookType] | map(.matcher) | index(matcher)) then
|
|
151
|
+
# Matcher exists - remove our hook if present, then prepend it
|
|
152
|
+
.hooks[hookType] |= map(
|
|
153
|
+
if .matcher == matcher then
|
|
154
|
+
.hooks = $hook.hooks[hookType][0].hooks + (.hooks | map(select(.command != targetCmd)))
|
|
155
|
+
else
|
|
156
|
+
.
|
|
157
|
+
end
|
|
158
|
+
)
|
|
159
|
+
else
|
|
160
|
+
# Matcher does not exist, add the entire entry
|
|
161
|
+
.hooks[hookType] += $hook.hooks[hookType]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
JQEOF
|
|
165
|
+
)
|
|
166
|
+
else
|
|
167
|
+
# Append: add hook to end of array (default)
|
|
168
|
+
JQ_SCRIPT=$(cat <<'JQEOF'
|
|
169
|
+
def hookType: $hookType;
|
|
170
|
+
def matcher: $matcher;
|
|
171
|
+
def targetCmd: $hook.hooks[hookType][0].hooks[0].command;
|
|
172
|
+
|
|
173
|
+
# Check if hook already exists anywhere
|
|
174
|
+
def hookExists:
|
|
175
|
+
(.hooks[hookType] // [])
|
|
176
|
+
| map(select(.matcher == matcher) | .hooks // [])
|
|
177
|
+
| flatten
|
|
178
|
+
| map(.command)
|
|
179
|
+
| any(. == targetCmd);
|
|
180
|
+
|
|
181
|
+
# If hook already exists, return unchanged
|
|
182
|
+
if hookExists then
|
|
183
|
+
.
|
|
184
|
+
else
|
|
185
|
+
# Ensure .hooks exists
|
|
186
|
+
.hooks //= {} |
|
|
187
|
+
|
|
188
|
+
# Ensure .hooks[hookType] exists
|
|
189
|
+
.hooks[hookType] //= [] |
|
|
190
|
+
|
|
191
|
+
# Check if our matcher already exists
|
|
192
|
+
if (.hooks[hookType] | map(.matcher) | index(matcher)) then
|
|
193
|
+
# Matcher exists, add our hook to its hooks array
|
|
194
|
+
.hooks[hookType] |= map(
|
|
195
|
+
if .matcher == matcher then
|
|
196
|
+
.hooks += $hook.hooks[hookType][0].hooks
|
|
197
|
+
else
|
|
198
|
+
.
|
|
199
|
+
end
|
|
200
|
+
)
|
|
201
|
+
else
|
|
202
|
+
# Matcher does not exist, add the entire entry
|
|
203
|
+
.hooks[hookType] += $hook.hooks[hookType]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
JQEOF
|
|
207
|
+
)
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Run jq with the appropriate script
|
|
211
|
+
jq --argjson hook "$HOOK_CONFIG" \
|
|
212
|
+
--arg hookType "$HOOK_TYPE" \
|
|
213
|
+
--arg matcher "$MATCHER" \
|
|
214
|
+
"$JQ_SCRIPT" "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp"
|
|
215
|
+
|
|
216
|
+
# Check if any changes were made
|
|
217
|
+
if diff -q "$SETTINGS_FILE" "$SETTINGS_FILE.tmp" >/dev/null 2>&1; then
|
|
218
|
+
rm "$SETTINGS_FILE.tmp"
|
|
219
|
+
echo "👌 $HOOK_NAME hook already bound"
|
|
220
|
+
exit 0
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Atomic replace
|
|
224
|
+
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
|
225
|
+
|
|
226
|
+
echo "🔗 $HOOK_NAME hook bound successfully!"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = bind behaver hooks to Claude settings
|
|
4
|
+
#
|
|
5
|
+
# .why = the behaver role uses a SessionStart hook to inject
|
|
6
|
+
# bound behavior context into agent sessions on start
|
|
7
|
+
# and compaction events.
|
|
8
|
+
#
|
|
9
|
+
# .how = calls findsert for the boot-behavior hook
|
|
10
|
+
#
|
|
11
|
+
# guarantee:
|
|
12
|
+
# ✔ idempotent: safe to rerun
|
|
13
|
+
# ✔ fail-fast on errors
|
|
14
|
+
######################################################################
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# fail loud: print what failed
|
|
19
|
+
trap 'echo "❌ init.claude.hooks.sh failed at line $LINENO" >&2' ERR
|
|
20
|
+
|
|
21
|
+
INITS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
FINDSERT="$INITS_DIR/init.claude.hooks.findsert.sh"
|
|
23
|
+
|
|
24
|
+
# path to hook scripts (relative to this script)
|
|
25
|
+
HOOKS_DIR="$INITS_DIR/claude.hooks"
|
|
26
|
+
|
|
27
|
+
# SessionStart hook: boot behavior context
|
|
28
|
+
# note: SessionStart hooks run on both session start AND compaction
|
|
29
|
+
"$FINDSERT" \
|
|
30
|
+
--hook-type SessionStart \
|
|
31
|
+
--matcher "*" \
|
|
32
|
+
--command "$HOOKS_DIR/sessionstart.boot-behavior.sh" \
|
|
33
|
+
--name "sessionstart.boot-behavior" \
|
|
34
|
+
--timeout 10
|