@rness/cli 0.6.2 → 0.7.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 +45 -3
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-7TOHFPW6.js → chunk-VYP4L7BQ.js} +442 -87
- package/dist/chunk-VYP4L7BQ.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-7TOHFPW6.js.map +0 -1
|
@@ -735,6 +735,9 @@ async function isClean(dir, ignore = []) {
|
|
|
735
735
|
[
|
|
736
736
|
"status",
|
|
737
737
|
"--porcelain",
|
|
738
|
+
// Every untracked file on its own line: a directory folded into one
|
|
739
|
+
// `?? dir/` entry would slip past a pathspec naming a file inside it.
|
|
740
|
+
"--untracked-files=all",
|
|
738
741
|
...pathspec.length > 0 ? ["--", ...pathspec] : []
|
|
739
742
|
],
|
|
740
743
|
dir
|
|
@@ -857,7 +860,7 @@ function isWorkspaceClone(name) {
|
|
|
857
860
|
}
|
|
858
861
|
var NAME_RULE = "lowercase letters, digits, '.', '_' and '-', not starting with '-'";
|
|
859
862
|
var ORG_NAME = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
|
|
860
|
-
var KEYS = ["contract", "org", "repos", "scopes"];
|
|
863
|
+
var KEYS = ["contract", "org", "agents", "repos", "scopes"];
|
|
861
864
|
function fail(message) {
|
|
862
865
|
throw new Error(`rness.json: ${message}`);
|
|
863
866
|
}
|
|
@@ -886,6 +889,15 @@ function readOrg(value) {
|
|
|
886
889
|
function workspaceName(manifest, root) {
|
|
887
890
|
return manifest.org ?? basename2(root);
|
|
888
891
|
}
|
|
892
|
+
var AGENT_NAME = /^[a-z][a-z0-9-]*$/;
|
|
893
|
+
function readAgents(value) {
|
|
894
|
+
if (value === void 0) return null;
|
|
895
|
+
if (!Array.isArray(value) || !value.every((v) => typeof v === "string" && AGENT_NAME.test(v)) || new Set(value).size !== value.length)
|
|
896
|
+
fail(
|
|
897
|
+
`"agents" must be a list of unique agent names (got ${JSON.stringify(value)})`
|
|
898
|
+
);
|
|
899
|
+
return [...value];
|
|
900
|
+
}
|
|
889
901
|
function readRepos(value) {
|
|
890
902
|
const raw = value ?? {};
|
|
891
903
|
if (!isRecord(raw)) fail('"repos" must be an object');
|
|
@@ -944,6 +956,7 @@ async function loadManifest(rnessDir) {
|
|
|
944
956
|
return {
|
|
945
957
|
contract: 1,
|
|
946
958
|
org: readOrg(data.org),
|
|
959
|
+
agents: readAgents(data.agents),
|
|
947
960
|
repos: readRepos(data.repos),
|
|
948
961
|
scopes: readScopes(data.scopes)
|
|
949
962
|
};
|
|
@@ -959,6 +972,10 @@ async function writeManifest(rnessDir, manifest) {
|
|
|
959
972
|
const lines = ["{", ' "contract": 1,'];
|
|
960
973
|
if (manifest.org !== null)
|
|
961
974
|
lines.push(` "org": ${JSON.stringify(manifest.org)},`);
|
|
975
|
+
if (manifest.agents !== null)
|
|
976
|
+
lines.push(
|
|
977
|
+
` "agents": [${manifest.agents.map((a) => JSON.stringify(a)).join(", ")}],`
|
|
978
|
+
);
|
|
962
979
|
lines.push(
|
|
963
980
|
' "repos": {',
|
|
964
981
|
repos.join(",\n"),
|
|
@@ -1045,13 +1062,13 @@ async function addRepository(input) {
|
|
|
1045
1062
|
await clone(url, dir, input.credentialsFor?.(url) ?? null);
|
|
1046
1063
|
action = "cloned";
|
|
1047
1064
|
}
|
|
1048
|
-
const
|
|
1065
|
+
const declared2 = [name];
|
|
1049
1066
|
for (const { path, base } of subs) {
|
|
1050
1067
|
if (!await exists(join4(input.root, ...path.split("/")))) {
|
|
1051
1068
|
if (action === "cloned") await rm3(dir, { recursive: true, force: true });
|
|
1052
1069
|
throw new Error(`${path} does not exist`);
|
|
1053
1070
|
}
|
|
1054
|
-
|
|
1071
|
+
declared2.push(base);
|
|
1055
1072
|
}
|
|
1056
1073
|
const manifest = {
|
|
1057
1074
|
...input.manifest,
|
|
@@ -1059,7 +1076,7 @@ async function addRepository(input) {
|
|
|
1059
1076
|
scopes
|
|
1060
1077
|
};
|
|
1061
1078
|
await writeManifest(input.rnessDir, manifest);
|
|
1062
|
-
return { manifest, name, url, action, declared };
|
|
1079
|
+
return { manifest, name, url, action, declared: declared2 };
|
|
1063
1080
|
}
|
|
1064
1081
|
async function workspaceDirs(repoDir) {
|
|
1065
1082
|
let patterns = [];
|
|
@@ -1372,6 +1389,9 @@ function reportError(e) {
|
|
|
1372
1389
|
return 1;
|
|
1373
1390
|
}
|
|
1374
1391
|
|
|
1392
|
+
// src/version.ts
|
|
1393
|
+
var VERSION = "0.7.0";
|
|
1394
|
+
|
|
1375
1395
|
// src/core/block.ts
|
|
1376
1396
|
import { createHash } from "crypto";
|
|
1377
1397
|
var BEGIN = "<!-- BEGIN rness -->";
|
|
@@ -1664,7 +1684,214 @@ async function ensureClaudeMd(dir) {
|
|
|
1664
1684
|
}
|
|
1665
1685
|
|
|
1666
1686
|
// src/commands/sync.ts
|
|
1667
|
-
import { join as
|
|
1687
|
+
import { join as join10 } from "path";
|
|
1688
|
+
|
|
1689
|
+
// src/core/agent-targets.ts
|
|
1690
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
1691
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
1692
|
+
|
|
1693
|
+
// src/core/agents.ts
|
|
1694
|
+
var TARGETS = {
|
|
1695
|
+
claude: {
|
|
1696
|
+
name: "claude",
|
|
1697
|
+
label: "Claude Code",
|
|
1698
|
+
file: ".claude/settings.json",
|
|
1699
|
+
// Read the context repository from a repository without a prompt. The
|
|
1700
|
+
// path is resolved against the project directory, and only once Claude
|
|
1701
|
+
// Code has trusted it (verified on 2.1.284, spec 0011 §7).
|
|
1702
|
+
guarantees: [
|
|
1703
|
+
{
|
|
1704
|
+
path: ["permissions", "additionalDirectories"],
|
|
1705
|
+
contains: "../../.rness"
|
|
1706
|
+
}
|
|
1707
|
+
]
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
var SUPPORTED_AGENTS = Object.keys(TARGETS);
|
|
1711
|
+
function unsupportedAgents(names) {
|
|
1712
|
+
return names.filter((n) => !Object.hasOwn(TARGETS, n));
|
|
1713
|
+
}
|
|
1714
|
+
function unsupportedMessage(name) {
|
|
1715
|
+
return `rness.json: agent "${name}" is not supported by @rness/cli ${VERSION} (supported: ${SUPPORTED_AGENTS.join(", ")})`;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/core/json-guarantees.ts
|
|
1719
|
+
function isObject(value) {
|
|
1720
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1721
|
+
}
|
|
1722
|
+
function parse3(text) {
|
|
1723
|
+
let data;
|
|
1724
|
+
try {
|
|
1725
|
+
data = JSON.parse(text);
|
|
1726
|
+
} catch {
|
|
1727
|
+
return { ok: false, reason: "not valid JSON" };
|
|
1728
|
+
}
|
|
1729
|
+
return isObject(data) ? { ok: true, data } : { ok: false, reason: "not a JSON object" };
|
|
1730
|
+
}
|
|
1731
|
+
function listAt(root, g, create) {
|
|
1732
|
+
let node = root;
|
|
1733
|
+
for (const [i, key] of g.path.slice(0, -1).entries()) {
|
|
1734
|
+
const next = node[key];
|
|
1735
|
+
if (next === void 0) {
|
|
1736
|
+
if (!create) return { kind: "absent" };
|
|
1737
|
+
node[key] = {};
|
|
1738
|
+
} else if (!isObject(next))
|
|
1739
|
+
return {
|
|
1740
|
+
kind: "invalid",
|
|
1741
|
+
reason: `${g.path.slice(0, i + 1).join(".")} is not an object`
|
|
1742
|
+
};
|
|
1743
|
+
node = node[key];
|
|
1744
|
+
}
|
|
1745
|
+
const last = g.path[g.path.length - 1] ?? "";
|
|
1746
|
+
const value = node[last];
|
|
1747
|
+
if (value === void 0) {
|
|
1748
|
+
if (!create) return { kind: "absent" };
|
|
1749
|
+
const list = [];
|
|
1750
|
+
node[last] = list;
|
|
1751
|
+
return { kind: "list", list };
|
|
1752
|
+
}
|
|
1753
|
+
if (!Array.isArray(value))
|
|
1754
|
+
return { kind: "invalid", reason: `${g.path.join(".")} is not a list` };
|
|
1755
|
+
return { kind: "list", list: value };
|
|
1756
|
+
}
|
|
1757
|
+
function styleOf(text) {
|
|
1758
|
+
const indent = text.split(/\r?\n/).slice(1).map((l) => /^[ \t]+/.exec(l)?.[0]).find((w) => w !== void 0);
|
|
1759
|
+
return {
|
|
1760
|
+
indent: indent ?? " ",
|
|
1761
|
+
eol: text.includes("\r\n") ? "\r\n" : "\n",
|
|
1762
|
+
final: text.endsWith("\n")
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
function ensureGuarantees(existing, guarantees) {
|
|
1766
|
+
const parsed = existing === null ? { ok: true, data: {} } : parse3(existing);
|
|
1767
|
+
if (!parsed.ok) return { kind: "invalid", reason: parsed.reason };
|
|
1768
|
+
let changed = false;
|
|
1769
|
+
for (const g of guarantees) {
|
|
1770
|
+
const found = listAt(parsed.data, g, true);
|
|
1771
|
+
if (found.kind === "invalid")
|
|
1772
|
+
return { kind: "invalid", reason: found.reason };
|
|
1773
|
+
if (found.kind === "list" && !found.list.includes(g.contains)) {
|
|
1774
|
+
found.list.push(g.contains);
|
|
1775
|
+
changed = true;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (existing === null)
|
|
1779
|
+
return {
|
|
1780
|
+
kind: "created",
|
|
1781
|
+
text: `${JSON.stringify(parsed.data, null, 2)}
|
|
1782
|
+
`
|
|
1783
|
+
};
|
|
1784
|
+
if (!changed) return { kind: "unchanged", text: existing };
|
|
1785
|
+
const { indent, eol, final } = styleOf(existing);
|
|
1786
|
+
const body = JSON.stringify(parsed.data, null, indent).split("\n").join(eol);
|
|
1787
|
+
return { kind: "updated", text: final ? `${body}${eol}` : body };
|
|
1788
|
+
}
|
|
1789
|
+
function missingGuarantees(existing, guarantees) {
|
|
1790
|
+
if (existing === null) return [...guarantees];
|
|
1791
|
+
const parsed = parse3(existing);
|
|
1792
|
+
if (!parsed.ok) return { invalid: parsed.reason };
|
|
1793
|
+
const missing = [];
|
|
1794
|
+
for (const g of guarantees) {
|
|
1795
|
+
const found = listAt(parsed.data, g, false);
|
|
1796
|
+
if (found.kind === "invalid") return { invalid: found.reason };
|
|
1797
|
+
if (found.kind === "absent" || !found.list.includes(g.contains))
|
|
1798
|
+
missing.push(g);
|
|
1799
|
+
}
|
|
1800
|
+
return missing;
|
|
1801
|
+
}
|
|
1802
|
+
function describeGuarantee(g) {
|
|
1803
|
+
return `${g.path.join(".")} lacks ${g.contains}`;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/core/agent-targets.ts
|
|
1807
|
+
async function filesOf(root, manifest, targets) {
|
|
1808
|
+
const files = [];
|
|
1809
|
+
for (const repo of Object.keys(manifest.repos)) {
|
|
1810
|
+
if (!await exists(join9(root, "org", repo))) continue;
|
|
1811
|
+
for (const target of targets)
|
|
1812
|
+
files.push({
|
|
1813
|
+
target,
|
|
1814
|
+
file: join9(root, "org", repo, ...target.file.split("/")),
|
|
1815
|
+
label: `org/${repo}/${target.file}`
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
return files;
|
|
1819
|
+
}
|
|
1820
|
+
function declared(manifest) {
|
|
1821
|
+
return (manifest.agents ?? []).flatMap(
|
|
1822
|
+
(name) => Object.hasOwn(TARGETS, name) ? [TARGETS[name]] : []
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
async function agentTargets(root, manifest, opts) {
|
|
1826
|
+
const outcomes = [];
|
|
1827
|
+
for (const { target, file, label } of await filesOf(
|
|
1828
|
+
root,
|
|
1829
|
+
manifest,
|
|
1830
|
+
declared(manifest)
|
|
1831
|
+
)) {
|
|
1832
|
+
const existing = await readOrNull(file);
|
|
1833
|
+
if (opts.check) {
|
|
1834
|
+
const missing = missingGuarantees(existing, target.guarantees);
|
|
1835
|
+
if ("invalid" in missing)
|
|
1836
|
+
outcomes.push({ label, status: "invalid", detail: missing.invalid });
|
|
1837
|
+
else if (missing.length > 0)
|
|
1838
|
+
outcomes.push({
|
|
1839
|
+
label,
|
|
1840
|
+
status: "stale",
|
|
1841
|
+
detail: missing.map(describeGuarantee).join("; ")
|
|
1842
|
+
});
|
|
1843
|
+
else outcomes.push({ label, status: "unchanged", detail: null });
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
const ensured = ensureGuarantees(existing, target.guarantees);
|
|
1847
|
+
if (ensured.kind === "invalid") {
|
|
1848
|
+
outcomes.push({ label, status: "invalid", detail: ensured.reason });
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
if (ensured.kind === "unchanged") {
|
|
1852
|
+
outcomes.push({ label, status: "unchanged", detail: null });
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
await mkdir2(dirname4(file), { recursive: true });
|
|
1856
|
+
await writeFileAtomic(file, ensured.text);
|
|
1857
|
+
outcomes.push({ label, status: "updated", detail: null });
|
|
1858
|
+
}
|
|
1859
|
+
return outcomes;
|
|
1860
|
+
}
|
|
1861
|
+
async function leftoverTargets(root, manifest) {
|
|
1862
|
+
if (manifest.agents === null) return [];
|
|
1863
|
+
const agents = manifest.agents;
|
|
1864
|
+
const undeclared = Object.values(TARGETS).filter(
|
|
1865
|
+
(t) => !agents.includes(t.name)
|
|
1866
|
+
);
|
|
1867
|
+
const leftovers = [];
|
|
1868
|
+
for (const { target, file, label } of await filesOf(
|
|
1869
|
+
root,
|
|
1870
|
+
manifest,
|
|
1871
|
+
undeclared
|
|
1872
|
+
)) {
|
|
1873
|
+
const existing = await readOrNull(file);
|
|
1874
|
+
if (existing === null) continue;
|
|
1875
|
+
const missing = missingGuarantees(existing, target.guarantees);
|
|
1876
|
+
if (!("invalid" in missing) && missing.length === 0) leftovers.push(label);
|
|
1877
|
+
}
|
|
1878
|
+
return leftovers;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/core/ask-agents.ts
|
|
1882
|
+
var AGENTS_QUESTION = "Which agents does your team use?";
|
|
1883
|
+
async function askAgents(p) {
|
|
1884
|
+
const answer = await p.multiselect({
|
|
1885
|
+
message: AGENTS_QUESTION,
|
|
1886
|
+
options: Object.values(TARGETS).map((t) => ({
|
|
1887
|
+
value: t.name,
|
|
1888
|
+
label: t.label
|
|
1889
|
+
})),
|
|
1890
|
+
required: false
|
|
1891
|
+
});
|
|
1892
|
+
if (p.isCancel(answer)) return null;
|
|
1893
|
+
return Array.isArray(answer) ? answer : [];
|
|
1894
|
+
}
|
|
1668
1895
|
|
|
1669
1896
|
// src/core/progress.ts
|
|
1670
1897
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -1853,7 +2080,7 @@ function targetsOf(manifest, root, only) {
|
|
|
1853
2080
|
const segments = entry.path.split("/");
|
|
1854
2081
|
targets.push({
|
|
1855
2082
|
scope: name,
|
|
1856
|
-
dir:
|
|
2083
|
+
dir: join10(root, ...segments),
|
|
1857
2084
|
depth: segments.length,
|
|
1858
2085
|
label: `${entry.path}/AGENTS.md`
|
|
1859
2086
|
});
|
|
@@ -1917,16 +2144,60 @@ async function syncCommand(opts, deps = {}) {
|
|
|
1917
2144
|
const check = opts.check === true;
|
|
1918
2145
|
const ownUi = deps.ui === void 0;
|
|
1919
2146
|
const ui = deps.ui ?? (opts.yes !== true && !check ? await makeUi(terminal) : { ...plainUi });
|
|
2147
|
+
const agentFlags = opts.agent ?? [];
|
|
2148
|
+
if (agentFlags.length > 0 && check) {
|
|
2149
|
+
process.stderr.write(
|
|
2150
|
+
"--agent cannot be combined with --check: it declares agents in rness.json\n"
|
|
2151
|
+
);
|
|
2152
|
+
return 2;
|
|
2153
|
+
}
|
|
2154
|
+
const unknown = unsupportedAgents(agentFlags)[0];
|
|
2155
|
+
if (unknown !== void 0) {
|
|
2156
|
+
process.stderr.write(
|
|
2157
|
+
`unknown agent "${unknown}" (supported: ${SUPPORTED_AGENTS.join(", ")})
|
|
2158
|
+
`
|
|
2159
|
+
);
|
|
2160
|
+
return 2;
|
|
2161
|
+
}
|
|
1920
2162
|
try {
|
|
1921
2163
|
const ws = await findWorkspace(cwd);
|
|
1922
2164
|
const manifest = await loadManifest(ws.rnessDir);
|
|
1923
2165
|
const org = workspaceName(manifest, ws.root);
|
|
2166
|
+
const unsupported = unsupportedAgents(manifest.agents ?? [])[0];
|
|
2167
|
+
if (unsupported !== void 0) {
|
|
2168
|
+
process.stderr.write(`${unsupportedMessage(unsupported)}
|
|
2169
|
+
`);
|
|
2170
|
+
return 1;
|
|
2171
|
+
}
|
|
1924
2172
|
if (ownUi) ui.intro(`Sync workspace ${org}`);
|
|
1925
2173
|
if (opts.scope !== void 0 && !Object.hasOwn(manifest.scopes, opts.scope)) {
|
|
1926
2174
|
process.stderr.write(`unknown scope: ${opts.scope}
|
|
1927
2175
|
`);
|
|
1928
2176
|
return 1;
|
|
1929
2177
|
}
|
|
2178
|
+
let declare = null;
|
|
2179
|
+
if (agentFlags.length > 0) {
|
|
2180
|
+
const current = manifest.agents ?? [];
|
|
2181
|
+
const added = agentFlags.filter((a) => !current.includes(a));
|
|
2182
|
+
if (added.length > 0) declare = [...current, ...new Set(added)];
|
|
2183
|
+
} else if (manifest.agents === null && !check && opts.yes !== true && opts.scope === void 0 && terminal.isTty()) {
|
|
2184
|
+
const answer = await askAgents(await terminal.prompts());
|
|
2185
|
+
if (answer === null) {
|
|
2186
|
+
ui.cancelled();
|
|
2187
|
+
return 1;
|
|
2188
|
+
}
|
|
2189
|
+
declare = answer;
|
|
2190
|
+
}
|
|
2191
|
+
if (declare !== null) {
|
|
2192
|
+
const before = manifest.agents ?? [];
|
|
2193
|
+
manifest.agents = declare;
|
|
2194
|
+
await writeManifest(ws.rnessDir, manifest);
|
|
2195
|
+
for (const name of declare.filter((a) => !before.includes(a)))
|
|
2196
|
+
ui.line("declared", `agent ${name} in .rness/rness.json`);
|
|
2197
|
+
if (declare.length === 0)
|
|
2198
|
+
ui.line("declared", "no agent in .rness/rness.json");
|
|
2199
|
+
ui.hint("rness.json changed: commit it in .rness, for the whole team");
|
|
2200
|
+
}
|
|
1930
2201
|
const clones = await clonesIn(ws.root);
|
|
1931
2202
|
const missing = Object.keys(manifest.repos).filter((n) => !clones.has(n));
|
|
1932
2203
|
let toClone = new Set(opts.all === true && !check ? missing : []);
|
|
@@ -1968,7 +2239,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
1968
2239
|
try {
|
|
1969
2240
|
const notCloned = /* @__PURE__ */ new Set();
|
|
1970
2241
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
1971
|
-
const dir =
|
|
2242
|
+
const dir = join10(ws.root, "org", name);
|
|
1972
2243
|
const label = `org/${name}`;
|
|
1973
2244
|
if (!await exists(dir)) {
|
|
1974
2245
|
if (!toClone.has(name)) {
|
|
@@ -1989,7 +2260,13 @@ async function syncCommand(opts, deps = {}) {
|
|
|
1989
2260
|
}
|
|
1990
2261
|
} else if (opts.pull === true && !check) {
|
|
1991
2262
|
try {
|
|
1992
|
-
|
|
2263
|
+
const written = [
|
|
2264
|
+
...MANAGED_FILES,
|
|
2265
|
+
...(manifest.agents ?? []).flatMap(
|
|
2266
|
+
(a) => Object.hasOwn(TARGETS, a) ? [TARGETS[a].file] : []
|
|
2267
|
+
)
|
|
2268
|
+
];
|
|
2269
|
+
if (!await isClean(dir, written)) {
|
|
1993
2270
|
lines.push(["skipped", `${label} (working tree not clean)`]);
|
|
1994
2271
|
continue;
|
|
1995
2272
|
}
|
|
@@ -2020,7 +2297,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2020
2297
|
lines.push(
|
|
2021
2298
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
2022
2299
|
);
|
|
2023
|
-
if (!await exists(
|
|
2300
|
+
if (!await exists(join10(ws.root, "org"))) {
|
|
2024
2301
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
2025
2302
|
} else {
|
|
2026
2303
|
let differences = 0;
|
|
@@ -2032,7 +2309,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2032
2309
|
continue;
|
|
2033
2310
|
}
|
|
2034
2311
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
2035
|
-
MANAGED_FILES.map((f) => isSymlink(
|
|
2312
|
+
MANAGED_FILES.map((f) => isSymlink(join10(t.dir, f)))
|
|
2036
2313
|
);
|
|
2037
2314
|
const atRoot = t.scope === null;
|
|
2038
2315
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -2055,7 +2332,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2055
2332
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
2056
2333
|
);
|
|
2057
2334
|
}
|
|
2058
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
2335
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join10(t.dir, "AGENTS.md"), block, check);
|
|
2059
2336
|
if (typeof agents === "object") {
|
|
2060
2337
|
problems.push(`${t.label}: ${agents.error}`);
|
|
2061
2338
|
continue;
|
|
@@ -2070,7 +2347,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2070
2347
|
}
|
|
2071
2348
|
if (claudeLinked === true) continue;
|
|
2072
2349
|
const claude = await syncBlockFile(
|
|
2073
|
-
|
|
2350
|
+
join10(t.dir, "CLAUDE.md"),
|
|
2074
2351
|
block,
|
|
2075
2352
|
check,
|
|
2076
2353
|
true
|
|
@@ -2086,6 +2363,28 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2086
2363
|
problems.push(
|
|
2087
2364
|
`${differences} block(s) out of date \u2014 run ${rnessCommand()} sync`
|
|
2088
2365
|
);
|
|
2366
|
+
if (opts.scope === void 0) {
|
|
2367
|
+
let stale = 0;
|
|
2368
|
+
for (const o of await agentTargets(ws.root, manifest, { check })) {
|
|
2369
|
+
if (o.status === "invalid")
|
|
2370
|
+
problems.push(
|
|
2371
|
+
`${o.label}: ${o.detail ?? "unreadable"}; fix it, then ${rnessCommand()} sync`
|
|
2372
|
+
);
|
|
2373
|
+
else if (o.status === "stale") {
|
|
2374
|
+
stale += 1;
|
|
2375
|
+
lines.push(["stale", `${o.label} (${o.detail ?? ""})`]);
|
|
2376
|
+
} else lines.push([o.status, o.label]);
|
|
2377
|
+
}
|
|
2378
|
+
if (stale > 0)
|
|
2379
|
+
problems.push(
|
|
2380
|
+
`${stale} agent file(s) out of date \u2014 run ${rnessCommand()} sync`
|
|
2381
|
+
);
|
|
2382
|
+
if (!check)
|
|
2383
|
+
for (const label of await leftoverTargets(ws.root, manifest))
|
|
2384
|
+
lines.push(
|
|
2385
|
+
`${label} still carries the values of an agent no longer in rness.json (left as is)`
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2089
2388
|
}
|
|
2090
2389
|
return problems.length > 0 ? 1 : 0;
|
|
2091
2390
|
} finally {
|
|
@@ -2105,7 +2404,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
2105
2404
|
}
|
|
2106
2405
|
|
|
2107
2406
|
// src/commands/add.ts
|
|
2108
|
-
import { basename as basename3, join as
|
|
2407
|
+
import { basename as basename3, join as join11 } from "path";
|
|
2109
2408
|
function printDeclared(ui, result, already) {
|
|
2110
2409
|
for (const name of result.declared) {
|
|
2111
2410
|
if (already.has(name)) continue;
|
|
@@ -2198,7 +2497,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
2198
2497
|
}
|
|
2199
2498
|
parsed = parseRepoSpec(spec, org, host);
|
|
2200
2499
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
2201
|
-
const dir =
|
|
2500
|
+
const dir = join11(ws.root, "org", parsed.name);
|
|
2202
2501
|
const present = await exists(dir);
|
|
2203
2502
|
if (interactive) {
|
|
2204
2503
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -2324,26 +2623,23 @@ async function contextCommand(opts) {
|
|
|
2324
2623
|
}
|
|
2325
2624
|
}
|
|
2326
2625
|
|
|
2327
|
-
// src/version.ts
|
|
2328
|
-
var VERSION = "0.6.2";
|
|
2329
|
-
|
|
2330
2626
|
// src/core/delegate.ts
|
|
2331
2627
|
import { readFile as readFile6 } from "fs/promises";
|
|
2332
|
-
import { join as
|
|
2628
|
+
import { join as join12 } from "path";
|
|
2333
2629
|
var DEFAULT_BIN = "dist/bin/rness.js";
|
|
2334
2630
|
async function readPinnedCli(rnessDir) {
|
|
2335
|
-
const dir =
|
|
2631
|
+
const dir = join12(rnessDir, "node_modules", "@rness", "cli");
|
|
2336
2632
|
let pkg;
|
|
2337
2633
|
try {
|
|
2338
|
-
pkg = JSON.parse(await readFile6(
|
|
2634
|
+
pkg = JSON.parse(await readFile6(join12(dir, "package.json"), "utf8"));
|
|
2339
2635
|
} catch {
|
|
2340
2636
|
return null;
|
|
2341
2637
|
}
|
|
2342
|
-
const
|
|
2638
|
+
const declared2 = pkg.bin;
|
|
2343
2639
|
let bin = DEFAULT_BIN;
|
|
2344
|
-
if (typeof
|
|
2345
|
-
else if (
|
|
2346
|
-
const named =
|
|
2640
|
+
if (typeof declared2 === "string") bin = declared2;
|
|
2641
|
+
else if (declared2 !== null && typeof declared2 === "object") {
|
|
2642
|
+
const named = declared2["rness"];
|
|
2347
2643
|
if (typeof named === "string") bin = named;
|
|
2348
2644
|
}
|
|
2349
2645
|
return {
|
|
@@ -2355,7 +2651,7 @@ async function readPinnedCli(rnessDir) {
|
|
|
2355
2651
|
async function pinnedBinPath(rnessDir) {
|
|
2356
2652
|
const pinned = await readPinnedCli(rnessDir);
|
|
2357
2653
|
if (pinned === null) return null;
|
|
2358
|
-
return
|
|
2654
|
+
return join12(pinned.dir, ...pinned.bin.split("/"));
|
|
2359
2655
|
}
|
|
2360
2656
|
async function findDelegate(cwd, ownVersion) {
|
|
2361
2657
|
let root;
|
|
@@ -2371,12 +2667,12 @@ async function findDelegate(cwd, ownVersion) {
|
|
|
2371
2667
|
if (pinned === null) return null;
|
|
2372
2668
|
const { version } = pinned;
|
|
2373
2669
|
if (version === null || version === ownVersion) return null;
|
|
2374
|
-
return { entry:
|
|
2670
|
+
return { entry: join12(pinned.dir, "dist", "index.js"), version, root };
|
|
2375
2671
|
}
|
|
2376
2672
|
|
|
2377
2673
|
// src/core/scaffold.ts
|
|
2378
2674
|
import { statSync } from "fs";
|
|
2379
|
-
import { dirname as
|
|
2675
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
2380
2676
|
import { fileURLToPath } from "url";
|
|
2381
2677
|
var SCAFFOLD_FILES = [
|
|
2382
2678
|
"AGENTS.md",
|
|
@@ -2398,30 +2694,30 @@ var SCAFFOLD_FILES = [
|
|
|
2398
2694
|
"docs/.gitkeep"
|
|
2399
2695
|
];
|
|
2400
2696
|
function scaffoldDir() {
|
|
2401
|
-
let dir =
|
|
2697
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
2402
2698
|
for (let i = 0; i < 4; i += 1) {
|
|
2403
|
-
const candidate =
|
|
2699
|
+
const candidate = join13(dir, "scaffold");
|
|
2404
2700
|
try {
|
|
2405
|
-
if (statSync(
|
|
2701
|
+
if (statSync(join13(candidate, "rness.json")).isFile()) return candidate;
|
|
2406
2702
|
} catch {
|
|
2407
2703
|
}
|
|
2408
|
-
dir =
|
|
2704
|
+
dir = dirname5(dir);
|
|
2409
2705
|
}
|
|
2410
2706
|
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
2411
2707
|
}
|
|
2412
2708
|
|
|
2413
2709
|
// src/core/scaffold-copy.ts
|
|
2414
|
-
import { chmod as chmod2, mkdir as
|
|
2415
|
-
import { dirname as
|
|
2710
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile7, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
2711
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
2416
2712
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
2417
2713
|
async function copyScaffold(dest, tokens) {
|
|
2418
2714
|
const source = scaffoldDir();
|
|
2419
2715
|
for (const rel of SCAFFOLD_FILES) {
|
|
2420
2716
|
if (rel === "rness.json") continue;
|
|
2421
|
-
const from =
|
|
2717
|
+
const from = join14(source, ...rel.split("/"));
|
|
2422
2718
|
const targetRel = rel === "_gitignore" ? ".gitignore" : rel;
|
|
2423
|
-
const to =
|
|
2424
|
-
await
|
|
2719
|
+
const to = join14(dest, ...targetRel.split("/"));
|
|
2720
|
+
await mkdir3(dirname6(to), { recursive: true });
|
|
2425
2721
|
let content = await readFile7(from, "utf8");
|
|
2426
2722
|
if (TOKENISED.has(rel)) {
|
|
2427
2723
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
@@ -2582,18 +2878,18 @@ function revokeUrl() {
|
|
|
2582
2878
|
}
|
|
2583
2879
|
|
|
2584
2880
|
// src/commands/create.ts
|
|
2585
|
-
import { mkdir as
|
|
2881
|
+
import { mkdir as mkdir6, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
|
|
2586
2882
|
import { tmpdir } from "os";
|
|
2587
|
-
import { join as
|
|
2883
|
+
import { join as join18, relative as relative4, resolve as resolve4 } from "path";
|
|
2588
2884
|
|
|
2589
2885
|
// src/core/new-workspace.ts
|
|
2590
|
-
import { mkdir as
|
|
2591
|
-
import { dirname as
|
|
2886
|
+
import { mkdir as mkdir4, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
2887
|
+
import { dirname as dirname7, join as join16, relative as relative2 } from "path";
|
|
2592
2888
|
|
|
2593
2889
|
// src/core/pinned.ts
|
|
2594
2890
|
import { execFile as execFile4 } from "child_process";
|
|
2595
2891
|
import { readFile as readFile8 } from "fs/promises";
|
|
2596
|
-
import { basename as basename4, join as
|
|
2892
|
+
import { basename as basename4, join as join15 } from "path";
|
|
2597
2893
|
import { promisify as promisify4 } from "util";
|
|
2598
2894
|
var execFileP4 = promisify4(execFile4);
|
|
2599
2895
|
var PACKAGE = "@rness/cli";
|
|
@@ -2618,7 +2914,7 @@ function compareVersions(a, b) {
|
|
|
2618
2914
|
async function readPackageJson(rnessDir) {
|
|
2619
2915
|
try {
|
|
2620
2916
|
const parsed = JSON.parse(
|
|
2621
|
-
await readFile8(
|
|
2917
|
+
await readFile8(join15(rnessDir, "package.json"), "utf8")
|
|
2622
2918
|
);
|
|
2623
2919
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
2624
2920
|
} catch {
|
|
@@ -2637,7 +2933,7 @@ async function readPin(rnessDir) {
|
|
|
2637
2933
|
return null;
|
|
2638
2934
|
}
|
|
2639
2935
|
async function writePin(rnessDir, from, to) {
|
|
2640
|
-
const file =
|
|
2936
|
+
const file = join15(rnessDir, "package.json");
|
|
2641
2937
|
const text = await readFile8(file, "utf8");
|
|
2642
2938
|
const entry = new RegExp(
|
|
2643
2939
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
@@ -2654,18 +2950,18 @@ var LOCKFILES = [
|
|
|
2654
2950
|
["package-lock.json", "npm"]
|
|
2655
2951
|
];
|
|
2656
2952
|
async function workspacePackageManager(rnessDir) {
|
|
2657
|
-
const
|
|
2658
|
-
if (typeof
|
|
2659
|
-
const name =
|
|
2953
|
+
const declared2 = (await readPackageJson(rnessDir))?.["packageManager"];
|
|
2954
|
+
if (typeof declared2 === "string") {
|
|
2955
|
+
const name = declared2.split("@")[0] ?? "";
|
|
2660
2956
|
if (isPackageManager(name)) return name;
|
|
2661
2957
|
}
|
|
2662
2958
|
for (const [lockfile, pm] of LOCKFILES)
|
|
2663
|
-
if (await exists(
|
|
2959
|
+
if (await exists(join15(rnessDir, lockfile))) return pm;
|
|
2664
2960
|
return "npm";
|
|
2665
2961
|
}
|
|
2666
2962
|
async function hasLockfile(rnessDir, pm) {
|
|
2667
2963
|
for (const [lockfile, owner] of LOCKFILES)
|
|
2668
|
-
if (owner === pm && await exists(
|
|
2964
|
+
if (owner === pm && await exists(join15(rnessDir, lockfile))) return true;
|
|
2669
2965
|
return false;
|
|
2670
2966
|
}
|
|
2671
2967
|
async function pinDrift(rnessDir) {
|
|
@@ -2721,9 +3017,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
2721
3017
|
}
|
|
2722
3018
|
}
|
|
2723
3019
|
async function runPinnedSync(root) {
|
|
2724
|
-
const installed = await readPinnedCli(
|
|
3020
|
+
const installed = await readPinnedCli(join15(root, ".rness"));
|
|
2725
3021
|
if (installed === null) return null;
|
|
2726
|
-
const pinned =
|
|
3022
|
+
const pinned = join15(installed.dir, ...installed.bin.split("/"));
|
|
2727
3023
|
if (!await exists(pinned))
|
|
2728
3024
|
return {
|
|
2729
3025
|
code: 1,
|
|
@@ -2785,7 +3081,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
2785
3081
|
};
|
|
2786
3082
|
}
|
|
2787
3083
|
async function syncPinned(root, shown, fallback) {
|
|
2788
|
-
const rnessDir =
|
|
3084
|
+
const rnessDir = join15(root, ".rness");
|
|
2789
3085
|
const installed = await readPinnedCli(rnessDir);
|
|
2790
3086
|
if (installed === null) {
|
|
2791
3087
|
if (fallback !== void 0) return fallback();
|
|
@@ -2795,7 +3091,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2795
3091
|
);
|
|
2796
3092
|
return 1;
|
|
2797
3093
|
}
|
|
2798
|
-
const pinned =
|
|
3094
|
+
const pinned = join15(installed.dir, ...installed.bin.split("/"));
|
|
2799
3095
|
if (!await exists(pinned)) {
|
|
2800
3096
|
process.stderr.write(
|
|
2801
3097
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -2842,11 +3138,11 @@ async function insideWorkspace(dir) {
|
|
|
2842
3138
|
}
|
|
2843
3139
|
async function targetProblem(cwd, root, pm) {
|
|
2844
3140
|
const shown = relative2(cwd, root) || ".";
|
|
2845
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(
|
|
3141
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname7(root)))
|
|
2846
3142
|
return "already inside an rness workspace; create runs from outside one";
|
|
2847
3143
|
if (!await exists(root)) return null;
|
|
2848
|
-
if (await exists(
|
|
2849
|
-
if (!await exists(
|
|
3144
|
+
if (await exists(join16(root, ".rness", "rness.json"))) {
|
|
3145
|
+
if (!await exists(join16(root, ".rness", "node_modules")))
|
|
2850
3146
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
2851
3147
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
2852
3148
|
}
|
|
@@ -2869,9 +3165,9 @@ async function installContext(input) {
|
|
|
2869
3165
|
}
|
|
2870
3166
|
async function buildContext(input) {
|
|
2871
3167
|
const { ui, root, shown, pm } = input;
|
|
2872
|
-
const rnessDir =
|
|
3168
|
+
const rnessDir = join16(root, ".rness");
|
|
2873
3169
|
const pmVersion = await packageManagerVersion(pm);
|
|
2874
|
-
await
|
|
3170
|
+
await mkdir4(root, { recursive: true });
|
|
2875
3171
|
try {
|
|
2876
3172
|
await copyScaffold(rnessDir, {
|
|
2877
3173
|
version: VERSION,
|
|
@@ -2937,8 +3233,8 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
2937
3233
|
}
|
|
2938
3234
|
|
|
2939
3235
|
// src/commands/create-blank.ts
|
|
2940
|
-
import { mkdir as
|
|
2941
|
-
import { join as
|
|
3236
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
3237
|
+
import { join as join17, relative as relative3, resolve as resolve3 } from "path";
|
|
2942
3238
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
2943
3239
|
function workspaceNameError(name) {
|
|
2944
3240
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -2984,6 +3280,14 @@ async function createBlank(input) {
|
|
|
2984
3280
|
);
|
|
2985
3281
|
return 2;
|
|
2986
3282
|
}
|
|
3283
|
+
let agents = input.agents.length > 0 ? [...input.agents] : null;
|
|
3284
|
+
if (agents === null && input.interactive) {
|
|
3285
|
+
agents = await askAgents(await input.prompts());
|
|
3286
|
+
if (agents === null) {
|
|
3287
|
+
ui.cancelled();
|
|
3288
|
+
return 0;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
2987
3291
|
if (input.interactive && !prompted) {
|
|
2988
3292
|
const p = await input.prompts();
|
|
2989
3293
|
const ok = await p.confirm({
|
|
@@ -2998,14 +3302,14 @@ async function createBlank(input) {
|
|
|
2998
3302
|
ui,
|
|
2999
3303
|
root,
|
|
3000
3304
|
shown,
|
|
3001
|
-
manifest: { contract: 1, org: null, repos: {}, scopes: {} },
|
|
3305
|
+
manifest: { contract: 1, org: null, agents, repos: {}, scopes: {} },
|
|
3002
3306
|
pm,
|
|
3003
3307
|
skipInstall: input.skipInstall,
|
|
3004
3308
|
kind: "blank workspace"
|
|
3005
3309
|
});
|
|
3006
3310
|
if (!built) return 1;
|
|
3007
|
-
await
|
|
3008
|
-
await commitContext({ ui, rnessDir:
|
|
3311
|
+
await mkdir5(join17(root, "org"), { recursive: true });
|
|
3312
|
+
await commitContext({ ui, rnessDir: join17(root, ".rness"), shown });
|
|
3009
3313
|
const code = await syncBlocks(
|
|
3010
3314
|
ui,
|
|
3011
3315
|
root,
|
|
@@ -3165,7 +3469,7 @@ async function cloneSelection(input) {
|
|
|
3165
3469
|
{ doing: `cloning org/${repo}`, git: true },
|
|
3166
3470
|
() => clone(
|
|
3167
3471
|
entry.url,
|
|
3168
|
-
|
|
3472
|
+
join18(input.root, "org", repo),
|
|
3169
3473
|
input.provider.credentialsFor(entry.url)
|
|
3170
3474
|
),
|
|
3171
3475
|
() => ["cloned", `org/${repo}`]
|
|
@@ -3296,6 +3600,15 @@ async function createCommand(opts, deps = {}) {
|
|
|
3296
3600
|
`);
|
|
3297
3601
|
return 2;
|
|
3298
3602
|
}
|
|
3603
|
+
const agentFlags = [...new Set(opts.agent ?? [])];
|
|
3604
|
+
const unknownAgent = unsupportedAgents(agentFlags)[0];
|
|
3605
|
+
if (unknownAgent !== void 0) {
|
|
3606
|
+
process.stderr.write(
|
|
3607
|
+
`unknown agent "${unknownAgent}" (supported: ${SUPPORTED_AGENTS.join(", ")})
|
|
3608
|
+
`
|
|
3609
|
+
);
|
|
3610
|
+
return 2;
|
|
3611
|
+
}
|
|
3299
3612
|
if (opts.ssh === true && opts.https === true) {
|
|
3300
3613
|
process.stderr.write("--ssh and --https cannot be combined\n");
|
|
3301
3614
|
return 2;
|
|
@@ -3369,6 +3682,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3369
3682
|
if (blank)
|
|
3370
3683
|
return await createBlank({
|
|
3371
3684
|
name: opts.name,
|
|
3685
|
+
agents: agentFlags,
|
|
3372
3686
|
cwd,
|
|
3373
3687
|
pm,
|
|
3374
3688
|
interactive,
|
|
@@ -3452,6 +3766,13 @@ async function createCommand(opts, deps = {}) {
|
|
|
3452
3766
|
return 1;
|
|
3453
3767
|
}
|
|
3454
3768
|
const joining = probe.kind === "found";
|
|
3769
|
+
if (joining && agentFlags.length > 0) {
|
|
3770
|
+
process.stderr.write(
|
|
3771
|
+
`${org}/.rness exists: --agent only applies to a new workspace; after joining, run ${rnessCommand()} sync${agentFlags.map((a) => ` --agent ${a}`).join("")}
|
|
3772
|
+
`
|
|
3773
|
+
);
|
|
3774
|
+
return 2;
|
|
3775
|
+
}
|
|
3455
3776
|
if (joining)
|
|
3456
3777
|
ui.line(
|
|
3457
3778
|
"found",
|
|
@@ -3464,10 +3785,16 @@ async function createCommand(opts, deps = {}) {
|
|
|
3464
3785
|
`${org}/.rness (or not visible to you) \u2014 starting a new workspace`,
|
|
3465
3786
|
`${org} has no workspace yet (or none you can see) \u2014 starting one`
|
|
3466
3787
|
);
|
|
3467
|
-
let catalogue = {
|
|
3788
|
+
let catalogue = {
|
|
3789
|
+
contract: 1,
|
|
3790
|
+
org,
|
|
3791
|
+
agents: null,
|
|
3792
|
+
repos: {},
|
|
3793
|
+
scopes: {}
|
|
3794
|
+
};
|
|
3468
3795
|
const staged = async () => {
|
|
3469
|
-
staging ??= await mkdtemp(
|
|
3470
|
-
return
|
|
3796
|
+
staging ??= await mkdtemp(join18(tmpdir(), "rness-join-"));
|
|
3797
|
+
return join18(staging, ".rness");
|
|
3471
3798
|
};
|
|
3472
3799
|
if (joining) {
|
|
3473
3800
|
const stagedDir = await staged();
|
|
@@ -3510,6 +3837,14 @@ async function createCommand(opts, deps = {}) {
|
|
|
3510
3837
|
} else {
|
|
3511
3838
|
specs = Object.keys(catalogue.repos);
|
|
3512
3839
|
}
|
|
3840
|
+
if (!joining) {
|
|
3841
|
+
if (agentFlags.length > 0) catalogue.agents = agentFlags;
|
|
3842
|
+
else if (interactive) {
|
|
3843
|
+
const answer = await askAgents(await prompts());
|
|
3844
|
+
if (answer === null) return cancelled(ui);
|
|
3845
|
+
catalogue.agents = answer;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3513
3848
|
if (interactive && !prompted) {
|
|
3514
3849
|
const p = await prompts();
|
|
3515
3850
|
const ok = await p.confirm({
|
|
@@ -3517,10 +3852,10 @@ async function createCommand(opts, deps = {}) {
|
|
|
3517
3852
|
});
|
|
3518
3853
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
3519
3854
|
}
|
|
3520
|
-
const rnessDir =
|
|
3855
|
+
const rnessDir = join18(root, ".rness");
|
|
3521
3856
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
3522
3857
|
if (joining) {
|
|
3523
|
-
await
|
|
3858
|
+
await mkdir6(root, { recursive: true });
|
|
3524
3859
|
await placeContext(
|
|
3525
3860
|
await staged(),
|
|
3526
3861
|
rnessDir,
|
|
@@ -3530,7 +3865,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3530
3865
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
3531
3866
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
3532
3867
|
await install();
|
|
3533
|
-
await
|
|
3868
|
+
await mkdir6(join18(root, "org"), { recursive: true });
|
|
3534
3869
|
const failures2 = await cloneSelection({
|
|
3535
3870
|
root,
|
|
3536
3871
|
rnessDir,
|
|
@@ -3583,7 +3918,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3583
3918
|
kind: "new workspace"
|
|
3584
3919
|
});
|
|
3585
3920
|
if (!built) return 1;
|
|
3586
|
-
await
|
|
3921
|
+
await mkdir6(join18(root, "org"), { recursive: true });
|
|
3587
3922
|
const failures = await cloneSelection({
|
|
3588
3923
|
root,
|
|
3589
3924
|
rnessDir,
|
|
@@ -3684,8 +4019,8 @@ async function logoutCommand() {
|
|
|
3684
4019
|
|
|
3685
4020
|
// src/commands/upgrade.ts
|
|
3686
4021
|
import { execFile as execFile5 } from "child_process";
|
|
3687
|
-
import { mkdir as
|
|
3688
|
-
import { basename as basename5, dirname as
|
|
4022
|
+
import { mkdir as mkdir7, readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
4023
|
+
import { basename as basename5, dirname as dirname8, join as join19, relative as relative5 } from "path";
|
|
3689
4024
|
import { promisify as promisify5 } from "util";
|
|
3690
4025
|
var execFileP5 = promisify5(execFile5);
|
|
3691
4026
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -3783,15 +4118,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3783
4118
|
return 1;
|
|
3784
4119
|
}
|
|
3785
4120
|
}
|
|
3786
|
-
const packageFile =
|
|
4121
|
+
const packageFile = join19(ws.rnessDir, "package.json");
|
|
3787
4122
|
const restore = /* @__PURE__ */ new Map([
|
|
3788
4123
|
[packageFile, await readFile9(packageFile, "utf8")]
|
|
3789
4124
|
]);
|
|
3790
4125
|
if (pin.spec !== target) await writePin(ws.rnessDir, pin.spec, target);
|
|
3791
|
-
const workflows =
|
|
4126
|
+
const workflows = join19(ws.rnessDir, ".github", "workflows");
|
|
3792
4127
|
for (const name of await readdir3(workflows).catch(() => [])) {
|
|
3793
4128
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
3794
|
-
const file =
|
|
4129
|
+
const file = join19(workflows, name);
|
|
3795
4130
|
const text = await readOrNull(file);
|
|
3796
4131
|
if (text === null) continue;
|
|
3797
4132
|
const shown = `.github/workflows/${name}`;
|
|
@@ -3809,14 +4144,14 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3809
4144
|
ui.warn(`${shown} names @rness/cli@${pin.spec}; update it by hand`);
|
|
3810
4145
|
}
|
|
3811
4146
|
}
|
|
3812
|
-
const dependabot =
|
|
4147
|
+
const dependabot = join19(ws.rnessDir, ".github", "dependabot.yml");
|
|
3813
4148
|
if (await exists(dependabot)) {
|
|
3814
4149
|
ui.hint(".github/dependabot.yml is already there \u2014 left as it is");
|
|
3815
4150
|
} else {
|
|
3816
|
-
await
|
|
4151
|
+
await mkdir7(dirname8(dependabot), { recursive: true });
|
|
3817
4152
|
await writeFileAtomic(
|
|
3818
4153
|
dependabot,
|
|
3819
|
-
await readFile9(
|
|
4154
|
+
await readFile9(join19(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
|
|
3820
4155
|
);
|
|
3821
4156
|
ui.line(
|
|
3822
4157
|
"wrote",
|
|
@@ -3878,7 +4213,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3878
4213
|
}
|
|
3879
4214
|
|
|
3880
4215
|
// src/core/blocks.ts
|
|
3881
|
-
import { join as
|
|
4216
|
+
import { join as join20 } from "path";
|
|
3882
4217
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
3883
4218
|
async function checkBlocks({
|
|
3884
4219
|
root,
|
|
@@ -3895,7 +4230,7 @@ async function checkBlocks({
|
|
|
3895
4230
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
3896
4231
|
checks
|
|
3897
4232
|
});
|
|
3898
|
-
if (!await exists(
|
|
4233
|
+
if (!await exists(join20(root, "org"))) return derive();
|
|
3899
4234
|
const targets = [
|
|
3900
4235
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
3901
4236
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -3904,7 +4239,7 @@ async function checkBlocks({
|
|
|
3904
4239
|
const segments = entry.path.split("/");
|
|
3905
4240
|
targets.push({
|
|
3906
4241
|
scope: name,
|
|
3907
|
-
dir:
|
|
4242
|
+
dir: join20(root, ...segments),
|
|
3908
4243
|
depth: segments.length,
|
|
3909
4244
|
file: "AGENTS.md",
|
|
3910
4245
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -3919,7 +4254,7 @@ async function checkBlocks({
|
|
|
3919
4254
|
);
|
|
3920
4255
|
continue;
|
|
3921
4256
|
}
|
|
3922
|
-
const text = await readOrNull(
|
|
4257
|
+
const text = await readOrNull(join20(t.dir, t.file));
|
|
3923
4258
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
3924
4259
|
const found = findBlock(lines);
|
|
3925
4260
|
if (found.kind === "none") {
|
|
@@ -3959,7 +4294,7 @@ async function checkBlocks({
|
|
|
3959
4294
|
}
|
|
3960
4295
|
|
|
3961
4296
|
// src/core/contract.ts
|
|
3962
|
-
import { join as
|
|
4297
|
+
import { join as join21 } from "path";
|
|
3963
4298
|
var STATUSES = {
|
|
3964
4299
|
adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
|
|
3965
4300
|
specs: [
|
|
@@ -3978,7 +4313,7 @@ async function checkContract(rnessDir) {
|
|
|
3978
4313
|
const problems = [];
|
|
3979
4314
|
for (const name of COLLECTIONS) {
|
|
3980
4315
|
const allowed = STATUSES[name];
|
|
3981
|
-
const items = await collectMarkdown(
|
|
4316
|
+
const items = await collectMarkdown(join21(rnessDir, name));
|
|
3982
4317
|
for (const item of items) {
|
|
3983
4318
|
const where = `${name}/${item.rel}`;
|
|
3984
4319
|
if (SKIP.has(where)) continue;
|
|
@@ -4030,6 +4365,16 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4030
4365
|
org
|
|
4031
4366
|
});
|
|
4032
4367
|
problems.push(...blocks.problems);
|
|
4368
|
+
for (const name of unsupportedAgents(manifest.agents ?? []))
|
|
4369
|
+
problems.push(unsupportedMessage(name));
|
|
4370
|
+
const targets = await agentTargets(ws.root, manifest, { check: true });
|
|
4371
|
+
for (const o of targets)
|
|
4372
|
+
if (o.status === "stale")
|
|
4373
|
+
problems.push(
|
|
4374
|
+
`${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
|
|
4375
|
+
);
|
|
4376
|
+
else if (o.status === "invalid")
|
|
4377
|
+
problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
|
|
4033
4378
|
const ui = await makeUi(deps.terminal ?? defaultTerminal);
|
|
4034
4379
|
if (!ui.session) {
|
|
4035
4380
|
for (const p of problems) process.stderr.write(`${p}
|
|
@@ -4052,6 +4397,8 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4052
4397
|
else if (c.status === "stale" || c.status === "malformed")
|
|
4053
4398
|
ui.error(c.message);
|
|
4054
4399
|
else ui.warn(c.message);
|
|
4400
|
+
for (const o of targets)
|
|
4401
|
+
if (o.status === "unchanged") ui.line("current", o.label);
|
|
4055
4402
|
for (const p of problems) if (!blocks.problems.includes(p)) ui.error(p);
|
|
4056
4403
|
ui.outro(
|
|
4057
4404
|
problems.length > 0 ? "Context mismatch" : "Context OK",
|
|
@@ -4126,7 +4473,11 @@ function buildProgram(state) {
|
|
|
4126
4473
|
).option("--scope <name>", "only this scope (the root block is skipped)").option(
|
|
4127
4474
|
"--check",
|
|
4128
4475
|
"render and compare only; exit 1 when a block is out of date"
|
|
4129
|
-
).option("--pull", "git pull --ff-only in every clean clone").option("--all", "clone every rness.json repository missing from org/").option(
|
|
4476
|
+
).option("--pull", "git pull --ff-only in every clean clone").option("--all", "clone every rness.json repository missing from org/").option(
|
|
4477
|
+
"--agent <name>",
|
|
4478
|
+
"declare an agent in rness.json, then write its files (repeatable; supported: claude)",
|
|
4479
|
+
(value, previous = []) => [...previous, value]
|
|
4480
|
+
).option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
4130
4481
|
state.code = await syncCommand(opts);
|
|
4131
4482
|
});
|
|
4132
4483
|
program.command("add").description(
|
|
@@ -4174,6 +4525,10 @@ function buildProgram(state) {
|
|
|
4174
4525
|
).option("--https", "force https://github.com/").option(
|
|
4175
4526
|
"--blank",
|
|
4176
4527
|
"a local workspace with no GitHub organization: .rness/, an empty org/, the root files"
|
|
4528
|
+
).option(
|
|
4529
|
+
"--agent <name>",
|
|
4530
|
+
"a new workspace's agent, declared in rness.json (repeatable; supported: claude)",
|
|
4531
|
+
(value, previous = []) => [...previous, value]
|
|
4177
4532
|
).option("--skip-install", "do not install .rness/ dependencies").option("-y, --yes", "do not ask for confirmation").option("--template <name>", "reserved").addOption(new Option("--host <base>").hideHelp()).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (orgArg, opts) => {
|
|
4178
4533
|
if (opts.blank === true) {
|
|
4179
4534
|
state.code = await createCommand(
|
|
@@ -4289,6 +4644,7 @@ export {
|
|
|
4289
4644
|
defaultTransport,
|
|
4290
4645
|
findWorkspace,
|
|
4291
4646
|
reportError,
|
|
4647
|
+
VERSION,
|
|
4292
4648
|
BEGIN,
|
|
4293
4649
|
END,
|
|
4294
4650
|
BLOCK_SIZE_WARNING,
|
|
@@ -4310,7 +4666,6 @@ export {
|
|
|
4310
4666
|
addCommand,
|
|
4311
4667
|
renderMarkdown,
|
|
4312
4668
|
contextCommand,
|
|
4313
|
-
VERSION,
|
|
4314
4669
|
findDelegate,
|
|
4315
4670
|
SCAFFOLD_FILES,
|
|
4316
4671
|
scaffoldDir,
|
|
@@ -4327,4 +4682,4 @@ export {
|
|
|
4327
4682
|
OWN_COMMANDS,
|
|
4328
4683
|
catchUp
|
|
4329
4684
|
};
|
|
4330
|
-
//# sourceMappingURL=chunk-
|
|
4685
|
+
//# sourceMappingURL=chunk-VYP4L7BQ.js.map
|