snipara-companion 3.2.27 → 3.2.29

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/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  Release notes for `snipara-companion`, newest first.
4
4
 
5
+ ## New In 3.2.29
6
+
7
+ - Extends `workflow producer-report` with attributed gated-execution samples
8
+ joined to their persisted supervisor reviews.
9
+ - Adds per-`workerId`/`workCategory` `workerTrust` breakdowns with execution,
10
+ review, accepted, blocked, incomplete receipt-family, and workflow fingerprint
11
+ counts.
12
+ - Keeps the read model deliberately supervised: every pair remains
13
+ `probation_supervised`, `hardGateReady=false`, and lists the evidence still
14
+ required before the separate Trust Promotion gates can be implemented.
15
+
16
+ ## New In 3.2.28
17
+
18
+ - Adds bounded Outcome Loop retrieval correlation to generated Codex, Claude,
19
+ Cursor, VS Code/Continue, and generic HTTP MCP references through
20
+ `SNIPARA_SESSION_ID` / `X-Snipara-Session-Id`.
21
+ - Prints the active correlation session during `init` and documents the
22
+ explicit `correlation_context.session_id` fallback for MCP clients that
23
+ cannot inject environment-backed headers.
24
+ - Keeps the correlation identifier telemetry-only and project-scoped; it does
25
+ not alter authentication or authorization.
26
+
5
27
  ## New In 3.2.27
6
28
 
7
29
  - Scopes `context-control drift` dirty Git detection to the ProjectContext
package/README.md CHANGED
@@ -49,6 +49,18 @@ Use this after `create-snipara` activation. `create-snipara` remains the
49
49
  canonical engine for first workspace setup; Companion owns the repeatable local
50
50
  continuity loop after that.
51
51
 
52
+ ## Retrieval And Outcome Correlation
53
+
54
+ `snipara-companion init` creates and prints a bounded workspace session ID.
55
+ Export it as `SNIPARA_SESSION_ID` before starting Codex, Claude, Cursor, VS Code,
56
+ Continue, or another HTTP MCP client. Generated configs forward it as
57
+ `X-Snipara-Session-Id`, while canonical execution events use the same Companion
58
+ session automatically. Clients without environment-backed headers can pass the
59
+ same value as `correlation_context.session_id` on retrieval tools.
60
+
61
+ The identifier is opaque, project-scoped telemetry. It does not grant access or
62
+ change authorization, and explicit per-call correlation remains authoritative.
63
+
52
64
  Example output excerpt:
53
65
 
54
66
  ```text
@@ -245,7 +257,11 @@ redacted Coding Intelligence Ledger, not automatic durable memory, worker
245
257
  execution, calibrated confidence, or server-side attestation. Use
246
258
  `workflow producer-report` to inspect local adoption, reason-code counts, sample
247
259
  size, reviewed/rejected/unreviewed counts, invalid artifacts, and calibration
248
- caveats before any future hard gate.
260
+ caveats before any future hard gate. The report also joins attributed gated
261
+ receipts from `.snipara/orchestrator/executions/` with persisted supervisor
262
+ reviews, then emits `workerReceipts` and a per-`workerId`/`workCategory`
263
+ `workerTrust` breakdown. This is observability only: every pair remains
264
+ `probation_supervised` and `hardGateReady=false`.
249
265
  The report also recognizes exported PR Answer Pack decision-capture artifacts
250
266
  with producer kind `pr_answer_pack_decision_capture`, so calibration can track
251
267
  more than the workflow producer once those artifacts are present locally.
package/dist/index.d.ts CHANGED
@@ -3543,6 +3543,39 @@ interface ProducerLoopArtifactReportSummary {
3543
3543
  reviewedAt?: string;
3544
3544
  reviewer?: string;
3545
3545
  }
3546
+ interface WorkerExecutionReceiptReportSummary {
3547
+ receiptId: string;
3548
+ schemaVersion: string;
3549
+ recordedAt?: string;
3550
+ workerId: string;
3551
+ workCategory: string;
3552
+ routingCardRef?: string;
3553
+ workflowFingerprint?: string;
3554
+ executionActor: string;
3555
+ status?: string;
3556
+ reviewStatus: "review_pending" | "accepted" | "blocked";
3557
+ executed: boolean;
3558
+ receiptFamilyComplete: boolean;
3559
+ missingReceiptFamilies: string[];
3560
+ path: string;
3561
+ relativePath: string;
3562
+ reviewPath?: string;
3563
+ reviewRelativePath?: string;
3564
+ }
3565
+ interface WorkerTrustReportRow {
3566
+ workerId: string;
3567
+ workCategory: string;
3568
+ state: "probation_supervised";
3569
+ sampleSize: number;
3570
+ executedSampleSize: number;
3571
+ reviewedSampleSize: number;
3572
+ verifiedSampleSize: number;
3573
+ blockedSampleSize: number;
3574
+ incompleteReceiptSampleSize: number;
3575
+ workflowFingerprints: string[];
3576
+ hardGateReady: false;
3577
+ nextRequired: string[];
3578
+ }
3546
3579
  interface ProducerLoopReport {
3547
3580
  version: typeof PRODUCER_LOOP_REPORT_VERSION;
3548
3581
  generatedAt: string;
@@ -3566,6 +3599,17 @@ interface ProducerLoopReport {
3566
3599
  reasonCodes: {
3567
3600
  counts: Record<string, number>;
3568
3601
  };
3602
+ workerReceipts: {
3603
+ sourceDirectories: string[];
3604
+ sampleSize: number;
3605
+ samples: WorkerExecutionReceiptReportSummary[];
3606
+ invalidArtifacts: Array<{
3607
+ path: string;
3608
+ relativePath: string;
3609
+ error: string;
3610
+ }>;
3611
+ };
3612
+ workerTrust: WorkerTrustReportRow[];
3569
3613
  calibration: {
3570
3614
  status: "no_samples" | "insufficient_samples" | "reviewable_sample_set";
3571
3615
  sampleSize: number;
package/dist/index.js CHANGED
@@ -5641,6 +5641,7 @@ Claude Code should apply this workflow automatically for project-specific work;
5641
5641
 
5642
5642
  - Bound Snipara project: \`${projectSlug}\`
5643
5643
  - Hosted MCP endpoint: \`${buildHostedMcpEndpoint(projectSlug)}\`
5644
+ - Keep \`SNIPARA_SESSION_ID\` equal to the active Companion session. Generated MCP configs send it as \`X-Snipara-Session-Id\`; when a client cannot send that header, pass the same value as \`correlation_context.session_id\` on retrieval tools.
5644
5645
  - At the start of substantial work, validate the hosted MCP surface with a tool-oriented call, then use \`snipara_recall\` and a targeted \`snipara_context_query\` before falling back to local search.
5645
5646
  - Do not treat empty MCP resources/templates as an outage. If the tool surface looks incomplete, call \`snipara_help(list_all=true)\` and compare exact tool names.
5646
5647
  - Use \`snipara_context_query\` for docs, business context, architecture notes, runbooks, and source truth. Use \`snipara_get_chunk\` for exact cited sections when references are returned.
@@ -5660,6 +5661,7 @@ function buildSharedWorkflowInstructionBlock(projectSlug, client) {
5660
5661
  This workspace is bound to Snipara project \`${projectSlug}\` for ${formatClientName(client)}. Agents should use Snipara automatically for project-specific context, decisions, and workflow state.
5661
5662
 
5662
5663
  - Hosted MCP endpoint: \`${buildHostedMcpEndpoint(projectSlug)}\`
5664
+ - Keep \`SNIPARA_SESSION_ID\` equal to the active Companion session. Generated MCP configs send it as \`X-Snipara-Session-Id\`; when a client cannot send that header, pass the same value as \`correlation_context.session_id\` on retrieval tools.
5663
5665
  - At the start of substantial work, validate the hosted MCP surface with a tool-oriented call, then use \`snipara_recall\` and a targeted \`snipara_context_query\` before falling back to local search.
5664
5666
  - Do not treat empty MCP resources/templates as an outage. If the tool surface looks incomplete, call \`snipara_help(list_all=true)\` and compare exact tool names.
5665
5667
  - Use \`snipara_context_query\` for docs, business context, architecture notes, runbooks, and source truth. Use \`snipara_get_chunk\` for exact cited sections when references are returned.
@@ -5911,6 +5913,7 @@ function generateCodexConfigString(projectSlug) {
5911
5913
  type = "streamable_http"
5912
5914
  url = "https://api.snipara.com/mcp/${projectSlug}"
5913
5915
  bearer_token_env_var = "SNIPARA_API_KEY"
5916
+ env_http_headers = { "X-Snipara-Session-Id" = "SNIPARA_SESSION_ID" }
5914
5917
  `;
5915
5918
  }
5916
5919
  function generateGenericMcpReferenceString(client, projectSlug) {
@@ -5920,7 +5923,8 @@ function generateGenericMcpReferenceString(client, projectSlug) {
5920
5923
  name: "snipara",
5921
5924
  url: `https://api.snipara.com/mcp/${projectSlug}`,
5922
5925
  headers: {
5923
- "X-API-Key": "${SNIPARA_API_KEY}"
5926
+ "X-API-Key": "${SNIPARA_API_KEY}",
5927
+ "X-Snipara-Session-Id": "${SNIPARA_SESSION_ID}"
5924
5928
  }
5925
5929
  },
5926
5930
  null,
@@ -5935,7 +5939,8 @@ function generateGenericMcpReferenceString(client, projectSlug) {
5935
5939
  type: "http",
5936
5940
  url: `https://api.snipara.com/mcp/${projectSlug}`,
5937
5941
  headers: {
5938
- "X-API-Key": "${SNIPARA_API_KEY}"
5942
+ "X-API-Key": "${SNIPARA_API_KEY}",
5943
+ "X-Snipara-Session-Id": "${SNIPARA_SESSION_ID}"
5939
5944
  }
5940
5945
  }
5941
5946
  }
@@ -5995,6 +6000,7 @@ async function callSniparaTool(name: string, args: Record<string, unknown>): Pro
5995
6000
  headers: {
5996
6001
  "Content-Type": "application/json",
5997
6002
  "X-API-Key": SNIPARA_API_KEY,
6003
+ "X-Snipara-Session-Id": process.env.SNIPARA_SESSION_ID || "",
5998
6004
  },
5999
6005
  body: JSON.stringify({
6000
6006
  jsonrpc: "2.0",
@@ -6561,12 +6567,13 @@ async function initCommand(options) {
6561
6567
  existingProjectId: existingConfig.projectId,
6562
6568
  selectedProject
6563
6569
  });
6570
+ const sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6564
6571
  saveConfig(
6565
6572
  {
6566
6573
  apiKey,
6567
6574
  apiUrl: existingConfig.apiUrl,
6568
6575
  projectId: projectIdentifier,
6569
- sessionId: `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
6576
+ sessionId,
6570
6577
  client: selectedClient
6571
6578
  },
6572
6579
  { cwd: projectDir, scope: "workspace" }
@@ -6688,6 +6695,10 @@ async function initCommand(options) {
6688
6695
  console.log(`Companion config: ${configPaths.companion}`);
6689
6696
  console.log(`Selected project: ${formatProjectChoice(selectedProject)}`);
6690
6697
  console.log(`Selected client: ${formatClientName(selectedClient)}`);
6698
+ console.log(`Correlation session: ${sessionId}`);
6699
+ console.log(
6700
+ "Export SNIPARA_SESSION_ID with this value before starting the MCP client so served context and execution outcomes share one bounded session."
6701
+ );
6691
6702
  if (options.withHooks) {
6692
6703
  console.log("\nNext steps:");
6693
6704
  if (hookClient === "cursor") {
@@ -21538,6 +21549,154 @@ function producerLoopCalibrationStatus(sampleSize, reviewedSampleSize, minReview
21538
21549
  }
21539
21550
  return "reviewable_sample_set";
21540
21551
  }
21552
+ var WORKER_RECEIPT_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "executions");
21553
+ var WORKER_REVIEW_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "reviews");
21554
+ var REQUIRED_WORKER_RECEIPT_FAMILIES = [
21555
+ "handoffReceiptId",
21556
+ "claimId",
21557
+ "proofReceiptIds",
21558
+ "outcomeReceiptId",
21559
+ "brainUpdateReceiptId"
21560
+ ];
21561
+ function listJsonFiles(relativeDir, cwd) {
21562
+ const directory = path22.join(cwd, relativeDir);
21563
+ if (!fs23.existsSync(directory)) {
21564
+ return [];
21565
+ }
21566
+ return fs23.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path22.join(directory, entry.name)).sort();
21567
+ }
21568
+ function readWorkerReviewFiles(cwd) {
21569
+ const reviews = /* @__PURE__ */ new Map();
21570
+ const invalid = [];
21571
+ for (const filePath of listJsonFiles(WORKER_REVIEW_RELATIVE_DIR, cwd)) {
21572
+ try {
21573
+ const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
21574
+ if (!isRecord10(parsed)) {
21575
+ throw new Error("review must be a JSON object");
21576
+ }
21577
+ const receiptId = stringValue7(parsed.receiptId);
21578
+ if (!receiptId) {
21579
+ throw new Error("review is missing receiptId");
21580
+ }
21581
+ reviews.set(receiptId, { value: parsed, path: filePath });
21582
+ } catch (error) {
21583
+ invalid.push({
21584
+ path: filePath,
21585
+ relativePath: toProjectRelativePath3(filePath, cwd),
21586
+ error: error instanceof Error ? error.message : String(error)
21587
+ });
21588
+ }
21589
+ }
21590
+ return { reviews, invalid };
21591
+ }
21592
+ function missingWorkerReceiptFamilies(receipt) {
21593
+ const refs = isRecord10(receipt.receiptRefs) ? receipt.receiptRefs : isRecord10(receipt.receipt) ? receipt.receipt : {};
21594
+ return REQUIRED_WORKER_RECEIPT_FAMILIES.filter((family) => {
21595
+ const value = refs[family];
21596
+ return family === "proofReceiptIds" ? !Array.isArray(value) || value.filter((item) => Boolean(stringValue7(item))).length === 0 : !stringValue7(value);
21597
+ });
21598
+ }
21599
+ function summarizeWorkerExecutionReceipts(cwd) {
21600
+ const reviewFiles = readWorkerReviewFiles(cwd);
21601
+ const invalid = [...reviewFiles.invalid];
21602
+ const samples = [];
21603
+ for (const filePath of listJsonFiles(WORKER_RECEIPT_RELATIVE_DIR, cwd)) {
21604
+ try {
21605
+ const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
21606
+ if (!isRecord10(parsed)) {
21607
+ throw new Error("receipt must be a JSON object");
21608
+ }
21609
+ const receiptId = stringValue7(parsed.receiptId);
21610
+ const schemaVersion = stringValue7(parsed.schemaVersion);
21611
+ if (!receiptId || !schemaVersion) {
21612
+ throw new Error("receipt is missing receiptId or schemaVersion");
21613
+ }
21614
+ const attribution = isRecord10(parsed.workerAttribution) ? parsed.workerAttribution : {};
21615
+ const gate2 = isRecord10(parsed.gate) ? parsed.gate : {};
21616
+ const review = reviewFiles.reviews.get(receiptId);
21617
+ const reviewStatusValue = stringValue7(review?.value.reviewStatus);
21618
+ const reviewStatus = reviewStatusValue === "accepted" ? "accepted" : reviewStatusValue === "blocked" ? "blocked" : "review_pending";
21619
+ const workerId = stringValue7(parsed.workerId) ?? stringValue7(attribution.workerId) ?? stringValue7(parsed.selectedWorkerCandidateId) ?? stringValue7(gate2.selectedWorkerCandidateId) ?? "main_agent";
21620
+ const workCategory = stringValue7(parsed.workCategory) ?? stringValue7(attribution.workCategory) ?? "unknown";
21621
+ const executionActor = stringValue7(parsed.executionActor) ?? stringValue7(attribution.executionActor) ?? (Number(parsed.workersSpawned ?? 0) > 0 ? "worker" : "main_agent");
21622
+ const missingReceiptFamilies = missingWorkerReceiptFamilies(parsed);
21623
+ samples.push({
21624
+ receiptId,
21625
+ schemaVersion,
21626
+ recordedAt: stringValue7(parsed.recordedAt),
21627
+ workerId,
21628
+ workCategory,
21629
+ routingCardRef: stringValue7(parsed.routingCardRef) ?? stringValue7(attribution.routingCardRef),
21630
+ workflowFingerprint: stringValue7(parsed.workflowFingerprint) ?? stringValue7(attribution.workflowFingerprint),
21631
+ executionActor,
21632
+ status: stringValue7(parsed.status),
21633
+ reviewStatus,
21634
+ executed: executionActor === "worker" || Number(parsed.workersSpawned ?? 0) > 0,
21635
+ receiptFamilyComplete: missingReceiptFamilies.length === 0,
21636
+ missingReceiptFamilies,
21637
+ path: filePath,
21638
+ relativePath: toProjectRelativePath3(filePath, cwd),
21639
+ ...review ? {
21640
+ reviewPath: review.path,
21641
+ reviewRelativePath: toProjectRelativePath3(review.path, cwd)
21642
+ } : {}
21643
+ });
21644
+ } catch (error) {
21645
+ invalid.push({
21646
+ path: filePath,
21647
+ relativePath: toProjectRelativePath3(filePath, cwd),
21648
+ error: error instanceof Error ? error.message : String(error)
21649
+ });
21650
+ }
21651
+ }
21652
+ samples.sort(
21653
+ (left, right) => (left.recordedAt ?? left.receiptId).localeCompare(right.recordedAt ?? right.receiptId)
21654
+ );
21655
+ return { samples, invalid };
21656
+ }
21657
+ function buildWorkerTrustRows(samples, minReviewSampleSize) {
21658
+ const grouped = /* @__PURE__ */ new Map();
21659
+ for (const sample of samples) {
21660
+ const key = `${sample.workerId}\0${sample.workCategory}`;
21661
+ grouped.set(key, [...grouped.get(key) ?? [], sample]);
21662
+ }
21663
+ return [...grouped.values()].map((group) => {
21664
+ const reviewedSampleSize = group.filter(
21665
+ (sample) => sample.reviewStatus !== "review_pending"
21666
+ ).length;
21667
+ const verifiedSampleSize = group.filter(
21668
+ (sample) => sample.reviewStatus === "accepted"
21669
+ ).length;
21670
+ const incompleteReceiptSampleSize = group.filter(
21671
+ (sample) => !sample.receiptFamilyComplete
21672
+ ).length;
21673
+ const nextRequired = [
21674
+ reviewedSampleSize < minReviewSampleSize ? `${minReviewSampleSize - reviewedSampleSize} more supervised review sample(s)` : void 0,
21675
+ verifiedSampleSize < 3 ? `${3 - verifiedSampleSize} more accepted verified sample(s)` : void 0,
21676
+ incompleteReceiptSampleSize > 0 ? `${incompleteReceiptSampleSize} sample(s) need complete receipt families` : void 0,
21677
+ group[0].workCategory === "unknown" ? "classify legacy samples by work category" : void 0,
21678
+ "Trust Promotion gate implementation remains required"
21679
+ ].filter((item) => Boolean(item));
21680
+ return {
21681
+ workerId: group[0].workerId,
21682
+ workCategory: group[0].workCategory,
21683
+ state: "probation_supervised",
21684
+ sampleSize: group.length,
21685
+ executedSampleSize: group.filter((sample) => sample.executed).length,
21686
+ reviewedSampleSize,
21687
+ verifiedSampleSize,
21688
+ blockedSampleSize: group.filter((sample) => sample.reviewStatus === "blocked").length,
21689
+ incompleteReceiptSampleSize,
21690
+ workflowFingerprints: uniqueStrings18(
21691
+ group.map((sample) => sample.workflowFingerprint).filter((value) => Boolean(value))
21692
+ ),
21693
+ hardGateReady: false,
21694
+ nextRequired
21695
+ };
21696
+ }).sort(
21697
+ (left, right) => left.workerId === right.workerId ? left.workCategory.localeCompare(right.workCategory) : left.workerId.localeCompare(right.workerId)
21698
+ );
21699
+ }
21541
21700
  function buildProducerLoopReport(options = {}) {
21542
21701
  const cwd = path22.resolve(options.cwd ?? process.cwd());
21543
21702
  const minReviewSampleSize = Math.max(
@@ -21578,12 +21737,16 @@ function buildProducerLoopReport(options = {}) {
21578
21737
  reviewedSampleSize,
21579
21738
  minReviewSampleSize
21580
21739
  );
21740
+ const workerReceiptReport = summarizeWorkerExecutionReceipts(cwd);
21741
+ const workerTrust = buildWorkerTrustRows(workerReceiptReport.samples, minReviewSampleSize);
21581
21742
  const recommendedActions = [
21582
21743
  artifacts.length === 0 ? "Run a current Producer Loop producer, such as workflow phase-commit/final-commit or PR Answer Pack decision capture, to create samples." : void 0,
21583
21744
  artifacts.length > 0 && reviewedSampleSize < minReviewSampleSize ? `Review at least ${minReviewSampleSize} local Producer Loop samples before considering any calibration signal.` : void 0,
21584
21745
  unreviewedSampleSize > 0 ? "Mark local samples with workflow producer-review after checking the embedded evidence." : void 0,
21585
21746
  rejectedSampleSize > 0 ? "Inspect rejected Producer Loop samples for false positives, missing context, or reason-code drift." : void 0,
21586
21747
  invalidArtifacts.length > 0 ? "Inspect invalid Producer Loop artifacts before trusting adoption counts." : void 0,
21748
+ workerReceiptReport.samples.length === 0 ? "Run agents execute-gated and persist agents review-gated results to create worker-attributed receipt samples." : void 0,
21749
+ workerReceiptReport.invalid.length > 0 ? "Inspect invalid worker execution receipts or reviews before trusting per-worker counts." : void 0,
21587
21750
  "Review false positives, missing outcomes, and reason-code drift before any future enforcement."
21588
21751
  ].filter((item) => Boolean(item));
21589
21752
  return {
@@ -21605,6 +21768,13 @@ function buildProducerLoopReport(options = {}) {
21605
21768
  reasonCodes: {
21606
21769
  counts: reasonCodeCounts
21607
21770
  },
21771
+ workerReceipts: {
21772
+ sourceDirectories: [WORKER_RECEIPT_RELATIVE_DIR, WORKER_REVIEW_RELATIVE_DIR],
21773
+ sampleSize: workerReceiptReport.samples.length,
21774
+ samples: workerReceiptReport.samples,
21775
+ invalidArtifacts: workerReceiptReport.invalid
21776
+ },
21777
+ workerTrust,
21608
21778
  calibration: {
21609
21779
  status: calibrationStatus,
21610
21780
  sampleSize: artifacts.length,
@@ -21804,6 +21974,19 @@ function printProducerLoopReport(report) {
21804
21974
  if (report.invalidArtifacts.length > 0) {
21805
21975
  printKeyValue2("Invalid artifacts:", report.invalidArtifacts.length);
21806
21976
  }
21977
+ printKeyValue2("Worker receipts:", report.workerReceipts.sampleSize);
21978
+ if (report.workerReceipts.invalidArtifacts.length > 0) {
21979
+ printKeyValue2("Invalid worker evidence:", report.workerReceipts.invalidArtifacts.length);
21980
+ }
21981
+ if (report.workerTrust.length > 0) {
21982
+ console.log("");
21983
+ console.log(import_chalk6.default.bold("Worker Trust (supervised probation)"));
21984
+ for (const row of report.workerTrust) {
21985
+ console.log(
21986
+ `- ${row.workerId} / ${row.workCategory}: ${row.verifiedSampleSize} accepted, ${row.blockedSampleSize} blocked, ${row.reviewedSampleSize}/${row.sampleSize} reviewed`
21987
+ );
21988
+ }
21989
+ }
21807
21990
  if (report.recommendedActions.length > 0) {
21808
21991
  console.log("");
21809
21992
  console.log(import_chalk6.default.bold("Recommended Actions"));
@@ -33396,7 +33579,7 @@ function applyLocalContextMutationPlan(options) {
33396
33579
  writtenFiles
33397
33580
  };
33398
33581
  }
33399
- function listJsonFiles(dir) {
33582
+ function listJsonFiles2(dir) {
33400
33583
  if (!fs30.existsSync(dir)) return [];
33401
33584
  return fs30.readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => path30.join(dir, name)).sort((left, right) => left.localeCompare(right));
33402
33585
  }
@@ -33523,7 +33706,7 @@ function collectWorkflowDriftSignals(cwd) {
33523
33706
  }
33524
33707
  function collectDecisionDriftSignals(cwd) {
33525
33708
  const pendingDir = path30.join(cwd, ".snipara", "decisions", "pending");
33526
- const pending = listJsonFiles(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33709
+ const pending = listJsonFiles2(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33527
33710
  return [
33528
33711
  pending.length > 0 ? {
33529
33712
  id: "decision-requests-pending",
@@ -33547,8 +33730,8 @@ function collectDecisionDriftSignals(cwd) {
33547
33730
  ];
33548
33731
  }
33549
33732
  function collectContextControlDriftSignals(cwd) {
33550
- const planFiles = listJsonFiles(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
33551
- const receiptFiles = listJsonFiles(path30.join(cwd, CONTEXT_CONTROL_APPLIED_RELATIVE_DIR));
33733
+ const planFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
33734
+ const receiptFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_APPLIED_RELATIVE_DIR));
33552
33735
  const receiptPlanHashes = new Set(
33553
33736
  receiptFiles.map((file) => readJsonFileSafe(file).value).filter(isContextMutationApplyReceipt).map((receipt) => receipt.planHash)
33554
33737
  );
@@ -324,7 +324,11 @@ snipara-companion workflow resume --include-session-context
324
324
  workflow phase/final commits or exported PR Answer Pack decision-capture
325
325
  producers, then reports adoption, producer kinds, workflow ids, reason-code
326
326
  counts, invalid artifacts, sample size, reviewed/rejected/unreviewed counts,
327
- and calibration caveats with `hardGateReady=false`.
327
+ and calibration caveats with `hardGateReady=false`. It also scans attributed
328
+ gated receipts and persisted reviews under `.snipara/orchestrator/`, reports
329
+ receipt-family completeness, and groups supervised evidence by
330
+ `(workerId, workCategory)` in `workerTrust`. It never promotes a worker or
331
+ changes an execution gate.
328
332
  - `workflow producer-review` marks one local Producer Loop artifact as
329
333
  `sample_reviewed` or `sample_rejected` after operator review. Use
330
334
  `--artifact <path|file|artifactId>` for an exact sample or `--latest` for the
@@ -821,6 +825,15 @@ Mistral generates MCP-first files (`MISTRAL.md`, Vibe config, Le Chat connector
821
825
  reference, and LangChain `ChatMistralAI.bindTools` snippets); Mistral request
822
826
  hooks are model request hooks, not local agent lifecycle hooks.
823
827
 
828
+ All generated HTTP/SSE references include a bounded correlation header sourced
829
+ from `SNIPARA_SESSION_ID`. Set it to the session printed by `init` before
830
+ starting Codex, Claude, Cursor, VS Code/Continue, GLM, or another generic MCP
831
+ host. The hosted server uses the header only for project-scoped retrieval
832
+ telemetry and ignores invalid values. If the host cannot inject environment
833
+ headers, pass the same value as `correlation_context.session_id` on
834
+ `snipara_context_query`, `snipara_recall`, `snipara_search`, `snipara_ask`, and
835
+ `snipara_get_chunk`.
836
+
824
837
  OpenClaw hooks remain separate:
825
838
 
826
839
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.27",
3
+ "version": "3.2.29",
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": {