rvlib-shipkit 0.11.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 +200 -0
- package/README.md +17 -3
- package/package.json +6 -3
- package/src/asks/asks-queue.ts +8 -0
- package/src/assets/kit-config.ts +2 -3
- package/src/assets/roll-workspace.ts +2 -1
- package/src/assets/run-assets.ts +0 -11
- package/src/assets/server/studio-server.ts +7 -24
- package/src/cli/cmd_Assets.ts +3 -14
- package/src/cli/cmd_Check.ts +22 -4
- package/src/cli/cmd_Reflect.ts +116 -0
- package/src/cli/cmd_Rules.ts +32 -0
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli.ts +5 -2
- package/src/collections/actions.ts +19 -0
- package/src/collections/collection-types.ts +13 -0
- package/src/collections/graphs.ts +34 -0
- package/src/collections/pings.ts +5 -1
- package/src/collections/posts.ts +20 -1
- package/src/collections/providers.ts +163 -50
- package/src/contracts/apply-contracts.ts +47 -0
- 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/contracts/run-contracts.ts +8 -3
- package/src/core/canonical-scripts.ts +46 -0
- package/src/core/check-suite.ts +6 -3
- package/src/core/locked-write.ts +39 -0
- package/src/core/motherhome.ts +87 -0
- package/src/core/run-check.ts +28 -15
- package/src/core/run-ci.ts +4 -0
- package/src/core/write-status.ts +7 -26
- package/src/init-repo.ts +27 -11
- package/src/reflect/prompt-corpus.ts +305 -0
- package/src/repo-config.ts +30 -38
- package/src/rules/SK001-package-json-shape.ts +156 -0
- package/src/rules/SK002-contracts-fulfillness.ts +19 -0
- package/src/rules/SK003-claude-md-shape.ts +64 -0
- package/src/rules/SK004-gitignore-policy.ts +105 -0
- package/src/rules/SK005-rvlib-versions.ts +99 -0
- package/src/rules/SK006-npm-publish-safety.ts +42 -0
- package/src/rules/SK007-github-visibility.ts +63 -0
- package/src/rules/SK008-reviewer-agent.ts +98 -0
- package/src/rules/rule-registry.ts +34 -0
- package/src/rules/rule.ts +62 -0
- package/src/rules/run-fix.ts +57 -0
- package/src/serve/hub-app/col-card.tsx +192 -0
- package/src/serve/hub-app/cols-svc.ts +136 -0
- package/src/serve/hub-app/geometry-view.tsx +155 -0
- package/src/serve/hub-app/health-svc.ts +49 -0
- package/src/serve/hub-app/hub-app.tsx +236 -215
- package/src/serve/hub-app/hub-st.ts +169 -0
- package/src/serve/hub-app/hub-types.ts +72 -0
- package/src/serve/hub-app/hub.css +286 -59
- package/src/serve/hub-app/index.html +1 -1
- package/src/serve/hub-app/md.ts +24 -0
- package/src/serve/hub-app/media-svc.ts +429 -0
- package/src/serve/hub-app/media-view.tsx +299 -0
- package/src/serve/run-serve.ts +23 -9
- package/src/serve/serve-hub.ts +62 -47
- package/src/shipkit.ts +9 -5
- package/src/assets/server/studio-page.ts +0 -687
- package/src/core/package-json-check.ts +0 -88
- package/src/serve/hub-pages.ts +0 -38
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,205 @@
|
|
|
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
|
+
|
|
37
|
+
## 0.13.0 — 2026-07-26
|
|
38
|
+
|
|
39
|
+
Stable rule ids. Every built-in invariant `shipkit check` enforces is now
|
|
40
|
+
an SK rule — one module per rule (`src/rules/SK###-<slug>.ts`), one
|
|
41
|
+
registry (`rule-registry.ts`, throws at import on a duplicate or malformed
|
|
42
|
+
id), ids never reused or renumbered. The two existing built-ins migrated
|
|
43
|
+
whole: SK001 package.json shape (was `core/package-json-check.ts`, deleted),
|
|
44
|
+
SK002 `.rv-*` contracts fulfillness. Line text changed on every consumer:
|
|
45
|
+
rule-emitted `ok:`/`WARN`/`FAIL:`/`skip:` lines now carry a `[SK###]` tag
|
|
46
|
+
after the wire prefix — on `check`, `shipkit contracts`, AND the ci
|
|
47
|
+
`contracts` task (one tagged rendering path,
|
|
48
|
+
`contracts/apply-contracts.ts`; `contractsConfigFrom`/`activeContracts`/
|
|
49
|
+
`applyContractsResult` moved there, still exported from the barrel). A
|
|
50
|
+
failing/warning rule always carries at least one `fix:` pointer (the
|
|
51
|
+
framework appends `docsBase + rule.owner` when the lint attached none).
|
|
52
|
+
New command: `shipkit rules [--json]` — the id catalog (plain lines, not
|
|
53
|
+
wire). Wire prefixes themselves are unchanged; matchers keying on prefixes
|
|
54
|
+
are unaffected.
|
|
55
|
+
|
|
56
|
+
`shipkit check --fix [SK### …]`: rules may carry an idempotent `fix()` —
|
|
57
|
+
apply, re-lint, show the real end state; never cached, never under ci;
|
|
58
|
+
unknown/unfixable ids fail loud. First fixers: SK001 (private:true, license
|
|
59
|
+
removal, canonical scripts from the ONE shared module
|
|
60
|
+
`core/canonical-scripts.ts` that `shipkit init` also scaffolds from —
|
|
61
|
+
bodies the vocabulary page doesn't state are named in the summary, never
|
|
62
|
+
invented; rvlib-* roots refused), SK003 CLAUDE.md include-index (new rule:
|
|
63
|
+
prose/extra lines/inverted order/untracked-CLAUDE.local.md FAIL; fix routes
|
|
64
|
+
a missing `@./agent/index.md` on an EXISTING file, never creates one),
|
|
65
|
+
SK004 `.gitignore` policy block (new rule: marker-keyed managed block of
|
|
66
|
+
the always-gitignored contract surfaces + tmp/; a well-formed block is
|
|
67
|
+
rewritten IN PLACE so below-block `!`-overrides keep winning; CRLF files
|
|
68
|
+
keep their EOL; repo lines outside the block never touched;
|
|
69
|
+
`defineRepo({ gitignore: [...] })` contributes repo lines INSIDE the
|
|
70
|
+
block, for names a published lib may not carry; `shipkit init` — fresh or
|
|
71
|
+
re-run on an existing repo — writes it through the same fixer). A failing
|
|
72
|
+
rule's fix pointers advertise `autofix: shipkit check --fix SK###`; the
|
|
73
|
+
post-fix re-lint strips the advertisement and an unchanged fix reports as
|
|
74
|
+
a note, not a WARN; `--fix` rejects `--json` and closes with a count of
|
|
75
|
+
rules no fixer covers. SK001's fixer refuses field flips on anything
|
|
76
|
+
publish-marked (files/exports/publishConfig/prepublishOnly) and never
|
|
77
|
+
duplicates a script a banned alias already satisfies.
|
|
78
|
+
|
|
79
|
+
SK005 rvlib versions: every `rvlib-*` dep is compared (real semver order,
|
|
80
|
+
so a dist-tag pointing BEHIND the installed version is reported, never a
|
|
81
|
+
silent downgrade) to npm latest on each `shipkit check` (rules may now
|
|
82
|
+
declare `ttl` — SK005 caches clean passes 24h; registry unreachable = loud
|
|
83
|
+
skip, never red; a `link:` dep that does not resolve is a FAIL instead of
|
|
84
|
+
a cryptic import crash). When a newer version is live the rule
|
|
85
|
+
SELF-UPDATES: `bun update --latest`, run by the lint itself, only when
|
|
86
|
+
package.json/lockfile are git-clean (else WARN deferred), never under ci;
|
|
87
|
+
the applied update itself reports as WARN — the repo was mutated and a
|
|
88
|
+
restart+commit is owed, so it lands on the attention surface and is never
|
|
89
|
+
cached. New `shipkit check --frozen` (or standing `SHIPKIT_FROZEN=1`)
|
|
90
|
+
reports drift without touching anything; the flag threads through
|
|
91
|
+
`CheckOpts` so doctor/TUI/hub callers can pass it too. Committing the bump
|
|
92
|
+
stays with the working agent — shipkit never commits. Suite-level fix
|
|
93
|
+
riding along: a TTL'd check that emitted a `skip` line is no longer
|
|
94
|
+
cached — "only clean passes replay" is now literally true (a dead
|
|
95
|
+
registry was being restated as fact for 24h).
|
|
96
|
+
|
|
97
|
+
SK006 npm publish safety: a `private: true` package carrying publishConfig
|
|
98
|
+
/ prepublish* scripts / an npm-publishing `publish` script FAILs — the
|
|
99
|
+
machinery is dead weight while private holds and a loaded gun the day it
|
|
100
|
+
flips; rvlib-* roots exempt; lint-only on purpose (a `publish` script may
|
|
101
|
+
deploy a site). SK007 github visibility: new repo.config
|
|
102
|
+
`visibility: 'private' | 'public'` (absent = private, defineRepo validates)
|
|
103
|
+
vs `gh repo view --json isPrivate` — mismatch FAILs in BOTH directions
|
|
104
|
+
(public-while-declared-private is the leak case), 7d clean-pass cache,
|
|
105
|
+
every unverifiable path (no gh, no remote, gh error) a loud skip that is
|
|
106
|
+
never red and never cached.
|
|
107
|
+
|
|
108
|
+
Motherhome reporting: `check`/`ci` (and `init` on an already-configured
|
|
109
|
+
repo) upsert one row for the repo — name, path, version (`+src` suffix
|
|
110
|
+
when consumed by path/link), npm|link|file|path mode, last-seen day —
|
|
111
|
+
into `<docsBase>/build/shipkit/local-projects.md`, ONLY when that page
|
|
112
|
+
already exists on this disk. The lib never creates the page and never
|
|
113
|
+
writes into a relative or URL docsBase — a consumer using docsBase as a
|
|
114
|
+
plain fix-pointer prefix will never find surprise files in its docs tree.
|
|
115
|
+
Whole read-modify-write under a file lock with atomic rename
|
|
116
|
+
(core/locked-write, extracted from write-status — one lock concern),
|
|
117
|
+
dead-path rows pruned, data-change-or->7d churn control, every failure a
|
|
118
|
+
WARN note that never breaks the calling command.
|
|
119
|
+
|
|
120
|
+
## 0.12.0 — 2026-07-23
|
|
121
|
+
|
|
122
|
+
ONE server, ONE page. `shipkit serve` runs the repo's single `Bun.serve`,
|
|
123
|
+
and `/` is the single surface: a mobx+react app (Bun HTML import, no build
|
|
124
|
+
step) holding the full media atelier (rolls, compare, pick, geometry
|
|
125
|
+
overlay, custom panels, uploads, generate queue) plus foldable sections
|
|
126
|
+
for asks, creds, posts, health (STATUS.md + streaming check runner) and
|
|
127
|
+
graphs (each lint-checked), with a segmented severity filter (all · needs ·
|
|
128
|
+
red) at `/?f=needs` / `/?f=red` (legacy `/?needs=1` reads as needs) and
|
|
129
|
+
hash deep-links (`#<asset-id>` / `#cat-<Category>`).
|
|
130
|
+
Kit-less repos serve the same page with no media section. Graph canvases
|
|
131
|
+
stay as drill-down pages (`/graph/wiki`, `/graph/agent`) with a home link.
|
|
132
|
+
|
|
133
|
+
New:
|
|
134
|
+
|
|
135
|
+
- Navigation on the one page: a thin icon RAIL that scrolls/unfolds to a
|
|
136
|
+
section (red dot when it holds a red item — never a separate page), a
|
|
137
|
+
sidebar SEARCH filtering rows and grid cards, a `cmd+j` palette jumping
|
|
138
|
+
to any item or section.
|
|
139
|
+
- Instances carry `md` (truth-file markdown body, frontmatter stripped,
|
|
140
|
+
4000-char cap): cards render it via marked (GFM — tables render) behind
|
|
141
|
+
a sanitize-html allowlist — clamped in the grid, full in the item view.
|
|
142
|
+
- Secret cards presented properly: mono name, service line,
|
|
143
|
+
present/missing chip, mint link; copy block only when missing.
|
|
144
|
+
- Roll strips: newest first with a stable tiebreak; the generating
|
|
145
|
+
spinner tile sits at the START of the strip.
|
|
146
|
+
- Every section has BOTH views: title → collection view (one card per
|
|
147
|
+
instance in the stage), row/card → item view with a back link. A graph's
|
|
148
|
+
item view EMBEDS the graph (iframe of /graph/*), never a bare link.
|
|
149
|
+
- Sections show everything, always: asks include the done/ archive
|
|
150
|
+
(quiet, remarkable — consult past answers), the secrets section lists
|
|
151
|
+
every repo-declared cred (present = quiet, missing = red with the mint
|
|
152
|
+
artifact); section headers render even at 0 items. "needs" is THE
|
|
153
|
+
waiting-on-me filter, "red" the hard-blocked cut.
|
|
154
|
+
- Watch mode is the house default for the hub script
|
|
155
|
+
(`bun --watch … serve`, `shipkit init` scaffolds it): lib/config edits
|
|
156
|
+
restart the server live. Accepted tradeoff: a restart drops in-memory
|
|
157
|
+
queue state (`assets fill` recovers).
|
|
158
|
+
- `serve.publicHost` (repo.config): off-machine hostname printed beside
|
|
159
|
+
localhost and appended to ping messages — a phone can't resolve
|
|
160
|
+
localhost.
|
|
161
|
+
- `/api/collections.json`: browse-all — every instance of every
|
|
162
|
+
collection, healthy included (quiet tiles), rendered as foldable
|
|
163
|
+
sections with red/yellow dots.
|
|
164
|
+
- Trust surface: instances carry `file` (the git file the tile IS — shown
|
|
165
|
+
on the card; the file always wins) and `img` (thumbnails via the media
|
|
166
|
+
API).
|
|
167
|
+
- Remarks: cards AND the asset view have a remark box; `/api/act/remark`
|
|
168
|
+
routes the text into the item's own file (post `## Feedback`, the ask
|
|
169
|
+
file, or `.rv-assets/notes.md` for media) and it returns as `remarks`
|
|
170
|
+
on the instance — `shipkit needs --json` briefs agents on them at boot.
|
|
171
|
+
- Posted/rejected post history is VISIBLE (quiet), no longer hidden;
|
|
172
|
+
media collections list ALL kit assets, not only needing ones.
|
|
173
|
+
- `shipkit reflect [repo…] [--short --out <dir> --json]`: your prompts
|
|
174
|
+
across every Claude Code session of a repo, chronological md corpus for a
|
|
175
|
+
retrospective. Corpus fidelity is the whole feature — it unions EVERY
|
|
176
|
+
config root on the machine (`$CLAUDE_CONFIG_DIR` plus `~/.claude` plus
|
|
177
|
+
sibling `~/.claude-<account>`; honoring only the env var hides the account
|
|
178
|
+
you are not currently running as), scans worktree session dirs, includes
|
|
179
|
+
the prompts you QUEUED while the agent was working (`queue-operation`
|
|
180
|
+
entries, marked `origin: 'queued'` — they carry mid-run corrections and
|
|
181
|
+
standing instructions, and when a run ends first they never become user
|
|
182
|
+
messages at all), drops harness-authored user entries (tool results,
|
|
183
|
+
command wrappers, compaction summaries, subagent prompts, SDK-driven
|
|
184
|
+
sessions), rescues the text you typed inside a slash command, strips
|
|
185
|
+
reminder blocks appended inside real prompts, recovers pruned sessions
|
|
186
|
+
from `sessions-index.json`, and removes only MECHANICAL copies — a
|
|
187
|
+
replayed entry, or a queued prompt arriving again as a message — so real
|
|
188
|
+
repetition (saying it twice) survives in the corpus. `--json` emits one
|
|
189
|
+
shape whatever the run looked like: `[{repo, prompts, error?}]`.
|
|
190
|
+
|
|
191
|
+
BREAKING (0.11.0 → 0.12.0):
|
|
192
|
+
|
|
193
|
+
- `shipkit assets serve` DELETED — the studio UI rides the hub page; the
|
|
194
|
+
media API mounts at `/media/*` (was `/assets/*` under the hub).
|
|
195
|
+
- `serveStudio`, `Studio`, `studioPage`, `withRail` exports DELETED
|
|
196
|
+
(`studioRoutes` stays — it IS the media API).
|
|
197
|
+
- `kit.port` DELETED from `defineKit` (nothing standalone listens anymore).
|
|
198
|
+
- `/status` and `/assets/` pages DELETED (404, no redirect); the check
|
|
199
|
+
stream moved `/status/run` → `/api/check/run`; the vanilla
|
|
200
|
+
template-string studio SPA and the injected rail are gone — the react
|
|
201
|
+
app is the only UI code path.
|
|
202
|
+
|
|
3
203
|
## 0.11.0 — 2026-07-23
|
|
4
204
|
|
|
5
205
|
The needs feed: the hub's `/` now answers "what needs a human right now,
|
package/README.md
CHANGED
|
@@ -34,16 +34,30 @@ shipkit ci commit gate: floor (lint/typecheck/test/imports/cycles —
|
|
|
34
34
|
+ your tasks, parallel DAG; refreshes STATUS.md
|
|
35
35
|
[--bail --skip g --only id --json]
|
|
36
36
|
shipkit contracts release-contract fulfillness alone [--venue v --json]
|
|
37
|
-
shipkit check periodic health: every *.check.ts in
|
|
38
|
-
auto-discovered + TTL-cached
|
|
37
|
+
shipkit check periodic health: built-in SK rules + every *.check.ts in
|
|
38
|
+
the repo, auto-discovered + TTL-cached
|
|
39
|
+
[--json --fresh --frozen --fix [SK### …]]
|
|
40
|
+
--fix applies rule autofixes then re-lints; --frozen =
|
|
41
|
+
self-maintaining rules (rvlib self-update) report only
|
|
42
|
+
shipkit rules the SK rule catalog: id, what it lints, owning doc,
|
|
43
|
+
fixable? [--json]
|
|
39
44
|
shipkit tui live dashboard: ci tasks + discovered checks + panels
|
|
40
45
|
the repo injects via repo.config tui.panels
|
|
41
46
|
shipkit asks human action queue: list · ask "<title>" · asks check (auto-close)
|
|
47
|
+
shipkit needs everything blocked on a human, red/yellow by collection
|
|
48
|
+
report-only, exit 0 always [--json]
|
|
42
49
|
shipkit wiki serve|lint — live md link-graph + orphan/dangling-ref lint
|
|
50
|
+
shipkit mv rename an md page + its sibling dir, sweeping every ref
|
|
43
51
|
shipkit assets serve|fill|add|gen|identity|check — the asset atelier
|
|
44
52
|
(declare kinds in .rv-assets/kit.ts, roll, curate, ship)
|
|
53
|
+
shipkit serve the repo's ONE server: hub page, graphs, studio, status
|
|
54
|
+
shipkit reflect your prompts across every Claude Code session of a repo:
|
|
55
|
+
all config roots, queued-mid-run ones included, harness
|
|
56
|
+
noise out, chronological md corpus for a retrospective
|
|
57
|
+
[repo…] [--short --out <dir> --json]
|
|
45
58
|
shipkit init scaffold config + scripts, print the resolved floor
|
|
46
59
|
[--bin <cli.ts path> for in-repo-source consumers]
|
|
60
|
+
shipkit start ensure <root>/<name> + its CLAUDE.md include; prints the dir
|
|
47
61
|
```
|
|
48
62
|
|
|
49
63
|
Output is a stable line protocol — `ok:` / `WARN` / `FAIL:` / `skip:` with
|
|
@@ -67,7 +81,7 @@ Ship your own contracts via `contracts: { extra: [defineContract({...})] }`
|
|
|
67
81
|
Everything the CLI does is importable: `runTasks`, `CheckSuite`,
|
|
68
82
|
`evaluateContracts`, the asks queue, `scanMdGraph`/`lintMd`/`serveMdGraph`,
|
|
69
83
|
and the assets surface (`defineKit`/`defineKind`, `identityKinds`,
|
|
70
|
-
`
|
|
84
|
+
`studioRoutes`, `saveEndpoint` via `rvlib-shipkit/vite`).
|
|
71
85
|
Ships TypeScript source; Bun ≥ 1.0.
|
|
72
86
|
|
|
73
87
|
MIT
|
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",
|
|
@@ -21,17 +21,20 @@
|
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/bun": "^1.3.14",
|
|
23
23
|
"@types/react": "^19.2.17",
|
|
24
|
-
"@types/react-dom": "^19.2.3"
|
|
24
|
+
"@types/react-dom": "^19.2.3",
|
|
25
|
+
"@types/sanitize-html": "^2.16.1"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@napi-rs/canvas": "^1.0.2",
|
|
28
29
|
"clipanion": "^4.0.0-rc.4",
|
|
29
30
|
"ink": "^7.1.1",
|
|
31
|
+
"marked": "^18.0.7",
|
|
30
32
|
"mobx": "^6.16.1",
|
|
31
33
|
"mobx-react-lite": "^4.1.1",
|
|
32
34
|
"react": "^19.2.7",
|
|
33
35
|
"react-dom": "^19.2.7",
|
|
34
|
-
"rvlib-ts-tools": "^0.
|
|
36
|
+
"rvlib-ts-tools": "^0.3.0",
|
|
37
|
+
"sanitize-html": "^2.17.6",
|
|
35
38
|
"sharp": "^0.35.3"
|
|
36
39
|
}
|
|
37
40
|
}
|
package/src/asks/asks-queue.ts
CHANGED
|
@@ -45,6 +45,14 @@ export function openAsks(dir: string): Ask[] {
|
|
|
45
45
|
return listAsks(dir).filter((a) => a.status !== 'done')
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** the <dir>/done archive, newest-first — READ-ONLY: never mints the dir
|
|
49
|
+
* (numeric filename prefixes are creation order, so reverse-sort = newest) */
|
|
50
|
+
export function doneAsks(dir: string): Ask[] {
|
|
51
|
+
const done = join(dir, 'done')
|
|
52
|
+
if (!existsSync(done)) return []
|
|
53
|
+
return readdirSync(done).filter((f) => f.endsWith('.md')).sort().reverse().map((f) => parse(done, f))
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
export function checkAsks(dir: string, print: (line: string) => void = console.log): { closed: number } {
|
|
49
57
|
const done = join(dir, 'done')
|
|
50
58
|
mkdirSync(done, { recursive: true })
|
package/src/assets/kit-config.ts
CHANGED
|
@@ -112,14 +112,13 @@ export type Kind = {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export type KitConfig = {
|
|
115
|
-
port?: number
|
|
116
115
|
/** roll workspace root, repo-root-relative; gitignored */
|
|
117
116
|
workspace?: string
|
|
118
117
|
comfy?: { host: string; workflows: string; heal?: string }
|
|
119
118
|
kinds: Kind[]
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
export type ResolvedKit = KitConfig & {
|
|
121
|
+
export type ResolvedKit = KitConfig & { workspace: string }
|
|
123
122
|
|
|
124
123
|
export function validateKit(k: KitConfig): string[] {
|
|
125
124
|
const errs: string[] = []
|
|
@@ -190,7 +189,7 @@ export function missingPanelScripts(kit: KitConfig, root: string): string[] {
|
|
|
190
189
|
export function defineKit(k: KitConfig): ResolvedKit {
|
|
191
190
|
const errs = validateKit(k)
|
|
192
191
|
if (errs.length > 0) throw new Error(`invalid kit:\n ${errs.join('\n ')}`)
|
|
193
|
-
return { ...k,
|
|
192
|
+
return { ...k, workspace: k.workspace ?? '.rv-assets/out' }
|
|
194
193
|
}
|
|
195
194
|
|
|
196
195
|
export const defineKind = (k: Kind): Kind => k
|
|
@@ -25,7 +25,8 @@ export function listRolls(dir: string): string[] {
|
|
|
25
25
|
if (!existsSync(dir)) return []
|
|
26
26
|
return readdirSync(dir)
|
|
27
27
|
.filter(isRollFile)
|
|
28
|
-
|
|
28
|
+
// newest first; filename tiebreak — same-second batches must not shuffle between polls
|
|
29
|
+
.sort((a, b) => statSync(join(dir, b)).mtimeMs - statSync(join(dir, a)).mtimeMs || a.localeCompare(b))
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export const pickPath = (dir: string): string => join(dir, 'pick.json')
|
package/src/assets/run-assets.ts
CHANGED
|
@@ -10,8 +10,6 @@ import { loadWorkflows } from './gen/workflow-loader.ts'
|
|
|
10
10
|
import { allAssets, findAsset, loadKit, type ResolvedKit } from './kit-config.ts'
|
|
11
11
|
import { NO_PICK_FIX, renderIdentity, type Brand } from './kits/identity-kit.ts'
|
|
12
12
|
import { assetDir, listRolls, readPick, readPromptOverlay } from './roll-workspace.ts'
|
|
13
|
-
import { page } from './server/studio-page.ts'
|
|
14
|
-
import { serveStudio } from './server/studio-server.ts'
|
|
15
13
|
|
|
16
14
|
export type Print = (line: string) => void
|
|
17
15
|
export type AssetsIo = { print: Print; printErr: Print }
|
|
@@ -61,15 +59,6 @@ async function guarded(io: AssetsIo, body: () => Promise<number>): Promise<numbe
|
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
export const runAssetsServe = (root: string, io: AssetsIo): Promise<number> =>
|
|
65
|
-
guarded(io, async () => {
|
|
66
|
-
const kit = await loadKit(root)
|
|
67
|
-
const studio = await serveStudio({ kit, root, page })
|
|
68
|
-
io.print(`ok: studio → http://127.0.0.1:${studio.server.port} (${allAssets(kit).length} assets)`)
|
|
69
|
-
await new Promise(() => {}) // Bun.serve owns the process — never resolve
|
|
70
|
-
return 0
|
|
71
|
-
})
|
|
72
|
-
|
|
73
62
|
export const runAssetsTopUp = (root: string, io: AssetsIo, o: { mode: 'fill' | 'add'; n: number; categories: string[] }): Promise<number> =>
|
|
74
63
|
guarded(io, async () => {
|
|
75
64
|
const kit = await loadKit(root)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The studio HTTP server — roll variants, compare, pick, geometry metadata,
|
|
2
|
-
// category grid data. 127.0.0.1 ONLY, forever.
|
|
3
|
-
//
|
|
2
|
+
// category grid data. 127.0.0.1 ONLY, forever. Watch-mode restarts drop the
|
|
3
|
+
// in-memory queue — accepted tradeoff (his order 2026-07-23); `shipkit
|
|
4
|
+
// assets fill` is the recovery path.
|
|
4
5
|
//
|
|
5
6
|
// ids/names are validated against the kit + the roll-file regex — no path
|
|
6
7
|
// from the client ever reaches the filesystem unchecked.
|
|
@@ -20,14 +21,11 @@ import { StudioQueue } from './studio-queue.ts'
|
|
|
20
21
|
export type StudioDeps = {
|
|
21
22
|
kit: ResolvedKit
|
|
22
23
|
root: string
|
|
23
|
-
page: () => string
|
|
24
24
|
/** test seam — defaults to the real engines. `context` = a derived
|
|
25
25
|
* asset's resolved parent picks (queue resolves it at run time) */
|
|
26
26
|
generateOne?: (found: FoundAsset, prompt: string, seed: number, destDir: string, onProgress?: OnProgress, context?: ParentContext) => Promise<void>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export type Studio = { server: ReturnType<typeof Bun.serve>; queue: StudioQueue; stop: () => void }
|
|
30
|
-
|
|
31
29
|
// what the studio detail view shows about an asset's plumbing (W2-2)
|
|
32
30
|
export type AssetInfo = {
|
|
33
31
|
backend: 'canvas' | 'comfy' | 'manual' | 'gemini'
|
|
@@ -42,9 +40,9 @@ export type AssetInfo = {
|
|
|
42
40
|
kitSource: string
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
// the studio's route table without a server: `handle` answers the
|
|
46
|
-
//
|
|
47
|
-
//
|
|
43
|
+
// the studio's route table without a server: `handle` answers the media
|
|
44
|
+
// path space ('/api/…', '/img', '/panel/…'); the hub — the repo's ONE
|
|
45
|
+
// server — mounts it under /media/*. The page itself is the hub's react app.
|
|
48
46
|
export type StudioRoutes = { handle: (req: Request, pathname: string) => Promise<Response>; queue: StudioQueue }
|
|
49
47
|
|
|
50
48
|
export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
|
|
@@ -220,8 +218,6 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
|
|
|
220
218
|
const q = (k: string): string => url.searchParams.get(k) ?? ''
|
|
221
219
|
const body = async (): Promise<Record<string, unknown>> => (await req.json().catch(() => ({}))) as Record<string, unknown>
|
|
222
220
|
|
|
223
|
-
if (pathname === '/') return new Response(deps.page(), { headers: { 'Content-Type': 'text/html; charset=utf-8' } })
|
|
224
|
-
|
|
225
221
|
if (pathname === '/api/state') {
|
|
226
222
|
const assets = await Promise.all(allAssets(kit).map(async (f) => {
|
|
227
223
|
const dir = dirOf(f.asset.id)
|
|
@@ -242,7 +238,7 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
|
|
|
242
238
|
const dir = dirOf(f.asset.id)
|
|
243
239
|
const pick = await readPick(dir)
|
|
244
240
|
const rolls = listRolls(dir).map((name) => ({ name, picked: name === pick.pick, alt: pick.alts.includes(name), hasMeta: existsSync(metaPath(dir, name)) }))
|
|
245
|
-
const custom = customPanelsOf(f).map((p) => ({ id: p.id, kind: f.kind.id, url:
|
|
241
|
+
const custom = customPanelsOf(f).map((p) => ({ id: p.id, kind: f.kind.id, url: `panel/${encodeURIComponent(f.kind.id)}/${encodeURIComponent(p.id)}.js` }))
|
|
246
242
|
return json({ asset: { ...f.asset, prompt: await promptOf(f), cat: f.kind.category, geometry: geometryOf(f) != null, custom }, info: infoOf(f), rolls, pick })
|
|
247
243
|
}
|
|
248
244
|
|
|
@@ -472,16 +468,3 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
|
|
|
472
468
|
|
|
473
469
|
return { handle, queue }
|
|
474
470
|
}
|
|
475
|
-
|
|
476
|
-
// standalone studio server (`shipkit assets serve`): the same routes on
|
|
477
|
-
// their own port. The hub (`shipkit serve`) mounts studioRoutes under
|
|
478
|
-
// /assets/ instead — one route table, two front doors.
|
|
479
|
-
export async function serveStudio(deps: StudioDeps): Promise<Studio> {
|
|
480
|
-
const routes = await studioRoutes(deps)
|
|
481
|
-
const server = Bun.serve({
|
|
482
|
-
hostname: '127.0.0.1',
|
|
483
|
-
port: deps.kit.port,
|
|
484
|
-
fetch: (req) => routes.handle(req, new URL(req.url).pathname),
|
|
485
|
-
})
|
|
486
|
-
return { server, queue: routes.queue, stop: () => server.stop(true) }
|
|
487
|
-
}
|
package/src/cli/cmd_Assets.ts
CHANGED
|
@@ -2,26 +2,15 @@
|
|
|
2
2
|
// (ComfyUI or procedural canvas), curate in the studio, ship with metadata
|
|
3
3
|
// write-through. Thin clipanion surfaces; logic in assets/run-assets.ts.
|
|
4
4
|
import { Command, Option } from 'clipanion'
|
|
5
|
-
import { runAssetsCheck, runAssetsGen, runAssetsIdentity,
|
|
5
|
+
import { runAssetsCheck, runAssetsGen, runAssetsIdentity, runAssetsTopUp, type AssetsIo } from '../assets/run-assets.ts'
|
|
6
6
|
|
|
7
7
|
const ioOf = (cmd: Command): AssetsIo => ({
|
|
8
8
|
print: (l) => cmd.context.stdout.write(`${l}\n`),
|
|
9
9
|
printErr: (l) => cmd.context.stderr.write(`${l}\n`),
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
static override usage = Command.Usage({
|
|
16
|
-
category: 'Assets',
|
|
17
|
-
description: 'the studio — roll, curate, pick, ship (127.0.0.1, kit port)',
|
|
18
|
-
examples: [['launch the studio for the cwd kit', 'shipkit assets serve']],
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
async execute(): Promise<number> {
|
|
22
|
-
return runAssetsServe(process.cwd(), ioOf(this))
|
|
23
|
-
}
|
|
24
|
-
}
|
|
12
|
+
// the studio UI has no standalone server command — it rides the hub page
|
|
13
|
+
// (`shipkit serve`, the repo's ONE Bun.serve)
|
|
25
14
|
|
|
26
15
|
export class AssetsFillCommand extends Command {
|
|
27
16
|
static override paths = [['assets', 'fill']]
|
package/src/cli/cmd_Check.ts
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
|
-
// shipkit check — periodic health, TTL-cached
|
|
2
|
-
//
|
|
1
|
+
// shipkit check — periodic health, TTL-cached; `--fix` applies rule
|
|
2
|
+
// autofixes then re-lints. Thin clipanion surface; logic in
|
|
3
|
+
// core/run-check.ts and rules/run-fix.ts. ci NEVER fixes.
|
|
3
4
|
import { Command, Option } from 'clipanion'
|
|
4
5
|
import { runCheck } from '../core/run-check.ts'
|
|
6
|
+
import { runFix } from '../rules/run-fix.ts'
|
|
5
7
|
|
|
6
8
|
export class CheckCommand extends Command {
|
|
7
9
|
static override paths = [['check']]
|
|
8
10
|
|
|
9
11
|
static override usage = Command.Usage({
|
|
10
12
|
category: 'Health',
|
|
11
|
-
description: 'periodic health:
|
|
13
|
+
description: 'periodic health: SK rules + every *.check.ts in the repo (auto-discovered, run as subprocesses), clean passes replay from a TTL cache; --fix applies rule autofixes',
|
|
12
14
|
examples: [
|
|
13
15
|
['run (cached where valid)', 'shipkit check'],
|
|
14
16
|
['bust every cache layer', 'shipkit check --fresh'],
|
|
17
|
+
['apply every rule autofix, re-lint', 'shipkit check --fix'],
|
|
18
|
+
['fix one rule', 'shipkit check --fix SK004'],
|
|
19
|
+
['report-only: self-maintaining rules never write', 'shipkit check --frozen'],
|
|
15
20
|
['machine-readable', 'shipkit check --json'],
|
|
16
21
|
],
|
|
17
22
|
})
|
|
18
23
|
|
|
19
24
|
json = Option.Boolean('--json', false, { description: 'machine-readable result instead of wire lines' })
|
|
20
25
|
fresh = Option.Boolean('--fresh', false, { description: 'bust every TTL cache layer (travels to child checks)' })
|
|
26
|
+
fix = Option.Boolean('--fix', false, { description: 'apply rule autofixes (all fixable rules, or only the listed SK ids), then re-lint' })
|
|
27
|
+
frozen = Option.Boolean('--frozen', false, { description: 'report-only mode for self-maintaining rules (SK005 never touches package.json)' })
|
|
28
|
+
fixIds = Option.Rest()
|
|
21
29
|
|
|
22
30
|
async execute(): Promise<number> {
|
|
23
31
|
try {
|
|
24
|
-
|
|
32
|
+
if (this.fixIds.length > 0 && !this.fix) {
|
|
33
|
+
this.context.stderr.write(`FAIL: rule ids need --fix (shipkit check --fix ${this.fixIds.join(' ')})\n`)
|
|
34
|
+
return 2
|
|
35
|
+
}
|
|
36
|
+
if (this.fix && this.json) {
|
|
37
|
+
this.context.stderr.write('FAIL: --fix has no --json shape — run the fix, then `shipkit check --json` for state\n')
|
|
38
|
+
return 2
|
|
39
|
+
}
|
|
40
|
+
if (this.fix)
|
|
41
|
+
return await runFix(process.cwd(), { ids: this.fixIds, print: (l) => this.context.stdout.write(`${l}\n`) })
|
|
42
|
+
return await runCheck(process.cwd(), { json: this.json, fresh: this.fresh, frozen: this.frozen, print: (l) => this.context.stdout.write(`${l}\n`) })
|
|
25
43
|
} catch (e) {
|
|
26
44
|
this.context.stderr.write(`FAIL: ${e instanceof Error ? e.message : String(e)}\n`)
|
|
27
45
|
return 1
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// shipkit reflect — the user's typed prompts across every Claude Code session
|
|
2
|
+
// of a repo, as a chronological corpus for retrospective extraction passes.
|
|
3
|
+
// REPORT-ONLY: exit 0 with prompts, 1 when a repo has no transcripts (loud,
|
|
4
|
+
// naming the config roots that were searched).
|
|
5
|
+
import { mkdirSync, writeFileSync } from 'fs'
|
|
6
|
+
import { basename, join, resolve } from 'path'
|
|
7
|
+
import { Command, Option } from 'clipanion'
|
|
8
|
+
import { type PromptEntry, collectRepoPrompts, corpusMarkdown, projectSlug } from '../reflect/prompt-corpus.ts'
|
|
9
|
+
|
|
10
|
+
const SHORT_MAX_LINES = 20
|
|
11
|
+
|
|
12
|
+
/** the --json unit: one per requested repo, failures carried in-band */
|
|
13
|
+
export type RepoDoc = { repo: string; prompts: PromptEntry[]; error?: string }
|
|
14
|
+
|
|
15
|
+
export class ReflectCommand extends Command {
|
|
16
|
+
static override paths = [['reflect']]
|
|
17
|
+
|
|
18
|
+
static override usage = Command.Usage({
|
|
19
|
+
category: 'Health',
|
|
20
|
+
description:
|
|
21
|
+
"the user's typed prompts across every Claude Code session of a repo — chronological md corpus for retrospectives",
|
|
22
|
+
examples: [
|
|
23
|
+
['corpus for the current repo', 'shipkit reflect'],
|
|
24
|
+
['corpus for other repos', 'shipkit reflect ~/dev/one-repo ~/dev/another-repo'],
|
|
25
|
+
['drop the long pastes', 'shipkit reflect --short'],
|
|
26
|
+
['batch to files instead of stdout', 'shipkit reflect ~/dev/a ~/dev/b --out stats'],
|
|
27
|
+
['machine-readable', 'shipkit reflect --json'],
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
repoPaths = Option.Rest()
|
|
32
|
+
json = Option.Boolean('--json', false, { description: 'emit [{repo, prompts, error?}] JSON — same shape for 1 or N repos' })
|
|
33
|
+
short = Option.Boolean('--short', false, { description: `only prompts of ${SHORT_MAX_LINES} lines or fewer` })
|
|
34
|
+
out = Option.String('--out', { description: 'write <repo>[.short].md (or .json) per repo into this dir instead of stdout' })
|
|
35
|
+
|
|
36
|
+
async execute(): Promise<number> {
|
|
37
|
+
const repos = this.repoPaths.length > 0 ? this.repoPaths : [process.cwd()]
|
|
38
|
+
if (this.out != null) mkdirSync(this.out, { recursive: true })
|
|
39
|
+
let failed = false
|
|
40
|
+
const jsonDocs: RepoDoc[] = []
|
|
41
|
+
for (const repo of repos) {
|
|
42
|
+
const { prompts, dirs, roots } = collectRepoPrompts(repo)
|
|
43
|
+
const kept = this.short ? prompts.filter((p) => p.text.split('\n').length <= SHORT_MAX_LINES) : prompts
|
|
44
|
+
if (kept.length === 0) {
|
|
45
|
+
const error = this.fail(repo, prompts.length, roots)
|
|
46
|
+
failed = true
|
|
47
|
+
if (this.json) jsonDocs.push({ repo: resolve(repo), prompts: [], error })
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
if (this.json) jsonDocs.push({ repo: resolve(repo), prompts: kept })
|
|
51
|
+
else this.emitMarkdown(repo, kept, dirs)
|
|
52
|
+
}
|
|
53
|
+
if (this.json) this.emitJson(jsonDocs)
|
|
54
|
+
return failed ? 1 : 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** one FAIL line per repo, and a fix that is not "do what you just did".
|
|
58
|
+
* Returns the same reason for the --json payload: a consumer must not have
|
|
59
|
+
* to diff stderr against stdout to learn a repo produced nothing. */
|
|
60
|
+
private fail(repo: string, total: number, roots: string[]): string {
|
|
61
|
+
const e = this.context.stderr
|
|
62
|
+
if (this.short && total > 0) {
|
|
63
|
+
const reason = `${total} prompts, none of ${SHORT_MAX_LINES} lines or fewer`
|
|
64
|
+
e.write(`FAIL: ${resolve(repo)}: ${reason}\n`)
|
|
65
|
+
e.write(` fix: drop --short\n`)
|
|
66
|
+
return reason
|
|
67
|
+
}
|
|
68
|
+
e.write(`FAIL: no typed prompts for ${resolve(repo)}\n`)
|
|
69
|
+
e.write(` looked for projects/${projectSlug(repo)}* under: ${roots.join(' · ')}\n`)
|
|
70
|
+
e.write(
|
|
71
|
+
this.repoPaths.length === 0
|
|
72
|
+
? ` fix: this repo has no Claude Code sessions — name another, shipkit reflect ~/dev/<repo>\n`
|
|
73
|
+
: ` fix: check the path (it is slugged verbatim) — a session dir appears only after a session ran there\n`,
|
|
74
|
+
)
|
|
75
|
+
return 'no typed prompts'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** ONE shape whatever the run looked like: RepoDoc[], failures included with
|
|
79
|
+
* their reason. A data-dependent schema breaks every consumer eventually. */
|
|
80
|
+
private emitJson(docs: RepoDoc[]): void {
|
|
81
|
+
if (this.out == null) {
|
|
82
|
+
this.context.stdout.write(`${JSON.stringify(docs, null, 2)}\n`)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
for (const d of docs) {
|
|
86
|
+
if (d.error != null) continue
|
|
87
|
+
const file = this.outFile(d.repo, '.json')
|
|
88
|
+
writeFileSync(file, `${JSON.stringify(d, null, 2)}\n`)
|
|
89
|
+
this.context.stdout.write(`ok: ${this.counts(d.prompts)} → ${file}\n`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** queued prompts are the ones typed mid-run: say how many, so a future hole
|
|
94
|
+
* in that path is visible instead of silent */
|
|
95
|
+
private counts(prompts: PromptEntry[]): string {
|
|
96
|
+
const queued = prompts.filter((p) => p.origin === 'queued').length
|
|
97
|
+
const base = `${String(prompts.length).padStart(4)} prompts`
|
|
98
|
+
return queued === 0 ? base : `${base} (${queued} queued mid-run)`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private emitMarkdown(repo: string, prompts: PromptEntry[], dirs: string[]): void {
|
|
102
|
+
const md = corpusMarkdown(repo, prompts)
|
|
103
|
+
if (this.out == null) {
|
|
104
|
+
this.context.stdout.write(md)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
const file = this.outFile(repo, '.md')
|
|
108
|
+
writeFileSync(file, md)
|
|
109
|
+
this.context.stdout.write(`ok: ${this.counts(prompts)} → ${file} (${dirs.length} session dir(s))\n`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** --short writes its OWN file: the filtered corpus must never overwrite the full one */
|
|
113
|
+
private outFile(repo: string, ext: string): string {
|
|
114
|
+
return join(this.out ?? '.', `${basename(resolve(repo))}${this.short ? '.short' : ''}${ext}`)
|
|
115
|
+
}
|
|
116
|
+
}
|