@rness/cli 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -6
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-ABCO6D6B.js → chunk-GAZDKEOH.js} +1046 -233
- package/dist/chunk-GAZDKEOH.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ABCO6D6B.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.1";
|
|
490
512
|
|
|
491
513
|
// src/core/pm.ts
|
|
492
514
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -1321,6 +1343,196 @@ ${content}`;
|
|
|
1321
1343
|
${comment(digest(text))}`;
|
|
1322
1344
|
}
|
|
1323
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
|
+
|
|
1324
1536
|
// src/pulse/layout.ts
|
|
1325
1537
|
var statusOf2 = (status2) => status2 === null || status2 === "?" ? null : status2;
|
|
1326
1538
|
var LIFECYCLE = [
|
|
@@ -1394,36 +1606,92 @@ function fieldsOf(tabs) {
|
|
|
1394
1606
|
}
|
|
1395
1607
|
return fields2;
|
|
1396
1608
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
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;
|
|
1399
1629
|
return {
|
|
1400
|
-
statuses:
|
|
1401
|
-
|
|
1630
|
+
statuses: once([
|
|
1631
|
+
...shape.statuses ?? [],
|
|
1632
|
+
...tabs.flatMap(collectionStatuses)
|
|
1633
|
+
]),
|
|
1634
|
+
fields: [],
|
|
1402
1635
|
types: tabs.map((t) => t.label),
|
|
1403
|
-
views: tabs.map((t) => ({
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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
|
+
// Made only when a document carries it: a directory with no card is no
|
|
1646
|
+
// label, though one carrying it once is taken off (`unlabels`).
|
|
1647
|
+
labels: once(want.flatMap((w) => w.labels))
|
|
1408
1648
|
};
|
|
1409
1649
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1650
|
+
var sessionsOf2 = (row) => row.sessions?.map((e) => e.agent === void 0 ? e.id : `${e.agent} \xB7 ${e.id}`).join(", ") ?? null;
|
|
1651
|
+
function desiredOf(tabs, org, own) {
|
|
1652
|
+
const fields2 = own === void 0 ? fieldsOf(tabs) : /* @__PURE__ */ new Map();
|
|
1653
|
+
const wanted = tabs.flatMap(
|
|
1654
|
+
(tab) => tab.rows.map((row) => {
|
|
1655
|
+
const { values: values2, labels } = own === void 0 ? { values: {}, labels: [] } : valuesOf(
|
|
1656
|
+
own.shape,
|
|
1657
|
+
own.fronts.get(row.path) ?? {},
|
|
1658
|
+
row.path.slice(tab.name.length + 1)
|
|
1659
|
+
);
|
|
1660
|
+
return {
|
|
1661
|
+
path: row.path,
|
|
1662
|
+
title: `${row.id} \u2014 ${row.title}`,
|
|
1663
|
+
body: headerOf(row.path, org),
|
|
1664
|
+
status: statusOf2(row.status),
|
|
1665
|
+
type: tab.label,
|
|
1666
|
+
statusField: fields2.get(tab.label)?.name ?? null,
|
|
1667
|
+
sessions: sessionsOf2(row),
|
|
1668
|
+
values: values2,
|
|
1669
|
+
labels,
|
|
1670
|
+
unlabels: []
|
|
1671
|
+
};
|
|
1672
|
+
})
|
|
1422
1673
|
);
|
|
1674
|
+
if (own === void 0) return wanted;
|
|
1675
|
+
const made = once([
|
|
1676
|
+
...wanted.flatMap((w) => w.labels),
|
|
1677
|
+
...own.shape.labels?.kind === "directory" ? own.directories ?? [] : []
|
|
1678
|
+
]);
|
|
1679
|
+
return wanted.map((w) => ({
|
|
1680
|
+
...w,
|
|
1681
|
+
unlabels: made.filter((l) => !w.labels.includes(l))
|
|
1682
|
+
}));
|
|
1423
1683
|
}
|
|
1424
1684
|
|
|
1425
1685
|
// src/pulse/plan.ts
|
|
1426
|
-
var
|
|
1686
|
+
var lower = (labels) => new Set(labels.map((l) => l.toLowerCase()));
|
|
1687
|
+
function labelsDiffer(item, w) {
|
|
1688
|
+
if (w.labels.length === 0 && w.unlabels.length === 0) return false;
|
|
1689
|
+
const carried = lower(item.issue?.labels ?? []);
|
|
1690
|
+
return w.labels.some((l) => !carried.has(l.toLowerCase())) || w.unlabels.some((l) => carried.has(l.toLowerCase()));
|
|
1691
|
+
}
|
|
1692
|
+
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(
|
|
1693
|
+
([name, value]) => (item.values[name] ?? null) !== value
|
|
1694
|
+
) || labelsDiffer(item, w);
|
|
1427
1695
|
var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
|
|
1428
1696
|
var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
|
|
1429
1697
|
var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
|
|
@@ -1449,7 +1717,7 @@ function unwantedPaths(want, have) {
|
|
|
1449
1717
|
function isGone(path, item, seen, opened) {
|
|
1450
1718
|
return seen.has(path) || item.issue !== null && opened.get(item.issue.number) === path;
|
|
1451
1719
|
}
|
|
1452
|
-
function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map()) {
|
|
1720
|
+
function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__PURE__ */ new Map(), elsewhere = () => false) {
|
|
1453
1721
|
const byPath = /* @__PURE__ */ new Map();
|
|
1454
1722
|
for (const item of have)
|
|
1455
1723
|
if (item.path !== null)
|
|
@@ -1482,7 +1750,8 @@ function planSync(want, have, seen = /* @__PURE__ */ new Set(), opened = /* @__P
|
|
|
1482
1750
|
for (const [path, item] of kept)
|
|
1483
1751
|
if (!wanted.has(path))
|
|
1484
1752
|
steps.push(
|
|
1485
|
-
|
|
1753
|
+
// Its own project holds it now: off this board, not closed.
|
|
1754
|
+
elsewhere(path) ? { kind: "remove", id: item.id } : isGone(path, item, seen, opened) ? gone(item) : { kind: "unchanged", id: item.id }
|
|
1486
1755
|
);
|
|
1487
1756
|
return [...steps, ...surplus];
|
|
1488
1757
|
}
|
|
@@ -1843,11 +2112,11 @@ async function createRepository(owner, name, options) {
|
|
|
1843
2112
|
}
|
|
1844
2113
|
|
|
1845
2114
|
// src/core/github-projects.ts
|
|
1846
|
-
function
|
|
2115
|
+
function isRecord3(v) {
|
|
1847
2116
|
return v !== null && typeof v === "object";
|
|
1848
2117
|
}
|
|
1849
|
-
var rateLimited = (body) =>
|
|
1850
|
-
(e) =>
|
|
2118
|
+
var rateLimited = (body) => isRecord3(body) && Array.isArray(body["errors"]) && body["errors"].some(
|
|
2119
|
+
(e) => isRecord3(e) && e["type"] === "RATE_LIMITED"
|
|
1851
2120
|
);
|
|
1852
2121
|
async function graphqlRaw(query, variables, options) {
|
|
1853
2122
|
const body = await postJson(
|
|
@@ -1857,7 +2126,7 @@ async function graphqlRaw(query, variables, options) {
|
|
|
1857
2126
|
true,
|
|
1858
2127
|
rateLimited
|
|
1859
2128
|
);
|
|
1860
|
-
return
|
|
2129
|
+
return isRecord3(body) ? body : {};
|
|
1861
2130
|
}
|
|
1862
2131
|
function firstError(answer) {
|
|
1863
2132
|
const first = answer.errors?.[0];
|
|
@@ -1933,6 +2202,7 @@ function toField(raw) {
|
|
|
1933
2202
|
id: raw.id ?? "",
|
|
1934
2203
|
databaseId,
|
|
1935
2204
|
name: raw.name ?? "",
|
|
2205
|
+
dataType: raw.dataType ?? (raw.options === void 0 ? "TEXT" : "SINGLE_SELECT"),
|
|
1936
2206
|
options: raw.options ?? null
|
|
1937
2207
|
};
|
|
1938
2208
|
}
|
|
@@ -1962,10 +2232,10 @@ var optionInput = (o, id) => ({
|
|
|
1962
2232
|
color: o.color,
|
|
1963
2233
|
description: ""
|
|
1964
2234
|
});
|
|
1965
|
-
var FIELD_SELECTION = `... on ProjectV2FieldCommon { id databaseId name }
|
|
2235
|
+
var FIELD_SELECTION = `... on ProjectV2FieldCommon { id databaseId name dataType }
|
|
1966
2236
|
... on ProjectV2SingleSelectField { options { id name color } }`;
|
|
1967
2237
|
async function createField(projectId, name, kind, o) {
|
|
1968
|
-
const single = kind !== "
|
|
2238
|
+
const single = typeof kind !== "string";
|
|
1969
2239
|
const d = await graphql(
|
|
1970
2240
|
`mutation($projectId: ID!, $name: String!, $dataType: ProjectV2CustomFieldType!${single ? ", $options: [ProjectV2SingleSelectFieldOptionInput!]" : ""}) {
|
|
1971
2241
|
createProjectV2Field(input: {
|
|
@@ -1975,7 +2245,7 @@ async function createField(projectId, name, kind, o) {
|
|
|
1975
2245
|
{
|
|
1976
2246
|
projectId,
|
|
1977
2247
|
name,
|
|
1978
|
-
dataType: single ? "SINGLE_SELECT" :
|
|
2248
|
+
dataType: single ? "SINGLE_SELECT" : kind,
|
|
1979
2249
|
...single ? { options: kind.options.map((n) => optionInput(n)) } : {}
|
|
1980
2250
|
},
|
|
1981
2251
|
o
|
|
@@ -2062,6 +2332,22 @@ async function listItems(projectId, o, { bodies = true } = {}) {
|
|
|
2062
2332
|
}
|
|
2063
2333
|
}
|
|
2064
2334
|
}
|
|
2335
|
+
... on ProjectV2ItemFieldDateValue {
|
|
2336
|
+
date
|
|
2337
|
+
field {
|
|
2338
|
+
... on ProjectV2FieldCommon {
|
|
2339
|
+
name
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
2344
|
+
number
|
|
2345
|
+
field {
|
|
2346
|
+
... on ProjectV2FieldCommon {
|
|
2347
|
+
name
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2065
2351
|
}
|
|
2066
2352
|
}
|
|
2067
2353
|
}
|
|
@@ -2081,7 +2367,7 @@ async function listItems(projectId, o, { bodies = true } = {}) {
|
|
|
2081
2367
|
const values2 = {};
|
|
2082
2368
|
for (const v of n.fieldValues?.nodes ?? []) {
|
|
2083
2369
|
const field = v.field?.name;
|
|
2084
|
-
const value = v.text ?? v.name;
|
|
2370
|
+
const value = v.text ?? v.name ?? v.date ?? (v.number === void 0 ? void 0 : String(v.number));
|
|
2085
2371
|
if (field !== void 0 && value !== void 0) values2[field] = value;
|
|
2086
2372
|
}
|
|
2087
2373
|
const c = n.content;
|
|
@@ -2152,7 +2438,7 @@ async function setValue(projectId, itemId, fieldId, value, o) {
|
|
|
2152
2438
|
projectId,
|
|
2153
2439
|
itemId,
|
|
2154
2440
|
fieldId,
|
|
2155
|
-
value: "
|
|
2441
|
+
value: "optionId" in value ? { singleSelectOptionId: value.optionId } : value
|
|
2156
2442
|
},
|
|
2157
2443
|
o
|
|
2158
2444
|
);
|
|
@@ -2351,6 +2637,142 @@ async function renameView(viewId, name, visibleFieldIds, o) {
|
|
|
2351
2637
|
o
|
|
2352
2638
|
);
|
|
2353
2639
|
}
|
|
2640
|
+
async function deleteItem(projectId, itemId, o) {
|
|
2641
|
+
await graphql(
|
|
2642
|
+
`
|
|
2643
|
+
mutation ($projectId: ID!, $itemId: ID!) {
|
|
2644
|
+
deleteProjectV2Item(input: { projectId: $projectId, itemId: $itemId }) {
|
|
2645
|
+
deletedItemId
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
`,
|
|
2649
|
+
{ projectId, itemId },
|
|
2650
|
+
o
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
async function projectText(projectId, o) {
|
|
2654
|
+
const d = await graphql(
|
|
2655
|
+
`
|
|
2656
|
+
query ($projectId: ID!) {
|
|
2657
|
+
node(id: $projectId) {
|
|
2658
|
+
... on ProjectV2 {
|
|
2659
|
+
projectReadme: readme
|
|
2660
|
+
projectDescription: shortDescription
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
`,
|
|
2665
|
+
{ projectId },
|
|
2666
|
+
o
|
|
2667
|
+
);
|
|
2668
|
+
return {
|
|
2669
|
+
readme: d.node?.projectReadme ?? "",
|
|
2670
|
+
shortDescription: d.node?.projectDescription ?? ""
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
async function updateProjectText(projectId, text, o) {
|
|
2674
|
+
await graphql(
|
|
2675
|
+
`
|
|
2676
|
+
mutation ($projectId: ID!, $readme: String, $shortDescription: String) {
|
|
2677
|
+
updateProjectV2(
|
|
2678
|
+
input: {
|
|
2679
|
+
projectId: $projectId
|
|
2680
|
+
readme: $readme
|
|
2681
|
+
shortDescription: $shortDescription
|
|
2682
|
+
}
|
|
2683
|
+
) {
|
|
2684
|
+
projectV2 {
|
|
2685
|
+
id
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
`,
|
|
2690
|
+
{ projectId, ...text },
|
|
2691
|
+
o
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
async function statusUpdates(projectId, o) {
|
|
2695
|
+
const d = await graphql(
|
|
2696
|
+
`
|
|
2697
|
+
query ($projectId: ID!) {
|
|
2698
|
+
node(id: $projectId) {
|
|
2699
|
+
... on ProjectV2 {
|
|
2700
|
+
statusUpdates(first: 100) {
|
|
2701
|
+
nodes {
|
|
2702
|
+
id
|
|
2703
|
+
body
|
|
2704
|
+
status
|
|
2705
|
+
startDate
|
|
2706
|
+
targetDate
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
`,
|
|
2713
|
+
{ projectId },
|
|
2714
|
+
o
|
|
2715
|
+
);
|
|
2716
|
+
return d.node?.statusUpdates.nodes ?? [];
|
|
2717
|
+
}
|
|
2718
|
+
async function createStatusUpdate(projectId, update, o) {
|
|
2719
|
+
await graphql(
|
|
2720
|
+
`
|
|
2721
|
+
mutation (
|
|
2722
|
+
$projectId: ID!
|
|
2723
|
+
$body: String!
|
|
2724
|
+
$status: ProjectV2StatusUpdateStatus!
|
|
2725
|
+
$startDate: Date
|
|
2726
|
+
$targetDate: Date
|
|
2727
|
+
) {
|
|
2728
|
+
createProjectV2StatusUpdate(
|
|
2729
|
+
input: {
|
|
2730
|
+
projectId: $projectId
|
|
2731
|
+
body: $body
|
|
2732
|
+
status: $status
|
|
2733
|
+
startDate: $startDate
|
|
2734
|
+
targetDate: $targetDate
|
|
2735
|
+
}
|
|
2736
|
+
) {
|
|
2737
|
+
statusUpdate {
|
|
2738
|
+
id
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
`,
|
|
2743
|
+
{ projectId, ...update },
|
|
2744
|
+
o
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
async function updateStatusUpdate(statusUpdateId, update, o) {
|
|
2748
|
+
await graphql(
|
|
2749
|
+
`
|
|
2750
|
+
mutation (
|
|
2751
|
+
$statusUpdateId: ID!
|
|
2752
|
+
$body: String!
|
|
2753
|
+
$status: ProjectV2StatusUpdateStatus!
|
|
2754
|
+
$startDate: Date
|
|
2755
|
+
$targetDate: Date
|
|
2756
|
+
) {
|
|
2757
|
+
updateProjectV2StatusUpdate(
|
|
2758
|
+
input: {
|
|
2759
|
+
statusUpdateId: $statusUpdateId
|
|
2760
|
+
body: $body
|
|
2761
|
+
status: $status
|
|
2762
|
+
startDate: $startDate
|
|
2763
|
+
targetDate: $targetDate
|
|
2764
|
+
}
|
|
2765
|
+
) {
|
|
2766
|
+
statusUpdate {
|
|
2767
|
+
id
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
`,
|
|
2772
|
+
{ statusUpdateId, ...update },
|
|
2773
|
+
o
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2354
2776
|
|
|
2355
2777
|
// src/core/github-issues.ts
|
|
2356
2778
|
async function repository(owner, name, label, o) {
|
|
@@ -2489,7 +2911,7 @@ async function reopenIssue(id, o) {
|
|
|
2489
2911
|
o
|
|
2490
2912
|
);
|
|
2491
2913
|
}
|
|
2492
|
-
async function addLabels(id,
|
|
2914
|
+
async function addLabels(id, labelIds2, o) {
|
|
2493
2915
|
await graphql(
|
|
2494
2916
|
`
|
|
2495
2917
|
mutation ($id: ID!, $labelIds: [ID!]!) {
|
|
@@ -2498,7 +2920,53 @@ async function addLabels(id, labelIds, o) {
|
|
|
2498
2920
|
}
|
|
2499
2921
|
}
|
|
2500
2922
|
`,
|
|
2501
|
-
{ id, labelIds },
|
|
2923
|
+
{ id, labelIds: labelIds2 },
|
|
2924
|
+
o
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
async function labelIds(owner, name, o) {
|
|
2928
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2929
|
+
let after = null;
|
|
2930
|
+
for (; ; ) {
|
|
2931
|
+
const d = await graphql(
|
|
2932
|
+
`
|
|
2933
|
+
query ($owner: String!, $name: String!, $after: String) {
|
|
2934
|
+
repository(owner: $owner, name: $name) {
|
|
2935
|
+
labelList: labels(first: 100, after: $after) {
|
|
2936
|
+
nodes {
|
|
2937
|
+
id
|
|
2938
|
+
name
|
|
2939
|
+
}
|
|
2940
|
+
pageInfo {
|
|
2941
|
+
hasNextPage
|
|
2942
|
+
endCursor
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
`,
|
|
2948
|
+
{ owner, name, after },
|
|
2949
|
+
o
|
|
2950
|
+
);
|
|
2951
|
+
const list2 = d.repository?.labelList;
|
|
2952
|
+
for (const l of list2?.nodes ?? []) ids.set(l.name, l.id);
|
|
2953
|
+
if (list2?.pageInfo.hasNextPage !== true || list2.pageInfo.endCursor === null)
|
|
2954
|
+
return ids;
|
|
2955
|
+
after = list2.pageInfo.endCursor;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
async function removeLabels(id, labelIds2, o) {
|
|
2959
|
+
await graphql(
|
|
2960
|
+
`
|
|
2961
|
+
mutation ($id: ID!, $labelIds: [ID!]!) {
|
|
2962
|
+
removeLabelsFromLabelable(
|
|
2963
|
+
input: { labelableId: $id, labelIds: $labelIds }
|
|
2964
|
+
) {
|
|
2965
|
+
clientMutationId
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
`,
|
|
2969
|
+
{ id, labelIds: labelIds2 },
|
|
2502
2970
|
o
|
|
2503
2971
|
);
|
|
2504
2972
|
}
|
|
@@ -2515,9 +2983,19 @@ var FORMERLY = {
|
|
|
2515
2983
|
[WORKING_SESSION]: "Session",
|
|
2516
2984
|
[SESSION_HISTORY]: "Sessions"
|
|
2517
2985
|
};
|
|
2518
|
-
var
|
|
2986
|
+
var valueOf2 = (values2, name) => values2[name] ?? values2[FORMERLY[name] ?? ""] ?? null;
|
|
2519
2987
|
var LABEL_COLOR = "5319e7";
|
|
2520
2988
|
var LABEL_DESCRIPTION = "A document of .rness, on Agent Pulse";
|
|
2989
|
+
var OWN_LABEL_COLOR = "ededed";
|
|
2990
|
+
var OWN_LABEL_DESCRIPTION = "A label a collection of .rness gives its documents";
|
|
2991
|
+
var CALENDAR_VIEW = "Calendar";
|
|
2992
|
+
var markerOf = (path) => `<!-- rness: ${path} -->`;
|
|
2993
|
+
var DATA_TYPE = {
|
|
2994
|
+
text: "TEXT",
|
|
2995
|
+
date: "DATE",
|
|
2996
|
+
number: "NUMBER",
|
|
2997
|
+
select: "SINGLE_SELECT"
|
|
2998
|
+
};
|
|
2521
2999
|
var labelled = (labels) => labels.some((l) => l.toLowerCase() === LABEL.toLowerCase());
|
|
2522
3000
|
var STATUS_FIELD = "Status";
|
|
2523
3001
|
var ALL_VIEW = "All";
|
|
@@ -2549,6 +3027,8 @@ var GitHubBoards = class {
|
|
|
2549
3027
|
#cache = /* @__PURE__ */ new Map();
|
|
2550
3028
|
/** Each organization's `.rness`, as first read: its id, its Issues, its label. */
|
|
2551
3029
|
#memories = /* @__PURE__ */ new Map();
|
|
3030
|
+
/** Each organization's `.rness` labels by name, read when a collection needs one. */
|
|
3031
|
+
#labels = /* @__PURE__ */ new Map();
|
|
2552
3032
|
constructor(options) {
|
|
2553
3033
|
this.#o = options;
|
|
2554
3034
|
}
|
|
@@ -2589,10 +3069,10 @@ var GitHubBoards = class {
|
|
|
2589
3069
|
);
|
|
2590
3070
|
}
|
|
2591
3071
|
/** The project only: the caller declares it before `ensureLayout` builds it. */
|
|
2592
|
-
async createBoard(org) {
|
|
3072
|
+
async createBoard(org, title = PROJECT_TITLE) {
|
|
2593
3073
|
const project = await createProject(
|
|
2594
3074
|
await orgId(org, this.#o),
|
|
2595
|
-
|
|
3075
|
+
title,
|
|
2596
3076
|
this.#o
|
|
2597
3077
|
);
|
|
2598
3078
|
const board = { org, number: project.number, url: project.url };
|
|
@@ -2603,7 +3083,7 @@ var GitHubBoards = class {
|
|
|
2603
3083
|
const project = await findProject(org, number, this.#o);
|
|
2604
3084
|
if (project === null) return null;
|
|
2605
3085
|
const board = { org, number, url: project.url };
|
|
2606
|
-
this.#remember(board, project.id);
|
|
3086
|
+
if (!this.#cache.has(this.#key(board))) this.#remember(board, project.id);
|
|
2607
3087
|
return board;
|
|
2608
3088
|
}
|
|
2609
3089
|
async ensureLayout(board, layout) {
|
|
@@ -2678,6 +3158,60 @@ var GitHubBoards = class {
|
|
|
2678
3158
|
const session = await text(WORKING_SESSION);
|
|
2679
3159
|
const path = await text("Path");
|
|
2680
3160
|
const sessions = await text(SESSION_HISTORY);
|
|
3161
|
+
const declared2 = [];
|
|
3162
|
+
for (const d of layout.declared) {
|
|
3163
|
+
const type = DATA_TYPE[d.type];
|
|
3164
|
+
const field = fields2.find((f) => f.name === d.name);
|
|
3165
|
+
if (field !== void 0 && field.dataType !== type)
|
|
3166
|
+
throw new Error(
|
|
3167
|
+
`the project's field ${d.name} is not a ${d.type} field: rename it on the project, or in the collection's README`
|
|
3168
|
+
);
|
|
3169
|
+
if (d.type !== "select") {
|
|
3170
|
+
if (field !== void 0) declared2.push(field);
|
|
3171
|
+
else {
|
|
3172
|
+
added.push(`field ${d.name}`);
|
|
3173
|
+
declared2.push(
|
|
3174
|
+
await createField(
|
|
3175
|
+
cache.projectId,
|
|
3176
|
+
d.name,
|
|
3177
|
+
DATA_TYPE[d.type],
|
|
3178
|
+
this.#o
|
|
3179
|
+
)
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
continue;
|
|
3183
|
+
}
|
|
3184
|
+
if (field === void 0 && d.options.length === 0) continue;
|
|
3185
|
+
const options = (names) => names.map((name) => ({
|
|
3186
|
+
name,
|
|
3187
|
+
color: field?.options?.find((o) => o.name === name)?.color ?? "GRAY"
|
|
3188
|
+
}));
|
|
3189
|
+
if (field === void 0) {
|
|
3190
|
+
added.push(`field ${d.name}`);
|
|
3191
|
+
declared2.push(
|
|
3192
|
+
await createField(
|
|
3193
|
+
cache.projectId,
|
|
3194
|
+
d.name,
|
|
3195
|
+
{ options: options(d.options) },
|
|
3196
|
+
this.#o
|
|
3197
|
+
)
|
|
3198
|
+
);
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
const now = namesOf(field);
|
|
3202
|
+
const next = orderedFirst(d.options, [
|
|
3203
|
+
...now,
|
|
3204
|
+
...d.options.filter((n) => !now.includes(n))
|
|
3205
|
+
]);
|
|
3206
|
+
if (next.every((n, i) => n === now[i]) && next.length === now.length)
|
|
3207
|
+
declared2.push(field);
|
|
3208
|
+
else {
|
|
3209
|
+
added.push(
|
|
3210
|
+
...d.options.filter((n) => !now.includes(n)).map((n) => `option ${n}`)
|
|
3211
|
+
);
|
|
3212
|
+
declared2.push(await setOptions(field, options(next), this.#o));
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
2681
3215
|
const current = await views(cache.projectId, this.#o);
|
|
2682
3216
|
const first = current[0];
|
|
2683
3217
|
if (first !== void 0 && first.name === DEFAULT_VIEW && !current.some((v) => v.name === ALL_VIEW)) {
|
|
@@ -2710,6 +3244,19 @@ var GitHubBoards = class {
|
|
|
2710
3244
|
this.#o
|
|
2711
3245
|
);
|
|
2712
3246
|
}
|
|
3247
|
+
if (layout.declared.some((d) => d.type === "date") && !current.some((v) => v.name === CALENDAR_VIEW)) {
|
|
3248
|
+
added.push(`view ${CALENDAR_VIEW}`);
|
|
3249
|
+
await createView(
|
|
3250
|
+
board.org,
|
|
3251
|
+
board.number,
|
|
3252
|
+
{
|
|
3253
|
+
name: CALENDAR_VIEW,
|
|
3254
|
+
layout: "roadmap",
|
|
3255
|
+
filter: viewFilter(layout.types[0] ?? "")
|
|
3256
|
+
},
|
|
3257
|
+
this.#o
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
2713
3260
|
if (!current.some((v) => v.name === WORKING_VIEW)) {
|
|
2714
3261
|
added.push(`view ${WORKING_VIEW}`);
|
|
2715
3262
|
await createView(
|
|
@@ -2740,6 +3287,9 @@ var GitHubBoards = class {
|
|
|
2740
3287
|
);
|
|
2741
3288
|
added.push(`label ${LABEL}`);
|
|
2742
3289
|
}
|
|
3290
|
+
for (const name of layout.labels)
|
|
3291
|
+
if ((await this.#ownLabel(board.org, name)).made)
|
|
3292
|
+
added.push(`label ${name}`);
|
|
2743
3293
|
const repository2 = `${board.org}/${MEMORY}`;
|
|
2744
3294
|
const linked = await linkedRepositories(cache.projectId, this.#o);
|
|
2745
3295
|
if (!linked.some((r) => r.toLowerCase() === repository2.toLowerCase())) {
|
|
@@ -2753,10 +3303,33 @@ var GitHubBoards = class {
|
|
|
2753
3303
|
agent,
|
|
2754
3304
|
session,
|
|
2755
3305
|
path,
|
|
2756
|
-
sessions
|
|
3306
|
+
sessions,
|
|
3307
|
+
...declared2
|
|
2757
3308
|
];
|
|
2758
3309
|
return added;
|
|
2759
3310
|
}
|
|
3311
|
+
/** A collection's label on `.rness`, made when missing; its id, and whether this made it. */
|
|
3312
|
+
async #ownLabel(org, name) {
|
|
3313
|
+
let ids = this.#labels.get(org);
|
|
3314
|
+
if (ids === void 0) {
|
|
3315
|
+
ids = await labelIds(org, MEMORY, this.#o);
|
|
3316
|
+
this.#labels.set(org, ids);
|
|
3317
|
+
}
|
|
3318
|
+
const known = [...ids].find(([n]) => n.toLowerCase() === name.toLowerCase());
|
|
3319
|
+
if (known !== void 0) return { id: known[1], made: false };
|
|
3320
|
+
const id = await createLabel(
|
|
3321
|
+
org,
|
|
3322
|
+
MEMORY,
|
|
3323
|
+
{
|
|
3324
|
+
name,
|
|
3325
|
+
color: OWN_LABEL_COLOR,
|
|
3326
|
+
description: OWN_LABEL_DESCRIPTION
|
|
3327
|
+
},
|
|
3328
|
+
this.#o
|
|
3329
|
+
);
|
|
3330
|
+
ids.set(name, id);
|
|
3331
|
+
return { id, made: true };
|
|
3332
|
+
}
|
|
2760
3333
|
async items(board, options = {}) {
|
|
2761
3334
|
const cache = await this.#cacheOf(board);
|
|
2762
3335
|
const raw = await listItems(cache.projectId, this.#o, options);
|
|
@@ -2772,15 +3345,17 @@ var GitHubBoards = class {
|
|
|
2772
3345
|
number: issue.number,
|
|
2773
3346
|
open: issue.open,
|
|
2774
3347
|
labelled: labelled(issue.labels),
|
|
2775
|
-
body: issue.body
|
|
3348
|
+
body: issue.body,
|
|
3349
|
+
labels: [...issue.labels]
|
|
2776
3350
|
},
|
|
2777
3351
|
title: r.title,
|
|
2778
3352
|
status: r.values[STATUS_FIELD] ?? null,
|
|
2779
3353
|
collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
|
|
2780
3354
|
type: r.values[COLLECTION_FIELD] ?? null,
|
|
2781
3355
|
agent: r.values["Agent"] ?? null,
|
|
2782
|
-
session:
|
|
2783
|
-
sessions:
|
|
3356
|
+
session: valueOf2(r.values, WORKING_SESSION),
|
|
3357
|
+
sessions: valueOf2(r.values, SESSION_HISTORY),
|
|
3358
|
+
values: { ...r.values }
|
|
2784
3359
|
};
|
|
2785
3360
|
});
|
|
2786
3361
|
}
|
|
@@ -2790,7 +3365,10 @@ var GitHubBoards = class {
|
|
|
2790
3365
|
const field = fields2.find((f) => f.name === name) ?? fields2.find((f) => f.name === FORMERLY[name]);
|
|
2791
3366
|
if (field === void 0) throw new Error(`the board has no field ${name}`);
|
|
2792
3367
|
let sent = null;
|
|
2793
|
-
if (value !== null && field.
|
|
3368
|
+
if (value !== null && field.dataType === "DATE") sent = { date: value };
|
|
3369
|
+
else if (value !== null && field.dataType === "NUMBER")
|
|
3370
|
+
sent = { number: Number(value) };
|
|
3371
|
+
else if (value !== null && field.options === null) sent = { text: value };
|
|
2794
3372
|
else if (value !== null) {
|
|
2795
3373
|
const option = field.options?.find((opt) => opt.name === value);
|
|
2796
3374
|
if (option === void 0)
|
|
@@ -2807,6 +3385,11 @@ var GitHubBoards = class {
|
|
|
2807
3385
|
if (step2.kind === "unchanged") return null;
|
|
2808
3386
|
const cache = await this.#cacheOf(board);
|
|
2809
3387
|
if (step2.kind === "create") return this.#create(board, cache, step2.want);
|
|
3388
|
+
if (step2.kind === "remove") {
|
|
3389
|
+
await deleteItem(cache.projectId, step2.id, this.#o);
|
|
3390
|
+
cache.known.delete(step2.id);
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
2810
3393
|
const known = await this.#known(board, step2.id);
|
|
2811
3394
|
if (step2.kind === "archive" || step2.kind === "close") {
|
|
2812
3395
|
if (step2.kind === "close" && known.issue?.open === true)
|
|
@@ -2837,6 +3420,7 @@ var GitHubBoards = class {
|
|
|
2837
3420
|
labels: []
|
|
2838
3421
|
};
|
|
2839
3422
|
await this.#issue(known, step2.want, labelId);
|
|
3423
|
+
await this.#ownLabels(board.org, known, step2.want);
|
|
2840
3424
|
await this.#values(board, step2.id, step2.want);
|
|
2841
3425
|
return { id: step2.id, number: made.number };
|
|
2842
3426
|
}
|
|
@@ -2846,6 +3430,7 @@ var GitHubBoards = class {
|
|
|
2846
3430
|
issue.open = true;
|
|
2847
3431
|
}
|
|
2848
3432
|
await this.#issue(known, step2.want, labelId);
|
|
3433
|
+
await this.#ownLabels(board.org, known, step2.want);
|
|
2849
3434
|
await this.#values(board, step2.id, step2.want);
|
|
2850
3435
|
return null;
|
|
2851
3436
|
}
|
|
@@ -2889,6 +3474,7 @@ var GitHubBoards = class {
|
|
|
2889
3474
|
};
|
|
2890
3475
|
cache.known.set(id2, known);
|
|
2891
3476
|
await this.#issue(known, want, labelId);
|
|
3477
|
+
await this.#ownLabels(board.org, known, want);
|
|
2892
3478
|
await this.#values(board, id2, want);
|
|
2893
3479
|
return { id: id2, number: found.number, adopted: true };
|
|
2894
3480
|
}
|
|
@@ -2912,6 +3498,7 @@ var GitHubBoards = class {
|
|
|
2912
3498
|
archived: false,
|
|
2913
3499
|
values: {}
|
|
2914
3500
|
});
|
|
3501
|
+
await this.#ownLabels(board.org, cache.known.get(id), want);
|
|
2915
3502
|
await this.#values(board, id, want);
|
|
2916
3503
|
return { id, number: made.number };
|
|
2917
3504
|
}
|
|
@@ -2927,18 +3514,45 @@ var GitHubBoards = class {
|
|
|
2927
3514
|
known.title = want.title;
|
|
2928
3515
|
}
|
|
2929
3516
|
}
|
|
3517
|
+
/** The labels a collection gives the issue, and those it takes off (spec 0025 §4). */
|
|
3518
|
+
async #ownLabels(org, known, want) {
|
|
3519
|
+
if (want.labels.length === 0 && want.unlabels.length === 0) return;
|
|
3520
|
+
const issue = this.#issueOf(known);
|
|
3521
|
+
const carried = (name) => issue.labels.find((l) => l.toLowerCase() === name.toLowerCase());
|
|
3522
|
+
const add = want.labels.filter((l) => carried(l) === void 0);
|
|
3523
|
+
const off = want.unlabels.filter((l) => carried(l) !== void 0);
|
|
3524
|
+
if (add.length > 0) {
|
|
3525
|
+
const ids = [];
|
|
3526
|
+
for (const name of add) ids.push((await this.#ownLabel(org, name)).id);
|
|
3527
|
+
await addLabels(issue.id, ids, this.#o);
|
|
3528
|
+
issue.labels.push(...add);
|
|
3529
|
+
}
|
|
3530
|
+
if (off.length > 0) {
|
|
3531
|
+
const ids = [];
|
|
3532
|
+
for (const name of off) ids.push((await this.#ownLabel(org, name)).id);
|
|
3533
|
+
await removeLabels(issue.id, ids, this.#o);
|
|
3534
|
+
issue.labels = issue.labels.filter(
|
|
3535
|
+
(l) => !off.some((o) => o.toLowerCase() === l.toLowerCase())
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
2930
3539
|
/** Path first — an issue on the board without it is the team's — then the fields, each only when it differs. */
|
|
2931
3540
|
async #values(board, id, want) {
|
|
2932
3541
|
const values2 = (await this.#known(board, id)).values;
|
|
3542
|
+
const fields2 = await this.#fields(board);
|
|
2933
3543
|
const wanted = [
|
|
2934
3544
|
["Path", want.path],
|
|
2935
3545
|
[COLLECTION_FIELD, want.type],
|
|
2936
3546
|
[STATUS_FIELD, want.status],
|
|
2937
3547
|
...want.statusField === null ? [] : [[want.statusField, want.status]],
|
|
2938
|
-
[SESSION_HISTORY, want.sessions]
|
|
3548
|
+
[SESSION_HISTORY, want.sessions],
|
|
3549
|
+
// A select with no option yet has no field yet: nothing to write.
|
|
3550
|
+
...Object.entries(want.values).filter(
|
|
3551
|
+
([name]) => fields2.some((f) => f.name === name)
|
|
3552
|
+
)
|
|
2939
3553
|
];
|
|
2940
3554
|
for (const [name, value] of wanted)
|
|
2941
|
-
if (
|
|
3555
|
+
if (valueOf2(values2, name) !== value)
|
|
2942
3556
|
await this.#set(board, id, name, value);
|
|
2943
3557
|
}
|
|
2944
3558
|
async #known(board, id) {
|
|
@@ -2967,6 +3581,48 @@ var GitHubBoards = class {
|
|
|
2967
3581
|
await this.#set(board, id, WORKING_SESSION, session);
|
|
2968
3582
|
}
|
|
2969
3583
|
}
|
|
3584
|
+
async describe(board, text) {
|
|
3585
|
+
const cache = await this.#cacheOf(board);
|
|
3586
|
+
const now = await projectText(cache.projectId, this.#o);
|
|
3587
|
+
const next = {};
|
|
3588
|
+
if (text.readme !== null && now.readme.trimEnd() !== text.readme.trimEnd())
|
|
3589
|
+
next.readme = text.readme.trimEnd();
|
|
3590
|
+
if (text.description !== null && now.shortDescription !== text.description)
|
|
3591
|
+
next.shortDescription = text.description;
|
|
3592
|
+
if (next.readme === void 0 && next.shortDescription === void 0)
|
|
3593
|
+
return [];
|
|
3594
|
+
await updateProjectText(cache.projectId, next, this.#o);
|
|
3595
|
+
return [
|
|
3596
|
+
...next.readme === void 0 ? [] : ["readme"],
|
|
3597
|
+
...next.shortDescription === void 0 ? [] : ["short description"]
|
|
3598
|
+
];
|
|
3599
|
+
}
|
|
3600
|
+
async postUpdates(board, updates) {
|
|
3601
|
+
if (updates.length === 0) return [];
|
|
3602
|
+
const cache = await this.#cacheOf(board);
|
|
3603
|
+
const there = await statusUpdates(cache.projectId, this.#o);
|
|
3604
|
+
const written = [];
|
|
3605
|
+
for (const u of updates) {
|
|
3606
|
+
const marker = markerOf(u.path);
|
|
3607
|
+
const input = {
|
|
3608
|
+
body: `${u.body.trimEnd()}
|
|
3609
|
+
|
|
3610
|
+
${marker}`,
|
|
3611
|
+
status: u.health.toUpperCase().replace("-", "_"),
|
|
3612
|
+
startDate: u.startDate,
|
|
3613
|
+
targetDate: u.targetDate
|
|
3614
|
+
};
|
|
3615
|
+
const posted = there.find((p) => p.body.includes(marker));
|
|
3616
|
+
if (posted === void 0) {
|
|
3617
|
+
await createStatusUpdate(cache.projectId, input, this.#o);
|
|
3618
|
+
written.push(`posted ${u.path}`);
|
|
3619
|
+
} else if (posted.body.trimEnd() !== input.body || posted.status !== input.status || (posted.startDate ?? null) !== input.startDate || (posted.targetDate ?? null) !== input.targetDate) {
|
|
3620
|
+
await updateStatusUpdate(posted.id, input, this.#o);
|
|
3621
|
+
written.push(`updated ${u.path}`);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
return written;
|
|
3625
|
+
}
|
|
2970
3626
|
};
|
|
2971
3627
|
|
|
2972
3628
|
// src/core/github-oauth-provider.ts
|
|
@@ -3047,8 +3703,8 @@ var GitHubOAuthProvider = class {
|
|
|
3047
3703
|
async checkIssues(org) {
|
|
3048
3704
|
return this.#boards().checkIssues(org);
|
|
3049
3705
|
}
|
|
3050
|
-
async createBoard(org) {
|
|
3051
|
-
return this.#boards().createBoard(org);
|
|
3706
|
+
async createBoard(org, title) {
|
|
3707
|
+
return this.#boards().createBoard(org, title);
|
|
3052
3708
|
}
|
|
3053
3709
|
async board(org, number) {
|
|
3054
3710
|
return this.#boards().board(org, number);
|
|
@@ -3065,6 +3721,12 @@ var GitHubOAuthProvider = class {
|
|
|
3065
3721
|
async mark(board, itemIds, session) {
|
|
3066
3722
|
return this.#boards().mark(board, itemIds, session);
|
|
3067
3723
|
}
|
|
3724
|
+
async describe(board, text) {
|
|
3725
|
+
return this.#boards().describe(board, text);
|
|
3726
|
+
}
|
|
3727
|
+
async postUpdates(board, updates) {
|
|
3728
|
+
return this.#boards().postUpdates(board, updates);
|
|
3729
|
+
}
|
|
3068
3730
|
credentialsFor(url) {
|
|
3069
3731
|
if (this.#token === null || !url.startsWith(GITHUB_HTTPS)) return null;
|
|
3070
3732
|
return { username: "x-access-token", password: this.#token.token };
|
|
@@ -3079,11 +3741,11 @@ async function githubProvider(apiBase, wait) {
|
|
|
3079
3741
|
}
|
|
3080
3742
|
|
|
3081
3743
|
// src/core/repos.ts
|
|
3082
|
-
import { glob as glob2, readFile as
|
|
3083
|
-
import { join as
|
|
3744
|
+
import { glob as glob2, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
3745
|
+
import { join as join9, posix as posix5, sep as sep2 } from "path";
|
|
3084
3746
|
async function addRepository(input) {
|
|
3085
3747
|
const { name, url } = parseRepoSpec(input.spec, input.org, input.host);
|
|
3086
|
-
const dir =
|
|
3748
|
+
const dir = join9(input.root, "org", name);
|
|
3087
3749
|
const repoPath = `org/${name}`;
|
|
3088
3750
|
const current = input.manifest.scopes[name];
|
|
3089
3751
|
if (current !== void 0 && current.path !== repoPath)
|
|
@@ -3126,7 +3788,7 @@ async function addRepository(input) {
|
|
|
3126
3788
|
}
|
|
3127
3789
|
const declared2 = [name];
|
|
3128
3790
|
for (const { path, base } of subs) {
|
|
3129
|
-
if (!await exists(
|
|
3791
|
+
if (!await exists(join9(input.root, ...path.split("/")))) {
|
|
3130
3792
|
if (action === "cloned") await rm3(dir, { recursive: true, force: true });
|
|
3131
3793
|
throw new Error(`${path} does not exist`);
|
|
3132
3794
|
}
|
|
@@ -3144,7 +3806,7 @@ async function workspaceDirs(repoDir) {
|
|
|
3144
3806
|
let patterns = [];
|
|
3145
3807
|
try {
|
|
3146
3808
|
const pkg = JSON.parse(
|
|
3147
|
-
await
|
|
3809
|
+
await readFile6(join9(repoDir, "package.json"), "utf8")
|
|
3148
3810
|
);
|
|
3149
3811
|
const w = pkg.workspaces;
|
|
3150
3812
|
if (Array.isArray(w))
|
|
@@ -3161,7 +3823,7 @@ async function workspaceDirs(repoDir) {
|
|
|
3161
3823
|
for (const pattern of patterns) {
|
|
3162
3824
|
for await (const match of glob2(pattern, { cwd: repoDir })) {
|
|
3163
3825
|
const rel = match.split(sep2).join(posix5.sep);
|
|
3164
|
-
if (await exists(
|
|
3826
|
+
if (await exists(join9(repoDir, ...rel.split(posix5.sep), "package.json")))
|
|
3165
3827
|
dirs.add(rel);
|
|
3166
3828
|
}
|
|
3167
3829
|
}
|
|
@@ -3412,8 +4074,8 @@ function httpsFlagLine(spec) {
|
|
|
3412
4074
|
}
|
|
3413
4075
|
|
|
3414
4076
|
// src/core/workspace.ts
|
|
3415
|
-
import { readdir as
|
|
3416
|
-
import { dirname as dirname3, join as
|
|
4077
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
4078
|
+
import { dirname as dirname3, join as join10, parse as parse2, resolve as resolve2 } from "path";
|
|
3417
4079
|
async function findWorkspace(startDir) {
|
|
3418
4080
|
const start = resolve2(startDir);
|
|
3419
4081
|
const { root: fsRoot } = parse2(start);
|
|
@@ -3423,18 +4085,18 @@ async function findWorkspace(startDir) {
|
|
|
3423
4085
|
if (dir === fsRoot) break;
|
|
3424
4086
|
}
|
|
3425
4087
|
for (const dir of ancestors) {
|
|
3426
|
-
if (await exists(
|
|
3427
|
-
return { root: dir, rnessDir:
|
|
4088
|
+
if (await exists(join10(dir, ".rness", "rness.json")))
|
|
4089
|
+
return { root: dir, rnessDir: join10(dir, ".rness") };
|
|
3428
4090
|
}
|
|
3429
4091
|
for (const dir of ancestors) {
|
|
3430
|
-
if (await exists(
|
|
4092
|
+
if (await exists(join10(dir, "rness.json")))
|
|
3431
4093
|
return { root: dirname3(dir), rnessDir: dir };
|
|
3432
4094
|
}
|
|
3433
4095
|
throw new Error(`no rness workspace found above ${startDir}`);
|
|
3434
4096
|
}
|
|
3435
4097
|
async function clonesIn(root) {
|
|
3436
4098
|
try {
|
|
3437
|
-
const entries = await
|
|
4099
|
+
const entries = await readdir3(join10(root, "org"), { withFileTypes: true });
|
|
3438
4100
|
return new Set(
|
|
3439
4101
|
entries.filter((e) => e.isDirectory() && isWorkspaceClone(e.name)).map((e) => e.name)
|
|
3440
4102
|
);
|
|
@@ -3458,15 +4120,15 @@ function reportError(e) {
|
|
|
3458
4120
|
}
|
|
3459
4121
|
|
|
3460
4122
|
// src/core/delegate.ts
|
|
3461
|
-
import { readFile as
|
|
3462
|
-
import { join as
|
|
4123
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4124
|
+
import { join as join11 } from "path";
|
|
3463
4125
|
var DEFAULT_BIN = "dist/bin/rness.js";
|
|
3464
4126
|
var PINNED_BIN = `node_modules/@rness/cli/${DEFAULT_BIN}`;
|
|
3465
4127
|
async function readPinnedCli(rnessDir) {
|
|
3466
|
-
const dir =
|
|
4128
|
+
const dir = join11(rnessDir, "node_modules", "@rness", "cli");
|
|
3467
4129
|
let pkg;
|
|
3468
4130
|
try {
|
|
3469
|
-
pkg = JSON.parse(await
|
|
4131
|
+
pkg = JSON.parse(await readFile7(join11(dir, "package.json"), "utf8"));
|
|
3470
4132
|
} catch {
|
|
3471
4133
|
return null;
|
|
3472
4134
|
}
|
|
@@ -3486,7 +4148,7 @@ async function readPinnedCli(rnessDir) {
|
|
|
3486
4148
|
async function pinnedBinPath(rnessDir) {
|
|
3487
4149
|
const pinned = await readPinnedCli(rnessDir);
|
|
3488
4150
|
if (pinned === null) return null;
|
|
3489
|
-
return
|
|
4151
|
+
return join11(pinned.dir, ...pinned.bin.split("/"));
|
|
3490
4152
|
}
|
|
3491
4153
|
async function findDelegate(cwd, ownVersion) {
|
|
3492
4154
|
let root;
|
|
@@ -3502,7 +4164,7 @@ async function findDelegate(cwd, ownVersion) {
|
|
|
3502
4164
|
if (pinned === null) return null;
|
|
3503
4165
|
const { version } = pinned;
|
|
3504
4166
|
if (version === null || version === ownVersion) return null;
|
|
3505
|
-
return { entry:
|
|
4167
|
+
return { entry: join11(pinned.dir, "dist", "index.js"), version, root };
|
|
3506
4168
|
}
|
|
3507
4169
|
|
|
3508
4170
|
// src/core/block.ts
|
|
@@ -3569,7 +4231,7 @@ function isCurrentBlock(blockLines, freshHash) {
|
|
|
3569
4231
|
}
|
|
3570
4232
|
|
|
3571
4233
|
// src/core/merge.ts
|
|
3572
|
-
import { join as
|
|
4234
|
+
import { join as join12 } from "path";
|
|
3573
4235
|
function findBlock(lines) {
|
|
3574
4236
|
const begins = [];
|
|
3575
4237
|
const ends = [];
|
|
@@ -3628,7 +4290,7 @@ function claudeMdAtRoot(existing, block) {
|
|
|
3628
4290
|
return merged.ok ? { ...merged, changed: merged.text !== existing } : merged;
|
|
3629
4291
|
}
|
|
3630
4292
|
async function ensureClaudeMd(dir) {
|
|
3631
|
-
const file =
|
|
4293
|
+
const file = join12(dir, "CLAUDE.md");
|
|
3632
4294
|
if (await isSymlink(file)) return "unchanged";
|
|
3633
4295
|
const existing = await readOrNull(file);
|
|
3634
4296
|
if (existing === null) {
|
|
@@ -3643,11 +4305,11 @@ async function ensureClaudeMd(dir) {
|
|
|
3643
4305
|
}
|
|
3644
4306
|
|
|
3645
4307
|
// src/commands/sync.ts
|
|
3646
|
-
import { join as
|
|
4308
|
+
import { join as join15 } from "path";
|
|
3647
4309
|
|
|
3648
4310
|
// src/core/agent-targets.ts
|
|
3649
4311
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
3650
|
-
import { dirname as dirname4, join as
|
|
4312
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
3651
4313
|
|
|
3652
4314
|
// src/core/claude-skills.ts
|
|
3653
4315
|
var PLUGIN = ".claude/skills/rness";
|
|
@@ -4092,8 +4754,8 @@ function describeGuarantee(g) {
|
|
|
4092
4754
|
|
|
4093
4755
|
// src/core/pinned.ts
|
|
4094
4756
|
import { execFile as execFile3 } from "child_process";
|
|
4095
|
-
import { readFile as
|
|
4096
|
-
import { basename as basename3, join as
|
|
4757
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
4758
|
+
import { basename as basename3, join as join13 } from "path";
|
|
4097
4759
|
import { promisify as promisify3 } from "util";
|
|
4098
4760
|
var execFileP3 = promisify3(execFile3);
|
|
4099
4761
|
var PACKAGE = "@rness/cli";
|
|
@@ -4118,7 +4780,7 @@ function compareVersions(a, b) {
|
|
|
4118
4780
|
async function readPackageJson(rnessDir) {
|
|
4119
4781
|
try {
|
|
4120
4782
|
const parsed = JSON.parse(
|
|
4121
|
-
await
|
|
4783
|
+
await readFile8(join13(rnessDir, "package.json"), "utf8")
|
|
4122
4784
|
);
|
|
4123
4785
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4124
4786
|
} catch {
|
|
@@ -4137,8 +4799,8 @@ async function readPin(rnessDir) {
|
|
|
4137
4799
|
return null;
|
|
4138
4800
|
}
|
|
4139
4801
|
async function writePin(rnessDir, from, to) {
|
|
4140
|
-
const file =
|
|
4141
|
-
const text = await
|
|
4802
|
+
const file = join13(rnessDir, "package.json");
|
|
4803
|
+
const text = await readFile8(file, "utf8");
|
|
4142
4804
|
const entry = new RegExp(
|
|
4143
4805
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
4144
4806
|
);
|
|
@@ -4160,12 +4822,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
4160
4822
|
if (isPackageManager(name)) return name;
|
|
4161
4823
|
}
|
|
4162
4824
|
for (const [lockfile, pm] of LOCKFILES)
|
|
4163
|
-
if (await exists(
|
|
4825
|
+
if (await exists(join13(rnessDir, lockfile))) return pm;
|
|
4164
4826
|
return "npm";
|
|
4165
4827
|
}
|
|
4166
4828
|
async function hasLockfile(rnessDir, pm) {
|
|
4167
4829
|
for (const [lockfile, owner] of LOCKFILES)
|
|
4168
|
-
if (owner === pm && await exists(
|
|
4830
|
+
if (owner === pm && await exists(join13(rnessDir, lockfile))) return true;
|
|
4169
4831
|
return false;
|
|
4170
4832
|
}
|
|
4171
4833
|
async function pinDrift(rnessDir) {
|
|
@@ -4221,9 +4883,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
4221
4883
|
}
|
|
4222
4884
|
}
|
|
4223
4885
|
async function runPinnedSync(root) {
|
|
4224
|
-
const installed = await readPinnedCli(
|
|
4886
|
+
const installed = await readPinnedCli(join13(root, ".rness"));
|
|
4225
4887
|
if (installed === null) return null;
|
|
4226
|
-
const pinned =
|
|
4888
|
+
const pinned = join13(installed.dir, ...installed.bin.split("/"));
|
|
4227
4889
|
if (!await exists(pinned))
|
|
4228
4890
|
return {
|
|
4229
4891
|
code: 1,
|
|
@@ -4298,7 +4960,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
4298
4960
|
};
|
|
4299
4961
|
}
|
|
4300
4962
|
async function syncPinned(root, shown, fallback) {
|
|
4301
|
-
const rnessDir =
|
|
4963
|
+
const rnessDir = join13(root, ".rness");
|
|
4302
4964
|
const installed = await readPinnedCli(rnessDir);
|
|
4303
4965
|
if (installed === null) {
|
|
4304
4966
|
if (fallback !== void 0) return fallback();
|
|
@@ -4308,7 +4970,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
4308
4970
|
);
|
|
4309
4971
|
return 1;
|
|
4310
4972
|
}
|
|
4311
|
-
const pinned =
|
|
4973
|
+
const pinned = join13(installed.dir, ...installed.bin.split("/"));
|
|
4312
4974
|
if (!await exists(pinned)) {
|
|
4313
4975
|
process.stderr.write(
|
|
4314
4976
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -4348,30 +5010,30 @@ function wholeDiff(existing, content) {
|
|
|
4348
5010
|
}
|
|
4349
5011
|
async function contextOf(root) {
|
|
4350
5012
|
return {
|
|
4351
|
-
packageManager: await workspacePackageManager(
|
|
5013
|
+
packageManager: await workspacePackageManager(join14(root, ".rness"))
|
|
4352
5014
|
};
|
|
4353
5015
|
}
|
|
4354
5016
|
var PATHS_ONLY = { packageManager: "npm" };
|
|
4355
5017
|
async function filesOf(root, manifest, targets) {
|
|
4356
5018
|
const files = [];
|
|
4357
|
-
if (!await exists(
|
|
5019
|
+
if (!await exists(join14(root, "org"))) return files;
|
|
4358
5020
|
const ctx = await contextOf(root);
|
|
4359
5021
|
for (const target2 of targets)
|
|
4360
5022
|
for (const owned of target2.files(ctx))
|
|
4361
5023
|
if (owned.at === "root")
|
|
4362
5024
|
files.push({
|
|
4363
5025
|
...owned,
|
|
4364
|
-
file:
|
|
5026
|
+
file: join14(root, ...owned.file.split("/")),
|
|
4365
5027
|
label: owned.file
|
|
4366
5028
|
});
|
|
4367
5029
|
for (const repo of Object.keys(manifest.repos)) {
|
|
4368
|
-
if (!await exists(
|
|
5030
|
+
if (!await exists(join14(root, "org", repo))) continue;
|
|
4369
5031
|
for (const target2 of targets)
|
|
4370
5032
|
for (const owned of target2.files(ctx))
|
|
4371
5033
|
if (owned.at === "clones")
|
|
4372
5034
|
files.push({
|
|
4373
5035
|
...owned,
|
|
4374
|
-
file:
|
|
5036
|
+
file: join14(root, "org", repo, ...owned.file.split("/")),
|
|
4375
5037
|
label: `org/${repo}/${owned.file}`
|
|
4376
5038
|
});
|
|
4377
5039
|
}
|
|
@@ -4695,7 +5357,7 @@ function targetsOf(manifest, root, only) {
|
|
|
4695
5357
|
const segments = entry.path.split("/");
|
|
4696
5358
|
targets.push({
|
|
4697
5359
|
scope: name,
|
|
4698
|
-
dir:
|
|
5360
|
+
dir: join15(root, ...segments),
|
|
4699
5361
|
depth: segments.length,
|
|
4700
5362
|
label: `${entry.path}/AGENTS.md`
|
|
4701
5363
|
});
|
|
@@ -4854,7 +5516,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4854
5516
|
try {
|
|
4855
5517
|
const notCloned = /* @__PURE__ */ new Set();
|
|
4856
5518
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
4857
|
-
const dir =
|
|
5519
|
+
const dir = join15(ws.root, "org", name);
|
|
4858
5520
|
const label = `org/${name}`;
|
|
4859
5521
|
if (!await exists(dir)) {
|
|
4860
5522
|
if (!toClone.has(name)) {
|
|
@@ -4906,7 +5568,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4906
5568
|
lines.push(
|
|
4907
5569
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
4908
5570
|
);
|
|
4909
|
-
if (!await exists(
|
|
5571
|
+
if (!await exists(join15(ws.root, "org"))) {
|
|
4910
5572
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
4911
5573
|
} else {
|
|
4912
5574
|
let differences = 0;
|
|
@@ -4918,7 +5580,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4918
5580
|
continue;
|
|
4919
5581
|
}
|
|
4920
5582
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
4921
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
5583
|
+
BLOCK_FILES.map((f) => isSymlink(join15(t.dir, f)))
|
|
4922
5584
|
);
|
|
4923
5585
|
const atRoot = t.scope === null;
|
|
4924
5586
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -4941,7 +5603,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4941
5603
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
4942
5604
|
);
|
|
4943
5605
|
}
|
|
4944
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
5606
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join15(t.dir, "AGENTS.md"), block, check);
|
|
4945
5607
|
if (typeof agents === "object") {
|
|
4946
5608
|
problems.push(`${t.label}: ${agents.error}`);
|
|
4947
5609
|
continue;
|
|
@@ -4956,7 +5618,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4956
5618
|
}
|
|
4957
5619
|
if (claudeLinked === true) continue;
|
|
4958
5620
|
const claude = await syncBlockFile(
|
|
4959
|
-
|
|
5621
|
+
join15(t.dir, "CLAUDE.md"),
|
|
4960
5622
|
block,
|
|
4961
5623
|
check,
|
|
4962
5624
|
true
|
|
@@ -5013,7 +5675,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
5013
5675
|
}
|
|
5014
5676
|
|
|
5015
5677
|
// src/commands/add.ts
|
|
5016
|
-
import { basename as basename4, join as
|
|
5678
|
+
import { basename as basename4, join as join16 } from "path";
|
|
5017
5679
|
function printDeclared(ui, result, already) {
|
|
5018
5680
|
for (const name of result.declared) {
|
|
5019
5681
|
if (already.has(name)) continue;
|
|
@@ -5106,7 +5768,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
5106
5768
|
}
|
|
5107
5769
|
parsed = parseRepoSpec(spec, org, host);
|
|
5108
5770
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
5109
|
-
const dir =
|
|
5771
|
+
const dir = join16(ws.root, "org", parsed.name);
|
|
5110
5772
|
const present = await exists(dir);
|
|
5111
5773
|
if (interactive) {
|
|
5112
5774
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -5234,7 +5896,7 @@ async function contextCommand(opts) {
|
|
|
5234
5896
|
|
|
5235
5897
|
// src/core/scaffold.ts
|
|
5236
5898
|
import { statSync } from "fs";
|
|
5237
|
-
import { dirname as dirname5, join as
|
|
5899
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
5238
5900
|
import { fileURLToPath } from "url";
|
|
5239
5901
|
var SCAFFOLD_FILES = [
|
|
5240
5902
|
"AGENTS.md",
|
|
@@ -5258,9 +5920,9 @@ var SCAFFOLD_FILES = [
|
|
|
5258
5920
|
function scaffoldDir() {
|
|
5259
5921
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5260
5922
|
for (let i = 0; i < 4; i += 1) {
|
|
5261
|
-
const candidate =
|
|
5923
|
+
const candidate = join17(dir, "scaffold");
|
|
5262
5924
|
try {
|
|
5263
|
-
if (statSync(
|
|
5925
|
+
if (statSync(join17(candidate, "rness.json")).isFile()) return candidate;
|
|
5264
5926
|
} catch {
|
|
5265
5927
|
}
|
|
5266
5928
|
dir = dirname5(dir);
|
|
@@ -5269,15 +5931,15 @@ function scaffoldDir() {
|
|
|
5269
5931
|
}
|
|
5270
5932
|
|
|
5271
5933
|
// src/core/scaffold-copy.ts
|
|
5272
|
-
import { chmod as chmod2, mkdir as mkdir3, readFile as
|
|
5273
|
-
import { dirname as dirname6, join as
|
|
5934
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
5935
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
5274
5936
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
5275
5937
|
async function renderScaffold(source, tokens) {
|
|
5276
5938
|
const files = [];
|
|
5277
5939
|
for (const rel of SCAFFOLD_FILES) {
|
|
5278
5940
|
if (rel === "rness.json") continue;
|
|
5279
|
-
const from =
|
|
5280
|
-
let content = await
|
|
5941
|
+
const from = join18(source, ...rel.split("/"));
|
|
5942
|
+
let content = await readFile9(from, "utf8");
|
|
5281
5943
|
if (TOKENISED.has(rel)) {
|
|
5282
5944
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
5283
5945
|
}
|
|
@@ -5291,7 +5953,7 @@ async function renderScaffold(source, tokens) {
|
|
|
5291
5953
|
}
|
|
5292
5954
|
async function copyScaffold(dest, tokens) {
|
|
5293
5955
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
5294
|
-
const to =
|
|
5956
|
+
const to = join18(dest, ...file.path.split("/"));
|
|
5295
5957
|
await mkdir3(dirname6(to), { recursive: true });
|
|
5296
5958
|
await writeFile2(to, file.content, "utf8");
|
|
5297
5959
|
if (file.executable) await chmod2(to, 493);
|
|
@@ -5400,7 +6062,7 @@ async function loginCommand(opts, deps = {}) {
|
|
|
5400
6062
|
ui.error(unsupportedProviderMessage(manifest.provider));
|
|
5401
6063
|
return 1;
|
|
5402
6064
|
}
|
|
5403
|
-
const needsProject = opts.project === true || manifest?.
|
|
6065
|
+
const needsProject = opts.project === true || manifest?.projects != null;
|
|
5404
6066
|
const bin = deps.bin ?? await realpath(process.argv[1] ?? "").catch(() => "");
|
|
5405
6067
|
const env = envToken();
|
|
5406
6068
|
if (env !== null)
|
|
@@ -5471,7 +6133,7 @@ function revokeUrl() {
|
|
|
5471
6133
|
// src/commands/create.ts
|
|
5472
6134
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, rename as rename2, rm as rm6 } from "fs/promises";
|
|
5473
6135
|
import { tmpdir as tmpdir2 } from "os";
|
|
5474
|
-
import { join as
|
|
6136
|
+
import { join as join22, relative as relative4, resolve as resolve5 } from "path";
|
|
5475
6137
|
|
|
5476
6138
|
// src/core/ask-provider.ts
|
|
5477
6139
|
var PROVIDER_QUESTION = "Where does your organization live?";
|
|
@@ -5487,14 +6149,14 @@ async function askProvider(p) {
|
|
|
5487
6149
|
}
|
|
5488
6150
|
|
|
5489
6151
|
// src/core/new-workspace.ts
|
|
5490
|
-
import { mkdir as mkdir4, readdir as
|
|
5491
|
-
import { dirname as dirname8, join as
|
|
6152
|
+
import { mkdir as mkdir4, readdir as readdir4, rm as rm5, stat as stat3 } from "fs/promises";
|
|
6153
|
+
import { dirname as dirname8, join as join20, relative as relative2 } from "path";
|
|
5492
6154
|
|
|
5493
6155
|
// src/core/scaffold-git.ts
|
|
5494
6156
|
import { execFile as execFile5 } from "child_process";
|
|
5495
6157
|
import { mkdtemp, realpath as realpath2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
5496
6158
|
import { tmpdir } from "os";
|
|
5497
|
-
import { dirname as dirname7, join as
|
|
6159
|
+
import { dirname as dirname7, join as join19, resolve as resolve3 } from "path";
|
|
5498
6160
|
import { promisify as promisify5 } from "util";
|
|
5499
6161
|
var execFileP5 = promisify5(execFile5);
|
|
5500
6162
|
var CREATE_SUBJECT = "chore: rness workspace context";
|
|
@@ -5582,8 +6244,8 @@ async function findScaffoldBase(rnessDir) {
|
|
|
5582
6244
|
};
|
|
5583
6245
|
}
|
|
5584
6246
|
async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
5585
|
-
const scratch = await mkdtemp(
|
|
5586
|
-
const env = { GIT_INDEX_FILE:
|
|
6247
|
+
const scratch = await mkdtemp(join19(tmpdir(), "rness-scaffold-"));
|
|
6248
|
+
const env = { GIT_INDEX_FILE: join19(scratch, "index") };
|
|
5587
6249
|
try {
|
|
5588
6250
|
await must(
|
|
5589
6251
|
rnessDir,
|
|
@@ -5591,7 +6253,7 @@ async function buildScaffoldCommit(rnessDir, base, files, version) {
|
|
|
5591
6253
|
env
|
|
5592
6254
|
);
|
|
5593
6255
|
for (const [i, file] of files.entries()) {
|
|
5594
|
-
const tmp =
|
|
6256
|
+
const tmp = join19(scratch, `blob-${i}`);
|
|
5595
6257
|
await writeFile3(tmp, file.content, "utf8");
|
|
5596
6258
|
const blob = await must(rnessDir, [
|
|
5597
6259
|
"hash-object",
|
|
@@ -5691,7 +6353,7 @@ async function withoutRedundantKeeps(rnessDir, files) {
|
|
|
5691
6353
|
|
|
5692
6354
|
// src/core/new-workspace.ts
|
|
5693
6355
|
async function isEmptyDir(dir) {
|
|
5694
|
-
return (await
|
|
6356
|
+
return (await readdir4(dir)).length === 0;
|
|
5695
6357
|
}
|
|
5696
6358
|
async function insideWorkspace(dir) {
|
|
5697
6359
|
try {
|
|
@@ -5706,8 +6368,8 @@ async function targetProblem(cwd, root, pm) {
|
|
|
5706
6368
|
if (await insideWorkspace(cwd) || await insideWorkspace(dirname8(root)))
|
|
5707
6369
|
return "already inside an rness workspace; create runs from outside one";
|
|
5708
6370
|
if (!await exists(root)) return null;
|
|
5709
|
-
if (await exists(
|
|
5710
|
-
if (!await exists(
|
|
6371
|
+
if (await exists(join20(root, ".rness", "rness.json"))) {
|
|
6372
|
+
if (!await exists(join20(root, ".rness", "node_modules")))
|
|
5711
6373
|
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
|
|
5712
6374
|
return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
|
|
5713
6375
|
}
|
|
@@ -5730,7 +6392,7 @@ async function installContext(input) {
|
|
|
5730
6392
|
}
|
|
5731
6393
|
async function buildContext(input) {
|
|
5732
6394
|
const { ui, root, shown, pm } = input;
|
|
5733
|
-
const rnessDir =
|
|
6395
|
+
const rnessDir = join20(root, ".rness");
|
|
5734
6396
|
const pmVersion = await packageManagerVersion(pm);
|
|
5735
6397
|
await mkdir4(root, { recursive: true });
|
|
5736
6398
|
try {
|
|
@@ -5802,7 +6464,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
5802
6464
|
|
|
5803
6465
|
// src/commands/create-blank.ts
|
|
5804
6466
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
5805
|
-
import { join as
|
|
6467
|
+
import { join as join21, relative as relative3, resolve as resolve4 } from "path";
|
|
5806
6468
|
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
5807
6469
|
function workspaceNameError(name) {
|
|
5808
6470
|
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
@@ -5875,7 +6537,7 @@ async function createBlank(input) {
|
|
|
5875
6537
|
provider: null,
|
|
5876
6538
|
org: null,
|
|
5877
6539
|
agents,
|
|
5878
|
-
|
|
6540
|
+
projects: null,
|
|
5879
6541
|
repos: {},
|
|
5880
6542
|
scopes: {}
|
|
5881
6543
|
},
|
|
@@ -5884,8 +6546,8 @@ async function createBlank(input) {
|
|
|
5884
6546
|
kind: "blank workspace"
|
|
5885
6547
|
});
|
|
5886
6548
|
if (!built) return 1;
|
|
5887
|
-
await mkdir5(
|
|
5888
|
-
await commitContext({ ui, rnessDir:
|
|
6549
|
+
await mkdir5(join21(root, "org"), { recursive: true });
|
|
6550
|
+
await commitContext({ ui, rnessDir: join21(root, ".rness"), shown });
|
|
5889
6551
|
const code = await syncBlocks(
|
|
5890
6552
|
ui,
|
|
5891
6553
|
root,
|
|
@@ -6045,7 +6707,7 @@ async function cloneSelection(input) {
|
|
|
6045
6707
|
{ doing: `cloning org/${repo}`, git: true },
|
|
6046
6708
|
() => clone(
|
|
6047
6709
|
entry.url,
|
|
6048
|
-
|
|
6710
|
+
join22(input.root, "org", repo),
|
|
6049
6711
|
input.provider.credentialsFor(entry.url)
|
|
6050
6712
|
),
|
|
6051
6713
|
() => ["cloned", `org/${repo}`]
|
|
@@ -6390,13 +7052,13 @@ async function createCommand(opts, deps = {}) {
|
|
|
6390
7052
|
provider: chosenProvider,
|
|
6391
7053
|
org,
|
|
6392
7054
|
agents: null,
|
|
6393
|
-
|
|
7055
|
+
projects: null,
|
|
6394
7056
|
repos: {},
|
|
6395
7057
|
scopes: {}
|
|
6396
7058
|
};
|
|
6397
7059
|
const staged = async () => {
|
|
6398
|
-
staging ??= await mkdtemp2(
|
|
6399
|
-
return
|
|
7060
|
+
staging ??= await mkdtemp2(join22(tmpdir2(), "rness-join-"));
|
|
7061
|
+
return join22(staging, ".rness");
|
|
6400
7062
|
};
|
|
6401
7063
|
if (joining) {
|
|
6402
7064
|
const stagedDir = await staged();
|
|
@@ -6458,7 +7120,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
6458
7120
|
});
|
|
6459
7121
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
6460
7122
|
}
|
|
6461
|
-
const rnessDir =
|
|
7123
|
+
const rnessDir = join22(root, ".rness");
|
|
6462
7124
|
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
6463
7125
|
if (joining) {
|
|
6464
7126
|
await mkdir6(root, { recursive: true });
|
|
@@ -6471,7 +7133,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
6471
7133
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
6472
7134
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
6473
7135
|
await install();
|
|
6474
|
-
await mkdir6(
|
|
7136
|
+
await mkdir6(join22(root, "org"), { recursive: true });
|
|
6475
7137
|
const failures2 = await cloneSelection({
|
|
6476
7138
|
root,
|
|
6477
7139
|
rnessDir,
|
|
@@ -6524,7 +7186,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
6524
7186
|
kind: "new workspace"
|
|
6525
7187
|
});
|
|
6526
7188
|
if (!built) return 1;
|
|
6527
|
-
await mkdir6(
|
|
7189
|
+
await mkdir6(join22(root, "org"), { recursive: true });
|
|
6528
7190
|
const failures = await cloneSelection({
|
|
6529
7191
|
root,
|
|
6530
7192
|
rnessDir,
|
|
@@ -6596,7 +7258,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
6596
7258
|
}
|
|
6597
7259
|
|
|
6598
7260
|
// src/core/blocks.ts
|
|
6599
|
-
import { join as
|
|
7261
|
+
import { join as join23 } from "path";
|
|
6600
7262
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
6601
7263
|
async function checkBlocks({
|
|
6602
7264
|
root,
|
|
@@ -6613,7 +7275,7 @@ async function checkBlocks({
|
|
|
6613
7275
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
6614
7276
|
checks
|
|
6615
7277
|
});
|
|
6616
|
-
if (!await exists(
|
|
7278
|
+
if (!await exists(join23(root, "org"))) return derive();
|
|
6617
7279
|
const targets = [
|
|
6618
7280
|
{ scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
|
|
6619
7281
|
{ scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
|
|
@@ -6622,7 +7284,7 @@ async function checkBlocks({
|
|
|
6622
7284
|
const segments = entry.path.split("/");
|
|
6623
7285
|
targets.push({
|
|
6624
7286
|
scope: name,
|
|
6625
|
-
dir:
|
|
7287
|
+
dir: join23(root, ...segments),
|
|
6626
7288
|
depth: segments.length,
|
|
6627
7289
|
file: "AGENTS.md",
|
|
6628
7290
|
label: `${entry.path}/AGENTS.md`
|
|
@@ -6637,7 +7299,7 @@ async function checkBlocks({
|
|
|
6637
7299
|
);
|
|
6638
7300
|
continue;
|
|
6639
7301
|
}
|
|
6640
|
-
const text = await readOrNull(
|
|
7302
|
+
const text = await readOrNull(join23(t.dir, t.file));
|
|
6641
7303
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
6642
7304
|
const found = findBlock(lines);
|
|
6643
7305
|
if (found.kind === "none") {
|
|
@@ -6706,9 +7368,9 @@ async function logoutCommand() {
|
|
|
6706
7368
|
|
|
6707
7369
|
// src/commands/upgrade.ts
|
|
6708
7370
|
import { execFile as execFile6 } from "child_process";
|
|
6709
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
7371
|
+
import { mkdtemp as mkdtemp3, readFile as readFile10, rm as rm7 } from "fs/promises";
|
|
6710
7372
|
import { tmpdir as tmpdir3 } from "os";
|
|
6711
|
-
import { basename as basename5, join as
|
|
7373
|
+
import { basename as basename5, join as join24, relative as relative5 } from "path";
|
|
6712
7374
|
import { promisify as promisify6 } from "util";
|
|
6713
7375
|
var execFileP6 = promisify6(execFile6);
|
|
6714
7376
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -6728,10 +7390,10 @@ async function latestVersion() {
|
|
|
6728
7390
|
async function scaffoldFor(version, rnessDir) {
|
|
6729
7391
|
if (version === VERSION) return { dir: scaffoldDir() };
|
|
6730
7392
|
const installed = await readPinnedCli(rnessDir);
|
|
6731
|
-
const inside2 = installed === null ? null :
|
|
6732
|
-
if (installed?.version === version && inside2 !== null && await exists(
|
|
7393
|
+
const inside2 = installed === null ? null : join24(installed.dir, "scaffold");
|
|
7394
|
+
if (installed?.version === version && inside2 !== null && await exists(join24(inside2, "package.json")))
|
|
6733
7395
|
return { dir: inside2 };
|
|
6734
|
-
const scratch = await mkdtemp3(
|
|
7396
|
+
const scratch = await mkdtemp3(join24(tmpdir3(), "rness-pack-"));
|
|
6735
7397
|
const cleanup = () => rm7(scratch, { recursive: true, force: true });
|
|
6736
7398
|
try {
|
|
6737
7399
|
const { stdout } = await execFileP6(
|
|
@@ -6746,8 +7408,8 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
6746
7408
|
{ timeout: 6e4 }
|
|
6747
7409
|
);
|
|
6748
7410
|
const tarball = stdout.trim().split("\n").at(-1) ?? "";
|
|
6749
|
-
await execFileP6("tar", ["-xzf",
|
|
6750
|
-
return { dir:
|
|
7411
|
+
await execFileP6("tar", ["-xzf", join24(scratch, tarball), "-C", scratch]);
|
|
7412
|
+
return { dir: join24(scratch, "package", "scaffold"), cleanup };
|
|
6751
7413
|
} catch (e) {
|
|
6752
7414
|
await cleanup();
|
|
6753
7415
|
throw new Error(
|
|
@@ -6759,7 +7421,7 @@ async function scaffoldFor(version, rnessDir) {
|
|
|
6759
7421
|
async function declaredManager(rnessDir, pm) {
|
|
6760
7422
|
try {
|
|
6761
7423
|
const pkg = JSON.parse(
|
|
6762
|
-
await
|
|
7424
|
+
await readFile10(join24(rnessDir, "package.json"), "utf8")
|
|
6763
7425
|
);
|
|
6764
7426
|
if (typeof pkg.packageManager === "string") return pkg.packageManager;
|
|
6765
7427
|
} catch {
|
|
@@ -6769,7 +7431,7 @@ async function declaredManager(rnessDir, pm) {
|
|
|
6769
7431
|
async function clonesToCommit(root, cwd, manifest, files) {
|
|
6770
7432
|
const clones = [];
|
|
6771
7433
|
for (const repo of Object.keys(manifest.repos)) {
|
|
6772
|
-
const dir =
|
|
7434
|
+
const dir = join24(root, "org", repo);
|
|
6773
7435
|
if (!await exists(dir)) continue;
|
|
6774
7436
|
try {
|
|
6775
7437
|
const paths = await changedPaths(dir, files);
|
|
@@ -6787,7 +7449,7 @@ async function filesTheSyncWrites(root, rnessDir, manifest) {
|
|
|
6787
7449
|
try {
|
|
6788
7450
|
const { stdout } = await execFileP6(
|
|
6789
7451
|
process.execPath,
|
|
6790
|
-
[
|
|
7452
|
+
[join24(pinned.dir, pinned.bin), "written-files", "--cwd", root],
|
|
6791
7453
|
{
|
|
6792
7454
|
cwd: root,
|
|
6793
7455
|
env: { ...process.env, NO_COLOR: "1", RNESS_NO_INSTALL: "1" },
|
|
@@ -6983,9 +7645,9 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal, deps =
|
|
|
6983
7645
|
await source.cleanup?.();
|
|
6984
7646
|
}
|
|
6985
7647
|
}
|
|
6986
|
-
const packageFile =
|
|
7648
|
+
const packageFile = join24(ws.rnessDir, "package.json");
|
|
6987
7649
|
const restore = /* @__PURE__ */ new Map([
|
|
6988
|
-
[packageFile, await
|
|
7650
|
+
[packageFile, await readFile10(packageFile, "utf8")]
|
|
6989
7651
|
]);
|
|
6990
7652
|
const moved = await readPin(ws.rnessDir);
|
|
6991
7653
|
if (moved !== null && moved.spec !== target2)
|
|
@@ -7179,8 +7841,8 @@ import { realpath as realpath4 } from "fs/promises";
|
|
|
7179
7841
|
import { isAbsolute as isAbsolute2, posix as posix8, relative as relative7, resolve as resolve7, sep as sep5 } from "path";
|
|
7180
7842
|
|
|
7181
7843
|
// src/mcp/tools.ts
|
|
7182
|
-
import { readFile as
|
|
7183
|
-
import { isAbsolute, join as
|
|
7844
|
+
import { readFile as readFile11, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
7845
|
+
import { isAbsolute, join as join25, posix as posix7, relative as relative6, resolve as resolve6, sep as sep4 } from "path";
|
|
7184
7846
|
var LABELS = {
|
|
7185
7847
|
standards: "Standards",
|
|
7186
7848
|
adr: "Decisions",
|
|
@@ -7209,7 +7871,7 @@ async function documents(rnessDir, only) {
|
|
|
7209
7871
|
const docs = [];
|
|
7210
7872
|
for (const collection of COLLECTIONS) {
|
|
7211
7873
|
if (only !== void 0 && collection !== only) continue;
|
|
7212
|
-
for (const item of await collectMarkdown(
|
|
7874
|
+
for (const item of await collectMarkdown(join25(rnessDir, collection))) {
|
|
7213
7875
|
const path = `${collection}/${item.rel}`;
|
|
7214
7876
|
if (path === TEMPLATE2) continue;
|
|
7215
7877
|
const status2 = item.fields?.["status"];
|
|
@@ -7331,7 +7993,7 @@ async function read(cwd, args) {
|
|
|
7331
7993
|
return refuse(
|
|
7332
7994
|
`${path}: ${info.size} bytes, over the 256 KiB rness_read returns`
|
|
7333
7995
|
);
|
|
7334
|
-
return { text: await
|
|
7996
|
+
return { text: await readFile11(real, "utf8") };
|
|
7335
7997
|
}
|
|
7336
7998
|
async function search(cwd, args) {
|
|
7337
7999
|
const { ws } = await workspaceAt(cwd);
|
|
@@ -7439,8 +8101,8 @@ function mcpTools(cwd) {
|
|
|
7439
8101
|
|
|
7440
8102
|
// src/pulse/detached.ts
|
|
7441
8103
|
import { spawn as nodeSpawn } from "child_process";
|
|
7442
|
-
import { mkdir as mkdir7, open as open2, readFile as
|
|
7443
|
-
import { join as
|
|
8104
|
+
import { mkdir as mkdir7, open as open2, readFile as readFile12, rm as rm8, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
8105
|
+
import { join as join26 } from "path";
|
|
7444
8106
|
import { setTimeout as delay2 } from "timers/promises";
|
|
7445
8107
|
var detached = (args, cwd) => {
|
|
7446
8108
|
const bin = process.argv[1];
|
|
@@ -7454,7 +8116,7 @@ var detached = (args, cwd) => {
|
|
|
7454
8116
|
child.on("error", () => void 0);
|
|
7455
8117
|
child.unref();
|
|
7456
8118
|
};
|
|
7457
|
-
var failureFile = () =>
|
|
8119
|
+
var failureFile = () => join26(configDir(), "pulse.json");
|
|
7458
8120
|
async function recordFailure(reason3) {
|
|
7459
8121
|
try {
|
|
7460
8122
|
await mkdir7(configDir(), { recursive: true, mode: 448 });
|
|
@@ -7468,7 +8130,7 @@ async function recordFailure(reason3) {
|
|
|
7468
8130
|
}
|
|
7469
8131
|
async function takeFailure() {
|
|
7470
8132
|
try {
|
|
7471
|
-
const data = JSON.parse(await
|
|
8133
|
+
const data = JSON.parse(await readFile12(failureFile(), "utf8"));
|
|
7472
8134
|
await rm8(failureFile(), { force: true });
|
|
7473
8135
|
const reason3 = data !== null && typeof data === "object" ? data["reason"] : void 0;
|
|
7474
8136
|
return typeof reason3 === "string" && reason3 !== "" ? reason3 : null;
|
|
@@ -7479,7 +8141,7 @@ async function takeFailure() {
|
|
|
7479
8141
|
var PULSE_LOCK_STALE_MS = 15 * 6e4;
|
|
7480
8142
|
async function oneSyncAtATime(key, work, options = {}) {
|
|
7481
8143
|
await mkdir7(configDir(), { recursive: true, mode: 448 });
|
|
7482
|
-
const file =
|
|
8144
|
+
const file = join26(configDir(), `pulse-${key}.lock`);
|
|
7483
8145
|
for (; ; ) {
|
|
7484
8146
|
try {
|
|
7485
8147
|
const handle = await open2(file, "wx");
|
|
@@ -7565,7 +8227,7 @@ function sessionOf(input) {
|
|
|
7565
8227
|
}
|
|
7566
8228
|
async function hasPulse(ws) {
|
|
7567
8229
|
try {
|
|
7568
|
-
return (await loadManifest(ws.rnessDir)).
|
|
8230
|
+
return (await loadManifest(ws.rnessDir)).projects !== null;
|
|
7569
8231
|
} catch {
|
|
7570
8232
|
return false;
|
|
7571
8233
|
}
|
|
@@ -7875,12 +8537,12 @@ async function mcpCommand(opts, io = {
|
|
|
7875
8537
|
}
|
|
7876
8538
|
|
|
7877
8539
|
// src/commands/pulse.ts
|
|
7878
|
-
import { readFile as
|
|
7879
|
-
import { join as
|
|
8540
|
+
import { readFile as readFile14, readdir as readdir5 } from "fs/promises";
|
|
8541
|
+
import { join as join27 } from "path";
|
|
7880
8542
|
import { setTimeout as delay3 } from "timers/promises";
|
|
7881
8543
|
|
|
7882
8544
|
// src/pulse/opened.ts
|
|
7883
|
-
import { mkdir as mkdir8, readFile as
|
|
8545
|
+
import { mkdir as mkdir8, readFile as readFile13, rename as rename3, rm as rm9, writeFile as writeFile5 } from "fs/promises";
|
|
7884
8546
|
import { dirname as dirname9 } from "path";
|
|
7885
8547
|
var NAME2 = "rness/opened";
|
|
7886
8548
|
function openedFile(rnessDir) {
|
|
@@ -7892,7 +8554,7 @@ async function readOpened(file) {
|
|
|
7892
8554
|
if (file === null) return opened;
|
|
7893
8555
|
let data;
|
|
7894
8556
|
try {
|
|
7895
|
-
data = JSON.parse(await
|
|
8557
|
+
data = JSON.parse(await readFile13(file, "utf8"));
|
|
7896
8558
|
} catch {
|
|
7897
8559
|
return opened;
|
|
7898
8560
|
}
|
|
@@ -7959,16 +8621,75 @@ async function missingAccess(provider) {
|
|
|
7959
8621
|
const scopes = await fromGithub(provider.scopes());
|
|
7960
8622
|
return scopes?.includes(PROJECT_SCOPE) === true ? null : NEEDS_SCOPE;
|
|
7961
8623
|
}
|
|
7962
|
-
async function
|
|
7963
|
-
|
|
8624
|
+
async function declaredBoards(c) {
|
|
8625
|
+
const projects = c.manifest.projects;
|
|
8626
|
+
if (projects === null)
|
|
7964
8627
|
throw new Error("no pulse declared \u2014 rness pulse create");
|
|
7965
8628
|
const missing = await missingAccess(c.provider);
|
|
7966
8629
|
if (missing !== null) throw new Error(missing);
|
|
7967
|
-
const
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
8630
|
+
const names = Object.keys(projects).sort(
|
|
8631
|
+
(a, b) => Number(a === PULSE) - Number(b === PULSE)
|
|
8632
|
+
);
|
|
8633
|
+
const declared2 = [];
|
|
8634
|
+
for (const name of names) {
|
|
8635
|
+
const number = projects[name];
|
|
8636
|
+
const board = await fromGithub(c.provider.board(c.org, number));
|
|
8637
|
+
if (board === null)
|
|
8638
|
+
throw new Error(`GitHub has no project ${number} in ${c.org}`);
|
|
8639
|
+
declared2.push({ name, board });
|
|
8640
|
+
}
|
|
8641
|
+
return declared2;
|
|
8642
|
+
}
|
|
8643
|
+
var labelOf = (name) => `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
8644
|
+
async function directoriesOf(dir) {
|
|
8645
|
+
try {
|
|
8646
|
+
return (await readdir5(dir, { withFileTypes: true })).filter(
|
|
8647
|
+
(e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "updates"
|
|
8648
|
+
).map((e) => e.name).sort();
|
|
8649
|
+
} catch {
|
|
8650
|
+
return [];
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
async function scopeOf(c, name, tabs, owners) {
|
|
8654
|
+
const within = (collection) => (path) => path.startsWith(`${collection}/`);
|
|
8655
|
+
if (name === PULSE) {
|
|
8656
|
+
const shown = tabs.filter((t) => !owners.includes(t.name));
|
|
8657
|
+
const elsewhere = (path) => owners.some((o) => within(o)(path));
|
|
8658
|
+
return {
|
|
8659
|
+
label: "Agent Pulse",
|
|
8660
|
+
want: desiredOf(shown, c.org),
|
|
8661
|
+
layout: layoutOf(shown),
|
|
8662
|
+
elsewhere,
|
|
8663
|
+
mine: (path) => !elsewhere(path),
|
|
8664
|
+
own: null
|
|
8665
|
+
};
|
|
8666
|
+
}
|
|
8667
|
+
const tab = tabs.find((t) => t.name === name) ?? {
|
|
8668
|
+
name,
|
|
8669
|
+
label: labelOf(name),
|
|
8670
|
+
rows: []
|
|
8671
|
+
};
|
|
8672
|
+
const dir = join27(c.rnessDir, name);
|
|
8673
|
+
const shape = await readShape(c.rnessDir, name);
|
|
8674
|
+
const fronts = new Map(
|
|
8675
|
+
(await collectMarkdown(dir)).map((item) => [
|
|
8676
|
+
`${name}/${item.rel}`,
|
|
8677
|
+
item.fields ?? {}
|
|
8678
|
+
])
|
|
8679
|
+
);
|
|
8680
|
+
const want = desiredOf([tab], c.org, {
|
|
8681
|
+
shape,
|
|
8682
|
+
fronts,
|
|
8683
|
+
directories: await directoriesOf(dir)
|
|
8684
|
+
});
|
|
8685
|
+
return {
|
|
8686
|
+
label: tab.label,
|
|
8687
|
+
want,
|
|
8688
|
+
layout: layoutOf([tab], { shape, want }),
|
|
8689
|
+
elsewhere: () => false,
|
|
8690
|
+
mine: within(name),
|
|
8691
|
+
own: { shape, updates: await readUpdates(c.rnessDir, name) }
|
|
8692
|
+
};
|
|
7972
8693
|
}
|
|
7973
8694
|
var plural = (n) => `${n} item${n === 1 ? "" : "s"}`;
|
|
7974
8695
|
var eachAdded = (ui, added) => {
|
|
@@ -7976,7 +8697,7 @@ var eachAdded = (ui, added) => {
|
|
|
7976
8697
|
if (a.startsWith("renamed ")) ui.line("renamed", a.slice("renamed ".length));
|
|
7977
8698
|
else ui.line("added", a);
|
|
7978
8699
|
};
|
|
7979
|
-
var createdByKind = (ui, added) => {
|
|
8700
|
+
var createdByKind = (ui, added, project) => {
|
|
7980
8701
|
for (const kind of ["field", "option", "view"]) {
|
|
7981
8702
|
const names = added.filter((a) => a.startsWith(`${kind} `)).map((a) => a.slice(kind.length + 1));
|
|
7982
8703
|
if (names.length > 0) ui.line("created", `${kind}s ${names.join(", ")}`);
|
|
@@ -7984,14 +8705,18 @@ var createdByKind = (ui, added) => {
|
|
|
7984
8705
|
for (const a of added) {
|
|
7985
8706
|
if (a.startsWith("label ")) ui.line("created", a);
|
|
7986
8707
|
else if (a.startsWith("link "))
|
|
7987
|
-
ui.line("linked",
|
|
8708
|
+
ui.line("linked", `${project} to ${a.slice("link ".length)}`);
|
|
7988
8709
|
}
|
|
7989
8710
|
};
|
|
8711
|
+
async function working(ui, doing, count3, work) {
|
|
8712
|
+
if (count3 === 0) return work();
|
|
8713
|
+
await ui.step({ doing, quiet: true }, work, () => null);
|
|
8714
|
+
}
|
|
7990
8715
|
async function filesOf2(rnessDir) {
|
|
7991
8716
|
const files = /* @__PURE__ */ new Set();
|
|
7992
8717
|
const walk = async (rel) => {
|
|
7993
|
-
const dir = rel === "" ? rnessDir :
|
|
7994
|
-
for (const e of await
|
|
8718
|
+
const dir = rel === "" ? rnessDir : join27(rnessDir, ...rel.split("/"));
|
|
8719
|
+
for (const e of await readdir5(dir, { withFileTypes: true })) {
|
|
7995
8720
|
const path = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
7996
8721
|
if (e.isDirectory()) {
|
|
7997
8722
|
if (e.name !== ".git" && e.name !== "node_modules") await walk(path);
|
|
@@ -8002,7 +8727,7 @@ async function filesOf2(rnessDir) {
|
|
|
8002
8727
|
return files;
|
|
8003
8728
|
}
|
|
8004
8729
|
var count2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
8005
|
-
function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
8730
|
+
function sayDone(ui, org, documents2, steps, writes, adopted, on) {
|
|
8006
8731
|
const n = (kind) => steps.filter((s) => s.kind === kind).length - (kind === "create" ? adopted : 0);
|
|
8007
8732
|
const reopened = steps.filter((s) => s.kind === "update" && s.reopen).length;
|
|
8008
8733
|
if (n("create") > 0) ui.line("created", count2(n("create"), "issue"));
|
|
@@ -8017,6 +8742,8 @@ function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
|
8017
8742
|
`${count2(n("convert"), "draft")} into issues of ${org}/.rness`
|
|
8018
8743
|
);
|
|
8019
8744
|
if (reopened > 0) ui.line("reopened", count2(reopened, "issue"));
|
|
8745
|
+
if (n("remove") > 0)
|
|
8746
|
+
ui.line("moved", `${plural(n("remove"))} to their collection's project`);
|
|
8020
8747
|
const written = new Set(writes.map((w) => w.id));
|
|
8021
8748
|
const bodyOnly = steps.filter(
|
|
8022
8749
|
(s) => s.kind === "unchanged" && written.has(s.id)
|
|
@@ -8034,25 +8761,27 @@ function sayDone(ui, org, documents2, steps, writes, adopted) {
|
|
|
8034
8761
|
].filter(([, k]) => k > 0).map(([word, k]) => `${k} ${word}`);
|
|
8035
8762
|
ui.line(
|
|
8036
8763
|
"synced",
|
|
8037
|
-
`${plural(documents2)}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`
|
|
8764
|
+
`${plural(documents2)}${on === void 0 ? "" : ` on ${on}`}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`
|
|
8038
8765
|
);
|
|
8039
8766
|
}
|
|
8040
|
-
async function syncBoard(c, board, ui, sayLayout) {
|
|
8767
|
+
async function syncBoard(c, board, ui, sayLayout, scope) {
|
|
8041
8768
|
await fromGithub(c.provider.checkIssues(c.org));
|
|
8042
|
-
const
|
|
8043
|
-
const want = desiredOf(tabs, c.org);
|
|
8769
|
+
const { want } = scope;
|
|
8044
8770
|
sayLayout(
|
|
8045
8771
|
ui,
|
|
8046
|
-
await fromGithub(c.provider.ensureLayout(board,
|
|
8772
|
+
await fromGithub(c.provider.ensureLayout(board, scope.layout)),
|
|
8773
|
+
scope.label
|
|
8047
8774
|
);
|
|
8048
8775
|
const have = await fromGithub(c.provider.items(board));
|
|
8049
8776
|
const record = await openedFile(c.rnessDir);
|
|
8050
|
-
const
|
|
8777
|
+
const all = await readOpened(record);
|
|
8778
|
+
const opened = new Map([...all].filter(([, path]) => scope.mine(path)));
|
|
8779
|
+
const others = [...all].filter(([, path]) => !scope.mine(path));
|
|
8051
8780
|
const seen = await seenPaths(c.rnessDir, [
|
|
8052
8781
|
...unwantedPaths(want, have),
|
|
8053
8782
|
...opened.values()
|
|
8054
8783
|
]);
|
|
8055
|
-
const steps = planSync(want, have, seen, opened);
|
|
8784
|
+
const steps = planSync(want, have, seen, opened, scope.elsewhere);
|
|
8056
8785
|
const changes = steps.filter((s) => s.kind !== "unchanged").length;
|
|
8057
8786
|
let writes = [];
|
|
8058
8787
|
let made = 0;
|
|
@@ -8062,21 +8791,31 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8062
8791
|
const created = /* @__PURE__ */ new Map();
|
|
8063
8792
|
const closed = /* @__PURE__ */ new Set();
|
|
8064
8793
|
try {
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8794
|
+
await working(
|
|
8795
|
+
ui,
|
|
8796
|
+
`writing ${plural(changes)} on ${scope.label}`,
|
|
8797
|
+
changes,
|
|
8798
|
+
async () => {
|
|
8799
|
+
for (const step2 of steps) {
|
|
8800
|
+
const p = await fromGithub(c.provider.apply(board, step2));
|
|
8801
|
+
if (p !== null && (step2.kind === "create" || step2.kind === "convert"))
|
|
8802
|
+
placed.set(step2.want.path, p);
|
|
8803
|
+
if (p?.adopted === true) adopted++;
|
|
8804
|
+
else if (p !== null && step2.kind === "create")
|
|
8805
|
+
created.set(step2.want.path, p);
|
|
8806
|
+
if (step2.kind === "close" || step2.kind === "archive")
|
|
8807
|
+
closed.add(step2.id);
|
|
8808
|
+
if (step2.kind !== "unchanged") made++;
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
);
|
|
8076
8812
|
} finally {
|
|
8077
8813
|
await writeOpened(
|
|
8078
8814
|
record,
|
|
8079
|
-
|
|
8815
|
+
new Map([
|
|
8816
|
+
...others,
|
|
8817
|
+
...stillOpened(opened, have, seen, closed, created)
|
|
8818
|
+
])
|
|
8080
8819
|
);
|
|
8081
8820
|
}
|
|
8082
8821
|
const issued = issuedAfter(steps, have, placed);
|
|
@@ -8088,17 +8827,24 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8088
8827
|
w.path,
|
|
8089
8828
|
issueBody({
|
|
8090
8829
|
path: w.path,
|
|
8091
|
-
text: await
|
|
8830
|
+
text: await readFile14(join27(c.rnessDir, ...w.path.split("/")), "utf8"),
|
|
8092
8831
|
org: c.org,
|
|
8093
8832
|
numbers,
|
|
8094
8833
|
files
|
|
8095
8834
|
})
|
|
8096
8835
|
);
|
|
8097
8836
|
writes = planBodies(bodies, issued);
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8837
|
+
await working(
|
|
8838
|
+
ui,
|
|
8839
|
+
`writing ${plural(writes.length)} bodies on ${scope.label}`,
|
|
8840
|
+
writes.length,
|
|
8841
|
+
async () => {
|
|
8842
|
+
for (const step2 of writes) {
|
|
8843
|
+
await fromGithub(c.provider.apply(board, step2));
|
|
8844
|
+
made++;
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
);
|
|
8102
8848
|
} catch (e) {
|
|
8103
8849
|
if (!(e instanceof RateLimitError)) throw e;
|
|
8104
8850
|
const total = changes + writes.length;
|
|
@@ -8107,7 +8853,43 @@ async function syncBoard(c, board, ui, sayLayout) {
|
|
|
8107
8853
|
{ cause: e }
|
|
8108
8854
|
);
|
|
8109
8855
|
}
|
|
8110
|
-
sayDone(
|
|
8856
|
+
sayDone(
|
|
8857
|
+
ui,
|
|
8858
|
+
c.org,
|
|
8859
|
+
want.length,
|
|
8860
|
+
steps,
|
|
8861
|
+
writes,
|
|
8862
|
+
adopted,
|
|
8863
|
+
scope.own === null ? void 0 : scope.label
|
|
8864
|
+
);
|
|
8865
|
+
if (scope.own !== null) {
|
|
8866
|
+
const { shape, updates } = scope.own;
|
|
8867
|
+
for (const what of await fromGithub(
|
|
8868
|
+
c.provider.describe(board, {
|
|
8869
|
+
readme: shape.readme,
|
|
8870
|
+
description: shape.description
|
|
8871
|
+
})
|
|
8872
|
+
))
|
|
8873
|
+
ui.line("wrote", `${what} of ${scope.label}`);
|
|
8874
|
+
for (const line3 of await fromGithub(
|
|
8875
|
+
c.provider.postUpdates(board, updates)
|
|
8876
|
+
)) {
|
|
8877
|
+
const [verb, ...rest] = line3.split(" ");
|
|
8878
|
+
ui.line(verb ?? "posted", rest.join(" "));
|
|
8879
|
+
}
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
async function syncAll(c, declared2, ui, sayLayout) {
|
|
8883
|
+
const tabs = await statusTabs(c.rnessDir);
|
|
8884
|
+
const owners = declared2.map((d) => d.name).filter((n) => n !== PULSE);
|
|
8885
|
+
for (const { name, board } of declared2)
|
|
8886
|
+
await syncBoard(
|
|
8887
|
+
c,
|
|
8888
|
+
board,
|
|
8889
|
+
ui,
|
|
8890
|
+
sayLayout(name),
|
|
8891
|
+
await scopeOf(c, name, tabs, owners)
|
|
8892
|
+
);
|
|
8111
8893
|
}
|
|
8112
8894
|
async function pulseCreateCommand(opts, deps = {}) {
|
|
8113
8895
|
try {
|
|
@@ -8115,9 +8897,20 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
8115
8897
|
const ui = deps.ui ?? await makeUi(terminal);
|
|
8116
8898
|
const wait = waitSaid(opts, ui);
|
|
8117
8899
|
let c = await context2(opts, wait);
|
|
8118
|
-
|
|
8900
|
+
const name = opts.collection ?? PULSE;
|
|
8901
|
+
const declared2 = c.manifest.projects?.[name];
|
|
8902
|
+
if (opts.collection === PULSE)
|
|
8903
|
+
throw new Error('"pulse" names Agent Pulse: rness pulse create');
|
|
8904
|
+
if (declared2 !== void 0)
|
|
8905
|
+
throw new Error(
|
|
8906
|
+
`already declared: ${boardUrl(c.org, declared2)} \u2014 rness pulse sync`
|
|
8907
|
+
);
|
|
8908
|
+
const tab = opts.collection === void 0 ? null : (await statusTabs(c.rnessDir)).find(
|
|
8909
|
+
(t) => t.name === opts.collection
|
|
8910
|
+
) ?? null;
|
|
8911
|
+
if (opts.collection !== void 0 && tab === null)
|
|
8119
8912
|
throw new Error(
|
|
8120
|
-
|
|
8913
|
+
`${opts.collection} is no collection of .rness: none of its documents carries a status`
|
|
8121
8914
|
);
|
|
8122
8915
|
const detected = providerOf(c.manifest);
|
|
8123
8916
|
if (!PROVIDERS[detected].available)
|
|
@@ -8155,19 +8948,26 @@ async function pulseCreateCommand(opts, deps = {}) {
|
|
|
8155
8948
|
"checked",
|
|
8156
8949
|
`${detected}, logged in as ${login}, scope ${PROJECT_SCOPE}`
|
|
8157
8950
|
);
|
|
8158
|
-
const board = await fromGithub(c.provider.createBoard(c.org));
|
|
8159
|
-
|
|
8951
|
+
const board = await fromGithub(c.provider.createBoard(c.org, tab?.label));
|
|
8952
|
+
const manifest = {
|
|
8160
8953
|
...c.manifest,
|
|
8161
8954
|
provider: detected,
|
|
8162
|
-
|
|
8163
|
-
}
|
|
8164
|
-
|
|
8955
|
+
projects: { ...c.manifest.projects, [name]: board.number }
|
|
8956
|
+
};
|
|
8957
|
+
await writeManifest(c.rnessDir, manifest);
|
|
8958
|
+
c = { ...c, manifest };
|
|
8959
|
+
ui.line("created", `${tab?.label ?? "Agent Pulse"} \u2014 ${board.url}`);
|
|
8165
8960
|
try {
|
|
8166
|
-
await
|
|
8961
|
+
await syncAll(
|
|
8962
|
+
c,
|
|
8963
|
+
await declaredBoards(c),
|
|
8964
|
+
ui,
|
|
8965
|
+
(n) => n === name ? createdByKind : eachAdded
|
|
8966
|
+
);
|
|
8167
8967
|
} finally {
|
|
8168
8968
|
ui.line(
|
|
8169
8969
|
"declared",
|
|
8170
|
-
|
|
8970
|
+
`${name} in .rness/rness.json \u2014 commit it: git -C .rness commit -am "chore: rness pulse${opts.collection === void 0 ? "" : ` ${opts.collection}`}"`
|
|
8171
8971
|
);
|
|
8172
8972
|
}
|
|
8173
8973
|
return 0;
|
|
@@ -8179,7 +8979,7 @@ async function pulseSyncCommand(opts, deps = {}) {
|
|
|
8179
8979
|
try {
|
|
8180
8980
|
const ui = deps.ui ?? await makeUi(deps.terminal ?? defaultTerminal);
|
|
8181
8981
|
const c = await context2(opts, waitSaid(opts, ui));
|
|
8182
|
-
await
|
|
8982
|
+
await syncAll(c, await declaredBoards(c), ui, () => eachAdded);
|
|
8183
8983
|
return 0;
|
|
8184
8984
|
} catch (e) {
|
|
8185
8985
|
return reportError(e);
|
|
@@ -8195,43 +8995,47 @@ async function pulseMarkCommand(opts) {
|
|
|
8195
8995
|
}
|
|
8196
8996
|
async function mark(opts) {
|
|
8197
8997
|
const c = await context2(opts);
|
|
8198
|
-
const
|
|
8199
|
-
const key = `${c.org}-${board.number}`;
|
|
8998
|
+
const declared2 = await declaredBoards(c);
|
|
8999
|
+
const key = `${c.org}-${declared2.map((d) => d.board.number).join("-")}`;
|
|
8200
9000
|
const lock = opts.sleep === void 0 ? {} : { sleep: opts.sleep };
|
|
8201
|
-
const
|
|
8202
|
-
|
|
9001
|
+
const syncEvery = () => syncAll(c, declared2, plainUi, () => eachAdded);
|
|
9002
|
+
const listed = (board) => fromGithub(c.provider.items(board, { bodies: false }));
|
|
8203
9003
|
if (opts.end === true) {
|
|
8204
9004
|
const id = opts.session.split(" \xB7 ").pop() ?? opts.session;
|
|
8205
|
-
const
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
});
|
|
9005
|
+
for (const { board } of declared2) {
|
|
9006
|
+
const ids = (await listed(board)).filter(
|
|
9007
|
+
(i) => i.session === opts.session || i.session?.endsWith(` \xB7 ${id}`)
|
|
9008
|
+
).map((i) => i.id);
|
|
9009
|
+
await fromGithub(c.provider.mark(board, ids, null));
|
|
9010
|
+
}
|
|
9011
|
+
await oneSyncAtATime(key, syncEvery, { ...lock, wait: true });
|
|
8213
9012
|
return 0;
|
|
8214
9013
|
}
|
|
8215
|
-
const
|
|
9014
|
+
const held = (path) => declared2.some((d) => d.name === PULSE || path.startsWith(`${d.name}/`));
|
|
9015
|
+
const wanted = new Set(opts.paths.filter(held));
|
|
8216
9016
|
const documents2 = new Map(
|
|
8217
9017
|
(await statusTabs(c.rnessDir)).flatMap(
|
|
8218
9018
|
(t) => t.rows.map((r) => [r.path, r.status])
|
|
8219
9019
|
)
|
|
8220
9020
|
);
|
|
9021
|
+
let items = /* @__PURE__ */ new Map();
|
|
9022
|
+
const listAll = async () => {
|
|
9023
|
+
items = /* @__PURE__ */ new Map();
|
|
9024
|
+
for (const { name, board } of declared2) items.set(name, await listed(board));
|
|
9025
|
+
};
|
|
9026
|
+
await listAll();
|
|
8221
9027
|
const onBoard = new Map(
|
|
8222
|
-
items.flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
|
|
9028
|
+
[...items.values()].flat().flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
|
|
8223
9029
|
);
|
|
8224
9030
|
const stale = (path) => documents2.has(path) && (!onBoard.has(path) || onBoard.get(path) !== documents2.get(path));
|
|
8225
9031
|
if ([...wanted].some(stale)) {
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
)
|
|
8231
|
-
|
|
9032
|
+
if (await oneSyncAtATime(key, syncEvery, lock)) await listAll();
|
|
9033
|
+
}
|
|
9034
|
+
for (const { name, board } of declared2) {
|
|
9035
|
+
const ids = (items.get(name) ?? []).filter((i) => i.path !== null && wanted.has(i.path)).map((i) => i.id);
|
|
9036
|
+
if (ids.length > 0)
|
|
9037
|
+
await fromGithub(c.provider.mark(board, ids, opts.session));
|
|
8232
9038
|
}
|
|
8233
|
-
const ids = items.filter((i) => i.path !== null && wanted.has(i.path)).map((i) => i.id);
|
|
8234
|
-
await fromGithub(c.provider.mark(board, ids, opts.session));
|
|
8235
9039
|
return 0;
|
|
8236
9040
|
}
|
|
8237
9041
|
|
|
@@ -8533,10 +9337,19 @@ function buildProgram(state) {
|
|
|
8533
9337
|
state.code = await syncCommand(opts);
|
|
8534
9338
|
});
|
|
8535
9339
|
const pulse = program.command("pulse").description("Show the agent's work on the organization's board");
|
|
8536
|
-
pulse.command("create").description(
|
|
8537
|
-
|
|
9340
|
+
pulse.command("create").description(
|
|
9341
|
+
"Create the organization's Agent Pulse board, or a collection's own project, and sync"
|
|
9342
|
+
).argument(
|
|
9343
|
+
"[collection]",
|
|
9344
|
+
"a directory of .rness/ whose documents get a project of their own"
|
|
9345
|
+
).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) => {
|
|
9346
|
+
state.code = await pulseCreateCommand(
|
|
9347
|
+
collection === void 0 ? opts : { ...opts, collection }
|
|
9348
|
+
);
|
|
8538
9349
|
});
|
|
8539
|
-
pulse.command("sync").description(
|
|
9350
|
+
pulse.command("sync").description(
|
|
9351
|
+
"Bring every declared project up to date with the documents of .rness/"
|
|
9352
|
+
).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
|
|
8540
9353
|
state.code = await pulseSyncCommand(opts);
|
|
8541
9354
|
});
|
|
8542
9355
|
pulse.command("mark", { hidden: true }).requiredOption("--session <id>").option(
|
|
@@ -8766,4 +9579,4 @@ export {
|
|
|
8766
9579
|
OWN_COMMANDS,
|
|
8767
9580
|
catchUp
|
|
8768
9581
|
};
|
|
8769
|
-
//# sourceMappingURL=chunk-
|
|
9582
|
+
//# sourceMappingURL=chunk-GAZDKEOH.js.map
|