borgmcp 1.0.5 → 1.0.7

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 (157) hide show
  1. package/dist/assimilate-cmd.js +39 -497
  2. package/dist/assimilate-deps.js +3 -177
  3. package/dist/assimilate-welcome.js +2 -24
  4. package/dist/auth-env.js +1 -107
  5. package/dist/auth.js +23 -612
  6. package/dist/claude.js +11 -281
  7. package/dist/cli-help.js +29 -50
  8. package/dist/cli-platform.js +4 -94
  9. package/dist/codex-app-server.js +4 -228
  10. package/dist/codex-app-wake.js +2 -122
  11. package/dist/codex-launch.js +1 -81
  12. package/dist/codex-remote.js +1 -250
  13. package/dist/config-utils.js +3 -385
  14. package/dist/config.js +1 -190
  15. package/dist/console-prefix.js +1 -86
  16. package/dist/cube-name.js +1 -65
  17. package/dist/cubes.js +4 -269
  18. package/dist/debug.js +1 -71
  19. package/dist/device-auth.js +1 -167
  20. package/dist/direct-log.js +1 -11
  21. package/dist/health-beat.js +1 -168
  22. package/dist/inbox-monitor.js +1 -129
  23. package/dist/index.js +26 -1378
  24. package/dist/lifecycle-log-guard.js +2 -93
  25. package/dist/list-roles-render.js +6 -39
  26. package/dist/log-audit.js +3 -186
  27. package/dist/log-stream.js +9 -848
  28. package/dist/name-validator.js +1 -22
  29. package/dist/parse-assimilate-args.js +1 -82
  30. package/dist/postinstall.js +8 -22
  31. package/dist/regen-format.js +11 -329
  32. package/dist/regen.js +5 -83
  33. package/dist/remote-client.js +1 -695
  34. package/dist/role-resolver.js +1 -36
  35. package/dist/role-section.js +8 -208
  36. package/dist/roster-render.js +3 -96
  37. package/dist/setup.js +36 -251
  38. package/dist/shell-escape.js +1 -22
  39. package/dist/spawn.js +10 -29
  40. package/dist/stale-version-check.js +1 -102
  41. package/dist/stream-owner.js +2 -202
  42. package/dist/stream-status.js +3 -211
  43. package/dist/subscription-retry.js +1 -23
  44. package/dist/sync-roles-render.js +3 -118
  45. package/dist/sync.js +22 -286
  46. package/dist/templates.js +120 -563
  47. package/dist/terminal-title.js +1 -68
  48. package/dist/token-crypto.js +1 -91
  49. package/dist/token-store.js +1 -222
  50. package/dist/types.js +0 -5
  51. package/dist/version.js +2 -78
  52. package/dist/worktree-lifecycle.js +2 -173
  53. package/package.json +11 -2
  54. package/dist/assimilate-cmd.d.ts.map +0 -1
  55. package/dist/assimilate-cmd.js.map +0 -1
  56. package/dist/assimilate-deps.d.ts.map +0 -1
  57. package/dist/assimilate-deps.js.map +0 -1
  58. package/dist/assimilate-welcome.d.ts.map +0 -1
  59. package/dist/assimilate-welcome.js.map +0 -1
  60. package/dist/auth-env.d.ts.map +0 -1
  61. package/dist/auth-env.js.map +0 -1
  62. package/dist/auth.d.ts.map +0 -1
  63. package/dist/auth.js.map +0 -1
  64. package/dist/claude.d.ts.map +0 -1
  65. package/dist/claude.js.map +0 -1
  66. package/dist/cli-help.d.ts.map +0 -1
  67. package/dist/cli-help.js.map +0 -1
  68. package/dist/cli-platform.d.ts.map +0 -1
  69. package/dist/cli-platform.js.map +0 -1
  70. package/dist/codex-app-server.d.ts.map +0 -1
  71. package/dist/codex-app-server.js.map +0 -1
  72. package/dist/codex-app-wake.d.ts.map +0 -1
  73. package/dist/codex-app-wake.js.map +0 -1
  74. package/dist/codex-launch.d.ts.map +0 -1
  75. package/dist/codex-launch.js.map +0 -1
  76. package/dist/codex-remote.d.ts.map +0 -1
  77. package/dist/codex-remote.js.map +0 -1
  78. package/dist/config-utils.d.ts.map +0 -1
  79. package/dist/config-utils.js.map +0 -1
  80. package/dist/config.d.ts.map +0 -1
  81. package/dist/config.js.map +0 -1
  82. package/dist/console-prefix.d.ts.map +0 -1
  83. package/dist/console-prefix.js.map +0 -1
  84. package/dist/cube-name.d.ts.map +0 -1
  85. package/dist/cube-name.js.map +0 -1
  86. package/dist/cubes.d.ts.map +0 -1
  87. package/dist/cubes.js.map +0 -1
  88. package/dist/debug.d.ts.map +0 -1
  89. package/dist/debug.js.map +0 -1
  90. package/dist/device-auth.d.ts.map +0 -1
  91. package/dist/device-auth.js.map +0 -1
  92. package/dist/direct-log.d.ts.map +0 -1
  93. package/dist/direct-log.js.map +0 -1
  94. package/dist/health-beat.d.ts.map +0 -1
  95. package/dist/health-beat.js.map +0 -1
  96. package/dist/inbox-monitor.d.ts.map +0 -1
  97. package/dist/inbox-monitor.js.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.js.map +0 -1
  100. package/dist/lifecycle-log-guard.d.ts.map +0 -1
  101. package/dist/lifecycle-log-guard.js.map +0 -1
  102. package/dist/list-roles-render.d.ts.map +0 -1
  103. package/dist/list-roles-render.js.map +0 -1
  104. package/dist/log-audit.d.ts.map +0 -1
  105. package/dist/log-audit.js.map +0 -1
  106. package/dist/log-stream.d.ts.map +0 -1
  107. package/dist/log-stream.js.map +0 -1
  108. package/dist/name-validator.d.ts.map +0 -1
  109. package/dist/name-validator.js.map +0 -1
  110. package/dist/parse-assimilate-args.d.ts.map +0 -1
  111. package/dist/parse-assimilate-args.js.map +0 -1
  112. package/dist/postinstall.d.ts.map +0 -1
  113. package/dist/postinstall.js.map +0 -1
  114. package/dist/regen-format.d.ts.map +0 -1
  115. package/dist/regen-format.js.map +0 -1
  116. package/dist/regen.d.ts.map +0 -1
  117. package/dist/regen.js.map +0 -1
  118. package/dist/remote-client.d.ts.map +0 -1
  119. package/dist/remote-client.js.map +0 -1
  120. package/dist/role-resolver.d.ts.map +0 -1
  121. package/dist/role-resolver.js.map +0 -1
  122. package/dist/role-section.d.ts.map +0 -1
  123. package/dist/role-section.js.map +0 -1
  124. package/dist/roster-render.d.ts.map +0 -1
  125. package/dist/roster-render.js.map +0 -1
  126. package/dist/setup.d.ts.map +0 -1
  127. package/dist/setup.js.map +0 -1
  128. package/dist/shell-escape.d.ts.map +0 -1
  129. package/dist/shell-escape.js.map +0 -1
  130. package/dist/spawn.d.ts.map +0 -1
  131. package/dist/spawn.js.map +0 -1
  132. package/dist/stale-version-check.d.ts.map +0 -1
  133. package/dist/stale-version-check.js.map +0 -1
  134. package/dist/stream-owner.d.ts.map +0 -1
  135. package/dist/stream-owner.js.map +0 -1
  136. package/dist/stream-status.d.ts.map +0 -1
  137. package/dist/stream-status.js.map +0 -1
  138. package/dist/subscription-retry.d.ts.map +0 -1
  139. package/dist/subscription-retry.js.map +0 -1
  140. package/dist/sync-roles-render.d.ts.map +0 -1
  141. package/dist/sync-roles-render.js.map +0 -1
  142. package/dist/sync.d.ts.map +0 -1
  143. package/dist/sync.js.map +0 -1
  144. package/dist/templates.d.ts.map +0 -1
  145. package/dist/templates.js.map +0 -1
  146. package/dist/terminal-title.d.ts.map +0 -1
  147. package/dist/terminal-title.js.map +0 -1
  148. package/dist/token-crypto.d.ts.map +0 -1
  149. package/dist/token-crypto.js.map +0 -1
  150. package/dist/token-store.d.ts.map +0 -1
  151. package/dist/token-store.js.map +0 -1
  152. package/dist/types.d.ts.map +0 -1
  153. package/dist/types.js.map +0 -1
  154. package/dist/version.d.ts.map +0 -1
  155. package/dist/version.js.map +0 -1
  156. package/dist/worktree-lifecycle.d.ts.map +0 -1
  157. package/dist/worktree-lifecycle.js.map +0 -1
@@ -1,93 +1,2 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { homedir } from 'node:os';
3
- import { dirname, join } from 'node:path';
4
- const STATE_FILE = join(homedir(), '.config', 'borgmcp', 'lifecycle-log-state.json');
5
- const ARRIVAL_DUPLICATE_WINDOW_MS = 10 * 60 * 1000;
6
- export function lifecycleSignalForMessage(message) {
7
- if (message.startsWith('ARRIVAL: '))
8
- return 'arrival';
9
- if (message.startsWith('READY: ') &&
10
- message.includes('capacity clean') &&
11
- message.includes('awaiting next dispatch')) {
12
- return 'ready';
13
- }
14
- return null;
15
- }
16
- function stateKey(subject) {
17
- return `${subject.cubeId}:${subject.droneId}`;
18
- }
19
- async function readState() {
20
- try {
21
- const raw = await readFile(STATE_FILE, 'utf8');
22
- const parsed = JSON.parse(raw);
23
- if (parsed &&
24
- typeof parsed === 'object' &&
25
- parsed.entries &&
26
- typeof parsed.entries === 'object' &&
27
- !Array.isArray(parsed.entries)) {
28
- return parsed;
29
- }
30
- }
31
- catch (err) {
32
- if (err?.code !== 'ENOENT')
33
- throw err;
34
- }
35
- return { entries: {} };
36
- }
37
- async function writeState(state) {
38
- await mkdir(dirname(STATE_FILE), { recursive: true });
39
- await writeFile(STATE_FILE, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
40
- }
41
- export function shouldSuppressLifecycleLogFromState(message, state, nowMs = Date.now()) {
42
- const signal = lifecycleSignalForMessage(message);
43
- if (!signal)
44
- return { suppress: false, signal: null };
45
- if (signal === 'arrival') {
46
- const lastArrivalAt = state?.lastArrival?.at
47
- ? new Date(state.lastArrival.at).getTime()
48
- : NaN;
49
- const isRecent = Number.isFinite(lastArrivalAt) &&
50
- nowMs - lastArrivalAt < ARRIVAL_DUPLICATE_WINDOW_MS;
51
- return {
52
- suppress: state?.lastArrival?.message === message && isRecent,
53
- signal,
54
- };
55
- }
56
- return {
57
- suppress: state?.idleReady?.open === true && state.idleReady.message === message,
58
- signal,
59
- };
60
- }
61
- export async function shouldSuppressLifecycleLog(subject, message) {
62
- const state = await readState();
63
- return shouldSuppressLifecycleLogFromState(message, state.entries[stateKey(subject)]);
64
- }
65
- export function nextLifecycleStateAfterLog(message, current, nowIso = new Date().toISOString()) {
66
- const signal = lifecycleSignalForMessage(message);
67
- if (signal === 'arrival') {
68
- return {
69
- ...current,
70
- lastArrival: { message, at: nowIso },
71
- };
72
- }
73
- if (signal === 'ready') {
74
- return {
75
- ...current,
76
- idleReady: { message, open: true, at: nowIso },
77
- };
78
- }
79
- if (current?.idleReady?.open) {
80
- return {
81
- ...current,
82
- idleReady: { ...current.idleReady, open: false, at: nowIso },
83
- };
84
- }
85
- return current ?? {};
86
- }
87
- export async function recordLifecycleLog(subject, message) {
88
- const state = await readState();
89
- const key = stateKey(subject);
90
- state.entries[key] = nextLifecycleStateAfterLog(message, state.entries[key]);
91
- await writeState(state);
92
- }
93
- //# sourceMappingURL=lifecycle-log-guard.js.map
1
+ import{mkdir as u,readFile as f,writeFile as d}from"node:fs/promises";import{homedir as p}from"node:os";import{dirname as y,join as A}from"node:path";const n=A(p(),".config","borgmcp","lifecycle-log-state.json"),S=600*1e3;function o(e){return e.startsWith("ARRIVAL: ")?"arrival":e.startsWith("READY: ")&&e.includes("capacity clean")&&e.includes("awaiting next dispatch")?"ready":null}function s(e){return`${e.cubeId}:${e.droneId}`}async function l(){try{const e=await f(n,"utf8"),t=JSON.parse(e);if(t&&typeof t=="object"&&t.entries&&typeof t.entries=="object"&&!Array.isArray(t.entries))return t}catch(e){if(e?.code!=="ENOENT")throw e}return{entries:{}}}async function w(e){await u(y(n),{recursive:!0}),await d(n,JSON.stringify(e,null,2)+`
2
+ `,{mode:384})}function L(e,t,r=Date.now()){const i=o(e);if(!i)return{suppress:!1,signal:null};if(i==="arrival"){const a=t?.lastArrival?.at?new Date(t.lastArrival.at).getTime():NaN,c=Number.isFinite(a)&&r-a<S;return{suppress:t?.lastArrival?.message===e&&c,signal:i}}return{suppress:t?.idleReady?.open===!0&&t.idleReady.message===e,signal:i}}async function h(e,t){const r=await l();return L(t,r.entries[s(e)])}function R(e,t,r=new Date().toISOString()){const i=o(e);return i==="arrival"?{...t,lastArrival:{message:e,at:r}}:i==="ready"?{...t,idleReady:{message:e,open:!0,at:r}}:t?.idleReady?.open?{...t,idleReady:{...t.idleReady,open:!1,at:r}}:t??{}}async function N(e,t){const r=await l(),i=s(e);r.entries[i]=R(t,r.entries[i]),await w(r)}export{o as lifecycleSignalForMessage,R as nextLifecycleStateAfterLog,N as recordLifecycleLog,h as shouldSuppressLifecycleLog,L as shouldSuppressLifecycleLogFromState};
@@ -1,39 +1,6 @@
1
- /**
2
- * Sprint 6 / gh#153 — pure render function for `borg:list-roles` MCP tool.
3
- *
4
- * Extracted from the inline `case 'borg:list-roles'` handler in
5
- * `index.ts` per drone-3's QA-FAIL 2026-05-18T13:27:53Z so the render
6
- * logic is unit-testable without exercising the full MCP tool dispatch
7
- * stack. The handler simply calls `renderRoleList(roles, cubeId)` and
8
- * returns its output.
9
- */
10
- /**
11
- * Render the role registry for a cube as a markdown list with role IDs
12
- * exposed for use with `borg:reassign-drone`. Returns the empty-roles
13
- * placeholder when the input array is empty.
14
- *
15
- * Each role line shape:
16
- * - **<name>**(<tags>) `<uuid>` — <description>
17
- *
18
- * Tags collected in order: Queen, human-seat, default, can-broadcast,
19
- * receives-all-direct. Joined with `, `; suffix omitted entirely when no tags apply.
20
- */
21
- export function renderRoleList(roles, cubeId) {
22
- if (!roles.length) {
23
- return 'No roles in this cube yet.';
24
- }
25
- const lines = roles.map((r) => {
26
- const tags = [
27
- r.role_class === 'queen' ? 'Queen' : null,
28
- r.is_human_seat ? 'human-seat' : null,
29
- r.is_default ? 'default' : null,
30
- r.can_broadcast ? 'can-broadcast' : null,
31
- r.receives_all_direct ? 'receives-all-direct' : null,
32
- ].filter(Boolean).join(', ');
33
- const tagSuffix = tags ? ` (${tags})` : '';
34
- const desc = r.short_description || '_(no description)_';
35
- return `- **${r.name}**${tagSuffix} \`${r.id}\` — ${desc}`;
36
- });
37
- return `Roles in cube ${cubeId} (${roles.length}):\n\n${lines.join('\n')}\n\nUse the role IDs above with \`borg:reassign-drone\` to change a drone's role.`;
38
- }
39
- //# sourceMappingURL=list-roles-render.js.map
1
+ function i(n,s){if(!n.length)return"No roles in this cube yet.";const l=n.map(e=>{const t=[e.role_class==="queen"?"Queen":null,e.is_human_seat?"human-seat":null,e.is_default?"default":null,e.can_broadcast?"can-broadcast":null,e.receives_all_direct?"receives-all-direct":null].filter(Boolean).join(", "),o=t?` (${t})`:"",a=e.short_description||"_(no description)_";return`- **${e.name}**${o} \`${e.id}\` \u2014 ${a}`});return`Roles in cube ${s} (${n.length}):
2
+
3
+ ${l.join(`
4
+ `)}
5
+
6
+ Use the role IDs above with \`borg:reassign-drone\` to change a drone's role.`}export{i as renderRoleList};
package/dist/log-audit.js CHANGED
@@ -1,187 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * borg-log-audit
4
- *
5
- * Domain-agnostic nudge: scans the Claude Code transcript and emits a
6
- * one-line warning to stdout if the drone has accumulated MATERIAL_THRESHOLD
7
- * or more state-changing tool calls (Edit / Write / Bash / etc.) since the
8
- * last borg:log post. Wired in as a UserPromptSubmit hook so the warning
9
- * becomes additional context for the next turn.
10
- *
11
- * Two refinements vs the v1 1-tool threshold (per drone-6's review):
12
- * 1. Counts material tools across all assistant turns until either the
13
- * threshold is hit OR a borg:log call is found (cooldown). One
14
- * diagnostic Bash no longer triggers; substantive work always does.
15
- * 2. Any borg:log in the scanback suppresses the nudge — so the drone
16
- * gets a turn of breathing room after each post.
17
- *
18
- * Stays generic — knows nothing about git, branches, or any project's
19
- * conventions. Only the Anthropic tool name `mcp__borg__borg_log` and a
20
- * small set of canonical mutating tool names. If no cube is active in
21
- * this project, silently exits.
22
- *
23
- * Hook input arrives as JSON on stdin (Claude Code's standard hook
24
- * contract). The relevant field is `transcript_path`.
25
- */
26
- import { existsSync, readFileSync } from 'node:fs';
27
- import { getActiveCube } from './cubes.js';
28
- import { handleVersionFlag } from './version.js';
29
- const MATERIAL_TOOLS = new Set([
30
- 'Edit',
31
- 'Write',
32
- 'MultiEdit',
33
- 'NotebookEdit',
34
- 'Bash',
35
- 'apply_patch',
36
- 'exec_command',
37
- 'functions.exec_command',
38
- 'functions.apply_patch',
39
- ]);
40
- const LOG_TOOL = 'mcp__borg__borg_log';
41
- // Number of state-changing tool calls since the last borg:log that the
42
- // drone is allowed before the audit nudges. 1 false-positives on
43
- // diagnostic Bash (git status, ls, etc.); 3 has been comfortable in
44
- // dogfooding — any substantive work crosses it within a turn or two.
45
- const MATERIAL_THRESHOLD = 3;
46
- // Cap on how many transcript entries we scan backwards before giving up.
47
- // Sessions with thousands of turns still resolve in milliseconds at this
48
- // bound, and anything truly old is no longer "the last span the drone
49
- // failed to log."
50
- const MAX_SCAN = 400;
51
- function isUserPrompt(entry) {
52
- const type = entry?.type ?? entry?.role;
53
- if (type !== 'user')
54
- return false;
55
- const content = entry?.message?.content ?? entry?.content;
56
- if (typeof content === 'string')
57
- return content.trim().length > 0;
58
- if (!Array.isArray(content))
59
- return false;
60
- // A "real" user prompt has at least one text block. A tool_result-only
61
- // user message is a continuation of an assistant span, not a prompt.
62
- return content.some((b) => b?.type === 'text');
63
- }
64
- function isAssistant(entry) {
65
- const type = entry?.type ?? entry?.role;
66
- return type === 'assistant' || type === 'response_item';
67
- }
68
- function scanAssistant(entry, state) {
69
- const payload = entry?.payload;
70
- const payloadToolName = payload?.type === 'function_call' || payload?.type === 'custom_tool_call'
71
- ? payload.name
72
- : null;
73
- if (typeof payloadToolName === 'string') {
74
- if (payloadToolName === LOG_TOOL) {
75
- state.loggedRecently = true;
76
- return;
77
- }
78
- if (MATERIAL_TOOLS.has(payloadToolName))
79
- state.material += 1;
80
- }
81
- const content = entry?.message?.content ?? entry?.content ?? [];
82
- if (!Array.isArray(content))
83
- return;
84
- // Walk the blocks newest-first WITHIN the entry. The caller already
85
- // visits entries newest-first. Counting forward within an entry would
86
- // either miss post-log material work (if log is in the same entry as
87
- // later material blocks) or inflate the count with pre-log work that
88
- // the log already covered. Reversing here keeps "material since the
89
- // last log" honest at block granularity.
90
- for (let i = content.length - 1; i >= 0; i--) {
91
- const block = content[i];
92
- if (block?.type !== 'tool_use')
93
- continue;
94
- if (block.name === LOG_TOOL) {
95
- state.loggedRecently = true;
96
- return;
97
- }
98
- if (MATERIAL_TOOLS.has(block.name))
99
- state.material += 1;
100
- }
101
- }
102
- async function readStdin() {
103
- if (process.stdin.isTTY)
104
- return '';
105
- const chunks = [];
106
- for await (const chunk of process.stdin)
107
- chunks.push(chunk);
108
- return Buffer.concat(chunks).toString('utf-8');
109
- }
110
- async function main() {
111
- handleVersionFlag();
112
- const raw = await readStdin();
113
- let input = {};
114
- if (raw.trim()) {
115
- try {
116
- input = JSON.parse(raw);
117
- }
118
- catch {
119
- // No usable input — silent exit.
120
- return;
121
- }
122
- }
123
- if (!input.transcript_path || !existsSync(input.transcript_path))
124
- return;
125
- if (input.cwd && existsSync(input.cwd)) {
126
- try {
127
- process.chdir(input.cwd);
128
- }
129
- catch {
130
- // Best-effort only; fall back to the hook process cwd.
131
- }
132
- }
133
- // Only nudge if there's an active cube in this project. Otherwise the
134
- // hook is fully inert.
135
- const active = await getActiveCube();
136
- if (!active)
137
- return;
138
- const lines = readFileSync(input.transcript_path, 'utf-8').split('\n').filter(Boolean);
139
- if (lines.length === 0)
140
- return;
141
- // Walk the transcript backwards from the end. The trailing entry MAY
142
- // be the user prompt that triggered this hook; skip it if so. From
143
- // there, accumulate material tool calls until either we hit a borg:log
144
- // (cooldown — suppress) or we cross MATERIAL_THRESHOLD (nudge). The
145
- // scan stops after MAX_SCAN entries to bound work on huge sessions.
146
- let i = lines.length - 1;
147
- const tail = safeParse(lines[i]);
148
- if (tail && isUserPrompt(tail))
149
- i--;
150
- const state = { material: 0, loggedRecently: false };
151
- let scanned = 0;
152
- for (; i >= 0 && scanned < MAX_SCAN; i--, scanned++) {
153
- const entry = safeParse(lines[i]);
154
- if (!entry)
155
- continue;
156
- if (isAssistant(entry)) {
157
- scanAssistant(entry, state);
158
- // Threshold has primacy over cooldown: when both could fire on the
159
- // same entry (e.g. an entry containing [log, Bash, Bash, Bash]
160
- // where the reversed scan first counts 3 material blocks before
161
- // hitting the log), we want the nudge — the post-log material
162
- // work hasn't been logged yet.
163
- if (state.material >= MATERIAL_THRESHOLD) {
164
- process.stdout.write(`Heads up: ${state.material}+ state-changing tool calls since the last \`borg:log\` post. ` +
165
- 'If that work was a substantive unit (a change that ships, a blocker hit, a finding ' +
166
- "worth sharing), post to the cube log per your role's conventions before continuing.\n");
167
- return;
168
- }
169
- if (state.loggedRecently)
170
- return; // cooldown
171
- }
172
- }
173
- // Reached MAX_SCAN or start of transcript without finding either a
174
- // log call or enough material work. Silent.
175
- }
176
- function safeParse(line) {
177
- try {
178
- return JSON.parse(line);
179
- }
180
- catch {
181
- return null;
182
- }
183
- }
184
- main().catch(() => {
185
- // Never fail a hook — silent on error.
186
- });
187
- //# sourceMappingURL=log-audit.js.map
2
+ import{existsSync as l,readFileSync as g}from"node:fs";import{getActiveCube as h}from"./cubes.js";import{handleVersionFlag as m}from"./version.js";const u=new Set(["Edit","Write","MultiEdit","NotebookEdit","Bash","apply_patch","exec_command","functions.exec_command","functions.apply_patch"]),f="mcp__borg__borg_log",d=3,y=400;function _(t){if((t?.type??t?.role)!=="user")return!1;const o=t?.message?.content??t?.content;return typeof o=="string"?o.trim().length>0:Array.isArray(o)?o.some(e=>e?.type==="text"):!1}function A(t){const n=t?.type??t?.role;return n==="assistant"||n==="response_item"}function w(t,n){const o=t?.payload,e=o?.type==="function_call"||o?.type==="custom_tool_call"?o.name:null;if(typeof e=="string"){if(e===f){n.loggedRecently=!0;return}u.has(e)&&(n.material+=1)}const r=t?.message?.content??t?.content??[];if(Array.isArray(r))for(let s=r.length-1;s>=0;s--){const i=r[s];if(i?.type==="tool_use"){if(i.name===f){n.loggedRecently=!0;return}u.has(i.name)&&(n.material+=1)}}}async function b(){if(process.stdin.isTTY)return"";const t=[];for await(const n of process.stdin)t.push(n);return Buffer.concat(t).toString("utf-8")}async function S(){m();const t=await b();let n={};if(t.trim())try{n=JSON.parse(t)}catch{return}if(!n.transcript_path||!l(n.transcript_path))return;if(n.cwd&&l(n.cwd))try{process.chdir(n.cwd)}catch{}if(!await h())return;const e=g(n.transcript_path,"utf-8").split(`
3
+ `).filter(Boolean);if(e.length===0)return;let r=e.length-1;const s=p(e[r]);s&&_(s)&&r--;const i={material:0,loggedRecently:!1};let c=0;for(;r>=0&&c<y;r--,c++){const a=p(e[r]);if(a&&A(a)){if(w(a,i),i.material>=d){process.stdout.write(`Heads up: ${i.material}+ state-changing tool calls since the last \`borg:log\` post. If that work was a substantive unit (a change that ships, a blocker hit, a finding worth sharing), post to the cube log per your role's conventions before continuing.
4
+ `);return}if(i.loggedRecently)return}}}function p(t){try{return JSON.parse(t)}catch{return null}}S().catch(()=>{});