@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 +2 -0
- package/dist/cli.js +61 -64
- package/dist/resources/coordinator.md +2 -0
- package/package.json +18 -4
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,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.
|
|
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: [
|
|
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: [
|
|
3478
|
-
|
|
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.
|
|
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 === "
|
|
3967
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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": [
|
|
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": [
|
|
12
|
-
|
|
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",
|