borgmcp 2.7.1 → 2.7.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.
- package/README.md +15 -10
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +83 -26
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/claude.js +6 -16
- package/dist/claude.js.map +1 -1
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +49 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cubes.d.ts +10 -5
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +54 -3
- package/dist/cubes.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -7
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +3 -0
- package/dist/log-stream.d.ts.map +1 -1
- package/dist/log-stream.js +6 -1
- package/dist/log-stream.js.map +1 -1
- package/dist/opencode-seat-identity.d.ts +22 -0
- package/dist/opencode-seat-identity.d.ts.map +1 -0
- package/dist/opencode-seat-identity.js +69 -0
- package/dist/opencode-seat-identity.js.map +1 -0
- package/dist/server-facade.d.ts +4 -0
- package/dist/server-facade.d.ts.map +1 -1
- package/dist/server-facade.js +11 -0
- package/dist/server-facade.js.map +1 -1
- package/dist/setup.js +2 -4
- package/dist/setup.js.map +1 -1
- package/dist/startup-services.d.ts +3 -1
- package/dist/startup-services.d.ts.map +1 -1
- package/dist/startup-services.js +7 -2
- package/dist/startup-services.js.map +1 -1
- package/dist/stream-owner.d.ts +9 -0
- package/dist/stream-owner.d.ts.map +1 -1
- package/dist/stream-owner.js +14 -2
- package/dist/stream-owner.js.map +1 -1
- package/dist/stream-status.d.ts.map +1 -1
- package/dist/stream-status.js +8 -1
- package/dist/stream-status.js.map +1 -1
- package/dist/tool-manifest.js +11 -11
- package/dist/tool-manifest.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +3 -3
- package/docs/RELEASING.md +11 -1
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +94 -26
- package/src/claude.ts +11 -16
- package/src/cli-help.ts +65 -1
- package/src/cubes.ts +70 -3
- package/src/index.ts +66 -7
- package/src/log-stream.ts +9 -1
- package/src/opencode-seat-identity.ts +111 -0
- package/src/server-facade.ts +15 -0
- package/src/setup.ts +2 -4
- package/src/startup-services.ts +8 -2
- package/src/stream-owner.ts +23 -2
- package/src/stream-status.ts +8 -1
- package/src/tool-manifest.ts +11 -11
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { ActiveCube } from './cubes.js';
|
|
4
|
+
|
|
5
|
+
export type OpenCodeSeatIdentityErrorCode =
|
|
6
|
+
| 'ROOTS_UNAVAILABLE'
|
|
7
|
+
| 'ROOTS_INVALID'
|
|
8
|
+
| 'SEAT_NOT_FOUND'
|
|
9
|
+
| 'SEAT_WORKTREE_MISMATCH';
|
|
10
|
+
|
|
11
|
+
export class OpenCodeSeatIdentityError extends Error {
|
|
12
|
+
constructor(
|
|
13
|
+
public readonly code: OpenCodeSeatIdentityErrorCode,
|
|
14
|
+
message: string,
|
|
15
|
+
public readonly sessionDirectory?: string,
|
|
16
|
+
public readonly seat?: Pick<ActiveCube, 'droneLabel' | 'worktree'>,
|
|
17
|
+
) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'OpenCodeSeatIdentityError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OpenCodeSeatIdentityDeps {
|
|
24
|
+
listRoots: () => Promise<{ roots?: Array<{ uri?: string }> }>;
|
|
25
|
+
findProjectRoot: (directory: string) => string;
|
|
26
|
+
getActiveCubeForWorktree: (worktree: string) => Promise<ActiveCube | null>;
|
|
27
|
+
pinSeatIdentity: (active: ActiveCube) => void;
|
|
28
|
+
childCwd: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function resolveOpenCodeSeatIdentity(
|
|
32
|
+
deps: OpenCodeSeatIdentityDeps,
|
|
33
|
+
): Promise<ActiveCube> {
|
|
34
|
+
let roots: { roots?: Array<{ uri?: string }> };
|
|
35
|
+
try {
|
|
36
|
+
roots = await deps.listRoots();
|
|
37
|
+
} catch {
|
|
38
|
+
throw new OpenCodeSeatIdentityError(
|
|
39
|
+
'ROOTS_UNAVAILABLE',
|
|
40
|
+
'OpenCode did not provide its session directory.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(roots.roots) || roots.roots.length !== 1) {
|
|
44
|
+
throw new OpenCodeSeatIdentityError(
|
|
45
|
+
'ROOTS_INVALID',
|
|
46
|
+
'OpenCode must provide exactly one local session directory.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const uri = roots.roots[0]?.uri;
|
|
50
|
+
let sessionDirectory: string;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = new URL(typeof uri === 'string' ? uri : '');
|
|
53
|
+
if (parsed.protocol !== 'file:' || parsed.hostname || parsed.search || parsed.hash) throw new Error();
|
|
54
|
+
sessionDirectory = resolve(fileURLToPath(parsed));
|
|
55
|
+
} catch {
|
|
56
|
+
throw new OpenCodeSeatIdentityError(
|
|
57
|
+
'ROOTS_INVALID',
|
|
58
|
+
'OpenCode provided an invalid or non-local session directory.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const sessionWorktree = deps.findProjectRoot(sessionDirectory);
|
|
63
|
+
const cwdWorktree = deps.findProjectRoot(deps.childCwd);
|
|
64
|
+
// Direct `borg` launch keeps cwd as its seat source. When a shared OpenCode
|
|
65
|
+
// server spawns the MCP child elsewhere, the session-scoped root is the
|
|
66
|
+
// launcher-conferred pin and replaces that ambient cwd inference.
|
|
67
|
+
const identityWorktree = cwdWorktree === sessionWorktree
|
|
68
|
+
? cwdWorktree
|
|
69
|
+
: sessionWorktree;
|
|
70
|
+
const active = await deps.getActiveCubeForWorktree(identityWorktree);
|
|
71
|
+
if (!active) {
|
|
72
|
+
throw new OpenCodeSeatIdentityError(
|
|
73
|
+
'SEAT_NOT_FOUND',
|
|
74
|
+
'No active Borg seat is bound to the OpenCode session directory.',
|
|
75
|
+
sessionWorktree,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (typeof active.worktree !== 'string' || resolve(active.worktree) !== resolve(sessionWorktree)) {
|
|
79
|
+
throw new OpenCodeSeatIdentityError(
|
|
80
|
+
'SEAT_WORKTREE_MISMATCH',
|
|
81
|
+
'The resolved Borg seat belongs to a different worktree than the OpenCode session.',
|
|
82
|
+
sessionWorktree,
|
|
83
|
+
active,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
deps.pinSeatIdentity(active);
|
|
88
|
+
return active;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function formatOpenCodeSeatIdentityError(
|
|
92
|
+
error: OpenCodeSeatIdentityError,
|
|
93
|
+
childCwd: string,
|
|
94
|
+
): string {
|
|
95
|
+
const lines = [
|
|
96
|
+
`Borg OpenCode seat identity error [${error.code}]`,
|
|
97
|
+
'',
|
|
98
|
+
error.message,
|
|
99
|
+
`- OpenCode session directory: ${error.sessionDirectory ?? 'unavailable'}`,
|
|
100
|
+
`- Borg MCP child cwd: ${childCwd}`,
|
|
101
|
+
];
|
|
102
|
+
if (error.seat) {
|
|
103
|
+
lines.push(`- Resolved seat: ${error.seat.droneLabel} (${error.seat.worktree})`);
|
|
104
|
+
}
|
|
105
|
+
lines.push(
|
|
106
|
+
'',
|
|
107
|
+
'The Borg stream and OpenCode wake injection were not started.',
|
|
108
|
+
'Exit this session and run `borg --cli opencode` from the intended worktree. If that worktree’s saved seat is stale, run `borg reset-local-seat` from that exact worktree before assimilating again.',
|
|
109
|
+
);
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
package/src/server-facade.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type ParsedServerFacadeArgs =
|
|
|
12
12
|
| { kind: 'help' }
|
|
13
13
|
| { kind: 'cube-init-help' }
|
|
14
14
|
| { kind: 'cube-init'; args: string[] }
|
|
15
|
+
| { kind: 'command-help'; command: ServerLifecycleCommand }
|
|
15
16
|
| { kind: 'command'; command: ServerLifecycleCommand; args: string[] }
|
|
16
17
|
| { kind: 'error'; reason: 'unknown-command'; command: string };
|
|
17
18
|
|
|
@@ -33,6 +34,9 @@ export function parseServerFacadeArgs(args: readonly string[]): ParsedServerFaca
|
|
|
33
34
|
if (!(SERVER_LIFECYCLE_COMMANDS as readonly string[]).includes(command)) {
|
|
34
35
|
return { kind: 'error', reason: 'unknown-command', command };
|
|
35
36
|
}
|
|
37
|
+
if (rest.some(isHelpFlag)) {
|
|
38
|
+
return { kind: 'command-help', command: command as ServerLifecycleCommand };
|
|
39
|
+
}
|
|
36
40
|
return {
|
|
37
41
|
kind: 'command',
|
|
38
42
|
command: command as ServerLifecycleCommand,
|
|
@@ -163,6 +167,13 @@ export function unknownServerCommandText(command: string): string {
|
|
|
163
167
|
);
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
export function serverLifecycleHelpText(command: ServerLifecycleCommand): string {
|
|
171
|
+
return (
|
|
172
|
+
`Usage: borg server ${command} [arguments]\n\n` +
|
|
173
|
+
`Command arguments are server-owned and pass to the verified borg-mcp-server executable when run.\n`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
export function missingServerExecutableText(command: ServerLifecycleCommand): string {
|
|
167
178
|
return (
|
|
168
179
|
`Local server command is unavailable: borg-mcp-server was not found.\n` +
|
|
@@ -256,6 +267,10 @@ export async function runEarlyServerFacade(
|
|
|
256
267
|
output.writeStdout(cubeInitHelpText(getPackageVersion()));
|
|
257
268
|
return 0;
|
|
258
269
|
}
|
|
270
|
+
if (parsed.kind === 'command-help') {
|
|
271
|
+
output.writeStdout(serverLifecycleHelpText(parsed.command));
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
259
274
|
if (parsed.kind === 'error') {
|
|
260
275
|
output.writeStderr(unknownServerCommandText(parsed.command));
|
|
261
276
|
return 1;
|
package/src/setup.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { handleVersionFlag } from './version.js';
|
|
|
38
38
|
import { initDebugFromArgv } from './debug.js';
|
|
39
39
|
import { defaultApprovalIo, setupApprovalWarnings } from './cli-tool-approval.js';
|
|
40
40
|
import { offerFirstRunServerInstall } from './first-run-server.js';
|
|
41
|
+
import { setupNextStepsText } from './cli-help.js';
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Main setup wizard
|
|
@@ -184,10 +185,7 @@ async function main() {
|
|
|
184
185
|
// Success message
|
|
185
186
|
console.log(chalk.green.bold('\nSetup complete!\n'));
|
|
186
187
|
console.log(chalk.yellow('🔄 Restart Claude Code / Codex / OpenCode (or open a new session) for the changes to take effect.\n'));
|
|
187
|
-
console.log(chalk.gray(
|
|
188
|
-
console.log(chalk.gray('1. cd into your project, then run "borg assimilate --host <host>" to join a cube'));
|
|
189
|
-
console.log(chalk.gray(' (this connects to your local server and launches your agent)'));
|
|
190
|
-
console.log(chalk.gray('2. Use `borg assimilate --host <host> --enroll` from the operator terminal to enroll a new client\n'));
|
|
188
|
+
console.log(chalk.gray(setupNextStepsText()));
|
|
191
189
|
}
|
|
192
190
|
|
|
193
191
|
// Run wizard
|
package/src/startup-services.ts
CHANGED
|
@@ -16,10 +16,16 @@ export interface McpStartupServices {
|
|
|
16
16
|
*/
|
|
17
17
|
export async function runMcpStartupServices(
|
|
18
18
|
readinessProbe: boolean,
|
|
19
|
-
services: McpStartupServices
|
|
19
|
+
services: McpStartupServices,
|
|
20
|
+
options: { openCodeFirst?: boolean } = {},
|
|
20
21
|
): Promise<void> {
|
|
21
22
|
if (readinessProbe) return;
|
|
22
|
-
const tasks = [
|
|
23
|
+
const tasks = options.openCodeFirst ? [
|
|
24
|
+
services.sessionStartHook,
|
|
25
|
+
services.auditHook,
|
|
26
|
+
services.openCode,
|
|
27
|
+
services.sseStream,
|
|
28
|
+
] : [
|
|
23
29
|
services.sessionStartHook,
|
|
24
30
|
services.auditHook,
|
|
25
31
|
services.sseStream,
|
package/src/stream-owner.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface StreamOwnerRecord {
|
|
|
24
24
|
cwd: string;
|
|
25
25
|
startedAt: string;
|
|
26
26
|
heartbeatAt: string;
|
|
27
|
+
worktree?: string;
|
|
28
|
+
droneLabel?: string;
|
|
29
|
+
cubeName?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export interface StreamOwnershipSnapshot {
|
|
@@ -33,6 +36,9 @@ export interface StreamOwnershipSnapshot {
|
|
|
33
36
|
cwd?: string;
|
|
34
37
|
startedAt?: string;
|
|
35
38
|
heartbeatAt?: string;
|
|
39
|
+
worktree?: string;
|
|
40
|
+
droneLabel?: string;
|
|
41
|
+
cubeName?: string;
|
|
36
42
|
ageMs?: number;
|
|
37
43
|
lockPath?: string;
|
|
38
44
|
/** Opened-directory identity used to bind inspection to later takeover. */
|
|
@@ -54,6 +60,9 @@ export interface StreamOwnerDeps {
|
|
|
54
60
|
locksDir?: string;
|
|
55
61
|
processNonce?: string;
|
|
56
62
|
processStartedAt?: string;
|
|
63
|
+
worktree?: string;
|
|
64
|
+
droneLabel?: string;
|
|
65
|
+
cubeName?: string;
|
|
57
66
|
isPidAlive?: (pid: number) => boolean;
|
|
58
67
|
beforeTakeoverVerify?: (takeoverPath: string) => Promise<void>;
|
|
59
68
|
beforeLeaseRefreshMutation?: (lockPath: string) => Promise<void>;
|
|
@@ -177,6 +186,9 @@ export async function readOwnershipSnapshot(
|
|
|
177
186
|
cwd: parsed.cwd,
|
|
178
187
|
startedAt: parsed.startedAt,
|
|
179
188
|
heartbeatAt: parsed.heartbeatAt,
|
|
189
|
+
worktree: parsed.worktree,
|
|
190
|
+
droneLabel: parsed.droneLabel,
|
|
191
|
+
cubeName: parsed.cubeName,
|
|
180
192
|
ageMs,
|
|
181
193
|
lockPath,
|
|
182
194
|
lockDev: lockStat.dev,
|
|
@@ -572,7 +584,10 @@ function sameOwner(left: StreamOwnerRecord, right: StreamOwnerRecord): boolean {
|
|
|
572
584
|
left.processNonce === right.processNonce &&
|
|
573
585
|
left.cwd === right.cwd &&
|
|
574
586
|
left.startedAt === right.startedAt &&
|
|
575
|
-
left.heartbeatAt === right.heartbeatAt
|
|
587
|
+
left.heartbeatAt === right.heartbeatAt &&
|
|
588
|
+
left.worktree === right.worktree &&
|
|
589
|
+
left.droneLabel === right.droneLabel &&
|
|
590
|
+
left.cubeName === right.cubeName;
|
|
576
591
|
}
|
|
577
592
|
|
|
578
593
|
async function readOwnershipRecord(lockPath: string): Promise<StreamOwnerRecord | null> {
|
|
@@ -655,6 +670,9 @@ function makeRecord(deps: StreamOwnerDeps): StreamOwnerRecord {
|
|
|
655
670
|
cwd: deps.cwd ?? process.cwd(),
|
|
656
671
|
startedAt: deps.processStartedAt ?? processStartedAt,
|
|
657
672
|
heartbeatAt: now().toISOString(),
|
|
673
|
+
...(deps.worktree ? { worktree: deps.worktree } : {}),
|
|
674
|
+
...(deps.droneLabel ? { droneLabel: deps.droneLabel } : {}),
|
|
675
|
+
...(deps.cubeName ? { cubeName: deps.cubeName } : {}),
|
|
658
676
|
};
|
|
659
677
|
}
|
|
660
678
|
|
|
@@ -668,7 +686,10 @@ function isRecord(value: any): value is StreamOwnerRecord {
|
|
|
668
686
|
isSafeLeaseText(value.processNonce, 128) &&
|
|
669
687
|
isSafeLeaseText(value.cwd, 4096) &&
|
|
670
688
|
isIsoTimestamp(value.startedAt) &&
|
|
671
|
-
isIsoTimestamp(value.heartbeatAt)
|
|
689
|
+
isIsoTimestamp(value.heartbeatAt) &&
|
|
690
|
+
(value.worktree === undefined || isSafeLeaseText(value.worktree, 4096)) &&
|
|
691
|
+
(value.droneLabel === undefined || isSafeLeaseText(value.droneLabel, 256)) &&
|
|
692
|
+
(value.cubeName === undefined || isSafeLeaseText(value.cubeName, 256))
|
|
672
693
|
);
|
|
673
694
|
}
|
|
674
695
|
|
package/src/stream-status.ts
CHANGED
|
@@ -161,7 +161,10 @@ export function renderStreamStatus(inputs: RenderInputs): string {
|
|
|
161
161
|
if (orphanedInitialization) {
|
|
162
162
|
summary = '**Stream blocked by an orphaned initialization lock.**';
|
|
163
163
|
} else if (ownedByOther) {
|
|
164
|
-
|
|
164
|
+
const owner = status.ownership!;
|
|
165
|
+
summary = owner.droneLabel && owner.worktree
|
|
166
|
+
? `**Stream owned by seat ${owner.droneLabel} in \`${owner.worktree}\`.**`
|
|
167
|
+
: '**Stream owned by another Borg MCP process.**';
|
|
165
168
|
} else if (isNotStarted) {
|
|
166
169
|
summary = '**Stream not started.**';
|
|
167
170
|
} else if (!status.connected) {
|
|
@@ -245,6 +248,8 @@ export function renderStreamStatus(inputs: RenderInputs): string {
|
|
|
245
248
|
|
|
246
249
|
if (ownedByOther) {
|
|
247
250
|
const owner = status.ownership!;
|
|
251
|
+
lines.push(`- **stream owner seat**: ${owner.droneLabel ?? '_(unknown)_'}`);
|
|
252
|
+
lines.push(`- **stream owner worktree**: ${owner.worktree ?? '_(unknown)_'}`);
|
|
248
253
|
lines.push(`- **stream owner pid**: ${owner.pid ?? '_(unknown)_'}`);
|
|
249
254
|
lines.push(`- **stream owner cwd**: ${owner.cwd ?? '_(unknown)_'}`);
|
|
250
255
|
lines.push(
|
|
@@ -254,6 +259,8 @@ export function renderStreamStatus(inputs: RenderInputs): string {
|
|
|
254
259
|
: '_(unknown)_'
|
|
255
260
|
}`
|
|
256
261
|
);
|
|
262
|
+
lines.push('');
|
|
263
|
+
lines.push('Continue in the owning seat, or close its duplicate agent session before relaunching from the intended worktree. The live owner releases this lock on exit; a stale lock is reclaimed automatically.');
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
if (wakePath.agentKind === 'opencode' && wakePath.openCode) {
|
package/src/tool-manifest.ts
CHANGED
|
@@ -58,10 +58,10 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
58
58
|
{
|
|
59
59
|
name: 'borg_assimilate',
|
|
60
60
|
description:
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
'
|
|
61
|
+
"Reconnect this session to the existing drone saved for this worktree. This tool " +
|
|
62
|
+
"never creates drones. Provide the cube's name; on a match it returns the cube " +
|
|
63
|
+
"directive, your role's instructions, and recent activity for that drone. To create " +
|
|
64
|
+
'a drone or switch cubes, run `borg assimilate` in a terminal instead.',
|
|
65
65
|
inputSchema: {
|
|
66
66
|
type: 'object',
|
|
67
67
|
properties: {
|
|
@@ -116,7 +116,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
116
116
|
{
|
|
117
117
|
name: 'borg_playbook',
|
|
118
118
|
description:
|
|
119
|
-
'Load the full operating-playbook chapter — the detailed disciplines, rationale, and examples behind the
|
|
119
|
+
'Load the full operating-playbook chapter — the detailed disciplines, rationale, and examples behind the abbreviated session instructions (verification discipline v1/v2/v3, concrete source-of-truth surfaces, four-surface propagation). This detail is omitted from the initial context to keep it light; fetch it ONCE per session when doing review/verify-class work. Static text — do NOT re-fetch on every wake.',
|
|
120
120
|
inputSchema: {
|
|
121
121
|
type: 'object',
|
|
122
122
|
properties: {},
|
|
@@ -153,7 +153,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
153
153
|
name: 'borg_role-rationale',
|
|
154
154
|
description:
|
|
155
155
|
"Fetch an on-demand rationale/case-study section for a role playbook. " +
|
|
156
|
-
"Pass a role name/id and a plain-label section key to read the rationale without expanding every
|
|
156
|
+
"Pass a role name/id and a plain-label section key to read the rationale without expanding every context refresh.",
|
|
157
157
|
inputSchema: {
|
|
158
158
|
type: 'object',
|
|
159
159
|
properties: {
|
|
@@ -249,7 +249,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
249
249
|
{
|
|
250
250
|
name: 'borg_decide',
|
|
251
251
|
description:
|
|
252
|
-
'Record a RATIFIED cube decision in the durable decision registry
|
|
252
|
+
'Record a RATIFIED cube decision in the durable decision registry so drones cite it by topic instead of restating from memory. Coordinator and Queen roles are workflow-eligible to ratify, but role labels grant no server permission; the selected local client must have a live cube-manage grant. Recording IS the ratification act; a decision is not ratified until it is in the registry. Topic-keyed: recording a new decision on an existing topic supersedes the prior (one active per topic). The decision appears in borg_regen and borg_decisions.',
|
|
253
253
|
inputSchema: {
|
|
254
254
|
type: 'object',
|
|
255
255
|
required: ['topic', 'decision'],
|
|
@@ -272,7 +272,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
272
272
|
{
|
|
273
273
|
name: 'borg_decisions',
|
|
274
274
|
description:
|
|
275
|
-
'List the active ratified decisions for the cube
|
|
275
|
+
'List the active ratified decisions for the cube — the source of truth to CITE instead of restating a decision from memory. Any member may read. Pass `topic` to fetch one topic\'s active decision; omit for all active decisions.',
|
|
276
276
|
inputSchema: {
|
|
277
277
|
type: 'object',
|
|
278
278
|
properties: {
|
|
@@ -350,7 +350,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
350
350
|
pattern: '^[a-z0-9-]+$',
|
|
351
351
|
maxLength: 64,
|
|
352
352
|
},
|
|
353
|
-
cube_directive: { type: 'string', description: 'Markdown
|
|
353
|
+
cube_directive: { type: 'string', description: 'Project-specific Markdown shown to every drone when it refreshes cube context.' },
|
|
354
354
|
template: {
|
|
355
355
|
type: 'string',
|
|
356
356
|
description: 'Optional template name to apply after cube creation (e.g. "software-dev"). Roles are merged by name; the default Drone role gets overwritten by the template if a same-named role is in the template.',
|
|
@@ -439,7 +439,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
439
439
|
short_description: { type: 'string', description: 'One-line summary, shown to every drone in the cube.' },
|
|
440
440
|
detailed_description: { type: 'string', description: 'Full playbook for drones in this role — workflow, conventions, log signals to post.' },
|
|
441
441
|
is_default: { type: 'boolean', description: 'If true, new drones assimilating into this cube are assigned this role. Demotes the previous default.' },
|
|
442
|
-
is_mandatory: { type: 'boolean', description: 'If true, role-less
|
|
442
|
+
is_mandatory: { type: 'boolean', description: 'If true, role-less assimilation prioritizes this unoccupied role before ordinary worker roles. Platform-wide management roles are never auto-assigned; a mandatory human-operator role is selected first until occupied.' },
|
|
443
443
|
is_human_seat: { type: 'boolean', description: 'If true, this role represents the cube\'s human-occupied seat (where the human Queen sits directly). The class-hierarchy guard in reassign-drone allows promotion FROM a human-seat role TO the platform Queen role; promotion from non-human-seat roles is rejected.' },
|
|
444
444
|
can_broadcast: { type: 'boolean', description: 'If true, drones in this role may post broadcast log entries when strict broadcast gating is enabled.' },
|
|
445
445
|
receives_all_direct: { type: 'boolean', description: 'If true, drones in this role can see direct log entries as observer/audit recipients.' },
|
|
@@ -458,7 +458,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
458
458
|
short_description: { type: 'string', description: 'New short description (optional).' },
|
|
459
459
|
detailed_description: { type: 'string', description: 'New detailed playbook (optional).' },
|
|
460
460
|
is_default: { type: 'boolean', description: 'Set true to make this the cube\'s default role (optional).' },
|
|
461
|
-
is_mandatory: { type: 'boolean', description: 'Set true/false to prioritize this unoccupied
|
|
461
|
+
is_mandatory: { type: 'boolean', description: 'Set true/false to prioritize this unoccupied role during role-less assimilation. Platform-wide management roles are never auto-assigned.' },
|
|
462
462
|
is_human_seat: { type: 'boolean', description: 'Set true/false to mark/unmark this as the cube\'s human-occupied seat (the elevation source for the platform Queen role).' },
|
|
463
463
|
can_broadcast: { type: 'boolean', description: 'Set true/false to allow or deny broadcast log entries when strict broadcast gating is enabled.' },
|
|
464
464
|
receives_all_direct: { type: 'boolean', description: 'Set true/false to grant or remove observer visibility into direct log entries.' },
|