@appchy/jarvis 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/bin/config-change.mjs +1 -1
  2. package/bin/jarvis.mjs +8 -12
  3. package/bin/pre-tool-use.mjs +2 -2
  4. package/bin/session-start.mjs +1 -1
  5. package/bin/stop.mjs +1 -1
  6. package/bin/user-prompt-submit.mjs +1 -1
  7. package/dist/bin.js +10168 -37341
  8. package/dist/bin.js.map +1 -1
  9. package/dist/data/backends.mjs +469 -0
  10. package/dist/data/chunk-7REP35VA.mjs +462 -0
  11. package/dist/data/chunk-AKQQC5IT.mjs +1224 -0
  12. package/dist/data/chunk-AYOJSS2F.mjs +72 -0
  13. package/dist/data/chunk-RRJ6KKYL.mjs +42 -0
  14. package/dist/data/chunk-YWSWQEJN.mjs +138 -0
  15. package/dist/data/embedders.mjs +80 -0
  16. package/dist/data/finders.mjs +35 -0
  17. package/dist/data/index.mjs +137 -0
  18. package/dist/data/linkers.mjs +83 -0
  19. package/dist/data/mcp.mjs +2624 -0
  20. package/dist/data/persistences.mjs +8 -0
  21. package/dist/data/rerankers.mjs +52 -0
  22. package/dist/data/stores.mjs +38 -0
  23. package/dist/hooks/config-change.js +7 -29
  24. package/dist/hooks/config-change.js.map +1 -1
  25. package/dist/hooks/pre-tool-use.js +129 -32
  26. package/dist/hooks/pre-tool-use.js.map +1 -1
  27. package/dist/hooks/session-start.js +72 -37
  28. package/dist/hooks/session-start.js.map +1 -1
  29. package/dist/hooks/stop.js +140 -32
  30. package/dist/hooks/stop.js.map +1 -1
  31. package/dist/hooks/user-prompt-submit.js +7 -29
  32. package/dist/hooks/user-prompt-submit.js.map +1 -1
  33. package/harness/assets/architecture-template.md +53 -0
  34. package/harness/assets/design-template.md +89 -0
  35. package/harness/assets/domains/architecture.md +43 -0
  36. package/harness/assets/domains/commercial.md +42 -0
  37. package/harness/assets/domains/design.md +42 -0
  38. package/harness/assets/domains/legal.md +43 -0
  39. package/harness/assets/domains/operations.md +39 -0
  40. package/harness/assets/domains/product.md +43 -0
  41. package/harness/assets/domains/quality.md +43 -0
  42. package/harness/assets/domains/security.md +44 -0
  43. package/harness/assets/domains/support.md +45 -0
  44. package/harness/assets/epic-template.md +85 -0
  45. package/harness/assets/feature-template.md +86 -0
  46. package/harness/assets/handoff-template.md +26 -0
  47. package/harness/assets/task-template.md +38 -0
  48. package/harness/assets/version-template.md +43 -0
  49. package/harness/harness/__init__.py +7 -0
  50. package/harness/harness/align.py +582 -0
  51. package/harness/harness/architecture.py +254 -0
  52. package/harness/harness/autonomy.py +374 -0
  53. package/harness/harness/branches.py +408 -0
  54. package/harness/harness/config.py +1482 -0
  55. package/harness/harness/coverage.py +199 -0
  56. package/harness/harness/epic.py +220 -0
  57. package/harness/harness/events.py +153 -0
  58. package/harness/harness/extend.py +99 -0
  59. package/harness/harness/frontmatter.py +218 -0
  60. package/harness/harness/gate.py +591 -0
  61. package/harness/harness/generate.py +267 -0
  62. package/harness/harness/git.py +775 -0
  63. package/harness/harness/ids.py +140 -0
  64. package/harness/harness/kickoff.py +231 -0
  65. package/harness/harness/lint.py +505 -0
  66. package/harness/harness/model.py +364 -0
  67. package/harness/harness/peers.py +187 -0
  68. package/harness/harness/product.py +29 -0
  69. package/harness/harness/registry.py +382 -0
  70. package/harness/harness/report.py +227 -0
  71. package/harness/harness/safety.py +387 -0
  72. package/harness/harness/scaffold.py +129 -0
  73. package/harness/harness/shard.py +63 -0
  74. package/harness/harness/shift.py +348 -0
  75. package/harness/harness/task.py +507 -0
  76. package/harness/harness/tree.py +258 -0
  77. package/harness/harness/version.py +305 -0
  78. package/harness/harness/wrap.py +217 -0
  79. package/harness/hooks/guard.py +259 -0
  80. package/harness/presets/appchy/PRESET.md +717 -0
  81. package/harness/presets/appchy/references/artifacts.md +539 -0
  82. package/harness/presets/appchy/references/graph.md +154 -0
  83. package/harness/presets/appchy/references/operations.md +444 -0
  84. package/harness/presets/appchy/references/research.md +216 -0
  85. package/harness/schema/work.config.schema.json +401 -0
  86. package/harness/test_work.py +5002 -0
  87. package/harness/work.py +534 -0
  88. package/package.json +34 -40
  89. package/bin/config-change.dev.mjs +0 -17
  90. package/bin/jarvis-dev.mjs +0 -30
  91. package/bin/pre-tool-use.dev.mjs +0 -23
  92. package/bin/session-start.dev.mjs +0 -17
  93. package/bin/stop.dev.mjs +0 -17
  94. package/bin/user-prompt-submit.dev.mjs +0 -17
  95. package/dev/bin.js +0 -38821
  96. package/dev/bin.js.map +0 -1
  97. package/dev/env.json +0 -1
  98. package/dev/hooks/config-change.js +0 -110
  99. package/dev/hooks/config-change.js.map +0 -1
  100. package/dev/hooks/pre-tool-use.js +0 -120
  101. package/dev/hooks/pre-tool-use.js.map +0 -1
  102. package/dev/hooks/session-start.js +0 -115
  103. package/dev/hooks/session-start.js.map +0 -1
  104. package/dev/hooks/stop.js +0 -112
  105. package/dev/hooks/stop.js.map +0 -1
  106. package/dev/hooks/user-prompt-submit.js +0 -111
  107. package/dev/hooks/user-prompt-submit.js.map +0 -1
  108. package/dist/env.json +0 -1
@@ -0,0 +1,72 @@
1
+ import {
2
+ files
3
+ } from "./chunk-YWSWQEJN.mjs";
4
+
5
+ // ../../packages/data/src/define.ts
6
+ function config(entries, settings) {
7
+ return { ...settings, plugins: entries };
8
+ }
9
+ function map(where, how, options = {}) {
10
+ const from = typeof where === "string" || Array.isArray(where) ? files(where) : where;
11
+ return {
12
+ entry: "map",
13
+ name: options.name ?? from.name,
14
+ from,
15
+ how,
16
+ ...options.dedupe !== void 0 ? { dedupe: options.dedupe } : {},
17
+ ...options.lens !== void 0 ? { lens: options.lens } : {}
18
+ };
19
+ }
20
+ function connect(run, options = {}) {
21
+ return { entry: "connect", name: options.name ?? "connect", run };
22
+ }
23
+ function source(spec) {
24
+ return { ...spec, seam: "source" };
25
+ }
26
+ function linker(spec) {
27
+ return { ...spec, seam: "linker" };
28
+ }
29
+ function enforcer(spec) {
30
+ return { ...spec, seam: "enforcer" };
31
+ }
32
+ function enricher(spec) {
33
+ return { ...spec, seam: "enricher" };
34
+ }
35
+ function backend(spec) {
36
+ return { ...spec, seam: "backend" };
37
+ }
38
+ function persistence(spec) {
39
+ return { ...spec, seam: "persistence" };
40
+ }
41
+ function labeler(spec) {
42
+ return { ...spec, seam: "labeler" };
43
+ }
44
+ function embedder(spec) {
45
+ return { ...spec, seam: "embedder" };
46
+ }
47
+ function store(spec) {
48
+ return { ...spec, seam: "store" };
49
+ }
50
+ function reranker(spec) {
51
+ return { ...spec, seam: "reranker" };
52
+ }
53
+ function extractor(spec) {
54
+ return { ...spec, seam: "extractor" };
55
+ }
56
+
57
+ export {
58
+ config,
59
+ map,
60
+ connect,
61
+ source,
62
+ linker,
63
+ enforcer,
64
+ enricher,
65
+ backend,
66
+ persistence,
67
+ labeler,
68
+ embedder,
69
+ store,
70
+ reranker,
71
+ extractor
72
+ };
@@ -0,0 +1,42 @@
1
+ import {
2
+ persistence
3
+ } from "./chunk-AYOJSS2F.mjs";
4
+
5
+ // ../../packages/data/src/persistences/local.ts
6
+ import { mkdir, readFile, writeFile } from "fs/promises";
7
+ import { dirname, join, resolve } from "path";
8
+ function localFiles(options = {}) {
9
+ const root = options.root ?? ".data";
10
+ const rel = {
11
+ snapshot: options.snapshot ?? "snapshot.json",
12
+ overlay: options.overlay ?? "trace.json",
13
+ embeddings: options.embeddings ?? "embeddings.json",
14
+ state: options.state ?? "state.json",
15
+ memory: options.memory ?? "memory.json"
16
+ };
17
+ const pathOf = (ctx, key) => resolve(
18
+ ctx.repoRoot,
19
+ root,
20
+ key.startsWith("plugin:") ? join("plugins", `${key.slice(7)}.json`) : rel[key]
21
+ );
22
+ return persistence({
23
+ name: "local-files",
24
+ load: async (ctx, key) => {
25
+ try {
26
+ return await readFile(pathOf(ctx, key), "utf8");
27
+ } catch {
28
+ return void 0;
29
+ }
30
+ },
31
+ save: async (ctx, key, bytes) => {
32
+ const path = pathOf(ctx, key);
33
+ await mkdir(dirname(path), { recursive: true });
34
+ await writeFile(path, bytes, "utf8");
35
+ },
36
+ locate: (ctx, key) => pathOf(ctx, key)
37
+ });
38
+ }
39
+
40
+ export {
41
+ localFiles
42
+ };
@@ -0,0 +1,138 @@
1
+ // ../../packages/data/src/frontmatter.ts
2
+ var FENCE = /^---\s*$/;
3
+ function parseFrontmatter(source) {
4
+ const lines = source.split(/\r?\n/);
5
+ if (lines[0] === void 0 || !FENCE.test(lines[0])) {
6
+ return { data: {}, body: source };
7
+ }
8
+ const data = {};
9
+ let i = 1;
10
+ let pendingKey = null;
11
+ let pendingList = [];
12
+ const flushList = () => {
13
+ if (pendingKey !== null) {
14
+ data[pendingKey] = pendingList;
15
+ pendingKey = null;
16
+ pendingList = [];
17
+ }
18
+ };
19
+ for (; i < lines.length; i++) {
20
+ const line = lines[i] ?? "";
21
+ if (FENCE.test(line)) {
22
+ flushList();
23
+ i++;
24
+ break;
25
+ }
26
+ const listItem = /^\s*-\s+(.*)$/.exec(line);
27
+ if (listItem && pendingKey !== null) {
28
+ pendingList.push(stripQuotes((listItem[1] ?? "").trim()));
29
+ continue;
30
+ }
31
+ const kv = /^([A-Za-z0-9_.-]+):\s*(.*)$/.exec(line);
32
+ if (kv) {
33
+ flushList();
34
+ const key = kv[1] ?? "";
35
+ const rawValue = (kv[2] ?? "").trim();
36
+ if (rawValue === "") {
37
+ pendingKey = key;
38
+ pendingList = [];
39
+ } else if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
40
+ data[key] = rawValue.slice(1, -1).split(",").map((s) => stripQuotes(s.trim())).filter((s) => s.length > 0);
41
+ } else {
42
+ data[key] = stripQuotes(rawValue);
43
+ }
44
+ }
45
+ }
46
+ flushList();
47
+ return { data, body: lines.slice(i).join("\n") };
48
+ }
49
+ function stripQuotes(value) {
50
+ if (value.length >= 2) {
51
+ const first = value[0];
52
+ const last = value[value.length - 1];
53
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
54
+ return value.slice(1, -1);
55
+ }
56
+ }
57
+ return value;
58
+ }
59
+
60
+ // ../../packages/data/src/paths.ts
61
+ import { relative } from "path";
62
+ import fg from "fast-glob";
63
+ import picomatch from "picomatch";
64
+ var DEFAULT_IGNORE = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
65
+ async function glob(patterns, request = {}) {
66
+ const cwd = request.cwd ?? process.cwd();
67
+ const found = await fg(patterns, {
68
+ cwd,
69
+ absolute: true,
70
+ onlyFiles: true,
71
+ ignore: request.ignore ?? DEFAULT_IGNORE
72
+ });
73
+ const paths = request.absolute === true ? found : found.map((path) => relative(cwd, path));
74
+ return paths.sort();
75
+ }
76
+ function matcher(patterns) {
77
+ const list = Array.isArray(patterns) ? patterns : [patterns];
78
+ const include = list.filter((pattern) => !pattern.startsWith("!"));
79
+ const exclude = list.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
80
+ if (include.length === 0) return () => false;
81
+ return picomatch(include, exclude.length > 0 ? { ignore: exclude } : {});
82
+ }
83
+ function beneath(path) {
84
+ const root = path.trim().replace(/\/+$/, "");
85
+ return (file) => file === root || file.startsWith(`${root}/`);
86
+ }
87
+ function dirOf(path) {
88
+ const cut = path.lastIndexOf("/");
89
+ return cut === -1 ? "" : path.slice(0, cut);
90
+ }
91
+
92
+ // ../../packages/data/src/finders/files.ts
93
+ import { readFile } from "fs/promises";
94
+ import { relative as relative2, resolve } from "path";
95
+ import fg2 from "fast-glob";
96
+ function files(patterns, options = {}) {
97
+ const globs = Array.isArray(patterns) ? patterns : [patterns];
98
+ return {
99
+ name: `files(${globs.join(", ")})`,
100
+ ...options.schema !== void 0 ? { schema: options.schema } : {},
101
+ ...options.lens !== void 0 ? { lens: options.lens } : {},
102
+ list: async (ctx) => {
103
+ const root = ctx.repoRoot;
104
+ const matched = await fg2(globs, {
105
+ cwd: options.cwd ? resolve(root, options.cwd) : root,
106
+ absolute: true,
107
+ onlyFiles: true,
108
+ ignore: options.ignore ?? DEFAULT_IGNORE
109
+ });
110
+ const whole = options.text === "file";
111
+ const read = await Promise.all(matched.map((file) => toItem(root, file, whole)));
112
+ return read.filter((item) => item !== void 0);
113
+ }
114
+ };
115
+ }
116
+ async function toItem(root, file, whole) {
117
+ const raw = await readFile(file, "utf8").catch(() => "");
118
+ if (!raw) return void 0;
119
+ const ref = relative2(root, file);
120
+ const { data, body } = parseFrontmatter(raw);
121
+ const text = whole ? raw : body;
122
+ return {
123
+ ref,
124
+ file: ref,
125
+ data,
126
+ text: async () => text
127
+ };
128
+ }
129
+
130
+ export {
131
+ parseFrontmatter,
132
+ DEFAULT_IGNORE,
133
+ glob,
134
+ matcher,
135
+ beneath,
136
+ dirOf,
137
+ files
138
+ };
@@ -0,0 +1,80 @@
1
+ import "./chunk-AKQQC5IT.mjs";
2
+ import "./chunk-7REP35VA.mjs";
3
+ import "./chunk-RRJ6KKYL.mjs";
4
+ import {
5
+ embedder
6
+ } from "./chunk-AYOJSS2F.mjs";
7
+ import "./chunk-YWSWQEJN.mjs";
8
+
9
+ // ../../packages/data/src/embedders/local.ts
10
+ var BGE_QUERY_INSTRUCTION = "Represent this sentence for searching relevant passages: ";
11
+ function resolveQueryInstruction(model) {
12
+ return /bge/i.test(model) ? BGE_QUERY_INSTRUCTION : "";
13
+ }
14
+ function localEmbeddings(options = {}) {
15
+ const model = options.model ?? "Xenova/bge-small-en-v1.5";
16
+ const pkg = "@huggingface/transformers";
17
+ let pipe;
18
+ const extractor = async () => {
19
+ if (!pipe) {
20
+ const mod = await import(pkg).catch(() => {
21
+ throw new Error(
22
+ "localEmbeddings needs '@huggingface/transformers' \u2014 run `pnpm add @huggingface/transformers` (optional dep)."
23
+ );
24
+ });
25
+ pipe = mod.pipeline(
26
+ "feature-extraction",
27
+ model
28
+ );
29
+ }
30
+ return pipe;
31
+ };
32
+ const queryInstruction = options.queryInstruction ?? resolveQueryInstruction(model);
33
+ const embed = async (texts) => {
34
+ if (texts.length === 0) return [];
35
+ const run = await extractor();
36
+ const out = await run(texts, { pooling: "mean", normalize: true });
37
+ return out.tolist();
38
+ };
39
+ return embedder({
40
+ name: `local:${model}`,
41
+ family: "localEmbeddings",
42
+ dimensions: options.dimensions ?? 384,
43
+ // On-device inference is CPU-bound: overlapping batches only fights itself for cores (the whole
44
+ // point of "no heavy local lifting"), so this embedder declares itself serial.
45
+ concurrency: 1,
46
+ embed,
47
+ // Query path prepends the model's instruction (bge); passages are embedded raw via `embed`.
48
+ ...queryInstruction ? { embedQuery: async (text) => (await embed([queryInstruction + text]))[0] ?? [] } : {}
49
+ });
50
+ }
51
+
52
+ // ../../packages/data/src/embedders/openai.ts
53
+ function openaiEmbeddings(options = {}) {
54
+ const model = options.model ?? "text-embedding-3-small";
55
+ const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
56
+ return embedder({
57
+ name: `openai:${model}`,
58
+ family: "openaiEmbeddings",
59
+ // A hosted endpoint is network-bound, so a cold start overlaps batches — kept modest so a large
60
+ // corpus doesn't trip per-minute rate limits; `concurrency` lets a self-hosted endpoint raise it.
61
+ concurrency: options.concurrency ?? 4,
62
+ embed: async (texts) => {
63
+ if (texts.length === 0) return [];
64
+ const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
65
+ if (!apiKey) throw new Error("openaiEmbeddings needs an apiKey or OPENAI_API_KEY.");
66
+ const res = await fetch(`${baseUrl}/embeddings`, {
67
+ method: "POST",
68
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
69
+ body: JSON.stringify({ model, input: texts })
70
+ });
71
+ if (!res.ok) throw new Error(`openai embeddings ${res.status}: ${await res.text()}`);
72
+ const json = await res.json();
73
+ return json.data.map((d) => d.embedding);
74
+ }
75
+ });
76
+ }
77
+ export {
78
+ localEmbeddings,
79
+ openaiEmbeddings
80
+ };
@@ -0,0 +1,35 @@
1
+ import {
2
+ DEFAULT_IGNORE,
3
+ files
4
+ } from "./chunk-YWSWQEJN.mjs";
5
+
6
+ // ../../packages/data/src/finders/declared.ts
7
+ function declared(items) {
8
+ return {
9
+ name: `declared(${items.map((one) => one.ref).join(", ")})`,
10
+ list: async () => items.map((one) => ({
11
+ ref: one.ref,
12
+ data: one.data,
13
+ text: async () => one.ref
14
+ }))
15
+ };
16
+ }
17
+
18
+ // ../../packages/data/src/finders/expand.ts
19
+ function expand(from, into, name) {
20
+ return {
21
+ name: name === void 0 ? from.name : `${name}(${from.name})`,
22
+ ...from.lens !== void 0 ? { lens: from.lens } : {},
23
+ list: async (ctx) => {
24
+ const out = [];
25
+ for (const item of await from.list(ctx)) out.push(...await into(item, ctx));
26
+ return out;
27
+ }
28
+ };
29
+ }
30
+ export {
31
+ DEFAULT_IGNORE,
32
+ declared,
33
+ expand,
34
+ files
35
+ };
@@ -0,0 +1,137 @@
1
+ import {
2
+ COVERAGE_VIA,
3
+ DERIVED_FROM,
4
+ FUZZY_EDGE_META,
5
+ FUZZY_RELATIONS,
6
+ GraphStore,
7
+ IMPORT_RELATIONS,
8
+ RESERVED,
9
+ TIER,
10
+ applyMemoryOverlay,
11
+ asString,
12
+ buildGraph,
13
+ buildIdRegex,
14
+ categoryOf,
15
+ countOccurrences,
16
+ dot,
17
+ edgeMatchesConfidence,
18
+ everywhere,
19
+ filesOf,
20
+ formatLedger,
21
+ isPrecise,
22
+ lineOf,
23
+ loadConfig,
24
+ matches,
25
+ mintId,
26
+ normalize,
27
+ printDiagnostics,
28
+ readJson,
29
+ runEnforcers,
30
+ sections,
31
+ slug,
32
+ stripExampleSpans,
33
+ summarize,
34
+ validateConfig,
35
+ values
36
+ } from "./chunk-AKQQC5IT.mjs";
37
+ import {
38
+ buildEmbeddings,
39
+ buildSearchRuntime,
40
+ embedQuery,
41
+ lexicalRank,
42
+ loadEmbeddings,
43
+ loadSearchRuntime,
44
+ rankNodes,
45
+ rrf,
46
+ searchText
47
+ } from "./chunk-7REP35VA.mjs";
48
+ import "./chunk-RRJ6KKYL.mjs";
49
+ import {
50
+ backend,
51
+ config,
52
+ connect,
53
+ embedder,
54
+ enforcer,
55
+ enricher,
56
+ extractor,
57
+ labeler,
58
+ linker,
59
+ map,
60
+ persistence,
61
+ reranker,
62
+ source,
63
+ store
64
+ } from "./chunk-AYOJSS2F.mjs";
65
+ import {
66
+ DEFAULT_IGNORE,
67
+ beneath,
68
+ dirOf,
69
+ glob,
70
+ matcher,
71
+ parseFrontmatter
72
+ } from "./chunk-YWSWQEJN.mjs";
73
+ export {
74
+ COVERAGE_VIA,
75
+ DEFAULT_IGNORE,
76
+ DERIVED_FROM,
77
+ FUZZY_EDGE_META,
78
+ FUZZY_RELATIONS,
79
+ GraphStore,
80
+ IMPORT_RELATIONS,
81
+ RESERVED,
82
+ TIER,
83
+ applyMemoryOverlay,
84
+ asString,
85
+ backend,
86
+ beneath,
87
+ buildEmbeddings,
88
+ buildGraph,
89
+ buildIdRegex,
90
+ buildSearchRuntime,
91
+ categoryOf,
92
+ config,
93
+ connect,
94
+ countOccurrences,
95
+ dirOf,
96
+ dot,
97
+ edgeMatchesConfidence,
98
+ embedQuery,
99
+ embedder,
100
+ enforcer,
101
+ enricher,
102
+ everywhere,
103
+ extractor,
104
+ filesOf,
105
+ formatLedger,
106
+ glob,
107
+ isPrecise,
108
+ labeler,
109
+ lexicalRank,
110
+ lineOf,
111
+ linker,
112
+ loadConfig,
113
+ loadEmbeddings,
114
+ loadSearchRuntime,
115
+ map,
116
+ matcher,
117
+ matches,
118
+ mintId,
119
+ normalize,
120
+ parseFrontmatter,
121
+ persistence,
122
+ printDiagnostics,
123
+ rankNodes,
124
+ readJson,
125
+ reranker,
126
+ rrf,
127
+ runEnforcers,
128
+ searchText,
129
+ sections,
130
+ slug,
131
+ source,
132
+ store,
133
+ stripExampleSpans,
134
+ summarize,
135
+ validateConfig,
136
+ values
137
+ };
@@ -0,0 +1,83 @@
1
+ import {
2
+ embedQuery,
3
+ loadEmbeddings
4
+ } from "./chunk-7REP35VA.mjs";
5
+ import {
6
+ linker
7
+ } from "./chunk-AYOJSS2F.mjs";
8
+ import "./chunk-YWSWQEJN.mjs";
9
+
10
+ // ../../packages/data/src/linkers/semantic.ts
11
+ var RELATION_RELATED = "relates_to";
12
+ var DEFAULT_TOP_K = 6;
13
+ var DEFAULT_SCORE_FLOOR = 0.2;
14
+ function relatesTo(options = {}) {
15
+ const requested = Number.isFinite(options.topK) ? options.topK ?? DEFAULT_TOP_K : DEFAULT_TOP_K;
16
+ const topK = Math.max(1, Math.floor(requested));
17
+ const scoreFloor = clampScore(options.scoreFloor ?? DEFAULT_SCORE_FLOOR);
18
+ const relation = options.relation?.trim() || RELATION_RELATED;
19
+ const allowed = options.artifactTypes ? new Set(options.artifactTypes) : void 0;
20
+ return linker({
21
+ name: "relates-to",
22
+ apply: async (ctx) => {
23
+ if (!ctx.config.search) return;
24
+ const runtime = await loadEmbeddings(ctx.config, ctx.graph).catch((err) => {
25
+ ctx.logger.warn(
26
+ `[data] semantic linker disabled: ${err instanceof Error ? err.message : String(err)}`
27
+ );
28
+ return void 0;
29
+ });
30
+ if (!runtime) return;
31
+ const filterIds = collectCodeNodes(ctx);
32
+ if (filterIds.size === 0) return;
33
+ const backendName = ctx.config.backend.name;
34
+ for (const artifact of ctx.graph.nodes()) {
35
+ if (artifact.owner === backendName) continue;
36
+ if (allowed && !allowed.has(artifact.type)) continue;
37
+ const query = queryText(artifact);
38
+ if (!query) continue;
39
+ const candidates = await semanticCandidates(runtime, query, filterIds, topK);
40
+ if (candidates.length === 0) continue;
41
+ const seen = /* @__PURE__ */ new Set();
42
+ for (const hit of candidates) {
43
+ if (hit.score < scoreFloor) break;
44
+ const code = ctx.graph.node(hit.id);
45
+ if (!code || code.owner !== backendName) continue;
46
+ const file = typeof code.metadata.sourceFile === "string" ? code.metadata.sourceFile : code.id;
47
+ if (seen.has(file)) continue;
48
+ seen.add(file);
49
+ ctx.addEdge({
50
+ from: code.id,
51
+ to: artifact.id,
52
+ relation,
53
+ metadata: { score: hit.score, source: "semantic" }
54
+ });
55
+ if (seen.size >= topK) break;
56
+ }
57
+ }
58
+ }
59
+ });
60
+ }
61
+ function collectCodeNodes(ctx) {
62
+ const ids = /* @__PURE__ */ new Set();
63
+ for (const n of ctx.graph.nodes()) {
64
+ if (n.owner === ctx.config.backend.name) ids.add(n.id);
65
+ }
66
+ return ids;
67
+ }
68
+ async function semanticCandidates(runtime, query, filterIds, topK) {
69
+ const q = await embedQuery(runtime.embedder, query);
70
+ return runtime.store.query(q, { topK, filterIds });
71
+ }
72
+ function queryText(node) {
73
+ const title = typeof node.metadata.title === "string" ? node.metadata.title : void 0;
74
+ const excerpt = typeof node.metadata.excerpt === "string" ? node.metadata.excerpt : void 0;
75
+ return [node.id, title, excerpt].filter((value) => typeof value === "string" && value.length > 0).join(" ");
76
+ }
77
+ function clampScore(value) {
78
+ if (!Number.isFinite(value)) return DEFAULT_SCORE_FLOOR;
79
+ return Math.max(0, Math.min(1, value));
80
+ }
81
+ export {
82
+ relatesTo
83
+ };