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
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* [[closed-trackable-specs-are-historical-record-not-living-docs]]) and
|
|
19
19
|
* classify each hit SUPERSEDED | SUBSUMED | SIBLING | UPSTREAM | PARENT.
|
|
20
20
|
* 3. Code audit — resolve the literal files/symbols the plan names against
|
|
21
|
-
* the
|
|
21
|
+
* the CODE checkout first and the vault checkout second
|
|
22
|
+
* ({@link resolveCodeAuditRoots}): unresolved in both → BLOCKER,
|
|
23
|
+
* resolved → INFO.
|
|
22
24
|
* 4. Canonical + supersession — borrow the SRP test from
|
|
23
25
|
* [[doc-canonicalization-protocol]] and the relationship language from
|
|
24
26
|
* [[supersession-protocol]].
|
|
@@ -31,7 +33,10 @@
|
|
|
31
33
|
|
|
32
34
|
import { createHash } from 'node:crypto';
|
|
33
35
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
34
|
-
import { join } from 'node:path';
|
|
36
|
+
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
37
|
+
import { PACKAGE_ROOT } from './package-root.mjs';
|
|
38
|
+
import { assertPathUnderRoot } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
39
|
+
import { resolveCodeAndVaultRoots } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
35
40
|
|
|
36
41
|
// In-flight statuses only. Terminal-status specs (closed / superseded /
|
|
37
42
|
// cancelled / duplicate / archived) are frozen historical records, filtered
|
|
@@ -56,11 +61,17 @@ const OVERLAP_DIRS = [
|
|
|
56
61
|
// Memory buckets scanned only for the Axis-B design-supersession pass (Pass 6).
|
|
57
62
|
const MEMORY_AXIS_DIRS = ['specs/memory/decisions', 'specs/memory/lessons'];
|
|
58
63
|
|
|
59
|
-
// Code roots scanned by the code-audit pass for symbol resolution
|
|
60
|
-
//
|
|
61
|
-
// (specs/ is intentionally excluded —
|
|
62
|
-
// mentioned only in prose specs must not
|
|
64
|
+
// Code roots scanned by the code-audit pass for symbol resolution, relative to
|
|
65
|
+
// each checkout in {@link resolveCodeAuditRoots}. Only those that exist are
|
|
66
|
+
// read; missing roots are skipped silently. (specs/ is intentionally excluded —
|
|
67
|
+
// it holds markdown, not code; symbols mentioned only in prose specs must not
|
|
68
|
+
// count as "resolved" in code.)
|
|
63
69
|
const CODE_ROOTS = ['vendor/heyspex-mcp', 'spex-console/src', 'scripts'];
|
|
70
|
+
|
|
71
|
+
// THIS PACKAGE's own root — `vendor/heyspex-mcp/plan-review.mjs` -> two levels
|
|
72
|
+
// up: the code repo root in the monorepo, `node_modules/heyspex` in a packed
|
|
73
|
+
// install. Same derivation `read-path.mjs` (PACKAGE_ROOT) and `dev-tools.mjs`
|
|
74
|
+
// (CHECKOUT_ROOT) already use for this package's own sibling code.
|
|
64
75
|
const CODE_EXTS = new Set(['.mjs', '.ts', '.tsx', '.js', '.jsx', '.cjs', '.mts', '.cts']);
|
|
65
76
|
const SKIP_DIRS = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.turbo', 'coverage']);
|
|
66
77
|
const MAX_CODE_FILES = 4000; // backstop so the audit can't walk an unbounded tree
|
|
@@ -541,9 +552,52 @@ function runOverlapPass(planBody, scope, candidates) {
|
|
|
541
552
|
return { in_flight_overlap: overlaps, supersedes, canonical_doc: canonicalDoc };
|
|
542
553
|
}
|
|
543
554
|
|
|
544
|
-
/**
|
|
545
|
-
|
|
546
|
-
|
|
555
|
+
/**
|
|
556
|
+
* The ordered checkouts the code audit resolves a plan's `files`/`symbols`
|
|
557
|
+
* against ([[HS-8BHX-OUJ0-DIAG-breakdown-plan-review-resolves-code]]).
|
|
558
|
+
*
|
|
559
|
+
* Since the repo split the vault (`specs/`) and the code (`package.json`,
|
|
560
|
+
* `scripts/`, `spex-console/src`, …) can be two separate checkouts, and the
|
|
561
|
+
* vault holds NO code — so resolving a plan's code paths against the vault root
|
|
562
|
+
* reported every real file as a `BLOCKER`, the highest severity the audit has.
|
|
563
|
+
* The CODE root therefore goes first (a plan's paths are almost always code),
|
|
564
|
+
* and the VAULT root is carried ALONGSIDE it rather than replacing it, so a plan
|
|
565
|
+
* that legitimately names vault content (`specs/…`, `heyspex.yaml`) still
|
|
566
|
+
* resolves. A path present in NEITHER checkout is still a real BLOCKER.
|
|
567
|
+
*
|
|
568
|
+
* Code-root precedence mirrors the two already-fixed sites of this bug family
|
|
569
|
+
* (`spex-console/bin/hs-check.ts`, `spex-console/bin/hs-revalidate.ts`): an
|
|
570
|
+
* explicit caller override, then the `HS_PKG_ROOT` env the `bin/hs.mjs`
|
|
571
|
+
* dispatcher forwards, then this package's own install location. The MCP server
|
|
572
|
+
* is spawned by the editor, not by that dispatcher, so `PACKAGE_ROOT` is the
|
|
573
|
+
* one that normally answers — the same derivation `read-path.mjs` and
|
|
574
|
+
* `dev-tools.mjs` use for this package's sibling code. Single-checkout installs
|
|
575
|
+
* collapse the two roots back to one entry, so nothing changes there.
|
|
576
|
+
*
|
|
577
|
+
* @param {string} vaultRoot resolved vault root (holds `specs/`)
|
|
578
|
+
* @param {string} [codeRootOverride] explicit code root (tests / future callers)
|
|
579
|
+
* @returns {{ label: string, root: string }[]}
|
|
580
|
+
*/
|
|
581
|
+
export function resolveCodeAuditRoots(vaultRoot, codeRootOverride) {
|
|
582
|
+
return resolveCodeAndVaultRoots({
|
|
583
|
+
vaultRoot,
|
|
584
|
+
explicit: codeRootOverride,
|
|
585
|
+
packageRoot: PACKAGE_ROOT,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** "the code root (C:\a) or the vault root (C:\b)" — AC3: say where we looked. */
|
|
590
|
+
function describeRoots(roots) {
|
|
591
|
+
return roots.map((r) => `the ${r.label} (${r.root})`).join(' or ');
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Recursively collect code files under `root` inside one checkout, bounded by
|
|
596
|
+
* MAX_CODE_FILES. Each entry keeps the checkout it came from so the reader can
|
|
597
|
+
* be told which root resolved a symbol.
|
|
598
|
+
*/
|
|
599
|
+
function collectCodeFiles(base, root, acc) {
|
|
600
|
+
const full = join(base.root, root);
|
|
547
601
|
if (!existsSync(full)) return;
|
|
548
602
|
let entries;
|
|
549
603
|
try {
|
|
@@ -555,45 +609,56 @@ function collectCodeFiles(vaultRoot, root, acc) {
|
|
|
555
609
|
if (acc.length >= MAX_CODE_FILES) return;
|
|
556
610
|
if (entry.isDirectory()) {
|
|
557
611
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
558
|
-
collectCodeFiles(
|
|
612
|
+
collectCodeFiles(base, `${root}/${entry.name}`, acc);
|
|
559
613
|
} else if (entry.isFile()) {
|
|
560
614
|
const dot = entry.name.lastIndexOf('.');
|
|
561
615
|
const ext = dot === -1 ? '' : entry.name.slice(dot);
|
|
562
616
|
if (!CODE_EXTS.has(ext)) continue;
|
|
563
|
-
acc.push(`${root}/${entry.name}`);
|
|
617
|
+
acc.push({ base, rel: `${root}/${entry.name}` });
|
|
564
618
|
}
|
|
565
619
|
}
|
|
566
620
|
}
|
|
567
621
|
|
|
568
622
|
/**
|
|
569
623
|
* Pass 3 — code audit. Resolve each file path and symbol the plan names against
|
|
570
|
-
* the
|
|
624
|
+
* the code checkout first and the vault checkout second
|
|
625
|
+
* ({@link resolveCodeAuditRoots}). Unresolved in BOTH → BLOCKER; resolved → INFO.
|
|
626
|
+
*
|
|
627
|
+
* @returns {{ findings: object[], roots: { label: string, root: string }[] }}
|
|
571
628
|
*/
|
|
572
|
-
function runCodeAudit(vaultRoot, scope, planContent) {
|
|
629
|
+
function runCodeAudit(vaultRoot, scope, planContent, codeRootOverride) {
|
|
573
630
|
const findings = [];
|
|
631
|
+
const roots = resolveCodeAuditRoots(vaultRoot, codeRootOverride);
|
|
574
632
|
|
|
575
|
-
// File-path resolution — cheap existence check.
|
|
633
|
+
// File-path resolution — cheap existence check, per checkout in order.
|
|
576
634
|
for (const f of scope.files) {
|
|
577
635
|
const clean = f.replace(/^\.\//, '');
|
|
578
|
-
const
|
|
636
|
+
const hit = roots.find((entry) => existsSync(join(entry.root, clean)));
|
|
579
637
|
findings.push({
|
|
580
638
|
kind: 'file',
|
|
581
639
|
ref: f,
|
|
582
|
-
severity:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
640
|
+
severity: hit ? 'INFO' : 'BLOCKER',
|
|
641
|
+
resolved_root: hit ? hit.label : null,
|
|
642
|
+
note: hit
|
|
643
|
+
? `File exists at ${clean} under the ${hit.label} (${hit.root}).`
|
|
644
|
+
: `Plan references ${f} but no such file exists under ${describeRoots(roots)} — fix the path or add it to scope.`,
|
|
586
645
|
});
|
|
587
646
|
}
|
|
588
647
|
|
|
589
648
|
// Symbol resolution — build the code corpus once, then test each symbol.
|
|
590
649
|
if (scope.symbols.length) {
|
|
591
650
|
const files = [];
|
|
592
|
-
for (const
|
|
651
|
+
for (const entry of roots) {
|
|
652
|
+
for (const root of CODE_ROOTS) collectCodeFiles(entry, root, files);
|
|
653
|
+
}
|
|
593
654
|
const corpus = [];
|
|
594
|
-
for (const
|
|
655
|
+
for (const file of files) {
|
|
595
656
|
try {
|
|
596
|
-
corpus.push({
|
|
657
|
+
corpus.push({
|
|
658
|
+
rel: file.rel,
|
|
659
|
+
base: file.base,
|
|
660
|
+
content: readFileSync(join(file.base.root, file.rel), 'utf8'),
|
|
661
|
+
});
|
|
597
662
|
} catch {
|
|
598
663
|
/* skip unreadable */
|
|
599
664
|
}
|
|
@@ -601,9 +666,11 @@ function runCodeAudit(vaultRoot, scope, planContent) {
|
|
|
601
666
|
for (const sym of scope.symbols) {
|
|
602
667
|
const re = new RegExp(`\\b${sym.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
|
|
603
668
|
const where = [];
|
|
669
|
+
const whereRoots = new Set();
|
|
604
670
|
for (const file of corpus) {
|
|
605
671
|
if (re.test(file.content)) {
|
|
606
672
|
where.push(file.rel);
|
|
673
|
+
whereRoots.add(file.base.label);
|
|
607
674
|
if (where.length >= 5) break;
|
|
608
675
|
}
|
|
609
676
|
}
|
|
@@ -612,14 +679,15 @@ function runCodeAudit(vaultRoot, scope, planContent) {
|
|
|
612
679
|
ref: sym,
|
|
613
680
|
severity: where.length ? 'INFO' : 'BLOCKER',
|
|
614
681
|
where,
|
|
682
|
+
resolved_root: where.length ? [...whereRoots].join(', ') : null,
|
|
615
683
|
note: where.length
|
|
616
|
-
? `Symbol resolves in ${where.length === 5 ? '5+' : where.length} file(s).`
|
|
617
|
-
: `Plan references \`${sym}\` but it is not defined anywhere in the scanned code roots (${CODE_ROOTS.join(', ')}) — verify the name or add the missing implementation to scope.`,
|
|
684
|
+
? `Symbol resolves in ${where.length === 5 ? '5+' : where.length} file(s) under the ${[...whereRoots].join(', ')}.`
|
|
685
|
+
: `Plan references \`${sym}\` but it is not defined anywhere in the scanned code roots (${CODE_ROOTS.join(', ')}) under ${describeRoots(roots)} — verify the name or add the missing implementation to scope.`,
|
|
618
686
|
});
|
|
619
687
|
}
|
|
620
688
|
}
|
|
621
689
|
|
|
622
|
-
return findings;
|
|
690
|
+
return { findings, roots };
|
|
623
691
|
}
|
|
624
692
|
|
|
625
693
|
/** Pass 4 — turn findings into actionable V2 patch hints. */
|
|
@@ -976,6 +1044,7 @@ export function buildQualityJudgeBrief(proposedTasks = []) {
|
|
|
976
1044
|
return lines.join('\n');
|
|
977
1045
|
}
|
|
978
1046
|
|
|
1047
|
+
|
|
979
1048
|
/**
|
|
980
1049
|
* Run the full plan review. Accepts either a `planPath` (read from disk,
|
|
981
1050
|
* relative to vaultRoot or absolute) or inline `planContent`. When
|
|
@@ -983,12 +1052,20 @@ export function buildQualityJudgeBrief(proposedTasks = []) {
|
|
|
983
1052
|
* synthesize review input from a draft task set. Writes a marker file keyed
|
|
984
1053
|
* by the sha256 of the plan content and returns the findings.
|
|
985
1054
|
*
|
|
1055
|
+
* `codeRoot` is an optional override for the CODE checkout the pass-3 audit
|
|
1056
|
+
* resolves file paths and symbols against; it defaults to `HS_PKG_ROOT` / this
|
|
1057
|
+
* package's own root, with the vault root carried alongside it
|
|
1058
|
+
* ({@link resolveCodeAuditRoots} —
|
|
1059
|
+
* [[HS-8BHX-OUJ0-DIAG-breakdown-plan-review-resolves-code]]).
|
|
1060
|
+
*
|
|
986
1061
|
* @returns { scope_terms, in_flight_overlap, canonical_doc, supersedes,
|
|
987
|
-
* code_audit,
|
|
988
|
-
* review_marker_path, review_scope,
|
|
1062
|
+
* code_audit, code_audit_roots, vocabulary_reconciliation,
|
|
1063
|
+
* v2_patch_hints, review_marker_path, review_scope,
|
|
1064
|
+
* quality_findings? } | { error, message }
|
|
989
1065
|
*/
|
|
990
1066
|
export function runPlanReview({
|
|
991
1067
|
vaultRoot,
|
|
1068
|
+
codeRoot,
|
|
992
1069
|
planPath,
|
|
993
1070
|
planContent,
|
|
994
1071
|
scope = 'plan',
|
|
@@ -1058,7 +1135,12 @@ export function runPlanReview({
|
|
|
1058
1135
|
const scopeTerms = extractScopeTerms(analysisText);
|
|
1059
1136
|
const candidates = loadOverlapCandidates(vaultRoot);
|
|
1060
1137
|
const { in_flight_overlap, supersedes, canonical_doc } = runOverlapPass(analysisText, scopeTerms, candidates);
|
|
1061
|
-
const code_audit = runCodeAudit(
|
|
1138
|
+
const { findings: code_audit, roots: codeAuditRoots } = runCodeAudit(
|
|
1139
|
+
vaultRoot,
|
|
1140
|
+
scopeTerms,
|
|
1141
|
+
content,
|
|
1142
|
+
codeRoot,
|
|
1143
|
+
);
|
|
1062
1144
|
|
|
1063
1145
|
// Pass 5 — vocabulary reconciliation, scoped to the specs the overlap pass
|
|
1064
1146
|
// already flagged (high precision). Index candidates by every id form so the
|
|
@@ -1088,6 +1170,9 @@ export function runPlanReview({
|
|
|
1088
1170
|
canonical_doc,
|
|
1089
1171
|
supersedes,
|
|
1090
1172
|
code_audit,
|
|
1173
|
+
// AC3 — say which checkouts the code audit searched, so a future wrong-root
|
|
1174
|
+
// failure is readable from the output instead of guessed at.
|
|
1175
|
+
code_audit_roots: codeAuditRoots.map((entry) => ({ label: entry.label, root: entry.root })),
|
|
1091
1176
|
vocabulary_reconciliation,
|
|
1092
1177
|
axis_b_drift: axis_b.axis_b_drift,
|
|
1093
1178
|
axis_b_decisions_checked: axis_b.axis_b_decisions_checked,
|
|
@@ -1123,10 +1208,14 @@ export function runPlanReview({
|
|
|
1123
1208
|
const reviewsDirRel = join('.heyspex', 'plan-reviews');
|
|
1124
1209
|
const reviewsDirAbs = join(vaultRoot, reviewsDirRel);
|
|
1125
1210
|
const markerRel = join(reviewsDirRel, `${hash}.json`);
|
|
1211
|
+
const markerAbs = join(vaultRoot, markerRel);
|
|
1126
1212
|
try {
|
|
1213
|
+
// Refuse BEFORE any mkdirSync/writeFileSync — a malformed vaultRoot must
|
|
1214
|
+
// create nothing on disk, not create it somewhere wrong ([[HS-MMR6]]).
|
|
1215
|
+
assertPathUnderRoot(vaultRoot, markerAbs, 'plan-review marker write');
|
|
1127
1216
|
if (!existsSync(reviewsDirAbs)) mkdirSync(reviewsDirAbs, { recursive: true });
|
|
1128
1217
|
const marker = { ...result, plan_content_sha256: hash, reviewed_marker: true };
|
|
1129
|
-
writeFileSync(
|
|
1218
|
+
writeFileSync(markerAbs, JSON.stringify(marker, null, 2), 'utf8');
|
|
1130
1219
|
result.review_marker_path = markerRel.replace(/\\/g, '/');
|
|
1131
1220
|
} catch (err) {
|
|
1132
1221
|
// Marker write failure is surfaced but doesn't drop the findings — the
|
|
@@ -1405,9 +1494,14 @@ export function runPlanQc({
|
|
|
1405
1494
|
axis_b_user_confirmed: userConfirmed && axisB.axis_b_drift.length > 0,
|
|
1406
1495
|
axis_b_confirmation_note: userConfirmed && confirmationNote ? confirmationNote : undefined,
|
|
1407
1496
|
};
|
|
1497
|
+
const qcMarkerAbs = join(vaultRoot, markerRel);
|
|
1408
1498
|
try {
|
|
1499
|
+
// Refuse BEFORE any mkdirSync/writeFileSync -- same guard, same reason as
|
|
1500
|
+
// the plan-review marker write above ([[HS-MMR6]]): a malformed vaultRoot
|
|
1501
|
+
// must create nothing on disk, not create it somewhere wrong.
|
|
1502
|
+
assertPathUnderRoot(vaultRoot, qcMarkerAbs, 'plan-review marker write');
|
|
1409
1503
|
if (!existsSync(qcDirAbs)) mkdirSync(qcDirAbs, { recursive: true });
|
|
1410
|
-
writeFileSync(
|
|
1504
|
+
writeFileSync(qcMarkerAbs, JSON.stringify(marker, null, 2), 'utf8');
|
|
1411
1505
|
return {
|
|
1412
1506
|
qc_marker_path: markerRel.replace(/\\/g, '/'),
|
|
1413
1507
|
plan_content_sha256: hash,
|
|
Binary file
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// heyspex_list_procedures, get_procedure, preflight handlers (disk and tier-aware
|
|
2
|
+
// variants; the procedure store itself is procedure-tools.mjs). Extracted from cli.mjs under
|
|
3
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
4
|
+
|
|
5
|
+
import { executeGetProcedure, executeGetProcedureViaTier, executeListProcedures, executeListProceduresViaTier, executeRunPreflight, executeRunPreflightViaTier } from './procedure-tools.mjs';
|
|
6
|
+
import { detectAgentIdentity, effectiveConsoleUrl } from './mcp-config.mjs';
|
|
7
|
+
import { preflightMarkerNotice, writePreflightMarker } from './preflight-gate.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* [[HS-K0ZP]]: reading research-capture-protocol directly is the same proof that
|
|
11
|
+
* running heyspex_preflight({ intent: "research" }) gives, so a successful
|
|
12
|
+
* heyspex_get_procedure on it writes the `research` marker too. Only a real body
|
|
13
|
+
* counts -- a NOT_FOUND response taught the agent nothing. Never throws: the
|
|
14
|
+
* marker write is fail-open by contract, and a read must never break on it.
|
|
15
|
+
*/
|
|
16
|
+
export const RESEARCH_PROTOCOL_NAME = 'research-capture-protocol';
|
|
17
|
+
export function recordResearchProtocolRead(vaultRoot, args, result) {
|
|
18
|
+
try {
|
|
19
|
+
if (String(args?.name ?? '').trim() !== RESEARCH_PROTOCOL_NAME) return false;
|
|
20
|
+
if (!result || result.error || !result.content) return false;
|
|
21
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
22
|
+
return writePreflightMarker(vaultRoot, 'research', {
|
|
23
|
+
sessionId: identity.sessionId,
|
|
24
|
+
agent: identity.agent,
|
|
25
|
+
procedures: [RESEARCH_PROTOCOL_NAME],
|
|
26
|
+
degradedSource: result.degraded ? 'disk' : undefined,
|
|
27
|
+
});
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
import { matchingRecipes } from './recipe-tools.mjs';
|
|
33
|
+
import { wrapDesignResult } from './tool-helpers.mjs';
|
|
34
|
+
import { MEMORY_BASELINE_TAGS, MEMORY_DESCRIPTION_TRUNCATE, MEMORY_INJECTION_LIMIT, detectMemoryAmendment, scoreRelevantMemories } from './on-deck-brief.mjs';
|
|
35
|
+
import { consoleReadErrorFrom, readViaSelector, resolveReadTier } from './read-path.mjs';
|
|
36
|
+
import { consoleFetch } from './console-transport.mjs';
|
|
37
|
+
import { extractSummary } from './memory-tools.mjs';
|
|
38
|
+
|
|
39
|
+
export function handleListProcedures(vaultRoot, args) {
|
|
40
|
+
return wrapDesignResult(executeListProcedures(vaultRoot, args));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function handleGetProcedure(vaultRoot, args) {
|
|
44
|
+
const result = executeGetProcedure(vaultRoot, args);
|
|
45
|
+
recordResearchProtocolRead(vaultRoot, args, result);
|
|
46
|
+
return wrapDesignResult(result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function handleRunPreflight(vaultRoot, args) {
|
|
50
|
+
const result = executeRunPreflight(vaultRoot, args);
|
|
51
|
+
|
|
52
|
+
// Proof-of-use ([[preflight-gate]]): record that the questions were actually
|
|
53
|
+
// asked, so the tools those questions govern can refuse when they were not.
|
|
54
|
+
// Only a real answer counts — an UNKNOWN_INTENT / CATALOG_NOT_FOUND response
|
|
55
|
+
// taught the agent nothing and must not clear a gate.
|
|
56
|
+
if (result && !result.error) {
|
|
57
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
58
|
+
const wrote = writePreflightMarker(vaultRoot, String(args.intent ?? '').trim(), {
|
|
59
|
+
sessionId: identity.sessionId,
|
|
60
|
+
agent: identity.agent,
|
|
61
|
+
threadType: args.thread_type ?? null,
|
|
62
|
+
procedures: Array.isArray(result.procedures)
|
|
63
|
+
? result.procedures.map((p) => p?.procedure).filter(Boolean)
|
|
64
|
+
: [],
|
|
65
|
+
});
|
|
66
|
+
// A marker we could not write used to be swallowed silently, so preflight
|
|
67
|
+
// reported success while the gated tools refused forever and told the caller
|
|
68
|
+
// to "call preflight, then retry" — a loop no retry escaped
|
|
69
|
+
// ([[HS-N71O-P3XQ-DIAG-six-new-qc-gates-fail-cold-verification]] item 3).
|
|
70
|
+
// Preflight still succeeds (fail-open); it just stops claiming it recorded
|
|
71
|
+
// something it did not.
|
|
72
|
+
const notice = preflightMarkerNotice(wrote);
|
|
73
|
+
if (notice) Object.assign(result, notice);
|
|
74
|
+
}
|
|
75
|
+
// Match workflow-recipes against the intent so recipes are discoverable the
|
|
76
|
+
// same way procedures are ([[HS-RQ9K-TASK]] / [[HS-5G0P-EPIC]]). Never throws.
|
|
77
|
+
let recipes = [];
|
|
78
|
+
try {
|
|
79
|
+
recipes = matchingRecipes(vaultRoot, args.intent);
|
|
80
|
+
} catch {
|
|
81
|
+
recipes = [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Recipe-only intent (e.g. "bug_fix"): a valid recipe signature that is NOT a
|
|
85
|
+
// procedure intent returns UNKNOWN_INTENT from the procedure map. Rather than
|
|
86
|
+
// hard-error, surface the matching recipes so the caller still gets a useful
|
|
87
|
+
// answer — recipe intents live in a different namespace than procedure intents.
|
|
88
|
+
if (result && result.error === 'UNKNOWN_INTENT' && recipes.length > 0) {
|
|
89
|
+
return wrapDesignResult({
|
|
90
|
+
intent: String(args.intent ?? '').trim(),
|
|
91
|
+
procedures: [],
|
|
92
|
+
recipes,
|
|
93
|
+
note:
|
|
94
|
+
'No procedure checklist for this intent, but it matches one or more workflow-recipes. Start one with heyspex_start_recipe, or call heyspex_preflight with a procedure intent (e.g. file_task, execute_spec).',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Attach recalled_memories so high-stakes decision points carry the same
|
|
99
|
+
// memory load /hs-next does. Skipped on error responses ([[HS-TASK-IMMF]]).
|
|
100
|
+
if (result && !result.error) {
|
|
101
|
+
try {
|
|
102
|
+
const recalled = scoreRelevantMemories(vaultRoot, {
|
|
103
|
+
// Use the intent name as a primary signal so an intent like
|
|
104
|
+
// recommend_location surfaces template/saas/architecture memories
|
|
105
|
+
// strongly. The baseline pool still applies on top.
|
|
106
|
+
primaryTags: typeof args.intent === 'string' ? [args.intent] : [],
|
|
107
|
+
});
|
|
108
|
+
if (recalled.items.length > 0) {
|
|
109
|
+
result.recalled_memories = recalled;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Recall failure must never break preflight — surface the procedures
|
|
113
|
+
// without memories rather than 500ing the response.
|
|
114
|
+
}
|
|
115
|
+
if (recipes.length > 0) {
|
|
116
|
+
result.recipes = recipes;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return wrapDesignResult(result);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Cloud-reliant sibling of `handleListProcedures` ([[HS-DJWP-MCPC-TASK]]
|
|
124
|
+
* "Procedures" family, backing `heyspex_list_procedures`). `handleListProcedures`
|
|
125
|
+
* stays untouched (its `diskRead` branch).
|
|
126
|
+
*/
|
|
127
|
+
export async function handleListProceduresViaTier(vaultRoot, args) {
|
|
128
|
+
const tier = await resolveReadTier();
|
|
129
|
+
const result = await executeListProceduresViaTier({
|
|
130
|
+
vaultRoot,
|
|
131
|
+
args,
|
|
132
|
+
tier,
|
|
133
|
+
consoleFetch,
|
|
134
|
+
consoleUrl: effectiveConsoleUrl(),
|
|
135
|
+
});
|
|
136
|
+
return wrapDesignResult(result);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Cloud-reliant sibling of `handleGetProcedure` ([[HS-DJWP-MCPC-TASK]]
|
|
141
|
+
* "Procedures" family, backing `heyspex_get_procedure`). `handleGetProcedure`
|
|
142
|
+
* stays untouched (its `diskRead` branch).
|
|
143
|
+
*/
|
|
144
|
+
export async function handleGetProcedureViaTier(vaultRoot, args) {
|
|
145
|
+
const tier = await resolveReadTier();
|
|
146
|
+
const result = await executeGetProcedureViaTier({
|
|
147
|
+
vaultRoot,
|
|
148
|
+
args,
|
|
149
|
+
tier,
|
|
150
|
+
consoleFetch,
|
|
151
|
+
consoleUrl: effectiveConsoleUrl(),
|
|
152
|
+
});
|
|
153
|
+
recordResearchProtocolRead(vaultRoot, args, result);
|
|
154
|
+
return wrapDesignResult(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cloud-reliant sibling of `handleRunPreflight` ([[HS-DJWP-MCPC-TASK]]
|
|
159
|
+
* "Procedures" + "Memories" families, backing `heyspex_preflight`).
|
|
160
|
+
* `handleRunPreflight` is left completely untouched — it is imported and
|
|
161
|
+
* called directly (synchronously) by `preflight-gate.test.mjs`, so it must
|
|
162
|
+
* keep returning a plain object, never a Promise. This is a SEPARATE function,
|
|
163
|
+
* used only by the live tool dispatch (`case 'heyspex_preflight'` below),
|
|
164
|
+
* which duplicates the marker-write / recipe-match / memory-recall tail
|
|
165
|
+
* (`writePreflightMarker`, `preflightMarkerNotice`, `matchingRecipes` are
|
|
166
|
+
* reused unchanged — only the tier-aware catalog + memory fetch differ).
|
|
167
|
+
*/
|
|
168
|
+
export async function handleRunPreflightViaTier(vaultRoot, args) {
|
|
169
|
+
const tier = await resolveReadTier();
|
|
170
|
+
const consoleUrl = effectiveConsoleUrl();
|
|
171
|
+
const result = await executeRunPreflightViaTier({ vaultRoot, args, tier, consoleFetch, consoleUrl });
|
|
172
|
+
|
|
173
|
+
if (result && !result.error) {
|
|
174
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
175
|
+
const wrote = writePreflightMarker(vaultRoot, String(args.intent ?? '').trim(), {
|
|
176
|
+
sessionId: identity.sessionId,
|
|
177
|
+
agent: identity.agent,
|
|
178
|
+
threadType: args.thread_type ?? null,
|
|
179
|
+
procedures: Array.isArray(result.procedures)
|
|
180
|
+
? result.procedures.map((p) => p?.procedure).filter(Boolean)
|
|
181
|
+
: [],
|
|
182
|
+
degradedSource: result.degraded ? 'disk' : undefined,
|
|
183
|
+
});
|
|
184
|
+
const notice = preflightMarkerNotice(wrote);
|
|
185
|
+
if (notice) Object.assign(result, notice);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let recipes = [];
|
|
189
|
+
try {
|
|
190
|
+
recipes = matchingRecipes(vaultRoot, args.intent);
|
|
191
|
+
} catch {
|
|
192
|
+
recipes = [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (result && result.error === 'UNKNOWN_INTENT' && recipes.length > 0) {
|
|
196
|
+
return wrapDesignResult({
|
|
197
|
+
intent: String(args.intent ?? '').trim(),
|
|
198
|
+
procedures: [],
|
|
199
|
+
recipes,
|
|
200
|
+
note:
|
|
201
|
+
'No procedure checklist for this intent, but it matches one or more workflow-recipes. Start one with heyspex_start_recipe, or call heyspex_preflight with a procedure intent (e.g. file_task, execute_spec).',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (result && !result.error) {
|
|
206
|
+
try {
|
|
207
|
+
const recalled = await scoreRelevantMemoriesViaTier(
|
|
208
|
+
vaultRoot,
|
|
209
|
+
{ primaryTags: typeof args.intent === 'string' ? [args.intent] : [] },
|
|
210
|
+
{ tier, consoleUrl },
|
|
211
|
+
);
|
|
212
|
+
if (recalled.items.length > 0) {
|
|
213
|
+
result.recalled_memories = recalled;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Recall failure must never break preflight.
|
|
217
|
+
}
|
|
218
|
+
if (recipes.length > 0) {
|
|
219
|
+
result.recipes = recipes;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return wrapDesignResult(result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Cloud-reliant sibling of `scoreRelevantMemories` ([[HS-DJWP-MCPC-TASK]]
|
|
227
|
+
* "Memories" family, backing the preflight memory recall). `scoreRelevantMemories`
|
|
228
|
+
* is left completely untouched (its callers — `handleRunPreflight`,
|
|
229
|
+
* `enrichOnDeckWithRecalledMemories` — keep the exact disk-only behavior they
|
|
230
|
+
* have today); this is a NEW, separate cloud path used only by
|
|
231
|
+
* `handleRunPreflightViaTier` below. Scoring logic (tag-overlap, amendment
|
|
232
|
+
* detection, truncation, sort) is duplicated rather than shared with
|
|
233
|
+
* `scoreRelevantMemories` deliberately — the two operate on differently-shaped
|
|
234
|
+
* inputs (disk: `{frontmatter, body}` per file; cloud: the console's flat
|
|
235
|
+
* `MemoryDoc` rows) and duplicating ~25 lines is a smaller risk than
|
|
236
|
+
* refactoring the already-tested disk function to share a helper.
|
|
237
|
+
*
|
|
238
|
+
* Unlike `handleRecallMemoriesViaTier`, no fidelity gap here — `MemoryDoc`
|
|
239
|
+
* already carries everything this scorer reads (`tags`, `body`, `name`, `id`).
|
|
240
|
+
*
|
|
241
|
+
* Split into a tag-only ranking pass ({@link rankMemoryCandidatesByTags}) and a
|
|
242
|
+
* per-item body-dependent builder ({@link buildScoredMemoryItem}) so the
|
|
243
|
+
* caller ({@link scoreRelevantMemoriesViaTier}) can rank against a
|
|
244
|
+
* summary-only listing (score is pure tag-overlap, never touches body) and
|
|
245
|
+
* only fetch body text for the top `limit` survivors —
|
|
246
|
+
* [[HS-LKV0-FUU4-DIAG-cloud-mode-recall-times-out-the]]: the console's
|
|
247
|
+
* `GET /api/memories` used to always ship a body per doc, which is what made
|
|
248
|
+
* a 896-doc vault blow the 30s fetch timeout on every one of these read paths.
|
|
249
|
+
*/
|
|
250
|
+
export function rankMemoryCandidatesByTags(docs, options = {}) {
|
|
251
|
+
const primaryTags = Array.isArray(options.primaryTags) ? options.primaryTags : [];
|
|
252
|
+
const baselineTags = Array.isArray(options.baselineTags) ? options.baselineTags : MEMORY_BASELINE_TAGS;
|
|
253
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : MEMORY_INJECTION_LIMIT;
|
|
254
|
+
|
|
255
|
+
const querySet = new Set([...primaryTags, ...baselineTags].map((t) => String(t).toLowerCase()));
|
|
256
|
+
if (querySet.size === 0) return [];
|
|
257
|
+
|
|
258
|
+
const scored = [];
|
|
259
|
+
for (const doc of docs) {
|
|
260
|
+
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
261
|
+
if (tags.length === 0) continue;
|
|
262
|
+
const tagSet = new Set(tags.map((t) => String(t).toLowerCase()));
|
|
263
|
+
let score = 0;
|
|
264
|
+
for (const q of querySet) if (tagSet.has(q)) score += 1;
|
|
265
|
+
if (score === 0) continue;
|
|
266
|
+
scored.push({ doc, score });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
scored.sort((a, b) => {
|
|
270
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
271
|
+
const aName = a.doc.name || a.doc.id || '';
|
|
272
|
+
const bName = b.doc.name || b.doc.id || '';
|
|
273
|
+
return String(aName).localeCompare(String(bName));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return scored.slice(0, limit);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Builds one scored-memory item from a ranked candidate + its (separately fetched) body. */
|
|
280
|
+
export function buildScoredMemoryItem(doc, score, body) {
|
|
281
|
+
const slug = doc.id;
|
|
282
|
+
const name = doc.name || doc.id || slug;
|
|
283
|
+
let description = extractSummary(body || '');
|
|
284
|
+
if (description.length > MEMORY_DESCRIPTION_TRUNCATE) {
|
|
285
|
+
description = description.slice(0, MEMORY_DESCRIPTION_TRUNCATE - 1) + '…';
|
|
286
|
+
}
|
|
287
|
+
const amended = detectMemoryAmendment(body || '', description);
|
|
288
|
+
if (amended) {
|
|
289
|
+
description = `${description} ⚠ ${amended} — this summary may not reflect the current ruling; READ THE FULL BODY before applying.`;
|
|
290
|
+
}
|
|
291
|
+
return { source: 'vault', name, link: `[[${slug}]]`, description, amended: amended || undefined, score };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function scoreRelevantMemoriesViaTier(vaultRoot, options, { tier, consoleUrl }) {
|
|
295
|
+
return readViaSelector({
|
|
296
|
+
tier,
|
|
297
|
+
diskRead: () => scoreRelevantMemories(vaultRoot, options),
|
|
298
|
+
neonRead: async () => {
|
|
299
|
+
// Summary-only listing — scoring is pure tag-overlap and never needs body.
|
|
300
|
+
const res = await consoleFetch('/api/memories');
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
throw consoleReadErrorFrom(res, `GET /api/memories returned ${res.status}: ${res.error || JSON.stringify(res.body ?? {})}`, null);
|
|
303
|
+
}
|
|
304
|
+
const docs = Array.isArray(res.body?.memories) ? res.body.memories : [];
|
|
305
|
+
const ranked = rankMemoryCandidatesByTags(docs, options);
|
|
306
|
+
if (ranked.length === 0) return { header: null, items: [] };
|
|
307
|
+
|
|
308
|
+
// Fetch bodies ONLY for the survivors that ranking already narrowed to
|
|
309
|
+
// (`limit`, default 5) — never one fetch per candidate across the whole set.
|
|
310
|
+
const idsQs = new URLSearchParams({ ids: ranked.map(({ doc }) => doc.id).join(','), fields: 'body' });
|
|
311
|
+
const bodyPath = `/api/memories?${idsQs}`;
|
|
312
|
+
const bodyRes = await consoleFetch(bodyPath);
|
|
313
|
+
if (!bodyRes.ok) {
|
|
314
|
+
throw consoleReadErrorFrom(bodyRes, `GET ${bodyPath} returned ${bodyRes.status}: ${bodyRes.error || JSON.stringify(bodyRes.body ?? {})}`, null);
|
|
315
|
+
}
|
|
316
|
+
const bodyDocs = Array.isArray(bodyRes.body?.memories) ? bodyRes.body.memories : [];
|
|
317
|
+
const bodyById = new Map(bodyDocs.map((d) => [d.id, typeof d.body === 'string' ? d.body : '']));
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
header: 'Retrieved from prior sessions. Apply at decision points; do not echo verbatim.',
|
|
321
|
+
items: ranked.map(({ doc, score }) => buildScoredMemoryItem(doc, score, bodyById.get(doc.id) || '')),
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
routeLabel: 'GET /api/memories',
|
|
325
|
+
consoleUrl,
|
|
326
|
+
});
|
|
327
|
+
}
|