snipara-companion 3.2.9 → 3.2.10

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ProjectIntentDetectionResult, ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary } from '@snipara/project-intelligence-contracts';
2
+ import { ProjectIntentDetectionResult, ProjectPolicyDecision, ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary, ProjectPolicyRule } from '@snipara/project-intelligence-contracts';
3
3
 
4
4
  declare function resolveQueryFromToolInput(toolInput?: string, tool?: string): string | null;
5
5
 
@@ -2030,6 +2030,7 @@ interface ProjectIntelligenceBrief {
2030
2030
  codeImpact?: Record<string, unknown>;
2031
2031
  codeImpactSourceSelection?: CodeGraphSourceSelection;
2032
2032
  localSessionSnapshot?: SessionSnapshot;
2033
+ projectPolicyDecision?: ProjectPolicyDecision;
2033
2034
  verificationPlan?: VerificationPlan;
2034
2035
  judgmentCard?: ProjectIntelligenceJudgmentCard;
2035
2036
  errors: Array<{
@@ -2375,7 +2376,7 @@ declare function formatCompanionEngineeringLeadPlanReport(report: CompanionEngin
2375
2376
  declare function leadPlanCommand(options: LeadPlanCommandOptions): Promise<void>;
2376
2377
 
2377
2378
  type ProjectPolicyGateSeverity = "advisory" | "required_action" | "block";
2378
- type ProjectPolicyGateSurface = "release" | "schema" | "auth" | "billing" | "deploy" | "package_surface";
2379
+ type ProjectPolicyGateSurface = "release" | "schema" | "auth" | "billing" | "deploy" | "package_surface" | "project_policy";
2379
2380
  type ProjectPolicyGateSampleMode = "not_applicable" | "structural" | "explicit_contract" | "sample_gated";
2380
2381
  interface ProjectPolicyGateSampleGate {
2381
2382
  mode: ProjectPolicyGateSampleMode;
@@ -2417,6 +2418,7 @@ interface ProjectPolicyGatesResult {
2417
2418
  release: boolean;
2418
2419
  summary: ProjectPolicyGatesSummary;
2419
2420
  gates: ProjectPolicyGateDecision[];
2421
+ projectPolicyDecision?: ProjectPolicyDecision;
2420
2422
  suggestedCommands: string[];
2421
2423
  }
2422
2424
  interface EvaluateProjectPolicyGatesInput {
@@ -2438,6 +2440,10 @@ interface EvaluateProjectPolicyGatesInput {
2438
2440
  command?: string;
2439
2441
  };
2440
2442
  judgmentCard?: ProjectIntelligenceJudgmentCard;
2443
+ projectPolicy?: {
2444
+ rules?: ProjectPolicyRule[];
2445
+ decision?: ProjectPolicyDecision;
2446
+ };
2441
2447
  }
2442
2448
  declare function evaluateProjectPolicyGates(input: EvaluateProjectPolicyGatesInput): ProjectPolicyGatesResult;
2443
2449
  declare function formatPolicyGateDecision(gateDecision: ProjectPolicyGateDecision): string[];
package/dist/index.js CHANGED
@@ -10634,6 +10634,151 @@ function buildIntentDetectionFromTimeline(events) {
10634
10634
  };
10635
10635
  }
10636
10636
 
10637
+ // ../project-intelligence-contracts/src/project-policy.ts
10638
+ var PROJECT_POLICY_DECISION_VERSION = "snipara.project_policy.decision.v0";
10639
+ var PROJECT_POLICY_RECEIPT_VERSION = "snipara.project_policy.receipt.v0";
10640
+ var VERDICT_RANK = {
10641
+ allow: 0,
10642
+ warn: 1,
10643
+ require_review: 2,
10644
+ block: 3
10645
+ };
10646
+ function evaluateProjectPolicyDecision(input) {
10647
+ const generatedAt = isoTimestamp2(input.now);
10648
+ const actionText = normalizeText2(
10649
+ [
10650
+ input.action.summary,
10651
+ input.action.surface,
10652
+ ...input.action.changedFiles ?? [],
10653
+ ...input.action.commands ?? [],
10654
+ metadataToText(input.action.metadata)
10655
+ ].filter((item) => Boolean(item))
10656
+ );
10657
+ const matchedRules = normalizeRules(input.rules).filter(
10658
+ (rule) => ruleMatchesAction(rule, actionText, input.action)
10659
+ );
10660
+ const verdict = matchedRules.reduce(
10661
+ (current, rule) => strongerVerdict(current, verdictForRule(rule, actionText)),
10662
+ "allow"
10663
+ );
10664
+ const confidence = matchedRules.length === 0 ? 1 : Math.max(...matchedRules.map((rule) => normalizeConfidence(rule.confidence)));
10665
+ const reasonCodes = buildReasonCodes(matchedRules, verdict);
10666
+ const requiredActions = uniqueStrings7(
10667
+ matchedRules.flatMap((rule) => rule.requiredActions ?? [rule.requirement])
10668
+ );
10669
+ const warnings = matchedRules.filter((rule) => verdictForRule(rule, actionText) === "warn").map((rule) => rule.requirement);
10670
+ const actionFingerprint = hashDecisionJsonValue(input.action);
10671
+ const receipt = {
10672
+ version: PROJECT_POLICY_RECEIPT_VERSION,
10673
+ receiptId: buildReceiptId(actionFingerprint, matchedRules),
10674
+ generatedAt,
10675
+ verdict,
10676
+ actionFingerprint,
10677
+ ruleRefs: matchedRules.map((rule) => rule.source.ref),
10678
+ confidence,
10679
+ reasonCodes,
10680
+ overrideAllowed: verdict !== "block",
10681
+ overrideRequiresReason: verdict !== "allow"
10682
+ };
10683
+ return {
10684
+ version: PROJECT_POLICY_DECISION_VERSION,
10685
+ generatedAt,
10686
+ verdict,
10687
+ confidence,
10688
+ matchedRules,
10689
+ reasonCodes,
10690
+ requiredActions,
10691
+ warnings,
10692
+ receipt
10693
+ };
10694
+ }
10695
+ function projectPolicyVerdictToGateSeverity(verdict) {
10696
+ if (verdict === "block") return "block";
10697
+ if (verdict === "require_review") return "required_action";
10698
+ return "advisory";
10699
+ }
10700
+ function normalizeRules(rules) {
10701
+ return rules.filter(
10702
+ (rule) => rule.id.trim() && rule.title.trim() && rule.requirement.trim() && rule.source.reviewStatus !== "rejected" && normalizeConfidence(rule.confidence) >= 0.5
10703
+ );
10704
+ }
10705
+ function ruleMatchesAction(rule, actionText, action) {
10706
+ if (action.surface && rule.scope !== "custom" && action.surface !== rule.scope) {
10707
+ return false;
10708
+ }
10709
+ const anchors = uniqueStrings7([
10710
+ rule.scope,
10711
+ ...rule.anchors,
10712
+ ...rule.forbiddenActions ?? [],
10713
+ ...rule.requiredActions ?? []
10714
+ ]);
10715
+ return anchors.some((anchor) => actionText.includes(normalizeText2(anchor)));
10716
+ }
10717
+ function verdictForRule(rule, actionText) {
10718
+ const forbidden = (rule.forbiddenActions ?? []).some(
10719
+ (item) => actionText.includes(normalizeText2(item))
10720
+ );
10721
+ if (rule.strength === "blocking" && forbidden && normalizeConfidence(rule.confidence) >= 0.8) {
10722
+ return "block";
10723
+ }
10724
+ if (rule.strength === "blocking" || rule.strength === "review_required") {
10725
+ return "require_review";
10726
+ }
10727
+ return "warn";
10728
+ }
10729
+ function strongerVerdict(left, right) {
10730
+ return VERDICT_RANK[right] > VERDICT_RANK[left] ? right : left;
10731
+ }
10732
+ function buildReasonCodes(rules, verdict) {
10733
+ if (rules.length === 0) {
10734
+ return ["project_policy_no_match"];
10735
+ }
10736
+ return uniqueStrings7([
10737
+ `project_policy_${verdict}`,
10738
+ ...rules.map((rule) => `project_policy_${rule.scope}`),
10739
+ ...rules.map((rule) => `project_policy_${rule.strength}`)
10740
+ ]);
10741
+ }
10742
+ function buildReceiptId(actionFingerprint, rules) {
10743
+ const hash = hashDecisionJsonValue({
10744
+ actionFingerprint,
10745
+ rules: rules.map((rule) => [rule.id, rule.source.ref])
10746
+ }).replace(/^sha256:/, "");
10747
+ return `project-policy-${hash.slice(0, 16)}`;
10748
+ }
10749
+ function isoTimestamp2(value) {
10750
+ if (value instanceof Date) return value.toISOString();
10751
+ if (typeof value === "string" && value.trim()) return new Date(value).toISOString();
10752
+ return (/* @__PURE__ */ new Date()).toISOString();
10753
+ }
10754
+ function normalizeConfidence(value) {
10755
+ if (!Number.isFinite(value)) return 0;
10756
+ return Math.max(0, Math.min(1, value));
10757
+ }
10758
+ function normalizeText2(value) {
10759
+ const text = Array.isArray(value) ? value.filter(Boolean).join("\n") : value ?? "";
10760
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
10761
+ }
10762
+ function uniqueStrings7(values) {
10763
+ const seen = /* @__PURE__ */ new Set();
10764
+ const result = [];
10765
+ for (const value of values) {
10766
+ const normalized = value.trim();
10767
+ if (!normalized || seen.has(normalized)) continue;
10768
+ seen.add(normalized);
10769
+ result.push(normalized);
10770
+ }
10771
+ return result;
10772
+ }
10773
+ function metadataToText(metadata) {
10774
+ if (!metadata) return "";
10775
+ try {
10776
+ return JSON.stringify(metadata).slice(0, 12e3);
10777
+ } catch {
10778
+ return "";
10779
+ }
10780
+ }
10781
+
10637
10782
  // src/commands/decision-requests.ts
10638
10783
  var fs12 = __toESM(require("fs"));
10639
10784
  var path11 = __toESM(require("path"));
@@ -10964,7 +11109,7 @@ function memoryEvidenceItem(value, options) {
10964
11109
  summary: options.summary,
10965
11110
  kind: options.kind,
10966
11111
  status: options.status,
10967
- files: uniqueStrings7(evidenceRefs),
11112
+ files: uniqueStrings8(evidenceRefs),
10968
11113
  metadata: compactObject({
10969
11114
  source: options.source,
10970
11115
  bucket: options.bucket,
@@ -11005,7 +11150,7 @@ function reviewQueueItems(result) {
11005
11150
  action: "review_queue_item",
11006
11151
  options: ["accept", "reject", "archive", "invalidate", "keep_pending"],
11007
11152
  recommendation: reviewStatus === "approved" || status === "active" ? "keep_pending" : "accept",
11008
- reasonCodes: uniqueStrings7(["memory_review_queue", reason ?? "", status ?? ""]),
11153
+ reasonCodes: uniqueStrings8(["memory_review_queue", reason ?? "", status ?? ""]),
11009
11154
  evidenceItem: memoryEvidenceItem(item, {
11010
11155
  ref: `memory:${memoryId}`,
11011
11156
  title: `Review memory ${memoryId}`,
@@ -11044,7 +11189,7 @@ function cleanCandidateItems(result) {
11044
11189
  action: `${bucket}_candidate`,
11045
11190
  options,
11046
11191
  recommendation,
11047
- reasonCodes: uniqueStrings7(["memory_clean_candidates", bucket, reason]),
11192
+ reasonCodes: uniqueStrings8(["memory_clean_candidates", bucket, reason]),
11048
11193
  evidenceItem: memoryEvidenceItem(candidate, {
11049
11194
  ref: `memory:${memoryId}`,
11050
11195
  title: `${bucket.replace(/_/g, " ")} memory candidate ${memoryId}`,
@@ -11081,7 +11226,7 @@ function duplicateCandidateItems(result) {
11081
11226
  action: "duplicate_candidate",
11082
11227
  options: ["merge", "supersede", "keep", "inspect"],
11083
11228
  recommendation: keepMemoryId ? "supersede" : "inspect",
11084
- reasonCodes: uniqueStrings7(["memory_duplicate_candidates", `group_${index + 1}`]),
11229
+ reasonCodes: uniqueStrings8(["memory_duplicate_candidates", `group_${index + 1}`]),
11085
11230
  evidenceItem: memoryEvidenceItem(candidate, {
11086
11231
  ref: `memory:${memoryId}`,
11087
11232
  title: `Duplicate memory candidate ${memoryId}`,
@@ -11311,10 +11456,10 @@ function buildMemoryAuditSummary(audit) {
11311
11456
  cleanupCandidateCounts,
11312
11457
  ...typeof compactDryRun.mutated === "boolean" ? { compactDryRunMutated: compactDryRun.mutated } : {},
11313
11458
  ...compactDryRunPlannedActions !== void 0 ? { compactDryRunPlannedActions } : {},
11314
- recommendedActions: uniqueStrings7(recommendedActions)
11459
+ recommendedActions: uniqueStrings8(recommendedActions)
11315
11460
  };
11316
11461
  }
11317
- function uniqueStrings7(values) {
11462
+ function uniqueStrings8(values) {
11318
11463
  return Array.from(new Set(values));
11319
11464
  }
11320
11465
  function printMemoryAuditSummary(summary) {
@@ -11529,7 +11674,7 @@ async function memoryAuditCommand(options) {
11529
11674
  var fs13 = __toESM(require("fs"));
11530
11675
  var path12 = __toESM(require("path"));
11531
11676
  var import_node_child_process2 = require("child_process");
11532
- function uniqueStrings8(values) {
11677
+ function uniqueStrings9(values) {
11533
11678
  return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
11534
11679
  }
11535
11680
  function slugify(value, fallback) {
@@ -11537,7 +11682,7 @@ function slugify(value, fallback) {
11537
11682
  return slug2 || fallback;
11538
11683
  }
11539
11684
  function keywordHints(value) {
11540
- return uniqueStrings8(
11685
+ return uniqueStrings9(
11541
11686
  value.split(/[^A-Za-z0-9_.:/-]+/).map((part) => part.trim()).filter((part) => part.length >= 4).slice(0, 8)
11542
11687
  );
11543
11688
  }
@@ -11583,17 +11728,17 @@ function expectedItems(section, values, files) {
11583
11728
  }
11584
11729
  function buildEvalCaseArtifact(options = {}) {
11585
11730
  const rootDir = path12.resolve(options.dir ?? process.cwd());
11586
- const files = uniqueStrings8(options.files);
11587
- const commandsRun = uniqueStrings8(options.commandRun);
11731
+ const files = uniqueStrings9(options.files);
11732
+ const commandsRun = uniqueStrings9(options.commandRun);
11588
11733
  const summary = options.summary?.trim() || "Snipara companion local workflow evaluation";
11589
11734
  const id = options.id?.trim() || slugify(summary, "snipara-companion-eval");
11590
11735
  const expected = {};
11591
11736
  const sectionInputs = {
11592
- context: uniqueStrings8(options.context),
11593
- decisions: uniqueStrings8(options.decision),
11594
- impact: uniqueStrings8(options.impact),
11595
- verification: uniqueStrings8(options.verification),
11596
- continuity: uniqueStrings8(options.continuity)
11737
+ context: uniqueStrings9(options.context),
11738
+ decisions: uniqueStrings9(options.decision),
11739
+ impact: uniqueStrings9(options.impact),
11740
+ verification: uniqueStrings9(options.verification),
11741
+ continuity: uniqueStrings9(options.continuity)
11597
11742
  };
11598
11743
  for (const [section, values] of Object.entries(sectionInputs)) {
11599
11744
  if (values.length > 0) {
@@ -13055,7 +13200,7 @@ var SESSION_SNAPSHOT_VERSION = "snipara.session_snapshot.v0";
13055
13200
  var ACTIVITY_RELATIVE_DIR = path16.join(".snipara", "activity");
13056
13201
  var ACTIVITY_TIMELINE_RELATIVE_PATH = path16.join(ACTIVITY_RELATIVE_DIR, "timeline.jsonl");
13057
13202
  var SESSION_SNAPSHOT_RELATIVE_PATH = path16.join(ACTIVITY_RELATIVE_DIR, "session.json");
13058
- function uniqueStrings9(values) {
13203
+ function uniqueStrings10(values) {
13059
13204
  return Array.from(new Set(values.map((value) => value?.trim()).filter(Boolean)));
13060
13205
  }
13061
13206
  function stableJson(value) {
@@ -13128,8 +13273,8 @@ function appendActivityEvent(input) {
13128
13273
  ...input.phaseId ? { phaseId: input.phaseId } : {},
13129
13274
  ...input.actor ? { actor: input.actor } : {},
13130
13275
  ...input.outcome ? { outcome: input.outcome } : {},
13131
- files: uniqueStrings9(input.files ?? []),
13132
- refs: uniqueStrings9(input.refs ?? []),
13276
+ files: uniqueStrings10(input.files ?? []),
13277
+ refs: uniqueStrings10(input.refs ?? []),
13133
13278
  metadata: input.metadata ?? {}
13134
13279
  };
13135
13280
  const timelinePath = getActivityTimelinePath(cwd);
@@ -13224,7 +13369,7 @@ function deriveSessionSnapshotSummary(args) {
13224
13369
  if (args.workflow?.status === "blocked") {
13225
13370
  riskReasons.push("workflow is blocked");
13226
13371
  }
13227
- const touchedFiles = uniqueStrings9(args.latestEvents.flatMap((event) => event.files)).slice(
13372
+ const touchedFiles = uniqueStrings10(args.latestEvents.flatMap((event) => event.files)).slice(
13228
13373
  0,
13229
13374
  20
13230
13375
  );
@@ -13324,7 +13469,7 @@ function readSessionSnapshot(cwd = process.cwd()) {
13324
13469
  }
13325
13470
 
13326
13471
  // src/commands/journal.ts
13327
- function uniqueStrings10(values) {
13472
+ function uniqueStrings11(values) {
13328
13473
  if (!values) {
13329
13474
  return [];
13330
13475
  }
@@ -13352,7 +13497,7 @@ function buildJournalCheckpointEntry(payload) {
13352
13497
  payload.actor ? `Actor: ${payload.actor}` : null,
13353
13498
  payload.attention ? `Attention: ${payload.attention}` : null,
13354
13499
  payload.next ? `Next: ${payload.next}` : null,
13355
- uniqueStrings10(payload.files).length > 0 ? `Files: ${uniqueStrings10(payload.files).join(", ")}` : null
13500
+ uniqueStrings11(payload.files).length > 0 ? `Files: ${uniqueStrings11(payload.files).join(", ")}` : null
13356
13501
  ].filter((line) => Boolean(line));
13357
13502
  return {
13358
13503
  text: lines.join("\n"),
@@ -13448,7 +13593,7 @@ function createRedactor(rootDir) {
13448
13593
  patterns.add(item.name);
13449
13594
  item.pattern.lastIndex = 0;
13450
13595
  }
13451
- const uniqueMatchedPatterns = uniqueStrings11(matchedPatterns);
13596
+ const uniqueMatchedPatterns = uniqueStrings12(matchedPatterns);
13452
13597
  if (uniqueMatchedPatterns.length > 0) {
13453
13598
  redactedValueCount += 1;
13454
13599
  }
@@ -13563,14 +13708,14 @@ function buildRepoState(input, options, rootDir, redactor) {
13563
13708
  const inputRepo = inputRecord(input, "repoState", "repo_state", "repo");
13564
13709
  const detected = detectGitState(rootDir);
13565
13710
  const statusCounts = countGitStatus(detected.statusLines);
13566
- const changedFiles = uniqueStrings11([
13711
+ const changedFiles = uniqueStrings12([
13567
13712
  ...normalizeUnknownStringList(inputRepo.changedFiles),
13568
13713
  ...normalizeUnknownStringList(inputRepo.changed_files),
13569
13714
  ...normalizeUnknownStringList(input.changedFiles),
13570
13715
  ...normalizeUnknownStringList(input.changed_files),
13571
13716
  ...options.changedFiles ?? []
13572
13717
  ]).map((item) => redactor.redact(item).value);
13573
- const recentFiles = uniqueStrings11([
13718
+ const recentFiles = uniqueStrings12([
13574
13719
  ...normalizeUnknownStringList(inputRepo.recentFiles),
13575
13720
  ...normalizeUnknownStringList(inputRepo.recent_files),
13576
13721
  ...normalizeUnknownStringList(input.recentFiles),
@@ -13638,18 +13783,18 @@ function normalizeEvidenceItem(value, section, index, redactor) {
13638
13783
  "command",
13639
13784
  "check"
13640
13785
  ]) ?? fallbackSummary(value, section, index);
13641
- const reasonCodes = uniqueStrings11([
13786
+ const reasonCodes = uniqueStrings12([
13642
13787
  `section_${section}`,
13643
13788
  ...normalizeUnknownStringList(value.reasonCodes),
13644
13789
  ...normalizeUnknownStringList(value.reason_codes)
13645
13790
  ]).map((item) => redactor.redact(item).value);
13646
- const files = uniqueStrings11([
13791
+ const files = uniqueStrings12([
13647
13792
  ...normalizeUnknownStringList(value.files),
13648
13793
  ...normalizeUnknownStringList(value.changedFiles),
13649
13794
  ...normalizeUnknownStringList(value.changed_files),
13650
13795
  ...normalizeUnknownStringList(value.file)
13651
13796
  ]).slice(0, MAX_LIST_ITEMS).map((item) => redactor.redact(item).value);
13652
- const commands = uniqueStrings11([
13797
+ const commands = uniqueStrings12([
13653
13798
  ...normalizeUnknownStringList(value.commands),
13654
13799
  ...normalizeUnknownStringList(value.command),
13655
13800
  ...normalizeUnknownStringList(value.checks),
@@ -13673,7 +13818,7 @@ function normalizeEvidenceItem(value, section, index, redactor) {
13673
13818
  });
13674
13819
  }
13675
13820
  function collectReasonCodes(input, cliValues, items, redactor) {
13676
- return uniqueStrings11([
13821
+ return uniqueStrings12([
13677
13822
  "coding_intelligence_ledger_v0",
13678
13823
  ...normalizeUnknownStringList(input.reasonCodes),
13679
13824
  ...normalizeUnknownStringList(input.reason_codes),
@@ -13704,13 +13849,13 @@ function buildCalibrationMetadata(input, cliValues, redactor) {
13704
13849
  "calibration",
13705
13850
  "calibration_metadata"
13706
13851
  );
13707
- const notes = uniqueStrings11([
13852
+ const notes = uniqueStrings12([
13708
13853
  ...normalizeUnknownStringList(calibration.notes),
13709
13854
  ...normalizeUnknownStringList(calibration.note),
13710
13855
  ...normalizeUnknownStringList(input.calibrationNotes),
13711
13856
  ...cliValues ?? []
13712
13857
  ]).slice(0, MAX_LIST_ITEMS).map((item) => redactor.redact(item).value);
13713
- const caveats = uniqueStrings11([
13858
+ const caveats = uniqueStrings12([
13714
13859
  ...normalizeUnknownStringList(calibration.caveats),
13715
13860
  ...normalizeUnknownStringList(calibration.caveat),
13716
13861
  ...normalizeUnknownStringList(input.calibrationCaveats)
@@ -13907,7 +14052,7 @@ function validIso(value) {
13907
14052
  const date = new Date(value);
13908
14053
  return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
13909
14054
  }
13910
- function uniqueStrings11(values) {
14055
+ function uniqueStrings12(values) {
13911
14056
  return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
13912
14057
  }
13913
14058
  function compactWhitespace(value) {
@@ -16209,16 +16354,16 @@ function upsertLocalAdaptiveRoutingPolicy(workerRole) {
16209
16354
  mode: stringValue6(existing.mode) ?? "catalog",
16210
16355
  plannerRetainsReasoning: existing.plannerRetainsReasoning !== false,
16211
16356
  preferLocalWorkers: true,
16212
- allowedEndpointTypes: uniqueStrings12([
16357
+ allowedEndpointTypes: uniqueStrings13([
16213
16358
  ...normalizeStringList3(existing.allowedEndpointTypes),
16214
16359
  "local",
16215
16360
  "cloud"
16216
16361
  ]),
16217
- preferredEndpointTypes: uniqueStrings12([
16362
+ preferredEndpointTypes: uniqueStrings13([
16218
16363
  "local",
16219
16364
  ...normalizeStringList3(existing.preferredEndpointTypes)
16220
16365
  ]),
16221
- allowedWorkerClasses: uniqueStrings12([
16366
+ allowedWorkerClasses: uniqueStrings13([
16222
16367
  ...normalizeStringList3(existing.allowedWorkerClasses),
16223
16368
  workerRole,
16224
16369
  "documentation",
@@ -16458,7 +16603,7 @@ function normalizeWorkerProfileLanguages(value) {
16458
16603
  return value.length > 0 ? value : ["typescript"];
16459
16604
  }
16460
16605
  function normalizeWorkerCapabilities(values) {
16461
- return values.length > 0 ? uniqueStrings12(values.map((value) => String(value).trim()).filter(Boolean)) : ["code", "repo_scan", "refactor"];
16606
+ return values.length > 0 ? uniqueStrings13(values.map((value) => String(value).trim()).filter(Boolean)) : ["code", "repo_scan", "refactor"];
16462
16607
  }
16463
16608
  function defaultCapabilitiesForWorker(workerRole) {
16464
16609
  if (workerRole === "documentation") {
@@ -16474,9 +16619,9 @@ function defaultCapabilitiesForWorker(workerRole) {
16474
16619
  }
16475
16620
  function normalizeStringList3(value) {
16476
16621
  const values = Array.isArray(value) ? value : value === void 0 ? [] : [value];
16477
- return uniqueStrings12(values.map(stringValue6).filter((item) => Boolean(item)));
16622
+ return uniqueStrings13(values.map(stringValue6).filter((item) => Boolean(item)));
16478
16623
  }
16479
- function uniqueStrings12(values) {
16624
+ function uniqueStrings13(values) {
16480
16625
  return Array.from(new Set(values));
16481
16626
  }
16482
16627
  function stringValue6(value) {
@@ -16634,7 +16779,7 @@ function numberValue4(value) {
16634
16779
  const parsed = Number(text);
16635
16780
  return Number.isFinite(parsed) ? parsed : void 0;
16636
16781
  }
16637
- function uniqueStrings13(values) {
16782
+ function uniqueStrings14(values) {
16638
16783
  return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
16639
16784
  }
16640
16785
  function normalizeEnum(value) {
@@ -17950,7 +18095,7 @@ function buildProducerLoopArtifact(options) {
17950
18095
  workflowId ?? "unmanaged",
17951
18096
  options.phase?.id ? `phase:${options.phase.id}` : "final"
17952
18097
  ].join(":");
17953
- const reasonCodes = uniqueStrings13([
18098
+ const reasonCodes = uniqueStrings14([
17954
18099
  "producer_loop_v0",
17955
18100
  options.kind,
17956
18101
  `outcome_${options.outcome}`,
@@ -18142,8 +18287,8 @@ function summarizeProducerLoopArtifactFile(filePath, cwd) {
18142
18287
  relativePath: relativePath2,
18143
18288
  artifactHash: `sha256:${hashContent(content)}`,
18144
18289
  ledgerHash: stringValue7(parsed.ledgerHash),
18145
- reasonCodes: uniqueStrings13(stringArrayValue(ledger?.reasonCodes)),
18146
- files: uniqueStrings13(stringArrayValue(producer.files)),
18290
+ reasonCodes: uniqueStrings14(stringArrayValue(ledger?.reasonCodes)),
18291
+ files: uniqueStrings14(stringArrayValue(producer.files)),
18147
18292
  calibrationStatus: stringValue7(calibration?.status),
18148
18293
  reviewStatus,
18149
18294
  reviewOutcome: stringValue7(review?.outcome),
@@ -18190,10 +18335,10 @@ function buildProducerLoopReport(options = {}) {
18190
18335
  (left, right) => left.generatedAt === right.generatedAt ? left.relativePath.localeCompare(right.relativePath) : left.generatedAt.localeCompare(right.generatedAt)
18191
18336
  );
18192
18337
  const invalidArtifacts = summaries.map((entry) => entry.invalid).filter((entry) => Boolean(entry));
18193
- const producerKinds = uniqueStrings13(artifacts.map((artifact) => artifact.producerKind)).filter(
18338
+ const producerKinds = uniqueStrings14(artifacts.map((artifact) => artifact.producerKind)).filter(
18194
18339
  (kind) => isProducerLoopProducerKind(kind)
18195
18340
  );
18196
- const workflowIds = uniqueStrings13(
18341
+ const workflowIds = uniqueStrings14(
18197
18342
  artifacts.map((artifact) => artifact.workflowId).filter((id) => Boolean(id))
18198
18343
  );
18199
18344
  const reasonCodeCounts = countStringOccurrences(
@@ -18344,7 +18489,7 @@ function readProducerLoopArtifactForReview(filePath) {
18344
18489
  return parsed;
18345
18490
  }
18346
18491
  function applyProducerLoopArtifactReview(artifact, options) {
18347
- const reviewNotes = uniqueStrings13(options.notes ?? []);
18492
+ const reviewNotes = uniqueStrings14(options.notes ?? []);
18348
18493
  const reviewNote = options.status === "sample_rejected" ? "Sample rejected by local operator review." : "Sample reviewed by local operator.";
18349
18494
  const existingCalibration = artifact.calibration ?? {
18350
18495
  status: "sample_unreviewed",
@@ -18359,7 +18504,7 @@ function applyProducerLoopArtifactReview(artifact, options) {
18359
18504
  status: options.status,
18360
18505
  sampleSize: 1,
18361
18506
  hardGateReady: false,
18362
- notes: uniqueStrings13([
18507
+ notes: uniqueStrings14([
18363
18508
  ...stringArrayValue(existingCalibration.notes),
18364
18509
  reviewNote,
18365
18510
  ...reviewNotes.map((note) => `Review note: ${note}`)
@@ -18574,7 +18719,7 @@ function applyDecisionRequestLocally(options) {
18574
18719
  reject: isRejectChoice(options.choice),
18575
18720
  outcome: isRejectChoice(options.choice) ? "false_positive" : "useful",
18576
18721
  reviewer: options.reviewer,
18577
- notes: uniqueStrings13([
18722
+ notes: uniqueStrings14([
18578
18723
  `decision_request:${request.requestId}`,
18579
18724
  ...options.note ? [options.note] : []
18580
18725
  ])
@@ -18597,7 +18742,7 @@ function recurringPolicySuggestionTargetCategory(request) {
18597
18742
  const metadata = isRecord9(item.metadata) ? item.metadata : void 0;
18598
18743
  return stringValue7(metadata?.category) ?? stringValue7(metadata?.type) ?? item.kind;
18599
18744
  }).filter((value) => Boolean(value)) ?? [];
18600
- return uniqueStrings13(evidenceCategories).sort().join("|") || request.evidence.applyPath || request.decision;
18745
+ return uniqueStrings14(evidenceCategories).sort().join("|") || request.evidence.applyPath || request.decision;
18601
18746
  }
18602
18747
  function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
18603
18748
  const grouped = /* @__PURE__ */ new Map();
@@ -18635,12 +18780,12 @@ function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
18635
18780
  status: record.response.choice,
18636
18781
  files: record.request.evidence.files?.slice(0, 12)
18637
18782
  })),
18638
- reasonCodes: uniqueStrings13([
18783
+ reasonCodes: uniqueStrings14([
18639
18784
  "recurring_decision_receipts",
18640
18785
  "never_ask_twice_candidate",
18641
18786
  ...latest.request.evidence.reasonCodes
18642
18787
  ]),
18643
- files: uniqueStrings13(repeated.flatMap((record) => record.request.evidence.files ?? [])),
18788
+ files: uniqueStrings14(repeated.flatMap((record) => record.request.evidence.files ?? [])),
18644
18789
  applyPath: "manual_context_review",
18645
18790
  applyCommand: "Review the suggested rule and add it to the appropriate project policy or AGENTS.md section manually."
18646
18791
  },
@@ -18782,12 +18927,12 @@ function buildProducerTriageRequest(report) {
18782
18927
  summary: `${candidates.length} unreviewed Producer Loop samples; ${report.invalidArtifacts.length} invalid artifacts; hardGateReady remains false.`,
18783
18928
  refs,
18784
18929
  items,
18785
- reasonCodes: uniqueStrings13([
18930
+ reasonCodes: uniqueStrings14([
18786
18931
  "triage_rules_v0",
18787
18932
  "producer_loop_report_valid",
18788
18933
  ...report.invalidArtifacts.length === 0 ? ["no_invalid_artifacts"] : ["invalid_artifacts_present"]
18789
18934
  ]),
18790
- files: uniqueStrings13(candidates.flatMap((artifact) => artifact.files)),
18935
+ files: uniqueStrings14(candidates.flatMap((artifact) => artifact.files)),
18791
18936
  applyPath: "workflow producer-review",
18792
18937
  applyCommand: "snipara-companion workflow decide <request-id> --choose accept_all --reviewer <name>"
18793
18938
  },
@@ -20563,8 +20708,8 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
20563
20708
  runtimeCatalog: catalog,
20564
20709
  routingCard: {
20565
20710
  ...routing.routingCard,
20566
- reasons: uniqueStrings13(reasons),
20567
- warnings: uniqueStrings13([...routing.routingCard.warnings, ...gatewayWarnings])
20711
+ reasons: uniqueStrings14(reasons),
20712
+ warnings: uniqueStrings14([...routing.routingCard.warnings, ...gatewayWarnings])
20568
20713
  }
20569
20714
  };
20570
20715
  } catch (error) {
@@ -20581,7 +20726,7 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
20581
20726
  },
20582
20727
  routingCard: {
20583
20728
  ...routing.routingCard,
20584
- warnings: uniqueStrings13([...routing.routingCard.warnings, warning])
20729
+ warnings: uniqueStrings14([...routing.routingCard.warnings, warning])
20585
20730
  }
20586
20731
  };
20587
20732
  }
@@ -20772,7 +20917,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
20772
20917
  const approvalRequired = booleanValue4(
20773
20918
  isRecord9(resolution?.policyDecision) ? resolution.policyDecision.approvalRequired : void 0
20774
20919
  );
20775
- const gatewayWarnings = uniqueStrings13([
20920
+ const gatewayWarnings = uniqueStrings14([
20776
20921
  ...resolutionWarnings,
20777
20922
  ...selectedCandidate ? [] : ["Local orchestrator did not select a worker candidate and will fail closed."]
20778
20923
  ]);
@@ -20794,7 +20939,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
20794
20939
  ...selectedCandidate?.workerClass ? { recommendedWorkerClass: selectedCandidate.workerClass } : {},
20795
20940
  ...resolutionRejectedReasons ? { rejectedReasons: resolutionRejectedReasons } : {},
20796
20941
  ...approvalRequired !== void 0 ? { humanApprovalRequired: approvalRequired } : {},
20797
- reasons: uniqueStrings13([
20942
+ reasons: uniqueStrings14([
20798
20943
  ...routing.routingCard.reasons,
20799
20944
  ...resolutionReasons,
20800
20945
  ...selectedCandidate ? [
@@ -20803,7 +20948,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
20803
20948
  ...stringValue7(selectedEndpoint?.model) ? [`selected local model ${String(selectedEndpoint?.model)}`] : []
20804
20949
  ] : ["local orchestrator could not resolve a concrete worker candidate"]
20805
20950
  ]),
20806
- warnings: uniqueStrings13([...routing.routingCard.warnings, ...gatewayWarnings])
20951
+ warnings: uniqueStrings14([...routing.routingCard.warnings, ...gatewayWarnings])
20807
20952
  }
20808
20953
  };
20809
20954
  } catch (error) {
@@ -20820,7 +20965,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
20820
20965
  },
20821
20966
  routingCard: {
20822
20967
  ...routing.routingCard,
20823
- warnings: uniqueStrings13([...routing.routingCard.warnings, warning])
20968
+ warnings: uniqueStrings14([...routing.routingCard.warnings, warning])
20824
20969
  }
20825
20970
  };
20826
20971
  }
@@ -22689,8 +22834,8 @@ function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = [
22689
22834
  ...routing,
22690
22835
  routingCard: {
22691
22836
  ...routing.routingCard,
22692
- reasons: uniqueStrings13([...routing.routingCard.reasons, ...policyReasons]),
22693
- warnings: uniqueStrings13([...routing.routingCard.warnings, ...policyWarnings])
22837
+ reasons: uniqueStrings14([...routing.routingCard.reasons, ...policyReasons]),
22838
+ warnings: uniqueStrings14([...routing.routingCard.warnings, ...policyWarnings])
22694
22839
  }
22695
22840
  };
22696
22841
  }
@@ -22706,7 +22851,7 @@ function normalizeAdaptiveRoutingMode(value) {
22706
22851
  return normalized === "off" || normalized === "recommend" || normalized === "catalog" ? normalized : null;
22707
22852
  }
22708
22853
  function normalizeAdaptiveWorkerClasses(values) {
22709
- return uniqueStrings13((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
22854
+ return uniqueStrings14((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
22710
22855
  (value) => ["documentation", "tests", "review", "coding"].includes(value)
22711
22856
  );
22712
22857
  }
@@ -26933,7 +27078,7 @@ function normalizeCaptureEvent(event, now, index) {
26933
27078
  const reasons = normalizeStringList4(event.reason).map(redactText);
26934
27079
  const evidence = normalizeStringList4(event.evidence).map(redactText);
26935
27080
  const commands = normalizeStringList4(event.commands).map(redactText);
26936
- const redactionPatterns = uniqueStrings14([
27081
+ const redactionPatterns = uniqueStrings15([
26937
27082
  ...summary.patterns,
26938
27083
  ...outcome.patterns,
26939
27084
  ...status.patterns,
@@ -26966,7 +27111,7 @@ function normalizeEventKind(value) {
26966
27111
  return "feedback";
26967
27112
  }
26968
27113
  function reasonCodesForEvent(event, kind, status) {
26969
- return uniqueStrings14([
27114
+ return uniqueStrings15([
26970
27115
  "why_outcome_capture_v1",
26971
27116
  "review_pending_authority",
26972
27117
  `source_${event.kind}`,
@@ -27031,7 +27176,7 @@ function readEventsFromFile(filePath) {
27031
27176
  function stableHash2(parts) {
27032
27177
  return (0, import_node_crypto8.createHash)("sha256").update(parts.join("\0")).digest("hex");
27033
27178
  }
27034
- function uniqueStrings14(values) {
27179
+ function uniqueStrings15(values) {
27035
27180
  return Array.from(new Set(values.filter(Boolean)));
27036
27181
  }
27037
27182
  function compactWhitespace3(value) {
@@ -28282,6 +28427,108 @@ function actionList(value) {
28282
28427
  return reason ? `${priority}${preview3(title, 80)} - ${preview3(reason, 140)}` : `${priority}${preview3(title, 160)}`;
28283
28428
  }).filter(Boolean);
28284
28429
  }
28430
+ function extractProjectPolicyRulesFromResumeContext(resumeContext) {
28431
+ if (!resumeContext) {
28432
+ return [];
28433
+ }
28434
+ return collectRecords(resumeContext).map(recordToProjectPolicyRule).filter((rule) => Boolean(rule));
28435
+ }
28436
+ function collectRecords(value, depth = 0) {
28437
+ if (depth > 5) {
28438
+ return [];
28439
+ }
28440
+ if (Array.isArray(value)) {
28441
+ return value.flatMap((item) => collectRecords(item, depth + 1));
28442
+ }
28443
+ if (!isRecord15(value)) {
28444
+ return [];
28445
+ }
28446
+ return [
28447
+ value,
28448
+ ...Object.values(value).flatMap(
28449
+ (item) => typeof item === "object" && item !== null ? collectRecords(item, depth + 1) : []
28450
+ )
28451
+ ];
28452
+ }
28453
+ function recordToProjectPolicyRule(record) {
28454
+ const type = preview3(record.type ?? record.memory_type ?? record.category, 80).toLowerCase();
28455
+ const content = preview3(record.content ?? record.text ?? record.summary ?? record.decision, 4e3);
28456
+ if (!content || !/(decision|policy|workflow-policy|roadmap)/i.test(type)) {
28457
+ return void 0;
28458
+ }
28459
+ const status = preview3(record.status ?? record.lifecycleStatus ?? "active", 40).toLowerCase();
28460
+ const reviewStatus = preview3(record.reviewStatus ?? record.review_status ?? "approved", 40).toLowerCase().replace("canonical", "approved");
28461
+ if (status && status !== "active") {
28462
+ return void 0;
28463
+ }
28464
+ if (reviewStatus && reviewStatus !== "approved") {
28465
+ return void 0;
28466
+ }
28467
+ const anchors = extractPolicyAnchors(content);
28468
+ if (anchors.length === 0) {
28469
+ return void 0;
28470
+ }
28471
+ const id = preview3(record.memory_id ?? record.id ?? record.source_id ?? anchors[0], 80);
28472
+ const confidence = numberValue8(record.confidence) ?? numberValue8(record.score) ?? 0.8;
28473
+ return {
28474
+ id,
28475
+ title: preview3(record.title ?? content, 120),
28476
+ scope: inferProjectPolicyScope(content, anchors),
28477
+ strength: inferProjectPolicyStrength(content),
28478
+ confidence,
28479
+ source: {
28480
+ kind: "decision_memory",
28481
+ ref: id.startsWith("memory:") ? id : `memory:${id}`,
28482
+ reviewStatus: "approved"
28483
+ },
28484
+ anchors,
28485
+ requirement: content.slice(0, 240),
28486
+ forbiddenActions: extractForbiddenActions(content)
28487
+ };
28488
+ }
28489
+ function extractPolicyAnchors(text) {
28490
+ const normalized = text.toLowerCase();
28491
+ return [
28492
+ ...Array.from(normalized.matchAll(/`([^`]{3,120})`/g), (match) => match[1]),
28493
+ ...Array.from(
28494
+ normalized.matchAll(/\b(?:apps|packages|deploy|docs|scripts|oss)\/[a-z0-9_[\]./-]+/g),
28495
+ (match) => match[0]
28496
+ ),
28497
+ ...["auth", "billing", "schema", "migration", "deploy", "package", "memory", "routing"].filter(
28498
+ (term) => normalized.includes(term)
28499
+ )
28500
+ ].filter((value, index, values) => value && values.indexOf(value) === index);
28501
+ }
28502
+ function inferProjectPolicyScope(text, anchors) {
28503
+ const haystack = `${text}
28504
+ ${anchors.join("\n")}`.toLowerCase();
28505
+ if (/\b(auth|oauth|session|permission|token|secret)\b/.test(haystack)) return "auth";
28506
+ if (/\b(billing|stripe|subscription|checkout|entitlement|quota)\b/.test(haystack)) {
28507
+ return "billing";
28508
+ }
28509
+ if (/\b(schema|migration|prisma|database)\b/.test(haystack)) return "schema";
28510
+ if (/\b(deploy|production|pre-deploy|zero-downtime)\b/.test(haystack)) return "deploy";
28511
+ if (/\b(package|npm|pypi|npx|publish|pack smoke)\b/.test(haystack)) return "package_surface";
28512
+ if (/\b(memory|reviewed memory|decision memory)\b/.test(haystack)) return "memory";
28513
+ if (/\b(routing|orchestrator|worker)\b/.test(haystack)) return "routing";
28514
+ return "custom";
28515
+ }
28516
+ function inferProjectPolicyStrength(text) {
28517
+ const normalized = text.toLowerCase();
28518
+ if (/\b(never|must not|do not|forbid|forbidden|disallow|block)\b/.test(normalized)) {
28519
+ return "blocking";
28520
+ }
28521
+ if (/\b(require review|requires review|approval|required|must)\b/.test(normalized)) {
28522
+ return "review_required";
28523
+ }
28524
+ return "advisory";
28525
+ }
28526
+ function extractForbiddenActions(text) {
28527
+ const normalized = text.toLowerCase();
28528
+ return ["bypass", "skip", "remove", "delete", "disable", "drop", "force"].filter(
28529
+ (term) => normalized.includes(term)
28530
+ );
28531
+ }
28285
28532
  function buildSuggestedCommands2(args) {
28286
28533
  const commands = [
28287
28534
  "snipara-companion team-sync what-changed",
@@ -28374,6 +28621,16 @@ async function buildProjectIntelligenceBrief(options) {
28374
28621
  });
28375
28622
  }
28376
28623
  }
28624
+ const projectPolicyRules = extractProjectPolicyRulesFromResumeContext(brief.resumeContext);
28625
+ if (projectPolicyRules.length > 0) {
28626
+ brief.projectPolicyDecision = evaluateProjectPolicyDecision({
28627
+ action: {
28628
+ summary: options.diffSummary ?? options.task ?? "Project Intelligence brief",
28629
+ changedFiles
28630
+ },
28631
+ rules: projectPolicyRules
28632
+ });
28633
+ }
28377
28634
  brief.verificationPlan = buildVerificationPlan({
28378
28635
  task: options.task,
28379
28636
  changedFiles,
@@ -28539,6 +28796,19 @@ async function projectIntelligenceBriefCommand(options) {
28539
28796
  console.log(import_chalk9.default.bold("Memory Authority And Health"));
28540
28797
  printMemoryHealth2(brief.memoryHealth);
28541
28798
  console.log("");
28799
+ if (brief.projectPolicyDecision) {
28800
+ console.log(import_chalk9.default.bold("Project Policy"));
28801
+ console.log(
28802
+ `Verdict: ${brief.projectPolicyDecision.verdict} (${Math.round(
28803
+ brief.projectPolicyDecision.confidence * 100
28804
+ )}%)`
28805
+ );
28806
+ if (brief.projectPolicyDecision.requiredActions.length > 0) {
28807
+ console.log(`Required: ${brief.projectPolicyDecision.requiredActions[0]}`);
28808
+ }
28809
+ console.log(`Receipt: ${brief.projectPolicyDecision.receipt.receiptId}`);
28810
+ console.log("");
28811
+ }
28542
28812
  console.log(import_chalk9.default.bold("Code Impact"));
28543
28813
  if (brief.codeImpactSourceSelection) {
28544
28814
  console.log(
@@ -29246,9 +29516,46 @@ function gateSummary(gates) {
29246
29516
  function packageReviewCommand2(input) {
29247
29517
  return input.packageReview?.command ?? "npm view snipara-companion version bin dist-tags --json";
29248
29518
  }
29519
+ function gateSurfaceFromPolicyScope(scope) {
29520
+ if (scope === "release" || scope === "schema" || scope === "auth" || scope === "billing" || scope === "deploy" || scope === "package_surface") {
29521
+ return scope;
29522
+ }
29523
+ return "project_policy";
29524
+ }
29249
29525
  function evaluateProjectPolicyGates(input) {
29250
29526
  const changedFiles = normalizeChangedFiles(input.changedFiles);
29251
29527
  const gates = [];
29528
+ const projectPolicyDecision = input.projectPolicy?.decision ?? (input.projectPolicy?.rules?.length ? evaluateProjectPolicyDecision({
29529
+ action: {
29530
+ summary: [input.task, input.diffSummary].filter(Boolean).join("\n"),
29531
+ changedFiles
29532
+ },
29533
+ rules: input.projectPolicy.rules
29534
+ }) : void 0);
29535
+ if (projectPolicyDecision && projectPolicyDecision.verdict !== "allow") {
29536
+ const strongestRule = projectPolicyDecision.matchedRules[0];
29537
+ gates.push(
29538
+ gate({
29539
+ id: "policy:project:decision-consistency",
29540
+ surface: strongestRule ? gateSurfaceFromPolicyScope(strongestRule.scope) : "project_policy",
29541
+ severity: projectPolicyVerdictToGateSeverity(projectPolicyDecision.verdict),
29542
+ title: "Project Policy decision consistency",
29543
+ rationale: "Approved project policy and decision memories can require review or block when the proposed action contradicts high-confidence constraints.",
29544
+ evidence: [
29545
+ `verdict ${projectPolicyDecision.verdict}`,
29546
+ `receipt ${projectPolicyDecision.receipt.receiptId}`,
29547
+ ...projectPolicyDecision.matchedRules.map((rule) => `${rule.source.ref}: ${rule.title}`)
29548
+ ],
29549
+ requiredActions: projectPolicyDecision.requiredActions.length > 0 ? projectPolicyDecision.requiredActions : ["Review the matching project policy decision before proceeding."],
29550
+ suggestedCommands: projectPolicyDecision.verdict === "block" ? ["snipara-companion decisions request --help"] : ["snipara-companion memory reviews --emit-decisions"],
29551
+ sampleGate: explicitContractSampleGate(
29552
+ "Project Policy gates are driven by reviewed decisions and explicit receipts, not statistical outcome thresholds."
29553
+ ),
29554
+ source: "project_policy_decision",
29555
+ reasonCodes: projectPolicyDecision.reasonCodes
29556
+ })
29557
+ );
29558
+ }
29252
29559
  if (input.release) {
29253
29560
  gates.push(
29254
29561
  gate({
@@ -29507,6 +29814,7 @@ function evaluateProjectPolicyGates(input) {
29507
29814
  release: Boolean(input.release),
29508
29815
  summary,
29509
29816
  gates,
29817
+ ...projectPolicyDecision ? { projectPolicyDecision } : {},
29510
29818
  suggestedCommands: suggestedCommands3
29511
29819
  };
29512
29820
  }
@@ -30011,7 +30319,10 @@ async function buildProjectIntelligenceRun(options) {
30011
30319
  skipPackageReview: options.skipPackageReview,
30012
30320
  guard,
30013
30321
  packageReview,
30014
- judgmentCard
30322
+ judgmentCard,
30323
+ projectPolicy: brief.projectPolicyDecision ? {
30324
+ decision: brief.projectPolicyDecision
30325
+ } : void 0
30015
30326
  });
30016
30327
  const verificationEvidence = verificationEvidenceFromRun({
30017
30328
  guard,
@@ -1037,6 +1037,12 @@ checkouts use hosted graph impact. It prints continuity signals, memory health,
1037
1037
  risk and verification hints, degraded surfaces, and the Judgment Card's
1038
1038
  weighted readiness, evidence, and required actions.
1039
1039
 
1040
+ When hosted resume context includes approved decision memories that match the
1041
+ task or changed files, the brief also emits a Project Policy decision receipt
1042
+ with an `allow`, `warn`, `require_review`, or `block` verdict. This is
1043
+ conservative by design: blocks require high-confidence reviewed policy plus a
1044
+ matching forbidden action, and no new default MCP tool is exposed.
1045
+
1040
1046
  Use `reality-check` or `intelligence reality-check` when a local hook, agent, or
1041
1047
  CI adapter needs the contradiction-to-reality gate without a full hosted brief:
1042
1048
 
@@ -1146,7 +1152,7 @@ Semantics:
1146
1152
  - `snipara-companion workflow timeline` = append-only activity timeline from `.snipara/activity/timeline.jsonl`, including workflow, Producer Loop, Decision Request, and Team Sync events emitted by Companion commands; add `--export md` for a redacted Markdown artifact
1147
1153
  - `snipara-companion workflow session` = writes and prints Session Snapshot V0 at `.snipara/activity/session.json` with latest activity, risk, touched files, next action, advisory Intent Detection V0 intent/confidence/signals/suggested mode, workflow/session counts, Producer Loop calibration, decision counts, Team Sync counts, and `hardRoutingAllowed=false`
1148
1154
  - `snipara-companion handoff` = top-level agent-ready Markdown/JSON handoff artifact plus the same local/hosted Team Sync handoff persistence
1149
- - `snipara-companion intelligence brief` = one local Project Intelligence brief that combines local Session Snapshot, hosted resume context, memory health, and code impact for a task
1155
+ - `snipara-companion intelligence brief` = one local Project Intelligence brief that combines local Session Snapshot, hosted resume context, memory health, Project Policy decision receipts, and code impact for a task
1150
1156
  - `snipara-companion intelligence reality-check` = Project Intelligence namespace alias for the same local Reality Check gate
1151
1157
  - `snipara-companion intelligence ledger-export` = structured redacted Coding Intelligence Ledger JSON for replay, review, and commercial proof assets without dumping raw transcripts
1152
1158
  - `snipara-companion run` = production Project Intelligence flow that combines the brief, guard action cards, package review, verification hints, and a final weighted Judgment Card
@@ -1172,7 +1178,7 @@ Semantics:
1172
1178
  - `snipara-companion team-sync resume` = reloads local carryover plus the hosted latest handoff and checkpoint-aware resume guidance when available
1173
1179
  - `snipara-companion final-commit` / `workflow final-commit` = final hosted commit for the managed workflow
1174
1180
  - `snipara-companion code callers/imports/neighbors/shortest-path/impact` = primary code graph surface for agents with shell access. These commands use `--source auto` by default; clean configured checkouts use hosted MCP, dirty/ahead worktrees use the local overlay, and every response reports `sourceSelection` plus agent guidance.
1175
- - `snipara-companion code symbol-card` = direct paid Context `snipara_code_symbol_card` for an important symbol before editing, with an agent guidance summary before raw JSON
1181
+ - `snipara-companion code symbol-card` = direct `snipara_code_symbol_card` for an important symbol before editing, with an agent guidance summary before raw JSON
1176
1182
  - `snipara-companion code impact --source hosted|local` = optional source override for debugging; normal agent instructions should leave `--source auto` in place. Hosted `snipara_code_impact` is the fallback when companion is unavailable or the canonical graph check after push/reindex.
1177
1183
  - `snipara-companion code local impact` = explicit repository-local file-level import impact from the local code overlay; keep this for power-user/debug workflows, and use `workflow impact-gate` when the file set should come from unpushed workflow commits
1178
1184
  - `snipara-companion doctor` = local readiness check for companion version skew, Snipara auth, deterministic hosted tool catalog access, Snipara Sandbox, Snipara Sandbox MCP wiring, provider keys, and Docker
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.9",
3
+ "version": "3.2.10",
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": {