@hegel-dev/companion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +84 -0
  2. package/dist/analyzers/prompt-analyzer.d.ts +7 -0
  3. package/dist/analyzers/prompt-analyzer.d.ts.map +1 -0
  4. package/dist/analyzers/prompt-analyzer.js +224 -0
  5. package/dist/analyzers/prompt-analyzer.js.map +1 -0
  6. package/dist/analyzers/response-analyzer.d.ts +6 -0
  7. package/dist/analyzers/response-analyzer.d.ts.map +1 -0
  8. package/dist/analyzers/response-analyzer.js +218 -0
  9. package/dist/analyzers/response-analyzer.js.map +1 -0
  10. package/dist/analyzers/session-analyzer.d.ts +10 -0
  11. package/dist/analyzers/session-analyzer.d.ts.map +1 -0
  12. package/dist/analyzers/session-analyzer.js +194 -0
  13. package/dist/analyzers/session-analyzer.js.map +1 -0
  14. package/dist/config.d.ts +11 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +27 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/debug-wrapper.d.ts +2 -0
  19. package/dist/debug-wrapper.d.ts.map +1 -0
  20. package/dist/debug-wrapper.js +55 -0
  21. package/dist/debug-wrapper.js.map +1 -0
  22. package/dist/dev-watch.d.ts +10 -0
  23. package/dist/dev-watch.d.ts.map +1 -0
  24. package/dist/dev-watch.js +55 -0
  25. package/dist/dev-watch.js.map +1 -0
  26. package/dist/format.d.ts +4 -0
  27. package/dist/format.d.ts.map +1 -0
  28. package/dist/format.js +38 -0
  29. package/dist/format.js.map +1 -0
  30. package/dist/hook.d.ts +8 -0
  31. package/dist/hook.d.ts.map +1 -0
  32. package/dist/hook.js +320 -0
  33. package/dist/hook.js.map +1 -0
  34. package/dist/hooks-generator.d.ts +45 -0
  35. package/dist/hooks-generator.d.ts.map +1 -0
  36. package/dist/hooks-generator.js +120 -0
  37. package/dist/hooks-generator.js.map +1 -0
  38. package/dist/mcp.d.ts +10 -0
  39. package/dist/mcp.d.ts.map +1 -0
  40. package/dist/mcp.js +173 -0
  41. package/dist/mcp.js.map +1 -0
  42. package/dist/prompts.d.ts +11 -0
  43. package/dist/prompts.d.ts.map +1 -0
  44. package/dist/prompts.js +85 -0
  45. package/dist/prompts.js.map +1 -0
  46. package/dist/setup.d.ts +28 -0
  47. package/dist/setup.d.ts.map +1 -0
  48. package/dist/setup.js +247 -0
  49. package/dist/setup.js.map +1 -0
  50. package/dist/state.d.ts +14 -0
  51. package/dist/state.d.ts.map +1 -0
  52. package/dist/state.js +77 -0
  53. package/dist/state.js.map +1 -0
  54. package/dist/transcript.d.ts +32 -0
  55. package/dist/transcript.d.ts.map +1 -0
  56. package/dist/transcript.js +156 -0
  57. package/dist/transcript.js.map +1 -0
  58. package/dist/types.d.ts +137 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +3 -0
  61. package/dist/types.js.map +1 -0
  62. package/hegel-vscode/hegel-companion-1.0.0.vsix +0 -0
  63. package/hegel.config.schema.json +63 -0
  64. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Hegel Companion
2
+
3
+ **Dialectical companion for AI-assisted development.**
4
+
5
+ Hegel sits alongside your Cursor IDE sessions and provides real-time critical thinking oversight — catching lazy prompts, flagging overconfident AI responses, tracking session quality degradation, and nudging you to maintain engineering rigor.
6
+
7
+ Named after Georg Wilhelm Friedrich Hegel's dialectical method: every thesis (your prompt) deserves an antithesis (critical review) before reaching synthesis (better code).
8
+
9
+ ## Two-Layer Analysis
10
+
11
+ ### Layer 1: Fast Rule-Based Checks (command hooks)
12
+ Runs in milliseconds, zero cost. Always active.
13
+
14
+ - Detects vague/lazy prompts ("fix it", "do the same", single-word confirmations)
15
+ - Catches prompt quality degradation over the session
16
+ - Flags rapid-fire prompting without review pauses
17
+ - Tracks scope creep and untested changes
18
+ - Detects overconfident AI language and sycophancy patterns
19
+ - Session fatigue warnings
20
+
21
+ ### Layer 2: LLM Deep Analysis (prompt hooks)
22
+ Uses your Cursor subscription models. Optional, configurable.
23
+
24
+ - Nuanced prompt quality evaluation beyond pattern matching
25
+ - Contextual assessment of whether a prompt has enough detail for its intent
26
+ - AI response review for missing edge cases, security blind spots, and scope creep
27
+ - Detects when you're blindly continuing from a previous AI response
28
+
29
+ ## Setup
30
+
31
+ ### Install via npm
32
+
33
+ Install the package and run the setup CLI from your Cursor project root:
34
+
35
+ ```bash
36
+ npm install --save-dev @hegel-dev/companion
37
+ npx hegel-companion init .
38
+ ```
39
+
40
+ Or, for a one-shot install without adding a dependency:
41
+
42
+ ```bash
43
+ npx @hegel-dev/companion init .
44
+ ```
45
+
46
+ > **Note:** This package was previously published as `hegel-companion`. That name is now deprecated — use `@hegel-dev/companion` for new installs. The CLI command (`hegel-companion`) is unchanged.
47
+
48
+ This single command will:
49
+ 1. Generate `.cursor/hooks.json` to wire up the analysis layers.
50
+ 2. Scaffold a default `hegel.config.json` in your project root.
51
+ 3. Register the `hegel-mcp` server in `.cursor/mcp.json`.
52
+ 4. Install the Hegel Cursor extension for the sidebar dashboard.
53
+ 5. Add a `.cursor/rules/hegel-companion.mdc` rule so the AI knows how to use the MCP tools.
54
+
55
+ ### Configure
56
+
57
+ The easiest way to configure Hegel is through the **Cursor Settings UI**:
58
+
59
+ 1. Open Settings (`Cmd/Ctrl + ,`)
60
+ 2. Search for **"Hegel"**
61
+ 3. Adjust your model, strictness, and other preferences.
62
+
63
+ These settings automatically sync to a `hegel.config.json` file in your project root, which you can commit to version control to share team-wide standards.
64
+
65
+ *(Note: Configuration changes are automatically detected when you start a new chat session).*
66
+
67
+ ## Architecture
68
+
69
+ Hegel operates as a local, privacy-first system:
70
+ - **Hooks**: Intercepts prompts and responses via Cursor's `.cursor/hooks.json`.
71
+ - **State**: Session state is stored locally in `.hegel-state/` as JSON files.
72
+ - **MCP Server**: Provides `hegel-status` and `hegel-review` tools to the AI.
73
+ - **Cursor Extension**: Reads the local state to power the sidebar dashboard and status bar.
74
+
75
+ ## Philosophy
76
+
77
+ > "The owl of Minerva spreads its wings only with the falling of the dusk."
78
+ > — Hegel, *Philosophy of Right*
79
+
80
+ Unlike Minerva's owl, Hegel doesn't wait for dusk. It watches in real time, helping you think critically *during* the creative process, not only in retrospect.
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,7 @@
1
+ import type { Concern, SessionState, PromptRecord, TranscriptContext } from "../types.js";
2
+ export declare function analyzePrompt(prompt: string, state: SessionState, transcript?: TranscriptContext | null): {
3
+ concerns: Concern[];
4
+ shouldBlock: boolean;
5
+ };
6
+ export declare function buildPromptRecord(prompt: string, concerns: Concern[]): PromptRecord;
7
+ //# sourceMappingURL=prompt-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/prompt-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAgC1F,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,EACnB,UAAU,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACpC;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CA4L/C;AAmBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,GAClB,YAAY,CAOd"}
@@ -0,0 +1,224 @@
1
+ import { recentPrompts, sessionDurationMinutes } from "../state.js";
2
+ import { isContextualFollowUp, lastResponseInvitesFollowUp, recentUserTurns } from "../transcript.js";
3
+ // ── Pattern Definitions ──
4
+ const LAZY_PATTERNS = [
5
+ { pattern: /^(fix it|fix this|do it|make it work|same thing|again)\.?$/i, label: "extremely vague directive" },
6
+ { pattern: /^(yes|no|ok|sure|go ahead|yep|yeah|continue|proceed)\.?$/i, label: "single-word confirmation without context" },
7
+ { pattern: /^do the same (for|with|to)/i, label: "'do the same' without specifying what" },
8
+ { pattern: /^(now do|and also|next)\b.{0,20}$/i, label: "short chained request lacking self-contained context" },
9
+ ];
10
+ const MISSING_CONTEXT_SIGNALS = [
11
+ { test: (p) => p.length < 15 && !p.includes("?"), message: "Very short prompt without a question — the AI will guess your intent" },
12
+ { test: (p) => /\b(it|this|that|these|those)\b/i.test(p) && p.split(/\s+/).length < 8, message: "Pronoun-heavy short prompt — 'it'/'this' may be ambiguous after context compaction" },
13
+ ];
14
+ const ACCEPTANCE_CRITERIA_HINTS = [
15
+ /\b(implement|create|build|add|write|refactor|migrate|redesign)\b/i,
16
+ ];
17
+ const HAS_CRITERIA = [
18
+ /\b(should|must|ensure|verify|test|expect|accept|criteria|requirement)\b/i,
19
+ /\b(when|if|unless|given|then)\b/i,
20
+ /\d+/, // numbers often indicate concrete specs
21
+ ];
22
+ const OPERATIONAL_FOLLOW_UP = /\b(commit|push|open pr|pull request|run tests?|test suite|coverage|build|review findings|update status|continue with|proceed with|release)\b/i;
23
+ // ── Analyzer ──
24
+ export function analyzePrompt(prompt, state, transcript) {
25
+ const concerns = [];
26
+ const words = prompt.trim().split(/\s+/);
27
+ const wc = words.length;
28
+ const ctx = transcript ?? null;
29
+ // When a transcript is available, determine whether this prompt is a
30
+ // natural follow-up to the preceding assistant response. This single
31
+ // flag gates most of the false-positive suppression below.
32
+ const isFollowUp = ctx ? isContextualFollowUp(prompt, ctx) : false;
33
+ const responseInvitedThis = ctx ? lastResponseInvitesFollowUp(ctx) : false;
34
+ const isOperationalFollowUp = OPERATIONAL_FOLLOW_UP.test(prompt);
35
+ const isFileReference = /^@\S+/.test(prompt.trim()) || prompt.includes("```");
36
+ // 1. Lazy/vague prompt detection
37
+ // Suppress when the assistant just asked a question or presented options
38
+ // and the user is responding with a short affirmative/directive.
39
+ for (const { pattern, label } of LAZY_PATTERNS) {
40
+ if (pattern.test(prompt.trim())) {
41
+ if (isFollowUp || responseInvitedThis) {
42
+ concerns.push({
43
+ severity: "info",
44
+ category: "prompt-quality",
45
+ message: `Short directive ("${prompt.trim().slice(0, 40)}") — appears to be a contextual follow-up`,
46
+ });
47
+ }
48
+ else {
49
+ concerns.push({
50
+ severity: "warning",
51
+ category: "prompt-quality",
52
+ message: `Lazy prompt detected: ${label}`,
53
+ suggestion: "Rewrite with specific intent, context, and expected outcome. What exactly should change, and how will you verify it worked?",
54
+ });
55
+ }
56
+ }
57
+ }
58
+ // 2. Missing context signals
59
+ // Suppress pronoun-heavy warning when pronouns clearly refer to the
60
+ // assistant's last response (transcript available and follow-up detected).
61
+ for (const { test, message } of MISSING_CONTEXT_SIGNALS) {
62
+ if (test(prompt)) {
63
+ if (isFollowUp)
64
+ continue;
65
+ concerns.push({
66
+ severity: "info",
67
+ category: "prompt-quality",
68
+ message,
69
+ suggestion: "Add explicit references to files, functions, or behaviors instead of relying on pronouns.",
70
+ });
71
+ }
72
+ }
73
+ // 3. Significant task without acceptance criteria
74
+ const isSignificantTask = ACCEPTANCE_CRITERIA_HINTS.some((r) => r.test(prompt));
75
+ const hasCriteria = HAS_CRITERIA.some((r) => r.test(prompt));
76
+ if (isSignificantTask && !hasCriteria && wc > 5) {
77
+ concerns.push({
78
+ severity: "info",
79
+ category: "missing-criteria",
80
+ message: "Task request without acceptance criteria",
81
+ suggestion: "Consider adding: what 'done' looks like, edge cases to handle, or how to verify the result.",
82
+ });
83
+ }
84
+ // 4. Prompt quality degradation over the session
85
+ // With a transcript, use actual user turn word counts for a more accurate
86
+ // comparison than the state-tracked PromptRecords (which only start
87
+ // tracking once the hook is installed).
88
+ const recent = recentPrompts(state, 5);
89
+ // Cap individual prompt word counts at 100 to prevent massive error logs
90
+ // from artificially inflating the average and causing false positives.
91
+ const capWc = (count) => Math.min(count, 100);
92
+ if (ctx && ctx.userTurnCount >= 4) {
93
+ const userTurns = recentUserTurns(ctx, 6);
94
+ if (userTurns.length >= 4) {
95
+ const avgRecent = userTurns.slice(0, -1).reduce((s, t) => s + capWc(t.wordCount), 0) / (userTurns.length - 1);
96
+ if (wc < avgRecent * 0.4 && wc < 15 && !isFollowUp && !isOperationalFollowUp && !isFileReference) {
97
+ concerns.push({
98
+ severity: "warning",
99
+ category: "prompt-degradation",
100
+ message: `Your prompts are getting shorter (transcript avg ${Math.round(avgRecent)} words → ${wc} now). Quality may be degrading.`,
101
+ suggestion: "Take a moment to formulate a complete, self-contained request. The AI performs better with clear context.",
102
+ });
103
+ }
104
+ }
105
+ }
106
+ else if (recent.length >= 3) {
107
+ const avgRecentLength = recent.reduce((s, r) => s + capWc(r.wordCount), 0) / recent.length;
108
+ if (wc < avgRecentLength * 0.4 && wc < 15 && !isFollowUp && !isOperationalFollowUp && !isFileReference) {
109
+ concerns.push({
110
+ severity: "warning",
111
+ category: "prompt-degradation",
112
+ message: `Your prompts are getting shorter (avg ${Math.round(avgRecentLength)} words → ${wc} now). Quality may be degrading.`,
113
+ suggestion: "Take a moment to formulate a complete, self-contained request. The AI performs better with clear context.",
114
+ });
115
+ }
116
+ }
117
+ // 5. Long session fatigue warning
118
+ const minutes = sessionDurationMinutes(state);
119
+ if (minutes > 60 && state.turnCount > 0 && state.turnCount % 10 === 0) {
120
+ concerns.push({
121
+ severity: "info",
122
+ category: "session-fatigue",
123
+ message: `Session running for ${Math.round(minutes)} minutes with ${state.turnCount} turns. Consider stepping back for a high-level review.`,
124
+ suggestion: "Open a new chat to review the overall changes made so far, or take a break to re-evaluate the approach.",
125
+ });
126
+ }
127
+ // 6. Rapid-fire prompts without review
128
+ if (recent.length >= 3) {
129
+ const lastThreeTimestamps = recent.slice(-3).map((r) => r.timestamp);
130
+ const gaps = [];
131
+ for (let i = 1; i < lastThreeTimestamps.length; i++) {
132
+ gaps.push(lastThreeTimestamps[i] - lastThreeTimestamps[i - 1]);
133
+ }
134
+ const avgGapSeconds = gaps.reduce((s, g) => s + g, 0) / gaps.length / 1000;
135
+ if (avgGapSeconds < 15) {
136
+ concerns.push({
137
+ severity: "warning",
138
+ category: "rapid-fire",
139
+ message: "Rapid-fire prompting detected — average gap between prompts is under 15 seconds",
140
+ suggestion: "Slow down. Are you reviewing the AI's responses before sending the next prompt? Quick iterations often lead to compounding errors.",
141
+ });
142
+ }
143
+ }
144
+ // 7. [Transcript-only] Repeated rephrasings — user struggling with the same request
145
+ if (ctx && ctx.userTurnCount >= 3) {
146
+ const lastUserTurns = recentUserTurns(ctx, 4);
147
+ if (lastUserTurns.length >= 3) {
148
+ const similarities = countSimilarPairs(lastUserTurns.map((t) => t.text.toLowerCase()));
149
+ if (similarities >= 2) {
150
+ concerns.push({
151
+ severity: "info",
152
+ category: "repeated-rephrase",
153
+ message: "You appear to be rephrasing the same request multiple times. The AI may not understand what you need.",
154
+ suggestion: "Try a different approach: break the task into smaller steps, provide a concrete example of expected output, or reference specific code.",
155
+ });
156
+ }
157
+ }
158
+ }
159
+ // 8. [Transcript-only] Context drift — current prompt is unrelated to the
160
+ // ongoing conversation thread without explicit topic change
161
+ if (ctx && ctx.userTurnCount >= 2 && wc > 10) {
162
+ const lastUser = ctx.lastUserText;
163
+ const lastAssistant = ctx.lastAssistantText;
164
+ if (lastUser && lastAssistant) {
165
+ const combined = (lastUser + " " + lastAssistant).toLowerCase();
166
+ const currentWords = new Set(words.map((w) => w.toLowerCase()).filter((w) => w.length > 4));
167
+ const overlapCount = [...currentWords].filter((w) => combined.includes(w)).length;
168
+ const overlapRatio = currentWords.size > 0 ? overlapCount / currentWords.size : 1;
169
+ if (overlapRatio < 0.1 && currentWords.size > 8) {
170
+ // Before flagging, check overlap with the session's first user prompt.
171
+ // In checklist-style sessions the user works through numbered items —
172
+ // each item has different vocabulary but relates to the opening plan.
173
+ const firstUserTurn = recentUserTurns(ctx, ctx.userTurnCount)[0];
174
+ let relatedToSessionPlan = false;
175
+ if (firstUserTurn && firstUserTurn !== ctx.turns.find(t => t.text === lastUser)) {
176
+ const planWords = firstUserTurn.text.toLowerCase();
177
+ const planOverlap = [...currentWords].filter((w) => planWords.includes(w)).length;
178
+ const planRatio = planOverlap / currentWords.size;
179
+ if (planRatio >= 0.15)
180
+ relatedToSessionPlan = true;
181
+ }
182
+ if (!relatedToSessionPlan) {
183
+ concerns.push({
184
+ severity: "info",
185
+ category: "context-drift",
186
+ message: "This prompt appears unrelated to the current conversation thread",
187
+ suggestion: "Consider starting a new chat for unrelated topics. Mixing contexts in a single session can confuse the AI and lead to errors.",
188
+ });
189
+ }
190
+ }
191
+ }
192
+ }
193
+ // Block on warning+ severity so the user actually sees Hegel's feedback.
194
+ // Cursor only surfaces beforeSubmitPrompt's user_message when continue=false.
195
+ const shouldBlock = concerns.some((c) => c.severity === "critical" || c.severity === "warning");
196
+ return { concerns, shouldBlock };
197
+ }
198
+ /**
199
+ * Counts how many adjacent pairs of strings share significant word overlap,
200
+ * indicating the user is rephrasing the same request.
201
+ */
202
+ function countSimilarPairs(texts) {
203
+ let count = 0;
204
+ for (let i = 1; i < texts.length; i++) {
205
+ const prev = new Set(texts[i - 1].split(/\s+/).filter((w) => w.length > 3));
206
+ const curr = new Set(texts[i].split(/\s+/).filter((w) => w.length > 3));
207
+ if (prev.size === 0 || curr.size === 0)
208
+ continue;
209
+ const overlap = [...curr].filter((w) => prev.has(w)).length;
210
+ const ratio = overlap / Math.min(prev.size, curr.size);
211
+ if (ratio > 0.5)
212
+ count++;
213
+ }
214
+ return count;
215
+ }
216
+ export function buildPromptRecord(prompt, concerns) {
217
+ return {
218
+ timestamp: Date.now(),
219
+ prompt,
220
+ wordCount: prompt.trim().split(/\s+/).length,
221
+ concerns,
222
+ };
223
+ }
224
+ //# sourceMappingURL=prompt-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-analyzer.js","sourceRoot":"","sources":["../../src/analyzers/prompt-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Concern, SessionState, ResponseRecord, TranscriptContext } from "../types.js";
2
+ export declare function analyzeResponse(text: string, state: SessionState, transcript?: TranscriptContext | null): {
3
+ concerns: Concern[];
4
+ };
5
+ export declare function buildResponseRecord(text: string, concerns: Concern[], unprompted?: boolean): ResponseRecord;
6
+ //# sourceMappingURL=response-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/response-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA6C5F,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,EACnB,UAAU,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACpC;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CA6IzB;AAyDD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,UAAQ,GACjB,cAAc,CAOhB"}
@@ -0,0 +1,218 @@
1
+ import { totalFilesEdited, totalLinesChanged } from "../state.js";
2
+ import { recentAssistantTurns, recentUserTurns } from "../transcript.js";
3
+ // ── Pattern Definitions ──
4
+ const OVERCONFIDENCE_PATTERNS = [
5
+ {
6
+ pattern: /\b(this is the (only )?correct|this will definitely|guaranteed to work)\b/i,
7
+ message: "Overconfident language — AI is claiming certainty without caveats",
8
+ },
9
+ {
10
+ pattern: /\b(I've fixed all|all issues are resolved|everything.+works now|completely fixed)\b/i,
11
+ message: "Blanket 'everything fixed' claim — did you verify all edge cases?",
12
+ },
13
+ ];
14
+ const SYCOPHANCY_PATTERNS = [
15
+ {
16
+ pattern: /\b(you'?re (absolutely |completely )?right|I (completely |fully )?agree with your (approach|assessment|analysis))\b/i,
17
+ message: "Sycophantic agreement detected — is the AI genuinely agreeing or just being agreeable?",
18
+ },
19
+ ];
20
+ const MISSING_VERIFICATION = [
21
+ {
22
+ trigger: /\b(refactor|restructur|rewrit|migrat|redesign|overhaul)\b/i,
23
+ check: /\b(test|verify|check|ensure|validate|confirm|run)\b/i,
24
+ message: "Major structural change proposed without explicit verification step",
25
+ suggestion: "Ask: 'How can we verify this refactoring didn't break anything?' before accepting.",
26
+ },
27
+ {
28
+ trigger: /\b(security|auth|access control|credential|secret|token|password|encrypt)\b/i,
29
+ check: /\b(review|audit|test|verify|vulnerabilit)\b/i,
30
+ message: "Security-related change without security review mention",
31
+ suggestion: "Security changes deserve explicit review. Consider asking for a threat model or security-specific test cases.",
32
+ },
33
+ ];
34
+ const SCOPE_CREEP_THRESHOLD = 8;
35
+ const LINES_CHANGED_THRESHOLD = 200;
36
+ const INTENTIONAL_BROAD_SCOPE_PATTERN = /\b(audit|refactor|fix(es)?|review|update|align|migrate|rename|across|all files|codebase|inconsistenc|cover|coverage|extend|next step|follow-?up|integration-style|also|proceed with|now do)\b/i;
37
+ // ── Analyzer ──
38
+ export function analyzeResponse(text, state, transcript) {
39
+ const concerns = [];
40
+ const ctx = transcript ?? null;
41
+ // 1. Overconfidence detection
42
+ for (const { pattern, message } of OVERCONFIDENCE_PATTERNS) {
43
+ if (pattern.test(text)) {
44
+ concerns.push({
45
+ severity: "warning",
46
+ category: "overconfidence",
47
+ message,
48
+ suggestion: "Ask the AI: 'What could go wrong with this approach?' or 'What edge cases might this miss?'",
49
+ });
50
+ break;
51
+ }
52
+ }
53
+ // 2. Sycophancy detection
54
+ for (const { pattern, message } of SYCOPHANCY_PATTERNS) {
55
+ if (pattern.test(text)) {
56
+ concerns.push({
57
+ severity: "info",
58
+ category: "sycophancy",
59
+ message,
60
+ suggestion: "If the AI immediately agrees with everything, try playing devil's advocate: 'What arguments exist against this approach?'",
61
+ });
62
+ break;
63
+ }
64
+ }
65
+ // 3. Missing verification for risky changes
66
+ const hasCodeChanges = totalFilesEdited(state) > 0 || totalLinesChanged(state) > 0;
67
+ if (hasCodeChanges) {
68
+ for (const { trigger, check, message, suggestion } of MISSING_VERIFICATION) {
69
+ if (trigger.test(text) && !check.test(text)) {
70
+ concerns.push({
71
+ severity: "warning",
72
+ category: "missing-verification",
73
+ message,
74
+ suggestion,
75
+ });
76
+ }
77
+ }
78
+ }
79
+ // 4. Scope creep: too many files touched in the session
80
+ // Suppress when the session's opening prompt explicitly describes
81
+ // multi-file work (audit, refactor, fix list, etc.)
82
+ const filesEdited = totalFilesEdited(state);
83
+ if (filesEdited > SCOPE_CREEP_THRESHOLD && state.turnCount > 3) {
84
+ const alreadyWarned = state.concerns.some((c) => c.category === "scope-creep");
85
+ let intentionalBroadScope = false;
86
+ if (ctx) {
87
+ const userTurns = recentUserTurns(ctx, ctx.userTurnCount);
88
+ const firstUser = userTurns[0];
89
+ const recentUsers = userTurns.slice(-3);
90
+ intentionalBroadScope = [firstUser, ...recentUsers]
91
+ .filter((turn) => !!turn)
92
+ .some((turn) => INTENTIONAL_BROAD_SCOPE_PATTERN.test(turn.text));
93
+ }
94
+ else if (state.prompts.length > 0) {
95
+ intentionalBroadScope = [state.prompts[0], ...state.prompts.slice(-3)]
96
+ .some((prompt) => INTENTIONAL_BROAD_SCOPE_PATTERN.test(prompt.prompt));
97
+ }
98
+ if (!alreadyWarned && !intentionalBroadScope) {
99
+ concerns.push({
100
+ severity: "warning",
101
+ category: "scope-creep",
102
+ message: `${filesEdited} files modified this session. The scope may be expanding beyond the original intent.`,
103
+ suggestion: "Pause and ask: 'Is this still aligned with what I originally set out to do?' Consider committing what you have and starting a focused follow-up.",
104
+ });
105
+ }
106
+ }
107
+ // 5. Large volume of changes without tests
108
+ const lines = totalLinesChanged(state);
109
+ if (lines > LINES_CHANGED_THRESHOLD) {
110
+ const alreadyWarned = state.concerns.some((c) => c.category === "untested-changes");
111
+ const hasTestActivity = ctx ?
112
+ recentUserTurns(ctx, 3).some(t => t && /\b(test|spec|jest|vitest|mocha|cypress|playwright)\b/i.test(t.text)) :
113
+ state.prompts.slice(-3).some(p => /\b(test|spec|jest|vitest|mocha|cypress|playwright)\b/i.test(p.prompt));
114
+ if (!alreadyWarned && !hasTestActivity) {
115
+ concerns.push({
116
+ severity: "warning",
117
+ category: "untested-changes",
118
+ message: `${lines} lines changed so far without any test-related activity detected`,
119
+ suggestion: "Consider asking the AI to generate tests for the changes, or run existing tests before continuing.",
120
+ });
121
+ }
122
+ }
123
+ // 6. [Transcript-only] Self-contradiction — the AI reverses a position
124
+ // it stated in a recent response without acknowledging the change.
125
+ if (ctx && ctx.assistantTurnCount >= 2) {
126
+ const recentResponses = recentAssistantTurns(ctx, 3);
127
+ const contradiction = detectContradiction(text, recentResponses.map((t) => t.text));
128
+ if (contradiction) {
129
+ concerns.push({
130
+ severity: "warning",
131
+ category: "self-contradiction",
132
+ message: contradiction,
133
+ suggestion: "Ask the AI: 'You previously said X but now say Y — what changed?' Contradictions may indicate the AI is guessing rather than reasoning.",
134
+ });
135
+ }
136
+ }
137
+ // 7. [Transcript-only] Non-responsive — the response doesn't address the
138
+ // user's last request (keyword overlap check).
139
+ // For multi-topic prompts (bullet points, line breaks), check each
140
+ // segment individually — the response may focus on one sub-topic.
141
+ if (ctx && ctx.lastUserText) {
142
+ const responseWords = significantWords(text);
143
+ const segments = ctx.lastUserText.split(/\n[-•*]\s*|\n{2,}/).filter(s => s.trim().length > 10);
144
+ const segmentSets = segments.length > 1
145
+ ? segments.map(s => significantWords(s))
146
+ : [significantWords(ctx.lastUserText)];
147
+ const bestRatio = Math.max(...segmentSets.map(userWords => {
148
+ if (userWords.size < 5)
149
+ return 1;
150
+ const overlap = [...userWords].filter(w => responseWords.has(w)).length;
151
+ return overlap / userWords.size;
152
+ }));
153
+ if (bestRatio < 0.05 && text.length > 200) {
154
+ concerns.push({
155
+ severity: "info",
156
+ category: "non-responsive",
157
+ message: "The AI's response has very low overlap with your request — it may be addressing something different",
158
+ suggestion: "Compare the response to your original question. If it's off-topic, re-state your request clearly.",
159
+ });
160
+ }
161
+ }
162
+ return { concerns };
163
+ }
164
+ /**
165
+ * Checks for simple contradictions between the current response and recent ones.
166
+ * Looks for explicit negation of previously stated positions.
167
+ */
168
+ function detectContradiction(current, previousResponses) {
169
+ const CONTRADICTION_PAIRS = [
170
+ [
171
+ /\b(you should|I recommend|the (best|right) (approach|way))\b.*\b(not|don't|avoid|instead of)\b.*\b(\w{4,})\b/i,
172
+ /\b(you should|I recommend|the (best|right) (approach|way))\b.*\b(\w{4,})\b/i,
173
+ "AI may have reversed its recommendation",
174
+ ],
175
+ [
176
+ /\b(this (is|was) (not |un)?necessary)\b/i,
177
+ /\b(this (is|was) (not |un)?necessary)\b/i,
178
+ "AI's assessment of necessity may have flipped",
179
+ ],
180
+ ];
181
+ for (const prev of previousResponses) {
182
+ for (const [currentPattern, prevPattern, message] of CONTRADICTION_PAIRS) {
183
+ if (currentPattern.test(current) && prevPattern.test(prev)) {
184
+ const currentNegated = /\b(not|don't|shouldn't|avoid|never)\b/i.test(current);
185
+ const prevNegated = /\b(not|don't|shouldn't|avoid|never)\b/i.test(prev);
186
+ if (currentNegated !== prevNegated) {
187
+ return message;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function significantWords(text) {
195
+ const STOP_WORDS = new Set([
196
+ "the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
197
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
198
+ "should", "may", "might", "shall", "can", "need", "must", "that",
199
+ "this", "these", "those", "with", "from", "into", "for", "and",
200
+ "but", "not", "you", "your", "it", "its", "they", "them", "their",
201
+ "what", "which", "who", "when", "where", "how", "all", "each",
202
+ "every", "both", "few", "more", "most", "other", "some", "such",
203
+ "than", "too", "very", "just", "also", "here", "there", "then",
204
+ "now", "only", "about", "after", "before", "between", "over",
205
+ ]);
206
+ return new Set(text.toLowerCase()
207
+ .split(/\s+/)
208
+ .filter((w) => w.length > 3 && !STOP_WORDS.has(w)));
209
+ }
210
+ export function buildResponseRecord(text, concerns, unprompted = false) {
211
+ return {
212
+ timestamp: Date.now(),
213
+ textLength: text.length,
214
+ concerns,
215
+ ...(unprompted && { unprompted }),
216
+ };
217
+ }
218
+ //# sourceMappingURL=response-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-analyzer.js","sourceRoot":"","sources":["../../src/analyzers/response-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEzE,4BAA4B;AAE5B,MAAM,uBAAuB,GAAgD;IAC3E;QACE,OAAO,EAAE,4EAA4E;QACrF,OAAO,EAAE,mEAAmE;KAC7E;IACD;QACE,OAAO,EAAE,sFAAsF;QAC/F,OAAO,EAAE,mEAAmE;KAC7E;CACF,CAAC;AAEF,MAAM,mBAAmB,GAAgD;IACvE;QACE,OAAO,EAAE,sHAAsH;QAC/H,OAAO,EAAE,wFAAwF;KAClG;CACF,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B;QACE,OAAO,EAAE,4DAA4D;QACrE,KAAK,EAAE,sDAAsD;QAC7D,OAAO,EAAE,qEAAqE;QAC9E,UAAU,EAAE,oFAAoF;KACjG;IACD;QACE,OAAO,EAAE,8EAA8E;QACvF,KAAK,EAAE,8CAA8C;QACrD,OAAO,EAAE,yDAAyD;QAClE,UAAU,EAAE,+GAA+G;KAC5H;CACF,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAChC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,+BAA+B,GAAG,gMAAgM,CAAC;AAEzO,iBAAiB;AAEjB,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,KAAmB,EACnB,UAAqC;IAErC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,UAAU,IAAI,IAAI,CAAC;IAE/B,8BAA8B;IAC9B,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,uBAAuB,EAAE,CAAC;QAC3D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,gBAAgB;gBAC1B,OAAO;gBACP,UAAU,EAAE,6FAA6F;aAC1G,CAAC,CAAC;YACH,MAAM;QACR,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,mBAAmB,EAAE,CAAC;QACvD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,YAAY;gBACtB,OAAO;gBACP,UAAU,EAAE,2HAA2H;aACxI,CAAC,CAAC;YACH,MAAM;QACR,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,MAAM,cAAc,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnF,IAAI,cAAc,EAAE,CAAC;QACnB,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,oBAAoB,EAAE,CAAC;YAC3E,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,SAAS;oBACnB,QAAQ,EAAE,sBAAsB;oBAChC,OAAO;oBACP,UAAU;iBACX,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,qEAAqE;IACrE,uDAAuD;IACvD,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,WAAW,GAAG,qBAAqB,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QAC/D,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,CACpC,CAAC;QACF,IAAI,qBAAqB,GAAG,KAAK,CAAC;QAClC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YAC1D,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACxC,qBAAqB,GAAG,CAAC,SAAS,EAAE,GAAG,WAAW,CAAC;iBAChD,MAAM,CAAC,CAAC,IAAI,EAAoC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAC1D,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,qBAAqB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;iBACnE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC,aAAa,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC7C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,aAAa;gBACvB,OAAO,EAAE,GAAG,WAAW,sFAAsF;gBAC7G,UAAU,EAAE,kJAAkJ;aAC/J,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,uBAAuB,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,kBAAkB,CACzC,CAAC;QACF,MAAM,eAAe,GAAG,GAAG,CAAC,CAAC;YAC3B,eAAe,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,uDAAuD,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9G,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,uDAAuD,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAE5G,IAAI,CAAC,aAAa,IAAI,CAAC,eAAe,EAAE,CAAC;YACvC,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,kBAAkB;gBAC5B,OAAO,EAAE,GAAG,KAAK,kEAAkE;gBACnF,UAAU,EAAE,oGAAoG;aACjH,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,sEAAsE;IACtE,IAAI,GAAG,IAAI,GAAG,CAAC,kBAAkB,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,eAAe,GAAG,oBAAoB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACpF,IAAI,aAAa,EAAE,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,oBAAoB;gBAC9B,OAAO,EAAE,aAAa;gBACtB,UAAU,EAAE,yIAAyI;aACtJ,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,kDAAkD;IAClD,sEAAsE;IACtE,qEAAqE;IACrE,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QAC5B,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QAC/F,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;YACrC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;QAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;YAC7B,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;YACjC,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACxE,OAAO,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC;QAClC,CAAC,CAAC,CACH,CAAC;QAEF,IAAI,SAAS,GAAG,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,gBAAgB;gBAC1B,OAAO,EAAE,qGAAqG;gBAC9G,UAAU,EAAE,mGAAmG;aAChH,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,OAAe,EACf,iBAA2B;IAE3B,MAAM,mBAAmB,GAAoC;QAC3D;YACE,+GAA+G;YAC/G,6EAA6E;YAC7E,yCAAyC;SAC1C;QACD;YACE,0CAA0C;YAC1C,0CAA0C;YAC1C,+CAA+C;SAChD;KACF,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,cAAc,EAAE,WAAW,EAAE,OAAO,CAAC,IAAI,mBAAmB,EAAE,CAAC;YACzE,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3D,MAAM,cAAc,GAAG,wCAAwC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC9E,MAAM,WAAW,GAAG,wCAAwC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxE,IAAI,cAAc,KAAK,WAAW,EAAE,CAAC;oBACnC,OAAO,OAAO,CAAC;gBACjB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;QACzB,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;QACnE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;QACnE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;QAChE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;QAC9D,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;QACjE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;QAC7D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;QAC/D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;QAC9D,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;KAC7D,CAAC,CAAC;IACH,OAAO,IAAI,GAAG,CACZ,IAAI,CAAC,WAAW,EAAE;SACf,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CACrD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,QAAmB,EACnB,UAAU,GAAG,KAAK;IAElB,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,UAAU,EAAE,IAAI,CAAC,MAAM;QACvB,QAAQ;QACR,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;KAClC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Concern, SessionState, TranscriptContext } from "../types.js";
2
+ /**
3
+ * Produces a human-readable session summary with accumulated concerns.
4
+ * Called at the stop hook — this is Hegel's "end of turn" dialectical review.
5
+ */
6
+ export declare function analyzeSession(state: SessionState, transcript?: TranscriptContext | null): {
7
+ summary: string;
8
+ concerns: Concern[];
9
+ };
10
+ //# sourceMappingURL=session-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/session-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA4D5E;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,UAAU,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACpC;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAqLA"}