convene-cli 1.13.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -207,9 +207,11 @@ class ConveneApi {
207
207
  }
208
208
  /**
209
209
  * GET /catalog — the canonical best-practices catalog (PUBLIC, no tenant data).
210
- * The CLI prefers this live read but is fully fail-soft: on any non-ok / network
211
- * error the caller falls back to the bundled offline mirror. Bounded by a short
212
- * timeout never the 10s default.
210
+ * The server returns a release ENVELOPE `{ version, contentHash, catalog }`; the
211
+ * actual Catalog lives under `.catalog` (loadCatalog unwraps it). The CLI prefers
212
+ * this live read but is fully fail-soft: on any non-ok / network error the caller
213
+ * falls back to the bundled offline mirror. Bounded by a short timeout — never
214
+ * the 10s default.
213
215
  */
214
216
  getCatalog(timeoutMs) {
215
217
  return this.request('GET', '/catalog', { timeoutMs });
@@ -6,16 +6,26 @@ const catalog_generated_1 = require("./catalog.generated");
6
6
  Object.defineProperty(exports, "CATALOG", { enumerable: true, get: function () { return catalog_generated_1.CATALOG; } });
7
7
  Object.defineProperty(exports, "CATALOG_VERSION", { enumerable: true, get: function () { return catalog_generated_1.CATALOG_VERSION; } });
8
8
  /**
9
- * Load the catalog, preferring the live server copy when `api` is given. Any
10
- * failure (no client, non-ok status, network error, empty body) silently falls
11
- * back to the bundled mirror this never throws.
9
+ * Load the catalog, preferring the live server copy when `api` is given. Unwraps
10
+ * the server's release envelope ({ version, contentHash, catalog }) and tolerates
11
+ * a bare Catalog for back-compat. Any failure (no client, non-ok status, network
12
+ * error, empty/malformed body) silently falls back to the bundled mirror — this
13
+ * never throws.
12
14
  */
13
15
  async function loadCatalog(api, timeoutMs = 5_000) {
14
16
  if (api) {
15
17
  try {
16
18
  const res = await api.getCatalog(timeoutMs);
17
- if (res.ok && res.json && Array.isArray(res.json.practices) && res.json.version) {
18
- return { catalog: res.json, source: 'live' };
19
+ // The server wraps the catalog in a release envelope
20
+ // ({ version, contentHash, catalog }); the Catalog (with `practices`) lives
21
+ // one level down under `.catalog`. Unwrap that, but also tolerate a bare
22
+ // Catalog for back-compat with a differently-shaped/older server. VALIDATE
23
+ // the UNWRAPPED object — the envelope's top-level `version` matches a bare
24
+ // Catalog's, so guarding on the inner `practices` is what tells them apart.
25
+ const body = res.json;
26
+ const cat = body && 'catalog' in body ? body.catalog : body;
27
+ if (res.ok && cat && Array.isArray(cat.practices) && typeof cat.version === 'string') {
28
+ return { catalog: cat, source: 'live' };
19
29
  }
20
30
  }
21
31
  catch {
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.semverLt = semverLt;
4
4
  exports.bumpClass = bumpClass;
5
5
  exports.compareToCatalog = compareToCatalog;
6
+ exports.catalogBehindLine = catalogBehindLine;
7
+ exports.bestPracticesFreshnessLine = bestPracticesFreshnessLine;
6
8
  /**
7
9
  * Strict SemVer less-than over the dotted numeric core (pre-release/build
8
10
  * metadata ignored — the catalog uses plain X.Y.Z). Missing components read as
@@ -69,3 +71,31 @@ function compareToCatalog(manifest, catalog) {
69
71
  unknownIds,
70
72
  };
71
73
  }
74
+ /**
75
+ * The canonical "your repo trails the catalog" nudge, shared by `convene doctor`
76
+ * and the `convene fetch` channel nudge so the SAME sentence ALWAYS means the
77
+ * same thing: `serverVersion` is the SERVER-published catalog version
78
+ * (authoritative), `repoVersion` is what this repo adopted. Never call this with
79
+ * a bundled-mirror version — a stale binary's bundled version is not "available"
80
+ * on the server (see `bestPracticesFreshnessLine`'s offline branch for that).
81
+ */
82
+ function catalogBehindLine(serverVersion, repoVersion) {
83
+ return `server catalog v${serverVersion} available (repo adopted v${repoVersion}) — run \`convene update\``;
84
+ }
85
+ /**
86
+ * The `convene doctor` best-practices freshness line, given a catalog comparison
87
+ * and WHERE the catalog came from. Server-truthful by construction: it only makes
88
+ * an "available"/"up to date" claim when the comparison was against the LIVE
89
+ * server catalog. When the server was unreachable the comparison is against the
90
+ * bundled mirror baked into this binary — which may trail OR lead the server — so
91
+ * it refuses to claim either and just states both versions + that the server is
92
+ * unknown. Pure (no stdout/network) so it unit-tests directly.
93
+ */
94
+ function bestPracticesFreshnessLine(cmp, source) {
95
+ if (source === 'live') {
96
+ return cmp.behind
97
+ ? catalogBehindLine(cmp.catalogVersion, cmp.repoVersion)
98
+ : `best practices up to date with server catalog v${cmp.repoVersion}`;
99
+ }
100
+ return `bundled catalog v${cmp.catalogVersion} (offline — server version unknown) vs repo adopted v${cmp.repoVersion}`;
101
+ }
@@ -636,22 +636,35 @@ async function doctor(opts) {
636
636
  for (const c of checks) {
637
637
  process.stdout.write(`${c.ok ? '✓' : '✗'} ${c.name.padEnd(8)} ${c.detail}\n`);
638
638
  }
639
- // Best practices (local-only, advisory): adopted-practice inventory + whether
640
- // the repo trails the catalog. Fail-soft — never throws and never alters the
641
- // doctor exit code.
642
- reportBestPractices(top, proj?.slug ?? null);
639
+ // Best practices (advisory): adopted-practice inventory + whether the repo
640
+ // trails the catalog. Fail-soft — never throws and never alters the doctor exit
641
+ // code. Resolve the catalog SERVER-TRUTHFULLY first (prefer the live published
642
+ // catalog, fall back to the bundled mirror) — the same `loadCatalog` resolver
643
+ // `convene update` uses — so the "available" line reflects what the SERVER
644
+ // publishes, not the version baked into this binary. doctor is already async +
645
+ // already does network I/O (api.me above), so a live fetch here costs nothing
646
+ // extra; loadCatalog is fail-soft and tags its source, so an unreachable server
647
+ // simply yields the bundled mirror, labelled honestly as offline.
648
+ const { catalog: bpCatalog, source: bpSource } = await (0, catalog_1.loadCatalog)(cfg.apiKey ? new api_1.ConveneApi(cfg.baseUrl, cfg.apiKey) : null);
649
+ reportBestPractices(top, proj?.slug ?? null, bpCatalog, bpSource);
643
650
  if (!checks.every((c) => c.ok))
644
651
  process.exitCode = 1;
645
652
  }
646
653
  /**
647
- * Print doctor's "Best practices" section. Local-only this phase: reads the repo
648
- * manifest and diffs it against the bundled catalog. Purely informational it
649
- * never throws (a malformed manifest is swallowed) and never sets the exit code.
650
- * - manifest present catalog freshness line + each adopted practice + unknowns.
654
+ * Print doctor's "Best practices" section. Diffs the repo manifest against the
655
+ * RESOLVED catalog (`catalog`/`source` come from loadCataloglive-preferred,
656
+ * bundled fallback). Purely informational it never throws (a malformed manifest
657
+ * is swallowed) and never sets the exit code.
658
+ * - manifest present → server-truthful freshness line + each adopted practice + unknowns.
651
659
  * - on the bus but no manifest → a single nudge to adopt some.
652
660
  * - not on the bus → nothing.
661
+ * The freshness wording is server-truthful: an "available"/"up to date" claim is
662
+ * made only when `source === 'live'`; an unreachable server yields the honest
663
+ * "bundled catalog … (offline — server version unknown)" line (see
664
+ * `bestPracticesFreshnessLine`). The same `catalogBehindLine` phrasing is shared
665
+ * with the `convene fetch` nudge so the sentence never means two different things.
653
666
  */
654
- function reportBestPractices(top, slug) {
667
+ function reportBestPractices(top, slug, catalog, source) {
655
668
  try {
656
669
  const manifest = (0, config_1.loadManifest)(top);
657
670
  if (!manifest) {
@@ -660,10 +673,8 @@ function reportBestPractices(top, slug) {
660
673
  }
661
674
  return;
662
675
  }
663
- const cmp = (0, manifest_1.compareToCatalog)(manifest, catalog_1.CATALOG);
664
- process.stdout.write(cmp.behind
665
- ? `· catalog v${cmp.catalogVersion} available (repo on v${cmp.repoVersion}) — run \`convene update\`\n`
666
- : `· best practices up to date at v${cmp.repoVersion}\n`);
676
+ const cmp = (0, manifest_1.compareToCatalog)(manifest, catalog);
677
+ process.stdout.write(`· ${(0, manifest_1.bestPracticesFreshnessLine)(cmp, source)}\n`);
667
678
  for (const p of cmp.adopted) {
668
679
  const flag = p.outdated ? ` (outdated → v${p.catalogVersion})` : '';
669
680
  process.stdout.write(` ${p.id} @ ${p.manifestVersion} [${p.level}]${flag}\n`);
@@ -105,7 +105,10 @@ function catalogBehindNudge(top) {
105
105
  return null;
106
106
  if (!(0, manifest_1.semverLt)(manifest.catalogVersion, live))
107
107
  return null;
108
- return `convene: best-practices catalog v${live} available (repo on v${manifest.catalogVersion}) — run \`convene update\``;
108
+ // Shared phrasing with `convene doctor` `live` is the SERVER version (the
109
+ // cache is populated from api.getCatalog), so the "server catalog vX" wording
110
+ // is accurate here and means the SAME thing in both surfaces.
111
+ return `convene: ${(0, manifest_1.catalogBehindLine)(live, manifest.catalogVersion)}`;
109
112
  }
110
113
  catch {
111
114
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convene-cli",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
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://convene.live",