@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.
- package/README.md +2 -0
- package/dist/cli.js +44 -61
- 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 {
|
|
2140
|
-
import {
|
|
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.
|
|
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.
|
|
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 === "
|
|
3967
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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"],
|