@rness/cli 0.10.0 → 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 +42 -1
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-R4BRAAC2.js → chunk-LW4S5OLI.js} +478 -65
- package/dist/chunk-LW4S5OLI.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-R4BRAAC2.js.map +0 -1
|
@@ -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
|
"",
|
|
@@ -1405,7 +1411,7 @@ function reportError(e) {
|
|
|
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
|
}
|
|
@@ -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
|
});
|
|
@@ -1746,6 +1752,48 @@ function hooks(rness) {
|
|
|
1746
1752
|
}
|
|
1747
1753
|
];
|
|
1748
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
|
+
}
|
|
1749
1797
|
var TARGETS = {
|
|
1750
1798
|
claude: {
|
|
1751
1799
|
name: "claude",
|
|
@@ -1786,13 +1834,15 @@ var TARGETS = {
|
|
|
1786
1834
|
}
|
|
1787
1835
|
]
|
|
1788
1836
|
},
|
|
1837
|
+
...plugin("../../.rness", "clones"),
|
|
1789
1838
|
// A session opened at the workspace root reads this file, one opened
|
|
1790
1839
|
// in a clone does not (spec 0011 §1): the hooks only, `.rness` inside.
|
|
1791
1840
|
{
|
|
1792
1841
|
file: ".claude/settings.json",
|
|
1793
1842
|
at: "root",
|
|
1794
1843
|
guarantees: hooks(".rness")
|
|
1795
|
-
}
|
|
1844
|
+
},
|
|
1845
|
+
...plugin(".rness", "root")
|
|
1796
1846
|
]
|
|
1797
1847
|
}
|
|
1798
1848
|
};
|
|
@@ -1907,6 +1957,10 @@ function describeGuarantee(g) {
|
|
|
1907
1957
|
}
|
|
1908
1958
|
|
|
1909
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
|
+
}
|
|
1910
1964
|
async function filesOf(root, manifest, targets) {
|
|
1911
1965
|
const files = [];
|
|
1912
1966
|
if (!await exists(join9(root, "org"))) return files;
|
|
@@ -1914,7 +1968,7 @@ async function filesOf(root, manifest, targets) {
|
|
|
1914
1968
|
for (const owned of target.files)
|
|
1915
1969
|
if (owned.at === "root")
|
|
1916
1970
|
files.push({
|
|
1917
|
-
|
|
1971
|
+
...owned,
|
|
1918
1972
|
file: join9(root, ...owned.file.split("/")),
|
|
1919
1973
|
label: owned.file
|
|
1920
1974
|
});
|
|
@@ -1924,7 +1978,7 @@ async function filesOf(root, manifest, targets) {
|
|
|
1924
1978
|
for (const owned of target.files)
|
|
1925
1979
|
if (owned.at === "clones")
|
|
1926
1980
|
files.push({
|
|
1927
|
-
|
|
1981
|
+
...owned,
|
|
1928
1982
|
file: join9(root, "org", repo, ...owned.file.split("/")),
|
|
1929
1983
|
label: `org/${repo}/${owned.file}`
|
|
1930
1984
|
});
|
|
@@ -1947,12 +2001,23 @@ function writtenFiles(manifest) {
|
|
|
1947
2001
|
}
|
|
1948
2002
|
async function agentTargets(root, manifest, opts) {
|
|
1949
2003
|
const outcomes = [];
|
|
1950
|
-
for (const
|
|
1951
|
-
|
|
1952
|
-
manifest,
|
|
1953
|
-
declared(manifest)
|
|
1954
|
-
)) {
|
|
2004
|
+
for (const owned of await filesOf(root, manifest, declared(manifest))) {
|
|
2005
|
+
const { file, label } = owned;
|
|
1955
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;
|
|
1956
2021
|
if (opts.check) {
|
|
1957
2022
|
const missing = missingGuarantees(existing, guarantees);
|
|
1958
2023
|
if ("invalid" in missing)
|
|
@@ -1988,15 +2053,16 @@ async function leftoverTargets(root, manifest) {
|
|
|
1988
2053
|
(t) => !agents.includes(t.name)
|
|
1989
2054
|
);
|
|
1990
2055
|
const leftovers = [];
|
|
1991
|
-
for (const
|
|
1992
|
-
|
|
1993
|
-
manifest,
|
|
1994
|
-
undeclared
|
|
1995
|
-
)) {
|
|
1996
|
-
const existing = await readOrNull(file);
|
|
2056
|
+
for (const owned of await filesOf(root, manifest, undeclared)) {
|
|
2057
|
+
const existing = await readOrNull(owned.file);
|
|
1997
2058
|
if (existing === null) continue;
|
|
1998
|
-
|
|
1999
|
-
|
|
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);
|
|
2000
2066
|
}
|
|
2001
2067
|
return leftovers;
|
|
2002
2068
|
}
|
|
@@ -2106,8 +2172,8 @@ var plainUi = {
|
|
|
2106
2172
|
{ label: options.doing, animate: options.git !== true },
|
|
2107
2173
|
work
|
|
2108
2174
|
);
|
|
2109
|
-
const
|
|
2110
|
-
if (
|
|
2175
|
+
const line3 = options.quiet === true ? null : done?.(result) ?? null;
|
|
2176
|
+
if (line3 !== null) this.line(line3[0], line3[1]);
|
|
2111
2177
|
return result;
|
|
2112
2178
|
}
|
|
2113
2179
|
};
|
|
@@ -2140,7 +2206,7 @@ function sessionUi(p) {
|
|
|
2140
2206
|
p.log.warn(text);
|
|
2141
2207
|
},
|
|
2142
2208
|
note: (title, lines) => {
|
|
2143
|
-
p.note(lines.join("\n"), title, { format: (
|
|
2209
|
+
p.note(lines.join("\n"), title, { format: (line3) => grey(line3) });
|
|
2144
2210
|
},
|
|
2145
2211
|
hint: (text) => {
|
|
2146
2212
|
p.log.message(paint("idle", text));
|
|
@@ -2154,20 +2220,20 @@ function sessionUi(p) {
|
|
|
2154
2220
|
async step(options, work, done) {
|
|
2155
2221
|
const label = options.sentence ?? tidy(options.doing);
|
|
2156
2222
|
const finish2 = (result) => done?.(result) ?? null;
|
|
2157
|
-
const label_ = (
|
|
2223
|
+
const label_ = (line3) => line3[2] ?? `${line3[1]} ${line3[0]}`;
|
|
2158
2224
|
if (options.git === true && !this.gitIsSilent) {
|
|
2159
2225
|
p.log.message(paint("idle", `${label}\u2026`));
|
|
2160
2226
|
const result = await work();
|
|
2161
|
-
const
|
|
2162
|
-
if (
|
|
2227
|
+
const line3 = finish2(result);
|
|
2228
|
+
if (line3 !== null) say(line3[0], label_(line3));
|
|
2163
2229
|
return result;
|
|
2164
2230
|
}
|
|
2165
2231
|
const spin = p.spinner();
|
|
2166
2232
|
spin.start(label);
|
|
2167
2233
|
try {
|
|
2168
2234
|
const result = await work();
|
|
2169
|
-
const
|
|
2170
|
-
spin.stop(
|
|
2235
|
+
const line3 = finish2(result);
|
|
2236
|
+
spin.stop(line3 === null ? label : label_(line3));
|
|
2171
2237
|
return result;
|
|
2172
2238
|
} catch (e) {
|
|
2173
2239
|
spin.error(`${label} \u2014 failed`);
|
|
@@ -3185,11 +3251,11 @@ function summariseSync(stdout) {
|
|
|
3185
3251
|
const blocks = /* @__PURE__ */ new Map();
|
|
3186
3252
|
const agentFiles = /* @__PURE__ */ new Map();
|
|
3187
3253
|
const others = [];
|
|
3188
|
-
for (const
|
|
3189
|
-
if (
|
|
3190
|
-
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);
|
|
3191
3257
|
if (m === null) {
|
|
3192
|
-
others.push(
|
|
3258
|
+
others.push(line3);
|
|
3193
3259
|
continue;
|
|
3194
3260
|
}
|
|
3195
3261
|
const [, verb = "", path = ""] = m;
|
|
@@ -3419,8 +3485,8 @@ async function mergeScaffold(rnessDir, commit, base) {
|
|
|
3419
3485
|
"--name-status",
|
|
3420
3486
|
"HEAD"
|
|
3421
3487
|
]);
|
|
3422
|
-
for (const
|
|
3423
|
-
const [letter = "", path = ""] =
|
|
3488
|
+
for (const line3 of status2.split("\n")) {
|
|
3489
|
+
const [letter = "", path = ""] = line3.split(" ");
|
|
3424
3490
|
if (path === "" || conflicts.includes(path)) continue;
|
|
3425
3491
|
if (letter === "A") changes.push(["added", path]);
|
|
3426
3492
|
else if (letter === "D") changes.push(["removed", path]);
|
|
@@ -3566,8 +3632,8 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
3566
3632
|
return 1;
|
|
3567
3633
|
}
|
|
3568
3634
|
const outcome = syncOutcome(result, { sshWorks: sshWorks === true });
|
|
3569
|
-
for (const
|
|
3570
|
-
for (const
|
|
3635
|
+
for (const line3 of outcome.hints) ui.hint(line3);
|
|
3636
|
+
for (const line3 of outcome.errors) ui.error(line3);
|
|
3571
3637
|
return outcome.code;
|
|
3572
3638
|
}
|
|
3573
3639
|
|
|
@@ -3672,7 +3738,7 @@ async function createBlank(input) {
|
|
|
3672
3738
|
`Workspace \`${name}\` is ready in ${shown}/.`,
|
|
3673
3739
|
"",
|
|
3674
3740
|
"Next:",
|
|
3675
|
-
...next.map((
|
|
3741
|
+
...next.map((line3) => ` ${line3}`),
|
|
3676
3742
|
""
|
|
3677
3743
|
].join("\n")
|
|
3678
3744
|
);
|
|
@@ -4315,7 +4381,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
4315
4381
|
`Workspace for \`${org}\` is ready in ${shown}/.`,
|
|
4316
4382
|
"",
|
|
4317
4383
|
"Next:",
|
|
4318
|
-
...next.map((
|
|
4384
|
+
...next.map((line3) => ` ${line3}`),
|
|
4319
4385
|
""
|
|
4320
4386
|
].join("\n")
|
|
4321
4387
|
);
|
|
@@ -4592,7 +4658,7 @@ function finish(ui, cwd, rnessDir, label, target, ending) {
|
|
|
4592
4658
|
"",
|
|
4593
4659
|
status("upgraded", `${label} to @rness/cli ${target}`),
|
|
4594
4660
|
"",
|
|
4595
|
-
...next.length > 0 ? ["Next:", ...next.map((
|
|
4661
|
+
...next.length > 0 ? ["Next:", ...next.map((line3) => ` ${line3}`), ""] : []
|
|
4596
4662
|
].join("\n")
|
|
4597
4663
|
);
|
|
4598
4664
|
return ending.commit === "refused" ? 1 : 0;
|
|
@@ -4813,8 +4879,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
4813
4879
|
ui.error(
|
|
4814
4880
|
`the commit of ${label} was refused; everything is staged \u2014 deal with what git said, then commit`
|
|
4815
4881
|
);
|
|
4816
|
-
for (const
|
|
4817
|
-
if (
|
|
4882
|
+
for (const line3 of committed.output.split("\n"))
|
|
4883
|
+
if (line3.trim() !== "") ui.hint(line3, "stderr");
|
|
4818
4884
|
}
|
|
4819
4885
|
return finish(ui, cwd, ws.rnessDir, label, target, {
|
|
4820
4886
|
commit: committed.kind,
|
|
@@ -4923,9 +4989,9 @@ async function readStdin() {
|
|
|
4923
4989
|
async function gitCredentialCommand(operation, input) {
|
|
4924
4990
|
if (operation !== "get") return 0;
|
|
4925
4991
|
const fields = /* @__PURE__ */ new Map();
|
|
4926
|
-
for (const
|
|
4927
|
-
const eq =
|
|
4928
|
-
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());
|
|
4929
4995
|
}
|
|
4930
4996
|
if (fields.get("protocol") !== "https" || fields.get("host") !== "github.com")
|
|
4931
4997
|
return 0;
|
|
@@ -4939,11 +5005,24 @@ password=${resolved.token}
|
|
|
4939
5005
|
|
|
4940
5006
|
// src/commands/hook.ts
|
|
4941
5007
|
import { realpath as realpath4 } from "fs/promises";
|
|
4942
|
-
import { isAbsolute as isAbsolute2, posix as
|
|
5008
|
+
import { isAbsolute as isAbsolute2, posix as posix6, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
4943
5009
|
|
|
4944
5010
|
// src/mcp/tools.ts
|
|
4945
5011
|
import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
4946
|
-
import { isAbsolute, join as join23, posix as
|
|
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;
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
// src/mcp/tools.ts
|
|
4947
5026
|
var LABELS = {
|
|
4948
5027
|
standards: "Standards",
|
|
4949
5028
|
adr: "Decisions",
|
|
@@ -4968,11 +5047,6 @@ async function workspaceAt(cwd) {
|
|
|
4968
5047
|
const ws = await findWorkspace(cwd);
|
|
4969
5048
|
return { ws, manifest: await loadManifest(ws.rnessDir) };
|
|
4970
5049
|
}
|
|
4971
|
-
function titleOf(heading, id) {
|
|
4972
|
-
if (id === null) return heading;
|
|
4973
|
-
const rest = new RegExp(`^${id}\\s*[\u2014\u2013:-]\\s*(.+)$`).exec(heading)?.[1];
|
|
4974
|
-
return rest ?? heading;
|
|
4975
|
-
}
|
|
4976
5050
|
async function documents(rnessDir, only) {
|
|
4977
5051
|
const docs = [];
|
|
4978
5052
|
for (const collection of COLLECTIONS) {
|
|
@@ -4981,14 +5055,14 @@ async function documents(rnessDir, only) {
|
|
|
4981
5055
|
const path = `${collection}/${item.rel}`;
|
|
4982
5056
|
if (path === TEMPLATE) continue;
|
|
4983
5057
|
const status2 = item.fields?.["status"];
|
|
4984
|
-
const id =
|
|
5058
|
+
const id = documentNumber(item.rel);
|
|
4985
5059
|
docs.push({
|
|
4986
5060
|
collection,
|
|
4987
5061
|
path,
|
|
4988
5062
|
scope: ownerScope(item.rel),
|
|
4989
5063
|
id,
|
|
4990
5064
|
status: typeof status2 === "string" ? status2 : null,
|
|
4991
|
-
title:
|
|
5065
|
+
title: documentTitle(item.title ?? posix5.basename(item.rel, ".md"), id),
|
|
4992
5066
|
text: item.body
|
|
4993
5067
|
});
|
|
4994
5068
|
}
|
|
@@ -5018,7 +5092,7 @@ async function context(cwd, args) {
|
|
|
5018
5092
|
} else
|
|
5019
5093
|
scope = resolveScope(
|
|
5020
5094
|
manifest,
|
|
5021
|
-
relative6(ws.root, cwd).split(sep4).join(
|
|
5095
|
+
relative6(ws.root, cwd).split(sep4).join(posix5.sep)
|
|
5022
5096
|
);
|
|
5023
5097
|
const summary = await scopeSummary(ws, manifest, scope);
|
|
5024
5098
|
return {
|
|
@@ -5231,7 +5305,7 @@ function cwdOf(input, env) {
|
|
|
5231
5305
|
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
5232
5306
|
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
5233
5307
|
}
|
|
5234
|
-
var toPosix2 = (p) => p.split(sep5).join(
|
|
5308
|
+
var toPosix2 = (p) => p.split(sep5).join(posix6.sep);
|
|
5235
5309
|
function count(n, [one, many]) {
|
|
5236
5310
|
return `${n} ${n === 1 ? one : many}`;
|
|
5237
5311
|
}
|
|
@@ -5383,10 +5457,10 @@ function errorLine(id, code, message2, data) {
|
|
|
5383
5457
|
function createProtocol(opts) {
|
|
5384
5458
|
const serverInfo = { name: opts.name, version: opts.version };
|
|
5385
5459
|
return {
|
|
5386
|
-
async handle(
|
|
5460
|
+
async handle(line3) {
|
|
5387
5461
|
let message2;
|
|
5388
5462
|
try {
|
|
5389
|
-
message2 = JSON.parse(
|
|
5463
|
+
message2 = JSON.parse(line3);
|
|
5390
5464
|
} catch {
|
|
5391
5465
|
return errorLine(null, -32700, "Parse error");
|
|
5392
5466
|
}
|
|
@@ -5495,15 +5569,349 @@ async function mcpCommand(opts, io = {
|
|
|
5495
5569
|
tools: mcpTools(opts.cwd ?? process.cwd())
|
|
5496
5570
|
});
|
|
5497
5571
|
const lines = createInterface({ input: io.input, crlfDelay: Infinity });
|
|
5498
|
-
for await (const
|
|
5499
|
-
if (
|
|
5500
|
-
const answer = await protocol.handle(
|
|
5572
|
+
for await (const line3 of lines) {
|
|
5573
|
+
if (line3.trim() === "") continue;
|
|
5574
|
+
const answer = await protocol.handle(line3);
|
|
5501
5575
|
if (answer !== null) io.output.write(`${answer}
|
|
5502
5576
|
`);
|
|
5503
5577
|
}
|
|
5504
5578
|
return 0;
|
|
5505
5579
|
}
|
|
5506
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
|
+
|
|
5507
5915
|
// src/cli.ts
|
|
5508
5916
|
function buildProgram(state) {
|
|
5509
5917
|
const program = new Command().name("rness").description("Configuration-plane CLI for rness workspaces").version(VERSION, "-v, --version", "print the version").exitOverride().showHelpAfterError().configureOutput({
|
|
@@ -5531,6 +5939,11 @@ function buildProgram(state) {
|
|
|
5531
5939
|
).option("--json", "print JSON instead of Markdown").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
5532
5940
|
state.code = await contextCommand(opts);
|
|
5533
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
|
+
});
|
|
5534
5947
|
program.command("validate").description(
|
|
5535
5948
|
"Check the .rness/ tree against the contract and every generated block"
|
|
5536
5949
|
).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
@@ -5759,4 +6172,4 @@ export {
|
|
|
5759
6172
|
OWN_COMMANDS,
|
|
5760
6173
|
catchUp
|
|
5761
6174
|
};
|
|
5762
|
-
//# sourceMappingURL=chunk-
|
|
6175
|
+
//# sourceMappingURL=chunk-LW4S5OLI.js.map
|