heyspex 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/bin/hs.mjs +931 -98
- package/bin/spexcode.mjs +262 -0
- package/dev-factory/engine/worktree.mjs +740 -0
- package/dev-factory/lib/code-repo-root.mjs +67 -0
- package/dev-factory/lib/context-budget.mjs +84 -0
- package/dev-factory/lib/epic-detect.mjs +190 -0
- package/dev-factory/lib/fleet-queue-file.mjs +846 -0
- package/dev-factory/lib/fleet-queue.mjs +426 -0
- package/dev-factory/lib/fleet-state.mjs +14 -18
- package/dev-factory/lib/model-catalog.mjs +338 -0
- package/dev-factory/lib/seat-context.mjs +199 -0
- package/dev-factory/lib/spec-io.mjs +93 -0
- package/dev-factory/lib/sufficiency-score.mjs +8 -2
- package/dev-factory/lib/worktree-allocator.mjs +744 -0
- package/dev-factory/model-catalog.json +222 -0
- package/dist/cli/hs-check.mjs +1085 -103
- package/dist/cli/hs-claim.mjs +936 -63
- package/dist/cli/hs-connect.mjs +9786 -1910
- package/dist/cli/hs-defer.mjs +5267 -3053
- package/dist/cli/hs-dev.mjs +564 -0
- package/dist/cli/hs-env.mjs +385 -299
- package/dist/cli/hs-index-rebuild.mjs +7129 -0
- package/dist/cli/hs-index-reconcile.mjs +7852 -0
- package/dist/cli/hs-init.mjs +777 -138
- package/dist/cli/hs-install.mjs +6583 -485
- package/dist/cli/hs-join.mjs +10137 -2368
- package/dist/cli/hs-vault-pull.mjs +4659 -5882
- package/dist/cli/hs-vault.mjs +3793 -1537
- package/dist/cli/hs-workspace.mjs +2550 -524
- package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
- package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
- package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
- package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-view.md +26 -0
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
- package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
- package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
- package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
- package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
- package/docs/mcp-client-config.md +259 -6
- package/package.json +231 -124
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
- package/plugins/heyspex/.mcp.json +12 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +62 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +227 -0
- package/plugins/heyspex/hooks/hooks.json +361 -0
- package/plugins/heyspex/mcp.json +13 -0
- package/scripts/check-mcp-freshness.mjs +17 -9
- package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
- package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
- package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
- package/scripts/claude-hooks/_intent-classifier-router.mjs +410 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +351 -0
- package/scripts/claude-hooks/_run.mjs +120 -0
- package/scripts/claude-hooks/_transcript-text.mjs +60 -0
- package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
- package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
- package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
- package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
- package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
- package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
- package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +575 -0
- package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
- package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
- package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
- package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
- package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
- package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
- package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
- package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
- package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
- package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
- package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
- package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
- package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
- package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
- package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +66 -0
- package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
- package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
- package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
- package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +247 -0
- package/scripts/claude-hooks/log-session-activity.mjs +341 -0
- package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
- package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
- package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
- package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
- package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
- package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
- package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
- package/scripts/claude-hooks/research-method-guard.mjs +398 -0
- package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
- package/scripts/claude-hooks/session-end.mjs +449 -0
- package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
- package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
- package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
- package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
- package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +298 -0
- package/scripts/cursor-hooks/manifest.mjs +235 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +268 -0
- package/scripts/hs-auto-resolve.mjs +240 -0
- package/scripts/hs-auto-source.mjs +398 -0
- package/scripts/hs-auto.mjs +134 -0
- package/scripts/hs-capture-cli.mjs +326 -0
- package/scripts/hs-classifier.mjs +136 -0
- package/scripts/hs-companion.mjs +88 -0
- package/scripts/hs-gateway.mjs +115 -0
- package/scripts/hs-mode.mjs +125 -0
- package/scripts/hs-next.mjs +441 -0
- package/scripts/hs-settings.mjs +338 -0
- package/scripts/hs-view.mjs +333 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +270 -0
- package/scripts/install-cursor-hooks.mjs +471 -0
- package/scripts/lib/agent-env-markers.mjs +38 -0
- package/scripts/lib/console-url-resolver.mjs +197 -60
- package/scripts/lib/customer-kit.mjs +886 -0
- package/scripts/lib/dev-mode.mjs +244 -28
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -0
- package/scripts/lib/hs-source-commit.mjs +59 -5
- package/scripts/lib/hs-source-identity.mjs +526 -0
- package/scripts/lib/hs-source-log.mjs +11 -21
- package/scripts/lib/hs-source-push.mjs +107 -58
- package/scripts/lib/hs-worktree-args.mjs +115 -0
- package/scripts/lib/shared-path-helpers.mjs +134 -0
- package/scripts/lib/standalone-doctor.mjs +540 -3
- package/scripts/lib/subagent-git-lock.mjs +109 -0
- package/scripts/lib/template-pin.mjs +34 -0
- package/scripts/provenance-lib.mjs +91 -0
- package/scripts/stamp-provenance-on-edit.mjs +222 -0
- package/scripts/wip-ledger-health.mjs +653 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
- package/spexharness/src/adapters/audit.mjs +197 -0
- package/spexharness/src/adapters/claude-code.mjs +441 -0
- package/spexharness/src/chat/tui/ansi.mjs +448 -0
- package/spexharness/src/chat/tui/palette.mjs +22 -0
- package/spexharness/src/chat/tui/select.mjs +208 -0
- package/spexharness/src/config-root.mjs +54 -0
- package/spexharness/src/context-budget.mjs +141 -0
- package/spexharness/src/dev-server-control.mjs +389 -0
- package/spexharness/src/doctrine/behaviour.mjs +371 -0
- package/spexharness/src/doctrine/corpus.mjs +733 -0
- package/spexharness/src/doctrine/inject.mjs +338 -0
- package/spexharness/src/doctrine/merge.mjs +184 -0
- package/spexharness/src/doctrine/sources.mjs +252 -0
- package/spexharness/src/doctrine/synthesize.mjs +401 -0
- package/spexharness/src/doctrine/vault-source.mjs +574 -0
- package/spexharness/src/doctrine/writer.mjs +176 -0
- package/spexharness/src/events.mjs +84 -0
- package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
- package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
- package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +274 -0
- package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
- package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
- package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
- package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
- package/spexharness/src/gates/git-identity-guard.mjs +61 -0
- package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
- package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
- package/spexharness/src/gates/git-stash-gate.mjs +159 -0
- package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
- package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
- package/spexharness/src/gates/inject-vault-context.mjs +239 -0
- package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
- package/spexharness/src/gates/plan-review-gate.mjs +233 -0
- package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
- package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
- package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
- package/spexharness/src/gates/settings-write-gate.mjs +104 -0
- package/spexharness/src/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +194 -0
- package/spexharness/src/intent-directive.mjs +541 -0
- package/spexharness/src/profile/learn.mjs +551 -0
- package/spexharness/src/profile/proposals.mjs +357 -0
- package/spexharness/src/profile/vault-inclusion.mjs +386 -0
- package/spexharness/src/redact.mjs +151 -0
- package/spexharness/src/rule-table.mjs +819 -0
- package/spexharness/src/seat-context.mjs +122 -0
- package/spexharness/src/session-bootstrap.mjs +512 -0
- package/spexharness/src/session-record.mjs +815 -0
- package/spexharness/src/token-inventory.mjs +207 -0
- package/vendor/heyspex-mcp/account-switch.mjs +95 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
- package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
- package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +46 -5
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +80 -1
- package/vendor/heyspex-mcp/capture-focus.mjs +1035 -29
- package/vendor/heyspex-mcp/capture-outbox.mjs +5 -1
- package/vendor/heyspex-mcp/capture-tools.mjs +1219 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1124 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2076 -0
- package/vendor/heyspex-mcp/cli.mjs +201 -15524
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +412 -0
- package/vendor/heyspex-mcp/config-seed.yaml +15 -0
- package/vendor/heyspex-mcp/config-settings.mjs +290 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +657 -16
- package/vendor/heyspex-mcp/console-transport.mjs +166 -0
- package/vendor/heyspex-mcp/credentials.mjs +584 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
- package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
- package/vendor/heyspex-mcp/design-tools.mjs +4 -1
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
- package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
- package/vendor/heyspex-mcp/dev-tools.mjs +124 -0
- package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
- package/vendor/heyspex-mcp/events-tools.mjs +207 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +688 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +69 -14
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +59 -0
- package/vendor/heyspex-mcp/http-transport.mjs +300 -0
- package/vendor/heyspex-mcp/list-tools.mjs +257 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +94 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/occupied-keys.mjs +141 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3360 -0
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
- package/vendor/heyspex-mcp/package-root.mjs +32 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
- package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
- package/vendor/heyspex-mcp/plan-push.mjs +906 -21
- package/vendor/heyspex-mcp/plan-review.mjs +124 -30
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +325 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +540 -351
- package/vendor/heyspex-mcp/promote-capture.mjs +386 -69
- package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
- package/vendor/heyspex-mcp/promote-research.mjs +660 -53
- package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
- package/vendor/heyspex-mcp/prompts.mjs +1460 -0
- package/vendor/heyspex-mcp/read-path.mjs +29 -9
- package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
- package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
- package/vendor/heyspex-mcp/register-spec.mjs +266 -54
- package/vendor/heyspex-mcp/request-context.mjs +59 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +623 -0
- package/vendor/heyspex-mcp/research-tools.mjs +4 -4
- package/vendor/heyspex-mcp/response-budget.mjs +348 -0
- package/vendor/heyspex-mcp/review-focus.mjs +25 -1
- package/vendor/heyspex-mcp/search-specs.mjs +280 -0
- package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
- package/vendor/heyspex-mcp/session-owner.mjs +108 -0
- package/vendor/heyspex-mcp/session-tools.mjs +152 -0
- package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +446 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +301 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +132 -6
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +224 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +108 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2813 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +639 -0
- package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
- package/vendor/heyspex-mcp/vault-io.mjs +724 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +366 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +91 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/hs-worktree.mjs — `hs worktree list|alloc|release|reap|rebind`.
|
|
3
|
+
// Task: HS-V9XH (epic HS-03DO "Claim-bound worktree isolation for parallel agent
|
|
4
|
+
// sessions"). Thin shell: parse argv (scripts/lib/hs-worktree-args.mjs), then call
|
|
5
|
+
// dev-factory/lib/worktree-allocator.mjs's exported functions IN-PROCESS — never
|
|
6
|
+
// shells out to the allocator's own CLI mode, so there is exactly one process per
|
|
7
|
+
// invocation and JSON output never has to round-trip through a child's stdout.
|
|
8
|
+
//
|
|
9
|
+
// Exit codes:
|
|
10
|
+
// 0 ok
|
|
11
|
+
// 1 usage (unknown verb/flag — from the parser)
|
|
12
|
+
// 2 check-failed — ONLY `reap --expect-removed` that removed nothing
|
|
13
|
+
// 11 bad-arg (missing required flag / value-flag with no value — from the parser)
|
|
14
|
+
// 13 write-failed (the allocator threw on a git/fs write — alloc or rebind)
|
|
15
|
+
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
import { parseWorktreeArgs } from "./lib/hs-worktree-args.mjs";
|
|
20
|
+
import {
|
|
21
|
+
makeCtx,
|
|
22
|
+
DEFAULT_DEPS,
|
|
23
|
+
keyInfoFor,
|
|
24
|
+
worktreePathFor,
|
|
25
|
+
allocateForClaim,
|
|
26
|
+
markReleased,
|
|
27
|
+
rebind,
|
|
28
|
+
listWorktrees,
|
|
29
|
+
reapWorktrees,
|
|
30
|
+
} from "../dev-factory/lib/worktree-allocator.mjs";
|
|
31
|
+
|
|
32
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
34
|
+
|
|
35
|
+
const EXIT_OK = 0;
|
|
36
|
+
const EXIT_CHECK_FAILED = 2;
|
|
37
|
+
const EXIT_WRITE_FAILED = 13;
|
|
38
|
+
|
|
39
|
+
// ── human-output formatting ─────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function formatAge(ms) {
|
|
42
|
+
if (ms == null || !Number.isFinite(ms) || ms < 0) return "-";
|
|
43
|
+
const s = Math.floor(ms / 1000);
|
|
44
|
+
if (s < 60) return `${s}s`;
|
|
45
|
+
const m = Math.floor(s / 60);
|
|
46
|
+
if (m < 60) return `${m}m`;
|
|
47
|
+
const h = Math.floor(m / 60);
|
|
48
|
+
if (h < 24) return `${h}h`;
|
|
49
|
+
const d = Math.floor(h / 24);
|
|
50
|
+
return `${d}d`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function formatPid(pid, alive) {
|
|
54
|
+
if (pid == null) return "-";
|
|
55
|
+
return `${pid}(${alive ? "alive" : "dead"})`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderTable(rows) {
|
|
59
|
+
const headers = ["PATH", "BRANCH", "KIND", "SPEC", "SESSION", "PID", "AGE", "DIRTY", "RELEASED"];
|
|
60
|
+
const data = rows.map((w) => [
|
|
61
|
+
w.path,
|
|
62
|
+
w.branch || "(detached)",
|
|
63
|
+
w.kind,
|
|
64
|
+
w.specId || "-",
|
|
65
|
+
w.sessionId || "-",
|
|
66
|
+
formatPid(w.pid, w.pidAlive),
|
|
67
|
+
formatAge(w.ageMs),
|
|
68
|
+
w.dirtyCount == null ? "?" : String(w.dirtyCount),
|
|
69
|
+
w.releasedAt ? "yes" : "-",
|
|
70
|
+
]);
|
|
71
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...data.map((r) => String(r[i]).length)));
|
|
72
|
+
const line = (cols) => cols.map((c, i) => String(c).padEnd(widths[i])).join(" ").trimEnd();
|
|
73
|
+
return [line(headers), ...data.map(line)];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── agent identity ──────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Session id default per task brief: env only, never the MCP's fuller
|
|
80
|
+
* detectAgentIdentity precedence (Cursor/CI vars, etc) — a --session flag
|
|
81
|
+
* always wins.
|
|
82
|
+
*/
|
|
83
|
+
function resolveSessionId(flags) {
|
|
84
|
+
return (
|
|
85
|
+
flags.session
|
|
86
|
+
|| process.env.CLAUDE_CODE_SESSION_ID
|
|
87
|
+
|| process.env.HEYSPEX_SESSION_ID
|
|
88
|
+
|| "cli"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Agent default per task brief: the same detection the MCP uses
|
|
94
|
+
* (vendor/heyspex-mcp/mcp-config.mjs detectAgentIdentity) when it is
|
|
95
|
+
* trivially importable, else HEYSPEX_AGENT env or "cli". A --agent flag
|
|
96
|
+
* always wins over both.
|
|
97
|
+
*/
|
|
98
|
+
async function resolveAgent(flags, sessionId) {
|
|
99
|
+
if (flags.agent) return flags.agent;
|
|
100
|
+
try {
|
|
101
|
+
const mod = await import("../vendor/heyspex-mcp/mcp-config.mjs");
|
|
102
|
+
if (typeof mod.detectAgentIdentity === "function") {
|
|
103
|
+
const identity = mod.detectAgentIdentity(undefined, sessionId);
|
|
104
|
+
if (identity?.agent) return identity.agent;
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Not importable in this install shape (e.g. a published package without
|
|
108
|
+
// vendor/heyspex-mcp) — fall back below.
|
|
109
|
+
}
|
|
110
|
+
return process.env.HEYSPEX_AGENT || "cli";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── main ─────────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
async function main() {
|
|
116
|
+
const parsed = parseWorktreeArgs(process.argv.slice(2));
|
|
117
|
+
if (!parsed.ok) {
|
|
118
|
+
process.stderr.write(`hs worktree: ${parsed.message}\n`);
|
|
119
|
+
process.exitCode = parsed.code;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const { verb, flags } = parsed;
|
|
123
|
+
const ctx = makeCtx({ repo: REPO_ROOT, worktreeRoot: flags.root || undefined });
|
|
124
|
+
|
|
125
|
+
const emit = (obj, lines) => {
|
|
126
|
+
if (flags.json) process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`);
|
|
127
|
+
else for (const l of lines) process.stdout.write(`${l}\n`);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (verb === "list") {
|
|
131
|
+
const worktrees = listWorktrees(ctx);
|
|
132
|
+
emit({ worktrees }, renderTable(worktrees));
|
|
133
|
+
process.exitCode = EXIT_OK;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (verb === "alloc") {
|
|
138
|
+
const info = keyInfoFor(flags.spec);
|
|
139
|
+
if (flags.dryRun) {
|
|
140
|
+
const wtPath = worktreePathFor(ctx, flags.spec);
|
|
141
|
+
const baseSha = (DEFAULT_DEPS.git(ctx, ctx.repo, "rev-parse", "HEAD").stdout || "").trim() || null;
|
|
142
|
+
emit(
|
|
143
|
+
{ dryRun: true, path: wtPath, branch: info.branch, baseSha },
|
|
144
|
+
[`would allocate ${wtPath} on ${info.branch} from ${baseSha ?? "(unknown)"}`],
|
|
145
|
+
);
|
|
146
|
+
process.exitCode = EXIT_OK;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const sessionId = resolveSessionId(flags);
|
|
150
|
+
const agent = await resolveAgent(flags, sessionId);
|
|
151
|
+
try {
|
|
152
|
+
const r = allocateForClaim(ctx, { specId: flags.spec, sessionId, agent, pid: process.pid });
|
|
153
|
+
emit(r, [`${r.created ? "allocated" : "re-attached"} ${r.path} on ${r.branch}`]);
|
|
154
|
+
process.exitCode = EXIT_OK;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
process.stderr.write(`hs worktree alloc: ${e.message}\n`);
|
|
157
|
+
process.exitCode = EXIT_WRITE_FAILED;
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (verb === "release") {
|
|
163
|
+
const r = markReleased(ctx, { specId: flags.spec });
|
|
164
|
+
emit(r, [
|
|
165
|
+
r.found
|
|
166
|
+
? `${r.alreadyReleased ? "already released" : "released"} ${worktreePathFor(ctx, flags.spec)}`
|
|
167
|
+
: `no worktree for ${flags.spec}`,
|
|
168
|
+
]);
|
|
169
|
+
process.exitCode = EXIT_OK;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (verb === "rebind") {
|
|
174
|
+
try {
|
|
175
|
+
const r = rebind(ctx, { fromSpecId: flags.spec, toSpecId: flags.to });
|
|
176
|
+
emit(r, [r.found ? `rebound ${flags.spec} -> ${flags.to} on ${r.branch}` : `no worktree for ${flags.spec}`]);
|
|
177
|
+
process.exitCode = EXIT_OK;
|
|
178
|
+
} catch (e) {
|
|
179
|
+
process.stderr.write(`hs worktree rebind: ${e.message}\n`);
|
|
180
|
+
process.exitCode = EXIT_WRITE_FAILED;
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// reap
|
|
186
|
+
const r = await reapWorktrees(ctx, {
|
|
187
|
+
dryRun: flags.dryRun,
|
|
188
|
+
includeForeign: flags.includeForeign,
|
|
189
|
+
yes: flags.yes,
|
|
190
|
+
});
|
|
191
|
+
emit(r, [
|
|
192
|
+
`${r.dryRun ? "would remove" : "removed"} ${r.removed.length}: ${r.removed.join(", ") || "(none)"}`,
|
|
193
|
+
...r.skipped.map((s) => ` skipped ${s.path} - ${s.reason}`),
|
|
194
|
+
]);
|
|
195
|
+
process.exitCode = flags.expectRemoved && r.removed.length === 0 ? EXIT_CHECK_FAILED : EXIT_OK;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main().catch((err) => {
|
|
199
|
+
process.stderr.write(`hs worktree: ${err?.stack ?? err}\n`);
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
});
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Install Claude Code lifecycle hooks that emit agent status to the console
|
|
3
|
+
// (HS-TASK-P89W). Merges entries into `.claude/settings.json` without
|
|
4
|
+
// clobbering existing hooks. Idempotent, and self-heals command-string drift.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node scripts/install-claude-code-hooks.mjs # install/repair
|
|
8
|
+
// node scripts/install-claude-code-hooks.mjs --check # exit 1 on drift
|
|
9
|
+
// node scripts/install-claude-code-hooks.mjs --uninstall
|
|
10
|
+
//
|
|
11
|
+
// What it writes:
|
|
12
|
+
// - PreToolUse → emit-claim-status.mjs --event=PreToolUse
|
|
13
|
+
// - Stop → emit-claim-status.mjs --event=Stop
|
|
14
|
+
// - PostToolUse → emit-claim-status.mjs --event=PostToolUse
|
|
15
|
+
// - SessionEnd → session-end.mjs (release claim on clean exit)
|
|
16
|
+
// - PreToolUse + PostToolUse → log-session-activity.mjs (agent.tool_called
|
|
17
|
+
// session log — HS-6QEW; reads hook_event_name from the stdin payload)
|
|
18
|
+
//
|
|
19
|
+
// Each hook command is identified by its script MARKER. On reinstall, any entry
|
|
20
|
+
// matching the marker but whose command string diverges from the canonical
|
|
21
|
+
// form is replaced — guards against the regression where the path got
|
|
22
|
+
// rewritten to a CWD-relative form (see [[hook-paths]]).
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
+
const SETTINGS_PATH = path.join(REPO_ROOT, '.claude', 'settings.json');
|
|
30
|
+
const HOOK_TIMEOUT_SECONDS = 5;
|
|
31
|
+
|
|
32
|
+
const CLAIM_STATUS_MARKER = 'scripts/claude-hooks/emit-claim-status.mjs';
|
|
33
|
+
const SESSION_END_MARKER = 'scripts/claude-hooks/session-end.mjs';
|
|
34
|
+
const SESSION_ACTIVITY_MARKER = 'scripts/claude-hooks/log-session-activity.mjs';
|
|
35
|
+
|
|
36
|
+
// Anchor every script path via $CLAUDE_PROJECT_DIR so hooks resolve regardless
|
|
37
|
+
// of the agent's cwd (it often drifts into spex-console/ during a session).
|
|
38
|
+
// See [[HS-LES-hook-paths]] and the prior NIHZ regression.
|
|
39
|
+
//
|
|
40
|
+
// Hooks are not invoked directly — every one routes through the `_run.mjs`
|
|
41
|
+
// shim, which is where the HeySpex master on/off switch lives (`hs mode off`
|
|
42
|
+
// makes every hook exit 0 without the settings file changing). See
|
|
43
|
+
// scripts/claude-hooks/_run.mjs and docs/claude-code-setup.md.
|
|
44
|
+
const SHIM = 'scripts/claude-hooks/_run.mjs';
|
|
45
|
+
|
|
46
|
+
/** Canonical command string for a hook: `node "$CLAUDE_PROJECT_DIR/_run.mjs" <name> [args]`. */
|
|
47
|
+
function shimCommand(scriptMarker, args = '') {
|
|
48
|
+
const name = scriptMarker.replace('scripts/claude-hooks/', '').replace(/\.mjs$/, '');
|
|
49
|
+
return `node "$CLAUDE_PROJECT_DIR/${SHIM}" ${name}${args}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Markers that identify one of our entries inside an event's groups. The shim
|
|
54
|
+
* form is first (what we self-heal toward); the pre-shim direct form is kept so
|
|
55
|
+
* a checkout installed before the shim landed gets REPAIRED in place rather
|
|
56
|
+
* than growing a duplicate entry alongside the old one.
|
|
57
|
+
*/
|
|
58
|
+
function markersFor(scriptMarker, args = '') {
|
|
59
|
+
const name = scriptMarker.replace('scripts/claude-hooks/', '').replace(/\.mjs$/, '');
|
|
60
|
+
return [`${SHIM}" ${name}${args}`, `${scriptMarker}"${args}`];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// One descriptor per (event, script). `markers` identify our entry within an
|
|
64
|
+
// event's groups; `command` is the canonical string we self-heal toward.
|
|
65
|
+
const HOOKS = [
|
|
66
|
+
{
|
|
67
|
+
event: 'PreToolUse',
|
|
68
|
+
markers: markersFor(CLAIM_STATUS_MARKER, ' --event=PreToolUse'),
|
|
69
|
+
withMatcher: true,
|
|
70
|
+
command: shimCommand(CLAIM_STATUS_MARKER, ' --event=PreToolUse'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
event: 'Stop',
|
|
74
|
+
markers: markersFor(CLAIM_STATUS_MARKER, ' --event=Stop'),
|
|
75
|
+
withMatcher: false,
|
|
76
|
+
command: shimCommand(CLAIM_STATUS_MARKER, ' --event=Stop'),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
event: 'PostToolUse',
|
|
80
|
+
markers: markersFor(CLAIM_STATUS_MARKER, ' --event=PostToolUse'),
|
|
81
|
+
withMatcher: true,
|
|
82
|
+
command: shimCommand(CLAIM_STATUS_MARKER, ' --event=PostToolUse'),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
event: 'SessionEnd',
|
|
86
|
+
markers: markersFor(SESSION_END_MARKER),
|
|
87
|
+
withMatcher: false,
|
|
88
|
+
command: shimCommand(SESSION_END_MARKER),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
event: 'PreToolUse',
|
|
92
|
+
markers: markersFor(SESSION_ACTIVITY_MARKER),
|
|
93
|
+
withMatcher: true,
|
|
94
|
+
command: shimCommand(SESSION_ACTIVITY_MARKER),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
event: 'PostToolUse',
|
|
98
|
+
markers: markersFor(SESSION_ACTIVITY_MARKER),
|
|
99
|
+
withMatcher: true,
|
|
100
|
+
command: shimCommand(SESSION_ACTIVITY_MARKER),
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const ALL_MARKERS = [...new Set(HOOKS.flatMap((h) => h.markers))];
|
|
105
|
+
|
|
106
|
+
function readSettings() {
|
|
107
|
+
try {
|
|
108
|
+
const raw = fs.readFileSync(SETTINGS_PATH, 'utf8').trim();
|
|
109
|
+
if (!raw) return {};
|
|
110
|
+
return JSON.parse(raw);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err && err.code === 'ENOENT') return {};
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function writeSettings(settings) {
|
|
118
|
+
fs.mkdirSync(path.dirname(SETTINGS_PATH), { recursive: true });
|
|
119
|
+
fs.writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function hookMatchesMarker(h, markers) {
|
|
123
|
+
if (!h || h.type !== 'command' || typeof h.command !== 'string') return false;
|
|
124
|
+
return [].concat(markers).some((m) => h.command.includes(m));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isOurHook(h) {
|
|
128
|
+
return hookMatchesMarker(h, ALL_MARKERS);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function stripOurHooks(groups) {
|
|
132
|
+
if (!Array.isArray(groups)) return [];
|
|
133
|
+
return groups
|
|
134
|
+
.map((group) => {
|
|
135
|
+
if (!group || !Array.isArray(group.hooks)) return group;
|
|
136
|
+
const hooks = group.hooks.filter((h) => !isOurHook(h));
|
|
137
|
+
return { ...group, hooks };
|
|
138
|
+
})
|
|
139
|
+
.filter((group) => Array.isArray(group?.hooks) && group.hooks.length > 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Returns 'added' (no entry existed), 'repaired' (entry existed with wrong
|
|
143
|
+
// command string and was rewritten), or 'noop' (entry already canonical).
|
|
144
|
+
function ensureHook(settings, { event, markers, command, withMatcher }) {
|
|
145
|
+
settings.hooks ??= {};
|
|
146
|
+
settings.hooks[event] ??= [];
|
|
147
|
+
const groups = settings.hooks[event];
|
|
148
|
+
|
|
149
|
+
let foundOurs = false;
|
|
150
|
+
let repaired = false;
|
|
151
|
+
for (const group of groups) {
|
|
152
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
153
|
+
for (const h of group.hooks) {
|
|
154
|
+
if (!hookMatchesMarker(h, markers)) continue;
|
|
155
|
+
foundOurs = true;
|
|
156
|
+
if (h.command !== command) {
|
|
157
|
+
h.command = command;
|
|
158
|
+
repaired = true;
|
|
159
|
+
}
|
|
160
|
+
if (h.timeout !== HOOK_TIMEOUT_SECONDS) {
|
|
161
|
+
h.timeout = HOOK_TIMEOUT_SECONDS;
|
|
162
|
+
repaired = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (foundOurs) return repaired ? 'repaired' : 'noop';
|
|
167
|
+
|
|
168
|
+
const newGroup = {
|
|
169
|
+
...(withMatcher ? { matcher: '.*' } : {}),
|
|
170
|
+
hooks: [{ type: 'command', command, timeout: HOOK_TIMEOUT_SECONDS }],
|
|
171
|
+
};
|
|
172
|
+
groups.push(newGroup);
|
|
173
|
+
return 'added';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function uninstall() {
|
|
177
|
+
const settings = readSettings();
|
|
178
|
+
if (!settings.hooks) {
|
|
179
|
+
console.log('install-claude-code-hooks: nothing to uninstall (no hooks block).');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let removed = 0;
|
|
183
|
+
const events = [...new Set(HOOKS.map((h) => h.event))];
|
|
184
|
+
for (const name of events) {
|
|
185
|
+
const before = settings.hooks[name];
|
|
186
|
+
if (!Array.isArray(before)) continue;
|
|
187
|
+
const after = stripOurHooks(before);
|
|
188
|
+
const droppedHookCount =
|
|
189
|
+
before.reduce((n, g) => n + (Array.isArray(g?.hooks) ? g.hooks.length : 0), 0) -
|
|
190
|
+
after.reduce((n, g) => n + (Array.isArray(g?.hooks) ? g.hooks.length : 0), 0);
|
|
191
|
+
removed += droppedHookCount;
|
|
192
|
+
if (after.length === 0) {
|
|
193
|
+
delete settings.hooks[name];
|
|
194
|
+
} else {
|
|
195
|
+
settings.hooks[name] = after;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
199
|
+
delete settings.hooks;
|
|
200
|
+
}
|
|
201
|
+
writeSettings(settings);
|
|
202
|
+
console.log(`install-claude-code-hooks: uninstalled ${removed} hook entr${removed === 1 ? 'y' : 'ies'}.`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function install() {
|
|
206
|
+
const settings = readSettings();
|
|
207
|
+
const added = [];
|
|
208
|
+
const repaired = [];
|
|
209
|
+
for (const descriptor of HOOKS) {
|
|
210
|
+
const outcome = ensureHook(settings, descriptor);
|
|
211
|
+
if (outcome === 'added') added.push(descriptor.event);
|
|
212
|
+
else if (outcome === 'repaired') repaired.push(descriptor.event);
|
|
213
|
+
}
|
|
214
|
+
if (added.length === 0 && repaired.length === 0) {
|
|
215
|
+
console.log('install-claude-code-hooks: already installed (no changes).');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
writeSettings(settings);
|
|
219
|
+
const parts = [];
|
|
220
|
+
if (added.length > 0) parts.push(`installed ${added.join(', ')}`);
|
|
221
|
+
if (repaired.length > 0) parts.push(`repaired ${repaired.join(', ')}`);
|
|
222
|
+
console.log(`install-claude-code-hooks: ${parts.join('; ')} → ${path.relative(REPO_ROOT, SETTINGS_PATH)}`);
|
|
223
|
+
console.log(' Wrappers: scripts/claude-hooks/emit-claim-status.mjs, scripts/claude-hooks/session-end.mjs');
|
|
224
|
+
console.log(' Console URL: .heyspex/console-url (auto-written by `npm run dev`) → HEYSPEX_CONSOLE_URL env var → http://localhost:3000');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Read-only audit: walks installed hooks and prints any drift without
|
|
228
|
+
// writing. Exit code 1 if anything is missing or has a non-canonical
|
|
229
|
+
// command string; 0 if everything is healthy. Intended for CI / preflight.
|
|
230
|
+
function check() {
|
|
231
|
+
const settings = readSettings();
|
|
232
|
+
const issues = [];
|
|
233
|
+
const groupsFor = (event) =>
|
|
234
|
+
Array.isArray(settings?.hooks?.[event]) ? settings.hooks[event] : [];
|
|
235
|
+
for (const { event, markers, command } of HOOKS) {
|
|
236
|
+
const groups = groupsFor(event);
|
|
237
|
+
const ours = groups
|
|
238
|
+
.flatMap((g) => (Array.isArray(g?.hooks) ? g.hooks : []))
|
|
239
|
+
.filter((h) => hookMatchesMarker(h, markers));
|
|
240
|
+
if (ours.length === 0) {
|
|
241
|
+
issues.push(`${event}: missing hook for ${markers[0]}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
for (const h of ours) {
|
|
245
|
+
if (h.command !== command) {
|
|
246
|
+
issues.push(`${event}: command drift — expected \`${command}\`, found \`${h.command}\``);
|
|
247
|
+
}
|
|
248
|
+
if (h.timeout !== HOOK_TIMEOUT_SECONDS) {
|
|
249
|
+
issues.push(`${event}: timeout drift — expected ${HOOK_TIMEOUT_SECONDS}, found ${h.timeout}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (issues.length === 0) {
|
|
254
|
+
console.log('install-claude-code-hooks --check: OK');
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
console.error('install-claude-code-hooks --check: drift detected');
|
|
258
|
+
for (const issue of issues) console.error(` - ${issue}`);
|
|
259
|
+
console.error(' Run `node scripts/install-claude-code-hooks.mjs` to repair.');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const mode = process.argv.includes('--uninstall')
|
|
264
|
+
? 'uninstall'
|
|
265
|
+
: process.argv.includes('--check')
|
|
266
|
+
? 'check'
|
|
267
|
+
: 'install';
|
|
268
|
+
if (mode === 'uninstall') uninstall();
|
|
269
|
+
else if (mode === 'check') check();
|
|
270
|
+
else install();
|