crewhaus 0.1.8 → 0.2.1

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 (138) hide show
  1. package/README.md +10 -3
  2. package/dist/advice-apply.d.ts +182 -0
  3. package/dist/advice-apply.js +286 -0
  4. package/dist/advise-rules.d.ts +348 -0
  5. package/dist/advise-rules.js +905 -0
  6. package/dist/alert-sink.d.ts +48 -0
  7. package/dist/alert-sink.js +86 -0
  8. package/dist/approval-gate.d.ts +127 -0
  9. package/dist/approval-gate.js +254 -0
  10. package/dist/audit-verify.d.ts +69 -0
  11. package/dist/audit-verify.js +97 -0
  12. package/dist/autodistill.d.ts +113 -0
  13. package/dist/autodistill.js +256 -0
  14. package/dist/channel-provision.d.ts +360 -0
  15. package/dist/channel-provision.js +881 -0
  16. package/dist/ci-scaffold.d.ts +31 -0
  17. package/dist/ci-scaffold.js +343 -0
  18. package/dist/compile-check.d.ts +90 -0
  19. package/dist/compile-check.js +285 -0
  20. package/dist/compliance-schedule.d.ts +35 -0
  21. package/dist/compliance-schedule.js +36 -0
  22. package/dist/context-pressure.d.ts +80 -0
  23. package/dist/context-pressure.js +166 -0
  24. package/dist/dataset-mine.d.ts +173 -0
  25. package/dist/dataset-mine.js +404 -0
  26. package/dist/datasets.d.ts +124 -0
  27. package/dist/datasets.js +260 -0
  28. package/dist/deploy-canary.d.ts +83 -0
  29. package/dist/deploy-canary.js +87 -0
  30. package/dist/doctor-checks.d.ts +33 -0
  31. package/dist/doctor-checks.js +92 -0
  32. package/dist/doctor-detect.d.ts +108 -0
  33. package/dist/doctor-detect.js +214 -0
  34. package/dist/doctor-fix.d.ts +81 -0
  35. package/dist/doctor-fix.js +164 -0
  36. package/dist/egress-triage.d.ts +121 -0
  37. package/dist/egress-triage.js +261 -0
  38. package/dist/eval-bridge.d.ts +114 -0
  39. package/dist/eval-bridge.js +158 -0
  40. package/dist/eval-coverage.d.ts +140 -0
  41. package/dist/eval-coverage.js +428 -0
  42. package/dist/eval-history.d.ts +48 -0
  43. package/dist/eval-history.js +157 -0
  44. package/dist/eval-matrix.d.ts +80 -0
  45. package/dist/eval-matrix.js +182 -0
  46. package/dist/eval-sentinel.d.ts +65 -0
  47. package/dist/eval-sentinel.js +132 -0
  48. package/dist/faq.d.ts +68 -0
  49. package/dist/faq.js +168 -0
  50. package/dist/feedback.d.ts +9 -2
  51. package/dist/feedback.js +17 -7
  52. package/dist/fewshot.d.ts +83 -0
  53. package/dist/fewshot.js +158 -0
  54. package/dist/fleet.d.ts +207 -0
  55. package/dist/fleet.js +488 -0
  56. package/dist/flywheel.d.ts +193 -0
  57. package/dist/flywheel.js +519 -0
  58. package/dist/graders-suggest.d.ts +186 -0
  59. package/dist/graders-suggest.js +658 -0
  60. package/dist/incident.d.ts +99 -0
  61. package/dist/incident.js +217 -0
  62. package/dist/index.d.ts +9 -1
  63. package/dist/index.js +11664 -963
  64. package/dist/init-interactive.d.ts +105 -0
  65. package/dist/init-interactive.js +208 -0
  66. package/dist/intents.d.ts +105 -0
  67. package/dist/intents.js +292 -0
  68. package/dist/judge-calibrate.d.ts +137 -0
  69. package/dist/judge-calibrate.js +247 -0
  70. package/dist/justification-calibrate.d.ts +150 -0
  71. package/dist/justification-calibrate.js +262 -0
  72. package/dist/justification-gate.d.ts +27 -6
  73. package/dist/justification-gate.js +30 -6
  74. package/dist/knowledge-sync.d.ts +179 -0
  75. package/dist/knowledge-sync.js +551 -0
  76. package/dist/lessons.d.ts +87 -0
  77. package/dist/lessons.js +207 -0
  78. package/dist/lint.d.ts +127 -0
  79. package/dist/lint.js +226 -0
  80. package/dist/loadtest.d.ts +114 -0
  81. package/dist/loadtest.js +196 -0
  82. package/dist/marketplace-cli.d.ts +110 -0
  83. package/dist/marketplace-cli.js +250 -0
  84. package/dist/mcp-doctor.d.ts +121 -0
  85. package/dist/mcp-doctor.js +249 -0
  86. package/dist/model-scan.d.ts +116 -0
  87. package/dist/model-scan.js +226 -0
  88. package/dist/onchain-tune.d.ts +164 -0
  89. package/dist/onchain-tune.js +346 -0
  90. package/dist/permissions-suggest.d.ts +126 -0
  91. package/dist/permissions-suggest.js +333 -0
  92. package/dist/pii-tune.d.ts +107 -0
  93. package/dist/pii-tune.js +122 -0
  94. package/dist/propose.d.ts +117 -0
  95. package/dist/propose.js +184 -0
  96. package/dist/refresh-goldens.d.ts +82 -0
  97. package/dist/refresh-goldens.js +221 -0
  98. package/dist/regression-pin.d.ts +160 -0
  99. package/dist/regression-pin.js +281 -0
  100. package/dist/retention.d.ts +193 -0
  101. package/dist/retention.js +607 -0
  102. package/dist/retire.d.ts +118 -0
  103. package/dist/retire.js +291 -0
  104. package/dist/right-size.d.ts +100 -0
  105. package/dist/right-size.js +123 -0
  106. package/dist/route.d.ts +19 -0
  107. package/dist/route.js +90 -0
  108. package/dist/scaffold-evals.d.ts +138 -0
  109. package/dist/scaffold-evals.js +410 -0
  110. package/dist/scope-audit-drift.d.ts +139 -0
  111. package/dist/scope-audit-drift.js +260 -0
  112. package/dist/security-corpus.d.ts +237 -0
  113. package/dist/security-corpus.js +516 -0
  114. package/dist/security-digest.d.ts +174 -0
  115. package/dist/security-digest.js +656 -0
  116. package/dist/sessions-index.d.ts +27 -0
  117. package/dist/sessions-index.js +51 -0
  118. package/dist/slo-doctor.d.ts +67 -0
  119. package/dist/slo-doctor.js +119 -0
  120. package/dist/slo-sink.d.ts +96 -0
  121. package/dist/slo-sink.js +107 -0
  122. package/dist/spec-changelog.d.ts +102 -0
  123. package/dist/spec-changelog.js +237 -0
  124. package/dist/state-backup.d.ts +223 -0
  125. package/dist/state-backup.js +648 -0
  126. package/dist/tools-cli.d.ts +170 -0
  127. package/dist/tools-cli.js +300 -0
  128. package/dist/triage.d.ts +202 -0
  129. package/dist/triage.js +403 -0
  130. package/dist/upgrade.d.ts +57 -0
  131. package/dist/upgrade.js +113 -0
  132. package/dist/version.d.ts +6 -0
  133. package/dist/version.js +27 -0
  134. package/dist/voice-eval.d.ts +138 -0
  135. package/dist/voice-eval.js +309 -0
  136. package/dist/watch.d.ts +58 -0
  137. package/dist/watch.js +97 -0
  138. package/package.json +90 -65
@@ -0,0 +1,105 @@
1
+ import { type Spec, SpecParseError } from "@crewhaus/spec";
2
+ /**
3
+ * Item 39 — `crewhaus init --interactive`. Folds the demos harness-designer
4
+ * pattern (an agent that interviews the user in plain English and emits a
5
+ * validated crewhaus.yaml) into core. Two design commitments:
6
+ *
7
+ * 1. NO dependency on the demos repo. The shape-decision guidance is
8
+ * bundled inline here ({@link SHAPE_GUIDANCE} / {@link buildInterviewSystemPrompt}),
9
+ * and validation is `parseSpec` called in-process — the single source of
10
+ * truth for what compiles.
11
+ * 2. Every draft the model emits is validated against the LIVE Zod union via
12
+ * `parseSpec`, and on a structured error the error text is fed back for a
13
+ * retry. So `init --interactive` cannot emit a spec that won't parse.
14
+ *
15
+ * Everything here is side-effect-free and unit-testable: the interview loop is
16
+ * driven by an INJECTED `proposeSpec` callback (the CLI wires the model
17
+ * adapter behind it), and the credential-free fallback is a pure
18
+ * {@link buildScriptedSpec} over stdin answers. The CLI wrapper in `index.ts`
19
+ * owns all I/O.
20
+ */
21
+ /** The fourteen target shapes, with the one-line "pick me when" guidance the
22
+ * interviewer uses. Bundled inline so core never reaches into the demos repo. */
23
+ export declare const SHAPE_GUIDANCE: ReadonlyArray<{
24
+ target: string;
25
+ when: string;
26
+ }>;
27
+ /** Shapes the scripted (no-credentials) fallback can emit end-to-end without
28
+ * additional required blocks the user would have to hand-author. cli is the
29
+ * overwhelmingly common case; workflow + research + eval are the other simple
30
+ * single-agent shapes whose minimal spec is fully derivable from a few answers. */
31
+ export declare const SCRIPTED_SHAPES: readonly ["cli", "workflow", "research"];
32
+ export type ScriptedShape = (typeof SCRIPTED_SHAPES)[number];
33
+ export declare function isScriptedShape(s: string): s is ScriptedShape;
34
+ /**
35
+ * The interviewer's system prompt. Bundles the shape catalog + the hard rules
36
+ * the emitted YAML must obey (single-line-safe names, the `$UPPER_SNAKE_CASE`
37
+ * secret convention, no `permissions.mode: bypass`). Kept as a builder so a
38
+ * test can assert the guidance is present without shipping a golden file.
39
+ */
40
+ export declare function buildInterviewSystemPrompt(): string;
41
+ /** The tool the interviewer calls to submit a candidate spec. Exported so the
42
+ * CLI wires the same schema into the adapter request. */
43
+ export declare const EMIT_SPEC_TOOL: {
44
+ name: string;
45
+ description: string;
46
+ input_schema: {
47
+ type: "object";
48
+ properties: {
49
+ yaml: {
50
+ type: "string";
51
+ description: string;
52
+ };
53
+ };
54
+ required: string[];
55
+ };
56
+ };
57
+ /** One turn of the interview: the injected model proposes a spec (the `yaml`
58
+ * string it passed to `emit_spec`), given the running feedback log of prior
59
+ * validation errors. Returns undefined if the model declined to emit. */
60
+ export type ProposeSpec = (feedback: readonly string[]) => Promise<string | undefined>;
61
+ export type InterviewResult = {
62
+ readonly yaml: string;
63
+ readonly spec: Spec;
64
+ /** How many drafts were tried before one validated (1 = first try). */
65
+ readonly attempts: number;
66
+ };
67
+ export declare class InterviewError extends SpecParseError {
68
+ }
69
+ /**
70
+ * Drive the validate-and-retry interview loop. Each attempt asks the injected
71
+ * `proposeSpec` for a draft, runs it through `parseSpec` (the LIVE union), and
72
+ * on a `SpecParseError` appends the error to the feedback log for the next
73
+ * attempt. Succeeds on the first draft that validates; throws
74
+ * {@link InterviewError} after `maxAttempts` failed drafts (or if the model
75
+ * declines to emit). Pure given its `proposeSpec` seam — no I/O.
76
+ */
77
+ export declare function runInterview(opts: {
78
+ readonly proposeSpec: ProposeSpec;
79
+ readonly maxAttempts?: number;
80
+ }): Promise<InterviewResult>;
81
+ /** The answers the scripted (no-credentials) questionnaire collects. Every
82
+ * field is a plain string/list so the CLI can gather them over stdin and a
83
+ * test can drive {@link buildScriptedSpec} directly. */
84
+ export type ScriptedAnswers = {
85
+ readonly name: string;
86
+ readonly shape: ScriptedShape;
87
+ readonly model: string;
88
+ readonly instructions: string;
89
+ /** Tool names for cli/research; ignored for workflow. */
90
+ readonly tools?: readonly string[];
91
+ /** The research goal (research shape only). */
92
+ readonly goal?: string;
93
+ };
94
+ /**
95
+ * Build a minimal, VALIDATED crewhaus.yaml from scripted answers — the
96
+ * credential-free degrade path. Emits the spec text, runs it through
97
+ * `parseSpec` (so even the scripted path can't produce an invalid spec), and
98
+ * returns both. Throws `SpecParseError` if the answers somehow don't validate
99
+ * (e.g. an unsafe name), which the CLI surfaces so the user can retry the
100
+ * offending answer.
101
+ */
102
+ export declare function buildScriptedSpec(answers: ScriptedAnswers): {
103
+ yaml: string;
104
+ spec: Spec;
105
+ };
@@ -0,0 +1,208 @@
1
+ import { SpecParseError, parseSpec } from "@crewhaus/spec";
2
+ /**
3
+ * Item 39 — `crewhaus init --interactive`. Folds the demos harness-designer
4
+ * pattern (an agent that interviews the user in plain English and emits a
5
+ * validated crewhaus.yaml) into core. Two design commitments:
6
+ *
7
+ * 1. NO dependency on the demos repo. The shape-decision guidance is
8
+ * bundled inline here ({@link SHAPE_GUIDANCE} / {@link buildInterviewSystemPrompt}),
9
+ * and validation is `parseSpec` called in-process — the single source of
10
+ * truth for what compiles.
11
+ * 2. Every draft the model emits is validated against the LIVE Zod union via
12
+ * `parseSpec`, and on a structured error the error text is fed back for a
13
+ * retry. So `init --interactive` cannot emit a spec that won't parse.
14
+ *
15
+ * Everything here is side-effect-free and unit-testable: the interview loop is
16
+ * driven by an INJECTED `proposeSpec` callback (the CLI wires the model
17
+ * adapter behind it), and the credential-free fallback is a pure
18
+ * {@link buildScriptedSpec} over stdin answers. The CLI wrapper in `index.ts`
19
+ * owns all I/O.
20
+ */
21
+ /** The fourteen target shapes, with the one-line "pick me when" guidance the
22
+ * interviewer uses. Bundled inline so core never reaches into the demos repo. */
23
+ export const SHAPE_GUIDANCE = [
24
+ {
25
+ target: "cli",
26
+ when: "a single interactive chat/coding agent run from a terminal (the default)",
27
+ },
28
+ { target: "workflow", when: "a fixed sequence of steps, each a prompt, threaded in order" },
29
+ { target: "channel", when: "a long-running bot that answers Slack/Telegram/Discord messages" },
30
+ { target: "graph", when: "a stateful DAG of LLM nodes with human-in-the-loop pause points" },
31
+ { target: "managed", when: "a multi-tenant gateway daemon with per-tenant token budgets" },
32
+ {
33
+ target: "pipeline",
34
+ when: "RAG: index documents into a vector store, then chat with retrieval",
35
+ },
36
+ {
37
+ target: "crew",
38
+ when: "multiple named roles that hand off to each other (router or match rules)",
39
+ },
40
+ { target: "research", when: "decompose a goal into sub-questions and write a cited report" },
41
+ { target: "batch", when: "a queue worker that runs the agent on each job with idempotency" },
42
+ { target: "voice", when: "a realtime audio agent (OpenAI realtime / Vapi) over telephony" },
43
+ { target: "browser", when: "a computer-use agent that drives a real browser" },
44
+ { target: "eval", when: "run an agent against a dataset and grade it (not a live agent)" },
45
+ { target: "onchain", when: "an event-driven daemon that reacts to on-chain triggers with txns" },
46
+ { target: "onchain-game", when: "a perceive-act loop against a game contract" },
47
+ ];
48
+ /** Shapes the scripted (no-credentials) fallback can emit end-to-end without
49
+ * additional required blocks the user would have to hand-author. cli is the
50
+ * overwhelmingly common case; workflow + research + eval are the other simple
51
+ * single-agent shapes whose minimal spec is fully derivable from a few answers. */
52
+ export const SCRIPTED_SHAPES = ["cli", "workflow", "research"];
53
+ export function isScriptedShape(s) {
54
+ return SCRIPTED_SHAPES.includes(s);
55
+ }
56
+ /**
57
+ * The interviewer's system prompt. Bundles the shape catalog + the hard rules
58
+ * the emitted YAML must obey (single-line-safe names, the `$UPPER_SNAKE_CASE`
59
+ * secret convention, no `permissions.mode: bypass`). Kept as a builder so a
60
+ * test can assert the guidance is present without shipping a golden file.
61
+ */
62
+ export function buildInterviewSystemPrompt() {
63
+ const shapes = SHAPE_GUIDANCE.map((s) => ` - ${s.target}: ${s.when}`).join("\n");
64
+ return [
65
+ "You are the CrewHaus harness designer. Interview the user in plain English to",
66
+ "understand what agent they want to build, then emit a single valid crewhaus.yaml",
67
+ "by calling the `emit_spec` tool with the full YAML as its `yaml` argument.",
68
+ "",
69
+ "Pick exactly ONE target shape:",
70
+ shapes,
71
+ "",
72
+ "Hard rules the YAML MUST obey (the validator rejects violations):",
73
+ ' - `name` and every role/step/node name: letters, digits, spaces, and "_ . - :" only',
74
+ " (no newlines, quotes, slashes, or comment/template delimiters).",
75
+ " - Secrets (tokens, API keys, channel credentials) MUST be written as a $UPPER_SNAKE_CASE",
76
+ " environment reference (e.g. $SLACK_BOT_TOKEN), NEVER a literal secret value.",
77
+ " - Do NOT set `permissions.mode: bypass` — it is rejected; use default/plan/auto.",
78
+ " - For a cli target: top-level `name`, `target: cli`, and an `agent` block with",
79
+ " `model` and `instructions`. Tools go in a top-level `tools:` list of names.",
80
+ " - Keep the spec minimal — only the blocks the user actually asked for.",
81
+ "",
82
+ "When the validator reports an error on your draft, FIX exactly that error and re-emit",
83
+ "— do not restart the interview.",
84
+ ].join("\n");
85
+ }
86
+ /** The tool the interviewer calls to submit a candidate spec. Exported so the
87
+ * CLI wires the same schema into the adapter request. */
88
+ export const EMIT_SPEC_TOOL = {
89
+ name: "emit_spec",
90
+ description: "Submit the complete crewhaus.yaml as a single YAML string. The validator parses it " +
91
+ "immediately; if it fails you will be told the exact error and must re-emit a corrected spec.",
92
+ input_schema: {
93
+ type: "object",
94
+ properties: {
95
+ yaml: { type: "string", description: "The full crewhaus.yaml document." },
96
+ },
97
+ required: ["yaml"],
98
+ },
99
+ };
100
+ export class InterviewError extends SpecParseError {
101
+ }
102
+ /**
103
+ * Drive the validate-and-retry interview loop. Each attempt asks the injected
104
+ * `proposeSpec` for a draft, runs it through `parseSpec` (the LIVE union), and
105
+ * on a `SpecParseError` appends the error to the feedback log for the next
106
+ * attempt. Succeeds on the first draft that validates; throws
107
+ * {@link InterviewError} after `maxAttempts` failed drafts (or if the model
108
+ * declines to emit). Pure given its `proposeSpec` seam — no I/O.
109
+ */
110
+ export async function runInterview(opts) {
111
+ const maxAttempts = opts.maxAttempts ?? 4;
112
+ const feedback = [];
113
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
114
+ const yaml = await opts.proposeSpec(feedback);
115
+ if (yaml === undefined) {
116
+ throw new InterviewError("the interviewer did not emit a spec (no emit_spec tool call) — try a more concrete description");
117
+ }
118
+ try {
119
+ const spec = parseSpec(yaml);
120
+ return { yaml, spec, attempts: attempt };
121
+ }
122
+ catch (err) {
123
+ if (err instanceof SpecParseError) {
124
+ feedback.push(err.message);
125
+ continue;
126
+ }
127
+ throw err;
128
+ }
129
+ }
130
+ throw new InterviewError(`interviewer failed to produce a valid spec after ${maxAttempts} attempts. Last errors:\n${feedback
131
+ .map((f) => ` - ${f}`)
132
+ .join("\n")}`);
133
+ }
134
+ /** YAML-quote a scalar that might contain special characters. The scripted
135
+ * answers are user free-text, so a name/model with a colon or `#` must be
136
+ * double-quoted to parse. Instructions use a block scalar (see below). */
137
+ function yamlScalar(value) {
138
+ // Safe bareword: no YAML-significant leading/embedded chars.
139
+ if (/^[A-Za-z0-9_][\w .-]*$/.test(value) && !/[:#]/.test(value))
140
+ return value;
141
+ return JSON.stringify(value); // JSON string is valid YAML double-quoted scalar.
142
+ }
143
+ /** Render a possibly-multiline instructions string as a literal block scalar. */
144
+ function yamlBlock(key, value, indent) {
145
+ const lines = value.split("\n");
146
+ const body = lines.map((l) => `${indent} ${l}`).join("\n");
147
+ return `${indent}${key}: |\n${body}`;
148
+ }
149
+ /**
150
+ * Build a minimal, VALIDATED crewhaus.yaml from scripted answers — the
151
+ * credential-free degrade path. Emits the spec text, runs it through
152
+ * `parseSpec` (so even the scripted path can't produce an invalid spec), and
153
+ * returns both. Throws `SpecParseError` if the answers somehow don't validate
154
+ * (e.g. an unsafe name), which the CLI surfaces so the user can retry the
155
+ * offending answer.
156
+ */
157
+ export function buildScriptedSpec(answers) {
158
+ let yaml;
159
+ if (answers.shape === "workflow") {
160
+ yaml = [
161
+ `name: ${yamlScalar(answers.name)}`,
162
+ "target: workflow",
163
+ `model: ${yamlScalar(answers.model)}`,
164
+ "steps:",
165
+ ` - name: ${yamlScalar(answers.name)}-step`,
166
+ // The step's `instructions:` is nested one list-item deep, so the block
167
+ // scalar indents at 4 spaces (2 for the list item + 2 for the map key).
168
+ yamlBlock("instructions", answers.instructions, " "),
169
+ "",
170
+ ].join("\n");
171
+ }
172
+ else if (answers.shape === "research") {
173
+ const toolsBlock = answers.tools && answers.tools.length > 0
174
+ ? `tools:\n${answers.tools.map((t) => ` - ${yamlScalar(t)}`).join("\n")}\n`
175
+ : "";
176
+ yaml = [
177
+ `name: ${yamlScalar(answers.name)}`,
178
+ "target: research",
179
+ "agent:",
180
+ ` model: ${yamlScalar(answers.model)}`,
181
+ yamlBlock("instructions", answers.instructions, " "),
182
+ `goal: ${yamlScalar(answers.goal ?? answers.instructions)}`,
183
+ toolsBlock.trimEnd(),
184
+ "",
185
+ ]
186
+ .filter((l) => l !== "")
187
+ .join("\n");
188
+ }
189
+ else {
190
+ // cli (default)
191
+ const toolsBlock = answers.tools && answers.tools.length > 0
192
+ ? `tools:\n${answers.tools.map((t) => ` - ${yamlScalar(t)}`).join("\n")}\n`
193
+ : "";
194
+ yaml = [
195
+ `name: ${yamlScalar(answers.name)}`,
196
+ "target: cli",
197
+ "agent:",
198
+ ` model: ${yamlScalar(answers.model)}`,
199
+ yamlBlock("instructions", answers.instructions, " "),
200
+ toolsBlock.trimEnd(),
201
+ "",
202
+ ]
203
+ .filter((l) => l !== "")
204
+ .join("\n");
205
+ }
206
+ const spec = parseSpec(yaml);
207
+ return { yaml, spec };
208
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Item 67 — `crewhaus intents`: end-user intent analytics. What are people
3
+ * actually asking the harness, and how well is it serving them?
4
+ *
5
+ * Clusters `user_message` inputs across sessions with the SAME deterministic
6
+ * greedy-Jaccard token clustering the `faq` / `graders suggest` / coverage
7
+ * features use (no model call), then ranks each intent cluster by:
8
+ *
9
+ * - FREQUENCY: total occurrences + distinct sessions.
10
+ * - SATISFACTION: mean normalized rating of the cluster's rated turns.
11
+ * - FAILURE RATE: share of the cluster's turns that hit a struggle signal
12
+ * (error / tool-error / loop / retry — mined by the same
13
+ * `dataset mine` signals), i.e. an UNMET intent.
14
+ * - RECENCY: first vs second half of the session window → a "rising"
15
+ * intent asked much more in the recent half.
16
+ *
17
+ * From those it surfaces four views: top intents, rising intents, low-
18
+ * satisfaction intents (down-rated / high-error), and unmet intents (the agent
19
+ * couldn't answer — errors/loops/retries). This is the product-analytics feed
20
+ * for `dataset mine` / `faq distill` / few-shot harvest.
21
+ *
22
+ * Everything here is pure + deterministic (the CLI entry file reads sessions +
23
+ * feedback and PII/secret-redacts the rendered examples via a passed-in
24
+ * callback). No credential/secret text enters a rendered example un-redacted.
25
+ */
26
+ import type { FeedbackRecord, LoggedEvent, SessionTurn } from "./feedback";
27
+ /** Thrown on malformed inputs / bad flags. The CLI routes it through `die()`. */
28
+ export declare class IntentsError extends Error {
29
+ readonly name = "IntentsError";
30
+ }
31
+ /** A per-turn struggle signal, keyed by session#turn — the same set `dataset
32
+ * mine` recognizes. Supplied by the CLI (from `mineSession`). */
33
+ export type TurnSignal = {
34
+ readonly sessionId: string;
35
+ readonly turnNumber: number;
36
+ };
37
+ /** One user turn tagged with recency (for the rising view) — a SessionTurn plus
38
+ * a monotonically increasing global ordinal across the scanned window. */
39
+ export type OrderedTurn = SessionTurn & {
40
+ /** 0-based position in the chronological scan order (older = smaller). */
41
+ readonly order: number;
42
+ };
43
+ /** One ranked intent cluster. */
44
+ export type Intent = {
45
+ /** The representative question (most-frequent-token, then shortest). */
46
+ readonly representative: string;
47
+ /** Total user turns in this cluster. */
48
+ readonly occurrences: number;
49
+ /** Distinct sessions that asked a question in this cluster. */
50
+ readonly sessionCount: number;
51
+ /** Mean normalized rating [0,1] over rated turns; undefined when none rated. */
52
+ readonly meanRating: number | undefined;
53
+ /** Number of the cluster's turns that hit a struggle signal. */
54
+ readonly failedTurns: number;
55
+ /** failedTurns / occurrences. */
56
+ readonly failureRate: number;
57
+ /** Occurrences in the OLDER half of the window. */
58
+ readonly earlyCount: number;
59
+ /** Occurrences in the RECENT half of the window. */
60
+ readonly recentCount: number;
61
+ /** A few representative example questions (caller redacts before render). */
62
+ readonly examples: readonly string[];
63
+ };
64
+ export type IntentDigest = {
65
+ readonly totalTurns: number;
66
+ readonly totalSessions: number;
67
+ readonly intents: readonly Intent[];
68
+ readonly topIntents: readonly Intent[];
69
+ readonly risingIntents: readonly Intent[];
70
+ readonly lowSatisfactionIntents: readonly Intent[];
71
+ readonly unmetIntents: readonly Intent[];
72
+ };
73
+ export type ClusterIntentsOptions = {
74
+ /** Jaccard overlap at/above which a question joins a cluster seed. Default 0.5. */
75
+ readonly threshold?: number;
76
+ /** Rating below which an intent counts as "low satisfaction". Default 0.5. */
77
+ readonly lowSatisfactionBelow?: number;
78
+ /** Failure rate at/above which an intent counts as "unmet". Default 0.34. */
79
+ readonly unmetFailureAtLeast?: number;
80
+ /** How many top/rising/etc. to surface in each view. Default 5. */
81
+ readonly topN?: number;
82
+ /** Max example questions kept per intent. Default 3. */
83
+ readonly examplesPerIntent?: number;
84
+ };
85
+ /**
86
+ * Cluster ordered user turns into ranked intents. Deterministic: turns are
87
+ * clustered greedily by normalized-token Jaccard overlap in `order` sequence;
88
+ * each cluster's representative is the highest-token-frequency question (ties →
89
+ * shortest, then lexicographic). The recency split is the median `order`.
90
+ */
91
+ export declare function clusterIntents(turns: ReadonlyArray<OrderedTurn>, feedback: ReadonlyArray<FeedbackRecord>, failedTurnKeys: ReadonlyArray<TurnSignal>, opts?: ClusterIntentsOptions): IntentDigest;
92
+ /** Build ordered turns from per-session events, oldest session first. The
93
+ * caller passes sessions already in chronological order (oldest→newest). */
94
+ export declare function orderedTurnsFromSessions(perSession: ReadonlyArray<{
95
+ sessionId: string;
96
+ events: ReadonlyArray<LoggedEvent>;
97
+ }>, deriveTurns: (events: ReadonlyArray<LoggedEvent>) => Array<Omit<SessionTurn, "sessionId">>): OrderedTurn[];
98
+ /** Apply the redactor to every rendered string in the digest. */
99
+ export declare function redactDigest(digest: IntentDigest, redact: (s: string) => string): IntentDigest;
100
+ /** Render the digest as plain text. */
101
+ export declare function renderIntentsText(digest: IntentDigest): string;
102
+ /** Render the digest as pretty JSON. */
103
+ export declare function renderIntentsJson(digest: IntentDigest): string;
104
+ /** Render the digest as a self-contained HTML report. */
105
+ export declare function renderIntentsHtml(digest: IntentDigest): string;