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.
Files changed (144) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +10 -9
  3. package/bin/specweave.js +2 -1
  4. package/dist/src/cli/commands/archive.d.ts.map +1 -1
  5. package/dist/src/cli/commands/archive.js +2 -1
  6. package/dist/src/cli/commands/archive.js.map +1 -1
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +33 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/helpers/ado-area-selector.d.ts +49 -0
  11. package/dist/src/cli/helpers/ado-area-selector.d.ts.map +1 -0
  12. package/dist/src/cli/helpers/ado-area-selector.js +162 -0
  13. package/dist/src/cli/helpers/ado-area-selector.js.map +1 -0
  14. package/dist/src/cli/helpers/init/config-detection.d.ts +5 -1
  15. package/dist/src/cli/helpers/init/config-detection.d.ts.map +1 -1
  16. package/dist/src/cli/helpers/init/config-detection.js +50 -17
  17. package/dist/src/cli/helpers/init/config-detection.js.map +1 -1
  18. package/dist/src/cli/helpers/init/external-import.js +3 -3
  19. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  20. package/dist/src/cli/helpers/init/repository-setup.d.ts +2 -0
  21. package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -1
  22. package/dist/src/cli/helpers/init/repository-setup.js +34 -2
  23. package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
  24. package/dist/src/cli/helpers/init/types.d.ts +3 -0
  25. package/dist/src/cli/helpers/init/types.d.ts.map +1 -1
  26. package/dist/src/cli/helpers/issue-tracker/ado.d.ts +17 -0
  27. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  28. package/dist/src/cli/helpers/issue-tracker/ado.js +308 -44
  29. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  30. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  31. package/dist/src/cli/helpers/issue-tracker/index.js +103 -35
  32. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  33. package/dist/src/cli/helpers/issue-tracker/types.d.ts +26 -2
  34. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  35. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  36. package/dist/src/core/increment/discipline-checker.js +1 -1
  37. package/dist/src/core/increment/increment-archiver.d.ts +13 -0
  38. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  39. package/dist/src/core/increment/increment-archiver.js +60 -3
  40. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  41. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  42. package/dist/src/core/living-docs/feature-archiver.js +75 -37
  43. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  44. package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -111
  45. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  46. package/dist/src/core/living-docs/living-docs-sync.js +18 -383
  47. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/file-utils.d.ts +30 -0
  49. package/dist/src/core/living-docs/sync-helpers/file-utils.d.ts.map +1 -0
  50. package/dist/src/core/living-docs/sync-helpers/file-utils.js +107 -0
  51. package/dist/src/core/living-docs/sync-helpers/file-utils.js.map +1 -0
  52. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +19 -0
  53. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -0
  54. package/dist/src/core/living-docs/sync-helpers/generators.js +146 -0
  55. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -0
  56. package/dist/src/core/living-docs/sync-helpers/index.d.ts +8 -0
  57. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -0
  58. package/dist/src/core/living-docs/sync-helpers/index.js +11 -0
  59. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -0
  60. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +19 -0
  61. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -0
  62. package/dist/src/core/living-docs/sync-helpers/parsers.js +94 -0
  63. package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -0
  64. package/dist/src/core/living-docs/types.d.ts +45 -0
  65. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  66. package/dist/src/core/types/config.d.ts +1 -1
  67. package/dist/src/core/types/config.js +2 -2
  68. package/dist/src/core/types/config.js.map +1 -1
  69. package/dist/src/importers/ado-importer.d.ts.map +1 -1
  70. package/dist/src/importers/ado-importer.js +3 -0
  71. package/dist/src/importers/ado-importer.js.map +1 -1
  72. package/dist/src/importers/item-converter.d.ts.map +1 -1
  73. package/dist/src/importers/item-converter.js +10 -2
  74. package/dist/src/importers/item-converter.js.map +1 -1
  75. package/dist/src/living-docs/fs-id-allocator.d.ts +5 -0
  76. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
  77. package/dist/src/living-docs/fs-id-allocator.js +31 -2
  78. package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
  79. package/dist/src/utils/external-resource-validator.d.ts +5 -192
  80. package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
  81. package/dist/src/utils/external-resource-validator.js +10 -1162
  82. package/dist/src/utils/external-resource-validator.js.map +1 -1
  83. package/dist/src/utils/validators/ado-validator.d.ts +94 -0
  84. package/dist/src/utils/validators/ado-validator.d.ts.map +1 -0
  85. package/dist/src/utils/validators/ado-validator.js +547 -0
  86. package/dist/src/utils/validators/ado-validator.js.map +1 -0
  87. package/dist/src/utils/validators/index.d.ts +11 -0
  88. package/dist/src/utils/validators/index.d.ts.map +1 -0
  89. package/dist/src/utils/validators/index.js +12 -0
  90. package/dist/src/utils/validators/index.js.map +1 -0
  91. package/dist/src/utils/validators/jira-validator.d.ts +70 -0
  92. package/dist/src/utils/validators/jira-validator.d.ts.map +1 -0
  93. package/dist/src/utils/validators/jira-validator.js +606 -0
  94. package/dist/src/utils/validators/jira-validator.js.map +1 -0
  95. package/dist/src/utils/validators/types.d.ts +82 -0
  96. package/dist/src/utils/validators/types.d.ts.map +1 -0
  97. package/dist/src/utils/validators/types.js +6 -0
  98. package/dist/src/utils/validators/types.js.map +1 -0
  99. package/package.json +1 -1
  100. package/plugins/specweave/.claude-plugin/plugin.json +7 -62
  101. package/plugins/specweave/commands/specweave-archive.md +3 -3
  102. package/plugins/specweave/commands/specweave-increment.md +18 -19
  103. package/plugins/specweave/hooks/hooks.json +3 -49
  104. package/plugins/specweave/hooks/hooks.json.bak +72 -0
  105. package/plugins/specweave/hooks/hooks.json.v1-backup +16 -0
  106. package/plugins/specweave/hooks/lib/update-status-line.sh +39 -15
  107. package/plugins/specweave/hooks/post-task-edit.sh +10 -0
  108. package/plugins/specweave/hooks/user-prompt-submit.sh +27 -8
  109. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +44 -0
  110. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +24 -0
  111. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +46 -0
  112. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +54 -0
  113. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +46 -0
  114. package/plugins/specweave/hooks/v2/handlers/status-update.sh +50 -0
  115. package/plugins/specweave/hooks/v2/hooks.json +16 -0
  116. package/plugins/specweave/hooks/v2/queue/dequeue.sh +30 -0
  117. package/plugins/specweave/hooks/v2/queue/enqueue.sh +41 -0
  118. package/plugins/specweave/hooks/v2/queue/processor.sh +72 -0
  119. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
  120. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +20 -1262
  121. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  122. package/plugins/specweave-release/commands/specweave-release-npm.md +132 -24
  123. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +30 -1254
  124. package/src/templates/tasks.md.template +2 -0
  125. package/plugins/specweave/hooks/docs-changed.sh.backup +0 -79
  126. package/plugins/specweave/hooks/human-input-required.sh.backup +0 -75
  127. package/plugins/specweave/hooks/post-first-increment.sh.backup +0 -61
  128. package/plugins/specweave/hooks/post-increment-change.sh.backup +0 -98
  129. package/plugins/specweave/hooks/post-increment-completion.sh.backup +0 -231
  130. package/plugins/specweave/hooks/post-increment-planning.sh.backup +0 -1048
  131. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +0 -147
  132. package/plugins/specweave/hooks/post-spec-update.sh.backup +0 -158
  133. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +0 -179
  134. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +0 -83
  135. package/plugins/specweave/hooks/pre-implementation.sh.backup +0 -67
  136. package/plugins/specweave/hooks/pre-task-completion.sh.backup +0 -194
  137. package/plugins/specweave/hooks/pre-tool-use.sh.backup +0 -133
  138. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +0 -386
  139. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +0 -353
  140. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +0 -172
  141. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
  142. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +0 -258
  143. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +0 -172
  144. 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: Block /specweave:increment if incomplete increments exist
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
- # Hard cap: never >2 active
84
- if [[ "$ACTIVE_COUNT" -ge 2 ]]; then
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": "block",
88
- "reason": " HARD CAP REACHED\n\nYou have $ACTIVE_COUNT active increments (absolute maximum: 2)\n\nActive increments:\n$ACTIVE_LIST\n\n💡 You MUST complete or pause existing work first:\n\n1️⃣ Complete an increment:\n /specweave:done <id>\n\n2️⃣ Pause an increment:\n /specweave:pause <id> --reason=\"...\"\n\n3️⃣ Check status:\n /specweave:status\n\n📝 Multiple hotfixes? Combine them into ONE increment!\n Example: 0009-security-fixes (SQL + XSS + CSRF)\n\n This limit is enforced for your productivity.\nResearch: 3+ concurrent tasks = 40% slower + more bugs"
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
- # Soft warning: 1 active (recommended limit)
95
- if [[ "$ACTIVE_COUNT" -ge 1 ]]; then
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: 1)\n\nActive increments:\n$ACTIVE_LIST\n\n🧠 Focus Principle: ONE active increment = maximum productivity\nStarting a 2nd increment reduces focus and velocity.\n\n💡 Consider:\n 1️⃣ Complete current work (recommended)\n 2️⃣ Pause current work (/specweave:pause)\n 3️⃣ Continue anyway (accept 20% productivity cost)\n\n⚠️ Emergency hotfix/bug? Use --type=hotfix or --type=bug to bypass this warning."
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
@@ -373,7 +373,6 @@ ${userStory.technicalContext}
373
373
  return mapping.task || "Task";
374
374
  case "Subtask":
375
375
  return mapping.task || "Task";
376
- // ADO doesn't have subtasks, use Task
377
376
  default:
378
377
  return "User Story";
379
378
  }