Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions agent-context.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
"autoAssign": true,
"tokenBudget": 2000,
"hierarchical": true,
"minHitWeight": 2,
"order": [
"post-it",
"memo",
Expand Down Expand Up @@ -221,6 +222,47 @@
"pagination": [
"페이지네이션",
"cursor"
],
"billing": [
"payments",
"charge",
"invoice",
"결제"
],
"payments": [
"billing",
"charge",
"invoice",
"결제",
"payment"
],
"payment": [
"billing",
"payments",
"charge",
"invoice",
"결제"
],
"charge": [
"billing",
"payments",
"invoice",
"결제",
"payment"
],
"invoice": [
"billing",
"payments",
"charge",
"결제",
"payment"
],
"결제": [
"billing",
"payments",
"charge",
"invoice",
"payment"
]
}
}
Expand Down
18 changes: 14 additions & 4 deletions tools/ac-fts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';

// Issue #14 fix: require an initialized project in cwd; never silently fall back
// to the source repo (that would pollute the package install / npx cache).
function resolveProjectRoot() {
const cwd = process.cwd();
if (existsSync(join(cwd, 'agent-context.config.json')) || existsSync(join(cwd, 'agent-context')))
return join(cwd, 'agent-context');
process.stderr.write("agent-context가 초기화되지 않았습니다. 먼저 'agent-context-init.mjs --yes' 를 실행하세요.\n");
process.stderr.write("(agent-context not initialized in cwd; run 'agent-context-init.mjs --yes' first.)\n");
process.stderr.write("cwd: " + cwd + "\n");
process.exit(1);
}
const ROOT = resolveProjectRoot();
const CONFIG = (() => {
for (const p of [join(process.cwd(),'agent-context.config.json'), new URL('../agent-context.config.json', import.meta.url).pathname])
if (existsSync(p)) return JSON.parse(readFileSync(p,'utf8'));
const p = join(process.cwd(), 'agent-context.config.json');
if (existsSync(p)) return JSON.parse(readFileSync(p, 'utf8'));
return {};
})();
const ROOT = (existsSync(join(process.cwd(),'agent-context')) ? join(process.cwd(),'agent-context')
: new URL('../agent-context', import.meta.url).pathname);
const INDEX_PATH = join(ROOT, 'index.json');
const PRIVATE = CONFIG.privateMirror || '.agent-context-runtime';
const DB_DIR = join(process.cwd(), PRIVATE);
Expand Down
18 changes: 14 additions & 4 deletions tools/ac-watch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@ import { execFileSync } from 'node:child_process';
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
import { join } from 'node:path';

// Issue #14 fix: require an initialized project in cwd; never silently fall back
// to the source repo (that would pollute the package install / npx cache).
function resolveProjectRoot() {
const cwd = process.cwd();
if (existsSync(join(cwd, 'agent-context.config.json')) || existsSync(join(cwd, 'agent-context')))
return join(cwd, 'agent-context');
process.stderr.write("agent-context가 초기화되지 않았습니다. 먼저 'agent-context-init.mjs --yes' 를 실행하세요.\n");
process.stderr.write("(agent-context not initialized in cwd; run 'agent-context-init.mjs --yes' first.)\n");
process.stderr.write("cwd: " + cwd + "\n");
process.exit(1);
}
const ROOT = resolveProjectRoot();
const CONFIG = (() => {
for (const p of [join(process.cwd(),'agent-context.config.json'), new URL('../agent-context.config.json', import.meta.url).pathname])
if (existsSync(p)) return JSON.parse(readFileSync(p,'utf8'));
const p = join(process.cwd(), 'agent-context.config.json');
if (existsSync(p)) return JSON.parse(readFileSync(p, 'utf8'));
return {};
})();
const ROOT = (existsSync(join(process.cwd(),'agent-context')) ? join(process.cwd(),'agent-context')
: new URL('../agent-context', import.meta.url).pathname);
const CAND = join(ROOT, '.candidates');

function git(args) {
Expand Down
35 changes: 24 additions & 11 deletions tools/agent-search-lite.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@ import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { pathToFileURL } from 'node:url';

// Issue #14 fix: require an initialized project in cwd; never silently fall back
// to the source repo.
function resolveProjectRoot() {
const cwd = process.cwd();
const ctxRoot = 'agent-context';
if (existsSync(join(cwd, 'agent-context.config.json')) || existsSync(join(cwd, ctxRoot)))
return join(cwd, ctxRoot);
process.stderr.write("agent-context가 초기화되지 않았습니다. 먼저 'agent-context-init.mjs --yes' 를 실행하세요.\n");
process.stderr.write("(agent-context not initialized in cwd; run 'agent-context-init.mjs --yes' first.)\n");
process.stderr.write("cwd: " + cwd + "\n");
process.exit(1);
}
const ROOT = resolveProjectRoot();
function resolveConfig() {
const cands = [
join(process.cwd(), 'agent-context.config.json'),
new URL('../agent-context.config.json', import.meta.url).pathname,
];
for (const p of cands) if (existsSync(p)) return JSON.parse(readFileSync(p, 'utf8'));
const p = join(process.cwd(), 'agent-context.config.json');
if (existsSync(p)) return JSON.parse(readFileSync(p, 'utf8'));
return { contextRoot: 'agent-context' };
}
const CONFIG = resolveConfig();
const ROOT = (existsSync(join(process.cwd(), 'agent-context.config.json')) || existsSync(join(process.cwd(), CONFIG.contextRoot || 'agent-context')))
? join(process.cwd(), CONFIG.contextRoot || 'agent-context')
: new URL(`../${CONFIG.contextRoot || 'agent-context'}`, import.meta.url).pathname;
const INDEX_PATH = join(ROOT, 'index.json');

const MIN_HIT_WEIGHT = CONFIG.search?.minHitWeight ?? 2;

const LEVELS = CONFIG.hierarchy?.levels || {
'post-it': { tokens: 15 }, memo: { tokens: 50 }, diary: { tokens: 200 },
bookshelf: { tokens: 1000 }, library: { tokens: 5000 },
Expand Down Expand Up @@ -103,8 +112,8 @@ function collect(entries, qTokens, opts, startRank, query) {
try { const d=(Date.now()-new Date(e.updated).getTime())/86400000; recency = d<7?1:(d<30?0.8:0.5); } catch {}
const score = hitScore*0.5 - levelDistance*0.1 + priorityScore*0.2 + recency*0.1;
const estTokens = LEVELS[lev]?.tokens || 200;
return { entry:e, lev, levelDistance, hitScore, score, estTokens };
}).filter(s => s.hitScore > 0 || s.entry.feature === query?.toLowerCase() || opts.level);
return { entry:e, lev, levelDistance, hitScore, score, estTokens, w };
}).filter(s => s.w >= (opts.minWeight ?? MIN_HIT_WEIGHT) || s.entry.feature === query?.toLowerCase() || opts.level);
scored.sort((a,b)=>b.score-a.score);
const top = scored.slice(0, limit);
const topTokens = top.reduce((s,x)=>s+x.estTokens,0);
Expand All @@ -115,6 +124,7 @@ function collect(entries, qTokens, opts, startRank, query) {
async function search(query, opts={}) {
let index; try { index = JSON.parse(readFileSync(INDEX_PATH,'utf8')); } catch { index = { entries: [] }; }
const entries = index.entries || [];
opts.minWeight = opts.minWeight ?? CONFIG.search?.minHitWeight ?? 2;
const assignedLevel = opts.level || heuristicLevel(query);
const rawTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
const qTokens = expandTokens(rawTokens);
Expand Down Expand Up @@ -155,6 +165,7 @@ async function search(query, opts={}) {
totalEntries: entries.length,
evaluated: res.evaluated,
hit: res.hit,
guidance: res.hit ? null : '관련 결과 없음 — 상위 레벨로 확장 검색 필요 (no related results; expand to a higher level)',
top: res.top.map(t=>({ id:t.entry.id, title:t.entry.title, level:t.lev, feature:t.entry.feature, priority:t.entry.priority, estTokens:t.estTokens, path:t.entry.path, summary:t.entry.summary })),
tokens: { top: res.topTokens, full: res.fullTokens, saving, avgPerQuery: res.top.length ? Math.round(res.topTokens/res.top.length) : 0 },
note: `Hierarchical ${ORDER.join('→')} — miss expands to larger levels`,
Expand Down Expand Up @@ -217,6 +228,7 @@ for (let i=0;i<a.length;i++){ const v=a[i];
else if(v==='--agent') out.agent=a[++i];
else if(v==='--refs') out.refs=a[++i];
else if(v==='--benchmark') out.benchmark=true;
else if(v==='--min-weight') out.minWeight=Number(a[++i]);
else if(v==='--help'||v==='-h') out.help=true;
else if(!v.startsWith('--') && out.query===null && !out.assign) out.query=v;
}
Expand Down Expand Up @@ -253,12 +265,13 @@ if (__isMain && out.benchmark) {
console.log(JSON.stringify({ benchmark:'live index, hierarchical vs full read', avgHitRate:(results.filter(r=>r.hit).length/results.length*100).toFixed(0)+'%', results }, null, 2));
process.exit(0);
}
if (__isMain) { const res = await search(out.query, { level: out.level, limit: out.limit });
if (__isMain) { const res = await search(out.query, { level: out.level, limit: out.limit, minWeight: out.minWeight });
if (out.json) console.log(JSON.stringify(res, null, 2));
else {
console.log(`\n🔍 "${res.query}" → 휴리스틱 라우터: ${res.assignedLevel}${res.expandedTo?` (miss→확장: ${res.expandedTo})`:''} | 동의어 +${res.router.synonymExpanded} | semantic:${res.router.semantic}`);
console.log(` order: ${res.order.join(' → ')} | total:${res.totalEntries} evaluated:${res.evaluated} | hit:${res.hit?'✅':'❌'} | tokens top:${res.tokens.top} vs full:${res.tokens.full} saving:${res.tokens.saving}`);
for (const t of res.top) console.log(` - [${t.level} ${t.feature}] ${t.title} (p${t.priority}) → ${t.path}`);
if (!res.hit && res.guidance) console.log(' ⚠️ ' + res.guidance);
console.log('');
}
}
Loading