gentyr 1.3.0
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/agents/antipattern-hunter.md +176 -0
- package/.claude/agents/code-reviewer.md +205 -0
- package/.claude/agents/code-writer.md +154 -0
- package/.claude/agents/deputy-cto.md +309 -0
- package/.claude/agents/feedback-agent.md +101 -0
- package/.claude/agents/investigator.md +136 -0
- package/.claude/agents/product-manager.md +97 -0
- package/.claude/agents/project-manager.md +116 -0
- package/.claude/agents/repo-hygiene-expert.md +626 -0
- package/.claude/agents/secret-manager.md +324 -0
- package/.claude/agents/test-writer.md +354 -0
- package/.claude/commands/configure-personas.md +144 -0
- package/.claude/commands/cto-report.md +36 -0
- package/.claude/commands/demo.md +89 -0
- package/.claude/commands/deputy-cto.md +345 -0
- package/.claude/commands/hotfix.md +31 -0
- package/.claude/commands/overdrive-gentyr.md +167 -0
- package/.claude/commands/product-manager.md +32 -0
- package/.claude/commands/push-migrations.md +86 -0
- package/.claude/commands/push-secrets.md +97 -0
- package/.claude/commands/services.json.example +30 -0
- package/.claude/commands/setup-gentyr.md +396 -0
- package/.claude/commands/show.md +42 -0
- package/.claude/commands/spawn-tasks.md +79 -0
- package/.claude/commands/toggle-automation-gentyr.md +75 -0
- package/.claude/commands/toggle-product-manager.md +19 -0
- package/.claude/commands/triage.md +69 -0
- package/.claude/hooks/README.md +686 -0
- package/.claude/hooks/__tests__/README.md +129 -0
- package/.claude/hooks/agent-tracker.js +434 -0
- package/.claude/hooks/antipattern-hunter-hook.js +401 -0
- package/.claude/hooks/api-key-watcher.js +289 -0
- package/.claude/hooks/block-no-verify.js +301 -0
- package/.claude/hooks/bypass-approval-hook.js +313 -0
- package/.claude/hooks/compliance-checker.js +1309 -0
- package/.claude/hooks/config-reader.js +143 -0
- package/.claude/hooks/credential-file-guard.js +1139 -0
- package/.claude/hooks/credential-health-check.js +168 -0
- package/.claude/hooks/credential-sync-hook.js +79 -0
- package/.claude/hooks/cto-notification-hook.js +656 -0
- package/.claude/hooks/feedback-launcher.js +424 -0
- package/.claude/hooks/feedback-orchestrator.js +367 -0
- package/.claude/hooks/gentyr-splash.js +47 -0
- package/.claude/hooks/gentyr-sync.js +389 -0
- package/.claude/hooks/hourly-automation.js +3340 -0
- package/.claude/hooks/key-sync.js +899 -0
- package/.claude/hooks/lib/approval-utils.js +731 -0
- package/.claude/hooks/lib/feature-branch-helper.js +102 -0
- package/.claude/hooks/lib/worktree-manager.js +330 -0
- package/.claude/hooks/mapping-validator.js +285 -0
- package/.claude/hooks/plan-executor.js +398 -0
- package/.claude/hooks/playwright-cli-guard.js +104 -0
- package/.claude/hooks/playwright-health-check.js +71 -0
- package/.claude/hooks/pre-commit-review.js +725 -0
- package/.claude/hooks/prompts/local-spec-enforcement.md +310 -0
- package/.claude/hooks/prompts/mapping-fix.md +92 -0
- package/.claude/hooks/prompts/mapping-review.md +140 -0
- package/.claude/hooks/prompts/schema-mapper.md +185 -0
- package/.claude/hooks/prompts/spec-enforcement.md +233 -0
- package/.claude/hooks/protected-action-approval-hook.js +336 -0
- package/.claude/hooks/protected-action-gate.js +562 -0
- package/.claude/hooks/protected-actions.json +208 -0
- package/.claude/hooks/protected-actions.json.template +122 -0
- package/.claude/hooks/quota-monitor.js +490 -0
- package/.claude/hooks/reporters/jest-failure-reporter.js +401 -0
- package/.claude/hooks/reporters/playwright-failure-reporter.js +446 -0
- package/.claude/hooks/reporters/vitest-failure-reporter.js +443 -0
- package/.claude/hooks/schema-mapper-hook.js +544 -0
- package/.claude/hooks/secret-leak-detector.js +216 -0
- package/.claude/hooks/session-reviver.js +514 -0
- package/.claude/hooks/slash-command-prefetch.js +1145 -0
- package/.claude/hooks/stale-work-detector.js +205 -0
- package/.claude/hooks/stop-continue-hook.js +414 -0
- package/.claude/hooks/todo-maintenance.js +522 -0
- package/.claude/hooks/todo-processing-prompt.md +75 -0
- package/.claude/hooks/usage-optimizer.js +791 -0
- package/.claude/mcp/README.md +246 -0
- package/.claude/settings.json.template +168 -0
- package/.mcp.json.template +207 -0
- package/CLAUDE.md +340 -0
- package/CLAUDE.md.gentyr-section +89 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/cli/commands/init.js +471 -0
- package/cli/commands/migrate.js +132 -0
- package/cli/commands/protect.js +271 -0
- package/cli/commands/scaffold.js +48 -0
- package/cli/commands/status.js +133 -0
- package/cli/commands/sync.js +101 -0
- package/cli/commands/uninstall.js +207 -0
- package/cli/index.js +111 -0
- package/cli/lib/config-gen.js +214 -0
- package/cli/lib/resolve-framework.js +97 -0
- package/cli/lib/state.js +140 -0
- package/cli/lib/symlinks.js +260 -0
- package/docs/AUTOMATION-SYSTEMS.md +484 -0
- package/docs/BINARY-PATCHING.md +212 -0
- package/docs/CHANGELOG.md +2830 -0
- package/docs/CREDENTIAL-DETECTION.md +151 -0
- package/docs/CTO-DASHBOARD.md +476 -0
- package/docs/DEPLOYMENT-FLOW.md +477 -0
- package/docs/DEVELOPER.md +116 -0
- package/docs/Executive.md +372 -0
- package/docs/SECRET-PATHS.md +77 -0
- package/docs/SETUP-GUIDE.md +419 -0
- package/docs/STACK.md +109 -0
- package/docs/TESTING.md +440 -0
- package/docs/assets/claude-logo.svg +3 -0
- package/docs/sessions/2026-01-24-spec-suite-implementation.md +190 -0
- package/docs/sessions/2026-02-15-feedback-e2e-audit.md +484 -0
- package/docs/sessions/2026-02-20-credential-rotation-experiments.md +340 -0
- package/docs/sessions/TEST-COVERAGE-REPORT-2026-02-20.md +168 -0
- package/docs/shared/EPHEMERAL-STATE-FILES.md +115 -0
- package/docs/shared/PROTECTION-SYSTEM.md +341 -0
- package/husky/post-commit +10 -0
- package/husky/pre-commit +40 -0
- package/husky/pre-push +94 -0
- package/package.json +43 -0
- package/packages/cto-dashboard/package-lock.json +3510 -0
- package/packages/cto-dashboard/package.json +41 -0
- package/packages/cto-dashboard/pnpm-lock.yaml +2168 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts +220 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js +376 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts +121 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js +180 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts +84 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js +309 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.js +8 -0
- package/packages/mcp-servers/dist/agent-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts +22 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.js +535 -0
- package/packages/mcp-servers/dist/agent-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts +258 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.js +81 -0
- package/packages/mcp-servers/dist/agent-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts +12 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js +919 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts +328 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js +128 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts +27 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js +167 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts +13 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js +959 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts +41 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js +8 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.js +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts +16 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.js +253 -0
- package/packages/mcp-servers/dist/cloudflare/server.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts +141 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.js +53 -0
- package/packages/mcp-servers/dist/cloudflare/types.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts +7 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.js +7 -0
- package/packages/mcp-servers/dist/codecov/index.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts +21 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.js +376 -0
- package/packages/mcp-servers/dist/codecov/server.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts +269 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.js +128 -0
- package/packages/mcp-servers/dist/codecov/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts +9 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.js +9 -0
- package/packages/mcp-servers/dist/cto-report/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts +14 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.js +859 -0
- package/packages/mcp-servers/dist/cto-report/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts +213 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.js +29 -0
- package/packages/mcp-servers/dist/cto-report/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.js +8 -0
- package/packages/mcp-servers/dist/cto-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts +20 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.js +538 -0
- package/packages/mcp-servers/dist/cto-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts +236 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.js +77 -0
- package/packages/mcp-servers/dist/cto-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts +7 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js +8 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts +23 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js +1700 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts +439 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js +102 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts +18 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js +259 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts +107 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js +31 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts +21 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js +580 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts +331 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js +40 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts +36 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js +392 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts +152 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js +67 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js.map +1 -0
- package/packages/mcp-servers/dist/github/index.d.ts +7 -0
- package/packages/mcp-servers/dist/github/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/index.js +7 -0
- package/packages/mcp-servers/dist/github/index.js.map +1 -0
- package/packages/mcp-servers/dist/github/server.d.ts +15 -0
- package/packages/mcp-servers/dist/github/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/server.js +686 -0
- package/packages/mcp-servers/dist/github/server.js.map +1 -0
- package/packages/mcp-servers/dist/github/types.d.ts +660 -0
- package/packages/mcp-servers/dist/github/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/types.js +209 -0
- package/packages/mcp-servers/dist/github/types.js.map +1 -0
- package/packages/mcp-servers/dist/index.d.ts +30 -0
- package/packages/mcp-servers/dist/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/index.js +32 -0
- package/packages/mcp-servers/dist/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts +15 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js +252 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts +74 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js +20 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.js +2 -0
- package/packages/mcp-servers/dist/onepassword/index.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.js +159 -0
- package/packages/mcp-servers/dist/onepassword/server.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts +55 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.js +22 -0
- package/packages/mcp-servers/dist/onepassword/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts +20 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.js +31 -0
- package/packages/mcp-servers/dist/playwright/helpers.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.js +5 -0
- package/packages/mcp-servers/dist/playwright/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts +13 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.js +1201 -0
- package/packages/mcp-servers/dist/playwright/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts +216 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.js +172 -0
- package/packages/mcp-servers/dist/playwright/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts +39 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js +71 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts +34 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js +538 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts +305 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js +123 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts +17 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.js +690 -0
- package/packages/mcp-servers/dist/product-manager/server.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts +286 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.js +99 -0
- package/packages/mcp-servers/dist/product-manager/types.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts +19 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js +174 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts +35 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js +465 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts +127 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js +80 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/render/index.d.ts +8 -0
- package/packages/mcp-servers/dist/render/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/index.js +8 -0
- package/packages/mcp-servers/dist/render/index.js.map +1 -0
- package/packages/mcp-servers/dist/render/server.d.ts +15 -0
- package/packages/mcp-servers/dist/render/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/server.js +428 -0
- package/packages/mcp-servers/dist/render/server.js.map +1 -0
- package/packages/mcp-servers/dist/render/types.d.ts +273 -0
- package/packages/mcp-servers/dist/render/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/types.js +102 -0
- package/packages/mcp-servers/dist/render/types.js.map +1 -0
- package/packages/mcp-servers/dist/resend/index.d.ts +7 -0
- package/packages/mcp-servers/dist/resend/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/index.js +7 -0
- package/packages/mcp-servers/dist/resend/index.js.map +1 -0
- package/packages/mcp-servers/dist/resend/server.d.ts +15 -0
- package/packages/mcp-servers/dist/resend/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/server.js +298 -0
- package/packages/mcp-servers/dist/resend/server.js.map +1 -0
- package/packages/mcp-servers/dist/resend/types.d.ts +222 -0
- package/packages/mcp-servers/dist/resend/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/types.js +58 -0
- package/packages/mcp-servers/dist/resend/types.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts +6 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.js +6 -0
- package/packages/mcp-servers/dist/review-queue/index.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts +17 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.js +348 -0
- package/packages/mcp-servers/dist/review-queue/server.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts +162 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.js +56 -0
- package/packages/mcp-servers/dist/review-queue/types.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts +19 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.js +1139 -0
- package/packages/mcp-servers/dist/secret-sync/server.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts +442 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.js +113 -0
- package/packages/mcp-servers/dist/secret-sync/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts +5 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.js +5 -0
- package/packages/mcp-servers/dist/session-events/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts +11 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.js +290 -0
- package/packages/mcp-servers/dist/session-events/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts +213 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.js +69 -0
- package/packages/mcp-servers/dist/session-events/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts +9 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.js +9 -0
- package/packages/mcp-servers/dist/session-restart/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts +20 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.js +411 -0
- package/packages/mcp-servers/dist/session-restart/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts +26 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.js +16 -0
- package/packages/mcp-servers/dist/session-restart/types.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.js +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts +14 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.js +454 -0
- package/packages/mcp-servers/dist/setup-helper/server.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts +81 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.js +41 -0
- package/packages/mcp-servers/dist/setup-helper/types.js.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts +31 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.js +126 -0
- package/packages/mcp-servers/dist/shared/audited-server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts +26 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.js +41 -0
- package/packages/mcp-servers/dist/shared/constants.js.map +1 -0
- package/packages/mcp-servers/dist/shared/index.d.ts +6 -0
- package/packages/mcp-servers/dist/shared/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/index.js +6 -0
- package/packages/mcp-servers/dist/shared/index.js.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts +11 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js +47 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts +20 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js +65 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js.map +1 -0
- package/packages/mcp-servers/dist/shared/server.d.ts +86 -0
- package/packages/mcp-servers/dist/shared/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/server.js +291 -0
- package/packages/mcp-servers/dist/shared/server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/types.d.ts +113 -0
- package/packages/mcp-servers/dist/shared/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/types.js +36 -0
- package/packages/mcp-servers/dist/shared/types.js.map +1 -0
- package/packages/mcp-servers/dist/show/server.d.ts +12 -0
- package/packages/mcp-servers/dist/show/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/server.js +97 -0
- package/packages/mcp-servers/dist/show/server.js.map +1 -0
- package/packages/mcp-servers/dist/show/types.d.ts +19 -0
- package/packages/mcp-servers/dist/show/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/types.js +32 -0
- package/packages/mcp-servers/dist/show/types.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.js +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts +13 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.js +692 -0
- package/packages/mcp-servers/dist/specs-browser/server.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts +337 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.js +134 -0
- package/packages/mcp-servers/dist/specs-browser/types.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts +10 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.js +10 -0
- package/packages/mcp-servers/dist/supabase/index.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts +20 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.js +451 -0
- package/packages/mcp-servers/dist/supabase/server.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts +196 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.js +76 -0
- package/packages/mcp-servers/dist/supabase/types.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts +5 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.js +5 -0
- package/packages/mcp-servers/dist/todo-db/index.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts +13 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.js +649 -0
- package/packages/mcp-servers/dist/todo-db/server.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts +225 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.js +69 -0
- package/packages/mcp-servers/dist/todo-db/types.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.js +8 -0
- package/packages/mcp-servers/dist/user-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts +25 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.js +914 -0
- package/packages/mcp-servers/dist/user-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts +415 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.js +132 -0
- package/packages/mcp-servers/dist/user-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts +9 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.js +9 -0
- package/packages/mcp-servers/dist/vercel/index.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts +17 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.js +265 -0
- package/packages/mcp-servers/dist/vercel/server.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts +189 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.js +65 -0
- package/packages/mcp-servers/dist/vercel/types.js.map +1 -0
- package/packages/mcp-servers/package-lock.json +3765 -0
- package/packages/mcp-servers/package.json +64 -0
- package/packages/mcp-servers/test/reporters/test-failure-reporter.ts +372 -0
- package/packages/mcp-servers/vitest.config.ts +27 -0
- package/scripts/__tests__/README.md +163 -0
- package/scripts/apply-credential-hardening.sh +271 -0
- package/scripts/credential-providers/manual.js +56 -0
- package/scripts/credential-providers/onepassword.js +85 -0
- package/scripts/credential-providers/provider-interface.js +104 -0
- package/scripts/encrypt-credential.js +337 -0
- package/scripts/feedback-launcher.js +338 -0
- package/scripts/feedback-orchestrator.js +373 -0
- package/scripts/fix-mcp-launcher-issues.sh +97 -0
- package/scripts/force-spawn-tasks.js +651 -0
- package/scripts/force-triage-reports.js +560 -0
- package/scripts/generate-protected-actions-spec.js +142 -0
- package/scripts/generate-proxy-certs.sh +158 -0
- package/scripts/grant-chrome-ext-permissions.sh +242 -0
- package/scripts/mcp-launcher.js +125 -0
- package/scripts/merge-settings.cjs +167 -0
- package/scripts/patch-clawd.py +844 -0
- package/scripts/patch-credential-cache.py +313 -0
- package/scripts/patches/credential-file-guard-patched.mjs +573 -0
- package/scripts/patches/credential-file-guard.js.patched +573 -0
- package/scripts/patches/verify-tokenizer.mjs +132 -0
- package/scripts/protect-framework.sh +478 -0
- package/scripts/readme-chrome.template +12 -0
- package/scripts/reap-completed-agents.js +439 -0
- package/scripts/reinstall.sh +86 -0
- package/scripts/resign-node.sh +185 -0
- package/scripts/rotation-proxy.js +656 -0
- package/scripts/rotation-stress-monitor.mjs +862 -0
- package/scripts/setup-automation-service.sh +648 -0
- package/scripts/setup-check.js +251 -0
- package/scripts/watch-claude-version.js +142 -0
- package/specs/framework/CORE-INVARIANTS.md +161 -0
- package/specs/patterns/AGENT-PATTERNS.md +223 -0
- package/specs/patterns/HOOK-PATTERNS.md +242 -0
- package/specs/patterns/MCP-SERVER-PATTERNS.md +144 -0
- package/templates/config/gitignore.template +14 -0
- package/templates/config/merge-chain-check.yml.template +51 -0
- package/templates/config/package.json.template +18 -0
- package/templates/config/pnpm-workspace.yaml +5 -0
- package/templates/config/services.json.template +18 -0
- package/templates/config/tsconfig.base.json +17 -0
- package/templates/scaffold/integrations/_template/.gitkeep +0 -0
- package/templates/scaffold/packages/logger/package.json +17 -0
- package/templates/scaffold/packages/logger/src/logger.ts +44 -0
- package/templates/scaffold/packages/shared/package.json +17 -0
- package/templates/scaffold/packages/shared/src/errors.ts +43 -0
- package/templates/scaffold/products/_product/apps/backend/package.json +21 -0
- package/templates/scaffold/products/_product/apps/backend/src/index.ts +17 -0
- package/templates/scaffold/products/_product/apps/extension/.gitkeep +0 -0
- package/templates/scaffold/products/_product/apps/web/.gitkeep +0 -0
- package/templates/scaffold/specs/global/.gitkeep +0 -0
- package/templates/scaffold/specs/local/.gitkeep +0 -0
- package/templates/scaffold/specs/reference/.gitkeep +0 -0
- package/version.json +15 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stale Work Detector for GENTYR Framework
|
|
5
|
+
*
|
|
6
|
+
* Detects stale work that may need attention:
|
|
7
|
+
* 1. Uncommitted changes (git status --porcelain)
|
|
8
|
+
* 2. Unpushed commits on local branches
|
|
9
|
+
* 3. Stale remote feature branches with no recent PR activity
|
|
10
|
+
*
|
|
11
|
+
* Returns a structured report for deputy-CTO briefing.
|
|
12
|
+
*
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
|
|
18
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
19
|
+
const DEFAULT_STALE_DAYS = 3;
|
|
20
|
+
|
|
21
|
+
const GIT_EXEC_OPTIONS = { cwd: PROJECT_DIR, encoding: 'utf8', timeout: 15000, stdio: 'pipe' };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect uncommitted changes via git status --porcelain.
|
|
25
|
+
* @returns {string[]} List of uncommitted file status lines
|
|
26
|
+
*/
|
|
27
|
+
function getUncommittedFiles() {
|
|
28
|
+
const status = execSync('git status --porcelain', { cwd: PROJECT_DIR, encoding: 'utf8', timeout: 10000, stdio: 'pipe' }).trim();
|
|
29
|
+
return status ? status.split('\n').map(l => l.trim()) : [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect local branches with commits not pushed to their remote tracking branch.
|
|
34
|
+
* @returns {Array<{ branch: string, commitCount: number }>}
|
|
35
|
+
*/
|
|
36
|
+
function getUnpushedBranches() {
|
|
37
|
+
const branchOutput = execSync("git branch --format='%(refname:short)'", GIT_EXEC_OPTIONS).trim();
|
|
38
|
+
if (!branchOutput) return [];
|
|
39
|
+
|
|
40
|
+
const branches = branchOutput.split('\n').map(b => b.trim().replace(/^'|'$/g, '')).filter(Boolean);
|
|
41
|
+
const unpushed = [];
|
|
42
|
+
|
|
43
|
+
for (const branch of branches) {
|
|
44
|
+
try {
|
|
45
|
+
const logOutput = execSync(`git log origin/${branch}..${branch} --oneline`, GIT_EXEC_OPTIONS).trim();
|
|
46
|
+
if (logOutput) {
|
|
47
|
+
const commitCount = logOutput.split('\n').length;
|
|
48
|
+
unpushed.push({ branch, commitCount });
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// Branch may not have a remote tracking branch - skip it
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return unpushed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a branch has a PR and whether that PR is also stale.
|
|
60
|
+
* @param {string} branchName - Branch name without origin/ prefix
|
|
61
|
+
* @param {number} staleDays - Number of days to consider stale
|
|
62
|
+
* @returns {{ hasPR: boolean, prStale: boolean }}
|
|
63
|
+
*/
|
|
64
|
+
function checkPRStatus(branchName, staleDays) {
|
|
65
|
+
try {
|
|
66
|
+
const prJson = execSync(
|
|
67
|
+
`gh pr list --head ${branchName} --state all --json updatedAt --limit 1`,
|
|
68
|
+
GIT_EXEC_OPTIONS
|
|
69
|
+
).trim();
|
|
70
|
+
const prs = JSON.parse(prJson);
|
|
71
|
+
|
|
72
|
+
if (!prs || prs.length === 0) {
|
|
73
|
+
return { hasPR: false, prStale: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const prUpdatedAt = new Date(prs[0].updatedAt);
|
|
77
|
+
const prAgeDays = (Date.now() - prUpdatedAt.getTime()) / (1000 * 86400);
|
|
78
|
+
return { hasPR: true, prStale: prAgeDays > staleDays };
|
|
79
|
+
} catch {
|
|
80
|
+
// gh CLI may not be available or authenticated
|
|
81
|
+
return { hasPR: false, prStale: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Detect remote feature branches that have gone stale.
|
|
87
|
+
* @param {number} staleDays - Number of days without activity to consider stale
|
|
88
|
+
* @returns {Array<{ branch: string, lastCommitAge: number, lastCommitDate: string, hasPR: boolean }>}
|
|
89
|
+
*/
|
|
90
|
+
function getStaleBranches(staleDays) {
|
|
91
|
+
let branchOutput;
|
|
92
|
+
try {
|
|
93
|
+
branchOutput = execSync("git branch -r --list 'origin/feature/*'", GIT_EXEC_OPTIONS).trim();
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!branchOutput) return [];
|
|
99
|
+
|
|
100
|
+
const remoteBranches = branchOutput.split('\n').map(b => b.trim()).filter(Boolean);
|
|
101
|
+
const staleBranches = [];
|
|
102
|
+
|
|
103
|
+
for (const remoteBranch of remoteBranches) {
|
|
104
|
+
try {
|
|
105
|
+
const timestampStr = execSync(`git log -1 --format=%ct ${remoteBranch}`, GIT_EXEC_OPTIONS).trim();
|
|
106
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
107
|
+
|
|
108
|
+
if (isNaN(timestamp)) continue;
|
|
109
|
+
|
|
110
|
+
const ageDays = (Date.now() / 1000 - timestamp) / 86400;
|
|
111
|
+
|
|
112
|
+
if (ageDays > staleDays) {
|
|
113
|
+
const branchWithoutOrigin = remoteBranch.replace(/^origin\//, '');
|
|
114
|
+
const { hasPR, prStale } = checkPRStatus(branchWithoutOrigin, staleDays);
|
|
115
|
+
|
|
116
|
+
// Only report as stale if there's no PR or the PR itself is stale
|
|
117
|
+
if (!hasPR || prStale) {
|
|
118
|
+
const lastCommitDate = new Date(timestamp * 1000).toISOString();
|
|
119
|
+
staleBranches.push({
|
|
120
|
+
branch: branchWithoutOrigin,
|
|
121
|
+
lastCommitAge: Math.round(ageDays),
|
|
122
|
+
lastCommitDate,
|
|
123
|
+
hasPR
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Skip branches we can't inspect
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return staleBranches;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Detect all stale work in the project.
|
|
137
|
+
* @param {object} options
|
|
138
|
+
* @param {number} [options.staleDays=3] - Number of days to consider a branch stale
|
|
139
|
+
* @returns {{ uncommittedFiles: string[], unpushedBranches: Array<{ branch: string, commitCount: number }>, staleBranches: Array<{ branch: string, lastCommitAge: number, lastCommitDate: string, hasPR: boolean }>, hasIssues: boolean, timestamp: string }}
|
|
140
|
+
*/
|
|
141
|
+
function detectStaleWork(options = {}) {
|
|
142
|
+
const staleDays = options.staleDays ?? DEFAULT_STALE_DAYS;
|
|
143
|
+
|
|
144
|
+
const uncommittedFiles = getUncommittedFiles();
|
|
145
|
+
const unpushedBranches = getUnpushedBranches();
|
|
146
|
+
const staleBranches = getStaleBranches(staleDays);
|
|
147
|
+
|
|
148
|
+
const hasIssues = uncommittedFiles.length > 0 || unpushedBranches.length > 0 || staleBranches.length > 0;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
uncommittedFiles,
|
|
152
|
+
unpushedBranches,
|
|
153
|
+
staleBranches,
|
|
154
|
+
hasIssues,
|
|
155
|
+
timestamp: new Date().toISOString()
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Format a stale work report into a human-readable string for deputy-CTO briefing.
|
|
161
|
+
* @param {{ uncommittedFiles: string[], unpushedBranches: Array<{ branch: string, commitCount: number }>, staleBranches: Array<{ branch: string, lastCommitAge: number, lastCommitDate: string, hasPR: boolean }>, hasIssues: boolean, timestamp: string }} report
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
function formatReport(report) {
|
|
165
|
+
const lines = ['## Stale Work Report', ''];
|
|
166
|
+
|
|
167
|
+
if (report.uncommittedFiles.length > 0) {
|
|
168
|
+
lines.push('### Uncommitted Changes');
|
|
169
|
+
for (const file of report.uncommittedFiles) {
|
|
170
|
+
lines.push(`- ${file}`);
|
|
171
|
+
}
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (report.unpushedBranches.length > 0) {
|
|
176
|
+
lines.push('### Unpushed Branches');
|
|
177
|
+
for (const { branch, commitCount } of report.unpushedBranches) {
|
|
178
|
+
lines.push(`- ${branch} (${commitCount} commit${commitCount === 1 ? '' : 's'} ahead)`);
|
|
179
|
+
}
|
|
180
|
+
lines.push('');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (report.staleBranches.length > 0) {
|
|
184
|
+
lines.push(`### Stale Feature Branches (>${DEFAULT_STALE_DAYS} days)`);
|
|
185
|
+
for (const { branch, lastCommitAge, hasPR } of report.staleBranches) {
|
|
186
|
+
const prStatus = hasPR ? 'PR stale' : 'no PR';
|
|
187
|
+
lines.push(`- ${branch} (${lastCommitAge} day${lastCommitAge === 1 ? '' : 's'}, ${prStatus})`);
|
|
188
|
+
}
|
|
189
|
+
lines.push('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!report.hasIssues) {
|
|
193
|
+
lines.push('No stale work detected.');
|
|
194
|
+
lines.push('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
lines.push('---');
|
|
198
|
+
lines.push(`Generated: ${report.timestamp}`);
|
|
199
|
+
|
|
200
|
+
return lines.join('\n');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export { detectStaleWork, formatReport, DEFAULT_STALE_DAYS };
|
|
204
|
+
|
|
205
|
+
export default { detectStaleWork, formatReport, DEFAULT_STALE_DAYS };
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stop Hook - Auto-continue for automated [Task] sessions + quota death detection
|
|
5
|
+
*
|
|
6
|
+
* This hook forces one continuation cycle for spawned sessions that begin with "[Task]".
|
|
7
|
+
* It checks:
|
|
8
|
+
* 1. Was the initial prompt tagged with "[Task]"? (automated session)
|
|
9
|
+
* 2. Is the session dying from a quota/rate limit? (detect and record for revival)
|
|
10
|
+
* 3. Is stop_hook_active false? (first stop, not already continuing)
|
|
11
|
+
*
|
|
12
|
+
* Quota detection: If the last JSONL entries show error:"rate_limit" + isApiErrorMessage,
|
|
13
|
+
* the hook writes recovery state and approves the stop immediately (instead of wasting
|
|
14
|
+
* the one remaining API call on a doomed retry). The session-reviver picks up later.
|
|
15
|
+
*
|
|
16
|
+
* It also attempts credential rotation when quota is hit, so the next retry (if any)
|
|
17
|
+
* or the revived session will use fresh credentials.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createInterface } from 'readline';
|
|
21
|
+
import fs from 'fs';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
import {
|
|
24
|
+
readRotationState,
|
|
25
|
+
writeRotationState,
|
|
26
|
+
logRotationEvent,
|
|
27
|
+
updateActiveCredentials,
|
|
28
|
+
checkKeyHealth,
|
|
29
|
+
selectActiveKey,
|
|
30
|
+
refreshExpiredToken,
|
|
31
|
+
} from './key-sync.js';
|
|
32
|
+
|
|
33
|
+
// Debug logging - writes to file since stdout is used for hook response
|
|
34
|
+
const DEBUG = true;
|
|
35
|
+
const DEBUG_LOG_PATH = path.join(process.cwd(), '.claude', 'hooks', 'stop-hook-debug.log');
|
|
36
|
+
|
|
37
|
+
function debugLog(message, data = null) {
|
|
38
|
+
if (!DEBUG) return;
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
let logLine = `[${timestamp}] ${message}`;
|
|
41
|
+
if (data !== null) {
|
|
42
|
+
logLine += '\n' + JSON.stringify(data, null, 2);
|
|
43
|
+
}
|
|
44
|
+
logLine += '\n---\n';
|
|
45
|
+
try {
|
|
46
|
+
fs.appendFileSync(DEBUG_LOG_PATH, logLine);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Ignore write errors
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function readStdin() {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
let data = '';
|
|
55
|
+
const rl = createInterface({ input: process.stdin });
|
|
56
|
+
rl.on('line', (line) => { data += line; });
|
|
57
|
+
rl.on('close', () => { resolve(data); });
|
|
58
|
+
// Timeout after 100ms if no data
|
|
59
|
+
setTimeout(() => { rl.close(); resolve(data); }, 100);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const STATE_DIR = path.join(process.cwd(), '.claude', 'state');
|
|
64
|
+
const QUOTA_INTERRUPTED_PATH = path.join(STATE_DIR, 'quota-interrupted-sessions.json');
|
|
65
|
+
const TAIL_BYTES = 8192;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read the last N bytes of a file (seek to end, no full-file read).
|
|
69
|
+
*/
|
|
70
|
+
function readTail(filePath, numBytes) {
|
|
71
|
+
let fd;
|
|
72
|
+
try {
|
|
73
|
+
fd = fs.openSync(filePath, 'r');
|
|
74
|
+
const stat = fs.fstatSync(fd);
|
|
75
|
+
const start = Math.max(0, stat.size - numBytes);
|
|
76
|
+
const buf = Buffer.alloc(Math.min(numBytes, stat.size));
|
|
77
|
+
const bytesRead = fs.readSync(fd, buf, 0, buf.length, start);
|
|
78
|
+
return buf.toString('utf8', 0, bytesRead);
|
|
79
|
+
} catch {
|
|
80
|
+
return '';
|
|
81
|
+
} finally {
|
|
82
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if the session is dying from a quota/rate limit by examining recent JSONL entries.
|
|
88
|
+
* Returns { isQuotaDeath: boolean, quotaMessage?: string }
|
|
89
|
+
*/
|
|
90
|
+
function detectQuotaDeath(transcriptPath) {
|
|
91
|
+
if (!transcriptPath) return { isQuotaDeath: false };
|
|
92
|
+
|
|
93
|
+
const tail = readTail(transcriptPath, TAIL_BYTES);
|
|
94
|
+
if (!tail) return { isQuotaDeath: false };
|
|
95
|
+
|
|
96
|
+
const lines = tail.split('\n').filter(l => l.trim());
|
|
97
|
+
|
|
98
|
+
// Check last 5 parseable entries for rate_limit error
|
|
99
|
+
let checked = 0;
|
|
100
|
+
for (let i = lines.length - 1; i >= 0 && checked < 5; i--) {
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = JSON.parse(lines[i]);
|
|
104
|
+
} catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
checked++;
|
|
108
|
+
|
|
109
|
+
if (parsed.error === 'rate_limit' && parsed.isApiErrorMessage === true) {
|
|
110
|
+
const quotaMessage = parsed.message?.content?.[0]?.text || 'Rate limit reached';
|
|
111
|
+
return { isQuotaDeath: true, quotaMessage };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { isQuotaDeath: false };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract agent ID from the first user message in transcript.
|
|
120
|
+
*/
|
|
121
|
+
function extractAgentId(transcriptPath) {
|
|
122
|
+
if (!transcriptPath) return null;
|
|
123
|
+
try {
|
|
124
|
+
// Read first 4KB for the initial prompt
|
|
125
|
+
let fd;
|
|
126
|
+
try {
|
|
127
|
+
fd = fs.openSync(transcriptPath, 'r');
|
|
128
|
+
const buf = Buffer.alloc(4096);
|
|
129
|
+
const bytesRead = fs.readSync(fd, buf, 0, 4096, 0);
|
|
130
|
+
const head = buf.toString('utf8', 0, bytesRead);
|
|
131
|
+
const match = head.match(/\[AGENT:(agent-[^\]]+)\]/);
|
|
132
|
+
return match ? match[1] : null;
|
|
133
|
+
} finally {
|
|
134
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Write a quota-interrupted session record for the session-reviver to pick up.
|
|
143
|
+
*/
|
|
144
|
+
function writeQuotaInterruptedSession(record) {
|
|
145
|
+
try {
|
|
146
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
147
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let data = { sessions: [] };
|
|
151
|
+
if (fs.existsSync(QUOTA_INTERRUPTED_PATH)) {
|
|
152
|
+
data = JSON.parse(fs.readFileSync(QUOTA_INTERRUPTED_PATH, 'utf8'));
|
|
153
|
+
if (!Array.isArray(data.sessions)) data.sessions = [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Don't duplicate
|
|
157
|
+
const existingIdx = data.sessions.findIndex(
|
|
158
|
+
s => s.transcriptPath === record.transcriptPath
|
|
159
|
+
);
|
|
160
|
+
if (existingIdx >= 0) {
|
|
161
|
+
data.sessions[existingIdx] = record;
|
|
162
|
+
} else {
|
|
163
|
+
data.sessions.push(record);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Clean up entries older than 30 minutes (matches session-reviver reader window)
|
|
167
|
+
const cutoff = Date.now() - 30 * 60 * 1000;
|
|
168
|
+
data.sessions = data.sessions.filter(s => new Date(s.interruptedAt).getTime() > cutoff);
|
|
169
|
+
|
|
170
|
+
fs.writeFileSync(QUOTA_INTERRUPTED_PATH, JSON.stringify(data, null, 2), 'utf8');
|
|
171
|
+
} catch {
|
|
172
|
+
// Non-fatal
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Attempt credential rotation when quota is hit.
|
|
178
|
+
* Returns true if rotation succeeded (a usable key was found and swapped).
|
|
179
|
+
*/
|
|
180
|
+
async function attemptQuotaRotation() {
|
|
181
|
+
try {
|
|
182
|
+
const state = readRotationState();
|
|
183
|
+
if (!state.active_key_id) return false;
|
|
184
|
+
|
|
185
|
+
// Refresh expired tokens before health-check so they can be candidates
|
|
186
|
+
for (const [keyId, keyData] of Object.entries(state.keys)) {
|
|
187
|
+
if (keyData.status === 'expired' && keyData.expiresAt && keyData.expiresAt < Date.now()) {
|
|
188
|
+
try {
|
|
189
|
+
const refreshed = await refreshExpiredToken(keyData);
|
|
190
|
+
if (refreshed === 'invalid_grant') {
|
|
191
|
+
keyData.status = 'invalid';
|
|
192
|
+
logRotationEvent(state, {
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
event: 'key_removed',
|
|
195
|
+
key_id: keyId,
|
|
196
|
+
reason: 'refresh_token_invalid_grant',
|
|
197
|
+
});
|
|
198
|
+
debugLog(`Refresh token revoked for key ${keyId.slice(0, 8)}... — marked invalid`);
|
|
199
|
+
} else if (refreshed) {
|
|
200
|
+
keyData.accessToken = refreshed.accessToken;
|
|
201
|
+
keyData.refreshToken = refreshed.refreshToken;
|
|
202
|
+
keyData.expiresAt = refreshed.expiresAt;
|
|
203
|
+
keyData.status = 'active';
|
|
204
|
+
logRotationEvent(state, {
|
|
205
|
+
timestamp: Date.now(),
|
|
206
|
+
event: 'key_added',
|
|
207
|
+
key_id: keyId,
|
|
208
|
+
reason: 'token_refreshed_by_stop_hook',
|
|
209
|
+
});
|
|
210
|
+
debugLog(`Refreshed expired token for key ${keyId.slice(0, 8)}...`);
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// Non-fatal: key stays expired
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
writeRotationState(state);
|
|
218
|
+
|
|
219
|
+
// Health-check all keys to get fresh usage data
|
|
220
|
+
const healthPromises = Object.entries(state.keys)
|
|
221
|
+
.filter(([_, k]) => k.status !== 'invalid' && k.status !== 'expired')
|
|
222
|
+
.map(async ([keyId, keyData]) => {
|
|
223
|
+
const result = await checkKeyHealth(keyData.accessToken);
|
|
224
|
+
if (result.valid && result.usage) {
|
|
225
|
+
keyData.last_health_check = Date.now();
|
|
226
|
+
keyData.last_usage = { ...result.usage, checked_at: Date.now() };
|
|
227
|
+
}
|
|
228
|
+
return { keyId, result };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await Promise.all(healthPromises);
|
|
232
|
+
writeRotationState(state);
|
|
233
|
+
|
|
234
|
+
const selectedKeyId = selectActiveKey(state);
|
|
235
|
+
if (selectedKeyId && selectedKeyId !== state.active_key_id) {
|
|
236
|
+
const previousKeyId = state.active_key_id;
|
|
237
|
+
state.active_key_id = selectedKeyId;
|
|
238
|
+
const selectedKey = state.keys[selectedKeyId];
|
|
239
|
+
selectedKey.last_used_at = Date.now();
|
|
240
|
+
|
|
241
|
+
logRotationEvent(state, {
|
|
242
|
+
timestamp: Date.now(),
|
|
243
|
+
event: 'key_switched',
|
|
244
|
+
key_id: selectedKeyId,
|
|
245
|
+
reason: `stop_hook_quota_rotation_from_${previousKeyId.slice(0, 8)}`,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
updateActiveCredentials(selectedKey);
|
|
249
|
+
writeRotationState(state);
|
|
250
|
+
debugLog('Quota rotation succeeded', { from: previousKeyId.slice(0, 8), to: selectedKeyId.slice(0, 8) });
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return false;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
debugLog('Quota rotation error', { error: err.message });
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function main() {
|
|
262
|
+
debugLog('Stop hook triggered');
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const stdinData = await readStdin();
|
|
266
|
+
|
|
267
|
+
debugLog('Raw stdin data', stdinData ? stdinData.substring(0, 2000) : '(empty)');
|
|
268
|
+
|
|
269
|
+
if (!stdinData) {
|
|
270
|
+
// No input, allow stop
|
|
271
|
+
debugLog('No stdin data, allowing stop');
|
|
272
|
+
console.log(JSON.stringify({ decision: 'approve' }));
|
|
273
|
+
process.exit(0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const input = JSON.parse(stdinData);
|
|
277
|
+
|
|
278
|
+
debugLog('Parsed input keys', Object.keys(input));
|
|
279
|
+
debugLog('Full input structure', input);
|
|
280
|
+
|
|
281
|
+
// Check if this is an automated [Task] session
|
|
282
|
+
// The initial prompt should be in the conversation history
|
|
283
|
+
const isTaskSession = checkIfTaskSession(input);
|
|
284
|
+
|
|
285
|
+
// Check for quota death BEFORE continuation logic
|
|
286
|
+
// If this is a rate_limit death, don't waste the API call on a continuation
|
|
287
|
+
if (isTaskSession) {
|
|
288
|
+
const quotaCheck = detectQuotaDeath(input.transcript_path);
|
|
289
|
+
if (quotaCheck.isQuotaDeath) {
|
|
290
|
+
debugLog('Quota death detected', { quotaMessage: quotaCheck.quotaMessage });
|
|
291
|
+
|
|
292
|
+
// Attempt credential rotation for the next session/revival
|
|
293
|
+
const rotated = await attemptQuotaRotation();
|
|
294
|
+
|
|
295
|
+
// Write recovery state for session-reviver
|
|
296
|
+
const agentId = extractAgentId(input.transcript_path);
|
|
297
|
+
writeQuotaInterruptedSession({
|
|
298
|
+
sessionId: input.session_id || null,
|
|
299
|
+
transcriptPath: input.transcript_path,
|
|
300
|
+
agentId,
|
|
301
|
+
quotaMessage: quotaCheck.quotaMessage,
|
|
302
|
+
interruptedAt: new Date().toISOString(),
|
|
303
|
+
credentialsRotated: rotated,
|
|
304
|
+
status: 'pending_revival',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
debugLog('Decision: APPROVE (quota death - recorded for revival)', {
|
|
308
|
+
agentId,
|
|
309
|
+
rotated,
|
|
310
|
+
quotaMessage: quotaCheck.quotaMessage,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
console.log(JSON.stringify({ decision: 'approve' }));
|
|
314
|
+
process.exit(0);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check if we're already in a continuation cycle
|
|
319
|
+
const alreadyContinuing = input.stop_hook_active === true;
|
|
320
|
+
|
|
321
|
+
debugLog('Decision factors', {
|
|
322
|
+
isTaskSession,
|
|
323
|
+
alreadyContinuing,
|
|
324
|
+
stop_hook_active: input.stop_hook_active,
|
|
325
|
+
CLAUDE_SPAWNED_SESSION: process.env.CLAUDE_SPAWNED_SESSION
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (isTaskSession && !alreadyContinuing) {
|
|
329
|
+
// First stop of a [Task] session - force one continuation
|
|
330
|
+
debugLog('Decision: BLOCK (first stop of [Task] session)');
|
|
331
|
+
console.log(JSON.stringify({
|
|
332
|
+
decision: 'block',
|
|
333
|
+
reason: 'If there is more work to investigate or resolve related to the initial [Task] request, continue working. Otherwise, you may stop.'
|
|
334
|
+
}));
|
|
335
|
+
} else {
|
|
336
|
+
// Either not a [Task] session, or already continued once - allow stop
|
|
337
|
+
debugLog('Decision: APPROVE', { reason: isTaskSession ? 'already continued once' : 'not a [Task] session' });
|
|
338
|
+
console.log(JSON.stringify({ decision: 'approve' }));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
process.exit(0);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
// On error, allow stop (fail open)
|
|
344
|
+
debugLog('Error in hook', { error: err.message, stack: err.stack });
|
|
345
|
+
console.error(`Stop hook error: ${err.message}`);
|
|
346
|
+
console.log(JSON.stringify({ decision: 'approve' }));
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check if this session started with a [Task] prefix
|
|
353
|
+
* @param {object} input - Hook input containing conversation context
|
|
354
|
+
* @returns {boolean}
|
|
355
|
+
*/
|
|
356
|
+
function checkIfTaskSession(input) {
|
|
357
|
+
// The Stop hook only receives: session_id, transcript_path, cwd, permission_mode, hook_event_name, stop_hook_active
|
|
358
|
+
// We need to read the transcript file to find the initial prompt
|
|
359
|
+
|
|
360
|
+
// 1. Read first 4KB of transcript file to find first user message (avoid full file read)
|
|
361
|
+
if (input.transcript_path) {
|
|
362
|
+
debugLog('Reading transcript head', input.transcript_path);
|
|
363
|
+
try {
|
|
364
|
+
let fd;
|
|
365
|
+
let transcriptHead;
|
|
366
|
+
try {
|
|
367
|
+
fd = fs.openSync(input.transcript_path, 'r');
|
|
368
|
+
const buf = Buffer.alloc(4096);
|
|
369
|
+
const bytesRead = fs.readSync(fd, buf, 0, 4096, 0);
|
|
370
|
+
transcriptHead = buf.toString('utf8', 0, bytesRead);
|
|
371
|
+
} finally {
|
|
372
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
373
|
+
}
|
|
374
|
+
const lines = transcriptHead.split('\n').filter(line => line.trim());
|
|
375
|
+
|
|
376
|
+
// JSONL format - each line is a JSON object
|
|
377
|
+
for (const line of lines.slice(0, 10)) { // Check first 10 lines
|
|
378
|
+
try {
|
|
379
|
+
const entry = JSON.parse(line);
|
|
380
|
+
|
|
381
|
+
// Look for human/user message type
|
|
382
|
+
if (entry.type === 'human' || entry.type === 'user') {
|
|
383
|
+
const content = entry.message?.content || entry.content || '';
|
|
384
|
+
debugLog('Found user message', content.substring(0, 300));
|
|
385
|
+
|
|
386
|
+
if (content.startsWith('[Task]')) {
|
|
387
|
+
debugLog('[Task] found in transcript first user message');
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
// Only check first user message
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
} catch (parseErr) {
|
|
394
|
+
// Skip malformed lines
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (err) {
|
|
399
|
+
debugLog('Error reading transcript', { error: err.message });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 2. Fallback: Check for CLAUDE_SPAWNED_SESSION env var
|
|
404
|
+
// This is set by hooks when spawning background agents
|
|
405
|
+
if (process.env.CLAUDE_SPAWNED_SESSION === 'true') {
|
|
406
|
+
debugLog('[Task] detected via CLAUDE_SPAWNED_SESSION env var');
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
debugLog('No [Task] marker found');
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
main();
|