@jmanuelcorral/openteam 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,8 +3,9 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/cli.ts
6
- import { execFile as execFile2 } from "node:child_process";
6
+ import { exec, execFile as execFile2 } from "node:child_process";
7
7
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
8
+ import { createRequire as createRequire2 } from "node:module";
8
9
  import { dirname as dirname5, resolve as resolve6 } from "node:path";
9
10
  import { promisify as promisify2 } from "node:util";
10
11
  import {
@@ -8552,6 +8553,10 @@ function recoverJournal(spec, text) {
8552
8553
 
8553
8554
  // src/cli/graphOperator.ts
8554
8555
  var ALLOWED_GATE = { allowed: true, violations: [] };
8556
+ var ALLOWED_EVALUATION = {
8557
+ kind: "evaluated",
8558
+ gate: ALLOWED_GATE
8559
+ };
8555
8560
  function journalRoot(config) {
8556
8561
  return config.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT;
8557
8562
  }
@@ -8641,7 +8646,7 @@ async function listWorktreeObservations(config, options) {
8641
8646
  })));
8642
8647
  }
8643
8648
  function createGraphOperator(options) {
8644
- const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_GATE);
8649
+ const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_EVALUATION);
8645
8650
  const worktreeOptions = options.exec === undefined ? undefined : {
8646
8651
  exec: options.exec,
8647
8652
  repoRoot: options.repoRoot ?? process.cwd(),
@@ -8920,6 +8925,35 @@ function createMigrateAdapter(root) {
8920
8925
  };
8921
8926
  }
8922
8927
 
8928
+ // src/cli/opencodeVersion.ts
8929
+ var SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
8930
+ function normalizeVersion(raw) {
8931
+ if (raw === undefined) {
8932
+ return;
8933
+ }
8934
+ const trimmed = raw.trim();
8935
+ if (trimmed === "" || trimmed.toLowerCase() === "unknown") {
8936
+ return;
8937
+ }
8938
+ const match = SEMVER_PATTERN.exec(trimmed);
8939
+ return match?.[1] ?? undefined;
8940
+ }
8941
+ async function resolveOpencodeVersionBestEffort(deps) {
8942
+ try {
8943
+ const fromPackage = normalizeVersion(deps.readPackageVersion("opencode-ai"));
8944
+ if (fromPackage !== undefined) {
8945
+ return fromPackage;
8946
+ }
8947
+ } catch {}
8948
+ try {
8949
+ const fromCommand = normalizeVersion(await deps.runVersionCommand());
8950
+ if (fromCommand !== undefined) {
8951
+ return fromCommand;
8952
+ }
8953
+ } catch {}
8954
+ return;
8955
+ }
8956
+
8923
8957
  // src/cli/purgeAdapter.ts
8924
8958
  import { execFile } from "node:child_process";
8925
8959
  import * as fs2 from "node:fs";
@@ -10239,6 +10273,54 @@ function createProbeRuntime(exec) {
10239
10273
  }
10240
10274
 
10241
10275
  // src/config/graphFeatureGate.ts
10276
+ function checkCertificate(status, name) {
10277
+ if (status.status === "absent") {
10278
+ return {
10279
+ code: `${name}-certificate-absent`,
10280
+ detail: `${name} certificate not found — run the ${name} certification suite to produce it`
10281
+ };
10282
+ }
10283
+ if (status.status === "invalid") {
10284
+ return {
10285
+ code: `${name}-certificate-invalid`,
10286
+ detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
10287
+ };
10288
+ }
10289
+ return;
10290
+ }
10291
+ function evaluateGraphGate(input) {
10292
+ const violations = [];
10293
+ const shadowV = checkCertificate(input.shadow, "shadow");
10294
+ if (shadowV !== undefined)
10295
+ violations.push(shadowV);
10296
+ const releaseV = checkCertificate(input.release, "release");
10297
+ if (releaseV !== undefined)
10298
+ violations.push(releaseV);
10299
+ const soakV = checkCertificate(input.soak, "soak");
10300
+ if (soakV !== undefined)
10301
+ violations.push(soakV);
10302
+ if (input.migration === "pending") {
10303
+ violations.push({
10304
+ code: "migration-pending",
10305
+ detail: "legacy migration has pending work — run the migration tool to completion before enabling active mode"
10306
+ });
10307
+ } else if (input.migration === "unknown") {
10308
+ violations.push({
10309
+ code: "migration-unknown",
10310
+ detail: "could not determine migration status — check ledger readability and storage access"
10311
+ });
10312
+ }
10313
+ if (!input.operatorApproval) {
10314
+ violations.push({
10315
+ code: "operator-approval-missing",
10316
+ detail: "set graph.operatorApproval to true in .opencode/openteam.json after reviewing all certificates and completing migration — this is a declaration of intent, not a proof"
10317
+ });
10318
+ }
10319
+ return {
10320
+ allowed: violations.length === 0,
10321
+ violations
10322
+ };
10323
+ }
10242
10324
  function isGateApplicable(mode) {
10243
10325
  return mode === "active";
10244
10326
  }
@@ -11057,7 +11139,7 @@ function legacyRosterFindings(audit) {
11057
11139
  }
11058
11140
  return findings;
11059
11141
  }
11060
- function rosterHealthSection(audit, loadError, rosterRoleCount) {
11142
+ function rosterHealthSection(audit, loadError, rosterRoleCount, rosterMissing) {
11061
11143
  const auditFindings = audit?.findings ?? legacyRosterFindings(audit ?? {});
11062
11144
  const unparseable = auditFindings.find((finding) => finding.kind === "unparseable-roster");
11063
11145
  const unparseableMessage = loadError ?? unparseable?.message;
@@ -11070,6 +11152,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
11070
11152
  ];
11071
11153
  }
11072
11154
  if (audit === undefined) {
11155
+ if (rosterMissing === true) {
11156
+ return [
11157
+ " roster health:",
11158
+ ` ✗ ${OPENTEAM_ROSTER_PATH} does not exist — no team roster has been cast.`,
11159
+ " consequence: without a roster openteam cannot distribute work across roles, so every request is handled by a single agent and no work splitting happens.",
11160
+ ` remedy: run \`openteam setup\` to cast a team and write the roster (${OPENTEAM_ROSTER_PATH}), then re-run \`openteam doctor\`.`
11161
+ ];
11162
+ }
11073
11163
  return [];
11074
11164
  }
11075
11165
  const missing = auditFindings.filter((finding) => finding.kind === "missing-guaranteed-role").map((finding) => finding.roleID);
@@ -11105,6 +11195,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
11105
11195
  }
11106
11196
  return lines;
11107
11197
  }
11198
+ function orchestratorAgentSection() {
11199
+ return [
11200
+ " orchestrator agent:",
11201
+ ` ✗ ${ORCHESTRATOR_AGENT_PATH} does not exist — opencode has no openteam coordinator agent.`,
11202
+ " consequence: this is the primary agent that reads the roster and calls `openteam-orchestrate` to fan work out across roles; without it opencode has no openteam coordinator, so every request is handled by a single agent and no work splitting happens.",
11203
+ ` remedy: run \`openteam setup\` to generate ${ORCHESTRATOR_AGENT_PATH}, then re-run \`openteam doctor\`.`
11204
+ ];
11205
+ }
11108
11206
  function gitignoreSection(gitignore) {
11109
11207
  if (gitignore.missingRules.length === 0) {
11110
11208
  return [
@@ -11168,7 +11266,10 @@ function renderDoctor(input) {
11168
11266
  if (input.diagnostics !== undefined) {
11169
11267
  lines.push(...diagnosticsSection(input.diagnostics));
11170
11268
  }
11171
- lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
11269
+ lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount, input.rosterMissing));
11270
+ if (input.orchestratorAgentMissing === true) {
11271
+ lines.push(...orchestratorAgentSection());
11272
+ }
11172
11273
  if (input.gitignore !== undefined) {
11173
11274
  lines.push(...gitignoreSection(input.gitignore));
11174
11275
  }
@@ -11237,14 +11338,31 @@ function leaseLine(lease) {
11237
11338
  const state = lease.released ? "released" : "active";
11238
11339
  return ` lease: ${lease.holder} → ${lease.runID} (${state})`;
11239
11340
  }
11240
- function renderGraphStatus(snapshot) {
11341
+ function appendEffectiveLines(lines, gate) {
11342
+ switch (gate.kind) {
11343
+ case "not-applicable":
11344
+ return;
11345
+ case "passed":
11346
+ lines.push(" effective: active");
11347
+ return;
11348
+ case "unevaluable":
11349
+ lines.push(" effective: unknown — could not evaluate the cutover gate");
11350
+ lines.push(` ${gate.reason}`);
11351
+ return;
11352
+ case "denied":
11353
+ lines.push(" effective: off — cutover gate denied");
11354
+ for (const violation of gate.violations) {
11355
+ lines.push(` • ${violation.code}: ${violation.detail}`);
11356
+ }
11357
+ }
11358
+ }
11359
+ function renderGraphStatus(snapshot, gate = { kind: "not-applicable" }) {
11241
11360
  const lines = [
11242
11361
  "openteam graph status:",
11243
- ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`,
11244
- leaseLine(snapshot.lease),
11245
- ` journal: ${snapshot.journal}`,
11246
- ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`
11362
+ ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`
11247
11363
  ];
11364
+ appendEffectiveLines(lines, gate);
11365
+ lines.push(leaseLine(snapshot.lease), ` journal: ${snapshot.journal}`, ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`);
11248
11366
  const needsReconciliation = isRecoveryRequired(snapshot.journal, snapshot.unknownEffects);
11249
11367
  if (needsReconciliation) {
11250
11368
  lines.push(" ⚠ Reconciliation required: journal or effects in an uncertain state.");
@@ -11495,11 +11613,152 @@ function mergeOpencodeConfigs(base, override) {
11495
11613
  return result;
11496
11614
  }
11497
11615
 
11616
+ // src/graph/soakLedger.ts
11617
+ import { z as z19 } from "zod";
11618
+ var Sha256Schema2 = z19.string().regex(/^[0-9a-f]{64}$/);
11619
+ var SoakPlatformSchema = z19.enum(["linux", "win32", "darwin"]);
11620
+ var PrivacyPairSchema = z19.object({
11621
+ surfacesScanned: z19.number().int().nonnegative(),
11622
+ rawFindings: z19.number().int().nonnegative()
11623
+ }).strict();
11624
+ var DuplicateEffectPairSchema = z19.object({
11625
+ effectsExamined: z19.number().int().nonnegative(),
11626
+ duplicatesFound: z19.number().int().nonnegative()
11627
+ }).strict();
11628
+ var ModelVerificationPairSchema = z19.object({
11629
+ nodesChecked: z19.number().int().nonnegative(),
11630
+ unverified: z19.number().int().nonnegative(),
11631
+ mismatched: z19.number().int().nonnegative()
11632
+ }).strict();
11633
+ var SoakObservationSchema = z19.object({
11634
+ version: z19.literal(1),
11635
+ seq: z19.number().int().nonnegative(),
11636
+ timestamp: z19.string().datetime(),
11637
+ platform: SoakPlatformSchema,
11638
+ opencodeVersion: z19.string().min(1),
11639
+ provenance: z19.enum(["genuine-usage", "ci-synthetic"]),
11640
+ traceDigest: Sha256Schema2,
11641
+ criticalDivergences: z19.number().int().nonnegative().nullable(),
11642
+ privacy: PrivacyPairSchema.nullable(),
11643
+ duplicateEffects: DuplicateEffectPairSchema.nullable(),
11644
+ modelVerification: ModelVerificationPairSchema.nullable(),
11645
+ chainDigest: Sha256Schema2
11646
+ }).strict();
11647
+ function canonicalObservationPreimage(obs) {
11648
+ return JSON.stringify({
11649
+ version: obs.version,
11650
+ seq: obs.seq,
11651
+ timestamp: obs.timestamp,
11652
+ platform: obs.platform,
11653
+ opencodeVersion: obs.opencodeVersion,
11654
+ provenance: obs.provenance,
11655
+ traceDigest: obs.traceDigest,
11656
+ criticalDivergences: obs.criticalDivergences,
11657
+ privacy: obs.privacy,
11658
+ duplicateEffects: obs.duplicateEffects,
11659
+ modelVerification: obs.modelVerification
11660
+ });
11661
+ }
11662
+ function validateChain(observations) {
11663
+ if (observations.length === 0) {
11664
+ return { valid: true, observations: [] };
11665
+ }
11666
+ let prevChainDigest = "0".repeat(64);
11667
+ for (let i = 0;i < observations.length; i++) {
11668
+ const obs = observations[i];
11669
+ if (obs.seq !== i) {
11670
+ return { valid: false, observations };
11671
+ }
11672
+ if (i > 0) {
11673
+ const prev = observations[i - 1];
11674
+ if (obs.timestamp <= prev.timestamp) {
11675
+ return { valid: false, observations };
11676
+ }
11677
+ }
11678
+ const preimage = canonicalObservationPreimage(obs);
11679
+ const expectedDigest = sha256Hex(prevChainDigest + preimage);
11680
+ if (obs.chainDigest !== expectedDigest) {
11681
+ return { valid: false, observations };
11682
+ }
11683
+ prevChainDigest = obs.chainDigest;
11684
+ }
11685
+ return { valid: true, observations };
11686
+ }
11687
+ function canonicalLedger(chains) {
11688
+ const validChains = [];
11689
+ for (const chain of chains) {
11690
+ const result = validateChain(chain);
11691
+ if (result.valid && chain.length > 0) {
11692
+ validChains.push(chain);
11693
+ }
11694
+ }
11695
+ validChains.sort((a, b) => {
11696
+ const aDigest = a[0].chainDigest;
11697
+ const bDigest = b[0].chainDigest;
11698
+ return aDigest < bDigest ? -1 : aDigest > bDigest ? 1 : 0;
11699
+ });
11700
+ const lines = [];
11701
+ for (const chain of validChains) {
11702
+ for (const obs of chain) {
11703
+ lines.push(JSON.stringify(obs));
11704
+ }
11705
+ }
11706
+ return sha256Hex(lines.join(`
11707
+ `));
11708
+ }
11709
+
11498
11710
  // src/storage/graph/soakRecorder.ts
11499
11711
  var SOAK_EVIDENCE_DIR = ".opencode/soak";
11500
11712
  var SOAK_RECORDER_BASE_DIR = ".opencode";
11501
11713
  var SOAK_RECORDER_ID_FILENAME = "soak-recorder-id";
11502
11714
  var SOAK_RECORDER_ID_PATH = `${SOAK_RECORDER_BASE_DIR}/${SOAK_RECORDER_ID_FILENAME}`;
11715
+ async function readAllChains(ports, evidenceDir) {
11716
+ if (!await ports.fs.exists(evidenceDir)) {
11717
+ return { chains: [], rejectedLines: 0, rejections: [] };
11718
+ }
11719
+ const files = await ports.fs.readDir(evidenceDir);
11720
+ const chains = [];
11721
+ const rejections = [];
11722
+ for (const file of files) {
11723
+ if (!file.endsWith(".jsonl")) {
11724
+ continue;
11725
+ }
11726
+ const content = await ports.fs.readFile(`${evidenceDir}/${file}`);
11727
+ const lines = content.split(`
11728
+ `);
11729
+ const observations = [];
11730
+ for (const [index, line] of lines.entries()) {
11731
+ if (line.trim().length === 0) {
11732
+ continue;
11733
+ }
11734
+ const lineNumber = index + 1;
11735
+ let rawParsed;
11736
+ try {
11737
+ rawParsed = JSON.parse(line);
11738
+ } catch {
11739
+ rejections.push({ file, lineNumber, detail: "invalid JSON" });
11740
+ continue;
11741
+ }
11742
+ const parsed = SoakObservationSchema.safeParse(rawParsed);
11743
+ if (parsed.success) {
11744
+ observations.push(parsed.data);
11745
+ } else {
11746
+ rejections.push({
11747
+ file,
11748
+ lineNumber,
11749
+ detail: formatSoakObservationIssues(parsed.error.issues)
11750
+ });
11751
+ }
11752
+ }
11753
+ if (observations.length > 0) {
11754
+ chains.push(observations);
11755
+ }
11756
+ }
11757
+ return { chains, rejectedLines: rejections.length, rejections };
11758
+ }
11759
+ function formatSoakObservationIssues(issues) {
11760
+ return issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
11761
+ }
11503
11762
 
11504
11763
  // src/commands/frontierCatalog.ts
11505
11764
  function weightedCost(inputUSD, outputUSD) {
@@ -13272,8 +13531,7 @@ async function runAgents(deps, configPath, opencodeConfigPaths, configResolution
13272
13531
  async function runGraph(positionals, deps, configPath, configResolution) {
13273
13532
  const sub = positionals[1];
13274
13533
  if (sub === "status") {
13275
- const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
13276
- return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
13534
+ return runGraphStatus(deps, configPath, configResolution);
13277
13535
  }
13278
13536
  if (sub === "cancel") {
13279
13537
  return runGraphCancel(positionals, deps, configPath, configResolution);
@@ -13328,7 +13586,9 @@ function renderGraphOutcome(outcome) {
13328
13586
  async function resolveOperatorGraphPolicy(config, operator) {
13329
13587
  let graph = resolveGraphConfig(config, "file");
13330
13588
  if (isGateApplicable(graph.effectiveMode)) {
13331
- graph = applyGraphGate(graph, await operator.evaluateCutoverGate());
13589
+ const evaluation = await operator.evaluateCutoverGate(config);
13590
+ const gate = evaluation.kind === "evaluated" ? evaluation.gate : { allowed: false, violations: [] };
13591
+ graph = applyGraphGate(graph, gate);
13332
13592
  }
13333
13593
  return {
13334
13594
  graph,
@@ -13346,6 +13606,32 @@ function checkOperatorGate(policy) {
13346
13606
  stdout: renderLegacyGraphReason(decision2)
13347
13607
  };
13348
13608
  }
13609
+ async function runGraphStatus(deps, configPath, configResolution) {
13610
+ const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
13611
+ if (!isGateApplicable(snapshot.effectiveMode)) {
13612
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
13613
+ }
13614
+ const operator = deps.graphOperator;
13615
+ if (operator === undefined) {
13616
+ const gate2 = {
13617
+ kind: "unevaluable",
13618
+ reason: "graph operator ports are not configured"
13619
+ };
13620
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
13621
+ }
13622
+ const config = await deps.loadConfig(configPath, configResolution);
13623
+ const evaluation = await operator.evaluateCutoverGate(config);
13624
+ if (evaluation.kind === "unevaluable") {
13625
+ const gate2 = {
13626
+ kind: "unevaluable",
13627
+ reason: evaluation.reason
13628
+ };
13629
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
13630
+ }
13631
+ const resolved = applyGraphGate(resolveGraphConfig(config, "file"), evaluation.gate);
13632
+ const gate = resolved.gateDenied === true ? { kind: "denied", violations: resolved.gateViolations ?? [] } : { kind: "passed" };
13633
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate) };
13634
+ }
13349
13635
  async function runGraphCancel(positionals, deps, configPath, configResolution) {
13350
13636
  const runID = positionals[2];
13351
13637
  if (runID === undefined || runID.trim() === "") {
@@ -13792,6 +14078,9 @@ async function runCli(argv, deps) {
13792
14078
  const summary = summarizeQueueWaits(queueEvents);
13793
14079
  queueWait = summary !== undefined ? { summary } : {};
13794
14080
  }
14081
+ const rosterMissing = deps.loadRosterForDoctor !== undefined && roster === undefined && rosterLoadError === undefined;
14082
+ const orchestratorAgentName = ORCHESTRATOR_AGENT_PATH.slice(ORCHESTRATOR_AGENT_PATH.lastIndexOf("/") + 1).replace(/\.md$/, "");
14083
+ const orchestratorAgentMissing = !agentFiles.some((file) => file.name === orchestratorAgentName);
13795
14084
  return {
13796
14085
  exitCode: 0,
13797
14086
  stdout: renderDoctor({
@@ -13805,6 +14094,8 @@ async function runCli(argv, deps) {
13805
14094
  ...rosterAudit !== undefined ? { rosterAudit } : {},
13806
14095
  ...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
13807
14096
  ...rosterLoadError !== undefined ? { rosterLoadError } : {},
14097
+ ...rosterMissing ? { rosterMissing } : {},
14098
+ ...orchestratorAgentMissing ? { orchestratorAgentMissing } : {},
13808
14099
  ...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
13809
14100
  ...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
13810
14101
  ...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
@@ -14258,472 +14549,1040 @@ function planStartupHygieneMigration(exists) {
14258
14549
  return planLegacyMigration(exists).filter((move) => move.portable === false && move.relocation === "move" && move.from.startsWith(".opencode/openteam/"));
14259
14550
  }
14260
14551
 
14261
- // src/local/cacheAdapter.ts
14262
- import * as fs3 from "node:fs";
14263
- import * as os from "node:os";
14264
- import * as path3 from "node:path";
14265
- var SCOPE_PREFIX2 = "@jmanuelcorral/openteam";
14266
- var SCOPE_SEGMENTS = SCOPE_PREFIX2.split("/");
14267
- function resolveCacheRoot(override) {
14268
- if (override !== undefined && override.trim() !== "") {
14269
- return override;
14552
+ // src/index.ts
14553
+ import {
14554
+ tool as tool2
14555
+ } from "@opencode-ai/plugin";
14556
+
14557
+ // src/graph/certificate.ts
14558
+ import { z as z20 } from "zod";
14559
+
14560
+ // src/contract/opencode.ts
14561
+ var SUPPORTED_OPENCODE_VERSIONS = [
14562
+ "1.17.13",
14563
+ "1.18.18",
14564
+ "1.18.19"
14565
+ ];
14566
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
14567
+ function parseSemver(version) {
14568
+ const normalized = version.trim().replace(/^[=^~v]+/, "");
14569
+ const match = SEMVER_RE.exec(normalized);
14570
+ if (match === null) {
14571
+ throw new Error(`invalid semver: "${version}"`);
14270
14572
  }
14271
- const base = process.env.XDG_CACHE_HOME ?? path3.join(os.homedir(), ".cache");
14272
- return path3.join(base, "opencode", "packages");
14573
+ return {
14574
+ major: Number(match[1]),
14575
+ minor: Number(match[2]),
14576
+ patch: Number(match[3])
14577
+ };
14273
14578
  }
14274
- function readJsonSafe(filePath) {
14275
- try {
14276
- const text2 = fs3.readFileSync(filePath, "utf8");
14277
- const parsed = JSON.parse(text2);
14278
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
14279
- return parsed;
14280
- }
14281
- return;
14282
- } catch {
14283
- return;
14579
+ function isCertificateVersionCompatible(certifiedVersion, liveVersion) {
14580
+ if (certifiedVersion === liveVersion) {
14581
+ return true;
14284
14582
  }
14285
- }
14286
- function extractVersion(pkg) {
14287
- if (pkg === undefined)
14288
- return "";
14289
- const v = pkg.version;
14290
- return typeof v === "string" ? v : "";
14291
- }
14292
- function readDirectoryEntries(dir) {
14583
+ let certified;
14584
+ let live;
14293
14585
  try {
14294
- return fs3.readdirSync(dir, { withFileTypes: true });
14586
+ certified = parseSemver(certifiedVersion);
14587
+ live = parseSemver(liveVersion);
14295
14588
  } catch {
14296
- return [];
14589
+ return false;
14297
14590
  }
14591
+ if (certified.major !== live.major || certified.minor !== live.minor) {
14592
+ return false;
14593
+ }
14594
+ return live.patch >= certified.patch;
14298
14595
  }
14299
- function lstatPlainDirectory(dir) {
14300
- try {
14301
- const stat2 = fs3.lstatSync(dir);
14302
- if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
14303
- return;
14304
- }
14305
- return stat2;
14306
- } catch {
14307
- return;
14596
+
14597
+ // src/graph/certificate.ts
14598
+ var SHADOW_CERTIFICATE_MIN_RUNS = 1000;
14599
+ var DivergenceCodeSchema = z20.enum([
14600
+ "status",
14601
+ "order",
14602
+ "roles",
14603
+ "models",
14604
+ "fixes",
14605
+ "outcome"
14606
+ ]);
14607
+ var ProvenanceSchema = z20.enum([
14608
+ "recomputed",
14609
+ "compiled",
14610
+ "validated",
14611
+ "carried"
14612
+ ]);
14613
+ var EvidenceSchema = z20.object({
14614
+ opencodeVersion: z20.string().min(1),
14615
+ parity: z20.object({
14616
+ runs: z20.number().int().nonnegative(),
14617
+ match: z20.number().int().nonnegative(),
14618
+ divergent: z20.number().int().nonnegative(),
14619
+ inconclusive: z20.number().int().nonnegative(),
14620
+ divergences: z20.array(DivergenceCodeSchema)
14621
+ }).strict(),
14622
+ legacy: z20.object({
14623
+ observed: z20.number().int().nonnegative(),
14624
+ duplicated: z20.number().int().nonnegative()
14625
+ }).strict(),
14626
+ privacy: z20.object({
14627
+ surfacesScanned: z20.number().int().nonnegative(),
14628
+ rawFindings: z20.number().int().nonnegative()
14629
+ }).strict(),
14630
+ overhead: z20.object({
14631
+ p95Millis: z20.number().nonnegative(),
14632
+ budgetMillis: z20.number().positive(),
14633
+ samples: z20.number().int().nonnegative()
14634
+ }).strict(),
14635
+ provenance: z20.record(DivergenceCodeSchema, ProvenanceSchema)
14636
+ }).strict();
14637
+ var CertificateSchema = z20.object({
14638
+ version: z20.literal(1),
14639
+ certificate: z20.literal("graph-shadow"),
14640
+ opencodeVersion: z20.string().min(1),
14641
+ evidence: EvidenceSchema,
14642
+ verdict: z20.enum(["pass", "fail"]),
14643
+ failedGates: z20.array(z20.enum([
14644
+ "sample",
14645
+ "parity",
14646
+ "one-legacy-execution",
14647
+ "privacy",
14648
+ "overhead"
14649
+ ])),
14650
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14651
+ }).strict();
14652
+
14653
+ class ShadowCertificateError extends Error {
14654
+ code;
14655
+ detail;
14656
+ constructor(code, detail) {
14657
+ super(`${code}: ${detail}`);
14658
+ this.name = "ShadowCertificateError";
14659
+ this.code = code;
14660
+ this.detail = detail;
14308
14661
  }
14309
14662
  }
14310
- function findEntryDirs(cacheRoot) {
14311
- const candidates = [];
14312
- for (const topEntry of readDirectoryEntries(cacheRoot)) {
14313
- const topPath = path3.join(cacheRoot, topEntry.name);
14314
- const topStat = lstatPlainDirectory(topPath);
14315
- if (topStat === undefined) {
14316
- continue;
14317
- }
14318
- if (!topEntry.name.startsWith("@")) {
14319
- candidates.push({
14320
- specDir: topEntry.name,
14321
- absolutePath: topPath,
14322
- stat: topStat
14323
- });
14324
- continue;
14325
- }
14326
- for (const packageEntry of readDirectoryEntries(topPath)) {
14327
- const packagePath = path3.join(topPath, packageEntry.name);
14328
- const packageStat = lstatPlainDirectory(packagePath);
14329
- if (packageStat === undefined) {
14330
- continue;
14331
- }
14332
- candidates.push({
14333
- specDir: `${topEntry.name}/${packageEntry.name}`,
14334
- absolutePath: packagePath,
14335
- stat: packageStat
14336
- });
14337
- }
14663
+ function evaluateGates(evidence) {
14664
+ const failed = [];
14665
+ if (evidence.parity.runs < SHADOW_CERTIFICATE_MIN_RUNS) {
14666
+ failed.push("sample");
14338
14667
  }
14339
- return candidates.sort((a, b) => a.specDir.localeCompare(b.specDir));
14668
+ if (evidence.parity.divergent > 0 || evidence.parity.inconclusive > 0 || evidence.parity.divergences.length > 0 || evidence.parity.match !== evidence.parity.runs) {
14669
+ failed.push("parity");
14670
+ }
14671
+ if (evidence.legacy.duplicated > 0 || evidence.legacy.observed < 1) {
14672
+ failed.push("one-legacy-execution");
14673
+ }
14674
+ if (evidence.privacy.rawFindings > 0 || evidence.privacy.surfacesScanned < 1) {
14675
+ failed.push("privacy");
14676
+ }
14677
+ if (evidence.overhead.samples < 1 || evidence.overhead.p95Millis > evidence.overhead.budgetMillis) {
14678
+ failed.push("overhead");
14679
+ }
14680
+ return failed;
14340
14681
  }
14341
- async function listEntries(cacheRoot) {
14342
- const entries = [];
14343
- for (const candidate of findEntryDirs(cacheRoot)) {
14344
- const { specDir, absolutePath, stat: stat2 } = candidate;
14345
- if (!specDir.startsWith(SCOPE_PREFIX2))
14346
- continue;
14347
- const mtimeIso = stat2.mtime.toISOString();
14348
- const entryPkg = readJsonSafe(path3.join(absolutePath, "package.json"));
14349
- const deps = entryPkg?.dependencies;
14350
- let pinnedVersion = "";
14351
- if (deps !== null && typeof deps === "object" && !Array.isArray(deps)) {
14352
- const depsObj = deps;
14353
- const pinned = depsObj[SCOPE_PREFIX2];
14354
- pinnedVersion = typeof pinned === "string" ? pinned : "";
14682
+ function shadowCertificateBindingDigest(evidence) {
14683
+ return sha256Hex(JSON.stringify({
14684
+ opencodeVersion: evidence.opencodeVersion,
14685
+ parity: {
14686
+ runs: evidence.parity.runs,
14687
+ match: evidence.parity.match,
14688
+ divergent: evidence.parity.divergent,
14689
+ inconclusive: evidence.parity.inconclusive,
14690
+ divergences: [...evidence.parity.divergences].sort()
14691
+ },
14692
+ legacy: {
14693
+ observed: evidence.legacy.observed,
14694
+ duplicated: evidence.legacy.duplicated
14695
+ },
14696
+ privacy: {
14697
+ surfacesScanned: evidence.privacy.surfacesScanned,
14698
+ rawFindings: evidence.privacy.rawFindings
14699
+ },
14700
+ overhead: {
14701
+ budgetMillis: evidence.overhead.budgetMillis,
14702
+ samples: evidence.overhead.samples
14703
+ },
14704
+ provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
14705
+ }));
14706
+ }
14707
+ function canonicalCertificate(certificate) {
14708
+ const evidence = certificate.evidence;
14709
+ return JSON.stringify({
14710
+ version: certificate.version,
14711
+ certificate: certificate.certificate,
14712
+ opencodeVersion: certificate.opencodeVersion,
14713
+ verdict: certificate.verdict,
14714
+ failedGates: [...certificate.failedGates],
14715
+ evidence: {
14716
+ opencodeVersion: evidence.opencodeVersion,
14717
+ parity: {
14718
+ runs: evidence.parity.runs,
14719
+ match: evidence.parity.match,
14720
+ divergent: evidence.parity.divergent,
14721
+ inconclusive: evidence.parity.inconclusive,
14722
+ divergences: [...evidence.parity.divergences].sort()
14723
+ },
14724
+ legacy: {
14725
+ observed: evidence.legacy.observed,
14726
+ duplicated: evidence.legacy.duplicated
14727
+ },
14728
+ privacy: {
14729
+ surfacesScanned: evidence.privacy.surfacesScanned,
14730
+ rawFindings: evidence.privacy.rawFindings
14731
+ },
14732
+ overhead: {
14733
+ p95Millis: evidence.overhead.p95Millis,
14734
+ budgetMillis: evidence.overhead.budgetMillis,
14735
+ samples: evidence.overhead.samples
14736
+ },
14737
+ provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
14355
14738
  }
14356
- const installedPkg = readJsonSafe(path3.join(absolutePath, "node_modules", ...SCOPE_SEGMENTS, "package.json"));
14357
- const installedVersion = extractVersion(installedPkg);
14358
- entries.push({
14359
- specDir,
14360
- absolutePath,
14361
- pinnedVersion,
14362
- installedVersion,
14363
- mtimeIso
14364
- });
14365
- }
14366
- return entries;
14739
+ });
14367
14740
  }
14368
- async function deleteEntry(entryPath) {
14369
- fs3.rmSync(entryPath, { recursive: true, force: true });
14741
+ function sameGates(a, b) {
14742
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
14370
14743
  }
14371
- async function isReparsePath(entryPath) {
14372
- try {
14373
- const stat2 = await fs3.promises.lstat(entryPath);
14374
- return stat2.isSymbolicLink();
14375
- } catch {
14376
- return true;
14744
+ function parseShadowCertificate(value, opencodeVersion) {
14745
+ const parsed = CertificateSchema.safeParse(value);
14746
+ if (!parsed.success) {
14747
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
14748
+ throw new ShadowCertificateError("invalid-certificate", detail);
14749
+ }
14750
+ const certificate = parsed.data;
14751
+ const failedGates = evaluateGates(certificate.evidence);
14752
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
14753
+ if (certificate.verdict !== verdict || !sameGates(certificate.failedGates, failedGates)) {
14754
+ throw new ShadowCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
14377
14755
  }
14756
+ const { digest, ...unsigned } = certificate;
14757
+ if (sha256Hex(canonicalCertificate(unsigned)) !== digest) {
14758
+ throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
14759
+ }
14760
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
14761
+ throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
14762
+ }
14763
+ return certificate;
14378
14764
  }
14379
- var realCacheAdapter = {
14380
- resolveCacheRoot,
14381
- listEntries,
14382
- deleteEntry,
14383
- isReparsePath
14384
- };
14765
+ var ReleaseDeterminismSchema = z20.object({ runs: z20.number().int().nonnegative(), allMatch: z20.boolean() }).strict();
14766
+ var ReleaseCrashRecoverySchema = z20.object({
14767
+ scenarios: z20.number().int().nonnegative(),
14768
+ allConverge: z20.boolean()
14769
+ }).strict();
14770
+ var ReleaseReplayEquivalenceSchema = z20.object({
14771
+ checks: z20.number().int().nonnegative(),
14772
+ allEquivalent: z20.boolean()
14773
+ }).strict();
14774
+ var ReleasePrivacySchema = z20.object({
14775
+ surfacesScanned: z20.number().int().nonnegative(),
14776
+ rawFindings: z20.number().int().nonnegative()
14777
+ }).strict();
14778
+ var ReleaseContractSchema = z20.object({
14779
+ roundTrips: z20.number().int().nonnegative(),
14780
+ allSettled: z20.boolean()
14781
+ }).strict();
14782
+ var ReleaseHealthSchema = z20.object({
14783
+ samples: z20.number().int().nonnegative(),
14784
+ bounded: z20.boolean(),
14785
+ idempotent: z20.boolean()
14786
+ }).strict();
14787
+ var ReleasePerformanceSchema = z20.object({
14788
+ p95Millis: z20.number().nonnegative(),
14789
+ budgetMillis: z20.number().positive(),
14790
+ samples: z20.number().int().nonnegative()
14791
+ }).strict();
14792
+ var ReleaseEvidenceSchema = z20.object({
14793
+ opencodeVersion: z20.string().min(1),
14794
+ platform: z20.enum(["linux", "win32"]),
14795
+ shadowCertificateDigest: z20.string().regex(/^[0-9a-f]{64}$/),
14796
+ determinism: ReleaseDeterminismSchema,
14797
+ crashRecovery: ReleaseCrashRecoverySchema,
14798
+ replayEquivalence: ReleaseReplayEquivalenceSchema,
14799
+ privacy: ReleasePrivacySchema,
14800
+ contract: ReleaseContractSchema,
14801
+ health: ReleaseHealthSchema,
14802
+ performance: ReleasePerformanceSchema
14803
+ }).strict();
14804
+ var ReleaseCertificateSchema = z20.object({
14805
+ version: z20.literal(2),
14806
+ certificate: z20.literal("graph-release"),
14807
+ platform: z20.enum(["linux", "win32"]),
14808
+ opencodeVersion: z20.string().min(1),
14809
+ shadowCertificateDigest: z20.string().regex(/^[0-9a-f]{64}$/),
14810
+ evidence: ReleaseEvidenceSchema,
14811
+ verdict: z20.enum(["pass", "fail"]),
14812
+ failedGates: z20.array(z20.enum([
14813
+ "shadow-valid",
14814
+ "determinism",
14815
+ "crash-recovery",
14816
+ "replay-equivalence",
14817
+ "privacy",
14818
+ "contract",
14819
+ "health",
14820
+ "performance"
14821
+ ])),
14822
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14823
+ }).strict();
14385
14824
 
14386
- // src/memory/rank.ts
14387
- var DAY_MS = 86400000;
14388
- var DEFAULT_RANK_SIGNALS = {
14389
- similarityWeight: 1,
14390
- recencyWeight: 0.2,
14391
- accessWeight: 0.1,
14392
- recencyHalfLifeMs: 14 * DAY_MS
14393
- };
14394
- function recencyTimestamp(record) {
14395
- if (record.kind === "preference" && record.lastAccessedAt !== null) {
14396
- return record.lastAccessedAt;
14825
+ class ReleaseCertificateError extends Error {
14826
+ code;
14827
+ detail;
14828
+ constructor(code, detail) {
14829
+ super(`${code}: ${detail}`);
14830
+ this.name = "ReleaseCertificateError";
14831
+ this.code = code;
14832
+ this.detail = detail;
14397
14833
  }
14398
- return record.createdAt;
14399
- }
14400
- function recencyScore(record, signals) {
14401
- const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
14402
- return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
14403
14834
  }
14404
- function accessScore(record) {
14405
- if (record.kind !== "preference") {
14406
- return 0;
14835
+ function evaluateReleaseGates(evidence, expectedShadowDigest) {
14836
+ const failed = [];
14837
+ if (evidence.shadowCertificateDigest !== expectedShadowDigest) {
14838
+ failed.push("shadow-valid");
14407
14839
  }
14408
- return 1 - 1 / (1 + record.accessCount);
14409
- }
14410
- function scoreCandidate(candidate, signals) {
14411
- return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
14840
+ if (evidence.determinism.runs < 1 || !evidence.determinism.allMatch) {
14841
+ failed.push("determinism");
14842
+ }
14843
+ if (evidence.crashRecovery.scenarios < 1 || !evidence.crashRecovery.allConverge) {
14844
+ failed.push("crash-recovery");
14845
+ }
14846
+ if (evidence.replayEquivalence.checks < 1 || !evidence.replayEquivalence.allEquivalent) {
14847
+ failed.push("replay-equivalence");
14848
+ }
14849
+ if (evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
14850
+ failed.push("privacy");
14851
+ }
14852
+ if (evidence.contract.roundTrips < 1 || !evidence.contract.allSettled) {
14853
+ failed.push("contract");
14854
+ }
14855
+ if (evidence.health.samples < 1 || !evidence.health.bounded || !evidence.health.idempotent) {
14856
+ failed.push("health");
14857
+ }
14858
+ if (evidence.performance.samples < 1 || evidence.performance.p95Millis > evidence.performance.budgetMillis) {
14859
+ failed.push("performance");
14860
+ }
14861
+ return failed;
14412
14862
  }
14413
- function rankRecall(candidates, signals) {
14414
- return candidates.map((candidate) => ({
14415
- ...candidate,
14416
- score: scoreCandidate(candidate, signals)
14417
- })).sort((a, b) => {
14418
- if (b.score !== a.score) {
14419
- return b.score - a.score;
14863
+ function canonicalReleaseCertificate(certificate) {
14864
+ const ev = certificate.evidence;
14865
+ return JSON.stringify({
14866
+ version: certificate.version,
14867
+ certificate: certificate.certificate,
14868
+ platform: certificate.platform,
14869
+ opencodeVersion: certificate.opencodeVersion,
14870
+ shadowCertificateDigest: certificate.shadowCertificateDigest,
14871
+ verdict: certificate.verdict,
14872
+ failedGates: [...certificate.failedGates],
14873
+ evidence: {
14874
+ opencodeVersion: ev.opencodeVersion,
14875
+ platform: ev.platform,
14876
+ shadowCertificateDigest: ev.shadowCertificateDigest,
14877
+ determinism: {
14878
+ runs: ev.determinism.runs,
14879
+ allMatch: ev.determinism.allMatch
14880
+ },
14881
+ crashRecovery: {
14882
+ scenarios: ev.crashRecovery.scenarios,
14883
+ allConverge: ev.crashRecovery.allConverge
14884
+ },
14885
+ replayEquivalence: {
14886
+ checks: ev.replayEquivalence.checks,
14887
+ allEquivalent: ev.replayEquivalence.allEquivalent
14888
+ },
14889
+ privacy: {
14890
+ surfacesScanned: ev.privacy.surfacesScanned,
14891
+ rawFindings: ev.privacy.rawFindings
14892
+ },
14893
+ contract: {
14894
+ roundTrips: ev.contract.roundTrips,
14895
+ allSettled: ev.contract.allSettled
14896
+ },
14897
+ health: {
14898
+ samples: ev.health.samples,
14899
+ bounded: ev.health.bounded,
14900
+ idempotent: ev.health.idempotent
14901
+ },
14902
+ performance: {
14903
+ p95Millis: ev.performance.p95Millis,
14904
+ budgetMillis: ev.performance.budgetMillis,
14905
+ samples: ev.performance.samples
14906
+ }
14420
14907
  }
14421
- return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
14422
14908
  });
14423
14909
  }
14424
-
14425
- // src/memory/temporal.ts
14426
- function isTransactionActiveAt(record, asOf) {
14427
- return record.invalidatedAt === null || record.invalidatedAt > asOf;
14910
+ function sameReleaseGates(a, b) {
14911
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
14428
14912
  }
14429
- function isValidAt(record, asOf) {
14430
- if (record.validFrom > asOf) {
14431
- return false;
14913
+ function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
14914
+ if (typeof value === "object" && value !== null && "version" in value && value.version === 1) {
14915
+ throw new ReleaseCertificateError("invalid-certificate", "certificate is schema version 1 (carried a removed 'migration' gate); " + "rebuild the release certificate against the current schema");
14432
14916
  }
14433
- return record.validUntil === null || record.validUntil > asOf;
14434
- }
14435
- function isActiveAt(record, asOf) {
14436
- return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
14437
- }
14438
- function filterActive(records, asOf) {
14439
- return records.filter((record) => isActiveAt(record, asOf));
14440
- }
14441
- function coalesceWindow(a, b) {
14442
- const validFrom = Math.min(a.validFrom, b.validFrom);
14443
- const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
14444
- return { validFrom, validUntil };
14917
+ const parsed = ReleaseCertificateSchema.safeParse(value);
14918
+ if (!parsed.success) {
14919
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
14920
+ throw new ReleaseCertificateError("invalid-certificate", detail);
14921
+ }
14922
+ const certificate = parsed.data;
14923
+ const inconsistencies = [];
14924
+ if (certificate.platform !== certificate.evidence.platform) {
14925
+ inconsistencies.push("platform");
14926
+ }
14927
+ if (certificate.opencodeVersion !== certificate.evidence.opencodeVersion) {
14928
+ inconsistencies.push("opencodeVersion");
14929
+ }
14930
+ if (certificate.shadowCertificateDigest !== certificate.evidence.shadowCertificateDigest) {
14931
+ inconsistencies.push("shadowCertificateDigest");
14932
+ }
14933
+ if (inconsistencies.length > 0) {
14934
+ throw new ReleaseCertificateError("field-inconsistency", `top-level/evidence divergence: ${inconsistencies.join(", ")}`);
14935
+ }
14936
+ const failedGates = evaluateReleaseGates(certificate.evidence, expectedShadowDigest);
14937
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
14938
+ if (certificate.verdict !== verdict || !sameReleaseGates(certificate.failedGates, failedGates)) {
14939
+ throw new ReleaseCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
14940
+ }
14941
+ const { digest, ...unsigned } = certificate;
14942
+ if (sha256Hex(canonicalReleaseCertificate(unsigned)) !== digest) {
14943
+ throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
14944
+ }
14945
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
14946
+ throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
14947
+ }
14948
+ return certificate;
14445
14949
  }
14446
-
14447
- // src/memory/types.ts
14448
- import { z as z19 } from "zod";
14449
- var SHARED_OWNER_KEY = "*";
14450
- var OwnerKeySchema = z19.string().min(1);
14451
- var MemoryKindSchema = z19.enum(["fact", "preference", "entity"]);
14452
- var MemoryBaseSchema = z19.object({
14453
- id: z19.string().min(1),
14454
- ownerKey: OwnerKeySchema,
14455
- confidence: z19.number().min(0).max(1),
14456
- validFrom: z19.number().finite(),
14457
- validUntil: z19.number().finite().nullable().default(null),
14458
- createdAt: z19.number().finite(),
14459
- invalidatedAt: z19.number().finite().nullable().default(null),
14460
- sourceHash: z19.string().min(1),
14461
- supersededBy: z19.string().min(1).nullable().default(null)
14462
- }).strict();
14463
- var FactSchema = MemoryBaseSchema.extend({
14464
- kind: z19.literal("fact"),
14465
- subject: z19.string().min(1),
14466
- predicate: z19.string().min(1),
14467
- object: z19.string().min(1),
14468
- category: z19.string().min(1).nullable().default(null)
14469
- });
14470
- var PreferenceSchema = MemoryBaseSchema.extend({
14471
- kind: z19.literal("preference"),
14472
- category: z19.string().min(1),
14473
- preference: z19.string().min(1),
14474
- context: z19.string().min(1).nullable().default(null),
14475
- lastAccessedAt: z19.number().finite().nullable().default(null),
14476
- accessCount: z19.number().int().min(0).default(0)
14477
- });
14478
- var EntitySchema = MemoryBaseSchema.extend({
14479
- kind: z19.literal("entity"),
14480
- canonicalName: z19.string().min(1),
14481
- type: z19.string().min(1),
14482
- aliases: z19.array(z19.string().min(1)).default([])
14483
- });
14484
- var RelationSchema = MemoryBaseSchema.extend({
14485
- kind: z19.literal("relation"),
14486
- from: z19.string().min(1),
14487
- to: z19.string().min(1),
14488
- predicate: z19.string().min(1),
14489
- annotation: z19.string().min(1).nullable().default(null)
14490
- });
14491
- var MemoryRecordSchema = z19.discriminatedUnion("kind", [
14492
- FactSchema,
14493
- PreferenceSchema,
14494
- EntitySchema,
14495
- RelationSchema
14950
+ var ShadowDivergenceCodeSchema = z20.enum([
14951
+ "status",
14952
+ "order",
14953
+ "roles",
14954
+ "models",
14955
+ "fixes",
14956
+ "outcome"
14496
14957
  ]);
14497
- var RecallQuerySchema = z19.object({
14498
- ownerKey: OwnerKeySchema,
14499
- kinds: z19.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
14500
- asOf: z19.number().finite().nullable().default(null),
14501
- limit: z19.number().int().positive().default(8),
14502
- minSimilarity: z19.number().min(0).max(1).default(0.2),
14503
- includeShared: z19.boolean().default(true)
14958
+ var RECOMPUTED_DIVERGENCE_CODES = ["fixes"];
14959
+ var RecomputedDivergenceCodeSchema = z20.enum(RECOMPUTED_DIVERGENCE_CODES);
14960
+ var SoakPolicySchema = z20.object({
14961
+ minimumGenuineObservations: z20.number().int().positive(),
14962
+ minimumDistinctDays: z20.number().int().positive(),
14963
+ requireGenuineUsageOnAllPlatforms: z20.boolean(),
14964
+ supportedOpencodeVersions: z20.array(z20.string().min(1)).min(1),
14965
+ criticalDivergenceCodes: z20.array(RecomputedDivergenceCodeSchema).min(1)
14966
+ }).strict();
14967
+ var DEFAULT_SOAK_POLICY = {
14968
+ minimumGenuineObservations: 100,
14969
+ minimumDistinctDays: 14,
14970
+ requireGenuineUsageOnAllPlatforms: true,
14971
+ supportedOpencodeVersions: [...SUPPORTED_OPENCODE_VERSIONS],
14972
+ criticalDivergenceCodes: ["fixes"]
14973
+ };
14974
+ var SoakPlatformEnumSchema = z20.enum(["linux", "win32", "darwin"]);
14975
+ var SoakPrivacyPairEvidenceSchema = z20.object({
14976
+ surfacesScanned: z20.number().int().nonnegative(),
14977
+ rawFindings: z20.number().int().nonnegative()
14978
+ }).strict();
14979
+ var SoakDuplicateEffectPairEvidenceSchema = z20.object({
14980
+ effectsExamined: z20.number().int().nonnegative(),
14981
+ duplicatesFound: z20.number().int().nonnegative()
14982
+ }).strict();
14983
+ var SoakModelVerificationPairEvidenceSchema = z20.object({
14984
+ nodesChecked: z20.number().int().nonnegative(),
14985
+ unverified: z20.number().int().nonnegative(),
14986
+ mismatched: z20.number().int().nonnegative()
14987
+ }).strict();
14988
+ var SoakEvidenceSchema = z20.object({
14989
+ totalObservations: z20.number().int().nonnegative(),
14990
+ genuineUsageObservations: z20.number().int().nonnegative(),
14991
+ ciSyntheticObservations: z20.number().int().nonnegative(),
14992
+ recorderCount: z20.number().int().nonnegative(),
14993
+ ineligibleObservations: z20.number().int().nonnegative(),
14994
+ firstTimestamp: z20.string().datetime().nullable(),
14995
+ lastTimestamp: z20.string().datetime().nullable(),
14996
+ distinctDays: z20.number().int().nonnegative(),
14997
+ criticalDivergences: z20.number().int().nonnegative().nullable(),
14998
+ privacy: SoakPrivacyPairEvidenceSchema.nullable(),
14999
+ duplicateEffects: SoakDuplicateEffectPairEvidenceSchema.nullable(),
15000
+ modelVerification: SoakModelVerificationPairEvidenceSchema.nullable(),
15001
+ platformsGenuine: z20.array(SoakPlatformEnumSchema),
15002
+ platformsSynthetic: z20.array(SoakPlatformEnumSchema),
15003
+ opencodeVersionsObserved: z20.array(z20.string().min(1)),
15004
+ invalidChains: z20.number().int().nonnegative()
15005
+ }).strict();
15006
+ var SoakCertificateSchema = z20.object({
15007
+ version: z20.literal(1),
15008
+ certificate: z20.literal("graph-soak"),
15009
+ trustBoundary: z20.literal("contributors"),
15010
+ policy: SoakPolicySchema,
15011
+ evidence: SoakEvidenceSchema,
15012
+ verdict: z20.enum(["pass", "fail"]),
15013
+ failedGates: z20.array(z20.enum([
15014
+ "minimum-observations",
15015
+ "minimum-duration",
15016
+ "chain-integrity",
15017
+ "zero-critical-divergence",
15018
+ "zero-privacy-breach",
15019
+ "zero-duplicate-effect",
15020
+ "zero-model-mismatch",
15021
+ "os-diversity",
15022
+ "opencode-pin"
15023
+ ])),
15024
+ ledgerDigest: z20.string().regex(/^[0-9a-f]{64}$/),
15025
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14504
15026
  }).strict();
14505
15027
 
14506
- // src/memory/vector.ts
14507
- function dot(a, b) {
14508
- if (a.length !== b.length) {
14509
- throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
14510
- }
14511
- let sum = 0;
14512
- for (let i = 0;i < a.length; i += 1) {
14513
- sum += a[i] * b[i];
15028
+ class SoakCertificateError extends Error {
15029
+ code;
15030
+ detail;
15031
+ constructor(code, detail) {
15032
+ super(`${code}: ${detail}`);
15033
+ this.name = "SoakCertificateError";
15034
+ this.code = code;
15035
+ this.detail = detail;
14514
15036
  }
14515
- return sum;
14516
15037
  }
14517
- function norm(a) {
14518
- let sum = 0;
14519
- for (let i = 0;i < a.length; i += 1) {
14520
- const value = a[i];
14521
- sum += value * value;
15038
+ function evaluateSoakGates(evidence, policy) {
15039
+ const failed = [];
15040
+ if (evidence.genuineUsageObservations < policy.minimumGenuineObservations) {
15041
+ failed.push("minimum-observations");
14522
15042
  }
14523
- return Math.sqrt(sum);
15043
+ if (evidence.distinctDays < policy.minimumDistinctDays) {
15044
+ failed.push("minimum-duration");
15045
+ }
15046
+ if (evidence.invalidChains > 0) {
15047
+ failed.push("chain-integrity");
15048
+ }
15049
+ if (evidence.criticalDivergences === null || evidence.criticalDivergences > 0) {
15050
+ failed.push("zero-critical-divergence");
15051
+ }
15052
+ if (evidence.privacy === null || evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
15053
+ failed.push("zero-privacy-breach");
15054
+ }
15055
+ if (evidence.duplicateEffects === null || evidence.duplicateEffects.effectsExamined < 1 || evidence.duplicateEffects.duplicatesFound > 0) {
15056
+ failed.push("zero-duplicate-effect");
15057
+ }
15058
+ if (evidence.modelVerification === null || evidence.modelVerification.nodesChecked < 1 || evidence.modelVerification.mismatched > 0) {
15059
+ failed.push("zero-model-mismatch");
15060
+ }
15061
+ if (policy.requireGenuineUsageOnAllPlatforms) {
15062
+ if (!evidence.platformsGenuine.includes("linux") || !evidence.platformsGenuine.includes("win32")) {
15063
+ failed.push("os-diversity");
15064
+ }
15065
+ } else {
15066
+ const allPlatforms = new Set([
15067
+ ...evidence.platformsGenuine,
15068
+ ...evidence.platformsSynthetic
15069
+ ]);
15070
+ if (!allPlatforms.has("linux") || !allPlatforms.has("win32") || evidence.platformsGenuine.length < 1) {
15071
+ failed.push("os-diversity");
15072
+ }
15073
+ }
15074
+ const supported = new Set(policy.supportedOpencodeVersions);
15075
+ if (evidence.opencodeVersionsObserved.length === 0 || !evidence.opencodeVersionsObserved.every((v) => supported.has(v))) {
15076
+ failed.push("opencode-pin");
15077
+ }
15078
+ return failed;
14524
15079
  }
14525
- function cosineSimilarity(a, b) {
14526
- const denom = norm(a) * norm(b);
14527
- if (denom === 0) {
14528
- return 0;
15080
+ function canonicalSoakCertificate(certificate) {
15081
+ const ev = certificate.evidence;
15082
+ const pol = certificate.policy;
15083
+ return JSON.stringify({
15084
+ version: certificate.version,
15085
+ certificate: certificate.certificate,
15086
+ trustBoundary: certificate.trustBoundary,
15087
+ policy: {
15088
+ minimumGenuineObservations: pol.minimumGenuineObservations,
15089
+ minimumDistinctDays: pol.minimumDistinctDays,
15090
+ requireGenuineUsageOnAllPlatforms: pol.requireGenuineUsageOnAllPlatforms,
15091
+ supportedOpencodeVersions: [...pol.supportedOpencodeVersions].sort(),
15092
+ criticalDivergenceCodes: [...pol.criticalDivergenceCodes].sort()
15093
+ },
15094
+ verdict: certificate.verdict,
15095
+ failedGates: [...certificate.failedGates],
15096
+ ledgerDigest: certificate.ledgerDigest,
15097
+ evidence: {
15098
+ totalObservations: ev.totalObservations,
15099
+ genuineUsageObservations: ev.genuineUsageObservations,
15100
+ ciSyntheticObservations: ev.ciSyntheticObservations,
15101
+ recorderCount: ev.recorderCount,
15102
+ ineligibleObservations: ev.ineligibleObservations,
15103
+ firstTimestamp: ev.firstTimestamp,
15104
+ lastTimestamp: ev.lastTimestamp,
15105
+ distinctDays: ev.distinctDays,
15106
+ criticalDivergences: ev.criticalDivergences,
15107
+ privacy: ev.privacy,
15108
+ duplicateEffects: ev.duplicateEffects,
15109
+ modelVerification: ev.modelVerification,
15110
+ platformsGenuine: [...ev.platformsGenuine].sort(),
15111
+ platformsSynthetic: [...ev.platformsSynthetic].sort(),
15112
+ opencodeVersionsObserved: [...ev.opencodeVersionsObserved].sort(),
15113
+ invalidChains: ev.invalidChains
15114
+ }
15115
+ });
15116
+ }
15117
+ function sameSoakGates(a, b) {
15118
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
15119
+ }
15120
+ function sameSoakPolicy(a, b) {
15121
+ const aVersions = [...a.supportedOpencodeVersions].sort();
15122
+ const bVersions = [...b.supportedOpencodeVersions].sort();
15123
+ return a.minimumGenuineObservations === b.minimumGenuineObservations && a.minimumDistinctDays === b.minimumDistinctDays && a.requireGenuineUsageOnAllPlatforms === b.requireGenuineUsageOnAllPlatforms && aVersions.length === bVersions.length && aVersions.every((v, i) => v === bVersions[i]) && a.criticalDivergenceCodes.length === b.criticalDivergenceCodes.length && [...a.criticalDivergenceCodes].sort().every((code, i) => code === [...b.criticalDivergenceCodes].sort()[i]);
15124
+ }
15125
+ function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
15126
+ const parsed = SoakCertificateSchema.safeParse(value);
15127
+ if (!parsed.success) {
15128
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
15129
+ throw new SoakCertificateError("invalid-certificate", detail);
14529
15130
  }
14530
- return dot(a, b) / denom;
15131
+ const certificate = parsed.data;
15132
+ if (!sameSoakPolicy(certificate.policy, expectedPolicy)) {
15133
+ throw new SoakCertificateError("policy-mismatch", "embedded policy diverges from the expected policy");
15134
+ }
15135
+ const failedGates = evaluateSoakGates(certificate.evidence, certificate.policy);
15136
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
15137
+ if (certificate.verdict !== verdict || !sameSoakGates(certificate.failedGates, failedGates)) {
15138
+ throw new SoakCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
15139
+ }
15140
+ const { digest, ...unsigned } = certificate;
15141
+ if (sha256Hex(canonicalSoakCertificate(unsigned)) !== digest) {
15142
+ throw new SoakCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
15143
+ }
15144
+ if (certificate.ledgerDigest !== expectedLedgerDigest) {
15145
+ throw new SoakCertificateError("ledger-mismatch", "ledger digest does not match the evidence the caller holds");
15146
+ }
15147
+ return certificate;
14531
15148
  }
14532
15149
 
14533
- // src/memory/recall.ts
14534
- function matchesScope(record, query) {
14535
- if (record.ownerKey === query.ownerKey) {
14536
- return true;
15150
+ // src/local/cacheAdapter.ts
15151
+ import * as fs3 from "node:fs";
15152
+ import * as os from "node:os";
15153
+ import * as path3 from "node:path";
15154
+ var SCOPE_PREFIX2 = "@jmanuelcorral/openteam";
15155
+ var SCOPE_SEGMENTS = SCOPE_PREFIX2.split("/");
15156
+ function resolveCacheRoot(override) {
15157
+ if (override !== undefined && override.trim() !== "") {
15158
+ return override;
14537
15159
  }
14538
- return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
15160
+ const base = process.env.XDG_CACHE_HOME ?? path3.join(os.homedir(), ".cache");
15161
+ return path3.join(base, "opencode", "packages");
14539
15162
  }
14540
- function recall(records, queryEmbedding, query, deps) {
14541
- const asOf = query.asOf ?? deps.now;
14542
- const kinds = new Set(query.kinds);
14543
- const candidates = [];
14544
- for (const record of filterActive(records, asOf)) {
14545
- if (!kinds.has(record.kind) || !matchesScope(record, query)) {
14546
- continue;
14547
- }
14548
- const embedding = deps.embeddings.get(record.id);
14549
- if (embedding === undefined) {
14550
- continue;
14551
- }
14552
- const similarity = cosineSimilarity(queryEmbedding, embedding);
14553
- if (similarity < query.minSimilarity) {
14554
- continue;
15163
+ function readJsonSafe(filePath) {
15164
+ try {
15165
+ const text2 = fs3.readFileSync(filePath, "utf8");
15166
+ const parsed = JSON.parse(text2);
15167
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
15168
+ return parsed;
14555
15169
  }
14556
- candidates.push({ record, similarity });
15170
+ return;
15171
+ } catch {
15172
+ return;
14557
15173
  }
14558
- const signals = {
14559
- now: deps.now,
14560
- ...deps.signals ?? DEFAULT_RANK_SIGNALS
14561
- };
14562
- return rankRecall(candidates, signals).slice(0, query.limit);
14563
15174
  }
14564
-
14565
- // src/memory/traverse.ts
14566
- function inOwnerScope(record, ownerKey, includeShared) {
14567
- if (record.ownerKey === ownerKey) {
14568
- return true;
15175
+ function extractVersion(pkg) {
15176
+ if (pkg === undefined)
15177
+ return "";
15178
+ const v = pkg.version;
15179
+ return typeof v === "string" ? v : "";
15180
+ }
15181
+ function readDirectoryEntries(dir) {
15182
+ try {
15183
+ return fs3.readdirSync(dir, { withFileTypes: true });
15184
+ } catch {
15185
+ return [];
14569
15186
  }
14570
- return includeShared && record.ownerKey === SHARED_OWNER_KEY;
14571
15187
  }
14572
- function traverseRelations(records, query) {
14573
- const includeShared = query.includeShared ?? true;
14574
- const adjacency = new Map;
14575
- for (const record of records) {
14576
- if (record.kind !== "relation") {
14577
- continue;
15188
+ function lstatPlainDirectory(dir) {
15189
+ try {
15190
+ const stat2 = fs3.lstatSync(dir);
15191
+ if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
15192
+ return;
14578
15193
  }
14579
- if (!isActiveAt(record, query.asOf)) {
15194
+ return stat2;
15195
+ } catch {
15196
+ return;
15197
+ }
15198
+ }
15199
+ function findEntryDirs(cacheRoot) {
15200
+ const candidates = [];
15201
+ for (const topEntry of readDirectoryEntries(cacheRoot)) {
15202
+ const topPath = path3.join(cacheRoot, topEntry.name);
15203
+ const topStat = lstatPlainDirectory(topPath);
15204
+ if (topStat === undefined) {
14580
15205
  continue;
14581
15206
  }
14582
- if (!inOwnerScope(record, query.ownerKey, includeShared)) {
15207
+ if (!topEntry.name.startsWith("@")) {
15208
+ candidates.push({
15209
+ specDir: topEntry.name,
15210
+ absolutePath: topPath,
15211
+ stat: topStat
15212
+ });
14583
15213
  continue;
14584
15214
  }
14585
- const list = adjacency.get(record.from);
14586
- if (list === undefined) {
14587
- adjacency.set(record.from, [record]);
14588
- } else {
14589
- list.push(record);
14590
- }
14591
- }
14592
- const visited = new Set([query.seed]);
14593
- const nodes = [{ id: query.seed, depth: 0 }];
14594
- const edges = [];
14595
- let frontier = [query.seed];
14596
- for (let depth = 0;depth < query.maxDepth; depth += 1) {
14597
- const nextFrontier = [];
14598
- for (const nodeId of frontier) {
14599
- const outgoing = adjacency.get(nodeId) ?? [];
14600
- for (const edge of outgoing) {
14601
- if (visited.has(edge.to)) {
14602
- continue;
14603
- }
14604
- visited.add(edge.to);
14605
- const childDepth = depth + 1;
14606
- nodes.push({ id: edge.to, depth: childDepth });
14607
- edges.push({
14608
- from: edge.from,
14609
- to: edge.to,
14610
- predicate: edge.predicate,
14611
- depth: childDepth
14612
- });
14613
- nextFrontier.push(edge.to);
15215
+ for (const packageEntry of readDirectoryEntries(topPath)) {
15216
+ const packagePath = path3.join(topPath, packageEntry.name);
15217
+ const packageStat = lstatPlainDirectory(packagePath);
15218
+ if (packageStat === undefined) {
15219
+ continue;
14614
15220
  }
15221
+ candidates.push({
15222
+ specDir: `${topEntry.name}/${packageEntry.name}`,
15223
+ absolutePath: packagePath,
15224
+ stat: packageStat
15225
+ });
14615
15226
  }
14616
- frontier = nextFrontier;
14617
15227
  }
14618
- return { seed: query.seed, nodes, edges };
14619
- }
14620
- function seedNodeId(record) {
14621
- return record.kind === "entity" ? record.canonicalName : undefined;
15228
+ return candidates.sort((a, b) => a.specDir.localeCompare(b.specDir));
14622
15229
  }
14623
- function recallGraph(records, queryEmbedding, query, options, deps) {
14624
- const seeds = recall(records, queryEmbedding, query, deps);
14625
- const asOf = query.asOf ?? deps.now;
14626
- const includeShared = options.includeShared ?? query.includeShared;
14627
- const traversals = [];
14628
- for (const result of seeds) {
14629
- const seed = seedNodeId(result.record);
14630
- if (seed === undefined) {
15230
+ async function listEntries(cacheRoot) {
15231
+ const entries = [];
15232
+ for (const candidate of findEntryDirs(cacheRoot)) {
15233
+ const { specDir, absolutePath, stat: stat2 } = candidate;
15234
+ if (!specDir.startsWith(SCOPE_PREFIX2))
14631
15235
  continue;
15236
+ const mtimeIso = stat2.mtime.toISOString();
15237
+ const entryPkg = readJsonSafe(path3.join(absolutePath, "package.json"));
15238
+ const deps = entryPkg?.dependencies;
15239
+ let pinnedVersion = "";
15240
+ if (deps !== null && typeof deps === "object" && !Array.isArray(deps)) {
15241
+ const depsObj = deps;
15242
+ const pinned = depsObj[SCOPE_PREFIX2];
15243
+ pinnedVersion = typeof pinned === "string" ? pinned : "";
14632
15244
  }
14633
- traversals.push({
14634
- seedRecordId: result.record.id,
14635
- traversal: traverseRelations(records, {
14636
- seed,
14637
- ownerKey: query.ownerKey,
14638
- asOf,
14639
- maxDepth: options.maxDepth,
14640
- includeShared
14641
- })
15245
+ const installedPkg = readJsonSafe(path3.join(absolutePath, "node_modules", ...SCOPE_SEGMENTS, "package.json"));
15246
+ const installedVersion = extractVersion(installedPkg);
15247
+ entries.push({
15248
+ specDir,
15249
+ absolutePath,
15250
+ pinnedVersion,
15251
+ installedVersion,
15252
+ mtimeIso
14642
15253
  });
14643
15254
  }
14644
- return { recall: seeds, traversals };
14645
- }
14646
-
14647
- // src/memory/merge.ts
14648
- var SEP = "\x00";
14649
- function factSlotKey(fact) {
14650
- return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
15255
+ return entries;
14651
15256
  }
14652
- function factValueKey(fact) {
14653
- return [factSlotKey(fact), fact.object].join(SEP);
15257
+ async function deleteEntry(entryPath) {
15258
+ fs3.rmSync(entryPath, { recursive: true, force: true });
14654
15259
  }
14655
- function preferenceSlotKey(preference) {
14656
- return [preference.category, preference.ownerKey].join(SEP);
15260
+ async function isReparsePath(entryPath) {
15261
+ try {
15262
+ const stat2 = await fs3.promises.lstat(entryPath);
15263
+ return stat2.isSymbolicLink();
15264
+ } catch {
15265
+ return true;
15266
+ }
14657
15267
  }
14658
- function preferenceValueKey(preference) {
14659
- return [preferenceSlotKey(preference), preference.preference].join(SEP);
15268
+ var realCacheAdapter = {
15269
+ resolveCacheRoot,
15270
+ listEntries,
15271
+ deleteEntry,
15272
+ isReparsePath
15273
+ };
15274
+
15275
+ // src/orchestrator/rosterPersistence.ts
15276
+ function rosterFence(roster) {
15277
+ const data = { universe: roster.universe, entries: roster.entries };
15278
+ return `\`\`\`json
15279
+ ${JSON.stringify(data, null, 2)}
15280
+ \`\`\``;
14660
15281
  }
14661
- function entitySlotKey(entity) {
14662
- return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
15282
+ function serializeRoster(roster) {
15283
+ return `# openteam roster
15284
+
15285
+ Universe: ${roster.universe}
15286
+
15287
+ ${rosterFence(roster)}
15288
+ `;
14663
15289
  }
14664
- function relationSlotKey(relation) {
14665
- return [relation.from, relation.predicate, relation.ownerKey].join(SEP);
15290
+ async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
15291
+ await storage.write(path4, serializeRoster(roster));
15292
+ return path4;
14666
15293
  }
14667
- function relationValueKey(relation) {
14668
- return [relationSlotKey(relation), relation.to].join(SEP);
15294
+ async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
15295
+ const content = await storage.read(path4);
15296
+ if (content === undefined) {
15297
+ return { status: "absent", path: path4 };
15298
+ }
15299
+ const parsed = parseRosterResult(content);
15300
+ if (!parsed.ok) {
15301
+ return { status: "invalid", path: path4, error: parsed.error };
15302
+ }
15303
+ return {
15304
+ status: "loaded",
15305
+ path: path4,
15306
+ roster: enforceGuaranteedRoles(parsed.roster)
15307
+ };
14669
15308
  }
14670
- function coalesceFact(existing, incoming) {
14671
- const window = coalesceWindow(existing, incoming);
15309
+ async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
15310
+ const existing = await loadRosterFile(storage, path4);
15311
+ if (existing.status === "loaded") {
15312
+ return { roster: existing.roster, regenerated: false };
15313
+ }
15314
+ const roster = enforceGuaranteedRoles(await generate());
15315
+ if (existing.status === "absent") {
15316
+ await persistRoster(storage, roster, path4);
15317
+ return { roster, regenerated: true };
15318
+ }
14672
15319
  return {
14673
- ...existing,
14674
- validFrom: window.validFrom,
14675
- validUntil: window.validUntil,
14676
- confidence: Math.max(existing.confidence, incoming.confidence)
15320
+ roster,
15321
+ regenerated: true,
15322
+ recoveredFromInvalidPersisted: true,
15323
+ invalidPersistedRosterError: existing.error.message
14677
15324
  };
14678
15325
  }
14679
- function applyFact(state, incoming, now) {
14680
- const incomingSlot = factSlotKey(incoming);
14681
- const incomingValue = factValueKey(incoming);
14682
- const superseded = [];
14683
- const records = [];
14684
- let reaffirmed = false;
14685
- for (const fact of state) {
14686
- if (!isActiveAt(fact, now)) {
14687
- records.push(fact);
14688
- continue;
14689
- }
14690
- if (factValueKey(fact) === incomingValue) {
14691
- records.push(coalesceFact(fact, incoming));
14692
- reaffirmed = true;
14693
- continue;
14694
- }
14695
- if (factSlotKey(fact) === incomingSlot) {
14696
- const dead = {
14697
- ...fact,
14698
- invalidatedAt: now,
14699
- supersededBy: incoming.id
14700
- };
14701
- superseded.push(dead);
14702
- records.push(dead);
14703
- continue;
14704
- }
14705
- records.push(fact);
15326
+
15327
+ // src/memory/types.ts
15328
+ import { z as z21 } from "zod";
15329
+ var SHARED_OWNER_KEY = "*";
15330
+ var OwnerKeySchema = z21.string().min(1);
15331
+ var MemoryKindSchema = z21.enum(["fact", "preference", "entity"]);
15332
+ var MemoryBaseSchema = z21.object({
15333
+ id: z21.string().min(1),
15334
+ ownerKey: OwnerKeySchema,
15335
+ confidence: z21.number().min(0).max(1),
15336
+ validFrom: z21.number().finite(),
15337
+ validUntil: z21.number().finite().nullable().default(null),
15338
+ createdAt: z21.number().finite(),
15339
+ invalidatedAt: z21.number().finite().nullable().default(null),
15340
+ sourceHash: z21.string().min(1),
15341
+ supersededBy: z21.string().min(1).nullable().default(null)
15342
+ }).strict();
15343
+ var FactSchema = MemoryBaseSchema.extend({
15344
+ kind: z21.literal("fact"),
15345
+ subject: z21.string().min(1),
15346
+ predicate: z21.string().min(1),
15347
+ object: z21.string().min(1),
15348
+ category: z21.string().min(1).nullable().default(null)
15349
+ });
15350
+ var PreferenceSchema = MemoryBaseSchema.extend({
15351
+ kind: z21.literal("preference"),
15352
+ category: z21.string().min(1),
15353
+ preference: z21.string().min(1),
15354
+ context: z21.string().min(1).nullable().default(null),
15355
+ lastAccessedAt: z21.number().finite().nullable().default(null),
15356
+ accessCount: z21.number().int().min(0).default(0)
15357
+ });
15358
+ var EntitySchema = MemoryBaseSchema.extend({
15359
+ kind: z21.literal("entity"),
15360
+ canonicalName: z21.string().min(1),
15361
+ type: z21.string().min(1),
15362
+ aliases: z21.array(z21.string().min(1)).default([])
15363
+ });
15364
+ var RelationSchema = MemoryBaseSchema.extend({
15365
+ kind: z21.literal("relation"),
15366
+ from: z21.string().min(1),
15367
+ to: z21.string().min(1),
15368
+ predicate: z21.string().min(1),
15369
+ annotation: z21.string().min(1).nullable().default(null)
15370
+ });
15371
+ var MemoryRecordSchema = z21.discriminatedUnion("kind", [
15372
+ FactSchema,
15373
+ PreferenceSchema,
15374
+ EntitySchema,
15375
+ RelationSchema
15376
+ ]);
15377
+ var RecallQuerySchema = z21.object({
15378
+ ownerKey: OwnerKeySchema,
15379
+ kinds: z21.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
15380
+ asOf: z21.number().finite().nullable().default(null),
15381
+ limit: z21.number().int().positive().default(8),
15382
+ minSimilarity: z21.number().min(0).max(1).default(0.2),
15383
+ includeShared: z21.boolean().default(true)
15384
+ }).strict();
15385
+
15386
+ // src/memory/rank.ts
15387
+ var DAY_MS = 86400000;
15388
+ var DEFAULT_RANK_SIGNALS = {
15389
+ similarityWeight: 1,
15390
+ recencyWeight: 0.2,
15391
+ accessWeight: 0.1,
15392
+ recencyHalfLifeMs: 14 * DAY_MS
15393
+ };
15394
+ function recencyTimestamp(record) {
15395
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
15396
+ return record.lastAccessedAt;
14706
15397
  }
14707
- if (!reaffirmed) {
14708
- records.push(incoming);
15398
+ return record.createdAt;
15399
+ }
15400
+ function recencyScore(record, signals) {
15401
+ const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
15402
+ return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
15403
+ }
15404
+ function accessScore(record) {
15405
+ if (record.kind !== "preference") {
15406
+ return 0;
14709
15407
  }
14710
- return { records, superseded };
15408
+ return 1 - 1 / (1 + record.accessCount);
14711
15409
  }
14712
- function applyPreference(state, incoming, now) {
14713
- const incomingSlot = preferenceSlotKey(incoming);
14714
- const incomingValue = preferenceValueKey(incoming);
14715
- const superseded = [];
14716
- const records = [];
14717
- let reaffirmed = false;
14718
- for (const preference of state) {
14719
- if (!isActiveAt(preference, now)) {
14720
- records.push(preference);
14721
- continue;
14722
- }
14723
- if (preferenceValueKey(preference) === incomingValue) {
14724
- records.push({
14725
- ...preference,
14726
- confidence: Math.max(preference.confidence, incoming.confidence)
15410
+ function scoreCandidate(candidate, signals) {
15411
+ return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
15412
+ }
15413
+ function rankRecall(candidates, signals) {
15414
+ return candidates.map((candidate) => ({
15415
+ ...candidate,
15416
+ score: scoreCandidate(candidate, signals)
15417
+ })).sort((a, b) => {
15418
+ if (b.score !== a.score) {
15419
+ return b.score - a.score;
15420
+ }
15421
+ return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
15422
+ });
15423
+ }
15424
+
15425
+ // src/memory/temporal.ts
15426
+ function isTransactionActiveAt(record, asOf) {
15427
+ return record.invalidatedAt === null || record.invalidatedAt > asOf;
15428
+ }
15429
+ function isValidAt(record, asOf) {
15430
+ if (record.validFrom > asOf) {
15431
+ return false;
15432
+ }
15433
+ return record.validUntil === null || record.validUntil > asOf;
15434
+ }
15435
+ function isActiveAt(record, asOf) {
15436
+ return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
15437
+ }
15438
+ function filterActive(records, asOf) {
15439
+ return records.filter((record) => isActiveAt(record, asOf));
15440
+ }
15441
+ function coalesceWindow(a, b) {
15442
+ const validFrom = Math.min(a.validFrom, b.validFrom);
15443
+ const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
15444
+ return { validFrom, validUntil };
15445
+ }
15446
+
15447
+ // src/memory/vector.ts
15448
+ function dot(a, b) {
15449
+ if (a.length !== b.length) {
15450
+ throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
15451
+ }
15452
+ let sum = 0;
15453
+ for (let i = 0;i < a.length; i += 1) {
15454
+ sum += a[i] * b[i];
15455
+ }
15456
+ return sum;
15457
+ }
15458
+ function norm(a) {
15459
+ let sum = 0;
15460
+ for (let i = 0;i < a.length; i += 1) {
15461
+ const value = a[i];
15462
+ sum += value * value;
15463
+ }
15464
+ return Math.sqrt(sum);
15465
+ }
15466
+ function cosineSimilarity(a, b) {
15467
+ const denom = norm(a) * norm(b);
15468
+ if (denom === 0) {
15469
+ return 0;
15470
+ }
15471
+ return dot(a, b) / denom;
15472
+ }
15473
+
15474
+ // src/memory/recall.ts
15475
+ function matchesScope(record, query) {
15476
+ if (record.ownerKey === query.ownerKey) {
15477
+ return true;
15478
+ }
15479
+ return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
15480
+ }
15481
+ function recall(records, queryEmbedding, query, deps) {
15482
+ const asOf = query.asOf ?? deps.now;
15483
+ const kinds = new Set(query.kinds);
15484
+ const candidates = [];
15485
+ for (const record of filterActive(records, asOf)) {
15486
+ if (!kinds.has(record.kind) || !matchesScope(record, query)) {
15487
+ continue;
15488
+ }
15489
+ const embedding = deps.embeddings.get(record.id);
15490
+ if (embedding === undefined) {
15491
+ continue;
15492
+ }
15493
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
15494
+ if (similarity < query.minSimilarity) {
15495
+ continue;
15496
+ }
15497
+ candidates.push({ record, similarity });
15498
+ }
15499
+ const signals = {
15500
+ now: deps.now,
15501
+ ...deps.signals ?? DEFAULT_RANK_SIGNALS
15502
+ };
15503
+ return rankRecall(candidates, signals).slice(0, query.limit);
15504
+ }
15505
+
15506
+ // src/memory/merge.ts
15507
+ var SEP = "\x00";
15508
+ function factSlotKey(fact) {
15509
+ return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
15510
+ }
15511
+ function factValueKey(fact) {
15512
+ return [factSlotKey(fact), fact.object].join(SEP);
15513
+ }
15514
+ function preferenceSlotKey(preference) {
15515
+ return [preference.category, preference.ownerKey].join(SEP);
15516
+ }
15517
+ function preferenceValueKey(preference) {
15518
+ return [preferenceSlotKey(preference), preference.preference].join(SEP);
15519
+ }
15520
+ function entitySlotKey(entity) {
15521
+ return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
15522
+ }
15523
+ function relationSlotKey(relation) {
15524
+ return [relation.from, relation.predicate, relation.ownerKey].join(SEP);
15525
+ }
15526
+ function relationValueKey(relation) {
15527
+ return [relationSlotKey(relation), relation.to].join(SEP);
15528
+ }
15529
+ function coalesceFact(existing, incoming) {
15530
+ const window = coalesceWindow(existing, incoming);
15531
+ return {
15532
+ ...existing,
15533
+ validFrom: window.validFrom,
15534
+ validUntil: window.validUntil,
15535
+ confidence: Math.max(existing.confidence, incoming.confidence)
15536
+ };
15537
+ }
15538
+ function applyFact(state, incoming, now) {
15539
+ const incomingSlot = factSlotKey(incoming);
15540
+ const incomingValue = factValueKey(incoming);
15541
+ const superseded = [];
15542
+ const records = [];
15543
+ let reaffirmed = false;
15544
+ for (const fact of state) {
15545
+ if (!isActiveAt(fact, now)) {
15546
+ records.push(fact);
15547
+ continue;
15548
+ }
15549
+ if (factValueKey(fact) === incomingValue) {
15550
+ records.push(coalesceFact(fact, incoming));
15551
+ reaffirmed = true;
15552
+ continue;
15553
+ }
15554
+ if (factSlotKey(fact) === incomingSlot) {
15555
+ const dead = {
15556
+ ...fact,
15557
+ invalidatedAt: now,
15558
+ supersededBy: incoming.id
15559
+ };
15560
+ superseded.push(dead);
15561
+ records.push(dead);
15562
+ continue;
15563
+ }
15564
+ records.push(fact);
15565
+ }
15566
+ if (!reaffirmed) {
15567
+ records.push(incoming);
15568
+ }
15569
+ return { records, superseded };
15570
+ }
15571
+ function applyPreference(state, incoming, now) {
15572
+ const incomingSlot = preferenceSlotKey(incoming);
15573
+ const incomingValue = preferenceValueKey(incoming);
15574
+ const superseded = [];
15575
+ const records = [];
15576
+ let reaffirmed = false;
15577
+ for (const preference of state) {
15578
+ if (!isActiveAt(preference, now)) {
15579
+ records.push(preference);
15580
+ continue;
15581
+ }
15582
+ if (preferenceValueKey(preference) === incomingValue) {
15583
+ records.push({
15584
+ ...preference,
15585
+ confidence: Math.max(preference.confidence, incoming.confidence)
14727
15586
  });
14728
15587
  reaffirmed = true;
14729
15588
  continue;
@@ -15076,44 +15935,517 @@ async function recallMemory(deps, queryText, query) {
15076
15935
  return recall(recordRead.records, queryEmbedding, query, recallDeps);
15077
15936
  }
15078
15937
 
15079
- // src/mcp/semanticRecall.ts
15080
- function buildSemanticRecall(deps, policy) {
15081
- return async ({ queryText, kinds, limit }) => {
15082
- const query = RecallQuerySchema.parse({
15083
- ownerKey: policy.ownerKey,
15084
- kinds,
15085
- asOf: policy.asOf ?? null,
15086
- limit,
15087
- minSimilarity: policy.minSimilarity,
15088
- includeShared: policy.includeShared
15089
- });
15090
- const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
15091
- index: deps.index,
15092
- embedder: deps.embedder,
15093
- now: deps.now(),
15094
- signals: deps.signals
15095
- };
15096
- return recallMemory(recallDeps, queryText, query);
15938
+ // src/plugin/orchestrateTool.ts
15939
+ import { tool } from "@opencode-ai/plugin";
15940
+ import { z as z22 } from "zod";
15941
+ var _pluginWarnedRoles = new Set;
15942
+ var RoleAssignmentSchema = z22.object({
15943
+ roleID: z22.string().min(1).describe("Role identifier from the roster"),
15944
+ prompt: z22.string().min(1).describe("Work prompt for this role"),
15945
+ title: z22.string().min(1).optional().describe("Human-readable title"),
15946
+ dependsOn: z22.array(z22.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
15947
+ }).strict();
15948
+ var OrchestratePayloadSchema = z22.object({
15949
+ assignments: z22.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
15950
+ parentSessionID: z22.string().min(1).optional().describe("Parent session for subsessions"),
15951
+ directory: z22.string().min(1).optional().describe("Working directory override")
15952
+ }).strict();
15953
+ function adaptSdkClient(sdk) {
15954
+ return {
15955
+ session: {
15956
+ create: async (args) => {
15957
+ const result = await sdk.session.create(args);
15958
+ const out = {};
15959
+ if (result.data !== undefined) {
15960
+ out.data = result.data;
15961
+ }
15962
+ if (result.error !== undefined) {
15963
+ out.error = result.error;
15964
+ }
15965
+ return out;
15966
+ },
15967
+ prompt: async (args) => {
15968
+ const result = await sdk.session.prompt(args);
15969
+ const out = {};
15970
+ if (result.data !== undefined) {
15971
+ out.data = result.data;
15972
+ }
15973
+ if (result.error !== undefined) {
15974
+ out.error = result.error;
15975
+ }
15976
+ return out;
15977
+ }
15978
+ }
15097
15979
  };
15098
15980
  }
15099
- var DEFAULT_TRAVERSAL_DEPTH = 3;
15100
- function buildRelationTraversal(deps, policy) {
15101
- return async ({ seed, maxDepth }) => {
15102
- const asOf = policy.asOf ?? deps.now();
15103
- return traverseRelations(deps.index.allRecords(), {
15104
- seed,
15105
- ownerKey: policy.ownerKey,
15106
- asOf,
15107
- maxDepth: maxDepth ?? DEFAULT_TRAVERSAL_DEPTH,
15108
- includeShared: policy.includeShared
15109
- });
15110
- };
15981
+
15982
+ // src/local/embeddings.ts
15983
+ function parseEmbeddingsResponse(payload) {
15984
+ if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
15985
+ throw new Error("Malformed /embeddings response: missing 'data'");
15986
+ }
15987
+ const data = payload.data;
15988
+ return data.map((entry, index) => {
15989
+ const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
15990
+ if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
15991
+ throw new Error(`Malformed /embeddings response: invalid vector at index ${index}`);
15992
+ }
15993
+ return embedding;
15994
+ });
15111
15995
  }
15112
- function buildGraphRecall(deps, policy) {
15113
- return async ({ queryText, limit, maxDepth }) => {
15114
- const query = RecallQuerySchema.parse({
15115
- ownerKey: policy.ownerKey,
15116
- kinds: ["fact", "preference", "entity"],
15996
+ async function embedTexts(request, deps) {
15997
+ if (request.texts.length === 0) {
15998
+ return [];
15999
+ }
16000
+ let response;
16001
+ try {
16002
+ response = await deps.fetch(embeddingsEndpoint(request.baseURL), {
16003
+ method: "POST",
16004
+ headers: {
16005
+ "content-type": "application/json",
16006
+ accept: "application/json"
16007
+ },
16008
+ body: JSON.stringify({
16009
+ model: request.modelID,
16010
+ input: [...request.texts]
16011
+ })
16012
+ });
16013
+ } catch (error) {
16014
+ throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
16015
+ }
16016
+ if (!response.ok) {
16017
+ throw new Error(`Local embeddings failed: HTTP ${response.status} on /embeddings`);
16018
+ }
16019
+ let payload;
16020
+ try {
16021
+ payload = await response.json();
16022
+ } catch (error) {
16023
+ throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
16024
+ }
16025
+ const vectors = parseEmbeddingsResponse(payload);
16026
+ if (vectors.length !== request.texts.length) {
16027
+ throw new Error(`Local embeddings returned ${vectors.length} vectors for ${request.texts.length} texts`);
16028
+ }
16029
+ return vectors;
16030
+ }
16031
+ function createLocalEmbedder(request, deps) {
16032
+ return (texts) => embedTexts({ ...request, texts }, deps);
16033
+ }
16034
+
16035
+ // src/memory/embedText.ts
16036
+ function normalize(parts) {
16037
+ return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
16038
+ }
16039
+ function recordEmbeddingText(record) {
16040
+ if (record.kind === "fact") {
16041
+ return normalize([
16042
+ record.subject,
16043
+ record.predicate,
16044
+ record.object,
16045
+ record.category ?? ""
16046
+ ]);
16047
+ }
16048
+ if (record.kind === "preference") {
16049
+ return normalize([
16050
+ record.category,
16051
+ record.preference,
16052
+ record.context ?? ""
16053
+ ]);
16054
+ }
16055
+ if (record.kind === "relation") {
16056
+ return normalize([
16057
+ record.from,
16058
+ record.predicate,
16059
+ record.to,
16060
+ record.annotation ?? ""
16061
+ ]);
16062
+ }
16063
+ return normalize([record.canonicalName, record.type, ...record.aliases]);
16064
+ }
16065
+
16066
+ // src/storage/index/memoryEmbeddings.ts
16067
+ async function ensureMemoryEmbeddings(deps) {
16068
+ const { index, embedder, model } = deps;
16069
+ const warn = deps.warn ?? console.warn;
16070
+ const storedModel = index.embeddingModel();
16071
+ const modelChanged = storedModel !== null && storedModel !== model;
16072
+ if (modelChanged) {
16073
+ index.clearEmbeddings();
16074
+ }
16075
+ index.setEmbeddingModel(model);
16076
+ const records = index.allRecords();
16077
+ index.pruneEmbeddings();
16078
+ const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
16079
+ const todo = records.filter((record) => !have.has(record.id));
16080
+ if (todo.length === 0) {
16081
+ return { embedded: 0, failed: 0, modelChanged };
16082
+ }
16083
+ let vectors;
16084
+ try {
16085
+ vectors = await embedder(todo.map(recordEmbeddingText));
16086
+ } catch (error) {
16087
+ const reason = error instanceof Error ? error.message : String(error);
16088
+ warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ${reason}. ` + "Recall over them is unavailable until the local runtime is reachable.");
16089
+ return { embedded: 0, failed: todo.length, modelChanged };
16090
+ }
16091
+ if (vectors.length !== todo.length) {
16092
+ warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ` + `the embedder returned ${vectors.length} vector(s). Recall over them is unavailable.`);
16093
+ return { embedded: 0, failed: todo.length, modelChanged };
16094
+ }
16095
+ const entries = todo.map((record, position) => ({
16096
+ recordId: record.id,
16097
+ vector: vectors[position]
16098
+ }));
16099
+ index.putEmbeddings(entries);
16100
+ return { embedded: entries.length, failed: 0, modelChanged };
16101
+ }
16102
+
16103
+ // src/storage/index/memoryRuntime.ts
16104
+ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
16105
+ const semantic = resolveMemorySemantic(config);
16106
+ if (!semantic.enabled || semantic.embeddings.provider === "none") {
16107
+ return;
16108
+ }
16109
+ if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
16110
+ return;
16111
+ }
16112
+ const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
16113
+ if (runtime?.baseURL === undefined) {
16114
+ return;
16115
+ }
16116
+ const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
16117
+ const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
16118
+ const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
16119
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
16120
+ const now = deps.now ?? (() => Date.now());
16121
+ const warn = deps.warn ?? console.warn;
16122
+ try {
16123
+ const db = await openDatabase(semantic.indexPath);
16124
+ const index = createMemoryIndex(db, warn);
16125
+ const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
16126
+ if (rebuildResult.rejectedLines > 0) {
16127
+ warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
16128
+ }
16129
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
16130
+ await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
16131
+ return {
16132
+ index,
16133
+ embedder,
16134
+ now,
16135
+ ownerKey: semantic.scope === "user" ? "user" : "project",
16136
+ semantic
16137
+ };
16138
+ } catch {
16139
+ return;
16140
+ }
16141
+ }
16142
+ // package.json
16143
+ var package_default = {
16144
+ name: "@jmanuelcorral/openteam",
16145
+ version: "0.16.1",
16146
+ packageManager: "bun@1.3.14",
16147
+ description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
16148
+ license: "MIT",
16149
+ author: "Jose Manuel Corral",
16150
+ repository: {
16151
+ type: "git",
16152
+ url: "git+https://github.com/jmanuelcorral/openteam.git"
16153
+ },
16154
+ homepage: "https://github.com/jmanuelcorral/openteam#readme",
16155
+ bugs: {
16156
+ url: "https://github.com/jmanuelcorral/openteam/issues"
16157
+ },
16158
+ keywords: [
16159
+ "opencode",
16160
+ "opencode-plugin",
16161
+ "llm",
16162
+ "routing",
16163
+ "local-llm",
16164
+ "ollama",
16165
+ "lm-studio",
16166
+ "foundry-local",
16167
+ "cost-optimization",
16168
+ "multi-agent"
16169
+ ],
16170
+ engines: {
16171
+ bun: ">=1.3",
16172
+ node: "^22.22.2 || ^24.15.0 || >=26.0.0"
16173
+ },
16174
+ type: "module",
16175
+ main: "./dist/index.js",
16176
+ module: "./dist/index.js",
16177
+ types: "./dist/index.d.ts",
16178
+ bin: {
16179
+ openteam: "./dist/cli.js"
16180
+ },
16181
+ exports: {
16182
+ ".": {
16183
+ types: "./dist/index.d.ts",
16184
+ import: "./dist/index.js"
16185
+ },
16186
+ "./package.json": "./package.json"
16187
+ },
16188
+ files: [
16189
+ "dist",
16190
+ "README.md",
16191
+ "LICENSE",
16192
+ "AGENTS.md",
16193
+ ".opencode/openteam.example.json",
16194
+ ".opencode/command/openteam.md"
16195
+ ],
16196
+ publishConfig: {
16197
+ access: "public",
16198
+ registry: "https://registry.npmjs.org/"
16199
+ },
16200
+ trustedDependencies: [],
16201
+ sideEffects: false,
16202
+ scripts: {
16203
+ prebuild: "bun run clean",
16204
+ build: "bun run build:js && bun run build:cli && bun run build:types",
16205
+ "build:js": "bun build ./src/index.ts --target=node --format=esm --outfile=dist/index.js --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph",
16206
+ "build:cli": 'bun build ./src/cli.ts --target=node --format=esm --outfile=dist/cli.js --banner "#!/usr/bin/env node" --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @clack/prompts --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph',
16207
+ "build:types": "tsc -p tsconfig.build.json",
16208
+ clean: `node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"`,
16209
+ test: "bun test",
16210
+ "test:cov": "bun test --coverage",
16211
+ "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
16212
+ "certify:release": "bun run scripts/certify-artifact.ts release",
16213
+ "coverage:check": "node scripts/check-coverage.mjs",
16214
+ typecheck: "tsc --noEmit",
16215
+ lint: "biome check .",
16216
+ "format:check": "biome format .",
16217
+ "docs:install": "cd docs && bun install --frozen-lockfile --ignore-scripts",
16218
+ "docs:dev": "cd docs && bun run docs:dev",
16219
+ "docs:build": "cd docs && bun run docs:build",
16220
+ "docs:preview": "cd docs && bun run docs:preview",
16221
+ prepublishOnly: "bun run build",
16222
+ "link:local": "bun run build && npm link",
16223
+ "hooks:install": "git config core.hooksPath .githooks"
16224
+ },
16225
+ dependencies: {
16226
+ "@clack/prompts": "1.7.0",
16227
+ "@langchain/core": "1.2.9",
16228
+ "@langchain/langgraph": "1.4.12",
16229
+ "@opencode-ai/plugin": "1.18.19",
16230
+ "@opencode-ai/sdk": "1.18.19",
16231
+ "@opentelemetry/api": "1.9.1",
16232
+ "@opentelemetry/exporter-trace-otlp-http": "0.221.0",
16233
+ "@opentelemetry/resources": "2.10.0",
16234
+ "@opentelemetry/sdk-trace-base": "2.10.0",
16235
+ zod: "4.4.3"
16236
+ },
16237
+ devDependencies: {
16238
+ "@biomejs/biome": "2.5.9",
16239
+ "@types/bun": "1.3.14",
16240
+ typescript: "7.0.2"
16241
+ }
16242
+ };
16243
+
16244
+ // src/version.ts
16245
+ var PACKAGE_VERSION = package_default.version;
16246
+
16247
+ // src/index.ts
16248
+ function warnSoakEvidenceRejections(result, warn) {
16249
+ if (result.rejectedLines === 0) {
16250
+ return;
16251
+ }
16252
+ (warn ?? console.warn)(`[openteam] soak evidence reader rejected ${result.rejectedLines} observation line(s); cutover gate evidence may be incomplete.`);
16253
+ for (const rejection of result.rejections) {
16254
+ (warn ?? console.warn)(`[openteam] • ${rejection.file}:${rejection.lineNumber}: ${rejection.detail}`);
16255
+ }
16256
+ }
16257
+ var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
16258
+ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
16259
+ var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
16260
+ async function readAndParseCertificate(storage, path4, parse2, digestExtractor) {
16261
+ try {
16262
+ const content = await storage.read(path4);
16263
+ if (content === undefined) {
16264
+ return { status: "absent" };
16265
+ }
16266
+ const raw = JSON.parse(content);
16267
+ const cert = parse2(raw);
16268
+ return { status: "valid", digest: digestExtractor(cert) };
16269
+ } catch (error) {
16270
+ if (isMissingFile(error)) {
16271
+ return { status: "absent" };
16272
+ }
16273
+ if (error instanceof ShadowCertificateError || error instanceof ReleaseCertificateError || error instanceof SoakCertificateError) {
16274
+ return { status: "invalid", code: error.code, detail: error.detail };
16275
+ }
16276
+ const msg = error instanceof Error ? error.message : String(error);
16277
+ return { status: "invalid", code: "read-error", detail: msg };
16278
+ }
16279
+ }
16280
+ async function buildGateInput(config, storage, opencodeVersion, warn) {
16281
+ const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence));
16282
+ const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
16283
+ const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
16284
+ let soakLedgerDigest = "0".repeat(64);
16285
+ try {
16286
+ const recorderPorts = recorderPortsFromStorage(storage);
16287
+ const chainRead = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
16288
+ warnSoakEvidenceRejections(chainRead, warn);
16289
+ if (chainRead.chains.length > 0) {
16290
+ soakLedgerDigest = canonicalLedger(chainRead.chains);
16291
+ }
16292
+ } catch {}
16293
+ const soak = await readAndParseCertificate(storage, SOAK_CERT_PATH, (raw) => parseSoakCertificate(raw, DEFAULT_SOAK_POLICY, soakLedgerDigest), (cert) => cert.digest);
16294
+ const migration = "complete";
16295
+ const operatorApproval = config.graph?.operatorApproval ?? false;
16296
+ return { shadow, release, soak, migration, operatorApproval };
16297
+ }
16298
+ var OPENCODE_VERSION_UNRESOLVED_REASON = "the opencode version is not determinable outside a running opencode session, " + "so the cutover gate cannot be verified here — the running plugin logs the " + "authoritative gate result and warns loudly on a real denial";
16299
+ async function evaluateCutoverGateForBoundary(config, storage, version) {
16300
+ if (version === undefined) {
16301
+ return { kind: "unevaluable", reason: OPENCODE_VERSION_UNRESOLVED_REASON };
16302
+ }
16303
+ const gateInput = await buildGateInput(config, storage, version, () => {});
16304
+ return { kind: "evaluated", gate: evaluateGraphGate(gateInput) };
16305
+ }
16306
+ function recorderPortsFromStorage(storage) {
16307
+ return {
16308
+ fs: {
16309
+ readFile: async (path4) => {
16310
+ const content = await storage.read(path4);
16311
+ if (content === undefined) {
16312
+ throw Object.assign(new Error(`ENOENT: ${path4}`), { code: "ENOENT" });
16313
+ }
16314
+ return content;
16315
+ },
16316
+ writeFile: (path4, content) => storage.write(path4, content),
16317
+ exists: (path4) => storage.exists(path4),
16318
+ readDir: (path4) => storage.list(path4),
16319
+ mkdir: (path4) => storage.mkdir(path4)
16320
+ },
16321
+ clock: { now: () => new Date().toISOString() },
16322
+ id: { randomUUID: () => crypto.randomUUID() }
16323
+ };
16324
+ }
16325
+ function isMissingFile(error) {
16326
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
16327
+ }
16328
+
16329
+ // src/memory/traverse.ts
16330
+ function inOwnerScope(record, ownerKey, includeShared) {
16331
+ if (record.ownerKey === ownerKey) {
16332
+ return true;
16333
+ }
16334
+ return includeShared && record.ownerKey === SHARED_OWNER_KEY;
16335
+ }
16336
+ function traverseRelations(records, query) {
16337
+ const includeShared = query.includeShared ?? true;
16338
+ const adjacency = new Map;
16339
+ for (const record of records) {
16340
+ if (record.kind !== "relation") {
16341
+ continue;
16342
+ }
16343
+ if (!isActiveAt(record, query.asOf)) {
16344
+ continue;
16345
+ }
16346
+ if (!inOwnerScope(record, query.ownerKey, includeShared)) {
16347
+ continue;
16348
+ }
16349
+ const list = adjacency.get(record.from);
16350
+ if (list === undefined) {
16351
+ adjacency.set(record.from, [record]);
16352
+ } else {
16353
+ list.push(record);
16354
+ }
16355
+ }
16356
+ const visited = new Set([query.seed]);
16357
+ const nodes = [{ id: query.seed, depth: 0 }];
16358
+ const edges = [];
16359
+ let frontier = [query.seed];
16360
+ for (let depth = 0;depth < query.maxDepth; depth += 1) {
16361
+ const nextFrontier = [];
16362
+ for (const nodeId of frontier) {
16363
+ const outgoing = adjacency.get(nodeId) ?? [];
16364
+ for (const edge of outgoing) {
16365
+ if (visited.has(edge.to)) {
16366
+ continue;
16367
+ }
16368
+ visited.add(edge.to);
16369
+ const childDepth = depth + 1;
16370
+ nodes.push({ id: edge.to, depth: childDepth });
16371
+ edges.push({
16372
+ from: edge.from,
16373
+ to: edge.to,
16374
+ predicate: edge.predicate,
16375
+ depth: childDepth
16376
+ });
16377
+ nextFrontier.push(edge.to);
16378
+ }
16379
+ }
16380
+ frontier = nextFrontier;
16381
+ }
16382
+ return { seed: query.seed, nodes, edges };
16383
+ }
16384
+ function seedNodeId(record) {
16385
+ return record.kind === "entity" ? record.canonicalName : undefined;
16386
+ }
16387
+ function recallGraph(records, queryEmbedding, query, options, deps) {
16388
+ const seeds = recall(records, queryEmbedding, query, deps);
16389
+ const asOf = query.asOf ?? deps.now;
16390
+ const includeShared = options.includeShared ?? query.includeShared;
16391
+ const traversals = [];
16392
+ for (const result of seeds) {
16393
+ const seed = seedNodeId(result.record);
16394
+ if (seed === undefined) {
16395
+ continue;
16396
+ }
16397
+ traversals.push({
16398
+ seedRecordId: result.record.id,
16399
+ traversal: traverseRelations(records, {
16400
+ seed,
16401
+ ownerKey: query.ownerKey,
16402
+ asOf,
16403
+ maxDepth: options.maxDepth,
16404
+ includeShared
16405
+ })
16406
+ });
16407
+ }
16408
+ return { recall: seeds, traversals };
16409
+ }
16410
+
16411
+ // src/mcp/semanticRecall.ts
16412
+ function buildSemanticRecall(deps, policy) {
16413
+ return async ({ queryText, kinds, limit }) => {
16414
+ const query = RecallQuerySchema.parse({
16415
+ ownerKey: policy.ownerKey,
16416
+ kinds,
16417
+ asOf: policy.asOf ?? null,
16418
+ limit,
16419
+ minSimilarity: policy.minSimilarity,
16420
+ includeShared: policy.includeShared
16421
+ });
16422
+ const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
16423
+ index: deps.index,
16424
+ embedder: deps.embedder,
16425
+ now: deps.now(),
16426
+ signals: deps.signals
16427
+ };
16428
+ return recallMemory(recallDeps, queryText, query);
16429
+ };
16430
+ }
16431
+ var DEFAULT_TRAVERSAL_DEPTH = 3;
16432
+ function buildRelationTraversal(deps, policy) {
16433
+ return async ({ seed, maxDepth }) => {
16434
+ const asOf = policy.asOf ?? deps.now();
16435
+ return traverseRelations(deps.index.allRecords(), {
16436
+ seed,
16437
+ ownerKey: policy.ownerKey,
16438
+ asOf,
16439
+ maxDepth: maxDepth ?? DEFAULT_TRAVERSAL_DEPTH,
16440
+ includeShared: policy.includeShared
16441
+ });
16442
+ };
16443
+ }
16444
+ function buildGraphRecall(deps, policy) {
16445
+ return async ({ queryText, limit, maxDepth }) => {
16446
+ const query = RecallQuerySchema.parse({
16447
+ ownerKey: policy.ownerKey,
16448
+ kinds: ["fact", "preference", "entity"],
15117
16449
  asOf: policy.asOf ?? null,
15118
16450
  limit,
15119
16451
  minSimilarity: policy.minSimilarity,
@@ -15368,584 +16700,223 @@ function projectRecall(results) {
15368
16700
  return {
15369
16701
  ...base,
15370
16702
  category: record.category,
15371
- preference: record.preference
15372
- };
15373
- }
15374
- const entity = record;
15375
- return { ...base, canonicalName: entity.canonicalName, type: entity.type };
15376
- });
15377
- }
15378
- function projectGraphRecall(graph) {
15379
- return {
15380
- recall: projectRecall(graph.recall),
15381
- traversals: graph.traversals.map((entry) => ({
15382
- seedRecordId: entry.seedRecordId,
15383
- traversal: projectRelationGraph(entry.traversal)
15384
- }))
15385
- };
15386
- }
15387
- async function callSemanticRecall(recall2, kinds, args) {
15388
- const query = optionalString(args.query);
15389
- if (query === undefined) {
15390
- return textContent({ error: "requiere 'query'" });
15391
- }
15392
- const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
15393
- const results = await recall2({ queryText: query, kinds, limit });
15394
- return textContent(projectRecall(results));
15395
- }
15396
- async function callRelationTraversal(traverse, args) {
15397
- const entity = optionalString(args.entity);
15398
- if (entity === undefined) {
15399
- return textContent({ error: "requiere 'entity'" });
15400
- }
15401
- const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
15402
- const graph = await traverse({ seed: entity, maxDepth: depth });
15403
- return textContent(projectRelationGraph(graph));
15404
- }
15405
- async function callGraphRecall(recall2, args) {
15406
- const query = optionalString(args.query);
15407
- if (query === undefined) {
15408
- return textContent({ error: "requiere 'query'" });
15409
- }
15410
- const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
15411
- const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
15412
- const graph = await recall2({ queryText: query, limit, maxDepth: depth });
15413
- return textContent(projectGraphRecall(graph));
15414
- }
15415
- async function callTool(name, args, deps) {
15416
- if (deps.recallSemantic !== undefined) {
15417
- switch (name) {
15418
- case "recall_facts":
15419
- return callSemanticRecall(deps.recallSemantic, ["fact"], args);
15420
- case "recall_preferences":
15421
- return callSemanticRecall(deps.recallSemantic, ["preference"], args);
15422
- case "similar_tasks":
15423
- return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
15424
- default:
15425
- break;
15426
- }
15427
- }
15428
- if (deps.recallRelations !== undefined && name === "recall_relations") {
15429
- return callRelationTraversal(deps.recallRelations, args);
15430
- }
15431
- if (deps.recallGraph !== undefined && name === "recall_graph") {
15432
- return callGraphRecall(deps.recallGraph, args);
15433
- }
15434
- const memory = await deps.readMemory();
15435
- switch (name) {
15436
- case "list_decisions": {
15437
- const query = {};
15438
- const agent = optionalString(args.agent);
15439
- const tag = optionalString(args.tag);
15440
- const limit = optionalLimit(args.limit);
15441
- if (agent !== undefined) {
15442
- query.agent = agent;
15443
- }
15444
- if (tag !== undefined) {
15445
- query.tag = tag;
15446
- }
15447
- if (limit !== undefined) {
15448
- query.limit = limit;
15449
- }
15450
- return textContent(queryDecisions(memory, query));
15451
- }
15452
- case "list_meetings":
15453
- return textContent(queryMeetings(memory, { limit: optionalLimit(args.limit) ?? 0 }));
15454
- case "list_sessions":
15455
- return textContent(querySessions(memory, { limit: optionalLimit(args.limit) ?? 0 }));
15456
- case "cost_summary":
15457
- return textContent(costSummary(memory));
15458
- default:
15459
- return;
15460
- }
15461
- }
15462
- async function handleMcpMessage(message, deps) {
15463
- const isNotification = message.id === undefined;
15464
- const id = message.id ?? null;
15465
- const serverInfo = deps.serverInfo ?? DEFAULT_SERVER_INFO;
15466
- switch (message.method) {
15467
- case "initialize":
15468
- return response(id, {
15469
- protocolVersion: MCP_PROTOCOL_VERSION,
15470
- capabilities: { tools: {} },
15471
- serverInfo
15472
- });
15473
- case "tools/list":
15474
- return response(id, { tools: availableTools(deps) });
15475
- case "tools/call": {
15476
- const params = message.params ?? {};
15477
- const name = optionalString(params.name);
15478
- if (name === undefined) {
15479
- return errorResponse(id, -32602, "tools/call requiere 'name'");
15480
- }
15481
- const args = typeof params.arguments === "object" && params.arguments !== null ? params.arguments : {};
15482
- const result = await callTool(name, args, deps);
15483
- if (result === undefined) {
15484
- return errorResponse(id, -32602, `unknown tool: ${name}`);
15485
- }
15486
- return response(id, result);
15487
- }
15488
- default:
15489
- if (isNotification) {
15490
- return null;
15491
- }
15492
- return errorResponse(id, -32601, `unsupported method: ${message.method}`);
15493
- }
15494
- }
15495
-
15496
- // src/mcp/stdio.ts
15497
- function parseErrorResponse() {
15498
- return JSON.stringify({
15499
- jsonrpc: "2.0",
15500
- id: null,
15501
- error: { code: -32700, message: "Invalid JSON" }
15502
- });
15503
- }
15504
- async function runMcpStdio(deps, io) {
15505
- for await (const rawLine of io.lines) {
15506
- const line = rawLine.trim();
15507
- if (line.length === 0) {
15508
- continue;
15509
- }
15510
- let message;
15511
- try {
15512
- message = JSON.parse(line);
15513
- } catch {
15514
- io.write(`${parseErrorResponse()}
15515
- `);
15516
- continue;
15517
- }
15518
- const reply = await handleMcpMessage(message, deps);
15519
- if (reply !== null) {
15520
- io.write(`${JSON.stringify(reply)}
15521
- `);
15522
- }
15523
- }
15524
- }
15525
- async function* linesFromReadable(readable) {
15526
- let buffer = "";
15527
- for await (const chunk of readable) {
15528
- buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
15529
- let newlineIndex = buffer.indexOf(`
15530
- `);
15531
- while (newlineIndex >= 0) {
15532
- yield buffer.slice(0, newlineIndex);
15533
- buffer = buffer.slice(newlineIndex + 1);
15534
- newlineIndex = buffer.indexOf(`
15535
- `);
15536
- }
15537
- }
15538
- if (buffer.length > 0) {
15539
- yield buffer;
15540
- }
15541
- }
15542
-
15543
- // src/orchestrator/rosterPersistence.ts
15544
- function rosterFence(roster) {
15545
- const data = { universe: roster.universe, entries: roster.entries };
15546
- return `\`\`\`json
15547
- ${JSON.stringify(data, null, 2)}
15548
- \`\`\``;
15549
- }
15550
- function serializeRoster(roster) {
15551
- return `# openteam roster
15552
-
15553
- Universe: ${roster.universe}
15554
-
15555
- ${rosterFence(roster)}
15556
- `;
15557
- }
15558
- async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
15559
- await storage.write(path4, serializeRoster(roster));
15560
- return path4;
15561
- }
15562
- async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
15563
- const content = await storage.read(path4);
15564
- if (content === undefined) {
15565
- return { status: "absent", path: path4 };
15566
- }
15567
- const parsed = parseRosterResult(content);
15568
- if (!parsed.ok) {
15569
- return { status: "invalid", path: path4, error: parsed.error };
15570
- }
15571
- return {
15572
- status: "loaded",
15573
- path: path4,
15574
- roster: enforceGuaranteedRoles(parsed.roster)
15575
- };
15576
- }
15577
- async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
15578
- const existing = await loadRosterFile(storage, path4);
15579
- if (existing.status === "loaded") {
15580
- return { roster: existing.roster, regenerated: false };
15581
- }
15582
- const roster = enforceGuaranteedRoles(await generate());
15583
- if (existing.status === "absent") {
15584
- await persistRoster(storage, roster, path4);
15585
- return { roster, regenerated: true };
15586
- }
15587
- return {
15588
- roster,
15589
- regenerated: true,
15590
- recoveredFromInvalidPersisted: true,
15591
- invalidPersistedRosterError: existing.error.message
15592
- };
16703
+ preference: record.preference
16704
+ };
16705
+ }
16706
+ const entity = record;
16707
+ return { ...base, canonicalName: entity.canonicalName, type: entity.type };
16708
+ });
15593
16709
  }
15594
-
15595
- // src/plugin/orchestrateTool.ts
15596
- import { tool } from "@opencode-ai/plugin";
15597
- import { z as z20 } from "zod";
15598
- var _pluginWarnedRoles = new Set;
15599
- var RoleAssignmentSchema = z20.object({
15600
- roleID: z20.string().min(1).describe("Role identifier from the roster"),
15601
- prompt: z20.string().min(1).describe("Work prompt for this role"),
15602
- title: z20.string().min(1).optional().describe("Human-readable title"),
15603
- dependsOn: z20.array(z20.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
15604
- }).strict();
15605
- var OrchestratePayloadSchema = z20.object({
15606
- assignments: z20.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
15607
- parentSessionID: z20.string().min(1).optional().describe("Parent session for subsessions"),
15608
- directory: z20.string().min(1).optional().describe("Working directory override")
15609
- }).strict();
15610
- function adaptSdkClient(sdk) {
16710
+ function projectGraphRecall(graph) {
15611
16711
  return {
15612
- session: {
15613
- create: async (args) => {
15614
- const result = await sdk.session.create(args);
15615
- const out = {};
15616
- if (result.data !== undefined) {
15617
- out.data = result.data;
15618
- }
15619
- if (result.error !== undefined) {
15620
- out.error = result.error;
15621
- }
15622
- return out;
15623
- },
15624
- prompt: async (args) => {
15625
- const result = await sdk.session.prompt(args);
15626
- const out = {};
15627
- if (result.data !== undefined) {
15628
- out.data = result.data;
15629
- }
15630
- if (result.error !== undefined) {
15631
- out.error = result.error;
15632
- }
15633
- return out;
15634
- }
15635
- }
16712
+ recall: projectRecall(graph.recall),
16713
+ traversals: graph.traversals.map((entry) => ({
16714
+ seedRecordId: entry.seedRecordId,
16715
+ traversal: projectRelationGraph(entry.traversal)
16716
+ }))
15636
16717
  };
15637
16718
  }
15638
-
15639
- // src/memory/consolidate.ts
15640
- var DAY_MS2 = 86400000;
15641
- var DEFAULT_DECAY_PARAMS = {
15642
- halfLifeMs: 90 * DAY_MS2,
15643
- forgetThreshold: 0.05
15644
- };
15645
- function lastReinforcedAt(record) {
15646
- if (record.kind === "preference" && record.lastAccessedAt !== null) {
15647
- return record.lastAccessedAt;
16719
+ async function callSemanticRecall(recall2, kinds, args) {
16720
+ const query = optionalString(args.query);
16721
+ if (query === undefined) {
16722
+ return textContent({ error: "requiere 'query'" });
15648
16723
  }
15649
- return record.createdAt;
15650
- }
15651
- function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
15652
- const ageMs = Math.max(0, now - lastReinforcedAt(record));
15653
- return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
16724
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
16725
+ const results = await recall2({ queryText: query, kinds, limit });
16726
+ return textContent(projectRecall(results));
15654
16727
  }
15655
- function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
15656
- const tombstones = [];
15657
- for (const record of records) {
15658
- if (!isTransactionActiveAt(record, now)) {
15659
- continue;
15660
- }
15661
- if (decayedConfidence(record, now, params) < params.forgetThreshold) {
15662
- tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
15663
- }
16728
+ async function callRelationTraversal(traverse, args) {
16729
+ const entity = optionalString(args.entity);
16730
+ if (entity === undefined) {
16731
+ return textContent({ error: "requiere 'entity'" });
15664
16732
  }
15665
- return tombstones;
16733
+ const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
16734
+ const graph = await traverse({ seed: entity, maxDepth: depth });
16735
+ return textContent(projectRelationGraph(graph));
15666
16736
  }
15667
-
15668
- // src/storage/index/memoryConsolidate.ts
15669
- async function runConsolidation(logPath, deps) {
15670
- const params = deps.params ?? DEFAULT_DECAY_PARAMS;
15671
- const text2 = await deps.storage.read(logPath) ?? "";
15672
- const { records, rejectedLines } = parseMemoryLog(text2);
15673
- const state = foldAssertions(records);
15674
- const tombstones = consolidate(allRecords(state), deps.now, params);
15675
- if (tombstones.length === 0) {
15676
- return { forgotten: 0, rejectedLines };
16737
+ async function callGraphRecall(recall2, args) {
16738
+ const query = optionalString(args.query);
16739
+ if (query === undefined) {
16740
+ return textContent({ error: "requiere 'query'" });
15677
16741
  }
15678
- const appended = `${tombstones.map(serializeAssertion).join(`
15679
- `)}
15680
- `;
15681
- await deps.storage.append(logPath, appended);
15682
- return { forgotten: tombstones.length, rejectedLines };
16742
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
16743
+ const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
16744
+ const graph = await recall2({ queryText: query, limit, maxDepth: depth });
16745
+ return textContent(projectGraphRecall(graph));
15683
16746
  }
15684
-
15685
- // src/local/embeddings.ts
15686
- function parseEmbeddingsResponse(payload) {
15687
- if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
15688
- throw new Error("Malformed /embeddings response: missing 'data'");
15689
- }
15690
- const data = payload.data;
15691
- return data.map((entry, index) => {
15692
- const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
15693
- if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
15694
- throw new Error(`Malformed /embeddings response: invalid vector at index ${index}`);
16747
+ async function callTool(name, args, deps) {
16748
+ if (deps.recallSemantic !== undefined) {
16749
+ switch (name) {
16750
+ case "recall_facts":
16751
+ return callSemanticRecall(deps.recallSemantic, ["fact"], args);
16752
+ case "recall_preferences":
16753
+ return callSemanticRecall(deps.recallSemantic, ["preference"], args);
16754
+ case "similar_tasks":
16755
+ return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
16756
+ default:
16757
+ break;
15695
16758
  }
15696
- return embedding;
15697
- });
15698
- }
15699
- async function embedTexts(request, deps) {
15700
- if (request.texts.length === 0) {
15701
- return [];
15702
- }
15703
- let response2;
15704
- try {
15705
- response2 = await deps.fetch(embeddingsEndpoint(request.baseURL), {
15706
- method: "POST",
15707
- headers: {
15708
- "content-type": "application/json",
15709
- accept: "application/json"
15710
- },
15711
- body: JSON.stringify({
15712
- model: request.modelID,
15713
- input: [...request.texts]
15714
- })
15715
- });
15716
- } catch (error) {
15717
- throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
15718
- }
15719
- if (!response2.ok) {
15720
- throw new Error(`Local embeddings failed: HTTP ${response2.status} on /embeddings`);
15721
- }
15722
- let payload;
15723
- try {
15724
- payload = await response2.json();
15725
- } catch (error) {
15726
- throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
15727
- }
15728
- const vectors = parseEmbeddingsResponse(payload);
15729
- if (vectors.length !== request.texts.length) {
15730
- throw new Error(`Local embeddings returned ${vectors.length} vectors for ${request.texts.length} texts`);
15731
16759
  }
15732
- return vectors;
15733
- }
15734
- function createLocalEmbedder(request, deps) {
15735
- return (texts) => embedTexts({ ...request, texts }, deps);
15736
- }
15737
-
15738
- // src/memory/embedText.ts
15739
- function normalize(parts) {
15740
- return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
15741
- }
15742
- function recordEmbeddingText(record) {
15743
- if (record.kind === "fact") {
15744
- return normalize([
15745
- record.subject,
15746
- record.predicate,
15747
- record.object,
15748
- record.category ?? ""
15749
- ]);
16760
+ if (deps.recallRelations !== undefined && name === "recall_relations") {
16761
+ return callRelationTraversal(deps.recallRelations, args);
15750
16762
  }
15751
- if (record.kind === "preference") {
15752
- return normalize([
15753
- record.category,
15754
- record.preference,
15755
- record.context ?? ""
15756
- ]);
16763
+ if (deps.recallGraph !== undefined && name === "recall_graph") {
16764
+ return callGraphRecall(deps.recallGraph, args);
15757
16765
  }
15758
- if (record.kind === "relation") {
15759
- return normalize([
15760
- record.from,
15761
- record.predicate,
15762
- record.to,
15763
- record.annotation ?? ""
15764
- ]);
16766
+ const memory = await deps.readMemory();
16767
+ switch (name) {
16768
+ case "list_decisions": {
16769
+ const query = {};
16770
+ const agent = optionalString(args.agent);
16771
+ const tag = optionalString(args.tag);
16772
+ const limit = optionalLimit(args.limit);
16773
+ if (agent !== undefined) {
16774
+ query.agent = agent;
16775
+ }
16776
+ if (tag !== undefined) {
16777
+ query.tag = tag;
16778
+ }
16779
+ if (limit !== undefined) {
16780
+ query.limit = limit;
16781
+ }
16782
+ return textContent(queryDecisions(memory, query));
16783
+ }
16784
+ case "list_meetings":
16785
+ return textContent(queryMeetings(memory, { limit: optionalLimit(args.limit) ?? 0 }));
16786
+ case "list_sessions":
16787
+ return textContent(querySessions(memory, { limit: optionalLimit(args.limit) ?? 0 }));
16788
+ case "cost_summary":
16789
+ return textContent(costSummary(memory));
16790
+ default:
16791
+ return;
15765
16792
  }
15766
- return normalize([record.canonicalName, record.type, ...record.aliases]);
15767
16793
  }
15768
-
15769
- // src/storage/index/memoryEmbeddings.ts
15770
- async function ensureMemoryEmbeddings(deps) {
15771
- const { index, embedder, model } = deps;
15772
- const warn = deps.warn ?? console.warn;
15773
- const storedModel = index.embeddingModel();
15774
- const modelChanged = storedModel !== null && storedModel !== model;
15775
- if (modelChanged) {
15776
- index.clearEmbeddings();
16794
+ async function handleMcpMessage(message, deps) {
16795
+ const isNotification = message.id === undefined;
16796
+ const id = message.id ?? null;
16797
+ const serverInfo = deps.serverInfo ?? DEFAULT_SERVER_INFO;
16798
+ switch (message.method) {
16799
+ case "initialize":
16800
+ return response(id, {
16801
+ protocolVersion: MCP_PROTOCOL_VERSION,
16802
+ capabilities: { tools: {} },
16803
+ serverInfo
16804
+ });
16805
+ case "tools/list":
16806
+ return response(id, { tools: availableTools(deps) });
16807
+ case "tools/call": {
16808
+ const params = message.params ?? {};
16809
+ const name = optionalString(params.name);
16810
+ if (name === undefined) {
16811
+ return errorResponse(id, -32602, "tools/call requiere 'name'");
16812
+ }
16813
+ const args = typeof params.arguments === "object" && params.arguments !== null ? params.arguments : {};
16814
+ const result = await callTool(name, args, deps);
16815
+ if (result === undefined) {
16816
+ return errorResponse(id, -32602, `unknown tool: ${name}`);
16817
+ }
16818
+ return response(id, result);
16819
+ }
16820
+ default:
16821
+ if (isNotification) {
16822
+ return null;
16823
+ }
16824
+ return errorResponse(id, -32601, `unsupported method: ${message.method}`);
15777
16825
  }
15778
- index.setEmbeddingModel(model);
15779
- const records = index.allRecords();
15780
- index.pruneEmbeddings();
15781
- const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
15782
- const todo = records.filter((record) => !have.has(record.id));
15783
- if (todo.length === 0) {
15784
- return { embedded: 0, failed: 0, modelChanged };
16826
+ }
16827
+
16828
+ // src/mcp/stdio.ts
16829
+ function parseErrorResponse() {
16830
+ return JSON.stringify({
16831
+ jsonrpc: "2.0",
16832
+ id: null,
16833
+ error: { code: -32700, message: "Invalid JSON" }
16834
+ });
16835
+ }
16836
+ async function runMcpStdio(deps, io) {
16837
+ for await (const rawLine of io.lines) {
16838
+ const line = rawLine.trim();
16839
+ if (line.length === 0) {
16840
+ continue;
16841
+ }
16842
+ let message;
16843
+ try {
16844
+ message = JSON.parse(line);
16845
+ } catch {
16846
+ io.write(`${parseErrorResponse()}
16847
+ `);
16848
+ continue;
16849
+ }
16850
+ const reply = await handleMcpMessage(message, deps);
16851
+ if (reply !== null) {
16852
+ io.write(`${JSON.stringify(reply)}
16853
+ `);
16854
+ }
15785
16855
  }
15786
- let vectors;
15787
- try {
15788
- vectors = await embedder(todo.map(recordEmbeddingText));
15789
- } catch (error) {
15790
- const reason = error instanceof Error ? error.message : String(error);
15791
- warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ${reason}. ` + "Recall over them is unavailable until the local runtime is reachable.");
15792
- return { embedded: 0, failed: todo.length, modelChanged };
16856
+ }
16857
+ async function* linesFromReadable(readable) {
16858
+ let buffer = "";
16859
+ for await (const chunk of readable) {
16860
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
16861
+ let newlineIndex = buffer.indexOf(`
16862
+ `);
16863
+ while (newlineIndex >= 0) {
16864
+ yield buffer.slice(0, newlineIndex);
16865
+ buffer = buffer.slice(newlineIndex + 1);
16866
+ newlineIndex = buffer.indexOf(`
16867
+ `);
16868
+ }
15793
16869
  }
15794
- if (vectors.length !== todo.length) {
15795
- warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ` + `the embedder returned ${vectors.length} vector(s). Recall over them is unavailable.`);
15796
- return { embedded: 0, failed: todo.length, modelChanged };
16870
+ if (buffer.length > 0) {
16871
+ yield buffer;
15797
16872
  }
15798
- const entries = todo.map((record, position) => ({
15799
- recordId: record.id,
15800
- vector: vectors[position]
15801
- }));
15802
- index.putEmbeddings(entries);
15803
- return { embedded: entries.length, failed: 0, modelChanged };
15804
16873
  }
15805
16874
 
15806
- // src/storage/index/memoryRuntime.ts
15807
- async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
15808
- const semantic = resolveMemorySemantic(config);
15809
- if (!semantic.enabled || semantic.embeddings.provider === "none") {
15810
- return;
15811
- }
15812
- if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
15813
- return;
15814
- }
15815
- const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
15816
- if (runtime?.baseURL === undefined) {
15817
- return;
16875
+ // src/memory/consolidate.ts
16876
+ var DAY_MS2 = 86400000;
16877
+ var DEFAULT_DECAY_PARAMS = {
16878
+ halfLifeMs: 90 * DAY_MS2,
16879
+ forgetThreshold: 0.05
16880
+ };
16881
+ function lastReinforcedAt(record) {
16882
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
16883
+ return record.lastAccessedAt;
15818
16884
  }
15819
- const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
15820
- const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
15821
- const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
15822
- const fetchImpl = deps.fetch ?? globalThis.fetch;
15823
- const now = deps.now ?? (() => Date.now());
15824
- const warn = deps.warn ?? console.warn;
15825
- try {
15826
- const db = await openDatabase(semantic.indexPath);
15827
- const index = createMemoryIndex(db, warn);
15828
- const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
15829
- if (rebuildResult.rejectedLines > 0) {
15830
- warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
16885
+ return record.createdAt;
16886
+ }
16887
+ function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
16888
+ const ageMs = Math.max(0, now - lastReinforcedAt(record));
16889
+ return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
16890
+ }
16891
+ function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
16892
+ const tombstones = [];
16893
+ for (const record of records) {
16894
+ if (!isTransactionActiveAt(record, now)) {
16895
+ continue;
16896
+ }
16897
+ if (decayedConfidence(record, now, params) < params.forgetThreshold) {
16898
+ tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
15831
16899
  }
15832
- const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
15833
- await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
15834
- return {
15835
- index,
15836
- embedder,
15837
- now,
15838
- ownerKey: semantic.scope === "user" ? "user" : "project",
15839
- semantic
15840
- };
15841
- } catch {
15842
- return;
15843
16900
  }
16901
+ return tombstones;
15844
16902
  }
15845
- // package.json
15846
- var package_default = {
15847
- name: "@jmanuelcorral/openteam",
15848
- version: "0.16.0",
15849
- packageManager: "bun@1.3.14",
15850
- description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
15851
- license: "MIT",
15852
- author: "Jose Manuel Corral",
15853
- repository: {
15854
- type: "git",
15855
- url: "git+https://github.com/jmanuelcorral/openteam.git"
15856
- },
15857
- homepage: "https://github.com/jmanuelcorral/openteam#readme",
15858
- bugs: {
15859
- url: "https://github.com/jmanuelcorral/openteam/issues"
15860
- },
15861
- keywords: [
15862
- "opencode",
15863
- "opencode-plugin",
15864
- "llm",
15865
- "routing",
15866
- "local-llm",
15867
- "ollama",
15868
- "lm-studio",
15869
- "foundry-local",
15870
- "cost-optimization",
15871
- "multi-agent"
15872
- ],
15873
- engines: {
15874
- bun: ">=1.3",
15875
- node: "^22.22.2 || ^24.15.0 || >=26.0.0"
15876
- },
15877
- type: "module",
15878
- main: "./dist/index.js",
15879
- module: "./dist/index.js",
15880
- types: "./dist/index.d.ts",
15881
- bin: {
15882
- openteam: "./dist/cli.js"
15883
- },
15884
- exports: {
15885
- ".": {
15886
- types: "./dist/index.d.ts",
15887
- import: "./dist/index.js"
15888
- },
15889
- "./package.json": "./package.json"
15890
- },
15891
- files: [
15892
- "dist",
15893
- "README.md",
15894
- "LICENSE",
15895
- "AGENTS.md",
15896
- ".opencode/openteam.example.json",
15897
- ".opencode/command/openteam.md"
15898
- ],
15899
- publishConfig: {
15900
- access: "public",
15901
- registry: "https://registry.npmjs.org/"
15902
- },
15903
- trustedDependencies: [],
15904
- sideEffects: false,
15905
- scripts: {
15906
- prebuild: "bun run clean",
15907
- build: "bun run build:js && bun run build:cli && bun run build:types",
15908
- "build:js": "bun build ./src/index.ts --target=node --format=esm --outfile=dist/index.js --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph",
15909
- "build:cli": 'bun build ./src/cli.ts --target=node --format=esm --outfile=dist/cli.js --banner "#!/usr/bin/env node" --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @clack/prompts --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph',
15910
- "build:types": "tsc -p tsconfig.build.json",
15911
- clean: `node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"`,
15912
- test: "bun test",
15913
- "test:cov": "bun test --coverage",
15914
- "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
15915
- "certify:release": "bun run scripts/certify-artifact.ts release",
15916
- "coverage:check": "node scripts/check-coverage.mjs",
15917
- typecheck: "tsc --noEmit",
15918
- lint: "biome check .",
15919
- "format:check": "biome format .",
15920
- "docs:install": "cd docs && bun install --frozen-lockfile --ignore-scripts",
15921
- "docs:dev": "cd docs && bun run docs:dev",
15922
- "docs:build": "cd docs && bun run docs:build",
15923
- "docs:preview": "cd docs && bun run docs:preview",
15924
- prepublishOnly: "bun run build",
15925
- "link:local": "bun run build && npm link",
15926
- "hooks:install": "git config core.hooksPath .githooks"
15927
- },
15928
- dependencies: {
15929
- "@clack/prompts": "1.7.0",
15930
- "@langchain/core": "1.2.9",
15931
- "@langchain/langgraph": "1.4.12",
15932
- "@opencode-ai/plugin": "1.18.19",
15933
- "@opencode-ai/sdk": "1.18.19",
15934
- "@opentelemetry/api": "1.9.1",
15935
- "@opentelemetry/exporter-trace-otlp-http": "0.221.0",
15936
- "@opentelemetry/resources": "2.10.0",
15937
- "@opentelemetry/sdk-trace-base": "2.10.0",
15938
- zod: "4.4.3"
15939
- },
15940
- devDependencies: {
15941
- "@biomejs/biome": "2.5.9",
15942
- "@types/bun": "1.3.14",
15943
- typescript: "7.0.2"
15944
- }
15945
- };
15946
16903
 
15947
- // src/version.ts
15948
- var PACKAGE_VERSION = package_default.version;
16904
+ // src/storage/index/memoryConsolidate.ts
16905
+ async function runConsolidation(logPath, deps) {
16906
+ const params = deps.params ?? DEFAULT_DECAY_PARAMS;
16907
+ const text2 = await deps.storage.read(logPath) ?? "";
16908
+ const { records, rejectedLines } = parseMemoryLog(text2);
16909
+ const state = foldAssertions(records);
16910
+ const tombstones = consolidate(allRecords(state), deps.now, params);
16911
+ if (tombstones.length === 0) {
16912
+ return { forgotten: 0, rejectedLines };
16913
+ }
16914
+ const appended = `${tombstones.map(serializeAssertion).join(`
16915
+ `)}
16916
+ `;
16917
+ await deps.storage.append(logPath, appended);
16918
+ return { forgotten: tombstones.length, rejectedLines };
16919
+ }
15949
16920
 
15950
16921
  // src/web/git.ts
15951
16922
  function createGitLastCommit(exec) {
@@ -15968,6 +16939,7 @@ function createGitLastCommit(exec) {
15968
16939
 
15969
16940
  // src/cli.ts
15970
16941
  var execFileAsync2 = promisify2(execFile2);
16942
+ var execAsync = promisify2(exec);
15971
16943
  var AGENT_DIR2 = dirname5(ORCHESTRATOR_AGENT_PATH);
15972
16944
  var storage = createFsStorageProvider(process.cwd());
15973
16945
  async function readStoredRosterForDoctor() {
@@ -15996,6 +16968,22 @@ var nodeExec2 = async (command, args) => {
15996
16968
  };
15997
16969
  }
15998
16970
  };
16971
+ var requireFromCli = createRequire2(import.meta.url);
16972
+ function readPackageVersion(packageName) {
16973
+ try {
16974
+ const pkg = requireFromCli(`${packageName}/package.json`);
16975
+ return typeof pkg.version === "string" ? pkg.version : undefined;
16976
+ } catch {
16977
+ return;
16978
+ }
16979
+ }
16980
+ async function runOpencodeVersionCommand() {
16981
+ const { stdout } = await execAsync("opencode --version", {
16982
+ timeout: 4000,
16983
+ windowsHide: true
16984
+ });
16985
+ return stdout;
16986
+ }
15999
16987
  function describeFileReadError(error) {
16000
16988
  const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
16001
16989
  if (code === "ENOENT") {
@@ -16153,7 +17141,11 @@ var deps = {
16153
17141
  storage,
16154
17142
  holder: "openteam-cli",
16155
17143
  exec: nodeExec2,
16156
- repoRoot: process.cwd()
17144
+ repoRoot: process.cwd(),
17145
+ evaluateCutoverGate: async (gateConfig) => evaluateCutoverGateForBoundary(gateConfig, storage, await resolveOpencodeVersionBestEffort({
17146
+ readPackageVersion,
17147
+ runVersionCommand: runOpencodeVersionCommand
17148
+ }))
16157
17149
  }),
16158
17150
  configPath: DEFAULT_CONFIG_PATH,
16159
17151
  telemetryPath: DEFAULT_TELEMETRY_PATH,