@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.
- package/README.md +55 -11
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-HGT7BIWK.js → chunk-ABCO6D6B.js} +325 -153
- package/dist/chunk-ABCO6D6B.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-HGT7BIWK.js.map +0 -1
|
@@ -486,7 +486,7 @@ function parseRepoSpec(spec, org, host) {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
// src/version.ts
|
|
489
|
-
var VERSION = "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
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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(
|
|
2678
|
+
const session = await text(WORKING_SESSION);
|
|
2501
2679
|
const path = await text("Path");
|
|
2502
|
-
const sessions = await text(
|
|
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
|
|
2605
|
-
sessions: r.values
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
[
|
|
2938
|
+
[SESSION_HISTORY, want.sessions]
|
|
2721
2939
|
];
|
|
2722
2940
|
for (const [name, value] of wanted)
|
|
2723
|
-
if ((values2
|
|
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,
|
|
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
|
|
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:
|
|
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
|
|
3499
|
-
unless
|
|
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
|
|
3538
|
-
unless
|
|
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
|
|
3580
|
-
unless
|
|
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
|
|
3611
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
6806
|
-
ws.
|
|
6807
|
-
|
|
6808
|
-
|
|
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)
|
|
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
|
|
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-
|
|
8769
|
+
//# sourceMappingURL=chunk-ABCO6D6B.js.map
|