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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cloud-tool-surface.mjs — the tools the REMOTE MCP endpoint serves.
|
|
3
|
+
*
|
|
4
|
+
* `POST /api/mcp` on the console runs `dispatchHeySpexTool` in-process on a
|
|
5
|
+
* shared serverless runtime with NO vault checkout: `vaultRoot` points at a
|
|
6
|
+
* directory that does not exist, there is no git, and `~/.heyspex/*` is the
|
|
7
|
+
* lambda's own (empty) home. Only handlers that do their work through the
|
|
8
|
+
* console HTTP API in the neon read tier are correct there. This module is
|
|
9
|
+
* the explicit allowlist; `tools/list` on the remote endpoint returns exactly
|
|
10
|
+
* these, and `tools/call` on anything else answers "needs the local server".
|
|
11
|
+
*
|
|
12
|
+
* Every entry was verified by reading its handler (audit 2026-09-14, WP6):
|
|
13
|
+
*
|
|
14
|
+
* PURE console-API handlers (no vault disk, no git, no home):
|
|
15
|
+
* recall_memories (memory-tools.mjs neon branch), list_procedures
|
|
16
|
+
* (procedure-handlers.mjs loadCatalogViaTier), handoff_record/load
|
|
17
|
+
* (session-tools.mjs), claim_spec / release_spec / report_progress /
|
|
18
|
+
* record_wip / defer_spec / claim_list / claim_acquire / claim_release
|
|
19
|
+
* (claim-tools.mjs, all postClaimEndpoint/consoleFetch), pin_get / pin_set
|
|
20
|
+
* / get_focus_areas / set_focus_areas (focus-tools.mjs), milestone_show /
|
|
21
|
+
* events_show (events-tools.mjs), list_known_tags (spec-edit-tools.mjs).
|
|
22
|
+
*
|
|
23
|
+
* Console-API handlers whose optional disk touches are read-only or fail
|
|
24
|
+
* open (result still correct, some enrichment degrades):
|
|
25
|
+
* get_procedure (marker write try/catch'd), preflight (marker write →
|
|
26
|
+
* `marker_warning`; recipes readdir guarded → []), submit_for_review
|
|
27
|
+
* (`.heyspex/config.yaml` review grain → default), file_spec (POST
|
|
28
|
+
* /api/specs; landing probe defaults expectTier=cloud in neon),
|
|
29
|
+
* next_on_deck (scheduler cohort + direct spec are tier-split to the
|
|
30
|
+
* console; the synchronous enrichers — plans, amendments, WIP fallback,
|
|
31
|
+
* memory scoring — are existsSync-guarded and silently empty),
|
|
32
|
+
* active_capture (DB-first via the console; disk fallbacks guarded).
|
|
33
|
+
*
|
|
34
|
+
* Deliberately EXCLUDED (disk/git-bound or process-global in a shared runtime):
|
|
35
|
+
* heyspex_update_capture – stashes to `<vaultRoot>/.heyspex/capture-outbox`
|
|
36
|
+
* on transport failure with no try/catch
|
|
37
|
+
* (capture-outbox.mjs) — a lambda outbox is a
|
|
38
|
+
* false "your words will sync" promise.
|
|
39
|
+
* heyspex_validate_linkage – full readdir walk of `<vaultRoot>/specs` (validate-linkage.mjs).
|
|
40
|
+
* heyspex_next_review – `git status`/`git show` via execSync + disk-only queue (next-review.mjs).
|
|
41
|
+
* heyspex_dev_mode – `set` writes `~/.heyspex/dev-mode.json` + credentials.yaml;
|
|
42
|
+
* `get` would report the LAMBDA's mode, not the caller's.
|
|
43
|
+
* heyspex_load_tools – mutates the process-global `loadedToolNames`
|
|
44
|
+
* (tool-catalog.mjs) shared by every tenant in the
|
|
45
|
+
* container; the remote surface is fixed anyway.
|
|
46
|
+
* heyspex_settings_list/get – read `<vaultRoot>/.heyspex/config.yaml` → misleading defaults.
|
|
47
|
+
* heyspex_design_* – read `<vaultRoot>/spex-console/public/design-system/*` and DESIGN.md.
|
|
48
|
+
* heyspex_list_recipes – readdir `<vaultRoot>/specs/recipes`.
|
|
49
|
+
* save_research, push_plan, backfill_epic, promote_*, register_spec,
|
|
50
|
+
* edit_file, record_alignment (git shelling), regen_*, ground_plan,
|
|
51
|
+
* plan_review/plan_qc, canvas_inbox, session_end/hooks_*, dev_server_status,
|
|
52
|
+
* fleet_queue_*, firecrawl/research-plan, import_agent, start_recipe,
|
|
53
|
+
* verify_reproduction, split_research, active/complete_research, pin_capture,
|
|
54
|
+
* suggest_pillar, validate_user_skills, next_resolution, resolve_*,
|
|
55
|
+
* claim_file/release_file_claim, evidence_show/export_events, propose_metadata
|
|
56
|
+
* – vault-disk, git, or local-process bound per
|
|
57
|
+
* the cloud-first audit; not re-verified here.
|
|
58
|
+
*/
|
|
59
|
+
import { TOOLS } from './tool-catalog.mjs';
|
|
60
|
+
|
|
61
|
+
/** Ordered allowlist — order is the order `tools/list` advertises them. */
|
|
62
|
+
export const CLOUD_TOOL_NAMES = Object.freeze([
|
|
63
|
+
// session + procedure discovery
|
|
64
|
+
'heyspex_recall_memories',
|
|
65
|
+
'heyspex_list_procedures',
|
|
66
|
+
'heyspex_get_procedure',
|
|
67
|
+
'heyspex_preflight',
|
|
68
|
+
'heyspex_handoff_load',
|
|
69
|
+
'heyspex_handoff_record',
|
|
70
|
+
// the claim contract
|
|
71
|
+
'heyspex_next_on_deck',
|
|
72
|
+
'heyspex_claim_spec',
|
|
73
|
+
'heyspex_release_spec',
|
|
74
|
+
'heyspex_report_progress',
|
|
75
|
+
'heyspex_submit_for_review',
|
|
76
|
+
'heyspex_record_wip',
|
|
77
|
+
// author_summary_then_claim completion ([[HS-T41O-NZ8S-DIAG]]): in cloud
|
|
78
|
+
// write mode the handler is GET + PUT /api/specs/:id only — no disk. The
|
|
79
|
+
// lambda's own home resolves to prod/cloud, so the local branch never runs.
|
|
80
|
+
'heyspex_author_summary',
|
|
81
|
+
'heyspex_defer_spec',
|
|
82
|
+
'heyspex_claim_list',
|
|
83
|
+
'heyspex_claim_acquire',
|
|
84
|
+
'heyspex_claim_release',
|
|
85
|
+
// filing + read-side context
|
|
86
|
+
'heyspex_file_spec',
|
|
87
|
+
'heyspex_active_capture',
|
|
88
|
+
'heyspex_milestone_show',
|
|
89
|
+
'heyspex_events_show',
|
|
90
|
+
// [[HS-YFLS]] fix 2: the duplicate check before filing. `handleSearchSpecs`
|
|
91
|
+
// is PURE consoleFetch (GET /api/search only) — no vault disk, no git, no
|
|
92
|
+
// home — so it is correct on the shared runtime, and it is exactly the tool a
|
|
93
|
+
// remote agent needs before POST /api/specs.
|
|
94
|
+
'heyspex_search_specs',
|
|
95
|
+
'heyspex_list_known_tags',
|
|
96
|
+
// focus / pin control plane
|
|
97
|
+
'heyspex_pin_get',
|
|
98
|
+
'heyspex_pin_set',
|
|
99
|
+
'heyspex_get_focus_areas',
|
|
100
|
+
'heyspex_set_focus_areas',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const CLOUD_TOOL_NAME_SET = new Set(CLOUD_TOOL_NAMES);
|
|
104
|
+
const CATALOG_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
|
|
105
|
+
|
|
106
|
+
/** True when `name` is served by the remote endpoint. */
|
|
107
|
+
export function isCloudSafeTool(name) {
|
|
108
|
+
return CLOUD_TOOL_NAME_SET.has(name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** True when `name` exists in the full catalog (local server surface). */
|
|
112
|
+
export function isCatalogTool(name) {
|
|
113
|
+
return CATALOG_BY_NAME.has(name);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The catalog entries for the remote surface, in allowlist order. A name the
|
|
118
|
+
* catalog no longer carries is dropped (never a hole in `tools/list`); the
|
|
119
|
+
* unit test asserts the two stay in sync so a drift fails CI, not a client.
|
|
120
|
+
*/
|
|
121
|
+
export function cloudToolSurface() {
|
|
122
|
+
return CLOUD_TOOL_NAMES.map((name) => CATALOG_BY_NAME.get(name)).filter(Boolean);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Allowlisted names missing from the catalog — expected empty; surfaced for the test + doctor. */
|
|
126
|
+
export function cloudSurfaceDrift() {
|
|
127
|
+
return CLOUD_TOOL_NAMES.filter((name) => !CATALOG_BY_NAME.has(name));
|
|
128
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cloud-vault-writes.mjs — the WRITE-side twin of `read-path.mjs`.
|
|
4
|
+
*
|
|
5
|
+
* `read-path.mjs` decides `disk` vs `neon` for every vault READ the MCP
|
|
6
|
+
* server makes. This module is the single place every vault WRITE goes when
|
|
7
|
+
* `resolveVaultWriteMode()` says `cloud`: a create becomes
|
|
8
|
+
* `POST /api/specs` (the console mints the id, the family and the filename),
|
|
9
|
+
* an edit of an existing spec becomes `PUT /api/specs/:id`, and a listing
|
|
10
|
+
* needed to build either becomes `GET /api/specs?type=…`.
|
|
11
|
+
*
|
|
12
|
+
* Why this exists: a disk write in cloud mode is a SPLIT BRAIN — a markdown
|
|
13
|
+
* file on a machine the console's index has never heard of, on a machine that
|
|
14
|
+
* may not even hold a clone of the vault. `handleSaveResearch`
|
|
15
|
+
* (research-handlers.mjs) and `handleFileSpec` (filing-tools.mjs) already
|
|
16
|
+
* file through the console; this module gives every OTHER writer (the
|
|
17
|
+
* promotion helpers, plan-push, register-spec, spec_edit, capture-focus) the
|
|
18
|
+
* same two verbs without each re-deriving the request shape, the
|
|
19
|
+
* idempotency-key rule, or the failure envelope.
|
|
20
|
+
*
|
|
21
|
+
* Transport-free by design, like read-path.mjs: callers inject
|
|
22
|
+
* `consoleFetch`. The module never imports console-transport.mjs, so every
|
|
23
|
+
* test drives it with a recorder and never opens a socket.
|
|
24
|
+
*
|
|
25
|
+
* Failure contract: every failure throws {@link CloudWriteUnreachableError},
|
|
26
|
+
* whose `message` STARTS WITH `CONSOLE_UNREACHABLE_CLOUD_MODE` and names the
|
|
27
|
+
* route and the console — the same string shape `readViaSelector` produces on
|
|
28
|
+
* the read side, so one `catch` in a handler can render either. There is no
|
|
29
|
+
* disk fallback: a cloud-reliant session that cannot reach its console fails
|
|
30
|
+
* loudly rather than leaving bytes behind that nothing indexes.
|
|
31
|
+
*
|
|
32
|
+
* There is deliberately NO rollback. `commitWrites`'s disk transaction can
|
|
33
|
+
* restore a snapshot; the console has no "un-create". Instead
|
|
34
|
+
* {@link commitWritesViaConsole} stops at the FIRST failure and reports which
|
|
35
|
+
* step failed and which steps already landed, so the caller can say exactly
|
|
36
|
+
* what state the vault is in ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]] AC5).
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { randomUUID } from 'node:crypto';
|
|
40
|
+
import { walkSpecListPages } from './list-tools.mjs';
|
|
41
|
+
import { parseFrontmatter } from './vault-io.mjs';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Raised when a `cloud`-mode write fails. Mirrors
|
|
45
|
+
* `ConsoleUnreachableCloudModeError` (read-path.mjs) exactly — same `name`,
|
|
46
|
+
* same message prefix — so callers that already render one render both.
|
|
47
|
+
*/
|
|
48
|
+
export class CloudWriteUnreachableError extends Error {
|
|
49
|
+
constructor(message, details = {}) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = 'CONSOLE_UNREACHABLE_CLOUD_MODE';
|
|
52
|
+
Object.assign(this, details);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Build the canonical failure error for a route + console + detail. */
|
|
57
|
+
export function cloudWriteFailure(routeLabel, consoleUrl, detail, details = {}) {
|
|
58
|
+
return new CloudWriteUnreachableError(
|
|
59
|
+
`CONSOLE_UNREACHABLE_CLOUD_MODE: route "${routeLabel}" failed against console ${consoleUrl ?? '<unknown console>'} — ${detail}`,
|
|
60
|
+
{ route: routeLabel, console_url: consoleUrl ?? null, ...details },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One-line summary of a consoleFetch failure result, for the message above. */
|
|
65
|
+
function describeResult(result) {
|
|
66
|
+
if (!result) return 'no response';
|
|
67
|
+
const bodyText =
|
|
68
|
+
result.error ||
|
|
69
|
+
(result.body ? JSON.stringify(result.body).slice(0, 400) : '') ||
|
|
70
|
+
'';
|
|
71
|
+
return `${result.status ?? '?'}${bodyText ? ` ${bodyText}` : ''}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fields the SERVER mints and a create request must therefore never carry.
|
|
76
|
+
* `id`/`file_id` are the file key itself (server-minted since 2026-07-10 —
|
|
77
|
+
* a locally minted key in a file-less vault collides with a cloud-side one);
|
|
78
|
+
* `created`/`updated`/`created_tz`/`updated_tz` are stamped by `createSpec`.
|
|
79
|
+
* `family` is NOT stripped: an explicit family is a legal, meaningful
|
|
80
|
+
* override (`createSpec` honours it over parent inheritance).
|
|
81
|
+
*/
|
|
82
|
+
export const SERVER_MINTED_FIELDS = Object.freeze([
|
|
83
|
+
'id',
|
|
84
|
+
'file_id',
|
|
85
|
+
'created',
|
|
86
|
+
'updated',
|
|
87
|
+
'created_tz',
|
|
88
|
+
'updated_tz',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The status the console seeds on every non-ADR create (`createSpec` in
|
|
93
|
+
* spex-console/src/lib/vault.ts). Sending it on a POST asks for nothing.
|
|
94
|
+
*/
|
|
95
|
+
export const DEFAULT_INITIAL_STATUS = 'open';
|
|
96
|
+
|
|
97
|
+
/** True when `value` is the default initial status (case/whitespace-insensitive). */
|
|
98
|
+
export function isDefaultInitialStatus(value) {
|
|
99
|
+
return typeof value === 'string' && value.trim().toLowerCase() === DEFAULT_INITIAL_STATUS;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Copy of `frontmatter` with every server-minted field removed. */
|
|
103
|
+
export function stripServerMintedFields(frontmatter = {}) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const [key, value] of Object.entries(frontmatter ?? {})) {
|
|
106
|
+
if (SERVER_MINTED_FIELDS.includes(key)) continue;
|
|
107
|
+
out[key] = value;
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Split a raw `.md` file (the shape every disk writer already builds) into the
|
|
114
|
+
* `{ frontmatter, body }` pair both console write routes take. Reuses
|
|
115
|
+
* `parseFrontmatter` from vault-io.mjs so the YAML dialect the vault writes is
|
|
116
|
+
* the YAML dialect this reads — no second parser to drift.
|
|
117
|
+
*/
|
|
118
|
+
export function splitRawSpec(raw) {
|
|
119
|
+
const { frontmatter, body } = parseFrontmatter(String(raw ?? ''));
|
|
120
|
+
return { frontmatter: frontmatter ?? {}, body: body ?? '' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Basename (no extension) from a console-returned relPath. */
|
|
124
|
+
export function basenameFromRelPath(relPath) {
|
|
125
|
+
if (!relPath) return null;
|
|
126
|
+
return String(relPath)
|
|
127
|
+
.split(/[\\/]/)
|
|
128
|
+
.pop()
|
|
129
|
+
.replace(/\.md$/i, '');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `POST /api/specs` — create one spec through the console.
|
|
134
|
+
*
|
|
135
|
+
* The request shape is `buildFileSpecPostBody`'s (filing-tools.mjs), down to
|
|
136
|
+
* the per-call `idempotency-key` header and `retryOnTimeout: false` (the
|
|
137
|
+
* [[HS-2FQY]] double-mint guard: a blind retry against a REMOTE console can
|
|
138
|
+
* land on a different serverless instance whose in-memory idempotency
|
|
139
|
+
* registry cannot replay the first create, minting a second id for one spec).
|
|
140
|
+
*
|
|
141
|
+
* Returns `{ specId, relPath, basename, spec, consoleWrite, warnings }`. The
|
|
142
|
+
* BASENAME is the load-bearing return value for promotion flows: every
|
|
143
|
+
* sibling write references the new document by `[[basename]]`, and in cloud
|
|
144
|
+
* mode that basename is only knowable after this call returns — which is why
|
|
145
|
+
* cloud-mode promotion is create-first, link-second.
|
|
146
|
+
*/
|
|
147
|
+
export async function createSpecViaConsole({
|
|
148
|
+
consoleFetch,
|
|
149
|
+
consoleUrl = null,
|
|
150
|
+
type,
|
|
151
|
+
name,
|
|
152
|
+
summary = null,
|
|
153
|
+
frontmatter = null,
|
|
154
|
+
body = null,
|
|
155
|
+
actingAgent = null,
|
|
156
|
+
idempotencyKey = null,
|
|
157
|
+
routeLabel = 'POST /api/specs',
|
|
158
|
+
}) {
|
|
159
|
+
if (typeof consoleFetch !== 'function') {
|
|
160
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, 'no consoleFetch dependency was injected');
|
|
161
|
+
}
|
|
162
|
+
if (typeof type !== 'string' || !type.trim()) {
|
|
163
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, 'a spec `type` is required to create through the console');
|
|
164
|
+
}
|
|
165
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
166
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, 'a spec `name` is required to create through the console');
|
|
167
|
+
}
|
|
168
|
+
const cleanedFm = stripServerMintedFields(frontmatter ?? {});
|
|
169
|
+
// `name`/`type`/`summary` ride at the TOP level of the request (the route's
|
|
170
|
+
// contract); leaving duplicates inside `frontmatter` lets the two disagree.
|
|
171
|
+
delete cleanedFm.name;
|
|
172
|
+
delete cleanedFm.type;
|
|
173
|
+
// [[HS-ISZP]]: `status:` is owned by the status route — the create route
|
|
174
|
+
// refuses it out of band (`out_of_band_status_write`). The DEFAULT initial
|
|
175
|
+
// value is not a transition (the server seeds `open` itself), so it is
|
|
176
|
+
// dropped here for every disk-template-derived body (research stub,
|
|
177
|
+
// diagnose, epic/task stubs). Any OTHER value is deliberately left in so the
|
|
178
|
+
// server refuses it loudly rather than this client silently downgrading it.
|
|
179
|
+
if (isDefaultInitialStatus(cleanedFm.status)) delete cleanedFm.status;
|
|
180
|
+
const requestBody = {
|
|
181
|
+
type: type.trim(),
|
|
182
|
+
name: name.trim(),
|
|
183
|
+
...(summary ? { summary } : {}),
|
|
184
|
+
...(Object.keys(cleanedFm).length > 0 ? { frontmatter: cleanedFm } : {}),
|
|
185
|
+
...(typeof body === 'string' ? { body } : {}),
|
|
186
|
+
...(actingAgent ? { acting_agent: actingAgent } : {}),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
let result;
|
|
190
|
+
try {
|
|
191
|
+
result = await consoleFetch('/api/specs', {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
body: requestBody,
|
|
194
|
+
headers: { 'idempotency-key': idempotencyKey ?? randomUUID() },
|
|
195
|
+
retryOnTimeout: false,
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, String(err?.message ?? err), {
|
|
199
|
+
attempted: { type: requestBody.type, name: requestBody.name },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!result?.ok) {
|
|
203
|
+
throw cloudWriteFailure(routeLabel, result?.url ?? consoleUrl, describeResult(result), {
|
|
204
|
+
status: result?.status ?? null,
|
|
205
|
+
console_response: result?.body ?? null,
|
|
206
|
+
attempted: { type: requestBody.type, name: requestBody.name },
|
|
207
|
+
...(result?.errorKind === 'timeout'
|
|
208
|
+
? {
|
|
209
|
+
hint:
|
|
210
|
+
'The create may still have completed server-side after this client timed out. '
|
|
211
|
+
+ 'Search for a spec with this exact name before retrying — a blind retry can mint a duplicate id ([[HS-2FQY]]).',
|
|
212
|
+
}
|
|
213
|
+
: {}),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const spec = result.body?.spec ?? null;
|
|
217
|
+
const specId = typeof spec?.id === 'string' && spec.id ? spec.id : null;
|
|
218
|
+
const relPath = spec?.relPath ?? spec?.path ?? null;
|
|
219
|
+
if (!specId) {
|
|
220
|
+
throw cloudWriteFailure(
|
|
221
|
+
routeLabel,
|
|
222
|
+
result.url ?? consoleUrl,
|
|
223
|
+
`answered ${result.status} but carried no spec.id — treat the spec as UNCREATED and check the console before retrying`,
|
|
224
|
+
{ status: result.status, console_response: result.body ?? null },
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
specId,
|
|
229
|
+
relPath,
|
|
230
|
+
basename: basenameFromRelPath(relPath) ?? specId,
|
|
231
|
+
spec,
|
|
232
|
+
consoleWrite: result.body?.write ?? null,
|
|
233
|
+
warnings: Array.isArray(result.body?.warnings) ? result.body.warnings : [],
|
|
234
|
+
consoleUrl: result.url ?? consoleUrl,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* `PUT /api/specs/:id` — replace an existing spec's frontmatter + body.
|
|
240
|
+
*
|
|
241
|
+
* The route takes `{ frontmatter: object, body: string }` and answers 404 for
|
|
242
|
+
* an id it cannot resolve (a real answer: the spec is not in this caller's
|
|
243
|
+
* vault), which is surfaced here as a distinct `notFound: true` failure so a
|
|
244
|
+
* caller can tell "unreachable console" from "no such spec".
|
|
245
|
+
*/
|
|
246
|
+
export async function updateSpecViaConsole({
|
|
247
|
+
consoleFetch,
|
|
248
|
+
consoleUrl = null,
|
|
249
|
+
specId,
|
|
250
|
+
frontmatter,
|
|
251
|
+
body,
|
|
252
|
+
routeLabel = null,
|
|
253
|
+
}) {
|
|
254
|
+
const route = routeLabel ?? `PUT /api/specs/${specId}`;
|
|
255
|
+
if (typeof consoleFetch !== 'function') {
|
|
256
|
+
throw cloudWriteFailure(route, consoleUrl, 'no consoleFetch dependency was injected');
|
|
257
|
+
}
|
|
258
|
+
if (typeof specId !== 'string' || !specId.trim()) {
|
|
259
|
+
throw cloudWriteFailure(route, consoleUrl, 'a spec id is required to update through the console');
|
|
260
|
+
}
|
|
261
|
+
if (typeof frontmatter !== 'object' || frontmatter === null) {
|
|
262
|
+
throw cloudWriteFailure(route, consoleUrl, 'PUT /api/specs/:id requires a frontmatter object');
|
|
263
|
+
}
|
|
264
|
+
let result;
|
|
265
|
+
try {
|
|
266
|
+
result = await consoleFetch(`/api/specs/${encodeURIComponent(specId)}`, {
|
|
267
|
+
method: 'PUT',
|
|
268
|
+
body: { frontmatter, body: typeof body === 'string' ? body : '' },
|
|
269
|
+
});
|
|
270
|
+
} catch (err) {
|
|
271
|
+
throw cloudWriteFailure(route, consoleUrl, String(err?.message ?? err), { spec_id: specId });
|
|
272
|
+
}
|
|
273
|
+
if (!result?.ok) {
|
|
274
|
+
throw cloudWriteFailure(route, result?.url ?? consoleUrl, describeResult(result), {
|
|
275
|
+
status: result?.status ?? null,
|
|
276
|
+
console_response: result?.body ?? null,
|
|
277
|
+
spec_id: specId,
|
|
278
|
+
...(result?.status === 404 ? { notFound: true } : {}),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const spec = result.body?.spec ?? null;
|
|
282
|
+
const relPath = spec?.relPath ?? spec?.path ?? null;
|
|
283
|
+
return {
|
|
284
|
+
specId: spec?.id ?? specId,
|
|
285
|
+
relPath,
|
|
286
|
+
basename: basenameFromRelPath(relPath) ?? spec?.id ?? specId,
|
|
287
|
+
spec,
|
|
288
|
+
consoleUrl: result.url ?? consoleUrl,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* `GET /api/specs?type=…` — the cloud twin of a `readdirSync(specs/<folder>)`
|
|
294
|
+
* scan. The keyset walk itself is {@link walkSpecListPages} (list-tools.mjs),
|
|
295
|
+
* the dependency-free primitive every other cloud listing in this package
|
|
296
|
+
* already uses — reused rather than re-implemented so there is exactly one
|
|
297
|
+
* place that knows the `next_cursor` contract. This wrapper only adds the
|
|
298
|
+
* `type`/`status`/`tag` filter and the shared failure envelope, so a listing
|
|
299
|
+
* that fails reads identically to a create or an edit that fails.
|
|
300
|
+
*
|
|
301
|
+
* Returns the raw summary rows (`{ id, basename, type, status, … }`) the route
|
|
302
|
+
* already emits — no reshaping, so callers keep whatever fields they read.
|
|
303
|
+
*/
|
|
304
|
+
export async function listSpecsViaConsole({
|
|
305
|
+
consoleFetch,
|
|
306
|
+
consoleUrl = null,
|
|
307
|
+
type = null,
|
|
308
|
+
status = null,
|
|
309
|
+
tag = null,
|
|
310
|
+
limit = 500,
|
|
311
|
+
maxPages = 50,
|
|
312
|
+
}) {
|
|
313
|
+
const routeLabel = `GET /api/specs${type ? `?type=${type}` : ''}`;
|
|
314
|
+
if (typeof consoleFetch !== 'function') {
|
|
315
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, 'no consoleFetch dependency was injected');
|
|
316
|
+
}
|
|
317
|
+
const params = new URLSearchParams();
|
|
318
|
+
if (type) params.set('type', type);
|
|
319
|
+
if (status) params.set('status', status);
|
|
320
|
+
if (tag) params.set('tag', tag);
|
|
321
|
+
const query = params.toString();
|
|
322
|
+
try {
|
|
323
|
+
return await walkSpecListPages(consoleFetch, `/api/specs${query ? `?${query}` : ''}`, {
|
|
324
|
+
limit,
|
|
325
|
+
maxPages,
|
|
326
|
+
});
|
|
327
|
+
} catch (err) {
|
|
328
|
+
throw cloudWriteFailure(routeLabel, consoleUrl, String(err?.message ?? err));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The cloud twin of `commitWrites` (promotion-helpers.mjs).
|
|
334
|
+
*
|
|
335
|
+
* Takes the SAME `{ kind: 'create' | 'update', absPath, content }` descriptors
|
|
336
|
+
* the disk path takes, so a caller switches paths with one `if` and never
|
|
337
|
+
* builds two different write plans. Cloud metadata that cannot be derived from
|
|
338
|
+
* the markdown may ride along on an optional `cloud` property:
|
|
339
|
+
* `{ cloud: { type, name, summary, specId } }`.
|
|
340
|
+
* When absent it is derived from the content's own frontmatter (`type:`,
|
|
341
|
+
* `name:`, `summary:`, `id:`) — which every vault document already carries.
|
|
342
|
+
*
|
|
343
|
+
* Writes run IN ORDER and stop at the first failure. Nothing is rolled back
|
|
344
|
+
* (the console has no un-create); the returned error names the failing step
|
|
345
|
+
* and lists the ones that already landed, so the caller reports the real state
|
|
346
|
+
* rather than implying a clean abort.
|
|
347
|
+
*
|
|
348
|
+
* Returns `{ ok: true, results }` where each result is
|
|
349
|
+
* `{ kind, absPath, specId, basename, relPath }`, or
|
|
350
|
+
* `{ ok: false, error, completed }` — the same `{ ok }`-shaped verdict
|
|
351
|
+
* `commitWrites` returns, so callers branch identically.
|
|
352
|
+
*/
|
|
353
|
+
export async function commitWritesViaConsole(writes, { consoleFetch, consoleUrl = null, actingAgent = null } = {}) {
|
|
354
|
+
const results = [];
|
|
355
|
+
for (let i = 0; i < (writes ?? []).length; i++) {
|
|
356
|
+
const w = writes[i];
|
|
357
|
+
const { frontmatter, body } = splitRawSpec(w.content);
|
|
358
|
+
const meta = w.cloud ?? {};
|
|
359
|
+
try {
|
|
360
|
+
if (w.kind === 'create') {
|
|
361
|
+
const type = meta.type ?? frontmatter.type ?? null;
|
|
362
|
+
const name = meta.name ?? frontmatter.name ?? null;
|
|
363
|
+
const summary = meta.summary ?? frontmatter.summary ?? null;
|
|
364
|
+
const created = await createSpecViaConsole({
|
|
365
|
+
consoleFetch,
|
|
366
|
+
consoleUrl,
|
|
367
|
+
type,
|
|
368
|
+
name,
|
|
369
|
+
summary,
|
|
370
|
+
frontmatter,
|
|
371
|
+
body,
|
|
372
|
+
actingAgent: meta.actingAgent ?? actingAgent,
|
|
373
|
+
});
|
|
374
|
+
results.push({
|
|
375
|
+
kind: 'create',
|
|
376
|
+
absPath: w.absPath ?? null,
|
|
377
|
+
specId: created.specId,
|
|
378
|
+
basename: created.basename,
|
|
379
|
+
relPath: created.relPath,
|
|
380
|
+
});
|
|
381
|
+
} else if (w.kind === 'update') {
|
|
382
|
+
const specId =
|
|
383
|
+
meta.specId ?? frontmatter.id ?? (w.absPath ? basenameFromRelPath(w.absPath) : null);
|
|
384
|
+
const updated = await updateSpecViaConsole({
|
|
385
|
+
consoleFetch,
|
|
386
|
+
consoleUrl,
|
|
387
|
+
specId,
|
|
388
|
+
frontmatter,
|
|
389
|
+
body,
|
|
390
|
+
});
|
|
391
|
+
results.push({
|
|
392
|
+
kind: 'update',
|
|
393
|
+
absPath: w.absPath ?? null,
|
|
394
|
+
specId: updated.specId,
|
|
395
|
+
basename: updated.basename,
|
|
396
|
+
relPath: updated.relPath,
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
throw cloudWriteFailure(
|
|
400
|
+
'commitWritesViaConsole',
|
|
401
|
+
consoleUrl,
|
|
402
|
+
`unknown write kind "${w.kind}" (expected "create" | "update")`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
err.failed_step = { index: i, kind: w.kind, abs_path: w.absPath ?? null };
|
|
407
|
+
err.completed_steps = results;
|
|
408
|
+
return { ok: false, error: err, completed: results };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { ok: true, results };
|
|
412
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# .heyspex/config.yaml — HeySpex workflow settings for this vault.
|
|
2
|
+
# Seeded by `hs init` (ADR-0009 §1: the file lives at the VAULT root and is git-tracked).
|
|
3
|
+
# Field reference: specs/protocols/agent-settings-protocol.md. Change values with
|
|
4
|
+
# `hs settings set <key> <value>` or heyspex_settings_set — never by hand.
|
|
5
|
+
|
|
6
|
+
mvp_mode:
|
|
7
|
+
enabled: true
|
|
8
|
+
review_grain: epic
|
|
9
|
+
review_backlog_never_blocks: true
|
|
10
|
+
|
|
11
|
+
pricing_tier:
|
|
12
|
+
tier: free
|
|
13
|
+
|
|
14
|
+
agent_behaviors:
|
|
15
|
+
ask_forgiveness_not_permission: true
|