rvlib-shipkit 0.13.0 → 0.14.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/CHANGELOG.md +34 -0
- package/package.json +2 -2
- package/src/contracts/contract-types.ts +5 -2
- package/src/contracts/contracts-engine.ts +13 -3
- package/src/contracts/manifests/discord.contract.ts +1 -1
- package/src/contracts/manifests/identity.contract.ts +1 -1
- package/src/contracts/manifests/itch.contract.ts +19 -2
- package/src/init-repo.ts +3 -0
- package/src/repo-config.ts +11 -1
- package/src/rules/SK008-reviewer-agent.ts +98 -0
- package/src/rules/rule-registry.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-07-27
|
|
4
|
+
|
|
5
|
+
New rule SK008 — the mandated reviewer agent type. A repo declares
|
|
6
|
+
`reviewer: { name, brief, tools? }` in `repo.config.ts`; the rule then
|
|
7
|
+
requires `.claude/agents/<name>.md` to exist, carry that type name, point at
|
|
8
|
+
the brief file (which must itself exist), and keep `tools:` inside a
|
|
9
|
+
read-only ALLOWLIST (default `Read, Grep, Glob, Bash`; widen it deliberately
|
|
10
|
+
with `reviewer.tools`). A denylist of today's edit-tool names would wave
|
|
11
|
+
through anything that edits by proxy, and an ABSENT `tools:` line is the
|
|
12
|
+
worst case: the harness inherits every tool. Fixable and create-only —
|
|
13
|
+
`shipkit check --fix SK008` writes a valid definition when the file is
|
|
14
|
+
missing and never overwrites an existing one, which may be hand-tuned.
|
|
15
|
+
Undeclared repos skip, and `shipkit init` scaffolds the key commented out.
|
|
16
|
+
`reviewerAgentIssues` is exported so a repo's own linter can enforce the same
|
|
17
|
+
invariants in its pre-commit hook without a second implementation. Note the
|
|
18
|
+
written agent file embeds the brief PATH: a repo that may go public should
|
|
19
|
+
gitignore it and let the fixer regenerate it per machine.
|
|
20
|
+
|
|
21
|
+
Contract rule `png(path, w, h, { atLeast: true })` — the venue's numbers as a
|
|
22
|
+
MINIMUM at a fixed aspect ratio (relative tolerance), for stores that DISPLAY
|
|
23
|
+
a size but accept larger uploads. Without it an exact-size rule fails a better
|
|
24
|
+
2x asset and blocks the release it was meant to gate. Applied to the itch
|
|
25
|
+
cover and the identity/discord image rules; Steam capsules stay exact, Valve
|
|
26
|
+
mandates those.
|
|
27
|
+
|
|
28
|
+
Dep: `rvlib-ts-tools` ^0.2.0 → ^0.3.0, so the zero-config `imports:check`
|
|
29
|
+
floor task inherits its honest reporting (a run that scanned zero files now
|
|
30
|
+
fails instead of printing green).
|
|
31
|
+
|
|
32
|
+
MIGRATION: the itch venue contract gains a required `.rv-itch/listing.md`
|
|
33
|
+
(every store-form field answered, paste-ready). A repo already shipping on itch
|
|
34
|
+
will see one new FAIL until the file exists; the docs that own the itch
|
|
35
|
+
contract carry a fill-in template for it.
|
|
36
|
+
|
|
3
37
|
## 0.13.0 — 2026-07-26
|
|
4
38
|
|
|
5
39
|
Stable rule ids. Every built-in invariant `shipkit check` enforces is now
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "All-in-one repo companion: ci gate (task DAG), auto-discovered health checks, release-contract fulfillness, asks queue, md-wiki tools, asset atelier, live tui",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"mobx-react-lite": "^4.1.1",
|
|
34
34
|
"react": "^19.2.7",
|
|
35
35
|
"react-dom": "^19.2.7",
|
|
36
|
-
"rvlib-ts-tools": "^0.
|
|
36
|
+
"rvlib-ts-tools": "^0.3.0",
|
|
37
37
|
"sanitize-html": "^2.17.6",
|
|
38
38
|
"sharp": "^0.35.3"
|
|
39
39
|
}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
export type RuleOpts = { warnOnly?: boolean }
|
|
8
8
|
export type FileRule = RuleOpts & { kind: 'file'; path: string; nonEmpty: boolean }
|
|
9
|
-
|
|
9
|
+
/** `atLeast`: the venue's numbers are a MINIMUM at a fixed aspect ratio (itch
|
|
10
|
+
* displays a 630x500 cover but accepts larger, and a 2x asset is better) —
|
|
11
|
+
* without it an exact-size rule blocks the release over a better file */
|
|
12
|
+
export type PngRule = RuleOpts & { kind: 'png'; path: string; width: number; height: number; atLeast?: boolean }
|
|
10
13
|
export type GlobRule = RuleOpts & { kind: 'glob'; pattern: string; min: number; width?: number; height?: number }
|
|
11
14
|
export type MdRule = RuleOpts & { kind: 'md'; path: string; mustContain: string[] }
|
|
12
15
|
export type DirRule = RuleOpts & { kind: 'dir'; path: string }
|
|
@@ -41,7 +44,7 @@ export type ContractManifest = {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export const file = (path: string, p?: RuleOpts & { nonEmpty?: boolean }): FileRule => ({ kind: 'file', path, nonEmpty: p?.nonEmpty ?? true, warnOnly: p?.warnOnly })
|
|
44
|
-
export const png = (path: string, width: number, height: number, p?: RuleOpts): PngRule => ({ kind: 'png', path, width, height, warnOnly: p?.warnOnly })
|
|
47
|
+
export const png = (path: string, width: number, height: number, p?: RuleOpts & { atLeast?: boolean }): PngRule => ({ kind: 'png', path, width, height, atLeast: p?.atLeast, warnOnly: p?.warnOnly })
|
|
45
48
|
export const glob = (pattern: string, p: RuleOpts & { min: number; width?: number; height?: number }): GlobRule => ({ kind: 'glob', pattern, min: p.min, width: p.width, height: p.height, warnOnly: p.warnOnly })
|
|
46
49
|
export const md = (path: string, p?: RuleOpts & { mustContain?: string[] }): MdRule => ({ kind: 'md', path, mustContain: p?.mustContain ?? [], warnOnly: p?.warnOnly })
|
|
47
50
|
export const dir = (path: string, p?: RuleOpts): DirRule => ({ kind: 'dir', path, warnOnly: p?.warnOnly })
|
|
@@ -57,11 +57,21 @@ async function checkRule(rule: ArtifactRule, env: ContractEnv): Promise<string[]
|
|
|
57
57
|
}
|
|
58
58
|
case 'png': {
|
|
59
59
|
const p = abs(env.root, rule.path)
|
|
60
|
-
|
|
60
|
+
const need = rule.atLeast === true ? `>= ${rule.width}x${rule.height} at ${rule.width}:${rule.height}` : `${rule.width}x${rule.height}`
|
|
61
|
+
if (!existsSync(p)) { problems.push(`${rule.path} missing — need png ${need}`); break }
|
|
61
62
|
const dims = pngDims(p)
|
|
62
63
|
if (dims == null) problems.push(`${rule.path} is not a readable png`)
|
|
63
|
-
else if (
|
|
64
|
-
problems.push(`${rule.path} is ${dims.width}x${dims.height} — need ${
|
|
64
|
+
else if (rule.atLeast !== true) {
|
|
65
|
+
if (dims.width !== rule.width || dims.height !== rule.height) problems.push(`${rule.path} is ${dims.width}x${dims.height} — need ${need}`)
|
|
66
|
+
} else {
|
|
67
|
+
const want = rule.width / rule.height
|
|
68
|
+
// RELATIVE tolerance: an absolute one is 0.8% at 630:500 and
|
|
69
|
+
// 3% at a tall ratio, i.e. a different rule per venue
|
|
70
|
+
const ratioOff = Math.abs(dims.width / dims.height - want) / want > 0.01
|
|
71
|
+
const tooSmall = dims.width < rule.width || dims.height < rule.height
|
|
72
|
+
if (tooSmall || ratioOff)
|
|
73
|
+
problems.push(`${rule.path} is ${dims.width}x${dims.height} — need ${need}${ratioOff ? ' (the ratio is off; a bigger asset at the same ratio is fine)' : ' (bigger is fine, smaller is not)'}`)
|
|
74
|
+
}
|
|
65
75
|
break
|
|
66
76
|
}
|
|
67
77
|
case 'glob': {
|
|
@@ -13,7 +13,7 @@ export default defineContract({
|
|
|
13
13
|
artifacts: [
|
|
14
14
|
file('.rv-identity/brand.ts'),
|
|
15
15
|
png('public/favicon.png', 512, 512),
|
|
16
|
-
png('public/og.png', 1200, 630),
|
|
16
|
+
png('public/og.png', 1200, 630, { atLeast: true }),
|
|
17
17
|
glob('public/logo.*', { min: 1 }),
|
|
18
18
|
],
|
|
19
19
|
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// normative WHAT for .rv-itch/ — the WHY + workflow live in the sibling page.
|
|
2
|
-
import { defineContract, dirExists, fileContains, glob, md, png, script } from 'rvlib-shipkit/src/contracts/contract-types.ts'
|
|
2
|
+
import { custom, defineContract, dirExists, fileContains, glob, md, png, script } from 'rvlib-shipkit/src/contracts/contract-types.ts'
|
|
3
|
+
import { existsSync, readFileSync } from 'fs'
|
|
4
|
+
import { join } from 'path'
|
|
3
5
|
|
|
4
6
|
export default defineContract({
|
|
5
7
|
venue: 'itch',
|
|
@@ -10,7 +12,22 @@ export default defineContract({
|
|
|
10
12
|
artifacts: [
|
|
11
13
|
// one AI-disclosure wording everywhere — page.md is its canonical home
|
|
12
14
|
md('.rv-itch/page.md', { mustContain: ['## AI disclosure'] }),
|
|
13
|
-
|
|
15
|
+
// every field of itch's create-project form, answered and ready to
|
|
16
|
+
// paste — the listing is a browser-only act, so the artifact IS the
|
|
17
|
+
// handoff (references/listing.template.md is the fill-in copy)
|
|
18
|
+
md('.rv-itch/listing.md', { mustContain: ['## Title', '## Short description or tagline', '## Classification', '## Genre', '## Tags', '## Visibility'] }),
|
|
19
|
+
// headings alone would pass the TEMPLATE copied in verbatim, and this
|
|
20
|
+
// file exists to be pasted with no decision left in it (field
|
|
21
|
+
// 2026-07-27: a reviewer proved `cp template listing.md` went green)
|
|
22
|
+
custom('.rv-itch/listing.md answered', (env) => {
|
|
23
|
+
const p = join(env.root, '.rv-itch/listing.md')
|
|
24
|
+
if (!existsSync(p)) return []
|
|
25
|
+
const left = [...readFileSync(p, 'utf8').matchAll(/<[a-z][^<>\n]{2,60}>/g)].map((m) => m[0])
|
|
26
|
+
return left.length === 0 ? [] : [`.rv-itch/listing.md still holds ${left.length} unanswered placeholder(s): ${[...new Set(left)].slice(0, 3).join(' ')} — it is pasted into the store form as-is`]
|
|
27
|
+
}),
|
|
28
|
+
// itch DISPLAYS 630x500 and accepts larger — an exact-size rule would
|
|
29
|
+
// block a release over a better cover (audit 2026-07-27)
|
|
30
|
+
png('.rv-itch/cover.png', 630, 500, { atLeast: true }),
|
|
14
31
|
glob('.rv-itch/screenshots/*.png', { min: 3 }),
|
|
15
32
|
],
|
|
16
33
|
})
|
package/src/init-repo.ts
CHANGED
|
@@ -19,6 +19,9 @@ export default defineRepo({
|
|
|
19
19
|
venues: [], // e.g. ['itch', 'steam'] — selects which .rv-<venue>/ contracts are REQUIRED
|
|
20
20
|
social: [], // e.g. ['x', 'discord'] — .rv-social/<channel>/ post folders (+ .rv-discord/)
|
|
21
21
|
tasks: [], // repo commit-gate tasks; deps double as race guards
|
|
22
|
+
// the mandated reviewer agent type (rule SK008) — uncomment with YOUR
|
|
23
|
+
// standing brief, then \`shipkit check --fix SK008\` writes the agent file:
|
|
24
|
+
// reviewer: { name: 'my-reviewer', brief: '~/path/to/reviewer-brief.md' },
|
|
22
25
|
// health checks: no config — drop a <name>.check.ts file anywhere,
|
|
23
26
|
// \`shipkit check\` auto-discovers and runs it (wire output, exit code)
|
|
24
27
|
})
|
package/src/repo-config.ts
CHANGED
|
@@ -62,6 +62,13 @@ export type RepoConfig = {
|
|
|
62
62
|
/** declared github visibility INTENT (absent = 'private') — rule SK007
|
|
63
63
|
* fails loud when `gh repo view` disagrees in either direction */
|
|
64
64
|
visibility?: 'private' | 'public'
|
|
65
|
+
/** the reviewer agent type this repo mandates: `.claude/agents/<name>.md`,
|
|
66
|
+
* whose system prompt points at `brief` (an md file holding the standing
|
|
67
|
+
* review framing — one home, shared across repos). `tools` overrides the
|
|
68
|
+
* read-only allowlist SK008 enforces (default: Read, Grep, Glob, Bash).
|
|
69
|
+
* Rule SK008 lints it, `check --fix SK008` writes it. Absent = no
|
|
70
|
+
* mandated reviewer. */
|
|
71
|
+
reviewer?: { name: string; brief: string; tools?: string[] }
|
|
65
72
|
/** extra dashboard panels appended after the built-in Tasks/Checks (`shipkit tui`) */
|
|
66
73
|
tui?: { panels?: TuiPanelDef[] }
|
|
67
74
|
/** the `shipkit serve` hub: port + optional graph hooks */
|
|
@@ -69,13 +76,16 @@ export type RepoConfig = {
|
|
|
69
76
|
logDir?: string
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'notifyCommand', 'contracts', 'gitignore', 'visibility', 'tui', 'serve', 'logDir'])
|
|
79
|
+
const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'notifyCommand', 'contracts', 'gitignore', 'visibility', 'reviewer', 'tui', 'serve', 'logDir'])
|
|
73
80
|
|
|
74
81
|
export function defineRepo(c: RepoConfig): RepoConfig {
|
|
75
82
|
if (typeof c.name !== 'string' || c.name === '') throw new Error('defineRepo: name is required')
|
|
76
83
|
for (const v of c.venues ?? []) if (!/^[a-z0-9-]+$/.test(v)) throw new Error(`defineRepo: venue '${v}' must be kebab-case`)
|
|
77
84
|
if (c.visibility != null && c.visibility !== 'private' && c.visibility !== 'public')
|
|
78
85
|
throw new Error(`defineRepo: visibility '${String(c.visibility)}' — 'private' or 'public' only`)
|
|
86
|
+
// a half-declared reviewer would lint a path like `.claude/agents/.md`
|
|
87
|
+
if (c.reviewer != null && (!/^[a-z0-9][a-z0-9-]*$/.test(c.reviewer.name ?? '') || (c.reviewer.brief ?? '') === ''))
|
|
88
|
+
throw new Error(`defineRepo: reviewer needs { name: kebab-case, brief: path to the standing brief }`)
|
|
79
89
|
// unknown keys die loud — a typo'd or removed key silently changing the
|
|
80
90
|
// gate is exactly the never-silence failure mode (A23)
|
|
81
91
|
for (const k of Object.keys(c))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// SK008 — the mandated reviewer agent type. A repo that declares
|
|
2
|
+
// `reviewer: { name, brief }` must carry `.claude/agents/<name>.md`: an agent
|
|
3
|
+
// definition whose system prompt POINTS at the brief file (ONE home for the
|
|
4
|
+
// text, so review framing can never drift per repo) and whose `tools:` line
|
|
5
|
+
// stays inside a read-only ALLOWLIST — a reviewer's product is findings, never
|
|
6
|
+
// fixes, and a denylist of today's edit-tool names would wave through anything
|
|
7
|
+
// that edits by proxy. An absent `tools:` line is the worst case of all: the
|
|
8
|
+
// harness then inherits every tool. The brief itself must exist. No
|
|
9
|
+
// declaration = not a reviewer repo, and the rule passes silently.
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
11
|
+
import { homedir } from 'os'
|
|
12
|
+
import { dirname, isAbsolute, join } from 'path'
|
|
13
|
+
import type { RepoConfig } from '../repo-config.ts'
|
|
14
|
+
import type { FixResult, Rule, RuleEnv } from './rule.ts'
|
|
15
|
+
|
|
16
|
+
/** default read-only toolset — an ALLOWLIST, not a denylist: a tool that can
|
|
17
|
+
* edit by proxy (spawning a subagent, an MCP writer) is exactly what a
|
|
18
|
+
* denylist of today's edit-tool names would wave through */
|
|
19
|
+
export const REVIEWER_TOOLS = ['Read', 'Grep', 'Glob', 'Bash']
|
|
20
|
+
|
|
21
|
+
export const reviewerAgentPath = (name: string): string => `.claude/agents/${name}.md`
|
|
22
|
+
|
|
23
|
+
// the invariants, as a pure function over the file text — the rule below and
|
|
24
|
+
// any repo-local linter call THIS, so there is one definition of "a valid
|
|
25
|
+
// reviewer agent file" and not one per caller
|
|
26
|
+
export function reviewerAgentIssues(text: string, reviewer: NonNullable<RepoConfig['reviewer']>): string[] {
|
|
27
|
+
const issues: string[] = []
|
|
28
|
+
const allowed = reviewer.tools ?? REVIEWER_TOOLS
|
|
29
|
+
const declared = text.match(/^name:\s*(\S+)\s*$/m)?.[1]
|
|
30
|
+
if (declared !== reviewer.name)
|
|
31
|
+
issues.push(`frontmatter name is ${declared == null ? 'missing' : `'${declared}'`} — it must be '${reviewer.name}', the type spawners ask for`)
|
|
32
|
+
if (!text.includes(reviewer.brief))
|
|
33
|
+
issues.push(`never points at the brief '${reviewer.brief}' — the agent's whole job is to load it before judging anything`)
|
|
34
|
+
const tools = text.match(/^tools:\s*(.+)$/m)?.[1]
|
|
35
|
+
if (tools == null) issues.push(`has no inline 'tools:' allowlist — omitting it inherits every tool, edit tools included`)
|
|
36
|
+
else
|
|
37
|
+
for (const t of tools.split(',').map((x) => x.trim()).filter(Boolean))
|
|
38
|
+
if (!allowed.includes(t)) issues.push(`grants '${t}', outside the allowlist (${allowed.join(', ')}) — a reviewer reports findings, it never edits, directly or by proxy`)
|
|
39
|
+
return issues
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** the brief must EXIST: a reviewer whose mandated first action is reading a
|
|
43
|
+
* missing file flounders silently, which is the one thing this rule prevents */
|
|
44
|
+
export function briefMissing(root: string, brief: string): boolean {
|
|
45
|
+
const p = brief.startsWith('~/') ? join(homedir(), brief.slice(2)) : isAbsolute(brief) ? brief : join(root, brief)
|
|
46
|
+
return !existsSync(p)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function canonicalAgent(reviewer: NonNullable<RepoConfig['reviewer']>): string {
|
|
50
|
+
return `---
|
|
51
|
+
name: ${reviewer.name}
|
|
52
|
+
description: The mandated critical reviewer for this repo. Use before every commit, and whenever a diff needs judging against what was actually asked for. Judges whether a change should exist at all, not just whether it is correct.
|
|
53
|
+
tools: ${(reviewer.tools ?? REVIEWER_TOOLS).join(', ')}
|
|
54
|
+
model: opus
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
You are this repo's critical reviewer. You have no Edit or Write tool on
|
|
58
|
+
purpose: your product is findings, never fixes. Bash is for READING the state
|
|
59
|
+
of things (\`git diff\`, the gates, running a claim to check it) — never for
|
|
60
|
+
writing files by another route.
|
|
61
|
+
|
|
62
|
+
FIRST ACTION, always: read \`${reviewer.brief}\` and follow it exactly. It tells
|
|
63
|
+
you what else to read, when to refuse the job, the order of the questions, and
|
|
64
|
+
the output shape.
|
|
65
|
+
|
|
66
|
+
That brief overrides any habit of reviewing a diff for correctness alone.
|
|
67
|
+
`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const reviewerAgent: Rule = {
|
|
71
|
+
id: 'SK008',
|
|
72
|
+
title: 'reviewer agent',
|
|
73
|
+
owner: 'identity/how-we-work.md',
|
|
74
|
+
lint: (t, env) => {
|
|
75
|
+
const r = env.config.reviewer
|
|
76
|
+
if (r == null) return t.ok('no reviewer agent declared (repo.config reviewer:) — skipped')
|
|
77
|
+
const rel = reviewerAgentPath(r.name)
|
|
78
|
+
const p = join(env.root, rel)
|
|
79
|
+
if (briefMissing(env.root, r.brief)) t.fail(`the declared brief '${r.brief}' does not exist — every review's mandated first action would fail`)
|
|
80
|
+
if (!existsSync(p)) return t.fail(`${rel} missing — the mandated reviewer type is unavailable, so every review runs on whatever framing the spawner improvised`)
|
|
81
|
+
const issues = reviewerAgentIssues(readFileSync(p, 'utf8'), r)
|
|
82
|
+
for (const i of issues) t.fail(`${rel}: ${i}`)
|
|
83
|
+
if (issues.length === 0 && !t.failed) t.ok(`${rel} → ${r.brief}`)
|
|
84
|
+
},
|
|
85
|
+
// create-if-missing only: an existing definition may carry a repo's own
|
|
86
|
+
// tuned prompt, and silently rewriting that would cost more than the
|
|
87
|
+
// finding it repairs. Violations in a present file stay a loud FAIL.
|
|
88
|
+
fix: (env: RuleEnv): FixResult => {
|
|
89
|
+
const r = env.config.reviewer
|
|
90
|
+
if (r == null) return { changed: false, summary: 'no reviewer declared — nothing to write' }
|
|
91
|
+
const rel = reviewerAgentPath(r.name)
|
|
92
|
+
const p = join(env.root, rel)
|
|
93
|
+
if (existsSync(p)) return { changed: false, summary: `${rel} exists — remaining findings are about its content, fix them by hand` }
|
|
94
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
95
|
+
writeFileSync(p, canonicalAgent(r))
|
|
96
|
+
return { changed: true, summary: `${rel} written — points at ${r.brief}, no edit tools` }
|
|
97
|
+
},
|
|
98
|
+
}
|
|
@@ -10,6 +10,7 @@ import { gitignorePolicy } from './SK004-gitignore-policy.ts'
|
|
|
10
10
|
import { rvlibVersions } from './SK005-rvlib-versions.ts'
|
|
11
11
|
import { npmPublishSafety } from './SK006-npm-publish-safety.ts'
|
|
12
12
|
import { githubVisibility } from './SK007-github-visibility.ts'
|
|
13
|
+
import { reviewerAgent } from './SK008-reviewer-agent.ts'
|
|
13
14
|
|
|
14
15
|
export const RULES: readonly Rule[] = [
|
|
15
16
|
packageJsonShape,
|
|
@@ -19,6 +20,7 @@ export const RULES: readonly Rule[] = [
|
|
|
19
20
|
rvlibVersions,
|
|
20
21
|
npmPublishSafety,
|
|
21
22
|
githubVisibility,
|
|
23
|
+
reviewerAgent,
|
|
22
24
|
]
|
|
23
25
|
|
|
24
26
|
const seen = new Set<string>()
|