@eviano/tribunal 0.1.2 → 0.2.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 +177 -11
- package/dist/analyzers/claimReconciliation.js +43 -22
- package/dist/analyzers/claimReconciliation.js.map +1 -1
- package/dist/analyzers/commentCodeDrift.d.ts +37 -0
- package/dist/analyzers/commentCodeDrift.js +241 -0
- package/dist/analyzers/commentCodeDrift.js.map +1 -0
- package/dist/analyzers/hallucinatedSymbol.js +22 -0
- package/dist/analyzers/hallucinatedSymbol.js.map +1 -1
- package/dist/analyzers/index.d.ts +5 -1
- package/dist/analyzers/index.js +12 -2
- package/dist/analyzers/index.js.map +1 -1
- package/dist/analyzers/riskyDiffNoTest.d.ts +36 -0
- package/dist/analyzers/riskyDiffNoTest.js +221 -0
- package/dist/analyzers/riskyDiffNoTest.js.map +1 -0
- package/dist/cli.js +167 -24
- package/dist/cli.js.map +1 -1
- package/dist/config/loadConfig.d.ts +19 -0
- package/dist/config/loadConfig.js +89 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/config/parseYaml.d.ts +17 -0
- package/dist/config/parseYaml.js +131 -0
- package/dist/config/parseYaml.js.map +1 -0
- package/dist/config/types.d.ts +17 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/diff/parseUnifiedDiff.d.ts +4 -0
- package/dist/diff/parseUnifiedDiff.js +45 -18
- package/dist/diff/parseUnifiedDiff.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +27 -0
- package/dist/paths.js +112 -0
- package/dist/paths.js.map +1 -0
- package/dist/propose.d.ts +103 -0
- package/dist/propose.js +175 -0
- package/dist/propose.js.map +1 -0
- package/dist/report/render.d.ts +5 -0
- package/dist/report/render.js +78 -0
- package/dist/report/render.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,27 +28,30 @@ guarantees, not vibes:
|
|
|
28
28
|
5. **Reporter-first.** Default mode is a non-blocking PR comment. Teams earn trust in the signal before
|
|
29
29
|
it can break their build.
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
[](https://www.npmjs.com/package/@eviano/tribunal)
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
npm
|
|
35
|
-
npm test # run the suite
|
|
36
|
-
npm run check -- check --help
|
|
36
|
+
npm i -D @eviano/tribunal
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Run it against
|
|
39
|
+
Run it against a PR range (report-only by default; gates only on 🔴 CONTRADICTED):
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
npx @eviano/tribunal check --base main --head HEAD
|
|
43
|
+
npx @eviano/tribunal check # diff working tree vs HEAD
|
|
44
|
+
npx @eviano/tribunal check --diff some.patch # analyze a unified diff file
|
|
45
|
+
npx @eviano/tribunal check --format json
|
|
46
|
+
npx @eviano/tribunal check --base main --head HEAD --hard-fail # block the build
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
## Develop from source
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
|
-
npm
|
|
52
|
+
npm install
|
|
53
|
+
npm test # run the suite
|
|
54
|
+
npm run check -- check --help
|
|
52
55
|
```
|
|
53
56
|
|
|
54
57
|
## Use it in CI (GitHub Action)
|
|
@@ -78,6 +81,78 @@ jobs:
|
|
|
78
81
|
|
|
79
82
|
This repo dogfoods its own action — see [.github/workflows/tribunal.yml](.github/workflows/tribunal.yml).
|
|
80
83
|
|
|
84
|
+
## SARIF output (GitHub code-scanning alerts)
|
|
85
|
+
|
|
86
|
+
Tribunal can emit [SARIF](https://docs.oasis-open.org/sarif/) so findings appear as GitHub
|
|
87
|
+
code-scanning alerts with proper tracking, dismissal, and filter-by-severity across runs.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
tribunal check --diff pr.diff --format sarif > tribunal.sarif
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Level mapping (deliberate — matches "gate only on CONTRADICTED"):
|
|
94
|
+
|
|
95
|
+
| Tribunal verdict | SARIF level |
|
|
96
|
+
|---|---|
|
|
97
|
+
| 🔴 CONTRADICTED | `error` |
|
|
98
|
+
| 🟡 UNVERIFIED | `note` |
|
|
99
|
+
| 🟢 PASS | _omitted_ |
|
|
100
|
+
|
|
101
|
+
Only the blocking verdict becomes an `error` alert; UNVERIFIED is visible but non-noisy. Each finding
|
|
102
|
+
carries a stable `partialFingerprints` (derived from analyzer + file + line + claim, **not** from the
|
|
103
|
+
prose) so GitHub tracks the same alert across runs — rewording a finding's title won't fork a new one.
|
|
104
|
+
|
|
105
|
+
Upload it in a workflow (bring your own upload step; the bundled action stays markdown-only to keep its
|
|
106
|
+
permission footprint minimal):
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
jobs:
|
|
110
|
+
tribunal-sarif:
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
permissions:
|
|
113
|
+
contents: read
|
|
114
|
+
security-events: write # required by upload-sarif
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v4
|
|
117
|
+
with: { fetch-depth: 0 }
|
|
118
|
+
- run: npx @eviano/tribunal check \
|
|
119
|
+
--base ${{ github.event.pull_request.base.sha }} \
|
|
120
|
+
--head ${{ github.event.pull_request.head.sha }} \
|
|
121
|
+
--format sarif > tribunal.sarif
|
|
122
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
123
|
+
with:
|
|
124
|
+
sarif_file: tribunal.sarif
|
|
125
|
+
category: tribunal
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
SARIF is data, never a gate — the `--hard-fail` exit code still comes from `exitCode()`. You can run
|
|
129
|
+
both: post the markdown comment (via the action) **and** upload SARIF in the same job.
|
|
130
|
+
|
|
131
|
+
## Configuration (`tribunal.yml`)
|
|
132
|
+
|
|
133
|
+
Optionally configure Tribunal with a `tribunal.yml` at the repo root (override the path with `--config
|
|
134
|
+
<file>` or `TRIBUNAL_CONFIG`). With **no** file, all defaults apply — config is purely additive.
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
# tribunal.yml
|
|
138
|
+
analyzers:
|
|
139
|
+
risky-diff-no-test: false # disable an analyzer (default: all enabled)
|
|
140
|
+
generated-paths: # EXTRA generated paths, appended to the built-ins
|
|
141
|
+
- vendor-generated/
|
|
142
|
+
- "**/*.gen.ts"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **`analyzers`** — a map of analyzer id → boolean. Default: every analyzer enabled. Disabling
|
|
146
|
+
`claim-reconciliation` just stops claim checks; claim-independent analyzers still run. **Unknown ids are
|
|
147
|
+
rejected** (typo guard): a misspelled key fails loud rather than silently no-op'ing.
|
|
148
|
+
- **`generated-paths`** — extra generated/build-output patterns, **appended** to the built-ins
|
|
149
|
+
(`dist/`, `action-dist/`, `*.min.js`, …). Config can only *add* coverage — never replace — so a user
|
|
150
|
+
can't accidentally drop the `dist/` safety net. Patterns may be dir-prefixes (`vendor-generated/`),
|
|
151
|
+
suffixes (`.gen.ts`), or simple globs (`**/*.gen.ts`, `src/generated/*`).
|
|
152
|
+
|
|
153
|
+
A present-but-malformed file (bad key, unknown analyzer, bad YAML) **fails loud** with a clear error
|
|
154
|
+
pointing at the line — it never silently degrades to defaults.
|
|
155
|
+
|
|
81
156
|
## What M0 catches: `assertion-free-test`
|
|
82
157
|
|
|
83
158
|
A test the PR added or changed that **can never fail** because it asserts nothing:
|
|
@@ -114,6 +189,57 @@ re-exports, and degrades to 🟡 UNVERIFIED whenever exports can't be statically
|
|
|
114
189
|
unresolvable re-exports). Default imports are never flagged (interop synthesizes a default). Scope for
|
|
115
190
|
M1 is imports; full identifier/call resolution is a later increment.
|
|
116
191
|
|
|
192
|
+
## What it also ships: `risky-diff-no-test`
|
|
193
|
+
|
|
194
|
+
A **signal** analyzer (never a gate) that fires when a PR touches a security-relevant area but adds no
|
|
195
|
+
correlated asserting test — the "silence is not an escape hatch" check. An agent that changes auth,
|
|
196
|
+
crypto, or payment code and ships nothing to cover it gets a finding even when it wrote no PR body:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
// src/auth.ts — changed by the PR
|
|
200
|
+
export function login(user, pass) { return user === pass; } // 🟡 UNVERIFIED: risky area ('auth'),
|
|
201
|
+
// no correlated asserting test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- 🟡 **UNVERIFIED** — a risky area (path segment *or* a changed-line identifier token: `auth`,
|
|
205
|
+
`crypto`, `payment`, `token`, `secret`, …) was touched but no correlated asserting test was detected.
|
|
206
|
+
**Never blocks** — "is this risky?" is semantic, and a semantic guess may never flip a build red.
|
|
207
|
+
- 🟢 **PASS** — risky area touched **and** a correlated asserting test was added (test basename stem
|
|
208
|
+
shares a token with the risky file, e.g. `auth.ts` ↔ `auth.test.ts`).
|
|
209
|
+
|
|
210
|
+
It tokenizes (camelCase / kebab / snake) and matches on whole tokens, not substrings, so `authors.ts`
|
|
211
|
+
and `tokenize.ts` are correctly *not* flagged. Because it can only ever emit PASS/UNVERIFIED, it is safe
|
|
212
|
+
to run under `--hard-fail`.
|
|
213
|
+
|
|
214
|
+
**Generated-path skip.** A bundled artifact (e.g. `action-dist/cli.cjs`, `dist/*.min.js`) carries the
|
|
215
|
+
project's own risky vocab but isn't human-authored source a reviewer can act on, so flagging it is
|
|
216
|
+
noisy. This analyzer **skips generated/build-output paths by default** (`dist/`, `action-dist/`,
|
|
217
|
+
`build/`, `out/`, `.next/`, `coverage/`, `node_modules/`, and `*.min.{js,cjs,mjs}` / `*.bundle.js`).
|
|
218
|
+
Pass `--no-skip-generated` (or set `TRIBUNAL_NO_SKIP_GENERATED=1`) to re-enable flagging them — an
|
|
219
|
+
opinionated default must never silently suppress a file you want checked.
|
|
220
|
+
|
|
221
|
+
## `comment-code-drift`
|
|
222
|
+
|
|
223
|
+
A **signal** analyzer (never a gate) that flags a comment/docstring which references a symbol whose
|
|
224
|
+
**definition changed** in the diff (body edit, rename, or removal), where the comment itself wasn't
|
|
225
|
+
freshly added. Catches the classic agent failure: it edits a function's behavior or renames it, but
|
|
226
|
+
leaves the docstring describing the old behavior/name.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// src/greet.ts
|
|
230
|
+
// greet: returns a friendly greeting ← pre-existing comment (not added this PR)
|
|
231
|
+
export function greet() { return 'hello'; } // ← body changed 'hi' → 'hello'
|
|
232
|
+
// → 🟡 UNVERIFIED: comment mentions 'greet', whose definition changed. Verify the wording is accurate.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- 🟡 **UNVERIFIED** — a comment (in the changed file **or** another changed file) mentions a changed
|
|
236
|
+
symbol by its **exact identifier token**, and the comment line was **not added** in this diff (a
|
|
237
|
+
freshly-added comment is unlikely stale — the key false-positive cut).
|
|
238
|
+
- It only fires when a **base ref** is available (it compares base vs head ASTs to find changed
|
|
239
|
+
declarations). With no base ref, it's silent.
|
|
240
|
+
- Like the other signal analyzers it **never blocks**: comment staleness is semantic, so it emits
|
|
241
|
+
PASS/UNVERIFIED only and is safe under `--hard-fail`.
|
|
242
|
+
|
|
117
243
|
## What M3 adds: `claim-reconciliation`
|
|
118
244
|
|
|
119
245
|
Verifies the agent's **own claims** against the diff — deterministically. Claims are declared in a
|
|
@@ -137,6 +263,46 @@ no-public-api-change
|
|
|
137
263
|
Pass claims with `--claims <file>` (a claims file) or `--pr-body <file>` (reads only the fenced block).
|
|
138
264
|
Adding a new claim is one entry in the verifier registry.
|
|
139
265
|
|
|
266
|
+
## `tribunal propose` — let an LLM PROPOSE claims (it never adjudicates)
|
|
267
|
+
|
|
268
|
+
No agent emits the ` ```tribunal ` convention today, so `claim-reconciliation` (above) does nothing on a
|
|
269
|
+
normal agent PR. `propose` closes that gap: it asks an LLM to *suggest* which claims a reviewer should
|
|
270
|
+
ask the deterministic engine to check, then writes a ` ```tribunal ` block that `check` consumes.
|
|
271
|
+
|
|
272
|
+
This is the narrow, Trust-Contract-compliant use of an LLM: it **proposes**; deterministic code
|
|
273
|
+
**adjudicates**. The LLM is never in the verification path.
|
|
274
|
+
|
|
275
|
+
**The two-step loop:**
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# 1) propose — the LLM reads the diff and suggests claims (writes claims.md)
|
|
279
|
+
tribunal propose --diff pr.diff \
|
|
280
|
+
--endpoint https://api.openai.com/v1 --model gpt-4o-mini \
|
|
281
|
+
--allow-send-diff --out claims.md
|
|
282
|
+
|
|
283
|
+
# 2) check — deterministic engine verifies the proposed claims against the diff
|
|
284
|
+
tribunal check --diff pr.diff --pr-body claims.md --hard-fail
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`propose` is pluggable: point `--endpoint` at any OpenAI-compatible chat-completions URL (OpenAI,
|
|
288
|
+
OpenRouter, a local Ollama / LM Studio server). Configure via flags or `TRIBUNAL_ENDPOINT` /
|
|
289
|
+
`TRIBUNAL_MODEL` / `TRIBUNAL_API_KEY`.
|
|
290
|
+
|
|
291
|
+
**The send-guard.** The diff is your source code, and sending it to an LLM endpoint is an outward-facing
|
|
292
|
+
publish. So `propose` **refuses to send by default** — without `--allow-send-diff` it prints the prompt
|
|
293
|
+
for review and sends *nothing*. Add the flag (or `TRIBUNAL_ALLOW_SEND_DIFF=1`) only when you've chosen
|
|
294
|
+
the endpoint and accept that the diff leaves the machine.
|
|
295
|
+
|
|
296
|
+
**Why this can't break the gate.** Two architectural guarantees hold even if the model hallucinates or is
|
|
297
|
+
prompt-injected:
|
|
298
|
+
|
|
299
|
+
1. `propose` is a separate command and code path. It never imports or calls any verifier, `runTribunal`,
|
|
300
|
+
or `exitCode`. It only reads a diff and writes a claims block.
|
|
301
|
+
2. The model can only ever emit claim *keys*. Keys outside the recognized set are downgraded to
|
|
302
|
+
🟡 UNVERIFIED by the verifier registry — never 🔴 CONTRADICTED. Since `--hard-fail` gates only on
|
|
303
|
+
CONTRADICTED, **the LLM path cannot manufacture a false red**, no matter what it returns. (A *legitimate*
|
|
304
|
+
CONTRADICTED can still appear — that's the deterministic checker doing its job, which is the point.)
|
|
305
|
+
|
|
140
306
|
## Benchmark — the moat-proof
|
|
141
307
|
|
|
142
308
|
Tribunal is only safe as a hard-fail gate if it almost never false-fires. That's measured, not
|
|
@@ -42,7 +42,20 @@ function verifyAddedTest(ctx) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
const EMPTY_EXPORTS = { names: new Set(), hasDefault: false, uncertain: false };
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* "no public API change" → the AGGREGATE exported-symbol set across changed files is identical between
|
|
47
|
+
* base and head.
|
|
48
|
+
*
|
|
49
|
+
* Why aggregate, not per-file: a refactor that moves `export const foo` from a.ts to b.ts (both changed)
|
|
50
|
+
* leaves the package surface unchanged, yet a per-file diff reports `-foo` in one file and `+foo` in the
|
|
51
|
+
* other — a false CONTRADICTED. Unchanged files export the same set on both sides, so they cancel out of
|
|
52
|
+
* the diff; aggregating only over changed files is therefore sound and bounded, and fixes the move case.
|
|
53
|
+
*
|
|
54
|
+
* Residual limitation: a symbol moved from a changed file into an UNCHANGED file still looks removed,
|
|
55
|
+
* because unchanged files are outside this diff-local view. Resolving that needs entry-point scoping
|
|
56
|
+
* (api-extractor); until then such a case degrades to UNVERIFIED via the `uncertain` path only when an
|
|
57
|
+
* uncertain module is present. This is strictly tighter than the prior per-file behavior.
|
|
58
|
+
*/
|
|
46
59
|
function verifyNoPublicApiChange(ctx) {
|
|
47
60
|
if (!ctx.base || !ctx.readBaseFile) {
|
|
48
61
|
return {
|
|
@@ -51,7 +64,10 @@ function verifyNoPublicApiChange(ctx) {
|
|
|
51
64
|
detail: 'Exported-symbol changes need a base ref to diff against; none was available.',
|
|
52
65
|
};
|
|
53
66
|
}
|
|
54
|
-
const
|
|
67
|
+
const baseNames = new Set();
|
|
68
|
+
const headNames = new Set();
|
|
69
|
+
let baseDefault = false;
|
|
70
|
+
let headDefault = false;
|
|
55
71
|
let uncertain = false;
|
|
56
72
|
for (const f of ctx.changedFiles) {
|
|
57
73
|
if (!SOURCE_EXT_RE.test(f.path) || isTestPath(f.path))
|
|
@@ -62,29 +78,34 @@ function verifyNoPublicApiChange(ctx) {
|
|
|
62
78
|
continue;
|
|
63
79
|
const baseExp = baseContent ? directExports(baseContent, f.path) : EMPTY_EXPORTS;
|
|
64
80
|
const headExp = headContent ? directExports(headContent, f.path) : EMPTY_EXPORTS;
|
|
65
|
-
if (baseExp.uncertain || headExp.uncertain)
|
|
81
|
+
if (baseExp.uncertain || headExp.uncertain)
|
|
66
82
|
uncertain = true;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
if (baseExp.hasDefault
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const parts = [];
|
|
76
|
-
if (added.length)
|
|
77
|
-
parts.push(`+${added.join(', +')}`);
|
|
78
|
-
if (removed.length)
|
|
79
|
-
parts.push(`-${removed.join(', -')}`);
|
|
80
|
-
changes.push(`${f.path} (${parts.join('; ')})`);
|
|
81
|
-
}
|
|
83
|
+
for (const n of baseExp.names)
|
|
84
|
+
baseNames.add(n);
|
|
85
|
+
for (const n of headExp.names)
|
|
86
|
+
headNames.add(n);
|
|
87
|
+
if (baseExp.hasDefault)
|
|
88
|
+
baseDefault = true;
|
|
89
|
+
if (headExp.hasDefault)
|
|
90
|
+
headDefault = true;
|
|
82
91
|
}
|
|
83
|
-
|
|
92
|
+
const added = [...headNames].filter((n) => !baseNames.has(n));
|
|
93
|
+
const removed = [...baseNames].filter((n) => !headNames.has(n));
|
|
94
|
+
if (baseDefault !== headDefault)
|
|
95
|
+
(headDefault ? added : removed).push('default');
|
|
96
|
+
// Only CONTRADICT when the net change is a certainty. If any changed module is non-enumerable
|
|
97
|
+
// (`export *`, CJS), a "removed" symbol may live there and an "added" one may have pre-existed — so
|
|
98
|
+
// degrade to UNVERIFIED rather than risk a false red (Trust Contract §3.4).
|
|
99
|
+
if ((added.length || removed.length) && !uncertain) {
|
|
100
|
+
const parts = [];
|
|
101
|
+
if (added.length)
|
|
102
|
+
parts.push(`+${added.join(', +')}`);
|
|
103
|
+
if (removed.length)
|
|
104
|
+
parts.push(`-${removed.join(', -')}`);
|
|
84
105
|
return {
|
|
85
106
|
verdict: 'CONTRADICTED',
|
|
86
|
-
title: 'Claimed no public API change, but
|
|
87
|
-
detail: `
|
|
107
|
+
title: 'Claimed no public API change, but the exported surface changed',
|
|
108
|
+
detail: `Net exported-symbol change across changed files: ${parts.join('; ')}.`,
|
|
88
109
|
};
|
|
89
110
|
}
|
|
90
111
|
if (uncertain) {
|
|
@@ -97,7 +118,7 @@ function verifyNoPublicApiChange(ctx) {
|
|
|
97
118
|
return {
|
|
98
119
|
verdict: 'PASS',
|
|
99
120
|
title: 'Claim confirmed: no public API change',
|
|
100
|
-
detail: '
|
|
121
|
+
detail: 'The aggregate exported-symbol set across changed files is unchanged.',
|
|
101
122
|
};
|
|
102
123
|
}
|
|
103
124
|
/** "no default flip" → no literal default parameter value changed between base and head. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claimReconciliation.js","sourceRoot":"","sources":["../../src/analyzers/claimReconciliation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAsB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpE;;;;;;;GAOG;AAEH,MAAM,aAAa,GAAG,kBAAkB,CAAC;AACzC,MAAM,YAAY,GAAG,uCAAuC,CAAC;AAC7D,MAAM,WAAW,GAAG,mCAAmC,CAAC;AAExD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AASD,qGAAqG;AACrG,SAAS,eAAe,CAAC,GAAoB;IAC3C,6FAA6F;IAC7F,MAAM,YAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC;IACpC,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAE1E,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO;YACL,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,6BAA6B;YACpC,MAAM,EAAE,iBAAiB,SAAS,uCAAuC;SAC1E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO;YACL,OAAO,EAAE,cAAc;YACvB,KAAK,EAAE,mDAAmD;YAC1D,MAAM,EAAE,kFAAkF;SAC3F,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,YAAY;QACrB,KAAK,EAAE,qDAAqD;QAC5D,MAAM,EAAE,iBAAiB,OAAO,uHAAuH;KACxJ,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAAkB,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAE/F
|
|
1
|
+
{"version":3,"file":"claimReconciliation.js","sourceRoot":"","sources":["../../src/analyzers/claimReconciliation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Analyzer } from '../types.js';
|
|
2
|
+
interface DeclInfo {
|
|
3
|
+
/** Declaration name (e.g. function/class/const/type name). */
|
|
4
|
+
name: string;
|
|
5
|
+
/** 1-based start line of the declaration (the `function foo(...)` line). */
|
|
6
|
+
line: number;
|
|
7
|
+
}
|
|
8
|
+
/** Collect top-level + class-member declaration names with their declaration line. */
|
|
9
|
+
declare function collectDeclarations(content: string, path: string): DeclInfo[];
|
|
10
|
+
/**
|
|
11
|
+
* Identify identifiers whose declaration changed in the diff for one file. A declaration "changed" if:
|
|
12
|
+
* - its line in base differs from head (body edit / moved), OR
|
|
13
|
+
* - it exists in base but not head (removed/renamed).
|
|
14
|
+
* Returns the set of changed names plus the file's added-line set (for the "comment not freshly added"
|
|
15
|
+
* exclusion).
|
|
16
|
+
*/
|
|
17
|
+
declare function changedSymbolNames(baseContent: string | null, headContent: string | null, path: string): {
|
|
18
|
+
names: Set<string>;
|
|
19
|
+
addedLines: Set<number>;
|
|
20
|
+
};
|
|
21
|
+
interface CommentMention {
|
|
22
|
+
/** File containing the comment. */
|
|
23
|
+
file: string;
|
|
24
|
+
/** 1-based line of the comment. */
|
|
25
|
+
line: number;
|
|
26
|
+
/** The symbol token it mentioned. */
|
|
27
|
+
symbol: string;
|
|
28
|
+
}
|
|
29
|
+
/** Find comments in `content` (lines NOT in `addedLines`) that mention any of `symbols` as a token. */
|
|
30
|
+
declare function findStaleCommentMentions(content: string, path: string, symbols: Set<string>, addedLines: Set<number>): CommentMention[];
|
|
31
|
+
export declare const commentCodeDrift: Analyzer;
|
|
32
|
+
export declare const __test__: {
|
|
33
|
+
collectDeclarations: typeof collectDeclarations;
|
|
34
|
+
changedSymbolNames: typeof changedSymbolNames;
|
|
35
|
+
findStaleCommentMentions: typeof findStaleCommentMentions;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { isGeneratedPath } from '../paths.js';
|
|
3
|
+
/**
|
|
4
|
+
* `comment-code-drift` — flags a comment/docstring that references a symbol whose **definition changed**
|
|
5
|
+
* in the diff, where the comment itself wasn't freshly added. Completes SPEC §5a (the last "later"
|
|
6
|
+
* analyzer). Catches the classic agent failure: it edits a function's behavior or renames it, but leaves
|
|
7
|
+
* the docstring describing the old behavior/name.
|
|
8
|
+
*
|
|
9
|
+
* The verdict is load-bearing, same as `risky-diff-no-test`: "does this comment describe stale code?" is
|
|
10
|
+
* a *semantic* judgement, and SPEC §3.4 forbids semantic CONTRADICTED. So this analyzer is
|
|
11
|
+
* **UNVERIFIED-only, never CONTRADICTED** — a signal, never a gate. Safe under `--hard-fail` by
|
|
12
|
+
* construction.
|
|
13
|
+
*
|
|
14
|
+
* Trigger (all must hold):
|
|
15
|
+
* 1. A source file in the diff has a CHANGED declaration for identifier `X` (body edit, rename, or
|
|
16
|
+
* removal — gathered from base+head ASTs).
|
|
17
|
+
* 2. A comment/docstring (same file OR another changed file) mentions `X` as a whole token.
|
|
18
|
+
* 3. The comment line was NOT added in this diff (a freshly-added comment is unlikely stale — this is
|
|
19
|
+
* the key false-positive cut).
|
|
20
|
+
*
|
|
21
|
+
* Cross-file: same-file + other changed files (per design). Generated paths and test files are excluded
|
|
22
|
+
* from the "changed symbol" source set (a test mentioning a renamed util is usually fine; a bundle's
|
|
23
|
+
* comments aren't authored).
|
|
24
|
+
*/
|
|
25
|
+
const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/i;
|
|
26
|
+
const TEST_FILE_RE = /(^|[./\\])(test|spec)\.[cm]?[jt]sx?$/i;
|
|
27
|
+
const TEST_DIR_RE = /(^|[/\\])(__tests__|tests?)[/\\]/i;
|
|
28
|
+
function isTestPath(path) {
|
|
29
|
+
return TEST_FILE_RE.test(path) || (TEST_DIR_RE.test(path) && SOURCE_EXT_RE.test(path));
|
|
30
|
+
}
|
|
31
|
+
function scriptKindFor(path) {
|
|
32
|
+
if (/\.tsx$/i.test(path))
|
|
33
|
+
return ts.ScriptKind.TSX;
|
|
34
|
+
if (/\.jsx$/i.test(path))
|
|
35
|
+
return ts.ScriptKind.JSX;
|
|
36
|
+
if (/\.[cm]?ts$/i.test(path))
|
|
37
|
+
return ts.ScriptKind.TS;
|
|
38
|
+
return ts.ScriptKind.JS;
|
|
39
|
+
}
|
|
40
|
+
/** Collect top-level + class-member declaration names with their declaration line. */
|
|
41
|
+
function collectDeclarations(content, path) {
|
|
42
|
+
const sf = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, scriptKindFor(path));
|
|
43
|
+
const out = [];
|
|
44
|
+
const visit = (node) => {
|
|
45
|
+
const name = declName(node);
|
|
46
|
+
if (name) {
|
|
47
|
+
const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
48
|
+
out.push({ name, line });
|
|
49
|
+
}
|
|
50
|
+
// Don't descend into function bodies (locals aren't "documented" at module/class level), but DO
|
|
51
|
+
// descend into class bodies to catch method renames.
|
|
52
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
ts.forEachChild(node, visit);
|
|
56
|
+
};
|
|
57
|
+
visit(sf);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function declName(node) {
|
|
61
|
+
// Named declarations we care about. NOTE: we handle VariableDeclaration (not VariableStatement) so a
|
|
62
|
+
// `const x` is reported once — visit descends from the statement into its declarators.
|
|
63
|
+
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
64
|
+
return node.name?.text ?? null;
|
|
65
|
+
}
|
|
66
|
+
if (ts.isVariableDeclaration(node)) {
|
|
67
|
+
return node.name && ts.isIdentifier(node.name) ? node.name.text : null;
|
|
68
|
+
}
|
|
69
|
+
if (ts.isInterfaceDeclaration(node) ||
|
|
70
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
71
|
+
ts.isEnumDeclaration(node)) {
|
|
72
|
+
return node.name.text;
|
|
73
|
+
}
|
|
74
|
+
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertyDeclaration(node)) {
|
|
75
|
+
return ts.isIdentifier(node.name) ? node.name.text : null;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Identify identifiers whose declaration changed in the diff for one file. A declaration "changed" if:
|
|
81
|
+
* - its line in base differs from head (body edit / moved), OR
|
|
82
|
+
* - it exists in base but not head (removed/renamed).
|
|
83
|
+
* Returns the set of changed names plus the file's added-line set (for the "comment not freshly added"
|
|
84
|
+
* exclusion).
|
|
85
|
+
*/
|
|
86
|
+
function changedSymbolNames(baseContent, headContent, path) {
|
|
87
|
+
const names = new Set();
|
|
88
|
+
const addedLines = new Set();
|
|
89
|
+
if (baseContent == null && headContent == null)
|
|
90
|
+
return { names, addedLines };
|
|
91
|
+
// Determine added/removed lines by a cheap line-set diff (the ChangedFile.addedLines only has ADDS;
|
|
92
|
+
// we also need REMOVED base lines to spot a renamed old name).
|
|
93
|
+
const baseLines = baseContent ? new Set(baseContent.split('\n')) : new Set();
|
|
94
|
+
const headLines = headContent ? headContent.split('\n') : [];
|
|
95
|
+
if (headContent) {
|
|
96
|
+
headLines.forEach((l, i) => {
|
|
97
|
+
if (!baseLines.has(l))
|
|
98
|
+
addedLines.add(i + 1);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Removed = base lines absent from head.
|
|
102
|
+
const removedLines = new Set();
|
|
103
|
+
const headLineSet = new Set(headLines);
|
|
104
|
+
if (baseContent) {
|
|
105
|
+
baseContent.split('\n').forEach((l, i) => {
|
|
106
|
+
if (!headLineSet.has(l))
|
|
107
|
+
removedLines.add(i + 1);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const baseDecls = baseContent ? collectDeclarations(baseContent, path) : [];
|
|
111
|
+
const headDecls = headContent ? collectDeclarations(headContent, path) : [];
|
|
112
|
+
const headByName = new Map(headDecls.map((d) => [d.name, d.line]));
|
|
113
|
+
// 1) base declarations removed from head (rename/removal) → old name is "changed".
|
|
114
|
+
for (const d of baseDecls) {
|
|
115
|
+
if (!headByName.has(d.name))
|
|
116
|
+
names.add(d.name);
|
|
117
|
+
}
|
|
118
|
+
// 2) declarations present in both whose declaration line is on a removed line → body edit or moved.
|
|
119
|
+
for (const d of headDecls) {
|
|
120
|
+
const was = baseDecls.find((b) => b.name === d.name);
|
|
121
|
+
if (was && (removedLines.has(was.line) || addedLines.has(d.line))) {
|
|
122
|
+
names.add(d.name);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { names, addedLines };
|
|
126
|
+
}
|
|
127
|
+
/** Find comments in `content` (lines NOT in `addedLines`) that mention any of `symbols` as a token. */
|
|
128
|
+
function findStaleCommentMentions(content, path, symbols, addedLines) {
|
|
129
|
+
if (symbols.size === 0 || !content)
|
|
130
|
+
return [];
|
|
131
|
+
const sf = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, scriptKindFor(path));
|
|
132
|
+
const mentions = [];
|
|
133
|
+
const symbolSet = new Set([...symbols].filter((s) => s && s.length >= 2 && !COMMON_WORDS.has(s.toLowerCase())));
|
|
134
|
+
if (symbolSet.size === 0)
|
|
135
|
+
return [];
|
|
136
|
+
const isComment = (kind) => kind === ts.SyntaxKind.SingleLineCommentTrivia ||
|
|
137
|
+
kind === ts.SyntaxKind.MultiLineCommentTrivia;
|
|
138
|
+
// `forEachLeadingCommentRange` runs per-node, so the same comment is reported once per following
|
|
139
|
+
// node. Track positions we've already inspected to emit each comment at most once.
|
|
140
|
+
const seenCommentPos = new Set();
|
|
141
|
+
const fullText = sf.text;
|
|
142
|
+
const visit = (node) => {
|
|
143
|
+
const start = node.getFullStart();
|
|
144
|
+
ts.forEachLeadingCommentRange(fullText, start, (pos, _end, kind) => {
|
|
145
|
+
if (!isComment(kind) || seenCommentPos.has(pos))
|
|
146
|
+
return;
|
|
147
|
+
seenCommentPos.add(pos);
|
|
148
|
+
const line = sf.getLineAndCharacterOfPosition(pos).line + 1;
|
|
149
|
+
if (addedLines.has(line))
|
|
150
|
+
return; // freshly-added comment → unlikely stale
|
|
151
|
+
const text = fullText.slice(pos, _end);
|
|
152
|
+
const toks = new Set(text.toLowerCase().split(/[^a-z0-9_$]+/i).filter(Boolean));
|
|
153
|
+
for (const sym of symbolSet) {
|
|
154
|
+
if (toks.has(sym) || toks.has(sym.toLowerCase())) {
|
|
155
|
+
mentions.push({ file: path, line, symbol: sym });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
ts.forEachChild(node, visit);
|
|
160
|
+
};
|
|
161
|
+
visit(sf);
|
|
162
|
+
return mentions;
|
|
163
|
+
}
|
|
164
|
+
// Words too common to treat as a "symbol reference" even if they happen to be a declaration name.
|
|
165
|
+
const COMMON_WORDS = new Set([
|
|
166
|
+
'data', 'value', 'item', 'result', 'error', 'options', 'config', 'props', 'state', 'type',
|
|
167
|
+
]);
|
|
168
|
+
export const commentCodeDrift = {
|
|
169
|
+
id: 'comment-code-drift',
|
|
170
|
+
title: 'Comment may describe changed code',
|
|
171
|
+
description: 'Signals a comment/docstring that references a symbol whose definition changed in the diff (body ' +
|
|
172
|
+
'edit, rename, or removal), where the comment itself was not freshly added. Never blocks: comment ' +
|
|
173
|
+
'staleness is semantic, so it emits UNVERIFIED only.',
|
|
174
|
+
kind: 'claim-independent',
|
|
175
|
+
run(ctx) {
|
|
176
|
+
// No base ref → can't compare; degrade to silence (no false signal).
|
|
177
|
+
if (!ctx.base || !ctx.readBaseFile)
|
|
178
|
+
return [];
|
|
179
|
+
// 1) Gather changed symbols per changed source file (excluding tests/generated).
|
|
180
|
+
const changedPerFile = new Map();
|
|
181
|
+
for (const f of ctx.changedFiles) {
|
|
182
|
+
if (f.status === 'deleted' || isTestPath(f.path) || !SOURCE_EXT_RE.test(f.path))
|
|
183
|
+
continue;
|
|
184
|
+
if (isGeneratedPath(f.path))
|
|
185
|
+
continue;
|
|
186
|
+
const baseContent = ctx.readBaseFile(f.path);
|
|
187
|
+
const headContent = ctx.readFile(f.path);
|
|
188
|
+
if (baseContent == null && headContent == null)
|
|
189
|
+
continue;
|
|
190
|
+
const info = changedSymbolNames(baseContent, headContent, f.path);
|
|
191
|
+
if (info.names.size > 0)
|
|
192
|
+
changedPerFile.set(f.path, info);
|
|
193
|
+
}
|
|
194
|
+
if (changedPerFile.size === 0)
|
|
195
|
+
return [];
|
|
196
|
+
// Union of all changed symbol names across changed files (for cross-file comment scan).
|
|
197
|
+
const allChangedNames = new Set();
|
|
198
|
+
for (const info of changedPerFile.values())
|
|
199
|
+
for (const n of info.names)
|
|
200
|
+
allChangedNames.add(n);
|
|
201
|
+
if (allChangedNames.size === 0)
|
|
202
|
+
return [];
|
|
203
|
+
// 2) Scan comments in each changed file (same-file + cross-file within the changed set).
|
|
204
|
+
const findings = [];
|
|
205
|
+
for (const f of ctx.changedFiles) {
|
|
206
|
+
if (f.status === 'deleted')
|
|
207
|
+
continue;
|
|
208
|
+
// Comments in test files still count (a test docstring can drift too), but generated paths don't.
|
|
209
|
+
if (!SOURCE_EXT_RE.test(f.path) || isGeneratedPath(f.path))
|
|
210
|
+
continue;
|
|
211
|
+
const content = ctx.readFile(f.path);
|
|
212
|
+
if (content == null)
|
|
213
|
+
continue;
|
|
214
|
+
const mentions = findStaleCommentMentions(content, f.path, allChangedNames, f.addedLines);
|
|
215
|
+
for (const m of mentions) {
|
|
216
|
+
findings.push({
|
|
217
|
+
analyzer: 'comment-code-drift',
|
|
218
|
+
verdict: 'UNVERIFIED',
|
|
219
|
+
file: m.file,
|
|
220
|
+
line: m.line,
|
|
221
|
+
title: 'Comment may describe code that changed',
|
|
222
|
+
detail: `A comment here mentions '${m.symbol}', whose definition changed in this PR. The comment ` +
|
|
223
|
+
`may now be stale. 'Stale' is a heuristic, so this never blocks — verify the wording is ` +
|
|
224
|
+
`still accurate.`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Dedupe by (file, line, symbol).
|
|
229
|
+
const seen = new Set();
|
|
230
|
+
return findings.filter((f) => {
|
|
231
|
+
const key = `${f.file}:${f.line}:${(f.detail.match(/'([^']+)'/) ?? ['', ''])[1]}`;
|
|
232
|
+
if (seen.has(key))
|
|
233
|
+
return false;
|
|
234
|
+
seen.add(key);
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
// Exposed for unit tests.
|
|
240
|
+
export const __test__ = { collectDeclarations, changedSymbolNames, findStaleCommentMentions };
|
|
241
|
+
//# sourceMappingURL=commentCodeDrift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commentCodeDrift.js","sourceRoot":"","sources":["../../src/analyzers/commentCodeDrift.ts"],"names":[],"mappings":"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"}
|