@shipright/shipright 0.1.0-nightly.0 → 0.1.0-nightly.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +44 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,6 +57,8 @@ The installer adds managed instructions under `.shipright/managed/` and editable
57
57
 
58
58
  OpenCode, Claude Code, and Codex receive native instruction bindings for the T3 integration. Commit each repo installation before creating isolated worktrees, and open fresh agent sessions to verify loaded instructions. Missing context or harness capabilities must be reported, not assumed.
59
59
 
60
+ Claude Code uses `.claude/rules/shipright.md` to request reading the bootstrap when appropriate. The rule is loaded as project guidance; it does not itself inject the bootstrap's contents. Updating migrates intact legacy Claude bindings and preserves an existing `@AGENTS.md` import. Locally modified bindings or an unmanaged rule at that path must be reconciled before updating.
61
+
60
62
  ## Safety and limitations
61
63
 
62
64
  Updates never overwrite local project/shared rules. Modified managed files and binding blocks cause conflicts instead of forced replacement. All selected repo plans are checked before the first write, but errors during writing can still cause a partial installation across repositories; completed directories are reported. Do not run concurrent installers.
package/dist/cli.js CHANGED
@@ -2136,9 +2136,8 @@ var program = new Command;
2136
2136
 
2137
2137
  // packages/cli/src/installer.ts
2138
2138
  import { createHash as createHash2 } from "crypto";
2139
- import { lstat as lstat3, readFile as readFile2, readdir as readdir2, realpath as realpath3 } from "fs/promises";
2140
- import { dirname as dirname3, join as join2, resolve as resolve3, relative as relative3, sep as sep3 } from "path";
2141
- import { homedir } from "os";
2139
+ import { readFile as readFile2, readdir as readdir2, realpath as realpath3 } from "fs/promises";
2140
+ import { join as join2, resolve as resolve3, relative as relative3, sep as sep3 } from "path";
2142
2141
  import { existsSync } from "fs";
2143
2142
 
2144
2143
  // node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
@@ -3466,7 +3465,7 @@ function applyEdits(text, edits) {
3466
3465
  // packages/cli/package.json
3467
3466
  var package_default = {
3468
3467
  name: "@shipright/shipright",
3469
- version: "0.1.0-nightly.0",
3468
+ version: "0.1.0-nightly.1",
3470
3469
  description: "Configurable engineering workflows for coding agents. Ship fast. Ship right.",
3471
3470
  license: "MIT",
3472
3471
  keywords: ["coding-agents", "workflow", "t3-code", "opencode", "claude-code", "codex", "bun"],
@@ -3755,6 +3754,11 @@ var resourceRoot = existsSync(bundledResources) ? bundledResources : resolve3(im
3755
3754
  var version = package_default.version;
3756
3755
  var start = "<!-- shipright:start -->";
3757
3756
  var end = "<!-- shipright:end -->";
3757
+ var claudeRulePath = ".claude/rules/shipright.md";
3758
+ var claudeRule = `# Shipright
3759
+
3760
+ For a technical implementation request, explicitly read .shipright/bootstrap.md if it is not already loaded and apply Shipright. For a question or discussion, do not start implementation. Do not run multiple workflows for the same request.
3761
+ `;
3758
3762
  var formats = { formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
3759
3763
  ` } };
3760
3764
  var configPaths = ["opencode.jsonc", "opencode.json", ".opencode/opencode.jsonc", ".opencode/opencode.json"];
@@ -3790,42 +3794,6 @@ function replaceBlock(text, value) {
3790
3794
  ` : ""}${value}
3791
3795
  `;
3792
3796
  }
3793
- async function claudeContext(root) {
3794
- let directory = root;
3795
- let claudeExists = false;
3796
- let ancestorAgents = false;
3797
- while (true) {
3798
- const candidates = ["CLAUDE.md", "CLAUDE.local.md", ...directory === homedir() ? [] : [".claude/CLAUDE.md"]];
3799
- for (const path2 of candidates) {
3800
- try {
3801
- if ((await lstat3(join2(directory, path2))).isFile())
3802
- claudeExists = true;
3803
- } catch (error) {
3804
- if (!missing(error))
3805
- throw error;
3806
- }
3807
- }
3808
- if (directory !== root) {
3809
- for (const path2 of ["AGENTS.md", ".claude/AGENTS.md"]) {
3810
- try {
3811
- if ((await lstat3(join2(directory, path2))).isFile()) {
3812
- const content = await readFile2(join2(directory, path2), "utf8");
3813
- const managed = block(content);
3814
- if ((managed ? content.replace(managed, "") : content).trim())
3815
- ancestorAgents = true;
3816
- }
3817
- } catch (error) {
3818
- if (!missing(error))
3819
- throw error;
3820
- }
3821
- }
3822
- }
3823
- if (dirname3(directory) === directory)
3824
- break;
3825
- directory = dirname3(directory);
3826
- }
3827
- return { claudeExists, ancestorAgents };
3828
- }
3829
3797
  async function resources(scope) {
3830
3798
  const files = new Map;
3831
3799
  async function visit2(dir) {
@@ -3894,7 +3862,7 @@ function validateManifest(value, allowedManaged) {
3894
3862
  if (b.target === "opencode") {
3895
3863
  if (b.kind !== "instructions" || !configPaths.includes(b.path) || typeof b.instructionsCreated !== "boolean")
3896
3864
  throw new Error("Invalid OpenCode binding");
3897
- } else if (b.kind !== "block" || !(b.target === "codex" ? instructionFiles.slice(0, 2) : ["CLAUDE.md", ".claude/CLAUDE.md", ...m.scope === "workspace" ? [".claude/rules/shipright.md"] : []]).includes(b.path) || !/^[a-f0-9]{64}$/.test(b.sha256 ?? "")) {
3865
+ } else if (!/^[a-f0-9]{64}$/.test(b.sha256 ?? "") || (b.target === "codex" ? b.kind !== "block" || !instructionFiles.slice(0, 2).includes(b.path) : !(b.kind === "file" && b.path === claudeRulePath && b.owned && b.fileCreated || b.kind === "block" && ["CLAUDE.md", ".claude/CLAUDE.md", ...m.scope === "workspace" ? [claudeRulePath] : []].includes(b.path)))) {
3898
3866
  throw new Error("Invalid instruction binding");
3899
3867
  }
3900
3868
  }
@@ -3940,6 +3908,8 @@ async function plan(options) {
3940
3908
  if (b.kind === "block" && (text === null || hash(block(text) ?? "") !== b.sha256)) {
3941
3909
  throw new Error(`Shipright block modified or missing: ${b.path}`);
3942
3910
  }
3911
+ if (b.kind === "file" && (text === null || hash(text) !== b.sha256))
3912
+ throw new Error(`Shipright rule modified or missing: ${b.path}`);
3943
3913
  if (b.kind === "instructions") {
3944
3914
  const present = [];
3945
3915
  for (const path2 of configPaths)
@@ -3963,8 +3933,11 @@ async function plan(options) {
3963
3933
  if (!b.owned)
3964
3934
  continue;
3965
3935
  const text = await current(b.path);
3966
- if (b.kind === "block") {
3967
- const after = replaceBlock(text, null);
3936
+ if (b.kind === "file") {
3937
+ await set(b.path, null);
3938
+ } else if (b.kind === "block") {
3939
+ const keepImport = b.target === "claude-code" && b.path === "CLAUDE.md" && /^@AGENTS\.md$/m.test(block(text) ?? "");
3940
+ const after = replaceBlock(text, keepImport ? "@AGENTS.md" : null);
3968
3941
  await set(b.path, b.fileCreated && !after.trim() ? null : after);
3969
3942
  } else {
3970
3943
  const config = objectConfig(text, b.path);
@@ -3987,6 +3960,21 @@ async function plan(options) {
3987
3960
  bindings: [...previous?.bindings ?? []],
3988
3961
  scope
3989
3962
  };
3963
+ const legacyClaude = installation.bindings.find((b) => b.target === "claude-code" && b.kind === "block");
3964
+ if (legacyClaude) {
3965
+ const old = await current(legacyClaude.path);
3966
+ const oldBlock = block(old);
3967
+ const preserveImport = legacyClaude.path === "CLAUDE.md" && /^@AGENTS\.md$/m.test(oldBlock);
3968
+ const after = replaceBlock(old, preserveImport ? "@AGENTS.md" : null);
3969
+ if (legacyClaude.path === claudeRulePath) {
3970
+ if (after.trim())
3971
+ throw new Error(`Unmanaged content in legacy Claude rule: ${claudeRulePath}`);
3972
+ await set(claudeRulePath, null);
3973
+ } else {
3974
+ await set(legacyClaude.path, legacyClaude.fileCreated && !after.trim() ? null : after);
3975
+ }
3976
+ installation.bindings = installation.bindings.filter((b) => b !== legacyClaude);
3977
+ }
3990
3978
  for (const [path2, text] of available) {
3991
3979
  if (isLocal(path2)) {
3992
3980
  if (await current(path2) === null)
@@ -3999,7 +3987,7 @@ async function plan(options) {
3999
3987
  installation.managedFiles[path2] = { sha256: hash(text) };
4000
3988
  }
4001
3989
  }
4002
- for (const target of [...new Set(selected)]) {
3990
+ for (const target of [...new Set([...selected, ...legacyClaude ? ["claude-code"] : []])]) {
4003
3991
  if (installation.bindings.some((b) => b.target === target))
4004
3992
  continue;
4005
3993
  if (target === "opencode") {
@@ -4025,23 +4013,18 @@ async function plan(options) {
4025
4013
  text = applyEdits(text, modify(text, ["instructions"], [...list, instructionPath], formats));
4026
4014
  await set(path2, text);
4027
4015
  installation.bindings.push({ target, path: path2, kind: "instructions", owned, fileCreated: existing === null, instructionsCreated: config.instructions === undefined });
4016
+ } else if (target === "claude-code") {
4017
+ if (await current(claudeRulePath) !== null)
4018
+ throw new Error(`Unmanaged file already exists: ${claudeRulePath}`);
4019
+ await set(claudeRulePath, claudeRule);
4020
+ installation.bindings.push({ target, path: claudeRulePath, kind: "file", owned: true, fileCreated: true, sha256: hash(claudeRule) });
4028
4021
  } else {
4029
4022
  const override = await current("AGENTS.override.md");
4030
- const claudePath = scope === "workspace" ? ".claude/rules/shipright.md" : await current("CLAUDE.md") !== null ? "CLAUDE.md" : await current(".claude/CLAUDE.md") !== null ? ".claude/CLAUDE.md" : "CLAUDE.md";
4031
- const path2 = target === "claude-code" ? claudePath : override?.trim() ? "AGENTS.override.md" : "AGENTS.md";
4023
+ const path2 = override?.trim() ? "AGENTS.override.md" : "AGENTS.md";
4032
4024
  const existing = await current(path2);
4033
4025
  if (block(existing ?? ""))
4034
4026
  throw new Error(`Unregistered Shipright block: ${path2}`);
4035
- const context = target === "claude-code" && scope === "repo" ? await claudeContext(root) : null;
4036
- if (context && existing === null && !context.claudeExists && context.ancestorAgents) {
4037
- throw new Error("Creating CLAUDE.md could mask ancestor AGENTS.md instructions; reconcile Claude instruction sources first");
4038
- }
4039
- const preserveAgents = target === "claude-code" && scope === "repo" && existing === null && !context?.claudeExists && (await current("AGENTS.md"))?.trim();
4040
- const claudeImport = path2 === ".claude/CLAUDE.md" ? "@../.shipright/bootstrap.md" : "@.shipright/bootstrap.md";
4041
- const content = target === "claude-code" && scope === "repo" ? `${start}
4042
- ${preserveAgents ? `@AGENTS.md
4043
- ` : ""}${claudeImport}
4044
- ${end}` : `${start}
4027
+ const content = `${start}
4045
4028
  For a technical implementation request, explicitly read .shipright/bootstrap.md if not already loaded and apply Shipright. For a question or discussion, do not start implementation. Do not run multiple workflows for the same request.
4046
4029
  ${end}`;
4047
4030
  await set(path2, replaceBlock(existing ?? "", content));
@@ -5463,11 +5446,11 @@ var SELECT_INSTRUCTIONS = [
5463
5446
  var i = `${styleText2("gray", S_BAR)} `;
5464
5447
 
5465
5448
  // packages/cli/src/cli-presentation.ts
5466
- import { dirname as dirname5, relative as relative4, resolve as resolve5 } from "path";
5449
+ import { dirname as dirname4, relative as relative4, resolve as resolve5 } from "path";
5467
5450
 
5468
5451
  // packages/cli/src/workspace-install.ts
5469
5452
  import { realpath as realpath4 } from "fs/promises";
5470
- import { dirname as dirname4, join as join3, resolve as resolve4 } from "path";
5453
+ import { dirname as dirname3, join as join3, resolve as resolve4 } from "path";
5471
5454
  async function planWorkspaceInstallation(options) {
5472
5455
  const root = await realpath4(resolve4(options.root));
5473
5456
  const config = await planWorkspace(root, options.repos);
@@ -5486,7 +5469,7 @@ async function planWorkspaceInstallation(options) {
5486
5469
  const repository = workspace.value.repositories[id];
5487
5470
  if (!repository)
5488
5471
  throw new Error(`Repository is not registered: ${id}`);
5489
- const location = await realpath4(resolve4(dirname4(workspace.file), repository.path));
5472
+ const location = await realpath4(resolve4(dirname3(workspace.file), repository.path));
5490
5473
  if (roots.has(location))
5491
5474
  throw new Error(`Duplicate or invalid repository location: ${id}`);
5492
5475
  roots.add(location);
@@ -5550,13 +5533,13 @@ async function choose(options) {
5550
5533
  if (entries.length) {
5551
5534
  const selected = await multiselect({
5552
5535
  message: "Select repositories to initialize and link",
5553
- options: entries.map(([id, repo]) => ({ value: id, label: id, hint: resolve5(dirname5(workspace.file), repo.path) })),
5536
+ options: entries.map(([id, repo]) => ({ value: id, label: id, hint: resolve5(dirname4(workspace.file), repo.path) })),
5554
5537
  initialValues: entries.map(([id]) => id),
5555
5538
  required: true
5556
5539
  });
5557
5540
  if (cancelled(selected))
5558
5541
  return false;
5559
- options.repo = selected.map((id) => `${id}=${relative4(workspace.root, resolve5(dirname5(workspace.file), workspace.value.repositories[id].path))}`);
5542
+ options.repo = selected.map((id) => `${id}=${relative4(workspace.root, resolve5(dirname4(workspace.file), workspace.value.repositories[id].path))}`);
5560
5543
  }
5561
5544
  }
5562
5545
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipright/shipright",
3
- "version": "0.1.0-nightly.0",
3
+ "version": "0.1.0-nightly.1",
4
4
  "description": "Configurable engineering workflows for coding agents. Ship fast. Ship right.",
5
5
  "license": "MIT",
6
6
  "keywords": ["coding-agents", "workflow", "t3-code", "opencode", "claude-code", "codex", "bun"],