@rness/cli 0.18.1 → 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 +96 -9
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-JHU2G7K7.js → chunk-U5SBNWVD.js} +653 -291
- 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/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.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);
|
|
@@ -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,6 +1240,21 @@ 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";
|
|
@@ -2040,9 +2074,9 @@ async function postJson(path, payload, options, explain = false, limited = () =>
|
|
|
2040
2074
|
continue;
|
|
2041
2075
|
}
|
|
2042
2076
|
if (!ok) {
|
|
2043
|
-
const
|
|
2044
|
-
if (typeof
|
|
2045
|
-
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, {
|
|
2046
2080
|
cause: { status: page.status, path }
|
|
2047
2081
|
});
|
|
2048
2082
|
throw httpError(page, path);
|
|
@@ -2066,9 +2100,9 @@ async function getScopes(options) {
|
|
|
2066
2100
|
const cause = { status: res.status, path: "/user" };
|
|
2067
2101
|
if (res.status === 401)
|
|
2068
2102
|
throw new Error("GitHub rejected the token (401)", { cause });
|
|
2069
|
-
const
|
|
2070
|
-
if (typeof
|
|
2071
|
-
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 });
|
|
2072
2106
|
throw new Error(`GitHub API answered ${res.status} for /user`, { cause });
|
|
2073
2107
|
}
|
|
2074
2108
|
const header2 = res.headers.get("x-oauth-scopes");
|
|
@@ -2115,8 +2149,8 @@ async function getOrgMembership(org, options) {
|
|
|
2115
2149
|
}
|
|
2116
2150
|
if (status2 === 404) return "not-member";
|
|
2117
2151
|
if (status2 === 403) {
|
|
2118
|
-
const
|
|
2119
|
-
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";
|
|
2120
2154
|
}
|
|
2121
2155
|
return "unknown";
|
|
2122
2156
|
}
|
|
@@ -2138,8 +2172,8 @@ async function createRepository(owner, name, options) {
|
|
|
2138
2172
|
throw new Error(`cannot reach the GitHub API: ${reason2(e)}`, { cause: e });
|
|
2139
2173
|
}
|
|
2140
2174
|
const body = await res.json().catch(() => void 0);
|
|
2141
|
-
const
|
|
2142
|
-
return { status: res.status, message:
|
|
2175
|
+
const message3 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
|
|
2176
|
+
return { status: res.status, message: message3 };
|
|
2143
2177
|
};
|
|
2144
2178
|
let answer = await post2(`/orgs/${encodeURIComponent(owner)}/repos`);
|
|
2145
2179
|
if (answer.status === 404 && options.self !== void 0 && options.self !== null && options.self.toLowerCase() === owner.toLowerCase())
|
|
@@ -4169,8 +4203,8 @@ function reportError(e) {
|
|
|
4169
4203
|
process.stderr.write(`${e.stack}
|
|
4170
4204
|
`);
|
|
4171
4205
|
} else {
|
|
4172
|
-
const
|
|
4173
|
-
process.stderr.write(`${paint("error",
|
|
4206
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
4207
|
+
process.stderr.write(`${paint("error", message3, process.stderr)}
|
|
4174
4208
|
`);
|
|
4175
4209
|
}
|
|
4176
4210
|
return 1;
|
|
@@ -4362,13 +4396,16 @@ async function ensureClaudeMd(dir) {
|
|
|
4362
4396
|
}
|
|
4363
4397
|
|
|
4364
4398
|
// src/commands/sync.ts
|
|
4365
|
-
import { join as
|
|
4399
|
+
import { join as join16 } from "path";
|
|
4366
4400
|
|
|
4367
4401
|
// src/core/agent-targets.ts
|
|
4368
4402
|
import { mkdir as mkdir2, rm as rm4, rmdir } from "fs/promises";
|
|
4369
|
-
import { dirname as
|
|
4403
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
4370
4404
|
|
|
4371
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";
|
|
4372
4409
|
var PLUGIN = ".claude/skills/rness";
|
|
4373
4410
|
var PLUGIN_JSON = `${JSON.stringify(
|
|
4374
4411
|
{
|
|
@@ -4386,7 +4423,9 @@ var PLUGIN_JSON = `${JSON.stringify(
|
|
|
4386
4423
|
"adr",
|
|
4387
4424
|
"specification",
|
|
4388
4425
|
"plan"
|
|
4389
|
-
]
|
|
4426
|
+
],
|
|
4427
|
+
// The contract of the mod's `$.state` (spec 0029 §3.1).
|
|
4428
|
+
types: "./types/index.d.ts"
|
|
4390
4429
|
},
|
|
4391
4430
|
null,
|
|
4392
4431
|
2
|
|
@@ -4635,6 +4674,45 @@ allowed-tools: Bash(${command}), Bash(${allocate})
|
|
|
4635
4674
|
|
|
4636
4675
|
${s.body(rness, allocate)}`;
|
|
4637
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
|
+
}
|
|
4638
4716
|
function pluginFiles(rness, at, ctx) {
|
|
4639
4717
|
return [
|
|
4640
4718
|
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
@@ -4648,6 +4726,11 @@ function pluginFiles(rness, at, ctx) {
|
|
|
4648
4726
|
at,
|
|
4649
4727
|
content: lifecycleSkill(rness, s)
|
|
4650
4728
|
})),
|
|
4729
|
+
...MOD_FILES.map((rel) => ({
|
|
4730
|
+
file: `${PLUGIN}/${rel}`,
|
|
4731
|
+
at,
|
|
4732
|
+
content: modFile(rel, rness)
|
|
4733
|
+
})),
|
|
4651
4734
|
...RETIRED_SKILLS.map((name) => ({
|
|
4652
4735
|
file: `${PLUGIN}/skills/${name}/SKILL.md`,
|
|
4653
4736
|
at,
|
|
@@ -4680,6 +4763,22 @@ function hooks(rness) {
|
|
|
4680
4763
|
},
|
|
4681
4764
|
label: "the rness session-start hook"
|
|
4682
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
|
+
},
|
|
4683
4782
|
{
|
|
4684
4783
|
path: ["hooks", "PostToolUse"],
|
|
4685
4784
|
contains: {
|
|
@@ -4874,7 +4973,7 @@ function describeGuarantee(g) {
|
|
|
4874
4973
|
// src/core/pinned.ts
|
|
4875
4974
|
import { execFile as execFile3 } from "child_process";
|
|
4876
4975
|
import { readFile as readFile8 } from "fs/promises";
|
|
4877
|
-
import { basename as basename3, join as
|
|
4976
|
+
import { basename as basename3, join as join14 } from "path";
|
|
4878
4977
|
import { promisify as promisify3 } from "util";
|
|
4879
4978
|
var execFileP3 = promisify3(execFile3);
|
|
4880
4979
|
var PACKAGE = "@rness/cli";
|
|
@@ -4899,7 +4998,7 @@ function compareVersions(a, b) {
|
|
|
4899
4998
|
async function readPackageJson(rnessDir) {
|
|
4900
4999
|
try {
|
|
4901
5000
|
const parsed = JSON.parse(
|
|
4902
|
-
await readFile8(
|
|
5001
|
+
await readFile8(join14(rnessDir, "package.json"), "utf8")
|
|
4903
5002
|
);
|
|
4904
5003
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4905
5004
|
} catch {
|
|
@@ -4918,7 +5017,7 @@ async function readPin(rnessDir) {
|
|
|
4918
5017
|
return null;
|
|
4919
5018
|
}
|
|
4920
5019
|
async function writePin(rnessDir, from, to) {
|
|
4921
|
-
const file =
|
|
5020
|
+
const file = join14(rnessDir, "package.json");
|
|
4922
5021
|
const text = await readFile8(file, "utf8");
|
|
4923
5022
|
const entry = new RegExp(
|
|
4924
5023
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
@@ -4941,12 +5040,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
4941
5040
|
if (isPackageManager(name)) return name;
|
|
4942
5041
|
}
|
|
4943
5042
|
for (const [lockfile, pm] of LOCKFILES)
|
|
4944
|
-
if (await exists(
|
|
5043
|
+
if (await exists(join14(rnessDir, lockfile))) return pm;
|
|
4945
5044
|
return "npm";
|
|
4946
5045
|
}
|
|
4947
5046
|
async function hasLockfile(rnessDir, pm) {
|
|
4948
5047
|
for (const [lockfile, owner] of LOCKFILES)
|
|
4949
|
-
if (owner === pm && await exists(
|
|
5048
|
+
if (owner === pm && await exists(join14(rnessDir, lockfile))) return true;
|
|
4950
5049
|
return false;
|
|
4951
5050
|
}
|
|
4952
5051
|
async function pinDrift(rnessDir) {
|
|
@@ -5002,9 +5101,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
5002
5101
|
}
|
|
5003
5102
|
}
|
|
5004
5103
|
async function runPinnedSync(root) {
|
|
5005
|
-
const installed = await readPinnedCli(
|
|
5104
|
+
const installed = await readPinnedCli(join14(root, ".rness"));
|
|
5006
5105
|
if (installed === null) return null;
|
|
5007
|
-
const pinned =
|
|
5106
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
5008
5107
|
if (!await exists(pinned))
|
|
5009
5108
|
return {
|
|
5010
5109
|
code: 1,
|
|
@@ -5079,7 +5178,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
5079
5178
|
};
|
|
5080
5179
|
}
|
|
5081
5180
|
async function syncPinned(root, shown, fallback) {
|
|
5082
|
-
const rnessDir =
|
|
5181
|
+
const rnessDir = join14(root, ".rness");
|
|
5083
5182
|
const installed = await readPinnedCli(rnessDir);
|
|
5084
5183
|
if (installed === null) {
|
|
5085
5184
|
if (fallback !== void 0) return fallback();
|
|
@@ -5089,7 +5188,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
5089
5188
|
);
|
|
5090
5189
|
return 1;
|
|
5091
5190
|
}
|
|
5092
|
-
const pinned =
|
|
5191
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
5093
5192
|
if (!await exists(pinned)) {
|
|
5094
5193
|
process.stderr.write(
|
|
5095
5194
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -5129,30 +5228,30 @@ function wholeDiff(existing, content) {
|
|
|
5129
5228
|
}
|
|
5130
5229
|
async function contextOf(root) {
|
|
5131
5230
|
return {
|
|
5132
|
-
packageManager: await workspacePackageManager(
|
|
5231
|
+
packageManager: await workspacePackageManager(join15(root, ".rness"))
|
|
5133
5232
|
};
|
|
5134
5233
|
}
|
|
5135
5234
|
var PATHS_ONLY = { packageManager: "npm" };
|
|
5136
5235
|
async function filesOf(root, manifest, targets) {
|
|
5137
5236
|
const files = [];
|
|
5138
|
-
if (!await exists(
|
|
5237
|
+
if (!await exists(join15(root, "org"))) return files;
|
|
5139
5238
|
const ctx = await contextOf(root);
|
|
5140
5239
|
for (const target2 of targets)
|
|
5141
5240
|
for (const owned of target2.files(ctx))
|
|
5142
5241
|
if (owned.at === "root")
|
|
5143
5242
|
files.push({
|
|
5144
5243
|
...owned,
|
|
5145
|
-
file:
|
|
5244
|
+
file: join15(root, ...owned.file.split("/")),
|
|
5146
5245
|
label: owned.file
|
|
5147
5246
|
});
|
|
5148
5247
|
for (const repo of Object.keys(manifest.repos)) {
|
|
5149
|
-
if (!await exists(
|
|
5248
|
+
if (!await exists(join15(root, "org", repo))) continue;
|
|
5150
5249
|
for (const target2 of targets)
|
|
5151
5250
|
for (const owned of target2.files(ctx))
|
|
5152
5251
|
if (owned.at === "clones")
|
|
5153
5252
|
files.push({
|
|
5154
5253
|
...owned,
|
|
5155
|
-
file:
|
|
5254
|
+
file: join15(root, "org", repo, ...owned.file.split("/")),
|
|
5156
5255
|
label: `org/${repo}/${owned.file}`
|
|
5157
5256
|
});
|
|
5158
5257
|
}
|
|
@@ -5188,7 +5287,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5188
5287
|
continue;
|
|
5189
5288
|
}
|
|
5190
5289
|
await rm4(file);
|
|
5191
|
-
await rmdir(
|
|
5290
|
+
await rmdir(dirname5(file)).catch(() => void 0);
|
|
5192
5291
|
outcomes.push({ label, status: "removed", detail: null });
|
|
5193
5292
|
continue;
|
|
5194
5293
|
}
|
|
@@ -5199,7 +5298,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5199
5298
|
else if (opts.check)
|
|
5200
5299
|
outcomes.push({ label, status: "stale", detail: diff });
|
|
5201
5300
|
else {
|
|
5202
|
-
await mkdir2(
|
|
5301
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5203
5302
|
await writeFileAtomic(file, owned.content);
|
|
5204
5303
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5205
5304
|
}
|
|
@@ -5228,7 +5327,7 @@ async function agentTargets(root, manifest, opts) {
|
|
|
5228
5327
|
outcomes.push({ label, status: "unchanged", detail: null });
|
|
5229
5328
|
continue;
|
|
5230
5329
|
}
|
|
5231
|
-
await mkdir2(
|
|
5330
|
+
await mkdir2(dirname5(file), { recursive: true });
|
|
5232
5331
|
await writeFileAtomic(file, ensured.text);
|
|
5233
5332
|
outcomes.push({ label, status: "updated", detail: null });
|
|
5234
5333
|
}
|
|
@@ -5420,8 +5519,8 @@ function sessionUi(p) {
|
|
|
5420
5519
|
intro: (title) => {
|
|
5421
5520
|
p.intro(badge(title));
|
|
5422
5521
|
},
|
|
5423
|
-
outro: (
|
|
5424
|
-
p.outro(badge(
|
|
5522
|
+
outro: (message3, tone = "done") => {
|
|
5523
|
+
p.outro(badge(message3, tone));
|
|
5425
5524
|
},
|
|
5426
5525
|
line: (verb, rest, sentence) => {
|
|
5427
5526
|
say(verb, sentence ?? `${rest} ${verb}`);
|
|
@@ -5492,7 +5591,7 @@ function targetsOf(manifest, root, only) {
|
|
|
5492
5591
|
const segments = entry.path.split("/");
|
|
5493
5592
|
targets.push({
|
|
5494
5593
|
scope: name,
|
|
5495
|
-
dir:
|
|
5594
|
+
dir: join16(root, ...segments),
|
|
5496
5595
|
depth: segments.length,
|
|
5497
5596
|
label: `${entry.path}/AGENTS.md`
|
|
5498
5597
|
});
|
|
@@ -5652,7 +5751,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5652
5751
|
try {
|
|
5653
5752
|
const notCloned = /* @__PURE__ */ new Set();
|
|
5654
5753
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
5655
|
-
const dir =
|
|
5754
|
+
const dir = join16(ws.root, "org", name);
|
|
5656
5755
|
const label = `org/${name}`;
|
|
5657
5756
|
if (!await exists(dir)) {
|
|
5658
5757
|
if (!toClone.has(name)) {
|
|
@@ -5704,7 +5803,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5704
5803
|
lines.push(
|
|
5705
5804
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
5706
5805
|
);
|
|
5707
|
-
if (!await exists(
|
|
5806
|
+
if (!await exists(join16(ws.root, "org"))) {
|
|
5708
5807
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
5709
5808
|
} else {
|
|
5710
5809
|
let differences = 0;
|
|
@@ -5716,7 +5815,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5716
5815
|
continue;
|
|
5717
5816
|
}
|
|
5718
5817
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
5719
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
5818
|
+
BLOCK_FILES.map((f) => isSymlink(join16(t.dir, f)))
|
|
5720
5819
|
);
|
|
5721
5820
|
const atRoot = t.scope === null;
|
|
5722
5821
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -5739,7 +5838,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5739
5838
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
5740
5839
|
);
|
|
5741
5840
|
}
|
|
5742
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
5841
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join16(t.dir, "AGENTS.md"), block, check);
|
|
5743
5842
|
if (typeof agents === "object") {
|
|
5744
5843
|
problems.push(`${t.label}: ${agents.error}`);
|
|
5745
5844
|
continue;
|
|
@@ -5754,7 +5853,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5754
5853
|
}
|
|
5755
5854
|
if (claudeLinked === true) continue;
|
|
5756
5855
|
const claude = await syncBlockFile(
|
|
5757
|
-
|
|
5856
|
+
join16(t.dir, "CLAUDE.md"),
|
|
5758
5857
|
block,
|
|
5759
5858
|
check,
|
|
5760
5859
|
true
|
|
@@ -5811,7 +5910,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5811
5910
|
}
|
|
5812
5911
|
|
|
5813
5912
|
// src/commands/add.ts
|
|
5814
|
-
import { basename as basename4, join as
|
|
5913
|
+
import { basename as basename4, join as join17 } from "path";
|
|
5815
5914
|
function printDeclared(ui, result, already) {
|
|
5816
5915
|
for (const name of result.declared) {
|
|
5817
5916
|
if (already.has(name)) continue;
|
|
@@ -5905,7 +6004,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5905
6004
|
}
|
|
5906
6005
|
parsed = parseRepoSpec(spec, org, host);
|
|
5907
6006
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
5908
|
-
const dir =
|
|
6007
|
+
const dir = join17(ws.root, "org", parsed.name);
|
|
5909
6008
|
const present = await exists(dir);
|
|
5910
6009
|
if (interactive) {
|
|
5911
6010
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -6032,9 +6131,9 @@ async function contextCommand(opts) {
|
|
|
6032
6131
|
}
|
|
6033
6132
|
|
|
6034
6133
|
// src/core/scaffold.ts
|
|
6035
|
-
import { statSync } from "fs";
|
|
6036
|
-
import { dirname as
|
|
6037
|
-
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";
|
|
6038
6137
|
var SCAFFOLD_FILES = [
|
|
6039
6138
|
"AGENTS.md",
|
|
6040
6139
|
"CONVENTIONS.md",
|
|
@@ -6055,27 +6154,27 @@ var SCAFFOLD_FILES = [
|
|
|
6055
6154
|
"docs/.gitkeep"
|
|
6056
6155
|
];
|
|
6057
6156
|
function scaffoldDir() {
|
|
6058
|
-
let dir =
|
|
6157
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
6059
6158
|
for (let i = 0; i < 4; i += 1) {
|
|
6060
|
-
const candidate =
|
|
6159
|
+
const candidate = join18(dir, "scaffold");
|
|
6061
6160
|
try {
|
|
6062
|
-
if (
|
|
6161
|
+
if (statSync2(join18(candidate, "rness.json")).isFile()) return candidate;
|
|
6063
6162
|
} catch {
|
|
6064
6163
|
}
|
|
6065
|
-
dir =
|
|
6164
|
+
dir = dirname6(dir);
|
|
6066
6165
|
}
|
|
6067
6166
|
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
6068
6167
|
}
|
|
6069
6168
|
|
|
6070
6169
|
// src/core/scaffold-copy.ts
|
|
6071
6170
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
6072
|
-
import { dirname as
|
|
6171
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
6073
6172
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
6074
6173
|
async function renderScaffold(source, tokens) {
|
|
6075
6174
|
const files = [];
|
|
6076
6175
|
for (const rel of SCAFFOLD_FILES) {
|
|
6077
6176
|
if (rel === "rness.json") continue;
|
|
6078
|
-
const from =
|
|
6177
|
+
const from = join19(source, ...rel.split("/"));
|
|
6079
6178
|
let content = await readFile9(from, "utf8");
|
|
6080
6179
|
if (TOKENISED.has(rel)) {
|
|
6081
6180
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
@@ -6090,8 +6189,8 @@ async function renderScaffold(source, tokens) {
|
|
|
6090
6189
|
}
|
|
6091
6190
|
async function copyScaffold(dest, tokens) {
|
|
6092
6191
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
6093
|
-
const to =
|
|
6094
|
-
await mkdir3(
|
|
6192
|
+
const to = join19(dest, ...file.path.split("/"));
|
|
6193
|
+
await mkdir3(dirname7(to), { recursive: true });
|
|
6095
6194
|
await writeFile2(to, file.content, "utf8");
|
|
6096
6195
|
if (file.executable) await chmod2(to, 493);
|
|
6097
6196
|
}
|
|
@@ -6272,7 +6371,7 @@ async function loginCommand(opts, deps = {}) {
|
|
|
6272
6371
|
// src/commands/create.ts
|
|
6273
6372
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm7 } from "fs/promises";
|
|
6274
6373
|
import { tmpdir as tmpdir2 } from "os";
|
|
6275
|
-
import { join as
|
|
6374
|
+
import { join as join23, relative as relative4, resolve as resolve5 } from "path";
|
|
6276
6375
|
|
|
6277
6376
|
// src/core/approval.ts
|
|
6278
6377
|
var APPROVAL_INTERVAL_MS = 5e3;
|
|
@@ -6391,13 +6490,13 @@ async function askProvider(p) {
|
|
|
6391
6490
|
|
|
6392
6491
|
// src/core/new-workspace.ts
|
|
6393
6492
|
import { mkdir as mkdir4, readdir as readdir4, rm as rm6, stat as stat3 } from "fs/promises";
|
|
6394
|
-
import { dirname as
|
|
6493
|
+
import { dirname as dirname9, join as join21, relative as relative2 } from "path";
|
|
6395
6494
|
|
|
6396
6495
|
// src/core/scaffold-git.ts
|
|
6397
6496
|
import { execFile as execFile5 } from "child_process";
|
|
6398
6497
|
import { mkdtemp, realpath as realpath2, rm as rm5, writeFile as writeFile3 } from "fs/promises";
|
|
6399
6498
|
import { tmpdir } from "os";
|
|
6400
|
-
import { dirname as
|
|
6499
|
+
import { dirname as dirname8, join as join20, resolve as resolve3 } from "path";
|
|
6401
6500
|
import { promisify as promisify5 } from "util";
|
|
6402
6501
|
var execFileP5 = promisify5(execFile5);
|
|
6403
6502
|
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
@@ -6411,7 +6510,7 @@ async function run(dir, args, env = {}) {
|
|
|
6411
6510
|
...process.env,
|
|
6412
6511
|
...env,
|
|
6413
6512
|
GIT_TERMINAL_PROMPT: "0",
|
|
6414
|
-
GIT_CEILING_DIRECTORIES:
|
|
6513
|
+
GIT_CEILING_DIRECTORIES: dirname8(resolve3(dir))
|
|
6415
6514
|
},
|
|
6416
6515
|
maxBuffer: 16 * 1024 * 1024
|
|
6417
6516
|
});
|
|
@@ -6438,13 +6537,13 @@ function scaffoldMessage(subject, version) {
|
|
|
6438
6537
|
|
|
6439
6538
|
${SCAFFOLD_TRAILER}: ${version}`;
|
|
6440
6539
|
}
|
|
6441
|
-
async function commitUpgrade(rnessDir,
|
|
6540
|
+
async function commitUpgrade(rnessDir, message3) {
|
|
6442
6541
|
const add = await run(rnessDir, ["add", "-A"]);
|
|
6443
6542
|
if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
|
|
6444
6543
|
const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
|
|
6445
6544
|
if (staged.code === 0 && !await mergeInProgress(rnessDir))
|
|
6446
6545
|
return { kind: "nothing" };
|
|
6447
|
-
const commit = await run(rnessDir, ["commit", "-q", "-m",
|
|
6546
|
+
const commit = await run(rnessDir, ["commit", "-q", "-m", message3]);
|
|
6448
6547
|
if (commit.code !== 0)
|
|
6449
6548
|
return {
|
|
6450
6549
|
kind: "refused",
|
|
@@ -6485,8 +6584,8 @@ async function findScaffoldBase(rnessDir) {
|
|
|
6485
6584
|
};
|
|
6486
6585
|
}
|
|
6487
6586
|
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
6488
|
-
const scratch = await mkdtemp(
|
|
6489
|
-
const env = { GIT_INDEX_FILE:
|
|
6587
|
+
const scratch = await mkdtemp(join20(tmpdir(), "rness-scaffold-"));
|
|
6588
|
+
const env = { GIT_INDEX_FILE: join20(scratch, "index") };
|
|
6490
6589
|
try {
|
|
6491
6590
|
await must(
|
|
6492
6591
|
rnessDir,
|
|
@@ -6494,7 +6593,7 @@ async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
|
6494
6593
|
env
|
|
6495
6594
|
);
|
|
6496
6595
|
for (const [i, file] of files.entries()) {
|
|
6497
|
-
const tmp =
|
|
6596
|
+
const tmp = join20(scratch, `blob-${i}`);
|
|
6498
6597
|
await writeFile3(tmp, file.content, "utf8");
|
|
6499
6598
|
const blob = await must(rnessDir, [
|
|
6500
6599
|
"hash-object",
|
|
@@ -6606,11 +6705,11 @@ async function insideWorkspace(dir) {
|
|
|
6606
6705
|
}
|
|
6607
6706
|
async function targetProblem(cwd, root, pm) {
|
|
6608
6707
|
const shown = relative2(cwd, root) || ".";
|
|
6609
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(
|
|
6708
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname9(root)))
|
|
6610
6709
|
return "already inside an rness workspace; create runs from outside one";
|
|
6611
6710
|
if (!await exists(root)) return null;
|
|
6612
|
-
if (await exists(
|
|
6613
|
-
if (!await exists(
|
|
6711
|
+
if (await exists(join21(root, ".rness", "rness.json"))) {
|
|
6712
|
+
if (!await exists(join21(root, ".rness", "node_modules")))
|
|
6614
6713
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
6615
6714
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
6616
6715
|
}
|
|
@@ -6633,7 +6732,7 @@ async function installContext(input) {
|
|
|
6633
6732
|
}
|
|
6634
6733
|
async function buildContext(input) {
|
|
6635
6734
|
const { ui, root, shown, pm } = input;
|
|
6636
|
-
const rnessDir =
|
|
6735
|
+
const rnessDir = join21(root, ".rness");
|
|
6637
6736
|
const pmVersion = await packageManagerVersion(pm);
|
|
6638
6737
|
await mkdir4(root, { recursive: true });
|
|
6639
6738
|
try {
|
|
@@ -6705,7 +6804,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
6705
6804
|
|
|
6706
6805
|
// src/commands/create-blank.ts
|
|
6707
6806
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
6708
|
-
import { join as
|
|
6807
|
+
import { join as join22, relative as relative3, resolve as resolve4 } from "path";
|
|
6709
6808
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
6710
6809
|
function workspaceNameError(name) {
|
|
6711
6810
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -6787,8 +6886,8 @@ async function createBlank(input) {
|
|
|
6787
6886
|
kind: "blank workspace"
|
|
6788
6887
|
});
|
|
6789
6888
|
if (!built) return 1;
|
|
6790
|
-
await mkdir5(
|
|
6791
|
-
await commitContext({ ui, rnessDir:
|
|
6889
|
+
await mkdir5(join22(root, "org"), { recursive: true });
|
|
6890
|
+
await commitContext({ ui, rnessDir: join22(root, ".rness"), shown });
|
|
6792
6891
|
const code = await syncBlocks(
|
|
6793
6892
|
ui,
|
|
6794
6893
|
root,
|
|
@@ -6932,7 +7031,7 @@ async function cloneSelection(input) {
|
|
|
6932
7031
|
{ doing: `cloning org/${repo}`, git: true },
|
|
6933
7032
|
() => clone(
|
|
6934
7033
|
entry.url,
|
|
6935
|
-
|
|
7034
|
+
join23(input.root, "org", repo),
|
|
6936
7035
|
input.provider.credentialsFor(entry.url)
|
|
6937
7036
|
),
|
|
6938
7037
|
() => ["cloned", `org/${repo}`]
|
|
@@ -6957,9 +7056,9 @@ async function cloneSelection(input) {
|
|
|
6957
7056
|
manifest = result.manifest;
|
|
6958
7057
|
ui.line("declared", `scope ${result.name} (org/${result.name})`);
|
|
6959
7058
|
} catch (e) {
|
|
6960
|
-
const
|
|
6961
|
-
ui.error(`${spec}: ${
|
|
6962
|
-
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 });
|
|
6963
7062
|
}
|
|
6964
7063
|
}
|
|
6965
7064
|
return failures;
|
|
@@ -7295,8 +7394,8 @@ async function createCommand(opts, deps = {}) {
|
|
|
7295
7394
|
scopes: {}
|
|
7296
7395
|
};
|
|
7297
7396
|
const staged = async () => {
|
|
7298
|
-
staging ??= await mkdtemp2(
|
|
7299
|
-
return
|
|
7397
|
+
staging ??= await mkdtemp2(join23(tmpdir2(), "rness-join-"));
|
|
7398
|
+
return join23(staging, ".rness");
|
|
7300
7399
|
};
|
|
7301
7400
|
if (joining) {
|
|
7302
7401
|
const stagedDir = await staged();
|
|
@@ -7358,7 +7457,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7358
7457
|
});
|
|
7359
7458
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
7360
7459
|
}
|
|
7361
|
-
const rnessDir =
|
|
7460
|
+
const rnessDir = join23(root, ".rness");
|
|
7362
7461
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
7363
7462
|
if (joining) {
|
|
7364
7463
|
await mkdir6(root, { recursive: true });
|
|
@@ -7371,7 +7470,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
7371
7470
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
7372
7471
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
7373
7472
|
await install();
|
|
7374
|
-
await mkdir6(
|
|
7473
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7375
7474
|
const failures2 = await cloneSelection({
|
|
7376
7475
|
root,
|
|
7377
7476
|
rnessDir,
|
|
@@ -7424,7 +7523,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7424
7523
|
kind: "new workspace"
|
|
7425
7524
|
});
|
|
7426
7525
|
if (!built) return 1;
|
|
7427
|
-
await mkdir6(
|
|
7526
|
+
await mkdir6(join23(root, "org"), { recursive: true });
|
|
7428
7527
|
const failures = await cloneSelection({
|
|
7429
7528
|
root,
|
|
7430
7529
|
rnessDir,
|
|
@@ -7496,7 +7595,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
7496
7595
|
}
|
|
7497
7596
|
|
|
7498
7597
|
// src/core/blocks.ts
|
|
7499
|
-
import { join as
|
|
7598
|
+
import { join as join24 } from "path";
|
|
7500
7599
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
7501
7600
|
async function checkBlocks({
|
|
7502
7601
|
root,
|
|
@@ -7505,15 +7604,15 @@ async function checkBlocks({
|
|
|
7505
7604
|
org
|
|
7506
7605
|
}) {
|
|
7507
7606
|
const checks = [];
|
|
7508
|
-
const note = (label, status2,
|
|
7509
|
-
checks.push({ label, status: status2, message:
|
|
7607
|
+
const note = (label, status2, message3 = null) => {
|
|
7608
|
+
checks.push({ label, status: status2, message: message3 });
|
|
7510
7609
|
};
|
|
7511
7610
|
const derive = () => ({
|
|
7512
7611
|
problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7513
7612
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
7514
7613
|
checks
|
|
7515
7614
|
});
|
|
7516
|
-
if (!await exists(
|
|
7615
|
+
if (!await exists(join24(root, "org"))) return derive();
|
|
7517
7616
|
const targets = [
|
|
7518
7617
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
7519
7618
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -7522,7 +7621,7 @@ async function checkBlocks({
|
|
|
7522
7621
|
const segments = entry.path.split("/");
|
|
7523
7622
|
targets.push({
|
|
7524
7623
|
scope: name,
|
|
7525
|
-
dir:
|
|
7624
|
+
dir: join24(root, ...segments),
|
|
7526
7625
|
depth: segments.length,
|
|
7527
7626
|
file: "AGENTS.md",
|
|
7528
7627
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -7537,7 +7636,7 @@ async function checkBlocks({
|
|
|
7537
7636
|
);
|
|
7538
7637
|
continue;
|
|
7539
7638
|
}
|
|
7540
|
-
const text = await readOrNull(
|
|
7639
|
+
const text = await readOrNull(join24(t.dir, t.file));
|
|
7541
7640
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
7542
7641
|
const found = findBlock(lines);
|
|
7543
7642
|
if (found.kind === "none") {
|
|
@@ -7608,7 +7707,7 @@ async function logoutCommand() {
|
|
|
7608
7707
|
import { execFile as execFile6 } from "child_process";
|
|
7609
7708
|
import { mkdtemp as mkdtemp3, readFile as readFile10, rm as rm8 } from "fs/promises";
|
|
7610
7709
|
import { tmpdir as tmpdir3 } from "os";
|
|
7611
|
-
import { basename as basename5, join as
|
|
7710
|
+
import { basename as basename5, join as join25, relative as relative5 } from "path";
|
|
7612
7711
|
import { promisify as promisify6 } from "util";
|
|
7613
7712
|
var execFileP6 = promisify6(execFile6);
|
|
7614
7713
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -7628,10 +7727,10 @@ async function latestVersion() {
|
|
|
7628
7727
|
async function scaffoldFor(version, rnessDir) {
|
|
7629
7728
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
7630
7729
|
const installed = await readPinnedCli(rnessDir);
|
|
7631
|
-
const inside2 = installed === null ? null :
|
|
7632
|
-
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")))
|
|
7633
7732
|
return { dir: inside2 };
|
|
7634
|
-
const scratch = await mkdtemp3(
|
|
7733
|
+
const scratch = await mkdtemp3(join25(tmpdir3(), "rness-pack-"));
|
|
7635
7734
|
const cleanup = () => rm8(scratch, { recursive: true, force: true });
|
|
7636
7735
|
try {
|
|
7637
7736
|
const { stdout } = await execFileP6(
|
|
@@ -7646,8 +7745,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7646
7745
|
{ timeout: 6e4 }
|
|
7647
7746
|
);
|
|
7648
7747
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
7649
|
-
await execFileP6("tar", ["-xzf",
|
|
7650
|
-
return { dir:
|
|
7748
|
+
await execFileP6("tar", ["-xzf", join25(scratch, tarball), "-C", scratch]);
|
|
7749
|
+
return { dir: join25(scratch, "package", "scaffold"), cleanup };
|
|
7651
7750
|
} catch (e) {
|
|
7652
7751
|
await cleanup();
|
|
7653
7752
|
throw new Error(
|
|
@@ -7659,7 +7758,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
7659
7758
|
async function declaredManager(rnessDir, pm) {
|
|
7660
7759
|
try {
|
|
7661
7760
|
const pkg = JSON.parse(
|
|
7662
|
-
await readFile10(
|
|
7761
|
+
await readFile10(join25(rnessDir, "package.json"), "utf8")
|
|
7663
7762
|
);
|
|
7664
7763
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
7665
7764
|
} catch {
|
|
@@ -7669,7 +7768,7 @@ async function declaredManager(rnessDir, pm) {
|
|
|
7669
7768
|
async function clonesToCommit(root, cwd, manifest, files) {
|
|
7670
7769
|
const clones = [];
|
|
7671
7770
|
for (const repo of Object.keys(manifest.repos)) {
|
|
7672
|
-
const dir =
|
|
7771
|
+
const dir = join25(root, "org", repo);
|
|
7673
7772
|
if (!await exists(dir)) continue;
|
|
7674
7773
|
try {
|
|
7675
7774
|
const paths = await changedPaths(dir, files);
|
|
@@ -7687,7 +7786,7 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7687
7786
|
try {
|
|
7688
7787
|
const { stdout } = await execFileP6(
|
|
7689
7788
|
process.execPath,
|
|
7690
|
-
[
|
|
7789
|
+
[join25(pinned.dir, pinned.bin), "written-files", "--cwd", root],
|
|
7691
7790
|
{
|
|
7692
7791
|
cwd: root,
|
|
7693
7792
|
env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
|
|
@@ -7703,14 +7802,14 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
7703
7802
|
}
|
|
7704
7803
|
function finish(ui, cwd, rnessDir, label, target2, ending) {
|
|
7705
7804
|
const rel = relative5(cwd, rnessDir) || ".";
|
|
7706
|
-
const
|
|
7805
|
+
const message3 = `chore: rness ${target2}`;
|
|
7707
7806
|
const next = [];
|
|
7708
7807
|
if (ending.commit === "committed") next.push(`git -C ${rel} push`);
|
|
7709
7808
|
if (ending.commit === "refused")
|
|
7710
|
-
next.push(`git -C ${rel} commit -m "${
|
|
7809
|
+
next.push(`git -C ${rel} commit -m "${message3}"`);
|
|
7711
7810
|
for (const c of ending.clones)
|
|
7712
7811
|
next.push(
|
|
7713
|
-
`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}"`
|
|
7714
7813
|
);
|
|
7715
7814
|
if (ending.commit !== "nothing")
|
|
7716
7815
|
next.push("# teammates: git pull \u2014 the next rness command installs it");
|
|
@@ -7883,7 +7982,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7883
7982
|
await source.cleanup?.();
|
|
7884
7983
|
}
|
|
7885
7984
|
}
|
|
7886
|
-
const packageFile =
|
|
7985
|
+
const packageFile = join25(ws.rnessDir, "package.json");
|
|
7887
7986
|
const restore = /* @__PURE__ */ new Map([
|
|
7888
7987
|
[packageFile, await readFile10(packageFile, "utf8")]
|
|
7889
7988
|
]);
|
|
@@ -7899,8 +7998,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7899
7998
|
() => ["installed", `dependencies with ${pm}`]
|
|
7900
7999
|
);
|
|
7901
8000
|
} catch (e) {
|
|
7902
|
-
const
|
|
7903
|
-
ui.error(
|
|
8001
|
+
const message4 = e instanceof Error ? e.message : String(e);
|
|
8002
|
+
ui.error(message4);
|
|
7904
8003
|
if (!merged) {
|
|
7905
8004
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
7906
8005
|
ui.hint(
|
|
@@ -7913,7 +8012,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7913
8012
|
`the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
|
|
7914
8013
|
"stderr"
|
|
7915
8014
|
);
|
|
7916
|
-
if (/minimum.?release.?age|NO_MATURE/i.test(
|
|
8015
|
+
if (/minimum.?release.?age|NO_MATURE/i.test(message4))
|
|
7917
8016
|
ui.hint(
|
|
7918
8017
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
7919
8018
|
"stderr"
|
|
@@ -7932,13 +8031,13 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
7932
8031
|
ui.gitIsSilent = true;
|
|
7933
8032
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
7934
8033
|
if (code !== 0) return code;
|
|
7935
|
-
const
|
|
7936
|
-
const committed = await commitUpgrade(ws.rnessDir,
|
|
8034
|
+
const message3 = `chore: rness ${target2}`;
|
|
8035
|
+
const committed = await commitUpgrade(ws.rnessDir, message3);
|
|
7937
8036
|
if (committed.kind === "committed")
|
|
7938
8037
|
ui.line(
|
|
7939
8038
|
"committed",
|
|
7940
|
-
`${label} \u2014 ${
|
|
7941
|
-
`Committed ${label}: ${
|
|
8039
|
+
`${label} \u2014 ${message3}`,
|
|
8040
|
+
`Committed ${label}: ${message3}`
|
|
7942
8041
|
);
|
|
7943
8042
|
if (committed.kind === "refused") {
|
|
7944
8043
|
ui.error(
|
|
@@ -8053,7 +8152,7 @@ import { Command, CommanderError, Option } from "commander";
|
|
|
8053
8152
|
|
|
8054
8153
|
// src/commands/doc.ts
|
|
8055
8154
|
import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
|
|
8056
|
-
import { join as
|
|
8155
|
+
import { join as join26 } from "path";
|
|
8057
8156
|
var NUMBERED = Object.keys(STATUSES);
|
|
8058
8157
|
var SECTIONS = {
|
|
8059
8158
|
specs: [
|
|
@@ -8077,8 +8176,8 @@ function headingsOf(template) {
|
|
|
8077
8176
|
return [...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim());
|
|
8078
8177
|
}
|
|
8079
8178
|
async function adrSections(rnessDir) {
|
|
8080
|
-
const own =
|
|
8081
|
-
const shipped =
|
|
8179
|
+
const own = join26(rnessDir, "adr", "0000-template.md");
|
|
8180
|
+
const shipped = join26(scaffoldDir(), "adr", "0000-template.md");
|
|
8082
8181
|
for (const file of [own, shipped]) {
|
|
8083
8182
|
try {
|
|
8084
8183
|
const sections = headingsOf(await readFile11(file, "utf8"));
|
|
@@ -8112,7 +8211,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8112
8211
|
const name = collection;
|
|
8113
8212
|
try {
|
|
8114
8213
|
const ws = await findWorkspace(opts.cwd ?? process.cwd());
|
|
8115
|
-
const dir =
|
|
8214
|
+
const dir = join26(ws.rnessDir, name);
|
|
8116
8215
|
await mkdir7(dir, { recursive: true });
|
|
8117
8216
|
let highest = 0;
|
|
8118
8217
|
for (const item of await collectMarkdown(dir)) {
|
|
@@ -8121,7 +8220,7 @@ async function docNewCommand(collection, opts) {
|
|
|
8121
8220
|
}
|
|
8122
8221
|
const number = String(highest + 1).padStart(4, "0");
|
|
8123
8222
|
const title = opts.title?.trim() || "Untitled";
|
|
8124
|
-
const file =
|
|
8223
|
+
const file = join26(dir, `${number}-${slugOf(title)}.md`);
|
|
8125
8224
|
const status2 = STATUSES[name]?.[0] ?? "Draft";
|
|
8126
8225
|
const date = today();
|
|
8127
8226
|
const front = [
|
|
@@ -8174,9 +8273,243 @@ password=${resolved.token}
|
|
|
8174
8273
|
import { realpath as realpath4 } from "fs/promises";
|
|
8175
8274
|
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
8176
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
|
+
|
|
8177
8510
|
// src/mcp/tools.ts
|
|
8178
|
-
import { readFile as
|
|
8179
|
-
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";
|
|
8180
8513
|
var LABELS = {
|
|
8181
8514
|
standards: "Standards",
|
|
8182
8515
|
adr: "Decisions",
|
|
@@ -8205,7 +8538,7 @@ async function documents(rnessDir, only) {
|
|
|
8205
8538
|
const docs = [];
|
|
8206
8539
|
for (const collection of COLLECTIONS) {
|
|
8207
8540
|
if (only !== void 0 && collection !== only) continue;
|
|
8208
|
-
for (const item of await collectMarkdown(
|
|
8541
|
+
for (const item of await collectMarkdown(join29(rnessDir, collection))) {
|
|
8209
8542
|
const path = `${collection}/${item.rel}`;
|
|
8210
8543
|
if (path === TEMPLATE2) continue;
|
|
8211
8544
|
const status2 = item.fields?.["status"];
|
|
@@ -8321,13 +8654,13 @@ async function read(cwd, args) {
|
|
|
8321
8654
|
}
|
|
8322
8655
|
if (!real.startsWith(`${root}${sep4}`))
|
|
8323
8656
|
return refuse(`${path}: leads outside .rness/`);
|
|
8324
|
-
const info = await
|
|
8657
|
+
const info = await stat5(real);
|
|
8325
8658
|
if (!info.isFile()) return refuse(`${path}: not a file`);
|
|
8326
8659
|
if (info.size > READ_LIMIT)
|
|
8327
8660
|
return refuse(
|
|
8328
8661
|
`${path}: ${info.size} bytes, over the 256 KiB rness_read returns`
|
|
8329
8662
|
);
|
|
8330
|
-
return { text: await
|
|
8663
|
+
return { text: await readFile14(real, "utf8") };
|
|
8331
8664
|
}
|
|
8332
8665
|
async function search(cwd, args) {
|
|
8333
8666
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -8433,74 +8766,7 @@ function mcpTools(cwd) {
|
|
|
8433
8766
|
];
|
|
8434
8767
|
}
|
|
8435
8768
|
|
|
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
|
|
8769
|
+
// src/commands/snapshot.ts
|
|
8504
8770
|
var NOUNS = {
|
|
8505
8771
|
standards: ["standard", "standards"],
|
|
8506
8772
|
adr: ["decision", "decisions"],
|
|
@@ -8508,7 +8774,37 @@ var NOUNS = {
|
|
|
8508
8774
|
plans: ["plan", "plans"],
|
|
8509
8775
|
skills: ["skill", "skills"]
|
|
8510
8776
|
};
|
|
8511
|
-
|
|
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
|
|
8512
8808
|
async function readInput(input) {
|
|
8513
8809
|
if (input.isTTY === true) return {};
|
|
8514
8810
|
let text = "";
|
|
@@ -8520,32 +8816,13 @@ async function readInput(input) {
|
|
|
8520
8816
|
return {};
|
|
8521
8817
|
}
|
|
8522
8818
|
}
|
|
8523
|
-
var
|
|
8819
|
+
var message2 = (e) => e instanceof Error ? e.message : String(e);
|
|
8524
8820
|
function cwdOf(input, env) {
|
|
8525
8821
|
const cwd = input["cwd"];
|
|
8526
8822
|
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
8527
8823
|
return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
|
|
8528
8824
|
}
|
|
8529
8825
|
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
8826
|
function sessionOf(input) {
|
|
8550
8827
|
const id = input["session_id"];
|
|
8551
8828
|
if (typeof id !== "string" || id === "") return null;
|
|
@@ -8573,21 +8850,6 @@ async function markDetached(ws, input, io, rest) {
|
|
|
8573
8850
|
} catch {
|
|
8574
8851
|
}
|
|
8575
8852
|
}
|
|
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
8853
|
async function sessionStart(input, io) {
|
|
8592
8854
|
const cwd = cwdOf(input, io.env);
|
|
8593
8855
|
let banner2 = null;
|
|
@@ -8597,15 +8859,11 @@ async function sessionStart(input, io) {
|
|
|
8597
8859
|
const ws = await findWorkspace(cwd);
|
|
8598
8860
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8599
8861
|
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(", ")}`;
|
|
8862
|
+
const view = await scopeBanner(ws, manifest, scope);
|
|
8863
|
+
banner2 = view.banner;
|
|
8606
8864
|
const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
|
|
8607
8865
|
context3 = [
|
|
8608
|
-
...
|
|
8866
|
+
...view.lines,
|
|
8609
8867
|
`These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
|
|
8610
8868
|
];
|
|
8611
8869
|
notes = await safetyNet(ws, manifest);
|
|
@@ -8617,14 +8875,17 @@ async function sessionStart(input, io) {
|
|
|
8617
8875
|
(session) => paths.length === 0 ? null : ["--session", session, ...paths.flatMap((p) => ["--path", p])]
|
|
8618
8876
|
);
|
|
8619
8877
|
} catch (e) {
|
|
8620
|
-
notes = [
|
|
8878
|
+
notes = [message2(e)];
|
|
8621
8879
|
}
|
|
8622
8880
|
const failure = await takeFailure();
|
|
8623
8881
|
if (failure !== null) notes.push(`pulse not updated \u2014 ${failure}`);
|
|
8624
|
-
const warnings = notes
|
|
8882
|
+
const warnings = noteLines(notes);
|
|
8883
|
+
const compact = input["source"] === "compact";
|
|
8884
|
+
const tooOld = compact ? null : await takeModNotice(io.env);
|
|
8625
8885
|
const shown = [
|
|
8626
|
-
...banner2 !== null &&
|
|
8627
|
-
...warnings
|
|
8886
|
+
...banner2 !== null && !compact ? [banner2] : [],
|
|
8887
|
+
...warnings,
|
|
8888
|
+
...tooOld !== null ? [tooOld] : []
|
|
8628
8889
|
];
|
|
8629
8890
|
return {
|
|
8630
8891
|
...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
|
|
@@ -8649,11 +8910,7 @@ async function postToolUse(input, io) {
|
|
|
8649
8910
|
} catch {
|
|
8650
8911
|
return 0;
|
|
8651
8912
|
}
|
|
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
|
-
);
|
|
8913
|
+
const rel = await within(ws.rnessDir, resolve7(cwd, path));
|
|
8657
8914
|
if (rel === null) return 0;
|
|
8658
8915
|
const first = rel.split("/")[0] ?? "";
|
|
8659
8916
|
let problems = [];
|
|
@@ -8662,7 +8919,7 @@ async function postToolUse(input, io) {
|
|
|
8662
8919
|
const manifest = await loadManifest(ws.rnessDir);
|
|
8663
8920
|
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
8664
8921
|
} catch (e) {
|
|
8665
|
-
problems = [
|
|
8922
|
+
problems = [message2(e)];
|
|
8666
8923
|
}
|
|
8667
8924
|
} else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
|
|
8668
8925
|
problems = (await checkContract(ws.rnessDir)).filter(
|
|
@@ -8685,6 +8942,95 @@ async function postToolUse(input, io) {
|
|
|
8685
8942
|
);
|
|
8686
8943
|
return 2;
|
|
8687
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
|
+
}
|
|
8688
9034
|
async function sessionEnd(input, io) {
|
|
8689
9035
|
try {
|
|
8690
9036
|
const ws = await findWorkspace(cwdOf(input, io.env));
|
|
@@ -8703,14 +9049,15 @@ async function hookCommand(event, io = {
|
|
|
8703
9049
|
error: process.stderr,
|
|
8704
9050
|
env: process.env
|
|
8705
9051
|
}) {
|
|
8706
|
-
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") {
|
|
8707
9053
|
io.error.write(
|
|
8708
|
-
`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)
|
|
8709
9055
|
`
|
|
8710
9056
|
);
|
|
8711
9057
|
return 1;
|
|
8712
9058
|
}
|
|
8713
9059
|
const input = await readInput(io.input);
|
|
9060
|
+
if (event === "pre-tool-use") return preToolUse(input, io);
|
|
8714
9061
|
if (event === "post-tool-use") return postToolUse(input, io);
|
|
8715
9062
|
if (event === "session-end") return sessionEnd(input, io);
|
|
8716
9063
|
io.output.write(`${JSON.stringify(await sessionStart(input, io))}
|
|
@@ -8736,31 +9083,31 @@ var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
|
8736
9083
|
function isObject2(value) {
|
|
8737
9084
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8738
9085
|
}
|
|
8739
|
-
function errorLine(id, code,
|
|
9086
|
+
function errorLine(id, code, message3, data) {
|
|
8740
9087
|
return JSON.stringify({
|
|
8741
9088
|
jsonrpc: "2.0",
|
|
8742
9089
|
id,
|
|
8743
|
-
error: data === void 0 ? { code, message:
|
|
9090
|
+
error: data === void 0 ? { code, message: message3 } : { code, message: message3, data }
|
|
8744
9091
|
});
|
|
8745
9092
|
}
|
|
8746
9093
|
function createProtocol(opts) {
|
|
8747
9094
|
const serverInfo = { name: opts.name, version: opts.version };
|
|
8748
9095
|
return {
|
|
8749
9096
|
async handle(line3) {
|
|
8750
|
-
let
|
|
9097
|
+
let message3;
|
|
8751
9098
|
try {
|
|
8752
|
-
|
|
9099
|
+
message3 = JSON.parse(line3);
|
|
8753
9100
|
} catch {
|
|
8754
9101
|
return errorLine(null, -32700, "Parse error");
|
|
8755
9102
|
}
|
|
8756
|
-
const rawId = isObject2(
|
|
9103
|
+
const rawId = isObject2(message3) ? message3["id"] : void 0;
|
|
8757
9104
|
const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
|
|
8758
|
-
if (!isObject2(
|
|
9105
|
+
if (!isObject2(message3) || message3["jsonrpc"] !== "2.0" || typeof message3["method"] !== "string")
|
|
8759
9106
|
return errorLine(id, -32600, "Invalid Request");
|
|
8760
|
-
if (!("id" in
|
|
9107
|
+
if (!("id" in message3)) return null;
|
|
8761
9108
|
if (id === null) return errorLine(null, -32600, "Invalid Request");
|
|
8762
|
-
const method =
|
|
8763
|
-
const params = isObject2(
|
|
9109
|
+
const method = message3["method"];
|
|
9110
|
+
const params = isObject2(message3["params"]) ? message3["params"] : {};
|
|
8764
9111
|
const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
|
|
8765
9112
|
const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
|
|
8766
9113
|
if (modern) {
|
|
@@ -8868,13 +9215,13 @@ async function mcpCommand(opts, io = {
|
|
|
8868
9215
|
}
|
|
8869
9216
|
|
|
8870
9217
|
// src/commands/pulse.ts
|
|
8871
|
-
import { readFile as
|
|
8872
|
-
import { join as
|
|
9218
|
+
import { readFile as readFile16, readdir as readdir5 } from "fs/promises";
|
|
9219
|
+
import { join as join30 } from "path";
|
|
8873
9220
|
import { setTimeout as delay3 } from "timers/promises";
|
|
8874
9221
|
|
|
8875
9222
|
// src/pulse/opened.ts
|
|
8876
|
-
import { mkdir as
|
|
8877
|
-
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";
|
|
8878
9225
|
var NAME2 = "rness/opened";
|
|
8879
9226
|
function openedFile(rnessDir) {
|
|
8880
9227
|
return gitPath(rnessDir, NAME2);
|
|
@@ -8885,7 +9232,7 @@ async function readOpened(file) {
|
|
|
8885
9232
|
if (file === null) return opened;
|
|
8886
9233
|
let data;
|
|
8887
9234
|
try {
|
|
8888
|
-
data = JSON.parse(await
|
|
9235
|
+
data = JSON.parse(await readFile15(file, "utf8"));
|
|
8889
9236
|
} catch {
|
|
8890
9237
|
return opened;
|
|
8891
9238
|
}
|
|
@@ -8904,7 +9251,7 @@ async function writeOpened(file, opened) {
|
|
|
8904
9251
|
return;
|
|
8905
9252
|
}
|
|
8906
9253
|
const entries = [...opened].sort(([a], [b]) => a - b);
|
|
8907
|
-
await
|
|
9254
|
+
await mkdir10(dirname10(file), { recursive: true });
|
|
8908
9255
|
const temporary = `${file}.${process.pid}`;
|
|
8909
9256
|
await writeFile6(
|
|
8910
9257
|
temporary,
|
|
@@ -8982,10 +9329,10 @@ async function directoriesOf(dir) {
|
|
|
8982
9329
|
}
|
|
8983
9330
|
}
|
|
8984
9331
|
async function scopeOf(c, name, tabs, owners) {
|
|
8985
|
-
const
|
|
9332
|
+
const within2 = (collection) => (path) => path.startsWith(`${collection}/`);
|
|
8986
9333
|
if (name === PULSE) {
|
|
8987
9334
|
const shown = tabs.filter((t) => !owners.includes(t.name));
|
|
8988
|
-
const elsewhere = (path) => owners.some((o) =>
|
|
9335
|
+
const elsewhere = (path) => owners.some((o) => within2(o)(path));
|
|
8989
9336
|
return {
|
|
8990
9337
|
label: "Agent Pulse",
|
|
8991
9338
|
want: desiredOf(shown, c.org),
|
|
@@ -9000,7 +9347,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
9000
9347
|
label: labelOf(name),
|
|
9001
9348
|
rows: []
|
|
9002
9349
|
};
|
|
9003
|
-
const dir =
|
|
9350
|
+
const dir = join30(c.rnessDir, name);
|
|
9004
9351
|
const shape = await readShape(c.rnessDir, name);
|
|
9005
9352
|
const fronts = new Map(
|
|
9006
9353
|
(await collectMarkdown(dir)).map((item) => [
|
|
@@ -9018,7 +9365,7 @@ async function scopeOf(c, name, tabs, owners) {
|
|
|
9018
9365
|
want,
|
|
9019
9366
|
layout: layoutOf([tab], { shape, want }),
|
|
9020
9367
|
elsewhere: () => false,
|
|
9021
|
-
mine:
|
|
9368
|
+
mine: within2(name),
|
|
9022
9369
|
own: { shape, updates: await readUpdates(c.rnessDir, name) }
|
|
9023
9370
|
};
|
|
9024
9371
|
}
|
|
@@ -9046,7 +9393,7 @@ async function working(ui, doing, count3, work) {
|
|
|
9046
9393
|
async function filesOf2(rnessDir) {
|
|
9047
9394
|
const files = /* @__PURE__ */ new Set();
|
|
9048
9395
|
const walk = async (rel) => {
|
|
9049
|
-
const dir = rel === "" ? rnessDir :
|
|
9396
|
+
const dir = rel === "" ? rnessDir : join30(rnessDir, ...rel.split("/"));
|
|
9050
9397
|
for (const e of await readdir5(dir, { withFileTypes: true })) {
|
|
9051
9398
|
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
9052
9399
|
if (e.isDirectory()) {
|
|
@@ -9158,7 +9505,7 @@ async function syncBoard(c, board, ui, sayLayout, scope) {
|
|
|
9158
9505
|
w.path,
|
|
9159
9506
|
issueBody({
|
|
9160
9507
|
path: w.path,
|
|
9161
|
-
text: await
|
|
9508
|
+
text: await readFile16(join30(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
9162
9509
|
org: c.org,
|
|
9163
9510
|
numbers,
|
|
9164
9511
|
files
|
|
@@ -9371,6 +9718,7 @@ async function mark(opts) {
|
|
|
9371
9718
|
}
|
|
9372
9719
|
|
|
9373
9720
|
// src/commands/status.ts
|
|
9721
|
+
import { posix as posix9, relative as relative8, sep as sep6 } from "path";
|
|
9374
9722
|
import { emitKeypressEvents } from "readline";
|
|
9375
9723
|
|
|
9376
9724
|
// src/core/status-view.ts
|
|
@@ -9573,7 +9921,8 @@ function showView(tabs, workspace, start, { input, output }) {
|
|
|
9573
9921
|
}
|
|
9574
9922
|
async function statusCommand(tab, opts, terminal = { input: process.stdin, output: process.stdout }) {
|
|
9575
9923
|
try {
|
|
9576
|
-
const
|
|
9924
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9925
|
+
const ws = await findWorkspace(cwd);
|
|
9577
9926
|
const manifest = await loadManifest(ws.rnessDir);
|
|
9578
9927
|
const workspace = workspaceName(manifest, ws.root);
|
|
9579
9928
|
const tabs = await statusTabs(ws.rnessDir);
|
|
@@ -9585,6 +9934,18 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9585
9934
|
);
|
|
9586
9935
|
return 2;
|
|
9587
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
|
+
}
|
|
9588
9949
|
if (terminal.input.isTTY === true && terminal.output.isTTY === true) {
|
|
9589
9950
|
await showView(
|
|
9590
9951
|
tabs,
|
|
@@ -9594,9 +9955,7 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
|
|
|
9594
9955
|
);
|
|
9595
9956
|
return 0;
|
|
9596
9957
|
}
|
|
9597
|
-
terminal.output.write(
|
|
9598
|
-
statusMarkdown(workspace, wanted === void 0 ? tabs : [wanted])
|
|
9599
|
-
);
|
|
9958
|
+
terminal.output.write(statusMarkdown(workspace, shown));
|
|
9600
9959
|
return 0;
|
|
9601
9960
|
} catch (e) {
|
|
9602
9961
|
return reportError(e);
|
|
@@ -9647,7 +10006,7 @@ function buildProgram(state) {
|
|
|
9647
10006
|
});
|
|
9648
10007
|
program.command("status").description(
|
|
9649
10008
|
"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) => {
|
|
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) => {
|
|
9651
10010
|
state.code = await statusCommand(tab, opts);
|
|
9652
10011
|
});
|
|
9653
10012
|
const doc = program.command("doc").description("Documents of the workspace: doc new <collection>");
|
|
@@ -9729,7 +10088,10 @@ function buildProgram(state) {
|
|
|
9729
10088
|
program.command("written-files", { hidden: true }).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
9730
10089
|
state.code = await writtenFilesCommand(opts);
|
|
9731
10090
|
});
|
|
9732
|
-
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) => {
|
|
9733
10095
|
state.code = await hookCommand(event);
|
|
9734
10096
|
});
|
|
9735
10097
|
program.command("upgrade").alias("update").description(
|
|
@@ -9918,4 +10280,4 @@ export {
|
|
|
9918
10280
|
OWN_COMMANDS,
|
|
9919
10281
|
catchUp
|
|
9920
10282
|
};
|
|
9921
|
-
//# sourceMappingURL=chunk-
|
|
10283
|
+
//# sourceMappingURL=chunk-U5SBNWVD.js.map
|