@rness/cli 0.15.1 → 0.17.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 +82 -16
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-7IVHL744.js → chunk-X52SHBRY.js} +1069 -237
- package/dist/chunk-X52SHBRY.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-7IVHL744.js.map +0 -1
|
@@ -292,6 +292,7 @@ var KEYS = [
|
|
|
292
292
|
"org",
|
|
293
293
|
"agents",
|
|
294
294
|
"pulse",
|
|
295
|
+
"projects",
|
|
295
296
|
"repos",
|
|
296
297
|
"scopes"
|
|
297
298
|
];
|
|
@@ -352,7 +353,26 @@ function readPulse(value) {
|
|
|
352
353
|
fail(
|
|
353
354
|
`"pulse" must be { "project": <number> } (got ${JSON.stringify(value)})`
|
|
354
355
|
);
|
|
355
|
-
return {
|
|
356
|
+
return { [PULSE]: value.project };
|
|
357
|
+
}
|
|
358
|
+
var PULSE = "pulse";
|
|
359
|
+
var PROJECT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
360
|
+
function readProjects(value) {
|
|
361
|
+
if (value === void 0) return null;
|
|
362
|
+
const entries = isRecord(value) ? Object.entries(value) : [];
|
|
363
|
+
const numbers = entries.map(([, n]) => n);
|
|
364
|
+
if (entries.length === 0 || !entries.every(
|
|
365
|
+
([name, n]) => PROJECT_NAME.test(name) && typeof n === "number" && Number.isInteger(n) && n >= 1
|
|
366
|
+
) || new Set(numbers).size !== numbers.length)
|
|
367
|
+
fail(
|
|
368
|
+
`"projects" must map names to distinct project numbers, as { "pulse": 4, "marketing": 5 } (got ${JSON.stringify(value)})`
|
|
369
|
+
);
|
|
370
|
+
return Object.fromEntries(entries);
|
|
371
|
+
}
|
|
372
|
+
function readProjectsOrPulse(data) {
|
|
373
|
+
if (data.pulse !== void 0 && data.projects !== void 0)
|
|
374
|
+
fail('"pulse" and "projects" at once: keep "projects" only');
|
|
375
|
+
return data.projects !== void 0 ? readProjects(data.projects) : readPulse(data.pulse);
|
|
356
376
|
}
|
|
357
377
|
function providerOf(manifest) {
|
|
358
378
|
if (manifest.provider !== null) return manifest.provider;
|
|
@@ -421,7 +441,7 @@ async function loadManifest(rnessDir) {
|
|
|
421
441
|
provider: readProvider(data.provider),
|
|
422
442
|
org: readOrg(data.org),
|
|
423
443
|
agents: readAgents(data.agents),
|
|
424
|
-
|
|
444
|
+
projects: readProjectsOrPulse(data),
|
|
425
445
|
repos: readRepos(data.repos),
|
|
426
446
|
scopes: readScopes(data.scopes)
|
|
427
447
|
};
|
|
@@ -443,8 +463,10 @@ async function writeManifest(rnessDir, manifest) {
|
|
|
443
463
|
lines.push(
|
|
444
464
|
` "agents": [${manifest.agents.map((a) => JSON.stringify(a)).join(", ")}],`
|
|
445
465
|
);
|
|
446
|
-
if (manifest.
|
|
447
|
-
lines.push(
|
|
466
|
+
if (manifest.projects !== null)
|
|
467
|
+
lines.push(
|
|
468
|
+
` "projects": { ${Object.entries(manifest.projects).map(([name, n]) => `${JSON.stringify(name)}: ${n}`).join(", ")} },`
|
|
469
|
+
);
|
|
448
470
|
lines.push(
|
|
449
471
|
' "repos": {',
|
|
450
472
|
repos.join(",\n"),
|
|
@@ -486,7 +508,7 @@ function parseRepoSpec(spec, org, host) {
|
|
|
486
508
|
}
|
|
487
509
|
|
|
488
510
|
// src/version.ts
|
|
489
|
-
var VERSION = "0.
|
|
511
|
+
var VERSION = "0.17.0";
|
|
490
512
|
|
|
491
513
|
// src/core/pm.ts
|
|
492
514
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -1073,10 +1095,17 @@ function statusOf(item) {
|
|
|
1073
1095
|
function sessionsOf(item) {
|
|
1074
1096
|
const listed = item.fields?.["sessions"];
|
|
1075
1097
|
if (!Array.isArray(listed)) return [];
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1098
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const entry of listed) {
|
|
1100
|
+
const fields2 = typeof entry === "string" ? { id: entry } : entry !== null && typeof entry === "object" ? entry : {};
|
|
1101
|
+
const { id, agent } = fields2;
|
|
1102
|
+
if (typeof id !== "string" || id === "" || entries.has(id)) continue;
|
|
1103
|
+
entries.set(
|
|
1104
|
+
id,
|
|
1105
|
+
typeof agent === "string" && agent !== "" ? { id, agent } : { id }
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
return [...entries.values()];
|
|
1080
1109
|
}
|
|
1081
1110
|
function rowOf(dir, item) {
|
|
1082
1111
|
const base = posix3.basename(item.rel, ".md");
|
|
@@ -1314,6 +1343,196 @@ ${content}`;
|
|
|
1314
1343
|
${comment(digest(text))}`;
|
|
1315
1344
|
}
|
|
1316
1345
|
|
|
1346
|
+
// src/pulse/collection.ts
|
|
1347
|
+
import { readFile as readFile5, readdir as readdir2 } from "fs/promises";
|
|
1348
|
+
import { join as join8 } from "path";
|
|
1349
|
+
var FIELD_TYPES = ["text", "date", "select", "number"];
|
|
1350
|
+
var TAKEN = new Set(
|
|
1351
|
+
[
|
|
1352
|
+
"Status",
|
|
1353
|
+
"Collection",
|
|
1354
|
+
"Agent",
|
|
1355
|
+
"Working session",
|
|
1356
|
+
"Path",
|
|
1357
|
+
"Session history",
|
|
1358
|
+
"Title",
|
|
1359
|
+
"Assignees",
|
|
1360
|
+
"Labels",
|
|
1361
|
+
"Linked pull requests",
|
|
1362
|
+
"Milestone",
|
|
1363
|
+
"Repository",
|
|
1364
|
+
"Reviewers",
|
|
1365
|
+
"Type",
|
|
1366
|
+
"Parent issue",
|
|
1367
|
+
"Sub-issues progress"
|
|
1368
|
+
].map((n) => n.toLowerCase())
|
|
1369
|
+
);
|
|
1370
|
+
var isRecord2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1371
|
+
var isNames = (v) => Array.isArray(v) && v.every((s) => typeof s === "string" && s.trim() !== "") && new Set(v).size === v.length;
|
|
1372
|
+
async function readText(path) {
|
|
1373
|
+
try {
|
|
1374
|
+
return await readFile5(path, "utf8");
|
|
1375
|
+
} catch (e) {
|
|
1376
|
+
if (e.code === "ENOENT") return null;
|
|
1377
|
+
throw e;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function readField(where, name, value) {
|
|
1381
|
+
const key = `fields.${name}`;
|
|
1382
|
+
if (TAKEN.has(name.toLowerCase()))
|
|
1383
|
+
throw new Error(`${where}: "${key}" is a field of rness: name it otherwise`);
|
|
1384
|
+
if (!isRecord2(value))
|
|
1385
|
+
throw new Error(`${where}: "fields" must map names to { type, from }`);
|
|
1386
|
+
const type = FIELD_TYPES.find((t) => t === value["type"]);
|
|
1387
|
+
if (type === void 0)
|
|
1388
|
+
throw new Error(
|
|
1389
|
+
`${where}: "${key}.type" must be one of ${FIELD_TYPES.join(", ")}`
|
|
1390
|
+
);
|
|
1391
|
+
const raw = value["from"];
|
|
1392
|
+
const from = typeof raw === "string" && raw !== "" ? [raw] : raw;
|
|
1393
|
+
if (!isNames(from) || from.length === 0)
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
`${where}: "${key}.from" must be a front-matter key or a list of them`
|
|
1396
|
+
);
|
|
1397
|
+
const options = value["options"] ?? [];
|
|
1398
|
+
if (!isNames(options) && !(Array.isArray(options) && options.length === 0))
|
|
1399
|
+
throw new Error(`${where}: "${key}.options" must be a list of names`);
|
|
1400
|
+
return { name, type, from, options: [...options] };
|
|
1401
|
+
}
|
|
1402
|
+
function readLabels(where, value) {
|
|
1403
|
+
if (value === void 0 || value === null) return null;
|
|
1404
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`${where}: "labels" must be directory or a front-matter key`
|
|
1407
|
+
);
|
|
1408
|
+
return value === "directory" ? { kind: "directory" } : { kind: "key", key: value };
|
|
1409
|
+
}
|
|
1410
|
+
async function readShape(rnessDir, collection) {
|
|
1411
|
+
const where = `${collection}/README.md`;
|
|
1412
|
+
const text = await readText(join8(rnessDir, collection, "README.md"));
|
|
1413
|
+
if (text === null)
|
|
1414
|
+
return {
|
|
1415
|
+
readme: null,
|
|
1416
|
+
description: null,
|
|
1417
|
+
statuses: null,
|
|
1418
|
+
fields: [],
|
|
1419
|
+
labels: null
|
|
1420
|
+
};
|
|
1421
|
+
let front;
|
|
1422
|
+
try {
|
|
1423
|
+
front = parseFrontMatter(text);
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
throw new Error(`${where}: ${e instanceof Error ? e.message : String(e)}`, {
|
|
1426
|
+
cause: e
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
const f = front ?? {};
|
|
1430
|
+
const description = f["description"] ?? null;
|
|
1431
|
+
if (description !== null && typeof description !== "string")
|
|
1432
|
+
throw new Error(`${where}: "description" must be text`);
|
|
1433
|
+
const statuses = f["statuses"] ?? null;
|
|
1434
|
+
if (statuses !== null && !isNames(statuses))
|
|
1435
|
+
throw new Error(`${where}: "statuses" must be a list of names`);
|
|
1436
|
+
const fields2 = f["fields"] ?? {};
|
|
1437
|
+
if (!isRecord2(fields2))
|
|
1438
|
+
throw new Error(`${where}: "fields" must map names to { type, from }`);
|
|
1439
|
+
return {
|
|
1440
|
+
readme: stripFrontMatter(text),
|
|
1441
|
+
description: description === null ? null : description.trim(),
|
|
1442
|
+
statuses: statuses === null ? null : [...statuses],
|
|
1443
|
+
fields: Object.entries(fields2).map(
|
|
1444
|
+
([name, v]) => readField(where, name, v)
|
|
1445
|
+
),
|
|
1446
|
+
labels: readLabels(where, f["labels"])
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
function dayOf(value) {
|
|
1450
|
+
if (value instanceof Date && !Number.isNaN(value.getTime()))
|
|
1451
|
+
return value.toISOString().slice(0, 10);
|
|
1452
|
+
if (typeof value !== "string") return null;
|
|
1453
|
+
const day = /^(\d{4}-\d{2}-\d{2})(?:$|[T ])/.exec(value.trim())?.[1];
|
|
1454
|
+
return day ?? null;
|
|
1455
|
+
}
|
|
1456
|
+
function valueOf(field, fields2) {
|
|
1457
|
+
const key = field.from.find(
|
|
1458
|
+
(k) => fields2[k] !== void 0 && fields2[k] !== null && fields2[k] !== ""
|
|
1459
|
+
);
|
|
1460
|
+
if (key === void 0) return null;
|
|
1461
|
+
const raw = fields2[key];
|
|
1462
|
+
switch (field.type) {
|
|
1463
|
+
case "date":
|
|
1464
|
+
return dayOf(raw);
|
|
1465
|
+
case "number": {
|
|
1466
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
1467
|
+
return typeof raw !== "boolean" && Number.isFinite(n) ? String(n) : null;
|
|
1468
|
+
}
|
|
1469
|
+
default:
|
|
1470
|
+
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
1471
|
+
return typeof raw === "object" ? null : String(raw);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
function valuesOf(shape, fields2, rel) {
|
|
1475
|
+
const values2 = Object.fromEntries(
|
|
1476
|
+
shape.fields.map((f) => [f.name, valueOf(f, fields2)])
|
|
1477
|
+
);
|
|
1478
|
+
const labels = [];
|
|
1479
|
+
if (shape.labels?.kind === "directory") {
|
|
1480
|
+
const slash = rel.indexOf("/");
|
|
1481
|
+
if (slash > 0) labels.push(rel.slice(0, slash));
|
|
1482
|
+
} else if (shape.labels?.kind === "key") {
|
|
1483
|
+
const raw = fields2[shape.labels.key];
|
|
1484
|
+
for (const v of Array.isArray(raw) ? raw : [raw])
|
|
1485
|
+
if (typeof v === "string" && v.trim() !== "" && !labels.includes(v))
|
|
1486
|
+
labels.push(v.trim());
|
|
1487
|
+
}
|
|
1488
|
+
return { values: values2, labels };
|
|
1489
|
+
}
|
|
1490
|
+
var HEALTHS = [
|
|
1491
|
+
"on-track",
|
|
1492
|
+
"at-risk",
|
|
1493
|
+
"off-track",
|
|
1494
|
+
"complete",
|
|
1495
|
+
"inactive"
|
|
1496
|
+
];
|
|
1497
|
+
async function readUpdates(rnessDir, collection) {
|
|
1498
|
+
let names;
|
|
1499
|
+
try {
|
|
1500
|
+
names = (await readdir2(join8(rnessDir, collection, "updates"), {
|
|
1501
|
+
withFileTypes: true
|
|
1502
|
+
})).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name).sort();
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
if (e.code === "ENOENT") return [];
|
|
1505
|
+
throw e;
|
|
1506
|
+
}
|
|
1507
|
+
const updates = [];
|
|
1508
|
+
for (const name of names) {
|
|
1509
|
+
const path = `${collection}/updates/${name}`;
|
|
1510
|
+
const text = await readText(join8(rnessDir, collection, "updates", name)) ?? "";
|
|
1511
|
+
let front;
|
|
1512
|
+
try {
|
|
1513
|
+
front = parseFrontMatter(text);
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
throw new Error(
|
|
1516
|
+
`${path}: ${e instanceof Error ? e.message : String(e)}`,
|
|
1517
|
+
{
|
|
1518
|
+
cause: e
|
|
1519
|
+
}
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
const health = HEALTHS.find((h) => h === front?.["health"]);
|
|
1523
|
+
if (health === void 0)
|
|
1524
|
+
throw new Error(`${path}: "health" must be one of ${HEALTHS.join(", ")}`);
|
|
1525
|
+
updates.push({
|
|
1526
|
+
path,
|
|
1527
|
+
health,
|
|
1528
|
+
startDate: dayOf(front?.["start_date"]),
|
|
1529
|
+
targetDate: dayOf(front?.["target_date"]),
|
|
1530
|
+
body: stripFrontMatter(text)
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return updates;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1317
1536
|
// src/pulse/layout.ts
|
|
1318
1537
|
var statusOf2 = (status2) => status2 === null || status2 === "?" ? null : status2;
|
|
1319
1538
|
var LIFECYCLE = [
|
|
@@ -1387,36 +1606,90 @@ function fieldsOf(tabs) {
|
|
|
1387
1606
|
}
|
|
1388
1607
|
return fields2;
|
|
1389
1608
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1609
|
+
var once = (values2) => [...new Set(values2)];
|
|
1610
|
+
function layoutOf(tabs, own) {
|
|
1611
|
+
if (own === void 0) {
|
|
1612
|
+
const fields2 = fieldsOf(tabs);
|
|
1613
|
+
return {
|
|
1614
|
+
statuses: inLifecycleOrder(
|
|
1615
|
+
[...fields2.values()].flatMap((f) => f.statuses)
|
|
1616
|
+
),
|
|
1617
|
+
fields: [...fields2.values()],
|
|
1618
|
+
types: tabs.map((t) => t.label),
|
|
1619
|
+
views: tabs.map((t) => ({
|
|
1620
|
+
name: t.label,
|
|
1621
|
+
type: t.label,
|
|
1622
|
+
field: fields2.get(t.label)?.name ?? null
|
|
1623
|
+
})),
|
|
1624
|
+
declared: [],
|
|
1625
|
+
labels: []
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
const { shape, want } = own;
|
|
1392
1629
|
return {
|
|
1393
|
-
statuses:
|
|
1394
|
-
|
|
1630
|
+
statuses: once([
|
|
1631
|
+
...shape.statuses ?? [],
|
|
1632
|
+
...tabs.flatMap(collectionStatuses)
|
|
1633
|
+
]),
|
|
1634
|
+
fields: [],
|
|
1395
1635
|
types: tabs.map((t) => t.label),
|
|
1396
|
-
views: tabs.map((t) => ({
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1636
|
+
views: tabs.map((t) => ({ name: t.label, type: t.label, field: null })),
|
|
1637
|
+
declared: shape.fields.map((f) => ({
|
|
1638
|
+
name: f.name,
|
|
1639
|
+
type: f.type,
|
|
1640
|
+
options: f.type === "select" ? once([
|
|
1641
|
+
...f.options,
|
|
1642
|
+
...want.map((w) => w.values[f.name]).filter((v) => typeof v === "string")
|
|
1643
|
+
]) : []
|
|
1644
|
+
})),
|
|
1645
|
+
labels: once(want.flatMap((w) => [...w.labels, ...w.unlabels]))
|
|
1401
1646
|
};
|
|
1402
1647
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1648
|
+
var sessionsOf2 = (row) => row.sessions?.map((e) => e.agent === void 0 ? e.id : `${e.agent} \xB7 ${e.id}`).join(", ") ?? null;
|
|
1649
|
+
function desiredOf(tabs, org, own) {
|
|
1650
|
+
const fields2 = own === void 0 ? fieldsOf(tabs) : /* @__PURE__ */ new Map();
|
|
1651
|
+
const wanted = tabs.flatMap(
|
|
1652
|
+
(tab) => tab.rows.map((row) => {
|
|
1653
|
+
const { values: values2, labels } = own === void 0 ? { values: {}, labels: [] } : valuesOf(
|
|
1654
|
+
own.shape,
|
|
1655
|
+
own.fronts.get(row.path) ?? {},
|
|
1656
|
+
row.path.slice(tab.name.length + 1)
|
|
1657
|
+
);
|
|
1658
|
+
return {
|
|
1659
|
+
path: row.path,
|
|
1660
|
+
title: `${row.id} \u2014 ${row.title}`,
|
|
1661
|
+
body: headerOf(row.path, org),
|
|
1662
|
+
status: statusOf2(row.status),
|
|
1663
|
+
type: tab.label,
|
|
1664
|
+
statusField: fields2.get(tab.label)?.name ?? null,
|
|
1665
|
+
sessions: sessionsOf2(row),
|
|
1666
|
+
values: values2,
|
|
1667
|
+
labels,
|
|
1668
|
+
unlabels: []
|
|
1669
|
+
};
|
|
1670
|
+
})
|
|
1415
1671
|
);
|
|
1672
|
+
if (own === void 0) return wanted;
|
|
1673
|
+
const made = once([
|
|
1674
|
+
...wanted.flatMap((w) => w.labels),
|
|
1675
|
+
...own.shape.labels?.kind === "directory" ? own.directories ?? [] : []
|
|
1676
|
+
]);
|
|
1677
|
+
return wanted.map((w) => ({
|
|
1678
|
+
...w,
|
|
1679
|
+
unlabels: made.filter((l) => !w.labels.includes(l))
|
|
1680
|
+
}));
|
|
1416
1681
|
}
|
|
1417
1682
|
|
|
1418
1683
|
// src/pulse/plan.ts
|
|
1419
|
-
var
|
|
1684
|
+
var lower = (labels) => new Set(labels.map((l) => l.toLowerCase()));
|
|
1685
|
+
function labelsDiffer(item, w) {
|
|
1686
|
+
if (w.labels.length === 0 && w.unlabels.length === 0) return false;
|
|
1687
|
+
const carried = lower(item.issue?.labels ?? []);
|
|
1688
|
+
return w.labels.some((l) => !carried.has(l.toLowerCase())) || w.unlabels.some((l) => carried.has(l.toLowerCase()));
|
|
1689
|
+
}
|
|
1690
|
+
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 || Object.entries(w.values).some(
|
|
1691
|
+
([name, value]) => (item.values[name] ?? null) !== value
|
|
1692
|
+
) || labelsDiffer(item, w);
|
|
1420
1693
|
var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
|
|
1421
1694
|
var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
|
|
1422
1695
|
var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
|
|
@@ -1442,7 +1715,7 @@ function unwantedPaths(want, have) {
|
|
|
1442
1715
|
function isGone(path, item, seen, opened) {
|
|
1443
1716
|
return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
|
|
1444
1717
|
}
|
|
1445
|
-
function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
|
|
1718
|
+
function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map(), elsewhere = () => false) {
|
|
1446
1719
|
const byPath = /* @__PURE__ */ new Map();
|
|
1447
1720
|
for (const item of have)
|
|
1448
1721
|
if (item.path !== null)
|
|
@@ -1475,7 +1748,8 @@ function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__P
|
|
|
1475
1748
|
for (const [path, item] of kept)
|
|
1476
1749
|
if (!wanted.has(path))
|
|
1477
1750
|
steps.push(
|
|
1478
|
-
|
|
1751
|
+
// Its own project holds it now: off this board, not closed.
|
|
1752
|
+
elsewhere(path) ? { kind: "remove", id: item.id } : isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
|
|
1479
1753
|
);
|
|
1480
1754
|
return [...steps, ...surplus];
|
|
1481
1755
|
}
|
|
@@ -1836,11 +2110,11 @@ async function createRepository(owner, name, options) {
|
|
|
1836
2110
|
}
|
|
1837
2111
|
|
|
1838
2112
|
// src/core/github-projects.ts
|
|
1839
|
-
function
|
|
2113
|
+
function isRecord3(v) {
|
|
1840
2114
|
return v !== null && typeof v === "object";
|
|
1841
2115
|
}
|
|
1842
|
-
var rateLimited = (body) =>
|
|
1843
|
-
(e) =>
|
|
2116
|
+
var rateLimited = (body) => isRecord3(body) && Array.isArray(body["errors"]) && body["errors"].some(
|
|
2117
|
+
(e) => isRecord3(e) && e["type"] === "RATE_LIMITED"
|
|
1844
2118
|
);
|
|
1845
2119
|
async function graphqlRaw(query, variables, options) {
|
|
1846
2120
|
const body = await postJson(
|
|
@@ -1850,7 +2124,7 @@ async function graphqlRaw(query, variables, options) {
|
|
|
1850
2124
|
true,
|
|
1851
2125
|
rateLimited
|
|
1852
2126
|
);
|
|
1853
|
-
return
|
|
2127
|
+
return isRecord3(body) ? body : {};
|
|
1854
2128
|
}
|
|
1855
2129
|
function firstError(answer) {
|
|
1856
2130
|
const first = answer.errors?.[0];
|
|
@@ -1926,6 +2200,7 @@ function toField(raw) {
|
|
|
1926
2200
|
id: raw.id ?? "",
|
|
1927
2201
|
databaseId,
|
|
1928
2202
|
name: raw.name ?? "",
|
|
2203
|
+
dataType: raw.dataType ?? (raw.options === void 0 ? "TEXT" : "SINGLE_SELECT"),
|
|
1929
2204
|
options: raw.options ?? null
|
|
1930
2205
|
};
|
|
1931
2206
|
}
|
|
@@ -1955,10 +2230,10 @@ var optionInput = (o, id) => ({
|
|
|
1955
2230
|
color: o.color,
|
|
1956
2231
|
description: ""
|
|
1957
2232
|
});
|
|
1958
|
-
var FIELD_SELECTION = `... on ProjectV2FieldCommon { id databaseId name }
|
|
2233
|
+
var FIELD_SELECTION = `... on ProjectV2FieldCommon { id databaseId name dataType }
|
|
1959
2234
|
... on ProjectV2SingleSelectField { options { id name color } }`;
|
|
1960
2235
|
async function createField(projectId, name, kind, o) {
|
|
1961
|
-
const single = kind !== "
|
|
2236
|
+
const single = typeof kind !== "string";
|
|
1962
2237
|
const d = await graphql(
|
|
1963
2238
|
`mutation($projectId: ID!, $name: String!, $dataType: ProjectV2CustomFieldType!${single ? ", $options: [ProjectV2SingleSelectFieldOptionInput!]" : ""}) {
|
|
1964
2239
|
createProjectV2Field(input: {
|
|
@@ -1968,7 +2243,7 @@ async function createField(projectId, name, kind, o) {
|
|
|
1968
2243
|
{
|
|
1969
2244
|
projectId,
|
|
1970
2245
|
name,
|
|
1971
|
-
dataType: single ? "SINGLE_SELECT" :
|
|
2246
|
+
dataType: single ? "SINGLE_SELECT" : kind,
|
|
1972
2247
|
...single ? { options: kind.options.map((n) => optionInput(n)) } : {}
|
|
1973
2248
|
},
|
|
1974
2249
|
o
|
|
@@ -1991,6 +2266,18 @@ async function setOptions(field, options, o) {
|
|
|
1991
2266
|
);
|
|
1992
2267
|
return toField(d.updateProjectV2Field.projectV2Field);
|
|
1993
2268
|
}
|
|
2269
|
+
async function renameField(fieldId, name, o) {
|
|
2270
|
+
const d = await graphql(
|
|
2271
|
+
`mutation($fieldId: ID!, $name: String!) {
|
|
2272
|
+
updateProjectV2Field(input: { fieldId: $fieldId, name: $name }) {
|
|
2273
|
+
projectV2Field { ${FIELD_SELECTION} }
|
|
2274
|
+
}
|
|
2275
|
+
}`,
|
|
2276
|
+
{ fieldId, name },
|
|
2277
|
+
o
|
|
2278
|
+
);
|
|
2279
|
+
return toField(d.updateProjectV2Field.projectV2Field);
|
|
2280
|
+
}
|
|
1994
2281
|
async function listItems(projectId, o, { bodies = true } = {}) {
|
|
1995
2282
|
const items = [];
|
|
1996
2283
|
let cursor = null;
|
|
@@ -2043,6 +2330,22 @@ async function listItems(projectId, o, { bodies = true } = {}) {
|
|
|
2043
2330
|
}
|
|
2044
2331
|
}
|
|
2045
2332
|
}
|
|
2333
|
+
... on ProjectV2ItemFieldDateValue {
|
|
2334
|
+
date
|
|
2335
|
+
field {
|
|
2336
|
+
... on ProjectV2FieldCommon {
|
|
2337
|
+
name
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
2342
|
+
number
|
|
2343
|
+
field {
|
|
2344
|
+
... on ProjectV2FieldCommon {
|
|
2345
|
+
name
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2046
2349
|
}
|
|
2047
2350
|
}
|
|
2048
2351
|
}
|
|
@@ -2062,7 +2365,7 @@ async function listItems(projectId, o, { bodies = true } = {}) {
|
|
|
2062
2365
|
const values2 = {};
|
|
2063
2366
|
for (const v of n.fieldValues?.nodes ?? []) {
|
|
2064
2367
|
const field = v.field?.name;
|
|
2065
|
-
const value = v.text ?? v.name;
|
|
2368
|
+
const value = v.text ?? v.name ?? v.date ?? (v.number === void 0 ? void 0 : String(v.number));
|
|
2066
2369
|
if (field !== void 0 && value !== void 0) values2[field] = value;
|
|
2067
2370
|
}
|
|
2068
2371
|
const c = n.content;
|
|
@@ -2133,7 +2436,7 @@ async function setValue(projectId, itemId, fieldId, value, o) {
|
|
|
2133
2436
|
projectId,
|
|
2134
2437
|
itemId,
|
|
2135
2438
|
fieldId,
|
|
2136
|
-
value: "
|
|
2439
|
+
value: "optionId" in value ? { singleSelectOptionId: value.optionId } : value
|
|
2137
2440
|
},
|
|
2138
2441
|
o
|
|
2139
2442
|
);
|
|
@@ -2332,6 +2635,142 @@ async function renameView(viewId, name, visibleFieldIds, o) {
|
|
|
2332
2635
|
o
|
|
2333
2636
|
);
|
|
2334
2637
|
}
|
|
2638
|
+
async function deleteItem(projectId, itemId, o) {
|
|
2639
|
+
await graphql(
|
|
2640
|
+
`
|
|
2641
|
+
mutation ($projectId: ID!, $itemId: ID!) {
|
|
2642
|
+
deleteProjectV2Item(input: { projectId: $projectId, itemId: $itemId }) {
|
|
2643
|
+
deletedItemId
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
`,
|
|
2647
|
+
{ projectId, itemId },
|
|
2648
|
+
o
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
async function projectText(projectId, o) {
|
|
2652
|
+
const d = await graphql(
|
|
2653
|
+
`
|
|
2654
|
+
query ($projectId: ID!) {
|
|
2655
|
+
node(id: $projectId) {
|
|
2656
|
+
... on ProjectV2 {
|
|
2657
|
+
projectReadme: readme
|
|
2658
|
+
projectDescription: shortDescription
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
`,
|
|
2663
|
+
{ projectId },
|
|
2664
|
+
o
|
|
2665
|
+
);
|
|
2666
|
+
return {
|
|
2667
|
+
readme: d.node?.projectReadme ?? "",
|
|
2668
|
+
shortDescription: d.node?.projectDescription ?? ""
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
async function updateProjectText(projectId, text, o) {
|
|
2672
|
+
await graphql(
|
|
2673
|
+
`
|
|
2674
|
+
mutation ($projectId: ID!, $readme: String, $shortDescription: String) {
|
|
2675
|
+
updateProjectV2(
|
|
2676
|
+
input: {
|
|
2677
|
+
projectId: $projectId
|
|
2678
|
+
readme: $readme
|
|
2679
|
+
shortDescription: $shortDescription
|
|
2680
|
+
}
|
|
2681
|
+
) {
|
|
2682
|
+
projectV2 {
|
|
2683
|
+
id
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
`,
|
|
2688
|
+
{ projectId, ...text },
|
|
2689
|
+
o
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
async function statusUpdates(projectId, o) {
|
|
2693
|
+
const d = await graphql(
|
|
2694
|
+
`
|
|
2695
|
+
query ($projectId: ID!) {
|
|
2696
|
+
node(id: $projectId) {
|
|
2697
|
+
... on ProjectV2 {
|
|
2698
|
+
statusUpdates(first: 100) {
|
|
2699
|
+
nodes {
|
|
2700
|
+
id
|
|
2701
|
+
body
|
|
2702
|
+
status
|
|
2703
|
+
startDate
|
|
2704
|
+
targetDate
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
`,
|
|
2711
|
+
{ projectId },
|
|
2712
|
+
o
|
|
2713
|
+
);
|
|
2714
|
+
return d.node?.statusUpdates.nodes ?? [];
|
|
2715
|
+
}
|
|
2716
|
+
async function createStatusUpdate(projectId, update, o) {
|
|
2717
|
+
await graphql(
|
|
2718
|
+
`
|
|
2719
|
+
mutation (
|
|
2720
|
+
$projectId: ID!
|
|
2721
|
+
$body: String!
|
|
2722
|
+
$status: ProjectV2StatusUpdateStatus!
|
|
2723
|
+
$startDate: Date
|
|
2724
|
+
$targetDate: Date
|
|
2725
|
+
) {
|
|
2726
|
+
createProjectV2StatusUpdate(
|
|
2727
|
+
input: {
|
|
2728
|
+
projectId: $projectId
|
|
2729
|
+
body: $body
|
|
2730
|
+
status: $status
|
|
2731
|
+
startDate: $startDate
|
|
2732
|
+
targetDate: $targetDate
|
|
2733
|
+
}
|
|
2734
|
+
) {
|
|
2735
|
+
statusUpdate {
|
|
2736
|
+
id
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
`,
|
|
2741
|
+
{ projectId, ...update },
|
|
2742
|
+
o
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
async function updateStatusUpdate(statusUpdateId, update, o) {
|
|
2746
|
+
await graphql(
|
|
2747
|
+
`
|
|
2748
|
+
mutation (
|
|
2749
|
+
$statusUpdateId: ID!
|
|
2750
|
+
$body: String!
|
|
2751
|
+
$status: ProjectV2StatusUpdateStatus!
|
|
2752
|
+
$startDate: Date
|
|
2753
|
+
$targetDate: Date
|
|
2754
|
+
) {
|
|
2755
|
+
updateProjectV2StatusUpdate(
|
|
2756
|
+
input: {
|
|
2757
|
+
statusUpdateId: $statusUpdateId
|
|
2758
|
+
body: $body
|
|
2759
|
+
status: $status
|
|
2760
|
+
startDate: $startDate
|
|
2761
|
+
targetDate: $targetDate
|
|
2762
|
+
}
|
|
2763
|
+
) {
|
|
2764
|
+
statusUpdate {
|
|
2765
|
+
id
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
`,
|
|
2770
|
+
{ statusUpdateId, ...update },
|
|
2771
|
+
o
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2335
2774
|
|
|
2336
2775
|
// src/core/github-issues.ts
|
|
2337
2776
|
async function repository(owner, name, label, o) {
|
|
@@ -2470,7 +2909,7 @@ async function reopenIssue(id, o) {
|
|
|
2470
2909
|
o
|
|
2471
2910
|
);
|
|
2472
2911
|
}
|
|
2473
|
-
async function addLabels(id,
|
|
2912
|
+
async function addLabels(id, labelIds2, o) {
|
|
2474
2913
|
await graphql(
|
|
2475
2914
|
`
|
|
2476
2915
|
mutation ($id: ID!, $labelIds: [ID!]!) {
|
|
@@ -2479,7 +2918,53 @@ async function addLabels(id, labelIds, o) {
|
|
|
2479
2918
|
}
|
|
2480
2919
|
}
|
|
2481
2920
|
`,
|
|
2482
|
-
{ id, labelIds },
|
|
2921
|
+
{ id, labelIds: labelIds2 },
|
|
2922
|
+
o
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2925
|
+
async function labelIds(owner, name, o) {
|
|
2926
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2927
|
+
let after = null;
|
|
2928
|
+
for (; ; ) {
|
|
2929
|
+
const d = await graphql(
|
|
2930
|
+
`
|
|
2931
|
+
query ($owner: String!, $name: String!, $after: String) {
|
|
2932
|
+
repository(owner: $owner, name: $name) {
|
|
2933
|
+
labelList: labels(first: 100, after: $after) {
|
|
2934
|
+
nodes {
|
|
2935
|
+
id
|
|
2936
|
+
name
|
|
2937
|
+
}
|
|
2938
|
+
pageInfo {
|
|
2939
|
+
hasNextPage
|
|
2940
|
+
endCursor
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
`,
|
|
2946
|
+
{ owner, name, after },
|
|
2947
|
+
o
|
|
2948
|
+
);
|
|
2949
|
+
const list2 = d.repository?.labelList;
|
|
2950
|
+
for (const l of list2?.nodes ?? []) ids.set(l.name, l.id);
|
|
2951
|
+
if (list2?.pageInfo.hasNextPage !== true || list2.pageInfo.endCursor === null)
|
|
2952
|
+
return ids;
|
|
2953
|
+
after = list2.pageInfo.endCursor;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
async function removeLabels(id, labelIds2, o) {
|
|
2957
|
+
await graphql(
|
|
2958
|
+
`
|
|
2959
|
+
mutation ($id: ID!, $labelIds: [ID!]!) {
|
|
2960
|
+
removeLabelsFromLabelable(
|
|
2961
|
+
input: { labelableId: $id, labelIds: $labelIds }
|
|
2962
|
+
) {
|
|
2963
|
+
clientMutationId
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
`,
|
|
2967
|
+
{ id, labelIds: labelIds2 },
|
|
2483
2968
|
o
|
|
2484
2969
|
);
|
|
2485
2970
|
}
|
|
@@ -2490,8 +2975,25 @@ var WORKING = "working";
|
|
|
2490
2975
|
var WORKING_VIEW = "Working";
|
|
2491
2976
|
var MEMORY = ".rness";
|
|
2492
2977
|
var LABEL = "rness";
|
|
2978
|
+
var WORKING_SESSION = "Working session";
|
|
2979
|
+
var SESSION_HISTORY = "Session history";
|
|
2980
|
+
var FORMERLY = {
|
|
2981
|
+
[WORKING_SESSION]: "Session",
|
|
2982
|
+
[SESSION_HISTORY]: "Sessions"
|
|
2983
|
+
};
|
|
2984
|
+
var valueOf2 = (values2, name) => values2[name] ?? values2[FORMERLY[name] ?? ""] ?? null;
|
|
2493
2985
|
var LABEL_COLOR = "5319e7";
|
|
2494
2986
|
var LABEL_DESCRIPTION = "A document of .rness, on Agent Pulse";
|
|
2987
|
+
var OWN_LABEL_COLOR = "ededed";
|
|
2988
|
+
var OWN_LABEL_DESCRIPTION = "A label a collection of .rness gives its documents";
|
|
2989
|
+
var CALENDAR_VIEW = "Calendar";
|
|
2990
|
+
var markerOf = (path) => `<!-- rness: ${path} -->`;
|
|
2991
|
+
var DATA_TYPE = {
|
|
2992
|
+
text: "TEXT",
|
|
2993
|
+
date: "DATE",
|
|
2994
|
+
number: "NUMBER",
|
|
2995
|
+
select: "SINGLE_SELECT"
|
|
2996
|
+
};
|
|
2495
2997
|
var labelled = (labels) => labels.some((l) => l.toLowerCase() === LABEL.toLowerCase());
|
|
2496
2998
|
var STATUS_FIELD = "Status";
|
|
2497
2999
|
var ALL_VIEW = "All";
|
|
@@ -2523,6 +3025,8 @@ var GitHubBoards = class {
|
|
|
2523
3025
|
#cache = /* @__PURE__ */ new Map();
|
|
2524
3026
|
/** Each organization's `.rness`, as first read: its id, its Issues, its label. */
|
|
2525
3027
|
#memories = /* @__PURE__ */ new Map();
|
|
3028
|
+
/** Each organization's `.rness` labels by name, read when a collection needs one. */
|
|
3029
|
+
#labels = /* @__PURE__ */ new Map();
|
|
2526
3030
|
constructor(options) {
|
|
2527
3031
|
this.#o = options;
|
|
2528
3032
|
}
|
|
@@ -2563,10 +3067,10 @@ var GitHubBoards = class {
|
|
|
2563
3067
|
);
|
|
2564
3068
|
}
|
|
2565
3069
|
/** The project only: the caller declares it before `ensureLayout` builds it. */
|
|
2566
|
-
async createBoard(org) {
|
|
3070
|
+
async createBoard(org, title = PROJECT_TITLE) {
|
|
2567
3071
|
const project = await createProject(
|
|
2568
3072
|
await orgId(org, this.#o),
|
|
2569
|
-
|
|
3073
|
+
title,
|
|
2570
3074
|
this.#o
|
|
2571
3075
|
);
|
|
2572
3076
|
const board = { org, number: project.number, url: project.url };
|
|
@@ -2577,7 +3081,7 @@ var GitHubBoards = class {
|
|
|
2577
3081
|
const project = await findProject(org, number, this.#o);
|
|
2578
3082
|
if (project === null) return null;
|
|
2579
3083
|
const board = { org, number, url: project.url };
|
|
2580
|
-
this.#remember(board, project.id);
|
|
3084
|
+
if (!this.#cache.has(this.#key(board))) this.#remember(board, project.id);
|
|
2581
3085
|
return board;
|
|
2582
3086
|
}
|
|
2583
3087
|
async ensureLayout(board, layout) {
|
|
@@ -2624,6 +3128,11 @@ var GitHubBoards = class {
|
|
|
2624
3128
|
const text = async (name) => {
|
|
2625
3129
|
const field = fields2.find((f) => f.name === name);
|
|
2626
3130
|
if (field !== void 0) return field;
|
|
3131
|
+
const was = fields2.find((f) => f.name === FORMERLY[name]);
|
|
3132
|
+
if (was !== void 0) {
|
|
3133
|
+
added.push(`renamed field ${was.name} \u2192 ${name}`);
|
|
3134
|
+
return renameField(was.id, name, this.#o);
|
|
3135
|
+
}
|
|
2627
3136
|
added.push(`field ${name}`);
|
|
2628
3137
|
return createField(cache.projectId, name, "TEXT", this.#o);
|
|
2629
3138
|
};
|
|
@@ -2644,9 +3153,63 @@ var GitHubBoards = class {
|
|
|
2644
3153
|
for (const f of layout.fields)
|
|
2645
3154
|
own.set(f.name, await single(f.name, "Status", f.statuses, true));
|
|
2646
3155
|
const agent = await single("Agent", "Agent", [WORKING]);
|
|
2647
|
-
const session = await text(
|
|
3156
|
+
const session = await text(WORKING_SESSION);
|
|
2648
3157
|
const path = await text("Path");
|
|
2649
|
-
const sessions = await text(
|
|
3158
|
+
const sessions = await text(SESSION_HISTORY);
|
|
3159
|
+
const declared2 = [];
|
|
3160
|
+
for (const d of layout.declared) {
|
|
3161
|
+
const type = DATA_TYPE[d.type];
|
|
3162
|
+
const field = fields2.find((f) => f.name === d.name);
|
|
3163
|
+
if (field !== void 0 && field.dataType !== type)
|
|
3164
|
+
throw new Error(
|
|
3165
|
+
`the project's field ${d.name} is not a ${d.type} field: rename it on the project, or in the collection's README`
|
|
3166
|
+
);
|
|
3167
|
+
if (d.type !== "select") {
|
|
3168
|
+
if (field !== void 0) declared2.push(field);
|
|
3169
|
+
else {
|
|
3170
|
+
added.push(`field ${d.name}`);
|
|
3171
|
+
declared2.push(
|
|
3172
|
+
await createField(
|
|
3173
|
+
cache.projectId,
|
|
3174
|
+
d.name,
|
|
3175
|
+
DATA_TYPE[d.type],
|
|
3176
|
+
this.#o
|
|
3177
|
+
)
|
|
3178
|
+
);
|
|
3179
|
+
}
|
|
3180
|
+
continue;
|
|
3181
|
+
}
|
|
3182
|
+
if (field === void 0 && d.options.length === 0) continue;
|
|
3183
|
+
const options = (names) => names.map((name) => ({
|
|
3184
|
+
name,
|
|
3185
|
+
color: field?.options?.find((o) => o.name === name)?.color ?? "GRAY"
|
|
3186
|
+
}));
|
|
3187
|
+
if (field === void 0) {
|
|
3188
|
+
added.push(`field ${d.name}`);
|
|
3189
|
+
declared2.push(
|
|
3190
|
+
await createField(
|
|
3191
|
+
cache.projectId,
|
|
3192
|
+
d.name,
|
|
3193
|
+
{ options: options(d.options) },
|
|
3194
|
+
this.#o
|
|
3195
|
+
)
|
|
3196
|
+
);
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
const now = namesOf(field);
|
|
3200
|
+
const next = orderedFirst(d.options, [
|
|
3201
|
+
...now,
|
|
3202
|
+
...d.options.filter((n) => !now.includes(n))
|
|
3203
|
+
]);
|
|
3204
|
+
if (next.every((n, i) => n === now[i]) && next.length === now.length)
|
|
3205
|
+
declared2.push(field);
|
|
3206
|
+
else {
|
|
3207
|
+
added.push(
|
|
3208
|
+
...d.options.filter((n) => !now.includes(n)).map((n) => `option ${n}`)
|
|
3209
|
+
);
|
|
3210
|
+
declared2.push(await setOptions(field, options(next), this.#o));
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
2650
3213
|
const current = await views(cache.projectId, this.#o);
|
|
2651
3214
|
const first = current[0];
|
|
2652
3215
|
if (first !== void 0 && first.name === DEFAULT_VIEW && !current.some((v) => v.name === ALL_VIEW)) {
|
|
@@ -2679,6 +3242,19 @@ var GitHubBoards = class {
|
|
|
2679
3242
|
this.#o
|
|
2680
3243
|
);
|
|
2681
3244
|
}
|
|
3245
|
+
if (layout.declared.some((d) => d.type === "date") && !current.some((v) => v.name === CALENDAR_VIEW)) {
|
|
3246
|
+
added.push(`view ${CALENDAR_VIEW}`);
|
|
3247
|
+
await createView(
|
|
3248
|
+
board.org,
|
|
3249
|
+
board.number,
|
|
3250
|
+
{
|
|
3251
|
+
name: CALENDAR_VIEW,
|
|
3252
|
+
layout: "roadmap",
|
|
3253
|
+
filter: viewFilter(layout.types[0] ?? "")
|
|
3254
|
+
},
|
|
3255
|
+
this.#o
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
2682
3258
|
if (!current.some((v) => v.name === WORKING_VIEW)) {
|
|
2683
3259
|
added.push(`view ${WORKING_VIEW}`);
|
|
2684
3260
|
await createView(
|
|
@@ -2709,6 +3285,9 @@ var GitHubBoards = class {
|
|
|
2709
3285
|
);
|
|
2710
3286
|
added.push(`label ${LABEL}`);
|
|
2711
3287
|
}
|
|
3288
|
+
for (const name of layout.labels)
|
|
3289
|
+
if ((await this.#ownLabel(board.org, name)).made)
|
|
3290
|
+
added.push(`label ${name}`);
|
|
2712
3291
|
const repository2 = `${board.org}/${MEMORY}`;
|
|
2713
3292
|
const linked = await linkedRepositories(cache.projectId, this.#o);
|
|
2714
3293
|
if (!linked.some((r) => r.toLowerCase() === repository2.toLowerCase())) {
|
|
@@ -2722,10 +3301,33 @@ var GitHubBoards = class {
|
|
|
2722
3301
|
agent,
|
|
2723
3302
|
session,
|
|
2724
3303
|
path,
|
|
2725
|
-
sessions
|
|
3304
|
+
sessions,
|
|
3305
|
+
...declared2
|
|
2726
3306
|
];
|
|
2727
3307
|
return added;
|
|
2728
3308
|
}
|
|
3309
|
+
/** A collection's label on `.rness`, made when missing; its id, and whether this made it. */
|
|
3310
|
+
async #ownLabel(org, name) {
|
|
3311
|
+
let ids = this.#labels.get(org);
|
|
3312
|
+
if (ids === void 0) {
|
|
3313
|
+
ids = await labelIds(org, MEMORY, this.#o);
|
|
3314
|
+
this.#labels.set(org, ids);
|
|
3315
|
+
}
|
|
3316
|
+
const known = [...ids].find(([n]) => n.toLowerCase() === name.toLowerCase());
|
|
3317
|
+
if (known !== void 0) return { id: known[1], made: false };
|
|
3318
|
+
const id = await createLabel(
|
|
3319
|
+
org,
|
|
3320
|
+
MEMORY,
|
|
3321
|
+
{
|
|
3322
|
+
name,
|
|
3323
|
+
color: OWN_LABEL_COLOR,
|
|
3324
|
+
description: OWN_LABEL_DESCRIPTION
|
|
3325
|
+
},
|
|
3326
|
+
this.#o
|
|
3327
|
+
);
|
|
3328
|
+
ids.set(name, id);
|
|
3329
|
+
return { id, made: true };
|
|
3330
|
+
}
|
|
2729
3331
|
async items(board, options = {}) {
|
|
2730
3332
|
const cache = await this.#cacheOf(board);
|
|
2731
3333
|
const raw = await listItems(cache.projectId, this.#o, options);
|
|
@@ -2741,24 +3343,30 @@ var GitHubBoards = class {
|
|
|
2741
3343
|
number: issue.number,
|
|
2742
3344
|
open: issue.open,
|
|
2743
3345
|
labelled: labelled(issue.labels),
|
|
2744
|
-
body: issue.body
|
|
3346
|
+
body: issue.body,
|
|
3347
|
+
labels: [...issue.labels]
|
|
2745
3348
|
},
|
|
2746
3349
|
title: r.title,
|
|
2747
3350
|
status: r.values[STATUS_FIELD] ?? null,
|
|
2748
3351
|
collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
|
|
2749
3352
|
type: r.values[COLLECTION_FIELD] ?? null,
|
|
2750
3353
|
agent: r.values["Agent"] ?? null,
|
|
2751
|
-
session: r.values
|
|
2752
|
-
sessions: r.values
|
|
3354
|
+
session: valueOf2(r.values, WORKING_SESSION),
|
|
3355
|
+
sessions: valueOf2(r.values, SESSION_HISTORY),
|
|
3356
|
+
values: { ...r.values }
|
|
2753
3357
|
};
|
|
2754
3358
|
});
|
|
2755
3359
|
}
|
|
2756
3360
|
async #set(board, itemId, name, value) {
|
|
2757
3361
|
const cache = await this.#cacheOf(board);
|
|
2758
|
-
const
|
|
3362
|
+
const fields2 = await this.#fields(board);
|
|
3363
|
+
const field = fields2.find((f) => f.name === name) ?? fields2.find((f) => f.name === FORMERLY[name]);
|
|
2759
3364
|
if (field === void 0) throw new Error(`the board has no field ${name}`);
|
|
2760
3365
|
let sent = null;
|
|
2761
|
-
if (value !== null && field.
|
|
3366
|
+
if (value !== null && field.dataType === "DATE") sent = { date: value };
|
|
3367
|
+
else if (value !== null && field.dataType === "NUMBER")
|
|
3368
|
+
sent = { number: Number(value) };
|
|
3369
|
+
else if (value !== null && field.options === null) sent = { text: value };
|
|
2762
3370
|
else if (value !== null) {
|
|
2763
3371
|
const option = field.options?.find((opt) => opt.name === value);
|
|
2764
3372
|
if (option === void 0)
|
|
@@ -2768,13 +3376,18 @@ var GitHubBoards = class {
|
|
|
2768
3376
|
await setValue(cache.projectId, itemId, field.id, sent, this.#o);
|
|
2769
3377
|
const known = cache.known.get(itemId);
|
|
2770
3378
|
if (known === void 0) return;
|
|
2771
|
-
if (value === null) delete known.values[name];
|
|
2772
|
-
else known.values[name] = value;
|
|
3379
|
+
if (value === null) delete known.values[field.name];
|
|
3380
|
+
else known.values[field.name] = value;
|
|
2773
3381
|
}
|
|
2774
3382
|
async apply(board, step2) {
|
|
2775
3383
|
if (step2.kind === "unchanged") return null;
|
|
2776
3384
|
const cache = await this.#cacheOf(board);
|
|
2777
3385
|
if (step2.kind === "create") return this.#create(board, cache, step2.want);
|
|
3386
|
+
if (step2.kind === "remove") {
|
|
3387
|
+
await deleteItem(cache.projectId, step2.id, this.#o);
|
|
3388
|
+
cache.known.delete(step2.id);
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
2778
3391
|
const known = await this.#known(board, step2.id);
|
|
2779
3392
|
if (step2.kind === "archive" || step2.kind === "close") {
|
|
2780
3393
|
if (step2.kind === "close" && known.issue?.open === true)
|
|
@@ -2805,6 +3418,7 @@ var GitHubBoards = class {
|
|
|
2805
3418
|
labels: []
|
|
2806
3419
|
};
|
|
2807
3420
|
await this.#issue(known, step2.want, labelId);
|
|
3421
|
+
await this.#ownLabels(board.org, known, step2.want);
|
|
2808
3422
|
await this.#values(board, step2.id, step2.want);
|
|
2809
3423
|
return { id: step2.id, number: made.number };
|
|
2810
3424
|
}
|
|
@@ -2814,6 +3428,7 @@ var GitHubBoards = class {
|
|
|
2814
3428
|
issue.open = true;
|
|
2815
3429
|
}
|
|
2816
3430
|
await this.#issue(known, step2.want, labelId);
|
|
3431
|
+
await this.#ownLabels(board.org, known, step2.want);
|
|
2817
3432
|
await this.#values(board, step2.id, step2.want);
|
|
2818
3433
|
return null;
|
|
2819
3434
|
}
|
|
@@ -2857,6 +3472,7 @@ var GitHubBoards = class {
|
|
|
2857
3472
|
};
|
|
2858
3473
|
cache.known.set(id2, known);
|
|
2859
3474
|
await this.#issue(known, want, labelId);
|
|
3475
|
+
await this.#ownLabels(board.org, known, want);
|
|
2860
3476
|
await this.#values(board, id2, want);
|
|
2861
3477
|
return { id: id2, number: found.number, adopted: true };
|
|
2862
3478
|
}
|
|
@@ -2880,6 +3496,7 @@ var GitHubBoards = class {
|
|
|
2880
3496
|
archived: false,
|
|
2881
3497
|
values: {}
|
|
2882
3498
|
});
|
|
3499
|
+
await this.#ownLabels(board.org, cache.known.get(id), want);
|
|
2883
3500
|
await this.#values(board, id, want);
|
|
2884
3501
|
return { id, number: made.number };
|
|
2885
3502
|
}
|
|
@@ -2895,18 +3512,45 @@ var GitHubBoards = class {
|
|
|
2895
3512
|
known.title = want.title;
|
|
2896
3513
|
}
|
|
2897
3514
|
}
|
|
3515
|
+
/** The labels a collection gives the issue, and those it takes off (spec 0025 §4). */
|
|
3516
|
+
async #ownLabels(org, known, want) {
|
|
3517
|
+
if (want.labels.length === 0 && want.unlabels.length === 0) return;
|
|
3518
|
+
const issue = this.#issueOf(known);
|
|
3519
|
+
const carried = (name) => issue.labels.find((l) => l.toLowerCase() === name.toLowerCase());
|
|
3520
|
+
const add = want.labels.filter((l) => carried(l) === void 0);
|
|
3521
|
+
const off = want.unlabels.filter((l) => carried(l) !== void 0);
|
|
3522
|
+
if (add.length > 0) {
|
|
3523
|
+
const ids = [];
|
|
3524
|
+
for (const name of add) ids.push((await this.#ownLabel(org, name)).id);
|
|
3525
|
+
await addLabels(issue.id, ids, this.#o);
|
|
3526
|
+
issue.labels.push(...add);
|
|
3527
|
+
}
|
|
3528
|
+
if (off.length > 0) {
|
|
3529
|
+
const ids = [];
|
|
3530
|
+
for (const name of off) ids.push((await this.#ownLabel(org, name)).id);
|
|
3531
|
+
await removeLabels(issue.id, ids, this.#o);
|
|
3532
|
+
issue.labels = issue.labels.filter(
|
|
3533
|
+
(l) => !off.some((o) => o.toLowerCase() === l.toLowerCase())
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
2898
3537
|
/** Path first — an issue on the board without it is the team's — then the fields, each only when it differs. */
|
|
2899
3538
|
async #values(board, id, want) {
|
|
2900
3539
|
const values2 = (await this.#known(board, id)).values;
|
|
3540
|
+
const fields2 = await this.#fields(board);
|
|
2901
3541
|
const wanted = [
|
|
2902
3542
|
["Path", want.path],
|
|
2903
3543
|
[COLLECTION_FIELD, want.type],
|
|
2904
3544
|
[STATUS_FIELD, want.status],
|
|
2905
3545
|
...want.statusField === null ? [] : [[want.statusField, want.status]],
|
|
2906
|
-
[
|
|
3546
|
+
[SESSION_HISTORY, want.sessions],
|
|
3547
|
+
// A select with no option yet has no field yet: nothing to write.
|
|
3548
|
+
...Object.entries(want.values).filter(
|
|
3549
|
+
([name]) => fields2.some((f) => f.name === name)
|
|
3550
|
+
)
|
|
2907
3551
|
];
|
|
2908
3552
|
for (const [name, value] of wanted)
|
|
2909
|
-
if ((values2
|
|
3553
|
+
if (valueOf2(values2, name) !== value)
|
|
2910
3554
|
await this.#set(board, id, name, value);
|
|
2911
3555
|
}
|
|
2912
3556
|
async #known(board, id) {
|
|
@@ -2932,9 +3576,51 @@ var GitHubBoards = class {
|
|
|
2932
3576
|
async mark(board, itemIds, session) {
|
|
2933
3577
|
for (const id of itemIds) {
|
|
2934
3578
|
await this.#set(board, id, "Agent", session === null ? null : WORKING);
|
|
2935
|
-
await this.#set(board, id,
|
|
3579
|
+
await this.#set(board, id, WORKING_SESSION, session);
|
|
2936
3580
|
}
|
|
2937
3581
|
}
|
|
3582
|
+
async describe(board, text) {
|
|
3583
|
+
const cache = await this.#cacheOf(board);
|
|
3584
|
+
const now = await projectText(cache.projectId, this.#o);
|
|
3585
|
+
const next = {};
|
|
3586
|
+
if (text.readme !== null && now.readme.trimEnd() !== text.readme.trimEnd())
|
|
3587
|
+
next.readme = text.readme.trimEnd();
|
|
3588
|
+
if (text.description !== null && now.shortDescription !== text.description)
|
|
3589
|
+
next.shortDescription = text.description;
|
|
3590
|
+
if (next.readme === void 0 && next.shortDescription === void 0)
|
|
3591
|
+
return [];
|
|
3592
|
+
await updateProjectText(cache.projectId, next, this.#o);
|
|
3593
|
+
return [
|
|
3594
|
+
...next.readme === void 0 ? [] : ["readme"],
|
|
3595
|
+
...next.shortDescription === void 0 ? [] : ["short description"]
|
|
3596
|
+
];
|
|
3597
|
+
}
|
|
3598
|
+
async postUpdates(board, updates) {
|
|
3599
|
+
if (updates.length === 0) return [];
|
|
3600
|
+
const cache = await this.#cacheOf(board);
|
|
3601
|
+
const there = await statusUpdates(cache.projectId, this.#o);
|
|
3602
|
+
const written = [];
|
|
3603
|
+
for (const u of updates) {
|
|
3604
|
+
const marker = markerOf(u.path);
|
|
3605
|
+
const input = {
|
|
3606
|
+
body: `${u.body.trimEnd()}
|
|
3607
|
+
|
|
3608
|
+
${marker}`,
|
|
3609
|
+
status: u.health.toUpperCase().replace("-", "_"),
|
|
3610
|
+
startDate: u.startDate,
|
|
3611
|
+
targetDate: u.targetDate
|
|
3612
|
+
};
|
|
3613
|
+
const posted = there.find((p) => p.body.includes(marker));
|
|
3614
|
+
if (posted === void 0) {
|
|
3615
|
+
await createStatusUpdate(cache.projectId, input, this.#o);
|
|
3616
|
+
written.push(`posted ${u.path}`);
|
|
3617
|
+
} else if (posted.body.trimEnd() !== input.body || posted.status !== input.status || (posted.startDate ?? null) !== input.startDate || (posted.targetDate ?? null) !== input.targetDate) {
|
|
3618
|
+
await updateStatusUpdate(posted.id, input, this.#o);
|
|
3619
|
+
written.push(`updated ${u.path}`);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
return written;
|
|
3623
|
+
}
|
|
2938
3624
|
};
|
|
2939
3625
|
|
|
2940
3626
|
// src/core/github-oauth-provider.ts
|
|
@@ -3015,8 +3701,8 @@ var GitHubOAuthProvider = class {
|
|
|
3015
3701
|
async checkIssues(org) {
|
|
3016
3702
|
return this.#boards().checkIssues(org);
|
|
3017
3703
|
}
|
|
3018
|
-
async createBoard(org) {
|
|
3019
|
-
return this.#boards().createBoard(org);
|
|
3704
|
+
async createBoard(org, title) {
|
|
3705
|
+
return this.#boards().createBoard(org, title);
|
|
3020
3706
|
}
|
|
3021
3707
|
async board(org, number) {
|
|
3022
3708
|
return this.#boards().board(org, number);
|
|
@@ -3033,6 +3719,12 @@ var GitHubOAuthProvider = class {
|
|
|
3033
3719
|
async mark(board, itemIds, session) {
|
|
3034
3720
|
return this.#boards().mark(board, itemIds, session);
|
|
3035
3721
|
}
|
|
3722
|
+
async describe(board, text) {
|
|
3723
|
+
return this.#boards().describe(board, text);
|
|
3724
|
+
}
|
|
3725
|
+
async postUpdates(board, updates) {
|
|
3726
|
+
return this.#boards().postUpdates(board, updates);
|
|
3727
|
+
}
|
|
3036
3728
|
credentialsFor(url) {
|
|
3037
3729
|
if (this.#token === null || !url.startsWith(GITHUB_HTTPS)) return null;
|
|
3038
3730
|
return { username: "x-access-token", password: this.#token.token };
|
|
@@ -3047,11 +3739,11 @@ async function githubProvider(apiBase, wait) {
|
|
|
3047
3739
|
}
|
|
3048
3740
|
|
|
3049
3741
|
// src/core/repos.ts
|
|
3050
|
-
import { glob as glob2, readFile as
|
|
3051
|
-
import { join as
|
|
3742
|
+
import { glob as glob2, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
3743
|
+
import { join as join9, posix as posix5, sep as sep2 } from "path";
|
|
3052
3744
|
async function addRepository(input) {
|
|
3053
3745
|
const { name, url } = parseRepoSpec(input.spec, input.org, input.host);
|
|
3054
|
-
const dir =
|
|
3746
|
+
const dir = join9(input.root, "org", name);
|
|
3055
3747
|
const repoPath = `org/${name}`;
|
|
3056
3748
|
const current = input.manifest.scopes[name];
|
|
3057
3749
|
if (current !== void 0 && current.path !== repoPath)
|
|
@@ -3094,7 +3786,7 @@ async function addRepository(input) {
|
|
|
3094
3786
|
}
|
|
3095
3787
|
const declared2 = [name];
|
|
3096
3788
|
for (const { path, base } of subs) {
|
|
3097
|
-
if (!await exists(
|
|
3789
|
+
if (!await exists(join9(input.root, ...path.split("/")))) {
|
|
3098
3790
|
if (action === "cloned") await rm3(dir, { recursive: true, force: true });
|
|
3099
3791
|
throw new Error(`${path} does not exist`);
|
|
3100
3792
|
}
|
|
@@ -3112,7 +3804,7 @@ async function workspaceDirs(repoDir) {
|
|
|
3112
3804
|
let patterns = [];
|
|
3113
3805
|
try {
|
|
3114
3806
|
const pkg = JSON.parse(
|
|
3115
|
-
await
|
|
3807
|
+
await readFile6(join9(repoDir, "package.json"), "utf8")
|
|
3116
3808
|
);
|
|
3117
3809
|
const w = pkg.workspaces;
|
|
3118
3810
|
if (Array.isArray(w))
|
|
@@ -3129,7 +3821,7 @@ async function workspaceDirs(repoDir) {
|
|
|
3129
3821
|
for (const pattern of patterns) {
|
|
3130
3822
|
for await (const match of glob2(pattern, { cwd: repoDir })) {
|
|
3131
3823
|
const rel = match.split(sep2).join(posix5.sep);
|
|
3132
|
-
if (await exists(
|
|
3824
|
+
if (await exists(join9(repoDir, ...rel.split(posix5.sep), "package.json")))
|
|
3133
3825
|
dirs.add(rel);
|
|
3134
3826
|
}
|
|
3135
3827
|
}
|
|
@@ -3380,8 +4072,8 @@ function httpsFlagLine(spec) {
|
|
|
3380
4072
|
}
|
|
3381
4073
|
|
|
3382
4074
|
// src/core/workspace.ts
|
|
3383
|
-
import { readdir as
|
|
3384
|
-
import { dirname as dirname3, join as
|
|
4075
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
4076
|
+
import { dirname as dirname3, join as join10, parse as parse2, resolve as resolve2 } from "path";
|
|
3385
4077
|
async function findWorkspace(startDir) {
|
|
3386
4078
|
const start = resolve2(startDir);
|
|
3387
4079
|
const { root: fsRoot } = parse2(start);
|
|
@@ -3391,18 +4083,18 @@ async function findWorkspace(startDir) {
|
|
|
3391
4083
|
if (dir === fsRoot) break;
|
|
3392
4084
|
}
|
|
3393
4085
|
for (const dir of ancestors) {
|
|
3394
|
-
if (await exists(
|
|
3395
|
-
return { root: dir, rnessDir:
|
|
4086
|
+
if (await exists(join10(dir, ".rness", "rness.json")))
|
|
4087
|
+
return { root: dir, rnessDir: join10(dir, ".rness") };
|
|
3396
4088
|
}
|
|
3397
4089
|
for (const dir of ancestors) {
|
|
3398
|
-
if (await exists(
|
|
4090
|
+
if (await exists(join10(dir, "rness.json")))
|
|
3399
4091
|
return { root: dirname3(dir), rnessDir: dir };
|
|
3400
4092
|
}
|
|
3401
4093
|
throw new Error(`no rness workspace found above ${startDir}`);
|
|
3402
4094
|
}
|
|
3403
4095
|
async function clonesIn(root) {
|
|
3404
4096
|
try {
|
|
3405
|
-
const entries = await
|
|
4097
|
+
const entries = await readdir3(join10(root, "org"), { withFileTypes: true });
|
|
3406
4098
|
return new Set(
|
|
3407
4099
|
entries.filter((e) => e.isDirectory() && isWorkspaceClone(e.name)).map((e) => e.name)
|
|
3408
4100
|
);
|
|
@@ -3426,15 +4118,15 @@ function reportError(e) {
|
|
|
3426
4118
|
}
|
|
3427
4119
|
|
|
3428
4120
|
// src/core/delegate.ts
|
|
3429
|
-
import { readFile as
|
|
3430
|
-
import { join as
|
|
4121
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4122
|
+
import { join as join11 } from "path";
|
|
3431
4123
|
var DEFAULT_BIN = "dist/bin/rness.js";
|
|
3432
4124
|
var PINNED_BIN = `node_modules/@rness/cli/${DEFAULT_BIN}`;
|
|
3433
4125
|
async function readPinnedCli(rnessDir) {
|
|
3434
|
-
const dir =
|
|
4126
|
+
const dir = join11(rnessDir, "node_modules", "@rness", "cli");
|
|
3435
4127
|
let pkg;
|
|
3436
4128
|
try {
|
|
3437
|
-
pkg = JSON.parse(await
|
|
4129
|
+
pkg = JSON.parse(await readFile7(join11(dir, "package.json"), "utf8"));
|
|
3438
4130
|
} catch {
|
|
3439
4131
|
return null;
|
|
3440
4132
|
}
|
|
@@ -3454,7 +4146,7 @@ async function readPinnedCli(rnessDir) {
|
|
|
3454
4146
|
async function pinnedBinPath(rnessDir) {
|
|
3455
4147
|
const pinned = await readPinnedCli(rnessDir);
|
|
3456
4148
|
if (pinned === null) return null;
|
|
3457
|
-
return
|
|
4149
|
+
return join11(pinned.dir, ...pinned.bin.split("/"));
|
|
3458
4150
|
}
|
|
3459
4151
|
async function findDelegate(cwd, ownVersion) {
|
|
3460
4152
|
let root;
|
|
@@ -3470,7 +4162,7 @@ async function findDelegate(cwd, ownVersion) {
|
|
|
3470
4162
|
if (pinned === null) return null;
|
|
3471
4163
|
const { version } = pinned;
|
|
3472
4164
|
if (version === null || version === ownVersion) return null;
|
|
3473
|
-
return { entry:
|
|
4165
|
+
return { entry: join11(pinned.dir, "dist", "index.js"), version, root };
|
|
3474
4166
|
}
|
|
3475
4167
|
|
|
3476
4168
|
// src/core/block.ts
|
|
@@ -3537,7 +4229,7 @@ function isCurrentBlock(blockLines, freshHash) {
|
|
|
3537
4229
|
}
|
|
3538
4230
|
|
|
3539
4231
|
// src/core/merge.ts
|
|
3540
|
-
import { join as
|
|
4232
|
+
import { join as join12 } from "path";
|
|
3541
4233
|
function findBlock(lines) {
|
|
3542
4234
|
const begins = [];
|
|
3543
4235
|
const ends = [];
|
|
@@ -3596,7 +4288,7 @@ function claudeMdAtRoot(existing, block) {
|
|
|
3596
4288
|
return merged.ok ? { ...merged, changed: merged.text !== existing } : merged;
|
|
3597
4289
|
}
|
|
3598
4290
|
async function ensureClaudeMd(dir) {
|
|
3599
|
-
const file =
|
|
4291
|
+
const file = join12(dir, "CLAUDE.md");
|
|
3600
4292
|
if (await isSymlink(file)) return "unchanged";
|
|
3601
4293
|
const existing = await readOrNull(file);
|
|
3602
4294
|
if (existing === null) {
|
|
@@ -3611,11 +4303,11 @@ async function ensureClaudeMd(dir) {
|
|
|
3611
4303
|
}
|
|
3612
4304
|
|
|
3613
4305
|
// src/commands/sync.ts
|
|
3614
|
-
import { join as
|
|
4306
|
+
import { join as join15 } from "path";
|
|
3615
4307
|
|
|
3616
4308
|
// src/core/agent-targets.ts
|
|
3617
4309
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
3618
|
-
import { dirname as dirname4, join as
|
|
4310
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
3619
4311
|
|
|
3620
4312
|
// src/core/claude-skills.ts
|
|
3621
4313
|
var PLUGIN = ".claude/skills/rness";
|
|
@@ -3681,8 +4373,10 @@ Writing:
|
|
|
3681
4373
|
\`org/\` it affects.
|
|
3682
4374
|
- An ADR it replaces is named in Context. That one becomes \`Superseded\`,
|
|
3683
4375
|
with \`superseded_by:\`, only once the new ADR is \`Accepted\`.
|
|
3684
|
-
- Add
|
|
3685
|
-
unless
|
|
4376
|
+
- Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
|
|
4377
|
+
\`sessions:\` in the front matter, unless that id is already there.
|
|
4378
|
+
\`<model>\` is the model you run as, as Claude Code names it, after
|
|
4379
|
+
\`Claude\`: for example \`Claude Opus 5.5\`.
|
|
3686
4380
|
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3687
4381
|
|
|
3688
4382
|
After writing: give the path and the Decision section, and say the ADR
|
|
@@ -3720,8 +4414,10 @@ Writing:
|
|
|
3720
4414
|
- What was checked in this session (a document read, a command run) is
|
|
3721
4415
|
written as verified, with the date; everything else as a proposal or an
|
|
3722
4416
|
open question.
|
|
3723
|
-
- Add
|
|
3724
|
-
unless
|
|
4417
|
+
- Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
|
|
4418
|
+
\`sessions:\` in the front matter, unless that id is already there.
|
|
4419
|
+
\`<model>\` is the model you run as, as Claude Code names it, after
|
|
4420
|
+
\`Claude\`: for example \`Claude Opus 5.5\`.
|
|
3725
4421
|
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3726
4422
|
|
|
3727
4423
|
After writing: give the path and the summary. It stays \`Draft\` until the
|
|
@@ -3762,8 +4458,10 @@ Writing:
|
|
|
3762
4458
|
repository has one.
|
|
3763
4459
|
- Where the plan decides something the specification leaves open, a
|
|
3764
4460
|
section says so.
|
|
3765
|
-
- Add
|
|
3766
|
-
unless
|
|
4461
|
+
- Add \`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
|
|
4462
|
+
\`sessions:\` in the front matter, unless that id is already there.
|
|
4463
|
+
\`<model>\` is the model you run as, as Claude Code names it, after
|
|
4464
|
+
\`Claude\`: for example \`Claude Opus 5.5\`.
|
|
3767
4465
|
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3768
4466
|
|
|
3769
4467
|
After writing: give the path and the list of tasks. It becomes \`Ready\` when
|
|
@@ -3793,8 +4491,11 @@ Arguments: \`$ARGUMENTS\`.
|
|
|
3793
4491
|
ran, what it showed, what could not run and why;
|
|
3794
4492
|
- the specification: \`Implemented\` once every plan of it is
|
|
3795
4493
|
\`Completed\`; \`updated\` today;
|
|
3796
|
-
- the plan and the specification: add
|
|
3797
|
-
|
|
4494
|
+
- the plan and the specification: add
|
|
4495
|
+
\`{ id: \${CLAUDE_SESSION_ID}, agent: <model> }\` at the end of
|
|
4496
|
+
\`sessions:\` in their front matter, unless that id is already there;
|
|
4497
|
+
\`<model>\` is the model you run as, as Claude Code names it, after
|
|
4498
|
+
\`Claude\`: for example \`Claude Opus 5.5\`;
|
|
3798
4499
|
- the smallest set of documents the work makes inaccurate: \`${rness}/docs/\`,
|
|
3799
4500
|
the READMEs of the repositories it changed.
|
|
3800
4501
|
6. A task not proven: the plan stays \`In progress\` \u2014 or \`Blocked\`, the
|
|
@@ -4051,8 +4752,8 @@ function describeGuarantee(g) {
|
|
|
4051
4752
|
|
|
4052
4753
|
// src/core/pinned.ts
|
|
4053
4754
|
import { execFile as execFile3 } from "child_process";
|
|
4054
|
-
import { readFile as
|
|
4055
|
-
import { basename as basename3, join as
|
|
4755
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
4756
|
+
import { basename as basename3, join as join13 } from "path";
|
|
4056
4757
|
import { promisify as promisify3 } from "util";
|
|
4057
4758
|
var execFileP3 = promisify3(execFile3);
|
|
4058
4759
|
var PACKAGE = "@rness/cli";
|
|
@@ -4077,7 +4778,7 @@ function compareVersions(a, b) {
|
|
|
4077
4778
|
async function readPackageJson(rnessDir) {
|
|
4078
4779
|
try {
|
|
4079
4780
|
const parsed = JSON.parse(
|
|
4080
|
-
await
|
|
4781
|
+
await readFile8(join13(rnessDir, "package.json"), "utf8")
|
|
4081
4782
|
);
|
|
4082
4783
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4083
4784
|
} catch {
|
|
@@ -4096,8 +4797,8 @@ async function readPin(rnessDir) {
|
|
|
4096
4797
|
return null;
|
|
4097
4798
|
}
|
|
4098
4799
|
async function writePin(rnessDir, from, to) {
|
|
4099
|
-
const file =
|
|
4100
|
-
const text = await
|
|
4800
|
+
const file = join13(rnessDir, "package.json");
|
|
4801
|
+
const text = await readFile8(file, "utf8");
|
|
4101
4802
|
const entry = new RegExp(
|
|
4102
4803
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
4103
4804
|
);
|
|
@@ -4119,12 +4820,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
4119
4820
|
if (isPackageManager(name)) return name;
|
|
4120
4821
|
}
|
|
4121
4822
|
for (const [lockfile, pm] of LOCKFILES)
|
|
4122
|
-
if (await exists(
|
|
4823
|
+
if (await exists(join13(rnessDir, lockfile))) return pm;
|
|
4123
4824
|
return "npm";
|
|
4124
4825
|
}
|
|
4125
4826
|
async function hasLockfile(rnessDir, pm) {
|
|
4126
4827
|
for (const [lockfile, owner] of LOCKFILES)
|
|
4127
|
-
if (owner === pm && await exists(
|
|
4828
|
+
if (owner === pm && await exists(join13(rnessDir, lockfile))) return true;
|
|
4128
4829
|
return false;
|
|
4129
4830
|
}
|
|
4130
4831
|
async function pinDrift(rnessDir) {
|
|
@@ -4180,9 +4881,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
4180
4881
|
}
|
|
4181
4882
|
}
|
|
4182
4883
|
async function runPinnedSync(root) {
|
|
4183
|
-
const installed = await readPinnedCli(
|
|
4884
|
+
const installed = await readPinnedCli(join13(root, ".rness"));
|
|
4184
4885
|
if (installed === null) return null;
|
|
4185
|
-
const pinned =
|
|
4886
|
+
const pinned = join13(installed.dir, ...installed.bin.split("/"));
|
|
4186
4887
|
if (!await exists(pinned))
|
|
4187
4888
|
return {
|
|
4188
4889
|
code: 1,
|
|
@@ -4257,7 +4958,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
4257
4958
|
};
|
|
4258
4959
|
}
|
|
4259
4960
|
async function syncPinned(root, shown, fallback) {
|
|
4260
|
-
const rnessDir =
|
|
4961
|
+
const rnessDir = join13(root, ".rness");
|
|
4261
4962
|
const installed = await readPinnedCli(rnessDir);
|
|
4262
4963
|
if (installed === null) {
|
|
4263
4964
|
if (fallback !== void 0) return fallback();
|
|
@@ -4267,7 +4968,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
4267
4968
|
);
|
|
4268
4969
|
return 1;
|
|
4269
4970
|
}
|
|
4270
|
-
const pinned =
|
|
4971
|
+
const pinned = join13(installed.dir, ...installed.bin.split("/"));
|
|
4271
4972
|
if (!await exists(pinned)) {
|
|
4272
4973
|
process.stderr.write(
|
|
4273
4974
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -4307,30 +5008,30 @@ function wholeDiff(existing, content) {
|
|
|
4307
5008
|
}
|
|
4308
5009
|
async function contextOf(root) {
|
|
4309
5010
|
return {
|
|
4310
|
-
packageManager: await workspacePackageManager(
|
|
5011
|
+
packageManager: await workspacePackageManager(join14(root, ".rness"))
|
|
4311
5012
|
};
|
|
4312
5013
|
}
|
|
4313
5014
|
var PATHS_ONLY = { packageManager: "npm" };
|
|
4314
5015
|
async function filesOf(root, manifest, targets) {
|
|
4315
5016
|
const files = [];
|
|
4316
|
-
if (!await exists(
|
|
5017
|
+
if (!await exists(join14(root, "org"))) return files;
|
|
4317
5018
|
const ctx = await contextOf(root);
|
|
4318
5019
|
for (const target2 of targets)
|
|
4319
5020
|
for (const owned of target2.files(ctx))
|
|
4320
5021
|
if (owned.at === "root")
|
|
4321
5022
|
files.push({
|
|
4322
5023
|
...owned,
|
|
4323
|
-
file:
|
|
5024
|
+
file: join14(root, ...owned.file.split("/")),
|
|
4324
5025
|
label: owned.file
|
|
4325
5026
|
});
|
|
4326
5027
|
for (const repo of Object.keys(manifest.repos)) {
|
|
4327
|
-
if (!await exists(
|
|
5028
|
+
if (!await exists(join14(root, "org", repo))) continue;
|
|
4328
5029
|
for (const target2 of targets)
|
|
4329
5030
|
for (const owned of target2.files(ctx))
|
|
4330
5031
|
if (owned.at === "clones")
|
|
4331
5032
|
files.push({
|
|
4332
5033
|
...owned,
|
|
4333
|
-
file:
|
|
5034
|
+
file: join14(root, "org", repo, ...owned.file.split("/")),
|
|
4334
5035
|
label: `org/${repo}/${owned.file}`
|
|
4335
5036
|
});
|
|
4336
5037
|
}
|
|
@@ -4654,7 +5355,7 @@ function targetsOf(manifest, root, only) {
|
|
|
4654
5355
|
const segments = entry.path.split("/");
|
|
4655
5356
|
targets.push({
|
|
4656
5357
|
scope: name,
|
|
4657
|
-
dir:
|
|
5358
|
+
dir: join15(root, ...segments),
|
|
4658
5359
|
depth: segments.length,
|
|
4659
5360
|
label: `${entry.path}/AGENTS.md`
|
|
4660
5361
|
});
|
|
@@ -4813,7 +5514,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4813
5514
|
try {
|
|
4814
5515
|
const notCloned = /* @__PURE__ */ new Set();
|
|
4815
5516
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
4816
|
-
const dir =
|
|
5517
|
+
const dir = join15(ws.root, "org", name);
|
|
4817
5518
|
const label = `org/${name}`;
|
|
4818
5519
|
if (!await exists(dir)) {
|
|
4819
5520
|
if (!toClone.has(name)) {
|
|
@@ -4865,7 +5566,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4865
5566
|
lines.push(
|
|
4866
5567
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
4867
5568
|
);
|
|
4868
|
-
if (!await exists(
|
|
5569
|
+
if (!await exists(join15(ws.root, "org"))) {
|
|
4869
5570
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
4870
5571
|
} else {
|
|
4871
5572
|
let differences = 0;
|
|
@@ -4877,7 +5578,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4877
5578
|
continue;
|
|
4878
5579
|
}
|
|
4879
5580
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
4880
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
5581
|
+
BLOCK_FILES.map((f) => isSymlink(join15(t.dir, f)))
|
|
4881
5582
|
);
|
|
4882
5583
|
const atRoot = t.scope === null;
|
|
4883
5584
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -4900,7 +5601,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4900
5601
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
4901
5602
|
);
|
|
4902
5603
|
}
|
|
4903
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
5604
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join15(t.dir, "AGENTS.md"), block, check);
|
|
4904
5605
|
if (typeof agents === "object") {
|
|
4905
5606
|
problems.push(`${t.label}: ${agents.error}`);
|
|
4906
5607
|
continue;
|
|
@@ -4915,7 +5616,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4915
5616
|
}
|
|
4916
5617
|
if (claudeLinked === true) continue;
|
|
4917
5618
|
const claude = await syncBlockFile(
|
|
4918
|
-
|
|
5619
|
+
join15(t.dir, "CLAUDE.md"),
|
|
4919
5620
|
block,
|
|
4920
5621
|
check,
|
|
4921
5622
|
true
|
|
@@ -4972,7 +5673,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4972
5673
|
}
|
|
4973
5674
|
|
|
4974
5675
|
// src/commands/add.ts
|
|
4975
|
-
import { basename as basename4, join as
|
|
5676
|
+
import { basename as basename4, join as join16 } from "path";
|
|
4976
5677
|
function printDeclared(ui, result, already) {
|
|
4977
5678
|
for (const name of result.declared) {
|
|
4978
5679
|
if (already.has(name)) continue;
|
|
@@ -5065,7 +5766,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5065
5766
|
}
|
|
5066
5767
|
parsed = parseRepoSpec(spec, org, host);
|
|
5067
5768
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
5068
|
-
const dir =
|
|
5769
|
+
const dir = join16(ws.root, "org", parsed.name);
|
|
5069
5770
|
const present = await exists(dir);
|
|
5070
5771
|
if (interactive) {
|
|
5071
5772
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -5193,7 +5894,7 @@ async function contextCommand(opts) {
|
|
|
5193
5894
|
|
|
5194
5895
|
// src/core/scaffold.ts
|
|
5195
5896
|
import { statSync } from "fs";
|
|
5196
|
-
import { dirname as dirname5, join as
|
|
5897
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
5197
5898
|
import { fileURLToPath } from "url";
|
|
5198
5899
|
var SCAFFOLD_FILES = [
|
|
5199
5900
|
"AGENTS.md",
|
|
@@ -5217,9 +5918,9 @@ var SCAFFOLD_FILES = [
|
|
|
5217
5918
|
function scaffoldDir() {
|
|
5218
5919
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5219
5920
|
for (let i = 0; i < 4; i += 1) {
|
|
5220
|
-
const candidate =
|
|
5921
|
+
const candidate = join17(dir, "scaffold");
|
|
5221
5922
|
try {
|
|
5222
|
-
if (statSync(
|
|
5923
|
+
if (statSync(join17(candidate, "rness.json")).isFile()) return candidate;
|
|
5223
5924
|
} catch {
|
|
5224
5925
|
}
|
|
5225
5926
|
dir = dirname5(dir);
|
|
@@ -5228,15 +5929,15 @@ function scaffoldDir() {
|
|
|
5228
5929
|
}
|
|
5229
5930
|
|
|
5230
5931
|
// src/core/scaffold-copy.ts
|
|
5231
|
-
import { chmod as chmod2, mkdir as mkdir3, readFile as
|
|
5232
|
-
import { dirname as dirname6, join as
|
|
5932
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
5933
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
5233
5934
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
5234
5935
|
async function renderScaffold(source, tokens) {
|
|
5235
5936
|
const files = [];
|
|
5236
5937
|
for (const rel of SCAFFOLD_FILES) {
|
|
5237
5938
|
if (rel === "rness.json") continue;
|
|
5238
|
-
const from =
|
|
5239
|
-
let content = await
|
|
5939
|
+
const from = join18(source, ...rel.split("/"));
|
|
5940
|
+
let content = await readFile9(from, "utf8");
|
|
5240
5941
|
if (TOKENISED.has(rel)) {
|
|
5241
5942
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
5242
5943
|
}
|
|
@@ -5250,7 +5951,7 @@ async function renderScaffold(source, tokens) {
|
|
|
5250
5951
|
}
|
|
5251
5952
|
async function copyScaffold(dest, tokens) {
|
|
5252
5953
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
5253
|
-
const to =
|
|
5954
|
+
const to = join18(dest, ...file.path.split("/"));
|
|
5254
5955
|
await mkdir3(dirname6(to), { recursive: true });
|
|
5255
5956
|
await writeFile2(to, file.content, "utf8");
|
|
5256
5957
|
if (file.executable) await chmod2(to, 493);
|
|
@@ -5359,7 +6060,7 @@ async function loginCommand(opts, deps = {}) {
|
|
|
5359
6060
|
ui.error(unsupportedProviderMessage(manifest.provider));
|
|
5360
6061
|
return 1;
|
|
5361
6062
|
}
|
|
5362
|
-
const needsProject = opts.project === true || manifest?.
|
|
6063
|
+
const needsProject = opts.project === true || manifest?.projects != null;
|
|
5363
6064
|
const bin = deps.bin ?? await realpath(process.argv[1] ?? "").catch(() => "");
|
|
5364
6065
|
const env = envToken();
|
|
5365
6066
|
if (env !== null)
|
|
@@ -5430,7 +6131,7 @@ function revokeUrl() {
|
|
|
5430
6131
|
// src/commands/create.ts
|
|
5431
6132
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm6 } from "fs/promises";
|
|
5432
6133
|
import { tmpdir as tmpdir2 } from "os";
|
|
5433
|
-
import { join as
|
|
6134
|
+
import { join as join22, relative as relative4, resolve as resolve5 } from "path";
|
|
5434
6135
|
|
|
5435
6136
|
// src/core/ask-provider.ts
|
|
5436
6137
|
var PROVIDER_QUESTION = "Where does your organization live?";
|
|
@@ -5446,14 +6147,14 @@ async function askProvider(p) {
|
|
|
5446
6147
|
}
|
|
5447
6148
|
|
|
5448
6149
|
// src/core/new-workspace.ts
|
|
5449
|
-
import { mkdir as mkdir4, readdir as
|
|
5450
|
-
import { dirname as dirname8, join as
|
|
6150
|
+
import { mkdir as mkdir4, readdir as readdir4, rm as rm5, stat as stat3 } from "fs/promises";
|
|
6151
|
+
import { dirname as dirname8, join as join20, relative as relative2 } from "path";
|
|
5451
6152
|
|
|
5452
6153
|
// src/core/scaffold-git.ts
|
|
5453
6154
|
import { execFile as execFile5 } from "child_process";
|
|
5454
6155
|
import { mkdtemp, realpath as realpath2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
5455
6156
|
import { tmpdir } from "os";
|
|
5456
|
-
import { dirname as dirname7, join as
|
|
6157
|
+
import { dirname as dirname7, join as join19, resolve as resolve3 } from "path";
|
|
5457
6158
|
import { promisify as promisify5 } from "util";
|
|
5458
6159
|
var execFileP5 = promisify5(execFile5);
|
|
5459
6160
|
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
@@ -5541,8 +6242,8 @@ async function findScaffoldBase(rnessDir) {
|
|
|
5541
6242
|
};
|
|
5542
6243
|
}
|
|
5543
6244
|
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
5544
|
-
const scratch = await mkdtemp(
|
|
5545
|
-
const env = { GIT_INDEX_FILE:
|
|
6245
|
+
const scratch = await mkdtemp(join19(tmpdir(), "rness-scaffold-"));
|
|
6246
|
+
const env = { GIT_INDEX_FILE: join19(scratch, "index") };
|
|
5546
6247
|
try {
|
|
5547
6248
|
await must(
|
|
5548
6249
|
rnessDir,
|
|
@@ -5550,7 +6251,7 @@ async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
|
5550
6251
|
env
|
|
5551
6252
|
);
|
|
5552
6253
|
for (const [i, file] of files.entries()) {
|
|
5553
|
-
const tmp =
|
|
6254
|
+
const tmp = join19(scratch, `blob-${i}`);
|
|
5554
6255
|
await writeFile3(tmp, file.content, "utf8");
|
|
5555
6256
|
const blob = await must(rnessDir, [
|
|
5556
6257
|
"hash-object",
|
|
@@ -5650,7 +6351,7 @@ async function withoutRedundantKeeps(rnessDir, files) {
|
|
|
5650
6351
|
|
|
5651
6352
|
// src/core/new-workspace.ts
|
|
5652
6353
|
async function isEmptyDir(dir) {
|
|
5653
|
-
return (await
|
|
6354
|
+
return (await readdir4(dir)).length === 0;
|
|
5654
6355
|
}
|
|
5655
6356
|
async function insideWorkspace(dir) {
|
|
5656
6357
|
try {
|
|
@@ -5665,8 +6366,8 @@ async function targetProblem(cwd, root, pm) {
|
|
|
5665
6366
|
if (await insideWorkspace(cwd) || await insideWorkspace(dirname8(root)))
|
|
5666
6367
|
return "already inside an rness workspace; create runs from outside one";
|
|
5667
6368
|
if (!await exists(root)) return null;
|
|
5668
|
-
if (await exists(
|
|
5669
|
-
if (!await exists(
|
|
6369
|
+
if (await exists(join20(root, ".rness", "rness.json"))) {
|
|
6370
|
+
if (!await exists(join20(root, ".rness", "node_modules")))
|
|
5670
6371
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
5671
6372
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
5672
6373
|
}
|
|
@@ -5689,7 +6390,7 @@ async function installContext(input) {
|
|
|
5689
6390
|
}
|
|
5690
6391
|
async function buildContext(input) {
|
|
5691
6392
|
const { ui, root, shown, pm } = input;
|
|
5692
|
-
const rnessDir =
|
|
6393
|
+
const rnessDir = join20(root, ".rness");
|
|
5693
6394
|
const pmVersion = await packageManagerVersion(pm);
|
|
5694
6395
|
await mkdir4(root, { recursive: true });
|
|
5695
6396
|
try {
|
|
@@ -5761,7 +6462,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
5761
6462
|
|
|
5762
6463
|
// src/commands/create-blank.ts
|
|
5763
6464
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
5764
|
-
import { join as
|
|
6465
|
+
import { join as join21, relative as relative3, resolve as resolve4 } from "path";
|
|
5765
6466
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
5766
6467
|
function workspaceNameError(name) {
|
|
5767
6468
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -5834,7 +6535,7 @@ async function createBlank(input) {
|
|
|
5834
6535
|
provider: null,
|
|
5835
6536
|
org: null,
|
|
5836
6537
|
agents,
|
|
5837
|
-
|
|
6538
|
+
projects: null,
|
|
5838
6539
|
repos: {},
|
|
5839
6540
|
scopes: {}
|
|
5840
6541
|
},
|
|
@@ -5843,8 +6544,8 @@ async function createBlank(input) {
|
|
|
5843
6544
|
kind: "blank workspace"
|
|
5844
6545
|
});
|
|
5845
6546
|
if (!built) return 1;
|
|
5846
|
-
await mkdir5(
|
|
5847
|
-
await commitContext({ ui, rnessDir:
|
|
6547
|
+
await mkdir5(join21(root, "org"), { recursive: true });
|
|
6548
|
+
await commitContext({ ui, rnessDir: join21(root, ".rness"), shown });
|
|
5848
6549
|
const code = await syncBlocks(
|
|
5849
6550
|
ui,
|
|
5850
6551
|
root,
|
|
@@ -6004,7 +6705,7 @@ async function cloneSelection(input) {
|
|
|
6004
6705
|
{ doing: `cloning org/${repo}`, git: true },
|
|
6005
6706
|
() => clone(
|
|
6006
6707
|
entry.url,
|
|
6007
|
-
|
|
6708
|
+
join22(input.root, "org", repo),
|
|
6008
6709
|
input.provider.credentialsFor(entry.url)
|
|
6009
6710
|
),
|
|
6010
6711
|
() => ["cloned", `org/${repo}`]
|
|
@@ -6349,13 +7050,13 @@ async function createCommand(opts, deps = {}) {
|
|
|
6349
7050
|
provider: chosenProvider,
|
|
6350
7051
|
org,
|
|
6351
7052
|
agents: null,
|
|
6352
|
-
|
|
7053
|
+
projects: null,
|
|
6353
7054
|
repos: {},
|
|
6354
7055
|
scopes: {}
|
|
6355
7056
|
};
|
|
6356
7057
|
const staged = async () => {
|
|
6357
|
-
staging ??= await mkdtemp2(
|
|
6358
|
-
return
|
|
7058
|
+
staging ??= await mkdtemp2(join22(tmpdir2(), "rness-join-"));
|
|
7059
|
+
return join22(staging, ".rness");
|
|
6359
7060
|
};
|
|
6360
7061
|
if (joining) {
|
|
6361
7062
|
const stagedDir = await staged();
|
|
@@ -6417,7 +7118,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
6417
7118
|
});
|
|
6418
7119
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
6419
7120
|
}
|
|
6420
|
-
const rnessDir =
|
|
7121
|
+
const rnessDir = join22(root, ".rness");
|
|
6421
7122
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
6422
7123
|
if (joining) {
|
|
6423
7124
|
await mkdir6(root, { recursive: true });
|
|
@@ -6430,7 +7131,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
6430
7131
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
6431
7132
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
6432
7133
|
await install();
|
|
6433
|
-
await mkdir6(
|
|
7134
|
+
await mkdir6(join22(root, "org"), { recursive: true });
|
|
6434
7135
|
const failures2 = await cloneSelection({
|
|
6435
7136
|
root,
|
|
6436
7137
|
rnessDir,
|
|
@@ -6483,7 +7184,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
6483
7184
|
kind: "new workspace"
|
|
6484
7185
|
});
|
|
6485
7186
|
if (!built) return 1;
|
|
6486
|
-
await mkdir6(
|
|
7187
|
+
await mkdir6(join22(root, "org"), { recursive: true });
|
|
6487
7188
|
const failures = await cloneSelection({
|
|
6488
7189
|
root,
|
|
6489
7190
|
rnessDir,
|
|
@@ -6555,7 +7256,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
6555
7256
|
}
|
|
6556
7257
|
|
|
6557
7258
|
// src/core/blocks.ts
|
|
6558
|
-
import { join as
|
|
7259
|
+
import { join as join23 } from "path";
|
|
6559
7260
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
6560
7261
|
async function checkBlocks({
|
|
6561
7262
|
root,
|
|
@@ -6572,7 +7273,7 @@ async function checkBlocks({
|
|
|
6572
7273
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
6573
7274
|
checks
|
|
6574
7275
|
});
|
|
6575
|
-
if (!await exists(
|
|
7276
|
+
if (!await exists(join23(root, "org"))) return derive();
|
|
6576
7277
|
const targets = [
|
|
6577
7278
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
6578
7279
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -6581,7 +7282,7 @@ async function checkBlocks({
|
|
|
6581
7282
|
const segments = entry.path.split("/");
|
|
6582
7283
|
targets.push({
|
|
6583
7284
|
scope: name,
|
|
6584
|
-
dir:
|
|
7285
|
+
dir: join23(root, ...segments),
|
|
6585
7286
|
depth: segments.length,
|
|
6586
7287
|
file: "AGENTS.md",
|
|
6587
7288
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -6596,7 +7297,7 @@ async function checkBlocks({
|
|
|
6596
7297
|
);
|
|
6597
7298
|
continue;
|
|
6598
7299
|
}
|
|
6599
|
-
const text = await readOrNull(
|
|
7300
|
+
const text = await readOrNull(join23(t.dir, t.file));
|
|
6600
7301
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
6601
7302
|
const found = findBlock(lines);
|
|
6602
7303
|
if (found.kind === "none") {
|
|
@@ -6665,9 +7366,9 @@ async function logoutCommand() {
|
|
|
6665
7366
|
|
|
6666
7367
|
// src/commands/upgrade.ts
|
|
6667
7368
|
import { execFile as execFile6 } from "child_process";
|
|
6668
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
7369
|
+
import { mkdtemp as mkdtemp3, readFile as readFile10, rm as rm7 } from "fs/promises";
|
|
6669
7370
|
import { tmpdir as tmpdir3 } from "os";
|
|
6670
|
-
import { basename as basename5, join as
|
|
7371
|
+
import { basename as basename5, join as join24, relative as relative5 } from "path";
|
|
6671
7372
|
import { promisify as promisify6 } from "util";
|
|
6672
7373
|
var execFileP6 = promisify6(execFile6);
|
|
6673
7374
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -6687,10 +7388,10 @@ async function latestVersion() {
|
|
|
6687
7388
|
async function scaffoldFor(version, rnessDir) {
|
|
6688
7389
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
6689
7390
|
const installed = await readPinnedCli(rnessDir);
|
|
6690
|
-
const inside2 = installed === null ? null :
|
|
6691
|
-
if (installed?.version === version && inside2 !== null && await exists(
|
|
7391
|
+
const inside2 = installed === null ? null : join24(installed.dir, "scaffold");
|
|
7392
|
+
if (installed?.version === version && inside2 !== null && await exists(join24(inside2, "package.json")))
|
|
6692
7393
|
return { dir: inside2 };
|
|
6693
|
-
const scratch = await mkdtemp3(
|
|
7394
|
+
const scratch = await mkdtemp3(join24(tmpdir3(), "rness-pack-"));
|
|
6694
7395
|
const cleanup = () => rm7(scratch, { recursive: true, force: true });
|
|
6695
7396
|
try {
|
|
6696
7397
|
const { stdout } = await execFileP6(
|
|
@@ -6705,8 +7406,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
6705
7406
|
{ timeout: 6e4 }
|
|
6706
7407
|
);
|
|
6707
7408
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
6708
|
-
await execFileP6("tar", ["-xzf",
|
|
6709
|
-
return { dir:
|
|
7409
|
+
await execFileP6("tar", ["-xzf", join24(scratch, tarball), "-C", scratch]);
|
|
7410
|
+
return { dir: join24(scratch, "package", "scaffold"), cleanup };
|
|
6710
7411
|
} catch (e) {
|
|
6711
7412
|
await cleanup();
|
|
6712
7413
|
throw new Error(
|
|
@@ -6718,7 +7419,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
6718
7419
|
async function declaredManager(rnessDir, pm) {
|
|
6719
7420
|
try {
|
|
6720
7421
|
const pkg = JSON.parse(
|
|
6721
|
-
await
|
|
7422
|
+
await readFile10(join24(rnessDir, "package.json"), "utf8")
|
|
6722
7423
|
);
|
|
6723
7424
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
6724
7425
|
} catch {
|
|
@@ -6728,7 +7429,7 @@ async function declaredManager(rnessDir, pm) {
|
|
|
6728
7429
|
async function clonesToCommit(root, cwd, manifest, files) {
|
|
6729
7430
|
const clones = [];
|
|
6730
7431
|
for (const repo of Object.keys(manifest.repos)) {
|
|
6731
|
-
const dir =
|
|
7432
|
+
const dir = join24(root, "org", repo);
|
|
6732
7433
|
if (!await exists(dir)) continue;
|
|
6733
7434
|
try {
|
|
6734
7435
|
const paths = await changedPaths(dir, files);
|
|
@@ -6746,7 +7447,7 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
6746
7447
|
try {
|
|
6747
7448
|
const { stdout } = await execFileP6(
|
|
6748
7449
|
process.execPath,
|
|
6749
|
-
[
|
|
7450
|
+
[join24(pinned.dir, pinned.bin), "written-files", "--cwd", root],
|
|
6750
7451
|
{
|
|
6751
7452
|
cwd: root,
|
|
6752
7453
|
env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
|
|
@@ -6942,9 +7643,9 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
6942
7643
|
await source.cleanup?.();
|
|
6943
7644
|
}
|
|
6944
7645
|
}
|
|
6945
|
-
const packageFile =
|
|
7646
|
+
const packageFile = join24(ws.rnessDir, "package.json");
|
|
6946
7647
|
const restore = /* @__PURE__ */ new Map([
|
|
6947
|
-
[packageFile, await
|
|
7648
|
+
[packageFile, await readFile10(packageFile, "utf8")]
|
|
6948
7649
|
]);
|
|
6949
7650
|
const moved = await readPin(ws.rnessDir);
|
|
6950
7651
|
if (moved !== null && moved.spec !== target2)
|
|
@@ -7138,8 +7839,8 @@ import { realpath as realpath4 } from "fs/promises";
|
|
|
7138
7839
|
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
7139
7840
|
|
|
7140
7841
|
// src/mcp/tools.ts
|
|
7141
|
-
import { readFile as
|
|
7142
|
-
import { isAbsolute, join as
|
|
7842
|
+
import { readFile as readFile11, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
7843
|
+
import { isAbsolute, join as join25, posix as posix7, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
7143
7844
|
var LABELS = {
|
|
7144
7845
|
standards: "Standards",
|
|
7145
7846
|
adr: "Decisions",
|
|
@@ -7168,7 +7869,7 @@ async function documents(rnessDir, only) {
|
|
|
7168
7869
|
const docs = [];
|
|
7169
7870
|
for (const collection of COLLECTIONS) {
|
|
7170
7871
|
if (only !== void 0 && collection !== only) continue;
|
|
7171
|
-
for (const item of await collectMarkdown(
|
|
7872
|
+
for (const item of await collectMarkdown(join25(rnessDir, collection))) {
|
|
7172
7873
|
const path = `${collection}/${item.rel}`;
|
|
7173
7874
|
if (path === TEMPLATE2) continue;
|
|
7174
7875
|
const status2 = item.fields?.["status"];
|
|
@@ -7290,7 +7991,7 @@ async function read(cwd, args) {
|
|
|
7290
7991
|
return refuse(
|
|
7291
7992
|
`${path}: ${info.size} bytes, over the 256 KiB rness_read returns`
|
|
7292
7993
|
);
|
|
7293
|
-
return { text: await
|
|
7994
|
+
return { text: await readFile11(real, "utf8") };
|
|
7294
7995
|
}
|
|
7295
7996
|
async function search(cwd, args) {
|
|
7296
7997
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -7398,8 +8099,8 @@ function mcpTools(cwd) {
|
|
|
7398
8099
|
|
|
7399
8100
|
// src/pulse/detached.ts
|
|
7400
8101
|
import { spawn as nodeSpawn } from "child_process";
|
|
7401
|
-
import { mkdir as mkdir7, open as open2, readFile as
|
|
7402
|
-
import { join as
|
|
8102
|
+
import { mkdir as mkdir7, open as open2, readFile as readFile12, rm as rm8, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
8103
|
+
import { join as join26 } from "path";
|
|
7403
8104
|
import { setTimeout as delay2 } from "timers/promises";
|
|
7404
8105
|
var detached = (args, cwd) => {
|
|
7405
8106
|
const bin = process.argv[1];
|
|
@@ -7413,7 +8114,7 @@ var detached = (args, cwd) => {
|
|
|
7413
8114
|
child.on("error", () => void 0);
|
|
7414
8115
|
child.unref();
|
|
7415
8116
|
};
|
|
7416
|
-
var failureFile = () =>
|
|
8117
|
+
var failureFile = () => join26(configDir(), "pulse.json");
|
|
7417
8118
|
async function recordFailure(reason3) {
|
|
7418
8119
|
try {
|
|
7419
8120
|
await mkdir7(configDir(), { recursive: true, mode: 448 });
|
|
@@ -7427,7 +8128,7 @@ async function recordFailure(reason3) {
|
|
|
7427
8128
|
}
|
|
7428
8129
|
async function takeFailure() {
|
|
7429
8130
|
try {
|
|
7430
|
-
const data = JSON.parse(await
|
|
8131
|
+
const data = JSON.parse(await readFile12(failureFile(), "utf8"));
|
|
7431
8132
|
await rm8(failureFile(), { force: true });
|
|
7432
8133
|
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
7433
8134
|
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
@@ -7438,7 +8139,7 @@ async function takeFailure() {
|
|
|
7438
8139
|
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
7439
8140
|
async function oneSyncAtATime(key, work, options = {}) {
|
|
7440
8141
|
await mkdir7(configDir(), { recursive: true, mode: 448 });
|
|
7441
|
-
const file =
|
|
8142
|
+
const file = join26(configDir(), `pulse-${key}.lock`);
|
|
7442
8143
|
for (; ; ) {
|
|
7443
8144
|
try {
|
|
7444
8145
|
const handle = await open2(file, "wx");
|
|
@@ -7524,7 +8225,7 @@ function sessionOf(input) {
|
|
|
7524
8225
|
}
|
|
7525
8226
|
async function hasPulse(ws) {
|
|
7526
8227
|
try {
|
|
7527
|
-
return (await loadManifest(ws.rnessDir)).
|
|
8228
|
+
return (await loadManifest(ws.rnessDir)).projects !== null;
|
|
7528
8229
|
} catch {
|
|
7529
8230
|
return false;
|
|
7530
8231
|
}
|
|
@@ -7834,12 +8535,12 @@ async function mcpCommand(opts, io = {
|
|
|
7834
8535
|
}
|
|
7835
8536
|
|
|
7836
8537
|
// src/commands/pulse.ts
|
|
7837
|
-
import { readFile as
|
|
7838
|
-
import { join as
|
|
8538
|
+
import { readFile as readFile14, readdir as readdir5 } from "fs/promises";
|
|
8539
|
+
import { join as join27 } from "path";
|
|
7839
8540
|
import { setTimeout as delay3 } from "timers/promises";
|
|
7840
8541
|
|
|
7841
8542
|
// src/pulse/opened.ts
|
|
7842
|
-
import { mkdir as mkdir8, readFile as
|
|
8543
|
+
import { mkdir as mkdir8, readFile as readFile13, rename as rename3, rm as rm9, writeFile as writeFile5 } from "fs/promises";
|
|
7843
8544
|
import { dirname as dirname9 } from "path";
|
|
7844
8545
|
var NAME2 = "rness/opened";
|
|
7845
8546
|
function openedFile(rnessDir) {
|
|
@@ -7851,7 +8552,7 @@ async function readOpened(file) {
|
|
|
7851
8552
|
if (file === null) return opened;
|
|
7852
8553
|
let data;
|
|
7853
8554
|
try {
|
|
7854
|
-
data = JSON.parse(await
|
|
8555
|
+
data = JSON.parse(await readFile13(file, "utf8"));
|
|
7855
8556
|
} catch {
|
|
7856
8557
|
return opened;
|
|
7857
8558
|
}
|
|
@@ -7918,20 +8619,81 @@ async function missingAccess(provider) {
|
|
|
7918
8619
|
const scopes = await fromGithub(provider.scopes());
|
|
7919
8620
|
return scopes?.includes(PROJECT_SCOPE) === true ? null : NEEDS_SCOPE;
|
|
7920
8621
|
}
|
|
7921
|
-
async function
|
|
7922
|
-
|
|
8622
|
+
async function declaredBoards(c) {
|
|
8623
|
+
const projects = c.manifest.projects;
|
|
8624
|
+
if (projects === null)
|
|
7923
8625
|
throw new Error("no pulse declared \u2014 rness pulse create");
|
|
7924
8626
|
const missing = await missingAccess(c.provider);
|
|
7925
8627
|
if (missing !== null) throw new Error(missing);
|
|
7926
|
-
const
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
8628
|
+
const names = Object.keys(projects).sort(
|
|
8629
|
+
(a, b) => Number(a === PULSE) - Number(b === PULSE)
|
|
8630
|
+
);
|
|
8631
|
+
const declared2 = [];
|
|
8632
|
+
for (const name of names) {
|
|
8633
|
+
const number = projects[name];
|
|
8634
|
+
const board = await fromGithub(c.provider.board(c.org, number));
|
|
8635
|
+
if (board === null)
|
|
8636
|
+
throw new Error(`GitHub has no project ${number} in ${c.org}`);
|
|
8637
|
+
declared2.push({ name, board });
|
|
8638
|
+
}
|
|
8639
|
+
return declared2;
|
|
8640
|
+
}
|
|
8641
|
+
var labelOf = (name) => `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
8642
|
+
async function directoriesOf(dir) {
|
|
8643
|
+
try {
|
|
8644
|
+
return (await readdir5(dir, { withFileTypes: true })).filter(
|
|
8645
|
+
(e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "updates"
|
|
8646
|
+
).map((e) => e.name).sort();
|
|
8647
|
+
} catch {
|
|
8648
|
+
return [];
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
async function scopeOf(c, name, tabs, owners) {
|
|
8652
|
+
const within = (collection) => (path) => path.startsWith(`${collection}/`);
|
|
8653
|
+
if (name === PULSE) {
|
|
8654
|
+
const shown = tabs.filter((t) => !owners.includes(t.name));
|
|
8655
|
+
const elsewhere = (path) => owners.some((o) => within(o)(path));
|
|
8656
|
+
return {
|
|
8657
|
+
label: "Agent Pulse",
|
|
8658
|
+
want: desiredOf(shown, c.org),
|
|
8659
|
+
layout: layoutOf(shown),
|
|
8660
|
+
elsewhere,
|
|
8661
|
+
mine: (path) => !elsewhere(path),
|
|
8662
|
+
own: null
|
|
8663
|
+
};
|
|
8664
|
+
}
|
|
8665
|
+
const tab = tabs.find((t) => t.name === name) ?? {
|
|
8666
|
+
name,
|
|
8667
|
+
label: labelOf(name),
|
|
8668
|
+
rows: []
|
|
8669
|
+
};
|
|
8670
|
+
const dir = join27(c.rnessDir, name);
|
|
8671
|
+
const shape = await readShape(c.rnessDir, name);
|
|
8672
|
+
const fronts = new Map(
|
|
8673
|
+
(await collectMarkdown(dir)).map((item) => [
|
|
8674
|
+
`${name}/${item.rel}`,
|
|
8675
|
+
item.fields ?? {}
|
|
8676
|
+
])
|
|
8677
|
+
);
|
|
8678
|
+
const want = desiredOf([tab], c.org, {
|
|
8679
|
+
shape,
|
|
8680
|
+
fronts,
|
|
8681
|
+
directories: await directoriesOf(dir)
|
|
8682
|
+
});
|
|
8683
|
+
return {
|
|
8684
|
+
label: tab.label,
|
|
8685
|
+
want,
|
|
8686
|
+
layout: layoutOf([tab], { shape, want }),
|
|
8687
|
+
elsewhere: () => false,
|
|
8688
|
+
mine: within(name),
|
|
8689
|
+
own: { shape, updates: await readUpdates(c.rnessDir, name) }
|
|
8690
|
+
};
|
|
7931
8691
|
}
|
|
7932
8692
|
var plural = (n) => `${n} item${n === 1 ? "" : "s"}`;
|
|
7933
8693
|
var eachAdded = (ui, added) => {
|
|
7934
|
-
for (const a of added)
|
|
8694
|
+
for (const a of added)
|
|
8695
|
+
if (a.startsWith("renamed ")) ui.line("renamed", a.slice("renamed ".length));
|
|
8696
|
+
else ui.line("added", a);
|
|
7935
8697
|
};
|
|
7936
8698
|
var createdByKind = (ui, added) => {
|
|
7937
8699
|
for (const kind of ["field", "option", "view"]) {
|
|
@@ -7947,8 +8709,8 @@ var createdByKind = (ui, added) => {
|
|
|
7947
8709
|
async function filesOf2(rnessDir) {
|
|
7948
8710
|
const files = /* @__PURE__ */ new Set();
|
|
7949
8711
|
const walk = async (rel) => {
|
|
7950
|
-
const dir = rel === "" ? rnessDir :
|
|
7951
|
-
for (const e of await
|
|
8712
|
+
const dir = rel === "" ? rnessDir : join27(rnessDir, ...rel.split("/"));
|
|
8713
|
+
for (const e of await readdir5(dir, { withFileTypes: true })) {
|
|
7952
8714
|
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
7953
8715
|
if (e.isDirectory()) {
|
|
7954
8716
|
if (e.name !== ".git" && e.name !== "node_modules") await walk(path);
|
|
@@ -7959,7 +8721,7 @@ async function filesOf2(rnessDir) {
|
|
|
7959
8721
|
return files;
|
|
7960
8722
|
}
|
|
7961
8723
|
var count2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
7962
|
-
function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
8724
|
+
function sayDone(ui, org, documents2, steps, writes, adopted, on) {
|
|
7963
8725
|
const n = (kind) => steps.filter((s) => s.kind === kind).length - (kind === "create" ? adopted : 0);
|
|
7964
8726
|
const reopened = steps.filter((s) => s.kind === "update" && s.reopen).length;
|
|
7965
8727
|
if (n("create") > 0) ui.line("created", count2(n("create"), "issue"));
|
|
@@ -7974,6 +8736,8 @@ function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
|
7974
8736
|
`${count2(n("convert"), "draft")} into issues of ${org}/.rness`
|
|
7975
8737
|
);
|
|
7976
8738
|
if (reopened > 0) ui.line("reopened", count2(reopened, "issue"));
|
|
8739
|
+
if (n("remove") > 0)
|
|
8740
|
+
ui.line("moved", `${plural(n("remove"))} to their collection's project`);
|
|
7977
8741
|
const written = new Set(writes.map((w) => w.id));
|
|
7978
8742
|
const bodyOnly = steps.filter(
|
|
7979
8743
|
(s) => s.kind === "unchanged" && written.has(s.id)
|
|
@@ -7991,25 +8755,23 @@ function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
|
7991
8755
|
].filter(([, k]) => k > 0).map(([word, k]) => `${k} ${word}`);
|
|
7992
8756
|
ui.line(
|
|
7993
8757
|
"synced",
|
|
7994
|
-
`${plural(documents2)}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`
|
|
8758
|
+
`${plural(documents2)}${on === void 0 ? "" : ` on ${on}`}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`
|
|
7995
8759
|
);
|
|
7996
8760
|
}
|
|
7997
|
-
async function syncBoard(c, board, ui, sayLayout) {
|
|
8761
|
+
async function syncBoard(c, board, ui, sayLayout, scope) {
|
|
7998
8762
|
await fromGithub(c.provider.checkIssues(c.org));
|
|
7999
|
-
const
|
|
8000
|
-
|
|
8001
|
-
sayLayout(
|
|
8002
|
-
ui,
|
|
8003
|
-
await fromGithub(c.provider.ensureLayout(board, layoutOf(tabs)))
|
|
8004
|
-
);
|
|
8763
|
+
const { want } = scope;
|
|
8764
|
+
sayLayout(ui, await fromGithub(c.provider.ensureLayout(board, scope.layout)));
|
|
8005
8765
|
const have = await fromGithub(c.provider.items(board));
|
|
8006
8766
|
const record = await openedFile(c.rnessDir);
|
|
8007
|
-
const
|
|
8767
|
+
const all = await readOpened(record);
|
|
8768
|
+
const opened = new Map([...all].filter(([, path]) => scope.mine(path)));
|
|
8769
|
+
const others = [...all].filter(([, path]) => !scope.mine(path));
|
|
8008
8770
|
const seen = await seenPaths(c.rnessDir, [
|
|
8009
8771
|
...unwantedPaths(want, have),
|
|
8010
8772
|
...opened.values()
|
|
8011
8773
|
]);
|
|
8012
|
-
const steps = planSync(want, have, seen, opened);
|
|
8774
|
+
const steps = planSync(want, have, seen, opened, scope.elsewhere);
|
|
8013
8775
|
const changes = steps.filter((s) => s.kind !== "unchanged").length;
|
|
8014
8776
|
let writes = [];
|
|
8015
8777
|
let made = 0;
|
|
@@ -8033,7 +8795,10 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8033
8795
|
} finally {
|
|
8034
8796
|
await writeOpened(
|
|
8035
8797
|
record,
|
|
8036
|
-
|
|
8798
|
+
new Map([
|
|
8799
|
+
...others,
|
|
8800
|
+
...stillOpened(opened, have, seen, closed, created)
|
|
8801
|
+
])
|
|
8037
8802
|
);
|
|
8038
8803
|
}
|
|
8039
8804
|
const issued = issuedAfter(steps, have, placed);
|
|
@@ -8045,7 +8810,7 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8045
8810
|
w.path,
|
|
8046
8811
|
issueBody({
|
|
8047
8812
|
path: w.path,
|
|
8048
|
-
text: await
|
|
8813
|
+
text: await readFile14(join27(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
8049
8814
|
org: c.org,
|
|
8050
8815
|
numbers,
|
|
8051
8816
|
files
|
|
@@ -8064,7 +8829,43 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8064
8829
|
{ cause: e }
|
|
8065
8830
|
);
|
|
8066
8831
|
}
|
|
8067
|
-
sayDone(
|
|
8832
|
+
sayDone(
|
|
8833
|
+
ui,
|
|
8834
|
+
c.org,
|
|
8835
|
+
want.length,
|
|
8836
|
+
steps,
|
|
8837
|
+
writes,
|
|
8838
|
+
adopted,
|
|
8839
|
+
scope.own === null ? void 0 : scope.label
|
|
8840
|
+
);
|
|
8841
|
+
if (scope.own !== null) {
|
|
8842
|
+
const { shape, updates } = scope.own;
|
|
8843
|
+
for (const what of await fromGithub(
|
|
8844
|
+
c.provider.describe(board, {
|
|
8845
|
+
readme: shape.readme,
|
|
8846
|
+
description: shape.description
|
|
8847
|
+
})
|
|
8848
|
+
))
|
|
8849
|
+
ui.line("wrote", `${what} of ${scope.label}`);
|
|
8850
|
+
for (const line3 of await fromGithub(
|
|
8851
|
+
c.provider.postUpdates(board, updates)
|
|
8852
|
+
)) {
|
|
8853
|
+
const [verb, ...rest] = line3.split(" ");
|
|
8854
|
+
ui.line(verb ?? "posted", rest.join(" "));
|
|
8855
|
+
}
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
async function syncAll(c, declared2, ui, sayLayout) {
|
|
8859
|
+
const tabs = await statusTabs(c.rnessDir);
|
|
8860
|
+
const owners = declared2.map((d) => d.name).filter((n) => n !== PULSE);
|
|
8861
|
+
for (const { name, board } of declared2)
|
|
8862
|
+
await syncBoard(
|
|
8863
|
+
c,
|
|
8864
|
+
board,
|
|
8865
|
+
ui,
|
|
8866
|
+
sayLayout(name),
|
|
8867
|
+
await scopeOf(c, name, tabs, owners)
|
|
8868
|
+
);
|
|
8068
8869
|
}
|
|
8069
8870
|
async function pulseCreateCommand(opts, deps = {}) {
|
|
8070
8871
|
try {
|
|
@@ -8072,9 +8873,20 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
8072
8873
|
const ui = deps.ui ?? await makeUi(terminal);
|
|
8073
8874
|
const wait = waitSaid(opts, ui);
|
|
8074
8875
|
let c = await context2(opts, wait);
|
|
8075
|
-
|
|
8876
|
+
const name = opts.collection ?? PULSE;
|
|
8877
|
+
const declared2 = c.manifest.projects?.[name];
|
|
8878
|
+
if (opts.collection === PULSE)
|
|
8879
|
+
throw new Error('"pulse" names Agent Pulse: rness pulse create');
|
|
8880
|
+
if (declared2 !== void 0)
|
|
8881
|
+
throw new Error(
|
|
8882
|
+
`already declared: ${boardUrl(c.org, declared2)} \u2014 rness pulse sync`
|
|
8883
|
+
);
|
|
8884
|
+
const tab = opts.collection === void 0 ? null : (await statusTabs(c.rnessDir)).find(
|
|
8885
|
+
(t) => t.name === opts.collection
|
|
8886
|
+
) ?? null;
|
|
8887
|
+
if (opts.collection !== void 0 && tab === null)
|
|
8076
8888
|
throw new Error(
|
|
8077
|
-
|
|
8889
|
+
`${opts.collection} is no collection of .rness: none of its documents carries a status`
|
|
8078
8890
|
);
|
|
8079
8891
|
const detected = providerOf(c.manifest);
|
|
8080
8892
|
if (!PROVIDERS[detected].available)
|
|
@@ -8112,19 +8924,26 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
8112
8924
|
"checked",
|
|
8113
8925
|
`${detected}, logged in as ${login}, scope ${PROJECT_SCOPE}`
|
|
8114
8926
|
);
|
|
8115
|
-
const board = await fromGithub(c.provider.createBoard(c.org));
|
|
8116
|
-
|
|
8927
|
+
const board = await fromGithub(c.provider.createBoard(c.org, tab?.label));
|
|
8928
|
+
const manifest = {
|
|
8117
8929
|
...c.manifest,
|
|
8118
8930
|
provider: detected,
|
|
8119
|
-
|
|
8120
|
-
}
|
|
8121
|
-
|
|
8931
|
+
projects: { ...c.manifest.projects, [name]: board.number }
|
|
8932
|
+
};
|
|
8933
|
+
await writeManifest(c.rnessDir, manifest);
|
|
8934
|
+
c = { ...c, manifest };
|
|
8935
|
+
ui.line("created", `${tab?.label ?? "Agent Pulse"} \u2014 ${board.url}`);
|
|
8122
8936
|
try {
|
|
8123
|
-
await
|
|
8937
|
+
await syncAll(
|
|
8938
|
+
c,
|
|
8939
|
+
await declaredBoards(c),
|
|
8940
|
+
ui,
|
|
8941
|
+
(n) => n === name ? createdByKind : eachAdded
|
|
8942
|
+
);
|
|
8124
8943
|
} finally {
|
|
8125
8944
|
ui.line(
|
|
8126
8945
|
"declared",
|
|
8127
|
-
|
|
8946
|
+
`${name} in .rness/rness.json \u2014 commit it: git -C .rness commit -am "chore: rness pulse${opts.collection === void 0 ? "" : ` ${opts.collection}`}"`
|
|
8128
8947
|
);
|
|
8129
8948
|
}
|
|
8130
8949
|
return 0;
|
|
@@ -8136,7 +8955,7 @@ async function pulseSyncCommand(opts, deps = {}) {
|
|
|
8136
8955
|
try {
|
|
8137
8956
|
const ui = deps.ui ?? await makeUi(deps.terminal ?? defaultTerminal);
|
|
8138
8957
|
const c = await context2(opts, waitSaid(opts, ui));
|
|
8139
|
-
await
|
|
8958
|
+
await syncAll(c, await declaredBoards(c), ui, () => eachAdded);
|
|
8140
8959
|
return 0;
|
|
8141
8960
|
} catch (e) {
|
|
8142
8961
|
return reportError(e);
|
|
@@ -8152,43 +8971,47 @@ async function pulseMarkCommand(opts) {
|
|
|
8152
8971
|
}
|
|
8153
8972
|
async function mark(opts) {
|
|
8154
8973
|
const c = await context2(opts);
|
|
8155
|
-
const
|
|
8156
|
-
const key = `${c.org}-${board.number}`;
|
|
8974
|
+
const declared2 = await declaredBoards(c);
|
|
8975
|
+
const key = `${c.org}-${declared2.map((d) => d.board.number).join("-")}`;
|
|
8157
8976
|
const lock = opts.sleep === void 0 ? {} : { sleep: opts.sleep };
|
|
8158
|
-
const
|
|
8159
|
-
|
|
8977
|
+
const syncEvery = () => syncAll(c, declared2, plainUi, () => eachAdded);
|
|
8978
|
+
const listed = (board) => fromGithub(c.provider.items(board, { bodies: false }));
|
|
8160
8979
|
if (opts.end === true) {
|
|
8161
8980
|
const id = opts.session.split(" \xB7 ").pop() ?? opts.session;
|
|
8162
|
-
const
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
});
|
|
8981
|
+
for (const { board } of declared2) {
|
|
8982
|
+
const ids = (await listed(board)).filter(
|
|
8983
|
+
(i) => i.session === opts.session || i.session?.endsWith(` \xB7 ${id}`)
|
|
8984
|
+
).map((i) => i.id);
|
|
8985
|
+
await fromGithub(c.provider.mark(board, ids, null));
|
|
8986
|
+
}
|
|
8987
|
+
await oneSyncAtATime(key, syncEvery, { ...lock, wait: true });
|
|
8170
8988
|
return 0;
|
|
8171
8989
|
}
|
|
8172
|
-
const
|
|
8990
|
+
const held = (path) => declared2.some((d) => d.name === PULSE || path.startsWith(`${d.name}/`));
|
|
8991
|
+
const wanted = new Set(opts.paths.filter(held));
|
|
8173
8992
|
const documents2 = new Map(
|
|
8174
8993
|
(await statusTabs(c.rnessDir)).flatMap(
|
|
8175
8994
|
(t) => t.rows.map((r) => [r.path, r.status])
|
|
8176
8995
|
)
|
|
8177
8996
|
);
|
|
8997
|
+
let items = /* @__PURE__ */ new Map();
|
|
8998
|
+
const listAll = async () => {
|
|
8999
|
+
items = /* @__PURE__ */ new Map();
|
|
9000
|
+
for (const { name, board } of declared2) items.set(name, await listed(board));
|
|
9001
|
+
};
|
|
9002
|
+
await listAll();
|
|
8178
9003
|
const onBoard = new Map(
|
|
8179
|
-
items.flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
|
|
9004
|
+
[...items.values()].flat().flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
|
|
8180
9005
|
);
|
|
8181
9006
|
const stale = (path) => documents2.has(path) && (!onBoard.has(path) || onBoard.get(path) !== documents2.get(path));
|
|
8182
9007
|
if ([...wanted].some(stale)) {
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
)
|
|
8188
|
-
|
|
9008
|
+
if (await oneSyncAtATime(key, syncEvery, lock)) await listAll();
|
|
9009
|
+
}
|
|
9010
|
+
for (const { name, board } of declared2) {
|
|
9011
|
+
const ids = (items.get(name) ?? []).filter((i) => i.path !== null && wanted.has(i.path)).map((i) => i.id);
|
|
9012
|
+
if (ids.length > 0)
|
|
9013
|
+
await fromGithub(c.provider.mark(board, ids, opts.session));
|
|
8189
9014
|
}
|
|
8190
|
-
const ids = items.filter((i) => i.path !== null && wanted.has(i.path)).map((i) => i.id);
|
|
8191
|
-
await fromGithub(c.provider.mark(board, ids, opts.session));
|
|
8192
9015
|
return 0;
|
|
8193
9016
|
}
|
|
8194
9017
|
|
|
@@ -8490,10 +9313,19 @@ function buildProgram(state) {
|
|
|
8490
9313
|
state.code = await syncCommand(opts);
|
|
8491
9314
|
});
|
|
8492
9315
|
const pulse = program.command("pulse").description("Show the agent's work on the organization's board");
|
|
8493
|
-
pulse.command("create").description(
|
|
8494
|
-
|
|
9316
|
+
pulse.command("create").description(
|
|
9317
|
+
"Create the organization's Agent Pulse board, or a collection's own project, and sync"
|
|
9318
|
+
).argument(
|
|
9319
|
+
"[collection]",
|
|
9320
|
+
"a directory of .rness/ whose documents get a project of their own"
|
|
9321
|
+
).option("-y, --yes", "do not ask for confirmation").addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (collection, opts) => {
|
|
9322
|
+
state.code = await pulseCreateCommand(
|
|
9323
|
+
collection === void 0 ? opts : { ...opts, collection }
|
|
9324
|
+
);
|
|
8495
9325
|
});
|
|
8496
|
-
pulse.command("sync").description(
|
|
9326
|
+
pulse.command("sync").description(
|
|
9327
|
+
"Bring every declared project up to date with the documents of .rness/"
|
|
9328
|
+
).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
8497
9329
|
state.code = await pulseSyncCommand(opts);
|
|
8498
9330
|
});
|
|
8499
9331
|
pulse.command("mark", { hidden: true }).requiredOption("--session <id>").option(
|
|
@@ -8723,4 +9555,4 @@ export {
|
|
|
8723
9555
|
OWN_COMMANDS,
|
|
8724
9556
|
catchUp
|
|
8725
9557
|
};
|
|
8726
|
-
//# sourceMappingURL=chunk-
|
|
9558
|
+
//# sourceMappingURL=chunk-X52SHBRY.js.map
|