@rness/cli 0.6.2 → 0.8.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 +97 -14
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-7TOHFPW6.js → chunk-VQMX52HT.js} +798 -179
- package/dist/chunk-VQMX52HT.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
|
|
@@ -794,7 +797,7 @@ function classifyProbe(url, exitCode, stderr) {
|
|
|
794
797
|
var STDERR_FLUSH_MS = 200;
|
|
795
798
|
async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
796
799
|
positional(url, "repository url");
|
|
797
|
-
return new Promise((
|
|
800
|
+
return new Promise((resolve6) => {
|
|
798
801
|
const auth = withCredentials(credentials);
|
|
799
802
|
const child = spawn(
|
|
800
803
|
"git",
|
|
@@ -813,7 +816,7 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
|
813
816
|
settled = true;
|
|
814
817
|
clearTimeout(timer);
|
|
815
818
|
child.stderr?.destroy();
|
|
816
|
-
|
|
819
|
+
resolve6(probe);
|
|
817
820
|
};
|
|
818
821
|
const timer = setTimeout(() => {
|
|
819
822
|
child.kill("SIGKILL");
|
|
@@ -824,14 +827,14 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
|
824
827
|
}, timeoutMs);
|
|
825
828
|
child.on("error", (e) => settle(classifyProbe(url, 1, e.message)));
|
|
826
829
|
child.on("exit", (code) => {
|
|
827
|
-
const
|
|
830
|
+
const finish2 = () => settle(classifyProbe(url, code ?? 1, chunks.join("")));
|
|
828
831
|
const stderr = child.stderr;
|
|
829
832
|
if (stderr === null || stderr.readableEnded || stderr.destroyed)
|
|
830
|
-
return
|
|
831
|
-
const flush = setTimeout(
|
|
833
|
+
return finish2();
|
|
834
|
+
const flush = setTimeout(finish2, STDERR_FLUSH_MS);
|
|
832
835
|
const done = () => {
|
|
833
836
|
clearTimeout(flush);
|
|
834
|
-
|
|
837
|
+
finish2();
|
|
835
838
|
};
|
|
836
839
|
stderr.once("end", done);
|
|
837
840
|
stderr.once("error", done);
|
|
@@ -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 = [];
|
|
@@ -1231,7 +1248,7 @@ async function detectGithubSsh(opts) {
|
|
|
1231
1248
|
...opts.interactive ? [] : ["-o", "BatchMode=yes"],
|
|
1232
1249
|
"git@github.com"
|
|
1233
1250
|
];
|
|
1234
|
-
return new Promise((
|
|
1251
|
+
return new Promise((resolve6) => {
|
|
1235
1252
|
const child = spawn2(opts.command ?? "ssh", args, {
|
|
1236
1253
|
stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
|
|
1237
1254
|
});
|
|
@@ -1244,7 +1261,7 @@ async function detectGithubSsh(opts) {
|
|
|
1244
1261
|
settled = true;
|
|
1245
1262
|
clearTimeout(timer);
|
|
1246
1263
|
child.stderr?.destroy();
|
|
1247
|
-
|
|
1264
|
+
resolve6(access2);
|
|
1248
1265
|
};
|
|
1249
1266
|
const timer = setTimeout(() => {
|
|
1250
1267
|
child.kill("SIGKILL");
|
|
@@ -1252,7 +1269,7 @@ async function detectGithubSsh(opts) {
|
|
|
1252
1269
|
}, timeoutMs);
|
|
1253
1270
|
child.on("error", (e) => settle({ ok: false, reason: e.message }));
|
|
1254
1271
|
child.on("exit", (code) => {
|
|
1255
|
-
const
|
|
1272
|
+
const finish2 = () => {
|
|
1256
1273
|
const stderr2 = chunks.join("");
|
|
1257
1274
|
const login = parseSshGreeting(stderr2);
|
|
1258
1275
|
settle(
|
|
@@ -1264,11 +1281,11 @@ async function detectGithubSsh(opts) {
|
|
|
1264
1281
|
};
|
|
1265
1282
|
const stderr = child.stderr;
|
|
1266
1283
|
if (stderr === null || stderr.readableEnded || stderr.destroyed)
|
|
1267
|
-
return
|
|
1268
|
-
const flush = setTimeout(
|
|
1284
|
+
return finish2();
|
|
1285
|
+
const flush = setTimeout(finish2, STDERR_FLUSH_MS2);
|
|
1269
1286
|
const done = () => {
|
|
1270
1287
|
clearTimeout(flush);
|
|
1271
|
-
|
|
1288
|
+
finish2();
|
|
1272
1289
|
};
|
|
1273
1290
|
stderr.once("end", done);
|
|
1274
1291
|
stderr.once("error", done);
|
|
@@ -1372,6 +1389,9 @@ function reportError(e) {
|
|
|
1372
1389
|
return 1;
|
|
1373
1390
|
}
|
|
1374
1391
|
|
|
1392
|
+
// src/version.ts
|
|
1393
|
+
var VERSION = "0.8.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"];
|
|
@@ -1803,12 +2030,12 @@ function sessionUi(p) {
|
|
|
1803
2030
|
},
|
|
1804
2031
|
async step(options, work, done) {
|
|
1805
2032
|
const label = options.sentence ?? tidy(options.doing);
|
|
1806
|
-
const
|
|
2033
|
+
const finish2 = (result) => done?.(result) ?? null;
|
|
1807
2034
|
const label_ = (line) => `${line[1]} ${line[0]}`;
|
|
1808
2035
|
if (options.git === true && !this.gitIsSilent) {
|
|
1809
2036
|
p.log.message(paint("idle", `${label}\u2026`));
|
|
1810
2037
|
const result = await work();
|
|
1811
|
-
const line =
|
|
2038
|
+
const line = finish2(result);
|
|
1812
2039
|
if (line !== null) say(line[0], label_(line));
|
|
1813
2040
|
return result;
|
|
1814
2041
|
}
|
|
@@ -1816,7 +2043,7 @@ function sessionUi(p) {
|
|
|
1816
2043
|
spin.start(label);
|
|
1817
2044
|
try {
|
|
1818
2045
|
const result = await work();
|
|
1819
|
-
const line =
|
|
2046
|
+
const line = finish2(result);
|
|
1820
2047
|
spin.stop(line === null ? label : label_(line));
|
|
1821
2048
|
return result;
|
|
1822
2049
|
} catch (e) {
|
|
@@ -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,37 +2694,45 @@ 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
|
-
async function
|
|
2418
|
-
const
|
|
2713
|
+
async function renderScaffold(source, tokens) {
|
|
2714
|
+
const files = [];
|
|
2419
2715
|
for (const rel of SCAFFOLD_FILES) {
|
|
2420
2716
|
if (rel === "rness.json") continue;
|
|
2421
|
-
const from =
|
|
2422
|
-
const targetRel = rel === "_gitignore" ? ".gitignore" : rel;
|
|
2423
|
-
const to = join13(dest, ...targetRel.split("/"));
|
|
2424
|
-
await mkdir2(dirname5(to), { recursive: true });
|
|
2717
|
+
const from = join14(source, ...rel.split("/"));
|
|
2425
2718
|
let content = await readFile7(from, "utf8");
|
|
2426
2719
|
if (TOKENISED.has(rel)) {
|
|
2427
2720
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
2428
2721
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2722
|
+
files.push({
|
|
2723
|
+
path: rel === "_gitignore" ? ".gitignore" : rel,
|
|
2724
|
+
content,
|
|
2725
|
+
executable: ((await stat2(from)).mode & 73) !== 0
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
return files;
|
|
2729
|
+
}
|
|
2730
|
+
async function copyScaffold(dest, tokens) {
|
|
2731
|
+
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
2732
|
+
const to = join14(dest, ...file.path.split("/"));
|
|
2733
|
+
await mkdir3(dirname6(to), { recursive: true });
|
|
2734
|
+
await writeFile2(to, file.content, "utf8");
|
|
2735
|
+
if (file.executable) await chmod2(to, 493);
|
|
2432
2736
|
}
|
|
2433
2737
|
}
|
|
2434
2738
|
|
|
@@ -2582,18 +2886,18 @@ function revokeUrl() {
|
|
|
2582
2886
|
}
|
|
2583
2887
|
|
|
2584
2888
|
// src/commands/create.ts
|
|
2585
|
-
import { mkdir as
|
|
2586
|
-
import { tmpdir } from "os";
|
|
2587
|
-
import { join as
|
|
2889
|
+
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm6 } from "fs/promises";
|
|
2890
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2891
|
+
import { join as join19, relative as relative4, resolve as resolve5 } from "path";
|
|
2588
2892
|
|
|
2589
2893
|
// src/core/new-workspace.ts
|
|
2590
|
-
import { mkdir as
|
|
2591
|
-
import { dirname as
|
|
2894
|
+
import { mkdir as mkdir4, readdir as readdir2, rm as rm5, stat as stat3 } from "fs/promises";
|
|
2895
|
+
import { dirname as dirname8, join as join17, relative as relative2 } from "path";
|
|
2592
2896
|
|
|
2593
2897
|
// src/core/pinned.ts
|
|
2594
2898
|
import { execFile as execFile4 } from "child_process";
|
|
2595
2899
|
import { readFile as readFile8 } from "fs/promises";
|
|
2596
|
-
import { basename as basename4, join as
|
|
2900
|
+
import { basename as basename4, join as join15 } from "path";
|
|
2597
2901
|
import { promisify as promisify4 } from "util";
|
|
2598
2902
|
var execFileP4 = promisify4(execFile4);
|
|
2599
2903
|
var PACKAGE = "@rness/cli";
|
|
@@ -2618,7 +2922,7 @@ function compareVersions(a, b) {
|
|
|
2618
2922
|
async function readPackageJson(rnessDir) {
|
|
2619
2923
|
try {
|
|
2620
2924
|
const parsed = JSON.parse(
|
|
2621
|
-
await readFile8(
|
|
2925
|
+
await readFile8(join15(rnessDir, "package.json"), "utf8")
|
|
2622
2926
|
);
|
|
2623
2927
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
2624
2928
|
} catch {
|
|
@@ -2637,7 +2941,7 @@ async function readPin(rnessDir) {
|
|
|
2637
2941
|
return null;
|
|
2638
2942
|
}
|
|
2639
2943
|
async function writePin(rnessDir, from, to) {
|
|
2640
|
-
const file =
|
|
2944
|
+
const file = join15(rnessDir, "package.json");
|
|
2641
2945
|
const text = await readFile8(file, "utf8");
|
|
2642
2946
|
const entry = new RegExp(
|
|
2643
2947
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
@@ -2654,18 +2958,18 @@ var LOCKFILES = [
|
|
|
2654
2958
|
["package-lock.json", "npm"]
|
|
2655
2959
|
];
|
|
2656
2960
|
async function workspacePackageManager(rnessDir) {
|
|
2657
|
-
const
|
|
2658
|
-
if (typeof
|
|
2659
|
-
const name =
|
|
2961
|
+
const declared2 = (await readPackageJson(rnessDir))?.["packageManager"];
|
|
2962
|
+
if (typeof declared2 === "string") {
|
|
2963
|
+
const name = declared2.split("@")[0] ?? "";
|
|
2660
2964
|
if (isPackageManager(name)) return name;
|
|
2661
2965
|
}
|
|
2662
2966
|
for (const [lockfile, pm] of LOCKFILES)
|
|
2663
|
-
if (await exists(
|
|
2967
|
+
if (await exists(join15(rnessDir, lockfile))) return pm;
|
|
2664
2968
|
return "npm";
|
|
2665
2969
|
}
|
|
2666
2970
|
async function hasLockfile(rnessDir, pm) {
|
|
2667
2971
|
for (const [lockfile, owner] of LOCKFILES)
|
|
2668
|
-
if (owner === pm && await exists(
|
|
2972
|
+
if (owner === pm && await exists(join15(rnessDir, lockfile))) return true;
|
|
2669
2973
|
return false;
|
|
2670
2974
|
}
|
|
2671
2975
|
async function pinDrift(rnessDir) {
|
|
@@ -2721,9 +3025,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
2721
3025
|
}
|
|
2722
3026
|
}
|
|
2723
3027
|
async function runPinnedSync(root) {
|
|
2724
|
-
const installed = await readPinnedCli(
|
|
3028
|
+
const installed = await readPinnedCli(join15(root, ".rness"));
|
|
2725
3029
|
if (installed === null) return null;
|
|
2726
|
-
const pinned =
|
|
3030
|
+
const pinned = join15(installed.dir, ...installed.bin.split("/"));
|
|
2727
3031
|
if (!await exists(pinned))
|
|
2728
3032
|
return {
|
|
2729
3033
|
code: 1,
|
|
@@ -2785,7 +3089,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
2785
3089
|
};
|
|
2786
3090
|
}
|
|
2787
3091
|
async function syncPinned(root, shown, fallback) {
|
|
2788
|
-
const rnessDir =
|
|
3092
|
+
const rnessDir = join15(root, ".rness");
|
|
2789
3093
|
const installed = await readPinnedCli(rnessDir);
|
|
2790
3094
|
if (installed === null) {
|
|
2791
3095
|
if (fallback !== void 0) return fallback();
|
|
@@ -2795,7 +3099,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2795
3099
|
);
|
|
2796
3100
|
return 1;
|
|
2797
3101
|
}
|
|
2798
|
-
const pinned =
|
|
3102
|
+
const pinned = join15(installed.dir, ...installed.bin.split("/"));
|
|
2799
3103
|
if (!await exists(pinned)) {
|
|
2800
3104
|
process.stderr.write(
|
|
2801
3105
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -2828,6 +3132,166 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2828
3132
|
}
|
|
2829
3133
|
}
|
|
2830
3134
|
|
|
3135
|
+
// src/core/scaffold-git.ts
|
|
3136
|
+
import { execFile as execFile5 } from "child_process";
|
|
3137
|
+
import { mkdtemp, realpath as realpath2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
3138
|
+
import { tmpdir } from "os";
|
|
3139
|
+
import { dirname as dirname7, join as join16, resolve as resolve3 } from "path";
|
|
3140
|
+
import { promisify as promisify5 } from "util";
|
|
3141
|
+
var execFileP5 = promisify5(execFile5);
|
|
3142
|
+
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
3143
|
+
var SCAFFOLD_SUBJECT = /^chore: rness scaffold (\S+)$/;
|
|
3144
|
+
var SCAFFOLD_TRAILER = "Rness-Scaffold";
|
|
3145
|
+
async function run(dir, args, env = {}) {
|
|
3146
|
+
try {
|
|
3147
|
+
const { stdout, stderr } = await execFileP5("git", [...args], {
|
|
3148
|
+
cwd: dir,
|
|
3149
|
+
env: {
|
|
3150
|
+
...process.env,
|
|
3151
|
+
...env,
|
|
3152
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3153
|
+
GIT_CEILING_DIRECTORIES: dirname7(resolve3(dir))
|
|
3154
|
+
},
|
|
3155
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3156
|
+
});
|
|
3157
|
+
return { code: 0, stdout, stderr };
|
|
3158
|
+
} catch (e) {
|
|
3159
|
+
const err = e;
|
|
3160
|
+
return {
|
|
3161
|
+
code: typeof err.code === "number" ? err.code : 1,
|
|
3162
|
+
stdout: err.stdout ?? "",
|
|
3163
|
+
stderr: err.stderr ?? String(e)
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
async function must(dir, args, env = {}) {
|
|
3168
|
+
const r = await run(dir, args, env);
|
|
3169
|
+
if (r.code !== 0) {
|
|
3170
|
+
const first = r.stderr.split("\n").find((l) => l.trim() !== "") ?? "unknown error";
|
|
3171
|
+
throw new Error(`git ${args[0] ?? ""} failed: ${first.trim()}`);
|
|
3172
|
+
}
|
|
3173
|
+
return r.stdout.trim();
|
|
3174
|
+
}
|
|
3175
|
+
function scaffoldMessage(subject, version) {
|
|
3176
|
+
return `${subject}
|
|
3177
|
+
|
|
3178
|
+
${SCAFFOLD_TRAILER}: ${version}`;
|
|
3179
|
+
}
|
|
3180
|
+
async function findScaffoldBase(rnessDir) {
|
|
3181
|
+
const log = await run(rnessDir, [
|
|
3182
|
+
"log",
|
|
3183
|
+
"--topo-order",
|
|
3184
|
+
`--format=%H%x1f%P%x1f%s%x1f%(trailers:key=${SCAFFOLD_TRAILER},valueonly,separator=%x2C)%x1e`,
|
|
3185
|
+
"HEAD"
|
|
3186
|
+
]);
|
|
3187
|
+
if (log.code !== 0) return null;
|
|
3188
|
+
const commits = log.stdout.split("").map((r) => r.trim()).filter((r) => r !== "").map((r) => {
|
|
3189
|
+
const [hash = "", parents = "", subject = "", trailer = ""] = r.split("");
|
|
3190
|
+
return { hash, parents: parents.trim(), subject, trailer: trailer.trim() };
|
|
3191
|
+
});
|
|
3192
|
+
for (const c of commits) {
|
|
3193
|
+
const m = SCAFFOLD_SUBJECT.exec(c.subject);
|
|
3194
|
+
if (m !== null) return { commit: c.hash, version: m[1] ?? null };
|
|
3195
|
+
}
|
|
3196
|
+
const root = commits.find(
|
|
3197
|
+
(c) => c.parents === "" && c.subject === CREATE_SUBJECT
|
|
3198
|
+
);
|
|
3199
|
+
if (root === void 0) return null;
|
|
3200
|
+
return {
|
|
3201
|
+
commit: root.hash,
|
|
3202
|
+
version: root.trailer === "" ? null : root.trailer
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
3206
|
+
const scratch = await mkdtemp(join16(tmpdir(), "rness-scaffold-"));
|
|
3207
|
+
const env = { GIT_INDEX_FILE: join16(scratch, "index") };
|
|
3208
|
+
try {
|
|
3209
|
+
await must(
|
|
3210
|
+
rnessDir,
|
|
3211
|
+
["read-tree", ...base === null ? ["--empty"] : [base]],
|
|
3212
|
+
env
|
|
3213
|
+
);
|
|
3214
|
+
for (const [i, file] of files.entries()) {
|
|
3215
|
+
const tmp = join16(scratch, `blob-${i}`);
|
|
3216
|
+
await writeFile3(tmp, file.content, "utf8");
|
|
3217
|
+
const blob = await must(rnessDir, [
|
|
3218
|
+
"hash-object",
|
|
3219
|
+
"-w",
|
|
3220
|
+
"--no-filters",
|
|
3221
|
+
"--",
|
|
3222
|
+
tmp
|
|
3223
|
+
]);
|
|
3224
|
+
await must(
|
|
3225
|
+
rnessDir,
|
|
3226
|
+
[
|
|
3227
|
+
"update-index",
|
|
3228
|
+
"--add",
|
|
3229
|
+
"--cacheinfo",
|
|
3230
|
+
`${file.executable ? "100755" : "100644"},${blob},${file.path}`
|
|
3231
|
+
],
|
|
3232
|
+
env
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
const tree = await must(rnessDir, ["write-tree"], env);
|
|
3236
|
+
return await must(rnessDir, [
|
|
3237
|
+
"commit-tree",
|
|
3238
|
+
tree,
|
|
3239
|
+
...base === null ? [] : ["-p", base],
|
|
3240
|
+
"-m",
|
|
3241
|
+
scaffoldMessage(`chore: rness scaffold ${version}`, version)
|
|
3242
|
+
]);
|
|
3243
|
+
} finally {
|
|
3244
|
+
await rm4(scratch, { recursive: true, force: true });
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
async function blobAt(rnessDir, rev, path) {
|
|
3248
|
+
const r = await run(rnessDir, [
|
|
3249
|
+
"rev-parse",
|
|
3250
|
+
"--verify",
|
|
3251
|
+
"--quiet",
|
|
3252
|
+
`${rev}:${path}`
|
|
3253
|
+
]);
|
|
3254
|
+
return r.code === 0 ? r.stdout.trim() : null;
|
|
3255
|
+
}
|
|
3256
|
+
async function mergeScaffold(rnessDir, commit, base) {
|
|
3257
|
+
await run(rnessDir, [
|
|
3258
|
+
"merge",
|
|
3259
|
+
"--no-commit",
|
|
3260
|
+
"--no-ff",
|
|
3261
|
+
...base === null ? ["--allow-unrelated-histories"] : [],
|
|
3262
|
+
commit
|
|
3263
|
+
]);
|
|
3264
|
+
const conflicts = (await must(rnessDir, ["diff", "--name-only", "--diff-filter=U"])).split("\n").filter((l) => l !== "");
|
|
3265
|
+
const changes = [];
|
|
3266
|
+
const status2 = await must(rnessDir, [
|
|
3267
|
+
"diff",
|
|
3268
|
+
"--cached",
|
|
3269
|
+
"--name-status",
|
|
3270
|
+
"HEAD"
|
|
3271
|
+
]);
|
|
3272
|
+
for (const line of status2.split("\n")) {
|
|
3273
|
+
const [letter = "", path = ""] = line.split(" ");
|
|
3274
|
+
if (path === "" || conflicts.includes(path)) continue;
|
|
3275
|
+
if (letter === "A") changes.push(["added", path]);
|
|
3276
|
+
else if (letter === "D") changes.push(["removed", path]);
|
|
3277
|
+
else if (letter === "M") {
|
|
3278
|
+
const untouched = base !== null && await blobAt(rnessDir, "HEAD", path) === await blobAt(rnessDir, base, path);
|
|
3279
|
+
changes.push([untouched ? "updated" : "merged", path]);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return { conflicts, changes };
|
|
3283
|
+
}
|
|
3284
|
+
async function isShallow(rnessDir) {
|
|
3285
|
+
const r = await run(rnessDir, ["rev-parse", "--is-shallow-repository"]);
|
|
3286
|
+
return r.code === 0 && r.stdout.trim() === "true";
|
|
3287
|
+
}
|
|
3288
|
+
async function isRepository2(rnessDir) {
|
|
3289
|
+
const r = await run(rnessDir, ["rev-parse", "--show-toplevel"]);
|
|
3290
|
+
if (r.code !== 0) return false;
|
|
3291
|
+
const real = (p) => realpath2(p).catch(() => resolve3(p));
|
|
3292
|
+
return await real(r.stdout.trim()) === await real(rnessDir);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
2831
3295
|
// src/core/new-workspace.ts
|
|
2832
3296
|
async function isEmptyDir(dir) {
|
|
2833
3297
|
return (await readdir2(dir)).length === 0;
|
|
@@ -2842,11 +3306,11 @@ async function insideWorkspace(dir) {
|
|
|
2842
3306
|
}
|
|
2843
3307
|
async function targetProblem(cwd, root, pm) {
|
|
2844
3308
|
const shown = relative2(cwd, root) || ".";
|
|
2845
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(
|
|
3309
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname8(root)))
|
|
2846
3310
|
return "already inside an rness workspace; create runs from outside one";
|
|
2847
3311
|
if (!await exists(root)) return null;
|
|
2848
|
-
if (await exists(
|
|
2849
|
-
if (!await exists(
|
|
3312
|
+
if (await exists(join17(root, ".rness", "rness.json"))) {
|
|
3313
|
+
if (!await exists(join17(root, ".rness", "node_modules")))
|
|
2850
3314
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
2851
3315
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
2852
3316
|
}
|
|
@@ -2869,9 +3333,9 @@ async function installContext(input) {
|
|
|
2869
3333
|
}
|
|
2870
3334
|
async function buildContext(input) {
|
|
2871
3335
|
const { ui, root, shown, pm } = input;
|
|
2872
|
-
const rnessDir =
|
|
3336
|
+
const rnessDir = join17(root, ".rness");
|
|
2873
3337
|
const pmVersion = await packageManagerVersion(pm);
|
|
2874
|
-
await
|
|
3338
|
+
await mkdir4(root, { recursive: true });
|
|
2875
3339
|
try {
|
|
2876
3340
|
await copyScaffold(rnessDir, {
|
|
2877
3341
|
version: VERSION,
|
|
@@ -2883,9 +3347,9 @@ async function buildContext(input) {
|
|
|
2883
3347
|
return true;
|
|
2884
3348
|
} catch (e) {
|
|
2885
3349
|
reportError(e);
|
|
2886
|
-
await
|
|
3350
|
+
await rm5(rnessDir, { recursive: true, force: true }).catch(() => void 0);
|
|
2887
3351
|
if (await isEmptyDir(root).catch(() => false))
|
|
2888
|
-
await
|
|
3352
|
+
await rm5(root, { recursive: true, force: true }).catch(() => void 0);
|
|
2889
3353
|
process.stderr.write(
|
|
2890
3354
|
`removed ${shown}/.rness after the failure; fix it and retry
|
|
2891
3355
|
`
|
|
@@ -2897,7 +3361,7 @@ async function commitContext(input) {
|
|
|
2897
3361
|
const { ui, rnessDir, shown } = input;
|
|
2898
3362
|
await init(rnessDir);
|
|
2899
3363
|
try {
|
|
2900
|
-
await commitAll(rnessDir,
|
|
3364
|
+
await commitAll(rnessDir, scaffoldMessage(CREATE_SUBJECT, VERSION));
|
|
2901
3365
|
ui.line("committed", `${shown}/.rness`);
|
|
2902
3366
|
} catch (e) {
|
|
2903
3367
|
ui.warn(
|
|
@@ -2937,8 +3401,8 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
2937
3401
|
}
|
|
2938
3402
|
|
|
2939
3403
|
// src/commands/create-blank.ts
|
|
2940
|
-
import { mkdir as
|
|
2941
|
-
import { join as
|
|
3404
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
3405
|
+
import { join as join18, relative as relative3, resolve as resolve4 } from "path";
|
|
2942
3406
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
2943
3407
|
function workspaceNameError(name) {
|
|
2944
3408
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -2970,7 +3434,7 @@ async function createBlank(input) {
|
|
|
2970
3434
|
name = answer.trim();
|
|
2971
3435
|
prompted = true;
|
|
2972
3436
|
}
|
|
2973
|
-
const root =
|
|
3437
|
+
const root = resolve4(cwd, name);
|
|
2974
3438
|
const shown = relative3(cwd, root) || ".";
|
|
2975
3439
|
const problem = await targetProblem(cwd, root, pm);
|
|
2976
3440
|
if (problem !== null) {
|
|
@@ -2984,6 +3448,14 @@ async function createBlank(input) {
|
|
|
2984
3448
|
);
|
|
2985
3449
|
return 2;
|
|
2986
3450
|
}
|
|
3451
|
+
let agents = input.agents.length > 0 ? [...input.agents] : null;
|
|
3452
|
+
if (agents === null && input.interactive) {
|
|
3453
|
+
agents = await askAgents(await input.prompts());
|
|
3454
|
+
if (agents === null) {
|
|
3455
|
+
ui.cancelled();
|
|
3456
|
+
return 0;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
2987
3459
|
if (input.interactive && !prompted) {
|
|
2988
3460
|
const p = await input.prompts();
|
|
2989
3461
|
const ok = await p.confirm({
|
|
@@ -2998,14 +3470,14 @@ async function createBlank(input) {
|
|
|
2998
3470
|
ui,
|
|
2999
3471
|
root,
|
|
3000
3472
|
shown,
|
|
3001
|
-
manifest: { contract: 1, org: null, repos: {}, scopes: {} },
|
|
3473
|
+
manifest: { contract: 1, org: null, agents, repos: {}, scopes: {} },
|
|
3002
3474
|
pm,
|
|
3003
3475
|
skipInstall: input.skipInstall,
|
|
3004
3476
|
kind: "blank workspace"
|
|
3005
3477
|
});
|
|
3006
3478
|
if (!built) return 1;
|
|
3007
|
-
await
|
|
3008
|
-
await commitContext({ ui, rnessDir:
|
|
3479
|
+
await mkdir5(join18(root, "org"), { recursive: true });
|
|
3480
|
+
await commitContext({ ui, rnessDir: join18(root, ".rness"), shown });
|
|
3009
3481
|
const code = await syncBlocks(
|
|
3010
3482
|
ui,
|
|
3011
3483
|
root,
|
|
@@ -3165,7 +3637,7 @@ async function cloneSelection(input) {
|
|
|
3165
3637
|
{ doing: `cloning org/${repo}`, git: true },
|
|
3166
3638
|
() => clone(
|
|
3167
3639
|
entry.url,
|
|
3168
|
-
|
|
3640
|
+
join19(input.root, "org", repo),
|
|
3169
3641
|
input.provider.credentialsFor(entry.url)
|
|
3170
3642
|
),
|
|
3171
3643
|
() => ["cloned", `org/${repo}`]
|
|
@@ -3296,6 +3768,15 @@ async function createCommand(opts, deps = {}) {
|
|
|
3296
3768
|
`);
|
|
3297
3769
|
return 2;
|
|
3298
3770
|
}
|
|
3771
|
+
const agentFlags = [...new Set(opts.agent ?? [])];
|
|
3772
|
+
const unknownAgent = unsupportedAgents(agentFlags)[0];
|
|
3773
|
+
if (unknownAgent !== void 0) {
|
|
3774
|
+
process.stderr.write(
|
|
3775
|
+
`unknown agent "${unknownAgent}" (supported: ${SUPPORTED_AGENTS.join(", ")})
|
|
3776
|
+
`
|
|
3777
|
+
);
|
|
3778
|
+
return 2;
|
|
3779
|
+
}
|
|
3299
3780
|
if (opts.ssh === true && opts.https === true) {
|
|
3300
3781
|
process.stderr.write("--ssh and --https cannot be combined\n");
|
|
3301
3782
|
return 2;
|
|
@@ -3369,6 +3850,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3369
3850
|
if (blank)
|
|
3370
3851
|
return await createBlank({
|
|
3371
3852
|
name: opts.name,
|
|
3853
|
+
agents: agentFlags,
|
|
3372
3854
|
cwd,
|
|
3373
3855
|
pm,
|
|
3374
3856
|
interactive,
|
|
@@ -3424,7 +3906,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3424
3906
|
org = answer.trim();
|
|
3425
3907
|
prompted = true;
|
|
3426
3908
|
}
|
|
3427
|
-
const root =
|
|
3909
|
+
const root = resolve5(cwd, org);
|
|
3428
3910
|
const shown = relative4(cwd, root) || ".";
|
|
3429
3911
|
const problem = await targetProblem(cwd, root, pm);
|
|
3430
3912
|
if (problem !== null) {
|
|
@@ -3452,6 +3934,13 @@ async function createCommand(opts, deps = {}) {
|
|
|
3452
3934
|
return 1;
|
|
3453
3935
|
}
|
|
3454
3936
|
const joining = probe.kind === "found";
|
|
3937
|
+
if (joining && agentFlags.length > 0) {
|
|
3938
|
+
process.stderr.write(
|
|
3939
|
+
`${org}/.rness exists: --agent only applies to a new workspace; after joining, run ${rnessCommand()} sync${agentFlags.map((a) => ` --agent ${a}`).join("")}
|
|
3940
|
+
`
|
|
3941
|
+
);
|
|
3942
|
+
return 2;
|
|
3943
|
+
}
|
|
3455
3944
|
if (joining)
|
|
3456
3945
|
ui.line(
|
|
3457
3946
|
"found",
|
|
@@ -3464,10 +3953,16 @@ async function createCommand(opts, deps = {}) {
|
|
|
3464
3953
|
`${org}/.rness (or not visible to you) \u2014 starting a new workspace`,
|
|
3465
3954
|
`${org} has no workspace yet (or none you can see) \u2014 starting one`
|
|
3466
3955
|
);
|
|
3467
|
-
let catalogue = {
|
|
3956
|
+
let catalogue = {
|
|
3957
|
+
contract: 1,
|
|
3958
|
+
org,
|
|
3959
|
+
agents: null,
|
|
3960
|
+
repos: {},
|
|
3961
|
+
scopes: {}
|
|
3962
|
+
};
|
|
3468
3963
|
const staged = async () => {
|
|
3469
|
-
staging ??= await
|
|
3470
|
-
return
|
|
3964
|
+
staging ??= await mkdtemp2(join19(tmpdir2(), "rness-join-"));
|
|
3965
|
+
return join19(staging, ".rness");
|
|
3471
3966
|
};
|
|
3472
3967
|
if (joining) {
|
|
3473
3968
|
const stagedDir = await staged();
|
|
@@ -3510,6 +4005,14 @@ async function createCommand(opts, deps = {}) {
|
|
|
3510
4005
|
} else {
|
|
3511
4006
|
specs = Object.keys(catalogue.repos);
|
|
3512
4007
|
}
|
|
4008
|
+
if (!joining) {
|
|
4009
|
+
if (agentFlags.length > 0) catalogue.agents = agentFlags;
|
|
4010
|
+
else if (interactive) {
|
|
4011
|
+
const answer = await askAgents(await prompts());
|
|
4012
|
+
if (answer === null) return cancelled(ui);
|
|
4013
|
+
catalogue.agents = answer;
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3513
4016
|
if (interactive && !prompted) {
|
|
3514
4017
|
const p = await prompts();
|
|
3515
4018
|
const ok = await p.confirm({
|
|
@@ -3517,10 +4020,10 @@ async function createCommand(opts, deps = {}) {
|
|
|
3517
4020
|
});
|
|
3518
4021
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
3519
4022
|
}
|
|
3520
|
-
const rnessDir =
|
|
4023
|
+
const rnessDir = join19(root, ".rness");
|
|
3521
4024
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
3522
4025
|
if (joining) {
|
|
3523
|
-
await
|
|
4026
|
+
await mkdir6(root, { recursive: true });
|
|
3524
4027
|
await placeContext(
|
|
3525
4028
|
await staged(),
|
|
3526
4029
|
rnessDir,
|
|
@@ -3530,7 +4033,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3530
4033
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
3531
4034
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
3532
4035
|
await install();
|
|
3533
|
-
await
|
|
4036
|
+
await mkdir6(join19(root, "org"), { recursive: true });
|
|
3534
4037
|
const failures2 = await cloneSelection({
|
|
3535
4038
|
root,
|
|
3536
4039
|
rnessDir,
|
|
@@ -3583,7 +4086,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3583
4086
|
kind: "new workspace"
|
|
3584
4087
|
});
|
|
3585
4088
|
if (!built) return 1;
|
|
3586
|
-
await
|
|
4089
|
+
await mkdir6(join19(root, "org"), { recursive: true });
|
|
3587
4090
|
const failures = await cloneSelection({
|
|
3588
4091
|
root,
|
|
3589
4092
|
rnessDir,
|
|
@@ -3650,7 +4153,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3650
4153
|
return reportError(e);
|
|
3651
4154
|
} finally {
|
|
3652
4155
|
if (staging !== void 0)
|
|
3653
|
-
await
|
|
4156
|
+
await rm6(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
3654
4157
|
}
|
|
3655
4158
|
}
|
|
3656
4159
|
|
|
@@ -3683,17 +4186,16 @@ async function logoutCommand() {
|
|
|
3683
4186
|
}
|
|
3684
4187
|
|
|
3685
4188
|
// src/commands/upgrade.ts
|
|
3686
|
-
import { execFile as
|
|
3687
|
-
import {
|
|
3688
|
-
import {
|
|
3689
|
-
import {
|
|
3690
|
-
|
|
4189
|
+
import { execFile as execFile6 } from "child_process";
|
|
4190
|
+
import { mkdtemp as mkdtemp3, readFile as readFile9, rm as rm7 } from "fs/promises";
|
|
4191
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
4192
|
+
import { basename as basename5, join as join20, relative as relative5 } from "path";
|
|
4193
|
+
import { promisify as promisify6 } from "util";
|
|
4194
|
+
var execFileP6 = promisify6(execFile6);
|
|
3691
4195
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
3692
|
-
var OLD_WORKFLOW_RUN = /npx --yes @rness\/cli@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?) validate/;
|
|
3693
|
-
var WORKFLOW_RUN = `npx --yes "@rness/cli@$(node -p "require('./package.json').devDependencies['@rness/cli']")" validate`;
|
|
3694
4196
|
async function latestVersion() {
|
|
3695
4197
|
try {
|
|
3696
|
-
const { stdout } = await
|
|
4198
|
+
const { stdout } = await execFileP6(
|
|
3697
4199
|
"npm",
|
|
3698
4200
|
["view", "@rness/cli@latest", "version"],
|
|
3699
4201
|
{ timeout: 3e4 }
|
|
@@ -3704,7 +4206,70 @@ async function latestVersion() {
|
|
|
3704
4206
|
return null;
|
|
3705
4207
|
}
|
|
3706
4208
|
}
|
|
3707
|
-
async function
|
|
4209
|
+
async function scaffoldFor(version, rnessDir) {
|
|
4210
|
+
if (version === VERSION) return { dir: scaffoldDir() };
|
|
4211
|
+
const installed = await readPinnedCli(rnessDir);
|
|
4212
|
+
const inside = installed === null ? null : join20(installed.dir, "scaffold");
|
|
4213
|
+
if (installed?.version === version && inside !== null && await exists(join20(inside, "package.json")))
|
|
4214
|
+
return { dir: inside };
|
|
4215
|
+
const scratch = await mkdtemp3(join20(tmpdir3(), "rness-pack-"));
|
|
4216
|
+
const cleanup = () => rm7(scratch, { recursive: true, force: true });
|
|
4217
|
+
try {
|
|
4218
|
+
const { stdout } = await execFileP6(
|
|
4219
|
+
"npm",
|
|
4220
|
+
[
|
|
4221
|
+
"pack",
|
|
4222
|
+
`@rness/cli@${version}`,
|
|
4223
|
+
"--pack-destination",
|
|
4224
|
+
scratch,
|
|
4225
|
+
"--silent"
|
|
4226
|
+
],
|
|
4227
|
+
{ timeout: 6e4 }
|
|
4228
|
+
);
|
|
4229
|
+
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
4230
|
+
await execFileP6("tar", ["-xzf", join20(scratch, tarball), "-C", scratch]);
|
|
4231
|
+
return { dir: join20(scratch, "package", "scaffold"), cleanup };
|
|
4232
|
+
} catch (e) {
|
|
4233
|
+
await cleanup();
|
|
4234
|
+
throw new Error(
|
|
4235
|
+
`cannot get the @rness/cli ${version} scaffold (npm pack): ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`,
|
|
4236
|
+
{ cause: e }
|
|
4237
|
+
);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
async function declaredManager(rnessDir, pm) {
|
|
4241
|
+
try {
|
|
4242
|
+
const pkg = JSON.parse(
|
|
4243
|
+
await readFile9(join20(rnessDir, "package.json"), "utf8")
|
|
4244
|
+
);
|
|
4245
|
+
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
4246
|
+
} catch {
|
|
4247
|
+
}
|
|
4248
|
+
return `${pm}@${await packageManagerVersion(pm)}`;
|
|
4249
|
+
}
|
|
4250
|
+
function finish(ui, cwd, rnessDir, label, target) {
|
|
4251
|
+
const rel = relative5(cwd, rnessDir) || ".";
|
|
4252
|
+
const next = [
|
|
4253
|
+
`git -C ${rel} add -A && git -C ${rel} commit -m "chore: rness ${target}"`,
|
|
4254
|
+
"# teammates: git pull \u2014 the next rness command installs it"
|
|
4255
|
+
];
|
|
4256
|
+
if (ui.session) {
|
|
4257
|
+
ui.note("Next steps", next);
|
|
4258
|
+
ui.outro(`${label} runs @rness/cli ${target}`);
|
|
4259
|
+
} else
|
|
4260
|
+
process.stdout.write(
|
|
4261
|
+
[
|
|
4262
|
+
"",
|
|
4263
|
+
status("upgraded", `${label} to @rness/cli ${target}`),
|
|
4264
|
+
"",
|
|
4265
|
+
"Next:",
|
|
4266
|
+
...next.map((line) => ` ${line}`),
|
|
4267
|
+
""
|
|
4268
|
+
].join("\n")
|
|
4269
|
+
);
|
|
4270
|
+
return 0;
|
|
4271
|
+
}
|
|
4272
|
+
async function upgradeCommand(version, opts, terminal = defaultTerminal, deps = {}) {
|
|
3708
4273
|
const cwd = opts.cwd ?? process.cwd();
|
|
3709
4274
|
if (version !== void 0 && !EXACT_VERSION.test(version)) {
|
|
3710
4275
|
process.stderr.write(
|
|
@@ -3746,7 +4311,21 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3746
4311
|
return 1;
|
|
3747
4312
|
}
|
|
3748
4313
|
const installed = (await readPinnedCli(ws.rnessDir))?.version ?? null;
|
|
3749
|
-
if (
|
|
4314
|
+
if (!await isRepository2(ws.rnessDir)) {
|
|
4315
|
+
process.stderr.write(`${label} is not a git repository
|
|
4316
|
+
`);
|
|
4317
|
+
return 1;
|
|
4318
|
+
}
|
|
4319
|
+
if (!await isClean(ws.rnessDir)) {
|
|
4320
|
+
process.stderr.write(
|
|
4321
|
+
`${label} has uncommitted changes; commit or stash them, then ${rnessCommand()} upgrade
|
|
4322
|
+
`
|
|
4323
|
+
);
|
|
4324
|
+
return 1;
|
|
4325
|
+
}
|
|
4326
|
+
const base = await findScaffoldBase(ws.rnessDir);
|
|
4327
|
+
const scaffoldCurrent = base !== null && base.version === target;
|
|
4328
|
+
if (pin.spec === target && installed === target && scaffoldCurrent) {
|
|
3750
4329
|
if (ui.session) ui.outro(`Already at ${target}`);
|
|
3751
4330
|
else process.stdout.write(`already at ${target}
|
|
3752
4331
|
`);
|
|
@@ -3761,7 +4340,9 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3761
4340
|
return 1;
|
|
3762
4341
|
}
|
|
3763
4342
|
const pm = await workspacePackageManager(ws.rnessDir);
|
|
3764
|
-
if (pin.spec === target)
|
|
4343
|
+
if (pin.spec === target && installed === target)
|
|
4344
|
+
ui.line("scaffold", `@rness/cli ${target} \u2014 merging it into ${label}`);
|
|
4345
|
+
else if (pin.spec === target)
|
|
3765
4346
|
ui.line(
|
|
3766
4347
|
"install",
|
|
3767
4348
|
`@rness/cli ${target} in ${label} (${pm}) \u2014 ${installed ?? "nothing"} is installed`
|
|
@@ -3783,46 +4364,58 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3783
4364
|
return 1;
|
|
3784
4365
|
}
|
|
3785
4366
|
}
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
4367
|
+
let merged = false;
|
|
4368
|
+
if (!scaffoldCurrent) {
|
|
4369
|
+
const source = await (deps.scaffoldFor ?? scaffoldFor)(
|
|
4370
|
+
target,
|
|
4371
|
+
ws.rnessDir
|
|
4372
|
+
);
|
|
4373
|
+
try {
|
|
4374
|
+
const files = await renderScaffold(source.dir, {
|
|
4375
|
+
version: target,
|
|
4376
|
+
packageManager: await declaredManager(ws.rnessDir, pm)
|
|
4377
|
+
});
|
|
4378
|
+
if (base === null)
|
|
4379
|
+
ui.line(
|
|
4380
|
+
"adopting",
|
|
4381
|
+
`the @rness/cli ${target} scaffold (no scaffold commit in ${label} yet)`
|
|
4382
|
+
);
|
|
4383
|
+
else ui.line("merging", `the @rness/cli ${target} scaffold`);
|
|
4384
|
+
const commit = await buildScaffoldCommit(
|
|
4385
|
+
ws.rnessDir,
|
|
4386
|
+
base?.commit ?? null,
|
|
4387
|
+
files,
|
|
4388
|
+
target
|
|
3803
4389
|
);
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
4390
|
+
const result = await mergeScaffold(
|
|
4391
|
+
ws.rnessDir,
|
|
4392
|
+
commit,
|
|
4393
|
+
base?.commit ?? null
|
|
3807
4394
|
);
|
|
3808
|
-
|
|
3809
|
-
|
|
4395
|
+
for (const [change, path] of result.changes) ui.line(change, path);
|
|
4396
|
+
if (result.conflicts.length > 0) {
|
|
4397
|
+
for (const path of result.conflicts) ui.error(`conflict ${path}`);
|
|
4398
|
+
const rel = relative5(cwd, ws.rnessDir) || ".";
|
|
4399
|
+
ui.hint(
|
|
4400
|
+
`resolve them in ${rel} (git checkout --ours <file> keeps yours, --theirs takes the scaffold's, or edit), git add, git commit, then ${rnessCommand()} upgrade again`,
|
|
4401
|
+
"stderr"
|
|
4402
|
+
);
|
|
4403
|
+
return 1;
|
|
4404
|
+
}
|
|
4405
|
+
merged = true;
|
|
4406
|
+
} finally {
|
|
4407
|
+
await source.cleanup?.();
|
|
3810
4408
|
}
|
|
3811
4409
|
}
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
);
|
|
3821
|
-
ui.line(
|
|
3822
|
-
"wrote",
|
|
3823
|
-
".rness/.github/dependabot.yml (@rness/cli now moves by pull request)"
|
|
3824
|
-
);
|
|
3825
|
-
}
|
|
4410
|
+
const packageFile = join20(ws.rnessDir, "package.json");
|
|
4411
|
+
const restore = /* @__PURE__ */ new Map([
|
|
4412
|
+
[packageFile, await readFile9(packageFile, "utf8")]
|
|
4413
|
+
]);
|
|
4414
|
+
const moved = await readPin(ws.rnessDir);
|
|
4415
|
+
if (moved !== null && moved.spec !== target)
|
|
4416
|
+
await writePin(ws.rnessDir, moved.spec, target);
|
|
4417
|
+
if (pin.spec === target && installed === target)
|
|
4418
|
+
return finish(ui, cwd, ws.rnessDir, label, target);
|
|
3826
4419
|
try {
|
|
3827
4420
|
await ui.step(
|
|
3828
4421
|
{ doing: `installing dependencies with ${pm}` },
|
|
@@ -3830,10 +4423,20 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3830
4423
|
() => ["installed", `dependencies with ${pm}`]
|
|
3831
4424
|
);
|
|
3832
4425
|
} catch (e) {
|
|
3833
|
-
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
3834
4426
|
const message = e instanceof Error ? e.message : String(e);
|
|
3835
4427
|
ui.error(message);
|
|
3836
|
-
|
|
4428
|
+
if (!merged) {
|
|
4429
|
+
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
4430
|
+
ui.hint(
|
|
4431
|
+
`restored ${relative5(cwd, packageFile) || packageFile}`,
|
|
4432
|
+
"stderr"
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
4435
|
+
if (merged)
|
|
4436
|
+
ui.hint(
|
|
4437
|
+
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
4438
|
+
"stderr"
|
|
4439
|
+
);
|
|
3837
4440
|
if (/minimum.?release.?age|NO_MATURE/i.test(message))
|
|
3838
4441
|
ui.hint(
|
|
3839
4442
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
@@ -3852,33 +4455,14 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3852
4455
|
ui.gitIsSilent = true;
|
|
3853
4456
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
3854
4457
|
if (code !== 0) return code;
|
|
3855
|
-
|
|
3856
|
-
const next = [
|
|
3857
|
-
`git -C ${rel} add -A && git -C ${rel} commit -m "chore: rness ${target}"`,
|
|
3858
|
-
"# teammates: git pull \u2014 the next rness command installs it"
|
|
3859
|
-
];
|
|
3860
|
-
if (ui.session) {
|
|
3861
|
-
ui.note("Next steps", next);
|
|
3862
|
-
ui.outro(`${label} runs @rness/cli ${target}`);
|
|
3863
|
-
} else
|
|
3864
|
-
process.stdout.write(
|
|
3865
|
-
[
|
|
3866
|
-
"",
|
|
3867
|
-
status("upgraded", `${label} to @rness/cli ${target}`),
|
|
3868
|
-
"",
|
|
3869
|
-
"Next:",
|
|
3870
|
-
...next.map((line) => ` ${line}`),
|
|
3871
|
-
""
|
|
3872
|
-
].join("\n")
|
|
3873
|
-
);
|
|
3874
|
-
return 0;
|
|
4458
|
+
return finish(ui, cwd, ws.rnessDir, label, target);
|
|
3875
4459
|
} catch (e) {
|
|
3876
4460
|
return reportError(e);
|
|
3877
4461
|
}
|
|
3878
4462
|
}
|
|
3879
4463
|
|
|
3880
4464
|
// src/core/blocks.ts
|
|
3881
|
-
import { join as
|
|
4465
|
+
import { join as join21 } from "path";
|
|
3882
4466
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
3883
4467
|
async function checkBlocks({
|
|
3884
4468
|
root,
|
|
@@ -3895,7 +4479,7 @@ async function checkBlocks({
|
|
|
3895
4479
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
3896
4480
|
checks
|
|
3897
4481
|
});
|
|
3898
|
-
if (!await exists(
|
|
4482
|
+
if (!await exists(join21(root, "org"))) return derive();
|
|
3899
4483
|
const targets = [
|
|
3900
4484
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
3901
4485
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -3904,7 +4488,7 @@ async function checkBlocks({
|
|
|
3904
4488
|
const segments = entry.path.split("/");
|
|
3905
4489
|
targets.push({
|
|
3906
4490
|
scope: name,
|
|
3907
|
-
dir:
|
|
4491
|
+
dir: join21(root, ...segments),
|
|
3908
4492
|
depth: segments.length,
|
|
3909
4493
|
file: "AGENTS.md",
|
|
3910
4494
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -3919,7 +4503,7 @@ async function checkBlocks({
|
|
|
3919
4503
|
);
|
|
3920
4504
|
continue;
|
|
3921
4505
|
}
|
|
3922
|
-
const text = await readOrNull(
|
|
4506
|
+
const text = await readOrNull(join21(t.dir, t.file));
|
|
3923
4507
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
3924
4508
|
const found = findBlock(lines);
|
|
3925
4509
|
if (found.kind === "none") {
|
|
@@ -3959,7 +4543,7 @@ async function checkBlocks({
|
|
|
3959
4543
|
}
|
|
3960
4544
|
|
|
3961
4545
|
// src/core/contract.ts
|
|
3962
|
-
import { join as
|
|
4546
|
+
import { join as join22 } from "path";
|
|
3963
4547
|
var STATUSES = {
|
|
3964
4548
|
adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
|
|
3965
4549
|
specs: [
|
|
@@ -3978,7 +4562,7 @@ async function checkContract(rnessDir) {
|
|
|
3978
4562
|
const problems = [];
|
|
3979
4563
|
for (const name of COLLECTIONS) {
|
|
3980
4564
|
const allowed = STATUSES[name];
|
|
3981
|
-
const items = await collectMarkdown(
|
|
4565
|
+
const items = await collectMarkdown(join22(rnessDir, name));
|
|
3982
4566
|
for (const item of items) {
|
|
3983
4567
|
const where = `${name}/${item.rel}`;
|
|
3984
4568
|
if (SKIP.has(where)) continue;
|
|
@@ -4030,11 +4614,35 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4030
4614
|
org
|
|
4031
4615
|
});
|
|
4032
4616
|
problems.push(...blocks.problems);
|
|
4617
|
+
for (const name of unsupportedAgents(manifest.agents ?? []))
|
|
4618
|
+
problems.push(unsupportedMessage(name));
|
|
4619
|
+
const targets = await agentTargets(ws.root, manifest, { check: true });
|
|
4620
|
+
for (const o of targets)
|
|
4621
|
+
if (o.status === "stale")
|
|
4622
|
+
problems.push(
|
|
4623
|
+
`${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
|
|
4624
|
+
);
|
|
4625
|
+
else if (o.status === "invalid")
|
|
4626
|
+
problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
|
|
4627
|
+
const scaffoldWarnings = [];
|
|
4628
|
+
if (await isRepository2(ws.rnessDir) && !await isShallow(ws.rnessDir)) {
|
|
4629
|
+
const pin = await readPin(ws.rnessDir);
|
|
4630
|
+
const base = await findScaffoldBase(ws.rnessDir);
|
|
4631
|
+
if (base === null || base.version === null)
|
|
4632
|
+
scaffoldWarnings.push(
|
|
4633
|
+
`the scaffold is not tracked in .rness yet: run ${rnessCommand()} upgrade`
|
|
4634
|
+
);
|
|
4635
|
+
else if (pin !== null && EXACT_VERSION.test(pin.spec) && pin.spec !== base.version)
|
|
4636
|
+
scaffoldWarnings.push(
|
|
4637
|
+
`the @rness/cli ${pin.spec} scaffold is not merged here: run ${rnessCommand()} upgrade`
|
|
4638
|
+
);
|
|
4639
|
+
}
|
|
4033
4640
|
const ui = await makeUi(deps.terminal ?? defaultTerminal);
|
|
4034
4641
|
if (!ui.session) {
|
|
4035
4642
|
for (const p of problems) process.stderr.write(`${p}
|
|
4036
4643
|
`);
|
|
4037
|
-
for (const w of blocks.warnings
|
|
4644
|
+
for (const w of [...blocks.warnings, ...scaffoldWarnings])
|
|
4645
|
+
process.stderr.write(`${warn(w)}
|
|
4038
4646
|
`);
|
|
4039
4647
|
if (problems.length > 0) return 1;
|
|
4040
4648
|
process.stdout.write("context ok\n");
|
|
@@ -4052,7 +4660,10 @@ async function validateCommand(opts, deps = {}) {
|
|
|
4052
4660
|
else if (c.status === "stale" || c.status === "malformed")
|
|
4053
4661
|
ui.error(c.message);
|
|
4054
4662
|
else ui.warn(c.message);
|
|
4663
|
+
for (const o of targets)
|
|
4664
|
+
if (o.status === "unchanged") ui.line("current", o.label);
|
|
4055
4665
|
for (const p of problems) if (!blocks.problems.includes(p)) ui.error(p);
|
|
4666
|
+
for (const w of scaffoldWarnings) ui.warn(w);
|
|
4056
4667
|
ui.outro(
|
|
4057
4668
|
problems.length > 0 ? "Context mismatch" : "Context OK",
|
|
4058
4669
|
problems.length > 0 ? "error" : "done"
|
|
@@ -4126,7 +4737,11 @@ function buildProgram(state) {
|
|
|
4126
4737
|
).option("--scope <name>", "only this scope (the root block is skipped)").option(
|
|
4127
4738
|
"--check",
|
|
4128
4739
|
"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(
|
|
4740
|
+
).option("--pull", "git pull --ff-only in every clean clone").option("--all", "clone every rness.json repository missing from org/").option(
|
|
4741
|
+
"--agent <name>",
|
|
4742
|
+
"declare an agent in rness.json, then write its files (repeatable; supported: claude)",
|
|
4743
|
+
(value, previous = []) => [...previous, value]
|
|
4744
|
+
).option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
4130
4745
|
state.code = await syncCommand(opts);
|
|
4131
4746
|
});
|
|
4132
4747
|
program.command("add").description(
|
|
@@ -4174,6 +4789,10 @@ function buildProgram(state) {
|
|
|
4174
4789
|
).option("--https", "force https://github.com/").option(
|
|
4175
4790
|
"--blank",
|
|
4176
4791
|
"a local workspace with no GitHub organization: .rness/, an empty org/, the root files"
|
|
4792
|
+
).option(
|
|
4793
|
+
"--agent <name>",
|
|
4794
|
+
"a new workspace's agent, declared in rness.json (repeatable; supported: claude)",
|
|
4795
|
+
(value, previous = []) => [...previous, value]
|
|
4177
4796
|
).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
4797
|
if (opts.blank === true) {
|
|
4179
4798
|
state.code = await createCommand(
|
|
@@ -4196,7 +4815,7 @@ function buildProgram(state) {
|
|
|
4196
4815
|
});
|
|
4197
4816
|
return program;
|
|
4198
4817
|
}
|
|
4199
|
-
async function
|
|
4818
|
+
async function run2(argv) {
|
|
4200
4819
|
const state = { code: 0 };
|
|
4201
4820
|
const program = buildProgram(state);
|
|
4202
4821
|
if (argv.length === 0) {
|
|
@@ -4289,6 +4908,7 @@ export {
|
|
|
4289
4908
|
defaultTransport,
|
|
4290
4909
|
findWorkspace,
|
|
4291
4910
|
reportError,
|
|
4911
|
+
VERSION,
|
|
4292
4912
|
BEGIN,
|
|
4293
4913
|
END,
|
|
4294
4914
|
BLOCK_SIZE_WARNING,
|
|
@@ -4310,7 +4930,6 @@ export {
|
|
|
4310
4930
|
addCommand,
|
|
4311
4931
|
renderMarkdown,
|
|
4312
4932
|
contextCommand,
|
|
4313
|
-
VERSION,
|
|
4314
4933
|
findDelegate,
|
|
4315
4934
|
SCAFFOLD_FILES,
|
|
4316
4935
|
scaffoldDir,
|
|
@@ -4323,8 +4942,8 @@ export {
|
|
|
4323
4942
|
STATUSES,
|
|
4324
4943
|
checkContract,
|
|
4325
4944
|
validateCommand,
|
|
4326
|
-
run,
|
|
4945
|
+
run2 as run,
|
|
4327
4946
|
OWN_COMMANDS,
|
|
4328
4947
|
catchUp
|
|
4329
4948
|
};
|
|
4330
|
-
//# sourceMappingURL=chunk-
|
|
4949
|
+
//# sourceMappingURL=chunk-VQMX52HT.js.map
|