specweave 1.0.261 → 1.0.263

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 (133) hide show
  1. package/.claude-plugin/README.md +0 -2
  2. package/CLAUDE.md +27 -27
  3. package/bin/specweave.js +14 -85
  4. package/dist/dashboard/assets/index-Cv1XUAKk.css +1 -0
  5. package/dist/dashboard/assets/index-DHOztQSu.js +11 -0
  6. package/dist/dashboard/index.html +2 -2
  7. package/dist/src/adapters/README.md +1 -1
  8. package/dist/src/adapters/agents-md-generator.js +1 -1
  9. package/dist/src/adapters/agents-md-generator.js.map +1 -1
  10. package/dist/src/adapters/claude/README.md +8 -8
  11. package/dist/src/adapters/claude/adapter.js +2 -2
  12. package/dist/src/adapters/claude-md-generator.js +2 -2
  13. package/dist/src/adapters/claude-md-generator.js.map +1 -1
  14. package/dist/src/adapters/cursor/README.md +7 -7
  15. package/dist/src/adapters/generic/README.md +2 -2
  16. package/dist/src/cli/commands/create-increment.d.ts +1 -1
  17. package/dist/src/cli/commands/create-increment.js +1 -1
  18. package/dist/src/cli/commands/update.d.ts.map +1 -1
  19. package/dist/src/cli/commands/update.js +64 -1
  20. package/dist/src/cli/commands/update.js.map +1 -1
  21. package/dist/src/cli/helpers/init/api-docs-config.js +7 -7
  22. package/dist/src/cli/helpers/init/api-docs-config.js.map +1 -1
  23. package/dist/src/core/config/types.d.ts +18 -0
  24. package/dist/src/core/config/types.d.ts.map +1 -1
  25. package/dist/src/core/config/types.js +4 -0
  26. package/dist/src/core/config/types.js.map +1 -1
  27. package/dist/src/core/external-tools/external-items-display.d.ts.map +1 -1
  28. package/dist/src/core/external-tools/external-items-display.js +1 -11
  29. package/dist/src/core/external-tools/external-items-display.js.map +1 -1
  30. package/dist/src/core/increment/increment-archiver.js +1 -1
  31. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  32. package/dist/src/core/increment/metadata-manager.js +2 -2
  33. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  34. package/dist/src/core/increment/template-creator.d.ts +1 -1
  35. package/dist/src/core/increment/template-creator.js +4 -4
  36. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
  37. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  38. package/dist/src/core/lazy-loading/llm-plugin-detector.js +15 -5
  39. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  40. package/dist/src/core/living-docs/feature-consistency-validator.js +1 -1
  41. package/dist/src/core/living-docs/feature-consistency-validator.js.map +1 -1
  42. package/dist/src/core/living-docs/scaffolding/scaffold.js +3 -3
  43. package/dist/src/core/notifications/command-integration.d.ts.map +1 -1
  44. package/dist/src/core/notifications/command-integration.js +0 -1
  45. package/dist/src/core/notifications/command-integration.js.map +1 -1
  46. package/dist/src/core/reflection/reflect-handler.js +2 -2
  47. package/dist/src/core/reflection/reflect-handler.js.map +1 -1
  48. package/dist/src/core/validators/ac-presence-validator.d.ts +1 -1
  49. package/dist/src/core/validators/ac-presence-validator.js +3 -3
  50. package/dist/src/core/validators/ac-presence-validator.js.map +1 -1
  51. package/dist/src/dashboard/server/command-runner.d.ts.map +1 -1
  52. package/dist/src/dashboard/server/command-runner.js +2 -2
  53. package/dist/src/dashboard/server/command-runner.js.map +1 -1
  54. package/dist/src/dashboard/server/dashboard-server.d.ts.map +1 -1
  55. package/dist/src/dashboard/server/dashboard-server.js +22 -10
  56. package/dist/src/dashboard/server/dashboard-server.js.map +1 -1
  57. package/dist/src/dashboard/server/data/dashboard-data-aggregator.d.ts +9 -1
  58. package/dist/src/dashboard/server/data/dashboard-data-aggregator.d.ts.map +1 -1
  59. package/dist/src/dashboard/server/data/dashboard-data-aggregator.js +140 -13
  60. package/dist/src/dashboard/server/data/dashboard-data-aggregator.js.map +1 -1
  61. package/dist/src/dashboard/server/data/plugin-scanner.d.ts +1 -1
  62. package/dist/src/dashboard/server/data/plugin-scanner.d.ts.map +1 -1
  63. package/dist/src/dashboard/server/data/plugin-scanner.js +2 -2
  64. package/dist/src/dashboard/server/data/plugin-scanner.js.map +1 -1
  65. package/dist/src/utils/agents-md-compiler.js +1 -1
  66. package/dist/src/utils/agents-md-compiler.js.map +1 -1
  67. package/dist/src/utils/find-project-root.d.ts +5 -4
  68. package/dist/src/utils/find-project-root.d.ts.map +1 -1
  69. package/dist/src/utils/find-project-root.js +8 -10
  70. package/dist/src/utils/find-project-root.js.map +1 -1
  71. package/dist/src/utils/generate-skills-index.js +3 -3
  72. package/dist/src/utils/notification-constants.js +1 -1
  73. package/dist/src/utils/notification-constants.js.map +1 -1
  74. package/package.json +1 -1
  75. package/plugins/FINAL-AUDIT-RECOMMENDATIONS.md +3 -3
  76. package/plugins/specweave/PLUGIN.md +0 -22
  77. package/plugins/specweave/commands/analytics.md +1 -1
  78. package/plugins/specweave/commands/discrepancies.md +0 -1
  79. package/plugins/specweave/commands/living-docs.md +0 -1
  80. package/plugins/specweave/commands/reconcile.md +1 -1
  81. package/plugins/specweave/hooks/hooks.json +19 -0
  82. package/plugins/specweave/hooks/pre-compact.sh +39 -0
  83. package/plugins/specweave/hooks/stop-sync.sh +23 -1
  84. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +4 -0
  85. package/plugins/specweave/hooks/user-prompt-submit.sh +193 -59
  86. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +83 -0
  87. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +7 -0
  88. package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +1 -1
  89. package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +25 -6
  90. package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +21 -3
  91. package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +52 -0
  92. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +2 -2
  93. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  94. package/plugins/specweave/scripts/track-analytics.sh +4 -0
  95. package/plugins/specweave/skills/do/SKILL.md +1 -1
  96. package/plugins/specweave/skills/done/SKILL.md +1 -1
  97. package/plugins/specweave/skills/framework/SKILL.md +4 -4
  98. package/plugins/specweave/skills/increment/SKILL.md +192 -25
  99. package/plugins/specweave/skills/next/SKILL.md +36 -630
  100. package/plugins/specweave/skills/pm/phases/00-deep-interview.md +2 -2
  101. package/plugins/specweave/skills/progress-sync/SKILL.md +7 -25
  102. package/plugins/specweave/skills/spec-generator/SKILL.md +44 -626
  103. package/plugins/specweave/skills/tdd-green/SKILL.md +10 -798
  104. package/plugins/specweave/skills/tdd-red/SKILL.md +8 -136
  105. package/plugins/specweave/skills/tdd-refactor/SKILL.md +15 -147
  106. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +23 -5
  107. package/src/templates/AGENTS.md.template +11 -11
  108. package/src/templates/CLAUDE.md.template +1 -1
  109. package/dist/dashboard/assets/index-CDl14O5G.css +0 -1
  110. package/dist/dashboard/assets/index-CmqBqnWd.js +0 -11
  111. package/plugins/specweave/commands/api-docs.md +0 -672
  112. package/plugins/specweave/commands/check-hooks.md +0 -241
  113. package/plugins/specweave/commands/embed-acs.md +0 -445
  114. package/plugins/specweave/commands/external.md +0 -145
  115. package/plugins/specweave/commands/import-docs.md +0 -212
  116. package/plugins/specweave/commands/migrate-config.md +0 -104
  117. package/plugins/specweave/commands/notifications.md +0 -94
  118. package/plugins/specweave/commands/plugin-validator.md +0 -429
  119. package/plugins/specweave/commands/revert-wip-limit.md +0 -82
  120. package/plugins/specweave/commands/sync-acs.md +0 -342
  121. package/plugins/specweave/commands/sync-specs.md +0 -339
  122. package/plugins/specweave/commands/sync-tasks.md +0 -255
  123. package/plugins/specweave/commands/update-scope.md +0 -351
  124. package/plugins/specweave/commands/validate-features.md +0 -207
  125. package/plugins/specweave/skills/archive-increments/SKILL.md +0 -209
  126. package/plugins/specweave/skills/code-review/SKILL.md +0 -598
  127. package/plugins/specweave/skills/increment-planner/SKILL.md +0 -238
  128. package/plugins/specweave/skills/increment-work-router/SKILL.md +0 -562
  129. package/plugins/specweave/skills/multi-project-spec-mapper/SKILL.md +0 -423
  130. package/plugins/specweave/skills/pm-closure-validation/SKILL.md +0 -542
  131. package/plugins/specweave/skills/smart-reopen-detector/SKILL.md +0 -245
  132. package/plugins/specweave/skills/tdd-orchestrator/SKILL.md +0 -228
  133. package/plugins/specweave/skills/umbrella-repo-detector/SKILL.md +0 -301
@@ -23,7 +23,7 @@
23
23
  # * Key insight: Users shouldn't need to know about LSP to benefit from it
24
24
  # - v1.0.177: SKILL CHAINING REMINDER - Add explicit guidance in SKILL FIRST message
25
25
  # * "SKILL FIRST" does NOT mean "only one skill"
26
- # * Shows domain skills to use after sw:increment-planner
26
+ # * Shows domain skills to use after sw:increment
27
27
  # * Points to CLAUDE.md "MANDATORY: Skill Chaining" section
28
28
  # - v1.0.175: CRITICAL FIX - Use installed_plugins.json as SOURCE OF TRUTH
29
29
  # * Reads ~/.claude/plugins/installed_plugins.json directly (eliminates false restart warnings)
@@ -33,8 +33,8 @@
33
33
  # * Post-install verification: re-checks registry after install to confirm success
34
34
  # * Increased timeouts: 5s → 10s for CLI operations (reduces timing issues)
35
35
  # * Guard against false positives: if install says "success" but not in registry → treat as already installed
36
- # - v1.0.169: DIRECT SKILL INVOCATION - Call sw:increment-planner directly (not wrapper)
37
- # * Skips 2-level indirection (hook → sw:increment command sw:increment-planner skill)
36
+ # - v1.0.169: DIRECT SKILL INVOCATION - Call sw:increment skill directly
37
+ # * Originally skipped wrapper indirection; increment-planner merged into increment in v1.0.261
38
38
  # * Passes FULL user prompt as args (not just extracted name)
39
39
  # * Uses <system><rules> tags (Claude-trained) instead of custom <mandatory_instruction>
40
40
  # * More concise, imperative instruction text
@@ -192,6 +192,21 @@ EOF
192
192
  fi
193
193
  fi
194
194
 
195
+ # ==============================================================================
196
+ # PROJECT ROOT DETECTION (walk up to find .specweave/config.json)
197
+ # ==============================================================================
198
+ # MUST run BEFORE any code that uses SW_PROJECT_ROOT (scope guard, logging, etc.)
199
+ # Checks for config.json to distinguish real projects from stale folders.
200
+ SW_PROJECT_ROOT=""
201
+ _swdir="$PWD"
202
+ while [[ "$_swdir" != "/" ]]; do
203
+ if [[ -f "$_swdir/.specweave/config.json" ]]; then
204
+ SW_PROJECT_ROOT="$_swdir"
205
+ break
206
+ fi
207
+ _swdir=$(dirname "$_swdir")
208
+ done
209
+
195
210
  # ==============================================================================
196
211
  # USER-LEVEL PLUGIN SCOPE GUARD (v1.0.249)
197
212
  # ==============================================================================
@@ -211,16 +226,21 @@ fi
211
226
  # 4. Uses a daily marker file to avoid running on every prompt
212
227
  #
213
228
  # Rate limit: runs at most once per day per project (marker in .specweave/state/)
214
- SCOPE_GUARD_MARKER="${SW_PROJECT_ROOT:-.}/.specweave/state/scope-guard.marker"
229
+ # GUARD: Only run if inside a valid SpecWeave project (prevents stale folder creation)
215
230
  SCOPE_GUARD_RUN=false
231
+ SCOPE_GUARD_MARKER=""
216
232
 
217
- if [[ -f "$SCOPE_GUARD_MARKER" ]]; then
218
- # Check if marker is from today (skip if already ran today)
219
- MARKER_DATE=$(cat "$SCOPE_GUARD_MARKER" 2>/dev/null)
220
- TODAY=$(date +%Y-%m-%d)
221
- [[ "$MARKER_DATE" != "$TODAY" ]] && SCOPE_GUARD_RUN=true
222
- else
223
- SCOPE_GUARD_RUN=true
233
+ if [[ -n "$SW_PROJECT_ROOT" ]]; then
234
+ SCOPE_GUARD_MARKER="$SW_PROJECT_ROOT/.specweave/state/scope-guard.marker"
235
+
236
+ if [[ -f "$SCOPE_GUARD_MARKER" ]]; then
237
+ # Check if marker is from today (skip if already ran today)
238
+ MARKER_DATE=$(cat "$SCOPE_GUARD_MARKER" 2>/dev/null)
239
+ TODAY=$(date +%Y-%m-%d)
240
+ [[ "$MARKER_DATE" != "$TODAY" ]] && SCOPE_GUARD_RUN=true
241
+ else
242
+ SCOPE_GUARD_RUN=true
243
+ fi
224
244
  fi
225
245
 
226
246
  if [[ "$SCOPE_GUARD_RUN" == "true" ]] && command -v jq >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
@@ -251,7 +271,7 @@ if [[ "$SCOPE_GUARD_RUN" == "true" ]] && command -v jq >/dev/null 2>&1 && comman
251
271
  done
252
272
 
253
273
  if [[ -n "$MIGRATED" ]]; then
254
- echo "[$(date -Iseconds)] scope-guard | migrated user→project: $MIGRATED" >> "${SW_PROJECT_ROOT:-.}/.specweave/state/hook.log" 2>/dev/null || true
274
+ echo "[$(date -Iseconds)] scope-guard | migrated user→project: $MIGRATED" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
255
275
  fi
256
276
 
257
277
  # CRITICAL FIX: Restore sw@specweave enabled state after uninstall operations
@@ -263,7 +283,7 @@ if [[ "$SCOPE_GUARD_RUN" == "true" ]] && command -v jq >/dev/null 2>&1 && comman
263
283
  # Re-enable core plugin (preserves all other settings)
264
284
  jq '.enabledPlugins."sw@specweave" = true' "$USER_SETTINGS" > "${USER_SETTINGS}.tmp" 2>/dev/null && \
265
285
  mv "${USER_SETTINGS}.tmp" "$USER_SETTINGS" 2>/dev/null || true
266
- echo "[$(date -Iseconds)] scope-guard | restored sw@specweave enabled state" >> "${SW_PROJECT_ROOT:-.}/.specweave/state/hook.log" 2>/dev/null || true
286
+ echo "[$(date -Iseconds)] scope-guard | restored sw@specweave enabled state" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
267
287
  fi
268
288
  fi
269
289
  fi
@@ -290,18 +310,7 @@ fi
290
310
  #
291
311
  # When both disabled: NO detection, NO LLM calls, fastest response time (~5-7s saved)
292
312
 
293
- # ==============================================================================
294
- # PROJECT ROOT DETECTION (walk up to find .specweave/ — prevents folder pollution)
295
- # ==============================================================================
296
- SW_PROJECT_ROOT=""
297
- _swdir="$PWD"
298
- while [[ "$_swdir" != "/" ]]; do
299
- if [[ -d "$_swdir/.specweave" ]]; then
300
- SW_PROJECT_ROOT="$_swdir"
301
- break
302
- fi
303
- _swdir=$(dirname "$_swdir")
304
- done
313
+ # PROJECT ROOT DETECTION was moved earlier (before scope guard) to prevent stale folder creation
305
314
 
306
315
  # Check config for pluginAutoLoad.enabled, suggestOnly and incrementAssist.enabled settings
307
316
  PLUGIN_AUTOLOAD_ENABLED=true
@@ -453,16 +462,17 @@ check_plugin_installed_from_json() {
453
462
  #
454
463
  # WHEN NOT TO CREATE INCREMENT (action: "none" ONLY):
455
464
  # ┌─────────────────────────────────────────────────────────────────────────────┐
456
- # │ • Questions: "how do I", "what is", "explain", "why does"
457
- # │ • Exploration: "show me", "find", "search for", "list"
458
- # │ • Commands: "run tests", "build", "deploy", "commit"
459
- # │ • Already in workflow: prompt starts with /sw:
460
- # │ • Chat/greetings: "hello", "thanks", general conversation
461
- # │ • EXPLICIT OPT-OUT:
462
- # │ - "don't create an increment", "no increment needed", "skip workflow"
463
- # │ - "just a quick fix", "without tracking", "no spec needed"
464
- # │ - "don't reopen", "leave it closed" (for completed increments)
465
- # │ - "I'll track it myself", "already in an increment"
465
+ # │ • Pure questions: "what is X?", "explain Y", "tell me about Z"
466
+ # │ • Exploration: "show me", "list", "search for"
467
+ # │ • Commands: "run tests", "build", "deploy", "commit"
468
+ # │ • Already in workflow: prompt starts with /sw:
469
+ # │ • Chat/greetings: "hello", "thanks", general conversation
470
+ # │ • EXPLICIT OPT-OUT: "don't create an increment", "skip workflow"
471
+ # │
472
+ # │ NOTE: These are NOT questions they are WORK requiring increments:
473
+ # │ "investigate", "debug", "troubleshoot", "why does X fail",
474
+ # │ "optimize", "improve", "secure", "audit", "solve", "resolve",
475
+ # │ "analyze", "root cause", "X is broken", "X keeps failing" │
466
476
  # └─────────────────────────────────────────────────────────────────────────────┘
467
477
  #
468
478
  # STILL SUGGEST INCREMENT (action: "small_fix"):
@@ -1004,13 +1014,21 @@ if [[ "${SPECWEAVE_DISABLE_AUTO_LOAD:-0}" != "1" ]] && [[ "${SPECWEAVE_DISABLE_H
1004
1014
 
1005
1015
  # Check if specweave CLI is available
1006
1016
  if command -v specweave >/dev/null 2>&1; then
1007
- # Setup logging
1008
- LAZY_LOAD_LOG="$HOME/.specweave/logs/lazy-loading.log"
1009
- mkdir -p "$(dirname "$LAZY_LOAD_LOG")" 2>/dev/null
1017
+ # Setup logging (use project root, never create dirs at $HOME)
1018
+ if [[ -n "$SW_PROJECT_ROOT" ]]; then
1019
+ LAZY_LOAD_LOG="$SW_PROJECT_ROOT/.specweave/logs/lazy-loading.log"
1020
+ else
1021
+ LAZY_LOAD_LOG="/dev/null"
1022
+ fi
1010
1023
 
1011
1024
  # Per-session cache to avoid redundant LLM calls (30 min TTL)
1012
- PROMPT_CACHE_DIR="$HOME/.specweave/state/prompt-cache"
1013
- mkdir -p "$PROMPT_CACHE_DIR" 2>/dev/null
1025
+ if [[ -n "$SW_PROJECT_ROOT" ]]; then
1026
+ PROMPT_CACHE_DIR="$SW_PROJECT_ROOT/.specweave/state/prompt-cache"
1027
+ mkdir -p "$PROMPT_CACHE_DIR" 2>/dev/null
1028
+ else
1029
+ PROMPT_CACHE_DIR="${TMPDIR:-/tmp}/specweave-prompt-cache"
1030
+ mkdir -p "$PROMPT_CACHE_DIR" 2>/dev/null
1031
+ fi
1014
1032
  PROMPT_HASH=$(echo "$PROMPT" | md5sum 2>/dev/null | cut -c1-16 || md5 -qs "$PROMPT" 2>/dev/null | cut -c1-16 || echo "nohash")
1015
1033
  CACHE_FILE="$PROMPT_CACHE_DIR/${PROMPT_HASH}.json"
1016
1034
 
@@ -1287,7 +1305,7 @@ Then spawn agent: \`Task({ subagent_type: \"${PRIMARY_PLUGIN}:${PRIMARY_SKILL_NA
1287
1305
  CMD="/sw:increment"
1288
1306
  [[ -n "$INC_NAME" ]] && CMD="/sw:increment \"$INC_NAME\""
1289
1307
 
1290
- # v1.0.169: Call sw:increment-planner DIRECTLY (not wrapper)
1308
+ # v1.0.169+: Call sw:increment skill directly
1291
1309
  # Pass user prompt so skill can extract context
1292
1310
  # INC_MANDATORY comes from detect-intent LLM response
1293
1311
  if [[ "$INC_MANDATORY" == "true" ]]; then
@@ -1300,14 +1318,14 @@ Then spawn agent: \`Task({ subagent_type: \"${PRIMARY_PLUGIN}:${PRIMARY_SKILL_NA
1300
1318
  DEEP_INTERVIEW_MSG=" Assess prompt completeness first — if gaps exist, ask 2-5 targeted questions before calling."
1301
1319
  fi
1302
1320
 
1303
- MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}SKILL FIRST: \`Skill({ skill: \"sw:increment-planner\" })\` — call BEFORE implementation.
1321
+ MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}SKILL FIRST: \`Skill({ skill: \"sw:increment\" })\` — call BEFORE implementation.
1304
1322
  Detection: ${INC_REASON} (confidence: ${INC_CONF}).${AGENT_DIRECTIVE}${DEEP_INTERVIEW_MSG}
1305
- After increment-planner, chain domain skills per tech stack (see CLAUDE.md Skill Chaining)."
1323
+ After increment, chain domain skills per tech stack (see CLAUDE.md Skill Chaining)."
1306
1324
  output_approve_with_context "$MSG"
1307
1325
  exit 0
1308
1326
  else
1309
1327
  # v1.0.260: Removed prompt embedding to save context budget
1310
- MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Increment suggested: \`Skill({ skill: \"sw:increment-planner\" })\` or \`$CMD\`. Reason: $INC_REASON${AGENT_DIRECTIVE}"
1328
+ MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Increment suggested: \`Skill({ skill: \"sw:increment\" })\` or \`$CMD\`. Reason: $INC_REASON${AGENT_DIRECTIVE}"
1311
1329
  output_approve_with_context "$MSG"
1312
1330
  exit 0
1313
1331
  fi
@@ -1315,7 +1333,7 @@ After increment-planner, chain domain skills per tech stack (see CLAUDE.md Skill
1315
1333
 
1316
1334
  hotfix)
1317
1335
  # v1.0.260: Removed prompt embedding to save context budget
1318
- MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Hotfix detected: \`Skill({ skill: \"sw:increment-planner\", args: \"--type=hotfix\" })\`. Reason: $INC_REASON"
1336
+ MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Hotfix detected: \`Skill({ skill: \"sw:increment\", args: \"--type=hotfix\" })\`. Reason: $INC_REASON"
1319
1337
  output_approve_with_context "$MSG"
1320
1338
  exit 0
1321
1339
  ;;
@@ -1333,7 +1351,7 @@ After increment-planner, chain domain skills per tech stack (see CLAUDE.md Skill
1333
1351
  CMD_SMALLFIX="/sw:increment"
1334
1352
  [[ -n "$INC_NAME" ]] && CMD_SMALLFIX="/sw:increment \"$INC_NAME\""
1335
1353
 
1336
- MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Small change — consider tracking: \`Skill({ skill: \"sw:increment-planner\" })\` or \`$CMD_SMALLFIX\`. Reason: $INC_REASON${AGENT_DIRECTIVE}"
1354
+ MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}Small change — consider tracking: \`Skill({ skill: \"sw:increment\" })\` or \`$CMD_SMALLFIX\`. Reason: $INC_REASON${AGENT_DIRECTIVE}"
1337
1355
  output_approve_with_context "$MSG"
1338
1356
  exit 0
1339
1357
  ;;
@@ -1449,23 +1467,52 @@ After increment-planner, chain domain skills per tech stack (see CLAUDE.md Skill
1449
1467
  # This fallback ONLY handles increment suggestions (not plugin installs).
1450
1468
  if [[ "$LLM_DETECTION_FAILED" == "true" && "$INCREMENT_ASSIST_ENABLED" == "true" ]]; then
1451
1469
  # Check for implementation-intent keywords
1452
- if echo "$PROMPT" | grep -qiE "(test|component|feature|fix|refactor|setup|configure|integrate|migrate|upgrade|write|style|design|add|create|implement|build|develop|deploy|scaffold|generate)"; then
1453
- # Exclude questions (starting with question words or ending with ?)
1454
- if ! echo "$PROMPT" | grep -qiE "^[[:space:]]*(what|how|why|explain|tell me|can you|does|should|is there|where|when|which)" && \
1470
+ # v1.0.261: Expanded from 20 to 65+ keywords across 9 categories:
1471
+ # Original, Investigation, Analysis, Problem-solving, Optimization,
1472
+ # Security, Documentation, DevOps/Data, Structural
1473
+ if echo "$PROMPT" | grep -qiE "(test|component|feature|fix|refactor|setup|configure|integrate|migrate|upgrade|write|style|design|add|create|implement|build|develop|deploy|scaffold|generate|investigate|debug|troubleshoot|diagnose|trace|profile|examine|inspect|reproduce|replicate|analyze|assess|audit|evaluate|benchmark|measure|validate|solve|resolve|address|tackle|determine|optimize|improve|reduce|minimize|eliminate|simplify|streamline|secure|harden|patch|sanitize|encrypt|document|provision|containerize|dockerize|seed|populate|import|export|transform|sync|batch|remove|delete|replace|convert|extract|merge|split|wrap|unwrap|decouple|modularize)"; then
1474
+ # Exclude PURE questions but NOT investigation/work prompts
1475
+ # v1.0.261: "why" and "how" removed — they almost always imply work intent
1476
+ # ("why does X fail" = investigation, "how do I fix X" = work)
1477
+ # Patterns made more specific to avoid false negatives
1478
+ if ! echo "$PROMPT" | grep -qiE "^[[:space:]]*(what is|what are|explain|tell me about|can you explain|does .* support|should I use|is there a|where is|when did|which one)" && \
1455
1479
  ! echo "$PROMPT" | grep -qE "\?[[:space:]]*$"; then
1456
1480
  # v1.0.260: Removed prompt embedding to save context budget
1457
1481
  if [[ "$INCREMENT_MANDATORY_CONFIG" == "true" ]]; then
1458
- FALLBACK_MSG="SKILL FIRST: \`Skill({ skill: \"sw:increment-planner\" })\` — call BEFORE implementation.
1482
+ FALLBACK_MSG="SKILL FIRST: \`Skill({ skill: \"sw:increment\" })\` — call BEFORE implementation.
1459
1483
  Detection: Implementation keywords detected (LLM unavailable, keyword fallback).
1460
- After increment-planner, chain domain skills per tech stack (see CLAUDE.md Skill Chaining)."
1484
+ After increment, chain domain skills per tech stack (see CLAUDE.md Skill Chaining)."
1461
1485
  else
1462
- FALLBACK_MSG="Increment suggested: \`Skill({ skill: \"sw:increment-planner\" })\`. Reason: Implementation keywords detected (LLM unavailable, keyword fallback)."
1486
+ FALLBACK_MSG="Increment suggested: \`Skill({ skill: \"sw:increment\" })\`. Reason: Implementation keywords detected (LLM unavailable, keyword fallback)."
1463
1487
  fi
1464
1488
  echo "[$(date -Iseconds)] keyword-fallback | prompt_keywords_matched=true | mandatory=$INCREMENT_MANDATORY_CONFIG" >> "$LAZY_LOAD_LOG"
1465
1489
  output_approve_with_context "$FALLBACK_MSG"
1466
1490
  exit 0
1467
1491
  fi
1468
1492
  fi
1493
+
1494
+ # ==================================================================
1495
+ # ERROR-STATE DETECTION: symptom-based prompts (v1.0.261)
1496
+ # ==================================================================
1497
+ # Catches prompts describing failure states without action verbs:
1498
+ # "the dashboard is broken", "login keeps failing", "app crashes on mobile"
1499
+ # Only runs when LLM failed AND primary keyword regex didn't match.
1500
+ if echo "$PROMPT" | grep -qiE "(is broken|keeps? failing|crash(es|ing)|hang(s|ing)|times? out|is slow|memory leak|performance issue|not working|throwing error|exception|stack trace|segfault|deadlock|race condition)"; then
1501
+ # Require 3+ words for context (not just bare "is broken")
1502
+ WORD_COUNT=$(echo "$PROMPT" | wc -w | tr -d ' ')
1503
+ if [[ "$WORD_COUNT" -ge 3 ]]; then
1504
+ if [[ "$INCREMENT_MANDATORY_CONFIG" == "true" ]]; then
1505
+ FALLBACK_MSG="SKILL FIRST: \`Skill({ skill: \"sw:increment\" })\` — call BEFORE implementation.
1506
+ Detection: Error/failure state detected (LLM unavailable, symptom fallback).
1507
+ After increment, chain domain skills per tech stack (see CLAUDE.md Skill Chaining)."
1508
+ else
1509
+ FALLBACK_MSG="Increment suggested: \`Skill({ skill: \"sw:increment\" })\`. Reason: Error/failure state detected (LLM unavailable, symptom fallback)."
1510
+ fi
1511
+ echo "[$(date -Iseconds)] symptom-fallback | prompt_symptom_matched=true | mandatory=$INCREMENT_MANDATORY_CONFIG" >> "$LAZY_LOAD_LOG"
1512
+ output_approve_with_context "$FALLBACK_MSG"
1513
+ exit 0
1514
+ fi
1515
+ fi
1469
1516
  fi
1470
1517
  fi
1471
1518
  fi
@@ -1565,10 +1612,11 @@ if [[ -n "$SW_PROJECT_ROOT" ]] && [[ -d "$SW_PROJECT_ROOT/.specweave" ]]; then
1565
1612
  AUTOLOAD_PLUGINS_MSG="$TDD_MSG"
1566
1613
  fi
1567
1614
 
1568
- # Log TDD activation
1569
- TDD_LOG="$HOME/.specweave/logs/tdd-enforcement.log"
1570
- mkdir -p "$(dirname "$TDD_LOG")" 2>/dev/null
1571
- echo "[$(date -Iseconds)] TDD_MODE=$TDD_MODE | enforcement=$TDD_ENFORCEMENT | source=$TDD_SOURCE" >> "$TDD_LOG" 2>/dev/null
1615
+ # Log TDD activation (use project root, never create dirs at $HOME)
1616
+ if [[ -n "$SW_PROJECT_ROOT" ]]; then
1617
+ TDD_LOG="$SW_PROJECT_ROOT/.specweave/logs/tdd-enforcement.log"
1618
+ echo "[$(date -Iseconds)] TDD_MODE=$TDD_MODE | enforcement=$TDD_ENFORCEMENT | source=$TDD_SOURCE" >> "$TDD_LOG" 2>/dev/null
1619
+ fi
1572
1620
  fi
1573
1621
  fi
1574
1622
 
@@ -1592,7 +1640,12 @@ fi
1592
1640
  # ==============================================================================
1593
1641
  # Even if prompt contains SpecWeave keywords, exit if no .specweave directory
1594
1642
  # v1.0.144: Still show plugin autoload message for non-SpecWeave projects
1595
- SPECWEAVE_DIR=".specweave"
1643
+ # Use SW_PROJECT_ROOT to avoid creating dirs relative to CWD
1644
+ if [[ -n "$SW_PROJECT_ROOT" ]]; then
1645
+ SPECWEAVE_DIR="$SW_PROJECT_ROOT/.specweave"
1646
+ else
1647
+ SPECWEAVE_DIR=".specweave"
1648
+ fi
1596
1649
  if [[ ! -d "$SPECWEAVE_DIR" ]]; then
1597
1650
  if [[ -n "$AUTOLOAD_PLUGINS_MSG" ]]; then
1598
1651
  # Show plugin loading feedback even for non-SpecWeave projects
@@ -1937,7 +1990,7 @@ if [[ "$DEEP_INTERVIEW_ENABLED" == "true" ]] && [[ -z "$ACTIVE_INCREMENT" ]]; th
1937
1990
  fi
1938
1991
 
1939
1992
  if [[ "$HAVE_ACTIVE_STATE" != "true" ]]; then
1940
- SMART_INTERVIEW_GATE_MSG="No active increment. Assess prompt completeness for complexity — if gaps, ask 2-5 targeted questions. If sufficient, call sw:increment-planner."
1993
+ SMART_INTERVIEW_GATE_MSG="No active increment. Assess prompt completeness for complexity — if gaps, ask 2-5 targeted questions. If sufficient, call sw:increment."
1941
1994
  fi
1942
1995
  fi
1943
1996
 
@@ -2174,7 +2227,60 @@ fi
2174
2227
  # P2 (important): LSP explicit request, WIP/interview gate
2175
2228
  # P3 (informational): LSP setup/install suggestions, archive suggestion
2176
2229
 
2177
- CONTEXT_BUDGET=2500 # Leave 500 chars headroom below 3000 max
2230
+ # ==============================================================================
2231
+ # CONTEXT BUDGET RESOLUTION (v1.0.262)
2232
+ # ==============================================================================
2233
+ # 1. Read base level from config.json (contextBudget.level)
2234
+ # 2. If autoAdapt=true, check context-pressure.json and step down
2235
+ # 3. Map level to char budget
2236
+ # 4. Environment override: SPECWEAVE_CONTEXT_BUDGET
2237
+
2238
+ BUDGET_LEVEL="full"
2239
+ AUTO_ADAPT=true
2240
+ if [[ -f "$CONFIG_PATH" ]] && command -v jq >/dev/null 2>&1; then
2241
+ BUDGET_LEVEL=$(jq -r '.contextBudget.level // "full"' "$CONFIG_PATH" 2>/dev/null || echo "full")
2242
+ AUTO_ADAPT_VAL=$(jq -r '.contextBudget.autoAdapt // true' "$CONFIG_PATH" 2>/dev/null || echo "true")
2243
+ [[ "$AUTO_ADAPT_VAL" == "false" ]] && AUTO_ADAPT=false
2244
+ fi
2245
+
2246
+ # Environment override (for quick testing without config changes)
2247
+ [[ -n "${SPECWEAVE_CONTEXT_BUDGET:-}" ]] && BUDGET_LEVEL="$SPECWEAVE_CONTEXT_BUDGET"
2248
+
2249
+ # Auto-adapt: check pressure state from PreCompact hook
2250
+ if [[ "$AUTO_ADAPT" == "true" ]] && [[ -n "$SW_PROJECT_ROOT" ]]; then
2251
+ PRESSURE_FILE="$SW_PROJECT_ROOT/.specweave/state/context-pressure.json"
2252
+ if [[ -f "$PRESSURE_FILE" ]] && command -v jq >/dev/null 2>&1; then
2253
+ PRESSURE_LEVEL=$(jq -r '.level // "normal"' "$PRESSURE_FILE" 2>/dev/null || echo "normal")
2254
+ case "$PRESSURE_LEVEL" in
2255
+ elevated)
2256
+ # Step down one level
2257
+ case "$BUDGET_LEVEL" in
2258
+ full) BUDGET_LEVEL="compact" ;;
2259
+ compact) BUDGET_LEVEL="minimal" ;;
2260
+ esac
2261
+ ;;
2262
+ critical)
2263
+ # Jump to minimal regardless of base
2264
+ [[ "$BUDGET_LEVEL" != "off" ]] && BUDGET_LEVEL="minimal"
2265
+ ;;
2266
+ esac
2267
+ fi
2268
+ fi
2269
+
2270
+ # Map level to char budget
2271
+ case "$BUDGET_LEVEL" in
2272
+ full) CONTEXT_BUDGET=2500 ;;
2273
+ compact) CONTEXT_BUDGET=1000 ;;
2274
+ minimal) CONTEXT_BUDGET=300 ;;
2275
+ off) CONTEXT_BUDGET=0 ;;
2276
+ *) CONTEXT_BUDGET=2500 ;;
2277
+ esac
2278
+
2279
+ # If budget is 0, skip all context assembly
2280
+ if [[ "$CONTEXT_BUDGET" -eq 0 ]]; then
2281
+ echo '{"decision":"approve"}'
2282
+ exit 0
2283
+ fi
2178
2284
 
2179
2285
  # Helper: Append message to FINAL_MESSAGE if it fits within budget
2180
2286
  # Args: $1=message to append
@@ -2208,6 +2314,34 @@ _budget_append "$LSP_INSTALL_MSG"
2208
2314
  _budget_append "$LSP_SETUP_SUGGESTION_MSG"
2209
2315
  _budget_append "$ARCHIVE_SUGGESTION_MSG"
2210
2316
 
2317
+ # ==============================================================================
2318
+ # TURN DEDUPLICATION: Skip identical context to save tokens (v1.0.262)
2319
+ # ==============================================================================
2320
+ # If this turn's context is identical to last turn's, don't re-inject it.
2321
+ # Claude already has it in history. Saves ~2500 chars per duplicate turn.
2322
+ if [[ -n "$FINAL_MESSAGE" ]] && [[ -n "$SW_PROJECT_ROOT" ]]; then
2323
+ DEDUP_HASH_FILE="$SW_PROJECT_ROOT/.specweave/state/.context-hash"
2324
+ CURRENT_HASH=""
2325
+ if command -v md5sum >/dev/null 2>&1; then
2326
+ CURRENT_HASH=$(printf '%s' "$FINAL_MESSAGE" | md5sum | cut -d' ' -f1)
2327
+ elif command -v md5 >/dev/null 2>&1; then
2328
+ CURRENT_HASH=$(printf '%s' "$FINAL_MESSAGE" | md5)
2329
+ fi
2330
+
2331
+ if [[ -n "$CURRENT_HASH" ]]; then
2332
+ if [[ -f "$DEDUP_HASH_FILE" ]]; then
2333
+ PREV_HASH=$(cat "$DEDUP_HASH_FILE" 2>/dev/null)
2334
+ if [[ "$CURRENT_HASH" == "$PREV_HASH" ]]; then
2335
+ # Identical to last turn — skip injection
2336
+ echo '{"decision":"approve"}'
2337
+ exit 0
2338
+ fi
2339
+ fi
2340
+ # Save hash for next turn
2341
+ echo "$CURRENT_HASH" > "$DEDUP_HASH_FILE" 2>/dev/null
2342
+ fi
2343
+ fi
2344
+
2211
2345
  if [[ -n "$FINAL_MESSAGE" ]]; then
2212
2346
  output_approve_with_context "$FINAL_MESSAGE"
2213
2347
  else
@@ -0,0 +1,83 @@
1
+ #!/bin/bash
2
+ # post-tool-use-analytics.sh - Track Skill and Task tool usage for analytics
3
+ #
4
+ # PostToolUse hook for Skill|Task tools (separate from Edit|Write dispatcher)
5
+ # Extracts skill/agent info from stdin JSON and calls track-analytics.sh
6
+ #
7
+ # CRITICAL: Must exit 0, never block, run <5s
8
+
9
+ set +e
10
+
11
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
12
+
13
+ # Project root detection
14
+ PROJECT_ROOT="$PWD"
15
+ while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
16
+ PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
17
+ done
18
+ [[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
19
+
20
+ # Read stdin (tool result JSON)
21
+ INPUT=""
22
+ if command -v gtimeout >/dev/null 2>&1; then
23
+ INPUT=$(gtimeout 1 cat 2>/dev/null || echo '{}')
24
+ elif command -v timeout >/dev/null 2>&1; then
25
+ INPUT=$(timeout 1 cat 2>/dev/null || echo '{}')
26
+ else
27
+ INPUT=$(cat 2>/dev/null || echo '{}')
28
+ fi
29
+
30
+ [[ -z "$INPUT" ]] && exit 0
31
+
32
+ # Script paths
33
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
34
+ PLUGIN_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
35
+ TRACK_SCRIPT="$PLUGIN_ROOT/scripts/track-analytics.sh"
36
+
37
+ [[ ! -f "$TRACK_SCRIPT" ]] && exit 0
38
+
39
+ # Detect active increment for context
40
+ ACTIVE_INCREMENT=""
41
+ CACHE_FILE="$PROJECT_ROOT/.specweave/state/status-line.json"
42
+ if [[ -f "$CACHE_FILE" ]] && command -v jq >/dev/null 2>&1; then
43
+ ACTIVE_INCREMENT=$(jq -r '.incrementId // ""' "$CACHE_FILE" 2>/dev/null || echo "")
44
+ fi
45
+
46
+ # Extract tool_name
47
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
48
+
49
+ # Fallback: detect tool type from input fields
50
+ if [[ -z "$TOOL_NAME" ]]; then
51
+ if echo "$INPUT" | grep -q '"skill"[[:space:]]*:'; then
52
+ TOOL_NAME="Skill"
53
+ elif echo "$INPUT" | grep -q '"subagent_type"[[:space:]]*:'; then
54
+ TOOL_NAME="Task"
55
+ fi
56
+ fi
57
+
58
+ case "$TOOL_NAME" in
59
+ Skill)
60
+ SKILL_NAME=$(echo "$INPUT" | grep -o '"skill"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
61
+ [[ -z "$SKILL_NAME" ]] && exit 0
62
+
63
+ # Extract plugin from skill name prefix
64
+ PLUGIN="specweave"
65
+ if [[ "$SKILL_NAME" == *:* ]]; then
66
+ PREFIX="${SKILL_NAME%%:*}"
67
+ if [[ "$PREFIX" != "sw" ]]; then
68
+ PLUGIN="$PREFIX"
69
+ fi
70
+ fi
71
+
72
+ bash "$TRACK_SCRIPT" skill "$SKILL_NAME" --plugin "$PLUGIN" --success --increment "$ACTIVE_INCREMENT" 2>/dev/null &
73
+ ;;
74
+
75
+ Task)
76
+ AGENT_TYPE=$(echo "$INPUT" | grep -o '"subagent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
77
+ [[ -z "$AGENT_TYPE" ]] && AGENT_TYPE="general"
78
+
79
+ bash "$TRACK_SCRIPT" agent "$AGENT_TYPE" --plugin specweave --success --increment "$ACTIVE_INCREMENT" 2>/dev/null &
80
+ ;;
81
+ esac
82
+
83
+ exit 0
@@ -39,6 +39,13 @@ if [[ -f "$AUTO_MODE_FILE" ]]; then
39
39
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] SessionStart: Cleared auto-mode session files (session-scoped)" >> "$PROJECT_ROOT/.specweave/logs/session.log" 2>/dev/null
40
40
  fi
41
41
 
42
+ # ============================================================================
43
+ # CONTEXT PRESSURE RESET: Clear pressure state for fresh session budget (v1.0.262)
44
+ # Ensures new sessions start at user's configured base budget level
45
+ # ============================================================================
46
+ rm -f "$STATE_DIR/context-pressure.json" 2>/dev/null
47
+ rm -f "$STATE_DIR/.context-hash" 2>/dev/null
48
+
42
49
  # ============================================================================
43
50
  # PLUGIN CACHE: Do NOT delete - Claude Code manages its own plugin cache
44
51
  # v1.0.206: (REVERTED) Cache deletion broke plugin loading - installed_plugins.json
@@ -9,7 +9,7 @@
9
9
  # user stories directly to spec.md without using PM expertise.
10
10
  #
11
11
  # WORKFLOW ENFORCED:
12
- # 1. increment-planner creates TEMPLATE via createIncrementTemplates() API
12
+ # 1. increment skill creates TEMPLATE via createIncrementTemplates() API
13
13
  # 2. Templates have markers like [Story Title], [user type], etc.
14
14
  # 3. User invokes PM skill to complete the template
15
15
  # 4. Direct full-content writes are BLOCKED
@@ -90,9 +90,27 @@ if [[ ! -f "$CONFIG_PATH" ]] || ! command -v jq >/dev/null 2>&1; then
90
90
  exit 0
91
91
  fi
92
92
 
93
- GH_ENABLED=$(jq -r '.sync.github.enabled // false' "$CONFIG_PATH" 2>/dev/null)
94
- JIRA_ENABLED=$(jq -r '.sync.jira.enabled // false' "$CONFIG_PATH" 2>/dev/null)
95
- ADO_ENABLED=$(jq -r '.sync.ado.enabled // false' "$CONFIG_PATH" 2>/dev/null)
93
+ # Use shared provider detection (supports PROFILES, LEGACY DIRECT, LEGACY PROVIDER formats)
94
+ HANDLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
95
+ SHARED_LIB="$HANDLER_DIR/../lib/check-provider-enabled.sh"
96
+ if [[ -f "$SHARED_LIB" ]]; then
97
+ source "$SHARED_LIB"
98
+ fi
99
+
100
+ GH_ENABLED="false"
101
+ JIRA_ENABLED="false"
102
+ ADO_ENABLED="false"
103
+
104
+ if type check_provider_enabled &>/dev/null; then
105
+ check_provider_enabled "$CONFIG_PATH" "github" && GH_ENABLED="true"
106
+ check_provider_enabled "$CONFIG_PATH" "jira" && JIRA_ENABLED="true"
107
+ check_provider_enabled "$CONFIG_PATH" "ado" && ADO_ENABLED="true"
108
+ else
109
+ # Fallback to legacy jq check if shared lib not available
110
+ GH_ENABLED=$(jq -r '.sync.github.enabled // false' "$CONFIG_PATH" 2>/dev/null)
111
+ JIRA_ENABLED=$(jq -r '.sync.jira.enabled // false' "$CONFIG_PATH" 2>/dev/null)
112
+ ADO_ENABLED=$(jq -r '.sync.ado.enabled // false' "$CONFIG_PATH" 2>/dev/null)
113
+ fi
96
114
 
97
115
  if [[ "$GH_ENABLED" != "true" && "$JIRA_ENABLED" != "true" && "$ADO_ENABLED" != "true" ]]; then
98
116
  log "No providers enabled. Skipping."
@@ -179,15 +197,16 @@ fi
179
197
  # ============================================================================
180
198
 
181
199
  # Get all US IDs that have external links from metadata.json
200
+ # Reads BOTH new format (externalLinks.github.issues) and old format (github.issues[].userStory)
182
201
  AFFECTED_US_IDS="[]"
183
202
  if [[ -f "$METADATA_PATH" ]]; then
184
- # Collect US IDs from all provider external links
185
203
  AFFECTED_US_IDS=$(jq -r '
186
204
  [
187
205
  (.externalLinks.github.issues // {} | keys[]),
188
206
  (.externalLinks.jira.userStories // {} | keys[]),
189
- (.externalLinks.ado.userStories // {} | keys[])
190
- ] | unique
207
+ (.externalLinks.ado.userStories // {} | keys[]),
208
+ (.github.issues // [] | .[].userStory // empty)
209
+ ] | flatten | unique
191
210
  ' "$METADATA_PATH" 2>/dev/null) || AFFECTED_US_IDS="[]"
192
211
  fi
193
212
 
@@ -90,9 +90,27 @@ rm -f "$DEBOUNCE_FILE" 2>/dev/null || true
90
90
 
91
91
  command -v jq >/dev/null 2>&1 || exit 0
92
92
 
93
- GH_ENABLED=$(jq -r '.sync.github.enabled // false' "$CONFIG_PATH" 2>/dev/null)
94
- JIRA_ENABLED=$(jq -r '.sync.jira.enabled // false' "$CONFIG_PATH" 2>/dev/null)
95
- ADO_ENABLED=$(jq -r '.sync.ado.enabled // false' "$CONFIG_PATH" 2>/dev/null)
93
+ # Use shared provider detection (supports PROFILES, LEGACY DIRECT, LEGACY PROVIDER formats)
94
+ HANDLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
95
+ SHARED_LIB="$HANDLER_DIR/../lib/check-provider-enabled.sh"
96
+ if [[ -f "$SHARED_LIB" ]]; then
97
+ source "$SHARED_LIB"
98
+ fi
99
+
100
+ GH_ENABLED="false"
101
+ JIRA_ENABLED="false"
102
+ ADO_ENABLED="false"
103
+
104
+ if type check_provider_enabled &>/dev/null; then
105
+ check_provider_enabled "$CONFIG_PATH" "github" && GH_ENABLED="true"
106
+ check_provider_enabled "$CONFIG_PATH" "jira" && JIRA_ENABLED="true"
107
+ check_provider_enabled "$CONFIG_PATH" "ado" && ADO_ENABLED="true"
108
+ else
109
+ # Fallback to legacy jq check if shared lib not available
110
+ GH_ENABLED=$(jq -r '.sync.github.enabled // false' "$CONFIG_PATH" 2>/dev/null)
111
+ JIRA_ENABLED=$(jq -r '.sync.jira.enabled // false' "$CONFIG_PATH" 2>/dev/null)
112
+ ADO_ENABLED=$(jq -r '.sync.ado.enabled // false' "$CONFIG_PATH" 2>/dev/null)
113
+ fi
96
114
 
97
115
  if [[ "$GH_ENABLED" != "true" && "$JIRA_ENABLED" != "true" && "$ADO_ENABLED" != "true" ]]; then
98
116
  log "No providers enabled. Skipping."
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # check-provider-enabled.sh - Shared provider config detection for SpecWeave hooks
3
+ #
4
+ # Supports ALL 3 config formats:
5
+ # 1. PROFILES: sync.profiles.*.provider == "github" + canUpdateExternalItems
6
+ # 2. LEGACY DIRECT: sync.github.enabled == true
7
+ # 3. LEGACY PROVIDER: sync.provider == "github" + sync.enabled == true
8
+ #
9
+ # Usage:
10
+ # source "path/to/check-provider-enabled.sh"
11
+ # if check_provider_enabled "$CONFIG_PATH" "github"; then
12
+ # echo "GitHub enabled"
13
+ # fi
14
+ #
15
+ # Returns: 0 = enabled, 1 = disabled
16
+ # Reference: github-sync-handler.sh:72-107 (canonical implementation)
17
+
18
+ check_provider_enabled() {
19
+ local config_file="$1"
20
+ local provider="$2"
21
+
22
+ [[ ! -f "$config_file" ]] && return 1
23
+
24
+ # Method 1: PROFILES format (sync.profiles with provider field)
25
+ if grep -q '"profiles"[[:space:]]*:' "$config_file" 2>/dev/null; then
26
+ if grep -q "\"provider\"[[:space:]]*:[[:space:]]*\"$provider\"" "$config_file" 2>/dev/null; then
27
+ # Also check canUpdateExternalItems (required for sync)
28
+ if grep -q '"canUpdateExternalItems"[[:space:]]*:[[:space:]]*true' "$config_file" 2>/dev/null; then
29
+ return 0
30
+ fi
31
+ fi
32
+ fi
33
+
34
+ # Method 2: LEGACY DIRECT (sync.{provider}.enabled: true)
35
+ if grep -q "\"$provider\"[[:space:]]*:" "$config_file" 2>/dev/null; then
36
+ if grep -A5 "\"$provider\"[[:space:]]*:" "$config_file" 2>/dev/null | grep -q '"enabled"[[:space:]]*:[[:space:]]*true'; then
37
+ return 0
38
+ fi
39
+ fi
40
+
41
+ # Method 3: LEGACY PROVIDER (sync.provider: "provider" + sync.enabled: true)
42
+ # Only check if no profiles section (pure legacy config)
43
+ if ! grep -q '"profiles"[[:space:]]*:' "$config_file" 2>/dev/null; then
44
+ if grep -q "\"provider\"[[:space:]]*:[[:space:]]*\"$provider\"" "$config_file" 2>/dev/null; then
45
+ if grep -q '"sync"' "$config_file" && grep -A2 '"sync"' "$config_file" | grep -q '"enabled"[[:space:]]*:[[:space:]]*true'; then
46
+ return 0
47
+ fi
48
+ fi
49
+ fi
50
+
51
+ return 1
52
+ }