avorelo 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +37 -55
  2. package/dist/avorelo.mjs +3297 -750
  3. package/package.json +8 -3
package/dist/avorelo.mjs CHANGED
@@ -1702,8 +1702,8 @@ var init_generic = __esm({
1702
1702
  const promptPath = join8(dir, ".avorelo", "prompt.md");
1703
1703
  if (existsSync9(promptPath)) {
1704
1704
  try {
1705
- const { unlinkSync: unlinkSync5 } = __require("node:fs");
1706
- unlinkSync5(promptPath);
1705
+ const { unlinkSync: unlinkSync6 } = __require("node:fs");
1706
+ unlinkSync6(promptPath);
1707
1707
  return { removed: [promptPath], preserved: [] };
1708
1708
  } catch {
1709
1709
  return { removed: [], preserved: [promptPath] };
@@ -2562,6 +2562,7 @@ function buildCapabilityRouteDecision(input) {
2562
2562
  const proposalHints = unique(input.proposalHints ?? []);
2563
2563
  const allowedLegacyFeatures = input.allowedLegacyFeatures ?? [];
2564
2564
  pushCapability(selected, expectedEvidence, reasonCodes, "context-check", "CONTEXT_CHECK_REUSED", ["context_check_result"]);
2565
+ pushCapability(selected, expectedEvidence, reasonCodes, "context-control", "CONTEXT_CONTROL_ACTIVE", ["context_control_summary"]);
2565
2566
  pushCapability(selected, expectedEvidence, reasonCodes, "tool-governance", "TOOL_GOVERNANCE_REUSED", ["tool_routing_projection"]);
2566
2567
  pushCapability(selected, expectedEvidence, reasonCodes, "receipt-trace", "KERNEL_RECEIPTS_REUSED", ["kernel_receipt_ref"]);
2567
2568
  pushCapability(selected, expectedEvidence, reasonCodes, "model-routing", "MODEL_ROUTING_SUBORDINATE", ["model_routing_projection"]);
@@ -2752,7 +2753,8 @@ var init_work_controls = __esm({
2752
2753
  "loop-control",
2753
2754
  "drift-guard",
2754
2755
  "company-loop",
2755
- "founder-cockpit"
2756
+ "founder-cockpit",
2757
+ "context-control"
2756
2758
  ];
2757
2759
  CAPABILITY_TO_LEGACY_FEATURE = {
2758
2760
  "production-confidence": "visual-proof",
@@ -5510,6 +5512,15 @@ var init_plan_contract = __esm({
5510
5512
  status: "implemented",
5511
5513
  enforcementSurfaces: ["cli", "local-dashboard"],
5512
5514
  privacyBoundary: "local_only"
5515
+ },
5516
+ {
5517
+ key: "artifact_guard_basic",
5518
+ pricingLabel: "Agent artifacts scanned for risks before AI touches the project",
5519
+ tier: "free",
5520
+ included: true,
5521
+ status: "implemented",
5522
+ enforcementSurfaces: ["cli"],
5523
+ privacyBoundary: "local_only"
5513
5524
  }
5514
5525
  ];
5515
5526
  PRO_CAPABILITIES = [
@@ -7325,8 +7336,8 @@ init_run();
7325
7336
  init_work_contract();
7326
7337
  init_receipts();
7327
7338
  init_state_ledger();
7328
- import { writeFileSync as writeFileSync37, mkdirSync as mkdirSync39, existsSync as existsSync62, readFileSync as readFileSync46, appendFileSync as appendFileSync11, rmSync as rmSync3, unlinkSync as unlinkSync4 } from "node:fs";
7329
- import { join as join61 } from "node:path";
7339
+ import { writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, existsSync as existsSync64, readFileSync as readFileSync50, appendFileSync as appendFileSync11, rmSync as rmSync3, unlinkSync as unlinkSync5 } from "node:fs";
7340
+ import { join as join66 } from "node:path";
7330
7341
 
7331
7342
  // src/avorelo/capabilities/activation/index.ts
7332
7343
  import { mkdtempSync, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, rmSync as rmSync2, existsSync as existsSync24 } from "node:fs";
@@ -8111,7 +8122,7 @@ function runFullActivation(targetDir) {
8111
8122
  const dashboardPath = join27(targetDir, ".avorelo", "dashboard", "index.html");
8112
8123
  const localDashboard = { available: existsSync30(dashboardPath), path: existsSync30(dashboardPath) ? dashboardPath : void 0 };
8113
8124
  const blockers = repairsBlocked.length;
8114
- const activationStatus = blockers > 0 ? "blocked" : "active_with_holds";
8125
+ const activationStatus = blockers > 0 ? "blocked" : "active";
8115
8126
  const existingState = readActivationState(targetDir);
8116
8127
  const workspaceId = existingState?.workspaceId || makeWorkspaceId(targetDir);
8117
8128
  const state = {
@@ -8836,7 +8847,7 @@ import { join as join40 } from "node:path";
8836
8847
 
8837
8848
  // src/avorelo/capabilities/runtime-flow/index.ts
8838
8849
  init_entitlement_resolver();
8839
- import { createHash as createHash12 } from "node:crypto";
8850
+ import { createHash as createHash13 } from "node:crypto";
8840
8851
  import { mkdirSync as mkdirSync24, writeFileSync as writeFileSync23, appendFileSync as appendFileSync9, readFileSync as readFileSync23, existsSync as existsSync39 } from "node:fs";
8841
8852
  import { join as join39 } from "node:path";
8842
8853
 
@@ -11464,137 +11475,1412 @@ function buildSummary(status, sourceCount, findingCount) {
11464
11475
  return `Checked ${sourceCount} instruction source(s). ${findingCount} finding(s) detected.`;
11465
11476
  }
11466
11477
 
11467
- // src/avorelo/capabilities/runtime-flow/index.ts
11468
- init_redaction();
11469
- function runtimeDir(dir) {
11470
- return join39(dir, ".avorelo", "runtime");
11478
+ // src/avorelo/kernel/context-control/classify.ts
11479
+ var SECRET_PATTERNS2 = [
11480
+ /(?:api[_-]?key|apikey)\s*[:=]\s*\S+/i,
11481
+ /(?:secret|token|password|passwd|pwd)\s*[:=]\s*\S+/i,
11482
+ /(?:aws|gcp|azure)[_-](?:access|secret|key)\s*[:=]/i,
11483
+ /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
11484
+ /(?:sk-|pk-|rk-)[a-zA-Z0-9]{20,}/,
11485
+ /ghp_[a-zA-Z0-9]{36}/,
11486
+ /glpat-[a-zA-Z0-9_-]{20,}/,
11487
+ /xox[bpsa]-[a-zA-Z0-9-]+/
11488
+ ];
11489
+ var FORBIDDEN_PATTERNS = [
11490
+ /PRIVATE[_ ]KEY/,
11491
+ /\.env\b/,
11492
+ /credentials\.json/,
11493
+ /id_rsa/,
11494
+ /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/
11495
+ ];
11496
+ function classifyContentType(content, label) {
11497
+ const lower = label.toLowerCase();
11498
+ const trimmed = content.slice(0, 2e3);
11499
+ if (lower.includes("log") || lower.includes("output")) {
11500
+ if (/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(trimmed)) return "log";
11501
+ if (/\b(?:ERROR|WARN|INFO|DEBUG|FATAL)\b/.test(trimmed)) return "log";
11502
+ if (/(?:exit code|exited with|status:)\s*\d/i.test(trimmed)) return "log";
11503
+ }
11504
+ if (lower.includes("test")) {
11505
+ if (/(?:PASS|FAIL|✓|✗|passed|failed)\s/i.test(trimmed)) return "test_output";
11506
+ if (/(?:tests?|specs?|suites?)\s+\d+/i.test(trimmed)) return "test_output";
11507
+ }
11508
+ if (lower.includes("diff") || lower.includes("patch")) return "diff";
11509
+ if (lower.includes("tree") || lower.includes("directory")) return "file_tree";
11510
+ if (lower.includes("conversation") || lower.includes("history") || lower.includes("session")) return "conversation_history";
11511
+ if (lower.includes("receipt") || lower.includes("proof") || lower.includes("evidence")) return "receipt_proof_evidence";
11512
+ if (lower.includes("doc") || lower.includes("readme") || lower.includes("markdown")) return "documentation";
11513
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
11514
+ try {
11515
+ JSON.parse(content);
11516
+ return "json_tool_output";
11517
+ } catch {
11518
+ }
11519
+ }
11520
+ if (/^diff --git/.test(trimmed) || /^@@\s/.test(trimmed)) return "diff";
11521
+ if (/^[├└│─\s]+/.test(trimmed) || /^\s*[a-z_-]+\/$/m.test(trimmed)) return "file_tree";
11522
+ if (/\b(?:function|class|const|let|var|import|export|def |async )\b/.test(trimmed)) return "source_like";
11523
+ return "unknown";
11471
11524
  }
11472
- function contextDir(dir) {
11473
- return join39(dir, ".avorelo", "context");
11525
+ function classifySensitivity(content) {
11526
+ for (const pattern of FORBIDDEN_PATTERNS) {
11527
+ if (pattern.test(content)) return "forbidden";
11528
+ }
11529
+ for (const pattern of SECRET_PATTERNS2) {
11530
+ if (pattern.test(content)) return "secret_like";
11531
+ }
11532
+ if (/\b(?:internal|private|confidential)\b/i.test(content.slice(0, 500))) return "internal";
11533
+ return "public";
11534
+ }
11535
+ function classifyPersistence(sensitivity) {
11536
+ switch (sensitivity) {
11537
+ case "forbidden":
11538
+ return { safeForModel: false, safeForPersistence: false, safeForCloudSync: false, localOnly: true, forbidden: true };
11539
+ case "secret_like":
11540
+ return { safeForModel: false, safeForPersistence: false, safeForCloudSync: false, localOnly: true, forbidden: false };
11541
+ case "local_only":
11542
+ return { safeForModel: true, safeForPersistence: true, safeForCloudSync: false, localOnly: true, forbidden: false };
11543
+ case "internal":
11544
+ return { safeForModel: true, safeForPersistence: true, safeForCloudSync: false, localOnly: false, forbidden: false };
11545
+ case "public":
11546
+ return { safeForModel: true, safeForPersistence: true, safeForCloudSync: true, localOnly: false, forbidden: false };
11547
+ }
11548
+ }
11549
+ function classifyVolatility(contentType) {
11550
+ switch (contentType) {
11551
+ case "documentation":
11552
+ case "receipt_proof_evidence":
11553
+ return "stable";
11554
+ case "file_tree":
11555
+ case "source_like":
11556
+ return "semi_stable";
11557
+ default:
11558
+ return "volatile";
11559
+ }
11560
+ }
11561
+ function classifyEvidenceRequirement(contentType, sensitivity) {
11562
+ if (sensitivity === "forbidden") return "forbidden";
11563
+ if (contentType === "receipt_proof_evidence") return "proof_critical";
11564
+ if (contentType === "test_output" || contentType === "diff") return "proof_critical";
11565
+ if (contentType === "log") return "helpful";
11566
+ if (contentType === "file_tree") return "noise";
11567
+ return "helpful";
11568
+ }
11569
+ function classifyRoleRelevance(contentType) {
11570
+ switch (contentType) {
11571
+ case "log":
11572
+ case "test_output":
11573
+ return ["executor", "reviewer", "proof_adapter"];
11574
+ case "diff":
11575
+ return ["reviewer", "proof_adapter"];
11576
+ case "json_tool_output":
11577
+ return ["executor", "reviewer"];
11578
+ case "file_tree":
11579
+ return ["executor"];
11580
+ case "receipt_proof_evidence":
11581
+ return ["proof_adapter", "receipt", "status"];
11582
+ case "conversation_history":
11583
+ return ["executor"];
11584
+ case "documentation":
11585
+ return ["executor", "reviewer"];
11586
+ default:
11587
+ return ["executor"];
11588
+ }
11474
11589
  }
11475
- var SAFE_FALLBACK_FORBIDDEN_ACTIONS = [
11476
- "persist_raw_prompt",
11477
- "persist_raw_source",
11478
- "persist_raw_secret",
11479
- "model_owns_READY",
11480
- "model_owns_entitlement",
11481
- "model_owns_production_readiness",
11482
- "claim_savings_without_evidence"
11590
+ function estimateTokens(content) {
11591
+ return Math.ceil(content.length / 4);
11592
+ }
11593
+ function classifyItem(item2) {
11594
+ const contentType = classifyContentType(item2.content, item2.label);
11595
+ const sensitivity = classifySensitivity(item2.content);
11596
+ const persistence = classifyPersistence(sensitivity);
11597
+ const volatility = classifyVolatility(contentType);
11598
+ const evidenceRequirement = classifyEvidenceRequirement(contentType, sensitivity);
11599
+ const roleRelevance = classifyRoleRelevance(contentType);
11600
+ const tokens = estimateTokens(item2.content);
11601
+ return {
11602
+ contentType,
11603
+ sensitivity,
11604
+ persistence,
11605
+ roleRelevance,
11606
+ volatility,
11607
+ evidenceRequirement,
11608
+ estimatedTokens: tokens
11609
+ };
11610
+ }
11611
+ function containsSecrets(content) {
11612
+ return SECRET_PATTERNS2.some((p) => p.test(content));
11613
+ }
11614
+ function containsForbiddenContent(content) {
11615
+ return FORBIDDEN_PATTERNS.some((p) => p.test(content));
11616
+ }
11617
+
11618
+ // src/avorelo/kernel/context-control/evidence-store.ts
11619
+ import { createHash as createHash12 } from "node:crypto";
11620
+ var evidenceCounter = 0;
11621
+ function createEvidenceRef(kind, sourceType, safeSummary, originalContent) {
11622
+ evidenceCounter++;
11623
+ const hash = createHash12("sha256").update(originalContent).digest("hex").slice(0, 16);
11624
+ return {
11625
+ id: `ctx-ev-${Date.now()}-${evidenceCounter}`,
11626
+ kind,
11627
+ sourceType,
11628
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11629
+ localOnly: true,
11630
+ safeSummary: safeSummary.slice(0, 200),
11631
+ retrievalPolicy: "available",
11632
+ redactionStatus: "clean",
11633
+ provenanceTags: [],
11634
+ contentHash: hash
11635
+ };
11636
+ }
11637
+ function createBlockedEvidenceRef(sourceType, reason) {
11638
+ evidenceCounter++;
11639
+ return {
11640
+ id: `ctx-ev-blocked-${Date.now()}-${evidenceCounter}`,
11641
+ kind: "BLOCKED",
11642
+ sourceType,
11643
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11644
+ localOnly: true,
11645
+ safeSummary: `Blocked: ${reason.slice(0, 150)}`,
11646
+ retrievalPolicy: "forbidden",
11647
+ redactionStatus: "forbidden",
11648
+ provenanceTags: [],
11649
+ contentHash: ""
11650
+ };
11651
+ }
11652
+
11653
+ // src/avorelo/kernel/context-control/reducers/logs.ts
11654
+ var ERROR_PATTERNS = [
11655
+ /\b(?:ERROR|FATAL|CRITICAL|PANIC)\b/i,
11656
+ /\bfailed\b/i,
11657
+ /\berror\b/i,
11658
+ /\bException\b/,
11659
+ /\bTraceback\b/,
11660
+ /exit code [1-9]/i,
11661
+ /exited with (?:code )?[1-9]/i,
11662
+ /\bsegfault\b/i,
11663
+ /\bOOM\b/,
11664
+ /\bkilled\b/i
11483
11665
  ];
11484
- function safeFallbackProjection(reasonCodes, extraVerifierPlan = []) {
11666
+ var WARNING_PATTERNS = [
11667
+ /\bWARN(?:ING)?\b/i,
11668
+ /\bdeprecated\b/i,
11669
+ /\bskipped\b/i
11670
+ ];
11671
+ var COMMAND_PATTERNS = [
11672
+ /^\$\s+.+/m,
11673
+ /^>\s+.+/m,
11674
+ /^Running\s+/m,
11675
+ /^Executing\s+/m
11676
+ ];
11677
+ var SECRET_LINE_PATTERNS = [
11678
+ /(?:api[_-]?key|apikey|secret|token|password|passwd|pwd)\s*[:=]\s*\S+/i,
11679
+ /(?:sk-|pk-|rk-)[a-zA-Z0-9]{20,}/,
11680
+ /ghp_[a-zA-Z0-9]{36}/,
11681
+ /(?:aws|gcp|azure)[_-](?:access|secret|key)/i
11682
+ ];
11683
+ function isImportantLine(line) {
11684
+ return ERROR_PATTERNS.some((p) => p.test(line)) || WARNING_PATTERNS.some((p) => p.test(line)) || COMMAND_PATTERNS.some((p) => p.test(line));
11685
+ }
11686
+ function containsSecret(line) {
11687
+ return SECRET_LINE_PATTERNS.some((p) => p.test(line));
11688
+ }
11689
+ function redactSecretLine(line) {
11690
+ return line.replace(
11691
+ /((?:api[_-]?key|apikey|secret|token|password|passwd|pwd)\s*[:=]\s*)\S+/gi,
11692
+ "$1[REDACTED]"
11693
+ ).replace(
11694
+ /(?:sk-|pk-|rk-)[a-zA-Z0-9]{20,}/g,
11695
+ "[REDACTED_KEY]"
11696
+ ).replace(
11697
+ /ghp_[a-zA-Z0-9]{36}/g,
11698
+ "[REDACTED_GITHUB_TOKEN]"
11699
+ );
11700
+ }
11701
+ function reduceLogs(content, label) {
11702
+ const tokensBefore = estimateTokens(content);
11703
+ const lines = content.split("\n");
11704
+ if (lines.length <= 50) {
11705
+ const cleaned = lines.map((l) => containsSecret(l) ? redactSecretLine(l) : l).join("\n");
11706
+ return {
11707
+ reduced: cleaned,
11708
+ estimatedTokensBefore: tokensBefore,
11709
+ estimatedTokensAfter: estimateTokens(cleaned),
11710
+ evidenceRef: createEvidenceRef("LOG_REDUCTION", "log", `Log: ${label}`, content),
11711
+ proofImpact: "PASS",
11712
+ reasonCodes: lines.some(containsSecret) ? ["SECRET_REDACTED_IN_LOG"] : ["SHORT_LOG_KEPT"],
11713
+ blockedContent: false
11714
+ };
11715
+ }
11716
+ const importantLines = [];
11717
+ const repeatedCounts = /* @__PURE__ */ new Map();
11718
+ let totalLines = lines.length;
11719
+ let errorCount = 0;
11720
+ let warnCount = 0;
11721
+ let secretsRedacted = 0;
11722
+ for (let i = 0; i < lines.length; i++) {
11723
+ const line = lines[i];
11724
+ const trimmed = line.trim();
11725
+ if (!trimmed) continue;
11726
+ if (containsSecret(line)) {
11727
+ secretsRedacted++;
11728
+ const redacted = redactSecretLine(line);
11729
+ if (isImportantLine(line)) {
11730
+ importantLines.push(redacted);
11731
+ }
11732
+ continue;
11733
+ }
11734
+ if (isImportantLine(line)) {
11735
+ if (ERROR_PATTERNS.some((p) => p.test(line))) errorCount++;
11736
+ if (WARNING_PATTERNS.some((p) => p.test(line))) warnCount++;
11737
+ importantLines.push(line);
11738
+ const contextStart = Math.max(0, i - 2);
11739
+ const contextEnd = Math.min(lines.length - 1, i + 2);
11740
+ for (let j = contextStart; j <= contextEnd; j++) {
11741
+ if (j !== i && lines[j].trim() && !isImportantLine(lines[j])) {
11742
+ if (!containsSecret(lines[j])) {
11743
+ importantLines.push(` ${lines[j]}`);
11744
+ }
11745
+ }
11746
+ }
11747
+ continue;
11748
+ }
11749
+ const normalized = trimmed.replace(/\d+/g, "N").replace(/0x[a-fA-F0-9]+/g, "0xN");
11750
+ repeatedCounts.set(normalized, (repeatedCounts.get(normalized) || 0) + 1);
11751
+ }
11752
+ const repeatedSummary = [];
11753
+ for (const [pattern, count] of repeatedCounts) {
11754
+ if (count >= 3) {
11755
+ repeatedSummary.push(` (${count}x) ${pattern.slice(0, 120)}`);
11756
+ }
11757
+ }
11758
+ const parts = [
11759
+ `--- Log Summary: ${label} ---`,
11760
+ `Total lines: ${totalLines}`,
11761
+ `Errors: ${errorCount} | Warnings: ${warnCount} | Secrets redacted: ${secretsRedacted}`,
11762
+ "",
11763
+ "--- Important Lines ---",
11764
+ ...importantLines.slice(0, 100)
11765
+ ];
11766
+ if (repeatedSummary.length > 0) {
11767
+ parts.push("", "--- Repeated Patterns ---", ...repeatedSummary.slice(0, 20));
11768
+ }
11769
+ const reduced = parts.join("\n");
11770
+ const tokensAfter = estimateTokens(reduced);
11771
+ const reasonCodes = ["LARGE_LOG_REDUCED"];
11772
+ if (secretsRedacted > 0) reasonCodes.push("SECRET_REDACTED_IN_LOG");
11773
+ if (errorCount > 0) reasonCodes.push("ERRORS_PRESERVED");
11774
+ if (repeatedSummary.length > 0) reasonCodes.push("REPEATED_LINES_SUMMARIZED");
11775
+ const proofImpact = errorCount > 0 && importantLines.length > 0 ? "PASS" : "DEGRADED";
11485
11776
  return {
11486
- selectedPrimitive: "deterministic_local_read",
11487
- selectedModelProfile: "none",
11488
- resolverStatus: reasonCodes.includes("MODEL_ROUTING_VERIFIER_REJECTED") ? "verifier_rejected" : "routing_unavailable",
11489
- providerClass: "none",
11490
- fallbackPlan: [],
11491
- verifierPlan: extraVerifierPlan,
11777
+ reduced,
11778
+ estimatedTokensBefore: tokensBefore,
11779
+ estimatedTokensAfter: tokensAfter,
11780
+ evidenceRef: createEvidenceRef("LOG_REDUCTION", "log", `Log reduced: ${label}`, content),
11781
+ proofImpact,
11492
11782
  reasonCodes,
11493
- forbiddenActions: [...SAFE_FALLBACK_FORBIDDEN_ACTIONS],
11494
- modelMayAssist: false,
11495
- modelMayDecide: false,
11496
- scannerMayDecide: false,
11497
- finalDecisionOwner: "kernel/stop-continue-gate",
11498
- containsRawPrompt: false,
11499
- containsRawSource: false,
11500
- containsRawSecret: false
11783
+ blockedContent: false
11501
11784
  };
11502
11785
  }
11503
- function freshRuntimeId(seed) {
11504
- return "rts_" + createHash12("sha256").update(seed).digest("hex").slice(0, 12);
11786
+
11787
+ // src/avorelo/kernel/context-control/reducers/json.ts
11788
+ var MAX_ARRAY_ITEMS = 3;
11789
+ var MAX_STRING_LENGTH2 = 200;
11790
+ var MAX_DEPTH = 6;
11791
+ function truncateValue(value, depth) {
11792
+ if (depth > MAX_DEPTH) return "[... nested beyond depth limit]";
11793
+ if (value === null || value === void 0) return value;
11794
+ if (typeof value === "boolean" || typeof value === "number") return value;
11795
+ if (typeof value === "string") {
11796
+ if (value.length > MAX_STRING_LENGTH2) {
11797
+ return value.slice(0, MAX_STRING_LENGTH2) + `... (${value.length} chars)`;
11798
+ }
11799
+ return value;
11800
+ }
11801
+ if (Array.isArray(value)) {
11802
+ if (value.length === 0) return [];
11803
+ const truncated = value.slice(0, MAX_ARRAY_ITEMS).map((v) => truncateValue(v, depth + 1));
11804
+ if (value.length > MAX_ARRAY_ITEMS) {
11805
+ truncated.push(`... (${value.length - MAX_ARRAY_ITEMS} more items, ${value.length} total)`);
11806
+ }
11807
+ return truncated;
11808
+ }
11809
+ if (typeof value === "object") {
11810
+ const obj = value;
11811
+ const keys = Object.keys(obj);
11812
+ const result3 = {};
11813
+ const priorityKeys = ["error", "message", "status", "code", "name", "type", "id", "path"];
11814
+ const sortedKeys = [
11815
+ ...priorityKeys.filter((k) => keys.includes(k)),
11816
+ ...keys.filter((k) => !priorityKeys.includes(k))
11817
+ ];
11818
+ for (const key of sortedKeys) {
11819
+ result3[key] = truncateValue(obj[key], depth + 1);
11820
+ }
11821
+ return result3;
11822
+ }
11823
+ return String(value);
11505
11824
  }
11506
- function coded(text) {
11825
+ function reduceJson(content, label) {
11826
+ const tokensBefore = estimateTokens(content);
11827
+ let parsed;
11507
11828
  try {
11508
- return String(redact(text).value).slice(0, 160);
11829
+ parsed = JSON.parse(content);
11509
11830
  } catch {
11510
- return "redacted";
11831
+ const fallback = content.slice(0, 2e3) + (content.length > 2e3 ? `
11832
+ ... (${content.length} chars total, malformed JSON)` : "");
11833
+ return {
11834
+ reduced: fallback,
11835
+ estimatedTokensBefore: tokensBefore,
11836
+ estimatedTokensAfter: estimateTokens(fallback),
11837
+ evidenceRef: createEvidenceRef("JSON_REDUCTION", "json_tool_output", `JSON fallback: ${label}`, content),
11838
+ proofImpact: "DEGRADED",
11839
+ reasonCodes: ["MALFORMED_JSON_TRUNCATED"],
11840
+ blockedContent: false
11841
+ };
11842
+ }
11843
+ const truncated = truncateValue(parsed, 0);
11844
+ const reduced = JSON.stringify(truncated, null, 2);
11845
+ const tokensAfter = estimateTokens(reduced);
11846
+ const reasonCodes = ["JSON_STRUCTURE_PRESERVED"];
11847
+ if (tokensAfter < tokensBefore * 0.8) {
11848
+ reasonCodes.push("JSON_ARRAYS_TRUNCATED");
11511
11849
  }
11850
+ return {
11851
+ reduced,
11852
+ estimatedTokensBefore: tokensBefore,
11853
+ estimatedTokensAfter: tokensAfter,
11854
+ evidenceRef: createEvidenceRef("JSON_REDUCTION", "json_tool_output", `JSON reduced: ${label}`, content),
11855
+ proofImpact: "PASS",
11856
+ reasonCodes,
11857
+ blockedContent: false
11858
+ };
11512
11859
  }
11513
- function mergeRuntimeGate(baseGate, actionVerdict) {
11514
- if (baseGate === "blocked" || actionVerdict === "block") return "blocked";
11515
- if (baseGate === "require_approval" || actionVerdict === "require_approval" || actionVerdict === "suggest_safer_action") {
11516
- return "require_approval";
11860
+
11861
+ // src/avorelo/kernel/context-control/reducers/file-tree.ts
11862
+ var GENERATED_DIRS = /* @__PURE__ */ new Set([
11863
+ "node_modules",
11864
+ "dist",
11865
+ "build",
11866
+ "out",
11867
+ ".next",
11868
+ ".nuxt",
11869
+ ".output",
11870
+ "coverage",
11871
+ ".nyc_output",
11872
+ "__pycache__",
11873
+ ".pytest_cache",
11874
+ ".git",
11875
+ ".svn",
11876
+ ".hg",
11877
+ "vendor",
11878
+ "bower_components",
11879
+ ".cache",
11880
+ ".parcel-cache",
11881
+ ".turbo",
11882
+ "target",
11883
+ "bin",
11884
+ "obj",
11885
+ ".terraform",
11886
+ ".serverless"
11887
+ ]);
11888
+ var PRIORITY_PATTERNS = [
11889
+ /^src\//,
11890
+ /^lib\//,
11891
+ /^app\//,
11892
+ /^pages\//,
11893
+ /^components\//,
11894
+ /^tests?\//,
11895
+ /^__tests__\//,
11896
+ /^spec\//,
11897
+ /^scripts?\//,
11898
+ /^config/,
11899
+ /^\.(?:env|eslint|prettier|babel|jest|vitest)/,
11900
+ /(?:package|tsconfig|webpack|vite|rollup)\.(?:json|config|js|ts|mjs|cjs)$/,
11901
+ /(?:README|CHANGELOG|LICENSE|CLAUDE|AGENTS|CONTRIBUTING)\b/i,
11902
+ /^docs?\//
11903
+ ];
11904
+ function isPriority(path) {
11905
+ return PRIORITY_PATTERNS.some((p) => p.test(path));
11906
+ }
11907
+ function isGeneratedDir(segment) {
11908
+ return GENERATED_DIRS.has(segment.toLowerCase());
11909
+ }
11910
+ function reduceFileTree(content, label) {
11911
+ const tokensBefore = estimateTokens(content);
11912
+ const lines = content.split("\n").filter((l) => l.trim());
11913
+ if (lines.length <= 100) {
11914
+ return {
11915
+ reduced: content,
11916
+ estimatedTokensBefore: tokensBefore,
11917
+ estimatedTokensAfter: tokensBefore,
11918
+ evidenceRef: null,
11919
+ proofImpact: "PASS",
11920
+ reasonCodes: ["SMALL_TREE_KEPT"],
11921
+ blockedContent: false
11922
+ };
11517
11923
  }
11518
- return "allow";
11924
+ const priorityPaths = [];
11925
+ const generatedDirs = /* @__PURE__ */ new Map();
11926
+ const otherPaths = [];
11927
+ for (const line of lines) {
11928
+ const cleaned = line.replace(/^[├└│─\s│]+/, "").trim();
11929
+ if (!cleaned) continue;
11930
+ const firstSegment = cleaned.split("/")[0];
11931
+ if (isGeneratedDir(firstSegment)) {
11932
+ generatedDirs.set(firstSegment, (generatedDirs.get(firstSegment) || 0) + 1);
11933
+ continue;
11934
+ }
11935
+ if (isPriority(cleaned)) {
11936
+ priorityPaths.push(cleaned);
11937
+ } else {
11938
+ otherPaths.push(cleaned);
11939
+ }
11940
+ }
11941
+ const parts = [
11942
+ `--- File Tree Summary: ${label} ---`,
11943
+ `Total entries: ${lines.length}`,
11944
+ "",
11945
+ "--- Source & Config (priority) ---",
11946
+ ...priorityPaths.slice(0, 80)
11947
+ ];
11948
+ if (otherPaths.length > 0) {
11949
+ parts.push("", `--- Other (${otherPaths.length} entries) ---`);
11950
+ parts.push(...otherPaths.slice(0, 30));
11951
+ if (otherPaths.length > 30) {
11952
+ parts.push(` ... (${otherPaths.length - 30} more)`);
11953
+ }
11954
+ }
11955
+ if (generatedDirs.size > 0) {
11956
+ parts.push("", "--- Generated/Vendor (summarized) ---");
11957
+ for (const [dir, count] of generatedDirs) {
11958
+ parts.push(` ${dir}/ (${count} entries)`);
11959
+ }
11960
+ }
11961
+ const reduced = parts.join("\n");
11962
+ return {
11963
+ reduced,
11964
+ estimatedTokensBefore: tokensBefore,
11965
+ estimatedTokensAfter: estimateTokens(reduced),
11966
+ evidenceRef: createEvidenceRef("FILE_TREE_REDUCTION", "file_tree", `Tree reduced: ${label}`, content),
11967
+ proofImpact: "PASS",
11968
+ reasonCodes: ["FILE_TREE_SUMMARIZED", "GENERATED_DIRS_COLLAPSED"],
11969
+ blockedContent: false
11970
+ };
11519
11971
  }
11520
- function isProofAdapter(adapterId) {
11521
- return adapterId === "semgrep" || adapterId === "playwright-proof" || adapterId === "github-actions";
11972
+
11973
+ // src/avorelo/kernel/context-control/reducers/test-output.ts
11974
+ var FAIL_PATTERNS = [
11975
+ /\b(?:FAIL|FAILED|FAILURE)\b/i,
11976
+ /✗|✘|×|❌/,
11977
+ /\bnot ok\b/i,
11978
+ /AssertionError/i,
11979
+ /\bExpected\b.*\bReceived\b/i,
11980
+ /\bexpected\b.*\bto (?:equal|be|match|include|have)\b/i,
11981
+ /Error:\s/,
11982
+ /\bat\s+\S+\s+\(/
11983
+ ];
11984
+ var PASS_PATTERNS = [
11985
+ /\b(?:PASS|PASSED|OK)\b/i,
11986
+ /✓|✔|✅/,
11987
+ /\bok\b/i
11988
+ ];
11989
+ var SUMMARY_PATTERNS = [
11990
+ /Tests?:\s*\d+/i,
11991
+ /Suites?:\s*\d+/i,
11992
+ /\d+\s+(?:passing|failing|pending|skipped)/i,
11993
+ /Test Files\s/i,
11994
+ /Duration[:\s]/i,
11995
+ /Time:\s/i,
11996
+ /Ran all test suites/i
11997
+ ];
11998
+ var COMMAND_PATTERNS2 = [
11999
+ /^\$\s+/,
12000
+ /^>\s+/,
12001
+ /^Running\s+/i,
12002
+ /^Executing\s+/i,
12003
+ /^npm\s+(?:run|test)/i,
12004
+ /^node\s+/,
12005
+ /^npx\s+/
12006
+ ];
12007
+ function reduceTestOutput(content, label) {
12008
+ const tokensBefore = estimateTokens(content);
12009
+ const lines = content.split("\n");
12010
+ if (lines.length <= 80) {
12011
+ return {
12012
+ reduced: content,
12013
+ estimatedTokensBefore: tokensBefore,
12014
+ estimatedTokensAfter: tokensBefore,
12015
+ evidenceRef: createEvidenceRef("TEST_OUTPUT_REDUCTION", "test_output", `Test output: ${label}`, content),
12016
+ proofImpact: "PASS",
12017
+ reasonCodes: ["SHORT_TEST_OUTPUT_KEPT"],
12018
+ blockedContent: false
12019
+ };
12020
+ }
12021
+ const failingLines = [];
12022
+ const summaryLines = [];
12023
+ const commandLines = [];
12024
+ let passingCount = 0;
12025
+ let failingCount = 0;
12026
+ let inFailBlock = false;
12027
+ let failBlockLines = 0;
12028
+ for (let i = 0; i < lines.length; i++) {
12029
+ const line = lines[i];
12030
+ if (COMMAND_PATTERNS2.some((p) => p.test(line))) {
12031
+ commandLines.push(line);
12032
+ continue;
12033
+ }
12034
+ if (SUMMARY_PATTERNS.some((p) => p.test(line))) {
12035
+ summaryLines.push(line);
12036
+ continue;
12037
+ }
12038
+ if (FAIL_PATTERNS.some((p) => p.test(line))) {
12039
+ inFailBlock = true;
12040
+ failBlockLines = 0;
12041
+ failingCount++;
12042
+ failingLines.push(line);
12043
+ continue;
12044
+ }
12045
+ if (inFailBlock && failBlockLines < 15) {
12046
+ failBlockLines++;
12047
+ failingLines.push(line);
12048
+ if (line.trim() === "" || failBlockLines >= 15) {
12049
+ inFailBlock = false;
12050
+ }
12051
+ continue;
12052
+ }
12053
+ if (PASS_PATTERNS.some((p) => p.test(line))) {
12054
+ passingCount++;
12055
+ inFailBlock = false;
12056
+ continue;
12057
+ }
12058
+ inFailBlock = false;
12059
+ }
12060
+ const parts = [
12061
+ `--- Test Output Summary: ${label} ---`
12062
+ ];
12063
+ if (commandLines.length > 0) {
12064
+ parts.push("Command: " + commandLines[0]);
12065
+ }
12066
+ parts.push(`Passing: ${passingCount} | Failing: ${failingCount}`);
12067
+ if (failingLines.length > 0) {
12068
+ parts.push("", "--- Failing Tests ---");
12069
+ parts.push(...failingLines.slice(0, 200));
12070
+ }
12071
+ if (summaryLines.length > 0) {
12072
+ parts.push("", "--- Summary ---");
12073
+ parts.push(...summaryLines);
12074
+ }
12075
+ if (failingCount === 0 && passingCount > 0) {
12076
+ parts.push("", "All tests passing.");
12077
+ }
12078
+ const reduced = parts.join("\n");
12079
+ const proofImpact = failingCount > 0 && failingLines.length > 0 ? "PASS" : failingCount > 0 ? "DEGRADED" : "PASS";
12080
+ return {
12081
+ reduced,
12082
+ estimatedTokensBefore: tokensBefore,
12083
+ estimatedTokensAfter: estimateTokens(reduced),
12084
+ evidenceRef: createEvidenceRef("TEST_OUTPUT_REDUCTION", "test_output", `Tests reduced: ${label}`, content),
12085
+ proofImpact,
12086
+ reasonCodes: [
12087
+ "TEST_OUTPUT_REDUCED",
12088
+ ...failingCount > 0 ? ["FAILING_TESTS_PRESERVED"] : ["ALL_TESTS_PASSING"],
12089
+ ...passingCount > 10 ? ["PASSING_TESTS_SUMMARIZED"] : []
12090
+ ],
12091
+ blockedContent: false
12092
+ };
11522
12093
  }
11523
- function buildProofAdapterReportItems(toolExecution) {
11524
- const found = [];
11525
- const verified = [];
11526
- const needsAttention = [];
11527
- const metadata = toolExecution.proofMetadata;
11528
- if (!metadata || !isProofAdapter(toolExecution.selectedAdapter)) return { found, verified, needsAttention };
11529
- const confidence = metadata.fake ? "inferred" : "measured";
11530
- if (toolExecution.executionStatus === "executed") {
11531
- verified.push({
11532
- code: "PROOF_ADAPTER_EXECUTED",
11533
- title: "Proof adapter executed",
11534
- summary: `${toolExecution.selectedAdapter}: ${metadata.summary}`,
11535
- confidence
11536
- });
11537
- } else if (toolExecution.executionStatus === "skipped") {
11538
- needsAttention.push({
11539
- code: "PROOF_ADAPTER_SKIPPED",
11540
- title: "Proof adapter skipped",
11541
- summary: `${toolExecution.selectedAdapter}: ${toolExecution.reasonCodes.join(",")}`,
11542
- confidence: "unavailable"
11543
- });
11544
- } else if (toolExecution.executionStatus === "failed" || toolExecution.executionStatus === "blocked") {
11545
- needsAttention.push({
11546
- code: "PROOF_ADAPTER_NOT_READY",
11547
- title: "Proof adapter needs attention",
11548
- summary: `${toolExecution.selectedAdapter}: ${toolExecution.reasonCodes.join(",")}`,
11549
- confidence
11550
- });
12094
+
12095
+ // src/avorelo/kernel/context-control/reducers/diff-summary.ts
12096
+ var GENERATED_FILE_PATTERNS = [
12097
+ /package-lock\.json$/,
12098
+ /yarn\.lock$/,
12099
+ /pnpm-lock\.yaml$/,
12100
+ /\.min\.\w+$/,
12101
+ /\.map$/,
12102
+ /dist\//,
12103
+ /build\//,
12104
+ /\.generated\./,
12105
+ /node_modules\//
12106
+ ];
12107
+ function isGeneratedFile(path) {
12108
+ return GENERATED_FILE_PATTERNS.some((p) => p.test(path));
12109
+ }
12110
+ function reduceDiffSummary(content, label) {
12111
+ const tokensBefore = estimateTokens(content);
12112
+ const lines = content.split("\n");
12113
+ const fileChanges = [];
12114
+ let totalInsertions = 0;
12115
+ let totalDeletions = 0;
12116
+ let currentFile = null;
12117
+ let currentInsertions = 0;
12118
+ let currentDeletions = 0;
12119
+ for (const line of lines) {
12120
+ const diffGit = line.match(/^diff --git a\/(.+?) b\//);
12121
+ if (diffGit) {
12122
+ if (currentFile) {
12123
+ fileChanges.push({
12124
+ path: currentFile,
12125
+ insertions: currentInsertions,
12126
+ deletions: currentDeletions,
12127
+ isGenerated: isGeneratedFile(currentFile)
12128
+ });
12129
+ totalInsertions += currentInsertions;
12130
+ totalDeletions += currentDeletions;
12131
+ }
12132
+ currentFile = diffGit[1];
12133
+ currentInsertions = 0;
12134
+ currentDeletions = 0;
12135
+ continue;
12136
+ }
12137
+ if (line.startsWith("+") && !line.startsWith("+++")) currentInsertions++;
12138
+ if (line.startsWith("-") && !line.startsWith("---")) currentDeletions++;
11551
12139
  }
11552
- if (metadata.findingCount > 0) {
11553
- found.push({
11554
- code: metadata.adapterClass === "ci_readonly" ? "CI_FINDINGS_SUMMARIZED" : "PROOF_FINDINGS_SUMMARIZED",
11555
- title: "Summarized proof findings",
11556
- summary: `${toolExecution.selectedAdapter}: findings=${metadata.findingCount} artifacts=${metadata.artifactCount}`,
11557
- confidence
12140
+ if (currentFile) {
12141
+ fileChanges.push({
12142
+ path: currentFile,
12143
+ insertions: currentInsertions,
12144
+ deletions: currentDeletions,
12145
+ isGenerated: isGeneratedFile(currentFile)
11558
12146
  });
12147
+ totalInsertions += currentInsertions;
12148
+ totalDeletions += currentDeletions;
11559
12149
  }
11560
- return { found, verified, needsAttention };
12150
+ if (fileChanges.length === 0) {
12151
+ const statLine = lines.find((l) => /\d+ files? changed/.test(l));
12152
+ if (statLine) {
12153
+ return {
12154
+ reduced: `--- Diff Summary: ${label} ---
12155
+ ${statLine}`,
12156
+ estimatedTokensBefore: tokensBefore,
12157
+ estimatedTokensAfter: estimateTokens(statLine),
12158
+ evidenceRef: createEvidenceRef("DIFF_SUMMARY", "diff", `Diff stats: ${label}`, content),
12159
+ proofImpact: "PASS",
12160
+ reasonCodes: ["DIFF_STAT_ONLY"],
12161
+ blockedContent: false
12162
+ };
12163
+ }
12164
+ return {
12165
+ reduced: `--- Diff Summary: ${label} ---
12166
+ No parseable diff content.`,
12167
+ estimatedTokensBefore: tokensBefore,
12168
+ estimatedTokensAfter: 10,
12169
+ evidenceRef: createEvidenceRef("DIFF_SUMMARY", "diff", `Diff empty: ${label}`, content),
12170
+ proofImpact: "UNKNOWN",
12171
+ reasonCodes: ["DIFF_NOT_PARSEABLE"],
12172
+ blockedContent: false
12173
+ };
12174
+ }
12175
+ const sourceFiles = fileChanges.filter((f) => !f.isGenerated);
12176
+ const generatedFiles = fileChanges.filter((f) => f.isGenerated);
12177
+ const riskMarkers = [];
12178
+ for (const f of sourceFiles) {
12179
+ if (/(?:auth|security|secret|credential|password|token)/i.test(f.path)) riskMarkers.push(`SECURITY_RELATED: ${f.path}`);
12180
+ if (/(?:migration|schema|database)/i.test(f.path)) riskMarkers.push(`MIGRATION: ${f.path}`);
12181
+ if (/(?:deploy|prod|release|ci|cd)/i.test(f.path)) riskMarkers.push(`DEPLOYMENT: ${f.path}`);
12182
+ }
12183
+ const parts = [
12184
+ `--- Diff Summary: ${label} ---`,
12185
+ `Files changed: ${fileChanges.length} (${sourceFiles.length} source, ${generatedFiles.length} generated)`,
12186
+ `Insertions: +${totalInsertions} | Deletions: -${totalDeletions}`
12187
+ ];
12188
+ if (riskMarkers.length > 0) {
12189
+ parts.push("", "Risk markers:", ...riskMarkers.map((r2) => ` \u26A0 ${r2}`));
12190
+ }
12191
+ parts.push("", "Source files:");
12192
+ for (const f of sourceFiles) {
12193
+ parts.push(` ${f.path} (+${f.insertions}/-${f.deletions})`);
12194
+ }
12195
+ if (generatedFiles.length > 0) {
12196
+ parts.push("", `Generated files: ${generatedFiles.length} (${generatedFiles.map((f) => f.path).join(", ")})`);
12197
+ }
12198
+ const reduced = parts.join("\n");
12199
+ return {
12200
+ reduced,
12201
+ estimatedTokensBefore: tokensBefore,
12202
+ estimatedTokensAfter: estimateTokens(reduced),
12203
+ evidenceRef: createEvidenceRef("DIFF_SUMMARY", "diff", `Diff summarized: ${label}`, content),
12204
+ proofImpact: "PASS",
12205
+ reasonCodes: ["DIFF_STATS_PRESERVED", "RAW_DIFF_NOT_PERSISTED", ...riskMarkers.length > 0 ? ["RISK_MARKERS_DETECTED"] : []],
12206
+ blockedContent: false
12207
+ };
11561
12208
  }
11562
- function runRuntimeSession(input) {
11563
- const { task, dir } = input;
11564
- const planTier = input.planTier ?? "Free";
11565
- const now = input.now ?? Date.now();
11566
- const createdAt = input.createdAt ?? new Date(now).toISOString();
11567
- const warnings = [];
11568
- let compiledContextPacket = null;
11569
- let executorContextPack = null;
11570
- let plannedToolExecution = null;
11571
- const routing = decideRouting({ task, dir, planTier });
11572
- const c = routing.contract;
11573
- const displayTask = routing.displayTask;
11574
- const proposalHints = detectProposalHints(displayTask);
11575
- const entitlementState = resolveSubscriptionEntitlements({ source: "runtime_flow" });
11576
- const capabilityRoute = buildCapabilityRouteDecision({
11577
- taskType: c.route === "deterministic_only" ? "docs" : c.route === "blocked" ? "deploy" : "code_generation",
11578
- riskClass: c.riskClass,
11579
- proofTier: c.proofTier,
11580
- approvalPolicy: c.approvalPolicy,
11581
- proposalHints,
11582
- paymentTouched: c.safetyBoundary.secretRiskCodes.includes("payment") || /payment|billing|invoice/i.test(displayTask),
11583
- authTouched: c.safetyBoundary.secretRiskCodes.includes("auth") || /auth|login|session/i.test(displayTask),
11584
- dashboardTouched: /dashboard|cockpit/i.test(displayTask),
11585
- publicCopyTouched: /public|pricing|landing/i.test(displayTask),
11586
- mcpTouched: /mcp|connector|tool config/i.test(displayTask),
11587
- deepMode: /\bdeep|loop|retry until|autonomous\b/i.test(displayTask),
11588
- browserAvailable: false,
11589
- allowedLegacyFeatures: entitlementState.legacyFeatures
11590
- });
11591
- const actionWorthiness = evaluateActionWorthiness({
11592
- objective: displayTask,
11593
- riskClass: c.riskClass,
11594
- approvalPolicy: c.approvalPolicy,
11595
- proposalHints
11596
- });
11597
- const workControlSummary = buildWorkControlReceiptSummary(capabilityRoute, actionWorthiness);
12209
+
12210
+ // src/avorelo/kernel/context-control/reducers/conversation.ts
12211
+ var OBJECTIVE_PATTERNS = [
12212
+ /\b(?:objective|goal|task|mission|purpose)\s*[:]/i,
12213
+ /\b(?:we need to|I need to|please|implement|fix|add|create|build|update|change|remove|refactor)\b/i
12214
+ ];
12215
+ var CONSTRAINT_PATTERNS = [
12216
+ /\b(?:must not|do not|don't|never|avoid|forbidden|required|constraint|boundary|limit)\b/i,
12217
+ /\b(?:before|after|unless|only if|except)\b/i
12218
+ ];
12219
+ var DECISION_PATTERNS = [
12220
+ /\b(?:decided|decision|chose|chosen|agreed|confirmed|approved|rejected|accepted)\b/i,
12221
+ /\b(?:we'll|we will|let's|approach|strategy|plan is)\b/i
12222
+ ];
12223
+ var UNRESOLVED_PATTERNS = [
12224
+ /\b(?:TODO|FIXME|HACK|QUESTION|TBD|unclear|not sure|need to figure|open question)\b/i,
12225
+ /\?\s*$/
12226
+ ];
12227
+ var STATUS_PATTERNS = [
12228
+ /\b(?:verified|tested|confirmed|working|broken|failing|passed|deployed|merged)\b/i,
12229
+ /\b(?:status|result|outcome|state)\s*[:]/i
12230
+ ];
12231
+ var NOISE_PATTERNS = [
12232
+ /^(?:okay|ok|sure|yes|no|thanks|thank you|got it|understood|right|exactly|perfect)[\.\!\s]*$/i,
12233
+ /^(?:let me|I'll|I will)\s+(?:check|look|see|read|examine)/i,
12234
+ /^(?:here's|here is)\s+(?:the|what)/i,
12235
+ /^(?:great|good|nice|excellent|awesome)/i
12236
+ ];
12237
+ function isNoise(line) {
12238
+ const trimmed = line.trim();
12239
+ if (!trimmed) return true;
12240
+ return NOISE_PATTERNS.some((p) => p.test(trimmed));
12241
+ }
12242
+ function matchesAny(line, patterns) {
12243
+ return patterns.some((p) => p.test(line));
12244
+ }
12245
+ function reduceConversation(content, label) {
12246
+ const tokensBefore = estimateTokens(content);
12247
+ const lines = content.split("\n");
12248
+ if (lines.length <= 60) {
12249
+ return {
12250
+ reduced: content,
12251
+ estimatedTokensBefore: tokensBefore,
12252
+ estimatedTokensAfter: tokensBefore,
12253
+ evidenceRef: createEvidenceRef("CONVERSATION_STATE_SUMMARY", "conversation_history", `Conversation: ${label}`, content),
12254
+ proofImpact: "PASS",
12255
+ reasonCodes: ["SHORT_CONVERSATION_KEPT"],
12256
+ blockedContent: false
12257
+ };
12258
+ }
12259
+ const objectives = [];
12260
+ const constraints = [];
12261
+ const decisions = [];
12262
+ const unresolved = [];
12263
+ const statusUpdates = [];
12264
+ let noiseRemoved = 0;
12265
+ for (const line of lines) {
12266
+ const trimmed = line.trim();
12267
+ if (!trimmed) continue;
12268
+ if (isNoise(trimmed)) {
12269
+ noiseRemoved++;
12270
+ continue;
12271
+ }
12272
+ if (matchesAny(trimmed, OBJECTIVE_PATTERNS)) objectives.push(trimmed);
12273
+ else if (matchesAny(trimmed, CONSTRAINT_PATTERNS)) constraints.push(trimmed);
12274
+ else if (matchesAny(trimmed, DECISION_PATTERNS)) decisions.push(trimmed);
12275
+ else if (matchesAny(trimmed, UNRESOLVED_PATTERNS)) unresolved.push(trimmed);
12276
+ else if (matchesAny(trimmed, STATUS_PATTERNS)) statusUpdates.push(trimmed);
12277
+ }
12278
+ const parts = [`--- Conversation Summary: ${label} ---`];
12279
+ if (objectives.length > 0) {
12280
+ parts.push("", "Objectives:", ...objectives.slice(0, 10).map((o) => ` - ${o.slice(0, 200)}`));
12281
+ }
12282
+ if (constraints.length > 0) {
12283
+ parts.push("", "Constraints:", ...constraints.slice(0, 10).map((c) => ` - ${c.slice(0, 200)}`));
12284
+ }
12285
+ if (decisions.length > 0) {
12286
+ parts.push("", "Decisions:", ...decisions.slice(0, 10).map((d) => ` - ${d.slice(0, 200)}`));
12287
+ }
12288
+ if (unresolved.length > 0) {
12289
+ parts.push("", "Unresolved:", ...unresolved.slice(0, 10).map((u) => ` - ${u.slice(0, 200)}`));
12290
+ }
12291
+ if (statusUpdates.length > 0) {
12292
+ parts.push("", "Latest status:", ...statusUpdates.slice(-5).map((s) => ` - ${s.slice(0, 200)}`));
12293
+ }
12294
+ parts.push("", `Noise removed: ${noiseRemoved} lines`);
12295
+ const reduced = parts.join("\n");
12296
+ return {
12297
+ reduced,
12298
+ estimatedTokensBefore: tokensBefore,
12299
+ estimatedTokensAfter: estimateTokens(reduced),
12300
+ evidenceRef: createEvidenceRef("CONVERSATION_STATE_SUMMARY", "conversation_history", `Conversation reduced: ${label}`, content),
12301
+ proofImpact: objectives.length > 0 || decisions.length > 0 ? "PASS" : "DEGRADED",
12302
+ reasonCodes: [
12303
+ "CONVERSATION_SUMMARIZED",
12304
+ ...noiseRemoved > 0 ? ["NOISE_REMOVED"] : [],
12305
+ ...objectives.length > 0 ? ["OBJECTIVES_PRESERVED"] : [],
12306
+ ...decisions.length > 0 ? ["DECISIONS_PRESERVED"] : [],
12307
+ ...unresolved.length > 0 ? ["UNRESOLVED_FLAGGED"] : []
12308
+ ],
12309
+ blockedContent: false
12310
+ };
12311
+ }
12312
+
12313
+ // src/avorelo/kernel/context-control/cache-align.ts
12314
+ function analyzeCacheAlignment(items) {
12315
+ let stableCount = 0;
12316
+ let semiStableCount = 0;
12317
+ let volatileCount = 0;
12318
+ const invalidationReasons = [];
12319
+ for (const item2 of items) {
12320
+ const contentType = classifyContentType(item2.content, item2.label);
12321
+ const volatility = classifyVolatility(contentType);
12322
+ switch (volatility) {
12323
+ case "stable":
12324
+ stableCount++;
12325
+ break;
12326
+ case "semi_stable":
12327
+ semiStableCount++;
12328
+ break;
12329
+ case "volatile":
12330
+ volatileCount++;
12331
+ break;
12332
+ }
12333
+ }
12334
+ const totalItems = items.length;
12335
+ const cacheBreakRisk = totalItems > 0 && volatileCount > stableCount;
12336
+ if (stableCount === 0 && totalItems > 0) {
12337
+ invalidationReasons.push("NO_STABLE_CONTEXT_BLOCKS");
12338
+ }
12339
+ if (volatileCount > stableCount * 2) {
12340
+ invalidationReasons.push("VOLATILE_DOMINATES_STABLE");
12341
+ }
12342
+ if (totalItems > 20) {
12343
+ invalidationReasons.push("HIGH_ITEM_COUNT_MAY_FRAGMENT_CACHE");
12344
+ }
12345
+ const layout = [];
12346
+ if (stableCount > 0) layout.push("1. System/product contract (stable)");
12347
+ if (stableCount > 0) layout.push("2. Project facts & conventions (stable)");
12348
+ if (semiStableCount > 0) layout.push("3. Repo map & dependencies (semi-stable)");
12349
+ if (volatileCount > 0) layout.push("4. Current task & tool output (volatile)");
12350
+ return {
12351
+ stableBlockCount: stableCount,
12352
+ semiStableBlockCount: semiStableCount,
12353
+ volatileBlockCount: volatileCount,
12354
+ cacheStablePrefixAvailable: stableCount > 0,
12355
+ cacheBreakRisk,
12356
+ cacheInvalidationReasons: invalidationReasons,
12357
+ suggestedPromptLayout: layout
12358
+ };
12359
+ }
12360
+
12361
+ // src/avorelo/kernel/context-control/diagnostics.ts
12362
+ function generateDiagnostics(result3) {
12363
+ const diagnostics = [];
12364
+ if (result3.blockedItemsCount > 0) {
12365
+ diagnostics.push({
12366
+ severity: "warning",
12367
+ code: "CONTEXT_ITEMS_BLOCKED",
12368
+ message: `${result3.blockedItemsCount} context item(s) were blocked due to forbidden or secret-like content.`,
12369
+ remediation: "Review blocked items and redact sensitive content before including in context.",
12370
+ safeForReceipt: true
12371
+ });
12372
+ }
12373
+ if (result3.proofImpact === "DEGRADED") {
12374
+ diagnostics.push({
12375
+ severity: "warning",
12376
+ code: "PROOF_IMPACT_DEGRADED",
12377
+ message: "Context compression may have removed proof-critical evidence.",
12378
+ remediation: "Rerun with evidence retrieval or use manual review to verify proof chain.",
12379
+ safeForReceipt: true
12380
+ });
12381
+ }
12382
+ if (result3.proofImpact === "UNKNOWN") {
12383
+ diagnostics.push({
12384
+ severity: "blocker",
12385
+ code: "PROOF_IMPACT_UNKNOWN",
12386
+ message: "Unable to determine proof impact of context compression.",
12387
+ remediation: "Review original context to determine if proof-critical evidence was affected.",
12388
+ safeForReceipt: true
12389
+ });
12390
+ }
12391
+ if (result3.compressionRatio > 0.95) {
12392
+ diagnostics.push({
12393
+ severity: "info",
12394
+ code: "HIGH_COMPRESSION_RATIO",
12395
+ message: `Context was compressed by ${Math.round(result3.compressionRatio * 100)}%. Verify that important context was preserved.`,
12396
+ remediation: "Check evidence refs to confirm key information is retrievable.",
12397
+ safeForReceipt: true
12398
+ });
12399
+ }
12400
+ if (result3.cacheAlignment.cacheBreakRisk) {
12401
+ diagnostics.push({
12402
+ severity: "info",
12403
+ code: "CACHE_BREAK_RISK",
12404
+ message: "Volatile context blocks outnumber stable blocks, reducing cache effectiveness.",
12405
+ remediation: "Move stable project context (system prompt, conventions) to the front of the prompt.",
12406
+ safeForReceipt: true
12407
+ });
12408
+ }
12409
+ if (!result3.cacheAlignment.cacheStablePrefixAvailable) {
12410
+ diagnostics.push({
12411
+ severity: "info",
12412
+ code: "NO_STABLE_PREFIX",
12413
+ message: "No stable context prefix detected. Provider prompt caching will be less effective.",
12414
+ remediation: "Add stable system/project context blocks at the beginning of the prompt.",
12415
+ safeForReceipt: true
12416
+ });
12417
+ }
12418
+ if (result3.estimatedInputTokensBefore > 1e5) {
12419
+ diagnostics.push({
12420
+ severity: "warning",
12421
+ code: "VERY_LARGE_CONTEXT",
12422
+ message: `Input context estimated at ${result3.estimatedInputTokensBefore.toLocaleString()} tokens before reduction.`,
12423
+ remediation: "Consider narrowing task scope or excluding unnecessary context sources.",
12424
+ safeForReceipt: true
12425
+ });
12426
+ }
12427
+ if (result3.evidenceRefs.length === 0 && result3.estimatedInputTokensBefore > result3.estimatedInputTokensAfter) {
12428
+ diagnostics.push({
12429
+ severity: "warning",
12430
+ code: "COMPRESSION_WITHOUT_EVIDENCE",
12431
+ message: "Context was compressed but no evidence refs were created for recovery.",
12432
+ remediation: "Ensure reducers create evidence refs for recoverable content.",
12433
+ safeForReceipt: true
12434
+ });
12435
+ }
12436
+ return diagnostics;
12437
+ }
12438
+ function buildReceiptSummary(result3) {
12439
+ const reductionPercent = result3.estimatedInputTokensBefore > 0 ? Math.round((result3.estimatedInputTokensBefore - result3.estimatedInputTokensAfter) / result3.estimatedInputTokensBefore * 100) : 0;
12440
+ let status;
12441
+ if (result3.decision === "BLOCK_FORBIDDEN_CONTEXT") {
12442
+ status = "BLOCKED";
12443
+ } else if (result3.proofImpact === "DEGRADED" || result3.proofImpact === "UNKNOWN") {
12444
+ status = "DEGRADED";
12445
+ } else if (result3.decision === "ALLOW_AS_IS" && result3.estimatedInputTokensBefore === result3.estimatedInputTokensAfter) {
12446
+ status = "INACTIVE";
12447
+ } else {
12448
+ status = "ACTIVE";
12449
+ }
12450
+ const summaryParts = [];
12451
+ if (status === "ACTIVE") {
12452
+ summaryParts.push(`Context reduced from ~${result3.estimatedInputTokensBefore.toLocaleString()} to ~${result3.estimatedInputTokensAfter.toLocaleString()} tokens (${reductionPercent}% reduction).`);
12453
+ } else if (status === "BLOCKED") {
12454
+ summaryParts.push("Context blocked due to forbidden content.");
12455
+ } else if (status === "DEGRADED") {
12456
+ summaryParts.push("Context compressed but proof chain may be weakened.");
12457
+ } else {
12458
+ summaryParts.push("Context passed through without reduction.");
12459
+ }
12460
+ if (result3.evidenceRefs.length > 0) {
12461
+ summaryParts.push(`${result3.evidenceRefs.length} evidence ref(s) available for retrieval.`);
12462
+ }
12463
+ return {
12464
+ contextControlStatus: status,
12465
+ estimatedContextBefore: result3.estimatedInputTokensBefore,
12466
+ estimatedContextSent: result3.estimatedInputTokensAfter,
12467
+ reductionPercent,
12468
+ evidenceRefCount: result3.evidenceRefs.length,
12469
+ blockedItemsCount: result3.blockedItemsCount,
12470
+ retrievalAvailable: result3.evidenceRefs.some((r2) => r2.retrievalPolicy === "available"),
12471
+ proofImpact: result3.proofImpact,
12472
+ topReasonCodes: result3.reasonCodes.slice(0, 5),
12473
+ summary: summaryParts.join(" ")
12474
+ };
12475
+ }
12476
+ function renderContextStats(summary) {
12477
+ const lines = [
12478
+ `Context Control: ${summary.contextControlStatus}`,
12479
+ `Estimated context before: ${summary.estimatedContextBefore.toLocaleString()} tokens`,
12480
+ `Estimated context sent: ${summary.estimatedContextSent.toLocaleString()} tokens`,
12481
+ `Reduction: ${summary.reductionPercent}%`,
12482
+ `Evidence refs: ${summary.evidenceRefCount}`,
12483
+ `Blocked items: ${summary.blockedItemsCount}`,
12484
+ `Retrieval available: ${summary.retrievalAvailable ? "yes" : "no"}`,
12485
+ `Proof impact: ${summary.proofImpact}`
12486
+ ];
12487
+ if (summary.topReasonCodes.length > 0) {
12488
+ lines.push(`Top reasons: ${summary.topReasonCodes.join(", ")}`);
12489
+ }
12490
+ return lines.join("\n");
12491
+ }
12492
+ function renderContextExplain(summary, diagnostics) {
12493
+ const parts = [
12494
+ renderContextStats(summary),
12495
+ "",
12496
+ "--- Explanation ---",
12497
+ summary.summary
12498
+ ];
12499
+ if (diagnostics.length > 0) {
12500
+ parts.push("", "--- Diagnostics ---");
12501
+ for (const d of diagnostics) {
12502
+ parts.push(`[${d.severity.toUpperCase()}] ${d.code}: ${d.message}`);
12503
+ parts.push(` Action: ${d.remediation}`);
12504
+ }
12505
+ }
12506
+ return parts.join("\n");
12507
+ }
12508
+
12509
+ // src/avorelo/kernel/context-control/learn.ts
12510
+ var KNOWN_FAILURE_MAPPINGS = [
12511
+ {
12512
+ pattern: /npm publish.*(?:OTP|auth|ENEEDAUTH)/i,
12513
+ candidateType: "WORKFLOW_CORRECTION",
12514
+ template: () => "When npm publish or dist-tag promotion requires auth/OTP, generate a local PowerShell script for the owner to run. Do not ask for OTP in chat.",
12515
+ target: "CLAUDE.md"
12516
+ },
12517
+ {
12518
+ pattern: /(?:EACCES|permission denied|EPERM)/i,
12519
+ candidateType: "TOOL_LIMITATION",
12520
+ template: (match) => `Permission error encountered: ${match.slice(0, 100)}. Consider running with elevated permissions or checking file ownership.`,
12521
+ target: "AGENTS.md"
12522
+ },
12523
+ {
12524
+ pattern: /(?:ENOSPC|disk full|no space)/i,
12525
+ candidateType: "TOOL_LIMITATION",
12526
+ template: () => "Disk space constraints detected. Clean build artifacts before large operations.",
12527
+ target: "AGENTS.md"
12528
+ },
12529
+ {
12530
+ pattern: /(?:timeout|ETIMEDOUT|ESOCKETTIMEDOUT)/i,
12531
+ candidateType: "WORKFLOW_CORRECTION",
12532
+ template: () => "Network timeouts are common in this environment. Use local-first approaches where possible.",
12533
+ target: "CLAUDE.md"
12534
+ },
12535
+ {
12536
+ pattern: /node_modules.*(?:large|huge|big|slow)/i,
12537
+ candidateType: "NOISE_PATTERN",
12538
+ template: () => "node_modules content should be excluded from context. Use package.json and lock file summaries instead.",
12539
+ target: "CLAUDE.md"
12540
+ }
12541
+ ];
12542
+ function isSafeCandidate(text) {
12543
+ const lower = text.toLowerCase();
12544
+ if (/(?:api[_-]?key|secret|token|password)\s*[:=]\s*\S{10,}/i.test(text)) return false;
12545
+ if (/-----BEGIN.*KEY-----/.test(text)) return false;
12546
+ if (/(?:ignore all|disregard|you are now|act as|pretend)/i.test(lower)) return false;
12547
+ return true;
12548
+ }
12549
+ function generateLearningCandidates(input) {
12550
+ const candidates = [];
12551
+ if (input.failurePatterns) {
12552
+ for (const failure of input.failurePatterns) {
12553
+ if (failure.occurrences < 2) continue;
12554
+ for (const mapping of KNOWN_FAILURE_MAPPINGS) {
12555
+ if (mapping.pattern.test(failure.pattern)) {
12556
+ const proposedText = mapping.template(failure.pattern);
12557
+ if (!isSafeCandidate(proposedText)) continue;
12558
+ candidates.push({
12559
+ candidateType: mapping.candidateType,
12560
+ sourceReceiptIds: failure.receiptIds.slice(0, 5),
12561
+ evidenceRefs: failure.evidenceRefs.slice(0, 5),
12562
+ confidence: failure.occurrences >= 5 ? "high" : failure.occurrences >= 3 ? "medium" : "low",
12563
+ proposedText,
12564
+ suggestedWriteTarget: mapping.target,
12565
+ requiresApproval: true,
12566
+ safeForPersistence: true,
12567
+ reasonCodes: [`REPEATED_FAILURE_${failure.occurrences}x`, `PATTERN_MATCH_${mapping.candidateType}`]
12568
+ });
12569
+ break;
12570
+ }
12571
+ }
12572
+ }
12573
+ }
12574
+ const receiptReasonCounts = /* @__PURE__ */ new Map();
12575
+ for (const receipt of input.receipts) {
12576
+ for (const code of receipt.topReasonCodes) {
12577
+ receiptReasonCounts.set(code, (receiptReasonCounts.get(code) || 0) + 1);
12578
+ }
12579
+ }
12580
+ for (const [code, count] of receiptReasonCounts) {
12581
+ if (count >= 3 && code.includes("BLOCKED")) {
12582
+ candidates.push({
12583
+ candidateType: "SAFETY_RULE",
12584
+ sourceReceiptIds: [],
12585
+ evidenceRefs: [],
12586
+ confidence: "medium",
12587
+ proposedText: `Frequently blocked context pattern: ${code}. Review whether this pattern should be allowlisted or permanently blocked.`,
12588
+ suggestedWriteTarget: "CLAUDE.md",
12589
+ requiresApproval: true,
12590
+ safeForPersistence: true,
12591
+ reasonCodes: [`FREQUENT_BLOCK_${count}x`, code]
12592
+ });
12593
+ }
12594
+ }
12595
+ return {
12596
+ candidates,
12597
+ dryRun: true,
12598
+ filesWritten: 0,
12599
+ summary: candidates.length > 0 ? `Generated ${candidates.length} learning candidate(s). All require explicit approval before writing.` : "No learning candidates generated from current evidence."
12600
+ };
12601
+ }
12602
+
12603
+ // src/avorelo/kernel/context-control/index.ts
12604
+ function selectReducer(item2) {
12605
+ const classification = item2.classification ?? classifyItem(item2);
12606
+ switch (classification.contentType) {
12607
+ case "log":
12608
+ return reduceLogs;
12609
+ case "json_tool_output":
12610
+ return reduceJson;
12611
+ case "file_tree":
12612
+ return reduceFileTree;
12613
+ case "test_output":
12614
+ return reduceTestOutput;
12615
+ case "diff":
12616
+ return reduceDiffSummary;
12617
+ case "conversation_history":
12618
+ return reduceConversation;
12619
+ default:
12620
+ return null;
12621
+ }
12622
+ }
12623
+ function isForbidden(item2, input) {
12624
+ if (containsForbiddenContent(item2.content)) return true;
12625
+ if (input.forbiddenContext) {
12626
+ for (const pattern of input.forbiddenContext) {
12627
+ if (item2.label.includes(pattern) || item2.content.includes(pattern)) return true;
12628
+ }
12629
+ }
12630
+ return false;
12631
+ }
12632
+ function isAllowed(item2, input) {
12633
+ if (!input.allowedContext || input.allowedContext.length === 0) return true;
12634
+ return input.allowedContext.some((a) => item2.label.includes(a) || item2.content.includes(a));
12635
+ }
12636
+ function needsManualApproval(item2, input) {
12637
+ if (input.riskLevel === "critical") return true;
12638
+ const classification = item2.classification ?? classifyItem(item2);
12639
+ if (classification.sensitivity === "secret_like" && input.redactionPolicy === "strict") return true;
12640
+ return false;
12641
+ }
12642
+ function processContextControl(input) {
12643
+ const classifiedItems = input.items.map((item2) => ({
12644
+ ...item2,
12645
+ classification: item2.classification ?? classifyItem(item2)
12646
+ }));
12647
+ const totalTokensBefore = classifiedItems.reduce((sum, item2) => sum + item2.classification.estimatedTokens, 0);
12648
+ const reducedParts = [];
12649
+ const evidenceRefs = [];
12650
+ const reasonCodes = [];
12651
+ const learningSignals = [];
12652
+ let blockedCount = 0;
12653
+ let overallProofImpact = "PASS";
12654
+ let overallDecision = "ALLOW_AS_IS";
12655
+ let overallReductionKind = "NONE";
12656
+ let anyReduced = false;
12657
+ let anyBlocked = false;
12658
+ for (const item2 of classifiedItems) {
12659
+ if (isForbidden(item2, input)) {
12660
+ blockedCount++;
12661
+ anyBlocked = true;
12662
+ evidenceRefs.push(createBlockedEvidenceRef(item2.classification.contentType, `Forbidden: ${item2.label}`));
12663
+ reasonCodes.push("FORBIDDEN_CONTEXT_BLOCKED");
12664
+ continue;
12665
+ }
12666
+ if (!isAllowed(item2, input)) {
12667
+ blockedCount++;
12668
+ evidenceRefs.push(createBlockedEvidenceRef(item2.classification.contentType, `Not in allowlist: ${item2.label}`));
12669
+ reasonCodes.push("CONTEXT_NOT_IN_ALLOWLIST");
12670
+ continue;
12671
+ }
12672
+ if (needsManualApproval(item2, input)) {
12673
+ blockedCount++;
12674
+ evidenceRefs.push(createBlockedEvidenceRef(item2.classification.contentType, `Requires approval: ${item2.label}`));
12675
+ reasonCodes.push("MANUAL_APPROVAL_REQUIRED");
12676
+ continue;
12677
+ }
12678
+ if (containsSecrets(item2.content)) {
12679
+ blockedCount++;
12680
+ anyBlocked = true;
12681
+ evidenceRefs.push(createBlockedEvidenceRef(item2.classification.contentType, `Secret-like content: ${item2.label}`));
12682
+ reasonCodes.push("SECRET_LIKE_CONTEXT_BLOCKED");
12683
+ continue;
12684
+ }
12685
+ const reducer = selectReducer(item2);
12686
+ if (reducer && item2.classification.estimatedTokens > 200) {
12687
+ const result3 = reducer(item2.content, item2.label);
12688
+ reducedParts.push(result3.reduced);
12689
+ if (result3.evidenceRef) evidenceRefs.push(result3.evidenceRef);
12690
+ reasonCodes.push(...result3.reasonCodes);
12691
+ anyReduced = true;
12692
+ if (result3.proofImpact === "DEGRADED") overallProofImpact = "DEGRADED";
12693
+ if (result3.proofImpact === "UNKNOWN" && overallProofImpact !== "DEGRADED") overallProofImpact = "UNKNOWN";
12694
+ } else {
12695
+ reducedParts.push(item2.content);
12696
+ }
12697
+ }
12698
+ const reducedContent = reducedParts.join("\n\n");
12699
+ const totalTokensAfter = estimateTokens(reducedContent);
12700
+ if (anyBlocked && blockedCount === classifiedItems.length) {
12701
+ overallDecision = "BLOCK_FORBIDDEN_CONTEXT";
12702
+ overallReductionKind = "BLOCKED";
12703
+ } else if (anyBlocked) {
12704
+ overallDecision = "COMPRESS_WITH_EVIDENCE";
12705
+ overallReductionKind = "BLOCKED";
12706
+ } else if (anyReduced) {
12707
+ overallDecision = "COMPRESS_WITH_EVIDENCE";
12708
+ const dominantType = classifiedItems.filter((i) => !isForbidden(i, input)).sort((a, b) => b.classification.estimatedTokens - a.classification.estimatedTokens)[0];
12709
+ if (dominantType) {
12710
+ const typeToKind = {
12711
+ log: "LOG_REDUCTION",
12712
+ json_tool_output: "JSON_REDUCTION",
12713
+ file_tree: "FILE_TREE_REDUCTION",
12714
+ test_output: "TEST_OUTPUT_REDUCTION",
12715
+ diff: "DIFF_SUMMARY",
12716
+ conversation_history: "CONVERSATION_STATE_SUMMARY"
12717
+ };
12718
+ overallReductionKind = typeToKind[dominantType.classification.contentType] ?? "NONE";
12719
+ }
12720
+ }
12721
+ if (totalTokensAfter > input.contextBudget) {
12722
+ reasonCodes.push("CONTEXT_EXCEEDS_BUDGET");
12723
+ if (overallDecision === "ALLOW_AS_IS") overallDecision = "ASK_FOR_NARROWER_SCOPE";
12724
+ }
12725
+ const compressionRatio = totalTokensBefore > 0 ? (totalTokensBefore - totalTokensAfter) / totalTokensBefore : 0;
12726
+ const deduplicatedReasonCodes = [...new Set(reasonCodes)];
12727
+ const cacheAlignment = analyzeCacheAlignment(classifiedItems);
12728
+ const partialResult = {
12729
+ decision: overallDecision,
12730
+ reductionKind: overallReductionKind,
12731
+ reasonCodes: deduplicatedReasonCodes,
12732
+ riskLevel: input.riskLevel ?? "low",
12733
+ safeForModel: !anyBlocked || blockedCount < classifiedItems.length,
12734
+ safeForPersistence: !deduplicatedReasonCodes.includes("SECRET_LIKE_CONTEXT_BLOCKED"),
12735
+ estimatedInputTokensBefore: totalTokensBefore,
12736
+ estimatedInputTokensAfter: totalTokensAfter,
12737
+ compressionRatio,
12738
+ evidenceRefs,
12739
+ blockedItemsCount: blockedCount,
12740
+ retrievalAvailable: evidenceRefs.some((r2) => r2.retrievalPolicy === "available"),
12741
+ proofImpact: overallProofImpact,
12742
+ receiptSafeSummary: null,
12743
+ diagnostics: [],
12744
+ cacheAlignment,
12745
+ learningSignals,
12746
+ reducedContent
12747
+ };
12748
+ partialResult.diagnostics = generateDiagnostics(partialResult);
12749
+ partialResult.receiptSafeSummary = buildReceiptSummary(partialResult);
12750
+ return partialResult;
12751
+ }
12752
+
12753
+ // src/avorelo/capabilities/runtime-flow/index.ts
12754
+ init_redaction();
12755
+ function runtimeDir(dir) {
12756
+ return join39(dir, ".avorelo", "runtime");
12757
+ }
12758
+ function contextDir(dir) {
12759
+ return join39(dir, ".avorelo", "context");
12760
+ }
12761
+ var SAFE_FALLBACK_FORBIDDEN_ACTIONS = [
12762
+ "persist_raw_prompt",
12763
+ "persist_raw_source",
12764
+ "persist_raw_secret",
12765
+ "model_owns_READY",
12766
+ "model_owns_entitlement",
12767
+ "model_owns_production_readiness",
12768
+ "claim_savings_without_evidence"
12769
+ ];
12770
+ function safeFallbackProjection(reasonCodes, extraVerifierPlan = []) {
12771
+ return {
12772
+ selectedPrimitive: "deterministic_local_read",
12773
+ selectedModelProfile: "none",
12774
+ resolverStatus: reasonCodes.includes("MODEL_ROUTING_VERIFIER_REJECTED") ? "verifier_rejected" : "routing_unavailable",
12775
+ providerClass: "none",
12776
+ fallbackPlan: [],
12777
+ verifierPlan: extraVerifierPlan,
12778
+ reasonCodes,
12779
+ forbiddenActions: [...SAFE_FALLBACK_FORBIDDEN_ACTIONS],
12780
+ modelMayAssist: false,
12781
+ modelMayDecide: false,
12782
+ scannerMayDecide: false,
12783
+ finalDecisionOwner: "kernel/stop-continue-gate",
12784
+ containsRawPrompt: false,
12785
+ containsRawSource: false,
12786
+ containsRawSecret: false
12787
+ };
12788
+ }
12789
+ function freshRuntimeId(seed) {
12790
+ return "rts_" + createHash13("sha256").update(seed).digest("hex").slice(0, 12);
12791
+ }
12792
+ function coded(text) {
12793
+ try {
12794
+ return String(redact(text).value).slice(0, 160);
12795
+ } catch {
12796
+ return "redacted";
12797
+ }
12798
+ }
12799
+ function mergeRuntimeGate(baseGate, actionVerdict) {
12800
+ if (baseGate === "blocked" || actionVerdict === "block") return "blocked";
12801
+ if (baseGate === "require_approval" || actionVerdict === "require_approval" || actionVerdict === "suggest_safer_action") {
12802
+ return "require_approval";
12803
+ }
12804
+ return "allow";
12805
+ }
12806
+ function isProofAdapter(adapterId) {
12807
+ return adapterId === "semgrep" || adapterId === "playwright-proof" || adapterId === "github-actions";
12808
+ }
12809
+ function buildProofAdapterReportItems(toolExecution) {
12810
+ const found = [];
12811
+ const verified = [];
12812
+ const needsAttention = [];
12813
+ const metadata = toolExecution.proofMetadata;
12814
+ if (!metadata || !isProofAdapter(toolExecution.selectedAdapter)) return { found, verified, needsAttention };
12815
+ const confidence = metadata.fake ? "inferred" : "measured";
12816
+ if (toolExecution.executionStatus === "executed") {
12817
+ verified.push({
12818
+ code: "PROOF_ADAPTER_EXECUTED",
12819
+ title: "Proof adapter executed",
12820
+ summary: `${toolExecution.selectedAdapter}: ${metadata.summary}`,
12821
+ confidence
12822
+ });
12823
+ } else if (toolExecution.executionStatus === "skipped") {
12824
+ needsAttention.push({
12825
+ code: "PROOF_ADAPTER_SKIPPED",
12826
+ title: "Proof adapter skipped",
12827
+ summary: `${toolExecution.selectedAdapter}: ${toolExecution.reasonCodes.join(",")}`,
12828
+ confidence: "unavailable"
12829
+ });
12830
+ } else if (toolExecution.executionStatus === "failed" || toolExecution.executionStatus === "blocked") {
12831
+ needsAttention.push({
12832
+ code: "PROOF_ADAPTER_NOT_READY",
12833
+ title: "Proof adapter needs attention",
12834
+ summary: `${toolExecution.selectedAdapter}: ${toolExecution.reasonCodes.join(",")}`,
12835
+ confidence
12836
+ });
12837
+ }
12838
+ if (metadata.findingCount > 0) {
12839
+ found.push({
12840
+ code: metadata.adapterClass === "ci_readonly" ? "CI_FINDINGS_SUMMARIZED" : "PROOF_FINDINGS_SUMMARIZED",
12841
+ title: "Summarized proof findings",
12842
+ summary: `${toolExecution.selectedAdapter}: findings=${metadata.findingCount} artifacts=${metadata.artifactCount}`,
12843
+ confidence
12844
+ });
12845
+ }
12846
+ return { found, verified, needsAttention };
12847
+ }
12848
+ function runRuntimeSession(input) {
12849
+ const { task, dir } = input;
12850
+ const planTier = input.planTier ?? "Free";
12851
+ const now = input.now ?? Date.now();
12852
+ const createdAt = input.createdAt ?? new Date(now).toISOString();
12853
+ const warnings = [];
12854
+ let compiledContextPacket = null;
12855
+ let executorContextPack = null;
12856
+ let plannedToolExecution = null;
12857
+ const routing = decideRouting({ task, dir, planTier });
12858
+ const c = routing.contract;
12859
+ const displayTask = routing.displayTask;
12860
+ const proposalHints = detectProposalHints(displayTask);
12861
+ const entitlementState = resolveSubscriptionEntitlements({ source: "runtime_flow" });
12862
+ const capabilityRoute = buildCapabilityRouteDecision({
12863
+ taskType: c.route === "deterministic_only" ? "docs" : c.route === "blocked" ? "deploy" : "code_generation",
12864
+ riskClass: c.riskClass,
12865
+ proofTier: c.proofTier,
12866
+ approvalPolicy: c.approvalPolicy,
12867
+ proposalHints,
12868
+ paymentTouched: c.safetyBoundary.secretRiskCodes.includes("payment") || /payment|billing|invoice/i.test(displayTask),
12869
+ authTouched: c.safetyBoundary.secretRiskCodes.includes("auth") || /auth|login|session/i.test(displayTask),
12870
+ dashboardTouched: /dashboard|cockpit/i.test(displayTask),
12871
+ publicCopyTouched: /public|pricing|landing/i.test(displayTask),
12872
+ mcpTouched: /mcp|connector|tool config/i.test(displayTask),
12873
+ deepMode: /\bdeep|loop|retry until|autonomous\b/i.test(displayTask),
12874
+ browserAvailable: false,
12875
+ allowedLegacyFeatures: entitlementState.legacyFeatures
12876
+ });
12877
+ const actionWorthiness = evaluateActionWorthiness({
12878
+ objective: displayTask,
12879
+ riskClass: c.riskClass,
12880
+ approvalPolicy: c.approvalPolicy,
12881
+ proposalHints
12882
+ });
12883
+ const workControlSummary = buildWorkControlReceiptSummary(capabilityRoute, actionWorthiness);
11598
12884
  const effectiveGate = mergeRuntimeGate(routing.gate, actionWorthiness.verdict);
11599
12885
  const layers = [{
11600
12886
  order: 1,
@@ -11851,6 +13137,54 @@ function runRuntimeSession(input) {
11851
13137
  } catch {
11852
13138
  base.contextPack = void 0;
11853
13139
  }
13140
+ try {
13141
+ if (compiledContextPacket) {
13142
+ const ccItems = compiledContextPacket.selectedRefs.map((ref2, i) => ({
13143
+ id: `cc_${i}`,
13144
+ label: ref2.label,
13145
+ content: `[${ref2.kind}] ${ref2.label} (${ref2.includeMode}/${ref2.safety})`
13146
+ }));
13147
+ const forbiddenLabels = compiledContextPacket.excludedRefs.map((r2) => r2.label);
13148
+ const budgetMap = { tiny: 4e3, small: 16e3, medium: 5e4, deep: 12e4 };
13149
+ const ccResult = processContextControl({
13150
+ consumerRole: "executor",
13151
+ items: ccItems,
13152
+ contextBudget: budgetMap[compiledContextPacket.contextBudget.targetSize] ?? 5e4,
13153
+ forbiddenContext: forbiddenLabels,
13154
+ redactionPolicy: "standard",
13155
+ riskLevel: c.riskClass === "critical" ? "critical" : c.riskClass
13156
+ });
13157
+ const summary = ccResult.receiptSafeSummary;
13158
+ const status = summary.controlStatus;
13159
+ const ref = (() => {
13160
+ try {
13161
+ const ccDir = join39(dir, ".avorelo", "context-control");
13162
+ mkdirSync24(ccDir, { recursive: true });
13163
+ const p = join39(ccDir, "latest.json");
13164
+ writeFileSync23(p, JSON.stringify(summary, null, 2));
13165
+ return p;
13166
+ } catch {
13167
+ return null;
13168
+ }
13169
+ })();
13170
+ const detail = `status=${status} context=${summary.estimatedContextBefore}\u2192${summary.estimatedContextSent} reduction=${summary.reductionPercent}%`;
13171
+ base.contextControl = {
13172
+ status,
13173
+ estimatedContextBefore: summary.estimatedContextBefore,
13174
+ estimatedContextAfter: summary.estimatedContextSent,
13175
+ reductionPercent: summary.reductionPercent,
13176
+ evidenceRefCount: summary.evidenceRefCount,
13177
+ blockedItemsCount: summary.blockedItemsCount,
13178
+ retrievalAvailable: summary.retrievalAvailable,
13179
+ proofImpact: summary.proofImpact,
13180
+ reasonCodes: summary.reasonCodes,
13181
+ ref
13182
+ };
13183
+ layers.push({ order: 3.5, layer: "context_control", capability: "context-control", status: "completed", ref, detail: coded(detail) });
13184
+ }
13185
+ } catch (e) {
13186
+ layers.push({ order: 3.5, layer: "context_control", capability: "context-control", status: "unavailable", ref: null, detail: coded(errCode(e)) });
13187
+ }
11854
13188
  if (plannedToolExecution) {
11855
13189
  const execCtx = {
11856
13190
  dir,
@@ -12393,6 +13727,18 @@ function buildControlCenter(dir, opts) {
12393
13727
  }
12394
13728
  } catch {
12395
13729
  }
13730
+ const contextControlSection = runtime?.contextControl ? {
13731
+ status: "available",
13732
+ controlStatus: runtime.contextControl.status,
13733
+ estimatedContextBefore: runtime.contextControl.estimatedContextBefore,
13734
+ estimatedContextAfter: runtime.contextControl.estimatedContextAfter,
13735
+ reductionPercent: runtime.contextControl.reductionPercent,
13736
+ evidenceRefCount: runtime.contextControl.evidenceRefCount,
13737
+ blockedItemsCount: runtime.contextControl.blockedItemsCount,
13738
+ retrievalAvailable: runtime.contextControl.retrievalAvailable,
13739
+ proofImpact: runtime.contextControl.proofImpact,
13740
+ reasonCodes: runtime.contextControl.reasonCodes
13741
+ } : { status: "unavailable" };
12396
13742
  const dash = buildLocalDashboard(dir, { now: opts.now, staleWindowMs: opts.staleWindowMs });
12397
13743
  const receiptsSection = {
12398
13744
  total: dash.totals.total,
@@ -12495,6 +13841,7 @@ function buildControlCenter(dir, opts) {
12495
13841
  continuity: continuitySection,
12496
13842
  efficiencySync: efficiencySection,
12497
13843
  contextCheck: contextCheckSection,
13844
+ contextControl: contextControlSection,
12498
13845
  receipts: receiptsSection,
12499
13846
  entitlementGate: entitlementGateSection,
12500
13847
  modelRouting: modelRoutingSection,
@@ -12534,6 +13881,7 @@ function renderText2(m) {
12534
13881
  lines.push(` Continuity:${s.continuity.status === "available" ? ` ${s.continuity.packetStatus} \xB7 ${s.continuity.safeNextActionCount} next action(s) \xB7 ${s.continuity.proofMissingCount} proof gap(s)` : " none"}`);
12535
13882
  lines.push(` Sync: ${s.efficiencySync.status === "available" ? `${s.efficiencySync.mode} \xB7 ${s.efficiencySync.eligibleCount} eligible / ${s.efficiencySync.blockedCount} blocked` : "none"}`);
12536
13883
  lines.push(` Context: ${s.contextCheck.status === "available" ? `${s.contextCheck.checkStatus} \xB7 risk=${s.contextCheck.riskLevel} \xB7 ${s.contextCheck.inputsChecked} source(s) \xB7 ${s.contextCheck.findingCount} finding(s) \xB7 policy=${s.contextCheck.policyPresent}` : "none"}`);
13884
+ lines.push(` Ctx ctrl: ${s.contextControl.status === "available" ? `${s.contextControl.controlStatus} \xB7 context=${s.contextControl.estimatedContextBefore}\u2192${s.contextControl.estimatedContextAfter} \xB7 reduction=${s.contextControl.reductionPercent}% \xB7 evidence=${s.contextControl.evidenceRefCount} \xB7 blocked=${s.contextControl.blockedItemsCount} \xB7 proof=${s.contextControl.proofImpact}` : "none"}`);
12537
13885
  lines.push(` Receipts: ${s.receipts.total} \xB7 done ${s.receipts.done} \xB7 blocked ${s.receipts.blocked} \xB7 needs-attention ${s.receipts.needsAttention} \xB7 stale ${s.receipts.stale}`);
12538
13886
  if (s.modelRouting.status === "available") {
12539
13887
  lines.push(` Routing: primitive=${s.modelRouting.selectedPrimitive} profile=${s.modelRouting.selectedModelProfile} resolver=${s.modelRouting.resolverStatus} provider=${s.modelRouting.providerClass}`);
@@ -12613,7 +13961,7 @@ function openControlCenter(dir, opts) {
12613
13961
  }
12614
13962
 
12615
13963
  // src/avorelo/capabilities/activation/init.ts
12616
- import { createHash as createHash13, randomUUID as randomUUID4 } from "node:crypto";
13964
+ import { createHash as createHash14, randomUUID as randomUUID4 } from "node:crypto";
12617
13965
  import { existsSync as existsSync41, mkdirSync as mkdirSync26, writeFileSync as writeFileSync25, readFileSync as readFileSync25, statSync as statSync4, accessSync, constants } from "node:fs";
12618
13966
  import { join as join41 } from "node:path";
12619
13967
  var ACTIVATION_V1_CONTRACT = "avorelo.activation.v1";
@@ -12628,7 +13976,7 @@ function activationPath(dir) {
12628
13976
  return join41(avoreloDir(dir), "activation.json");
12629
13977
  }
12630
13978
  function freshActivationId(seed) {
12631
- return "act_" + createHash13("sha256").update(seed).digest("hex").slice(0, 12);
13979
+ return "act_" + createHash14("sha256").update(seed).digest("hex").slice(0, 12);
12632
13980
  }
12633
13981
  function loadWorkspace(dir) {
12634
13982
  const p = workspacePath(dir);
@@ -12833,14 +14181,14 @@ function renderDogfoodCheck(r2) {
12833
14181
  }
12834
14182
 
12835
14183
  // src/avorelo/capabilities/core-readiness/index.ts
12836
- import { createHash as createHash15 } from "node:crypto";
14184
+ import { createHash as createHash16 } from "node:crypto";
12837
14185
  import { existsSync as existsSync43, readFileSync as readFileSync27 } from "node:fs";
12838
14186
  import { join as join43 } from "node:path";
12839
14187
 
12840
14188
  // src/avorelo/capabilities/canonical-readiness/index.ts
12841
14189
  import { existsSync as existsSync42, readFileSync as readFileSync26, readdirSync as readdirSync11, statSync as statSync5 } from "node:fs";
12842
14190
  import { join as join42 } from "node:path";
12843
- import { createHash as createHash14 } from "node:crypto";
14191
+ import { createHash as createHash15 } from "node:crypto";
12844
14192
  var FORBIDDEN_CLAIM_PATTERNS = [
12845
14193
  { code: "guaranteed_savings", re: /guaranteed savings|guarantee[sd]? .{0,20}savings/ },
12846
14194
  { code: "zero_leak_guarantee", re: /zero[- ]leak guarantee|guarantee[sd]? .{0,20}no leak|no secret will ever leak/ },
@@ -13046,7 +14394,7 @@ function buildCanonicalReadinessReport(target, opts = {}) {
13046
14394
  contract: "avorelo.canonicalReadiness.v1",
13047
14395
  schemaVersion: 1,
13048
14396
  createdAt,
13049
- readinessId: "rdy_" + createHash14("sha256").update(`${createdAt}:${result3}:${blockers.length}`).digest("hex").slice(0, 12),
14397
+ readinessId: "rdy_" + createHash15("sha256").update(`${createdAt}:${result3}:${blockers.length}`).digest("hex").slice(0, 12),
13050
14398
  result: result3,
13051
14399
  phaseCoverage,
13052
14400
  oldRepoCapabilityCoverage,
@@ -13239,7 +14587,7 @@ function buildCoreReadiness(opts) {
13239
14587
  contract: CORE_READINESS_CONTRACT,
13240
14588
  schemaVersion: 1,
13241
14589
  createdAt,
13242
- reportId: "core_" + createHash15("sha256").update(`${createdAt}:${result3}`).digest("hex").slice(0, 12),
14590
+ reportId: "core_" + createHash16("sha256").update(`${createdAt}:${result3}`).digest("hex").slice(0, 12),
13243
14591
  result: result3,
13244
14592
  checks,
13245
14593
  safetyInvariants,
@@ -16076,7 +17424,7 @@ var FORBIDDEN_KEYS2 = /* @__PURE__ */ new Set([
16076
17424
  "rawPath",
16077
17425
  "settings"
16078
17426
  ]);
16079
- var SECRET_PATTERNS2 = [
17427
+ var SECRET_PATTERNS3 = [
16080
17428
  /ghp_[A-Za-z0-9]{36}/,
16081
17429
  /gho_[A-Za-z0-9]{36}/,
16082
17430
  /github_pat_[A-Za-z0-9_]{22,}/,
@@ -16122,7 +17470,7 @@ function validateDogfoodLearningPayload(payload) {
16122
17470
  function inspectStringValue(val, key) {
16123
17471
  if (val.length > 256) return { valid: false, code: "value_too_long", detail: `${key}: ${val.length} chars` };
16124
17472
  if (val.includes("\n") && val.split("\n").length > 2) return { valid: false, code: "multiline_value", detail: key };
16125
- for (const pat of SECRET_PATTERNS2) {
17473
+ for (const pat of SECRET_PATTERNS3) {
16126
17474
  if (pat.test(val)) return { valid: false, code: "secret_pattern", detail: key };
16127
17475
  }
16128
17476
  if (PATH_PATTERN.test(val) && key !== "createdAt") return { valid: false, code: "path_value", detail: key };
@@ -16416,9 +17764,9 @@ function renderPayloadPreview(p) {
16416
17764
  }
16417
17765
 
16418
17766
  // src/avorelo/capabilities/cloud-sync/claim-token.ts
16419
- import { randomBytes, createHash as createHash16 } from "node:crypto";
17767
+ import { randomBytes, createHash as createHash17 } from "node:crypto";
16420
17768
  function generateLocalFingerprint(target) {
16421
- return createHash16("sha256").update(target).digest("hex").slice(0, 16);
17769
+ return createHash17("sha256").update(target).digest("hex").slice(0, 16);
16422
17770
  }
16423
17771
  var CLAIM_EXPIRY_MS = 24 * 60 * 60 * 1e3;
16424
17772
 
@@ -16724,7 +18072,7 @@ function getTelemetryConfig(dir, state) {
16724
18072
  }
16725
18073
 
16726
18074
  // src/avorelo/telemetry/privacy.ts
16727
- import { createHmac as createHmac2, createHash as createHash17, randomUUID as randomUUID9 } from "node:crypto";
18075
+ import { createHmac as createHmac2, createHash as createHash18, randomUUID as randomUUID9 } from "node:crypto";
16728
18076
  import { basename as basename4, resolve as resolve4 } from "node:path";
16729
18077
  var FORBIDDEN_FIELD_PATTERNS = [
16730
18078
  /rawprompt/i,
@@ -16883,7 +18231,7 @@ function inferAgentType(commandName) {
16883
18231
  function sanitizeTelemetryEvent(rawEvent, ctx) {
16884
18232
  const unsafeFields = detectUnsafeTelemetryFields(rawEvent);
16885
18233
  const excludedFields = new Set(unsafeFields);
16886
- const eventId = typeof rawEvent.eventId === "string" && rawEvent.eventId ? rawEvent.eventId : `evt_${createHash17("sha256").update(randomUUID9()).digest("hex").slice(0, 16)}`;
18234
+ const eventId = typeof rawEvent.eventId === "string" && rawEvent.eventId ? rawEvent.eventId : `evt_${createHash18("sha256").update(randomUUID9()).digest("hex").slice(0, 16)}`;
16887
18235
  const occurredAt = typeof rawEvent.occurredAt === "string" && rawEvent.occurredAt ? rawEvent.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
16888
18236
  const repoRoot2 = typeof rawEvent.repoRoot === "string" ? rawEvent.repoRoot : void 0;
16889
18237
  const remoteUrl = typeof rawEvent.remoteUrl === "string" ? rawEvent.remoteUrl : void 0;
@@ -16949,662 +18297,1726 @@ function readJsonl(path) {
16949
18297
  const parsed = safeParseJson(trimmed);
16950
18298
  if (parsed) values.push(parsed);
16951
18299
  }
16952
- return values;
16953
- }
16954
- function writeJson(path, value) {
16955
- ensureParent(path);
16956
- writeFileSync36(path, JSON.stringify(value, null, 2));
16957
- }
16958
- function writeJsonl(path, values) {
16959
- ensureParent(path);
16960
- const body = values.map((value) => JSON.stringify(value)).join("\n");
16961
- writeFileSync36(path, body.length > 0 ? `${body}
16962
- ` : "");
18300
+ return values;
18301
+ }
18302
+ function writeJson(path, value) {
18303
+ ensureParent(path);
18304
+ writeFileSync36(path, JSON.stringify(value, null, 2));
18305
+ }
18306
+ function writeJsonl(path, values) {
18307
+ ensureParent(path);
18308
+ const body = values.map((value) => JSON.stringify(value)).join("\n");
18309
+ writeFileSync36(path, body.length > 0 ? `${body}
18310
+ ` : "");
18311
+ }
18312
+ function ensureTelemetryState(dir, now = Date.now()) {
18313
+ const paths = getTelemetryConfig(dir, buildTelemetryState(now));
18314
+ if (existsSync60(paths.statePath)) {
18315
+ const parsed = safeParseJson(readFileSync44(paths.statePath, "utf8"));
18316
+ if (parsed?.contract === "avorelo.telemetry.state.v1") return parsed;
18317
+ }
18318
+ const created = buildTelemetryState(now);
18319
+ writeTelemetryState(dir, created);
18320
+ return created;
18321
+ }
18322
+ function readTelemetryState(dir) {
18323
+ return ensureTelemetryState(dir);
18324
+ }
18325
+ function writeTelemetryState(dir, state) {
18326
+ const paths = getTelemetryConfig(dir, state);
18327
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
18328
+ writeJson(paths.statePath, state);
18329
+ }
18330
+ function appendTelemetryEvent(dir, event) {
18331
+ const state = ensureTelemetryState(dir);
18332
+ const config2 = getTelemetryConfig(dir, state);
18333
+ ensureParent(config2.eventsPath);
18334
+ appendFileSync10(config2.eventsPath, `${JSON.stringify(event)}
18335
+ `);
18336
+ }
18337
+ function readTelemetryEvents(dir) {
18338
+ const state = ensureTelemetryState(dir);
18339
+ const config2 = getTelemetryConfig(dir, state);
18340
+ return readJsonl(config2.eventsPath);
18341
+ }
18342
+ function readTelemetryQueue(dir) {
18343
+ const state = ensureTelemetryState(dir);
18344
+ const config2 = getTelemetryConfig(dir, state);
18345
+ return readJsonl(config2.queuePath);
18346
+ }
18347
+ function writeTelemetryQueue(dir, records) {
18348
+ const state = ensureTelemetryState(dir);
18349
+ const config2 = getTelemetryConfig(dir, state);
18350
+ writeJsonl(config2.queuePath, records);
18351
+ }
18352
+ function writeStoredTelemetryRollups(dir, rollups) {
18353
+ const state = ensureTelemetryState(dir);
18354
+ const config2 = getTelemetryConfig(dir, state);
18355
+ writeJson(config2.rollupsPath, rollups);
18356
+ }
18357
+
18358
+ // src/avorelo/telemetry/queue.ts
18359
+ import { randomUUID as randomUUID10 } from "node:crypto";
18360
+ var BACKOFF_MS = [
18361
+ 15 * 60 * 1e3,
18362
+ 60 * 60 * 1e3,
18363
+ 6 * 60 * 60 * 1e3,
18364
+ 24 * 60 * 60 * 1e3
18365
+ ];
18366
+ function nextRetryDelayMs(attempts) {
18367
+ return BACKOFF_MS[Math.min(Math.max(attempts - 1, 0), BACKOFF_MS.length - 1)];
18368
+ }
18369
+ function enqueueTelemetryEvent(dir, event, now = Date.now()) {
18370
+ const queue = readTelemetryQueue(dir);
18371
+ const record = {
18372
+ schemaVersion: "avorelo.telemetry.queue.v1",
18373
+ queueId: `queue_${randomUUID10().replace(/-/g, "").slice(0, 16)}`,
18374
+ event,
18375
+ enqueuedAt: new Date(now).toISOString(),
18376
+ nextAttemptAt: new Date(now).toISOString(),
18377
+ attempts: 0,
18378
+ sentAt: null,
18379
+ quarantinedAt: null,
18380
+ lastErrorCode: null
18381
+ };
18382
+ queue.push(record);
18383
+ writeTelemetryQueue(dir, queue);
18384
+ return record;
18385
+ }
18386
+ function getQueuedTelemetryEvents(dir, now = Date.now()) {
18387
+ return readTelemetryQueue(dir).filter((record) => !record.sentAt && !record.quarantinedAt && Date.parse(record.nextAttemptAt) <= now);
18388
+ }
18389
+ function countQueuedTelemetryEvents(dir) {
18390
+ return readTelemetryQueue(dir).filter((record) => !record.sentAt && !record.quarantinedAt).length;
18391
+ }
18392
+ function markTelemetryBatchSent(dir, queueIds, sentAt = (/* @__PURE__ */ new Date()).toISOString()) {
18393
+ const queue = readTelemetryQueue(dir).map((record) => queueIds.includes(record.queueId) ? { ...record, sentAt, lastErrorCode: null } : record);
18394
+ writeTelemetryQueue(dir, queue);
18395
+ }
18396
+ function markTelemetryBatchRetry(dir, queueIds, errorCode, now = Date.now()) {
18397
+ const queue = readTelemetryQueue(dir).map((record) => {
18398
+ if (!queueIds.includes(record.queueId)) return record;
18399
+ const attempts = record.attempts + 1;
18400
+ return {
18401
+ ...record,
18402
+ attempts,
18403
+ lastErrorCode: errorCode,
18404
+ nextAttemptAt: new Date(now + nextRetryDelayMs(attempts)).toISOString()
18405
+ };
18406
+ });
18407
+ writeTelemetryQueue(dir, queue);
18408
+ }
18409
+ function quarantineTelemetryBatch(dir, queueIds, errorCode, now = Date.now()) {
18410
+ const quarantinedAt = new Date(now).toISOString();
18411
+ const queue = readTelemetryQueue(dir).map((record) => queueIds.includes(record.queueId) ? { ...record, lastErrorCode: errorCode, quarantinedAt } : record);
18412
+ writeTelemetryQueue(dir, queue);
18413
+ }
18414
+
18415
+ // src/avorelo/telemetry/rollups.ts
18416
+ function nowIso2() {
18417
+ return (/* @__PURE__ */ new Date()).toISOString();
18418
+ }
18419
+ function periodStart(period, now = Date.now()) {
18420
+ switch (period) {
18421
+ case "24h":
18422
+ return now - 24 * 60 * 60 * 1e3;
18423
+ case "7d":
18424
+ return now - 7 * 24 * 60 * 60 * 1e3;
18425
+ case "30d":
18426
+ return now - 30 * 24 * 60 * 60 * 1e3;
18427
+ case "90d":
18428
+ return now - 90 * 24 * 60 * 60 * 1e3;
18429
+ }
18430
+ }
18431
+ function filterByPeriod(events, period, now = Date.now()) {
18432
+ const start = periodStart(period, now);
18433
+ return events.filter((event) => {
18434
+ const ts = Date.parse(event.occurredAt);
18435
+ return Number.isFinite(ts) && ts >= start && ts <= now;
18436
+ });
18437
+ }
18438
+ function countEvents(events, name) {
18439
+ return events.filter((event) => event.eventName === name).length;
18440
+ }
18441
+ function uniqueCount(events, selector) {
18442
+ const values = /* @__PURE__ */ new Set();
18443
+ for (const event of events) {
18444
+ const value = selector(event);
18445
+ if (value) values.add(value);
18446
+ }
18447
+ return values.size;
18448
+ }
18449
+ function rate(numerator, denominator) {
18450
+ if (denominator <= 0) return 0;
18451
+ return Number((numerator / denominator).toFixed(4));
18452
+ }
18453
+ function weekStart(iso) {
18454
+ const date = new Date(iso);
18455
+ const day = (date.getUTCDay() + 6) % 7;
18456
+ date.setUTCDate(date.getUTCDate() - day);
18457
+ date.setUTCHours(0, 0, 0, 0);
18458
+ return date.toISOString();
18459
+ }
18460
+ function buildWeeklyTrend(events) {
18461
+ const weekly = /* @__PURE__ */ new Map();
18462
+ for (const event of events) {
18463
+ if (event.eventName !== "controlled_session_completed" && event.eventName !== "controlled_session_blocked") continue;
18464
+ const key = weekStart(event.occurredAt);
18465
+ const bucket = weekly.get(key) ?? { controlledSessions: 0, activeRepos: /* @__PURE__ */ new Set() };
18466
+ bucket.controlledSessions += 1;
18467
+ if (event.repoFingerprint) bucket.activeRepos.add(event.repoFingerprint);
18468
+ weekly.set(key, bucket);
18469
+ }
18470
+ return Array.from(weekly.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-12).map(([start, value]) => ({
18471
+ weekStart: start,
18472
+ controlledSessions: value.controlledSessions,
18473
+ activeRepos: value.activeRepos.size
18474
+ }));
16963
18475
  }
16964
- function ensureTelemetryState(dir, now = Date.now()) {
16965
- const paths = getTelemetryConfig(dir, buildTelemetryState(now));
16966
- if (existsSync60(paths.statePath)) {
16967
- const parsed = safeParseJson(readFileSync44(paths.statePath, "utf8"));
16968
- if (parsed?.contract === "avorelo.telemetry.state.v1") return parsed;
18476
+ function returningAfterDays(events, days) {
18477
+ const byInstallation = /* @__PURE__ */ new Map();
18478
+ for (const event of events) {
18479
+ if (!event.anonymousInstallationId) continue;
18480
+ const list = byInstallation.get(event.anonymousInstallationId) ?? [];
18481
+ list.push(event.occurredAt);
18482
+ byInstallation.set(event.anonymousInstallationId, list);
16969
18483
  }
16970
- const created = buildTelemetryState(now);
16971
- writeTelemetryState(dir, created);
16972
- return created;
18484
+ let count = 0;
18485
+ const thresholdMs = days * 24 * 60 * 60 * 1e3;
18486
+ for (const timestamps of byInstallation.values()) {
18487
+ const sorted = timestamps.map((ts) => Date.parse(ts)).filter(Number.isFinite).sort((a, b) => a - b);
18488
+ if (sorted.length < 2) continue;
18489
+ if (sorted[sorted.length - 1] - sorted[0] >= thresholdMs) count++;
18490
+ }
18491
+ return count;
16973
18492
  }
16974
- function readTelemetryState(dir) {
16975
- return ensureTelemetryState(dir);
18493
+ function retainedWeeks(events, weeks, kind) {
18494
+ const byKey = /* @__PURE__ */ new Map();
18495
+ for (const event of events) {
18496
+ const key = kind === "repo" ? event.repoFingerprint : event.anonymousInstallationId;
18497
+ if (!key) continue;
18498
+ const weeksSeen = byKey.get(key) ?? /* @__PURE__ */ new Set();
18499
+ weeksSeen.add(weekStart(event.occurredAt));
18500
+ byKey.set(key, weeksSeen);
18501
+ }
18502
+ let count = 0;
18503
+ for (const weeksSeen of byKey.values()) {
18504
+ if (weeksSeen.size >= weeks) count++;
18505
+ }
18506
+ return count;
16976
18507
  }
16977
- function writeTelemetryState(dir, state) {
16978
- const paths = getTelemetryConfig(dir, state);
16979
- state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
16980
- writeJson(paths.statePath, state);
18508
+ function providerCounts(events, selector) {
18509
+ const counts = {};
18510
+ for (const event of events) {
18511
+ const value = selector(event);
18512
+ if (!value) continue;
18513
+ counts[value] = (counts[value] ?? 0) + 1;
18514
+ }
18515
+ return counts;
16981
18516
  }
16982
- function appendTelemetryEvent(dir, event) {
16983
- const state = ensureTelemetryState(dir);
16984
- const config2 = getTelemetryConfig(dir, state);
16985
- ensureParent(config2.eventsPath);
16986
- appendFileSync10(config2.eventsPath, `${JSON.stringify(event)}
16987
- `);
18517
+ function computeTelemetrySummary(events, state, period, now = Date.now()) {
18518
+ const scoped = filterByPeriod(events, period, now);
18519
+ const weeklyTrend = buildWeeklyTrend(events);
18520
+ const latestWeek = weeklyTrend[weeklyTrend.length - 1];
18521
+ const previousWeek = weeklyTrend[weeklyTrend.length - 2];
18522
+ const completedSessions = countEvents(scoped, "controlled_session_completed");
18523
+ const failedSessions = countEvents(scoped, "controlled_session_failed");
18524
+ const blockedSessions = countEvents(scoped, "controlled_session_blocked");
18525
+ const startedSessions = countEvents(scoped, "controlled_session_started");
18526
+ const controlledSessions = Math.max(startedSessions, completedSessions + failedSessions + blockedSessions);
18527
+ const receiptsGenerated = countEvents(scoped, "receipt_generated") + countEvents(scoped, "first_receipt_created");
18528
+ const receiptsOpened = countEvents(scoped, "receipt_opened") + countEvents(scoped, "first_open_used");
18529
+ const activeRepos = uniqueCount(scoped, (event) => event.repoFingerprint);
18530
+ const claimStarted = countEvents(scoped, "claim_started");
18531
+ const claimCompleted = countEvents(scoped, "claim_completed");
18532
+ const prLinkedReceipts = countEvents(scoped, "receipt_linked_to_pr");
18533
+ return {
18534
+ period,
18535
+ generatedAt: nowIso2(),
18536
+ telemetryMode: state.modeOverride ?? "default_cloud",
18537
+ lastUploadAt: state.lastUploadAt,
18538
+ privacy: {
18539
+ rawCodeCollected: 0,
18540
+ rawPromptsCollected: 0,
18541
+ rawDiffsCollected: 0,
18542
+ secretsCollected: 0,
18543
+ envVarsCollected: 0,
18544
+ terminalOutputCollected: 0,
18545
+ unsafePayloadsRejected: state.unsafePayloadsRejected,
18546
+ telemetryBatchesAccepted: state.acceptedBatches,
18547
+ telemetryBatchesRejected: state.rejectedBatches
18548
+ },
18549
+ topCards: {
18550
+ controlledSessions,
18551
+ completedSessions,
18552
+ failedSessions,
18553
+ blockedSessions,
18554
+ activeRepos,
18555
+ receiptsGenerated,
18556
+ receiptsOpened,
18557
+ returningRepos4w: retainedWeeks(events, 4, "repo"),
18558
+ prLinkedReceipts,
18559
+ gatesTriggered: countEvents(scoped, "approval_gate_required") + countEvents(scoped, "unsafe_action_blocked"),
18560
+ claimConversion: claimStarted > 0 ? Number((claimCompleted / claimStarted).toFixed(4)) : 0
18561
+ },
18562
+ northStar: {
18563
+ weeklyControlledAgentWorkSessionsInActiveRepos: latestWeek?.controlledSessions ?? 0,
18564
+ sessionsPerActiveRepo: activeRepos > 0 ? Number((completedSessions + blockedSessions) / activeRepos) : 0,
18565
+ weekOverWeekGrowth: latestWeek && previousWeek && previousWeek.controlledSessions > 0 ? Number(((latestWeek.controlledSessions - previousWeek.controlledSessions) / previousWeek.controlledSessions).toFixed(4)) : null,
18566
+ twelveWeekTrend: weeklyTrend
18567
+ },
18568
+ activation: {
18569
+ firstCommand: countEvents(scoped, "first_command_run"),
18570
+ firstReceipt: countEvents(scoped, "first_receipt_created"),
18571
+ firstOpen: countEvents(scoped, "first_open_used"),
18572
+ secondSession: countEvents(scoped, "controlled_session_completed") + countEvents(scoped, "controlled_session_failed") + countEvents(scoped, "controlled_session_blocked"),
18573
+ resumeFromReceipt: countEvents(scoped, "receipt_used_for_resume")
18574
+ },
18575
+ retention: {
18576
+ d1ReturningInstallations: returningAfterDays(events, 1),
18577
+ d7ReturningInstallations: returningAfterDays(events, 7),
18578
+ d28ReturningInstallations: returningAfterDays(events, 28),
18579
+ w1RetainedRepos: retainedWeeks(events, 1, "repo"),
18580
+ w2RetainedRepos: retainedWeeks(events, 2, "repo"),
18581
+ w4RetainedRepos: retainedWeeks(events, 4, "repo"),
18582
+ w1RetainedInstallations: retainedWeeks(events, 1, "installation"),
18583
+ w2RetainedInstallations: retainedWeeks(events, 2, "installation"),
18584
+ w4RetainedInstallations: retainedWeeks(events, 4, "installation"),
18585
+ fourWeekRetainedRepos: retainedWeeks(events, 4, "repo"),
18586
+ fourWeekRetainedInstallations: retainedWeeks(events, 4, "installation")
18587
+ },
18588
+ receipts: {
18589
+ generated: receiptsGenerated,
18590
+ opened: receiptsOpened,
18591
+ exported: countEvents(scoped, "receipt_exported"),
18592
+ resumeUsed: countEvents(scoped, "receipt_used_for_resume"),
18593
+ reviewUsed: countEvents(scoped, "receipt_used_for_review"),
18594
+ prLinked: prLinkedReceipts,
18595
+ openRate: rate(receiptsOpened, receiptsGenerated),
18596
+ resumeRate: rate(countEvents(scoped, "receipt_used_for_resume"), receiptsGenerated),
18597
+ reviewRate: rate(countEvents(scoped, "receipt_used_for_review"), receiptsGenerated),
18598
+ exportRate: rate(countEvents(scoped, "receipt_exported"), receiptsGenerated),
18599
+ prLinkRate: rate(prLinkedReceipts, receiptsGenerated)
18600
+ },
18601
+ workControl: {
18602
+ approvalGates: countEvents(scoped, "approval_gate_required") + countEvents(scoped, "approval_gate_passed") + countEvents(scoped, "approval_gate_rejected"),
18603
+ unsafeActionsBlocked: countEvents(scoped, "unsafe_action_blocked"),
18604
+ secretRisksDetected: countEvents(scoped, "secret_risk_detected"),
18605
+ policyEvents: countEvents(scoped, "policy_config_created") + countEvents(scoped, "policy_config_updated"),
18606
+ blockedSessions
18607
+ },
18608
+ integrations: {
18609
+ gitProviders: providerCounts(scoped, (event) => event.gitProvider),
18610
+ ciProviders: providerCounts(scoped, (event) => event.ciProvider),
18611
+ prContextDetected: countEvents(scoped, "pr_context_detected"),
18612
+ prReceiptAttached: countEvents(scoped, "pr_receipt_attached"),
18613
+ ciChecksCreated: countEvents(scoped, "ci_check_created"),
18614
+ ciChecksPassed: countEvents(scoped, "ci_check_passed"),
18615
+ ciChecksFailed: countEvents(scoped, "ci_check_failed"),
18616
+ jiraIssuesLinked: countEvents(scoped, "jira_issue_linked")
18617
+ },
18618
+ commercialIntent: {
18619
+ pricingViews: countEvents(scoped, "pricing_viewed"),
18620
+ proLimitReached: countEvents(scoped, "pro_limit_reached"),
18621
+ teamLimitReached: countEvents(scoped, "team_limit_reached"),
18622
+ claimStarted,
18623
+ claimCompleted,
18624
+ teamInviteStarted: countEvents(scoped, "team_invite_started"),
18625
+ ssoInterest: countEvents(scoped, "sso_interest"),
18626
+ enterpriseWaitlistJoined: countEvents(scoped, "enterprise_waitlist_joined"),
18627
+ teamAdminInterest: countEvents(scoped, "team_admin_interest")
18628
+ }
18629
+ };
16988
18630
  }
16989
- function readTelemetryEvents(dir) {
16990
- const state = ensureTelemetryState(dir);
16991
- const config2 = getTelemetryConfig(dir, state);
16992
- return readJsonl(config2.eventsPath);
18631
+ function buildStoredTelemetryRollups(events, state, now = Date.now()) {
18632
+ return {
18633
+ schemaVersion: "avorelo.telemetry.rollups.v1",
18634
+ generatedAt: new Date(now).toISOString(),
18635
+ summaries: {
18636
+ "24h": computeTelemetrySummary(events, state, "24h", now),
18637
+ "7d": computeTelemetrySummary(events, state, "7d", now),
18638
+ "30d": computeTelemetrySummary(events, state, "30d", now),
18639
+ "90d": computeTelemetrySummary(events, state, "90d", now)
18640
+ }
18641
+ };
16993
18642
  }
16994
- function readTelemetryQueue(dir) {
16995
- const state = ensureTelemetryState(dir);
16996
- const config2 = getTelemetryConfig(dir, state);
16997
- return readJsonl(config2.queuePath);
18643
+
18644
+ // src/avorelo/telemetry/events.ts
18645
+ function refreshRollups(dir) {
18646
+ const state = readTelemetryState(dir);
18647
+ const events = readTelemetryEvents(dir);
18648
+ writeStoredTelemetryRollups(dir, buildStoredTelemetryRollups(events, state));
16998
18649
  }
16999
- function writeTelemetryQueue(dir, records) {
17000
- const state = ensureTelemetryState(dir);
17001
- const config2 = getTelemetryConfig(dir, state);
17002
- writeJsonl(config2.queuePath, records);
18650
+ function shouldQueue(event) {
18651
+ return event.eventName !== "telemetry_batch_sent" && event.eventName !== "telemetry_batch_failed";
17003
18652
  }
17004
- function writeStoredTelemetryRollups(dir, rollups) {
17005
- const state = ensureTelemetryState(dir);
18653
+ function recordTelemetryEvent(dir, rawEvent) {
18654
+ const state = readTelemetryState(dir);
17006
18655
  const config2 = getTelemetryConfig(dir, state);
17007
- writeJson(config2.rollupsPath, rollups);
17008
- }
17009
-
17010
- // src/avorelo/telemetry/queue.ts
17011
- import { randomUUID as randomUUID10 } from "node:crypto";
17012
- var BACKOFF_MS = [
17013
- 15 * 60 * 1e3,
17014
- 60 * 60 * 1e3,
17015
- 6 * 60 * 60 * 1e3,
17016
- 24 * 60 * 60 * 1e3
17017
- ];
17018
- function nextRetryDelayMs(attempts) {
17019
- return BACKOFF_MS[Math.min(Math.max(attempts - 1, 0), BACKOFF_MS.length - 1)];
17020
- }
17021
- function enqueueTelemetryEvent(dir, event, now = Date.now()) {
17022
- const queue = readTelemetryQueue(dir);
17023
- const record = {
17024
- schemaVersion: "avorelo.telemetry.queue.v1",
17025
- queueId: `queue_${randomUUID10().replace(/-/g, "").slice(0, 16)}`,
17026
- event,
17027
- enqueuedAt: new Date(now).toISOString(),
17028
- nextAttemptAt: new Date(now).toISOString(),
17029
- attempts: 0,
17030
- sentAt: null,
17031
- quarantinedAt: null,
17032
- lastErrorCode: null
18656
+ if (!config2.enabled || config2.mode === "off") {
18657
+ return { ok: false, queued: false, excludedFields: [], unsafeFields: [], reason: "telemetry_disabled" };
18658
+ }
18659
+ const mode = config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud";
18660
+ const sanitized = sanitizeTelemetryEvent(rawEvent, {
18661
+ appVersion: config2.appVersion,
18662
+ mode,
18663
+ anonymousInstallationId: config2.anonymousInstallationId,
18664
+ anonymousWorkspaceId: config2.anonymousWorkspaceId,
18665
+ anonymousTeamId: config2.anonymousTeamId,
18666
+ osFamily: config2.osFamily,
18667
+ salt: state.installationSalt
18668
+ });
18669
+ appendTelemetryEvent(dir, sanitized.event);
18670
+ let queued = false;
18671
+ if (shouldQueue(sanitized.event) && config2.mode !== "local_only" && config2.endpointUrl) {
18672
+ enqueueTelemetryEvent(dir, sanitized.event);
18673
+ state.lastQueuedAt = (/* @__PURE__ */ new Date()).toISOString();
18674
+ writeTelemetryState(dir, state);
18675
+ queued = true;
18676
+ }
18677
+ refreshRollups(dir);
18678
+ return {
18679
+ ok: true,
18680
+ queued,
18681
+ event: sanitized.event,
18682
+ excludedFields: sanitized.excludedFields,
18683
+ unsafeFields: sanitized.unsafeFields
17033
18684
  };
17034
- queue.push(record);
17035
- writeTelemetryQueue(dir, queue);
17036
- return record;
17037
18685
  }
17038
- function getQueuedTelemetryEvents(dir, now = Date.now()) {
17039
- return readTelemetryQueue(dir).filter((record) => !record.sentAt && !record.quarantinedAt && Date.parse(record.nextAttemptAt) <= now);
18686
+ function disableTelemetry(dir, mode = "off") {
18687
+ const state = readTelemetryState(dir);
18688
+ state.modeOverride = mode;
18689
+ writeTelemetryState(dir, state);
17040
18690
  }
17041
- function countQueuedTelemetryEvents(dir) {
17042
- return readTelemetryQueue(dir).filter((record) => !record.sentAt && !record.quarantinedAt).length;
18691
+ function setTelemetryMode(dir, mode) {
18692
+ const state = readTelemetryState(dir);
18693
+ state.modeOverride = mode;
18694
+ writeTelemetryState(dir, state);
17043
18695
  }
17044
- function markTelemetryBatchSent(dir, queueIds, sentAt = (/* @__PURE__ */ new Date()).toISOString()) {
17045
- const queue = readTelemetryQueue(dir).map((record) => queueIds.includes(record.queueId) ? { ...record, sentAt, lastErrorCode: null } : record);
17046
- writeTelemetryQueue(dir, queue);
18696
+
18697
+ // src/avorelo/telemetry/telemetry-status.ts
18698
+ function buildTelemetryStatus(dir) {
18699
+ const state = readTelemetryState(dir);
18700
+ const config2 = getTelemetryConfig(dir, state);
18701
+ return {
18702
+ defaultOn: true,
18703
+ enabled: config2.enabled,
18704
+ mode: config2.mode,
18705
+ lastUpload: state.lastUploadAt,
18706
+ queuedEventCount: countQueuedTelemetryEvents(dir),
18707
+ collected: [
18708
+ "Anonymized installation/workspace/team identifiers",
18709
+ "Repo fingerprints",
18710
+ "Command names and safe status buckets",
18711
+ "Receipt, session, and integration metadata",
18712
+ "Work-control and commercial intent event counts"
18713
+ ],
18714
+ neverCollected: [
18715
+ "Source code",
18716
+ "Raw prompts",
18717
+ "Raw diffs or patches",
18718
+ "Secrets or tokens",
18719
+ "Env var values",
18720
+ "Terminal output",
18721
+ "Repo names",
18722
+ "Org names",
18723
+ "Remote URLs",
18724
+ "Absolute file paths",
18725
+ "Emails and usernames"
18726
+ ],
18727
+ disableInstructions: [
18728
+ "AVORELO_TELEMETRY=off",
18729
+ "avorelo telemetry disable",
18730
+ "avorelo config set telemetry.mode local-only"
18731
+ ]
18732
+ };
17047
18733
  }
17048
- function markTelemetryBatchRetry(dir, queueIds, errorCode, now = Date.now()) {
17049
- const queue = readTelemetryQueue(dir).map((record) => {
17050
- if (!queueIds.includes(record.queueId)) return record;
17051
- const attempts = record.attempts + 1;
17052
- return {
17053
- ...record,
17054
- attempts,
17055
- lastErrorCode: errorCode,
17056
- nextAttemptAt: new Date(now + nextRetryDelayMs(attempts)).toISOString()
17057
- };
18734
+ function renderTelemetryStatus(status) {
18735
+ return [
18736
+ "Avorelo Telemetry Status",
18737
+ " Telemetry is on by default: true",
18738
+ ` Enabled: ${status.enabled}`,
18739
+ ` Mode: ${status.mode}`,
18740
+ ` Last upload: ${status.lastUpload ?? "never"}`,
18741
+ ` Queued events: ${status.queuedEventCount}`,
18742
+ "",
18743
+ " Collected:",
18744
+ ...status.collected.map((line) => ` - ${line}`),
18745
+ "",
18746
+ " Never collected:",
18747
+ ...status.neverCollected.map((line) => ` - ${line}`),
18748
+ "",
18749
+ " Disable/local-only:",
18750
+ ...status.disableInstructions.map((line) => ` ${line}`),
18751
+ ""
18752
+ ].join("\n");
18753
+ }
18754
+
18755
+ // src/avorelo/telemetry/telemetry-preview.ts
18756
+ function buildTelemetryPreview(dir) {
18757
+ const state = readTelemetryState(dir);
18758
+ state.lastPreviewAt = (/* @__PURE__ */ new Date()).toISOString();
18759
+ writeTelemetryState(dir, state);
18760
+ const config2 = getTelemetryConfig(dir, state);
18761
+ const samplePayload = sanitizeTelemetryEvent({
18762
+ eventName: "controlled_session_completed",
18763
+ commandName: "run",
18764
+ durationMs: 42e3,
18765
+ repoRoot: dir,
18766
+ repoName: "demo-private-repo",
18767
+ remoteUrl: "https://github.com/acme/private-demo",
18768
+ sessionId: "session-local-123",
18769
+ receiptId: "receipt-local-456",
18770
+ rawPrompt: "write code",
18771
+ sourceCode: "const secret = process.env.API_KEY;",
18772
+ errorMessage: "network timeout while syncing"
18773
+ }, {
18774
+ appVersion: config2.appVersion,
18775
+ mode: config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud",
18776
+ anonymousInstallationId: config2.anonymousInstallationId,
18777
+ anonymousWorkspaceId: config2.anonymousWorkspaceId,
18778
+ anonymousTeamId: config2.anonymousTeamId,
18779
+ osFamily: config2.osFamily,
18780
+ salt: state.installationSalt
17058
18781
  });
17059
- writeTelemetryQueue(dir, queue);
18782
+ return {
18783
+ samplePayload,
18784
+ excludedSensitiveFields: samplePayload.excludedFields
18785
+ };
17060
18786
  }
17061
- function quarantineTelemetryBatch(dir, queueIds, errorCode, now = Date.now()) {
17062
- const quarantinedAt = new Date(now).toISOString();
17063
- const queue = readTelemetryQueue(dir).map((record) => queueIds.includes(record.queueId) ? { ...record, lastErrorCode: errorCode, quarantinedAt } : record);
17064
- writeTelemetryQueue(dir, queue);
18787
+ function renderTelemetryPreview(preview) {
18788
+ return [
18789
+ "Avorelo Telemetry Preview",
18790
+ " Sample safe payload:",
18791
+ JSON.stringify(preview.samplePayload.event, null, 2),
18792
+ "",
18793
+ " Excluded sensitive fields:",
18794
+ ...preview.excludedSensitiveFields.map((field) => ` - ${field}`),
18795
+ "",
18796
+ " No source, prompts, diffs, secrets, env values, terminal output, repo names, or org names are included.",
18797
+ ""
18798
+ ].join("\n");
17065
18799
  }
17066
18800
 
17067
- // src/avorelo/telemetry/rollups.ts
17068
- function nowIso2() {
17069
- return (/* @__PURE__ */ new Date()).toISOString();
17070
- }
17071
- function periodStart(period, now = Date.now()) {
17072
- switch (period) {
17073
- case "24h":
17074
- return now - 24 * 60 * 60 * 1e3;
17075
- case "7d":
17076
- return now - 7 * 24 * 60 * 60 * 1e3;
17077
- case "30d":
17078
- return now - 30 * 24 * 60 * 60 * 1e3;
17079
- case "90d":
17080
- return now - 90 * 24 * 60 * 60 * 1e3;
18801
+ // src/avorelo/telemetry/integration-detection.ts
18802
+ import { existsSync as existsSync61, readFileSync as readFileSync45 } from "node:fs";
18803
+ import { join as join60 } from "node:path";
18804
+ function detectGitProviderFromConfig(dir) {
18805
+ const configPath = join60(dir, ".git", "config");
18806
+ if (!existsSync61(configPath)) return "unknown";
18807
+ try {
18808
+ const content = readFileSync45(configPath, "utf8").toLowerCase();
18809
+ if (content.includes("github.com")) return "github";
18810
+ if (content.includes("gitlab.com")) return "gitlab";
18811
+ if (content.includes("bitbucket.org")) return "bitbucket";
18812
+ return "local";
18813
+ } catch {
18814
+ return "unknown";
17081
18815
  }
17082
18816
  }
17083
- function filterByPeriod(events, period, now = Date.now()) {
17084
- const start = periodStart(period, now);
17085
- return events.filter((event) => {
17086
- const ts = Date.parse(event.occurredAt);
17087
- return Number.isFinite(ts) && ts >= start && ts <= now;
17088
- });
17089
- }
17090
- function countEvents(events, name) {
17091
- return events.filter((event) => event.eventName === name).length;
18817
+ function detectCiProvider(dir) {
18818
+ if (existsSync61(join60(dir, ".github", "workflows"))) return "github_actions";
18819
+ if (existsSync61(join60(dir, ".gitlab-ci.yml"))) return "gitlab_ci";
18820
+ if (existsSync61(join60(dir, ".circleci", "config.yml"))) return "circle";
18821
+ if (existsSync61(join60(dir, "Jenkinsfile"))) return "jenkins";
18822
+ return "unknown";
17092
18823
  }
17093
- function uniqueCount(events, selector) {
17094
- const values = /* @__PURE__ */ new Set();
17095
- for (const event of events) {
17096
- const value = selector(event);
17097
- if (value) values.add(value);
18824
+ function detectRepoVisibility(dir) {
18825
+ const configPath = join60(dir, ".git", "config");
18826
+ if (!existsSync61(configPath)) return "unknown";
18827
+ try {
18828
+ const content = readFileSync45(configPath, "utf8").toLowerCase();
18829
+ if (content.includes("github.com")) {
18830
+ return content.includes("/public") ? "public" : "private";
18831
+ }
18832
+ return "unknown";
18833
+ } catch {
18834
+ return "unknown";
17098
18835
  }
17099
- return values.size;
17100
18836
  }
17101
- function rate(numerator, denominator) {
17102
- if (denominator <= 0) return 0;
17103
- return Number((numerator / denominator).toFixed(4));
18837
+ function detectIntegrations(dir) {
18838
+ return {
18839
+ gitProvider: detectGitProviderFromConfig(dir),
18840
+ ciProvider: detectCiProvider(dir),
18841
+ repoVisibility: detectRepoVisibility(dir)
18842
+ };
17104
18843
  }
17105
- function weekStart(iso) {
17106
- const date = new Date(iso);
17107
- const day = (date.getUTCDay() + 6) % 7;
17108
- date.setUTCDate(date.getUTCDate() - day);
17109
- date.setUTCHours(0, 0, 0, 0);
17110
- return date.toISOString();
18844
+
18845
+ // src/avorelo/telemetry/sender.ts
18846
+ import { randomUUID as randomUUID11 } from "node:crypto";
18847
+ function buildBatch(dir, limit) {
18848
+ const state = readTelemetryState(dir);
18849
+ const config2 = getTelemetryConfig(dir, state);
18850
+ const records = getQueuedTelemetryEvents(dir).slice(0, limit);
18851
+ if (records.length === 0) return { batch: null, queueIds: [] };
18852
+ return {
18853
+ batch: {
18854
+ schemaVersion: "avorelo.telemetry.batch.v1",
18855
+ batchId: `batch_${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
18856
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18857
+ appVersion: config2.appVersion,
18858
+ anonymousInstallationId: config2.anonymousInstallationId,
18859
+ events: records.map((record) => record.event),
18860
+ privacy: {
18861
+ rawCodeIncluded: false,
18862
+ rawPromptsIncluded: false,
18863
+ rawDiffsIncluded: false,
18864
+ secretsIncluded: false,
18865
+ envIncluded: false,
18866
+ terminalOutputIncluded: false
18867
+ }
18868
+ },
18869
+ queueIds: records.map((record) => record.queueId)
18870
+ };
18871
+ }
18872
+ function appendTransportEvent(dir, eventName, errorClass) {
18873
+ const state = readTelemetryState(dir);
18874
+ const config2 = getTelemetryConfig(dir, state);
18875
+ appendTelemetryEvent(dir, {
18876
+ schemaVersion: "avorelo.telemetry.v1",
18877
+ eventId: `evt_${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
18878
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
18879
+ eventName,
18880
+ mode: config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud",
18881
+ anonymousInstallationId: config2.anonymousInstallationId,
18882
+ anonymousWorkspaceId: config2.anonymousWorkspaceId ?? void 0,
18883
+ anonymousTeamId: config2.anonymousTeamId ?? void 0,
18884
+ appVersion: config2.appVersion,
18885
+ osFamily: config2.osFamily,
18886
+ errorClass
18887
+ });
17111
18888
  }
17112
- function buildWeeklyTrend(events) {
17113
- const weekly = /* @__PURE__ */ new Map();
17114
- for (const event of events) {
17115
- if (event.eventName !== "controlled_session_completed" && event.eventName !== "controlled_session_blocked") continue;
17116
- const key = weekStart(event.occurredAt);
17117
- const bucket = weekly.get(key) ?? { controlledSessions: 0, activeRepos: /* @__PURE__ */ new Set() };
17118
- bucket.controlledSessions += 1;
17119
- if (event.repoFingerprint) bucket.activeRepos.add(event.repoFingerprint);
17120
- weekly.set(key, bucket);
18889
+ async function sendDueTelemetry(dir, opts) {
18890
+ const state = readTelemetryState(dir);
18891
+ const config2 = getTelemetryConfig(dir, state);
18892
+ const queuedCount = countQueuedTelemetryEvents(dir);
18893
+ if (!config2.enabled || config2.mode === "off") {
18894
+ return { attempted: false, sent: false, reason: "telemetry_disabled", queuedCount };
17121
18895
  }
17122
- return Array.from(weekly.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-12).map(([start, value]) => ({
17123
- weekStart: start,
17124
- controlledSessions: value.controlledSessions,
17125
- activeRepos: value.activeRepos.size
17126
- }));
17127
- }
17128
- function returningAfterDays(events, days) {
17129
- const byInstallation = /* @__PURE__ */ new Map();
17130
- for (const event of events) {
17131
- if (!event.anonymousInstallationId) continue;
17132
- const list = byInstallation.get(event.anonymousInstallationId) ?? [];
17133
- list.push(event.occurredAt);
17134
- byInstallation.set(event.anonymousInstallationId, list);
18896
+ if (config2.mode === "local_only" || !config2.endpointUrl) {
18897
+ return { attempted: false, sent: false, reason: "local_only_or_no_endpoint", queuedCount };
17135
18898
  }
17136
- let count = 0;
17137
- const thresholdMs = days * 24 * 60 * 60 * 1e3;
17138
- for (const timestamps of byInstallation.values()) {
17139
- const sorted = timestamps.map((ts) => Date.parse(ts)).filter(Number.isFinite).sort((a, b) => a - b);
17140
- if (sorted.length < 2) continue;
17141
- if (sorted[sorted.length - 1] - sorted[0] >= thresholdMs) count++;
18899
+ const now = opts?.now ?? Date.now();
18900
+ const lastUploadMs = state.lastUploadAt ? Date.parse(state.lastUploadAt) : 0;
18901
+ const dueByInterval = lastUploadMs === 0 || now - lastUploadMs >= config2.uploadIntervalMs;
18902
+ const dueByQueueSize = queuedCount >= config2.batchSize;
18903
+ const dueByOpportunistic = lastUploadMs === 0 || now - lastUploadMs >= config2.opportunisticIntervalMs;
18904
+ if (!opts?.force && !dueByInterval && !dueByQueueSize && !dueByOpportunistic) {
18905
+ return { attempted: false, sent: false, reason: "not_due", queuedCount };
17142
18906
  }
17143
- return count;
17144
- }
17145
- function retainedWeeks(events, weeks, kind) {
17146
- const byKey = /* @__PURE__ */ new Map();
17147
- for (const event of events) {
17148
- const key = kind === "repo" ? event.repoFingerprint : event.anonymousInstallationId;
17149
- if (!key) continue;
17150
- const weeksSeen = byKey.get(key) ?? /* @__PURE__ */ new Set();
17151
- weeksSeen.add(weekStart(event.occurredAt));
17152
- byKey.set(key, weeksSeen);
18907
+ const { batch, queueIds } = buildBatch(dir, config2.batchSize);
18908
+ if (!batch || queueIds.length === 0) {
18909
+ return { attempted: false, sent: false, reason: "queue_empty", queuedCount };
17153
18910
  }
17154
- let count = 0;
17155
- for (const weeksSeen of byKey.values()) {
17156
- if (weeksSeen.size >= weeks) count++;
18911
+ const controller = new AbortController();
18912
+ const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
18913
+ try {
18914
+ const response = await fetch(config2.endpointUrl, {
18915
+ method: "POST",
18916
+ headers: { "Content-Type": "application/json" },
18917
+ body: JSON.stringify(batch),
18918
+ signal: controller.signal
18919
+ });
18920
+ clearTimeout(timeout);
18921
+ if (response.ok) {
18922
+ markTelemetryBatchSent(dir, queueIds, new Date(now).toISOString());
18923
+ state.lastUploadAt = new Date(now).toISOString();
18924
+ state.lastBatchId = batch.batchId;
18925
+ state.lastBatchFailureAt = null;
18926
+ state.lastBatchFailureCode = null;
18927
+ state.acceptedBatches += 1;
18928
+ writeTelemetryState(dir, state);
18929
+ appendTransportEvent(dir, "telemetry_batch_sent");
18930
+ return { attempted: true, sent: true, reason: "accepted", queuedCount };
18931
+ }
18932
+ if (response.status >= 400 && response.status < 500) {
18933
+ quarantineTelemetryBatch(dir, queueIds, `http_${response.status}`, now);
18934
+ state.rejectedBatches += 1;
18935
+ state.lastBatchFailureAt = new Date(now).toISOString();
18936
+ state.lastBatchFailureCode = `http_${response.status}`;
18937
+ writeTelemetryState(dir, state);
18938
+ appendTransportEvent(dir, "telemetry_batch_failed", `http_${response.status}`);
18939
+ return { attempted: true, sent: false, reason: `client_error_${response.status}`, queuedCount };
18940
+ }
18941
+ markTelemetryBatchRetry(dir, queueIds, `http_${response.status}`, now);
18942
+ state.lastBatchFailureAt = new Date(now).toISOString();
18943
+ state.lastBatchFailureCode = `http_${response.status}`;
18944
+ writeTelemetryState(dir, state);
18945
+ appendTransportEvent(dir, "telemetry_batch_failed", `http_${response.status}`);
18946
+ return { attempted: true, sent: false, reason: `server_error_${response.status}`, queuedCount };
18947
+ } catch (error) {
18948
+ clearTimeout(timeout);
18949
+ const code = error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error";
18950
+ markTelemetryBatchRetry(dir, queueIds, code, now);
18951
+ state.lastBatchFailureAt = new Date(now).toISOString();
18952
+ state.lastBatchFailureCode = code;
18953
+ writeTelemetryState(dir, state);
18954
+ appendTransportEvent(dir, "telemetry_batch_failed", code);
18955
+ return { attempted: true, sent: false, reason: code, queuedCount };
17157
18956
  }
17158
- return count;
17159
18957
  }
17160
- function providerCounts(events, selector) {
17161
- const counts = {};
17162
- for (const event of events) {
17163
- const value = selector(event);
17164
- if (!value) continue;
17165
- counts[value] = (counts[value] ?? 0) + 1;
18958
+
18959
+ // src/avorelo/kernel/agent-artifact-guard/guard-handler.ts
18960
+ import { existsSync as existsSync63, readFileSync as readFileSync49 } from "node:fs";
18961
+ import { join as join65 } from "node:path";
18962
+
18963
+ // src/avorelo/kernel/agent-artifact-guard/index.ts
18964
+ import { join as join64 } from "node:path";
18965
+ import { readFileSync as readFileSync48 } from "node:fs";
18966
+
18967
+ // src/avorelo/kernel/agent-artifact-guard/artifact-discovery.ts
18968
+ import { readdirSync as readdirSync18, statSync as statSync13, existsSync as existsSync62 } from "node:fs";
18969
+ import { join as join61, relative as relative3, extname as extname2 } from "node:path";
18970
+ var SCANNED_EXTENSIONS = /* @__PURE__ */ new Set([
18971
+ ".md",
18972
+ ".js",
18973
+ ".ts",
18974
+ ".json",
18975
+ ".yml",
18976
+ ".yaml",
18977
+ ".sh",
18978
+ ".py",
18979
+ ".txt",
18980
+ ".toml"
18981
+ ]);
18982
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
18983
+ "node_modules",
18984
+ "dist",
18985
+ ".git",
18986
+ "build",
18987
+ "out",
18988
+ ".next",
18989
+ "__pycache__"
18990
+ ]);
18991
+ var ARTIFACT_PATTERNS = [
18992
+ { kind: "claude-skill", paths: [".claude/skills"], recursive: true },
18993
+ { kind: "claude-instructions", paths: ["CLAUDE.md", ".claude/CLAUDE.md"] },
18994
+ { kind: "codex-instructions", paths: ["AGENTS.md", "CODEX.md", ".codex/CODEX.md"] },
18995
+ { kind: "cursor-rules", paths: [".cursor/rules", ".cursorrules"], recursive: true },
18996
+ { kind: "mcp-config", paths: [".claude/mcp.json", ".cursor/mcp.json", "mcp.json", ".mcp.json"] },
18997
+ { kind: "github-actions", paths: [".github/workflows"], recursive: true },
18998
+ { kind: "package-scripts", paths: ["package.json"] },
18999
+ { kind: "hooks", paths: [".husky", ".git/hooks", ".claude/hooks"], recursive: true }
19000
+ ];
19001
+ function walkDir2(dir, results) {
19002
+ let entries;
19003
+ try {
19004
+ entries = readdirSync18(dir);
19005
+ } catch {
19006
+ return;
19007
+ }
19008
+ for (const entry of entries) {
19009
+ if (IGNORED_DIRS.has(entry)) continue;
19010
+ const full = join61(dir, entry);
19011
+ let st;
19012
+ try {
19013
+ st = statSync13(full);
19014
+ } catch {
19015
+ continue;
19016
+ }
19017
+ if (st.isDirectory()) {
19018
+ walkDir2(full, results);
19019
+ } else if (st.isFile() && SCANNED_EXTENSIONS.has(extname2(full).toLowerCase())) {
19020
+ results.push(full);
19021
+ }
17166
19022
  }
17167
- return counts;
17168
19023
  }
17169
- function computeTelemetrySummary(events, state, period, now = Date.now()) {
17170
- const scoped = filterByPeriod(events, period, now);
17171
- const weeklyTrend = buildWeeklyTrend(events);
17172
- const latestWeek = weeklyTrend[weeklyTrend.length - 1];
17173
- const previousWeek = weeklyTrend[weeklyTrend.length - 2];
17174
- const completedSessions = countEvents(scoped, "controlled_session_completed");
17175
- const failedSessions = countEvents(scoped, "controlled_session_failed");
17176
- const blockedSessions = countEvents(scoped, "controlled_session_blocked");
17177
- const startedSessions = countEvents(scoped, "controlled_session_started");
17178
- const controlledSessions = Math.max(startedSessions, completedSessions + failedSessions + blockedSessions);
17179
- const receiptsGenerated = countEvents(scoped, "receipt_generated") + countEvents(scoped, "first_receipt_created");
17180
- const receiptsOpened = countEvents(scoped, "receipt_opened") + countEvents(scoped, "first_open_used");
17181
- const activeRepos = uniqueCount(scoped, (event) => event.repoFingerprint);
17182
- const claimStarted = countEvents(scoped, "claim_started");
17183
- const claimCompleted = countEvents(scoped, "claim_completed");
17184
- const prLinkedReceipts = countEvents(scoped, "receipt_linked_to_pr");
17185
- return {
17186
- period,
17187
- generatedAt: nowIso2(),
17188
- telemetryMode: state.modeOverride ?? "default_cloud",
17189
- lastUploadAt: state.lastUploadAt,
17190
- privacy: {
17191
- rawCodeCollected: 0,
17192
- rawPromptsCollected: 0,
17193
- rawDiffsCollected: 0,
17194
- secretsCollected: 0,
17195
- envVarsCollected: 0,
17196
- terminalOutputCollected: 0,
17197
- unsafePayloadsRejected: state.unsafePayloadsRejected,
17198
- telemetryBatchesAccepted: state.acceptedBatches,
17199
- telemetryBatchesRejected: state.rejectedBatches
17200
- },
17201
- topCards: {
17202
- controlledSessions,
17203
- completedSessions,
17204
- failedSessions,
17205
- blockedSessions,
17206
- activeRepos,
17207
- receiptsGenerated,
17208
- receiptsOpened,
17209
- returningRepos4w: retainedWeeks(events, 4, "repo"),
17210
- prLinkedReceipts,
17211
- gatesTriggered: countEvents(scoped, "approval_gate_required") + countEvents(scoped, "unsafe_action_blocked"),
17212
- claimConversion: claimStarted > 0 ? Number((claimCompleted / claimStarted).toFixed(4)) : 0
17213
- },
17214
- northStar: {
17215
- weeklyControlledAgentWorkSessionsInActiveRepos: latestWeek?.controlledSessions ?? 0,
17216
- sessionsPerActiveRepo: activeRepos > 0 ? Number((completedSessions + blockedSessions) / activeRepos) : 0,
17217
- weekOverWeekGrowth: latestWeek && previousWeek && previousWeek.controlledSessions > 0 ? Number(((latestWeek.controlledSessions - previousWeek.controlledSessions) / previousWeek.controlledSessions).toFixed(4)) : null,
17218
- twelveWeekTrend: weeklyTrend
17219
- },
17220
- activation: {
17221
- firstCommand: countEvents(scoped, "first_command_run"),
17222
- firstReceipt: countEvents(scoped, "first_receipt_created"),
17223
- firstOpen: countEvents(scoped, "first_open_used"),
17224
- secondSession: countEvents(scoped, "controlled_session_completed") + countEvents(scoped, "controlled_session_failed") + countEvents(scoped, "controlled_session_blocked"),
17225
- resumeFromReceipt: countEvents(scoped, "receipt_used_for_resume")
17226
- },
17227
- retention: {
17228
- d1ReturningInstallations: returningAfterDays(events, 1),
17229
- d7ReturningInstallations: returningAfterDays(events, 7),
17230
- d28ReturningInstallations: returningAfterDays(events, 28),
17231
- w1RetainedRepos: retainedWeeks(events, 1, "repo"),
17232
- w2RetainedRepos: retainedWeeks(events, 2, "repo"),
17233
- w4RetainedRepos: retainedWeeks(events, 4, "repo"),
17234
- w1RetainedInstallations: retainedWeeks(events, 1, "installation"),
17235
- w2RetainedInstallations: retainedWeeks(events, 2, "installation"),
17236
- w4RetainedInstallations: retainedWeeks(events, 4, "installation"),
17237
- fourWeekRetainedRepos: retainedWeeks(events, 4, "repo"),
17238
- fourWeekRetainedInstallations: retainedWeeks(events, 4, "installation")
17239
- },
17240
- receipts: {
17241
- generated: receiptsGenerated,
17242
- opened: receiptsOpened,
17243
- exported: countEvents(scoped, "receipt_exported"),
17244
- resumeUsed: countEvents(scoped, "receipt_used_for_resume"),
17245
- reviewUsed: countEvents(scoped, "receipt_used_for_review"),
17246
- prLinked: prLinkedReceipts,
17247
- openRate: rate(receiptsOpened, receiptsGenerated),
17248
- resumeRate: rate(countEvents(scoped, "receipt_used_for_resume"), receiptsGenerated),
17249
- reviewRate: rate(countEvents(scoped, "receipt_used_for_review"), receiptsGenerated),
17250
- exportRate: rate(countEvents(scoped, "receipt_exported"), receiptsGenerated),
17251
- prLinkRate: rate(prLinkedReceipts, receiptsGenerated)
17252
- },
17253
- workControl: {
17254
- approvalGates: countEvents(scoped, "approval_gate_required") + countEvents(scoped, "approval_gate_passed") + countEvents(scoped, "approval_gate_rejected"),
17255
- unsafeActionsBlocked: countEvents(scoped, "unsafe_action_blocked"),
17256
- secretRisksDetected: countEvents(scoped, "secret_risk_detected"),
17257
- policyEvents: countEvents(scoped, "policy_config_created") + countEvents(scoped, "policy_config_updated"),
17258
- blockedSessions
17259
- },
17260
- integrations: {
17261
- gitProviders: providerCounts(scoped, (event) => event.gitProvider),
17262
- ciProviders: providerCounts(scoped, (event) => event.ciProvider),
17263
- prContextDetected: countEvents(scoped, "pr_context_detected"),
17264
- prReceiptAttached: countEvents(scoped, "pr_receipt_attached"),
17265
- ciChecksCreated: countEvents(scoped, "ci_check_created"),
17266
- ciChecksPassed: countEvents(scoped, "ci_check_passed"),
17267
- ciChecksFailed: countEvents(scoped, "ci_check_failed"),
17268
- jiraIssuesLinked: countEvents(scoped, "jira_issue_linked")
17269
- },
17270
- commercialIntent: {
17271
- pricingViews: countEvents(scoped, "pricing_viewed"),
17272
- proLimitReached: countEvents(scoped, "pro_limit_reached"),
17273
- teamLimitReached: countEvents(scoped, "team_limit_reached"),
17274
- claimStarted,
17275
- claimCompleted,
17276
- teamInviteStarted: countEvents(scoped, "team_invite_started"),
17277
- ssoInterest: countEvents(scoped, "sso_interest"),
17278
- enterpriseWaitlistJoined: countEvents(scoped, "enterprise_waitlist_joined"),
17279
- teamAdminInterest: countEvents(scoped, "team_admin_interest")
19024
+ function discoverArtifacts(projectRoot) {
19025
+ const artifacts = [];
19026
+ for (const pattern of ARTIFACT_PATTERNS) {
19027
+ for (const p of pattern.paths) {
19028
+ const fullPath = join61(projectRoot, p);
19029
+ if (!existsSync62(fullPath)) continue;
19030
+ let st;
19031
+ try {
19032
+ st = statSync13(fullPath);
19033
+ } catch {
19034
+ continue;
19035
+ }
19036
+ if (st.isFile()) {
19037
+ if (SCANNED_EXTENSIONS.has(extname2(fullPath).toLowerCase()) || fullPath.endsWith(".json")) {
19038
+ artifacts.push({
19039
+ kind: pattern.kind,
19040
+ path: relative3(projectRoot, fullPath)
19041
+ });
19042
+ }
19043
+ } else if (st.isDirectory() && pattern.recursive) {
19044
+ const files = [];
19045
+ walkDir2(fullPath, files);
19046
+ for (const f of files) {
19047
+ artifacts.push({
19048
+ kind: pattern.kind,
19049
+ path: relative3(projectRoot, f)
19050
+ });
19051
+ }
19052
+ }
17280
19053
  }
17281
- };
19054
+ }
19055
+ return artifacts;
17282
19056
  }
17283
- function buildStoredTelemetryRollups(events, state, now = Date.now()) {
17284
- return {
17285
- schemaVersion: "avorelo.telemetry.rollups.v1",
17286
- generatedAt: new Date(now).toISOString(),
17287
- summaries: {
17288
- "24h": computeTelemetrySummary(events, state, "24h", now),
17289
- "7d": computeTelemetrySummary(events, state, "7d", now),
17290
- "30d": computeTelemetrySummary(events, state, "30d", now),
17291
- "90d": computeTelemetrySummary(events, state, "90d", now)
17292
- }
17293
- };
19057
+ function classifyArtifact(filePath) {
19058
+ const lower = filePath.toLowerCase().replace(/\\/g, "/");
19059
+ if (lower.includes(".claude/skills/")) return "claude-skill";
19060
+ if (lower.includes("claude.md")) return "claude-instructions";
19061
+ if (lower.includes("agents.md") || lower.includes("codex.md")) return "codex-instructions";
19062
+ if (lower.includes(".cursor/rules") || lower.includes(".cursorrules")) return "cursor-rules";
19063
+ if (lower.includes("mcp.json") || lower.includes("mcp.yaml")) return "mcp-config";
19064
+ if (lower.includes(".github/workflows/")) return "github-actions";
19065
+ if (lower === "package.json" || lower.endsWith("/package.json")) return "package-scripts";
19066
+ if (lower.includes(".husky/") || lower.includes("/hooks/")) return "hooks";
19067
+ if (lower.endsWith(".sh")) return "shell-script";
19068
+ return "unknown";
17294
19069
  }
17295
19070
 
17296
- // src/avorelo/telemetry/events.ts
17297
- function refreshRollups(dir) {
17298
- const state = readTelemetryState(dir);
17299
- const events = readTelemetryEvents(dir);
17300
- writeStoredTelemetryRollups(dir, buildStoredTelemetryRollups(events, state));
19071
+ // src/avorelo/kernel/agent-artifact-guard/rules.ts
19072
+ var RULES2 = [
19073
+ // --- Destructive commands ---
19074
+ {
19075
+ id: "destructive-rm-rf",
19076
+ title: "Recursive force delete",
19077
+ severity: "critical",
19078
+ category: "destructive-command",
19079
+ pattern: /rm\s+-(r|f|rf|fr)\s/,
19080
+ artifactKinds: "all",
19081
+ description: "Recursively and forcibly deletes files or directories. Can cause irreversible data loss.",
19082
+ recommendedAction: "Remove or scope the delete command. Use safe alternatives with explicit paths."
19083
+ },
19084
+ {
19085
+ id: "destructive-drop-table",
19086
+ title: "SQL DROP TABLE",
19087
+ severity: "critical",
19088
+ category: "destructive-command",
19089
+ pattern: /DROP\s+TABLE/i,
19090
+ artifactKinds: "all",
19091
+ description: "Drops a database table, causing irreversible data loss.",
19092
+ recommendedAction: "Remove DROP TABLE or add explicit safety guards."
19093
+ },
19094
+ {
19095
+ id: "destructive-format-disk",
19096
+ title: "Disk format command",
19097
+ severity: "critical",
19098
+ category: "destructive-command",
19099
+ pattern: /(?:mkfs|format\s+[A-Z]:)/i,
19100
+ artifactKinds: "all",
19101
+ description: "Formats a disk or partition, destroying all data.",
19102
+ recommendedAction: "Remove disk formatting commands from agent artifacts."
19103
+ },
19104
+ // --- Remote code execution ---
19105
+ {
19106
+ id: "rce-curl-pipe-bash",
19107
+ title: "Pipe curl to shell",
19108
+ severity: "critical",
19109
+ category: "remote-code-execution",
19110
+ pattern: /curl\s[^|]*\|\s*(?:bash|sh|zsh)/,
19111
+ artifactKinds: "all",
19112
+ description: "Downloads and immediately executes a remote script. Classic remote code execution vector.",
19113
+ recommendedAction: "Download the script first, review it, then execute separately."
19114
+ },
19115
+ {
19116
+ id: "rce-wget-pipe-sh",
19117
+ title: "Pipe wget to shell",
19118
+ severity: "critical",
19119
+ category: "remote-code-execution",
19120
+ pattern: /wget\s[^|]*\|\s*(?:bash|sh|zsh)/,
19121
+ artifactKinds: "all",
19122
+ description: "Downloads and immediately executes a remote script via wget.",
19123
+ recommendedAction: "Download the script first, review it, then execute separately."
19124
+ },
19125
+ {
19126
+ id: "rce-base64-decode-exec",
19127
+ title: "Base64 decode and execute",
19128
+ severity: "critical",
19129
+ category: "remote-code-execution",
19130
+ pattern: /base64\s+-d\s*\|\s*(?:bash|sh)/,
19131
+ artifactKinds: "all",
19132
+ description: "Decodes an obfuscated payload and pipes it to a shell.",
19133
+ recommendedAction: "Remove obfuscated execution. Use plain, reviewable scripts."
19134
+ },
19135
+ {
19136
+ id: "rce-reverse-shell",
19137
+ title: "Reverse or bind shell",
19138
+ severity: "critical",
19139
+ category: "remote-code-execution",
19140
+ pattern: /(?:nc\s+-e|\/dev\/tcp\/|ncat\s.*-e|socat\s.*exec)/,
19141
+ artifactKinds: "all",
19142
+ description: "Opens an interactive shell to a remote host. Post-exploitation technique.",
19143
+ recommendedAction: "Remove reverse/bind shell commands."
19144
+ },
19145
+ // --- Secret exposure ---
19146
+ {
19147
+ id: "secret-private-key",
19148
+ title: "Private key material",
19149
+ severity: "critical",
19150
+ category: "secret-exposure",
19151
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
19152
+ artifactKinds: "all",
19153
+ description: "Contains or references private key material.",
19154
+ recommendedAction: "Remove private key content. Use secret management instead."
19155
+ },
19156
+ {
19157
+ id: "secret-id-rsa",
19158
+ title: "SSH private key file reference",
19159
+ severity: "critical",
19160
+ category: "secret-exposure",
19161
+ pattern: /(?:id_rsa|id_ed25519|id_ecdsa)(?:\s|$|")/,
19162
+ artifactKinds: "all",
19163
+ description: "References an SSH private key file directly.",
19164
+ recommendedAction: "Use SSH agent or key references instead of direct file paths."
19165
+ },
19166
+ {
19167
+ id: "secret-ssh-dir",
19168
+ title: "SSH directory access",
19169
+ severity: "high",
19170
+ category: "secret-exposure",
19171
+ pattern: /~\/\.ssh\//,
19172
+ artifactKinds: "all",
19173
+ description: "Accesses the SSH directory containing private keys and host configurations.",
19174
+ recommendedAction: "Avoid direct SSH directory access in agent artifacts."
19175
+ },
19176
+ {
19177
+ id: "secret-env-file",
19178
+ title: ".env file reference",
19179
+ severity: "medium",
19180
+ category: "secret-exposure",
19181
+ pattern: /(?:\.env(?:\.\w+)?)\b/,
19182
+ artifactKinds: "all",
19183
+ description: "References a .env file which typically contains secrets and API keys.",
19184
+ recommendedAction: "Review whether the .env reference exposes sensitive values."
19185
+ },
19186
+ {
19187
+ id: "secret-process-env",
19188
+ title: "Environment variable access",
19189
+ severity: "low",
19190
+ category: "secret-exposure",
19191
+ pattern: /process\.env\b/,
19192
+ artifactKinds: "all",
19193
+ description: "Accesses environment variables which may contain secrets.",
19194
+ recommendedAction: "Verify that accessed environment variables do not contain sensitive values."
19195
+ },
19196
+ {
19197
+ id: "secret-api-key-pattern",
19198
+ title: "API key or token pattern",
19199
+ severity: "high",
19200
+ category: "secret-exposure",
19201
+ pattern: /(?:api[_-]?key|api[_-]?secret|auth[_-]?token)\s*[:=]/i,
19202
+ artifactKinds: "all",
19203
+ description: "Contains an API key or authentication token assignment.",
19204
+ recommendedAction: "Move credentials to environment variables or a secret manager."
19205
+ },
19206
+ // --- Privilege escalation ---
19207
+ {
19208
+ id: "priv-sudo",
19209
+ title: "Privilege escalation via sudo",
19210
+ severity: "high",
19211
+ category: "privilege-escalation",
19212
+ pattern: /\bsudo\b/,
19213
+ artifactKinds: "all",
19214
+ description: "Runs commands with elevated privileges.",
19215
+ recommendedAction: "Review whether elevated privileges are necessary. Scope to specific commands."
19216
+ },
19217
+ {
19218
+ id: "priv-chmod-exec",
19219
+ title: "Make file executable",
19220
+ severity: "medium",
19221
+ category: "privilege-escalation",
19222
+ pattern: /chmod\s+\+x/,
19223
+ artifactKinds: "all",
19224
+ description: "Marks a file as executable, often preceding execution of a downloaded binary.",
19225
+ recommendedAction: "Verify the file being made executable is trusted."
19226
+ },
19227
+ // --- Prompt injection / hidden instructions ---
19228
+ {
19229
+ id: "injection-prompt-override",
19230
+ title: "Prompt injection phrasing",
19231
+ severity: "high",
19232
+ category: "prompt-injection",
19233
+ pattern: /(?:ignore\s+(?:all\s+)?(?:previous|prior|above)\s+instructions|you\s+are\s+now\s+a|forget\s+(?:all\s+)?(?:your|previous)\s+instructions|disregard\s+(?:all\s+)?(?:previous|prior)\s+instructions|override\s+(?:all\s+)?(?:safety|security)\s+(?:rules|policies|instructions))/i,
19234
+ artifactKinds: "all",
19235
+ description: "Contains phrasing commonly used in prompt injection attacks to override agent instructions.",
19236
+ recommendedAction: "Remove instruction-override phrasing. Rewrite as direct task instructions."
19237
+ },
19238
+ {
19239
+ id: "injection-do-not-tell",
19240
+ title: "Concealment instruction",
19241
+ severity: "high",
19242
+ category: "hidden-instruction",
19243
+ pattern: /(?:do\s+not\s+tell\s+(?:the\s+)?user|hide\s+(?:this|these)\s+(?:changes?|from)|don'?t\s+(?:mention|reveal|show|disclose)\s+(?:this|these))/i,
19244
+ artifactKinds: "all",
19245
+ description: "Instructs the agent to conceal actions or information from the user.",
19246
+ recommendedAction: "Remove concealment instructions. Agent actions should be transparent."
19247
+ },
19248
+ {
19249
+ id: "injection-skip-tests",
19250
+ title: "Test bypass instruction",
19251
+ severity: "high",
19252
+ category: "hidden-instruction",
19253
+ pattern: /(?:skip\s+(?:all\s+)?tests?|don'?t\s+run\s+tests?|ignore\s+(?:test|lint|check)\s+(?:failures?|errors?|results?)|bypass\s+(?:ci|checks?|validation))/i,
19254
+ artifactKinds: "all",
19255
+ description: "Instructs the agent to skip testing, linting, or validation checks.",
19256
+ recommendedAction: "Remove test-bypass instructions. All changes should be validated."
19257
+ },
19258
+ {
19259
+ id: "injection-ignore-policy",
19260
+ title: "Policy bypass instruction",
19261
+ severity: "high",
19262
+ category: "hidden-instruction",
19263
+ pattern: /(?:ignore\s+(?:all\s+)?polic(?:y|ies)|always\s+approve|auto[_-]?approve\s+(?:all|everything)|skip\s+(?:review|approval))/i,
19264
+ artifactKinds: "all",
19265
+ description: "Instructs the agent to bypass approval policies or auto-approve actions.",
19266
+ recommendedAction: "Remove policy-bypass instructions. Approval workflows should be respected."
19267
+ },
19268
+ {
19269
+ id: "injection-push-to-main",
19270
+ title: "Direct push to main branch",
19271
+ severity: "high",
19272
+ category: "hidden-instruction",
19273
+ pattern: /(?:push\s+(?:directly\s+)?to\s+(?:main|master|production)|force[_-]?push|--force\s+(?:origin\s+)?(?:main|master))/i,
19274
+ artifactKinds: "all",
19275
+ description: "Instructs the agent to push directly to a protected branch or force-push.",
19276
+ recommendedAction: "Use pull requests and standard review workflows instead of direct pushes."
19277
+ },
19278
+ // --- Self-modifying ---
19279
+ {
19280
+ id: "selfmod-modify-own-skill",
19281
+ title: "Self-modifying skill instruction",
19282
+ severity: "high",
19283
+ category: "self-modifying",
19284
+ pattern: /(?:modify\s+(?:this|your\s+own)\s+(?:skill|instructions?|rules?|config)|write\s+(?:to|back\s+to)\s+(?:SKILL|CLAUDE|AGENTS)\.md|update\s+(?:your\s+own|this)\s+(?:skill|config)\s+file)/i,
19285
+ artifactKinds: "all",
19286
+ description: "Instructs the agent to modify its own skill, instructions, or configuration files.",
19287
+ recommendedAction: "Remove self-modification instructions. Agent artifacts should be immutable during execution."
19288
+ },
19289
+ // --- MCP tool poisoning ---
19290
+ {
19291
+ id: "mcp-hidden-instruction",
19292
+ title: "Hidden instruction in MCP tool description",
19293
+ severity: "high",
19294
+ category: "mcp-tool-poisoning",
19295
+ pattern: /(?:when\s+(?:the\s+)?user\s+(?:asks?|requests?)|secretly|silently\s+(?:send|post|upload|forward))/i,
19296
+ artifactKinds: ["mcp-config"],
19297
+ description: "MCP tool description contains instruction-like content that could manipulate agent behavior.",
19298
+ recommendedAction: "Review MCP tool descriptions for hidden instructions. Descriptions should only describe functionality."
19299
+ },
19300
+ // --- Package lifecycle risk ---
19301
+ {
19302
+ id: "pkg-lifecycle-exec",
19303
+ title: "Process execution in package lifecycle script",
19304
+ severity: "high",
19305
+ category: "package-lifecycle-risk",
19306
+ pattern: /(?:child_process|exec|spawn|execSync|spawnSync)\s*\(/,
19307
+ artifactKinds: ["package-scripts", "shell-script"],
19308
+ description: "Package lifecycle script or hook executes arbitrary processes.",
19309
+ recommendedAction: "Review lifecycle scripts for unexpected process execution."
19310
+ },
19311
+ {
19312
+ id: "pkg-lifecycle-network",
19313
+ title: "Network access in package lifecycle script",
19314
+ severity: "high",
19315
+ category: "package-lifecycle-risk",
19316
+ pattern: /(?:fetch|axios|https?\.(?:get|post|request)|XMLHttpRequest|node-fetch)\s*\(/,
19317
+ artifactKinds: ["package-scripts", "shell-script"],
19318
+ description: "Package lifecycle script makes network requests, potentially exfiltrating data.",
19319
+ recommendedAction: "Review lifecycle scripts for unexpected network access."
19320
+ },
19321
+ // --- Network exfiltration ---
19322
+ {
19323
+ id: "network-external-call",
19324
+ title: "External network request",
19325
+ severity: "medium",
19326
+ category: "network-exfiltration",
19327
+ pattern: /\b(?:curl|wget|fetch|axios|requests\.(?:get|post))\b/,
19328
+ artifactKinds: ["claude-skill", "claude-instructions", "cursor-rules", "codex-instructions", "hooks"],
19329
+ description: "Makes an external network request which could exfiltrate data or retrieve malicious payloads.",
19330
+ recommendedAction: "Verify the network call is necessary and targets a trusted endpoint."
19331
+ },
19332
+ // --- Eval / dynamic execution ---
19333
+ {
19334
+ id: "exec-eval",
19335
+ title: "Dynamic code execution",
19336
+ severity: "high",
19337
+ category: "remote-code-execution",
19338
+ pattern: /\b(?:eval|Invoke-Expression|Function\s*\()\s*\(/,
19339
+ artifactKinds: "all",
19340
+ description: "Executes a string as code, enabling injection and obfuscation attacks.",
19341
+ recommendedAction: "Remove eval/dynamic execution. Use explicit, reviewable code paths."
19342
+ },
19343
+ {
19344
+ id: "exec-child-process",
19345
+ title: "Node child_process import",
19346
+ severity: "high",
19347
+ category: "remote-code-execution",
19348
+ pattern: /require\s*\(\s*['"]child_process['"]\s*\)|from\s+['"]child_process['"]/,
19349
+ artifactKinds: "all",
19350
+ description: "Imports Node.js child_process module for arbitrary command execution.",
19351
+ recommendedAction: "Review whether child_process usage is necessary and scoped."
19352
+ },
19353
+ // --- Filesystem access ---
19354
+ {
19355
+ id: "fs-broad-access",
19356
+ title: "Broad filesystem access",
19357
+ severity: "medium",
19358
+ category: "filesystem-access",
19359
+ pattern: /(?:fs\.(?:readdir|rmdir|unlink)Sync|glob\s*\(\s*['"]\/|readdir\s*\(\s*['"]\/)/,
19360
+ artifactKinds: "all",
19361
+ description: "Performs broad filesystem operations that could access or delete files outside the project.",
19362
+ recommendedAction: "Scope filesystem operations to the project directory."
19363
+ },
19364
+ // --- Deploy / publish ---
19365
+ {
19366
+ id: "deploy-npm-publish",
19367
+ title: "npm publish command",
19368
+ severity: "high",
19369
+ category: "deploy-publish",
19370
+ pattern: /npm\s+publish/,
19371
+ artifactKinds: "all",
19372
+ description: "Publishes a package to npm, which is an irreversible public action.",
19373
+ recommendedAction: "Remove automatic publish commands. Publishing should be a deliberate human action."
19374
+ },
19375
+ {
19376
+ id: "deploy-dangerous-action",
19377
+ title: "Dangerous deploy/publish in CI",
19378
+ severity: "high",
19379
+ category: "deploy-publish",
19380
+ pattern: /(?:deploy\s+(?:--)?(?:prod|production)|push\s+(?:to\s+)?(?:prod|production|live))/i,
19381
+ artifactKinds: ["github-actions", "shell-script", "hooks"],
19382
+ description: "Deploys directly to production without explicit approval gates.",
19383
+ recommendedAction: "Add approval gates before production deployments."
19384
+ }
19385
+ ];
19386
+ function getRule(id) {
19387
+ return RULES2.find((r2) => r2.id === id);
17301
19388
  }
17302
- function shouldQueue(event) {
17303
- return event.eventName !== "telemetry_batch_sent" && event.eventName !== "telemetry_batch_failed";
19389
+
19390
+ // src/avorelo/kernel/agent-artifact-guard/scanner.ts
19391
+ var MAX_PREVIEW_LENGTH = 80;
19392
+ function safeMatchPreview(match) {
19393
+ let preview = match.trim();
19394
+ if (preview.length > MAX_PREVIEW_LENGTH) {
19395
+ preview = preview.slice(0, MAX_PREVIEW_LENGTH) + "...";
19396
+ }
19397
+ preview = preview.replace(
19398
+ /(?:key|secret|token|password|passwd|pwd)\s*[:=]\s*['"]?[a-zA-Z0-9_\-./+=]{8,}/gi,
19399
+ (m) => {
19400
+ const eqIdx = m.search(/[:=]/);
19401
+ if (eqIdx >= 0) return m.slice(0, eqIdx + 1) + " [REDACTED]";
19402
+ return "[REDACTED]";
19403
+ }
19404
+ );
19405
+ return preview;
17304
19406
  }
17305
- function recordTelemetryEvent(dir, rawEvent) {
17306
- const state = readTelemetryState(dir);
17307
- const config2 = getTelemetryConfig(dir, state);
17308
- if (!config2.enabled || config2.mode === "off") {
17309
- return { ok: false, queued: false, excludedFields: [], unsafeFields: [], reason: "telemetry_disabled" };
19407
+ function rulesForArtifact(kind) {
19408
+ return RULES2.filter(
19409
+ (r2) => r2.artifactKinds === "all" || r2.artifactKinds.includes(kind)
19410
+ );
19411
+ }
19412
+ function scanContent2(content, filePath, artifactKind) {
19413
+ const kind = artifactKind ?? classifyArtifact(filePath);
19414
+ const applicable = rulesForArtifact(kind);
19415
+ const findings = [];
19416
+ const lines = content.split("\n");
19417
+ for (const rule of applicable) {
19418
+ if (rule.multiline) continue;
19419
+ for (let i = 0; i < lines.length; i++) {
19420
+ const match = rule.pattern.exec(lines[i]);
19421
+ if (match) {
19422
+ findings.push({
19423
+ ruleId: rule.id,
19424
+ title: rule.title,
19425
+ severity: rule.severity,
19426
+ category: rule.category,
19427
+ file: filePath,
19428
+ line: i + 1,
19429
+ matchPreview: safeMatchPreview(match[0]),
19430
+ description: rule.description,
19431
+ recommendedAction: rule.recommendedAction,
19432
+ artifactKind: kind
19433
+ });
19434
+ }
19435
+ }
17310
19436
  }
17311
- const mode = config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud";
17312
- const sanitized = sanitizeTelemetryEvent(rawEvent, {
17313
- appVersion: config2.appVersion,
17314
- mode,
17315
- anonymousInstallationId: config2.anonymousInstallationId,
17316
- anonymousWorkspaceId: config2.anonymousWorkspaceId,
17317
- anonymousTeamId: config2.anonymousTeamId,
17318
- osFamily: config2.osFamily,
17319
- salt: state.installationSalt
17320
- });
17321
- appendTelemetryEvent(dir, sanitized.event);
17322
- let queued = false;
17323
- if (shouldQueue(sanitized.event) && config2.mode !== "local_only" && config2.endpointUrl) {
17324
- enqueueTelemetryEvent(dir, sanitized.event);
17325
- state.lastQueuedAt = (/* @__PURE__ */ new Date()).toISOString();
17326
- writeTelemetryState(dir, state);
17327
- queued = true;
19437
+ for (const rule of applicable) {
19438
+ if (!rule.multiline) continue;
19439
+ const match = rule.pattern.exec(content);
19440
+ if (match) {
19441
+ const lineNumber = content.slice(0, match.index).split("\n").length;
19442
+ findings.push({
19443
+ ruleId: rule.id,
19444
+ title: rule.title,
19445
+ severity: rule.severity,
19446
+ category: rule.category,
19447
+ file: filePath,
19448
+ line: lineNumber,
19449
+ matchPreview: safeMatchPreview(match[0]),
19450
+ description: rule.description,
19451
+ recommendedAction: rule.recommendedAction,
19452
+ artifactKind: kind
19453
+ });
19454
+ }
17328
19455
  }
17329
- refreshRollups(dir);
17330
- return {
17331
- ok: true,
17332
- queued,
17333
- event: sanitized.event,
17334
- excludedFields: sanitized.excludedFields,
17335
- unsafeFields: sanitized.unsafeFields
17336
- };
19456
+ findings.sort((a, b) => a.line - b.line);
19457
+ return findings;
17337
19458
  }
17338
- function disableTelemetry(dir, mode = "off") {
17339
- const state = readTelemetryState(dir);
17340
- state.modeOverride = mode;
17341
- writeTelemetryState(dir, state);
19459
+
19460
+ // src/avorelo/kernel/agent-artifact-guard/scorer.ts
19461
+ var SEVERITY_WEIGHTS = {
19462
+ low: 1,
19463
+ medium: 3,
19464
+ high: 6,
19465
+ critical: 10
19466
+ };
19467
+ function countBySeverity(findings) {
19468
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
19469
+ for (const f of findings) {
19470
+ counts[f.severity]++;
19471
+ }
19472
+ return counts;
17342
19473
  }
17343
- function setTelemetryMode(dir, mode) {
17344
- const state = readTelemetryState(dir);
17345
- state.modeOverride = mode;
17346
- writeTelemetryState(dir, state);
19474
+ function computeRiskScore(counts) {
19475
+ if (counts.critical > 0) return 10;
19476
+ const weighted = counts.high * SEVERITY_WEIGHTS.high + counts.medium * SEVERITY_WEIGHTS.medium + counts.low * SEVERITY_WEIGHTS.low;
19477
+ if (weighted === 0) return 0;
19478
+ const score = Math.min(9.9, Math.log2(weighted + 1) * 1.5);
19479
+ return Math.round(score * 10) / 10;
17347
19480
  }
17348
19481
 
17349
- // src/avorelo/telemetry/telemetry-status.ts
17350
- function buildTelemetryStatus(dir) {
17351
- const state = readTelemetryState(dir);
17352
- const config2 = getTelemetryConfig(dir, state);
17353
- return {
17354
- defaultOn: true,
17355
- enabled: config2.enabled,
17356
- mode: config2.mode,
17357
- lastUpload: state.lastUploadAt,
17358
- queuedEventCount: countQueuedTelemetryEvents(dir),
17359
- collected: [
17360
- "Anonymized installation/workspace/team identifiers",
17361
- "Repo fingerprints",
17362
- "Command names and safe status buckets",
17363
- "Receipt, session, and integration metadata",
17364
- "Work-control and commercial intent event counts"
17365
- ],
17366
- neverCollected: [
17367
- "Source code",
17368
- "Raw prompts",
17369
- "Raw diffs or patches",
17370
- "Secrets or tokens",
17371
- "Env var values",
17372
- "Terminal output",
17373
- "Repo names",
17374
- "Org names",
17375
- "Remote URLs",
17376
- "Absolute file paths",
17377
- "Emails and usernames"
17378
- ],
17379
- disableInstructions: [
17380
- "AVORELO_TELEMETRY=off",
17381
- "avorelo telemetry disable",
17382
- "avorelo config set telemetry.mode local-only"
17383
- ]
17384
- };
17385
- }
17386
- function renderTelemetryStatus(status) {
17387
- return [
17388
- "Avorelo Telemetry Status",
17389
- " Telemetry is on by default: true",
17390
- ` Enabled: ${status.enabled}`,
17391
- ` Mode: ${status.mode}`,
17392
- ` Last upload: ${status.lastUpload ?? "never"}`,
17393
- ` Queued events: ${status.queuedEventCount}`,
17394
- "",
17395
- " Collected:",
17396
- ...status.collected.map((line) => ` - ${line}`),
17397
- "",
17398
- " Never collected:",
17399
- ...status.neverCollected.map((line) => ` - ${line}`),
17400
- "",
17401
- " Disable/local-only:",
17402
- ...status.disableInstructions.map((line) => ` ${line}`),
17403
- ""
17404
- ].join("\n");
19482
+ // src/avorelo/kernel/agent-artifact-guard/policy.ts
19483
+ function evaluatePolicy2(counts, riskScore) {
19484
+ let findingOutcome = "pass";
19485
+ if (counts.critical > 0) findingOutcome = "blocked";
19486
+ else if (counts.high > 0) findingOutcome = "requires_approval";
19487
+ else if (counts.medium > 0) findingOutcome = "warn";
19488
+ let scoreOutcome = "pass";
19489
+ if (riskScore >= 9) scoreOutcome = "blocked";
19490
+ else if (riskScore >= 6) scoreOutcome = "requires_approval";
19491
+ else if (riskScore >= 3) scoreOutcome = "warn";
19492
+ const ORDER = ["pass", "warn", "requires_approval", "blocked"];
19493
+ const findingRank = ORDER.indexOf(findingOutcome);
19494
+ const scoreRank = ORDER.indexOf(scoreOutcome);
19495
+ return ORDER[Math.max(findingRank, scoreRank)];
19496
+ }
19497
+ function computeNextBestAction(outcome, counts) {
19498
+ switch (outcome) {
19499
+ case "blocked":
19500
+ return `${counts.critical} critical finding(s) must be resolved before proceeding.`;
19501
+ case "requires_approval":
19502
+ return `${counts.high} high-severity finding(s) require review and approval before proceeding.`;
19503
+ case "warn":
19504
+ return `${counts.medium} medium-severity finding(s) detected. Review recommended.`;
19505
+ case "pass":
19506
+ return "No blocking findings. Artifacts are ready for use.";
19507
+ }
17405
19508
  }
17406
19509
 
17407
- // src/avorelo/telemetry/telemetry-preview.ts
17408
- function buildTelemetryPreview(dir) {
17409
- const state = readTelemetryState(dir);
17410
- state.lastPreviewAt = (/* @__PURE__ */ new Date()).toISOString();
17411
- writeTelemetryState(dir, state);
17412
- const config2 = getTelemetryConfig(dir, state);
17413
- const samplePayload = sanitizeTelemetryEvent({
17414
- eventName: "controlled_session_completed",
17415
- commandName: "run",
17416
- durationMs: 42e3,
17417
- repoRoot: dir,
17418
- repoName: "demo-private-repo",
17419
- remoteUrl: "https://github.com/acme/private-demo",
17420
- sessionId: "session-local-123",
17421
- receiptId: "receipt-local-456",
17422
- rawPrompt: "write code",
17423
- sourceCode: "const secret = process.env.API_KEY;",
17424
- errorMessage: "network timeout while syncing"
17425
- }, {
17426
- appVersion: config2.appVersion,
17427
- mode: config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud",
17428
- anonymousInstallationId: config2.anonymousInstallationId,
17429
- anonymousWorkspaceId: config2.anonymousWorkspaceId,
17430
- anonymousTeamId: config2.anonymousTeamId,
17431
- osFamily: config2.osFamily,
17432
- salt: state.installationSalt
17433
- });
19510
+ // src/avorelo/kernel/agent-artifact-guard/receipt.ts
19511
+ import { createHash as createHash19 } from "node:crypto";
19512
+ function createReceipt(result3) {
17434
19513
  return {
17435
- samplePayload,
17436
- excludedSensitiveFields: samplePayload.excludedFields
19514
+ receiptType: "agent-artifact-guard-v1",
19515
+ scannedAt: result3.scannedAt,
19516
+ projectRootHash: createHash19("sha256").update(result3.projectRoot).digest("hex"),
19517
+ filesScanned: result3.filesScanned,
19518
+ artifacts: result3.artifacts.map((a) => ({ kind: a.kind, path: a.path })),
19519
+ findingSummary: {
19520
+ total: result3.findings.length,
19521
+ ...result3.counts
19522
+ },
19523
+ findings: result3.findings.map((f) => ({
19524
+ ruleId: f.ruleId,
19525
+ severity: f.severity,
19526
+ file: f.file,
19527
+ line: f.line,
19528
+ title: f.title,
19529
+ matchPreview: f.matchPreview,
19530
+ recommendedAction: f.recommendedAction
19531
+ })),
19532
+ riskScore: result3.riskScore,
19533
+ policyOutcome: result3.policyOutcome,
19534
+ nextBestAction: result3.nextBestAction,
19535
+ containsRawPrompt: false,
19536
+ containsRawSource: false,
19537
+ containsRawSecret: false,
19538
+ contentStored: false
17437
19539
  };
17438
19540
  }
17439
- function renderTelemetryPreview(preview) {
17440
- return [
17441
- "Avorelo Telemetry Preview",
17442
- " Sample safe payload:",
17443
- JSON.stringify(preview.samplePayload.event, null, 2),
17444
- "",
17445
- " Excluded sensitive fields:",
17446
- ...preview.excludedSensitiveFields.map((field) => ` - ${field}`),
17447
- "",
17448
- " No source, prompts, diffs, secrets, env values, terminal output, repo names, or org names are included.",
17449
- ""
17450
- ].join("\n");
19541
+
19542
+ // src/avorelo/kernel/agent-artifact-guard/reporter.ts
19543
+ var SEVERITY_BADGE = {
19544
+ critical: "[CRITICAL]",
19545
+ high: "[HIGH]",
19546
+ medium: "[MEDIUM]",
19547
+ low: "[LOW]"
19548
+ };
19549
+ var OUTCOME_LABEL = {
19550
+ pass: "PASS",
19551
+ warn: "WARN",
19552
+ requires_approval: "REQUIRES APPROVAL",
19553
+ blocked: "BLOCKED"
19554
+ };
19555
+ function riskBar(score) {
19556
+ const filled = Math.round(score / 10 * 20);
19557
+ return "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
19558
+ }
19559
+ function groupByFile(findings) {
19560
+ const map = /* @__PURE__ */ new Map();
19561
+ for (const f of findings) {
19562
+ const group = map.get(f.file) ?? [];
19563
+ group.push(f);
19564
+ map.set(f.file, group);
19565
+ }
19566
+ return map;
19567
+ }
19568
+ function renderReport(result3) {
19569
+ const lines = [];
19570
+ lines.push(`Artifact Guard Scan`);
19571
+ lines.push(`Path: ${result3.projectRoot}`);
19572
+ lines.push(`Files scanned: ${result3.filesScanned}`);
19573
+ lines.push(`Artifacts found: ${result3.artifacts.length}`);
19574
+ lines.push(`Findings: ${result3.findings.length}`);
19575
+ lines.push("");
19576
+ if (result3.findings.length > 0) {
19577
+ const grouped = groupByFile(result3.findings);
19578
+ for (const [file, findings] of grouped) {
19579
+ lines.push(` ${file}`);
19580
+ for (const f of findings) {
19581
+ lines.push(` L${f.line} ${SEVERITY_BADGE[f.severity]} ${f.title} (${f.ruleId})`);
19582
+ lines.push(` ${f.matchPreview}`);
19583
+ lines.push(` ${f.description}`);
19584
+ }
19585
+ lines.push("");
19586
+ }
19587
+ }
19588
+ lines.push(`Risk: ${riskBar(result3.riskScore)} ${result3.riskScore}/10`);
19589
+ lines.push(`Outcome: ${OUTCOME_LABEL[result3.policyOutcome]}`);
19590
+ lines.push("");
19591
+ lines.push(`Next: ${result3.nextBestAction}`);
19592
+ return lines.join("\n");
19593
+ }
19594
+ function renderSummary(result3) {
19595
+ const parts = [];
19596
+ parts.push(`Artifact Guard: ${OUTCOME_LABEL[result3.policyOutcome]}`);
19597
+ if (result3.findings.length > 0) {
19598
+ const counts = [];
19599
+ if (result3.counts.critical > 0) counts.push(`${result3.counts.critical} critical`);
19600
+ if (result3.counts.high > 0) counts.push(`${result3.counts.high} high`);
19601
+ if (result3.counts.medium > 0) counts.push(`${result3.counts.medium} medium`);
19602
+ if (result3.counts.low > 0) counts.push(`${result3.counts.low} low`);
19603
+ parts.push(`(${counts.join(", ")})`);
19604
+ } else {
19605
+ parts.push("(clean)");
19606
+ }
19607
+ return parts.join(" ");
19608
+ }
19609
+ function toJson(result3) {
19610
+ return JSON.stringify(result3, null, 2);
17451
19611
  }
17452
19612
 
17453
- // src/avorelo/telemetry/integration-detection.ts
17454
- import { existsSync as existsSync61, readFileSync as readFileSync45 } from "node:fs";
17455
- import { join as join60 } from "node:path";
17456
- function detectGitProviderFromConfig(dir) {
17457
- const configPath = join60(dir, ".git", "config");
17458
- if (!existsSync61(configPath)) return "unknown";
19613
+ // src/avorelo/kernel/agent-artifact-guard/allowlist.ts
19614
+ import { readFileSync as readFileSync46 } from "node:fs";
19615
+ import { join as join62 } from "node:path";
19616
+ function loadAllowlist(projectRoot) {
19617
+ const filePath = join62(projectRoot, ".avorelo", "artifact-guard", "allowlist.json");
17459
19618
  try {
17460
- const content = readFileSync45(configPath, "utf8").toLowerCase();
17461
- if (content.includes("github.com")) return "github";
17462
- if (content.includes("gitlab.com")) return "gitlab";
17463
- if (content.includes("bitbucket.org")) return "bitbucket";
17464
- return "local";
19619
+ const raw = readFileSync46(filePath, "utf-8");
19620
+ const parsed = JSON.parse(raw);
19621
+ if (!Array.isArray(parsed.entries)) return [];
19622
+ return parsed.entries.filter((e) => typeof e.reason === "string" && e.reason.length > 0);
17465
19623
  } catch {
17466
- return "unknown";
19624
+ return [];
17467
19625
  }
17468
19626
  }
17469
- function detectCiProvider(dir) {
17470
- if (existsSync61(join60(dir, ".github", "workflows"))) return "github_actions";
17471
- if (existsSync61(join60(dir, ".gitlab-ci.yml"))) return "gitlab_ci";
17472
- if (existsSync61(join60(dir, ".circleci", "config.yml"))) return "circle";
17473
- if (existsSync61(join60(dir, "Jenkinsfile"))) return "jenkins";
17474
- return "unknown";
19627
+ function isAllowlisted(finding, allowlist) {
19628
+ if (finding.severity === "critical") return false;
19629
+ for (const entry of allowlist) {
19630
+ if (!entry.reason) continue;
19631
+ const ruleMatch = !entry.ruleId || entry.ruleId === finding.ruleId;
19632
+ const fileMatch = !entry.file || finding.file === entry.file || finding.file.startsWith(entry.file);
19633
+ if (ruleMatch && fileMatch) return true;
19634
+ }
19635
+ return false;
17475
19636
  }
17476
- function detectRepoVisibility(dir) {
17477
- const configPath = join60(dir, ".git", "config");
17478
- if (!existsSync61(configPath)) return "unknown";
19637
+
19638
+ // src/avorelo/kernel/agent-artifact-guard/receipt-store.ts
19639
+ import { mkdirSync as mkdirSync39, writeFileSync as writeFileSync37, readdirSync as readdirSync19, readFileSync as readFileSync47, unlinkSync as unlinkSync4 } from "node:fs";
19640
+ import { join as join63 } from "node:path";
19641
+ var RECEIPT_DIR = "artifact-guard";
19642
+ var MAX_STORED_RECEIPTS = 50;
19643
+ function receiptDir(projectRoot) {
19644
+ return join63(projectRoot, ".avorelo", RECEIPT_DIR);
19645
+ }
19646
+ function receiptFilename(receipt) {
19647
+ const ts = receipt.scannedAt.replace(/[:.]/g, "-");
19648
+ return `receipt-${ts}.json`;
19649
+ }
19650
+ function storeReceipt(projectRoot, receipt) {
19651
+ const dir = receiptDir(projectRoot);
19652
+ mkdirSync39(dir, { recursive: true });
19653
+ const filename = receiptFilename(receipt);
19654
+ const filePath = join63(dir, filename);
19655
+ writeFileSync37(filePath, JSON.stringify(receipt, null, 2), "utf-8");
19656
+ pruneOldReceipts(dir);
19657
+ return filePath;
19658
+ }
19659
+ function loadLatestReceipt(projectRoot) {
19660
+ const dir = receiptDir(projectRoot);
19661
+ let files;
17479
19662
  try {
17480
- const content = readFileSync45(configPath, "utf8").toLowerCase();
17481
- if (content.includes("github.com")) {
17482
- return content.includes("/public") ? "public" : "private";
19663
+ files = readdirSync19(dir).filter((f) => f.startsWith("receipt-") && f.endsWith(".json"));
19664
+ } catch {
19665
+ return null;
19666
+ }
19667
+ if (files.length === 0) return null;
19668
+ files.sort();
19669
+ const latest = files[files.length - 1];
19670
+ try {
19671
+ const raw = readFileSync47(join63(dir, latest), "utf-8");
19672
+ return JSON.parse(raw);
19673
+ } catch {
19674
+ return null;
19675
+ }
19676
+ }
19677
+ function pruneOldReceipts(dir) {
19678
+ try {
19679
+ const files = readdirSync19(dir).filter((f) => f.startsWith("receipt-") && f.endsWith(".json")).sort();
19680
+ while (files.length > MAX_STORED_RECEIPTS) {
19681
+ const oldest = files.shift();
19682
+ try {
19683
+ unlinkSync4(join63(dir, oldest));
19684
+ } catch {
19685
+ break;
19686
+ }
17483
19687
  }
17484
- return "unknown";
17485
19688
  } catch {
17486
- return "unknown";
17487
19689
  }
17488
19690
  }
17489
- function detectIntegrations(dir) {
19691
+
19692
+ // src/avorelo/kernel/agent-artifact-guard/index.ts
19693
+ function scan(projectRoot, options) {
19694
+ const artifacts = discoverArtifacts(projectRoot);
19695
+ const allFindings = [];
19696
+ for (const artifact of artifacts) {
19697
+ const fullPath = join64(projectRoot, artifact.path);
19698
+ let content;
19699
+ try {
19700
+ content = readFileSync48(fullPath, "utf-8");
19701
+ } catch {
19702
+ continue;
19703
+ }
19704
+ const findings = scanContent2(content, artifact.path, artifact.kind);
19705
+ allFindings.push(...findings);
19706
+ }
19707
+ let effectiveFindings = allFindings;
19708
+ let allowlistedCount = 0;
19709
+ if (options?.applyAllowlist !== false) {
19710
+ const allowlist = loadAllowlist(projectRoot);
19711
+ if (allowlist.length > 0) {
19712
+ effectiveFindings = [];
19713
+ for (const f of allFindings) {
19714
+ if (isAllowlisted(f, allowlist)) {
19715
+ allowlistedCount++;
19716
+ } else {
19717
+ effectiveFindings.push(f);
19718
+ }
19719
+ }
19720
+ }
19721
+ }
19722
+ const counts = countBySeverity(effectiveFindings);
19723
+ const riskScore = computeRiskScore(counts);
19724
+ const policyOutcome = evaluatePolicy2(counts, riskScore);
19725
+ let nextBestAction = computeNextBestAction(policyOutcome, counts);
19726
+ if (allowlistedCount > 0) {
19727
+ nextBestAction += ` (${allowlistedCount} finding(s) allowlisted)`;
19728
+ }
17490
19729
  return {
17491
- gitProvider: detectGitProviderFromConfig(dir),
17492
- ciProvider: detectCiProvider(dir),
17493
- repoVisibility: detectRepoVisibility(dir)
19730
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
19731
+ projectRoot,
19732
+ filesScanned: artifacts.length,
19733
+ artifacts,
19734
+ findings: effectiveFindings,
19735
+ counts,
19736
+ riskScore,
19737
+ policyOutcome,
19738
+ nextBestAction
17494
19739
  };
17495
19740
  }
17496
19741
 
17497
- // src/avorelo/telemetry/sender.ts
17498
- import { randomUUID as randomUUID11 } from "node:crypto";
17499
- function buildBatch(dir, limit) {
17500
- const state = readTelemetryState(dir);
17501
- const config2 = getTelemetryConfig(dir, state);
17502
- const records = getQueuedTelemetryEvents(dir).slice(0, limit);
17503
- if (records.length === 0) return { batch: null, queueIds: [] };
19742
+ // src/avorelo/kernel/agent-artifact-guard/guard-handler.ts
19743
+ function checkActivation(dir) {
19744
+ const configPath = join65(dir, ".avorelo", "model-routing", "config.json");
19745
+ try {
19746
+ if (existsSync63(configPath)) {
19747
+ const raw = JSON.parse(readFileSync49(configPath, "utf-8"));
19748
+ return {
19749
+ activated: true,
19750
+ routingMode: raw.mode ?? "seamless",
19751
+ localFirst: raw.localFirst ?? true,
19752
+ registryLoaded: raw.registryLoaded ?? false,
19753
+ cloudRoutingEnabled: raw.cloudRoutingEnabled ?? false,
19754
+ defaultPolicy: raw.defaultPolicy ?? "safety_proof_privacy_before_cost"
19755
+ };
19756
+ }
19757
+ } catch {
19758
+ }
17504
19759
  return {
17505
- batch: {
17506
- schemaVersion: "avorelo.telemetry.batch.v1",
17507
- batchId: `batch_${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
17508
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17509
- appVersion: config2.appVersion,
17510
- anonymousInstallationId: config2.anonymousInstallationId,
17511
- events: records.map((record) => record.event),
17512
- privacy: {
17513
- rawCodeIncluded: false,
17514
- rawPromptsIncluded: false,
17515
- rawDiffsIncluded: false,
17516
- secretsIncluded: false,
17517
- envIncluded: false,
17518
- terminalOutputIncluded: false
19760
+ activated: false,
19761
+ routingMode: "seamless",
19762
+ localFirst: true,
19763
+ registryLoaded: false,
19764
+ cloudRoutingEnabled: false,
19765
+ defaultPolicy: "safety_proof_privacy_before_cost"
19766
+ };
19767
+ }
19768
+ function getRoutingStatus(state) {
19769
+ return `Routing: ${state.activated ? "active" : "inactive"} (${state.routingMode})`;
19770
+ }
19771
+ function activationWarning(outcome, nextAction) {
19772
+ switch (outcome) {
19773
+ case "blocked":
19774
+ return `Activation warning: ${nextAction} Activation cannot claim safe readiness.`;
19775
+ case "requires_approval":
19776
+ return `Review needed: ${nextAction}`;
19777
+ case "warn":
19778
+ return `Note: ${nextAction}`;
19779
+ case "pass":
19780
+ return null;
19781
+ }
19782
+ }
19783
+ function handleActivate(dir, options) {
19784
+ const state = checkActivation(dir);
19785
+ const guardResult = scan(dir);
19786
+ const receipt = createReceipt(guardResult);
19787
+ const receiptPath = storeReceipt(dir, receipt);
19788
+ const guardLine = renderSummary(guardResult);
19789
+ const warning = activationWarning(guardResult.policyOutcome, guardResult.nextBestAction);
19790
+ const lines = [getRoutingStatus(state), guardLine];
19791
+ if (warning) lines.push(warning);
19792
+ lines.push(`Receipt: ${receiptPath}`);
19793
+ if (options.json) {
19794
+ return {
19795
+ exitCode: guardResult.policyOutcome === "blocked" ? 1 : 0,
19796
+ stdout: lines.join("\n"),
19797
+ json: {
19798
+ activated: state.activated,
19799
+ artifactGuard: {
19800
+ policyOutcome: guardResult.policyOutcome,
19801
+ riskScore: guardResult.riskScore,
19802
+ findingsCount: guardResult.findings.length,
19803
+ counts: guardResult.counts,
19804
+ nextBestAction: guardResult.nextBestAction
19805
+ },
19806
+ receiptPath
17519
19807
  }
17520
- },
17521
- queueIds: records.map((record) => record.queueId)
19808
+ };
19809
+ }
19810
+ return {
19811
+ exitCode: guardResult.policyOutcome === "blocked" ? 1 : 0,
19812
+ stdout: lines.join("\n")
17522
19813
  };
17523
19814
  }
17524
- function appendTransportEvent(dir, eventName, errorClass) {
17525
- const state = readTelemetryState(dir);
17526
- const config2 = getTelemetryConfig(dir, state);
17527
- appendTelemetryEvent(dir, {
17528
- schemaVersion: "avorelo.telemetry.v1",
17529
- eventId: `evt_${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
17530
- occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
17531
- eventName,
17532
- mode: config2.mode === "claimed_team" ? "claimed_team" : config2.mode === "local_only" ? "local_only" : "default_cloud",
17533
- anonymousInstallationId: config2.anonymousInstallationId,
17534
- anonymousWorkspaceId: config2.anonymousWorkspaceId ?? void 0,
17535
- anonymousTeamId: config2.anonymousTeamId ?? void 0,
17536
- appVersion: config2.appVersion,
17537
- osFamily: config2.osFamily,
17538
- errorClass
17539
- });
19815
+ function handleStatus(dir, options) {
19816
+ const state = checkActivation(dir);
19817
+ const status = getRoutingStatus(state);
19818
+ const guardResult = scan(dir);
19819
+ if (options.json) {
19820
+ return {
19821
+ exitCode: 0,
19822
+ stdout: `${status}
19823
+ ${renderSummary(guardResult)}`,
19824
+ json: {
19825
+ routing: {
19826
+ active: state.activated,
19827
+ mode: state.routingMode,
19828
+ localFirst: state.localFirst
19829
+ },
19830
+ artifactGuard: {
19831
+ policyOutcome: guardResult.policyOutcome,
19832
+ riskScore: guardResult.riskScore,
19833
+ findingsCount: guardResult.findings.length
19834
+ }
19835
+ }
19836
+ };
19837
+ }
19838
+ return { exitCode: 0, stdout: `${status}
19839
+ ${renderSummary(guardResult)}` };
17540
19840
  }
17541
- async function sendDueTelemetry(dir, opts) {
17542
- const state = readTelemetryState(dir);
17543
- const config2 = getTelemetryConfig(dir, state);
17544
- const queuedCount = countQueuedTelemetryEvents(dir);
17545
- if (!config2.enabled || config2.mode === "off") {
17546
- return { attempted: false, sent: false, reason: "telemetry_disabled", queuedCount };
19841
+ function handleDoctor(dir, options) {
19842
+ const state = checkActivation(dir);
19843
+ const guardResult = scan(dir);
19844
+ const lines = [
19845
+ getRoutingStatus(state),
19846
+ `Registry: ${state.registryLoaded ? "loaded" : "not loaded"}`,
19847
+ `Cloud: ${state.cloudRoutingEnabled ? "enabled" : "disabled"}`,
19848
+ `Policy: ${state.defaultPolicy}`,
19849
+ renderSummary(guardResult)
19850
+ ];
19851
+ if (options.json) {
19852
+ return {
19853
+ exitCode: 0,
19854
+ stdout: lines.join("\n"),
19855
+ json: {
19856
+ ...state,
19857
+ artifactGuard: {
19858
+ policyOutcome: guardResult.policyOutcome,
19859
+ riskScore: guardResult.riskScore,
19860
+ findingsCount: guardResult.findings.length,
19861
+ counts: guardResult.counts
19862
+ }
19863
+ }
19864
+ };
17547
19865
  }
17548
- if (config2.mode === "local_only" || !config2.endpointUrl) {
17549
- return { attempted: false, sent: false, reason: "local_only_or_no_endpoint", queuedCount };
19866
+ return { exitCode: 0, stdout: lines.join("\n") };
19867
+ }
19868
+ function handleReadiness(dir, options) {
19869
+ const state = checkActivation(dir);
19870
+ const guardResult = scan(dir);
19871
+ const receipt = createReceipt(guardResult);
19872
+ storeReceipt(dir, receipt);
19873
+ const latestReceipt = loadLatestReceipt(dir);
19874
+ const artifactKinds = [...new Set(guardResult.artifacts.map((a) => a.kind))];
19875
+ const highestSeverity = guardResult.counts.critical > 0 ? "critical" : guardResult.counts.high > 0 ? "high" : guardResult.counts.medium > 0 ? "medium" : guardResult.counts.low > 0 ? "low" : "none";
19876
+ const blockingCount = guardResult.counts.critical + guardResult.counts.high;
19877
+ const lines = [
19878
+ `Readiness Check`,
19879
+ ``,
19880
+ `Routing: ${state.activated ? "active" : "inactive"}`,
19881
+ `Artifact Guard: available`,
19882
+ ` Files scanned: ${guardResult.filesScanned}`,
19883
+ ` Artifact kinds: ${artifactKinds.length > 0 ? artifactKinds.join(", ") : "none detected"}`,
19884
+ ` Highest severity: ${highestSeverity}`,
19885
+ ` Policy outcome: ${guardResult.policyOutcome.toUpperCase()}`,
19886
+ ` Blocking findings: ${blockingCount}`,
19887
+ ` Latest receipt: ${latestReceipt ? "available" : "none"}`,
19888
+ ` Next: ${guardResult.nextBestAction}`
19889
+ ];
19890
+ if (guardResult.policyOutcome === "blocked") {
19891
+ lines.push(``);
19892
+ lines.push(`Readiness: NOT READY \u2014 critical findings must be resolved`);
19893
+ } else if (guardResult.policyOutcome === "requires_approval") {
19894
+ lines.push(``);
19895
+ lines.push(`Readiness: REVIEW NEEDED \u2014 high-severity findings require approval`);
19896
+ } else if (guardResult.policyOutcome === "warn") {
19897
+ lines.push(``);
19898
+ lines.push(`Readiness: READY (with warnings)`);
19899
+ } else {
19900
+ lines.push(``);
19901
+ lines.push(`Readiness: READY`);
17550
19902
  }
17551
- const now = opts?.now ?? Date.now();
17552
- const lastUploadMs = state.lastUploadAt ? Date.parse(state.lastUploadAt) : 0;
17553
- const dueByInterval = lastUploadMs === 0 || now - lastUploadMs >= config2.uploadIntervalMs;
17554
- const dueByQueueSize = queuedCount >= config2.batchSize;
17555
- const dueByOpportunistic = lastUploadMs === 0 || now - lastUploadMs >= config2.opportunisticIntervalMs;
17556
- if (!opts?.force && !dueByInterval && !dueByQueueSize && !dueByOpportunistic) {
17557
- return { attempted: false, sent: false, reason: "not_due", queuedCount };
19903
+ if (options.json) {
19904
+ return {
19905
+ exitCode: guardResult.policyOutcome === "blocked" ? 1 : 0,
19906
+ stdout: lines.join("\n"),
19907
+ json: {
19908
+ routing: { active: state.activated },
19909
+ artifactGuard: {
19910
+ available: true,
19911
+ filesScanned: guardResult.filesScanned,
19912
+ artifactKinds,
19913
+ highestSeverity,
19914
+ policyOutcome: guardResult.policyOutcome,
19915
+ riskScore: guardResult.riskScore,
19916
+ blockingFindings: blockingCount,
19917
+ counts: guardResult.counts,
19918
+ latestReceipt: latestReceipt ? true : false,
19919
+ nextBestAction: guardResult.nextBestAction
19920
+ },
19921
+ readiness: guardResult.policyOutcome === "blocked" ? "not_ready" : guardResult.policyOutcome === "requires_approval" ? "review_needed" : "ready"
19922
+ }
19923
+ };
17558
19924
  }
17559
- const { batch, queueIds } = buildBatch(dir, config2.batchSize);
17560
- if (!batch || queueIds.length === 0) {
17561
- return { attempted: false, sent: false, reason: "queue_empty", queuedCount };
19925
+ return {
19926
+ exitCode: guardResult.policyOutcome === "blocked" ? 1 : 0,
19927
+ stdout: lines.join("\n")
19928
+ };
19929
+ }
19930
+ function handleReceipt(dir, options) {
19931
+ const sub = options.receiptSubcommand ?? "latest";
19932
+ if (sub === "latest") {
19933
+ const receipt = loadLatestReceipt(dir);
19934
+ if (!receipt) {
19935
+ return { exitCode: 0, stdout: "No artifact guard receipt found. Run `avorelo guard scan` first." };
19936
+ }
19937
+ if (options.json) {
19938
+ return {
19939
+ exitCode: 0,
19940
+ stdout: JSON.stringify(receipt, null, 2),
19941
+ json: receipt
19942
+ };
19943
+ }
19944
+ const lines = [
19945
+ `Latest Artifact Guard Receipt`,
19946
+ ` Type: ${receipt.receiptType}`,
19947
+ ` Scanned: ${receipt.scannedAt}`,
19948
+ ` Files: ${receipt.filesScanned}`,
19949
+ ` Findings: ${receipt.findingSummary.total}`,
19950
+ ` Risk score: ${receipt.riskScore}/10`,
19951
+ ` Outcome: ${receipt.policyOutcome.toUpperCase()}`,
19952
+ ` Next: ${receipt.nextBestAction}`,
19953
+ ` Raw content stored: ${receipt.contentStored}`
19954
+ ];
19955
+ return { exitCode: 0, stdout: lines.join("\n") };
17562
19956
  }
17563
- const controller = new AbortController();
17564
- const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
17565
- try {
17566
- const response = await fetch(config2.endpointUrl, {
17567
- method: "POST",
17568
- headers: { "Content-Type": "application/json" },
17569
- body: JSON.stringify(batch),
17570
- signal: controller.signal
17571
- });
17572
- clearTimeout(timeout);
17573
- if (response.ok) {
17574
- markTelemetryBatchSent(dir, queueIds, new Date(now).toISOString());
17575
- state.lastUploadAt = new Date(now).toISOString();
17576
- state.lastBatchId = batch.batchId;
17577
- state.lastBatchFailureAt = null;
17578
- state.lastBatchFailureCode = null;
17579
- state.acceptedBatches += 1;
17580
- writeTelemetryState(dir, state);
17581
- appendTransportEvent(dir, "telemetry_batch_sent");
17582
- return { attempted: true, sent: true, reason: "accepted", queuedCount };
19957
+ return { exitCode: 2, stdout: "Usage: avorelo receipt latest" };
19958
+ }
19959
+ function handleGuard(dir, options) {
19960
+ const sub = options.guardSubcommand ?? "scan";
19961
+ if (sub === "explain") {
19962
+ const ruleId = options.guardArgs?.[0];
19963
+ if (!ruleId) {
19964
+ return { exitCode: 2, stdout: "Usage: avorelo guard explain <ruleId>" };
17583
19965
  }
17584
- if (response.status >= 400 && response.status < 500) {
17585
- quarantineTelemetryBatch(dir, queueIds, `http_${response.status}`, now);
17586
- state.rejectedBatches += 1;
17587
- state.lastBatchFailureAt = new Date(now).toISOString();
17588
- state.lastBatchFailureCode = `http_${response.status}`;
17589
- writeTelemetryState(dir, state);
17590
- appendTransportEvent(dir, "telemetry_batch_failed", `http_${response.status}`);
17591
- return { attempted: true, sent: false, reason: `client_error_${response.status}`, queuedCount };
19966
+ const rule = getRule(ruleId);
19967
+ if (!rule) {
19968
+ return { exitCode: 2, stdout: `Unknown rule: ${ruleId}` };
17592
19969
  }
17593
- markTelemetryBatchRetry(dir, queueIds, `http_${response.status}`, now);
17594
- state.lastBatchFailureAt = new Date(now).toISOString();
17595
- state.lastBatchFailureCode = `http_${response.status}`;
17596
- writeTelemetryState(dir, state);
17597
- appendTransportEvent(dir, "telemetry_batch_failed", `http_${response.status}`);
17598
- return { attempted: true, sent: false, reason: `server_error_${response.status}`, queuedCount };
17599
- } catch (error) {
17600
- clearTimeout(timeout);
17601
- const code = error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error";
17602
- markTelemetryBatchRetry(dir, queueIds, code, now);
17603
- state.lastBatchFailureAt = new Date(now).toISOString();
17604
- state.lastBatchFailureCode = code;
17605
- writeTelemetryState(dir, state);
17606
- appendTransportEvent(dir, "telemetry_batch_failed", code);
17607
- return { attempted: true, sent: false, reason: code, queuedCount };
19970
+ const lines = [
19971
+ `Rule: ${rule.id}`,
19972
+ `Title: ${rule.title}`,
19973
+ `Severity: ${rule.severity}`,
19974
+ `Category: ${rule.category}`,
19975
+ `Description: ${rule.description}`,
19976
+ `Recommended action: ${rule.recommendedAction}`,
19977
+ `Applies to: ${rule.artifactKinds === "all" ? "all artifact kinds" : rule.artifactKinds.join(", ")}`
19978
+ ];
19979
+ return { exitCode: 0, stdout: lines.join("\n") };
19980
+ }
19981
+ const result3 = scan(dir);
19982
+ const receipt = createReceipt(result3);
19983
+ const receiptPath = storeReceipt(dir, receipt);
19984
+ if (options.json) {
19985
+ return {
19986
+ exitCode: result3.policyOutcome === "blocked" ? 1 : 0,
19987
+ stdout: toJson(result3),
19988
+ json: result3
19989
+ };
19990
+ }
19991
+ if (options.ci) {
19992
+ return {
19993
+ exitCode: result3.policyOutcome === "blocked" ? 1 : 0,
19994
+ stdout: toJson(result3)
19995
+ };
19996
+ }
19997
+ const report = renderReport(result3);
19998
+ return {
19999
+ exitCode: result3.policyOutcome === "blocked" ? 1 : 0,
20000
+ stdout: `${report}
20001
+ Receipt: ${receiptPath}`
20002
+ };
20003
+ }
20004
+ function handleCli(options, dir) {
20005
+ switch (options.command) {
20006
+ case "activate":
20007
+ return handleActivate(dir, options);
20008
+ case "status":
20009
+ return handleStatus(dir, options);
20010
+ case "doctor":
20011
+ return handleDoctor(dir, options);
20012
+ case "guard":
20013
+ return handleGuard(dir, options);
20014
+ case "readiness":
20015
+ return handleReadiness(dir, options);
20016
+ case "receipt":
20017
+ return handleReceipt(dir, options);
20018
+ case "run":
20019
+ return { exitCode: 0, stdout: "run not implemented in guard handler" };
17608
20020
  }
17609
20021
  }
17610
20022
 
@@ -17695,6 +20107,10 @@ function help() {
17695
20107
  "",
17696
20108
  " context check [--target <dir>] [--json] [--strict] [--ci] Agent context integrity check",
17697
20109
  "",
20110
+ " guard scan [--target <dir>] [--json] [--ci] Scan agent artifacts for risks",
20111
+ " guard explain <rule-id> Explain a detection rule",
20112
+ " receipt latest [--target <dir>] [--json] Show latest artifact guard receipt",
20113
+ "",
17698
20114
  " dogfood-learning status [--target <dir>] [--json] Show learning status",
17699
20115
  " dogfood-learning preview [--target <dir>] [--json] Preview sanitized learning payload",
17700
20116
  " dogfood-learning send [--target <dir>] [--dry-run] [--json] Send or queue learning signal",
@@ -17738,8 +20154,8 @@ async function cmdActivate(args) {
17738
20154
  const r2 = activate(target, { approve: true });
17739
20155
  persistReceipt(target, r2.receipt);
17740
20156
  const state2 = runFullActivation(target);
17741
- state2.setupSteps.push({ id: "hooks_installed", label: "Claude Code hooks installed", status: r2.ok ? "passed" : "blocked", evidencePath: join61(target, ".claude", "settings.json") });
17742
- if (r2.ok) state2.receipts.push({ id: r2.receipt.receiptId, path: join61(target, ".avorelo", "receipts", `${r2.receipt.receiptId}.json`), type: "activation_with_hooks" });
20157
+ state2.setupSteps.push({ id: "hooks_installed", label: "Claude Code hooks installed", status: r2.ok ? "passed" : "blocked", evidencePath: join66(target, ".claude", "settings.json") });
20158
+ if (r2.ok) state2.receipts.push({ id: r2.receipt.receiptId, path: join66(target, ".avorelo", "receipts", `${r2.receipt.receiptId}.json`), type: "activation_with_hooks" });
17743
20159
  persistActivationV2(target, state2);
17744
20160
  process.stdout.write(JSON.stringify({ ok: r2.ok, mode: "activate-with-hooks", target, installed: r2.validate.wellFormed, receipt: r2.receipt.receiptId }, null, 2) + "\n");
17745
20161
  return r2.ok ? 0 : 1;
@@ -17762,7 +20178,7 @@ Activation taxonomy: ${preflight.taxonomy}
17762
20178
  process.stderr.write("Continuing with activation despite warnings...\n\n");
17763
20179
  }
17764
20180
  const state = runFullActivation(target);
17765
- const contract = createWorkContract({ contractId: "canonical-activate", objective: "full local-first activation", allowedPaths: [join61(target, ".avorelo")], planTier: "Free" });
20181
+ const contract = createWorkContract({ contractId: "canonical-activate", objective: "full local-first activation", allowedPaths: [join66(target, ".avorelo")], planTier: "Free" });
17766
20182
  const ledger = new StateLedger();
17767
20183
  const receipt = writeReceipt(ledger, {
17768
20184
  contractId: contract.contractId,
@@ -17777,7 +20193,7 @@ Activation taxonomy: ${preflight.taxonomy}
17777
20193
  redactionClasses: [],
17778
20194
  receiptId: "rcpt_canonical_activation"
17779
20195
  });
17780
- state.receipts.push({ id: receipt.receiptId, path: join61(target, ".avorelo", "receipts", `${receipt.receiptId}.json`), type: "canonical_activation" });
20196
+ state.receipts.push({ id: receipt.receiptId, path: join66(target, ".avorelo", "receipts", `${receipt.receiptId}.json`), type: "canonical_activation" });
17781
20197
  persistActivationV2(target, state);
17782
20198
  persistReceipt(target, receipt);
17783
20199
  const detection = runFullDetection(target);
@@ -17790,7 +20206,7 @@ Activation taxonomy: ${preflight.taxonomy}
17790
20206
  ` Mode: ${state.activationMode}`,
17791
20207
  ` Scope: ${scope}`,
17792
20208
  ` Status: ${state.activationStatus}`,
17793
- ` State: ${join61(target, ACTIVATION_STATE_DIR, ACTIVATION_STATE_FILE)}`
20209
+ ` State: ${join66(target, ACTIVATION_STATE_DIR, ACTIVATION_STATE_FILE)}`
17794
20210
  ];
17795
20211
  if (fv.found.length > 0) {
17796
20212
  lines.push("", " Found:");
@@ -17805,17 +20221,22 @@ Activation taxonomy: ${preflight.taxonomy}
17805
20221
  lines.push("", " Fixed:");
17806
20222
  for (const f of fv.fixed) lines.push(` ${f}`);
17807
20223
  }
17808
- if (fv.needsAttention.length > 0) {
20224
+ const blockedItems = fv.needsAttention.filter((n) => n.startsWith("Blocked:"));
20225
+ if (blockedItems.length > 0) {
17809
20226
  lines.push("", " Needs attention:");
17810
- for (const n of fv.needsAttention) lines.push(` ${n}`);
20227
+ for (const n of blockedItems) lines.push(` ${n}`);
17811
20228
  }
17812
20229
  lines.push(
17813
20230
  "",
17814
20231
  ` Run entry: ${state.runEntry.installed ? "installed" : "not installed"}`,
17815
- ` Billing: ${state.billing.status}`,
17816
- ` Auth: ${state.auth.status}`,
17817
- ` Production: not ready`
20232
+ ` Agent Guard: available`,
20233
+ ` Receipts: working`
17818
20234
  );
20235
+ const optionalItems = fv.needsAttention.filter((n) => !n.startsWith("Blocked:"));
20236
+ if (optionalItems.length > 0) {
20237
+ lines.push("", " Optional (not required for local activation):");
20238
+ for (const n of optionalItems) lines.push(` ${n}`);
20239
+ }
17819
20240
  let linkResult = "no claim provided";
17820
20241
  if (claimToken) {
17821
20242
  const baseUrl = process.env.APP_BASE_URL ?? "https://app.avorelo.com";
@@ -17906,6 +20327,14 @@ async function cmdDoctor(args) {
17906
20327
  if (ctxResult.findings.length > 0) {
17907
20328
  for (const f of ctxResult.findings.slice(0, 3)) lines.push(` ${f.severity}: ${f.message}`);
17908
20329
  }
20330
+ const ccLatest = loadLatestRuntimeSession(target);
20331
+ if (ccLatest?.contextControl) {
20332
+ const cc = ccLatest.contextControl;
20333
+ lines.push("");
20334
+ lines.push(` Context Control: ${cc.status} (reduction=${cc.reductionPercent}%, evidence=${cc.evidenceRefCount}, blocked=${cc.blockedItemsCount})`);
20335
+ if (cc.blockedItemsCount > 0) lines.push(` WARNING: ${cc.blockedItemsCount} context item(s) blocked by policy`);
20336
+ if (cc.proofImpact === "degraded") lines.push(` WARNING: proof impact is degraded \u2014 evidence refs may be incomplete`);
20337
+ }
17909
20338
  try {
17910
20339
  const { getModelRegistry: getModelRegistry2, getLocalModels: getLocalModels2, getAllProviders: getAllProviders2, isProviderAvailable: isProviderAvailable2 } = (init_model_routing2(), __toCommonJS(model_routing_exports));
17911
20340
  const models = getModelRegistry2();
@@ -18043,6 +20472,7 @@ function cmdStatus(args) {
18043
20472
  ` detected: ${[contract.gitDetected ? "git" : "no-git", contract.packageDetected ? "package" : "no-package"].join(", ")}`,
18044
20473
  ` workspace: local-only \xB7 cloud not claimed`,
18045
20474
  ` last run: ${latest ? `${latest.status} (${latest.runtimeSessionId})` : "none yet"}`,
20475
+ ` ctx control: ${latest?.contextControl ? `${latest.contextControl.status} \xB7 reduction=${latest.contextControl.reductionPercent}%` : "not yet run"}`,
18046
20476
  ` routing: seamless model routing active (local-first, no credentials required)`,
18047
20477
  ` control ctr: available \u2014 avorelo control-center --target .`,
18048
20478
  ` next: ${contract.firstRunRecommended.command} \u2014 ${contract.firstRunRecommended.reason}`,
@@ -18071,7 +20501,7 @@ function cmdStatus(args) {
18071
20501
  ` activation: ${state.activationStatus}`,
18072
20502
  ` mode: ${state.activationMode}`,
18073
20503
  ` contract: ${state.contract}`,
18074
- ` state: ${join61(target, ACTIVATION_STATE_DIR, ACTIVATION_STATE_FILE)}`,
20504
+ ` state: ${join66(target, ACTIVATION_STATE_DIR, ACTIVATION_STATE_FILE)}`,
18075
20505
  ` hooks: ${v.installed ? v.wellFormed ? "installed (6/6)" : `partial` : "not installed"}`
18076
20506
  ];
18077
20507
  if (isV2) {
@@ -18144,7 +20574,7 @@ function cmdVerify(args) {
18144
20574
  }
18145
20575
  const input = loadProofInput(target);
18146
20576
  if (input) {
18147
- const contract = createWorkContract({ contractId: "verify", objective: input.objective ?? "verify real-workflow proof", allowedPaths: [join61(target, "src")], planTier: "Free" });
20577
+ const contract = createWorkContract({ contractId: "verify", objective: input.objective ?? "verify real-workflow proof", allowedPaths: [join66(target, "src")], planTier: "Free" });
18148
20578
  const r2 = evaluateProof({ contract, artifacts: input.artifacts, readbacks: input.readbacks, dir: target, sampleSize: input.sampleSize, receiptId: "rcpt_verify" });
18149
20579
  process.stdout.write(JSON.stringify({ scope: "full", activationState: { valid: true }, proof: { decision: r2.decision, confidence: r2.confidence } }, null, 2) + "\n");
18150
20580
  return r2.decision === "STOP_DONE" ? 0 : 1;
@@ -18153,13 +20583,13 @@ function cmdVerify(args) {
18153
20583
  return 0;
18154
20584
  }
18155
20585
  function cmdSite(args) {
18156
- const outDir = arg(args, "--out", join61(process.cwd(), ".avorelo", "site"));
20586
+ const outDir = arg(args, "--out", join66(process.cwd(), ".avorelo", "site"));
18157
20587
  const r2 = buildSite(outDir);
18158
20588
  process.stdout.write(JSON.stringify({ ok: r2.ok, outDir: r2.outDir, indexPath: r2.indexPath, pages: r2.pages }, null, 2) + "\n");
18159
20589
  return r2.ok ? 0 : 1;
18160
20590
  }
18161
20591
  function cmdServe(args) {
18162
- const outDir = arg(args, "--out", join61(process.cwd(), ".avorelo", "site"));
20592
+ const outDir = arg(args, "--out", join66(process.cwd(), ".avorelo", "site"));
18163
20593
  const port = Number(arg(args, "--port", "0"));
18164
20594
  buildSite(outDir);
18165
20595
  serve(outDir, { port: Number.isFinite(port) ? port : 0 }).then((h) => {
@@ -18191,7 +20621,7 @@ function cmdServe(args) {
18191
20621
  }
18192
20622
  function readStdinJson() {
18193
20623
  try {
18194
- const raw = readFileSync46(0, "utf8");
20624
+ const raw = readFileSync50(0, "utf8");
18195
20625
  return raw.trim() ? JSON.parse(raw) : {};
18196
20626
  } catch {
18197
20627
  return {};
@@ -18215,9 +20645,9 @@ function cmdLifecycleHook(args) {
18215
20645
  const sourceLabel = String(ccEvent.tool_name ?? "").toLowerCase().includes("mcp") ? "tool_output" : "tool_output";
18216
20646
  const r3 = scanContent({ content: toolOutput, sourceKind: sourceLabel });
18217
20647
  try {
18218
- const dir = join61(cwd, ".avorelo", "secret-boundary");
18219
- mkdirSync39(dir, { recursive: true });
18220
- appendFileSync11(join61(dir, "receipts.jsonl"), JSON.stringify(redact(r3.receipt).value) + "\n");
20648
+ const dir = join66(cwd, ".avorelo", "secret-boundary");
20649
+ mkdirSync40(dir, { recursive: true });
20650
+ appendFileSync11(join66(dir, "receipts.jsonl"), JSON.stringify(redact(r3.receipt).value) + "\n");
18221
20651
  } catch {
18222
20652
  }
18223
20653
  process.stdout.write(JSON.stringify({
@@ -18234,13 +20664,13 @@ function cmdLifecycleHook(args) {
18234
20664
  }
18235
20665
  const looksLikeToolRequest = payloadRaw && ccEvent.tool !== void 0 && ccEvent.workingDir !== void 0;
18236
20666
  const req = looksLikeToolRequest ? ccEvent : mapClaudeCodeEvent(ccEvent, cwd);
18237
- const contract = createWorkContract({ contractId: "hook", objective: "lifecycle hook", allowedPaths: [join61(cwd, "src")], planTier: "Free" });
20667
+ const contract = createWorkContract({ contractId: "hook", objective: "lifecycle hook", allowedPaths: [join66(cwd, "src")], planTier: "Free" });
18238
20668
  const r2 = handleLifecycleHook(event, req, { contract });
18239
20669
  try {
18240
- const dir = join61(cwd, ".avorelo", "events");
18241
- mkdirSync39(dir, { recursive: true });
20670
+ const dir = join66(cwd, ".avorelo", "events");
20671
+ mkdirSync40(dir, { recursive: true });
18242
20672
  const entry = redact({ ts: Date.now(), event: r2.event, tool: req.tool, verdict: r2.verdict, reasonCodes: r2.reasonCodes, redactionClasses: r2.redactionClasses, latencyMs: r2.latencyMs }).value;
18243
- appendFileSync11(join61(dir, "hook-fires.jsonl"), JSON.stringify(entry) + "\n");
20673
+ appendFileSync11(join66(dir, "hook-fires.jsonl"), JSON.stringify(entry) + "\n");
18244
20674
  } catch {
18245
20675
  }
18246
20676
  if (r2.exitCode === 2) {
@@ -18647,13 +21077,13 @@ function cmdTokenCost(args) {
18647
21077
  }
18648
21078
  if (sub === "import" || sub === "validate") {
18649
21079
  const file = arg(args, "--file");
18650
- if (!file || !existsSync62(file)) {
21080
+ if (!file || !existsSync64(file)) {
18651
21081
  process.stderr.write("Usage: avorelo token-cost " + sub + " --file <path> [--json]\n");
18652
21082
  return 2;
18653
21083
  }
18654
21084
  let parsed;
18655
21085
  try {
18656
- parsed = JSON.parse(readFileSync46(file, "utf8"));
21086
+ parsed = JSON.parse(readFileSync50(file, "utf8"));
18657
21087
  } catch {
18658
21088
  process.stderr.write("Invalid JSON file.\n");
18659
21089
  return 1;
@@ -18794,9 +21224,9 @@ function cmdContextCheck(args) {
18794
21224
  const ci = args.includes("--ci");
18795
21225
  const wcPath = arg(args, "--work-contract");
18796
21226
  let workContract;
18797
- if (wcPath && existsSync62(wcPath)) {
21227
+ if (wcPath && existsSync64(wcPath)) {
18798
21228
  try {
18799
- workContract = JSON.parse(readFileSync46(wcPath, "utf8"));
21229
+ workContract = JSON.parse(readFileSync50(wcPath, "utf8"));
18800
21230
  } catch {
18801
21231
  }
18802
21232
  }
@@ -18824,8 +21254,64 @@ function cmdContext(args) {
18824
21254
  const asJson = args.includes("--json");
18825
21255
  const task = args.slice(1).find((a) => !a.startsWith("--") && a !== target);
18826
21256
  if (sub === "check") return cmdContextCheck(args.slice(1));
21257
+ if (sub === "stats") {
21258
+ const latest = loadLatestRuntimeSession(target);
21259
+ if (!latest?.contextControl) {
21260
+ process.stderr.write("No context control data yet. Run a session first.\n");
21261
+ return 1;
21262
+ }
21263
+ const cc = latest.contextControl;
21264
+ process.stdout.write(renderContextStats({
21265
+ contextControlStatus: cc.status === "optimized" ? "ACTIVE" : cc.status === "blocked" ? "BLOCKED" : "ACTIVE",
21266
+ estimatedContextBefore: cc.estimatedContextBefore,
21267
+ estimatedContextSent: cc.estimatedContextAfter,
21268
+ reductionPercent: cc.reductionPercent,
21269
+ evidenceRefCount: cc.evidenceRefCount,
21270
+ blockedItemsCount: cc.blockedItemsCount,
21271
+ retrievalAvailable: cc.retrievalAvailable,
21272
+ proofImpact: cc.proofImpact,
21273
+ topReasonCodes: cc.reasonCodes,
21274
+ summary: `Context control ${cc.status}`
21275
+ }) + "\n");
21276
+ return 0;
21277
+ }
21278
+ if (sub === "explain") {
21279
+ const latest = loadLatestRuntimeSession(target);
21280
+ if (!latest?.contextControl) {
21281
+ process.stderr.write("No context control data yet. Run a session first.\n");
21282
+ return 1;
21283
+ }
21284
+ const cc = latest.contextControl;
21285
+ process.stdout.write(renderContextExplain({
21286
+ contextControlStatus: cc.status === "optimized" ? "ACTIVE" : cc.status === "blocked" ? "BLOCKED" : "ACTIVE",
21287
+ estimatedContextBefore: cc.estimatedContextBefore,
21288
+ estimatedContextSent: cc.estimatedContextAfter,
21289
+ reductionPercent: cc.reductionPercent,
21290
+ evidenceRefCount: cc.evidenceRefCount,
21291
+ blockedItemsCount: cc.blockedItemsCount,
21292
+ retrievalAvailable: cc.retrievalAvailable,
21293
+ proofImpact: cc.proofImpact,
21294
+ topReasonCodes: cc.reasonCodes,
21295
+ summary: `Context control ${cc.status}`
21296
+ }, []) + "\n");
21297
+ return 0;
21298
+ }
21299
+ if (sub === "learn") {
21300
+ if (!args.includes("--dry-run")) {
21301
+ process.stderr.write("Usage: avorelo context learn --dry-run [--target <dir>]\nLearning candidates are dry-run only.\n");
21302
+ return 2;
21303
+ }
21304
+ const result3 = generateLearningCandidates({ failurePatterns: [], receipts: [] });
21305
+ process.stdout.write(`Learning candidates (dry-run): ${result3.candidates.length}
21306
+ `);
21307
+ for (const c of result3.candidates) {
21308
+ process.stdout.write(` - [${c.priority}] ${c.label}: ${c.description}
21309
+ `);
21310
+ }
21311
+ return 0;
21312
+ }
18827
21313
  if (sub !== "compile") {
18828
- process.stderr.write("Usage: avorelo context <compile|check> [args]\n");
21314
+ process.stderr.write("Usage: avorelo context <compile|check|stats|explain|learn> [args]\n");
18829
21315
  return 2;
18830
21316
  }
18831
21317
  if (!task) {
@@ -18854,12 +21340,68 @@ function cmdContext(args) {
18854
21340
  ].filter(Boolean).join("\n"));
18855
21341
  return 0;
18856
21342
  }
21343
+ function cmdGuard(args) {
21344
+ const sub = args[0];
21345
+ const target = arg(args, "--target", process.cwd());
21346
+ const asJson = args.includes("--json");
21347
+ const ci = args.includes("--ci");
21348
+ if (sub === "scan") {
21349
+ const output = handleCli(
21350
+ { command: "guard", json: asJson, verbose: false, guardSubcommand: "scan", ci },
21351
+ target
21352
+ );
21353
+ process.stdout.write(output.stdout + "\n");
21354
+ return output.exitCode;
21355
+ }
21356
+ if (sub === "explain") {
21357
+ const ruleId = args[1];
21358
+ const output = handleCli(
21359
+ { command: "guard", json: false, verbose: false, guardSubcommand: "explain", guardArgs: [ruleId] },
21360
+ target
21361
+ );
21362
+ process.stdout.write(output.stdout + "\n");
21363
+ return output.exitCode;
21364
+ }
21365
+ process.stderr.write("Usage: avorelo guard <scan|explain> [args]\n");
21366
+ return 2;
21367
+ }
21368
+ function cmdReceipt(args) {
21369
+ const sub = args[0];
21370
+ const target = arg(args, "--target", process.cwd());
21371
+ const asJson = args.includes("--json");
21372
+ if (sub === "latest" || sub === void 0) {
21373
+ const output = handleCli(
21374
+ { command: "receipt", json: asJson, verbose: false, receiptSubcommand: "latest" },
21375
+ target
21376
+ );
21377
+ process.stdout.write(output.stdout + "\n");
21378
+ const latest = loadLatestRuntimeSession(target);
21379
+ if (latest?.contextControl && !asJson) {
21380
+ const cc = latest.contextControl;
21381
+ process.stdout.write([
21382
+ "",
21383
+ "Context Control:",
21384
+ ` Status: ${cc.status}`,
21385
+ ` Context before: ${cc.estimatedContextBefore}`,
21386
+ ` Context after: ${cc.estimatedContextAfter}`,
21387
+ ` Reduction: ${cc.reductionPercent}%`,
21388
+ ` Evidence refs: ${cc.evidenceRefCount}`,
21389
+ ` Blocked items: ${cc.blockedItemsCount}`,
21390
+ ` Proof impact: ${cc.proofImpact}`,
21391
+ ""
21392
+ ].join("\n"));
21393
+ }
21394
+ return output.exitCode;
21395
+ }
21396
+ process.stderr.write("Usage: avorelo receipt latest [--target <dir>] [--json]\n");
21397
+ return 2;
21398
+ }
18857
21399
  function cmdSecretBoundary(args) {
18858
21400
  const sub = args[0];
18859
21401
  const asJson = args.includes("--json");
18860
21402
  let content = arg(args, "--content");
18861
21403
  const file = arg(args, "--file");
18862
- if (file && existsSync62(file)) content = readFileSync46(file, "utf8");
21404
+ if (file && existsSync64(file)) content = readFileSync50(file, "utf8");
18863
21405
  if (content === void 0) content = "";
18864
21406
  if (sub === "scan" || sub === void 0) {
18865
21407
  const r2 = scanContent({ content, sourceKind: file ? "file" : "tool_output" });
@@ -18926,7 +21468,7 @@ function cmdExplain(args) {
18926
21468
  const changedFiles = [];
18927
21469
  for (const { adapter } of detected) {
18928
21470
  const surface = adapter.getInstructionSurface(target);
18929
- if (surface && existsSync62(surface)) changedFiles.push(surface);
21471
+ if (surface && existsSync64(surface)) changedFiles.push(surface);
18930
21472
  }
18931
21473
  if (changedFiles.length > 0) {
18932
21474
  lines.push(" Changed:");
@@ -18940,7 +21482,7 @@ function cmdExplain(args) {
18940
21482
  lines.push("");
18941
21483
  lines.push(` Hooks: ${hookValidation.installed ? "installed" : "not installed"}`);
18942
21484
  lines.push(" Cloud: off (local-first)");
18943
- lines.push(` Receipts: ${join61(target, ".avorelo", "receipts")}`);
21485
+ lines.push(` Receipts: ${join66(target, ".avorelo", "receipts")}`);
18944
21486
  const sessionStatus = getSessionStatus(target);
18945
21487
  if (sessionStatus) {
18946
21488
  lines.push("");
@@ -19100,7 +21642,7 @@ function cmdFeedback(args) {
19100
21642
  }
19101
21643
  if (sub === "share") {
19102
21644
  const file = arg(args, "--file");
19103
- if (!file || !existsSync62(file)) {
21645
+ if (!file || !existsSync64(file)) {
19104
21646
  process.stderr.write("Usage: avorelo feedback share --file <bundle-path>\n");
19105
21647
  return 2;
19106
21648
  }
@@ -19404,7 +21946,7 @@ No loop metadata found for ${loopId}.
19404
21946
  if (sub === "doctor") {
19405
21947
  const issues = [];
19406
21948
  const ok = [];
19407
- if (existsSync62(join61(target, ".git"))) {
21949
+ if (existsSync64(join66(target, ".git"))) {
19408
21950
  ok.push("Git repository detected");
19409
21951
  } else {
19410
21952
  issues.push("Not a git repository \u2014 loop needs git for drift detection");
@@ -19414,12 +21956,12 @@ No loop metadata found for ${loopId}.
19414
21956
  } else {
19415
21957
  issues.push("Claude Code CLI not found \u2014 install it or ensure `claude` is on PATH");
19416
21958
  }
19417
- const testDir = join61(target, ".avorelo", "loops");
21959
+ const testDir = join66(target, ".avorelo", "loops");
19418
21960
  try {
19419
- mkdirSync39(testDir, { recursive: true });
19420
- const testFile = join61(testDir, ".doctor_probe");
19421
- writeFileSync37(testFile, "probe");
19422
- unlinkSync4(testFile);
21961
+ mkdirSync40(testDir, { recursive: true });
21962
+ const testFile = join66(testDir, ".doctor_probe");
21963
+ writeFileSync38(testFile, "probe");
21964
+ unlinkSync5(testFile);
19423
21965
  ok.push("Storage writable (.avorelo/loops/)");
19424
21966
  } catch {
19425
21967
  issues.push("Cannot write to .avorelo/loops/ \u2014 check permissions");
@@ -19460,11 +22002,11 @@ function cmdUninstallAll(args) {
19460
22002
  const target = arg(args, "--target", process.cwd());
19461
22003
  const adapterResult = uninstallAll(target);
19462
22004
  const hookResult = uninstall(target);
19463
- const removed = [...adapterResult.removed, ...hookResult.restored ? [join61(target, ".claude", "settings.json")] : []];
22005
+ const removed = [...adapterResult.removed, ...hookResult.restored ? [join66(target, ".claude", "settings.json")] : []];
19464
22006
  const preserved = adapterResult.preserved;
19465
- const avoreloDir2 = join61(target, ".avorelo");
22007
+ const avoreloDir2 = join66(target, ".avorelo");
19466
22008
  try {
19467
- if (existsSync62(avoreloDir2)) {
22009
+ if (existsSync64(avoreloDir2)) {
19468
22010
  rmSync3(avoreloDir2, { recursive: true, force: true });
19469
22011
  removed.push(avoreloDir2);
19470
22012
  }
@@ -20057,7 +22599,7 @@ function cmdConfig(args) {
20057
22599
  function avoreloVersion() {
20058
22600
  for (const rel of ["../package.json", "../../package.json", "../../../package.json", "../../../../package.json"]) {
20059
22601
  try {
20060
- const pkg = JSON.parse(readFileSync46(join61(import.meta.dirname, rel), "utf8"));
22602
+ const pkg = JSON.parse(readFileSync50(join66(import.meta.dirname, rel), "utf8"));
20061
22603
  if (pkg.name === "avorelo" && pkg.version) return pkg.version;
20062
22604
  } catch {
20063
22605
  }
@@ -20168,6 +22710,10 @@ function main(argv) {
20168
22710
  return cmdReadiness(rest);
20169
22711
  case "loop":
20170
22712
  return cmdLoop(rest);
22713
+ case "guard":
22714
+ return cmdGuard(rest);
22715
+ case "receipt":
22716
+ return cmdReceipt(rest);
20171
22717
  default:
20172
22718
  return help();
20173
22719
  }
@@ -20348,6 +22894,7 @@ async function finalize(exitCode) {
20348
22894
  } catch {
20349
22895
  }
20350
22896
  if (_cmd !== "serve" && _cmd !== "webhook" && _cmd !== "sync") {
22897
+ process.exitCode = exitCode;
20351
22898
  setTimeout(() => process.exit(exitCode), 50).unref();
20352
22899
  }
20353
22900
  }