agentfootprint 6.23.0 → 6.25.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/README.md +31 -0
- package/bin/agentfootprint-lint-tools.mjs +14 -0
- package/dist/esm/lib/influence-core/cache.js +149 -0
- package/dist/esm/lib/influence-core/cache.js.map +1 -0
- package/dist/esm/lib/influence-core/index.js +32 -0
- package/dist/esm/lib/influence-core/index.js.map +1 -0
- package/dist/esm/lib/influence-core/margin.js +110 -0
- package/dist/esm/lib/influence-core/margin.js.map +1 -0
- package/dist/esm/lib/influence-core/signals.js +232 -0
- package/dist/esm/lib/influence-core/signals.js.map +1 -0
- package/dist/esm/lib/influence-core/similarity.js +79 -0
- package/dist/esm/lib/influence-core/similarity.js.map +1 -0
- package/dist/esm/lib/influence-core/types.js +35 -0
- package/dist/esm/lib/influence-core/types.js.map +1 -0
- package/dist/esm/lib/tool-lint/analyze.js +235 -0
- package/dist/esm/lib/tool-lint/analyze.js.map +1 -0
- package/dist/esm/lib/tool-lint/cli.js +198 -0
- package/dist/esm/lib/tool-lint/cli.js.map +1 -0
- package/dist/esm/lib/tool-lint/format.js +61 -0
- package/dist/esm/lib/tool-lint/format.js.map +1 -0
- package/dist/esm/lib/tool-lint/index.js +23 -0
- package/dist/esm/lib/tool-lint/index.js.map +1 -0
- package/dist/esm/lib/tool-lint/rules.js +249 -0
- package/dist/esm/lib/tool-lint/rules.js.map +1 -0
- package/dist/esm/lib/tool-lint/types.js +25 -0
- package/dist/esm/lib/tool-lint/types.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/bounded.js +76 -0
- package/dist/esm/lib/trace-toolpack/bounded.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/index.js +10 -0
- package/dist/esm/lib/trace-toolpack/index.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +699 -0
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/types.js +24 -0
- package/dist/esm/lib/trace-toolpack/types.js.map +1 -0
- package/dist/esm/observe.js +25 -0
- package/dist/esm/observe.js.map +1 -1
- package/dist/esm/recorders/observability/ToolChoiceRecorder.js +261 -0
- package/dist/esm/recorders/observability/ToolChoiceRecorder.js.map +1 -0
- package/dist/lib/influence-core/cache.js +155 -0
- package/dist/lib/influence-core/cache.js.map +1 -0
- package/dist/lib/influence-core/index.js +50 -0
- package/dist/lib/influence-core/index.js.map +1 -0
- package/dist/lib/influence-core/margin.js +114 -0
- package/dist/lib/influence-core/margin.js.map +1 -0
- package/dist/lib/influence-core/signals.js +242 -0
- package/dist/lib/influence-core/signals.js.map +1 -0
- package/dist/lib/influence-core/similarity.js +83 -0
- package/dist/lib/influence-core/similarity.js.map +1 -0
- package/dist/lib/influence-core/types.js +38 -0
- package/dist/lib/influence-core/types.js.map +1 -0
- package/dist/lib/tool-lint/analyze.js +242 -0
- package/dist/lib/tool-lint/analyze.js.map +1 -0
- package/dist/lib/tool-lint/cli.js +203 -0
- package/dist/lib/tool-lint/cli.js.map +1 -0
- package/dist/lib/tool-lint/format.js +65 -0
- package/dist/lib/tool-lint/format.js.map +1 -0
- package/dist/lib/tool-lint/index.js +43 -0
- package/dist/lib/tool-lint/index.js.map +1 -0
- package/dist/lib/tool-lint/rules.js +256 -0
- package/dist/lib/tool-lint/rules.js.map +1 -0
- package/dist/lib/tool-lint/types.js +26 -0
- package/dist/lib/tool-lint/types.js.map +1 -0
- package/dist/lib/trace-toolpack/bounded.js +86 -0
- package/dist/lib/trace-toolpack/bounded.js.map +1 -0
- package/dist/lib/trace-toolpack/index.js +16 -0
- package/dist/lib/trace-toolpack/index.js.map +1 -0
- package/dist/lib/trace-toolpack/traceToolpack.js +704 -0
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -0
- package/dist/lib/trace-toolpack/types.js +28 -0
- package/dist/lib/trace-toolpack/types.js.map +1 -0
- package/dist/observe.js +64 -1
- package/dist/observe.js.map +1 -1
- package/dist/recorders/observability/ToolChoiceRecorder.js +266 -0
- package/dist/recorders/observability/ToolChoiceRecorder.js.map +1 -0
- package/dist/types/lib/influence-core/cache.d.ts +95 -0
- package/dist/types/lib/influence-core/cache.d.ts.map +1 -0
- package/dist/types/lib/influence-core/index.d.ts +33 -0
- package/dist/types/lib/influence-core/index.d.ts.map +1 -0
- package/dist/types/lib/influence-core/margin.d.ts +34 -0
- package/dist/types/lib/influence-core/margin.d.ts.map +1 -0
- package/dist/types/lib/influence-core/signals.d.ts +104 -0
- package/dist/types/lib/influence-core/signals.d.ts.map +1 -0
- package/dist/types/lib/influence-core/similarity.d.ts +26 -0
- package/dist/types/lib/influence-core/similarity.d.ts.map +1 -0
- package/dist/types/lib/influence-core/types.d.ts +158 -0
- package/dist/types/lib/influence-core/types.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/analyze.d.ts +84 -0
- package/dist/types/lib/tool-lint/analyze.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/cli.d.ts +44 -0
- package/dist/types/lib/tool-lint/cli.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/format.d.ts +19 -0
- package/dist/types/lib/tool-lint/format.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/index.d.ts +24 -0
- package/dist/types/lib/tool-lint/index.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/rules.d.ts +86 -0
- package/dist/types/lib/tool-lint/rules.d.ts.map +1 -0
- package/dist/types/lib/tool-lint/types.d.ts +156 -0
- package/dist/types/lib/tool-lint/types.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/bounded.d.ts +48 -0
- package/dist/types/lib/trace-toolpack/bounded.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/index.d.ts +10 -0
- package/dist/types/lib/trace-toolpack/index.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts +70 -0
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/types.d.ts +60 -0
- package/dist/types/lib/trace-toolpack/types.d.ts.map +1 -0
- package/dist/types/observe.d.ts +4 -0
- package/dist/types/observe.d.ts.map +1 -1
- package/dist/types/recorders/observability/ToolChoiceRecorder.d.ts +165 -0
- package/dist/types/recorders/observability/ToolChoiceRecorder.d.ts.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pairwiseSimilarity — pairwise cosine over a set of texts
|
|
3
|
+
* (RFC-002 C1's core: tool descriptions → matrix + ranked pairs).
|
|
4
|
+
*
|
|
5
|
+
* Pattern: pure async function, embedder-injected. No thresholds, no
|
|
6
|
+
* verdicts, no lint rules — those are C1's `analyzeToolCatalog`
|
|
7
|
+
* policy layer ON TOP of this geometry. The core stays
|
|
8
|
+
* reusable for any "how confusable are these texts" question.
|
|
9
|
+
* Role: `src/lib/influence-core/` leaf. No agent/runtime imports.
|
|
10
|
+
*
|
|
11
|
+
* Honest claim: similarity is embedding geometry over the DESCRIPTIONS
|
|
12
|
+
* — a confusability HEURISTIC, not a measurement of the model's actual
|
|
13
|
+
* selection function (RFC-002 §2; tier 3 validates the proxy via
|
|
14
|
+
* choice-entropy sampling).
|
|
15
|
+
*/
|
|
16
|
+
import { cosineSimilarity } from '../../memory/embedding/cosine.js';
|
|
17
|
+
/**
|
|
18
|
+
* Embed every item once (deduplicated batch) and compute the full
|
|
19
|
+
* cosine matrix plus ranked upper-triangle pairs (descending; ties
|
|
20
|
+
* keep input pair order).
|
|
21
|
+
*
|
|
22
|
+
* Invariants (pinned by property tests):
|
|
23
|
+
* - `matrix[i][j] === matrix[j][i]` — computed once, mirrored.
|
|
24
|
+
* - `matrix[i][i] === 1` EXACTLY — set by definition, so
|
|
25
|
+
* self-similarity is an invariant rather than a float artifact
|
|
26
|
+
* (and duplicate texts at different ids still compare via cosine).
|
|
27
|
+
* - N items → N·(N−1)/2 pairs.
|
|
28
|
+
*/
|
|
29
|
+
export async function pairwiseSimilarity(args) {
|
|
30
|
+
const { items, embedder } = args;
|
|
31
|
+
assertUniqueIds(items);
|
|
32
|
+
const ids = items.map((item) => item.id);
|
|
33
|
+
if (items.length === 0)
|
|
34
|
+
return { ids, matrix: [], pairs: [] };
|
|
35
|
+
// Deduplicated embedding pass (identical descriptions embed once).
|
|
36
|
+
const distinct = [...new Set(items.map((item) => item.text))];
|
|
37
|
+
const vectors = embedder.embedBatch
|
|
38
|
+
? await embedder.embedBatch({
|
|
39
|
+
texts: distinct,
|
|
40
|
+
...(args.signal ? { signal: args.signal } : {}),
|
|
41
|
+
})
|
|
42
|
+
: await sequentialEmbed(embedder, distinct, args.signal);
|
|
43
|
+
const vectorByText = new Map();
|
|
44
|
+
for (let i = 0; i < distinct.length; i++)
|
|
45
|
+
vectorByText.set(distinct[i], vectors[i]);
|
|
46
|
+
const itemVecs = items.map((item) => vectorByText.get(item.text));
|
|
47
|
+
// Upper triangle once, mirrored; diagonal exactly 1 by definition.
|
|
48
|
+
const matrix = items.map(() => new Array(items.length).fill(0));
|
|
49
|
+
const pairs = [];
|
|
50
|
+
for (let i = 0; i < items.length; i++) {
|
|
51
|
+
matrix[i][i] = 1;
|
|
52
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
53
|
+
const similarity = cosineSimilarity(itemVecs[i], itemVecs[j]);
|
|
54
|
+
matrix[i][j] = similarity;
|
|
55
|
+
matrix[j][i] = similarity;
|
|
56
|
+
pairs.push({ a: ids[i], b: ids[j], similarity });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Stable sort — ties keep (i, j) input order.
|
|
60
|
+
pairs.sort((p, q) => q.similarity - p.similarity);
|
|
61
|
+
return { ids, matrix, pairs };
|
|
62
|
+
}
|
|
63
|
+
async function sequentialEmbed(embedder, texts, signal) {
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const text of texts) {
|
|
66
|
+
out.push(await embedder.embed({ text, ...(signal ? { signal } : {}) }));
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function assertUniqueIds(items) {
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const item of items) {
|
|
73
|
+
if (seen.has(item.id)) {
|
|
74
|
+
throw new Error(`pairwiseSimilarity: duplicate item id '${item.id}' — ids must be unique`);
|
|
75
|
+
}
|
|
76
|
+
seen.add(item.id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=similarity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/similarity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* influence-core types — the ONE embedding-based scoring contract.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Strategy seam (plug-and-play meta-pattern) — the frame and
|
|
5
|
+
* rule engine are the library's; the `Embedder` is consumer-
|
|
6
|
+
* injected, exactly like NarrativeFormatter / reliability /
|
|
7
|
+
* permission / commentary strategies.
|
|
8
|
+
* Role: `src/lib/` leaf module. Shared by the FDL paper pipeline
|
|
9
|
+
* (Visible Reasoning, Eq. 1–6), RFC-002's tool-catalog lint +
|
|
10
|
+
* margin recorder (C1/C4/C5), and RFC-003 Part B's LLM-edge
|
|
11
|
+
* weigher (D7). Extracted as RFC-003 block D6 so all three
|
|
12
|
+
* consumers share one scoring engine and one embedding cache.
|
|
13
|
+
*
|
|
14
|
+
* ## Honest claim (RFC-002 §2, the FDL discipline)
|
|
15
|
+
*
|
|
16
|
+
* Every score produced under these types is a PROXY computed from
|
|
17
|
+
* embedding geometry — cosine similarity over consumer-injected
|
|
18
|
+
* embeddings. None of it reads model internals. Scores mean "high
|
|
19
|
+
* semantic alignment", never "the model chose/answered BECAUSE".
|
|
20
|
+
* Scores are not additive across items and are not causal attribution
|
|
21
|
+
* — counterfactual ablation (RFC-003 stage 4) is where causal claims
|
|
22
|
+
* live.
|
|
23
|
+
*/
|
|
24
|
+
/** Paper defaults: α=0.40, β=0.30, γ=0.20, δ=0.10 (sum to 1.0). */
|
|
25
|
+
export const DEFAULT_INFLUENCE_WEIGHTS = Object.freeze({
|
|
26
|
+
fa: 0.4,
|
|
27
|
+
avg: 0.3,
|
|
28
|
+
persist: 0.2,
|
|
29
|
+
depth: 0.1,
|
|
30
|
+
});
|
|
31
|
+
/** Paper default for the PERSIST threshold T (Eq. 3). */
|
|
32
|
+
export const DEFAULT_PERSISTENCE_THRESHOLD = 0.3;
|
|
33
|
+
/** RFC-002 §4 default: margins below this flag the choice as `narrow`. */
|
|
34
|
+
export const DEFAULT_MARGIN_THRESHOLD = 0.05;
|
|
35
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/influence-core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA0BH,mEAAmE;AACnE,MAAM,CAAC,MAAM,yBAAyB,GAAqB,MAAM,CAAC,MAAM,CAAC;IACvE,EAAE,EAAE,GAAG;IACP,GAAG,EAAE,GAAG;IACR,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,GAAG;CACX,CAAC,CAAC;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,CAAC;AAEjD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analyzeToolCatalog — the tool-catalog confusability lint
|
|
3
|
+
* (RFC-002 block C1, the adoption front door).
|
|
4
|
+
*
|
|
5
|
+
* Pattern: policy layer over `pairwiseSimilarity` (influence-core) — the
|
|
6
|
+
* geometry is computed there; thresholds, verdicts, hints and
|
|
7
|
+
* the structural rule pack live here. Everything is consumer-
|
|
8
|
+
* injectable with our defaults (the plug-and-play meta-pattern).
|
|
9
|
+
* Role: `src/lib/tool-lint/`. ZERO stack buy-in — plain
|
|
10
|
+
* `{ name, description?, inputSchema? }[]` in, report out.
|
|
11
|
+
* `catalogFromTools` adapts the library's own `Tool[]`;
|
|
12
|
+
* `coerceCatalog` (cli.ts) normalizes OpenAI/Anthropic/MCP
|
|
13
|
+
* shapes.
|
|
14
|
+
*
|
|
15
|
+
* ## What is embedded (and why)
|
|
16
|
+
*
|
|
17
|
+
* `confusabilityText(tool)` = tokenized name + ': ' + description. The
|
|
18
|
+
* model differentiates tools by name AND description together, so two
|
|
19
|
+
* tools with near-identical names and overlapping descriptions ARE the
|
|
20
|
+
* confusability case (`get_fcns_database` vs `influx_get_fcns_database`)
|
|
21
|
+
* — embedding only the prose would miss the name signal.
|
|
22
|
+
*
|
|
23
|
+
* ## Calibration (RFC-002 §3 — read this before trusting verdicts)
|
|
24
|
+
*
|
|
25
|
+
* Absolute cosine ranges are PER-EMBEDDER. The default threshold (0.85)
|
|
26
|
+
* is a starting point for real sentence embedders. The test/demo
|
|
27
|
+
* `mockEmbedder` (character-frequency) compresses unrelated prose into
|
|
28
|
+
* ~0.85–0.97 — with it, use `MOCK_EMBEDDER_CALIBRATION` and trust only
|
|
29
|
+
* the RELATIVE ordering in `report.similarity.ranked` (the acceptance
|
|
30
|
+
* fixtures assert ordering, never absolute scores).
|
|
31
|
+
*/
|
|
32
|
+
import { pairwiseSimilarity } from '../influence-core/index.js';
|
|
33
|
+
import { defaultStructuralRules } from './rules.js';
|
|
34
|
+
/** Default `confusabilityThreshold` — a starting point for REAL sentence
|
|
35
|
+
* embedders (unrelated tool descriptions typically land 0.3–0.7).
|
|
36
|
+
* Calibrate per embedder; meaningless for the mock (see below). */
|
|
37
|
+
export const DEFAULT_CONFUSABILITY_THRESHOLD = 0.85;
|
|
38
|
+
/** Default `watchBand` below the threshold. */
|
|
39
|
+
export const DEFAULT_WATCH_BAND = 0.05;
|
|
40
|
+
/**
|
|
41
|
+
* Threshold/band calibrated for the char-frequency `mockEmbedder` on
|
|
42
|
+
* realistic tool prose (seed corpus: the Neo SAN catalog). The mock
|
|
43
|
+
* compresses unrelated descriptions into ~0.85–0.97 cosine, so expect
|
|
44
|
+
* false positives even at 0.94 — with the mock, the RELATIVE ordering
|
|
45
|
+
* of `report.similarity.ranked` is the trustworthy signal; absolute
|
|
46
|
+
* verdicts are only honest with a real embedder + per-embedder
|
|
47
|
+
* calibration.
|
|
48
|
+
*/
|
|
49
|
+
export const MOCK_EMBEDDER_CALIBRATION = Object.freeze({
|
|
50
|
+
confusabilityThreshold: 0.94,
|
|
51
|
+
watchBand: 0.02,
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Adapt the library's `Tool[]` (from `defineTool` / `Agent.tool`) to the
|
|
55
|
+
* lint's plain catalog shape. Trivial on purpose: `Tool.schema` already
|
|
56
|
+
* IS `{ name, description, inputSchema }`.
|
|
57
|
+
*/
|
|
58
|
+
export function catalogFromTools(tools) {
|
|
59
|
+
return tools.map((tool) => ({
|
|
60
|
+
name: tool.schema.name,
|
|
61
|
+
description: tool.schema.description,
|
|
62
|
+
inputSchema: tool.schema.inputSchema,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The text the confusability analysis embeds for one tool: the name with
|
|
67
|
+
* `_`/`-`/camelCase boundaries opened into words, then the description.
|
|
68
|
+
* Exported so consumers can reproduce or replace the construction.
|
|
69
|
+
*/
|
|
70
|
+
export function confusabilityText(tool) {
|
|
71
|
+
const name = tool.name
|
|
72
|
+
.replace(/[_-]+/g, ' ')
|
|
73
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
74
|
+
.trim();
|
|
75
|
+
const description = tool.description?.trim() ?? '';
|
|
76
|
+
return description.length > 0 ? `${name}: ${description}` : name;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Lint a tool catalog: pairwise confusability over what the model reads
|
|
80
|
+
* (when an embedder is supplied) + the structural rule pack. Returns a
|
|
81
|
+
* report whose `ok` is the CI gate.
|
|
82
|
+
*
|
|
83
|
+
* Duplicate tool names are themselves reported as structural errors
|
|
84
|
+
* (rule `duplicate-name`, built-in precondition — a catalog where two
|
|
85
|
+
* tools share a name is broken before any similarity question); the
|
|
86
|
+
* duplicates are dropped from the similarity analysis (first one wins).
|
|
87
|
+
*/
|
|
88
|
+
export async function analyzeToolCatalog(tools, options = {}) {
|
|
89
|
+
const confusabilityThreshold = options.confusabilityThreshold ?? DEFAULT_CONFUSABILITY_THRESHOLD;
|
|
90
|
+
const watchBand = options.watchBand ?? DEFAULT_WATCH_BAND;
|
|
91
|
+
const rules = options.rules ?? defaultStructuralRules;
|
|
92
|
+
const failOn = options.failOn ?? 'error';
|
|
93
|
+
// ── built-in precondition: duplicate names ──
|
|
94
|
+
const structural = [];
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const unique = [];
|
|
97
|
+
for (const tool of tools) {
|
|
98
|
+
if (seen.has(tool.name)) {
|
|
99
|
+
structural.push({
|
|
100
|
+
rule: 'duplicate-name',
|
|
101
|
+
tool: tool.name,
|
|
102
|
+
severity: 'error',
|
|
103
|
+
message: 'two tools share this name — the model cannot address them distinctly',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
seen.add(tool.name);
|
|
108
|
+
unique.push(tool);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ── structural rule pack ──
|
|
112
|
+
for (const tool of unique) {
|
|
113
|
+
for (const rule of rules) {
|
|
114
|
+
structural.push(...rule.check(tool, unique));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ── pairwise confusability (embedder-gated) ──
|
|
118
|
+
let ranked = [];
|
|
119
|
+
const confusable = [];
|
|
120
|
+
const watch = [];
|
|
121
|
+
const analyzed = options.embedder !== undefined && unique.length >= 2;
|
|
122
|
+
if (options.embedder !== undefined && unique.length >= 2) {
|
|
123
|
+
const byName = new Map(unique.map((tool) => [tool.name, tool]));
|
|
124
|
+
const result = await pairwiseSimilarity({
|
|
125
|
+
items: unique.map((tool) => ({ id: tool.name, text: confusabilityText(tool) })),
|
|
126
|
+
embedder: options.embedder,
|
|
127
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
128
|
+
});
|
|
129
|
+
ranked = result.pairs;
|
|
130
|
+
for (const pair of result.pairs) {
|
|
131
|
+
if (pair.similarity >= confusabilityThreshold) {
|
|
132
|
+
confusable.push({
|
|
133
|
+
kind: 'confusable',
|
|
134
|
+
a: pair.a,
|
|
135
|
+
b: pair.b,
|
|
136
|
+
similarity: pair.similarity,
|
|
137
|
+
// byName lookups are safe: pair ids come from `unique` itself.
|
|
138
|
+
hint: differentiationHint(byName.get(pair.a), byName.get(pair.b)),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else if (pair.similarity >= confusabilityThreshold - watchBand) {
|
|
142
|
+
watch.push({
|
|
143
|
+
kind: 'watch',
|
|
144
|
+
a: pair.a,
|
|
145
|
+
b: pair.b,
|
|
146
|
+
similarity: pair.similarity,
|
|
147
|
+
hint: differentiationHint(byName.get(pair.a), byName.get(pair.b)),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const errors = structural.filter((f) => f.severity === 'error').length;
|
|
153
|
+
const warnings = structural.length - errors;
|
|
154
|
+
const gateFailures = failOn === 'warn' ? structural.length : errors;
|
|
155
|
+
return {
|
|
156
|
+
ok: confusable.length === 0 && gateFailures === 0,
|
|
157
|
+
toolCount: tools.length,
|
|
158
|
+
similarity: {
|
|
159
|
+
analyzed,
|
|
160
|
+
confusable,
|
|
161
|
+
watch,
|
|
162
|
+
ranked,
|
|
163
|
+
thresholds: { confusabilityThreshold, watchBand },
|
|
164
|
+
},
|
|
165
|
+
structural,
|
|
166
|
+
summary: { confusable: confusable.length, watch: watch.length, errors, warnings },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// ── differentiating-axis hint (heuristic, honest) ────────────────────
|
|
170
|
+
const STOPWORDS = new Set([
|
|
171
|
+
'a',
|
|
172
|
+
'an',
|
|
173
|
+
'and',
|
|
174
|
+
'are',
|
|
175
|
+
'by',
|
|
176
|
+
'for',
|
|
177
|
+
'from',
|
|
178
|
+
'get',
|
|
179
|
+
'in',
|
|
180
|
+
'into',
|
|
181
|
+
'is',
|
|
182
|
+
'its',
|
|
183
|
+
'of',
|
|
184
|
+
'on',
|
|
185
|
+
'or',
|
|
186
|
+
'the',
|
|
187
|
+
'to',
|
|
188
|
+
'which',
|
|
189
|
+
'with',
|
|
190
|
+
]);
|
|
191
|
+
function nameTokens(name) {
|
|
192
|
+
return name
|
|
193
|
+
.replace(/[_-]+/g, ' ')
|
|
194
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
195
|
+
.toLowerCase()
|
|
196
|
+
.split(/\s+/)
|
|
197
|
+
.filter((t) => t.length > 0);
|
|
198
|
+
}
|
|
199
|
+
function descriptionTokens(tool) {
|
|
200
|
+
return new Set((tool.description ?? '')
|
|
201
|
+
.toLowerCase()
|
|
202
|
+
.split(/[^a-z0-9_]+/)
|
|
203
|
+
.filter((t) => t.length >= 3 && !STOPWORDS.has(t)));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Suggest the DIFFERENTIATING AXIS for a flagged pair. Heuristic: when
|
|
207
|
+
* the names are near-twins (≤2 distinct tokens), the qualifier IS the
|
|
208
|
+
* axis — the descriptions must say when to choose each variant. When the
|
|
209
|
+
* names differ, surface the few description terms each tool does NOT
|
|
210
|
+
* share, as the place to anchor an explicit choice condition.
|
|
211
|
+
*/
|
|
212
|
+
export function differentiationHint(a, b) {
|
|
213
|
+
const aTokens = nameTokens(a.name);
|
|
214
|
+
const bTokens = nameTokens(b.name);
|
|
215
|
+
const aOnly = aTokens.filter((t) => !bTokens.includes(t));
|
|
216
|
+
const bOnly = bTokens.filter((t) => !aTokens.includes(t));
|
|
217
|
+
const shared = aTokens.filter((t) => bTokens.includes(t));
|
|
218
|
+
if (shared.length >= 2 && aOnly.length + bOnly.length <= 2) {
|
|
219
|
+
const diff = [...aOnly, ...bOnly].map((t) => `'${t}'`).join(' vs ') || 'nothing — the names match';
|
|
220
|
+
return (`names differ only by ${diff} — make the descriptions say WHEN to choose each ` +
|
|
221
|
+
`(different backend/data source? live vs historical? freshness?), ` +
|
|
222
|
+
`e.g. "Use for …; prefer ${b.name} when …"`);
|
|
223
|
+
}
|
|
224
|
+
const aDesc = descriptionTokens(a);
|
|
225
|
+
const bDesc = descriptionTokens(b);
|
|
226
|
+
const aDistinct = [...aDesc].filter((t) => !bDesc.has(t)).slice(0, 3);
|
|
227
|
+
const bDistinct = [...bDesc].filter((t) => !aDesc.has(t)).slice(0, 3);
|
|
228
|
+
if (aDistinct.length === 0 && bDistinct.length === 0) {
|
|
229
|
+
return `the descriptions are near-duplicates — rewrite one to state when it, and not ${b.name}, is the right call`;
|
|
230
|
+
}
|
|
231
|
+
return (`descriptions overlap heavily; the distinct terms are ` +
|
|
232
|
+
`${a.name}: [${aDistinct.join(', ')}] vs ${b.name}: [${bDistinct.join(', ')}] — ` +
|
|
233
|
+
`lead with an explicit choice condition ("use when …") built on those`);
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../../../src/lib/tool-lint/analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-lint CLI core (RFC-002 block C3 — the CI gate).
|
|
3
|
+
*
|
|
4
|
+
* Pattern: humble shell — `bin/agentfootprint-lint-tools.mjs` is a
|
|
5
|
+
* 3-line wrapper; ALL behavior (arg parsing, catalog coercion,
|
|
6
|
+
* report, exit code) lives here so it is unit-testable without
|
|
7
|
+
* spawning a process.
|
|
8
|
+
* Role: `src/lib/tool-lint/`. Reads ONE JSON file of tools, prints a
|
|
9
|
+
* report, returns the process exit code:
|
|
10
|
+
* 0 — report.ok
|
|
11
|
+
* 1 — findings failed the gate (!ok)
|
|
12
|
+
* 2 — usage / input error (bad flags, unreadable file,
|
|
13
|
+
* unrecognized JSON shape)
|
|
14
|
+
*
|
|
15
|
+
* ## Embedder & gating honesty
|
|
16
|
+
*
|
|
17
|
+
* The CLI has no way to receive a consumer embedder, so it uses the
|
|
18
|
+
* built-in deterministic mock (char-frequency, offline, dependency-free)
|
|
19
|
+
* for the similarity RANKING — and, by default, does NOT gate on it:
|
|
20
|
+
* without `--threshold`, similarity is report-only (relative ordering +
|
|
21
|
+
* watch hints) and the exit code reflects structural findings alone.
|
|
22
|
+
* Pass `--threshold` to make confusable pairs fail the gate — you own
|
|
23
|
+
* the calibration at that point (start from
|
|
24
|
+
* `MOCK_EMBEDDER_CALIBRATION.confusabilityThreshold` = 0.94). For real
|
|
25
|
+
* embedder gating, use `analyzeToolCatalog` from
|
|
26
|
+
* `agentfootprint/observe` in a small script instead.
|
|
27
|
+
*/
|
|
28
|
+
import { readFile } from 'node:fs/promises';
|
|
29
|
+
import { mockEmbedder } from '../../memory/embedding/mockEmbedder.js';
|
|
30
|
+
import { analyzeToolCatalog, MOCK_EMBEDDER_CALIBRATION } from './analyze.js';
|
|
31
|
+
import { formatToolCatalogReport } from './format.js';
|
|
32
|
+
const USAGE = `usage: agentfootprint-lint-tools <tools.json> [options]
|
|
33
|
+
|
|
34
|
+
<tools.json> JSON file with your tool catalog. Accepted shapes:
|
|
35
|
+
[{ name, description, inputSchema? }] (plain / MCP tool)
|
|
36
|
+
{ tools: [...] } (MCP tools/list result)
|
|
37
|
+
[{ type: 'function', function: {...} }] (OpenAI)
|
|
38
|
+
[{ name, description, input_schema }] (Anthropic)
|
|
39
|
+
|
|
40
|
+
--threshold <num> gate on confusable pairs at this cosine (mock-embedder
|
|
41
|
+
starting point: ${MOCK_EMBEDDER_CALIBRATION.confusabilityThreshold}). Without it, similarity is
|
|
42
|
+
REPORT-ONLY and only structural findings gate.
|
|
43
|
+
--watch-band <num> advisory band below the threshold (default ${MOCK_EMBEDDER_CALIBRATION.watchBand} with --threshold)
|
|
44
|
+
--strict structural warnings also fail the gate
|
|
45
|
+
--no-similarity skip the similarity analysis entirely
|
|
46
|
+
--top <n> ranked pairs to print (default 10)
|
|
47
|
+
--json print the full report as JSON instead of text
|
|
48
|
+
|
|
49
|
+
exit codes: 0 ok · 1 findings failed the gate · 2 usage/input error`;
|
|
50
|
+
/**
|
|
51
|
+
* Normalize any of the recognized tool-list JSON shapes to the lint's
|
|
52
|
+
* plain catalog. Throws (with a shape description) on unrecognized
|
|
53
|
+
* input — the CLI maps that to exit code 2.
|
|
54
|
+
*/
|
|
55
|
+
export function coerceCatalog(json) {
|
|
56
|
+
// { tools: [...] } — MCP `tools/list` result envelope.
|
|
57
|
+
const list = Array.isArray(json)
|
|
58
|
+
? json
|
|
59
|
+
: json !== null &&
|
|
60
|
+
typeof json === 'object' &&
|
|
61
|
+
Array.isArray(json.tools)
|
|
62
|
+
? json.tools
|
|
63
|
+
: undefined;
|
|
64
|
+
if (list === undefined) {
|
|
65
|
+
throw new Error('expected a JSON array of tools or { tools: [...] }');
|
|
66
|
+
}
|
|
67
|
+
return list.map((raw, index) => {
|
|
68
|
+
if (raw === null || typeof raw !== 'object') {
|
|
69
|
+
throw new Error(`tools[${index}] is not an object`);
|
|
70
|
+
}
|
|
71
|
+
const entry = raw;
|
|
72
|
+
// OpenAI: { type: 'function', function: { name, description, parameters } }
|
|
73
|
+
const fn = entry.type === 'function' && entry.function !== null && typeof entry.function === 'object'
|
|
74
|
+
? entry.function
|
|
75
|
+
: entry;
|
|
76
|
+
const name = fn.name;
|
|
77
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
78
|
+
throw new Error(`tools[${index}] has no string 'name'`);
|
|
79
|
+
}
|
|
80
|
+
const description = typeof fn.description === 'string' ? fn.description : undefined;
|
|
81
|
+
// inputSchema (MCP/ours) | input_schema (Anthropic) | parameters (OpenAI)
|
|
82
|
+
const schema = fn.inputSchema ?? fn.input_schema ?? fn.parameters;
|
|
83
|
+
const inputSchema = schema !== null && typeof schema === 'object'
|
|
84
|
+
? schema
|
|
85
|
+
: undefined;
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
...(description !== undefined ? { description } : {}),
|
|
89
|
+
...(inputSchema !== undefined ? { inputSchema } : {}),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function parseArgs(argv) {
|
|
94
|
+
let file;
|
|
95
|
+
let threshold;
|
|
96
|
+
let watchBand;
|
|
97
|
+
let strict = false;
|
|
98
|
+
let similarity = true;
|
|
99
|
+
let top = 10;
|
|
100
|
+
let json = false;
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
const arg = argv[i];
|
|
103
|
+
const numberFlag = (name) => {
|
|
104
|
+
const value = Number(argv[++i]);
|
|
105
|
+
if (!Number.isFinite(value))
|
|
106
|
+
throw new Error(`${name} expects a number`);
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
if (arg === '--threshold')
|
|
110
|
+
threshold = numberFlag('--threshold');
|
|
111
|
+
else if (arg === '--watch-band')
|
|
112
|
+
watchBand = numberFlag('--watch-band');
|
|
113
|
+
else if (arg === '--strict')
|
|
114
|
+
strict = true;
|
|
115
|
+
else if (arg === '--no-similarity')
|
|
116
|
+
similarity = false;
|
|
117
|
+
else if (arg === '--top')
|
|
118
|
+
top = numberFlag('--top');
|
|
119
|
+
else if (arg === '--json')
|
|
120
|
+
json = true;
|
|
121
|
+
else if (arg === '--help' || arg === '-h')
|
|
122
|
+
throw new Error(USAGE);
|
|
123
|
+
else if (arg.startsWith('-'))
|
|
124
|
+
throw new Error(`unknown flag '${arg}'\n\n${USAGE}`);
|
|
125
|
+
else if (file === undefined)
|
|
126
|
+
file = arg;
|
|
127
|
+
else
|
|
128
|
+
throw new Error(`unexpected extra argument '${arg}'\n\n${USAGE}`);
|
|
129
|
+
}
|
|
130
|
+
if (file === undefined)
|
|
131
|
+
throw new Error(USAGE);
|
|
132
|
+
return {
|
|
133
|
+
file,
|
|
134
|
+
...(threshold !== undefined ? { threshold } : {}),
|
|
135
|
+
...(watchBand !== undefined ? { watchBand } : {}),
|
|
136
|
+
strict,
|
|
137
|
+
similarity,
|
|
138
|
+
top,
|
|
139
|
+
json,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Run the lint CLI. Returns the exit code (never calls `process.exit` —
|
|
144
|
+
* the bin wrapper assigns it to `process.exitCode`).
|
|
145
|
+
*/
|
|
146
|
+
export async function runToolLintCli(argv, io = {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
stdout: (line) => console.log(line),
|
|
149
|
+
// eslint-disable-next-line no-console
|
|
150
|
+
stderr: (line) => console.error(line),
|
|
151
|
+
}) {
|
|
152
|
+
let args;
|
|
153
|
+
try {
|
|
154
|
+
args = parseArgs(argv);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
io.stderr(error.message);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
let catalog;
|
|
161
|
+
try {
|
|
162
|
+
catalog = coerceCatalog(JSON.parse(await readFile(args.file, 'utf8')));
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
io.stderr(`agentfootprint-lint-tools: ${args.file}: ${error.message}`);
|
|
166
|
+
return 2;
|
|
167
|
+
}
|
|
168
|
+
// Without --threshold the mock-embedder similarity is REPORT-ONLY:
|
|
169
|
+
// rank pairs at an unreachable threshold (no 'confusable'/'watch'
|
|
170
|
+
// verdicts) so only the relative-ordering section prints.
|
|
171
|
+
const gateOnSimilarity = args.threshold !== undefined;
|
|
172
|
+
const threshold = args.threshold ?? Infinity;
|
|
173
|
+
const watchBand = args.watchBand ?? (gateOnSimilarity ? MOCK_EMBEDDER_CALIBRATION.watchBand : 0);
|
|
174
|
+
const report = await analyzeToolCatalog(catalog, {
|
|
175
|
+
...(args.similarity ? { embedder: mockEmbedder() } : {}),
|
|
176
|
+
confusabilityThreshold: threshold,
|
|
177
|
+
watchBand,
|
|
178
|
+
failOn: args.strict ? 'warn' : 'error',
|
|
179
|
+
});
|
|
180
|
+
if (args.json) {
|
|
181
|
+
io.stdout(JSON.stringify(report, null, 2));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
if (args.similarity) {
|
|
185
|
+
io.stdout(gateOnSimilarity
|
|
186
|
+
? '⚠ similarity uses the built-in deterministic mock embedder — you own the ' +
|
|
187
|
+
'calibration of --threshold (cosine ranges are per-embedder; mock compresses ' +
|
|
188
|
+
'prose to ~0.85–0.97). Trust relative ordering first.'
|
|
189
|
+
: 'ℹ similarity is REPORT-ONLY (no --threshold): ranked pairs below are the ' +
|
|
190
|
+
'relative-ordering view from the built-in mock embedder. Pass --threshold ' +
|
|
191
|
+
`(mock starting point ${MOCK_EMBEDDER_CALIBRATION.confusabilityThreshold}) to gate on confusable pairs.`);
|
|
192
|
+
io.stdout('');
|
|
193
|
+
}
|
|
194
|
+
io.stdout(formatToolCatalogReport(report, { topPairs: args.top }));
|
|
195
|
+
}
|
|
196
|
+
return report.ok ? 0 : 1;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/lib/tool-lint/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* formatToolCatalogReport — human-readable rendering of a lint report.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: pure presenter. One report → one string; used verbatim by the
|
|
5
|
+
* CLI (`agentfootprint-lint-tools`) and the examples so output
|
|
6
|
+
* stays byte-identical across surfaces.
|
|
7
|
+
* Role: `src/lib/tool-lint/` leaf. No I/O.
|
|
8
|
+
*/
|
|
9
|
+
export function formatToolCatalogReport(report, options = {}) {
|
|
10
|
+
const topPairs = options.topPairs ?? 10;
|
|
11
|
+
const maxWatch = options.maxWatch ?? 10;
|
|
12
|
+
const lines = [];
|
|
13
|
+
const { similarity, structural, summary } = report;
|
|
14
|
+
lines.push(`tool-catalog lint — ${report.toolCount} tools · ` +
|
|
15
|
+
`${summary.confusable} confusable · ${summary.watch} watch · ` +
|
|
16
|
+
`${summary.errors} errors · ${summary.warnings} warnings`);
|
|
17
|
+
if (similarity.analyzed) {
|
|
18
|
+
lines.push('', `confusability (threshold ${similarity.thresholds.confusabilityThreshold}, ` +
|
|
19
|
+
`watch band ${similarity.thresholds.watchBand}):`);
|
|
20
|
+
if (similarity.confusable.length === 0 && similarity.watch.length === 0) {
|
|
21
|
+
lines.push(' no pairs at or near the threshold');
|
|
22
|
+
}
|
|
23
|
+
for (const pair of similarity.confusable) {
|
|
24
|
+
lines.push(` ✗ CONFUSABLE ${pair.similarity.toFixed(4)} ${pair.a} <> ${pair.b}`);
|
|
25
|
+
lines.push(` hint: ${pair.hint}`);
|
|
26
|
+
}
|
|
27
|
+
for (const pair of similarity.watch.slice(0, maxWatch)) {
|
|
28
|
+
lines.push(` ~ watch ${pair.similarity.toFixed(4)} ${pair.a} <> ${pair.b}`);
|
|
29
|
+
lines.push(` hint: ${pair.hint}`);
|
|
30
|
+
}
|
|
31
|
+
if (similarity.watch.length > maxWatch) {
|
|
32
|
+
lines.push(` … and ${similarity.watch.length - maxWatch} more watch pairs (see report.similarity.watch)`);
|
|
33
|
+
}
|
|
34
|
+
if (topPairs > 0 && similarity.ranked.length > 0) {
|
|
35
|
+
lines.push('', ` most-similar pairs (relative ordering — top ${topPairs}):`);
|
|
36
|
+
for (const pair of similarity.ranked.slice(0, topPairs)) {
|
|
37
|
+
lines.push(` ${pair.similarity.toFixed(4)} ${pair.a} <> ${pair.b}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
lines.push('', 'confusability: skipped (no embedder supplied — structural rules only)');
|
|
43
|
+
}
|
|
44
|
+
if (structural.length > 0) {
|
|
45
|
+
lines.push('', 'structural findings:');
|
|
46
|
+
for (const finding of structural) {
|
|
47
|
+
const where = finding.param ? `${finding.tool}.${finding.param}` : finding.tool;
|
|
48
|
+
const mark = finding.severity === 'error' ? '✗' : '~';
|
|
49
|
+
lines.push(` ${mark} ${finding.severity.padEnd(5)} [${finding.rule}] ${where}`);
|
|
50
|
+
lines.push(` ${finding.message}`);
|
|
51
|
+
if (finding.suggestion)
|
|
52
|
+
lines.push(` suggest: ${finding.suggestion}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
lines.push('', 'structural findings: none');
|
|
57
|
+
}
|
|
58
|
+
lines.push('', report.ok ? 'RESULT: ok' : 'RESULT: FAIL');
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../src/lib/tool-lint/format.ts"],"names":[],"mappings":"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"}
|