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,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * hs-source-branch.mjs — `hs source branch declare|show`, plus
4
+ * `resolvePushBranch` used by `hs source push` when no branch is typed
5
+ * ([[HS-G01N-CWPX-TASK-hs-source-declares-the-session-branch]], epic
6
+ * [[HS-B8CY-CWPX-EPIC-one-session-one-reviewable-change]]).
7
+ *
8
+ * The branch DECISION stays on the server: `GET /api/source/session-branch`
9
+ * runs the one shared resolver (`resolveSessionTargetBranch`,
10
+ * spex-console/src/lib/git-export/branch-resolver.ts) — the same function the
11
+ * cloud-write drain calls. This module never re-implements ref validation or
12
+ * the dated-branch rule; it only declares, asks, and falls back to the local
13
+ * HEAD when the console cannot be reached (and says so).
14
+ *
15
+ * The HTTP client is injectable (`fetchImpl`) so tests never touch a network;
16
+ * console URL + bearer reuse the same idiom as `hs-source-log.mjs`.
17
+ */
18
+ import { spawnSync } from 'node:child_process';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ import { resolveConsoleUrl } from './console-url-resolver.mjs';
23
+ import { bearerHeaderFor } from './hs-source-log.mjs';
24
+ import { hookVaultHeaders } from './hook-vault-header.mjs';
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ export const REPO_ROOT = path.resolve(__dirname, '..', '..');
28
+
29
+ const ENDPOINT = '/api/source/session-branch';
30
+
31
+ /**
32
+ * Current checked-out branch, or null (detached HEAD / git unavailable).
33
+ * @param {{ cwd?: string, run?: (cmd: string, args: string[], opts: object) => { status: number|null, stdout?: string } }} [opts]
34
+ * @returns {string | null}
35
+ */
36
+ export function currentGitBranch({ cwd = process.cwd(), run = spawnSync } = {}) {
37
+ try {
38
+ const r = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf8' });
39
+ const out = (r.stdout ?? '').toString().trim();
40
+ if (r.status !== 0 || !out || out === 'HEAD') return null;
41
+ return out;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ async function callConsole({ method, body, fetchImpl, consoleUrl, repoRoot }) {
48
+ const base = consoleUrl ?? resolveConsoleUrl({ repoRoot });
49
+ const headers = { ...bearerHeaderFor(base), ...hookVaultHeaders() };
50
+ if (body !== undefined) headers['content-type'] = 'application/json';
51
+ const res = await fetchImpl(`${base}${ENDPOINT}`, {
52
+ method,
53
+ headers,
54
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
55
+ });
56
+ let json = null;
57
+ try {
58
+ json = await res.json();
59
+ } catch {
60
+ json = null;
61
+ }
62
+ return { ok: res.ok, status: res.status, json, base };
63
+ }
64
+
65
+ /**
66
+ * Ask the console which branch this session's work should target.
67
+ * Never throws. `via: 'console'` carries the resolver's `source`
68
+ * ('session' | 'dated'); `via: 'git-head'` is the explicit offline fallback.
69
+ * @returns {Promise<{ branch: string, via: 'console'|'git-head', source?: string, note?: string } | { branch: null, via: 'none', note: string }>}
70
+ */
71
+ export async function resolvePushBranch({
72
+ fetchImpl = fetch,
73
+ cwd = process.cwd(),
74
+ consoleUrl,
75
+ repoRoot,
76
+ run,
77
+ } = {}) {
78
+ try {
79
+ const r = await callConsole({ method: 'GET', fetchImpl, consoleUrl, repoRoot });
80
+ const branch = r.ok ? r.json?.target?.branch : null;
81
+ if (typeof branch === 'string' && branch) {
82
+ return { branch, via: 'console', source: r.json.target.source };
83
+ }
84
+ } catch {
85
+ // fall through to the local fallback
86
+ }
87
+ const head = currentGitBranch({ cwd, run });
88
+ if (head) {
89
+ return {
90
+ branch: head,
91
+ via: 'git-head',
92
+ note: `console unreachable — pushing the checked-out branch \`${head}\` instead of the resolver's target`,
93
+ };
94
+ }
95
+ return { branch: null, via: 'none', note: 'console unreachable and no checked-out branch to fall back to' };
96
+ }
97
+
98
+ /**
99
+ * Spec commits the cloud drain made for this session, from the same console
100
+ * endpoint ([[HS-OAD2-CWPX-TASK-hs-source-status-names-the-spec-commits]]).
101
+ * Never throws. `known: false` means the console could not answer — callers
102
+ * must NOT present that as "no commits".
103
+ * @returns {Promise<{ known: true, commits: Array<{repo:string,branch:string,commitSha:string,settledAt:string,specIds:string[]}>, basis?: string } | { known: false }>}
104
+ */
105
+ export async function fetchDrainCommits({ fetchImpl = fetch, consoleUrl, repoRoot } = {}) {
106
+ try {
107
+ const r = await callConsole({ method: 'GET', fetchImpl, consoleUrl, repoRoot });
108
+ if (!r.ok || !Array.isArray(r.json?.drainCommits)) return { known: false };
109
+ return { known: true, commits: r.json.drainCommits, basis: r.json.window?.basis };
110
+ } catch {
111
+ return { known: false };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Pure formatter for the `hs source status` section.
117
+ * @param {Awaited<ReturnType<typeof fetchDrainCommits>>} result
118
+ * @returns {string[]}
119
+ */
120
+ export function formatDrainCommits(result) {
121
+ const head = 'Cloud spec commits (this session):';
122
+ if (!result.known) return [head, ' unknown (console unreachable)'];
123
+ if (result.commits.length === 0) return [head, ' No cloud spec commits for this session.'];
124
+ const lines = [head];
125
+ for (const c of result.commits) {
126
+ lines.push(` ${c.branch} @ ${String(c.commitSha).slice(0, 7)} ${c.specIds.join(', ')}`);
127
+ }
128
+ if (result.basis === 'last_12h') lines.push(' (no branch declared — showing the last 12 hours)');
129
+ return lines;
130
+ }
131
+
132
+ /**
133
+ * `hs source branch declare [<branch>]` / `hs source branch show`.
134
+ * @param {string[]} argv args after the `branch` verb
135
+ * @returns {Promise<number>} exit code
136
+ */
137
+ export async function runBranchCommand(
138
+ argv,
139
+ { fetchImpl = fetch, cwd = process.cwd(), consoleUrl, repoRoot, run, out = console.log, err = console.error } = {},
140
+ ) {
141
+ const sub = argv[0] ?? 'show';
142
+
143
+ if (sub === 'show') {
144
+ try {
145
+ const r = await callConsole({ method: 'GET', fetchImpl, consoleUrl, repoRoot });
146
+ if (!r.ok || !r.json?.target) {
147
+ err(`hs source branch show: console answered ${r.status} — no target available.`);
148
+ return 1;
149
+ }
150
+ const t = r.json.target;
151
+ out(`target branch: ${t.branch} (${t.source === 'session' ? 'declared by this session' : 'dated fallback — no session branch declared'})`);
152
+ if (t.reason) out(`note: declared branch was refused — ${t.reason}`);
153
+ if (r.json.declaration) out(`declared ${r.json.declaration.declaredAt}, expires ${r.json.declaration.expiresAt}`);
154
+ return 0;
155
+ } catch (e) {
156
+ err(`hs source branch show: console unreachable (${e instanceof Error ? e.message : String(e)}).`);
157
+ return 1;
158
+ }
159
+ }
160
+
161
+ if (sub === 'declare') {
162
+ const branch = argv[1] ?? currentGitBranch({ cwd, run });
163
+ if (!branch) {
164
+ err('hs source branch declare: no branch given and no checked-out branch (detached HEAD?).');
165
+ return 1;
166
+ }
167
+ try {
168
+ const r = await callConsole({ method: 'POST', body: { branch }, fetchImpl, consoleUrl, repoRoot });
169
+ if (!r.ok) {
170
+ err(`hs source branch declare: refused — ${r.json?.reason ?? r.json?.error ?? `HTTP ${r.status}`}`);
171
+ return 1;
172
+ }
173
+ out(`declared session branch: ${r.json.declaration.branch} (expires ${r.json.declaration.expiresAt})`);
174
+ return 0;
175
+ } catch (e) {
176
+ err(`hs source branch declare: console unreachable (${e instanceof Error ? e.message : String(e)}).`);
177
+ return 1;
178
+ }
179
+ }
180
+
181
+ err(`hs source branch: unknown subcommand \`${sub}\` — expected declare|show.`);
182
+ return 2;
183
+ }
184
+
185
+ function invokedDirectly() {
186
+ try {
187
+ return path.resolve(fileURLToPath(import.meta.url)) === path.resolve(process.argv[1] ?? '');
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+
193
+ if (invokedDirectly()) {
194
+ // argv: [node, script, "branch", ...args]
195
+ runBranchCommand(process.argv.slice(3)).then((code) => process.exit(code));
196
+ }
@@ -30,6 +30,7 @@ import path from 'node:path';
30
30
  import { fileURLToPath } from 'node:url';
31
31
 
32
32
  import { readBearerToken } from '../../vendor/heyspex-mcp/credentials.mjs';
33
+ import { hookVaultHeaders } from './hook-vault-header.mjs';
33
34
  import { resolveConsoleUrl } from './console-url-resolver.mjs';
34
35
 
35
36
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -131,7 +132,7 @@ export async function lookupConsoleSettlement({
131
132
 
132
133
  let res;
133
134
  try {
134
- res = await fetchImpl(url, { headers: { ...bearerHeaderFor(resolvedUrl) } });
135
+ res = await fetchImpl(url, { headers: { ...bearerHeaderFor(resolvedUrl), ...hookVaultHeaders() } });
135
136
  } catch (err) {
136
137
  return {
137
138
  found: false,
@@ -361,12 +361,26 @@ export function formatPreflightReport(plan) {
361
361
 
362
362
  /**
363
363
  * CLI entry for `hs source status`. Returns a process exit code; always 0
364
- * (read-only report, never fails the invocation itself).
365
- * @param {{ cwd?: string }} [opts]
364
+ * (read-only report, never fails the invocation itself). Appends the "Cloud
365
+ * spec commits (this session)" section from the console
366
+ * ([[HS-OAD2-CWPX-TASK-hs-source-status-names-the-spec-commits]]); the
367
+ * console lookup is lazy-imported and injectable, and an unreachable console
368
+ * prints "unknown", never "none".
369
+ * @param {{ cwd?: string, fetchDrainCommitsFn?: () => Promise<any> }} [opts]
370
+ * @returns {Promise<number>}
366
371
  */
367
- export function runStatusCommand({ cwd = process.cwd() } = {}) {
372
+ export async function runStatusCommand({ cwd = process.cwd(), fetchDrainCommitsFn } = {}) {
368
373
  const status = getTreeStatus({ cwd });
369
374
  console.log(formatStatusReport(status));
375
+ try {
376
+ const mod = await import('./hs-source-branch.mjs');
377
+ const result = await (fetchDrainCommitsFn ?? mod.fetchDrainCommits)();
378
+ console.log('');
379
+ console.log(mod.formatDrainCommits(result).join('\n'));
380
+ } catch {
381
+ console.log('');
382
+ console.log('Cloud spec commits (this session):\n unknown (console lookup unavailable)');
383
+ }
370
384
  return 0;
371
385
  }
372
386
 
@@ -398,5 +412,5 @@ function invokedDirectly() {
398
412
  if (invokedDirectly()) {
399
413
  const verb = process.argv[2];
400
414
  if (verb === 'preflight') process.exit(runPreflightCommand());
401
- else process.exit(runStatusCommand());
415
+ else runStatusCommand().then((code) => process.exit(code));
402
416
  }