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
package/dist/cli/hs-init.mjs
CHANGED
|
@@ -1,19 +1,182 @@
|
|
|
1
1
|
import { createRequire as __hsCreateRequire } from 'node:module';
|
|
2
2
|
const require = __hsCreateRequire(import.meta.url);
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// vendor/heyspex-mcp/credentials.mjs
|
|
9
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
|
+
import os, { homedir } from "node:os";
|
|
11
|
+
import { dirname, join, resolve as resolve2 } from "node:path";
|
|
12
|
+
var init_credentials = __esm({
|
|
13
|
+
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
14
|
+
"use strict";
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// scripts/lib/dev-mode.mjs
|
|
19
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20
|
+
import { homedir as homedir2 } from "node:os";
|
|
21
|
+
import { join as join2 } from "node:path";
|
|
22
|
+
function devModeFilePath(homeDir = homedir2()) {
|
|
23
|
+
return join2(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
24
|
+
}
|
|
25
|
+
function normalizeMode(raw) {
|
|
26
|
+
const v = String(raw ?? "").trim().toLowerCase();
|
|
27
|
+
if (v === "prod" || v === "production") return { mode: "prod", reliance: "cloud" };
|
|
28
|
+
if (v === "dev") return { mode: "dev", reliance: null };
|
|
29
|
+
if (v === "local") return { mode: "dev", reliance: "local" };
|
|
30
|
+
if (v === "cloud") return { mode: "dev", reliance: "cloud" };
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function normalizeReliance(raw) {
|
|
34
|
+
const v = String(raw ?? "").trim().toLowerCase();
|
|
35
|
+
return v === "local" || v === "cloud" ? v : null;
|
|
36
|
+
}
|
|
37
|
+
function deriveSemantics(mode, reliance) {
|
|
38
|
+
const effectiveReliance = mode === "prod" ? "cloud" : reliance ?? "local";
|
|
39
|
+
const local = mode === "dev" && effectiveReliance === "local";
|
|
40
|
+
return {
|
|
41
|
+
reliance: effectiveReliance,
|
|
42
|
+
consoleUrl: local ? LOCAL_CONSOLE_URL : CLOUD_CONSOLE_URL,
|
|
43
|
+
readTier: local ? "disk" : "neon",
|
|
44
|
+
vaultWriteMode: local ? "local" : "cloud"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function readLocalDevModeRecord({ homeDir = homedir2(), readFile = defaultReadFile } = {}) {
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = readFile(devModeFilePath(homeDir));
|
|
51
|
+
} catch {
|
|
52
|
+
return { record: null, parseError: null };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
const mode = parsed?.mode === "prod" || parsed?.mode === "dev" ? parsed.mode : null;
|
|
57
|
+
const reliance = normalizeReliance(parsed?.reliance);
|
|
58
|
+
if (!mode || mode === "dev" && !reliance) {
|
|
59
|
+
return { record: null, parseError: `invalid dev-mode record: ${JSON.stringify(parsed)}` };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
record: {
|
|
63
|
+
mode,
|
|
64
|
+
reliance: mode === "prod" ? "cloud" : reliance,
|
|
65
|
+
console_url: typeof parsed.console_url === "string" ? parsed.console_url : null,
|
|
66
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : null,
|
|
67
|
+
source: typeof parsed.source === "string" ? parsed.source : null
|
|
68
|
+
},
|
|
69
|
+
parseError: null
|
|
70
|
+
};
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function resolveDevMode({
|
|
76
|
+
env = process.env,
|
|
77
|
+
homeDir = homedir2(),
|
|
78
|
+
readFile = defaultReadFile,
|
|
79
|
+
cloudSetting = void 0
|
|
80
|
+
} = {}) {
|
|
81
|
+
const local = readLocalDevModeRecord({ homeDir, readFile });
|
|
82
|
+
const cloudKnown = cloudSetting !== void 0;
|
|
83
|
+
const cloudValue = cloudKnown && cloudSetting ? {
|
|
84
|
+
mode: cloudSetting.mode === "dev" ? "dev" : "prod",
|
|
85
|
+
reliance: normalizeReliance(cloudSetting.reliance) ?? "cloud"
|
|
86
|
+
} : null;
|
|
87
|
+
let mode;
|
|
88
|
+
let reliance;
|
|
89
|
+
let source;
|
|
90
|
+
let relianceSource;
|
|
91
|
+
const envParsed = normalizeMode(env.HEYSPEX_DEV_MODE);
|
|
92
|
+
if (envParsed) {
|
|
93
|
+
mode = envParsed.mode;
|
|
94
|
+
const envReliance = envParsed.reliance ?? normalizeReliance(env.HEYSPEX_DEV_RELIANCE);
|
|
95
|
+
reliance = envReliance ?? "local";
|
|
96
|
+
source = "env";
|
|
97
|
+
relianceSource = envReliance ? "env" : "assumed-local";
|
|
98
|
+
} else if (local.record) {
|
|
99
|
+
mode = local.record.mode;
|
|
100
|
+
reliance = local.record.reliance;
|
|
101
|
+
source = "local-override";
|
|
102
|
+
} else if (cloudValue) {
|
|
103
|
+
mode = cloudValue.mode;
|
|
104
|
+
reliance = cloudValue.reliance;
|
|
105
|
+
source = "cloud-setting";
|
|
106
|
+
} else {
|
|
107
|
+
mode = "prod";
|
|
108
|
+
reliance = "cloud";
|
|
109
|
+
source = "default";
|
|
110
|
+
}
|
|
111
|
+
const semantics = deriveSemantics(mode, reliance);
|
|
112
|
+
const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
|
|
113
|
+
const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
|
|
114
|
+
return {
|
|
115
|
+
mode,
|
|
116
|
+
reliance: semantics.reliance,
|
|
117
|
+
consoleUrl,
|
|
118
|
+
readTier: semantics.readTier,
|
|
119
|
+
vaultWriteMode: semantics.vaultWriteMode,
|
|
120
|
+
sources: {
|
|
121
|
+
mode: source,
|
|
122
|
+
reliance: relianceSource ?? source,
|
|
123
|
+
consoleUrl: source === "local-override" && local.record?.console_url ? "local-override" : "derived",
|
|
124
|
+
readTier: "derived",
|
|
125
|
+
vaultWriteMode: "derived"
|
|
126
|
+
},
|
|
127
|
+
localOverride: {
|
|
128
|
+
present: local.record !== null,
|
|
129
|
+
record: local.record,
|
|
130
|
+
parseError: local.parseError,
|
|
131
|
+
staleAgainstCloud
|
|
132
|
+
},
|
|
133
|
+
cloudSetting: { known: cloudKnown, value: cloudValue }
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
137
|
+
var init_dev_mode = __esm({
|
|
138
|
+
"scripts/lib/dev-mode.mjs"() {
|
|
139
|
+
"use strict";
|
|
140
|
+
init_credentials();
|
|
141
|
+
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
142
|
+
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
143
|
+
DEV_MODE_FILENAME = "dev-mode.json";
|
|
144
|
+
defaultReadFile = (p) => readFileSync2(p, "utf8");
|
|
145
|
+
}
|
|
146
|
+
});
|
|
3
147
|
|
|
4
148
|
// spex-console/bin/hs-init.ts
|
|
5
149
|
import {
|
|
6
150
|
copyFileSync,
|
|
7
|
-
existsSync,
|
|
8
|
-
mkdirSync,
|
|
151
|
+
existsSync as existsSync4,
|
|
152
|
+
mkdirSync as mkdirSync4,
|
|
9
153
|
readdirSync,
|
|
10
|
-
readFileSync as
|
|
11
|
-
|
|
154
|
+
readFileSync as readFileSync8,
|
|
155
|
+
statSync,
|
|
156
|
+
writeFileSync as writeFileSync4
|
|
12
157
|
} from "node:fs";
|
|
13
|
-
import { dirname, join as
|
|
14
|
-
import
|
|
158
|
+
import { dirname as dirname5, join as join9, resolve as resolve5 } from "node:path";
|
|
159
|
+
import process3 from "node:process";
|
|
15
160
|
import { createInterface } from "node:readline/promises";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
161
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
162
|
+
|
|
163
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
164
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
165
|
+
function resolveCodeRoot({
|
|
166
|
+
explicit,
|
|
167
|
+
packageRoot,
|
|
168
|
+
vaultRoot,
|
|
169
|
+
env = process.env
|
|
170
|
+
} = {}) {
|
|
171
|
+
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
172
|
+
if (fromExplicit) return fromExplicit;
|
|
173
|
+
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
174
|
+
if (fromEnv) return fromEnv;
|
|
175
|
+
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
176
|
+
if (fromPackage) return fromPackage;
|
|
177
|
+
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
178
|
+
return fromVault;
|
|
179
|
+
}
|
|
17
180
|
|
|
18
181
|
// spex-console/src/lib/cli/customer-claude-md.ts
|
|
19
182
|
function renderCustomerClaudeMd({
|
|
@@ -65,15 +228,38 @@ The vault under \`specs/\` is the only persistent queue. File new work via \`hey
|
|
|
65
228
|
This CLAUDE.md was generated for **${vaultName}** from HeySpex template version **${templateVersion}** \u2014 it is yours to edit. Unlike HeySpex's own platform contract (which carries HeySpex's internal build tooling), everything above applies to *your* vault and *your* agents. Edit it freely as your project's conventions evolve; a later template update will not overwrite your changes.
|
|
66
229
|
`;
|
|
67
230
|
}
|
|
68
|
-
function
|
|
231
|
+
function writeProjectContract(ctx, options = {}) {
|
|
232
|
+
const content = renderCustomerClaudeMd({
|
|
233
|
+
vaultName: ctx.vaultName,
|
|
234
|
+
templateVersion: ctx.templateVersion
|
|
235
|
+
});
|
|
236
|
+
const results = [];
|
|
237
|
+
for (const file of options.files ?? ["CLAUDE.md"]) {
|
|
238
|
+
const destPath = ctx.deps.joinPath(ctx.targetDir, file);
|
|
239
|
+
if (!ctx.deps.exists(destPath)) {
|
|
240
|
+
ctx.deps.writeFile(destPath, content);
|
|
241
|
+
ctx.recordWrite?.(destPath);
|
|
242
|
+
results.push({ file, action: "created", path: destPath });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (ctx.deps.readFile?.(destPath) === content) {
|
|
246
|
+
results.push({ file, action: "unchanged" });
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const templatePath = ctx.deps.joinPath(ctx.targetDir, `${file}.template`);
|
|
250
|
+
if (ctx.deps.readFile?.(templatePath) !== content) {
|
|
251
|
+
ctx.deps.writeFile(templatePath, content);
|
|
252
|
+
ctx.recordWrite?.(templatePath);
|
|
253
|
+
}
|
|
254
|
+
const hint = `${file} already exists - left untouched. Generated contract written to ${file}.template; compare with: diff ${file} ${file}.template`;
|
|
255
|
+
options.notify?.(hint);
|
|
256
|
+
results.push({ file, action: "template", path: templatePath, hint });
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
260
|
+
function customerClaudeMdHook(notify) {
|
|
69
261
|
return (ctx) => {
|
|
70
|
-
|
|
71
|
-
vaultName: ctx.vaultName,
|
|
72
|
-
templateVersion: ctx.templateVersion
|
|
73
|
-
});
|
|
74
|
-
const destPath = ctx.deps.joinPath(ctx.targetDir, "CLAUDE.md");
|
|
75
|
-
ctx.deps.writeFile(destPath, content);
|
|
76
|
-
ctx.recordWrite(destPath);
|
|
262
|
+
writeProjectContract(ctx, { notify });
|
|
77
263
|
};
|
|
78
264
|
}
|
|
79
265
|
|
|
@@ -81,69 +267,182 @@ function customerClaudeMdHook() {
|
|
|
81
267
|
import { randomUUID } from "node:crypto";
|
|
82
268
|
|
|
83
269
|
// scripts/lib/console-url-resolver.mjs
|
|
84
|
-
|
|
85
|
-
import {
|
|
86
|
-
import {
|
|
87
|
-
|
|
270
|
+
init_dev_mode();
|
|
271
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
272
|
+
import { homedir as homedir3 } from "node:os";
|
|
273
|
+
import { join as join3 } from "node:path";
|
|
274
|
+
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
88
275
|
function trimTrailingSlash(url) {
|
|
89
276
|
return url.replace(/\/+$/, "");
|
|
90
277
|
}
|
|
91
|
-
|
|
278
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
|
|
279
|
+
function isLoopbackConsoleUrl(url) {
|
|
280
|
+
if (typeof url !== "string" || !url) return false;
|
|
281
|
+
try {
|
|
282
|
+
return LOOPBACK_HOSTS.has(new URL(url).hostname.toLowerCase());
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
var staleLoopbackCredentialsWarned = false;
|
|
288
|
+
function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
289
|
+
if (staleLoopbackCredentialsWarned) return;
|
|
290
|
+
staleLoopbackCredentialsWarned = true;
|
|
291
|
+
try {
|
|
292
|
+
log(
|
|
293
|
+
`[heyspex] ignoring console_url ${credentialsUrl} from ~/.heyspex/credentials.yaml: the resolved reliance is cloud, and a loopback console cannot serve it \u2014 using ${derivedUrl} (set HEYSPEX_CONSOLE_URL to override explicitly, or run \`hs dev mode:local\` to go back to a local console).
|
|
294
|
+
`
|
|
295
|
+
);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function readCredentialsConsoleUrl({ homeDir = homedir3(), readFile = defaultReadFile2 } = {}) {
|
|
92
300
|
try {
|
|
93
|
-
const raw =
|
|
94
|
-
const match = raw.match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
301
|
+
const raw = readFile(join3(homeDir, ".heyspex", "credentials.yaml"));
|
|
302
|
+
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
95
303
|
const value = match?.[1]?.trim();
|
|
96
304
|
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
97
305
|
} catch {
|
|
98
306
|
return null;
|
|
99
307
|
}
|
|
100
308
|
}
|
|
101
|
-
function
|
|
309
|
+
function defaultReadFile2(p) {
|
|
310
|
+
return readFileSync3(p, "utf8");
|
|
311
|
+
}
|
|
312
|
+
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
|
|
102
313
|
if (!repoRoot) return null;
|
|
103
|
-
|
|
104
|
-
"console-url"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
]) {
|
|
108
|
-
try {
|
|
109
|
-
const discovery = readFileSync(join(repoRoot, ".heyspex", name), "utf8").trim();
|
|
110
|
-
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
111
|
-
} catch {
|
|
112
|
-
}
|
|
314
|
+
try {
|
|
315
|
+
const discovery = String(readDiscovery(join3(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
316
|
+
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
317
|
+
} catch {
|
|
113
318
|
}
|
|
114
319
|
return null;
|
|
115
320
|
}
|
|
116
321
|
var ConsoleUrlUnresolvedError = class extends Error {
|
|
117
322
|
constructor() {
|
|
118
323
|
super(
|
|
119
|
-
|
|
324
|
+
"resolveConsoleUrl: local reliance with no HEYSPEX_CONSOLE_URL env, no console_url: in ~/.heyspex/credentials.yaml, and no .heyspex/console-url discovery file \u2014 refusing to silently fall back to the localhost:3000 default in strict mode."
|
|
120
325
|
);
|
|
121
326
|
this.name = "ConsoleUrlUnresolvedError";
|
|
122
327
|
}
|
|
123
328
|
};
|
|
329
|
+
function resolveModeEnvelope({ env, homeDir }) {
|
|
330
|
+
try {
|
|
331
|
+
const resolved = resolveDevMode({ env, homeDir });
|
|
332
|
+
return {
|
|
333
|
+
reliance: resolved.reliance === "cloud" ? "cloud" : "local",
|
|
334
|
+
consoleUrl: resolved.consoleUrl || DEFAULT_CONSOLE_URL
|
|
335
|
+
};
|
|
336
|
+
} catch {
|
|
337
|
+
return { reliance: "local", consoleUrl: DEFAULT_CONSOLE_URL };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
124
340
|
function resolveConsoleUrl({
|
|
125
341
|
repoRoot,
|
|
126
342
|
env = process.env,
|
|
127
|
-
homeDir =
|
|
128
|
-
strict = false
|
|
343
|
+
homeDir = homedir3(),
|
|
344
|
+
strict = false,
|
|
345
|
+
devMode,
|
|
346
|
+
log = (line) => process.stderr.write(line),
|
|
347
|
+
readFile = defaultReadFile2,
|
|
348
|
+
readDiscovery = defaultReadFile2
|
|
129
349
|
} = {}) {
|
|
130
|
-
const envUrl =
|
|
350
|
+
const envUrl = env.HEYSPEX_CONSOLE_URL?.trim();
|
|
131
351
|
if (envUrl) return trimTrailingSlash(envUrl);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
352
|
+
const mode = devMode ?? resolveModeEnvelope({ env, homeDir });
|
|
353
|
+
const cloud = mode.reliance === "cloud";
|
|
354
|
+
const derivedUrl = trimTrailingSlash(
|
|
355
|
+
cloud && isLoopbackConsoleUrl(mode.consoleUrl) ? CLOUD_CONSOLE_URL : mode.consoleUrl || DEFAULT_CONSOLE_URL
|
|
356
|
+
);
|
|
357
|
+
const credentialsUrl = readCredentialsConsoleUrl({ homeDir, readFile });
|
|
358
|
+
if (credentialsUrl) {
|
|
359
|
+
if (cloud && isLoopbackConsoleUrl(credentialsUrl)) {
|
|
360
|
+
warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log });
|
|
361
|
+
} else {
|
|
362
|
+
return credentialsUrl;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!cloud) {
|
|
366
|
+
const discoveryUrl = readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery });
|
|
367
|
+
if (discoveryUrl) return discoveryUrl;
|
|
368
|
+
if (strict) throw new ConsoleUrlUnresolvedError();
|
|
369
|
+
}
|
|
370
|
+
return derivedUrl;
|
|
138
371
|
}
|
|
139
372
|
|
|
140
373
|
// spex-console/src/lib/cli/control-shared.ts
|
|
141
|
-
import
|
|
142
|
-
|
|
143
|
-
|
|
374
|
+
import path from "node:path";
|
|
375
|
+
|
|
376
|
+
// scripts/lib/hs-command.mjs
|
|
377
|
+
import { existsSync } from "node:fs";
|
|
378
|
+
import { delimiter, dirname as dirname2, join as join4 } from "node:path";
|
|
379
|
+
import { fileURLToPath } from "node:url";
|
|
380
|
+
var HS_BIN_NAME = "hs";
|
|
381
|
+
var HS_DISPATCHER_PATH = (() => {
|
|
382
|
+
try {
|
|
383
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
384
|
+
return join4(dirname2(dirname2(here)), "bin", "hs.mjs");
|
|
385
|
+
} catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
})();
|
|
389
|
+
function shellQuote(p) {
|
|
390
|
+
return /[\\\s]/.test(p) ? `"${p}"` : p;
|
|
391
|
+
}
|
|
392
|
+
var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER_PATH)}` : HS_BIN_NAME;
|
|
393
|
+
|
|
394
|
+
// vendor/heyspex-mcp/console-fetch.mjs
|
|
395
|
+
init_credentials();
|
|
396
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
397
|
+
import { homedir as homedir6 } from "node:os";
|
|
398
|
+
import { join as join7 } from "node:path";
|
|
399
|
+
|
|
400
|
+
// vendor/heyspex-mcp/session-owner.mjs
|
|
401
|
+
init_credentials();
|
|
402
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
403
|
+
import { homedir as homedir4 } from "node:os";
|
|
404
|
+
import { join as join5 } from "node:path";
|
|
405
|
+
|
|
406
|
+
// vendor/heyspex-mcp/vault-less-endpoints.mjs
|
|
407
|
+
var VAULT_LESS_ENDPOINTS = Object.freeze([
|
|
408
|
+
"* /api/mcp/session",
|
|
409
|
+
"* /api/mcp/pairing-codes",
|
|
410
|
+
"POST /api/vaults",
|
|
411
|
+
"GET /api/vaults",
|
|
412
|
+
"POST /api/workspaces",
|
|
413
|
+
"GET /api/workspaces",
|
|
414
|
+
"* /api/admin/dev-mode"
|
|
415
|
+
]);
|
|
416
|
+
|
|
417
|
+
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
418
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
419
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
420
|
+
import { dirname as dirname3, join as join6, resolve as resolve3 } from "node:path";
|
|
421
|
+
|
|
422
|
+
// vendor/heyspex-mcp/console-fetch.mjs
|
|
423
|
+
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
424
|
+
|
|
425
|
+
// spex-console/src/lib/cli/control-shared.ts
|
|
426
|
+
init_credentials();
|
|
427
|
+
|
|
428
|
+
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
429
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
430
|
+
import { homedir as homedir7 } from "node:os";
|
|
431
|
+
import { dirname as dirname4, join as join8, resolve as resolve4 } from "node:path";
|
|
432
|
+
|
|
433
|
+
// spex-console/src/lib/cli/control-shared.ts
|
|
144
434
|
var EXIT = {
|
|
145
435
|
/** Success. */
|
|
146
436
|
OK: 0,
|
|
437
|
+
/**
|
|
438
|
+
* A read-only CHECK ran correctly and its answer is "no" — `hs connect
|
|
439
|
+
* --check` on an unconnected repo. Distinct from {@link EXIT.USAGE} so a
|
|
440
|
+
* script can tell "you typed the command wrong" from "the command worked,
|
|
441
|
+
* the repo is not connected". Both were `1` until 2026-09-18, which made
|
|
442
|
+
* `--check` unusable in a conditional: every caller had to parse stdout to
|
|
443
|
+
* find out which of the two had happened.
|
|
444
|
+
*/
|
|
445
|
+
CHECK_FAILED: 2,
|
|
147
446
|
/** Usage error — unknown subcommand/flag, or a missing required argument. */
|
|
148
447
|
USAGE: 1,
|
|
149
448
|
/** Claim race lost — the spec is already claimed by another agent (409). */
|
|
@@ -165,12 +464,63 @@ var EXIT = {
|
|
|
165
464
|
* from `USAGE`/`INVALID_ARG` — this is an authorization refusal, not a
|
|
166
465
|
* malformed command.
|
|
167
466
|
*/
|
|
168
|
-
FORBIDDEN: 15
|
|
467
|
+
FORBIDDEN: 15,
|
|
468
|
+
/**
|
|
469
|
+
* A CLOUD write refused because no account id could be resolved (no
|
|
470
|
+
* `HS_ACCOUNT_ID` env var, no `account_id:` in `~/.heyspex/config.yaml`) --
|
|
471
|
+
* `resolveCloudAccountIdOrRefuse` in `vault-workspace-shared.ts`
|
|
472
|
+
* ([[HS-YU1S-UAGY-TASK-cloud-writes-refuse-without-a]]). Distinct from
|
|
473
|
+
* `FORBIDDEN` -- this is "not signed in at all", not "signed in but not
|
|
474
|
+
* authorized for this override".
|
|
475
|
+
*/
|
|
476
|
+
SIGN_IN_REQUIRED: 16,
|
|
477
|
+
/**
|
|
478
|
+
* Signed in, but the account a repo would be attached to was not confirmed:
|
|
479
|
+
* a non-interactive install/connect with no matching `--account` / `--yes`,
|
|
480
|
+
* an `account_id` that disagrees with the bearer's user, or the human
|
|
481
|
+
* aborting the identity prompt (`install-identity.ts`,
|
|
482
|
+
* [[HS-HAQI-KODY-DIAG-hs-install-and-hs-connect-bind-a-new]]).
|
|
483
|
+
*/
|
|
484
|
+
IDENTITY_UNCONFIRMED: 17,
|
|
485
|
+
/**
|
|
486
|
+
* The repo this verb runs in is not registered on this machine (no
|
|
487
|
+
* `~/.heyspex/registry.json` row for its root, or a legacy slug row with no
|
|
488
|
+
* cloud `vault_id` in its heyspex.yaml), so the call was refused LOCALLY —
|
|
489
|
+
* nothing was sent. Names `hs connect`
|
|
490
|
+
* ([[HS-MVG4-WW9R-TASK-mcp-and-hs-name-account-vault-on-every]]).
|
|
491
|
+
*/
|
|
492
|
+
VAULT_NOT_REGISTERED: 18,
|
|
493
|
+
/**
|
|
494
|
+
* The local half of a setup verb (`hs connect`, `hs vault *`, `hs workspace
|
|
495
|
+
* *`) was written but the CLOUD half did not land -- the console refused
|
|
496
|
+
* it or was never reached. The verb's receipt names the failed half and the
|
|
497
|
+
* exact command to re-run
|
|
498
|
+
* ([[HS-STCY-WW9R-TASK-add-a-shared-receipt-that-reads-back]], invariant 6).
|
|
499
|
+
* Distinct from {@link EXIT.WRITE_FAILED} so a script can tell "re-run to
|
|
500
|
+
* sync the cloud" from a generic server failure.
|
|
501
|
+
*/
|
|
502
|
+
CLOUD_WRITE_FAILED: 19,
|
|
503
|
+
/**
|
|
504
|
+
* The repo's committed `heyspex.yaml` records `account: <A>` but the machine
|
|
505
|
+
* is signed in as another account, so `hs connect` refused and wrote
|
|
506
|
+
* nothing (marker, registry, cloud). Repair: `hs login --switch-account <A>`
|
|
507
|
+
* ([[HS-VWUD-WW9R-TASK-record-the-connecting-account-in]], invariant 4).
|
|
508
|
+
*/
|
|
509
|
+
ACCOUNT_MISMATCH: 20,
|
|
510
|
+
/**
|
|
511
|
+
* Both sides of a setup verb's receipt were read back and still DISAGREE
|
|
512
|
+
* after a short bounded re-read of the cloud side (a briefly stale read is
|
|
513
|
+
* absorbed first). The write may well have landed; what the verb cannot
|
|
514
|
+
* claim is that this machine and the console now say the same thing. The
|
|
515
|
+
* receipt prints the diffs and the command to re-run
|
|
516
|
+
* ([[HS-FKSR-WW9R-TASK-make-one-hs-connect-run-create-a-cloud]], invariant 6).
|
|
517
|
+
* Distinct from {@link EXIT.CLOUD_WRITE_FAILED} (a write that did not land).
|
|
518
|
+
*/
|
|
519
|
+
RECEIPT_DISAGREE: 21
|
|
169
520
|
};
|
|
170
521
|
|
|
171
522
|
// spex-console/src/lib/cli/init.ts
|
|
172
523
|
var HEYSPEX_YAML_SCHEMA_VERSION = 2;
|
|
173
|
-
var DEFAULT_CONSOLE_URL2 = resolveConsoleUrl();
|
|
174
524
|
function mintVaultId() {
|
|
175
525
|
return randomUUID();
|
|
176
526
|
}
|
|
@@ -181,6 +531,30 @@ function parseVaultId(content) {
|
|
|
181
531
|
const unquoted = raw.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
182
532
|
return unquoted.length > 0 ? unquoted : null;
|
|
183
533
|
}
|
|
534
|
+
function parseAccountId(content) {
|
|
535
|
+
const m = content.match(/^account:[ \t]*(.+?)\s*$/m);
|
|
536
|
+
if (!m) return null;
|
|
537
|
+
const raw = m[1].replace(/\s+#.*$/, "").trim();
|
|
538
|
+
const unquoted = raw.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
539
|
+
return unquoted.length > 0 ? unquoted : null;
|
|
540
|
+
}
|
|
541
|
+
var MARKER_ACCOUNT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.@+-]*$/;
|
|
542
|
+
function recordMarkerAccount(content, accountId) {
|
|
543
|
+
if (!MARKER_ACCOUNT_ID_RE.test(accountId)) {
|
|
544
|
+
throw new Error(`recordMarkerAccount: "${accountId}" is not a valid account id`);
|
|
545
|
+
}
|
|
546
|
+
const recorded = parseAccountId(content);
|
|
547
|
+
if (recorded === accountId) return { action: "unchanged", content };
|
|
548
|
+
if (recorded !== null) return { action: "conflict", recorded, content };
|
|
549
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
550
|
+
const lines = content.replace(/(\r?\n)+$/, "").split(/\r?\n/);
|
|
551
|
+
const kept = lines.filter((l) => !/^account:\s*$/.test(l));
|
|
552
|
+
const vaultIdx = kept.findIndex((l) => /^vault_id:/.test(l));
|
|
553
|
+
const line = `account: ${accountId}`;
|
|
554
|
+
if (vaultIdx >= 0) kept.splice(vaultIdx + 1, 0, line);
|
|
555
|
+
else kept.push(line);
|
|
556
|
+
return { action: "added", content: `${kept.join(eol)}${eol}` };
|
|
557
|
+
}
|
|
184
558
|
function appendVaultIdLine(content, vaultId) {
|
|
185
559
|
const trimmed = content.replace(/\n+$/, "");
|
|
186
560
|
return `${trimmed}
|
|
@@ -188,21 +562,22 @@ vault_id: ${vaultId}
|
|
|
188
562
|
`;
|
|
189
563
|
}
|
|
190
564
|
function parseInitArgs(argv) {
|
|
191
|
-
let consoleUrl = (
|
|
192
|
-
DEFAULT_CONSOLE_URL2;
|
|
565
|
+
let consoleUrl = resolveConsoleUrl();
|
|
193
566
|
let vaultRoot = "specs/";
|
|
194
567
|
let force = false;
|
|
195
568
|
let json = false;
|
|
196
569
|
let customerVaultDir;
|
|
197
570
|
let principles;
|
|
198
571
|
let vaultName;
|
|
199
|
-
let
|
|
572
|
+
let git = false;
|
|
200
573
|
for (let i = 0; i < argv.length; i++) {
|
|
201
574
|
const arg = argv[i];
|
|
202
575
|
if (arg === "--help" || arg === "-h") return { help: true };
|
|
203
576
|
else if (arg === "--json") json = true;
|
|
204
577
|
else if (arg === "--force") force = true;
|
|
205
|
-
else if (arg === "--
|
|
578
|
+
else if (arg === "--git") git = true;
|
|
579
|
+
else if (arg === "--no-git")
|
|
580
|
+
git = false;
|
|
206
581
|
else if (arg === "--principles" || arg.startsWith("--principles=")) {
|
|
207
582
|
const v = arg === "--principles" ? argv[++i] : arg.slice("--principles=".length);
|
|
208
583
|
if (v === void 0 || v.trim() === "") {
|
|
@@ -215,8 +590,7 @@ function parseInitArgs(argv) {
|
|
|
215
590
|
return { error: { code: EXIT.INVALID_ARG, message: "--vault-name requires a value" } };
|
|
216
591
|
}
|
|
217
592
|
vaultName = v.trim();
|
|
218
|
-
} else if (arg === "--console-url" || arg.startsWith("--console-url=")
|
|
219
|
-
arg.startsWith("--cockpit-url=")) {
|
|
593
|
+
} else if (arg === "--console-url" || arg.startsWith("--console-url=")) {
|
|
220
594
|
const eq = arg.indexOf("=");
|
|
221
595
|
const v = eq === -1 ? argv[++i] : arg.slice(eq + 1);
|
|
222
596
|
if (v === void 0 || v.trim() === "") {
|
|
@@ -247,17 +621,36 @@ function parseInitArgs(argv) {
|
|
|
247
621
|
}
|
|
248
622
|
}
|
|
249
623
|
return {
|
|
250
|
-
args: { consoleUrl, vaultRoot, force, json, customerVaultDir, principles, vaultName,
|
|
624
|
+
args: { consoleUrl, vaultRoot, force, json, customerVaultDir, principles, vaultName, git }
|
|
251
625
|
};
|
|
252
626
|
}
|
|
627
|
+
var HEYSPEX_CONFIG_DIR = ".heyspex";
|
|
628
|
+
var HEYSPEX_CONFIG_FILE = "config.yaml";
|
|
629
|
+
function seedConfigYaml(root, readSeed, fs) {
|
|
630
|
+
const configDir = fs.joinPath(root, HEYSPEX_CONFIG_DIR);
|
|
631
|
+
const configPath = fs.joinPath(configDir, HEYSPEX_CONFIG_FILE);
|
|
632
|
+
if (fs.exists(configPath)) return { status: "kept", path: configPath };
|
|
633
|
+
if (!readSeed) return { status: "no_seed", path: configPath };
|
|
634
|
+
let seed;
|
|
635
|
+
try {
|
|
636
|
+
seed = readSeed();
|
|
637
|
+
} catch {
|
|
638
|
+
seed = null;
|
|
639
|
+
}
|
|
640
|
+
if (seed === null) return { status: "no_seed", path: configPath };
|
|
641
|
+
fs.mkdirRecursive(configDir);
|
|
642
|
+
fs.writeFile(configPath, seed);
|
|
643
|
+
return { status: "seeded", path: configPath };
|
|
644
|
+
}
|
|
253
645
|
function renderHeyspexYaml(args) {
|
|
254
|
-
|
|
646
|
+
const body = [
|
|
255
647
|
`schema_version: ${HEYSPEX_YAML_SCHEMA_VERSION}`,
|
|
256
648
|
`console_url: ${args.consoleUrl}`,
|
|
257
649
|
`vault_root: ${args.vaultRoot}`,
|
|
258
650
|
`vault_id: ${args.vaultId}`,
|
|
259
651
|
""
|
|
260
652
|
].join("\n");
|
|
653
|
+
return args.accountId ? recordMarkerAccount(body, args.accountId).content : body;
|
|
261
654
|
}
|
|
262
655
|
function readExistingContent(deps, markerPath) {
|
|
263
656
|
if (!deps.readFile) return null;
|
|
@@ -272,17 +665,60 @@ function runInit(args, deps) {
|
|
|
272
665
|
const exists = deps.fileExists(markerPath);
|
|
273
666
|
const existingContent = exists ? readExistingContent(deps, markerPath) : null;
|
|
274
667
|
const existingVaultId = existingContent ? parseVaultId(existingContent) : null;
|
|
668
|
+
if (deps.mkdirRecursive) {
|
|
669
|
+
const specsPath = deps.joinPath(deps.repoRoot, args.vaultRoot);
|
|
670
|
+
try {
|
|
671
|
+
deps.mkdirRecursive(specsPath);
|
|
672
|
+
} catch (err) {
|
|
673
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
674
|
+
return {
|
|
675
|
+
code: EXIT.WRITE_FAILED,
|
|
676
|
+
message: `failed to create ${specsPath}: ${message}`,
|
|
677
|
+
payload: { ok: false, error: "write_failed", path: specsPath, message },
|
|
678
|
+
isError: true
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
let configSeed = null;
|
|
683
|
+
const mkdirRecursive = deps.mkdirRecursive;
|
|
684
|
+
if (mkdirRecursive) {
|
|
685
|
+
const readConfigSeed2 = deps.readConfigSeed;
|
|
686
|
+
try {
|
|
687
|
+
configSeed = seedConfigYaml(
|
|
688
|
+
deps.repoRoot,
|
|
689
|
+
readConfigSeed2 ? () => readConfigSeed2.call(deps) : void 0,
|
|
690
|
+
{
|
|
691
|
+
exists: (p) => deps.fileExists(p),
|
|
692
|
+
mkdirRecursive: (p) => mkdirRecursive.call(deps, p),
|
|
693
|
+
writeFile: (p, c) => deps.writeFile(p, c),
|
|
694
|
+
joinPath: (...s) => deps.joinPath(...s)
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
699
|
+
const configPath = deps.joinPath(deps.repoRoot, HEYSPEX_CONFIG_DIR, HEYSPEX_CONFIG_FILE);
|
|
700
|
+
return {
|
|
701
|
+
code: EXIT.WRITE_FAILED,
|
|
702
|
+
message: `failed to seed ${configPath}: ${message}`,
|
|
703
|
+
payload: { ok: false, error: "write_failed", path: configPath, message },
|
|
704
|
+
isError: true
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const configNote = configSeed?.status === "seeded" ? ` Seeded ${configSeed.path}.` : configSeed?.status === "kept" ? ` Kept existing ${configSeed.path}.` : "";
|
|
709
|
+
const configPayload = configSeed === null || configSeed.status === "no_seed" ? {} : { config_yaml: configSeed.path, config_yaml_seeded: configSeed.status === "seeded" };
|
|
275
710
|
if (exists && !args.force) {
|
|
276
711
|
if (existingVaultId) {
|
|
277
712
|
return {
|
|
278
713
|
code: EXIT.OK,
|
|
279
|
-
message: `heyspex.yaml already exists at ${markerPath} \u2014 vault already initialized (use --force to overwrite)
|
|
714
|
+
message: `heyspex.yaml already exists at ${markerPath} \u2014 vault already initialized (use --force to overwrite).${configNote}`,
|
|
280
715
|
payload: {
|
|
281
716
|
ok: true,
|
|
282
717
|
created: false,
|
|
283
718
|
path: markerPath,
|
|
284
719
|
reason: "already_initialized",
|
|
285
|
-
vault_id: existingVaultId
|
|
720
|
+
vault_id: existingVaultId,
|
|
721
|
+
...configPayload
|
|
286
722
|
},
|
|
287
723
|
isError: false
|
|
288
724
|
};
|
|
@@ -290,8 +726,14 @@ function runInit(args, deps) {
|
|
|
290
726
|
if (existingContent === null) {
|
|
291
727
|
return {
|
|
292
728
|
code: EXIT.OK,
|
|
293
|
-
message: `heyspex.yaml already exists at ${markerPath} \u2014 vault already initialized (use --force to overwrite)
|
|
294
|
-
payload: {
|
|
729
|
+
message: `heyspex.yaml already exists at ${markerPath} \u2014 vault already initialized (use --force to overwrite).${configNote}`,
|
|
730
|
+
payload: {
|
|
731
|
+
ok: true,
|
|
732
|
+
created: false,
|
|
733
|
+
path: markerPath,
|
|
734
|
+
reason: "already_initialized",
|
|
735
|
+
...configPayload
|
|
736
|
+
},
|
|
295
737
|
isError: false
|
|
296
738
|
};
|
|
297
739
|
}
|
|
@@ -310,19 +752,21 @@ function runInit(args, deps) {
|
|
|
310
752
|
}
|
|
311
753
|
return {
|
|
312
754
|
code: EXIT.OK,
|
|
313
|
-
message: `Added vault_id to existing heyspex.yaml at ${markerPath} (schema_version unchanged)
|
|
755
|
+
message: `Added vault_id to existing heyspex.yaml at ${markerPath} (schema_version unchanged).${configNote}`,
|
|
314
756
|
payload: {
|
|
315
757
|
ok: true,
|
|
316
758
|
created: false,
|
|
317
759
|
backfilled: true,
|
|
318
760
|
path: markerPath,
|
|
319
|
-
vault_id: mintedId
|
|
761
|
+
vault_id: mintedId,
|
|
762
|
+
...configPayload
|
|
320
763
|
},
|
|
321
764
|
isError: false
|
|
322
765
|
};
|
|
323
766
|
}
|
|
324
767
|
const vaultId = existingVaultId ?? args.adoptedVaultId ?? mintVaultId();
|
|
325
|
-
const
|
|
768
|
+
const accountId = args.accountId ?? (existingContent ? parseAccountId(existingContent) : null);
|
|
769
|
+
const content = renderHeyspexYaml({ ...args, vaultId, accountId });
|
|
326
770
|
try {
|
|
327
771
|
deps.writeFile(markerPath, content);
|
|
328
772
|
} catch (err) {
|
|
@@ -336,7 +780,7 @@ function runInit(args, deps) {
|
|
|
336
780
|
}
|
|
337
781
|
return {
|
|
338
782
|
code: EXIT.OK,
|
|
339
|
-
message: `${exists ? "Overwrote" : "Wrote"} ${markerPath} (schema_version ${HEYSPEX_YAML_SCHEMA_VERSION})
|
|
783
|
+
message: `${exists ? "Overwrote" : "Wrote"} ${markerPath} (schema_version ${HEYSPEX_YAML_SCHEMA_VERSION}).${configNote}`,
|
|
340
784
|
payload: {
|
|
341
785
|
ok: true,
|
|
342
786
|
created: true,
|
|
@@ -345,7 +789,9 @@ function runInit(args, deps) {
|
|
|
345
789
|
schema_version: HEYSPEX_YAML_SCHEMA_VERSION,
|
|
346
790
|
console_url: args.consoleUrl,
|
|
347
791
|
vault_root: args.vaultRoot,
|
|
348
|
-
vault_id: vaultId
|
|
792
|
+
vault_id: vaultId,
|
|
793
|
+
...accountId ? { account: accountId } : {},
|
|
794
|
+
...configPayload
|
|
349
795
|
},
|
|
350
796
|
isError: false
|
|
351
797
|
};
|
|
@@ -354,27 +800,38 @@ var HELP = [
|
|
|
354
800
|
"Usage:",
|
|
355
801
|
" hs init [--console-url <url>] [--vault-root <path>] [--force] [--json]",
|
|
356
802
|
" hs init --customer-vault <dir> [--console-url <url>] [--principles <all|none|a,b>]",
|
|
357
|
-
" [--vault-name <name>] [--
|
|
803
|
+
" [--vault-name <name>] [--git] [--json]",
|
|
358
804
|
"",
|
|
359
805
|
"Write the top-level heyspex.yaml vault marker (schema_version, console_url,",
|
|
360
|
-
"vault_root, vault_id
|
|
806
|
+
"vault_root, vault_id; `hs connect` also records `account:`, the account id the",
|
|
807
|
+
"repo was connected under) \u2014 the committed file the console / web companion read",
|
|
361
808
|
"to bootstrap a vault. Idempotent: an existing marker is left untouched unless",
|
|
362
809
|
"--force, EXCEPT a missing vault_id is backfilled in place either way (backward",
|
|
363
810
|
"compatible \u2014 no schema_version bump). vault_id is reused exactly when already",
|
|
364
811
|
"present; a fresh one is minted only when absent.",
|
|
365
|
-
"
|
|
812
|
+
"Also scaffolds the vault_root folder and seeds .heyspex/config.yaml (the",
|
|
813
|
+
"HeySpex workflow settings, ADR-0009) from the shipped template",
|
|
814
|
+
"(vendor/heyspex-mcp/config-seed.yaml) when that file is absent. An existing",
|
|
815
|
+
".heyspex/config.yaml is never rewritten \u2014 not even with --force; change its",
|
|
816
|
+
"values with `hs settings set <key> <value>`.",
|
|
366
817
|
"",
|
|
367
818
|
"--customer-vault <dir> scaffolds a REAL customer vault at <dir> instead: the",
|
|
368
|
-
"specs/ folder tree, heyspex.yaml,
|
|
369
|
-
"
|
|
819
|
+
"specs/ folder tree, heyspex.yaml, the seeded .heyspex/config.yaml, and every",
|
|
820
|
+
"row of the template shipping manifest",
|
|
821
|
+
"(spex-console/src/lib/cli/template-manifest.ts) \u2014 universal docs",
|
|
370
822
|
"copied verbatim, template-shape docs written as author-prompt skeletons \u2014",
|
|
371
|
-
"plus a generated CLAUDE.md, the kept-principles index, and
|
|
372
|
-
"one initial `git init` + commit
|
|
373
|
-
"
|
|
823
|
+
"plus a generated CLAUDE.md, the kept-principles index, and the template_version",
|
|
824
|
+
"pin; with --git, one initial `git init` + commit. Rerunnable: a <dir> that already",
|
|
825
|
+
"holds heyspex.yaml and an empty/placeholder-only specs/ is accepted, existing",
|
|
826
|
+
"files are never overwritten (an existing CLAUDE.md gets a CLAUDE.md.template",
|
|
827
|
+
"beside it), and the result lists each manifest row as created or kept. Refuses a",
|
|
828
|
+
"non-empty <dir> without heyspex.yaml, or a specs/ holding managed specs.",
|
|
829
|
+
"See init.customer-vault.ts.",
|
|
374
830
|
"",
|
|
375
831
|
"Flags:",
|
|
376
|
-
" --console-url <url> console_url value (default:
|
|
377
|
-
"
|
|
832
|
+
" --console-url <url> console_url value (default: the mode-aware console --",
|
|
833
|
+
" $HEYSPEX_CONSOLE_URL, else the signed-in console, else",
|
|
834
|
+
" https://app.heyspex.com in prod mode).",
|
|
378
835
|
" --vault-root <path> vault_root value (default: specs/).",
|
|
379
836
|
" --customer-vault <dir> Scaffold a new customer vault at <dir> (see above).",
|
|
380
837
|
" --principles <all|none|a,b> Which universal principle docs to ship",
|
|
@@ -382,8 +839,9 @@ var HELP = [
|
|
|
382
839
|
" keep/skip prompt per principle. Omitted + no TTY: all.",
|
|
383
840
|
" --vault-name <name> Display name for the scaffolded vault (--customer-vault",
|
|
384
841
|
" only; default: <dir>'s basename).",
|
|
385
|
-
" --
|
|
386
|
-
"
|
|
842
|
+
" --git Also run git init + one initial commit (--customer-vault only;",
|
|
843
|
+
" default: no git). --no-git is accepted as a no-op.",
|
|
844
|
+
" --force Overwrite an existing heyspex.yaml (never .heyspex/config.yaml).",
|
|
387
845
|
" --json Emit a machine-readable JSON result.",
|
|
388
846
|
" --help, -h Show this help.",
|
|
389
847
|
"",
|
|
@@ -407,14 +865,69 @@ var UNIVERSAL_TEMPLATE_ROWS = [
|
|
|
407
865
|
{
|
|
408
866
|
platformSource: "specs/protocols/memory-protocol.md",
|
|
409
867
|
dest: "specs/protocols/memory-protocol.md"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
// ~455 lines of HS-REV / MVP-grain / guardrail detail that cannot be
|
|
871
|
+
// curated cleanly: ships a template-shape abridgement instead.
|
|
872
|
+
platformSource: "specs/protocols/session-close-protocol.md",
|
|
873
|
+
abridgedSource: "spex-console/templates/abridged/specs/protocols/session-close-protocol.md",
|
|
874
|
+
dest: "specs/protocols/session-close-protocol.md"
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
platformSource: "specs/protocols/spec-summary-authoring.md",
|
|
878
|
+
dest: "specs/protocols/spec-summary-authoring.md",
|
|
879
|
+
dropSections: ["Keeping the code snapshot in sync", "Change log"],
|
|
880
|
+
dropLinePatterns: ["vendor/heyspex-mcp/summary-contract\\.mjs"]
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
platformSource: "specs/protocols/destructive-action-checklist.md",
|
|
884
|
+
dest: "specs/protocols/destructive-action-checklist.md",
|
|
885
|
+
dropSections: ["Change log"],
|
|
886
|
+
dropLinePatterns: ["spex-console/scripts/validate-destructive-actions\\.mjs"],
|
|
887
|
+
replacements: [
|
|
888
|
+
[
|
|
889
|
+
"The 2026-05-22 idea-discard incident showed that strong UX *principles*\n([[errors-destructive-recovery]], [[reversible-user-actions]]) are not\nenough",
|
|
890
|
+
"A past idea-discard data-loss incident showed that strong UX *principles*\n(recoverable destructive actions, reversible user actions) are not\nenough"
|
|
891
|
+
],
|
|
892
|
+
[
|
|
893
|
+
"Today: linked from `CLAUDE.md` Console pre-ship checklist; agents must\nwalk it",
|
|
894
|
+
"Today: agents must\nwalk it"
|
|
895
|
+
],
|
|
896
|
+
[
|
|
897
|
+
'- [ ] If a *new* destructive surface is introduced, append it to the\n "Where this is applied" table in\n [[destructive-action-needs-explicit-button-and-undo]].',
|
|
898
|
+
"- [ ] If a *new* destructive surface is introduced, record it in a vault memory."
|
|
899
|
+
],
|
|
900
|
+
[
|
|
901
|
+
"- [ ] Read [[errors-destructive-recovery]] and\n [[destructive-action-needs-explicit-button-and-undo]] in full.",
|
|
902
|
+
"- [ ] Read any vault memories on destructive-action recovery and Undo."
|
|
903
|
+
],
|
|
904
|
+
[
|
|
905
|
+
" (Not covered here; see existing dirty-editor\n pattern in `specs-surface.tsx`.)",
|
|
906
|
+
" (Not covered here.)"
|
|
907
|
+
]
|
|
908
|
+
]
|
|
410
909
|
}
|
|
411
910
|
];
|
|
412
911
|
|
|
413
912
|
// dist/cli/._template-manifest-sources.cli-shim.mjs
|
|
414
913
|
var CURATED_TEMPLATE_ROOT = "dist/cli-templates/customer-vault";
|
|
415
914
|
|
|
915
|
+
// scripts/lib/template-pin.mjs
|
|
916
|
+
var TEMPLATE_VERSION = "1.1.0";
|
|
917
|
+
var TEMPLATE_CORE_DESTS = Object.freeze([
|
|
918
|
+
"specs/north-star.md",
|
|
919
|
+
"specs/principles-index.md",
|
|
920
|
+
"specs/roadmap.md",
|
|
921
|
+
"specs/entity-model.md",
|
|
922
|
+
"specs/protocols/canonical-frontmatter-templates.md",
|
|
923
|
+
"specs/protocols/spec-hierarchy.md",
|
|
924
|
+
"specs/protocols/memory-protocol.md",
|
|
925
|
+
"specs/protocols/session-close-protocol.md",
|
|
926
|
+
"specs/protocols/spec-summary-authoring.md",
|
|
927
|
+
"specs/protocols/destructive-action-checklist.md"
|
|
928
|
+
]);
|
|
929
|
+
|
|
416
930
|
// spex-console/src/lib/cli/template-manifest.ts
|
|
417
|
-
var TEMPLATE_VERSION = "1.0.0";
|
|
418
931
|
var TODO_NORTH_STAR = `---
|
|
419
932
|
id: HS-L1
|
|
420
933
|
name: TODO \u2014 your product name
|
|
@@ -804,7 +1317,7 @@ var TEMPLATE_MANIFEST = [
|
|
|
804
1317
|
];
|
|
805
1318
|
|
|
806
1319
|
// spex-console/src/lib/cli/init.customer-vault.ts
|
|
807
|
-
var
|
|
1320
|
+
var DEFAULT_CONSOLE_URL2 = "https://console.heyspex.local";
|
|
808
1321
|
var SCAFFOLD_FOLDERS = [
|
|
809
1322
|
"tasks",
|
|
810
1323
|
"epics",
|
|
@@ -821,23 +1334,63 @@ var SCAFFOLD_FOLDERS = [
|
|
|
821
1334
|
"checks",
|
|
822
1335
|
"events"
|
|
823
1336
|
];
|
|
1337
|
+
var PLACEHOLDER_FILES = /* @__PURE__ */ new Set([".gitkeep"]);
|
|
1338
|
+
function isDir(deps, path2, name) {
|
|
1339
|
+
if (deps.isDirectory) return deps.isDirectory(path2);
|
|
1340
|
+
try {
|
|
1341
|
+
const kids = deps.readdir(path2);
|
|
1342
|
+
return kids.length > 0 || !name.includes(".");
|
|
1343
|
+
} catch {
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function findManagedSpecs(deps, targetDir, manifestDests) {
|
|
1348
|
+
const out = [];
|
|
1349
|
+
const walk = (abs, rel) => {
|
|
1350
|
+
for (const name of deps.readdir(abs)) {
|
|
1351
|
+
const childAbs = deps.joinPath(abs, name);
|
|
1352
|
+
const childRel = `${rel}/${name}`;
|
|
1353
|
+
if (isDir(deps, childAbs, name)) walk(childAbs, childRel);
|
|
1354
|
+
else if (!PLACEHOLDER_FILES.has(name) && name.endsWith(".md") && !manifestDests.has(childRel))
|
|
1355
|
+
out.push(childRel);
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
const specsRoot = deps.joinPath(targetDir, "specs");
|
|
1359
|
+
if (deps.exists(specsRoot)) walk(specsRoot, "specs");
|
|
1360
|
+
return out;
|
|
1361
|
+
}
|
|
824
1362
|
function defaultVaultName(targetDir) {
|
|
825
1363
|
return basename(targetDir) || targetDir;
|
|
826
1364
|
}
|
|
827
1365
|
function runCustomerVaultInit(args, deps, options = {}) {
|
|
828
1366
|
const { targetDir } = args;
|
|
829
1367
|
const written = [];
|
|
1368
|
+
const filter = options.manifestFilter ?? (() => true);
|
|
1369
|
+
const rows = TEMPLATE_MANIFEST.filter(filter);
|
|
830
1370
|
if (deps.exists(targetDir)) {
|
|
831
|
-
const
|
|
832
|
-
if (
|
|
1371
|
+
const markerPresent = deps.exists(deps.joinPath(targetDir, "heyspex.yaml"));
|
|
1372
|
+
if (!markerPresent && deps.readdir(targetDir).length > 0) {
|
|
833
1373
|
return {
|
|
834
1374
|
code: EXIT.INVALID_ARG,
|
|
835
|
-
message: `${targetDir} is not empty \u2014 customer-vault init refuses to write into an existing, non-empty directory.`,
|
|
1375
|
+
message: `${targetDir} is not empty \u2014 customer-vault init refuses to write into an existing, non-empty directory that has no heyspex.yaml.`,
|
|
836
1376
|
payload: { ok: false, error: "target_not_empty", path: targetDir },
|
|
837
1377
|
isError: true,
|
|
838
1378
|
written: []
|
|
839
1379
|
};
|
|
840
1380
|
}
|
|
1381
|
+
if (markerPresent) {
|
|
1382
|
+
const manifestDests = new Set(TEMPLATE_MANIFEST.map((r) => r.dest));
|
|
1383
|
+
const managed = findManagedSpecs(deps, targetDir, manifestDests);
|
|
1384
|
+
if (managed.length > 0) {
|
|
1385
|
+
return {
|
|
1386
|
+
code: EXIT.INVALID_ARG,
|
|
1387
|
+
message: `${targetDir}/specs already holds managed spec(s) (${managed.slice(0, 3).join(", ")}${managed.length > 3 ? ", ..." : ""}) \u2014 customer-vault init refuses to write over an existing vault.`,
|
|
1388
|
+
payload: { ok: false, error: "specs_not_empty", path: targetDir, specs: managed },
|
|
1389
|
+
isError: true,
|
|
1390
|
+
written: []
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
841
1394
|
} else {
|
|
842
1395
|
deps.mkdirRecursive(targetDir);
|
|
843
1396
|
}
|
|
@@ -846,19 +1399,37 @@ function runCustomerVaultInit(args, deps, options = {}) {
|
|
|
846
1399
|
for (const folder of SCAFFOLD_FOLDERS) {
|
|
847
1400
|
deps.mkdirRecursive(deps.joinPath(specsRoot, folder));
|
|
848
1401
|
}
|
|
849
|
-
const vaultId = mintVaultId();
|
|
850
|
-
const yamlContent = renderHeyspexYaml({
|
|
851
|
-
consoleUrl: options.consoleUrl ?? DEFAULT_CONSOLE_URL3,
|
|
852
|
-
vaultRoot: "specs/",
|
|
853
|
-
vaultId
|
|
854
|
-
});
|
|
855
1402
|
const heyspexYamlPath = deps.joinPath(targetDir, "heyspex.yaml");
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1403
|
+
let vaultId;
|
|
1404
|
+
if (deps.exists(heyspexYamlPath)) {
|
|
1405
|
+
const existingYaml = deps.readFile?.(heyspexYamlPath) ?? null;
|
|
1406
|
+
vaultId = (existingYaml ? parseVaultId(existingYaml) : null) ?? "";
|
|
1407
|
+
} else {
|
|
1408
|
+
vaultId = mintVaultId();
|
|
1409
|
+
deps.writeFile(
|
|
1410
|
+
heyspexYamlPath,
|
|
1411
|
+
renderHeyspexYaml({
|
|
1412
|
+
consoleUrl: options.consoleUrl ?? DEFAULT_CONSOLE_URL2,
|
|
1413
|
+
vaultRoot: "specs/",
|
|
1414
|
+
vaultId
|
|
1415
|
+
})
|
|
1416
|
+
);
|
|
1417
|
+
written.push(heyspexYamlPath);
|
|
1418
|
+
}
|
|
1419
|
+
const readConfigSeed2 = deps.readConfigSeed;
|
|
1420
|
+
const configSeed = seedConfigYaml(
|
|
1421
|
+
targetDir,
|
|
1422
|
+
readConfigSeed2 ? () => readConfigSeed2.call(deps) : void 0,
|
|
1423
|
+
deps
|
|
1424
|
+
);
|
|
1425
|
+
if (configSeed.status === "seeded") written.push(configSeed.path);
|
|
1426
|
+
const rowResults = [];
|
|
860
1427
|
for (const row of rows) {
|
|
861
1428
|
const destAbs = deps.joinPath(targetDir, ...row.dest.split("/"));
|
|
1429
|
+
if (deps.exists(destAbs)) {
|
|
1430
|
+
rowResults.push({ dest: row.dest, action: "kept" });
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
862
1433
|
if (row.classification === "universal-template") {
|
|
863
1434
|
const srcAbs = deps.joinPath(deps.platformRoot, ...row.source.split("/"));
|
|
864
1435
|
deps.copyFile(srcAbs, destAbs);
|
|
@@ -871,6 +1442,7 @@ function runCustomerVaultInit(args, deps, options = {}) {
|
|
|
871
1442
|
deps.writeFile(destAbs, row.content);
|
|
872
1443
|
}
|
|
873
1444
|
written.push(destAbs);
|
|
1445
|
+
rowResults.push({ dest: row.dest, action: "created" });
|
|
874
1446
|
}
|
|
875
1447
|
if (options.afterScaffold && options.afterScaffold.length > 0) {
|
|
876
1448
|
const ctx = {
|
|
@@ -878,22 +1450,28 @@ function runCustomerVaultInit(args, deps, options = {}) {
|
|
|
878
1450
|
vaultName: options.vaultName ?? defaultVaultName(targetDir),
|
|
879
1451
|
templateVersion: TEMPLATE_VERSION,
|
|
880
1452
|
manifestRows: rows,
|
|
1453
|
+
rows: rowResults,
|
|
881
1454
|
deps,
|
|
882
|
-
recordWrite: (
|
|
883
|
-
written.push(
|
|
1455
|
+
recordWrite: (path2) => {
|
|
1456
|
+
written.push(path2);
|
|
884
1457
|
}
|
|
885
1458
|
};
|
|
886
1459
|
for (const hook of options.afterScaffold) hook(ctx);
|
|
887
1460
|
}
|
|
1461
|
+
const createdCount = rowResults.filter((r) => r.action === "created").length;
|
|
1462
|
+
const keptCount = rowResults.length - createdCount;
|
|
888
1463
|
return {
|
|
889
1464
|
code: EXIT.OK,
|
|
890
|
-
message: `Initialized customer vault at ${targetDir} (${written.length} files written, template v${TEMPLATE_VERSION}).`,
|
|
1465
|
+
message: `Initialized customer vault at ${targetDir} (${written.length} files written, ${createdCount} manifest rows created, ${keptCount} kept, template v${TEMPLATE_VERSION}).`,
|
|
891
1466
|
payload: {
|
|
892
1467
|
ok: true,
|
|
893
1468
|
targetDir,
|
|
894
1469
|
templateVersion: TEMPLATE_VERSION,
|
|
895
1470
|
vaultId,
|
|
896
|
-
written
|
|
1471
|
+
written,
|
|
1472
|
+
rows: rowResults,
|
|
1473
|
+
created: createdCount,
|
|
1474
|
+
kept: keptCount
|
|
897
1475
|
},
|
|
898
1476
|
isError: false,
|
|
899
1477
|
written
|
|
@@ -902,7 +1480,6 @@ function runCustomerVaultInit(args, deps, options = {}) {
|
|
|
902
1480
|
|
|
903
1481
|
// spex-console/src/lib/cli/init-git-finalize.ts
|
|
904
1482
|
import { spawnSync } from "node:child_process";
|
|
905
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
906
1483
|
var nodeGitRunner = {
|
|
907
1484
|
run(args, cwd) {
|
|
908
1485
|
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
@@ -929,14 +1506,19 @@ function ensureTemplateVersionLine(content, version) {
|
|
|
929
1506
|
template_version: ${version}
|
|
930
1507
|
`;
|
|
931
1508
|
}
|
|
1509
|
+
function templatePinHook() {
|
|
1510
|
+
return (ctx) => {
|
|
1511
|
+
const yamlPath = ctx.deps.joinPath(ctx.targetDir, "heyspex.yaml");
|
|
1512
|
+
const existingYaml = ctx.deps.readFile?.(yamlPath);
|
|
1513
|
+
if (existingYaml === null || existingYaml === void 0) return;
|
|
1514
|
+
if (readTemplateVersion(existingYaml) === ctx.templateVersion) return;
|
|
1515
|
+
ctx.deps.writeFile(yamlPath, ensureTemplateVersionLine(existingYaml, ctx.templateVersion));
|
|
1516
|
+
ctx.recordWrite(yamlPath);
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
932
1519
|
function gitFinalizeHook(runner, notify) {
|
|
933
1520
|
return (ctx) => {
|
|
934
1521
|
const target = ctx.targetDir;
|
|
935
|
-
const yamlPath = ctx.deps.joinPath(target, "heyspex.yaml");
|
|
936
|
-
const existingYaml = readFileSync3(yamlPath, "utf8");
|
|
937
|
-
const updatedYaml = ensureTemplateVersionLine(existingYaml, ctx.templateVersion);
|
|
938
|
-
ctx.deps.writeFile(yamlPath, updatedYaml);
|
|
939
|
-
ctx.recordWrite(yamlPath);
|
|
940
1522
|
const probe = runner.run(["--version"], target);
|
|
941
1523
|
if (!probe.ok) {
|
|
942
1524
|
notify("git not found - vault left uninitialized, run git init yourself");
|
|
@@ -1015,6 +1597,7 @@ function principleManifestFilter(selected) {
|
|
|
1015
1597
|
}
|
|
1016
1598
|
function principlesIndexHook(selected, rows) {
|
|
1017
1599
|
return (ctx) => {
|
|
1600
|
+
if (ctx.rows.some((r) => r.dest === PRINCIPLES_INDEX_DEST && r.action === "kept")) return;
|
|
1018
1601
|
const indexRow = rows.find((row) => row.dest === PRINCIPLES_INDEX_DEST);
|
|
1019
1602
|
if (!indexRow || indexRow.content === void 0) {
|
|
1020
1603
|
throw new Error(
|
|
@@ -1031,10 +1614,53 @@ function principlesIndexHook(selected, rows) {
|
|
|
1031
1614
|
};
|
|
1032
1615
|
}
|
|
1033
1616
|
|
|
1617
|
+
// spex-console/bin/run-entry.ts
|
|
1618
|
+
import process2 from "node:process";
|
|
1619
|
+
var BinExit = class extends Error {
|
|
1620
|
+
code;
|
|
1621
|
+
constructor(code) {
|
|
1622
|
+
super(`exit ${code}`);
|
|
1623
|
+
this.name = "BinExit";
|
|
1624
|
+
this.code = code;
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
function runEntry(name, main2, options = {}) {
|
|
1628
|
+
const { fallbackCode = 1, unhandledLabel = "unhandled" } = options;
|
|
1629
|
+
void (async () => {
|
|
1630
|
+
try {
|
|
1631
|
+
await main2();
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
if (err instanceof BinExit) {
|
|
1634
|
+
process2.exitCode = err.code;
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1638
|
+
process2.stderr.write(`${name}: ${unhandledLabel}: ${message}
|
|
1639
|
+
`);
|
|
1640
|
+
process2.exitCode = fallbackCode;
|
|
1641
|
+
}
|
|
1642
|
+
})();
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1034
1645
|
// spex-console/bin/hs-init.ts
|
|
1035
|
-
var INTERACTIVE =
|
|
1646
|
+
var INTERACTIVE = process3.stdin.isTTY === true;
|
|
1647
|
+
var PKG_ROOT = resolve5(
|
|
1648
|
+
resolveCodeRoot({ packageRoot: resolve5(dirname5(fileURLToPath2(import.meta.url)), "..", "..") })
|
|
1649
|
+
);
|
|
1650
|
+
var CONFIG_SEED_PATH = join9(PKG_ROOT, "vendor", "heyspex-mcp", "config-seed.yaml");
|
|
1651
|
+
function readConfigSeed() {
|
|
1652
|
+
try {
|
|
1653
|
+
return readFileSync8(CONFIG_SEED_PATH, "utf8");
|
|
1654
|
+
} catch {
|
|
1655
|
+
process3.stderr.write(
|
|
1656
|
+
`hs init: warning \u2014 config seed template not found at ${CONFIG_SEED_PATH}; .heyspex/config.yaml was not seeded (run \`hs settings set <key> <value>\` to create it).
|
|
1657
|
+
`
|
|
1658
|
+
);
|
|
1659
|
+
return null;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1036
1662
|
async function askYesNo(question) {
|
|
1037
|
-
const rl = createInterface({ input:
|
|
1663
|
+
const rl = createInterface({ input: process3.stdin, output: process3.stdout });
|
|
1038
1664
|
try {
|
|
1039
1665
|
const answer = (await rl.question(`${question} `)).trim().toLowerCase();
|
|
1040
1666
|
return answer === "y" || answer === "yes" || answer === "";
|
|
@@ -1047,9 +1673,9 @@ async function resolvePrincipleSelection(principlesFlag) {
|
|
|
1047
1673
|
return parsePrinciplesFlag(principlesFlag, TEMPLATE_MANIFEST);
|
|
1048
1674
|
}
|
|
1049
1675
|
if (INTERACTIVE) {
|
|
1050
|
-
|
|
1676
|
+
process3.stdout.write("Select which universal principle docs to ship with this vault:\n");
|
|
1051
1677
|
for (const choice of listPrincipleChoices(TEMPLATE_MANIFEST)) {
|
|
1052
|
-
|
|
1678
|
+
process3.stdout.write(` - ${choice.key}: ${choice.summary}
|
|
1053
1679
|
`);
|
|
1054
1680
|
}
|
|
1055
1681
|
const selected = await promptPrincipleSelection(askYesNo, TEMPLATE_MANIFEST);
|
|
@@ -1058,47 +1684,60 @@ async function resolvePrincipleSelection(principlesFlag) {
|
|
|
1058
1684
|
return parsePrinciplesFlag(void 0, TEMPLATE_MANIFEST);
|
|
1059
1685
|
}
|
|
1060
1686
|
async function main() {
|
|
1061
|
-
const repoRoot =
|
|
1062
|
-
const parsed = parseInitArgs(
|
|
1687
|
+
const repoRoot = process3.env.HS_VAULT_ROOT ? resolve5(process3.env.HS_VAULT_ROOT) : process3.cwd();
|
|
1688
|
+
const parsed = parseInitArgs(process3.argv.slice(3));
|
|
1063
1689
|
if (parsed.help) {
|
|
1064
|
-
|
|
1690
|
+
process3.stdout.write(`${HELP}
|
|
1065
1691
|
`);
|
|
1066
|
-
|
|
1692
|
+
process3.exitCode = EXIT.OK;
|
|
1693
|
+
return;
|
|
1067
1694
|
}
|
|
1068
1695
|
if (parsed.error) {
|
|
1069
|
-
|
|
1696
|
+
process3.stderr.write(`hs init: ${parsed.error.message}
|
|
1070
1697
|
`);
|
|
1071
|
-
|
|
1698
|
+
process3.exitCode = parsed.error.code;
|
|
1699
|
+
return;
|
|
1072
1700
|
}
|
|
1073
1701
|
const args = parsed.args;
|
|
1074
1702
|
if (args.customerVaultDir) {
|
|
1075
|
-
const here =
|
|
1076
|
-
const platformRoot =
|
|
1077
|
-
const targetDir =
|
|
1703
|
+
const here = dirname5(fileURLToPath2(import.meta.url));
|
|
1704
|
+
const platformRoot = resolve5(here, "..", "..");
|
|
1705
|
+
const targetDir = resolve5(args.customerVaultDir);
|
|
1078
1706
|
const deps = {
|
|
1079
1707
|
platformRoot,
|
|
1080
|
-
exists: (p) =>
|
|
1708
|
+
exists: (p) => existsSync4(p),
|
|
1081
1709
|
readdir: (p) => readdirSync(p),
|
|
1082
1710
|
mkdirRecursive: (p) => {
|
|
1083
|
-
|
|
1711
|
+
mkdirSync4(p, { recursive: true });
|
|
1084
1712
|
},
|
|
1085
|
-
writeFile: (p, c) =>
|
|
1713
|
+
writeFile: (p, c) => writeFileSync4(p, c, "utf8"),
|
|
1086
1714
|
copyFile: (src, dest) => copyFileSync(src, dest),
|
|
1087
|
-
joinPath: (...s) =>
|
|
1715
|
+
joinPath: (...s) => join9(...s),
|
|
1716
|
+
readConfigSeed,
|
|
1717
|
+
readFile: (p) => {
|
|
1718
|
+
try {
|
|
1719
|
+
return readFileSync8(p, "utf8");
|
|
1720
|
+
} catch {
|
|
1721
|
+
return null;
|
|
1722
|
+
}
|
|
1723
|
+
},
|
|
1724
|
+
isDirectory: (p) => statSync(p).isDirectory()
|
|
1088
1725
|
};
|
|
1089
1726
|
const principlesResult = await resolvePrincipleSelection(args.principles);
|
|
1090
1727
|
if (!principlesResult.ok) {
|
|
1091
|
-
|
|
1728
|
+
process3.stderr.write(`hs init: ${principlesResult.message}
|
|
1092
1729
|
`);
|
|
1093
|
-
|
|
1730
|
+
process3.exitCode = EXIT.INVALID_ARG;
|
|
1731
|
+
return;
|
|
1094
1732
|
}
|
|
1095
1733
|
const selected = principlesResult.selected;
|
|
1096
1734
|
const notices = [];
|
|
1097
1735
|
const afterScaffold = [
|
|
1098
|
-
customerClaudeMdHook(),
|
|
1099
|
-
principlesIndexHook(selected, TEMPLATE_MANIFEST)
|
|
1736
|
+
customerClaudeMdHook((msg) => notices.push(msg)),
|
|
1737
|
+
principlesIndexHook(selected, TEMPLATE_MANIFEST),
|
|
1738
|
+
templatePinHook()
|
|
1100
1739
|
];
|
|
1101
|
-
if (
|
|
1740
|
+
if (args.git) {
|
|
1102
1741
|
afterScaffold.push(gitFinalizeHook(nodeGitRunner, (msg) => notices.push(msg)));
|
|
1103
1742
|
}
|
|
1104
1743
|
const result2 = runCustomerVaultInit({ targetDir }, deps, {
|
|
@@ -1107,7 +1746,7 @@ async function main() {
|
|
|
1107
1746
|
manifestFilter: principleManifestFilter(selected),
|
|
1108
1747
|
afterScaffold
|
|
1109
1748
|
});
|
|
1110
|
-
const sink2 = result2.isError ?
|
|
1749
|
+
const sink2 = result2.isError ? process3.stderr : process3.stdout;
|
|
1111
1750
|
if (args.json) {
|
|
1112
1751
|
sink2.write(`${JSON.stringify({ ...result2.payload, notices })}
|
|
1113
1752
|
`);
|
|
@@ -1117,29 +1756,29 @@ async function main() {
|
|
|
1117
1756
|
for (const notice of notices) sink2.write(`${notice}
|
|
1118
1757
|
`);
|
|
1119
1758
|
}
|
|
1120
|
-
|
|
1759
|
+
process3.exitCode = result2.code;
|
|
1760
|
+
return;
|
|
1121
1761
|
}
|
|
1122
1762
|
const result = runInit(args, {
|
|
1123
1763
|
repoRoot,
|
|
1124
|
-
fileExists: (p) =>
|
|
1764
|
+
fileExists: (p) => existsSync4(p),
|
|
1125
1765
|
readFile: (p) => {
|
|
1126
1766
|
try {
|
|
1127
|
-
return
|
|
1767
|
+
return readFileSync8(p, "utf8");
|
|
1128
1768
|
} catch {
|
|
1129
1769
|
return null;
|
|
1130
1770
|
}
|
|
1131
1771
|
},
|
|
1132
|
-
writeFile: (p, c) =>
|
|
1133
|
-
joinPath: (...s) =>
|
|
1772
|
+
writeFile: (p, c) => writeFileSync4(p, c, "utf8"),
|
|
1773
|
+
joinPath: (...s) => join9(...s),
|
|
1774
|
+
mkdirRecursive: (p) => {
|
|
1775
|
+
mkdirSync4(p, { recursive: true });
|
|
1776
|
+
},
|
|
1777
|
+
readConfigSeed
|
|
1134
1778
|
});
|
|
1135
|
-
const sink = result.isError ?
|
|
1779
|
+
const sink = result.isError ? process3.stderr : process3.stdout;
|
|
1136
1780
|
sink.write(`${args.json ? JSON.stringify(result.payload) : result.message}
|
|
1137
1781
|
`);
|
|
1138
|
-
|
|
1782
|
+
process3.exitCode = result.code;
|
|
1139
1783
|
}
|
|
1140
|
-
|
|
1141
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1142
|
-
process2.stderr.write(`hs init: unhandled: ${message}
|
|
1143
|
-
`);
|
|
1144
|
-
process2.exit(EXIT.WRITE_FAILED);
|
|
1145
|
-
});
|
|
1784
|
+
runEntry("hs init", main, { fallbackCode: EXIT.WRITE_FAILED });
|