rvlib-shipkit 0.38.1 → 1.0.1
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 +379 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/derived.ts +1 -1
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +49 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +16 -3
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +257 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +616 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +88 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +152 -189
- package/src/core/run-fix.ts +99 -0
- package/src/core/run-fixers.ts +64 -33
- package/src/core/self-update.ts +11 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/fleet/scan-fleet.ts +16 -0
- package/src/hooks/leak-guard.ts +20 -9
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +38 -26
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +157 -88
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +9 -4
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +209 -15
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/scan-md.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
package/src/cli/cmd_Ci.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// shipkit ci — the commit/deploy gate. Thin clipanion surface; logic in
|
|
2
2
|
// core/run-ci.ts.
|
|
3
3
|
import { Command, Option } from 'clipanion'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import { recordExit, startDetached } from '../core/jobs.ts'
|
|
4
6
|
import { runCi } from '../core/run-ci.ts'
|
|
5
7
|
import { maybeSelfUpdateAndRestart } from '../core/self-update.ts'
|
|
6
8
|
|
|
@@ -9,35 +11,68 @@ export class CiCommand extends Command {
|
|
|
9
11
|
|
|
10
12
|
static override usage = Command.Usage({
|
|
11
13
|
category: 'Gate',
|
|
12
|
-
description: 'THE command. Repairs what a machine can (formatter, imports, repo shape), runs every
|
|
14
|
+
description: 'THE command. Repairs what a machine can (formatter, imports, repo shape), then runs every check the gate asks for, through one DAG. A check that declares blocking:false warns instead of failing. Run this instead of anything else.',
|
|
13
15
|
examples: [
|
|
14
16
|
['full gate, mechanical fixes applied', 'shipkit ci'],
|
|
15
17
|
['gate without the repair pass (checks can still write)', 'shipkit ci --no-fix'],
|
|
16
18
|
['leave repo config files alone', 'shipkit ci --no-fix-rules'],
|
|
17
19
|
['stop at the first failure', 'shipkit ci --bail'],
|
|
18
|
-
['one
|
|
19
|
-
['
|
|
20
|
-
['
|
|
20
|
+
['one check + its dep closure', 'shipkit ci --only typecheck'],
|
|
21
|
+
['every check declaring one group', 'shipkit ci --only wiki'],
|
|
22
|
+
['run everything, take nothing from the cache', 'shipkit ci --fresh'],
|
|
23
|
+
['include an opt in check', 'shipkit ci --with up:itch'],
|
|
24
|
+
['in the background, read it back with shipkit status', 'shipkit ci --detach'],
|
|
21
25
|
],
|
|
22
26
|
})
|
|
23
27
|
|
|
24
28
|
bail = Option.Boolean('--bail', false, { description: 'stop at the first failure' })
|
|
25
29
|
json = Option.Boolean('--json', false, { description: 'machine-readable result instead of wire lines' })
|
|
26
|
-
fix = Option.Boolean('--fix', true, { description: 'repair the mechanical slots before gating. Default on. --no-fix skips the repair pass, checks can still write. Under CI the fixers still run but the gate FAILS on any file they rewrite' })
|
|
27
|
-
fixRules = Option.Boolean('--fix-rules', true, { description: 'apply the structural
|
|
28
|
-
frozen = Option.Boolean('--frozen', false, { description: 'report-only mode for self-maintaining
|
|
29
|
-
skip = Option.Array('--skip', [], { description: 'skip a
|
|
30
|
-
withIds = Option.Array('--with', [], { description: 'include
|
|
31
|
-
only = Option.Array('--only', [], { description: 'run only this
|
|
32
|
-
fresh = Option.Boolean('--fresh', false, { description: '
|
|
30
|
+
fix = Option.Boolean('--fix', true, { description: 'repair the mechanical slots before gating. Default on. --no-fix skips the repair pass, the checks can still write. Under CI the fixers still run but the gate FAILS on any file they rewrite' })
|
|
31
|
+
fixRules = Option.Boolean('--fix-rules', true, { description: 'apply the structural autofixes of the built-in checks too. Default on. --no-fix-rules leaves repo config alone' })
|
|
32
|
+
frozen = Option.Boolean('--frozen', false, { description: 'report-only mode for self-maintaining checks: SK005 never touches package.json, and generated paths are never untracked. STATUS.md is still refreshed on disk, but it is gitignored, so a run leaves the tree clean' })
|
|
33
|
+
skip = Option.Array('--skip', [], { description: 'skip a check group (repeatable)' })
|
|
34
|
+
withIds = Option.Array('--with', [], { description: 'include a check the quick gate leaves out (repeatable)' })
|
|
35
|
+
only = Option.Array('--only', [], { description: 'run only this check + its dep closure (repeatable). A group name runs every check declaring that group' })
|
|
36
|
+
fresh = Option.Boolean('--fresh', false, { description: 'run every check: no answer comes from the cache, and what this run finds is stored fresh' })
|
|
33
37
|
full = Option.Boolean('--full', false, { description: `run the FULL test tier: every suite the repo declares, the slow ones it skips while you work included. What the release path always runs. Same as SHIPKIT_TEST_FULL=1` })
|
|
34
38
|
|
|
39
|
+
detach = Option.Boolean('--detach', false, { description: 'start this gate in the background and return at once with a run id. `shipkit status <id>` prints it. The gate is an ordinary one: same cache, same run record' })
|
|
40
|
+
job = Option.String('--job', { hidden: true })
|
|
41
|
+
|
|
35
42
|
async execute(): Promise<number> {
|
|
43
|
+
const root = process.cwd()
|
|
44
|
+
if (this.detach) {
|
|
45
|
+
const d = startDetached(root, { cli: join(import.meta.dir, '../cli.ts'), args: this.argsAgain() })
|
|
46
|
+
for (const l of d.lines) this.context.stdout.write(`${l}\n`)
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
const code = await this.gate(root)
|
|
50
|
+
if (this.job != null) recordExit(root, this.job, code)
|
|
51
|
+
return code
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** this invocation's flags, --detach aside, for the gate that runs detached */
|
|
55
|
+
private argsAgain(): string[] {
|
|
56
|
+
return [
|
|
57
|
+
...(this.bail ? ['--bail'] : []),
|
|
58
|
+
...(this.json ? ['--json'] : []),
|
|
59
|
+
...(this.fix ? [] : ['--no-fix']),
|
|
60
|
+
...(this.fixRules ? [] : ['--no-fix-rules']),
|
|
61
|
+
...(this.frozen ? ['--frozen'] : []),
|
|
62
|
+
...this.skip.flatMap((v) => ['--skip', v]),
|
|
63
|
+
...this.withIds.flatMap((v) => ['--with', v]),
|
|
64
|
+
...this.only.flatMap((v) => ['--only', v]),
|
|
65
|
+
...(this.fresh ? ['--fresh'] : []),
|
|
66
|
+
...(this.full ? ['--full'] : []),
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async gate(root: string): Promise<number> {
|
|
36
71
|
// a stale toolkit updates itself and restarts this exact invocation
|
|
37
72
|
// (loop-guarded, skipped under CI/frozen/source consumption)
|
|
38
|
-
await maybeSelfUpdateAndRestart(
|
|
73
|
+
await maybeSelfUpdateAndRestart(root)
|
|
39
74
|
try {
|
|
40
|
-
return await runCi(
|
|
75
|
+
return await runCi(root, {
|
|
41
76
|
bail: this.bail,
|
|
42
77
|
json: this.json,
|
|
43
78
|
fix: this.fix,
|
package/src/cli/cmd_Commands.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// drifts the moment a flag changes, and then teaches commands that do not
|
|
3
3
|
// exist. rendering the live registry makes the doc a build artifact.
|
|
4
4
|
import { Command, Option } from 'clipanion'
|
|
5
|
+
import { spliceMarkedBlock } from '../core/doc-block.ts'
|
|
5
6
|
|
|
6
7
|
export type CommandOption = { definition: string; description: string }
|
|
7
8
|
export type CommandRow = {
|
|
@@ -74,12 +75,7 @@ export const END = '<!-- /shipkit:commands -->'
|
|
|
74
75
|
/** replace the marked block in a host file. Returns null when the markers are
|
|
75
76
|
* missing, so a renamed or hand-mangled host fails loud instead of silently
|
|
76
77
|
* writing nothing. */
|
|
77
|
-
export
|
|
78
|
-
const a = host.indexOf(BEGIN)
|
|
79
|
-
const b = host.indexOf(END)
|
|
80
|
-
if (a === -1 || b === -1 || b < a) return null
|
|
81
|
-
return `${host.slice(0, a + BEGIN.length)}\n\n${block}\n\n${host.slice(b)}`
|
|
82
|
-
}
|
|
78
|
+
export const spliceBlock = (host: string, block: string): string | null => spliceMarkedBlock(host, block, BEGIN, END)
|
|
83
79
|
|
|
84
80
|
export class CommandsCommand extends Command {
|
|
85
81
|
static override paths = [['commands']]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// `shipkit config-keys` — the repo.config.ts surface as markdown, read from
|
|
2
|
+
// the types themselves. Twin of `shipkit commands`: the binary is the source,
|
|
3
|
+
// the page is the artifact, and a lint in the docs repo fails when the two
|
|
4
|
+
// disagree.
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { Command, Option } from 'clipanion'
|
|
7
|
+
import { BEGIN, END, CHECK_BEGIN, CHECK_END, configKeys, renderKeys, undocumented } from '../core/config-keys.ts'
|
|
8
|
+
import { spliceMarkedBlock } from '../core/doc-block.ts'
|
|
9
|
+
|
|
10
|
+
/** the two types the walks start from, and the files that declare them: what
|
|
11
|
+
* a repo declares in its CONFIG, and what one `*.check.ts` file declares */
|
|
12
|
+
export const CONFIG_TYPE = 'RepoConfig'
|
|
13
|
+
export const CHECK_TYPE = 'CheckDef'
|
|
14
|
+
export const configSourceFile = (): string => fileURLToPath(new URL('../repo-config.ts', import.meta.url))
|
|
15
|
+
export const checkSourceFile = (): string => fileURLToPath(new URL('../check.ts', import.meta.url))
|
|
16
|
+
|
|
17
|
+
export class ConfigKeysCommand extends Command {
|
|
18
|
+
static override paths = [['config-keys']]
|
|
19
|
+
|
|
20
|
+
static override usage = Command.Usage({
|
|
21
|
+
category: 'Health',
|
|
22
|
+
description: 'every repo.config.ts key and every check field with its doc, read from the types — the source a config reference page is generated FROM',
|
|
23
|
+
examples: [
|
|
24
|
+
['markdown reference', 'shipkit config-keys'],
|
|
25
|
+
['machine-readable', 'shipkit config-keys --json'],
|
|
26
|
+
['write into a docs page between the markers', 'shipkit config-keys --out wiki/build/shipkit/config-keys.md'],
|
|
27
|
+
['fail if that page is stale (lint mode)', 'shipkit config-keys --out wiki/build/shipkit/config-keys.md --check'],
|
|
28
|
+
['name the keys carrying no doc comment', 'shipkit config-keys --undocumented'],
|
|
29
|
+
],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
json = Option.Boolean('--json', false, { description: 'machine-readable { config, check } of [{path, name, type, optional, doc, depth}]' })
|
|
33
|
+
out = Option.String('--out', { description: 'host file carrying the shipkit:config-keys and shipkit:check-keys markers. Both blocks are rewritten' })
|
|
34
|
+
check = Option.Boolean('--check', false, { description: 'with --out: exit 1 if the block is stale, write nothing' })
|
|
35
|
+
undocumented = Option.Boolean('--undocumented', false, { description: 'list the keys with no doc comment in the source, and exit 1 if there are any' })
|
|
36
|
+
|
|
37
|
+
async execute(): Promise<number> {
|
|
38
|
+
const keys = configKeys(configSourceFile(), CONFIG_TYPE)
|
|
39
|
+
const checkKeys = configKeys(checkSourceFile(), CHECK_TYPE)
|
|
40
|
+
if (this.undocumented) {
|
|
41
|
+
const missing = [...undocumented(keys), ...undocumented(checkKeys)]
|
|
42
|
+
if (missing.length === 0) { this.context.stdout.write(`ok: every ${CONFIG_TYPE} and ${CHECK_TYPE} key carries a doc comment (${keys.length + checkKeys.length} keys)\n`); return 0 }
|
|
43
|
+
this.context.stdout.write(`FAIL: ${missing.length} key(s) carry no doc comment — a key nobody documented is a key nobody uses\n`)
|
|
44
|
+
for (const p of missing) this.context.stdout.write(` ${p}\n`)
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
if (this.out == null) {
|
|
48
|
+
this.context.stdout.write(this.json ? `${JSON.stringify({ config: keys, check: checkKeys })}\n` : `${renderKeys(keys)}\n\n${renderKeys(checkKeys)}\n`)
|
|
49
|
+
return 0
|
|
50
|
+
}
|
|
51
|
+
const { existsSync, readFileSync, writeFileSync } = await import('fs')
|
|
52
|
+
if (!existsSync(this.out)) { this.context.stdout.write(`FAIL: ${this.out} does not exist\n`); return 1 }
|
|
53
|
+
const host = readFileSync(this.out, 'utf8')
|
|
54
|
+
const spliced = spliceMarkedBlock(host, renderKeys(keys), BEGIN, END)
|
|
55
|
+
if (spliced == null) { this.context.stdout.write(`FAIL: ${this.out} has no ${BEGIN} / ${END} markers\n`); return 1 }
|
|
56
|
+
const next = spliceMarkedBlock(spliced, renderKeys(checkKeys), CHECK_BEGIN, CHECK_END)
|
|
57
|
+
if (next == null) { this.context.stdout.write(`FAIL: ${this.out} has no ${CHECK_BEGIN} / ${CHECK_END} markers\n`); return 1 }
|
|
58
|
+
const total = keys.length + checkKeys.length
|
|
59
|
+
if (next === host) { this.context.stdout.write(`ok: ${this.out} config reference current (${total} keys)\n`); return 0 }
|
|
60
|
+
if (this.check) {
|
|
61
|
+
this.context.stdout.write(`FAIL: ${this.out} config reference is STALE — the types carry keys the page does not list\n`)
|
|
62
|
+
this.context.stdout.write(` fix: shipkit config-keys --out ${this.out}\n`)
|
|
63
|
+
return 1
|
|
64
|
+
}
|
|
65
|
+
writeFileSync(this.out, next)
|
|
66
|
+
this.context.stdout.write(`ok: ${this.out} config reference rewritten (${total} keys)\n`)
|
|
67
|
+
return 0
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/cli/cmd_Contracts.ts
CHANGED
|
@@ -8,7 +8,7 @@ export class ContractsCommand extends Command {
|
|
|
8
8
|
|
|
9
9
|
static override usage = Command.Usage({
|
|
10
10
|
category: 'Gate',
|
|
11
|
-
description: 'release-contract fulfillness, fast and filesystem-only — deploy
|
|
11
|
+
description: 'release-contract fulfillness, fast and filesystem-only — deploy checks dep on it',
|
|
12
12
|
examples: [
|
|
13
13
|
['all applicable contracts', 'shipkit contracts'],
|
|
14
14
|
['one venue', 'shipkit contracts --venue itch'],
|
package/src/cli/cmd_Fleet.ts
CHANGED
|
@@ -5,10 +5,17 @@
|
|
|
5
5
|
import { Command, Option } from 'clipanion'
|
|
6
6
|
import { existsSync } from 'fs'
|
|
7
7
|
import { join } from 'path'
|
|
8
|
-
import { npmLatest, resolveLatest, scanFleet, type FleetRepo } from '../fleet/scan-fleet.ts'
|
|
8
|
+
import { npmLatest, resolveLatest, scanFleet, updateRefusal, type FleetRepo } from '../fleet/scan-fleet.ts'
|
|
9
9
|
import { spawnWithTimeout } from '../core/spawn-timeout.ts'
|
|
10
|
+
import { detectPackageManager, isRefusal } from '../core/package-manager.ts'
|
|
10
11
|
|
|
11
12
|
const short = (name: string): string => name.replace(/^rvlib-/, '')
|
|
13
|
+
// the hand-run fallback speaks the manager THAT repo installs with: telling
|
|
14
|
+
// an npm consumer to run bun leaves its package-lock.json behind
|
|
15
|
+
const manualUpdate = (r: FleetRepo): string => {
|
|
16
|
+
const pm = detectPackageManager(r.dir)
|
|
17
|
+
return isRefusal(pm) ? `<its package manager> update ${r.behind.map((d) => d.name).join(' ')} (${pm.refused})` : pm.update(r.behind.map((d) => d.name)).join(' ')
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
export class FleetCommand extends Command {
|
|
14
21
|
static override paths = [['fleet']]
|
|
@@ -88,9 +95,15 @@ export class FleetCommand extends Command {
|
|
|
88
95
|
failed++
|
|
89
96
|
continue
|
|
90
97
|
}
|
|
98
|
+
const refused = updateRefusal(r)
|
|
99
|
+
if (refused != null) {
|
|
100
|
+
w(`WARN ${r.name}: not updating: ${refused}`)
|
|
101
|
+
failed++
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
91
104
|
const cli = join(r.dir, 'node_modules', 'rvlib-shipkit', 'src', 'cli.ts')
|
|
92
105
|
if (!existsSync(cli)) {
|
|
93
|
-
w(`WARN ${r.name}: no shipkit installed, so it cannot update itself — run: cd ${r.dir} &&
|
|
106
|
+
w(`WARN ${r.name}: no shipkit installed, so it cannot update itself — run: cd ${r.dir} && ${manualUpdate(r)}`)
|
|
94
107
|
failed++
|
|
95
108
|
continue
|
|
96
109
|
}
|
|
@@ -105,7 +118,7 @@ export class FleetCommand extends Command {
|
|
|
105
118
|
else {
|
|
106
119
|
failed++
|
|
107
120
|
w(`WARN ${r.name} did not move — ${line ?? `check exited ${res.code} with no rvlib line`}`)
|
|
108
|
-
w(` fix: cd ${r.dir} &&
|
|
121
|
+
w(` fix: cd ${r.dir} && ${manualUpdate(r)}`)
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
// a repo whose gate refused (dirty tree, frozen, a lease held elsewhere)
|
package/src/cli/cmd_Hooks.ts
CHANGED
|
@@ -13,7 +13,7 @@ export class HooksInstallCommand extends Command {
|
|
|
13
13
|
static override paths = [['hooks', 'install']]
|
|
14
14
|
static override usage = Command.Usage({
|
|
15
15
|
category: 'Gate',
|
|
16
|
-
description: 'write hooks/pre-commit + hooks/commit-msg and point core.hooksPath at them (idempotent)',
|
|
16
|
+
description: 'write .shipkit/hooks/pre-commit + .shipkit/hooks/commit-msg and point core.hooksPath at them (idempotent)',
|
|
17
17
|
examples: [['wire the hooks', 'shipkit hooks install']],
|
|
18
18
|
})
|
|
19
19
|
|
package/src/cli/cmd_Keys.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
// shipkit keys — what every gate
|
|
1
|
+
// shipkit keys — what every gate check is keyed on, and what it is NOT.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// This command exists so the key layer is auditable by eye, whatever the
|
|
4
|
+
// cache serves off it: a cache whose keys nobody can read is a
|
|
5
5
|
// cache nobody can debug the day it serves a wrong answer. `--why <id>` prints
|
|
6
6
|
// the exact component list a key was hashed from, which is the only honest
|
|
7
7
|
// answer to "why did this key move".
|
|
8
8
|
import { createHash } from 'crypto'
|
|
9
9
|
import { Command, Option } from 'clipanion'
|
|
10
10
|
import { loadRepoConfig } from '../repo-config.ts'
|
|
11
|
-
import { envTier, FULL_TESTS_ENV,
|
|
11
|
+
import { envTier, FULL_TESTS_ENV, gateChecks } from '../ledger/gate-checks.ts'
|
|
12
12
|
import { KeyLayer } from '../ledger/git-key.ts'
|
|
13
|
+
import { Ledger } from '../ledger/ledger.ts'
|
|
14
|
+
import { gateBinary } from '../ledger/toolchain-identity.ts'
|
|
13
15
|
|
|
14
16
|
export class KeysCommand extends Command {
|
|
15
17
|
static override paths = [['keys']]
|
|
16
18
|
|
|
17
19
|
static override usage = Command.Usage({
|
|
18
20
|
category: 'Health',
|
|
19
|
-
description: 'the cache key of every gate
|
|
21
|
+
description: 'the cache key of every gate check, and the reason the undeclared ones have none. Reports, never gates',
|
|
20
22
|
examples: [
|
|
21
|
-
['every
|
|
23
|
+
['every check and its key', 'shipkit keys'],
|
|
22
24
|
['only the ones nothing can cache yet', 'shipkit keys --burn-down'],
|
|
23
25
|
['why this key is what it is', 'shipkit keys --why typecheck'],
|
|
24
26
|
['re-hash after the fact and report drift', 'shipkit keys --rescan'],
|
|
@@ -27,9 +29,9 @@ export class KeysCommand extends Command {
|
|
|
27
29
|
],
|
|
28
30
|
})
|
|
29
31
|
|
|
30
|
-
json = Option.Boolean('--json', false, { description: 'machine-readable {
|
|
31
|
-
burnDown = Option.Boolean('--burn-down', false, { description: 'list only the
|
|
32
|
-
why = Option.String('--why', { required: false, description: 'print every component that went into ONE
|
|
32
|
+
json = Option.Boolean('--json', false, { description: 'machine-readable { checks: CheckKey[], burnDown, errors, scanMs }' })
|
|
33
|
+
burnDown = Option.Boolean('--burn-down', false, { description: 'list only the checks nothing can cache yet, and why' })
|
|
34
|
+
why = Option.String('--why', { required: false, description: 'print every component that went into ONE check\'s key' })
|
|
33
35
|
full = Option.Boolean('--full', false, { description: `render the keys a FULL-tier run asks for (same as ${FULL_TESTS_ENV}=1). The tier is in every task's key` })
|
|
34
36
|
rescan = Option.Boolean('--rescan', false, { description: 'run the post-run re-hash and name every input that moved since the scan' })
|
|
35
37
|
|
|
@@ -40,29 +42,51 @@ export class KeysCommand extends Command {
|
|
|
40
42
|
// every task's env, which is in the key, so a surface that renders keys
|
|
41
43
|
// without it renders a tier nobody asked for
|
|
42
44
|
const tier = this.full ? 'full' : envTier()
|
|
43
|
-
const decls =
|
|
44
|
-
const layer = KeyLayer.open(root, decls, { cliEntry:
|
|
45
|
+
const decls = await gateChecks(root, c, { tier })
|
|
46
|
+
const layer = KeyLayer.open(root, decls, { cliEntry: gateBinary() })
|
|
45
47
|
const keys = layer.keys()
|
|
46
48
|
const out = (l: string): void => { this.context.stdout.write(`${l}\n`) }
|
|
47
49
|
|
|
48
50
|
if (this.json) {
|
|
49
51
|
const drift = this.rescan ? layer.rescan() : null
|
|
50
|
-
out(JSON.stringify({ tier,
|
|
52
|
+
out(JSON.stringify({ tier, checks: keys, burnDown: layer.registration.burnDown, errors: layer.registration.errors, layerMs: Math.round(layer.openMs), scanMs: Math.round(layer.scanMs), failure: layer.failure, drift }))
|
|
51
53
|
return layer.registration.errors.length > 0 || layer.failure != null ? 1 : 0
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
if (this.why != null) {
|
|
55
57
|
const k = keys.find((u) => u.id.toLowerCase() === this.why!.toLowerCase())
|
|
56
58
|
if (k == null) {
|
|
57
|
-
out(`FAIL: no gate
|
|
59
|
+
out(`FAIL: no gate check '${this.why}' — run \`shipkit keys\` for the list`)
|
|
58
60
|
return 1
|
|
59
61
|
}
|
|
60
|
-
out(`${k.
|
|
62
|
+
out(`${k.id} — ${k.population}`)
|
|
61
63
|
if (k.key == null) {
|
|
62
64
|
out(` no key: ${k.reason ?? 'undeclared'}`)
|
|
63
65
|
return 0
|
|
64
66
|
}
|
|
65
67
|
out(` key ${k.key}`)
|
|
68
|
+
if (k.population === 'reads') {
|
|
69
|
+
for (const comp of k.components) out(` ${comp}`)
|
|
70
|
+
// the rest of its identity is what it READ, stored with the
|
|
71
|
+
// answer and re-checked before any replay
|
|
72
|
+
const set = Ledger.open(root, decls, { cliEntry: gateBinary(), read: true }).storedReads(k.id)
|
|
73
|
+
if (set == null) {
|
|
74
|
+
out(' no stored answer yet: the next run records what it reads')
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
const files = Object.entries(set.files)
|
|
78
|
+
const exists = Object.entries(set.exists)
|
|
79
|
+
const lists = Object.entries(set.lists)
|
|
80
|
+
const dirs = Object.entries(set.dirs ?? {})
|
|
81
|
+
const links = Object.entries(set.links ?? {})
|
|
82
|
+
out(` read set: ${files.length} file(s), ${exists.length} existence probe(s), ${lists.length} listing(s), ${dirs.length} dir read(s)${links.length > 0 ? `, ${links.length} link probe(s)` : ''}`)
|
|
83
|
+
for (const [p, oid] of files) out(` read ${p}=${oid ?? 'absent'}`)
|
|
84
|
+
for (const [p, kind] of exists) out(` exists ${p}=${kind ?? 'absent'}`)
|
|
85
|
+
for (const [g, h] of lists) out(` list ${g}=${h}`)
|
|
86
|
+
for (const [d, h] of dirs) out(` entries ${d}=${h ?? 'absent'}`)
|
|
87
|
+
for (const [p, is] of links) out(` link ${p}=${is ? 'yes' : 'no'}`)
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
66
90
|
// the excluded block is 44 lines here and it buries the component
|
|
67
91
|
// that actually moved. It stays HASHED line by line, so this is a
|
|
68
92
|
// rendering choice and not a different key: the digest moves with
|
|
@@ -81,22 +105,23 @@ export class KeysCommand extends Command {
|
|
|
81
105
|
|
|
82
106
|
for (const e of layer.registration.errors) out(`FAIL: ${e}`)
|
|
83
107
|
if (layer.failure != null) {
|
|
84
|
-
out(`FAIL: git scan failed, no
|
|
108
|
+
out(`FAIL: git scan failed, no check may be keyed — ${layer.failure}`)
|
|
85
109
|
out(' fix: nothing is cached, so the gate is unaffected. A stale scratch index clears with: rm .git/shipkit/scan.idx')
|
|
86
110
|
}
|
|
87
111
|
|
|
88
|
-
// the burn-down is what nobody has LOOKED at. A
|
|
112
|
+
// the burn-down is what nobody has LOOKED at. A check that declared
|
|
89
113
|
// `never` with a reason has been looked at, and listing it here would
|
|
90
114
|
// put four permanent rows on a list whose whole job is to shrink
|
|
91
115
|
const decided = new Set(layer.decls.filter((d) => d.never != null && d.never !== '').map((d) => d.id))
|
|
92
116
|
const shown = this.burnDown ? keys.filter((k) => k.population === 'none' && !decided.has(k.id)) : keys
|
|
93
|
-
for (const k of shown) out(`${k.population.padEnd(7)} ${k.
|
|
117
|
+
for (const k of shown) out(`${k.population.padEnd(7)} ${k.id.padEnd(28)} ${k.key ?? `— ${k.reason ?? ''}`}`)
|
|
94
118
|
|
|
95
119
|
const content = keys.filter((k) => k.population === 'content').length
|
|
120
|
+
const reads = keys.filter((k) => k.population === 'reads').length
|
|
96
121
|
const world = keys.filter((k) => k.population === 'world').length
|
|
97
122
|
const none = keys.filter((k) => k.population === 'none').length
|
|
98
123
|
const todo = layer.registration.burnDown.length
|
|
99
|
-
out(`\n${keys.length}
|
|
124
|
+
out(`\n${keys.length} checks — ${content} content, ${reads} reads, ${world} world, ${none} never cached (${none - todo} on purpose, ${todo} still undeclared) · ${tier} test tier · key layer ${Math.round(layer.openMs)}ms, of which ${Math.round(layer.scanMs)}ms of git`)
|
|
100
125
|
if (todo > 0 && !this.burnDown) out(` the burn-down: shipkit keys --burn-down`)
|
|
101
126
|
|
|
102
127
|
if (this.rescan) {
|
package/src/cli/cmd_Mv.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// shipkit mv, ONE rename command for the whole repo. An md page routes to
|
|
2
|
-
// mv/mv-md.ts (page + sibling asset dir + check
|
|
2
|
+
// mv/mv-md.ts (page + sibling asset dir + its check file + every tracked ref);
|
|
3
3
|
// anything else runs shipkit's own rename, which rewrites every path
|
|
4
4
|
// reference in the code. Renaming used to mean knowing WHICH tool applied,
|
|
5
5
|
// and knowing that is exactly the kind of doubt the toolkit exists to remove.
|
|
@@ -12,7 +12,7 @@ export class MvCommand extends Command {
|
|
|
12
12
|
|
|
13
13
|
static override usage = Command.Usage({
|
|
14
14
|
category: 'Refactor',
|
|
15
|
-
description: 'rename anything and sweep every reference: an md page (plus its sibling dir and check
|
|
15
|
+
description: 'rename anything and sweep every reference: an md page (plus its sibling dir and its check file) or a code file or folder. Dry run by default. Renames must be CHEAP',
|
|
16
16
|
examples: [
|
|
17
17
|
['dry run, the default, shows what changes', 'shipkit mv docs/old-name.md docs/new-name.md'],
|
|
18
18
|
['apply it', 'shipkit mv docs/old-name.md docs/new-name.md --write'],
|
package/src/cli/cmd_Prose.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Command, Option } from 'clipanion'
|
|
5
5
|
import { loadRepoConfig } from '../repo-config.ts'
|
|
6
6
|
import { classSummary, proseLines, runProseCheck } from '../prose/run-prose.ts'
|
|
7
|
+
import { CheckCtx } from '../core/check-ctx.ts'
|
|
7
8
|
|
|
8
9
|
export class ProseCommand extends Command {
|
|
9
10
|
static override paths = [['prose']]
|
|
@@ -39,7 +40,7 @@ export class ProseCommand extends Command {
|
|
|
39
40
|
)
|
|
40
41
|
return 0
|
|
41
42
|
}
|
|
42
|
-
const r = runProseCheck({ root, config: cfg, paths: explicit ? this.targets : undefined, fix: this.fix })
|
|
43
|
+
const r = runProseCheck({ c: CheckCtx.open({ root, config }), config: cfg, paths: explicit ? this.targets : undefined, fix: this.fix })
|
|
43
44
|
if (this.json) {
|
|
44
45
|
// every hit carries its file: a flat list without one cannot be
|
|
45
46
|
// acted on, and the grouping is already in byFile
|
|
@@ -48,6 +49,14 @@ export class ProseCommand extends Command {
|
|
|
48
49
|
return r.byTier.fix + r.byTier.hard > 0 ? 1 : 0
|
|
49
50
|
}
|
|
50
51
|
const w = (l: string) => this.context.stdout.write(`${l}\n`)
|
|
52
|
+
// a declared scope that resolves to nothing reads as `0 file(s)
|
|
53
|
+
// STE-clean`, the same green a clean repo gets, over a check that
|
|
54
|
+
// inspected no file at all (rule SK014 warns on the same fact)
|
|
55
|
+
if (!explicit && r.files === 0) {
|
|
56
|
+
w(`WARN prose scope matches no file: ${(cfg.include ?? []).join(', ')} — declared and inspecting nothing`)
|
|
57
|
+
w(' fix: fix the globs in repo.config.ts prose.include (or its exempt list), or drop the key to opt out')
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
51
60
|
if (r.fixed.length > 0) w(`ok: rewrote ${r.fixed.length} file(s)`)
|
|
52
61
|
for (const l of proseLines(r, { cap: Number(this.cap) || 25 })) w(l)
|
|
53
62
|
const blocking = r.byTier.fix + r.byTier.hard
|
package/src/cli/cmd_Releases.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// shipkit releases — every
|
|
1
|
+
// shipkit releases — every releasing unit and where it actually stands. Thin
|
|
2
2
|
// clipanion surface; logic in release/run-releases.ts.
|
|
3
3
|
import { Command, Option } from 'clipanion'
|
|
4
4
|
import { loadRepoConfig } from '../repo-config.ts'
|
|
@@ -9,7 +9,7 @@ export class ReleasesCommand extends Command {
|
|
|
9
9
|
|
|
10
10
|
static override usage = Command.Usage({
|
|
11
11
|
category: 'Gate',
|
|
12
|
-
description: 'every
|
|
12
|
+
description: 'every unit that releases and where it stands: version, npm, tag local and remote, GitHub release, drift. Reports, never gates (only an unresolvable config exits non-zero)',
|
|
13
13
|
examples: [
|
|
14
14
|
['what is published and what is not', 'shipkit releases'],
|
|
15
15
|
['filesystem + local git only, no network', 'shipkit releases --offline'],
|
package/src/cli/cmd_Serve.ts
CHANGED
|
@@ -8,7 +8,7 @@ export class ServeCommand extends Command {
|
|
|
8
8
|
|
|
9
9
|
static override usage = Command.Usage({
|
|
10
10
|
category: 'Serve',
|
|
11
|
-
description: 'ONE server, ONE page: media studio + asks + creds + posts + health +
|
|
11
|
+
description: 'ONE server, ONE page: media studio + asks + creds + posts + health + wikis, each wiki a link graph page',
|
|
12
12
|
examples: [
|
|
13
13
|
['serve the repo toolbox', 'shipkit serve'],
|
|
14
14
|
['pin the port for this launch', 'shipkit serve --port 4804'],
|
package/src/cli/cmd_Start.ts
CHANGED
|
@@ -9,10 +9,10 @@ export class StartCommand extends Command {
|
|
|
9
9
|
|
|
10
10
|
static override usage = Command.Usage({
|
|
11
11
|
category: 'Setup',
|
|
12
|
-
description: 'create (or enter) ~/dev/<name> from ANY cwd, READY to work in. On any dir with no commit history it writes the day-one shape (package.json + src/index.ts + lint/fmt/typecheck, tsconfig, .vscode tasks + settings, the verify skill, an agent/ wiki, .
|
|
12
|
+
description: 'create (or enter) ~/dev/<name> from ANY cwd, READY to work in. On any dir with no commit history it writes the day-one shape (package.json + src/index.ts + lint/fmt/typecheck, tsconfig, .vscode tasks + settings, the verify skill, an agent/ wiki, .shipkit/reflections), installs the toolchain, wires shipkit (repo.config.ts via init), and runs the whole bootstrap chain: git init, first commit, PRIVATE github repo, push. Loose files ride in that first commit, and only a repo with real history is left as it is. Writes are create-only for files, additive for package.json scripts and .gitignore. CLAUDE.md is the two-line house template on its own (HOUSE_INCLUDE in src/start-repo.ts: the base wiki include, then `@./agent/index.md`, and --include overrides it), applied only on a machine where the house index exists, and the agent route lands last whenever the repo has agent/index.md. The house lines also opt the repo into the .claude/ policy files (a pre-approved shell allowlist + the attribution silencer), so a start with no house widens no agent permissions. Prints the dir on stdout so a shell alias can cd there. cwd-only sibling: `shipkit init`',
|
|
13
13
|
examples: [
|
|
14
|
-
['create or enter ~/dev/pong: dir, package.json, shipkit wiring, git, first commit, private github repo, push', 'shipkit start pong'],
|
|
15
|
-
['
|
|
14
|
+
['create or enter ~/dev/pong: dir, package.json, shipkit wiring, git, first commit, private github repo, push, the two-line CLAUDE.md', 'shipkit start pong'],
|
|
15
|
+
['another house: replace the include lines', "shipkit start pong --include '@~/wiki/index.md' --include '@./agent/index.md'"],
|
|
16
16
|
['adopt a pre-existing repo from anywhere', 'shipkit start my-old-project'],
|
|
17
17
|
['force the private github repo for a folder that was already there', 'shipkit start old-notes --github'],
|
|
18
18
|
['local git and the first commit, never touch github', 'shipkit start pong --no-github'],
|
|
@@ -21,9 +21,9 @@ export class StartCommand extends Command {
|
|
|
21
21
|
|
|
22
22
|
name = Option.String()
|
|
23
23
|
root = Option.String('--root', { description: 'parent dir for repos (default ~/dev)' })
|
|
24
|
-
include = Option.Array('--include', { description: 'line CLAUDE.md must carry, repeatable, order kept. Omitted
|
|
25
|
-
alsoOk = Option.Array('--also-ok', { description: 'alternative lines satisfying the first --include — never double-add' })
|
|
26
|
-
skip = Option.Array('--skip', { description: 'repo names shipkit never touches: no CLAUDE.md include, no wiring' })
|
|
24
|
+
include = Option.Array('--include', { description: 'line CLAUDE.md must carry, repeatable, order kept. Omitted: the house template (HOUSE_INCLUDE in src/start-repo.ts), the base wiki include then `@./agent/index.md`, and on a machine without that wiki the agent route alone' })
|
|
25
|
+
alsoOk = Option.Array('--also-ok', { description: 'alternative lines satisfying the first --include — never double-add. Omitted: `@./CLAUDE.local.md`, the public-repo indirection' })
|
|
26
|
+
skip = Option.Array('--skip', { description: 'repo names shipkit never touches: no CLAUDE.md include, no wiring. Omitted: the house list (HOUSE_SKIP in src/start-repo.ts)' })
|
|
27
27
|
github = Option.Boolean('--github', { description: 'ON by default when a repo is being STARTED (a dir this run created, or one with a package.json): it gets a PRIVATE github repo wired as origin, and the first commit is pushed to it. Pass --github to force it for a pre-existing folder, --no-github to keep everything local, the first commit included (SHIPKIT_NO_GITHUB=1 does it for every run)' })
|
|
28
28
|
|
|
29
29
|
async execute(): Promise<number> {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// shipkit status — read back a gate started with `shipkit ci --detach`. Thin
|
|
2
|
+
// clipanion surface; logic in core/jobs.ts.
|
|
3
|
+
import { Command, Option } from 'clipanion'
|
|
4
|
+
import { jobStatus, STILL_RUNNING } from '../core/jobs.ts'
|
|
5
|
+
|
|
6
|
+
export class StatusCommand extends Command {
|
|
7
|
+
static override paths = [['status']]
|
|
8
|
+
|
|
9
|
+
static override usage = Command.Usage({
|
|
10
|
+
category: 'Gate',
|
|
11
|
+
description: `a gate started with \`shipkit ci --detach\`: its wire lines so far, then its verdict. Exit code = the gate's own once it finished, ${STILL_RUNNING} while it still runs, 1 when it died without one`,
|
|
12
|
+
examples: [
|
|
13
|
+
['the latest detached run of this checkout', 'shipkit status'],
|
|
14
|
+
['one run by id', 'shipkit status ci-mf3k2a-1x2y'],
|
|
15
|
+
],
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
id = Option.String({ required: false })
|
|
19
|
+
|
|
20
|
+
async execute(): Promise<number> {
|
|
21
|
+
const r = jobStatus(process.cwd(), this.id)
|
|
22
|
+
for (const l of r.lines) this.context.stdout.write(`${l}\n`)
|
|
23
|
+
return r.code
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/cli/cmd_Tui.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// shipkit tui — the live dashboard: Tasks (the ci gate, streamed through
|
|
2
|
-
// the DAG runner) +
|
|
2
|
+
// the DAG runner) + checks (everything the health pass asks for) built in,
|
|
3
3
|
// repo extras injected via repo.config `tui: { panels: [...] }`.
|
|
4
4
|
// The body lives in src/tui/run-tui.ts and is loaded ONLY when the command
|
|
5
5
|
// runs: the react/ink chain must never load for `shipkit ci` in a repo
|
|
@@ -11,7 +11,7 @@ export class TuiCommand extends Command {
|
|
|
11
11
|
|
|
12
12
|
static override usage = Command.Usage({
|
|
13
13
|
category: 'Health',
|
|
14
|
-
description: 'live dashboard:
|
|
14
|
+
description: 'live dashboard: the gate build steps + every other check, plus panels the repo injects via repo.config tui.panels',
|
|
15
15
|
examples: [['open the dashboard', 'shipkit tui']],
|
|
16
16
|
})
|
|
17
17
|
|
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { AssetsAddCommand, AssetsCheckCommand, AssetsFillCommand, AssetsGenComma
|
|
|
9
9
|
import { BumpCommand } from './cli/cmd_Bump.ts'
|
|
10
10
|
import { CheckCommand } from './cli/cmd_Check.ts'
|
|
11
11
|
import { CommandsCommand } from './cli/cmd_Commands.ts'
|
|
12
|
+
import { ConfigKeysCommand } from './cli/cmd_ConfigKeys.ts'
|
|
12
13
|
import { CiCommand } from './cli/cmd_Ci.ts'
|
|
13
14
|
import { ContractsCommand } from './cli/cmd_Contracts.ts'
|
|
14
15
|
import { FleetCommand } from './cli/cmd_Fleet.ts'
|
|
@@ -22,10 +23,10 @@ import { ProseCommand } from './cli/cmd_Prose.ts'
|
|
|
22
23
|
import { ReflectCommand } from './cli/cmd_Reflect.ts'
|
|
23
24
|
import { ReleaseCommand } from './cli/cmd_Release.ts'
|
|
24
25
|
import { ReleasesCommand } from './cli/cmd_Releases.ts'
|
|
25
|
-
import {
|
|
26
|
-
import { UnitsCommand } from './cli/cmd_Units.ts'
|
|
26
|
+
import { ChecksCommand, UnitsAliasCommand } from './cli/cmd_Checks.ts'
|
|
27
27
|
import { ServeCommand } from './cli/cmd_Serve.ts'
|
|
28
28
|
import { SmokeCommand } from './cli/cmd_Smoke.ts'
|
|
29
|
+
import { StatusCommand } from './cli/cmd_Status.ts'
|
|
29
30
|
import { StartCommand } from './cli/cmd_Start.ts'
|
|
30
31
|
import { TuiCommand } from './cli/cmd_Tui.ts'
|
|
31
32
|
import { WikiLintCommand, WikiServeCommand } from './cli/cmd_Wiki.ts'
|
|
@@ -47,6 +48,7 @@ export function buildCli(): Cli {
|
|
|
47
48
|
cli.register(Builtins.HelpCommand)
|
|
48
49
|
cli.register(Builtins.VersionCommand)
|
|
49
50
|
cli.register(CiCommand)
|
|
51
|
+
cli.register(StatusCommand)
|
|
50
52
|
cli.register(BumpCommand)
|
|
51
53
|
cli.register(ReleaseCommand)
|
|
52
54
|
cli.register(ReleasesCommand)
|
|
@@ -77,10 +79,11 @@ export function buildCli(): Cli {
|
|
|
77
79
|
cli.register(ProseCommand)
|
|
78
80
|
cli.register(SmokeCommand)
|
|
79
81
|
cli.register(ReflectCommand)
|
|
80
|
-
cli.register(RulesCommand)
|
|
81
82
|
cli.register(KeysCommand)
|
|
82
|
-
cli.register(
|
|
83
|
+
cli.register(ChecksCommand)
|
|
84
|
+
cli.register(UnitsAliasCommand)
|
|
83
85
|
cli.register(CommandsCommand)
|
|
86
|
+
cli.register(ConfigKeysCommand)
|
|
84
87
|
|
|
85
88
|
return cli
|
|
86
89
|
}
|
|
@@ -10,7 +10,7 @@ const today = (): string => new Date().toISOString().slice(0, 10)
|
|
|
10
10
|
|
|
11
11
|
const postPath = (root: string, channel: string, file: string): string => {
|
|
12
12
|
if (channel.includes('/') || channel.includes('..') || file.includes('/') || file.includes('..')) throw new Error('bad path')
|
|
13
|
-
const p = join(root, '.
|
|
13
|
+
const p = join(root, '.shipkit/social', channel, file)
|
|
14
14
|
if (!existsSync(p)) throw new Error(`no such post: ${channel}/${file}`)
|
|
15
15
|
return p
|
|
16
16
|
}
|
|
@@ -25,7 +25,7 @@ const setStatus = (p: string, status: string): void => {
|
|
|
25
25
|
* (files KEPT — rejections are training data for the next generation) */
|
|
26
26
|
export function pickPostVariant(root: string, channel: string, file: string): void {
|
|
27
27
|
const p = postPath(root, channel, file)
|
|
28
|
-
const dir = join(root, '.
|
|
28
|
+
const dir = join(root, '.shipkit/social', channel)
|
|
29
29
|
const slug = parsePostFile(dir, channel, file).slug
|
|
30
30
|
setStatus(p, 'picked')
|
|
31
31
|
for (const f of readdirSync(dir).filter((x) => x.endsWith('.md') && x !== file)) {
|
|
@@ -55,11 +55,11 @@ export function appendPostFeedback(root: string, channel: string, file: string,
|
|
|
55
55
|
writeFileSync(p, /^## Feedback\s*$/m.test(md) ? `${md.trimEnd()}\n${line}\n` : `${md.trimEnd()}\n\n## Feedback\n${line}\n`)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/** an asset remark → `.
|
|
58
|
+
/** an asset remark → `.shipkit/assets/notes.md` (git-tracked; the studio
|
|
59
59
|
* workspace is gitignored, so notes live beside kit.ts) */
|
|
60
60
|
export function appendAssetNote(root: string, assetId: string, text: string): void {
|
|
61
61
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(assetId)) throw new Error('bad asset id')
|
|
62
|
-
const p = join(root, '.
|
|
62
|
+
const p = join(root, '.shipkit/assets', 'notes.md')
|
|
63
63
|
const line = `- ${today()} [${assetId}]: ${text.replace(/\n+/g, ' ').trim()}`
|
|
64
64
|
writeFileSync(p, existsSync(p) ? `${readFileSync(p, 'utf8').trimEnd()}\n${line}\n` : `# asset notes, read by agents at boot\n\n${line}\n`)
|
|
65
65
|
}
|