specweave 0.30.19 → 0.32.2
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 +176 -2
- package/README.md +22 -0
- package/bin/specweave.js +18 -1
- package/dist/src/cli/commands/cache.d.ts +17 -0
- package/dist/src/cli/commands/cache.d.ts.map +1 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/cache.js.map +1 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/plan/increment-detector.js +2 -2
- package/dist/src/cli/commands/plan/increment-detector.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js.map +1 -1
- package/dist/src/cli/commands/sync-specs.js +2 -2
- package/dist/src/cli/commands/sync-specs.js.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
- package/dist/src/cli/workers/living-docs-worker.js +66 -1
- package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.js +5 -2
- package/dist/src/core/discrepancy/increment-generator.js.map +1 -1
- package/dist/src/core/external-tools/external-items-counter.d.ts +62 -0
- package/dist/src/core/external-tools/external-items-counter.d.ts.map +1 -0
- package/dist/src/core/external-tools/external-items-counter.js +206 -0
- package/dist/src/core/external-tools/external-items-counter.js.map +1 -0
- package/dist/src/core/external-tools/external-items-display.d.ts +39 -0
- package/dist/src/core/external-tools/external-items-display.d.ts.map +1 -0
- package/dist/src/core/external-tools/external-items-display.js +185 -0
- package/dist/src/core/external-tools/external-items-display.js.map +1 -0
- package/dist/src/core/external-tools/index.d.ts +8 -0
- package/dist/src/core/external-tools/index.d.ts.map +1 -0
- package/dist/src/core/external-tools/index.js +8 -0
- package/dist/src/core/external-tools/index.js.map +1 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.d.ts +39 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.js +188 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.d.ts +38 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.js +136 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/providers/index.d.ts +7 -0
- package/dist/src/core/external-tools/providers/index.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/index.js +7 -0
- package/dist/src/core/external-tools/providers/index.js.map +1 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.d.ts +42 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.js +153 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/types.d.ts +78 -0
- package/dist/src/core/external-tools/types.d.ts.map +1 -0
- package/dist/src/core/external-tools/types.js +19 -0
- package/dist/src/core/external-tools/types.js.map +1 -0
- package/dist/src/core/increment/duplicate-detector.js +2 -2
- package/dist/src/core/increment/duplicate-detector.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +24 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +59 -2
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-status.js +2 -2
- package/dist/src/core/increment/increment-status.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +98 -37
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +119 -68
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/metadata-validator.js +1 -1
- package/dist/src/core/increment/metadata-validator.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +4 -0
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +247 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts +15 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +138 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts +24 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js +198 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts +17 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js +241 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts +28 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js +197 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +154 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts +42 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js +343 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +146 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js +7 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +5 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +36 -2
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.js +1 -0
- package/dist/src/core/llm/providers/azure-openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +2 -0
- package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.js +1 -0
- package/dist/src/core/llm/providers/openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.js +1 -0
- package/dist/src/core/llm/providers/vertex-ai-provider.js.map +1 -1
- package/dist/src/core/sync/spec-increment-mapper.js +3 -3
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +25 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +135 -5
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/types/dashboard-cache.d.ts +181 -0
- package/dist/src/types/dashboard-cache.d.ts.map +1 -0
- package/dist/src/types/dashboard-cache.js +65 -0
- package/dist/src/types/dashboard-cache.js.map +1 -0
- package/dist/src/utils/docs-validator.d.ts +131 -0
- package/dist/src/utils/docs-validator.d.ts.map +1 -0
- package/dist/src/utils/docs-validator.js +529 -0
- package/dist/src/utils/docs-validator.js.map +1 -0
- package/dist/src/utils/feature-id-collision.js +1 -1
- package/dist/src/utils/feature-id-collision.js.map +1 -1
- package/dist/src/utils/html-to-mdx.d.ts +1 -0
- package/dist/src/utils/html-to-mdx.d.ts.map +1 -1
- package/dist/src/utils/html-to-mdx.js +43 -5
- package/dist/src/utils/html-to-mdx.js.map +1 -1
- package/package.json +1 -5
- package/plugins/specweave/agents/pm/AGENT.md +10 -7
- package/plugins/specweave/commands/specweave-archive-features.md +5 -7
- package/plugins/specweave/commands/specweave-archive.md +2 -1
- package/plugins/specweave/commands/specweave-do.md +35 -1
- package/plugins/specweave/commands/specweave-done.md +96 -0
- package/plugins/specweave/commands/specweave-external.md +150 -0
- package/plugins/specweave/commands/specweave-import-external.md +45 -18
- package/plugins/specweave/commands/specweave-increment.md +331 -33
- package/plugins/specweave/commands/specweave-jobs.md +2 -2
- package/plugins/specweave/commands/specweave-progress.md +4 -4
- package/plugins/specweave/commands/specweave-restore-feature.md +5 -4
- package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
- package/plugins/specweave/commands/specweave-sync-specs.md +216 -322
- package/plugins/specweave/commands/specweave-validate-features.md +13 -8
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +33 -4
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/lib/common-setup.sh +375 -0
- package/plugins/specweave/hooks/lib/crash-prevention.sh +336 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +4 -23
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -6
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +8 -37
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +2 -11
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/universal/dispatcher.mjs +135 -42
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +183 -0
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +140 -38
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +12 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +89 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +211 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +163 -0
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +26 -28
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +50 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +2 -2
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -1
- package/plugins/specweave/scripts/README.md +166 -0
- package/plugins/specweave/scripts/cleanup-state.sh +142 -0
- package/plugins/specweave/scripts/force-kill.sh +142 -0
- package/plugins/specweave/scripts/jobs.js +171 -0
- package/plugins/specweave/scripts/progress.js +170 -0
- package/plugins/specweave/scripts/read-costs.sh +132 -0
- package/plugins/specweave/scripts/read-jobs.sh +324 -0
- package/plugins/specweave/scripts/read-progress.sh +185 -0
- package/plugins/specweave/scripts/read-status.sh +146 -0
- package/plugins/specweave/scripts/read-workflow.sh +173 -0
- package/plugins/specweave/scripts/rebuild-dashboard-cache.sh +327 -0
- package/plugins/specweave/scripts/session-watchdog.sh +192 -0
- package/plugins/specweave/scripts/status.js +154 -0
- package/plugins/specweave/scripts/update-dashboard-cache.sh +281 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +333 -24
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +17 -9
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +6 -2
- package/plugins/specweave/skills/instant-status/SKILL.md +70 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-docs/commands/build.md +32 -4
- package/plugins/specweave-docs/commands/preview.md +43 -1
- package/plugins/specweave-docs/commands/validate.md +250 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -626
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -939
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +0 -265
- package/plugins/specweave/hooks/post-write-spec.sh +0 -267
- package/plugins/specweave/hooks/pre-edit-spec.sh +0 -151
- package/plugins/specweave/hooks/pre-write-spec.sh +0 -151
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: specweave:validate-features
|
|
3
|
-
description: Validate feature folder consistency
|
|
3
|
+
description: Validate feature folder consistency across project folders. Detects orphaned features, missing FEATURE.md, and auto-repairs discrepancies. Use for periodic health checks of living docs structure.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Validate Feature Folder Consistency
|
|
7
7
|
|
|
8
|
-
Validates that
|
|
8
|
+
Validates that feature folders in project directories (e.g., `specweave/FS-XXX/`) have proper structure.
|
|
9
|
+
|
|
10
|
+
**Note (v5.0.0+)**: The `_features/` folder is OBSOLETE. Features live in `{project}/FS-XXX/`.
|
|
11
|
+
If you find features in `_features/`, they should be migrated to the correct project folder.
|
|
9
12
|
|
|
10
13
|
---
|
|
11
14
|
|
|
@@ -120,9 +123,9 @@ Manual intervention required for:
|
|
|
120
123
|
{list orphaned features}
|
|
121
124
|
|
|
122
125
|
Options:
|
|
123
|
-
1. Delete orphaned
|
|
126
|
+
1. Delete orphaned folder if no longer needed
|
|
124
127
|
2. Re-sync from increment if increment still exists
|
|
125
|
-
3.
|
|
128
|
+
3. Move to correct project folder manually
|
|
126
129
|
{/if}
|
|
127
130
|
```
|
|
128
131
|
|
|
@@ -147,8 +150,8 @@ Discrepancies found: 1
|
|
|
147
150
|
⚠️ DISCREPANCIES FOUND
|
|
148
151
|
|
|
149
152
|
Feature: FS-062
|
|
150
|
-
Type:
|
|
151
|
-
Description: Feature FS-062 exists
|
|
153
|
+
Type: missing_feature_md
|
|
154
|
+
Description: Feature FS-062 folder exists but missing FEATURE.md
|
|
152
155
|
Auto-repairable: Yes
|
|
153
156
|
Linked increment: 0062-test-living-docs-auto-sync (not found)
|
|
154
157
|
|
|
@@ -184,15 +187,17 @@ Discrepancies found: 1
|
|
|
184
187
|
## WHEN TO USE
|
|
185
188
|
|
|
186
189
|
**Use this command when**:
|
|
187
|
-
-
|
|
190
|
+
- Feature folders are missing FEATURE.md or us-*.md files
|
|
188
191
|
- After interrupted/failed sync operations
|
|
189
192
|
- After manual cleanup of increments
|
|
190
193
|
- Periodic health check of living docs structure
|
|
194
|
+
- Legacy migration from `_features/` to `{project}/` folders
|
|
191
195
|
|
|
192
196
|
**Root cause of discrepancies**:
|
|
193
|
-
1. Sync interrupted
|
|
197
|
+
1. Sync interrupted during feature creation
|
|
194
198
|
2. Increment deleted without cleaning up living docs
|
|
195
199
|
3. Manual editing of living docs structure
|
|
200
|
+
4. Legacy `_features/` folders not yet migrated (v5.0.0+)
|
|
196
201
|
|
|
197
202
|
---
|
|
198
203
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SpecWeave Docs-Changed Hook
|
|
4
|
+
# Runs after file changes are detected
|
|
5
|
+
# Detects if documentation was changed during implementation
|
|
6
|
+
# Triggers review workflow if needed
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
# Find project root by searching upward for .specweave/ directory
|
|
11
|
+
# Works regardless of where hook is installed (source or .claude/hooks/)
|
|
12
|
+
find_project_root() {
|
|
13
|
+
local dir="$1"
|
|
14
|
+
while [ "$dir" != "/" ]; do
|
|
15
|
+
if [ -d "$dir/.specweave" ]; then
|
|
16
|
+
echo "$dir"
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
dir="$(dirname "$dir")"
|
|
20
|
+
done
|
|
21
|
+
# Fallback: try current directory
|
|
22
|
+
if [ -d "$(pwd)/.specweave" ]; then
|
|
23
|
+
pwd
|
|
24
|
+
else
|
|
25
|
+
echo "$(pwd)"
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
30
|
+
cd "$PROJECT_ROOT"
|
|
31
|
+
|
|
32
|
+
# Colors
|
|
33
|
+
RED='\033[0;31m'
|
|
34
|
+
YELLOW='\033[1;33m'
|
|
35
|
+
NC='\033[0m'
|
|
36
|
+
|
|
37
|
+
# Get changed files (git)
|
|
38
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
39
|
+
# Not a git repository, skip
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
CHANGED_FILES=$(git diff --name-only HEAD 2>/dev/null || echo "")
|
|
44
|
+
|
|
45
|
+
if [ -z "$CHANGED_FILES" ]; then
|
|
46
|
+
# No changes
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Check if any documentation files changed
|
|
51
|
+
DOC_CHANGES=$(echo "$CHANGED_FILES" | grep -E '\.specweave/(docs|increments/.*/.*\.md)' || true)
|
|
52
|
+
|
|
53
|
+
if [ -n "$DOC_CHANGES" ]; then
|
|
54
|
+
echo -e "${RED}⚠️ Documentation changed during implementation${NC}"
|
|
55
|
+
echo ""
|
|
56
|
+
echo "📋 Files changed:"
|
|
57
|
+
echo "$DOC_CHANGES" | sed 's/^/ /'
|
|
58
|
+
echo ""
|
|
59
|
+
echo -e "${YELLOW}🔔 Recommended actions:${NC}"
|
|
60
|
+
echo " 1. Review documentation changes"
|
|
61
|
+
echo " 2. Update tasks.md if architecture changed"
|
|
62
|
+
echo " 3. Type /review-docs to see full impact"
|
|
63
|
+
echo ""
|
|
64
|
+
|
|
65
|
+
# Play notification sound
|
|
66
|
+
case "$(uname -s)" in
|
|
67
|
+
Darwin)
|
|
68
|
+
afplay /System/Library/Sounds/Ping.aiff 2>/dev/null &
|
|
69
|
+
;;
|
|
70
|
+
Linux)
|
|
71
|
+
paplay /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null || true
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
# Log to hooks log
|
|
76
|
+
LOGS_DIR=".specweave/logs"
|
|
77
|
+
mkdir -p "$LOGS_DIR"
|
|
78
|
+
echo "[$(date)] Documentation changed: $DOC_CHANGES" >> "$LOGS_DIR/hooks.log"
|
|
79
|
+
fi
|
|
@@ -5,19 +5,38 @@
|
|
|
5
5
|
"hooks": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"command": "bash -c '
|
|
8
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/dispatchers/session-start.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"continue\\\":true}\")'"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"UserPromptSubmit": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt-submit.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"approve\\\"}\")'"
|
|
9
19
|
}
|
|
10
20
|
]
|
|
11
21
|
}
|
|
12
22
|
],
|
|
13
23
|
"PreToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"matcher": "Bash",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/bash-file-guard.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
14
33
|
{
|
|
15
34
|
"matcher": "Edit|Write",
|
|
16
35
|
"matcher_content": "metadata\\.json.*completed",
|
|
17
36
|
"hooks": [
|
|
18
37
|
{
|
|
19
38
|
"type": "command",
|
|
20
|
-
"command": "bash -c '
|
|
39
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/completion-guard.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
21
40
|
}
|
|
22
41
|
]
|
|
23
42
|
},
|
|
@@ -27,7 +46,17 @@
|
|
|
27
46
|
"hooks": [
|
|
28
47
|
{
|
|
29
48
|
"type": "command",
|
|
30
|
-
"command": "bash -c '
|
|
49
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/spec-project-validator.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"matcher": "Write|Edit",
|
|
55
|
+
"matcher_content": "\\.specweave/docs/internal/specs/_features/",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/features-folder-guard.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
31
60
|
}
|
|
32
61
|
]
|
|
33
62
|
}
|
|
@@ -39,7 +68,7 @@
|
|
|
39
68
|
"hooks": [
|
|
40
69
|
{
|
|
41
70
|
"type": "command",
|
|
42
|
-
"command": "bash -c '
|
|
71
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/dispatchers/post-tool-use.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"continue\\\":true}\")'"
|
|
43
72
|
}
|
|
44
73
|
]
|
|
45
74
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SpecWeave Human-Input-Required Hook
|
|
4
|
+
# Runs when Claude needs clarification or approval
|
|
5
|
+
#
|
|
6
|
+
# Actions:
|
|
7
|
+
# 1. Play notification sound (Ping.aiff)
|
|
8
|
+
# 2. Log the question/requirement
|
|
9
|
+
# 3. Record in current increment's work log (if applicable)
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
# Find project root by searching upward for .specweave/ directory
|
|
14
|
+
# Works regardless of where hook is installed (source or .claude/hooks/)
|
|
15
|
+
find_project_root() {
|
|
16
|
+
local dir="$1"
|
|
17
|
+
while [ "$dir" != "/" ]; do
|
|
18
|
+
if [ -d "$dir/.specweave" ]; then
|
|
19
|
+
echo "$dir"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
dir="$(dirname "$dir")"
|
|
23
|
+
done
|
|
24
|
+
# Fallback: try current directory
|
|
25
|
+
if [ -d "$(pwd)/.specweave" ]; then
|
|
26
|
+
pwd
|
|
27
|
+
else
|
|
28
|
+
echo "$(pwd)"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
33
|
+
cd "$PROJECT_ROOT"
|
|
34
|
+
|
|
35
|
+
# Get question/requirement (passed as argument or default)
|
|
36
|
+
QUESTION="${1:-User input required}"
|
|
37
|
+
|
|
38
|
+
echo "❓ Human input required"
|
|
39
|
+
|
|
40
|
+
# 1. Play notification sound (cross-platform)
|
|
41
|
+
play_sound() {
|
|
42
|
+
case "$(uname -s)" in
|
|
43
|
+
Darwin)
|
|
44
|
+
afplay /System/Library/Sounds/Ping.aiff 2>/dev/null &
|
|
45
|
+
;;
|
|
46
|
+
Linux)
|
|
47
|
+
paplay /usr/share/sounds/freedesktop/stereo/dialog-question.oga 2>/dev/null || \
|
|
48
|
+
aplay /usr/share/sounds/alsa/Side_Left.wav 2>/dev/null || true
|
|
49
|
+
;;
|
|
50
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
51
|
+
powershell -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\notify.wav').PlaySync();" 2>/dev/null || true
|
|
52
|
+
;;
|
|
53
|
+
esac
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
play_sound &
|
|
57
|
+
|
|
58
|
+
# 2. Log to main hooks log
|
|
59
|
+
LOGS_DIR=".specweave/logs"
|
|
60
|
+
mkdir -p "$LOGS_DIR"
|
|
61
|
+
echo "[$(date)] Human input required: $QUESTION" >> "$LOGS_DIR/hooks.log"
|
|
62
|
+
|
|
63
|
+
# 3. Log to current work context (if exists)
|
|
64
|
+
CURRENT_WORK=$(find .specweave/work -maxdepth 1 -type d -name "current-*" | head -1 || true)
|
|
65
|
+
|
|
66
|
+
if [ -n "$CURRENT_WORK" ] && [ -d "$CURRENT_WORK" ]; then
|
|
67
|
+
echo "" >> "$CURRENT_WORK/notes.md"
|
|
68
|
+
echo "## Input Required ($(date +%Y-%m-%d\ %H:%M))" >> "$CURRENT_WORK/notes.md"
|
|
69
|
+
echo "" >> "$CURRENT_WORK/notes.md"
|
|
70
|
+
echo "$QUESTION" >> "$CURRENT_WORK/notes.md"
|
|
71
|
+
echo "" >> "$CURRENT_WORK/notes.md"
|
|
72
|
+
echo "📝 Logged to $CURRENT_WORK/notes.md"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
echo "✅ Hook complete"
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# common-setup.sh - Unified Hook Library for Crash Prevention
|
|
3
|
+
#
|
|
4
|
+
# This library consolidates ALL common patterns from 19+ hooks into
|
|
5
|
+
# a single source of truth. Extracted from ADRs: 0060, 0068, 0073,
|
|
6
|
+
# 0128, 0130, 0157, 0189.
|
|
7
|
+
#
|
|
8
|
+
# USAGE:
|
|
9
|
+
# source "${CLAUDE_PLUGIN_ROOT}/hooks/lib/common-setup.sh"
|
|
10
|
+
# setup_hook_environment || exit 0
|
|
11
|
+
#
|
|
12
|
+
# v0.33.0 - Consolidated from 7 ADRs (2,500+ lines → 200 lines)
|
|
13
|
+
|
|
14
|
+
set +e # NEVER use set -e in hooks (ADR-0157 Rule 2)
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# CONFIGURATION
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
export HOOK_VERSION="0.33.0"
|
|
21
|
+
export HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}"
|
|
22
|
+
export HOOK_DEBUG="${HOOK_DEBUG:-0}"
|
|
23
|
+
|
|
24
|
+
# State directories (lazy-created)
|
|
25
|
+
_STATE_DIR=""
|
|
26
|
+
_CACHE_DIR=""
|
|
27
|
+
_LOG_DIR=""
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# 1. PROJECT ROOT DETECTION (ADR-0068, ADR-0128)
|
|
31
|
+
# ============================================================================
|
|
32
|
+
|
|
33
|
+
find_project_root() {
|
|
34
|
+
local dir="${1:-$(pwd)}"
|
|
35
|
+
while [[ "$dir" != "/" ]]; do
|
|
36
|
+
if [[ -d "$dir/.specweave" ]]; then
|
|
37
|
+
echo "$dir"
|
|
38
|
+
return 0
|
|
39
|
+
fi
|
|
40
|
+
dir=$(dirname "$dir")
|
|
41
|
+
done
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ============================================================================
|
|
46
|
+
# 2. EMERGENCY KILL SWITCH (ADR-0068 Layer 1, ADR-0157 Rule 4)
|
|
47
|
+
# ============================================================================
|
|
48
|
+
|
|
49
|
+
check_kill_switch() {
|
|
50
|
+
if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
|
|
51
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Kill switch active" >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# ============================================================================
|
|
58
|
+
# 3. CIRCUIT BREAKER (ADR-0068 Layer 2)
|
|
59
|
+
# ============================================================================
|
|
60
|
+
|
|
61
|
+
# Circuit breaker state (3 failures = trip)
|
|
62
|
+
_CIRCUIT_BREAKER_FILE=""
|
|
63
|
+
_CIRCUIT_BREAKER_THRESHOLD=3
|
|
64
|
+
|
|
65
|
+
init_circuit_breaker() {
|
|
66
|
+
local project_root="$1"
|
|
67
|
+
_CIRCUIT_BREAKER_FILE="${project_root}/.specweave/state/.hook-circuit-breaker"
|
|
68
|
+
mkdir -p "$(dirname "$_CIRCUIT_BREAKER_FILE")" 2>/dev/null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
check_circuit_breaker() {
|
|
72
|
+
[[ -z "$_CIRCUIT_BREAKER_FILE" ]] && return 0
|
|
73
|
+
|
|
74
|
+
if [[ -f "$_CIRCUIT_BREAKER_FILE" ]]; then
|
|
75
|
+
local failures
|
|
76
|
+
failures=$(cat "$_CIRCUIT_BREAKER_FILE" 2>/dev/null || echo "0")
|
|
77
|
+
if [[ "$failures" -ge "$_CIRCUIT_BREAKER_THRESHOLD" ]]; then
|
|
78
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Circuit breaker OPEN ($failures failures)" >&2
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
record_circuit_breaker_failure() {
|
|
86
|
+
[[ -z "$_CIRCUIT_BREAKER_FILE" ]] && return
|
|
87
|
+
|
|
88
|
+
local failures
|
|
89
|
+
failures=$(cat "$_CIRCUIT_BREAKER_FILE" 2>/dev/null || echo "0")
|
|
90
|
+
echo "$((failures + 1))" > "$_CIRCUIT_BREAKER_FILE"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
reset_circuit_breaker() {
|
|
94
|
+
[[ -n "$_CIRCUIT_BREAKER_FILE" ]] && rm -f "$_CIRCUIT_BREAKER_FILE"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ============================================================================
|
|
98
|
+
# 4. FILE-BASED RECURSION GUARD (ADR-0073 - replaces failed env var approach)
|
|
99
|
+
# ============================================================================
|
|
100
|
+
|
|
101
|
+
_RECURSION_GUARD_FILE=""
|
|
102
|
+
|
|
103
|
+
init_recursion_guard() {
|
|
104
|
+
local project_root="$1"
|
|
105
|
+
_RECURSION_GUARD_FILE="${project_root}/.specweave/state/.hook-recursion-guard"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
check_recursion_guard() {
|
|
109
|
+
[[ -z "$_RECURSION_GUARD_FILE" ]] && return 0
|
|
110
|
+
|
|
111
|
+
if [[ -f "$_RECURSION_GUARD_FILE" ]]; then
|
|
112
|
+
# Check if guard is stale (>30s old)
|
|
113
|
+
local guard_age
|
|
114
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
115
|
+
guard_age=$(( $(date +%s) - $(stat -f %m "$_RECURSION_GUARD_FILE" 2>/dev/null || echo "0") ))
|
|
116
|
+
else
|
|
117
|
+
guard_age=$(( $(date +%s) - $(stat -c %Y "$_RECURSION_GUARD_FILE" 2>/dev/null || echo "0") ))
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ "$guard_age" -lt 30 ]]; then
|
|
121
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Recursion guard active (${guard_age}s old)" >&2
|
|
122
|
+
return 1
|
|
123
|
+
else
|
|
124
|
+
# Stale guard - clean it up
|
|
125
|
+
rm -f "$_RECURSION_GUARD_FILE"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
return 0
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
acquire_recursion_guard() {
|
|
132
|
+
[[ -z "$_RECURSION_GUARD_FILE" ]] && return 1
|
|
133
|
+
|
|
134
|
+
touch "$_RECURSION_GUARD_FILE"
|
|
135
|
+
# Auto-cleanup on exit
|
|
136
|
+
trap 'rm -f "$_RECURSION_GUARD_FILE" 2>/dev/null' EXIT
|
|
137
|
+
return 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
release_recursion_guard() {
|
|
141
|
+
[[ -n "$_RECURSION_GUARD_FILE" ]] && rm -f "$_RECURSION_GUARD_FILE" 2>/dev/null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ============================================================================
|
|
145
|
+
# 5. LOCK FILE ACQUISITION (ADR-0068 Layer 3)
|
|
146
|
+
# ============================================================================
|
|
147
|
+
|
|
148
|
+
_LOCK_FILE=""
|
|
149
|
+
_LOCK_TIMEOUT=5
|
|
150
|
+
|
|
151
|
+
acquire_hook_lock() {
|
|
152
|
+
local lock_name="${1:-default}"
|
|
153
|
+
local project_root="${2:-$(find_project_root)}"
|
|
154
|
+
|
|
155
|
+
_LOCK_FILE="${project_root}/.specweave/state/.hook-${lock_name}.lock"
|
|
156
|
+
mkdir -p "$(dirname "$_LOCK_FILE")" 2>/dev/null
|
|
157
|
+
|
|
158
|
+
local attempts=0
|
|
159
|
+
while [[ $attempts -lt $((_LOCK_TIMEOUT * 5)) ]]; do
|
|
160
|
+
if mkdir "$_LOCK_FILE" 2>/dev/null; then
|
|
161
|
+
trap 'rmdir "$_LOCK_FILE" 2>/dev/null || true' EXIT
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Check for stale lock (>60s old)
|
|
166
|
+
if [[ -d "$_LOCK_FILE" ]]; then
|
|
167
|
+
local lock_age
|
|
168
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
169
|
+
lock_age=$(( $(date +%s) - $(stat -f %m "$_LOCK_FILE" 2>/dev/null || echo "0") ))
|
|
170
|
+
else
|
|
171
|
+
lock_age=$(( $(date +%s) - $(stat -c %Y "$_LOCK_FILE" 2>/dev/null || echo "0") ))
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [[ "$lock_age" -gt 60 ]]; then
|
|
175
|
+
rmdir "$_LOCK_FILE" 2>/dev/null
|
|
176
|
+
continue
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
sleep 0.2
|
|
181
|
+
attempts=$((attempts + 1))
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Lock acquisition timeout: $lock_name" >&2
|
|
185
|
+
return 1
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
release_hook_lock() {
|
|
189
|
+
[[ -n "$_LOCK_FILE" ]] && rmdir "$_LOCK_FILE" 2>/dev/null
|
|
190
|
+
_LOCK_FILE=""
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# ============================================================================
|
|
194
|
+
# 6. DEBOUNCING (ADR-0060 Tier 1, ADR-0130)
|
|
195
|
+
# ============================================================================
|
|
196
|
+
|
|
197
|
+
_DEBOUNCE_FILE=""
|
|
198
|
+
_DEBOUNCE_WINDOW=5 # seconds
|
|
199
|
+
|
|
200
|
+
check_debounce() {
|
|
201
|
+
local debounce_key="${1:-default}"
|
|
202
|
+
local project_root="${2:-$(find_project_root)}"
|
|
203
|
+
|
|
204
|
+
_DEBOUNCE_FILE="${project_root}/.specweave/state/.last-hook-${debounce_key}"
|
|
205
|
+
|
|
206
|
+
if [[ -f "$_DEBOUNCE_FILE" ]]; then
|
|
207
|
+
local last_run
|
|
208
|
+
last_run=$(cat "$_DEBOUNCE_FILE" 2>/dev/null || echo "0")
|
|
209
|
+
local now
|
|
210
|
+
now=$(date +%s)
|
|
211
|
+
|
|
212
|
+
if [[ $((now - last_run)) -lt $_DEBOUNCE_WINDOW ]]; then
|
|
213
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Debounced: $debounce_key" >&2
|
|
214
|
+
return 1
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
return 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
record_debounce() {
|
|
221
|
+
[[ -n "$_DEBOUNCE_FILE" ]] && date +%s > "$_DEBOUNCE_FILE"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# ============================================================================
|
|
225
|
+
# 7. BULK OPERATION DETECTION (ADR-0130)
|
|
226
|
+
# ============================================================================
|
|
227
|
+
|
|
228
|
+
_BULK_COUNTER_FILE=""
|
|
229
|
+
_BULK_THRESHOLD=5 # 5+ ops in 10s = bulk mode
|
|
230
|
+
_BULK_WINDOW=10
|
|
231
|
+
|
|
232
|
+
is_bulk_operation() {
|
|
233
|
+
local project_root="${1:-$(find_project_root)}"
|
|
234
|
+
|
|
235
|
+
_BULK_COUNTER_FILE="${project_root}/.specweave/state/.bulk-op-counter"
|
|
236
|
+
mkdir -p "$(dirname "$_BULK_COUNTER_FILE")" 2>/dev/null
|
|
237
|
+
|
|
238
|
+
local now
|
|
239
|
+
now=$(date +%s)
|
|
240
|
+
|
|
241
|
+
# Read current count and timestamp
|
|
242
|
+
local count=0
|
|
243
|
+
local start_time=$now
|
|
244
|
+
|
|
245
|
+
if [[ -f "$_BULK_COUNTER_FILE" ]]; then
|
|
246
|
+
read -r count start_time < "$_BULK_COUNTER_FILE" 2>/dev/null || true
|
|
247
|
+
|
|
248
|
+
# Reset if window expired
|
|
249
|
+
if [[ $((now - start_time)) -gt $_BULK_WINDOW ]]; then
|
|
250
|
+
count=0
|
|
251
|
+
start_time=$now
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# Increment and save
|
|
256
|
+
count=$((count + 1))
|
|
257
|
+
echo "$count $start_time" > "$_BULK_COUNTER_FILE"
|
|
258
|
+
|
|
259
|
+
if [[ $count -ge $_BULK_THRESHOLD ]]; then
|
|
260
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Bulk operation detected ($count ops in window)" >&2
|
|
261
|
+
return 0 # IS bulk operation
|
|
262
|
+
fi
|
|
263
|
+
return 1 # NOT bulk operation
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# ============================================================================
|
|
267
|
+
# 8. LOG ROTATION (ADR-0060)
|
|
268
|
+
# ============================================================================
|
|
269
|
+
|
|
270
|
+
_LOG_FILE=""
|
|
271
|
+
_LOG_MAX_SIZE=1048576 # 1MB
|
|
272
|
+
|
|
273
|
+
init_log() {
|
|
274
|
+
local log_name="${1:-hooks}"
|
|
275
|
+
local project_root="${2:-$(find_project_root)}"
|
|
276
|
+
|
|
277
|
+
_LOG_DIR="${project_root}/.specweave/logs"
|
|
278
|
+
mkdir -p "$_LOG_DIR" 2>/dev/null
|
|
279
|
+
_LOG_FILE="${_LOG_DIR}/${log_name}.log"
|
|
280
|
+
|
|
281
|
+
# Rotate if too large
|
|
282
|
+
if [[ -f "$_LOG_FILE" ]]; then
|
|
283
|
+
local size
|
|
284
|
+
size=$(stat -f%z "$_LOG_FILE" 2>/dev/null || stat -c%s "$_LOG_FILE" 2>/dev/null || echo "0")
|
|
285
|
+
if [[ "$size" -gt "$_LOG_MAX_SIZE" ]]; then
|
|
286
|
+
mv "$_LOG_FILE" "${_LOG_FILE}.1" 2>/dev/null
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
log_hook() {
|
|
292
|
+
local level="$1"
|
|
293
|
+
shift
|
|
294
|
+
local msg="$*"
|
|
295
|
+
|
|
296
|
+
[[ -z "$_LOG_FILE" ]] && return
|
|
297
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] $msg" >> "$_LOG_FILE"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# ============================================================================
|
|
301
|
+
# 9. UNIFIED SETUP (ALL CHECKS IN ONE CALL)
|
|
302
|
+
# ============================================================================
|
|
303
|
+
|
|
304
|
+
setup_hook_environment() {
|
|
305
|
+
local hook_name="${1:-unnamed}"
|
|
306
|
+
local require_lock="${2:-false}"
|
|
307
|
+
local enable_recursion_guard="${3:-false}"
|
|
308
|
+
|
|
309
|
+
# Find project root
|
|
310
|
+
PROJECT_ROOT=$(find_project_root)
|
|
311
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
312
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Not in SpecWeave project" >&2
|
|
313
|
+
return 1
|
|
314
|
+
fi
|
|
315
|
+
export PROJECT_ROOT
|
|
316
|
+
|
|
317
|
+
# Initialize state directory
|
|
318
|
+
_STATE_DIR="${PROJECT_ROOT}/.specweave/state"
|
|
319
|
+
mkdir -p "$_STATE_DIR" 2>/dev/null
|
|
320
|
+
|
|
321
|
+
# Layer 1: Kill switch
|
|
322
|
+
check_kill_switch || return 1
|
|
323
|
+
|
|
324
|
+
# Layer 2: Circuit breaker
|
|
325
|
+
init_circuit_breaker "$PROJECT_ROOT"
|
|
326
|
+
check_circuit_breaker || return 1
|
|
327
|
+
|
|
328
|
+
# Layer 3: Recursion guard (if enabled)
|
|
329
|
+
if [[ "$enable_recursion_guard" == "true" ]]; then
|
|
330
|
+
init_recursion_guard "$PROJECT_ROOT"
|
|
331
|
+
check_recursion_guard || return 1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Layer 4: Lock (if required)
|
|
335
|
+
if [[ "$require_lock" == "true" ]]; then
|
|
336
|
+
acquire_hook_lock "$hook_name" "$PROJECT_ROOT" || return 1
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# Layer 5: Debouncing
|
|
340
|
+
check_debounce "$hook_name" "$PROJECT_ROOT" || return 1
|
|
341
|
+
|
|
342
|
+
# Initialize logging
|
|
343
|
+
init_log "hooks" "$PROJECT_ROOT"
|
|
344
|
+
|
|
345
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Environment ready: $hook_name" >&2
|
|
346
|
+
return 0
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
# ============================================================================
|
|
350
|
+
# 10. SAFE EXIT HANDLERS
|
|
351
|
+
# ============================================================================
|
|
352
|
+
|
|
353
|
+
# Always exit 0 from hooks (ADR-0157 Rule 6)
|
|
354
|
+
# Exception: PreToolUse guards exit 2 to block
|
|
355
|
+
hook_exit_success() {
|
|
356
|
+
release_hook_lock
|
|
357
|
+
release_recursion_guard
|
|
358
|
+
exit 0
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
hook_exit_block() {
|
|
362
|
+
release_hook_lock
|
|
363
|
+
release_recursion_guard
|
|
364
|
+
exit 2 # Block PreToolUse
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# Record failure and exit safely
|
|
368
|
+
hook_exit_with_failure() {
|
|
369
|
+
local msg="$1"
|
|
370
|
+
record_circuit_breaker_failure
|
|
371
|
+
log_hook "ERROR" "$msg"
|
|
372
|
+
release_hook_lock
|
|
373
|
+
release_recursion_guard
|
|
374
|
+
exit 0 # Still exit 0 to not break Claude
|
|
375
|
+
}
|