borgmcp-shared 0.6.4 → 0.7.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.
@@ -10,7 +10,12 @@ import {
10
10
  type ProtocolEnvelope,
11
11
  } from './contract.js';
12
12
  import { decodeEnrichedStreamEntry } from './sse.js';
13
- import type { Decision, EnrichedStreamEntry } from './types.js';
13
+ import type {
14
+ AppendLogResponse,
15
+ Decision,
16
+ EnrichedStreamEntry,
17
+ RoutingEcho,
18
+ } from './types.js';
14
19
 
15
20
  export interface ReadLogRequest {
16
21
  cursor: LogCursor | null;
@@ -26,7 +31,7 @@ export interface ClaimRecord {
26
31
  stale: boolean;
27
32
  }
28
33
 
29
- export interface AppendLogResult {
34
+ export interface AppendLogResult extends Omit<AppendLogResponse, 'entry'> {
30
35
  entry: EnrichedStreamEntry;
31
36
  }
32
37
 
@@ -214,10 +219,54 @@ function decodeClaimRecord(value: unknown): ClaimRecord {
214
219
  };
215
220
  }
216
221
 
222
+ function decodeRoutingEcho(value: unknown): RoutingEcho {
223
+ const input = object(value);
224
+ exact(
225
+ input,
226
+ ['class', 'recipients', 'fellOpen', 'message'],
227
+ ['class', 'recipients', 'fellOpen', 'message'],
228
+ );
229
+ if (!Array.isArray(input.recipients) || input.recipients.length > 100) {
230
+ throw new ProtocolContractError('Invalid routing recipient list.');
231
+ }
232
+ if (typeof input.fellOpen !== 'boolean') {
233
+ throw new ProtocolContractError('Invalid routing fell-open flag.');
234
+ }
235
+ return {
236
+ class: nullableString(input.class, 'routing.class', 64),
237
+ recipients: input.recipients.map((recipient) =>
238
+ boundedString(recipient, 'routing.recipients', 120)
239
+ ),
240
+ fellOpen: input.fellOpen,
241
+ message: nullableString(input.message, 'routing.message', 512),
242
+ };
243
+ }
244
+
245
+ function decodeUnreachableRecipient(
246
+ value: unknown,
247
+ ): NonNullable<AppendLogResponse['unreachableRecipients']>[number] {
248
+ const input = object(value);
249
+ exact(input, ['id', 'label'], ['id', 'label']);
250
+ return {
251
+ id: boundedString(input.id, 'unreachableRecipients.id', 120),
252
+ label: boundedString(input.label, 'unreachableRecipients.label', 120),
253
+ };
254
+ }
255
+
217
256
  export function decodeAppendLogResult(value: unknown): AppendLogResult {
218
257
  const input = object(value);
219
- exact(input, ['entry'], ['entry']);
220
- return { entry: decodeEnrichedStreamEntry(input.entry) };
258
+ exact(input, ['entry', 'routing', 'unreachableRecipients'], ['entry']);
259
+ const output: AppendLogResult = { entry: decodeEnrichedStreamEntry(input.entry) };
260
+ if (input.routing !== undefined) {
261
+ output.routing = input.routing === null ? null : decodeRoutingEcho(input.routing);
262
+ }
263
+ if (input.unreachableRecipients !== undefined) {
264
+ if (!Array.isArray(input.unreachableRecipients) || input.unreachableRecipients.length > 100) {
265
+ throw new ProtocolContractError('Invalid unreachable-recipient list.');
266
+ }
267
+ output.unreachableRecipients = input.unreachableRecipients.map(decodeUnreachableRecipient);
268
+ }
269
+ return output;
221
270
  }
222
271
 
223
272
  export function decodeAppendLogResultEnvelope(
@@ -1,4 +1,4 @@
1
- /** Current Borg coordination protocol generation. Clean-slate v5. */
2
- export const PROTOCOL_VERSION = '5' as const;
1
+ /** Current Borg coordination protocol generation. Clean-slate v6. */
2
+ export const PROTOCOL_VERSION = '6' as const;
3
3
 
4
4
  export type ProtocolVersion = typeof PROTOCOL_VERSION;
package/src/templates.ts CHANGED
@@ -51,6 +51,11 @@ export const NEW_CUBE_TEMPLATE_PRESENTATIONS = [
51
51
  label: 'Starter',
52
52
  short_description: 'Minimal roles for general projects.',
53
53
  },
54
+ {
55
+ name: 'local-model',
56
+ label: 'Local Model',
57
+ short_description: 'Maximizes local-model execution through complete, machine-checkable work packets.',
58
+ },
54
59
  ] as const;
55
60
 
56
61
  export const ESCALATION_DISCIPLINE = `
@@ -266,9 +271,16 @@ Before changing code:
266
271
  - Inspect existing code and tests. Preserve unrelated and pre-existing changes.
267
272
  - If the request is ambiguous in a way that changes scope, post BLOCKED with the smallest decision needed.
268
273
 
274
+ Implementation discipline:
275
+ - Read and trace the real affected flow before choosing an implementation.
276
+ - Prefer, in order: no change when the requirement is already satisfied; an existing repository helper or pattern; the standard library or native platform; an already-installed dependency; only then the minimum new code.
277
+ - Make the smallest change that satisfies the complete authorized acceptance criteria. Prefer the least complex implementation that fully works, not the least work.
278
+ - For defects, inspect sibling callers and fix the root cause at the narrowest shared point when that is safer and smaller than per-caller patches.
279
+ - Never simplify away trust-boundary validation, security controls, data-loss prevention, accessibility requirements, explicit acceptance criteria, or proportionate regression tests.
280
+
269
281
  While working:
270
282
  - Post STARTING with the branch and first concrete action, then substantive PROGRESS during active work.
271
- - Make the smallest coherent implementation. Do not add cleanup, broad refactors, speculative hardening, documentation programs, or follow-up issues unless assigned.
283
+ - Do not add cleanup, broad refactors, speculative hardening, documentation programs, or follow-up issues unless assigned.
272
284
  - A discovered issue outside the slice is a finding, not permission to fix it.
273
285
  - Add proportionate tests for behavior you change. Run the repository checks required by the touched surface.
274
286
 
@@ -503,9 +515,184 @@ const STARTER: Template = {
503
515
  ],
504
516
  };
505
517
 
518
+ const LOCAL_MODEL_TAXONOMY: MessageTaxonomy = [
519
+ {
520
+ class: 'executor-echo',
521
+ prefixes: ['PACKET-ECHO'],
522
+ routing: 'directed',
523
+ default_to: ['shaper'],
524
+ },
525
+ {
526
+ class: 'executor-refusal',
527
+ prefixes: ['SPEC-GAP'],
528
+ routing: 'directed',
529
+ default_to: ['shaper'],
530
+ },
531
+ {
532
+ class: 'executor-completion',
533
+ prefixes: ['PACKET-DONE'],
534
+ routing: 'directed',
535
+ default_to: ['shaper'],
536
+ lifecycle: 'completion',
537
+ },
538
+ {
539
+ class: 'packet-dispatch',
540
+ prefixes: ['EXECUTE PACKET'],
541
+ routing: 'directed',
542
+ default_to: ['executor'],
543
+ lifecycle: 'dispatch',
544
+ },
545
+ {
546
+ class: 'packet-verdict',
547
+ prefixes: ['ACCEPT', 'REJECT'],
548
+ routing: 'directed',
549
+ default_to: ['executor'],
550
+ },
551
+ {
552
+ class: 'blocked-signal',
553
+ prefixes: ['BLOCKED'],
554
+ routing: 'directed',
555
+ default_to: ['director', 'queen'],
556
+ },
557
+ {
558
+ class: 'review-request',
559
+ prefixes: ['REVIEW-READY'],
560
+ routing: 'directed',
561
+ default_to: ['director', 'queen'],
562
+ },
563
+ {
564
+ class: 'director-dispatch',
565
+ prefixes: ['DISPATCH', 'HOLD'],
566
+ routing: 'directed',
567
+ default_to: ['shaper'],
568
+ lifecycle: 'dispatch',
569
+ },
570
+ {
571
+ class: 'director-approval',
572
+ prefixes: ['APPROVED'],
573
+ routing: 'directed',
574
+ default_to: ['shaper'],
575
+ lifecycle: 'completion',
576
+ },
577
+ {
578
+ class: 'cube-wide',
579
+ prefixes: ['DECISION'],
580
+ routing: 'broadcast',
581
+ },
582
+ ];
583
+
584
+ const LOCAL_MODEL_DIRECTIVE = `## Verification-cost workflow
585
+
586
+ - Work only on the human-authorized outcome. Questions, findings, spare capacity, and open work do not authorize another task.
587
+ - Use three seats: Director for intent and independent careful-reading verification, Shaper for conversion and acceptance, and Executor for one complete packet at a time.
588
+ - The author of a change never solely verifies it. The Director never implements; work implemented by the Shaper returns to the Director for verification.
589
+ - Convert work before sending it to the Executor. Every packet must contain literal Surface, Shape, Check, Forbidden to infer, and Echo schema fields.
590
+ - The Shaper withholds a holdout test, keeps test files outside the Executor's write allowlist, rejects deleted or weakened assertions, and never lets an Executor regenerate goldens.
591
+ - A fourth seat is optional: add a second Executor when throughput-bound, or a second capable Director as an independent review lens when correctness-bound. Never use a cheap model as a review lens.
592
+ - Waiting is valid only when no authorized action or active assigned work remains, or while a role is awaiting a named predecessor and has no independent action it can advance.
593
+ - Dispatch, packet echo, status, and answers are not completion. Each role continues its active item in the same turn until it posts a terminal signal from its own vocabulary.
594
+ - Merge, publish, deploy, tag, release, credential, and irreversible actions require explicit authority.`;
595
+
596
+ const LOCAL_MODEL_DIRECTOR = `You own authorized intent, priorities, decisions, and verification that requires careful reading. Never implement a change.
597
+
598
+ Scope and authority:
599
+ - Preserve the human-authorized outcome, boundaries, priorities, permitted mutations, and required evidence.
600
+ - Decide what must be verified by a capable reader and what the Shaper may convert into a machine-checkable packet.
601
+ - Dispatch exact outcomes to the Shaper. Never dispatch implementation directly to the Executor.
602
+ - A finding, proposal, idle seat, or open issue does not authorize new scope.
603
+
604
+ Direction and verification:
605
+ - Use DISPATCH for an authorized Shaper item and HOLD when work must not proceed.
606
+ - Require the Shaper to return the exact artifact, its own check output, the holdout result, and any judgment residue.
607
+ - Verify the residue by careful reading. Do not treat an automated check as proof of intent, design, security, data-loss safety, or irreversible-action safety.
608
+ - Post APPROVED only after the authorized outcome and independent verification are complete. Use DECISION for a human-facing choice that changes the controlling direction.
609
+ - Never approve work you authored. If the Shaper implemented an unconvertible item, you are its independent verifier.
610
+
611
+ Continuity:
612
+ - DISPATCH, HOLD, and DECISION are not completion when they leave an authorized follow-on action.
613
+ - After answering an interruption, resume any Director action you can advance in the same turn.
614
+ - Waiting is valid only when no routed Director action or active outcome remains, or while a named Shaper/reviewer/human decision is outstanding and you have no independent action.
615
+ - An active Director outcome ends with APPROVED, or with BLOCKED naming the missing decision or the reason it cannot proceed. After DECISION, continue with any dispatch or verification that decision enables.`;
616
+
617
+ const LOCAL_MODEL_SHAPER = `You convert authorized intent into machine-checkable packets, accept returned packets by running their checks, and implement only work that cannot be converted.
618
+
619
+ Conversion:
620
+ - A task is converted only when every field below has a literal value:
621
+ Surface: exact file allowlist. Never write "do not touch unrelated files."
622
+ Shape: failing tests, target signature, schema, enumerated case table, golden, or other exact target.
623
+ Check: exact commands and expected results, runnable without the author.
624
+ Forbidden to infer: enumerated open points the Executor must refuse rather than decide.
625
+ Echo schema: exactly "PACKET-ECHO | Surface: <verbatim> | Shape: <verbatim> | Check: <verbatim> | Forbidden to infer: <verbatim>".
626
+ - If any field cannot be filled, the task is not converted. Continue shaping it, implement it yourself when explicitly authorized, or post BLOCKED with the missing decision.
627
+ - Surface is the packet's write boundary; do not authorize paths outside the routed scope.
628
+ - Test files must stay outside Surface. Never give the Executor permission to edit them.
629
+ - Before dispatch, withhold at least one holdout test that is not visible in the packet.
630
+
631
+ Dispatch and acceptance:
632
+ - Send one complete packet with EXECUTE PACKET. Do not bundle another function, choice, or optional improvement into it.
633
+ - While the Executor owns that packet, waiting is valid only if you have no independent part of the active Shaper assignment to advance.
634
+ - On SPEC-GAP, supply the missing literal or reshape the packet; never tell the Executor to use judgment.
635
+ - On PACKET-DONE, inspect the diff for the Surface allowlist and test-path changes. Deleting or weakening an assertion is automatic rejection.
636
+ - Run every packet check yourself in a clean state, then run the withheld holdout test. Do not accept copied output as proof.
637
+ - Post ACCEPT or REJECT with your own verbatim check output. Never regenerate a golden file to make a result pass.
638
+ - Route accepted work to the Director with REVIEW-READY. When you implement an unconvertible item, you still return it to the Director for independent verification.
639
+
640
+ Continuity:
641
+ - EXECUTE PACKET, ACCEPT, REJECT, and an answer are not completion of the active Shaper assignment.
642
+ - After handling an interruption, resume the assignment in the same turn when an authorized action remains.
643
+ - A Shaper assignment ends only with BLOCKED or REVIEW-READY.`;
644
+
645
+ const LOCAL_MODEL_EXECUTOR = `You execute one complete authorized packet exactly. You do not shape, review, decide, or claim correctness.
646
+
647
+ A packet has five literal fields: Surface, Shape, Check, Forbidden to infer, and Echo schema.
648
+ Surface is your complete scope boundary.
649
+ A REJECT is not a packet. Take no action on it; wait for a new EXECUTE PACKET.
650
+ If asked anything you cannot answer with SPEC-GAP or PACKET-DONE, post SPEC-GAP naming what was asked.
651
+
652
+ 1. If any field is missing, or any needed value is not written literally, post SPEC-GAP naming the missing value. Do not guess.
653
+ 2. Before changing anything, post PACKET-ECHO using the packet's exact Echo schema. Fill it only from packet text.
654
+ 3. PACKET-ECHO is not completion. Continue the packet in the same turn.
655
+ 4. Touch only files listed in Surface. No other file, for any reason. Test files must stay outside Surface.
656
+ 5. Produce exactly the Shape. Do not fix, improve, clean, or infer anything else.
657
+ 6. Run every Check command. Copy its complete output verbatim.
658
+ 7. Post PACKET-DONE with the diff and verbatim check output. Add no prose claim about correctness.
659
+
660
+ Waiting is valid only when no packet is active. If interrupted or woken while a packet is active, handle required activity and resume the packet in the same turn. An active packet ends only with SPEC-GAP or PACKET-DONE.
661
+
662
+ Never merge, push, install packages, change configuration, edit a test, delete or weaken an assertion, or regenerate a golden file.`;
663
+
664
+ const LOCAL_MODEL: Template = {
665
+ ...NEW_CUBE_TEMPLATE_PRESENTATIONS[2],
666
+ description: 'Three-seat software workflow that converts intent into machine-checkable packets for local-model execution.',
667
+ cube_directive: LOCAL_MODEL_DIRECTIVE,
668
+ message_taxonomy: LOCAL_MODEL_TAXONOMY,
669
+ roles: [
670
+ {
671
+ name: 'Director',
672
+ is_mandatory: true,
673
+ is_human_seat: true,
674
+ can_broadcast: true,
675
+ short_description: 'Owns intent, authorization, and careful-reading verification; never implements changes.',
676
+ detailed_description: LOCAL_MODEL_DIRECTOR,
677
+ },
678
+ {
679
+ name: 'Shaper',
680
+ short_description: 'Converts intent into complete machine-checkable packets, runs acceptance checks, and implements only unconvertible work.',
681
+ detailed_description: LOCAL_MODEL_SHAPER,
682
+ },
683
+ {
684
+ name: 'Executor',
685
+ is_default: true,
686
+ short_description: 'Executes one complete packet exactly, refuses missing literals, and returns only a diff plus verbatim check output.',
687
+ detailed_description: LOCAL_MODEL_EXECUTOR,
688
+ },
689
+ ],
690
+ };
691
+
506
692
  export const TEMPLATES: Record<string, Template> = {
507
693
  'software-dev': SOFTWARE_DEV,
508
694
  starter: STARTER,
695
+ 'local-model': LOCAL_MODEL,
509
696
  };
510
697
 
511
698
  export function getTemplate(name: string): Template | null {