@tienne/gestalt 0.86.0 → 0.88.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.
Files changed (133) hide show
  1. package/CLAUDE.md +6 -2
  2. package/dist/bin/gestalt-hook.d.ts +5 -0
  3. package/dist/bin/gestalt-hook.d.ts.map +1 -0
  4. package/dist/bin/gestalt-hook.js +51 -0
  5. package/dist/bin/gestalt-hook.js.map +1 -0
  6. package/dist/package.json +4 -3
  7. package/dist/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -2
  8. package/dist/plugin/skills/blast-radius/SKILL.md +11 -1
  9. package/dist/plugin/skills/diff-radius/SKILL.md +11 -0
  10. package/dist/plugin/skills/review/SKILL.md +9 -2
  11. package/dist/schemas/gestalt.schema.json +19 -0
  12. package/dist/src/cli/commands/harness-refs.d.ts +10 -0
  13. package/dist/src/cli/commands/harness-refs.d.ts.map +1 -1
  14. package/dist/src/cli/commands/harness-refs.js +40 -0
  15. package/dist/src/cli/commands/harness-refs.js.map +1 -1
  16. package/dist/src/cli/index.d.ts.map +1 -1
  17. package/dist/src/cli/index.js +10 -1
  18. package/dist/src/cli/index.js.map +1 -1
  19. package/dist/src/code-graph/cochange.d.ts +40 -0
  20. package/dist/src/code-graph/cochange.d.ts.map +1 -1
  21. package/dist/src/code-graph/cochange.js +88 -3
  22. package/dist/src/code-graph/cochange.js.map +1 -1
  23. package/dist/src/code-graph/engine.d.ts +35 -1
  24. package/dist/src/code-graph/engine.d.ts.map +1 -1
  25. package/dist/src/code-graph/engine.js +300 -55
  26. package/dist/src/code-graph/engine.js.map +1 -1
  27. package/dist/src/code-graph/freshness.d.ts +39 -0
  28. package/dist/src/code-graph/freshness.d.ts.map +1 -0
  29. package/dist/src/code-graph/freshness.js +93 -0
  30. package/dist/src/code-graph/freshness.js.map +1 -0
  31. package/dist/src/code-graph/hooks/background.d.ts +18 -0
  32. package/dist/src/code-graph/hooks/background.d.ts.map +1 -0
  33. package/dist/src/code-graph/hooks/background.js +55 -0
  34. package/dist/src/code-graph/hooks/background.js.map +1 -0
  35. package/dist/src/code-graph/hooks/pointers.d.ts +66 -0
  36. package/dist/src/code-graph/hooks/pointers.d.ts.map +1 -0
  37. package/dist/src/code-graph/hooks/pointers.js +292 -0
  38. package/dist/src/code-graph/hooks/pointers.js.map +1 -0
  39. package/dist/src/code-graph/hooks/rank.d.ts +36 -0
  40. package/dist/src/code-graph/hooks/rank.d.ts.map +1 -0
  41. package/dist/src/code-graph/hooks/rank.js +212 -0
  42. package/dist/src/code-graph/hooks/rank.js.map +1 -0
  43. package/dist/src/code-graph/hooks/run.d.ts +37 -0
  44. package/dist/src/code-graph/hooks/run.d.ts.map +1 -0
  45. package/dist/src/code-graph/hooks/run.js +324 -0
  46. package/dist/src/code-graph/hooks/run.js.map +1 -0
  47. package/dist/src/code-graph/hooks/settings.d.ts +11 -0
  48. package/dist/src/code-graph/hooks/settings.d.ts.map +1 -0
  49. package/dist/src/code-graph/hooks/settings.js +25 -0
  50. package/dist/src/code-graph/hooks/settings.js.map +1 -0
  51. package/dist/src/code-graph/hooks/state.d.ts +20 -0
  52. package/dist/src/code-graph/hooks/state.d.ts.map +1 -0
  53. package/dist/src/code-graph/hooks/state.js +73 -0
  54. package/dist/src/code-graph/hooks/state.js.map +1 -0
  55. package/dist/src/code-graph/index.d.ts +6 -2
  56. package/dist/src/code-graph/index.d.ts.map +1 -1
  57. package/dist/src/code-graph/index.js +3 -1
  58. package/dist/src/code-graph/index.js.map +1 -1
  59. package/dist/src/code-graph/ko-text.d.ts +39 -0
  60. package/dist/src/code-graph/ko-text.d.ts.map +1 -0
  61. package/dist/src/code-graph/ko-text.js +223 -0
  62. package/dist/src/code-graph/ko-text.js.map +1 -0
  63. package/dist/src/code-graph/lock.d.ts +30 -0
  64. package/dist/src/code-graph/lock.d.ts.map +1 -0
  65. package/dist/src/code-graph/lock.js +140 -0
  66. package/dist/src/code-graph/lock.js.map +1 -0
  67. package/dist/src/code-graph/plugins/typescript-skeleton.d.ts +7 -0
  68. package/dist/src/code-graph/plugins/typescript-skeleton.d.ts.map +1 -0
  69. package/dist/src/code-graph/plugins/typescript-skeleton.js +157 -0
  70. package/dist/src/code-graph/plugins/typescript-skeleton.js.map +1 -0
  71. package/dist/src/code-graph/plugins/typescript.d.ts.map +1 -1
  72. package/dist/src/code-graph/plugins/typescript.js +58 -0
  73. package/dist/src/code-graph/plugins/typescript.js.map +1 -1
  74. package/dist/src/code-graph/storage.d.ts +85 -0
  75. package/dist/src/code-graph/storage.d.ts.map +1 -1
  76. package/dist/src/code-graph/storage.js +318 -3
  77. package/dist/src/code-graph/storage.js.map +1 -1
  78. package/dist/src/code-graph/types.d.ts +66 -0
  79. package/dist/src/code-graph/types.d.ts.map +1 -1
  80. package/dist/src/core/config.d.ts +33 -0
  81. package/dist/src/core/config.d.ts.map +1 -1
  82. package/dist/src/core/config.js +14 -0
  83. package/dist/src/core/config.js.map +1 -1
  84. package/dist/src/core/hooks-env.d.ts +9 -0
  85. package/dist/src/core/hooks-env.d.ts.map +1 -0
  86. package/dist/src/core/hooks-env.js +16 -0
  87. package/dist/src/core/hooks-env.js.map +1 -0
  88. package/dist/src/harness-review/backward-search.d.ts +15 -2
  89. package/dist/src/harness-review/backward-search.d.ts.map +1 -1
  90. package/dist/src/harness-review/backward-search.js +101 -43
  91. package/dist/src/harness-review/backward-search.js.map +1 -1
  92. package/dist/src/harness-review/collect.d.ts +10 -1
  93. package/dist/src/harness-review/collect.d.ts.map +1 -1
  94. package/dist/src/harness-review/collect.js +66 -6
  95. package/dist/src/harness-review/collect.js.map +1 -1
  96. package/dist/src/harness-review/github-code-search.d.ts +32 -1
  97. package/dist/src/harness-review/github-code-search.d.ts.map +1 -1
  98. package/dist/src/harness-review/github-code-search.js +86 -2
  99. package/dist/src/harness-review/github-code-search.js.map +1 -1
  100. package/dist/src/harness-review/hybrid-search.d.ts +35 -0
  101. package/dist/src/harness-review/hybrid-search.d.ts.map +1 -0
  102. package/dist/src/harness-review/hybrid-search.js +63 -0
  103. package/dist/src/harness-review/hybrid-search.js.map +1 -0
  104. package/dist/src/harness-review/related-clones.d.ts +66 -0
  105. package/dist/src/harness-review/related-clones.d.ts.map +1 -0
  106. package/dist/src/harness-review/related-clones.js +309 -0
  107. package/dist/src/harness-review/related-clones.js.map +1 -0
  108. package/dist/src/harness-review/search-backend.d.ts +23 -1
  109. package/dist/src/harness-review/search-backend.d.ts.map +1 -1
  110. package/dist/src/harness-review/search-backend.js +13 -1
  111. package/dist/src/harness-review/search-backend.js.map +1 -1
  112. package/dist/src/humanize/detectors.js +1 -1
  113. package/dist/src/humanize/detectors.js.map +1 -1
  114. package/dist/src/mcp/schemas.d.ts +9 -3
  115. package/dist/src/mcp/schemas.d.ts.map +1 -1
  116. package/dist/src/mcp/schemas.js +19 -2
  117. package/dist/src/mcp/schemas.js.map +1 -1
  118. package/dist/src/mcp/server.d.ts.map +1 -1
  119. package/dist/src/mcp/server.js +21 -3
  120. package/dist/src/mcp/server.js.map +1 -1
  121. package/dist/src/mcp/tools/code-graph-passthrough.d.ts +6 -1
  122. package/dist/src/mcp/tools/code-graph-passthrough.d.ts.map +1 -1
  123. package/dist/src/mcp/tools/code-graph-passthrough.js +95 -6
  124. package/dist/src/mcp/tools/code-graph-passthrough.js.map +1 -1
  125. package/package.json +4 -3
  126. package/plugin/.codex-plugin/plugin.json +1 -1
  127. package/plugin/.mcp.json +1 -1
  128. package/plugin/mcp.json +1 -1
  129. package/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -2
  130. package/plugin/skills/blast-radius/SKILL.md +11 -1
  131. package/plugin/skills/diff-radius/SKILL.md +11 -0
  132. package/plugin/skills/review/SKILL.md +9 -2
  133. package/schemas/gestalt.schema.json +19 -0
@@ -0,0 +1,93 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ /**
4
+ * mtime이 마지막 확인 시각에서 이만큼 안쪽이면 stat이 같아도 다시 읽는다.
5
+ * 확인 직후 같은 mtime 해상도 안에서 다시 쓰인 파일은 size까지 같으면
6
+ * stat만으로 구분이 안 된다 (git의 racy-clean과 같은 문제다).
7
+ * FAT 계열이 2초 해상도라 그 폭에 맞췄다.
8
+ */
9
+ export const RACY_WINDOW_MS = 2000;
10
+ export function hashContent(content) {
11
+ return createHash('sha256').update(content).digest('hex');
12
+ }
13
+ export function hasStructuralDrift(d) {
14
+ return d.added.length > 0 || d.modified.length > 0 || d.removed.length > 0;
15
+ }
16
+ export function hasAnyDrift(d) {
17
+ return hasStructuralDrift(d) || d.touched.length > 0;
18
+ }
19
+ /**
20
+ * 디스크의 현재 파일 목록과 store를 맞대 바뀐 파일을 가린다. store에는 쓰지 않는다.
21
+ *
22
+ * - size와 mtime이 둘 다 같으면 파일을 읽지 않는다
23
+ * - 하나라도 다르면 읽어서 해시로 확정한다
24
+ * - stat 행이 없고 노드만 있는 파일은 stat 컬럼이 생기기 전에 빌드된 DB다.
25
+ * 노드의 file_hash로 한 번 확정하고 나면 다음부터는 stat으로 넘어간다
26
+ */
27
+ export function detectDrift(store, currentFiles) {
28
+ const start = Date.now();
29
+ const stats = store.getAllFileStats();
30
+ const graphFiles = new Set(store.getGraphFilePaths());
31
+ const report = {
32
+ added: [],
33
+ modified: [],
34
+ removed: [],
35
+ touched: [],
36
+ unchanged: 0,
37
+ durationMs: 0,
38
+ };
39
+ const current = new Set(currentFiles);
40
+ for (const filePath of currentFiles) {
41
+ let size;
42
+ let mtimeMs;
43
+ try {
44
+ const st = statSync(filePath);
45
+ size = st.size;
46
+ mtimeMs = st.mtimeMs;
47
+ }
48
+ catch {
49
+ // 목록을 뽑은 뒤 지워진 파일이다. 다음 검사에서 removed로 잡힌다
50
+ continue;
51
+ }
52
+ const stamp = { filePath, size, mtimeMs };
53
+ const row = stats.get(filePath);
54
+ if (row) {
55
+ const racy = row.mtimeMs >= row.checkedAt - RACY_WINDOW_MS;
56
+ if (row.size === size && row.mtimeMs === mtimeMs && !racy) {
57
+ report.unchanged++;
58
+ continue;
59
+ }
60
+ classifyByHash(report, stamp, row.fileHash);
61
+ continue;
62
+ }
63
+ if (graphFiles.has(filePath)) {
64
+ classifyByHash(report, stamp, store.getFileHash(filePath));
65
+ continue;
66
+ }
67
+ report.added.push(stamp);
68
+ }
69
+ for (const filePath of new Set([...stats.keys(), ...graphFiles])) {
70
+ if (!current.has(filePath))
71
+ report.removed.push(filePath);
72
+ }
73
+ report.durationMs = Date.now() - start;
74
+ return report;
75
+ }
76
+ function classifyByHash(report, stamp, knownHash) {
77
+ let hash;
78
+ try {
79
+ hash = hashContent(readFileSync(stamp.filePath, 'utf-8'));
80
+ }
81
+ catch {
82
+ // 못 읽으면 바뀐 걸로 보고 빌드에 넘긴다. 빌드가 skippedFiles로 남긴다
83
+ report.modified.push(stamp);
84
+ return;
85
+ }
86
+ if (knownHash !== null && hash === knownHash) {
87
+ report.touched.push({ ...stamp, fileHash: hash, checkedAt: Date.now() });
88
+ }
89
+ else {
90
+ report.modified.push(stamp);
91
+ }
92
+ }
93
+ //# sourceMappingURL=freshness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../../src/code-graph/freshness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export type RefreshSpawner = (repoRoot: string) => void;
2
+ /**
3
+ * 훅 엔트리를 `refresh` 인자로 다시 띄운다. 부모는 기다리지 않는다.
4
+ * 엔진은 typescript 파서까지 불러와 기동만 200ms를 넘게 먹으니 훅 프로세스 안에서 돌리지 않는다.
5
+ *
6
+ * MCP 질의가 재색인을 넘길 때는 `reindex`로 띄운다. 그쪽은 훅이 꺼져 있어도 돌아야 한다.
7
+ * execArgv는 tsx로 띄운 개발 서버가 .ts 엔트리를 넘길 때 로더를 물려주는 자리다.
8
+ */
9
+ export declare function detachedRefreshSpawner(entry: string, command?: 'refresh' | 'reindex', execArgv?: string[]): RefreshSpawner;
10
+ /**
11
+ * 직전 요청에서 minIntervalMs가 안 지났거나 다른 프로세스가 갱신 락을 쥐고 있으면 안 띄운다.
12
+ * 연달아 Edit 하는 동안 갱신 프로세스가 줄줄이 뜨는 걸 막는다. 막힌 변경은
13
+ * 턴 끝의 Stop 훅이 다시 요청해서 반영한다.
14
+ *
15
+ * 띄웠으면 true.
16
+ */
17
+ export declare function requestRefresh(repoRoot: string, spawner: RefreshSpawner, minIntervalMs: number, now?: number): boolean;
18
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../../../src/code-graph/hooks/background.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,SAAS,GAAG,SAAqB,EAC1C,QAAQ,GAAE,MAAM,EAAO,GACtB,cAAc,CAUhB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,EACrB,GAAG,SAAa,GACf,OAAO,CAoBT"}
@@ -0,0 +1,55 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdirSync, statSync, utimesSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { LOCK_STALE_MS } from '../lock.js';
5
+ import { hooksDir } from './state.js';
6
+ /**
7
+ * 훅 엔트리를 `refresh` 인자로 다시 띄운다. 부모는 기다리지 않는다.
8
+ * 엔진은 typescript 파서까지 불러와 기동만 200ms를 넘게 먹으니 훅 프로세스 안에서 돌리지 않는다.
9
+ *
10
+ * MCP 질의가 재색인을 넘길 때는 `reindex`로 띄운다. 그쪽은 훅이 꺼져 있어도 돌아야 한다.
11
+ * execArgv는 tsx로 띄운 개발 서버가 .ts 엔트리를 넘길 때 로더를 물려주는 자리다.
12
+ */
13
+ export function detachedRefreshSpawner(entry, command = 'refresh', execArgv = []) {
14
+ return (repoRoot) => {
15
+ const child = spawn(process.execPath, [...execArgv, entry, command, repoRoot], {
16
+ detached: true,
17
+ stdio: 'ignore',
18
+ env: process.env,
19
+ });
20
+ child.on('error', () => { });
21
+ child.unref();
22
+ };
23
+ }
24
+ /**
25
+ * 직전 요청에서 minIntervalMs가 안 지났거나 다른 프로세스가 갱신 락을 쥐고 있으면 안 띄운다.
26
+ * 연달아 Edit 하는 동안 갱신 프로세스가 줄줄이 뜨는 걸 막는다. 막힌 변경은
27
+ * 턴 끝의 Stop 훅이 다시 요청해서 반영한다.
28
+ *
29
+ * 띄웠으면 true.
30
+ */
31
+ export function requestRefresh(repoRoot, spawner, minIntervalMs, now = Date.now()) {
32
+ const stamp = join(hooksDir(repoRoot), 'refresh.stamp');
33
+ try {
34
+ if (now - statSync(stamp).mtimeMs < minIntervalMs)
35
+ return false;
36
+ }
37
+ catch {
38
+ // 처음 요청한다
39
+ }
40
+ try {
41
+ // 죽은 프로세스가 남긴 락이면 갱신이 영영 안 뜬다. 판정은 엔진 락과 같은 상한을 쓴다
42
+ const lock = statSync(join(repoRoot, '.gestalt', 'code-graph.lock'));
43
+ if (now - lock.mtimeMs < LOCK_STALE_MS)
44
+ return false;
45
+ }
46
+ catch {
47
+ // 락이 없다
48
+ }
49
+ mkdirSync(hooksDir(repoRoot), { recursive: true });
50
+ writeFileSync(stamp, '');
51
+ utimesSync(stamp, now / 1000, now / 1000);
52
+ spawner(repoRoot);
53
+ return true;
54
+ }
55
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../../../../src/code-graph/hooks/background.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAa,EACb,UAAiC,SAAS,EAC1C,WAAqB,EAAE;IAEvB,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE;YAC7E,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;YACf,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5B,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,OAAuB,EACvB,aAAqB,EACrB,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,aAAa;YAAE,OAAO,KAAK,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,UAAU;IACZ,CAAC;IACD,IAAI,CAAC;QACH,mDAAmD;QACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACrE,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,aAAa;YAAE,OAAO,KAAK,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ;IACV,CAAC;IAED,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,aAAa,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzB,UAAU,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;IAC1C,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,66 @@
1
+ import type { CodeGraphStore } from '../storage.js';
2
+ import { type CodeGraphNode } from '../types.js';
3
+ import { type PromptTokens } from './rank.js';
4
+ /**
5
+ * 프롬프트 하나를 파일 포인터 몇 개로 바꾼다. 훅과 평가 스크립트가 이 함수 하나를 같이 쓴다.
6
+ *
7
+ * 신호는 셋이다. 영문 식별자(심볼 이름과 경로), 한국어 bigram(주석과 커밋 메시지),
8
+ * 티켓 키. 셋 다 파일 단위로 모아 점수를 더한다. 같은 파일을 가리키는 서로 다른 신호는
9
+ * 서로를 보강해야 해서, 노드 단위로 따로 줄 세우면 그게 안 된다.
10
+ */
11
+ /**
12
+ * 관련성 게이트. 1위 점수가 이보다 낮으면 아무것도 안 넣는다.
13
+ * 흔한 토큰 하나만 맞은 경우(idf 2~3)는 여기서 걸린다. 드문 토큰 하나나 흔한 토큰 둘은 넘는다.
14
+ * 값은 hooks 테스트의 관련, 무관 프롬프트 쌍과 평가 스크립트로 정했다.
15
+ */
16
+ export declare const MIN_POINTER_SCORE = 4;
17
+ /**
18
+ * 한글 내용어가 둘 이상인 프롬프트의 포인터가 넘어야 하는 점수를 IDF 최댓값 ln(1+N)의 배수로 둔다.
19
+ * 고정값으로 두면 커밋 수에 따라 뜻이 달라진다. N은 색인한 커밋 수와 주석 노드 수 중 큰 쪽이다.
20
+ *
21
+ * 영문으로 걸린 포인터에도 건다. 한국어 프롬프트에 섞인 영문은 `API`, `A/B`, `flag`처럼
22
+ * 흔한 기술 용어가 대부분이라서다. 모노레포 평가에서 영문 신호만의 정밀도가 2%였다.
23
+ * 배수는 gestalt(N≈430)와 모노레포(N≈8천) 시점 분할 평가에서 두 레포 모두
24
+ * 정밀도가 오르고 후속 응답 잡음이 내려가는 쪽으로 골랐다
25
+ */
26
+ export declare const TEXT_PROMPT_IDF_MULTIPLE = 3.5;
27
+ export interface Pointer {
28
+ node: CodeGraphNode;
29
+ relPath: string;
30
+ score: number;
31
+ /** 통째 일치 가산을 뺀 점수. 상대 컷은 이걸로 건다 */
32
+ baseScore: number;
33
+ /** 맞은 영문 토큰 */
34
+ matched: string[];
35
+ /** 맞은 한글 어절, 프롬프트에 적힌 그대로 (프롬프트 순서) */
36
+ koWords: string[];
37
+ koSource?: 'commit' | 'comment';
38
+ /** 이 파일을 건드린 커밋 중 프롬프트와 비슷했던 수 */
39
+ koCommits?: number;
40
+ ticket?: {
41
+ key: string;
42
+ count: number;
43
+ };
44
+ }
45
+ export interface PointerOptions {
46
+ /** 평가용. 끄면 영문 신호만 쓴다. 한국어와 티켓 키를 같이 끈다 */
47
+ korean?: boolean;
48
+ exists?: (filePath: string) => boolean;
49
+ }
50
+ export declare function rankPointers(store: CodeGraphStore, repoRoot: string, tokens: PromptTokens, limit: number, opts?: PointerOptions): Pointer[];
51
+ /**
52
+ * 점수만으로는 못 거르는 경우를 막는 증거 조건. 포인터마다 따로 보고 하나는 맞아야 남는다.
53
+ *
54
+ * - 티켓 키가 맞았다
55
+ * - 그 포인터에서 서로 다른 영문 토큰과 한글 어절을 합쳐 둘 이상 맞았다.
56
+ * 한국어만으로 걸렸으면 서로 다른 어절 둘이 맞아야 한다는 뜻이다
57
+ * - 프롬프트에 조각 둘 이상짜리 식별자가 통째로 적혔고 그게 고른 심볼 이름이다
58
+ *
59
+ * 포인터 셋을 합쳐 세면 "점심"이 한 파일에, "먹을지"가 다른 파일에 맞아도 통과한다.
60
+ * `push`나 `summarize`처럼 짧은 함수 이름도 드물어서 idf가 높아, 토큰 하나로 들어가면
61
+ * "push는 하지 마" 같은 프롬프트에 엉뚱한 파일이 붙는다.
62
+ */
63
+ export declare function hasEnoughEvidence(p: Pointer, tokens: PromptTokens): boolean;
64
+ /** 주입 문구에 붙이는 "어떤 단어로 걸렸는지". 에이전트가 포인터를 얼마나 믿을지 가늠하게 한다 */
65
+ export declare function evidenceLabel(p: Pointer): string;
66
+ //# sourceMappingURL=pointers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointers.d.ts","sourceRoot":"","sources":["../../../../src/code-graph/hooks/pointers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAA+B,KAAK,YAAY,EAAmB,MAAM,WAAW,CAAC;AAE5F;;;;;;GAMG;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAe5C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uCAAuC;IACvC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACxC;AAyID,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,cAAmB,GACxB,OAAO,EAAE,CA8GX;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAI3E;AAWD,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAQhD"}
@@ -0,0 +1,292 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { basename, sep } from 'node:path';
3
+ import { NodeKind } from '../types.js';
4
+ import { scoreNodes, splitIdentifier } from './rank.js';
5
+ /**
6
+ * 프롬프트 하나를 파일 포인터 몇 개로 바꾼다. 훅과 평가 스크립트가 이 함수 하나를 같이 쓴다.
7
+ *
8
+ * 신호는 셋이다. 영문 식별자(심볼 이름과 경로), 한국어 bigram(주석과 커밋 메시지),
9
+ * 티켓 키. 셋 다 파일 단위로 모아 점수를 더한다. 같은 파일을 가리키는 서로 다른 신호는
10
+ * 서로를 보강해야 해서, 노드 단위로 따로 줄 세우면 그게 안 된다.
11
+ */
12
+ /**
13
+ * 관련성 게이트. 1위 점수가 이보다 낮으면 아무것도 안 넣는다.
14
+ * 흔한 토큰 하나만 맞은 경우(idf 2~3)는 여기서 걸린다. 드문 토큰 하나나 흔한 토큰 둘은 넘는다.
15
+ * 값은 hooks 테스트의 관련, 무관 프롬프트 쌍과 평가 스크립트로 정했다.
16
+ */
17
+ export const MIN_POINTER_SCORE = 4;
18
+ /**
19
+ * 한글 내용어가 둘 이상인 프롬프트의 포인터가 넘어야 하는 점수를 IDF 최댓값 ln(1+N)의 배수로 둔다.
20
+ * 고정값으로 두면 커밋 수에 따라 뜻이 달라진다. N은 색인한 커밋 수와 주석 노드 수 중 큰 쪽이다.
21
+ *
22
+ * 영문으로 걸린 포인터에도 건다. 한국어 프롬프트에 섞인 영문은 `API`, `A/B`, `flag`처럼
23
+ * 흔한 기술 용어가 대부분이라서다. 모노레포 평가에서 영문 신호만의 정밀도가 2%였다.
24
+ * 배수는 gestalt(N≈430)와 모노레포(N≈8천) 시점 분할 평가에서 두 레포 모두
25
+ * 정밀도가 오르고 후속 응답 잡음이 내려가는 쪽으로 골랐다
26
+ */
27
+ export const TEXT_PROMPT_IDF_MULTIPLE = 3.5;
28
+ /** 티켓 키가 맞으면 다른 신호 없이도 1위가 되게 준다 */
29
+ const TICKET_SCORE = 12;
30
+ /** 흔한 bigram 하나가 수만 행을 끌고 오지 않게 막는 조회 상한 */
31
+ const MAX_TERM_HITS = 20_000;
32
+ /** 한 어절의 bigram 중 이만큼은 맞아야 그 어절이 걸린 것으로 친다 */
33
+ const EOJEOL_COVERAGE = 0.5;
34
+ /** 커밋 메시지 길이 보정(BM25의 k1, b). 본문이 긴 커밋이 단어 수만으로 이기지 않게 */
35
+ const BM25_K1 = 1.2;
36
+ const BM25_B = 0.75;
37
+ /** 프롬프트와 비슷한 커밋을 이만큼만 본다 */
38
+ const TOP_COMMITS = 20;
39
+ /** 한 파일에 걸린 커밋 점수를 더할 때 2등부터 깎는 비율 */
40
+ const COMMIT_DECAY = 0.5;
41
+ function isTestPath(relPath) {
42
+ return /(^|\/)(tests?|__tests__)\//.test(relPath) || /\.(test|spec)\./.test(relPath);
43
+ }
44
+ function fileNode(filePath, relPath) {
45
+ return {
46
+ id: `file:${filePath}`,
47
+ kind: NodeKind.File,
48
+ name: basename(filePath),
49
+ filePath,
50
+ lineStart: 1,
51
+ isTest: isTestPath(relPath),
52
+ updatedAt: 0,
53
+ };
54
+ }
55
+ /** 어절마다 bigram 절반 이상이 맞아야 걸린 것으로 치고 걸린 어절의 IDF 합을 더한다 */
56
+ function matchEojeols(tokens, has, idf) {
57
+ let score = 0;
58
+ const words = new Set();
59
+ tokens.ko.forEach((e, i) => {
60
+ const hits = e.terms.filter(has);
61
+ if (hits.length === 0 || hits.length / e.terms.length < EOJEOL_COVERAGE)
62
+ return;
63
+ words.add(i);
64
+ score += hits.reduce((sum, t) => sum + idf(t), 0) / Math.sqrt(e.terms.length);
65
+ });
66
+ return { score, words };
67
+ }
68
+ /** 조회 행을 문서(노드나 커밋)마다 맞은 term 집합으로 묶는다 */
69
+ function groupHits(rows, key) {
70
+ const out = new Map();
71
+ for (const r of rows) {
72
+ const k = key(r);
73
+ let set = out.get(k);
74
+ if (!set)
75
+ out.set(k, (set = new Set()));
76
+ set.add(r.term);
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * 한국어 신호를 노드(주석)와 파일(커밋) 단위 점수로 바꾼다.
82
+ *
83
+ * 어절마다 bigram의 절반 이상이 맞아야 그 어절이 걸린 것으로 친다. "해상도"의 bigram
84
+ * 하나("상도")만 다른 낱말에서 맞는 우연을 거르려는 것이다. 걸린 어절의 점수는 맞은
85
+ * bigram의 IDF 합을 bigram 수의 제곱근으로 나눈 값이다 — 긴 낱말이 짧은 낱말을 bigram
86
+ * 개수만으로 이기지 않게 한다.
87
+ *
88
+ * 커밋은 커밋 하나가 문서 하나다. 어절 둘 이상이 한 커밋 메시지에서 같이 맞아야 그 커밋을
89
+ * 쓴다. 비슷한 커밋을 몇 개 고른 뒤 그 커밋들이 건드린 파일에 점수를 나눠준다. 많은 파일을
90
+ * 건드린 커밋일수록 파일 하나에 가는 몫이 작다.
91
+ */
92
+ function scoreKorean(store, tokens) {
93
+ const byNode = new Map();
94
+ const byFile = new Map();
95
+ const allTerms = [...new Set(tokens.ko.flatMap((e) => e.terms))];
96
+ if (allTerms.length === 0)
97
+ return { byNode, byFile, minScore: 0 };
98
+ const st = store.getTextTermStats(allTerms);
99
+ const minScore = Math.max(MIN_POINTER_SCORE, TEXT_PROMPT_IDF_MULTIPLE * Math.log(1 + Math.max(st.commits, st.docs)));
100
+ const idf = (n, df) => Math.log(1 + n / (1 + (df ?? 0)));
101
+ const docIdf = (t) => idf(st.docs, st.docDf.get(t));
102
+ const byDoc = groupHits(store.getDocTermHits(allTerms, MAX_TERM_HITS), (r) => r.nodeId);
103
+ for (const [nodeId, terms] of byDoc) {
104
+ const m = matchEojeols(tokens, (t) => terms.has(t), docIdf);
105
+ if (m.words.size > 0)
106
+ byNode.set(nodeId, { ...m, commits: 0 });
107
+ }
108
+ const commitIdf = (t) => idf(st.commits, st.commitDf.get(t));
109
+ const bySha = groupHits(store.getCommitTermHits(allTerms, MAX_TERM_HITS), (r) => r.sha);
110
+ const matched = [];
111
+ for (const [sha, terms] of bySha) {
112
+ const m = matchEojeols(tokens, (t) => terms.has(t), commitIdf);
113
+ if (m.words.size >= 2)
114
+ matched.push({ sha, ...m });
115
+ }
116
+ matched.sort((a, b) => b.score - a.score);
117
+ const top = matched.slice(0, TOP_COMMITS);
118
+ const info = new Map(store.getTextCommits(top.map((c) => c.sha)).map((c) => [c.sha, c]));
119
+ const shares = new Map();
120
+ for (const c of top) {
121
+ const meta = info.get(c.sha);
122
+ if (!meta || meta.files.length === 0)
123
+ continue;
124
+ const lenNorm = (BM25_K1 + 1) / (1 + BM25_K1 * (1 - BM25_B + (BM25_B * meta.terms) / st.avgCommitTerms));
125
+ const share = (c.score * lenNorm) / (1 + Math.log2(meta.files.length));
126
+ for (const f of meta.files) {
127
+ let s = shares.get(f);
128
+ if (!s)
129
+ shares.set(f, (s = { scores: [], words: new Set() }));
130
+ s.scores.push(share);
131
+ for (const w of c.words)
132
+ s.words.add(w);
133
+ }
134
+ }
135
+ for (const [file, s] of shares) {
136
+ s.scores.sort((a, b) => b - a);
137
+ const score = s.scores.reduce((sum, v, i) => sum + v * COMMIT_DECAY ** i, 0);
138
+ byFile.set(file, { score, words: s.words, commits: s.scores.length });
139
+ }
140
+ return { byNode, byFile, minScore };
141
+ }
142
+ export function rankPointers(store, repoRoot, tokens, limit, opts = {}) {
143
+ const korean = opts.korean ?? true;
144
+ const exists = opts.exists ?? existsSync;
145
+ const rel = (f) => (f.startsWith(repoRoot + sep) ? f.slice(repoRoot.length + 1) : f);
146
+ // 영문: 노드 단위 점수
147
+ const nodeScores = new Map();
148
+ if (tokens.parts.length > 0) {
149
+ const candidates = store.searchNodesByTokens(repoRoot, tokens.parts, 5000);
150
+ if (candidates.length > 0) {
151
+ for (const r of scoreNodes(repoRoot, candidates, tokens, store.getStats('').totalNodes)) {
152
+ nodeScores.set(r.node.id, { node: r.node, english: r });
153
+ }
154
+ }
155
+ }
156
+ // 한국어: 주석은 노드에, 커밋은 파일에
157
+ let byFileKo = new Map();
158
+ let minText = 0;
159
+ if (korean && tokens.ko.length > 0) {
160
+ const { byNode, byFile, minScore } = scoreKorean(store, tokens);
161
+ byFileKo = byFile;
162
+ // 한글 내용어가 한 어절뿐이면 영문 프롬프트에 한국어 토씨가 붙은 꼴이다. 문턱을 걸지 않는다
163
+ if (tokens.ko.length >= 2)
164
+ minText = minScore;
165
+ const missing = [...byNode.keys()].filter((id) => !nodeScores.has(id));
166
+ for (const node of store.getNodesByIds(missing))
167
+ nodeScores.set(node.id, { node });
168
+ for (const [id, hit] of byNode) {
169
+ const entry = nodeScores.get(id);
170
+ if (entry)
171
+ entry.ko = hit;
172
+ }
173
+ }
174
+ const files = new Map();
175
+ const agg = (filePath) => {
176
+ let a = files.get(filePath);
177
+ if (!a) {
178
+ a = { filePath };
179
+ files.set(filePath, a);
180
+ }
181
+ return a;
182
+ };
183
+ for (const { node, english, ko } of nodeScores.values()) {
184
+ let base = (english?.baseScore ?? 0) + (ko?.score ?? 0);
185
+ if (ko && !english && node.isTest)
186
+ base *= 0.7;
187
+ const score = base + (english ? english.score - english.baseScore : 0);
188
+ const a = agg(node.filePath);
189
+ if (!a.best || score > a.best.score) {
190
+ a.best = {
191
+ node,
192
+ score,
193
+ base,
194
+ matched: english?.matched ?? [],
195
+ koWords: ko?.words ?? new Set(),
196
+ };
197
+ }
198
+ }
199
+ for (const [filePath, hit] of byFileKo)
200
+ agg(filePath).commit = hit;
201
+ if (korean && tokens.tickets.length > 0) {
202
+ // 그 티켓으로 가장 많이 고친 파일만 남긴다. 후속 PR은 앞 PR과 다른 파일을 고치는 일이 많아
203
+ // 한두 번 걸친 파일까지 넣으면 정밀도가 깎인다 (모노레포 롤링 평가 41% → 44%)
204
+ const rows = store.getTicketFiles(tokens.tickets);
205
+ const max = Math.max(0, ...rows.map((t) => t.count));
206
+ for (const t of rows) {
207
+ if (t.count < max)
208
+ continue;
209
+ const a = agg(t.filePath);
210
+ if (!a.ticket)
211
+ a.ticket = { key: t.ticket, count: t.count };
212
+ }
213
+ }
214
+ const ranked = [];
215
+ for (const a of files.values()) {
216
+ const relPath = rel(a.filePath);
217
+ const testMul = isTestPath(relPath) ? 0.7 : 1;
218
+ const commitScore = (a.commit?.score ?? 0) * testMul;
219
+ const ticketScore = a.ticket ? TICKET_SCORE + Math.log2(1 + a.ticket.count) : 0;
220
+ const base = (a.best?.base ?? 0) + commitScore + ticketScore;
221
+ const score = (a.best?.score ?? 0) + commitScore + ticketScore;
222
+ const words = new Set([...(a.best?.koWords ?? []), ...(a.commit?.words ?? [])]);
223
+ ranked.push({
224
+ node: a.best?.node ?? fileNode(a.filePath, relPath),
225
+ relPath,
226
+ score,
227
+ baseScore: base,
228
+ matched: a.best?.matched ?? [],
229
+ koWords: [...words].sort((x, y) => x - y).map((i) => tokens.ko[i].surface),
230
+ koSource: a.commit ? 'commit' : a.best?.koWords.size ? 'comment' : undefined,
231
+ koCommits: a.commit?.commits,
232
+ ticket: a.ticket,
233
+ });
234
+ }
235
+ ranked.sort((x, y) => y.score - x.score || x.relPath.localeCompare(y.relPath));
236
+ // 한글 내용어가 둘 이상인 프롬프트면 포인터마다 문턱을 넘어야 한다. 티켓 키나 식별자를 통째로 적은 경우는
237
+ // 사람이 대상을 짚은 것이라 문턱과 상관없이 둔다. 그 밖에는 1위만 MIN_POINTER_SCORE를
238
+ // 넘으면 되고 나머지는 상대 컷으로만 거른다
239
+ const passes = (p) => hasEnoughEvidence(p, tokens) && (p.score >= minText || !!p.ticket || namedExactly(p, tokens));
240
+ const top = ranked[0];
241
+ if (!top || top.score < MIN_POINTER_SCORE)
242
+ return [];
243
+ // 1위의 절반에 못 미치는 건 버린다. 이력에만 남고 지워진 파일도 여기서 거른다
244
+ const picks = [];
245
+ for (const r of ranked) {
246
+ if (r.baseScore < top.baseScore * 0.5)
247
+ continue;
248
+ if (!passes(r) || !exists(r.node.filePath))
249
+ continue;
250
+ picks.push(r);
251
+ if (picks.length >= limit)
252
+ break;
253
+ }
254
+ return picks;
255
+ }
256
+ /**
257
+ * 점수만으로는 못 거르는 경우를 막는 증거 조건. 포인터마다 따로 보고 하나는 맞아야 남는다.
258
+ *
259
+ * - 티켓 키가 맞았다
260
+ * - 그 포인터에서 서로 다른 영문 토큰과 한글 어절을 합쳐 둘 이상 맞았다.
261
+ * 한국어만으로 걸렸으면 서로 다른 어절 둘이 맞아야 한다는 뜻이다
262
+ * - 프롬프트에 조각 둘 이상짜리 식별자가 통째로 적혔고 그게 고른 심볼 이름이다
263
+ *
264
+ * 포인터 셋을 합쳐 세면 "점심"이 한 파일에, "먹을지"가 다른 파일에 맞아도 통과한다.
265
+ * `push`나 `summarize`처럼 짧은 함수 이름도 드물어서 idf가 높아, 토큰 하나로 들어가면
266
+ * "push는 하지 마" 같은 프롬프트에 엉뚱한 파일이 붙는다.
267
+ */
268
+ export function hasEnoughEvidence(p, tokens) {
269
+ if (p.ticket)
270
+ return true;
271
+ if (new Set(p.matched).size + p.koWords.length >= 2)
272
+ return true;
273
+ return namedExactly(p, tokens);
274
+ }
275
+ /** 프롬프트에 조각 둘 이상짜리 식별자가 통째로 적혔고 그게 이 포인터의 심볼 이름이다 */
276
+ function namedExactly(p, tokens) {
277
+ return (p.node.kind !== NodeKind.File &&
278
+ splitIdentifier(p.node.name).length >= 2 &&
279
+ tokens.wholeNames.has(p.node.name.toLowerCase()));
280
+ }
281
+ /** 주입 문구에 붙이는 "어떤 단어로 걸렸는지". 에이전트가 포인터를 얼마나 믿을지 가늠하게 한다 */
282
+ export function evidenceLabel(p) {
283
+ const parts = [];
284
+ if (p.ticket)
285
+ parts.push(`티켓 ${p.ticket.key} ${p.ticket.count}건`);
286
+ if (p.koWords.length > 0) {
287
+ const words = `"${p.koWords.slice(0, 3).join(' ')}"`;
288
+ parts.push(p.koSource === 'commit' ? `커밋 ${words} ${p.koCommits ?? 0}건` : `주석 ${words}`);
289
+ }
290
+ return parts.length > 0 ? ` (${parts.join(', ')})` : '';
291
+ }
292
+ //# sourceMappingURL=pointers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointers.js","sourceRoot":"","sources":["../../../../src/code-graph/hooks/pointers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type CodeGraphNode } from '../types.js';
2
+ import { type KoEojeol } from '../ko-text.js';
3
+ /** camelCase, snake_case, kebab-case, 경로 구분자를 쪼개 소문자 조각으로 돌려준다 */
4
+ export declare function splitIdentifier(name: string): string[];
5
+ export interface PromptTokens {
6
+ /** 조각 단위 토큰 (stem 적용) */
7
+ parts: string[];
8
+ /** 프롬프트에 식별자 통째로 적힌 이름 (소문자, 4자 이상) */
9
+ wholeNames: Set<string>;
10
+ /** 한글 어절. 줄기와 bigram */
11
+ ko: KoEojeol[];
12
+ /** `CT-31408` 꼴 티켓 키 */
13
+ tickets: string[];
14
+ }
15
+ export declare function tokenizePrompt(prompt: string): PromptTokens;
16
+ export interface RankedNode {
17
+ node: CodeGraphNode;
18
+ relPath: string;
19
+ score: number;
20
+ /** 통째 일치 가산을 뺀 점수. 상대 컷은 이걸로 건다 */
21
+ baseScore: number;
22
+ matched: string[];
23
+ }
24
+ /**
25
+ * 후보 노드마다 점수를 매긴다. 점수는 맞은 토큰의 IDF 합이다.
26
+ *
27
+ * - 이름에서 맞은 토큰은 그대로, 경로에서만 맞은 토큰은 절반만 친다. 경로 조각은 같은
28
+ * 디렉토리 파일 전부가 공유해서 그것만으로는 어느 파일인지 못 가린다
29
+ * - 이름 조각을 많이 덮을수록 더 준다. `refresh`는 `refresh`에 `refreshIndexCache`보다 가깝다
30
+ * - 프롬프트에 이름이 통째로 적혔으면 가산한다
31
+ * - 테스트 파일은 깎는다. 사용자가 고치려는 건 대개 구현 쪽이다
32
+ *
33
+ * idf의 N은 전체 노드 수다. 후보 수로 잡으면 후보가 적은 질의일수록 흔한 토큰이 귀해 보인다.
34
+ */
35
+ export declare function scoreNodes(repoRoot: string, candidates: CodeGraphNode[], tokens: PromptTokens, totalNodes: number): RankedNode[];
36
+ //# sourceMappingURL=rank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../../../src/code-graph/hooks/rank.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAsH7E,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOtD;AAED,MAAM,WAAW,YAAY;IAC3B,yBAAyB;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uCAAuC;IACvC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,wBAAwB;IACxB,EAAE,EAAE,QAAQ,EAAE,CAAC;IACf,wBAAwB;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAqB3D;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,aAAa,EAAE,EAC3B,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,GACjB,UAAU,EAAE,CA0Cd"}