heyspex 0.2.0 → 0.2.1

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 (145) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/bin/hs.mjs +163 -11
  3. package/dist/cli/hs-check.mjs +88 -46
  4. package/dist/cli/hs-claim.mjs +84 -39
  5. package/dist/cli/hs-connect.mjs +583 -129
  6. package/dist/cli/hs-defer.mjs +251 -30
  7. package/dist/cli/hs-dev.mjs +20 -8
  8. package/dist/cli/hs-env.mjs +62 -34
  9. package/dist/cli/hs-index-rebuild.mjs +288 -83
  10. package/dist/cli/hs-index-reconcile.mjs +397 -111
  11. package/dist/cli/hs-init.mjs +2143 -91
  12. package/dist/cli/hs-install.mjs +628 -106
  13. package/dist/cli/hs-join.mjs +686 -236
  14. package/dist/cli/hs-vault-pull.mjs +3189 -179
  15. package/dist/cli/hs-vault-push.mjs +6433 -0
  16. package/dist/cli/hs-vault.mjs +396 -178
  17. package/dist/cli/hs-workspace.mjs +289 -151
  18. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
  19. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
  20. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
  21. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
  22. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
  23. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
  24. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
  25. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
  26. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
  27. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
  28. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
  29. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
  30. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
  31. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
  32. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
  33. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
  34. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
  35. package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
  36. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
  37. package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
  38. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
  39. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
  40. package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
  41. package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
  42. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
  43. package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
  44. package/docs/copilot-plugin-install.md +162 -0
  45. package/docs/driving-heyspex-from-copilot.md +165 -0
  46. package/package.json +20 -2
  47. package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
  48. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
  49. package/plugins/heyspex/README.md +309 -0
  50. package/plugins/heyspex/bin/run-hook.mjs +20 -2
  51. package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
  52. package/plugins/heyspex/hooks/hooks.json +94 -37
  53. package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
  54. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
  55. package/scripts/claude-hooks/_run.mjs +316 -23
  56. package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
  57. package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
  58. package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
  59. package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
  60. package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
  61. package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
  62. package/scripts/claude-hooks/session-end.mjs +2 -0
  63. package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
  64. package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
  65. package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
  66. package/scripts/cursor-hooks/manifest.mjs +46 -0
  67. package/scripts/heyspex-mode.mjs +20 -0
  68. package/scripts/hs-mode.mjs +1 -1
  69. package/scripts/hs-view.mjs +180 -17
  70. package/scripts/install-claude-code-hooks.mjs +3 -1
  71. package/scripts/lib/console-url-resolver.mjs +3 -2
  72. package/scripts/lib/customer-kit.mjs +327 -10
  73. package/scripts/lib/dev-mode.mjs +7 -6
  74. package/scripts/lib/directive-obligations.mjs +349 -0
  75. package/scripts/lib/hook-dispositions.mjs +210 -0
  76. package/scripts/lib/hook-vault-header.mjs +30 -0
  77. package/scripts/lib/hs-source-branch.mjs +196 -0
  78. package/scripts/lib/hs-source-log.mjs +2 -1
  79. package/scripts/lib/hs-source-preflight.mjs +18 -4
  80. package/scripts/lib/hs-source-push.mjs +386 -356
  81. package/scripts/lib/window-placement.mjs +109 -0
  82. package/spexharness/src/adapters/claude-code.mjs +11 -3
  83. package/spexharness/src/adapters/copilot.mjs +236 -0
  84. package/spexharness/src/adapters/detect-harness.mjs +34 -0
  85. package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
  86. package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
  87. package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
  88. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
  89. package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
  90. package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
  91. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
  92. package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
  93. package/spexharness/src/intent-classifier.mjs +4 -2
  94. package/spexharness/src/intent-directive.mjs +626 -541
  95. package/spexharness/src/rule-table.mjs +31 -1
  96. package/vendor/heyspex-mcp/account-switch.mjs +6 -5
  97. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
  98. package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
  99. package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
  100. package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
  101. package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
  102. package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
  103. package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
  104. package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
  105. package/vendor/heyspex-mcp/cli.mjs +7 -1
  106. package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
  107. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
  108. package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
  109. package/vendor/heyspex-mcp/console-transport.mjs +7 -1
  110. package/vendor/heyspex-mcp/credentials.mjs +9 -8
  111. package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
  112. package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
  113. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
  114. package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
  115. package/vendor/heyspex-mcp/http-transport.mjs +2 -0
  116. package/vendor/heyspex-mcp/list-tools.mjs +1 -1
  117. package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
  118. package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
  119. package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
  120. package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
  121. package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
  122. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
  123. package/vendor/heyspex-mcp/plan-push.mjs +3 -2
  124. package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
  125. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
  126. package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
  127. package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
  128. package/vendor/heyspex-mcp/promote-research.mjs +6 -0
  129. package/vendor/heyspex-mcp/read-path.mjs +162 -6
  130. package/vendor/heyspex-mcp/request-context.mjs +25 -1
  131. package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
  132. package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
  133. package/vendor/heyspex-mcp/session-owner.mjs +3 -2
  134. package/vendor/heyspex-mcp/session-tools.mjs +10 -0
  135. package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
  136. package/vendor/heyspex-mcp/split-research.mjs +3 -0
  137. package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
  138. package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
  139. package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
  140. package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
  141. package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
  142. package/vendor/heyspex-mcp/vault-io.mjs +720 -724
  143. package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
  144. package/vendor/heyspex-mcp/whoami.mjs +2 -1
  145. package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * MCP-side backstop for the directive-obligation gate ([[HS-20SW-DRSM-DIAG-heyspex-capture-on-idea-and-workflow]]).
3
+ *
4
+ * The PreToolUse hook (spexharness/src/gates/directive-obligation-gate.mjs) is the primary enforcement,
5
+ * but hooks do not load on every surface (a bare MCP client, a seat with no harness). This module lets
6
+ * the tools themselves refuse implementation work while a capture directive is outstanding. It reads
7
+ * the SAME state file the hook writes, through scripts/lib/directive-obligations.mjs (never a second
8
+ * parser): `.heyspex/directive-obligations.json` under the project root.
9
+ *
10
+ * FAIL OPEN everywhere: a missing / unreadable state file, no obligation, any thrown error -> never refuse.
11
+ * Break-glass (owner only, env): HEYSPEX_OBLIGATION_BACKSTOP_OFF=1.
12
+ */
13
+ import path from 'node:path';
14
+
15
+ import { outstanding, readObligations } from '../../scripts/lib/directive-obligations.mjs';
16
+
17
+ export const OBLIGATION_BACKSTOP_OFF_ENV = 'HEYSPEX_OBLIGATION_BACKSTOP_OFF';
18
+ export const CAPTURE_OBLIGATION_ERROR = 'CAPTURE_OBLIGATION_OUTSTANDING';
19
+ /** With no resolvable session id, only an obligation armed this recently counts. */
20
+ export const NO_SESSION_WINDOW_MS = 30 * 60 * 1000;
21
+ /** heyspex_file_spec types that are implementation filings (research/diagnose/memory/capture stay open). */
22
+ export const IMPLEMENTATION_TYPES = Object.freeze(['task', 'epic', 'milestone']);
23
+
24
+ /** The ids the MCP identity layer returns when it could not resolve a real session. */
25
+ function isUnresolvedSession(sessionId) {
26
+ const s = String(sessionId ?? '').trim();
27
+ return !s || s === 'local';
28
+ }
29
+
30
+ /** Project roots to look in, in order: the hook's own (CLAUDE_PROJECT_DIR), the cwd, then the vault root. */
31
+ export function candidateRoots({ roots, vaultRoot, env = process.env } = {}) {
32
+ if (Array.isArray(roots) && roots.length) return roots;
33
+ const out = [];
34
+ const fromEnv = String(env.CLAUDE_PROJECT_DIR ?? '').trim();
35
+ if (fromEnv) out.push(fromEnv);
36
+ out.push(process.cwd());
37
+ if (vaultRoot) out.push(vaultRoot);
38
+ return [...new Set(out.map((r) => path.resolve(r)))];
39
+ }
40
+
41
+ function summarize(ob, reason) {
42
+ return { outstanding: true, reason, armedAt: ob.issued_at ?? null, directiveKey: ob.id ?? ob.prompt_sha ?? null };
43
+ }
44
+
45
+ const NONE = (reason) => ({ outstanding: false, reason, armedAt: null, directiveKey: null });
46
+
47
+ /**
48
+ * @param {{ sessionId?: string, now?: number, roots?: string[], vaultRoot?: string, env?: object }} [opts]
49
+ * @returns {{ outstanding: boolean, reason: string, armedAt: string|null, directiveKey: string|null }}
50
+ */
51
+ export function checkOutstandingCaptureObligation({ sessionId, now = Date.now(), roots, vaultRoot, env = process.env } = {}) {
52
+ try {
53
+ if (String(env[OBLIGATION_BACKSTOP_OFF_ENV] ?? '') === '1') return NONE('backstop off (env)');
54
+ const known = !isUnresolvedSession(sessionId);
55
+ for (const root of candidateRoots({ roots, vaultRoot, env })) {
56
+ const full = readObligations(root, known ? String(sessionId).trim() : undefined);
57
+ if (!full) continue;
58
+ if (known) {
59
+ const open = outstanding(full, String(sessionId).trim(), now);
60
+ if (open.length) return summarize(open[0], 'capture directive issued for this session and not yet satisfied');
61
+ continue;
62
+ }
63
+ // No session id: any unsatisfied obligation armed in this project within the window.
64
+ const records = [full, ...Object.entries(full.others ?? {}).map(([sid, o]) => ({ ...o, session_id: sid }))];
65
+ for (const rec of records) {
66
+ if (!rec?.session_id) continue;
67
+ const open = outstanding(rec, rec.session_id, now).filter((o) => now - Date.parse(o.issued_at) <= NO_SESSION_WINDOW_MS);
68
+ if (open.length) return summarize(open[0], 'capture directive armed in this project within 30 minutes and not yet satisfied (session unknown)');
69
+ }
70
+ }
71
+ return NONE('no outstanding capture obligation');
72
+ } catch {
73
+ return NONE('obligation state unreadable (fail open)');
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Tool-level verdict. Returns null (allow) or { error, message, tool, armed_at, directive_key }.
79
+ * Refuses heyspex_claim_spec always, and heyspex_file_spec only for task/epic/milestone filings.
80
+ */
81
+ export function evaluateObligationBackstop(toolName, args = {}, opts = {}) {
82
+ try {
83
+ if (toolName !== 'heyspex_claim_spec' && toolName !== 'heyspex_file_spec') return null;
84
+ if (toolName === 'heyspex_file_spec') {
85
+ const type = String(args?.type ?? '').trim().toLowerCase();
86
+ if (!IMPLEMENTATION_TYPES.includes(type)) return null;
87
+ }
88
+ const r = checkOutstandingCaptureObligation(opts);
89
+ if (!r.outstanding) return null;
90
+ return {
91
+ error: CAPTURE_OBLIGATION_ERROR,
92
+ message:
93
+ `${toolName} refused: the capture_on_idea directive was issued (${r.armedAt ?? 'earlier'}) and the capture has not happened. ` +
94
+ 'Call heyspex_update_capture for the armed capture (with the owner\'s exact words), or have the owner say "discard". ' +
95
+ `Then retry. (Owner-only break-glass: ${OBLIGATION_BACKSTOP_OFF_ENV}=1.)`,
96
+ tool: toolName,
97
+ armed_at: r.armedAt,
98
+ directive_key: r.directiveKey,
99
+ };
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
@@ -121,6 +121,7 @@ export async function collectOccupiedKeysEitherKindViaTier(
121
121
  { tier, consoleUrl, consoleFetch = defaultConsoleFetch, walk = walkSpecListPages } = {},
122
122
  ) {
123
123
  return readViaSelector({
124
+ allowDiskFallback: false,
124
125
  tier,
125
126
  diskRead: () => collectOccupiedKeysEitherKind(vaultRoot),
126
127
  neonRead: async () => {
@@ -44,7 +44,7 @@
44
44
  // full-mode each get phase-appropriate dispatch.
45
45
  // ────────────────────────────────────────────────────────────────────────────
46
46
 
47
- import { ConsoleUnreachableCloudModeError, readViaSelector, resolveReadTier, resolveVaultWriteMode } from './read-path.mjs';
47
+ import { ConsoleUnreachableCloudModeError, readViaSelector, resolveReadTier, resolveVaultWriteMode, withDegradationScope } from './read-path.mjs';
48
48
  import { consoleFetch, controlError, controlOk, enrichConsoleFailurePayloadAsync } from './console-transport.mjs';
49
49
  import { disambiguateClaimSpecId, findSpecFile, loadRoadmap, loadSpec, loadSpecByIdViaTier, loadTask, loadTaskViaGraph, parseFrontmatter, prefetchCloudTaskGraph, wikilinkTarget } from './vault-io.mjs';
50
50
  import { isAbsolute, join } from 'node:path';
@@ -1924,7 +1924,7 @@ export function composeAgentOrchestration(result, args = {}) {
1924
1924
  '',
1925
1925
  '---',
1926
1926
  '',
1927
- `📝 \`${displaySpecId}\` has no \`summary:\` yet — author one in the spec's frontmatter before claiming.`,
1927
+ `📝 \`${displaySpecId}\` has no \`summary:\` yet — author one with \`heyspex_author_summary\` (cloud reliance) or in the file's frontmatter (local reliance) before claiming.`,
1928
1928
  ].join('\n');
1929
1929
  return {
1930
1930
  user_prompt: notice,
@@ -2860,7 +2860,24 @@ export async function injectVaultWriteMode(args = {}, resolveWriteMode = resolve
2860
2860
  return args;
2861
2861
  }
2862
2862
 
2863
+ /**
2864
+ * Degrade-don't-die wrapper: when any cloud loader inside the brief fell back
2865
+ * to the local clone ({@link readViaSelector}), the response JSON carries
2866
+ * `degraded: true, source: 'disk', cloud_error, warning` at its top level.
2867
+ */
2863
2868
  export async function handleNextOnDeck(vaultRoot, config, args) {
2869
+ const { value, degraded } = await withDegradationScope(() => handleNextOnDeckInner(vaultRoot, config, args));
2870
+ if (!degraded || !value || value.isError) return value;
2871
+ try {
2872
+ const first = value.content?.[0];
2873
+ const parsed = JSON.parse(first.text);
2874
+ return { ...value, content: [{ ...first, text: JSON.stringify({ ...parsed, ...degraded }, null, 2) }] };
2875
+ } catch {
2876
+ return value;
2877
+ }
2878
+ }
2879
+
2880
+ async function handleNextOnDeckInner(vaultRoot, config, args) {
2864
2881
  // Resolve the Neon layer-1 pin first (async), then the frozen milestone
2865
2882
  // snapshot order (async, [[HS-TASK-JHIA]]), then run the sync resolver with
2866
2883
  // both threaded in via `neon_pin_spec_id` / `frozen_epic_order`. See