diff --git a/__tests__/e2e/onboarding-invocation.e2e.ts b/__tests__/e2e/onboarding-invocation.e2e.ts
index c44e73a..2daf58f 100644
--- a/__tests__/e2e/onboarding-invocation.e2e.ts
+++ b/__tests__/e2e/onboarding-invocation.e2e.ts
@@ -11,14 +11,24 @@ const IDE_CASES = [
downPresses: 0,
command: 'claude',
expectedArgs: ['--print', '--output-format', 'stream-json', '--verbose'],
- expectedPromptSnippets: ['Confidence SDK', 'React', 'mcp__confidence-flags__'],
+ expectedPromptSnippets: [
+ 'Confidence SDK',
+ '.claude/skills/analyze-project/SKILL.md',
+ 'existing codebase',
+ 'mcp__confidence-flags__',
+ ],
},
{
name: 'Cursor',
downPresses: 1,
command: 'cursor',
expectedArgs: ['--print', '--output-format', 'stream-json', '--approve-mcps', '--yolo'],
- expectedPromptSnippets: ['Confidence SDK', 'React', 'mcp__confidence-flags__'],
+ expectedPromptSnippets: [
+ 'Confidence SDK',
+ '.cursor/skills/analyze-project/SKILL.md',
+ 'existing codebase',
+ 'mcp__confidence-flags__',
+ ],
firstArg: 'agent',
},
{
@@ -26,7 +36,12 @@ const IDE_CASES = [
downPresses: 2,
command: 'codex',
expectedArgs: ['exec', '--json', '--sandbox', 'danger-full-access', '-'],
- expectedPromptSnippets: ['Confidence SDK', 'React', 'confidence-flags:'],
+ expectedPromptSnippets: [
+ 'Confidence SDK',
+ '.agents/skills/analyze-project/SKILL.md',
+ 'existing codebase',
+ 'confidence-flags:',
+ ],
},
] as const;
diff --git a/__tests__/e2e/skip-onboarding.e2e.ts b/__tests__/e2e/skip-onboarding.e2e.ts
index 56625cc..b27765e 100644
--- a/__tests__/e2e/skip-onboarding.e2e.ts
+++ b/__tests__/e2e/skip-onboarding.e2e.ts
@@ -15,7 +15,7 @@ describe('when the user skips onboarding', () => {
await session.waitForText("What's next?");
// Docs and dashboard links still appear
- await session.waitForText('Docs:');
+ await session.waitForText('Documentation:');
await session.waitForText('Dashboard:');
});
diff --git a/__tests__/e2e/skip-plugins.e2e.ts b/__tests__/e2e/skip-plugins.e2e.ts
index 66803da..f6ffa43 100644
--- a/__tests__/e2e/skip-plugins.e2e.ts
+++ b/__tests__/e2e/skip-plugins.e2e.ts
@@ -49,7 +49,7 @@ describe('when the user skips installing AI plugin', () => {
await session.waitForText('onboarding complete', { timeout: 60_000 });
// Done — onboarding ran so report file and code changes appear
- await session.waitForText('What we set up');
+ await session.waitForText('What we have set up');
await session.waitForText('CONFIDENCE_QUICKSTART.md');
});
});
diff --git a/__tests__/ui/screens/DoneScreen.test.tsx b/__tests__/ui/screens/DoneScreen.test.tsx
index a523cc6..ceae8b3 100644
--- a/__tests__/ui/screens/DoneScreen.test.tsx
+++ b/__tests__/ui/screens/DoneScreen.test.tsx
@@ -43,7 +43,7 @@ describe('DoneScreen', () => {
using sut = renderScreen(, { screen: ScreenId.Done });
store.setCodeChanges(['Added @spotify-confidence/sdk', 'Created confidence.config.ts']);
await waitFor(() => {
- expect(sut.lastFrame()).toContain('What we set up');
+ expect(sut.lastFrame()).toContain('What we have set up');
expect(sut.lastFrame()).toContain('confidence.config.ts');
});
});
diff --git a/src/integrations/shared.ts b/src/integrations/shared.ts
index ac0bef1..a7c80ad 100644
--- a/src/integrations/shared.ts
+++ b/src/integrations/shared.ts
@@ -4,6 +4,7 @@ import { join } from 'node:path';
import { SKILLS_BASE_URL } from '@lib/constants.js';
export const PLUGIN_SKILLS = [
+ 'analyze-project',
'onboard-confidence',
'onboard-confidence-dry-run',
'setup-warehouse',
diff --git a/src/lib/onboarding-prompt/index.ts b/src/lib/onboarding-prompt/index.ts
index 874a6ce..bf3a2ff 100644
--- a/src/lib/onboarding-prompt/index.ts
+++ b/src/lib/onboarding-prompt/index.ts
@@ -2,7 +2,7 @@ import type { ChosenIde, OnboardingGoal } from '../session.js';
import { addIf } from '../prompt-utils.js';
import { preflight, scaffold } from './preflight.js';
import { determineSDK, resolveClient } from './sdk.js';
-import { integrateSDK } from './integrate.js';
+import { integrateSDK, integrateViaSkill } from './integrate.js';
import { determineRecordingSDK, integrateRecording } from './session-recording.js';
import { migrateFlags } from './migration.js';
import { generateReport, summary, rules } from './report.js';
@@ -33,20 +33,32 @@ export function buildOnboardingPrompt({
hasPlugins = false,
}: PromptOptions): string {
const steps = new StepCounter(isEmptyProject ? 2 : 1);
- const includeFlags = goal === 'feature-flags' || goal === 'all';
- const includeRecording = goal === 'session-recording' || goal === 'all';
const tools = buildToolVars(ide);
+ const includingFlags = goal === 'feature-flags' || goal === 'all';
+ const includingRecording = goal === 'session-recording' || goal === 'all';
+ const usingSkill = includingFlags && hasPlugins;
+
const sections = [
preamble(framework, projectDir, isEmptyProject, goal),
preflight(tools),
addIf(isEmptyProject, () => scaffold(framework, steps.next())),
- addIf(includeFlags, () => determineSDK(framework, steps.next(), tools)),
- addIf(includeFlags, () => resolveClient(framework, steps.next(), tools)),
- addIf(includeFlags, () => integrateSDK(steps.next(), steps.current - 2, isEmptyProject, tools)),
- addIf(includeRecording, () => determineRecordingSDK(framework, steps.next(), tools)),
- addIf(includeRecording, () => integrateRecording(steps.next(), isEmptyProject)),
+
+ usingSkill
+ ? [integrateViaSkill(framework, steps.next(), isEmptyProject, ide)]
+ : [
+ addIf(includingFlags, () => determineSDK(framework, steps.next(), tools)),
+ addIf(includingFlags, () => resolveClient(framework, steps.next(), tools)),
+ addIf(includingFlags, () =>
+ integrateSDK(steps.next(), steps.current - 2, isEmptyProject, tools),
+ ),
+ ],
+
+ addIf(includingRecording, () => determineRecordingSDK(framework, steps.next(), tools)),
+ addIf(includingRecording, () => integrateRecording(steps.next(), isEmptyProject)),
+
...migrations.map((m) => migrateFlags(m, steps.next())),
+
generateReport({ step: steps.next(), isEmptyProject, goal, hasPlugins }),
summary(steps.next()),
rules(),
diff --git a/src/lib/onboarding-prompt/integrate.ts b/src/lib/onboarding-prompt/integrate.ts
index ade9a3d..c969996 100644
--- a/src/lib/onboarding-prompt/integrate.ts
+++ b/src/lib/onboarding-prompt/integrate.ts
@@ -1,6 +1,34 @@
+import type { ChosenIde } from '../session.js';
import { CONFIDENCE_DOCS_URL } from '../constants.js';
import { loadStep } from './steps/load.js';
+const SKILLS_DIR: Record = {
+ claude: '.claude/skills',
+ cursor: '.cursor/skills',
+ codex: '.agents/skills',
+};
+
+export function integrateViaSkill(
+ framework: string,
+ step: number,
+ isEmptyProject: boolean,
+ ide: ChosenIde,
+): string {
+ return loadStep('integrate-via-skill.md', {
+ STEP: step,
+ FRAMEWORK: framework,
+ SKILLS_DIR: SKILLS_DIR[ide],
+
+ DOMAIN_CONTEXT: isEmptyProject
+ ? "The project was just scaffolded — treat the sample app's features as the domain."
+ : 'The project is an existing codebase. Study its code to understand the domain, UI flows, and business logic.',
+
+ INSERTION_HINT: isEmptyProject
+ ? 'For fresh scaffolds, use the scaffold\'s default heading or welcome text as the insertion point — the "aha" moment works just as well on boilerplate. Demonstrate at least two use cases (e.g. a gradual rollout for a heading change and a kill switch for a feature section).'
+ : 'Read the top 2–3 candidate files and pick the best one: a single visible string or component, no complex conditionals already wrapping it, in a file the user will recognize.',
+ });
+}
+
const REACT_GOTCHAS = `
**React/Next.js gotchas:**
diff --git a/src/lib/onboarding-prompt/steps/integrate-via-skill.md b/src/lib/onboarding-prompt/steps/integrate-via-skill.md
new file mode 100644
index 0000000..e62d044
--- /dev/null
+++ b/src/lib/onboarding-prompt/steps/integrate-via-skill.md
@@ -0,0 +1,31 @@
+## {{STEP}}. Integrate feature flags
+
+Print "STATUS: Analyzing project for flag integration points..."
+
+Read `{{SKILLS_DIR}}/analyze-project/SKILL.md` as a **methodology reference** — use it for what to analyze, how to identify flag candidates, which SDK to pick, and how to create and wire flags. Ignore its output formatting entirely: no step tracker, no EDUCATE blocks, no AskUserQuestion calls.
+
+Execute the skill's workflow automatically, without pausing for user input:
+
+- **Analysis** (skill steps 1–5): scan the project, check for existing providers, identify flag candidates, look up best practices, and select the best proposal(s).
+- **Implementation** (skill step 6): determine the SDK, set up the client, create the flag(s), install packages, add integration code, and verify the build.
+
+{{DOMAIN_CONTEXT}}
+
+{{INSERTION_HINT}}
+
+**Output format — use this instead of anything in the skill:**
+
+The only user-visible output is STATUS-prefixed lines (~60 chars max). No step tracker boxes, no EDUCATE blocks, no headers, no AskUserQuestion. Print STATUS lines before each phase and periodically within longer phases:
+
+- "STATUS: Scanning for existing flag usage..."
+- "STATUS: Determining the right Confidence SDK..."
+- "STATUS: Resolving SDK client and secret..."
+- "STATUS: Creating feature flags..."
+- After each flag: "STATUS: Created flag: "
+- "STATUS: Installing Confidence SDK packages..."
+- "STATUS: Adding provider and flag evaluation code..."
+- After wiring each flag: "STATUS: Integrated flag: "
+- "STATUS: Verifying project builds..."
+
+Read the client secret from CONFIDENCE_CLIENT_SECRET env var in all generated code.
+Use the OpenFeature API with local resolve where supported. Access flag values via dot notation: `flag-name.property`.
diff --git a/src/ui/tui/screens/about/AboutScreen.tsx b/src/ui/tui/screens/about/AboutScreen.tsx
index 96eead3..e84a5ca 100644
--- a/src/ui/tui/screens/about/AboutScreen.tsx
+++ b/src/ui/tui/screens/about/AboutScreen.tsx
@@ -31,11 +31,11 @@ export function AboutScreen() {
- {'Docs: '}
+ {' Docs: '}
{CONFIDENCE_DOCS_URL}
- {'Dashboard: '}
+ {'Dashboard: '}
{CONFIDENCE_DASHBOARD_URL}
diff --git a/src/ui/tui/screens/done/DoneScreen.tsx b/src/ui/tui/screens/done/DoneScreen.tsx
index af73963..aad03ee 100644
--- a/src/ui/tui/screens/done/DoneScreen.tsx
+++ b/src/ui/tui/screens/done/DoneScreen.tsx
@@ -9,12 +9,14 @@ import { launchChatSession, getIntegration } from '@integrations/index.js';
import { useSession, $session } from '../../store.js';
import { track } from '@lib/telemetry.js';
import { doneActionSelected } from './telemetry-events.js';
+import { useIsShort } from '@ui/tui/hooks/useIsShort.js';
const MAX_SHOWN_CHANGES = 5;
export function DoneScreen() {
const session = useSession();
const { exit } = useApp();
+ const isShort = useIsShort();
const narrow = useIsNarrow();
const { reportFile, codeChanges, projectDir, ide } = session;
const align = narrow ? HAlign.Left : HAlign.Center;
@@ -28,42 +30,40 @@ export function DoneScreen() {
- {codeChanges.length > 0 && (
-
-
- What we set up:
+ {codeChanges.length > 0 && !isShort && (
+ <>
+
+ What we have set up:
- {codeChanges
- .toReversed()
- .slice(0, MAX_SHOWN_CHANGES)
- .map((change, i) => (
-
- {Icons.bullet}
- {change}
-
- ))}
-
+
+ {codeChanges
+ .toReversed()
+ .slice(0, MAX_SHOWN_CHANGES)
+ .map((change, i) => (
+
+ {Icons.bullet}
+ {change}
+
+ ))}
+
+ >
)}
- {reportFile && (
-
- Full details:
-
- {Icons.bullet}
+
+ {reportFile && (
+
+ {' Full report: '}
{reportFile}
-
-
- )}
-
-
+
+ )}
- {'Docs: '}
+ {'Documentation: '}
{CONFIDENCE_DOCS_URL}
- {'Dashboard: '}
+ {' Dashboard: '}
{CONFIDENCE_DASHBOARD_URL}
diff --git a/src/ui/tui/screens/welcome/WelcomeScreen.tsx b/src/ui/tui/screens/welcome/WelcomeScreen.tsx
index d85dddd..906ea23 100644
--- a/src/ui/tui/screens/welcome/WelcomeScreen.tsx
+++ b/src/ui/tui/screens/welcome/WelcomeScreen.tsx
@@ -99,12 +99,12 @@ export function WelcomeScreen() {
- {' Directory '}
+ {'Directory '}
{Icons.check}
{dir}
- {' Framework '}
+ {'Framework '}
{detectionAttempted ? (
<>
{frameworkIcon}
@@ -116,7 +116,7 @@ export function WelcomeScreen() {
)}
- {' Telemetry '}
+ {'Telemetry '}
{telemetryOn ? (
<>
{Icons.check}