@friedbotstudio/create-baseline 0.5.0 → 0.6.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/README.md CHANGED
@@ -41,7 +41,7 @@ A discipline layer for Claude Code. Hooks at every tool boundary, a workflow tha
41
41
 
42
42
  ## What this is
43
43
 
44
- The Claude Code Baseline is a repository overlay shipped via `npx @friedbotstudio/create-baseline ./target`. It installs **22 hooks** at Claude's tool boundaries, **37 skills** organised into nine categories, **1 subagent** for parallel work in isolated worktrees, an **11-phase workflow** from intake to commit, and **3 user-typed consent gates** that Claude cannot forge.
44
+ The Claude Code Baseline is a repository overlay shipped via `npx @friedbotstudio/create-baseline ./target`. It installs **22 hooks** at Claude's tool boundaries, **38 skills** organised into ten categories, **1 subagent** for parallel work in isolated worktrees, an **11-phase workflow** from intake to commit, and **3 user-typed consent gates** that Claude cannot forge.
45
45
 
46
46
  Every soft engineering rule a team usually repeats every session — *don't push, don't `--amend`, don't self-approve specs, don't skip phases, don't mock internal modules* — becomes a structural guarantee because the hooks run **outside Claude's tool boundary**. Claude cannot disable a hook with a flag, cannot write a consent marker, cannot reorder the phases without an explicit exception that triage records on disk.
47
47
 
package/bin/cli.js CHANGED
@@ -18,7 +18,7 @@ const PKG_ROOT = resolve(__dirname, '..');
18
18
 
19
19
  const HELP_TEXT = `Usage:
20
20
  create-baseline <target> [options] install the baseline
21
- create-baseline upgrade [target] three-way merge against an installed baseline
21
+ create-baseline upgrade [target] three-tier merge (mechanical / semantic / binary-prompt)
22
22
  create-baseline doctor [target] report drift in an installed target
23
23
 
24
24
  Materializes the Claude Code baseline (.claude/, CLAUDE.md, .mcp.json,
@@ -31,10 +31,15 @@ Install modes:
31
31
 
32
32
  Upgrade:
33
33
  Replaces the prior --merge flag. Reads <target>/.claude/.baseline-manifest.json
34
- and runs a three-way merge against the shipped template. Prunes baseline files
35
- removed upstream that the user hadn't touched; customized-stale files are
36
- preserved (exit 3) or interactively resolved when stdout is a TTY (keep
37
- mine / take theirs / abort).
34
+ and runs a three-tier merge against the shipped template:
35
+ - tier 1 (binary prompt): customized files prompt "Keep your version / Use
36
+ new baseline / Show diff" in TTY mode (exit 3 on any skipped).
37
+ - tier 2 (mechanical): files routed through git merge-file --diff3 with
38
+ BASE recovered from .claude/.baseline-prior/ cache or npm fallback;
39
+ clean merges land silently, conflicts surface with markers (exit 4).
40
+ - tier 3 (semantic): staged at .claude/state/upgrade/<ts>/ for the
41
+ /upgrade-project Claude Code skill to reconcile (exit 5).
42
+ Prunes baseline files removed upstream that the user hadn't touched.
38
43
  --dry-run Print intended actions without writing.
39
44
 
40
45
  Doctor:
@@ -227,6 +232,13 @@ async function dispatchUpgrade(target, values, templateDir) {
227
232
  await usageError(`No baseline manifest at ${manifestPath}. Run a fresh install first.`);
228
233
  return 2;
229
234
  }
235
+ const { findPendingStage } = await import('../src/cli/upgrade-tiers.js');
236
+ const pending = await findPendingStage(target);
237
+ if (pending) {
238
+ const fileLines = pending.files.map((f) => ` - ${f}`).join('\n');
239
+ io.log(`Pending semantic-merge stage at ${pending.stage_ts}.\n${pending.files.length} file(s) awaiting reconciliation:\n${fileLines}\nOpen Claude Code and run /upgrade-project to reconcile.`);
240
+ return 5;
241
+ }
230
242
  if (process.stdout.isTTY) {
231
243
  const tui = await import('../src/cli/tui/upgrade.js');
232
244
  return tui.run({
@@ -241,17 +253,32 @@ async function runPlainUpgrade(target, values, templateDir, manifestPath) {
241
253
  const oldManifest = await loadManifest(manifestPath);
242
254
  const tplFiles = await listShippedFiles(templateDir);
243
255
  const newManifest = await buildManifestFromDir(templateDir, tplFiles);
256
+ await overlayShippedTiers(templateDir, newManifest);
244
257
  if (values['dry-run']) {
245
258
  io.log(`Would upgrade ${tplFiles.length} files into ${target}`);
246
259
  return 0;
247
260
  }
248
261
  const report = await threeWayMerge(templateDir, target, oldManifest, newManifest);
249
262
  for (const action of report.actions) {
250
- io.log(`${action.kind.padEnd(24)} ${action.path}`);
263
+ io.log(`${action.kind.padEnd(28)} ${action.path}`);
251
264
  }
252
265
  return report.exitCode;
253
266
  }
254
267
 
268
+ async function overlayShippedTiers(templateDir, newManifest) {
269
+ const shippedPath = join(templateDir, '.claude/manifest.json');
270
+ if (!existsSync(shippedPath)) return;
271
+ const { readFile: rf } = await import('node:fs/promises');
272
+ const shipped = JSON.parse(await rf(shippedPath, 'utf8'));
273
+ if (!shipped?.files) return;
274
+ for (const rel of Object.keys(newManifest.files)) {
275
+ const shippedEntry = shipped.files[rel];
276
+ if (shippedEntry && typeof shippedEntry === 'object' && typeof shippedEntry.tier === 'string') {
277
+ newManifest.files[rel] = { sha256: newManifest.files[rel], tier: shippedEntry.tier };
278
+ }
279
+ }
280
+ }
281
+
255
282
  async function dispatchDoctor(positionals, values) {
256
283
  const target = resolve(positionals[1] ?? '.');
257
284
  const report = await runDoctor(target, { strict: !!values.strict });