@rness/cli 0.14.0 → 0.15.1

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.
@@ -486,7 +486,7 @@ function parseRepoSpec(spec, org, host) {
486
486
  }
487
487
 
488
488
  // src/version.ts
489
- var VERSION = "0.14.0";
489
+ var VERSION = "0.15.1";
490
490
 
491
491
  // src/core/pm.ts
492
492
  import { execFile as execFile2 } from "child_process";
@@ -1070,15 +1070,25 @@ function statusOf(item) {
1070
1070
  const status2 = item.fields?.["status"];
1071
1071
  return typeof status2 === "string" && status2 !== "" ? status2 : null;
1072
1072
  }
1073
+ function sessionsOf(item) {
1074
+ const listed = item.fields?.["sessions"];
1075
+ if (!Array.isArray(listed)) return [];
1076
+ const ids = listed.filter(
1077
+ (s) => typeof s === "string" && s !== ""
1078
+ );
1079
+ return [...new Set(ids)];
1080
+ }
1073
1081
  function rowOf(dir, item) {
1074
1082
  const base = posix3.basename(item.rel, ".md");
1075
1083
  const date = /^(\d{4}-\d{2}-\d{2})-/.exec(base)?.[1] ?? null;
1076
1084
  const number = date === null ? documentNumber(item.rel) : null;
1085
+ const sessions = sessionsOf(item);
1077
1086
  return {
1078
1087
  id: date ?? number ?? base,
1079
1088
  title: documentTitle(item.title ?? base, number),
1080
1089
  status: statusOf(item),
1081
- path: `${dir}/${item.rel}`
1090
+ path: `${dir}/${item.rel}`,
1091
+ ...sessions.length > 0 ? { sessions } : {}
1082
1092
  };
1083
1093
  }
1084
1094
  function newestFirst(a, b) {
@@ -1399,11 +1409,120 @@ function desiredOf(tabs, org) {
1399
1409
  body: headerOf(row.path, org),
1400
1410
  status: statusOf2(row.status),
1401
1411
  type: tab.label,
1402
- statusField: fields2.get(tab.label)?.name ?? null
1412
+ statusField: fields2.get(tab.label)?.name ?? null,
1413
+ sessions: row.sessions?.join(", ") ?? null
1403
1414
  }))
1404
1415
  );
1405
1416
  }
1406
1417
 
1418
+ // src/pulse/plan.ts
1419
+ var differs = (item, w) => item.title !== w.title || item.status !== w.status || w.statusField !== null && item.collectionStatus !== w.status || item.type !== w.type || item.sessions !== w.sessions;
1420
+ var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
1421
+ var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
1422
+ var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
1423
+ var firstLine3 = (body) => body.split(/\r\n?|\n/, 1)[0] ?? "";
1424
+ function strays(have) {
1425
+ const byLine = /* @__PURE__ */ new Map();
1426
+ for (const item of have) {
1427
+ if (item.path !== null || item.issue === null || item.issue.body === null || !item.issue.labelled)
1428
+ continue;
1429
+ const line3 = firstLine3(item.issue.body);
1430
+ byLine.set(line3, [...byLine.get(line3) ?? [], item]);
1431
+ }
1432
+ for (const items of byLine.values()) items.sort(keptFirst);
1433
+ return byLine;
1434
+ }
1435
+ function unwantedPaths(want, have) {
1436
+ const wanted = new Set(want.map((w) => w.path));
1437
+ const paths = /* @__PURE__ */ new Set();
1438
+ for (const item of have)
1439
+ if (item.path !== null && !wanted.has(item.path)) paths.add(item.path);
1440
+ return [...paths];
1441
+ }
1442
+ function isGone(path, item, seen, opened) {
1443
+ return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
1444
+ }
1445
+ function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
1446
+ const byPath = /* @__PURE__ */ new Map();
1447
+ for (const item of have)
1448
+ if (item.path !== null)
1449
+ byPath.set(item.path, [...byPath.get(item.path) ?? [], item]);
1450
+ const kept = /* @__PURE__ */ new Map();
1451
+ const surplus = [];
1452
+ for (const [path, items] of byPath) {
1453
+ const [keep, ...rest] = [...items].sort(keptFirst);
1454
+ if (keep !== void 0) kept.set(path, keep);
1455
+ surplus.push(...rest.map(gone));
1456
+ }
1457
+ const left = strays(have);
1458
+ const steps = [];
1459
+ const wanted = /* @__PURE__ */ new Set();
1460
+ for (const w of want) {
1461
+ wanted.add(w.path);
1462
+ const item = kept.get(w.path) ?? left.get(w.body)?.[0];
1463
+ if (item === void 0) steps.push({ kind: "create", want: w });
1464
+ else if (item.issue === null)
1465
+ steps.push({ kind: "convert", id: item.id, want: w });
1466
+ else if (item.path === null || !item.issue.open || !item.issue.labelled || differs(item, w))
1467
+ steps.push({
1468
+ kind: "update",
1469
+ id: item.id,
1470
+ want: w,
1471
+ reopen: !item.issue.open
1472
+ });
1473
+ else steps.push({ kind: "unchanged", id: item.id });
1474
+ }
1475
+ for (const [path, item] of kept)
1476
+ if (!wanted.has(path))
1477
+ steps.push(
1478
+ isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
1479
+ );
1480
+ return [...steps, ...surplus];
1481
+ }
1482
+ function stillOpened(opened, have, seen, closed, created) {
1483
+ const items = /* @__PURE__ */ new Map();
1484
+ for (const item of have)
1485
+ if (item.issue !== null) items.set(item.issue.number, item.id);
1486
+ const next = /* @__PURE__ */ new Map();
1487
+ for (const [number, path] of opened) {
1488
+ const id = items.get(number);
1489
+ if (id !== void 0 && !closed.has(id) && !seen.has(path))
1490
+ next.set(number, path);
1491
+ }
1492
+ for (const [path, placed] of created) next.set(placed.number, path);
1493
+ return next;
1494
+ }
1495
+ function issuedAfter(steps, have, placed) {
1496
+ const byId = new Map(have.map((i) => [i.id, i]));
1497
+ const issued = /* @__PURE__ */ new Map();
1498
+ for (const step2 of steps) {
1499
+ if (step2.kind === "create" || step2.kind === "convert") {
1500
+ const p = placed.get(step2.want.path);
1501
+ if (p !== void 0) issued.set(step2.want.path, { ...p, body: null });
1502
+ } else if (step2.kind === "update" || step2.kind === "unchanged") {
1503
+ const item = byId.get(step2.id);
1504
+ const path = step2.kind === "update" ? step2.want.path : item?.path;
1505
+ if (item === void 0 || path == null || item.issue === null) continue;
1506
+ issued.set(path, {
1507
+ id: item.id,
1508
+ number: item.issue.number,
1509
+ body: item.issue.body
1510
+ });
1511
+ }
1512
+ }
1513
+ return issued;
1514
+ }
1515
+ function planBodies(bodies, issued) {
1516
+ const steps = [];
1517
+ for (const [path, body] of bodies) {
1518
+ const i = issued.get(path);
1519
+ if (i === void 0) continue;
1520
+ if (i.body === null || digestOf(i.body) !== digestOf(body))
1521
+ steps.push({ kind: "body", id: i.id, body });
1522
+ }
1523
+ return steps;
1524
+ }
1525
+
1407
1526
  // src/core/github.ts
1408
1527
  import { setTimeout as delay } from "timers/promises";
1409
1528
  var DEFAULT_GITHUB_API = "https://api.github.com";
@@ -2226,6 +2345,45 @@ async function repository(owner, name, label, o) {
2226
2345
  const r = d?.repository ?? null;
2227
2346
  return r === null ? null : { id: r.id, issues: r.hasIssuesEnabled, labelId: r.label?.id ?? null };
2228
2347
  }
2348
+ async function openIssues(owner, name, label, o) {
2349
+ const found = [];
2350
+ let after = null;
2351
+ for (; ; ) {
2352
+ const d = await graphql(
2353
+ `
2354
+ query (
2355
+ $owner: String!
2356
+ $name: String!
2357
+ $label: String!
2358
+ $after: String
2359
+ ) {
2360
+ repository(owner: $owner, name: $name) {
2361
+ issues(first: 100, after: $after, states: OPEN, labels: [$label]) {
2362
+ nodes {
2363
+ id
2364
+ number
2365
+ title
2366
+ body
2367
+ }
2368
+ pageInfo {
2369
+ hasNextPage
2370
+ endCursor
2371
+ }
2372
+ }
2373
+ }
2374
+ }
2375
+ `,
2376
+ { owner, name, label, after },
2377
+ o
2378
+ );
2379
+ const page = d.repository?.issues;
2380
+ if (page === void 0) return found;
2381
+ found.push(...page.nodes);
2382
+ if (!page.pageInfo.hasNextPage || page.pageInfo.endCursor === null)
2383
+ return found;
2384
+ after = page.pageInfo.endCursor;
2385
+ }
2386
+ }
2229
2387
  async function createLabel(owner, name, label, o) {
2230
2388
  const body = await postJson(
2231
2389
  `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/labels`,
@@ -2488,6 +2646,7 @@ var GitHubBoards = class {
2488
2646
  const agent = await single("Agent", "Agent", [WORKING]);
2489
2647
  const session = await text("Session");
2490
2648
  const path = await text("Path");
2649
+ const sessions = await text("Sessions");
2491
2650
  const current = await views(cache.projectId, this.#o);
2492
2651
  const first = current[0];
2493
2652
  if (first !== void 0 && first.name === DEFAULT_VIEW && !current.some((v) => v.name === ALL_VIEW)) {
@@ -2556,7 +2715,15 @@ var GitHubBoards = class {
2556
2715
  await linkRepository(cache.projectId, memory.id, this.#o);
2557
2716
  added.push(`link ${repository2}`);
2558
2717
  }
2559
- cache.fields = [status2, collection, ...own.values(), agent, session, path];
2718
+ cache.fields = [
2719
+ status2,
2720
+ collection,
2721
+ ...own.values(),
2722
+ agent,
2723
+ session,
2724
+ path,
2725
+ sessions
2726
+ ];
2560
2727
  return added;
2561
2728
  }
2562
2729
  async items(board, options = {}) {
@@ -2581,7 +2748,8 @@ var GitHubBoards = class {
2581
2748
  collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
2582
2749
  type: r.values[COLLECTION_FIELD] ?? null,
2583
2750
  agent: r.values["Agent"] ?? null,
2584
- session: r.values["Session"] ?? null
2751
+ session: r.values["Session"] ?? null,
2752
+ sessions: r.values["Sessions"] ?? null
2585
2753
  };
2586
2754
  });
2587
2755
  }
@@ -2649,10 +2817,49 @@ var GitHubBoards = class {
2649
2817
  await this.#values(board, step2.id, step2.want);
2650
2818
  return null;
2651
2819
  }
2652
- /** A new document's issue: labelled, the first line as body, added to the board, Path first. */
2820
+ /**
2821
+ * An open issue of `.rness`, labelled `rness`, whose body starts with the
2822
+ * document's first line — made by an earlier sync whose item the board's
2823
+ * listing did not show yet, 18 s later (spec 0020 §7) — or null. Read once
2824
+ * per process, at the first create; an issue taken is taken off the list.
2825
+ */
2826
+ async #adoptable(board, cache, want) {
2827
+ cache.open ??= await openIssues(board.org, MEMORY, LABEL, this.#o);
2828
+ const at = cache.open.findIndex((i) => firstLine3(i.body) === want.body);
2829
+ if (at === -1) return null;
2830
+ return cache.open.splice(at, 1)[0] ?? null;
2831
+ }
2832
+ /**
2833
+ * A new document's issue: labelled, the first line as body, added to the
2834
+ * board, Path first. One already made for it is added instead: GitHub adds
2835
+ * an issue already on the board as the item it is, so no second one.
2836
+ */
2653
2837
  async #create(board, cache, want) {
2654
2838
  const memory = await this.#memory(board.org);
2655
2839
  const labelId = await this.#labelId(board);
2840
+ const found = await this.#adoptable(board, cache, want);
2841
+ if (found !== null) {
2842
+ const id2 = await addItem(cache.projectId, found.id, this.#o);
2843
+ const known = cache.known.get(id2) ?? {
2844
+ id: id2,
2845
+ draftId: null,
2846
+ issue: {
2847
+ id: found.id,
2848
+ number: found.number,
2849
+ open: true,
2850
+ repository: `${board.org}/${MEMORY}`,
2851
+ body: found.body,
2852
+ labels: [LABEL]
2853
+ },
2854
+ title: found.title,
2855
+ archived: false,
2856
+ values: {}
2857
+ };
2858
+ cache.known.set(id2, known);
2859
+ await this.#issue(known, want, labelId);
2860
+ await this.#values(board, id2, want);
2861
+ return { id: id2, number: found.number, adopted: true };
2862
+ }
2656
2863
  const made = await createIssue(
2657
2864
  memory.id,
2658
2865
  { title: want.title, body: want.body, labelIds: [labelId] },
@@ -2695,7 +2902,8 @@ var GitHubBoards = class {
2695
2902
  ["Path", want.path],
2696
2903
  [COLLECTION_FIELD, want.type],
2697
2904
  [STATUS_FIELD, want.status],
2698
- ...want.statusField === null ? [] : [[want.statusField, want.status]]
2905
+ ...want.statusField === null ? [] : [[want.statusField, want.status]],
2906
+ ["Sessions", want.sessions]
2699
2907
  ];
2700
2908
  for (const [name, value] of wanted)
2701
2909
  if ((values2[name] ?? null) !== value)
@@ -3063,7 +3271,7 @@ var GREETING = /^Hi ([^!\s]+)! You've successfully authenticated/m;
3063
3271
  function parseSshGreeting(stderr) {
3064
3272
  return GREETING.exec(stderr)?.[1] ?? null;
3065
3273
  }
3066
- function firstLine3(text) {
3274
+ function firstLine4(text) {
3067
3275
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? null;
3068
3276
  }
3069
3277
  var STDERR_FLUSH_MS2 = 200;
@@ -3103,7 +3311,7 @@ async function detectGithubSsh(opts) {
3103
3311
  settle(
3104
3312
  login !== null ? { ok: true, login } : {
3105
3313
  ok: false,
3106
- reason: firstLine3(stderr2) ?? `ssh exited with code ${code ?? "?"}`
3314
+ reason: firstLine4(stderr2) ?? `ssh exited with code ${code ?? "?"}`
3107
3315
  }
3108
3316
  );
3109
3317
  };
@@ -3473,6 +3681,8 @@ Writing:
3473
3681
  \`org/\` it affects.
3474
3682
  - An ADR it replaces is named in Context. That one becomes \`Superseded\`,
3475
3683
  with \`superseded_by:\`, only once the new ADR is \`Accepted\`.
3684
+ - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3685
+ unless it is already there.
3476
3686
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3477
3687
 
3478
3688
  After writing: give the path and the Decision section, and say the ADR
@@ -3510,6 +3720,8 @@ Writing:
3510
3720
  - What was checked in this session (a document read, a command run) is
3511
3721
  written as verified, with the date; everything else as a proposal or an
3512
3722
  open question.
3723
+ - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3724
+ unless it is already there.
3513
3725
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3514
3726
 
3515
3727
  After writing: give the path and the summary. It stays \`Draft\` until the
@@ -3550,6 +3762,8 @@ Writing:
3550
3762
  repository has one.
3551
3763
  - Where the plan decides something the specification leaves open, a
3552
3764
  section says so.
3765
+ - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3766
+ unless it is already there.
3553
3767
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3554
3768
 
3555
3769
  After writing: give the path and the list of tasks. It becomes \`Ready\` when
@@ -3579,6 +3793,8 @@ Arguments: \`$ARGUMENTS\`.
3579
3793
  ran, what it showed, what could not run and why;
3580
3794
  - the specification: \`Implemented\` once every plan of it is
3581
3795
  \`Completed\`; \`updated\` today;
3796
+ - the plan and the specification: add \`\${CLAUDE_SESSION_ID}\` at the end
3797
+ of \`sessions:\` in their front matter, unless it is already there;
3582
3798
  - the smallest set of documents the work makes inaccurate: \`${rness}/docs/\`,
3583
3799
  the READMEs of the repositories it changed.
3584
3800
  6. A task not proven: the plan stays \`In progress\` \u2014 or \`Blocked\`, the
@@ -5660,7 +5876,7 @@ async function createBlank(input) {
5660
5876
  }
5661
5877
 
5662
5878
  // src/commands/create.ts
5663
- function firstLine4(text) {
5879
+ function firstLine5(text) {
5664
5880
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
5665
5881
  }
5666
5882
  function splitSpecs(list2) {
@@ -5719,7 +5935,7 @@ async function pickRepositories(input) {
5719
5935
  ui.hint(`listed the first ${MAX_PAGES * PER_PAGE} repositories of ${org}`);
5720
5936
  } catch (e) {
5721
5937
  failed = true;
5722
- ui.warn(firstLine4(e instanceof Error ? e.message : String(e)));
5938
+ ui.warn(firstLine5(e instanceof Error ? e.message : String(e)));
5723
5939
  }
5724
5940
  const inCatalogue = (name) => Object.hasOwn(input.catalogue, name);
5725
5941
  const byLabel = (a, b) => a.label.localeCompare(b.label, "en", { sensitivity: "base" });
@@ -5813,7 +6029,7 @@ async function cloneSelection(input) {
5813
6029
  manifest = result.manifest;
5814
6030
  ui.line("declared", `scope ${result.name} (org/${result.name})`);
5815
6031
  } catch (e) {
5816
- const message2 = firstLine4(e instanceof Error ? e.message : String(e));
6032
+ const message2 = firstLine5(e instanceof Error ? e.message : String(e));
5817
6033
  ui.error(`${spec}: ${message2}`);
5818
6034
  failures.push({ spec, message: message2 });
5819
6035
  }
@@ -5908,7 +6124,7 @@ async function publishContext(input) {
5908
6124
  );
5909
6125
  return true;
5910
6126
  } catch (e) {
5911
- ui.warn(firstLine4(e instanceof Error ? e.message : String(e)));
6127
+ ui.warn(firstLine5(e instanceof Error ? e.message : String(e)));
5912
6128
  return false;
5913
6129
  }
5914
6130
  }
@@ -6509,8 +6725,7 @@ async function declaredManager(rnessDir, pm) {
6509
6725
  }
6510
6726
  return `${pm}@${await packageManagerVersion(pm)}`;
6511
6727
  }
6512
- async function clonesToCommit(root, cwd, manifest) {
6513
- const files = writtenFiles(manifest);
6728
+ async function clonesToCommit(root, cwd, manifest, files) {
6514
6729
  const clones = [];
6515
6730
  for (const repo of Object.keys(manifest.repos)) {
6516
6731
  const dir = join23(root, "org", repo);
@@ -6524,6 +6739,27 @@ async function clonesToCommit(root, cwd, manifest) {
6524
6739
  }
6525
6740
  return clones;
6526
6741
  }
6742
+ async function filesTheSyncWrites(root, rnessDir, manifest) {
6743
+ const own = writtenFiles(manifest);
6744
+ const pinned = await readPinnedCli(rnessDir);
6745
+ if (pinned === null) return own;
6746
+ try {
6747
+ const { stdout } = await execFileP6(
6748
+ process.execPath,
6749
+ [join23(pinned.dir, pinned.bin), "written-files", "--cwd", root],
6750
+ {
6751
+ cwd: root,
6752
+ env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
6753
+ timeout: 1e4
6754
+ }
6755
+ );
6756
+ const theirs = JSON.parse(stdout);
6757
+ if (Array.isArray(theirs) && theirs.every((f) => typeof f === "string"))
6758
+ return [.../* @__PURE__ */ new Set([...own, ...theirs])];
6759
+ } catch {
6760
+ }
6761
+ return own;
6762
+ }
6527
6763
  function finish(ui, cwd, rnessDir, label, target2, ending) {
6528
6764
  const rel = relative5(cwd, rnessDir) || ".";
6529
6765
  const message2 = `chore: rness ${target2}`;
@@ -6772,11 +7008,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
6772
7008
  }
6773
7009
  return finish(ui, cwd, ws.rnessDir, label, target2, {
6774
7010
  commit: committed.kind,
6775
- clones: await clonesToCommit(
6776
- ws.root,
6777
- cwd,
6778
- await loadManifest(ws.rnessDir)
6779
- )
7011
+ clones: await (async () => {
7012
+ const manifest = await loadManifest(ws.rnessDir);
7013
+ return clonesToCommit(
7014
+ ws.root,
7015
+ cwd,
7016
+ manifest,
7017
+ await filesTheSyncWrites(ws.root, ws.rnessDir, manifest)
7018
+ );
7019
+ })()
6780
7020
  });
6781
7021
  } catch (e) {
6782
7022
  return reportError(e);
@@ -7642,114 +7882,6 @@ async function writeOpened(file, opened) {
7642
7882
  }
7643
7883
  }
7644
7884
 
7645
- // src/pulse/plan.ts
7646
- var differs = (item, w) => item.title !== w.title || item.status !== w.status || w.statusField !== null && item.collectionStatus !== w.status || item.type !== w.type;
7647
- var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
7648
- var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
7649
- var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
7650
- var firstLine5 = (body) => body.split(/\r\n?|\n/, 1)[0] ?? "";
7651
- function strays(have) {
7652
- const byLine = /* @__PURE__ */ new Map();
7653
- for (const item of have) {
7654
- if (item.path !== null || item.issue === null || item.issue.body === null || !item.issue.labelled)
7655
- continue;
7656
- const line3 = firstLine5(item.issue.body);
7657
- byLine.set(line3, [...byLine.get(line3) ?? [], item]);
7658
- }
7659
- for (const items of byLine.values()) items.sort(keptFirst);
7660
- return byLine;
7661
- }
7662
- function unwantedPaths(want, have) {
7663
- const wanted = new Set(want.map((w) => w.path));
7664
- const paths = /* @__PURE__ */ new Set();
7665
- for (const item of have)
7666
- if (item.path !== null && !wanted.has(item.path)) paths.add(item.path);
7667
- return [...paths];
7668
- }
7669
- function isGone(path, item, seen, opened) {
7670
- return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
7671
- }
7672
- function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
7673
- const byPath = /* @__PURE__ */ new Map();
7674
- for (const item of have)
7675
- if (item.path !== null)
7676
- byPath.set(item.path, [...byPath.get(item.path) ?? [], item]);
7677
- const kept = /* @__PURE__ */ new Map();
7678
- const surplus = [];
7679
- for (const [path, items] of byPath) {
7680
- const [keep, ...rest] = [...items].sort(keptFirst);
7681
- if (keep !== void 0) kept.set(path, keep);
7682
- surplus.push(...rest.map(gone));
7683
- }
7684
- const left = strays(have);
7685
- const steps = [];
7686
- const wanted = /* @__PURE__ */ new Set();
7687
- for (const w of want) {
7688
- wanted.add(w.path);
7689
- const item = kept.get(w.path) ?? left.get(w.body)?.[0];
7690
- if (item === void 0) steps.push({ kind: "create", want: w });
7691
- else if (item.issue === null)
7692
- steps.push({ kind: "convert", id: item.id, want: w });
7693
- else if (item.path === null || !item.issue.open || !item.issue.labelled || differs(item, w))
7694
- steps.push({
7695
- kind: "update",
7696
- id: item.id,
7697
- want: w,
7698
- reopen: !item.issue.open
7699
- });
7700
- else steps.push({ kind: "unchanged", id: item.id });
7701
- }
7702
- for (const [path, item] of kept)
7703
- if (!wanted.has(path))
7704
- steps.push(
7705
- isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
7706
- );
7707
- return [...steps, ...surplus];
7708
- }
7709
- function stillOpened(opened, have, seen, closed, created) {
7710
- const items = /* @__PURE__ */ new Map();
7711
- for (const item of have)
7712
- if (item.issue !== null) items.set(item.issue.number, item.id);
7713
- const next = /* @__PURE__ */ new Map();
7714
- for (const [number, path] of opened) {
7715
- const id = items.get(number);
7716
- if (id !== void 0 && !closed.has(id) && !seen.has(path))
7717
- next.set(number, path);
7718
- }
7719
- for (const [path, placed] of created) next.set(placed.number, path);
7720
- return next;
7721
- }
7722
- function issuedAfter(steps, have, placed) {
7723
- const byId = new Map(have.map((i) => [i.id, i]));
7724
- const issued = /* @__PURE__ */ new Map();
7725
- for (const step2 of steps) {
7726
- if (step2.kind === "create" || step2.kind === "convert") {
7727
- const p = placed.get(step2.want.path);
7728
- if (p !== void 0) issued.set(step2.want.path, { ...p, body: null });
7729
- } else if (step2.kind === "update" || step2.kind === "unchanged") {
7730
- const item = byId.get(step2.id);
7731
- const path = step2.kind === "update" ? step2.want.path : item?.path;
7732
- if (item === void 0 || path == null || item.issue === null) continue;
7733
- issued.set(path, {
7734
- id: item.id,
7735
- number: item.issue.number,
7736
- body: item.issue.body
7737
- });
7738
- }
7739
- }
7740
- return issued;
7741
- }
7742
- function planBodies(bodies, issued) {
7743
- const steps = [];
7744
- for (const [path, body] of bodies) {
7745
- const i = issued.get(path);
7746
- if (i === void 0) continue;
7747
- if (i.body === null || digestOf(i.body) !== digestOf(body))
7748
- steps.push({ kind: "body", id: i.id, body });
7749
- }
7750
- return steps;
7751
- }
7752
-
7753
7885
  // src/commands/pulse.ts
7754
7886
  var PROJECT_SCOPE = "project";
7755
7887
  var NEEDS_LOGIN = "the pulse needs a GitHub login: run rness login";
@@ -7827,10 +7959,15 @@ async function filesOf2(rnessDir) {
7827
7959
  return files;
7828
7960
  }
7829
7961
  var count2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
7830
- function sayDone(ui, org, documents2, steps, writes) {
7831
- const n = (kind) => steps.filter((s) => s.kind === kind).length;
7962
+ function sayDone(ui, org, documents2, steps, writes, adopted) {
7963
+ const n = (kind) => steps.filter((s) => s.kind === kind).length - (kind === "create" ? adopted : 0);
7832
7964
  const reopened = steps.filter((s) => s.kind === "update" && s.reopen).length;
7833
7965
  if (n("create") > 0) ui.line("created", count2(n("create"), "issue"));
7966
+ if (adopted > 0)
7967
+ ui.line(
7968
+ "adopted",
7969
+ `${count2(adopted, "issue")} already made for its document`
7970
+ );
7834
7971
  if (n("convert") > 0)
7835
7972
  ui.line(
7836
7973
  "converted",
@@ -7843,11 +7980,12 @@ function sayDone(ui, org, documents2, steps, writes) {
7843
7980
  ).length;
7844
7981
  const parts = [
7845
7982
  ["created", n("create")],
7983
+ ["adopted", adopted],
7846
7984
  ["converted", n("convert")],
7847
7985
  ["updated", n("update") + bodyOnly],
7848
7986
  [
7849
7987
  "unchanged",
7850
- documents2 - n("create") - n("convert") - n("update") - bodyOnly
7988
+ documents2 - n("create") - adopted - n("convert") - n("update") - bodyOnly
7851
7989
  ],
7852
7990
  ["archived", n("close") + n("archive")]
7853
7991
  ].filter(([, k]) => k > 0).map(([word, k]) => `${k} ${word}`);
@@ -7875,6 +8013,7 @@ async function syncBoard(c, board, ui, sayLayout) {
7875
8013
  const changes = steps.filter((s) => s.kind !== "unchanged").length;
7876
8014
  let writes = [];
7877
8015
  let made = 0;
8016
+ let adopted = 0;
7878
8017
  try {
7879
8018
  const placed = /* @__PURE__ */ new Map();
7880
8019
  const created = /* @__PURE__ */ new Map();
@@ -7884,7 +8023,9 @@ async function syncBoard(c, board, ui, sayLayout) {
7884
8023
  const p = await fromGithub(c.provider.apply(board, step2));
7885
8024
  if (p !== null && (step2.kind === "create" || step2.kind === "convert"))
7886
8025
  placed.set(step2.want.path, p);
7887
- if (p !== null && step2.kind === "create") created.set(step2.want.path, p);
8026
+ if (p?.adopted === true) adopted++;
8027
+ else if (p !== null && step2.kind === "create")
8028
+ created.set(step2.want.path, p);
7888
8029
  if (step2.kind === "close" || step2.kind === "archive")
7889
8030
  closed.add(step2.id);
7890
8031
  if (step2.kind !== "unchanged") made++;
@@ -7923,7 +8064,7 @@ async function syncBoard(c, board, ui, sayLayout) {
7923
8064
  { cause: e }
7924
8065
  );
7925
8066
  }
7926
- sayDone(ui, c.org, want.length, steps, writes);
8067
+ sayDone(ui, c.org, want.length, steps, writes, adopted);
7927
8068
  }
7928
8069
  async function pulseCreateCommand(opts, deps = {}) {
7929
8070
  try {
@@ -8029,13 +8170,16 @@ async function mark(opts) {
8029
8170
  return 0;
8030
8171
  }
8031
8172
  const wanted = new Set(opts.paths);
8032
- const documents2 = new Set(
8033
- (await statusTabs(c.rnessDir)).flatMap((t) => t.rows.map((r) => r.path))
8173
+ const documents2 = new Map(
8174
+ (await statusTabs(c.rnessDir)).flatMap(
8175
+ (t) => t.rows.map((r) => [r.path, r.status])
8176
+ )
8034
8177
  );
8035
- const onBoard = new Set(
8036
- items.flatMap((i) => i.path === null ? [] : [i.path])
8178
+ const onBoard = new Map(
8179
+ items.flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
8037
8180
  );
8038
- if ([...wanted].some((p) => documents2.has(p) && !onBoard.has(p))) {
8181
+ const stale = (path) => documents2.has(path) && (!onBoard.has(path) || onBoard.get(path) !== documents2.get(path));
8182
+ if ([...wanted].some(stale)) {
8039
8183
  const synced = await oneSyncAtATime(
8040
8184
  key,
8041
8185
  () => syncBoard(c, board, plainUi, eachAdded),
@@ -8281,6 +8425,21 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
8281
8425
  }
8282
8426
  }
8283
8427
 
8428
+ // src/commands/written-files.ts
8429
+ async function writtenFilesCommand(opts) {
8430
+ try {
8431
+ const ws = await findWorkspace(opts.cwd ?? process.cwd());
8432
+ const manifest = await loadManifest(ws.rnessDir);
8433
+ process.stdout.write(`${JSON.stringify(writtenFiles(manifest))}
8434
+ `);
8435
+ return 0;
8436
+ } catch (e) {
8437
+ process.stderr.write(`${e instanceof Error ? e.message : String(e)}
8438
+ `);
8439
+ return 1;
8440
+ }
8441
+ }
8442
+
8284
8443
  // src/cli.ts
8285
8444
  function buildProgram(state) {
8286
8445
  const program = new Command().name("rness").description("Configuration-plane CLI for rness workspaces").version(VERSION, "-v, --version", "print the version").exitOverride().showHelpAfterError().configureOutput({
@@ -8372,6 +8531,9 @@ function buildProgram(state) {
8372
8531
  program.command("git-credential", { hidden: true }).argument("<operation>").action(async (operation) => {
8373
8532
  state.code = await gitCredentialCommand(operation);
8374
8533
  });
8534
+ program.command("written-files", { hidden: true }).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
8535
+ state.code = await writtenFilesCommand(opts);
8536
+ });
8375
8537
  program.command("hook", { hidden: true }).argument("<event>", "session-start, post-tool-use or session-end").action(async (event) => {
8376
8538
  state.code = await hookCommand(event);
8377
8539
  });
@@ -8561,4 +8723,4 @@ export {
8561
8723
  OWN_COMMANDS,
8562
8724
  catchUp
8563
8725
  };
8564
- //# sourceMappingURL=chunk-AXTFDXN2.js.map
8726
+ //# sourceMappingURL=chunk-7IVHL744.js.map