perchai-cli 2.4.33 → 2.4.35

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 (2) hide show
  1. package/dist/perch.mjs +567 -92
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -76218,6 +76218,7 @@ function getToolDisplayName(toolName) {
76218
76218
  var NON_MODULE_TOOL_OWNERS, TOOL_RISK, TOOL_DISPLAY_NAMES;
76219
76219
  var init_catalog = __esm({
76220
76220
  "features/perchTerminal/runtime/toolSystem/catalog.ts"() {
76221
+ "use strict";
76221
76222
  init_toolNames();
76222
76223
  NON_MODULE_TOOL_OWNERS = {
76223
76224
  [TOOL_NAMES.listSources]: "lane",
@@ -134940,9 +134941,29 @@ function validateArgs(name, args) {
134940
134941
  if (args.reason !== void 0 && typeof args.reason !== "string")
134941
134942
  return "task_stop.reason must be a string.";
134942
134943
  return null;
134943
- case TOOL_NAMES.spawnWorker:
134944
- if (typeof args.workerId !== "string" || !args.workerId.trim()) {
134945
- return "spawn_worker.workerId must be a non-empty string.";
134944
+ case TOOL_NAMES.spawnWorker: {
134945
+ const hasWorkerId = typeof args.workerId === "string" && args.workerId.trim().length > 0;
134946
+ const customRaw = args.custom;
134947
+ if (!hasWorkerId && customRaw === void 0) {
134948
+ return "spawn_worker requires workerId (registered worker) or custom { displayName, allowedTools, ... } (ad-hoc worker).";
134949
+ }
134950
+ if (customRaw !== void 0) {
134951
+ if (!customRaw || typeof customRaw !== "object" || Array.isArray(customRaw)) {
134952
+ return "spawn_worker.custom must be an object.";
134953
+ }
134954
+ const custom = customRaw;
134955
+ if (typeof custom.displayName !== "string" || !custom.displayName.trim()) {
134956
+ return "spawn_worker.custom.displayName must be a non-empty string.";
134957
+ }
134958
+ if (custom.allowedTools !== void 0 && (!Array.isArray(custom.allowedTools) || !custom.allowedTools.every((tool) => typeof tool === "string"))) {
134959
+ return "spawn_worker.custom.allowedTools must be an array of tool name strings.";
134960
+ }
134961
+ if (custom.writesWorkspace !== void 0 && typeof custom.writesWorkspace !== "boolean") {
134962
+ return "spawn_worker.custom.writesWorkspace must be a boolean.";
134963
+ }
134964
+ if (custom.outputContract !== void 0 && typeof custom.outputContract !== "string") {
134965
+ return "spawn_worker.custom.outputContract must be a string.";
134966
+ }
134946
134967
  }
134947
134968
  if (typeof args.objective !== "string" || !args.objective.trim())
134948
134969
  return "spawn_worker.objective must be a non-empty string.";
@@ -134962,6 +134983,7 @@ function validateArgs(name, args) {
134962
134983
  if (args.maxIterations !== void 0 && !isPositiveNumber(args.maxIterations))
134963
134984
  return "spawn_worker.maxIterations must be a positive number.";
134964
134985
  return null;
134986
+ }
134965
134987
  case TOOL_NAMES.dispatchAgent: {
134966
134988
  if (Array.isArray(args.tasks)) {
134967
134989
  if (args.tasks.length === 0)
@@ -135928,13 +135950,38 @@ function getDesktopToolDefinitions() {
135928
135950
  type: "function",
135929
135951
  function: {
135930
135952
  name: TOOL_NAMES.spawnWorker,
135931
- description: "Spawn one bounded specialist sub-agent. Use simple capability-shaped workers when possible: source_reader for scoped local-file evidence, math_checker for exact sandbox totals/reconciliation, contradiction_verifier for conflict checks, financial_writer/general_writer for narrative from provided facts. Best for independent work where the main loop only needs the conclusion. Do not use context-only managed analysts as raw-folder readers; give them pre-read JSON. The main loop remains owner of decisions, writing, and real actions. Prefer dispatch_agent for parallel batches. Do not repeat source_reader after it returns a DONE/PARTIAL manifest; advance to math_checker or verification.",
135953
+ description: "Spawn one bounded specialist sub-agent. Use simple capability-shaped workers when possible: source_reader for scoped local-file evidence, math_checker for exact sandbox totals/reconciliation, contradiction_verifier for conflict checks, financial_writer/general_writer for narrative from provided facts. When no registered worker fits, pass custom{} instead of workerId to spawn a one-off ad-hoc worker with its own name and minimal tool scope. Best for independent work where the main loop only needs the conclusion. Do not use context-only managed analysts as raw-folder readers; give them pre-read JSON. The main loop remains owner of decisions, writing, and real actions. Prefer dispatch_agent for parallel batches. Do not repeat source_reader after it returns a DONE/PARTIAL manifest; advance to math_checker or verification.",
135932
135954
  parameters: {
135933
135955
  type: "object",
135934
135956
  properties: {
135935
135957
  workerId: {
135936
135958
  type: "string",
135937
- description: "Registered worker or role id. Simple roles: source_reader, math_checker, contradiction_verifier, financial_writer, general_writer. Quill roles: legal_source_scout, legal_case_scout, source_verifier, doc_writer, email_sender, calendar_scheduler."
135959
+ description: "Registered worker or role id. Simple roles: source_reader, math_checker, contradiction_verifier, financial_writer, general_writer. Quill roles: legal_source_scout, legal_case_scout, source_verifier, doc_writer, email_sender, calendar_scheduler. Omit when passing custom."
135960
+ },
135961
+ custom: {
135962
+ type: "object",
135963
+ description: "Inline ad-hoc worker spec, used INSTEAD of workerId when no registered worker fits. The worker is run-scoped: registered for this spawn, unregistered after. Orchestration tools are never granted; write tools require writesWorkspace=true.",
135964
+ properties: {
135965
+ displayName: {
135966
+ type: "string",
135967
+ description: 'Short task-specific role name, e.g. "Changelog Summarizer".'
135968
+ },
135969
+ allowedTools: {
135970
+ type: "array",
135971
+ items: { type: "string" },
135972
+ description: "Minimal tool names this worker needs. Unknown/forbidden tools are dropped."
135973
+ },
135974
+ writesWorkspace: {
135975
+ type: "boolean",
135976
+ description: "True only if the worker must edit files inside the workspace root."
135977
+ },
135978
+ outputContract: {
135979
+ type: "string",
135980
+ description: "Short contract for the worker's structured output."
135981
+ }
135982
+ },
135983
+ required: ["displayName"],
135984
+ additionalProperties: false
135938
135985
  },
135939
135986
  objective: {
135940
135987
  type: "string",
@@ -135963,7 +136010,7 @@ function getDesktopToolDefinitions() {
135963
136010
  description: "Optional child loop cap. Defaults to the worker manifest cap."
135964
136011
  }
135965
136012
  },
135966
- required: ["workerId", "objective"],
136013
+ required: ["objective"],
135967
136014
  additionalProperties: false
135968
136015
  }
135969
136016
  }
@@ -205266,7 +205313,159 @@ var init_sendWorkerMessage2 = __esm({
205266
205313
  }
205267
205314
  });
205268
205315
 
205316
+ // features/perchTerminal/runtime/workers/adhocManifest.ts
205317
+ function buildAdhocWorkerManifest(spec) {
205318
+ const displayName = spec.displayName.replace(/\s+/g, " ").trim().slice(0, MAX_NAME_CHARS) || "Ad-hoc Worker";
205319
+ const writesWorkspace = spec.writesWorkspace === true;
205320
+ const outputContract = (spec.outputContract ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_CONTRACT_CHARS) || DEFAULT_OUTPUT_CONTRACT;
205321
+ const requested = [...new Set(spec.allowedTools ?? [])].filter(
205322
+ (tool) => typeof tool === "string" && tool.trim().length > 0
205323
+ );
205324
+ const droppedTools = [];
205325
+ const allowedTools = requested.filter((tool) => {
205326
+ if (ADHOC_FORBIDDEN_TOOLS.includes(tool)) {
205327
+ droppedTools.push(tool);
205328
+ return false;
205329
+ }
205330
+ if (!writesWorkspace && ADHOC_WRITE_TOOLS.has(tool)) {
205331
+ droppedTools.push(tool);
205332
+ return false;
205333
+ }
205334
+ return true;
205335
+ });
205336
+ const maxIterations = clampInt(spec.maxIterations, 1, ADHOC_MAX_ITERATIONS, 6);
205337
+ const workerId = uniqueAdhocWorkerId(displayName);
205338
+ const manifest = {
205339
+ workerId,
205340
+ name: displayName,
205341
+ description: `Ad-hoc worker (run-scoped): ${displayName}.`,
205342
+ lane: writesWorkspace ? "code_worker" : "fast_worker",
205343
+ systemPrompt: [
205344
+ `You are ${displayName}, one bounded ad-hoc worker spawned for a single objective.`,
205345
+ "Work only on the objective you are given. Never delegate, never spawn other workers, never start suites.",
205346
+ `Respect the output contract exactly: ${outputContract}.`,
205347
+ writesWorkspace ? "You may edit files, but only inside the active workspace root, and only edits the objective requires." : "You are read-only: never write, move, or delete files."
205348
+ ].join("\n"),
205349
+ allowedTools,
205350
+ maxIterations,
205351
+ callableAgents: [],
205352
+ outputContract
205353
+ };
205354
+ return { manifest, droppedTools };
205355
+ }
205356
+ function uniqueAdhocWorkerId(displayName) {
205357
+ const slug = displayName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 24) || "worker";
205358
+ const base = `adhoc_${Date.now().toString(36)}_${slug}`;
205359
+ let candidate = base;
205360
+ let bump = 0;
205361
+ while (getWorkerManifest(candidate)) {
205362
+ bump += 1;
205363
+ candidate = `${base}_${bump}`;
205364
+ }
205365
+ return candidate;
205366
+ }
205367
+ function clampInt(value, min2, max2, fallback) {
205368
+ const num = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
205369
+ return Math.min(Math.max(num, min2), max2);
205370
+ }
205371
+ var ADHOC_FORBIDDEN_TOOLS, ADHOC_WRITE_TOOLS, ADHOC_MAX_ITERATIONS, MAX_NAME_CHARS, MAX_CONTRACT_CHARS, DEFAULT_OUTPUT_CONTRACT;
205372
+ var init_adhocManifest = __esm({
205373
+ "features/perchTerminal/runtime/workers/adhocManifest.ts"() {
205374
+ "use strict";
205375
+ init_toolNames();
205376
+ init_registry();
205377
+ init_types();
205378
+ ADHOC_FORBIDDEN_TOOLS = [
205379
+ ...WORKER_DISALLOWED_TOOLS,
205380
+ TOOL_NAMES.dispatchAgent,
205381
+ TOOL_NAMES.sendWorkerMessage,
205382
+ TOOL_NAMES.taskStop,
205383
+ TOOL_NAMES.runSuite,
205384
+ TOOL_NAMES.listSuiteCatalog,
205385
+ TOOL_NAMES.proposeSuitePlan,
205386
+ TOOL_NAMES.executeSuitePlan,
205387
+ TOOL_NAMES.proposeWork,
205388
+ TOOL_NAMES.executeWork,
205389
+ TOOL_NAMES.proposePlan
205390
+ ];
205391
+ ADHOC_WRITE_TOOLS = /* @__PURE__ */ new Set([
205392
+ TOOL_NAMES.writeLocalFile,
205393
+ TOOL_NAMES.editLocalFile,
205394
+ TOOL_NAMES.deleteLocalFile,
205395
+ TOOL_NAMES.moveLocalFile,
205396
+ TOOL_NAMES.copyLocalFile,
205397
+ TOOL_NAMES.createDirectory
205398
+ ]);
205399
+ ADHOC_MAX_ITERATIONS = 10;
205400
+ MAX_NAME_CHARS = 40;
205401
+ MAX_CONTRACT_CHARS = 240;
205402
+ DEFAULT_OUTPUT_CONTRACT = "Return a short structured summary of what was found or done.";
205403
+ }
205404
+ });
205405
+
205406
+ // features/perchTerminal/runtime/flock/flockNicknames.ts
205407
+ function seedFromFlockId(flockId) {
205408
+ let hash = 2166136261;
205409
+ for (let i = 0; i < flockId.length; i++) {
205410
+ hash ^= flockId.charCodeAt(i);
205411
+ hash = Math.imul(hash, 16777619);
205412
+ }
205413
+ return hash >>> 0;
205414
+ }
205415
+ function flockNicknameRotation(flockId) {
205416
+ const names = [...FLOCK_NICKNAMES];
205417
+ let state = seedFromFlockId(flockId) || 1;
205418
+ const nextRandom = () => {
205419
+ state = Math.imul(state, 1664525) + 1013904223 >>> 0;
205420
+ return state / 4294967296;
205421
+ };
205422
+ for (let i = names.length - 1; i > 0; i--) {
205423
+ const j = Math.floor(nextRandom() * (i + 1));
205424
+ [names[i], names[j]] = [names[j], names[i]];
205425
+ }
205426
+ return names;
205427
+ }
205428
+ function flockNicknameFor(flockId, index) {
205429
+ const rotation = flockNicknameRotation(flockId);
205430
+ return rotation[index % rotation.length];
205431
+ }
205432
+ var FLOCK_NICKNAMES;
205433
+ var init_flockNicknames = __esm({
205434
+ "features/perchTerminal/runtime/flock/flockNicknames.ts"() {
205435
+ "use strict";
205436
+ FLOCK_NICKNAMES = [
205437
+ "Little Dum Dum",
205438
+ "Molly",
205439
+ "Boomer",
205440
+ "Shawarma",
205441
+ "Curie",
205442
+ "Eagle",
205443
+ "Noether",
205444
+ "Wonky",
205445
+ "Lovelace",
205446
+ "Bell",
205447
+ "Turing",
205448
+ "Faraday",
205449
+ "Biscuit",
205450
+ "Ice Cream",
205451
+ "Minsky"
205452
+ ];
205453
+ }
205454
+ });
205455
+
205269
205456
  // features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts
205457
+ function parseAdhocSpec(raw) {
205458
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
205459
+ const record = raw;
205460
+ if (typeof record.displayName !== "string" || !record.displayName.trim()) return null;
205461
+ return {
205462
+ displayName: record.displayName,
205463
+ allowedTools: Array.isArray(record.allowedTools) ? record.allowedTools.filter((tool) => typeof tool === "string") : void 0,
205464
+ writesWorkspace: record.writesWorkspace === true,
205465
+ maxIterations: typeof record.maxIterations === "number" ? record.maxIterations : void 0,
205466
+ outputContract: typeof record.outputContract === "string" ? record.outputContract : void 0
205467
+ };
205468
+ }
205270
205469
  var spawnWorkerTool;
205271
205470
  var init_spawnWorker = __esm({
205272
205471
  "features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts"() {
@@ -205276,12 +205475,53 @@ var init_spawnWorker = __esm({
205276
205475
  init_agentDispatch();
205277
205476
  init_localScope();
205278
205477
  init_toolNames();
205478
+ init_adhocManifest();
205479
+ init_registry();
205480
+ init_workerManifest();
205481
+ init_flockNicknames();
205279
205482
  spawnWorkerTool = {
205280
205483
  name: TOOL_NAMES.spawnWorker,
205281
205484
  classification: { native: false },
205282
205485
  handler: async (args, ctx) => {
205283
- const workerId = String(args.workerId ?? "");
205486
+ let workerId = String(args.workerId ?? "");
205284
205487
  const objective = String(args.objective ?? "");
205488
+ let adhocManifestId = null;
205489
+ let adhocNickname = null;
205490
+ const customSpec = parseAdhocSpec(args.custom);
205491
+ if (!workerId && args.custom !== void 0 && !customSpec) {
205492
+ return {
205493
+ ok: false,
205494
+ workerId: "",
205495
+ summary: "spawn_worker custom spec is invalid: custom.displayName (non-empty string) is required.",
205496
+ errorCode: "adhoc_spec_invalid"
205497
+ };
205498
+ }
205499
+ if (workerId && customSpec && !resolveWorkerManifest(workerId)) {
205500
+ workerId = "";
205501
+ }
205502
+ if (!workerId && customSpec) {
205503
+ const { manifest, droppedTools } = buildAdhocWorkerManifest(customSpec);
205504
+ registerWorkerManifest(manifest);
205505
+ adhocManifestId = manifest.workerId;
205506
+ adhocNickname = flockNicknameFor(manifest.workerId, 0);
205507
+ workerId = manifest.workerId;
205508
+ if (droppedTools.length > 0 && ctx.onEvent) {
205509
+ ctx.onEvent({
205510
+ type: "diagnostic",
205511
+ code: "adhoc_worker_tools_dropped",
205512
+ message: `Ad-hoc worker "${manifest.name}" was not granted: ${droppedTools.join(", ")} (orchestration or write tools outside its scope).`,
205513
+ ts: (/* @__PURE__ */ new Date()).toISOString()
205514
+ });
205515
+ }
205516
+ }
205517
+ if (!workerId) {
205518
+ return {
205519
+ ok: false,
205520
+ workerId: "",
205521
+ summary: "spawn_worker requires either workerId (registered worker) or custom { displayName, allowedTools, ... } for an ad-hoc worker.",
205522
+ errorCode: "worker_id_missing"
205523
+ };
205524
+ }
205285
205525
  const context = typeof args.context === "string" || typeof args.context === "object" && args.context !== null && !Array.isArray(args.context) ? args.context : void 0;
205286
205526
  const lane = typeof args.lane === "string" ? args.lane : void 0;
205287
205527
  const maxIterations = typeof args.maxIterations === "number" ? args.maxIterations : void 0;
@@ -205312,26 +205552,31 @@ var init_spawnWorker = __esm({
205312
205552
  Proceed with the available context. If a missing fact is essential, ask the user for that fact instead of stopping.` : "",
205313
205553
  capabilityNote ? `Capability note: ${capabilityNote}` : ""
205314
205554
  ].filter(Boolean).join("\n\n");
205315
- return spawnWorker(
205316
- { workerId, objective: workerObjective, context: enrichedContext, lane, maxIterations },
205317
- {
205318
- workspaceRoot: effectiveWorkspaceRoot(ctx),
205319
- desktopConnected: ctx.desktopConnected,
205320
- activeRootPath: ctx.activeRootPath,
205321
- permissionMode: ctx.permissionMode,
205322
- workspaceId: ctx.workspaceId,
205323
- threadId: ctx.threadId,
205324
- selectedSourceId: ctx.selectedSourceId,
205325
- supabaseConfigured: ctx.supabaseConfigured,
205326
- supabase: ctx.supabase,
205327
- runId: ctx.runId,
205328
- founderModelSelection: ctx.founderModelSelection ?? null,
205329
- onEvent: ctx.onEvent,
205330
- signal: ctx.signal,
205331
- parentToolCallId: ctx.parentToolCallId,
205332
- mcpTools: ctx.mcpTools ?? []
205333
- }
205334
- );
205555
+ try {
205556
+ const result2 = await spawnWorker(
205557
+ { workerId, objective: workerObjective, context: enrichedContext, lane, maxIterations },
205558
+ {
205559
+ workspaceRoot: effectiveWorkspaceRoot(ctx),
205560
+ desktopConnected: ctx.desktopConnected,
205561
+ activeRootPath: ctx.activeRootPath,
205562
+ permissionMode: ctx.permissionMode,
205563
+ workspaceId: ctx.workspaceId,
205564
+ threadId: ctx.threadId,
205565
+ selectedSourceId: ctx.selectedSourceId,
205566
+ supabaseConfigured: ctx.supabaseConfigured,
205567
+ supabase: ctx.supabase,
205568
+ runId: ctx.runId,
205569
+ founderModelSelection: ctx.founderModelSelection ?? null,
205570
+ onEvent: ctx.onEvent,
205571
+ signal: ctx.signal,
205572
+ parentToolCallId: ctx.parentToolCallId,
205573
+ mcpTools: ctx.mcpTools ?? []
205574
+ }
205575
+ );
205576
+ return adhocNickname ? { ...result2, nickname: adhocNickname } : result2;
205577
+ } finally {
205578
+ if (adhocManifestId) unregisterWorkerManifest(adhocManifestId);
205579
+ }
205335
205580
  }
205336
205581
  };
205337
205582
  }
@@ -221145,56 +221390,6 @@ var init_flockModelHints = __esm({
221145
221390
  }
221146
221391
  });
221147
221392
 
221148
- // features/perchTerminal/runtime/flock/flockNicknames.ts
221149
- function seedFromFlockId(flockId) {
221150
- let hash = 2166136261;
221151
- for (let i = 0; i < flockId.length; i++) {
221152
- hash ^= flockId.charCodeAt(i);
221153
- hash = Math.imul(hash, 16777619);
221154
- }
221155
- return hash >>> 0;
221156
- }
221157
- function flockNicknameRotation(flockId) {
221158
- const names = [...FLOCK_NICKNAMES];
221159
- let state = seedFromFlockId(flockId) || 1;
221160
- const nextRandom = () => {
221161
- state = Math.imul(state, 1664525) + 1013904223 >>> 0;
221162
- return state / 4294967296;
221163
- };
221164
- for (let i = names.length - 1; i > 0; i--) {
221165
- const j = Math.floor(nextRandom() * (i + 1));
221166
- [names[i], names[j]] = [names[j], names[i]];
221167
- }
221168
- return names;
221169
- }
221170
- function flockNicknameFor(flockId, index) {
221171
- const rotation = flockNicknameRotation(flockId);
221172
- return rotation[index % rotation.length];
221173
- }
221174
- var FLOCK_NICKNAMES;
221175
- var init_flockNicknames = __esm({
221176
- "features/perchTerminal/runtime/flock/flockNicknames.ts"() {
221177
- "use strict";
221178
- FLOCK_NICKNAMES = [
221179
- "Little Dum Dum",
221180
- "Molly",
221181
- "Boomer",
221182
- "Shawarma",
221183
- "Curie",
221184
- "Eagle",
221185
- "Noether",
221186
- "Wonky",
221187
- "Lovelace",
221188
- "Bell",
221189
- "Turing",
221190
- "Faraday",
221191
- "Biscuit",
221192
- "Ice Cream",
221193
- "Minsky"
221194
- ];
221195
- }
221196
- });
221197
-
221198
221393
  // features/perchTerminal/runtime/flock/flockRoles.ts
221199
221394
  function listFlockRoleSpecs() {
221200
221395
  return Object.values(FLOCK_ROLES);
@@ -221448,6 +221643,53 @@ var init_flockRoles = __esm({
221448
221643
  "Return JSON with continuityStatus (pass|partial|fail), contradictions (string[]), styleIssues (string[]), warnings (string[])."
221449
221644
  )
221450
221645
  },
221646
+ legal_drafter: {
221647
+ roleId: "legal_drafter",
221648
+ workerId: "flock_legal_drafter",
221649
+ displayName: "Legal Drafter",
221650
+ role: "worker",
221651
+ lane: "writer",
221652
+ allowedTools: [
221653
+ TOOL_NAMES.searchKnowledge,
221654
+ TOOL_NAMES.glob,
221655
+ TOOL_NAMES.grep,
221656
+ TOOL_NAMES.readLocalFile,
221657
+ TOOL_NAMES.listLocalSources,
221658
+ TOOL_NAMES.readLocalSourceFile,
221659
+ TOOL_NAMES.writeLocalFile
221660
+ ],
221661
+ writesWorkspace: true,
221662
+ maxIterations: 10,
221663
+ outputContract: "JSON { title, markdown, irac: { issue, rules, application, conclusion }, authoritiesCited, needsVerification, notes }",
221664
+ objectiveTemplate: (task) => bounded(
221665
+ task,
221666
+ "Draft a legal memo in strict IRAC form \u2014 Issue, Rule, Application, Conclusion, every section present and labeled. First call searchKnowledge for IRAC memo exemplars or templates and imitate their structure and register when any exist. Closed universe: cite ONLY authorities found in the provided case packet (local sources), with pin cites; never invent or import outside authority. Propositions you cannot support from the packet go in needsVerification, never in the prose as settled law.",
221667
+ "Return JSON with title, markdown, irac {issue, rules, application, conclusion}, authoritiesCited (string[]), needsVerification (string[]), notes (string[])."
221668
+ )
221669
+ },
221670
+ legal_citation_checker: {
221671
+ roleId: "legal_citation_checker",
221672
+ workerId: "flock_legal_citation_checker",
221673
+ displayName: "Legal Citation Checker",
221674
+ role: "verifier",
221675
+ lane: "verifier",
221676
+ // Closed universe by construction: no web or global knowledge tools — the packet is the law.
221677
+ allowedTools: [
221678
+ TOOL_NAMES.glob,
221679
+ TOOL_NAMES.grep,
221680
+ TOOL_NAMES.readLocalFile,
221681
+ TOOL_NAMES.listLocalSources,
221682
+ TOOL_NAMES.readLocalSourceFile
221683
+ ],
221684
+ writesWorkspace: false,
221685
+ maxIterations: 10,
221686
+ outputContract: "JSON { verificationStatus: 'pass'|'partial'|'fail', verifiedCitations, unsupportedCitations: [{ citation, claim, reason }], missingFromPacket, warnings }",
221687
+ objectiveTemplate: (task) => bounded(
221688
+ task,
221689
+ "Closed-universe citation check: for every authority cited in the provided draft or memo, locate it in the provided case packet (local sources via listLocalSources/grep/readLocalFile). Verify the authority exists in the packet, quoted language appears in it, and each pin cite supports the stated proposition. Mark pass only when the packet supports the citation. Never consult outside sources \u2014 an authority absent from the packet is reported in missingFromPacket, not assumed valid.",
221690
+ "Return JSON with verificationStatus (pass|partial|fail), verifiedCitations, unsupportedCitations ({citation, claim, reason}[]), missingFromPacket (string[]), warnings (string[])."
221691
+ )
221692
+ },
221451
221693
  browser_operator: {
221452
221694
  roleId: "browser_operator",
221453
221695
  // Reuses the roster manifest from workers/registry — its system prompt and
@@ -221519,6 +221761,22 @@ function planFlock(rawTask, options = {}) {
221519
221761
  if ((options.surface ?? detectFlockSurface()) === "cli") {
221520
221762
  selected.delete("browser_operator");
221521
221763
  }
221764
+ if (selected.has("legal_drafter")) {
221765
+ if (!/\b(write|draft|compose|prepare)\b/i.test(task)) {
221766
+ selected.delete("legal_drafter");
221767
+ selected.add("legal_citation_checker");
221768
+ }
221769
+ selected.delete("writer");
221770
+ selected.delete("researcher");
221771
+ selected.delete("citation_checker");
221772
+ selected.delete("consistency_checker");
221773
+ selected.delete("source_verifier");
221774
+ }
221775
+ if (selected.has("legal_drafter")) {
221776
+ selected.add("legal_citation_checker");
221777
+ } else if (selected.has("legal_citation_checker")) {
221778
+ selected.add("workspace_scout");
221779
+ }
221522
221780
  if (selected.has("patch_worker") || selected.has("test_runner") || selected.has("ui_reviewer")) {
221523
221781
  selected.add("workspace_scout");
221524
221782
  }
@@ -221592,6 +221850,7 @@ var init_flockPlanner = __esm({
221592
221850
  { roleId: "writer", pattern: /\b(write|draft|compose|essay|memo|report|article|document|copy)\b/i },
221593
221851
  { roleId: "citation_checker", pattern: /\b(citations?|verify|check sources?|fact.?check|references?)\b/i },
221594
221852
  { roleId: "consistency_checker", pattern: /\b(fiction|stor(?:y|ies)|novel|chapters?|poems?|screenplay|scripts?|lyrics)\b/i },
221853
+ { roleId: "legal_drafter", pattern: /\b(irac|memorandum|legal (?:memo|brief|writing|citations?)|case ?packet|bluebook|pin ?cites?)\b/i },
221595
221854
  { roleId: "browser_operator", pattern: /\b(browser|open|navigate|click|fill|submit|login|webpage|url|site)\b/i }
221596
221855
  ];
221597
221856
  ROLE_TRIM_PRIORITY = [
@@ -221599,6 +221858,8 @@ var init_flockPlanner = __esm({
221599
221858
  "writer",
221600
221859
  "test_runner",
221601
221860
  "workspace_scout",
221861
+ "legal_drafter",
221862
+ "legal_citation_checker",
221602
221863
  "researcher",
221603
221864
  "citation_checker",
221604
221865
  "consistency_checker",
@@ -221641,6 +221902,7 @@ function buildFlockPlannerPrompts(ctx) {
221641
221902
  "- dependsOn: ids of earlier workers whose output this worker needs. No cycles. You decide the graph: parallel where independent, chained where not. Final verifier/reducer workers usually benefit from depending on the workers they check \u2014 guidance, not a requirement.",
221642
221903
  "- baseWorkerId: set ONLY to reuse an id from existingWorkers; otherwise null. Prefer reusing existingWorkers ids when a role matches instead of inventing a near-duplicate.",
221643
221904
  "- Writing tasks: pair the draft with exactly ONE draft verifier \u2014 reuse flock_citation_checker (together with flock_researcher) when the piece makes factual or source-backed claims, or flock_consistency_checker for creative/fiction drafts (continuity, voice, pacing \u2014 no researcher, never demand citations from fiction).",
221905
+ "- Legal writing or citation-checking tasks (IRAC memos, briefs, case packets, pin cites): reuse flock_legal_drafter and flock_legal_citation_checker instead of the generic writing roles. Closed universe \u2014 verification runs ONLY against the user's provided case packet/local sources, never the open web; a legal draft must include flock_legal_citation_checker.",
221644
221906
  "- Decline (accepted=false, with reason) tasks that are trivial, conversational, or need no fanout."
221645
221907
  ].join("\n");
221646
221908
  ensureFlockWorkersRegistered();
@@ -221746,7 +222008,7 @@ function validateLlmFlockPlan(record, ctx) {
221746
222008
  const objective = sanitizeText(worker.objective, MAX_OBJECTIVE_CHARS);
221747
222009
  if (!displayName || !objective) return null;
221748
222010
  const role = VALID_ROLES.has(worker.phase) ? worker.phase : "worker";
221749
- const outputContract = sanitizeLine(worker.outputContract, MAX_CONTRACT_CHARS) || "Return a short structured summary of what was found or done.";
222011
+ const outputContract = sanitizeLine(worker.outputContract, MAX_CONTRACT_CHARS2) || "Return a short structured summary of what was found or done.";
221750
222012
  const requestedTools = Array.isArray(worker.allowedTools) ? worker.allowedTools.filter((tool) => typeof tool === "string") : [];
221751
222013
  let allowedTools = [...new Set(requestedTools)].filter(
221752
222014
  (tool) => available.has(tool) && !FLOCK_FORBIDDEN_TOOLS.includes(tool)
@@ -221757,7 +222019,7 @@ function validateLlmFlockPlan(record, ctx) {
221757
222019
  if (!writesWorkspace) {
221758
222020
  allowedTools = allowedTools.filter((tool) => !WRITE_TOOLS.has(tool));
221759
222021
  }
221760
- const maxIterations = clampInt(worker.maxIterations, 1, caps.maxIterationsPerWorker, 6);
222022
+ const maxIterations = clampInt2(worker.maxIterations, 1, caps.maxIterationsPerWorker, 6);
221761
222023
  const baseWorkerId = typeof worker.baseWorkerId === "string" && worker.baseWorkerId.trim() ? worker.baseWorkerId.trim() : null;
221762
222024
  const reusedManifest = baseWorkerId ? resolveWorkerManifest(baseWorkerId) : null;
221763
222025
  const flockWorkerId = `fw${index + 1}_${slugify(displayName)}`;
@@ -221823,14 +222085,56 @@ function validateLlmFlockPlan(record, ctx) {
221823
222085
  }
221824
222086
  }
221825
222087
  }
221826
- const writerWorker = surfacedWorkers.find(
221827
- (worker) => worker.workerId === FLOCK_ROLES.writer.workerId
222088
+ const legalDrafterWorker = surfacedWorkers.find(
222089
+ (worker) => worker.workerId === FLOCK_ROLES.legal_drafter.workerId
221828
222090
  );
222091
+ if (legalDrafterWorker && !surfacedWorkers.some((worker) => worker.workerId === FLOCK_ROLES.legal_citation_checker.workerId)) {
222092
+ const spec = FLOCK_ROLES.legal_citation_checker;
222093
+ const genericVerifierIndex = surfacedWorkers.findIndex(
222094
+ (worker) => worker.workerId === FLOCK_ROLES.citation_checker.workerId || worker.workerId === FLOCK_ROLES.consistency_checker.workerId
222095
+ );
222096
+ if (genericVerifierIndex >= 0) {
222097
+ const existing = surfacedWorkers[genericVerifierIndex];
222098
+ surfacedWorkers[genericVerifierIndex] = {
222099
+ ...existing,
222100
+ workerId: spec.workerId,
222101
+ displayName: spec.displayName,
222102
+ role: spec.role,
222103
+ objective: spec.objectiveTemplate(ctx.task),
222104
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
222105
+ allowedTools: [...spec.allowedTools],
222106
+ writesWorkspace: false,
222107
+ writeScope: null,
222108
+ outputContract: spec.outputContract,
222109
+ dependsOn: [legalDrafterWorker.flockWorkerId],
222110
+ dynamicManifest: null
222111
+ };
222112
+ } else if (surfacedWorkers.length < FLOCK_MAX_WORKERS) {
222113
+ const index = surfacedWorkers.length;
222114
+ surfacedWorkers.push({
222115
+ flockWorkerId: `fw${index + 1}_${spec.roleId}`,
222116
+ workerId: spec.workerId,
222117
+ displayName: spec.displayName,
222118
+ nickname: flockNicknameFor(ctx.flockId, index),
222119
+ role: spec.role,
222120
+ objective: spec.objectiveTemplate(ctx.task),
222121
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
222122
+ allowedTools: [...spec.allowedTools],
222123
+ writesWorkspace: false,
222124
+ writeScope: null,
222125
+ outputContract: spec.outputContract,
222126
+ dependsOn: [legalDrafterWorker.flockWorkerId],
222127
+ modelOverride: null,
222128
+ dynamicManifest: null
222129
+ });
222130
+ }
222131
+ }
222132
+ const writerWorker = legalDrafterWorker ?? surfacedWorkers.find((worker) => worker.workerId === FLOCK_ROLES.writer.workerId);
221829
222133
  if (writerWorker && !surfacedWorkers.some((worker) => worker.role === "verifier") && surfacedWorkers.length < FLOCK_MAX_WORKERS) {
221830
222134
  const hasResearch = surfacedWorkers.some(
221831
222135
  (worker) => worker.workerId === FLOCK_ROLES.researcher.workerId
221832
222136
  );
221833
- const spec = hasResearch ? FLOCK_ROLES.citation_checker : FLOCK_ROLES.consistency_checker;
222137
+ const spec = legalDrafterWorker ? FLOCK_ROLES.legal_citation_checker : hasResearch ? FLOCK_ROLES.citation_checker : FLOCK_ROLES.consistency_checker;
221834
222138
  const index = surfacedWorkers.length;
221835
222139
  surfacedWorkers.push({
221836
222140
  flockWorkerId: `fw${index + 1}_${spec.roleId}`,
@@ -221900,11 +222204,11 @@ function sanitizeText(value, max2) {
221900
222204
  if (typeof value !== "string") return "";
221901
222205
  return value.trim().slice(0, max2);
221902
222206
  }
221903
- function clampInt(value, min2, max2, fallback) {
222207
+ function clampInt2(value, min2, max2, fallback) {
221904
222208
  const num = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
221905
222209
  return Math.min(Math.max(num, min2), max2);
221906
222210
  }
221907
- var FLOCK_FORBIDDEN_TOOLS, WRITE_TOOLS, GUI_ONLY_WORKER_IDS, GUI_ONLY_TOOLS, VALID_ROLES, MAX_DISPLAY_NAME_CHARS, MAX_OBJECTIVE_CHARS, MAX_CONTRACT_CHARS, MAX_ROSTER_IN_PROMPT, MAX_TOOLS_IN_PROMPT;
222211
+ var FLOCK_FORBIDDEN_TOOLS, WRITE_TOOLS, GUI_ONLY_WORKER_IDS, GUI_ONLY_TOOLS, VALID_ROLES, MAX_DISPLAY_NAME_CHARS, MAX_OBJECTIVE_CHARS, MAX_CONTRACT_CHARS2, MAX_ROSTER_IN_PROMPT, MAX_TOOLS_IN_PROMPT;
221908
222212
  var init_flockLlmPlanner = __esm({
221909
222213
  "features/perchTerminal/runtime/flock/flockLlmPlanner.ts"() {
221910
222214
  "use strict";
@@ -221945,7 +222249,7 @@ var init_flockLlmPlanner = __esm({
221945
222249
  VALID_ROLES = /* @__PURE__ */ new Set(["scout", "worker", "verifier", "reducer"]);
221946
222250
  MAX_DISPLAY_NAME_CHARS = 40;
221947
222251
  MAX_OBJECTIVE_CHARS = 1200;
221948
- MAX_CONTRACT_CHARS = 240;
222252
+ MAX_CONTRACT_CHARS2 = 240;
221949
222253
  MAX_ROSTER_IN_PROMPT = 24;
221950
222254
  MAX_TOOLS_IN_PROMPT = 80;
221951
222255
  }
@@ -222088,14 +222392,19 @@ async function runFlockTurn(input, deps, options = {}) {
222088
222392
  }
222089
222393
  const spawnFn = options.spawnWorkerFn ?? spawnWorker;
222090
222394
  const outcomes = [];
222395
+ const revisionOutcomes = [];
222396
+ let revisionReport = null;
222091
222397
  const outputByFlockWorkerId = /* @__PURE__ */ new Map();
222092
- const sharedContext = { task };
222398
+ const sharedContext = {
222399
+ task,
222400
+ currentDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
222401
+ };
222093
222402
  let toolCallsUsed = 0;
222094
222403
  let toolCallsReserved = 0;
222095
222404
  let workersAwaitingLaunch = plan.workers.length;
222096
222405
  try {
222097
222406
  const phases = [...new Set(plan.workers.map((worker) => FLOCK_ROLE_ORDER[worker.role]))].sort((a, b2) => a - b2);
222098
- const runReadyWorker = async (worker) => {
222407
+ const runReadyWorker = async (worker, contextOverride) => {
222099
222408
  const workersLeftIncludingThis = Math.max(1, workersAwaitingLaunch);
222100
222409
  workersAwaitingLaunch = Math.max(0, workersAwaitingLaunch - 1);
222101
222410
  if (flockRun.controller.signal.aborted) {
@@ -222130,11 +222439,14 @@ async function runFlockTurn(input, deps, options = {}) {
222130
222439
  toolCallsReserved += reservedToolCalls;
222131
222440
  emitWorkerUpdate(emit, plan, worker, "running");
222132
222441
  try {
222442
+ const context = contextOverride ?? buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan);
222443
+ const objective = worker.role === "worker" && contextContainsSources(context) ? `${worker.objective}
222444
+ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
222133
222445
  const result2 = await spawnFn(
222134
222446
  {
222135
222447
  workerId: worker.workerId,
222136
- objective: worker.objective,
222137
- context: buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan),
222448
+ objective,
222449
+ context,
222138
222450
  maxIterations: worker.maxIterations,
222139
222451
  maxToolCalls: reservedToolCalls
222140
222452
  },
@@ -222191,7 +222503,9 @@ async function runFlockTurn(input, deps, options = {}) {
222191
222503
  break;
222192
222504
  }
222193
222505
  for (const worker of readyWorkers) pending.delete(worker.flockWorkerId);
222194
- const batchOutcomes = await Promise.all(readyWorkers.map(runReadyWorker));
222506
+ const batchOutcomes = await Promise.all(
222507
+ readyWorkers.map((worker) => runReadyWorker(worker))
222508
+ );
222195
222509
  for (const outcome of batchOutcomes) {
222196
222510
  outcomes.push(outcome);
222197
222511
  if (!outcome.result) continue;
@@ -222201,6 +222515,43 @@ async function runFlockTurn(input, deps, options = {}) {
222201
222515
  }
222202
222516
  }
222203
222517
  }
222518
+ const candidate = findRevisionCandidate(plan, outcomes, outputByFlockWorkerId);
222519
+ if (candidate && !flockRun.controller.signal.aborted) {
222520
+ revisionReport = candidate;
222521
+ const revisionWorker = {
222522
+ ...candidate.draftWorker,
222523
+ flockWorkerId: `${candidate.draftWorker.flockWorkerId}_r1`,
222524
+ displayName: `${candidate.draftWorker.displayName} (revision)`,
222525
+ objective: buildRevisionObjective(task, candidate),
222526
+ dependsOn: []
222527
+ };
222528
+ const revisionOutcome = await runReadyWorker(revisionWorker, {
222529
+ task,
222530
+ currentDate: sharedContext.currentDate,
222531
+ draft: candidate.draftOutput,
222532
+ verifierFindings: candidate.findings.raw
222533
+ });
222534
+ revisionOutcomes.push(revisionOutcome);
222535
+ candidate.revised = revisionOutcome.status === "done";
222536
+ if (revisionOutcome.result) {
222537
+ const revisedOutput = revisionOutcome.result.structuredOutput ?? clampLine(revisionOutcome.result.summary, 1200);
222538
+ sharedContext[revisionWorker.displayName] = revisedOutput;
222539
+ outputByFlockWorkerId.set(revisionWorker.flockWorkerId, revisedOutput);
222540
+ const recheckWorker = {
222541
+ ...candidate.verifierWorker,
222542
+ flockWorkerId: `${candidate.verifierWorker.flockWorkerId}_r1`,
222543
+ displayName: `${candidate.verifierWorker.displayName} (recheck)`,
222544
+ dependsOn: []
222545
+ };
222546
+ const recheckOutcome = await runReadyWorker(recheckWorker, {
222547
+ task,
222548
+ currentDate: sharedContext.currentDate,
222549
+ dependencies: { [revisionWorker.displayName]: revisedOutput }
222550
+ });
222551
+ revisionOutcomes.push(recheckOutcome);
222552
+ candidate.recheckFindings = recheckOutcome.result ? extractVerifierFindings(recheckOutcome.result.structuredOutput) : null;
222553
+ }
222554
+ }
222204
222555
  } finally {
222205
222556
  clearTimeout(wallTimer);
222206
222557
  finishRuntimeRun(
@@ -222213,7 +222564,14 @@ async function runFlockTurn(input, deps, options = {}) {
222213
222564
  const workersFailed = outcomes.filter((outcome) => outcome.status === "failed").length;
222214
222565
  const flockStatus = userCancelled || wallTimeHit && workersDone === 0 ? "cancelled" : workersFailed === 0 && workersDone === plan.workers.length ? "completed" : workersDone > 0 ? "partial" : "failed";
222215
222566
  const assistantText = [
222216
- buildFlockSummary(plan, outcomes, flockStatus, toolCallsUsed, wallTimeHit),
222567
+ buildFlockSummary(
222568
+ plan,
222569
+ [...outcomes, ...revisionOutcomes],
222570
+ flockStatus,
222571
+ toolCallsUsed,
222572
+ wallTimeHit
222573
+ ),
222574
+ ...buildVerificationSection(revisionReport),
222217
222575
  ...modelOverrides.applied.map(
222218
222576
  (override) => `Model override: ${override.displayName} ran on ${override.label}.`
222219
222577
  ),
@@ -222300,7 +222658,117 @@ function buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan)
222300
222658
  const depWorker = plan.workers.find((candidate) => candidate.flockWorkerId === dep);
222301
222659
  dependencies[depWorker?.displayName ?? dep] = output;
222302
222660
  }
222303
- return { task: sharedContext.task, dependencies };
222661
+ return { task: sharedContext.task, currentDate: sharedContext.currentDate, dependencies };
222662
+ }
222663
+ function contextContainsSources(context) {
222664
+ const scan = (value, depth) => {
222665
+ if (!value || typeof value !== "object" || Array.isArray(value) || depth > 3) return false;
222666
+ const record = value;
222667
+ const sources = record.sources;
222668
+ if (Array.isArray(sources) && sources.some(
222669
+ (source) => source && typeof source === "object" && typeof source.url === "string"
222670
+ )) {
222671
+ return true;
222672
+ }
222673
+ return Object.values(record).some((nested) => scan(nested, depth + 1));
222674
+ };
222675
+ return scan(context, 0);
222676
+ }
222677
+ function extractVerifierFindings(output) {
222678
+ if (!output || typeof output !== "object" || Array.isArray(output)) return null;
222679
+ const record = output;
222680
+ const verdictRaw = record.verificationStatus ?? record.continuityStatus ?? record.verdict;
222681
+ const verdict = typeof verdictRaw === "string" ? verdictRaw.trim().toLowerCase() : null;
222682
+ const issues = [];
222683
+ for (const key of [
222684
+ "unsupportedClaims",
222685
+ "unsupportedCitations",
222686
+ "missingFromPacket",
222687
+ "contradictions",
222688
+ "styleIssues",
222689
+ "unsupported"
222690
+ ]) {
222691
+ const value = record[key];
222692
+ if (!Array.isArray(value)) continue;
222693
+ for (const item of value) {
222694
+ const text = typeof item === "string" ? item : JSON.stringify(item);
222695
+ const clean = clampLine(text, 200);
222696
+ if (clean && clean !== "{}") issues.push(clean);
222697
+ }
222698
+ }
222699
+ return { verdict, issues, raw: record };
222700
+ }
222701
+ function verifierFindingsNeedRevision(findings) {
222702
+ if (!findings) return false;
222703
+ return findings.issues.length > 0 || findings.verdict === "fail" || findings.verdict === "partial";
222704
+ }
222705
+ function findRevisionCandidate(plan, outcomes, outputByFlockWorkerId) {
222706
+ for (const outcome of outcomes) {
222707
+ if (outcome.worker.role !== "verifier" || outcome.status !== "done" || !outcome.result) {
222708
+ continue;
222709
+ }
222710
+ const findings = extractVerifierFindings(outcome.result.structuredOutput);
222711
+ if (!verifierFindingsNeedRevision(findings)) continue;
222712
+ const draftWorker = resolveDraftWorkerForVerifier(plan, outcome.worker, outcomes);
222713
+ if (!draftWorker) continue;
222714
+ const draftOutput = outputByFlockWorkerId.get(draftWorker.flockWorkerId);
222715
+ if (draftOutput === void 0) continue;
222716
+ return {
222717
+ verifierWorker: outcome.worker,
222718
+ draftWorker,
222719
+ draftOutput,
222720
+ findings,
222721
+ revised: false,
222722
+ recheckFindings: null
222723
+ };
222724
+ }
222725
+ return null;
222726
+ }
222727
+ function resolveDraftWorkerForVerifier(plan, verifier, outcomes) {
222728
+ for (const dep of verifier.dependsOn) {
222729
+ const worker = plan.workers.find((candidate) => candidate.flockWorkerId === dep);
222730
+ if (worker?.role === "worker") return worker;
222731
+ }
222732
+ const doneWorkers = outcomes.filter((outcome) => outcome.status === "done" && outcome.worker.role === "worker").map((outcome) => outcome.worker);
222733
+ const writer = doneWorkers.find(
222734
+ (worker) => worker.workerId === "flock_legal_drafter" || worker.workerId === "flock_writer"
222735
+ );
222736
+ if (writer) return writer;
222737
+ const writingWorkers = doneWorkers.filter((worker) => worker.writesWorkspace);
222738
+ if (writingWorkers.length === 1) return writingWorkers[0];
222739
+ return doneWorkers.length === 1 ? doneWorkers[0] : null;
222740
+ }
222741
+ function buildRevisionObjective(task, candidate) {
222742
+ return [
222743
+ "Bounded Flock revision \u2014 stay strictly inside this scope.",
222744
+ `Task: ${task}`,
222745
+ `A verifier (${candidate.verifierWorker.displayName}) reviewed your draft (context key "draft") and reported findings (context key "verifierFindings").`,
222746
+ "Revise the draft: fix what the findings support fixing; remove or explicitly flag any claim you cannot support. Keep the draft's purpose, format, and output contract. Do not start over.",
222747
+ `Output contract: ${candidate.draftWorker.outputContract}`,
222748
+ "Do not delegate, spawn workers, or start suites. Stop when the findings are addressed."
222749
+ ].join("\n");
222750
+ }
222751
+ function buildVerificationSection(report) {
222752
+ if (!report) return [];
222753
+ const lines = [];
222754
+ lines.push(
222755
+ `Verification (${report.verifierWorker.displayName}): ${report.findings.verdict ?? "issues found"} \u2014 ${report.findings.issues.length} finding(s).`
222756
+ );
222757
+ const shown = report.findings.issues.slice(0, 6);
222758
+ for (const issue of shown) lines.push(`- ${issue}`);
222759
+ if (report.findings.issues.length > shown.length) {
222760
+ lines.push(`- \u2026and ${report.findings.issues.length - shown.length} more.`);
222761
+ }
222762
+ if (report.revised) {
222763
+ const recheck = report.recheckFindings;
222764
+ lines.push(
222765
+ recheck ? `The draft was revised once; recheck verdict: ${recheck.verdict ?? "no structured verdict"}${recheck.issues.length ? ` (${recheck.issues.length} remaining finding(s))` : ""}.` : "The draft was revised once; the recheck returned no structured verdict."
222766
+ );
222767
+ } else {
222768
+ lines.push("No revision pass ran \u2014 the findings above stand as reported.");
222769
+ }
222770
+ lines.push("Ask about any finding to discuss it, or rerun /flock to retry with adjustments.");
222771
+ return lines;
222304
222772
  }
222305
222773
  function buildSpawnContext(input, flockId, signal, emit, worker) {
222306
222774
  return {
@@ -222365,6 +222833,7 @@ function makeFlockRunId() {
222365
222833
  function now11() {
222366
222834
  return (/* @__PURE__ */ new Date()).toISOString();
222367
222835
  }
222836
+ var FLOCK_GROUNDING_CONTRACT;
222368
222837
  var init_runFlockTurn = __esm({
222369
222838
  "features/perchTerminal/runtime/flock/runFlockTurn.ts"() {
222370
222839
  "use strict";
@@ -222378,6 +222847,12 @@ var init_runFlockTurn = __esm({
222378
222847
  init_flockLlmPlanner();
222379
222848
  init_flockPlanner();
222380
222849
  init_flockRoles();
222850
+ FLOCK_GROUNDING_CONTRACT = [
222851
+ "Grounding contract: your context includes sources[] gathered by research.",
222852
+ "Every nontrivial factual claim in the draft must carry an inline [n] marker mapping to one of those sources, with a numbered source list at the end.",
222853
+ "Claims you cannot support from sources[] belong in needsVerification, never in the prose as fact.",
222854
+ "Do not cite anything that is not in sources[]."
222855
+ ].join(" ");
222381
222856
  }
222382
222857
  });
222383
222858
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.33",
3
+ "version": "2.4.35",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"