heyspex 0.1.0 → 0.2.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/README.md +21 -0
- package/bin/hs.mjs +931 -98
- 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 +1085 -103
- package/dist/cli/hs-claim.mjs +936 -63
- package/dist/cli/hs-connect.mjs +9786 -1910
- package/dist/cli/hs-defer.mjs +5267 -3053
- package/dist/cli/hs-dev.mjs +564 -0
- package/dist/cli/hs-env.mjs +385 -299
- package/dist/cli/hs-index-rebuild.mjs +7129 -0
- package/dist/cli/hs-index-reconcile.mjs +7852 -0
- package/dist/cli/hs-init.mjs +777 -138
- package/dist/cli/hs-install.mjs +6583 -485
- package/dist/cli/hs-join.mjs +10137 -2368
- package/dist/cli/hs-vault-pull.mjs +4659 -5882
- package/dist/cli/hs-vault.mjs +3793 -1537
- package/dist/cli/hs-workspace.mjs +2550 -524
- 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 +28 -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 +26 -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 +31 -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 +30 -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/mcp-client-config.md +259 -6
- package/package.json +231 -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/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +62 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +227 -0
- package/plugins/heyspex/hooks/hooks.json +361 -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 +410 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +351 -0
- package/scripts/claude-hooks/_run.mjs +120 -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-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 +575 -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/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 +66 -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 +247 -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 +449 -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/wip-checkpoint.mjs +298 -0
- package/scripts/cursor-hooks/manifest.mjs +235 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +268 -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 +333 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +270 -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 +197 -60
- package/scripts/lib/customer-kit.mjs +886 -0
- package/scripts/lib/dev-mode.mjs +244 -28
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -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 +11 -21
- package/scripts/lib/hs-source-push.mjs +107 -58
- 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/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 +441 -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/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 +274 -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/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +194 -0
- package/spexharness/src/intent-directive.mjs +541 -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 +819 -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 +95 -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 +46 -5
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +80 -1
- package/vendor/heyspex-mcp/capture-focus.mjs +1035 -29
- package/vendor/heyspex-mcp/capture-outbox.mjs +5 -1
- package/vendor/heyspex-mcp/capture-tools.mjs +1219 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1124 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2076 -0
- package/vendor/heyspex-mcp/cli.mjs +201 -15524
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +412 -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 +657 -16
- package/vendor/heyspex-mcp/console-transport.mjs +166 -0
- package/vendor/heyspex-mcp/credentials.mjs +584 -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 +124 -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 +688 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +69 -14
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +59 -0
- package/vendor/heyspex-mcp/http-transport.mjs +300 -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 +94 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/occupied-keys.mjs +141 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3360 -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 +906 -21
- 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 +325 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +540 -351
- package/vendor/heyspex-mcp/promote-capture.mjs +386 -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 +660 -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 +29 -9
- 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 +59 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +623 -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/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 +108 -0
- package/vendor/heyspex-mcp/session-tools.mjs +152 -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 +446 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +301 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +132 -6
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +224 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +108 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2813 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +639 -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 +724 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +366 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +91 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
// JSON-RPC method handlers for initialize, tools/list, tools/call (the
|
|
2
|
+
// dispatchHeySpexTool switch), session/end and hooks/*, plus the seat banner and
|
|
3
|
+
// peer-presence injection applied to every tool result. Extracted from cli.mjs under
|
|
4
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
5
|
+
|
|
6
|
+
import { executeHook, finalizeToolResultWithHooks, listHookConfig, loadHooksConfig, matchHooks, runHooks } from './hooks-engine.mjs';
|
|
7
|
+
import { detectAgentIdentity, getConfig } from './mcp-config.mjs';
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { evaluatePreflightGate } from './preflight-gate.mjs';
|
|
10
|
+
import { TOOLS, handleLoadTools } from './tool-catalog.mjs';
|
|
11
|
+
import { handleNextOnDeck, handleNextResolution, handleNextReview, handleResolvePrerequisite, handleSuggestPillar } from './on-deck-brief.mjs';
|
|
12
|
+
import { handleFileSpec, handleGroundPlan, handlePlanQc, handlePlanReview, handlePushPlan, handleRegisterSpec, handleValidateLinkage, handleValidateUserSkills } from './filing-tools.mjs';
|
|
13
|
+
import { handleActiveResearch, handleCompleteResearch, handlePromoteResearch, handlePromoteResearchToDiagnose, handleSaveResearch, handleSplitResearch } from './research-handlers.mjs';
|
|
14
|
+
import { handleRecallMemoriesViaTier } from './memory-tools.mjs';
|
|
15
|
+
import { handleCanvasInbox, handleEditFile, handleListKnownTags, handleProposeMetadata, handleRegenTask, handleRegenTasksForEpic, handleShipEpic } from './spec-edit-tools.mjs';
|
|
16
|
+
import { handleAuthorSummary } from './summary-write-tools.mjs';
|
|
17
|
+
import { handleActiveCapture, handlePinCapture, handlePromoteCapture, handlePromoteCaptureToDiagnose, handleUpdateCapture } from './capture-tools.mjs';
|
|
18
|
+
import { handleClaimAcquire, handleClaimFile, handleClaimList, handleClaimRelease, handleClaimSpec, handleDeferSpec, handleRecordAlignmentViaTier, handleRecordWip, handleReleaseFileClaim, handleReleaseSpec, handleReportProgress, handleSubmitForReview } from './claim-tools.mjs';
|
|
19
|
+
import { handlePromoteDiagnose, handleVerifyReproduction } from './diagnose-handlers.mjs';
|
|
20
|
+
import { handleHandoffLoad, handleHandoffRecord, handleImportAgent } from './session-tools.mjs';
|
|
21
|
+
import { handleEventsShow, handleEvidenceShow, handleExportEvents, handleMilestoneShow } from './events-tools.mjs';
|
|
22
|
+
import { handleClearFocusAreas, handleGetFocusAreas, handlePinGet, handlePinSet, handleResolveFocusIntent, handleSetFocusAreas } from './focus-tools.mjs';
|
|
23
|
+
import { handleSearchSpecs } from './search-specs.mjs';
|
|
24
|
+
import { handleDesignGetDoc, handleDesignGetPattern, handleDesignGetToken, handleDesignListPatterns, handleDesignProposePattern } from './design-handlers.mjs';
|
|
25
|
+
import { handleGetProcedureViaTier, handleListProceduresViaTier, handleRunPreflightViaTier } from './procedure-handlers.mjs';
|
|
26
|
+
import { handleListRecipes, handleStartRecipe } from './recipe-handlers.mjs';
|
|
27
|
+
import { handleFleetQueueClaim, handleFleetQueueComplete, handleFleetQueueList, handleFleetQueueSubmit } from './fleet-handlers.mjs';
|
|
28
|
+
import { handleDevMode, handleDevServerStatus } from './dev-tools.mjs';
|
|
29
|
+
import { wrapDesignResult } from './tool-helpers.mjs';
|
|
30
|
+
import { executeSettingsGet, executeSettingsList, executeSettingsSet } from './settings-tools.mjs';
|
|
31
|
+
import { executeFirecrawlResearch, executeResolveResearchPlan } from './research-tools.mjs';
|
|
32
|
+
import { formatSeatBanner, readSeatsRegistry, resolveSeat } from './seat-identity.mjs';
|
|
33
|
+
import { seatBlockForSessionSurface } from './seat-block-ledger.mjs';
|
|
34
|
+
import { resolveCooModeEnabled } from '../../dev-factory/lib/fleet-readiness.mjs';
|
|
35
|
+
import { applyToolBudget } from './response-budget.mjs';
|
|
36
|
+
import { resolveDevMode } from '../../scripts/lib/dev-mode.mjs';
|
|
37
|
+
import { dirname, join } from 'node:path';
|
|
38
|
+
import { MODULE_DIR } from './package-root.mjs';
|
|
39
|
+
|
|
40
|
+
export const SERVER_NAME = 'heyspex';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read the server version from the nearest `package.json`, walking up from
|
|
44
|
+
* THIS file's own directory (`vendor/heyspex-mcp/package.json` if one ever
|
|
45
|
+
* exists, else the repo root's) — [[HS-7OKX-0JNK-TASK]]. Previously a
|
|
46
|
+
* hard-coded `'0.9.0'` that had drifted from the packed `heyspex@0.1.0`, so
|
|
47
|
+
* the `initialize` handshake's `serverInfo.version` disagreed with what was
|
|
48
|
+
* actually installed. Never throws: an unreadable/missing/malformed
|
|
49
|
+
* package.json anywhere up the tree falls through to `'0.0.0-unknown'`.
|
|
50
|
+
*/
|
|
51
|
+
export function readServerVersion() {
|
|
52
|
+
let dir = MODULE_DIR;
|
|
53
|
+
for (let i = 0; i < 16; i += 1) {
|
|
54
|
+
const candidate = join(dir, 'package.json');
|
|
55
|
+
if (existsSync(candidate)) {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
58
|
+
if (pkg && typeof pkg.version === 'string' && pkg.version.trim()) {
|
|
59
|
+
return pkg.version;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// malformed package.json at this level — keep walking up rather than throw
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const parent = dirname(dir);
|
|
66
|
+
if (parent === dir) break; // reached filesystem root
|
|
67
|
+
dir = parent;
|
|
68
|
+
}
|
|
69
|
+
return '0.0.0-unknown';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const SERVER_VERSION = readServerVersion();
|
|
73
|
+
|
|
74
|
+
// MCP Channels capability is advertised in the initialize handshake.
|
|
75
|
+
// Per https://code.claude.com/docs/en/channels-reference#server-options the
|
|
76
|
+
// channel capability is nested under `experimental` and pushed events use
|
|
77
|
+
// the `notifications/claude/channel` method. The instructions string is
|
|
78
|
+
// injected into Claude's system prompt so it knows how to react to
|
|
79
|
+
// <channel source="heyspex" …> events emitted by capture-channel.mjs.
|
|
80
|
+
// Owning epic: [[HS-EPIC-QXJ3-modal-agent-handshake-mcp-channels-push-]].
|
|
81
|
+
export const CHANNEL_INSTRUCTIONS =
|
|
82
|
+
'Events from the heyspex capture flow arrive as <channel source="heyspex" capture_id="HS-CAP-XXXX" event_type="user_quote">…</channel>. ' +
|
|
83
|
+
'When you receive one, call heyspex_active_capture WITH spec_id set to that exact capture_id ' +
|
|
84
|
+
'(e.g. heyspex_active_capture({ spec_id: "HS-CAP-XXXX" })) to fetch the latest capture state, and pass that same spec_id ' +
|
|
85
|
+
'to every heyspex_update_capture call for this capture. Do NOT call these tools without spec_id when you have the channel capture_id — ' +
|
|
86
|
+
'the no-spec_id path attaches to whichever capture was most recently active, which may be a different session (another console tab, ' +
|
|
87
|
+
'the companion app, or another agent) and would write to the wrong capture id. Then respond per the /hs-capture capture protocol ' +
|
|
88
|
+
'(Capture → Ground=Expand [grounding auto-writes agent_expansion, no confirm] → Promote to research). Do not infer user_quote ' +
|
|
89
|
+
'contents from the channel hint alone — always fetch fresh state via the tool.';
|
|
90
|
+
|
|
91
|
+
// ── Dev-factory seat identity delivery (epic HS-CXO0) ────────────────────────
|
|
92
|
+
// Resolve the seat THIS caller holds — gated by the shared fleet-readiness
|
|
93
|
+
// predicate (identical to the SessionStart hook → no drift). Returns null (inject
|
|
94
|
+
// nothing) on any error, cooMode off / auto-not-ready, or an unknown caller. A
|
|
95
|
+
// Builder worker resolves to the Builder seat, a COO session to COO — each is told
|
|
96
|
+
// its OWN seat, never another's.
|
|
97
|
+
export function resolveInjectableSeat() {
|
|
98
|
+
try {
|
|
99
|
+
const { vaultRoot } = getConfig();
|
|
100
|
+
const registry = readSeatsRegistry(vaultRoot);
|
|
101
|
+
if (!registry) return null;
|
|
102
|
+
if (!resolveCooModeEnabled(registry.settings?.cooMode).enabled) return null;
|
|
103
|
+
const identity = detectAgentIdentity();
|
|
104
|
+
return resolveSeat(identity, { registry, env: process.env });
|
|
105
|
+
} catch {
|
|
106
|
+
return null; // seat logic must never break the server
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function handleInitialize(params) {
|
|
111
|
+
let instructions = CHANNEL_INSTRUCTIONS;
|
|
112
|
+
try {
|
|
113
|
+
const banner = formatSeatBanner(resolveInjectableSeat());
|
|
114
|
+
if (banner) {
|
|
115
|
+
// Gate on the shared per-session ledger (HS-8U8X) rather than emitting
|
|
116
|
+
// unconditionally — the first tool result of the session (see cli.mjs
|
|
117
|
+
// maybePrependSeatBanner) shares this same ledger, so whichever surface
|
|
118
|
+
// asks first wins and the other gets null, never a second copy.
|
|
119
|
+
const { sessionId } = detectAgentIdentity();
|
|
120
|
+
const toSend = await seatBlockForSessionSurface(sessionId, 'mcp_instructions', banner);
|
|
121
|
+
if (toSend) instructions += `\n\n${toSend}`;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// never break the initialize handshake on seat logic
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
protocolVersion: '2024-11-05',
|
|
128
|
+
capabilities: {
|
|
129
|
+
// listChanged: heyspex_load_tools expands the visible surface at runtime
|
|
130
|
+
// ([[HS-LCRS-TASK]] progressive disclosure) and notifies the client.
|
|
131
|
+
tools: { listChanged: true },
|
|
132
|
+
prompts: {},
|
|
133
|
+
// [[HS-EPIC-QXJ3]] — channels capability nested under `experimental`
|
|
134
|
+
// per https://code.claude.com/docs/en/channels-reference#server-options.
|
|
135
|
+
// Presence registers the notification listener; emission happens in
|
|
136
|
+
// capture-channel.mjs via sendNotification('notifications/claude/channel', …).
|
|
137
|
+
experimental: {
|
|
138
|
+
'claude/channel': {},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
instructions,
|
|
142
|
+
serverInfo: {
|
|
143
|
+
name: SERVER_NAME,
|
|
144
|
+
version: SERVER_VERSION,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function handleToolsList() {
|
|
150
|
+
// `budget` is a server-side contract (enforced below), not part of the MCP
|
|
151
|
+
// Tool wire shape — strip it so the client sees only name/description/schema.
|
|
152
|
+
return { tools: TOOLS.map(({ budget: _budget, ...tool }) => tool) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The catalog's declared byte budget for a tool, or `null` when it declares none. */
|
|
156
|
+
export function toolBudgetFor(name) {
|
|
157
|
+
const tool = TOOLS_BY_NAME.get(name);
|
|
158
|
+
return tool?.budget && Number(tool.budget.max_bytes) > 0 ? tool.budget : null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const TOOLS_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Every tool call goes through here. A tool that declares `budget` in the
|
|
165
|
+
* catalog ([[HS-4H8T-8KK5-TASK-every-mcp-read-tool-declares-a-byte]]) has
|
|
166
|
+
* its reply bounded to `budget.max_bytes` on the way out — trailing list items
|
|
167
|
+
* dropped behind a `next_cursor`, `truncated: true` — and a budget cursor on
|
|
168
|
+
* the way in resumes from the first dropped item (see response-budget.mjs).
|
|
169
|
+
* A tool without a budget is dispatched untouched.
|
|
170
|
+
*/
|
|
171
|
+
export function dispatchHeySpexTool(name, args, vaultRoot, config) {
|
|
172
|
+
return applyToolBudget(
|
|
173
|
+
name,
|
|
174
|
+
args,
|
|
175
|
+
(effectiveArgs) => dispatchHeySpexToolUnbudgeted(name, effectiveArgs, vaultRoot, config),
|
|
176
|
+
toolBudgetFor(name),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function dispatchHeySpexToolUnbudgeted(name, args, vaultRoot, config) {
|
|
181
|
+
switch (name) {
|
|
182
|
+
case 'heyspex_load_tools':
|
|
183
|
+
// Normally intercepted parent-side at the tools/call dispatch; this case is
|
|
184
|
+
// defense-in-depth for direct handleToolsCall callers (subprocess/CLI paths).
|
|
185
|
+
return handleLoadTools(args);
|
|
186
|
+
case 'heyspex_next_on_deck':
|
|
187
|
+
return handleNextOnDeck(vaultRoot, config, args);
|
|
188
|
+
case 'heyspex_next_review':
|
|
189
|
+
return handleNextReview(vaultRoot, config, args);
|
|
190
|
+
case 'heyspex_next_resolution':
|
|
191
|
+
return handleNextResolution(args);
|
|
192
|
+
case 'heyspex_validate_linkage':
|
|
193
|
+
return handleValidateLinkage(vaultRoot, args);
|
|
194
|
+
case 'heyspex_suggest_pillar':
|
|
195
|
+
return handleSuggestPillar(args);
|
|
196
|
+
case 'heyspex_validate_user_skills':
|
|
197
|
+
return handleValidateUserSkills(vaultRoot, args);
|
|
198
|
+
case 'heyspex_register_spec':
|
|
199
|
+
return handleRegisterSpec(vaultRoot, args);
|
|
200
|
+
case 'heyspex_save_research':
|
|
201
|
+
return handleSaveResearch(vaultRoot, args);
|
|
202
|
+
case 'heyspex_recall_memories':
|
|
203
|
+
return handleRecallMemoriesViaTier(vaultRoot, args);
|
|
204
|
+
case 'heyspex_push_plan':
|
|
205
|
+
return handlePushPlan(vaultRoot, args);
|
|
206
|
+
case 'heyspex_backfill_epic':
|
|
207
|
+
return handleShipEpic(vaultRoot, args);
|
|
208
|
+
case 'heyspex_ground_plan':
|
|
209
|
+
return handleGroundPlan(vaultRoot, args);
|
|
210
|
+
case 'heyspex_plan_review':
|
|
211
|
+
return handlePlanReview(vaultRoot, args);
|
|
212
|
+
case 'heyspex_plan_qc':
|
|
213
|
+
return handlePlanQc(vaultRoot, args);
|
|
214
|
+
case 'heyspex_active_capture':
|
|
215
|
+
return handleActiveCapture(vaultRoot, args);
|
|
216
|
+
case 'heyspex_update_capture':
|
|
217
|
+
return handleUpdateCapture(vaultRoot, args);
|
|
218
|
+
case 'heyspex_pin_capture':
|
|
219
|
+
return handlePinCapture(vaultRoot, args);
|
|
220
|
+
case 'heyspex_active_research':
|
|
221
|
+
return handleActiveResearch(vaultRoot, args);
|
|
222
|
+
case 'heyspex_complete_research':
|
|
223
|
+
return handleCompleteResearch(vaultRoot, args);
|
|
224
|
+
case 'heyspex_claim_spec':
|
|
225
|
+
return handleClaimSpec(args);
|
|
226
|
+
case 'heyspex_report_progress':
|
|
227
|
+
return handleReportProgress(args);
|
|
228
|
+
case 'heyspex_submit_for_review':
|
|
229
|
+
return handleSubmitForReview(vaultRoot, args);
|
|
230
|
+
case 'heyspex_verify_reproduction':
|
|
231
|
+
return handleVerifyReproduction(vaultRoot, args);
|
|
232
|
+
case 'heyspex_promote_diagnose':
|
|
233
|
+
return handlePromoteDiagnose(vaultRoot, args);
|
|
234
|
+
case 'heyspex_release_spec':
|
|
235
|
+
return handleReleaseSpec(args);
|
|
236
|
+
case 'heyspex_record_wip':
|
|
237
|
+
return handleRecordWip(args);
|
|
238
|
+
case 'heyspex_author_summary':
|
|
239
|
+
return handleAuthorSummary(vaultRoot, args);
|
|
240
|
+
case 'heyspex_defer_spec':
|
|
241
|
+
return handleDeferSpec(args);
|
|
242
|
+
case 'heyspex_handoff_record':
|
|
243
|
+
return handleHandoffRecord(args);
|
|
244
|
+
case 'heyspex_handoff_load':
|
|
245
|
+
return handleHandoffLoad(args);
|
|
246
|
+
case 'heyspex_milestone_show':
|
|
247
|
+
return handleMilestoneShow(args);
|
|
248
|
+
case 'heyspex_pin_get':
|
|
249
|
+
return handlePinGet(args);
|
|
250
|
+
case 'heyspex_pin_set':
|
|
251
|
+
return handlePinSet(args);
|
|
252
|
+
case 'heyspex_resolve_prerequisite':
|
|
253
|
+
return handleResolvePrerequisite(args);
|
|
254
|
+
case 'heyspex_claim_acquire':
|
|
255
|
+
return handleClaimAcquire(args);
|
|
256
|
+
case 'heyspex_claim_release':
|
|
257
|
+
return handleClaimRelease(args);
|
|
258
|
+
case 'heyspex_claim_list':
|
|
259
|
+
return handleClaimList(args);
|
|
260
|
+
case 'heyspex_claim_file':
|
|
261
|
+
return handleClaimFile(args);
|
|
262
|
+
case 'heyspex_release_file_claim':
|
|
263
|
+
return handleReleaseFileClaim(args);
|
|
264
|
+
case 'heyspex_edit_file':
|
|
265
|
+
return handleEditFile(args);
|
|
266
|
+
case 'heyspex_list_known_tags':
|
|
267
|
+
return handleListKnownTags(args);
|
|
268
|
+
case 'heyspex_set_focus_areas':
|
|
269
|
+
return handleSetFocusAreas(args);
|
|
270
|
+
case 'heyspex_get_focus_areas':
|
|
271
|
+
return handleGetFocusAreas(args);
|
|
272
|
+
case 'heyspex_clear_focus_areas':
|
|
273
|
+
return handleClearFocusAreas(args);
|
|
274
|
+
case 'heyspex_resolve_focus_intent':
|
|
275
|
+
return handleResolveFocusIntent(args);
|
|
276
|
+
case 'heyspex_search_specs':
|
|
277
|
+
return handleSearchSpecs(args);
|
|
278
|
+
case 'heyspex_events_show':
|
|
279
|
+
return handleEventsShow(args);
|
|
280
|
+
case 'heyspex_evidence_show':
|
|
281
|
+
return handleEvidenceShow(args);
|
|
282
|
+
case 'heyspex_export_events':
|
|
283
|
+
return handleExportEvents(args);
|
|
284
|
+
case 'heyspex_propose_metadata':
|
|
285
|
+
return handleProposeMetadata(args);
|
|
286
|
+
case 'heyspex_promote_research':
|
|
287
|
+
return handlePromoteResearch(vaultRoot, args);
|
|
288
|
+
case 'heyspex_promote_capture':
|
|
289
|
+
return handlePromoteCapture(vaultRoot, args);
|
|
290
|
+
case 'heyspex_promote_capture_to_diagnose':
|
|
291
|
+
return handlePromoteCaptureToDiagnose(vaultRoot, args);
|
|
292
|
+
case 'heyspex_promote_research_to_diagnose':
|
|
293
|
+
return handlePromoteResearchToDiagnose(vaultRoot, args);
|
|
294
|
+
case 'heyspex_split_research':
|
|
295
|
+
return handleSplitResearch(vaultRoot, args);
|
|
296
|
+
case 'heyspex_record_alignment':
|
|
297
|
+
return handleRecordAlignmentViaTier(vaultRoot, config, args);
|
|
298
|
+
case 'heyspex_design_list_patterns':
|
|
299
|
+
return handleDesignListPatterns(vaultRoot, args);
|
|
300
|
+
case 'heyspex_design_get_pattern':
|
|
301
|
+
return handleDesignGetPattern(vaultRoot, args);
|
|
302
|
+
case 'heyspex_design_get_token':
|
|
303
|
+
return handleDesignGetToken(vaultRoot, args);
|
|
304
|
+
case 'heyspex_design_get_doc':
|
|
305
|
+
return handleDesignGetDoc(vaultRoot, args);
|
|
306
|
+
case 'heyspex_design_propose_pattern':
|
|
307
|
+
return handleDesignProposePattern(vaultRoot, args);
|
|
308
|
+
case 'heyspex_list_procedures':
|
|
309
|
+
return handleListProceduresViaTier(vaultRoot, args);
|
|
310
|
+
case 'heyspex_get_procedure':
|
|
311
|
+
return handleGetProcedureViaTier(vaultRoot, args);
|
|
312
|
+
case 'heyspex_file_spec':
|
|
313
|
+
return handleFileSpec(args, vaultRoot);
|
|
314
|
+
case 'heyspex_import_agent':
|
|
315
|
+
return handleImportAgent(args);
|
|
316
|
+
case 'heyspex_preflight':
|
|
317
|
+
return handleRunPreflightViaTier(vaultRoot, args);
|
|
318
|
+
case 'heyspex_list_recipes':
|
|
319
|
+
return handleListRecipes(vaultRoot, args);
|
|
320
|
+
case 'heyspex_start_recipe':
|
|
321
|
+
return handleStartRecipe(vaultRoot, args);
|
|
322
|
+
case 'heyspex_canvas_inbox':
|
|
323
|
+
return handleCanvasInbox(vaultRoot, args);
|
|
324
|
+
case 'heyspex_fleet_queue_submit':
|
|
325
|
+
return handleFleetQueueSubmit(args);
|
|
326
|
+
case 'heyspex_fleet_queue_claim':
|
|
327
|
+
return handleFleetQueueClaim(args);
|
|
328
|
+
case 'heyspex_fleet_queue_complete':
|
|
329
|
+
return handleFleetQueueComplete(args);
|
|
330
|
+
case 'heyspex_fleet_queue_list':
|
|
331
|
+
return handleFleetQueueList(args);
|
|
332
|
+
|
|
333
|
+
case 'heyspex_dev_server_status':
|
|
334
|
+
return handleDevServerStatus(vaultRoot);
|
|
335
|
+
case 'heyspex_dev_mode':
|
|
336
|
+
return handleDevMode(vaultRoot, args);
|
|
337
|
+
case 'heyspex_settings_list':
|
|
338
|
+
return wrapDesignResult(executeSettingsList(vaultRoot, args));
|
|
339
|
+
case 'heyspex_settings_get':
|
|
340
|
+
return wrapDesignResult(executeSettingsGet(vaultRoot, args));
|
|
341
|
+
case 'heyspex_settings_set':
|
|
342
|
+
return wrapDesignResult(executeSettingsSet(vaultRoot, args));
|
|
343
|
+
case 'heyspex_resolve_research_plan':
|
|
344
|
+
// async since HS-ZJSP (the Firecrawl endpoint is resolved through the lazy
|
|
345
|
+
// dev-factory bridge) — same Promise-returning shape as the case below.
|
|
346
|
+
return executeResolveResearchPlan(vaultRoot).then(wrapDesignResult);
|
|
347
|
+
case 'heyspex_firecrawl_research':
|
|
348
|
+
// dispatchHeySpexTool is NOT async (mirrors the heyspex_dev_server_status precedent
|
|
349
|
+
// above) — return the chained Promise directly; finalizeToolResultWithHooks/
|
|
350
|
+
// Promise.resolve() at the call site unwrap it.
|
|
351
|
+
return executeFirecrawlResearch(args).then(wrapDesignResult);
|
|
352
|
+
case 'heyspex_regen_task':
|
|
353
|
+
return handleRegenTask(vaultRoot, args);
|
|
354
|
+
case 'heyspex_regen_tasks_for_epic':
|
|
355
|
+
return handleRegenTasksForEpic(vaultRoot, args);
|
|
356
|
+
default:
|
|
357
|
+
return {
|
|
358
|
+
isError: true,
|
|
359
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Mode-aware vault gate, shared by `tools/call` (here) and `prompts/get`
|
|
366
|
+
* (cli.mjs). Returns `null` to proceed, or a `{ error, message }` payload.
|
|
367
|
+
*
|
|
368
|
+
* The old gate was `if (!existsSync(vaultRoot)) refuse` — mode-blind. In
|
|
369
|
+
* prod / cloud reliance (`resolveDevMode().readTier === 'neon'`) the server
|
|
370
|
+
* runs locally under Claude Code but every read is Neon-via-console and every
|
|
371
|
+
* write goes to the console, so a machine may legitimately have NO vault
|
|
372
|
+
* clone on disk; refusing there took every tool offline for exactly the
|
|
373
|
+
* customers the cloud path exists for. So: an absent root only refuses on the
|
|
374
|
+
* disk tier. On the neon tier the call proceeds with `vaultRoot` possibly
|
|
375
|
+
* absent — the `*ViaTier` handlers never touch it for reads, and the few
|
|
376
|
+
* machine-local writes (preflight markers) fall back to the HeySpex home
|
|
377
|
+
* (preflight-gate.mjs `preflightMarkerDir`).
|
|
378
|
+
*
|
|
379
|
+
* `resolveDevMode` is synchronous (env -> ~/.heyspex/dev-mode.json -> default
|
|
380
|
+
* prod) so `handleToolsCall` keeps its sync contract; a failure to resolve the
|
|
381
|
+
* mode degrades to the disk-tier refusal, the safe direction.
|
|
382
|
+
*
|
|
383
|
+
* Coverage: vault-gate-mode-aware.test.mjs drives `handleToolsCall` itself with
|
|
384
|
+
* a nonexistent root on both tiers. Note that cloud-reliant-session.test.mjs
|
|
385
|
+
* BYPASSES this gate on purpose — it calls the `*ViaTier` helpers directly —
|
|
386
|
+
* so it proves the handlers, not the gate; keep both.
|
|
387
|
+
*/
|
|
388
|
+
export function vaultRootGateVerdict(vaultRoot, { env = process.env } = {}) {
|
|
389
|
+
if (typeof vaultRoot === 'string' && vaultRoot && existsSync(vaultRoot)) return null;
|
|
390
|
+
let readTier = 'disk';
|
|
391
|
+
try {
|
|
392
|
+
readTier = resolveDevMode({ env }).readTier;
|
|
393
|
+
} catch {
|
|
394
|
+
readTier = 'disk';
|
|
395
|
+
}
|
|
396
|
+
if (readTier === 'neon') return null;
|
|
397
|
+
return { error: 'VAULT_NOT_FOUND', message: `Vault root not found: ${vaultRoot}` };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function handleToolsCall(params) {
|
|
401
|
+
let { name, arguments: args = {} } = params;
|
|
402
|
+
const config = getConfig();
|
|
403
|
+
const vaultRoot = config.vaultRoot;
|
|
404
|
+
|
|
405
|
+
const gate = vaultRootGateVerdict(vaultRoot);
|
|
406
|
+
if (gate) {
|
|
407
|
+
return {
|
|
408
|
+
isError: true,
|
|
409
|
+
content: [{ type: 'text', text: JSON.stringify(gate) }],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const hooksCfg = loadHooksConfig(vaultRoot);
|
|
414
|
+
|
|
415
|
+
// Meta hook plumbing — avoid Pre/PostToolUse recursion on hook tools themselves.
|
|
416
|
+
if (name === 'heyspex_session_end') return handleSessionEnd(vaultRoot, args, hooksCfg);
|
|
417
|
+
if (name === 'heyspex_hooks_list') return applyToolBudget(name, args, () => handleHooksList(vaultRoot), toolBudgetFor(name));
|
|
418
|
+
if (name === 'heyspex_hooks_invoke') return handleHooksInvoke(vaultRoot, args, hooksCfg);
|
|
419
|
+
|
|
420
|
+
// ── PreToolUse hooks ──
|
|
421
|
+
const preResult = runHooks('PreToolUse', name, args, null, hooksCfg);
|
|
422
|
+
if (preResult.blocked) {
|
|
423
|
+
return {
|
|
424
|
+
isError: true,
|
|
425
|
+
content: [{
|
|
426
|
+
type: 'text',
|
|
427
|
+
text: JSON.stringify({
|
|
428
|
+
error: 'HOOK_BLOCKED',
|
|
429
|
+
message: preResult.reason ?? 'Blocked by PreToolUse hook',
|
|
430
|
+
}),
|
|
431
|
+
}],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (preResult.modifiedInput) {
|
|
435
|
+
args = preResult.modifiedInput;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Preflight proof-of-use ([[preflight-gate]]). Deliberately here and not in a
|
|
439
|
+
// PreToolUse hook: hooks fail open and `.heyspex/mode.json` can disarm them,
|
|
440
|
+
// whereas this runs on every call in every session. `refuse` returns instead
|
|
441
|
+
// of dispatching; `warn` lets the call through and rides along on the result.
|
|
442
|
+
const preflightVerdict = evaluatePreflightGate(
|
|
443
|
+
vaultRoot,
|
|
444
|
+
name,
|
|
445
|
+
args,
|
|
446
|
+
detectAgentIdentity(args.agent, args.session_id).sessionId,
|
|
447
|
+
);
|
|
448
|
+
if (preflightVerdict && preflightVerdict.level === 'refuse') {
|
|
449
|
+
return {
|
|
450
|
+
isError: true,
|
|
451
|
+
content: [{
|
|
452
|
+
type: 'text',
|
|
453
|
+
text: JSON.stringify({
|
|
454
|
+
error: preflightVerdict.error,
|
|
455
|
+
message: preflightVerdict.message,
|
|
456
|
+
required_intents: preflightVerdict.intents,
|
|
457
|
+
tool: preflightVerdict.tool,
|
|
458
|
+
}, null, 2),
|
|
459
|
+
}],
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const result = dispatchHeySpexTool(name, args, vaultRoot, config);
|
|
464
|
+
const finalized = finalizeToolResultWithHooks(name, args, result, hooksCfg, preResult);
|
|
465
|
+
return preflightVerdict && preflightVerdict.level === 'warn'
|
|
466
|
+
? attachPreflightWarning(finalized, preflightVerdict)
|
|
467
|
+
: finalized;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Ride a warn-level preflight verdict along on an otherwise successful result.
|
|
472
|
+
* Best-effort by construction — a result whose text is not JSON is left exactly
|
|
473
|
+
* as it was rather than mangled to carry a warning.
|
|
474
|
+
*/
|
|
475
|
+
export function attachPreflightWarning(result, verdict) {
|
|
476
|
+
try {
|
|
477
|
+
const block = result?.content?.[0];
|
|
478
|
+
if (!block || block.type !== 'text' || typeof block.text !== 'string') return result;
|
|
479
|
+
const payload = JSON.parse(block.text);
|
|
480
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return result;
|
|
481
|
+
payload.preflight_warning = {
|
|
482
|
+
code: verdict.error,
|
|
483
|
+
message: verdict.message,
|
|
484
|
+
required_intents: verdict.intents,
|
|
485
|
+
};
|
|
486
|
+
return {
|
|
487
|
+
...result,
|
|
488
|
+
content: [{ ...block, text: JSON.stringify(payload, null, 2) }, ...result.content.slice(1)],
|
|
489
|
+
};
|
|
490
|
+
} catch {
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Handle heyspex_session_end — fire SessionEnd hooks and return results.
|
|
497
|
+
*
|
|
498
|
+
* The agent calls this explicitly at session close to trigger cleanup hooks
|
|
499
|
+
* (release claim, record WIP, notify console). In Claude Code these fire
|
|
500
|
+
* automatically via SessionEnd/Stop hooks; in Command Code the agent must
|
|
501
|
+
* call this tool.
|
|
502
|
+
*/
|
|
503
|
+
export function handleSessionEnd(vaultRoot, args, hooksCfg) {
|
|
504
|
+
const cfg = hooksCfg ?? loadHooksConfig(vaultRoot);
|
|
505
|
+
const groups = matchHooks('SessionEnd', 'heyspex_session_end', cfg);
|
|
506
|
+
if (groups.length === 0) {
|
|
507
|
+
return {
|
|
508
|
+
content: [{
|
|
509
|
+
type: 'text',
|
|
510
|
+
text: JSON.stringify({
|
|
511
|
+
session_ended: true,
|
|
512
|
+
hooks_run: 0,
|
|
513
|
+
note: 'No SessionEnd hooks configured. Call heyspex_release_spec if a spec is claimed.',
|
|
514
|
+
}, null, 2),
|
|
515
|
+
}],
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const results = [];
|
|
520
|
+
const payload = {
|
|
521
|
+
hook_event_name: 'SessionEnd',
|
|
522
|
+
tool_name: 'heyspex_session_end',
|
|
523
|
+
tool_input: { reason: args?.reason ?? 'completed' },
|
|
524
|
+
cwd: vaultRoot,
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
for (const group of groups) {
|
|
528
|
+
const handlers = group.hooks ?? [];
|
|
529
|
+
for (const hookDef of handlers) {
|
|
530
|
+
try {
|
|
531
|
+
const r = executeHook(hookDef, payload, { vaultRoot });
|
|
532
|
+
results.push({
|
|
533
|
+
command: hookDef.command,
|
|
534
|
+
args: hookDef.args,
|
|
535
|
+
success: !r.blocked,
|
|
536
|
+
...(r.reason ? { reason: r.reason } : {}),
|
|
537
|
+
});
|
|
538
|
+
} catch (err) {
|
|
539
|
+
results.push({
|
|
540
|
+
command: hookDef.command,
|
|
541
|
+
success: false,
|
|
542
|
+
error: err?.message ?? String(err),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
content: [{
|
|
550
|
+
type: 'text',
|
|
551
|
+
text: JSON.stringify({
|
|
552
|
+
session_ended: true,
|
|
553
|
+
hooks_run: results.length,
|
|
554
|
+
results,
|
|
555
|
+
}, null, 2),
|
|
556
|
+
}],
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Handle heyspex_hooks_invoke — run lifecycle hooks on demand (Command Code parity).
|
|
562
|
+
*/
|
|
563
|
+
export function handleHooksInvoke(vaultRoot, args, hooksCfg) {
|
|
564
|
+
const event = args?.event;
|
|
565
|
+
const validEvents = ['UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'SessionEnd'];
|
|
566
|
+
if (!validEvents.includes(event)) {
|
|
567
|
+
return {
|
|
568
|
+
isError: true,
|
|
569
|
+
content: [{
|
|
570
|
+
type: 'text',
|
|
571
|
+
text: JSON.stringify({
|
|
572
|
+
error: 'INVALID_EVENT',
|
|
573
|
+
message: `event must be one of: ${validEvents.join(', ')}`,
|
|
574
|
+
}),
|
|
575
|
+
}],
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (event === 'SessionEnd') {
|
|
580
|
+
return handleSessionEnd(vaultRoot, { reason: args?.reason ?? 'hooks_invoke' }, hooksCfg);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (event === 'UserPromptSubmit') {
|
|
584
|
+
const prompt = args?.prompt;
|
|
585
|
+
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
586
|
+
return {
|
|
587
|
+
isError: true,
|
|
588
|
+
content: [{
|
|
589
|
+
type: 'text',
|
|
590
|
+
text: JSON.stringify({
|
|
591
|
+
error: 'MISSING_PROMPT',
|
|
592
|
+
message: 'prompt is required for UserPromptSubmit',
|
|
593
|
+
}),
|
|
594
|
+
}],
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
const hookResult = runHooks('UserPromptSubmit', '*', { prompt }, null, hooksCfg);
|
|
598
|
+
return formatHookInvokeResult(hookResult);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const toolName = args?.tool_name ?? '*';
|
|
602
|
+
const toolInput = args?.tool_input ?? {};
|
|
603
|
+
|
|
604
|
+
if (event === 'PreToolUse') {
|
|
605
|
+
const hookResult = runHooks('PreToolUse', toolName, toolInput, null, hooksCfg);
|
|
606
|
+
return formatHookInvokeResult(hookResult);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const toolResponse = args?.tool_response ?? {};
|
|
610
|
+
const hookResult = runHooks('PostToolUse', toolName, toolInput, toolResponse, hooksCfg);
|
|
611
|
+
return formatHookInvokeResult(hookResult);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function formatHookInvokeResult(hookResult) {
|
|
615
|
+
return {
|
|
616
|
+
content: [{
|
|
617
|
+
type: 'text',
|
|
618
|
+
text: JSON.stringify({
|
|
619
|
+
blocked: hookResult.blocked ?? false,
|
|
620
|
+
reason: hookResult.reason ?? null,
|
|
621
|
+
additional_contexts: hookResult.additionalContexts ?? [],
|
|
622
|
+
modified_input: hookResult.modifiedInput ?? null,
|
|
623
|
+
}, null, 2),
|
|
624
|
+
}],
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Handle heyspex_hooks_list — return the active hook configuration.
|
|
630
|
+
*/
|
|
631
|
+
export function handleHooksList(vaultRoot) {
|
|
632
|
+
const cfg = listHookConfig(vaultRoot);
|
|
633
|
+
return {
|
|
634
|
+
content: [{
|
|
635
|
+
type: 'text',
|
|
636
|
+
text: JSON.stringify(cfg, null, 2),
|
|
637
|
+
}],
|
|
638
|
+
};
|
|
639
|
+
}
|