@rness/cli 0.15.0 → 0.16.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.
@@ -486,7 +486,7 @@ function parseRepoSpec(spec, org, host) {
486
486
  }
487
487
 
488
488
  // src/version.ts
489
- var VERSION = "0.15.0";
489
+ var VERSION = "0.16.0";
490
490
 
491
491
  // src/core/pm.ts
492
492
  import { execFile as execFile2 } from "child_process";
@@ -1073,10 +1073,17 @@ function statusOf(item) {
1073
1073
  function sessionsOf(item) {
1074
1074
  const listed = item.fields?.["sessions"];
1075
1075
  if (!Array.isArray(listed)) return [];
1076
- const ids = listed.filter(
1077
- (s) => typeof s === "string" && s !== ""
1078
- );
1079
- return [...new Set(ids)];
1076
+ const entries = /* @__PURE__ */ new Map();
1077
+ for (const entry of listed) {
1078
+ const fields2 = typeof entry === "string" ? { id: entry } : entry !== null && typeof entry === "object" ? entry : {};
1079
+ const { id, agent } = fields2;
1080
+ if (typeof id !== "string" || id === "" || entries.has(id)) continue;
1081
+ entries.set(
1082
+ id,
1083
+ typeof agent === "string" && agent !== "" ? { id, agent } : { id }
1084
+ );
1085
+ }
1086
+ return [...entries.values()];
1080
1087
  }
1081
1088
  function rowOf(dir, item) {
1082
1089
  const base = posix3.basename(item.rel, ".md");
@@ -1410,11 +1417,119 @@ function desiredOf(tabs, org) {
1410
1417
  status: statusOf2(row.status),
1411
1418
  type: tab.label,
1412
1419
  statusField: fields2.get(tab.label)?.name ?? null,
1413
- sessions: row.sessions?.join(", ") ?? null
1420
+ sessions: row.sessions?.map((e) => e.agent === void 0 ? e.id : `${e.agent} \xB7 ${e.id}`).join(", ") ?? null
1414
1421
  }))
1415
1422
  );
1416
1423
  }
1417
1424
 
1425
+ // src/pulse/plan.ts
1426
+ 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;
1427
+ var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
1428
+ var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
1429
+ var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
1430
+ var firstLine3 = (body) => body.split(/\r\n?|\n/, 1)[0] ?? "";
1431
+ function strays(have) {
1432
+ const byLine = /* @__PURE__ */ new Map();
1433
+ for (const item of have) {
1434
+ if (item.path !== null || item.issue === null || item.issue.body === null || !item.issue.labelled)
1435
+ continue;
1436
+ const line3 = firstLine3(item.issue.body);
1437
+ byLine.set(line3, [...byLine.get(line3) ?? [], item]);
1438
+ }
1439
+ for (const items of byLine.values()) items.sort(keptFirst);
1440
+ return byLine;
1441
+ }
1442
+ function unwantedPaths(want, have) {
1443
+ const wanted = new Set(want.map((w) => w.path));
1444
+ const paths = /* @__PURE__ */ new Set();
1445
+ for (const item of have)
1446
+ if (item.path !== null && !wanted.has(item.path)) paths.add(item.path);
1447
+ return [...paths];
1448
+ }
1449
+ function isGone(path, item, seen, opened) {
1450
+ return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
1451
+ }
1452
+ function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
1453
+ const byPath = /* @__PURE__ */ new Map();
1454
+ for (const item of have)
1455
+ if (item.path !== null)
1456
+ byPath.set(item.path, [...byPath.get(item.path) ?? [], item]);
1457
+ const kept = /* @__PURE__ */ new Map();
1458
+ const surplus = [];
1459
+ for (const [path, items] of byPath) {
1460
+ const [keep, ...rest] = [...items].sort(keptFirst);
1461
+ if (keep !== void 0) kept.set(path, keep);
1462
+ surplus.push(...rest.map(gone));
1463
+ }
1464
+ const left = strays(have);
1465
+ const steps = [];
1466
+ const wanted = /* @__PURE__ */ new Set();
1467
+ for (const w of want) {
1468
+ wanted.add(w.path);
1469
+ const item = kept.get(w.path) ?? left.get(w.body)?.[0];
1470
+ if (item === void 0) steps.push({ kind: "create", want: w });
1471
+ else if (item.issue === null)
1472
+ steps.push({ kind: "convert", id: item.id, want: w });
1473
+ else if (item.path === null || !item.issue.open || !item.issue.labelled || differs(item, w))
1474
+ steps.push({
1475
+ kind: "update",
1476
+ id: item.id,
1477
+ want: w,
1478
+ reopen: !item.issue.open
1479
+ });
1480
+ else steps.push({ kind: "unchanged", id: item.id });
1481
+ }
1482
+ for (const [path, item] of kept)
1483
+ if (!wanted.has(path))
1484
+ steps.push(
1485
+ isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
1486
+ );
1487
+ return [...steps, ...surplus];
1488
+ }
1489
+ function stillOpened(opened, have, seen, closed, created) {
1490
+ const items = /* @__PURE__ */ new Map();
1491
+ for (const item of have)
1492
+ if (item.issue !== null) items.set(item.issue.number, item.id);
1493
+ const next = /* @__PURE__ */ new Map();
1494
+ for (const [number, path] of opened) {
1495
+ const id = items.get(number);
1496
+ if (id !== void 0 && !closed.has(id) && !seen.has(path))
1497
+ next.set(number, path);
1498
+ }
1499
+ for (const [path, placed] of created) next.set(placed.number, path);
1500
+ return next;
1501
+ }
1502
+ function issuedAfter(steps, have, placed) {
1503
+ const byId = new Map(have.map((i) => [i.id, i]));
1504
+ const issued = /* @__PURE__ */ new Map();
1505
+ for (const step2 of steps) {
1506
+ if (step2.kind === "create" || step2.kind === "convert") {
1507
+ const p = placed.get(step2.want.path);
1508
+ if (p !== void 0) issued.set(step2.want.path, { ...p, body: null });
1509
+ } else if (step2.kind === "update" || step2.kind === "unchanged") {
1510
+ const item = byId.get(step2.id);
1511
+ const path = step2.kind === "update" ? step2.want.path : item?.path;
1512
+ if (item === void 0 || path == null || item.issue === null) continue;
1513
+ issued.set(path, {
1514
+ id: item.id,
1515
+ number: item.issue.number,
1516
+ body: item.issue.body
1517
+ });
1518
+ }
1519
+ }
1520
+ return issued;
1521
+ }
1522
+ function planBodies(bodies, issued) {
1523
+ const steps = [];
1524
+ for (const [path, body] of bodies) {
1525
+ const i = issued.get(path);
1526
+ if (i === void 0) continue;
1527
+ if (i.body === null || digestOf(i.body) !== digestOf(body))
1528
+ steps.push({ kind: "body", id: i.id, body });
1529
+ }
1530
+ return steps;
1531
+ }
1532
+
1418
1533
  // src/core/github.ts
1419
1534
  import { setTimeout as delay } from "timers/promises";
1420
1535
  var DEFAULT_GITHUB_API = "https://api.github.com";
@@ -1883,6 +1998,18 @@ async function setOptions(field, options, o) {
1883
1998
  );
1884
1999
  return toField(d.updateProjectV2Field.projectV2Field);
1885
2000
  }
2001
+ async function renameField(fieldId, name, o) {
2002
+ const d = await graphql(
2003
+ `mutation($fieldId: ID!, $name: String!) {
2004
+ updateProjectV2Field(input: { fieldId: $fieldId, name: $name }) {
2005
+ projectV2Field { ${FIELD_SELECTION} }
2006
+ }
2007
+ }`,
2008
+ { fieldId, name },
2009
+ o
2010
+ );
2011
+ return toField(d.updateProjectV2Field.projectV2Field);
2012
+ }
1886
2013
  async function listItems(projectId, o, { bodies = true } = {}) {
1887
2014
  const items = [];
1888
2015
  let cursor = null;
@@ -2237,6 +2364,45 @@ async function repository(owner, name, label, o) {
2237
2364
  const r = d?.repository ?? null;
2238
2365
  return r === null ? null : { id: r.id, issues: r.hasIssuesEnabled, labelId: r.label?.id ?? null };
2239
2366
  }
2367
+ async function openIssues(owner, name, label, o) {
2368
+ const found = [];
2369
+ let after = null;
2370
+ for (; ; ) {
2371
+ const d = await graphql(
2372
+ `
2373
+ query (
2374
+ $owner: String!
2375
+ $name: String!
2376
+ $label: String!
2377
+ $after: String
2378
+ ) {
2379
+ repository(owner: $owner, name: $name) {
2380
+ issues(first: 100, after: $after, states: OPEN, labels: [$label]) {
2381
+ nodes {
2382
+ id
2383
+ number
2384
+ title
2385
+ body
2386
+ }
2387
+ pageInfo {
2388
+ hasNextPage
2389
+ endCursor
2390
+ }
2391
+ }
2392
+ }
2393
+ }
2394
+ `,
2395
+ { owner, name, label, after },
2396
+ o
2397
+ );
2398
+ const page = d.repository?.issues;
2399
+ if (page === void 0) return found;
2400
+ found.push(...page.nodes);
2401
+ if (!page.pageInfo.hasNextPage || page.pageInfo.endCursor === null)
2402
+ return found;
2403
+ after = page.pageInfo.endCursor;
2404
+ }
2405
+ }
2240
2406
  async function createLabel(owner, name, label, o) {
2241
2407
  const body = await postJson(
2242
2408
  `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/labels`,
@@ -2343,6 +2509,13 @@ var WORKING = "working";
2343
2509
  var WORKING_VIEW = "Working";
2344
2510
  var MEMORY = ".rness";
2345
2511
  var LABEL = "rness";
2512
+ var WORKING_SESSION = "Working session";
2513
+ var SESSION_HISTORY = "Session history";
2514
+ var FORMERLY = {
2515
+ [WORKING_SESSION]: "Session",
2516
+ [SESSION_HISTORY]: "Sessions"
2517
+ };
2518
+ var valueOf = (values2, name) => values2[name] ?? values2[FORMERLY[name] ?? ""] ?? null;
2346
2519
  var LABEL_COLOR = "5319e7";
2347
2520
  var LABEL_DESCRIPTION = "A document of .rness, on Agent Pulse";
2348
2521
  var labelled = (labels) => labels.some((l) => l.toLowerCase() === LABEL.toLowerCase());
@@ -2477,6 +2650,11 @@ var GitHubBoards = class {
2477
2650
  const text = async (name) => {
2478
2651
  const field = fields2.find((f) => f.name === name);
2479
2652
  if (field !== void 0) return field;
2653
+ const was = fields2.find((f) => f.name === FORMERLY[name]);
2654
+ if (was !== void 0) {
2655
+ added.push(`renamed field ${was.name} \u2192 ${name}`);
2656
+ return renameField(was.id, name, this.#o);
2657
+ }
2480
2658
  added.push(`field ${name}`);
2481
2659
  return createField(cache.projectId, name, "TEXT", this.#o);
2482
2660
  };
@@ -2497,9 +2675,9 @@ var GitHubBoards = class {
2497
2675
  for (const f of layout.fields)
2498
2676
  own.set(f.name, await single(f.name, "Status", f.statuses, true));
2499
2677
  const agent = await single("Agent", "Agent", [WORKING]);
2500
- const session = await text("Session");
2678
+ const session = await text(WORKING_SESSION);
2501
2679
  const path = await text("Path");
2502
- const sessions = await text("Sessions");
2680
+ const sessions = await text(SESSION_HISTORY);
2503
2681
  const current = await views(cache.projectId, this.#o);
2504
2682
  const first = current[0];
2505
2683
  if (first !== void 0 && first.name === DEFAULT_VIEW && !current.some((v) => v.name === ALL_VIEW)) {
@@ -2601,14 +2779,15 @@ var GitHubBoards = class {
2601
2779
  collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
2602
2780
  type: r.values[COLLECTION_FIELD] ?? null,
2603
2781
  agent: r.values["Agent"] ?? null,
2604
- session: r.values["Session"] ?? null,
2605
- sessions: r.values["Sessions"] ?? null
2782
+ session: valueOf(r.values, WORKING_SESSION),
2783
+ sessions: valueOf(r.values, SESSION_HISTORY)
2606
2784
  };
2607
2785
  });
2608
2786
  }
2609
2787
  async #set(board, itemId, name, value) {
2610
2788
  const cache = await this.#cacheOf(board);
2611
- const field = (await this.#fields(board)).find((f) => f.name === name);
2789
+ const fields2 = await this.#fields(board);
2790
+ const field = fields2.find((f) => f.name === name) ?? fields2.find((f) => f.name === FORMERLY[name]);
2612
2791
  if (field === void 0) throw new Error(`the board has no field ${name}`);
2613
2792
  let sent = null;
2614
2793
  if (value !== null && field.options === null) sent = { text: value };
@@ -2621,8 +2800,8 @@ var GitHubBoards = class {
2621
2800
  await setValue(cache.projectId, itemId, field.id, sent, this.#o);
2622
2801
  const known = cache.known.get(itemId);
2623
2802
  if (known === void 0) return;
2624
- if (value === null) delete known.values[name];
2625
- else known.values[name] = value;
2803
+ if (value === null) delete known.values[field.name];
2804
+ else known.values[field.name] = value;
2626
2805
  }
2627
2806
  async apply(board, step2) {
2628
2807
  if (step2.kind === "unchanged") return null;
@@ -2670,10 +2849,49 @@ var GitHubBoards = class {
2670
2849
  await this.#values(board, step2.id, step2.want);
2671
2850
  return null;
2672
2851
  }
2673
- /** A new document's issue: labelled, the first line as body, added to the board, Path first. */
2852
+ /**
2853
+ * An open issue of `.rness`, labelled `rness`, whose body starts with the
2854
+ * document's first line — made by an earlier sync whose item the board's
2855
+ * listing did not show yet, 18 s later (spec 0020 §7) — or null. Read once
2856
+ * per process, at the first create; an issue taken is taken off the list.
2857
+ */
2858
+ async #adoptable(board, cache, want) {
2859
+ cache.open ??= await openIssues(board.org, MEMORY, LABEL, this.#o);
2860
+ const at = cache.open.findIndex((i) => firstLine3(i.body) === want.body);
2861
+ if (at === -1) return null;
2862
+ return cache.open.splice(at, 1)[0] ?? null;
2863
+ }
2864
+ /**
2865
+ * A new document's issue: labelled, the first line as body, added to the
2866
+ * board, Path first. One already made for it is added instead: GitHub adds
2867
+ * an issue already on the board as the item it is, so no second one.
2868
+ */
2674
2869
  async #create(board, cache, want) {
2675
2870
  const memory = await this.#memory(board.org);
2676
2871
  const labelId = await this.#labelId(board);
2872
+ const found = await this.#adoptable(board, cache, want);
2873
+ if (found !== null) {
2874
+ const id2 = await addItem(cache.projectId, found.id, this.#o);
2875
+ const known = cache.known.get(id2) ?? {
2876
+ id: id2,
2877
+ draftId: null,
2878
+ issue: {
2879
+ id: found.id,
2880
+ number: found.number,
2881
+ open: true,
2882
+ repository: `${board.org}/${MEMORY}`,
2883
+ body: found.body,
2884
+ labels: [LABEL]
2885
+ },
2886
+ title: found.title,
2887
+ archived: false,
2888
+ values: {}
2889
+ };
2890
+ cache.known.set(id2, known);
2891
+ await this.#issue(known, want, labelId);
2892
+ await this.#values(board, id2, want);
2893
+ return { id: id2, number: found.number, adopted: true };
2894
+ }
2677
2895
  const made = await createIssue(
2678
2896
  memory.id,
2679
2897
  { title: want.title, body: want.body, labelIds: [labelId] },
@@ -2717,10 +2935,10 @@ var GitHubBoards = class {
2717
2935
  [COLLECTION_FIELD, want.type],
2718
2936
  [STATUS_FIELD, want.status],
2719
2937
  ...want.statusField === null ? [] : [[want.statusField, want.status]],
2720
- ["Sessions", want.sessions]
2938
+ [SESSION_HISTORY, want.sessions]
2721
2939
  ];
2722
2940
  for (const [name, value] of wanted)
2723
- if ((values2[name] ?? null) !== value)
2941
+ if (valueOf(values2, name) !== value)
2724
2942
  await this.#set(board, id, name, value);
2725
2943
  }
2726
2944
  async #known(board, id) {
@@ -2746,7 +2964,7 @@ var GitHubBoards = class {
2746
2964
  async mark(board, itemIds, session) {
2747
2965
  for (const id of itemIds) {
2748
2966
  await this.#set(board, id, "Agent", session === null ? null : WORKING);
2749
- await this.#set(board, id, "Session", session);
2967
+ await this.#set(board, id, WORKING_SESSION, session);
2750
2968
  }
2751
2969
  }
2752
2970
  };
@@ -3085,7 +3303,7 @@ var GREETING = /^Hi ([^!\s]+)! You've successfully authenticated/m;
3085
3303
  function parseSshGreeting(stderr) {
3086
3304
  return GREETING.exec(stderr)?.[1] ?? null;
3087
3305
  }
3088
- function firstLine3(text) {
3306
+ function firstLine4(text) {
3089
3307
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? null;
3090
3308
  }
3091
3309
  var STDERR_FLUSH_MS2 = 200;
@@ -3125,7 +3343,7 @@ async function detectGithubSsh(opts) {
3125
3343
  settle(
3126
3344
  login !== null ? { ok: true, login } : {
3127
3345
  ok: false,
3128
- reason: firstLine3(stderr2) ?? `ssh exited with code ${code ?? "?"}`
3346
+ reason: firstLine4(stderr2) ?? `ssh exited with code ${code ?? "?"}`
3129
3347
  }
3130
3348
  );
3131
3349
  };
@@ -3495,8 +3713,10 @@ Writing:
3495
3713
  \`org/\` it affects.
3496
3714
  - An ADR it replaces is named in Context. That one becomes \`Superseded\`,
3497
3715
  with \`superseded_by:\`, only once the new ADR is \`Accepted\`.
3498
- - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3499
- unless it is already there.
3716
+ - Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
3717
+ \`sessions:\` in the front matter, unless that id is already there.
3718
+ \`<model>\` is the model you run as, as Claude Code names it, after
3719
+ \`Claude\`: for example \`Claude Opus 5.5\`.
3500
3720
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3501
3721
 
3502
3722
  After writing: give the path and the Decision section, and say the ADR
@@ -3534,8 +3754,10 @@ Writing:
3534
3754
  - What was checked in this session (a document read, a command run) is
3535
3755
  written as verified, with the date; everything else as a proposal or an
3536
3756
  open question.
3537
- - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3538
- unless it is already there.
3757
+ - Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
3758
+ \`sessions:\` in the front matter, unless that id is already there.
3759
+ \`<model>\` is the model you run as, as Claude Code names it, after
3760
+ \`Claude\`: for example \`Claude Opus 5.5\`.
3539
3761
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3540
3762
 
3541
3763
  After writing: give the path and the summary. It stays \`Draft\` until the
@@ -3576,8 +3798,10 @@ Writing:
3576
3798
  repository has one.
3577
3799
  - Where the plan decides something the specification leaves open, a
3578
3800
  section says so.
3579
- - Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
3580
- unless it is already there.
3801
+ - Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
3802
+ \`sessions:\` in the front matter, unless that id is already there.
3803
+ \`<model>\` is the model you run as, as Claude Code names it, after
3804
+ \`Claude\`: for example \`Claude Opus 5.5\`.
3581
3805
  - The rness hook checks every edit under \`${rness}/\`: fix what it reports.
3582
3806
 
3583
3807
  After writing: give the path and the list of tasks. It becomes \`Ready\` when
@@ -3607,8 +3831,11 @@ Arguments: \`$ARGUMENTS\`.
3607
3831
  ran, what it showed, what could not run and why;
3608
3832
  - the specification: \`Implemented\` once every plan of it is
3609
3833
  \`Completed\`; \`updated\` today;
3610
- - the plan and the specification: add \`\${CLAUDE_SESSION_ID}\` at the end
3611
- of \`sessions:\` in their front matter, unless it is already there;
3834
+ - the plan and the specification: add
3835
+ \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
3836
+ \`sessions:\` in their front matter, unless that id is already there;
3837
+ \`<model>\` is the model you run as, as Claude Code names it, after
3838
+ \`Claude\`: for example \`Claude Opus 5.5\`;
3612
3839
  - the smallest set of documents the work makes inaccurate: \`${rness}/docs/\`,
3613
3840
  the READMEs of the repositories it changed.
3614
3841
  6. A task not proven: the plan stays \`In progress\` \u2014 or \`Blocked\`, the
@@ -5690,7 +5917,7 @@ async function createBlank(input) {
5690
5917
  }
5691
5918
 
5692
5919
  // src/commands/create.ts
5693
- function firstLine4(text) {
5920
+ function firstLine5(text) {
5694
5921
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
5695
5922
  }
5696
5923
  function splitSpecs(list2) {
@@ -5749,7 +5976,7 @@ async function pickRepositories(input) {
5749
5976
  ui.hint(`listed the first ${MAX_PAGES * PER_PAGE} repositories of ${org}`);
5750
5977
  } catch (e) {
5751
5978
  failed = true;
5752
- ui.warn(firstLine4(e instanceof Error ? e.message : String(e)));
5979
+ ui.warn(firstLine5(e instanceof Error ? e.message : String(e)));
5753
5980
  }
5754
5981
  const inCatalogue = (name) => Object.hasOwn(input.catalogue, name);
5755
5982
  const byLabel = (a, b) => a.label.localeCompare(b.label, "en", { sensitivity: "base" });
@@ -5843,7 +6070,7 @@ async function cloneSelection(input) {
5843
6070
  manifest = result.manifest;
5844
6071
  ui.line("declared", `scope ${result.name} (org/${result.name})`);
5845
6072
  } catch (e) {
5846
- const message2 = firstLine4(e instanceof Error ? e.message : String(e));
6073
+ const message2 = firstLine5(e instanceof Error ? e.message : String(e));
5847
6074
  ui.error(`${spec}: ${message2}`);
5848
6075
  failures.push({ spec, message: message2 });
5849
6076
  }
@@ -5938,7 +6165,7 @@ async function publishContext(input) {
5938
6165
  );
5939
6166
  return true;
5940
6167
  } catch (e) {
5941
- ui.warn(firstLine4(e instanceof Error ? e.message : String(e)));
6168
+ ui.warn(firstLine5(e instanceof Error ? e.message : String(e)));
5942
6169
  return false;
5943
6170
  }
5944
6171
  }
@@ -6539,8 +6766,7 @@ async function declaredManager(rnessDir, pm) {
6539
6766
  }
6540
6767
  return `${pm}@${await packageManagerVersion(pm)}`;
6541
6768
  }
6542
- async function clonesToCommit(root, cwd, manifest) {
6543
- const files = writtenFiles(manifest);
6769
+ async function clonesToCommit(root, cwd, manifest, files) {
6544
6770
  const clones = [];
6545
6771
  for (const repo of Object.keys(manifest.repos)) {
6546
6772
  const dir = join23(root, "org", repo);
@@ -6554,6 +6780,27 @@ async function clonesToCommit(root, cwd, manifest) {
6554
6780
  }
6555
6781
  return clones;
6556
6782
  }
6783
+ async function filesTheSyncWrites(root, rnessDir, manifest) {
6784
+ const own = writtenFiles(manifest);
6785
+ const pinned = await readPinnedCli(rnessDir);
6786
+ if (pinned === null) return own;
6787
+ try {
6788
+ const { stdout } = await execFileP6(
6789
+ process.execPath,
6790
+ [join23(pinned.dir, pinned.bin), "written-files", "--cwd", root],
6791
+ {
6792
+ cwd: root,
6793
+ env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
6794
+ timeout: 1e4
6795
+ }
6796
+ );
6797
+ const theirs = JSON.parse(stdout);
6798
+ if (Array.isArray(theirs) && theirs.every((f) => typeof f === "string"))
6799
+ return [.../* @__PURE__ */ new Set([...own, ...theirs])];
6800
+ } catch {
6801
+ }
6802
+ return own;
6803
+ }
6557
6804
  function finish(ui, cwd, rnessDir, label, target2, ending) {
6558
6805
  const rel = relative5(cwd, rnessDir) || ".";
6559
6806
  const message2 = `chore: rness ${target2}`;
@@ -6802,11 +7049,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
6802
7049
  }
6803
7050
  return finish(ui, cwd, ws.rnessDir, label, target2, {
6804
7051
  commit: committed.kind,
6805
- clones: await clonesToCommit(
6806
- ws.root,
6807
- cwd,
6808
- await loadManifest(ws.rnessDir)
6809
- )
7052
+ clones: await (async () => {
7053
+ const manifest = await loadManifest(ws.rnessDir);
7054
+ return clonesToCommit(
7055
+ ws.root,
7056
+ cwd,
7057
+ manifest,
7058
+ await filesTheSyncWrites(ws.root, ws.rnessDir, manifest)
7059
+ );
7060
+ })()
6810
7061
  });
6811
7062
  } catch (e) {
6812
7063
  return reportError(e);
@@ -7672,114 +7923,6 @@ async function writeOpened(file, opened) {
7672
7923
  }
7673
7924
  }
7674
7925
 
7675
- // src/pulse/plan.ts
7676
- 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;
7677
- var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
7678
- var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
7679
- var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
7680
- var firstLine5 = (body) => body.split(/\r\n?|\n/, 1)[0] ?? "";
7681
- function strays(have) {
7682
- const byLine = /* @__PURE__ */ new Map();
7683
- for (const item of have) {
7684
- if (item.path !== null || item.issue === null || item.issue.body === null || !item.issue.labelled)
7685
- continue;
7686
- const line3 = firstLine5(item.issue.body);
7687
- byLine.set(line3, [...byLine.get(line3) ?? [], item]);
7688
- }
7689
- for (const items of byLine.values()) items.sort(keptFirst);
7690
- return byLine;
7691
- }
7692
- function unwantedPaths(want, have) {
7693
- const wanted = new Set(want.map((w) => w.path));
7694
- const paths = /* @__PURE__ */ new Set();
7695
- for (const item of have)
7696
- if (item.path !== null && !wanted.has(item.path)) paths.add(item.path);
7697
- return [...paths];
7698
- }
7699
- function isGone(path, item, seen, opened) {
7700
- return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
7701
- }
7702
- function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
7703
- const byPath = /* @__PURE__ */ new Map();
7704
- for (const item of have)
7705
- if (item.path !== null)
7706
- byPath.set(item.path, [...byPath.get(item.path) ?? [], item]);
7707
- const kept = /* @__PURE__ */ new Map();
7708
- const surplus = [];
7709
- for (const [path, items] of byPath) {
7710
- const [keep, ...rest] = [...items].sort(keptFirst);
7711
- if (keep !== void 0) kept.set(path, keep);
7712
- surplus.push(...rest.map(gone));
7713
- }
7714
- const left = strays(have);
7715
- const steps = [];
7716
- const wanted = /* @__PURE__ */ new Set();
7717
- for (const w of want) {
7718
- wanted.add(w.path);
7719
- const item = kept.get(w.path) ?? left.get(w.body)?.[0];
7720
- if (item === void 0) steps.push({ kind: "create", want: w });
7721
- else if (item.issue === null)
7722
- steps.push({ kind: "convert", id: item.id, want: w });
7723
- else if (item.path === null || !item.issue.open || !item.issue.labelled || differs(item, w))
7724
- steps.push({
7725
- kind: "update",
7726
- id: item.id,
7727
- want: w,
7728
- reopen: !item.issue.open
7729
- });
7730
- else steps.push({ kind: "unchanged", id: item.id });
7731
- }
7732
- for (const [path, item] of kept)
7733
- if (!wanted.has(path))
7734
- steps.push(
7735
- isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
7736
- );
7737
- return [...steps, ...surplus];
7738
- }
7739
- function stillOpened(opened, have, seen, closed, created) {
7740
- const items = /* @__PURE__ */ new Map();
7741
- for (const item of have)
7742
- if (item.issue !== null) items.set(item.issue.number, item.id);
7743
- const next = /* @__PURE__ */ new Map();
7744
- for (const [number, path] of opened) {
7745
- const id = items.get(number);
7746
- if (id !== void 0 && !closed.has(id) && !seen.has(path))
7747
- next.set(number, path);
7748
- }
7749
- for (const [path, placed] of created) next.set(placed.number, path);
7750
- return next;
7751
- }
7752
- function issuedAfter(steps, have, placed) {
7753
- const byId = new Map(have.map((i) => [i.id, i]));
7754
- const issued = /* @__PURE__ */ new Map();
7755
- for (const step2 of steps) {
7756
- if (step2.kind === "create" || step2.kind === "convert") {
7757
- const p = placed.get(step2.want.path);
7758
- if (p !== void 0) issued.set(step2.want.path, { ...p, body: null });
7759
- } else if (step2.kind === "update" || step2.kind === "unchanged") {
7760
- const item = byId.get(step2.id);
7761
- const path = step2.kind === "update" ? step2.want.path : item?.path;
7762
- if (item === void 0 || path == null || item.issue === null) continue;
7763
- issued.set(path, {
7764
- id: item.id,
7765
- number: item.issue.number,
7766
- body: item.issue.body
7767
- });
7768
- }
7769
- }
7770
- return issued;
7771
- }
7772
- function planBodies(bodies, issued) {
7773
- const steps = [];
7774
- for (const [path, body] of bodies) {
7775
- const i = issued.get(path);
7776
- if (i === void 0) continue;
7777
- if (i.body === null || digestOf(i.body) !== digestOf(body))
7778
- steps.push({ kind: "body", id: i.id, body });
7779
- }
7780
- return steps;
7781
- }
7782
-
7783
7926
  // src/commands/pulse.ts
7784
7927
  var PROJECT_SCOPE = "project";
7785
7928
  var NEEDS_LOGIN = "the pulse needs a GitHub login: run rness login";
@@ -7829,7 +7972,9 @@ async function declaredBoard(c) {
7829
7972
  }
7830
7973
  var plural = (n) => `${n} item${n === 1 ? "" : "s"}`;
7831
7974
  var eachAdded = (ui, added) => {
7832
- for (const a of added) ui.line("added", a);
7975
+ for (const a of added)
7976
+ if (a.startsWith("renamed ")) ui.line("renamed", a.slice("renamed ".length));
7977
+ else ui.line("added", a);
7833
7978
  };
7834
7979
  var createdByKind = (ui, added) => {
7835
7980
  for (const kind of ["field", "option", "view"]) {
@@ -7857,10 +8002,15 @@ async function filesOf2(rnessDir) {
7857
8002
  return files;
7858
8003
  }
7859
8004
  var count2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
7860
- function sayDone(ui, org, documents2, steps, writes) {
7861
- const n = (kind) => steps.filter((s) => s.kind === kind).length;
8005
+ function sayDone(ui, org, documents2, steps, writes, adopted) {
8006
+ const n = (kind) => steps.filter((s) => s.kind === kind).length - (kind === "create" ? adopted : 0);
7862
8007
  const reopened = steps.filter((s) => s.kind === "update" && s.reopen).length;
7863
8008
  if (n("create") > 0) ui.line("created", count2(n("create"), "issue"));
8009
+ if (adopted > 0)
8010
+ ui.line(
8011
+ "adopted",
8012
+ `${count2(adopted, "issue")} already made for its document`
8013
+ );
7864
8014
  if (n("convert") > 0)
7865
8015
  ui.line(
7866
8016
  "converted",
@@ -7873,11 +8023,12 @@ function sayDone(ui, org, documents2, steps, writes) {
7873
8023
  ).length;
7874
8024
  const parts = [
7875
8025
  ["created", n("create")],
8026
+ ["adopted", adopted],
7876
8027
  ["converted", n("convert")],
7877
8028
  ["updated", n("update") + bodyOnly],
7878
8029
  [
7879
8030
  "unchanged",
7880
- documents2 - n("create") - n("convert") - n("update") - bodyOnly
8031
+ documents2 - n("create") - adopted - n("convert") - n("update") - bodyOnly
7881
8032
  ],
7882
8033
  ["archived", n("close") + n("archive")]
7883
8034
  ].filter(([, k]) => k > 0).map(([word, k]) => `${k} ${word}`);
@@ -7905,6 +8056,7 @@ async function syncBoard(c, board, ui, sayLayout) {
7905
8056
  const changes = steps.filter((s) => s.kind !== "unchanged").length;
7906
8057
  let writes = [];
7907
8058
  let made = 0;
8059
+ let adopted = 0;
7908
8060
  try {
7909
8061
  const placed = /* @__PURE__ */ new Map();
7910
8062
  const created = /* @__PURE__ */ new Map();
@@ -7914,7 +8066,9 @@ async function syncBoard(c, board, ui, sayLayout) {
7914
8066
  const p = await fromGithub(c.provider.apply(board, step2));
7915
8067
  if (p !== null && (step2.kind === "create" || step2.kind === "convert"))
7916
8068
  placed.set(step2.want.path, p);
7917
- if (p !== null && step2.kind === "create") created.set(step2.want.path, p);
8069
+ if (p?.adopted === true) adopted++;
8070
+ else if (p !== null && step2.kind === "create")
8071
+ created.set(step2.want.path, p);
7918
8072
  if (step2.kind === "close" || step2.kind === "archive")
7919
8073
  closed.add(step2.id);
7920
8074
  if (step2.kind !== "unchanged") made++;
@@ -7953,7 +8107,7 @@ async function syncBoard(c, board, ui, sayLayout) {
7953
8107
  { cause: e }
7954
8108
  );
7955
8109
  }
7956
- sayDone(ui, c.org, want.length, steps, writes);
8110
+ sayDone(ui, c.org, want.length, steps, writes, adopted);
7957
8111
  }
7958
8112
  async function pulseCreateCommand(opts, deps = {}) {
7959
8113
  try {
@@ -8314,6 +8468,21 @@ async function statusCommand(tab, opts, terminal = { input: process.stdin, outpu
8314
8468
  }
8315
8469
  }
8316
8470
 
8471
+ // src/commands/written-files.ts
8472
+ async function writtenFilesCommand(opts) {
8473
+ try {
8474
+ const ws = await findWorkspace(opts.cwd ?? process.cwd());
8475
+ const manifest = await loadManifest(ws.rnessDir);
8476
+ process.stdout.write(`${JSON.stringify(writtenFiles(manifest))}
8477
+ `);
8478
+ return 0;
8479
+ } catch (e) {
8480
+ process.stderr.write(`${e instanceof Error ? e.message : String(e)}
8481
+ `);
8482
+ return 1;
8483
+ }
8484
+ }
8485
+
8317
8486
  // src/cli.ts
8318
8487
  function buildProgram(state) {
8319
8488
  const program = new Command().name("rness").description("Configuration-plane CLI for rness workspaces").version(VERSION, "-v, --version", "print the version").exitOverride().showHelpAfterError().configureOutput({
@@ -8405,6 +8574,9 @@ function buildProgram(state) {
8405
8574
  program.command("git-credential", { hidden: true }).argument("<operation>").action(async (operation) => {
8406
8575
  state.code = await gitCredentialCommand(operation);
8407
8576
  });
8577
+ program.command("written-files", { hidden: true }).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
8578
+ state.code = await writtenFilesCommand(opts);
8579
+ });
8408
8580
  program.command("hook", { hidden: true }).argument("<event>", "session-start, post-tool-use or session-end").action(async (event) => {
8409
8581
  state.code = await hookCommand(event);
8410
8582
  });
@@ -8594,4 +8766,4 @@ export {
8594
8766
  OWN_COMMANDS,
8595
8767
  catchUp
8596
8768
  };
8597
- //# sourceMappingURL=chunk-HGT7BIWK.js.map
8769
+ //# sourceMappingURL=chunk-ABCO6D6B.js.map