@olegkoval/agent-skills 1.66.0 → 1.67.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/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +44 -18
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/implementation.md +16 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/triage-logic.md +16 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/output-format.md +1 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/risk-paths.mjs +67 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +44 -18
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/implementation.md +16 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/triage-logic.md +16 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/output-format.md +1 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/risk-paths.mjs +67 -0
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
|
@@ -285,23 +285,49 @@ Then:
|
|
|
285
285
|
`LOCAL_BRANCH` is `main`, `master`, `develop`, `staging`, or `production`, stop
|
|
286
286
|
and say so.
|
|
287
287
|
|
|
288
|
-
**Depth:** explicit
|
|
289
|
-
|
|
290
|
-
`gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles,files,baseRefName,state,mergeCommit
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
288
|
+
**Depth:** record an explicit `scan`, `medium`, `deep` or `deps` request;
|
|
289
|
+
otherwise select a provisional depth from the per-file stats. In `pr` mode use
|
|
290
|
+
`gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles,files,baseRefName,state,mergeCommit`.
|
|
291
|
+
Keep `baseRefName` as `BASE_BRANCH`, `changedFiles` as `EXPECTED_FILES`, and
|
|
292
|
+
`mergeCommit.oid` as `MERGE_COMMIT` when merged. In `branch` mode use
|
|
293
|
+
`git diff --numstat <MERGE_BASE> HEAD`.
|
|
294
|
+
|
|
295
|
+
**Mandatory risk check (every depth, including explicit requests):** after
|
|
296
|
+
Track B has saved `pr.diff`, and before finalizing depth or starting Step 2,
|
|
297
|
+
run `scripts/risk-paths.mjs` with that diff and the exact changed paths.
|
|
298
|
+
Resolve the script beside the selected `SKILL.md`: in Codex use
|
|
299
|
+
`<directory containing selected SKILL.md>/scripts/risk-paths.mjs`; in Claude
|
|
300
|
+
use `${CLAUDE_PLUGIN_ROOT}/skills/lekker-review/scripts/risk-paths.mjs`.
|
|
301
|
+
Use a shell-free process API with separate arguments:
|
|
302
|
+
`execFileSync('node', [riskScript, '--diff', diffFile, '--', ...changedPaths])`.
|
|
303
|
+
Never interpolate PR-controlled paths into shell source. Alternatively pipe
|
|
304
|
+
`git diff --name-only -z <MERGE_BASE> HEAD` into
|
|
305
|
+
`node "<resolved riskScript>" --stdin0 --diff "<scratchpad>/pr.diff"`.
|
|
306
|
+
Do not continue on classifier failure; report risk classification unavailable.
|
|
307
|
+
|
|
308
|
+
The classifier detects configured path patterns for CI workflows, auth,
|
|
309
|
+
billing, installers/setup, and P21, plus `p21` in added lines. These are path
|
|
310
|
+
heuristics, not complete detection of auth/billing behavior in generic files;
|
|
311
|
+
reviewers still inspect that behavior in the diff. Whenever
|
|
312
|
+
`needsHumanReview: true`, always put `NEEDS HUMAN REVIEW: <categories>` in the
|
|
313
|
+
review header, even for an explicit scan. A human must read those paths.
|
|
314
|
+
|
|
315
|
+
Explicit depth wins, but never suppresses this marker. Without an explicit
|
|
316
|
+
request, apply AUTO-DEPTH in this order after the risk check:
|
|
317
|
+
- `deep` if the classifier returns `deep: true`, additions+deletions > 800,
|
|
318
|
+
changedFiles > 25, or the diff touches `migrations/` or `*.sql`.
|
|
319
|
+
- `deps` (candidate) if every changed file is a lockfile or `package.json`.
|
|
320
|
+
Confirm after Track A with `scripts/deps-only.mjs`; until then run Track B
|
|
321
|
+
as for `scan`.
|
|
322
|
+
- `scan` if additions+deletions < 150 AND changedFiles <= 5.
|
|
323
|
+
- `medium` otherwise.
|
|
324
|
+
|
|
325
|
+
Lockfiles (`package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`,
|
|
326
|
+
`pnpm-lock.yaml`, `bun.lock`, `bun.lockb`) do not count toward additions+deletions.
|
|
327
|
+
Step 0 depth is provisional: if the mandatory check upgrades scan, run Track A
|
|
328
|
+
and gather the newly required Track B sources before Step 2. Branch mode uses
|
|
329
|
+
Track A's merge-base diff. State final depth and whether auto-selected in the
|
|
330
|
+
review header.
|
|
305
331
|
|
|
306
332
|
**`--post` flag:** parse and store as `POST_REVIEW=true`.
|
|
307
333
|
|
|
@@ -673,7 +699,7 @@ Write `<scratchpad>/context.json` with keys:
|
|
|
673
699
|
"botPatternsFile": "<BOT_PATTERNS_FILE, or null>",
|
|
674
700
|
"depsReport": "<deps-report.json contents at depth deps, else null>",
|
|
675
701
|
"depsInstall": "<worktree.json depsInstall>",
|
|
676
|
-
"prBody": "<PR body at depth
|
|
702
|
+
"prBody": "<full PR body at every depth; empty string in branch mode>"
|
|
677
703
|
}
|
|
678
704
|
```
|
|
679
705
|
|
package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/implementation.md
CHANGED
|
@@ -48,6 +48,22 @@ Axes to cover:
|
|
|
48
48
|
fixed, the mitigation fully resolves the defect, or the PR is not a bug fix.
|
|
49
49
|
- Integration Contracts: Shopify API misuse, BC API assumptions, webhook
|
|
50
50
|
idempotency, external API pagination not handled.
|
|
51
|
+
- Rollout safety (external writes): when the diff changes what is WRITTEN to
|
|
52
|
+
another system (an API payload, field mapping, or a DB write into a system
|
|
53
|
+
this service does not own), state whether the PR has a feature flag
|
|
54
|
+
(Reflag / `getShopFlag` / `isEnabled` style check around the new write) or a
|
|
55
|
+
documented revert plan (PR body or code comment). Look in the diff and in the
|
|
56
|
+
PR body in CONTEXT_FILE. When WORKTREE_PATH is available, trace the changed
|
|
57
|
+
write through unchanged callers and flag guards before deciding either is
|
|
58
|
+
absent. Without a worktree, absence from the diff is not proof: report a
|
|
59
|
+
missing rollout control only when the diff demonstrates that the changed
|
|
60
|
+
write bypasses a guard and the PR body provides no revert plan. Otherwise
|
|
61
|
+
mark rollout verification unavailable rather than inventing a finding.
|
|
62
|
+
If neither control exists on that evidence, report an `important` finding:
|
|
63
|
+
`title` starts "No flag or revert plan for external write", `badCode` quotes
|
|
64
|
+
the changed write, `fix` names the flag guard to add. A pure UI/copy change or
|
|
65
|
+
a write that is trivially undone by deploying again is exempt. Quiet when the
|
|
66
|
+
flag or plan is present; do not report its presence.
|
|
51
67
|
|
|
52
68
|
Boundary contract and lifecycle map (required for every Critical/Important
|
|
53
69
|
finding involving routing, persistence, caching, fallback, proxying, or an
|
package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/triage-logic.md
CHANGED
|
@@ -11,6 +11,22 @@ Axes to cover (Critical/Important only):
|
|
|
11
11
|
- Scalability: N+1 queries, missing pagination, unbounded collections, missing rate-limit
|
|
12
12
|
- Integration Contracts: Shopify API misuse, webhook idempotency not handled
|
|
13
13
|
- Correctness: off-by-one, wrong operator, missing null guard
|
|
14
|
+
- Rollout safety (external writes): when the diff changes what is WRITTEN to
|
|
15
|
+
another system (an API payload, field mapping, or a DB write into a system
|
|
16
|
+
this service does not own), state whether the PR has a feature flag
|
|
17
|
+
(Reflag / `getShopFlag` / `isEnabled` style check around the new write) or a
|
|
18
|
+
documented revert plan (PR body or code comment). Look in the diff and in the
|
|
19
|
+
PR body in CONTEXT_FILE. When WORKTREE_PATH is available, trace the changed
|
|
20
|
+
write through unchanged callers and flag guards before deciding either is
|
|
21
|
+
absent. Without a worktree, absence from the diff is not proof: report a
|
|
22
|
+
missing rollout control only when the diff demonstrates that the changed
|
|
23
|
+
write bypasses a guard and the PR body provides no revert plan. Otherwise
|
|
24
|
+
mark rollout verification unavailable rather than inventing a finding.
|
|
25
|
+
If neither control exists on that evidence, report an `important` finding:
|
|
26
|
+
`title` starts "No flag or revert plan for external write", `badCode` quotes
|
|
27
|
+
the changed write, `fix` names the flag guard to add. A pure UI/copy change or
|
|
28
|
+
a write that is trivially undone by deploying again is exempt. Quiet when the
|
|
29
|
+
flag or plan is present; do not report its presence.
|
|
14
30
|
|
|
15
31
|
CI_STATUS: read key "ciStatus" from the JSON file CONTEXT_FILE.
|
|
16
32
|
EXISTING_REVIEWS: read key "existingReviews" from CONTEXT_FILE (awareness only, skip findings at same file:line)
|
|
@@ -24,6 +24,7 @@ Output the review as a markdown response (not via printf). No ANSI escapes.
|
|
|
24
24
|
**Proofs:** <M proven / N attempted> *(include only when N > 0)*
|
|
25
25
|
**CI:** <✅ All passing | ⚠️ N failing: check-name | ⏳ Pending | N/A>
|
|
26
26
|
**Depth:** <📦 deps | ⚡ scan | 🔍 medium | 🔬 deep>
|
|
27
|
+
**NEEDS HUMAN REVIEW:** <categories from risk-paths.mjs> *(only when `needsHumanReview` is true: a person must read these paths whatever the verdict)*
|
|
27
28
|
**Engine:** <workflow | codex | revmux>
|
|
28
29
|
|
|
29
30
|
---
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Path-based risk for AUTO-DEPTH. Size alone misses the small PRs that matter:
|
|
3
|
+
// a 10-line change to a workflow file, a session check or a P21 mapping.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// node risk-paths.mjs [--diff <pr.diff>] -- <changed-file>...
|
|
7
|
+
// git diff --name-only -z <base>...HEAD | node risk-paths.mjs --stdin0 --diff <pr.diff>
|
|
8
|
+
// Use a shell-free process API for file arguments; never interpolate PR paths.
|
|
9
|
+
//
|
|
10
|
+
// Prints {"deep": bool, "needsHumanReview": bool, "categories": [{category, files}]}.
|
|
11
|
+
// `--diff` also lets the identifier `p21` count when it only appears in added lines.
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
export const RISK_RULES = [
|
|
17
|
+
{ category: 'ci-workflow', test: (f) => /^\.github\/workflows\//.test(f) },
|
|
18
|
+
{ category: 'auth', test: (f) => /(^|[/._-])(auth|authn|authz|authenticate|authenticated|authentication|authorize|authorized|authorization|oauth|session|sessions|permission|permissions|rbac|acl|jwt|login)([/._-]|$)/i.test(f.replace(/([a-z0-9])([A-Z])/g, '$1-$2')) },
|
|
19
|
+
{ category: 'billing', test: (f) => /(billing|payment|payments|invoice|invoices|checkout|stripe|subscription)/i.test(f) },
|
|
20
|
+
{ category: 'installer', test: (f) => /(^|\/)(install|setup|bootstrap|preinstall|postinstall)(?:[^/.]*|[^/]*\.(sh|bash|zsh|ps1|js|mjs|cjs|ts|py|bat|cmd))$/i.test(f) },
|
|
21
|
+
{ category: 'p21', test: (f) => /p21/i.test(f) },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function addedLinesMention(diffText, pattern) {
|
|
25
|
+
return diffText.split('\n').some((l) => l.startsWith('+') && !l.startsWith('+++') && pattern.test(l));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function riskPaths(files, diffText = '') {
|
|
29
|
+
const categories = RISK_RULES
|
|
30
|
+
.map((rule) => ({ category: rule.category, files: files.filter(rule.test) }))
|
|
31
|
+
.map((c) => (c.category === 'p21' && c.files.length === 0 && addedLinesMention(diffText, /p21/i)
|
|
32
|
+
? { ...c, files: ['(identifier p21 in added lines)'] }
|
|
33
|
+
: c))
|
|
34
|
+
.filter((c) => c.files.length > 0);
|
|
35
|
+
return { deep: categories.length > 0, needsHumanReview: categories.length > 0, categories };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function main(argv) {
|
|
39
|
+
let diffText = '';
|
|
40
|
+
let inputMode = null;
|
|
41
|
+
const files = [];
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const arg = argv[i];
|
|
44
|
+
if (arg === '--') {
|
|
45
|
+
files.push(...argv.slice(i + 1));
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--diff') {
|
|
49
|
+
if (!argv[i + 1] || argv[i + 1] === '--') throw new Error('--diff requires a file');
|
|
50
|
+
diffText = readFileSync(argv[++i], 'utf8');
|
|
51
|
+
} else if (arg === '--stdin' || arg === '--stdin0') {
|
|
52
|
+
if (inputMode) throw new Error('Choose only one stdin mode');
|
|
53
|
+
inputMode = arg;
|
|
54
|
+
} else if (arg.startsWith('--')) {
|
|
55
|
+
throw new Error(`Unknown option: ${arg}; put filenames after --`);
|
|
56
|
+
} else {
|
|
57
|
+
files.push(arg);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (inputMode) {
|
|
61
|
+
if (files.length) throw new Error('Do not mix stdin and file arguments');
|
|
62
|
+
files.push(...readFileSync(0, 'utf8').split(inputMode === '--stdin0' ? '\0' : '\n').filter(Boolean));
|
|
63
|
+
}
|
|
64
|
+
console.log(JSON.stringify(riskPaths(files, diffText)));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -284,23 +284,49 @@ Then:
|
|
|
284
284
|
`LOCAL_BRANCH` is `main`, `master`, `develop`, `staging`, or `production`, stop
|
|
285
285
|
and say so.
|
|
286
286
|
|
|
287
|
-
**Depth:** explicit
|
|
288
|
-
|
|
289
|
-
`gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles,files,baseRefName,state,mergeCommit
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
287
|
+
**Depth:** record an explicit `scan`, `medium`, `deep` or `deps` request;
|
|
288
|
+
otherwise select a provisional depth from the per-file stats. In `pr` mode use
|
|
289
|
+
`gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles,files,baseRefName,state,mergeCommit`.
|
|
290
|
+
Keep `baseRefName` as `BASE_BRANCH`, `changedFiles` as `EXPECTED_FILES`, and
|
|
291
|
+
`mergeCommit.oid` as `MERGE_COMMIT` when merged. In `branch` mode use
|
|
292
|
+
`git diff --numstat <MERGE_BASE> HEAD`.
|
|
293
|
+
|
|
294
|
+
**Mandatory risk check (every depth, including explicit requests):** after
|
|
295
|
+
Track B has saved `pr.diff`, and before finalizing depth or starting Step 2,
|
|
296
|
+
run `scripts/risk-paths.mjs` with that diff and the exact changed paths.
|
|
297
|
+
Resolve the script beside the selected `SKILL.md`: in Codex use
|
|
298
|
+
`<directory containing selected SKILL.md>/scripts/risk-paths.mjs`; in Claude
|
|
299
|
+
use `${CLAUDE_PLUGIN_ROOT}/skills/lekker-review/scripts/risk-paths.mjs`.
|
|
300
|
+
Use a shell-free process API with separate arguments:
|
|
301
|
+
`execFileSync('node', [riskScript, '--diff', diffFile, '--', ...changedPaths])`.
|
|
302
|
+
Never interpolate PR-controlled paths into shell source. Alternatively pipe
|
|
303
|
+
`git diff --name-only -z <MERGE_BASE> HEAD` into
|
|
304
|
+
`node "<resolved riskScript>" --stdin0 --diff "<scratchpad>/pr.diff"`.
|
|
305
|
+
Do not continue on classifier failure; report risk classification unavailable.
|
|
306
|
+
|
|
307
|
+
The classifier detects configured path patterns for CI workflows, auth,
|
|
308
|
+
billing, installers/setup, and P21, plus `p21` in added lines. These are path
|
|
309
|
+
heuristics, not complete detection of auth/billing behavior in generic files;
|
|
310
|
+
reviewers still inspect that behavior in the diff. Whenever
|
|
311
|
+
`needsHumanReview: true`, always put `NEEDS HUMAN REVIEW: <categories>` in the
|
|
312
|
+
review header, even for an explicit scan. A human must read those paths.
|
|
313
|
+
|
|
314
|
+
Explicit depth wins, but never suppresses this marker. Without an explicit
|
|
315
|
+
request, apply AUTO-DEPTH in this order after the risk check:
|
|
316
|
+
- `deep` if the classifier returns `deep: true`, additions+deletions > 800,
|
|
317
|
+
changedFiles > 25, or the diff touches `migrations/` or `*.sql`.
|
|
318
|
+
- `deps` (candidate) if every changed file is a lockfile or `package.json`.
|
|
319
|
+
Confirm after Track A with `scripts/deps-only.mjs`; until then run Track B
|
|
320
|
+
as for `scan`.
|
|
321
|
+
- `scan` if additions+deletions < 150 AND changedFiles <= 5.
|
|
322
|
+
- `medium` otherwise.
|
|
323
|
+
|
|
324
|
+
Lockfiles (`package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`,
|
|
325
|
+
`pnpm-lock.yaml`, `bun.lock`, `bun.lockb`) do not count toward additions+deletions.
|
|
326
|
+
Step 0 depth is provisional: if the mandatory check upgrades scan, run Track A
|
|
327
|
+
and gather the newly required Track B sources before Step 2. Branch mode uses
|
|
328
|
+
Track A's merge-base diff. State final depth and whether auto-selected in the
|
|
329
|
+
review header.
|
|
304
330
|
|
|
305
331
|
**`--post` flag:** parse and store as `POST_REVIEW=true`.
|
|
306
332
|
|
|
@@ -672,7 +698,7 @@ Write `<scratchpad>/context.json` with keys:
|
|
|
672
698
|
"botPatternsFile": "<BOT_PATTERNS_FILE, or null>",
|
|
673
699
|
"depsReport": "<deps-report.json contents at depth deps, else null>",
|
|
674
700
|
"depsInstall": "<worktree.json depsInstall>",
|
|
675
|
-
"prBody": "<PR body at depth
|
|
701
|
+
"prBody": "<full PR body at every depth; empty string in branch mode>"
|
|
676
702
|
}
|
|
677
703
|
```
|
|
678
704
|
|
|
@@ -48,6 +48,22 @@ Axes to cover:
|
|
|
48
48
|
fixed, the mitigation fully resolves the defect, or the PR is not a bug fix.
|
|
49
49
|
- Integration Contracts: Shopify API misuse, BC API assumptions, webhook
|
|
50
50
|
idempotency, external API pagination not handled.
|
|
51
|
+
- Rollout safety (external writes): when the diff changes what is WRITTEN to
|
|
52
|
+
another system (an API payload, field mapping, or a DB write into a system
|
|
53
|
+
this service does not own), state whether the PR has a feature flag
|
|
54
|
+
(Reflag / `getShopFlag` / `isEnabled` style check around the new write) or a
|
|
55
|
+
documented revert plan (PR body or code comment). Look in the diff and in the
|
|
56
|
+
PR body in CONTEXT_FILE. When WORKTREE_PATH is available, trace the changed
|
|
57
|
+
write through unchanged callers and flag guards before deciding either is
|
|
58
|
+
absent. Without a worktree, absence from the diff is not proof: report a
|
|
59
|
+
missing rollout control only when the diff demonstrates that the changed
|
|
60
|
+
write bypasses a guard and the PR body provides no revert plan. Otherwise
|
|
61
|
+
mark rollout verification unavailable rather than inventing a finding.
|
|
62
|
+
If neither control exists on that evidence, report an `important` finding:
|
|
63
|
+
`title` starts "No flag or revert plan for external write", `badCode` quotes
|
|
64
|
+
the changed write, `fix` names the flag guard to add. A pure UI/copy change or
|
|
65
|
+
a write that is trivially undone by deploying again is exempt. Quiet when the
|
|
66
|
+
flag or plan is present; do not report its presence.
|
|
51
67
|
|
|
52
68
|
Boundary contract and lifecycle map (required for every Critical/Important
|
|
53
69
|
finding involving routing, persistence, caching, fallback, proxying, or an
|
|
@@ -11,6 +11,22 @@ Axes to cover (Critical/Important only):
|
|
|
11
11
|
- Scalability: N+1 queries, missing pagination, unbounded collections, missing rate-limit
|
|
12
12
|
- Integration Contracts: Shopify API misuse, webhook idempotency not handled
|
|
13
13
|
- Correctness: off-by-one, wrong operator, missing null guard
|
|
14
|
+
- Rollout safety (external writes): when the diff changes what is WRITTEN to
|
|
15
|
+
another system (an API payload, field mapping, or a DB write into a system
|
|
16
|
+
this service does not own), state whether the PR has a feature flag
|
|
17
|
+
(Reflag / `getShopFlag` / `isEnabled` style check around the new write) or a
|
|
18
|
+
documented revert plan (PR body or code comment). Look in the diff and in the
|
|
19
|
+
PR body in CONTEXT_FILE. When WORKTREE_PATH is available, trace the changed
|
|
20
|
+
write through unchanged callers and flag guards before deciding either is
|
|
21
|
+
absent. Without a worktree, absence from the diff is not proof: report a
|
|
22
|
+
missing rollout control only when the diff demonstrates that the changed
|
|
23
|
+
write bypasses a guard and the PR body provides no revert plan. Otherwise
|
|
24
|
+
mark rollout verification unavailable rather than inventing a finding.
|
|
25
|
+
If neither control exists on that evidence, report an `important` finding:
|
|
26
|
+
`title` starts "No flag or revert plan for external write", `badCode` quotes
|
|
27
|
+
the changed write, `fix` names the flag guard to add. A pure UI/copy change or
|
|
28
|
+
a write that is trivially undone by deploying again is exempt. Quiet when the
|
|
29
|
+
flag or plan is present; do not report its presence.
|
|
14
30
|
|
|
15
31
|
CI_STATUS: read key "ciStatus" from the JSON file CONTEXT_FILE.
|
|
16
32
|
EXISTING_REVIEWS: read key "existingReviews" from CONTEXT_FILE (awareness only, skip findings at same file:line)
|
|
@@ -24,6 +24,7 @@ Output the review as a markdown response (not via printf). No ANSI escapes.
|
|
|
24
24
|
**Proofs:** <M proven / N attempted> *(include only when N > 0)*
|
|
25
25
|
**CI:** <✅ All passing | ⚠️ N failing: check-name | ⏳ Pending | N/A>
|
|
26
26
|
**Depth:** <📦 deps | ⚡ scan | 🔍 medium | 🔬 deep>
|
|
27
|
+
**NEEDS HUMAN REVIEW:** <categories from risk-paths.mjs> *(only when `needsHumanReview` is true: a person must read these paths whatever the verdict)*
|
|
27
28
|
**Engine:** <workflow | codex | revmux>
|
|
28
29
|
|
|
29
30
|
---
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Path-based risk for AUTO-DEPTH. Size alone misses the small PRs that matter:
|
|
3
|
+
// a 10-line change to a workflow file, a session check or a P21 mapping.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// node risk-paths.mjs [--diff <pr.diff>] -- <changed-file>...
|
|
7
|
+
// git diff --name-only -z <base>...HEAD | node risk-paths.mjs --stdin0 --diff <pr.diff>
|
|
8
|
+
// Use a shell-free process API for file arguments; never interpolate PR paths.
|
|
9
|
+
//
|
|
10
|
+
// Prints {"deep": bool, "needsHumanReview": bool, "categories": [{category, files}]}.
|
|
11
|
+
// `--diff` also lets the identifier `p21` count when it only appears in added lines.
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
export const RISK_RULES = [
|
|
17
|
+
{ category: 'ci-workflow', test: (f) => /^\.github\/workflows\//.test(f) },
|
|
18
|
+
{ category: 'auth', test: (f) => /(^|[/._-])(auth|authn|authz|authenticate|authenticated|authentication|authorize|authorized|authorization|oauth|session|sessions|permission|permissions|rbac|acl|jwt|login)([/._-]|$)/i.test(f.replace(/([a-z0-9])([A-Z])/g, '$1-$2')) },
|
|
19
|
+
{ category: 'billing', test: (f) => /(billing|payment|payments|invoice|invoices|checkout|stripe|subscription)/i.test(f) },
|
|
20
|
+
{ category: 'installer', test: (f) => /(^|\/)(install|setup|bootstrap|preinstall|postinstall)(?:[^/.]*|[^/]*\.(sh|bash|zsh|ps1|js|mjs|cjs|ts|py|bat|cmd))$/i.test(f) },
|
|
21
|
+
{ category: 'p21', test: (f) => /p21/i.test(f) },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function addedLinesMention(diffText, pattern) {
|
|
25
|
+
return diffText.split('\n').some((l) => l.startsWith('+') && !l.startsWith('+++') && pattern.test(l));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function riskPaths(files, diffText = '') {
|
|
29
|
+
const categories = RISK_RULES
|
|
30
|
+
.map((rule) => ({ category: rule.category, files: files.filter(rule.test) }))
|
|
31
|
+
.map((c) => (c.category === 'p21' && c.files.length === 0 && addedLinesMention(diffText, /p21/i)
|
|
32
|
+
? { ...c, files: ['(identifier p21 in added lines)'] }
|
|
33
|
+
: c))
|
|
34
|
+
.filter((c) => c.files.length > 0);
|
|
35
|
+
return { deep: categories.length > 0, needsHumanReview: categories.length > 0, categories };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function main(argv) {
|
|
39
|
+
let diffText = '';
|
|
40
|
+
let inputMode = null;
|
|
41
|
+
const files = [];
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const arg = argv[i];
|
|
44
|
+
if (arg === '--') {
|
|
45
|
+
files.push(...argv.slice(i + 1));
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--diff') {
|
|
49
|
+
if (!argv[i + 1] || argv[i + 1] === '--') throw new Error('--diff requires a file');
|
|
50
|
+
diffText = readFileSync(argv[++i], 'utf8');
|
|
51
|
+
} else if (arg === '--stdin' || arg === '--stdin0') {
|
|
52
|
+
if (inputMode) throw new Error('Choose only one stdin mode');
|
|
53
|
+
inputMode = arg;
|
|
54
|
+
} else if (arg.startsWith('--')) {
|
|
55
|
+
throw new Error(`Unknown option: ${arg}; put filenames after --`);
|
|
56
|
+
} else {
|
|
57
|
+
files.push(arg);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (inputMode) {
|
|
61
|
+
if (files.length) throw new Error('Do not mix stdin and file arguments');
|
|
62
|
+
files.push(...readFileSync(0, 'utf8').split(inputMode === '--stdin0' ? '\0' : '\n').filter(Boolean));
|
|
63
|
+
}
|
|
64
|
+
console.log(JSON.stringify(riskPaths(files, diffText)));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(2));
|