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.
- package/CLAUDE.md +1 -1
- package/bin/specweave.js +23 -1
- package/dist/src/cli/commands/hook.d.ts +15 -0
- package/dist/src/cli/commands/hook.d.ts.map +1 -0
- package/dist/src/cli/commands/hook.js +61 -0
- package/dist/src/cli/commands/hook.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +5 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +11 -1
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-setup.js +7 -3
- package/dist/src/cli/commands/sync-setup.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/config/types.d.ts +18 -2
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
- package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/hook-router.js +75 -0
- package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
- package/dist/src/core/hooks/handlers/index.d.ts +10 -0
- package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/index.js +9 -0
- package/dist/src/core/hooks/handlers/index.js.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
- package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
- package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
- package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
- package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
- package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
- package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
- package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/session-start.js +111 -0
- package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
- package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
- package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
- package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
- package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
- package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
- package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
- package/dist/src/core/hooks/handlers/types.d.ts +63 -0
- package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/types.js +27 -0
- package/dist/src/core/hooks/handlers/types.js.map +1 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
- package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
- package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/utils.js +64 -0
- package/dist/src/core/hooks/handlers/utils.js.map +1 -0
- package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
- package/dist/src/core/increment/completion-validator.js +32 -0
- package/dist/src/core/increment/completion-validator.js.map +1 -1
- package/dist/src/init/research/types.d.ts +1 -1
- package/dist/src/sync/sync-target-resolver.js.map +1 -1
- package/dist/src/utils/lock-manager.d.ts.map +1 -1
- package/dist/src/utils/lock-manager.js +5 -0
- package/dist/src/utils/lock-manager.js.map +1 -1
- package/dist/src/utils/plugin-copier.d.ts +10 -0
- package/dist/src/utils/plugin-copier.d.ts.map +1 -1
- package/dist/src/utils/plugin-copier.js +63 -35
- package/dist/src/utils/plugin-copier.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/sw-closer.md +3 -2
- package/plugins/specweave/hooks/hooks.json +10 -10
- package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
- package/plugins/specweave/skills/done/SKILL.md +56 -21
- package/plugins/specweave/skills/grill/SKILL.md +1 -1
- package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
- package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
- package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
- package/src/templates/CLAUDE.md.template +7 -4
- package/plugins/specweave/hooks/README.md +0 -493
- package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
- package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
- package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
- package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
- package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
- package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
- package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
- package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
- package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
- package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
- package/plugins/specweave/hooks/log-decision.sh +0 -168
- package/plugins/specweave/hooks/pre-compact.sh +0 -64
- package/plugins/specweave/hooks/startup-health-check.sh +0 -64
- package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
- package/plugins/specweave/hooks/stop-reflect.sh +0 -336
- package/plugins/specweave/hooks/stop-sync.sh +0 -283
- package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
- package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
- package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
- package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
- package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
- package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
- package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
- package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
- package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
- package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
- package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
- package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
- package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
- package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
- package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
- package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
- package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
- package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
- package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
- package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
- package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
- package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
- package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
- package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
- package/plugins/specweave/hooks/v2/session-end.sh +0 -139
- 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
|