@ritualai/cli 0.7.10 → 0.7.11

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.
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLatestNpmVersion = getLatestNpmVersion;
4
+ exports.isNewerVersion = isNewerVersion;
5
+ const node_https_1 = require("node:https");
6
+ /**
7
+ * Fetch the latest published version of an npm package from the public
8
+ * registry. Used by `ritual doctor` to surface "you're on 0.7.10 but
9
+ * 0.7.11 is available."
10
+ *
11
+ * Pure HTTPS — no dependency on a registry client library. We hit the
12
+ * package metadata endpoint and read `dist-tags.latest`, which is what
13
+ * `npm view <pkg> version` returns.
14
+ *
15
+ * Hard timeout because `ritual doctor` runs synchronously in the user's
16
+ * terminal and a hung network call would hang the diagnostic. Caller
17
+ * should treat null as "couldn't determine" (e.g. offline, registry
18
+ * down, corporate proxy) rather than "package doesn't exist."
19
+ *
20
+ * Why we don't use `child_process.execSync('npm view ...')`:
21
+ * - Shells out to `npm`, which loads ~600ms of Node startup just for
22
+ * the registry round-trip.
23
+ * - Adds an implicit dependency on `npm` being on PATH (we don't
24
+ * enforce that — a user could be on pnpm or yarn).
25
+ * - Surfaces npm's stderr noise (deprecation warnings, etc.) to the
26
+ * diagnostic output.
27
+ */
28
+ async function getLatestNpmVersion(packageName, timeoutMs = 2000) {
29
+ return new Promise((resolve) => {
30
+ // Encode scoped package names (`@scope/name`) for the URL path.
31
+ // `encodeURIComponent` turns `@` into `%40` and `/` into `%2F`,
32
+ // which is the registry's expected form for scoped packages.
33
+ const encoded = encodeURIComponent(packageName);
34
+ const url = `https://registry.npmjs.org/${encoded}`;
35
+ const req = (0, node_https_1.request)(url, {
36
+ method: 'GET',
37
+ headers: {
38
+ // Slim metadata response — full doc is megabytes for
39
+ // packages with long publish histories. `abbreviated`
40
+ // returns only the fields we need (~5KB).
41
+ Accept: 'application/vnd.npm.install-v1+json',
42
+ 'User-Agent': '@ritualai/cli (doctor)',
43
+ },
44
+ }, (res) => {
45
+ if (res.statusCode !== 200) {
46
+ res.resume(); // drain and discard
47
+ resolve(null);
48
+ return;
49
+ }
50
+ const chunks = [];
51
+ res.on('data', (c) => chunks.push(c));
52
+ res.on('end', () => {
53
+ try {
54
+ const body = Buffer.concat(chunks).toString('utf-8');
55
+ const parsed = JSON.parse(body);
56
+ const latest = parsed['dist-tags']?.latest;
57
+ resolve(typeof latest === 'string' ? latest : null);
58
+ }
59
+ catch {
60
+ resolve(null);
61
+ }
62
+ });
63
+ res.on('error', () => resolve(null));
64
+ });
65
+ req.setTimeout(timeoutMs, () => {
66
+ req.destroy();
67
+ resolve(null);
68
+ });
69
+ req.on('error', () => resolve(null));
70
+ req.end();
71
+ });
72
+ }
73
+ /**
74
+ * Loose semver compare: return true iff `latest` is strictly newer than
75
+ * `installed`. Tolerates `0.0.0-unknown` (used when we can't read our
76
+ * own version) by treating it as "always outdated" — which surfaces a
77
+ * loud error in doctor rather than silently passing.
78
+ *
79
+ * We deliberately don't pull in `semver` as a dep here — the cost
80
+ * (parse error handling, prerelease semantics, range syntax) isn't
81
+ * justified for a one-call compare. Splitting on '.' and comparing as
82
+ * numeric tuples is sufficient for our 0.X.Y release line.
83
+ */
84
+ function isNewerVersion(installed, latest) {
85
+ if (installed === latest)
86
+ return false;
87
+ if (installed === '0.0.0-unknown')
88
+ return true;
89
+ const parse = (v) => v
90
+ .split('-')[0] // strip prerelease (e.g. -beta.1)
91
+ .split('.')
92
+ .map((s) => Number.parseInt(s, 10))
93
+ .map((n) => (Number.isFinite(n) ? n : 0));
94
+ const a = parse(installed);
95
+ const b = parse(latest);
96
+ const len = Math.max(a.length, b.length);
97
+ for (let i = 0; i < len; i++) {
98
+ const ai = a[i] ?? 0;
99
+ const bi = b[i] ?? 0;
100
+ if (bi > ai)
101
+ return true;
102
+ if (bi < ai)
103
+ return false;
104
+ }
105
+ return false;
106
+ }
107
+ //# sourceMappingURL=npm-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry.js","sourceRoot":"","sources":["../../src/lib/npm-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findLocalSkillBundles = findLocalSkillBundles;
4
+ exports.findStaleBundles = findStaleBundles;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const providers_1 = require("./agents/providers");
8
+ const skill_manifest_1 = require("./skill-manifest");
9
+ /**
10
+ * Walk every known provider's `projectSkillDir` under `projectDir` and
11
+ * collect each child directory as a candidate skill bundle. Reads each
12
+ * manifest and compares to `cliVersion`.
13
+ *
14
+ * Hidden directories (those starting with `.`) are skipped — they're
15
+ * either dotfiles or our own manifest companions, never skill dirs.
16
+ *
17
+ * `cliVersion` is passed in (rather than read from package-info here)
18
+ * so tests can inject a fixed value.
19
+ */
20
+ function findLocalSkillBundles(projectDir, cliVersion) {
21
+ const found = [];
22
+ for (const provider of providers_1.PROVIDERS) {
23
+ const skillRoot = (0, node_path_1.join)(projectDir, provider.projectSkillDir);
24
+ if (!(0, node_fs_1.existsSync)(skillRoot))
25
+ continue;
26
+ let entries;
27
+ try {
28
+ entries = (0, node_fs_1.readdirSync)(skillRoot, { withFileTypes: true })
29
+ .filter((d) => d.isDirectory() && !d.name.startsWith('.'))
30
+ .map((d) => d.name);
31
+ }
32
+ catch {
33
+ continue;
34
+ }
35
+ for (const skillName of entries) {
36
+ const path = (0, node_path_1.join)(skillRoot, skillName);
37
+ // Defensive: a non-directory slipping through (race, symlink
38
+ // loop) just gets skipped.
39
+ try {
40
+ if (!(0, node_fs_1.statSync)(path).isDirectory())
41
+ continue;
42
+ }
43
+ catch {
44
+ continue;
45
+ }
46
+ const manifest = (0, skill_manifest_1.readSkillManifest)(path);
47
+ const drift = (0, skill_manifest_1.compareToCliVersion)(manifest, cliVersion);
48
+ found.push({ provider, skillName, path, drift });
49
+ }
50
+ }
51
+ return found;
52
+ }
53
+ /**
54
+ * Subset of bundles whose drift status indicates a refresh would help.
55
+ * Both `drift` (manifest present, version mismatch) AND `unknown`
56
+ * (manifest missing — pre-0.7.11 bundle) are considered actionable:
57
+ * running `ritual init --skills-only` is the same fix for either case.
58
+ */
59
+ function findStaleBundles(bundles) {
60
+ return bundles.filter((b) => b.drift.kind === 'drift' || b.drift.kind === 'unknown');
61
+ }
62
+ //# sourceMappingURL=skill-bundles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-bundles.js","sourceRoot":"","sources":["../../src/lib/skill-bundles.ts"],"names":[],"mappings":";;AAqCA,sDAgCC;AAQD,4CAEC;AA/ED,qCAA4D;AAC5D,yCAAiC;AACjC,kDAAmE;AACnE,qDAA4F;AAuB5F;;;;;;;;;;GAUG;AACH,SAAgB,qBAAqB,CAAC,UAAkB,EAAE,UAAkB;IAC3E,MAAM,KAAK,GAAuB,EAAE,CAAC;IAErC,KAAK,MAAM,QAAQ,IAAI,qBAAS,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,IAAA,gBAAI,EAAC,UAAU,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAA,oBAAU,EAAC,SAAS,CAAC;YAAE,SAAS;QAErC,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACJ,OAAO,GAAG,IAAA,qBAAW,EAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;iBACvD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;iBACzD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACR,SAAS;QACV,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACxC,6DAA6D;YAC7D,2BAA2B;YAC3B,IAAI,CAAC;gBACJ,IAAI,CAAC,IAAA,kBAAQ,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE;oBAAE,SAAS;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACR,SAAS;YACV,CAAC;YACD,MAAM,QAAQ,GAAG,IAAA,kCAAiB,EAAC,IAAI,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,IAAA,oCAAmB,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACxD,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,OAA2B;IAC3D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MANIFEST_FILENAME = void 0;
4
+ exports.writeSkillManifest = writeSkillManifest;
5
+ exports.readSkillManifest = readSkillManifest;
6
+ exports.compareToCliVersion = compareToCliVersion;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ exports.MANIFEST_FILENAME = '.ritual-bundle.json';
10
+ /**
11
+ * Write a manifest into `dir`. Used by the build-skills script.
12
+ *
13
+ * Always overwrites. `dir` must exist — caller is responsible for
14
+ * mkdir.
15
+ */
16
+ function writeSkillManifest(dir, manifest) {
17
+ const path = (0, node_path_1.join)(dir, exports.MANIFEST_FILENAME);
18
+ (0, node_fs_1.writeFileSync)(path, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
19
+ }
20
+ /**
21
+ * Read a manifest from `dir`. Returns `null` if absent or unparseable.
22
+ *
23
+ * Never throws — drift checks are best-effort and an unreadable
24
+ * manifest just means "we can't tell," not "crash the doctor."
25
+ */
26
+ function readSkillManifest(dir) {
27
+ const path = (0, node_path_1.join)(dir, exports.MANIFEST_FILENAME);
28
+ if (!(0, node_fs_1.existsSync)(path))
29
+ return null;
30
+ try {
31
+ const raw = (0, node_fs_1.readFileSync)(path, 'utf-8');
32
+ const parsed = JSON.parse(raw);
33
+ if (typeof parsed.cliVersion !== 'string' || parsed.cliVersion.length === 0) {
34
+ return null;
35
+ }
36
+ if (typeof parsed.builtAt !== 'string' || parsed.builtAt.length === 0) {
37
+ return null;
38
+ }
39
+ return { cliVersion: parsed.cliVersion, builtAt: parsed.builtAt };
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ function compareToCliVersion(manifest, cliVersion) {
46
+ if (!manifest)
47
+ return { kind: 'unknown' };
48
+ if (manifest.cliVersion === cliVersion) {
49
+ return { kind: 'in-sync', bundleVersion: manifest.cliVersion };
50
+ }
51
+ return { kind: 'drift', bundleVersion: manifest.cliVersion, cliVersion };
52
+ }
53
+ //# sourceMappingURL=skill-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-manifest.js","sourceRoot":"","sources":["../../src/lib/skill-manifest.ts"],"names":[],"mappings":";;;AA0CA,gDAGC;AAQD,8CAgBC;AAkBD,kDASC;AAhGD,qCAAkE;AAClE,yCAAiC;AAiCpB,QAAA,iBAAiB,GAAG,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,GAAW,EAAE,QAA6B;IAC5E,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,GAAG,EAAE,yBAAiB,CAAC,CAAC;IAC1C,IAAA,uBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACxE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAW;IAC5C,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,GAAG,EAAE,yBAAiB,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiC,CAAC;QAC/D,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7E,OAAO,IAAI,CAAC;QACb,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvE,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAkBD,SAAgB,mBAAmB,CAClC,QAAoC,EACpC,UAAkB;IAElB,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC1C,IAAI,QAAQ,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC;AAC1E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ritualai/cli",
3
- "version": "0.7.10",
3
+ "version": "0.7.11",
4
4
  "description": "Ritual CLI — scaffold AI coding agent skills + register MCP servers. Connects Claude Code, Cursor, Windsurf, Kiro, Gemini CLI, VS Code/Copilot, and Codex to Ritual Cloud.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,4 @@
1
+ {
2
+ "cliVersion": "0.7.11",
3
+ "builtAt": "2026-05-14T12:14:29.050Z"
4
+ }
@@ -397,7 +397,7 @@ Steps:
397
397
  > **{candidate.name}** *(LLM confidence: {Math.round(candidate.llmConfidence * 100)}%)*
398
398
  > - *"{candidate.problemStatement first 120 chars, no ellipsis padding}..."*
399
399
  > - Why I think it overlaps: {candidate.llmRationale}
400
- > - URL: `https://dev.ritualapp.cloud/e/{candidate.explorationId}`
400
+ > - URL: `https://app.ritualapp.cloud/e/{candidate.explorationId}`
401
401
  > {endfor}
402
402
  >
403
403
  > **Choose:**
@@ -983,7 +983,7 @@ Optimize for:
983
983
 
984
984
  References:
985
985
  - {RB/decision/exploration label} — {one-line meaning}
986
- Source: {exploration title or id}{ optional ': https://dev.ritualapp.cloud/e/{exploration_id}' if available}
986
+ Source: {exploration title or id}{ optional ': https://app.ritualapp.cloud/e/{exploration_id}' if available}
987
987
  - {reference}
988
988
 
989
989
  Reply `use` to use this frame and review discovery questions.
@@ -1040,7 +1040,7 @@ Store `exploration_id`. Move the progress header from Scope to Discovery:
1040
1040
  Ritual build
1041
1041
  ✓ Context ✓ Scope ● Discovery ○ Recommendations ○ Build brief ○ Implementation (Your agent)
1042
1042
 
1043
- Exploration created. Track progress at https://dev.ritualapp.cloud/e/{exploration_id}
1043
+ Exploration created. Track progress at https://app.ritualapp.cloud/e/{exploration_id}
1044
1044
 
1045
1045
  Next: generate discovery questions to resolve the implementation trade-offs.
1046
1046
  ```
@@ -1504,10 +1504,67 @@ If you don't already know the user's role on this workspace, prefer the workspac
1504
1504
 
1505
1505
  ##### 9.1 — Landing screen: grouped category summary + compact scope
1506
1506
 
1507
- The recommendations review is the most-read screen in the whole build flow. Two design rules:
1507
+ The recommendations review is the most-read screen in the whole build flow.
1508
1508
 
1509
- 1. **Group by `metadata.category.name`.** NEVER show a flat numbered 1..N list. The category is the organizing unit; recommendations are stable `R1..RN` IDs assigned in order of appearance ACROSS categories (top-to-bottom, NOT per-category — so a user can say `detail R7` unambiguously without naming the category).
1510
- 2. **Compact scope reference at top.** Show a single-line compressed version of the problem statement so the user can read recommendations against what they were optimized for. Support `show scope` to expand to full.
1509
+ ```text
1510
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1511
+ STEP 9.1 RENDERING CONTRACT — non-negotiable
1512
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1513
+
1514
+ Landing view is for SELECTION, not reading. Full prose belongs in 9.3.
1515
+
1516
+ ✓ DO:
1517
+ - Number categories `1.`, `2.`, `3.` … `K.` in page order
1518
+ - Assign recommendations stable `R1`, `R2`, … `RN` IDs GLOBALLY across
1519
+ all categories (NOT restart per-category — so `detail R7` is
1520
+ unambiguous without naming the category)
1521
+ - Show recommendation TITLES ONLY at the landing
1522
+ - Indent recs 3 spaces under their category
1523
+ - One blank line between categories
1524
+ - Include compact scope line above the list
1525
+ - End with the action block (`accept recommended` etc.)
1526
+
1527
+ ✗ DO NOT:
1528
+ - Do NOT render recs as a flat `1..N` list with no category structure
1529
+ - Do NOT use raw numeric rec IDs like `1.`, `2.`, `3.` — use `R1`, `R2`, `R3`
1530
+ - Do NOT show recommendation `content` / summary text at the landing
1531
+ - Do NOT show acceptance criteria, rationale, tactics, or references at
1532
+ the landing — those are 9.3 detail-card content
1533
+ - Do NOT omit category numbering (the prefix is what separates this
1534
+ from a wall-of-text grouped list)
1535
+ - Do NOT invent a "raw / deduped" framing line — the API does not return
1536
+ pre-dedup counts. Use `{N} recommendations across {K} categories.`
1537
+
1538
+ OBSERVED FAILURE — never render this:
1539
+
1540
+ Recommendations (13)
1541
+
1542
+ 1. Backfill legacy data to fail-closed PRIVATE visibility
1543
+ Add a constrained `visibility` field with DB default PRIVATE...
1544
+
1545
+ 2. Codify actor-state rights around owner-only mutation
1546
+ Define a single permission matrix across PRIVATE/SHARED/PUBLIC...
1547
+
1548
+ 3. Centralize object permissions across storefront and dashboard
1549
+ Reusable `can_view_wishlist` / `can_edit_wishlist` methods...
1550
+
1551
+ Why wrong:
1552
+ - Looks grouped only implicitly; categories not visible as headers
1553
+ - Uses `1..N` numeric IDs (should be `R1..RN`)
1554
+ - Dumps `content` summaries — defeats the purpose of a landing view
1555
+ - `detail R7` no longer maps to a stable ID
1556
+
1557
+ PREFLIGHT — before printing 9.1 output, self-check:
1558
+ □ Did I group by `metadata.category.name`?
1559
+ □ Did I prefix each category with `1.`, `2.`, … `K.`?
1560
+ □ Did I assign global `R1..RN` IDs across categories?
1561
+ □ Did I show titles only (no summaries / no rationale)?
1562
+ □ Is there a compact scope line above the list?
1563
+ □ Does my action block use `R{N}` references that match the IDs above?
1564
+
1565
+ If any answer is no, FIX BEFORE PRINTING.
1566
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1567
+ ```
1511
1568
 
1512
1569
  Full rail at the landing (Recommendations stage opens):
1513
1570
 
@@ -1542,7 +1599,11 @@ Pulse: Reasoning Readiness ~88% · Context Debt 12% · +26% (implementation-read
1542
1599
  Recommended: reply `accept recommended` to approve these {N} and generate
1543
1600
  the build brief.
1544
1601
 
1545
- Or reply `detail R7`, `change R3: <edit>`, `drop R8`, `add <topic>`, or `hold`.
1602
+ Or reply:
1603
+ - `detail R7` to inspect one recommendation
1604
+ - `drop R8` to remove one before accepting
1605
+ - `change R3: <edit>` to revise one
1606
+ - `hold` to stop here
1546
1607
  ```
1547
1608
 
1548
1609
  Notes:
@@ -1551,7 +1612,9 @@ Notes:
1551
1612
  - **One blank line between categories**; titles indent at 3 spaces (so the eye lands on the category name first, then the R-IDs scan down).
1552
1613
  - **Pulse uses full labels** per `references/cli-output-contract.md` § Pulse tier labels.
1553
1614
  - **`accept recommended`** is the visible CTA — NOT `accept all`. "Recommended" frames the action as "the curated set you see on screen"; "all" sounds like a bulk operation over deduped/hidden/raw recs.
1554
- - **`add <topic>`** lets the user request an additional recommendation on the fly (e.g. `add telemetry` agent calls `regenerate_recommendation` with a new rec hint, or asks the user to clarify what's missing). If the backing API doesn't yet support targeted single-rec addition, prompt the user with: "I can add a rec for `{topic}` by regenerating the full set with that pinned — that takes ~30s. Or hold the current set and add manually via the web UI."
1615
+ - **Surface 4 actions at first render not more.** The four above (`detail R{N}`, `drop R{N}`, `change R{N}: <edit>`, `hold`) cover the decisive cases. Less-frequent actions stay discoverable but off-screen at the landing:
1616
+ - **`add <topic>`** lets the user request an additional recommendation on the fly (e.g. `add telemetry` → agent calls `regenerate_recommendation` with a new rec hint, or asks the user to clarify what's missing). Surface it in `help` or on the 9.3 detail card, NOT in the landing action line — too many actions at once weakens the selection screen. If the backing API doesn't yet support targeted single-rec addition, prompt the user with: "I can add a rec for `{topic}` by regenerating the full set with that pinned — that takes ~30s. Or hold the current set and add manually via the web UI."
1617
+ - **`show scope`** to expand the full problem statement (covered in 9.2).
1555
1618
 
1556
1619
  ##### 9.2 — `show scope` handling
1557
1620
 
@@ -1646,7 +1709,7 @@ Ritual build
1646
1709
 
1647
1710
  Accepted {N} recommendations.
1648
1711
 
1649
- View: https://dev.ritualapp.cloud/e/{exploration_id}
1712
+ View: https://app.ritualapp.cloud/e/{exploration_id}
1650
1713
 
1651
1714
  Next: generating the build brief…
1652
1715
  ```
@@ -1783,7 +1846,7 @@ When the brief content is in hand (from generate OR polling), **don't dump 300 l
1783
1846
  ```markdown
1784
1847
  <!--
1785
1848
  Generated by Ritual
1786
- Exploration: https://dev.ritualapp.cloud/e/{exploration_id}
1849
+ Exploration: https://app.ritualapp.cloud/e/{exploration_id}
1787
1850
  Build brief id: {brief_id}
1788
1851
  Do not remove this header; it preserves implementation lineage.
1789
1852
  -->
@@ -1927,7 +1990,7 @@ feat(<area>): <one-line headline>
1927
1990
  <short body — what changed, why, key trade-off>
1928
1991
 
1929
1992
  Ritual-Exploration: <exploration_id>
1930
- Ritual-Exploration-Url: https://dev.ritualapp.cloud/e/<exploration_id>
1993
+ Ritual-Exploration-Url: https://app.ritualapp.cloud/e/<exploration_id>
1931
1994
  Ritual-RBs-Satisfied: RB-1, RB-2, RB-7
1932
1995
  ```
1933
1996
 
@@ -1980,7 +2043,7 @@ If the user says "y" / "push" / "open PR":
1980
2043
 
1981
2044
  ## Exploration
1982
2045
 
1983
- - Exploration: [<exploration name>](https://dev.ritualapp.cloud/e/<exploration_id>)
2046
+ - Exploration: [<exploration name>](https://app.ritualapp.cloud/e/<exploration_id>)
1984
2047
  - Build brief: see `BUILD-BRIEF.md` (committed in this PR for reviewer reference)
1985
2048
  - Deferrals intentionally punted: <count, with one-line each>
1986
2049
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "cliVersion": "0.7.11",
3
+ "builtAt": "2026-05-14T12:14:29.050Z"
4
+ }
@@ -397,7 +397,7 @@ Steps:
397
397
  > **{candidate.name}** *(LLM confidence: {Math.round(candidate.llmConfidence * 100)}%)*
398
398
  > - *"{candidate.problemStatement first 120 chars, no ellipsis padding}..."*
399
399
  > - Why I think it overlaps: {candidate.llmRationale}
400
- > - URL: `https://dev.ritualapp.cloud/e/{candidate.explorationId}`
400
+ > - URL: `https://app.ritualapp.cloud/e/{candidate.explorationId}`
401
401
  > {endfor}
402
402
  >
403
403
  > **Choose:**
@@ -983,7 +983,7 @@ Optimize for:
983
983
 
984
984
  References:
985
985
  - {RB/decision/exploration label} — {one-line meaning}
986
- Source: {exploration title or id}{ optional ': https://dev.ritualapp.cloud/e/{exploration_id}' if available}
986
+ Source: {exploration title or id}{ optional ': https://app.ritualapp.cloud/e/{exploration_id}' if available}
987
987
  - {reference}
988
988
 
989
989
  Reply `use` to use this frame and review discovery questions.
@@ -1040,7 +1040,7 @@ Store `exploration_id`. Move the progress header from Scope to Discovery:
1040
1040
  Ritual build
1041
1041
  ✓ Context ✓ Scope ● Discovery ○ Recommendations ○ Build brief ○ Implementation (Your agent)
1042
1042
 
1043
- Exploration created. Track progress at https://dev.ritualapp.cloud/e/{exploration_id}
1043
+ Exploration created. Track progress at https://app.ritualapp.cloud/e/{exploration_id}
1044
1044
 
1045
1045
  Next: generate discovery questions to resolve the implementation trade-offs.
1046
1046
  ```
@@ -1504,10 +1504,67 @@ If you don't already know the user's role on this workspace, prefer the workspac
1504
1504
 
1505
1505
  ##### 9.1 — Landing screen: grouped category summary + compact scope
1506
1506
 
1507
- The recommendations review is the most-read screen in the whole build flow. Two design rules:
1507
+ The recommendations review is the most-read screen in the whole build flow.
1508
1508
 
1509
- 1. **Group by `metadata.category.name`.** NEVER show a flat numbered 1..N list. The category is the organizing unit; recommendations are stable `R1..RN` IDs assigned in order of appearance ACROSS categories (top-to-bottom, NOT per-category — so a user can say `detail R7` unambiguously without naming the category).
1510
- 2. **Compact scope reference at top.** Show a single-line compressed version of the problem statement so the user can read recommendations against what they were optimized for. Support `show scope` to expand to full.
1509
+ ```text
1510
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1511
+ STEP 9.1 RENDERING CONTRACT — non-negotiable
1512
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1513
+
1514
+ Landing view is for SELECTION, not reading. Full prose belongs in 9.3.
1515
+
1516
+ ✓ DO:
1517
+ - Number categories `1.`, `2.`, `3.` … `K.` in page order
1518
+ - Assign recommendations stable `R1`, `R2`, … `RN` IDs GLOBALLY across
1519
+ all categories (NOT restart per-category — so `detail R7` is
1520
+ unambiguous without naming the category)
1521
+ - Show recommendation TITLES ONLY at the landing
1522
+ - Indent recs 3 spaces under their category
1523
+ - One blank line between categories
1524
+ - Include compact scope line above the list
1525
+ - End with the action block (`accept recommended` etc.)
1526
+
1527
+ ✗ DO NOT:
1528
+ - Do NOT render recs as a flat `1..N` list with no category structure
1529
+ - Do NOT use raw numeric rec IDs like `1.`, `2.`, `3.` — use `R1`, `R2`, `R3`
1530
+ - Do NOT show recommendation `content` / summary text at the landing
1531
+ - Do NOT show acceptance criteria, rationale, tactics, or references at
1532
+ the landing — those are 9.3 detail-card content
1533
+ - Do NOT omit category numbering (the prefix is what separates this
1534
+ from a wall-of-text grouped list)
1535
+ - Do NOT invent a "raw / deduped" framing line — the API does not return
1536
+ pre-dedup counts. Use `{N} recommendations across {K} categories.`
1537
+
1538
+ OBSERVED FAILURE — never render this:
1539
+
1540
+ Recommendations (13)
1541
+
1542
+ 1. Backfill legacy data to fail-closed PRIVATE visibility
1543
+ Add a constrained `visibility` field with DB default PRIVATE...
1544
+
1545
+ 2. Codify actor-state rights around owner-only mutation
1546
+ Define a single permission matrix across PRIVATE/SHARED/PUBLIC...
1547
+
1548
+ 3. Centralize object permissions across storefront and dashboard
1549
+ Reusable `can_view_wishlist` / `can_edit_wishlist` methods...
1550
+
1551
+ Why wrong:
1552
+ - Looks grouped only implicitly; categories not visible as headers
1553
+ - Uses `1..N` numeric IDs (should be `R1..RN`)
1554
+ - Dumps `content` summaries — defeats the purpose of a landing view
1555
+ - `detail R7` no longer maps to a stable ID
1556
+
1557
+ PREFLIGHT — before printing 9.1 output, self-check:
1558
+ □ Did I group by `metadata.category.name`?
1559
+ □ Did I prefix each category with `1.`, `2.`, … `K.`?
1560
+ □ Did I assign global `R1..RN` IDs across categories?
1561
+ □ Did I show titles only (no summaries / no rationale)?
1562
+ □ Is there a compact scope line above the list?
1563
+ □ Does my action block use `R{N}` references that match the IDs above?
1564
+
1565
+ If any answer is no, FIX BEFORE PRINTING.
1566
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1567
+ ```
1511
1568
 
1512
1569
  Full rail at the landing (Recommendations stage opens):
1513
1570
 
@@ -1542,7 +1599,11 @@ Pulse: Reasoning Readiness ~88% · Context Debt 12% · +26% (implementation-read
1542
1599
  Recommended: reply `accept recommended` to approve these {N} and generate
1543
1600
  the build brief.
1544
1601
 
1545
- Or reply `detail R7`, `change R3: <edit>`, `drop R8`, `add <topic>`, or `hold`.
1602
+ Or reply:
1603
+ - `detail R7` to inspect one recommendation
1604
+ - `drop R8` to remove one before accepting
1605
+ - `change R3: <edit>` to revise one
1606
+ - `hold` to stop here
1546
1607
  ```
1547
1608
 
1548
1609
  Notes:
@@ -1551,7 +1612,9 @@ Notes:
1551
1612
  - **One blank line between categories**; titles indent at 3 spaces (so the eye lands on the category name first, then the R-IDs scan down).
1552
1613
  - **Pulse uses full labels** per `references/cli-output-contract.md` § Pulse tier labels.
1553
1614
  - **`accept recommended`** is the visible CTA — NOT `accept all`. "Recommended" frames the action as "the curated set you see on screen"; "all" sounds like a bulk operation over deduped/hidden/raw recs.
1554
- - **`add <topic>`** lets the user request an additional recommendation on the fly (e.g. `add telemetry` agent calls `regenerate_recommendation` with a new rec hint, or asks the user to clarify what's missing). If the backing API doesn't yet support targeted single-rec addition, prompt the user with: "I can add a rec for `{topic}` by regenerating the full set with that pinned — that takes ~30s. Or hold the current set and add manually via the web UI."
1615
+ - **Surface 4 actions at first render not more.** The four above (`detail R{N}`, `drop R{N}`, `change R{N}: <edit>`, `hold`) cover the decisive cases. Less-frequent actions stay discoverable but off-screen at the landing:
1616
+ - **`add <topic>`** lets the user request an additional recommendation on the fly (e.g. `add telemetry` → agent calls `regenerate_recommendation` with a new rec hint, or asks the user to clarify what's missing). Surface it in `help` or on the 9.3 detail card, NOT in the landing action line — too many actions at once weakens the selection screen. If the backing API doesn't yet support targeted single-rec addition, prompt the user with: "I can add a rec for `{topic}` by regenerating the full set with that pinned — that takes ~30s. Or hold the current set and add manually via the web UI."
1617
+ - **`show scope`** to expand the full problem statement (covered in 9.2).
1555
1618
 
1556
1619
  ##### 9.2 — `show scope` handling
1557
1620
 
@@ -1646,7 +1709,7 @@ Ritual build
1646
1709
 
1647
1710
  Accepted {N} recommendations.
1648
1711
 
1649
- View: https://dev.ritualapp.cloud/e/{exploration_id}
1712
+ View: https://app.ritualapp.cloud/e/{exploration_id}
1650
1713
 
1651
1714
  Next: generating the build brief…
1652
1715
  ```
@@ -1783,7 +1846,7 @@ When the brief content is in hand (from generate OR polling), **don't dump 300 l
1783
1846
  ```markdown
1784
1847
  <!--
1785
1848
  Generated by Ritual
1786
- Exploration: https://dev.ritualapp.cloud/e/{exploration_id}
1849
+ Exploration: https://app.ritualapp.cloud/e/{exploration_id}
1787
1850
  Build brief id: {brief_id}
1788
1851
  Do not remove this header; it preserves implementation lineage.
1789
1852
  -->
@@ -1927,7 +1990,7 @@ feat(<area>): <one-line headline>
1927
1990
  <short body — what changed, why, key trade-off>
1928
1991
 
1929
1992
  Ritual-Exploration: <exploration_id>
1930
- Ritual-Exploration-Url: https://dev.ritualapp.cloud/e/<exploration_id>
1993
+ Ritual-Exploration-Url: https://app.ritualapp.cloud/e/<exploration_id>
1931
1994
  Ritual-RBs-Satisfied: RB-1, RB-2, RB-7
1932
1995
  ```
1933
1996
 
@@ -1980,7 +2043,7 @@ If the user says "y" / "push" / "open PR":
1980
2043
 
1981
2044
  ## Exploration
1982
2045
 
1983
- - Exploration: [<exploration name>](https://dev.ritualapp.cloud/e/<exploration_id>)
2046
+ - Exploration: [<exploration name>](https://app.ritualapp.cloud/e/<exploration_id>)
1984
2047
  - Build brief: see `BUILD-BRIEF.md` (committed in this PR for reviewer reference)
1985
2048
  - Deferrals intentionally punted: <count, with one-line each>
1986
2049
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "cliVersion": "0.7.11",
3
+ "builtAt": "2026-05-14T12:14:29.050Z"
4
+ }