@rness/cli 0.9.2 → 0.10.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.
@@ -558,8 +558,8 @@ async function getOrgMembership(org, options) {
558
558
  }
559
559
  if (status2 === 404) return "not-member";
560
560
  if (status2 === 403) {
561
- const message = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
562
- if (/OAuth App access restrictions/i.test(message)) return "restricted";
561
+ const message2 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
562
+ if (/OAuth App access restrictions/i.test(message2)) return "restricted";
563
563
  }
564
564
  return "unknown";
565
565
  }
@@ -581,8 +581,8 @@ async function createRepository(owner, name, options) {
581
581
  throw new Error(`cannot reach the GitHub API: ${reason2(e)}`, { cause: e });
582
582
  }
583
583
  const body = await res.json().catch(() => void 0);
584
- const message = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
585
- return { status: res.status, message };
584
+ const message2 = body !== null && typeof body === "object" ? String(body["message"] ?? "") : "";
585
+ return { status: res.status, message: message2 };
586
586
  };
587
587
  let answer = await post2(`/orgs/${encodeURIComponent(owner)}/repos`);
588
588
  if (answer.status === 404 && options.self !== void 0 && options.self !== null && options.self.toLowerCase() === owner.toLowerCase())
@@ -775,9 +775,9 @@ async function originUrl(dir) {
775
775
  async function init(dir) {
776
776
  await git(["init", "-q", "-b", "main"], dir);
777
777
  }
778
- async function commitAll(dir, message) {
778
+ async function commitAll(dir, message2) {
779
779
  await git(["add", "-A"], dir);
780
- await git(["commit", "-q", "-m", message], dir);
780
+ await git(["commit", "-q", "-m", message2], dir);
781
781
  }
782
782
 
783
783
  // src/core/remote.ts
@@ -812,7 +812,7 @@ function classifyProbe(url, exitCode, stderr) {
812
812
  var STDERR_FLUSH_MS = 200;
813
813
  async function probeRemote(url, timeoutMs = 6e4, credentials) {
814
814
  positional(url, "repository url");
815
- return new Promise((resolve7) => {
815
+ return new Promise((resolve8) => {
816
816
  const auth = withCredentials(credentials);
817
817
  const child = spawn(
818
818
  "git",
@@ -831,7 +831,7 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
831
831
  settled = true;
832
832
  clearTimeout(timer);
833
833
  child.stderr?.destroy();
834
- resolve7(probe);
834
+ resolve8(probe);
835
835
  };
836
836
  const timer = setTimeout(() => {
837
837
  child.kill("SIGKILL");
@@ -876,8 +876,8 @@ function isWorkspaceClone(name) {
876
876
  var NAME_RULE = "lowercase letters, digits, '.', '_' and '-', not starting with '-'";
877
877
  var ORG_NAME = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
878
878
  var KEYS = ["contract", "org", "agents", "repos", "scopes"];
879
- function fail(message) {
880
- throw new Error(`rness.json: ${message}`);
879
+ function fail(message2) {
880
+ throw new Error(`rness.json: ${message2}`);
881
881
  }
882
882
  function isRecord(value) {
883
883
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1263,7 +1263,7 @@ async function detectGithubSsh(opts) {
1263
1263
  ...opts.interactive ? [] : ["-o", "BatchMode=yes"],
1264
1264
  "git@github.com"
1265
1265
  ];
1266
- return new Promise((resolve7) => {
1266
+ return new Promise((resolve8) => {
1267
1267
  const child = spawn2(opts.command ?? "ssh", args, {
1268
1268
  stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
1269
1269
  });
@@ -1276,7 +1276,7 @@ async function detectGithubSsh(opts) {
1276
1276
  settled = true;
1277
1277
  clearTimeout(timer);
1278
1278
  child.stderr?.destroy();
1279
- resolve7(access2);
1279
+ resolve8(access2);
1280
1280
  };
1281
1281
  const timer = setTimeout(() => {
1282
1282
  child.kill("SIGKILL");
@@ -1397,15 +1397,15 @@ function reportError(e) {
1397
1397
  process.stderr.write(`${e.stack}
1398
1398
  `);
1399
1399
  } else {
1400
- const message = e instanceof Error ? e.message : String(e);
1401
- process.stderr.write(`${paint("error", message, process.stderr)}
1400
+ const message2 = e instanceof Error ? e.message : String(e);
1401
+ process.stderr.write(`${paint("error", message2, process.stderr)}
1402
1402
  `);
1403
1403
  }
1404
1404
  return 1;
1405
1405
  }
1406
1406
 
1407
1407
  // src/version.ts
1408
- var VERSION = "0.9.2";
1408
+ var VERSION = "0.10.0";
1409
1409
 
1410
1410
  // src/core/block.ts
1411
1411
  import { createHash } from "crypto";
@@ -1473,8 +1473,8 @@ function isCurrentBlock(blockLines, freshHash) {
1473
1473
  // src/core/frontmatter.ts
1474
1474
  import { parse as parse2 } from "yaml";
1475
1475
  var FRONT_MATTER = /^---\r?\n(?:([\s\S]*?)\r?\n)?---(?:\r?\n|$)/;
1476
- function fail2(message) {
1477
- throw new Error(`front matter: ${message}`);
1476
+ function fail2(message2) {
1477
+ throw new Error(`front matter: ${message2}`);
1478
1478
  }
1479
1479
  function stripBom(source) {
1480
1480
  return source.startsWith("\uFEFF") ? source.slice(1) : source;
@@ -1706,6 +1706,46 @@ import { mkdir as mkdir2 } from "fs/promises";
1706
1706
  import { dirname as dirname4, join as join9 } from "path";
1707
1707
 
1708
1708
  // src/core/agents.ts
1709
+ var PINNED_BIN = "node_modules/@rness/cli/dist/bin/rness.js";
1710
+ function hookLine(rness, event) {
1711
+ const run3 = `f="$CLAUDE_PROJECT_DIR/${rness}/${PINNED_BIN}"; if [ -f "$f" ]; then node "$f" hook ${event};`;
1712
+ if (event === "post-tool-use") return `${run3} fi`;
1713
+ const missing = JSON.stringify({
1714
+ systemMessage: `rness: ${rness} is not installed, so the workspace context is not loaded. Clone the workspace, then install its dependencies in .rness.`
1715
+ });
1716
+ return `${run3} else echo '${missing}'; fi`;
1717
+ }
1718
+ function hooks(rness) {
1719
+ return [
1720
+ {
1721
+ path: ["hooks", "SessionStart"],
1722
+ contains: {
1723
+ hooks: [
1724
+ {
1725
+ type: "command",
1726
+ command: hookLine(rness, "session-start"),
1727
+ timeout: 10
1728
+ }
1729
+ ]
1730
+ },
1731
+ label: "the rness session-start hook"
1732
+ },
1733
+ {
1734
+ path: ["hooks", "PostToolUse"],
1735
+ contains: {
1736
+ matcher: "Edit|Write",
1737
+ hooks: [
1738
+ {
1739
+ type: "command",
1740
+ command: hookLine(rness, "post-tool-use"),
1741
+ timeout: 10
1742
+ }
1743
+ ]
1744
+ },
1745
+ label: "the rness post-tool-use hook"
1746
+ }
1747
+ ];
1748
+ }
1709
1749
  var TARGETS = {
1710
1750
  claude: {
1711
1751
  name: "claude",
@@ -1713,6 +1753,7 @@ var TARGETS = {
1713
1753
  files: [
1714
1754
  {
1715
1755
  file: ".claude/settings.json",
1756
+ at: "clones",
1716
1757
  guarantees: [
1717
1758
  // Read the context repository from a repository without a prompt.
1718
1759
  // Resolved against the project directory, and applied once Claude
@@ -1720,11 +1761,13 @@ var TARGETS = {
1720
1761
  {
1721
1762
  path: ["permissions", "additionalDirectories"],
1722
1763
  contains: "../../.rness"
1723
- }
1764
+ },
1765
+ ...hooks("../../.rness")
1724
1766
  ]
1725
1767
  },
1726
1768
  {
1727
1769
  file: ".mcp.json",
1770
+ at: "clones",
1728
1771
  guarantees: [
1729
1772
  // The pinned copy, by a path relative to the repository — the
1730
1773
  // directory Claude Code starts the server in (spec 0014 §6). Not
@@ -1742,6 +1785,13 @@ var TARGETS = {
1742
1785
  }
1743
1786
  }
1744
1787
  ]
1788
+ },
1789
+ // A session opened at the workspace root reads this file, one opened
1790
+ // in a clone does not (spec 0011 §1): the hooks only, `.rness` inside.
1791
+ {
1792
+ file: ".claude/settings.json",
1793
+ at: "root",
1794
+ guarantees: hooks(".rness")
1745
1795
  }
1746
1796
  ]
1747
1797
  }
@@ -1755,6 +1805,7 @@ function unsupportedMessage(name) {
1755
1805
  }
1756
1806
 
1757
1807
  // src/core/json-guarantees.ts
1808
+ import { isDeepStrictEqual } from "util";
1758
1809
  function isObject(value) {
1759
1810
  return value !== null && typeof value === "object" && !Array.isArray(value);
1760
1811
  }
@@ -1798,14 +1849,15 @@ function apply(root, g, create) {
1798
1849
  }
1799
1850
  if (value === void 0) {
1800
1851
  if (!create) return { holds: false, changed: false };
1801
- parent[last] = [g.contains];
1852
+ parent[last] = [structuredClone(g.contains)];
1802
1853
  return { holds: true, changed: true };
1803
1854
  }
1804
1855
  if (!Array.isArray(value))
1805
1856
  return { invalid: `${g.path.join(".")} is not a list` };
1806
- if (value.includes(g.contains)) return { holds: true, changed: false };
1857
+ if (value.some((entry) => isDeepStrictEqual(entry, g.contains)))
1858
+ return { holds: true, changed: false };
1807
1859
  if (!create) return { holds: false, changed: false };
1808
- value.push(g.contains);
1860
+ value.push(structuredClone(g.contains));
1809
1861
  return { holds: true, changed: true };
1810
1862
  }
1811
1863
  function styleOf(text) {
@@ -1849,21 +1901,33 @@ function missingGuarantees(existing, guarantees) {
1849
1901
  return missing;
1850
1902
  }
1851
1903
  function describeGuarantee(g) {
1852
- return "value" in g ? `${g.path.join(".")} is missing` : `${g.path.join(".")} lacks ${g.contains}`;
1904
+ if ("value" in g) return `${g.path.join(".")} is missing`;
1905
+ const what = g.label ?? (typeof g.contains === "string" ? g.contains : JSON.stringify(g.contains));
1906
+ return `${g.path.join(".")} lacks ${what}`;
1853
1907
  }
1854
1908
 
1855
1909
  // src/core/agent-targets.ts
1856
1910
  async function filesOf(root, manifest, targets) {
1857
1911
  const files = [];
1912
+ if (!await exists(join9(root, "org"))) return files;
1913
+ for (const target of targets)
1914
+ for (const owned of target.files)
1915
+ if (owned.at === "root")
1916
+ files.push({
1917
+ guarantees: owned.guarantees,
1918
+ file: join9(root, ...owned.file.split("/")),
1919
+ label: owned.file
1920
+ });
1858
1921
  for (const repo of Object.keys(manifest.repos)) {
1859
1922
  if (!await exists(join9(root, "org", repo))) continue;
1860
1923
  for (const target of targets)
1861
1924
  for (const owned of target.files)
1862
- files.push({
1863
- guarantees: owned.guarantees,
1864
- file: join9(root, "org", repo, ...owned.file.split("/")),
1865
- label: `org/${repo}/${owned.file}`
1866
- });
1925
+ if (owned.at === "clones")
1926
+ files.push({
1927
+ guarantees: owned.guarantees,
1928
+ file: join9(root, "org", repo, ...owned.file.split("/")),
1929
+ label: `org/${repo}/${owned.file}`
1930
+ });
1867
1931
  }
1868
1932
  return files;
1869
1933
  }
@@ -1876,7 +1940,9 @@ var BLOCK_FILES = ["AGENTS.md", "CLAUDE.md"];
1876
1940
  function writtenFiles(manifest) {
1877
1941
  return [
1878
1942
  ...BLOCK_FILES,
1879
- ...declared(manifest).flatMap((t) => t.files.map((f) => f.file))
1943
+ ...declared(manifest).flatMap(
1944
+ (t) => t.files.filter((f) => f.at === "clones").map((f) => f.file)
1945
+ )
1880
1946
  ];
1881
1947
  }
1882
1948
  async function agentTargets(root, manifest, opts) {
@@ -2064,8 +2130,8 @@ function sessionUi(p) {
2064
2130
  intro: (title) => {
2065
2131
  p.intro(badge(title));
2066
2132
  },
2067
- outro: (message, tone = "done") => {
2068
- p.outro(badge(message, tone));
2133
+ outro: (message2, tone = "done") => {
2134
+ p.outro(badge(message2, tone));
2069
2135
  },
2070
2136
  line: (verb, rest, sentence) => {
2071
2137
  say(verb, sentence ?? `${rest} ${verb}`);
@@ -3240,13 +3306,13 @@ function scaffoldMessage(subject, version) {
3240
3306
 
3241
3307
  ${SCAFFOLD_TRAILER}: ${version}`;
3242
3308
  }
3243
- async function commitUpgrade(rnessDir, message) {
3309
+ async function commitUpgrade(rnessDir, message2) {
3244
3310
  const add = await run(rnessDir, ["add", "-A"]);
3245
3311
  if (add.code !== 0) return { kind: "refused", output: add.stderr.trim() };
3246
3312
  const staged = await run(rnessDir, ["diff", "--cached", "--quiet"]);
3247
3313
  if (staged.code === 0 && !await mergeInProgress(rnessDir))
3248
3314
  return { kind: "nothing" };
3249
- const commit = await run(rnessDir, ["commit", "-q", "-m", message]);
3315
+ const commit = await run(rnessDir, ["commit", "-q", "-m", message2]);
3250
3316
  if (commit.code !== 0)
3251
3317
  return {
3252
3318
  kind: "refused",
@@ -3767,9 +3833,9 @@ async function cloneSelection(input) {
3767
3833
  manifest = result.manifest;
3768
3834
  ui.line("declared", `scope ${result.name} (org/${result.name})`);
3769
3835
  } catch (e) {
3770
- const message = firstLine4(e instanceof Error ? e.message : String(e));
3771
- ui.error(`${spec}: ${message}`);
3772
- failures.push({ spec, message });
3836
+ const message2 = firstLine4(e instanceof Error ? e.message : String(e));
3837
+ ui.error(`${spec}: ${message2}`);
3838
+ failures.push({ spec, message: message2 });
3773
3839
  }
3774
3840
  }
3775
3841
  return failures;
@@ -4262,36 +4328,89 @@ ${next2.map((l) => ` ${l}`).join("\n")}
4262
4328
  }
4263
4329
  }
4264
4330
 
4265
- // src/commands/logout.ts
4266
- async function logoutCommand() {
4267
- try {
4268
- const stored = await readAuth();
4269
- await clearAuth();
4270
- const undone = await removeGitSetup().catch(() => false);
4271
- if (stored === null && !undone) {
4272
- process.stdout.write("not logged in\n");
4273
- return 0;
4331
+ // src/core/blocks.ts
4332
+ import { join as join20 } from "path";
4333
+ var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
4334
+ async function checkBlocks({
4335
+ root,
4336
+ rnessDir,
4337
+ manifest,
4338
+ org
4339
+ }) {
4340
+ const checks = [];
4341
+ const note = (label, status2, message2 = null) => {
4342
+ checks.push({ label, status: status2, message: message2 });
4343
+ };
4344
+ const derive = () => ({
4345
+ problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
4346
+ warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
4347
+ checks
4348
+ });
4349
+ if (!await exists(join20(root, "org"))) return derive();
4350
+ const targets = [
4351
+ { scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
4352
+ { scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
4353
+ ];
4354
+ for (const [name, entry] of Object.entries(manifest.scopes)) {
4355
+ const segments = entry.path.split("/");
4356
+ targets.push({
4357
+ scope: name,
4358
+ dir: join20(root, ...segments),
4359
+ depth: segments.length,
4360
+ file: "AGENTS.md",
4361
+ label: `${entry.path}/AGENTS.md`
4362
+ });
4363
+ }
4364
+ for (const t of targets) {
4365
+ if (!await exists(t.dir)) {
4366
+ note(
4367
+ t.label,
4368
+ "missing-dir",
4369
+ `${t.label}: directory not present, block not checked`
4370
+ );
4371
+ continue;
4274
4372
  }
4275
- if (stored !== null)
4276
- process.stdout.write(
4277
- `${status("logged out", `${stored.login} (github.com)`)}
4278
- `
4373
+ const text = await readOrNull(join20(t.dir, t.file));
4374
+ const lines = text === null ? [] : text.split(/\r?\n/);
4375
+ const found = findBlock(lines);
4376
+ if (found.kind === "none") {
4377
+ note(
4378
+ t.label,
4379
+ "no-block",
4380
+ `${t.label}: no rness block yet (run ${rnessCommand()} sync)`
4279
4381
  );
4280
- if (undone)
4281
- process.stdout.write(
4282
- `${status("updated", "git: rness no longer answers for https://github.com")}
4283
- `
4382
+ continue;
4383
+ }
4384
+ if (found.kind === "error") {
4385
+ note(t.label, "malformed", `${t.label}: ${found.message}`);
4386
+ continue;
4387
+ }
4388
+ const context2 = await assembleContext({
4389
+ rnessDir,
4390
+ manifest,
4391
+ scope: t.scope
4392
+ });
4393
+ const fresh2 = renderBlock({
4394
+ scope: t.scope,
4395
+ org,
4396
+ depth: t.depth,
4397
+ context: context2
4398
+ });
4399
+ const pointerLeft = t.file === "CLAUDE.md" && lines.some((l) => l.trim() === "@AGENTS.md");
4400
+ if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash) && !pointerLeft)
4401
+ note(t.label, "current");
4402
+ else
4403
+ note(
4404
+ t.label,
4405
+ "stale",
4406
+ `${t.label}: stale rness block (run ${rnessCommand()} sync)`
4284
4407
  );
4285
- process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
4286
- `);
4287
- return 0;
4288
- } catch (e) {
4289
- return reportError(e);
4290
4408
  }
4409
+ return derive();
4291
4410
  }
4292
4411
 
4293
4412
  // src/core/contract.ts
4294
- import { join as join20 } from "path";
4413
+ import { join as join21 } from "path";
4295
4414
  var STATUSES = {
4296
4415
  adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
4297
4416
  specs: [
@@ -4310,7 +4429,7 @@ async function checkContract(rnessDir) {
4310
4429
  const problems = [];
4311
4430
  for (const name of COLLECTIONS) {
4312
4431
  const allowed = STATUSES[name];
4313
- const items = await collectMarkdown(join20(rnessDir, name));
4432
+ const items = await collectMarkdown(join21(rnessDir, name));
4314
4433
  for (const item of items) {
4315
4434
  const where = `${name}/${item.rel}`;
4316
4435
  if (SKIP.has(where)) continue;
@@ -4346,11 +4465,39 @@ async function checkContract(rnessDir) {
4346
4465
  return problems;
4347
4466
  }
4348
4467
 
4468
+ // src/commands/logout.ts
4469
+ async function logoutCommand() {
4470
+ try {
4471
+ const stored = await readAuth();
4472
+ await clearAuth();
4473
+ const undone = await removeGitSetup().catch(() => false);
4474
+ if (stored === null && !undone) {
4475
+ process.stdout.write("not logged in\n");
4476
+ return 0;
4477
+ }
4478
+ if (stored !== null)
4479
+ process.stdout.write(
4480
+ `${status("logged out", `${stored.login} (github.com)`)}
4481
+ `
4482
+ );
4483
+ if (undone)
4484
+ process.stdout.write(
4485
+ `${status("updated", "git: rness no longer answers for https://github.com")}
4486
+ `
4487
+ );
4488
+ process.stdout.write(`revoke rness on GitHub: ${revokeUrl()}
4489
+ `);
4490
+ return 0;
4491
+ } catch (e) {
4492
+ return reportError(e);
4493
+ }
4494
+ }
4495
+
4349
4496
  // src/commands/upgrade.ts
4350
4497
  import { execFile as execFile6 } from "child_process";
4351
4498
  import { mkdtemp as mkdtemp3, readFile as readFile9, rm as rm7 } from "fs/promises";
4352
4499
  import { tmpdir as tmpdir3 } from "os";
4353
- import { basename as basename5, join as join21, relative as relative5 } from "path";
4500
+ import { basename as basename5, join as join22, relative as relative5 } from "path";
4354
4501
  import { promisify as promisify6 } from "util";
4355
4502
  var execFileP6 = promisify6(execFile6);
4356
4503
  var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
@@ -4370,10 +4517,10 @@ async function latestVersion() {
4370
4517
  async function scaffoldFor(version, rnessDir) {
4371
4518
  if (version === VERSION) return { dir: scaffoldDir() };
4372
4519
  const installed = await readPinnedCli(rnessDir);
4373
- const inside = installed === null ? null : join21(installed.dir, "scaffold");
4374
- if (installed?.version === version && inside !== null && await exists(join21(inside, "package.json")))
4375
- return { dir: inside };
4376
- const scratch = await mkdtemp3(join21(tmpdir3(), "rness-pack-"));
4520
+ const inside2 = installed === null ? null : join22(installed.dir, "scaffold");
4521
+ if (installed?.version === version && inside2 !== null && await exists(join22(inside2, "package.json")))
4522
+ return { dir: inside2 };
4523
+ const scratch = await mkdtemp3(join22(tmpdir3(), "rness-pack-"));
4377
4524
  const cleanup = () => rm7(scratch, { recursive: true, force: true });
4378
4525
  try {
4379
4526
  const { stdout } = await execFileP6(
@@ -4388,8 +4535,8 @@ async function scaffoldFor(version, rnessDir) {
4388
4535
  { timeout: 6e4 }
4389
4536
  );
4390
4537
  const tarball = stdout.trim().split("\n").at(-1) ?? "";
4391
- await execFileP6("tar", ["-xzf", join21(scratch, tarball), "-C", scratch]);
4392
- return { dir: join21(scratch, "package", "scaffold"), cleanup };
4538
+ await execFileP6("tar", ["-xzf", join22(scratch, tarball), "-C", scratch]);
4539
+ return { dir: join22(scratch, "package", "scaffold"), cleanup };
4393
4540
  } catch (e) {
4394
4541
  await cleanup();
4395
4542
  throw new Error(
@@ -4401,7 +4548,7 @@ async function scaffoldFor(version, rnessDir) {
4401
4548
  async function declaredManager(rnessDir, pm) {
4402
4549
  try {
4403
4550
  const pkg = JSON.parse(
4404
- await readFile9(join21(rnessDir, "package.json"), "utf8")
4551
+ await readFile9(join22(rnessDir, "package.json"), "utf8")
4405
4552
  );
4406
4553
  if (typeof pkg.packageManager === "string") return pkg.packageManager;
4407
4554
  } catch {
@@ -4412,7 +4559,7 @@ async function clonesToCommit(root, cwd, manifest) {
4412
4559
  const files = writtenFiles(manifest);
4413
4560
  const clones = [];
4414
4561
  for (const repo of Object.keys(manifest.repos)) {
4415
- const dir = join21(root, "org", repo);
4562
+ const dir = join22(root, "org", repo);
4416
4563
  if (!await exists(dir)) continue;
4417
4564
  try {
4418
4565
  const paths = await changedPaths(dir, files);
@@ -4425,14 +4572,14 @@ async function clonesToCommit(root, cwd, manifest) {
4425
4572
  }
4426
4573
  function finish(ui, cwd, rnessDir, label, target, ending) {
4427
4574
  const rel = relative5(cwd, rnessDir) || ".";
4428
- const message = `chore: rness ${target}`;
4575
+ const message2 = `chore: rness ${target}`;
4429
4576
  const next = [];
4430
4577
  if (ending.commit === "committed") next.push(`git -C ${rel} push`);
4431
4578
  if (ending.commit === "refused")
4432
- next.push(`git -C ${rel} commit -m "${message}"`);
4579
+ next.push(`git -C ${rel} commit -m "${message2}"`);
4433
4580
  for (const c of ending.clones)
4434
4581
  next.push(
4435
- `git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message}"`
4582
+ `git -C ${c.dir} add ${c.paths.join(" ")} && git -C ${c.dir} commit -m "${message2}"`
4436
4583
  );
4437
4584
  if (ending.commit !== "nothing")
4438
4585
  next.push("# teammates: git pull \u2014 the next rness command installs it");
@@ -4605,7 +4752,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
4605
4752
  await source.cleanup?.();
4606
4753
  }
4607
4754
  }
4608
- const packageFile = join21(ws.rnessDir, "package.json");
4755
+ const packageFile = join22(ws.rnessDir, "package.json");
4609
4756
  const restore = /* @__PURE__ */ new Map([
4610
4757
  [packageFile, await readFile9(packageFile, "utf8")]
4611
4758
  ]);
@@ -4621,8 +4768,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
4621
4768
  () => ["installed", `dependencies with ${pm}`]
4622
4769
  );
4623
4770
  } catch (e) {
4624
- const message2 = e instanceof Error ? e.message : String(e);
4625
- ui.error(message2);
4771
+ const message3 = e instanceof Error ? e.message : String(e);
4772
+ ui.error(message3);
4626
4773
  if (!merged) {
4627
4774
  for (const [file, text] of restore) await writeFileAtomic(file, text);
4628
4775
  ui.hint(
@@ -4635,7 +4782,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
4635
4782
  `the scaffold merge is still staged; undo it with: git -C ${relative5(cwd, ws.rnessDir) || "."} merge --abort`,
4636
4783
  "stderr"
4637
4784
  );
4638
- if (/minimum.?release.?age|NO_MATURE/i.test(message2))
4785
+ if (/minimum.?release.?age|NO_MATURE/i.test(message3))
4639
4786
  ui.hint(
4640
4787
  "pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
4641
4788
  "stderr"
@@ -4654,13 +4801,13 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
4654
4801
  ui.gitIsSilent = true;
4655
4802
  const code = await syncBlocks(ui, ws.root, basename5(ws.root));
4656
4803
  if (code !== 0) return code;
4657
- const message = `chore: rness ${target}`;
4658
- const committed = await commitUpgrade(ws.rnessDir, message);
4804
+ const message2 = `chore: rness ${target}`;
4805
+ const committed = await commitUpgrade(ws.rnessDir, message2);
4659
4806
  if (committed.kind === "committed")
4660
4807
  ui.line(
4661
4808
  "committed",
4662
- `${label} \u2014 ${message}`,
4663
- `Committed ${label}: ${message}`
4809
+ `${label} \u2014 ${message2}`,
4810
+ `Committed ${label}: ${message2}`
4664
4811
  );
4665
4812
  if (committed.kind === "refused") {
4666
4813
  ui.error(
@@ -4682,85 +4829,28 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
4682
4829
  }
4683
4830
  }
4684
4831
 
4685
- // src/core/blocks.ts
4686
- import { join as join22 } from "path";
4687
- var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
4688
- async function checkBlocks({
4689
- root,
4690
- rnessDir,
4691
- manifest,
4692
- org
4693
- }) {
4694
- const checks = [];
4695
- const note = (label, status2, message = null) => {
4696
- checks.push({ label, status: status2, message });
4697
- };
4698
- const derive = () => ({
4699
- problems: checks.filter((c) => PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
4700
- warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
4701
- checks
4832
+ // src/core/check-workspace.ts
4833
+ async function checkWorkspace(ws, manifest) {
4834
+ for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
4835
+ const problems = await checkContract(ws.rnessDir);
4836
+ const blocks = await checkBlocks({
4837
+ root: ws.root,
4838
+ rnessDir: ws.rnessDir,
4839
+ manifest,
4840
+ org: workspaceName(manifest, ws.root)
4702
4841
  });
4703
- if (!await exists(join22(root, "org"))) return derive();
4704
- const targets = [
4705
- { scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
4706
- { scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
4707
- ];
4708
- for (const [name, entry] of Object.entries(manifest.scopes)) {
4709
- const segments = entry.path.split("/");
4710
- targets.push({
4711
- scope: name,
4712
- dir: join22(root, ...segments),
4713
- depth: segments.length,
4714
- file: "AGENTS.md",
4715
- label: `${entry.path}/AGENTS.md`
4716
- });
4717
- }
4718
- for (const t of targets) {
4719
- if (!await exists(t.dir)) {
4720
- note(
4721
- t.label,
4722
- "missing-dir",
4723
- `${t.label}: directory not present, block not checked`
4724
- );
4725
- continue;
4726
- }
4727
- const text = await readOrNull(join22(t.dir, t.file));
4728
- const lines = text === null ? [] : text.split(/\r?\n/);
4729
- const found = findBlock(lines);
4730
- if (found.kind === "none") {
4731
- note(
4732
- t.label,
4733
- "no-block",
4734
- `${t.label}: no rness block yet (run ${rnessCommand()} sync)`
4735
- );
4736
- continue;
4737
- }
4738
- if (found.kind === "error") {
4739
- note(t.label, "malformed", `${t.label}: ${found.message}`);
4740
- continue;
4741
- }
4742
- const context2 = await assembleContext({
4743
- rnessDir,
4744
- manifest,
4745
- scope: t.scope
4746
- });
4747
- const fresh2 = renderBlock({
4748
- scope: t.scope,
4749
- org,
4750
- depth: t.depth,
4751
- context: context2
4752
- });
4753
- const pointerLeft = t.file === "CLAUDE.md" && lines.some((l) => l.trim() === "@AGENTS.md");
4754
- if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash) && !pointerLeft)
4755
- note(t.label, "current");
4756
- else
4757
- note(
4758
- t.label,
4759
- "stale",
4760
- `${t.label}: stale rness block (run ${rnessCommand()} sync)`
4842
+ problems.push(...blocks.problems);
4843
+ for (const name of unsupportedAgents(manifest.agents ?? []))
4844
+ problems.push(unsupportedMessage(name));
4845
+ const targets = await agentTargets(ws.root, manifest, { check: true });
4846
+ for (const o of targets)
4847
+ if (o.status === "stale")
4848
+ problems.push(
4849
+ `${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
4761
4850
  );
4762
- }
4763
- return derive();
4851
+ else if (o.status === "invalid")
4852
+ problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
4853
+ return { problems, blocks, targets };
4764
4854
  }
4765
4855
 
4766
4856
  // src/commands/validate.ts
@@ -4769,26 +4859,8 @@ async function validateCommand(opts, deps = {}) {
4769
4859
  try {
4770
4860
  const ws = await findWorkspace(cwd);
4771
4861
  const manifest = await loadManifest(ws.rnessDir);
4772
- for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
4773
4862
  const org = workspaceName(manifest, ws.root);
4774
- const problems = await checkContract(ws.rnessDir);
4775
- const blocks = await checkBlocks({
4776
- root: ws.root,
4777
- rnessDir: ws.rnessDir,
4778
- manifest,
4779
- org
4780
- });
4781
- problems.push(...blocks.problems);
4782
- for (const name of unsupportedAgents(manifest.agents ?? []))
4783
- problems.push(unsupportedMessage(name));
4784
- const targets = await agentTargets(ws.root, manifest, { check: true });
4785
- for (const o of targets)
4786
- if (o.status === "stale")
4787
- problems.push(
4788
- `${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
4789
- );
4790
- else if (o.status === "invalid")
4791
- problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
4863
+ const { problems, blocks, targets } = await checkWorkspace(ws, manifest);
4792
4864
  const scaffoldWarnings = [];
4793
4865
  if (await isRepository2(ws.rnessDir) && !await isShallow(ws.rnessDir)) {
4794
4866
  const pin = await readPin(ws.rnessDir);
@@ -4865,134 +4937,9 @@ password=${resolved.token}
4865
4937
  return 0;
4866
4938
  }
4867
4939
 
4868
- // src/commands/mcp.ts
4869
- import { createInterface } from "readline";
4870
-
4871
- // src/mcp/protocol.ts
4872
- var MODERN_VERSIONS = ["2026-07-28"];
4873
- var LEGACY_VERSIONS = [
4874
- "2025-11-25",
4875
- "2025-06-18",
4876
- "2025-03-26",
4877
- "2024-11-05"
4878
- ];
4879
- var SUPPORTED = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
4880
- var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
4881
- var CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
4882
- var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
4883
- function isObject2(value) {
4884
- return value !== null && typeof value === "object" && !Array.isArray(value);
4885
- }
4886
- function errorLine(id, code, message, data) {
4887
- return JSON.stringify({
4888
- jsonrpc: "2.0",
4889
- id,
4890
- error: data === void 0 ? { code, message } : { code, message, data }
4891
- });
4892
- }
4893
- function createProtocol(opts) {
4894
- const serverInfo = { name: opts.name, version: opts.version };
4895
- return {
4896
- async handle(line2) {
4897
- let message;
4898
- try {
4899
- message = JSON.parse(line2);
4900
- } catch {
4901
- return errorLine(null, -32700, "Parse error");
4902
- }
4903
- const rawId = isObject2(message) ? message["id"] : void 0;
4904
- const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
4905
- if (!isObject2(message) || message["jsonrpc"] !== "2.0" || typeof message["method"] !== "string")
4906
- return errorLine(id, -32600, "Invalid Request");
4907
- if (!("id" in message)) return null;
4908
- if (id === null) return errorLine(null, -32600, "Invalid Request");
4909
- const method = message["method"];
4910
- const params = isObject2(message["params"]) ? message["params"] : {};
4911
- const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
4912
- const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
4913
- if (modern) {
4914
- const requested = meta[VERSION_KEY];
4915
- if (!MODERN_VERSIONS.includes(requested))
4916
- return errorLine(id, -32022, "Unsupported protocol version", {
4917
- supported: SUPPORTED,
4918
- requested
4919
- });
4920
- if (!isObject2(meta[CAPABILITIES_KEY]))
4921
- return errorLine(
4922
- id,
4923
- -32602,
4924
- `Invalid params: ${CAPABILITIES_KEY} is required`
4925
- );
4926
- }
4927
- const answer = (result) => JSON.stringify({
4928
- jsonrpc: "2.0",
4929
- id,
4930
- result: modern ? {
4931
- resultType: "complete",
4932
- ...result,
4933
- _meta: { [SERVER_INFO_KEY]: serverInfo }
4934
- } : result
4935
- });
4936
- switch (method) {
4937
- case "server/discover":
4938
- return answer({
4939
- supportedVersions: SUPPORTED,
4940
- capabilities: { tools: {} }
4941
- });
4942
- case "initialize": {
4943
- const asked = params["protocolVersion"];
4944
- const protocolVersion = typeof asked === "string" && LEGACY_VERSIONS.includes(asked) ? asked : LEGACY_VERSIONS[0];
4945
- return JSON.stringify({
4946
- jsonrpc: "2.0",
4947
- id,
4948
- result: {
4949
- protocolVersion,
4950
- capabilities: { tools: {} },
4951
- serverInfo
4952
- }
4953
- });
4954
- }
4955
- case "ping":
4956
- return answer({});
4957
- case "tools/list":
4958
- return answer({
4959
- tools: opts.tools.map((t) => ({
4960
- name: t.name,
4961
- title: t.title,
4962
- description: t.description,
4963
- inputSchema: t.inputSchema
4964
- }))
4965
- });
4966
- case "tools/call": {
4967
- const name = params["name"];
4968
- const tool = opts.tools.find((t) => t.name === name);
4969
- if (tool === void 0)
4970
- return errorLine(id, -32602, `Unknown tool: ${String(name)}`);
4971
- const args = isObject2(params["arguments"]) ? params["arguments"] : {};
4972
- try {
4973
- const r = await tool.call(args);
4974
- return answer({
4975
- content: [{ type: "text", text: r.text }],
4976
- isError: r.isError === true
4977
- });
4978
- } catch (e) {
4979
- return answer({
4980
- content: [
4981
- {
4982
- type: "text",
4983
- text: `${tool.name} failed: ${e instanceof Error ? e.message : String(e)}`
4984
- }
4985
- ],
4986
- isError: true
4987
- });
4988
- }
4989
- }
4990
- default:
4991
- return errorLine(id, -32601, "Method not found");
4992
- }
4993
- }
4994
- };
4995
- }
4940
+ // src/commands/hook.ts
4941
+ import { realpath as realpath4 } from "fs/promises";
4942
+ import { isAbsolute as isAbsolute2, posix as posix5, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
4996
4943
 
4997
4944
  // src/mcp/tools.ts
4998
4945
  import { readFile as readFile10, realpath as realpath3, stat as stat4 } from "fs/promises";
@@ -5073,21 +5020,31 @@ async function context(cwd, args) {
5073
5020
  manifest,
5074
5021
  relative6(ws.root, cwd).split(sep4).join(posix4.sep)
5075
5022
  );
5023
+ const summary = await scopeSummary(ws, manifest, scope);
5024
+ return {
5025
+ text: [
5026
+ ...summary.lines,
5027
+ "Read one with rness_read; search with rness_search."
5028
+ ].join("\n")
5029
+ };
5030
+ }
5031
+ async function scopeSummary(ws, manifest, scope) {
5076
5032
  const entry = scope === null ? void 0 : manifest.scopes[scope];
5077
5033
  const where = scope === null || entry === void 0 ? "global scope" : `scope ${scope} (${entry.path}${entry.extends.length > 0 ? `, extends ${entry.extends.join(", ")}` : ""})`;
5078
5034
  const ctx = await assembleContext({ rnessDir: ws.rnessDir, manifest, scope });
5079
5035
  const index = new Map(
5080
5036
  (await documents(ws.rnessDir)).map((d) => [d.path, d])
5081
5037
  );
5082
- const out = [`Workspace ${workspaceName(manifest, ws.root)} \xB7 ${where}`];
5038
+ const lines = [`Workspace ${workspaceName(manifest, ws.root)} \xB7 ${where}`];
5039
+ const counts = {};
5083
5040
  for (const collection of ctx.collections) {
5084
5041
  const docs = collection.files.map((f) => index.get(`${collection.name}/${f.rel}`)).filter((d) => d !== void 0);
5085
5042
  if (docs.length === 0) continue;
5086
- out.push(`${LABELS[collection.name]} (${docs.length}):`);
5087
- for (const d of docs) out.push(line(d));
5043
+ counts[collection.name] = docs.length;
5044
+ lines.push(`${LABELS[collection.name]} (${docs.length}):`);
5045
+ for (const d of docs) lines.push(line(d));
5088
5046
  }
5089
- out.push("Read one with rness_read; search with rness_search.");
5090
- return { text: out.join("\n") };
5047
+ return { where, counts, lines };
5091
5048
  }
5092
5049
  async function list(cwd, args) {
5093
5050
  const { ws } = await workspaceAt(cwd);
@@ -5248,6 +5205,285 @@ function mcpTools(cwd) {
5248
5205
  ];
5249
5206
  }
5250
5207
 
5208
+ // src/commands/hook.ts
5209
+ var NOUNS = {
5210
+ standards: ["standard", "standards"],
5211
+ adr: ["decision", "decisions"],
5212
+ specs: ["specification", "specifications"],
5213
+ plans: ["plan", "plans"],
5214
+ skills: ["skill", "skills"]
5215
+ };
5216
+ var SHOWN_PROBLEMS = 3;
5217
+ async function readInput(input) {
5218
+ if (input.isTTY === true) return {};
5219
+ let text = "";
5220
+ for await (const chunk of input) text += String(chunk);
5221
+ try {
5222
+ const data = JSON.parse(text);
5223
+ return data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
5224
+ } catch {
5225
+ return {};
5226
+ }
5227
+ }
5228
+ var message = (e) => e instanceof Error ? e.message : String(e);
5229
+ function cwdOf(input, env) {
5230
+ const cwd = input["cwd"];
5231
+ if (typeof cwd === "string" && cwd !== "") return cwd;
5232
+ return env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
5233
+ }
5234
+ var toPosix2 = (p) => p.split(sep5).join(posix5.sep);
5235
+ function count(n, [one, many]) {
5236
+ return `${n} ${n === 1 ? one : many}`;
5237
+ }
5238
+ async function safetyNet(ws, manifest) {
5239
+ const notes = [];
5240
+ const drift = await pinDrift(ws.rnessDir);
5241
+ if (drift !== null)
5242
+ notes.push(
5243
+ `.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${await workspacePackageManager(ws.rnessDir)} install in .rness`
5244
+ );
5245
+ try {
5246
+ const { problems } = await checkWorkspace(ws, manifest);
5247
+ if (problems.length > 0)
5248
+ notes.push(
5249
+ `${count(problems.length, ["problem", "problems"])} in the workspace context \u2014 run rness validate`,
5250
+ ...problems.slice(0, SHOWN_PROBLEMS).map((p) => ` ${p}`)
5251
+ );
5252
+ } catch (e) {
5253
+ notes.push(message(e));
5254
+ }
5255
+ return notes;
5256
+ }
5257
+ async function sessionStart(input, env) {
5258
+ const cwd = cwdOf(input, env);
5259
+ let banner2 = null;
5260
+ let context2 = [];
5261
+ let notes;
5262
+ try {
5263
+ const ws = await findWorkspace(cwd);
5264
+ const manifest = await loadManifest(ws.rnessDir);
5265
+ const scope = resolveScope(manifest, toPosix2(relative7(ws.root, cwd)));
5266
+ const summary = await scopeSummary(ws, manifest, scope);
5267
+ const counts = COLLECTIONS.flatMap((c) => {
5268
+ const n = summary.counts[c];
5269
+ return n === void 0 ? [] : [count(n, NOUNS[c])];
5270
+ });
5271
+ banner2 = `rness ${VERSION} \xB7 ${workspaceName(manifest, ws.root)} \xB7 ${scope === null ? "global scope" : `scope ${scope}`} \u2014 ${counts.length === 0 ? "nothing applies yet" : counts.join(", ")}`;
5272
+ const at = toPosix2(relative7(cwd, ws.rnessDir)) || ".rness";
5273
+ context2 = [
5274
+ ...summary.lines,
5275
+ `These documents are in ${at}: read one there, or with rness_read when the rness MCP server is connected.`
5276
+ ];
5277
+ notes = await safetyNet(ws, manifest);
5278
+ } catch (e) {
5279
+ notes = [message(e)];
5280
+ }
5281
+ const warnings = notes.map((n) => `rness: ${n}`);
5282
+ const shown = [
5283
+ ...banner2 !== null && input["source"] !== "compact" ? [banner2] : [],
5284
+ ...warnings
5285
+ ];
5286
+ return {
5287
+ ...shown.length > 0 ? { systemMessage: shown.join("\n") } : {},
5288
+ hookSpecificOutput: {
5289
+ hookEventName: "SessionStart",
5290
+ additionalContext: [...context2, ...warnings].join("\n")
5291
+ }
5292
+ };
5293
+ }
5294
+ function inside(from, to) {
5295
+ const rel = relative7(from, to);
5296
+ return rel === "" || rel.startsWith("..") || isAbsolute2(rel) ? null : toPosix2(rel);
5297
+ }
5298
+ async function postToolUse(input, io) {
5299
+ const toolInput = input["tool_input"];
5300
+ const path = toolInput !== null && typeof toolInput === "object" ? toolInput["file_path"] : void 0;
5301
+ if (typeof path !== "string" || path === "") return 0;
5302
+ const cwd = cwdOf(input, io.env);
5303
+ let ws;
5304
+ try {
5305
+ ws = await findWorkspace(cwd);
5306
+ } catch {
5307
+ return 0;
5308
+ }
5309
+ const file = resolve7(cwd, path);
5310
+ const rel = inside(ws.rnessDir, file) ?? inside(
5311
+ await realpath4(ws.rnessDir).catch(() => ws.rnessDir),
5312
+ await realpath4(file).catch(() => file)
5313
+ );
5314
+ if (rel === null) return 0;
5315
+ const first = rel.split("/")[0] ?? "";
5316
+ let problems = [];
5317
+ if (rel === "rness.json") {
5318
+ try {
5319
+ const manifest = await loadManifest(ws.rnessDir);
5320
+ for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
5321
+ } catch (e) {
5322
+ problems = [message(e)];
5323
+ }
5324
+ } else if (COLLECTIONS.includes(first) && rel.endsWith(".md"))
5325
+ problems = (await checkContract(ws.rnessDir)).filter(
5326
+ (p) => p.startsWith(`${rel}:`)
5327
+ );
5328
+ if (problems.length === 0) return 0;
5329
+ io.error.write(
5330
+ [
5331
+ ...problems.map((p) => `rness: ${p}`),
5332
+ `Fix it in ${rel}; rness validate checks the whole workspace.`,
5333
+ ""
5334
+ ].join("\n")
5335
+ );
5336
+ return 2;
5337
+ }
5338
+ async function hookCommand(event, io = {
5339
+ input: process.stdin,
5340
+ output: process.stdout,
5341
+ error: process.stderr,
5342
+ env: process.env
5343
+ }) {
5344
+ if (event !== "session-start" && event !== "post-tool-use") {
5345
+ io.error.write(
5346
+ `unknown hook event ${JSON.stringify(event)} (known: session-start, post-tool-use)
5347
+ `
5348
+ );
5349
+ return 1;
5350
+ }
5351
+ const input = await readInput(io.input);
5352
+ if (event === "post-tool-use") return postToolUse(input, io);
5353
+ io.output.write(`${JSON.stringify(await sessionStart(input, io.env))}
5354
+ `);
5355
+ return 0;
5356
+ }
5357
+
5358
+ // src/commands/mcp.ts
5359
+ import { createInterface } from "readline";
5360
+
5361
+ // src/mcp/protocol.ts
5362
+ var MODERN_VERSIONS = ["2026-07-28"];
5363
+ var LEGACY_VERSIONS = [
5364
+ "2025-11-25",
5365
+ "2025-06-18",
5366
+ "2025-03-26",
5367
+ "2024-11-05"
5368
+ ];
5369
+ var SUPPORTED = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
5370
+ var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
5371
+ var CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
5372
+ var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
5373
+ function isObject2(value) {
5374
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5375
+ }
5376
+ function errorLine(id, code, message2, data) {
5377
+ return JSON.stringify({
5378
+ jsonrpc: "2.0",
5379
+ id,
5380
+ error: data === void 0 ? { code, message: message2 } : { code, message: message2, data }
5381
+ });
5382
+ }
5383
+ function createProtocol(opts) {
5384
+ const serverInfo = { name: opts.name, version: opts.version };
5385
+ return {
5386
+ async handle(line2) {
5387
+ let message2;
5388
+ try {
5389
+ message2 = JSON.parse(line2);
5390
+ } catch {
5391
+ return errorLine(null, -32700, "Parse error");
5392
+ }
5393
+ const rawId = isObject2(message2) ? message2["id"] : void 0;
5394
+ const id = typeof rawId === "string" || typeof rawId === "number" ? rawId : null;
5395
+ if (!isObject2(message2) || message2["jsonrpc"] !== "2.0" || typeof message2["method"] !== "string")
5396
+ return errorLine(id, -32600, "Invalid Request");
5397
+ if (!("id" in message2)) return null;
5398
+ if (id === null) return errorLine(null, -32600, "Invalid Request");
5399
+ const method = message2["method"];
5400
+ const params = isObject2(message2["params"]) ? message2["params"] : {};
5401
+ const meta = isObject2(params["_meta"]) ? params["_meta"] : null;
5402
+ const modern = meta !== null && typeof meta[VERSION_KEY] === "string";
5403
+ if (modern) {
5404
+ const requested = meta[VERSION_KEY];
5405
+ if (!MODERN_VERSIONS.includes(requested))
5406
+ return errorLine(id, -32022, "Unsupported protocol version", {
5407
+ supported: SUPPORTED,
5408
+ requested
5409
+ });
5410
+ if (!isObject2(meta[CAPABILITIES_KEY]))
5411
+ return errorLine(
5412
+ id,
5413
+ -32602,
5414
+ `Invalid params: ${CAPABILITIES_KEY} is required`
5415
+ );
5416
+ }
5417
+ const answer = (result) => JSON.stringify({
5418
+ jsonrpc: "2.0",
5419
+ id,
5420
+ result: modern ? {
5421
+ resultType: "complete",
5422
+ ...result,
5423
+ _meta: { [SERVER_INFO_KEY]: serverInfo }
5424
+ } : result
5425
+ });
5426
+ switch (method) {
5427
+ case "server/discover":
5428
+ return answer({
5429
+ supportedVersions: SUPPORTED,
5430
+ capabilities: { tools: {} }
5431
+ });
5432
+ case "initialize": {
5433
+ const asked = params["protocolVersion"];
5434
+ const protocolVersion = typeof asked === "string" && LEGACY_VERSIONS.includes(asked) ? asked : LEGACY_VERSIONS[0];
5435
+ return JSON.stringify({
5436
+ jsonrpc: "2.0",
5437
+ id,
5438
+ result: {
5439
+ protocolVersion,
5440
+ capabilities: { tools: {} },
5441
+ serverInfo
5442
+ }
5443
+ });
5444
+ }
5445
+ case "ping":
5446
+ return answer({});
5447
+ case "tools/list":
5448
+ return answer({
5449
+ tools: opts.tools.map((t) => ({
5450
+ name: t.name,
5451
+ title: t.title,
5452
+ description: t.description,
5453
+ inputSchema: t.inputSchema
5454
+ }))
5455
+ });
5456
+ case "tools/call": {
5457
+ const name = params["name"];
5458
+ const tool = opts.tools.find((t) => t.name === name);
5459
+ if (tool === void 0)
5460
+ return errorLine(id, -32602, `Unknown tool: ${String(name)}`);
5461
+ const args = isObject2(params["arguments"]) ? params["arguments"] : {};
5462
+ try {
5463
+ const r = await tool.call(args);
5464
+ return answer({
5465
+ content: [{ type: "text", text: r.text }],
5466
+ isError: r.isError === true
5467
+ });
5468
+ } catch (e) {
5469
+ return answer({
5470
+ content: [
5471
+ {
5472
+ type: "text",
5473
+ text: `${tool.name} failed: ${e instanceof Error ? e.message : String(e)}`
5474
+ }
5475
+ ],
5476
+ isError: true
5477
+ });
5478
+ }
5479
+ }
5480
+ default:
5481
+ return errorLine(id, -32601, "Method not found");
5482
+ }
5483
+ }
5484
+ };
5485
+ }
5486
+
5251
5487
  // src/commands/mcp.ts
5252
5488
  async function mcpCommand(opts, io = {
5253
5489
  input: process.stdin,
@@ -5337,6 +5573,9 @@ function buildProgram(state) {
5337
5573
  program.command("git-credential", { hidden: true }).argument("<operation>").action(async (operation) => {
5338
5574
  state.code = await gitCredentialCommand(operation);
5339
5575
  });
5576
+ program.command("hook", { hidden: true }).argument("<event>", "session-start or post-tool-use").action(async (event) => {
5577
+ state.code = await hookCommand(event);
5578
+ });
5340
5579
  program.command("upgrade").alias("update").description(
5341
5580
  "Move this workspace to another @rness/cli: pin it in .rness/package.json, install, sync"
5342
5581
  ).argument("[version]", "an exact version (default: the latest release)").option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (version, opts) => {
@@ -5414,7 +5653,8 @@ var OWN_COMMANDS = [
5414
5653
  "update",
5415
5654
  "login",
5416
5655
  "logout",
5417
- "git-credential"
5656
+ "git-credential",
5657
+ "hook"
5418
5658
  ];
5419
5659
  var CATCH_UP_DONE = "RNESS_CATCH_UP_DONE";
5420
5660
  async function catchUp(argv, deps = {}) {
@@ -5509,14 +5749,14 @@ export {
5509
5749
  copyScaffold,
5510
5750
  loginCommand,
5511
5751
  createCommand,
5512
- logoutCommand,
5752
+ checkBlocks,
5513
5753
  STATUSES,
5514
5754
  checkContract,
5755
+ logoutCommand,
5515
5756
  upgradeCommand,
5516
- checkBlocks,
5517
5757
  validateCommand,
5518
5758
  run2 as run,
5519
5759
  OWN_COMMANDS,
5520
5760
  catchUp
5521
5761
  };
5522
- //# sourceMappingURL=chunk-JKDF7ROY.js.map
5762
+ //# sourceMappingURL=chunk-R4BRAAC2.js.map