@almadar/workspace 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -233,6 +233,9 @@ function compiledFile(workDir, relPath) {
233
233
  function appMarkerFile(workDir) {
234
234
  return path2.join(workDir, WORKSPACE_LAYOUT.APP_MARKER);
235
235
  }
236
+ function workspaceIndexManifestFile(workDir) {
237
+ return path2.join(workDir, WORKSPACE_LAYOUT.ALMADAR_DIR, "index.json");
238
+ }
236
239
  function sandboxedPath(workDir, relPath) {
237
240
  if (typeof relPath !== "string" || relPath.length === 0) {
238
241
  throw new Error("sandbox: empty relative path");
@@ -549,8 +552,10 @@ async function appendJsonLine(backend, absPath, value) {
549
552
  }
550
553
 
551
554
  // src/workspace-index/types.ts
552
- var WORKSPACE_INDEX_SCHEMA_VERSION = 1;
555
+ var WORKSPACE_INDEX_SCHEMA_VERSION = 2;
553
556
  var DEFAULT_COERCION_THRESHOLD = 0.85;
557
+ var RRF_K = 60;
558
+ var DEFAULT_RETRIEVAL_TOP_K = 3;
554
559
 
555
560
  // src/workspace-index/cosine.ts
556
561
  function cosineSimilarity(a, b) {
@@ -593,6 +598,75 @@ function deriveExtraTraitAlias(emit) {
593
598
  if (emit.name && emit.name.length > 0) return emit.name;
594
599
  return tailOfRef(emit.ref);
595
600
  }
601
+ function composeOrbitalContentFingerprint(spec) {
602
+ const segments = [composeOrbitalIdentityFingerprint(spec)];
603
+ const params = asJsonObject(spec["params"]);
604
+ if (params) {
605
+ const traitOverrides = asJsonObject(params["traitOverrides"]);
606
+ if (traitOverrides) {
607
+ for (const trait of Object.keys(traitOverrides).sort()) {
608
+ segments.push(trait);
609
+ const override = asJsonObject(traitOverrides[trait]);
610
+ if (!override) continue;
611
+ const config = asJsonObject(override["config"]);
612
+ if (!config) continue;
613
+ for (const knob of Object.keys(config).sort()) {
614
+ const rendered = renderKnobValue(config[knob]);
615
+ if (rendered.length > 0) segments.push(`${trait}.${knob}: ${rendered}`);
616
+ }
617
+ }
618
+ }
619
+ const fields = params["entityFields"];
620
+ if (Array.isArray(fields)) {
621
+ const names = [];
622
+ for (const f of fields) {
623
+ const obj = asJsonObject(f);
624
+ if (obj && typeof obj["name"] === "string") names.push(obj["name"]);
625
+ }
626
+ if (names.length > 0) segments.push(names.join(", "));
627
+ }
628
+ const extras = params["extraTraits"];
629
+ if (Array.isArray(extras)) {
630
+ const labels = [];
631
+ for (const e of extras) {
632
+ const obj = asJsonObject(e);
633
+ if (!obj) continue;
634
+ const ref = typeof obj["ref"] === "string" ? obj["ref"] : null;
635
+ const name = typeof obj["name"] === "string" ? obj["name"] : null;
636
+ const label = name && name.length > 0 ? name : ref ? tailOfRef(ref) : null;
637
+ if (label) labels.push(label);
638
+ }
639
+ if (labels.length > 0) segments.push(labels.join(", "));
640
+ }
641
+ const ruleOverlay = asJsonObject(params["ruleOverlay"]);
642
+ if (ruleOverlay) {
643
+ const rules = ruleOverlay["rules"];
644
+ if (Array.isArray(rules)) {
645
+ const caps = [];
646
+ for (const r of rules) {
647
+ const obj = asJsonObject(r);
648
+ if (obj && typeof obj["capability"] === "string") caps.push(obj["capability"]);
649
+ }
650
+ if (caps.length > 0) segments.push(caps.join(", "));
651
+ }
652
+ }
653
+ }
654
+ return segments.filter((s) => s.length > 0).join(" \xB7 ");
655
+ }
656
+ function renderKnobValue(value) {
657
+ if (value === void 0 || value === null) return "";
658
+ if (typeof value === "string") return value;
659
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
660
+ if (Array.isArray(value)) {
661
+ const parts = [];
662
+ for (const entry of value) {
663
+ if (typeof entry === "string") parts.push(entry);
664
+ else if (typeof entry === "number" || typeof entry === "boolean") parts.push(String(entry));
665
+ }
666
+ return parts.join(", ");
667
+ }
668
+ return "";
669
+ }
596
670
  function pushString(out, value) {
597
671
  if (typeof value === "string" && value.length > 0) out.push(value);
598
672
  }
@@ -627,6 +701,392 @@ function checksumSpec(spec) {
627
701
  return createHash("sha256").update(JSON.stringify(spec)).digest("hex");
628
702
  }
629
703
 
704
+ // src/workspace-index/bm25.ts
705
+ function tokenizeSpec(spec) {
706
+ const tokens = [];
707
+ pushTokens(tokens, spec["orbitalName"]);
708
+ const params = asJsonObject2(spec["params"]);
709
+ if (params) {
710
+ pushTokens(tokens, params["entityName"]);
711
+ const fields = params["entityFields"];
712
+ if (Array.isArray(fields)) {
713
+ for (const f of fields) {
714
+ const obj = asJsonObject2(f);
715
+ if (obj) pushTokens(tokens, obj["name"]);
716
+ }
717
+ }
718
+ const extras = params["extraTraits"];
719
+ if (Array.isArray(extras)) {
720
+ for (const e of extras) {
721
+ const obj = asJsonObject2(e);
722
+ if (!obj) continue;
723
+ pushTokens(tokens, obj["name"]);
724
+ const ref = obj["ref"];
725
+ if (typeof ref === "string") {
726
+ pushTokens(tokens, tailOfRef2(ref));
727
+ }
728
+ }
729
+ }
730
+ const pages = params["pages"];
731
+ if (Array.isArray(pages)) {
732
+ for (const p of pages) {
733
+ const obj = asJsonObject2(p);
734
+ if (!obj) continue;
735
+ const path9 = obj["path"];
736
+ if (typeof path9 === "string") pushTokens(tokens, path9);
737
+ }
738
+ }
739
+ const ruleOverlay = asJsonObject2(params["ruleOverlay"]);
740
+ if (ruleOverlay) {
741
+ const rules = ruleOverlay["rules"];
742
+ if (Array.isArray(rules)) {
743
+ for (const r of rules) {
744
+ const obj = asJsonObject2(r);
745
+ if (obj) pushTokens(tokens, obj["capability"]);
746
+ }
747
+ }
748
+ }
749
+ const traitOverrides = asJsonObject2(params["traitOverrides"]);
750
+ if (traitOverrides) {
751
+ for (const trait of Object.keys(traitOverrides)) {
752
+ pushTokens(tokens, trait);
753
+ const override = asJsonObject2(traitOverrides[trait]);
754
+ if (!override) continue;
755
+ const config = asJsonObject2(override["config"]);
756
+ if (!config) continue;
757
+ for (const knob of Object.keys(config)) {
758
+ pushPrimitiveValue(tokens, config[knob]);
759
+ }
760
+ }
761
+ }
762
+ }
763
+ return tokens;
764
+ }
765
+ function buildBM25Table(tokensPerOrbital) {
766
+ const documents = {};
767
+ const docFreq = {};
768
+ let totalLen = 0;
769
+ const orbitals = Object.keys(tokensPerOrbital);
770
+ for (const orbital of orbitals) {
771
+ const list = tokensPerOrbital[orbital] ?? [];
772
+ const termFreq = {};
773
+ for (const t of list) {
774
+ termFreq[t] = (termFreq[t] ?? 0) + 1;
775
+ }
776
+ const docLen = list.length;
777
+ totalLen += docLen;
778
+ documents[orbital] = { orbital, termFreq, docLen };
779
+ for (const term of Object.keys(termFreq)) {
780
+ docFreq[term] = (docFreq[term] ?? 0) + 1;
781
+ }
782
+ }
783
+ const docCount = orbitals.length;
784
+ const avgDocLen = docCount > 0 ? totalLen / docCount : 0;
785
+ return { documents, docFreq, docCount, avgDocLen };
786
+ }
787
+ function queryBM25(table, query, opts) {
788
+ const k1 = opts?.k1 ?? 1.5;
789
+ const b = opts?.b ?? 0.75;
790
+ const queryTokensRaw = tokenize(query);
791
+ if (queryTokensRaw.length === 0) return [];
792
+ const uniqueQueryTokens = Array.from(new Set(queryTokensRaw));
793
+ const { documents, docFreq, docCount, avgDocLen } = table;
794
+ if (docCount === 0 || avgDocLen === 0) return [];
795
+ const results = [];
796
+ for (const orbital of Object.keys(documents)) {
797
+ const doc = documents[orbital];
798
+ if (!doc) continue;
799
+ let score = 0;
800
+ const matched = [];
801
+ for (const qt of uniqueQueryTokens) {
802
+ const tf = doc.termFreq[qt] ?? 0;
803
+ if (tf === 0) continue;
804
+ const df = docFreq[qt] ?? 0;
805
+ const idf = Math.log1p((docCount - df + 0.5) / (df + 0.5));
806
+ const norm = tf + k1 * (1 - b + b * doc.docLen / avgDocLen);
807
+ const contribution = idf * tf * (k1 + 1) / norm;
808
+ if (contribution > 0) {
809
+ score += contribution;
810
+ matched.push(qt);
811
+ }
812
+ }
813
+ if (score > 0) results.push({ orbital, score, matchedTokens: matched });
814
+ }
815
+ results.sort((a, b2) => b2.score - a.score);
816
+ return results;
817
+ }
818
+ function tokenize(text) {
819
+ if (text.length === 0) return [];
820
+ const out = [];
821
+ const pieces = text.split(/[\s_\-/.:]+/u);
822
+ for (const piece of pieces) {
823
+ if (piece.length === 0) continue;
824
+ const lower = piece.toLowerCase();
825
+ if (lower.length >= 2) out.push(lower);
826
+ const parts = splitCamel(piece);
827
+ if (parts.length > 1) {
828
+ for (const part of parts) {
829
+ const p = part.toLowerCase();
830
+ if (p.length >= 2) out.push(p);
831
+ }
832
+ }
833
+ }
834
+ return out;
835
+ }
836
+ function splitCamel(s) {
837
+ return s.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/u);
838
+ }
839
+ function pushTokens(out, value) {
840
+ if (typeof value !== "string" || value.length === 0) return;
841
+ for (const t of tokenize(value)) out.push(t);
842
+ }
843
+ function pushPrimitiveValue(out, value) {
844
+ if (value === void 0 || value === null) return;
845
+ if (typeof value === "string") {
846
+ for (const t of tokenize(value)) out.push(t);
847
+ return;
848
+ }
849
+ if (typeof value === "number" || typeof value === "boolean") {
850
+ for (const t of tokenize(String(value))) out.push(t);
851
+ return;
852
+ }
853
+ if (Array.isArray(value)) {
854
+ for (const entry of value) pushPrimitiveValue(out, entry);
855
+ }
856
+ }
857
+ function asJsonObject2(value) {
858
+ if (value === void 0 || value === null) return null;
859
+ if (typeof value !== "object" || Array.isArray(value)) return null;
860
+ return value;
861
+ }
862
+ function tailOfRef2(ref) {
863
+ const match = /\.traits\.([A-Za-z0-9_]+)$/.exec(ref);
864
+ return match ? match[1] : ref;
865
+ }
866
+
867
+ // src/workspace-index/rrf.ts
868
+ function rrfFuse(rankings, k = RRF_K) {
869
+ if (rankings.length === 0) return [];
870
+ const order = [];
871
+ const seen = /* @__PURE__ */ new Set();
872
+ for (const ranking of rankings) {
873
+ for (const key of ranking) {
874
+ if (!seen.has(key)) {
875
+ seen.add(key);
876
+ order.push(key);
877
+ }
878
+ }
879
+ }
880
+ const ranksByKey = /* @__PURE__ */ new Map();
881
+ const scoreByKey = /* @__PURE__ */ new Map();
882
+ for (const key of order) {
883
+ ranksByKey.set(
884
+ key,
885
+ rankings.map(() => null)
886
+ );
887
+ scoreByKey.set(key, 0);
888
+ }
889
+ for (let i = 0; i < rankings.length; i++) {
890
+ const ranking = rankings[i];
891
+ if (!ranking) continue;
892
+ for (let j = 0; j < ranking.length; j++) {
893
+ const key = ranking[j];
894
+ if (key === void 0) continue;
895
+ const rank = j + 1;
896
+ const ranks = ranksByKey.get(key);
897
+ if (ranks) ranks[i] = rank;
898
+ scoreByKey.set(key, (scoreByKey.get(key) ?? 0) + 1 / (k + rank));
899
+ }
900
+ }
901
+ const indexed = order.map((key, idx) => ({
902
+ key,
903
+ idx,
904
+ score: scoreByKey.get(key) ?? 0,
905
+ ranks: ranksByKey.get(key) ?? []
906
+ }));
907
+ indexed.sort((a, b) => {
908
+ if (b.score !== a.score) return b.score - a.score;
909
+ return a.idx - b.idx;
910
+ });
911
+ return indexed.map(({ key, score, ranks }) => ({ key, score, ranks }));
912
+ }
913
+
914
+ // src/workspace-index/graph-builders.ts
915
+ function buildIntentMaps(specs) {
916
+ const entityNameOverrides = {};
917
+ const traitRefImporters = {};
918
+ const ruleCapabilities = {};
919
+ const organism = {};
920
+ const orbitalNames = Object.keys(specs).sort();
921
+ for (const orbital of orbitalNames) {
922
+ const spec = specs[orbital];
923
+ if (!spec) continue;
924
+ const organismName = typeof spec["organism"] === "string" ? spec["organism"] : null;
925
+ if (organismName && organismName.length > 0) {
926
+ (organism[organismName] ?? (organism[organismName] = [])).push(orbital);
927
+ }
928
+ const params = asJsonObject3(spec["params"]);
929
+ if (!params) continue;
930
+ const defaultEntity = typeof params["entityName"] === "string" ? params["entityName"] : null;
931
+ const traitOverrides = asJsonObject3(params["traitOverrides"]);
932
+ if (traitOverrides) {
933
+ for (const trait of Object.keys(traitOverrides).sort()) {
934
+ const override = asJsonObject3(traitOverrides[trait]);
935
+ if (!override) continue;
936
+ const linked = override["linkedEntity"];
937
+ if (typeof linked === "string" && linked.length > 0) {
938
+ (entityNameOverrides[linked] ?? (entityNameOverrides[linked] = [])).push({
939
+ orbital,
940
+ trait,
941
+ source: "override"
942
+ });
943
+ }
944
+ }
945
+ }
946
+ const extras = params["extraTraits"];
947
+ if (Array.isArray(extras)) {
948
+ for (const e of extras) {
949
+ const obj = asJsonObject3(e);
950
+ if (!obj) continue;
951
+ const ref = obj["ref"];
952
+ if (typeof ref === "string" && ref.length > 0) {
953
+ (traitRefImporters[ref] ?? (traitRefImporters[ref] = [])).push(orbital);
954
+ }
955
+ }
956
+ }
957
+ const ruleOverlay = asJsonObject3(params["ruleOverlay"]);
958
+ if (ruleOverlay) {
959
+ const rules = ruleOverlay["rules"];
960
+ if (Array.isArray(rules)) {
961
+ for (const r of rules) {
962
+ const obj = asJsonObject3(r);
963
+ if (!obj) continue;
964
+ const capability = obj["capability"];
965
+ if (typeof capability !== "string" || capability.length === 0) continue;
966
+ const entityName = typeof obj["entityName"] === "string" && obj["entityName"].length > 0 ? obj["entityName"] : defaultEntity;
967
+ if (!entityName) continue;
968
+ (ruleCapabilities[capability] ?? (ruleCapabilities[capability] = [])).push({
969
+ orbital,
970
+ entityName,
971
+ capability
972
+ });
973
+ }
974
+ }
975
+ }
976
+ }
977
+ return { entityNameOverrides, traitRefImporters, ruleCapabilities, organism };
978
+ }
979
+ function buildComposedMaps(schemaOrbContent) {
980
+ if (schemaOrbContent === null) return { events: {}, entityNameComposed: {} };
981
+ let parsed;
982
+ try {
983
+ parsed = JSON.parse(schemaOrbContent);
984
+ } catch {
985
+ return { events: {}, entityNameComposed: {} };
986
+ }
987
+ const root = asJsonObject3(parsed);
988
+ if (!root) return { events: {}, entityNameComposed: {} };
989
+ const orbitals = root["orbitals"];
990
+ if (!Array.isArray(orbitals)) return { events: {}, entityNameComposed: {} };
991
+ const events = {};
992
+ const entityNameComposed = {};
993
+ for (const o of orbitals) {
994
+ const orbObj = asJsonObject3(o);
995
+ if (!orbObj) continue;
996
+ const orbitalName = typeof orbObj["name"] === "string" ? orbObj["name"] : null;
997
+ if (!orbitalName) continue;
998
+ const entity = asJsonObject3(orbObj["entity"]);
999
+ if (entity) {
1000
+ const entityName = entity["name"];
1001
+ if (typeof entityName === "string" && entityName.length > 0) {
1002
+ (entityNameComposed[entityName] ?? (entityNameComposed[entityName] = [])).push({
1003
+ orbital: orbitalName,
1004
+ trait: "<orbital-entity>",
1005
+ source: "composed"
1006
+ });
1007
+ }
1008
+ }
1009
+ const traits = orbObj["traits"];
1010
+ if (!Array.isArray(traits)) continue;
1011
+ for (const t of traits) {
1012
+ const traitObj = asJsonObject3(t);
1013
+ if (!traitObj) continue;
1014
+ const traitName = typeof traitObj["name"] === "string" ? traitObj["name"] : null;
1015
+ if (!traitName) continue;
1016
+ const linked = traitObj["linkedEntity"];
1017
+ if (typeof linked === "string" && linked.length > 0) {
1018
+ (entityNameComposed[linked] ?? (entityNameComposed[linked] = [])).push({
1019
+ orbital: orbitalName,
1020
+ trait: traitName,
1021
+ source: "composed"
1022
+ });
1023
+ }
1024
+ const states = traitObj["states"];
1025
+ if (!Array.isArray(states)) continue;
1026
+ for (const s of states) {
1027
+ const stateObj = asJsonObject3(s);
1028
+ if (!stateObj) continue;
1029
+ const stateName = typeof stateObj["name"] === "string" ? stateObj["name"] : void 0;
1030
+ const transitions = stateObj["transitions"];
1031
+ if (!Array.isArray(transitions)) continue;
1032
+ for (const tr of transitions) {
1033
+ const trObj = asJsonObject3(tr);
1034
+ if (!trObj) continue;
1035
+ const listenEvent = trObj["event"];
1036
+ if (typeof listenEvent === "string" && listenEvent.length > 0) {
1037
+ (events[listenEvent] ?? (events[listenEvent] = [])).push({
1038
+ orbital: orbitalName,
1039
+ trait: traitName,
1040
+ state: stateName,
1041
+ role: "listen"
1042
+ });
1043
+ }
1044
+ const emit = asJsonObject3(trObj["emit"]);
1045
+ if (emit) {
1046
+ const emitEvent = emit["event"];
1047
+ if (typeof emitEvent === "string" && emitEvent.length > 0) {
1048
+ const scope = emit["scope"];
1049
+ const scopeNarrowed = scope === "internal" || scope === "external" ? scope : void 0;
1050
+ const edge = {
1051
+ orbital: orbitalName,
1052
+ trait: traitName,
1053
+ state: stateName,
1054
+ role: "emit",
1055
+ ...scopeNarrowed ? { scope: scopeNarrowed } : {}
1056
+ };
1057
+ (events[emitEvent] ?? (events[emitEvent] = [])).push(edge);
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ return { events, entityNameComposed };
1065
+ }
1066
+ function asJsonObject3(value) {
1067
+ if (value === void 0 || value === null) return null;
1068
+ if (typeof value !== "object" || Array.isArray(value)) return null;
1069
+ return value;
1070
+ }
1071
+
1072
+ // src/workspace-index/manifest.ts
1073
+ function manifestPath(workDir) {
1074
+ return workspaceIndexManifestFile(workDir);
1075
+ }
1076
+ async function readManifest(backend, workDir) {
1077
+ const raw = await readJsonFile(backend, manifestPath(workDir));
1078
+ if (raw === null) return null;
1079
+ if (raw.schemaVersion !== WORKSPACE_INDEX_SCHEMA_VERSION) return null;
1080
+ return raw;
1081
+ }
1082
+ async function writeManifest(backend, workDir, manifest) {
1083
+ await writeJsonFile(
1084
+ backend,
1085
+ manifestPath(workDir),
1086
+ manifest
1087
+ );
1088
+ }
1089
+
630
1090
  // src/workspace-index/index-impl.ts
631
1091
  var WorkspaceIndexImpl = class {
632
1092
  constructor(deps) {
@@ -636,13 +1096,53 @@ var WorkspaceIndexImpl = class {
636
1096
  this.bakeQueue = /* @__PURE__ */ new Map();
637
1097
  /** Names whose checksum doesn't match the current spec — surfaced via stats. */
638
1098
  this.stale = /* @__PURE__ */ new Set();
1099
+ /** Cached spec.json per orbital so manifest rebuilds don't re-read disk. */
1100
+ this.specsByOrbital = /* @__PURE__ */ new Map();
1101
+ /** Tokenized BM25 rows per orbital — rebuilt incrementally. */
1102
+ this.tokensByOrbital = /* @__PURE__ */ new Map();
1103
+ /** Last-known recency timeline per orbital. */
1104
+ this.recencyByOrbital = /* @__PURE__ */ new Map();
1105
+ /** Latest manifest payload (BM25 + intent + composed + recency). */
1106
+ this.manifest = emptyManifest();
1107
+ /** Serialized manifest writes so concurrent observer fires don't race. */
1108
+ this.manifestQueue = Promise.resolve();
639
1109
  this.deps.sinks.subscribe({
640
- onWrite: async (event) => {
641
- if (event.kind !== "spec") return;
642
- await this.rebakeOrbital(event.orbital, event.content);
643
- }
1110
+ onWrite: (event) => this.onWorkspaceWrite(event)
644
1111
  });
645
1112
  }
1113
+ async onWorkspaceWrite(event) {
1114
+ switch (event.kind) {
1115
+ case "spec":
1116
+ await this.rebakeOrbital(event.orbital, event.content);
1117
+ this.specsByOrbital.set(event.orbital, event.content);
1118
+ this.tokensByOrbital.set(event.orbital, tokenizeSpec(event.content));
1119
+ await this.scheduleManifestRebuild();
1120
+ return;
1121
+ case "schema":
1122
+ await this.scheduleManifestRebuild();
1123
+ return;
1124
+ case "params-history":
1125
+ this.recencyByOrbital.set(event.orbital, deriveRecencyEntry(event.orbital, [event.row], this.recencyByOrbital.get(event.orbital)));
1126
+ await this.scheduleManifestRebuild();
1127
+ return;
1128
+ case "orbital-archived":
1129
+ this.entries.delete(event.name);
1130
+ this.specsByOrbital.delete(event.name);
1131
+ this.tokensByOrbital.delete(event.name);
1132
+ this.recencyByOrbital.delete(event.name);
1133
+ await this.scheduleManifestRebuild();
1134
+ return;
1135
+ case "orbital-renamed":
1136
+ renameKey(this.entries, event.from, event.to);
1137
+ renameKey(this.specsByOrbital, event.from, event.to);
1138
+ renameKey(this.tokensByOrbital, event.from, event.to);
1139
+ renameKey(this.recencyByOrbital, event.from, event.to);
1140
+ await this.scheduleManifestRebuild();
1141
+ return;
1142
+ default:
1143
+ return;
1144
+ }
1145
+ }
646
1146
  async warm() {
647
1147
  const orbitals = this.deps.listOrbitals();
648
1148
  const work = [];
@@ -651,6 +1151,12 @@ var WorkspaceIndexImpl = class {
651
1151
  if (spec === null) {
652
1152
  continue;
653
1153
  }
1154
+ this.specsByOrbital.set(orbital, spec);
1155
+ this.tokensByOrbital.set(orbital, tokenizeSpec(spec));
1156
+ const history = this.deps.readHistory(orbital);
1157
+ if (history.length > 0) {
1158
+ this.recencyByOrbital.set(orbital, deriveRecencyEntry(orbital, history, void 0));
1159
+ }
654
1160
  const checksum = checksumSpec(spec);
655
1161
  const existing = await readSidecar(this.deps.backend, this.deps.workDir, orbital);
656
1162
  if (existing !== null && existing.specChecksum === checksum) {
@@ -661,6 +1167,12 @@ var WorkspaceIndexImpl = class {
661
1167
  work.push(this.bakeAndPersist(orbital, spec, checksum));
662
1168
  }
663
1169
  await Promise.all(work);
1170
+ const restored = await readManifest(this.deps.backend, this.deps.workDir);
1171
+ if (restored !== null) {
1172
+ this.manifest = restored;
1173
+ this.hydrateRecencyFromManifest(restored);
1174
+ }
1175
+ await this.rebuildManifest();
664
1176
  }
665
1177
  async resolveOrbitalName(name, opts) {
666
1178
  const threshold = opts?.threshold ?? DEFAULT_COERCION_THRESHOLD;
@@ -715,6 +1227,133 @@ var WorkspaceIndexImpl = class {
715
1227
  staleOrbitals: Array.from(this.stale).sort()
716
1228
  };
717
1229
  }
1230
+ // ── Phase B — RRF-hybrid retrieval ──────────────────────────────────────────
1231
+ async retrieveOrbitalsForPrompt(prompt, opts) {
1232
+ const topK = opts?.topK ?? DEFAULT_RETRIEVAL_TOP_K;
1233
+ const rrfK = opts?.rrfK ?? RRF_K;
1234
+ if (this.entries.size === 0) return [];
1235
+ const sparseHits = queryBM25(this.manifest.bm25, prompt, opts?.bm25);
1236
+ const sparseRanking = sparseHits.map((h) => h.orbital);
1237
+ const matchedTokensByOrbital = /* @__PURE__ */ new Map();
1238
+ for (const hit of sparseHits) matchedTokensByOrbital.set(hit.orbital, hit.matchedTokens);
1239
+ const [promptVector] = await this.embedOne(prompt);
1240
+ const dense = [];
1241
+ for (const [orbital, entry] of this.entries) {
1242
+ const sim = cosineSimilarity(promptVector, entry.contentVector);
1243
+ if (sim > 0) dense.push({ orbital, sim });
1244
+ }
1245
+ dense.sort((a, b) => b.sim - a.sim);
1246
+ const denseRanking = dense.map((d) => d.orbital);
1247
+ const fused = rrfFuse([denseRanking, sparseRanking], rrfK);
1248
+ return fused.slice(0, topK).map((entry) => ({
1249
+ orbitalName: entry.key,
1250
+ score: entry.score,
1251
+ matchedTokens: matchedTokensByOrbital.get(entry.key) ?? [],
1252
+ matchedKnobs: this.explainKnobMatches(entry.key, prompt),
1253
+ rankByDense: entry.ranks[0] ?? null,
1254
+ rankBySparse: entry.ranks[1] ?? null
1255
+ }));
1256
+ }
1257
+ findByToken(query) {
1258
+ const hits = queryBM25(this.manifest.bm25, query);
1259
+ return hits.map((h) => h.orbital);
1260
+ }
1261
+ // ── Phase C — graph queries ────────────────────────────────────────────────
1262
+ orbitalsListeningTo(event) {
1263
+ return (this.manifest.composed.events[event] ?? []).filter((e) => e.role === "listen");
1264
+ }
1265
+ orbitalsEmitting(event) {
1266
+ return (this.manifest.composed.events[event] ?? []).filter((e) => e.role === "emit");
1267
+ }
1268
+ orbitalsLinkedToEntity(entity) {
1269
+ const composed = this.manifest.composed.entityNameComposed[entity] ?? [];
1270
+ const override = this.manifest.intent.entityNameOverrides[entity] ?? [];
1271
+ return [...composed, ...override];
1272
+ }
1273
+ extraTraitImporters(refPath) {
1274
+ return this.manifest.intent.traitRefImporters[refPath] ?? [];
1275
+ }
1276
+ rulesAppliedTo(entity) {
1277
+ const out = [];
1278
+ for (const bindings of Object.values(this.manifest.intent.ruleCapabilities)) {
1279
+ for (const b of bindings) if (b.entityName === entity) out.push(b);
1280
+ }
1281
+ return out;
1282
+ }
1283
+ recentlyEdited(opts) {
1284
+ const withinTurns = opts?.withinTurns ?? 5;
1285
+ const entries = [];
1286
+ for (const [orbital, recency] of Object.entries(this.manifest.recency)) {
1287
+ entries.push({ orbital, recencyTurn: recency.recencyTurn });
1288
+ }
1289
+ if (entries.length === 0) return [];
1290
+ const maxTurn = entries.reduce((m, e) => e.recencyTurn > m ? e.recencyTurn : m, -Infinity);
1291
+ entries.sort((a, b) => b.recencyTurn - a.recencyTurn);
1292
+ return entries.filter((e) => e.recencyTurn > maxTurn - withinTurns).map((e) => e.orbital);
1293
+ }
1294
+ // ── Manifest internals ─────────────────────────────────────────────────────
1295
+ scheduleManifestRebuild() {
1296
+ const next = this.manifestQueue.then(() => this.rebuildManifest());
1297
+ this.manifestQueue = next.catch(() => void 0);
1298
+ return next;
1299
+ }
1300
+ async rebuildManifest() {
1301
+ const tokensPerOrbital = {};
1302
+ for (const [orbital, tokens] of this.tokensByOrbital) {
1303
+ tokensPerOrbital[orbital] = tokens;
1304
+ }
1305
+ const specsPerOrbital = {};
1306
+ for (const [orbital, spec] of this.specsByOrbital) {
1307
+ specsPerOrbital[orbital] = spec;
1308
+ }
1309
+ const recency = {};
1310
+ for (const [orbital, entry] of this.recencyByOrbital) {
1311
+ recency[orbital] = entry;
1312
+ }
1313
+ const next = {
1314
+ schemaVersion: WORKSPACE_INDEX_SCHEMA_VERSION,
1315
+ bm25: buildBM25Table(tokensPerOrbital),
1316
+ intent: buildIntentMaps(specsPerOrbital),
1317
+ composed: buildComposedMaps(this.deps.readSchema()),
1318
+ recency,
1319
+ bakedAt: Date.now()
1320
+ };
1321
+ this.manifest = next;
1322
+ await writeManifest(this.deps.backend, this.deps.workDir, next);
1323
+ await this.deps.sinks.notifyAll({
1324
+ kind: "workspace-index-manifest",
1325
+ content: serializeManifestForMirror(next)
1326
+ });
1327
+ }
1328
+ hydrateRecencyFromManifest(manifest) {
1329
+ for (const [orbital, entry] of Object.entries(manifest.recency)) {
1330
+ this.recencyByOrbital.set(orbital, entry);
1331
+ }
1332
+ }
1333
+ explainKnobMatches(orbital, prompt) {
1334
+ const spec = this.specsByOrbital.get(orbital);
1335
+ if (!spec) return [];
1336
+ const params = asJsonObject4(spec["params"]);
1337
+ if (!params) return [];
1338
+ const traitOverrides = asJsonObject4(params["traitOverrides"]);
1339
+ if (!traitOverrides) return [];
1340
+ const promptLower = prompt.toLowerCase();
1341
+ const out = [];
1342
+ for (const trait of Object.keys(traitOverrides).sort()) {
1343
+ const override = asJsonObject4(traitOverrides[trait]);
1344
+ if (!override) continue;
1345
+ const config = asJsonObject4(override["config"]);
1346
+ if (!config) continue;
1347
+ for (const knob of Object.keys(config).sort()) {
1348
+ const rendered = renderKnobValueForExplain(config[knob]);
1349
+ if (rendered.length === 0) continue;
1350
+ if (promptLower.includes(rendered.toLowerCase())) {
1351
+ out.push(`${trait}.${knob}`);
1352
+ }
1353
+ }
1354
+ }
1355
+ return out;
1356
+ }
718
1357
  async rebakeOrbital(orbital, spec) {
719
1358
  const checksum = checksumSpec(spec);
720
1359
  const existing = this.entries.get(orbital);
@@ -731,15 +1370,20 @@ var WorkspaceIndexImpl = class {
731
1370
  async bakeAndPersist(orbital, spec, checksum) {
732
1371
  this.stale.add(orbital);
733
1372
  const identityFingerprint = composeOrbitalIdentityFingerprint(spec);
1373
+ const contentFingerprint = composeOrbitalContentFingerprint(spec);
734
1374
  const extraInputs = extractExtraTraitInputs(spec);
735
- const allTexts = [identityFingerprint, ...extraInputs.map((e) => e.fingerprint)];
1375
+ const allTexts = [
1376
+ identityFingerprint,
1377
+ contentFingerprint,
1378
+ ...extraInputs.map((e) => e.fingerprint)
1379
+ ];
736
1380
  const { embeddings } = await this.deps.embedder.embedBatch(allTexts);
737
1381
  if (embeddings.length !== allTexts.length) {
738
1382
  throw new Error(
739
1383
  `[workspace-index] embedder returned ${embeddings.length} vectors for ${allTexts.length} inputs`
740
1384
  );
741
1385
  }
742
- const [identityVector, ...extraVectors] = embeddings;
1386
+ const [identityVector, contentVector, ...extraVectors] = embeddings;
743
1387
  const extraTraitIdentities = extraInputs.map((input, idx) => ({
744
1388
  ref: input.ref,
745
1389
  alias: input.alias,
@@ -751,6 +1395,8 @@ var WorkspaceIndexImpl = class {
751
1395
  specChecksum: checksum,
752
1396
  identityVector,
753
1397
  identityFingerprint,
1398
+ contentVector,
1399
+ contentFingerprint,
754
1400
  extraTraitIdentities,
755
1401
  bakedAt: Date.now()
756
1402
  };
@@ -779,6 +1425,8 @@ function serializeEntryForMirror(entry) {
779
1425
  specChecksum: entry.specChecksum,
780
1426
  identityVector: [...entry.identityVector],
781
1427
  identityFingerprint: entry.identityFingerprint,
1428
+ contentVector: [...entry.contentVector],
1429
+ contentFingerprint: entry.contentFingerprint,
782
1430
  extraTraitIdentities: entry.extraTraitIdentities.map((e) => ({
783
1431
  ref: e.ref,
784
1432
  alias: e.alias,
@@ -789,13 +1437,13 @@ function serializeEntryForMirror(entry) {
789
1437
  };
790
1438
  }
791
1439
  function extractExtraTraitInputs(spec) {
792
- const params = asJsonObject2(spec["params"]);
1440
+ const params = asJsonObject4(spec["params"]);
793
1441
  if (!params) return [];
794
1442
  const extras = params["extraTraits"];
795
1443
  if (!Array.isArray(extras)) return [];
796
1444
  const out = [];
797
1445
  for (const entry of extras) {
798
- const obj = asJsonObject2(entry);
1446
+ const obj = asJsonObject4(entry);
799
1447
  if (!obj) continue;
800
1448
  const ref = typeof obj["ref"] === "string" ? obj["ref"] : null;
801
1449
  if (ref === null) continue;
@@ -810,11 +1458,102 @@ function extractExtraTraitInputs(spec) {
810
1458
  }
811
1459
  return out;
812
1460
  }
813
- function asJsonObject2(value) {
1461
+ function asJsonObject4(value) {
814
1462
  if (value === void 0 || value === null) return null;
815
1463
  if (typeof value !== "object" || Array.isArray(value)) return null;
816
1464
  return value;
817
1465
  }
1466
+ function emptyManifest() {
1467
+ return {
1468
+ schemaVersion: WORKSPACE_INDEX_SCHEMA_VERSION,
1469
+ bm25: { documents: {}, docFreq: {}, docCount: 0, avgDocLen: 0 },
1470
+ intent: { entityNameOverrides: {}, traitRefImporters: {}, ruleCapabilities: {}, organism: {} },
1471
+ composed: { events: {}, entityNameComposed: {} },
1472
+ recency: {},
1473
+ bakedAt: 0
1474
+ };
1475
+ }
1476
+ function deriveRecencyEntry(_orbital, rows, prior) {
1477
+ let recencyTurn = prior?.recencyTurn ?? 0;
1478
+ let lastChange = prior?.lastChange ?? 0;
1479
+ for (let i = 0; i < rows.length; i++) {
1480
+ const row = rows[i];
1481
+ const turn = typeof row["turn"] === "number" ? row["turn"] : recencyTurn + 1;
1482
+ const ts = typeof row["timestamp"] === "number" ? row["timestamp"] : 0;
1483
+ if (turn > recencyTurn) recencyTurn = turn;
1484
+ if (ts > lastChange) lastChange = ts;
1485
+ }
1486
+ return { recencyTurn, lastChange };
1487
+ }
1488
+ function renameKey(map, from, to) {
1489
+ if (!map.has(from)) return;
1490
+ const value = map.get(from);
1491
+ if (value === void 0) return;
1492
+ map.delete(from);
1493
+ map.set(to, value);
1494
+ }
1495
+ function renderKnobValueForExplain(value) {
1496
+ if (typeof value === "string") return value;
1497
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1498
+ return "";
1499
+ }
1500
+ function serializeManifestForMirror(manifest) {
1501
+ return {
1502
+ schemaVersion: manifest.schemaVersion,
1503
+ bm25: serializeBM25(manifest.bm25),
1504
+ intent: serializeIntent(manifest.intent),
1505
+ composed: serializeComposed(manifest.composed),
1506
+ recency: serializeRecency(manifest.recency),
1507
+ bakedAt: manifest.bakedAt
1508
+ };
1509
+ }
1510
+ function serializeBM25(t) {
1511
+ const documents = {};
1512
+ for (const [orbital, doc] of Object.entries(t.documents)) {
1513
+ documents[orbital] = serializeBM25Doc(doc);
1514
+ }
1515
+ return {
1516
+ documents,
1517
+ docFreq: { ...t.docFreq },
1518
+ docCount: t.docCount,
1519
+ avgDocLen: t.avgDocLen
1520
+ };
1521
+ }
1522
+ function serializeBM25Doc(doc) {
1523
+ return {
1524
+ orbital: doc.orbital,
1525
+ termFreq: { ...doc.termFreq },
1526
+ docLen: doc.docLen
1527
+ };
1528
+ }
1529
+ function serializeIntent(intent) {
1530
+ return {
1531
+ entityNameOverrides: serializeMapOfArrays(intent.entityNameOverrides, (b) => ({ ...b })),
1532
+ traitRefImporters: serializeMapOfArrays(intent.traitRefImporters, (s) => s),
1533
+ ruleCapabilities: serializeMapOfArrays(intent.ruleCapabilities, (b) => ({ ...b })),
1534
+ organism: serializeMapOfArrays(intent.organism, (s) => s)
1535
+ };
1536
+ }
1537
+ function serializeComposed(composed) {
1538
+ return {
1539
+ events: serializeMapOfArrays(composed.events, (e) => ({ ...e })),
1540
+ entityNameComposed: serializeMapOfArrays(composed.entityNameComposed, (b) => ({ ...b }))
1541
+ };
1542
+ }
1543
+ function serializeRecency(recency) {
1544
+ const out = {};
1545
+ for (const [orbital, entry] of Object.entries(recency)) {
1546
+ out[orbital] = { recencyTurn: entry.recencyTurn, lastChange: entry.lastChange };
1547
+ }
1548
+ return out;
1549
+ }
1550
+ function serializeMapOfArrays(map, projectOne) {
1551
+ const out = {};
1552
+ for (const [key, items] of Object.entries(map)) {
1553
+ out[key] = items.map(projectOne);
1554
+ }
1555
+ return out;
1556
+ }
818
1557
 
819
1558
  // src/service.ts
820
1559
  var COORD_FILES = {
@@ -847,7 +1586,9 @@ var WorkspaceServiceImpl = class {
847
1586
  sinks: this.sinks,
848
1587
  embedder: args.embedder,
849
1588
  listOrbitals: () => this.listOrbitals(),
850
- readSpec: (orbital) => this.readSpec(orbital)
1589
+ readSpec: (orbital) => this.readSpec(orbital),
1590
+ readSchema: () => this.readSchema(),
1591
+ readHistory: (orbital) => this.readHistory(orbital)
851
1592
  });
852
1593
  }
853
1594
  // === Identity ===
@@ -1405,6 +2146,6 @@ async function resolveLifecycle(backend, opts) {
1405
2146
  return { workDir, appId: opts.appId };
1406
2147
  }
1407
2148
 
1408
- export { DEFAULT_COERCION_THRESHOLD, WORKSPACE_INDEX_SCHEMA_VERSION, openWorkspace };
2149
+ export { DEFAULT_COERCION_THRESHOLD, DEFAULT_RETRIEVAL_TOP_K, RRF_K, WORKSPACE_INDEX_SCHEMA_VERSION, openWorkspace };
1409
2150
  //# sourceMappingURL=index.js.map
1410
2151
  //# sourceMappingURL=index.js.map