@rness/cli 0.18.1 → 0.20.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 +122 -9
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-JHU2G7K7.js → chunk-WIZHU4WT.js} +677 -293
- package/dist/chunk-WIZHU4WT.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 +499 -0
- package/mod/types/index.d.ts +49 -0
- package/package.json +5 -1
- package/dist/chunk-JHU2G7K7.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.20.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);
|
|
@@ -774,11 +779,25 @@ var STATUSES = {
|
|
|
774
779
|
};
|
|
775
780
|
var SKIP = /* @__PURE__ */ new Set(["adr/0000-template.md"]);
|
|
776
781
|
var PLACEMENT_KEYS = ["scopes", "scope"];
|
|
777
|
-
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) {
|
|
778
797
|
const problems = [];
|
|
779
798
|
for (const name of COLLECTIONS) {
|
|
780
799
|
const allowed = STATUSES[name];
|
|
781
|
-
const items = await
|
|
800
|
+
const items = await itemsOf(rnessDir, name, override);
|
|
782
801
|
const byNumber = /* @__PURE__ */ new Map();
|
|
783
802
|
for (const item of items) {
|
|
784
803
|
const where = `${name}/${item.rel}`;
|
|
@@ -1221,13 +1240,34 @@ function statusMarkdown(workspace, tabs) {
|
|
|
1221
1240
|
}
|
|
1222
1241
|
return out.join("\n");
|
|
1223
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
|
+
}
|
|
1224
1258
|
|
|
1225
1259
|
// src/pulse/body.ts
|
|
1226
1260
|
import { createHash } from "crypto";
|
|
1227
1261
|
import { posix as posix4 } from "path";
|
|
1228
|
-
|
|
1262
|
+
|
|
1263
|
+
// src/pulse/urls.ts
|
|
1264
|
+
var boardUrl = (org, project) => `https://github.com/orgs/${org}/projects/${project}`;
|
|
1265
|
+
var itemUrl = (org, project, path) => `${boardUrl(org, project)}?filterQuery=${encodeURIComponent(`path:"${path}"`)}`;
|
|
1229
1266
|
var blobUrl = (org, path) => `https://github.com/${org}/.rness/blob/main/${encodeURI(path)}`;
|
|
1230
1267
|
var issueUrl = (org, number) => `https://github.com/${org}/.rness/issues/${number}`;
|
|
1268
|
+
|
|
1269
|
+
// src/pulse/body.ts
|
|
1270
|
+
var BODY_LIMIT = 65536;
|
|
1231
1271
|
function headerOf(path, org) {
|
|
1232
1272
|
return `\`${path}\` \xB7 [on GitHub](${blobUrl(org, path)})`;
|
|
1233
1273
|
}
|
|
@@ -2040,9 +2080,9 @@ async function postJson(path, payload, options, explain = false, limited = () =>
|
|
|
2040
2080
|
continue;
|
|
2041
2081
|
}
|
|
2042
2082
|
if (!ok) {
|
|
2043
|
-
const
|
|
2044
|
-
if (typeof
|
|
2045
|
-
throw new GitHubMessageError(
|
|
2083
|
+
const message3 = explain && page.status !== 401 && page.status !== 429 && page.body !== null && typeof page.body === "object" ? page.body.message : void 0;
|
|
2084
|
+
if (typeof message3 === "string" && message3 !== "")
|
|
2085
|
+
throw new GitHubMessageError(message3, {
|
|
2046
2086
|
cause: { status: page.status, path }
|
|
2047
2087
|
});
|
|
2048
2088
|
throw httpError(page, path);
|
|
@@ -2066,9 +2106,9 @@ async function getScopes(options) {
|
|
|
2066
2106
|
const cause = { status: res.status, path: "/user" };
|
|
2067
2107
|
if (res.status === 401)
|
|
2068
2108
|
throw new Error("GitHub rejected the token (401)", { cause });
|
|
2069
|
-
const
|
|
2070
|
-
if (typeof
|
|
2071
|
-
throw new GitHubMessageError(
|
|
2109
|
+
const message3 = body !== null && typeof body === "object" ? body["message"] : void 0;
|
|
2110
|
+
if (typeof message3 === "string" && message3 !== "")
|
|
2111
|
+
throw new GitHubMessageError(message3, { cause });
|
|
2072
2112
|
throw new Error(`GitHub API answered ${res.status} for /user`, { cause });
|
|
2073
2113
|
}
|
|
2074
2114
|
const header2 = res.headers.get("x-oauth-scopes");
|
|
@@ -2115,8 +2155,8 @@ async function getOrgMembership(org, options) {
|
|
|
2115
2155
|
}
|
|
2116
2156
|
if (status2 === 404) return "not-member";
|
|
2117
2157
|
if (status2 === 403) {
|
|
2118
|
-
const
|
|
2119
|
-
if (/OAuth App access restrictions/i.test(
|
|
2158
|
+
const message3 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
2159
|
+
if (/OAuth App access restrictions/i.test(message3)) return "restricted";
|
|
2120
2160
|
}
|
|
2121
2161
|
return "unknown";
|
|
2122
2162
|
}
|
|
@@ -2138,8 +2178,8 @@ async function createRepository(owner, name, options) {
|
|
|
2138
2178
|
throw new Error(`cannot reach the GitHub API: ${reason2(e)}`, { cause: e });
|
|
2139
2179
|
}
|
|
2140
2180
|
const body = await res.json().catch(() => void 0);
|
|
2141
|
-
const
|
|
2142
|
-
return { status: res.status, message:
|
|
2181
|
+
const message3 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
2182
|
+
return { status: res.status, message: message3 };
|
|
2143
2183
|
};
|
|
2144
2184
|
let answer = await post2(`/orgs/${encodeURIComponent(owner)}/repos`);
|
|
2145
2185
|
if (answer.status === 404 && options.self !== void 0 && options.self !== null && options.self.toLowerCase() === owner.toLowerCase())
|
|
@@ -4169,8 +4209,8 @@ function reportError(e) {
|
|
|
4169
4209
|
process.stderr.write(`${e.stack}
|
|
4170
4210
|
`);
|
|
4171
4211
|
} else {
|
|
4172
|
-
const
|
|
4173
|
-
process.stderr.write(`${paint("error",
|
|
4212
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
4213
|
+
process.stderr.write(`${paint("error", message3, process.stderr)}
|
|
4174
4214
|
`);
|
|
4175
4215
|
}
|
|
4176
4216
|
return 1;
|
|
@@ -4362,13 +4402,16 @@ async function ensureClaudeMd(dir) {
|
|
|
4362
4402
|
}
|
|
4363
4403
|
|
|
4364
4404
|
// src/commands/sync.ts
|
|
4365
|
-
import { join as
|
|
4405
|
+
import { join as join16 } from "path";
|
|
4366
4406
|
|
|
4367
4407
|
// src/core/agent-targets.ts
|
|
4368
4408
|
import { mkdir as mkdir2, rm as rm4, rmdir } from "fs/promises";
|
|
4369
|
-
import { dirname as
|
|
4409
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
4370
4410
|
|
|
4371
4411
|
// src/core/claude-skills.ts
|
|
4412
|
+
import { readFileSync, statSync } from "fs";
|
|
4413
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
4414
|
+
import { fileURLToPath } from "url";
|
|
4372
4415
|
var PLUGIN = ".claude/skills/rness";
|
|
4373
4416
|
var PLUGIN_JSON = `${JSON.stringify(
|
|
4374
4417
|
{
|
|
@@ -4386,7 +4429,9 @@ var PLUGIN_JSON = `${JSON.stringify(
|
|
|
4386
4429
|
"adr",
|
|
4387
4430
|
"specification",
|
|
4388
4431
|
"plan"
|
|
4389
|
-
]
|
|
4432
|
+
],
|
|
4433
|
+
// The contract of the mod's `$.state` (spec 0029 §3.1).
|
|
4434
|
+
types: "./types/index.d.ts"
|
|
4390
4435
|
},
|
|
4391
4436
|
null,
|
|
4392
4437
|
2
|
|
@@ -4635,6 +4680,45 @@ allowed-tools: Bash(${command}), Bash(${allocate})
|
|
|
4635
4680
|
|
|
4636
4681
|
${s.body(rness, allocate)}`;
|
|
4637
4682
|
}
|
|
4683
|
+
function modDir() {
|
|
4684
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
4685
|
+
for (let i = 0; i < 4; i += 1) {
|
|
4686
|
+
const candidate = join13(dir, "mod");
|
|
4687
|
+
try {
|
|
4688
|
+
if (statSync(join13(candidate, "hooks", "hooks.json")).isFile())
|
|
4689
|
+
return candidate;
|
|
4690
|
+
} catch {
|
|
4691
|
+
}
|
|
4692
|
+
dir = dirname4(dir);
|
|
4693
|
+
}
|
|
4694
|
+
throw new Error("mod directory not found next to the @rness/cli package");
|
|
4695
|
+
}
|
|
4696
|
+
var MOD_FILES = [
|
|
4697
|
+
"hooks/hooks.json",
|
|
4698
|
+
"hooks/register.tsx",
|
|
4699
|
+
"types/index.d.ts"
|
|
4700
|
+
];
|
|
4701
|
+
var RNESS_LINE = "const RNESS = '.rness'";
|
|
4702
|
+
var modSources = null;
|
|
4703
|
+
function modSource(rel) {
|
|
4704
|
+
if (modSources === null) {
|
|
4705
|
+
const dir = modDir();
|
|
4706
|
+
modSources = new Map(
|
|
4707
|
+
MOD_FILES.map((f) => [
|
|
4708
|
+
f,
|
|
4709
|
+
readFileSync(join13(dir, ...f.split("/")), "utf8")
|
|
4710
|
+
])
|
|
4711
|
+
);
|
|
4712
|
+
}
|
|
4713
|
+
return modSources.get(rel) ?? "";
|
|
4714
|
+
}
|
|
4715
|
+
function modFile(rel, rness) {
|
|
4716
|
+
const text = modSource(rel);
|
|
4717
|
+
if (rel !== "hooks/register.tsx") return text;
|
|
4718
|
+
if (!text.includes(RNESS_LINE))
|
|
4719
|
+
throw new Error(`mod/hooks/register.tsx lacks ${RNESS_LINE}`);
|
|
4720
|
+
return text.replace(RNESS_LINE, `const RNESS = '${rness}'`);
|
|
4721
|
+
}
|
|
4638
4722
|
function pluginFiles(rness, at, ctx) {
|
|
4639
4723
|
return [
|
|
4640
4724
|
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
@@ -4648,6 +4732,11 @@ function pluginFiles(rness, at, ctx) {
|
|
|
4648
4732
|
at,
|
|
4649
4733
|
content: lifecycleSkill(rness, s)
|
|
4650
4734
|
})),
|
|
4735
|
+
...MOD_FILES.map((rel) => ({
|
|
4736
|
+
file: `${PLUGIN}/${rel}`,
|
|
4737
|
+
at,
|
|
4738
|
+
content: modFile(rel, rness)
|
|
4739
|
+
})),
|
|
4651
4740
|
...RETIRED_SKILLS.map((name) => ({
|
|
4652
4741
|
file: `${PLUGIN}/skills/${name}/SKILL.md`,
|
|
4653
4742
|
at,
|
|
@@ -4680,6 +4769,22 @@ function hooks(rness) {
|
|
|
4680
4769
|
},
|
|
4681
4770
|
label: "the rness session-start hook"
|
|
4682
4771
|
},
|
|
4772
|
+
{
|
|
4773
|
+
// Before an edit: refused when it would change what sync generates,
|
|
4774
|
+
// or break the contract of .rness (spec 0029 §4).
|
|
4775
|
+
path: ["hooks", "PreToolUse"],
|
|
4776
|
+
contains: {
|
|
4777
|
+
matcher: "Edit|Write",
|
|
4778
|
+
hooks: [
|
|
4779
|
+
{
|
|
4780
|
+
type: "command",
|
|
4781
|
+
command: hookLine(rness, "pre-tool-use"),
|
|
4782
|
+
timeout: 10
|
|
4783
|
+
}
|
|
4784
|
+
]
|
|
4785
|
+
},
|
|
4786
|
+
label: "the rness pre-tool-use hook"
|
|
4787
|
+
},
|
|
4683
4788
|
{
|
|
4684
4789
|
path: ["hooks", "PostToolUse"],
|
|
4685
4790
|
contains: {
|
|
@@ -4874,7 +4979,7 @@ function describeGuarantee(g) {
|
|
|
4874
4979
|
// src/core/pinned.ts
|
|
4875
4980
|
import { execFile as execFile3 } from "child_process";
|
|
4876
4981
|
import { readFile as readFile8 } from "fs/promises";
|
|
4877
|
-
import { basename as basename3, join as
|
|
4982
|
+
import { basename as basename3, join as join14 } from "path";
|
|
4878
4983
|
import { promisify as promisify3 } from "util";
|
|
4879
4984
|
var execFileP3 = promisify3(execFile3);
|
|
4880
4985
|
var PACKAGE = "@rness/cli";
|
|
@@ -4899,7 +5004,7 @@ function compareVersions(a, b) {
|
|
|
4899
5004
|
async function readPackageJson(rnessDir) {
|
|
4900
5005
|
try {
|
|
4901
5006
|
const parsed = JSON.parse(
|
|
4902
|
-
await readFile8(
|
|
5007
|
+
await readFile8(join14(rnessDir, "package.json"), "utf8")
|
|
4903
5008
|
);
|
|
4904
5009
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4905
5010
|
} catch {
|
|
@@ -4918,7 +5023,7 @@ async function readPin(rnessDir) {
|
|
|
4918
5023
|
return null;
|
|
4919
5024
|
}
|
|
4920
5025
|
async function writePin(rnessDir, from, to) {
|
|
4921
|
-
const file =
|
|
5026
|
+
const file = join14(rnessDir, "package.json");
|
|
4922
5027
|
const text = await readFile8(file, "utf8");
|
|
4923
5028
|
const entry = new RegExp(
|
|
4924
5029
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
@@ -4941,12 +5046,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
4941
5046
|
if (isPackageManager(name)) return name;
|
|
4942
5047
|
}
|
|
4943
5048
|
for (const [lockfile, pm] of LOCKFILES)
|
|
4944
|
-
if (await exists(
|
|
5049
|
+
if (await exists(join14(rnessDir, lockfile))) return pm;
|
|
4945
5050
|
return "npm";
|
|
4946
5051
|
}
|
|
4947
5052
|
async function hasLockfile(rnessDir, pm) {
|
|
4948
5053
|
for (const [lockfile, owner] of LOCKFILES)
|
|
4949
|
-
if (owner === pm && await exists(
|
|
5054
|
+
if (owner === pm && await exists(join14(rnessDir, lockfile))) return true;
|
|
4950
5055
|
return false;
|
|
4951
5056
|
}
|
|
4952
5057
|
async function pinDrift(rnessDir) {
|
|
@@ -5002,9 +5107,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
5002
5107
|
}
|
|
5003
5108
|
}
|
|
5004
5109
|
async function runPinnedSync(root) {
|
|
5005
|
-
const installed = await readPinnedCli(
|
|
5110
|
+
const installed = await readPinnedCli(join14(root, ".rness"));
|
|
5006
5111
|
if (installed === null) return null;
|
|
5007
|
-
const pinned =
|
|
5112
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
5008
5113
|
if (!await exists(pinned))
|
|
5009
5114
|
return {
|
|
5010
5115
|
code: 1,
|
|
@@ -5079,7 +5184,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
5079
5184
|
};
|
|
5080
5185
|
}
|
|
5081
5186
|
async function syncPinned(root, shown, fallback) {
|
|
5082
|
-
const rnessDir =
|
|
5187
|
+
const rnessDir = join14(root, ".rness");
|
|
5083
5188
|
const installed = await readPinnedCli(rnessDir);
|
|
5084
5189
|
if (installed === null) {
|
|
5085
5190
|
if (fallback !== void 0) return fallback();
|
|
@@ -5089,7 +5194,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
5089
5194
|
);
|
|
5090
5195
|
return 1;
|
|
5091
5196
|
}
|
|
5092
|
-
const pinned =
|
|
5197
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
5093
5198
|
if (!await exists(pinned)) {
|
|
5094
5199
|
process.stderr.write(
|
|
5095
5200
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -5129,30 +5234,30 @@ function wholeDiff(existing, content) {
|
|
|
5129
5234
|
}
|
|
5130
5235
|
async function contextOf(root) {
|
|
5131
5236
|
return {
|
|
5132
|
-
packageManager: await workspacePackageManager(
|
|
5237
|
+
packageManager: await workspacePackageManager(join15(root, ".rness"))
|
|
5133
5238
|
};
|
|
5134
5239
|
}
|
|
5135
5240
|
var PATHS_ONLY = { packageManager: "npm" };
|
|
5136
5241
|
async function filesOf(root, manifest, targets) {
|
|
5137
5242
|
const files = [];
|
|
5138
|
-
if (!await exists(
|
|
5243
|
+
if (!await exists(join15(root, "org"))) return files;
|
|
5139
5244
|
const ctx = await contextOf(root);
|
|
5140
5245
|
for (const target2 of targets)
|
|
5141
5246
|
for (const owned of target2.files(ctx))
|
|
5142
5247
|
if (owned.at === "root")
|
|
5143
5248
|
files.push({
|
|
5144
5249
|
...owned,
|
|
5145
|
-
file:
|
|
5250
|
+
file: join15(root, ...owned.file.split("/")),
|
|
5146
5251
|
label: owned.file
|
|
5147
5252
|
});
|
|
5148
5253
|
for (const repo of Object.keys(manifest.repos)) {
|
|
5149
|
-
if (!await exists(
|
|
5254
|
+
if (!await exists(join15(root, "org", repo))) continue;
|
|
5150
5255
|
for (const target2 of targets)
|
|
5151
5256
|
for (const owned of target2.files(ctx))
|
|
5152
5257
|
if (owned.at === "clones")
|
|
5153
5258
|
files.push({
|
|
5154
5259
|
...owned,
|
|
5155
|
-
file:
|
|
5260
|
+
file: join15(root, "org", repo, ...owned.file.split("/")),
|
|
5156
5261
|
label: `org/${repo}/${owned.file}`
|
|
5157
5262
|
});
|
|
5158
5263
|
}
|
|
@@ -5188,7 +5293,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5188
5293
|
continue;
|
|
5189
5294
|
}
|
|
5190
5295
|
await rm4(file);
|
|
5191
|
-
await rmdir(
|
|
5296
|
+
await rmdir(dirname5(file)).catch(() => void 0);
|
|
5192
5297
|
outcomes.push({ label, status: "removed", detail: null });
|
|
5193
5298
|
continue;
|
|
5194
5299
|
}
|
|
@@ -5199,7 +5304,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5199
5304
|
else if (opts.check)
|
|
5200
5305
|
outcomes.push({ label, status: "stale", detail: diff });
|
|
5201
5306
|
else {
|
|
5202
|
-
await mkdir2(
|
|
5307
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5203
5308
|
await writeFileAtomic(file, owned.content);
|
|
5204
5309
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5205
5310
|
}
|
|
@@ -5228,7 +5333,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5228
5333
|
outcomes.push({ label, status: "unchanged", detail: null });
|
|
5229
5334
|
continue;
|
|
5230
5335
|
}
|
|
5231
|
-
await mkdir2(
|
|
5336
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5232
5337
|
await writeFileAtomic(file, ensured.text);
|
|
5233
5338
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5234
5339
|
}
|
|
@@ -5420,8 +5525,8 @@ function sessionUi(p) {
|
|
|
5420
5525
|
intro: (title) => {
|
|
5421
5526
|
p.intro(badge(title));
|
|
5422
5527
|
},
|
|
5423
|
-
outro: (
|
|
5424
|
-
p.outro(badge(
|
|
5528
|
+
outro: (message3, tone = "done") => {
|
|
5529
|
+
p.outro(badge(message3, tone));
|
|
5425
5530
|
},
|
|
5426
5531
|
line: (verb, rest, sentence) => {
|
|
5427
5532
|
say(verb, sentence ?? `${rest} ${verb}`);
|
|
@@ -5492,7 +5597,7 @@ function targetsOf(manifest, root, only) {
|
|
|
5492
5597
|
const segments = entry.path.split("/");
|
|
5493
5598
|
targets.push({
|
|
5494
5599
|
scope: name,
|
|
5495
|
-
dir:
|
|
5600
|
+
dir: join16(root, ...segments),
|
|
5496
5601
|
depth: segments.length,
|
|
5497
5602
|
label: `${entry.path}/AGENTS.md`
|
|
5498
5603
|
});
|
|
@@ -5652,7 +5757,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5652
5757
|
try {
|
|
5653
5758
|
const notCloned = /* @__PURE__ */ new Set();
|
|
5654
5759
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
5655
|
-
const dir =
|
|
5760
|
+
const dir = join16(ws.root, "org", name);
|
|
5656
5761
|
const label = `org/${name}`;
|
|
5657
5762
|
if (!await exists(dir)) {
|
|
5658
5763
|
if (!toClone.has(name)) {
|
|
@@ -5704,7 +5809,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5704
5809
|
lines.push(
|
|
5705
5810
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
5706
5811
|
);
|
|
5707
|
-
if (!await exists(
|
|
5812
|
+
if (!await exists(join16(ws.root, "org"))) {
|
|
5708
5813
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
5709
5814
|
} else {
|
|
5710
5815
|
let differences = 0;
|
|
@@ -5716,7 +5821,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5716
5821
|
continue;
|
|
5717
5822
|
}
|
|
5718
5823
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
5719
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
5824
|
+
BLOCK_FILES.map((f) => isSymlink(join16(t.dir, f)))
|
|
5720
5825
|
);
|
|
5721
5826
|
const atRoot = t.scope === null;
|
|
5722
5827
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -5739,7 +5844,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5739
5844
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
5740
5845
|
);
|
|
5741
5846
|
}
|
|
5742
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
5847
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join16(t.dir, "AGENTS.md"), block, check);
|
|
5743
5848
|
if (typeof agents === "object") {
|
|
5744
5849
|
problems.push(`${t.label}: ${agents.error}`);
|
|
5745
5850
|
continue;
|
|
@@ -5754,7 +5859,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5754
5859
|
}
|
|
5755
5860
|
if (claudeLinked === true) continue;
|
|
5756
5861
|
const claude = await syncBlockFile(
|
|
5757
|
-
|
|
5862
|
+
join16(t.dir, "CLAUDE.md"),
|
|
5758
5863
|
block,
|
|
5759
5864
|
check,
|
|
5760
5865
|
true
|
|
@@ -5811,7 +5916,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5811
5916
|
}
|
|
5812
5917
|
|
|
5813
5918
|
// src/commands/add.ts
|
|
5814
|
-
import { basename as basename4, join as
|
|
5919
|
+
import { basename as basename4, join as join17 } from "path";
|
|
5815
5920
|
function printDeclared(ui, result, already) {
|
|
5816
5921
|
for (const name of result.declared) {
|
|
5817
5922
|
if (already.has(name)) continue;
|
|
@@ -5905,7 +6010,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5905
6010
|
}
|
|
5906
6011
|
parsed = parseRepoSpec(spec, org, host);
|
|
5907
6012
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
5908
|
-
const dir =
|
|
6013
|
+
const dir = join17(ws.root, "org", parsed.name);
|
|
5909
6014
|
const present = await exists(dir);
|
|
5910
6015
|
if (interactive) {
|
|
5911
6016
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -6032,9 +6137,9 @@ async function contextCommand(opts) {
|
|
|
6032
6137
|
}
|
|
6033
6138
|
|
|
6034
6139
|
// src/core/scaffold.ts
|
|
6035
|
-
import { statSync } from "fs";
|
|
6036
|
-
import { dirname as
|
|
6037
|
-
import { fileURLToPath } from "url";
|
|
6140
|
+
import { statSync as statSync2 } from "fs";
|
|
6141
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
6142
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6038
6143
|
var SCAFFOLD_FILES = [
|
|
6039
6144
|
"AGENTS.md",
|
|
6040
6145
|
"CONVENTIONS.md",
|
|
@@ -6055,27 +6160,27 @@ var SCAFFOLD_FILES = [
|
|
|
6055
6160
|
"docs/.gitkeep"
|
|
6056
6161
|
];
|
|
6057
6162
|
function scaffoldDir() {
|
|
6058
|
-
let dir =
|
|
6163
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
6059
6164
|
for (let i = 0; i < 4; i += 1) {
|
|
6060
|
-
const candidate =
|
|
6165
|
+
const candidate = join18(dir, "scaffold");
|
|
6061
6166
|
try {
|
|
6062
|
-
if (
|
|
6167
|
+
if (statSync2(join18(candidate, "rness.json")).isFile()) return candidate;
|
|
6063
6168
|
} catch {
|
|
6064
6169
|
}
|
|
6065
|
-
dir =
|
|
6170
|
+
dir = dirname6(dir);
|
|
6066
6171
|
}
|
|
6067
6172
|
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
6068
6173
|
}
|
|
6069
6174
|
|
|
6070
6175
|
// src/core/scaffold-copy.ts
|
|
6071
6176
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
6072
|
-
import { dirname as
|
|
6177
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
6073
6178
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
6074
6179
|
async function renderScaffold(source, tokens) {
|
|
6075
6180
|
const files = [];
|
|
6076
6181
|
for (const rel of SCAFFOLD_FILES) {
|
|
6077
6182
|
if (rel === "rness.json") continue;
|
|
6078
|
-
const from =
|
|
6183
|
+
const from = join19(source, ...rel.split("/"));
|
|
6079
6184
|
let content = await readFile9(from, "utf8");
|
|
6080
6185
|
if (TOKENISED.has(rel)) {
|
|
6081
6186
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
@@ -6090,8 +6195,8 @@ async function renderScaffold(source, tokens) {
|
|
|
6090
6195
|
}
|
|
6091
6196
|
async function copyScaffold(dest, tokens) {
|
|
6092
6197
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
6093
|
-
const to =
|
|
6094
|
-
await mkdir3(
|
|
6198
|
+
const to = join19(dest, ...file.path.split("/"));
|
|
6199
|
+
await mkdir3(dirname7(to), { recursive: true });
|
|
6095
6200
|
await writeFile2(to, file.content, "utf8");
|
|
6096
6201
|
if (file.executable) await chmod2(to, 493);
|
|
6097
6202
|
}
|
|
@@ -6272,7 +6377,7 @@ async function loginCommand(opts, deps = {}) {
|
|
|
6272
6377
|
// src/commands/create.ts
|
|
6273
6378
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm7 } from "fs/promises";
|
|
6274
6379
|
import { tmpdir as tmpdir2 } from "os";
|
|
6275
|
-
import { join as
|
|
6380
|
+
import { join as join23, relative as relative4, resolve as resolve5 } from "path";
|
|
6276
6381
|
|
|
6277
6382
|
// src/core/approval.ts
|
|
6278
6383
|
var APPROVAL_INTERVAL_MS = 5e3;
|
|
@@ -6391,13 +6496,13 @@ async function askProvider(p) {
|
|
|
6391
6496
|
|
|
6392
6497
|
// src/core/new-workspace.ts
|
|
6393
6498
|
import { mkdir as mkdir4, readdir as readdir4, rm as rm6, stat as stat3 } from "fs/promises";
|
|
6394
|
-
import { dirname as
|
|
6499
|
+
import { dirname as dirname9, join as join21, relative as relative2 } from "path";
|
|
6395
6500
|
|
|
6396
6501
|
// src/core/scaffold-git.ts
|
|
6397
6502
|
import { execFile as execFile5 } from "child_process";
|
|
6398
6503
|
import { mkdtemp, realpath as realpath2, rm as rm5, writeFile as writeFile3 } from "fs/promises";
|
|
6399
6504
|
import { tmpdir } from "os";
|
|
6400
|
-
import { dirname as
|
|
6505
|
+
import { dirname as dirname8, join as join20, resolve as resolve3 } from "path";
|
|
6401
6506
|
import { promisify as promisify5 } from "util";
|
|
6402
6507
|
var execFileP5 = promisify5(execFile5);
|
|
6403
6508
|
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
@@ -6411,7 +6516,7 @@ async function run(dir, args, env = {}) {
|
|
|
6411
6516
|
...process.env,
|
|
6412
6517
|
...env,
|
|
6413
6518
|
GIT_TERMINAL_PROMPT: "0",
|
|
6414
|
-
GIT_CEILING_DIRECTORIES:
|
|
6519
|
+
GIT_CEILING_DIRECTORIES: dirname8(resolve3(dir))
|
|
6415
6520
|
},
|
|
6416
6521
|
maxBuffer: 16 * 1024 * 1024
|
|
6417
6522
|
});
|
|
@@ -6438,13 +6543,13 @@ function scaffoldMessage(subject, version) {
|
|
|
6438
6543
|
|
|
6439
6544
|
${SCAFFOLD_TRAILER}: ${version}`;
|
|
6440
6545
|
}
|
|
6441
|
-
async function commitUpgrade(rnessDir,
|
|
6546
|
+
async function commitUpgrade(rnessDir, message3) {
|
|
6442
6547
|
const add = await run(rnessDir, ["add", "-A"]);
|
|
6443
6548
|
if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
|
|
6444
6549
|
const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
|
|
6445
6550
|
if (staged.code === 0 && !await mergeInProgress(rnessDir))
|
|
6446
6551
|
return { kind: "nothing" };
|
|
6447
|
-
const commit = await run(rnessDir, ["commit", "-q", "-m",
|
|
6552
|
+
const commit = await run(rnessDir, ["commit", "-q", "-m", message3]);
|
|
6448
6553
|
if (commit.code !== 0)
|
|
6449
6554
|
return {
|
|
6450
6555
|
kind: "refused",
|
|
@@ -6485,8 +6590,8 @@ async function findScaffoldBase(rnessDir) {
|
|
|
6485
6590
|
};
|
|
6486
6591
|
}
|
|
6487
6592
|
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
6488
|
-
const scratch = await mkdtemp(
|
|
6489
|
-
const env = { GIT_INDEX_FILE:
|
|
6593
|
+
const scratch = await mkdtemp(join20(tmpdir(), "rness-scaffold-"));
|
|
6594
|
+
const env = { GIT_INDEX_FILE: join20(scratch, "index") };
|
|
6490
6595
|
try {
|
|
6491
6596
|
await must(
|
|
6492
6597
|
rnessDir,
|
|
@@ -6494,7 +6599,7 @@ async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
|
6494
6599
|
env
|
|
6495
6600
|
);
|
|
6496
6601
|
for (const [i, file] of files.entries()) {
|
|
6497
|
-
const tmp =
|
|
6602
|
+
const tmp = join20(scratch, `blob-${i}`);
|
|
6498
6603
|
await writeFile3(tmp, file.content, "utf8");
|
|
6499
6604
|
const blob = await must(rnessDir, [
|
|
6500
6605
|
"hash-object",
|
|
@@ -6606,11 +6711,11 @@ async function insideWorkspace(dir) {
|
|
|
6606
6711
|
}
|
|
6607
6712
|
async function targetProblem(cwd, root, pm) {
|
|
6608
6713
|
const shown = relative2(cwd, root) || ".";
|
|
6609
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(
|
|
6714
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname9(root)))
|
|
6610
6715
|
return "already inside an rness workspace; create runs from outside one";
|
|
6611
6716
|
if (!await exists(root)) return null;
|
|
6612
|
-
if (await exists(
|
|
6613
|
-
if (!await exists(
|
|
6717
|
+
if (await exists(join21(root, ".rness", "rness.json"))) {
|
|
6718
|
+
if (!await exists(join21(root, ".rness", "node_modules")))
|
|
6614
6719
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
6615
6720
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
6616
6721
|
}
|
|
@@ -6633,7 +6738,7 @@ async function installContext(input) {
|
|
|
6633
6738
|
}
|
|
6634
6739
|
async function buildContext(input) {
|
|
6635
6740
|
const { ui, root, shown, pm } = input;
|
|
6636
|
-
const rnessDir =
|
|
6741
|
+
const rnessDir = join21(root, ".rness");
|
|
6637
6742
|
const pmVersion = await packageManagerVersion(pm);
|
|
6638
6743
|
await mkdir4(root, { recursive: true });
|
|
6639
6744
|
try {
|
|
@@ -6705,7 +6810,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
6705
6810
|
|
|
6706
6811
|
// src/commands/create-blank.ts
|
|
6707
6812
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
6708
|
-
import { join as
|
|
6813
|
+
import { join as join22, relative as relative3, resolve as resolve4 } from "path";
|
|
6709
6814
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
6710
6815
|
function workspaceNameError(name) {
|
|
6711
6816
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -6787,8 +6892,8 @@ async function createBlank(input) {
|
|
|
6787
6892
|
kind: "blank workspace"
|
|
6788
6893
|
});
|
|
6789
6894
|
if (!built) return 1;
|
|
6790
|
-
await mkdir5(
|
|
6791
|
-
await commitContext({ ui, rnessDir:
|
|
6895
|
+
await mkdir5(join22(root, "org"), { recursive: true });
|
|
6896
|
+
await commitContext({ ui, rnessDir: join22(root, ".rness"), shown });
|
|
6792
6897
|
const code = await syncBlocks(
|
|
6793
6898
|
ui,
|
|
6794
6899
|
root,
|
|
@@ -6932,7 +7037,7 @@ async function cloneSelection(input) {
|
|
|
6932
7037
|
{ doing: `cloning org/${repo}`, git: true },
|
|
6933
7038
|
() => clone(
|
|
6934
7039
|
entry.url,
|
|
6935
|
-
|
|
7040
|
+
join23(input.root, "org", repo),
|
|
6936
7041
|
input.provider.credentialsFor(entry.url)
|
|
6937
7042
|
),
|
|
6938
7043
|
() => ["cloned", `org/${repo}`]
|
|
@@ -6957,9 +7062,9 @@ async function cloneSelection(input) {
|
|
|
6957
7062
|
manifest = result.manifest;
|
|
6958
7063
|
ui.line("declared", `scope ${result.name} (org/${result.name})`);
|
|
6959
7064
|
} catch (e) {
|
|
6960
|
-
const
|
|
6961
|
-
ui.error(`${spec}: ${
|
|
6962
|
-
failures.push({ spec, message:
|
|
7065
|
+
const message3 = firstLine5(e instanceof Error ? e.message : String(e));
|
|
7066
|
+
ui.error(`${spec}: ${message3}`);
|
|
7067
|
+
failures.push({ spec, message: message3 });
|
|
6963
7068
|
}
|
|
6964
7069
|
}
|
|
6965
7070
|
return failures;
|
|
@@ -7295,8 +7400,8 @@ async function createCommand(opts, deps = {}) {
|
|
|
7295
7400
|
scopes: {}
|
|
7296
7401
|
};
|
|
7297
7402
|
const staged = async () => {
|
|
7298
|
-
staging ??= await mkdtemp2(
|
|
7299
|
-
return
|
|
7403
|
+
staging ??= await mkdtemp2(join23(tmpdir2(), "rness-join-"));
|
|
7404
|
+
return join23(staging, ".rness");
|
|
7300
7405
|
};
|
|
7301
7406
|
if (joining) {
|
|
7302
7407
|
const stagedDir = await staged();
|
|
@@ -7358,7 +7463,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7358
7463
|
});
|
|
7359
7464
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
7360
7465
|
}
|
|
7361
|
-
const rnessDir =
|
|
7466
|
+
const rnessDir = join23(root, ".rness");
|
|
7362
7467
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
7363
7468
|
if (joining) {
|
|
7364
7469
|
await mkdir6(root, { recursive: true });
|
|
@@ -7371,7 +7476,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7371
7476
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
7372
7477
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
7373
7478
|
await install();
|
|
7374
|
-
await mkdir6(
|
|
7479
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7375
7480
|
const failures2 = await cloneSelection({
|
|
7376
7481
|
root,
|
|
7377
7482
|
rnessDir,
|
|
@@ -7424,7 +7529,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7424
7529
|
kind: "new workspace"
|
|
7425
7530
|
});
|
|
7426
7531
|
if (!built) return 1;
|
|
7427
|
-
await mkdir6(
|
|
7532
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7428
7533
|
const failures = await cloneSelection({
|
|
7429
7534
|
root,
|
|
7430
7535
|
rnessDir,
|
|
@@ -7496,7 +7601,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7496
7601
|
}
|
|
7497
7602
|
|
|
7498
7603
|
// src/core/blocks.ts
|
|
7499
|
-
import { join as
|
|
7604
|
+
import { join as join24 } from "path";
|
|
7500
7605
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
7501
7606
|
async function checkBlocks({
|
|
7502
7607
|
root,
|
|
@@ -7505,15 +7610,15 @@ async function checkBlocks({
|
|
|
7505
7610
|
org
|
|
7506
7611
|
}) {
|
|
7507
7612
|
const checks = [];
|
|
7508
|
-
const note = (label, status2,
|
|
7509
|
-
checks.push({ label, status: status2, message:
|
|
7613
|
+
const note = (label, status2, message3 = null) => {
|
|
7614
|
+
checks.push({ label, status: status2, message: message3 });
|
|
7510
7615
|
};
|
|
7511
7616
|
const derive = () => ({
|
|
7512
7617
|
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7513
7618
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7514
7619
|
checks
|
|
7515
7620
|
});
|
|
7516
|
-
if (!await exists(
|
|
7621
|
+
if (!await exists(join24(root, "org"))) return derive();
|
|
7517
7622
|
const targets = [
|
|
7518
7623
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
7519
7624
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -7522,7 +7627,7 @@ async function checkBlocks({
|
|
|
7522
7627
|
const segments = entry.path.split("/");
|
|
7523
7628
|
targets.push({
|
|
7524
7629
|
scope: name,
|
|
7525
|
-
dir:
|
|
7630
|
+
dir: join24(root, ...segments),
|
|
7526
7631
|
depth: segments.length,
|
|
7527
7632
|
file: "AGENTS.md",
|
|
7528
7633
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -7537,7 +7642,7 @@ async function checkBlocks({
|
|
|
7537
7642
|
);
|
|
7538
7643
|
continue;
|
|
7539
7644
|
}
|
|
7540
|
-
const text = await readOrNull(
|
|
7645
|
+
const text = await readOrNull(join24(t.dir, t.file));
|
|
7541
7646
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
7542
7647
|
const found = findBlock(lines);
|
|
7543
7648
|
if (found.kind === "none") {
|
|
@@ -7608,7 +7713,7 @@ async function logoutCommand() {
|
|
|
7608
7713
|
import { execFile as execFile6 } from "child_process";
|
|
7609
7714
|
import { mkdtemp as mkdtemp3, readFile as readFile10, rm as rm8 } from "fs/promises";
|
|
7610
7715
|
import { tmpdir as tmpdir3 } from "os";
|
|
7611
|
-
import { basename as basename5, join as
|
|
7716
|
+
import { basename as basename5, join as join25, relative as relative5 } from "path";
|
|
7612
7717
|
import { promisify as promisify6 } from "util";
|
|
7613
7718
|
var execFileP6 = promisify6(execFile6);
|
|
7614
7719
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -7628,10 +7733,10 @@ async function latestVersion() {
|
|
|
7628
7733
|
async function scaffoldFor(version, rnessDir) {
|
|
7629
7734
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
7630
7735
|
const installed = await readPinnedCli(rnessDir);
|
|
7631
|
-
const inside2 = installed === null ? null :
|
|
7632
|
-
if (installed?.version === version && inside2 !== null && await exists(
|
|
7736
|
+
const inside2 = installed === null ? null : join25(installed.dir, "scaffold");
|
|
7737
|
+
if (installed?.version === version && inside2 !== null && await exists(join25(inside2, "package.json")))
|
|
7633
7738
|
return { dir: inside2 };
|
|
7634
|
-
const scratch = await mkdtemp3(
|
|
7739
|
+
const scratch = await mkdtemp3(join25(tmpdir3(), "rness-pack-"));
|
|
7635
7740
|
const cleanup = () => rm8(scratch, { recursive: true, force: true });
|
|
7636
7741
|
try {
|
|
7637
7742
|
const { stdout } = await execFileP6(
|
|
@@ -7646,8 +7751,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7646
7751
|
{ timeout: 6e4 }
|
|
7647
7752
|
);
|
|
7648
7753
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
7649
|
-
await execFileP6("tar", ["-xzf",
|
|
7650
|
-
return { dir:
|
|
7754
|
+
await execFileP6("tar", ["-xzf", join25(scratch, tarball), "-C", scratch]);
|
|
7755
|
+
return { dir: join25(scratch, "package", "scaffold"), cleanup };
|
|
7651
7756
|
} catch (e) {
|
|
7652
7757
|
await cleanup();
|
|
7653
7758
|
throw new Error(
|
|
@@ -7659,7 +7764,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7659
7764
|
async function declaredManager(rnessDir, pm) {
|
|
7660
7765
|
try {
|
|
7661
7766
|
const pkg = JSON.parse(
|
|
7662
|
-
await readFile10(
|
|
7767
|
+
await readFile10(join25(rnessDir, "package.json"), "utf8")
|
|
7663
7768
|
);
|
|
7664
7769
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
7665
7770
|
} catch {
|
|
@@ -7669,7 +7774,7 @@ async function declaredManager(rnessDir, pm) {
|
|
|
7669
7774
|
async function clonesToCommit(root, cwd, manifest, files) {
|
|
7670
7775
|
const clones = [];
|
|
7671
7776
|
for (const repo of Object.keys(manifest.repos)) {
|
|
7672
|
-
const dir =
|
|
7777
|
+
const dir = join25(root, "org", repo);
|
|
7673
7778
|
if (!await exists(dir)) continue;
|
|
7674
7779
|
try {
|
|
7675
7780
|
const paths = await changedPaths(dir, files);
|
|
@@ -7687,7 +7792,7 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7687
7792
|
try {
|
|
7688
7793
|
const { stdout } = await execFileP6(
|
|
7689
7794
|
process.execPath,
|
|
7690
|
-
[
|
|
7795
|
+
[join25(pinned.dir, pinned.bin), "written-files", "--cwd", root],
|
|
7691
7796
|
{
|
|
7692
7797
|
cwd: root,
|
|
7693
7798
|
env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
|
|
@@ -7703,14 +7808,14 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7703
7808
|
}
|
|
7704
7809
|
function finish(ui, cwd, rnessDir, label, target2, ending) {
|
|
7705
7810
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
7706
|
-
const
|
|
7811
|
+
const message3 = `chore: rness ${target2}`;
|
|
7707
7812
|
const next = [];
|
|
7708
7813
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
7709
7814
|
if (ending.commit === "refused")
|
|
7710
|
-
next.push(`git -C ${rel} commit -m "${
|
|
7815
|
+
next.push(`git -C ${rel} commit -m "${message3}"`);
|
|
7711
7816
|
for (const c of ending.clones)
|
|
7712
7817
|
next.push(
|
|
7713
|
-
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${
|
|
7818
|
+
`git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message3}"`
|
|
7714
7819
|
);
|
|
7715
7820
|
if (ending.commit !== "nothing")
|
|
7716
7821
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
@@ -7883,7 +7988,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7883
7988
|
await source.cleanup?.();
|
|
7884
7989
|
}
|
|
7885
7990
|
}
|
|
7886
|
-
const packageFile =
|
|
7991
|
+
const packageFile = join25(ws.rnessDir, "package.json");
|
|
7887
7992
|
const restore = /* @__PURE__ */ new Map([
|
|
7888
7993
|
[packageFile, await readFile10(packageFile, "utf8")]
|
|
7889
7994
|
]);
|
|
@@ -7899,8 +8004,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7899
8004
|
() => ["installed", `dependencies with ${pm}`]
|
|
7900
8005
|
);
|
|
7901
8006
|
} catch (e) {
|
|
7902
|
-
const
|
|
7903
|
-
ui.error(
|
|
8007
|
+
const message4 = e instanceof Error ? e.message : String(e);
|
|
8008
|
+
ui.error(message4);
|
|
7904
8009
|
if (!merged) {
|
|
7905
8010
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
7906
8011
|
ui.hint(
|
|
@@ -7913,7 +8018,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7913
8018
|
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
7914
8019
|
"stderr"
|
|
7915
8020
|
);
|
|
7916
|
-
if (/minimum.?release.?age|NO_MATURE/i.test(
|
|
8021
|
+
if (/minimum.?release.?age|NO_MATURE/i.test(message4))
|
|
7917
8022
|
ui.hint(
|
|
7918
8023
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
7919
8024
|
"stderr"
|
|
@@ -7932,13 +8037,13 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7932
8037
|
ui.gitIsSilent = true;
|
|
7933
8038
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
7934
8039
|
if (code !== 0) return code;
|
|
7935
|
-
const
|
|
7936
|
-
const committed = await commitUpgrade(ws.rnessDir,
|
|
8040
|
+
const message3 = `chore: rness ${target2}`;
|
|
8041
|
+
const committed = await commitUpgrade(ws.rnessDir, message3);
|
|
7937
8042
|
if (committed.kind === "committed")
|
|
7938
8043
|
ui.line(
|
|
7939
8044
|
"committed",
|
|
7940
|
-
`${label} \u2014 ${
|
|
7941
|
-
`Committed ${label}: ${
|
|
8045
|
+
`${label} \u2014 ${message3}`,
|
|
8046
|
+
`Committed ${label}: ${message3}`
|
|
7942
8047
|
);
|
|
7943
8048
|
if (committed.kind === "refused") {
|
|
7944
8049
|
ui.error(
|
|
@@ -8053,7 +8158,7 @@ import { Command, CommanderError, Option } from "commander";
|
|
|
8053
8158
|
|
|
8054
8159
|
// src/commands/doc.ts
|
|
8055
8160
|
import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
|
|
8056
|
-
import { join as
|
|
8161
|
+
import { join as join26 } from "path";
|
|
8057
8162
|
var NUMBERED = Object.keys(STATUSES);
|
|
8058
8163
|
var SECTIONS = {
|
|
8059
8164
|
specs: [
|
|
@@ -8077,8 +8182,8 @@ function headingsOf(template) {
|
|
|
8077
8182
|
return [...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim());
|
|
8078
8183
|
}
|
|
8079
8184
|
async function adrSections(rnessDir) {
|
|
8080
|
-
const own =
|
|
8081
|
-
const shipped =
|
|
8185
|
+
const own = join26(rnessDir, "adr", "0000-template.md");
|
|
8186
|
+
const shipped = join26(scaffoldDir(), "adr", "0000-template.md");
|
|
8082
8187
|
for (const file of [own, shipped]) {
|
|
8083
8188
|
try {
|
|
8084
8189
|
const sections = headingsOf(await readFile11(file, "utf8"));
|
|
@@ -8112,7 +8217,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8112
8217
|
const name = collection;
|
|
8113
8218
|
try {
|
|
8114
8219
|
const ws = await findWorkspace(opts.cwd ?? process.cwd());
|
|
8115
|
-
const dir =
|
|
8220
|
+
const dir = join26(ws.rnessDir, name);
|
|
8116
8221
|
await mkdir7(dir, { recursive: true });
|
|
8117
8222
|
let highest = 0;
|
|
8118
8223
|
for (const item of await collectMarkdown(dir)) {
|
|
@@ -8121,7 +8226,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8121
8226
|
}
|
|
8122
8227
|
const number = String(highest + 1).padStart(4, "0");
|
|
8123
8228
|
const title = opts.title?.trim() || "Untitled";
|
|
8124
|
-
const file =
|
|
8229
|
+
const file = join26(dir, `${number}-${slugOf(title)}.md`);
|
|
8125
8230
|
const status2 = STATUSES[name]?.[0] ?? "Draft";
|
|
8126
8231
|
const date = today();
|
|
8127
8232
|
const front = [
|
|
@@ -8174,9 +8279,243 @@ password=${resolved.token}
|
|
|
8174
8279
|
import { realpath as realpath4 } from "fs/promises";
|
|
8175
8280
|
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
8176
8281
|
|
|
8282
|
+
// src/core/claude-code.ts
|
|
8283
|
+
import { mkdir as mkdir8, readFile as readFile12 } from "fs/promises";
|
|
8284
|
+
import { join as join27 } from "path";
|
|
8285
|
+
var MOD_FLOOR = "2.1.280";
|
|
8286
|
+
var AGENT = /^claude-code_(\d+)-(\d+)-(\d+)_[a-z]+$/;
|
|
8287
|
+
function claudeCodeVersion(env) {
|
|
8288
|
+
const m = AGENT.exec(env["AI_AGENT"] ?? "");
|
|
8289
|
+
return m === null ? null : `${m[1]}.${m[2]}.${m[3]}`;
|
|
8290
|
+
}
|
|
8291
|
+
function belowModFloor(version) {
|
|
8292
|
+
const have = version.split(".").map(Number);
|
|
8293
|
+
const floor = MOD_FLOOR.split(".").map(Number);
|
|
8294
|
+
for (let i = 0; i < 3; i++) {
|
|
8295
|
+
const a = have[i] ?? 0;
|
|
8296
|
+
const b = floor[i] ?? 0;
|
|
8297
|
+
if (a !== b) return a < b;
|
|
8298
|
+
}
|
|
8299
|
+
return false;
|
|
8300
|
+
}
|
|
8301
|
+
var noticeFile = () => join27(configDir(), "claude-code.json");
|
|
8302
|
+
async function shownVersions() {
|
|
8303
|
+
try {
|
|
8304
|
+
const data = JSON.parse(await readFile12(noticeFile(), "utf8"));
|
|
8305
|
+
const shown = data !== null && typeof data === "object" ? data["modNoticeShown"] : void 0;
|
|
8306
|
+
return Array.isArray(shown) ? shown.filter((v) => typeof v === "string") : [];
|
|
8307
|
+
} catch {
|
|
8308
|
+
return [];
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
8311
|
+
async function takeModNotice(env) {
|
|
8312
|
+
const version = claudeCodeVersion(env);
|
|
8313
|
+
if (version === null || !belowModFloor(version)) return null;
|
|
8314
|
+
if ((env["CLAUDE_CODE_ENTRYPOINT"] ?? "").startsWith("sdk-")) return null;
|
|
8315
|
+
const shown = await shownVersions();
|
|
8316
|
+
if (shown.includes(version)) return null;
|
|
8317
|
+
try {
|
|
8318
|
+
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8319
|
+
await writeFileAtomic(
|
|
8320
|
+
noticeFile(),
|
|
8321
|
+
`${JSON.stringify({ modNoticeShown: [...shown, version] })}
|
|
8322
|
+
`
|
|
8323
|
+
);
|
|
8324
|
+
} catch {
|
|
8325
|
+
}
|
|
8326
|
+
return `rness: Claude Code ${version} shows no rness band, status line or pane; ${MOD_FLOOR} or later does \u2014 claude update`;
|
|
8327
|
+
}
|
|
8328
|
+
|
|
8329
|
+
// src/core/edit-guard.ts
|
|
8330
|
+
function occurrences(text, needle) {
|
|
8331
|
+
const spans = [];
|
|
8332
|
+
for (let i = text.indexOf(needle); i !== -1; i = text.indexOf(needle, i + needle.length))
|
|
8333
|
+
spans.push([i, i + needle.length]);
|
|
8334
|
+
return spans;
|
|
8335
|
+
}
|
|
8336
|
+
var QUOTES = {
|
|
8337
|
+
"\u2018": "'",
|
|
8338
|
+
"\u2019": "'",
|
|
8339
|
+
"\u201C": '"',
|
|
8340
|
+
"\u201D": '"'
|
|
8341
|
+
};
|
|
8342
|
+
function loose(text) {
|
|
8343
|
+
let out = "";
|
|
8344
|
+
const at = [];
|
|
8345
|
+
for (let i = 0; i < text.length; i++) {
|
|
8346
|
+
const c = text[i] ?? "";
|
|
8347
|
+
if (c === "\r" && text[i + 1] === "\n") continue;
|
|
8348
|
+
out += QUOTES[c] ?? c;
|
|
8349
|
+
at.push(i);
|
|
8350
|
+
}
|
|
8351
|
+
at.push(text.length);
|
|
8352
|
+
return { text: out, at };
|
|
8353
|
+
}
|
|
8354
|
+
function replayEdit(text, oldString, newString, all) {
|
|
8355
|
+
if (oldString === "") return null;
|
|
8356
|
+
let spans = occurrences(text, oldString);
|
|
8357
|
+
if (spans.length === 0) {
|
|
8358
|
+
const file = loose(text);
|
|
8359
|
+
spans = occurrences(file.text, loose(oldString).text).map(
|
|
8360
|
+
([s, e]) => [file.at[s] ?? 0, (file.at[e - 1] ?? 0) + 1]
|
|
8361
|
+
);
|
|
8362
|
+
}
|
|
8363
|
+
if (spans.length === 0 || !all && spans.length !== 1) return null;
|
|
8364
|
+
let after = text;
|
|
8365
|
+
for (const [s, e] of [...spans].reverse())
|
|
8366
|
+
after = `${after.slice(0, s)}${newString}${after.slice(e)}`;
|
|
8367
|
+
return { after, spans };
|
|
8368
|
+
}
|
|
8369
|
+
var OWNED = /(?:^|\/)\.claude\/skills\/rness\//;
|
|
8370
|
+
var ownedWhole = (rel) => OWNED.test(rel);
|
|
8371
|
+
function ownedRefusal(label) {
|
|
8372
|
+
return `rness: ${label} is written whole by rness sync, and the next sync overwrites it. Leave it to rness.`;
|
|
8373
|
+
}
|
|
8374
|
+
var MARKER = /^<!-- rness: (\S+) -->$/;
|
|
8375
|
+
function blockOf(text) {
|
|
8376
|
+
const lines = text.split(/\r?\n/);
|
|
8377
|
+
const found = findBlock(lines);
|
|
8378
|
+
if (found.kind !== "one") return null;
|
|
8379
|
+
const starts = [0];
|
|
8380
|
+
for (const m of text.matchAll(/\r?\n/g))
|
|
8381
|
+
starts.push((m.index ?? 0) + m[0].length);
|
|
8382
|
+
const from = starts[found.begin] ?? 0;
|
|
8383
|
+
const to = (starts[found.end] ?? 0) + (lines[found.end] ?? "").length;
|
|
8384
|
+
return { begin: found.begin, end: found.end, from, to, lines };
|
|
8385
|
+
}
|
|
8386
|
+
function lineAt(text, offset) {
|
|
8387
|
+
return text.slice(0, offset).split(/\r?\n/).length - 1;
|
|
8388
|
+
}
|
|
8389
|
+
function sourceOf(block, line3) {
|
|
8390
|
+
for (let i = Math.min(line3, block.end); i > block.begin; i--) {
|
|
8391
|
+
const m = MARKER.exec((block.lines[i] ?? "").trimEnd());
|
|
8392
|
+
if (m !== null) return `.rness/${m[1] ?? ""}`;
|
|
8393
|
+
}
|
|
8394
|
+
return ".rness/ and rness.json";
|
|
8395
|
+
}
|
|
8396
|
+
function blockRefusal(label, block, source) {
|
|
8397
|
+
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.`;
|
|
8398
|
+
}
|
|
8399
|
+
function editRefusal(label, before, spans) {
|
|
8400
|
+
const block = blockOf(before);
|
|
8401
|
+
if (block === null) return null;
|
|
8402
|
+
const hit = spans.find(([s, e]) => s < block.to && e > block.from);
|
|
8403
|
+
if (hit === void 0) return null;
|
|
8404
|
+
return blockRefusal(
|
|
8405
|
+
label,
|
|
8406
|
+
block,
|
|
8407
|
+
sourceOf(block, lineAt(before, Math.max(hit[0], block.from)))
|
|
8408
|
+
);
|
|
8409
|
+
}
|
|
8410
|
+
function writeRefusal(label, before, after) {
|
|
8411
|
+
const block = blockOf(before);
|
|
8412
|
+
if (block === null) return null;
|
|
8413
|
+
const kept = blockOf(after);
|
|
8414
|
+
const was = before.slice(block.from, block.to);
|
|
8415
|
+
if (kept !== null && after.slice(kept.from, kept.to) === was) return null;
|
|
8416
|
+
const next = kept === null ? [] : kept.lines.slice(kept.begin, kept.end + 1);
|
|
8417
|
+
const old = block.lines.slice(block.begin, block.end + 1);
|
|
8418
|
+
const first = old.findIndex((line3, i) => line3 !== next[i]);
|
|
8419
|
+
return blockRefusal(
|
|
8420
|
+
label,
|
|
8421
|
+
block,
|
|
8422
|
+
kept === null ? ".rness/ and rness.json" : sourceOf(block, block.begin + Math.max(first, 0))
|
|
8423
|
+
);
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
// src/core/safety-net.ts
|
|
8427
|
+
var SHOWN_PROBLEMS = 3;
|
|
8428
|
+
var message = (e) => e instanceof Error ? e.message : String(e);
|
|
8429
|
+
async function safetyNet(ws, manifest) {
|
|
8430
|
+
const notes = [];
|
|
8431
|
+
const drift = await pinDrift(ws.rnessDir);
|
|
8432
|
+
if (drift !== null)
|
|
8433
|
+
notes.push(
|
|
8434
|
+
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
8435
|
+
);
|
|
8436
|
+
try {
|
|
8437
|
+
const { problems } = await checkWorkspace(ws, manifest);
|
|
8438
|
+
if (problems.length > 0)
|
|
8439
|
+
notes.push(
|
|
8440
|
+
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
8441
|
+
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
8442
|
+
);
|
|
8443
|
+
} catch (e) {
|
|
8444
|
+
notes.push(message(e));
|
|
8445
|
+
}
|
|
8446
|
+
return notes;
|
|
8447
|
+
}
|
|
8448
|
+
|
|
8449
|
+
// src/pulse/detached.ts
|
|
8450
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
8451
|
+
import { mkdir as mkdir9, open as open2, readFile as readFile13, rm as rm9, stat as stat4, writeFile as writeFile5 } from "fs/promises";
|
|
8452
|
+
import { join as join28 } from "path";
|
|
8453
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
8454
|
+
var detached = (args, cwd) => {
|
|
8455
|
+
const bin = process.argv[1];
|
|
8456
|
+
if (bin === void 0) return;
|
|
8457
|
+
const child = nodeSpawn(process.execPath, [bin, ...args], {
|
|
8458
|
+
detached: true,
|
|
8459
|
+
stdio: "ignore",
|
|
8460
|
+
cwd,
|
|
8461
|
+
env: { ...process.env, RNESS_NO_DELEGATE: "1" }
|
|
8462
|
+
});
|
|
8463
|
+
child.on("error", () => void 0);
|
|
8464
|
+
child.unref();
|
|
8465
|
+
};
|
|
8466
|
+
var failureFile = () => join28(configDir(), "pulse.json");
|
|
8467
|
+
async function recordFailure(reason3) {
|
|
8468
|
+
try {
|
|
8469
|
+
await mkdir9(configDir(), { recursive: true, mode: 448 });
|
|
8470
|
+
await writeFile5(
|
|
8471
|
+
failureFile(),
|
|
8472
|
+
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), reason: reason3 })}
|
|
8473
|
+
`
|
|
8474
|
+
);
|
|
8475
|
+
} catch {
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
async function takeFailure() {
|
|
8479
|
+
try {
|
|
8480
|
+
const data = JSON.parse(await readFile13(failureFile(), "utf8"));
|
|
8481
|
+
await rm9(failureFile(), { force: true });
|
|
8482
|
+
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
8483
|
+
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
8484
|
+
} catch {
|
|
8485
|
+
return null;
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
8489
|
+
async function oneSyncAtATime(key, work, options = {}) {
|
|
8490
|
+
await mkdir9(configDir(), { recursive: true, mode: 448 });
|
|
8491
|
+
const file = join28(configDir(), `pulse-${key}.lock`);
|
|
8492
|
+
for (; ; ) {
|
|
8493
|
+
try {
|
|
8494
|
+
const handle = await open2(file, "wx");
|
|
8495
|
+
await handle.close();
|
|
8496
|
+
break;
|
|
8497
|
+
} catch (e) {
|
|
8498
|
+
if (e.code !== "EEXIST") throw e;
|
|
8499
|
+
const age = await stat4(file).then(
|
|
8500
|
+
(s) => Date.now() - s.mtimeMs,
|
|
8501
|
+
() => 0
|
|
8502
|
+
);
|
|
8503
|
+
if (age > PULSE_LOCK_STALE_MS) await rm9(file, { force: true });
|
|
8504
|
+
else if (options.wait !== true) return false;
|
|
8505
|
+
else await (options.sleep ?? ((ms) => delay2(ms)))(1e3);
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8508
|
+
try {
|
|
8509
|
+
await work();
|
|
8510
|
+
return true;
|
|
8511
|
+
} finally {
|
|
8512
|
+
await rm9(file, { force: true });
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8515
|
+
|
|
8177
8516
|
// src/mcp/tools.ts
|
|
8178
|
-
import { readFile as
|
|
8179
|
-
import { isAbsolute, join as
|
|
8517
|
+
import { readFile as readFile14, realpath as realpath3, stat as stat5 } from "fs/promises";
|
|
8518
|
+
import { isAbsolute, join as join29, posix as posix7, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
8180
8519
|
var LABELS = {
|
|
8181
8520
|
standards: "Standards",
|
|
8182
8521
|
adr: "Decisions",
|
|
@@ -8205,7 +8544,7 @@ async function documents(rnessDir, only) {
|
|
|
8205
8544
|
const docs = [];
|
|
8206
8545
|
for (const collection of COLLECTIONS) {
|
|
8207
8546
|
if (only !== void 0 && collection !== only) continue;
|
|
8208
|
-
for (const item of await collectMarkdown(
|
|
8547
|
+
for (const item of await collectMarkdown(join29(rnessDir, collection))) {
|
|
8209
8548
|
const path = `${collection}/${item.rel}`;
|
|
8210
8549
|
if (path === TEMPLATE2) continue;
|
|
8211
8550
|
const status2 = item.fields?.["status"];
|
|
@@ -8321,13 +8660,13 @@ async function read(cwd, args) {
|
|
|
8321
8660
|
}
|
|
8322
8661
|
if (!real.startsWith(`${root}${sep4}`))
|
|
8323
8662
|
return refuse(`${path}: leads outside .rness/`);
|
|
8324
|
-
const info = await
|
|
8663
|
+
const info = await stat5(real);
|
|
8325
8664
|
if (!info.isFile()) return refuse(`${path}: not a file`);
|
|
8326
8665
|
if (info.size > READ_LIMIT)
|
|
8327
8666
|
return refuse(
|
|
8328
8667
|
`${path}: ${info.size} bytes, over the 256 KiB rness_read returns`
|
|
8329
8668
|
);
|
|
8330
|
-
return { text: await
|
|
8669
|
+
return { text: await readFile14(real, "utf8") };
|
|
8331
8670
|
}
|
|
8332
8671
|
async function search(cwd, args) {
|
|
8333
8672
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -8433,74 +8772,7 @@ function mcpTools(cwd) {
|
|
|
8433
8772
|
];
|
|
8434
8773
|
}
|
|
8435
8774
|
|
|
8436
|
-
// src/
|
|
8437
|
-
import { spawn as nodeSpawn } from "child_process";
|
|
8438
|
-
import { mkdir as mkdir8, open as open2, readFile as readFile13, rm as rm9, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
8439
|
-
import { join as join27 } from "path";
|
|
8440
|
-
import { setTimeout as delay2 } from "timers/promises";
|
|
8441
|
-
var detached = (args, cwd) => {
|
|
8442
|
-
const bin = process.argv[1];
|
|
8443
|
-
if (bin === void 0) return;
|
|
8444
|
-
const child = nodeSpawn(process.execPath, [bin, ...args], {
|
|
8445
|
-
detached: true,
|
|
8446
|
-
stdio: "ignore",
|
|
8447
|
-
cwd,
|
|
8448
|
-
env: { ...process.env, RNESS_NO_DELEGATE: "1" }
|
|
8449
|
-
});
|
|
8450
|
-
child.on("error", () => void 0);
|
|
8451
|
-
child.unref();
|
|
8452
|
-
};
|
|
8453
|
-
var failureFile = () => join27(configDir(), "pulse.json");
|
|
8454
|
-
async function recordFailure(reason3) {
|
|
8455
|
-
try {
|
|
8456
|
-
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8457
|
-
await writeFile5(
|
|
8458
|
-
failureFile(),
|
|
8459
|
-
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), reason: reason3 })}
|
|
8460
|
-
`
|
|
8461
|
-
);
|
|
8462
|
-
} catch {
|
|
8463
|
-
}
|
|
8464
|
-
}
|
|
8465
|
-
async function takeFailure() {
|
|
8466
|
-
try {
|
|
8467
|
-
const data = JSON.parse(await readFile13(failureFile(), "utf8"));
|
|
8468
|
-
await rm9(failureFile(), { force: true });
|
|
8469
|
-
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
8470
|
-
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
8471
|
-
} catch {
|
|
8472
|
-
return null;
|
|
8473
|
-
}
|
|
8474
|
-
}
|
|
8475
|
-
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
8476
|
-
async function oneSyncAtATime(key, work, options = {}) {
|
|
8477
|
-
await mkdir8(configDir(), { recursive: true, mode: 448 });
|
|
8478
|
-
const file = join27(configDir(), `pulse-${key}.lock`);
|
|
8479
|
-
for (; ; ) {
|
|
8480
|
-
try {
|
|
8481
|
-
const handle = await open2(file, "wx");
|
|
8482
|
-
await handle.close();
|
|
8483
|
-
break;
|
|
8484
|
-
} catch (e) {
|
|
8485
|
-
if (e.code !== "EEXIST") throw e;
|
|
8486
|
-
const age = await stat5(file).then(
|
|
8487
|
-
(s) => Date.now() - s.mtimeMs,
|
|
8488
|
-
() => 0
|
|
8489
|
-
);
|
|
8490
|
-
if (age > PULSE_LOCK_STALE_MS) await rm9(file, { force: true });
|
|
8491
|
-
else if (options.wait !== true) return false;
|
|
8492
|
-
else await (options.sleep ?? ((ms) => delay2(ms)))(1e3);
|
|
8493
|
-
}
|
|
8494
|
-
}
|
|
8495
|
-
try {
|
|
8496
|
-
await work();
|
|
8497
|
-
return true;
|
|
8498
|
-
} finally {
|
|
8499
|
-
await rm9(file, { force: true });
|
|
8500
|
-
}
|
|
8501
|
-
}
|
|
8502
|
-
|
|
8503
|
-
// src/commands/hook.ts
|
|
8775
|
+
// src/commands/snapshot.ts
|
|
8504
8776
|
var NOUNS = {
|
|
8505
8777
|
standards: ["standard", "standards"],
|
|
8506
8778
|
adr: ["decision", "decisions"],
|
|
@@ -8508,7 +8780,54 @@ var NOUNS = {
|
|
|
8508
8780
|
plans: ["plan", "plans"],
|
|
8509
8781
|
skills: ["skill", "skills"]
|
|
8510
8782
|
};
|
|
8511
|
-
|
|
8783
|
+
async function scopeBanner(ws, manifest, scope) {
|
|
8784
|
+
const summary = await scopeSummary(ws, manifest, scope);
|
|
8785
|
+
const counts = COLLECTIONS.flatMap((c) => {
|
|
8786
|
+
const n = summary.counts[c];
|
|
8787
|
+
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
8788
|
+
});
|
|
8789
|
+
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(", ")}`;
|
|
8790
|
+
return { banner: banner2, lines: summary.lines };
|
|
8791
|
+
}
|
|
8792
|
+
var noteLines = (notes) => notes.map((n) => `rness: ${n}`);
|
|
8793
|
+
function statusLine(scope, inProgress, notes) {
|
|
8794
|
+
const acting = notes.filter((n) => !n.startsWith(" ")).length;
|
|
8795
|
+
return `${scope ?? "global"} \xB7 ${inProgress.length} in progress${acting > 0 ? ` \xB7 \u26A0 ${acting}` : ""}`;
|
|
8796
|
+
}
|
|
8797
|
+
function pulseOf(manifest) {
|
|
8798
|
+
const project = manifest.projects?.[PULSE];
|
|
8799
|
+
return manifest.org === null || project === void 0 ? null : { org: manifest.org, project };
|
|
8800
|
+
}
|
|
8801
|
+
function snapshotTabs(tabs, pulse) {
|
|
8802
|
+
return tabs.map((tab) => ({
|
|
8803
|
+
name: tab.name,
|
|
8804
|
+
label: tab.label,
|
|
8805
|
+
rows: tab.rows.map((row) => ({
|
|
8806
|
+
...row,
|
|
8807
|
+
color: row.status === null ? "red" : optionColor("Status", row.status),
|
|
8808
|
+
link: pulse === null ? null : itemUrl(pulse.org, pulse.project, row.path)
|
|
8809
|
+
}))
|
|
8810
|
+
}));
|
|
8811
|
+
}
|
|
8812
|
+
async function statusSnapshot(ws, manifest, scope, tabs) {
|
|
8813
|
+
const { banner: banner2 } = await scopeBanner(ws, manifest, scope);
|
|
8814
|
+
const inProgress = await plansInProgress(ws, manifest, scope);
|
|
8815
|
+
const notes = await safetyNet(ws, manifest);
|
|
8816
|
+
const pulse = pulseOf(manifest);
|
|
8817
|
+
return {
|
|
8818
|
+
version: VERSION,
|
|
8819
|
+
workspace: workspaceName(manifest, ws.root),
|
|
8820
|
+
scope,
|
|
8821
|
+
banner: banner2,
|
|
8822
|
+
statusLine: statusLine(scope, inProgress, notes),
|
|
8823
|
+
inProgress,
|
|
8824
|
+
notes: noteLines(notes),
|
|
8825
|
+
pulse: pulse === null ? null : boardUrl(pulse.org, pulse.project),
|
|
8826
|
+
tabs: snapshotTabs(tabs, pulse)
|
|
8827
|
+
};
|
|
8828
|
+
}
|
|
8829
|
+
|
|
8830
|
+
// src/commands/hook.ts
|
|
8512
8831
|
async function readInput(input) {
|
|
8513
8832
|
if (input.isTTY === true) return {};
|
|
8514
8833
|
let text = "";
|
|
@@ -8520,32 +8839,13 @@ async function readInput(input) {
|
|
|
8520
8839
|
return {};
|
|
8521
8840
|
}
|
|
8522
8841
|
}
|
|
8523
|
-
var
|
|
8842
|
+
var message2 = (e) => e instanceof Error ? e.message : String(e);
|
|
8524
8843
|
function cwdOf(input, env) {
|
|
8525
8844
|
const cwd = input["cwd"];
|
|
8526
8845
|
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
8527
8846
|
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
8528
8847
|
}
|
|
8529
8848
|
var toPosix2 = (p) => p.split(sep5).join(posix8.sep);
|
|
8530
|
-
async function safetyNet(ws, manifest) {
|
|
8531
|
-
const notes = [];
|
|
8532
|
-
const drift = await pinDrift(ws.rnessDir);
|
|
8533
|
-
if (drift !== null)
|
|
8534
|
-
notes.push(
|
|
8535
|
-
`.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
|
|
8536
|
-
);
|
|
8537
|
-
try {
|
|
8538
|
-
const { problems } = await checkWorkspace(ws, manifest);
|
|
8539
|
-
if (problems.length > 0)
|
|
8540
|
-
notes.push(
|
|
8541
|
-
`${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
|
|
8542
|
-
...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
|
|
8543
|
-
);
|
|
8544
|
-
} catch (e) {
|
|
8545
|
-
notes.push(message(e));
|
|
8546
|
-
}
|
|
8547
|
-
return notes;
|
|
8548
|
-
}
|
|
8549
8849
|
function sessionOf(input) {
|
|
8550
8850
|
const id = input["session_id"];
|
|
8551
8851
|
if (typeof id !== "string" || id === "") return null;
|
|
@@ -8573,21 +8873,6 @@ async function markDetached(ws, input, io, rest) {
|
|
|
8573
8873
|
} catch {
|
|
8574
8874
|
}
|
|
8575
8875
|
}
|
|
8576
|
-
async function plansInProgress(ws, manifest, scope) {
|
|
8577
|
-
const context3 = await assembleContext({
|
|
8578
|
-
rnessDir: ws.rnessDir,
|
|
8579
|
-
manifest,
|
|
8580
|
-
scope
|
|
8581
|
-
});
|
|
8582
|
-
const plans = context3.collections.find((c) => c.name === "plans");
|
|
8583
|
-
return (plans?.files ?? []).filter((f) => {
|
|
8584
|
-
try {
|
|
8585
|
-
return parseFrontMatter(f.body)?.["status"] === "In progress";
|
|
8586
|
-
} catch {
|
|
8587
|
-
return false;
|
|
8588
|
-
}
|
|
8589
|
-
}).map((f) => `plans/${f.rel}`);
|
|
8590
|
-
}
|
|
8591
8876
|
async function sessionStart(input, io) {
|
|
8592
8877
|
const cwd = cwdOf(input, io.env);
|
|
8593
8878
|
let banner2 = null;
|
|
@@ -8597,15 +8882,11 @@ async function sessionStart(input, io) {
|
|
|
8597
8882
|
const ws = await findWorkspace(cwd);
|
|
8598
8883
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8599
8884
|
const scope = resolveScope(manifest, toPosix2(relative7(ws.root, cwd)));
|
|
8600
|
-
const
|
|
8601
|
-
|
|
8602
|
-
const n = summary.counts[c];
|
|
8603
|
-
return n === void 0 ? [] : [count(n, NOUNS[c])];
|
|
8604
|
-
});
|
|
8605
|
-
banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
|
|
8885
|
+
const view = await scopeBanner(ws, manifest, scope);
|
|
8886
|
+
banner2 = view.banner;
|
|
8606
8887
|
const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
|
|
8607
8888
|
context3 = [
|
|
8608
|
-
...
|
|
8889
|
+
...view.lines,
|
|
8609
8890
|
`These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
|
|
8610
8891
|
];
|
|
8611
8892
|
notes = await safetyNet(ws, manifest);
|
|
@@ -8617,14 +8898,17 @@ async function sessionStart(input, io) {
|
|
|
8617
8898
|
(session) => paths.length === 0 ? null : ["--session", session, ...paths.flatMap((p) => ["--path", p])]
|
|
8618
8899
|
);
|
|
8619
8900
|
} catch (e) {
|
|
8620
|
-
notes = [
|
|
8901
|
+
notes = [message2(e)];
|
|
8621
8902
|
}
|
|
8622
8903
|
const failure = await takeFailure();
|
|
8623
8904
|
if (failure !== null) notes.push(`pulse not updated \u2014 ${failure}`);
|
|
8624
|
-
const warnings = notes
|
|
8905
|
+
const warnings = noteLines(notes);
|
|
8906
|
+
const compact = input["source"] === "compact";
|
|
8907
|
+
const tooOld = compact ? null : await takeModNotice(io.env);
|
|
8625
8908
|
const shown = [
|
|
8626
|
-
...banner2 !== null &&
|
|
8627
|
-
...warnings
|
|
8909
|
+
...banner2 !== null && !compact ? [banner2] : [],
|
|
8910
|
+
...warnings,
|
|
8911
|
+
...tooOld !== null ? [tooOld] : []
|
|
8628
8912
|
];
|
|
8629
8913
|
return {
|
|
8630
8914
|
...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
|
|
@@ -8649,11 +8933,7 @@ async function postToolUse(input, io) {
|
|
|
8649
8933
|
} catch {
|
|
8650
8934
|
return 0;
|
|
8651
8935
|
}
|
|
8652
|
-
const
|
|
8653
|
-
const rel = inside(ws.rnessDir, file) ?? inside(
|
|
8654
|
-
await realpath4(ws.rnessDir).catch(() => ws.rnessDir),
|
|
8655
|
-
await realpath4(file).catch(() => file)
|
|
8656
|
-
);
|
|
8936
|
+
const rel = await within(ws.rnessDir, resolve7(cwd, path));
|
|
8657
8937
|
if (rel === null) return 0;
|
|
8658
8938
|
const first = rel.split("/")[0] ?? "";
|
|
8659
8939
|
let problems = [];
|
|
@@ -8662,7 +8942,7 @@ async function postToolUse(input, io) {
|
|
|
8662
8942
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8663
8943
|
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
8664
8944
|
} catch (e) {
|
|
8665
|
-
problems = [
|
|
8945
|
+
problems = [message2(e)];
|
|
8666
8946
|
}
|
|
8667
8947
|
} else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
|
|
8668
8948
|
problems = (await checkContract(ws.rnessDir)).filter(
|
|
@@ -8685,6 +8965,95 @@ async function postToolUse(input, io) {
|
|
|
8685
8965
|
);
|
|
8686
8966
|
return 2;
|
|
8687
8967
|
}
|
|
8968
|
+
async function within(base, file) {
|
|
8969
|
+
return inside(base, file) ?? inside(
|
|
8970
|
+
await realpath4(base).catch(() => base),
|
|
8971
|
+
await realpath4(file).catch(() => file)
|
|
8972
|
+
);
|
|
8973
|
+
}
|
|
8974
|
+
function manifestProblems(text) {
|
|
8975
|
+
if (text === null) return [];
|
|
8976
|
+
try {
|
|
8977
|
+
const manifest = parseManifest(text);
|
|
8978
|
+
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
8979
|
+
return [];
|
|
8980
|
+
} catch (e) {
|
|
8981
|
+
return [message2(e)];
|
|
8982
|
+
}
|
|
8983
|
+
}
|
|
8984
|
+
async function addedProblems(ws, rel, before, after) {
|
|
8985
|
+
let was;
|
|
8986
|
+
let will;
|
|
8987
|
+
if (rel === "rness.json") {
|
|
8988
|
+
was = manifestProblems(before);
|
|
8989
|
+
will = manifestProblems(after);
|
|
8990
|
+
} else {
|
|
8991
|
+
const first = rel.split("/")[0] ?? "";
|
|
8992
|
+
if (!COLLECTIONS.includes(first) || !rel.endsWith(".md"))
|
|
8993
|
+
return [];
|
|
8994
|
+
const mine = (p) => p.startsWith(`${rel}:`);
|
|
8995
|
+
was = (await checkContract(ws.rnessDir)).filter(mine);
|
|
8996
|
+
will = (await checkContract(ws.rnessDir, { rel, text: after })).filter(mine);
|
|
8997
|
+
}
|
|
8998
|
+
return will.filter((p) => !was.includes(p));
|
|
8999
|
+
}
|
|
9000
|
+
async function preToolUse(input, io) {
|
|
9001
|
+
const toolInput = input["tool_input"];
|
|
9002
|
+
if (toolInput === null || typeof toolInput !== "object") return 0;
|
|
9003
|
+
const args = toolInput;
|
|
9004
|
+
const path = args["file_path"];
|
|
9005
|
+
if (typeof path !== "string" || path === "") return 0;
|
|
9006
|
+
try {
|
|
9007
|
+
const cwd = cwdOf(input, io.env);
|
|
9008
|
+
const ws = await findWorkspace(cwd);
|
|
9009
|
+
const file = resolve7(cwd, path);
|
|
9010
|
+
const label = await within(ws.root, file);
|
|
9011
|
+
if (label === null) return 0;
|
|
9012
|
+
if (ownedWhole(label)) return refuse2(io, [ownedRefusal(label)]);
|
|
9013
|
+
const rel = await within(ws.rnessDir, file);
|
|
9014
|
+
const blocked = BLOCK_FILES.includes(posix8.basename(label));
|
|
9015
|
+
if (rel === null && !blocked) return 0;
|
|
9016
|
+
const before = await readOrNull(file);
|
|
9017
|
+
let after;
|
|
9018
|
+
let spans = null;
|
|
9019
|
+
if (input["tool_name"] === "Write") {
|
|
9020
|
+
const content = args["content"];
|
|
9021
|
+
if (typeof content !== "string") return 0;
|
|
9022
|
+
after = content;
|
|
9023
|
+
} else {
|
|
9024
|
+
const { old_string, new_string, replace_all } = args;
|
|
9025
|
+
if (before === null || typeof old_string !== "string" || typeof new_string !== "string")
|
|
9026
|
+
return 0;
|
|
9027
|
+
const replayed = replayEdit(
|
|
9028
|
+
before,
|
|
9029
|
+
old_string,
|
|
9030
|
+
new_string,
|
|
9031
|
+
replace_all === true
|
|
9032
|
+
);
|
|
9033
|
+
if (replayed === null) return 0;
|
|
9034
|
+
after = replayed.after;
|
|
9035
|
+
spans = replayed.spans;
|
|
9036
|
+
}
|
|
9037
|
+
if (blocked && before !== null) {
|
|
9038
|
+
const reason3 = spans === null ? writeRefusal(label, before, after) : editRefusal(label, before, spans);
|
|
9039
|
+
if (reason3 !== null) return refuse2(io, [reason3]);
|
|
9040
|
+
}
|
|
9041
|
+
if (rel === null) return 0;
|
|
9042
|
+
const added = await addedProblems(ws, rel, before, after);
|
|
9043
|
+
if (added.length === 0) return 0;
|
|
9044
|
+
return refuse2(io, [
|
|
9045
|
+
...added.map((p) => `rness: ${p}`),
|
|
9046
|
+
`Fix the edit of ${rel}; rness validate checks the whole workspace.`
|
|
9047
|
+
]);
|
|
9048
|
+
} catch {
|
|
9049
|
+
return 0;
|
|
9050
|
+
}
|
|
9051
|
+
}
|
|
9052
|
+
function refuse2(io, lines) {
|
|
9053
|
+
io.error.write(`${lines.join("\n")}
|
|
9054
|
+
`);
|
|
9055
|
+
return 2;
|
|
9056
|
+
}
|
|
8688
9057
|
async function sessionEnd(input, io) {
|
|
8689
9058
|
try {
|
|
8690
9059
|
const ws = await findWorkspace(cwdOf(input, io.env));
|
|
@@ -8703,14 +9072,15 @@ async function hookCommand(event, io = {
|
|
|
8703
9072
|
error: process.stderr,
|
|
8704
9073
|
env: process.env
|
|
8705
9074
|
}) {
|
|
8706
|
-
if (event !== "session-start" && event !== "post-tool-use" && event !== "session-end") {
|
|
9075
|
+
if (event !== "session-start" && event !== "pre-tool-use" && event !== "post-tool-use" && event !== "session-end") {
|
|
8707
9076
|
io.error.write(
|
|
8708
|
-
`unknown hook event ${JSON.stringify(event)} (known: session-start, post-tool-use, session-end)
|
|
9077
|
+
`unknown hook event ${JSON.stringify(event)} (known: session-start, pre-tool-use, post-tool-use, session-end)
|
|
8709
9078
|
`
|
|
8710
9079
|
);
|
|
8711
9080
|
return 1;
|
|
8712
9081
|
}
|
|
8713
9082
|
const input = await readInput(io.input);
|
|
9083
|
+
if (event === "pre-tool-use") return preToolUse(input, io);
|
|
8714
9084
|
if (event === "post-tool-use") return postToolUse(input, io);
|
|
8715
9085
|
if (event === "session-end") return sessionEnd(input, io);
|
|
8716
9086
|
io.output.write(`${JSON.stringify(await sessionStart(input, io))}
|
|
@@ -8736,31 +9106,31 @@ var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
|
8736
9106
|
function isObject2(value) {
|
|
8737
9107
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8738
9108
|
}
|
|
8739
|
-
function errorLine(id, code,
|
|
9109
|
+
function errorLine(id, code, message3, data) {
|
|
8740
9110
|
return JSON.stringify({
|
|
8741
9111
|
jsonrpc: "2.0",
|
|
8742
9112
|
id,
|
|
8743
|
-
error: data === void 0 ? { code, message:
|
|
9113
|
+
error: data === void 0 ? { code, message: message3 } : { code, message: message3, data }
|
|
8744
9114
|
});
|
|
8745
9115
|
}
|
|
8746
9116
|
function createProtocol(opts) {
|
|
8747
9117
|
const serverInfo = { name: opts.name, version: opts.version };
|
|
8748
9118
|
return {
|
|
8749
9119
|
async handle(line3) {
|
|
8750
|
-
let
|
|
9120
|
+
let message3;
|
|
8751
9121
|
try {
|
|
8752
|
-
|
|
9122
|
+
message3 = JSON.parse(line3);
|
|
8753
9123
|
} catch {
|
|
8754
9124
|
return errorLine(null, -32700, "Parse error");
|
|
8755
9125
|
}
|
|
8756
|
-
const rawId = isObject2(
|
|
9126
|
+
const rawId = isObject2(message3) ? message3["id"] : void 0;
|
|
8757
9127
|
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
8758
|
-
if (!isObject2(
|
|
9128
|
+
if (!isObject2(message3) || message3["jsonrpc"] !== "2.0" || typeof message3["method"] !== "string")
|
|
8759
9129
|
return errorLine(id, -32600, "Invalid Request");
|
|
8760
|
-
if (!("id" in
|
|
9130
|
+
if (!("id" in message3)) return null;
|
|
8761
9131
|
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
8762
|
-
const method =
|
|
8763
|
-
const params = isObject2(
|
|
9132
|
+
const method = message3["method"];
|
|
9133
|
+
const params = isObject2(message3["params"]) ? message3["params"] : {};
|
|
8764
9134
|
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
8765
9135
|
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
8766
9136
|
if (modern) {
|
|
@@ -8868,13 +9238,13 @@ async function mcpCommand(opts, io = {
|
|
|
8868
9238
|
}
|
|
8869
9239
|
|
|
8870
9240
|
// src/commands/pulse.ts
|
|
8871
|
-
import { readFile as
|
|
8872
|
-
import { join as
|
|
9241
|
+
import { readFile as readFile16, readdir as readdir5 } from "fs/promises";
|
|
9242
|
+
import { join as join30 } from "path";
|
|
8873
9243
|
import { setTimeout as delay3 } from "timers/promises";
|
|
8874
9244
|
|
|
8875
9245
|
// src/pulse/opened.ts
|
|
8876
|
-
import { mkdir as
|
|
8877
|
-
import { dirname as
|
|
9246
|
+
import { mkdir as mkdir10, readFile as readFile15, rename as rename3, rm as rm10, writeFile as writeFile6 } from "fs/promises";
|
|
9247
|
+
import { dirname as dirname10 } from "path";
|
|
8878
9248
|
var NAME2 = "rness/opened";
|
|
8879
9249
|
function openedFile(rnessDir) {
|
|
8880
9250
|
return gitPath(rnessDir, NAME2);
|
|
@@ -8885,7 +9255,7 @@ async function readOpened(file) {
|
|
|
8885
9255
|
if (file === null) return opened;
|
|
8886
9256
|
let data;
|
|
8887
9257
|
try {
|
|
8888
|
-
data = JSON.parse(await
|
|
9258
|
+
data = JSON.parse(await readFile15(file, "utf8"));
|
|
8889
9259
|
} catch {
|
|
8890
9260
|
return opened;
|
|
8891
9261
|
}
|
|
@@ -8904,7 +9274,7 @@ async function writeOpened(file, opened) {
|
|
|
8904
9274
|
return;
|
|
8905
9275
|
}
|
|
8906
9276
|
const entries = [...opened].sort(([a], [b]) => a - b);
|
|
8907
|
-
await
|
|
9277
|
+
await mkdir10(dirname10(file), { recursive: true });
|
|
8908
9278
|
const temporary = `${file}.${process.pid}`;
|
|
8909
9279
|
await writeFile6(
|
|
8910
9280
|
temporary,
|
|
@@ -8920,7 +9290,6 @@ async function writeOpened(file, opened) {
|
|
|
8920
9290
|
var PROJECT_SCOPE = "project";
|
|
8921
9291
|
var NEEDS_LOGIN = "the pulse needs a GitHub login: run rness login";
|
|
8922
9292
|
var NEEDS_SCOPE = "the pulse needs the project scope: run rness login";
|
|
8923
|
-
var boardUrl = (org, project) => `https://github.com/orgs/${org}/projects/${project}`;
|
|
8924
9293
|
async function fromGithub(work) {
|
|
8925
9294
|
try {
|
|
8926
9295
|
return await work;
|
|
@@ -8982,10 +9351,10 @@ async function directoriesOf(dir) {
|
|
|
8982
9351
|
}
|
|
8983
9352
|
}
|
|
8984
9353
|
async function scopeOf(c, name, tabs, owners) {
|
|
8985
|
-
const
|
|
9354
|
+
const within2 = (collection) => (path) => path.startsWith(`${collection}/`);
|
|
8986
9355
|
if (name === PULSE) {
|
|
8987
9356
|
const shown = tabs.filter((t) => !owners.includes(t.name));
|
|
8988
|
-
const elsewhere = (path) => owners.some((o) =>
|
|
9357
|
+
const elsewhere = (path) => owners.some((o) => within2(o)(path));
|
|
8989
9358
|
return {
|
|
8990
9359
|
label: "Agent Pulse",
|
|
8991
9360
|
want: desiredOf(shown, c.org),
|
|
@@ -9000,7 +9369,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
9000
9369
|
label: labelOf(name),
|
|
9001
9370
|
rows: []
|
|
9002
9371
|
};
|
|
9003
|
-
const dir =
|
|
9372
|
+
const dir = join30(c.rnessDir, name);
|
|
9004
9373
|
const shape = await readShape(c.rnessDir, name);
|
|
9005
9374
|
const fronts = new Map(
|
|
9006
9375
|
(await collectMarkdown(dir)).map((item) => [
|
|
@@ -9018,7 +9387,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
9018
9387
|
want,
|
|
9019
9388
|
layout: layoutOf([tab], { shape, want }),
|
|
9020
9389
|
elsewhere: () => false,
|
|
9021
|
-
mine:
|
|
9390
|
+
mine: within2(name),
|
|
9022
9391
|
own: { shape, updates: await readUpdates(c.rnessDir, name) }
|
|
9023
9392
|
};
|
|
9024
9393
|
}
|
|
@@ -9046,7 +9415,7 @@ async function working(ui, doing, count3, work) {
|
|
|
9046
9415
|
async function filesOf2(rnessDir) {
|
|
9047
9416
|
const files = /* @__PURE__ */ new Set();
|
|
9048
9417
|
const walk = async (rel) => {
|
|
9049
|
-
const dir = rel === "" ? rnessDir :
|
|
9418
|
+
const dir = rel === "" ? rnessDir : join30(rnessDir, ...rel.split("/"));
|
|
9050
9419
|
for (const e of await readdir5(dir, { withFileTypes: true })) {
|
|
9051
9420
|
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
9052
9421
|
if (e.isDirectory()) {
|
|
@@ -9158,7 +9527,7 @@ async function syncBoard(c, board, ui, sayLayout, scope) {
|
|
|
9158
9527
|
w.path,
|
|
9159
9528
|
issueBody({
|
|
9160
9529
|
path: w.path,
|
|
9161
|
-
text: await
|
|
9530
|
+
text: await readFile16(join30(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
9162
9531
|
org: c.org,
|
|
9163
9532
|
numbers,
|
|
9164
9533
|
files
|
|
@@ -9371,6 +9740,7 @@ async function mark(opts) {
|
|
|
9371
9740
|
}
|
|
9372
9741
|
|
|
9373
9742
|
// src/commands/status.ts
|
|
9743
|
+
import { posix as posix9, relative as relative8, sep as sep6 } from "path";
|
|
9374
9744
|
import { emitKeypressEvents } from "readline";
|
|
9375
9745
|
|
|
9376
9746
|
// src/core/status-view.ts
|
|
@@ -9573,7 +9943,8 @@ function showView(tabs, workspace, start, { input, output }) {
|
|
|
9573
9943
|
}
|
|
9574
9944
|
async function statusCommand(tab, opts, terminal = { input: process.stdin, output: process.stdout }) {
|
|
9575
9945
|
try {
|
|
9576
|
-
const
|
|
9946
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9947
|
+
const ws = await findWorkspace(cwd);
|
|
9577
9948
|
const manifest = await loadManifest(ws.rnessDir);
|
|
9578
9949
|
const workspace = workspaceName(manifest, ws.root);
|
|
9579
9950
|
const tabs = await statusTabs(ws.rnessDir);
|
|
@@ -9585,6 +9956,18 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9585
9956
|
);
|
|
9586
9957
|
return 2;
|
|
9587
9958
|
}
|
|
9959
|
+
const shown = wanted === void 0 ? tabs : [wanted];
|
|
9960
|
+
if (opts.json === true) {
|
|
9961
|
+
const scope = resolveScope(
|
|
9962
|
+
manifest,
|
|
9963
|
+
relative8(ws.root, cwd).split(sep6).join(posix9.sep)
|
|
9964
|
+
);
|
|
9965
|
+
terminal.output.write(
|
|
9966
|
+
`${JSON.stringify(await statusSnapshot(ws, manifest, scope, shown))}
|
|
9967
|
+
`
|
|
9968
|
+
);
|
|
9969
|
+
return 0;
|
|
9970
|
+
}
|
|
9588
9971
|
if (terminal.input.isTTY === true && terminal.output.isTTY === true) {
|
|
9589
9972
|
await showView(
|
|
9590
9973
|
tabs,
|
|
@@ -9594,9 +9977,7 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9594
9977
|
);
|
|
9595
9978
|
return 0;
|
|
9596
9979
|
}
|
|
9597
|
-
terminal.output.write(
|
|
9598
|
-
statusMarkdown(workspace, wanted === void 0 ? tabs : [wanted])
|
|
9599
|
-
);
|
|
9980
|
+
terminal.output.write(statusMarkdown(workspace, shown));
|
|
9600
9981
|
return 0;
|
|
9601
9982
|
} catch (e) {
|
|
9602
9983
|
return reportError(e);
|
|
@@ -9647,7 +10028,7 @@ function buildProgram(state) {
|
|
|
9647
10028
|
});
|
|
9648
10029
|
program.command("status").description(
|
|
9649
10030
|
"Show the status of every decision, specification and plan, a tab per directory"
|
|
9650
|
-
).argument("[tab]", "open on this tab: adr, specs, plans, \u2026").addOption(new Option("--cwd <dir>").hideHelp()).action(async (tab, opts) => {
|
|
10031
|
+
).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) => {
|
|
9651
10032
|
state.code = await statusCommand(tab, opts);
|
|
9652
10033
|
});
|
|
9653
10034
|
const doc = program.command("doc").description("Documents of the workspace: doc new <collection>");
|
|
@@ -9729,7 +10110,10 @@ function buildProgram(state) {
|
|
|
9729
10110
|
program.command("written-files", { hidden: true }).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
9730
10111
|
state.code = await writtenFilesCommand(opts);
|
|
9731
10112
|
});
|
|
9732
|
-
program.command("hook", { hidden: true }).argument(
|
|
10113
|
+
program.command("hook", { hidden: true }).argument(
|
|
10114
|
+
"<event>",
|
|
10115
|
+
"session-start, pre-tool-use, post-tool-use or session-end"
|
|
10116
|
+
).action(async (event) => {
|
|
9733
10117
|
state.code = await hookCommand(event);
|
|
9734
10118
|
});
|
|
9735
10119
|
program.command("upgrade").alias("update").description(
|
|
@@ -9918,4 +10302,4 @@ export {
|
|
|
9918
10302
|
OWN_COMMANDS,
|
|
9919
10303
|
catchUp
|
|
9920
10304
|
};
|
|
9921
|
-
//# sourceMappingURL=chunk-
|
|
10305
|
+
//# sourceMappingURL=chunk-WIZHU4WT.js.map
|