snipara-companion 3.2.28 → 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,17 @@
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
+
5
16
  ## New In 3.2.28
6
17
 
7
18
  - Adds bounded Outcome Loop retrieval correlation to generated Codex, Claude,
package/README.md CHANGED
@@ -257,7 +257,11 @@ redacted Coding Intelligence Ledger, not automatic durable memory, worker
257
257
  execution, calibrated confidence, or server-side attestation. Use
258
258
  `workflow producer-report` to inspect local adoption, reason-code counts, sample
259
259
  size, reviewed/rejected/unreviewed counts, invalid artifacts, and calibration
260
- 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`.
261
265
  The report also recognizes exported PR Answer Pack decision-capture artifacts
262
266
  with producer kind `pr_answer_pack_decision_capture`, so calibration can track
263
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
@@ -21549,6 +21549,154 @@ function producerLoopCalibrationStatus(sampleSize, reviewedSampleSize, minReview
21549
21549
  }
21550
21550
  return "reviewable_sample_set";
21551
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
+ }
21552
21700
  function buildProducerLoopReport(options = {}) {
21553
21701
  const cwd = path22.resolve(options.cwd ?? process.cwd());
21554
21702
  const minReviewSampleSize = Math.max(
@@ -21589,12 +21737,16 @@ function buildProducerLoopReport(options = {}) {
21589
21737
  reviewedSampleSize,
21590
21738
  minReviewSampleSize
21591
21739
  );
21740
+ const workerReceiptReport = summarizeWorkerExecutionReceipts(cwd);
21741
+ const workerTrust = buildWorkerTrustRows(workerReceiptReport.samples, minReviewSampleSize);
21592
21742
  const recommendedActions = [
21593
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,
21594
21744
  artifacts.length > 0 && reviewedSampleSize < minReviewSampleSize ? `Review at least ${minReviewSampleSize} local Producer Loop samples before considering any calibration signal.` : void 0,
21595
21745
  unreviewedSampleSize > 0 ? "Mark local samples with workflow producer-review after checking the embedded evidence." : void 0,
21596
21746
  rejectedSampleSize > 0 ? "Inspect rejected Producer Loop samples for false positives, missing context, or reason-code drift." : void 0,
21597
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,
21598
21750
  "Review false positives, missing outcomes, and reason-code drift before any future enforcement."
21599
21751
  ].filter((item) => Boolean(item));
21600
21752
  return {
@@ -21616,6 +21768,13 @@ function buildProducerLoopReport(options = {}) {
21616
21768
  reasonCodes: {
21617
21769
  counts: reasonCodeCounts
21618
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,
21619
21778
  calibration: {
21620
21779
  status: calibrationStatus,
21621
21780
  sampleSize: artifacts.length,
@@ -21815,6 +21974,19 @@ function printProducerLoopReport(report) {
21815
21974
  if (report.invalidArtifacts.length > 0) {
21816
21975
  printKeyValue2("Invalid artifacts:", report.invalidArtifacts.length);
21817
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
+ }
21818
21990
  if (report.recommendedActions.length > 0) {
21819
21991
  console.log("");
21820
21992
  console.log(import_chalk6.default.bold("Recommended Actions"));
@@ -33407,7 +33579,7 @@ function applyLocalContextMutationPlan(options) {
33407
33579
  writtenFiles
33408
33580
  };
33409
33581
  }
33410
- function listJsonFiles(dir) {
33582
+ function listJsonFiles2(dir) {
33411
33583
  if (!fs30.existsSync(dir)) return [];
33412
33584
  return fs30.readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => path30.join(dir, name)).sort((left, right) => left.localeCompare(right));
33413
33585
  }
@@ -33534,7 +33706,7 @@ function collectWorkflowDriftSignals(cwd) {
33534
33706
  }
33535
33707
  function collectDecisionDriftSignals(cwd) {
33536
33708
  const pendingDir = path30.join(cwd, ".snipara", "decisions", "pending");
33537
- const pending = listJsonFiles(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33709
+ const pending = listJsonFiles2(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33538
33710
  return [
33539
33711
  pending.length > 0 ? {
33540
33712
  id: "decision-requests-pending",
@@ -33558,8 +33730,8 @@ function collectDecisionDriftSignals(cwd) {
33558
33730
  ];
33559
33731
  }
33560
33732
  function collectContextControlDriftSignals(cwd) {
33561
- const planFiles = listJsonFiles(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
33562
- 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));
33563
33735
  const receiptPlanHashes = new Set(
33564
33736
  receiptFiles.map((file) => readJsonFileSafe(file).value).filter(isContextMutationApplyReceipt).map((receipt) => receipt.planHash)
33565
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.28",
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": {