@rness/cli 0.9.2 → 0.11.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 +89 -1
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-JKDF7ROY.js → chunk-LW4S5OLI.js} +1021 -368
- package/dist/chunk-LW4S5OLI.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);
|
|
@@ -1198,6 +1198,12 @@ function badge(text, tone = "intro", stream = process.stdout) {
|
|
|
1198
1198
|
});
|
|
1199
1199
|
return painted === padded ? text : painted;
|
|
1200
1200
|
}
|
|
1201
|
+
function inverse(text, stream = process.stdout) {
|
|
1202
|
+
const plain = `[${text.slice(1, -1)}]`;
|
|
1203
|
+
if (noColor()) return plain;
|
|
1204
|
+
const painted = styleText("inverse", text, { stream, validateStream: true });
|
|
1205
|
+
return painted === text ? plain : painted;
|
|
1206
|
+
}
|
|
1201
1207
|
function grey(text, stream = process.stdout) {
|
|
1202
1208
|
if (noColor()) return text;
|
|
1203
1209
|
return styleText("gray", text, { stream, validateStream: true });
|
|
@@ -1213,8 +1219,8 @@ function warn(text) {
|
|
|
1213
1219
|
var WORD = [" \u2588\u2580\u2588 \u2588\u2584 \u2588 \u2588\u2580\u2580 \u2588\u2580\u2580 \u2588\u2580\u2580", " \u2588\u2580\u2584 \u2588 \u2580\u2588 \u2588\u2588\u2584 \u2584\u2584\u2588 \u2584\u2584\u2588"];
|
|
1214
1220
|
var FROM = [63, 185, 80];
|
|
1215
1221
|
var TO = [57, 197, 207];
|
|
1216
|
-
function gradient(
|
|
1217
|
-
const chars = [...
|
|
1222
|
+
function gradient(line3) {
|
|
1223
|
+
const chars = [...line3];
|
|
1218
1224
|
const last = Math.max(1, chars.length - 1);
|
|
1219
1225
|
const painted = chars.map((ch, i) => {
|
|
1220
1226
|
if (ch === " ") return ch;
|
|
@@ -1231,7 +1237,7 @@ function banner(version, stream = process.stdout) {
|
|
|
1231
1237
|
const coloured = paint("done", "x", stream) !== "x";
|
|
1232
1238
|
const trueColour = coloured && typeof stream.hasColors === "function" && stream.hasColors(2 ** 24);
|
|
1233
1239
|
const word = WORD.map(
|
|
1234
|
-
(
|
|
1240
|
+
(line3) => trueColour ? gradient(line3) : paint("done", line3, stream)
|
|
1235
1241
|
);
|
|
1236
1242
|
return [
|
|
1237
1243
|
"",
|
|
@@ -1263,7 +1269,7 @@ async function detectGithubSsh(opts) {
|
|
|
1263
1269
|
...opts.interactive ? [] : ["-o", "BatchMode=yes"],
|
|
1264
1270
|
"git@github.com"
|
|
1265
1271
|
];
|
|
1266
|
-
return new Promise((
|
|
1272
|
+
return new Promise((resolve8) => {
|
|
1267
1273
|
const child = spawn2(opts.command ?? "ssh", args, {
|
|
1268
1274
|
stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
|
|
1269
1275
|
});
|
|
@@ -1276,7 +1282,7 @@ async function detectGithubSsh(opts) {
|
|
|
1276
1282
|
settled = true;
|
|
1277
1283
|
clearTimeout(timer);
|
|
1278
1284
|
child.stderr?.destroy();
|
|
1279
|
-
|
|
1285
|
+
resolve8(access2);
|
|
1280
1286
|
};
|
|
1281
1287
|
const timer = setTimeout(() => {
|
|
1282
1288
|
child.kill("SIGKILL");
|
|
@@ -1397,15 +1403,15 @@ function reportError(e) {
|
|
|
1397
1403
|
process.stderr.write(`${e.stack}
|
|
1398
1404
|
`);
|
|
1399
1405
|
} else {
|
|
1400
|
-
const
|
|
1401
|
-
process.stderr.write(`${paint("error",
|
|
1406
|
+
const message2 = e instanceof Error ? e.message : String(e);
|
|
1407
|
+
process.stderr.write(`${paint("error", message2, process.stderr)}
|
|
1402
1408
|
`);
|
|
1403
1409
|
}
|
|
1404
1410
|
return 1;
|
|
1405
1411
|
}
|
|
1406
1412
|
|
|
1407
1413
|
// src/version.ts
|
|
1408
|
-
var VERSION = "0.
|
|
1414
|
+
var VERSION = "0.11.0";
|
|
1409
1415
|
|
|
1410
1416
|
// src/core/block.ts
|
|
1411
1417
|
import { createHash } from "crypto";
|
|
@@ -1457,8 +1463,8 @@ ${rules.join("\n\n")}`;
|
|
|
1457
1463
|
const text = [BEGIN, header(scope, hash), body, END].join("\n");
|
|
1458
1464
|
return { text, hash, bytes: Buffer.byteLength(text, "utf8") };
|
|
1459
1465
|
}
|
|
1460
|
-
function parseHeader(
|
|
1461
|
-
const m = HEADER.exec(
|
|
1466
|
+
function parseHeader(line3) {
|
|
1467
|
+
const m = HEADER.exec(line3.trimEnd());
|
|
1462
1468
|
if (m === null) return null;
|
|
1463
1469
|
return { version: m[1] ?? null, scope: m[2] ?? "", hash: m[3] ?? "" };
|
|
1464
1470
|
}
|
|
@@ -1473,8 +1479,8 @@ function isCurrentBlock(blockLines, freshHash) {
|
|
|
1473
1479
|
// src/core/frontmatter.ts
|
|
1474
1480
|
import { parse as parse2 } from "yaml";
|
|
1475
1481
|
var FRONT_MATTER = /^---\r?\n(?:([\s\S]*?)\r?\n)?---(?:\r?\n|$)/;
|
|
1476
|
-
function fail2(
|
|
1477
|
-
throw new Error(`front matter: ${
|
|
1482
|
+
function fail2(message2) {
|
|
1483
|
+
throw new Error(`front matter: ${message2}`);
|
|
1478
1484
|
}
|
|
1479
1485
|
function stripBom(source) {
|
|
1480
1486
|
return source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
@@ -1629,8 +1635,8 @@ import { join as join8 } from "path";
|
|
|
1629
1635
|
function findBlock(lines) {
|
|
1630
1636
|
const begins = [];
|
|
1631
1637
|
const ends = [];
|
|
1632
|
-
lines.forEach((
|
|
1633
|
-
const l =
|
|
1638
|
+
lines.forEach((line3, i) => {
|
|
1639
|
+
const l = line3.trimEnd();
|
|
1634
1640
|
if (l === BEGIN) begins.push(i);
|
|
1635
1641
|
if (l === END) ends.push(i);
|
|
1636
1642
|
});
|
|
@@ -1706,6 +1712,88 @@ import { mkdir as mkdir2 } from "fs/promises";
|
|
|
1706
1712
|
import { dirname as dirname4, join as join9 } from "path";
|
|
1707
1713
|
|
|
1708
1714
|
// src/core/agents.ts
|
|
1715
|
+
var PINNED_BIN = "node_modules/@rness/cli/dist/bin/rness.js";
|
|
1716
|
+
function hookLine(rness, event) {
|
|
1717
|
+
const run3 = `f="$CLAUDE_PROJECT_DIR/${rness}/${PINNED_BIN}"; if [ -f "$f" ]; then node "$f" hook ${event};`;
|
|
1718
|
+
if (event === "post-tool-use") return `${run3} fi`;
|
|
1719
|
+
const missing = JSON.stringify({
|
|
1720
|
+
systemMessage: `rness: ${rness} is not installed, so the workspace context is not loaded. Clone the workspace, then install its dependencies in .rness.`
|
|
1721
|
+
});
|
|
1722
|
+
return `${run3} else echo '${missing}'; fi`;
|
|
1723
|
+
}
|
|
1724
|
+
function hooks(rness) {
|
|
1725
|
+
return [
|
|
1726
|
+
{
|
|
1727
|
+
path: ["hooks", "SessionStart"],
|
|
1728
|
+
contains: {
|
|
1729
|
+
hooks: [
|
|
1730
|
+
{
|
|
1731
|
+
type: "command",
|
|
1732
|
+
command: hookLine(rness, "session-start"),
|
|
1733
|
+
timeout: 10
|
|
1734
|
+
}
|
|
1735
|
+
]
|
|
1736
|
+
},
|
|
1737
|
+
label: "the rness session-start hook"
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
path: ["hooks", "PostToolUse"],
|
|
1741
|
+
contains: {
|
|
1742
|
+
matcher: "Edit|Write",
|
|
1743
|
+
hooks: [
|
|
1744
|
+
{
|
|
1745
|
+
type: "command",
|
|
1746
|
+
command: hookLine(rness, "post-tool-use"),
|
|
1747
|
+
timeout: 10
|
|
1748
|
+
}
|
|
1749
|
+
]
|
|
1750
|
+
},
|
|
1751
|
+
label: "the rness post-tool-use hook"
|
|
1752
|
+
}
|
|
1753
|
+
];
|
|
1754
|
+
}
|
|
1755
|
+
var PLUGIN = ".claude/skills/rness";
|
|
1756
|
+
var PLUGIN_JSON = `${JSON.stringify(
|
|
1757
|
+
{
|
|
1758
|
+
name: "rness",
|
|
1759
|
+
description: "The rness workspace in Claude Code: /rness:status."
|
|
1760
|
+
},
|
|
1761
|
+
null,
|
|
1762
|
+
2
|
|
1763
|
+
)}
|
|
1764
|
+
`;
|
|
1765
|
+
function statusSkill(rness) {
|
|
1766
|
+
const command = `node "\${CLAUDE_PROJECT_DIR}/${rness}/${PINNED_BIN}" status --cwd "\${CLAUDE_PROJECT_DIR}"`;
|
|
1767
|
+
return `---
|
|
1768
|
+
name: status
|
|
1769
|
+
description: The status of every decision, specification, plan and other tracked document of the rness workspace, one table per directory. Read-only.
|
|
1770
|
+
argument-hint: '[tab]'
|
|
1771
|
+
disable-model-invocation: true
|
|
1772
|
+
allowed-tools: Bash(${command})
|
|
1773
|
+
---
|
|
1774
|
+
|
|
1775
|
+
!\`${command}\`
|
|
1776
|
+
|
|
1777
|
+
Show the output above to the user as it is: the heading and the tables,
|
|
1778
|
+
nothing added, nothing summarised, no other tool. Arguments: \`$ARGUMENTS\`.
|
|
1779
|
+
When they name a tab, show only that tab's section. If the output says the
|
|
1780
|
+
module cannot be found, say instead that ${rness} is not installed next to
|
|
1781
|
+
this repository.
|
|
1782
|
+
|
|
1783
|
+
End with this line: _For the view with tabs and scrolling: Ctrl+Z, then
|
|
1784
|
+
\`npx @rness/cli status\` (q to close), then \`fg\`._
|
|
1785
|
+
`;
|
|
1786
|
+
}
|
|
1787
|
+
function plugin(rness, at) {
|
|
1788
|
+
return [
|
|
1789
|
+
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
1790
|
+
{
|
|
1791
|
+
file: `${PLUGIN}/skills/status/SKILL.md`,
|
|
1792
|
+
at,
|
|
1793
|
+
content: statusSkill(rness)
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
}
|
|
1709
1797
|
var TARGETS = {
|
|
1710
1798
|
claude: {
|
|
1711
1799
|
name: "claude",
|
|
@@ -1713,6 +1801,7 @@ var TARGETS = {
|
|
|
1713
1801
|
files: [
|
|
1714
1802
|
{
|
|
1715
1803
|
file: ".claude/settings.json",
|
|
1804
|
+
at: "clones",
|
|
1716
1805
|
guarantees: [
|
|
1717
1806
|
// Read the context repository from a repository without a prompt.
|
|
1718
1807
|
// Resolved against the project directory, and applied once Claude
|
|
@@ -1720,11 +1809,13 @@ var TARGETS = {
|
|
|
1720
1809
|
{
|
|
1721
1810
|
path: ["permissions", "additionalDirectories"],
|
|
1722
1811
|
contains: "../../.rness"
|
|
1723
|
-
}
|
|
1812
|
+
},
|
|
1813
|
+
...hooks("../../.rness")
|
|
1724
1814
|
]
|
|
1725
1815
|
},
|
|
1726
1816
|
{
|
|
1727
1817
|
file: ".mcp.json",
|
|
1818
|
+
at: "clones",
|
|
1728
1819
|
guarantees: [
|
|
1729
1820
|
// The pinned copy, by a path relative to the repository — the
|
|
1730
1821
|
// directory Claude Code starts the server in (spec 0014 §6). Not
|
|
@@ -1742,7 +1833,16 @@ var TARGETS = {
|
|
|
1742
1833
|
}
|
|
1743
1834
|
}
|
|
1744
1835
|
]
|
|
1745
|
-
}
|
|
1836
|
+
},
|
|
1837
|
+
...plugin("../../.rness", "clones"),
|
|
1838
|
+
// A session opened at the workspace root reads this file, one opened
|
|
1839
|
+
// in a clone does not (spec 0011 §1): the hooks only, `.rness` inside.
|
|
1840
|
+
{
|
|
1841
|
+
file: ".claude/settings.json",
|
|
1842
|
+
at: "root",
|
|
1843
|
+
guarantees: hooks(".rness")
|
|
1844
|
+
},
|
|
1845
|
+
...plugin(".rness", "root")
|
|
1746
1846
|
]
|
|
1747
1847
|
}
|
|
1748
1848
|
};
|
|
@@ -1755,6 +1855,7 @@ function unsupportedMessage(name) {
|
|
|
1755
1855
|
}
|
|
1756
1856
|
|
|
1757
1857
|
// src/core/json-guarantees.ts
|
|
1858
|
+
import { isDeepStrictEqual } from "util";
|
|
1758
1859
|
function isObject(value) {
|
|
1759
1860
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1760
1861
|
}
|
|
@@ -1798,14 +1899,15 @@ function apply(root, g, create) {
|
|
|
1798
1899
|
}
|
|
1799
1900
|
if (value === void 0) {
|
|
1800
1901
|
if (!create) return { holds: false, changed: false };
|
|
1801
|
-
parent[last] = [g.contains];
|
|
1902
|
+
parent[last] = [structuredClone(g.contains)];
|
|
1802
1903
|
return { holds: true, changed: true };
|
|
1803
1904
|
}
|
|
1804
1905
|
if (!Array.isArray(value))
|
|
1805
1906
|
return { invalid: `${g.path.join(".")} is not a list` };
|
|
1806
|
-
if (value.
|
|
1907
|
+
if (value.some((entry) => isDeepStrictEqual(entry, g.contains)))
|
|
1908
|
+
return { holds: true, changed: false };
|
|
1807
1909
|
if (!create) return { holds: false, changed: false };
|
|
1808
|
-
value.push(g.contains);
|
|
1910
|
+
value.push(structuredClone(g.contains));
|
|
1809
1911
|
return { holds: true, changed: true };
|
|
1810
1912
|
}
|
|
1811
1913
|
function styleOf(text) {
|
|
@@ -1849,21 +1951,37 @@ function missingGuarantees(existing, guarantees) {
|
|
|
1849
1951
|
return missing;
|
|
1850
1952
|
}
|
|
1851
1953
|
function describeGuarantee(g) {
|
|
1852
|
-
|
|
1954
|
+
if ("value" in g) return `${g.path.join(".")} is missing`;
|
|
1955
|
+
const what = g.label ?? (typeof g.contains === "string" ? g.contains : JSON.stringify(g.contains));
|
|
1956
|
+
return `${g.path.join(".")} lacks ${what}`;
|
|
1853
1957
|
}
|
|
1854
1958
|
|
|
1855
1959
|
// src/core/agent-targets.ts
|
|
1960
|
+
function wholeDiff(existing, content) {
|
|
1961
|
+
if (existing === content) return null;
|
|
1962
|
+
return existing === null ? "missing" : "differs from what rness writes";
|
|
1963
|
+
}
|
|
1856
1964
|
async function filesOf(root, manifest, targets) {
|
|
1857
1965
|
const files = [];
|
|
1966
|
+
if (!await exists(join9(root, "org"))) return files;
|
|
1967
|
+
for (const target of targets)
|
|
1968
|
+
for (const owned of target.files)
|
|
1969
|
+
if (owned.at === "root")
|
|
1970
|
+
files.push({
|
|
1971
|
+
...owned,
|
|
1972
|
+
file: join9(root, ...owned.file.split("/")),
|
|
1973
|
+
label: owned.file
|
|
1974
|
+
});
|
|
1858
1975
|
for (const repo of Object.keys(manifest.repos)) {
|
|
1859
1976
|
if (!await exists(join9(root, "org", repo))) continue;
|
|
1860
1977
|
for (const target of targets)
|
|
1861
1978
|
for (const owned of target.files)
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1979
|
+
if (owned.at === "clones")
|
|
1980
|
+
files.push({
|
|
1981
|
+
...owned,
|
|
1982
|
+
file: join9(root, "org", repo, ...owned.file.split("/")),
|
|
1983
|
+
label: `org/${repo}/${owned.file}`
|
|
1984
|
+
});
|
|
1867
1985
|
}
|
|
1868
1986
|
return files;
|
|
1869
1987
|
}
|
|
@@ -1876,17 +1994,30 @@ var BLOCK_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
|
1876
1994
|
function writtenFiles(manifest) {
|
|
1877
1995
|
return [
|
|
1878
1996
|
...BLOCK_FILES,
|
|
1879
|
-
...declared(manifest).flatMap(
|
|
1997
|
+
...declared(manifest).flatMap(
|
|
1998
|
+
(t) => t.files.filter((f) => f.at === "clones").map((f) => f.file)
|
|
1999
|
+
)
|
|
1880
2000
|
];
|
|
1881
2001
|
}
|
|
1882
2002
|
async function agentTargets(root, manifest, opts) {
|
|
1883
2003
|
const outcomes = [];
|
|
1884
|
-
for (const
|
|
1885
|
-
|
|
1886
|
-
manifest,
|
|
1887
|
-
declared(manifest)
|
|
1888
|
-
)) {
|
|
2004
|
+
for (const owned of await filesOf(root, manifest, declared(manifest))) {
|
|
2005
|
+
const { file, label } = owned;
|
|
1889
2006
|
const existing = await readOrNull(file);
|
|
2007
|
+
if ("content" in owned) {
|
|
2008
|
+
const diff = wholeDiff(existing, owned.content);
|
|
2009
|
+
if (diff === null)
|
|
2010
|
+
outcomes.push({ label, status: "unchanged", detail: null });
|
|
2011
|
+
else if (opts.check)
|
|
2012
|
+
outcomes.push({ label, status: "stale", detail: diff });
|
|
2013
|
+
else {
|
|
2014
|
+
await mkdir2(dirname4(file), { recursive: true });
|
|
2015
|
+
await writeFileAtomic(file, owned.content);
|
|
2016
|
+
outcomes.push({ label, status: "updated", detail: null });
|
|
2017
|
+
}
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
const { guarantees } = owned;
|
|
1890
2021
|
if (opts.check) {
|
|
1891
2022
|
const missing = missingGuarantees(existing, guarantees);
|
|
1892
2023
|
if ("invalid" in missing)
|
|
@@ -1922,15 +2053,16 @@ async function leftoverTargets(root, manifest) {
|
|
|
1922
2053
|
(t) => !agents.includes(t.name)
|
|
1923
2054
|
);
|
|
1924
2055
|
const leftovers = [];
|
|
1925
|
-
for (const
|
|
1926
|
-
|
|
1927
|
-
manifest,
|
|
1928
|
-
undeclared
|
|
1929
|
-
)) {
|
|
1930
|
-
const existing = await readOrNull(file);
|
|
2056
|
+
for (const owned of await filesOf(root, manifest, undeclared)) {
|
|
2057
|
+
const existing = await readOrNull(owned.file);
|
|
1931
2058
|
if (existing === null) continue;
|
|
1932
|
-
|
|
1933
|
-
|
|
2059
|
+
if ("content" in owned) {
|
|
2060
|
+
if (existing === owned.content) leftovers.push(owned.label);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
const missing = missingGuarantees(existing, owned.guarantees);
|
|
2064
|
+
if (!("invalid" in missing) && missing.length === 0)
|
|
2065
|
+
leftovers.push(owned.label);
|
|
1934
2066
|
}
|
|
1935
2067
|
return leftovers;
|
|
1936
2068
|
}
|
|
@@ -2040,8 +2172,8 @@ var plainUi = {
|
|
|
2040
2172
|
{ label: options.doing, animate: options.git !== true },
|
|
2041
2173
|
work
|
|
2042
2174
|
);
|
|
2043
|
-
const
|
|
2044
|
-
if (
|
|
2175
|
+
const line3 = options.quiet === true ? null : done?.(result) ?? null;
|
|
2176
|
+
if (line3 !== null) this.line(line3[0], line3[1]);
|
|
2045
2177
|
return result;
|
|
2046
2178
|
}
|
|
2047
2179
|
};
|
|
@@ -2064,8 +2196,8 @@ function sessionUi(p) {
|
|
|
2064
2196
|
intro: (title) => {
|
|
2065
2197
|
p.intro(badge(title));
|
|
2066
2198
|
},
|
|
2067
|
-
outro: (
|
|
2068
|
-
p.outro(badge(
|
|
2199
|
+
outro: (message2, tone = "done") => {
|
|
2200
|
+
p.outro(badge(message2, tone));
|
|
2069
2201
|
},
|
|
2070
2202
|
line: (verb, rest, sentence) => {
|
|
2071
2203
|
say(verb, sentence ?? `${rest} ${verb}`);
|
|
@@ -2074,7 +2206,7 @@ function sessionUi(p) {
|
|
|
2074
2206
|
p.log.warn(text);
|
|
2075
2207
|
},
|
|
2076
2208
|
note: (title, lines) => {
|
|
2077
|
-
p.note(lines.join("\n"), title, { format: (
|
|
2209
|
+
p.note(lines.join("\n"), title, { format: (line3) => grey(line3) });
|
|
2078
2210
|
},
|
|
2079
2211
|
hint: (text) => {
|
|
2080
2212
|
p.log.message(paint("idle", text));
|
|
@@ -2088,20 +2220,20 @@ function sessionUi(p) {
|
|
|
2088
2220
|
async step(options, work, done) {
|
|
2089
2221
|
const label = options.sentence ?? tidy(options.doing);
|
|
2090
2222
|
const finish2 = (result) => done?.(result) ?? null;
|
|
2091
|
-
const label_ = (
|
|
2223
|
+
const label_ = (line3) => line3[2] ?? `${line3[1]} ${line3[0]}`;
|
|
2092
2224
|
if (options.git === true && !this.gitIsSilent) {
|
|
2093
2225
|
p.log.message(paint("idle", `${label}\u2026`));
|
|
2094
2226
|
const result = await work();
|
|
2095
|
-
const
|
|
2096
|
-
if (
|
|
2227
|
+
const line3 = finish2(result);
|
|
2228
|
+
if (line3 !== null) say(line3[0], label_(line3));
|
|
2097
2229
|
return result;
|
|
2098
2230
|
}
|
|
2099
2231
|
const spin = p.spinner();
|
|
2100
2232
|
spin.start(label);
|
|
2101
2233
|
try {
|
|
2102
2234
|
const result = await work();
|
|
2103
|
-
const
|
|
2104
|
-
spin.stop(
|
|
2235
|
+
const line3 = finish2(result);
|
|
2236
|
+
spin.stop(line3 === null ? label : label_(line3));
|
|
2105
2237
|
return result;
|
|
2106
2238
|
} catch (e) {
|
|
2107
2239
|
spin.error(`${label} \u2014 failed`);
|
|
@@ -3119,11 +3251,11 @@ function summariseSync(stdout) {
|
|
|
3119
3251
|
const blocks = /* @__PURE__ */ new Map();
|
|
3120
3252
|
const agentFiles = /* @__PURE__ */ new Map();
|
|
3121
3253
|
const others = [];
|
|
3122
|
-
for (const
|
|
3123
|
-
if (
|
|
3124
|
-
const m = /^(updated|unchanged|stale)\s+(\S+)/.exec(
|
|
3254
|
+
for (const line3 of stdout.split("\n")) {
|
|
3255
|
+
if (line3.trim() === "") continue;
|
|
3256
|
+
const m = /^(updated|unchanged|stale)\s+(\S+)/.exec(line3);
|
|
3125
3257
|
if (m === null) {
|
|
3126
|
-
others.push(
|
|
3258
|
+
others.push(line3);
|
|
3127
3259
|
continue;
|
|
3128
3260
|
}
|
|
3129
3261
|
const [, verb = "", path = ""] = m;
|
|
@@ -3240,13 +3372,13 @@ function scaffoldMessage(subject, version) {
|
|
|
3240
3372
|
|
|
3241
3373
|
${SCAFFOLD_TRAILER}: ${version}`;
|
|
3242
3374
|
}
|
|
3243
|
-
async function commitUpgrade(rnessDir,
|
|
3375
|
+
async function commitUpgrade(rnessDir, message2) {
|
|
3244
3376
|
const add = await run(rnessDir, ["add", "-A"]);
|
|
3245
3377
|
if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
|
|
3246
3378
|
const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
|
|
3247
3379
|
if (staged.code === 0 && !await mergeInProgress(rnessDir))
|
|
3248
3380
|
return { kind: "nothing" };
|
|
3249
|
-
const commit = await run(rnessDir, ["commit", "-q", "-m",
|
|
3381
|
+
const commit = await run(rnessDir, ["commit", "-q", "-m", message2]);
|
|
3250
3382
|
if (commit.code !== 0)
|
|
3251
3383
|
return {
|
|
3252
3384
|
kind: "refused",
|
|
@@ -3353,8 +3485,8 @@ async function mergeScaffold(rnessDir, commit, base) {
|
|
|
3353
3485
|
"--name-status",
|
|
3354
3486
|
"HEAD"
|
|
3355
3487
|
]);
|
|
3356
|
-
for (const
|
|
3357
|
-
const [letter = "", path = ""] =
|
|
3488
|
+
for (const line3 of status2.split("\n")) {
|
|
3489
|
+
const [letter = "", path = ""] = line3.split(" ");
|
|
3358
3490
|
if (path === "" || conflicts.includes(path)) continue;
|
|
3359
3491
|
if (letter === "A") changes.push(["added", path]);
|
|
3360
3492
|
else if (letter === "D") changes.push(["removed", path]);
|
|
@@ -3500,8 +3632,8 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
3500
3632
|
return 1;
|
|
3501
3633
|
}
|
|
3502
3634
|
const outcome = syncOutcome(result, { sshWorks: sshWorks === true });
|
|
3503
|
-
for (const
|
|
3504
|
-
for (const
|
|
3635
|
+
for (const line3 of outcome.hints) ui.hint(line3);
|
|
3636
|
+
for (const line3 of outcome.errors) ui.error(line3);
|
|
3505
3637
|
return outcome.code;
|
|
3506
3638
|
}
|
|
3507
3639
|
|
|
@@ -3606,7 +3738,7 @@ async function createBlank(input) {
|
|
|
3606
3738
|
`Workspace \`${name}\` is ready in ${shown}/.`,
|
|
3607
3739
|
"",
|
|
3608
3740
|
"Next:",
|
|
3609
|
-
...next.map((
|
|
3741
|
+
...next.map((line3) => ` ${line3}`),
|
|
3610
3742
|
""
|
|
3611
3743
|
].join("\n")
|
|
3612
3744
|
);
|
|
@@ -3767,9 +3899,9 @@ async function cloneSelection(input) {
|
|
|
3767
3899
|
manifest = result.manifest;
|
|
3768
3900
|
ui.line("declared", `scope ${result.name} (org/${result.name})`);
|
|
3769
3901
|
} catch (e) {
|
|
3770
|
-
const
|
|
3771
|
-
ui.error(`${spec}: ${
|
|
3772
|
-
failures.push({ spec, message });
|
|
3902
|
+
const message2 = firstLine4(e instanceof Error ? e.message : String(e));
|
|
3903
|
+
ui.error(`${spec}: ${message2}`);
|
|
3904
|
+
failures.push({ spec, message: message2 });
|
|
3773
3905
|
}
|
|
3774
3906
|
}
|
|
3775
3907
|
return failures;
|
|
@@ -4249,7 +4381,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
4249
4381
|
`Workspace for \`${org}\` is ready in ${shown}/.`,
|
|
4250
4382
|
"",
|
|
4251
4383
|
"Next:",
|
|
4252
|
-
...next.map((
|
|
4384
|
+
...next.map((line3) => ` ${line3}`),
|
|
4253
4385
|
""
|
|
4254
4386
|
].join("\n")
|
|
4255
4387
|
);
|
|
@@ -4262,36 +4394,89 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
4262
4394
|
}
|
|
4263
4395
|
}
|
|
4264
4396
|
|
|
4265
|
-
// src/
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4397
|
+
// src/core/blocks.ts
|
|
4398
|
+
import { join as join20 } from "path";
|
|
4399
|
+
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
4400
|
+
async function checkBlocks({
|
|
4401
|
+
root,
|
|
4402
|
+
rnessDir,
|
|
4403
|
+
manifest,
|
|
4404
|
+
org
|
|
4405
|
+
}) {
|
|
4406
|
+
const checks = [];
|
|
4407
|
+
const note = (label, status2, message2 = null) => {
|
|
4408
|
+
checks.push({ label, status: status2, message: message2 });
|
|
4409
|
+
};
|
|
4410
|
+
const derive = () => ({
|
|
4411
|
+
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4412
|
+
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
4413
|
+
checks
|
|
4414
|
+
});
|
|
4415
|
+
if (!await exists(join20(root, "org"))) return derive();
|
|
4416
|
+
const targets = [
|
|
4417
|
+
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
4418
|
+
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
4419
|
+
];
|
|
4420
|
+
for (const [name, entry] of Object.entries(manifest.scopes)) {
|
|
4421
|
+
const segments = entry.path.split("/");
|
|
4422
|
+
targets.push({
|
|
4423
|
+
scope: name,
|
|
4424
|
+
dir: join20(root, ...segments),
|
|
4425
|
+
depth: segments.length,
|
|
4426
|
+
file: "AGENTS.md",
|
|
4427
|
+
label: `${entry.path}/AGENTS.md`
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
for (const t of targets) {
|
|
4431
|
+
if (!await exists(t.dir)) {
|
|
4432
|
+
note(
|
|
4433
|
+
t.label,
|
|
4434
|
+
"missing-dir",
|
|
4435
|
+
`${t.label}: directory not present, block not checked`
|
|
4436
|
+
);
|
|
4437
|
+
continue;
|
|
4274
4438
|
}
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4439
|
+
const text = await readOrNull(join20(t.dir, t.file));
|
|
4440
|
+
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
4441
|
+
const found = findBlock(lines);
|
|
4442
|
+
if (found.kind === "none") {
|
|
4443
|
+
note(
|
|
4444
|
+
t.label,
|
|
4445
|
+
"no-block",
|
|
4446
|
+
`${t.label}: no rness block yet (run ${rnessCommand()} sync)`
|
|
4279
4447
|
);
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
`
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
if (found.kind === "error") {
|
|
4451
|
+
note(t.label, "malformed", `${t.label}: ${found.message}`);
|
|
4452
|
+
continue;
|
|
4453
|
+
}
|
|
4454
|
+
const context2 = await assembleContext({
|
|
4455
|
+
rnessDir,
|
|
4456
|
+
manifest,
|
|
4457
|
+
scope: t.scope
|
|
4458
|
+
});
|
|
4459
|
+
const fresh2 = renderBlock({
|
|
4460
|
+
scope: t.scope,
|
|
4461
|
+
org,
|
|
4462
|
+
depth: t.depth,
|
|
4463
|
+
context: context2
|
|
4464
|
+
});
|
|
4465
|
+
const pointerLeft = t.file === "CLAUDE.md" && lines.some((l) => l.trim() === "@AGENTS.md");
|
|
4466
|
+
if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash) && !pointerLeft)
|
|
4467
|
+
note(t.label, "current");
|
|
4468
|
+
else
|
|
4469
|
+
note(
|
|
4470
|
+
t.label,
|
|
4471
|
+
"stale",
|
|
4472
|
+
`${t.label}: stale rness block (run ${rnessCommand()} sync)`
|
|
4284
4473
|
);
|
|
4285
|
-
process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
|
|
4286
|
-
`);
|
|
4287
|
-
return 0;
|
|
4288
|
-
} catch (e) {
|
|
4289
|
-
return reportError(e);
|
|
4290
4474
|
}
|
|
4475
|
+
return derive();
|
|
4291
4476
|
}
|
|
4292
4477
|
|
|
4293
4478
|
// src/core/contract.ts
|
|
4294
|
-
import { join as
|
|
4479
|
+
import { join as join21 } from "path";
|
|
4295
4480
|
var STATUSES = {
|
|
4296
4481
|
adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
|
|
4297
4482
|
specs: [
|
|
@@ -4310,7 +4495,7 @@ async function checkContract(rnessDir) {
|
|
|
4310
4495
|
const problems = [];
|
|
4311
4496
|
for (const name of COLLECTIONS) {
|
|
4312
4497
|
const allowed = STATUSES[name];
|
|
4313
|
-
const items = await collectMarkdown(
|
|
4498
|
+
const items = await collectMarkdown(join21(rnessDir, name));
|
|
4314
4499
|
for (const item of items) {
|
|
4315
4500
|
const where = `${name}/${item.rel}`;
|
|
4316
4501
|
if (SKIP.has(where)) continue;
|
|
@@ -4346,11 +4531,39 @@ async function checkContract(rnessDir) {
|
|
|
4346
4531
|
return problems;
|
|
4347
4532
|
}
|
|
4348
4533
|
|
|
4534
|
+
// src/commands/logout.ts
|
|
4535
|
+
async function logoutCommand() {
|
|
4536
|
+
try {
|
|
4537
|
+
const stored = await readAuth();
|
|
4538
|
+
await clearAuth();
|
|
4539
|
+
const undone = await removeGitSetup().catch(() => false);
|
|
4540
|
+
if (stored === null && !undone) {
|
|
4541
|
+
process.stdout.write("not logged in\n");
|
|
4542
|
+
return 0;
|
|
4543
|
+
}
|
|
4544
|
+
if (stored !== null)
|
|
4545
|
+
process.stdout.write(
|
|
4546
|
+
`${status("logged out", `${stored.login} (github.com)`)}
|
|
4547
|
+
`
|
|
4548
|
+
);
|
|
4549
|
+
if (undone)
|
|
4550
|
+
process.stdout.write(
|
|
4551
|
+
`${status("updated", "git: rness no longer answers for https://github.com")}
|
|
4552
|
+
`
|
|
4553
|
+
);
|
|
4554
|
+
process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
|
|
4555
|
+
`);
|
|
4556
|
+
return 0;
|
|
4557
|
+
} catch (e) {
|
|
4558
|
+
return reportError(e);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4349
4562
|
// src/commands/upgrade.ts
|
|
4350
4563
|
import { execFile as execFile6 } from "child_process";
|
|
4351
4564
|
import { mkdtemp as mkdtemp3, readFile as readFile9, rm as rm7 } from "fs/promises";
|
|
4352
4565
|
import { tmpdir as tmpdir3 } from "os";
|
|
4353
|
-
import { basename as basename5, join as
|
|
4566
|
+
import { basename as basename5, join as join22, relative as relative5 } from "path";
|
|
4354
4567
|
import { promisify as promisify6 } from "util";
|
|
4355
4568
|
var execFileP6 = promisify6(execFile6);
|
|
4356
4569
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -4370,10 +4583,10 @@ async function latestVersion() {
|
|
|
4370
4583
|
async function scaffoldFor(version, rnessDir) {
|
|
4371
4584
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
4372
4585
|
const installed = await readPinnedCli(rnessDir);
|
|
4373
|
-
const
|
|
4374
|
-
if (installed?.version === version &&
|
|
4375
|
-
return { dir:
|
|
4376
|
-
const scratch = await mkdtemp3(
|
|
4586
|
+
const inside2 = installed === null ? null : join22(installed.dir, "scaffold");
|
|
4587
|
+
if (installed?.version === version && inside2 !== null && await exists(join22(inside2, "package.json")))
|
|
4588
|
+
return { dir: inside2 };
|
|
4589
|
+
const scratch = await mkdtemp3(join22(tmpdir3(), "rness-pack-"));
|
|
4377
4590
|
const cleanup = () => rm7(scratch, { recursive: true, force: true });
|
|
4378
4591
|
try {
|
|
4379
4592
|
const { stdout } = await execFileP6(
|
|
@@ -4388,8 +4601,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
4388
4601
|
{ timeout: 6e4 }
|
|
4389
4602
|
);
|
|
4390
4603
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
4391
|
-
await execFileP6("tar", ["-xzf",
|
|
4392
|
-
return { dir:
|
|
4604
|
+
await execFileP6("tar", ["-xzf", join22(scratch, tarball), "-C", scratch]);
|
|
4605
|
+
return { dir: join22(scratch, "package", "scaffold"), cleanup };
|
|
4393
4606
|
} catch (e) {
|
|
4394
4607
|
await cleanup();
|
|
4395
4608
|
throw new Error(
|
|
@@ -4401,7 +4614,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
4401
4614
|
async function declaredManager(rnessDir, pm) {
|
|
4402
4615
|
try {
|
|
4403
4616
|
const pkg = JSON.parse(
|
|
4404
|
-
await readFile9(
|
|
4617
|
+
await readFile9(join22(rnessDir, "package.json"), "utf8")
|
|
4405
4618
|
);
|
|
4406
4619
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
4407
4620
|
} catch {
|
|
@@ -4412,7 +4625,7 @@ async function clonesToCommit(root, cwd, manifest) {
|
|
|
4412
4625
|
const files = writtenFiles(manifest);
|
|
4413
4626
|
const clones = [];
|
|
4414
4627
|
for (const repo of Object.keys(manifest.repos)) {
|
|
4415
|
-
const dir =
|
|
4628
|
+
const dir = join22(root, "org", repo);
|
|
4416
4629
|
if (!await exists(dir)) continue;
|
|
4417
4630
|
try {
|
|
4418
4631
|
const paths = await changedPaths(dir, files);
|
|
@@ -4425,14 +4638,14 @@ async function clonesToCommit(root, cwd, manifest) {
|
|
|
4425
4638
|
}
|
|
4426
4639
|
function finish(ui, cwd, rnessDir, label, target, ending) {
|
|
4427
4640
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
4428
|
-
const
|
|
4641
|
+
const message2 = `chore: rness ${target}`;
|
|
4429
4642
|
const next = [];
|
|
4430
4643
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
4431
4644
|
if (ending.commit === "refused")
|
|
4432
|
-
next.push(`git -C ${rel} commit -m "${
|
|
4645
|
+
next.push(`git -C ${rel} commit -m "${message2}"`);
|
|
4433
4646
|
for (const c of ending.clones)
|
|
4434
4647
|
next.push(
|
|
4435
|
-
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${
|
|
4648
|
+
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message2}"`
|
|
4436
4649
|
);
|
|
4437
4650
|
if (ending.commit !== "nothing")
|
|
4438
4651
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
@@ -4445,7 +4658,7 @@ function finish(ui, cwd, rnessDir, label, target, ending) {
|
|
|
4445
4658
|
"",
|
|
4446
4659
|
status("upgraded", `${label} to @rness/cli ${target}`),
|
|
4447
4660
|
"",
|
|
4448
|
-
...next.length > 0 ? ["Next:", ...next.map((
|
|
4661
|
+
...next.length > 0 ? ["Next:", ...next.map((line3) => ` ${line3}`), ""] : []
|
|
4449
4662
|
].join("\n")
|
|
4450
4663
|
);
|
|
4451
4664
|
return ending.commit === "refused" ? 1 : 0;
|
|
@@ -4605,7 +4818,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4605
4818
|
await source.cleanup?.();
|
|
4606
4819
|
}
|
|
4607
4820
|
}
|
|
4608
|
-
const packageFile =
|
|
4821
|
+
const packageFile = join22(ws.rnessDir, "package.json");
|
|
4609
4822
|
const restore = /* @__PURE__ */ new Map([
|
|
4610
4823
|
[packageFile, await readFile9(packageFile, "utf8")]
|
|
4611
4824
|
]);
|
|
@@ -4621,8 +4834,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4621
4834
|
() => ["installed", `dependencies with ${pm}`]
|
|
4622
4835
|
);
|
|
4623
4836
|
} catch (e) {
|
|
4624
|
-
const
|
|
4625
|
-
ui.error(
|
|
4837
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
4838
|
+
ui.error(message3);
|
|
4626
4839
|
if (!merged) {
|
|
4627
4840
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
4628
4841
|
ui.hint(
|
|
@@ -4635,7 +4848,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4635
4848
|
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
4636
4849
|
"stderr"
|
|
4637
4850
|
);
|
|
4638
|
-
if (/minimum.?release.?age|NO_MATURE/i.test(
|
|
4851
|
+
if (/minimum.?release.?age|NO_MATURE/i.test(message3))
|
|
4639
4852
|
ui.hint(
|
|
4640
4853
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
4641
4854
|
"stderr"
|
|
@@ -4654,20 +4867,20 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4654
4867
|
ui.gitIsSilent = true;
|
|
4655
4868
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
4656
4869
|
if (code !== 0) return code;
|
|
4657
|
-
const
|
|
4658
|
-
const committed = await commitUpgrade(ws.rnessDir,
|
|
4870
|
+
const message2 = `chore: rness ${target}`;
|
|
4871
|
+
const committed = await commitUpgrade(ws.rnessDir, message2);
|
|
4659
4872
|
if (committed.kind === "committed")
|
|
4660
4873
|
ui.line(
|
|
4661
4874
|
"committed",
|
|
4662
|
-
`${label} \u2014 ${
|
|
4663
|
-
`Committed ${label}: ${
|
|
4875
|
+
`${label} \u2014 ${message2}`,
|
|
4876
|
+
`Committed ${label}: ${message2}`
|
|
4664
4877
|
);
|
|
4665
4878
|
if (committed.kind === "refused") {
|
|
4666
4879
|
ui.error(
|
|
4667
4880
|
`the commit of ${label} was refused; everything is staged \u2014 deal with what git said, then commit`
|
|
4668
4881
|
);
|
|
4669
|
-
for (const
|
|
4670
|
-
if (
|
|
4882
|
+
for (const line3 of committed.output.split("\n"))
|
|
4883
|
+
if (line3.trim() !== "") ui.hint(line3, "stderr");
|
|
4671
4884
|
}
|
|
4672
4885
|
return finish(ui, cwd, ws.rnessDir, label, target, {
|
|
4673
4886
|
commit: committed.kind,
|
|
@@ -4682,85 +4895,28 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4682
4895
|
}
|
|
4683
4896
|
}
|
|
4684
4897
|
|
|
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
|
|
4898
|
+
// src/core/check-workspace.ts
|
|
4899
|
+
async function checkWorkspace(ws, manifest) {
|
|
4900
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
4901
|
+
const problems = await checkContract(ws.rnessDir);
|
|
4902
|
+
const blocks = await checkBlocks({
|
|
4903
|
+
root: ws.root,
|
|
4904
|
+
rnessDir: ws.rnessDir,
|
|
4905
|
+
manifest,
|
|
4906
|
+
org: workspaceName(manifest, ws.root)
|
|
4702
4907
|
});
|
|
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)`
|
|
4908
|
+
problems.push(...blocks.problems);
|
|
4909
|
+
for (const name of unsupportedAgents(manifest.agents ?? []))
|
|
4910
|
+
problems.push(unsupportedMessage(name));
|
|
4911
|
+
const targets = await agentTargets(ws.root, manifest, { check: true });
|
|
4912
|
+
for (const o of targets)
|
|
4913
|
+
if (o.status === "stale")
|
|
4914
|
+
problems.push(
|
|
4915
|
+
`${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
|
|
4761
4916
|
);
|
|
4762
|
-
|
|
4763
|
-
|
|
4917
|
+
else if (o.status === "invalid")
|
|
4918
|
+
problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
|
|
4919
|
+
return { problems, blocks, targets };
|
|
4764
4920
|
}
|
|
4765
4921
|
|
|
4766
4922
|
// src/commands/validate.ts
|
|
@@ -4769,26 +4925,8 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4769
4925
|
try {
|
|
4770
4926
|
const ws = await findWorkspace(cwd);
|
|
4771
4927
|
const manifest = await loadManifest(ws.rnessDir);
|
|
4772
|
-
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
4773
4928
|
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"}`);
|
|
4929
|
+
const { problems, blocks, targets } = await checkWorkspace(ws, manifest);
|
|
4792
4930
|
const scaffoldWarnings = [];
|
|
4793
4931
|
if (await isRepository2(ws.rnessDir) && !await isShallow(ws.rnessDir)) {
|
|
4794
4932
|
const pin = await readPin(ws.rnessDir);
|
|
@@ -4851,9 +4989,9 @@ async function readStdin() {
|
|
|
4851
4989
|
async function gitCredentialCommand(operation, input) {
|
|
4852
4990
|
if (operation !== "get") return 0;
|
|
4853
4991
|
const fields = /* @__PURE__ */ new Map();
|
|
4854
|
-
for (const
|
|
4855
|
-
const eq =
|
|
4856
|
-
if (eq > 0) fields.set(
|
|
4992
|
+
for (const line3 of (input ?? await readStdin()).split("\n")) {
|
|
4993
|
+
const eq = line3.indexOf("=");
|
|
4994
|
+
if (eq > 0) fields.set(line3.slice(0, eq), line3.slice(eq + 1).trimEnd());
|
|
4857
4995
|
}
|
|
4858
4996
|
if (fields.get("protocol") !== "https" || fields.get("host") !== "github.com")
|
|
4859
4997
|
return 0;
|
|
@@ -4865,138 +5003,26 @@ password=${resolved.token}
|
|
|
4865
5003
|
return 0;
|
|
4866
5004
|
}
|
|
4867
5005
|
|
|
4868
|
-
// src/commands/
|
|
4869
|
-
import {
|
|
5006
|
+
// src/commands/hook.ts
|
|
5007
|
+
import { realpath as realpath4 } from "fs/promises";
|
|
5008
|
+
import { isAbsolute as isAbsolute2, posix as posix6, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
4870
5009
|
|
|
4871
|
-
// src/mcp/
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
];
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
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
|
-
};
|
|
5010
|
+
// src/mcp/tools.ts
|
|
5011
|
+
import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
5012
|
+
import { isAbsolute, join as join23, posix as posix5, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
5013
|
+
|
|
5014
|
+
// src/core/documents.ts
|
|
5015
|
+
import { posix as posix4 } from "path";
|
|
5016
|
+
function documentNumber(rel) {
|
|
5017
|
+
return /^(\d{4})-/.exec(posix4.basename(rel))?.[1] ?? null;
|
|
5018
|
+
}
|
|
5019
|
+
function documentTitle(heading, number) {
|
|
5020
|
+
if (number === null) return heading;
|
|
5021
|
+
const rest = new RegExp(`^${number}\\s*[\u2014\u2013:-]\\s*(.+)$`).exec(heading)?.[1];
|
|
5022
|
+
return rest ?? heading;
|
|
4995
5023
|
}
|
|
4996
5024
|
|
|
4997
5025
|
// src/mcp/tools.ts
|
|
4998
|
-
import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
4999
|
-
import { isAbsolute, join as join23, posix as posix4, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
5000
5026
|
var LABELS = {
|
|
5001
5027
|
standards: "Standards",
|
|
5002
5028
|
adr: "Decisions",
|
|
@@ -5021,11 +5047,6 @@ async function workspaceAt(cwd) {
|
|
|
5021
5047
|
const ws = await findWorkspace(cwd);
|
|
5022
5048
|
return { ws, manifest: await loadManifest(ws.rnessDir) };
|
|
5023
5049
|
}
|
|
5024
|
-
function titleOf(heading, id) {
|
|
5025
|
-
if (id === null) return heading;
|
|
5026
|
-
const rest = new RegExp(`^${id}\\s*[\u2014\u2013:-]\\s*(.+)$`).exec(heading)?.[1];
|
|
5027
|
-
return rest ?? heading;
|
|
5028
|
-
}
|
|
5029
5050
|
async function documents(rnessDir, only) {
|
|
5030
5051
|
const docs = [];
|
|
5031
5052
|
for (const collection of COLLECTIONS) {
|
|
@@ -5034,14 +5055,14 @@ async function documents(rnessDir, only) {
|
|
|
5034
5055
|
const path = `${collection}/${item.rel}`;
|
|
5035
5056
|
if (path === TEMPLATE) continue;
|
|
5036
5057
|
const status2 = item.fields?.["status"];
|
|
5037
|
-
const id =
|
|
5058
|
+
const id = documentNumber(item.rel);
|
|
5038
5059
|
docs.push({
|
|
5039
5060
|
collection,
|
|
5040
5061
|
path,
|
|
5041
5062
|
scope: ownerScope(item.rel),
|
|
5042
5063
|
id,
|
|
5043
5064
|
status: typeof status2 === "string" ? status2 : null,
|
|
5044
|
-
title:
|
|
5065
|
+
title: documentTitle(item.title ?? posix5.basename(item.rel, ".md"), id),
|
|
5045
5066
|
text: item.body
|
|
5046
5067
|
});
|
|
5047
5068
|
}
|
|
@@ -5071,23 +5092,33 @@ async function context(cwd, args) {
|
|
|
5071
5092
|
} else
|
|
5072
5093
|
scope = resolveScope(
|
|
5073
5094
|
manifest,
|
|
5074
|
-
relative6(ws.root, cwd).split(sep4).join(
|
|
5095
|
+
relative6(ws.root, cwd).split(sep4).join(posix5.sep)
|
|
5075
5096
|
);
|
|
5097
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
5098
|
+
return {
|
|
5099
|
+
text: [
|
|
5100
|
+
...summary.lines,
|
|
5101
|
+
"Read one with rness_read; search with rness_search."
|
|
5102
|
+
].join("\n")
|
|
5103
|
+
};
|
|
5104
|
+
}
|
|
5105
|
+
async function scopeSummary(ws, manifest, scope) {
|
|
5076
5106
|
const entry = scope === null ? void 0 : manifest.scopes[scope];
|
|
5077
5107
|
const where = scope === null || entry === void 0 ? "global scope" : `scope ${scope} (${entry.path}${entry.extends.length > 0 ? `, extends ${entry.extends.join(", ")}` : ""})`;
|
|
5078
5108
|
const ctx = await assembleContext({ rnessDir: ws.rnessDir, manifest, scope });
|
|
5079
5109
|
const index = new Map(
|
|
5080
5110
|
(await documents(ws.rnessDir)).map((d) => [d.path, d])
|
|
5081
5111
|
);
|
|
5082
|
-
const
|
|
5112
|
+
const lines = [`Workspace ${workspaceName(manifest, ws.root)} \xB7 ${where}`];
|
|
5113
|
+
const counts = {};
|
|
5083
5114
|
for (const collection of ctx.collections) {
|
|
5084
5115
|
const docs = collection.files.map((f) => index.get(`${collection.name}/${f.rel}`)).filter((d) => d !== void 0);
|
|
5085
5116
|
if (docs.length === 0) continue;
|
|
5086
|
-
|
|
5087
|
-
|
|
5117
|
+
counts[collection.name] = docs.length;
|
|
5118
|
+
lines.push(`${LABELS[collection.name]} (${docs.length}):`);
|
|
5119
|
+
for (const d of docs) lines.push(line(d));
|
|
5088
5120
|
}
|
|
5089
|
-
|
|
5090
|
-
return { text: out.join("\n") };
|
|
5121
|
+
return { where, counts, lines };
|
|
5091
5122
|
}
|
|
5092
5123
|
async function list(cwd, args) {
|
|
5093
5124
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -5248,6 +5279,285 @@ function mcpTools(cwd) {
|
|
|
5248
5279
|
];
|
|
5249
5280
|
}
|
|
5250
5281
|
|
|
5282
|
+
// src/commands/hook.ts
|
|
5283
|
+
var NOUNS = {
|
|
5284
|
+
standards: ["standard", "standards"],
|
|
5285
|
+
adr: ["decision", "decisions"],
|
|
5286
|
+
specs: ["specification", "specifications"],
|
|
5287
|
+
plans: ["plan", "plans"],
|
|
5288
|
+
skills: ["skill", "skills"]
|
|
5289
|
+
};
|
|
5290
|
+
var SHOWN_PROBLEMS = 3;
|
|
5291
|
+
async function readInput(input) {
|
|
5292
|
+
if (input.isTTY === true) return {};
|
|
5293
|
+
let text = "";
|
|
5294
|
+
for await (const chunk of input) text += String(chunk);
|
|
5295
|
+
try {
|
|
5296
|
+
const data = JSON.parse(text);
|
|
5297
|
+
return data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
5298
|
+
} catch {
|
|
5299
|
+
return {};
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
var message = (e) => e instanceof Error ? e.message : String(e);
|
|
5303
|
+
function cwdOf(input, env) {
|
|
5304
|
+
const cwd = input["cwd"];
|
|
5305
|
+
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
5306
|
+
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
5307
|
+
}
|
|
5308
|
+
var toPosix2 = (p) => p.split(sep5).join(posix6.sep);
|
|
5309
|
+
function count(n, [one, many]) {
|
|
5310
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
5311
|
+
}
|
|
5312
|
+
async function safetyNet(ws, manifest) {
|
|
5313
|
+
const notes = [];
|
|
5314
|
+
const drift = await pinDrift(ws.rnessDir);
|
|
5315
|
+
if (drift !== null)
|
|
5316
|
+
notes.push(
|
|
5317
|
+
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
5318
|
+
);
|
|
5319
|
+
try {
|
|
5320
|
+
const { problems } = await checkWorkspace(ws, manifest);
|
|
5321
|
+
if (problems.length > 0)
|
|
5322
|
+
notes.push(
|
|
5323
|
+
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
5324
|
+
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
5325
|
+
);
|
|
5326
|
+
} catch (e) {
|
|
5327
|
+
notes.push(message(e));
|
|
5328
|
+
}
|
|
5329
|
+
return notes;
|
|
5330
|
+
}
|
|
5331
|
+
async function sessionStart(input, env) {
|
|
5332
|
+
const cwd = cwdOf(input, env);
|
|
5333
|
+
let banner2 = null;
|
|
5334
|
+
let context2 = [];
|
|
5335
|
+
let notes;
|
|
5336
|
+
try {
|
|
5337
|
+
const ws = await findWorkspace(cwd);
|
|
5338
|
+
const manifest = await loadManifest(ws.rnessDir);
|
|
5339
|
+
const scope = resolveScope(manifest, toPosix2(relative7(ws.root, cwd)));
|
|
5340
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
5341
|
+
const counts = COLLECTIONS.flatMap((c) => {
|
|
5342
|
+
const n = summary.counts[c];
|
|
5343
|
+
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
5344
|
+
});
|
|
5345
|
+
banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
|
|
5346
|
+
const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
|
|
5347
|
+
context2 = [
|
|
5348
|
+
...summary.lines,
|
|
5349
|
+
`These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
|
|
5350
|
+
];
|
|
5351
|
+
notes = await safetyNet(ws, manifest);
|
|
5352
|
+
} catch (e) {
|
|
5353
|
+
notes = [message(e)];
|
|
5354
|
+
}
|
|
5355
|
+
const warnings = notes.map((n) => `rness: ${n}`);
|
|
5356
|
+
const shown = [
|
|
5357
|
+
...banner2 !== null && input["source"] !== "compact" ? [banner2] : [],
|
|
5358
|
+
...warnings
|
|
5359
|
+
];
|
|
5360
|
+
return {
|
|
5361
|
+
...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
|
|
5362
|
+
hookSpecificOutput: {
|
|
5363
|
+
hookEventName: "SessionStart",
|
|
5364
|
+
additionalContext: [...context2, ...warnings].join("\n")
|
|
5365
|
+
}
|
|
5366
|
+
};
|
|
5367
|
+
}
|
|
5368
|
+
function inside(from, to) {
|
|
5369
|
+
const rel = relative7(from, to);
|
|
5370
|
+
return rel === "" || rel.startsWith("..") || isAbsolute2(rel) ? null : toPosix2(rel);
|
|
5371
|
+
}
|
|
5372
|
+
async function postToolUse(input, io) {
|
|
5373
|
+
const toolInput = input["tool_input"];
|
|
5374
|
+
const path = toolInput !== null && typeof toolInput === "object" ? toolInput["file_path"] : void 0;
|
|
5375
|
+
if (typeof path !== "string" || path === "") return 0;
|
|
5376
|
+
const cwd = cwdOf(input, io.env);
|
|
5377
|
+
let ws;
|
|
5378
|
+
try {
|
|
5379
|
+
ws = await findWorkspace(cwd);
|
|
5380
|
+
} catch {
|
|
5381
|
+
return 0;
|
|
5382
|
+
}
|
|
5383
|
+
const file = resolve7(cwd, path);
|
|
5384
|
+
const rel = inside(ws.rnessDir, file) ?? inside(
|
|
5385
|
+
await realpath4(ws.rnessDir).catch(() => ws.rnessDir),
|
|
5386
|
+
await realpath4(file).catch(() => file)
|
|
5387
|
+
);
|
|
5388
|
+
if (rel === null) return 0;
|
|
5389
|
+
const first = rel.split("/")[0] ?? "";
|
|
5390
|
+
let problems = [];
|
|
5391
|
+
if (rel === "rness.json") {
|
|
5392
|
+
try {
|
|
5393
|
+
const manifest = await loadManifest(ws.rnessDir);
|
|
5394
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
5395
|
+
} catch (e) {
|
|
5396
|
+
problems = [message(e)];
|
|
5397
|
+
}
|
|
5398
|
+
} else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
|
|
5399
|
+
problems = (await checkContract(ws.rnessDir)).filter(
|
|
5400
|
+
(p) => p.startsWith(`${rel}:`)
|
|
5401
|
+
);
|
|
5402
|
+
if (problems.length === 0) return 0;
|
|
5403
|
+
io.error.write(
|
|
5404
|
+
[
|
|
5405
|
+
...problems.map((p) => `rness: ${p}`),
|
|
5406
|
+
`Fix it in ${rel}; rness validate checks the whole workspace.`,
|
|
5407
|
+
""
|
|
5408
|
+
].join("\n")
|
|
5409
|
+
);
|
|
5410
|
+
return 2;
|
|
5411
|
+
}
|
|
5412
|
+
async function hookCommand(event, io = {
|
|
5413
|
+
input: process.stdin,
|
|
5414
|
+
output: process.stdout,
|
|
5415
|
+
error: process.stderr,
|
|
5416
|
+
env: process.env
|
|
5417
|
+
}) {
|
|
5418
|
+
if (event !== "session-start" && event !== "post-tool-use") {
|
|
5419
|
+
io.error.write(
|
|
5420
|
+
`unknown hook event ${JSON.stringify(event)} (known: session-start, post-tool-use)
|
|
5421
|
+
`
|
|
5422
|
+
);
|
|
5423
|
+
return 1;
|
|
5424
|
+
}
|
|
5425
|
+
const input = await readInput(io.input);
|
|
5426
|
+
if (event === "post-tool-use") return postToolUse(input, io);
|
|
5427
|
+
io.output.write(`${JSON.stringify(await sessionStart(input, io.env))}
|
|
5428
|
+
`);
|
|
5429
|
+
return 0;
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5432
|
+
// src/commands/mcp.ts
|
|
5433
|
+
import { createInterface } from "readline";
|
|
5434
|
+
|
|
5435
|
+
// src/mcp/protocol.ts
|
|
5436
|
+
var MODERN_VERSIONS = ["2026-07-28"];
|
|
5437
|
+
var LEGACY_VERSIONS = [
|
|
5438
|
+
"2025-11-25",
|
|
5439
|
+
"2025-06-18",
|
|
5440
|
+
"2025-03-26",
|
|
5441
|
+
"2024-11-05"
|
|
5442
|
+
];
|
|
5443
|
+
var SUPPORTED = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
|
|
5444
|
+
var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
5445
|
+
var CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
5446
|
+
var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
5447
|
+
function isObject2(value) {
|
|
5448
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5449
|
+
}
|
|
5450
|
+
function errorLine(id, code, message2, data) {
|
|
5451
|
+
return JSON.stringify({
|
|
5452
|
+
jsonrpc: "2.0",
|
|
5453
|
+
id,
|
|
5454
|
+
error: data === void 0 ? { code, message: message2 } : { code, message: message2, data }
|
|
5455
|
+
});
|
|
5456
|
+
}
|
|
5457
|
+
function createProtocol(opts) {
|
|
5458
|
+
const serverInfo = { name: opts.name, version: opts.version };
|
|
5459
|
+
return {
|
|
5460
|
+
async handle(line3) {
|
|
5461
|
+
let message2;
|
|
5462
|
+
try {
|
|
5463
|
+
message2 = JSON.parse(line3);
|
|
5464
|
+
} catch {
|
|
5465
|
+
return errorLine(null, -32700, "Parse error");
|
|
5466
|
+
}
|
|
5467
|
+
const rawId = isObject2(message2) ? message2["id"] : void 0;
|
|
5468
|
+
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
5469
|
+
if (!isObject2(message2) || message2["jsonrpc"] !== "2.0" || typeof message2["method"] !== "string")
|
|
5470
|
+
return errorLine(id, -32600, "Invalid Request");
|
|
5471
|
+
if (!("id" in message2)) return null;
|
|
5472
|
+
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
5473
|
+
const method = message2["method"];
|
|
5474
|
+
const params = isObject2(message2["params"]) ? message2["params"] : {};
|
|
5475
|
+
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
5476
|
+
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
5477
|
+
if (modern) {
|
|
5478
|
+
const requested = meta[VERSION_KEY];
|
|
5479
|
+
if (!MODERN_VERSIONS.includes(requested))
|
|
5480
|
+
return errorLine(id, -32022, "Unsupported protocol version", {
|
|
5481
|
+
supported: SUPPORTED,
|
|
5482
|
+
requested
|
|
5483
|
+
});
|
|
5484
|
+
if (!isObject2(meta[CAPABILITIES_KEY]))
|
|
5485
|
+
return errorLine(
|
|
5486
|
+
id,
|
|
5487
|
+
-32602,
|
|
5488
|
+
`Invalid params: ${CAPABILITIES_KEY} is required`
|
|
5489
|
+
);
|
|
5490
|
+
}
|
|
5491
|
+
const answer = (result) => JSON.stringify({
|
|
5492
|
+
jsonrpc: "2.0",
|
|
5493
|
+
id,
|
|
5494
|
+
result: modern ? {
|
|
5495
|
+
resultType: "complete",
|
|
5496
|
+
...result,
|
|
5497
|
+
_meta: { [SERVER_INFO_KEY]: serverInfo }
|
|
5498
|
+
} : result
|
|
5499
|
+
});
|
|
5500
|
+
switch (method) {
|
|
5501
|
+
case "server/discover":
|
|
5502
|
+
return answer({
|
|
5503
|
+
supportedVersions: SUPPORTED,
|
|
5504
|
+
capabilities: { tools: {} }
|
|
5505
|
+
});
|
|
5506
|
+
case "initialize": {
|
|
5507
|
+
const asked = params["protocolVersion"];
|
|
5508
|
+
const protocolVersion = typeof asked === "string" && LEGACY_VERSIONS.includes(asked) ? asked : LEGACY_VERSIONS[0];
|
|
5509
|
+
return JSON.stringify({
|
|
5510
|
+
jsonrpc: "2.0",
|
|
5511
|
+
id,
|
|
5512
|
+
result: {
|
|
5513
|
+
protocolVersion,
|
|
5514
|
+
capabilities: { tools: {} },
|
|
5515
|
+
serverInfo
|
|
5516
|
+
}
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5519
|
+
case "ping":
|
|
5520
|
+
return answer({});
|
|
5521
|
+
case "tools/list":
|
|
5522
|
+
return answer({
|
|
5523
|
+
tools: opts.tools.map((t) => ({
|
|
5524
|
+
name: t.name,
|
|
5525
|
+
title: t.title,
|
|
5526
|
+
description: t.description,
|
|
5527
|
+
inputSchema: t.inputSchema
|
|
5528
|
+
}))
|
|
5529
|
+
});
|
|
5530
|
+
case "tools/call": {
|
|
5531
|
+
const name = params["name"];
|
|
5532
|
+
const tool = opts.tools.find((t) => t.name === name);
|
|
5533
|
+
if (tool === void 0)
|
|
5534
|
+
return errorLine(id, -32602, `Unknown tool: ${String(name)}`);
|
|
5535
|
+
const args = isObject2(params["arguments"]) ? params["arguments"] : {};
|
|
5536
|
+
try {
|
|
5537
|
+
const r = await tool.call(args);
|
|
5538
|
+
return answer({
|
|
5539
|
+
content: [{ type: "text", text: r.text }],
|
|
5540
|
+
isError: r.isError === true
|
|
5541
|
+
});
|
|
5542
|
+
} catch (e) {
|
|
5543
|
+
return answer({
|
|
5544
|
+
content: [
|
|
5545
|
+
{
|
|
5546
|
+
type: "text",
|
|
5547
|
+
text: `${tool.name} failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5548
|
+
}
|
|
5549
|
+
],
|
|
5550
|
+
isError: true
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
default:
|
|
5555
|
+
return errorLine(id, -32601, "Method not found");
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
|
|
5251
5561
|
// src/commands/mcp.ts
|
|
5252
5562
|
async function mcpCommand(opts, io = {
|
|
5253
5563
|
input: process.stdin,
|
|
@@ -5259,15 +5569,349 @@ async function mcpCommand(opts, io = {
|
|
|
5259
5569
|
tools: mcpTools(opts.cwd ?? process.cwd())
|
|
5260
5570
|
});
|
|
5261
5571
|
const lines = createInterface({ input: io.input, crlfDelay: Infinity });
|
|
5262
|
-
for await (const
|
|
5263
|
-
if (
|
|
5264
|
-
const answer = await protocol.handle(
|
|
5572
|
+
for await (const line3 of lines) {
|
|
5573
|
+
if (line3.trim() === "") continue;
|
|
5574
|
+
const answer = await protocol.handle(line3);
|
|
5265
5575
|
if (answer !== null) io.output.write(`${answer}
|
|
5266
5576
|
`);
|
|
5267
5577
|
}
|
|
5268
5578
|
return 0;
|
|
5269
5579
|
}
|
|
5270
5580
|
|
|
5581
|
+
// src/commands/status.ts
|
|
5582
|
+
import { emitKeypressEvents } from "readline";
|
|
5583
|
+
|
|
5584
|
+
// src/core/status.ts
|
|
5585
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
5586
|
+
import { join as join24, posix as posix7 } from "path";
|
|
5587
|
+
var FIXED = [
|
|
5588
|
+
["adr", "ADR"],
|
|
5589
|
+
["specs", "Specs"],
|
|
5590
|
+
["plans", "Plans"]
|
|
5591
|
+
];
|
|
5592
|
+
var NOT_DISCOVERED = /* @__PURE__ */ new Set([
|
|
5593
|
+
...FIXED.map(([name]) => name),
|
|
5594
|
+
"standards",
|
|
5595
|
+
"skills",
|
|
5596
|
+
"node_modules"
|
|
5597
|
+
]);
|
|
5598
|
+
var TEMPLATE2 = "adr/0000-template.md";
|
|
5599
|
+
function statusOf(item) {
|
|
5600
|
+
const status2 = item.fields?.["status"];
|
|
5601
|
+
return typeof status2 === "string" && status2 !== "" ? status2 : null;
|
|
5602
|
+
}
|
|
5603
|
+
function rowOf(dir, item) {
|
|
5604
|
+
const base = posix7.basename(item.rel, ".md");
|
|
5605
|
+
const date = /^(\d{4}-\d{2}-\d{2})-/.exec(base)?.[1] ?? null;
|
|
5606
|
+
const number = date === null ? documentNumber(item.rel) : null;
|
|
5607
|
+
return {
|
|
5608
|
+
id: date ?? number ?? base,
|
|
5609
|
+
title: documentTitle(item.title ?? base, number),
|
|
5610
|
+
status: statusOf(item),
|
|
5611
|
+
path: `${dir}/${item.rel}`
|
|
5612
|
+
};
|
|
5613
|
+
}
|
|
5614
|
+
function newestFirst(a, b) {
|
|
5615
|
+
const [x, y] = [posix7.basename(a.path), posix7.basename(b.path)];
|
|
5616
|
+
if (x !== y) return x < y ? 1 : -1;
|
|
5617
|
+
return a.path < b.path ? 1 : a.path > b.path ? -1 : 0;
|
|
5618
|
+
}
|
|
5619
|
+
async function statusTabs(rnessDir) {
|
|
5620
|
+
const tabs = [];
|
|
5621
|
+
for (const [name, label] of FIXED) {
|
|
5622
|
+
const rows = (await collectMarkdown(join24(rnessDir, name))).map((item) => rowOf(name, item)).filter((row) => row.path !== TEMPLATE2);
|
|
5623
|
+
tabs.push({ name, label, rows: rows.sort(newestFirst) });
|
|
5624
|
+
}
|
|
5625
|
+
const entries = await readdir3(rnessDir, { withFileTypes: true }).catch(
|
|
5626
|
+
() => []
|
|
5627
|
+
);
|
|
5628
|
+
const discovered = entries.filter(
|
|
5629
|
+
(e) => e.isDirectory() && !e.name.startsWith(".") && !NOT_DISCOVERED.has(e.name)
|
|
5630
|
+
).map((e) => e.name).sort();
|
|
5631
|
+
for (const name of discovered) {
|
|
5632
|
+
const rows = (await collectMarkdown(join24(rnessDir, name))).filter((item) => statusOf(item) !== null).map((item) => rowOf(name, item));
|
|
5633
|
+
if (rows.length > 0)
|
|
5634
|
+
tabs.push({
|
|
5635
|
+
name,
|
|
5636
|
+
label: `${name.charAt(0).toUpperCase()}${name.slice(1)}`,
|
|
5637
|
+
rows: rows.sort(newestFirst)
|
|
5638
|
+
});
|
|
5639
|
+
}
|
|
5640
|
+
return tabs;
|
|
5641
|
+
}
|
|
5642
|
+
function findTab(tabs, wanted) {
|
|
5643
|
+
const w = wanted.toLowerCase();
|
|
5644
|
+
return tabs.find(
|
|
5645
|
+
(tab) => tab.name.toLowerCase() === w || tab.label.toLowerCase() === w
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
var DONE2 = /* @__PURE__ */ new Set([
|
|
5649
|
+
"accepted",
|
|
5650
|
+
"implemented",
|
|
5651
|
+
"completed",
|
|
5652
|
+
"published"
|
|
5653
|
+
]);
|
|
5654
|
+
var DROPPED = /* @__PURE__ */ new Set([
|
|
5655
|
+
"rejected",
|
|
5656
|
+
"superseded",
|
|
5657
|
+
"abandoned"
|
|
5658
|
+
]);
|
|
5659
|
+
function statusTone(status2) {
|
|
5660
|
+
if (status2 === null) return "missing";
|
|
5661
|
+
const s = status2.toLowerCase();
|
|
5662
|
+
if (DONE2.has(s)) return "done";
|
|
5663
|
+
if (DROPPED.has(s)) return "dropped";
|
|
5664
|
+
return "active";
|
|
5665
|
+
}
|
|
5666
|
+
var cell = (text) => text.replaceAll("|", "\\|");
|
|
5667
|
+
function statusMarkdown(workspace, tabs) {
|
|
5668
|
+
const out = [`${workspace} \xB7 status`, ""];
|
|
5669
|
+
for (const tab of tabs) {
|
|
5670
|
+
out.push(`## ${tab.label} (${tab.rows.length})`, "");
|
|
5671
|
+
if (tab.rows.length === 0) {
|
|
5672
|
+
out.push("_Nothing yet._", "");
|
|
5673
|
+
continue;
|
|
5674
|
+
}
|
|
5675
|
+
out.push("| Id | Title | Status |", "| --- | --- | --- |");
|
|
5676
|
+
for (const row of tab.rows)
|
|
5677
|
+
out.push(
|
|
5678
|
+
`| ${cell(row.id)} | ${cell(row.title)} | ${cell(row.status ?? "?")} |`
|
|
5679
|
+
);
|
|
5680
|
+
out.push("");
|
|
5681
|
+
}
|
|
5682
|
+
return out.join("\n");
|
|
5683
|
+
}
|
|
5684
|
+
|
|
5685
|
+
// src/core/status-view.ts
|
|
5686
|
+
var FRAME = 5;
|
|
5687
|
+
function pageSize(height) {
|
|
5688
|
+
return Math.max(1, height - FRAME);
|
|
5689
|
+
}
|
|
5690
|
+
function lastTop(tab, page) {
|
|
5691
|
+
return Math.max(0, (tab?.rows.length ?? 0) - page);
|
|
5692
|
+
}
|
|
5693
|
+
function press(view, key, tabs, page) {
|
|
5694
|
+
const n = tabs.length;
|
|
5695
|
+
if (key === "right" || key === "tab")
|
|
5696
|
+
return { tab: (view.tab + 1) % n, top: 0 };
|
|
5697
|
+
if (key === "left" || key === "backtab")
|
|
5698
|
+
return { tab: (view.tab - 1 + n) % n, top: 0 };
|
|
5699
|
+
const moved = {
|
|
5700
|
+
up: view.top - 1,
|
|
5701
|
+
down: view.top + 1,
|
|
5702
|
+
pageup: view.top - page,
|
|
5703
|
+
pagedown: view.top + page,
|
|
5704
|
+
home: 0,
|
|
5705
|
+
end: Number.MAX_SAFE_INTEGER
|
|
5706
|
+
};
|
|
5707
|
+
const top = moved[key] ?? view.top;
|
|
5708
|
+
return {
|
|
5709
|
+
tab: view.tab,
|
|
5710
|
+
top: Math.min(Math.max(0, top), lastTop(tabs[view.tab], page))
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
function clampView(view, tabs, page) {
|
|
5714
|
+
return {
|
|
5715
|
+
tab: view.tab,
|
|
5716
|
+
top: Math.min(view.top, lastTop(tabs[view.tab], page))
|
|
5717
|
+
};
|
|
5718
|
+
}
|
|
5719
|
+
var width = (text) => [...text].length;
|
|
5720
|
+
function fit(text, n) {
|
|
5721
|
+
if (n <= 0) return "";
|
|
5722
|
+
const chars = [...text];
|
|
5723
|
+
if (chars.length <= n) return text + " ".repeat(n - chars.length);
|
|
5724
|
+
return `${chars.slice(0, n - 1).join("").trimEnd()}\u2026`.padEnd(n);
|
|
5725
|
+
}
|
|
5726
|
+
function line2(segments, columns) {
|
|
5727
|
+
let left = columns;
|
|
5728
|
+
let out = "";
|
|
5729
|
+
for (const [text, style] of segments) {
|
|
5730
|
+
if (left <= 0) break;
|
|
5731
|
+
const chars = [...text];
|
|
5732
|
+
const kept = chars.length <= left ? text : chars.slice(0, left).join("");
|
|
5733
|
+
left -= width(kept);
|
|
5734
|
+
out += style === void 0 ? kept : style(kept);
|
|
5735
|
+
}
|
|
5736
|
+
return out;
|
|
5737
|
+
}
|
|
5738
|
+
function tabBar(tabs, active, look) {
|
|
5739
|
+
const segments = [[" "]];
|
|
5740
|
+
tabs.forEach((tab, i) => {
|
|
5741
|
+
if (i > 0) segments.push([" "]);
|
|
5742
|
+
const label = ` ${tab.label} (${tab.rows.length}) `;
|
|
5743
|
+
segments.push(i === active ? [label, look.inverse] : [label]);
|
|
5744
|
+
});
|
|
5745
|
+
return segments;
|
|
5746
|
+
}
|
|
5747
|
+
function rowLines(tab, top, page, columns, look) {
|
|
5748
|
+
if (tab === void 0 || tab.rows.length === 0)
|
|
5749
|
+
return [[[" Nothing here yet.", look.dim]]];
|
|
5750
|
+
const idWidth = Math.max(...tab.rows.map((r) => width(r.id)));
|
|
5751
|
+
const statusWidth = Math.max(...tab.rows.map((r) => width(r.status ?? "?")));
|
|
5752
|
+
const titleWidth = Math.max(
|
|
5753
|
+
1,
|
|
5754
|
+
columns - 1 - (2 + idWidth + 2 + 2 + statusWidth)
|
|
5755
|
+
);
|
|
5756
|
+
return tab.rows.slice(top, top + page).map((row) => [
|
|
5757
|
+
[" "],
|
|
5758
|
+
[row.id.padEnd(idWidth)],
|
|
5759
|
+
[" "],
|
|
5760
|
+
[fit(row.title, titleWidth)],
|
|
5761
|
+
[" "],
|
|
5762
|
+
[
|
|
5763
|
+
(row.status ?? "?").padEnd(statusWidth),
|
|
5764
|
+
(text) => look.tone(statusTone(row.status), text)
|
|
5765
|
+
]
|
|
5766
|
+
]);
|
|
5767
|
+
}
|
|
5768
|
+
function footer(above, below) {
|
|
5769
|
+
const parts = [
|
|
5770
|
+
...above > 0 ? [`\u2191 ${above} above`] : [],
|
|
5771
|
+
...below > 0 ? [`\u2193 ${below} more`] : [],
|
|
5772
|
+
"\u2190/\u2192 tab \xB7 \u2191/\u2193 scroll \xB7 q close"
|
|
5773
|
+
];
|
|
5774
|
+
return ` ${parts.join(" \xB7 ")}`;
|
|
5775
|
+
}
|
|
5776
|
+
function renderView(view, tabs, workspace, columns, height, look) {
|
|
5777
|
+
const page = pageSize(height);
|
|
5778
|
+
const tab = tabs[view.tab];
|
|
5779
|
+
const rule = [[` ${"\u2500".repeat(Math.max(0, columns - 2))}`]];
|
|
5780
|
+
const rows = rowLines(tab, view.top, page, columns, look);
|
|
5781
|
+
const shown = Math.min(page, rows.length);
|
|
5782
|
+
const total = tab?.rows.length ?? 0;
|
|
5783
|
+
const lines = [
|
|
5784
|
+
[[` rness \xB7 ${workspace}`, look.bold]],
|
|
5785
|
+
tabBar(tabs, view.tab, look),
|
|
5786
|
+
rule,
|
|
5787
|
+
...rows,
|
|
5788
|
+
...Array.from({ length: page - shown }, () => [[""]]),
|
|
5789
|
+
rule,
|
|
5790
|
+
[[footer(view.top, Math.max(0, total - view.top - page)), look.dim]]
|
|
5791
|
+
];
|
|
5792
|
+
return lines.slice(0, Math.max(0, height)).map((l) => line2(l, columns));
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
// src/commands/status.ts
|
|
5796
|
+
var ENTER = "\x1B[?1049h\x1B[?25l";
|
|
5797
|
+
var LEAVE = "\x1B[?25h\x1B[?1049l";
|
|
5798
|
+
function lookFor(output) {
|
|
5799
|
+
return {
|
|
5800
|
+
bold: (text) => bold(text, output),
|
|
5801
|
+
dim: (text) => grey(text, output),
|
|
5802
|
+
inverse: (text) => inverse(text, output),
|
|
5803
|
+
tone: (tone, text) => tone === "done" ? paint("done", text, output) : tone === "dropped" ? grey(text, output) : tone === "missing" ? paint("error", text, output) : paint("warn", text, output)
|
|
5804
|
+
};
|
|
5805
|
+
}
|
|
5806
|
+
var MOVES = /* @__PURE__ */ new Set([
|
|
5807
|
+
"left",
|
|
5808
|
+
"right",
|
|
5809
|
+
"up",
|
|
5810
|
+
"down",
|
|
5811
|
+
"pageup",
|
|
5812
|
+
"pagedown",
|
|
5813
|
+
"home",
|
|
5814
|
+
"end"
|
|
5815
|
+
]);
|
|
5816
|
+
function keyOf(key) {
|
|
5817
|
+
const name = key?.name;
|
|
5818
|
+
if (name === void 0) return null;
|
|
5819
|
+
if (name === "q" || name === "escape" || key?.ctrl === true && name === "c")
|
|
5820
|
+
return "close";
|
|
5821
|
+
if (name === "tab") return key?.shift === true ? "backtab" : "tab";
|
|
5822
|
+
return MOVES.has(name) ? name : null;
|
|
5823
|
+
}
|
|
5824
|
+
function showView(tabs, workspace, start, { input, output }) {
|
|
5825
|
+
return new Promise((resolve8, reject) => {
|
|
5826
|
+
const look = lookFor(output);
|
|
5827
|
+
let view = { tab: start, top: 0 };
|
|
5828
|
+
const rows = () => output.rows || 24;
|
|
5829
|
+
const draw = () => {
|
|
5830
|
+
view = clampView(view, tabs, pageSize(rows()));
|
|
5831
|
+
const lines = renderView(
|
|
5832
|
+
view,
|
|
5833
|
+
tabs,
|
|
5834
|
+
workspace,
|
|
5835
|
+
output.columns || 80,
|
|
5836
|
+
rows(),
|
|
5837
|
+
look
|
|
5838
|
+
);
|
|
5839
|
+
output.write(`\x1B[H${lines.map((l) => `${l}\x1B[K`).join("\r\n")}\x1B[J`);
|
|
5840
|
+
};
|
|
5841
|
+
const restore = () => {
|
|
5842
|
+
output.write(LEAVE);
|
|
5843
|
+
};
|
|
5844
|
+
const close = (error) => {
|
|
5845
|
+
input.off("keypress", onKey);
|
|
5846
|
+
output.off("resize", onResize);
|
|
5847
|
+
process.off("exit", restore);
|
|
5848
|
+
input.setRawMode(false);
|
|
5849
|
+
input.pause();
|
|
5850
|
+
restore();
|
|
5851
|
+
if (error === void 0) resolve8();
|
|
5852
|
+
else reject(error);
|
|
5853
|
+
};
|
|
5854
|
+
const guarded = (step2) => {
|
|
5855
|
+
try {
|
|
5856
|
+
step2();
|
|
5857
|
+
} catch (e) {
|
|
5858
|
+
close(e);
|
|
5859
|
+
}
|
|
5860
|
+
};
|
|
5861
|
+
const onKey = (_, key) => {
|
|
5862
|
+
const k = keyOf(key);
|
|
5863
|
+
if (k === "close") close();
|
|
5864
|
+
else if (k !== null)
|
|
5865
|
+
guarded(() => {
|
|
5866
|
+
view = press(view, k, tabs, pageSize(rows()));
|
|
5867
|
+
draw();
|
|
5868
|
+
});
|
|
5869
|
+
};
|
|
5870
|
+
const onResize = () => {
|
|
5871
|
+
guarded(draw);
|
|
5872
|
+
};
|
|
5873
|
+
emitKeypressEvents(input);
|
|
5874
|
+
input.setRawMode(true);
|
|
5875
|
+
input.resume();
|
|
5876
|
+
output.write(ENTER);
|
|
5877
|
+
process.on("exit", restore);
|
|
5878
|
+
input.on("keypress", onKey);
|
|
5879
|
+
output.on("resize", onResize);
|
|
5880
|
+
guarded(draw);
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5883
|
+
async function statusCommand(tab, opts, terminal = { input: process.stdin, output: process.stdout }) {
|
|
5884
|
+
try {
|
|
5885
|
+
const ws = await findWorkspace(opts.cwd ?? process.cwd());
|
|
5886
|
+
const manifest = await loadManifest(ws.rnessDir);
|
|
5887
|
+
const workspace = workspaceName(manifest, ws.root);
|
|
5888
|
+
const tabs = await statusTabs(ws.rnessDir);
|
|
5889
|
+
const wanted = tab === void 0 ? void 0 : findTab(tabs, tab);
|
|
5890
|
+
if (tab !== void 0 && wanted === void 0) {
|
|
5891
|
+
process.stderr.write(
|
|
5892
|
+
`unknown tab "${tab}" (tabs: ${tabs.map((t) => t.name).join(", ")})
|
|
5893
|
+
`
|
|
5894
|
+
);
|
|
5895
|
+
return 2;
|
|
5896
|
+
}
|
|
5897
|
+
if (terminal.input.isTTY === true && terminal.output.isTTY === true) {
|
|
5898
|
+
await showView(
|
|
5899
|
+
tabs,
|
|
5900
|
+
workspace,
|
|
5901
|
+
wanted === void 0 ? 0 : tabs.indexOf(wanted),
|
|
5902
|
+
terminal
|
|
5903
|
+
);
|
|
5904
|
+
return 0;
|
|
5905
|
+
}
|
|
5906
|
+
terminal.output.write(
|
|
5907
|
+
statusMarkdown(workspace, wanted === void 0 ? tabs : [wanted])
|
|
5908
|
+
);
|
|
5909
|
+
return 0;
|
|
5910
|
+
} catch (e) {
|
|
5911
|
+
return reportError(e);
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5271
5915
|
// src/cli.ts
|
|
5272
5916
|
function buildProgram(state) {
|
|
5273
5917
|
const program = new Command().name("rness").description("Configuration-plane CLI for rness workspaces").version(VERSION, "-v, --version", "print the version").exitOverride().showHelpAfterError().configureOutput({
|
|
@@ -5295,6 +5939,11 @@ function buildProgram(state) {
|
|
|
5295
5939
|
).option("--json", "print JSON instead of Markdown").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
5296
5940
|
state.code = await contextCommand(opts);
|
|
5297
5941
|
});
|
|
5942
|
+
program.command("status").description(
|
|
5943
|
+
"Show the status of every decision, specification and plan, a tab per directory"
|
|
5944
|
+
).argument("[tab]", "open on this tab: adr, specs, plans, \u2026").addOption(new Option("--cwd <dir>").hideHelp()).action(async (tab, opts) => {
|
|
5945
|
+
state.code = await statusCommand(tab, opts);
|
|
5946
|
+
});
|
|
5298
5947
|
program.command("validate").description(
|
|
5299
5948
|
"Check the .rness/ tree against the contract and every generated block"
|
|
5300
5949
|
).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
@@ -5337,6 +5986,9 @@ function buildProgram(state) {
|
|
|
5337
5986
|
program.command("git-credential", { hidden: true }).argument("<operation>").action(async (operation) => {
|
|
5338
5987
|
state.code = await gitCredentialCommand(operation);
|
|
5339
5988
|
});
|
|
5989
|
+
program.command("hook", { hidden: true }).argument("<event>", "session-start or post-tool-use").action(async (event) => {
|
|
5990
|
+
state.code = await hookCommand(event);
|
|
5991
|
+
});
|
|
5340
5992
|
program.command("upgrade").alias("update").description(
|
|
5341
5993
|
"Move this workspace to another @rness/cli: pin it in .rness/package.json, install, sync"
|
|
5342
5994
|
).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 +6066,8 @@ var OWN_COMMANDS = [
|
|
|
5414
6066
|
"update",
|
|
5415
6067
|
"login",
|
|
5416
6068
|
"logout",
|
|
5417
|
-
"git-credential"
|
|
6069
|
+
"git-credential",
|
|
6070
|
+
"hook"
|
|
5418
6071
|
];
|
|
5419
6072
|
var CATCH_UP_DONE = "RNESS_CATCH_UP_DONE";
|
|
5420
6073
|
async function catchUp(argv, deps = {}) {
|
|
@@ -5509,14 +6162,14 @@ export {
|
|
|
5509
6162
|
copyScaffold,
|
|
5510
6163
|
loginCommand,
|
|
5511
6164
|
createCommand,
|
|
5512
|
-
|
|
6165
|
+
checkBlocks,
|
|
5513
6166
|
STATUSES,
|
|
5514
6167
|
checkContract,
|
|
6168
|
+
logoutCommand,
|
|
5515
6169
|
upgradeCommand,
|
|
5516
|
-
checkBlocks,
|
|
5517
6170
|
validateCommand,
|
|
5518
6171
|
run2 as run,
|
|
5519
6172
|
OWN_COMMANDS,
|
|
5520
6173
|
catchUp
|
|
5521
6174
|
};
|
|
5522
|
-
//# sourceMappingURL=chunk-
|
|
6175
|
+
//# sourceMappingURL=chunk-LW4S5OLI.js.map
|