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
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* 2. **Append, not overwrite.** The console PATCH route stores top-level keys
|
|
25
25
|
* verbatim (last-write-wins), so multi-beat `user_quote` calls would clobber
|
|
26
26
|
* each other. We read the prior buffer and append, sending the full array.
|
|
27
|
+
* The same rule applies to the five single-object timestamped sections
|
|
28
|
+
* (`details`/`example`/`userStories`/`doneWhen`/`jtbd`): a second call on
|
|
29
|
+
* the same field combines onto the prior call's text (blank-line joined)
|
|
30
|
+
* instead of replacing it — [[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]].
|
|
31
|
+
* `coreIdea`/`summary` are deliberately NOT combined — a capture's core
|
|
32
|
+
* idea/summary is its current best statement, not an accumulating log.
|
|
27
33
|
*
|
|
28
34
|
* `confirmed: true` is set on every agent-authored section because the MCP write
|
|
29
35
|
* is an explicit commit — the agent only calls this after the conversational
|
|
@@ -48,6 +54,34 @@ function priorChatTurns(currentBuffer) {
|
|
|
48
54
|
);
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Read the prior confirmed text for a single-object timestamped section
|
|
59
|
+
* (`details`/`example`/`userStories`/`doneWhen`/`jtbd`) so a second
|
|
60
|
+
* `heyspex_update_capture` call can accumulate onto it instead of
|
|
61
|
+
* overwriting it ([[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]]).
|
|
62
|
+
* The console PATCH route stores whatever this module sends verbatim
|
|
63
|
+
* (last-write-wins on the whole key), so accumulation has to happen here,
|
|
64
|
+
* on the read-before-write side — the same reason userQuotes/chatTurns
|
|
65
|
+
* already read the prior buffer before building their new array value.
|
|
66
|
+
*/
|
|
67
|
+
function priorTimestampedText(currentBuffer, key) {
|
|
68
|
+
const raw = currentBuffer?.[key];
|
|
69
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw) && typeof raw.text === 'string') {
|
|
70
|
+
return raw.text.trim();
|
|
71
|
+
}
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Combine a prior section's text with a new call's text so neither call's
|
|
77
|
+
* content is lost — joined with a blank line so the materialized section
|
|
78
|
+
* reads as separate paragraphs, not run together.
|
|
79
|
+
*/
|
|
80
|
+
function combineTimestampedText(currentBuffer, key, nextText) {
|
|
81
|
+
const prior = priorTimestampedText(currentBuffer, key);
|
|
82
|
+
return prior ? `${prior}\n\n${nextText}` : nextText;
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
function collapse(value) {
|
|
52
86
|
return String(value).trim().replace(/\n+/g, ' ');
|
|
53
87
|
}
|
|
@@ -85,13 +119,17 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
|
|
|
85
119
|
}
|
|
86
120
|
|
|
87
121
|
// agent_expansion is the `## Expanded Concept` deep-dive section → buffer.details.
|
|
122
|
+
// Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
|
|
88
123
|
if (args.agent_expansion !== undefined && String(args.agent_expansion).trim()) {
|
|
89
|
-
|
|
124
|
+
const text = combineTimestampedText(currentBuffer, 'details', String(args.agent_expansion).trim());
|
|
125
|
+
patches.push({ path: 'details', value: { at: iso, text, confirmed: true } });
|
|
90
126
|
}
|
|
91
127
|
|
|
92
128
|
// example is the `## Example` section (real or AI-illustrative) → buffer.example.
|
|
129
|
+
// Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
|
|
93
130
|
if (args.example !== undefined && String(args.example).trim()) {
|
|
94
|
-
|
|
131
|
+
const text = combineTimestampedText(currentBuffer, 'example', String(args.example).trim());
|
|
132
|
+
patches.push({ path: 'example', value: { at: iso, text, confirmed: true } });
|
|
95
133
|
}
|
|
96
134
|
|
|
97
135
|
// user_stories is the `## User Stories` section → buffer.userStories. Root cause
|
|
@@ -100,9 +138,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
|
|
|
100
138
|
// console content_buffer (and therefore the materialized file) never saw it —
|
|
101
139
|
// silent data loss, unlike example which has always had this push.
|
|
102
140
|
if (args.user_stories !== undefined && String(args.user_stories).trim()) {
|
|
141
|
+
const text = combineTimestampedText(currentBuffer, 'userStories', String(args.user_stories).trim());
|
|
103
142
|
patches.push({
|
|
104
143
|
path: 'userStories',
|
|
105
|
-
value: { at: iso, text
|
|
144
|
+
value: { at: iso, text, confirmed: true },
|
|
106
145
|
});
|
|
107
146
|
}
|
|
108
147
|
|
|
@@ -122,9 +161,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
|
|
|
122
161
|
// {at,text,confirmed} shape as the other agent-authored sections (example,
|
|
123
162
|
// user_stories, details) — sibling half of the HS-NR43 fix above.
|
|
124
163
|
if (args.done_when !== undefined && String(args.done_when).trim()) {
|
|
164
|
+
const text = combineTimestampedText(currentBuffer, 'doneWhen', String(args.done_when).trim());
|
|
125
165
|
patches.push({
|
|
126
166
|
path: 'doneWhen',
|
|
127
|
-
value: { at: iso, text
|
|
167
|
+
value: { at: iso, text, confirmed: true },
|
|
128
168
|
});
|
|
129
169
|
}
|
|
130
170
|
|
|
@@ -137,9 +177,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
|
|
|
137
177
|
// is refused by name at the handler's first hop, never silently dropped here
|
|
138
178
|
// — [[additionalproperties-false-in-an-mcp]].
|
|
139
179
|
if (args.jtbd !== undefined && String(args.jtbd).trim()) {
|
|
180
|
+
const text = combineTimestampedText(currentBuffer, 'jtbd', String(args.jtbd).trim());
|
|
140
181
|
patches.push({
|
|
141
182
|
path: 'jtbd',
|
|
142
|
-
value: { at: iso, text
|
|
183
|
+
value: { at: iso, text, confirmed: true },
|
|
143
184
|
});
|
|
144
185
|
}
|
|
145
186
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, watch as fsWatch } from 'node:fs';
|
|
14
14
|
import { basename as pathBasename, dirname, join } from 'node:path';
|
|
15
|
-
import {
|
|
15
|
+
import { loadCaptureFocusOffline, captureFocusPath } from './capture-focus.mjs';
|
|
16
16
|
import { parseSpecId } from './spec-id-lib.mjs';
|
|
17
17
|
|
|
18
18
|
// Exponential reconnect backoff (ms). Capped at 30s; jittered ±50% on use.
|
|
@@ -205,6 +205,54 @@ export function createCaptureChannelWatcher({
|
|
|
205
205
|
return body?.session ?? null;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the LIVE capture-on-deck pin, the same way `readCaptureFocusVendor`
|
|
210
|
+
* does elsewhere in the vendor MCP (`capture-tools.mjs`): GET the Neon
|
|
211
|
+
* control-plane pin + selection, falling back to the offline file
|
|
212
|
+
* (`loadCaptureFocusOffline`) only when the console can't be reached, and only
|
|
213
|
+
* when that file pin is not itself stale. Kept local
|
|
214
|
+
* (own `fetchImpl`/`getConsoleUrl`, mirroring `fetchSessionSnapshot` above)
|
|
215
|
+
* rather than importing capture-tools.mjs, so this watcher's boot-time pin
|
|
216
|
+
* read stays on the SAME injectable transport its tests already stub.
|
|
217
|
+
*
|
|
218
|
+
* Fixes [[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]: on a
|
|
219
|
+
* console-backed vault the pin lives in Neon, not the offline file — every
|
|
220
|
+
* MCP server start used to `loadCaptureFocus(vaultRoot)` (the file) here,
|
|
221
|
+
* so on this repo it printed "pin → HS-CAP-VDPB is already materialized"
|
|
222
|
+
* for a capture promoted two months earlier, on every single boot.
|
|
223
|
+
*/
|
|
224
|
+
async function fetchLiveCaptureFocus() {
|
|
225
|
+
try {
|
|
226
|
+
const consoleUrl = getConsoleUrl().replace(/\/$/, '');
|
|
227
|
+
const [pinRes, selRes] = await Promise.all([
|
|
228
|
+
fetchImpl(`${consoleUrl}/api/control/capture-focus-pin`, { headers: { accept: 'application/json' } }),
|
|
229
|
+
fetchImpl(`${consoleUrl}/api/control/capture-focus-selection`, { headers: { accept: 'application/json' } }),
|
|
230
|
+
]);
|
|
231
|
+
if (pinRes.ok) {
|
|
232
|
+
const pinBody = await pinRes.json();
|
|
233
|
+
const pin = pinBody?.pin ?? null;
|
|
234
|
+
if (!pin) return null; // Neon authoritative — no pin means nothing on deck.
|
|
235
|
+
const selBody = selRes.ok ? await selRes.json() : null;
|
|
236
|
+
const sel = selBody?.selection ?? null;
|
|
237
|
+
const base = (p) => String(p || '').replace(/^.*[/\\]/, '');
|
|
238
|
+
const selMatches = sel?.specPath ? base(sel.specPath).startsWith(pin.specId) : false;
|
|
239
|
+
const focus = {
|
|
240
|
+
spec_path: selMatches ? sel.specPath : `specs/captures/${pin.specId}.md`,
|
|
241
|
+
pinned_at: pin.pinnedAt,
|
|
242
|
+
};
|
|
243
|
+
if (pin.expiresAt) focus.expires = pin.expiresAt;
|
|
244
|
+
focus.kind = 'idea';
|
|
245
|
+
return focus;
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
// Fall through to the offline file seam.
|
|
249
|
+
}
|
|
250
|
+
// Offline seam — refuse a rotted file pin instead of starting a watcher on it.
|
|
251
|
+
// This watcher spent two months announcing a June capture read off this file
|
|
252
|
+
// ([[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]).
|
|
253
|
+
return loadCaptureFocusOffline(vaultRoot).focus;
|
|
254
|
+
}
|
|
255
|
+
|
|
208
256
|
async function openAndConsumeStream(sessionId, controller) {
|
|
209
257
|
const consoleUrl = getConsoleUrl();
|
|
210
258
|
const url = `${consoleUrl.replace(/\/$/, '')}/api/capture/sessions/${encodeURIComponent(sessionId)}/events`;
|
|
@@ -447,13 +495,14 @@ export function createCaptureChannelWatcher({
|
|
|
447
495
|
if (pinDebounceTimer) clearTimeout(pinDebounceTimer);
|
|
448
496
|
pinDebounceTimer = setTimeout(() => {
|
|
449
497
|
pinDebounceTimer = null;
|
|
450
|
-
reevaluatePin();
|
|
498
|
+
reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
|
|
451
499
|
}, PIN_WATCH_DEBOUNCE_MS);
|
|
452
500
|
}
|
|
453
501
|
|
|
454
|
-
function reevaluatePin() {
|
|
502
|
+
async function reevaluatePin() {
|
|
455
503
|
if (stopped) return;
|
|
456
|
-
const focus =
|
|
504
|
+
const focus = await fetchLiveCaptureFocus();
|
|
505
|
+
if (stopped) return; // may have been stopped while the pin fetch was in flight
|
|
457
506
|
const nextSessionId = focus ? deriveSessionIdFromFocus(focus) : null;
|
|
458
507
|
|
|
459
508
|
if (nextSessionId === activeSessionId) {
|
|
@@ -533,7 +582,8 @@ export function createCaptureChannelWatcher({
|
|
|
533
582
|
return;
|
|
534
583
|
}
|
|
535
584
|
startPinWatcher();
|
|
536
|
-
|
|
585
|
+
// Initial open if pin already exists — fire-and-forget (async network read).
|
|
586
|
+
reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
|
|
537
587
|
}
|
|
538
588
|
|
|
539
589
|
function stop() {
|
|
@@ -19,6 +19,17 @@ import {
|
|
|
19
19
|
removeOutboxEntry,
|
|
20
20
|
stashCaptureArgs,
|
|
21
21
|
} from "./capture-outbox.mjs";
|
|
22
|
+
import {
|
|
23
|
+
CONSOLE_NOT_SIGNED_IN,
|
|
24
|
+
consoleAuthErrorMessage,
|
|
25
|
+
consoleUnauthorizedMessage,
|
|
26
|
+
} from "./console-fetch.mjs";
|
|
27
|
+
// The runnable form of a HeySpex command on THIS machine — `hs login` when the
|
|
28
|
+
// bin is on PATH, else an absolute-path `node "<install>/bin/hs.mjs" login`.
|
|
29
|
+
// Never an `npx` form: npx resolves from the READER's working directory, and
|
|
30
|
+
// outside a checkout `npx hs` installs the unrelated public package hs@2.0.1
|
|
31
|
+
// ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
|
|
32
|
+
import { hsCommand } from "../../scripts/lib/hs-command.mjs";
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* Error taxonomy for capture-session console calls ([[HS-K27H-DIAG-0G86]]).
|
|
@@ -39,14 +50,28 @@ import {
|
|
|
39
50
|
* message verbatim. (Entries ALREADY in the outbox from a genuine
|
|
40
51
|
* transport failure still pause the drain on 5xx and retry later — the
|
|
41
52
|
* ban is on adding new entries for an answered request.)
|
|
53
|
+
* - `CONSOLE_LOCAL_URL_IN_CLOUD_MODE` / `CONSOLE_NOT_SIGNED_IN`
|
|
54
|
+
* ([[HS-9R6S-N8VP-DIAG-capture-writes-report-the-console-as]]): the call
|
|
55
|
+
* never had a chance — cloud mode pointed at a local console, no bearer
|
|
56
|
+
* token on this machine, or an answered HTTP 401. NEVER stash: nothing
|
|
57
|
+
* becomes reachable until `hs login` runs, so a "your words are buffered
|
|
58
|
+
* and will sync" promise would be false AND would hide the real cause.
|
|
59
|
+
* console-fetch stamps `authProblem` on the transport result; the 401 form
|
|
60
|
+
* stays inside the 4xx "the server ANSWERED, never stash" family and only
|
|
61
|
+
* swaps the generic CAPTURE_CONFLICT code for an actionable one.
|
|
42
62
|
*/
|
|
43
63
|
export const CAPTURE_ERROR_UNREACHABLE = "CONSOLE_UNREACHABLE";
|
|
44
64
|
export const CAPTURE_ERROR_CONFLICT = "CAPTURE_CONFLICT";
|
|
45
65
|
export const CAPTURE_ERROR_FAILED = "PATCH_FAILED";
|
|
66
|
+
export const CAPTURE_ERROR_NOT_SIGNED_IN = CONSOLE_NOT_SIGNED_IN;
|
|
46
67
|
|
|
47
68
|
/** Classify a `consoleFetch` result by HTTP status range. */
|
|
48
69
|
export function classifyConsoleFetchError(result) {
|
|
70
|
+
// Sign-in problems are stamped by console-fetch on the status-0 result and
|
|
71
|
+
// outrank the generic transport class — they never buffer.
|
|
72
|
+
if (result.authProblem) return result.authProblem;
|
|
49
73
|
if (result.status === 0) return CAPTURE_ERROR_UNREACHABLE;
|
|
74
|
+
if (result.status === 401) return CONSOLE_NOT_SIGNED_IN;
|
|
50
75
|
if (result.status >= 400 && result.status < 500)
|
|
51
76
|
return CAPTURE_ERROR_CONFLICT;
|
|
52
77
|
return CAPTURE_ERROR_FAILED;
|
|
@@ -66,7 +91,7 @@ export function extractConsoleMessage(result) {
|
|
|
66
91
|
* - list/create helper failures: `{ ok:false, error, result }` (status on `result`)
|
|
67
92
|
*
|
|
68
93
|
* @returns {{
|
|
69
|
-
* kind: 'transport'|'client'|'server',
|
|
94
|
+
* kind: 'transport'|'auth'|'client'|'server',
|
|
70
95
|
* shouldStash: boolean,
|
|
71
96
|
* error: string,
|
|
72
97
|
* http_status: number,
|
|
@@ -86,6 +111,38 @@ export function describeCaptureWriteFailure(failure) {
|
|
|
86
111
|
const serverCode = typeof result?.body?.error === "string" ? result.body.error : null;
|
|
87
112
|
const serverMessage = typeof result?.body?.message === "string" ? result.body.message : null;
|
|
88
113
|
|
|
114
|
+
// Sign-in problems first ([[HS-9R6S-N8VP-DIAG]]). A refused connection to the
|
|
115
|
+
// WRONG console is not an outage, and a 401 is not a capture conflict —
|
|
116
|
+
// both are "run hs login", and neither may touch the offline buffer.
|
|
117
|
+
const authProblem =
|
|
118
|
+
typeof result?.authProblem === "string" ? result.authProblem : null;
|
|
119
|
+
if (status === 0 && authProblem) {
|
|
120
|
+
return {
|
|
121
|
+
kind: "auth",
|
|
122
|
+
shouldStash: false,
|
|
123
|
+
error: authProblem,
|
|
124
|
+
http_status: 0,
|
|
125
|
+
server_error_code: null,
|
|
126
|
+
server_message: null,
|
|
127
|
+
message:
|
|
128
|
+
result?.authMessage
|
|
129
|
+
|| consoleAuthErrorMessage(authProblem, result?.url)
|
|
130
|
+
|| null,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (status === 401) {
|
|
134
|
+
const verbatim401 = serverMessage ? ` The server said: ${serverMessage}` : "";
|
|
135
|
+
return {
|
|
136
|
+
kind: "auth",
|
|
137
|
+
shouldStash: false,
|
|
138
|
+
error: CONSOLE_NOT_SIGNED_IN,
|
|
139
|
+
http_status: 401,
|
|
140
|
+
server_error_code: serverCode,
|
|
141
|
+
server_message: serverMessage,
|
|
142
|
+
message: `${consoleUnauthorizedMessage(result?.url)}${verbatim401}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
89
146
|
// status 0 ⇔ genuine transport failure (ECONNREFUSED / DNS / timeout /
|
|
90
147
|
// abort — console-fetch stamps status 0 on every non-response). Anything
|
|
91
148
|
// else is an HTTP response the server actually returned.
|
|
@@ -142,6 +199,11 @@ export function describeCaptureWriteFailure(failure) {
|
|
|
142
199
|
* structured shape.
|
|
143
200
|
* - 4xx → `status: "refused"` + the server's error/message verbatim (AC2/AC5).
|
|
144
201
|
* - 5xx → `status: "failed"` + the server's message verbatim (AC3/AC5).
|
|
202
|
+
* - sign-in (`authProblem` on a status-0 result, or an answered 401) →
|
|
203
|
+
* `status: "blocked"`, `buffered: false`, `action` = the sign-in command in
|
|
204
|
+
* the form this machine can run (`hs login`, or `node "<abs>/bin/hs.mjs" login`),
|
|
205
|
+
* and a message
|
|
206
|
+
* that says plainly the words were NOT saved ([[HS-9R6S-N8VP-DIAG]]).
|
|
145
207
|
*/
|
|
146
208
|
export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
|
|
147
209
|
const d = describeCaptureWriteFailure(failure);
|
|
@@ -161,6 +223,23 @@ export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
|
|
|
161
223
|
pending_sync: pending,
|
|
162
224
|
};
|
|
163
225
|
}
|
|
226
|
+
if (d.kind === "auth") {
|
|
227
|
+
// NOT buffered, and the payload must not imply otherwise ([[HS-9R6S-N8VP-DIAG]]).
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
status: "blocked",
|
|
231
|
+
error: d.error,
|
|
232
|
+
http_status: d.http_status,
|
|
233
|
+
server_error: d.server_error_code,
|
|
234
|
+
console_error: d.server_message ?? d.server_error_code,
|
|
235
|
+
buffered: false,
|
|
236
|
+
stashed: [],
|
|
237
|
+
// The form that actually runs on this machine, never the bare `hs`
|
|
238
|
+
// spelling ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
|
|
239
|
+
action: hsCommand("login"),
|
|
240
|
+
message: `This capture was not filed, and your words were not buffered. ${d.message}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
164
243
|
return {
|
|
165
244
|
ok: false,
|
|
166
245
|
status: d.kind === "client" ? "refused" : "failed",
|