@productbrain/cli 0.1.0-beta.10339 → 0.1.0-beta.10343

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 (54) hide show
  1. package/dist/__tests__/route-latency-budget.test.js +3 -4
  2. package/dist/__tests__/route-latency-budget.test.js.map +1 -1
  3. package/dist/generated/routeLatencyBudget.generated.d.ts +0 -1
  4. package/dist/generated/routeLatencyBudget.generated.d.ts.map +1 -1
  5. package/dist/generated/routeLatencyBudget.generated.js +0 -5
  6. package/dist/generated/routeLatencyBudget.generated.js.map +1 -1
  7. package/dist/lib/client.d.ts.map +1 -1
  8. package/dist/lib/client.js +0 -1
  9. package/dist/lib/client.js.map +1 -1
  10. package/package.json +1 -1
  11. package/dist/__tests__/onboarding-path-b.test.d.ts +0 -2
  12. package/dist/__tests__/onboarding-path-b.test.d.ts.map +0 -1
  13. package/dist/__tests__/onboarding-path-b.test.js +0 -46
  14. package/dist/__tests__/onboarding-path-b.test.js.map +0 -1
  15. package/dist/__tests__/onboarding.test.d.ts +0 -6
  16. package/dist/__tests__/onboarding.test.d.ts.map +0 -1
  17. package/dist/__tests__/onboarding.test.js +0 -371
  18. package/dist/__tests__/onboarding.test.js.map +0 -1
  19. package/dist/__tests__/onboardingFallback.test.d.ts +0 -2
  20. package/dist/__tests__/onboardingFallback.test.d.ts.map +0 -1
  21. package/dist/__tests__/onboardingFallback.test.js +0 -74
  22. package/dist/__tests__/onboardingFallback.test.js.map +0 -1
  23. package/dist/lib/conversation-engine.d.ts +0 -45
  24. package/dist/lib/conversation-engine.d.ts.map +0 -1
  25. package/dist/lib/conversation-engine.js +0 -112
  26. package/dist/lib/conversation-engine.js.map +0 -1
  27. package/dist/lib/conversation-phases.d.ts +0 -59
  28. package/dist/lib/conversation-phases.d.ts.map +0 -1
  29. package/dist/lib/conversation-phases.js +0 -11
  30. package/dist/lib/conversation-phases.js.map +0 -1
  31. package/dist/lib/conversation-signals.d.ts +0 -30
  32. package/dist/lib/conversation-signals.d.ts.map +0 -1
  33. package/dist/lib/conversation-signals.js +0 -64
  34. package/dist/lib/conversation-signals.js.map +0 -1
  35. package/dist/lib/onboarding-path-b.d.ts +0 -10
  36. package/dist/lib/onboarding-path-b.d.ts.map +0 -1
  37. package/dist/lib/onboarding-path-b.js +0 -238
  38. package/dist/lib/onboarding-path-b.js.map +0 -1
  39. package/dist/lib/onboarding-shared.d.ts +0 -103
  40. package/dist/lib/onboarding-shared.d.ts.map +0 -1
  41. package/dist/lib/onboarding-shared.js +0 -208
  42. package/dist/lib/onboarding-shared.js.map +0 -1
  43. package/dist/lib/onboarding-shared.test.d.ts +0 -2
  44. package/dist/lib/onboarding-shared.test.d.ts.map +0 -1
  45. package/dist/lib/onboarding-shared.test.js +0 -131
  46. package/dist/lib/onboarding-shared.test.js.map +0 -1
  47. package/dist/lib/onboarding-topics.d.ts +0 -27
  48. package/dist/lib/onboarding-topics.d.ts.map +0 -1
  49. package/dist/lib/onboarding-topics.js +0 -57
  50. package/dist/lib/onboarding-topics.js.map +0 -1
  51. package/dist/lib/onboarding.d.ts +0 -21
  52. package/dist/lib/onboarding.d.ts.map +0 -1
  53. package/dist/lib/onboarding.js +0 -387
  54. package/dist/lib/onboarding.js.map +0 -1
@@ -1,131 +0,0 @@
1
- /**
2
- * WP-770 why-producers (TASK-360, TEN-3918) ruling: onboarding never invents a why. A
3
- * why-required extracted entry (strategy, decisions) with no LLM-supplied `why` is not
4
- * captured by validateExtractedEntries — it is reported by findSkippedForMissingWhy instead
5
- * of being filled with a copy of `description` (a description is what/that, not why).
6
- * Onboarding batch capture is blocked on TEN-3886 (no `pb` in a worktree), so this proves the
7
- * payload shape only — not a live capture.
8
- */
9
- import { describe, expect, it, vi } from 'vitest';
10
- import { validateExtractedEntries, findSkippedForMissingWhy, batchCaptureEntries } from './onboarding-shared';
11
- const kernelCallWithSessionMock = vi.fn();
12
- vi.mock('./client.js', () => ({
13
- kernelCallWithSession: (...args) => kernelCallWithSessionMock(...args),
14
- }));
15
- describe('validateExtractedEntries — why-required skip rule', () => {
16
- it('captures a why-required entry when the LLM supplied its own `why`', () => {
17
- const entries = validateExtractedEntries([
18
- { collection: 'decisions', name: 'Adopt X over Y', description: 'We chose X', why: 'X scales better' },
19
- ]);
20
- expect(entries).toHaveLength(1);
21
- expect(entries[0].why).toBe('X scales better');
22
- });
23
- it('does NOT capture a why-required entry with no `why` — never fabricates one from description', () => {
24
- const entries = validateExtractedEntries([
25
- { collection: 'strategy', name: 'Product Vision', description: 'Automate bank transactions' },
26
- ]);
27
- expect(entries).toHaveLength(0);
28
- });
29
- it('captures a non-why-required entry (tensions) with no `why` — the field stays absent, never filled', () => {
30
- const entries = validateExtractedEntries([
31
- { collection: 'tensions', name: 'Two teams diverge', description: 'Team A and Team B disagree on X' },
32
- ]);
33
- expect(entries).toHaveLength(1);
34
- expect(entries[0].why).toBeUndefined();
35
- });
36
- });
37
- describe('findSkippedForMissingWhy', () => {
38
- it('reports a why-required entry with no `why` as skipped', () => {
39
- const skipped = findSkippedForMissingWhy([
40
- { collection: 'strategy', name: 'Product Vision', description: 'Automate bank transactions' },
41
- ]);
42
- expect(skipped).toEqual([{ name: 'Product Vision', collection: 'strategy' }]);
43
- });
44
- it('does not report an entry that has a `why`, or a non-why-required entry', () => {
45
- const skipped = findSkippedForMissingWhy([
46
- { collection: 'decisions', name: 'Adopt X', description: 'We chose X', why: 'X scales better' },
47
- { collection: 'tensions', name: 'Two teams diverge', description: 'disagree' },
48
- ]);
49
- expect(skipped).toEqual([]);
50
- });
51
- it('does not double-count an entry already dropped for invalid shape (empty name/description)', () => {
52
- const skipped = findSkippedForMissingWhy([
53
- { collection: 'strategy', name: '', description: 'Automate bank transactions' },
54
- ]);
55
- expect(skipped).toEqual([]);
56
- });
57
- });
58
- describe('chat→entries path — exact shape the fixed extraction schema now emits (Codex P1 PRRT_kwDORS4ils6mNFUa)', () => {
59
- it('why present (a real string, not null): captured, with why threaded through', () => {
60
- const entries = validateExtractedEntries([
61
- { collection: 'decisions', name: 'Chose Dropbox over S3', description: 'moves files to Dropbox', typeLabel: 'A decision', why: 'the team already uses it daily' },
62
- ]);
63
- expect(entries).toHaveLength(1);
64
- expect(entries[0].why).toBe('the team already uses it daily');
65
- const skipped = findSkippedForMissingWhy([
66
- { collection: 'decisions', name: 'Chose Dropbox over S3', description: 'moves files to Dropbox', typeLabel: 'A decision', why: 'the team already uses it daily' },
67
- ]);
68
- expect(skipped).toEqual([]);
69
- });
70
- it('why absent as the literal `null` the strict schema always emits: skipped, reported — never treated as a truthy string', () => {
71
- const entries = validateExtractedEntries([
72
- { collection: 'strategy', name: 'Automated invoice matching', description: 'connects to Revolut and matches by confidence score', typeLabel: 'What you\'re building', why: null },
73
- ]);
74
- expect(entries).toHaveLength(0);
75
- const skipped = findSkippedForMissingWhy([
76
- { collection: 'strategy', name: 'Automated invoice matching', description: 'connects to Revolut and matches by confidence score', typeLabel: 'What you\'re building', why: null },
77
- ]);
78
- expect(skipped).toEqual([{ name: 'Automated invoice matching', collection: 'strategy' }]);
79
- });
80
- it('why: null on a non-why-required kind (glossary/tensions) is fine — captured, never skipped', () => {
81
- const entries = validateExtractedEntries([
82
- { collection: 'tensions', name: 'Manual review when confidence is low', description: 'have to manually review in a triage queue', typeLabel: 'A challenge', why: null },
83
- ]);
84
- expect(entries).toHaveLength(1);
85
- expect(entries[0].why).toBeUndefined();
86
- });
87
- });
88
- describe('batchCaptureEntries — `why` in the create payload', () => {
89
- it('includes `why` in the chain.createEntry data payload when the entry has one', async () => {
90
- kernelCallWithSessionMock.mockResolvedValueOnce({ entryId: 'DEC-1' });
91
- await batchCaptureEntries([
92
- { collection: 'decisions', name: 'Adopt X', description: 'We chose X', typeLabel: 'Decision', why: 'X scales better' },
93
- ]);
94
- expect(kernelCallWithSessionMock).toHaveBeenCalledWith('chain.createEntry', expect.objectContaining({
95
- data: { description: 'We chose X', why: 'X scales better' },
96
- }));
97
- });
98
- it('omits `why` from the data payload when the entry has none', async () => {
99
- kernelCallWithSessionMock.mockResolvedValueOnce({ entryId: 'TEN-1' });
100
- await batchCaptureEntries([
101
- { collection: 'tensions', name: 'Two teams diverge', description: 'disagree', typeLabel: 'Tension' },
102
- ]);
103
- expect(kernelCallWithSessionMock).toHaveBeenCalledWith('chain.createEntry', expect.objectContaining({
104
- data: { description: 'disagree' },
105
- }));
106
- });
107
- });
108
- describe('classifyExtractedEntries — seen-tracking (WP-770 why-producers round 4, TASK-360, PRRT_kwDORS4ils6mNX-n)', () => {
109
- it('a later duplicate name WITH a why replaces an earlier skipped same-named row instead of being deduped against it', () => {
110
- const entries = validateExtractedEntries([
111
- { collection: 'decisions', name: 'Adopt X over Y', description: 'We chose X' }, // no why: skipped
112
- { collection: 'decisions', name: 'Adopt X over Y', description: 'We chose X', why: 'X scales better' }, // same name, now with why
113
- ]);
114
- expect(entries).toHaveLength(1);
115
- expect(entries[0].why).toBe('X scales better');
116
- const skipped = findSkippedForMissingWhy([
117
- { collection: 'decisions', name: 'Adopt X over Y', description: 'We chose X' },
118
- { collection: 'decisions', name: 'Adopt X over Y', description: 'We chose X', why: 'X scales better' },
119
- ]);
120
- expect(skipped).toEqual([{ name: 'Adopt X over Y', collection: 'decisions' }]);
121
- });
122
- it('a genuine duplicate (both accepted, same why-having name) still dedupes to one entry', () => {
123
- const entries = validateExtractedEntries([
124
- { collection: 'decisions', name: 'Adopt X', description: 'We chose X', why: 'X scales better' },
125
- { collection: 'decisions', name: 'Adopt X', description: 'Different desc', why: 'A different reason' },
126
- ]);
127
- expect(entries).toHaveLength(1);
128
- expect(entries[0].why).toBe('X scales better'); // first-accepted wins
129
- });
130
- });
131
- //# sourceMappingURL=onboarding-shared.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-shared.test.js","sourceRoot":"","sources":["../../src/lib/onboarding-shared.test.ts"],"names":[],"mappings":"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"}
@@ -1,27 +0,0 @@
1
- /**
2
- * Onboarding conversation topics — coach pattern for first-time setup.
3
- * WP-304: Each topic has goal/behaviour/criteria. LLM judges completion,
4
- * heuristic signals are backup. Topics auto-skip when content is already covered.
5
- */
6
- import type { ConversationSignals } from './conversation-phases.js';
7
- /** A conversation topic with coaching directives and completion criteria. */
8
- export interface OnboardingTopic {
9
- /** Unique topic identifier. */
10
- id: string;
11
- /** What we're trying to learn from this topic. */
12
- goal: string;
13
- /** How the LLM should behave during this topic. */
14
- behaviour: string;
15
- /** When this topic is complete — the LLM judges this. */
16
- criteria: string;
17
- /** Max exchanges before forced advancement. Safety valve. */
18
- maxExchanges: number;
19
- /** If this signal is already true, skip this topic entirely. Null = always run. */
20
- skipIfSignal: keyof ConversationSignals | null;
21
- /** Heuristic backup: if this signal is true, topic is complete even if LLM disagrees. */
22
- heuristicSignal: keyof ConversationSignals | null;
23
- }
24
- export declare const ONBOARDING_TOPICS: OnboardingTopic[];
25
- /** Check if user wants to skip the conversation. */
26
- export declare function userWantsToSkip(text: string): boolean;
27
- //# sourceMappingURL=onboarding-topics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-topics.d.ts","sourceRoot":"","sources":["../../src/lib/onboarding-topics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IAEX,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IAEb,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAElB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IAEjB,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IAErB,mFAAmF;IACnF,YAAY,EAAE,MAAM,mBAAmB,GAAG,IAAI,CAAC;IAE/C,yFAAyF;IACzF,eAAe,EAAE,MAAM,mBAAmB,GAAG,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,iBAAiB,EAAE,eAAe,EA4C9C,CAAC;AAKF,oDAAoD;AACpD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -1,57 +0,0 @@
1
- /**
2
- * Onboarding conversation topics — coach pattern for first-time setup.
3
- * WP-304: Each topic has goal/behaviour/criteria. LLM judges completion,
4
- * heuristic signals are backup. Topics auto-skip when content is already covered.
5
- */
6
- export const ONBOARDING_TOPICS = [
7
- {
8
- id: 'why_here',
9
- goal: 'Understand what brought this person to Product Brain. Follow wherever they go — product, problem, context, or all three.',
10
- behaviour: `Open with: "Hey, what brings you to Product Brain?"
11
- Then follow what's interesting. If they describe a product, get curious about it.
12
- If they describe a problem, lean in. If they're vague, ask the one thing you'd most want to know.
13
- Never ask more than one question. Never explain what Product Brain does — they know enough to be here.`,
14
- criteria: `The user has said something genuine — anything beyond "I don't know" or "just checking it out."
15
- A complete sentence about their work, their problem, or their situation meets criteria.
16
- Criteria is NOT met if the user gave only a non-answer (one word, "not sure", "just exploring").`,
17
- maxExchanges: 3,
18
- skipIfSignal: null,
19
- heuristicSignal: 'hasSubstantiveAnswer',
20
- },
21
- {
22
- id: 'what_they_build',
23
- goal: 'Understand concretely what this person is building — specific enough to explain to someone else in one sentence.',
24
- behaviour: `Pick one concrete noun or verb from their last answer and go one level deeper.
25
- Ask about what it replaces, or how it works today — whichever fits what they said.
26
- Never name the category back at them ("so an AI tool?").
27
- Never ask them to "elaborate" or "tell me more."
28
- One direct question only.`,
29
- criteria: `The user has described what they're building with enough specificity that you could explain it in one sentence to someone who wasn't in this conversation.
30
- Vague answers ("a tool", "an app", "something with AI") do NOT meet criteria.
31
- A described function, a named user group, or a concrete workflow DOES.`,
32
- maxExchanges: 2,
33
- skipIfSignal: 'mentionedProduct',
34
- heuristicSignal: 'mentionedProduct',
35
- },
36
- {
37
- id: 'what_is_hard',
38
- goal: "Understand what's actually hard or broken right now — the friction that brought them here.",
39
- behaviour: `Pick one specific thing they mentioned building and ask what breaks, what slows them down, or what they're not happy with.
40
- Be concrete and direct: "What breaks when X?" or "Where does the rework happen?"
41
- Not: "What challenges do you face?" or "What's the hardest part?" (too generic).
42
- One question only.`,
43
- criteria: `The user has described a specific friction, pain, or failure mode in their own words.
44
- "It's hard" or "there are problems" does NOT meet criteria.
45
- A named workflow that breaks, a named cost, or a described workaround DOES.`,
46
- maxExchanges: 2,
47
- skipIfSignal: 'mentionedChallenge',
48
- heuristicSignal: 'mentionedChallenge',
49
- },
50
- ];
51
- /** User wants to skip — detected by regex on user input. */
52
- const SKIP_PATTERNS = [/\b(?:skip|done|that'?s it|wrap up|let'?s go|finish|end)\b/i];
53
- /** Check if user wants to skip the conversation. */
54
- export function userWantsToSkip(text) {
55
- return SKIP_PATTERNS.some((p) => p.test(text));
56
- }
57
- //# sourceMappingURL=onboarding-topics.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-topics.js","sourceRoot":"","sources":["../../src/lib/onboarding-topics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4BH,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD;QACE,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,0HAA0H;QAChI,SAAS,EAAE;;;uGAGwF;QACnG,QAAQ,EAAE;;iGAEmF;QAC7F,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,IAAI;QAClB,eAAe,EAAE,sBAAsB;KACxC;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,kHAAkH;QACxH,SAAS,EAAE;;;;0BAIW;QACtB,QAAQ,EAAE;;uEAEyD;QACnE,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,kBAAkB;QAChC,eAAe,EAAE,kBAAkB;KACpC;IACD;QACE,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,4FAA4F;QAClG,SAAS,EAAE;;;mBAGI;QACf,QAAQ,EAAE;;4EAE8D;QACxE,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,oBAAoB;QAClC,eAAe,EAAE,oBAAoB;KACtC;CACF,CAAC;AAEF,4DAA4D;AAC5D,MAAM,aAAa,GAAG,CAAC,4DAA4D,CAAC,CAAC;AAErF,oDAAoD;AACpD,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -1,21 +0,0 @@
1
- /**
2
- * Onboarding conversation — coach-pattern LLM conversation for new workspaces.
3
- * WP-304: Topics with goal/behaviour/criteria. LLM judges completion, heuristic backup.
4
- *
5
- * Uses @clack/prompts (WP-303 S1) and the MCP gateway to call the
6
- * Convex onboardingChat action for conversation and extraction,
7
- * then chain.createEntry for batch-capture.
8
- */
9
- /**
10
- * Run the onboarding conversation: topic-by-topic coach pattern, extraction, and auto-capture.
11
- */
12
- export declare function runOnboardingConversation(workspaceName: string): Promise<void>;
13
- /**
14
- * Deterministic fallback when LLM is unavailable. Neither FALLBACK_QUESTIONS asks for a
15
- * reason (only "what" and "what's the challenge"), so a strategy entry here never has one —
16
- * WP-770 why-producers ruling (TASK-360, DEC-1792, PRRT_kwDORS4ils6mNX-o): never copy the
17
- * answer into `why` as filler; skip the why-required strategy entry and report it instead of
18
- * capturing it without a reason (tensions is not why-required, so it always captures).
19
- */
20
- export declare function runFallbackOnboarding(workspaceName: string): Promise<void>;
21
- //# sourceMappingURL=onboarding.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/lib/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuCH;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmUpF;AAYD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8EhF"}
@@ -1,387 +0,0 @@
1
- /**
2
- * Onboarding conversation — coach-pattern LLM conversation for new workspaces.
3
- * WP-304: Topics with goal/behaviour/criteria. LLM judges completion, heuristic backup.
4
- *
5
- * Uses @clack/prompts (WP-303 S1) and the MCP gateway to call the
6
- * Convex onboardingChat action for conversation and extraction,
7
- * then chain.createEntry for batch-capture.
8
- */
9
- import { kernelCall } from './client.js';
10
- import { ask, confirm, select } from './prompts.js';
11
- import { bold, dim, green, icons } from './style.js';
12
- import { trackEvent } from './telemetry.js';
13
- import { detectSignals, mergeSignals } from './conversation-signals.js';
14
- import { evaluateTopic, buildTopicDirective, sanitizeResponse, shouldSkipTopic, isTooSimilar } from './conversation-engine.js';
15
- import { ONBOARDING_TOPICS, userWantsToSkip } from './onboarding-topics.js';
16
- import { FEEDBACK_SUBMIT_HINT } from '../formatters/session.js';
17
- import { countWords, validateExtractedEntries, findSkippedForMissingWhy, displayEntryExample, showCapturedSummary, batchCaptureEntries, truncate, WHY_REQUIRED_ONBOARDING_COLLECTIONS, } from './onboarding-shared.js';
18
- /** Hard cap on total exchanges across all topics. */
19
- const MAX_TOTAL_EXCHANGES = 10;
20
- /** WP-770 why-producers ruling (TASK-360): report entries skipped for lacking a `why` — shared
21
- * by both the length-0 early-return and the normal Step 6 report below. */
22
- function reportSkippedForWhy(skipped) {
23
- if (skipped.length === 0)
24
- return;
25
- process.stdout.write(`\n ${dim(`${skipped.length} skipped, needs a why:`)}\n`);
26
- for (const s of skipped) {
27
- process.stdout.write(` ${dim(icons.dot)} ${s.name}\n`);
28
- }
29
- }
30
- // ── Main conversation ───────────────────────────────────────────────────────
31
- /**
32
- * Run the onboarding conversation: topic-by-topic coach pattern, extraction, and auto-capture.
33
- */
34
- export async function runOnboardingConversation(workspaceName) {
35
- trackEvent('setup_started', { mode: 'llm' });
36
- // Initialize conversation state
37
- const state = {
38
- messages: [],
39
- userMessageCount: 0,
40
- totalUserWords: 0,
41
- topicExchanges: {},
42
- completedTopics: new Set(),
43
- currentTopicId: ONBOARDING_TOPICS[0].id,
44
- completedQA: [],
45
- // Legacy aliases — kept for type compat during transition
46
- phaseExchanges: {},
47
- completedPhases: new Set(),
48
- currentPhaseId: ONBOARDING_TOPICS[0].id,
49
- signals: detectSignals([]),
50
- refusalStreak: 0,
51
- };
52
- let totalExchanges = 0;
53
- let emptyAttempts = 0;
54
- const recentAssistantMessages = [];
55
- // Step 1: Get the LLM's opening (topic 1 generates the greeting)
56
- const firstTopic = ONBOARDING_TOPICS[0];
57
- const openingDirective = buildTopicDirective(firstTopic, 0, ONBOARDING_TOPICS.length, [], workspaceName);
58
- let greeting;
59
- try {
60
- const result = await kernelCall('onboarding.chat', {
61
- messages: [],
62
- workspaceName,
63
- phaseDirective: openingDirective,
64
- turnNumber: 1,
65
- totalUserWords: 0,
66
- refusalStreak: 0,
67
- });
68
- if (!result?.content) {
69
- await runFallbackOnboarding(workspaceName);
70
- return;
71
- }
72
- greeting = sanitizeResponse(result.content);
73
- }
74
- catch {
75
- await runFallbackOnboarding(workspaceName);
76
- return;
77
- }
78
- process.stdout.write(`\n ${greeting}\n\n`);
79
- state.messages.push({ role: 'assistant', content: greeting });
80
- recentAssistantMessages.push(greeting);
81
- // Step 2: Topic-by-topic conversation loop
82
- let currentTopicIndex = 0;
83
- while (currentTopicIndex < ONBOARDING_TOPICS.length && totalExchanges < MAX_TOTAL_EXCHANGES) {
84
- const topic = ONBOARDING_TOPICS[currentTopicIndex];
85
- state.currentTopicId = topic.id;
86
- // DEBUG: trace loop state
87
- if (process.env.PB_DEBUG) {
88
- console.error(`[DEBUG] topic=${topic.id} idx=${currentTopicIndex} exchanges=${totalExchanges} words=${state.totalUserWords} skip=${shouldSkipTopic(topic, state.signals, state.totalUserWords)}`);
89
- }
90
- // Skip check: if the signal for this topic is already present AND we have enough content
91
- if (shouldSkipTopic(topic, state.signals, state.totalUserWords)) {
92
- state.completedTopics.add(topic.id);
93
- currentTopicIndex++;
94
- continue;
95
- }
96
- // Get user input
97
- let userInput;
98
- try {
99
- userInput = await ask({
100
- message: 'You',
101
- placeholder: totalExchanges === 0 ? 'Tell me about your product...' : '',
102
- });
103
- }
104
- catch {
105
- process.stdout.write(`\n ${dim('No worries! Run')} ${bold('pb capture')} ${dim('anytime to add knowledge.')}\n\n`);
106
- return;
107
- }
108
- if (!userInput.trim()) {
109
- emptyAttempts++;
110
- if (emptyAttempts >= 2) {
111
- process.stdout.write(`\n ${dim('No worries — run')} ${bold('pb capture')} ${dim('anytime to teach your AI tools more.')}\n\n`);
112
- return;
113
- }
114
- process.stdout.write(` ${dim('Just hit Enter again to skip, or type anything to continue.')}\n\n`);
115
- continue;
116
- }
117
- emptyAttempts = 0;
118
- // Check if user wants to skip
119
- if (userWantsToSkip(userInput) && state.totalUserWords > 10) {
120
- break;
121
- }
122
- // Update state
123
- state.messages.push({ role: 'user', content: userInput });
124
- state.userMessageCount++;
125
- state.totalUserWords += countWords(userInput);
126
- state.topicExchanges[topic.id] = (state.topicExchanges[topic.id] ?? 0) + 1;
127
- totalExchanges++;
128
- // Detect refusals (very short non-answers)
129
- const wordCount = countWords(userInput);
130
- if (wordCount <= 2) {
131
- state.refusalStreak++;
132
- }
133
- else {
134
- state.refusalStreak = 0;
135
- }
136
- // Update heuristic signals
137
- state.signals = detectSignals(state.messages);
138
- // Get LLM response with topic directive
139
- const topicDirective = buildTopicDirective(topic, currentTopicIndex, ONBOARDING_TOPICS.length, state.completedQA, workspaceName);
140
- let topicComplete = false;
141
- try {
142
- const result = await kernelCall('onboarding.chat', {
143
- messages: state.messages,
144
- workspaceName,
145
- phaseDirective: topicDirective,
146
- turnNumber: totalExchanges + 1,
147
- totalUserWords: state.totalUserWords,
148
- refusalStreak: state.refusalStreak,
149
- });
150
- if (result?.content) {
151
- // Merge LLM-reported signals with heuristic signals
152
- if (result.signals) {
153
- state.signals = mergeSignals(state.signals, result.signals);
154
- }
155
- // Read LLM's topic completion judgment
156
- topicComplete = result.topicComplete ?? false;
157
- // Post-process response
158
- let displayMessage = sanitizeResponse(result.content);
159
- // Detect repetition — if too similar, bump refusal streak for next call
160
- if (isTooSimilar(displayMessage, recentAssistantMessages)) {
161
- state.refusalStreak++;
162
- }
163
- process.stdout.write(`\n ${displayMessage}\n\n`);
164
- state.messages.push({ role: 'assistant', content: displayMessage });
165
- recentAssistantMessages.push(displayMessage);
166
- }
167
- }
168
- catch (err) {
169
- // LLM call failed — try to continue with what we have
170
- if (process.env.PB_DEBUG)
171
- console.error(`[DEBUG] LLM catch, breaking. Error:`, err);
172
- break;
173
- }
174
- // Evaluate topic completion: LLM judgment + heuristic backup + forced advancement
175
- const evalResult = evaluateTopic(topic, state, topicComplete);
176
- if (process.env.PB_DEBUG) {
177
- console.error(`[DEBUG] eval: complete=${evalResult.complete} forced=${evalResult.forced} skip=${evalResult.skip} topicComplete=${topicComplete} topicExchanges=${state.topicExchanges[topic.id]}`);
178
- }
179
- if (evalResult.complete) {
180
- // Accumulate the last Q&A for context in later topics
181
- const lastAssistant = recentAssistantMessages[recentAssistantMessages.length - 1] ?? '';
182
- state.completedQA.push({
183
- topicId: topic.id,
184
- question: lastAssistant,
185
- answer: userInput,
186
- });
187
- state.completedTopics.add(topic.id);
188
- currentTopicIndex++;
189
- }
190
- // else: stay in current topic, loop again
191
- }
192
- // Hard floor: barely any content — offer manual capture
193
- // Threshold: 3 words means basically no real content. Previous threshold of 10
194
- // was too aggressive — 3 short answers can be valid (e.g., "a finance app" + "me" = 4 words).
195
- if (state.totalUserWords < 3 && state.userMessageCount > 0) {
196
- process.stdout.write(`\n ${dim('No problem — you can add knowledge at your own pace with')} ${bold('pb capture')}\n\n`);
197
- trackEvent('setup_completed', { capturedCount: 0, reason: 'insufficient_content' });
198
- return;
199
- }
200
- trackEvent('first_capture_prompted', { mode: 'llm', exchanges: totalExchanges });
201
- // Step 3: Extract structured data from conversation
202
- if (state.messages.filter((m) => m.role === 'user').length === 0) {
203
- process.stdout.write(`\n ${dim('No worries! Run')} ${bold('pb capture')} ${dim('anytime to add knowledge.')}\n\n`);
204
- return;
205
- }
206
- let entriesToCapture;
207
- let skippedForWhy = [];
208
- try {
209
- const result = await kernelCall('onboarding.chat', {
210
- messages: state.messages,
211
- workspaceName,
212
- mode: 'extract',
213
- });
214
- if (!result?.entries?.length) {
215
- process.stdout.write(`\n ${dim("Couldn't organize your answers just yet. Try")} ${bold('pb capture')} ${dim('to add knowledge manually.')}\n\n`);
216
- return;
217
- }
218
- // WP-770 why-producers ruling (TASK-360): an extracted strategy/decisions entry with no
219
- // LLM-supplied `why` is not captured — reported below instead (Step 6).
220
- entriesToCapture = validateExtractedEntries(result.entries).slice(0, 5);
221
- skippedForWhy = findSkippedForMissingWhy(result.entries);
222
- }
223
- catch {
224
- process.stdout.write(`\n ${dim("Couldn't organize your answers just yet. Try")} ${bold('pb capture')} ${dim('to add knowledge manually.')}\n\n`);
225
- return;
226
- }
227
- if (entriesToCapture.length === 0) {
228
- reportSkippedForWhy(skippedForWhy);
229
- process.stdout.write(`\n ${dim('Nothing to save yet. Try')} ${bold('pb capture')} ${dim('to add knowledge manually.')}\n\n`);
230
- return;
231
- }
232
- // Step 4: Show ONE example with schema, offer to browse more
233
- const itemCount = entriesToCapture.length;
234
- process.stdout.write(`\n From this conversation, I'd remember ${bold(String(itemCount))} item${itemCount === 1 ? '' : 's'}.\n`);
235
- process.stdout.write(` Here's an example:\n\n`);
236
- displayEntryExample(entriesToCapture[0]);
237
- let exampleIndex = 1;
238
- let shouldSave = false;
239
- // eslint-disable-next-line no-constant-condition
240
- while (true) {
241
- const hasMoreExamples = exampleIndex < entriesToCapture.length;
242
- const options = [
243
- { value: 'save', label: 'Save all to my product brain', hint: `${itemCount} item${itemCount === 1 ? '' : 's'}` },
244
- ];
245
- if (hasMoreExamples) {
246
- options.push({ value: 'another', label: 'Show another example' });
247
- }
248
- options.push({ value: 'skip', label: 'Not right now' });
249
- let choice;
250
- try {
251
- choice = await select({
252
- message: '',
253
- options,
254
- });
255
- }
256
- catch {
257
- process.stdout.write(`\n ${dim('No problem — run')} ${bold('pb capture')} ${dim('anytime.')}\n\n`);
258
- return;
259
- }
260
- if (choice === 'skip') {
261
- process.stdout.write(`\n ${dim('No problem — run')} ${bold('pb capture')} ${dim('anytime to add more.')}\n\n`);
262
- return;
263
- }
264
- if (choice === 'save') {
265
- shouldSave = true;
266
- break;
267
- }
268
- if (choice === 'another') {
269
- displayEntryExample(entriesToCapture[exampleIndex]);
270
- exampleIndex++;
271
- }
272
- }
273
- if (!shouldSave)
274
- return;
275
- // Step 5: Batch-capture entries
276
- const { capturedCount, captured, errors } = await batchCaptureEntries(entriesToCapture);
277
- // Step 6: Report results
278
- if (capturedCount > 0) {
279
- process.stdout.write(`\n ${green(icons.pass)} Saved ${capturedCount} item${capturedCount === 1 ? '' : 's'} to your product brain.\n`);
280
- trackEvent('capture_completed', { mode: 'llm', count: capturedCount });
281
- }
282
- if (errors.length > 0) {
283
- process.stdout.write(`\n ${dim(`${errors.length} couldn't be saved.`)}\n`);
284
- }
285
- reportSkippedForWhy(skippedForWhy);
286
- // Step 7: Deterministic proof + closing — WP-770 why-producers round 4 (PRRT_kwDORS4ils6mNX-o):
287
- // list only entries batchCaptureEntries actually captured, never the full input list (a
288
- // failed capture must not appear as "remembered").
289
- showCapturedSummary(captured, capturedCount);
290
- process.stdout.write(` ${dim('Run')} ${bold('pb capture')} ${dim('anytime to teach your AI tools more.')}\n\n`);
291
- // WP-584 S4 (FEAT-1389 folded in): teach the feedback verb + behaviour-not-content rule at
292
- // the natural end-of-onboarding beat — reuses the SAME constant session start already prints,
293
- // never a fourth independent copy of this sentence.
294
- process.stdout.write(` ${FEEDBACK_SUBMIT_HINT}\n\n`);
295
- trackEvent('setup_completed', { capturedCount });
296
- }
297
- // ── Deterministic fallback ──────────────────────────────────────────��─────
298
- // P3b U0b (TASK-427, WP-780, TEN-3855): the "Who is it for?" question is dropped — the
299
- // audiences collection is retired and nothing is repointed into another collection; an
300
- // audience claim is strategy prose the founder writes later, not a drawer.
301
- const FALLBACK_QUESTIONS = [
302
- { message: 'What are you building?', placeholder: 'A tool for...' },
303
- { message: "What's the biggest challenge right now?", placeholder: 'Scaling, onboarding, pricing...' },
304
- ];
305
- /**
306
- * Deterministic fallback when LLM is unavailable. Neither FALLBACK_QUESTIONS asks for a
307
- * reason (only "what" and "what's the challenge"), so a strategy entry here never has one —
308
- * WP-770 why-producers ruling (TASK-360, DEC-1792, PRRT_kwDORS4ils6mNX-o): never copy the
309
- * answer into `why` as filler; skip the why-required strategy entry and report it instead of
310
- * capturing it without a reason (tensions is not why-required, so it always captures).
311
- */
312
- export async function runFallbackOnboarding(workspaceName) {
313
- trackEvent('setup_started', { mode: 'fallback' });
314
- process.stdout.write(`\n ${bold(`Let me learn about ${workspaceName}.`)} ${dim('(Quick — 2 questions)')}\n\n`);
315
- const answers = [];
316
- for (const q of FALLBACK_QUESTIONS) {
317
- try {
318
- const answer = await ask({ message: q.message, placeholder: q.placeholder });
319
- if (answer.trim())
320
- answers.push(answer.trim());
321
- }
322
- catch {
323
- break;
324
- }
325
- }
326
- if (answers.length === 0) {
327
- process.stdout.write(`\n ${dim('No worries! Add knowledge anytime with')} ${bold('pb capture')}\n\n`);
328
- return;
329
- }
330
- trackEvent('first_capture_prompted', { mode: 'fallback' });
331
- const entriesToCapture = [];
332
- const skippedForWhy = [];
333
- if (answers[0]) {
334
- if (WHY_REQUIRED_ONBOARDING_COLLECTIONS.has('strategy')) {
335
- skippedForWhy.push({ name: 'Product Vision', collection: 'strategy' });
336
- }
337
- else {
338
- entriesToCapture.push({
339
- collection: 'strategy',
340
- name: 'Product Vision',
341
- description: answers[0],
342
- typeLabel: "What you're building",
343
- });
344
- }
345
- }
346
- if (answers[1]) {
347
- entriesToCapture.push({
348
- collection: 'tensions',
349
- name: truncate(answers[1], 120),
350
- description: answers[1],
351
- typeLabel: "A challenge you're facing",
352
- });
353
- }
354
- if (entriesToCapture.length === 0) {
355
- reportSkippedForWhy(skippedForWhy);
356
- process.stdout.write(`\n ${dim('Nothing to save yet. Try')} ${bold('pb capture')} ${dim('to add knowledge manually.')}\n\n`);
357
- return;
358
- }
359
- // Show extraction
360
- process.stdout.write('\n');
361
- for (const entry of entriesToCapture) {
362
- displayEntryExample(entry);
363
- }
364
- let approved;
365
- try {
366
- approved = await confirm({ message: 'Ready to save these to your product brain?', initialValue: true });
367
- }
368
- catch {
369
- return;
370
- }
371
- if (!approved)
372
- return;
373
- const { capturedCount, captured } = await batchCaptureEntries(entriesToCapture);
374
- if (capturedCount > 0) {
375
- process.stdout.write(`\n ${green(icons.pass)} Saved ${capturedCount} item${capturedCount === 1 ? '' : 's'} to your product brain.\n`);
376
- trackEvent('capture_completed', { mode: 'fallback', count: capturedCount });
377
- }
378
- reportSkippedForWhy(skippedForWhy);
379
- // WP-770 why-producers round 4 (PRRT_kwDORS4ils6mNX-o): list only what was actually captured.
380
- showCapturedSummary(captured, capturedCount);
381
- process.stdout.write(` ${dim('Run')} ${bold('pb capture')} ${dim('anytime to teach your AI tools more.')}\n\n`);
382
- // WP-584 S4 (FEAT-1389 folded in): same teaching beat as the LLM-mode close above — the
383
- // fallback path is a real onboarding completion too, not a degraded one that skips it.
384
- process.stdout.write(` ${FEEDBACK_SUBMIT_HINT}\n\n`);
385
- trackEvent('setup_completed', { mode: 'fallback', capturedCount });
386
- }
387
- //# sourceMappingURL=onboarding.js.map