@toolbaux/guardian 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ const _require = createRequire(import.meta.url);
4
+ const { version } = _require("../package.json");
2
5
  import { Command } from "commander";
3
6
  import { runExtract } from "./commands/extract.js";
4
7
  import { runDrift } from "./commands/drift.js";
@@ -24,7 +27,7 @@ const program = new Command();
24
27
  program
25
28
  .name("guardian")
26
29
  .description("Guardian — Architectural intelligence for codebases (by Toolbaux)")
27
- .version("0.1.0");
30
+ .version(version);
28
31
  program
29
32
  .command("generate")
30
33
  .description("Generate compact AI-ready architecture context")
@@ -11,6 +11,7 @@ export async function runSearch(options) {
11
11
  const heatmap = await loadHeatmap(inputDir);
12
12
  const funcIntel = await loadFunctionIntelligence(inputDir);
13
13
  const types = normalizeTypes(options.types);
14
+ const projectRoot = options.projectRoot ?? process.cwd();
14
15
  const matches = searchSnapshots({
15
16
  architecture,
16
17
  ux,
@@ -18,6 +19,8 @@ export async function runSearch(options) {
18
19
  types,
19
20
  heatmap,
20
21
  funcIntel,
22
+ projectRoot,
23
+ topN: options.topN ?? 10,
21
24
  });
22
25
  const content = renderSearchMarkdown(options.query, matches);
23
26
  if (options.output) {
@@ -113,7 +116,7 @@ function scoreItem(queryTokens, item) {
113
116
  return Math.min(1, total / queryTokens.length);
114
117
  }
115
118
  function searchSnapshots(params) {
116
- const { architecture, ux, query, types, heatmap, funcIntel } = params;
119
+ const { architecture, ux, query, types, heatmap, funcIntel, projectRoot, topN } = params;
117
120
  const queryTokens = tokenize(query);
118
121
  const matches = [];
119
122
  const pageUsage = buildComponentPageUsage(ux);
@@ -243,16 +246,23 @@ function searchSnapshots(params) {
243
246
  }
244
247
  if (types.has("functions") && funcIntel) {
245
248
  const queryTokens = tokenize(query);
246
- // 1. Name match — function / theorem name contains a query token
247
- for (const fn of funcIntel.functions) {
248
- const score = scoreItem(queryTokens, {
249
- name: fn.name,
250
- file: fn.file,
251
- text: [...fn.stringLiterals, ...fn.regexPatterns, ...fn.calls, fn.language],
252
- });
253
- if (score <= 0)
249
+ const fnMatches = [];
250
+ // Build a map: file → model field names (feature 3 field augmentation)
251
+ const fileToFields = new Map();
252
+ for (const model of architecture.data_models) {
253
+ if (!model.file)
254
254
  continue;
255
- const lineRange = `${fn.lines[0]}–${fn.lines[1]}`;
255
+ const existing = fileToFields.get(model.file) ?? [];
256
+ fileToFields.set(model.file, [...existing, ...model.fields]);
257
+ }
258
+ // Helper: relativize a path if it looks absolute
259
+ const relativize = (filePath) => {
260
+ if (!path.isAbsolute(filePath))
261
+ return filePath;
262
+ return path.relative(projectRoot, filePath);
263
+ };
264
+ // Helper: build detail lines for a function hit
265
+ const buildDetail = (fn, relFile) => {
256
266
  const detail = [];
257
267
  if (fn.stringLiterals.length > 0) {
258
268
  detail.push(`Literals: ${formatList(fn.stringLiterals.slice(0, 3).map((l) => `"${l.slice(0, 60)}"`), 3)}`);
@@ -263,42 +273,71 @@ function searchSnapshots(params) {
263
273
  if (fn.calls.length > 0) {
264
274
  detail.push(`Calls: ${formatList(fn.calls, 5)}`);
265
275
  }
266
- matches.push({
276
+ // Feature 3 — append model field names for the file this function lives in
277
+ const fields = fileToFields.get(fn.file) ?? fileToFields.get(relFile);
278
+ if (fields && fields.length > 0) {
279
+ detail.push(`Model fields: ${formatList(fields.slice(0, 8), 8)}`);
280
+ }
281
+ return detail;
282
+ };
283
+ // 1. Name match — function / theorem name contains a query token
284
+ for (const fn of funcIntel.functions) {
285
+ const score = scoreItem(queryTokens, {
286
+ name: fn.name,
287
+ file: fn.file,
288
+ text: [...fn.stringLiterals, ...fn.regexPatterns, ...fn.calls, fn.language],
289
+ });
290
+ if (score <= 0)
291
+ continue;
292
+ const relFile = relativize(fn.file);
293
+ const lineRange = `${fn.lines[0]}–${fn.lines[1]}`;
294
+ const detail = buildDetail(fn, relFile);
295
+ fnMatches.push({
267
296
  type: "functions",
268
297
  name: `${fn.name} (${fn.language})`,
269
298
  score,
270
299
  markdown: [
271
- `**${fn.name}** · ${fn.file}:${lineRange} · ${fn.language}${fn.isAsync ? " · async" : ""}`,
300
+ `**${fn.name}** · ${relFile}:${lineRange} · ${fn.language}${fn.isAsync ? " · async" : ""}`,
272
301
  ...detail,
273
302
  ],
274
303
  });
275
304
  }
276
305
  // 2. Literal index match — query token appears in a function's string/regex literals
277
- // (additive: surfaces functions whose body contains the queried literal even if
278
- // the function name itself doesn't match)
306
+ // Uses proper scoreItem() ranking instead of hardcoded 0.6 to prevent noise flooding.
279
307
  for (const tok of queryTokens) {
280
308
  const hits = funcIntel.literal_index[tok.toLowerCase()];
281
309
  if (!hits)
282
310
  continue;
283
311
  for (const hit of hits) {
284
312
  // Skip if we already emitted this function via name match above
285
- if (matches.some((m) => m.type === "functions" && m.name.startsWith(hit.function + " ("))) {
313
+ if (fnMatches.some((m) => m.type === "functions" && m.name.startsWith(hit.function + " ("))) {
286
314
  continue;
287
315
  }
288
316
  const fn = funcIntel.functions.find((f) => f.file === hit.file && f.name === hit.function);
289
317
  if (!fn)
290
318
  continue;
291
- matches.push({
319
+ const score = scoreItem(queryTokens, {
320
+ name: fn.name,
321
+ file: fn.file,
322
+ text: [...fn.stringLiterals, ...fn.regexPatterns, ...fn.calls, fn.language],
323
+ });
324
+ const relFile = relativize(fn.file);
325
+ const detail = buildDetail(fn, relFile);
326
+ fnMatches.push({
292
327
  type: "functions",
293
328
  name: `${fn.name} (${fn.language})`,
294
- score: 0.6,
329
+ score: Math.max(score, 0.2), // floor at 0.2 so literal hits still surface but rank below name matches
295
330
  markdown: [
296
- `**${fn.name}** · ${fn.file}:${fn.lines[0]}–${fn.lines[1]} · ${fn.language}`,
331
+ `**${fn.name}** · ${relFile}:${fn.lines[0]}–${fn.lines[1]} · ${fn.language}`,
297
332
  `Matched literal/pattern containing "${tok}"`,
333
+ ...detail,
298
334
  ],
299
335
  });
300
336
  }
301
337
  }
338
+ // Feature 2 — rank by score, cap at topN to prevent context flooding
339
+ fnMatches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
340
+ matches.push(...fnMatches.slice(0, topN));
302
341
  }
303
342
  return matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
304
343
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolbaux/guardian",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "description": "Architectural intelligence for codebases. Verify that AI-generated code matches your architectural intent.",
6
6
  "keywords": [