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
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
|
|
8
|
-
import { join } from 'node:path';
|
|
8
|
+
import { join, isAbsolute, resolve, sep } from 'node:path';
|
|
9
9
|
|
|
10
10
|
import { allocateRandomVaultId } from '../../scripts/vault-id-lib.mjs';
|
|
11
11
|
import { yamlScalar } from './yaml-scalar.mjs';
|
|
12
|
-
import { mintFamilyKey, mintFileKey, parseSpecId } from './spec-id-lib.mjs';
|
|
12
|
+
import { collectOccupiedKeysEitherKind, mintFamilyKey, mintFileKey, parseSpecId } from './spec-id-lib.mjs';
|
|
13
13
|
import {
|
|
14
14
|
isoNow,
|
|
15
15
|
deriveSlug,
|
|
@@ -22,12 +22,15 @@ import {
|
|
|
22
22
|
} from './promotion-helpers.mjs';
|
|
23
23
|
import { extractHandoffEnvelopeYaml } from '../../spex-console/src/lib/indexer/handoff-envelope.mjs';
|
|
24
24
|
import { buildDiagnoseSkeletonSections } from './diagnose-skeleton.mjs';
|
|
25
|
+
import { assertPathUnderRoot } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
25
26
|
|
|
26
27
|
/** Default cap when frontmatter omits `attempt_count` ([[HS-TASK-6OU5]]). */
|
|
27
28
|
export const DEFAULT_DEFER_RECLAIM_ATTEMPT_CAP = 3;
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
function collectOccupiedDiagnoseIds(vaultRoot) {
|
|
30
32
|
const occupied = new Set();
|
|
33
|
+
// Disk-only scan — misses Neon-only (cloud-filed) diagnoses; see [[HS-7UJZ-O86I-DIAG]].
|
|
31
34
|
const dir = join(vaultRoot, 'specs', 'diagnoses');
|
|
32
35
|
if (!existsSync(dir)) return occupied;
|
|
33
36
|
for (const file of readdirSync(dir)) {
|
|
@@ -232,7 +235,24 @@ export function executeDeferredSplitReclaim(vaultRoot, task, opts = {}) {
|
|
|
232
235
|
parseSpecId(allocatedFamilyId)?.familyKey ?? mintFamilyKey(vaultRoot, { randomUnit: opts.randomUnit });
|
|
233
236
|
// Per-file FILE KEY (owner ruling 2026-07-10) — filename + `file_id:` SSOT,
|
|
234
237
|
// and (V3) the anchor for `id:` itself.
|
|
235
|
-
|
|
238
|
+
// FILE KEY occupancy ([[HS-FJWL]]). `opts.occupiedKeys` lets a tier-aware
|
|
239
|
+
// caller hand in the set resolved for ITS read tier
|
|
240
|
+
// (`collectOccupiedKeysEitherKindViaTier`); absent one, this falls back to
|
|
241
|
+
// the local `specs/**` walk.
|
|
242
|
+
//
|
|
243
|
+
// The fallback is safe HERE and nowhere else, for a reason worth stating:
|
|
244
|
+
// this function is synchronous and its only caller is the console wrapper
|
|
245
|
+
// `spex-console/src/lib/delegation/deferred-reclaim.ts`, which `readFileSync`s
|
|
246
|
+
// the task out of `vaultRoot()` and lets this module `commitWrites` the DLQ
|
|
247
|
+
// diagnose back into `specs/diagnoses/` — a disk-backed vault end to end (the
|
|
248
|
+
// line below already notes the cloud-mode gap, [[HS-7UJZ-O86I-DIAG]]). There
|
|
249
|
+
// is no MCP caller. A session with no local vault fails at the file READ long
|
|
250
|
+
// before it reaches the mint. Give this an async caller and pass
|
|
251
|
+
// `occupiedKeys`; do not let the fallback become the cloud path.
|
|
252
|
+
const fileKey = mintFileKey(
|
|
253
|
+
opts.occupiedKeys instanceof Set ? opts.occupiedKeys : collectOccupiedKeysEitherKind(vaultRoot),
|
|
254
|
+
{ randomUnit: opts.randomUnit },
|
|
255
|
+
);
|
|
236
256
|
const diagnoseId = `HS-${fileKey}`;
|
|
237
257
|
const built = buildDlqDiagnoseContent({
|
|
238
258
|
diagnoseId,
|
|
@@ -246,7 +266,13 @@ export function executeDeferredSplitReclaim(vaultRoot, task, opts = {}) {
|
|
|
246
266
|
now,
|
|
247
267
|
});
|
|
248
268
|
|
|
269
|
+
// Disk-only write target — a cloud-mode session leaves no Neon row; see [[HS-7UJZ-O86I-DIAG]].
|
|
249
270
|
const diagnoseDir = join(vaultRoot, 'specs', 'diagnoses');
|
|
271
|
+
try {
|
|
272
|
+
assertPathUnderRoot(vaultRoot, diagnoseDir, 'deferred-reclaim write');
|
|
273
|
+
} catch (err) {
|
|
274
|
+
return { ok: false, error: 'BAD_VAULT_ROOT', message: err.message };
|
|
275
|
+
}
|
|
250
276
|
if (!existsSync(diagnoseDir)) mkdirSync(diagnoseDir, { recursive: true });
|
|
251
277
|
const diagnoseAbsPath = join(diagnoseDir, `${built.basename}.md`);
|
|
252
278
|
writes.push({ kind: 'create', absPath: diagnoseAbsPath, content: built.content });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// heyspex_design_* handlers (thin wrappers over design-tools.mjs). Extracted from cli.mjs under
|
|
2
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
3
|
+
|
|
4
|
+
import { detectAgentIdentity } from './mcp-config.mjs';
|
|
5
|
+
import { wrapDesignResult } from './tool-helpers.mjs';
|
|
6
|
+
import { executeGetDoc as executeDesignGetDoc, executeGetPattern as executeDesignGetPattern, executeGetToken as executeDesignGetToken, executeListPatterns as executeDesignListPatterns, executeProposePattern as executeDesignProposePattern } from './design-tools.mjs';
|
|
7
|
+
|
|
8
|
+
export function handleDesignListPatterns(vaultRoot, args) {
|
|
9
|
+
return wrapDesignResult(executeDesignListPatterns(vaultRoot, args));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function handleDesignGetPattern(vaultRoot, args) {
|
|
13
|
+
return wrapDesignResult(executeDesignGetPattern(vaultRoot, args));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handleDesignGetToken(vaultRoot, args) {
|
|
17
|
+
return wrapDesignResult(executeDesignGetToken(vaultRoot, args));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function handleDesignGetDoc(vaultRoot, args) {
|
|
21
|
+
return wrapDesignResult(executeDesignGetDoc(vaultRoot, args));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function handleDesignProposePattern(vaultRoot, args) {
|
|
25
|
+
const identity = detectAgentIdentity();
|
|
26
|
+
const enriched = {
|
|
27
|
+
...args,
|
|
28
|
+
proposed_by: args.proposed_by ?? `${identity.agent} · ${identity.sessionId}`,
|
|
29
|
+
};
|
|
30
|
+
return wrapDesignResult(executeDesignProposePattern(vaultRoot, enriched));
|
|
31
|
+
}
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
14
|
-
import { join, relative } from 'node:path';
|
|
14
|
+
import { join, relative, isAbsolute, resolve, sep } from 'node:path';
|
|
15
|
+
import { assertPathUnderRoot } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
const REGISTRY_DIR = ['spex-console', 'public', 'design-system'];
|
|
17
19
|
const ITEMS_DIR = ['spex-console', 'public', 'design-system', 'r'];
|
|
@@ -416,6 +418,7 @@ export function executeProposePattern(vaultRoot, args = {}) {
|
|
|
416
418
|
|
|
417
419
|
try {
|
|
418
420
|
if (!existsSync(proposalsDir)) {
|
|
421
|
+
assertPathUnderRoot(vaultRoot, proposalsDir, 'design-tools write');
|
|
419
422
|
mkdirSync(proposalsDir, { recursive: true });
|
|
420
423
|
}
|
|
421
424
|
writeFileSync(filepath, JSON.stringify(proposal, null, 2) + '\n', 'utf8');
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Why this file exists ([[HS-ZJSP-0JNK-TASK-ship-the-heyspex-mcp-server-as-an]]):
|
|
6
6
|
* `research-tools.mjs` used to `import` two modules from
|
|
7
|
-
* `../../dev-factory/
|
|
7
|
+
* `../../dev-factory/spexcrawl/` at the top level. Those two static imports
|
|
8
8
|
* dragged **25 files** into the MCP server's static import closure —
|
|
9
9
|
* `dev-factory/router.mjs` (model routing), `dev-factory/cloud-clients.mjs`
|
|
10
10
|
* (provider clients + env loading), `dev-factory/cost-tracker.mjs`, and the whole
|
|
11
|
-
* `dev-factory/
|
|
11
|
+
* `dev-factory/spexcrawl/` engine. Any npm package that ships a working MCP
|
|
12
12
|
* server therefore had to ship all of them, and `npm publish` is a one-way door.
|
|
13
13
|
*
|
|
14
14
|
* Loading them through `await import()` instead means:
|
|
@@ -40,8 +40,8 @@ export function loadDreamResearch() {
|
|
|
40
40
|
dreamResearchPromise = (async () => {
|
|
41
41
|
try {
|
|
42
42
|
const [adapter, tool] = await Promise.all([
|
|
43
|
-
import('../../dev-factory/
|
|
44
|
-
import('../../dev-factory/
|
|
43
|
+
import('../../dev-factory/spexcrawl/firecrawl-adapter.mjs'),
|
|
44
|
+
import('../../dev-factory/spexcrawl/interactive-firecrawl-tool.mjs'),
|
|
45
45
|
]);
|
|
46
46
|
return {
|
|
47
47
|
available: true,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev-mode-core.mjs — one bundler-safe loader for `scripts/lib/dev-mode.mjs`.
|
|
3
|
+
*
|
|
4
|
+
* Several handlers on the cloud-safe surface (next_on_deck's WIP pre-fetch,
|
|
5
|
+
* file_spec's landing probe, read-path's tier selector) load the shared
|
|
6
|
+
* dev-mode core through a PATH they build at runtime
|
|
7
|
+
* (`pathToFileURL(join(root, 'scripts', 'lib', 'dev-mode.mjs'))`). That is
|
|
8
|
+
* correct for the stdio server — `HS_PKG_ROOT` and the pre-split vault-root
|
|
9
|
+
* layout both have to keep working — but it is invisible to a bundler, so on
|
|
10
|
+
* the console's `POST /api/mcp` deployment the built path pointed at the
|
|
11
|
+
* BUILD machine's checkout, missed, and the caller silently degraded (no WIP
|
|
12
|
+
* block, no landing probe).
|
|
13
|
+
*
|
|
14
|
+
* {@link importDevModeCore} keeps the runtime candidates first (identical
|
|
15
|
+
* stdio behaviour) and falls back to a STATIC relative specifier, which
|
|
16
|
+
* webpack traces at build time and therefore bundles. Packed installs resolve
|
|
17
|
+
* it to the same file the path candidates name
|
|
18
|
+
* (`node_modules/heyspex/scripts/lib/dev-mode.mjs`, shipped via package.json
|
|
19
|
+
* `files`), so the fallback is the same module by another route — never a
|
|
20
|
+
* different implementation.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { pathToFileURL } from 'node:url';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load the shared dev-mode core.
|
|
27
|
+
*
|
|
28
|
+
* @param {Array<string|null|undefined>} [candidatePaths]
|
|
29
|
+
* Absolute paths to try, in order, before the bundled fallback. A path that
|
|
30
|
+
* does not exist (or whose import throws) is skipped.
|
|
31
|
+
* @returns {Promise<typeof import('../../scripts/lib/dev-mode.mjs')>}
|
|
32
|
+
*/
|
|
33
|
+
export async function importDevModeCore(candidatePaths = []) {
|
|
34
|
+
for (const candidate of candidatePaths) {
|
|
35
|
+
if (typeof candidate !== 'string' || !candidate) continue;
|
|
36
|
+
try {
|
|
37
|
+
if (existsSync(candidate)) return await import(pathToFileURL(candidate).href);
|
|
38
|
+
} catch {
|
|
39
|
+
// Unreadable/unloadable candidate — try the next one, then the fallback.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Static specifier: the bundler resolves this at build time, so the module
|
|
43
|
+
// travels with the serverless chunk instead of being looked up on disk.
|
|
44
|
+
return import('../../scripts/lib/dev-mode.mjs');
|
|
45
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// heyspex_dev_mode and heyspex_dev_server_status. Extracted from cli.mjs under
|
|
2
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
3
|
+
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { PACKAGE_ROOT } from './package-root.mjs';
|
|
8
|
+
import { resolveCodeRoot } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
9
|
+
|
|
10
|
+
// scripts/dev-server-status.mjs and scripts/lib/dev-mode.mjs are CODE-repo
|
|
11
|
+
// artifacts that ship with this package — since the repo split (2026-08-31)
|
|
12
|
+
// the vault root no longer holds them, so resolve them relative to THIS
|
|
13
|
+
// file's checkout first and fall back to the vault root only for pre-split
|
|
14
|
+
// layouts where they were the same repo. `HS_PKG_ROOT` (set by the `hs.mjs`
|
|
15
|
+
// dispatcher for checks-running verbs — see `scripts/lib/checks-code-root.test.mjs`)
|
|
16
|
+
// wins over the derived root when a caller knows the real code checkout is
|
|
17
|
+
// elsewhere; same precedence plan-review.mjs's `resolveCodeAuditRoots` uses.
|
|
18
|
+
const CHECKOUT_ROOT = resolveCodeRoot({ packageRoot: PACKAGE_ROOT });
|
|
19
|
+
|
|
20
|
+
export async function handleDevServerStatus(vaultRoot) {
|
|
21
|
+
try {
|
|
22
|
+
const local = join(CHECKOUT_ROOT, 'scripts', 'dev-server-status.mjs');
|
|
23
|
+
const scriptPath = existsSync(local)
|
|
24
|
+
? local
|
|
25
|
+
: join(vaultRoot, 'scripts', 'dev-server-status.mjs');
|
|
26
|
+
const coreUrl = pathToFileURL(scriptPath).href;
|
|
27
|
+
const mod = await import(coreUrl);
|
|
28
|
+
const status = await mod.getDevServerStatus({ vaultRoot });
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
isError: true,
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify(
|
|
36
|
+
{ error: 'DEV_SERVER_STATUS_FAILED', message: String(err?.message ?? err) },
|
|
37
|
+
null,
|
|
38
|
+
2,
|
|
39
|
+
),
|
|
40
|
+
}],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* heyspex_dev_mode — get/set the dev/prod mode + local/cloud reliance (epic
|
|
47
|
+
* HS-Q6M2). Same self-containment pattern as handleDevServerStatus above:
|
|
48
|
+
* vendor/ stays dependency-free by dynamically importing the shared core
|
|
49
|
+
* (scripts/lib/dev-mode.mjs) from the vault root at call time.
|
|
50
|
+
*/
|
|
51
|
+
export async function handleDevMode(vaultRoot, args = {}) {
|
|
52
|
+
try {
|
|
53
|
+
const local = join(CHECKOUT_ROOT, 'scripts', 'lib', 'dev-mode.mjs');
|
|
54
|
+
const scriptPath = existsSync(local)
|
|
55
|
+
? local
|
|
56
|
+
: join(vaultRoot, 'scripts', 'lib', 'dev-mode.mjs');
|
|
57
|
+
const coreUrl = pathToFileURL(scriptPath).href;
|
|
58
|
+
const mod = await import(coreUrl);
|
|
59
|
+
if (args.action === 'set') {
|
|
60
|
+
if (!args.mode) {
|
|
61
|
+
return {
|
|
62
|
+
isError: true,
|
|
63
|
+
content: [{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: JSON.stringify({ error: 'MISSING_MODE', message: 'action:"set" requires mode (prod | dev | local | cloud)' }),
|
|
66
|
+
}],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
let result;
|
|
70
|
+
try {
|
|
71
|
+
result = await mod.applyDevMode(
|
|
72
|
+
{ mode: args.mode, reliance: args.reliance },
|
|
73
|
+
{ source: 'mcp' },
|
|
74
|
+
);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err?.name === 'DevModeNotAdminError') {
|
|
77
|
+
return {
|
|
78
|
+
isError: true,
|
|
79
|
+
content: [{
|
|
80
|
+
type: 'text',
|
|
81
|
+
text: JSON.stringify({ error: 'NOT_ADMIN', message: err.message }),
|
|
82
|
+
}],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
88
|
+
}
|
|
89
|
+
// get (default): two-pass — resolve without the cloud rung to learn which
|
|
90
|
+
// console to ask, then fold the fetched cloud setting into the resolution.
|
|
91
|
+
const pre = mod.resolveDevMode();
|
|
92
|
+
const cloud = await mod.fetchCloudDevMode({ consoleUrl: pre.consoleUrl });
|
|
93
|
+
const resolved = mod.resolveDevMode({
|
|
94
|
+
cloudSetting: cloud.ok ? cloud.setting : undefined,
|
|
95
|
+
});
|
|
96
|
+
// Same `mode_override` object `hs dev --json` / `hs doctor --json` carry
|
|
97
|
+
// (HS-EVVI — the MCP must never disagree with the CLI on mode). Guarded:
|
|
98
|
+
// an older shipped core without the helper still answers.
|
|
99
|
+
const modeOverride =
|
|
100
|
+
typeof mod.modeOverrideOf === 'function' ? mod.modeOverrideOf(resolved) : null;
|
|
101
|
+
return {
|
|
102
|
+
content: [{
|
|
103
|
+
type: 'text',
|
|
104
|
+
text: JSON.stringify(
|
|
105
|
+
{ resolved, cloudError: cloud.error ?? null, mode_override: modeOverride },
|
|
106
|
+
null,
|
|
107
|
+
2,
|
|
108
|
+
),
|
|
109
|
+
}],
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return {
|
|
113
|
+
isError: true,
|
|
114
|
+
content: [{
|
|
115
|
+
type: 'text',
|
|
116
|
+
text: JSON.stringify(
|
|
117
|
+
{ error: 'DEV_MODE_FAILED', message: String(err?.message ?? err) },
|
|
118
|
+
null,
|
|
119
|
+
2,
|
|
120
|
+
),
|
|
121
|
+
}],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// heyspex_verify_reproduction and heyspex_promote_diagnose handlers. Extracted from cli.mjs under
|
|
2
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
3
|
+
|
|
4
|
+
import { executePromoteDiagnose } from './promote-diagnose.mjs';
|
|
5
|
+
import { consoleFetch } from './console-transport.mjs';
|
|
6
|
+
import { resolveConsoleReliance } from './console-fetch.mjs';
|
|
7
|
+
import { resolveSpecIdForMutation } from './tool-helpers.mjs';
|
|
8
|
+
import { executeVerifyReproduction } from './verify-reproduction.mjs';
|
|
9
|
+
import { detectAgentIdentity } from './mcp-config.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `heyspex_promote_diagnose` — the breakdown stage entered from a bug write-up
|
|
13
|
+
* ([[HS-ALBV]]). Same stage as `heyspex_promote_research`, second input kind.
|
|
14
|
+
*/
|
|
15
|
+
export async function handlePromoteDiagnose(vaultRoot, args = {}) {
|
|
16
|
+
const result = await executePromoteDiagnose({ vaultRoot, args, deps: { consoleFetch } });
|
|
17
|
+
if (result.error) {
|
|
18
|
+
return {
|
|
19
|
+
isError: true,
|
|
20
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `heyspex_verify_reproduction` — record a reproduction re-run on a diagnose and
|
|
30
|
+
* close it on a clean run ([[HS-09W1]] of [[HS-ALBV]]). The close goes through the
|
|
31
|
+
* status API so the reproduction gate inside it is what decides, not this handler.
|
|
32
|
+
* In cloud reliance the diagnose is resolved and the verdict recorded through the
|
|
33
|
+
* console — a cloud-filed diagnose has no file on disk ([[HS-7UJZ-O86I-DIAG]]).
|
|
34
|
+
*/
|
|
35
|
+
export async function handleVerifyReproduction(vaultRoot, args = {}) {
|
|
36
|
+
const resolved = await resolveSpecIdForMutation(args.spec_id);
|
|
37
|
+
if (resolved.response) return resolved.response;
|
|
38
|
+
const result = await executeVerifyReproduction({
|
|
39
|
+
vaultRoot,
|
|
40
|
+
consoleFetch,
|
|
41
|
+
args: { ...args, spec_id: resolved.specId },
|
|
42
|
+
agent: detectAgentIdentity(args.by, undefined).agent,
|
|
43
|
+
getReliance: resolveConsoleReliance,
|
|
44
|
+
});
|
|
45
|
+
if (result.error) {
|
|
46
|
+
return {
|
|
47
|
+
isError: true,
|
|
48
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Read-only audit views: heyspex_events_show, export_events, evidence_show, milestone_show. Extracted from cli.mjs under
|
|
2
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
3
|
+
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { consoleFetch, controlError, controlOk } from './console-transport.mjs';
|
|
8
|
+
import { consoleErrorCode, consoleErrorMessage } from './console-fetch.mjs';
|
|
9
|
+
import { resolveActiveVault } from './vault-resolve.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Read the current on-deck pin — [[HS-EPIC-NCPS]] focus_pin / [[HS-EPIC-CLMW]]
|
|
13
|
+
* `hs pin get`. GET /api/control/pin → { pin: row | null }.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Locked empty-state copy — byte-identical to the CLI core
|
|
17
|
+
* (`spex-console/src/lib/cli/milestone-show.ts` `NEEDS_BREAKDOWN_MESSAGE`) and
|
|
18
|
+
* the MCEP pill sub-line. [[HS-TASK-HMSE]] / [[HS-EPIC-MNBD]].
|
|
19
|
+
*/
|
|
20
|
+
export const MILESTONE_NEEDS_BREAKDOWN_MESSAGE = 'Needs breakdown — file the first epic to start';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Show a milestone + its structured needs-breakdown shape ([[HS-TASK-HMSE]]).
|
|
24
|
+
* Reads the spec via GET /api/specs/:id, counts `epics:`, and returns
|
|
25
|
+
* epics_count / needs_breakdown / needs_breakdown_message so console and agent
|
|
26
|
+
* consumers read the empty state without re-deriving it.
|
|
27
|
+
*/
|
|
28
|
+
export async function handleMilestoneShow(args = {}) {
|
|
29
|
+
if (!args.spec_id || typeof args.spec_id !== 'string') {
|
|
30
|
+
return {
|
|
31
|
+
isError: true,
|
|
32
|
+
content: [
|
|
33
|
+
{ type: 'text', text: JSON.stringify({ error: 'INVALID_ARGS', message: 'spec_id is required' }) },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const result = await consoleFetch(`/api/specs/${encodeURIComponent(args.spec_id)}`);
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
const payload = {
|
|
40
|
+
error: consoleErrorCode(result, 'MILESTONE_SHOW_FAILED'),
|
|
41
|
+
message: consoleErrorMessage(result, `GET /api/specs/${args.spec_id} returned ${result.status}`),
|
|
42
|
+
status: result.status,
|
|
43
|
+
url: result.url,
|
|
44
|
+
console_response: result.body,
|
|
45
|
+
attempted: { spec_id: args.spec_id },
|
|
46
|
+
};
|
|
47
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
48
|
+
}
|
|
49
|
+
const spec = result.body?.spec ?? null;
|
|
50
|
+
if (!spec || spec.type !== 'milestone') {
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: JSON.stringify({
|
|
57
|
+
error: 'NOT_A_MILESTONE',
|
|
58
|
+
message: `${args.spec_id} is ${spec ? `a ${spec.type}` : 'not found'}, not a milestone`,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const epics = spec.frontmatter?.epics;
|
|
65
|
+
const epicsCount = Array.isArray(epics) ? epics.length : 0;
|
|
66
|
+
const needsBreakdown = epicsCount === 0;
|
|
67
|
+
const payload = {
|
|
68
|
+
id: spec.id,
|
|
69
|
+
name: spec.name ?? null,
|
|
70
|
+
status: spec.status ?? null,
|
|
71
|
+
created: spec.frontmatter?.created ?? null,
|
|
72
|
+
epics_count: epicsCount,
|
|
73
|
+
needs_breakdown: needsBreakdown,
|
|
74
|
+
needs_breakdown_message: needsBreakdown ? MILESTONE_NEEDS_BREAKDOWN_MESSAGE : null,
|
|
75
|
+
};
|
|
76
|
+
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read the per-spec audit log — [[HS-EPIC-NCPS]] spec_events / [[HS-EPIC-CLMW]]
|
|
81
|
+
* `hs events show`. GET /api/control/spec-events?spec_id=&kind=&since=&limit=.
|
|
82
|
+
*/
|
|
83
|
+
export async function handleEventsShow(args = {}) {
|
|
84
|
+
const params = new URLSearchParams();
|
|
85
|
+
if (typeof args.spec_id === 'string') params.set('spec_id', args.spec_id);
|
|
86
|
+
if (typeof args.kind === 'string') params.set('kind', args.kind);
|
|
87
|
+
if (typeof args.since === 'string') params.set('since', args.since);
|
|
88
|
+
if (args.limit !== undefined && args.limit !== null) params.set('limit', String(args.limit));
|
|
89
|
+
const result = await consoleFetch(`/api/control/spec-events?${params.toString()}`);
|
|
90
|
+
if (!result.ok) return controlError(result, 'EVENTS_SHOW_FAILED', { spec_id: args.spec_id });
|
|
91
|
+
return controlOk(result.body);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Consolidated evidence-trail read path
|
|
96
|
+
* ([[HS-BNDJ-TASK-consolidated-evidence-read-path-evidence]]). Thin wrapper
|
|
97
|
+
* around GET /api/evidence/:specId — the same `getEvidenceTrail` logic
|
|
98
|
+
* (`spex-console/src/lib/evidence-trail.ts`) that backs the console UI, so
|
|
99
|
+
* an assisting agent gets one consolidated answer in one call.
|
|
100
|
+
*/
|
|
101
|
+
export async function handleEvidenceShow(args = {}) {
|
|
102
|
+
if (!args.spec_id || typeof args.spec_id !== 'string') {
|
|
103
|
+
return {
|
|
104
|
+
isError: true,
|
|
105
|
+
content: [
|
|
106
|
+
{ type: 'text', text: JSON.stringify({ error: 'INVALID_ARGS', message: 'spec_id is required' }) },
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const result = await consoleFetch(`/api/evidence/${encodeURIComponent(args.spec_id)}`);
|
|
111
|
+
if (!result.ok) return controlError(result, 'EVIDENCE_SHOW_FAILED', { spec_id: args.spec_id });
|
|
112
|
+
return controlOk(result.body);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Idempotent union-and-sort merge of one month file — the SAME rule the
|
|
117
|
+
* console's `mergeMonthLines` (`spex-console/src/lib/control-plane/audit-export.ts`)
|
|
118
|
+
* applies: existing lines ∪ fresh lines, deduped, sorted (every line starts
|
|
119
|
+
* with `{"timestamp":"<iso>"` so the sort is timestamp-primary), trailing
|
|
120
|
+
* newline. Re-running over the same events is byte-identical; existing lines
|
|
121
|
+
* are never dropped.
|
|
122
|
+
*/
|
|
123
|
+
export function mergeMonthLinesLocal(existing, freshContent) {
|
|
124
|
+
const set = new Set();
|
|
125
|
+
for (const src of [existing ?? '', freshContent ?? '']) {
|
|
126
|
+
for (const line of src.split('\n')) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (trimmed) set.add(trimmed);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (set.size === 0) return '';
|
|
132
|
+
return `${[...set].sort().join('\n')}\n`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Write the month files a CLOUD export returned (`mode: "return"`) into the
|
|
137
|
+
* caller's vault repo at `<specsDir>/events/<name>`, merging idempotently.
|
|
138
|
+
* Returns the local receipt to splice into the tool result.
|
|
139
|
+
*/
|
|
140
|
+
export function writeReturnedMonthFiles(files, specsDirAbs, fsDeps = {}) {
|
|
141
|
+
const exists = fsDeps.existsSync ?? existsSync;
|
|
142
|
+
const read = fsDeps.readFileSync ?? readFileSync;
|
|
143
|
+
const write = fsDeps.writeFileSync ?? writeFileSync;
|
|
144
|
+
const mkdir = fsDeps.mkdirSync ?? mkdirSync;
|
|
145
|
+
const eventsDir = join(specsDirAbs, 'events');
|
|
146
|
+
const written = [];
|
|
147
|
+
const unchanged = [];
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
if (!file || typeof file.name !== 'string' || !/^\d{4}-\d{2}\.jsonl$/.test(file.name)) continue;
|
|
150
|
+
const target = join(eventsDir, file.name);
|
|
151
|
+
const existing = exists(target) ? read(target, 'utf8') : null;
|
|
152
|
+
const merged = mergeMonthLinesLocal(existing, file.content);
|
|
153
|
+
if (merged === (existing ?? '')) {
|
|
154
|
+
unchanged.push(target);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
mkdir(eventsDir, { recursive: true });
|
|
158
|
+
write(target, merged, 'utf8');
|
|
159
|
+
written.push(target);
|
|
160
|
+
}
|
|
161
|
+
return { vault_events_dir: eventsDir, files_written_locally: written, unchanged_locally: unchanged };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run the idempotent audit export — [[HS-EPIC-AEHR]] §1. POST
|
|
166
|
+
* /api/control/export-events. The on-close flush surface for the
|
|
167
|
+
* session-close protocol (no automatic harness hook yet).
|
|
168
|
+
*
|
|
169
|
+
* Two contracts, chosen by the CONSOLE ([[HS-ZD70-OX3V-DIAG-heyspex-export-events-always-409s-in]]):
|
|
170
|
+
* a local console writes `specs/events/YYYY-MM.jsonl` itself (`mode:
|
|
171
|
+
* "vault"`); a cloud console has no vault on disk, so it RETURNS the month
|
|
172
|
+
* files (`mode: "return"`) and this tool merges them into the caller's vault
|
|
173
|
+
* repo — the same file, written from the other side.
|
|
174
|
+
*/
|
|
175
|
+
export async function handleExportEvents(args = {}) {
|
|
176
|
+
const body = {};
|
|
177
|
+
if (typeof args.since === 'string' && args.since.trim()) body.since = args.since.trim();
|
|
178
|
+
// A LOCAL console writes into ITS OWN vault root; naming ours lets it refuse
|
|
179
|
+
// (409 vault_root_mismatch) instead of rewriting a different vault's
|
|
180
|
+
// specs/events/ when this session points at the wrong console. A cloud
|
|
181
|
+
// console ignores it (it returns the files instead).
|
|
182
|
+
let vault = null;
|
|
183
|
+
try {
|
|
184
|
+
vault = resolveActiveVault();
|
|
185
|
+
if (vault?.specsDirAbs) body.expected_vault_root = vault.specsDirAbs;
|
|
186
|
+
} catch { /* best-effort — the export still runs unguarded when unresolvable */ }
|
|
187
|
+
const result = await consoleFetch('/api/control/export-events', { method: 'POST', body });
|
|
188
|
+
if (!result.ok) return controlError(result, 'EXPORT_EVENTS_FAILED');
|
|
189
|
+
const payload = result.body ?? {};
|
|
190
|
+
if (payload.mode === 'return' && Array.isArray(payload.files)) {
|
|
191
|
+
if (!vault?.specsDirAbs) {
|
|
192
|
+
return controlOk({
|
|
193
|
+
...payload,
|
|
194
|
+
files: payload.files.map((f) => ({ name: f?.name, bytes: (f?.content ?? '').length })),
|
|
195
|
+
note:
|
|
196
|
+
'The cloud console returned the month files but no local vault could be resolved from this session, so nothing was written. Run from a checkout of the vault repo (heyspex.yaml at its root) to land specs/events/.',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const receipt = writeReturnedMonthFiles(payload.files, vault.specsDirAbs);
|
|
200
|
+
return controlOk({
|
|
201
|
+
...payload,
|
|
202
|
+
files: payload.files.map((f) => ({ name: f?.name, bytes: (f?.content ?? '').length })),
|
|
203
|
+
...receipt,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return controlOk(payload);
|
|
207
|
+
}
|