specweave 1.0.550 → 1.0.552

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 (166) hide show
  1. package/CLAUDE.md +1 -1
  2. package/bin/specweave.js +23 -1
  3. package/dist/src/cli/commands/hook.d.ts +15 -0
  4. package/dist/src/cli/commands/hook.d.ts.map +1 -0
  5. package/dist/src/cli/commands/hook.js +61 -0
  6. package/dist/src/cli/commands/hook.js.map +1 -0
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +5 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
  11. package/dist/src/cli/commands/refresh-plugins.js +11 -1
  12. package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
  13. package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
  14. package/dist/src/cli/commands/sync-setup.js +7 -3
  15. package/dist/src/cli/commands/sync-setup.js.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
  17. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
  19. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
  20. package/dist/src/config/types.d.ts +2 -2
  21. package/dist/src/core/config/types.d.ts +18 -2
  22. package/dist/src/core/config/types.d.ts.map +1 -1
  23. package/dist/src/core/config/types.js.map +1 -1
  24. package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
  25. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
  26. package/dist/src/core/hooks/handlers/hook-router.js +75 -0
  27. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
  28. package/dist/src/core/hooks/handlers/index.d.ts +10 -0
  29. package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
  30. package/dist/src/core/hooks/handlers/index.js +9 -0
  31. package/dist/src/core/hooks/handlers/index.js.map +1 -0
  32. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
  33. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
  34. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
  35. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
  36. package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
  37. package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
  38. package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
  39. package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
  40. package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
  41. package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
  42. package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
  43. package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
  44. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
  45. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
  46. package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
  47. package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
  48. package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
  49. package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
  50. package/dist/src/core/hooks/handlers/session-start.js +111 -0
  51. package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
  52. package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
  53. package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
  54. package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
  55. package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
  56. package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
  57. package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
  58. package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
  59. package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
  60. package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
  61. package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
  62. package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
  63. package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
  64. package/dist/src/core/hooks/handlers/types.d.ts +63 -0
  65. package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
  66. package/dist/src/core/hooks/handlers/types.js +27 -0
  67. package/dist/src/core/hooks/handlers/types.js.map +1 -0
  68. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
  69. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
  70. package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
  71. package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
  72. package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
  73. package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
  74. package/dist/src/core/hooks/handlers/utils.js +64 -0
  75. package/dist/src/core/hooks/handlers/utils.js.map +1 -0
  76. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  77. package/dist/src/core/increment/completion-validator.js +32 -0
  78. package/dist/src/core/increment/completion-validator.js.map +1 -1
  79. package/dist/src/init/research/types.d.ts +1 -1
  80. package/dist/src/sync/sync-target-resolver.js.map +1 -1
  81. package/dist/src/utils/lock-manager.d.ts.map +1 -1
  82. package/dist/src/utils/lock-manager.js +5 -0
  83. package/dist/src/utils/lock-manager.js.map +1 -1
  84. package/dist/src/utils/plugin-copier.d.ts +10 -0
  85. package/dist/src/utils/plugin-copier.d.ts.map +1 -1
  86. package/dist/src/utils/plugin-copier.js +63 -35
  87. package/dist/src/utils/plugin-copier.js.map +1 -1
  88. package/package.json +1 -1
  89. package/plugins/specweave/agents/sw-closer.md +3 -2
  90. package/plugins/specweave/hooks/hooks.json +10 -10
  91. package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
  92. package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
  93. package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
  94. package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
  95. package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
  96. package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
  97. package/plugins/specweave/skills/done/SKILL.md +56 -21
  98. package/plugins/specweave/skills/grill/SKILL.md +1 -1
  99. package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
  100. package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
  101. package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
  102. package/src/templates/CLAUDE.md.template +7 -4
  103. package/plugins/specweave/hooks/README.md +0 -493
  104. package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
  105. package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
  106. package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
  107. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
  108. package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
  109. package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
  110. package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
  111. package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
  112. package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
  113. package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
  114. package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
  115. package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
  116. package/plugins/specweave/hooks/log-decision.sh +0 -168
  117. package/plugins/specweave/hooks/pre-compact.sh +0 -64
  118. package/plugins/specweave/hooks/startup-health-check.sh +0 -64
  119. package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
  120. package/plugins/specweave/hooks/stop-reflect.sh +0 -336
  121. package/plugins/specweave/hooks/stop-sync.sh +0 -283
  122. package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
  123. package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
  124. package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
  125. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
  126. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
  127. package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
  128. package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
  129. package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
  130. package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
  131. package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
  132. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
  133. package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
  134. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
  135. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
  136. package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
  137. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
  138. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
  139. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
  140. package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
  141. package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
  142. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
  143. package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
  144. package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
  145. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
  146. package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
  147. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
  148. package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
  149. package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
  150. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
  151. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
  152. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
  153. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
  154. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
  155. package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
  156. package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
  157. package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
  158. package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
  159. package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
  160. package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
  161. package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
  162. package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
  163. package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
  164. package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
  165. package/plugins/specweave/hooks/v2/session-end.sh +0 -139
  166. package/plugins/specweave/hooks/validate-skill-activations.sh +0 -227
@@ -1,276 +0,0 @@
1
- #!/bin/bash
2
- # stop-auto-v5.sh - Auto Mode Stop Hook (v5.0 - Gate Only)
3
- # ADR-0225: "Increments ARE the state. Hooks ARE the sync."
4
- # Hook = simple gate. Quality gates (tests, build, coverage) belong in /sw:done.
5
- # Sourceable: set __STOP_AUTO_V5_SOURCED=1 to load functions only.
6
- set +e
7
-
8
- # ── Timing ───────────────────────────────────────────────────────────────
9
- if [[ "$OSTYPE" == "darwin"* ]]; then _START_MS=$(($(date +%s) * 1000))
10
- else _START_MS=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0"))); fi
11
- _get_duration_ms() {
12
- local n; if [[ "$OSTYPE" == "darwin"* ]]; then n=$(($(date +%s) * 1000))
13
- else n=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0"))); fi
14
- echo $((n - _START_MS))
15
- }
16
-
17
- # ── Stdin & paths ────────────────────────────────────────────────────────
18
- if [ -z "${__STOP_AUTO_V5_SOURCED:-}" ]; then
19
- STDIN_DATA=$(cat)
20
- CLAUDE_SESSION_ID=$(echo "$STDIN_DATA" | jq -r '.session_id // ""' 2>/dev/null || echo "")
21
- else
22
- STDIN_DATA=""
23
- CLAUDE_SESSION_ID=""
24
- fi
25
-
26
- # Project root detection (walk up to find .specweave/ — prevents pollution of subdirectories)
27
- if [[ -n "${PROJECT_ROOT:-}" ]] && [[ -f "$PROJECT_ROOT/.specweave/config.json" ]]; then
28
- : # env var already set and valid
29
- else
30
- PROJECT_ROOT="$PWD"
31
- while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; do
32
- PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
33
- done
34
- fi
35
-
36
- # Not a SpecWeave project — approve and exit (MUST be before any mkdir)
37
- if [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; then
38
- echo '{"decision":"approve"}'
39
- exit 0
40
- fi
41
-
42
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
- [ -f "$SCRIPT_DIR/log-decision.sh" ] && source "$SCRIPT_DIR/log-decision.sh"
44
-
45
- SW="$PROJECT_ROOT/.specweave"
46
- STATE_DIR="$SW/state"
47
- LOGS_DIR="$SW/logs"
48
- LOG_FILE="$LOGS_DIR/stop-auto.log"
49
-
50
- log() {
51
- mkdir -p "$LOGS_DIR" 2>/dev/null
52
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $1" >> "$LOG_FILE" 2>/dev/null
53
- [ "${SPECWEAVE_DEBUG_HOOKS:-0}" = "1" ] && echo -e "\033[36m[stop-auto-v5]\033[0m $1" >&2
54
- }
55
-
56
- # ── Helpers ──────────────────────────────────────────────────────────────
57
- count_pending_tasks() {
58
- local f="$1"; [ ! -f "$f" ] && echo "0" && return
59
- local c; c=$(grep -c '\[ \]' "$f" 2>/dev/null) || true; echo "${c:-0}"
60
- }
61
- count_completed_tasks() {
62
- local f="$1"; [ ! -f "$f" ] && echo "0" && return
63
- local c; c=$(grep -c '\[x\]' "$f" 2>/dev/null) || true; echo "${c:-0}"
64
- }
65
- count_open_acs() {
66
- local f="$1"; [ ! -f "$f" ] && echo "0" && return
67
- local c; c=$(grep -c '\[ \]' "$f" 2>/dev/null) || true; echo "${c:-0}"
68
- }
69
- get_next_task_title() {
70
- local f="$1"; [ ! -f "$f" ] && echo "" && return
71
- local lnum; lnum=$(grep -n '\[ \]' "$f" 2>/dev/null | head -1 | cut -d: -f1)
72
- [ -z "$lnum" ] && echo "" && return
73
- # Search backwards from [ ] line to find ### T-NNN: Title heading
74
- local title; title=$(head -n "$lnum" "$f" | grep '### T-' | tail -1 | sed 's/^### T-[0-9]*: //')
75
- echo "${title}" | head -c 80
76
- }
77
- silent_approve() {
78
- local reason="$1" rc="${2:-session_inactive}" ctx="${3:-"{}"}"
79
- log "APPROVE: $reason"
80
- type log_decision &>/dev/null && log_decision "stop-auto" "approve" "$rc" "$reason" "$ctx" "$(_get_duration_ms)"
81
- echo '{"decision":"approve"}'; exit 0
82
- }
83
- loud_approve() {
84
- local reason="$1" rc="${2:-session_complete}" ctx="${3:-"{}"}" msg="$4"
85
- log "APPROVE (loud): $reason"
86
- type log_decision &>/dev/null && log_decision "stop-auto" "approve" "$rc" "$reason" "$ctx" "$(_get_duration_ms)"
87
- jq -n --arg d "approve" --arg r "$reason" --arg m "$msg" '{decision:$d,reason:$r,systemMessage:$m}'; exit 0
88
- }
89
- block() {
90
- local reason="$1" rc="${2:-work_remaining}" ctx="${3:-"{}"}" msg="$4"
91
- log "BLOCK: $reason"
92
- type log_decision &>/dev/null && log_decision "stop-auto" "block" "$rc" "$reason" "$ctx" "$(_get_duration_ms)"
93
- jq -n --arg d "block" --arg r "$reason" --arg rc "$rc" --arg m "$msg" '{decision:$d,reason:$r,reasonCode:$rc,systemMessage:$m}'; exit 0
94
- }
95
-
96
- # ── Source-only guard ────────────────────────────────────────────────────
97
- [ "${__STOP_AUTO_V5_SOURCED:-}" = "1" ] && return 0 2>/dev/null || true
98
- [ "${__STOP_AUTO_V5_SOURCED:-}" = "1" ] && exit 0
99
-
100
- # ═════════════════════════════════════════════════════════════════════════
101
- # MAIN
102
- # ═════════════════════════════════════════════════════════════════════════
103
- INC_DIR="$SW/increments"
104
- CFG="$SW/config.json"
105
- SESSION="$STATE_DIR/auto-mode.json"
106
-
107
- # Per-session auto-mode.json takes priority over global
108
- if [ -n "$CLAUDE_SESSION_ID" ] && [ -f "$STATE_DIR/sessions/$CLAUDE_SESSION_ID/auto-mode.json" ]; then
109
- SESSION="$STATE_DIR/sessions/$CLAUDE_SESSION_ID/auto-mode.json"
110
- log "Using per-session auto-mode: $CLAUDE_SESSION_ID"
111
- fi
112
-
113
- # Per-session turn counter and dedup files when session ID is available
114
- if [ -n "$CLAUDE_SESSION_ID" ] && [ -d "$STATE_DIR/sessions/$CLAUDE_SESSION_ID" ]; then
115
- TURN_FILE="$STATE_DIR/sessions/$CLAUDE_SESSION_ID/.stop-auto-turns"
116
- DEDUP_PREV="$STATE_DIR/sessions/$CLAUDE_SESSION_ID/.stop-auto-dedup-prev"
117
- else
118
- TURN_FILE="$STATE_DIR/.stop-auto-turns"
119
- DEDUP_PREV="$STATE_DIR/.stop-auto-dedup-prev"
120
- fi
121
-
122
- # 1. Quick exits
123
- [ ! -d "$SW" ] && silent_approve "Not a SpecWeave project" "not_specweave_project"
124
- [ ! -d "$INC_DIR" ] && silent_approve "No increments directory" "no_increments_dir"
125
-
126
- # 2. Config
127
- MAX_TURNS=20; MAX_AGE=7200; AUTO_ON="true"; DEDUP_WIN="${SPECWEAVE_STOP_HOOK_DEDUP:-30}"
128
- if [ -f "$CFG" ]; then
129
- AUTO_ON=$(jq -r '.auto.enabled // true' "$CFG" 2>/dev/null || echo "true")
130
- MAX_TURNS=$(jq -r '.auto.maxTurns // 20' "$CFG" 2>/dev/null || echo "20")
131
- MAX_AGE=$(jq -r '.auto.maxSessionAge // 7200' "$CFG" 2>/dev/null || echo "7200")
132
- fi
133
- [ "$AUTO_ON" != "true" ] && silent_approve "Auto mode disabled" "auto_disabled"
134
-
135
- # 3. Session marker
136
- [ ! -f "$SESSION" ] && silent_approve "No auto session" "session_inactive" '{"sessionActive":false}'
137
- ACTIVE=$(jq -r '.active // false' "$SESSION" 2>/dev/null || echo "false")
138
- [ "$ACTIVE" != "true" ] && silent_approve "Session not active" "session_inactive" '{"sessionActive":false}'
139
-
140
- # 4. Staleness
141
- MTIME=$(stat -f%m "$SESSION" 2>/dev/null || stat -c%Y "$SESSION" 2>/dev/null || echo "0")
142
- NOW=$(date +%s); AGE=$((NOW - MTIME))
143
- if [ "$AGE" -gt "$MAX_AGE" ]; then
144
- rm -f "$SESSION" "$DEDUP_PREV" "$TURN_FILE" 2>/dev/null
145
- silent_approve "Stale session (${AGE}s)" "session_stale" \
146
- "$(jq -n --argjson a "$AGE" --argjson m "$MAX_AGE" '{sessionAge:$a,maxSessionAge:$m}')"
147
- fi
148
- touch "$SESSION" 2>/dev/null
149
-
150
- # 5. Turn counter (never resets during session)
151
- TURN=1
152
- if [ -f "$TURN_FILE" ]; then
153
- S=$(cat "$TURN_FILE" 2>/dev/null || echo "0")
154
- [[ "$S" =~ ^[0-9]+$ ]] && TURN=$((S + 1))
155
- fi
156
- mkdir -p "$STATE_DIR" 2>/dev/null
157
- echo "$TURN" > "$TURN_FILE.tmp" && mv "$TURN_FILE.tmp" "$TURN_FILE" 2>/dev/null
158
-
159
- if [ "$TURN" -ge "$MAX_TURNS" ]; then
160
- rm -f "$SESSION" "$DEDUP_PREV" "$TURN_FILE" 2>/dev/null
161
- loud_approve "Turn limit ($TURN/$MAX_TURNS)" "turn_limit" \
162
- "$(jq -n --argjson c "$TURN" --argjson m "$MAX_TURNS" '{turn:{current:$c,max:$m}}')" \
163
- "Turn limit reached ($TURN/$MAX_TURNS). Run /sw:auto to start a new session."
164
- fi
165
-
166
- # 6. Dedup (write-first prevents race)
167
- if [ -f "$DEDUP_PREV" ]; then
168
- LAST=$(cat "$DEDUP_PREV" 2>/dev/null || echo "0")
169
- EL=$((NOW - LAST))
170
- if [ "$EL" -lt "$DEDUP_WIN" ]; then
171
- silent_approve "Deduplicated (${EL}s)" "deduplicated" \
172
- "$(jq -n --argjson e "$EL" --argjson w "$DEDUP_WIN" '{elapsed:$e,dedupWindow:$w}')"
173
- fi
174
- fi
175
- echo "$NOW" > "$DEDUP_PREV" 2>/dev/null
176
-
177
- # 7. Read userGoal and simple mode from session marker
178
- USER_GOAL=$(jq -r '.userGoal // ""' "$SESSION" 2>/dev/null || echo "")
179
- SIMPLE=$(jq -r '.simple // false' "$SESSION" 2>/dev/null || echo "false")
180
-
181
- # 8. Scan active increments (enriched: next task, progress fraction)
182
- TP=0; TAC=0; IC=0; ILIST=""
183
- _SCORE_SCRIPT="$SCRIPT_DIR/lib/score-increment.sh"
184
- for meta in $(find "$INC_DIR" -maxdepth 2 -name "metadata.json" 2>/dev/null); do
185
- st=$(jq -r '.status // "unknown"' "$meta" 2>/dev/null || echo "unknown")
186
- [ "$st" != "active" ] && [ "$st" != "in-progress" ] && continue
187
- d=$(dirname "$meta"); id=$(basename "$d")
188
- p=$(count_pending_tasks "$d/tasks.md"); a=$(count_open_acs "$d/spec.md")
189
- if [ "$p" -gt 0 ] || [ "$a" -gt 0 ]; then
190
- TP=$((TP + p)); TAC=$((TAC + a)); IC=$((IC + 1))
191
- # Extract next pending task title using helper
192
- _next_task=$(get_next_task_title "$d/tasks.md")
193
- # Count completed tasks for progress fraction
194
- _done=$(grep -c '\[x\]' "$d/tasks.md" 2>/dev/null) || _done=0
195
- _total=$((_done + p))
196
- # Score against userGoal if set and scoring script available
197
- _score=""
198
- if [ -n "$USER_GOAL" ] && [ -f "$_SCORE_SCRIPT" ]; then
199
- _score=$(bash "$_SCORE_SCRIPT" "$d" "$USER_GOAL" 2>/dev/null || echo "0")
200
- fi
201
- # Extended ILIST format: id|pending|acs|next_task|done|total|score
202
- _entry="$id|$p|$a|$_next_task|$_done|$_total|${_score:-0}"
203
- [ -z "$ILIST" ] && ILIST="$_entry" || ILIST="$ILIST,$_entry"
204
- fi
205
- done
206
-
207
- # 9. All work items complete → check if closure needed
208
- if [ "$IC" -eq 0 ]; then
209
- # Count active/in-progress increments (regardless of task completion)
210
- _ACTIVE_COUNT=0; _ACTIVE_IDS=""
211
- for _meta in $(find "$INC_DIR" -maxdepth 2 -name "metadata.json" 2>/dev/null); do
212
- _st=$(jq -r '.status // "unknown"' "$_meta" 2>/dev/null || echo "unknown")
213
- if [ "$_st" = "active" ] || [ "$_st" = "in-progress" ]; then
214
- _ACTIVE_COUNT=$((_ACTIVE_COUNT + 1))
215
- _d=$(dirname "$_meta"); _id=$(basename "$_d")
216
- [ -z "$_ACTIVE_IDS" ] && _ACTIVE_IDS="$_id" || _ACTIVE_IDS="$_ACTIVE_IDS, $_id"
217
- fi
218
- done
219
-
220
- if [ "$_ACTIVE_COUNT" -gt 0 ]; then
221
- # Active increments with all tasks done → block for closure (do NOT clean up state)
222
- _CLOSE_MSG="All tasks and ACs complete. Run \`/sw:done --auto <id>\` to close: ${_ACTIVE_IDS}"
223
- block "All work complete, needs closure" "all_complete_needs_closure" \
224
- "$(jq -n --argjson t "$TURN" --arg ids "$_ACTIVE_IDS" '{turn:$t,incrementIds:$ids}')" \
225
- "$_CLOSE_MSG"
226
- else
227
- # All increments already closed (completed/archived) → approve and clean up
228
- rm -f "$SESSION" "$DEDUP_PREV" "$TURN_FILE" 2>/dev/null
229
- loud_approve "All increments closed" "all_complete" \
230
- "$(jq -n --argjson t "$TURN" '{turn:$t}')" \
231
- "All increments are closed. Auto session complete."
232
- fi
233
- fi
234
-
235
- # 10. Work remains → block with context message (simple or enriched)
236
- # Sort entries by score descending (highest-relevance first) when userGoal is set
237
- SORTED_ILIST="$ILIST"
238
- if [ -n "$USER_GOAL" ] && [ "$IC" -gt 1 ]; then
239
- # Sort by score field (7th field) descending
240
- SORTED_ILIST=$(echo "$ILIST" | tr ',' '\n' | sort -t'|' -k7 -nr | tr '\n' ',')
241
- SORTED_ILIST="${SORTED_ILIST%,}" # trim trailing comma
242
- fi
243
-
244
- # Extract best increment ID (first entry after sorting)
245
- _BEST_ID=""
246
- IFS=',' read -ra ENTRIES <<< "$SORTED_ILIST"
247
- IFS='|' read -r _BEST_ID _rest <<< "${ENTRIES[0]}"
248
-
249
- if [ "$SIMPLE" = "true" ]; then
250
- # Simple mode: minimal message to reduce context tokens per turn
251
- BMSG="Continue: /sw:do ${_BEST_ID} ($TP tasks left, turn $TURN/$MAX_TURNS)"
252
- BMSG=$(echo -e "$BMSG")
253
- else
254
- # Full mode: enriched context with per-increment progress and next task
255
- DETAILS=""
256
- for entry in "${ENTRIES[@]}"; do
257
- IFS='|' read -r eid ep ea enext edone etotal escore <<< "$entry"
258
- _progress="${edone:-0}/${etotal:-0} tasks"
259
- _next_info=""
260
- [ -n "$enext" ] && _next_info=" | Next: $enext"
261
- DETAILS="${DETAILS}\n ▸ ${eid}: ${_progress}${_next_info}"
262
- done
263
-
264
- BMSG=""
265
- [ -n "$USER_GOAL" ] && BMSG="Goal: ${USER_GOAL}\n"
266
- BMSG="${BMSG}Auto Mode: ${IC} increment(s) need work${DETAILS}"
267
- BMSG="${BMSG}\nTurn $TURN/$MAX_TURNS | Continue: /sw:do ${_BEST_ID}"
268
- BMSG=$(echo -e "$BMSG")
269
- fi
270
-
271
- block "Work remaining: $TP tasks, $TAC ACs" "work_remaining" \
272
- "$(jq -n --argjson p "$TP" --argjson a "$TAC" --argjson i "$IC" \
273
- --argjson t "$TURN" --argjson mt "$MAX_TURNS" --arg il "$ILIST" \
274
- --arg goal "$USER_GOAL" --arg best "$_BEST_ID" \
275
- '{pendingTasks:$p,openAcs:$a,incompleteIncrements:$i,increments:$il,turn:{current:$t,max:$mt},userGoal:$goal,recommended:$best}')" \
276
- "$BMSG"
@@ -1,336 +0,0 @@
1
- #!/bin/bash
2
- # stop-reflect.sh - Session Reflection Hook (v3.1 - Structured Decision Logging)
3
- #
4
- # ARCHITECTURE (v3.1):
5
- # - Uses specweave CLI (TypeScript) for reflection
6
- # - Learnings go to CLAUDE.md under "## Skill Memories" section
7
- # - User can disable via config: { "reflect": { "enabled": false } }
8
- # - Structured decision logging to .specweave/logs/decisions.jsonl
9
- #
10
- # This hook ALWAYS approves (never blocks sessions) but may trigger
11
- # background learning extraction if enabled.
12
-
13
- set +e # Never fail - this is a non-critical hook
14
-
15
- # Capture start time for duration tracking (macOS doesn't support %N, fallback to seconds only)
16
- if [[ "$OSTYPE" == "darwin"* ]]; then
17
- _START_TIME_MS=$(($(date +%s) * 1000))
18
- else
19
- _START_TIME_MS=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
20
- fi
21
-
22
- # Read input from stdin (required by Claude Code hooks)
23
- INPUT=$(cat)
24
-
25
- # Parse input fields
26
- TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""' 2>/dev/null)
27
-
28
- # Project root detection (walk up to find .specweave/ — prevents pollution of subdirectories)
29
- if [[ -n "${PROJECT_ROOT:-}" ]] && [[ -f "$PROJECT_ROOT/.specweave/config.json" ]]; then
30
- : # env var already set and valid
31
- else
32
- PROJECT_ROOT="$PWD"
33
- while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; do
34
- PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
35
- done
36
- fi
37
-
38
- # Not a SpecWeave project — approve and exit (MUST be before any mkdir)
39
- if [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; then
40
- echo '{"decision":"approve"}'
41
- exit 0
42
- fi
43
-
44
- CONFIG_FILE="$PROJECT_ROOT/.specweave/config.json"
45
- LOGS_DIR="$PROJECT_ROOT/.specweave/logs/reflect"
46
- STATE_DIR="$PROJECT_ROOT/.specweave/state"
47
-
48
- # Source the shared decision logging utility
49
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
50
- if [ -f "$SCRIPT_DIR/log-decision.sh" ]; then
51
- source "$SCRIPT_DIR/log-decision.sh"
52
- fi
53
-
54
- # Get duration in milliseconds
55
- _get_duration_ms() {
56
- local end_time_ms
57
- if [[ "$OSTYPE" == "darwin"* ]]; then
58
- end_time_ms=$(($(date +%s) * 1000))
59
- else
60
- end_time_ms=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
61
- fi
62
- echo $((end_time_ms - _START_TIME_MS))
63
- }
64
-
65
- # Ensure logs directory exists
66
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
67
-
68
- # Logging function
69
- log_reflect() {
70
- local level="$1"
71
- shift
72
- local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date +%Y-%m-%dT%H:%M:%SZ)
73
- local log_file="$LOGS_DIR/reflect.log"
74
-
75
- # Rotate if too large (keep last 50 lines)
76
- if [ -f "$log_file" ]; then
77
- local lines=$(wc -l < "$log_file" 2>/dev/null || echo "0")
78
- if [ "$lines" -gt 100 ]; then
79
- tail -n 50 "$log_file" > "$log_file.tmp" 2>/dev/null && mv "$log_file.tmp" "$log_file" 2>/dev/null || true
80
- fi
81
- fi
82
-
83
- echo "[$timestamp] [$level] $*" >> "$log_file" 2>/dev/null || true
84
- }
85
-
86
- # Log decision with context for structured logging
87
- log_reflect_decision() {
88
- local reason_code="$1"
89
- local reason="$2"
90
- local transcript_lines="${3:-0}"
91
- local reflection_enabled="${4:-true}"
92
- local learnings_extracted="${5:-0}"
93
- local learning_categories_json="${6:-[]}"
94
-
95
- if type log_decision &>/dev/null; then
96
- local context_json
97
- context_json=$(jq -n \
98
- --argjson transcriptLines "$transcript_lines" \
99
- --argjson reflectionEnabled "$reflection_enabled" \
100
- --argjson learningsExtracted "$learnings_extracted" \
101
- --argjson learningCategories "$learning_categories_json" \
102
- --arg exitReason "$reason_code" \
103
- --arg triggerReason "session_end" \
104
- '{
105
- transcriptLines: $transcriptLines,
106
- reflectionEnabled: $reflectionEnabled,
107
- learningsExtracted: $learningsExtracted,
108
- learningCategories: $learningCategories,
109
- exitReason: $exitReason,
110
- triggerReason: $triggerReason
111
- }')
112
-
113
- log_decision "stop-reflect" "approve" "$reason_code" "$reason" "$context_json" "$(_get_duration_ms)"
114
- fi
115
- }
116
-
117
- # Check if reflection is enabled
118
- is_reflect_enabled() {
119
- if [ ! -f "$CONFIG_FILE" ]; then
120
- return 0 # Default to enabled if no config
121
- fi
122
-
123
- # Note: can't use // true because jq treats false as falsy
124
- local enabled=$(jq -r 'if .reflect.enabled == false then "false" else "true" end' "$CONFIG_FILE" 2>/dev/null)
125
- [ "$enabled" = "true" ]
126
- }
127
-
128
- # Cleanup ephemeral state files
129
- cleanup_session_state() {
130
- rm -f "$STATE_DIR/.current-agent-type" 2>/dev/null || true
131
- }
132
-
133
- # Cross-platform timeout function
134
- # macOS doesn't have GNU timeout - needs gtimeout (coreutils) or perl fallback
135
- run_with_timeout() {
136
- local timeout_sec="$1"
137
- shift
138
-
139
- # Try gtimeout first (macOS with coreutils: brew install coreutils)
140
- if command -v gtimeout >/dev/null 2>&1; then
141
- gtimeout "$timeout_sec" "$@"
142
- return $?
143
- fi
144
-
145
- # Try GNU timeout (Linux, or macOS with coreutils in PATH)
146
- if command -v timeout >/dev/null 2>&1; then
147
- timeout "$timeout_sec" "$@"
148
- return $?
149
- fi
150
-
151
- # Perl fallback (available on all macOS/Linux by default)
152
- # alarm() sends SIGALRM after N seconds, which terminates the exec'd process
153
- perl -e 'alarm shift; exec @ARGV' "$timeout_sec" "$@"
154
- return $?
155
- }
156
-
157
- # Run reflection using TypeScript implementation
158
- run_reflection() {
159
- local transcript="$1"
160
-
161
- # Check if reflect is enabled
162
- if ! is_reflect_enabled; then
163
- log_reflect "info" "Reflection disabled in config"
164
- return 0
165
- fi
166
-
167
- # Ensure transcript exists
168
- if [ -z "$transcript" ] || [ ! -f "$transcript" ]; then
169
- log_reflect "info" "No transcript available"
170
- return 0
171
- fi
172
-
173
- # Check transcript has meaningful content
174
- local lines=$(wc -l < "$transcript" 2>/dev/null | tr -d ' ')
175
- if [ "$lines" -lt 10 ]; then
176
- log_reflect "info" "Transcript too short ($lines lines)"
177
- return 0
178
- fi
179
-
180
- log_reflect "info" "Starting reflection ($lines lines)"
181
-
182
- # Use specweave CLI (TypeScript implementation)
183
- if command -v specweave >/dev/null 2>&1; then
184
- (
185
- cd "$PROJECT_ROOT"
186
- run_with_timeout 60 specweave reflect-stop "$transcript" --silent >> "$LOGS_DIR/auto-reflect.log" 2>&1
187
- local result=$?
188
-
189
- if [ $result -eq 0 ]; then
190
- log_reflect "info" "Reflection completed successfully"
191
- elif [ $result -eq 124 ]; then
192
- log_reflect "warn" "Reflection timed out"
193
- else
194
- log_reflect "warn" "Reflection completed with exit code $result"
195
- fi
196
- ) &
197
- disown 2>/dev/null
198
- log_reflect "info" "Reflection started in background (detached)"
199
- else
200
- log_reflect "warn" "specweave CLI not found"
201
- fi
202
-
203
- return 0
204
- }
205
-
206
- # Run reflection with structured logging (synchronous for test purposes, captures learnings)
207
- run_reflection_with_logging() {
208
- local transcript="$1"
209
- local transcript_lines="$2"
210
- local learnings_extracted=0
211
- local learning_categories="[]"
212
- local exit_reason="nothing_to_learn"
213
- local reason_msg="No learnings extracted"
214
-
215
- log_reflect "info" "Starting reflection ($transcript_lines lines)"
216
-
217
- # Use specweave CLI (TypeScript implementation)
218
- if command -v specweave >/dev/null 2>&1; then
219
- # Create a temp file to capture output
220
- local temp_output
221
- temp_output=$(mktemp)
222
-
223
- (
224
- cd "$PROJECT_ROOT"
225
- run_with_timeout 60 specweave reflect-stop "$transcript" --silent 2>&1 | tee "$temp_output" >> "$LOGS_DIR/auto-reflect.log"
226
- local result=$?
227
-
228
- # Parse learnings from output
229
- if [ -f "$temp_output" ]; then
230
- # Try to extract learnings count from output
231
- local count_line
232
- count_line=$(grep -i "learnings extracted:" "$temp_output" 2>/dev/null | head -1)
233
- if [ -n "$count_line" ]; then
234
- learnings_extracted=$(echo "$count_line" | grep -oE '[0-9]+' | head -1)
235
- learnings_extracted=${learnings_extracted:-0}
236
- fi
237
-
238
- # Try to extract categories from output
239
- local cat_line
240
- cat_line=$(grep -i "categories:" "$temp_output" 2>/dev/null | head -1)
241
- if [ -n "$cat_line" ]; then
242
- # Convert "Categories: devops, logging" to JSON array
243
- local cats
244
- cats=$(echo "$cat_line" | sed 's/.*[Cc]ategories:[[:space:]]*//' | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' | jq -R . | jq -s .)
245
- learning_categories=${cats:-"[]"}
246
- fi
247
- fi
248
-
249
- # Determine exit reason based on result and learnings
250
- if [ $result -eq 124 ]; then
251
- exit_reason="timeout"
252
- reason_msg="Reflection timed out"
253
- log_reflect "warn" "Reflection timed out"
254
- elif [ $result -ne 0 ]; then
255
- exit_reason="error"
256
- reason_msg="Reflection error (exit $result)"
257
- log_reflect "warn" "Reflection completed with exit code $result"
258
- elif [ "$learnings_extracted" -gt 0 ]; then
259
- exit_reason="learnings_saved"
260
- reason_msg="Extracted $learnings_extracted learnings"
261
- log_reflect "info" "Reflection completed successfully"
262
- else
263
- exit_reason="nothing_to_learn"
264
- reason_msg="No learnings to extract"
265
- log_reflect "info" "Reflection completed - no learnings"
266
- fi
267
-
268
- # Log the decision with full context
269
- log_reflect_decision "$exit_reason" "$reason_msg" "$transcript_lines" true "$learnings_extracted" "$learning_categories"
270
-
271
- rm -f "$temp_output" 2>/dev/null
272
- ) &
273
- disown 2>/dev/null
274
- log_reflect "info" "Reflection started in background (detached)"
275
- else
276
- log_reflect "warn" "specweave CLI not found"
277
- log_reflect_decision "error" "specweave CLI not found" "$transcript_lines" true 0 "[]"
278
- fi
279
-
280
- return 0
281
- }
282
-
283
- # Main execution
284
- main() {
285
- # SILENT approve - prevents UI feedback loops
286
- local silent_approve='{"decision":"approve"}'
287
-
288
- # Always cleanup ephemeral session state
289
- cleanup_session_state
290
-
291
- # Check if reflection is enabled first
292
- local reflect_enabled=true
293
- if [ -f "$CONFIG_FILE" ]; then
294
- local enabled_val
295
- # Note: can't use // true because jq treats false as falsy
296
- # Use if-then-else to properly handle boolean false
297
- enabled_val=$(jq -r 'if .reflect.enabled == false then "false" else "true" end' "$CONFIG_FILE" 2>/dev/null)
298
- if [ "$enabled_val" = "false" ]; then
299
- reflect_enabled=false
300
- fi
301
- fi
302
-
303
- # Quick bail if no transcript
304
- if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
305
- log_reflect_decision "no_transcript" "No transcript available" 0 "$reflect_enabled" 0 "[]"
306
- echo "$silent_approve"
307
- exit 0
308
- fi
309
-
310
- # Get transcript line count
311
- local transcript_lines
312
- transcript_lines=$(wc -l < "$TRANSCRIPT_PATH" 2>/dev/null | tr -d ' ') || transcript_lines=0
313
-
314
- # Check if reflection is disabled
315
- if [ "$reflect_enabled" = "false" ]; then
316
- log_reflect_decision "disabled" "Reflection disabled in config" "$transcript_lines" false 0 "[]"
317
- echo "$silent_approve"
318
- exit 0
319
- fi
320
-
321
- # Check if transcript is too short
322
- if [ "$transcript_lines" -lt 10 ]; then
323
- log_reflect_decision "transcript_too_short" "Transcript too short ($transcript_lines lines)" "$transcript_lines" true 0 "[]"
324
- echo "$silent_approve"
325
- exit 0
326
- fi
327
-
328
- # Run reflection (async, non-blocking) and capture learnings
329
- run_reflection_with_logging "$TRANSCRIPT_PATH" "$transcript_lines"
330
-
331
- # Always approve
332
- echo "$silent_approve"
333
- exit 0
334
- }
335
-
336
- main