@rness/cli 0.13.0 → 0.15.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 +76 -14
- package/dist/bin/rness.js +1 -1
- package/dist/{chunk-4VTYSPZW.js → chunk-HGT7BIWK.js} +609 -396
- package/dist/chunk-HGT7BIWK.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4VTYSPZW.js.map +0 -1
|
@@ -486,7 +486,7 @@ function parseRepoSpec(spec, org, host) {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
// src/version.ts
|
|
489
|
-
var VERSION = "0.
|
|
489
|
+
var VERSION = "0.15.0";
|
|
490
490
|
|
|
491
491
|
// src/core/pm.ts
|
|
492
492
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -523,6 +523,12 @@ function rnessRunner(pm, version) {
|
|
|
523
523
|
return "yarn dlx @rness/cli";
|
|
524
524
|
return "npx @rness/cli";
|
|
525
525
|
}
|
|
526
|
+
function localRunner(pm) {
|
|
527
|
+
if (pm === "pnpm") return "pnpm";
|
|
528
|
+
if (pm === "yarn") return "yarn";
|
|
529
|
+
if (pm === "bun") return "bunx";
|
|
530
|
+
return "npx";
|
|
531
|
+
}
|
|
526
532
|
function rnessCommand(env = process.env) {
|
|
527
533
|
const agent = env["npm_config_user_agent"] ?? "";
|
|
528
534
|
if (agent === "") return "rness";
|
|
@@ -1064,15 +1070,25 @@ function statusOf(item) {
|
|
|
1064
1070
|
const status2 = item.fields?.["status"];
|
|
1065
1071
|
return typeof status2 === "string" && status2 !== "" ? status2 : null;
|
|
1066
1072
|
}
|
|
1073
|
+
function sessionsOf(item) {
|
|
1074
|
+
const listed = item.fields?.["sessions"];
|
|
1075
|
+
if (!Array.isArray(listed)) return [];
|
|
1076
|
+
const ids = listed.filter(
|
|
1077
|
+
(s) => typeof s === "string" && s !== ""
|
|
1078
|
+
);
|
|
1079
|
+
return [...new Set(ids)];
|
|
1080
|
+
}
|
|
1067
1081
|
function rowOf(dir, item) {
|
|
1068
1082
|
const base = posix3.basename(item.rel, ".md");
|
|
1069
1083
|
const date = /^(\d{4}-\d{2}-\d{2})-/.exec(base)?.[1] ?? null;
|
|
1070
1084
|
const number = date === null ? documentNumber(item.rel) : null;
|
|
1085
|
+
const sessions = sessionsOf(item);
|
|
1071
1086
|
return {
|
|
1072
1087
|
id: date ?? number ?? base,
|
|
1073
1088
|
title: documentTitle(item.title ?? base, number),
|
|
1074
1089
|
status: statusOf(item),
|
|
1075
|
-
path: `${dir}/${item.rel}
|
|
1090
|
+
path: `${dir}/${item.rel}`,
|
|
1091
|
+
...sessions.length > 0 ? { sessions } : {}
|
|
1076
1092
|
};
|
|
1077
1093
|
}
|
|
1078
1094
|
function newestFirst(a, b) {
|
|
@@ -1393,7 +1409,8 @@ function desiredOf(tabs, org) {
|
|
|
1393
1409
|
body: headerOf(row.path, org),
|
|
1394
1410
|
status: statusOf2(row.status),
|
|
1395
1411
|
type: tab.label,
|
|
1396
|
-
statusField: fields2.get(tab.label)?.name ?? null
|
|
1412
|
+
statusField: fields2.get(tab.label)?.name ?? null,
|
|
1413
|
+
sessions: row.sessions?.join(", ") ?? null
|
|
1397
1414
|
}))
|
|
1398
1415
|
);
|
|
1399
1416
|
}
|
|
@@ -2482,6 +2499,7 @@ var GitHubBoards = class {
|
|
|
2482
2499
|
const agent = await single("Agent", "Agent", [WORKING]);
|
|
2483
2500
|
const session = await text("Session");
|
|
2484
2501
|
const path = await text("Path");
|
|
2502
|
+
const sessions = await text("Sessions");
|
|
2485
2503
|
const current = await views(cache.projectId, this.#o);
|
|
2486
2504
|
const first = current[0];
|
|
2487
2505
|
if (first !== void 0 && first.name === DEFAULT_VIEW && !current.some((v) => v.name === ALL_VIEW)) {
|
|
@@ -2550,7 +2568,15 @@ var GitHubBoards = class {
|
|
|
2550
2568
|
await linkRepository(cache.projectId, memory.id, this.#o);
|
|
2551
2569
|
added.push(`link ${repository2}`);
|
|
2552
2570
|
}
|
|
2553
|
-
cache.fields = [
|
|
2571
|
+
cache.fields = [
|
|
2572
|
+
status2,
|
|
2573
|
+
collection,
|
|
2574
|
+
...own.values(),
|
|
2575
|
+
agent,
|
|
2576
|
+
session,
|
|
2577
|
+
path,
|
|
2578
|
+
sessions
|
|
2579
|
+
];
|
|
2554
2580
|
return added;
|
|
2555
2581
|
}
|
|
2556
2582
|
async items(board, options = {}) {
|
|
@@ -2575,7 +2601,8 @@ var GitHubBoards = class {
|
|
|
2575
2601
|
collectionStatus: r.values[statusFieldName(r.values[COLLECTION_FIELD] ?? "")] ?? null,
|
|
2576
2602
|
type: r.values[COLLECTION_FIELD] ?? null,
|
|
2577
2603
|
agent: r.values["Agent"] ?? null,
|
|
2578
|
-
session: r.values["Session"] ?? null
|
|
2604
|
+
session: r.values["Session"] ?? null,
|
|
2605
|
+
sessions: r.values["Sessions"] ?? null
|
|
2579
2606
|
};
|
|
2580
2607
|
});
|
|
2581
2608
|
}
|
|
@@ -2689,7 +2716,8 @@ var GitHubBoards = class {
|
|
|
2689
2716
|
["Path", want.path],
|
|
2690
2717
|
[COLLECTION_FIELD, want.type],
|
|
2691
2718
|
[STATUS_FIELD, want.status],
|
|
2692
|
-
...want.statusField === null ? [] : [[want.statusField, want.status]]
|
|
2719
|
+
...want.statusField === null ? [] : [[want.statusField, want.status]],
|
|
2720
|
+
["Sessions", want.sessions]
|
|
2693
2721
|
];
|
|
2694
2722
|
for (const [name, value] of wanted)
|
|
2695
2723
|
if ((values2[name] ?? null) !== value)
|
|
@@ -3211,6 +3239,54 @@ function reportError(e) {
|
|
|
3211
3239
|
return 1;
|
|
3212
3240
|
}
|
|
3213
3241
|
|
|
3242
|
+
// src/core/delegate.ts
|
|
3243
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3244
|
+
import { join as join10 } from "path";
|
|
3245
|
+
var DEFAULT_BIN = "dist/bin/rness.js";
|
|
3246
|
+
var PINNED_BIN = `node_modules/@rness/cli/${DEFAULT_BIN}`;
|
|
3247
|
+
async function readPinnedCli(rnessDir) {
|
|
3248
|
+
const dir = join10(rnessDir, "node_modules", "@rness", "cli");
|
|
3249
|
+
let pkg;
|
|
3250
|
+
try {
|
|
3251
|
+
pkg = JSON.parse(await readFile6(join10(dir, "package.json"), "utf8"));
|
|
3252
|
+
} catch {
|
|
3253
|
+
return null;
|
|
3254
|
+
}
|
|
3255
|
+
const declared2 = pkg.bin;
|
|
3256
|
+
let bin = DEFAULT_BIN;
|
|
3257
|
+
if (typeof declared2 === "string") bin = declared2;
|
|
3258
|
+
else if (declared2 !== null && typeof declared2 === "object") {
|
|
3259
|
+
const named = declared2["rness"];
|
|
3260
|
+
if (typeof named === "string") bin = named;
|
|
3261
|
+
}
|
|
3262
|
+
return {
|
|
3263
|
+
dir,
|
|
3264
|
+
bin,
|
|
3265
|
+
version: typeof pkg.version === "string" ? pkg.version : null
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
async function pinnedBinPath(rnessDir) {
|
|
3269
|
+
const pinned = await readPinnedCli(rnessDir);
|
|
3270
|
+
if (pinned === null) return null;
|
|
3271
|
+
return join10(pinned.dir, ...pinned.bin.split("/"));
|
|
3272
|
+
}
|
|
3273
|
+
async function findDelegate(cwd, ownVersion) {
|
|
3274
|
+
let root;
|
|
3275
|
+
let rnessDir;
|
|
3276
|
+
try {
|
|
3277
|
+
const ws = await findWorkspace(cwd);
|
|
3278
|
+
root = ws.root;
|
|
3279
|
+
rnessDir = ws.rnessDir;
|
|
3280
|
+
} catch {
|
|
3281
|
+
return null;
|
|
3282
|
+
}
|
|
3283
|
+
const pinned = await readPinnedCli(rnessDir);
|
|
3284
|
+
if (pinned === null) return null;
|
|
3285
|
+
const { version } = pinned;
|
|
3286
|
+
if (version === null || version === ownVersion) return null;
|
|
3287
|
+
return { entry: join10(pinned.dir, "dist", "index.js"), version, root };
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3214
3290
|
// src/core/block.ts
|
|
3215
3291
|
import { createHash as createHash2 } from "crypto";
|
|
3216
3292
|
var BEGIN = "<!-- BEGIN rness -->";
|
|
@@ -3275,7 +3351,7 @@ function isCurrentBlock(blockLines, freshHash) {
|
|
|
3275
3351
|
}
|
|
3276
3352
|
|
|
3277
3353
|
// src/core/merge.ts
|
|
3278
|
-
import { join as
|
|
3354
|
+
import { join as join11 } from "path";
|
|
3279
3355
|
function findBlock(lines) {
|
|
3280
3356
|
const begins = [];
|
|
3281
3357
|
const ends = [];
|
|
@@ -3334,7 +3410,7 @@ function claudeMdAtRoot(existing, block) {
|
|
|
3334
3410
|
return merged.ok ? { ...merged, changed: merged.text !== existing } : merged;
|
|
3335
3411
|
}
|
|
3336
3412
|
async function ensureClaudeMd(dir) {
|
|
3337
|
-
const file =
|
|
3413
|
+
const file = join11(dir, "CLAUDE.md");
|
|
3338
3414
|
if (await isSymlink(file)) return "unchanged";
|
|
3339
3415
|
const existing = await readOrNull(file);
|
|
3340
3416
|
if (existing === null) {
|
|
@@ -3349,14 +3425,230 @@ async function ensureClaudeMd(dir) {
|
|
|
3349
3425
|
}
|
|
3350
3426
|
|
|
3351
3427
|
// src/commands/sync.ts
|
|
3352
|
-
import { join as
|
|
3428
|
+
import { join as join14 } from "path";
|
|
3353
3429
|
|
|
3354
3430
|
// src/core/agent-targets.ts
|
|
3355
3431
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
3356
|
-
import { dirname as dirname4, join as
|
|
3432
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
3433
|
+
|
|
3434
|
+
// src/core/claude-skills.ts
|
|
3435
|
+
var PLUGIN = ".claude/skills/rness";
|
|
3436
|
+
var PLUGIN_JSON = `${JSON.stringify(
|
|
3437
|
+
{
|
|
3438
|
+
name: "rness",
|
|
3439
|
+
description: "The rness workspace in Claude Code: /rness:status, /rness:adr, /rness:spec, /rness:plan, /rness:done."
|
|
3440
|
+
},
|
|
3441
|
+
null,
|
|
3442
|
+
2
|
|
3443
|
+
)}
|
|
3444
|
+
`;
|
|
3445
|
+
function statusSkill(rness, pm) {
|
|
3446
|
+
const command = `node "\${CLAUDE_PROJECT_DIR}/${rness}/${PINNED_BIN}" status --cwd "\${CLAUDE_PROJECT_DIR}"`;
|
|
3447
|
+
return `---
|
|
3448
|
+
name: status
|
|
3449
|
+
description: The status of every decision, specification, plan and other tracked document of the rness workspace, one table per directory. Read-only.
|
|
3450
|
+
argument-hint: '[tab]'
|
|
3451
|
+
disable-model-invocation: true
|
|
3452
|
+
allowed-tools: Bash(${command})
|
|
3453
|
+
---
|
|
3454
|
+
|
|
3455
|
+
!\`${command}\`
|
|
3456
|
+
|
|
3457
|
+
Show the output above to the user as it is: the heading and the tables,
|
|
3458
|
+
nothing added, nothing summarised, no other tool. Arguments: \`$ARGUMENTS\`.
|
|
3459
|
+
When they name a tab, show only that tab's section. If the output says the
|
|
3460
|
+
module cannot be found, say instead that ${rness} is not installed next to
|
|
3461
|
+
this repository.
|
|
3462
|
+
|
|
3463
|
+
End with this line: _For the view with tabs and scrolling: here, Ctrl+Z,
|
|
3464
|
+
then \`npx @rness/cli status\` (q to close), then \`fg\`; or in another
|
|
3465
|
+
terminal, from the workspace's \`.rness/\`: \`${localRunner(pm)} rness status\`._
|
|
3466
|
+
`;
|
|
3467
|
+
}
|
|
3468
|
+
var LIFECYCLE2 = [
|
|
3469
|
+
{
|
|
3470
|
+
name: "adr",
|
|
3471
|
+
description: "Record a decision of the rness workspace as an ADR in .rness/adr/ \u2014 a choice expensive to reverse or that creates a lasting constraint. Use when the developer asks for an ADR, or when the conversation reaches such a decision.",
|
|
3472
|
+
hint: "[subject | NNNN]",
|
|
3473
|
+
tab: "adr",
|
|
3474
|
+
body: (rness) => `You record a decision of this workspace as an ADR in \`${rness}/adr/\`. The table
|
|
3475
|
+
above lists the existing ADRs, newest first. Arguments: \`$ARGUMENTS\`.
|
|
3476
|
+
|
|
3477
|
+
Before writing:
|
|
3478
|
+
|
|
3479
|
+
1. If the developer did not ask for an ADR in so many words, propose one in
|
|
3480
|
+
a single line \u2014 its title, and why the choice is hard to reverse \u2014 and
|
|
3481
|
+
wait for a yes.
|
|
3482
|
+
2. Read \`${rness}/CONVENTIONS.md\` and \`${rness}/adr/0000-template.md\`: they hold the
|
|
3483
|
+
rules and the sections; this skill is only the procedure.
|
|
3484
|
+
3. Arguments naming an ADR (\`NNNN\`): reopen that file. An \`Accepted\` ADR is
|
|
3485
|
+
never edited: offer a new ADR that supersedes it.
|
|
3486
|
+
4. Otherwise the subject is the arguments, else the decision just
|
|
3487
|
+
discussed. Context, alternatives or consequences missing from the
|
|
3488
|
+
conversation: ask for them, one question at a time. Never invent them.
|
|
3489
|
+
|
|
3490
|
+
Writing:
|
|
3491
|
+
|
|
3492
|
+
- \`${rness}/adr/NNNN-<slug>.md\`, NNNN the highest number above plus one.
|
|
3493
|
+
- The template's front matter and sections, filled in, its guidance lines
|
|
3494
|
+
removed: \`date\` today, \`status: Proposed\`, \`repo\` the repositories under
|
|
3495
|
+
\`org/\` it affects.
|
|
3496
|
+
- An ADR it replaces is named in Context. That one becomes \`Superseded\`,
|
|
3497
|
+
with \`superseded_by:\`, only once the new ADR is \`Accepted\`.
|
|
3498
|
+
- Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
|
|
3499
|
+
unless it is already there.
|
|
3500
|
+
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3501
|
+
|
|
3502
|
+
After writing: give the path and the Decision section, and say the ADR
|
|
3503
|
+
stays \`Proposed\` until the developer accepts it. Change a status only when
|
|
3504
|
+
the developer says so. Do not commit.
|
|
3505
|
+
`
|
|
3506
|
+
},
|
|
3507
|
+
{
|
|
3508
|
+
name: "spec",
|
|
3509
|
+
description: "Write a specification of the rness workspace in .rness/specs/ \u2014 the outcome wanted and its scope, before any plan. Use when the developer asks for a spec, or when agreed work is too large to start without one.",
|
|
3510
|
+
hint: "[subject | NNNN]",
|
|
3511
|
+
tab: "specs",
|
|
3512
|
+
body: (rness) => `You write a specification of this workspace in \`${rness}/specs/\`. The table above
|
|
3513
|
+
lists the existing ones, newest first. Arguments: \`$ARGUMENTS\`.
|
|
3514
|
+
|
|
3515
|
+
Before writing:
|
|
3516
|
+
|
|
3517
|
+
1. If the developer did not ask for a specification in so many words,
|
|
3518
|
+
propose one in a single line and wait for a yes.
|
|
3519
|
+
2. Read \`${rness}/CONVENTIONS.md\`, then the most recent specification above: match
|
|
3520
|
+
its front matter, sections and tone.
|
|
3521
|
+
3. Arguments naming a specification (\`NNNN\`): reopen it. One that is
|
|
3522
|
+
\`Implemented\`, \`Superseded\` or \`Rejected\` is not edited: offer a new one.
|
|
3523
|
+
4. Otherwise the subject is the arguments, else the work just discussed.
|
|
3524
|
+
The problem, what is in and out of scope, the ADRs it rests on: ask what
|
|
3525
|
+
the conversation does not say, one question at a time.
|
|
3526
|
+
|
|
3527
|
+
Writing:
|
|
3528
|
+
|
|
3529
|
+
- \`${rness}/specs/NNNN-<slug>.md\`, NNNN the highest number above plus one.
|
|
3530
|
+
- \`date\` and \`updated\` today, \`status: Draft\`, \`repo\`, and \`adr:\` linking
|
|
3531
|
+
the decisions it rests on \u2014 or the rationale in the body when none does.
|
|
3532
|
+
- No specification yet to follow: Summary, Scope, Out of scope,
|
|
3533
|
+
Alternatives considered, To verify, Tests.
|
|
3534
|
+
- What was checked in this session (a document read, a command run) is
|
|
3535
|
+
written as verified, with the date; everything else as a proposal or an
|
|
3536
|
+
open question.
|
|
3537
|
+
- Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
|
|
3538
|
+
unless it is already there.
|
|
3539
|
+
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3540
|
+
|
|
3541
|
+
After writing: give the path and the summary. It stays \`Draft\` until the
|
|
3542
|
+
developer moves it (\`Proposed\` for review, \`Approved\` to plan it). No plan
|
|
3543
|
+
here: \`/rness:plan NNNN\` once approved. Do not commit.
|
|
3544
|
+
`
|
|
3545
|
+
},
|
|
3546
|
+
{
|
|
3547
|
+
name: "plan",
|
|
3548
|
+
description: "Turn an approved specification of the rness workspace into a plan in .rness/plans/ \u2014 ordered tasks, each with the command that proves it done. Use when the developer asks for a plan, or when a specification has just been approved.",
|
|
3549
|
+
hint: "<spec NNNN>",
|
|
3550
|
+
tab: "plans",
|
|
3551
|
+
body: (rness) => `You turn a specification into a plan in \`${rness}/plans/\`. The table above lists
|
|
3552
|
+
the existing plans, newest first. Arguments, the specification:
|
|
3553
|
+
\`$ARGUMENTS\`.
|
|
3554
|
+
|
|
3555
|
+
Before writing:
|
|
3556
|
+
|
|
3557
|
+
1. If the developer did not ask for a plan in so many words, propose one in
|
|
3558
|
+
a single line and wait for a yes.
|
|
3559
|
+
2. No specification named: ask which. Read it, \`${rness}/CONVENTIONS.md\`, and the
|
|
3560
|
+
most recent plan above: match its front matter and sections.
|
|
3561
|
+
3. A specification that is not \`Approved\`: say so, and plan it only if the
|
|
3562
|
+
developer confirms. A plan of it that is neither \`Completed\` nor
|
|
3563
|
+
\`Abandoned\`: reopen it rather than write a second.
|
|
3564
|
+
4. Read the code and the repositories the specification touches: every
|
|
3565
|
+
task names real files and real commands.
|
|
3566
|
+
|
|
3567
|
+
Writing:
|
|
3568
|
+
|
|
3569
|
+
- \`${rness}/plans/NNNN-<slug>.md\`, NNNN the highest plan number above plus one \u2014
|
|
3570
|
+
not the specification's.
|
|
3571
|
+
- \`date\` and \`updated\` today, \`status: Draft\`, \`repo\`, \`spec:\` linking the
|
|
3572
|
+
specification.
|
|
3573
|
+
- Tasks in order, as checkboxes. Each gives the files, what changes, and
|
|
3574
|
+
the command that proves it (a test, a build, a run). The last tasks:
|
|
3575
|
+
the documentation the work makes inaccurate, then the release when the
|
|
3576
|
+
repository has one.
|
|
3577
|
+
- Where the plan decides something the specification leaves open, a
|
|
3578
|
+
section says so.
|
|
3579
|
+
- Add \`\${CLAUDE_SESSION_ID}\` at the end of \`sessions:\` in the front matter,
|
|
3580
|
+
unless it is already there.
|
|
3581
|
+
- The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3582
|
+
|
|
3583
|
+
After writing: give the path and the list of tasks. It becomes \`Ready\` when
|
|
3584
|
+
the developer agrees. Do not commit.
|
|
3585
|
+
`
|
|
3586
|
+
},
|
|
3587
|
+
{
|
|
3588
|
+
name: "done",
|
|
3589
|
+
description: "Close a piece of work of the rness workspace \u2014 check each task of its plan on evidence, then move the plan and its specification to their final status and correct the documents the work made inaccurate. Use when the developer says the work is done, or when the last task of a plan passes.",
|
|
3590
|
+
hint: "[plan NNNN]",
|
|
3591
|
+
tab: "plans",
|
|
3592
|
+
body: (rness) => `You close a piece of work: its plan in \`${rness}/plans/\`, its specification, and
|
|
3593
|
+
what they made inaccurate. The table above lists the plans, newest first.
|
|
3594
|
+
Arguments: \`$ARGUMENTS\`.
|
|
3595
|
+
|
|
3596
|
+
1. The plan: the one named, else the one \`In progress\`. Several or none:
|
|
3597
|
+
ask.
|
|
3598
|
+
2. If the developer did not ask to close it in so many words, propose it in
|
|
3599
|
+
a single line and wait for a yes.
|
|
3600
|
+
3. Read the plan, its specification and \`${rness}/CONVENTIONS.md\`.
|
|
3601
|
+
4. Evidence first. For each task, what shows it done: a test, a build, a
|
|
3602
|
+
command's output in this session. A task without evidence: run its
|
|
3603
|
+
check now, or report it. Never mark a task done on belief.
|
|
3604
|
+
5. All tasks proven:
|
|
3605
|
+
- the plan: \`status: Completed\`, \`updated\` today, the checkboxes of the
|
|
3606
|
+
proven tasks ticked, and a section \`Verification (YYYY-MM-DD)\` \u2014 what
|
|
3607
|
+
ran, what it showed, what could not run and why;
|
|
3608
|
+
- the specification: \`Implemented\` once every plan of it is
|
|
3609
|
+
\`Completed\`; \`updated\` today;
|
|
3610
|
+
- the plan and the specification: add \`\${CLAUDE_SESSION_ID}\` at the end
|
|
3611
|
+
of \`sessions:\` in their front matter, unless it is already there;
|
|
3612
|
+
- the smallest set of documents the work makes inaccurate: \`${rness}/docs/\`,
|
|
3613
|
+
the READMEs of the repositories it changed.
|
|
3614
|
+
6. A task not proven: the plan stays \`In progress\` \u2014 or \`Blocked\`, the
|
|
3615
|
+
blocker written in it. Say what remains.
|
|
3616
|
+
7. The rness hook checks every edit under \`${rness}/\`: fix what it reports.
|
|
3617
|
+
|
|
3618
|
+
After: list the files changed and the statuses moved. Do not commit.
|
|
3619
|
+
`
|
|
3620
|
+
}
|
|
3621
|
+
];
|
|
3622
|
+
function lifecycleSkill(rness, s) {
|
|
3623
|
+
const command = `node "\${CLAUDE_PROJECT_DIR}/${rness}/${PINNED_BIN}" status ${s.tab} --cwd "\${CLAUDE_PROJECT_DIR}"`;
|
|
3624
|
+
return `---
|
|
3625
|
+
name: ${s.name}
|
|
3626
|
+
description: ${s.description}
|
|
3627
|
+
argument-hint: '${s.hint}'
|
|
3628
|
+
allowed-tools: Bash(${command})
|
|
3629
|
+
---
|
|
3630
|
+
|
|
3631
|
+
!\`${command}\`
|
|
3632
|
+
|
|
3633
|
+
${s.body(rness)}`;
|
|
3634
|
+
}
|
|
3635
|
+
function pluginFiles(rness, at, ctx) {
|
|
3636
|
+
return [
|
|
3637
|
+
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
3638
|
+
{
|
|
3639
|
+
file: `${PLUGIN}/skills/status/SKILL.md`,
|
|
3640
|
+
at,
|
|
3641
|
+
content: statusSkill(rness, ctx.packageManager)
|
|
3642
|
+
},
|
|
3643
|
+
...LIFECYCLE2.map((s) => ({
|
|
3644
|
+
file: `${PLUGIN}/skills/${s.name}/SKILL.md`,
|
|
3645
|
+
at,
|
|
3646
|
+
content: lifecycleSkill(rness, s)
|
|
3647
|
+
}))
|
|
3648
|
+
];
|
|
3649
|
+
}
|
|
3357
3650
|
|
|
3358
3651
|
// src/core/agents.ts
|
|
3359
|
-
var PINNED_BIN = "node_modules/@rness/cli/dist/bin/rness.js";
|
|
3360
3652
|
function hookLine(rness, event) {
|
|
3361
3653
|
const run3 = `f="$CLAUDE_PROJECT_DIR/${rness}/${PINNED_BIN}"; if [ -f "$f" ]; then node "$f" hook ${event};`;
|
|
3362
3654
|
if (event !== "session-start") return `${run3} fi`;
|
|
@@ -3409,53 +3701,11 @@ function hooks(rness) {
|
|
|
3409
3701
|
}
|
|
3410
3702
|
];
|
|
3411
3703
|
}
|
|
3412
|
-
var PLUGIN = ".claude/skills/rness";
|
|
3413
|
-
var PLUGIN_JSON = `${JSON.stringify(
|
|
3414
|
-
{
|
|
3415
|
-
name: "rness",
|
|
3416
|
-
description: "The rness workspace in Claude Code: /rness:status."
|
|
3417
|
-
},
|
|
3418
|
-
null,
|
|
3419
|
-
2
|
|
3420
|
-
)}
|
|
3421
|
-
`;
|
|
3422
|
-
function statusSkill(rness) {
|
|
3423
|
-
const command = `node "\${CLAUDE_PROJECT_DIR}/${rness}/${PINNED_BIN}" status --cwd "\${CLAUDE_PROJECT_DIR}"`;
|
|
3424
|
-
return `---
|
|
3425
|
-
name: status
|
|
3426
|
-
description: The status of every decision, specification, plan and other tracked document of the rness workspace, one table per directory. Read-only.
|
|
3427
|
-
argument-hint: '[tab]'
|
|
3428
|
-
disable-model-invocation: true
|
|
3429
|
-
allowed-tools: Bash(${command})
|
|
3430
|
-
---
|
|
3431
|
-
|
|
3432
|
-
!\`${command}\`
|
|
3433
|
-
|
|
3434
|
-
Show the output above to the user as it is: the heading and the tables,
|
|
3435
|
-
nothing added, nothing summarised, no other tool. Arguments: \`$ARGUMENTS\`.
|
|
3436
|
-
When they name a tab, show only that tab's section. If the output says the
|
|
3437
|
-
module cannot be found, say instead that ${rness} is not installed next to
|
|
3438
|
-
this repository.
|
|
3439
|
-
|
|
3440
|
-
End with this line: _For the view with tabs and scrolling: Ctrl+Z, then
|
|
3441
|
-
\`npx @rness/cli status\` (q to close), then \`fg\`._
|
|
3442
|
-
`;
|
|
3443
|
-
}
|
|
3444
|
-
function plugin(rness, at) {
|
|
3445
|
-
return [
|
|
3446
|
-
{ file: `${PLUGIN}/.claude-plugin/plugin.json`, at, content: PLUGIN_JSON },
|
|
3447
|
-
{
|
|
3448
|
-
file: `${PLUGIN}/skills/status/SKILL.md`,
|
|
3449
|
-
at,
|
|
3450
|
-
content: statusSkill(rness)
|
|
3451
|
-
}
|
|
3452
|
-
];
|
|
3453
|
-
}
|
|
3454
3704
|
var TARGETS = {
|
|
3455
3705
|
claude: {
|
|
3456
3706
|
name: "claude",
|
|
3457
3707
|
label: "Claude Code",
|
|
3458
|
-
files: [
|
|
3708
|
+
files: (ctx) => [
|
|
3459
3709
|
{
|
|
3460
3710
|
file: ".claude/settings.json",
|
|
3461
3711
|
at: "clones",
|
|
@@ -3491,7 +3741,7 @@ var TARGETS = {
|
|
|
3491
3741
|
}
|
|
3492
3742
|
]
|
|
3493
3743
|
},
|
|
3494
|
-
...
|
|
3744
|
+
...pluginFiles("../../.rness", "clones", ctx),
|
|
3495
3745
|
// A session opened at the workspace root reads this file, one opened
|
|
3496
3746
|
// in a clone does not (spec 0011 §1): the hooks only, `.rness` inside.
|
|
3497
3747
|
{
|
|
@@ -3499,7 +3749,7 @@ var TARGETS = {
|
|
|
3499
3749
|
at: "root",
|
|
3500
3750
|
guarantees: hooks(".rness")
|
|
3501
3751
|
},
|
|
3502
|
-
...
|
|
3752
|
+
...pluginFiles(".rness", "root", ctx)
|
|
3503
3753
|
]
|
|
3504
3754
|
}
|
|
3505
3755
|
};
|
|
@@ -3613,30 +3863,288 @@ function describeGuarantee(g) {
|
|
|
3613
3863
|
return `${g.path.join(".")} lacks ${what}`;
|
|
3614
3864
|
}
|
|
3615
3865
|
|
|
3866
|
+
// src/core/pinned.ts
|
|
3867
|
+
import { execFile as execFile3 } from "child_process";
|
|
3868
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
3869
|
+
import { basename as basename3, join as join12 } from "path";
|
|
3870
|
+
import { promisify as promisify3 } from "util";
|
|
3871
|
+
var execFileP3 = promisify3(execFile3);
|
|
3872
|
+
var PACKAGE = "@rness/cli";
|
|
3873
|
+
var EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
3874
|
+
function compareVersions(a, b) {
|
|
3875
|
+
const split = (v) => {
|
|
3876
|
+
const dash = v.indexOf("-");
|
|
3877
|
+
const core = dash === -1 ? v : v.slice(0, dash);
|
|
3878
|
+
return [core.split(".").map(Number), dash === -1 ? null : v.slice(dash + 1)];
|
|
3879
|
+
};
|
|
3880
|
+
const [coreA, preA] = split(a);
|
|
3881
|
+
const [coreB, preB] = split(b);
|
|
3882
|
+
for (let i = 0; i < 3; i += 1) {
|
|
3883
|
+
const diff = (coreA[i] ?? 0) - (coreB[i] ?? 0);
|
|
3884
|
+
if (diff !== 0) return diff;
|
|
3885
|
+
}
|
|
3886
|
+
if (preA === preB) return 0;
|
|
3887
|
+
if (preA === null) return 1;
|
|
3888
|
+
if (preB === null) return -1;
|
|
3889
|
+
return preA.localeCompare(preB, "en", { numeric: true });
|
|
3890
|
+
}
|
|
3891
|
+
async function readPackageJson(rnessDir) {
|
|
3892
|
+
try {
|
|
3893
|
+
const parsed = JSON.parse(
|
|
3894
|
+
await readFile7(join12(rnessDir, "package.json"), "utf8")
|
|
3895
|
+
);
|
|
3896
|
+
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
3897
|
+
} catch {
|
|
3898
|
+
return null;
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
async function readPin(rnessDir) {
|
|
3902
|
+
const pkg = await readPackageJson(rnessDir);
|
|
3903
|
+
if (pkg === null) return null;
|
|
3904
|
+
for (const field of ["devDependencies", "dependencies"]) {
|
|
3905
|
+
const deps = pkg[field];
|
|
3906
|
+
if (deps === null || typeof deps !== "object") continue;
|
|
3907
|
+
const spec = deps[PACKAGE];
|
|
3908
|
+
if (typeof spec === "string") return { spec, field };
|
|
3909
|
+
}
|
|
3910
|
+
return null;
|
|
3911
|
+
}
|
|
3912
|
+
async function writePin(rnessDir, from, to) {
|
|
3913
|
+
const file = join12(rnessDir, "package.json");
|
|
3914
|
+
const text = await readFile7(file, "utf8");
|
|
3915
|
+
const entry = new RegExp(
|
|
3916
|
+
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
3917
|
+
);
|
|
3918
|
+
if (!entry.test(text))
|
|
3919
|
+
throw new Error(`"${PACKAGE}": "${from}" not found in package.json`);
|
|
3920
|
+
await writeFileAtomic(file, text.replace(entry, `$1${to}$2`));
|
|
3921
|
+
}
|
|
3922
|
+
var LOCKFILES = [
|
|
3923
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
3924
|
+
["yarn.lock", "yarn"],
|
|
3925
|
+
["bun.lock", "bun"],
|
|
3926
|
+
["bun.lockb", "bun"],
|
|
3927
|
+
["package-lock.json", "npm"]
|
|
3928
|
+
];
|
|
3929
|
+
async function workspacePackageManager(rnessDir) {
|
|
3930
|
+
const declared2 = (await readPackageJson(rnessDir))?.["packageManager"];
|
|
3931
|
+
if (typeof declared2 === "string") {
|
|
3932
|
+
const name = declared2.split("@")[0] ?? "";
|
|
3933
|
+
if (isPackageManager(name)) return name;
|
|
3934
|
+
}
|
|
3935
|
+
for (const [lockfile, pm] of LOCKFILES)
|
|
3936
|
+
if (await exists(join12(rnessDir, lockfile))) return pm;
|
|
3937
|
+
return "npm";
|
|
3938
|
+
}
|
|
3939
|
+
async function hasLockfile(rnessDir, pm) {
|
|
3940
|
+
for (const [lockfile, owner] of LOCKFILES)
|
|
3941
|
+
if (owner === pm && await exists(join12(rnessDir, lockfile))) return true;
|
|
3942
|
+
return false;
|
|
3943
|
+
}
|
|
3944
|
+
async function pinDrift(rnessDir) {
|
|
3945
|
+
const pin = await readPin(rnessDir);
|
|
3946
|
+
if (pin === null || !EXACT_VERSION.test(pin.spec)) return null;
|
|
3947
|
+
const installed = (await readPinnedCli(rnessDir))?.version ?? null;
|
|
3948
|
+
return installed === pin.spec ? null : { pin: pin.spec, installed };
|
|
3949
|
+
}
|
|
3950
|
+
async function driftWarning(cwd) {
|
|
3951
|
+
let root;
|
|
3952
|
+
let rnessDir;
|
|
3953
|
+
try {
|
|
3954
|
+
const ws = await findWorkspace(cwd);
|
|
3955
|
+
root = ws.root;
|
|
3956
|
+
rnessDir = ws.rnessDir;
|
|
3957
|
+
} catch {
|
|
3958
|
+
return null;
|
|
3959
|
+
}
|
|
3960
|
+
const drift = await pinDrift(rnessDir);
|
|
3961
|
+
if (drift === null) return null;
|
|
3962
|
+
const pm = await workspacePackageManager(rnessDir);
|
|
3963
|
+
return warn(
|
|
3964
|
+
`${basename3(root)}/.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${pm} install in .rness`
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
var CHILD_DEADLINE_MS = 12e4;
|
|
3968
|
+
function childEnv(env = process.env) {
|
|
3969
|
+
return {
|
|
3970
|
+
...env,
|
|
3971
|
+
GIT_SSH_COMMAND: `${env["GIT_SSH_COMMAND"] ?? "ssh"} -o BatchMode=yes`,
|
|
3972
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
async function ensureInstalled(rnessDir, opts = {}) {
|
|
3976
|
+
const drift = await pinDrift(rnessDir);
|
|
3977
|
+
if (drift === null) return null;
|
|
3978
|
+
const env = opts.env ?? process.env;
|
|
3979
|
+
if (env["RNESS_NO_INSTALL"] === "1")
|
|
3980
|
+
return { installed: false, pin: drift.pin, reason: "RNESS_NO_INSTALL=1" };
|
|
3981
|
+
const pm = await workspacePackageManager(rnessDir);
|
|
3982
|
+
const frozen = await hasLockfile(rnessDir, pm);
|
|
3983
|
+
const install = opts.install ?? installDependencies;
|
|
3984
|
+
opts.announce?.(drift.pin);
|
|
3985
|
+
try {
|
|
3986
|
+
await install(pm, rnessDir, { frozen });
|
|
3987
|
+
return { installed: true, pin: drift.pin, reason: null };
|
|
3988
|
+
} catch (e) {
|
|
3989
|
+
return {
|
|
3990
|
+
installed: false,
|
|
3991
|
+
pin: drift.pin,
|
|
3992
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
async function runPinnedSync(root) {
|
|
3997
|
+
const installed = await readPinnedCli(join12(root, ".rness"));
|
|
3998
|
+
if (installed === null) return null;
|
|
3999
|
+
const pinned = join12(installed.dir, ...installed.bin.split("/"));
|
|
4000
|
+
if (!await exists(pinned))
|
|
4001
|
+
return {
|
|
4002
|
+
code: 1,
|
|
4003
|
+
stdout: "",
|
|
4004
|
+
stderr: `@rness/cli in .rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
4005
|
+
`
|
|
4006
|
+
};
|
|
4007
|
+
try {
|
|
4008
|
+
const { stdout, stderr } = await execFileP3(
|
|
4009
|
+
process.execPath,
|
|
4010
|
+
[pinned, "sync", "--yes"],
|
|
4011
|
+
{
|
|
4012
|
+
cwd: root,
|
|
4013
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
4014
|
+
env: childEnv(),
|
|
4015
|
+
timeout: CHILD_DEADLINE_MS,
|
|
4016
|
+
killSignal: "SIGKILL"
|
|
4017
|
+
}
|
|
4018
|
+
);
|
|
4019
|
+
return { code: 0, stdout, stderr };
|
|
4020
|
+
} catch (e) {
|
|
4021
|
+
const err = e;
|
|
4022
|
+
const deadline = err.killed === true ? `rness sync in .rness did not finish within ${CHILD_DEADLINE_MS / 1e3}s and was stopped
|
|
4023
|
+
` : "";
|
|
4024
|
+
return {
|
|
4025
|
+
code: typeof err.code === "number" ? err.code : 1,
|
|
4026
|
+
stdout: err.stdout ?? "",
|
|
4027
|
+
stderr: `${err.stderr ?? ""}${deadline}`
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
var SYNC_VERBS = ["updated", "unchanged", "stale"];
|
|
4032
|
+
function counted(noun, counts) {
|
|
4033
|
+
const total = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
4034
|
+
const detail = SYNC_VERBS.filter((verb) => counts.has(verb)).map((verb) => `${counts.get(verb) ?? 0} ${verb}`).join(", ");
|
|
4035
|
+
return `${total} ${noun}${total === 1 ? "" : "s"}${detail === "" ? "" : `: ${detail}`}`;
|
|
4036
|
+
}
|
|
4037
|
+
function summariseSync(stdout) {
|
|
4038
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
4039
|
+
const agentFiles = /* @__PURE__ */ new Map();
|
|
4040
|
+
const others = [];
|
|
4041
|
+
for (const line3 of stdout.split("\n")) {
|
|
4042
|
+
if (line3.trim() === "") continue;
|
|
4043
|
+
const m = /^(updated|unchanged|stale)\s+(\S+)/.exec(line3);
|
|
4044
|
+
if (m === null) {
|
|
4045
|
+
others.push(line3);
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
const [, verb = "", path = ""] = m;
|
|
4049
|
+
const counts = /(^|\/)(AGENTS|CLAUDE)\.md$/.test(path) ? blocks : agentFiles;
|
|
4050
|
+
counts.set(verb, (counts.get(verb) ?? 0) + 1);
|
|
4051
|
+
}
|
|
4052
|
+
return {
|
|
4053
|
+
summary: [
|
|
4054
|
+
counted("block", blocks),
|
|
4055
|
+
...agentFiles.size > 0 ? [counted("agent file", agentFiles)] : []
|
|
4056
|
+
].join("; "),
|
|
4057
|
+
others
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
4060
|
+
function syncOutcome(result, opts = {}) {
|
|
4061
|
+
const { others } = summariseSync(result.stdout);
|
|
4062
|
+
const wrote = /^(updated|unchanged|stale)\s/m.test(result.stdout);
|
|
4063
|
+
const stderr = result.stderr.split("\n").map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
4064
|
+
const hints = wrote ? [...others, ...stderr] : others;
|
|
4065
|
+
if (opts.sshWorks === true && /Permission denied \(publickey\)/.test(result.stderr))
|
|
4066
|
+
hints.push(...agentHintLines());
|
|
4067
|
+
return {
|
|
4068
|
+
code: wrote ? 0 : result.code,
|
|
4069
|
+
hints,
|
|
4070
|
+
errors: wrote ? [] : stderr
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
async function syncPinned(root, shown, fallback) {
|
|
4074
|
+
const rnessDir = join12(root, ".rness");
|
|
4075
|
+
const installed = await readPinnedCli(rnessDir);
|
|
4076
|
+
if (installed === null) {
|
|
4077
|
+
if (fallback !== void 0) return fallback();
|
|
4078
|
+
process.stderr.write(
|
|
4079
|
+
`@rness/cli is not installed in ${shown}/.rness; install in .rness/, then ${rnessCommand()} sync
|
|
4080
|
+
`
|
|
4081
|
+
);
|
|
4082
|
+
return 1;
|
|
4083
|
+
}
|
|
4084
|
+
const pinned = join12(installed.dir, ...installed.bin.split("/"));
|
|
4085
|
+
if (!await exists(pinned)) {
|
|
4086
|
+
process.stderr.write(
|
|
4087
|
+
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
4088
|
+
`
|
|
4089
|
+
);
|
|
4090
|
+
return 1;
|
|
4091
|
+
}
|
|
4092
|
+
try {
|
|
4093
|
+
const { stdout, stderr } = await execFileP3(
|
|
4094
|
+
process.execPath,
|
|
4095
|
+
[pinned, "sync", "--yes"],
|
|
4096
|
+
{
|
|
4097
|
+
cwd: root,
|
|
4098
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
4099
|
+
env: childEnv(),
|
|
4100
|
+
timeout: CHILD_DEADLINE_MS,
|
|
4101
|
+
killSignal: "SIGKILL"
|
|
4102
|
+
}
|
|
4103
|
+
);
|
|
4104
|
+
process.stdout.write(stdout);
|
|
4105
|
+
if (stderr !== "") process.stderr.write(stderr);
|
|
4106
|
+
return 0;
|
|
4107
|
+
} catch (e) {
|
|
4108
|
+
const err = e;
|
|
4109
|
+
if (err.stdout !== void 0 && err.stdout !== "")
|
|
4110
|
+
process.stdout.write(err.stdout);
|
|
4111
|
+
if (err.stderr !== void 0 && err.stderr !== "")
|
|
4112
|
+
process.stderr.write(err.stderr);
|
|
4113
|
+
return typeof err.code === "number" ? err.code : 1;
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
|
|
3616
4117
|
// src/core/agent-targets.ts
|
|
3617
4118
|
function wholeDiff(existing, content) {
|
|
3618
4119
|
if (existing === content) return null;
|
|
3619
4120
|
return existing === null ? "missing" : "differs from what rness writes";
|
|
3620
4121
|
}
|
|
4122
|
+
async function contextOf(root) {
|
|
4123
|
+
return {
|
|
4124
|
+
packageManager: await workspacePackageManager(join13(root, ".rness"))
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
var PATHS_ONLY = { packageManager: "npm" };
|
|
3621
4128
|
async function filesOf(root, manifest, targets) {
|
|
3622
4129
|
const files = [];
|
|
3623
|
-
if (!await exists(
|
|
4130
|
+
if (!await exists(join13(root, "org"))) return files;
|
|
4131
|
+
const ctx = await contextOf(root);
|
|
3624
4132
|
for (const target2 of targets)
|
|
3625
|
-
for (const owned of target2.files)
|
|
4133
|
+
for (const owned of target2.files(ctx))
|
|
3626
4134
|
if (owned.at === "root")
|
|
3627
4135
|
files.push({
|
|
3628
4136
|
...owned,
|
|
3629
|
-
file:
|
|
4137
|
+
file: join13(root, ...owned.file.split("/")),
|
|
3630
4138
|
label: owned.file
|
|
3631
4139
|
});
|
|
3632
4140
|
for (const repo of Object.keys(manifest.repos)) {
|
|
3633
|
-
if (!await exists(
|
|
4141
|
+
if (!await exists(join13(root, "org", repo))) continue;
|
|
3634
4142
|
for (const target2 of targets)
|
|
3635
|
-
for (const owned of target2.files)
|
|
4143
|
+
for (const owned of target2.files(ctx))
|
|
3636
4144
|
if (owned.at === "clones")
|
|
3637
4145
|
files.push({
|
|
3638
4146
|
...owned,
|
|
3639
|
-
file:
|
|
4147
|
+
file: join13(root, "org", repo, ...owned.file.split("/")),
|
|
3640
4148
|
label: `org/${repo}/${owned.file}`
|
|
3641
4149
|
});
|
|
3642
4150
|
}
|
|
@@ -3652,7 +4160,7 @@ function writtenFiles(manifest) {
|
|
|
3652
4160
|
return [
|
|
3653
4161
|
...BLOCK_FILES,
|
|
3654
4162
|
...declared(manifest).flatMap(
|
|
3655
|
-
(t) => t.files.filter((f) => f.at === "clones").map((f) => f.file)
|
|
4163
|
+
(t) => t.files(PATHS_ONLY).filter((f) => f.at === "clones").map((f) => f.file)
|
|
3656
4164
|
)
|
|
3657
4165
|
];
|
|
3658
4166
|
}
|
|
@@ -3960,7 +4468,7 @@ function targetsOf(manifest, root, only) {
|
|
|
3960
4468
|
const segments = entry.path.split("/");
|
|
3961
4469
|
targets.push({
|
|
3962
4470
|
scope: name,
|
|
3963
|
-
dir:
|
|
4471
|
+
dir: join14(root, ...segments),
|
|
3964
4472
|
depth: segments.length,
|
|
3965
4473
|
label: `${entry.path}/AGENTS.md`
|
|
3966
4474
|
});
|
|
@@ -4119,7 +4627,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4119
4627
|
try {
|
|
4120
4628
|
const notCloned = /* @__PURE__ */ new Set();
|
|
4121
4629
|
for (const [name, repo] of Object.entries(manifest.repos)) {
|
|
4122
|
-
const dir =
|
|
4630
|
+
const dir = join14(ws.root, "org", name);
|
|
4123
4631
|
const label = `org/${name}`;
|
|
4124
4632
|
if (!await exists(dir)) {
|
|
4125
4633
|
if (!toClone.has(name)) {
|
|
@@ -4171,7 +4679,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4171
4679
|
lines.push(
|
|
4172
4680
|
`not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
|
|
4173
4681
|
);
|
|
4174
|
-
if (!await exists(
|
|
4682
|
+
if (!await exists(join14(ws.root, "org"))) {
|
|
4175
4683
|
lines.push(["skipped", "blocks (no org/ directory here)"]);
|
|
4176
4684
|
} else {
|
|
4177
4685
|
let differences = 0;
|
|
@@ -4183,7 +4691,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4183
4691
|
continue;
|
|
4184
4692
|
}
|
|
4185
4693
|
const [agentsLinked, claudeLinked] = await Promise.all(
|
|
4186
|
-
BLOCK_FILES.map((f) => isSymlink(
|
|
4694
|
+
BLOCK_FILES.map((f) => isSymlink(join14(t.dir, f)))
|
|
4187
4695
|
);
|
|
4188
4696
|
const atRoot = t.scope === null;
|
|
4189
4697
|
if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
|
|
@@ -4206,7 +4714,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4206
4714
|
`${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
|
|
4207
4715
|
);
|
|
4208
4716
|
}
|
|
4209
|
-
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(
|
|
4717
|
+
const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join14(t.dir, "AGENTS.md"), block, check);
|
|
4210
4718
|
if (typeof agents === "object") {
|
|
4211
4719
|
problems.push(`${t.label}: ${agents.error}`);
|
|
4212
4720
|
continue;
|
|
@@ -4221,7 +4729,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4221
4729
|
}
|
|
4222
4730
|
if (claudeLinked === true) continue;
|
|
4223
4731
|
const claude = await syncBlockFile(
|
|
4224
|
-
|
|
4732
|
+
join14(t.dir, "CLAUDE.md"),
|
|
4225
4733
|
block,
|
|
4226
4734
|
check,
|
|
4227
4735
|
true
|
|
@@ -4278,7 +4786,7 @@ async function syncCommand(opts, deps = {}) {
|
|
|
4278
4786
|
}
|
|
4279
4787
|
|
|
4280
4788
|
// src/commands/add.ts
|
|
4281
|
-
import { basename as
|
|
4789
|
+
import { basename as basename4, join as join15 } from "path";
|
|
4282
4790
|
function printDeclared(ui, result, already) {
|
|
4283
4791
|
for (const name of result.declared) {
|
|
4284
4792
|
if (already.has(name)) continue;
|
|
@@ -4313,7 +4821,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
4313
4821
|
}
|
|
4314
4822
|
if (parsed.name === CONTEXT_REPO) {
|
|
4315
4823
|
process.stderr.write(
|
|
4316
|
-
`${CONTEXT_REPO} is the workspace context, not a member repository \u2014 it is already cloned at ${
|
|
4824
|
+
`${CONTEXT_REPO} is the workspace context, not a member repository \u2014 it is already cloned at ${basename4(ws.root)}/${CONTEXT_REPO}
|
|
4317
4825
|
`
|
|
4318
4826
|
);
|
|
4319
4827
|
return 2;
|
|
@@ -4371,7 +4879,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
4371
4879
|
}
|
|
4372
4880
|
parsed = parseRepoSpec(spec, org, host);
|
|
4373
4881
|
const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
4374
|
-
const dir =
|
|
4882
|
+
const dir = join15(ws.root, "org", parsed.name);
|
|
4375
4883
|
const present = await exists(dir);
|
|
4376
4884
|
if (interactive) {
|
|
4377
4885
|
const { confirm, isCancel } = await terminal.prompts();
|
|
@@ -4488,65 +4996,18 @@ async function contextCommand(opts) {
|
|
|
4488
4996
|
scope
|
|
4489
4997
|
});
|
|
4490
4998
|
process.stdout.write(
|
|
4491
|
-
opts.json ? `${JSON.stringify(ctx, null, 2)}
|
|
4492
|
-
` : renderMarkdown(ctx)
|
|
4493
|
-
);
|
|
4494
|
-
return 0;
|
|
4495
|
-
} catch (e) {
|
|
4496
|
-
return reportError(e);
|
|
4497
|
-
}
|
|
4498
|
-
}
|
|
4499
|
-
|
|
4500
|
-
// src/core/delegate.ts
|
|
4501
|
-
import { readFile as readFile6 } from "fs/promises";
|
|
4502
|
-
import { join as join14 } from "path";
|
|
4503
|
-
var DEFAULT_BIN = "dist/bin/rness.js";
|
|
4504
|
-
async function readPinnedCli(rnessDir) {
|
|
4505
|
-
const dir = join14(rnessDir, "node_modules", "@rness", "cli");
|
|
4506
|
-
let pkg;
|
|
4507
|
-
try {
|
|
4508
|
-
pkg = JSON.parse(await readFile6(join14(dir, "package.json"), "utf8"));
|
|
4509
|
-
} catch {
|
|
4510
|
-
return null;
|
|
4511
|
-
}
|
|
4512
|
-
const declared2 = pkg.bin;
|
|
4513
|
-
let bin = DEFAULT_BIN;
|
|
4514
|
-
if (typeof declared2 === "string") bin = declared2;
|
|
4515
|
-
else if (declared2 !== null && typeof declared2 === "object") {
|
|
4516
|
-
const named = declared2["rness"];
|
|
4517
|
-
if (typeof named === "string") bin = named;
|
|
4518
|
-
}
|
|
4519
|
-
return {
|
|
4520
|
-
dir,
|
|
4521
|
-
bin,
|
|
4522
|
-
version: typeof pkg.version === "string" ? pkg.version : null
|
|
4523
|
-
};
|
|
4524
|
-
}
|
|
4525
|
-
async function pinnedBinPath(rnessDir) {
|
|
4526
|
-
const pinned = await readPinnedCli(rnessDir);
|
|
4527
|
-
if (pinned === null) return null;
|
|
4528
|
-
return join14(pinned.dir, ...pinned.bin.split("/"));
|
|
4529
|
-
}
|
|
4530
|
-
async function findDelegate(cwd, ownVersion) {
|
|
4531
|
-
let root;
|
|
4532
|
-
let rnessDir;
|
|
4533
|
-
try {
|
|
4534
|
-
const ws = await findWorkspace(cwd);
|
|
4535
|
-
root = ws.root;
|
|
4536
|
-
rnessDir = ws.rnessDir;
|
|
4537
|
-
} catch {
|
|
4538
|
-
return null;
|
|
4999
|
+
opts.json ? `${JSON.stringify(ctx, null, 2)}
|
|
5000
|
+
` : renderMarkdown(ctx)
|
|
5001
|
+
);
|
|
5002
|
+
return 0;
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
return reportError(e);
|
|
4539
5005
|
}
|
|
4540
|
-
const pinned = await readPinnedCli(rnessDir);
|
|
4541
|
-
if (pinned === null) return null;
|
|
4542
|
-
const { version } = pinned;
|
|
4543
|
-
if (version === null || version === ownVersion) return null;
|
|
4544
|
-
return { entry: join14(pinned.dir, "dist", "index.js"), version, root };
|
|
4545
5006
|
}
|
|
4546
5007
|
|
|
4547
5008
|
// src/core/scaffold.ts
|
|
4548
5009
|
import { statSync } from "fs";
|
|
4549
|
-
import { dirname as dirname5, join as
|
|
5010
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
4550
5011
|
import { fileURLToPath } from "url";
|
|
4551
5012
|
var SCAFFOLD_FILES = [
|
|
4552
5013
|
"AGENTS.md",
|
|
@@ -4570,9 +5031,9 @@ var SCAFFOLD_FILES = [
|
|
|
4570
5031
|
function scaffoldDir() {
|
|
4571
5032
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
4572
5033
|
for (let i = 0; i < 4; i += 1) {
|
|
4573
|
-
const candidate =
|
|
5034
|
+
const candidate = join16(dir, "scaffold");
|
|
4574
5035
|
try {
|
|
4575
|
-
if (statSync(
|
|
5036
|
+
if (statSync(join16(candidate, "rness.json")).isFile()) return candidate;
|
|
4576
5037
|
} catch {
|
|
4577
5038
|
}
|
|
4578
5039
|
dir = dirname5(dir);
|
|
@@ -4581,15 +5042,15 @@ function scaffoldDir() {
|
|
|
4581
5042
|
}
|
|
4582
5043
|
|
|
4583
5044
|
// src/core/scaffold-copy.ts
|
|
4584
|
-
import { chmod as chmod2, mkdir as mkdir3, readFile as
|
|
4585
|
-
import { dirname as dirname6, join as
|
|
5045
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile8, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
5046
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
4586
5047
|
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
4587
5048
|
async function renderScaffold(source, tokens) {
|
|
4588
5049
|
const files = [];
|
|
4589
5050
|
for (const rel of SCAFFOLD_FILES) {
|
|
4590
5051
|
if (rel === "rness.json") continue;
|
|
4591
|
-
const from =
|
|
4592
|
-
let content = await
|
|
5052
|
+
const from = join17(source, ...rel.split("/"));
|
|
5053
|
+
let content = await readFile8(from, "utf8");
|
|
4593
5054
|
if (TOKENISED.has(rel)) {
|
|
4594
5055
|
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
4595
5056
|
}
|
|
@@ -4603,7 +5064,7 @@ async function renderScaffold(source, tokens) {
|
|
|
4603
5064
|
}
|
|
4604
5065
|
async function copyScaffold(dest, tokens) {
|
|
4605
5066
|
for (const file of await renderScaffold(scaffoldDir(), tokens)) {
|
|
4606
|
-
const to =
|
|
5067
|
+
const to = join17(dest, ...file.path.split("/"));
|
|
4607
5068
|
await mkdir3(dirname6(to), { recursive: true });
|
|
4608
5069
|
await writeFile2(to, file.content, "utf8");
|
|
4609
5070
|
if (file.executable) await chmod2(to, 493);
|
|
@@ -4615,16 +5076,16 @@ import { spawn as spawn3 } from "child_process";
|
|
|
4615
5076
|
import { realpath } from "fs/promises";
|
|
4616
5077
|
|
|
4617
5078
|
// src/core/setup-git.ts
|
|
4618
|
-
import { execFile as
|
|
4619
|
-
import { promisify as
|
|
4620
|
-
var
|
|
5079
|
+
import { execFile as execFile4 } from "child_process";
|
|
5080
|
+
import { promisify as promisify4 } from "util";
|
|
5081
|
+
var execFileP4 = promisify4(execFile4);
|
|
4621
5082
|
var KEY = "credential.https://github.com.helper";
|
|
4622
5083
|
function helperValue(node, bin) {
|
|
4623
5084
|
return `!'${node}' '${bin}' git-credential`;
|
|
4624
5085
|
}
|
|
4625
5086
|
var isOurs = (value) => /^!'[^']+' '[^']*rness[^']*' git-credential$/.test(value);
|
|
4626
5087
|
async function config(args) {
|
|
4627
|
-
const { stdout } = await
|
|
5088
|
+
const { stdout } = await execFileP4("git", ["config", "--global", ...args]);
|
|
4628
5089
|
return stdout;
|
|
4629
5090
|
}
|
|
4630
5091
|
async function values() {
|
|
@@ -4802,257 +5263,6 @@ async function askProvider(p) {
|
|
|
4802
5263
|
import { mkdir as mkdir4, readdir as readdir3, rm as rm5, stat as stat3 } from "fs/promises";
|
|
4803
5264
|
import { dirname as dirname8, join as join19, relative as relative2 } from "path";
|
|
4804
5265
|
|
|
4805
|
-
// src/core/pinned.ts
|
|
4806
|
-
import { execFile as execFile4 } from "child_process";
|
|
4807
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
4808
|
-
import { basename as basename4, join as join17 } from "path";
|
|
4809
|
-
import { promisify as promisify4 } from "util";
|
|
4810
|
-
var execFileP4 = promisify4(execFile4);
|
|
4811
|
-
var PACKAGE = "@rness/cli";
|
|
4812
|
-
var EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
4813
|
-
function compareVersions(a, b) {
|
|
4814
|
-
const split = (v) => {
|
|
4815
|
-
const dash = v.indexOf("-");
|
|
4816
|
-
const core = dash === -1 ? v : v.slice(0, dash);
|
|
4817
|
-
return [core.split(".").map(Number), dash === -1 ? null : v.slice(dash + 1)];
|
|
4818
|
-
};
|
|
4819
|
-
const [coreA, preA] = split(a);
|
|
4820
|
-
const [coreB, preB] = split(b);
|
|
4821
|
-
for (let i = 0; i < 3; i += 1) {
|
|
4822
|
-
const diff = (coreA[i] ?? 0) - (coreB[i] ?? 0);
|
|
4823
|
-
if (diff !== 0) return diff;
|
|
4824
|
-
}
|
|
4825
|
-
if (preA === preB) return 0;
|
|
4826
|
-
if (preA === null) return 1;
|
|
4827
|
-
if (preB === null) return -1;
|
|
4828
|
-
return preA.localeCompare(preB, "en", { numeric: true });
|
|
4829
|
-
}
|
|
4830
|
-
async function readPackageJson(rnessDir) {
|
|
4831
|
-
try {
|
|
4832
|
-
const parsed = JSON.parse(
|
|
4833
|
-
await readFile8(join17(rnessDir, "package.json"), "utf8")
|
|
4834
|
-
);
|
|
4835
|
-
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
4836
|
-
} catch {
|
|
4837
|
-
return null;
|
|
4838
|
-
}
|
|
4839
|
-
}
|
|
4840
|
-
async function readPin(rnessDir) {
|
|
4841
|
-
const pkg = await readPackageJson(rnessDir);
|
|
4842
|
-
if (pkg === null) return null;
|
|
4843
|
-
for (const field of ["devDependencies", "dependencies"]) {
|
|
4844
|
-
const deps = pkg[field];
|
|
4845
|
-
if (deps === null || typeof deps !== "object") continue;
|
|
4846
|
-
const spec = deps[PACKAGE];
|
|
4847
|
-
if (typeof spec === "string") return { spec, field };
|
|
4848
|
-
}
|
|
4849
|
-
return null;
|
|
4850
|
-
}
|
|
4851
|
-
async function writePin(rnessDir, from, to) {
|
|
4852
|
-
const file = join17(rnessDir, "package.json");
|
|
4853
|
-
const text = await readFile8(file, "utf8");
|
|
4854
|
-
const entry = new RegExp(
|
|
4855
|
-
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
4856
|
-
);
|
|
4857
|
-
if (!entry.test(text))
|
|
4858
|
-
throw new Error(`"${PACKAGE}": "${from}" not found in package.json`);
|
|
4859
|
-
await writeFileAtomic(file, text.replace(entry, `$1${to}$2`));
|
|
4860
|
-
}
|
|
4861
|
-
var LOCKFILES = [
|
|
4862
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
4863
|
-
["yarn.lock", "yarn"],
|
|
4864
|
-
["bun.lock", "bun"],
|
|
4865
|
-
["bun.lockb", "bun"],
|
|
4866
|
-
["package-lock.json", "npm"]
|
|
4867
|
-
];
|
|
4868
|
-
async function workspacePackageManager(rnessDir) {
|
|
4869
|
-
const declared2 = (await readPackageJson(rnessDir))?.["packageManager"];
|
|
4870
|
-
if (typeof declared2 === "string") {
|
|
4871
|
-
const name = declared2.split("@")[0] ?? "";
|
|
4872
|
-
if (isPackageManager(name)) return name;
|
|
4873
|
-
}
|
|
4874
|
-
for (const [lockfile, pm] of LOCKFILES)
|
|
4875
|
-
if (await exists(join17(rnessDir, lockfile))) return pm;
|
|
4876
|
-
return "npm";
|
|
4877
|
-
}
|
|
4878
|
-
async function hasLockfile(rnessDir, pm) {
|
|
4879
|
-
for (const [lockfile, owner] of LOCKFILES)
|
|
4880
|
-
if (owner === pm && await exists(join17(rnessDir, lockfile))) return true;
|
|
4881
|
-
return false;
|
|
4882
|
-
}
|
|
4883
|
-
async function pinDrift(rnessDir) {
|
|
4884
|
-
const pin = await readPin(rnessDir);
|
|
4885
|
-
if (pin === null || !EXACT_VERSION.test(pin.spec)) return null;
|
|
4886
|
-
const installed = (await readPinnedCli(rnessDir))?.version ?? null;
|
|
4887
|
-
return installed === pin.spec ? null : { pin: pin.spec, installed };
|
|
4888
|
-
}
|
|
4889
|
-
async function driftWarning(cwd) {
|
|
4890
|
-
let root;
|
|
4891
|
-
let rnessDir;
|
|
4892
|
-
try {
|
|
4893
|
-
const ws = await findWorkspace(cwd);
|
|
4894
|
-
root = ws.root;
|
|
4895
|
-
rnessDir = ws.rnessDir;
|
|
4896
|
-
} catch {
|
|
4897
|
-
return null;
|
|
4898
|
-
}
|
|
4899
|
-
const drift = await pinDrift(rnessDir);
|
|
4900
|
-
if (drift === null) return null;
|
|
4901
|
-
const pm = await workspacePackageManager(rnessDir);
|
|
4902
|
-
return warn(
|
|
4903
|
-
`${basename4(root)}/.rness pins @rness/cli ${drift.pin} but ${drift.installed ?? "nothing"} is installed \u2014 run ${pm} install in .rness`
|
|
4904
|
-
);
|
|
4905
|
-
}
|
|
4906
|
-
var CHILD_DEADLINE_MS = 12e4;
|
|
4907
|
-
function childEnv(env = process.env) {
|
|
4908
|
-
return {
|
|
4909
|
-
...env,
|
|
4910
|
-
GIT_SSH_COMMAND: `${env["GIT_SSH_COMMAND"] ?? "ssh"} -o BatchMode=yes`,
|
|
4911
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
4912
|
-
};
|
|
4913
|
-
}
|
|
4914
|
-
async function ensureInstalled(rnessDir, opts = {}) {
|
|
4915
|
-
const drift = await pinDrift(rnessDir);
|
|
4916
|
-
if (drift === null) return null;
|
|
4917
|
-
const env = opts.env ?? process.env;
|
|
4918
|
-
if (env["RNESS_NO_INSTALL"] === "1")
|
|
4919
|
-
return { installed: false, pin: drift.pin, reason: "RNESS_NO_INSTALL=1" };
|
|
4920
|
-
const pm = await workspacePackageManager(rnessDir);
|
|
4921
|
-
const frozen = await hasLockfile(rnessDir, pm);
|
|
4922
|
-
const install = opts.install ?? installDependencies;
|
|
4923
|
-
opts.announce?.(drift.pin);
|
|
4924
|
-
try {
|
|
4925
|
-
await install(pm, rnessDir, { frozen });
|
|
4926
|
-
return { installed: true, pin: drift.pin, reason: null };
|
|
4927
|
-
} catch (e) {
|
|
4928
|
-
return {
|
|
4929
|
-
installed: false,
|
|
4930
|
-
pin: drift.pin,
|
|
4931
|
-
reason: e instanceof Error ? e.message : String(e)
|
|
4932
|
-
};
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
async function runPinnedSync(root) {
|
|
4936
|
-
const installed = await readPinnedCli(join17(root, ".rness"));
|
|
4937
|
-
if (installed === null) return null;
|
|
4938
|
-
const pinned = join17(installed.dir, ...installed.bin.split("/"));
|
|
4939
|
-
if (!await exists(pinned))
|
|
4940
|
-
return {
|
|
4941
|
-
code: 1,
|
|
4942
|
-
stdout: "",
|
|
4943
|
-
stderr: `@rness/cli in .rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
4944
|
-
`
|
|
4945
|
-
};
|
|
4946
|
-
try {
|
|
4947
|
-
const { stdout, stderr } = await execFileP4(
|
|
4948
|
-
process.execPath,
|
|
4949
|
-
[pinned, "sync", "--yes"],
|
|
4950
|
-
{
|
|
4951
|
-
cwd: root,
|
|
4952
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
4953
|
-
env: childEnv(),
|
|
4954
|
-
timeout: CHILD_DEADLINE_MS,
|
|
4955
|
-
killSignal: "SIGKILL"
|
|
4956
|
-
}
|
|
4957
|
-
);
|
|
4958
|
-
return { code: 0, stdout, stderr };
|
|
4959
|
-
} catch (e) {
|
|
4960
|
-
const err = e;
|
|
4961
|
-
const deadline = err.killed === true ? `rness sync in .rness did not finish within ${CHILD_DEADLINE_MS / 1e3}s and was stopped
|
|
4962
|
-
` : "";
|
|
4963
|
-
return {
|
|
4964
|
-
code: typeof err.code === "number" ? err.code : 1,
|
|
4965
|
-
stdout: err.stdout ?? "",
|
|
4966
|
-
stderr: `${err.stderr ?? ""}${deadline}`
|
|
4967
|
-
};
|
|
4968
|
-
}
|
|
4969
|
-
}
|
|
4970
|
-
var SYNC_VERBS = ["updated", "unchanged", "stale"];
|
|
4971
|
-
function counted(noun, counts) {
|
|
4972
|
-
const total = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
4973
|
-
const detail = SYNC_VERBS.filter((verb) => counts.has(verb)).map((verb) => `${counts.get(verb) ?? 0} ${verb}`).join(", ");
|
|
4974
|
-
return `${total} ${noun}${total === 1 ? "" : "s"}${detail === "" ? "" : `: ${detail}`}`;
|
|
4975
|
-
}
|
|
4976
|
-
function summariseSync(stdout) {
|
|
4977
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4978
|
-
const agentFiles = /* @__PURE__ */ new Map();
|
|
4979
|
-
const others = [];
|
|
4980
|
-
for (const line3 of stdout.split("\n")) {
|
|
4981
|
-
if (line3.trim() === "") continue;
|
|
4982
|
-
const m = /^(updated|unchanged|stale)\s+(\S+)/.exec(line3);
|
|
4983
|
-
if (m === null) {
|
|
4984
|
-
others.push(line3);
|
|
4985
|
-
continue;
|
|
4986
|
-
}
|
|
4987
|
-
const [, verb = "", path = ""] = m;
|
|
4988
|
-
const counts = /(^|\/)(AGENTS|CLAUDE)\.md$/.test(path) ? blocks : agentFiles;
|
|
4989
|
-
counts.set(verb, (counts.get(verb) ?? 0) + 1);
|
|
4990
|
-
}
|
|
4991
|
-
return {
|
|
4992
|
-
summary: [
|
|
4993
|
-
counted("block", blocks),
|
|
4994
|
-
...agentFiles.size > 0 ? [counted("agent file", agentFiles)] : []
|
|
4995
|
-
].join("; "),
|
|
4996
|
-
others
|
|
4997
|
-
};
|
|
4998
|
-
}
|
|
4999
|
-
function syncOutcome(result, opts = {}) {
|
|
5000
|
-
const { others } = summariseSync(result.stdout);
|
|
5001
|
-
const wrote = /^(updated|unchanged|stale)\s/m.test(result.stdout);
|
|
5002
|
-
const stderr = result.stderr.split("\n").map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
5003
|
-
const hints = wrote ? [...others, ...stderr] : others;
|
|
5004
|
-
if (opts.sshWorks === true && /Permission denied \(publickey\)/.test(result.stderr))
|
|
5005
|
-
hints.push(...agentHintLines());
|
|
5006
|
-
return {
|
|
5007
|
-
code: wrote ? 0 : result.code,
|
|
5008
|
-
hints,
|
|
5009
|
-
errors: wrote ? [] : stderr
|
|
5010
|
-
};
|
|
5011
|
-
}
|
|
5012
|
-
async function syncPinned(root, shown, fallback) {
|
|
5013
|
-
const rnessDir = join17(root, ".rness");
|
|
5014
|
-
const installed = await readPinnedCli(rnessDir);
|
|
5015
|
-
if (installed === null) {
|
|
5016
|
-
if (fallback !== void 0) return fallback();
|
|
5017
|
-
process.stderr.write(
|
|
5018
|
-
`@rness/cli is not installed in ${shown}/.rness; install in .rness/, then ${rnessCommand()} sync
|
|
5019
|
-
`
|
|
5020
|
-
);
|
|
5021
|
-
return 1;
|
|
5022
|
-
}
|
|
5023
|
-
const pinned = join17(installed.dir, ...installed.bin.split("/"));
|
|
5024
|
-
if (!await exists(pinned)) {
|
|
5025
|
-
process.stderr.write(
|
|
5026
|
-
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
5027
|
-
`
|
|
5028
|
-
);
|
|
5029
|
-
return 1;
|
|
5030
|
-
}
|
|
5031
|
-
try {
|
|
5032
|
-
const { stdout, stderr } = await execFileP4(
|
|
5033
|
-
process.execPath,
|
|
5034
|
-
[pinned, "sync", "--yes"],
|
|
5035
|
-
{
|
|
5036
|
-
cwd: root,
|
|
5037
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
5038
|
-
env: childEnv(),
|
|
5039
|
-
timeout: CHILD_DEADLINE_MS,
|
|
5040
|
-
killSignal: "SIGKILL"
|
|
5041
|
-
}
|
|
5042
|
-
);
|
|
5043
|
-
process.stdout.write(stdout);
|
|
5044
|
-
if (stderr !== "") process.stderr.write(stderr);
|
|
5045
|
-
return 0;
|
|
5046
|
-
} catch (e) {
|
|
5047
|
-
const err = e;
|
|
5048
|
-
if (err.stdout !== void 0 && err.stdout !== "")
|
|
5049
|
-
process.stdout.write(err.stdout);
|
|
5050
|
-
if (err.stderr !== void 0 && err.stderr !== "")
|
|
5051
|
-
process.stderr.write(err.stderr);
|
|
5052
|
-
return typeof err.code === "number" ? err.code : 1;
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
|
|
5056
5266
|
// src/core/scaffold-git.ts
|
|
5057
5267
|
import { execFile as execFile5 } from "child_process";
|
|
5058
5268
|
import { mkdtemp, realpath as realpath2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -7463,7 +7673,7 @@ async function writeOpened(file, opened) {
|
|
|
7463
7673
|
}
|
|
7464
7674
|
|
|
7465
7675
|
// src/pulse/plan.ts
|
|
7466
|
-
var differs = (item, w) => item.title !== w.title || item.status !== w.status || w.statusField !== null && item.collectionStatus !== w.status || item.type !== w.type;
|
|
7676
|
+
var differs = (item, w) => item.title !== w.title || item.status !== w.status || w.statusField !== null && item.collectionStatus !== w.status || item.type !== w.type || item.sessions !== w.sessions;
|
|
7467
7677
|
var rank = (i) => i.issue === null ? 2 : i.issue.open ? 0 : 1;
|
|
7468
7678
|
var keptFirst = (a, b) => rank(a) - rank(b) || (a.issue?.number ?? 0) - (b.issue?.number ?? 0);
|
|
7469
7679
|
var gone = (item) => item.issue === null ? { kind: "archive", id: item.id } : { kind: "close", id: item.id };
|
|
@@ -7849,13 +8059,16 @@ async function mark(opts) {
|
|
|
7849
8059
|
return 0;
|
|
7850
8060
|
}
|
|
7851
8061
|
const wanted = new Set(opts.paths);
|
|
7852
|
-
const documents2 = new
|
|
7853
|
-
(await statusTabs(c.rnessDir)).flatMap(
|
|
8062
|
+
const documents2 = new Map(
|
|
8063
|
+
(await statusTabs(c.rnessDir)).flatMap(
|
|
8064
|
+
(t) => t.rows.map((r) => [r.path, r.status])
|
|
8065
|
+
)
|
|
7854
8066
|
);
|
|
7855
|
-
const onBoard = new
|
|
7856
|
-
items.flatMap((i) => i.path === null ? [] : [i.path])
|
|
8067
|
+
const onBoard = new Map(
|
|
8068
|
+
items.flatMap((i) => i.path === null ? [] : [[i.path, i.status]])
|
|
7857
8069
|
);
|
|
7858
|
-
|
|
8070
|
+
const stale = (path) => documents2.has(path) && (!onBoard.has(path) || onBoard.get(path) !== documents2.get(path));
|
|
8071
|
+
if ([...wanted].some(stale)) {
|
|
7859
8072
|
const synced = await oneSyncAtATime(
|
|
7860
8073
|
key,
|
|
7861
8074
|
() => syncBoard(c, board, plainUi, eachAdded),
|
|
@@ -8353,6 +8566,7 @@ export {
|
|
|
8353
8566
|
defaultTransport,
|
|
8354
8567
|
findWorkspace,
|
|
8355
8568
|
reportError,
|
|
8569
|
+
findDelegate,
|
|
8356
8570
|
BEGIN,
|
|
8357
8571
|
END,
|
|
8358
8572
|
BLOCK_SIZE_WARNING,
|
|
@@ -8367,7 +8581,6 @@ export {
|
|
|
8367
8581
|
addCommand,
|
|
8368
8582
|
renderMarkdown,
|
|
8369
8583
|
contextCommand,
|
|
8370
|
-
findDelegate,
|
|
8371
8584
|
SCAFFOLD_FILES,
|
|
8372
8585
|
scaffoldDir,
|
|
8373
8586
|
copyScaffold,
|
|
@@ -8381,4 +8594,4 @@ export {
|
|
|
8381
8594
|
OWN_COMMANDS,
|
|
8382
8595
|
catchUp
|
|
8383
8596
|
};
|
|
8384
|
-
//# sourceMappingURL=chunk-
|
|
8597
|
+
//# sourceMappingURL=chunk-HGT7BIWK.js.map
|