@ritualai/cli 0.7.15 → 0.8.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.
Files changed (42) hide show
  1. package/dist/commands/doctor.js +66 -0
  2. package/dist/commands/doctor.js.map +1 -1
  3. package/dist/commands/init.js +468 -108
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/agents/providers.js +33 -5
  8. package/dist/lib/agents/providers.js.map +1 -1
  9. package/dist/lib/build-flow-explainer.js +226 -0
  10. package/dist/lib/build-flow-explainer.js.map +1 -0
  11. package/dist/lib/final-cta-box.js +224 -0
  12. package/dist/lib/final-cta-box.js.map +1 -0
  13. package/dist/lib/gitignore-update.js +13 -4
  14. package/dist/lib/gitignore-update.js.map +1 -1
  15. package/dist/lib/onboarding-state.js +140 -0
  16. package/dist/lib/onboarding-state.js.map +1 -0
  17. package/dist/lib/persona-picker.js +171 -0
  18. package/dist/lib/persona-picker.js.map +1 -0
  19. package/dist/lib/persona-samples.js +245 -0
  20. package/dist/lib/persona-samples.js.map +1 -0
  21. package/dist/lib/project-config.js.map +1 -1
  22. package/dist/lib/skill-bundles.js +4 -0
  23. package/dist/lib/skill-bundles.js.map +1 -1
  24. package/dist/lib/skill-copy.js +62 -10
  25. package/dist/lib/skill-copy.js.map +1 -1
  26. package/dist/lib/workspace-explainer.js +193 -0
  27. package/dist/lib/workspace-explainer.js.map +1 -0
  28. package/dist/lib/workspace-flow.js +8 -7
  29. package/dist/lib/workspace-flow.js.map +1 -1
  30. package/package.json +73 -73
  31. package/skills/claude-code/ritual/.ritual-bundle.json +2 -2
  32. package/skills/claude-code/ritual/references/build-flow.md +51 -14
  33. package/skills/codex/ritual/.ritual-bundle.json +2 -2
  34. package/skills/codex/ritual/references/build-flow.md +51 -14
  35. package/skills/cursor/ritual/.ritual-bundle.json +2 -2
  36. package/skills/cursor/ritual/references/build-flow.md +51 -14
  37. package/skills/gemini/ritual/.ritual-bundle.json +2 -2
  38. package/skills/gemini/ritual/references/build-flow.md +51 -14
  39. package/skills/kiro/ritual/.ritual-bundle.json +2 -2
  40. package/skills/kiro/ritual/references/build-flow.md +51 -14
  41. package/skills/vscode/ritual/.ritual-bundle.json +2 -2
  42. package/skills/vscode/ritual/references/build-flow.md +51 -14
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * Final CTA box — last screen of `ritual init` in compact mode.
4
+ *
5
+ * Two-column "Ready to ship" panel that lands RIGHT after the
6
+ * build-flow explainer (which is the "here's what /ritual build
7
+ * does" payoff). Its job: turn the FTUE into action.
8
+ *
9
+ * LEFT column — primary CTA. Per-agent restart instructions
10
+ * (only the agents we actually wired this run),
11
+ * then the literal command to ask their coding
12
+ * agent next: `/ritual build "<your feature>"`.
13
+ *
14
+ * RIGHT column — "more info — invoke when you want it" menu of
15
+ * secondary commands. By NOT printing the details
16
+ * by default, we keep the CTA scannable; the user
17
+ * knows what to type if they want depth.
18
+ *
19
+ * Why a separate file: the box helpers (top/padded/bottom) +
20
+ * stripAnsi + column-padding math are ~50 lines and they recur
21
+ * across the FTUE panels. Better to factor here so each new panel
22
+ * can `import { boxTop, boxPadded, boxBottom }` rather than copy-
23
+ * pasting. Composable. If we add more panels we lift these into
24
+ * `box.ts`.
25
+ *
26
+ * Width: 110 chars. Deliberately matches `welcome-banner.ts` so
27
+ * the FTUE is visually bookended — same frame at the open as the
28
+ * close. The two FTUE-middle explainers (workspace + build-flow)
29
+ * use the narrower 78-char frame because their bodies need to
30
+ * breathe vertically rather than horizontally.
31
+ *
32
+ * Non-TTY / narrow terminal fallback: caller is expected to choose
33
+ * `printFinalCtaBoxTerse` instead — same content, no chrome.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.printFinalCtaBox = printFinalCtaBox;
37
+ exports.printFinalCtaBoxTerse = printFinalCtaBoxTerse;
38
+ exports.boxTop = boxTop;
39
+ exports.boxBottom = boxBottom;
40
+ const colors_1 = require("./colors");
41
+ /** Width target. Matches welcome-banner.ts so FTUE is bookended. */
42
+ const W = 110;
43
+ /** Left column width (CTA). Sized for the longest restart bullet
44
+ * ("VS Code: Cmd+Shift+P → \"Developer: Reload Window\"" = ~50 chars
45
+ * after the bullet indent) PLUS the 4-char `⚠ ` warning marker
46
+ * + warning text. Sized once here so the whole frame doesn't shift
47
+ * if we add an agent later — easier than per-row truncation. */
48
+ const LEFT_W = 64;
49
+ /**
50
+ * Render the final CTA box to stdout. Caller chooses whether to
51
+ * invoke this (compact mode) or the verbose-mode 3 print blocks.
52
+ */
53
+ function printFinalCtaBox(opts) {
54
+ const titleBar = (0, colors_1.color)(colors_1.RITUAL_TEAL, '─ ready to ship ');
55
+ const top = boxTop(W, titleBar);
56
+ const bottom = boxBottom(W);
57
+ const leftLines = renderLeftColumn(opts);
58
+ const rightLines = renderRightColumn();
59
+ // Pad both columns to the same row count so the inner divider
60
+ // reads cleanly top-to-bottom.
61
+ const rows = Math.max(leftLines.length, rightLines.length);
62
+ while (leftLines.length < rows)
63
+ leftLines.push('');
64
+ while (rightLines.length < rows)
65
+ rightLines.push('');
66
+ const divider = (0, colors_1.color)(colors_1.RITUAL_TEAL, '│');
67
+ const rightW = W - LEFT_W - 3; // minus 3 dividers (outer + inner + outer)
68
+ const lines = [];
69
+ lines.push(top);
70
+ lines.push(twoColRow('', '', divider));
71
+ for (let i = 0; i < rows; i++) {
72
+ const l = padDisplay(leftLines[i], LEFT_W);
73
+ const r = padDisplay(rightLines[i], rightW);
74
+ lines.push(`${divider}${l}${divider}${r}${divider}`);
75
+ }
76
+ lines.push(twoColRow('', '', divider));
77
+ lines.push(bottom);
78
+ for (const ln of lines)
79
+ process.stdout.write(ln + '\n');
80
+ }
81
+ /**
82
+ * Narrow-terminal fallback (TTY-styled). Renders the same content
83
+ * as the rich 110-wide box but stacked vertically with a single
84
+ * accent-color rule between sections instead of split columns —
85
+ * fits at any width ≥ 78 cols. Honors NO_COLOR / non-TTY via the
86
+ * `color/dim/boldColor` helpers (they auto-degrade to plain text).
87
+ *
88
+ * Fires when stdout columns are between 78 and 99, OR when stdout
89
+ * isn't a TTY (CI / piped output) — the colors collapse to plain
90
+ * text in that path.
91
+ */
92
+ function printFinalCtaBoxTerse(opts) {
93
+ const out = process.stdout.write.bind(process.stdout);
94
+ const rule = (label) => ` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, '─')} ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, label)}`;
95
+ const amber = [0xe0, 0xa8, 0x4f];
96
+ out('\n');
97
+ out(` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, 'ready to ship')}\n`);
98
+ out('\n');
99
+ if (opts.insideClaudeWarning) {
100
+ out(` ${(0, colors_1.boldColor)(amber, '⚠ ' + opts.insideClaudeWarning)}\n`);
101
+ out('\n');
102
+ }
103
+ out(`${rule('1. restart your coding agents')}\n`);
104
+ for (const line of restartLinesFor(opts.wiredAgentNames)) {
105
+ out(` ${(0, colors_1.dim)('•')} ${line}\n`);
106
+ }
107
+ out('\n');
108
+ out(`${rule('2. ask your coding agent')}\n`);
109
+ out(` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, '›')} /ritual build "<your feature>"\n`);
110
+ out('\n');
111
+ out(` ${(0, colors_1.dim)('more info — invoke when you want it:')}\n`);
112
+ const pairs = [
113
+ ['ritual init --verbose', 'what was wired up · paths · key info'],
114
+ ['ritual doctor', 'verify the MCP connection · diagnose'],
115
+ ['ritual graph status', 'see what\'s in this workspace'],
116
+ ];
117
+ const colW = Math.max(...pairs.map(([c]) => c.length));
118
+ for (const [cmd, desc] of pairs) {
119
+ out(` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, cmd.padEnd(colW))} ${(0, colors_1.dim)('— ' + desc)}\n`);
120
+ }
121
+ out('\n');
122
+ }
123
+ // ─── column content ──────────────────────────────────────────────────────────
124
+ function renderLeftColumn(opts) {
125
+ const lines = [];
126
+ if (opts.insideClaudeWarning) {
127
+ // ⚠ warning belongs on the LEFT (CTA side) because the user
128
+ // has to act on it before the CTA below it works. Bold so it
129
+ // doesn't get lost in the chrome.
130
+ lines.push(` ${(0, colors_1.boldColor)([0xe0, 0xa8, 0x4f], '⚠ ' + opts.insideClaudeWarning)}`);
131
+ lines.push('');
132
+ }
133
+ lines.push(` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, '1. restart your coding agents')}`);
134
+ for (const r of restartLinesFor(opts.wiredAgentNames)) {
135
+ lines.push(` ${(0, colors_1.dim)('•')} ${r}`);
136
+ }
137
+ lines.push('');
138
+ lines.push(` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, '2. ask your coding agent')}`);
139
+ lines.push('');
140
+ lines.push(` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, '›')} /ritual build "<your feature>"`);
141
+ return lines;
142
+ }
143
+ function renderRightColumn() {
144
+ // Each secondary command is a 2-row pair: command name (cyan
145
+ // accent) + one-line description (dim). Blank rows between pairs
146
+ // so the eye groups them; matches the rhythm of `gh help` output.
147
+ const pairs = [
148
+ ['ritual init --verbose', 'what was wired up · paths · key info'],
149
+ ['ritual doctor', 'verify the MCP connection · diagnose'],
150
+ ['ritual graph status', 'see what\'s in this workspace'],
151
+ ];
152
+ const lines = [
153
+ ` ${(0, colors_1.dim)('more info — invoke when you want it:')}`,
154
+ '',
155
+ ];
156
+ for (let i = 0; i < pairs.length; i++) {
157
+ const [cmd, desc] = pairs[i];
158
+ lines.push(` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, cmd)}`);
159
+ lines.push(` ${(0, colors_1.dim)(desc)}`);
160
+ if (i < pairs.length - 1)
161
+ lines.push('');
162
+ }
163
+ return lines;
164
+ }
165
+ /**
166
+ * Build the per-agent restart bullets. Recognizes the agent names
167
+ * we actually ship for (Claude Code, Cursor, Windsurf, Kiro, etc.)
168
+ * and uses the documented one-keystroke restart action for each;
169
+ * falls through to a generic "restart Agent X" for anything we
170
+ * don't have a specific gesture for.
171
+ */
172
+ function restartLinesFor(agentNames) {
173
+ if (agentNames.length === 0) {
174
+ return ['restart your coding agent so it loads the MCP server'];
175
+ }
176
+ return agentNames.map((name) => {
177
+ switch (name) {
178
+ case 'Claude Code':
179
+ return 'Claude Code: exit any session, then run `claude` again';
180
+ case 'Cursor':
181
+ return 'Cursor: Cmd-Q, then reopen';
182
+ case 'Windsurf':
183
+ return 'Windsurf: restart the app';
184
+ case 'Kiro':
185
+ return 'Kiro: restart the app';
186
+ case 'Gemini CLI':
187
+ return 'Gemini CLI: start a fresh `gemini` session';
188
+ case 'VS Code (Copilot)':
189
+ return 'VS Code: Cmd+Shift+P → "Developer: Reload Window"';
190
+ case 'Codex':
191
+ return 'Codex: start a fresh `codex` session';
192
+ default:
193
+ return `${name}: restart so it loads the MCP server`;
194
+ }
195
+ });
196
+ }
197
+ // ─── shared box / padding helpers ───────────────────────────────────────────
198
+ function boxTop(width, title) {
199
+ const stripped = stripAnsi(title);
200
+ const fillWidth = width - stripped.length - 4;
201
+ return (0, colors_1.color)(colors_1.RITUAL_TEAL, '╭') + ' ' + title + ' ' + (0, colors_1.color)(colors_1.RITUAL_TEAL, '─'.repeat(Math.max(0, fillWidth)) + '╮');
202
+ }
203
+ function boxBottom(width) {
204
+ // Inner ┴ where the column divider was — same shape as
205
+ // welcome-banner so the two frames look related.
206
+ const left = '─'.repeat(LEFT_W);
207
+ const right = '─'.repeat(Math.max(0, width - LEFT_W - 3));
208
+ return (0, colors_1.color)(colors_1.RITUAL_TEAL, `╰${left}┴${right}╯`);
209
+ }
210
+ function twoColRow(left, right, divider) {
211
+ const rightW = W - LEFT_W - 3;
212
+ return `${divider}${padDisplay(left, LEFT_W)}${divider}${padDisplay(right, rightW)}${divider}`;
213
+ }
214
+ function padDisplay(s, width) {
215
+ const visLen = stripAnsi(s).length;
216
+ if (visLen >= width)
217
+ return s;
218
+ return s + ' '.repeat(width - visLen);
219
+ }
220
+ function stripAnsi(s) {
221
+ // eslint-disable-next-line no-control-regex
222
+ return s.replace(/\x1b\[[0-9;]*m/g, '');
223
+ }
224
+ //# sourceMappingURL=final-cta-box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"final-cta-box.js","sourceRoot":"","sources":["../../src/lib/final-cta-box.ts"],"names":[],"mappings":"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"}
@@ -42,12 +42,15 @@ const END_MARKER = '# End: ritual init';
42
42
  /**
43
43
  * Returns the gitignore entry for a single provider — the project
44
44
  * skill dir with a trailing slash so git treats it as a directory
45
- * match.
45
+ * match. Returns null for MCP-only agents (no projectSkillDir),
46
+ * which the caller should filter out before assembling the block.
46
47
  */
47
48
  function entryForProvider(provider) {
48
49
  // projectSkillDir is e.g. `.claude/skills` or `.cursor/rules`.
49
50
  // We ignore only the `ritual/` SKILL package under it — the
50
51
  // user's own custom rules/skills siblings stay tracked.
52
+ if (!provider.projectSkillDir)
53
+ return null;
51
54
  return `${provider.projectSkillDir}/ritual/`;
52
55
  }
53
56
  /**
@@ -58,6 +61,7 @@ function entryForProvider(provider) {
58
61
  function buildBlock(scaffoldedProviders) {
59
62
  const entries = scaffoldedProviders
60
63
  .map(entryForProvider)
64
+ .filter((e) => e !== null)
61
65
  // Sort so the block's contents are deterministic regardless of
62
66
  // detect()-order changes between releases. Makes diffs in
63
67
  // people's `.gitignore` files signal-not-noise.
@@ -103,9 +107,14 @@ function replaceOrAppendBlock(body, newBlock) {
103
107
  function updateProjectGitignore(projectRoot, scaffoldedProviders) {
104
108
  const gitignorePath = (0, node_path_1.join)(projectRoot, '.gitignore');
105
109
  const preExisting = (0, node_fs_1.existsSync)(gitignorePath);
106
- const entries = scaffoldedProviders.map(entryForProvider).sort();
107
- // No-op fast path: nothing to ignore.
108
- if (scaffoldedProviders.length === 0) {
110
+ const entries = scaffoldedProviders
111
+ .map(entryForProvider)
112
+ .filter((e) => e !== null)
113
+ .sort();
114
+ // No-op fast path: nothing to ignore (either no providers
115
+ // scaffolded at all, or all of them are MCP-only with no
116
+ // project-local skill dir).
117
+ if (entries.length === 0) {
109
118
  return { gitignorePath, preExisting, entries, changed: false };
110
119
  }
111
120
  const currentBody = preExisting ? (0, node_fs_1.readFileSync)(gitignorePath, 'utf8') : '';
@@ -1 +1 @@
1
- {"version":3,"file":"gitignore-update.js","sourceRoot":"","sources":["../../src/lib/gitignore-update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gitignore-update.js","sourceRoot":"","sources":["../../src/lib/gitignore-update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ /**
3
+ * Per-machine onboarding state.
4
+ *
5
+ * Tracks whether the three FTUE screens (persona picker, workspace
6
+ * explainer, build-flow explainer) have been shown on this machine.
7
+ * We want the explainers to teach the mental model ONCE per machine,
8
+ * not every time someone runs `ritual init` in a new repo — by the
9
+ * time you're cloning your second project we'd just be in your way.
10
+ *
11
+ * Storage shape (`~/.config/ritual/onboarding.json`):
12
+ * {
13
+ * "version": 1,
14
+ * "personaPickedAt": "2026-05-19T20:01:00Z",
15
+ * "personaSlug": "backend-services",
16
+ * "workspaceExplainerSeenAt": "2026-05-19T20:01:12Z",
17
+ * "buildFlowExplainerSeenAt": "2026-05-19T20:01:25Z"
18
+ * }
19
+ *
20
+ * Each field is optional and additive — we only set it once that
21
+ * screen has been shown (and, for the picker, confirmed). Missing
22
+ * fields are treated as "not yet seen". This makes the schema
23
+ * forward-compatible: adding a screen #4 in a later release just
24
+ * means a new field that's missing on existing installs.
25
+ *
26
+ * Why a separate file from `credentials.json`:
27
+ * - We want to be able to reset onboarding (`ritual init
28
+ * --re-onboard`) without nuking the user's auth tokens.
29
+ * - This file is non-secret — losing it just means we re-onboard
30
+ * once, never a security issue. Different blast radius from
31
+ * credentials, so different file.
32
+ *
33
+ * Why per-MACHINE not per-PROJECT:
34
+ * - The persona is a "what kind of work do you do" question, not
35
+ * "what kind of work does this repo support". You're the same
36
+ * dev across all your repos.
37
+ * - The explainers are mental-model teaching — once you've seen
38
+ * them, you've seen them.
39
+ * - Project-level overrides still work via `.ritual/config.json`
40
+ * (the per-project persona override picks up here as fallback
41
+ * when the project file doesn't set one explicitly).
42
+ */
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.readOnboardingState = readOnboardingState;
45
+ exports.updateOnboardingState = updateOnboardingState;
46
+ exports.shouldShowPersonaPicker = shouldShowPersonaPicker;
47
+ exports.shouldShowWorkspaceExplainer = shouldShowWorkspaceExplainer;
48
+ exports.shouldShowBuildFlowExplainer = shouldShowBuildFlowExplainer;
49
+ exports.resetOnboardingState = resetOnboardingState;
50
+ const node_os_1 = require("node:os");
51
+ const node_path_1 = require("node:path");
52
+ const node_fs_1 = require("node:fs");
53
+ /** Bump if the on-disk schema changes incompatibly. */
54
+ const CURRENT_VERSION = 1;
55
+ /**
56
+ * Resolve the onboarding-state file path. Honors `XDG_CONFIG_HOME`
57
+ * for the same reasons as `config.ts` — Linux dotfile convention +
58
+ * clean override seam for tests.
59
+ *
60
+ * Evaluated lazily on each call so tests can mutate env between
61
+ * sub-tests without needing to reset the module cache.
62
+ */
63
+ function onboardingPath() {
64
+ const xdg = process.env.XDG_CONFIG_HOME;
65
+ const base = xdg && xdg.length > 0 ? xdg : (0, node_path_1.join)((0, node_os_1.homedir)(), '.config');
66
+ return (0, node_path_1.join)(base, 'ritual', 'onboarding.json');
67
+ }
68
+ /**
69
+ * Read the current state. Returns an empty object (with the right
70
+ * version) when the file doesn't exist or is corrupt — corruption
71
+ * here is a "show the screens again" outcome, never a hard error,
72
+ * because the worst case is we onboard the user a second time.
73
+ */
74
+ function readOnboardingState() {
75
+ const path = onboardingPath();
76
+ if (!(0, node_fs_1.existsSync)(path))
77
+ return { version: CURRENT_VERSION };
78
+ try {
79
+ const raw = (0, node_fs_1.readFileSync)(path, 'utf-8');
80
+ const parsed = JSON.parse(raw);
81
+ if (parsed.version !== CURRENT_VERSION) {
82
+ // Future schema bumps land here — for now we treat any
83
+ // mismatch as "fresh state" rather than try to migrate.
84
+ return { version: CURRENT_VERSION };
85
+ }
86
+ return { ...parsed, version: CURRENT_VERSION };
87
+ }
88
+ catch {
89
+ return { version: CURRENT_VERSION };
90
+ }
91
+ }
92
+ /**
93
+ * Merge `patch` into the existing on-disk state and persist. We
94
+ * always read-then-write rather than blind-overwrite so a partial
95
+ * patch (e.g. "mark workspace explainer seen") doesn't blow away
96
+ * unrelated fields (persona pick) written by an earlier screen.
97
+ */
98
+ function updateOnboardingState(patch) {
99
+ const current = readOnboardingState();
100
+ const next = { ...current, ...patch, version: CURRENT_VERSION };
101
+ persist(next);
102
+ return next;
103
+ }
104
+ /**
105
+ * Convenience predicates the init flow uses to decide whether to
106
+ * render each screen. Centralized here so the gating policy lives
107
+ * in one place — if we later decide the workspace explainer should
108
+ * re-show after a year or after a major version bump, we change
109
+ * the predicate, not every call site.
110
+ */
111
+ function shouldShowPersonaPicker(state) {
112
+ return !state.personaPickedAt;
113
+ }
114
+ function shouldShowWorkspaceExplainer(state) {
115
+ return !state.workspaceExplainerSeenAt;
116
+ }
117
+ function shouldShowBuildFlowExplainer(state) {
118
+ return !state.buildFlowExplainerSeenAt;
119
+ }
120
+ /**
121
+ * Wipe the on-disk state. Used by `ritual init --re-onboard` (a
122
+ * support escape hatch for "I want to see the intro again"). We
123
+ * write `{}` instead of unlinking so file permissions / SELinux
124
+ * contexts / Windows ACLs aren't disturbed.
125
+ */
126
+ function resetOnboardingState() {
127
+ persist({ version: CURRENT_VERSION });
128
+ }
129
+ // ─── internal ──────────────────────────────────────────────────────────────
130
+ function persist(state) {
131
+ const path = onboardingPath();
132
+ const dir = (0, node_path_1.join)(path, '..');
133
+ if (!(0, node_fs_1.existsSync)(dir))
134
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
135
+ // 0600 on the file so we don't widen access just because the
136
+ // parent dir is group-readable. Onboarding state isn't secret
137
+ // but there's no reason to make it world-visible either.
138
+ (0, node_fs_1.writeFileSync)(path, JSON.stringify(state, null, 2), { encoding: 'utf-8', mode: 0o600 });
139
+ }
140
+ //# sourceMappingURL=onboarding-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding-state.js","sourceRoot":"","sources":["../../src/lib/onboarding-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ /**
3
+ * Persona picker — onboarding screen #1 of 3.
4
+ *
5
+ * Shown once per machine on first `ritual init` (gated by
6
+ * onboarding-state.ts). Determines two downstream things:
7
+ *
8
+ * 1. The default template `/ritual build` will reach for. Persona
9
+ * slug equals the template's schema.id, so a user who picks
10
+ * "Backend Services" gets the backend-services Engineering
11
+ * template by default — they can still override per build.
12
+ * 2. The accent + framing used in screens #2 and #3 so the whole
13
+ * onboarding cohort feels personal.
14
+ *
15
+ * UI version (this file): numbered-list with color accents, chip
16
+ * tags ("APIs · workers · idempotency"), and a "see more" gate so
17
+ * we surface the 5 most-common personas first and the long tail
18
+ * only when asked. This is the pragmatic version; an Ink-based
19
+ * card UI is a polish pass we can layer on later without changing
20
+ * the contract here.
21
+ *
22
+ * Non-TTY fallback: returns `null` immediately. Callers must check
23
+ * `process.stdin.isTTY` before invoking the interactive version —
24
+ * see init.ts for the pattern. The seed value can also be supplied
25
+ * via `--persona <slug>` on the command line for CI bootstrapping.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.__test = void 0;
29
+ exports.pickPersona = pickPersona;
30
+ const colors_1 = require("./colors");
31
+ const prompt_1 = require("./prompt");
32
+ const persona_samples_1 = require("./persona-samples");
33
+ /**
34
+ * Run the persona picker. Returns the picked persona, or `null` if
35
+ * the user skipped explicitly (entered empty / "skip" / etc).
36
+ *
37
+ * Throws on non-TTY stdin (Node's readline doesn't support it). The
38
+ * caller must gate on `process.stdin.isTTY` first.
39
+ */
40
+ async function pickPersona(opts = {}) {
41
+ const featured = orderByFeatured(persona_samples_1.PERSONA_SAMPLES, persona_samples_1.FEATURED_PERSONA_SLUGS);
42
+ let showingAll = opts.showAll ?? false;
43
+ let visibleCount = showingAll ? featured.length : Math.min(persona_samples_1.FEATURED_PERSONA_SLUGS.length, featured.length);
44
+ printHeader();
45
+ printPersonaList(featured.slice(0, visibleCount));
46
+ // Initial prompt: number, "more", or skip
47
+ while (true) {
48
+ const answer = (await (0, prompt_1.prompt)(formatPickPrompt(visibleCount, showingAll))).trim();
49
+ if (!answer || answer.toLowerCase() === 'skip') {
50
+ process.stdout.write('\n' + (0, colors_1.dim)(' no persona set — /ritual build will ask you per exploration.') + '\n\n');
51
+ return null;
52
+ }
53
+ // Allow "more" to expand the list (one-way; once expanded, the
54
+ // long-tail personas are shown until pick / skip).
55
+ if (answer.toLowerCase() === 'more' && !showingAll) {
56
+ const tail = featured.slice(visibleCount);
57
+ process.stdout.write('\n');
58
+ printPersonaList(tail, visibleCount);
59
+ showingAll = true;
60
+ visibleCount = featured.length;
61
+ continue;
62
+ }
63
+ const idx = parseChoice(answer, featured.length);
64
+ if (idx === null) {
65
+ process.stdout.write((0, colors_1.dim)(` didn't recognize "${answer}" — try a number, "more", or "skip".`) + '\n');
66
+ continue;
67
+ }
68
+ const picked = featured[idx];
69
+ process.stdout.write('\n ' +
70
+ (0, colors_1.boldColor)(picked.accent, `✓ ${picked.label}`) +
71
+ (0, colors_1.dim)(` · default template for /ritual build`) +
72
+ '\n\n');
73
+ return { slug: picked.slug, label: picked.label };
74
+ }
75
+ }
76
+ // ─── rendering ──────────────────────────────────────────────────────────────
77
+ function printHeader() {
78
+ const lines = [
79
+ '',
80
+ ' ' + (0, colors_1.boldColor)(colors_1.RITUAL_TEAL, 'What kind of code do you work on most?'),
81
+ ' ' + (0, colors_1.dim)('you can always change this later — it\'s a default, not a lock-in.'),
82
+ '',
83
+ ];
84
+ for (const l of lines)
85
+ process.stdout.write(l + '\n');
86
+ }
87
+ /**
88
+ * Render a list of personas as `N) Label tag · tag · tag`.
89
+ *
90
+ * `startIndex` is the 0-based index of the FIRST persona in the slice,
91
+ * so when we render the long-tail after "more", the numbering continues
92
+ * from where the featured list left off (6, 7, 8…).
93
+ */
94
+ function printPersonaList(personas, startIndex = 0) {
95
+ const numWidth = String(startIndex + personas.length).length;
96
+ const labelWidth = Math.max(...personas.map((p) => p.label.length));
97
+ for (let i = 0; i < personas.length; i++) {
98
+ const p = personas[i];
99
+ const n = String(startIndex + i + 1).padStart(numWidth, ' ');
100
+ const label = (0, colors_1.boldColor)(p.accent, p.label.padEnd(labelWidth, ' '));
101
+ const tags = p.caresAbout.slice(0, 4).join(' · ');
102
+ process.stdout.write(` ${(0, colors_1.color)(p.accent, n + ')')} ${label} ${(0, colors_1.dim)(tags)}\n`);
103
+ }
104
+ process.stdout.write('\n');
105
+ }
106
+ function formatPickPrompt(visibleCount, showingAll) {
107
+ const hint = showingAll
108
+ ? `pick a number (1-${visibleCount}), or "skip"`
109
+ : `pick a number (1-${visibleCount}), "more" to see the rest, or "skip"`;
110
+ return ` ${(0, colors_1.dim)(hint)}\n ${(0, colors_1.color)(colors_1.RITUAL_TEAL, '›')} `;
111
+ }
112
+ // ─── helpers ────────────────────────────────────────────────────────────────
113
+ /**
114
+ * Reorder `samples` so the featured slugs come first (in the order
115
+ * given by `featuredSlugs`) and the rest follow in their natural
116
+ * order. Defensive against missing slugs — if a featured slug has no
117
+ * matching sample, we just skip it.
118
+ */
119
+ function orderByFeatured(samples, featuredSlugs) {
120
+ const bySlug = new Map(samples.map((s) => [s.slug, s]));
121
+ const featured = [];
122
+ for (const slug of featuredSlugs) {
123
+ const s = bySlug.get(slug);
124
+ if (s)
125
+ featured.push(s);
126
+ }
127
+ const tail = samples.filter((s) => !featuredSlugs.includes(s.slug));
128
+ return [...featured, ...tail];
129
+ }
130
+ /**
131
+ * Parse a user's text answer into a 0-based index into the full
132
+ * persona list. Returns null if the answer doesn't resolve. We
133
+ * accept 1-based numbers (matching the rendered list) and a tiny
134
+ * set of label aliases — "backend", "frontend" — for hand-typers.
135
+ *
136
+ * Exported (alongside `orderByFeatured`) under `__test` so the spec
137
+ * can exercise parsing without spinning up readline. Not part of the
138
+ * public API; treat the underscore prefix as the contract.
139
+ */
140
+ function parseChoice(answer, listLength) {
141
+ const trimmed = answer.trim().toLowerCase();
142
+ // Empty input is never a match — defense-in-depth even though
143
+ // the caller short-circuits "" as "skip". Without this, the
144
+ // substring loop below would resolve "" against every label.
145
+ if (!trimmed)
146
+ return null;
147
+ const asNumber = Number(trimmed);
148
+ if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= listLength) {
149
+ return asNumber - 1;
150
+ }
151
+ // Word aliases — substring match against label or shortLabel. We
152
+ // keep this loose because the picker isn't latency-sensitive and
153
+ // it's friendlier than rejecting "frontend" because the label is
154
+ // "Frontend Web".
155
+ for (let i = 0; i < persona_samples_1.PERSONA_SAMPLES.length; i++) {
156
+ const p = persona_samples_1.PERSONA_SAMPLES[i];
157
+ if (trimmed === p.slug ||
158
+ p.label.toLowerCase().includes(trimmed) ||
159
+ p.shortLabel.toLowerCase() === trimmed) {
160
+ return i;
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+ /**
166
+ * Test seam — internal pure helpers exported solely for unit
167
+ * coverage. Do not consume from non-spec code; the underscore
168
+ * prefix is the contract.
169
+ */
170
+ exports.__test = { parseChoice, orderByFeatured };
171
+ //# sourceMappingURL=persona-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona-picker.js","sourceRoot":"","sources":["../../src/lib/persona-picker.ts"],"names":[],"mappings":"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"}