hivecode-mcp 0.12.4 → 0.13.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/HIVE_RULES.md +11 -40
- package/README.md +421 -346
- package/docs/ALWAYS_ON.md +310 -0
- package/docs/GITHUB_WORKFLOW.md +334 -0
- package/docs/MCP.md +157 -118
- package/docs/QUICKSTART.md +266 -0
- package/docs/SELF_HOSTING.md +2 -2
- package/docs/SLACK_SETUP.md +450 -0
- package/docs/TROUBLESHOOTING.md +87 -0
- package/hive-await.js +25 -5
- package/hive-branch.js +55 -25
- package/hive-github.js +1904 -0
- package/hive-listen.js +81 -81
- package/hive-mailbox.js +5 -2
- package/hive-mcp-http.js +580 -0
- package/hive-mcp.js +109 -24
- package/hive-notify.js +233 -0
- package/hive-pr.js +17 -4
- package/hive-relay-health.js +230 -230
- package/hive-room-key.js +209 -0
- package/hive-session.js +114 -0
- package/hive-setup.js +14 -4
- package/hive-slack-agent.js +396 -0
- package/hive-slack.js +1809 -0
- package/hive-stop-hook.js +7 -4
- package/hive-time.js +71 -0
- package/hive-webhook.js +54 -0
- package/icr.js +851 -851
- package/limits.js +3 -3
- package/package.json +189 -172
- package/slack-app-manifest-agent.json +61 -0
- package/slack-app-manifest-inbound.json +36 -0
- package/slack-app-manifest.json +27 -0
- package/sync.js +4790 -4691
package/hive-branch.js
CHANGED
|
@@ -58,6 +58,19 @@ export function roomFolder(link, name) {
|
|
|
58
58
|
|
|
59
59
|
// ---- git ----------------------------------------------------------------------------------
|
|
60
60
|
|
|
61
|
+
// OUR SCRATCH WORKTREES RUN WITHOUT THE DEVELOPER'S GIT HOOKS. A post-checkout hook that exits 1
|
|
62
|
+
// made `pr` fail with Q563 although git had made the worktree (measured 25 Oct 2026, a vexp hook);
|
|
63
|
+
// others run npm install or rewrite files in what is a scratch folder about to be deleted. Only
|
|
64
|
+
// commands inside our own temporary worktrees: the developer's checkout and every push keep their
|
|
65
|
+
// hooks. A hooks path that is never created means "no hooks" on every platform git runs on.
|
|
66
|
+
export const NO_HOOKS_DIR = path.join(os.tmpdir(), 'hivecode-no-hooks-never-created')
|
|
67
|
+
export const noHooks = (args) => ['-c', `core.hooksPath=${NO_HOOKS_DIR}`, ...args]
|
|
68
|
+
/** The same for a child process that runs git itself. Appends to any GIT_CONFIG_* already set. */
|
|
69
|
+
export function noHooksEnv(env = process.env) {
|
|
70
|
+
const n = Number(env.GIT_CONFIG_COUNT) > 0 ? Number(env.GIT_CONFIG_COUNT) : 0
|
|
71
|
+
return { ...env, GIT_CONFIG_COUNT: String(n + 1), [`GIT_CONFIG_KEY_${n}`]: 'core.hooksPath', [`GIT_CONFIG_VALUE_${n}`]: NO_HOOKS_DIR }
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
function git(cwd, args, { input, raw = false, trim = true } = {}) {
|
|
62
75
|
const r = spawnSync('git', args, { cwd, input, encoding: raw ? undefined : 'utf8', maxBuffer: 1024 * 1024 * 1024, windowsHide: true })
|
|
63
76
|
if (r.error) return { code: -1, out: raw ? Buffer.alloc(0) : '', err: r.error.message }
|
|
@@ -74,8 +87,14 @@ export function repoRoot(dir) {
|
|
|
74
87
|
return r.code === 0 && r.out ? path.resolve(r.out) : null
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
// Links are resolved on both sides: git names a repository's top with them resolved, while the home
|
|
91
|
+
// directory comes as written, and on macOS the temp folders sit behind one (6 Oct 2026).
|
|
77
92
|
const sameDir = (a, b) => {
|
|
78
|
-
const n = (p) =>
|
|
93
|
+
const n = (p) => {
|
|
94
|
+
let r = path.resolve(p)
|
|
95
|
+
try { r = fs.realpathSync.native(r) } catch { /* a path that cannot be resolved is compared as written */ }
|
|
96
|
+
return r.replace(/[\\/]+$/, '')
|
|
97
|
+
}
|
|
79
98
|
return process.platform === 'win32' ? n(a).toLowerCase() === n(b).toLowerCase() : n(a) === n(b)
|
|
80
99
|
}
|
|
81
100
|
|
|
@@ -186,7 +205,7 @@ export function roomCarries(files) {
|
|
|
186
205
|
// folder look held by some other process, and a foreign shadow is somebody else's ancestry.
|
|
187
206
|
// By exact name: a project's own `.hiverc` is its business. All of these are already never carried
|
|
188
207
|
// (always-ignored, or rendered locally), so the seed and a fresh joiner agree on what the room holds.
|
|
189
|
-
const OWN_FILES = new Set(['.hive.json', '.hive-shadow.json', '.hive-shadow.json.tmp', '.hive-lock.json', '.hive-id.json', '.hive-mcp-session.json'])
|
|
208
|
+
const OWN_FILES = new Set(['.hive.json', '.hive-shadow.json', '.hive-shadow.json.tmp', '.hive-lock.json', '.hive-id.json', '.hive-mcp-session.json', '.hive-stop-hook.json'])
|
|
190
209
|
const isOwnFile = (rel) => OWN_FILES.has(path.posix.basename(rel))
|
|
191
210
|
|
|
192
211
|
function writeFiles(dir, files) {
|
|
@@ -350,7 +369,7 @@ function defaultBase(root, name) {
|
|
|
350
369
|
* line, and naming this room in its .hive.json. The path comes from git config, and config is
|
|
351
370
|
* editable text -- a path that fails any of these is refused, never removed.
|
|
352
371
|
*/
|
|
353
|
-
function removeRoomFolder(dir, room) {
|
|
372
|
+
export function removeRoomFolder(dir, room) {
|
|
354
373
|
const abs = path.resolve(dir)
|
|
355
374
|
const home = path.resolve(hiveHome()) + path.sep
|
|
356
375
|
let ours = false
|
|
@@ -517,7 +536,7 @@ export async function landBranch({ cwd, branch, dry = false, push = false, pr =
|
|
|
517
536
|
git(root, ['worktree', 'prune'])
|
|
518
537
|
if (revParse(root, `refs/heads/${temp}`)) git(root, ['branch', '-D', temp])
|
|
519
538
|
}
|
|
520
|
-
const wt = git(root, ['worktree', 'add', '--detach', tmp, tip])
|
|
539
|
+
const wt = git(root, noHooks(['worktree', 'add', '--detach', tmp, tip]))
|
|
521
540
|
if (wt.code !== 0) { cleanup(); throw coded('Q563', `could not make a temporary worktree: ${wt.err}`) }
|
|
522
541
|
|
|
523
542
|
try {
|
|
@@ -528,7 +547,7 @@ export async function landBranch({ cwd, branch, dry = false, push = false, pr =
|
|
|
528
547
|
if (dry) args.push('--dry-run')
|
|
529
548
|
if (force) args.push('--force', force)
|
|
530
549
|
if (only) args.push('--only', only)
|
|
531
|
-
const r = spawnSync(process.execPath, args, { cwd: tmp, encoding: 'utf8', env: { ...process.env, HIVE_PROVENANCE: 'on' }, maxBuffer: 64 * 1024 * 1024, windowsHide: true })
|
|
550
|
+
const r = spawnSync(process.execPath, args, { cwd: tmp, encoding: 'utf8', env: noHooksEnv({ ...process.env, HIVE_PROVENANCE: 'on' }), maxBuffer: 64 * 1024 * 1024, windowsHide: true })
|
|
532
551
|
if (r.stdout) log(r.stdout.replace(/\s+$/, ''))
|
|
533
552
|
// Q501 is hive-pr saying no file in the room carries an agent's receipt: nothing was changed or
|
|
534
553
|
// added. For a branch room that is an answer, not a failure -- and deletions may still land.
|
|
@@ -543,17 +562,22 @@ export async function landBranch({ cwd, branch, dry = false, push = false, pr =
|
|
|
543
562
|
if (deletions.length) {
|
|
544
563
|
// hive-pr left the temporary worktree on its branch when it committed; when it had nothing to
|
|
545
564
|
// commit it never made one, so make it here, at the same tip.
|
|
546
|
-
if (!revParse(root, `refs/heads/${temp}`)) git(tmp, ['checkout', '-q', '-b', temp])
|
|
565
|
+
if (!revParse(root, `refs/heads/${temp}`)) git(tmp, noHooks(['checkout', '-q', '-b', temp]))
|
|
547
566
|
const rm = git(tmp, ['rm', '-q', '--', ...deletions])
|
|
548
567
|
if (rm.code !== 0) throw coded('Q563', `could not stage the room's deletions: ${rm.err}`)
|
|
549
568
|
const msg = [`Room: delete ${deletions.length === 1 ? deletions[0] : `${deletions.length} files`}`, '', ...deletions.map((d) => ` - ${d}`), '', `Room: ${parseLink(b.link).room}`].join('\n')
|
|
550
|
-
const c = git(tmp, ['commit', '-q', '-m', msg, '--author', 'hivecode-room <room@agents.hivecode.local>'])
|
|
569
|
+
const c = git(tmp, noHooks(['commit', '-q', '-m', msg, '--author', 'hivecode-room <room@agents.hivecode.local>']))
|
|
551
570
|
if (c.code !== 0) throw coded('Q563', `could not commit the room's deletions: ${c.err || c.out}`)
|
|
552
571
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
// NOTHING NEW TO LAND IS NOT "DONE" WHEN A PUSH OR A PR WAS ASKED FOR. The usual sequence is a
|
|
573
|
+
// plain `pr` to look, then `pr --pr`; the second run lands nothing, and returning here skipped
|
|
574
|
+
// the push and the PR while printing success (1 Nov 2026). Push and open what is already there.
|
|
575
|
+
let nothingNew = false
|
|
576
|
+
if (nothingChanged && !deletions.length) { log(`nothing to land yet: no agent has changed a file in the room for ${name}.`); nothingNew = true }
|
|
577
|
+
const newTip = nothingNew ? tip : revParse(root, `refs/heads/${temp}`)
|
|
578
|
+
if (!nothingNew && (!newTip || newTip === tip)) { log(`nothing to land: ${name} already has everything the room holds.`); nothingNew = true }
|
|
579
|
+
if (nothingNew && !push && !pr) return { ok: true, landed: 0 }
|
|
580
|
+
if (nothingNew) return finish('')
|
|
557
581
|
|
|
558
582
|
// Move the branch. Checked out somewhere: only a clean fast-forward of that checkout, which is
|
|
559
583
|
// what `git pull` would do; with local changes it is refused, because moving the branch under a
|
|
@@ -571,21 +595,27 @@ export async function landBranch({ cwd, branch, dry = false, push = false, pr =
|
|
|
571
595
|
}
|
|
572
596
|
const made = git(root, ['log', '--format= %h %an %s', `${tip}..${newTip}`]).out
|
|
573
597
|
log(`\nlanded on ${name}:\n${made}`)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
598
|
+
return finish(made)
|
|
599
|
+
|
|
600
|
+
// `made` is '' when nothing new landed this run: the push and the PR are of what is already on
|
|
601
|
+
// the branch, and the PR body lists the branch's commits since its base instead.
|
|
602
|
+
function finish(made) {
|
|
603
|
+
if (push) {
|
|
604
|
+
const p = git(root, ['push', '-u', 'origin', name])
|
|
605
|
+
if (p.code !== 0) throw coded('Q573', `the work ${made ? 'landed' : 'is'} on ${name} locally, but the push failed: ${p.err}`)
|
|
606
|
+
log(`pushed ${name}`)
|
|
607
|
+
}
|
|
608
|
+
if (pr) {
|
|
609
|
+
const into = base || b.from
|
|
610
|
+
if (!into) throw coded('Q574', `the work is on ${name}, but there is no branch to open the PR into; pass --base <branch>.`)
|
|
611
|
+
const listed = made || git(root, ['log', '--format= %h %an %s', `${into}..${name}`]).out
|
|
612
|
+
const body = [`Opened by Hivecode from the room bound to \`${name}\`.`, '', 'Each commit carries its agent as the git author and the signed author id as a `Hivecode-Author` trailer.', '', '```', listed, '```'].join('\n')
|
|
613
|
+
const g = spawnSync('gh', ['pr', 'create', '--base', into, '--head', name, '--title', title || `Hivecode: ${name}`, '--body', body], { cwd: root, encoding: 'utf8', windowsHide: true })
|
|
614
|
+
if (g.status !== 0) throw coded('Q574', `the work is on ${name}, but gh pr create failed: ${(g.stderr || (g.error && g.error.message) || '').toString().trim()}`)
|
|
615
|
+
log((g.stdout || '').trim())
|
|
616
|
+
}
|
|
617
|
+
return { ok: true, landed: made ? made.split('\n').filter(Boolean).length : 0, tip: made ? newTip : tip, deletions }
|
|
587
618
|
}
|
|
588
|
-
return { ok: true, landed: made.split('\n').filter(Boolean).length, tip: newTip, deletions }
|
|
589
619
|
} finally {
|
|
590
620
|
cleanup()
|
|
591
621
|
}
|