@shipright/shipright 0.1.0-nightly.0 → 0.1.0-nightly.2

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
@@ -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,16 +3465,30 @@ 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.2",
3470
3469
  description: "Configurable engineering workflows for coding agents. Ship fast. Ship right.",
3471
3470
  license: "MIT",
3472
- keywords: ["coding-agents", "workflow", "t3-code", "opencode", "claude-code", "codex", "bun"],
3471
+ keywords: [
3472
+ "coding-agents",
3473
+ "workflow",
3474
+ "t3-code",
3475
+ "opencode",
3476
+ "claude-code",
3477
+ "codex",
3478
+ "bun"
3479
+ ],
3473
3480
  type: "module",
3474
3481
  bin: {
3475
3482
  shipright: "dist/cli.js"
3476
3483
  },
3477
- files: ["dist", "README.md", "LICENSE"],
3478
- engines: { bun: ">=1.3.10" },
3484
+ files: [
3485
+ "dist",
3486
+ "README.md",
3487
+ "LICENSE"
3488
+ ],
3489
+ engines: {
3490
+ bun: ">=1.3.10"
3491
+ },
3479
3492
  publishConfig: {
3480
3493
  access: "public",
3481
3494
  tag: "nightly",
@@ -3755,6 +3768,11 @@ var resourceRoot = existsSync(bundledResources) ? bundledResources : resolve3(im
3755
3768
  var version = package_default.version;
3756
3769
  var start = "<!-- shipright:start -->";
3757
3770
  var end = "<!-- shipright:end -->";
3771
+ var claudeRulePath = ".claude/rules/shipright.md";
3772
+ var claudeRule = `# Shipright
3773
+
3774
+ 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.
3775
+ `;
3758
3776
  var formats = { formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
3759
3777
  ` } };
3760
3778
  var configPaths = ["opencode.jsonc", "opencode.json", ".opencode/opencode.jsonc", ".opencode/opencode.json"];
@@ -3790,42 +3808,6 @@ function replaceBlock(text, value) {
3790
3808
  ` : ""}${value}
3791
3809
  `;
3792
3810
  }
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
3811
  async function resources(scope) {
3830
3812
  const files = new Map;
3831
3813
  async function visit2(dir) {
@@ -3894,7 +3876,7 @@ function validateManifest(value, allowedManaged) {
3894
3876
  if (b.target === "opencode") {
3895
3877
  if (b.kind !== "instructions" || !configPaths.includes(b.path) || typeof b.instructionsCreated !== "boolean")
3896
3878
  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 ?? "")) {
3879
+ } 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
3880
  throw new Error("Invalid instruction binding");
3899
3881
  }
3900
3882
  }
@@ -3940,6 +3922,8 @@ async function plan(options) {
3940
3922
  if (b.kind === "block" && (text === null || hash(block(text) ?? "") !== b.sha256)) {
3941
3923
  throw new Error(`Shipright block modified or missing: ${b.path}`);
3942
3924
  }
3925
+ if (b.kind === "file" && (text === null || hash(text) !== b.sha256))
3926
+ throw new Error(`Shipright rule modified or missing: ${b.path}`);
3943
3927
  if (b.kind === "instructions") {
3944
3928
  const present = [];
3945
3929
  for (const path2 of configPaths)
@@ -3963,8 +3947,11 @@ async function plan(options) {
3963
3947
  if (!b.owned)
3964
3948
  continue;
3965
3949
  const text = await current(b.path);
3966
- if (b.kind === "block") {
3967
- const after = replaceBlock(text, null);
3950
+ if (b.kind === "file") {
3951
+ await set(b.path, null);
3952
+ } else if (b.kind === "block") {
3953
+ const keepImport = b.target === "claude-code" && b.path === "CLAUDE.md" && /^@AGENTS\.md$/m.test(block(text) ?? "");
3954
+ const after = replaceBlock(text, keepImport ? "@AGENTS.md" : null);
3968
3955
  await set(b.path, b.fileCreated && !after.trim() ? null : after);
3969
3956
  } else {
3970
3957
  const config = objectConfig(text, b.path);
@@ -3987,6 +3974,21 @@ async function plan(options) {
3987
3974
  bindings: [...previous?.bindings ?? []],
3988
3975
  scope
3989
3976
  };
3977
+ const legacyClaude = installation.bindings.find((b) => b.target === "claude-code" && b.kind === "block");
3978
+ if (legacyClaude) {
3979
+ const old = await current(legacyClaude.path);
3980
+ const oldBlock = block(old);
3981
+ const preserveImport = legacyClaude.path === "CLAUDE.md" && /^@AGENTS\.md$/m.test(oldBlock);
3982
+ const after = replaceBlock(old, preserveImport ? "@AGENTS.md" : null);
3983
+ if (legacyClaude.path === claudeRulePath) {
3984
+ if (after.trim())
3985
+ throw new Error(`Unmanaged content in legacy Claude rule: ${claudeRulePath}`);
3986
+ await set(claudeRulePath, null);
3987
+ } else {
3988
+ await set(legacyClaude.path, legacyClaude.fileCreated && !after.trim() ? null : after);
3989
+ }
3990
+ installation.bindings = installation.bindings.filter((b) => b !== legacyClaude);
3991
+ }
3990
3992
  for (const [path2, text] of available) {
3991
3993
  if (isLocal(path2)) {
3992
3994
  if (await current(path2) === null)
@@ -3999,7 +4001,7 @@ async function plan(options) {
3999
4001
  installation.managedFiles[path2] = { sha256: hash(text) };
4000
4002
  }
4001
4003
  }
4002
- for (const target of [...new Set(selected)]) {
4004
+ for (const target of [...new Set([...selected, ...legacyClaude ? ["claude-code"] : []])]) {
4003
4005
  if (installation.bindings.some((b) => b.target === target))
4004
4006
  continue;
4005
4007
  if (target === "opencode") {
@@ -4025,23 +4027,18 @@ async function plan(options) {
4025
4027
  text = applyEdits(text, modify(text, ["instructions"], [...list, instructionPath], formats));
4026
4028
  await set(path2, text);
4027
4029
  installation.bindings.push({ target, path: path2, kind: "instructions", owned, fileCreated: existing === null, instructionsCreated: config.instructions === undefined });
4030
+ } else if (target === "claude-code") {
4031
+ if (await current(claudeRulePath) !== null)
4032
+ throw new Error(`Unmanaged file already exists: ${claudeRulePath}`);
4033
+ await set(claudeRulePath, claudeRule);
4034
+ installation.bindings.push({ target, path: claudeRulePath, kind: "file", owned: true, fileCreated: true, sha256: hash(claudeRule) });
4028
4035
  } else {
4029
4036
  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";
4037
+ const path2 = override?.trim() ? "AGENTS.override.md" : "AGENTS.md";
4032
4038
  const existing = await current(path2);
4033
4039
  if (block(existing ?? ""))
4034
4040
  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}
4041
+ const content = `${start}
4045
4042
  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
4043
  ${end}`;
4047
4044
  await set(path2, replaceBlock(existing ?? "", content));
@@ -5463,11 +5460,11 @@ var SELECT_INSTRUCTIONS = [
5463
5460
  var i = `${styleText2("gray", S_BAR)} `;
5464
5461
 
5465
5462
  // packages/cli/src/cli-presentation.ts
5466
- import { dirname as dirname5, relative as relative4, resolve as resolve5 } from "path";
5463
+ import { dirname as dirname4, relative as relative4, resolve as resolve5 } from "path";
5467
5464
 
5468
5465
  // packages/cli/src/workspace-install.ts
5469
5466
  import { realpath as realpath4 } from "fs/promises";
5470
- import { dirname as dirname4, join as join3, resolve as resolve4 } from "path";
5467
+ import { dirname as dirname3, join as join3, resolve as resolve4 } from "path";
5471
5468
  async function planWorkspaceInstallation(options) {
5472
5469
  const root = await realpath4(resolve4(options.root));
5473
5470
  const config = await planWorkspace(root, options.repos);
@@ -5486,7 +5483,7 @@ async function planWorkspaceInstallation(options) {
5486
5483
  const repository = workspace.value.repositories[id];
5487
5484
  if (!repository)
5488
5485
  throw new Error(`Repository is not registered: ${id}`);
5489
- const location = await realpath4(resolve4(dirname4(workspace.file), repository.path));
5486
+ const location = await realpath4(resolve4(dirname3(workspace.file), repository.path));
5490
5487
  if (roots.has(location))
5491
5488
  throw new Error(`Duplicate or invalid repository location: ${id}`);
5492
5489
  roots.add(location);
@@ -5550,13 +5547,13 @@ async function choose(options) {
5550
5547
  if (entries.length) {
5551
5548
  const selected = await multiselect({
5552
5549
  message: "Select repositories to initialize and link",
5553
- options: entries.map(([id, repo]) => ({ value: id, label: id, hint: resolve5(dirname5(workspace.file), repo.path) })),
5550
+ options: entries.map(([id, repo]) => ({ value: id, label: id, hint: resolve5(dirname4(workspace.file), repo.path) })),
5554
5551
  initialValues: entries.map(([id]) => id),
5555
5552
  required: true
5556
5553
  });
5557
5554
  if (cancelled(selected))
5558
5555
  return false;
5559
- options.repo = selected.map((id) => `${id}=${relative4(workspace.root, resolve5(dirname5(workspace.file), workspace.value.repositories[id].path))}`);
5556
+ options.repo = selected.map((id) => `${id}=${relative4(workspace.root, resolve5(dirname4(workspace.file), workspace.value.repositories[id].path))}`);
5560
5557
  }
5561
5558
  }
5562
5559
  return true;
@@ -22,6 +22,8 @@ You manage handoffs between responsibilities and acceptance conditions; do not r
22
22
 
23
23
  Read cycle and budget limits in the project contract's `workflow.md`; session-specific settings may supplement them under project rules. Ask for them if missing. Stop on exhausted budget, churn without progress, a missing required capability, or an out-of-mandate blocker. Present options and a recommendation, without false acceptance or escalating every ordinary detail.
24
24
 
25
+ Apply any authorized model, provider, and fallback policy from the applicable contract. Check available capabilities before delegation; catalog presence alone does not establish entitlement or cost. Use only authorized fallbacks, report the actual selection and reason for any substitution, and stop to ask if none is available. Never knowingly switch to unapproved paid usage. Instructions cannot enforce provider billing settings; report uncertainty rather than assuming protection. A fallback does not relax review independence.
26
+
25
27
  ## Recovery, learning, and reporting
26
28
 
27
29
  After an interruption, recover tasks, worktree, decisions, findings, and results before restarting. Do not duplicate operations whose outcome is unknown; check their state first.
package/package.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
2
  "name": "@shipright/shipright",
3
- "version": "0.1.0-nightly.0",
3
+ "version": "0.1.0-nightly.2",
4
4
  "description": "Configurable engineering workflows for coding agents. Ship fast. Ship right.",
5
5
  "license": "MIT",
6
- "keywords": ["coding-agents", "workflow", "t3-code", "opencode", "claude-code", "codex", "bun"],
6
+ "keywords": [
7
+ "coding-agents",
8
+ "workflow",
9
+ "t3-code",
10
+ "opencode",
11
+ "claude-code",
12
+ "codex",
13
+ "bun"
14
+ ],
7
15
  "type": "module",
8
16
  "bin": {
9
17
  "shipright": "dist/cli.js"
10
18
  },
11
- "files": ["dist", "README.md", "LICENSE"],
12
- "engines": { "bun": ">=1.3.10" },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "engines": {
25
+ "bun": ">=1.3.10"
26
+ },
13
27
  "publishConfig": {
14
28
  "access": "public",
15
29
  "tag": "nightly",