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
|
@@ -1,307 +1,386 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* hs-source-push.mjs — `hs source push`: credential-hang prevention and
|
|
4
|
-
* destructive-verb refusal (HS-XFB3-CWPX, epic
|
|
5
|
-
* [[HS-Q0K8-CWPX-EPIC-spexsource-local-git-preflight-commit]]).
|
|
6
|
-
*
|
|
7
|
-
* Fixes the exact failure mode in
|
|
8
|
-
* [[specs/memory/lessons/an-agent-shell-git-push-hangs-on-the.md]]: `git
|
|
9
|
-
* push` over HTTPS on Windows falling through to the invisible GUI
|
|
10
|
-
* credential-manager prompt and hanging forever with no output. The fix is
|
|
11
|
-
* BOTH of: (1) substituting `gh`'s own credential helper
|
|
12
|
-
* (`-c credential.helper= -c "credential.helper=!gh auth git-credential"`)
|
|
13
|
-
* and (2) `GIT_TERMINAL_PROMPT=0` in the child env — the lesson is explicit
|
|
14
|
-
* that #2 alone does not stop the GUI-prompt path, so both are always
|
|
15
|
-
* applied together, on every invocation, unconditionally.
|
|
16
|
-
*
|
|
17
|
-
* Force-push and protected-ref refusal DEFERS to the existing destructive-git
|
|
18
|
-
* guardrail (`classifyGitDestructive` in
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs-source-push.mjs — `hs source push`: credential-hang prevention and
|
|
4
|
+
* destructive-verb refusal (HS-XFB3-CWPX, epic
|
|
5
|
+
* [[HS-Q0K8-CWPX-EPIC-spexsource-local-git-preflight-commit]]).
|
|
6
|
+
*
|
|
7
|
+
* Fixes the exact failure mode in
|
|
8
|
+
* [[specs/memory/lessons/an-agent-shell-git-push-hangs-on-the.md]]: `git
|
|
9
|
+
* push` over HTTPS on Windows falling through to the invisible GUI
|
|
10
|
+
* credential-manager prompt and hanging forever with no output. The fix is
|
|
11
|
+
* BOTH of: (1) substituting `gh`'s own credential helper
|
|
12
|
+
* (`-c credential.helper= -c "credential.helper=!gh auth git-credential"`)
|
|
13
|
+
* and (2) `GIT_TERMINAL_PROMPT=0` in the child env — the lesson is explicit
|
|
14
|
+
* that #2 alone does not stop the GUI-prompt path, so both are always
|
|
15
|
+
* applied together, on every invocation, unconditionally.
|
|
16
|
+
*
|
|
17
|
+
* Force-push and protected-ref refusal DEFERS to the existing destructive-git
|
|
18
|
+
* guardrail (`classifyGitDestructive` in
|
|
19
|
+
* spexharness/src/gates/git-stash-gate.decide.mjs — relocated out of
|
|
20
|
+
* scripts/claude-hooks/git-stash-gate.mjs by HS-IQBF; that hook script is now
|
|
21
|
+
* a generated shell over the spexharness/src/gates/git-stash-gate.mjs port)
|
|
22
|
+
* and the protected-ref list already used by scripts/hs-night-branch.mjs —
|
|
23
|
+
* this module does not ship a second, competing allowlist.
|
|
24
|
+
*
|
|
25
|
+
* Both of those modules (plus git-stash-gate.decide.mjs's own hook-chain
|
|
26
|
+
* dependencies) are dev-only — never in the package's `files`
|
|
27
|
+
* (RUNTIME_FILES / `package.json`) — so a static import of either one crashed
|
|
28
|
+
* EVERY `hs source push` invocation with a raw `ERR_MODULE_NOT_FOUND` on a
|
|
29
|
+
* published npm install. They are loaded via a guarded dynamic import instead
|
|
30
|
+
* (see `loadDestructiveGitGuard` below); `pushBranch` needs them on every
|
|
31
|
+
* call (the force-push/protected-ref check is not behind an opt-in flag), so
|
|
32
|
+
* a resolution failure refuses the ENTIRE push — zero `git push` subprocess
|
|
33
|
+
* calls — with a clear message, never a stack trace.
|
|
34
|
+
*/
|
|
35
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
|
|
39
|
+
import { scanPaths } from './hs-source-secret-scan.mjs';
|
|
40
|
+
|
|
41
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
export const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
43
|
+
|
|
44
|
+
export const DEFAULT_TIMEOUT_MS = 60_000;
|
|
45
|
+
|
|
46
|
+
export const DESTRUCTIVE_GIT_GUARD_UNAVAILABLE_MESSAGE =
|
|
47
|
+
'hs source push: refusing — the destructive-git safety check ' +
|
|
48
|
+
'(scripts/hs-night-branch.mjs, scripts/claude-hooks/git-stash-gate.mjs) is not available on ' +
|
|
49
|
+
'this install. Those modules ship only inside a full source checkout of the HeySpex repo, not ' +
|
|
50
|
+
'a published npm install, and `hs source push` requires them to classify force-push / ' +
|
|
51
|
+
'protected-ref requests before it ever touches git — so it refuses rather than pushing ' +
|
|
52
|
+
'without that check.';
|
|
53
|
+
|
|
54
|
+
let destructiveGitGuardPromise;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Guarded, on-demand, cached load of the two dev-only modules `pushBranch`
|
|
58
|
+
* depends on for every call. Never throws — a resolution failure (module
|
|
59
|
+
* absent, as on a published install) comes back as `{ ok: false }` so the
|
|
60
|
+
* caller can refuse gracefully instead of letting `ERR_MODULE_NOT_FOUND`
|
|
61
|
+
* propagate as an unhandled rejection / stack trace.
|
|
62
|
+
* @returns {Promise<{ ok: true, PROTECTED_REFS: string[], classifyGitDestructive: Function } | { ok: false, error: unknown }>}
|
|
63
|
+
*/
|
|
64
|
+
export function loadDestructiveGitGuard() {
|
|
65
|
+
if (!destructiveGitGuardPromise) {
|
|
66
|
+
destructiveGitGuardPromise = Promise.all([
|
|
67
|
+
import('../hs-night-branch.mjs'),
|
|
68
|
+
import('../../spexharness/src/gates/git-stash-gate.decide.mjs'),
|
|
69
|
+
]).then(
|
|
70
|
+
([nightBranch, stashGate]) => ({
|
|
71
|
+
ok: true,
|
|
72
|
+
PROTECTED_REFS: nightBranch.PROTECTED_REFS,
|
|
73
|
+
classifyGitDestructive: stashGate.classifyGitDestructive,
|
|
74
|
+
}),
|
|
75
|
+
(error) => ({ ok: false, error }),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return destructiveGitGuardPromise;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pushes `branch` to `remote` through the credential-hang-safe invocation,
|
|
83
|
+
* refusing outright (zero subprocess calls) on a force-push flag or a
|
|
84
|
+
* protected-ref target, and killing the child if it exceeds `timeoutMs`.
|
|
85
|
+
*
|
|
86
|
+
* @param {{
|
|
87
|
+
* remote?: string,
|
|
88
|
+
* branch: string,
|
|
89
|
+
* extraArgs?: string[],
|
|
90
|
+
* timeoutMs?: number,
|
|
91
|
+
* cwd?: string,
|
|
92
|
+
* spawnFn?: typeof spawn,
|
|
93
|
+
* preScan?: (ctx: { remote: string, branch: string, cwd: string }) =>
|
|
94
|
+
* ({ ok: boolean, message?: string } | null | undefined),
|
|
95
|
+
* }} opts
|
|
96
|
+
* @returns {Promise<{ ok: boolean, code: string, message?: string }>}
|
|
97
|
+
*/
|
|
98
|
+
export async function pushBranch({
|
|
99
|
+
remote = 'origin',
|
|
100
|
+
branch,
|
|
101
|
+
extraArgs = [],
|
|
102
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
103
|
+
cwd = process.cwd(),
|
|
104
|
+
spawnFn = spawn,
|
|
105
|
+
preScan = null,
|
|
106
|
+
} = {}) {
|
|
107
|
+
if (!branch || typeof branch !== 'string' || branch.trim() === '') {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
code: 'NO_BRANCH',
|
|
111
|
+
message: 'hs source push: a branch name is required (e.g. `hs source push origin my-branch`).',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- destructive-git guard load — every push needs it, not just an
|
|
116
|
+
// opt-in flag, so a resolution failure (module absent, as on a published
|
|
117
|
+
// install) refuses the entire push before any git subprocess is spawned ----
|
|
118
|
+
const guard = await loadDestructiveGitGuard();
|
|
119
|
+
if (!guard.ok) {
|
|
120
|
+
return { ok: false, code: 'PACKAGE_INCOMPLETE', message: DESTRUCTIVE_GIT_GUARD_UNAVAILABLE_MESSAGE };
|
|
121
|
+
}
|
|
122
|
+
const { PROTECTED_REFS, classifyGitDestructive } = guard;
|
|
123
|
+
|
|
124
|
+
// ---- protected-ref refusal — reuses hs-night-branch.mjs's own list ----
|
|
125
|
+
if (PROTECTED_REFS.includes(branch)) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
code: 'PROTECTED_REF',
|
|
129
|
+
message: `hs source push: refusing to push to protected ref \`${branch}\` (${PROTECTED_REFS.join(', ')}). This is never allowed through hs source push.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---- force-push refusal — defers to the destructive-git guardrail's
|
|
134
|
+
// own classifier rather than a second, competing allowlist ----
|
|
135
|
+
const syntheticCommand = ['git', 'push', ...extraArgs, remote, branch].join(' ');
|
|
136
|
+
if (classifyGitDestructive('Bash', { command: syntheticCommand }) === 'gated') {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
code: 'FORCE_REFUSED',
|
|
140
|
+
message:
|
|
141
|
+
'hs source push: refusing — this invocation is classified as a gated destructive git action (force-push or similar) by the existing destructive-git guardrail. Force-push is never allowed through hs source push; use the guardrail\'s own owner-authorized path if this is truly intended.',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---- secret-scan extension point (implemented by sibling task
|
|
146
|
+
// HS-5NXX-CWPX; inert here — a caller that passes no preScan gets no
|
|
147
|
+
// scanning, exactly today's behavior) ----
|
|
148
|
+
if (typeof preScan === 'function') {
|
|
149
|
+
let scanResult;
|
|
150
|
+
try {
|
|
151
|
+
scanResult = preScan({ remote, branch, cwd });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
code: 'PRESCAN_ERROR',
|
|
156
|
+
message: `hs source push: the preScan hook threw — ${err instanceof Error ? err.message : String(err)}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (scanResult && scanResult.ok === false) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
code: 'PRESCAN_BLOCKED',
|
|
163
|
+
message: scanResult.message || 'hs source push: blocked by preScan (likely secret detected).',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return new Promise((resolve) => {
|
|
169
|
+
// ---- the actual push: credential-helper override + GIT_TERMINAL_PROMPT=0,
|
|
170
|
+
// both together, unconditionally, per the recorded lesson ----
|
|
171
|
+
const argv = [
|
|
172
|
+
'-c',
|
|
173
|
+
'credential.helper=',
|
|
174
|
+
'-c',
|
|
175
|
+
'credential.helper=!gh auth git-credential',
|
|
176
|
+
'push',
|
|
177
|
+
...extraArgs,
|
|
178
|
+
remote,
|
|
179
|
+
branch,
|
|
180
|
+
];
|
|
181
|
+
const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
182
|
+
|
|
183
|
+
let settled = false;
|
|
184
|
+
let timedOut = false;
|
|
185
|
+
|
|
186
|
+
const child = spawnFn('git', argv, { cwd, env, stdio: 'inherit' });
|
|
187
|
+
|
|
188
|
+
// Deliberately NOT unref'd: the timer is always cleared the moment the
|
|
189
|
+
// child exits/errors (see finish() below), so it never outlives the
|
|
190
|
+
// child in practice — and staying ref'd is what lets a hung child
|
|
191
|
+
// actually be observed and killed rather than letting the process (or
|
|
192
|
+
// a test's event loop) end early while the timer is still outstanding.
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
timedOut = true;
|
|
195
|
+
try {
|
|
196
|
+
child.kill();
|
|
197
|
+
} catch {
|
|
198
|
+
/* best effort — the exit/error handler below still resolves */
|
|
199
|
+
}
|
|
200
|
+
}, timeoutMs);
|
|
201
|
+
|
|
202
|
+
const finish = (result) => {
|
|
203
|
+
if (settled) return;
|
|
204
|
+
settled = true;
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
resolve(result);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
child.on('error', (err) => {
|
|
210
|
+
finish({ ok: false, code: 'SPAWN_ERROR', message: `hs source push: failed to run git — ${err.message}` });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
child.on('exit', (code, signal) => {
|
|
214
|
+
if (timedOut) {
|
|
215
|
+
finish({
|
|
216
|
+
ok: false,
|
|
217
|
+
code: 'TIMEOUT',
|
|
218
|
+
message: `hs source push: push exceeded the ${timeoutMs}ms wall-clock timeout and was killed. This is a HUNG PUSH (most likely the Windows credential-manager GUI prompt — [[specs/memory/lessons/an-agent-shell-git-push-hangs-on-the.md]]), not a normal git error. Re-check \`gh auth status\` and retry.`,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (code === 0) {
|
|
223
|
+
finish({ ok: true, code: 'OK' });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
finish({
|
|
227
|
+
ok: false,
|
|
228
|
+
code: 'GIT_EXIT_NONZERO',
|
|
229
|
+
message: `hs source push: git push exited with code ${code}${signal ? ` (signal ${signal})` : ''}.`,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Lists the paths touched by the commit(s) this push is about to send —
|
|
237
|
+
* the diff between the remote-tracking ref and HEAD when one exists, or
|
|
238
|
+
* just HEAD's own files for a branch never pushed before. Read-only: only
|
|
239
|
+
* `git rev-parse`/`git diff`/`git show --name-only` are ever run. Injectable
|
|
240
|
+
* `run` so tests never touch a real repo.
|
|
241
|
+
* @param {{ remote: string, branch: string, cwd: string, run?: (cmd:string,args:string[],opts:object)=>{status:number,stdout?:string} }} args
|
|
242
|
+
* @returns {string[]}
|
|
243
|
+
*/
|
|
244
|
+
export function pathsInOutgoingCommits({ remote, branch, cwd, run = defaultDiffRun }) {
|
|
245
|
+
const remoteRef = `${remote}/${branch}`;
|
|
246
|
+
const check = run('git', ['rev-parse', '--verify', '--quiet', remoteRef], { cwd });
|
|
247
|
+
const hasRemoteRef = check.status === 0;
|
|
248
|
+
const diffArgs = hasRemoteRef
|
|
249
|
+
? ['diff', '--name-only', `${remoteRef}..HEAD`]
|
|
250
|
+
: ['show', '--name-only', '--pretty=format:', 'HEAD'];
|
|
251
|
+
const result = run('git', diffArgs, { cwd });
|
|
252
|
+
if (result.status !== 0) return [];
|
|
253
|
+
return String(result.stdout ?? '')
|
|
254
|
+
.split(/\r?\n/)
|
|
255
|
+
.map((l) => l.trim())
|
|
256
|
+
.filter(Boolean);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function defaultDiffRun(cmd, args, opts = {}) {
|
|
260
|
+
try {
|
|
261
|
+
const stdout = execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], ...opts });
|
|
262
|
+
return { status: 0, stdout };
|
|
263
|
+
} catch (err) {
|
|
264
|
+
return { status: typeof err.status === 'number' ? err.status : 1, stdout: err.stdout ? String(err.stdout) : '' };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Default no-branch resolver. Imported lazily so the (console-HTTP) resolver
|
|
270
|
+
* module graph loads ONLY when no branch was typed — an explicit-branch push
|
|
271
|
+
* keeps its original, minimal import surface.
|
|
272
|
+
*/
|
|
273
|
+
async function lazyResolvePushBranch(opts) {
|
|
274
|
+
const { resolvePushBranch } = await import('./hs-source-branch.mjs');
|
|
275
|
+
return resolvePushBranch(opts);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* CLI entry for `hs source push`. Parses `[remote] <branch> [--force] [-f]
|
|
280
|
+
* [--force-with-lease] [--timeout <ms>]` from argv. Any force flag is
|
|
281
|
+
* forwarded into extraArgs so the guardrail classifier sees it and refuses —
|
|
282
|
+
* never silently dropped.
|
|
283
|
+
*
|
|
284
|
+
* Wires the secret-scan gate (HS-5NXX-CWPX) as the `preScan` passed into
|
|
285
|
+
* `pushBranch`: it lists the paths in the commits about to be pushed and
|
|
286
|
+
* scans them, blocking the push (with zero `git push` subprocess calls) on
|
|
287
|
+
* any finding.
|
|
288
|
+
*
|
|
289
|
+
* @param {string[]} argv (verb-stripped: args after `push`)
|
|
290
|
+
* @param {{
|
|
291
|
+
* cwd?: string,
|
|
292
|
+
* pushBranchFn?: typeof pushBranch,
|
|
293
|
+
* scanPathsFn?: typeof scanPaths,
|
|
294
|
+
* listOutgoingPaths?: typeof pathsInOutgoingCommits,
|
|
295
|
+
* }} [opts]
|
|
296
|
+
* @returns {Promise<number>} process exit code
|
|
297
|
+
*/
|
|
298
|
+
export async function runPushCommand(
|
|
299
|
+
argv,
|
|
300
|
+
{
|
|
301
|
+
cwd = process.cwd(),
|
|
302
|
+
pushBranchFn = pushBranch,
|
|
303
|
+
scanPathsFn = scanPaths,
|
|
304
|
+
listOutgoingPaths = pathsInOutgoingCommits,
|
|
305
|
+
resolvePushBranchFn = lazyResolvePushBranch,
|
|
306
|
+
} = {},
|
|
307
|
+
) {
|
|
308
|
+
const positional = [];
|
|
309
|
+
const extraArgs = [];
|
|
310
|
+
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < argv.length; i++) {
|
|
313
|
+
const a = argv[i];
|
|
314
|
+
if (a === '--timeout' && argv[i + 1] !== undefined) {
|
|
315
|
+
const parsed = Number(argv[i + 1]);
|
|
316
|
+
if (Number.isFinite(parsed) && parsed > 0) timeoutMs = parsed;
|
|
317
|
+
i++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (a === '--force' || a === '-f' || a === '--force-with-lease' || a === '--delete') {
|
|
321
|
+
extraArgs.push(a);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
positional.push(a);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const remote = positional.length > 1 ? positional[0] : 'origin';
|
|
328
|
+
let branch = positional.length > 1 ? positional[1] : positional[0];
|
|
329
|
+
|
|
330
|
+
// No branch typed: ask the ONE shared branch resolver (the same function the
|
|
331
|
+
// cloud-write drain calls) via the console; offline it falls back to the
|
|
332
|
+
// checked-out branch and says so. An explicit branch is never second-guessed.
|
|
333
|
+
if (!branch) {
|
|
334
|
+
const resolved = await resolvePushBranchFn({ cwd });
|
|
335
|
+
if (!resolved.branch) {
|
|
336
|
+
console.error(`hs source push: ${resolved.note}. Pass a branch explicitly.`);
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
if (resolved.note) console.error(`hs source push: ${resolved.note}.`);
|
|
340
|
+
else console.log(`hs source push: target branch \`${resolved.branch}\` (${resolved.source === 'session' ? 'declared session branch' : 'resolver fallback'}).`);
|
|
341
|
+
branch = resolved.branch;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const preScan = ({ remote: r, branch: b, cwd: c }) => {
|
|
345
|
+
let paths;
|
|
346
|
+
try {
|
|
347
|
+
paths = listOutgoingPaths({ remote: r, branch: b, cwd: c });
|
|
348
|
+
} catch {
|
|
349
|
+
// Read-only listing failed (e.g. no commits yet) — nothing to scan,
|
|
350
|
+
// never block the push on a listing failure.
|
|
351
|
+
return { ok: true };
|
|
352
|
+
}
|
|
353
|
+
const findings = scanPathsFn(paths, { cwd: c });
|
|
354
|
+
if (findings.length > 0) {
|
|
355
|
+
const names = findings.map((f) => `${f.path} (${f.rule})`).join(', ');
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
message: `hs source push: secret scan blocked the push — likely secret(s) found: ${names}`,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return { ok: true };
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const result = await pushBranchFn({ remote, branch, extraArgs, timeoutMs, cwd, preScan });
|
|
365
|
+
if (!result.ok) {
|
|
366
|
+
console.error(result.message);
|
|
367
|
+
return 1;
|
|
368
|
+
}
|
|
369
|
+
console.log(`hs source push: pushed ${remote}/${branch}.`);
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ---- direct-invocation entry --------------------------------------------
|
|
374
|
+
|
|
375
|
+
function invokedDirectly() {
|
|
376
|
+
try {
|
|
377
|
+
return path.resolve(fileURLToPath(import.meta.url)) === path.resolve(process.argv[1] ?? '');
|
|
378
|
+
} catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (invokedDirectly()) {
|
|
384
|
+
// argv: [node, script, "push", ...flags] — drop the leading verb token.
|
|
385
|
+
runPushCommand(process.argv.slice(3)).then((code) => process.exit(code));
|
|
386
|
+
}
|