rvlib-shipkit 1.9.0 → 2.0.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 +1 -1
- package/src/check.ts +5 -2
- package/src/checks/SK001-package-json-shape.check.ts +47 -12
- package/src/checks/SK013-one-way-in-policy.check.ts +8 -7
- package/src/checks/SK017-vscode-hub-task.check.ts +109 -106
- package/src/checks/SK019-tests-run-in-units.check.ts +2 -1
- package/src/checks/SK027-agent-wiki-links.check.ts +5 -2
- package/src/checks/SK029-explicit-check-files.check.ts +25 -8
- package/src/checks/SK034-commit-gates-armed.check.ts +90 -0
- package/src/cli/cmd_Ci.ts +3 -3
- package/src/cli/cmd_Commands.ts +21 -5
- package/src/cli/cmd_Hooks.ts +6 -2
- package/src/cli/cmd_Init.ts +1 -1
- package/src/cli/cmd_Serve.ts +1 -2
- package/src/cli/cmd_Start.ts +1 -1
- package/src/cli.ts +2 -4
- package/src/core/budget.ts +6 -8
- package/src/core/bun-builtins.ts +36 -0
- package/src/core/bun-tool-cli.ts +23 -0
- package/src/core/canonical-scripts.ts +6 -4
- package/src/core/check-ctx.ts +104 -10
- package/src/core/check-file-names.ts +2 -3
- package/src/core/check-pass.ts +17 -1
- package/src/core/check-runner.ts +19 -6
- package/src/core/config-rewrite.ts +27 -6
- package/src/core/discover-checks.ts +30 -10
- package/src/core/floor-resolve.ts +16 -7
- package/src/core/job-run.ts +2 -2
- package/src/core/layout.ts +4 -1
- package/src/core/legacy-shapes.ts +40 -0
- package/src/core/one-lib.ts +1 -1
- package/src/core/one-way-in-policy.ts +106 -13
- package/src/core/pre-commit-discovery.ts +106 -0
- package/src/core/retired-checks.ts +2 -0
- package/src/core/run-ci.ts +16 -10
- package/src/core/run-fix.ts +9 -0
- package/src/core/run-fixers.ts +91 -35
- package/src/core/runs-matrix.ts +7 -0
- package/src/core/slots.ts +1 -1
- package/src/core/vendored-read.ts +39 -0
- package/src/core/vendored.ts +82 -0
- package/src/core/waivers.ts +125 -0
- package/src/core/when-block.ts +25 -185
- package/src/core/when.ts +30 -13
- package/src/core/wire.ts +17 -1
- package/src/git-bootstrap.ts +1 -1
- package/src/home/attach.ts +3 -5
- package/src/home/insights.ts +6 -1
- package/src/home/pm-config.ts +2 -2
- package/src/home/run-daemon.ts +2 -2
- package/src/home/run-unit-action.ts +2 -2
- package/src/hooks/commit-message.ts +2 -14
- package/src/hooks/hook-gates.ts +38 -0
- package/src/hooks/leak-guard.ts +8 -3
- package/src/hooks/one-way-in-guard.ts +8 -2
- package/src/hooks/root-identity.ts +9 -6
- package/src/hooks/run-hooks.ts +72 -41
- package/src/hooks/staged.ts +49 -0
- package/src/hooks/tree-shrink.ts +41 -0
- package/src/init-repo.ts +0 -42
- package/src/ledger/check-table.ts +1 -1
- package/src/ledger/gate-checks.ts +45 -9
- package/src/ledger/ledger.ts +6 -1
- package/src/ledger/repo-view.ts +14 -4
- package/src/ledger/toolchain-identity.ts +4 -2
- package/src/mv/mv-md.ts +6 -1
- package/src/problems/extract.ts +27 -5
- package/src/problems/problem-line.ts +2 -3
- package/src/release/run-release.ts +13 -8
- package/src/repo-config.ts +44 -27
- package/src/scaffold/files/bunfig.toml +11 -0
- package/src/scaffold/files/vscode-settings.json +1 -2
- package/src/scaffold/jsonc-spans.ts +16 -0
- package/src/scaffold/project-scaffold.ts +43 -77
- package/src/serve/hub-app/check-view.tsx +4 -4
- package/src/serve/hub-app/check-words.ts +1 -0
- package/src/serve/hub-app/checks-svc.ts +1 -2
- package/src/serve/hub-app/home.html +0 -1
- package/src/serve/hub-app/hub-types.ts +0 -2
- package/src/serve/hub-app/index.html +0 -1
- package/src/serve/hub-app/project-list.css +1 -2
- package/src/serve/hub-app/tree-build.ts +3 -2
- package/src/serve/hub-app/tree-svc.ts +2 -2
- package/src/serve/hub-port.ts +3 -3
- package/src/serve/hub-runtime.ts +0 -3
- package/src/serve/serve-hub.ts +1 -16
- package/src/serve/tracked-watch.ts +1 -1
- package/src/start-repo.ts +15 -3
- package/src/suite/judge-suites.ts +29 -28
- package/src/suite/select.ts +2 -0
- package/src/suite/suite-cli.ts +6 -3
- package/src/suite/suite-cpu-preload.ts +7 -11
- package/src/suite/test-units.ts +7 -4
- package/src/ts/config.ts +12 -5
- package/src/ts/imports.ts +3 -1
- package/src/tui/build-steps-panel.ts +1 -1
- package/src/wiki/scan-md.ts +16 -9
- package/src/checks/SK009-commit-ai-tells.check.ts +0 -131
- package/src/checks/SK014-prose-ste.check.ts +0 -78
- package/src/cli/cmd_Prose.ts +0 -74
- package/src/prose/prose-view.ts +0 -90
- package/src/prose/run-prose.ts +0 -187
- package/src/prose/ste.ts +0 -552
- package/src/scaffold/files/vscode-tasks.json +0 -24
- package/src/serve/hub-app/prose-hits-st.ts +0 -125
- package/src/serve/hub-app/prose-hits-view.tsx +0 -176
- package/src/serve/hub-app/prose-hits.css +0 -21
package/src/prose/run-prose.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// `shipkit prose` — the file-walking half of the STE checker. The judgement
|
|
2
|
-
// lives in ste.ts; this module resolves WHICH files a repo puts in scope,
|
|
3
|
-
// prints the wire lines, and applies the mechanical fixes.
|
|
4
|
-
//
|
|
5
|
-
// Scope is repo knowledge, never a hardcoded path list: a repo declares
|
|
6
|
-
// `prose: { include, exempt }` in repo.config.ts. A repo that declares
|
|
7
|
-
// nothing is not checked, and the rule says so instead of failing a repo
|
|
8
|
-
// that never opted in.
|
|
9
|
-
//
|
|
10
|
-
// It reads and writes ONLY through the check context it is handed, so rule
|
|
11
|
-
// SK014 is cached on the scope's listings and the files in it.
|
|
12
|
-
import { isAbsolute, relative, resolve } from 'path'
|
|
13
|
-
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
14
|
-
import { checkProse, fixProse, tierOf, type ProseClass, type ProseHit, type ProseTier, type ProseWords } from './ste.ts'
|
|
15
|
-
|
|
16
|
-
/** what the walker reads and writes through: a check context */
|
|
17
|
-
export type ProseDisk = Pick<CheckCtx, 'root' | 'read' | 'list' | 'write'>
|
|
18
|
-
|
|
19
|
-
export type ProseConfig = {
|
|
20
|
-
/** globs (repo-relative) that ARE house prose. Absent = the repo is not checked. */
|
|
21
|
-
include?: string[]
|
|
22
|
-
/** globs never checked: journals, archives, upstream snapshots, generated files */
|
|
23
|
-
exempt?: string[]
|
|
24
|
-
/** classes this repo does not check, with the reason living in repo.config.ts */
|
|
25
|
-
allow?: ProseClass[]
|
|
26
|
-
/** the 20-word procedural limit instead of the 25-word descriptive one */
|
|
27
|
-
strict?: boolean
|
|
28
|
-
/** this repo's own word lists over the house dictionary: `allow` drops
|
|
29
|
-
* every hit on a word or phrase, `ban` refuses an extra one (a hard hit,
|
|
30
|
-
* with its replacement). They bind this repo only, and the hub's SK014
|
|
31
|
-
* page writes them through a managed block inside this object */
|
|
32
|
-
words?: ProseWords
|
|
33
|
-
/** soft-tier ratchet: the count this repo is allowed to carry. The gate
|
|
34
|
-
* fails ABOVE it, so the number can only go down. Absent is the default,
|
|
35
|
-
* and it means soft hits warn in `shipkit prose` and never reach a gate
|
|
36
|
-
* or an attention surface. A four-digit count nobody can act on is worse
|
|
37
|
-
* than no number at all. */
|
|
38
|
-
baseline?: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type FileReport = {
|
|
42
|
-
file: string
|
|
43
|
-
hits: ProseHit[]
|
|
44
|
-
/** the file as it was judged, kept only when the caller asked, so a page
|
|
45
|
-
* can quote the line around each hit without reading the file twice */
|
|
46
|
-
text?: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type ProseReport = {
|
|
50
|
-
files: number
|
|
51
|
-
hits: ProseHit[]
|
|
52
|
-
byFile: FileReport[]
|
|
53
|
-
byTier: Record<ProseTier, number>
|
|
54
|
-
byClass: Record<string, number>
|
|
55
|
-
fixable: number
|
|
56
|
-
/** files rewritten by a --fix run */
|
|
57
|
-
fixed: string[]
|
|
58
|
-
scoped: boolean // did this repo declare any include globs?
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const DEFAULT_EXEMPT = ['**/node_modules/**', '**/CHANGELOG.md', '**/dist/**', '**/.git/**']
|
|
62
|
-
|
|
63
|
-
/** a generated file is not house prose, and rewriting it loses the change on
|
|
64
|
-
* the next render */
|
|
65
|
-
const isGenerated = (text: string): boolean => text.slice(0, 400).includes('<!-- generated')
|
|
66
|
-
|
|
67
|
-
/** an explicitly named path, resolved against the repo and REFUSED when it
|
|
68
|
-
* lands outside: `--fix` writes, and a checker that rewrites files outside
|
|
69
|
-
* the repo it was pointed at is a much worse tool than one that says no. */
|
|
70
|
-
export function explicitPath(root: string, p: string): string {
|
|
71
|
-
const abs = isAbsolute(p) ? p : resolve(root, p)
|
|
72
|
-
const rel = relative(root, abs)
|
|
73
|
-
if (rel === '' || rel.startsWith('..')) throw new Error(`FAIL ${p} is outside ${root} — shipkit prose only reads and writes inside the repo`)
|
|
74
|
-
return rel
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** does `path` match `pattern` under the dot rule: a hidden segment (one
|
|
78
|
-
* starting with a dot) only where the pattern names a hidden segment itself,
|
|
79
|
-
* never inside a `**` or under a wildcard segment */
|
|
80
|
-
export function hiddenAllowed(pattern: string, path: string): boolean {
|
|
81
|
-
const pat = pattern.split('/')
|
|
82
|
-
const segs = path.split('/')
|
|
83
|
-
const hidden = (s: string): boolean => s.startsWith('.') && s !== '.' && s !== '..'
|
|
84
|
-
const star = pat.indexOf('**')
|
|
85
|
-
const head = star === -1 ? pat : pat.slice(0, star)
|
|
86
|
-
const tail = star === -1 ? [] : pat.slice(pat.lastIndexOf('**') + 1)
|
|
87
|
-
for (let i = 0; i < segs.length; i++) {
|
|
88
|
-
if (!hidden(segs[i]!)) continue
|
|
89
|
-
const fromEnd = segs.length - 1 - i
|
|
90
|
-
const p = i < head.length ? head[i] : fromEnd < tail.length ? tail[tail.length - 1 - fromEnd] : undefined
|
|
91
|
-
if (p == null || !p.startsWith('.')) return false
|
|
92
|
-
}
|
|
93
|
-
return true
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** the files in scope: what git sees (tracked, or untracked and not ignored)
|
|
97
|
-
* matching an include glob, the dot rule applied, minus every exempt glob */
|
|
98
|
-
export function proseFiles(d: Pick<ProseDisk, 'list'>, cfg: ProseConfig): string[] {
|
|
99
|
-
const include = cfg.include ?? []
|
|
100
|
-
if (include.length === 0) return []
|
|
101
|
-
const exempt = [...DEFAULT_EXEMPT, ...(cfg.exempt ?? [])].map((g) => new Bun.Glob(g))
|
|
102
|
-
const seen = new Set<string>()
|
|
103
|
-
for (const pattern of include)
|
|
104
|
-
for (const rel of d.list(pattern)) {
|
|
105
|
-
if (!hiddenAllowed(pattern, rel)) continue
|
|
106
|
-
if (exempt.some((g) => g.match(rel))) continue
|
|
107
|
-
seen.add(rel)
|
|
108
|
-
}
|
|
109
|
-
return [...seen].sort()
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function runProseCheck(o: { c: ProseDisk; config: ProseConfig; paths?: string[]; fix?: boolean; keepText?: boolean }): ProseReport {
|
|
113
|
-
const cfg = o.config
|
|
114
|
-
const d = o.c
|
|
115
|
-
const files = o.paths != null && o.paths.length > 0 ? o.paths.map((p) => explicitPath(d.root, p)) : proseFiles(d, cfg)
|
|
116
|
-
const explicit = o.paths != null && o.paths.length > 0
|
|
117
|
-
const opts = { allow: cfg.allow, strict: cfg.strict, words: cfg.words }
|
|
118
|
-
const byFile: FileReport[] = []
|
|
119
|
-
const hits: ProseHit[] = []
|
|
120
|
-
const fixed: string[] = []
|
|
121
|
-
for (const rel of files) {
|
|
122
|
-
let text: string | null
|
|
123
|
-
try {
|
|
124
|
-
text = d.read(rel)
|
|
125
|
-
} catch (e) {
|
|
126
|
-
text = null
|
|
127
|
-
if (explicit) throw new Error(`FAIL cannot read ${rel} — ${String(e)}`)
|
|
128
|
-
}
|
|
129
|
-
if (text == null) {
|
|
130
|
-
// a path a HUMAN typed that cannot be read is a loud failure. A
|
|
131
|
-
// path the listing produced can vanish between the scan and the
|
|
132
|
-
// read, and skipping that one is right.
|
|
133
|
-
if (explicit) throw new Error(`FAIL cannot read ${rel} — no such file`)
|
|
134
|
-
continue
|
|
135
|
-
}
|
|
136
|
-
if (isGenerated(text)) continue
|
|
137
|
-
if (o.fix === true) {
|
|
138
|
-
const out = fixProse(text, opts)
|
|
139
|
-
if (out.applied > 0) {
|
|
140
|
-
d.write(rel, out.text)
|
|
141
|
-
fixed.push(rel)
|
|
142
|
-
text = out.text
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const fh = checkProse(text, opts)
|
|
146
|
-
if (fh.length > 0) byFile.push({ file: rel, hits: fh, ...(o.keepText === true ? { text } : {}) })
|
|
147
|
-
hits.push(...fh)
|
|
148
|
-
}
|
|
149
|
-
const byTier: Record<ProseTier, number> = { fix: 0, hard: 0, soft: 0 }
|
|
150
|
-
const byClass: Record<string, number> = {}
|
|
151
|
-
for (const h of hits) {
|
|
152
|
-
byTier[h.tier]++
|
|
153
|
-
byClass[h.cls] = (byClass[h.cls] ?? 0) + 1
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
files: files.length,
|
|
157
|
-
hits,
|
|
158
|
-
byFile,
|
|
159
|
-
byTier,
|
|
160
|
-
byClass,
|
|
161
|
-
fixable: hits.filter((h) => h.fix != null).length,
|
|
162
|
-
fixed,
|
|
163
|
-
scoped: (cfg.include ?? []).length > 0,
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** one line per hit, capped: a first STE pass on a real repo finds hundreds,
|
|
168
|
-
* and a wall of them hides the mechanical ones that `--fix` clears in a
|
|
169
|
-
* second. The per-class totals below the cap keep the report complete. */
|
|
170
|
-
export function proseLines(r: ProseReport, o: { cap?: number; tiers?: ProseTier[] } = {}): string[] {
|
|
171
|
-
const cap = o.cap ?? 25
|
|
172
|
-
const tiers = new Set(o.tiers ?? (['fix', 'hard', 'soft'] as ProseTier[]))
|
|
173
|
-
const shown = r.hits.filter((h) => tiers.has(h.tier))
|
|
174
|
-
const out: string[] = []
|
|
175
|
-
const fileOf = new Map<ProseHit, string>()
|
|
176
|
-
for (const f of r.byFile) for (const h of f.hits) fileOf.set(h, f.file)
|
|
177
|
-
for (const h of shown.slice(0, cap))
|
|
178
|
-
out.push(`${fileOf.get(h) ?? '?'}:${h.line} [${h.cls}] ${JSON.stringify(h.text.trim())} — ${h.why}${h.fix != null ? ` (--fix writes ${JSON.stringify(h.fix.trim())})` : ''}`)
|
|
179
|
-
if (shown.length > cap) out.push(`… and ${shown.length - cap} more`)
|
|
180
|
-
return out
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export const classSummary = (r: ProseReport): string =>
|
|
184
|
-
Object.entries(r.byClass)
|
|
185
|
-
.sort((a, b) => b[1] - a[1])
|
|
186
|
-
.map(([c, n]) => `${c} ${n}${tierOf(c as ProseClass) === 'soft' ? '' : '!'}`)
|
|
187
|
-
.join(', ')
|