brainclaw 1.22.0 → 1.24.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.
Files changed (38) hide show
  1. package/dist/brainclaw-vscode.vsix +0 -0
  2. package/dist/cli/register-capture.js +15 -0
  3. package/dist/cli/register-cloud.js +121 -13
  4. package/dist/commands/cloud.js +534 -39
  5. package/dist/commands/loops-handlers.js +0 -1
  6. package/dist/commands/mcp-catalog.js +24 -256
  7. package/dist/commands/mcp-read-handlers.js +5 -1
  8. package/dist/commands/mcp-schemas.generated.js +811 -1
  9. package/dist/commands/mcp-write-coordination.js +16 -7
  10. package/dist/commands/mcp.js +45 -1
  11. package/dist/commands/memory-confirm.js +83 -0
  12. package/dist/commands/switch.js +24 -2
  13. package/dist/core/assignment-request-schema.js +112 -0
  14. package/dist/core/capture-schema.js +62 -0
  15. package/dist/core/claim-request-schema.js +72 -0
  16. package/dist/core/code-map/aggregate.js +36 -1
  17. package/dist/core/federation-emit.js +283 -0
  18. package/dist/core/federation-grant-transport.js +196 -0
  19. package/dist/core/federation-grant.js +223 -0
  20. package/dist/core/federation-keyring.js +39 -0
  21. package/dist/core/federation-opaque-ids.js +111 -0
  22. package/dist/core/federation-outbox-v2.js +36 -2
  23. package/dist/core/federation-pairing.js +87 -12
  24. package/dist/core/federation-pull.js +375 -0
  25. package/dist/core/federation-push.js +274 -0
  26. package/dist/core/federation-rotation.js +124 -0
  27. package/dist/core/federation-state.js +81 -6
  28. package/dist/core/sequence-request-schema.js +93 -0
  29. package/dist/core/session-request-schema.js +90 -0
  30. package/dist/core/step-request-schema.js +112 -0
  31. package/dist/core/store-resolution.js +34 -5
  32. package/dist/core/warnings.js +37 -0
  33. package/dist/facts.js +7 -7
  34. package/dist/facts.json +6 -6
  35. package/docs/design/federation-onboarding-usecases.md +254 -0
  36. package/docs/design/pairing-v3-brief.md +80 -0
  37. package/docs/integrations/mcp.md +1 -1
  38. package/package.json +1 -1
@@ -22,7 +22,7 @@ import { validateMcpField } from '../core/input-validation.js';
22
22
  import { generateCandidateIdWithLabel, saveCandidate } from '../core/candidates.js';
23
23
  import { validateLoopProjectResolution } from '../core/loops/project-resolution.js';
24
24
  import { coordinateNextActions, dispatchNextActions } from '../core/next-actions.js';
25
- import { agentValidationFailedWarning, planAlreadyAssignedWarning, pushStructuredWarning, scopeAlreadyClaimedWarning, } from '../core/warnings.js';
25
+ import { agentValidationFailedWarning, consultAutoExecuteNoOpWarning, planAlreadyAssignedWarning, pushStructuredWarning, scopeAlreadyClaimedWarning, } from '../core/warnings.js';
26
26
  import { ackMessage, getThread, hasActiveAssignment, sendMessage } from '../core/messaging.js';
27
27
  import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatcher.js';
28
28
  import { CoordinateRequestSchema } from '../core/facade-schema.js';
@@ -847,7 +847,10 @@ export async function handleBclawCoordinate(args, ctx) {
847
847
  // the target inbox(es) and never spawns. autoExecute is a no-op here, so
848
848
  // say so explicitly rather than silently ignoring a caller who set it.
849
849
  if (req.autoExecute === true) {
850
- warnings.push("autoExecute has no effect on intent='consult': consult delivers the RFC to the target inbox(es) only and never spawns an agent targets pick it up via their own bclaw_work. For real spawning use bclaw_dispatch(intent='execute') on a sequence, or intent='assign'/'review' (pln#626).");
850
+ // pln#626 phase 3le refus passe desormais par le canal STRUCTURE. Un texte libre
851
+ // vaut mieux que le silence, mais un agent ne peut pas brancher dessus ; un code
852
+ // le peut, et la next_action nomme les deux chemins qui spawnent vraiment.
853
+ pushStructuredWarning(warnings, warningDetails, consultAutoExecuteNoOpWarning());
851
854
  }
852
855
  const consultThreadId = req.threadId ?? `thread_${crypto.randomBytes(4).toString('hex')}`;
853
856
  const contacted = [];
@@ -1972,11 +1975,17 @@ export async function handleBclawLoop(args, ctx) {
1972
1975
  response: createToolErrorResponse('intent_not_exposed', "bclaw_loop(intent='open') is not exposed standalone: it creates a loop structure without dispatching any turn, so the work never starts. Use bclaw_coordinate(intent='review', open_loop=true, targetAgents=[…]) or bclaw_coordinate(intent='ideate') — they open the loop AND dispatch the first turn."),
1973
1976
  };
1974
1977
  }
1975
- // pln#562 step 4 dispatching a turn hands work to another agent; gate
1976
- // it at the same trust bar as the other dispatch surfaces.
1977
- // pln#632 — `bind` also SPAWNS real workers (it dispatches the loop's linked
1978
- // sequence), so it is gated at the same 'trusted' bar as turn-dispatch / coordinate.
1979
- if ((args?.intent === 'turn' && args?.dispatch === true) || args?.intent === 'bind') {
1978
+ // pln#632 `bind` SPAWNE de vrais workers (il dispatche la séquence liée de la
1979
+ // boucle), donc il est protégé au barreau 'trusted' comme les autres surfaces de
1980
+ // dispatch.
1981
+ //
1982
+ // LA BRANCHE `turn && dispatch === true` A ÉTÉ RETIRÉE (pln#626 phase 4). Le drapeau
1983
+ // `TurnInput.dispatch` était déclaré, transporté jusqu'à `turn()` — et JAMAIS LU. Une
1984
+ // porte de confiance sur un no-op est pire qu'absente : elle fait croire qu'un chemin
1985
+ // sensible est gardé, et un lecteur qui la voit conclut à tort que `dispatch: true` a
1986
+ // un effet. Le drapeau lui-même est supprimé dans le même commit ; garder la porte
1987
+ // aurait laissé la fausse impression intacte.
1988
+ if (args?.intent === 'bind') {
1980
1989
  const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', cwd, connectionSessionId);
1981
1990
  if (resolved.error) {
1982
1991
  return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
@@ -1026,6 +1026,50 @@ function resolveWorkspaceAnchor(cwd) {
1026
1026
  }
1027
1027
  // Read handlers moved to mcp-read-handlers.ts
1028
1028
  import { handleMcpReadToolCall } from './mcp-read-handlers.js';
1029
+ /**
1030
+ * Projection COMPACTE de `open_work` (pln#598 etape 2).
1031
+ *
1032
+ * POURQUOI TRONQUER LA DESCRIPTION, et pourquoi ICI seulement. Une description
1033
+ * d'assignation porte le BRIEF COMPLET du worker : mesure sur ce depot, des briefs de
1034
+ * dispatch a plus de 1 500 caracteres, plusieurs a la fois dans open_work. Servis en
1035
+ * entier dans une reponse dite « compacte », ils en font l'essentiel du poids — pour un
1036
+ * texte que l'appelant a ecrit lui-meme et peut relire a la demande.
1037
+ *
1038
+ * La troncature vit dans la BRANCHE COMPACTE et non dans context.ts : les autres
1039
+ * consommateurs de `open_work` — session-end, le board, les surfaces de diagnostic —
1040
+ * ont besoin du texte entier, et le tronquer a la source leur retirerait sans le dire.
1041
+ *
1042
+ * LE TEXTE N'EST PAS PERDU, il est DIFFERE : chaque entree tronquee porte de quoi le
1043
+ * recuperer. Un allegement qui supprime l'information au lieu de la deplacer n'allege
1044
+ * rien — il deplace le probleme sur l'appelant, qui devine au lieu de lire.
1045
+ */
1046
+ const OPEN_WORK_DESCRIPTION_LIMIT = 200;
1047
+ function compactOpenWork(openWork) {
1048
+ if (!openWork || typeof openWork !== 'object')
1049
+ return openWork ?? null;
1050
+ const source = openWork;
1051
+ if (!Array.isArray(source.active_assignments))
1052
+ return openWork;
1053
+ return {
1054
+ ...source,
1055
+ active_assignments: source.active_assignments.map((assignment) => {
1056
+ const description = assignment['description'];
1057
+ if (typeof description !== 'string' || description.length <= OPEN_WORK_DESCRIPTION_LIMIT) {
1058
+ return assignment;
1059
+ }
1060
+ return {
1061
+ ...assignment,
1062
+ description: `${description.slice(0, OPEN_WORK_DESCRIPTION_LIMIT)}…`,
1063
+ description_truncated: true,
1064
+ // La suite EXACTE, pas une invitation vague : l'agent doit pouvoir la rejouer
1065
+ // telle quelle. C'est la regle de src/core/next-actions.ts — une action qui ne
1066
+ // decoule pas de ce qui s'est passe est du bruit ; celle-ci decoule de la
1067
+ // troncature qu'on vient d'appliquer.
1068
+ full_text_via: { tool: 'bclaw_get', args: { entity: 'assignment', id: assignment['id'] } },
1069
+ };
1070
+ }),
1071
+ };
1072
+ }
1029
1073
  export { handleMcpReadToolCall };
1030
1074
  async function _executeMcpToolCallInner(payload) {
1031
1075
  const { name, args, cwd, connectionSessionId } = payload;
@@ -1430,7 +1474,7 @@ async function _executeMcpToolCallInner(payload) {
1430
1474
  workflow_hints: hintsPool.slice(0, 3),
1431
1475
  ...(hintsAggregate ? { workflow_hints_aggregate: hintsAggregate } : {}),
1432
1476
  claim_conflicts: contextResult.claim_conflicts ?? [],
1433
- open_work: contextResult.open_work ?? null,
1477
+ open_work: compactOpenWork(contextResult.open_work),
1434
1478
  _compact: true,
1435
1479
  _full_context_hint: 'Use bclaw_context(kind="memory") for the full payload.',
1436
1480
  };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `brainclaw confirm` — attester ou infirmer l'applicabilité d'un item de mémoire
3
+ * (pln#620 étapes 2 et 3).
4
+ *
5
+ * ── POURQUOI CETTE COMMANDE EXISTE ────────────────────────────────────────────
6
+ * `recordMemoryEvent` était déjà écrit, testé, et branché sur un schéma
7
+ * (`MemoryConfirmationEvent`) porté par les traps, décisions et contraintes. Il n'était
8
+ * appelé DEPUIS NULLE PART. Mesuré au moment du correctif : 0 item sur 471 portait la
9
+ * moindre confirmation.
10
+ *
11
+ * C'est exactement la classe de défaut que trp#1292 décrit — un cœur vert et une
12
+ * fonctionnalité inerte, parce que rien ne la tire depuis une surface qu'un agent ou un
13
+ * opérateur appelle réellement.
14
+ *
15
+ * ── CE QUE ÇA CHANGE POUR LA PRIORISATION ─────────────────────────────────────
16
+ * Une mémoire non vérifiée ne peut pas justifier une priorité P0. Un trap écrit en mars,
17
+ * jamais reconfirmé, décrit peut-être un code qui n'existe plus — la démolition de la
18
+ * fédération v1 en a périmé plusieurs d'un coup. Sans trace d'applicabilité, on ne peut
19
+ * pas distinguer « toujours vrai » de « personne n'a revérifié depuis six mois ».
20
+ *
21
+ * L'ÉVIDENCE EST OBLIGATOIRE POUR CONFIRMER, et c'est le cœur du dispositif. Une
22
+ * confirmation sans preuve — un fichier:ligne, un sha, une sortie de commande — ne serait
23
+ * qu'une opinion horodatée, et deux opinions ne valent pas mieux qu'une. Infirmer, en
24
+ * revanche, ne l'exige pas : constater qu'un symbole a disparu est en soi la preuve.
25
+ */
26
+ import { recordMemoryEvent } from '../core/memory-lifecycle.js';
27
+ import { resolveEffectiveCwd } from '../core/store-resolution.js';
28
+ import { resolveCurrentAgentName } from '../core/agent-registry.js';
29
+ const ENTITIES = ['trap', 'decision', 'constraint'];
30
+ const KINDS = ['confirm', 'infirm', 'saved_me', 'misled_me'];
31
+ export function runMemoryConfirm(options) {
32
+ const entity = options.entity;
33
+ if (!ENTITIES.includes(entity)) {
34
+ console.error(`Entité inconnue '${options.entity}'. Attendu : ${ENTITIES.join(', ')}.`);
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ const kind = options.kind;
39
+ if (!KINDS.includes(kind)) {
40
+ console.error(`Type inconnu '${options.kind}'. Attendu : ${KINDS.join(', ')}.`);
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ // CONFIRMER SANS PREUVE EST REFUSÉ. Le but de ce dispositif est qu'une priorité puisse
45
+ // s'appuyer sur une vérification ; une attestation sans pointeur vers ce qui a été
46
+ // vérifié ne porte aucune information de plus que la date.
47
+ if (kind === 'confirm' && !options.evidence) {
48
+ console.error("Une confirmation exige --evidence : un fichier:ligne, un sha de commit, ou une sortie de commande.\n"
49
+ + "Sans preuve, l'attestation n'est qu'une opinion horodatée et ne peut pas justifier une priorité.");
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ const cwd = options.cwd ?? resolveEffectiveCwd();
54
+ const by = resolveCurrentAgentName(cwd);
55
+ let result;
56
+ try {
57
+ result = recordMemoryEvent({
58
+ entity, id: options.id, kind, by,
59
+ evidence: options.evidence,
60
+ note: options.note,
61
+ cwd,
62
+ });
63
+ }
64
+ catch (err) {
65
+ console.error(err instanceof Error ? err.message : String(err));
66
+ process.exitCode = 1;
67
+ return;
68
+ }
69
+ if (options.json) {
70
+ console.log(JSON.stringify(result, null, 2));
71
+ return;
72
+ }
73
+ console.log(`✔ ${result.entity} ${result.id} — ${result.kind} par ${by}`);
74
+ if (options.evidence)
75
+ console.log(` preuve : ${options.evidence}`);
76
+ console.log(` confirmations : ${result.confirmation_count} · infirmations : ${result.infirmation_count}`
77
+ + ` · a servi : ${result.saved_me_count} · a induit en erreur : ${result.misled_me_count}`);
78
+ if (result.last_confirmed_at)
79
+ console.log(` dernière confirmation : ${result.last_confirmed_at}`);
80
+ if (result.last_infirmed_at)
81
+ console.log(` dernière infirmation : ${result.last_infirmed_at}`);
82
+ }
83
+ //# sourceMappingURL=memory-confirm.js.map
@@ -116,6 +116,7 @@ export function listAvailableProjectsForSession(cwd, sessionId) {
116
116
  : undefined;
117
117
  const projects = [];
118
118
  const seen = new Set();
119
+ const sessionDivergence = effective.session_divergence;
119
120
  const addProject = (project) => {
120
121
  const projectPath = path.resolve(project.path);
121
122
  if (seen.has(projectPath))
@@ -154,7 +155,12 @@ export function listAvailableProjectsForSession(cwd, sessionId) {
154
155
  relative_path: path.relative(wsRoot, linkPath) || '.',
155
156
  });
156
157
  }
157
- return { workspace_root: wsRoot, active_source: activeSource, projects };
158
+ return {
159
+ workspace_root: wsRoot,
160
+ active_source: activeSource,
161
+ projects,
162
+ ...(sessionDivergence ? { session_divergence: sessionDivergence } : {}),
163
+ };
158
164
  }
159
165
  export function runSwitch(projectRef, options = {}) {
160
166
  // Use real cwd, not effective cwd — switch must see the full workspace
@@ -308,8 +314,12 @@ function showCurrent(wsRoot, cwd, json) {
308
314
  }
309
315
  const rel = path.relative(wsRoot, active.path) || '.';
310
316
  const switchedBy = 'switched_by' in active ? active.switched_by : undefined;
317
+ const divergence = effective.session_divergence;
311
318
  if (json) {
312
- console.log(JSON.stringify({ active: true, ...active, relative_path: rel, scope: source }));
319
+ console.log(JSON.stringify({
320
+ active: true, ...active, relative_path: rel, scope: source,
321
+ ...(divergence ? { session_divergence: divergence } : {}),
322
+ }));
313
323
  }
314
324
  else {
315
325
  const scopeHint = source === 'session' ? ' (session-scoped)' : ' (global — all agents)';
@@ -317,6 +327,17 @@ function showCurrent(wsRoot, cwd, json) {
317
327
  console.log(` switched at: ${active.switched_at}`);
318
328
  if (switchedBy)
319
329
  console.log(` switched by: ${switchedBy}`);
330
+ if (divergence) {
331
+ // Le defaut d'origine n'etait pas qu'un lecteur se trompait : c'est que deux
332
+ // lecteurs pouvaient diverger EN SILENCE. Le dire est tout l'objet de ce bloc.
333
+ const label = divergence.session_project_name
334
+ ? `"${divergence.session_project_name}"`
335
+ : divergence.session_project_path;
336
+ console.log('');
337
+ console.log(` ⚠ Divergence : un record de session designe ${label},`);
338
+ console.log(` mais la resolution a retenu ce projet via '${divergence.resolved_via}'.`);
339
+ console.log(` Les ecritures suivent la resolution, pas la session.`);
340
+ }
320
341
  }
321
342
  }
322
343
  function listProjects(wsRoot, cwd, json) {
@@ -328,6 +349,7 @@ function listProjects(wsRoot, cwd, json) {
328
349
  workspace: result.workspace_root,
329
350
  active_source: result.active_source,
330
351
  projects: result.projects,
352
+ ...(result.session_divergence ? { session_divergence: result.session_divergence } : {}),
331
353
  }, null, 2));
332
354
  return;
333
355
  }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Schémas zod des entrées de la famille ASSIGNMENT — `bclaw_assignment_update`,
3
+ * `bclaw_assignment_action`, `bclaw_assignment_events` (pln#599 batch 2, cinquième famille).
4
+ *
5
+ * ── LA FAMILLE LA PLUS PROFONDE MIGRÉE JUSQU'ICI ──────────────────────────────
6
+ * `assignment_update` porte deux objets imbriqués qui ont LEURS PROPRES `required` :
7
+ * l'item d'`artifacts` (`type`+`ref`) et `action_required` (`kind`+`title`+`prompt`).
8
+ * Ces requis internes font partie du contrat publié et doivent survivre à la migration —
9
+ * c'est le pendant profond du piège de la famille séquence, où un requis de premier niveau
10
+ * était passé optionnel sans que rien d'autre que le fingerprint ne le voie.
11
+ *
12
+ * ── DEUX OBJETS DÉLIBÉRÉMENT LIBRES ───────────────────────────────────────────
13
+ * `payload` et `response_schema` sont publiés comme `{ type: 'object' }` NU : aucune
14
+ * propriété, aucune contrainte. Ce sont des sacs de données dont la forme appartient à
15
+ * l'appelant.
16
+ *
17
+ * Les quatre constructions candidates ont été MESURÉES, pas supposées :
18
+ * z.object({}) -> properties:{} + additionalProperties:false
19
+ * (un objet qui n'accepte plus RIEN — le
20
+ * durcissement le plus radical possible, sur les
21
+ * deux champs les plus ouverts de la famille)
22
+ * z.looseObject({}) -> properties:{} + additionalProperties:{}
23
+ * z.record(z.string(), z.unknown()) -> + propertyNames + additionalProperties
24
+ * z.unknown().meta({ type:'object' }) -> { type: 'object' } <- seul exact
25
+ * La comparaison à la version manuelle a rejeté les trois premiers.
26
+ *
27
+ * ── CE QUI GARDE SON ENUM, ET POURQUOI ────────────────────────────────────────
28
+ * `status`, `outcome` et `action_required.kind` en avaient déjà un dans la version
29
+ * manuelle : le leur retirer serait l'assouplissement symétrique du durcissement qu'on
30
+ * évite ailleurs. En revanche `artifacts[].type` et `eventType` restent des chaînes
31
+ * libres bien que leurs valeurs soient énumérées en description — les resserrer serait un
32
+ * rejet nouveau.
33
+ */
34
+ import { z } from 'zod';
35
+ /** Identité de l'appelant — commune à toutes les familles migrées. */
36
+ const CallerIdentity = {
37
+ agent: z.string().describe('Agent name.').optional(),
38
+ agentId: z.string().describe('Registered agent id.').optional(),
39
+ };
40
+ /** Item d'artefact. `type` et `ref` sont REQUIS — requis IMBRIQUÉ, à préserver. */
41
+ const ArtifactSchema = z.object({
42
+ type: z.string().describe('Artifact type: commit, branch, file, pr, test_result.'),
43
+ ref: z.string().describe('Reference: SHA, branch name, file path, PR URL.'),
44
+ description: z.string().describe('Optional description.').optional(),
45
+ });
46
+ /** ActionRequired. `kind`+`title`+`prompt` REQUIS — second requis imbriqué. */
47
+ const ActionRequiredSchema = z
48
+ .object({
49
+ kind: z
50
+ .enum(['approval', 'user_input', 'clarification', 'plan_approval'])
51
+ .describe('Kind of action needed.'),
52
+ title: z.string().describe('Short title shown to supervisors/UI.'),
53
+ prompt: z.string().describe('Question or approval prompt to answer.'),
54
+ options: z.array(z.string()).describe('Optional answer choices.').optional(),
55
+ // Objet NU. Mesure des candidats plutôt que supposition :
56
+ // z.object({}) -> properties:{} + additionalProperties:false (n'accepte RIEN)
57
+ // z.looseObject({}) -> properties:{} + additionalProperties:{} (accepte, mais ajoute
58
+ // deux clés que la version publiée n'avait pas)
59
+ // z.record(...) -> ajoute propertyNames + additionalProperties
60
+ // z.unknown().meta({ type: 'object' }) -> { type: 'object' } exactement.
61
+ // Seul le dernier reproduit la surface manuelle.
62
+ response_schema: z
63
+ .unknown()
64
+ .meta({ type: 'object' })
65
+ .describe('Optional structured response schema hint.')
66
+ .optional(),
67
+ tags: z.array(z.string()).describe('Optional tags.').optional(),
68
+ })
69
+ .describe('Optional ActionRequired payload when status=blocked. Lets the worker request approval, user input, or clarification before resuming.');
70
+ export const AssignmentUpdateRequestSchema = z.object({
71
+ assignment_id: z.string().describe('Assignment ID from the dispatch brief (asgn_xxx).'),
72
+ status: z
73
+ .enum(['accepted', 'started', 'progress', 'completed', 'failed', 'blocked'])
74
+ .describe('Lifecycle status to report.'),
75
+ message: z.string().describe('Human-readable status message or progress note.').optional(),
76
+ artifacts: z
77
+ .array(ArtifactSchema)
78
+ .describe('Artifacts produced. Most useful for completed status.')
79
+ .optional(),
80
+ error_message: z.string().describe('Error details (for failed status).').optional(),
81
+ blocker: z.string().describe('Blocker description (for blocked status).').optional(),
82
+ action_required: ActionRequiredSchema.optional(),
83
+ ...CallerIdentity,
84
+ });
85
+ export const AssignmentActionRequestSchema = z.object({
86
+ action_id: z.string().describe('ActionRequired ID (act_xxx).'),
87
+ outcome: z
88
+ .enum(['resolved', 'rejected', 'cancelled'])
89
+ .describe('How the supervisor resolves the pending action.'),
90
+ text: z.string().describe('Human-readable response or rationale.').optional(),
91
+ // Objet NU, même raison et même construction que response_schema.
92
+ payload: z
93
+ .unknown()
94
+ .meta({ type: 'object' })
95
+ .describe('Optional structured response payload.')
96
+ .optional(),
97
+ agent: z.string().describe('Supervisor/agent responding to the action.').optional(),
98
+ agentId: z.string().describe('Registered agent id.').optional(),
99
+ });
100
+ export const AssignmentEventsRequestSchema = z.object({
101
+ assignmentId: z.string().describe('Filter by linked assignment ID.').optional(),
102
+ runId: z.string().describe('Filter by linked run ID.').optional(),
103
+ claimId: z.string().describe('Filter by linked claim ID.').optional(),
104
+ sessionId: z.string().describe('Filter by runtime session ID.').optional(),
105
+ agent: z.string().describe('Filter by agent name.').optional(),
106
+ eventType: z.string().describe('Filter by runtime event type.').optional(),
107
+ id: z.string().describe('Get a single runtime event by ID.').optional(),
108
+ limit: z.number().describe('Maximum number of events to return (default: 20).').optional(),
109
+ offset: z.number().describe('Number of events to skip (for pagination).').optional(),
110
+ compact: z.boolean().describe('Return only key fields to reduce output size.').optional(),
111
+ });
112
+ //# sourceMappingURL=assignment-request-schema.js.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Schémas zod des entrées de la famille CAPTURE — `bclaw_write_note` et
3
+ * `bclaw_quick_capture` (pln#599 batch 1, première famille).
4
+ *
5
+ * ── POURQUOI UNE FAMILLE ENTIÈRE, ET PAS TROIS OUTILS AU HASARD ───────────────
6
+ * Une source unique de vérité n'en est une que si elle couvre un ensemble COHÉRENT. Un
7
+ * catalogue à moitié dérivé double la maintenance — deux mécanismes à comprendre, deux
8
+ * endroits où corriger — sans donner le bénéfice, qui est de ne plus pouvoir faire
9
+ * diverger le schéma publié de la validation réelle. Le découpage par famille garantit
10
+ * qu'aucune surface ne reste à cheval sur les deux.
11
+ *
12
+ * ── LA CONTRAINTE QUI GOUVERNE CE FICHIER : FINGERPRINT INCHANGÉ ──────────────
13
+ * Le JSON Schema dérivé de ces objets doit être IDENTIQUE à celui écrit à la main qu'il
14
+ * remplace. Un champ optionnel devenu requis, un `enum` perdu, une `description`
15
+ * reformulée : chacun est une modification de la surface publique que des agents ont déjà
16
+ * apprise. La migration doit être invisible côté fil ; c'est ce que le test de parité
17
+ * vérifie.
18
+ *
19
+ * D'où des choix qui paraîtraient étranges hors de ce contexte — `visibility` reste une
20
+ * chaîne libre et non un enum, `crossProject` et `cross_project` coexistent. Ce ne sont
21
+ * pas des approximations : ce sont les schémas ACTUELS, et les resserrer ici mélangerait
22
+ * une migration mécanique avec un changement de contrat.
23
+ */
24
+ import { z } from 'zod';
25
+ /** Identité de l'appelant — présente à l'identique sur les deux outils de la famille. */
26
+ const CallerIdentity = {
27
+ agent: z.string().describe('Agent name.').optional(),
28
+ agentId: z.string().describe('Registered agent id.').optional(),
29
+ };
30
+ export const WriteNoteRequestSchema = z.object({
31
+ text: z.string().describe('Note content.'),
32
+ ...CallerIdentity,
33
+ tags: z.array(z.string()).describe('Optional tags.').optional(),
34
+ // Chaîne libre et NON un enum : c'est l'état actuel du schéma publié. Le resserrer
35
+ // serait un changement de contrat déguisé en migration.
36
+ visibility: z.string().describe('Visibility: shared, machine, private.').optional(),
37
+ ttl: z.string().describe('Optional TTL: 30m, 2h, 7d.').optional(),
38
+ autoReflect: z
39
+ .boolean()
40
+ .describe('Attempt to reflect the runtime note into durable memory immediately.')
41
+ .optional(),
42
+ crossProject: z
43
+ .string()
44
+ .describe('Push note to a linked project (name or path). Requires role: publisher in cross_project_links config.')
45
+ .optional(),
46
+ // L'alias snake_case coexiste avec le camelCase. Le retirer casserait les appelants qui
47
+ // l'utilisent ; la migration ne doit rien retirer.
48
+ cross_project: z.string().describe('Snake_case alias of crossProject.').optional(),
49
+ });
50
+ export const QuickCaptureRequestSchema = z.object({
51
+ text: z.string().describe('Free-form capture text.'),
52
+ type: z
53
+ .enum(['decision', 'trap', 'constraint', 'note'])
54
+ .describe('Caller-asserted classification. Strongly recommended — the calling agent knows the nature of the capture better than keyword heuristics (cnd_abe61d68: 18 false contradiction positives on a review summary).')
55
+ .optional(),
56
+ context: z
57
+ .string()
58
+ .describe('Optional file/path/scope context to associate with the capture.')
59
+ .optional(),
60
+ ...CallerIdentity,
61
+ });
62
+ //# sourceMappingURL=capture-schema.js.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Schémas zod des entrées de la famille CLAIM — `bclaw_claim` et `bclaw_release_claim`
3
+ * (pln#599 batch 2, deuxième famille composite).
4
+ *
5
+ * ── CONTRAINTE, INCHANGÉE DEPUIS LA FAMILLE CAPTURE ───────────────────────────
6
+ * Le JSON Schema produit doit être byte-identique à celui écrit à la main : le fingerprint
7
+ * de gouvernance (tests/unit/mcp-governance.test.ts) ne doit pas bouger. Une migration qui
8
+ * déplace le fingerprint n'est plus une migration, c'est un changement de surface publique.
9
+ *
10
+ * ── LES DEUX PIÈGES PAYÉS COMPTANT SUR #220/#221, REPRODUITS ICI EN GARDE ──────
11
+ * 1. zod émet `additionalProperties: false` d'office. Le laisser DURCIT le contrat : un
12
+ * client passant une clé inconnue était ACCEPTÉ (clé ignorée) et se ferait désormais
13
+ * rejeter. Le générateur le retire — À LA RACINE UNIQUEMENT, cf. OPEN_SCHEMAS.
14
+ * 2. Un champ REQUIS dans la version manuelle doit le rester. `rank` était passé optionnel
15
+ * par inadvertance sur la famille séquence : assouplissement du contrat, attrapé par le
16
+ * seul fingerprint. Ici les requis sont `scope`+`description` (claim) et `id` (release).
17
+ *
18
+ * Ni l'un ni l'autre n'avait été vu par mes propres vérifications, ni par le snapshot du
19
+ * registre CLI — qui mesure quelque chose de plus faible et donne une fausse assurance.
20
+ *
21
+ * ── CE QUI N'EST PAS RESSERRÉ, DÉLIBÉRÉMENT ───────────────────────────────────
22
+ * `store` et `planStatus` restent des chaînes libres et non des enums, bien que leurs
23
+ * valeurs utiles soient énumérées dans leur description. Les resserrer mérite sa propre
24
+ * décision : ce serait un rejet nouveau sur des appels aujourd'hui acceptés.
25
+ * `handoffMode` garde en revanche son enum, parce qu'il en avait DÉJÀ un.
26
+ */
27
+ import { z } from 'zod';
28
+ /** Identité de l'appelant — commune à la famille, comme pour capture et séquence. */
29
+ const CallerIdentity = {
30
+ agent: z.string().describe('Agent or person name.').optional(),
31
+ agentId: z.string().describe('Registered agent id.').optional(),
32
+ };
33
+ export const ClaimRequestSchema = z.object({
34
+ scope: z.string().describe('Scope being claimed.'),
35
+ description: z.string().describe('Description of the work.'),
36
+ ...CallerIdentity,
37
+ planId: z.string().describe('Optional linked plan item ID.').optional(),
38
+ project: z
39
+ .string()
40
+ .describe('Project name or path. Use this when working on a project different from the MCP server workspace (e.g. CLI agents in a different directory).')
41
+ .optional(),
42
+ // Chaîne libre, PAS un enum : les trois niveaux sont documentés mais la valeur reste
43
+ // ouverte côté schéma publié.
44
+ store: z.string().describe('Target store level: local (default), repo, workspace.').optional(),
45
+ worktreeBranch: z
46
+ .string()
47
+ .describe('Branch name for the worktree. Defaults to feat/<scope-slug>.')
48
+ .optional(),
49
+ worktree: z
50
+ .boolean()
51
+ .describe('Whether to create an isolated git worktree (default true). Pass false for an advisory-only lock with no worktree (trp#431) — for in-place work in the main tree.')
52
+ .optional(),
53
+ advisory: z
54
+ .boolean()
55
+ .describe('Alias for worktree:false — advisory-only lock with no worktree (trp#431).')
56
+ .optional(),
57
+ // Enum CONSERVÉ : il existait déjà dans le schéma manuel. Le retirer serait
58
+ // l'assouplissement symétrique du durcissement qu'on évite ailleurs.
59
+ handoffMode: z
60
+ .enum(['self-commit', 'integrator'])
61
+ .describe('Handoff mode: "self-commit" (worker commits+merges) or "integrator" (another agent reviews+merges). Default: self-commit.')
62
+ .optional(),
63
+ });
64
+ export const ReleaseClaimRequestSchema = z.object({
65
+ id: z.string().describe('Claim ID to release.'),
66
+ planStatus: z.string().describe('Optional: update linked plan status.').optional(),
67
+ coordinator_override: z
68
+ .boolean()
69
+ .describe('Opt-in override for a trusted+ caller releasing a claim they do NOT own (cross-agent teardown, ghost-claim cleanup). Rejected for contributor-level callers; audited when used. trp#928.')
70
+ .optional(),
71
+ });
72
+ //# sourceMappingURL=claim-request-schema.js.map
@@ -468,6 +468,41 @@ export function aggregateBrief(target, limit, resolved, currentHead, memoryReade
468
468
  ...(capped[i].cross_package ? { cross_package: true } : {}),
469
469
  ...(capped[i].local ? { local: true } : {}),
470
470
  }));
471
- return { target, suggested_files_to_read: suggested, related_memory: related, freshness_badge: mergeBadges(perStore.map((p) => ({ ref: p.ref, badge: p.badge, hasIndex: p.r.hasIndex }))) };
471
+ return {
472
+ target,
473
+ suggested_files_to_read: suggested,
474
+ related_memory: summarizeRelatedMemory(related),
475
+ freshness_badge: mergeBadges(perStore.map((p) => ({ ref: p.ref, badge: p.badge, hasIndex: p.r.hasIndex }))),
476
+ };
477
+ }
478
+ /**
479
+ * Resume la memoire liee servie par `code_brief` (pln#598 etape 3).
480
+ *
481
+ * POURQUOI. Un trap ou une decision de ce depot depasse regulierement 2 000 caracteres —
482
+ * plusieurs des textes ecrits pendant la refonte federation v2 en font le double. Un
483
+ * `code_brief` qui attache trois d'entre eux sert des milliers de caracteres avant meme
484
+ * que l'agent n'ait ouvert un fichier, pour un contenu qu'il ne lira peut-etre pas.
485
+ *
486
+ * LE TEXTE N'EST PAS PERDU, IL EST DIFFERE. Chaque entree raccourcie porte l'appel EXACT
487
+ * qui rend l'integralite. Un allegement qui supprime l'information au lieu de la deplacer
488
+ * force l'agent a deviner — et deviner sur un trap est precisement ce que les traps
489
+ * existent pour eviter.
490
+ *
491
+ * `id`, `kind`, `tags` et `related_paths` restent ENTIERS : ce sont eux qui permettent de
492
+ * decider s'il vaut la peine d'aller lire. Les tronquer ferait economiser des octets sur
493
+ * la seule partie qui sert a trier.
494
+ */
495
+ const RELATED_MEMORY_TEXT_LIMIT = 300;
496
+ export function summarizeRelatedMemory(items) {
497
+ return items.map((item) => {
498
+ if (typeof item.text !== 'string' || item.text.length <= RELATED_MEMORY_TEXT_LIMIT)
499
+ return item;
500
+ return {
501
+ ...item,
502
+ text: `${item.text.slice(0, RELATED_MEMORY_TEXT_LIMIT)}…`,
503
+ text_truncated: true,
504
+ full_text_via: { tool: 'bclaw_get', args: { entity: item.kind, id: item.id } },
505
+ };
506
+ });
472
507
  }
473
508
  //# sourceMappingURL=aggregate.js.map