rvlib-shipkit 0.28.0 → 0.29.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.29.0
4
+
5
+ - **Creation output states facts instead of listing chores.** A fresh `start` ended on five lines that meant nothing to the person reading them: `skip: lint/typecheck/test (no … script)` about scripts a repo born one second ago has no use for, plus `next:` nags to fill venues and mint identity assets, neither of which anybody has decided on yet. `init` now reports the gate floor as ONE line naming what resolved, and `start` closes with one measured summary (`<name> is ready — N file(s) committed, pushed to <remote>`). Venues raise themselves through the contract checker the day one is declared, and identity through rule SK002 the day the repo grows a public surface. An output that lists undecided work trains the reader to skip all of it.
6
+
3
7
  ## 0.28.0
4
8
 
5
9
  - **The `.claude/` policy files are OPT-IN.** 0.27.0 wrote a pre-approved shell allowlist (`curl *`, `git push*`, `./deploy.sh`) and an attribution silencer into every started repo, gitignored and therefore invisible to review. Those are decisions about what an agent can do in a repo and what its git history says, so they now require the caller to declare a house (`claudePolicy`, which `start` enables when it seeds CLAUDE.md include lines), and the grant prints once by name when it lands. The verify skill still lands for everyone.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rvlib-shipkit",
3
- "version": "0.28.0",
3
+ "version": "0.29.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/init-repo.ts CHANGED
@@ -99,11 +99,15 @@ export async function initRepo(root: string, opts: InitOpts = {}): Promise<numbe
99
99
  // `shipkit check --fix SK004`, one code path
100
100
  const g = await gitignorePolicy.fix!({ root, config: defineRepo({ name }) })
101
101
  p(`ok: .gitignore policy block ${g.changed ? 'written' : 'already current'}`)
102
- // show what the gate actually is — a thin floor must be seen at init
103
- // time, not discovered after a bad commit slipped through
102
+ // what the gate actually runs, in ONE line: a thin floor must be seen at
103
+ // init time rather than discovered after a bad commit slipped through.
104
+ // The per-task resolution is diagnostics, and a `skip:` naming a script
105
+ // this repo has no use for yet reads as a chore list on a repo that was
106
+ // born a second ago. `shipkit ci` reports the same facts when they
107
+ // matter, and reports them about a repo that has code in it
104
108
  const floor = resolveFloor(defineRepo({ name }), root)
105
- for (const t of floor.tasks) p(`ok: floor ${t.id} ← ${floor.sources[t.id] ?? 'unknown'}`)
106
- for (const u of floor.unfilled) p(`skip: ${u.id} (${u.reason})`)
109
+ if (floor.tasks.length > 0) p(`ok: gate floor: ${floor.tasks.map((t) => t.id).join(', ')}`)
110
+ else p(`WARN the gate would check NOTHING here (${floor.unfilled.map((u) => u.id).join(', ')} all unresolved) — see \`shipkit rules\``)
107
111
  // every verb comes from the `shipkit` bin — dead on arrival unless the
108
112
  // package is actually installed (bunx runs without adding the dep)
109
113
  if (opts.bin == null) {
@@ -116,7 +120,11 @@ export async function initRepo(root: string, opts: InitOpts = {}): Promise<numbe
116
120
  }
117
121
  // last, so the first commit carries repo.config.ts and the scripts above
118
122
  if (wired && opts.noPublish !== true) await publishRepo(root, { github: opts.github, gh: opts.gh, print: p })
119
- p('next: fill venues/social in repo.config.ts, then `shipkit ci`')
120
- p('next: identity+assets — mint .rv-identity/brand.ts + .rv-assets/kit.ts, `shipkit assets identity` (the identity contract expects public/ favicon+og+logo)')
123
+ // no chore list here. Venues belong in repo.config the day a venue is
124
+ // CHOSEN, and the contract checker fails the gate from that day on.
125
+ // Identity is needed once the repo has a public surface, and rule SK002
126
+ // warns exactly then. Both told at creation time are noise about a
127
+ // decision nobody has made yet. `start` owns the one closing line
128
+ if (opts.noPublish !== true) p('next: `shipkit ci` is the gate — run it to check your work')
121
129
  return 0
122
130
  }
package/src/start-repo.ts CHANGED
@@ -148,6 +148,7 @@ export async function startRepo(name: string, opts: StartOpts = {}): Promise<num
148
148
  }
149
149
  // last line, so what to do next is the last thing read
150
150
  if (unscaffolded) console.error(`next: scaffold the app in ${dir} (package.json), then \`shipkit init\` inside it — the commit gates arm on the first ci run`)
151
+ else console.error(`ok: ${name} is ready — ${await summarize(dir)}`)
151
152
  }
152
153
  // a child process cannot cd its parent shell, so the dir goes to stdout
153
154
  // and a sourced wrapper does the move. Without one, say the move out
@@ -157,3 +158,20 @@ export async function startRepo(name: string, opts: StartOpts = {}): Promise<num
157
158
  console.log(dir)
158
159
  return 0
159
160
  }
161
+
162
+ /** the one closing fact: what this repo IS now, measured rather than
163
+ * narrated. Everything a repo still needs is reported by the gate, about a
164
+ * repo with code in it, on the day it matters */
165
+ async function summarize(dir: string): Promise<string> {
166
+ const git = runner('git', dir, 30_000)
167
+ const files = (await git(['ls-files'])).out.trim().split('\n').filter((l) => l !== '').length
168
+ const head = await git(['log', '-1', '--format=%h'])
169
+ // git merges stderr into stdout, so a missing remote answers with its
170
+ // error text: only a zero exit means that output is a url
171
+ const origin = await git(['remote', 'get-url', 'origin'])
172
+ const pushed = (await git(['rev-parse', '--verify', '@{upstream}'])).code === 0
173
+ const bits = [head.code === 0 ? `${files} file(s) committed` : 'not committed yet']
174
+ if (origin.code === 0) bits.push(pushed ? `pushed to ${origin.out.trim()}` : `remote wired, nothing pushed yet`)
175
+ else bits.push('local only, no remote')
176
+ return bits.join(', ')
177
+ }