@rness/cli 0.18.0 → 0.19.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 +120 -10
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-VGBS7HDY.js → chunk-U5SBNWVD.js} +760 -356
- package/dist/chunk-U5SBNWVD.js.map +1 -0
- package/dist/index.js +1 -1
- package/mod/.claude-plugin/plugin.json +21 -0
- package/mod/hooks/hooks.json +1 -0
- package/mod/hooks/register.tsx +188 -0
- package/mod/types/index.d.ts +36 -0
- package/package.json +5 -1
- package/scaffold/CONVENTIONS.md +5 -0
- package/dist/chunk-VGBS7HDY.js.map +0 -1
|
@@ -186,9 +186,9 @@ async function originUrl(dir) {
|
|
|
186
186
|
async function init(dir) {
|
|
187
187
|
await git(["init", "-q", "-b", "main"], dir);
|
|
188
188
|
}
|
|
189
|
-
async function commitAll(dir,
|
|
189
|
+
async function commitAll(dir, message3) {
|
|
190
190
|
await git(["add", "-A"], dir);
|
|
191
|
-
await git(["commit", "-q", "-m",
|
|
191
|
+
await git(["commit", "-q", "-m", message3], dir);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// src/core/remote.ts
|
|
@@ -296,8 +296,8 @@ var KEYS = [
|
|
|
296
296
|
"repos",
|
|
297
297
|
"scopes"
|
|
298
298
|
];
|
|
299
|
-
function fail(
|
|
300
|
-
throw new Error(`rness.json: ${
|
|
299
|
+
function fail(message3) {
|
|
300
|
+
throw new Error(`rness.json: ${message3}`);
|
|
301
301
|
}
|
|
302
302
|
function isRecord(value) {
|
|
303
303
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -424,6 +424,9 @@ async function loadManifest(rnessDir) {
|
|
|
424
424
|
} catch {
|
|
425
425
|
fail("not found");
|
|
426
426
|
}
|
|
427
|
+
return parseManifest(raw);
|
|
428
|
+
}
|
|
429
|
+
function parseManifest(raw) {
|
|
427
430
|
let data;
|
|
428
431
|
try {
|
|
429
432
|
data = JSON.parse(raw);
|
|
@@ -508,7 +511,7 @@ function parseRepoSpec(spec, org, host) {
|
|
|
508
511
|
}
|
|
509
512
|
|
|
510
513
|
// src/version.ts
|
|
511
|
-
var VERSION = "0.
|
|
514
|
+
var VERSION = "0.19.0";
|
|
512
515
|
|
|
513
516
|
// src/core/pm.ts
|
|
514
517
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -588,8 +591,8 @@ async function installDependencies(pm, dir, opts = {}) {
|
|
|
588
591
|
// src/core/frontmatter.ts
|
|
589
592
|
import { parse } from "yaml";
|
|
590
593
|
var FRONT_MATTER = /^---\r?\n(?:([\s\S]*?)\r?\n)?---(?:\r?\n|$)/;
|
|
591
|
-
function fail2(
|
|
592
|
-
throw new Error(`front matter: ${
|
|
594
|
+
function fail2(message3) {
|
|
595
|
+
throw new Error(`front matter: ${message3}`);
|
|
593
596
|
}
|
|
594
597
|
function stripBom(source) {
|
|
595
598
|
return source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
@@ -628,29 +631,31 @@ import { join as join3, posix, sep } from "path";
|
|
|
628
631
|
function toPosix(rel) {
|
|
629
632
|
return rel.split(sep).join(posix.sep);
|
|
630
633
|
}
|
|
634
|
+
function markdownItem(path, rel, body) {
|
|
635
|
+
let fields2 = null;
|
|
636
|
+
let fieldsError = null;
|
|
637
|
+
try {
|
|
638
|
+
fields2 = parseFrontMatter(body);
|
|
639
|
+
} catch (e) {
|
|
640
|
+
fieldsError = e instanceof Error ? e.message : String(e);
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
path,
|
|
644
|
+
rel,
|
|
645
|
+
fields: fields2,
|
|
646
|
+
fieldsError,
|
|
647
|
+
title: extractTitle(body),
|
|
648
|
+
body,
|
|
649
|
+
content: stripFrontMatter(body)
|
|
650
|
+
};
|
|
651
|
+
}
|
|
631
652
|
async function collectMarkdown(dir) {
|
|
632
653
|
const rels = [];
|
|
633
654
|
for await (const rel of glob("**/*.md", { cwd: dir })) rels.push(toPosix(rel));
|
|
634
655
|
const items = await Promise.all(
|
|
635
656
|
rels.map(async (rel) => {
|
|
636
657
|
const path = join3(dir, ...rel.split(posix.sep));
|
|
637
|
-
|
|
638
|
-
let fields2 = null;
|
|
639
|
-
let fieldsError = null;
|
|
640
|
-
try {
|
|
641
|
-
fields2 = parseFrontMatter(body);
|
|
642
|
-
} catch (e) {
|
|
643
|
-
fieldsError = e instanceof Error ? e.message : String(e);
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
path,
|
|
647
|
-
rel,
|
|
648
|
-
fields: fields2,
|
|
649
|
-
fieldsError,
|
|
650
|
-
title: extractTitle(body),
|
|
651
|
-
body,
|
|
652
|
-
content: stripFrontMatter(body)
|
|
653
|
-
};
|
|
658
|
+
return markdownItem(path, rel, await readFile3(path, "utf8"));
|
|
654
659
|
})
|
|
655
660
|
);
|
|
656
661
|
return items.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
@@ -744,7 +749,13 @@ import { join as join5 } from "path";
|
|
|
744
749
|
|
|
745
750
|
// src/core/documents.ts
|
|
746
751
|
import { posix as posix2 } from "path";
|
|
752
|
+
function documentDate(rel) {
|
|
753
|
+
return /^((?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))-/.exec(
|
|
754
|
+
posix2.basename(rel)
|
|
755
|
+
)?.[1] ?? null;
|
|
756
|
+
}
|
|
747
757
|
function documentNumber(rel) {
|
|
758
|
+
if (documentDate(rel) !== null) return null;
|
|
748
759
|
return /^(\d{4})-/.exec(posix2.basename(rel))?.[1] ?? null;
|
|
749
760
|
}
|
|
750
761
|
function documentTitle(heading, number) {
|
|
@@ -768,11 +779,25 @@ var STATUSES = {
|
|
|
768
779
|
};
|
|
769
780
|
var SKIP = /* @__PURE__ */ new Set(["adr/0000-template.md"]);
|
|
770
781
|
var PLACEMENT_KEYS = ["scopes", "scope"];
|
|
771
|
-
async function
|
|
782
|
+
async function itemsOf(rnessDir, name, override) {
|
|
783
|
+
const items = await collectMarkdown(join5(rnessDir, name));
|
|
784
|
+
const prefix = `${name}/`;
|
|
785
|
+
if (override === void 0 || !override.rel.startsWith(prefix)) return items;
|
|
786
|
+
const rel = override.rel.slice(prefix.length);
|
|
787
|
+
const item = markdownItem(
|
|
788
|
+
join5(rnessDir, ...override.rel.split("/")),
|
|
789
|
+
rel,
|
|
790
|
+
override.text
|
|
791
|
+
);
|
|
792
|
+
return [...items.filter((i) => i.rel !== rel), item].sort(
|
|
793
|
+
(a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
async function checkContract(rnessDir, override) {
|
|
772
797
|
const problems = [];
|
|
773
798
|
for (const name of COLLECTIONS) {
|
|
774
799
|
const allowed = STATUSES[name];
|
|
775
|
-
const items = await
|
|
800
|
+
const items = await itemsOf(rnessDir, name, override);
|
|
776
801
|
const byNumber = /* @__PURE__ */ new Map();
|
|
777
802
|
for (const item of items) {
|
|
778
803
|
const where = `${name}/${item.rel}`;
|
|
@@ -788,9 +813,16 @@ async function checkContract(rnessDir) {
|
|
|
788
813
|
`${where}: the number ${number} is also that of ${files.filter((f) => f !== where).join(", ")}`
|
|
789
814
|
);
|
|
790
815
|
}
|
|
816
|
+
const next = String(
|
|
817
|
+
Math.max(0, ...[...byNumber.keys()].map(Number)) + 1
|
|
818
|
+
).padStart(4, "0");
|
|
791
819
|
for (const item of items) {
|
|
792
820
|
const where = `${name}/${item.rel}`;
|
|
793
821
|
if (SKIP.has(where)) continue;
|
|
822
|
+
if (allowed !== void 0 && documentNumber(item.rel) === null)
|
|
823
|
+
problems.push(
|
|
824
|
+
`${where}: not numbered; name it ${next}-<slug>.md, the next number of ${name}`
|
|
825
|
+
);
|
|
794
826
|
if (item.fieldsError !== null) {
|
|
795
827
|
problems.push(`${where}: invalid front matter (${item.fieldsError})`);
|
|
796
828
|
continue;
|
|
@@ -1127,8 +1159,8 @@ function sessionsOf(item) {
|
|
|
1127
1159
|
}
|
|
1128
1160
|
function rowOf(dir, item) {
|
|
1129
1161
|
const base = posix3.basename(item.rel, ".md");
|
|
1130
|
-
const date =
|
|
1131
|
-
const number =
|
|
1162
|
+
const date = documentDate(item.rel);
|
|
1163
|
+
const number = documentNumber(item.rel);
|
|
1132
1164
|
const sessions = sessionsOf(item);
|
|
1133
1165
|
return {
|
|
1134
1166
|
id: date ?? number ?? base,
|
|
@@ -1208,6 +1240,21 @@ function statusMarkdown(workspace, tabs) {
|
|
|
1208
1240
|
}
|
|
1209
1241
|
return out.join("\n");
|
|
1210
1242
|
}
|
|
1243
|
+
async function plansInProgress(ws, manifest, scope) {
|
|
1244
|
+
const context3 = await assembleContext({
|
|
1245
|
+
rnessDir: ws.rnessDir,
|
|
1246
|
+
manifest,
|
|
1247
|
+
scope
|
|
1248
|
+
});
|
|
1249
|
+
const plans = context3.collections.find((c) => c.name === "plans");
|
|
1250
|
+
return (plans?.files ?? []).filter((f) => {
|
|
1251
|
+
try {
|
|
1252
|
+
return parseFrontMatter(f.body)?.["status"] === "In progress";
|
|
1253
|
+
} catch {
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
}).map((f) => `plans/${f.rel}`);
|
|
1257
|
+
}
|
|
1211
1258
|
|
|
1212
1259
|
// src/pulse/body.ts
|
|
1213
1260
|
import { createHash } from "crypto";
|
|
@@ -2027,9 +2074,9 @@ async function postJson(path, payload, options, explain = false, limited = () =>
|
|
|
2027
2074
|
continue;
|
|
2028
2075
|
}
|
|
2029
2076
|
if (!ok) {
|
|
2030
|
-
const
|
|
2031
|
-
if (typeof
|
|
2032
|
-
throw new GitHubMessageError(
|
|
2077
|
+
const message3 = explain && page.status !== 401 && page.status !== 429 && page.body !== null && typeof page.body === "object" ? page.body.message : void 0;
|
|
2078
|
+
if (typeof message3 === "string" && message3 !== "")
|
|
2079
|
+
throw new GitHubMessageError(message3, {
|
|
2033
2080
|
cause: { status: page.status, path }
|
|
2034
2081
|
});
|
|
2035
2082
|
throw httpError(page, path);
|
|
@@ -2053,9 +2100,9 @@ async function getScopes(options) {
|
|
|
2053
2100
|
const cause = { status: res.status, path: "/user" };
|
|
2054
2101
|
if (res.status === 401)
|
|
2055
2102
|
throw new Error("GitHub rejected the token (401)", { cause });
|
|
2056
|
-
const
|
|
2057
|
-
if (typeof
|
|
2058
|
-
throw new GitHubMessageError(
|
|
2103
|
+
const message3 = body !== null && typeof body === "object" ? body["message"] : void 0;
|
|
2104
|
+
if (typeof message3 === "string" && message3 !== "")
|
|
2105
|
+
throw new GitHubMessageError(message3, { cause });
|
|
2059
2106
|
throw new Error(`GitHub API answered ${res.status} for /user`, { cause });
|
|
2060
2107
|
}
|
|
2061
2108
|
const header2 = res.headers.get("x-oauth-scopes");
|
|
@@ -2102,8 +2149,8 @@ async function getOrgMembership(org, options) {
|
|
|
2102
2149
|
}
|
|
2103
2150
|
if (status2 === 404) return "not-member";
|
|
2104
2151
|
if (status2 === 403) {
|
|
2105
|
-
const
|
|
2106
|
-
if (/OAuth App access restrictions/i.test(
|
|
2152
|
+
const message3 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
2153
|
+
if (/OAuth App access restrictions/i.test(message3)) return "restricted";
|
|
2107
2154
|
}
|
|
2108
2155
|
return "unknown";
|
|
2109
2156
|
}
|
|
@@ -2125,8 +2172,8 @@ async function createRepository(owner, name, options) {
|
|
|
2125
2172
|
throw new Error(`cannot reach the GitHub API: ${reason2(e)}`, { cause: e });
|
|
2126
2173
|
}
|
|
2127
2174
|
const body = await res.json().catch(() => void 0);
|
|
2128
|
-
const
|
|
2129
|
-
return { status: res.status, message:
|
|
2175
|
+
const message3 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
2176
|
+
return { status: res.status, message: message3 };
|
|
2130
2177
|
};
|
|
2131
2178
|
let answer = await post2(`/orgs/${encodeURIComponent(owner)}/repos`);
|
|
2132
2179
|
if (answer.status === 404 && options.self !== void 0 && options.self !== null && options.self.toLowerCase() === owner.toLowerCase())
|
|
@@ -3948,6 +3995,9 @@ function inverse(text, stream = process.stdout) {
|
|
|
3948
3995
|
const painted = styleText("inverse", text, { stream, validateStream: true });
|
|
3949
3996
|
return painted === text ? plain : painted;
|
|
3950
3997
|
}
|
|
3998
|
+
function count(n, [one, many]) {
|
|
3999
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
4000
|
+
}
|
|
3951
4001
|
function grey(text, stream = process.stdout) {
|
|
3952
4002
|
if (noColor()) return text;
|
|
3953
4003
|
return styleText("gray", text, { stream, validateStream: true });
|
|
@@ -4021,12 +4071,12 @@ async function detectGithubSsh(opts) {
|
|
|
4021
4071
|
child.stderr?.setEncoding("utf8");
|
|
4022
4072
|
child.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
4023
4073
|
let settled = false;
|
|
4024
|
-
const settle = (
|
|
4074
|
+
const settle = (access2) => {
|
|
4025
4075
|
if (settled) return;
|
|
4026
4076
|
settled = true;
|
|
4027
4077
|
clearTimeout(timer);
|
|
4028
4078
|
child.stderr?.destroy();
|
|
4029
|
-
resolve8(
|
|
4079
|
+
resolve8(access2);
|
|
4030
4080
|
};
|
|
4031
4081
|
const timer = setTimeout(() => {
|
|
4032
4082
|
child.kill("SIGKILL");
|
|
@@ -4071,8 +4121,8 @@ async function testGithubSsh(transport, interactive, announce) {
|
|
|
4071
4121
|
};
|
|
4072
4122
|
}
|
|
4073
4123
|
var loggedInAs = (login) => login === void 0 || login === null ? "" : `, logged in as ${login}`;
|
|
4074
|
-
function usingSentence(
|
|
4075
|
-
return
|
|
4124
|
+
function usingSentence(access2, login) {
|
|
4125
|
+
return access2.ok ? `SSH works for github.com (as ${access2.login}) \u2014 repositories are cloned over SSH` : `No SSH access to github.com (${access2.reason}) \u2014 repositories are cloned over HTTPS${loggedInAs(login)}`;
|
|
4076
4126
|
}
|
|
4077
4127
|
function isSshUrl(url, hosts) {
|
|
4078
4128
|
return url.startsWith(hosts.ssh);
|
|
@@ -4083,8 +4133,8 @@ var CHECKING = [
|
|
|
4083
4133
|
"ssh access to github.com\u2026",
|
|
4084
4134
|
"Checking SSH access to github.com \u2014 ssh may ask for your passphrase"
|
|
4085
4135
|
];
|
|
4086
|
-
function usingRest(
|
|
4087
|
-
return
|
|
4136
|
+
function usingRest(access2, login) {
|
|
4137
|
+
return access2.ok ? `ssh (github.com as ${access2.login})` : `https (ssh to github.com unavailable: ${access2.reason}${loggedInAs(login)})`;
|
|
4088
4138
|
}
|
|
4089
4139
|
function agentHintLines(platform = process.platform) {
|
|
4090
4140
|
const mac = platform === "darwin";
|
|
@@ -4094,6 +4144,12 @@ function agentHintLines(platform = process.platform) {
|
|
|
4094
4144
|
` ~/.ssh/config: AddKeysToAgent yes${mac ? ", UseKeychain yes" : ""}`
|
|
4095
4145
|
];
|
|
4096
4146
|
}
|
|
4147
|
+
function passphraseHint(platform = process.platform) {
|
|
4148
|
+
return [
|
|
4149
|
+
"ssh asked for your passphrase, and asks again for every repository unless an agent holds the key:",
|
|
4150
|
+
...agentHintLines(platform).slice(1)
|
|
4151
|
+
].join("\n");
|
|
4152
|
+
}
|
|
4097
4153
|
function sshWorkspaceLines(reason3) {
|
|
4098
4154
|
return [
|
|
4099
4155
|
`this workspace clones over SSH (rness.json) but ssh to github.com fails: ${reason3}`,
|
|
@@ -4147,8 +4203,8 @@ function reportError(e) {
|
|
|
4147
4203
|
process.stderr.write(`${e.stack}
|
|
4148
4204
|
`);
|
|
4149
4205
|
} else {
|
|
4150
|
-
const
|
|
4151
|
-
process.stderr.write(`${paint("error",
|
|
4206
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
4207
|
+
process.stderr.write(`${paint("error", message3, process.stderr)}
|
|
4152
4208
|
`);
|
|
4153
4209
|
}
|
|
4154
4210
|
return 1;
|
|
@@ -4340,13 +4396,16 @@ async function ensureClaudeMd(dir) {
|
|
|
4340
4396
|
}
|
|
4341
4397
|
|
|
4342
4398
|
// src/commands/sync.ts
|
|
4343
|
-
import { join as
|
|
4399
|
+
import { join as join16 } from "path";
|
|
4344
4400
|
|
|
4345
4401
|
// src/core/agent-targets.ts
|
|
4346
4402
|
import { mkdir as mkdir2, rm as rm4, rmdir } from "fs/promises";
|
|
4347
|
-
import { dirname as
|
|
4403
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
4348
4404
|
|
|
4349
4405
|
// src/core/claude-skills.ts
|
|
4406
|
+
import { readFileSync, statSync } from "fs";
|
|
4407
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
4408
|
+
import { fileURLToPath } from "url";
|
|
4350
4409
|
var PLUGIN = ".claude/skills/rness";
|
|
4351
4410
|
var PLUGIN_JSON = `${JSON.stringify(
|
|
4352
4411
|
{
|
|
@@ -4364,7 +4423,9 @@ var PLUGIN_JSON = `${JSON.stringify(
|
|
|
4364
4423
|
"adr",
|
|
4365
4424
|
"specification",
|
|
4366
4425
|
"plan"
|
|
4367
|
-
]
|
|
4426
|
+
],
|
|
4427
|
+
// The contract of the mod's `$.state` (spec 0029 §3.1).
|
|
4428
|
+
types: "./types/index.d.ts"
|
|
4368
4429
|
},
|
|
4369
4430
|
null,
|
|
4370
4431
|
2
|
|
@@ -4613,6 +4674,45 @@ allowed-tools: Bash(${command}), Bash(${allocate})
|
|
|
4613
4674
|
|
|
4614
4675
|
${s.body(rness, allocate)}`;
|
|
4615
4676
|
}
|
|
4677
|
+
function modDir() {
|
|
4678
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
4679
|
+
for (let i = 0; i < 4; i += 1) {
|
|
4680
|
+
const candidate = join13(dir, "mod");
|
|
4681
|
+
try {
|
|
4682
|
+
if (statSync(join13(candidate, "hooks", "hooks.json")).isFile())
|
|
4683
|
+
return candidate;
|
|
4684
|
+
} catch {
|
|
4685
|
+
}
|
|
4686
|
+
dir = dirname4(dir);
|
|
4687
|
+
}
|
|
4688
|
+
throw new Error("mod directory not found next to the @rness/cli package");
|
|
4689
|
+
}
|
|
4690
|
+
var MOD_FILES = [
|
|
4691
|
+
"hooks/hooks.json",
|
|
4692
|
+
"hooks/register.tsx",
|
|
4693
|
+
"types/index.d.ts"
|
|
4694
|
+
];
|
|
4695
|
+
var RNESS_LINE = "const RNESS = '.rness'";
|
|
4696
|
+
var modSources = null;
|
|
4697
|
+
function modSource(rel) {
|
|
4698
|
+
if (modSources === null) {
|
|
4699
|
+
const dir = modDir();
|
|
4700
|
+
modSources = new Map(
|
|
4701
|
+
MOD_FILES.map((f) => [
|
|
4702
|
+
f,
|
|
4703
|
+
readFileSync(join13(dir, ...f.split("/")), "utf8")
|
|
4704
|
+
])
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
return modSources.get(rel) ?? "";
|
|
4708
|
+
}
|
|
4709
|
+
function modFile(rel, rness) {
|
|
4710
|
+
const text = modSource(rel);
|
|
4711
|
+
if (rel !== "hooks/register.tsx") return text;
|
|
4712
|
+
if (!text.includes(RNESS_LINE))
|
|
4713
|
+
throw new Error(`mod/hooks/register.tsx lacks ${RNESS_LINE}`);
|
|
4714
|
+
return text.replace(RNESS_LINE, `const RNESS = '${rness}'`);
|
|
4715
|
+
}
|
|
4616
4716
|
function pluginFiles(rness, at, ctx) {
|
|
4617
4717
|
return [
|
|
4618
4718
|
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
@@ -4626,6 +4726,11 @@ function pluginFiles(rness, at, ctx) {
|
|
|
4626
4726
|
at,
|
|
4627
4727
|
content: lifecycleSkill(rness, s)
|
|
4628
4728
|
})),
|
|
4729
|
+
...MOD_FILES.map((rel) => ({
|
|
4730
|
+
file: `${PLUGIN}/${rel}`,
|
|
4731
|
+
at,
|
|
4732
|
+
content: modFile(rel, rness)
|
|
4733
|
+
})),
|
|
4629
4734
|
...RETIRED_SKILLS.map((name) => ({
|
|
4630
4735
|
file: `${PLUGIN}/skills/${name}/SKILL.md`,
|
|
4631
4736
|
at,
|
|
@@ -4658,6 +4763,22 @@ function hooks(rness) {
|
|
|
4658
4763
|
},
|
|
4659
4764
|
label: "the rness session-start hook"
|
|
4660
4765
|
},
|
|
4766
|
+
{
|
|
4767
|
+
// Before an edit: refused when it would change what sync generates,
|
|
4768
|
+
// or break the contract of .rness (spec 0029 §4).
|
|
4769
|
+
path: ["hooks", "PreToolUse"],
|
|
4770
|
+
contains: {
|
|
4771
|
+
matcher: "Edit|Write",
|
|
4772
|
+
hooks: [
|
|
4773
|
+
{
|
|
4774
|
+
type: "command",
|
|
4775
|
+
command: hookLine(rness, "pre-tool-use"),
|
|
4776
|
+
timeout: 10
|
|
4777
|
+
}
|
|
4778
|
+
]
|
|
4779
|
+
},
|
|
4780
|
+
label: "the rness pre-tool-use hook"
|
|
4781
|
+
},
|
|
4661
4782
|
{
|
|
4662
4783
|
path: ["hooks", "PostToolUse"],
|
|
4663
4784
|
contains: {
|
|
@@ -4852,7 +4973,7 @@ function describeGuarantee(g) {
|
|
|
4852
4973
|
// src/core/pinned.ts
|
|
4853
4974
|
import { execFile as execFile3 } from "child_process";
|
|
4854
4975
|
import { readFile as readFile8 } from "fs/promises";
|
|
4855
|
-
import { basename as basename3, join as
|
|
4976
|
+
import { basename as basename3, join as join14 } from "path";
|
|
4856
4977
|
import { promisify as promisify3 } from "util";
|
|
4857
4978
|
var execFileP3 = promisify3(execFile3);
|
|
4858
4979
|
var PACKAGE = "@rness/cli";
|
|
@@ -4877,7 +4998,7 @@ function compareVersions(a, b) {
|
|
|
4877
4998
|
async function readPackageJson(rnessDir) {
|
|
4878
4999
|
try {
|
|
4879
5000
|
const parsed = JSON.parse(
|
|
4880
|
-
await readFile8(
|
|
5001
|
+
await readFile8(join14(rnessDir, "package.json"), "utf8")
|
|
4881
5002
|
);
|
|
4882
5003
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4883
5004
|
} catch {
|
|
@@ -4896,7 +5017,7 @@ async function readPin(rnessDir) {
|
|
|
4896
5017
|
return null;
|
|
4897
5018
|
}
|
|
4898
5019
|
async function writePin(rnessDir, from, to) {
|
|
4899
|
-
const file =
|
|
5020
|
+
const file = join14(rnessDir, "package.json");
|
|
4900
5021
|
const text = await readFile8(file, "utf8");
|
|
4901
5022
|
const entry = new RegExp(
|
|
4902
5023
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
@@ -4919,12 +5040,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
4919
5040
|
if (isPackageManager(name)) return name;
|
|
4920
5041
|
}
|
|
4921
5042
|
for (const [lockfile, pm] of LOCKFILES)
|
|
4922
|
-
if (await exists(
|
|
5043
|
+
if (await exists(join14(rnessDir, lockfile))) return pm;
|
|
4923
5044
|
return "npm";
|
|
4924
5045
|
}
|
|
4925
5046
|
async function hasLockfile(rnessDir, pm) {
|
|
4926
5047
|
for (const [lockfile, owner] of LOCKFILES)
|
|
4927
|
-
if (owner === pm && await exists(
|
|
5048
|
+
if (owner === pm && await exists(join14(rnessDir, lockfile))) return true;
|
|
4928
5049
|
return false;
|
|
4929
5050
|
}
|
|
4930
5051
|
async function pinDrift(rnessDir) {
|
|
@@ -4980,9 +5101,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
4980
5101
|
}
|
|
4981
5102
|
}
|
|
4982
5103
|
async function runPinnedSync(root) {
|
|
4983
|
-
const installed = await readPinnedCli(
|
|
5104
|
+
const installed = await readPinnedCli(join14(root, ".rness"));
|
|
4984
5105
|
if (installed === null) return null;
|
|
4985
|
-
const pinned =
|
|
5106
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
4986
5107
|
if (!await exists(pinned))
|
|
4987
5108
|
return {
|
|
4988
5109
|
code: 1,
|
|
@@ -5057,7 +5178,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
5057
5178
|
};
|
|
5058
5179
|
}
|
|
5059
5180
|
async function syncPinned(root, shown, fallback) {
|
|
5060
|
-
const rnessDir =
|
|
5181
|
+
const rnessDir = join14(root, ".rness");
|
|
5061
5182
|
const installed = await readPinnedCli(rnessDir);
|
|
5062
5183
|
if (installed === null) {
|
|
5063
5184
|
if (fallback !== void 0) return fallback();
|
|
@@ -5067,7 +5188,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
5067
5188
|
);
|
|
5068
5189
|
return 1;
|
|
5069
5190
|
}
|
|
5070
|
-
const pinned =
|
|
5191
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
5071
5192
|
if (!await exists(pinned)) {
|
|
5072
5193
|
process.stderr.write(
|
|
5073
5194
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -5107,30 +5228,30 @@ function wholeDiff(existing, content) {
|
|
|
5107
5228
|
}
|
|
5108
5229
|
async function contextOf(root) {
|
|
5109
5230
|
return {
|
|
5110
|
-
packageManager: await workspacePackageManager(
|
|
5231
|
+
packageManager: await workspacePackageManager(join15(root, ".rness"))
|
|
5111
5232
|
};
|
|
5112
5233
|
}
|
|
5113
5234
|
var PATHS_ONLY = { packageManager: "npm" };
|
|
5114
5235
|
async function filesOf(root, manifest, targets) {
|
|
5115
5236
|
const files = [];
|
|
5116
|
-
if (!await exists(
|
|
5237
|
+
if (!await exists(join15(root, "org"))) return files;
|
|
5117
5238
|
const ctx = await contextOf(root);
|
|
5118
5239
|
for (const target2 of targets)
|
|
5119
5240
|
for (const owned of target2.files(ctx))
|
|
5120
5241
|
if (owned.at === "root")
|
|
5121
5242
|
files.push({
|
|
5122
5243
|
...owned,
|
|
5123
|
-
file:
|
|
5244
|
+
file: join15(root, ...owned.file.split("/")),
|
|
5124
5245
|
label: owned.file
|
|
5125
5246
|
});
|
|
5126
5247
|
for (const repo of Object.keys(manifest.repos)) {
|
|
5127
|
-
if (!await exists(
|
|
5248
|
+
if (!await exists(join15(root, "org", repo))) continue;
|
|
5128
5249
|
for (const target2 of targets)
|
|
5129
5250
|
for (const owned of target2.files(ctx))
|
|
5130
5251
|
if (owned.at === "clones")
|
|
5131
5252
|
files.push({
|
|
5132
5253
|
...owned,
|
|
5133
|
-
file:
|
|
5254
|
+
file: join15(root, "org", repo, ...owned.file.split("/")),
|
|
5134
5255
|
label: `org/${repo}/${owned.file}`
|
|
5135
5256
|
});
|
|
5136
5257
|
}
|
|
@@ -5166,7 +5287,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5166
5287
|
continue;
|
|
5167
5288
|
}
|
|
5168
5289
|
await rm4(file);
|
|
5169
|
-
await rmdir(
|
|
5290
|
+
await rmdir(dirname5(file)).catch(() => void 0);
|
|
5170
5291
|
outcomes.push({ label, status: "removed", detail: null });
|
|
5171
5292
|
continue;
|
|
5172
5293
|
}
|
|
@@ -5177,7 +5298,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5177
5298
|
else if (opts.check)
|
|
5178
5299
|
outcomes.push({ label, status: "stale", detail: diff });
|
|
5179
5300
|
else {
|
|
5180
|
-
await mkdir2(
|
|
5301
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5181
5302
|
await writeFileAtomic(file, owned.content);
|
|
5182
5303
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5183
5304
|
}
|
|
@@ -5206,7 +5327,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5206
5327
|
outcomes.push({ label, status: "unchanged", detail: null });
|
|
5207
5328
|
continue;
|
|
5208
5329
|
}
|
|
5209
|
-
await mkdir2(
|
|
5330
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5210
5331
|
await writeFileAtomic(file, ensured.text);
|
|
5211
5332
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5212
5333
|
}
|
|
@@ -5398,8 +5519,8 @@ function sessionUi(p) {
|
|
|
5398
5519
|
intro: (title) => {
|
|
5399
5520
|
p.intro(badge(title));
|
|
5400
5521
|
},
|
|
5401
|
-
outro: (
|
|
5402
|
-
p.outro(badge(
|
|
5522
|
+
outro: (message3, tone = "done") => {
|
|
5523
|
+
p.outro(badge(message3, tone));
|
|
5403
5524
|
},
|
|
5404
5525
|
line: (verb, rest, sentence) => {
|
|
5405
5526
|
say(verb, sentence ?? `${rest} ${verb}`);
|
|
@@ -5470,7 +5591,7 @@ function targetsOf(manifest, root, only) {
|
|
|
5470
5591
|
const segments = entry.path.split("/");
|
|
5471
5592
|
targets.push({
|
|
5472
5593
|
scope: name,
|
|
5473
|
-
dir:
|
|
5594
|
+
dir: join16(root, ...segments),
|
|
5474
5595
|
depth: segments.length,
|
|
5475
5596
|
label: `${entry.path}/AGENTS.md`
|
|
5476
5597
|
});
|
|
@@ -5509,7 +5630,7 @@ async function askWhatToSync(input) {
|
|
|
5509
5630
|
{
|
|
5510
5631
|
value: SELECT_ALL,
|
|
5511
5632
|
label: "Select all",
|
|
5512
|
-
hint: `${input.missing.length
|
|
5633
|
+
hint: `${count(input.missing.length, ["repository", "repositories"])} from rness.json`
|
|
5513
5634
|
},
|
|
5514
5635
|
...input.missing.map((name) => ({ value: name, label: name }))
|
|
5515
5636
|
],
|
|
@@ -5610,15 +5731,16 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5610
5731
|
if (!overSsh) ui.gitIsSilent = true;
|
|
5611
5732
|
if (overSsh) {
|
|
5612
5733
|
const interactive = opts.yes !== true && terminal.isTty();
|
|
5613
|
-
const { access:
|
|
5734
|
+
const { access: access2, unattended } = await testGithubSsh(
|
|
5614
5735
|
transport,
|
|
5615
5736
|
interactive,
|
|
5616
5737
|
() => ui.line(...CHECKING)
|
|
5617
5738
|
);
|
|
5618
5739
|
if (unattended) ui.gitIsSilent = true;
|
|
5619
|
-
if (
|
|
5740
|
+
else if (access2.ok) ui.hint(passphraseHint(), "stderr");
|
|
5741
|
+
if (!access2.ok) {
|
|
5620
5742
|
process.stderr.write(
|
|
5621
|
-
`${[...sshWorkspaceLines(
|
|
5743
|
+
`${[...sshWorkspaceLines(access2.reason), ...INSTEAD_OF_LINES].join("\n")}
|
|
5622
5744
|
`
|
|
5623
5745
|
);
|
|
5624
5746
|
return 1;
|
|
@@ -5629,7 +5751,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5629
5751
|
try {
|
|
5630
5752
|
const notCloned = /* @__PURE__ */ new Set();
|
|
5631
5753
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
5632
|
-
const dir =
|
|
5754
|
+
const dir = join16(ws.root, "org", name);
|
|
5633
5755
|
const label = `org/${name}`;
|
|
5634
5756
|
if (!await exists(dir)) {
|
|
5635
5757
|
if (!toClone.has(name)) {
|
|
@@ -5681,7 +5803,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5681
5803
|
lines.push(
|
|
5682
5804
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
5683
5805
|
);
|
|
5684
|
-
if (!await exists(
|
|
5806
|
+
if (!await exists(join16(ws.root, "org"))) {
|
|
5685
5807
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
5686
5808
|
} else {
|
|
5687
5809
|
let differences = 0;
|
|
@@ -5693,7 +5815,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5693
5815
|
continue;
|
|
5694
5816
|
}
|
|
5695
5817
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
5696
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
5818
|
+
BLOCK_FILES.map((f) => isSymlink(join16(t.dir, f)))
|
|
5697
5819
|
);
|
|
5698
5820
|
const atRoot = t.scope === null;
|
|
5699
5821
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -5716,7 +5838,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5716
5838
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
5717
5839
|
);
|
|
5718
5840
|
}
|
|
5719
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
5841
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join16(t.dir, "AGENTS.md"), block, check);
|
|
5720
5842
|
if (typeof agents === "object") {
|
|
5721
5843
|
problems.push(`${t.label}: ${agents.error}`);
|
|
5722
5844
|
continue;
|
|
@@ -5731,7 +5853,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5731
5853
|
}
|
|
5732
5854
|
if (claudeLinked === true) continue;
|
|
5733
5855
|
const claude = await syncBlockFile(
|
|
5734
|
-
|
|
5856
|
+
join16(t.dir, "CLAUDE.md"),
|
|
5735
5857
|
block,
|
|
5736
5858
|
check,
|
|
5737
5859
|
true
|
|
@@ -5788,7 +5910,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5788
5910
|
}
|
|
5789
5911
|
|
|
5790
5912
|
// src/commands/add.ts
|
|
5791
|
-
import { basename as basename4, join as
|
|
5913
|
+
import { basename as basename4, join as join17 } from "path";
|
|
5792
5914
|
function printDeclared(ui, result, already) {
|
|
5793
5915
|
for (const name of result.declared) {
|
|
5794
5916
|
if (already.has(name)) continue;
|
|
@@ -5847,20 +5969,21 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5847
5969
|
if (host === void 0 && opts.https === true) host = transport.hosts.https;
|
|
5848
5970
|
if (host === void 0 && FULL_URL2.test(spec)) host = transport.hosts.https;
|
|
5849
5971
|
if (host === void 0) {
|
|
5850
|
-
const { access:
|
|
5972
|
+
const { access: access2, unattended } = await testGithubSsh(
|
|
5851
5973
|
transport,
|
|
5852
5974
|
interactive,
|
|
5853
5975
|
() => ui.line(...CHECKING)
|
|
5854
5976
|
);
|
|
5855
|
-
if (unattended || !
|
|
5977
|
+
if (unattended || !access2.ok) ui.gitIsSilent = true;
|
|
5978
|
+
else ui.hint(passphraseHint(), "stderr");
|
|
5856
5979
|
const overSsh = Object.values(manifest.repos).some(
|
|
5857
5980
|
(r) => isSshUrl(r.url, transport.hosts)
|
|
5858
5981
|
);
|
|
5859
|
-
if (
|
|
5860
|
-
ui.line("using", usingRest(
|
|
5861
|
-
host =
|
|
5982
|
+
if (access2.ok || !overSsh) {
|
|
5983
|
+
ui.line("using", usingRest(access2), usingSentence(access2));
|
|
5984
|
+
host = access2.ok ? transport.hosts.ssh : transport.hosts.https;
|
|
5862
5985
|
} else {
|
|
5863
|
-
const lines = sshWorkspaceLines(
|
|
5986
|
+
const lines = sshWorkspaceLines(access2.reason);
|
|
5864
5987
|
if (!interactive) {
|
|
5865
5988
|
process.stderr.write(
|
|
5866
5989
|
`${[...lines, httpsFlagLine(spec)].join("\n")}
|
|
@@ -5881,7 +6004,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5881
6004
|
}
|
|
5882
6005
|
parsed = parseRepoSpec(spec, org, host);
|
|
5883
6006
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
5884
|
-
const dir =
|
|
6007
|
+
const dir = join17(ws.root, "org", parsed.name);
|
|
5885
6008
|
const present = await exists(dir);
|
|
5886
6009
|
if (interactive) {
|
|
5887
6010
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -6008,9 +6131,9 @@ async function contextCommand(opts) {
|
|
|
6008
6131
|
}
|
|
6009
6132
|
|
|
6010
6133
|
// src/core/scaffold.ts
|
|
6011
|
-
import { statSync } from "fs";
|
|
6012
|
-
import { dirname as
|
|
6013
|
-
import { fileURLToPath } from "url";
|
|
6134
|
+
import { statSync as statSync2 } from "fs";
|
|
6135
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
6136
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6014
6137
|
var SCAFFOLD_FILES = [
|
|
6015
6138
|
"AGENTS.md",
|
|
6016
6139
|
"CONVENTIONS.md",
|
|
@@ -6031,27 +6154,27 @@ var SCAFFOLD_FILES = [
|
|
|
6031
6154
|
"docs/.gitkeep"
|
|
6032
6155
|
];
|
|
6033
6156
|
function scaffoldDir() {
|
|
6034
|
-
let dir =
|
|
6157
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
6035
6158
|
for (let i = 0; i < 4; i += 1) {
|
|
6036
|
-
const candidate =
|
|
6159
|
+
const candidate = join18(dir, "scaffold");
|
|
6037
6160
|
try {
|
|
6038
|
-
if (
|
|
6161
|
+
if (statSync2(join18(candidate, "rness.json")).isFile()) return candidate;
|
|
6039
6162
|
} catch {
|
|
6040
6163
|
}
|
|
6041
|
-
dir =
|
|
6164
|
+
dir = dirname6(dir);
|
|
6042
6165
|
}
|
|
6043
6166
|
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
6044
6167
|
}
|
|
6045
6168
|
|
|
6046
6169
|
// src/core/scaffold-copy.ts
|
|
6047
6170
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
6048
|
-
import { dirname as
|
|
6171
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
6049
6172
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
6050
6173
|
async function renderScaffold(source, tokens) {
|
|
6051
6174
|
const files = [];
|
|
6052
6175
|
for (const rel of SCAFFOLD_FILES) {
|
|
6053
6176
|
if (rel === "rness.json") continue;
|
|
6054
|
-
const from =
|
|
6177
|
+
const from = join19(source, ...rel.split("/"));
|
|
6055
6178
|
let content = await readFile9(from, "utf8");
|
|
6056
6179
|
if (TOKENISED.has(rel)) {
|
|
6057
6180
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
@@ -6066,8 +6189,8 @@ async function renderScaffold(source, tokens) {
|
|
|
6066
6189
|
}
|
|
6067
6190
|
async function copyScaffold(dest, tokens) {
|
|
6068
6191
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
6069
|
-
const to =
|
|
6070
|
-
await mkdir3(
|
|
6192
|
+
const to = join19(dest, ...file.path.split("/"));
|
|
6193
|
+
await mkdir3(dirname7(to), { recursive: true });
|
|
6071
6194
|
await writeFile2(to, file.content, "utf8");
|
|
6072
6195
|
if (file.executable) await chmod2(to, 493);
|
|
6073
6196
|
}
|
|
@@ -6248,7 +6371,7 @@ async function loginCommand(opts, deps = {}) {
|
|
|
6248
6371
|
// src/commands/create.ts
|
|
6249
6372
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm7 } from "fs/promises";
|
|
6250
6373
|
import { tmpdir as tmpdir2 } from "os";
|
|
6251
|
-
import { join as
|
|
6374
|
+
import { join as join23, relative as relative4, resolve as resolve5 } from "path";
|
|
6252
6375
|
|
|
6253
6376
|
// src/core/approval.ts
|
|
6254
6377
|
var APPROVAL_INTERVAL_MS = 5e3;
|
|
@@ -6262,12 +6385,12 @@ async function checkAccess(input) {
|
|
|
6262
6385
|
const login = (await provider.identity())?.login;
|
|
6263
6386
|
const as = login === void 0 ? "" : ` (as ${login})`;
|
|
6264
6387
|
const member = () => ui.line("member", `${org}${as}`, `You are a member of ${org}${as}`);
|
|
6265
|
-
const
|
|
6266
|
-
if (
|
|
6388
|
+
const access2 = await provider.organizationAccess(org);
|
|
6389
|
+
if (access2 === "member") {
|
|
6267
6390
|
member();
|
|
6268
|
-
return
|
|
6391
|
+
return access2;
|
|
6269
6392
|
}
|
|
6270
|
-
if (
|
|
6393
|
+
if (access2 !== "restricted") return access2;
|
|
6271
6394
|
const url = provider.approvalUrl();
|
|
6272
6395
|
ui.warn(
|
|
6273
6396
|
`${org} has not approved rness, so its private repositories are hidden`
|
|
@@ -6296,23 +6419,42 @@ async function checkAccess(input) {
|
|
|
6296
6419
|
const who = input.canGrant === true ? org : `an owner of ${org}`;
|
|
6297
6420
|
const waiting = `for ${who} to approve rness on github.com`;
|
|
6298
6421
|
if (!ui.session) ui.line("waiting", `${waiting}\u2026`);
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6422
|
+
let interrupted = false;
|
|
6423
|
+
let wake = () => void 0;
|
|
6424
|
+
const interrupt = () => {
|
|
6425
|
+
interrupted = true;
|
|
6426
|
+
wake();
|
|
6427
|
+
};
|
|
6428
|
+
if (!ui.session) process.once("SIGINT", interrupt);
|
|
6429
|
+
let outcome;
|
|
6430
|
+
try {
|
|
6431
|
+
outcome = await ui.step(
|
|
6432
|
+
{
|
|
6433
|
+
doing: `waiting ${waiting}`,
|
|
6434
|
+
sentence: `Waiting ${waiting}`,
|
|
6435
|
+
quiet: true
|
|
6436
|
+
},
|
|
6437
|
+
async () => {
|
|
6438
|
+
const deadline = now() + APPROVAL_WAIT_MS;
|
|
6439
|
+
for (; ; ) {
|
|
6440
|
+
await Promise.race([
|
|
6441
|
+
sleep(APPROVAL_INTERVAL_MS),
|
|
6442
|
+
new Promise((resolve8) => {
|
|
6443
|
+
wake = resolve8;
|
|
6444
|
+
})
|
|
6445
|
+
]);
|
|
6446
|
+
if (interrupted) return "cancelled";
|
|
6447
|
+
const again = await provider.organizationAccess(org);
|
|
6448
|
+
if (again === "member" || again === "not-member") return again;
|
|
6449
|
+
if (now() >= deadline) return "timeout";
|
|
6450
|
+
}
|
|
6451
|
+
},
|
|
6452
|
+
(r) => r === "member" ? ["member", `${org}${as}`] : null
|
|
6453
|
+
);
|
|
6454
|
+
} finally {
|
|
6455
|
+
process.off("SIGINT", interrupt);
|
|
6456
|
+
}
|
|
6457
|
+
if (outcome === "cancelled") return outcome;
|
|
6316
6458
|
if (outcome === "member") {
|
|
6317
6459
|
if (!ui.session) member();
|
|
6318
6460
|
return outcome;
|
|
@@ -6348,13 +6490,13 @@ async function askProvider(p) {
|
|
|
6348
6490
|
|
|
6349
6491
|
// src/core/new-workspace.ts
|
|
6350
6492
|
import { mkdir as mkdir4, readdir as readdir4, rm as rm6, stat as stat3 } from "fs/promises";
|
|
6351
|
-
import { dirname as
|
|
6493
|
+
import { dirname as dirname9, join as join21, relative as relative2 } from "path";
|
|
6352
6494
|
|
|
6353
6495
|
// src/core/scaffold-git.ts
|
|
6354
6496
|
import { execFile as execFile5 } from "child_process";
|
|
6355
6497
|
import { mkdtemp, realpath as realpath2, rm as rm5, writeFile as writeFile3 } from "fs/promises";
|
|
6356
6498
|
import { tmpdir } from "os";
|
|
6357
|
-
import { dirname as
|
|
6499
|
+
import { dirname as dirname8, join as join20, resolve as resolve3 } from "path";
|
|
6358
6500
|
import { promisify as promisify5 } from "util";
|
|
6359
6501
|
var execFileP5 = promisify5(execFile5);
|
|
6360
6502
|
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
@@ -6368,7 +6510,7 @@ async function run(dir, args, env = {}) {
|
|
|
6368
6510
|
...process.env,
|
|
6369
6511
|
...env,
|
|
6370
6512
|
GIT_TERMINAL_PROMPT: "0",
|
|
6371
|
-
GIT_CEILING_DIRECTORIES:
|
|
6513
|
+
GIT_CEILING_DIRECTORIES: dirname8(resolve3(dir))
|
|
6372
6514
|
},
|
|
6373
6515
|
maxBuffer: 16 * 1024 * 1024
|
|
6374
6516
|
});
|
|
@@ -6395,13 +6537,13 @@ function scaffoldMessage(subject, version) {
|
|
|
6395
6537
|
|
|
6396
6538
|
${SCAFFOLD_TRAILER}: ${version}`;
|
|
6397
6539
|
}
|
|
6398
|
-
async function commitUpgrade(rnessDir,
|
|
6540
|
+
async function commitUpgrade(rnessDir, message3) {
|
|
6399
6541
|
const add = await run(rnessDir, ["add", "-A"]);
|
|
6400
6542
|
if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
|
|
6401
6543
|
const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
|
|
6402
6544
|
if (staged.code === 0 && !await mergeInProgress(rnessDir))
|
|
6403
6545
|
return { kind: "nothing" };
|
|
6404
|
-
const commit = await run(rnessDir, ["commit", "-q", "-m",
|
|
6546
|
+
const commit = await run(rnessDir, ["commit", "-q", "-m", message3]);
|
|
6405
6547
|
if (commit.code !== 0)
|
|
6406
6548
|
return {
|
|
6407
6549
|
kind: "refused",
|
|
@@ -6442,8 +6584,8 @@ async function findScaffoldBase(rnessDir) {
|
|
|
6442
6584
|
};
|
|
6443
6585
|
}
|
|
6444
6586
|
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
6445
|
-
const scratch = await mkdtemp(
|
|
6446
|
-
const env = { GIT_INDEX_FILE:
|
|
6587
|
+
const scratch = await mkdtemp(join20(tmpdir(), "rness-scaffold-"));
|
|
6588
|
+
const env = { GIT_INDEX_FILE: join20(scratch, "index") };
|
|
6447
6589
|
try {
|
|
6448
6590
|
await must(
|
|
6449
6591
|
rnessDir,
|
|
@@ -6451,7 +6593,7 @@ async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
|
6451
6593
|
env
|
|
6452
6594
|
);
|
|
6453
6595
|
for (const [i, file] of files.entries()) {
|
|
6454
|
-
const tmp =
|
|
6596
|
+
const tmp = join20(scratch, `blob-${i}`);
|
|
6455
6597
|
await writeFile3(tmp, file.content, "utf8");
|
|
6456
6598
|
const blob = await must(rnessDir, [
|
|
6457
6599
|
"hash-object",
|
|
@@ -6563,11 +6705,11 @@ async function insideWorkspace(dir) {
|
|
|
6563
6705
|
}
|
|
6564
6706
|
async function targetProblem(cwd, root, pm) {
|
|
6565
6707
|
const shown = relative2(cwd, root) || ".";
|
|
6566
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(
|
|
6708
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname9(root)))
|
|
6567
6709
|
return "already inside an rness workspace; create runs from outside one";
|
|
6568
6710
|
if (!await exists(root)) return null;
|
|
6569
|
-
if (await exists(
|
|
6570
|
-
if (!await exists(
|
|
6711
|
+
if (await exists(join21(root, ".rness", "rness.json"))) {
|
|
6712
|
+
if (!await exists(join21(root, ".rness", "node_modules")))
|
|
6571
6713
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
6572
6714
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
6573
6715
|
}
|
|
@@ -6590,7 +6732,7 @@ async function installContext(input) {
|
|
|
6590
6732
|
}
|
|
6591
6733
|
async function buildContext(input) {
|
|
6592
6734
|
const { ui, root, shown, pm } = input;
|
|
6593
|
-
const rnessDir =
|
|
6735
|
+
const rnessDir = join21(root, ".rness");
|
|
6594
6736
|
const pmVersion = await packageManagerVersion(pm);
|
|
6595
6737
|
await mkdir4(root, { recursive: true });
|
|
6596
6738
|
try {
|
|
@@ -6662,7 +6804,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
6662
6804
|
|
|
6663
6805
|
// src/commands/create-blank.ts
|
|
6664
6806
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
6665
|
-
import { join as
|
|
6807
|
+
import { join as join22, relative as relative3, resolve as resolve4 } from "path";
|
|
6666
6808
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
6667
6809
|
function workspaceNameError(name) {
|
|
6668
6810
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -6744,8 +6886,8 @@ async function createBlank(input) {
|
|
|
6744
6886
|
kind: "blank workspace"
|
|
6745
6887
|
});
|
|
6746
6888
|
if (!built) return 1;
|
|
6747
|
-
await mkdir5(
|
|
6748
|
-
await commitContext({ ui, rnessDir:
|
|
6889
|
+
await mkdir5(join22(root, "org"), { recursive: true });
|
|
6890
|
+
await commitContext({ ui, rnessDir: join22(root, ".rness"), shown });
|
|
6749
6891
|
const code = await syncBlocks(
|
|
6750
6892
|
ui,
|
|
6751
6893
|
root,
|
|
@@ -6804,7 +6946,7 @@ async function pickRepositories(input) {
|
|
|
6804
6946
|
// list below it must agree (spec 0009).
|
|
6805
6947
|
(l) => [
|
|
6806
6948
|
"listed",
|
|
6807
|
-
`${l.repositories.filter(isMemberRepo).length
|
|
6949
|
+
`${count(l.repositories.filter(isMemberRepo).length, ["repository", "repositories"])} of ${org}`
|
|
6808
6950
|
]
|
|
6809
6951
|
);
|
|
6810
6952
|
listed = listing.repositories;
|
|
@@ -6889,7 +7031,7 @@ async function cloneSelection(input) {
|
|
|
6889
7031
|
{ doing: `cloning org/${repo}`, git: true },
|
|
6890
7032
|
() => clone(
|
|
6891
7033
|
entry.url,
|
|
6892
|
-
|
|
7034
|
+
join23(input.root, "org", repo),
|
|
6893
7035
|
input.provider.credentialsFor(entry.url)
|
|
6894
7036
|
),
|
|
6895
7037
|
() => ["cloned", `org/${repo}`]
|
|
@@ -6914,9 +7056,9 @@ async function cloneSelection(input) {
|
|
|
6914
7056
|
manifest = result.manifest;
|
|
6915
7057
|
ui.line("declared", `scope ${result.name} (org/${result.name})`);
|
|
6916
7058
|
} catch (e) {
|
|
6917
|
-
const
|
|
6918
|
-
ui.error(`${spec}: ${
|
|
6919
|
-
failures.push({ spec, message:
|
|
7059
|
+
const message3 = firstLine5(e instanceof Error ? e.message : String(e));
|
|
7060
|
+
ui.error(`${spec}: ${message3}`);
|
|
7061
|
+
failures.push({ spec, message: message3 });
|
|
6920
7062
|
}
|
|
6921
7063
|
}
|
|
6922
7064
|
return failures;
|
|
@@ -7085,6 +7227,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7085
7227
|
() => ui.line(...CHECKING)
|
|
7086
7228
|
);
|
|
7087
7229
|
if (sshTest.unattended) ui.gitIsSilent = true;
|
|
7230
|
+
else if (sshTest.access.ok) ui.hint(passphraseHint(), "stderr");
|
|
7088
7231
|
}
|
|
7089
7232
|
return sshTest.access;
|
|
7090
7233
|
};
|
|
@@ -7094,12 +7237,12 @@ async function createCommand(opts, deps = {}) {
|
|
|
7094
7237
|
if (opts.host !== void 0) return opts.host;
|
|
7095
7238
|
if (opts.ssh === true) return transport.hosts.ssh;
|
|
7096
7239
|
if (opts.https === true) return transport.hosts.https;
|
|
7097
|
-
const
|
|
7098
|
-
const github =
|
|
7240
|
+
const access2 = await testSsh();
|
|
7241
|
+
const github = access2.ok ? null : await getProvider();
|
|
7099
7242
|
const login = github?.authenticated ? (await github.identity())?.login : void 0;
|
|
7100
|
-
ui.line("using", usingRest(
|
|
7101
|
-
if (!
|
|
7102
|
-
return
|
|
7243
|
+
ui.line("using", usingRest(access2, login), usingSentence(access2, login));
|
|
7244
|
+
if (!access2.ok) ui.gitIsSilent = true;
|
|
7245
|
+
return access2.ok ? transport.hosts.ssh : transport.hosts.https;
|
|
7103
7246
|
};
|
|
7104
7247
|
let loaded;
|
|
7105
7248
|
const prompts = async () => loaded ??= await terminal.prompts();
|
|
@@ -7251,8 +7394,8 @@ async function createCommand(opts, deps = {}) {
|
|
|
7251
7394
|
scopes: {}
|
|
7252
7395
|
};
|
|
7253
7396
|
const staged = async () => {
|
|
7254
|
-
staging ??= await mkdtemp2(
|
|
7255
|
-
return
|
|
7397
|
+
staging ??= await mkdtemp2(join23(tmpdir2(), "rness-join-"));
|
|
7398
|
+
return join23(staging, ".rness");
|
|
7256
7399
|
};
|
|
7257
7400
|
if (joining) {
|
|
7258
7401
|
const stagedDir = await staged();
|
|
@@ -7273,10 +7416,10 @@ async function createCommand(opts, deps = {}) {
|
|
|
7273
7416
|
(r) => isSshUrl(r.url, transport.hosts)
|
|
7274
7417
|
);
|
|
7275
7418
|
if (overSsh && opts.ssh !== true && opts.host === void 0) {
|
|
7276
|
-
const
|
|
7277
|
-
if (!
|
|
7419
|
+
const access2 = await testSsh();
|
|
7420
|
+
if (!access2.ok) {
|
|
7278
7421
|
process.stderr.write(
|
|
7279
|
-
`${[...sshWorkspaceLines(
|
|
7422
|
+
`${[...sshWorkspaceLines(access2.reason), ...INSTEAD_OF_LINES].join("\n")}
|
|
7280
7423
|
`
|
|
7281
7424
|
);
|
|
7282
7425
|
return 1;
|
|
@@ -7314,7 +7457,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7314
7457
|
});
|
|
7315
7458
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
7316
7459
|
}
|
|
7317
|
-
const rnessDir =
|
|
7460
|
+
const rnessDir = join23(root, ".rness");
|
|
7318
7461
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
7319
7462
|
if (joining) {
|
|
7320
7463
|
await mkdir6(root, { recursive: true });
|
|
@@ -7327,7 +7470,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7327
7470
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
7328
7471
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
7329
7472
|
await install();
|
|
7330
|
-
await mkdir6(
|
|
7473
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7331
7474
|
const failures2 = await cloneSelection({
|
|
7332
7475
|
root,
|
|
7333
7476
|
rnessDir,
|
|
@@ -7380,7 +7523,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7380
7523
|
kind: "new workspace"
|
|
7381
7524
|
});
|
|
7382
7525
|
if (!built) return 1;
|
|
7383
|
-
await mkdir6(
|
|
7526
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7384
7527
|
const failures = await cloneSelection({
|
|
7385
7528
|
root,
|
|
7386
7529
|
rnessDir,
|
|
@@ -7452,7 +7595,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7452
7595
|
}
|
|
7453
7596
|
|
|
7454
7597
|
// src/core/blocks.ts
|
|
7455
|
-
import { join as
|
|
7598
|
+
import { join as join24 } from "path";
|
|
7456
7599
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
7457
7600
|
async function checkBlocks({
|
|
7458
7601
|
root,
|
|
@@ -7461,15 +7604,15 @@ async function checkBlocks({
|
|
|
7461
7604
|
org
|
|
7462
7605
|
}) {
|
|
7463
7606
|
const checks = [];
|
|
7464
|
-
const note = (label, status2,
|
|
7465
|
-
checks.push({ label, status: status2, message:
|
|
7607
|
+
const note = (label, status2, message3 = null) => {
|
|
7608
|
+
checks.push({ label, status: status2, message: message3 });
|
|
7466
7609
|
};
|
|
7467
7610
|
const derive = () => ({
|
|
7468
7611
|
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7469
7612
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7470
7613
|
checks
|
|
7471
7614
|
});
|
|
7472
|
-
if (!await exists(
|
|
7615
|
+
if (!await exists(join24(root, "org"))) return derive();
|
|
7473
7616
|
const targets = [
|
|
7474
7617
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
7475
7618
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -7478,7 +7621,7 @@ async function checkBlocks({
|
|
|
7478
7621
|
const segments = entry.path.split("/");
|
|
7479
7622
|
targets.push({
|
|
7480
7623
|
scope: name,
|
|
7481
|
-
dir:
|
|
7624
|
+
dir: join24(root, ...segments),
|
|
7482
7625
|
depth: segments.length,
|
|
7483
7626
|
file: "AGENTS.md",
|
|
7484
7627
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -7493,7 +7636,7 @@ async function checkBlocks({
|
|
|
7493
7636
|
);
|
|
7494
7637
|
continue;
|
|
7495
7638
|
}
|
|
7496
|
-
const text = await readOrNull(
|
|
7639
|
+
const text = await readOrNull(join24(t.dir, t.file));
|
|
7497
7640
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
7498
7641
|
const found = findBlock(lines);
|
|
7499
7642
|
if (found.kind === "none") {
|
|
@@ -7564,7 +7707,7 @@ async function logoutCommand() {
|
|
|
7564
7707
|
import { execFile as execFile6 } from "child_process";
|
|
7565
7708
|
import { mkdtemp as mkdtemp3, readFile as readFile10, rm as rm8 } from "fs/promises";
|
|
7566
7709
|
import { tmpdir as tmpdir3 } from "os";
|
|
7567
|
-
import { basename as basename5, join as
|
|
7710
|
+
import { basename as basename5, join as join25, relative as relative5 } from "path";
|
|
7568
7711
|
import { promisify as promisify6 } from "util";
|
|
7569
7712
|
var execFileP6 = promisify6(execFile6);
|
|
7570
7713
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -7584,10 +7727,10 @@ async function latestVersion() {
|
|
|
7584
7727
|
async function scaffoldFor(version, rnessDir) {
|
|
7585
7728
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
7586
7729
|
const installed = await readPinnedCli(rnessDir);
|
|
7587
|
-
const inside2 = installed === null ? null :
|
|
7588
|
-
if (installed?.version === version && inside2 !== null && await exists(
|
|
7730
|
+
const inside2 = installed === null ? null : join25(installed.dir, "scaffold");
|
|
7731
|
+
if (installed?.version === version && inside2 !== null && await exists(join25(inside2, "package.json")))
|
|
7589
7732
|
return { dir: inside2 };
|
|
7590
|
-
const scratch = await mkdtemp3(
|
|
7733
|
+
const scratch = await mkdtemp3(join25(tmpdir3(), "rness-pack-"));
|
|
7591
7734
|
const cleanup = () => rm8(scratch, { recursive: true, force: true });
|
|
7592
7735
|
try {
|
|
7593
7736
|
const { stdout } = await execFileP6(
|
|
@@ -7602,8 +7745,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7602
7745
|
{ timeout: 6e4 }
|
|
7603
7746
|
);
|
|
7604
7747
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
7605
|
-
await execFileP6("tar", ["-xzf",
|
|
7606
|
-
return { dir:
|
|
7748
|
+
await execFileP6("tar", ["-xzf", join25(scratch, tarball), "-C", scratch]);
|
|
7749
|
+
return { dir: join25(scratch, "package", "scaffold"), cleanup };
|
|
7607
7750
|
} catch (e) {
|
|
7608
7751
|
await cleanup();
|
|
7609
7752
|
throw new Error(
|
|
@@ -7615,7 +7758,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7615
7758
|
async function declaredManager(rnessDir, pm) {
|
|
7616
7759
|
try {
|
|
7617
7760
|
const pkg = JSON.parse(
|
|
7618
|
-
await readFile10(
|
|
7761
|
+
await readFile10(join25(rnessDir, "package.json"), "utf8")
|
|
7619
7762
|
);
|
|
7620
7763
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
7621
7764
|
} catch {
|
|
@@ -7625,7 +7768,7 @@ async function declaredManager(rnessDir, pm) {
|
|
|
7625
7768
|
async function clonesToCommit(root, cwd, manifest, files) {
|
|
7626
7769
|
const clones = [];
|
|
7627
7770
|
for (const repo of Object.keys(manifest.repos)) {
|
|
7628
|
-
const dir =
|
|
7771
|
+
const dir = join25(root, "org", repo);
|
|
7629
7772
|
if (!await exists(dir)) continue;
|
|
7630
7773
|
try {
|
|
7631
7774
|
const paths = await changedPaths(dir, files);
|
|
@@ -7643,7 +7786,7 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7643
7786
|
try {
|
|
7644
7787
|
const { stdout } = await execFileP6(
|
|
7645
7788
|
process.execPath,
|
|
7646
|
-
[
|
|
7789
|
+
[join25(pinned.dir, pinned.bin), "written-files", "--cwd", root],
|
|
7647
7790
|
{
|
|
7648
7791
|
cwd: root,
|
|
7649
7792
|
env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
|
|
@@ -7659,14 +7802,14 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7659
7802
|
}
|
|
7660
7803
|
function finish(ui, cwd, rnessDir, label, target2, ending) {
|
|
7661
7804
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
7662
|
-
const
|
|
7805
|
+
const message3 = `chore: rness ${target2}`;
|
|
7663
7806
|
const next = [];
|
|
7664
7807
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
7665
7808
|
if (ending.commit === "refused")
|
|
7666
|
-
next.push(`git -C ${rel} commit -m "${
|
|
7809
|
+
next.push(`git -C ${rel} commit -m "${message3}"`);
|
|
7667
7810
|
for (const c of ending.clones)
|
|
7668
7811
|
next.push(
|
|
7669
|
-
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${
|
|
7812
|
+
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message3}"`
|
|
7670
7813
|
);
|
|
7671
7814
|
if (ending.commit !== "nothing")
|
|
7672
7815
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
@@ -7839,7 +7982,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7839
7982
|
await source.cleanup?.();
|
|
7840
7983
|
}
|
|
7841
7984
|
}
|
|
7842
|
-
const packageFile =
|
|
7985
|
+
const packageFile = join25(ws.rnessDir, "package.json");
|
|
7843
7986
|
const restore = /* @__PURE__ */ new Map([
|
|
7844
7987
|
[packageFile, await readFile10(packageFile, "utf8")]
|
|
7845
7988
|
]);
|
|
@@ -7855,8 +7998,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7855
7998
|
() => ["installed", `dependencies with ${pm}`]
|
|
7856
7999
|
);
|
|
7857
8000
|
} catch (e) {
|
|
7858
|
-
const
|
|
7859
|
-
ui.error(
|
|
8001
|
+
const message4 = e instanceof Error ? e.message : String(e);
|
|
8002
|
+
ui.error(message4);
|
|
7860
8003
|
if (!merged) {
|
|
7861
8004
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
7862
8005
|
ui.hint(
|
|
@@ -7869,7 +8012,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7869
8012
|
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
7870
8013
|
"stderr"
|
|
7871
8014
|
);
|
|
7872
|
-
if (/minimum.?release.?age|NO_MATURE/i.test(
|
|
8015
|
+
if (/minimum.?release.?age|NO_MATURE/i.test(message4))
|
|
7873
8016
|
ui.hint(
|
|
7874
8017
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
7875
8018
|
"stderr"
|
|
@@ -7888,13 +8031,13 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7888
8031
|
ui.gitIsSilent = true;
|
|
7889
8032
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
7890
8033
|
if (code !== 0) return code;
|
|
7891
|
-
const
|
|
7892
|
-
const committed = await commitUpgrade(ws.rnessDir,
|
|
8034
|
+
const message3 = `chore: rness ${target2}`;
|
|
8035
|
+
const committed = await commitUpgrade(ws.rnessDir, message3);
|
|
7893
8036
|
if (committed.kind === "committed")
|
|
7894
8037
|
ui.line(
|
|
7895
8038
|
"committed",
|
|
7896
|
-
`${label} \u2014 ${
|
|
7897
|
-
`Committed ${label}: ${
|
|
8039
|
+
`${label} \u2014 ${message3}`,
|
|
8040
|
+
`Committed ${label}: ${message3}`
|
|
7898
8041
|
);
|
|
7899
8042
|
if (committed.kind === "refused") {
|
|
7900
8043
|
ui.error(
|
|
@@ -8008,8 +8151,8 @@ async function validateCommand(opts, deps = {}) {
|
|
|
8008
8151
|
import { Command, CommanderError, Option } from "commander";
|
|
8009
8152
|
|
|
8010
8153
|
// src/commands/doc.ts
|
|
8011
|
-
import {
|
|
8012
|
-
import { join as
|
|
8154
|
+
import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
|
|
8155
|
+
import { join as join26 } from "path";
|
|
8013
8156
|
var NUMBERED = Object.keys(STATUSES);
|
|
8014
8157
|
var SECTIONS = {
|
|
8015
8158
|
specs: [
|
|
@@ -8033,8 +8176,8 @@ function headingsOf(template) {
|
|
|
8033
8176
|
return [...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim());
|
|
8034
8177
|
}
|
|
8035
8178
|
async function adrSections(rnessDir) {
|
|
8036
|
-
const own =
|
|
8037
|
-
const shipped =
|
|
8179
|
+
const own = join26(rnessDir, "adr", "0000-template.md");
|
|
8180
|
+
const shipped = join26(scaffoldDir(), "adr", "0000-template.md");
|
|
8038
8181
|
for (const file of [own, shipped]) {
|
|
8039
8182
|
try {
|
|
8040
8183
|
const sections = headingsOf(await readFile11(file, "utf8"));
|
|
@@ -8044,6 +8187,19 @@ async function adrSections(rnessDir) {
|
|
|
8044
8187
|
}
|
|
8045
8188
|
return ["Context", "Decision", "Alternatives Considered", "Consequences"];
|
|
8046
8189
|
}
|
|
8190
|
+
async function writeDocument(file, text) {
|
|
8191
|
+
try {
|
|
8192
|
+
await writeFile4(file, text, { flag: "wx" });
|
|
8193
|
+
} catch (e) {
|
|
8194
|
+
if (e.code !== "EEXIST") throw e;
|
|
8195
|
+
process.stderr.write(`${file} exists; nothing written
|
|
8196
|
+
`);
|
|
8197
|
+
return 1;
|
|
8198
|
+
}
|
|
8199
|
+
process.stdout.write(`${file}
|
|
8200
|
+
`);
|
|
8201
|
+
return 0;
|
|
8202
|
+
}
|
|
8047
8203
|
async function docNewCommand(collection, opts) {
|
|
8048
8204
|
if (!NUMBERED.includes(collection)) {
|
|
8049
8205
|
process.stderr.write(
|
|
@@ -8055,7 +8211,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8055
8211
|
const name = collection;
|
|
8056
8212
|
try {
|
|
8057
8213
|
const ws = await findWorkspace(opts.cwd ?? process.cwd());
|
|
8058
|
-
const dir =
|
|
8214
|
+
const dir = join26(ws.rnessDir, name);
|
|
8059
8215
|
await mkdir7(dir, { recursive: true });
|
|
8060
8216
|
let highest = 0;
|
|
8061
8217
|
for (const item of await collectMarkdown(dir)) {
|
|
@@ -8064,7 +8220,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8064
8220
|
}
|
|
8065
8221
|
const number = String(highest + 1).padStart(4, "0");
|
|
8066
8222
|
const title = opts.title?.trim() || "Untitled";
|
|
8067
|
-
const file =
|
|
8223
|
+
const file = join26(dir, `${number}-${slugOf(title)}.md`);
|
|
8068
8224
|
const status2 = STATUSES[name]?.[0] ?? "Draft";
|
|
8069
8225
|
const date = today();
|
|
8070
8226
|
const front = [
|
|
@@ -8083,20 +8239,8 @@ async function docNewCommand(collection, opts) {
|
|
|
8083
8239
|
...name === "plans" ? [] : ["", "---"],
|
|
8084
8240
|
...sections.flatMap((s) => ["", `## ${s}`])
|
|
8085
8241
|
];
|
|
8086
|
-
|
|
8087
|
-
await access2(file);
|
|
8088
|
-
process.stderr.write(`${file} exists; nothing written
|
|
8089
|
-
`);
|
|
8090
|
-
return 1;
|
|
8091
|
-
} catch {
|
|
8092
|
-
}
|
|
8093
|
-
await writeFile4(file, `${[...front, "", ...body].join("\n")}
|
|
8094
|
-
`, {
|
|
8095
|
-
flag: "wx"
|
|
8096
|
-
});
|
|
8097
|
-
process.stdout.write(`${file}
|
|
8242
|
+
return await writeDocument(file, `${[...front, "", ...body].join("\n")}
|
|
8098
8243
|
`);
|
|
8099
|
-
return 0;
|
|
8100
8244
|
} catch (e) {
|
|
8101
8245
|
return reportError(e);
|
|
8102
8246
|
}
|
|
@@ -8129,9 +8273,243 @@ password=${resolved.token}
|
|
|
8129
8273
|
import { realpath as realpath4 } from "fs/promises";
|
|
8130
8274
|
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
8131
8275
|
|
|
8276
|
+
// src/core/claude-code.ts
|
|
8277
|
+
import { mkdir as mkdir8, readFile as readFile12 } from "fs/promises";
|
|
8278
|
+
import { join as join27 } from "path";
|
|
8279
|
+
var MOD_FLOOR = "2.1.280";
|
|
8280
|
+
var AGENT = /^claude-code_(\d+)-(\d+)-(\d+)_[a-z]+$/;
|
|
8281
|
+
function claudeCodeVersion(env) {
|
|
8282
|
+
const m = AGENT.exec(env["AI_AGENT"] ?? "");
|
|
8283
|
+
return m === null ? null : `${m[1]}.${m[2]}.${m[3]}`;
|
|
8284
|
+
}
|
|
8285
|
+
function belowModFloor(version) {
|
|
8286
|
+
const have = version.split(".").map(Number);
|
|
8287
|
+
const floor = MOD_FLOOR.split(".").map(Number);
|
|
8288
|
+
for (let i = 0; i < 3; i++) {
|
|
8289
|
+
const a = have[i] ?? 0;
|
|
8290
|
+
const b = floor[i] ?? 0;
|
|
8291
|
+
if (a !== b) return a < b;
|
|
8292
|
+
}
|
|
8293
|
+
return false;
|
|
8294
|
+
}
|
|
8295
|
+
var noticeFile = () => join27(configDir(), "claude-code.json");
|
|
8296
|
+
async function shownVersions() {
|
|
8297
|
+
try {
|
|
8298
|
+
const data = JSON.parse(await readFile12(noticeFile(), "utf8"));
|
|
8299
|
+
const shown = data !== null && typeof data === "object" ? data["modNoticeShown"] : void 0;
|
|
8300
|
+
return Array.isArray(shown) ? shown.filter((v) => typeof v === "string") : [];
|
|
8301
|
+
} catch {
|
|
8302
|
+
return [];
|
|
8303
|
+
}
|
|
8304
|
+
}
|
|
8305
|
+
async function takeModNotice(env) {
|
|
8306
|
+
const version = claudeCodeVersion(env);
|
|
8307
|
+
if (version === null || !belowModFloor(version)) return null;
|
|
8308
|
+
if ((env["CLAUDE_CODE_ENTRYPOINT"] ?? "").startsWith("sdk-")) return null;
|
|
8309
|
+
const shown = await shownVersions();
|
|
8310
|
+
if (shown.includes(version)) return null;
|
|
8311
|
+
try {
|
|
8312
|
+
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8313
|
+
await writeFileAtomic(
|
|
8314
|
+
noticeFile(),
|
|
8315
|
+
`${JSON.stringify({ modNoticeShown: [...shown, version] })}
|
|
8316
|
+
`
|
|
8317
|
+
);
|
|
8318
|
+
} catch {
|
|
8319
|
+
}
|
|
8320
|
+
return `rness: Claude Code ${version} shows no rness band, status line or pane; ${MOD_FLOOR} or later does \u2014 claude update`;
|
|
8321
|
+
}
|
|
8322
|
+
|
|
8323
|
+
// src/core/edit-guard.ts
|
|
8324
|
+
function occurrences(text, needle) {
|
|
8325
|
+
const spans = [];
|
|
8326
|
+
for (let i = text.indexOf(needle); i !== -1; i = text.indexOf(needle, i + needle.length))
|
|
8327
|
+
spans.push([i, i + needle.length]);
|
|
8328
|
+
return spans;
|
|
8329
|
+
}
|
|
8330
|
+
var QUOTES = {
|
|
8331
|
+
"\u2018": "'",
|
|
8332
|
+
"\u2019": "'",
|
|
8333
|
+
"\u201C": '"',
|
|
8334
|
+
"\u201D": '"'
|
|
8335
|
+
};
|
|
8336
|
+
function loose(text) {
|
|
8337
|
+
let out = "";
|
|
8338
|
+
const at = [];
|
|
8339
|
+
for (let i = 0; i < text.length; i++) {
|
|
8340
|
+
const c = text[i] ?? "";
|
|
8341
|
+
if (c === "\r" && text[i + 1] === "\n") continue;
|
|
8342
|
+
out += QUOTES[c] ?? c;
|
|
8343
|
+
at.push(i);
|
|
8344
|
+
}
|
|
8345
|
+
at.push(text.length);
|
|
8346
|
+
return { text: out, at };
|
|
8347
|
+
}
|
|
8348
|
+
function replayEdit(text, oldString, newString, all) {
|
|
8349
|
+
if (oldString === "") return null;
|
|
8350
|
+
let spans = occurrences(text, oldString);
|
|
8351
|
+
if (spans.length === 0) {
|
|
8352
|
+
const file = loose(text);
|
|
8353
|
+
spans = occurrences(file.text, loose(oldString).text).map(
|
|
8354
|
+
([s, e]) => [file.at[s] ?? 0, (file.at[e - 1] ?? 0) + 1]
|
|
8355
|
+
);
|
|
8356
|
+
}
|
|
8357
|
+
if (spans.length === 0 || !all && spans.length !== 1) return null;
|
|
8358
|
+
let after = text;
|
|
8359
|
+
for (const [s, e] of [...spans].reverse())
|
|
8360
|
+
after = `${after.slice(0, s)}${newString}${after.slice(e)}`;
|
|
8361
|
+
return { after, spans };
|
|
8362
|
+
}
|
|
8363
|
+
var OWNED = /(?:^|\/)\.claude\/skills\/rness\//;
|
|
8364
|
+
var ownedWhole = (rel) => OWNED.test(rel);
|
|
8365
|
+
function ownedRefusal(label) {
|
|
8366
|
+
return `rness: ${label} is written whole by rness sync, and the next sync overwrites it. Leave it to rness.`;
|
|
8367
|
+
}
|
|
8368
|
+
var MARKER = /^<!-- rness: (\S+) -->$/;
|
|
8369
|
+
function blockOf(text) {
|
|
8370
|
+
const lines = text.split(/\r?\n/);
|
|
8371
|
+
const found = findBlock(lines);
|
|
8372
|
+
if (found.kind !== "one") return null;
|
|
8373
|
+
const starts = [0];
|
|
8374
|
+
for (const m of text.matchAll(/\r?\n/g))
|
|
8375
|
+
starts.push((m.index ?? 0) + m[0].length);
|
|
8376
|
+
const from = starts[found.begin] ?? 0;
|
|
8377
|
+
const to = (starts[found.end] ?? 0) + (lines[found.end] ?? "").length;
|
|
8378
|
+
return { begin: found.begin, end: found.end, from, to, lines };
|
|
8379
|
+
}
|
|
8380
|
+
function lineAt(text, offset) {
|
|
8381
|
+
return text.slice(0, offset).split(/\r?\n/).length - 1;
|
|
8382
|
+
}
|
|
8383
|
+
function sourceOf(block, line3) {
|
|
8384
|
+
for (let i = Math.min(line3, block.end); i > block.begin; i--) {
|
|
8385
|
+
const m = MARKER.exec((block.lines[i] ?? "").trimEnd());
|
|
8386
|
+
if (m !== null) return `.rness/${m[1] ?? ""}`;
|
|
8387
|
+
}
|
|
8388
|
+
return ".rness/ and rness.json";
|
|
8389
|
+
}
|
|
8390
|
+
function blockRefusal(label, block, source) {
|
|
8391
|
+
return `rness: lines ${block.begin + 1}\u2013${block.end + 1} of ${label} are generated by rness sync from ${source}. Edit that file, then run rness sync.`;
|
|
8392
|
+
}
|
|
8393
|
+
function editRefusal(label, before, spans) {
|
|
8394
|
+
const block = blockOf(before);
|
|
8395
|
+
if (block === null) return null;
|
|
8396
|
+
const hit = spans.find(([s, e]) => s < block.to && e > block.from);
|
|
8397
|
+
if (hit === void 0) return null;
|
|
8398
|
+
return blockRefusal(
|
|
8399
|
+
label,
|
|
8400
|
+
block,
|
|
8401
|
+
sourceOf(block, lineAt(before, Math.max(hit[0], block.from)))
|
|
8402
|
+
);
|
|
8403
|
+
}
|
|
8404
|
+
function writeRefusal(label, before, after) {
|
|
8405
|
+
const block = blockOf(before);
|
|
8406
|
+
if (block === null) return null;
|
|
8407
|
+
const kept = blockOf(after);
|
|
8408
|
+
const was = before.slice(block.from, block.to);
|
|
8409
|
+
if (kept !== null && after.slice(kept.from, kept.to) === was) return null;
|
|
8410
|
+
const next = kept === null ? [] : kept.lines.slice(kept.begin, kept.end + 1);
|
|
8411
|
+
const old = block.lines.slice(block.begin, block.end + 1);
|
|
8412
|
+
const first = old.findIndex((line3, i) => line3 !== next[i]);
|
|
8413
|
+
return blockRefusal(
|
|
8414
|
+
label,
|
|
8415
|
+
block,
|
|
8416
|
+
kept === null ? ".rness/ and rness.json" : sourceOf(block, block.begin + Math.max(first, 0))
|
|
8417
|
+
);
|
|
8418
|
+
}
|
|
8419
|
+
|
|
8420
|
+
// src/core/safety-net.ts
|
|
8421
|
+
var SHOWN_PROBLEMS = 3;
|
|
8422
|
+
var message = (e) => e instanceof Error ? e.message : String(e);
|
|
8423
|
+
async function safetyNet(ws, manifest) {
|
|
8424
|
+
const notes = [];
|
|
8425
|
+
const drift = await pinDrift(ws.rnessDir);
|
|
8426
|
+
if (drift !== null)
|
|
8427
|
+
notes.push(
|
|
8428
|
+
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
8429
|
+
);
|
|
8430
|
+
try {
|
|
8431
|
+
const { problems } = await checkWorkspace(ws, manifest);
|
|
8432
|
+
if (problems.length > 0)
|
|
8433
|
+
notes.push(
|
|
8434
|
+
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
8435
|
+
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
8436
|
+
);
|
|
8437
|
+
} catch (e) {
|
|
8438
|
+
notes.push(message(e));
|
|
8439
|
+
}
|
|
8440
|
+
return notes;
|
|
8441
|
+
}
|
|
8442
|
+
|
|
8443
|
+
// src/pulse/detached.ts
|
|
8444
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
8445
|
+
import { mkdir as mkdir9, open as open2, readFile as readFile13, rm as rm9, stat as stat4, writeFile as writeFile5 } from "fs/promises";
|
|
8446
|
+
import { join as join28 } from "path";
|
|
8447
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
8448
|
+
var detached = (args, cwd) => {
|
|
8449
|
+
const bin = process.argv[1];
|
|
8450
|
+
if (bin === void 0) return;
|
|
8451
|
+
const child = nodeSpawn(process.execPath, [bin, ...args], {
|
|
8452
|
+
detached: true,
|
|
8453
|
+
stdio: "ignore",
|
|
8454
|
+
cwd,
|
|
8455
|
+
env: { ...process.env, RNESS_NO_DELEGATE: "1" }
|
|
8456
|
+
});
|
|
8457
|
+
child.on("error", () => void 0);
|
|
8458
|
+
child.unref();
|
|
8459
|
+
};
|
|
8460
|
+
var failureFile = () => join28(configDir(), "pulse.json");
|
|
8461
|
+
async function recordFailure(reason3) {
|
|
8462
|
+
try {
|
|
8463
|
+
await mkdir9(configDir(), { recursive: true, mode: 448 });
|
|
8464
|
+
await writeFile5(
|
|
8465
|
+
failureFile(),
|
|
8466
|
+
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), reason: reason3 })}
|
|
8467
|
+
`
|
|
8468
|
+
);
|
|
8469
|
+
} catch {
|
|
8470
|
+
}
|
|
8471
|
+
}
|
|
8472
|
+
async function takeFailure() {
|
|
8473
|
+
try {
|
|
8474
|
+
const data = JSON.parse(await readFile13(failureFile(), "utf8"));
|
|
8475
|
+
await rm9(failureFile(), { force: true });
|
|
8476
|
+
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
8477
|
+
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
8478
|
+
} catch {
|
|
8479
|
+
return null;
|
|
8480
|
+
}
|
|
8481
|
+
}
|
|
8482
|
+
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
8483
|
+
async function oneSyncAtATime(key, work, options = {}) {
|
|
8484
|
+
await mkdir9(configDir(), { recursive: true, mode: 448 });
|
|
8485
|
+
const file = join28(configDir(), `pulse-${key}.lock`);
|
|
8486
|
+
for (; ; ) {
|
|
8487
|
+
try {
|
|
8488
|
+
const handle = await open2(file, "wx");
|
|
8489
|
+
await handle.close();
|
|
8490
|
+
break;
|
|
8491
|
+
} catch (e) {
|
|
8492
|
+
if (e.code !== "EEXIST") throw e;
|
|
8493
|
+
const age = await stat4(file).then(
|
|
8494
|
+
(s) => Date.now() - s.mtimeMs,
|
|
8495
|
+
() => 0
|
|
8496
|
+
);
|
|
8497
|
+
if (age > PULSE_LOCK_STALE_MS) await rm9(file, { force: true });
|
|
8498
|
+
else if (options.wait !== true) return false;
|
|
8499
|
+
else await (options.sleep ?? ((ms) => delay2(ms)))(1e3);
|
|
8500
|
+
}
|
|
8501
|
+
}
|
|
8502
|
+
try {
|
|
8503
|
+
await work();
|
|
8504
|
+
return true;
|
|
8505
|
+
} finally {
|
|
8506
|
+
await rm9(file, { force: true });
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
|
|
8132
8510
|
// src/mcp/tools.ts
|
|
8133
|
-
import { readFile as
|
|
8134
|
-
import { isAbsolute, join as
|
|
8511
|
+
import { readFile as readFile14, realpath as realpath3, stat as stat5 } from "fs/promises";
|
|
8512
|
+
import { isAbsolute, join as join29, posix as posix7, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
8135
8513
|
var LABELS = {
|
|
8136
8514
|
standards: "Standards",
|
|
8137
8515
|
adr: "Decisions",
|
|
@@ -8160,7 +8538,7 @@ async function documents(rnessDir, only) {
|
|
|
8160
8538
|
const docs = [];
|
|
8161
8539
|
for (const collection of COLLECTIONS) {
|
|
8162
8540
|
if (only !== void 0 && collection !== only) continue;
|
|
8163
|
-
for (const item of await collectMarkdown(
|
|
8541
|
+
for (const item of await collectMarkdown(join29(rnessDir, collection))) {
|
|
8164
8542
|
const path = `${collection}/${item.rel}`;
|
|
8165
8543
|
if (path === TEMPLATE2) continue;
|
|
8166
8544
|
const status2 = item.fields?.["status"];
|
|
@@ -8276,13 +8654,13 @@ async function read(cwd, args) {
|
|
|
8276
8654
|
}
|
|
8277
8655
|
if (!real.startsWith(`${root}${sep4}`))
|
|
8278
8656
|
return refuse(`${path}: leads outside .rness/`);
|
|
8279
|
-
const info = await
|
|
8657
|
+
const info = await stat5(real);
|
|
8280
8658
|
if (!info.isFile()) return refuse(`${path}: not a file`);
|
|
8281
8659
|
if (info.size > READ_LIMIT)
|
|
8282
8660
|
return refuse(
|
|
8283
8661
|
`${path}: ${info.size} bytes, over the 256 KiB rness_read returns`
|
|
8284
8662
|
);
|
|
8285
|
-
return { text: await
|
|
8663
|
+
return { text: await readFile14(real, "utf8") };
|
|
8286
8664
|
}
|
|
8287
8665
|
async function search(cwd, args) {
|
|
8288
8666
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -8388,74 +8766,7 @@ function mcpTools(cwd) {
|
|
|
8388
8766
|
];
|
|
8389
8767
|
}
|
|
8390
8768
|
|
|
8391
|
-
// src/
|
|
8392
|
-
import { spawn as nodeSpawn } from "child_process";
|
|
8393
|
-
import { mkdir as mkdir8, open as open2, readFile as readFile13, rm as rm9, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
8394
|
-
import { join as join27 } from "path";
|
|
8395
|
-
import { setTimeout as delay2 } from "timers/promises";
|
|
8396
|
-
var detached = (args, cwd) => {
|
|
8397
|
-
const bin = process.argv[1];
|
|
8398
|
-
if (bin === void 0) return;
|
|
8399
|
-
const child = nodeSpawn(process.execPath, [bin, ...args], {
|
|
8400
|
-
detached: true,
|
|
8401
|
-
stdio: "ignore",
|
|
8402
|
-
cwd,
|
|
8403
|
-
env: { ...process.env, RNESS_NO_DELEGATE: "1" }
|
|
8404
|
-
});
|
|
8405
|
-
child.on("error", () => void 0);
|
|
8406
|
-
child.unref();
|
|
8407
|
-
};
|
|
8408
|
-
var failureFile = () => join27(configDir(), "pulse.json");
|
|
8409
|
-
async function recordFailure(reason3) {
|
|
8410
|
-
try {
|
|
8411
|
-
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8412
|
-
await writeFile5(
|
|
8413
|
-
failureFile(),
|
|
8414
|
-
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), reason: reason3 })}
|
|
8415
|
-
`
|
|
8416
|
-
);
|
|
8417
|
-
} catch {
|
|
8418
|
-
}
|
|
8419
|
-
}
|
|
8420
|
-
async function takeFailure() {
|
|
8421
|
-
try {
|
|
8422
|
-
const data = JSON.parse(await readFile13(failureFile(), "utf8"));
|
|
8423
|
-
await rm9(failureFile(), { force: true });
|
|
8424
|
-
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
8425
|
-
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
8426
|
-
} catch {
|
|
8427
|
-
return null;
|
|
8428
|
-
}
|
|
8429
|
-
}
|
|
8430
|
-
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
8431
|
-
async function oneSyncAtATime(key, work, options = {}) {
|
|
8432
|
-
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8433
|
-
const file = join27(configDir(), `pulse-${key}.lock`);
|
|
8434
|
-
for (; ; ) {
|
|
8435
|
-
try {
|
|
8436
|
-
const handle = await open2(file, "wx");
|
|
8437
|
-
await handle.close();
|
|
8438
|
-
break;
|
|
8439
|
-
} catch (e) {
|
|
8440
|
-
if (e.code !== "EEXIST") throw e;
|
|
8441
|
-
const age = await stat5(file).then(
|
|
8442
|
-
(s) => Date.now() - s.mtimeMs,
|
|
8443
|
-
() => 0
|
|
8444
|
-
);
|
|
8445
|
-
if (age > PULSE_LOCK_STALE_MS) await rm9(file, { force: true });
|
|
8446
|
-
else if (options.wait !== true) return false;
|
|
8447
|
-
else await (options.sleep ?? ((ms) => delay2(ms)))(1e3);
|
|
8448
|
-
}
|
|
8449
|
-
}
|
|
8450
|
-
try {
|
|
8451
|
-
await work();
|
|
8452
|
-
return true;
|
|
8453
|
-
} finally {
|
|
8454
|
-
await rm9(file, { force: true });
|
|
8455
|
-
}
|
|
8456
|
-
}
|
|
8457
|
-
|
|
8458
|
-
// src/commands/hook.ts
|
|
8769
|
+
// src/commands/snapshot.ts
|
|
8459
8770
|
var NOUNS = {
|
|
8460
8771
|
standards: ["standard", "standards"],
|
|
8461
8772
|
adr: ["decision", "decisions"],
|
|
@@ -8463,7 +8774,37 @@ var NOUNS = {
|
|
|
8463
8774
|
plans: ["plan", "plans"],
|
|
8464
8775
|
skills: ["skill", "skills"]
|
|
8465
8776
|
};
|
|
8466
|
-
|
|
8777
|
+
async function scopeBanner(ws, manifest, scope) {
|
|
8778
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
8779
|
+
const counts = COLLECTIONS.flatMap((c) => {
|
|
8780
|
+
const n = summary.counts[c];
|
|
8781
|
+
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
8782
|
+
});
|
|
8783
|
+
const banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
|
|
8784
|
+
return { banner: banner2, lines: summary.lines };
|
|
8785
|
+
}
|
|
8786
|
+
var noteLines = (notes) => notes.map((n) => `rness: ${n}`);
|
|
8787
|
+
function statusLine(scope, inProgress, notes) {
|
|
8788
|
+
const acting = notes.filter((n) => !n.startsWith(" ")).length;
|
|
8789
|
+
return `${scope ?? "global"} \xB7 ${inProgress.length} in progress${acting > 0 ? ` \xB7 \u26A0 ${acting}` : ""}`;
|
|
8790
|
+
}
|
|
8791
|
+
async function statusSnapshot(ws, manifest, scope, tabs) {
|
|
8792
|
+
const { banner: banner2 } = await scopeBanner(ws, manifest, scope);
|
|
8793
|
+
const inProgress = await plansInProgress(ws, manifest, scope);
|
|
8794
|
+
const notes = await safetyNet(ws, manifest);
|
|
8795
|
+
return {
|
|
8796
|
+
version: VERSION,
|
|
8797
|
+
workspace: workspaceName(manifest, ws.root),
|
|
8798
|
+
scope,
|
|
8799
|
+
banner: banner2,
|
|
8800
|
+
statusLine: statusLine(scope, inProgress, notes),
|
|
8801
|
+
inProgress,
|
|
8802
|
+
notes: noteLines(notes),
|
|
8803
|
+
tabs
|
|
8804
|
+
};
|
|
8805
|
+
}
|
|
8806
|
+
|
|
8807
|
+
// src/commands/hook.ts
|
|
8467
8808
|
async function readInput(input) {
|
|
8468
8809
|
if (input.isTTY === true) return {};
|
|
8469
8810
|
let text = "";
|
|
@@ -8475,35 +8816,13 @@ async function readInput(input) {
|
|
|
8475
8816
|
return {};
|
|
8476
8817
|
}
|
|
8477
8818
|
}
|
|
8478
|
-
var
|
|
8819
|
+
var message2 = (e) => e instanceof Error ? e.message : String(e);
|
|
8479
8820
|
function cwdOf(input, env) {
|
|
8480
8821
|
const cwd = input["cwd"];
|
|
8481
8822
|
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
8482
8823
|
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
8483
8824
|
}
|
|
8484
8825
|
var toPosix2 = (p) => p.split(sep5).join(posix8.sep);
|
|
8485
|
-
function count(n, [one, many]) {
|
|
8486
|
-
return `${n} ${n === 1 ? one : many}`;
|
|
8487
|
-
}
|
|
8488
|
-
async function safetyNet(ws, manifest) {
|
|
8489
|
-
const notes = [];
|
|
8490
|
-
const drift = await pinDrift(ws.rnessDir);
|
|
8491
|
-
if (drift !== null)
|
|
8492
|
-
notes.push(
|
|
8493
|
-
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
8494
|
-
);
|
|
8495
|
-
try {
|
|
8496
|
-
const { problems } = await checkWorkspace(ws, manifest);
|
|
8497
|
-
if (problems.length > 0)
|
|
8498
|
-
notes.push(
|
|
8499
|
-
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
8500
|
-
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
8501
|
-
);
|
|
8502
|
-
} catch (e) {
|
|
8503
|
-
notes.push(message(e));
|
|
8504
|
-
}
|
|
8505
|
-
return notes;
|
|
8506
|
-
}
|
|
8507
8826
|
function sessionOf(input) {
|
|
8508
8827
|
const id = input["session_id"];
|
|
8509
8828
|
if (typeof id !== "string" || id === "") return null;
|
|
@@ -8531,21 +8850,6 @@ async function markDetached(ws, input, io, rest) {
|
|
|
8531
8850
|
} catch {
|
|
8532
8851
|
}
|
|
8533
8852
|
}
|
|
8534
|
-
async function plansInProgress(ws, manifest, scope) {
|
|
8535
|
-
const context3 = await assembleContext({
|
|
8536
|
-
rnessDir: ws.rnessDir,
|
|
8537
|
-
manifest,
|
|
8538
|
-
scope
|
|
8539
|
-
});
|
|
8540
|
-
const plans = context3.collections.find((c) => c.name === "plans");
|
|
8541
|
-
return (plans?.files ?? []).filter((f) => {
|
|
8542
|
-
try {
|
|
8543
|
-
return parseFrontMatter(f.body)?.["status"] === "In progress";
|
|
8544
|
-
} catch {
|
|
8545
|
-
return false;
|
|
8546
|
-
}
|
|
8547
|
-
}).map((f) => `plans/${f.rel}`);
|
|
8548
|
-
}
|
|
8549
8853
|
async function sessionStart(input, io) {
|
|
8550
8854
|
const cwd = cwdOf(input, io.env);
|
|
8551
8855
|
let banner2 = null;
|
|
@@ -8555,15 +8859,11 @@ async function sessionStart(input, io) {
|
|
|
8555
8859
|
const ws = await findWorkspace(cwd);
|
|
8556
8860
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8557
8861
|
const scope = resolveScope(manifest, toPosix2(relative7(ws.root, cwd)));
|
|
8558
|
-
const
|
|
8559
|
-
|
|
8560
|
-
const n = summary.counts[c];
|
|
8561
|
-
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
8562
|
-
});
|
|
8563
|
-
banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
|
|
8862
|
+
const view = await scopeBanner(ws, manifest, scope);
|
|
8863
|
+
banner2 = view.banner;
|
|
8564
8864
|
const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
|
|
8565
8865
|
context3 = [
|
|
8566
|
-
...
|
|
8866
|
+
...view.lines,
|
|
8567
8867
|
`These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
|
|
8568
8868
|
];
|
|
8569
8869
|
notes = await safetyNet(ws, manifest);
|
|
@@ -8575,14 +8875,17 @@ async function sessionStart(input, io) {
|
|
|
8575
8875
|
(session) => paths.length === 0 ? null : ["--session", session, ...paths.flatMap((p) => ["--path", p])]
|
|
8576
8876
|
);
|
|
8577
8877
|
} catch (e) {
|
|
8578
|
-
notes = [
|
|
8878
|
+
notes = [message2(e)];
|
|
8579
8879
|
}
|
|
8580
8880
|
const failure = await takeFailure();
|
|
8581
8881
|
if (failure !== null) notes.push(`pulse not updated \u2014 ${failure}`);
|
|
8582
|
-
const warnings = notes
|
|
8882
|
+
const warnings = noteLines(notes);
|
|
8883
|
+
const compact = input["source"] === "compact";
|
|
8884
|
+
const tooOld = compact ? null : await takeModNotice(io.env);
|
|
8583
8885
|
const shown = [
|
|
8584
|
-
...banner2 !== null &&
|
|
8585
|
-
...warnings
|
|
8886
|
+
...banner2 !== null && !compact ? [banner2] : [],
|
|
8887
|
+
...warnings,
|
|
8888
|
+
...tooOld !== null ? [tooOld] : []
|
|
8586
8889
|
];
|
|
8587
8890
|
return {
|
|
8588
8891
|
...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
|
|
@@ -8607,11 +8910,7 @@ async function postToolUse(input, io) {
|
|
|
8607
8910
|
} catch {
|
|
8608
8911
|
return 0;
|
|
8609
8912
|
}
|
|
8610
|
-
const
|
|
8611
|
-
const rel = inside(ws.rnessDir, file) ?? inside(
|
|
8612
|
-
await realpath4(ws.rnessDir).catch(() => ws.rnessDir),
|
|
8613
|
-
await realpath4(file).catch(() => file)
|
|
8614
|
-
);
|
|
8913
|
+
const rel = await within(ws.rnessDir, resolve7(cwd, path));
|
|
8615
8914
|
if (rel === null) return 0;
|
|
8616
8915
|
const first = rel.split("/")[0] ?? "";
|
|
8617
8916
|
let problems = [];
|
|
@@ -8620,7 +8919,7 @@ async function postToolUse(input, io) {
|
|
|
8620
8919
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8621
8920
|
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
8622
8921
|
} catch (e) {
|
|
8623
|
-
problems = [
|
|
8922
|
+
problems = [message2(e)];
|
|
8624
8923
|
}
|
|
8625
8924
|
} else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
|
|
8626
8925
|
problems = (await checkContract(ws.rnessDir)).filter(
|
|
@@ -8643,6 +8942,95 @@ async function postToolUse(input, io) {
|
|
|
8643
8942
|
);
|
|
8644
8943
|
return 2;
|
|
8645
8944
|
}
|
|
8945
|
+
async function within(base, file) {
|
|
8946
|
+
return inside(base, file) ?? inside(
|
|
8947
|
+
await realpath4(base).catch(() => base),
|
|
8948
|
+
await realpath4(file).catch(() => file)
|
|
8949
|
+
);
|
|
8950
|
+
}
|
|
8951
|
+
function manifestProblems(text) {
|
|
8952
|
+
if (text === null) return [];
|
|
8953
|
+
try {
|
|
8954
|
+
const manifest = parseManifest(text);
|
|
8955
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
8956
|
+
return [];
|
|
8957
|
+
} catch (e) {
|
|
8958
|
+
return [message2(e)];
|
|
8959
|
+
}
|
|
8960
|
+
}
|
|
8961
|
+
async function addedProblems(ws, rel, before, after) {
|
|
8962
|
+
let was;
|
|
8963
|
+
let will;
|
|
8964
|
+
if (rel === "rness.json") {
|
|
8965
|
+
was = manifestProblems(before);
|
|
8966
|
+
will = manifestProblems(after);
|
|
8967
|
+
} else {
|
|
8968
|
+
const first = rel.split("/")[0] ?? "";
|
|
8969
|
+
if (!COLLECTIONS.includes(first) || !rel.endsWith(".md"))
|
|
8970
|
+
return [];
|
|
8971
|
+
const mine = (p) => p.startsWith(`${rel}:`);
|
|
8972
|
+
was = (await checkContract(ws.rnessDir)).filter(mine);
|
|
8973
|
+
will = (await checkContract(ws.rnessDir, { rel, text: after })).filter(mine);
|
|
8974
|
+
}
|
|
8975
|
+
return will.filter((p) => !was.includes(p));
|
|
8976
|
+
}
|
|
8977
|
+
async function preToolUse(input, io) {
|
|
8978
|
+
const toolInput = input["tool_input"];
|
|
8979
|
+
if (toolInput === null || typeof toolInput !== "object") return 0;
|
|
8980
|
+
const args = toolInput;
|
|
8981
|
+
const path = args["file_path"];
|
|
8982
|
+
if (typeof path !== "string" || path === "") return 0;
|
|
8983
|
+
try {
|
|
8984
|
+
const cwd = cwdOf(input, io.env);
|
|
8985
|
+
const ws = await findWorkspace(cwd);
|
|
8986
|
+
const file = resolve7(cwd, path);
|
|
8987
|
+
const label = await within(ws.root, file);
|
|
8988
|
+
if (label === null) return 0;
|
|
8989
|
+
if (ownedWhole(label)) return refuse2(io, [ownedRefusal(label)]);
|
|
8990
|
+
const rel = await within(ws.rnessDir, file);
|
|
8991
|
+
const blocked = BLOCK_FILES.includes(posix8.basename(label));
|
|
8992
|
+
if (rel === null && !blocked) return 0;
|
|
8993
|
+
const before = await readOrNull(file);
|
|
8994
|
+
let after;
|
|
8995
|
+
let spans = null;
|
|
8996
|
+
if (input["tool_name"] === "Write") {
|
|
8997
|
+
const content = args["content"];
|
|
8998
|
+
if (typeof content !== "string") return 0;
|
|
8999
|
+
after = content;
|
|
9000
|
+
} else {
|
|
9001
|
+
const { old_string, new_string, replace_all } = args;
|
|
9002
|
+
if (before === null || typeof old_string !== "string" || typeof new_string !== "string")
|
|
9003
|
+
return 0;
|
|
9004
|
+
const replayed = replayEdit(
|
|
9005
|
+
before,
|
|
9006
|
+
old_string,
|
|
9007
|
+
new_string,
|
|
9008
|
+
replace_all === true
|
|
9009
|
+
);
|
|
9010
|
+
if (replayed === null) return 0;
|
|
9011
|
+
after = replayed.after;
|
|
9012
|
+
spans = replayed.spans;
|
|
9013
|
+
}
|
|
9014
|
+
if (blocked && before !== null) {
|
|
9015
|
+
const reason3 = spans === null ? writeRefusal(label, before, after) : editRefusal(label, before, spans);
|
|
9016
|
+
if (reason3 !== null) return refuse2(io, [reason3]);
|
|
9017
|
+
}
|
|
9018
|
+
if (rel === null) return 0;
|
|
9019
|
+
const added = await addedProblems(ws, rel, before, after);
|
|
9020
|
+
if (added.length === 0) return 0;
|
|
9021
|
+
return refuse2(io, [
|
|
9022
|
+
...added.map((p) => `rness: ${p}`),
|
|
9023
|
+
`Fix the edit of ${rel}; rness validate checks the whole workspace.`
|
|
9024
|
+
]);
|
|
9025
|
+
} catch {
|
|
9026
|
+
return 0;
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
function refuse2(io, lines) {
|
|
9030
|
+
io.error.write(`${lines.join("\n")}
|
|
9031
|
+
`);
|
|
9032
|
+
return 2;
|
|
9033
|
+
}
|
|
8646
9034
|
async function sessionEnd(input, io) {
|
|
8647
9035
|
try {
|
|
8648
9036
|
const ws = await findWorkspace(cwdOf(input, io.env));
|
|
@@ -8661,14 +9049,15 @@ async function hookCommand(event, io = {
|
|
|
8661
9049
|
error: process.stderr,
|
|
8662
9050
|
env: process.env
|
|
8663
9051
|
}) {
|
|
8664
|
-
if (event !== "session-start" && event !== "post-tool-use" && event !== "session-end") {
|
|
9052
|
+
if (event !== "session-start" && event !== "pre-tool-use" && event !== "post-tool-use" && event !== "session-end") {
|
|
8665
9053
|
io.error.write(
|
|
8666
|
-
`unknown hook event ${JSON.stringify(event)} (known: session-start, post-tool-use, session-end)
|
|
9054
|
+
`unknown hook event ${JSON.stringify(event)} (known: session-start, pre-tool-use, post-tool-use, session-end)
|
|
8667
9055
|
`
|
|
8668
9056
|
);
|
|
8669
9057
|
return 1;
|
|
8670
9058
|
}
|
|
8671
9059
|
const input = await readInput(io.input);
|
|
9060
|
+
if (event === "pre-tool-use") return preToolUse(input, io);
|
|
8672
9061
|
if (event === "post-tool-use") return postToolUse(input, io);
|
|
8673
9062
|
if (event === "session-end") return sessionEnd(input, io);
|
|
8674
9063
|
io.output.write(`${JSON.stringify(await sessionStart(input, io))}
|
|
@@ -8694,31 +9083,31 @@ var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
|
8694
9083
|
function isObject2(value) {
|
|
8695
9084
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8696
9085
|
}
|
|
8697
|
-
function errorLine(id, code,
|
|
9086
|
+
function errorLine(id, code, message3, data) {
|
|
8698
9087
|
return JSON.stringify({
|
|
8699
9088
|
jsonrpc: "2.0",
|
|
8700
9089
|
id,
|
|
8701
|
-
error: data === void 0 ? { code, message:
|
|
9090
|
+
error: data === void 0 ? { code, message: message3 } : { code, message: message3, data }
|
|
8702
9091
|
});
|
|
8703
9092
|
}
|
|
8704
9093
|
function createProtocol(opts) {
|
|
8705
9094
|
const serverInfo = { name: opts.name, version: opts.version };
|
|
8706
9095
|
return {
|
|
8707
9096
|
async handle(line3) {
|
|
8708
|
-
let
|
|
9097
|
+
let message3;
|
|
8709
9098
|
try {
|
|
8710
|
-
|
|
9099
|
+
message3 = JSON.parse(line3);
|
|
8711
9100
|
} catch {
|
|
8712
9101
|
return errorLine(null, -32700, "Parse error");
|
|
8713
9102
|
}
|
|
8714
|
-
const rawId = isObject2(
|
|
9103
|
+
const rawId = isObject2(message3) ? message3["id"] : void 0;
|
|
8715
9104
|
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
8716
|
-
if (!isObject2(
|
|
9105
|
+
if (!isObject2(message3) || message3["jsonrpc"] !== "2.0" || typeof message3["method"] !== "string")
|
|
8717
9106
|
return errorLine(id, -32600, "Invalid Request");
|
|
8718
|
-
if (!("id" in
|
|
9107
|
+
if (!("id" in message3)) return null;
|
|
8719
9108
|
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
8720
|
-
const method =
|
|
8721
|
-
const params = isObject2(
|
|
9109
|
+
const method = message3["method"];
|
|
9110
|
+
const params = isObject2(message3["params"]) ? message3["params"] : {};
|
|
8722
9111
|
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
8723
9112
|
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
8724
9113
|
if (modern) {
|
|
@@ -8826,13 +9215,13 @@ async function mcpCommand(opts, io = {
|
|
|
8826
9215
|
}
|
|
8827
9216
|
|
|
8828
9217
|
// src/commands/pulse.ts
|
|
8829
|
-
import { readFile as
|
|
8830
|
-
import { join as
|
|
9218
|
+
import { readFile as readFile16, readdir as readdir5 } from "fs/promises";
|
|
9219
|
+
import { join as join30 } from "path";
|
|
8831
9220
|
import { setTimeout as delay3 } from "timers/promises";
|
|
8832
9221
|
|
|
8833
9222
|
// src/pulse/opened.ts
|
|
8834
|
-
import { mkdir as
|
|
8835
|
-
import { dirname as
|
|
9223
|
+
import { mkdir as mkdir10, readFile as readFile15, rename as rename3, rm as rm10, writeFile as writeFile6 } from "fs/promises";
|
|
9224
|
+
import { dirname as dirname10 } from "path";
|
|
8836
9225
|
var NAME2 = "rness/opened";
|
|
8837
9226
|
function openedFile(rnessDir) {
|
|
8838
9227
|
return gitPath(rnessDir, NAME2);
|
|
@@ -8843,7 +9232,7 @@ async function readOpened(file) {
|
|
|
8843
9232
|
if (file === null) return opened;
|
|
8844
9233
|
let data;
|
|
8845
9234
|
try {
|
|
8846
|
-
data = JSON.parse(await
|
|
9235
|
+
data = JSON.parse(await readFile15(file, "utf8"));
|
|
8847
9236
|
} catch {
|
|
8848
9237
|
return opened;
|
|
8849
9238
|
}
|
|
@@ -8862,7 +9251,7 @@ async function writeOpened(file, opened) {
|
|
|
8862
9251
|
return;
|
|
8863
9252
|
}
|
|
8864
9253
|
const entries = [...opened].sort(([a], [b]) => a - b);
|
|
8865
|
-
await
|
|
9254
|
+
await mkdir10(dirname10(file), { recursive: true });
|
|
8866
9255
|
const temporary = `${file}.${process.pid}`;
|
|
8867
9256
|
await writeFile6(
|
|
8868
9257
|
temporary,
|
|
@@ -8940,10 +9329,10 @@ async function directoriesOf(dir) {
|
|
|
8940
9329
|
}
|
|
8941
9330
|
}
|
|
8942
9331
|
async function scopeOf(c, name, tabs, owners) {
|
|
8943
|
-
const
|
|
9332
|
+
const within2 = (collection) => (path) => path.startsWith(`${collection}/`);
|
|
8944
9333
|
if (name === PULSE) {
|
|
8945
9334
|
const shown = tabs.filter((t) => !owners.includes(t.name));
|
|
8946
|
-
const elsewhere = (path) => owners.some((o) =>
|
|
9335
|
+
const elsewhere = (path) => owners.some((o) => within2(o)(path));
|
|
8947
9336
|
return {
|
|
8948
9337
|
label: "Agent Pulse",
|
|
8949
9338
|
want: desiredOf(shown, c.org),
|
|
@@ -8958,7 +9347,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
8958
9347
|
label: labelOf(name),
|
|
8959
9348
|
rows: []
|
|
8960
9349
|
};
|
|
8961
|
-
const dir =
|
|
9350
|
+
const dir = join30(c.rnessDir, name);
|
|
8962
9351
|
const shape = await readShape(c.rnessDir, name);
|
|
8963
9352
|
const fronts = new Map(
|
|
8964
9353
|
(await collectMarkdown(dir)).map((item) => [
|
|
@@ -8976,7 +9365,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
8976
9365
|
want,
|
|
8977
9366
|
layout: layoutOf([tab], { shape, want }),
|
|
8978
9367
|
elsewhere: () => false,
|
|
8979
|
-
mine:
|
|
9368
|
+
mine: within2(name),
|
|
8980
9369
|
own: { shape, updates: await readUpdates(c.rnessDir, name) }
|
|
8981
9370
|
};
|
|
8982
9371
|
}
|
|
@@ -9004,7 +9393,7 @@ async function working(ui, doing, count3, work) {
|
|
|
9004
9393
|
async function filesOf2(rnessDir) {
|
|
9005
9394
|
const files = /* @__PURE__ */ new Set();
|
|
9006
9395
|
const walk = async (rel) => {
|
|
9007
|
-
const dir = rel === "" ? rnessDir :
|
|
9396
|
+
const dir = rel === "" ? rnessDir : join30(rnessDir, ...rel.split("/"));
|
|
9008
9397
|
for (const e of await readdir5(dir, { withFileTypes: true })) {
|
|
9009
9398
|
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
9010
9399
|
if (e.isDirectory()) {
|
|
@@ -9116,7 +9505,7 @@ async function syncBoard(c, board, ui, sayLayout, scope) {
|
|
|
9116
9505
|
w.path,
|
|
9117
9506
|
issueBody({
|
|
9118
9507
|
path: w.path,
|
|
9119
|
-
text: await
|
|
9508
|
+
text: await readFile16(join30(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
9120
9509
|
org: c.org,
|
|
9121
9510
|
numbers,
|
|
9122
9511
|
files
|
|
@@ -9329,6 +9718,7 @@ async function mark(opts) {
|
|
|
9329
9718
|
}
|
|
9330
9719
|
|
|
9331
9720
|
// src/commands/status.ts
|
|
9721
|
+
import { posix as posix9, relative as relative8, sep as sep6 } from "path";
|
|
9332
9722
|
import { emitKeypressEvents } from "readline";
|
|
9333
9723
|
|
|
9334
9724
|
// src/core/status-view.ts
|
|
@@ -9531,7 +9921,8 @@ function showView(tabs, workspace, start, { input, output }) {
|
|
|
9531
9921
|
}
|
|
9532
9922
|
async function statusCommand(tab, opts, terminal = { input: process.stdin, output: process.stdout }) {
|
|
9533
9923
|
try {
|
|
9534
|
-
const
|
|
9924
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9925
|
+
const ws = await findWorkspace(cwd);
|
|
9535
9926
|
const manifest = await loadManifest(ws.rnessDir);
|
|
9536
9927
|
const workspace = workspaceName(manifest, ws.root);
|
|
9537
9928
|
const tabs = await statusTabs(ws.rnessDir);
|
|
@@ -9543,6 +9934,18 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9543
9934
|
);
|
|
9544
9935
|
return 2;
|
|
9545
9936
|
}
|
|
9937
|
+
const shown = wanted === void 0 ? tabs : [wanted];
|
|
9938
|
+
if (opts.json === true) {
|
|
9939
|
+
const scope = resolveScope(
|
|
9940
|
+
manifest,
|
|
9941
|
+
relative8(ws.root, cwd).split(sep6).join(posix9.sep)
|
|
9942
|
+
);
|
|
9943
|
+
terminal.output.write(
|
|
9944
|
+
`${JSON.stringify(await statusSnapshot(ws, manifest, scope, shown))}
|
|
9945
|
+
`
|
|
9946
|
+
);
|
|
9947
|
+
return 0;
|
|
9948
|
+
}
|
|
9546
9949
|
if (terminal.input.isTTY === true && terminal.output.isTTY === true) {
|
|
9547
9950
|
await showView(
|
|
9548
9951
|
tabs,
|
|
@@ -9552,9 +9955,7 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9552
9955
|
);
|
|
9553
9956
|
return 0;
|
|
9554
9957
|
}
|
|
9555
|
-
terminal.output.write(
|
|
9556
|
-
statusMarkdown(workspace, wanted === void 0 ? tabs : [wanted])
|
|
9557
|
-
);
|
|
9958
|
+
terminal.output.write(statusMarkdown(workspace, shown));
|
|
9558
9959
|
return 0;
|
|
9559
9960
|
} catch (e) {
|
|
9560
9961
|
return reportError(e);
|
|
@@ -9605,7 +10006,7 @@ function buildProgram(state) {
|
|
|
9605
10006
|
});
|
|
9606
10007
|
program.command("status").description(
|
|
9607
10008
|
"Show the status of every decision, specification and plan, a tab per directory"
|
|
9608
|
-
).argument("[tab]", "open on this tab: adr, specs, plans, \u2026").addOption(new Option("--cwd <dir>").hideHelp()).action(async (tab, opts) => {
|
|
10009
|
+
).argument("[tab]", "open on this tab: adr, specs, plans, \u2026").option("--json", "print JSON instead: what the Claude Code plugin draws").addOption(new Option("--cwd <dir>").hideHelp()).action(async (tab, opts) => {
|
|
9609
10010
|
state.code = await statusCommand(tab, opts);
|
|
9610
10011
|
});
|
|
9611
10012
|
const doc = program.command("doc").description("Documents of the workspace: doc new <collection>");
|
|
@@ -9687,7 +10088,10 @@ function buildProgram(state) {
|
|
|
9687
10088
|
program.command("written-files", { hidden: true }).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
9688
10089
|
state.code = await writtenFilesCommand(opts);
|
|
9689
10090
|
});
|
|
9690
|
-
program.command("hook", { hidden: true }).argument(
|
|
10091
|
+
program.command("hook", { hidden: true }).argument(
|
|
10092
|
+
"<event>",
|
|
10093
|
+
"session-start, pre-tool-use, post-tool-use or session-end"
|
|
10094
|
+
).action(async (event) => {
|
|
9691
10095
|
state.code = await hookCommand(event);
|
|
9692
10096
|
});
|
|
9693
10097
|
program.command("upgrade").alias("update").description(
|
|
@@ -9876,4 +10280,4 @@ export {
|
|
|
9876
10280
|
OWN_COMMANDS,
|
|
9877
10281
|
catchUp
|
|
9878
10282
|
};
|
|
9879
|
-
//# sourceMappingURL=chunk-
|
|
10283
|
+
//# sourceMappingURL=chunk-U5SBNWVD.js.map
|