heyspex 0.1.0 → 0.2.1
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-plugin/marketplace.json +14 -0
- package/README.md +21 -0
- package/bin/hs.mjs +1089 -104
- package/bin/spexcode.mjs +262 -0
- package/dev-factory/engine/worktree.mjs +740 -0
- package/dev-factory/lib/code-repo-root.mjs +67 -0
- package/dev-factory/lib/context-budget.mjs +84 -0
- package/dev-factory/lib/epic-detect.mjs +190 -0
- package/dev-factory/lib/fleet-queue-file.mjs +846 -0
- package/dev-factory/lib/fleet-queue.mjs +426 -0
- package/dev-factory/lib/fleet-state.mjs +14 -18
- package/dev-factory/lib/model-catalog.mjs +338 -0
- package/dev-factory/lib/seat-context.mjs +199 -0
- package/dev-factory/lib/spec-io.mjs +93 -0
- package/dev-factory/lib/sufficiency-score.mjs +8 -2
- package/dev-factory/lib/worktree-allocator.mjs +744 -0
- package/dev-factory/model-catalog.json +222 -0
- package/dist/cli/hs-check.mjs +1127 -103
- package/dist/cli/hs-claim.mjs +981 -63
- package/dist/cli/hs-connect.mjs +10059 -1729
- package/dist/cli/hs-defer.mjs +5507 -3072
- package/dist/cli/hs-dev.mjs +576 -0
- package/dist/cli/hs-env.mjs +416 -302
- package/dist/cli/hs-index-rebuild.mjs +7334 -0
- package/dist/cli/hs-index-reconcile.mjs +8138 -0
- package/dist/cli/hs-init.mjs +2840 -149
- package/dist/cli/hs-install.mjs +7214 -594
- package/dist/cli/hs-join.mjs +10640 -2421
- package/dist/cli/hs-vault-pull.mjs +8157 -6370
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +4029 -1555
- package/dist/cli/hs-workspace.mjs +2702 -538
- package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
- package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
- package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
- package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
- package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
- package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
- package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
- package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
- package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
- package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
- package/docs/copilot-plugin-install.md +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/docs/mcp-client-config.md +259 -6
- package/package.json +249 -124
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
- package/plugins/heyspex/.mcp.json +12 -0
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +80 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
- package/plugins/heyspex/hooks/hooks.json +418 -0
- package/plugins/heyspex/mcp.json +13 -0
- package/scripts/check-mcp-freshness.mjs +17 -9
- package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
- package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
- package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
- package/scripts/claude-hooks/_run.mjs +413 -0
- package/scripts/claude-hooks/_transcript-text.mjs +60 -0
- package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
- package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
- package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
- package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
- package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
- package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
- package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
- package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
- package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
- package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
- package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
- package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
- package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
- package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
- package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
- package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
- package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
- package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
- package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
- package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
- package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
- package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
- package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
- package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
- package/scripts/claude-hooks/log-session-activity.mjs +341 -0
- package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
- package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
- package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
- package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
- package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
- package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
- package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
- package/scripts/claude-hooks/research-method-guard.mjs +398 -0
- package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
- package/scripts/claude-hooks/session-end.mjs +451 -0
- package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
- package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
- package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
- package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
- package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
- package/scripts/cursor-hooks/manifest.mjs +281 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +288 -0
- package/scripts/hs-auto-resolve.mjs +240 -0
- package/scripts/hs-auto-source.mjs +398 -0
- package/scripts/hs-auto.mjs +134 -0
- package/scripts/hs-capture-cli.mjs +326 -0
- package/scripts/hs-classifier.mjs +136 -0
- package/scripts/hs-companion.mjs +88 -0
- package/scripts/hs-gateway.mjs +115 -0
- package/scripts/hs-mode.mjs +125 -0
- package/scripts/hs-next.mjs +441 -0
- package/scripts/hs-settings.mjs +338 -0
- package/scripts/hs-view.mjs +496 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +272 -0
- package/scripts/install-cursor-hooks.mjs +471 -0
- package/scripts/lib/agent-env-markers.mjs +38 -0
- package/scripts/lib/console-url-resolver.mjs +199 -61
- package/scripts/lib/customer-kit.mjs +1203 -0
- package/scripts/lib/dev-mode.mjs +249 -32
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-commit.mjs +59 -5
- package/scripts/lib/hs-source-identity.mjs +526 -0
- package/scripts/lib/hs-source-log.mjs +13 -22
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -307
- package/scripts/lib/hs-worktree-args.mjs +115 -0
- package/scripts/lib/shared-path-helpers.mjs +134 -0
- package/scripts/lib/standalone-doctor.mjs +540 -3
- package/scripts/lib/subagent-git-lock.mjs +109 -0
- package/scripts/lib/template-pin.mjs +34 -0
- package/scripts/lib/window-placement.mjs +109 -0
- package/scripts/provenance-lib.mjs +91 -0
- package/scripts/stamp-provenance-on-edit.mjs +222 -0
- package/scripts/wip-ledger-health.mjs +653 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
- package/spexharness/src/adapters/audit.mjs +197 -0
- package/spexharness/src/adapters/claude-code.mjs +449 -0
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/chat/tui/ansi.mjs +448 -0
- package/spexharness/src/chat/tui/palette.mjs +22 -0
- package/spexharness/src/chat/tui/select.mjs +208 -0
- package/spexharness/src/config-root.mjs +54 -0
- package/spexharness/src/context-budget.mjs +141 -0
- package/spexharness/src/dev-server-control.mjs +389 -0
- package/spexharness/src/doctrine/behaviour.mjs +371 -0
- package/spexharness/src/doctrine/corpus.mjs +733 -0
- package/spexharness/src/doctrine/inject.mjs +338 -0
- package/spexharness/src/doctrine/merge.mjs +184 -0
- package/spexharness/src/doctrine/sources.mjs +252 -0
- package/spexharness/src/doctrine/synthesize.mjs +401 -0
- package/spexharness/src/doctrine/vault-source.mjs +574 -0
- package/spexharness/src/doctrine/writer.mjs +176 -0
- package/spexharness/src/events.mjs +84 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
- package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
- package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
- package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
- package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
- package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
- package/spexharness/src/gates/git-identity-guard.mjs +61 -0
- package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
- package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
- package/spexharness/src/gates/git-stash-gate.mjs +159 -0
- package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
- package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
- package/spexharness/src/gates/inject-vault-context.mjs +239 -0
- package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
- package/spexharness/src/gates/plan-review-gate.mjs +233 -0
- package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
- package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
- package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
- package/spexharness/src/gates/settings-write-gate.mjs +104 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +196 -0
- package/spexharness/src/intent-directive.mjs +626 -0
- package/spexharness/src/profile/learn.mjs +551 -0
- package/spexharness/src/profile/proposals.mjs +357 -0
- package/spexharness/src/profile/vault-inclusion.mjs +386 -0
- package/spexharness/src/redact.mjs +151 -0
- package/spexharness/src/rule-table.mjs +849 -0
- package/spexharness/src/seat-context.mjs +122 -0
- package/spexharness/src/session-bootstrap.mjs +512 -0
- package/spexharness/src/session-record.mjs +815 -0
- package/spexharness/src/token-inventory.mjs +207 -0
- package/vendor/heyspex-mcp/account-switch.mjs +96 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
- package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
- package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
- package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
- package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
- package/vendor/heyspex-mcp/cli.mjs +207 -15524
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
- package/vendor/heyspex-mcp/config-seed.yaml +15 -0
- package/vendor/heyspex-mcp/config-settings.mjs +290 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
- package/vendor/heyspex-mcp/console-transport.mjs +172 -0
- package/vendor/heyspex-mcp/credentials.mjs +585 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
- package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
- package/vendor/heyspex-mcp/design-tools.mjs +4 -1
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
- package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
- package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
- package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
- package/vendor/heyspex-mcp/events-tools.mjs +207 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
- package/vendor/heyspex-mcp/http-transport.mjs +302 -0
- package/vendor/heyspex-mcp/list-tools.mjs +257 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
- package/vendor/heyspex-mcp/package-root.mjs +32 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
- package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
- package/vendor/heyspex-mcp/plan-push.mjs +908 -22
- package/vendor/heyspex-mcp/plan-review.mjs +124 -30
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
- package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
- package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
- package/vendor/heyspex-mcp/promote-research.mjs +666 -53
- package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
- package/vendor/heyspex-mcp/prompts.mjs +1460 -0
- package/vendor/heyspex-mcp/read-path.mjs +191 -15
- package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
- package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
- package/vendor/heyspex-mcp/register-spec.mjs +266 -54
- package/vendor/heyspex-mcp/request-context.mjs +83 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
- package/vendor/heyspex-mcp/research-tools.mjs +4 -4
- package/vendor/heyspex-mcp/response-budget.mjs +348 -0
- package/vendor/heyspex-mcp/review-focus.mjs +25 -1
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/search-specs.mjs +280 -0
- package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
- package/vendor/heyspex-mcp/session-owner.mjs +109 -0
- package/vendor/heyspex-mcp/session-tools.mjs +162 -0
- package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +304 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
- package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
- package/vendor/heyspex-mcp/vault-io.mjs +720 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +92 -0
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Shared auto-close terminal helpers — ship detection, pending arm, process kill,
|
|
3
|
+
// debug trace. Used by close-terminal-on-ship.mjs (hooks) and terminal-close-watcher.mjs.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
import { getActiveConfig } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
|
|
11
|
+
|
|
12
|
+
export const STATUS_REVIEW_RE = /^\s*status:\s*in_review\s*$/m;
|
|
13
|
+
export const STATUS_CLOSED_RE = /^\s*status:\s*closed\s*$/m;
|
|
14
|
+
export const RECORD_WIP_TOOL_RE = /heyspex_record_wip/i;
|
|
15
|
+
export const REVIEW_DOC_RE = /[\\/]specs[\\/]reviews[\\/]HS-REV-/i;
|
|
16
|
+
|
|
17
|
+
const EDIT_TOOL_NAMES = new Set([
|
|
18
|
+
'Edit',
|
|
19
|
+
'Write',
|
|
20
|
+
'MultiEdit',
|
|
21
|
+
'StrReplace',
|
|
22
|
+
'search_replace',
|
|
23
|
+
'apply_patch',
|
|
24
|
+
'WriteFile',
|
|
25
|
+
'Replace',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export function repoRoot() {
|
|
29
|
+
const fromEnv = process.env.CLAUDE_PROJECT_DIR?.trim();
|
|
30
|
+
if (fromEnv) return fromEnv.replace(/[\\/]+$/, '');
|
|
31
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEBUG_LOG_NAME = 'terminal-close-debug.jsonl';
|
|
35
|
+
/** Opt-in only — default off so hooks never grow a multi-MB trace during normal agent work. */
|
|
36
|
+
const DEBUG_LOG_MAX_BYTES = 256 * 1024;
|
|
37
|
+
|
|
38
|
+
export function terminalCloseDebugEnabled() {
|
|
39
|
+
return process.env.HEYSPEX_TERMINAL_CLOSE_DEBUG === '1';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function trimDebugLogIfOversized(logPath) {
|
|
43
|
+
try {
|
|
44
|
+
const stat = fs.statSync(logPath);
|
|
45
|
+
if (stat.size <= DEBUG_LOG_MAX_BYTES) return;
|
|
46
|
+
const raw = fs.readFileSync(logPath, 'utf8');
|
|
47
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
48
|
+
const keep = lines.slice(-200);
|
|
49
|
+
fs.writeFileSync(logPath, keep.join('\n') + (keep.length ? '\n' : ''));
|
|
50
|
+
} catch {
|
|
51
|
+
// absent or unreadable — append below will create it
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function debugLog(event, detail = {}) {
|
|
56
|
+
if (!terminalCloseDebugEnabled()) return;
|
|
57
|
+
try {
|
|
58
|
+
const logPath = path.join(repoRoot(), '.heyspex', DEBUG_LOG_NAME);
|
|
59
|
+
trimDebugLogIfOversized(logPath);
|
|
60
|
+
const line =
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
at: new Date().toISOString(),
|
|
63
|
+
event,
|
|
64
|
+
pid: process.pid,
|
|
65
|
+
ppid: process.ppid,
|
|
66
|
+
...detail,
|
|
67
|
+
}) + '\n';
|
|
68
|
+
fs.appendFileSync(logPath, line);
|
|
69
|
+
} catch {
|
|
70
|
+
// never block
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolveCloseTerminalOnTaskCompleteFromSettings() {
|
|
75
|
+
if (process.env.HEYSPEX_AUTO_CLOSE_TERMINAL === '0') return false;
|
|
76
|
+
try {
|
|
77
|
+
const raw = fs.readFileSync(path.join(repoRoot(), 'spex-console', 'settings.json'), 'utf8');
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
const launch = parsed?.launchModels;
|
|
80
|
+
if (typeof launch?.closeTerminalOnTaskComplete === 'boolean') {
|
|
81
|
+
return launch.closeTerminalOnTaskComplete;
|
|
82
|
+
}
|
|
83
|
+
if (typeof launch?.closeTerminalOnTaskCreation === 'boolean') {
|
|
84
|
+
return launch.closeTerminalOnTaskCreation;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const PENDING_CLOSE_FILE = '.pending-terminal-close';
|
|
93
|
+
|
|
94
|
+
export function pendingTerminalClosePath() {
|
|
95
|
+
return path.join(repoRoot(), '.heyspex', PENDING_CLOSE_FILE);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function readPendingTerminalClose() {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(fs.readFileSync(pendingTerminalClosePath(), 'utf8'));
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function armPendingTerminalClose(sessionId, reason = 'ship') {
|
|
107
|
+
try {
|
|
108
|
+
fs.mkdirSync(path.join(repoRoot(), '.heyspex'), { recursive: true });
|
|
109
|
+
const body = {
|
|
110
|
+
armedAt: new Date().toISOString(),
|
|
111
|
+
sessionId: typeof sessionId === 'string' ? sessionId : null,
|
|
112
|
+
reason,
|
|
113
|
+
};
|
|
114
|
+
fs.writeFileSync(pendingTerminalClosePath(), JSON.stringify(body));
|
|
115
|
+
debugLog('armed', body);
|
|
116
|
+
} catch {
|
|
117
|
+
// never block the hook
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function clearPendingTerminalClose() {
|
|
122
|
+
try {
|
|
123
|
+
fs.unlinkSync(pendingTerminalClosePath());
|
|
124
|
+
} catch {
|
|
125
|
+
// absent is fine
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isPendingTerminalCloseArmed() {
|
|
130
|
+
return readPendingTerminalClose() !== null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Bases that can contain spec files. Since the repo split (2026-08-31) the
|
|
135
|
+
* vault lives outside the code repo, so spec edits arrive as paths under the
|
|
136
|
+
* registry-resolved vault root, not under CLAUDE_PROJECT_DIR — test both.
|
|
137
|
+
*/
|
|
138
|
+
export function specBaseRoots() {
|
|
139
|
+
const roots = [repoRoot()];
|
|
140
|
+
try {
|
|
141
|
+
const resolved = getActiveConfig();
|
|
142
|
+
if (resolved?.repoRootAbs && !roots.includes(resolved.repoRootAbs)) {
|
|
143
|
+
roots.push(resolved.repoRootAbs);
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// registry unreadable — the code-repo base still applies
|
|
147
|
+
}
|
|
148
|
+
return roots;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function isUnderSpecsTaskOrEpic(filePath) {
|
|
152
|
+
if (typeof filePath !== 'string' || filePath.length === 0) return false;
|
|
153
|
+
const abs = path.resolve(filePath);
|
|
154
|
+
for (const root of specBaseRoots()) {
|
|
155
|
+
for (const sub of ['specs/tasks', 'specs/epics']) {
|
|
156
|
+
const base = path.join(root, sub);
|
|
157
|
+
const rel = path.relative(base, abs);
|
|
158
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function isReviewDocPath(filePath) {
|
|
165
|
+
if (typeof filePath !== 'string' || filePath.length === 0) return false;
|
|
166
|
+
return REVIEW_DOC_RE.test(path.resolve(filePath));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function textFlipToShipped(prev, next) {
|
|
170
|
+
const wasShipped = STATUS_REVIEW_RE.test(prev) || STATUS_CLOSED_RE.test(prev);
|
|
171
|
+
const nowShipped = STATUS_REVIEW_RE.test(next) || STATUS_CLOSED_RE.test(next);
|
|
172
|
+
return nowShipped && !wasShipped;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function detectShipFromEditLike(toolName, toolInput) {
|
|
176
|
+
if (!toolInput || typeof toolInput !== 'object') return false;
|
|
177
|
+
if (!isUnderSpecsTaskOrEpic(toolInput.file_path)) return false;
|
|
178
|
+
|
|
179
|
+
if (toolName === 'Edit' || toolName === 'StrReplace' || toolName === 'search_replace') {
|
|
180
|
+
const next = typeof toolInput.new_string === 'string' ? toolInput.new_string : '';
|
|
181
|
+
const prev = typeof toolInput.old_string === 'string' ? toolInput.old_string : '';
|
|
182
|
+
return textFlipToShipped(prev, next);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (toolName === 'MultiEdit') {
|
|
186
|
+
const edits = Array.isArray(toolInput.edits) ? toolInput.edits : [];
|
|
187
|
+
return edits.some((edit) => {
|
|
188
|
+
const next = typeof edit?.new_string === 'string' ? edit.new_string : '';
|
|
189
|
+
const prev = typeof edit?.old_string === 'string' ? edit.old_string : '';
|
|
190
|
+
return textFlipToShipped(prev, next);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (toolName === 'Write' || toolName === 'WriteFile') {
|
|
195
|
+
const content = typeof toolInput.content === 'string' ? toolInput.content : '';
|
|
196
|
+
return STATUS_REVIEW_RE.test(content) || STATUS_CLOSED_RE.test(content);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function detectShipFromAfterFileEdit(payload) {
|
|
203
|
+
const filePath = payload?.file_path;
|
|
204
|
+
if (!isUnderSpecsTaskOrEpic(filePath)) return false;
|
|
205
|
+
const edits = Array.isArray(payload?.edits) ? payload.edits : [];
|
|
206
|
+
return edits.some((edit) => {
|
|
207
|
+
const next = typeof edit?.new_string === 'string' ? edit.new_string : '';
|
|
208
|
+
const prev = typeof edit?.old_string === 'string' ? edit.old_string : '';
|
|
209
|
+
return textFlipToShipped(prev, next);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function normalizeHookEvent(payload) {
|
|
214
|
+
const raw = payload?.hook_event_name;
|
|
215
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
216
|
+
const key = raw.trim();
|
|
217
|
+
if (key === 'postToolUse' || key === 'PostToolUse') return 'postToolUse';
|
|
218
|
+
if (key === 'sessionEnd' || key === 'SessionEnd') return 'sessionEnd';
|
|
219
|
+
if (key === 'afterFileEdit') return 'afterFileEdit';
|
|
220
|
+
if (key === 'afterMCPExecution') return 'afterMCPExecution';
|
|
221
|
+
if (key === 'stop' || key === 'Stop') return 'stop';
|
|
222
|
+
return key;
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(payload?.edits) && payload?.file_path) return 'afterFileEdit';
|
|
225
|
+
if (typeof payload?.tool_name === 'string') return 'postToolUse';
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function toolCallFailed(payload) {
|
|
230
|
+
const response = payload?.tool_response;
|
|
231
|
+
if (response && typeof response === 'object' && response.is_error === true) return true;
|
|
232
|
+
if (payload?.failure_type) return true;
|
|
233
|
+
const output = payload?.tool_output;
|
|
234
|
+
if (typeof output === 'string') {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(output);
|
|
237
|
+
if (parsed?.isError === true || parsed?.error) return true;
|
|
238
|
+
} catch {
|
|
239
|
+
// not json
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function isShipEvent(payload) {
|
|
246
|
+
const event = normalizeHookEvent(payload);
|
|
247
|
+
if (!event) return false;
|
|
248
|
+
if (toolCallFailed(payload)) return false;
|
|
249
|
+
|
|
250
|
+
if (event === 'afterFileEdit') {
|
|
251
|
+
return detectShipFromAfterFileEdit(payload);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (event === 'postToolUse') {
|
|
255
|
+
const toolName = payload?.tool_name;
|
|
256
|
+
if (typeof toolName === 'string' && EDIT_TOOL_NAMES.has(toolName)) {
|
|
257
|
+
return detectShipFromEditLike(toolName, payload?.tool_input ?? {});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function isRecordWipSuccess(payload) {
|
|
265
|
+
const event = normalizeHookEvent(payload);
|
|
266
|
+
if (event !== 'postToolUse' && event !== 'afterMCPExecution') return false;
|
|
267
|
+
if (toolCallFailed(payload)) return false;
|
|
268
|
+
|
|
269
|
+
const toolName = typeof payload?.tool_name === 'string' ? payload.tool_name : '';
|
|
270
|
+
if (!RECORD_WIP_TOOL_RE.test(toolName)) return false;
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function isReviewDocWriteSuccess(payload) {
|
|
275
|
+
const event = normalizeHookEvent(payload);
|
|
276
|
+
if (event !== 'postToolUse' && event !== 'afterFileEdit') return false;
|
|
277
|
+
if (toolCallFailed(payload)) return false;
|
|
278
|
+
|
|
279
|
+
if (event === 'afterFileEdit') {
|
|
280
|
+
return isReviewDocPath(payload?.file_path);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const toolName = payload?.tool_name;
|
|
284
|
+
if (toolName !== 'Write' && toolName !== 'WriteFile' && toolName !== 'Edit') return false;
|
|
285
|
+
return isReviewDocPath(payload?.tool_input?.file_path);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function isSessionEndEvent(payload) {
|
|
289
|
+
return normalizeHookEvent(payload) === 'sessionEnd';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function wipStateUpdatedAfter(armedAtIso) {
|
|
293
|
+
if (!armedAtIso) return false;
|
|
294
|
+
const armedMs = Date.parse(armedAtIso);
|
|
295
|
+
if (!Number.isFinite(armedMs)) return false;
|
|
296
|
+
// Since the repo split the ledger heyspex_record_wip writes lives in the VAULT.
|
|
297
|
+
// Watching only repoRoot() here meant a recorded WIP never satisfied the arm, so
|
|
298
|
+
// the ship-close never fired. Check every base that can hold a ledger.
|
|
299
|
+
for (const root of specBaseRoots()) {
|
|
300
|
+
try {
|
|
301
|
+
const raw = fs.readFileSync(path.join(root, '.heyspex', 'wip-state.json'), 'utf8');
|
|
302
|
+
const data = JSON.parse(raw);
|
|
303
|
+
if (!data || typeof data !== 'object') continue;
|
|
304
|
+
for (const entry of Object.values(data)) {
|
|
305
|
+
const updatedAt = entry?.updatedAt;
|
|
306
|
+
if (typeof updatedAt === 'string' && Date.parse(updatedAt) > armedMs) return true;
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
// missing or unreadable at this root — try the next one
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function shouldCloseNow() {
|
|
316
|
+
const pending = readPendingTerminalClose();
|
|
317
|
+
if (!pending?.armedAt) return false;
|
|
318
|
+
return wipStateUpdatedAfter(pending.armedAt);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function scheduleAgentHostExit(startPid = process.ppid) {
|
|
322
|
+
if (!Number.isFinite(startPid) || startPid <= 0) {
|
|
323
|
+
debugLog('kill_skipped', { reason: 'invalid_start_pid', startPid });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
debugLog('kill_scheduled', { startPid });
|
|
328
|
+
|
|
329
|
+
if (process.platform === 'win32') {
|
|
330
|
+
const ps = [
|
|
331
|
+
'-NoProfile',
|
|
332
|
+
'-WindowStyle',
|
|
333
|
+
'Hidden',
|
|
334
|
+
'-Command',
|
|
335
|
+
`
|
|
336
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
337
|
+
$current = ${startPid}
|
|
338
|
+
for ($i = 0; $i -lt 40; $i++) {
|
|
339
|
+
$p = Get-CimInstance Win32_Process -Filter "ProcessId=$current"
|
|
340
|
+
if (-not $p) { break }
|
|
341
|
+
$name = [string]$p.Name
|
|
342
|
+
$cmd = [string]$p.CommandLine
|
|
343
|
+
if ($name -ieq 'claude.exe') { Stop-Process -Id $current -Force; exit 0 }
|
|
344
|
+
if ($cmd -match 'cursor-agent|@anthropic-ai/claude-code|\\\\cursor-agent\\\\') { Stop-Process -Id $current -Force; exit 0 }
|
|
345
|
+
$parent = [int]$p.ParentProcessId
|
|
346
|
+
if ($parent -le 0 -or $parent -eq $current) { break }
|
|
347
|
+
$current = $parent
|
|
348
|
+
}
|
|
349
|
+
Stop-Process -Id ${startPid} -Force -ErrorAction SilentlyContinue
|
|
350
|
+
`.trim(),
|
|
351
|
+
];
|
|
352
|
+
try {
|
|
353
|
+
const child = spawn('powershell.exe', ps, {
|
|
354
|
+
detached: true,
|
|
355
|
+
stdio: 'ignore',
|
|
356
|
+
windowsHide: true,
|
|
357
|
+
});
|
|
358
|
+
child.unref();
|
|
359
|
+
} catch (err) {
|
|
360
|
+
debugLog('kill_failed', { message: err instanceof Error ? err.message : String(err) });
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const sh = `pid=${startPid}; for i in $(seq 1 30); do
|
|
366
|
+
line=$(ps -p "$pid" -o comm= 2>/dev/null) || break
|
|
367
|
+
case "$line" in *claude*|*Claude*) kill -TERM "$pid" 2>/dev/null; exit 0 ;; esac
|
|
368
|
+
cmd=$(ps -p "$pid" -o args= 2>/dev/null || true)
|
|
369
|
+
case "$cmd" in *cursor-agent*) kill -TERM "$pid" 2>/dev/null; exit 0 ;; esac
|
|
370
|
+
ppid=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ')
|
|
371
|
+
[ -z "$ppid" ] || [ "$ppid" = "0" ] || [ "$ppid" = "$pid" ] && break
|
|
372
|
+
pid=$ppid
|
|
373
|
+
done
|
|
374
|
+
kill -TERM ${startPid} 2>/dev/null || true`;
|
|
375
|
+
try {
|
|
376
|
+
const child = spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' });
|
|
377
|
+
child.unref();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
debugLog('kill_failed', { message: err instanceof Error ? err.message : String(err) });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function tryCloseIfReady(reason) {
|
|
384
|
+
if (!resolveCloseTerminalOnTaskCompleteFromSettings()) {
|
|
385
|
+
debugLog('close_skipped', { reason: 'setting_disabled', trigger: reason });
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (!isPendingTerminalCloseArmed()) {
|
|
389
|
+
debugLog('close_skipped', { reason: 'not_armed', trigger: reason });
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
if (!shouldCloseNow() && reason !== 'session_end' && reason !== 'record_wip') {
|
|
393
|
+
debugLog('close_skipped', { reason: 'wip_not_updated', trigger: reason });
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
clearPendingTerminalClose();
|
|
397
|
+
debugLog('closing', { trigger: reason });
|
|
398
|
+
scheduleAgentHostExit();
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function readStdin() {
|
|
403
|
+
if (process.stdin.isTTY) return null;
|
|
404
|
+
return new Promise((resolve) => {
|
|
405
|
+
let buf = '';
|
|
406
|
+
let resolved = false;
|
|
407
|
+
const finish = (value) => {
|
|
408
|
+
if (resolved) return;
|
|
409
|
+
resolved = true;
|
|
410
|
+
resolve(value);
|
|
411
|
+
};
|
|
412
|
+
const timer = setTimeout(() => finish(buf || null), 500);
|
|
413
|
+
process.stdin.setEncoding('utf8');
|
|
414
|
+
process.stdin.on('data', (chunk) => {
|
|
415
|
+
buf += chunk;
|
|
416
|
+
});
|
|
417
|
+
process.stdin.on('end', () => {
|
|
418
|
+
clearTimeout(timer);
|
|
419
|
+
finish(buf || null);
|
|
420
|
+
});
|
|
421
|
+
process.stdin.on('error', () => {
|
|
422
|
+
clearTimeout(timer);
|
|
423
|
+
finish(buf || null);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Spawn-side fallback when hooks miss ship/close events (external cursor-agent terminal).
|
|
3
|
+
// Polls claimed spec status + wip-state.json; kills the agent host when close-out lands.
|
|
4
|
+
//
|
|
5
|
+
// Started detached from spawnNewAgent when auto-close is enabled.
|
|
6
|
+
// Mute: HEYSPEX_AUTO_CLOSE_TERMINAL=0 or HEYSPEX_CLOSE_TERMINAL_ON_SHIP_OFF=1
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
armPendingTerminalClose,
|
|
14
|
+
debugLog,
|
|
15
|
+
clearPendingTerminalClose,
|
|
16
|
+
isPendingTerminalCloseArmed,
|
|
17
|
+
readPendingTerminalClose,
|
|
18
|
+
repoRoot,
|
|
19
|
+
resolveCloseTerminalOnTaskCompleteFromSettings,
|
|
20
|
+
specBaseRoots,
|
|
21
|
+
scheduleAgentHostExit,
|
|
22
|
+
shouldCloseNow,
|
|
23
|
+
STATUS_CLOSED_RE,
|
|
24
|
+
STATUS_REVIEW_RE,
|
|
25
|
+
} from './terminal-close-shared.mjs';
|
|
26
|
+
|
|
27
|
+
const POLL_MS = 3000;
|
|
28
|
+
const MAX_MS = 8 * 60 * 60 * 1000;
|
|
29
|
+
|
|
30
|
+
function readActiveClaimSpecId() {
|
|
31
|
+
try {
|
|
32
|
+
const raw = fs.readFileSync(path.join(repoRoot(), '.heyspex', 'active-claims.json'), 'utf8');
|
|
33
|
+
const claims = JSON.parse(raw);
|
|
34
|
+
const session =
|
|
35
|
+
process.env.CURSOR_TRACE_ID ??
|
|
36
|
+
process.env.CLAUDE_CODE_SESSION_ID ??
|
|
37
|
+
process.env.HEYSPEX_SESSION_ID ??
|
|
38
|
+
'';
|
|
39
|
+
if (!Array.isArray(claims)) return null;
|
|
40
|
+
const mine = claims.find(
|
|
41
|
+
(c) =>
|
|
42
|
+
c &&
|
|
43
|
+
(session
|
|
44
|
+
? c.sessionId === session || c.session_id === session
|
|
45
|
+
: c.agent === process.env.HEYSPEX_AGENT),
|
|
46
|
+
);
|
|
47
|
+
return mine?.specId ?? mine?.spec_id ?? null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findSpecFile(specId) {
|
|
54
|
+
for (const root of specBaseRoots()) {
|
|
55
|
+
for (const dir of ['specs/tasks', 'specs/epics']) {
|
|
56
|
+
const folder = path.join(root, dir);
|
|
57
|
+
let names;
|
|
58
|
+
try {
|
|
59
|
+
names = fs.readdirSync(folder);
|
|
60
|
+
} catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const hit = names.find((n) => n.startsWith(`${specId}-`) || n.startsWith(specId));
|
|
64
|
+
if (hit) return path.join(folder, hit);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readSpecStatus(specPath) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(specPath, 'utf8');
|
|
73
|
+
const m = raw.match(/^\s*status:\s*(\S+)\s*$/m);
|
|
74
|
+
return m?.[1] ?? null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isShippedStatus(status) {
|
|
81
|
+
return status === 'in_review' || status === 'closed' || status === 'review_pending';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function pollOnce() {
|
|
85
|
+
if (!resolveCloseTerminalOnTaskCompleteFromSettings()) return false;
|
|
86
|
+
|
|
87
|
+
const specId = readActiveClaimSpecId();
|
|
88
|
+
if (!specId) return false;
|
|
89
|
+
|
|
90
|
+
const specPath = findSpecFile(specId);
|
|
91
|
+
if (!specPath) return false;
|
|
92
|
+
|
|
93
|
+
const status = readSpecStatus(specPath);
|
|
94
|
+
if (!isShippedStatus(status)) return false;
|
|
95
|
+
|
|
96
|
+
if (!isPendingTerminalCloseArmed()) {
|
|
97
|
+
armPendingTerminalClose(null, `watcher:${status}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (shouldCloseNow()) {
|
|
101
|
+
debugLog('watcher_close', { specId, status });
|
|
102
|
+
clearPendingTerminalClose();
|
|
103
|
+
scheduleAgentHostExit();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pending = readPendingTerminalClose();
|
|
108
|
+
if (pending?.armedAt) {
|
|
109
|
+
debugLog('watcher_waiting_wip', { specId, status, armedAt: pending.armedAt });
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const started = Date.now();
|
|
115
|
+
debugLog('watcher_start', { pid: process.pid });
|
|
116
|
+
|
|
117
|
+
while (Date.now() - started < MAX_MS) {
|
|
118
|
+
if (pollOnce()) break;
|
|
119
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
debugLog('watcher_exit', {});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
|
|
3
|
+
// This file only wires Claude Code's hook stdin/stdout contract to the
|
|
4
|
+
// spexharness engine port of this gate. To change what this gate DECIDES,
|
|
5
|
+
// edit spexharness/src/gates/vault-mirror-catchup.mjs. To change the SHAPE of this shell,
|
|
6
|
+
// edit the generator, then rerun: node scripts/generate-hook-shells.mjs
|
|
7
|
+
|
|
8
|
+
import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
|
|
9
|
+
import gate from '../../spexharness/src/gates/vault-mirror-catchup.mjs';
|
|
10
|
+
|
|
11
|
+
await runGateAsHook(gate);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
|
|
3
|
+
// This file only wires Claude Code's hook stdin/stdout contract to the
|
|
4
|
+
// spexharness engine port of this gate. To change what this gate DECIDES,
|
|
5
|
+
// edit spexharness/src/gates/vault-write-tier-guard.mjs. To change the SHAPE of this shell,
|
|
6
|
+
// edit the generator, then rerun: node scripts/generate-hook-shells.mjs
|
|
7
|
+
|
|
8
|
+
import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
|
|
9
|
+
import gate from '../../spexharness/src/gates/vault-write-tier-guard.mjs';
|
|
10
|
+
|
|
11
|
+
await runGateAsHook(gate);
|