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,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs auto — resolve /hs-auto [filename] and print dispatch plan.
|
|
4
|
+
*/
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { resolveHsAutoEntry } from './hs-auto-resolve.mjs';
|
|
9
|
+
|
|
10
|
+
const HELP = `hs auto — autonomous COO dispatch resolver
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
hs auto [filename] [--json] [--dry-run]
|
|
14
|
+
hs auto --help
|
|
15
|
+
|
|
16
|
+
Arguments:
|
|
17
|
+
filename Optional. One of:
|
|
18
|
+
- vault spec id (HS-…-TASK-… / HS-…-EPIC-… / HS-…-DIAG-… / HS-…-RES-…)
|
|
19
|
+
- path to a spec .md under specs/
|
|
20
|
+
- path to a source file (matched via task target: frontmatter)
|
|
21
|
+
Diagnose + research specs enter the source_plan → epic → tasks → dispatch pipeline.
|
|
22
|
+
Omit to enter discovery mode (backlog-selector after pre-flight).
|
|
23
|
+
|
|
24
|
+
Flags:
|
|
25
|
+
--json Machine-readable JSON on stdout
|
|
26
|
+
--dry-run Same as default (never dispatches — prints plan only)
|
|
27
|
+
--help, -h Show this help
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const EXIT = {
|
|
31
|
+
OK: 0,
|
|
32
|
+
USAGE: 1,
|
|
33
|
+
RESOLVE_FAILED: 2,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** @param {string[]} argv tokens after "auto" */
|
|
37
|
+
export function parseAutoArgs(argv) {
|
|
38
|
+
let filename;
|
|
39
|
+
let json = false;
|
|
40
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
41
|
+
const a = argv[i];
|
|
42
|
+
if (a === '--help' || a === '-h') return { help: true };
|
|
43
|
+
if (a === '--json') {
|
|
44
|
+
json = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (a === '--dry-run') continue;
|
|
48
|
+
if (a.startsWith('-')) {
|
|
49
|
+
return { error: { code: EXIT.USAGE, message: `unknown flag: ${a}` } };
|
|
50
|
+
}
|
|
51
|
+
if (!filename) {
|
|
52
|
+
filename = a;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
return { error: { code: EXIT.USAGE, message: `unexpected argument: ${a}` } };
|
|
56
|
+
}
|
|
57
|
+
return { args: { filename, json } };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const PREFLIGHT = [
|
|
61
|
+
'node dev-factory/fleet/manager.mjs --status',
|
|
62
|
+
'node dev-factory/pipeline/backlog-selector.mjs --real',
|
|
63
|
+
'node dev-factory/router.mjs --self-test',
|
|
64
|
+
'node dev-factory/lib/coo-detect.mjs --self-test',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
const parsed = parseAutoArgs(process.argv.slice(2));
|
|
69
|
+
if (parsed.help) {
|
|
70
|
+
process.stdout.write(`${HELP}\n`);
|
|
71
|
+
process.exit(EXIT.OK);
|
|
72
|
+
}
|
|
73
|
+
if (parsed.error) {
|
|
74
|
+
process.stderr.write(`hs auto: ${parsed.error.message}\n`);
|
|
75
|
+
process.exit(parsed.error.code);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const vaultRoot = process.env.HS_VAULT_ROOT;
|
|
79
|
+
if (!vaultRoot) {
|
|
80
|
+
process.stderr.write('hs auto: HS_VAULT_ROOT is not set (the dispatcher must pass it).\n');
|
|
81
|
+
process.exit(EXIT.USAGE);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const resolved = resolveHsAutoEntry(vaultRoot, parsed.args.filename);
|
|
85
|
+
const payload = {
|
|
86
|
+
...resolved,
|
|
87
|
+
preflight: PREFLIGHT,
|
|
88
|
+
discover_command: 'node dev-factory/pipeline/backlog-selector.mjs --real',
|
|
89
|
+
overnight_command:
|
|
90
|
+
'node dev-factory/pipeline/run-overnight.mjs --concurrent --max-tasks 5 --max-concurrent 4',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (parsed.args.json) {
|
|
94
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
95
|
+
process.exit(resolved.error ? EXIT.RESOLVE_FAILED : EXIT.OK);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (resolved.error) {
|
|
99
|
+
process.stderr.write(`hs auto: ${resolved.error} — ${resolved.message}\n`);
|
|
100
|
+
process.exit(EXIT.RESOLVE_FAILED);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const lines = ['# /hs-auto plan', ''];
|
|
104
|
+
lines.push('## Pre-flight');
|
|
105
|
+
for (const cmd of PREFLIGHT) lines.push(`- \`${cmd}\``);
|
|
106
|
+
lines.push('');
|
|
107
|
+
if (resolved.mode === 'discover') {
|
|
108
|
+
lines.push('## Discovery (no filename)');
|
|
109
|
+
lines.push(`- \`${payload.discover_command}\``);
|
|
110
|
+
} else if (resolved.mode === 'source_plan') {
|
|
111
|
+
lines.push(`## Source plan — \`${resolved.spec_id}\` (${resolved.source_kind})`);
|
|
112
|
+
lines.push(resolved.message ?? 'File remediation epic + tasks, then re-resolve.');
|
|
113
|
+
if (resolved.re_resolve_command) lines.push(`- Re-resolve: \`${resolved.re_resolve_command}\``);
|
|
114
|
+
} else if (resolved.mode === 'breakdown') {
|
|
115
|
+
lines.push(`## Breakdown — \`${resolved.spec_id}\``);
|
|
116
|
+
lines.push(`- \`${resolved.breakdown_command}\``);
|
|
117
|
+
} else if (resolved.mode === 'dispatch') {
|
|
118
|
+
lines.push(`## Dispatch — \`${resolved.spec_id}\``);
|
|
119
|
+
lines.push(`- target: \`${resolved.target}\``);
|
|
120
|
+
lines.push(`- \`${resolved.dispatch_command}\``);
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
123
|
+
process.exit(EXIT.OK);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const invoked = process.argv[1]
|
|
127
|
+
&& fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
128
|
+
|
|
129
|
+
if (invoked) {
|
|
130
|
+
main().catch((err) => {
|
|
131
|
+
process.stderr.write(`hs auto: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
132
|
+
process.exit(EXIT.RESOLVE_FAILED);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs capture — CLI capture into the HeySpex console drafts inbox.
|
|
4
|
+
*
|
|
5
|
+
* The CLI face of the multi-capture workflow (companion voice + browser are
|
|
6
|
+
* the other faces). Posts a heyspex.capture/0.1 envelope to the console's
|
|
7
|
+
* POST /api/drafts — the same wire shape the companion's
|
|
8
|
+
* DraftsSpexConsoleClient sends — so CLI captures land in the same inbox and
|
|
9
|
+
* can join the same visual capture groups.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* hs capture "<text>" [--title <t>] [--add] [--json]
|
|
13
|
+
* hs capture --clipboard [--title <t>] [--add] [--json]
|
|
14
|
+
* hs capture --file <path> [--title <t>] [--add] [--json]
|
|
15
|
+
*
|
|
16
|
+
* --add chains this capture into the PREVIOUS CLI capture's group (within a
|
|
17
|
+
* 2-minute window) — the "add another?" gesture. Group state lives in
|
|
18
|
+
* .heyspex/cli-capture-group.json.
|
|
19
|
+
*/
|
|
20
|
+
import { execFileSync } from "node:child_process";
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import process from "node:process";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { pathToFileURL } from "node:url";
|
|
26
|
+
|
|
27
|
+
import { resolveConsoleUrl, DEFAULT_CONSOLE_URL } from "./lib/console-url-resolver.mjs";
|
|
28
|
+
import { heyspexHome } from "./lib/heyspex-home.mjs";
|
|
29
|
+
import { stashCaptureArgs } from "../vendor/heyspex-mcp/capture-outbox.mjs";
|
|
30
|
+
import { readBearerToken } from "../vendor/heyspex-mcp/credentials.mjs";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The bearer for a paired cloud console (`/api/drafts`-adjacent surfaces need
|
|
34
|
+
* it; a local dev console ignores it) — the ONE shared reader
|
|
35
|
+
* (`vendor/heyspex-mcp/credentials.mjs`,
|
|
36
|
+
* [[HS-HL0D-WW9R-TASK-share-one-bearer-reader-and-one-console]]), with
|
|
37
|
+
* `HEYSPEX_HOME` honoured through `heyspexHome()`.
|
|
38
|
+
*/
|
|
39
|
+
function readCredentialsMcpToken() {
|
|
40
|
+
return readBearerToken({ homeDir: heyspexHome() });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CHAIN_WINDOW_MS = 2 * 60 * 1000; // keep in sync with CaptureGroupTracker.ChainWindow
|
|
44
|
+
|
|
45
|
+
function usage() {
|
|
46
|
+
process.stdout.write(
|
|
47
|
+
[
|
|
48
|
+
"hs capture — capture text/clipboard/file into the console drafts inbox",
|
|
49
|
+
"",
|
|
50
|
+
"Usage:",
|
|
51
|
+
' hs capture "<text>" [--title <t>] [--add] [--json]',
|
|
52
|
+
" hs capture --clipboard [--title <t>] [--add] [--json]",
|
|
53
|
+
" hs capture --file <path> [--title <t>] [--add] [--json]",
|
|
54
|
+
"",
|
|
55
|
+
" --add Chain into the previous CLI capture's group (within 2 minutes)",
|
|
56
|
+
" --title Optional capture title",
|
|
57
|
+
" --url Base console URL (default: shared resolver — HEYSPEX_CONSOLE_URL, then paired console_url in ~/.heyspex/credentials.yaml, then the repo discovery file, then http://localhost:3000)",
|
|
58
|
+
" --json Machine-readable result",
|
|
59
|
+
"",
|
|
60
|
+
].join("\n"),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseArgs(argv) {
|
|
65
|
+
const opts = {
|
|
66
|
+
text: null, clipboard: false, file: null,
|
|
67
|
+
title: null, add: false, json: false,
|
|
68
|
+
url: null, // resolved via the shared console-url resolver unless --url overrides
|
|
69
|
+
};
|
|
70
|
+
for (let i = 0; i < argv.length; i++) {
|
|
71
|
+
const a = argv[i];
|
|
72
|
+
if (a === "--help" || a === "-h") return { help: true };
|
|
73
|
+
else if (a === "--clipboard") opts.clipboard = true;
|
|
74
|
+
else if (a === "--file") opts.file = argv[++i];
|
|
75
|
+
else if (a === "--title") opts.title = argv[++i];
|
|
76
|
+
else if (a === "--url") opts.url = argv[++i];
|
|
77
|
+
else if (a === "--add") opts.add = true;
|
|
78
|
+
else if (a === "--json") opts.json = true;
|
|
79
|
+
else if (!a.startsWith("--") && opts.text === null) opts.text = a;
|
|
80
|
+
else return { error: `unknown argument: ${a}` };
|
|
81
|
+
}
|
|
82
|
+
return { opts };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readClipboard() {
|
|
86
|
+
if (process.platform !== "win32") {
|
|
87
|
+
throw new Error("--clipboard is only implemented on Windows for now");
|
|
88
|
+
}
|
|
89
|
+
return execFileSync(
|
|
90
|
+
"powershell.exe",
|
|
91
|
+
["-NoProfile", "-Command", "Get-Clipboard -Raw"],
|
|
92
|
+
{ encoding: "utf8", windowsHide: true },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Same semantics as the companion's CaptureGroupTracker: every capture gets
|
|
97
|
+
* a group id; --add joins the previous group (window permitting). */
|
|
98
|
+
function nextGroup(vaultRoot, chain) {
|
|
99
|
+
const stateDir = path.join(vaultRoot, ".heyspex");
|
|
100
|
+
const stateFile = path.join(stateDir, "cli-capture-group.json");
|
|
101
|
+
let prev = null;
|
|
102
|
+
try {
|
|
103
|
+
prev = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
104
|
+
} catch { /* first run / corrupt — start fresh */ }
|
|
105
|
+
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
let group;
|
|
108
|
+
if (chain && prev?.groupId && now - (prev.at ?? 0) <= CHAIN_WINDOW_MS) {
|
|
109
|
+
group = { groupId: prev.groupId, seq: (prev.seq ?? 1) + 1, at: now };
|
|
110
|
+
} else {
|
|
111
|
+
group = { groupId: randomUUID().replaceAll("-", "").slice(0, 12), seq: 1, at: now };
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
|
|
115
|
+
writeFileSync(stateFile, JSON.stringify(group));
|
|
116
|
+
} catch { /* stateless fallback — capture still goes through */ }
|
|
117
|
+
return group;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** True when `url`'s hostname is a loopback address — localhost, 127.0.0.1,
|
|
121
|
+
* or [::1] (bracketed or not). Non-loopback hosts (a paired cloud console,
|
|
122
|
+
* a LAN address, etc.) never get the hosted-console retry: if a deliberately
|
|
123
|
+
* configured console is unreachable, that's a real outage to surface, not a
|
|
124
|
+
* dead local dev server to route around. */
|
|
125
|
+
function isLoopback(url) {
|
|
126
|
+
try {
|
|
127
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
128
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* POST a capture to `url`, with a local-dead-console fallback ladder:
|
|
136
|
+
* 1. POST to `url` (the resolved console — often the local dev server).
|
|
137
|
+
* 2. If that fails to CONNECT (not an HTTP error) and `url` is loopback,
|
|
138
|
+
* retry once against `hostedUrl` with the identical body/headers/trace id.
|
|
139
|
+
* 3. If the hosted retry also fails to connect, stash the payload to the
|
|
140
|
+
* vault's disk outbox via `stash` and report that instead of throwing.
|
|
141
|
+
*
|
|
142
|
+
* A non-loopback URL that fails to connect is never retried or stashed — the
|
|
143
|
+
* caller configured that console on purpose, so an outage there is reported
|
|
144
|
+
* as-is. An HTTP error response (4xx/5xx) from either console is likewise
|
|
145
|
+
* reported as-is, never retried or stashed — the console IS reachable.
|
|
146
|
+
*
|
|
147
|
+
* `fetchImpl` and `stash` are injectable so tests never open a real socket
|
|
148
|
+
* or touch the real outbox directory.
|
|
149
|
+
*/
|
|
150
|
+
export async function postCaptureWithFallback({
|
|
151
|
+
url,
|
|
152
|
+
payload,
|
|
153
|
+
token,
|
|
154
|
+
vaultRoot,
|
|
155
|
+
fetchImpl = fetch,
|
|
156
|
+
hostedUrl = DEFAULT_CONSOLE_URL,
|
|
157
|
+
stash = stashCaptureArgs,
|
|
158
|
+
}) {
|
|
159
|
+
const traceId = payload.trace_id;
|
|
160
|
+
const body = JSON.stringify({ traceId, payload, errors: [] });
|
|
161
|
+
const headers = {
|
|
162
|
+
"content-type": "application/json",
|
|
163
|
+
"x-heyspex-trace-id": traceId,
|
|
164
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const post = (targetUrl) =>
|
|
168
|
+
fetchImpl(`${targetUrl.replace(/\/$/, "")}/api/drafts`, { method: "POST", headers, body });
|
|
169
|
+
|
|
170
|
+
let res;
|
|
171
|
+
let consoleUrl = url;
|
|
172
|
+
try {
|
|
173
|
+
res = await post(url);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (!isLoopback(url)) {
|
|
176
|
+
return { kind: "connect-error", url, err };
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
res = await post(hostedUrl);
|
|
180
|
+
consoleUrl = hostedUrl;
|
|
181
|
+
} catch (err2) {
|
|
182
|
+
const stashed = stash(vaultRoot, { user_quote: payload.article_text });
|
|
183
|
+
return {
|
|
184
|
+
kind: "stashed",
|
|
185
|
+
files: stashed.map((e) => e.path),
|
|
186
|
+
primaryErr: err,
|
|
187
|
+
hostedErr: err2,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
const errBody = await res.text().catch(() => "");
|
|
194
|
+
return { kind: "http-error", status: res.status, body: errBody, url: consoleUrl };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const result = await res.json().catch(() => ({}));
|
|
198
|
+
return { kind: "ok", consoleUrl, retried: consoleUrl !== url, result };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function main() {
|
|
202
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
203
|
+
if (parsed.help) { usage(); return; }
|
|
204
|
+
if (parsed.error) {
|
|
205
|
+
process.stderr.write(`hs capture: ${parsed.error}\n`);
|
|
206
|
+
process.exit(2);
|
|
207
|
+
}
|
|
208
|
+
const opts = parsed.opts;
|
|
209
|
+
|
|
210
|
+
let content, contentSource;
|
|
211
|
+
if (opts.clipboard) {
|
|
212
|
+
content = readClipboard();
|
|
213
|
+
contentSource = "cli-clipboard";
|
|
214
|
+
} else if (opts.file) {
|
|
215
|
+
// hs.mjs runs this script with cwd=PKG_ROOT (the platform checkout) and
|
|
216
|
+
// forwards the caller's real invocation directory as HS_INVOKED_CWD —
|
|
217
|
+
// resolve the user-typed --file against THAT, not this process's own
|
|
218
|
+
// (wrong) cwd, so `hs capture --file <relative>` finds the file where
|
|
219
|
+
// the caller stood rather than reporting it missing at a platform path.
|
|
220
|
+
const invokedCwd = process.env.HS_INVOKED_CWD?.trim() || process.cwd();
|
|
221
|
+
const p = path.resolve(invokedCwd, opts.file);
|
|
222
|
+
if (!existsSync(p)) {
|
|
223
|
+
process.stderr.write(`hs capture: file not found: ${p}\n`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
content = readFileSync(p, "utf8");
|
|
227
|
+
contentSource = "cli-file";
|
|
228
|
+
opts.title ??= path.basename(p);
|
|
229
|
+
} else if (opts.text) {
|
|
230
|
+
content = opts.text;
|
|
231
|
+
contentSource = "cli";
|
|
232
|
+
} else {
|
|
233
|
+
usage();
|
|
234
|
+
process.exit(2);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!content || !content.trim()) {
|
|
238
|
+
process.stderr.write("hs capture: nothing to capture (empty content)\n");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const vaultRoot = process.env.HS_VAULT_ROOT ?? process.cwd();
|
|
243
|
+
opts.url ??= resolveConsoleUrl({ repoRoot: vaultRoot });
|
|
244
|
+
const group = nextGroup(vaultRoot, opts.add);
|
|
245
|
+
const traceId = `cli-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
246
|
+
|
|
247
|
+
const payload = {
|
|
248
|
+
schema: "heyspex.capture/0.1",
|
|
249
|
+
trace_id: traceId,
|
|
250
|
+
tier: 3,
|
|
251
|
+
captured_at: new Date().toISOString(),
|
|
252
|
+
capture_channel: "cli",
|
|
253
|
+
source: contentSource,
|
|
254
|
+
content_source: contentSource,
|
|
255
|
+
title: opts.title,
|
|
256
|
+
article_text: content,
|
|
257
|
+
degraded: null,
|
|
258
|
+
errors: null,
|
|
259
|
+
capture_group_id: group.groupId,
|
|
260
|
+
capture_group_seq: group.seq,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const mcpToken = readCredentialsMcpToken();
|
|
264
|
+
const outcome = await postCaptureWithFallback({
|
|
265
|
+
url: opts.url,
|
|
266
|
+
payload,
|
|
267
|
+
token: mcpToken,
|
|
268
|
+
vaultRoot,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (outcome.kind === "connect-error") {
|
|
272
|
+
process.stderr.write(
|
|
273
|
+
`hs capture: console unreachable at ${outcome.url} (${outcome.err.cause?.code ?? outcome.err.message})\n`,
|
|
274
|
+
);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (outcome.kind === "http-error") {
|
|
279
|
+
process.stderr.write(
|
|
280
|
+
`hs capture: POST /api/drafts failed ${outcome.status}: ${outcome.body.slice(0, 300)}\n`,
|
|
281
|
+
);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (outcome.kind === "stashed") {
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
process.stdout.write(JSON.stringify({
|
|
288
|
+
ok: true, stashed: true, traceId, files: outcome.files,
|
|
289
|
+
}, null, 2) + "\n");
|
|
290
|
+
} else {
|
|
291
|
+
process.stdout.write(
|
|
292
|
+
`hs capture: local and hosted consoles both unreachable — saved to disk outbox instead\n` +
|
|
293
|
+
outcome.files.map((f) => ` ${f}`).join("\n") + "\n",
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// outcome.kind === "ok"
|
|
300
|
+
if (opts.json) {
|
|
301
|
+
process.stdout.write(JSON.stringify({
|
|
302
|
+
ok: true, traceId, consoleUrl: outcome.consoleUrl,
|
|
303
|
+
group: group.groupId, seq: group.seq,
|
|
304
|
+
chars: content.length, response: outcome.result,
|
|
305
|
+
}, null, 2) + "\n");
|
|
306
|
+
} else {
|
|
307
|
+
const chained = group.seq > 1 ? ` (group ${group.groupId} · #${group.seq})` : "";
|
|
308
|
+
const via = outcome.retried
|
|
309
|
+
? ` — local console unreachable, sent to ${outcome.consoleUrl} instead`
|
|
310
|
+
: "";
|
|
311
|
+
process.stdout.write(
|
|
312
|
+
`captured ${content.length} chars → drafts${chained}${via}\n` +
|
|
313
|
+
` add another: hs capture --clipboard --add (within 2 minutes)\n`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Guard so this module can be `import`ed (e.g. by hs-capture-cli.test.mjs)
|
|
319
|
+
// without running main() — bin/hs.mjs spawns this file as a child process
|
|
320
|
+
// with the file path as argv[1], so this only fires under that spawn.
|
|
321
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
322
|
+
main().catch((err) => {
|
|
323
|
+
process.stderr.write(`hs capture: ${err.message}\n`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs classifier stats — read-only view of HeySpex Intent Classifier decisions.
|
|
4
|
+
* Dispatched from bin/hs.mjs. Reads .heyspex/intent-classifier-history.jsonl (the
|
|
5
|
+
* append-only decision log the classifier hooks write) and prints counts per behavior
|
|
6
|
+
* and layer plus the most recent fires. Pure read; never writes, never networks.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* hs classifier stats [--json] [--recent N]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import process from 'node:process';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
|
|
19
|
+
const HELP = `hs classifier stats — HeySpex Intent Classifier decision stats (read-only)
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
hs classifier stats [--json] [--recent N]
|
|
23
|
+
|
|
24
|
+
Reads .heyspex/intent-classifier-history.jsonl. Shows totals per behavior + layer
|
|
25
|
+
(lexical vs semantic) + decision (fire / self_check_injected / below_threshold /
|
|
26
|
+
error), and the N most recent fires (default 10). self_check_injected is the default
|
|
27
|
+
semantic-layer signal: an OFFER count (how often the deterministic self-check block was
|
|
28
|
+
injected), not an agent-fire count — the agent's own judgment decides whether to act.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
function historyPath() {
|
|
32
|
+
const root = process.env.HS_VAULT_ROOT || process.env.HEYSPEX_VAULT_ROOT || REPO_ROOT;
|
|
33
|
+
return path.join(root, '.heyspex', 'intent-classifier-history.jsonl');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readRows() {
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = fs.readFileSync(historyPath(), 'utf8');
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const rows = [];
|
|
44
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
45
|
+
const t = line.trim();
|
|
46
|
+
if (!t) continue;
|
|
47
|
+
try {
|
|
48
|
+
const r = JSON.parse(t);
|
|
49
|
+
if (r && r.event_kind === 'intent_classifier.decision') rows.push(r);
|
|
50
|
+
} catch {
|
|
51
|
+
/* skip a corrupt line — never throw */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function main(argv) {
|
|
58
|
+
const args = argv[0] === 'classifier' ? argv.slice(1) : argv.slice();
|
|
59
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
60
|
+
process.stdout.write(HELP);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
const verb = args.find((a) => !a.startsWith('--')) || 'stats';
|
|
64
|
+
if (verb !== 'stats') {
|
|
65
|
+
process.stderr.write(`hs classifier: unknown subcommand \`${verb}\` (expected: stats)\n${HELP}`);
|
|
66
|
+
process.exit(2);
|
|
67
|
+
}
|
|
68
|
+
const asJson = args.includes('--json');
|
|
69
|
+
const recentIdx = args.indexOf('--recent');
|
|
70
|
+
const recentN = recentIdx >= 0 && args[recentIdx + 1] ? Math.max(0, parseInt(args[recentIdx + 1], 10) || 0) : 10;
|
|
71
|
+
|
|
72
|
+
const rows = readRows();
|
|
73
|
+
|
|
74
|
+
// Aggregate: behavior(rule) → { fire, self_check_injected, below_threshold, error, lexical, semantic }.
|
|
75
|
+
// self_check_injected is the DEFAULT semantic-layer signal ([[HS-M4PN-TASK]]): the
|
|
76
|
+
// hook injected a deterministic self-check block and the agent (not a model) decided
|
|
77
|
+
// whether to act. There is no synchronous confidence/verdict, so this is an OFFER
|
|
78
|
+
// count (fire rate of the injection), not a fire count of a behavior. Actual fires by
|
|
79
|
+
// the agent are observable downstream (capture-history, settings-history, defer edits).
|
|
80
|
+
const byBehavior = {};
|
|
81
|
+
const layerTotals = { lexical: 0, semantic: 0 };
|
|
82
|
+
const decisionTotals = { fire: 0, self_check_injected: 0, below_threshold: 0, error: 0 };
|
|
83
|
+
for (const r of rows) {
|
|
84
|
+
const key = r.rule || 'unknown';
|
|
85
|
+
const b = (byBehavior[key] ??= { fire: 0, self_check_injected: 0, below_threshold: 0, error: 0, lexical: 0, semantic: 0 });
|
|
86
|
+
if (r.decision && b[r.decision] !== undefined) b[r.decision] += 1;
|
|
87
|
+
if (r.layer && b[r.layer] !== undefined) b[r.layer] += 1;
|
|
88
|
+
if (r.layer && layerTotals[r.layer] !== undefined) layerTotals[r.layer] += 1;
|
|
89
|
+
if (r.decision && decisionTotals[r.decision] !== undefined) decisionTotals[r.decision] += 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const recentFires = rows
|
|
93
|
+
.filter((r) => r.decision === 'fire')
|
|
94
|
+
.slice(-recentN)
|
|
95
|
+
.reverse()
|
|
96
|
+
.map((r) => ({ timestamp: r.timestamp, behavior: r.rule, layer: r.layer, confidence: r.confidence ?? null }));
|
|
97
|
+
|
|
98
|
+
if (asJson) {
|
|
99
|
+
process.stdout.write(
|
|
100
|
+
JSON.stringify(
|
|
101
|
+
{ total: rows.length, layerTotals, decisionTotals, byBehavior, recentFires },
|
|
102
|
+
null,
|
|
103
|
+
2,
|
|
104
|
+
) + '\n',
|
|
105
|
+
);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const out = [];
|
|
110
|
+
out.push(`HeySpex Intent Classifier — ${rows.length} decision(s) logged`);
|
|
111
|
+
out.push(` layers: lexical ${layerTotals.lexical} · semantic ${layerTotals.semantic}`);
|
|
112
|
+
out.push(` decisions: fire ${decisionTotals.fire} · self_check_injected ${decisionTotals.self_check_injected} · below_threshold ${decisionTotals.below_threshold} · error ${decisionTotals.error}`);
|
|
113
|
+
out.push('');
|
|
114
|
+
out.push(' per behavior:');
|
|
115
|
+
const names = Object.keys(byBehavior).sort();
|
|
116
|
+
if (names.length === 0) {
|
|
117
|
+
out.push(' (no decisions logged yet)');
|
|
118
|
+
} else {
|
|
119
|
+
for (const name of names) {
|
|
120
|
+
const b = byBehavior[name];
|
|
121
|
+
out.push(` ${name.padEnd(26)} fire ${b.fire} · self-check ${b.self_check_injected} · below ${b.below_threshold} · error ${b.error} (lexical ${b.lexical} / semantic ${b.semantic})`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (recentFires.length) {
|
|
125
|
+
out.push('');
|
|
126
|
+
out.push(` recent fires (last ${recentFires.length}):`);
|
|
127
|
+
for (const f of recentFires) {
|
|
128
|
+
const conf = f.confidence !== null ? ` conf=${f.confidence}` : '';
|
|
129
|
+
out.push(` ${f.timestamp} ${String(f.behavior).padEnd(26)} ${f.layer}${conf}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main(process.argv.slice(2));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `hs companion ...` — the desktop companion's CLI face.
|
|
4
|
+
*
|
|
5
|
+
* start|stop|status|build tray-host lifecycle → companion/start.ps1 (PowerShell 7),
|
|
6
|
+
* which launches companion/src/HeySpex.Companion.Host directly
|
|
7
|
+
* (the versioned-folder indirection was retired 2026-08-12).
|
|
8
|
+
* settings list|get|set every Companion setting, via the console's
|
|
9
|
+
* GET/PUT /api/companion/settings ([[HS-UDH1]]); plus the
|
|
10
|
+
* short aliases (mute, volume, orb, wake, ...).
|
|
11
|
+
*
|
|
12
|
+
* Parsing, the key/alias tables, value coercion, and the request shape live in
|
|
13
|
+
* scripts/lib/hs-companion-settings.mjs so they are unit-testable without a
|
|
14
|
+
* process; this file only spawns, prints, and exits.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import process from "node:process";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
makeCompanionConsoleFetch,
|
|
24
|
+
parseCompanionArgs,
|
|
25
|
+
renderHelp,
|
|
26
|
+
runCompanionSettings,
|
|
27
|
+
} from "./lib/hs-companion-settings.mjs";
|
|
28
|
+
|
|
29
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
30
|
+
const START_PS1 = path.join(PKG_ROOT, "companion", "start.ps1");
|
|
31
|
+
|
|
32
|
+
function resolvePwsh() {
|
|
33
|
+
return process.env.HEYSPEX_COMPANION_POWERSHELL?.trim() || "pwsh";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function runHost(sub, rest) {
|
|
37
|
+
if (!existsSync(START_PS1)) {
|
|
38
|
+
process.stderr.write(`hs companion: start script not found at ${START_PS1}\n`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const pwshArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", START_PS1, "-Action", sub];
|
|
43
|
+
if (sub === "start" && rest.includes("--build")) {
|
|
44
|
+
pwshArgs.push("-Build");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const child = spawn(resolvePwsh(), pwshArgs, { cwd: PKG_ROOT, stdio: "inherit" });
|
|
48
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : code ?? 0));
|
|
49
|
+
child.on("error", (err) => {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`hs companion: failed to run ${resolvePwsh()}: ${err.message}\n` +
|
|
52
|
+
"Install PowerShell 7 (pwsh) or set HEYSPEX_COMPANION_POWERSHELL to its path.\n",
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main(argv) {
|
|
59
|
+
const parsed = parseCompanionArgs(argv);
|
|
60
|
+
|
|
61
|
+
if (parsed.error) {
|
|
62
|
+
process.stderr.write(`hs companion: ${parsed.error}\n\n${renderHelp("companion")}\n`);
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
if (parsed.kind === "help") {
|
|
66
|
+
process.stdout.write(`${renderHelp(parsed.topic)}\n`);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
if (parsed.kind === "host") {
|
|
70
|
+
runHost(parsed.sub, parsed.rest);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// settings — resolved console (or --url) + the shared bearer ladder.
|
|
75
|
+
const consoleFetch = makeCompanionConsoleFetch({ url: parsed.url, repoRoot: process.env.HS_VAULT_ROOT });
|
|
76
|
+
const result = await runCompanionSettings(parsed, { consoleFetch });
|
|
77
|
+
if (result.ok) {
|
|
78
|
+
process.stdout.write(`${result.stdout}\n`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
process.stderr.write(`hs companion: ${result.stderr}\n`);
|
|
82
|
+
process.exit(result.exitCode);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
86
|
+
process.stderr.write(`hs companion: ${err.message}\n`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|