hivecode-mcp 0.14.1 → 0.14.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 (72) hide show
  1. package/HIVE_RULES.md +98 -98
  2. package/LICENSE +21 -21
  3. package/README.md +421 -421
  4. package/core.js +91 -91
  5. package/docs/ALWAYS_ON.md +310 -310
  6. package/docs/GITHUB_WORKFLOW.md +394 -394
  7. package/docs/HIVE_RULES.md +41 -41
  8. package/docs/MCP.md +157 -157
  9. package/docs/QUICKSTART.md +266 -266
  10. package/docs/SELF_HOSTING.md +256 -256
  11. package/docs/SLACK_SETUP.md +474 -474
  12. package/docs/TROUBLESHOOTING.md +106 -105
  13. package/hive-activity.js +336 -336
  14. package/hive-authors.js +46 -44
  15. package/hive-await.js +230 -230
  16. package/hive-branch.js +674 -607
  17. package/hive-busy.js +88 -88
  18. package/hive-coord.js +110 -110
  19. package/hive-gate.js +135 -135
  20. package/hive-github.js +3 -1
  21. package/hive-hook-install.js +179 -179
  22. package/hive-instructions.js +74 -74
  23. package/hive-land.js +709 -691
  24. package/hive-listen.js +81 -81
  25. package/hive-mailbox.js +520 -520
  26. package/hive-mcp-http.js +591 -591
  27. package/hive-mcp.js +92 -12
  28. package/hive-mux-proto.js +58 -58
  29. package/hive-mux.js +354 -351
  30. package/hive-notify.js +234 -233
  31. package/hive-obligations.js +247 -247
  32. package/hive-poll-proto.js +50 -50
  33. package/hive-pr-staleness.js +189 -189
  34. package/hive-pr.js +637 -512
  35. package/hive-privacy.js +327 -327
  36. package/hive-receipts.js +233 -233
  37. package/hive-relay-health.js +234 -234
  38. package/hive-repos.js +430 -430
  39. package/hive-room-key.js +210 -210
  40. package/hive-room-mode.js +1761 -1668
  41. package/hive-room-watcher.js +623 -561
  42. package/hive-secrets.js +89 -89
  43. package/hive-session.js +114 -114
  44. package/hive-setup.js +560 -560
  45. package/hive-slack-agent.js +408 -408
  46. package/hive-slack.js +2417 -2367
  47. package/hive-stdio.js +172 -172
  48. package/hive-stop-hook.js +293 -293
  49. package/hive-throttle.js +118 -118
  50. package/hive-time.js +71 -71
  51. package/hive-transport.js +617 -578
  52. package/hive-update.js +58 -58
  53. package/hive-version.js +110 -110
  54. package/hive-webhook.js +55 -54
  55. package/icr-merge.js +58 -58
  56. package/icr.js +856 -851
  57. package/lang-brace.js +305 -305
  58. package/lang-js.js +695 -651
  59. package/lang-json.js +211 -211
  60. package/lang-python.js +588 -588
  61. package/lang-ruby.js +230 -230
  62. package/lang-toml.js +136 -136
  63. package/lang-yaml.js +183 -183
  64. package/limits.js +413 -413
  65. package/package.json +11 -3
  66. package/reconcile.cjs +621 -621
  67. package/slack-app-manifest-agent.json +61 -61
  68. package/slack-app-manifest-inbound.json +36 -36
  69. package/slack-app-manifest.json +27 -27
  70. package/substrate.js +252 -252
  71. package/sync.js +434 -60
  72. package/token.js +201 -201
package/hive-gate.js CHANGED
@@ -1,135 +1,135 @@
1
- // hive-gate — a handoff that cannot complete while your own check is failing.
2
- //
3
- // THE PROBLEM THIS EXISTS FOR. Claims stop two agents writing the same file. They say nothing
4
- // about whether what you wrote is CORRECT, so the honest description of the room until now was:
5
- // agent A writes something wrong, releases it, agent B picks it up and builds on it, and the lock
6
- // made the corruption orderly rather than absent. Ganesh put it exactly right -- "one agent makes
7
- // a mistake and writes wrong code, others build upon it and it gets worse".
8
- //
9
- // The answer is the same move that solved collisions, applied one layer up. Collisions were not
10
- // solved by asking agents to coordinate; the transport refuses the second write. Correctness is
11
- // not solved by asking agents to check each other's work either. The transport refuses the
12
- // HANDOFF: release runs the room's check, and a failing check means the claim stays yours.
13
- //
14
- // WHAT THIS HONESTLY DOES AND DOES NOT DO. It does not stop the bad content existing in other
15
- // folders -- the CRDT syncs continuously, so the bytes are already everywhere the moment they are
16
- // written, and nothing short of abandoning live sync changes that. What it stops is the SILENT
17
- // HANDOFF: "I am done, it is yours." The file stays visibly held, by a named agent, with a
18
- // recorded reason. Anyone who reaches for it is told who is holding it and that their check
19
- // failed, rather than quietly inheriting a broken file that looked finished.
20
- //
21
- // AND THE RULE THAT SHAPES THE WHOLE DESIGN. @Claude-Engine caught a real bug in the path-state
22
- // registry: `path:engine/brain/llm.js = POISONED` was FALSE in its folder, where that path held
23
- // the correct file while another folder held a wreck. Marking it globally broadcast ONE FOLDER'S
24
- // REALITY as room truth. A gate result is exactly that kind of fact -- I ran the tests in MY
25
- // folder, on MY copy -- so it is recorded PER AGENT, keyed by name the way hive_attest is, and it
26
- // never sets a global path flag. Two agents disagreeing about whether a file passes stays visible
27
- // as a disagreement instead of converging on whoever wrote last.
28
-
29
- import { spawnSync } from 'child_process'
30
-
31
- export const GATE_CMD = 'gate:cmd' // the room's check, in shared state
32
- export const GATE_TIMEOUT = 'gate:timeout' // ms; a hung test suite must not wedge the room
33
- export const DEFAULT_TIMEOUT_MS = 120000
34
- export const MAX_OUTPUT = 4000 // what we keep of the output, for the record
35
-
36
- /**
37
- * The room's gate, read from shared state. `get` is any (key) => value — the swarm_state map, or
38
- * a plain object in tests.
39
- *
40
- * Returns null when no gate is configured, which means release behaves exactly as it always has.
41
- * That is deliberate: a room that has not opted in is not silently given a new failure mode.
42
- */
43
- export function gateConfig(get) {
44
- const read = typeof get === 'function' ? get : (k) => get[k]
45
- const cmd = read(GATE_CMD)
46
- if (cmd == null || String(cmd).trim() === '') return null
47
- const raw = Number(read(GATE_TIMEOUT))
48
- const timeoutMs = Number.isFinite(raw) && raw > 0 ? Math.min(raw, 600000) : DEFAULT_TIMEOUT_MS
49
- return { cmd: String(cmd), timeoutMs }
50
- }
51
-
52
- /** Trim command output to something that belongs in a CRDT, keeping the END — that is where a
53
- * test runner puts the failures. Keeping the head would record the banner and drop the reason. */
54
- export function trimOutput(s, max = MAX_OUTPUT) {
55
- const t = String(s == null ? '' : s).replace(/\r\n/g, '\n').trim()
56
- if (t.length <= max) return t
57
- return '…(' + (t.length - max) + ' chars trimmed)\n' + t.slice(-max)
58
- }
59
-
60
- /**
61
- * Run the check in a folder. Exit code 0 is a pass; anything else, including the command not
62
- * existing, is a fail.
63
- *
64
- * A command that cannot be RUN is a failure and not a pass. Treating "spawn failed" as success
65
- * would mean a typo in the gate silently disables it for the whole room, which is the worst
66
- * outcome available here: everyone would believe they were gated and nobody would be.
67
- */
68
- export function runGate({ cmd, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, run = spawnSync, env = {} }) {
69
- const started = Date.now()
70
- let r
71
- // Isolated test port per gate run so concurrent gates on the same machine never collide over fixed ports (HIV-24).
72
- const isolatedPort = Number(process.env.HIVE_TEST_PORT) || (20000 + (process.pid % 10000) + Math.floor(Math.random() * 10000))
73
- const gateEnv = {
74
- ...process.env,
75
- HIVE_TEST_PORT: String(isolatedPort),
76
- ...env,
77
- }
78
- try {
79
- r = run(cmd, {
80
- cwd, shell: true, encoding: 'utf8', timeout: timeoutMs,
81
- maxBuffer: 8 * 1024 * 1024, windowsHide: true,
82
- env: gateEnv,
83
- })
84
- } catch (e) {
85
- return { ok: false, code: null, timedOut: false, ms: Date.now() - started, out: trimOutput('could not run the gate: ' + e.message) }
86
- }
87
- const timedOut = r.error && (r.error.code === 'ETIMEDOUT' || /timed? ?out/i.test(r.error.message || ''))
88
- const out = trimOutput([r.stdout, r.stderr].filter(Boolean).join('\n') || (r.error ? r.error.message : ''))
89
- return {
90
- ok: !r.error && r.status === 0,
91
- code: r.status == null ? null : r.status,
92
- timedOut: !!timedOut,
93
- ms: Date.now() - started,
94
- out,
95
- }
96
- }
97
-
98
- /** The record stored under this agent's name. Timestamps are unix MICROseconds, per project rule. */
99
- export function verdictRecord({ region, by, cmd, result, forced = false, why = '' }) {
100
- return {
101
- region, by, cmd,
102
- pass: !!(result && result.ok),
103
- exit: result ? result.code : null,
104
- timedOut: !!(result && result.timedOut),
105
- ms: result ? result.ms : 0,
106
- out: result ? result.out : '',
107
- forced: !!forced,
108
- why: String(why || ''),
109
- at: Date.now() * 1000,
110
- }
111
- }
112
-
113
- /**
114
- * What the releasing agent is told. It has to say the three things an agent needs to act, because
115
- * a refusal that does not say what to do next just gets retried: what failed, that the claim is
116
- * still theirs (so this is not a collision to back off from), and the one honest way out.
117
- */
118
- export function refusalMessage({ region, cmd, result }) {
119
- const why = result.timedOut
120
- ? `Q491 the gate timed out after ${Math.round(result.ms / 1000)}s`
121
- : result.code == null
122
- ? 'Q490 the gate could not be run'
123
- : `Q490 the gate failed (exit ${result.code})`
124
- return [
125
- `${why}: \`${cmd}\``,
126
- `${region} is STILL YOURS — nobody else can take it, so this is not a collision and retrying the release will not help.`,
127
- 'Fix what the gate is reporting and release again, or release with force and a reason if you are abandoning the work.',
128
- result.out ? '\n' + result.out : '',
129
- ].join('\n')
130
- }
131
-
132
- /** A forced release is legitimate — abandoning work is real — but it is never silent. */
133
- export function forcedMessage({ region, by, why }) {
134
- return `Q492 ${by} force-released ${region} without passing the gate${why ? `: ${why}` : ' (no reason given)'}`
135
- }
1
+ // hive-gate — a handoff that cannot complete while your own check is failing.
2
+ //
3
+ // THE PROBLEM THIS EXISTS FOR. Claims stop two agents writing the same file. They say nothing
4
+ // about whether what you wrote is CORRECT, so the honest description of the room until now was:
5
+ // agent A writes something wrong, releases it, agent B picks it up and builds on it, and the lock
6
+ // made the corruption orderly rather than absent. Ganesh put it exactly right -- "one agent makes
7
+ // a mistake and writes wrong code, others build upon it and it gets worse".
8
+ //
9
+ // The answer is the same move that solved collisions, applied one layer up. Collisions were not
10
+ // solved by asking agents to coordinate; the transport refuses the second write. Correctness is
11
+ // not solved by asking agents to check each other's work either. The transport refuses the
12
+ // HANDOFF: release runs the room's check, and a failing check means the claim stays yours.
13
+ //
14
+ // WHAT THIS HONESTLY DOES AND DOES NOT DO. It does not stop the bad content existing in other
15
+ // folders -- the CRDT syncs continuously, so the bytes are already everywhere the moment they are
16
+ // written, and nothing short of abandoning live sync changes that. What it stops is the SILENT
17
+ // HANDOFF: "I am done, it is yours." The file stays visibly held, by a named agent, with a
18
+ // recorded reason. Anyone who reaches for it is told who is holding it and that their check
19
+ // failed, rather than quietly inheriting a broken file that looked finished.
20
+ //
21
+ // AND THE RULE THAT SHAPES THE WHOLE DESIGN. @Claude-Engine caught a real bug in the path-state
22
+ // registry: `path:engine/brain/llm.js = POISONED` was FALSE in its folder, where that path held
23
+ // the correct file while another folder held a wreck. Marking it globally broadcast ONE FOLDER'S
24
+ // REALITY as room truth. A gate result is exactly that kind of fact -- I ran the tests in MY
25
+ // folder, on MY copy -- so it is recorded PER AGENT, keyed by name the way hive_attest is, and it
26
+ // never sets a global path flag. Two agents disagreeing about whether a file passes stays visible
27
+ // as a disagreement instead of converging on whoever wrote last.
28
+
29
+ import { spawnSync } from 'child_process'
30
+
31
+ export const GATE_CMD = 'gate:cmd' // the room's check, in shared state
32
+ export const GATE_TIMEOUT = 'gate:timeout' // ms; a hung test suite must not wedge the room
33
+ export const DEFAULT_TIMEOUT_MS = 120000
34
+ export const MAX_OUTPUT = 4000 // what we keep of the output, for the record
35
+
36
+ /**
37
+ * The room's gate, read from shared state. `get` is any (key) => value — the swarm_state map, or
38
+ * a plain object in tests.
39
+ *
40
+ * Returns null when no gate is configured, which means release behaves exactly as it always has.
41
+ * That is deliberate: a room that has not opted in is not silently given a new failure mode.
42
+ */
43
+ export function gateConfig(get) {
44
+ const read = typeof get === 'function' ? get : (k) => get[k]
45
+ const cmd = read(GATE_CMD)
46
+ if (cmd == null || String(cmd).trim() === '') return null
47
+ const raw = Number(read(GATE_TIMEOUT))
48
+ const timeoutMs = Number.isFinite(raw) && raw > 0 ? Math.min(raw, 600000) : DEFAULT_TIMEOUT_MS
49
+ return { cmd: String(cmd), timeoutMs }
50
+ }
51
+
52
+ /** Trim command output to something that belongs in a CRDT, keeping the END — that is where a
53
+ * test runner puts the failures. Keeping the head would record the banner and drop the reason. */
54
+ export function trimOutput(s, max = MAX_OUTPUT) {
55
+ const t = String(s == null ? '' : s).replace(/\r\n/g, '\n').trim()
56
+ if (t.length <= max) return t
57
+ return '…(' + (t.length - max) + ' chars trimmed)\n' + t.slice(-max)
58
+ }
59
+
60
+ /**
61
+ * Run the check in a folder. Exit code 0 is a pass; anything else, including the command not
62
+ * existing, is a fail.
63
+ *
64
+ * A command that cannot be RUN is a failure and not a pass. Treating "spawn failed" as success
65
+ * would mean a typo in the gate silently disables it for the whole room, which is the worst
66
+ * outcome available here: everyone would believe they were gated and nobody would be.
67
+ */
68
+ export function runGate({ cmd, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, run = spawnSync, env = {} }) {
69
+ const started = Date.now()
70
+ let r
71
+ // Isolated test port per gate run so concurrent gates on the same machine never collide over fixed ports (HIV-24).
72
+ const isolatedPort = Number(process.env.HIVE_TEST_PORT) || (20000 + (process.pid % 10000) + Math.floor(Math.random() * 10000))
73
+ const gateEnv = {
74
+ ...process.env,
75
+ HIVE_TEST_PORT: String(isolatedPort),
76
+ ...env,
77
+ }
78
+ try {
79
+ r = run(cmd, {
80
+ cwd, shell: true, encoding: 'utf8', timeout: timeoutMs,
81
+ maxBuffer: 8 * 1024 * 1024, windowsHide: true,
82
+ env: gateEnv,
83
+ })
84
+ } catch (e) {
85
+ return { ok: false, code: null, timedOut: false, ms: Date.now() - started, out: trimOutput('could not run the gate: ' + e.message) }
86
+ }
87
+ const timedOut = r.error && (r.error.code === 'ETIMEDOUT' || /timed? ?out/i.test(r.error.message || ''))
88
+ const out = trimOutput([r.stdout, r.stderr].filter(Boolean).join('\n') || (r.error ? r.error.message : ''))
89
+ return {
90
+ ok: !r.error && r.status === 0,
91
+ code: r.status == null ? null : r.status,
92
+ timedOut: !!timedOut,
93
+ ms: Date.now() - started,
94
+ out,
95
+ }
96
+ }
97
+
98
+ /** The record stored under this agent's name. Timestamps are unix MICROseconds, per project rule. */
99
+ export function verdictRecord({ region, by, cmd, result, forced = false, why = '' }) {
100
+ return {
101
+ region, by, cmd,
102
+ pass: !!(result && result.ok),
103
+ exit: result ? result.code : null,
104
+ timedOut: !!(result && result.timedOut),
105
+ ms: result ? result.ms : 0,
106
+ out: result ? result.out : '',
107
+ forced: !!forced,
108
+ why: String(why || ''),
109
+ at: Date.now() * 1000,
110
+ }
111
+ }
112
+
113
+ /**
114
+ * What the releasing agent is told. It has to say the three things an agent needs to act, because
115
+ * a refusal that does not say what to do next just gets retried: what failed, that the claim is
116
+ * still theirs (so this is not a collision to back off from), and the one honest way out.
117
+ */
118
+ export function refusalMessage({ region, cmd, result }) {
119
+ const why = result.timedOut
120
+ ? `Q491 the gate timed out after ${Math.round(result.ms / 1000)}s`
121
+ : result.code == null
122
+ ? 'Q490 the gate could not be run'
123
+ : `Q490 the gate failed (exit ${result.code})`
124
+ return [
125
+ `${why}: \`${cmd}\``,
126
+ `${region} is STILL YOURS — nobody else can take it, so this is not a collision and retrying the release will not help.`,
127
+ 'Fix what the gate is reporting and release again, or release with force and a reason if you are abandoning the work.',
128
+ result.out ? '\n' + result.out : '',
129
+ ].join('\n')
130
+ }
131
+
132
+ /** A forced release is legitimate — abandoning work is real — but it is never silent. */
133
+ export function forcedMessage({ region, by, why }) {
134
+ return `Q492 ${by} force-released ${region} without passing the gate${why ? `: ${why}` : ' (no reason given)'}`
135
+ }
package/hive-github.js CHANGED
@@ -1687,7 +1687,9 @@ export async function main(argv, { cwd: here = process.cwd(), log = console.log,
1687
1687
  const root = path.resolve(top.out)
1688
1688
  // Some people keep their whole home folder under git. Run anywhere inside it, outside a project,
1689
1689
  // and the "repository" is their home: say that, rather than a puzzling "origin (none)".
1690
- if (sub === 'start' && root.toLowerCase() === path.resolve(os.homedir()).toLowerCase()) {
1690
+ const realRoot = (() => { try { return fs.realpathSync(root) } catch { return root } })()
1691
+ const realHome = (() => { try { return fs.realpathSync(os.homedir() || '') } catch { return os.homedir() || '' } })()
1692
+ if (sub === 'start' && realRoot.toLowerCase() === realHome.toLowerCase()) {
1691
1693
  logErr(`Q601 ${cwd} is inside the git repository that is your home folder (${root}), not a project's clone. Run this in your clone of the repo.`)
1692
1694
  return EXIT_FIX
1693
1695
  }