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