convene-cli 1.4.1 → 1.4.3

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.
@@ -8,6 +8,7 @@ exports.upsertMarkerBlock = upsertMarkerBlock;
8
8
  exports.removeMarkerBlock = removeMarkerBlock;
9
9
  exports.removeGitignoreGuard = removeGitignoreGuard;
10
10
  exports.removeTomlBlock = removeTomlBlock;
11
+ exports.refreshDocs = refreshDocs;
11
12
  exports.init = init;
12
13
  /**
13
14
  * `convene init` — one-command repo onboarding. IDEMPOTENT + merge-safe
@@ -520,10 +521,111 @@ async function adoptBestPractices(top, slug, opts) {
520
521
  log('· reported adoption to the dashboard');
521
522
  }
522
523
  }
524
+ /**
525
+ * Re-render the managed COORDINATION blocks (CLAUDE.md + AGENTS.md) at the current
526
+ * template. Pure per file (P0-IDEMPOTENT). The two blocks intentionally diverge:
527
+ * CLAUDE.md (conveneBlock) omits the manual-deploy line (its PreToolUse hook gates
528
+ * deploys); AGENTS.md (conveneAgentsBlock) adds it for tools with no in-time gate.
529
+ * upsertMarkerBlock preserves everything OUTSIDE the markers.
530
+ */
531
+ function writeCoordinationBlocks(top, slug, member, baseUrl) {
532
+ const fileBlocks = [
533
+ ['CLAUDE.md', (0, protocol_1.conveneBlock)(slug, member, baseUrl)],
534
+ ['AGENTS.md', (0, protocol_1.conveneAgentsBlock)(slug, member, baseUrl)],
535
+ ];
536
+ for (const [fname, block] of fileBlocks) {
537
+ const file = node_path_1.default.join(top, fname);
538
+ const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : '';
539
+ const result = writeIfChanged(file, upsertMarkerBlock(old, block));
540
+ const note = result === 'created'
541
+ ? 'created — Convene block added'
542
+ : result === 'updated'
543
+ ? 'merged — your content preserved'
544
+ : 'unchanged';
545
+ log(`${result === 'unchanged' ? '·' : '✓'} ${fname} (${note})`);
546
+ }
547
+ }
548
+ /**
549
+ * Stage + commit ONLY the convene files (CONVENE_PATHS) as one isolated commit —
550
+ * never `git add -A`, so convene changes are never bundled into unrelated work.
551
+ * Shared by `convene init --commit` and `convene init --refresh-docs --commit`.
552
+ */
553
+ function commitConveneFiles(top, message, label) {
554
+ const paths = exports.CONVENE_PATHS.filter((p) => node_fs_1.default.existsSync(node_path_1.default.join(top, p)));
555
+ if (!paths.length)
556
+ return;
557
+ // Decide up front whether anything under the managed paths is actually pending
558
+ // (staged/unstaged/untracked). This makes `--commit` deterministic and TRANSPARENT:
559
+ // a clean re-render says "already up to date" instead of a vague no-op, and a tree
560
+ // dirtied by an EARLIER `--refresh-docs` (without `--commit`) still gets committed
561
+ // — the gotcha that previously left changes stranded.
562
+ const pending = (0, git_1.gitPendingPaths)(paths, top);
563
+ if (!pending.length) {
564
+ log(`· ${label}: managed files already committed — nothing to commit.`);
565
+ return;
566
+ }
567
+ if (!(0, git_1.gitAddPaths)(paths, top)) {
568
+ log('⚠ could not stage the convene files — commit them manually.');
569
+ return;
570
+ }
571
+ const res = (0, git_1.gitCommit)(message, paths, top);
572
+ if (res.ok) {
573
+ log(`✓ committed ${label} as one isolated commit${res.sha ? ` (${res.sha})` : ''} — ${pending.length} convene file(s) staged, nothing else.`);
574
+ }
575
+ else {
576
+ // pending changes existed but the commit did not land — never silent.
577
+ log(`⚠ ${pending.length} convene file(s) are staged but the commit did not land — commit them manually (\`git commit\`).`);
578
+ }
579
+ }
580
+ /**
581
+ * `convene init --refresh-docs` — re-render the managed DOC surfaces (CLAUDE.md +
582
+ * AGENTS.md coordination blocks, the cross-agent rule files, and the MCP client
583
+ * configs) at the CURRENT CLI template, for a repo that is ALREADY on Convene.
584
+ *
585
+ * This is the propagation path init/setup lacked: `convene setup` on an already-
586
+ * onboarded repo only re-confirms identity, so improvements to the block templates
587
+ * (e.g. the connect-line in protocol.ts) never reached existing repos. Refresh fixes
588
+ * that with NO network/identity work — every template is a pure function of
589
+ * (slug, member, baseUrl), all read from local config — so it is byte-idempotent.
590
+ * Best-practice CONTENT is intentionally NOT touched here (that is `convene update`'s
591
+ * job: versioned + consented), and CONVENE_PROTOCOL.md is left as-is (write-if-absent
592
+ * / hand-enrichable), same as init.
593
+ */
594
+ async function refreshDocs(opts) {
595
+ const top = (0, git_1.gitToplevel)();
596
+ if (!top)
597
+ (0, ctx_1.die)('not a git repository — run `convene init --refresh-docs` inside a repo');
598
+ const existing = (0, config_1.loadProjectConfig)(top);
599
+ if (!existing?.slug) {
600
+ (0, ctx_1.die)('this repo is not on Convene yet — run `convene setup` first; `--refresh-docs` only re-renders an already-onboarded repo.');
601
+ }
602
+ const cfg = (0, config_1.resolveConfig)();
603
+ const slug = existing.slug;
604
+ const member = cfg.member;
605
+ const baseUrl = cfg.baseUrl;
606
+ const skipAgentRules = opts.noAgentRules === true || opts.agentRules === false;
607
+ const skipMcp = opts.noMcp === true || opts.mcp === false;
608
+ log(`Refreshing Convene managed blocks for "${slug}" at the current template (no identity/network changes)…`);
609
+ writeCoordinationBlocks(top, slug, member, baseUrl);
610
+ if (!skipAgentRules)
611
+ writeAgentRules(top, slug, member, baseUrl);
612
+ if (!skipMcp)
613
+ writeMcpConfigs(top, baseUrl);
614
+ if (opts.commit)
615
+ commitConveneFiles(top, 'Refresh Convene managed blocks to current template', 'the refresh');
616
+ log('');
617
+ log('Done — managed blocks re-rendered. Your content outside the convene markers is untouched.');
618
+ log('(Best-practice content is managed separately — run `convene update` to take catalog changes.)');
619
+ }
523
620
  async function init(opts) {
524
621
  const top = (0, git_1.gitToplevel)();
525
622
  if (!top)
526
623
  (0, ctx_1.die)('not a git repository — run `convene init` inside a repo');
624
+ // `--refresh-docs` is a re-render of an already-onboarded repo's managed blocks —
625
+ // not onboarding. It needs no consent gate / identity / network, so short-circuit
626
+ // here, before all of that.
627
+ if (opts.refreshDocs)
628
+ return refreshDocs(opts);
527
629
  // Consent gate: onboarding writes a footprint and registers per-prompt hooks — it
528
630
  // must be a DELIBERATE choice, never an accidental side-effect. A human at a
529
631
  // terminal (TTY) confirms simply by running it; an agent / CI (no TTY) must pass
@@ -658,21 +760,7 @@ async function init(opts) {
658
760
  // gates deploys), AGENTS.md uses conveneAgentsBlock (adds the explicit
659
761
  // `convene deploy` line for tools with no in-time gate). Each is PURE, so a
660
762
  // re-run is byte-identical per file (P0-IDEMPOTENT).
661
- const fileBlocks = [
662
- ['CLAUDE.md', (0, protocol_1.conveneBlock)(slug, member, baseUrl)],
663
- ['AGENTS.md', (0, protocol_1.conveneAgentsBlock)(slug, member, baseUrl)],
664
- ];
665
- for (const [fname, block] of fileBlocks) {
666
- const file = node_path_1.default.join(top, fname);
667
- const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : '';
668
- const result = writeIfChanged(file, upsertMarkerBlock(old, block));
669
- const note = result === 'created'
670
- ? 'created — Convene block added'
671
- : result === 'updated'
672
- ? 'merged — your content preserved'
673
- : 'unchanged';
674
- log(`${result === 'unchanged' ? '·' : '✓'} ${fname} (${note})`);
675
- }
763
+ writeCoordinationBlocks(top, slug, member, baseUrl);
676
764
  // 6. portable protocol doc — write only if ABSENT (mirrors the memory-seed
677
765
  // pattern). The doc is hand-enrichable; unconditionally overwriting it with
678
766
  // the generated stub would clobber any teammate's expanded protocol spec.
@@ -750,19 +838,8 @@ async function init(opts) {
750
838
  // 8a. optional isolated commit — stage ONLY the convene files (never `git add -A`),
751
839
  // so onboarding can never be bundled into unrelated work (the VAcontractorCo
752
840
  // entangled-commit failure). Off by default; `--commit` opts in.
753
- if (opts.commit) {
754
- const paths = exports.CONVENE_PATHS.filter((p) => node_fs_1.default.existsSync(node_path_1.default.join(top, p)));
755
- if (paths.length && (0, git_1.gitAddPaths)(paths, top)) {
756
- const res = (0, git_1.gitCommit)('Onboard onto Convene coordination bus', paths, top);
757
- if (res.ok)
758
- log(`✓ committed onboarding as one isolated commit${res.sha ? ` (${res.sha})` : ''} — only the convene files were staged.`);
759
- else
760
- log('· nothing committed (no staged changes).');
761
- }
762
- else if (paths.length) {
763
- log('⚠ could not stage the convene files — commit them manually.');
764
- }
765
- }
841
+ if (opts.commit)
842
+ commitConveneFiles(top, 'Onboard onto Convene coordination bus', 'onboarding');
766
843
  // 9. teammate one-liner
767
844
  log('');
768
845
  log(`Done. Project "${slug}" — dashboard: ${baseUrl}/p/${slug}`);
package/dist/git.js CHANGED
@@ -26,6 +26,7 @@ exports.gitConfigUnsetLocal = gitConfigUnsetLocal;
26
26
  exports.pathIsTracked = pathIsTracked;
27
27
  exports.hasStagedChanges = hasStagedChanges;
28
28
  exports.gitAddPaths = gitAddPaths;
29
+ exports.gitPendingPaths = gitPendingPaths;
29
30
  exports.gitCommit = gitCommit;
30
31
  exports.gitPathIsIgnored = gitPathIsIgnored;
31
32
  /** Cross-platform git helpers. No shell strings — spawn git directly (P0-XPLAT). */
@@ -305,6 +306,30 @@ function gitAddPaths(paths, cwd = process.cwd()) {
305
306
  return false;
306
307
  }
307
308
  }
309
+ /**
310
+ * The subset of `paths` that have PENDING changes (staged, unstaged, or untracked),
311
+ * via `git status --porcelain -- <paths>`. Lets a caller decide whether there is
312
+ * anything to commit BEFORE running `git commit` — so an isolated-commit helper can
313
+ * say "already up to date" plainly instead of relying on a non-zero `git commit`
314
+ * exit. Returns [] on any error / clean tree (never throws).
315
+ */
316
+ function gitPendingPaths(paths, cwd = process.cwd()) {
317
+ if (paths.length === 0)
318
+ return [];
319
+ try {
320
+ const r = (0, node_child_process_1.spawnSync)('git', ['status', '--porcelain', '--', ...paths], { cwd, encoding: 'utf8', timeout: 5000 });
321
+ if (r.status !== 0 || !r.stdout)
322
+ return [];
323
+ // Porcelain v1 line: "XY <path>"; we only need the count/names, not the status.
324
+ return r.stdout
325
+ .split('\n')
326
+ .map((l) => l.slice(3).trim())
327
+ .filter(Boolean);
328
+ }
329
+ catch {
330
+ return [];
331
+ }
332
+ }
308
333
  /**
309
334
  * Commit ONLY the given pathspecs (`git commit -m <msg> -- <paths>`), leaving any
310
335
  * other staged changes untouched — the isolated-commit guarantee. Returns the short
package/dist/index.js CHANGED
@@ -263,6 +263,7 @@ program
263
263
  .option('--yes', 'confirm onboarding non-interactively (required for agents/CI)')
264
264
  .option('--commit', 'commit ONLY the convene files as one isolated commit (never `git add -A`)')
265
265
  .option('--offline', 'write local files only (no API calls)')
266
+ .option('--refresh-docs', 're-render the managed CLAUDE.md/AGENTS.md blocks + agent rules + MCP configs at the current template (already-onboarded repos; no identity/network changes)')
266
267
  .option('--tier <names>', 'best practices: comma-separated tiers to adopt (essentials,recommended,advanced)')
267
268
  .option('--practice <id[=level]>', 'best practice to adopt (id or id=level; repeatable)', (v, acc) => (acc.push(v), acc), [])
268
269
  .option('--all-practices', 'adopt every catalog best practice at its default level')
package/dist/protocol.js CHANGED
@@ -31,7 +31,7 @@ function block(flavor, slug, member, baseUrl) {
31
31
  `This repo is on **Convene** — a tool-agnostic AI development coordination bus — as project \`${slug}\`.`,
32
32
  `Dashboard: ${baseUrl}/p/${slug}`,
33
33
  '',
34
- `> **Not connected on this machine?** Paste this one line into your Claude Code **or** Codex session: *"Connect me to Convene for this repo: run \`npm i -g convene-cli@latest\`, then \`convene setup\` and follow the prompts, then \`convene doctor\`."* or run those yourself, or fetch ${baseUrl}/start and follow it. One time per machine; both tools are then wired automatically.`,
34
+ `> **Asked to get set up / "up and running" with Convene on this machine?** Run these three: \`npm i -g convene-cli@latest\` \`convene setup\` (follow the prompts) \`convene doctor\`. One time per machine; wires Claude Code **and** Codex. Already connected? \`convene doctor\` confirms it. Detail: ${baseUrl}/start.`,
35
35
  '',
36
36
  'Each turn you get a `<convene-channel>` block — a health line, open items addressed to you, and',
37
37
  'recent activity. (Claude Code injects it via the `convene fetch` UserPromptSubmit hook; with other',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convene-cli",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Convene CLI — AI development coordination bus client + UserPromptSubmit hook. Install: npm i -g convene-cli; then `convene setup`.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://dev.convene.live",