heyspex 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +14 -0
- package/README.md +21 -0
- package/bin/hs.mjs +1089 -104
- package/bin/spexcode.mjs +262 -0
- package/dev-factory/engine/worktree.mjs +740 -0
- package/dev-factory/lib/code-repo-root.mjs +67 -0
- package/dev-factory/lib/context-budget.mjs +84 -0
- package/dev-factory/lib/epic-detect.mjs +190 -0
- package/dev-factory/lib/fleet-queue-file.mjs +846 -0
- package/dev-factory/lib/fleet-queue.mjs +426 -0
- package/dev-factory/lib/fleet-state.mjs +14 -18
- package/dev-factory/lib/model-catalog.mjs +338 -0
- package/dev-factory/lib/seat-context.mjs +199 -0
- package/dev-factory/lib/spec-io.mjs +93 -0
- package/dev-factory/lib/sufficiency-score.mjs +8 -2
- package/dev-factory/lib/worktree-allocator.mjs +744 -0
- package/dev-factory/model-catalog.json +222 -0
- package/dist/cli/hs-check.mjs +1127 -103
- package/dist/cli/hs-claim.mjs +981 -63
- package/dist/cli/hs-connect.mjs +10059 -1729
- package/dist/cli/hs-defer.mjs +5507 -3072
- package/dist/cli/hs-dev.mjs +576 -0
- package/dist/cli/hs-env.mjs +416 -302
- package/dist/cli/hs-index-rebuild.mjs +7334 -0
- package/dist/cli/hs-index-reconcile.mjs +8138 -0
- package/dist/cli/hs-init.mjs +2840 -149
- package/dist/cli/hs-install.mjs +7214 -594
- package/dist/cli/hs-join.mjs +10640 -2421
- package/dist/cli/hs-vault-pull.mjs +8157 -6370
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +4029 -1555
- package/dist/cli/hs-workspace.mjs +2702 -538
- package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
- package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
- package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
- package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
- package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
- package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
- package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
- package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
- package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
- package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
- package/docs/copilot-plugin-install.md +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/docs/mcp-client-config.md +259 -6
- package/package.json +249 -124
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
- package/plugins/heyspex/.mcp.json +12 -0
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +80 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
- package/plugins/heyspex/hooks/hooks.json +418 -0
- package/plugins/heyspex/mcp.json +13 -0
- package/scripts/check-mcp-freshness.mjs +17 -9
- package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
- package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
- package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
- package/scripts/claude-hooks/_run.mjs +413 -0
- package/scripts/claude-hooks/_transcript-text.mjs +60 -0
- package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
- package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
- package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
- package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
- package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
- package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
- package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
- package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
- package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
- package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
- package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
- package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
- package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
- package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
- package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
- package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
- package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
- package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
- package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
- package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
- package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
- package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
- package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
- package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
- package/scripts/claude-hooks/log-session-activity.mjs +341 -0
- package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
- package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
- package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
- package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
- package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
- package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
- package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
- package/scripts/claude-hooks/research-method-guard.mjs +398 -0
- package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
- package/scripts/claude-hooks/session-end.mjs +451 -0
- package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
- package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
- package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
- package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
- package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
- package/scripts/cursor-hooks/manifest.mjs +281 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +288 -0
- package/scripts/hs-auto-resolve.mjs +240 -0
- package/scripts/hs-auto-source.mjs +398 -0
- package/scripts/hs-auto.mjs +134 -0
- package/scripts/hs-capture-cli.mjs +326 -0
- package/scripts/hs-classifier.mjs +136 -0
- package/scripts/hs-companion.mjs +88 -0
- package/scripts/hs-gateway.mjs +115 -0
- package/scripts/hs-mode.mjs +125 -0
- package/scripts/hs-next.mjs +441 -0
- package/scripts/hs-settings.mjs +338 -0
- package/scripts/hs-view.mjs +496 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +272 -0
- package/scripts/install-cursor-hooks.mjs +471 -0
- package/scripts/lib/agent-env-markers.mjs +38 -0
- package/scripts/lib/console-url-resolver.mjs +199 -61
- package/scripts/lib/customer-kit.mjs +1203 -0
- package/scripts/lib/dev-mode.mjs +249 -32
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-commit.mjs +59 -5
- package/scripts/lib/hs-source-identity.mjs +526 -0
- package/scripts/lib/hs-source-log.mjs +13 -22
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -307
- package/scripts/lib/hs-worktree-args.mjs +115 -0
- package/scripts/lib/shared-path-helpers.mjs +134 -0
- package/scripts/lib/standalone-doctor.mjs +540 -3
- package/scripts/lib/subagent-git-lock.mjs +109 -0
- package/scripts/lib/template-pin.mjs +34 -0
- package/scripts/lib/window-placement.mjs +109 -0
- package/scripts/provenance-lib.mjs +91 -0
- package/scripts/stamp-provenance-on-edit.mjs +222 -0
- package/scripts/wip-ledger-health.mjs +653 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
- package/spexharness/src/adapters/audit.mjs +197 -0
- package/spexharness/src/adapters/claude-code.mjs +449 -0
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/chat/tui/ansi.mjs +448 -0
- package/spexharness/src/chat/tui/palette.mjs +22 -0
- package/spexharness/src/chat/tui/select.mjs +208 -0
- package/spexharness/src/config-root.mjs +54 -0
- package/spexharness/src/context-budget.mjs +141 -0
- package/spexharness/src/dev-server-control.mjs +389 -0
- package/spexharness/src/doctrine/behaviour.mjs +371 -0
- package/spexharness/src/doctrine/corpus.mjs +733 -0
- package/spexharness/src/doctrine/inject.mjs +338 -0
- package/spexharness/src/doctrine/merge.mjs +184 -0
- package/spexharness/src/doctrine/sources.mjs +252 -0
- package/spexharness/src/doctrine/synthesize.mjs +401 -0
- package/spexharness/src/doctrine/vault-source.mjs +574 -0
- package/spexharness/src/doctrine/writer.mjs +176 -0
- package/spexharness/src/events.mjs +84 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
- package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
- package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
- package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
- package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
- package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
- package/spexharness/src/gates/git-identity-guard.mjs +61 -0
- package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
- package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
- package/spexharness/src/gates/git-stash-gate.mjs +159 -0
- package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
- package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
- package/spexharness/src/gates/inject-vault-context.mjs +239 -0
- package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
- package/spexharness/src/gates/plan-review-gate.mjs +233 -0
- package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
- package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
- package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
- package/spexharness/src/gates/settings-write-gate.mjs +104 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +196 -0
- package/spexharness/src/intent-directive.mjs +626 -0
- package/spexharness/src/profile/learn.mjs +551 -0
- package/spexharness/src/profile/proposals.mjs +357 -0
- package/spexharness/src/profile/vault-inclusion.mjs +386 -0
- package/spexharness/src/redact.mjs +151 -0
- package/spexharness/src/rule-table.mjs +849 -0
- package/spexharness/src/seat-context.mjs +122 -0
- package/spexharness/src/session-bootstrap.mjs +512 -0
- package/spexharness/src/session-record.mjs +815 -0
- package/spexharness/src/token-inventory.mjs +207 -0
- package/vendor/heyspex-mcp/account-switch.mjs +96 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
- package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
- package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
- package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
- package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
- package/vendor/heyspex-mcp/cli.mjs +207 -15524
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
- package/vendor/heyspex-mcp/config-seed.yaml +15 -0
- package/vendor/heyspex-mcp/config-settings.mjs +290 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
- package/vendor/heyspex-mcp/console-transport.mjs +172 -0
- package/vendor/heyspex-mcp/credentials.mjs +585 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
- package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
- package/vendor/heyspex-mcp/design-tools.mjs +4 -1
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
- package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
- package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
- package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
- package/vendor/heyspex-mcp/events-tools.mjs +207 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
- package/vendor/heyspex-mcp/http-transport.mjs +302 -0
- package/vendor/heyspex-mcp/list-tools.mjs +257 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
- package/vendor/heyspex-mcp/package-root.mjs +32 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
- package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
- package/vendor/heyspex-mcp/plan-push.mjs +908 -22
- package/vendor/heyspex-mcp/plan-review.mjs +124 -30
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
- package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
- package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
- package/vendor/heyspex-mcp/promote-research.mjs +666 -53
- package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
- package/vendor/heyspex-mcp/prompts.mjs +1460 -0
- package/vendor/heyspex-mcp/read-path.mjs +191 -15
- package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
- package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
- package/vendor/heyspex-mcp/register-spec.mjs +266 -54
- package/vendor/heyspex-mcp/request-context.mjs +83 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
- package/vendor/heyspex-mcp/research-tools.mjs +4 -4
- package/vendor/heyspex-mcp/response-budget.mjs +348 -0
- package/vendor/heyspex-mcp/review-focus.mjs +25 -1
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/search-specs.mjs +280 -0
- package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
- package/vendor/heyspex-mcp/session-owner.mjs +109 -0
- package/vendor/heyspex-mcp/session-tools.mjs +162 -0
- package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +304 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
- package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
- package/vendor/heyspex-mcp/vault-io.mjs +720 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +92 -0
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -0,0 +1,1460 @@
|
|
|
1
|
+
// MCP prompts (prompts/list, prompts/get): the PROMPTS catalog and every
|
|
2
|
+
// handle*Prompt renderer (/hs-next, /hs-capture, /hs-ground, /hs-push, /hs-ship,
|
|
3
|
+
// /hs-review, /hs-res, procedures, design, promote/split research). 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 { executeGetPattern as executeDesignGetPattern, executeListPatterns as executeDesignListPatterns, listProposals as listDesignProposals } from './design-tools.mjs';
|
|
7
|
+
import { PREFLIGHT_INTENTS, executeGetProcedure, executeListProcedures, executeRunPreflight } from './procedure-tools.mjs';
|
|
8
|
+
import { buildHsResPromptMarkdown, loadResearchFocus } from './research-focus.mjs';
|
|
9
|
+
import { resolveNextReviewPayload } from './next-review.mjs';
|
|
10
|
+
import { applyOnDeckHttpAugments, attachConsoleStatusToOnDeck, injectCloudWipState, injectFrozenMilestoneOrder, nextReviewDeps, resolveNextOnDeckPayload } from './on-deck-brief.mjs';
|
|
11
|
+
import { buildPushWorkflowMarkdown, buildShipWorkflowMarkdown } from './plan-push.mjs';
|
|
12
|
+
import { detectAgentIdentity } from './mcp-config.mjs';
|
|
13
|
+
import { capturePinWarningBanner, createCaptureSessionOnConsole, drainCaptureOutbox, getCaptureSessionOnConsole, isCaptureSpecId, listPendingCaptureSessions, normalizeCaptureBuffer, patchCaptureSessionOnConsole, writeCaptureFocusVendor } from './capture-tools.mjs';
|
|
14
|
+
import { buildHsCapturePromptMarkdown, executePinCapture, extractCaptureCoreFromBody, extractCaptureNotesFromBody, findCaptureSpecFile } from './capture-focus.mjs';
|
|
15
|
+
import { outboxCount } from './capture-outbox.mjs';
|
|
16
|
+
import { findSpecFile, loadSpec, parseFrontmatter } from './vault-io.mjs';
|
|
17
|
+
import { injectNeonFocusPin } from './focus-tools.mjs';
|
|
18
|
+
|
|
19
|
+
// MCP prompts — surfaced in Cursor/Claude as slash commands (e.g. /hs-next)
|
|
20
|
+
export const PROMPTS = [
|
|
21
|
+
{
|
|
22
|
+
name: 'hs-push',
|
|
23
|
+
description:
|
|
24
|
+
'Push an epic implementation plan into the HeySpex vault (write specs, validate IDs, linkage checklist).',
|
|
25
|
+
arguments: [
|
|
26
|
+
{
|
|
27
|
+
name: 'epic_name',
|
|
28
|
+
description: 'Working title for the epic (optional if plan is only in chat)',
|
|
29
|
+
required: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'plan_hint',
|
|
33
|
+
description: 'One-line summary of what to file',
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'hs-push-next',
|
|
40
|
+
description:
|
|
41
|
+
'Full workflow: push epic plan to HeySpex AND pin on deck (focus.yaml, NEXT STEP, in_progress).',
|
|
42
|
+
arguments: [
|
|
43
|
+
{
|
|
44
|
+
name: 'epic_name',
|
|
45
|
+
description: 'Working title for the epic',
|
|
46
|
+
required: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'implementation_prompt',
|
|
50
|
+
description: 'Ready-to-paste prompt for the NEXT STEP block',
|
|
51
|
+
required: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'focus_reason',
|
|
55
|
+
description: 'Why this epic is on deck',
|
|
56
|
+
required: false,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'hs-capture',
|
|
62
|
+
description:
|
|
63
|
+
'Capture-only session: load or auto-create the capture on deck, then append user words via heyspex_update_capture. Works from any MCP agent without console pre-setup, or after console + New → Capture.',
|
|
64
|
+
arguments: [
|
|
65
|
+
{
|
|
66
|
+
name: 'spec_id',
|
|
67
|
+
description: 'Optional HS-CAP-* id (default: .heyspex/capture-focus.yaml)',
|
|
68
|
+
required: false,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'hs-res',
|
|
74
|
+
description:
|
|
75
|
+
'Research capture from a doc-toolbar Promote pin. Loads .heyspex/research-focus.yaml, confirms scope with the user, runs the deep dive, and finalizes via heyspex_complete_research (writes HS-RES-*, updates idea promoted_to, clears pin).',
|
|
76
|
+
arguments: [],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'hs-next',
|
|
80
|
+
description:
|
|
81
|
+
'Next on-deck execution prompt from the HeySpex roadmap. Use `/hs-next` for focus.yaml default, or `/hs-next HS-EPIC-XXXX` for a specific epic.',
|
|
82
|
+
arguments: [
|
|
83
|
+
{
|
|
84
|
+
name: 'spec_id',
|
|
85
|
+
description: 'Specific epic/task ID to fetch (e.g., "HS-EPIC-6RKN"). If omitted, uses focus.yaml or manifest order.',
|
|
86
|
+
required: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'roadmap_id',
|
|
90
|
+
description: "Roadmap to query (default: env HEYSPEX_ROADMAP_ID or 'HS-ROADMAP-PRODUCT')",
|
|
91
|
+
required: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'include_horizon',
|
|
95
|
+
description: 'Include horizon backlog when no scheduled open tasks remain',
|
|
96
|
+
required: false,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'ignore_focus',
|
|
100
|
+
description: 'Skip .heyspex/focus.yaml override and use manifest traversal',
|
|
101
|
+
required: false,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'hs-review',
|
|
107
|
+
description:
|
|
108
|
+
'Next review-ready spec from the HeySpex review queue. Use `/hs-review` for `.heyspex/review-focus.yaml` default (or oldest in_review with HS-REV companion), or `/hs-review HS-TASK-XXXX` for a specific spec. Pastes parent body + HS-REV doc + four-axis rubric so a reviewing agent can walk acceptance.',
|
|
109
|
+
arguments: [
|
|
110
|
+
{
|
|
111
|
+
name: 'spec_id',
|
|
112
|
+
description:
|
|
113
|
+
'Specific spec id to fetch review context for (e.g., "HS-TASK-RVDC"). If omitted, uses .heyspex/review-focus.yaml or oldest in_review.',
|
|
114
|
+
required: false,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'roadmap_id',
|
|
118
|
+
description: "Roadmap to query (default: env HEYSPEX_ROADMAP_ID or 'HS-ROADMAP-PRODUCT')",
|
|
119
|
+
required: false,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'ignore_focus',
|
|
123
|
+
description: 'Skip .heyspex/review-focus.yaml override and use manifest/folder-scan traversal',
|
|
124
|
+
required: false,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'hs-backfill',
|
|
130
|
+
description:
|
|
131
|
+
'Retroactively backfill completed work into the vault (Review column). Creates epic + tasks with status: in_review and generates review doc.',
|
|
132
|
+
arguments: [
|
|
133
|
+
{
|
|
134
|
+
name: 'epic_name',
|
|
135
|
+
description: 'Title of the epic (what was accomplished)',
|
|
136
|
+
required: true,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'summary',
|
|
140
|
+
description: 'Brief summary of what was done',
|
|
141
|
+
required: false,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'hs-ground',
|
|
147
|
+
description:
|
|
148
|
+
'Holistic scan, north-star alignment, architecture placement, plan stress-test, then enhance and optimize before push.',
|
|
149
|
+
arguments: [
|
|
150
|
+
{
|
|
151
|
+
name: 'topic',
|
|
152
|
+
description: 'Short title of the feature or request',
|
|
153
|
+
required: false,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'summary',
|
|
157
|
+
description: 'Longer description or draft plan from chat',
|
|
158
|
+
required: false,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'save_research',
|
|
162
|
+
description: 'If true, offer heyspex_save_research when discovery is large',
|
|
163
|
+
required: false,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'hs-design-list',
|
|
169
|
+
description:
|
|
170
|
+
'Browse design system patterns from spex-console/public/design-system/. Compact summaries — use /hs-design-get for full JSON. Filter by tag, layer, or query substring.',
|
|
171
|
+
arguments: [
|
|
172
|
+
{ name: 'tag', description: 'Filter to patterns whose hsTags contains this tag (e.g. "on-deck")', required: false },
|
|
173
|
+
{ name: 'layer', description: 'Filter by hsLayer (primitive|semantic|component|pattern)', required: false },
|
|
174
|
+
{ name: 'query', description: 'Substring match against name/title/description', required: false },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'hs-design-get',
|
|
179
|
+
description:
|
|
180
|
+
'Fetch one full design-system pattern (registry-item.json) by slug. Use this instead of grepping spex-console/DESIGN.md.',
|
|
181
|
+
arguments: [
|
|
182
|
+
{ name: 'name', description: 'Pattern slug (e.g. "on-deck-callout-sky")', required: true },
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'hs-design-propose',
|
|
187
|
+
description:
|
|
188
|
+
'File a new design pattern as a draft proposal at specs/design-proposals/<name>.json (humans review and promote).',
|
|
189
|
+
arguments: [
|
|
190
|
+
{ name: 'name', description: 'Kebab-case slug for the new pattern', required: false },
|
|
191
|
+
{ name: 'title', description: 'Short human title for the pattern', required: false },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'hs-procedure-list',
|
|
196
|
+
description:
|
|
197
|
+
'Browse HeySpex procedures from specs/protocols/.catalog.json. Compact summaries — use /hs-procedure for the full body. Filter by tag, trigger intent, or query substring.',
|
|
198
|
+
arguments: [
|
|
199
|
+
{ name: 'tag', description: 'Filter to procedures whose tags include this value (e.g. "vault", "mcp")', required: false },
|
|
200
|
+
{ name: 'trigger', description: 'Filter by trigger intent (e.g. "file_milestone", "destructive_action")', required: false },
|
|
201
|
+
{ name: 'query', description: 'Substring match against name/title/summary', required: false },
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'hs-procedure',
|
|
206
|
+
description:
|
|
207
|
+
'Fetch one full procedure body from specs/protocols/<name>.md by slug. Use this instead of grepping example specs for structural conventions.',
|
|
208
|
+
arguments: [
|
|
209
|
+
{ name: 'name', description: 'Procedure slug (e.g. "spec-hierarchy", "memory-protocol")', required: true },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'hs-preflight',
|
|
214
|
+
description:
|
|
215
|
+
'Pre-flight checklist for a named agent intent — returns the procedures to consult before acting, each annotated with severity (required/recommended) and a short why. Then fetch each body via /hs-procedure.',
|
|
216
|
+
arguments: [
|
|
217
|
+
{ name: 'intent', description: 'Intent slug: file_milestone | file_epic | file_task | file_iteration | file_diagnose | file_research | file_memory_lesson | file_memory_decision | file_memory_reference | file_memory_ui_pattern | file_review | file_capture | destructive_action | protocol_edit | memory_creation | close_session | claim_spec | execute_spec | exit_plan_mode | recommend_location | focus_pivot | server_impacting_action', required: true },
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'hs-promote-research',
|
|
222
|
+
description:
|
|
223
|
+
'Promote a research doc (HS-RES-*) to a milestone with AI-assisted epic stubs ([[HS-TASK-QQHP]]). Two-phase: first call without accepted_stubs returns proposed stubs for human review; second call with accepted_stubs executes the transactional writes (research.enhances/spawned_epics/status + milestone.epics/informed_by + new epic files).',
|
|
224
|
+
arguments: [
|
|
225
|
+
{ name: 'research_id', description: 'HS-RES-* id of the research to promote', required: true },
|
|
226
|
+
{ name: 'target_milestone', description: 'HS-MIL-* id, basename, or "NEW"', required: true },
|
|
227
|
+
{ name: 'intent', description: 'spawn | augment | both', required: true },
|
|
228
|
+
{ name: 'new_milestone_name', description: 'Required when target_milestone === "NEW"', required: false },
|
|
229
|
+
{ name: 'spawn_epic_count', description: 'Hint for AI proposer (default 2, clamped to [1,6])', required: false },
|
|
230
|
+
{ name: 'augment_epic_ids', description: 'HS-EPIC-* ids to augment (required when intent includes "augment")', required: false },
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'hs-split-research',
|
|
235
|
+
description:
|
|
236
|
+
'Split a research doc (HS-RES-*) into N (≥2) child research docs before promotion ([[HS-TASK-GTH9]]). Original becomes a closed audit archive with `split_into:` linkage; each child carries `split_from:` + a caller-prepared body_slice + a subset of original.sources_captures. Capture files\' `informs_research:` arrays update bidirectionally.',
|
|
237
|
+
arguments: [
|
|
238
|
+
{ name: 'research_id', description: 'HS-RES-* id of the research to split', required: true },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: 'hs-promote-capture',
|
|
243
|
+
description:
|
|
244
|
+
'Promote 1+ captures (HS-CAP-*) into a research doc (new or existing) with AI-assisted outline ([[HS-TASK-WQCE]]). Two-phase for NEW research: first call returns a proposed { name, outline, tags, enhances_suggestion } for human review; second call with accepted_outline executes the transactional writes (research file create, capture.informs_research + first-time promoted_to). Existing research is single-phase append. Closed research is allowed with a TARGET_RESEARCH_CLOSED warning.',
|
|
245
|
+
arguments: [
|
|
246
|
+
{ name: 'capture_ids', description: 'Comma-separated HS-CAP-* ids (1+)', required: true },
|
|
247
|
+
{ name: 'target_research', description: 'HS-RES-* id or "NEW"', required: true },
|
|
248
|
+
{ name: 'new_research_name', description: 'Required when target_research === "NEW"', required: false },
|
|
249
|
+
{ name: 'enhances', description: 'Optional HS-MIL-* id/basename to set on NEW research', required: false },
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* /hs-promote-research prompt — returns instruction text telling the agent to
|
|
256
|
+
* invoke the heyspex_promote_research tool with the right args. The tool is
|
|
257
|
+
* MCP-callable directly; this prompt is the slash-command bridge.
|
|
258
|
+
*/
|
|
259
|
+
export function handlePromoteResearchPrompt(args = {}) {
|
|
260
|
+
const researchId = args.research_id ? String(args.research_id).trim() : '';
|
|
261
|
+
const target = args.target_milestone ? String(args.target_milestone).trim() : '';
|
|
262
|
+
const intent = args.intent ? String(args.intent).trim() : '';
|
|
263
|
+
|
|
264
|
+
const lines = [
|
|
265
|
+
'# /hs-promote-research — research → milestone promotion',
|
|
266
|
+
'',
|
|
267
|
+
'**Default policy:** `target_milestone: "NEW"` (omit the field to default). Only attach to an existing milestone when it is `status: open` and clearly extends the same thesis. Spawning into `in_progress` milestones is blocked unless the owner approves `attach_to_in_progress_milestone: true` + `attach_override_reason`.',
|
|
268
|
+
'',
|
|
269
|
+
'Invoke `mcp__heyspex__heyspex_promote_research` with the args below.',
|
|
270
|
+
'',
|
|
271
|
+
'**Two-phase flow.** Call once without `accepted_stubs` to get AI-proposed epic stubs back for human review. Then call again with `accepted_stubs: [...]` (agent-edited subset) to execute the transactional writes.',
|
|
272
|
+
'',
|
|
273
|
+
'## Phase 1 — proposal',
|
|
274
|
+
'',
|
|
275
|
+
'```',
|
|
276
|
+
'heyspex_promote_research({',
|
|
277
|
+
` research_id: "${researchId || 'HS-RES-XXXX'}",`,
|
|
278
|
+
` target_milestone: "${target || 'NEW'}",`,
|
|
279
|
+
` new_milestone_name: "${target === 'NEW' || !target ? 'Milestone title' : '(omit when not NEW)'}",`,
|
|
280
|
+
` intent: "${intent || 'spawn | augment | both'}",`,
|
|
281
|
+
intent === 'augment'
|
|
282
|
+
? ' augment_epic_ids: ["HS-EPIC-…"]'
|
|
283
|
+
: ' // spawn_epic_count: 2 (optional hint for AI)',
|
|
284
|
+
'})',
|
|
285
|
+
'```',
|
|
286
|
+
'',
|
|
287
|
+
'Show the user `proposed_stubs[]`. Let them edit/remove/approve.',
|
|
288
|
+
'',
|
|
289
|
+
'## Phase 2 — execute (after human confirms)',
|
|
290
|
+
'',
|
|
291
|
+
'```',
|
|
292
|
+
'heyspex_promote_research({',
|
|
293
|
+
` research_id: "${researchId || 'HS-RES-XXXX'}",`,
|
|
294
|
+
` target_milestone: "${target || 'HS-MIL-YYYY | NEW'}",`,
|
|
295
|
+
` intent: "${intent || 'spawn'}",`,
|
|
296
|
+
' accepted_stubs: [/* confirmed stub objects from phase 1 */]',
|
|
297
|
+
'})',
|
|
298
|
+
'```',
|
|
299
|
+
'',
|
|
300
|
+
'On success: research gets `enhances:` + `spawned_epics:` + `enhances_epics:` set and **stays `status: open`** (research is evergreen — [[research-is-evergreen]]); milestone\'s `epics:` + `informed_by:` extended; new epic files created with `parent` / `milestone` / `derived_from`. All writes roll back if any step fails.',
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
description: 'hs-promote-research — research → milestone promotion',
|
|
305
|
+
messages: [
|
|
306
|
+
{
|
|
307
|
+
role: 'user',
|
|
308
|
+
content: { type: 'text', text: lines.join('\n') },
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* /hs-split-research prompt — returns instruction text telling the agent to
|
|
316
|
+
* invoke the heyspex_split_research tool. Single-phase (no AI), caller
|
|
317
|
+
* partitions body_slice + sources_captures across child entries.
|
|
318
|
+
*/
|
|
319
|
+
export function handleSplitResearchPrompt(args = {}) {
|
|
320
|
+
const researchId = args.research_id ? String(args.research_id).trim() : '';
|
|
321
|
+
|
|
322
|
+
const lines = [
|
|
323
|
+
'# /hs-split-research — split research that spans multiple milestones',
|
|
324
|
+
'',
|
|
325
|
+
'Invoke `mcp__heyspex__heyspex_split_research` after the human has prepared the body-slice + sources_captures partition for each child.',
|
|
326
|
+
'',
|
|
327
|
+
'The original research becomes a closed audit archive with `split_into:` linkage; each child carries `split_from:` plus its own `sources_captures` subset and body. Children land as `status: open` — milestone binding happens later via `/hs-promote-research`.',
|
|
328
|
+
'',
|
|
329
|
+
'## Call shape',
|
|
330
|
+
'',
|
|
331
|
+
'```',
|
|
332
|
+
'heyspex_split_research({',
|
|
333
|
+
` research_id: "${researchId || 'HS-RES-XXXX'}",`,
|
|
334
|
+
' splits: [',
|
|
335
|
+
' {',
|
|
336
|
+
' name: "Child research title #1",',
|
|
337
|
+
' target_milestone: "HS-MIL-YYYY", // or "NEW"',
|
|
338
|
+
' // new_milestone_name: "…", // required when target_milestone === "NEW"',
|
|
339
|
+
' body_slice: "…markdown for this child\'s body…",',
|
|
340
|
+
' sources_captures: ["HS-CAP-AAAA"] // subset of original.sources_captures',
|
|
341
|
+
' },',
|
|
342
|
+
' {',
|
|
343
|
+
' name: "Child research title #2",',
|
|
344
|
+
' target_milestone: "HS-MIL-ZZZZ",',
|
|
345
|
+
' body_slice: "…",',
|
|
346
|
+
' sources_captures: ["HS-CAP-BBBB", "HS-CAP-CCCC"]',
|
|
347
|
+
' }',
|
|
348
|
+
' // … ≥2 entries total',
|
|
349
|
+
' ]',
|
|
350
|
+
'})',
|
|
351
|
+
'```',
|
|
352
|
+
'',
|
|
353
|
+
'## Rules',
|
|
354
|
+
'',
|
|
355
|
+
'- Refuses to split if the original already has `split_into:` set (no double-split). The archive stays `status: open` — research is evergreen ([[research-is-evergreen]]).',
|
|
356
|
+
'- Each `sources_captures` entry must be a subset of the original\'s `sources_captures:`. Captures may appear in multiple children (Capture → Research is M:N).',
|
|
357
|
+
'- Caller prepares `body_slice` — no AI auto-partition in v1.',
|
|
358
|
+
'- All writes commit transactionally; any single-step failure rolls back the rest.',
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
description: 'hs-split-research — split research across milestones',
|
|
363
|
+
messages: [
|
|
364
|
+
{
|
|
365
|
+
role: 'user',
|
|
366
|
+
content: { type: 'text', text: lines.join('\n') },
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* /hs-promote-capture prompt — returns instruction text telling the agent to
|
|
374
|
+
* invoke the heyspex_promote_capture tool. Two-phase for NEW research,
|
|
375
|
+
* single-phase append for existing research.
|
|
376
|
+
*/
|
|
377
|
+
export function handlePromoteCapturePrompt(args = {}) {
|
|
378
|
+
const captureIdsRaw = args.capture_ids ? String(args.capture_ids).trim() : '';
|
|
379
|
+
const captureIds = captureIdsRaw
|
|
380
|
+
? captureIdsRaw
|
|
381
|
+
.split(/[,\s]+/)
|
|
382
|
+
.filter((v) => v.length > 0)
|
|
383
|
+
: [];
|
|
384
|
+
const target = args.target_research ? String(args.target_research).trim() : '';
|
|
385
|
+
const newName = args.new_research_name ? String(args.new_research_name).trim() : '';
|
|
386
|
+
const enhances = args.enhances ? String(args.enhances).trim() : '';
|
|
387
|
+
|
|
388
|
+
const captureIdsRendered =
|
|
389
|
+
captureIds.length > 0
|
|
390
|
+
? `[${captureIds.map((id) => `"${id}"`).join(', ')}]`
|
|
391
|
+
: '["HS-CAP-AAAA", "HS-CAP-BBBB"]';
|
|
392
|
+
|
|
393
|
+
const lines = [
|
|
394
|
+
'# /hs-promote-capture — capture(s) → research promotion',
|
|
395
|
+
'',
|
|
396
|
+
'Invoke `mcp__heyspex__heyspex_promote_capture` with the args below.',
|
|
397
|
+
'',
|
|
398
|
+
'**Flow depends on target_research:**',
|
|
399
|
+
'- `target_research: "NEW"` → **two-phase**: first call without `accepted_outline` returns a proposed `{ name, outline, tags, enhances_suggestion }` for human review. Second call with `accepted_outline` populated executes the writes.',
|
|
400
|
+
'- `target_research: "HS-RES-XXXX"` (existing) → **single-phase**: appends captures into the existing research\'s `sources_captures:`, sets each capture\'s `informs_research:`, and sets `promoted_to:` on captures that had none. Idempotent on re-runs.',
|
|
401
|
+
'',
|
|
402
|
+
'## Phase 1 — proposal (NEW research only)',
|
|
403
|
+
'',
|
|
404
|
+
'```',
|
|
405
|
+
'heyspex_promote_capture({',
|
|
406
|
+
` capture_ids: ${captureIdsRendered},`,
|
|
407
|
+
` target_research: "${target || 'NEW'}",`,
|
|
408
|
+
` new_research_name: "${newName || 'Working title for the new research doc'}",`,
|
|
409
|
+
enhances ? ` enhances: "${enhances}",` : ' // enhances: "HS-MIL-…", // optional',
|
|
410
|
+
'})',
|
|
411
|
+
'```',
|
|
412
|
+
'',
|
|
413
|
+
'Show the user `proposed_outline`. Let them edit name/outline/tags/enhances_suggestion.',
|
|
414
|
+
'',
|
|
415
|
+
'## Phase 2 — execute (after human confirms, or single-phase for existing research)',
|
|
416
|
+
'',
|
|
417
|
+
'```',
|
|
418
|
+
'heyspex_promote_capture({',
|
|
419
|
+
` capture_ids: ${captureIdsRendered},`,
|
|
420
|
+
` target_research: "${target || 'NEW or HS-RES-XXXX'}",`,
|
|
421
|
+
target === 'NEW' || !target ? ` new_research_name: "${newName || 'Final research title'}",` : '',
|
|
422
|
+
target === 'NEW' || !target
|
|
423
|
+
? ' accepted_outline: { name, outline, tags, enhances_suggestion? }'
|
|
424
|
+
: ' // existing target — no accepted_outline needed',
|
|
425
|
+
'})',
|
|
426
|
+
]
|
|
427
|
+
.filter(Boolean)
|
|
428
|
+
.concat([
|
|
429
|
+
'```',
|
|
430
|
+
'',
|
|
431
|
+
'On success: capture.informs_research extended for every input capture; capture.promoted_to set on first promotion only; research.sources_captures extended (research file created if NEW). Closed-research target returns `warning: "TARGET_RESEARCH_CLOSED"` but still appends. All writes commit transactionally; any failure rolls back.',
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
description: 'hs-promote-capture — capture(s) → research promotion',
|
|
436
|
+
messages: [
|
|
437
|
+
{
|
|
438
|
+
role: 'user',
|
|
439
|
+
content: { type: 'text', text: lines.join('\n') },
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function handlePromptsList() {
|
|
446
|
+
return { prompts: PROMPTS };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function handleDesignListPrompt(vaultRoot, args = {}) {
|
|
450
|
+
const result = executeDesignListPatterns(vaultRoot, args);
|
|
451
|
+
if (result.error) {
|
|
452
|
+
return {
|
|
453
|
+
description: `hs-design-list — ${result.error}`,
|
|
454
|
+
messages: [
|
|
455
|
+
{
|
|
456
|
+
role: 'user',
|
|
457
|
+
content: {
|
|
458
|
+
type: 'text',
|
|
459
|
+
text: `Could not list design patterns:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const filterSummary = [
|
|
467
|
+
args.tag ? `tag=${args.tag}` : null,
|
|
468
|
+
args.layer ? `layer=${args.layer}` : null,
|
|
469
|
+
args.query ? `query="${args.query}"` : null,
|
|
470
|
+
].filter(Boolean).join(', ') || 'no filters';
|
|
471
|
+
|
|
472
|
+
const lines = [
|
|
473
|
+
`# HeySpex design patterns — ${result.count}/${result.total}`,
|
|
474
|
+
'',
|
|
475
|
+
`_Filters: ${filterSummary}_`,
|
|
476
|
+
'',
|
|
477
|
+
];
|
|
478
|
+
if (result.patterns.length === 0) {
|
|
479
|
+
lines.push('_No matching patterns. Drop a filter or call `heyspex_design_list_patterns` with different args._');
|
|
480
|
+
} else {
|
|
481
|
+
for (const p of result.patterns) {
|
|
482
|
+
const tagList = p.tags.length > 0 ? ` · tags: ${p.tags.join(', ')}` : '';
|
|
483
|
+
lines.push(`- **\`${p.name}\`** — ${p.title ?? '(untitled)'} _(layer: ${p.layer ?? '—'})${tagList}_`);
|
|
484
|
+
if (p.summary) lines.push(` ${p.summary}`);
|
|
485
|
+
}
|
|
486
|
+
lines.push('');
|
|
487
|
+
lines.push('---');
|
|
488
|
+
lines.push('');
|
|
489
|
+
lines.push('Fetch a full registry item with `heyspex_design_get_pattern({ name: "<slug>" })`.');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
description: `hs-design-list — ${result.count} pattern(s)`,
|
|
494
|
+
messages: [
|
|
495
|
+
{ role: 'user', content: { type: 'text', text: lines.join('\n') } },
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function handleDesignGetPrompt(vaultRoot, args = {}) {
|
|
501
|
+
const name = String(args.name ?? '').trim();
|
|
502
|
+
if (!name) {
|
|
503
|
+
return {
|
|
504
|
+
description: 'hs-design-get — missing pattern name',
|
|
505
|
+
messages: [
|
|
506
|
+
{
|
|
507
|
+
role: 'user',
|
|
508
|
+
content: {
|
|
509
|
+
type: 'text',
|
|
510
|
+
text: 'Call `/hs-design-get <pattern-slug>` (e.g. `/hs-design-get on-deck-callout-sky`). Use `/hs-design-list` first to discover slugs.',
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
const result = executeDesignGetPattern(vaultRoot, { name });
|
|
517
|
+
if (result.error) {
|
|
518
|
+
return {
|
|
519
|
+
description: `hs-design-get — ${result.error}`,
|
|
520
|
+
messages: [
|
|
521
|
+
{
|
|
522
|
+
role: 'user',
|
|
523
|
+
content: {
|
|
524
|
+
type: 'text',
|
|
525
|
+
text: `Could not fetch \`${name}\`:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n\nRun \`/hs-design-list\` to see available patterns.`,
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const text = [
|
|
533
|
+
`# Design pattern — \`${result.pattern.name}\``,
|
|
534
|
+
'',
|
|
535
|
+
`_Source: ${result.source}_`,
|
|
536
|
+
'',
|
|
537
|
+
'```json',
|
|
538
|
+
JSON.stringify(result.pattern, null, 2),
|
|
539
|
+
'```',
|
|
540
|
+
'',
|
|
541
|
+
`For the prose anchor in \`spex-console/DESIGN.md\`, call \`heyspex_design_get_doc({ name: "${result.pattern.name}" })\`.`,
|
|
542
|
+
].join('\n');
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
description: `hs-design-get — ${result.pattern.name}`,
|
|
546
|
+
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export function handleDesignProposePrompt(vaultRoot, args = {}) {
|
|
551
|
+
const existingProposals = listDesignProposals(vaultRoot);
|
|
552
|
+
const slugHint = String(args.name ?? '<kebab-case-slug>').trim();
|
|
553
|
+
const titleHint = String(args.title ?? '<short human title>').trim();
|
|
554
|
+
|
|
555
|
+
const text = [
|
|
556
|
+
'# HeySpex hs-design-propose',
|
|
557
|
+
'',
|
|
558
|
+
'Draft a new design pattern proposal. The MCP tool writes the JSON to `specs/design-proposals/<name>.json`; a human reviews and promotes it into `spex-console/public/design-system/r/` later.',
|
|
559
|
+
'',
|
|
560
|
+
'**Execute:** Call MCP tool `heyspex_design_propose_pattern` with JSON:',
|
|
561
|
+
'',
|
|
562
|
+
'```json',
|
|
563
|
+
JSON.stringify(
|
|
564
|
+
{
|
|
565
|
+
name: slugHint,
|
|
566
|
+
title: titleHint,
|
|
567
|
+
description: '<one-paragraph: purpose, anatomy, when to use>',
|
|
568
|
+
hsLayer: '<primitive|semantic|component|pattern>',
|
|
569
|
+
hsTags: ['<tag1>', '<tag2>'],
|
|
570
|
+
hsDecisionRefs: ['<memory-decision-basename>'],
|
|
571
|
+
cssVars: {
|
|
572
|
+
dark: { '--example-var': '#000000' },
|
|
573
|
+
light: { '--example-var': '#ffffff' },
|
|
574
|
+
},
|
|
575
|
+
hsCodePreview: '<optional standalone HTML snippet>',
|
|
576
|
+
},
|
|
577
|
+
null,
|
|
578
|
+
2,
|
|
579
|
+
),
|
|
580
|
+
'```',
|
|
581
|
+
'',
|
|
582
|
+
'**Conventions:**',
|
|
583
|
+
'- `name` must be kebab-case and unique against `spex-console/public/design-system/r/*.json`.',
|
|
584
|
+
'- `hsLayer` is required — pick the most specific tier the pattern fits.',
|
|
585
|
+
'- After filing, edit `specs/design-proposals/<name>.json` directly to refine before promotion.',
|
|
586
|
+
'',
|
|
587
|
+
existingProposals.length > 0
|
|
588
|
+
? `_Existing proposals (${existingProposals.length}): ${existingProposals.map((s) => `\`${s}\``).join(', ')}_`
|
|
589
|
+
: '_No existing proposals yet._',
|
|
590
|
+
].join('\n');
|
|
591
|
+
|
|
592
|
+
return {
|
|
593
|
+
description: 'hs-design-propose — draft a new pattern',
|
|
594
|
+
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export function handleProcedureListPrompt(vaultRoot, args = {}) {
|
|
599
|
+
const result = executeListProcedures(vaultRoot, args);
|
|
600
|
+
if (result.error) {
|
|
601
|
+
return {
|
|
602
|
+
description: `hs-procedure-list — ${result.error}`,
|
|
603
|
+
messages: [
|
|
604
|
+
{
|
|
605
|
+
role: 'user',
|
|
606
|
+
content: {
|
|
607
|
+
type: 'text',
|
|
608
|
+
text: `Could not list procedures:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const filterSummary = [
|
|
616
|
+
args.tag ? `tag=${args.tag}` : null,
|
|
617
|
+
args.trigger ? `trigger=${args.trigger}` : null,
|
|
618
|
+
args.query ? `query="${args.query}"` : null,
|
|
619
|
+
].filter(Boolean).join(', ') || 'no filters';
|
|
620
|
+
|
|
621
|
+
const lines = [
|
|
622
|
+
`# HeySpex procedures — ${result.procedures.length} on this page${result.next_cursor ? ' (more available — pass `after`)' : ''}`,
|
|
623
|
+
'',
|
|
624
|
+
`_Filters: ${filterSummary}_`,
|
|
625
|
+
'',
|
|
626
|
+
];
|
|
627
|
+
if (result.procedures.length === 0) {
|
|
628
|
+
lines.push('_No matching procedures. Drop a filter or call `heyspex_list_procedures` with different args._');
|
|
629
|
+
} else {
|
|
630
|
+
for (const p of result.procedures) {
|
|
631
|
+
const tagList = p.tags.length > 0 ? ` · tags: ${p.tags.join(', ')}` : '';
|
|
632
|
+
const triggerList = p.triggers.length > 0 ? ` · triggers: ${p.triggers.join(', ')}` : '';
|
|
633
|
+
lines.push(`- **\`${p.name}\`** — ${p.title ?? '(untitled)'}${tagList}${triggerList}`);
|
|
634
|
+
if (p.summary) lines.push(` ${p.summary}`);
|
|
635
|
+
}
|
|
636
|
+
lines.push('');
|
|
637
|
+
lines.push('---');
|
|
638
|
+
lines.push('');
|
|
639
|
+
lines.push('Fetch a full procedure body with `/hs-procedure <name>` (e.g. `/hs-procedure spec-hierarchy`).');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
description: `hs-procedure-list — ${result.procedures.length} procedure(s)${result.next_cursor ? ' (paged)' : ''}`,
|
|
644
|
+
messages: [
|
|
645
|
+
{ role: 'user', content: { type: 'text', text: lines.join('\n') } },
|
|
646
|
+
],
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export function handleProcedurePrompt(vaultRoot, args = {}) {
|
|
651
|
+
const name = String(args.name ?? '').trim();
|
|
652
|
+
if (!name) {
|
|
653
|
+
return {
|
|
654
|
+
description: 'hs-procedure — missing procedure name',
|
|
655
|
+
messages: [
|
|
656
|
+
{
|
|
657
|
+
role: 'user',
|
|
658
|
+
content: {
|
|
659
|
+
type: 'text',
|
|
660
|
+
text: 'Call `/hs-procedure <name>` (e.g. `/hs-procedure spec-hierarchy`). Use `/hs-procedure-list` first to discover slugs.',
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const result = executeGetProcedure(vaultRoot, { name });
|
|
667
|
+
if (result.error) {
|
|
668
|
+
return {
|
|
669
|
+
description: `hs-procedure — ${result.error}`,
|
|
670
|
+
messages: [
|
|
671
|
+
{
|
|
672
|
+
role: 'user',
|
|
673
|
+
content: {
|
|
674
|
+
type: 'text',
|
|
675
|
+
text: `Could not fetch \`${name}\`:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n\nRun \`/hs-procedure-list\` to see available procedures.`,
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const { procedure, content, source } = result;
|
|
683
|
+
const tagList = procedure.tags.length > 0 ? procedure.tags.join(', ') : '—';
|
|
684
|
+
const triggerList = procedure.triggers.length > 0 ? procedure.triggers.join(', ') : '—';
|
|
685
|
+
|
|
686
|
+
const text = [
|
|
687
|
+
`# Procedure — \`${procedure.name}\``,
|
|
688
|
+
'',
|
|
689
|
+
`**${procedure.title ?? '(untitled)'}** · id \`${procedure.id ?? '—'}\` · status \`${procedure.status ?? '—'}\``,
|
|
690
|
+
`tags: ${tagList} · triggers: ${triggerList}`,
|
|
691
|
+
'',
|
|
692
|
+
`_Source: \`${source}\`_`,
|
|
693
|
+
'',
|
|
694
|
+
'---',
|
|
695
|
+
'',
|
|
696
|
+
content,
|
|
697
|
+
].join('\n');
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
description: `hs-procedure — ${procedure.name}`,
|
|
701
|
+
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function handlePreflightPrompt(vaultRoot, args = {}) {
|
|
706
|
+
const intent = String(args.intent ?? '').trim();
|
|
707
|
+
if (!intent) {
|
|
708
|
+
return {
|
|
709
|
+
description: 'hs-preflight — missing intent',
|
|
710
|
+
messages: [
|
|
711
|
+
{
|
|
712
|
+
role: 'user',
|
|
713
|
+
content: {
|
|
714
|
+
type: 'text',
|
|
715
|
+
text: `Call \`/hs-preflight <intent>\`. Valid intents: ${PREFLIGHT_INTENTS.join(', ')}.`,
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const result = executeRunPreflight(vaultRoot, { intent });
|
|
722
|
+
if (result.error) {
|
|
723
|
+
return {
|
|
724
|
+
description: `hs-preflight — ${result.error}`,
|
|
725
|
+
messages: [
|
|
726
|
+
{
|
|
727
|
+
role: 'user',
|
|
728
|
+
content: {
|
|
729
|
+
type: 'text',
|
|
730
|
+
text: `Pre-flight failed:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const lines = [
|
|
738
|
+
`# Pre-flight — \`${result.intent}\``,
|
|
739
|
+
'',
|
|
740
|
+
`Consult these procedures before acting. Fetch each body with \`/hs-procedure <name>\`.`,
|
|
741
|
+
'',
|
|
742
|
+
];
|
|
743
|
+
for (const row of result.procedures) {
|
|
744
|
+
const badge =
|
|
745
|
+
row.severity === 'required'
|
|
746
|
+
? '**required**'
|
|
747
|
+
: row.severity === 'advisory'
|
|
748
|
+
? '_advisory (not in this vault)_'
|
|
749
|
+
: '_recommended_';
|
|
750
|
+
const path = row.path ? ` · \`${row.path}\`` : ' · _missing from catalog_';
|
|
751
|
+
lines.push(`- ${badge} — **\`${row.procedure}\`**${path}`);
|
|
752
|
+
if (row.title) lines.push(` ${row.title}`);
|
|
753
|
+
if (row.why) lines.push(` Why: ${row.why}`);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (result.filing_tool) {
|
|
757
|
+
const ft = result.filing_tool;
|
|
758
|
+
lines.push('');
|
|
759
|
+
lines.push('## File it with the canonical tool');
|
|
760
|
+
lines.push('');
|
|
761
|
+
lines.push(`Call \`${ft.tool}\` — do **not** copy a sibling spec's frontmatter.`);
|
|
762
|
+
lines.push('');
|
|
763
|
+
lines.push('```jsonc');
|
|
764
|
+
lines.push(`${ft.tool}(${JSON.stringify(ft.minimal_call ?? {}, null, 2)})`);
|
|
765
|
+
lines.push('```');
|
|
766
|
+
if (ft.note) lines.push(`> ${ft.note}`);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (result.close_tool) {
|
|
770
|
+
const ct = result.close_tool;
|
|
771
|
+
lines.push('');
|
|
772
|
+
lines.push('## Close-out status flip (required)');
|
|
773
|
+
lines.push('');
|
|
774
|
+
lines.push(`Call \`${ct.tool}\` — **never** hand-edit \`status:\` in frontmatter.`);
|
|
775
|
+
lines.push('');
|
|
776
|
+
lines.push('```jsonc');
|
|
777
|
+
lines.push(`${ct.tool}(${JSON.stringify(ct.minimal_call ?? {}, null, 2)})`);
|
|
778
|
+
lines.push('```');
|
|
779
|
+
if (ct.note) lines.push(`> ${ct.note}`);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
description: `hs-preflight — ${result.intent} (${result.procedures.length})`,
|
|
784
|
+
messages: [{ role: 'user', content: { type: 'text', text: lines.join('\n') } }],
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function handleResPrompt(vaultRoot) {
|
|
789
|
+
const focus = loadResearchFocus(vaultRoot);
|
|
790
|
+
const markdown = buildHsResPromptMarkdown(focus, { vaultRoot });
|
|
791
|
+
return {
|
|
792
|
+
description: focus?.source_idea_id
|
|
793
|
+
? `Research on deck: ${focus.source_idea_id} (scope: ${focus.scope || 'internal'})`
|
|
794
|
+
: 'No research pinned — run console Promote first',
|
|
795
|
+
messages: [
|
|
796
|
+
{
|
|
797
|
+
role: 'user',
|
|
798
|
+
content: { type: 'text', text: markdown },
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
export function handleReviewPrompt(vaultRoot, config, args = {}) {
|
|
805
|
+
const { error, result } = resolveNextReviewPayload(
|
|
806
|
+
vaultRoot,
|
|
807
|
+
config,
|
|
808
|
+
args,
|
|
809
|
+
nextReviewDeps(),
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
if (error) {
|
|
813
|
+
return {
|
|
814
|
+
description: `hs-review — ${error.error}`,
|
|
815
|
+
messages: [
|
|
816
|
+
{
|
|
817
|
+
role: 'user',
|
|
818
|
+
content: {
|
|
819
|
+
type: 'text',
|
|
820
|
+
text: `HeySpex could not resolve the next review-ready spec:\n\n\`\`\`json\n${JSON.stringify(error, null, 2)}\n\`\`\``,
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const header = `# Next review — ${result.spec_id}
|
|
828
|
+
|
|
829
|
+
**${result.title}** · status \`${result.status}\` · source \`${result.source_kind}\`${result.phase_name ? ` · phase **${result.phase_name}**` : ''}${typeof result.age_in_review_days === 'number' ? ` · age ${result.age_in_review_days}d` : ''}
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
`;
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
description: `Next review: ${result.spec_id} — ${result.title}`,
|
|
837
|
+
messages: [
|
|
838
|
+
{
|
|
839
|
+
role: 'user',
|
|
840
|
+
content: {
|
|
841
|
+
type: 'text',
|
|
842
|
+
text: `${header}${result.user_prompt_markdown ?? result.prompt_markdown}\n\n---\n\n_Review this spec next. Parent: \`${result.parent_spec_path}\` · Review doc: \`${result.review_doc_path}\` · resolved ${result.resolved_at}_`,
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* [[HS-PG9I-TASK]] — synthesize the minimal markdown body the prompt builder needs
|
|
851
|
+
* to render the Summary section for a DB-resident (not-yet-materialized) capture,
|
|
852
|
+
* whose state lives in the content_buffer rather than a markdown file.
|
|
853
|
+
*/
|
|
854
|
+
export function buildResumePromptBody(norm = {}) {
|
|
855
|
+
let body = '# Core Idea\n';
|
|
856
|
+
if (norm.core) body += `\n${norm.core}\n`;
|
|
857
|
+
if (norm.summary) body += `\n## Summary\n\n${norm.summary}\n`;
|
|
858
|
+
return body;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* [[HS-PG9I-TASK]] / [[HS-34KP-RES]] — resolve and attach to an EXPLICITLY named
|
|
863
|
+
* capture, DB-first with a disk fallback for materialized/legacy captures. Mirrors
|
|
864
|
+
* handleActiveCapture's id-honoring pattern. Never lists pending sessions and never
|
|
865
|
+
* creates a new capture — a missing id returns a clear "no such capture" message.
|
|
866
|
+
* When the capture already has a Core Idea, the prompt renders the resume branch
|
|
867
|
+
* (expand / iterate / promote) instead of a fresh first-capture instruction.
|
|
868
|
+
*/
|
|
869
|
+
export async function resumeCaptureById(vaultRoot, identity, promptName, wantedId) {
|
|
870
|
+
// DB FIRST: a pending capture session has no markdown file yet, so a disk-only
|
|
871
|
+
// lookup would 404 it. Resolve the live session row by id.
|
|
872
|
+
const got = await getCaptureSessionOnConsole(wantedId);
|
|
873
|
+
if (got.ok && got.session) {
|
|
874
|
+
let session = got.session;
|
|
875
|
+
// Attach this agent/session to the named capture — no second row (Q3 semantics).
|
|
876
|
+
const attachResult = await patchCaptureSessionOnConsole(session.id, {
|
|
877
|
+
attach: { agent: identity.agent, sessionId: identity.sessionId },
|
|
878
|
+
});
|
|
879
|
+
// Keep the capture-on-deck pin pointing at the resumed capture so a later no-id
|
|
880
|
+
// call — in this or a fresh session — resolves back to it, not to whatever was
|
|
881
|
+
// most-recently-active.
|
|
882
|
+
// Measured, not assumed ([[HS-IOJW-5YDH-DIAG]] sweep): the return value used to
|
|
883
|
+
// be discarded, so a pin that never persisted still rendered a clean "resumed"
|
|
884
|
+
// prompt — and the next no-id /hs-capture then resolved to a DIFFERENT capture.
|
|
885
|
+
const focusPin = await writeCaptureFocusVendor(vaultRoot, {
|
|
886
|
+
specId: session.id,
|
|
887
|
+
specPath: session.specPath || '',
|
|
888
|
+
reason: `Resumed via /${promptName}`,
|
|
889
|
+
claim: { claimed_by: identity.agent, claim_session: identity.sessionId },
|
|
890
|
+
});
|
|
891
|
+
// Flush any offline-buffered captures into this session now that we're
|
|
892
|
+
// online (append against the live buffer, not a blank one — HS-K27H).
|
|
893
|
+
const drain = await drainCaptureOutbox(
|
|
894
|
+
vaultRoot,
|
|
895
|
+
session.id,
|
|
896
|
+
identity,
|
|
897
|
+
session.contentBuffer && typeof session.contentBuffer === 'object'
|
|
898
|
+
? session.contentBuffer
|
|
899
|
+
: {},
|
|
900
|
+
);
|
|
901
|
+
if (drain.drained > 0) {
|
|
902
|
+
const refetched = await getCaptureSessionOnConsole(session.id);
|
|
903
|
+
if (refetched.ok && refetched.session) session = refetched.session;
|
|
904
|
+
}
|
|
905
|
+
const contentBuffer = session.contentBuffer || {};
|
|
906
|
+
const norm = normalizeCaptureBuffer(contentBuffer);
|
|
907
|
+
const isResume = norm.core.length > 0;
|
|
908
|
+
const resolved = {
|
|
909
|
+
spec_id: session.id,
|
|
910
|
+
spec_path: session.specPath || null,
|
|
911
|
+
name: contentBuffer.name || `Capture ${session.id}`,
|
|
912
|
+
core_idea: norm.core,
|
|
913
|
+
notes: norm.notes,
|
|
914
|
+
body: buildResumePromptBody(norm),
|
|
915
|
+
frontmatter: { id: session.id, status: session.status },
|
|
916
|
+
};
|
|
917
|
+
const openedAgo = session.createdAt
|
|
918
|
+
? formatTimeSince(new Date(session.createdAt))
|
|
919
|
+
: 'unknown';
|
|
920
|
+
const banner = isResume
|
|
921
|
+
? `_Resumed capture \`${session.id}\` — it already has a Core Idea. This is a **resume**, not a new capture: offer to expand, iterate, or promote to research; do not re-capture from scratch._\n\n`
|
|
922
|
+
: `_Attached to capture \`${session.id}\` (opened by ${session.openedBy}, ${openedAgo} ago)._\n\n`;
|
|
923
|
+
const markdown =
|
|
924
|
+
capturePinWarningBanner(focusPin, session.id) +
|
|
925
|
+
banner +
|
|
926
|
+
buildHsCapturePromptMarkdown(resolved, { fresh: false, resume: isResume });
|
|
927
|
+
return {
|
|
928
|
+
description: `Capture on deck: ${session.id} (${isResume ? 'resumed' : 'attached'}${attachResult.ok ? '' : ' — attach failed'}${focusPin.pinned ? '' : ' — PIN NOT WRITTEN'})`,
|
|
929
|
+
messages: [{ role: 'user', content: { type: 'text', text: markdown } }],
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// DB miss (404 / GET_FAILED) or console unreachable → fall back to disk for a
|
|
934
|
+
// materialized / legacy capture whose live session row is gone.
|
|
935
|
+
const specPath =
|
|
936
|
+
findCaptureSpecFile(vaultRoot, wantedId) || findSpecFile(vaultRoot, wantedId);
|
|
937
|
+
if (specPath) {
|
|
938
|
+
const spec = loadSpec(vaultRoot, specPath);
|
|
939
|
+
if (spec) {
|
|
940
|
+
const coreIdea = extractCaptureCoreFromBody(spec.body) || '';
|
|
941
|
+
const isResume = coreIdea.trim().length > 0;
|
|
942
|
+
// Re-pin the capture-on-deck to this materialized capture.
|
|
943
|
+
const pin = executePinCapture(vaultRoot, parseFrontmatter, loadSpec, {
|
|
944
|
+
spec_id: wantedId,
|
|
945
|
+
reason: `Resumed via /${promptName}`,
|
|
946
|
+
});
|
|
947
|
+
const resolved = {
|
|
948
|
+
spec_id: spec.frontmatter?.id || wantedId,
|
|
949
|
+
spec_path: specPath,
|
|
950
|
+
name: spec.frontmatter?.name || `Capture ${wantedId}`,
|
|
951
|
+
core_idea: coreIdea,
|
|
952
|
+
notes: extractCaptureNotesFromBody(spec.body) || '',
|
|
953
|
+
body: spec.body || '',
|
|
954
|
+
frontmatter: spec.frontmatter || { id: wantedId },
|
|
955
|
+
};
|
|
956
|
+
const banner = isResume
|
|
957
|
+
? `_Resumed capture \`${resolved.spec_id}\` from disk — it already has a Core Idea. This is a **resume**, not a new capture: offer to expand, iterate, or promote to research; do not re-capture from scratch._\n\n`
|
|
958
|
+
: `_Attached to capture \`${resolved.spec_id}\` (from disk)._\n\n`;
|
|
959
|
+
const markdown = banner + buildHsCapturePromptMarkdown(resolved, { fresh: false, resume: isResume });
|
|
960
|
+
return {
|
|
961
|
+
description: `Capture on deck: ${resolved.spec_id} (${isResume ? 'resumed' : 'attached'} from disk${pin.error ? ' — pin failed' : ''})`,
|
|
962
|
+
messages: [{ role: 'user', content: { type: 'text', text: markdown } }],
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Neither a live session nor a disk file → clear not-found. NEVER auto-create a
|
|
968
|
+
// near-miss id (the [[capture-guardrails]] "scope to the intended id" rule).
|
|
969
|
+
return {
|
|
970
|
+
description: `${promptName} — no such capture ${wantedId}`,
|
|
971
|
+
messages: [
|
|
972
|
+
{
|
|
973
|
+
role: 'user',
|
|
974
|
+
content: {
|
|
975
|
+
type: 'text',
|
|
976
|
+
text:
|
|
977
|
+
`**No capture \`${wantedId}\` found.**\n\n` +
|
|
978
|
+
`There is no live capture session or vault file with that id, so I did **not** open a workspace — ` +
|
|
979
|
+
`and I did **not** create a new capture, which would silently drop the id you named.\n\n` +
|
|
980
|
+
`- To start a fresh capture, run \`/hs-capture\` with no id.\n` +
|
|
981
|
+
`- If you expected \`${wantedId}\` to exist, double-check the id.`,
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
],
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* [[HS-TASK-MJJK]] — /hs-capture prompt handler rewired for session-first attach.
|
|
990
|
+
*
|
|
991
|
+
* 1. Query GET /api/capture/sessions?status=pending for an existing session.
|
|
992
|
+
* 2. If found: attach to it (PATCH with agent info); no new row created.
|
|
993
|
+
* 3. If not found: POST to create a new pending session.
|
|
994
|
+
* 4. If API unreachable: fall back to file-based logic (no regression).
|
|
995
|
+
*
|
|
996
|
+
* Running /hs-capture twice back-to-back results in one row, not two (Q3 attach semantics).
|
|
997
|
+
*/
|
|
998
|
+
export async function handleCapturePrompt(vaultRoot, args = {}) {
|
|
999
|
+
const promptName = 'hs-capture';
|
|
1000
|
+
const identity = detectAgentIdentity();
|
|
1001
|
+
|
|
1002
|
+
// [[HS-PG9I-TASK]] / [[HS-34KP-RES]] — honor an EXPLICIT capture id (resume-aware).
|
|
1003
|
+
// Pre-fix bug: the id was pinned but the session-first path below IGNORED the pin,
|
|
1004
|
+
// listed pending sessions, and minted a NEW capture (VDPB → V5KI). When an id is
|
|
1005
|
+
// named we resolve THAT capture (DB-first, disk fallback) and short-circuit — never
|
|
1006
|
+
// fall through to list/create. This is the cross-session resume guarantee: re-entering
|
|
1007
|
+
// /hs-capture with an id reopens the same capture instead of attaching to whatever was
|
|
1008
|
+
// most-recently-active. A missing id returns a clear error, never a silent auto-create.
|
|
1009
|
+
if (args.spec_id && isCaptureSpecId(args.spec_id)) {
|
|
1010
|
+
const wantedId = String(args.spec_id).trim().toUpperCase();
|
|
1011
|
+
return await resumeCaptureById(vaultRoot, identity, promptName, wantedId);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// [[HS-TASK-MJJK]] — No explicit id: query sessions API for pending sessions first
|
|
1015
|
+
const sessionsResult = await listPendingCaptureSessions();
|
|
1016
|
+
|
|
1017
|
+
if (sessionsResult.ok && sessionsResult.sessions.length > 0) {
|
|
1018
|
+
// Attach to existing session (Q3: attach silently, no second row)
|
|
1019
|
+
let session = sessionsResult.sessions[0];
|
|
1020
|
+
const attachResult = await patchCaptureSessionOnConsole(session.id, {
|
|
1021
|
+
attach: {
|
|
1022
|
+
agent: identity.agent,
|
|
1023
|
+
sessionId: identity.sessionId,
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// Flush any offline-buffered captures into this session now that we're
|
|
1028
|
+
// online (append against the live buffer, not a blank one — HS-K27H).
|
|
1029
|
+
const drain = await drainCaptureOutbox(
|
|
1030
|
+
vaultRoot,
|
|
1031
|
+
session.id,
|
|
1032
|
+
identity,
|
|
1033
|
+
session.contentBuffer && typeof session.contentBuffer === 'object'
|
|
1034
|
+
? session.contentBuffer
|
|
1035
|
+
: {},
|
|
1036
|
+
);
|
|
1037
|
+
if (drain.drained > 0) {
|
|
1038
|
+
const refetched = await getCaptureSessionOnConsole(session.id);
|
|
1039
|
+
if (refetched.ok && refetched.session) session = refetched.session;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Calculate time since session was opened
|
|
1043
|
+
const openedAgo = session.createdAt
|
|
1044
|
+
? formatTimeSince(new Date(session.createdAt))
|
|
1045
|
+
: 'unknown';
|
|
1046
|
+
|
|
1047
|
+
// Build resolved object for prompt markdown
|
|
1048
|
+
const contentBuffer = session.contentBuffer || {};
|
|
1049
|
+
const resolved = {
|
|
1050
|
+
spec_id: session.id,
|
|
1051
|
+
spec_path: session.specPath || null,
|
|
1052
|
+
name: contentBuffer.name || `Capture ${session.id}`,
|
|
1053
|
+
core_idea: contentBuffer.core_idea || '',
|
|
1054
|
+
notes: contentBuffer.notes || '',
|
|
1055
|
+
body: '', // Body is in contentBuffer, not a file
|
|
1056
|
+
frontmatter: { id: session.id, status: session.status },
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const attachNote = `_Attached to existing capture \`${session.id}\` (opened by ${session.openedBy}, ${openedAgo} ago)._\n\n`;
|
|
1060
|
+
const markdown = attachNote + buildHsCapturePromptMarkdown(resolved, { fresh: false });
|
|
1061
|
+
|
|
1062
|
+
return {
|
|
1063
|
+
description: `Capture on deck: ${session.id} (attached${attachResult.ok ? '' : ' — attach failed'})`,
|
|
1064
|
+
messages: [
|
|
1065
|
+
{
|
|
1066
|
+
role: 'user',
|
|
1067
|
+
content: {
|
|
1068
|
+
type: 'text',
|
|
1069
|
+
text: markdown,
|
|
1070
|
+
},
|
|
1071
|
+
},
|
|
1072
|
+
],
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// No pending session found — try to create a new one via API
|
|
1077
|
+
if (sessionsResult.ok) {
|
|
1078
|
+
const createResult = await createCaptureSessionOnConsole({
|
|
1079
|
+
opened_by: 'agent',
|
|
1080
|
+
opened_by_session: identity.sessionId,
|
|
1081
|
+
mode: 'attended',
|
|
1082
|
+
status: 'pending',
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
if (createResult.ok && createResult.session) {
|
|
1086
|
+
let session = createResult.session;
|
|
1087
|
+
|
|
1088
|
+
// Pin capture-on-deck + layer-2 metadata to Neon ([[HS-TASK-CFL2]]; file
|
|
1089
|
+
// is the offline fallback).
|
|
1090
|
+
const focusPin = await writeCaptureFocusVendor(vaultRoot, {
|
|
1091
|
+
specId: session.id,
|
|
1092
|
+
specPath: session.specPath || '',
|
|
1093
|
+
reason: `Auto-created via /${promptName}`,
|
|
1094
|
+
claim: { claimed_by: identity.agent, claim_session: identity.sessionId },
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// Flush any offline-buffered captures into the new session (a fresh
|
|
1098
|
+
// session's buffer is empty, so {} is genuinely correct here).
|
|
1099
|
+
const drain = await drainCaptureOutbox(vaultRoot, session.id, identity, {});
|
|
1100
|
+
if (drain.drained > 0) {
|
|
1101
|
+
const refetched = await getCaptureSessionOnConsole(session.id);
|
|
1102
|
+
if (refetched.ok && refetched.session) session = refetched.session;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const contentBuffer = session.contentBuffer || {};
|
|
1106
|
+
const resolved = {
|
|
1107
|
+
spec_id: session.id,
|
|
1108
|
+
spec_path: session.specPath || null,
|
|
1109
|
+
name: contentBuffer.name || `Capture ${session.id}`,
|
|
1110
|
+
core_idea: contentBuffer.core_idea || '',
|
|
1111
|
+
notes: contentBuffer.notes || '',
|
|
1112
|
+
body: '',
|
|
1113
|
+
frontmatter: { id: session.id, status: session.status },
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
const freshNote = `_Auto-created \`${session.id}\` — no prior content. The user's next message will be the first capture._\n\n`;
|
|
1117
|
+
const markdown =
|
|
1118
|
+
capturePinWarningBanner(focusPin, session.id) +
|
|
1119
|
+
freshNote +
|
|
1120
|
+
buildHsCapturePromptMarkdown(resolved, { fresh: true });
|
|
1121
|
+
|
|
1122
|
+
return {
|
|
1123
|
+
description: `Capture on deck: ${session.id} (auto-created via API)`,
|
|
1124
|
+
messages: [
|
|
1125
|
+
{
|
|
1126
|
+
role: 'user',
|
|
1127
|
+
content: {
|
|
1128
|
+
type: 'text',
|
|
1129
|
+
text: markdown,
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
],
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// API unreachable or create failed — capture is online-only, so we do NOT mint
|
|
1138
|
+
// a local file scaffold ([[heyspex-is-online-only-no-offline]]). /hs-capture
|
|
1139
|
+
// itself carries no user words yet (those arrive via heyspex_update_capture,
|
|
1140
|
+
// which stashes to the offline buffer), so there's nothing to stash here — just
|
|
1141
|
+
// a graceful, SaaS-grade message. Anything already buffered stays safe and will
|
|
1142
|
+
// sync on reconnect.
|
|
1143
|
+
const pending = outboxCount(vaultRoot);
|
|
1144
|
+
const pendingNote = pending > 0
|
|
1145
|
+
? ` You have ${pending} capture${pending === 1 ? '' : 's'} saved offline; they'll sync automatically once the connection is back.`
|
|
1146
|
+
: '';
|
|
1147
|
+
return {
|
|
1148
|
+
description: `${promptName} — server unreachable (capture deferred)`,
|
|
1149
|
+
messages: [
|
|
1150
|
+
{
|
|
1151
|
+
role: 'user',
|
|
1152
|
+
content: {
|
|
1153
|
+
type: 'text',
|
|
1154
|
+
text:
|
|
1155
|
+
"**HeySpex can't reach the server right now, so I can't open your capture workspace yet.**\n\n" +
|
|
1156
|
+
"Anything you've already captured is safe." + pendingNote + "\n\n" +
|
|
1157
|
+
"Tell me what you'd like to capture and I'll hold onto your exact words — they'll be filed automatically the moment the connection is back. Nothing will be lost.",
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
],
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Format a time duration since a given date in human-readable form.
|
|
1166
|
+
*/
|
|
1167
|
+
export function formatTimeSince(date) {
|
|
1168
|
+
const now = new Date();
|
|
1169
|
+
const diffMs = now - date;
|
|
1170
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
1171
|
+
if (diffMins < 1) return '<1m';
|
|
1172
|
+
if (diffMins < 60) return `${diffMins}m`;
|
|
1173
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
1174
|
+
if (diffHours < 24) return `${diffHours}h`;
|
|
1175
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
1176
|
+
return `${diffDays}d`;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
export function handleGroundPrompt(args = {}) {
|
|
1180
|
+
// Thin trigger (G7R2/G7R6): call the bounded report path and paste it verbatim.
|
|
1181
|
+
// The legacy six-phase read-everything scaffold (buildGroundWorkflowMarkdown) is
|
|
1182
|
+
// retired from the prompt surface — it caused the context-bloat failure documented
|
|
1183
|
+
// in hs-ground-as-built-mcp-failure-modes.
|
|
1184
|
+
const text = [
|
|
1185
|
+
'# Ground plan (`/hs-ground`)',
|
|
1186
|
+
'',
|
|
1187
|
+
'Bounded pre-plan grounding. Substrate is production-lexical; **you are the reranker**.',
|
|
1188
|
+
'',
|
|
1189
|
+
'**Execute:** Call MCP tool `heyspex_ground_plan` with `report: true`:',
|
|
1190
|
+
'',
|
|
1191
|
+
'```json',
|
|
1192
|
+
JSON.stringify(
|
|
1193
|
+
{
|
|
1194
|
+
topic: args.topic || '<Short feature title>',
|
|
1195
|
+
summary: args.summary || args.plan_hint || '<Description from chat>',
|
|
1196
|
+
areas: ['<optional: console | companion | mcp | vault | indexer>'],
|
|
1197
|
+
report: true,
|
|
1198
|
+
max_tokens: 2000,
|
|
1199
|
+
},
|
|
1200
|
+
null,
|
|
1201
|
+
2,
|
|
1202
|
+
),
|
|
1203
|
+
'```',
|
|
1204
|
+
'',
|
|
1205
|
+
'**Then:** Paste the returned report verbatim and act on it. Do **NOT** open the discovered specs, run broad `rg`, or read whole files — open only the one or two specs you judge directly relevant.',
|
|
1206
|
+
'',
|
|
1207
|
+
'If the footer shows **BLOCKING** conflicts, run `/hs-resolve` first, then re-run `/hs-ground`.',
|
|
1208
|
+
'',
|
|
1209
|
+
'**Handoff:** With a grounded plan, run `/hs-push` or `/hs-push-next`.',
|
|
1210
|
+
].join('\n');
|
|
1211
|
+
|
|
1212
|
+
return {
|
|
1213
|
+
description: 'hs-ground — bounded pre-plan grounding before push',
|
|
1214
|
+
messages: [
|
|
1215
|
+
{
|
|
1216
|
+
role: 'user',
|
|
1217
|
+
content: {
|
|
1218
|
+
type: 'text',
|
|
1219
|
+
text,
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
],
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export function handlePushPrompt(name, args = {}) {
|
|
1227
|
+
const onDeck = name === 'hs-push-next';
|
|
1228
|
+
const workflow = buildPushWorkflowMarkdown({
|
|
1229
|
+
on_deck: onDeck,
|
|
1230
|
+
epic_name: args.epic_name,
|
|
1231
|
+
plan_hint: args.plan_hint || args.implementation_prompt?.slice(0, 200),
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
const toolHint = [
|
|
1235
|
+
'',
|
|
1236
|
+
'---',
|
|
1237
|
+
'',
|
|
1238
|
+
'**Execute:** Call MCP tool `heyspex_push_plan` with JSON from the plan above, e.g.:',
|
|
1239
|
+
'',
|
|
1240
|
+
'```json',
|
|
1241
|
+
JSON.stringify(
|
|
1242
|
+
{
|
|
1243
|
+
epic_name: args.epic_name || '<Epic title from plan>',
|
|
1244
|
+
summary: '<Plain-language epic summary per spec-summary-authoring>',
|
|
1245
|
+
plan_markdown: '<Epic body markdown>',
|
|
1246
|
+
implementation_prompt: args.implementation_prompt || '<Optional NEXT STEP override>',
|
|
1247
|
+
source_plan_paths: [
|
|
1248
|
+
'.cursor/plans/<name>.plan.md',
|
|
1249
|
+
'C:\\Users\\<you>\\.cursor\\plans\\<name>.plan.md',
|
|
1250
|
+
],
|
|
1251
|
+
on_deck: onDeck,
|
|
1252
|
+
focus_reason: args.focus_reason || (onDeck ? 'hs-push-next' : undefined),
|
|
1253
|
+
},
|
|
1254
|
+
null,
|
|
1255
|
+
2,
|
|
1256
|
+
),
|
|
1257
|
+
'```',
|
|
1258
|
+
'',
|
|
1259
|
+
onDeck
|
|
1260
|
+
? 'Then run `/hs-next` or `heyspex_next_on_deck` to pull the on-deck prompt.'
|
|
1261
|
+
: 'When ready to implement, run `/hs-push-next` or re-push with `on_deck: true`.',
|
|
1262
|
+
].join('\n');
|
|
1263
|
+
|
|
1264
|
+
return {
|
|
1265
|
+
description: onDeck
|
|
1266
|
+
? 'hs-push-next — push plan to HeySpex and pin on deck'
|
|
1267
|
+
: 'hs-push — push epic plan to HeySpex vault',
|
|
1268
|
+
messages: [
|
|
1269
|
+
{
|
|
1270
|
+
role: 'user',
|
|
1271
|
+
content: {
|
|
1272
|
+
type: 'text',
|
|
1273
|
+
text: `${workflow}\n${toolHint}`,
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
export function handleShipPrompt(args = {}) {
|
|
1281
|
+
const workflow = buildShipWorkflowMarkdown();
|
|
1282
|
+
|
|
1283
|
+
const toolHint = [
|
|
1284
|
+
'',
|
|
1285
|
+
'---',
|
|
1286
|
+
'',
|
|
1287
|
+
'**Execute:** Call MCP tool `heyspex_backfill_epic` with JSON gathered from the conversation:',
|
|
1288
|
+
'',
|
|
1289
|
+
'```json',
|
|
1290
|
+
JSON.stringify(
|
|
1291
|
+
{
|
|
1292
|
+
epic_name: args.epic_name || '<What was accomplished>',
|
|
1293
|
+
summary: args.summary || '<Brief description>',
|
|
1294
|
+
plan_markdown: '<What was done, acceptance criteria met>',
|
|
1295
|
+
children: [
|
|
1296
|
+
{ name: '<Task 1>', summary: '<What was done>' },
|
|
1297
|
+
],
|
|
1298
|
+
files_changed: [
|
|
1299
|
+
'<path/to/file1.ts>',
|
|
1300
|
+
'<path/to/file2.tsx>',
|
|
1301
|
+
],
|
|
1302
|
+
roadmap_id: 'HS-ROADMAP-PRODUCT',
|
|
1303
|
+
roadmap_phase: 'P0',
|
|
1304
|
+
},
|
|
1305
|
+
null,
|
|
1306
|
+
2,
|
|
1307
|
+
),
|
|
1308
|
+
'```',
|
|
1309
|
+
'',
|
|
1310
|
+
'**Roadmap registration:** By default, shipped epics are added to `P0` of `HS-ROADMAP-PRODUCT` so they appear in the kanban Review column. Use `skip_roadmap_registration: true` to disable.',
|
|
1311
|
+
'',
|
|
1312
|
+
'Then commit and push the new specs.',
|
|
1313
|
+
].join('\n');
|
|
1314
|
+
|
|
1315
|
+
return {
|
|
1316
|
+
description: 'hs-backfill — retroactively backfill completed work into Review',
|
|
1317
|
+
messages: [
|
|
1318
|
+
{
|
|
1319
|
+
role: 'user',
|
|
1320
|
+
content: {
|
|
1321
|
+
type: 'text',
|
|
1322
|
+
text: `${workflow}\n${toolHint}`,
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1325
|
+
],
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
export function handlePromptsGet(vaultRoot, config, params) {
|
|
1330
|
+
const { name, arguments: args = {} } = params;
|
|
1331
|
+
|
|
1332
|
+
if (name === 'hs-push' || name === 'hs-push-next') {
|
|
1333
|
+
return handlePushPrompt(name, args);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (name === 'hs-backfill') {
|
|
1337
|
+
return handleShipPrompt(args);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
if (name === 'hs-ground') {
|
|
1341
|
+
return handleGroundPrompt(args);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (name === 'hs-capture') {
|
|
1345
|
+
// [[HS-TASK-MJJK]] — async handler for session-first attach
|
|
1346
|
+
return handleCapturePrompt(vaultRoot, args);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if (name === 'hs-res') {
|
|
1350
|
+
return handleResPrompt(vaultRoot, args);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (name === 'hs-review') {
|
|
1354
|
+
return handleReviewPrompt(vaultRoot, config, args);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
if (name === 'hs-design-list') {
|
|
1358
|
+
return handleDesignListPrompt(vaultRoot, args);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (name === 'hs-design-get') {
|
|
1362
|
+
return handleDesignGetPrompt(vaultRoot, args);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (name === 'hs-design-propose') {
|
|
1366
|
+
return handleDesignProposePrompt(vaultRoot, args);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (name === 'hs-procedure-list') {
|
|
1370
|
+
return handleProcedureListPrompt(vaultRoot, args);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
if (name === 'hs-procedure') {
|
|
1374
|
+
return handleProcedurePrompt(vaultRoot, args);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (name === 'hs-preflight') {
|
|
1378
|
+
return handlePreflightPrompt(vaultRoot, args);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (name === 'hs-promote-research') {
|
|
1382
|
+
return handlePromoteResearchPrompt(args);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (name === 'hs-split-research') {
|
|
1386
|
+
return handleSplitResearchPrompt(args);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (name === 'hs-promote-capture') {
|
|
1390
|
+
return handlePromoteCapturePrompt(args);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (name !== 'hs-next') {
|
|
1394
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Async: resolves the Neon layer-1 pin (`injectNeonFocusPin`) before the sync
|
|
1398
|
+
// resolver. handlePromptsGet stays sync and returns this promise; the
|
|
1399
|
+
// `prompts/get` dispatcher already awaits a thenable result.
|
|
1400
|
+
return handleNextOnDeckPrompt(vaultRoot, config, args);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* The `hs-next` prompt body, async so it can consult the Neon layer-1 focus pin
|
|
1405
|
+
* before running the synchronous on-deck resolver. Mirrors `handleNextOnDeck`
|
|
1406
|
+
* (the tools/call path) — both inject `neon_pin_spec_id` via `injectNeonFocusPin`.
|
|
1407
|
+
*/
|
|
1408
|
+
export async function handleNextOnDeckPrompt(vaultRoot, config, args) {
|
|
1409
|
+
const resolvedArgs = await injectCloudWipState(
|
|
1410
|
+
vaultRoot,
|
|
1411
|
+
await injectFrozenMilestoneOrder(await injectNeonFocusPin(args)),
|
|
1412
|
+
);
|
|
1413
|
+
const { error, result } = await resolveNextOnDeckAroundActiveClaims(
|
|
1414
|
+
vaultRoot,
|
|
1415
|
+
config,
|
|
1416
|
+
resolvedArgs,
|
|
1417
|
+
);
|
|
1418
|
+
|
|
1419
|
+
if (error) {
|
|
1420
|
+
return {
|
|
1421
|
+
description: `hs-next — ${error.error}`,
|
|
1422
|
+
messages: [
|
|
1423
|
+
{
|
|
1424
|
+
role: 'user',
|
|
1425
|
+
content: {
|
|
1426
|
+
type: 'text',
|
|
1427
|
+
text: `HeySpex could not resolve the next on-deck task:\n\n\`\`\`json\n${JSON.stringify(error, null, 2)}\n\`\`\``,
|
|
1428
|
+
},
|
|
1429
|
+
},
|
|
1430
|
+
],
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
await applyOnDeckHttpAugments(result, resolvedArgs);
|
|
1435
|
+
await attachConsoleStatusToOnDeck(result);
|
|
1436
|
+
|
|
1437
|
+
const header = `# Next on deck — ${result.spec_id}
|
|
1438
|
+
|
|
1439
|
+
**${result.title}** · status \`${result.status}\` · source \`${result.source_kind}\`
|
|
1440
|
+
${result.phase_name ? ` · phase **${result.phase_name}**` : ''}
|
|
1441
|
+
|
|
1442
|
+
---
|
|
1443
|
+
|
|
1444
|
+
`;
|
|
1445
|
+
|
|
1446
|
+
return {
|
|
1447
|
+
description: `Next on deck: ${result.spec_id} — ${result.title}`,
|
|
1448
|
+
messages: [
|
|
1449
|
+
{
|
|
1450
|
+
role: 'user',
|
|
1451
|
+
content: {
|
|
1452
|
+
type: 'text',
|
|
1453
|
+
// user_prompt_markdown is the post-HS-TASK-HSNX user-facing field;
|
|
1454
|
+
// fall back to the @deprecated prompt_markdown alias for back-compat.
|
|
1455
|
+
text: `${header}${result.user_prompt_markdown ?? result.prompt_markdown}\n\n---\n\n_Work this task next. Spec: \`${result.spec_path}\` · resolved ${result.resolved_at}_`,
|
|
1456
|
+
},
|
|
1457
|
+
},
|
|
1458
|
+
],
|
|
1459
|
+
};
|
|
1460
|
+
}
|