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,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* template-pin.mjs — the ONE home of the customer-vault template version and
|
|
3
|
+
* the list of docs a healthy customer vault must always contain (HS-KH0Q).
|
|
4
|
+
*
|
|
5
|
+
* Zero dependencies (node builtins only) so BOTH consumers can import it:
|
|
6
|
+
* - `spex-console/src/lib/cli/template-manifest.ts` re-exports it, and
|
|
7
|
+
* - the zero-dependency standalone doctor (`scripts/lib/standalone-doctor.mjs`,
|
|
8
|
+
* the `vault-template` row) reads it without pulling in the console.
|
|
9
|
+
*
|
|
10
|
+
* Bump `TEMPLATE_VERSION` whenever the shipped manifest content changes in a
|
|
11
|
+
* way a customer vault should be able to detect (new universal doc, changed
|
|
12
|
+
* skeleton, etc.). `TEMPLATE_CORE_DESTS` is guarded against drift from the
|
|
13
|
+
* real manifest by `spex-console/src/lib/cli/template-manifest.test.ts`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Bumped whenever the shipped manifest content changes. */
|
|
17
|
+
export const TEMPLATE_VERSION = "1.1.0";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Vault-relative (POSIX) paths every scaffolded customer vault must hold:
|
|
21
|
+
* the foundational docs plus every universal protocol.
|
|
22
|
+
*/
|
|
23
|
+
export const TEMPLATE_CORE_DESTS = Object.freeze([
|
|
24
|
+
"specs/north-star.md",
|
|
25
|
+
"specs/principles-index.md",
|
|
26
|
+
"specs/roadmap.md",
|
|
27
|
+
"specs/entity-model.md",
|
|
28
|
+
"specs/protocols/canonical-frontmatter-templates.md",
|
|
29
|
+
"specs/protocols/spec-hierarchy.md",
|
|
30
|
+
"specs/protocols/memory-protocol.md",
|
|
31
|
+
"specs/protocols/session-close-protocol.md",
|
|
32
|
+
"specs/protocols/spec-summary-authoring.md",
|
|
33
|
+
"specs/protocols/destructive-action-checklist.md",
|
|
34
|
+
]);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window placement for the two-pane capture layout
|
|
3
|
+
* ([[HS-I2YN-ST07-EPIC-two-pane-layout-pipeline-view-on-the]]).
|
|
4
|
+
*
|
|
5
|
+
* The owner wants the `hs view` window (pipeline + capture doc) and the
|
|
6
|
+
* PowerShell SpexCode agent window both visible at once. Neither launcher used
|
|
7
|
+
* to place its window, so they stacked wherever the OS put them. This module is
|
|
8
|
+
* the ONE place that decides the geometry; both launchers call it:
|
|
9
|
+
*
|
|
10
|
+
* - `scripts/hs-view.mjs` -> side "right" (this window)
|
|
11
|
+
* - the agent launch (HS-TXFW) -> `complementarySide("right")` = "left"
|
|
12
|
+
*
|
|
13
|
+
* Pure geometry (`computeWindowPlacement`) is separated from the one
|
|
14
|
+
* side-effecting probe (`getPrimaryWorkArea`) so the maths is unit-testable.
|
|
15
|
+
*
|
|
16
|
+
* Safety: placement is always BEST EFFORT. Any failure (non-Windows, probe
|
|
17
|
+
* timeout, nonsense numbers) yields `null` and the caller simply omits the
|
|
18
|
+
* position flags, i.e. today's behavior. Multi-monitor: only the primary
|
|
19
|
+
* monitor's work area is used by default (taskbar excluded), so a window can
|
|
20
|
+
* never be pushed onto a monitor that was unplugged or a negative coordinate
|
|
21
|
+
* the OS would clamp unpredictably.
|
|
22
|
+
*/
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
|
|
25
|
+
/** Sides a window can take. `none` disables placement entirely. */
|
|
26
|
+
export const PLACE_SIDES = new Set(['left', 'right', 'none']);
|
|
27
|
+
|
|
28
|
+
/** The other half of the screen - what the sibling window should take. */
|
|
29
|
+
export function complementarySide(side) {
|
|
30
|
+
if (side === 'left') return 'right';
|
|
31
|
+
if (side === 'right') return 'left';
|
|
32
|
+
return 'none';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Parse `left|right|none` (case-insensitive); anything else -> null. */
|
|
36
|
+
export function parsePlaceSide(value) {
|
|
37
|
+
const v = String(value ?? '').trim().toLowerCase();
|
|
38
|
+
return PLACE_SIDES.has(v) ? v : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validWorkArea(w) {
|
|
42
|
+
return (
|
|
43
|
+
w &&
|
|
44
|
+
[w.x, w.y, w.width, w.height].every((n) => Number.isFinite(n)) &&
|
|
45
|
+
w.width >= 400 &&
|
|
46
|
+
w.height >= 300
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Geometry for one window on one half of a work area.
|
|
52
|
+
*
|
|
53
|
+
* Width is the requested width capped at half the work area, so the two
|
|
54
|
+
* windows can never overlap; height fills the work area. Returns
|
|
55
|
+
* `{ x, y, width, height }` or null when placement should be skipped.
|
|
56
|
+
*/
|
|
57
|
+
export function computeWindowPlacement({ workArea, side, width, height }) {
|
|
58
|
+
if (side !== 'left' && side !== 'right') return null;
|
|
59
|
+
if (!validWorkArea(workArea)) return null;
|
|
60
|
+
const half = Math.floor(workArea.width / 2);
|
|
61
|
+
const w = Math.max(1, Math.min(Number.isFinite(width) && width > 0 ? width : half, half));
|
|
62
|
+
const h = Math.max(
|
|
63
|
+
1,
|
|
64
|
+
Math.min(Number.isFinite(height) && height > 0 ? height : workArea.height, workArea.height),
|
|
65
|
+
);
|
|
66
|
+
// Full-height halves unless the caller asked for something shorter.
|
|
67
|
+
const x = side === 'left' ? workArea.x : workArea.x + workArea.width - w;
|
|
68
|
+
return { x, y: workArea.y, width: w, height: h };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The Chromium app-mode flags for a placement. */
|
|
72
|
+
export function placementFlags(p) {
|
|
73
|
+
return p ? [`--window-position=${p.x},${p.y}`, `--window-size=${p.width},${p.height}`] : [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Primary monitor work area (taskbar excluded) in the units Chromium's
|
|
78
|
+
* `--window-position` uses. Windows only; null elsewhere or on any failure.
|
|
79
|
+
* Override for tests / unusual setups with `HEYSPEX_VIEW_WORKAREA=x,y,w,h`.
|
|
80
|
+
*/
|
|
81
|
+
export function getPrimaryWorkArea(env = process.env, platform = process.platform) {
|
|
82
|
+
const override = String(env.HEYSPEX_VIEW_WORKAREA ?? '').trim();
|
|
83
|
+
if (override) {
|
|
84
|
+
const [x, y, width, height] = override.split(',').map((s) => Number(s.trim()));
|
|
85
|
+
const w = { x, y, width, height };
|
|
86
|
+
return validWorkArea(w) ? w : null;
|
|
87
|
+
}
|
|
88
|
+
if (platform !== 'win32') return null;
|
|
89
|
+
try {
|
|
90
|
+
const r = spawnSync(
|
|
91
|
+
'powershell.exe',
|
|
92
|
+
[
|
|
93
|
+
'-NoProfile',
|
|
94
|
+
'-NonInteractive',
|
|
95
|
+
'-Command',
|
|
96
|
+
'Add-Type -AssemblyName System.Windows.Forms; ' +
|
|
97
|
+
'$a=[System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea; ' +
|
|
98
|
+
'"$($a.X),$($a.Y),$($a.Width),$($a.Height)"',
|
|
99
|
+
],
|
|
100
|
+
{ encoding: 'utf8', timeout: 4000, windowsHide: true },
|
|
101
|
+
);
|
|
102
|
+
if (r.status !== 0) return null;
|
|
103
|
+
const [x, y, width, height] = String(r.stdout).trim().split(',').map(Number);
|
|
104
|
+
const w = { x, y, width, height };
|
|
105
|
+
return validWorkArea(w) ? w : null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared authorship-provenance targeting predicate
|
|
3
|
+
* ([[HS-EPIC-IKIM-authorship-provenance-frontmatter-owner]]).
|
|
4
|
+
*
|
|
5
|
+
* SSOT for "which specs carry the universal `owner` requirement" — imported by
|
|
6
|
+
* both the gate (`validate-vault-provenance.mjs`) and the one-time backfill
|
|
7
|
+
* (`backfill-vault-provenance.mjs`) so they cannot drift. If they drifted, the
|
|
8
|
+
* post-backfill `block` gate could fire on a file the backfill never touched.
|
|
9
|
+
*
|
|
10
|
+
* `owner` is universally required ([[entity-model]] §Authorship provenance). The
|
|
11
|
+
* `contributors` / `assisted_by` lists are intentionally OPTIONAL (auto-stamped,
|
|
12
|
+
* omit-when-empty), so they are NOT part of this predicate. Frozen statuses
|
|
13
|
+
* (closed/superseded/cancelled/duplicate) are skipped — their frontmatter is
|
|
14
|
+
* immutable historical record.
|
|
15
|
+
*/
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
|
|
21
|
+
export const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
22
|
+
const SPECS_DIR = path.join(REPO_ROOT, "specs");
|
|
23
|
+
|
|
24
|
+
// Reuse spex-console's installed js-yaml (transitive via gray-matter) — same pattern
|
|
25
|
+
// as validate-vault-frontmatter.mjs; no deps of our own.
|
|
26
|
+
const spexConsoleRequire = createRequire(path.join(REPO_ROOT, "spex-console", "package.json"));
|
|
27
|
+
export const yaml = spexConsoleRequire("js-yaml");
|
|
28
|
+
|
|
29
|
+
/** Statuses whose frontmatter is frozen historical record — never flagged. */
|
|
30
|
+
export const FROZEN_STATUSES = new Set(["closed", "superseded", "cancelled", "duplicate"]);
|
|
31
|
+
|
|
32
|
+
/** Recursively collect every *.md under specs/ (relative to repo root, posix sep). */
|
|
33
|
+
export function allSpecFiles() {
|
|
34
|
+
/** @type {string[]} */
|
|
35
|
+
const out = [];
|
|
36
|
+
const walk = (dir) => {
|
|
37
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
38
|
+
const abs = path.join(dir, entry.name);
|
|
39
|
+
if (entry.isDirectory()) walk(abs);
|
|
40
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
41
|
+
out.push(path.relative(REPO_ROOT, abs).split(path.sep).join("/"));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
if (fs.existsSync(SPECS_DIR)) walk(SPECS_DIR);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Extract the raw frontmatter block (between the leading `---` fences). */
|
|
50
|
+
export function extractFrontmatter(raw) {
|
|
51
|
+
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
52
|
+
if (!m) return null;
|
|
53
|
+
return m[1];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a spec's frontmatter to an object.
|
|
58
|
+
* @returns {object|null} null when there is no frontmatter, the YAML is
|
|
59
|
+
* malformed, or it is not a mapping — all of which this gate skips (malformed
|
|
60
|
+
* YAML is the frontmatter gate's job, not ours).
|
|
61
|
+
*/
|
|
62
|
+
export function parseFrontmatter(raw) {
|
|
63
|
+
const fm = extractFrontmatter(raw);
|
|
64
|
+
if (fm === null) return null;
|
|
65
|
+
let data;
|
|
66
|
+
try {
|
|
67
|
+
data = yaml.load(fm);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!data || typeof data !== "object") return null;
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* True when this spec carries the universal `owner` requirement: a typed entity
|
|
77
|
+
* spec whose status is not frozen. Prose-only docs and frozen records are out of
|
|
78
|
+
* scope for both the gate and the backfill.
|
|
79
|
+
*/
|
|
80
|
+
export function isTypedNonFrozen(data) {
|
|
81
|
+
if (!data || typeof data !== "object") return false;
|
|
82
|
+
if (typeof data.type !== "string" || !data.type.trim()) return false;
|
|
83
|
+
const status = typeof data.status === "string" ? data.status.trim().toLowerCase() : "";
|
|
84
|
+
if (FROZEN_STATUSES.has(status)) return false;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The trimmed `owner` scalar, or "" when absent/blank. */
|
|
89
|
+
export function ownerOf(data) {
|
|
90
|
+
return typeof data.owner === "string" ? data.owner.trim() : "";
|
|
91
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Write-time `assisted_by` stamp for direct Edit/Write/MultiEdit tool writes
|
|
4
|
+
* ([[HS-EPIC-IKIM-authorship-provenance-frontmatter-owner]] Phase 3).
|
|
5
|
+
*
|
|
6
|
+
* Wired as a Claude Code **PostToolUse** hook (`.claude/settings.json`). The
|
|
7
|
+
* `updateSpec` edit-path stamp only fires for console-UI / MCP PUT writes; an
|
|
8
|
+
* agent that edits a spec file directly via the Edit tool bypasses it. The
|
|
9
|
+
* literal "git pre-commit stamp" can't fix this — the human reviewer runs the
|
|
10
|
+
* commit and the agent has released its claim by then, so neither the committer
|
|
11
|
+
* nor the claim trail names the editing agent. The only place the agent identity
|
|
12
|
+
* is reliably present is *at edit time*, in the agent's own process env — which
|
|
13
|
+
* is exactly when a PostToolUse hook runs.
|
|
14
|
+
*
|
|
15
|
+
* SAFETY (this runs on every spec edit): it only ever APPENDS one `assisted_by`
|
|
16
|
+
* entry, append-only + de-duped, and NO-OPS (touches nothing) on any frontmatter
|
|
17
|
+
* shape it isn't fully confident about. It always exits 0 — a stamp failure must
|
|
18
|
+
* never disrupt the agent's edit. Reads the PostToolUse payload as JSON on stdin.
|
|
19
|
+
*
|
|
20
|
+
* Targeting (typed + non-frozen) and frontmatter parsing are shared with the gate
|
|
21
|
+
* via `provenance-lib.mjs`, so this stamps exactly the specs the gate governs.
|
|
22
|
+
*/
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { REPO_ROOT, parseFrontmatter, isTypedNonFrozen } from "./provenance-lib.mjs";
|
|
26
|
+
import { getActiveConfig } from "../vendor/heyspex-mcp/vault-resolve.mjs";
|
|
27
|
+
|
|
28
|
+
/** Exit without touching anything — the safe default for every uncertain case. */
|
|
29
|
+
function bail() {
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The acting agent's display name, or null when it can't be identified. */
|
|
34
|
+
function detectAgent() {
|
|
35
|
+
const claude = (
|
|
36
|
+
process.env.CLAUDE_CODE_SESSION_ID ||
|
|
37
|
+
process.env.CLAUDE_SESSION_ID ||
|
|
38
|
+
process.env.CLAUDECODE ||
|
|
39
|
+
""
|
|
40
|
+
).trim();
|
|
41
|
+
if (claude) return "Claude Code";
|
|
42
|
+
const cursor = (
|
|
43
|
+
process.env.CURSOR_TRACE_ID ||
|
|
44
|
+
process.env.CURSOR_SESSION_ID ||
|
|
45
|
+
process.env.COMPOSER_SESSION_ID ||
|
|
46
|
+
""
|
|
47
|
+
).trim();
|
|
48
|
+
if (cursor) return "Cursor";
|
|
49
|
+
const explicit = process.env.HEYSPEX_AGENT?.trim();
|
|
50
|
+
if (explicit) return explicit;
|
|
51
|
+
return null; // unknown agent — don't stamp a placeholder
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read all of stdin (the PostToolUse JSON payload). */
|
|
55
|
+
function readStdin() {
|
|
56
|
+
try {
|
|
57
|
+
return fs.readFileSync(0, "utf8");
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Append `agent` to the frontmatter's `assisted_by` list via surgical text edit
|
|
65
|
+
* (no YAML reflow). Returns the patched text, or null to signal "no safe edit"
|
|
66
|
+
* (caller no-ops). `current` is the parsed value of `assisted_by` (for dedup).
|
|
67
|
+
*/
|
|
68
|
+
function appendAssistedBy(raw, agent, current) {
|
|
69
|
+
// Unexpected shape (present but not a list) → never touch it.
|
|
70
|
+
if (current !== undefined && !Array.isArray(current)) return null;
|
|
71
|
+
// Already present → idempotent no-op. An entry counts as "this agent" whether
|
|
72
|
+
// it is bare ("Claude Code") or model-qualified ("Claude Code:Fable 5") — the
|
|
73
|
+
// `AGENT:MODEL` form ([[entity-model]] §Authorship provenance). Without the
|
|
74
|
+
// prefix check, stamping a bare "Claude Code" onto a spec already crediting
|
|
75
|
+
// "Claude Code:Fable 5" would append a redundant, less-informative duplicate.
|
|
76
|
+
if (
|
|
77
|
+
Array.isArray(current) &&
|
|
78
|
+
current.some((v) => {
|
|
79
|
+
if (typeof v !== "string") return false;
|
|
80
|
+
const t = v.trim();
|
|
81
|
+
return t === agent || t.startsWith(`${agent}:`);
|
|
82
|
+
})
|
|
83
|
+
) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
88
|
+
const lines = raw.split(/\r?\n/);
|
|
89
|
+
|
|
90
|
+
// Frontmatter fences (the first must be line 0; parseFrontmatter guaranteed it).
|
|
91
|
+
let f1 = -1;
|
|
92
|
+
let f2 = -1;
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
if (lines[i].trim() === "---") {
|
|
95
|
+
if (f1 < 0) f1 = i;
|
|
96
|
+
else {
|
|
97
|
+
f2 = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (f1 !== 0 || f2 < 0) return null;
|
|
103
|
+
|
|
104
|
+
// Locate a top-level `assisted_by:` line within the block.
|
|
105
|
+
let abIdx = -1;
|
|
106
|
+
for (let i = f1 + 1; i < f2; i++) {
|
|
107
|
+
if (/^assisted_by\s*:/.test(lines[i])) {
|
|
108
|
+
abIdx = i;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (abIdx < 0) {
|
|
114
|
+
// No assisted_by yet — insert a block list after owner: / status: / type:.
|
|
115
|
+
let anchor = -1;
|
|
116
|
+
for (const key of ["owner", "status", "type"]) {
|
|
117
|
+
for (let i = f1 + 1; i < f2; i++) {
|
|
118
|
+
if (new RegExp(`^${key}\\s*:`).test(lines[i])) {
|
|
119
|
+
anchor = i;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (anchor >= 0) break;
|
|
124
|
+
}
|
|
125
|
+
if (anchor < 0) return null;
|
|
126
|
+
lines.splice(anchor + 1, 0, "assisted_by:", ` - ${agent}`);
|
|
127
|
+
return lines.join(eol);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const abLine = lines[abIdx];
|
|
131
|
+
const inlineMatch = abLine.match(/^(assisted_by\s*:\s*)\[(.*)\]\s*$/);
|
|
132
|
+
if (inlineMatch) {
|
|
133
|
+
// Inline form: assisted_by: [A, B] or assisted_by: []
|
|
134
|
+
const inner = inlineMatch[2].trim();
|
|
135
|
+
const next = inner.length === 0 ? agent : `${inner}, ${agent}`;
|
|
136
|
+
lines[abIdx] = `${inlineMatch[1]}[${next}]`;
|
|
137
|
+
return lines.join(eol);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (/^assisted_by\s*:\s*$/.test(abLine)) {
|
|
141
|
+
// Block form: find the last contiguous `- ` item under it and insert after.
|
|
142
|
+
let lastItem = abIdx;
|
|
143
|
+
let itemIndent = " ";
|
|
144
|
+
for (let i = abIdx + 1; i < f2; i++) {
|
|
145
|
+
const m = lines[i].match(/^(\s+)-\s+/);
|
|
146
|
+
if (m) {
|
|
147
|
+
lastItem = i;
|
|
148
|
+
itemIndent = m[1];
|
|
149
|
+
} else if (lines[i].trim() === "") {
|
|
150
|
+
continue; // tolerate blank lines inside the block
|
|
151
|
+
} else {
|
|
152
|
+
break; // next key — list ended
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (lastItem === abIdx) {
|
|
156
|
+
// `assisted_by:` with no items yet — add the first.
|
|
157
|
+
lines.splice(abIdx + 1, 0, ` - ${agent}`);
|
|
158
|
+
} else {
|
|
159
|
+
lines.splice(lastItem + 1, 0, `${itemIndent}- ${agent}`);
|
|
160
|
+
}
|
|
161
|
+
return lines.join(eol);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// `assisted_by:` in some other shape (e.g. a scalar) → don't touch.
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function main() {
|
|
169
|
+
const payload = readStdin();
|
|
170
|
+
let filePath = "";
|
|
171
|
+
try {
|
|
172
|
+
const obj = JSON.parse(payload);
|
|
173
|
+
filePath = obj?.tool_input?.file_path || obj?.tool_input?.filePath || "";
|
|
174
|
+
} catch {
|
|
175
|
+
return bail();
|
|
176
|
+
}
|
|
177
|
+
if (typeof filePath !== "string" || !filePath.trim()) return bail();
|
|
178
|
+
|
|
179
|
+
const abs = path.resolve(filePath);
|
|
180
|
+
// Must be a markdown spec under a specs/ dir — the code repo's (legacy) or
|
|
181
|
+
// the registry-resolved vault's (the live one since the 2026-08-31 repo split).
|
|
182
|
+
if (!abs.endsWith(".md")) return bail();
|
|
183
|
+
const bases = [REPO_ROOT];
|
|
184
|
+
try {
|
|
185
|
+
const vault = getActiveConfig()?.repoRootAbs;
|
|
186
|
+
if (vault && vault !== REPO_ROOT) bases.push(vault);
|
|
187
|
+
} catch {
|
|
188
|
+
// registry unreadable — the code-repo base still applies
|
|
189
|
+
}
|
|
190
|
+
const underSpecs = bases.some((b) => abs.startsWith(path.join(b, "specs") + path.sep));
|
|
191
|
+
if (!underSpecs) return bail();
|
|
192
|
+
|
|
193
|
+
let raw;
|
|
194
|
+
try {
|
|
195
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
196
|
+
} catch {
|
|
197
|
+
return bail();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const data = parseFrontmatter(raw);
|
|
201
|
+
if (data === null) return bail(); // no/ malformed frontmatter — not our concern
|
|
202
|
+
if (!isTypedNonFrozen(data)) return bail(); // prose-only or frozen historical record
|
|
203
|
+
|
|
204
|
+
const agent = detectAgent();
|
|
205
|
+
if (!agent) return bail();
|
|
206
|
+
|
|
207
|
+
const patched = appendAssistedBy(raw, agent, data.assisted_by);
|
|
208
|
+
if (patched === null || patched === raw) return bail();
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
fs.writeFileSync(abs, patched);
|
|
212
|
+
} catch {
|
|
213
|
+
// Swallow — a stamp must never disrupt the edit.
|
|
214
|
+
}
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
main();
|
|
220
|
+
} catch {
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|