rvlib-shipkit 0.9.0 → 0.10.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 +15 -0
- package/package.json +1 -1
- package/src/cli/cmd_Start.ts +3 -3
- package/src/start-repo.ts +29 -19
- package/src/wiki/scan-md.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 — 2026-07-22
|
|
4
|
+
|
|
5
|
+
New:
|
|
6
|
+
|
|
7
|
+
- `shipkit start`: `--include` is now repeatable — every given line is
|
|
8
|
+
ensured in CLAUDE.md, in order, inserted after any leading include lines
|
|
9
|
+
already present (never double-added; `--also-ok` satisfies the first
|
|
10
|
+
include).
|
|
11
|
+
|
|
12
|
+
Fixed:
|
|
13
|
+
|
|
14
|
+
- md graph scanner: a backticked/arrow token carrying the scan root's own
|
|
15
|
+
dir name (`agent/architecture.md` while scanning `agent/`) now resolves —
|
|
16
|
+
routing tables written repo-root-relative finally produce edges.
|
|
17
|
+
|
|
3
18
|
## 0.9.0 — 2026-07-22
|
|
4
19
|
|
|
5
20
|
New:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
package/src/cli/cmd_Start.ts
CHANGED
|
@@ -12,14 +12,14 @@ export class StartCommand extends Command {
|
|
|
12
12
|
description: 'ensure <root>/<name> exists, optionally with a CLAUDE.md include line; prints the dir on stdout so a shell alias can cd there',
|
|
13
13
|
examples: [
|
|
14
14
|
['start (or enter) a repo', 'shipkit start pong'],
|
|
15
|
-
['seed CLAUDE.md
|
|
15
|
+
['seed CLAUDE.md includes', "shipkit start pong --include '@~/wiki/index.md' --include '@./agent/index.md'"],
|
|
16
16
|
],
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
name = Option.String()
|
|
20
20
|
root = Option.String('--root', { description: 'parent dir for repos (default ~/dev)' })
|
|
21
|
-
include = Option.
|
|
22
|
-
alsoOk = Option.Array('--also-ok', { description: 'alternative lines
|
|
21
|
+
include = Option.Array('--include', { description: 'line CLAUDE.md must carry, repeatable, order kept; omitted = only ensure the dir' })
|
|
22
|
+
alsoOk = Option.Array('--also-ok', { description: 'alternative lines satisfying the first --include — never double-add' })
|
|
23
23
|
skip = Option.Array('--skip', { description: 'repo names that never get the include' })
|
|
24
24
|
|
|
25
25
|
async execute(): Promise<number> {
|
package/src/start-repo.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// `shipkit start` — ensure <root>/<name> exists and, when --include
|
|
2
|
-
// given, that CLAUDE.md carries
|
|
3
|
-
// cmd_Start. Status lines go to STDERR — the ONLY stdout
|
|
4
|
-
// resolved dir, so a sourced shell alias can
|
|
5
|
-
// (the one thing a child process can't do
|
|
1
|
+
// `shipkit start` — ensure <root>/<name> exists and, when --include lines
|
|
2
|
+
// are given, that CLAUDE.md carries them (in order, at the top). Plain
|
|
3
|
+
// module behind cmd_Start. Status lines go to STDERR — the ONLY stdout
|
|
4
|
+
// output is the resolved dir, so a sourced shell alias can
|
|
5
|
+
// `cd "$(shipkit start <name>)"` (the one thing a child process can't do
|
|
6
|
+
// for its parent shell).
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
7
8
|
import { homedir } from 'os'
|
|
8
9
|
import { join } from 'path'
|
|
@@ -10,16 +11,14 @@ import { join } from 'path'
|
|
|
10
11
|
export type StartOpts = {
|
|
11
12
|
/** parent dir for repos — default ~/dev */
|
|
12
13
|
root?: string
|
|
13
|
-
/**
|
|
14
|
-
include?: string
|
|
15
|
-
/** alternative lines that
|
|
14
|
+
/** lines CLAUDE.md must carry, in order; omitted = only ensure the dir */
|
|
15
|
+
include?: string[]
|
|
16
|
+
/** alternative lines that satisfy the FIRST include — never double-add */
|
|
16
17
|
alsoOk?: string[]
|
|
17
|
-
/** repo names that never get
|
|
18
|
+
/** repo names that never get includes */
|
|
18
19
|
skip?: string[]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
22
|
-
|
|
23
22
|
export function startRepo(name: string, opts: StartOpts = {}): number {
|
|
24
23
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
|
|
25
24
|
console.error(`FAIL: bad repo name '${name}' — letters/digits/._- only, no leading dot, no slashes`)
|
|
@@ -30,18 +29,29 @@ export function startRepo(name: string, opts: StartOpts = {}): number {
|
|
|
30
29
|
mkdirSync(dir, { recursive: true })
|
|
31
30
|
console.error(`ok: created ${dir}`)
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const want = opts.include ?? []
|
|
33
|
+
if (want.length === 0 || (opts.skip ?? []).includes(name)) {
|
|
34
|
+
if (want.length > 0) console.error(`skip: ${name} is include-exempt — CLAUDE.md untouched`)
|
|
35
35
|
} else {
|
|
36
36
|
const claudeMd = join(dir, 'CLAUDE.md')
|
|
37
37
|
if (!existsSync(claudeMd)) {
|
|
38
|
-
writeFileSync(claudeMd, `${
|
|
39
|
-
console.error(
|
|
38
|
+
writeFileSync(claudeMd, `${want.join('\n')}\n`)
|
|
39
|
+
console.error(`ok: CLAUDE.md created with ${want.length} include line(s)`)
|
|
40
40
|
} else {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const cur = readFileSync(claudeMd, 'utf8')
|
|
42
|
+
const lineSet = new Set(cur.split('\n'))
|
|
43
|
+
const alt = opts.alsoOk ?? []
|
|
44
|
+
const satisfied = (w: string): boolean => lineSet.has(w) || (w === want[0] && alt.some((a) => lineSet.has(a)))
|
|
45
|
+
const missing = want.filter((w) => !satisfied(w))
|
|
46
|
+
if (missing.length > 0) {
|
|
47
|
+
// insert after any leading include/alt lines so order survives
|
|
48
|
+
const lines = cur.split('\n')
|
|
49
|
+
const known = new Set([...want, ...alt])
|
|
50
|
+
let at = 0
|
|
51
|
+
while (at < lines.length && known.has(lines[at]!)) at++
|
|
52
|
+
lines.splice(at, 0, ...missing)
|
|
53
|
+
writeFileSync(claudeMd, lines.join('\n'))
|
|
54
|
+
console.error(`ok: ${missing.length} missing include line(s) inserted into CLAUDE.md`)
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
}
|
package/src/wiki/scan-md.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// unreachable files stay in the graph with no depth (lint-md.ts turns those,
|
|
9
9
|
// plus dangling refs, into failures — the graph itself is a viewer).
|
|
10
10
|
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
|
11
|
-
import { dirname, join, relative, resolve } from 'path'
|
|
11
|
+
import { basename, dirname, join, relative, resolve } from 'path'
|
|
12
12
|
import type { MdEdge, MdGraph, MdNode } from 'rvlib-shipkit/src/wiki/md-graph.ts'
|
|
13
13
|
|
|
14
14
|
export type ScanMdConfig = {
|
|
@@ -43,8 +43,13 @@ export function scanMd(cfg: ScanMdConfig): MdScan {
|
|
|
43
43
|
files.sort()
|
|
44
44
|
const inGraph = new Set(files)
|
|
45
45
|
|
|
46
|
+
// writers inside a scanned dir often name paths from the REPO root
|
|
47
|
+
// ("agent/architecture.md" while scanning agent/) — accept that form too
|
|
48
|
+
const rootBase = `${basename(cfg.root)}/`
|
|
46
49
|
const resolveRef = (fromRel: string, token: string): string | null => {
|
|
47
|
-
|
|
50
|
+
const cands = [resolve(join(cfg.root, dirname(fromRel)), token), resolve(cfg.root, token)]
|
|
51
|
+
if (token.startsWith(rootBase)) cands.push(resolve(cfg.root, token.slice(rootBase.length)))
|
|
52
|
+
for (const cand of cands) {
|
|
48
53
|
const rel = relative(cfg.root, cand)
|
|
49
54
|
if (!rel.startsWith('..') && inGraph.has(rel)) return rel
|
|
50
55
|
}
|