specweave 0.32.0 → 0.32.3
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 +215 -2
- package/README.md +22 -0
- package/bin/specweave.js +52 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts +100 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +291 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts +103 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +310 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts +126 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +207 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -0
- package/dist/src/adapters/codex/README.md +1 -1
- package/dist/src/adapters/codex/adapter.js +1 -1
- package/dist/src/cli/commands/archive.d.ts +2 -0
- package/dist/src/cli/commands/archive.d.ts.map +1 -1
- package/dist/src/cli/commands/archive.js +33 -0
- package/dist/src/cli/commands/archive.js.map +1 -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/context.d.ts +92 -0
- package/dist/src/cli/commands/context.d.ts.map +1 -0
- package/dist/src/cli/commands/context.js +205 -0
- package/dist/src/cli/commands/context.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +112 -70
- 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/helpers/init/external-import.d.ts +3 -0
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +17 -4
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +1 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +2 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
- package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.js +9 -2
- package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.js +21 -4
- package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
- 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/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 +49 -4
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +123 -22
- 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 +150 -0
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +216 -4
- 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/living-docs/feature-archiver.d.ts +4 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +32 -10
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +8 -4
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/index.d.ts +50 -0
- package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/index.js +56 -0
- package/dist/src/core/living-docs/governance/index.js.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
- package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
- 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 +299 -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 +22 -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 +482 -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 +190 -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 +11 -3
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +53 -10
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
- package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.js +123 -19
- package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
- package/dist/src/core/llm/provider-factory.js +2 -2
- package/dist/src/core/llm/provider-factory.js.map +1 -1
- package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
- package/dist/src/core/llm/providers/bedrock-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/importers/jira-importer.d.ts +14 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +75 -0
- package/dist/src/importers/jira-importer.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/integrations/jira/jira-token-provider.d.ts +93 -0
- package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
- package/dist/src/integrations/jira/jira-token-provider.js +160 -0
- package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
- package/dist/src/sync/ado-reconciler.d.ts +92 -0
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
- package/dist/src/sync/ado-reconciler.js +335 -0
- package/dist/src/sync/ado-reconciler.js.map +1 -0
- package/dist/src/sync/jira-reconciler.d.ts +106 -0
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
- package/dist/src/sync/jira-reconciler.js +405 -0
- package/dist/src/sync/jira-reconciler.js.map +1 -0
- 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/types/model-selection.d.ts +6 -4
- package/dist/src/types/model-selection.d.ts.map +1 -1
- package/dist/src/types/model-selection.js +3 -1
- package/dist/src/types/model-selection.js.map +1 -1
- 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/external-tool-drift-detector.d.ts +1 -1
- package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
- package/dist/src/utils/external-tool-drift-detector.js +5 -4
- package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
- 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/feature-id-derivation.d.ts +8 -3
- package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
- package/dist/src/utils/feature-id-derivation.js +14 -6
- package/dist/src/utils/feature-id-derivation.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/dist/src/utils/model-selection.d.ts +3 -4
- package/dist/src/utils/model-selection.d.ts.map +1 -1
- package/dist/src/utils/model-selection.js +3 -4
- package/dist/src/utils/model-selection.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
- 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-costs.md +4 -4
- package/plugins/specweave/commands/specweave-do.md +44 -10
- package/plugins/specweave/commands/specweave-done.md +109 -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/commands/specweave-validate.md +27 -1
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +43 -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/spec-project-validator.sh +80 -25
- package/plugins/specweave/hooks/universal/dispatcher.mjs +135 -42
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +183 -0
- 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/hooks/v2/guards/increment-duplicate-guard.sh +135 -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/code-standards-analyzer/SKILL.md +58 -6
- package/plugins/specweave/skills/increment-planner/SKILL.md +388 -48
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +17 -7
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +6 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
- package/plugins/specweave/skills/instant-status/SKILL.md +70 -0
- package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
- package/plugins/specweave-ado/commands/reconcile.md +120 -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/ado-duplicate-detector.js +279 -0
- package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -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/agents/github-manager/AGENT.md +2 -2
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -0
- 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-infrastructure/skills/hetzner-provisioner/README.md +1 -1
- package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
- package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
- package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
- package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
- package/plugins/specweave-jira/commands/close.md +297 -0
- package/plugins/specweave-jira/commands/create.md +198 -0
- package/plugins/specweave-jira/commands/reconcile.md +123 -0
- package/plugins/specweave-jira/commands/status.md +215 -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-jira/lib/jira-duplicate-detector.js +296 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
- package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
- package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -0
- 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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - session-start
|
|
13
13
|
* - post-tool-use
|
|
14
14
|
* - completion-guard
|
|
15
|
+
* - bash-file-guard
|
|
15
16
|
*
|
|
16
17
|
* @module hooks/universal/dispatcher
|
|
17
18
|
*/
|
|
@@ -27,18 +28,82 @@ const __dirname = dirname(__filename);
|
|
|
27
28
|
// Hook type from arguments
|
|
28
29
|
const hookType = process.argv[2] || 'unknown';
|
|
29
30
|
|
|
31
|
+
// Global timeout for hook execution (30 seconds max)
|
|
32
|
+
const HOOK_TIMEOUT_MS = 30000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Detect if we're in a development environment with duplicate hooks
|
|
36
|
+
*
|
|
37
|
+
* Problem: When working IN the specweave project AND having the marketplace
|
|
38
|
+
* plugin installed, Claude Code loads hooks from BOTH sources, causing:
|
|
39
|
+
* - Duplicate hook execution
|
|
40
|
+
* - Potential race conditions
|
|
41
|
+
* - Confusing output
|
|
42
|
+
*
|
|
43
|
+
* Solution: If we detect we're running from the marketplace copy while
|
|
44
|
+
* the CWD is the specweave project itself, skip execution (let local hooks handle it).
|
|
45
|
+
*
|
|
46
|
+
* @returns {boolean} true if we should skip this hook execution
|
|
47
|
+
*/
|
|
48
|
+
function shouldSkipDueToDevEnvironment() {
|
|
49
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || '';
|
|
50
|
+
const cwd = process.cwd();
|
|
51
|
+
|
|
52
|
+
// Check if running from marketplace (installed plugin)
|
|
53
|
+
const isFromMarketplace = pluginRoot.includes('.claude/plugins/marketplaces/');
|
|
54
|
+
|
|
55
|
+
// Check if CWD is the specweave project itself
|
|
56
|
+
const isInSpecweaveProject = existsSync(join(cwd, 'plugins', 'specweave', 'hooks', 'hooks.json'));
|
|
57
|
+
|
|
58
|
+
// Skip marketplace hooks when developing specweave locally
|
|
59
|
+
if (isFromMarketplace && isInSpecweaveProject) {
|
|
60
|
+
// Output diagnostic only for session-start (once per session)
|
|
61
|
+
if (hookType === 'session-start') {
|
|
62
|
+
console.log(JSON.stringify({
|
|
63
|
+
continue: true,
|
|
64
|
+
systemMessage: 'SpecWeave dev environment: Using local hooks (marketplace hooks skipped)'
|
|
65
|
+
}));
|
|
66
|
+
} else {
|
|
67
|
+
console.log(JSON.stringify({ continue: true }));
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
30
75
|
/**
|
|
31
|
-
*
|
|
76
|
+
* Wrap a promise with a timeout
|
|
77
|
+
* @param promise - Promise to wrap
|
|
78
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
79
|
+
* @param cleanup - Optional cleanup function (e.g., to kill child process)
|
|
80
|
+
*/
|
|
81
|
+
function withTimeout(promise, timeoutMs, cleanup) {
|
|
82
|
+
let timeoutId;
|
|
83
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
84
|
+
timeoutId = setTimeout(() => {
|
|
85
|
+
if (cleanup) cleanup();
|
|
86
|
+
reject(new Error(`Hook timeout after ${timeoutMs}ms`));
|
|
87
|
+
}, timeoutMs);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
91
|
+
clearTimeout(timeoutId);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find the dist/src/hooks directory (TypeScript compiled hooks)
|
|
32
97
|
*/
|
|
33
98
|
function findHooksDir() {
|
|
34
99
|
// Try multiple locations
|
|
35
100
|
const candidates = [
|
|
36
|
-
// Production: node_modules/specweave/dist/hooks
|
|
37
|
-
join(__dirname, '..', '..', '..', '..', 'node_modules', 'specweave', 'dist', 'hooks'),
|
|
38
|
-
// Development: project root dist/hooks
|
|
101
|
+
// Production: node_modules/specweave/dist/src/hooks
|
|
102
|
+
join(__dirname, '..', '..', '..', '..', 'node_modules', 'specweave', 'dist', 'src', 'hooks'),
|
|
103
|
+
// Development: project root dist/src/hooks
|
|
104
|
+
join(__dirname, '..', '..', '..', '..', 'dist', 'src', 'hooks'),
|
|
105
|
+
// Fallback: older path without src/ (backward compatibility)
|
|
39
106
|
join(__dirname, '..', '..', '..', '..', 'dist', 'hooks'),
|
|
40
|
-
// Relative to this file
|
|
41
|
-
join(__dirname, '..', '..', '..', '..', 'src', 'hooks'),
|
|
42
107
|
];
|
|
43
108
|
|
|
44
109
|
for (const candidate of candidates) {
|
|
@@ -52,7 +117,7 @@ function findHooksDir() {
|
|
|
52
117
|
}
|
|
53
118
|
|
|
54
119
|
/**
|
|
55
|
-
* Run a hook script
|
|
120
|
+
* Run a hook script with timeout protection
|
|
56
121
|
*/
|
|
57
122
|
async function runHook(scriptName) {
|
|
58
123
|
const hooksDir = findHooksDir();
|
|
@@ -74,13 +139,26 @@ async function runHook(scriptName) {
|
|
|
74
139
|
windowsHide: true,
|
|
75
140
|
});
|
|
76
141
|
|
|
77
|
-
|
|
142
|
+
const execPromise = new Promise((resolve) => {
|
|
78
143
|
child.on('exit', (code) => resolve(code || 0));
|
|
79
144
|
child.on('error', () => {
|
|
80
145
|
console.log(JSON.stringify({ continue: true }));
|
|
81
146
|
resolve(1);
|
|
82
147
|
});
|
|
83
148
|
});
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
|
|
152
|
+
// Kill the child process on timeout
|
|
153
|
+
try {
|
|
154
|
+
child.kill('SIGTERM');
|
|
155
|
+
setTimeout(() => child.kill('SIGKILL'), 1000);
|
|
156
|
+
} catch { /* ignore */ }
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.log(JSON.stringify({ continue: true, error: err.message }));
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
84
162
|
}
|
|
85
163
|
|
|
86
164
|
/**
|
|
@@ -124,7 +202,39 @@ function findGitBash() {
|
|
|
124
202
|
}
|
|
125
203
|
|
|
126
204
|
/**
|
|
127
|
-
*
|
|
205
|
+
* Helper to spawn a bash script with timeout protection
|
|
206
|
+
*/
|
|
207
|
+
async function spawnBashWithTimeout(bashExe, args, options = {}) {
|
|
208
|
+
const child = spawn(bashExe, args, {
|
|
209
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
210
|
+
windowsHide: true,
|
|
211
|
+
...options,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const execPromise = new Promise((resolve) => {
|
|
215
|
+
child.on('exit', (code) => resolve(code || 0));
|
|
216
|
+
child.on('error', () => {
|
|
217
|
+
console.log(JSON.stringify({ continue: true }));
|
|
218
|
+
resolve(1);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
|
|
224
|
+
// Kill the child process on timeout
|
|
225
|
+
try {
|
|
226
|
+
child.kill('SIGTERM');
|
|
227
|
+
setTimeout(() => child.kill('SIGKILL'), 1000);
|
|
228
|
+
} catch { /* ignore */ }
|
|
229
|
+
});
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.log(JSON.stringify({ continue: true, error: err.message }));
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fallback to bash script if TypeScript not built (with timeout protection)
|
|
128
238
|
*
|
|
129
239
|
* @param bashScript - Name of the bash script (e.g., 'post-tool-use.sh')
|
|
130
240
|
* @param subdir - Subdirectory under v2 (e.g., 'dispatchers', 'guards')
|
|
@@ -143,17 +253,7 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
143
253
|
// Strategy 1: Git Bash (preferred - most common)
|
|
144
254
|
const bashExe = findGitBash();
|
|
145
255
|
if (bashExe) {
|
|
146
|
-
|
|
147
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
148
|
-
windowsHide: true,
|
|
149
|
-
});
|
|
150
|
-
return new Promise((resolve) => {
|
|
151
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
152
|
-
child.on('error', () => {
|
|
153
|
-
console.log(JSON.stringify({ continue: true }));
|
|
154
|
-
resolve(1);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
256
|
+
return spawnBashWithTimeout(bashExe, [scriptPath]);
|
|
157
257
|
}
|
|
158
258
|
|
|
159
259
|
// Strategy 2: WSL (if Git Bash not available) - FALLBACK only
|
|
@@ -166,17 +266,7 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
166
266
|
const wslScriptPath = scriptPath
|
|
167
267
|
.replace(/\\/g, '/')
|
|
168
268
|
.replace(/^([A-Za-z]):/, (_, d) => `/mnt/${d.toLowerCase()}`);
|
|
169
|
-
|
|
170
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
171
|
-
windowsHide: true,
|
|
172
|
-
});
|
|
173
|
-
return new Promise((resolve) => {
|
|
174
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
175
|
-
child.on('error', () => {
|
|
176
|
-
console.log(JSON.stringify({ continue: true }));
|
|
177
|
-
resolve(1);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
269
|
+
return spawnBashWithTimeout('wsl', ['bash', wslScriptPath]);
|
|
180
270
|
}
|
|
181
271
|
|
|
182
272
|
// Strategy 3: No bash available - output warning and continue
|
|
@@ -188,21 +278,18 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
188
278
|
return;
|
|
189
279
|
}
|
|
190
280
|
|
|
191
|
-
// POSIX (macOS, Linux) - run directly
|
|
192
|
-
|
|
193
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
194
|
-
});
|
|
195
|
-
return new Promise((resolve) => {
|
|
196
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
197
|
-
child.on('error', () => {
|
|
198
|
-
console.log(JSON.stringify({ continue: true }));
|
|
199
|
-
resolve(1);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
281
|
+
// POSIX (macOS, Linux) - run directly with timeout
|
|
282
|
+
return spawnBashWithTimeout('bash', [scriptPath]);
|
|
202
283
|
}
|
|
203
284
|
|
|
204
285
|
// Main routing
|
|
205
286
|
async function main() {
|
|
287
|
+
// CRITICAL: Skip if we're in dev environment with duplicate hooks
|
|
288
|
+
// This prevents marketplace hooks from running when local hooks are available
|
|
289
|
+
if (shouldSkipDueToDevEnvironment()) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
206
293
|
const isWindows = process.platform === 'win32';
|
|
207
294
|
|
|
208
295
|
try {
|
|
@@ -235,6 +322,12 @@ async function main() {
|
|
|
235
322
|
await fallbackToBash('completion-guard.sh', 'guards');
|
|
236
323
|
break;
|
|
237
324
|
|
|
325
|
+
case 'bash-file-guard':
|
|
326
|
+
// CRITICAL: Prevents infinite hangs from heredoc/echo file creation
|
|
327
|
+
// See CLAUDE.md Rule 9 for why this is essential
|
|
328
|
+
await fallbackToBash('bash-file-guard.sh', 'guards');
|
|
329
|
+
break;
|
|
330
|
+
|
|
238
331
|
default:
|
|
239
332
|
console.log(JSON.stringify({ continue: true, error: `Unknown hook type: ${hookType}` }));
|
|
240
333
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# fail-fast-wrapper.sh - HARD TIMEOUT wrapper for all hooks
|
|
3
|
+
# If ANY hook takes longer than HOOK_TIMEOUT, it gets KILLED.
|
|
4
|
+
#
|
|
5
|
+
# Usage: bash fail-fast-wrapper.sh <hook-script> [args...]
|
|
6
|
+
#
|
|
7
|
+
# Environment:
|
|
8
|
+
# HOOK_TIMEOUT - max seconds (default: 5)
|
|
9
|
+
# HOOK_DEBUG - set to 1 for verbose logging
|
|
10
|
+
#
|
|
11
|
+
# Exit behavior:
|
|
12
|
+
# - Returns hook output on success
|
|
13
|
+
# - Returns safe JSON on timeout ({"continue":true} or {"decision":"approve"})
|
|
14
|
+
# - NEVER hangs - timeout is enforced with SIGKILL
|
|
15
|
+
#
|
|
16
|
+
# CRASH PREVENTION:
|
|
17
|
+
# - Integrates with crash-prevention.sh for process storm detection
|
|
18
|
+
# - Auto-kills zombie processes on timeout
|
|
19
|
+
# - Records failures for circuit breaker
|
|
20
|
+
#
|
|
21
|
+
# v0.33.0 - Enhanced with crash prevention integration
|
|
22
|
+
|
|
23
|
+
set -o pipefail
|
|
24
|
+
|
|
25
|
+
# === Configuration ===
|
|
26
|
+
HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}" # 5 seconds - more than enough for any hook
|
|
27
|
+
HOOK_DEBUG="${HOOK_DEBUG:-0}"
|
|
28
|
+
LOG_FILE="${HOME}/.claude/hook-failures.log"
|
|
29
|
+
|
|
30
|
+
# === Crash Prevention Integration ===
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
CRASH_PREVENTION="${SCRIPT_DIR}/../lib/crash-prevention.sh"
|
|
33
|
+
|
|
34
|
+
# Source crash prevention if available (non-blocking)
|
|
35
|
+
if [[ -f "$CRASH_PREVENTION" ]]; then
|
|
36
|
+
source "$CRASH_PREVENTION" 2>/dev/null || true
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# === Helper functions ===
|
|
40
|
+
log_debug() {
|
|
41
|
+
[[ "$HOOK_DEBUG" == "1" ]] && echo "[DEBUG $(date +%H:%M:%S)] $*" >&2
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
log_failure() {
|
|
45
|
+
local msg="$1"
|
|
46
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
47
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] HOOK TIMEOUT: $msg" >> "$LOG_FILE"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# === Safe JSON output based on hook type ===
|
|
51
|
+
get_safe_output() {
|
|
52
|
+
local script="$1"
|
|
53
|
+
# PreToolUse hooks need "decision" format
|
|
54
|
+
if [[ "$script" == *"guard"* ]] || [[ "$script" == *"validator"* ]] || [[ "$script" == *"PreToolUse"* ]]; then
|
|
55
|
+
echo '{"decision":"allow"}'
|
|
56
|
+
else
|
|
57
|
+
echo '{"continue":true}'
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# === Read stdin with timeout ===
|
|
62
|
+
# Critical: stdin can block forever if not handled properly
|
|
63
|
+
read_stdin_with_timeout() {
|
|
64
|
+
local stdin_content=""
|
|
65
|
+
|
|
66
|
+
# Use read with timeout (integer seconds for bash compatibility)
|
|
67
|
+
if read -t 1 -r line; then
|
|
68
|
+
stdin_content="$line"
|
|
69
|
+
# Continue reading remaining lines (no timeout - stdin should be closed)
|
|
70
|
+
while IFS= read -r line; do
|
|
71
|
+
stdin_content="${stdin_content}"$'\n'"${line}"
|
|
72
|
+
done
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
echo "$stdin_content"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# === Main execution ===
|
|
79
|
+
main() {
|
|
80
|
+
local script="$1"
|
|
81
|
+
shift
|
|
82
|
+
local args=("$@")
|
|
83
|
+
|
|
84
|
+
if [[ -z "$script" ]]; then
|
|
85
|
+
echo '{"continue":true}'
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [[ ! -f "$script" ]]; then
|
|
90
|
+
log_debug "Script not found: $script"
|
|
91
|
+
echo '{"continue":true}'
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# === CRASH PREVENTION: Process Storm Detection ===
|
|
96
|
+
# If too many hooks are running, skip this one to prevent cascade
|
|
97
|
+
if type detect_process_storm &>/dev/null; then
|
|
98
|
+
local storm_status
|
|
99
|
+
storm_status=$(detect_process_storm 25)
|
|
100
|
+
if [[ "$storm_status" == STORM* ]]; then
|
|
101
|
+
log_failure "$script - BLOCKED due to process storm: $storm_status"
|
|
102
|
+
get_safe_output "$script"
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
log_debug "Executing: $script (timeout: ${HOOK_TIMEOUT}s)"
|
|
108
|
+
|
|
109
|
+
# Read stdin first (with its own timeout)
|
|
110
|
+
local stdin_content
|
|
111
|
+
stdin_content=$(read_stdin_with_timeout)
|
|
112
|
+
|
|
113
|
+
# Execute the hook with hard timeout
|
|
114
|
+
# Using timeout with --kill-after to ensure SIGKILL if SIGTERM doesn't work
|
|
115
|
+
local output
|
|
116
|
+
local exit_code
|
|
117
|
+
|
|
118
|
+
# Create temp file for output (avoid subshell issues)
|
|
119
|
+
local tmp_out
|
|
120
|
+
tmp_out=$(mktemp)
|
|
121
|
+
|
|
122
|
+
# Run with timeout - kill entire process group on timeout
|
|
123
|
+
if command -v gtimeout >/dev/null 2>&1; then
|
|
124
|
+
# macOS with coreutils
|
|
125
|
+
echo "$stdin_content" | gtimeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
|
|
126
|
+
exit_code=$?
|
|
127
|
+
elif command -v timeout >/dev/null 2>&1; then
|
|
128
|
+
# Linux
|
|
129
|
+
echo "$stdin_content" | timeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
|
|
130
|
+
exit_code=$?
|
|
131
|
+
else
|
|
132
|
+
# Fallback: manual timeout using background process
|
|
133
|
+
(
|
|
134
|
+
echo "$stdin_content" | bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
|
|
135
|
+
) &
|
|
136
|
+
local pid=$!
|
|
137
|
+
|
|
138
|
+
# Wait with timeout
|
|
139
|
+
local count=0
|
|
140
|
+
while kill -0 "$pid" 2>/dev/null && [[ $count -lt $((HOOK_TIMEOUT * 10)) ]]; do
|
|
141
|
+
sleep 0.1
|
|
142
|
+
count=$((count + 1))
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
146
|
+
# Still running - kill it!
|
|
147
|
+
kill -9 "$pid" 2>/dev/null
|
|
148
|
+
wait "$pid" 2>/dev/null
|
|
149
|
+
exit_code=124 # timeout exit code
|
|
150
|
+
else
|
|
151
|
+
wait "$pid"
|
|
152
|
+
exit_code=$?
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
output=$(cat "$tmp_out" 2>/dev/null)
|
|
157
|
+
rm -f "$tmp_out"
|
|
158
|
+
|
|
159
|
+
# Handle timeout (exit code 124 or 137)
|
|
160
|
+
if [[ $exit_code -eq 124 ]] || [[ $exit_code -eq 137 ]]; then
|
|
161
|
+
log_failure "$script - killed after ${HOOK_TIMEOUT}s"
|
|
162
|
+
log_debug "TIMEOUT: $script killed after ${HOOK_TIMEOUT}s"
|
|
163
|
+
|
|
164
|
+
# === CRASH PREVENTION: Clean up potential zombie processes ===
|
|
165
|
+
if type kill_zombie_heredocs &>/dev/null; then
|
|
166
|
+
kill_zombie_heredocs 2>/dev/null || true
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
get_safe_output "$script"
|
|
170
|
+
exit 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Return output or safe default
|
|
174
|
+
if [[ -n "$output" ]]; then
|
|
175
|
+
echo "$output"
|
|
176
|
+
else
|
|
177
|
+
get_safe_output "$script"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
exit 0
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main "$@"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# SpecWeave UserPromptSubmit Hook (v0.
|
|
3
|
+
# SpecWeave UserPromptSubmit Hook (v0.33.0 - SCRIPT DELEGATION)
|
|
4
4
|
# Fires BEFORE user's command executes (prompt-based hook)
|
|
5
5
|
# Purpose: Discipline validation, context injection, command suggestions
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# FEATURES:
|
|
8
|
+
# - v0.33.0: Script delegation - status commands bypass LLM entirely (<1s)
|
|
9
|
+
# - v0.26.13: jq for JSON parsing (10x faster than node -e)
|
|
10
|
+
# - Single active increment detection (cached, not 4x!)
|
|
11
|
+
# - Deferred heavy checks (SpecSyncManager only when needed)
|
|
12
|
+
# - Ultra-fast early exits
|
|
13
13
|
#
|
|
14
|
-
# Performance: <
|
|
14
|
+
# Performance: Status commands <1s (was 3+ min), other prompts <10ms
|
|
15
15
|
|
|
16
16
|
set +e
|
|
17
17
|
|
|
@@ -45,6 +45,131 @@ if [[ ! -d "$SPECWEAVE_DIR" ]]; then
|
|
|
45
45
|
exit 0
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
|
+
# ==============================================================================
|
|
49
|
+
# INSTANT SCRIPT EXECUTION: Status commands bypass LLM entirely (v0.33.0)
|
|
50
|
+
# ==============================================================================
|
|
51
|
+
# These commands need NO LLM reasoning - execute scripts directly for <1s response
|
|
52
|
+
# Pattern: Detect command → Execute script → Return output via "block" → Exit
|
|
53
|
+
|
|
54
|
+
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
55
|
+
SCRIPTS_DIR="$PLUGIN_ROOT/scripts"
|
|
56
|
+
|
|
57
|
+
# Helper: Escape output for JSON (handles newlines, quotes, backslashes)
|
|
58
|
+
escape_json() {
|
|
59
|
+
local input="$1"
|
|
60
|
+
# Escape backslashes, then quotes, then newlines
|
|
61
|
+
echo "$input" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# /specweave:jobs → Execute read-jobs.sh (pure bash, ~2ms)
|
|
65
|
+
if echo "$PROMPT" | grep -qE "^/specweave:jobs($| )"; then
|
|
66
|
+
ARGS=$(echo "$PROMPT" | sed 's|^/specweave:jobs\s*||')
|
|
67
|
+
if [[ -f "$SCRIPTS_DIR/read-jobs.sh" ]]; then
|
|
68
|
+
OUTPUT=$(cd "$(pwd)" && bash "$SCRIPTS_DIR/read-jobs.sh" $ARGS 2>&1)
|
|
69
|
+
elif [[ -f "$SCRIPTS_DIR/jobs.js" ]] && command -v node >/dev/null 2>&1; then
|
|
70
|
+
OUTPUT=$(cd "$(pwd)" && node "$SCRIPTS_DIR/jobs.js" $ARGS 2>&1)
|
|
71
|
+
else
|
|
72
|
+
OUTPUT="❌ No jobs script available"
|
|
73
|
+
fi
|
|
74
|
+
OUTPUT_ESCAPED=$(escape_json "$OUTPUT")
|
|
75
|
+
printf '{"decision":"block","reason":"%s"}\n' "$OUTPUT_ESCAPED"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# /specweave:progress → Execute read-progress.sh (pure bash, ~30ms)
|
|
80
|
+
if echo "$PROMPT" | grep -qE "^/specweave:progress($| )"; then
|
|
81
|
+
ARGS=$(echo "$PROMPT" | sed 's|^/specweave:progress\s*||')
|
|
82
|
+
if [[ -f "$SCRIPTS_DIR/read-progress.sh" ]]; then
|
|
83
|
+
OUTPUT=$(cd "$(pwd)" && bash "$SCRIPTS_DIR/read-progress.sh" $ARGS 2>&1)
|
|
84
|
+
elif [[ -f "$SCRIPTS_DIR/progress.js" ]] && command -v node >/dev/null 2>&1; then
|
|
85
|
+
OUTPUT=$(cd "$(pwd)" && node "$SCRIPTS_DIR/progress.js" $ARGS 2>&1)
|
|
86
|
+
else
|
|
87
|
+
OUTPUT="❌ No progress script available"
|
|
88
|
+
fi
|
|
89
|
+
OUTPUT_ESCAPED=$(escape_json "$OUTPUT")
|
|
90
|
+
printf '{"decision":"block","reason":"%s"}\n' "$OUTPUT_ESCAPED"
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# /specweave:status → Execute read-status.sh (pure bash, ~150ms)
|
|
95
|
+
if echo "$PROMPT" | grep -qE "^/specweave:status($| )"; then
|
|
96
|
+
ARGS=$(echo "$PROMPT" | sed 's|^/specweave:status\s*||')
|
|
97
|
+
if [[ -f "$SCRIPTS_DIR/read-status.sh" ]]; then
|
|
98
|
+
OUTPUT=$(cd "$(pwd)" && bash "$SCRIPTS_DIR/read-status.sh" $ARGS 2>&1)
|
|
99
|
+
elif [[ -f "$SCRIPTS_DIR/status.js" ]] && command -v node >/dev/null 2>&1; then
|
|
100
|
+
OUTPUT=$(cd "$(pwd)" && node "$SCRIPTS_DIR/status.js" $ARGS 2>&1)
|
|
101
|
+
else
|
|
102
|
+
OUTPUT="❌ No status script available"
|
|
103
|
+
fi
|
|
104
|
+
OUTPUT_ESCAPED=$(escape_json "$OUTPUT")
|
|
105
|
+
printf '{"decision":"block","reason":"%s"}\n' "$OUTPUT_ESCAPED"
|
|
106
|
+
exit 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# /specweave:workflow → Execute read-workflow.sh (pure bash, ~100ms)
|
|
110
|
+
if echo "$PROMPT" | grep -qE "^/specweave:workflow($| )"; then
|
|
111
|
+
ARGS=$(echo "$PROMPT" | sed 's|^/specweave:workflow\s*||')
|
|
112
|
+
if [[ -f "$SCRIPTS_DIR/read-workflow.sh" ]]; then
|
|
113
|
+
OUTPUT=$(cd "$(pwd)" && bash "$SCRIPTS_DIR/read-workflow.sh" $ARGS 2>&1)
|
|
114
|
+
else
|
|
115
|
+
OUTPUT="❌ No workflow script available"
|
|
116
|
+
fi
|
|
117
|
+
OUTPUT_ESCAPED=$(escape_json "$OUTPUT")
|
|
118
|
+
printf '{"decision":"block","reason":"%s"}\n' "$OUTPUT_ESCAPED"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# /specweave:costs → Execute read-costs.sh (pure bash, ~50ms)
|
|
123
|
+
if echo "$PROMPT" | grep -qE "^/specweave:costs($| )"; then
|
|
124
|
+
ARGS=$(echo "$PROMPT" | sed 's|^/specweave:costs\s*||')
|
|
125
|
+
if [[ -f "$SCRIPTS_DIR/read-costs.sh" ]]; then
|
|
126
|
+
OUTPUT=$(cd "$(pwd)" && bash "$SCRIPTS_DIR/read-costs.sh" $ARGS 2>&1)
|
|
127
|
+
else
|
|
128
|
+
OUTPUT="❌ No costs script available"
|
|
129
|
+
fi
|
|
130
|
+
OUTPUT_ESCAPED=$(escape_json "$OUTPUT")
|
|
131
|
+
printf '{"decision":"block","reason":"%s"}\n' "$OUTPUT_ESCAPED"
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# ==============================================================================
|
|
136
|
+
# TASK COUNT GUARD: Block /specweave:do for oversized increments (v0.32.2+)
|
|
137
|
+
# ==============================================================================
|
|
138
|
+
# >8 tasks = context explosion = CRASH (per CLAUDE.md)
|
|
139
|
+
MAX_TASKS=8
|
|
140
|
+
|
|
141
|
+
if echo "$PROMPT" | grep -qE "^/specweave:do($| )"; then
|
|
142
|
+
# Extract increment ID from prompt
|
|
143
|
+
DO_INCREMENT_ID=$(echo "$PROMPT" | grep -oE "[0-9]{4}[a-zA-Z0-9-]*" | head -1)
|
|
144
|
+
|
|
145
|
+
# If no ID provided, find active increment
|
|
146
|
+
if [[ -z "$DO_INCREMENT_ID" ]]; then
|
|
147
|
+
for meta in "$SPECWEAVE_DIR/increments"/*/metadata.json; do
|
|
148
|
+
[[ -f "$meta" ]] || continue
|
|
149
|
+
if command -v jq >/dev/null 2>&1; then
|
|
150
|
+
status=$(jq -r '.status // "unknown"' "$meta" 2>/dev/null)
|
|
151
|
+
else
|
|
152
|
+
status=$(grep -oP '"status"\s*:\s*"\K[^"]*' "$meta" 2>/dev/null || echo "unknown")
|
|
153
|
+
fi
|
|
154
|
+
if [[ "$status" == "active" || "$status" == "in-progress" ]]; then
|
|
155
|
+
DO_INCREMENT_ID=$(basename "$(dirname "$meta")")
|
|
156
|
+
break
|
|
157
|
+
fi
|
|
158
|
+
done
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
if [[ -n "$DO_INCREMENT_ID" ]]; then
|
|
162
|
+
TASKS_FILE="$SPECWEAVE_DIR/increments/$DO_INCREMENT_ID/tasks.md"
|
|
163
|
+
if [[ -f "$TASKS_FILE" ]]; then
|
|
164
|
+
TASK_COUNT=$(grep -c "^### T-" "$TASKS_FILE" 2>/dev/null || echo "0")
|
|
165
|
+
if [[ "$TASK_COUNT" -gt "$MAX_TASKS" ]]; then
|
|
166
|
+
printf '{"decision":"block","reason":"❌ TASK COUNT EXCEEDS LIMIT\\n\\nIncrement %s has %s tasks (maximum: %s)\\n\\n>8 tasks = context explosion = CRASH\\n\\n💡 REQUIRED: Split into smaller increments:\\n\\n Pattern: %s/ → Split into:\\n • %s-part1/ (T-001 to T-004)\\n • Next increment (T-005 to T-008)\\n • Next increment (T-009+)\\n\\n⚠️ DO NOT PROCEED until tasks.md has ≤8 tasks!"}\n' "$DO_INCREMENT_ID" "$TASK_COUNT" "$MAX_TASKS" "$DO_INCREMENT_ID" "$DO_INCREMENT_ID"
|
|
167
|
+
exit 0
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
|
|
48
173
|
# ==============================================================================
|
|
49
174
|
# CACHED ACTIVE INCREMENT DETECTION (ONCE - reused throughout!)
|
|
50
175
|
# ==============================================================================
|
|
@@ -101,23 +226,13 @@ if echo "$PROMPT" | grep -q "/specweave:increment"; then
|
|
|
101
226
|
|
|
102
227
|
# Above hard cap: strong warning but NOT a block (user decides!)
|
|
103
228
|
if [[ "$ACTIVE_COUNT" -ge "$HARD_CAP" ]]; then
|
|
104
|
-
|
|
105
|
-
{
|
|
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."
|
|
108
|
-
}
|
|
109
|
-
EOF
|
|
229
|
+
printf '{"decision":"approve","systemMessage":"⚠️ WIP LIMIT EXCEEDED (%s/%s)\\n\\nYou have %s active increments (configured maximum: %s)\\n\\nActive increments:\\n%s\\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."}\n' "$ACTIVE_COUNT" "$HARD_CAP" "$ACTIVE_COUNT" "$HARD_CAP" "$ACTIVE_LIST"
|
|
110
230
|
exit 0
|
|
111
231
|
fi
|
|
112
232
|
|
|
113
233
|
# At soft limit: mild warning, approve
|
|
114
234
|
if [[ "$ACTIVE_COUNT" -ge "$SOFT_LIMIT" ]]; then
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
"decision": "approve",
|
|
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"
|
|
119
|
-
}
|
|
120
|
-
EOF
|
|
235
|
+
printf '{"decision":"approve","systemMessage":"⚠️ WIP LIMIT REACHED (%s/%s)\\n\\nYou have %s active increment(s) (recommended limit: %s)\\n\\nActive increments:\\n%s\\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"}\n' "$ACTIVE_COUNT" "$SOFT_LIMIT" "$ACTIVE_COUNT" "$SOFT_LIMIT" "$ACTIVE_LIST"
|
|
121
236
|
exit 0
|
|
122
237
|
fi
|
|
123
238
|
fi
|
|
@@ -163,12 +278,7 @@ if [[ -n "$ACTIVE_INCREMENT" ]] && echo "$PROMPT" | grep -qE "/(specweave:)?(syn
|
|
|
163
278
|
if [[ -f "$SPEC_FILE" ]] && [[ -f "$PLAN_FILE" ]]; then
|
|
164
279
|
# Check if spec is newer than plan (indicates spec changes need sync)
|
|
165
280
|
if [[ -n $(find "$SPEC_FILE" -newer "$PLAN_FILE" 2>/dev/null) ]]; then
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
"decision": "approve",
|
|
169
|
-
"systemMessage": "⚠️ Spec changes detected in $ACTIVE_INCREMENT\n\nspec.md has been modified after plan.md.\nConsider running /specweave:sync-docs to update living documentation."
|
|
170
|
-
}
|
|
171
|
-
EOF
|
|
281
|
+
printf '{"decision":"approve","systemMessage":"⚠️ Spec changes detected in %s\\n\\nspec.md has been modified after plan.md.\\nConsider running /specweave:sync-docs to update living documentation."}\n' "$ACTIVE_INCREMENT"
|
|
172
282
|
exit 0
|
|
173
283
|
fi
|
|
174
284
|
fi
|
|
@@ -253,19 +363,11 @@ fi
|
|
|
253
363
|
# ==============================================================================
|
|
254
364
|
|
|
255
365
|
if [[ -n "$CONTEXT" ]]; then
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
"decision":
|
|
259
|
-
"systemMessage": "$CONTEXT"
|
|
260
|
-
}
|
|
261
|
-
EOF
|
|
366
|
+
# Escape context for JSON (newlines, quotes)
|
|
367
|
+
CONTEXT_ESCAPED=$(echo "$CONTEXT" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
|
|
368
|
+
printf '{"decision":"approve","systemMessage":"%s"}\n' "$CONTEXT_ESCAPED"
|
|
262
369
|
else
|
|
263
|
-
|
|
264
|
-
cat <<EOF
|
|
265
|
-
{
|
|
266
|
-
"decision": "approve"
|
|
267
|
-
}
|
|
268
|
-
EOF
|
|
370
|
+
echo '{"decision":"approve"}'
|
|
269
371
|
fi
|
|
270
372
|
|
|
271
373
|
exit 0
|