snipara-companion 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3365,6 +3365,7 @@ interface AdaptiveWorkProfile {
3365
3365
  contextBudget: string;
3366
3366
  reasoningDepth: string;
3367
3367
  evidenceRequirements?: string[];
3368
+ preferredProfileStrengths?: string[];
3368
3369
  notes?: string[];
3369
3370
  }
3370
3371
  interface AdaptiveModelRequirements {
@@ -3377,6 +3378,7 @@ interface AdaptiveModelRequirements {
3377
3378
  capabilities: string[];
3378
3379
  forbiddenCapabilities: string[];
3379
3380
  writeScope: string[];
3381
+ structuredOutputRequired?: boolean;
3380
3382
  preferredEndpointTypes?: string[];
3381
3383
  allowedEndpointTypes?: string[];
3382
3384
  catalogLimit?: number;
@@ -3394,13 +3396,13 @@ interface AdaptiveRoutingCard {
3394
3396
  requirements: AdaptiveModelRequirements;
3395
3397
  recommendedWorkerClass: string;
3396
3398
  costEstimate: AdaptiveRoutingCostEstimate;
3397
- humanApprovalRequired: true;
3399
+ humanApprovalRequired: boolean;
3398
3400
  fallback: "main_agent";
3399
3401
  reasons: string[];
3400
3402
  warnings: string[];
3401
3403
  }
3402
3404
  interface AdaptiveRoutingGatewayStatus {
3403
- source: "hosted_mcp";
3405
+ source: "hosted_mcp" | "local_orchestrator";
3404
3406
  success: boolean;
3405
3407
  resolutionStatus?: string;
3406
3408
  candidateCount: number;
@@ -3409,14 +3411,32 @@ interface AdaptiveRoutingGatewayStatus {
3409
3411
  }
3410
3412
  interface AdaptiveRoutingRuntimeCatalog {
3411
3413
  version?: string;
3414
+ source?: string;
3415
+ provider?: string;
3416
+ baseUrl?: string;
3417
+ models?: string[];
3418
+ apiPaths?: Record<string, unknown>;
3419
+ workerEndpoints?: Record<string, Record<string, unknown>>;
3420
+ workerProfiles?: Record<string, Record<string, unknown>>;
3412
3421
  candidates: Array<Record<string, unknown>>;
3413
3422
  }
3423
+ interface AdaptiveRoutingResolution {
3424
+ status?: string;
3425
+ selected?: Record<string, unknown>;
3426
+ policyDecision?: Record<string, unknown>;
3427
+ evaluatedCount?: number;
3428
+ rejectedCount?: number;
3429
+ fallback?: string;
3430
+ reasons?: string[];
3431
+ warnings?: string[];
3432
+ }
3414
3433
  interface AdaptiveWorkRoutingRecommendation {
3415
3434
  workProfile: AdaptiveWorkProfile;
3416
3435
  requirements: AdaptiveModelRequirements;
3417
3436
  routingCard: AdaptiveRoutingCard;
3418
3437
  gateway?: AdaptiveRoutingGatewayStatus;
3419
3438
  runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
3439
+ resolution?: AdaptiveRoutingResolution;
3420
3440
  }
3421
3441
  interface AdaptiveWorkRoutingOptions {
3422
3442
  query: string;
@@ -3426,6 +3446,8 @@ interface AdaptiveWorkRoutingOptions {
3426
3446
  allowedEndpointTypes?: string[];
3427
3447
  workerRole?: string;
3428
3448
  plannerRetainsReasoning?: boolean;
3449
+ preferredProfileStrengths?: string[];
3450
+ structuredOutputRequired?: boolean;
3429
3451
  catalogLimit?: number;
3430
3452
  dailyBudgetCents?: number;
3431
3453
  monthlyBudgetCents?: number;
@@ -3458,6 +3480,7 @@ interface OrchestratorHandoffArtifact {
3458
3480
  routingCard?: AdaptiveRoutingCard;
3459
3481
  gateway?: AdaptiveRoutingGatewayStatus;
3460
3482
  runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
3483
+ resolution?: AdaptiveRoutingResolution;
3461
3484
  };
3462
3485
  task: {
3463
3486
  title: string;
package/dist/index.js CHANGED
@@ -14627,7 +14627,8 @@ function buildOrchestratorHandoff(options) {
14627
14627
  requirements: options.adaptiveRouting.requirements,
14628
14628
  routingCard: options.adaptiveRouting.routingCard,
14629
14629
  ...options.adaptiveRouting.gateway ? { gateway: options.adaptiveRouting.gateway } : {},
14630
- ...options.adaptiveRouting.runtimeCatalog ? { runtimeCatalog: options.adaptiveRouting.runtimeCatalog } : {}
14630
+ ...options.adaptiveRouting.runtimeCatalog ? { runtimeCatalog: options.adaptiveRouting.runtimeCatalog } : {},
14631
+ ...options.adaptiveRouting.resolution ? { resolution: options.adaptiveRouting.resolution } : {}
14631
14632
  } : {}
14632
14633
  },
14633
14634
  task: {
@@ -14680,8 +14681,12 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
14680
14681
  const reasoningDepth = inferAdaptiveReasoningDepth(risk, taskType);
14681
14682
  const workerRole = options.workerRole ?? inferAdaptiveWorkerRole(taskType);
14682
14683
  const plannerRetainsReasoning = options.plannerRetainsReasoning ?? preferredEndpointTypes.includes("local");
14684
+ const preferredProfileStrengths = normalizeProfileStrengths(
14685
+ options.preferredProfileStrengths ?? inferAdaptivePreferredProfileStrengths(taskType, options.query, changedFiles, workerRole)
14686
+ );
14683
14687
  const capabilities = inferAdaptiveCapabilities(taskType, workerRole);
14684
14688
  const forbiddenCapabilities = risk === "high" ? ["secrets", "prod_write"] : ["secrets"];
14689
+ const structuredOutputRequired = options.structuredOutputRequired ?? inferAdaptiveStructuredOutputRequirement(taskType, options.query, changedFiles, workerRole);
14685
14690
  const workProfile = compactObject2({
14686
14691
  taskType,
14687
14692
  risk,
@@ -14689,6 +14694,7 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
14689
14694
  contextBudget,
14690
14695
  reasoningDepth,
14691
14696
  evidenceRequirements: inferAdaptiveEvidenceRequirements(options.query, risk),
14697
+ preferredProfileStrengths,
14692
14698
  notes: [
14693
14699
  "Generated by snipara-companion as recommendation-only routing metadata.",
14694
14700
  "snipara-orchestrator must resolve the worker against the runtime catalog before execution."
@@ -14704,6 +14710,7 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
14704
14710
  capabilities,
14705
14711
  forbiddenCapabilities,
14706
14712
  writeScope: changedFiles,
14713
+ structuredOutputRequired: structuredOutputRequired ? true : void 0,
14707
14714
  preferredEndpointTypes,
14708
14715
  allowedEndpointTypes,
14709
14716
  catalogLimit: options.catalogLimit,
@@ -14820,6 +14827,54 @@ function inferAdaptiveCapabilities(taskType, workerRole) {
14820
14827
  }
14821
14828
  return ["execution"];
14822
14829
  }
14830
+ function inferAdaptivePreferredProfileStrengths(taskType, query, changedFiles, workerRole) {
14831
+ const normalizedQuery = query.toLowerCase();
14832
+ const strengths = /* @__PURE__ */ new Set();
14833
+ const codeLikeScope = workerRole === "coding" || workerRole === "testing" || taskType === "coding" || taskType === "critical_code" || changedFiles.some((file) => /\.(ts|tsx|js|jsx|py|go|rs|java|kt|swift|rb|php)$/i.test(file));
14834
+ if (codeLikeScope) {
14835
+ strengths.add("code");
14836
+ }
14837
+ if (/\b(structured|json|schema|signature|serialization|contract|patch|file operations?)\b/i.test(
14838
+ normalizedQuery
14839
+ ) || codeLikeScope) {
14840
+ strengths.add("structured_output");
14841
+ }
14842
+ if (/\b(refactor|rewrite|rename|sweep|cross-file|multi-file)\b/i.test(normalizedQuery) || changedFiles.length > 8) {
14843
+ strengths.add("refactor");
14844
+ }
14845
+ if (/\b(repo|repository|codebase|explore|scan|survey|context|continuity|architecture)\b/i.test(
14846
+ normalizedQuery
14847
+ ) || changedFiles.length > 5) {
14848
+ strengths.add("long_context");
14849
+ strengths.add("repo_scan");
14850
+ }
14851
+ if (/\b(agent|workflow|checkpoint|handoff|delegate|orchestrat)\b/i.test(normalizedQuery)) {
14852
+ strengths.add("agentic_exploration");
14853
+ }
14854
+ if (taskType === "release" || taskType === "critical_code" || workerRole === "validation") {
14855
+ strengths.add("execution_safe");
14856
+ }
14857
+ return Array.from(strengths).sort();
14858
+ }
14859
+ function inferAdaptiveStructuredOutputRequirement(taskType, query, changedFiles, workerRole) {
14860
+ if (workerRole === "documentation" && taskType === "documentation") {
14861
+ return false;
14862
+ }
14863
+ if (workerRole === "coding" || workerRole === "testing") {
14864
+ return true;
14865
+ }
14866
+ if (taskType === "coding" || taskType === "critical_code" || taskType === "tests") {
14867
+ return true;
14868
+ }
14869
+ if (/\b(structured|json|schema|signature|serialization|contract|patch|file operations?)\b/i.test(
14870
+ query
14871
+ )) {
14872
+ return true;
14873
+ }
14874
+ return changedFiles.some(
14875
+ (file) => /\.(ts|tsx|js|jsx|py|go|rs|java|kt|swift|rb|php)$/i.test(file)
14876
+ );
14877
+ }
14823
14878
  function inferAdaptiveEvidenceRequirements(query, risk) {
14824
14879
  const evidence = /* @__PURE__ */ new Set();
14825
14880
  if (/\b(test|tests|pytest|unit|integration|e2e|smoke|verify|verification)\b/i.test(query)) {
@@ -14833,6 +14888,13 @@ function inferAdaptiveEvidenceRequirements(query, risk) {
14833
14888
  }
14834
14889
  return Array.from(evidence).sort();
14835
14890
  }
14891
+ function normalizeProfileStrengths(values) {
14892
+ return Array.from(
14893
+ new Set(
14894
+ (values ?? []).map((value) => stringValue8(value)?.toLowerCase()).filter((value) => Boolean(value))
14895
+ )
14896
+ ).sort();
14897
+ }
14836
14898
  function normalizeEndpointTypes(values) {
14837
14899
  return Array.from(
14838
14900
  new Set(
@@ -18236,6 +18298,7 @@ var FINAL_COMMIT_RETRY_TIMEOUT_MS = 45e3;
18236
18298
  var FINAL_COMMIT_SUMMARY_MAX_CHARS = 1200;
18237
18299
  var FINAL_COMMIT_RETRY_SUMMARY_MAX_CHARS = 600;
18238
18300
  var DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT = 20;
18301
+ var DEFAULT_LOCAL_ORCHESTRATOR_TIMEOUT_MS = 8e3;
18239
18302
  var ADAPTIVE_ROUTING_POLICY_RELATIVE_PATH = path21.join(".snipara", "adaptive-routing.json");
18240
18303
  var SHARED_CONTEXT_INTENT_PATTERN = /\b(standard|standards|convention|conventions|guideline|guidelines|best practice|best practices|policy|policies|compliance|compliant|security rules|team rules|style guide|playbook|checklist)\b/i;
18241
18304
  var DOCUMENT_SYNC_FORMATS = {
@@ -20777,10 +20840,21 @@ function printAdaptiveRoutingRecommendation(routing) {
20777
20840
  }
20778
20841
  if (routing.gateway) {
20779
20842
  printKeyValue(
20780
- "Hosted catalog:",
20843
+ routing.gateway.source === "local_orchestrator" ? "Local route:" : "Hosted catalog:",
20781
20844
  routing.gateway.success ? `${routing.gateway.candidateCount} candidate(s), ${routing.gateway.resolutionStatus ?? "ready"}` : "unavailable"
20782
20845
  );
20783
20846
  }
20847
+ const selectedCandidate = adaptiveRoutingSelectedCandidate(routing.resolution);
20848
+ if (selectedCandidate) {
20849
+ printKeyValue("Selected candidate:", selectedCandidate.candidateId);
20850
+ if (selectedCandidate.endpointType) {
20851
+ printKeyValue("Selected endpoint:", selectedCandidate.endpointType);
20852
+ }
20853
+ }
20854
+ const selectedWorkerEndpoint = selectedCandidate && routing.runtimeCatalog?.workerEndpoints ? routing.runtimeCatalog.workerEndpoints[selectedCandidate.candidateId] : void 0;
20855
+ if (selectedWorkerEndpoint?.model) {
20856
+ printKeyValue("Selected model:", String(selectedWorkerEndpoint.model));
20857
+ }
20784
20858
  if (routing.routingCard.reasons.length > 0) {
20785
20859
  console.log("Reasons:");
20786
20860
  for (const reason of routing.routingCard.reasons) {
@@ -20965,11 +21039,191 @@ function normalizeAdaptiveRoutingCatalog(value) {
20965
21039
  const candidates = Array.isArray(record.candidates) ? record.candidates.filter(
20966
21040
  (candidate) => Boolean(candidate) && typeof candidate === "object" && !Array.isArray(candidate)
20967
21041
  ) : [];
21042
+ const models = Array.isArray(record.models) ? record.models.map((item) => stringValue9(item)).filter((item) => Boolean(item)) : void 0;
21043
+ const workerEndpoints = isRecord12(record.workerEndpoints) ? Object.fromEntries(
21044
+ Object.entries(record.workerEndpoints).filter(
21045
+ ([key, value2]) => Boolean(stringValue9(key)) && isRecord12(value2)
21046
+ )
21047
+ ) : void 0;
21048
+ const workerProfiles = isRecord12(record.workerProfiles) ? Object.fromEntries(
21049
+ Object.entries(record.workerProfiles).filter(
21050
+ ([key, value2]) => Boolean(stringValue9(key)) && isRecord12(value2)
21051
+ )
21052
+ ) : void 0;
20968
21053
  return {
20969
21054
  version: stringValue9(record.version),
21055
+ source: stringValue9(record.source),
21056
+ provider: stringValue9(record.provider),
21057
+ baseUrl: stringValue9(record.baseUrl),
21058
+ ...models && models.length > 0 ? { models } : {},
21059
+ ...isRecord12(record.apiPaths) ? { apiPaths: record.apiPaths } : {},
21060
+ ...workerEndpoints ? { workerEndpoints } : {},
21061
+ ...workerProfiles ? { workerProfiles } : {},
20970
21062
  candidates
20971
21063
  };
20972
21064
  }
21065
+ function adaptiveRoutingSelectedCandidate(resolution) {
21066
+ if (!resolution || !isRecord12(resolution.selected)) {
21067
+ return void 0;
21068
+ }
21069
+ const candidate = isRecord12(resolution.selected.candidate) ? resolution.selected.candidate : void 0;
21070
+ const candidateId = stringValue9(candidate?.candidateId);
21071
+ if (!candidateId) {
21072
+ return void 0;
21073
+ }
21074
+ return {
21075
+ candidateId,
21076
+ workerClass: stringValue9(candidate?.workerClass),
21077
+ endpointType: stringValue9(candidate?.endpointType),
21078
+ catalogSource: stringValue9(candidate?.catalogSource)
21079
+ };
21080
+ }
21081
+ function normalizeAdaptiveRoutingResolution(value) {
21082
+ if (!isRecord12(value)) {
21083
+ return void 0;
21084
+ }
21085
+ const selected = isRecord12(value.selected) ? value.selected : void 0;
21086
+ const candidate = selected && isRecord12(selected.candidate) ? selected.candidate : void 0;
21087
+ const selectedPayload = selected && candidate ? {
21088
+ candidate,
21089
+ ...numberValue6(selected.score) !== void 0 ? { score: numberValue6(selected.score) } : {},
21090
+ ...isRecord12(selected.scoreBreakdown) ? { scoreBreakdown: selected.scoreBreakdown } : {},
21091
+ ...normalizeStringArray3(selected.reasons) ? { reasons: normalizeStringArray3(selected.reasons) } : {}
21092
+ } : void 0;
21093
+ const reasons = normalizeStringArray3(value.reasons);
21094
+ const warnings = normalizeStringArray3(value.warnings);
21095
+ return {
21096
+ status: stringValue9(value.status),
21097
+ ...selectedPayload ? { selected: selectedPayload } : {},
21098
+ ...isRecord12(value.policyDecision) ? { policyDecision: value.policyDecision } : {},
21099
+ ...numberValue6(value.evaluatedCount) !== void 0 ? { evaluatedCount: numberValue6(value.evaluatedCount) } : {},
21100
+ ...numberValue6(value.rejectedCount) !== void 0 ? { rejectedCount: numberValue6(value.rejectedCount) } : {},
21101
+ ...stringValue9(value.fallback) ? { fallback: stringValue9(value.fallback) } : {},
21102
+ ...reasons ? { reasons } : {},
21103
+ ...warnings ? { warnings } : {}
21104
+ };
21105
+ }
21106
+ function shouldResolveAdaptiveRoutingLocally(options, routing) {
21107
+ if (!routing) {
21108
+ return false;
21109
+ }
21110
+ if (!options.routeLocalWorkers && !options.routingLocalBaseUrl && !options.routingLocalModel && !options.routingLocalPreferModel && !options.routingLocalProvider) {
21111
+ return false;
21112
+ }
21113
+ const preferred = routing.requirements.preferredEndpointTypes ?? [];
21114
+ const allowed = routing.requirements.allowedEndpointTypes ?? [];
21115
+ return preferred.includes("local") || allowed.length === 0 || allowed.includes("local");
21116
+ }
21117
+ function runOrchestratorJsonCommand(args, cwd = process.cwd()) {
21118
+ const output = (0, import_node_child_process8.execFileSync)("snipara-orchestrator", args, {
21119
+ cwd,
21120
+ encoding: "utf8",
21121
+ stdio: ["ignore", "pipe", "pipe"],
21122
+ timeout: DEFAULT_LOCAL_ORCHESTRATOR_TIMEOUT_MS
21123
+ }).trim();
21124
+ return output ? parseJsonRecord(output, "snipara-orchestrator JSON output") : {};
21125
+ }
21126
+ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = process.cwd()) {
21127
+ try {
21128
+ const catalogArgs = [
21129
+ "local-model-catalog",
21130
+ "--json",
21131
+ "--worker-role",
21132
+ routing.requirements.workerRole
21133
+ ];
21134
+ for (const writeScope of routing.requirements.writeScope ?? []) {
21135
+ catalogArgs.push("--write-scope", writeScope);
21136
+ }
21137
+ if (options.routingLocalBaseUrl) {
21138
+ catalogArgs.push("--base-url", options.routingLocalBaseUrl);
21139
+ }
21140
+ if (options.routingLocalModel) {
21141
+ catalogArgs.push("--model", options.routingLocalModel);
21142
+ } else if (options.routingLocalPreferModel) {
21143
+ catalogArgs.push("--prefer-model", options.routingLocalPreferModel);
21144
+ } else if (options.routeLocalWorkers) {
21145
+ catalogArgs.push("--all-models");
21146
+ }
21147
+ if (options.routingLocalProvider) {
21148
+ catalogArgs.push("--provider", options.routingLocalProvider);
21149
+ }
21150
+ const runtimeCatalog = normalizeAdaptiveRoutingCatalog(
21151
+ runOrchestratorJsonCommand(catalogArgs, cwd)
21152
+ );
21153
+ const routeArgs = [
21154
+ "route",
21155
+ "--dry-run",
21156
+ "--json",
21157
+ "--work-profile-json",
21158
+ JSON.stringify(routing.workProfile),
21159
+ "--requirements-json",
21160
+ JSON.stringify(routing.requirements)
21161
+ ];
21162
+ for (const candidate of runtimeCatalog.candidates) {
21163
+ routeArgs.push("--candidate-json", JSON.stringify(candidate));
21164
+ }
21165
+ const resolution = normalizeAdaptiveRoutingResolution(
21166
+ runOrchestratorJsonCommand(routeArgs, cwd)
21167
+ );
21168
+ const selectedCandidate = adaptiveRoutingSelectedCandidate(resolution);
21169
+ const selectedEndpoint = selectedCandidate && runtimeCatalog.workerEndpoints ? runtimeCatalog.workerEndpoints[selectedCandidate.candidateId] : void 0;
21170
+ const resolutionWarnings = normalizeStringArray3(resolution?.warnings) ?? [];
21171
+ const resolutionReasons = normalizeStringArray3(resolution?.reasons) ?? [];
21172
+ const approvalRequired = booleanValue2(
21173
+ isRecord12(resolution?.policyDecision) ? resolution.policyDecision.approvalRequired : void 0
21174
+ );
21175
+ const gatewayWarnings = uniqueStrings5([
21176
+ ...resolutionWarnings,
21177
+ ...selectedCandidate ? [] : ["Local orchestrator did not select a worker candidate and will fail closed."]
21178
+ ]);
21179
+ const gateway = {
21180
+ source: "local_orchestrator",
21181
+ success: stringValue9(resolution?.status) === "resolved",
21182
+ resolutionStatus: stringValue9(resolution?.status),
21183
+ candidateCount: runtimeCatalog.candidates.length,
21184
+ fallback: stringValue9(resolution?.fallback) ?? routing.requirements.fallback,
21185
+ warnings: gatewayWarnings
21186
+ };
21187
+ return {
21188
+ ...routing,
21189
+ gateway,
21190
+ runtimeCatalog,
21191
+ ...resolution ? { resolution } : {},
21192
+ routingCard: {
21193
+ ...routing.routingCard,
21194
+ ...selectedCandidate?.workerClass ? { recommendedWorkerClass: selectedCandidate.workerClass } : {},
21195
+ ...approvalRequired !== void 0 ? { humanApprovalRequired: approvalRequired } : {},
21196
+ reasons: uniqueStrings5([
21197
+ ...routing.routingCard.reasons,
21198
+ ...resolutionReasons,
21199
+ ...selectedCandidate ? [
21200
+ `local orchestrator resolved worker candidate ${selectedCandidate.candidateId}`,
21201
+ ...selectedCandidate.endpointType ? [`selected worker endpoint is ${selectedCandidate.endpointType}`] : [],
21202
+ ...stringValue9(selectedEndpoint?.model) ? [`selected local model ${String(selectedEndpoint?.model)}`] : []
21203
+ ] : ["local orchestrator could not resolve a concrete worker candidate"]
21204
+ ]),
21205
+ warnings: uniqueStrings5([...routing.routingCard.warnings, ...gatewayWarnings])
21206
+ }
21207
+ };
21208
+ } catch (error) {
21209
+ const warning = `Local orchestrator routing unavailable; keeping current routing metadata (${toPreview(error)}).`;
21210
+ return {
21211
+ ...routing,
21212
+ gateway: {
21213
+ source: "local_orchestrator",
21214
+ success: false,
21215
+ resolutionStatus: "unavailable",
21216
+ candidateCount: 0,
21217
+ fallback: routing.requirements.fallback,
21218
+ warnings: [warning]
21219
+ },
21220
+ routingCard: {
21221
+ ...routing.routingCard,
21222
+ warnings: uniqueStrings5([...routing.routingCard.warnings, warning])
21223
+ }
21224
+ };
21225
+ }
21226
+ }
20973
21227
  function printUploadResult(path23, result) {
20974
21228
  printKeyValue("Uploaded:", path23);
20975
21229
  printCompactObject(result, ["message", "document_id", "documentId", "version", "status"]);
@@ -22456,7 +22710,8 @@ async function workflowRunCommand(options) {
22456
22710
  hostedConfigured
22457
22711
  );
22458
22712
  const adaptiveRoutingDryRun = adaptiveRoutingIntent.shouldBuild ? buildWorkflowAdaptiveRouting(options, adaptiveRoutingPolicy, adaptiveRoutingIntent.warnings) : null;
22459
- const adaptiveRouting = adaptiveRoutingDryRun && adaptiveRoutingIntent.shouldUseHostedCatalog && client ? await enrichAdaptiveRoutingWithHostedCatalog(client, adaptiveRoutingDryRun) : adaptiveRoutingDryRun;
22713
+ const adaptiveRoutingWithCatalog = adaptiveRoutingDryRun && adaptiveRoutingIntent.shouldUseHostedCatalog && client ? await enrichAdaptiveRoutingWithHostedCatalog(client, adaptiveRoutingDryRun) : adaptiveRoutingDryRun;
22714
+ const adaptiveRouting = shouldResolveAdaptiveRoutingLocally(options, adaptiveRoutingWithCatalog) ? enrichAdaptiveRoutingWithLocalOrchestrator(adaptiveRoutingWithCatalog, options) : adaptiveRoutingWithCatalog;
22460
22715
  const orchestratorRecommendation = getOrchestratorRecommendation(options.query, options.mode, {
22461
22716
  policyAutoRoute: options.autoRouteOrchestrator,
22462
22717
  policySource: options.orchestratorPolicySource,
@@ -22679,7 +22934,7 @@ async function workflowRunCommand(options) {
22679
22934
  }
22680
22935
  function shouldBuildAdaptiveRouting(options) {
22681
22936
  return Boolean(
22682
- options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.plannerRetainsReasoning
22937
+ options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.routingLocalBaseUrl || options.routingLocalModel || options.routingLocalPreferModel || options.routingLocalProvider || options.plannerRetainsReasoning
22683
22938
  );
22684
22939
  }
22685
22940
  function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = []) {
@@ -24794,6 +25049,12 @@ workflow.command("run").description("Run a LITE/STANDARD/FULL/orchestrate workfl
24794
25049
  collectOption,
24795
25050
  []
24796
25051
  ).option(
25052
+ "--routing-local-base-url <url>",
25053
+ "Local OpenAI-compatible runtime base URL for explicit worker routing"
25054
+ ).option("--routing-local-model <id>", "Explicit local model id for worker routing").option(
25055
+ "--routing-local-prefer-model <text>",
25056
+ "Prefer a local /v1/models entry containing this text during worker routing"
25057
+ ).option("--routing-local-provider <provider>", "Provider label for local worker routing").option(
24797
25058
  "--planner-retains-reasoning",
24798
25059
  "Mark the main planner as retaining deep reasoning while the worker executes scoped work"
24799
25060
  ).option("--write-plan-file <file>", "Write the generated FULL-mode plan as workflow JSON").option(
@@ -24816,6 +25077,10 @@ workflow.command("run").description("Run a LITE/STANDARD/FULL/orchestrate workfl
24816
25077
  routingWorkerRole: options.routingWorkerRole,
24817
25078
  routingPreferredEndpoints: options.routingPreferredEndpoint,
24818
25079
  routingAllowedEndpoints: options.routingAllowedEndpoint,
25080
+ routingLocalBaseUrl: options.routingLocalBaseUrl,
25081
+ routingLocalModel: options.routingLocalModel,
25082
+ routingLocalPreferModel: options.routingLocalPreferModel,
25083
+ routingLocalProvider: options.routingLocalProvider,
24819
25084
  plannerRetainsReasoning: options.plannerRetainsReasoning ? true : void 0,
24820
25085
  writePlanFile: options.writePlanFile,
24821
25086
  startWorkflowFromPlan: Boolean(options.startWorkflowFromPlan),
@@ -273,6 +273,62 @@ Project credentials stay server-side behind the hosted gateway; local endpoints
273
273
  such as Ollama, LM Studio, AnythingLLM, or other OpenAI-compatible servers must
274
274
  be reachable from the worker execution environment.
275
275
 
276
+ For a local LM Studio or OpenAI-compatible worker, use Companion to emit the
277
+ handoff, then let `snipara-orchestrator` resolve that handoff against an explicit
278
+ local runtime catalog. In the Codex workflow, Codex remains the chief architect,
279
+ lead orchestrator, and quality verifier; Qwen and Devstral are bounded local
280
+ worker candidates whose outputs still need Codex review and proof gates.
281
+
282
+ Use Qwen for reflection, architecture, and documentation:
283
+
284
+ ```bash
285
+ snipara-companion workflow run \
286
+ --mode full \
287
+ --adaptive-routing-dry-run \
288
+ --route-local-workers \
289
+ --routing-worker-role documentation \
290
+ --routing-preferred-endpoint local \
291
+ --routing-allowed-endpoint local \
292
+ --planner-retains-reasoning \
293
+ "Update a scoped docs surface"
294
+
295
+ snipara-orchestrator local-model-catalog \
296
+ --base-url http://127.0.0.1:1234 \
297
+ --model qwen/qwen3-30b-a3b-2507 \
298
+ --worker-role documentation \
299
+ --capability documentation \
300
+ --capability architecture_review \
301
+ --capability planning \
302
+ --json > .snipara/local-qwen-docs-runtime-catalog.json
303
+
304
+ snipara-orchestrator route --dry-run \
305
+ --work-profile-json '{"taskType":"documentation","risk":"low","scope":["docs/**"],"contextBudget":"small","reasoningDepth":"low"}' \
306
+ --requirements-json '{"workerRole":"documentation","plannerRetainsReasoning":true,"preferredEndpointTypes":["local"],"allowedEndpointTypes":["local"],"writeScope":["docs/**"],"capabilities":["documentation"]}' \
307
+ --catalog-file .snipara/local-qwen-docs-runtime-catalog.json \
308
+ --json
309
+ ```
310
+
311
+ Use Devstral for development and refactoring work:
312
+
313
+ ```bash
314
+ snipara-orchestrator local-model-catalog \
315
+ --base-url http://127.0.0.1:1234 \
316
+ --prefer-model devstral \
317
+ --worker-role coding \
318
+ --capability code_edit \
319
+ --capability refactor \
320
+ --json > .snipara/local-devstral-runtime-catalog.json
321
+ ```
322
+
323
+ The local catalog records the OpenAI-compatible routes exposed by LM Studio:
324
+ `GET /v1/models`, `POST /v1/responses`, `POST /v1/chat/completions`,
325
+ `POST /v1/completions`, and `POST /v1/embeddings`. `--prefer-model devstral`
326
+ selects the first `/v1/models` id containing `devstral`; use `--model <id>` to
327
+ pin Qwen or any other exact local model id. This makes Qwen, Devstral, or
328
+ another local model routable through the Companion/Orchestrator contract while
329
+ keeping execution fail-closed: the selected candidate is a receipt-backed worker
330
+ target, not an automatically launched process.
331
+
276
332
  For the open package without Snipara SaaS, add a local policy file:
277
333
 
278
334
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {