@chatman-media/kb 1.3.1 → 1.4.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.
@@ -54,6 +54,15 @@ export interface AnswerInput {
54
54
  * uses a calm FAQ-support block instead — answering questions without selling.
55
55
  */
56
56
  supportPhase?: "docs" | "submitted";
57
+ /**
58
+ * Per-stage override (Phase 2 C-2): the lead's current funnel-stage goal/
59
+ * guidance from stage_definitions. Takes precedence over the Style's
60
+ * per-sales-stage config when composing the prompt.
61
+ */
62
+ stageOverride?: {
63
+ goal: string;
64
+ guidance?: string;
65
+ };
57
66
  /**
58
67
  * Called after every `answerWithRag` or `answerWithRagStream` call with the
59
68
  * final telemetry. Useful for logging, metrics, or A/B experiment recording
@@ -1 +1 @@
1
- {"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA8BzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAgG/E;AA2OD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA0HpF;AAED,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,CAAC,CAAA;CAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAiG/E;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA8BzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAgG/E;AA4OD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA2HpF;AAED,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,CAAC,CAAA;CAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAiG/E;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=extract-user-facts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-user-facts.test.d.ts","sourceRoot":"","sources":["../src/extract-user-facts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fact-checker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-checker.test.d.ts","sourceRoot":"","sources":["../src/fact-checker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=grade-skills.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade-skills.test.d.ts","sourceRoot":"","sources":["../src/grade-skills.test.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -45100,9 +45100,11 @@ ${triggerLine}${h.body}`;
45100
45100
  const skillsBlock = skillsForStage.length ? `\u041F\u0420\u0418\u0401\u041C\u042B (\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0443\u043C\u0435\u0441\u0442\u043D\u044B\u0435, \u043D\u0435 \u0432\u0441\u0435 \u0441\u0440\u0430\u0437\u0443 \u2014 \u0432\u044B\u0431\u0438\u0440\u0430\u0439 \u043F\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0443):
45101
45101
  ` + skillsForStage.map((s) => `- ${s.displayName} \u2014 ${s.promptFragment}`).join(`
45102
45102
  `) : "";
45103
- const stageBlock = stageCfg ? `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage.toUpperCase()}.
45104
- ` + `\u0426\u0415\u041B\u042C \u042D\u0422\u0410\u041F\u0410: ${stageCfg.goal}.` + (stageCfg.guidance ? `
45105
- \u041A\u0410\u041A: ${stageCfg.guidance}` : "") + (stageCfg.groundingRequired ? `
45103
+ const effGoal = options.stageOverride?.goal ?? stageCfg?.goal;
45104
+ const effGuidance = options.stageOverride?.guidance ?? stageCfg?.guidance;
45105
+ const stageBlock = effGoal ? `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage.toUpperCase()}.
45106
+ ` + `\u0426\u0415\u041B\u042C \u042D\u0422\u0410\u041F\u0410: ${effGoal}.` + (effGuidance ? `
45107
+ \u041A\u0410\u041A: ${effGuidance}` : "") + (stageCfg?.groundingRequired ? `
45106
45108
  GROUNDING: \u043D\u0430 \u044D\u0442\u043E\u043C \u044D\u0442\u0430\u043F\u0435 \u0432\u0441\u0435 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B (\u0446\u0438\u0444\u0440\u044B, \u0441\u0443\u043C\u043C\u044B, \u0441\u0440\u043E\u043A\u0438) \u0431\u0435\u0440\u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0438\u0437 \u0441\u0435\u043A\u0446\u0438\u0438 KB CONTEXT \u043D\u0438\u0436\u0435. \u0415\u0441\u043B\u0438 \u0435\u0451 \u043D\u0435\u0442 \u0438\u043B\u0438 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0444\u0430\u043A\u0442\u0430 \u0432 \u043D\u0435\u0439 \u043D\u0435\u0442 \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u0441\u043A\u0430\u0436\u0438 \u0447\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u0438\u0448\u044C.` : "") : `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage}. (\u0421\u043F\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u044D\u0442\u0430\u043F\u0430 \u043D\u0435\u0442 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043E\u0431\u0449\u0438\u0439 \u0441\u0442\u0438\u043B\u044C.)`;
45107
45109
  const minorRule = guardrails.noMinors ? "- \u0415\u0441\u043B\u0438 prospect <18 \u043B\u0435\u0442 \u2014 \u0432\u0435\u0436\u043B\u0438\u0432\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0434\u0438\u0430\u043B\u043E\u0433." : "";
45108
45110
  const topicsRule = guardrails.forbiddenTopics.length ? `- \u0417\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u044B\u0435 \u0442\u0435\u043C\u044B: ${guardrails.forbiddenTopics.join(", ")}.` : "";
@@ -59905,7 +59907,8 @@ ${kbContextStr}` : vacBlock : kbContextStr;
59905
59907
  ...input.conversationSummary ? { conversationSummary: input.conversationSummary } : {},
59906
59908
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
59907
59909
  ...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
59908
- ...input.supportPhase ? { supportPhase: input.supportPhase } : {}
59910
+ ...input.supportPhase ? { supportPhase: input.supportPhase } : {},
59911
+ ...input.stageOverride ? { stageOverride: input.stageOverride } : {}
59909
59912
  });
59910
59913
  temperature = input.style.model.temperature;
59911
59914
  } else {
@@ -60092,7 +60095,8 @@ ${kbContextStr}` : vacBlock : kbContextStr;
60092
60095
  ...input.conversationSummary ? { conversationSummary: input.conversationSummary } : {},
60093
60096
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
60094
60097
  ...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
60095
- ...input.supportPhase ? { supportPhase: input.supportPhase } : {}
60098
+ ...input.supportPhase ? { supportPhase: input.supportPhase } : {},
60099
+ ...input.stageOverride ? { stageOverride: input.stageOverride } : {}
60096
60100
  });
60097
60101
  temperature = input.style.model.temperature;
60098
60102
  } else {
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAqIR"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAyIR"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prompt.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.test.d.ts","sourceRoot":"","sources":["../src/prompt.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reflect.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflect.test.d.ts","sourceRoot":"","sources":["../src/reflect.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rewrite-query.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite-query.test.d.ts","sourceRoot":"","sources":["../src/rewrite-query.test.ts"],"names":[],"mappings":""}
package/dist/styles.d.ts CHANGED
@@ -182,5 +182,14 @@ export interface ComposeOptions {
182
182
  * block instead. `docs` = collecting their documents, `submitted` = filed.
183
183
  */
184
184
  supportPhase?: "docs" | "submitted";
185
+ /**
186
+ * Per-stage override (Phase 2): goal/guidance from the lead's current funnel
187
+ * stage_definition. Takes precedence over `style.stages[stage]` for the stage
188
+ * block. Lets AI-built funnels carry per-stage instructions.
189
+ */
190
+ stageOverride?: {
191
+ goal: string;
192
+ guidance?: string;
193
+ };
185
194
  }
186
195
  //# sourceMappingURL=styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;CACrC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatman-media/kb",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Tenant-scoped Knowledge Base: hybrid retrieval (pgvector + BM25), ingest, answer pipeline, persona/skill composition. LLM I/O живёт в @chatman-media/llm-router.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "url": "https://github.com/chatman-media/lead-engine/issues"
57
57
  },
58
58
  "dependencies": {
59
- "@chatman-media/llm-router": "1.1.0",
59
+ "@chatman-media/llm-router": "1.1.1",
60
60
  "unpdf": "^1.6.2",
61
61
  "zod": "^4.4.1"
62
62
  },
@@ -57,6 +57,12 @@ export interface AnswerInput {
57
57
  * uses a calm FAQ-support block instead — answering questions without selling.
58
58
  */
59
59
  supportPhase?: "docs" | "submitted";
60
+ /**
61
+ * Per-stage override (Phase 2 C-2): the lead's current funnel-stage goal/
62
+ * guidance from stage_definitions. Takes precedence over the Style's
63
+ * per-sales-stage config when composing the prompt.
64
+ */
65
+ stageOverride?: { goal: string; guidance?: string };
60
66
  /**
61
67
  * Called after every `answerWithRag` or `answerWithRagStream` call with the
62
68
  * final telemetry. Useful for logging, metrics, or A/B experiment recording
package/src/answer.ts CHANGED
@@ -234,6 +234,7 @@ async function answerFromHits(opts: {
234
234
  ? { directorHooks: input.directorHooks }
235
235
  : {}),
236
236
  ...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
237
+ ...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
237
238
  });
238
239
  temperature = input.style.model.temperature;
239
240
  } else {
@@ -503,6 +504,7 @@ export async function* answerWithRagStream(input: AnswerInput): AsyncIterable<st
503
504
  ? { directorHooks: input.directorHooks }
504
505
  : {}),
505
506
  ...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
507
+ ...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
506
508
  });
507
509
  temperature = input.style.model.temperature;
508
510
  } else {
@@ -0,0 +1,44 @@
1
+ import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { extractUserFacts, parseFactsFromLlmOutput } from "./extract-user-facts.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+
9
+ describe("parseFactsFromLlmOutput", () => {
10
+ it("нет {} → {}", () => {
11
+ expect(parseFactsFromLlmOutput("no json")).toEqual({});
12
+ });
13
+ it("битый JSON → {}", () => {
14
+ expect(parseFactsFromLlmOutput("{broken")).toEqual({});
15
+ });
16
+ it("массив → {}", () => {
17
+ expect(parseFactsFromLlmOutput("[1,2]")).toEqual({});
18
+ });
19
+ it("валидный объект + coercion числовых значений", () => {
20
+ expect(parseFactsFromLlmOutput('{"name":"Аня","age":23}')).toEqual({ name: "Аня", age: "23" });
21
+ });
22
+ it("пустые / слишком длинные значения и длинные ключи отбрасываются", () => {
23
+ const longVal = "x".repeat(300);
24
+ const longKey = "k".repeat(50);
25
+ const out = parseFactsFromLlmOutput(`{"city":" ","name":"Аня","big":"${longVal}","${longKey}":"v"}`);
26
+ expect(out).toEqual({ name: "Аня" });
27
+ });
28
+ it("code-fence/think снимаются", () => {
29
+ expect(parseFactsFromLlmOutput('```json\n{"name":"Ира"}\n```')).toEqual({ name: "Ира" });
30
+ });
31
+ });
32
+
33
+ describe("extractUserFacts", () => {
34
+ const msgs: ChatMessage[] = [{ role: "user", content: "я Аня, мне 23" }];
35
+ it("нет user-сообщений → {} без LLM", async () => {
36
+ expect(await extractUserFacts({ messages: [{ role: "assistant", content: "hi" }], chat: chatThrows() })).toEqual({});
37
+ });
38
+ it("парсит факты из LLM", async () => {
39
+ expect(await extractUserFacts({ messages: msgs, chat: chat('{"name":"Аня","age":"23"}') })).toEqual({ name: "Аня", age: "23" });
40
+ });
41
+ it("LLM упал → {}", async () => {
42
+ expect(await extractUserFacts({ messages: msgs, chat: chatThrows() })).toEqual({});
43
+ });
44
+ });
@@ -0,0 +1,66 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { afterEach, describe, expect, it } from "bun:test";
3
+ import { checkFacts, parseFactCheckResult } from "./fact-checker.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("down"); } }) as unknown as ChatClient;
8
+
9
+ const savedEnv = process.env.RAG_FACT_CHECKER_FAIL_OPEN;
10
+ afterEach(() => {
11
+ if (savedEnv === undefined) delete process.env.RAG_FACT_CHECKER_FAIL_OPEN;
12
+ else process.env.RAG_FACT_CHECKER_FAIL_OPEN = savedEnv;
13
+ });
14
+
15
+ describe("parseFactCheckResult", () => {
16
+ it("чистый JSON", () => {
17
+ expect(parseFactCheckResult('{"grounded":false,"vacancyOk":true,"reason":"x"}')).toEqual({
18
+ grounded: false,
19
+ vacancyOk: true,
20
+ reason: "x",
21
+ });
22
+ });
23
+ it("code-fence + think убираются", () => {
24
+ expect(parseFactCheckResult('<think>..</think>```json\n{"grounded":true,"vacancyOk":false}\n```')).toMatchObject({
25
+ grounded: true,
26
+ vacancyOk: false,
27
+ });
28
+ });
29
+ it("нет {} → OK (fail-open парсинга)", () => {
30
+ expect(parseFactCheckResult("no json")).toEqual({ grounded: true, vacancyOk: true });
31
+ });
32
+ it("битый JSON → OK", () => {
33
+ expect(parseFactCheckResult("{broken")).toEqual({ grounded: true, vacancyOk: true });
34
+ });
35
+ it("non-bool поля → дефолт true", () => {
36
+ expect(parseFactCheckResult('{"grounded":"yes"}')).toEqual({ grounded: true, vacancyOk: true });
37
+ });
38
+ });
39
+
40
+ describe("checkFacts", () => {
41
+ const base = { question: "q", answer: "Зарплата 1500$", context: "контекст 1500$" };
42
+ it("пустой ответ → OK без вызова LLM", async () => {
43
+ expect(await checkFacts({ ...base, answer: " ", chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
44
+ });
45
+ it("нет контекста и вакансий → OK", async () => {
46
+ expect(await checkFacts({ ...base, context: "", chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
47
+ });
48
+ it("есть контекст → парсит вердикт LLM", async () => {
49
+ const r = await checkFacts({ ...base, chat: chat('{"grounded":false,"vacancyOk":true,"reason":"выдумал"}') });
50
+ expect(r.grounded).toBe(false);
51
+ });
52
+ it("vacancies-ветка промпта", async () => {
53
+ const r = await checkFacts({ ...base, vacanciesBlock: "Вакансия: 2000$", chat: chat('{"grounded":true,"vacancyOk":false,"reason":"не совпало"}') });
54
+ expect(r.vacancyOk).toBe(false);
55
+ });
56
+ it("LLM упал → fail-closed по умолчанию", async () => {
57
+ delete process.env.RAG_FACT_CHECKER_FAIL_OPEN;
58
+ const r = await checkFacts({ ...base, chat: chatThrows() });
59
+ expect(r.grounded).toBe(false);
60
+ expect(r.reason).toContain("checker_error");
61
+ });
62
+ it("LLM упал + FAIL_OPEN=1 → пропускаем", async () => {
63
+ process.env.RAG_FACT_CHECKER_FAIL_OPEN = "1";
64
+ expect(await checkFacts({ ...base, chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
65
+ });
66
+ });
@@ -0,0 +1,39 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { gradeSkills, parseSlugList } from "./grade-skills.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+ const ALLOWED = ["mirroring", "scarcity-spots-left", "tactical-empathy"] as const;
9
+
10
+ describe("parseSlugList", () => {
11
+ it("пусто → []", () => {
12
+ expect(parseSlugList("", ALLOWED)).toEqual([]);
13
+ });
14
+ it("JSON-массив, фильтр по allowed", () => {
15
+ expect(parseSlugList('["mirroring","unknown-skill","tactical-empathy"]', ALLOWED)).toEqual(["mirroring", "tactical-empathy"]);
16
+ });
17
+ it("code-fenced JSON", () => {
18
+ expect(parseSlugList('```json\n["scarcity-spots-left"]\n```', ALLOWED)).toEqual(["scarcity-spots-left"]);
19
+ });
20
+ it("comma/newline текст → fallback-парсинг", () => {
21
+ expect(parseSlugList("mirroring, tactical-empathy", ALLOWED)).toEqual(["mirroring", "tactical-empathy"]);
22
+ });
23
+ it("всё неразрешённое → []", () => {
24
+ expect(parseSlugList('["foo","bar"]', ALLOWED)).toEqual([]);
25
+ });
26
+ });
27
+
28
+ describe("gradeSkills", () => {
29
+ const base = { question: "q", reply: "r", availableSlugs: ALLOWED };
30
+ it("пустой availableSlugs → [] без LLM", async () => {
31
+ expect(await gradeSkills({ ...base, availableSlugs: [], chat: chatThrows() })).toEqual([]);
32
+ });
33
+ it("парсит и фильтрует ответ LLM", async () => {
34
+ expect(await gradeSkills({ ...base, chat: chat('["mirroring","x"]') })).toEqual(["mirroring"]);
35
+ });
36
+ it("LLM упал → [] (failure-soft)", async () => {
37
+ expect(await gradeSkills({ ...base, chat: chatThrows() })).toEqual([]);
38
+ });
39
+ });
@@ -0,0 +1,44 @@
1
+ // Unit tests for kb composeSystemPrompt — the prompt builder used by the reply
2
+ // pipeline (answer.ts). Focus: Phase 2 C-2 `stageOverride` precedence (the
3
+ // lead's funnel-stage goal/guidance wins over the Style's per-sales-stage cfg).
4
+
5
+ import { describe, expect, it } from "bun:test";
6
+ import { composeSystemPrompt } from "./prompt.ts";
7
+ import type { Style } from "./styles.ts";
8
+
9
+ const baseStyle: Style = {
10
+ slug: "t",
11
+ displayName: "T",
12
+ persona: { name: "Алекс", role: "human" },
13
+ voice: { tone: "friendly", language: "ru", forbid: [] },
14
+ framework: "SPIN",
15
+ hooks: [],
16
+ stages: { qualify: { goal: "STYLE_GOAL", groundingRequired: false } },
17
+ fewShot: [],
18
+ guardrails: { noMinors: true, botDisclosureOnDirectQuestion: true, forbiddenTopics: [] },
19
+ model: { id: "x", temperature: 0.5, maxTokens: 100 },
20
+ };
21
+
22
+ describe("composeSystemPrompt — stageOverride (Phase 2 C-2)", () => {
23
+ it("без override → goal берётся из Style", () => {
24
+ const p = composeSystemPrompt(baseStyle, "qualify");
25
+ expect(p).toContain("STYLE_GOAL");
26
+ });
27
+
28
+ it("stageOverride имеет приоритет над Style для goal и guidance", () => {
29
+ const p = composeSystemPrompt(baseStyle, "qualify", null, {
30
+ stageOverride: { goal: "OVERRIDE_GOAL", guidance: "OVERRIDE_GUIDE" },
31
+ });
32
+ expect(p).toContain("OVERRIDE_GOAL");
33
+ expect(p).toContain("OVERRIDE_GUIDE");
34
+ expect(p).not.toContain("STYLE_GOAL");
35
+ });
36
+
37
+ it("stageOverride работает для стадии без конфига в Style", () => {
38
+ // "close" нет в baseStyle.stages → раньше был бы generic-блок; теперь override.
39
+ const p = composeSystemPrompt(baseStyle, "close", null, {
40
+ stageOverride: { goal: "CLOSE_GOAL" },
41
+ });
42
+ expect(p).toContain("CLOSE_GOAL");
43
+ });
44
+ });
package/src/prompt.ts CHANGED
@@ -121,11 +121,15 @@ export function composeSystemPrompt(
121
121
  skillsForStage.map((s) => `- ${s.displayName} — ${s.promptFragment}`).join("\n")
122
122
  : "";
123
123
 
124
- const stageBlock = stageCfg
124
+ // Per-stage instructions: the lead's funnel-stage override (Phase 2) takes
125
+ // precedence over the Style's per-sales-stage config; grounding stays from style.
126
+ const effGoal = options.stageOverride?.goal ?? stageCfg?.goal;
127
+ const effGuidance = options.stageOverride?.guidance ?? stageCfg?.guidance;
128
+ const stageBlock = effGoal
125
129
  ? `ТЕКУЩИЙ ЭТАП: ${stage.toUpperCase()}.\n` +
126
- `ЦЕЛЬ ЭТАПА: ${stageCfg.goal}.` +
127
- (stageCfg.guidance ? `\nКАК: ${stageCfg.guidance}` : "") +
128
- (stageCfg.groundingRequired
130
+ `ЦЕЛЬ ЭТАПА: ${effGoal}.` +
131
+ (effGuidance ? `\nКАК: ${effGuidance}` : "") +
132
+ (stageCfg?.groundingRequired
129
133
  ? `\nGROUNDING: на этом этапе все конкретные факты (цифры, суммы, сроки) бери ТОЛЬКО из секции KB CONTEXT ниже. Если её нет или нужного факта в ней нет — не выдумывай, скажи что уточнишь.`
130
134
  : "")
131
135
  : `ТЕКУЩИЙ ЭТАП: ${stage}. (Специфических правил для этапа нет — используй общий стиль.)`;
@@ -0,0 +1,44 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { parseReflection, verifyAnswer } from "./reflect.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+
9
+ describe("parseReflection", () => {
10
+ it("нет {} → grounded:true", () => {
11
+ expect(parseReflection("plain")).toEqual({ grounded: true });
12
+ });
13
+ it("битый JSON → true", () => {
14
+ expect(parseReflection("{nope")).toEqual({ grounded: true });
15
+ });
16
+ it("grounded non-bool → true", () => {
17
+ expect(parseReflection('{"grounded":1}')).toEqual({ grounded: true });
18
+ });
19
+ it("grounded:true → {true}", () => {
20
+ expect(parseReflection('{"grounded":true}')).toEqual({ grounded: true });
21
+ });
22
+ it("grounded:false → {false, reason}", () => {
23
+ expect(parseReflection('{"grounded":false,"reason":"выдумал город"}')).toEqual({ grounded: false, reason: "выдумал город" });
24
+ });
25
+ it("grounded:false без reason → unknown", () => {
26
+ expect(parseReflection('{"grounded":false}')).toEqual({ grounded: false, reason: "unknown" });
27
+ });
28
+ });
29
+
30
+ describe("verifyAnswer", () => {
31
+ const base = { question: "q", answer: "Дубай 1500$", context: "Дубай 1500$" };
32
+ it("пустой ответ → grounded:true", async () => {
33
+ expect(await verifyAnswer({ ...base, answer: " ", chat: chatThrows() })).toEqual({ grounded: true });
34
+ });
35
+ it("нет контекста → grounded:true", async () => {
36
+ expect(await verifyAnswer({ ...base, context: "", chat: chatThrows() })).toEqual({ grounded: true });
37
+ });
38
+ it("LLM-вердикт false → {false,reason}", async () => {
39
+ expect(await verifyAnswer({ ...base, chat: chat('{"grounded":false,"reason":"r"}') })).toMatchObject({ grounded: false });
40
+ });
41
+ it("LLM упал → grounded:true (fail-open)", async () => {
42
+ expect(await verifyAnswer({ ...base, chat: chatThrows() })).toEqual({ grounded: true });
43
+ });
44
+ });
@@ -0,0 +1,56 @@
1
+ import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { questionNeedsRewrite, rewriteQuery, sanitizeRewritten } from "./rewrite-query.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+ const hist: ChatMessage[] = [{ role: "assistant", content: "в Дубае платят 1500" }];
9
+
10
+ describe("questionNeedsRewrite", () => {
11
+ it("пустой → false", () => {
12
+ expect(questionNeedsRewrite(" ", hist)).toBe(false);
13
+ });
14
+ it("нет истории → false", () => {
15
+ expect(questionNeedsRewrite("а там как?", [])).toBe(false);
16
+ });
17
+ it("короткий (<=4 слова) с историей → true", () => {
18
+ expect(questionNeedsRewrite("а виза как?", hist)).toBe(true);
19
+ });
20
+ it("дейктик-маркер → true", () => {
21
+ expect(questionNeedsRewrite("сколько стоит это оформление документов в итоге", hist)).toBe(true);
22
+ });
23
+ it("follow-up союз в начале → true", () => {
24
+ expect(questionNeedsRewrite("или есть другие варианты работы там сейчас", hist)).toBe(true);
25
+ });
26
+ it("длинный самостоятельный → false", () => {
27
+ expect(questionNeedsRewrite("сколько платят моделям в Дубае за месяц работы по контракту", hist)).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe("sanitizeRewritten", () => {
32
+ it("снимает think/fence/«ответ:» и берёт первую строку", () => {
33
+ expect(sanitizeRewritten('<think>..</think>ответ: какие условия в Дубае\nещё текст', "fb", 200)).toBe("какие условия в Дубае");
34
+ });
35
+ it("пусто/мусор → fallback", () => {
36
+ expect(sanitizeRewritten(" \n ", "ОРИГИНАЛ", 200)).toBe("ОРИГИНАЛ");
37
+ });
38
+ it("обрезка по maxLength", () => {
39
+ expect(sanitizeRewritten("абвгде", "fb", 3)).toBe("абв");
40
+ });
41
+ });
42
+
43
+ describe("rewriteQuery", () => {
44
+ it("пустой вопрос → как есть", async () => {
45
+ expect(await rewriteQuery({ question: " ", chat: chatThrows() })).toBe("");
46
+ });
47
+ it("не нужен рерайт (нет истории) → оригинал без вызова LLM", async () => {
48
+ expect(await rewriteQuery({ question: "а там?", chat: chatThrows() })).toBe("а там?");
49
+ });
50
+ it("нужен рерайт → sanitized из LLM", async () => {
51
+ expect(await rewriteQuery({ question: "а виза?", history: hist, chat: chat("как оформляется виза") })).toBe("как оформляется виза");
52
+ });
53
+ it("LLM упал → оригинал", async () => {
54
+ expect(await rewriteQuery({ question: "а виза?", history: hist, chat: chatThrows() })).toBe("а виза?");
55
+ });
56
+ });
package/src/styles.ts CHANGED
@@ -135,4 +135,10 @@ export interface ComposeOptions {
135
135
  * block instead. `docs` = collecting their documents, `submitted` = filed.
136
136
  */
137
137
  supportPhase?: "docs" | "submitted";
138
+ /**
139
+ * Per-stage override (Phase 2): goal/guidance from the lead's current funnel
140
+ * stage_definition. Takes precedence over `style.stages[stage]` for the stage
141
+ * block. Lets AI-built funnels carry per-stage instructions.
142
+ */
143
+ stageOverride?: { goal: string; guidance?: string };
138
144
  }