@integrity-labs/agt-cli 0.28.836 → 0.28.838
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/assets/review/post-review-findings.mjs +154 -5
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-E6B43IJZ.js → chunk-MZNTGZA7.js} +32 -3
- package/dist/{chunk-E6B43IJZ.js.map → chunk-MZNTGZA7.js.map} +1 -1
- package/dist/lib/manager-worker.js +2 -2
- package/dist/mcp/direct-chat-channel.js +148 -119
- package/dist/mcp/index.js +196 -120
- package/package.json +1 -1
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* That copy is now DOWNSTREAM of this one. Do NOT add an equality sync test:
|
|
17
17
|
* this copy is expected to diverge, and already has — `refuseIdentity()` below
|
|
18
18
|
* makes a write without the VERIFIED review-bot identity impossible here, while
|
|
19
|
-
* the plugin copy stays permissive for the human who runs it.
|
|
20
|
-
*
|
|
19
|
+
* the plugin copy stays permissive for the human who runs it. `--min-severity`
|
|
20
|
+
* (below) is the second such divergence, and for the same reason: a human reads
|
|
21
|
+
* every finding and decides; an unattended agent posts what it is handed.
|
|
21
22
|
*/
|
|
22
23
|
/**
|
|
23
24
|
* post-review-findings — deliver `/s:code-review` and `/s:security-review`
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
* post-review-findings.mjs --pr 5409 --post
|
|
49
50
|
* post-review-findings.mjs --pr 5409 --status
|
|
50
51
|
* post-review-findings.mjs --pr 5409 --resolve <id> --reason "..."
|
|
52
|
+
* post-review-findings.mjs --pr 5409 --post --min-severity major --min-severity P2
|
|
51
53
|
*/
|
|
52
54
|
|
|
53
55
|
import { execFileSync } from 'node:child_process';
|
|
@@ -91,7 +93,10 @@ export function marker(id) {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export function buildBody(f, id) {
|
|
94
|
-
|
|
96
|
+
// NOT `?? 'minor'`. That printed a severity nobody assigned, and once a floor
|
|
97
|
+
// exists it would also make an unlabelled finding filterable — a silent drop
|
|
98
|
+
// caused entirely by the default. Unrated says what is true.
|
|
99
|
+
const sev = f.severity ? String(f.severity).toUpperCase() : 'UNRATED';
|
|
95
100
|
const cat = f.category ? ` · ${f.category}` : '';
|
|
96
101
|
return [
|
|
97
102
|
`**${sev}${cat}** — ${f.summary}`,
|
|
@@ -104,6 +109,132 @@ export function buildBody(f, id) {
|
|
|
104
109
|
].join('\n');
|
|
105
110
|
}
|
|
106
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The severity floor, and why it does NOT merge the two scales into one order.
|
|
114
|
+
*
|
|
115
|
+
* The two producers declare two different vocabularies and neither defines what
|
|
116
|
+
* its levels mean:
|
|
117
|
+
* `/s:code-review` — "severity": "major|minor" (commands/code-review.md)
|
|
118
|
+
* `/s:security-review` — "severity": "P1|P2|P3" (commands/security-review.md)
|
|
119
|
+
*
|
|
120
|
+
* Measured over the 54 artefacts in `.smithers/review` on 2026-09-07 (39
|
|
121
|
+
* findings): the code lane emitted major x10 / minor x21, the security lane
|
|
122
|
+
* emitted P2 x1 / P3 x5, and P0 appears nowhere — in the artefacts, in either
|
|
123
|
+
* command, or in this tree. The lanes do not share a token.
|
|
124
|
+
*
|
|
125
|
+
* So a single merged rank would need a crosswalk (is `major` a P1 or a P2?)
|
|
126
|
+
* that NOTHING in this repo states. Inventing one is not a tidy-up, it decides
|
|
127
|
+
* something: place `major` above P2 and `--min-severity major` mutes 100% of
|
|
128
|
+
* the security lane's observed output — every finding it has ever produced —
|
|
129
|
+
* while reading like an ordinary noise filter. That is a suppression nobody
|
|
130
|
+
* chose, invisible on the wire, which is the failure this reviewer exists to
|
|
131
|
+
* find in other people's code.
|
|
132
|
+
*
|
|
133
|
+
* Comparison is therefore SCALE-LOCAL. A floor filters only findings written in
|
|
134
|
+
* its own vocabulary; anything on the other scale, or on no scale, is posted
|
|
135
|
+
* and reported. Concretely: `--min-severity major` drops `minor` and leaves
|
|
136
|
+
* every P-scale finding standing; `--min-severity P2` drops `P3` and leaves
|
|
137
|
+
* every code finding standing. If the producers ever agree a crosswalk, it
|
|
138
|
+
* belongs here as one table with that agreement cited — not guessed from the
|
|
139
|
+
* shape of the words.
|
|
140
|
+
*
|
|
141
|
+
* A consequence, and the reason `--min-severity` REPEATS: one value can never
|
|
142
|
+
* floor both lanes. "Post only the serious ones" against a two-lane producer is
|
|
143
|
+
* two floors — `--min-severity major --min-severity P2` — so the flag collects
|
|
144
|
+
* rather than overwrites. Repeating it for the SAME scale is refused instead of
|
|
145
|
+
* last-winning, on the rule this file already applies to `--status --resolve`:
|
|
146
|
+
* a flag that is accepted and then ignored is indistinguishable from one that
|
|
147
|
+
* was honoured.
|
|
148
|
+
*/
|
|
149
|
+
export const SEVERITY_SCALES = {
|
|
150
|
+
code: { major: 2, minor: 1 },
|
|
151
|
+
security: { p1: 3, p2: 2, p3: 1 },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** `{ scale, rank }` for a severity token, or null when it is on no known scale. */
|
|
155
|
+
export function severityRank(value) {
|
|
156
|
+
if (value === undefined || value === null) return null;
|
|
157
|
+
const k = String(value).trim().toLowerCase();
|
|
158
|
+
for (const [scale, table] of Object.entries(SEVERITY_SCALES)) {
|
|
159
|
+
if (Object.hasOwn(table, k)) return { scale, rank: table[k] };
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Resolve the `--min-severity` argument, or throw.
|
|
166
|
+
*
|
|
167
|
+
* Fail-CLOSED here, unlike the fail-open treatment of an unknown finding
|
|
168
|
+
* severity below, because the two unknowns fail in opposite directions. An
|
|
169
|
+
* unrecognised FLOOR would compare against nothing and silently filter nothing
|
|
170
|
+
* — a run that reads exactly like a floor that was honoured and had no work to
|
|
171
|
+
* do. There is no output that distinguishes them, so it is refused instead.
|
|
172
|
+
*/
|
|
173
|
+
export function parseMinSeverity(value) {
|
|
174
|
+
const r = severityRank(value);
|
|
175
|
+
if (r) return { ...r, token: String(value).trim() };
|
|
176
|
+
const known = Object.values(SEVERITY_SCALES).flatMap((t) => Object.keys(t)).join(', ');
|
|
177
|
+
throw new Error(
|
|
178
|
+
`--min-severity "${value}" is not a severity this reviewer knows. Use one of: ${known} ` +
|
|
179
|
+
'(case-insensitive). Floors are compared within one scale only — see SEVERITY_SCALES.',
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Resolve every `--min-severity` into one floor per scale, or throw. */
|
|
184
|
+
export function parseMinSeverityList(values) {
|
|
185
|
+
const floors = {};
|
|
186
|
+
for (const v of values) {
|
|
187
|
+
const f = parseMinSeverity(v);
|
|
188
|
+
if (floors[f.scale]) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`--min-severity given twice for the ${f.scale} scale ("${floors[f.scale].token}" and "${f.token}"). ` +
|
|
191
|
+
'Only one can apply, and silently keeping the last is indistinguishable from honouring both. ' +
|
|
192
|
+
'Pass one floor per scale.',
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
floors[f.scale] = f;
|
|
196
|
+
}
|
|
197
|
+
return floors;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Split findings into those that clear the floor and those it withholds.
|
|
202
|
+
*
|
|
203
|
+
* Everything the floor cannot compare is KEPT, each carrying `_kept` saying why.
|
|
204
|
+
* Fail-open is the only safe direction: a finding that posts when it need not is
|
|
205
|
+
* noise a reader can skip, while one withheld by a comparison that never
|
|
206
|
+
* happened is invisible to everyone, including the agent that produced it.
|
|
207
|
+
*
|
|
208
|
+
* `blocking: true` also bypasses the floor. All four findings carrying that
|
|
209
|
+
* field in the measured set are `false`, so this rule has never yet fired — it
|
|
210
|
+
* is here because the alternative is a reviewer withholding a finding its own
|
|
211
|
+
* producer marked as blocking, and the cost of the rule while it lies dormant
|
|
212
|
+
* is nil.
|
|
213
|
+
*/
|
|
214
|
+
export function applySeverityFloor(findings, floors) {
|
|
215
|
+
if (!floors || Object.keys(floors).length === 0) return { kept: findings, suppressed: [] };
|
|
216
|
+
const kept = [];
|
|
217
|
+
const suppressed = [];
|
|
218
|
+
for (const f of findings) {
|
|
219
|
+
if (f.blocking === true) {
|
|
220
|
+
kept.push({ ...f, _kept: 'marked blocking' });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const r = severityRank(f.severity);
|
|
224
|
+
const floor = r ? floors[r.scale] : undefined;
|
|
225
|
+
if (!r) {
|
|
226
|
+
kept.push({ ...f, _kept: f.severity ? `severity "${f.severity}" is on no known scale` : 'no severity' });
|
|
227
|
+
} else if (!floor) {
|
|
228
|
+
kept.push({ ...f, _kept: `no floor given for the ${r.scale} scale` });
|
|
229
|
+
} else if (r.rank >= floor.rank) {
|
|
230
|
+
kept.push(f);
|
|
231
|
+
} else {
|
|
232
|
+
suppressed.push(f);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { kept, suppressed };
|
|
236
|
+
}
|
|
237
|
+
|
|
107
238
|
/** Thin `gh` wrapper. `input` is piped to stdin, for endpoints taking a body. */
|
|
108
239
|
let AUTH_TOKEN = null;
|
|
109
240
|
|
|
@@ -383,6 +514,9 @@ export function parseArgs(argv) {
|
|
|
383
514
|
else if (k === '--dir') a.dir = argv[++i];
|
|
384
515
|
else if (k === '--resolve') a.resolve = argv[++i];
|
|
385
516
|
else if (k === '--reason') a.reason = argv[++i];
|
|
517
|
+
// Collects, rather than overwrites — see SEVERITY_SCALES: one value cannot
|
|
518
|
+
// floor both lanes, so repeating the flag is the intended way to floor both.
|
|
519
|
+
else if (k === '--min-severity') (a.minSeverity ??= []).push(argv[++i]);
|
|
386
520
|
}
|
|
387
521
|
return a;
|
|
388
522
|
}
|
|
@@ -460,6 +594,9 @@ export function refuseIdentity({ kind, willWrite }) {
|
|
|
460
594
|
async function main() {
|
|
461
595
|
const a = parseArgs(process.argv.slice(2));
|
|
462
596
|
if (!a.pr) throw new Error('--pr is required');
|
|
597
|
+
// Before the identity hop and before the PR is read: an unusable floor should
|
|
598
|
+
// cost one line of output, not a scan that then cannot be delivered as asked.
|
|
599
|
+
const floors = a.minSeverity === undefined ? null : parseMinSeverityList(a.minSeverity);
|
|
463
600
|
const repo = a.repo ?? gh(['repo', 'view', '--json', 'nameWithOwner'], {}).nameWithOwner;
|
|
464
601
|
const dir = a.dir ?? REVIEW_DIR;
|
|
465
602
|
|
|
@@ -520,7 +657,8 @@ async function main() {
|
|
|
520
657
|
}
|
|
521
658
|
// A merged or closed PR takes comments happily and shows them to nobody.
|
|
522
659
|
const head = view.headRefOid;
|
|
523
|
-
const
|
|
660
|
+
const loaded = loadFindings(dir, a.pr, head);
|
|
661
|
+
const { kept: findings, suppressed } = applySeverityFloor(loaded, floors);
|
|
524
662
|
// --slurp (gh >= 2.44) is REQUIRED with --paginate here: without it gh emits
|
|
525
663
|
// one JSON array PER PAGE, and gh()'s JSON.parse of the concatenation throws
|
|
526
664
|
// `Unexpected token [`. That makes --post fail on exactly the large PRs it is
|
|
@@ -541,7 +679,18 @@ async function main() {
|
|
|
541
679
|
const newInline = inline.filter((f) => !already.has(findingId(f)));
|
|
542
680
|
const newOutside = outside.filter((f) => !already.has(findingId(f)));
|
|
543
681
|
|
|
544
|
-
console.log(`PR #${a.pr} @ ${head.slice(0, 9)} — ${
|
|
682
|
+
console.log(`PR #${a.pr} @ ${head.slice(0, 9)} — ${loaded.length} finding(s) loaded`);
|
|
683
|
+
if (floors) {
|
|
684
|
+
const unfiltered = findings.filter((f) => f._kept);
|
|
685
|
+
const label = Object.values(floors).map((f) => `${f.token} (${f.scale})`).join(', ');
|
|
686
|
+
console.log(
|
|
687
|
+
` severity floor: ${label} — ${suppressed.length} withheld, ${unfiltered.length} kept unfiltered`,
|
|
688
|
+
);
|
|
689
|
+
// A withheld finding exists nowhere else once this process exits, so it is
|
|
690
|
+
// named here rather than counted. A count alone cannot be checked.
|
|
691
|
+
for (const f of suppressed) console.log(` [withheld] ${f.severity} ${f.file} ${findingId(f)} ${f.summary.slice(0, 60)}`);
|
|
692
|
+
for (const f of unfiltered) console.log(` [kept] ${f.severity ?? '-'} ${f.file} ${findingId(f)} (${f._kept})`);
|
|
693
|
+
}
|
|
545
694
|
console.log(` inline-anchorable: ${inline.length} outside-diff: ${outside.length}`);
|
|
546
695
|
console.log(` already posted: ${inline.length + outside.length - newInline.length - newOutside.length}`);
|
|
547
696
|
console.log(` would post now: ${newInline.length} inline + ${newOutside.length} in the body\n`);
|
package/dist/bin/agt.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
success,
|
|
41
41
|
table,
|
|
42
42
|
warn
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-MZNTGZA7.js";
|
|
44
44
|
import {
|
|
45
45
|
getProjectDir,
|
|
46
46
|
isSessionResumeDisabled,
|
|
@@ -5467,7 +5467,7 @@ import { execFileSync, execSync } from "child_process";
|
|
|
5467
5467
|
import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
|
|
5468
5468
|
import chalk18 from "chalk";
|
|
5469
5469
|
import ora16 from "ora";
|
|
5470
|
-
var cliVersion = true ? "0.28.
|
|
5470
|
+
var cliVersion = true ? "0.28.838" : "dev";
|
|
5471
5471
|
async function fetchLatestVersion() {
|
|
5472
5472
|
const host2 = getHost();
|
|
5473
5473
|
if (!host2) return null;
|
|
@@ -6658,7 +6658,7 @@ function handleError(err) {
|
|
|
6658
6658
|
}
|
|
6659
6659
|
|
|
6660
6660
|
// src/bin/agt.ts
|
|
6661
|
-
var cliVersion2 = true ? "0.28.
|
|
6661
|
+
var cliVersion2 = true ? "0.28.838" : "dev";
|
|
6662
6662
|
var program = new Command();
|
|
6663
6663
|
program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
|
|
6664
6664
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
@@ -451,7 +451,7 @@ function orderTitlesForDescription(entries) {
|
|
|
451
451
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
|
|
452
452
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync4, chmodSync as chmodSync4, readdirSync, rmSync as rmSync2, copyFileSync, lstatSync, realpathSync, symlinkSync, readlinkSync, renameSync as renameSync4, opendirSync } from "fs";
|
|
453
453
|
import { join as join3, relative, dirname as dirname3 } from "path";
|
|
454
|
-
import { homedir as homedir3 } from "os";
|
|
454
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
455
455
|
import { execFile } from "child_process";
|
|
456
456
|
|
|
457
457
|
// ../../packages/core/dist/integrations/xurl-config.js
|
|
@@ -1864,6 +1864,34 @@ function sweepScratchDir(codeName, now = Date.now()) {
|
|
|
1864
1864
|
}
|
|
1865
1865
|
return removed;
|
|
1866
1866
|
}
|
|
1867
|
+
var STRANDED_ARTEFACT_TMP_PREFIXES = [
|
|
1868
|
+
"augmented-artefact-",
|
|
1869
|
+
"augmented-artefact-source-"
|
|
1870
|
+
];
|
|
1871
|
+
function sweepStrandedArtefactTmpDirs(now = Date.now()) {
|
|
1872
|
+
const root = tmpdir();
|
|
1873
|
+
let entries;
|
|
1874
|
+
try {
|
|
1875
|
+
entries = readdirSync(root);
|
|
1876
|
+
} catch {
|
|
1877
|
+
return 0;
|
|
1878
|
+
}
|
|
1879
|
+
const cutoff = now - SCRATCH_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
1880
|
+
let removed = 0;
|
|
1881
|
+
for (const entry of entries) {
|
|
1882
|
+
if (!STRANDED_ARTEFACT_TMP_PREFIXES.some((prefix) => entry.startsWith(prefix)))
|
|
1883
|
+
continue;
|
|
1884
|
+
const full = join3(root, entry);
|
|
1885
|
+
try {
|
|
1886
|
+
if (isFreshWithin(full, cutoff))
|
|
1887
|
+
continue;
|
|
1888
|
+
rmSync2(full, { recursive: true, force: true });
|
|
1889
|
+
removed += 1;
|
|
1890
|
+
} catch {
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return removed;
|
|
1894
|
+
}
|
|
1867
1895
|
var SCRATCH_SCAN_ENTRY_BUDGET = 5e3;
|
|
1868
1896
|
function isFreshWithin(path, cutoff) {
|
|
1869
1897
|
let budget = SCRATCH_SCAN_ENTRY_BUDGET;
|
|
@@ -2051,6 +2079,7 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
2051
2079
|
mkdirSync3(getScratchDir(codeName), { recursive: true });
|
|
2052
2080
|
mkdirSync3(getAgentTmpDir(codeName), { recursive: true });
|
|
2053
2081
|
sweepScratchDir(codeName);
|
|
2082
|
+
sweepStrandedArtefactTmpDirs();
|
|
2054
2083
|
} catch (err) {
|
|
2055
2084
|
process.stderr.write(`[scratch] [ensure-or-sweep-failed] agent=${codeName} error=${err.message}
|
|
2056
2085
|
`);
|
|
@@ -6537,7 +6566,7 @@ function exchangeFailureKind(err) {
|
|
|
6537
6566
|
}
|
|
6538
6567
|
|
|
6539
6568
|
// src/lib/api-client.ts
|
|
6540
|
-
var agtCliVersion = true ? "0.28.
|
|
6569
|
+
var agtCliVersion = true ? "0.28.838" : "dev";
|
|
6541
6570
|
var lastConfigHash = null;
|
|
6542
6571
|
function setConfigHash(hash) {
|
|
6543
6572
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -10895,4 +10924,4 @@ export {
|
|
|
10895
10924
|
managerInstallSystemUnitCommand,
|
|
10896
10925
|
managerUninstallSystemUnitCommand
|
|
10897
10926
|
};
|
|
10898
|
-
//# sourceMappingURL=chunk-
|
|
10927
|
+
//# sourceMappingURL=chunk-MZNTGZA7.js.map
|