specweave 0.28.36 → 0.28.42
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 +161 -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 +1 -1
- 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 +10 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +107 -22
- 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 +30 -10
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -0
- 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 +2 -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 +86 -0
- package/dist/src/utils/validators/ado-validator.d.ts.map +1 -0
- package/dist/src/utils/validators/ado-validator.js +528 -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/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
|
@@ -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
|