@integrity-labs/agt-cli 0.28.891 → 0.28.892
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/bin/agt.js +5 -5
- package/dist/{chunk-BH5RFBUM.js → chunk-FZ6KIIYX.js} +55 -15
- package/dist/chunk-FZ6KIIYX.js.map +1 -0
- package/dist/{chunk-2IVLMJXQ.js → chunk-SUVQ2KSP.js} +4 -4
- package/dist/{chunk-SXYTFXPO.js → chunk-WMJBXN6N.js} +2 -2
- package/dist/{claude-pair-runtime-TCDKO2CZ.js → claude-pair-runtime-RTD7ZCVS.js} +2 -2
- package/dist/lib/manager-worker.js +14 -39
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +54 -14
- package/dist/mcp/index.js +54 -14
- package/dist/mcp/origami.js +54 -14
- package/dist/mcp/slack-channel.js +54 -14
- package/dist/mcp/telegram-channel.js +54 -14
- package/dist/{persistent-session-ZDZSEODJ.js → persistent-session-ARKZDYMX.js} +3 -3
- package/dist/{responsiveness-probe-7CY5VZMG.js → responsiveness-probe-X2WXPIUI.js} +3 -3
- package/dist/{session-auth-dead-65TG2DLW.js → session-auth-dead-BLEV4AUJ.js} +2 -2
- package/package.json +2 -2
- package/dist/assets/review/SKILL.md +0 -223
- package/dist/assets/review/SWEEP.md +0 -169
- package/dist/chunk-BH5RFBUM.js.map +0 -1
- /package/dist/{chunk-2IVLMJXQ.js.map → chunk-SUVQ2KSP.js.map} +0 -0
- /package/dist/{chunk-SXYTFXPO.js.map → chunk-WMJBXN6N.js.map} +0 -0
- /package/dist/{claude-pair-runtime-TCDKO2CZ.js.map → claude-pair-runtime-RTD7ZCVS.js.map} +0 -0
- /package/dist/{persistent-session-ZDZSEODJ.js.map → persistent-session-ARKZDYMX.js.map} +0 -0
- /package/dist/{responsiveness-probe-7CY5VZMG.js.map → responsiveness-probe-X2WXPIUI.js.map} +0 -0
- /package/dist/{session-auth-dead-65TG2DLW.js.map → session-auth-dead-BLEV4AUJ.js.map} +0 -0
|
@@ -35774,6 +35774,12 @@ var CURSOR_SHORTFALL_REASONS = [
|
|
|
35774
35774
|
var NON_SHORTFALL_KEYS = new Set(CURSOR_ADVANCE_NON_SHORTFALL_KEYS);
|
|
35775
35775
|
var KNOWN_REASONS = new Set(CURSOR_SHORTFALL_REASONS.filter((k) => !NON_SHORTFALL_KEYS.has(k)));
|
|
35776
35776
|
|
|
35777
|
+
// ../core/dist/direct-chat/agent-history-notices.js
|
|
35778
|
+
var AGENT_HISTORY_TURNS_ONLY_OR_FILTER = [
|
|
35779
|
+
"payload->>kind.is.null",
|
|
35780
|
+
`payload->>kind.in.("",${[...USER_FACING_NOTICE_KINDS].sort().join(",")})`
|
|
35781
|
+
].join(",");
|
|
35782
|
+
|
|
35777
35783
|
// ../core/dist/onboarding/state-machine.js
|
|
35778
35784
|
var AREA_ORDER = [
|
|
35779
35785
|
"framing",
|
|
@@ -38442,16 +38448,36 @@ var FLAG_REGISTRY = [
|
|
|
38442
38448
|
},
|
|
38443
38449
|
{
|
|
38444
38450
|
key: "ninjafy-brand",
|
|
38445
|
-
description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: there is now exactly ONE consumer \u2014 (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 evaluating in the active-org cookie scope, so a host- or agent-scoped row is accepted by the table but never reached by the console. It resolves the key ONCE per request and passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop. Resolution is SERVER-side deliberately: the client hook starts at its default and updates after an async GET /flags, so anything gated on it paints Augmented and then repaints. ENG-8858 moved the PALETTE for that reason (a full-page colour flash); ENG-9227 moved the WORDMARK, which had been the last surface still swapping after hydration. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because
|
|
38451
|
+
description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: there is now exactly ONE consumer \u2014 (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 evaluating in the active-org cookie scope, so a host- or agent-scoped row is accepted by the table but never reached by the console. It resolves the key ONCE per request and passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop. Resolution is SERVER-side deliberately: the client hook starts at its default and updates after an async GET /flags, so anything gated on it paints Augmented and then repaints. ENG-8858 moved the PALETTE for that reason (a full-page colour flash); ENG-9227 moved the WORDMARK, which had been the last surface still swapping after hydration. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because fetchPublicFlags returns null without a verified session and a pre-auth page has neither a session nor an active org \u2014 a PER-ORG flag structurally cannot brand one. ENG-9830 CORRECTS what used to be written here ("those surfaces follow only when the stage-wide default flips"): they do not follow it at all. Two reasons. The value returned on a null fetch is the CALL SITE\u2019s passed argument, not this registry default; and six such pages (/login, /reset-password, /auth/confirm, the pre-redeem member-signin gate and the two public Slack-install terminal pages) do not mount <BrandStamp/> AT ALL \u2014 deliberately, enumerated with per-page reasons in eng-9266-brand-stamp-coverage.test.ts. They become branded when the flag is REMOVED at cutover step 2 and the palette moves into bare :root, with no read left to gate it. NO LONGER DARK: ENG-9830 cutover step 1 flipped the default to true, so an org with no override now sees Ninjafy. The key survives only until cutover step 2 deletes it.',
|
|
38446
38452
|
flagType: "boolean",
|
|
38447
|
-
//
|
|
38448
|
-
//
|
|
38449
|
-
//
|
|
38450
|
-
// rebrand
|
|
38451
|
-
|
|
38453
|
+
// ENG-9830 (cutover step 1): flipped `false` -> `true`. The comment this
|
|
38454
|
+
// replaces argued `false` was the fail-closed direction — "never leak an
|
|
38455
|
+
// UNANNOUNCED rebrand to every customer at once". That was correct for as
|
|
38456
|
+
// long as the rebrand was unannounced. It is not any more: Ninjafy is the
|
|
38457
|
+
// live, announced brand (ENG-9613 reconciled the console onto the master
|
|
38458
|
+
// assets while the flag was already ON in production), so `false` now points
|
|
38459
|
+
// at the brand we no longer ship. A flags outage that reverted a customer to
|
|
38460
|
+
// Augmented would be the incident, not the safe harbour.
|
|
38461
|
+
//
|
|
38462
|
+
// The old rationale is recorded rather than deleted because it is a correct
|
|
38463
|
+
// argument whose PREMISE expired — reading the change without it looks like
|
|
38464
|
+
// someone overriding a deliberate fail-closed default.
|
|
38465
|
+
//
|
|
38466
|
+
// NOTE this value is only consulted by the API evaluator when building the
|
|
38467
|
+
// `GET /flags` map for an org with no override. It is NOT the fallback the
|
|
38468
|
+
// webapp uses on an outage: `getPublicBooleanFlagServer(key, defaultValue)`
|
|
38469
|
+
// returns its own PASSED argument when the fetch fails, so the call sites in
|
|
38470
|
+
// (dashboard)/layout.tsx and components/brand-stamp.tsx carry `true` too.
|
|
38471
|
+
// Flipping only one of the two leaves the cutover half-applied.
|
|
38472
|
+
defaultValue: true,
|
|
38452
38473
|
// ENG-9227: there are NO client readers of this key left. (dashboard)/
|
|
38453
38474
|
// layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
|
|
38454
38475
|
// passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
|
|
38476
|
+
// "ONCE" is about the chrome, not the key: components/brand-stamp.tsx is a
|
|
38477
|
+
// SECOND, independent server read of `ninjafy-brand` (CodeRabbit, #5385).
|
|
38478
|
+
// Both are listed in the note above because both carry an outage fallback
|
|
38479
|
+
// that has to move together — reading this paragraph alone would leave you
|
|
38480
|
+
// updating one of the two.
|
|
38455
38481
|
// ENG-8858 did that for the THEME; ENG-9227 finished the job for the
|
|
38456
38482
|
// WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
|
|
38457
38483
|
// "augmented.team" to "ninjafy" after hydration.
|
|
@@ -38473,24 +38499,38 @@ var FLAG_REGISTRY = [
|
|
|
38473
38499
|
},
|
|
38474
38500
|
{
|
|
38475
38501
|
key: "ninjafy-logo",
|
|
38476
|
-
description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It
|
|
38502
|
+
description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It existed so candidate logo treatments could be put in front of a human and compared in the running console, rather than judged from a static export. That comparison has happened: ENG-9830 cutover step 1 chose TWO-TONE, so that is now the default and an unset flag renders it. NOTE this reverses the property this description used to claim \u2014 an unset flag was a no-op while the default was lockup (what ENG-9298 shipped), and that is deliberately given up at cutover: the default MOVING is the change. The key survives only until cutover step 2 hardcodes the chosen treatment and deletes it. lockup = mark and wordmark together, both in the brand blue \u2014 what ENG-9298 shipped. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork); the chosen one. wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
|
|
38477
38503
|
flagType: "enum",
|
|
38478
38504
|
allowedValues: ["lockup", "two-tone", "wordmark"],
|
|
38479
|
-
//
|
|
38480
|
-
//
|
|
38481
|
-
//
|
|
38505
|
+
// ENG-9830 (cutover step 1): `lockup` -> `two-tone`. The treatments existed
|
|
38506
|
+
// so they could be compared in the running console rather than judged from a
|
|
38507
|
+
// static export (ENG-9365); that comparison has now happened and `two-tone`
|
|
38508
|
+
// — the mark in brand blue with the wordmark in the neutral text colour — is
|
|
38509
|
+
// the chosen treatment. It becomes the hardcoded one when this key is
|
|
38510
|
+
// removed at cutover step 2.
|
|
38511
|
+
//
|
|
38512
|
+
// The rule this value obeys is unchanged: this flag has no "off" direction,
|
|
38513
|
+
// so fail-safe means "the treatment currently in production", not the
|
|
38482
38514
|
// narrowest one. A flag-DB read error, an archived row, or a stored value
|
|
38483
38515
|
// outside allowedValues all resolve here (evaluate.ts normalizeFlagValue),
|
|
38484
|
-
// which is what makes an experimental value impossible to strand.
|
|
38485
|
-
|
|
38516
|
+
// which is what makes an experimental value impossible to strand. Only which
|
|
38517
|
+
// treatment holds that position has moved.
|
|
38518
|
+
//
|
|
38519
|
+
// Same split as ninjafy-brand above: this governs the API evaluation path
|
|
38520
|
+
// only. The webapp's outage fallback is the argument passed at the
|
|
38521
|
+
// (dashboard)/layout.tsx call site, which carries 'two-tone' to match.
|
|
38522
|
+
defaultValue: "two-tone",
|
|
38486
38523
|
// Same trap as ninjafy-brand, and it bites harder here because there are no
|
|
38487
38524
|
// client readers of this key AT ALL — it is server-resolved by construction.
|
|
38488
38525
|
// `public` does NOT mean "read by a browser"; it means "readable through the
|
|
38489
38526
|
// public map". getPublicEnumFlagServer resolves against the same GET /flags
|
|
38490
38527
|
// endpoint the browser uses, and that route filters to listPublicFlagKeys()
|
|
38491
38528
|
// (routes/flags.ts). Drop `public` and the key is simply absent from the map,
|
|
38492
|
-
// so the helper falls through to
|
|
38493
|
-
// the dropdown appearing to do nothing, with no error
|
|
38529
|
+
// so the helper falls through to the fallback passed at that call site and
|
|
38530
|
+
// returns it FOREVER — the dropdown appearing to do nothing, with no error
|
|
38531
|
+
// raised anywhere. Named as the call site rather than as a literal on
|
|
38532
|
+
// purpose: this comment said `lockup` for exactly as long as `lockup` was
|
|
38533
|
+
// the answer, and ENG-9830 flipping the winner is what made it a lie.
|
|
38494
38534
|
public: true
|
|
38495
38535
|
},
|
|
38496
38536
|
{
|
|
@@ -62,8 +62,8 @@ import {
|
|
|
62
62
|
writeDirectChatSessionState,
|
|
63
63
|
writeEgressAllowlist,
|
|
64
64
|
writePersistentClaudeWrapper
|
|
65
|
-
} from "./chunk-
|
|
66
|
-
import "./chunk-
|
|
65
|
+
} from "./chunk-WMJBXN6N.js";
|
|
66
|
+
import "./chunk-FZ6KIIYX.js";
|
|
67
67
|
import "./chunk-XWVM4KPK.js";
|
|
68
68
|
export {
|
|
69
69
|
CLAUDE_HOST_WIDE,
|
|
@@ -130,4 +130,4 @@ export {
|
|
|
130
130
|
writeEgressAllowlist,
|
|
131
131
|
writePersistentClaudeWrapper
|
|
132
132
|
};
|
|
133
|
-
//# sourceMappingURL=persistent-session-
|
|
133
|
+
//# sourceMappingURL=persistent-session-ARKZDYMX.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-WMJBXN6N.js";
|
|
4
|
+
import "./chunk-FZ6KIIYX.js";
|
|
5
5
|
import "./chunk-XWVM4KPK.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/responsiveness-probe.ts
|
|
@@ -764,4 +764,4 @@ export {
|
|
|
764
764
|
readAndResetSlackReplyBindingClassifications,
|
|
765
765
|
readAndResetSlackReplyTargetClassifications
|
|
766
766
|
};
|
|
767
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
767
|
+
//# sourceMappingURL=responsiveness-probe-X2WXPIUI.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionTranscriptDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FZ6KIIYX.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/session-auth-dead.ts
|
|
6
6
|
import { closeSync, openSync, readSync, readdirSync, statSync } from "fs";
|
|
@@ -203,4 +203,4 @@ export {
|
|
|
203
203
|
decideSessionAuthState,
|
|
204
204
|
probeSessionAuth
|
|
205
205
|
};
|
|
206
|
-
//# sourceMappingURL=session-auth-dead-
|
|
206
|
+
//# sourceMappingURL=session-auth-dead-BLEV4AUJ.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integrity-labs/agt-cli",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.892",
|
|
4
4
|
"description": "Augmented Team CLI — agent provisioning and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsup && npm run build:mcp-assets && npm run build:cli-assets",
|
|
26
26
|
"build:mcp-assets": "mkdir -p dist/mcp && cp ../../packages/mcp/dist/index.js dist/mcp/index.js && cp ../../packages/mcp/dist/slack-channel.js dist/mcp/slack-channel.js && cp ../../packages/mcp/dist/direct-chat-channel.js dist/mcp/direct-chat-channel.js && cp ../../packages/mcp/dist/telegram-channel.js dist/mcp/telegram-channel.js && cp ../../packages/mcp/dist/teams-channel.js dist/mcp/teams-channel.js && cp ../../packages/mcp/dist/whatsapp-channel.js dist/mcp/whatsapp-channel.js && cp ../../packages/mcp/dist/whatsapp-link.js dist/mcp/whatsapp-link.js && cp ../../packages/mcp/dist/remote-oauth-proxy.js dist/mcp/remote-oauth-proxy.js && cp ../../packages/mcp/dist/computer-use-proxy.js dist/mcp/computer-use-proxy.js && cp ../../packages/augmented-admin-mcp/dist/index.js dist/mcp/augmented-admin.js && cp ../../packages/augmented-support-mcp/dist/index.js dist/mcp/augmented-support.js && cp ../../packages/augmented-help-kb-mcp/dist/index.js dist/mcp/augmented-help-kb.js && cp ../../packages/origami-mcp-server/dist/index.js dist/mcp/origami.js && cp ../../packages/xero-mcp-server/dist/index.js dist/mcp/xero.js",
|
|
27
|
-
"build:cli-assets": "mkdir -p dist/assets && cp assets/impersonate-statusline.sh dist/assets/impersonate-statusline.sh && chmod +x dist/assets/impersonate-statusline.sh && mkdir -p dist/assets/review && cp assets/review/post-review-findings.mjs dist/assets/review/post-review-findings.mjs && cp assets/review/obiwan-auth.mjs dist/assets/review/obiwan-auth.mjs &&
|
|
27
|
+
"build:cli-assets": "mkdir -p dist/assets && cp assets/impersonate-statusline.sh dist/assets/impersonate-statusline.sh && chmod +x dist/assets/impersonate-statusline.sh && mkdir -p dist/assets/review && cp assets/review/post-review-findings.mjs dist/assets/review/post-review-findings.mjs && cp assets/review/obiwan-auth.mjs dist/assets/review/obiwan-auth.mjs && chmod +x dist/assets/review/post-review-findings.mjs",
|
|
28
28
|
"dev": "tsx watch src/bin/agt.ts",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"typecheck": "bash ../../scripts/typecheck-guard.sh tsc --noEmit",
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pr-review
|
|
3
|
-
description: Review a GitHub pull request's diff and write the findings to a JSON artefact. Use when asked to review a PR, or when a card asks you to review a repo's pull request. Produces the artefact only — it never posts to GitHub.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Reviewing a pull request
|
|
7
|
-
|
|
8
|
-
You are a Ninjafy agent (Ninjafy and Augmented Team are one platform, so you
|
|
9
|
-
answer to the old name too). This skill is how you review a pull request.
|
|
10
|
-
|
|
11
|
-
**You write a file. You post nothing.** Posting is a separate, gated step that
|
|
12
|
-
reads the file this skill produces. Producing the artefact and publishing it are
|
|
13
|
-
deliberately different actions, so a review can be made and read before anybody
|
|
14
|
-
decides it should appear on the PR.
|
|
15
|
-
|
|
16
|
-
You do **not** need a checkout. Everything below comes from the GitHub API via
|
|
17
|
-
`gh`, which is already on your PATH and already authenticated.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 1. Resolve the PR
|
|
22
|
-
|
|
23
|
-
You need the repository as `owner/name` and the PR number. If you were given
|
|
24
|
-
only a URL, read them off it.
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
gh api repos/OWNER/NAME/pulls/NUMBER
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
From the response take `number`, `title`, `state`, `head.sha` and `base.sha`.
|
|
31
|
-
|
|
32
|
-
- **`state` is not `open`** — say so and stop. Reviewing a closed chapter helps
|
|
33
|
-
nobody.
|
|
34
|
-
- **The call fails** — say so and stop. Do not review from memory of the repo.
|
|
35
|
-
|
|
36
|
-
`head.sha` is the commit you are reviewing. Every later step is pinned to it,
|
|
37
|
-
and it is part of the artefact's filename, because a review of one commit says
|
|
38
|
-
nothing about another.
|
|
39
|
-
|
|
40
|
-
## 2. Get the diff — from the API, not from a working tree
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
gh api repos/OWNER/NAME/pulls/NUMBER/files --paginate --slurp
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Each entry has `filename`, `status`, `patch` (a unified diff hunk) and the
|
|
47
|
-
change counts. Read the patches; that is the change.
|
|
48
|
-
|
|
49
|
-
**Use this endpoint and no other.** The step that posts findings anchors each
|
|
50
|
-
one to a line it computes from *these same patches* — the right-hand side of
|
|
51
|
-
each hunk, which is the only place GitHub accepts an inline comment. A line
|
|
52
|
-
number taken from anywhere else (a local `git diff`, a whole-file read, a
|
|
53
|
-
`compare` against a different base) can be perfectly correct about the code and
|
|
54
|
-
still unanchorable, and an unanchorable finding is quietly demoted out of the
|
|
55
|
-
place a reader would look for it.
|
|
56
|
-
|
|
57
|
-
Some entries have **no `patch`** — a binary file, or one too large for GitHub to
|
|
58
|
-
render. Do not guess at their contents. Either fetch the file if you need it, or
|
|
59
|
-
leave it unreviewed and say which files you skipped.
|
|
60
|
-
|
|
61
|
-
### Then check the PR has not moved under you
|
|
62
|
-
|
|
63
|
-
This endpoint returns the diff **as it is now**, not the diff at the `head.sha`
|
|
64
|
-
you read in step 1. If someone pushed while you were reading, you now hold one
|
|
65
|
-
commit's diff about to be filed under another commit's name.
|
|
66
|
-
|
|
67
|
-
So read the head again and compare:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
gh api repos/OWNER/NAME/pulls/NUMBER --jq .head.sha
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
- **Same as step 1** — carry on.
|
|
74
|
-
- **Different** — start again from step 1 against the new head. If it moves a
|
|
75
|
-
second time, stop and write an `incomplete` artefact saying the PR is moving
|
|
76
|
-
faster than you can read it.
|
|
77
|
-
|
|
78
|
-
Do not simply relabel what you already read with the newer sha. That is the
|
|
79
|
-
failure this check exists to prevent, not a way around it: the artefact would be
|
|
80
|
-
a review of code nobody can see any more, indistinguishable from a review of the
|
|
81
|
-
code that is there.
|
|
82
|
-
|
|
83
|
-
## 3. Load the repo's own rules
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
gh api repos/OWNER/NAME/contents/CLAUDE.md?ref=HEAD_SHA --jq .content
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
gh api repos/OWNER/NAME/contents/docs/reference/review-rules.md?ref=HEAD_SHA --jq .content
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Both are base64; decode them. **Issue both requests before you begin step 4 —
|
|
94
|
-
this is not optional.** Either may 404, which is fine and normal: most repos
|
|
95
|
-
have neither.
|
|
96
|
-
|
|
97
|
-
Record which happened, because "absent" and "never asked" are different facts
|
|
98
|
-
and the artefact cannot tell them apart on its own. `rulesFile` is the path when
|
|
99
|
-
the fetch returned content (at either ref), and `null` **only after you have
|
|
100
|
-
actually observed a 404 at both**. If you did not issue the request, the review is `incomplete` with that as
|
|
101
|
-
the reason — a review conducted without rules it never looked for must not be
|
|
102
|
-
filed as though the rules were absent.
|
|
103
|
-
|
|
104
|
-
Where `review-rules.md` exists it **outranks your priors**: a rule saying "this
|
|
105
|
-
is deliberate here, do not flag it" exists because a reviewer already raised
|
|
106
|
-
that exact thing and a human said no. Where it does not exist, review anyway and
|
|
107
|
-
say so in your summary — a repo without one gets a generic review, which is
|
|
108
|
-
worth having and worth labelling as such.
|
|
109
|
-
|
|
110
|
-
Pin both reads to `HEAD_SHA` first. Reading the default branch's rules against a
|
|
111
|
-
branch's code compares the change to something it was never written against.
|
|
112
|
-
|
|
113
|
-
**If `review-rules.md` 404s at `HEAD_SHA`, retry it at the default branch, and
|
|
114
|
-
record which ref supplied it.** A branch cut before the rules file landed — or
|
|
115
|
-
simply never merged forward — has no copy of it at its own head, and that is the
|
|
116
|
-
common case, not the rare one: measured 2026-09-08, **8 of the 12 newest open
|
|
117
|
-
PRs** 404 on it. Pinning alone therefore withholds the corpus from most reviews
|
|
118
|
-
precisely while it is newest. Rules describe the repo's current standards rather
|
|
119
|
-
than the branch's snapshot, so the fallback is sound; what would not be sound is
|
|
120
|
-
applying them without saying so. Set `rulesRef` to `"head"` or `"default"`
|
|
121
|
-
alongside `rulesFile`.
|
|
122
|
-
|
|
123
|
-
Do NOT pipe or redirect these commands. Use `--jq` on the `gh` call itself and
|
|
124
|
-
read the output directly. A reviewer runs unattended under a tool allowlist
|
|
125
|
-
narrow enough that it cannot post to GitHub, and `gh … | jq` or `gh … > file`
|
|
126
|
-
is a compound command that such an allowlist rejects — so composing a shell
|
|
127
|
-
pipeline costs you turns and buys nothing.
|
|
128
|
-
|
|
129
|
-
## 4. Review
|
|
130
|
-
|
|
131
|
-
Prioritise, in this order:
|
|
132
|
-
|
|
133
|
-
1. **Correctness.** Does it do what it claims? What input makes it wrong?
|
|
134
|
-
|
|
135
|
-
2. **Claims that outrun the code.** A comment, commit message or PR body
|
|
136
|
-
asserting a guard, a threshold or a behaviour the code does not implement.
|
|
137
|
-
These are worse than a missing guard, because they stop the next person
|
|
138
|
-
looking. Read what the change *says about itself*, and check it.
|
|
139
|
-
|
|
140
|
-
3. **Silent failure.** A path where something goes wrong and nothing reports it:
|
|
141
|
-
a swallowed error, a fallback that renders a failed measurement as a
|
|
142
|
-
plausible normal value, an empty result indistinguishable from "nothing to
|
|
143
|
-
report". Ask of every failure branch: *if this fired right now, how would
|
|
144
|
-
anyone know?*
|
|
145
|
-
|
|
146
|
-
4. **Tests that cannot fail.** A test that looks correct and is structurally
|
|
147
|
-
incapable of failing reports coverage it does not provide, and CI will never
|
|
148
|
-
reveal it, because CI only ever runs it green.
|
|
149
|
-
|
|
150
|
-
5. **Security visible in the diff** — a new unauthenticated path, a secret on a
|
|
151
|
-
command line or in an environment variable, an unparameterised query, a taint
|
|
152
|
-
flow from request input to a sink.
|
|
153
|
-
|
|
154
|
-
**Do not spend the review on formatting, naming, or suggesting more cases for
|
|
155
|
-
behaviour already covered.** A review that is 80% nits gets skimmed, and then
|
|
156
|
-
the one real finding in it gets skimmed too.
|
|
157
|
-
|
|
158
|
-
## 5. Say which of the three outcomes you reached
|
|
159
|
-
|
|
160
|
-
They are different states and reporting them identically is the failure this
|
|
161
|
-
skill exists to find in other people's code:
|
|
162
|
-
|
|
163
|
-
- You reviewed the diff and **found nothing**. A legitimate, useful result.
|
|
164
|
-
- You reviewed the diff and **found things**.
|
|
165
|
-
- You **could not finish** — the diff was too large to hold, a file had no
|
|
166
|
-
patch and you needed it, an API call failed. Say that. Never present a partial
|
|
167
|
-
read as a clean one.
|
|
168
|
-
|
|
169
|
-
## 6. Write the artefact
|
|
170
|
-
|
|
171
|
-
One file per PR head, under `.smithers/review/` relative to your working
|
|
172
|
-
directory (create it if absent):
|
|
173
|
-
|
|
174
|
-
```text
|
|
175
|
-
.smithers/review/<pr>-<first 12 chars of head sha>.code.json
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
```json
|
|
179
|
-
{
|
|
180
|
-
"pr": 1234,
|
|
181
|
-
"head": "abc123def456",
|
|
182
|
-
"reviewedAt": "2026-09-07T00:00:00Z",
|
|
183
|
-
"rulesFile": "docs/reference/review-rules.md",
|
|
184
|
-
"rulesRef": "head",
|
|
185
|
-
"status": "reviewed",
|
|
186
|
-
"findings": [
|
|
187
|
-
{
|
|
188
|
-
"file": "packages/api/src/routes/thing.ts",
|
|
189
|
-
"line": 42,
|
|
190
|
-
"severity": "major",
|
|
191
|
-
"category": "correctness",
|
|
192
|
-
"summary": "one sentence stating the defect",
|
|
193
|
-
"why": "the failing case, concretely: these inputs produce this wrong output"
|
|
194
|
-
}
|
|
195
|
-
]
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**`severity` is `major` or `minor`. Nothing else.** Not `high`, not `critical`,
|
|
200
|
-
not `P1`. The step that posts these filters by severity, and it compares within
|
|
201
|
-
a known vocabulary: a token it does not recognise is kept rather than dropped,
|
|
202
|
-
so an invented level does not vanish — but it does defeat the filter, silently,
|
|
203
|
-
while the filter carries on printing as though it were working. `P1`/`P2`/`P3`
|
|
204
|
-
belong to the separate security review and are not yours to emit here.
|
|
205
|
-
|
|
206
|
-
`category` is one of `correctness`, `claim`, `silent-failure`, `test`,
|
|
207
|
-
`security`.
|
|
208
|
-
|
|
209
|
-
**`status` is `reviewed` or `incomplete`.** `incomplete` takes a `reason`. An
|
|
210
|
-
empty `findings` array on an `incomplete` review must never be read as clean,
|
|
211
|
-
and the posting step refuses to post from one at all — which is the whole point
|
|
212
|
-
of the field. Use it rather than shrinking the review to fit.
|
|
213
|
-
|
|
214
|
-
`line` is a right-hand line number from the patches you read in step 2. Omit it
|
|
215
|
-
if the finding is about the change as a whole rather than one line.
|
|
216
|
-
|
|
217
|
-
## 7. Report, and stop
|
|
218
|
-
|
|
219
|
-
Print counts by severity and category, the artefact path, and whether a rules
|
|
220
|
-
file was found. Then **stop**.
|
|
221
|
-
|
|
222
|
-
Do not post the findings. Do not open a review. Do not comment on the PR. If you
|
|
223
|
-
were asked to review, you have done what was asked.
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pr-review-sweep
|
|
3
|
-
description: Find the pull requests in scope that have no review for their current head, and review them. Use when a recurring card asks you to sweep a repository's open PRs. Reviews via the pr-review skill; posts nothing.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Sweeping a repository's open pull requests
|
|
7
|
-
|
|
8
|
-
You are a Ninjafy agent (Ninjafy and Augmented Team are one platform, so you
|
|
9
|
-
answer to the old name too). This skill is the loop that decides **which** pull
|
|
10
|
-
requests get reviewed. The `pr-review` skill is what reviews one. Keep them
|
|
11
|
-
separate: this one chooses, that one reads.
|
|
12
|
-
|
|
13
|
-
**You post nothing, and neither does the review.** Every artefact this skill
|
|
14
|
-
touches is a file under your own working directory.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 1. Read your scope — and refuse without one
|
|
19
|
-
|
|
20
|
-
The card that woke you must name a scope. One of:
|
|
21
|
-
|
|
22
|
-
- `label:<name>` — open PRs carrying that label
|
|
23
|
-
- `author:<login>` — open PRs opened by that account
|
|
24
|
-
- `prs:<n>,<n>,<n>` — an explicit list
|
|
25
|
-
|
|
26
|
-
**If the card names no scope, review nothing.** Write the sweep summary
|
|
27
|
-
(section 5) with `"status": "refused"` and `"reason": "no_scope"`, say so, and
|
|
28
|
-
stop.
|
|
29
|
-
|
|
30
|
-
This is not caution, it is the point. An unscoped sweep is "review every open
|
|
31
|
-
PR", which at this repository's volume is the most expensive thing you could be
|
|
32
|
-
asked to do, and nobody would have asked for it — they would have forgotten to
|
|
33
|
-
say. A missing scope is a missing instruction, so the safe reading is *nothing*,
|
|
34
|
-
never *everything*.
|
|
35
|
-
|
|
36
|
-
You also need the repository as `owner/name`. If the card does not name one,
|
|
37
|
-
that is `"reason": "no_repo"` and the same refusal.
|
|
38
|
-
|
|
39
|
-
## 2. List the candidates
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
gh pr list --repo OWNER/NAME --state open --limit 100 \
|
|
43
|
-
--json number,headRefOid,title,isDraft,author
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Add `--label <name>` or `--author <login>` per the scope.
|
|
47
|
-
|
|
48
|
-
For a `prs:` scope, read each one with `gh api repos/OWNER/NAME/pulls/NUMBER`
|
|
49
|
-
instead. **That payload names the same things differently** — `head.sha`, not
|
|
50
|
-
`headRefOid`; `draft`, not `isDraft` — and it does not filter by state, so check
|
|
51
|
-
`state` is `open` yourself and skip anything else. A closed PR reached this way
|
|
52
|
-
would otherwise enter the sweep, which the `--state open` above rules out for
|
|
53
|
-
every other scope.
|
|
54
|
-
|
|
55
|
-
**Skip drafts.** A draft moves under you by design, so a review of one is
|
|
56
|
-
usually filed against a head nobody will ever see again.
|
|
57
|
-
|
|
58
|
-
The head sha is `headRefOid` from the list, or `head.sha` from the API.
|
|
59
|
-
Everything below keys on its first 12 characters.
|
|
60
|
-
|
|
61
|
-
## 3. Decide what to review — read the artefact, not just its name
|
|
62
|
-
|
|
63
|
-
For each candidate, the artefact is:
|
|
64
|
-
|
|
65
|
-
```text
|
|
66
|
-
.smithers/review/<pr>-<first 12 chars of head sha>.code.json
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**A file existing is not a review having happened.** Read it and branch on
|
|
70
|
-
`status`:
|
|
71
|
-
|
|
72
|
-
- **No file** → review it.
|
|
73
|
-
- **`"status": "reviewed"`** → skip. This head has been reviewed, whether it
|
|
74
|
-
found anything or not. Count it as `alreadyReviewed`.
|
|
75
|
-
- **`"status": "incomplete"`** → this is a review that could not finish — an API
|
|
76
|
-
call failed, the diff was unreadable, the PR moved twice. Retry it, **up to 3
|
|
77
|
-
attempts total**. Read its `attempts` field and **remember the number** — you
|
|
78
|
-
will need it in section 4, and it is about to be overwritten. An absent field
|
|
79
|
-
means 1. At 3 or more, skip it and count it as `abandoned`.
|
|
80
|
-
|
|
81
|
-
Treating `incomplete` as done is the failure this branch exists to prevent: one
|
|
82
|
-
transient API error would park that head forever, and the sweep would report a
|
|
83
|
-
clean pass over a PR nobody ever read. Treating it as never-attempted is the
|
|
84
|
-
opposite failure — an unreadable diff retried every five minutes for a week.
|
|
85
|
-
|
|
86
|
-
A force-push changes the head, so it changes the filename, so it gets a fresh
|
|
87
|
-
review with a fresh attempt count. You do not need to do anything about that.
|
|
88
|
-
|
|
89
|
-
## 4. Review, up to the cap
|
|
90
|
-
|
|
91
|
-
Review the candidates in ascending PR number, invoking the **`pr-review`** skill
|
|
92
|
-
for each. **Stop after 3 reviews in one sweep** unless the card names a
|
|
93
|
-
different `cap:<n>`. Count the rest as `skippedByCap`; the next tick takes them.
|
|
94
|
-
|
|
95
|
-
The cap is a cost guard, not a queue-management nicety. Each review is a full
|
|
96
|
-
read of a diff and costs real money against a shared subscription, and a sweep
|
|
97
|
-
that finds thirty unreviewed PRs is exactly when you least want to run thirty
|
|
98
|
-
reviews back to back.
|
|
99
|
-
|
|
100
|
-
**After a retry, record the attempt — from the number you remembered, not from
|
|
101
|
-
the file.** If the artefact `pr-review` just wrote is `incomplete`, set its
|
|
102
|
-
`attempts` to **one more than the value you read in section 3** (which was 1 if
|
|
103
|
-
the field was absent), then move on.
|
|
104
|
-
|
|
105
|
-
`pr-review` overwrites the artefact: same filename, fresh body, no `attempts`
|
|
106
|
-
field. So "increment what is in the file" reads 1 every time, the count never
|
|
107
|
-
reaches 3, and an unreadable diff is retried every tick forever — the cap looking
|
|
108
|
-
like it works while being unreachable. The remembered number is the only one
|
|
109
|
-
that carries.
|
|
110
|
-
|
|
111
|
-
### Do not read what the other reviewers said
|
|
112
|
-
|
|
113
|
-
Never read `/reviews`, `/comments`, `/issues/NUMBER/comments`, or any other
|
|
114
|
-
reviewer's findings, on any PR, at any point in this sweep.
|
|
115
|
-
|
|
116
|
-
Your review has to be worth comparing against another reviewer's on the same
|
|
117
|
-
commit, and a review that has read the other one is not evidence of anything.
|
|
118
|
-
This is easy to do by accident — the endpoints are one call away and they look
|
|
119
|
-
like context.
|
|
120
|
-
|
|
121
|
-
## 5. Write the sweep summary — this is how anyone knows you ran
|
|
122
|
-
|
|
123
|
-
Every sweep writes a summary, including a sweep that reviewed nothing and a
|
|
124
|
-
sweep that refused.
|
|
125
|
-
|
|
126
|
-
Append one line to `.smithers/review/sweep-log.jsonl`, and overwrite
|
|
127
|
-
`.smithers/review/sweep-latest.json` with the same object:
|
|
128
|
-
|
|
129
|
-
```json
|
|
130
|
-
{
|
|
131
|
-
"at": "2026-09-09T04:00:00Z",
|
|
132
|
-
"status": "swept",
|
|
133
|
-
"repo": "owner/name",
|
|
134
|
-
"scope": "label:obi-wan-review",
|
|
135
|
-
"cap": 3,
|
|
136
|
-
"open": 12,
|
|
137
|
-
"candidates": 4,
|
|
138
|
-
"alreadyReviewed": 2,
|
|
139
|
-
"reviewed": [{ "pr": 1234, "head": "abc123def456", "status": "reviewed", "findings": 2 }],
|
|
140
|
-
"abandoned": [],
|
|
141
|
-
"skippedByCap": 1,
|
|
142
|
-
"posted": 0
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
`status` is `swept` or `refused`; a `refused` summary carries `reason` and omits
|
|
147
|
-
the sweep counts — **but still carries `"posted": 0`**, which is not one of them.
|
|
148
|
-
It is the field the whole phase is checked on, and a summary that may drop it is
|
|
149
|
-
a guarantee with a hole exactly where a refusal happened.
|
|
150
|
-
|
|
151
|
-
**Write it even when the answer is boring, and especially then.** A reviewer
|
|
152
|
-
that has quietly stopped working — a credential gone, the skill missing, an
|
|
153
|
-
account at its limit — posts nothing and files nothing, which is precisely what
|
|
154
|
-
a working reviewer looks like on a quiet day. The difference between the two is
|
|
155
|
-
that one of them wrote this file. `"reviewed": []` with `"candidates": 0` is a
|
|
156
|
-
healthy sweep. **No summary at all is the alarm.**
|
|
157
|
-
|
|
158
|
-
`posted` is always `0`. It is in the summary because a number that is always
|
|
159
|
-
zero is checkable, and a promise in prose is not.
|
|
160
|
-
|
|
161
|
-
## 6. Report, and stop
|
|
162
|
-
|
|
163
|
-
Say which repo and scope you swept, how many PRs were in scope, how many you
|
|
164
|
-
reviewed, how many were already done, and anything you abandoned or capped.
|
|
165
|
-
Name the summary file.
|
|
166
|
-
|
|
167
|
-
Then stop. Do not post the findings. Do not open a review. Do not comment on any
|
|
168
|
-
PR. Reviews are published by a separate, gated step that reads these artefacts —
|
|
169
|
-
not by you, and not in this phase.
|