specweave 0.28.36 → 0.28.45
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 +21 -0
- package/README.md +10 -9
- package/bin/specweave.js +2 -1
- package/dist/src/cli/commands/archive.d.ts.map +1 -1
- package/dist/src/cli/commands/archive.js +2 -1
- package/dist/src/cli/commands/archive.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +33 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/ado-area-selector.d.ts +49 -0
- package/dist/src/cli/helpers/ado-area-selector.d.ts.map +1 -0
- package/dist/src/cli/helpers/ado-area-selector.js +162 -0
- package/dist/src/cli/helpers/ado-area-selector.js.map +1 -0
- package/dist/src/cli/helpers/init/config-detection.d.ts +5 -1
- package/dist/src/cli/helpers/init/config-detection.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/config-detection.js +50 -17
- package/dist/src/cli/helpers/init/config-detection.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +3 -3
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/repository-setup.d.ts +2 -0
- package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/repository-setup.js +34 -2
- package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
- package/dist/src/cli/helpers/init/types.d.ts +3 -0
- package/dist/src/cli/helpers/init/types.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts +17 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +308 -44
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +103 -35
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts +26 -2
- package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
- package/dist/src/core/increment/discipline-checker.js +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +13 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +60 -3
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +75 -37
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -111
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +18 -383
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/file-utils.d.ts +30 -0
- package/dist/src/core/living-docs/sync-helpers/file-utils.d.ts.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/file-utils.js +107 -0
- package/dist/src/core/living-docs/sync-helpers/file-utils.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +19 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.js +146 -0
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +8 -0
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/index.js +11 -0
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +19 -0
- package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/parsers.js +94 -0
- package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +45 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/types/config.d.ts +1 -1
- package/dist/src/core/types/config.js +2 -2
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/importers/ado-importer.d.ts.map +1 -1
- package/dist/src/importers/ado-importer.js +3 -0
- package/dist/src/importers/ado-importer.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +10 -2
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +5 -0
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.js +31 -2
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
- package/dist/src/utils/external-resource-validator.d.ts +5 -192
- package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
- package/dist/src/utils/external-resource-validator.js +10 -1162
- package/dist/src/utils/external-resource-validator.js.map +1 -1
- package/dist/src/utils/validators/ado-validator.d.ts +94 -0
- package/dist/src/utils/validators/ado-validator.d.ts.map +1 -0
- package/dist/src/utils/validators/ado-validator.js +547 -0
- package/dist/src/utils/validators/ado-validator.js.map +1 -0
- package/dist/src/utils/validators/index.d.ts +11 -0
- package/dist/src/utils/validators/index.d.ts.map +1 -0
- package/dist/src/utils/validators/index.js +12 -0
- package/dist/src/utils/validators/index.js.map +1 -0
- package/dist/src/utils/validators/jira-validator.d.ts +70 -0
- package/dist/src/utils/validators/jira-validator.d.ts.map +1 -0
- package/dist/src/utils/validators/jira-validator.js +606 -0
- package/dist/src/utils/validators/jira-validator.js.map +1 -0
- package/dist/src/utils/validators/types.d.ts +82 -0
- package/dist/src/utils/validators/types.d.ts.map +1 -0
- package/dist/src/utils/validators/types.js +6 -0
- package/dist/src/utils/validators/types.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +7 -62
- package/plugins/specweave/commands/specweave-archive.md +3 -3
- package/plugins/specweave/commands/specweave-increment.md +18 -19
- package/plugins/specweave/hooks/hooks.json +3 -49
- package/plugins/specweave/hooks/hooks.json.bak +72 -0
- package/plugins/specweave/hooks/hooks.json.v1-backup +16 -0
- package/plugins/specweave/hooks/lib/update-status-line.sh +39 -15
- package/plugins/specweave/hooks/post-task-edit.sh +10 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +27 -8
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +44 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +24 -0
- package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +46 -0
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +54 -0
- package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +46 -0
- package/plugins/specweave/hooks/v2/handlers/status-update.sh +50 -0
- package/plugins/specweave/hooks/v2/hooks.json +16 -0
- package/plugins/specweave/hooks/v2/queue/dequeue.sh +30 -0
- package/plugins/specweave/hooks/v2/queue/enqueue.sh +41 -0
- package/plugins/specweave/hooks/v2/queue/processor.sh +72 -0
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +20 -1262
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
- package/plugins/specweave-release/commands/specweave-release-npm.md +132 -24
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +30 -1254
- package/src/templates/tasks.md.template +2 -0
- package/plugins/specweave/hooks/docs-changed.sh.backup +0 -79
- package/plugins/specweave/hooks/human-input-required.sh.backup +0 -75
- package/plugins/specweave/hooks/post-first-increment.sh.backup +0 -61
- package/plugins/specweave/hooks/post-increment-change.sh.backup +0 -98
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +0 -231
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +0 -1048
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +0 -147
- package/plugins/specweave/hooks/post-spec-update.sh.backup +0 -158
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +0 -179
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +0 -83
- package/plugins/specweave/hooks/pre-implementation.sh.backup +0 -67
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +0 -194
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +0 -133
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +0 -386
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +0 -353
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +0 -172
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +0 -258
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +0 -172
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +0 -110
|
@@ -76,27 +76,46 @@ if [[ -d "$SPECWEAVE_DIR/increments" ]]; then
|
|
|
76
76
|
fi
|
|
77
77
|
|
|
78
78
|
# ==============================================================================
|
|
79
|
-
# DISCIPLINE VALIDATION:
|
|
79
|
+
# DISCIPLINE VALIDATION: Warn about WIP limits (configurable, not hard block!)
|
|
80
80
|
# ==============================================================================
|
|
81
81
|
|
|
82
82
|
if echo "$PROMPT" | grep -q "/specweave:increment"; then
|
|
83
|
-
#
|
|
84
|
-
|
|
83
|
+
# Read limits from config.json (respect user's settings!)
|
|
84
|
+
CONFIG_FILE="$SPECWEAVE_DIR/config.json"
|
|
85
|
+
SOFT_LIMIT=1
|
|
86
|
+
HARD_CAP=3
|
|
87
|
+
|
|
88
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
89
|
+
if command -v jq >/dev/null 2>&1; then
|
|
90
|
+
SOFT_LIMIT=$(jq -r '.limits.maxActiveIncrements // 1' "$CONFIG_FILE" 2>/dev/null || echo "1")
|
|
91
|
+
HARD_CAP=$(jq -r '.limits.hardCap // 3' "$CONFIG_FILE" 2>/dev/null || echo "3")
|
|
92
|
+
else
|
|
93
|
+
SOFT_LIMIT=$(grep -oP '"maxActiveIncrements"\s*:\s*\K[0-9]+' "$CONFIG_FILE" 2>/dev/null || echo "1")
|
|
94
|
+
HARD_CAP=$(grep -oP '"hardCap"\s*:\s*\K[0-9]+' "$CONFIG_FILE" 2>/dev/null || echo "3")
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Ensure valid numbers
|
|
99
|
+
[[ ! "$SOFT_LIMIT" =~ ^[0-9]+$ ]] && SOFT_LIMIT=1
|
|
100
|
+
[[ ! "$HARD_CAP" =~ ^[0-9]+$ ]] && HARD_CAP=3
|
|
101
|
+
|
|
102
|
+
# Above hard cap: strong warning but NOT a block (user decides!)
|
|
103
|
+
if [[ "$ACTIVE_COUNT" -ge "$HARD_CAP" ]]; then
|
|
85
104
|
cat <<EOF
|
|
86
105
|
{
|
|
87
|
-
"decision": "
|
|
88
|
-
"
|
|
106
|
+
"decision": "approve",
|
|
107
|
+
"systemMessage": "⚠️ WIP LIMIT EXCEEDED (${ACTIVE_COUNT}/${HARD_CAP})\n\nYou have $ACTIVE_COUNT active increments (configured maximum: $HARD_CAP)\n\nActive increments:\n$ACTIVE_LIST\n\n🧠 Research shows 3+ concurrent tasks = 40% slower + more bugs\n\n💡 Options:\n 1️⃣ Complete an increment: /specweave:done <id>\n 2️⃣ Pause an increment: /specweave:pause <id>\n 3️⃣ Increase limit: Edit .specweave/config.json limits.hardCap\n 4️⃣ Continue anyway (not recommended)\n\n📝 To proceed anyway, just confirm your intent."
|
|
89
108
|
}
|
|
90
109
|
EOF
|
|
91
110
|
exit 0
|
|
92
111
|
fi
|
|
93
112
|
|
|
94
|
-
#
|
|
95
|
-
if [[ "$ACTIVE_COUNT" -ge
|
|
113
|
+
# At soft limit: mild warning, approve
|
|
114
|
+
if [[ "$ACTIVE_COUNT" -ge "$SOFT_LIMIT" ]]; then
|
|
96
115
|
cat <<EOF
|
|
97
116
|
{
|
|
98
117
|
"decision": "approve",
|
|
99
|
-
"systemMessage": "⚠️ WIP LIMIT REACHED\n\nYou have $ACTIVE_COUNT active increment (recommended limit:
|
|
118
|
+
"systemMessage": "⚠️ WIP LIMIT REACHED (${ACTIVE_COUNT}/${SOFT_LIMIT})\n\nYou have $ACTIVE_COUNT active increment(s) (recommended limit: $SOFT_LIMIT)\n\nActive increments:\n$ACTIVE_LIST\n\n🧠 Focus Principle: Fewer active increments = maximum productivity\n\n💡 Consider:\n 1️⃣ Complete current work (recommended)\n 2️⃣ Pause current work (/specweave:pause)\n 3️⃣ Continue anyway\n\n⚠️ Emergency hotfix/bug? Use --type=hotfix or --type=bug"
|
|
100
119
|
}
|
|
101
120
|
EOF
|
|
102
121
|
exit 0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# post-tool-use.sh - Single dispatcher for ALL PostToolUse events
|
|
3
|
+
# Replaces: post-task-edit, post-metadata-change, post-increment-planning, etc.
|
|
4
|
+
# Goal: <10ms execution, queue heavy work for async processing
|
|
5
|
+
set +e
|
|
6
|
+
|
|
7
|
+
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
8
|
+
|
|
9
|
+
# Find project root
|
|
10
|
+
PROJECT_ROOT="$PWD"
|
|
11
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
12
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
13
|
+
done
|
|
14
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
15
|
+
|
|
16
|
+
# Read stdin (tool result JSON)
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
|
|
19
|
+
# Extract file path (fast grep, no jq)
|
|
20
|
+
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
|
|
21
|
+
[[ -z "$FILE_PATH" ]] && exit 0
|
|
22
|
+
|
|
23
|
+
# Detect event type based on file
|
|
24
|
+
EVENT_TYPE=""
|
|
25
|
+
case "$FILE_PATH" in
|
|
26
|
+
*/.specweave/increments/*/tasks.md) EVENT_TYPE="task.updated" ;;
|
|
27
|
+
*/.specweave/increments/*/spec.md) EVENT_TYPE="spec.updated" ;;
|
|
28
|
+
*/.specweave/increments/*/metadata.json) EVENT_TYPE="metadata.changed" ;;
|
|
29
|
+
*/.specweave/increments/*/plan.md) EVENT_TYPE="plan.updated" ;;
|
|
30
|
+
*) exit 0 ;; # Not a specweave file, ignore
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
# Extract increment ID
|
|
34
|
+
INC_ID=$(echo "$FILE_PATH" | grep -o '[0-9][0-9][0-9][0-9]-[^/]*' | head -1)
|
|
35
|
+
|
|
36
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
37
|
+
|
|
38
|
+
# SYNCHRONOUS: Status update only (fast, always needed)
|
|
39
|
+
bash "$HOOK_DIR/handlers/status-update.sh" "$INC_ID" 2>/dev/null &
|
|
40
|
+
|
|
41
|
+
# ASYNC: Queue heavy operations for background processing
|
|
42
|
+
bash "$HOOK_DIR/queue/enqueue.sh" "$EVENT_TYPE" "$INC_ID" 2>/dev/null
|
|
43
|
+
|
|
44
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-start.sh - Launch background processor on session start
|
|
3
|
+
# Ultra-fast, non-blocking
|
|
4
|
+
set +e
|
|
5
|
+
|
|
6
|
+
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
7
|
+
|
|
8
|
+
# Find project root
|
|
9
|
+
PROJECT_ROOT="$PWD"
|
|
10
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
11
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
12
|
+
done
|
|
13
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Consume stdin
|
|
16
|
+
cat > /dev/null
|
|
17
|
+
|
|
18
|
+
PROCESSOR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../queue" && pwd)/processor.sh"
|
|
19
|
+
[[ ! -f "$PROCESSOR" ]] && exit 0
|
|
20
|
+
|
|
21
|
+
# Launch processor in background (daemon mode)
|
|
22
|
+
nohup bash "$PROCESSOR" --daemon > /dev/null 2>&1 &
|
|
23
|
+
disown 2>/dev/null
|
|
24
|
+
exit 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ac-validation-handler.sh - Validate AC completion status
|
|
3
|
+
# Checks that completed tasks have their ACs checked in spec.md
|
|
4
|
+
# Non-blocking, logs warnings only
|
|
5
|
+
set +e
|
|
6
|
+
|
|
7
|
+
INC_ID="${1:-}"
|
|
8
|
+
[[ -z "$INC_ID" ]] && exit 0
|
|
9
|
+
|
|
10
|
+
# Find project root
|
|
11
|
+
PROJECT_ROOT="$PWD"
|
|
12
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
13
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
14
|
+
done
|
|
15
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
16
|
+
|
|
17
|
+
INC_DIR="$PROJECT_ROOT/.specweave/increments/$INC_ID"
|
|
18
|
+
TASKS_FILE="$INC_DIR/tasks.md"
|
|
19
|
+
SPEC_FILE="$INC_DIR/spec.md"
|
|
20
|
+
LOG_FILE="$PROJECT_ROOT/.specweave/logs/ac-validation.log"
|
|
21
|
+
|
|
22
|
+
[[ ! -f "$TASKS_FILE" ]] || [[ ! -f "$SPEC_FILE" ]] && exit 0
|
|
23
|
+
|
|
24
|
+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null
|
|
25
|
+
|
|
26
|
+
# Find completed tasks and their ACs
|
|
27
|
+
WARNINGS=""
|
|
28
|
+
while IFS= read -r line; do
|
|
29
|
+
if [[ "$line" =~ ^\*\*Satisfies\ ACs\*\*:\ (.+)$ ]]; then
|
|
30
|
+
ACS="${BASH_REMATCH[1]}"
|
|
31
|
+
# Check each AC
|
|
32
|
+
for ac in $(echo "$ACS" | tr ',' ' '); do
|
|
33
|
+
ac=$(echo "$ac" | tr -d ' ')
|
|
34
|
+
[[ -z "$ac" ]] && continue
|
|
35
|
+
# Check if AC is checked in spec.md
|
|
36
|
+
if ! grep -qE "^\s*-\s*\[x\]\s*\*\*${ac}\*\*" "$SPEC_FILE" 2>/dev/null; then
|
|
37
|
+
WARNINGS="$WARNINGS\n - $ac not checked in spec.md"
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
fi
|
|
41
|
+
done < <(grep -A5 "\[x\] completed" "$TASKS_FILE" 2>/dev/null | grep "Satisfies ACs")
|
|
42
|
+
|
|
43
|
+
if [[ -n "$WARNINGS" ]]; then
|
|
44
|
+
echo "[$(date)] $INC_ID: AC validation warnings:$WARNINGS" >> "$LOG_FILE"
|
|
45
|
+
fi
|
|
46
|
+
exit 0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# github-sync-handler.sh - Sync increment status to GitHub issue
|
|
3
|
+
# Called async by processor, non-blocking, error-tolerant
|
|
4
|
+
set +e
|
|
5
|
+
|
|
6
|
+
INC_ID="${1:-}"
|
|
7
|
+
[[ -z "$INC_ID" ]] && exit 0
|
|
8
|
+
|
|
9
|
+
# Find project root
|
|
10
|
+
PROJECT_ROOT="$PWD"
|
|
11
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
12
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
13
|
+
done
|
|
14
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
15
|
+
|
|
16
|
+
CONFIG_FILE="$PROJECT_ROOT/.specweave/config.json"
|
|
17
|
+
[[ ! -f "$CONFIG_FILE" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
# Check if GitHub sync is enabled
|
|
20
|
+
GITHUB_ENABLED=$(grep -o '"enabled"[[:space:]]*:[[:space:]]*true' "$CONFIG_FILE" | head -1)
|
|
21
|
+
[[ -z "$GITHUB_ENABLED" ]] && exit 0
|
|
22
|
+
|
|
23
|
+
# Throttle: max once per 5 minutes per increment
|
|
24
|
+
THROTTLE_FILE="$PROJECT_ROOT/.specweave/state/.github-sync-$INC_ID"
|
|
25
|
+
if [[ -f "$THROTTLE_FILE" ]]; then
|
|
26
|
+
AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
|
|
27
|
+
[[ $AGE -lt 300 ]] && exit 0
|
|
28
|
+
fi
|
|
29
|
+
touch "$THROTTLE_FILE"
|
|
30
|
+
|
|
31
|
+
# Load GitHub token
|
|
32
|
+
GITHUB_TOKEN=""
|
|
33
|
+
[[ -f "$PROJECT_ROOT/.env" ]] && GITHUB_TOKEN=$(grep -E "^GITHUB_TOKEN=" "$PROJECT_ROOT/.env" | cut -d'=' -f2- | tr -d '"'"'")
|
|
34
|
+
[[ -z "$GITHUB_TOKEN" ]] && exit 0
|
|
35
|
+
|
|
36
|
+
# Find sync script
|
|
37
|
+
SYNC_SCRIPT=""
|
|
38
|
+
for path in \
|
|
39
|
+
"$PROJECT_ROOT/dist/plugins/specweave-github/lib/github-feature-sync-cli.js" \
|
|
40
|
+
"${CLAUDE_PLUGIN_ROOT:-/specweave-github}/lib/github-feature-sync-cli.js"; do
|
|
41
|
+
[[ -f "$path" ]] && { SYNC_SCRIPT="$path"; break; }
|
|
42
|
+
done
|
|
43
|
+
[[ -z "$SYNC_SCRIPT" ]] && exit 0
|
|
44
|
+
|
|
45
|
+
# Extract feature ID
|
|
46
|
+
SPEC_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/spec.md"
|
|
47
|
+
FEATURE_ID=""
|
|
48
|
+
[[ -f "$SPEC_FILE" ]] && FEATURE_ID=$(grep -E "^(epic|feature_id):" "$SPEC_FILE" | head -1 | sed 's/.*:[[:space:]]*//' | tr -d '"'"'")
|
|
49
|
+
[[ -z "$FEATURE_ID" ]] && exit 0
|
|
50
|
+
|
|
51
|
+
# Run sync (timeout 60s)
|
|
52
|
+
cd "$PROJECT_ROOT" || exit 0
|
|
53
|
+
GITHUB_TOKEN="$GITHUB_TOKEN" timeout 60 node "$SYNC_SCRIPT" "$FEATURE_ID" >/dev/null 2>&1
|
|
54
|
+
exit 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# living-docs-handler.sh - Sync increment to living docs
|
|
3
|
+
# Called async by processor, non-blocking, error-tolerant
|
|
4
|
+
set +e
|
|
5
|
+
|
|
6
|
+
INC_ID="${1:-}"
|
|
7
|
+
[[ -z "$INC_ID" ]] && exit 0
|
|
8
|
+
|
|
9
|
+
# Find project root
|
|
10
|
+
PROJECT_ROOT="$PWD"
|
|
11
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
12
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
13
|
+
done
|
|
14
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
15
|
+
|
|
16
|
+
# Throttle: max once per minute per increment
|
|
17
|
+
THROTTLE_FILE="$PROJECT_ROOT/.specweave/state/.living-docs-$INC_ID"
|
|
18
|
+
if [[ -f "$THROTTLE_FILE" ]]; then
|
|
19
|
+
AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
|
|
20
|
+
[[ $AGE -lt 60 ]] && exit 0
|
|
21
|
+
fi
|
|
22
|
+
touch "$THROTTLE_FILE"
|
|
23
|
+
|
|
24
|
+
# Find sync script
|
|
25
|
+
SYNC_SCRIPT=""
|
|
26
|
+
for path in \
|
|
27
|
+
"$PROJECT_ROOT/plugins/specweave/lib/hooks/sync-living-docs.js" \
|
|
28
|
+
"$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/sync-living-docs.js" \
|
|
29
|
+
"${CLAUDE_PLUGIN_ROOT:-}/lib/hooks/sync-living-docs.js"; do
|
|
30
|
+
[[ -f "$path" ]] && { SYNC_SCRIPT="$path"; break; }
|
|
31
|
+
done
|
|
32
|
+
[[ -z "$SYNC_SCRIPT" ]] && exit 0
|
|
33
|
+
|
|
34
|
+
# Extract feature ID from spec.md
|
|
35
|
+
SPEC_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/spec.md"
|
|
36
|
+
FEATURE_ID=""
|
|
37
|
+
[[ -f "$SPEC_FILE" ]] && FEATURE_ID=$(grep -E "^(epic|feature_id):" "$SPEC_FILE" | head -1 | sed 's/.*:[[:space:]]*//' | tr -d '"'"'")
|
|
38
|
+
|
|
39
|
+
# Run sync (timeout 30s)
|
|
40
|
+
cd "$PROJECT_ROOT" || exit 0
|
|
41
|
+
if [[ -n "$FEATURE_ID" ]]; then
|
|
42
|
+
FEATURE_ID="$FEATURE_ID" timeout 30 node "$SYNC_SCRIPT" "$INC_ID" >/dev/null 2>&1
|
|
43
|
+
else
|
|
44
|
+
timeout 30 node "$SYNC_SCRIPT" "$INC_ID" >/dev/null 2>&1
|
|
45
|
+
fi
|
|
46
|
+
exit 0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# status-update.sh - Fast status line update (synchronous)
|
|
3
|
+
# Goal: <20ms execution, pure bash, no external processes
|
|
4
|
+
set +e
|
|
5
|
+
|
|
6
|
+
INC_ID="${1:-}"
|
|
7
|
+
|
|
8
|
+
# Find project root
|
|
9
|
+
PROJECT_ROOT="$PWD"
|
|
10
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
11
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
12
|
+
done
|
|
13
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
STATE_DIR="$PROJECT_ROOT/.specweave/state"
|
|
16
|
+
CACHE_FILE="$STATE_DIR/status-line.json"
|
|
17
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
18
|
+
|
|
19
|
+
# TTL check (10 seconds)
|
|
20
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
21
|
+
CACHE_AGE=$(($(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0)))
|
|
22
|
+
[[ $CACHE_AGE -lt 10 ]] && exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Find active increment if not provided
|
|
26
|
+
if [[ -z "$INC_ID" ]]; then
|
|
27
|
+
for meta in "$PROJECT_ROOT/.specweave/increments"/[0-9]*/metadata.json; do
|
|
28
|
+
[[ -f "$meta" ]] || continue
|
|
29
|
+
STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$meta" | grep -o '"[^"]*"$' | tr -d '"')
|
|
30
|
+
[[ "$STATUS" == "active" || "$STATUS" == "planning" ]] && {
|
|
31
|
+
INC_ID=$(basename "$(dirname "$meta")")
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
done
|
|
35
|
+
fi
|
|
36
|
+
[[ -z "$INC_ID" ]] && { echo '{"current":null}' > "$CACHE_FILE"; exit 0; }
|
|
37
|
+
|
|
38
|
+
TASKS_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/tasks.md"
|
|
39
|
+
[[ ! -f "$TASKS_FILE" ]] && exit 0
|
|
40
|
+
|
|
41
|
+
# Count tasks (pure bash, fast)
|
|
42
|
+
TOTAL=$(grep -c "^###\? T-" "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
43
|
+
DONE=$(grep -c "\[x\]" "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
44
|
+
PCT=0; [[ $TOTAL -gt 0 ]] && PCT=$((DONE * 100 / TOTAL))
|
|
45
|
+
|
|
46
|
+
# Write cache (atomic)
|
|
47
|
+
cat > "$CACHE_FILE" << EOF
|
|
48
|
+
{"current":{"id":"$INC_ID","completed":$DONE,"total":$TOTAL,"percentage":$PCT},"ts":"$(date +%s)"}
|
|
49
|
+
EOF
|
|
50
|
+
exit 0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write",
|
|
6
|
+
"matcher_content": "\\.specweave/increments/",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/v2/dispatchers/post-tool-use.sh"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# dequeue.sh - Get and remove next event from queue
|
|
3
|
+
# Usage: dequeue.sh [--peek]
|
|
4
|
+
# Returns JSON event or empty if queue is empty
|
|
5
|
+
set +e
|
|
6
|
+
|
|
7
|
+
PEEK=false
|
|
8
|
+
[[ "$1" == "--peek" ]] && PEEK=true
|
|
9
|
+
|
|
10
|
+
# Find project root
|
|
11
|
+
PROJECT_ROOT="$PWD"
|
|
12
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
13
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
14
|
+
done
|
|
15
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
16
|
+
|
|
17
|
+
QUEUE_DIR="$PROJECT_ROOT/.specweave/state/event-queue"
|
|
18
|
+
[[ ! -d "$QUEUE_DIR" ]] && exit 0
|
|
19
|
+
|
|
20
|
+
# Get oldest event file (FIFO)
|
|
21
|
+
OLDEST=$(ls -1 "$QUEUE_DIR"/*.event 2>/dev/null | head -1)
|
|
22
|
+
[[ -z "$OLDEST" ]] && exit 0
|
|
23
|
+
|
|
24
|
+
# Output event content
|
|
25
|
+
cat "$OLDEST" 2>/dev/null
|
|
26
|
+
|
|
27
|
+
# Remove if not peeking
|
|
28
|
+
[[ "$PEEK" == "false" ]] && rm -f "$OLDEST" 2>/dev/null
|
|
29
|
+
|
|
30
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# enqueue.sh - Add event to queue with deduplication
|
|
3
|
+
# Usage: enqueue.sh <event_type> <event_data>
|
|
4
|
+
# Events are deduplicated by type+data hash within 5 second window
|
|
5
|
+
set +e
|
|
6
|
+
|
|
7
|
+
EVENT_TYPE="${1:-unknown}"
|
|
8
|
+
EVENT_DATA="${2:-}"
|
|
9
|
+
|
|
10
|
+
# Find project root
|
|
11
|
+
PROJECT_ROOT="$PWD"
|
|
12
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
13
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
14
|
+
done
|
|
15
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
16
|
+
|
|
17
|
+
QUEUE_DIR="$PROJECT_ROOT/.specweave/state/event-queue"
|
|
18
|
+
mkdir -p "$QUEUE_DIR" 2>/dev/null || exit 0
|
|
19
|
+
|
|
20
|
+
# Deduplication: hash event type + data
|
|
21
|
+
HASH=$(echo "${EVENT_TYPE}:${EVENT_DATA}" | md5 | cut -c1-8)
|
|
22
|
+
DEDUP_FILE="$QUEUE_DIR/.dedup-$HASH"
|
|
23
|
+
DEDUP_TTL=5
|
|
24
|
+
|
|
25
|
+
# Check if duplicate (within TTL)
|
|
26
|
+
if [[ -f "$DEDUP_FILE" ]]; then
|
|
27
|
+
AGE=$(($(date +%s) - $(stat -f %m "$DEDUP_FILE" 2>/dev/null || echo 0)))
|
|
28
|
+
[[ $AGE -lt $DEDUP_TTL ]] && exit 0
|
|
29
|
+
fi
|
|
30
|
+
touch "$DEDUP_FILE"
|
|
31
|
+
|
|
32
|
+
# Enqueue event (atomic write)
|
|
33
|
+
TIMESTAMP=$(date +%s%N)
|
|
34
|
+
EVENT_FILE="$QUEUE_DIR/${TIMESTAMP}-${EVENT_TYPE}.event"
|
|
35
|
+
cat > "$EVENT_FILE" << EOF
|
|
36
|
+
{"type":"$EVENT_TYPE","data":"$EVENT_DATA","ts":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
|
|
37
|
+
EOF
|
|
38
|
+
|
|
39
|
+
# Cleanup old dedup files (>30s)
|
|
40
|
+
find "$QUEUE_DIR" -name ".dedup-*" -mmin +1 -delete 2>/dev/null &
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# processor.sh - Background event processor
|
|
3
|
+
# Processes queued events asynchronously, routes to handlers
|
|
4
|
+
# Usage: processor.sh [--daemon]
|
|
5
|
+
# Self-terminates after 30s of idle
|
|
6
|
+
set +e
|
|
7
|
+
|
|
8
|
+
DAEMON_MODE=false
|
|
9
|
+
[[ "$1" == "--daemon" ]] && DAEMON_MODE=true
|
|
10
|
+
|
|
11
|
+
# Find project root
|
|
12
|
+
PROJECT_ROOT="$PWD"
|
|
13
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
14
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
15
|
+
done
|
|
16
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
17
|
+
|
|
18
|
+
QUEUE_DIR="$PROJECT_ROOT/.specweave/state/event-queue"
|
|
19
|
+
HANDLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../handlers" && pwd)"
|
|
20
|
+
LOG_FILE="$PROJECT_ROOT/.specweave/logs/processor.log"
|
|
21
|
+
PID_FILE="$PROJECT_ROOT/.specweave/state/.processor.pid"
|
|
22
|
+
IDLE_TIMEOUT=30
|
|
23
|
+
IDLE_COUNT=0
|
|
24
|
+
|
|
25
|
+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null
|
|
26
|
+
|
|
27
|
+
# Check if already running
|
|
28
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
29
|
+
OLD_PID=$(cat "$PID_FILE" 2>/dev/null)
|
|
30
|
+
if kill -0 "$OLD_PID" 2>/dev/null; then
|
|
31
|
+
exit 0 # Already running
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
echo $$ > "$PID_FILE"
|
|
35
|
+
trap 'rm -f "$PID_FILE"' EXIT
|
|
36
|
+
|
|
37
|
+
log() { echo "[$(date +%H:%M:%S)] $1" >> "$LOG_FILE" 2>/dev/null; }
|
|
38
|
+
log "Processor started (PID: $$)"
|
|
39
|
+
|
|
40
|
+
process_event() {
|
|
41
|
+
local EVENT_JSON="$1"
|
|
42
|
+
local EVENT_TYPE=$(echo "$EVENT_JSON" | grep -o '"type":"[^"]*"' | cut -d'"' -f4)
|
|
43
|
+
local EVENT_DATA=$(echo "$EVENT_JSON" | grep -o '"data":"[^"]*"' | cut -d'"' -f4)
|
|
44
|
+
|
|
45
|
+
log "Processing: $EVENT_TYPE ($EVENT_DATA)"
|
|
46
|
+
|
|
47
|
+
case "$EVENT_TYPE" in
|
|
48
|
+
task.updated|spec.updated)
|
|
49
|
+
bash "$HANDLER_DIR/living-docs-handler.sh" "$EVENT_DATA" 2>/dev/null
|
|
50
|
+
bash "$HANDLER_DIR/ac-validation-handler.sh" "$EVENT_DATA" 2>/dev/null
|
|
51
|
+
;;
|
|
52
|
+
metadata.changed)
|
|
53
|
+
bash "$HANDLER_DIR/github-sync-handler.sh" "$EVENT_DATA" 2>/dev/null
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Main loop
|
|
59
|
+
while true; do
|
|
60
|
+
EVENT=$(bash "$(dirname "${BASH_SOURCE[0]}")/dequeue.sh" 2>/dev/null)
|
|
61
|
+
|
|
62
|
+
if [[ -n "$EVENT" ]]; then
|
|
63
|
+
IDLE_COUNT=0
|
|
64
|
+
process_event "$EVENT"
|
|
65
|
+
else
|
|
66
|
+
IDLE_COUNT=$((IDLE_COUNT + 1))
|
|
67
|
+
[[ $IDLE_COUNT -ge $IDLE_TIMEOUT ]] && { log "Idle timeout, exiting"; exit 0; }
|
|
68
|
+
sleep 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
[[ "$DAEMON_MODE" == "false" ]] && [[ $IDLE_COUNT -ge 3 ]] && exit 0
|
|
72
|
+
done
|