heyspex 0.2.1 → 0.2.3

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.
Files changed (70) hide show
  1. package/README.md +10 -0
  2. package/bin/hs-vault-binding.mjs +157 -0
  3. package/bin/hs.mjs +99 -81
  4. package/bin/spexcode.mjs +91 -0
  5. package/dev-factory/engine/worktree.mjs +36 -7
  6. package/dev-factory/lib/epic-detect.mjs +2 -2
  7. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  8. package/dev-factory/lib/fleet-state.mjs +2 -2
  9. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  10. package/dist/cli/hs-check.mjs +76 -46
  11. package/dist/cli/hs-claim.mjs +63 -33
  12. package/dist/cli/hs-connect.mjs +520 -214
  13. package/dist/cli/hs-defer.mjs +161 -19
  14. package/dist/cli/hs-dev.mjs +11 -5
  15. package/dist/cli/hs-env.mjs +187 -161
  16. package/dist/cli/hs-index-rebuild.mjs +123 -78
  17. package/dist/cli/hs-index-reconcile.mjs +203 -107
  18. package/dist/cli/hs-init.mjs +426 -179
  19. package/dist/cli/hs-install.mjs +759 -437
  20. package/dist/cli/hs-join.mjs +590 -383
  21. package/dist/cli/hs-vault-pull.mjs +359 -254
  22. package/dist/cli/hs-vault-push.mjs +151 -113
  23. package/dist/cli/hs-vault.mjs +271 -104
  24. package/dist/cli/hs-workspace.mjs +90 -57
  25. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  26. package/docs/copilot-plugin-install.md +189 -162
  27. package/docs/driving-heyspex-from-copilot.md +174 -165
  28. package/docs/mcp-client-config.md +93 -25
  29. package/package.json +255 -249
  30. package/plugins/heyspex/.mcp.json +3 -4
  31. package/plugins/heyspex/README.md +323 -309
  32. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  33. package/plugins/heyspex/mcp.json +12 -13
  34. package/scripts/check-mcp-freshness.mjs +6 -4
  35. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  36. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  37. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  38. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  39. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  40. package/scripts/claude-statusline/statusline-command.mjs +1058 -0
  41. package/scripts/dev-server-status.mjs +3 -2
  42. package/scripts/dev-server-stop.mjs +3 -2
  43. package/scripts/hs-capture-cli.mjs +2 -2
  44. package/scripts/hs-settings.mjs +2 -2
  45. package/scripts/hs-view.mjs +3 -2
  46. package/scripts/install-copilot-hooks.mjs +318 -0
  47. package/scripts/lib/copilot-cli.mjs +108 -0
  48. package/scripts/lib/customer-kit.mjs +223 -30
  49. package/scripts/lib/dev-mode.mjs +28 -2
  50. package/scripts/lib/shared-path-helpers.mjs +32 -0
  51. package/scripts/lib/standalone-doctor.mjs +131 -32
  52. package/scripts/wip-ledger-health.mjs +3 -2
  53. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  54. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  55. package/spexharness/src/intent-directive.mjs +839 -626
  56. package/spexharness/src/rule-table.mjs +1 -1
  57. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  58. package/vendor/heyspex-mcp/cli.mjs +20 -4
  59. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  60. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  61. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  62. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  63. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  64. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  65. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  66. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  67. package/vendor/heyspex-mcp/tool-dispatch.mjs +59 -23
  68. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  69. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  70. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -48,7 +48,7 @@
48
48
  import fs from 'node:fs';
49
49
  import path from 'node:path';
50
50
  import { fileURLToPath } from 'node:url';
51
- import { getActiveConfig } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
51
+ import { getActiveConfig, isResolvedVault } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
52
52
  import { hookVaultHeaders } from '../lib/hook-vault-header.mjs';
53
53
  import { resolveConsoleUrl as resolveConsoleUrlShared } from '../lib/console-url-resolver.mjs';
54
54
  import { readCredentialsMcpToken, resolveDevMode } from '../lib/dev-mode.mjs';
@@ -68,26 +68,48 @@ function repoRoot() {
68
68
  // code checkout. Reading repoRoot() here previewed a frozen pre-split snapshot to
69
69
  // every new session — the HS-OR8H repo-split "quiet window OPEN" notice kept
70
70
  // re-appearing for days after the window closed, because the live ledger had moved
71
- // and this copy never advanced. Resolve the vault first; fall back to the code repo
72
- // so a pre-split or standalone checkout still previews its own ledger.
71
+ // and this copy never advanced. Resolve the vault first; fall back to the session's
72
+ // own project so a pre-split or standalone checkout still previews its own ledger.
73
73
  function vaultRoot() {
74
74
  try {
75
- return getActiveConfig()?.repoRootAbs ?? null;
75
+ const resolved = getActiveConfig();
76
+ return isResolvedVault(resolved) ? resolved.repoRootAbs ?? null : null;
76
77
  } catch {
77
- return null; // registry unreadable — the code-repo base still applies
78
+ return null; // registry unreadable — the project base still applies
78
79
  }
79
80
  }
80
81
 
81
- // Ledger roots in precedence order: vault first, code repo second.
82
+ // The session's project — NOT this script's location. In a satellite repo the hook
83
+ // runs out of the `heyspex` package (node_modules/heyspex, often an npm link to the
84
+ // platform checkout), so repoRoot() is the PLATFORM's tree and its ledger belongs to
85
+ // somebody else's sessions: a satellite once previewed the platform checkout's
86
+ // `HS-EPIC-FILLER-20` (a leaked wip-state.prune.test.ts fixture) as its own WIP.
87
+ function projectDir() {
88
+ const fromEnv = process.env.CLAUDE_PROJECT_DIR;
89
+ return path.resolve(typeof fromEnv === 'string' && fromEnv.trim() ? fromEnv.trim() : process.cwd());
90
+ }
91
+
92
+ // Ledger roots in precedence order: bound vault first, the session's project second.
93
+ // The package's own checkout is never a root unless it IS the project.
82
94
  function ledgerRoots() {
83
95
  const roots = [];
84
96
  const vault = vaultRoot();
85
- if (vault) roots.push(vault);
86
- const repo = repoRoot();
87
- if (!roots.includes(repo)) roots.push(repo);
97
+ if (vault) roots.push(path.resolve(vault));
98
+ const project = projectDir();
99
+ if (!roots.includes(project)) roots.push(project);
88
100
  return roots;
89
101
  }
90
102
 
103
+ // Defense in depth against test fixtures that escaped into a real ledger (the
104
+ // wip-state*.test.ts suites write ids like HS-EPIC-FILLER-07, HS-EPIC-OLD34,
105
+ // HS-EPIC-BUD-PROTECT under vault id `vault-test`). Real ids are server-minted
106
+ // file keys; a SessionStart preview must never present one of these as the
107
+ // prior session's hand-off.
108
+ const FIXTURE_ID_RE = /^HS-EPIC-(?:FILLER-\d+|OLD\d+|BUD-[A-Z0-9-]+|CLAIMED-BUD|TRIGGER(?:-[A-Z0-9-]+)?)$/;
109
+ function isFixtureLedgerEntry(epicId) {
110
+ return typeof epicId === 'string' && FIXTURE_ID_RE.test(epicId);
111
+ }
112
+
91
113
  async function readStdinJson() {
92
114
  if (process.stdin.isTTY) return null;
93
115
  return new Promise((resolve) => {
@@ -152,6 +174,7 @@ function mostRecentLedgerEntry() {
152
174
  let best = null;
153
175
  for (const [epicId, entry] of Object.entries(parsed)) {
154
176
  if (!entry || typeof entry !== 'object') continue;
177
+ if (isFixtureLedgerEntry(epicId)) continue;
155
178
  const updatedAt = typeof entry.updatedAt === 'string' ? entry.updatedAt : '';
156
179
  if (!best || updatedAt > best.updatedAt) {
157
180
  best = { epicId, updatedAt, entry };
@@ -228,6 +251,7 @@ async function fetchCloudLedgerEntry(root) {
228
251
  let best = null;
229
252
  for (const [specId, entry] of Object.entries(wipState)) {
230
253
  if (!entry || typeof entry !== 'object') continue;
254
+ if (isFixtureLedgerEntry(specId)) continue;
231
255
  const updatedAt = typeof entry.updatedAt === 'string' ? entry.updatedAt : '';
232
256
  if (!best || updatedAt > best.updatedAt) {
233
257
  best = { epicId: specId, updatedAt, entry };