@rness/cli 0.9.2 → 0.10.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 +48 -1
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-JKDF7ROY.js → chunk-R4BRAAC2.js} +552 -312
- package/dist/chunk-R4BRAAC2.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-JKDF7ROY.js.map +0 -1
|
@@ -558,8 +558,8 @@ async function getOrgMembership(org, options) {
|
|
|
558
558
|
}
|
|
559
559
|
if (status2 === 404) return "not-member";
|
|
560
560
|
if (status2 === 403) {
|
|
561
|
-
const
|
|
562
|
-
if (/OAuth App access restrictions/i.test(
|
|
561
|
+
const message2 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
562
|
+
if (/OAuth App access restrictions/i.test(message2)) return "restricted";
|
|
563
563
|
}
|
|
564
564
|
return "unknown";
|
|
565
565
|
}
|
|
@@ -581,8 +581,8 @@ async function createRepository(owner, name, options) {
|
|
|
581
581
|
throw new Error(`cannot reach the GitHub API: ${reason2(e)}`, { cause: e });
|
|
582
582
|
}
|
|
583
583
|
const body = await res.json().catch(() => void 0);
|
|
584
|
-
const
|
|
585
|
-
return { status: res.status, message };
|
|
584
|
+
const message2 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
585
|
+
return { status: res.status, message: message2 };
|
|
586
586
|
};
|
|
587
587
|
let answer = await post2(`/orgs/${encodeURIComponent(owner)}/repos`);
|
|
588
588
|
if (answer.status === 404 && options.self !== void 0 && options.self !== null && options.self.toLowerCase() === owner.toLowerCase())
|
|
@@ -775,9 +775,9 @@ async function originUrl(dir) {
|
|
|
775
775
|
async function init(dir) {
|
|
776
776
|
await git(["init", "-q", "-b", "main"], dir);
|
|
777
777
|
}
|
|
778
|
-
async function commitAll(dir,
|
|
778
|
+
async function commitAll(dir, message2) {
|
|
779
779
|
await git(["add", "-A"], dir);
|
|
780
|
-
await git(["commit", "-q", "-m",
|
|
780
|
+
await git(["commit", "-q", "-m", message2], dir);
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
// src/core/remote.ts
|
|
@@ -812,7 +812,7 @@ function classifyProbe(url, exitCode, stderr) {
|
|
|
812
812
|
var STDERR_FLUSH_MS = 200;
|
|
813
813
|
async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
814
814
|
positional(url, "repository url");
|
|
815
|
-
return new Promise((
|
|
815
|
+
return new Promise((resolve8) => {
|
|
816
816
|
const auth = withCredentials(credentials);
|
|
817
817
|
const child = spawn(
|
|
818
818
|
"git",
|
|
@@ -831,7 +831,7 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
|
831
831
|
settled = true;
|
|
832
832
|
clearTimeout(timer);
|
|
833
833
|
child.stderr?.destroy();
|
|
834
|
-
|
|
834
|
+
resolve8(probe);
|
|
835
835
|
};
|
|
836
836
|
const timer = setTimeout(() => {
|
|
837
837
|
child.kill("SIGKILL");
|
|
@@ -876,8 +876,8 @@ function isWorkspaceClone(name) {
|
|
|
876
876
|
var NAME_RULE = "lowercase letters, digits, '.', '_' and '-', not starting with '-'";
|
|
877
877
|
var ORG_NAME = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
|
|
878
878
|
var KEYS = ["contract", "org", "agents", "repos", "scopes"];
|
|
879
|
-
function fail(
|
|
880
|
-
throw new Error(`rness.json: ${
|
|
879
|
+
function fail(message2) {
|
|
880
|
+
throw new Error(`rness.json: ${message2}`);
|
|
881
881
|
}
|
|
882
882
|
function isRecord(value) {
|
|
883
883
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1263,7 +1263,7 @@ async function detectGithubSsh(opts) {
|
|
|
1263
1263
|
...opts.interactive ? [] : ["-o", "BatchMode=yes"],
|
|
1264
1264
|
"git@github.com"
|
|
1265
1265
|
];
|
|
1266
|
-
return new Promise((
|
|
1266
|
+
return new Promise((resolve8) => {
|
|
1267
1267
|
const child = spawn2(opts.command ?? "ssh", args, {
|
|
1268
1268
|
stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
|
|
1269
1269
|
});
|
|
@@ -1276,7 +1276,7 @@ async function detectGithubSsh(opts) {
|
|
|
1276
1276
|
settled = true;
|
|
1277
1277
|
clearTimeout(timer);
|
|
1278
1278
|
child.stderr?.destroy();
|
|
1279
|
-
|
|
1279
|
+
resolve8(access2);
|
|
1280
1280
|
};
|
|
1281
1281
|
const timer = setTimeout(() => {
|
|
1282
1282
|
child.kill("SIGKILL");
|
|
@@ -1397,15 +1397,15 @@ function reportError(e) {
|
|
|
1397
1397
|
process.stderr.write(`${e.stack}
|
|
1398
1398
|
`);
|
|
1399
1399
|
} else {
|
|
1400
|
-
const
|
|
1401
|
-
process.stderr.write(`${paint("error",
|
|
1400
|
+
const message2 = e instanceof Error ? e.message : String(e);
|
|
1401
|
+
process.stderr.write(`${paint("error", message2, process.stderr)}
|
|
1402
1402
|
`);
|
|
1403
1403
|
}
|
|
1404
1404
|
return 1;
|
|
1405
1405
|
}
|
|
1406
1406
|
|
|
1407
1407
|
// src/version.ts
|
|
1408
|
-
var VERSION = "0.
|
|
1408
|
+
var VERSION = "0.10.0";
|
|
1409
1409
|
|
|
1410
1410
|
// src/core/block.ts
|
|
1411
1411
|
import { createHash } from "crypto";
|
|
@@ -1473,8 +1473,8 @@ function isCurrentBlock(blockLines, freshHash) {
|
|
|
1473
1473
|
// src/core/frontmatter.ts
|
|
1474
1474
|
import { parse as parse2 } from "yaml";
|
|
1475
1475
|
var FRONT_MATTER = /^---\r?\n(?:([\s\S]*?)\r?\n)?---(?:\r?\n|$)/;
|
|
1476
|
-
function fail2(
|
|
1477
|
-
throw new Error(`front matter: ${
|
|
1476
|
+
function fail2(message2) {
|
|
1477
|
+
throw new Error(`front matter: ${message2}`);
|
|
1478
1478
|
}
|
|
1479
1479
|
function stripBom(source) {
|
|
1480
1480
|
return source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
@@ -1706,6 +1706,46 @@ import { mkdir as mkdir2 } from "fs/promises";
|
|
|
1706
1706
|
import { dirname as dirname4, join as join9 } from "path";
|
|
1707
1707
|
|
|
1708
1708
|
// src/core/agents.ts
|
|
1709
|
+
var PINNED_BIN = "node_modules/@rness/cli/dist/bin/rness.js";
|
|
1710
|
+
function hookLine(rness, event) {
|
|
1711
|
+
const run3 = `f="$CLAUDE_PROJECT_DIR/${rness}/${PINNED_BIN}"; if [ -f "$f" ]; then node "$f" hook ${event};`;
|
|
1712
|
+
if (event === "post-tool-use") return `${run3} fi`;
|
|
1713
|
+
const missing = JSON.stringify({
|
|
1714
|
+
systemMessage: `rness: ${rness} is not installed, so the workspace context is not loaded. Clone the workspace, then install its dependencies in .rness.`
|
|
1715
|
+
});
|
|
1716
|
+
return `${run3} else echo '${missing}'; fi`;
|
|
1717
|
+
}
|
|
1718
|
+
function hooks(rness) {
|
|
1719
|
+
return [
|
|
1720
|
+
{
|
|
1721
|
+
path: ["hooks", "SessionStart"],
|
|
1722
|
+
contains: {
|
|
1723
|
+
hooks: [
|
|
1724
|
+
{
|
|
1725
|
+
type: "command",
|
|
1726
|
+
command: hookLine(rness, "session-start"),
|
|
1727
|
+
timeout: 10
|
|
1728
|
+
}
|
|
1729
|
+
]
|
|
1730
|
+
},
|
|
1731
|
+
label: "the rness session-start hook"
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
path: ["hooks", "PostToolUse"],
|
|
1735
|
+
contains: {
|
|
1736
|
+
matcher: "Edit|Write",
|
|
1737
|
+
hooks: [
|
|
1738
|
+
{
|
|
1739
|
+
type: "command",
|
|
1740
|
+
command: hookLine(rness, "post-tool-use"),
|
|
1741
|
+
timeout: 10
|
|
1742
|
+
}
|
|
1743
|
+
]
|
|
1744
|
+
},
|
|
1745
|
+
label: "the rness post-tool-use hook"
|
|
1746
|
+
}
|
|
1747
|
+
];
|
|
1748
|
+
}
|
|
1709
1749
|
var TARGETS = {
|
|
1710
1750
|
claude: {
|
|
1711
1751
|
name: "claude",
|
|
@@ -1713,6 +1753,7 @@ var TARGETS = {
|
|
|
1713
1753
|
files: [
|
|
1714
1754
|
{
|
|
1715
1755
|
file: ".claude/settings.json",
|
|
1756
|
+
at: "clones",
|
|
1716
1757
|
guarantees: [
|
|
1717
1758
|
// Read the context repository from a repository without a prompt.
|
|
1718
1759
|
// Resolved against the project directory, and applied once Claude
|
|
@@ -1720,11 +1761,13 @@ var TARGETS = {
|
|
|
1720
1761
|
{
|
|
1721
1762
|
path: ["permissions", "additionalDirectories"],
|
|
1722
1763
|
contains: "../../.rness"
|
|
1723
|
-
}
|
|
1764
|
+
},
|
|
1765
|
+
...hooks("../../.rness")
|
|
1724
1766
|
]
|
|
1725
1767
|
},
|
|
1726
1768
|
{
|
|
1727
1769
|
file: ".mcp.json",
|
|
1770
|
+
at: "clones",
|
|
1728
1771
|
guarantees: [
|
|
1729
1772
|
// The pinned copy, by a path relative to the repository — the
|
|
1730
1773
|
// directory Claude Code starts the server in (spec 0014 §6). Not
|
|
@@ -1742,6 +1785,13 @@ var TARGETS = {
|
|
|
1742
1785
|
}
|
|
1743
1786
|
}
|
|
1744
1787
|
]
|
|
1788
|
+
},
|
|
1789
|
+
// A session opened at the workspace root reads this file, one opened
|
|
1790
|
+
// in a clone does not (spec 0011 §1): the hooks only, `.rness` inside.
|
|
1791
|
+
{
|
|
1792
|
+
file: ".claude/settings.json",
|
|
1793
|
+
at: "root",
|
|
1794
|
+
guarantees: hooks(".rness")
|
|
1745
1795
|
}
|
|
1746
1796
|
]
|
|
1747
1797
|
}
|
|
@@ -1755,6 +1805,7 @@ function unsupportedMessage(name) {
|
|
|
1755
1805
|
}
|
|
1756
1806
|
|
|
1757
1807
|
// src/core/json-guarantees.ts
|
|
1808
|
+
import { isDeepStrictEqual } from "util";
|
|
1758
1809
|
function isObject(value) {
|
|
1759
1810
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1760
1811
|
}
|
|
@@ -1798,14 +1849,15 @@ function apply(root, g, create) {
|
|
|
1798
1849
|
}
|
|
1799
1850
|
if (value === void 0) {
|
|
1800
1851
|
if (!create) return { holds: false, changed: false };
|
|
1801
|
-
parent[last] = [g.contains];
|
|
1852
|
+
parent[last] = [structuredClone(g.contains)];
|
|
1802
1853
|
return { holds: true, changed: true };
|
|
1803
1854
|
}
|
|
1804
1855
|
if (!Array.isArray(value))
|
|
1805
1856
|
return { invalid: `${g.path.join(".")} is not a list` };
|
|
1806
|
-
if (value.
|
|
1857
|
+
if (value.some((entry) => isDeepStrictEqual(entry, g.contains)))
|
|
1858
|
+
return { holds: true, changed: false };
|
|
1807
1859
|
if (!create) return { holds: false, changed: false };
|
|
1808
|
-
value.push(g.contains);
|
|
1860
|
+
value.push(structuredClone(g.contains));
|
|
1809
1861
|
return { holds: true, changed: true };
|
|
1810
1862
|
}
|
|
1811
1863
|
function styleOf(text) {
|
|
@@ -1849,21 +1901,33 @@ function missingGuarantees(existing, guarantees) {
|
|
|
1849
1901
|
return missing;
|
|
1850
1902
|
}
|
|
1851
1903
|
function describeGuarantee(g) {
|
|
1852
|
-
|
|
1904
|
+
if ("value" in g) return `${g.path.join(".")} is missing`;
|
|
1905
|
+
const what = g.label ?? (typeof g.contains === "string" ? g.contains : JSON.stringify(g.contains));
|
|
1906
|
+
return `${g.path.join(".")} lacks ${what}`;
|
|
1853
1907
|
}
|
|
1854
1908
|
|
|
1855
1909
|
// src/core/agent-targets.ts
|
|
1856
1910
|
async function filesOf(root, manifest, targets) {
|
|
1857
1911
|
const files = [];
|
|
1912
|
+
if (!await exists(join9(root, "org"))) return files;
|
|
1913
|
+
for (const target of targets)
|
|
1914
|
+
for (const owned of target.files)
|
|
1915
|
+
if (owned.at === "root")
|
|
1916
|
+
files.push({
|
|
1917
|
+
guarantees: owned.guarantees,
|
|
1918
|
+
file: join9(root, ...owned.file.split("/")),
|
|
1919
|
+
label: owned.file
|
|
1920
|
+
});
|
|
1858
1921
|
for (const repo of Object.keys(manifest.repos)) {
|
|
1859
1922
|
if (!await exists(join9(root, "org", repo))) continue;
|
|
1860
1923
|
for (const target of targets)
|
|
1861
1924
|
for (const owned of target.files)
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1925
|
+
if (owned.at === "clones")
|
|
1926
|
+
files.push({
|
|
1927
|
+
guarantees: owned.guarantees,
|
|
1928
|
+
file: join9(root, "org", repo, ...owned.file.split("/")),
|
|
1929
|
+
label: `org/${repo}/${owned.file}`
|
|
1930
|
+
});
|
|
1867
1931
|
}
|
|
1868
1932
|
return files;
|
|
1869
1933
|
}
|
|
@@ -1876,7 +1940,9 @@ var BLOCK_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
|
1876
1940
|
function writtenFiles(manifest) {
|
|
1877
1941
|
return [
|
|
1878
1942
|
...BLOCK_FILES,
|
|
1879
|
-
...declared(manifest).flatMap(
|
|
1943
|
+
...declared(manifest).flatMap(
|
|
1944
|
+
(t) => t.files.filter((f) => f.at === "clones").map((f) => f.file)
|
|
1945
|
+
)
|
|
1880
1946
|
];
|
|
1881
1947
|
}
|
|
1882
1948
|
async function agentTargets(root, manifest, opts) {
|
|
@@ -2064,8 +2130,8 @@ function sessionUi(p) {
|
|
|
2064
2130
|
intro: (title) => {
|
|
2065
2131
|
p.intro(badge(title));
|
|
2066
2132
|
},
|
|
2067
|
-
outro: (
|
|
2068
|
-
p.outro(badge(
|
|
2133
|
+
outro: (message2, tone = "done") => {
|
|
2134
|
+
p.outro(badge(message2, tone));
|
|
2069
2135
|
},
|
|
2070
2136
|
line: (verb, rest, sentence) => {
|
|
2071
2137
|
say(verb, sentence ?? `${rest} ${verb}`);
|
|
@@ -3240,13 +3306,13 @@ function scaffoldMessage(subject, version) {
|
|
|
3240
3306
|
|
|
3241
3307
|
${SCAFFOLD_TRAILER}: ${version}`;
|
|
3242
3308
|
}
|
|
3243
|
-
async function commitUpgrade(rnessDir,
|
|
3309
|
+
async function commitUpgrade(rnessDir, message2) {
|
|
3244
3310
|
const add = await run(rnessDir, ["add", "-A"]);
|
|
3245
3311
|
if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
|
|
3246
3312
|
const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
|
|
3247
3313
|
if (staged.code === 0 && !await mergeInProgress(rnessDir))
|
|
3248
3314
|
return { kind: "nothing" };
|
|
3249
|
-
const commit = await run(rnessDir, ["commit", "-q", "-m",
|
|
3315
|
+
const commit = await run(rnessDir, ["commit", "-q", "-m", message2]);
|
|
3250
3316
|
if (commit.code !== 0)
|
|
3251
3317
|
return {
|
|
3252
3318
|
kind: "refused",
|
|
@@ -3767,9 +3833,9 @@ async function cloneSelection(input) {
|
|
|
3767
3833
|
manifest = result.manifest;
|
|
3768
3834
|
ui.line("declared", `scope ${result.name} (org/${result.name})`);
|
|
3769
3835
|
} catch (e) {
|
|
3770
|
-
const
|
|
3771
|
-
ui.error(`${spec}: ${
|
|
3772
|
-
failures.push({ spec, message });
|
|
3836
|
+
const message2 = firstLine4(e instanceof Error ? e.message : String(e));
|
|
3837
|
+
ui.error(`${spec}: ${message2}`);
|
|
3838
|
+
failures.push({ spec, message: message2 });
|
|
3773
3839
|
}
|
|
3774
3840
|
}
|
|
3775
3841
|
return failures;
|
|
@@ -4262,36 +4328,89 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
4262
4328
|
}
|
|
4263
4329
|
}
|
|
4264
4330
|
|
|
4265
|
-
// src/
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4331
|
+
// src/core/blocks.ts
|
|
4332
|
+
import { join as join20 } from "path";
|
|
4333
|
+
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
4334
|
+
async function checkBlocks({
|
|
4335
|
+
root,
|
|
4336
|
+
rnessDir,
|
|
4337
|
+
manifest,
|
|
4338
|
+
org
|
|
4339
|
+
}) {
|
|
4340
|
+
const checks = [];
|
|
4341
|
+
const note = (label, status2, message2 = null) => {
|
|
4342
|
+
checks.push({ label, status: status2, message: message2 });
|
|
4343
|
+
};
|
|
4344
|
+
const derive = () => ({
|
|
4345
|
+
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4346
|
+
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4347
|
+
checks
|
|
4348
|
+
});
|
|
4349
|
+
if (!await exists(join20(root, "org"))) return derive();
|
|
4350
|
+
const targets = [
|
|
4351
|
+
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
4352
|
+
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
4353
|
+
];
|
|
4354
|
+
for (const [name, entry] of Object.entries(manifest.scopes)) {
|
|
4355
|
+
const segments = entry.path.split("/");
|
|
4356
|
+
targets.push({
|
|
4357
|
+
scope: name,
|
|
4358
|
+
dir: join20(root, ...segments),
|
|
4359
|
+
depth: segments.length,
|
|
4360
|
+
file: "AGENTS.md",
|
|
4361
|
+
label: `${entry.path}/AGENTS.md`
|
|
4362
|
+
});
|
|
4363
|
+
}
|
|
4364
|
+
for (const t of targets) {
|
|
4365
|
+
if (!await exists(t.dir)) {
|
|
4366
|
+
note(
|
|
4367
|
+
t.label,
|
|
4368
|
+
"missing-dir",
|
|
4369
|
+
`${t.label}: directory not present, block not checked`
|
|
4370
|
+
);
|
|
4371
|
+
continue;
|
|
4274
4372
|
}
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4373
|
+
const text = await readOrNull(join20(t.dir, t.file));
|
|
4374
|
+
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
4375
|
+
const found = findBlock(lines);
|
|
4376
|
+
if (found.kind === "none") {
|
|
4377
|
+
note(
|
|
4378
|
+
t.label,
|
|
4379
|
+
"no-block",
|
|
4380
|
+
`${t.label}: no rness block yet (run ${rnessCommand()} sync)`
|
|
4279
4381
|
);
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
`
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
if (found.kind === "error") {
|
|
4385
|
+
note(t.label, "malformed", `${t.label}: ${found.message}`);
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
const context2 = await assembleContext({
|
|
4389
|
+
rnessDir,
|
|
4390
|
+
manifest,
|
|
4391
|
+
scope: t.scope
|
|
4392
|
+
});
|
|
4393
|
+
const fresh2 = renderBlock({
|
|
4394
|
+
scope: t.scope,
|
|
4395
|
+
org,
|
|
4396
|
+
depth: t.depth,
|
|
4397
|
+
context: context2
|
|
4398
|
+
});
|
|
4399
|
+
const pointerLeft = t.file === "CLAUDE.md" && lines.some((l) => l.trim() === "@AGENTS.md");
|
|
4400
|
+
if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash) && !pointerLeft)
|
|
4401
|
+
note(t.label, "current");
|
|
4402
|
+
else
|
|
4403
|
+
note(
|
|
4404
|
+
t.label,
|
|
4405
|
+
"stale",
|
|
4406
|
+
`${t.label}: stale rness block (run ${rnessCommand()} sync)`
|
|
4284
4407
|
);
|
|
4285
|
-
process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
|
|
4286
|
-
`);
|
|
4287
|
-
return 0;
|
|
4288
|
-
} catch (e) {
|
|
4289
|
-
return reportError(e);
|
|
4290
4408
|
}
|
|
4409
|
+
return derive();
|
|
4291
4410
|
}
|
|
4292
4411
|
|
|
4293
4412
|
// src/core/contract.ts
|
|
4294
|
-
import { join as
|
|
4413
|
+
import { join as join21 } from "path";
|
|
4295
4414
|
var STATUSES = {
|
|
4296
4415
|
adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
|
|
4297
4416
|
specs: [
|
|
@@ -4310,7 +4429,7 @@ async function checkContract(rnessDir) {
|
|
|
4310
4429
|
const problems = [];
|
|
4311
4430
|
for (const name of COLLECTIONS) {
|
|
4312
4431
|
const allowed = STATUSES[name];
|
|
4313
|
-
const items = await collectMarkdown(
|
|
4432
|
+
const items = await collectMarkdown(join21(rnessDir, name));
|
|
4314
4433
|
for (const item of items) {
|
|
4315
4434
|
const where = `${name}/${item.rel}`;
|
|
4316
4435
|
if (SKIP.has(where)) continue;
|
|
@@ -4346,11 +4465,39 @@ async function checkContract(rnessDir) {
|
|
|
4346
4465
|
return problems;
|
|
4347
4466
|
}
|
|
4348
4467
|
|
|
4468
|
+
// src/commands/logout.ts
|
|
4469
|
+
async function logoutCommand() {
|
|
4470
|
+
try {
|
|
4471
|
+
const stored = await readAuth();
|
|
4472
|
+
await clearAuth();
|
|
4473
|
+
const undone = await removeGitSetup().catch(() => false);
|
|
4474
|
+
if (stored === null && !undone) {
|
|
4475
|
+
process.stdout.write("not logged in\n");
|
|
4476
|
+
return 0;
|
|
4477
|
+
}
|
|
4478
|
+
if (stored !== null)
|
|
4479
|
+
process.stdout.write(
|
|
4480
|
+
`${status("logged out", `${stored.login} (github.com)`)}
|
|
4481
|
+
`
|
|
4482
|
+
);
|
|
4483
|
+
if (undone)
|
|
4484
|
+
process.stdout.write(
|
|
4485
|
+
`${status("updated", "git: rness no longer answers for https://github.com")}
|
|
4486
|
+
`
|
|
4487
|
+
);
|
|
4488
|
+
process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
|
|
4489
|
+
`);
|
|
4490
|
+
return 0;
|
|
4491
|
+
} catch (e) {
|
|
4492
|
+
return reportError(e);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4349
4496
|
// src/commands/upgrade.ts
|
|
4350
4497
|
import { execFile as execFile6 } from "child_process";
|
|
4351
4498
|
import { mkdtemp as mkdtemp3, readFile as readFile9, rm as rm7 } from "fs/promises";
|
|
4352
4499
|
import { tmpdir as tmpdir3 } from "os";
|
|
4353
|
-
import { basename as basename5, join as
|
|
4500
|
+
import { basename as basename5, join as join22, relative as relative5 } from "path";
|
|
4354
4501
|
import { promisify as promisify6 } from "util";
|
|
4355
4502
|
var execFileP6 = promisify6(execFile6);
|
|
4356
4503
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -4370,10 +4517,10 @@ async function latestVersion() {
|
|
|
4370
4517
|
async function scaffoldFor(version, rnessDir) {
|
|
4371
4518
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
4372
4519
|
const installed = await readPinnedCli(rnessDir);
|
|
4373
|
-
const
|
|
4374
|
-
if (installed?.version === version &&
|
|
4375
|
-
return { dir:
|
|
4376
|
-
const scratch = await mkdtemp3(
|
|
4520
|
+
const inside2 = installed === null ? null : join22(installed.dir, "scaffold");
|
|
4521
|
+
if (installed?.version === version && inside2 !== null && await exists(join22(inside2, "package.json")))
|
|
4522
|
+
return { dir: inside2 };
|
|
4523
|
+
const scratch = await mkdtemp3(join22(tmpdir3(), "rness-pack-"));
|
|
4377
4524
|
const cleanup = () => rm7(scratch, { recursive: true, force: true });
|
|
4378
4525
|
try {
|
|
4379
4526
|
const { stdout } = await execFileP6(
|
|
@@ -4388,8 +4535,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
4388
4535
|
{ timeout: 6e4 }
|
|
4389
4536
|
);
|
|
4390
4537
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
4391
|
-
await execFileP6("tar", ["-xzf",
|
|
4392
|
-
return { dir:
|
|
4538
|
+
await execFileP6("tar", ["-xzf", join22(scratch, tarball), "-C", scratch]);
|
|
4539
|
+
return { dir: join22(scratch, "package", "scaffold"), cleanup };
|
|
4393
4540
|
} catch (e) {
|
|
4394
4541
|
await cleanup();
|
|
4395
4542
|
throw new Error(
|
|
@@ -4401,7 +4548,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
4401
4548
|
async function declaredManager(rnessDir, pm) {
|
|
4402
4549
|
try {
|
|
4403
4550
|
const pkg = JSON.parse(
|
|
4404
|
-
await readFile9(
|
|
4551
|
+
await readFile9(join22(rnessDir, "package.json"), "utf8")
|
|
4405
4552
|
);
|
|
4406
4553
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
4407
4554
|
} catch {
|
|
@@ -4412,7 +4559,7 @@ async function clonesToCommit(root, cwd, manifest) {
|
|
|
4412
4559
|
const files = writtenFiles(manifest);
|
|
4413
4560
|
const clones = [];
|
|
4414
4561
|
for (const repo of Object.keys(manifest.repos)) {
|
|
4415
|
-
const dir =
|
|
4562
|
+
const dir = join22(root, "org", repo);
|
|
4416
4563
|
if (!await exists(dir)) continue;
|
|
4417
4564
|
try {
|
|
4418
4565
|
const paths = await changedPaths(dir, files);
|
|
@@ -4425,14 +4572,14 @@ async function clonesToCommit(root, cwd, manifest) {
|
|
|
4425
4572
|
}
|
|
4426
4573
|
function finish(ui, cwd, rnessDir, label, target, ending) {
|
|
4427
4574
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
4428
|
-
const
|
|
4575
|
+
const message2 = `chore: rness ${target}`;
|
|
4429
4576
|
const next = [];
|
|
4430
4577
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
4431
4578
|
if (ending.commit === "refused")
|
|
4432
|
-
next.push(`git -C ${rel} commit -m "${
|
|
4579
|
+
next.push(`git -C ${rel} commit -m "${message2}"`);
|
|
4433
4580
|
for (const c of ending.clones)
|
|
4434
4581
|
next.push(
|
|
4435
|
-
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${
|
|
4582
|
+
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message2}"`
|
|
4436
4583
|
);
|
|
4437
4584
|
if (ending.commit !== "nothing")
|
|
4438
4585
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
@@ -4605,7 +4752,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4605
4752
|
await source.cleanup?.();
|
|
4606
4753
|
}
|
|
4607
4754
|
}
|
|
4608
|
-
const packageFile =
|
|
4755
|
+
const packageFile = join22(ws.rnessDir, "package.json");
|
|
4609
4756
|
const restore = /* @__PURE__ */ new Map([
|
|
4610
4757
|
[packageFile, await readFile9(packageFile, "utf8")]
|
|
4611
4758
|
]);
|
|
@@ -4621,8 +4768,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4621
4768
|
() => ["installed", `dependencies with ${pm}`]
|
|
4622
4769
|
);
|
|
4623
4770
|
} catch (e) {
|
|
4624
|
-
const
|
|
4625
|
-
ui.error(
|
|
4771
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
4772
|
+
ui.error(message3);
|
|
4626
4773
|
if (!merged) {
|
|
4627
4774
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
4628
4775
|
ui.hint(
|
|
@@ -4635,7 +4782,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4635
4782
|
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
4636
4783
|
"stderr"
|
|
4637
4784
|
);
|
|
4638
|
-
if (/minimum.?release.?age|NO_MATURE/i.test(
|
|
4785
|
+
if (/minimum.?release.?age|NO_MATURE/i.test(message3))
|
|
4639
4786
|
ui.hint(
|
|
4640
4787
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
4641
4788
|
"stderr"
|
|
@@ -4654,13 +4801,13 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4654
4801
|
ui.gitIsSilent = true;
|
|
4655
4802
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
4656
4803
|
if (code !== 0) return code;
|
|
4657
|
-
const
|
|
4658
|
-
const committed = await commitUpgrade(ws.rnessDir,
|
|
4804
|
+
const message2 = `chore: rness ${target}`;
|
|
4805
|
+
const committed = await commitUpgrade(ws.rnessDir, message2);
|
|
4659
4806
|
if (committed.kind === "committed")
|
|
4660
4807
|
ui.line(
|
|
4661
4808
|
"committed",
|
|
4662
|
-
`${label} \u2014 ${
|
|
4663
|
-
`Committed ${label}: ${
|
|
4809
|
+
`${label} \u2014 ${message2}`,
|
|
4810
|
+
`Committed ${label}: ${message2}`
|
|
4664
4811
|
);
|
|
4665
4812
|
if (committed.kind === "refused") {
|
|
4666
4813
|
ui.error(
|
|
@@ -4682,85 +4829,28 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4682
4829
|
}
|
|
4683
4830
|
}
|
|
4684
4831
|
|
|
4685
|
-
// src/core/
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
const checks = [];
|
|
4695
|
-
const note = (label, status2, message = null) => {
|
|
4696
|
-
checks.push({ label, status: status2, message });
|
|
4697
|
-
};
|
|
4698
|
-
const derive = () => ({
|
|
4699
|
-
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4700
|
-
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4701
|
-
checks
|
|
4832
|
+
// src/core/check-workspace.ts
|
|
4833
|
+
async function checkWorkspace(ws, manifest) {
|
|
4834
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
4835
|
+
const problems = await checkContract(ws.rnessDir);
|
|
4836
|
+
const blocks = await checkBlocks({
|
|
4837
|
+
root: ws.root,
|
|
4838
|
+
rnessDir: ws.rnessDir,
|
|
4839
|
+
manifest,
|
|
4840
|
+
org: workspaceName(manifest, ws.root)
|
|
4702
4841
|
});
|
|
4703
|
-
|
|
4704
|
-
const
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
scope: name,
|
|
4712
|
-
dir: join22(root, ...segments),
|
|
4713
|
-
depth: segments.length,
|
|
4714
|
-
file: "AGENTS.md",
|
|
4715
|
-
label: `${entry.path}/AGENTS.md`
|
|
4716
|
-
});
|
|
4717
|
-
}
|
|
4718
|
-
for (const t of targets) {
|
|
4719
|
-
if (!await exists(t.dir)) {
|
|
4720
|
-
note(
|
|
4721
|
-
t.label,
|
|
4722
|
-
"missing-dir",
|
|
4723
|
-
`${t.label}: directory not present, block not checked`
|
|
4724
|
-
);
|
|
4725
|
-
continue;
|
|
4726
|
-
}
|
|
4727
|
-
const text = await readOrNull(join22(t.dir, t.file));
|
|
4728
|
-
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
4729
|
-
const found = findBlock(lines);
|
|
4730
|
-
if (found.kind === "none") {
|
|
4731
|
-
note(
|
|
4732
|
-
t.label,
|
|
4733
|
-
"no-block",
|
|
4734
|
-
`${t.label}: no rness block yet (run ${rnessCommand()} sync)`
|
|
4735
|
-
);
|
|
4736
|
-
continue;
|
|
4737
|
-
}
|
|
4738
|
-
if (found.kind === "error") {
|
|
4739
|
-
note(t.label, "malformed", `${t.label}: ${found.message}`);
|
|
4740
|
-
continue;
|
|
4741
|
-
}
|
|
4742
|
-
const context2 = await assembleContext({
|
|
4743
|
-
rnessDir,
|
|
4744
|
-
manifest,
|
|
4745
|
-
scope: t.scope
|
|
4746
|
-
});
|
|
4747
|
-
const fresh2 = renderBlock({
|
|
4748
|
-
scope: t.scope,
|
|
4749
|
-
org,
|
|
4750
|
-
depth: t.depth,
|
|
4751
|
-
context: context2
|
|
4752
|
-
});
|
|
4753
|
-
const pointerLeft = t.file === "CLAUDE.md" && lines.some((l) => l.trim() === "@AGENTS.md");
|
|
4754
|
-
if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash) && !pointerLeft)
|
|
4755
|
-
note(t.label, "current");
|
|
4756
|
-
else
|
|
4757
|
-
note(
|
|
4758
|
-
t.label,
|
|
4759
|
-
"stale",
|
|
4760
|
-
`${t.label}: stale rness block (run ${rnessCommand()} sync)`
|
|
4842
|
+
problems.push(...blocks.problems);
|
|
4843
|
+
for (const name of unsupportedAgents(manifest.agents ?? []))
|
|
4844
|
+
problems.push(unsupportedMessage(name));
|
|
4845
|
+
const targets = await agentTargets(ws.root, manifest, { check: true });
|
|
4846
|
+
for (const o of targets)
|
|
4847
|
+
if (o.status === "stale")
|
|
4848
|
+
problems.push(
|
|
4849
|
+
`${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
|
|
4761
4850
|
);
|
|
4762
|
-
|
|
4763
|
-
|
|
4851
|
+
else if (o.status === "invalid")
|
|
4852
|
+
problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
|
|
4853
|
+
return { problems, blocks, targets };
|
|
4764
4854
|
}
|
|
4765
4855
|
|
|
4766
4856
|
// src/commands/validate.ts
|
|
@@ -4769,26 +4859,8 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4769
4859
|
try {
|
|
4770
4860
|
const ws = await findWorkspace(cwd);
|
|
4771
4861
|
const manifest = await loadManifest(ws.rnessDir);
|
|
4772
|
-
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
4773
4862
|
const org = workspaceName(manifest, ws.root);
|
|
4774
|
-
const problems = await
|
|
4775
|
-
const blocks = await checkBlocks({
|
|
4776
|
-
root: ws.root,
|
|
4777
|
-
rnessDir: ws.rnessDir,
|
|
4778
|
-
manifest,
|
|
4779
|
-
org
|
|
4780
|
-
});
|
|
4781
|
-
problems.push(...blocks.problems);
|
|
4782
|
-
for (const name of unsupportedAgents(manifest.agents ?? []))
|
|
4783
|
-
problems.push(unsupportedMessage(name));
|
|
4784
|
-
const targets = await agentTargets(ws.root, manifest, { check: true });
|
|
4785
|
-
for (const o of targets)
|
|
4786
|
-
if (o.status === "stale")
|
|
4787
|
-
problems.push(
|
|
4788
|
-
`${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
|
|
4789
|
-
);
|
|
4790
|
-
else if (o.status === "invalid")
|
|
4791
|
-
problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
|
|
4863
|
+
const { problems, blocks, targets } = await checkWorkspace(ws, manifest);
|
|
4792
4864
|
const scaffoldWarnings = [];
|
|
4793
4865
|
if (await isRepository2(ws.rnessDir) && !await isShallow(ws.rnessDir)) {
|
|
4794
4866
|
const pin = await readPin(ws.rnessDir);
|
|
@@ -4865,134 +4937,9 @@ password=${resolved.token}
|
|
|
4865
4937
|
return 0;
|
|
4866
4938
|
}
|
|
4867
4939
|
|
|
4868
|
-
// src/commands/
|
|
4869
|
-
import {
|
|
4870
|
-
|
|
4871
|
-
// src/mcp/protocol.ts
|
|
4872
|
-
var MODERN_VERSIONS = ["2026-07-28"];
|
|
4873
|
-
var LEGACY_VERSIONS = [
|
|
4874
|
-
"2025-11-25",
|
|
4875
|
-
"2025-06-18",
|
|
4876
|
-
"2025-03-26",
|
|
4877
|
-
"2024-11-05"
|
|
4878
|
-
];
|
|
4879
|
-
var SUPPORTED = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
|
|
4880
|
-
var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
4881
|
-
var CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
4882
|
-
var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
4883
|
-
function isObject2(value) {
|
|
4884
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4885
|
-
}
|
|
4886
|
-
function errorLine(id, code, message, data) {
|
|
4887
|
-
return JSON.stringify({
|
|
4888
|
-
jsonrpc: "2.0",
|
|
4889
|
-
id,
|
|
4890
|
-
error: data === void 0 ? { code, message } : { code, message, data }
|
|
4891
|
-
});
|
|
4892
|
-
}
|
|
4893
|
-
function createProtocol(opts) {
|
|
4894
|
-
const serverInfo = { name: opts.name, version: opts.version };
|
|
4895
|
-
return {
|
|
4896
|
-
async handle(line2) {
|
|
4897
|
-
let message;
|
|
4898
|
-
try {
|
|
4899
|
-
message = JSON.parse(line2);
|
|
4900
|
-
} catch {
|
|
4901
|
-
return errorLine(null, -32700, "Parse error");
|
|
4902
|
-
}
|
|
4903
|
-
const rawId = isObject2(message) ? message["id"] : void 0;
|
|
4904
|
-
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
4905
|
-
if (!isObject2(message) || message["jsonrpc"] !== "2.0" || typeof message["method"] !== "string")
|
|
4906
|
-
return errorLine(id, -32600, "Invalid Request");
|
|
4907
|
-
if (!("id" in message)) return null;
|
|
4908
|
-
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
4909
|
-
const method = message["method"];
|
|
4910
|
-
const params = isObject2(message["params"]) ? message["params"] : {};
|
|
4911
|
-
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
4912
|
-
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
4913
|
-
if (modern) {
|
|
4914
|
-
const requested = meta[VERSION_KEY];
|
|
4915
|
-
if (!MODERN_VERSIONS.includes(requested))
|
|
4916
|
-
return errorLine(id, -32022, "Unsupported protocol version", {
|
|
4917
|
-
supported: SUPPORTED,
|
|
4918
|
-
requested
|
|
4919
|
-
});
|
|
4920
|
-
if (!isObject2(meta[CAPABILITIES_KEY]))
|
|
4921
|
-
return errorLine(
|
|
4922
|
-
id,
|
|
4923
|
-
-32602,
|
|
4924
|
-
`Invalid params: ${CAPABILITIES_KEY} is required`
|
|
4925
|
-
);
|
|
4926
|
-
}
|
|
4927
|
-
const answer = (result) => JSON.stringify({
|
|
4928
|
-
jsonrpc: "2.0",
|
|
4929
|
-
id,
|
|
4930
|
-
result: modern ? {
|
|
4931
|
-
resultType: "complete",
|
|
4932
|
-
...result,
|
|
4933
|
-
_meta: { [SERVER_INFO_KEY]: serverInfo }
|
|
4934
|
-
} : result
|
|
4935
|
-
});
|
|
4936
|
-
switch (method) {
|
|
4937
|
-
case "server/discover":
|
|
4938
|
-
return answer({
|
|
4939
|
-
supportedVersions: SUPPORTED,
|
|
4940
|
-
capabilities: { tools: {} }
|
|
4941
|
-
});
|
|
4942
|
-
case "initialize": {
|
|
4943
|
-
const asked = params["protocolVersion"];
|
|
4944
|
-
const protocolVersion = typeof asked === "string" && LEGACY_VERSIONS.includes(asked) ? asked : LEGACY_VERSIONS[0];
|
|
4945
|
-
return JSON.stringify({
|
|
4946
|
-
jsonrpc: "2.0",
|
|
4947
|
-
id,
|
|
4948
|
-
result: {
|
|
4949
|
-
protocolVersion,
|
|
4950
|
-
capabilities: { tools: {} },
|
|
4951
|
-
serverInfo
|
|
4952
|
-
}
|
|
4953
|
-
});
|
|
4954
|
-
}
|
|
4955
|
-
case "ping":
|
|
4956
|
-
return answer({});
|
|
4957
|
-
case "tools/list":
|
|
4958
|
-
return answer({
|
|
4959
|
-
tools: opts.tools.map((t) => ({
|
|
4960
|
-
name: t.name,
|
|
4961
|
-
title: t.title,
|
|
4962
|
-
description: t.description,
|
|
4963
|
-
inputSchema: t.inputSchema
|
|
4964
|
-
}))
|
|
4965
|
-
});
|
|
4966
|
-
case "tools/call": {
|
|
4967
|
-
const name = params["name"];
|
|
4968
|
-
const tool = opts.tools.find((t) => t.name === name);
|
|
4969
|
-
if (tool === void 0)
|
|
4970
|
-
return errorLine(id, -32602, `Unknown tool: ${String(name)}`);
|
|
4971
|
-
const args = isObject2(params["arguments"]) ? params["arguments"] : {};
|
|
4972
|
-
try {
|
|
4973
|
-
const r = await tool.call(args);
|
|
4974
|
-
return answer({
|
|
4975
|
-
content: [{ type: "text", text: r.text }],
|
|
4976
|
-
isError: r.isError === true
|
|
4977
|
-
});
|
|
4978
|
-
} catch (e) {
|
|
4979
|
-
return answer({
|
|
4980
|
-
content: [
|
|
4981
|
-
{
|
|
4982
|
-
type: "text",
|
|
4983
|
-
text: `${tool.name} failed: ${e instanceof Error ? e.message : String(e)}`
|
|
4984
|
-
}
|
|
4985
|
-
],
|
|
4986
|
-
isError: true
|
|
4987
|
-
});
|
|
4988
|
-
}
|
|
4989
|
-
}
|
|
4990
|
-
default:
|
|
4991
|
-
return errorLine(id, -32601, "Method not found");
|
|
4992
|
-
}
|
|
4993
|
-
}
|
|
4994
|
-
};
|
|
4995
|
-
}
|
|
4940
|
+
// src/commands/hook.ts
|
|
4941
|
+
import { realpath as realpath4 } from "fs/promises";
|
|
4942
|
+
import { isAbsolute as isAbsolute2, posix as posix5, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
4996
4943
|
|
|
4997
4944
|
// src/mcp/tools.ts
|
|
4998
4945
|
import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
@@ -5073,21 +5020,31 @@ async function context(cwd, args) {
|
|
|
5073
5020
|
manifest,
|
|
5074
5021
|
relative6(ws.root, cwd).split(sep4).join(posix4.sep)
|
|
5075
5022
|
);
|
|
5023
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
5024
|
+
return {
|
|
5025
|
+
text: [
|
|
5026
|
+
...summary.lines,
|
|
5027
|
+
"Read one with rness_read; search with rness_search."
|
|
5028
|
+
].join("\n")
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
async function scopeSummary(ws, manifest, scope) {
|
|
5076
5032
|
const entry = scope === null ? void 0 : manifest.scopes[scope];
|
|
5077
5033
|
const where = scope === null || entry === void 0 ? "global scope" : `scope ${scope} (${entry.path}${entry.extends.length > 0 ? `, extends ${entry.extends.join(", ")}` : ""})`;
|
|
5078
5034
|
const ctx = await assembleContext({ rnessDir: ws.rnessDir, manifest, scope });
|
|
5079
5035
|
const index = new Map(
|
|
5080
5036
|
(await documents(ws.rnessDir)).map((d) => [d.path, d])
|
|
5081
5037
|
);
|
|
5082
|
-
const
|
|
5038
|
+
const lines = [`Workspace ${workspaceName(manifest, ws.root)} \xB7 ${where}`];
|
|
5039
|
+
const counts = {};
|
|
5083
5040
|
for (const collection of ctx.collections) {
|
|
5084
5041
|
const docs = collection.files.map((f) => index.get(`${collection.name}/${f.rel}`)).filter((d) => d !== void 0);
|
|
5085
5042
|
if (docs.length === 0) continue;
|
|
5086
|
-
|
|
5087
|
-
|
|
5043
|
+
counts[collection.name] = docs.length;
|
|
5044
|
+
lines.push(`${LABELS[collection.name]} (${docs.length}):`);
|
|
5045
|
+
for (const d of docs) lines.push(line(d));
|
|
5088
5046
|
}
|
|
5089
|
-
|
|
5090
|
-
return { text: out.join("\n") };
|
|
5047
|
+
return { where, counts, lines };
|
|
5091
5048
|
}
|
|
5092
5049
|
async function list(cwd, args) {
|
|
5093
5050
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -5248,6 +5205,285 @@ function mcpTools(cwd) {
|
|
|
5248
5205
|
];
|
|
5249
5206
|
}
|
|
5250
5207
|
|
|
5208
|
+
// src/commands/hook.ts
|
|
5209
|
+
var NOUNS = {
|
|
5210
|
+
standards: ["standard", "standards"],
|
|
5211
|
+
adr: ["decision", "decisions"],
|
|
5212
|
+
specs: ["specification", "specifications"],
|
|
5213
|
+
plans: ["plan", "plans"],
|
|
5214
|
+
skills: ["skill", "skills"]
|
|
5215
|
+
};
|
|
5216
|
+
var SHOWN_PROBLEMS = 3;
|
|
5217
|
+
async function readInput(input) {
|
|
5218
|
+
if (input.isTTY === true) return {};
|
|
5219
|
+
let text = "";
|
|
5220
|
+
for await (const chunk of input) text += String(chunk);
|
|
5221
|
+
try {
|
|
5222
|
+
const data = JSON.parse(text);
|
|
5223
|
+
return data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
5224
|
+
} catch {
|
|
5225
|
+
return {};
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
var message = (e) => e instanceof Error ? e.message : String(e);
|
|
5229
|
+
function cwdOf(input, env) {
|
|
5230
|
+
const cwd = input["cwd"];
|
|
5231
|
+
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
5232
|
+
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
5233
|
+
}
|
|
5234
|
+
var toPosix2 = (p) => p.split(sep5).join(posix5.sep);
|
|
5235
|
+
function count(n, [one, many]) {
|
|
5236
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
5237
|
+
}
|
|
5238
|
+
async function safetyNet(ws, manifest) {
|
|
5239
|
+
const notes = [];
|
|
5240
|
+
const drift = await pinDrift(ws.rnessDir);
|
|
5241
|
+
if (drift !== null)
|
|
5242
|
+
notes.push(
|
|
5243
|
+
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
5244
|
+
);
|
|
5245
|
+
try {
|
|
5246
|
+
const { problems } = await checkWorkspace(ws, manifest);
|
|
5247
|
+
if (problems.length > 0)
|
|
5248
|
+
notes.push(
|
|
5249
|
+
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
5250
|
+
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
5251
|
+
);
|
|
5252
|
+
} catch (e) {
|
|
5253
|
+
notes.push(message(e));
|
|
5254
|
+
}
|
|
5255
|
+
return notes;
|
|
5256
|
+
}
|
|
5257
|
+
async function sessionStart(input, env) {
|
|
5258
|
+
const cwd = cwdOf(input, env);
|
|
5259
|
+
let banner2 = null;
|
|
5260
|
+
let context2 = [];
|
|
5261
|
+
let notes;
|
|
5262
|
+
try {
|
|
5263
|
+
const ws = await findWorkspace(cwd);
|
|
5264
|
+
const manifest = await loadManifest(ws.rnessDir);
|
|
5265
|
+
const scope = resolveScope(manifest, toPosix2(relative7(ws.root, cwd)));
|
|
5266
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
5267
|
+
const counts = COLLECTIONS.flatMap((c) => {
|
|
5268
|
+
const n = summary.counts[c];
|
|
5269
|
+
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
5270
|
+
});
|
|
5271
|
+
banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
|
|
5272
|
+
const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
|
|
5273
|
+
context2 = [
|
|
5274
|
+
...summary.lines,
|
|
5275
|
+
`These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
|
|
5276
|
+
];
|
|
5277
|
+
notes = await safetyNet(ws, manifest);
|
|
5278
|
+
} catch (e) {
|
|
5279
|
+
notes = [message(e)];
|
|
5280
|
+
}
|
|
5281
|
+
const warnings = notes.map((n) => `rness: ${n}`);
|
|
5282
|
+
const shown = [
|
|
5283
|
+
...banner2 !== null && input["source"] !== "compact" ? [banner2] : [],
|
|
5284
|
+
...warnings
|
|
5285
|
+
];
|
|
5286
|
+
return {
|
|
5287
|
+
...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
|
|
5288
|
+
hookSpecificOutput: {
|
|
5289
|
+
hookEventName: "SessionStart",
|
|
5290
|
+
additionalContext: [...context2, ...warnings].join("\n")
|
|
5291
|
+
}
|
|
5292
|
+
};
|
|
5293
|
+
}
|
|
5294
|
+
function inside(from, to) {
|
|
5295
|
+
const rel = relative7(from, to);
|
|
5296
|
+
return rel === "" || rel.startsWith("..") || isAbsolute2(rel) ? null : toPosix2(rel);
|
|
5297
|
+
}
|
|
5298
|
+
async function postToolUse(input, io) {
|
|
5299
|
+
const toolInput = input["tool_input"];
|
|
5300
|
+
const path = toolInput !== null && typeof toolInput === "object" ? toolInput["file_path"] : void 0;
|
|
5301
|
+
if (typeof path !== "string" || path === "") return 0;
|
|
5302
|
+
const cwd = cwdOf(input, io.env);
|
|
5303
|
+
let ws;
|
|
5304
|
+
try {
|
|
5305
|
+
ws = await findWorkspace(cwd);
|
|
5306
|
+
} catch {
|
|
5307
|
+
return 0;
|
|
5308
|
+
}
|
|
5309
|
+
const file = resolve7(cwd, path);
|
|
5310
|
+
const rel = inside(ws.rnessDir, file) ?? inside(
|
|
5311
|
+
await realpath4(ws.rnessDir).catch(() => ws.rnessDir),
|
|
5312
|
+
await realpath4(file).catch(() => file)
|
|
5313
|
+
);
|
|
5314
|
+
if (rel === null) return 0;
|
|
5315
|
+
const first = rel.split("/")[0] ?? "";
|
|
5316
|
+
let problems = [];
|
|
5317
|
+
if (rel === "rness.json") {
|
|
5318
|
+
try {
|
|
5319
|
+
const manifest = await loadManifest(ws.rnessDir);
|
|
5320
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
5321
|
+
} catch (e) {
|
|
5322
|
+
problems = [message(e)];
|
|
5323
|
+
}
|
|
5324
|
+
} else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
|
|
5325
|
+
problems = (await checkContract(ws.rnessDir)).filter(
|
|
5326
|
+
(p) => p.startsWith(`${rel}:`)
|
|
5327
|
+
);
|
|
5328
|
+
if (problems.length === 0) return 0;
|
|
5329
|
+
io.error.write(
|
|
5330
|
+
[
|
|
5331
|
+
...problems.map((p) => `rness: ${p}`),
|
|
5332
|
+
`Fix it in ${rel}; rness validate checks the whole workspace.`,
|
|
5333
|
+
""
|
|
5334
|
+
].join("\n")
|
|
5335
|
+
);
|
|
5336
|
+
return 2;
|
|
5337
|
+
}
|
|
5338
|
+
async function hookCommand(event, io = {
|
|
5339
|
+
input: process.stdin,
|
|
5340
|
+
output: process.stdout,
|
|
5341
|
+
error: process.stderr,
|
|
5342
|
+
env: process.env
|
|
5343
|
+
}) {
|
|
5344
|
+
if (event !== "session-start" && event !== "post-tool-use") {
|
|
5345
|
+
io.error.write(
|
|
5346
|
+
`unknown hook event ${JSON.stringify(event)} (known: session-start, post-tool-use)
|
|
5347
|
+
`
|
|
5348
|
+
);
|
|
5349
|
+
return 1;
|
|
5350
|
+
}
|
|
5351
|
+
const input = await readInput(io.input);
|
|
5352
|
+
if (event === "post-tool-use") return postToolUse(input, io);
|
|
5353
|
+
io.output.write(`${JSON.stringify(await sessionStart(input, io.env))}
|
|
5354
|
+
`);
|
|
5355
|
+
return 0;
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5358
|
+
// src/commands/mcp.ts
|
|
5359
|
+
import { createInterface } from "readline";
|
|
5360
|
+
|
|
5361
|
+
// src/mcp/protocol.ts
|
|
5362
|
+
var MODERN_VERSIONS = ["2026-07-28"];
|
|
5363
|
+
var LEGACY_VERSIONS = [
|
|
5364
|
+
"2025-11-25",
|
|
5365
|
+
"2025-06-18",
|
|
5366
|
+
"2025-03-26",
|
|
5367
|
+
"2024-11-05"
|
|
5368
|
+
];
|
|
5369
|
+
var SUPPORTED = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
|
|
5370
|
+
var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
5371
|
+
var CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
5372
|
+
var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
5373
|
+
function isObject2(value) {
|
|
5374
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5375
|
+
}
|
|
5376
|
+
function errorLine(id, code, message2, data) {
|
|
5377
|
+
return JSON.stringify({
|
|
5378
|
+
jsonrpc: "2.0",
|
|
5379
|
+
id,
|
|
5380
|
+
error: data === void 0 ? { code, message: message2 } : { code, message: message2, data }
|
|
5381
|
+
});
|
|
5382
|
+
}
|
|
5383
|
+
function createProtocol(opts) {
|
|
5384
|
+
const serverInfo = { name: opts.name, version: opts.version };
|
|
5385
|
+
return {
|
|
5386
|
+
async handle(line2) {
|
|
5387
|
+
let message2;
|
|
5388
|
+
try {
|
|
5389
|
+
message2 = JSON.parse(line2);
|
|
5390
|
+
} catch {
|
|
5391
|
+
return errorLine(null, -32700, "Parse error");
|
|
5392
|
+
}
|
|
5393
|
+
const rawId = isObject2(message2) ? message2["id"] : void 0;
|
|
5394
|
+
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
5395
|
+
if (!isObject2(message2) || message2["jsonrpc"] !== "2.0" || typeof message2["method"] !== "string")
|
|
5396
|
+
return errorLine(id, -32600, "Invalid Request");
|
|
5397
|
+
if (!("id" in message2)) return null;
|
|
5398
|
+
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
5399
|
+
const method = message2["method"];
|
|
5400
|
+
const params = isObject2(message2["params"]) ? message2["params"] : {};
|
|
5401
|
+
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
5402
|
+
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
5403
|
+
if (modern) {
|
|
5404
|
+
const requested = meta[VERSION_KEY];
|
|
5405
|
+
if (!MODERN_VERSIONS.includes(requested))
|
|
5406
|
+
return errorLine(id, -32022, "Unsupported protocol version", {
|
|
5407
|
+
supported: SUPPORTED,
|
|
5408
|
+
requested
|
|
5409
|
+
});
|
|
5410
|
+
if (!isObject2(meta[CAPABILITIES_KEY]))
|
|
5411
|
+
return errorLine(
|
|
5412
|
+
id,
|
|
5413
|
+
-32602,
|
|
5414
|
+
`Invalid params: ${CAPABILITIES_KEY} is required`
|
|
5415
|
+
);
|
|
5416
|
+
}
|
|
5417
|
+
const answer = (result) => JSON.stringify({
|
|
5418
|
+
jsonrpc: "2.0",
|
|
5419
|
+
id,
|
|
5420
|
+
result: modern ? {
|
|
5421
|
+
resultType: "complete",
|
|
5422
|
+
...result,
|
|
5423
|
+
_meta: { [SERVER_INFO_KEY]: serverInfo }
|
|
5424
|
+
} : result
|
|
5425
|
+
});
|
|
5426
|
+
switch (method) {
|
|
5427
|
+
case "server/discover":
|
|
5428
|
+
return answer({
|
|
5429
|
+
supportedVersions: SUPPORTED,
|
|
5430
|
+
capabilities: { tools: {} }
|
|
5431
|
+
});
|
|
5432
|
+
case "initialize": {
|
|
5433
|
+
const asked = params["protocolVersion"];
|
|
5434
|
+
const protocolVersion = typeof asked === "string" && LEGACY_VERSIONS.includes(asked) ? asked : LEGACY_VERSIONS[0];
|
|
5435
|
+
return JSON.stringify({
|
|
5436
|
+
jsonrpc: "2.0",
|
|
5437
|
+
id,
|
|
5438
|
+
result: {
|
|
5439
|
+
protocolVersion,
|
|
5440
|
+
capabilities: { tools: {} },
|
|
5441
|
+
serverInfo
|
|
5442
|
+
}
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
case "ping":
|
|
5446
|
+
return answer({});
|
|
5447
|
+
case "tools/list":
|
|
5448
|
+
return answer({
|
|
5449
|
+
tools: opts.tools.map((t) => ({
|
|
5450
|
+
name: t.name,
|
|
5451
|
+
title: t.title,
|
|
5452
|
+
description: t.description,
|
|
5453
|
+
inputSchema: t.inputSchema
|
|
5454
|
+
}))
|
|
5455
|
+
});
|
|
5456
|
+
case "tools/call": {
|
|
5457
|
+
const name = params["name"];
|
|
5458
|
+
const tool = opts.tools.find((t) => t.name === name);
|
|
5459
|
+
if (tool === void 0)
|
|
5460
|
+
return errorLine(id, -32602, `Unknown tool: ${String(name)}`);
|
|
5461
|
+
const args = isObject2(params["arguments"]) ? params["arguments"] : {};
|
|
5462
|
+
try {
|
|
5463
|
+
const r = await tool.call(args);
|
|
5464
|
+
return answer({
|
|
5465
|
+
content: [{ type: "text", text: r.text }],
|
|
5466
|
+
isError: r.isError === true
|
|
5467
|
+
});
|
|
5468
|
+
} catch (e) {
|
|
5469
|
+
return answer({
|
|
5470
|
+
content: [
|
|
5471
|
+
{
|
|
5472
|
+
type: "text",
|
|
5473
|
+
text: `${tool.name} failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5474
|
+
}
|
|
5475
|
+
],
|
|
5476
|
+
isError: true
|
|
5477
|
+
});
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
default:
|
|
5481
|
+
return errorLine(id, -32601, "Method not found");
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
};
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5251
5487
|
// src/commands/mcp.ts
|
|
5252
5488
|
async function mcpCommand(opts, io = {
|
|
5253
5489
|
input: process.stdin,
|
|
@@ -5337,6 +5573,9 @@ function buildProgram(state) {
|
|
|
5337
5573
|
program.command("git-credential", { hidden: true }).argument("<operation>").action(async (operation) => {
|
|
5338
5574
|
state.code = await gitCredentialCommand(operation);
|
|
5339
5575
|
});
|
|
5576
|
+
program.command("hook", { hidden: true }).argument("<event>", "session-start or post-tool-use").action(async (event) => {
|
|
5577
|
+
state.code = await hookCommand(event);
|
|
5578
|
+
});
|
|
5340
5579
|
program.command("upgrade").alias("update").description(
|
|
5341
5580
|
"Move this workspace to another @rness/cli: pin it in .rness/package.json, install, sync"
|
|
5342
5581
|
).argument("[version]", "an exact version (default: the latest release)").option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (version, opts) => {
|
|
@@ -5414,7 +5653,8 @@ var OWN_COMMANDS = [
|
|
|
5414
5653
|
"update",
|
|
5415
5654
|
"login",
|
|
5416
5655
|
"logout",
|
|
5417
|
-
"git-credential"
|
|
5656
|
+
"git-credential",
|
|
5657
|
+
"hook"
|
|
5418
5658
|
];
|
|
5419
5659
|
var CATCH_UP_DONE = "RNESS_CATCH_UP_DONE";
|
|
5420
5660
|
async function catchUp(argv, deps = {}) {
|
|
@@ -5509,14 +5749,14 @@ export {
|
|
|
5509
5749
|
copyScaffold,
|
|
5510
5750
|
loginCommand,
|
|
5511
5751
|
createCommand,
|
|
5512
|
-
|
|
5752
|
+
checkBlocks,
|
|
5513
5753
|
STATUSES,
|
|
5514
5754
|
checkContract,
|
|
5755
|
+
logoutCommand,
|
|
5515
5756
|
upgradeCommand,
|
|
5516
|
-
checkBlocks,
|
|
5517
5757
|
validateCommand,
|
|
5518
5758
|
run2 as run,
|
|
5519
5759
|
OWN_COMMANDS,
|
|
5520
5760
|
catchUp
|
|
5521
5761
|
};
|
|
5522
|
-
//# sourceMappingURL=chunk-
|
|
5762
|
+
//# sourceMappingURL=chunk-R4BRAAC2.js.map
|