mnueron 0.4.0 → 0.6.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 (54) hide show
  1. package/INSTALL.md +125 -0
  2. package/LICENSE +28 -21
  3. package/NOTICE +164 -0
  4. package/README.md +443 -422
  5. package/dist/cli.js +114 -26
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.js +4 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/detectors/codex.js +138 -0
  10. package/dist/detectors/codex.js.map +1 -0
  11. package/dist/detectors/index.js +2 -0
  12. package/dist/detectors/index.js.map +1 -1
  13. package/dist/detectors/json_detector.js +1 -1
  14. package/dist/lib/context-engine/confidence.js +153 -0
  15. package/dist/lib/context-engine/confidence.js.map +1 -0
  16. package/dist/lib/context-engine/entities.js +179 -0
  17. package/dist/lib/context-engine/entities.js.map +1 -0
  18. package/dist/lib/context-engine/index.js +74 -0
  19. package/dist/lib/context-engine/index.js.map +1 -0
  20. package/dist/lib/context-engine/intent.js +139 -0
  21. package/dist/lib/context-engine/intent.js.map +1 -0
  22. package/dist/lib/context-engine/runbook-detector.js +206 -0
  23. package/dist/lib/context-engine/runbook-detector.js.map +1 -0
  24. package/dist/lib/date-anchors.js +351 -0
  25. package/dist/lib/date-anchors.js.map +1 -0
  26. package/dist/lib/temporal-intent.js +98 -0
  27. package/dist/lib/temporal-intent.js.map +1 -0
  28. package/dist/runbook/auto-extract.js +415 -0
  29. package/dist/runbook/auto-extract.js.map +1 -0
  30. package/dist/runbook/capture.js +214 -0
  31. package/dist/runbook/capture.js.map +1 -0
  32. package/dist/runbook/explain.js +154 -0
  33. package/dist/runbook/explain.js.map +1 -0
  34. package/dist/runbook/fingerprint.js +128 -0
  35. package/dist/runbook/fingerprint.js.map +1 -0
  36. package/dist/runbook/search.js +76 -0
  37. package/dist/runbook/search.js.map +1 -0
  38. package/dist/runbook/types.js +15 -0
  39. package/dist/runbook/types.js.map +1 -0
  40. package/dist/setup.js +32 -4
  41. package/dist/setup.js.map +1 -1
  42. package/dist/store/entity-extractor.js +69 -2
  43. package/dist/store/entity-extractor.js.map +1 -1
  44. package/dist/store/local-db.js +33 -0
  45. package/dist/store/local-db.js.map +1 -0
  46. package/dist/store/local.js +230 -229
  47. package/dist/store/local.js.map +1 -1
  48. package/dist/store/procedural.js +185 -0
  49. package/dist/store/procedural.js.map +1 -1
  50. package/dist/store/remote.js +73 -0
  51. package/dist/store/remote.js.map +1 -1
  52. package/dist/tools.js +393 -8
  53. package/dist/tools.js.map +1 -1
  54. package/package.json +63 -55
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Context-intent classifier.
3
+ *
4
+ * Given the user's active context (current text in editor, chat input,
5
+ * doc, etc.), classify WHAT KIND of task they're doing:
6
+ *
7
+ * coding — writing/editing source code
8
+ * deploying — release / rollout / production push
9
+ * debugging — investigating an error, reading logs, fixing a bug
10
+ * testing — writing test cases, QA, validation
11
+ * documenting — readme, comments, API docs, runbooks
12
+ * planning — design doc, roadmap, sprint planning, architecture
13
+ * temporal — questions about when things happened (reuses temporal-intent)
14
+ * none — fallback when no strong signal
15
+ *
16
+ * This is the gate for everything downstream: which namespace to search,
17
+ * which kind of recall to prefer (memories vs runbooks), what confidence
18
+ * threshold to apply.
19
+ *
20
+ * Pure module — no I/O, no DB, no LLM. Trivially unit-testable. The
21
+ * patterns are tuned to be HIGH PRECISION (false positives are worse
22
+ * than misses for the "should I surface a suggestion?" use case).
23
+ */
24
+ import { isTemporalQuery } from '../temporal-intent.js';
25
+ /**
26
+ * Patterns per intent kind. Order matters within an array — the first
27
+ * match wins for tie-breaking when two kinds score equal.
28
+ *
29
+ * Each entry has a regex + a weight 0-1. Multiple matches in the same
30
+ * kind compound (capped at 1.0). The kind with the highest total weight
31
+ * wins.
32
+ */
33
+ const SIGNALS = {
34
+ coding: [
35
+ // Source-code markers
36
+ { re: /```(?:typescript|javascript|tsx?|jsx?|python|py|go|rust|java|c\+\+|cpp|csharp|cs|ruby|rb|php|swift|kotlin|scala|sql)\b/i, w: 0.6, label: 'fenced-code-lang' },
37
+ { re: /\b(?:function|const|let|var|import|export|class|interface|def|async|await)\s+[a-z_]/i, w: 0.45, label: 'language-keyword' },
38
+ { re: /\b(?:return|throw|try|catch|if|else|for|while|switch)\b.*[{(]/i, w: 0.25, label: 'control-flow' },
39
+ { re: /\.(?:then|catch|map|filter|reduce|forEach)\b/, w: 0.35, label: 'method-chain' },
40
+ { re: /\b(?:writing|coding|implementing|refactoring|fixing|adding)\s+(?:a\s+)?(?:function|method|class|component|endpoint|api|route|handler)\b/i, w: 0.55, label: 'verb-noun' },
41
+ // File paths in src/, components/, lib/, etc.
42
+ { re: /\b(?:src|components|lib|hooks|utils|services|controllers|routes|pages|app)\/[a-z0-9-_/]+\.(?:ts|tsx|js|jsx|py|go|rs|java|cs|rb|php|swift|kt)\b/i, w: 0.5, label: 'src-file-path' },
43
+ ],
44
+ deploying: [
45
+ { re: /\b(?:deploy(?:ing|ment)?|rollout|roll\s+out|release|push\s+to\s+(?:prod|production|staging|live))\b/i, w: 0.7, label: 'deploy-verb' },
46
+ { re: /\b(?:vercel|netlify|fly\.io|railway|render|heroku|aws|gcp|azure|kubernetes|k8s|docker)\s+(?:deploy|push|build)/i, w: 0.6, label: 'platform-deploy' },
47
+ { re: /\b(?:git\s+push|npm\s+publish|cargo\s+publish|pip\s+upload|docker\s+push)\b/i, w: 0.55, label: 'publish-cmd' },
48
+ { re: /\b(?:rolling\s+back|hotfix|cab(?:\s+release)?|prod\s+(?:incident|release))\b/i, w: 0.65, label: 'release-event' },
49
+ { re: /\b(?:staging|production)\s+(?:server|env|environment)\b/i, w: 0.45, label: 'env-mention' },
50
+ { re: /\bblue-green\b|\bcanary\b/i, w: 0.5, label: 'deploy-strategy' },
51
+ ],
52
+ debugging: [
53
+ { re: /\b(?:error|exception|failure|failed|fatal|panic|stack\s+trace|traceback)\b/i, w: 0.55, label: 'error-noun' },
54
+ { re: /\bfix(?:ing|ed)?\s+(?:a\s+)?(?:bug|issue|error|problem|crash)\b/i, w: 0.65, label: 'fix-verb' },
55
+ { re: /\b(?:debug(?:ging)?|investigate|investigating|root\s+cause|why\s+is|what's\s+wrong)\b/i, w: 0.6, label: 'debug-verb' },
56
+ { re: /\b(?:sqlstate|err_|errno|err\s+\d|http\s+(?:4|5)\d{2})\b/i, w: 0.55, label: 'error-code' },
57
+ { re: /\b(?:not\s+working|broken|crashing|hangs|times?\s+out)\b/i, w: 0.45, label: 'broken-phrase' },
58
+ { re: /\b(?:console\.log|print\(|debugger|breakpoint|pdb|gdb)\b/i, w: 0.4, label: 'debug-tool' },
59
+ ],
60
+ testing: [
61
+ { re: /\b(?:test(?:s|ing|ed)?|spec(?:s)?|describe|it\(|expect\(|assert(?:Equals?|True|False)?)\b/i, w: 0.5, label: 'test-verb' },
62
+ { re: /\b(?:vitest|jest|mocha|chai|pytest|unittest|rspec|junit|xunit)\b/i, w: 0.65, label: 'test-framework' },
63
+ { re: /\b(?:qa|q\.a\.|quality\s+assurance|test\s+plan|test\s+case|cr\s+coverage|cab\s+coverage|regression\s+test)\b/i, w: 0.6, label: 'qa-noun' },
64
+ { re: /\b(?:writing|adding|fixing)\s+(?:a\s+)?test/i, w: 0.55, label: 'verb-test' },
65
+ { re: /\b(?:should|must|expects?\s+to)\s+(?:return|throw|equal|contain|match)\b/i, w: 0.4, label: 'assertion-language' },
66
+ ],
67
+ documenting: [
68
+ { re: /\b(?:docu?mentation|readme|docstring|api\s+docs|user\s+guide|how-?to)\b/i, w: 0.6, label: 'doc-noun' },
69
+ { re: /\bwriting\s+(?:a\s+)?(?:doc|guide|readme|tutorial|spec|specification)\b/i, w: 0.6, label: 'writing-doc' },
70
+ { re: /\b(?:markdown|\.md\b|<!--|jsdoc|tsdoc|sphinx|docusaurus)\b/i, w: 0.45, label: 'doc-format' },
71
+ { re: /\b(?:install(?:ation)?|setup|getting\s+started|quickstart|configure)\b/i, w: 0.4, label: 'setup-doc-topic' },
72
+ { re: /^#+\s+/m, w: 0.3, label: 'md-header' },
73
+ ],
74
+ planning: [
75
+ { re: /\b(?:roadmap|sprint|planning|design\s+doc|rfc|architecture|adr)\b/i, w: 0.65, label: 'planning-noun' },
76
+ { re: /\b(?:should\s+we|let'?s|we\s+need\s+to|next\s+up|todo|tbd|wip)\b/i, w: 0.3, label: 'planning-phrase' },
77
+ { re: /\b(?:decision|decided|trade-?off|alternative(?:s)?|considered)\b/i, w: 0.45, label: 'decision-language' },
78
+ { re: /\b(?:phase\s+\d|milestone|deadline|deliverable)\b/i, w: 0.5, label: 'milestone' },
79
+ { re: /\b(?:goal|objective|requirement|user\s+story|acceptance\s+criteria)\b/i, w: 0.5, label: 'goal-language' },
80
+ ],
81
+ };
82
+ /**
83
+ * Classify the active context. Returns the highest-scoring intent above
84
+ * a minimum signal floor of 0.25 (below that, it's "none").
85
+ *
86
+ * Why not multi-label: in practice the user is doing one thing at a
87
+ * time. A single intent keeps the downstream namespace/threshold logic
88
+ * deterministic. If multi-label is needed later, we can return a sorted
89
+ * list and let callers consume top-N.
90
+ */
91
+ export function classifyIntent(text) {
92
+ if (!text || text.trim().length < 5) {
93
+ return { kind: 'none', confidence: 0, signals: [] };
94
+ }
95
+ // Special case: temporal queries are detected by a separate, more
96
+ // specific classifier we already shipped. If it fires AND no other
97
+ // intent scores higher, return 'temporal'.
98
+ const isTemporal = isTemporalQuery(text);
99
+ // Score each non-temporal kind.
100
+ const scores = {};
101
+ for (const [kind, patterns] of Object.entries(SIGNALS)) {
102
+ let total = 0;
103
+ const hits = [];
104
+ for (const p of patterns) {
105
+ if (p.re.test(text)) {
106
+ total += p.w;
107
+ hits.push(p.label);
108
+ }
109
+ }
110
+ // Cap at 1.0 — diminishing returns past full signal.
111
+ scores[kind] = { score: Math.min(total, 1.0), signals: hits };
112
+ }
113
+ // Pick the top-scoring kind.
114
+ let topKind = 'none';
115
+ let topScore = 0;
116
+ let topSignals = [];
117
+ for (const [kind, { score, signals }] of Object.entries(scores)) {
118
+ if (score > topScore) {
119
+ topKind = kind;
120
+ topScore = score;
121
+ topSignals = signals;
122
+ }
123
+ }
124
+ // Minimum floor: if nothing fired above 0.25, treat as 'none' unless
125
+ // it's a clear temporal query (which can have its own classifier hit).
126
+ if (topScore < 0.25) {
127
+ if (isTemporal) {
128
+ return { kind: 'temporal', confidence: 0.6, signals: ['temporal-classifier'] };
129
+ }
130
+ return { kind: 'none', confidence: 0, signals: [] };
131
+ }
132
+ // Temporal-vs-other tiebreak: if temporal fires AND another intent
133
+ // scores below 0.45, prefer temporal (it's a more specific signal).
134
+ if (isTemporal && topScore < 0.45) {
135
+ return { kind: 'temporal', confidence: 0.6, signals: ['temporal-classifier'] };
136
+ }
137
+ return { kind: topKind, confidence: topScore, signals: topSignals };
138
+ }
139
+ //# sourceMappingURL=intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.js","sourceRoot":"","sources":["../../../src/lib/context-engine/intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Runbook-pattern detector.
3
+ *
4
+ * Given a chunk of active context, decide whether it looks like a
5
+ * sequence of repeatable operational steps that the user might want to
6
+ * save as a runbook. Triggers the "Do you want to create a runbook
7
+ * from this?" suggestion.
8
+ *
9
+ * Different from `src/runbook/fingerprint.ts` (which fingerprints
10
+ * INDIVIDUAL errors) and from `src/runbook/auto-extract.ts` (which
11
+ * LLM-extracts from past Cowork chats). This detector is REAL-TIME and
12
+ * regex-only — runs on every typing pause, decides "is this currently
13
+ * being written like a runbook?"
14
+ *
15
+ * Signals it looks for:
16
+ *
17
+ * 1. Numbered or bulleted list of 3+ items
18
+ * 2. Multiple fenced code blocks (commands) in sequence
19
+ * 3. Imperative verbs at line starts ("Run", "Click", "Open", "Copy")
20
+ * 4. Sequential step language ("first", "then", "next", "finally")
21
+ * 5. Test pattern: each step is a single-line action + optional code
22
+ *
23
+ * NEGATIVE signals that demote runbook-confidence:
24
+ * - Continuous prose paragraphs without lists
25
+ * - Code that's clearly NOT a command (function definitions, classes)
26
+ * - Q&A or back-and-forth dialog
27
+ *
28
+ * Pure module — no I/O. Sibling to intent.ts and entities.ts.
29
+ */
30
+ export function detectRunbook(text) {
31
+ if (!text || text.trim().length < 30) {
32
+ return { isRunbook: false, confidence: 0, steps: [], suggestedTitle: null, signals: [] };
33
+ }
34
+ const lines = text.split(/\r?\n/);
35
+ const signals = [];
36
+ let score = 0;
37
+ // ─── Signal 1: numbered list of 3+ items ─────────────────────────────
38
+ const numberedItems = [];
39
+ for (let i = 0; i < lines.length; i++) {
40
+ const m = lines[i].match(/^\s*(\d+)[.)\]]\s+(.+)/);
41
+ if (m)
42
+ numberedItems.push({ idx: i, text: m[2].trim() });
43
+ }
44
+ if (numberedItems.length >= 3) {
45
+ score += 0.5;
46
+ signals.push(`numbered-list-${numberedItems.length}`);
47
+ }
48
+ else if (numberedItems.length === 2) {
49
+ score += 0.2;
50
+ signals.push('numbered-list-short');
51
+ }
52
+ // ─── Signal 2: bulleted list of 3+ items ─────────────────────────────
53
+ const bulletItems = [];
54
+ for (let i = 0; i < lines.length; i++) {
55
+ const m = lines[i].match(/^\s*[-*•]\s+(.+)/);
56
+ if (m)
57
+ bulletItems.push({ idx: i, text: m[1].trim() });
58
+ }
59
+ if (bulletItems.length >= 4) {
60
+ score += 0.35;
61
+ signals.push(`bullet-list-${bulletItems.length}`);
62
+ }
63
+ // ─── Signal 3: fenced code blocks (multiple = command sequence) ──────
64
+ const fencedBlocks = Array.from(text.matchAll(/```(?:bash|sh|powershell|ps1|zsh|fish|cmd|console)?\n([\s\S]*?)```/gi));
65
+ if (fencedBlocks.length >= 2) {
66
+ score += 0.4;
67
+ signals.push(`fenced-code-${fencedBlocks.length}`);
68
+ }
69
+ else if (fencedBlocks.length === 1 && fencedBlocks[0][1].split('\n').filter(l => l.trim()).length >= 3) {
70
+ score += 0.25;
71
+ signals.push('fenced-code-multiline');
72
+ }
73
+ // ─── Signal 4: imperative verbs at line starts ───────────────────────
74
+ // "Run X", "Open Y", "Click Z", "Copy ...", "Navigate to ..."
75
+ // The `(?:\d+[.)\]]?\s+|[-*•]\s+)?` prefix allows list-marker text
76
+ // BEFORE the imperative — so "1. Open Chrome" and "- Click button"
77
+ // both count. Without this, numbered/bulleted runbooks hide their
78
+ // imperatives behind list markers and score 0 on this signal.
79
+ const imperativeRe = /^\s*(?:\d+[.)\]]\s+|[-*•]\s+)?(?:Run|Open|Click|Copy|Paste|Navigate|Type|Press|Enter|Select|Go\s+to|Visit|Download|Upload|Install|Verify|Check|Confirm|Wait|Save|Submit|Apply|Apply|Deploy|Push|Pull|Build|Test|Edit|Add|Remove|Delete|Update|Set|Configure|Start|Stop|Restart|Login|Sign\s+in|Sign\s+up|Connect|Disconnect|Toggle|Trigger|Verify)\s+/i;
80
+ let imperativeCount = 0;
81
+ for (const line of lines) {
82
+ if (imperativeRe.test(line))
83
+ imperativeCount++;
84
+ }
85
+ if (imperativeCount >= 3) {
86
+ score += 0.4;
87
+ signals.push(`imperative-${imperativeCount}`);
88
+ }
89
+ else if (imperativeCount === 2) {
90
+ score += 0.15;
91
+ signals.push('imperative-pair');
92
+ }
93
+ // ─── Signal 5: sequential step language ──────────────────────────────
94
+ const sequentialPhrases = [
95
+ /\bfirst,?\s+\w+/i,
96
+ /\bthen,?\s+\w+/i,
97
+ /\bnext,?\s+\w+/i,
98
+ /\bafter\s+(?:that|this),?\s+\w+/i,
99
+ /\bfinally,?\s+\w+/i,
100
+ /\blastly,?\s+\w+/i,
101
+ /\bstep\s+\d+\b/i,
102
+ ];
103
+ const seqHits = sequentialPhrases.filter(re => re.test(text)).length;
104
+ if (seqHits >= 2) {
105
+ score += 0.25;
106
+ signals.push(`sequential-${seqHits}`);
107
+ }
108
+ // ─── NEGATIVE: continuous prose ──────────────────────────────────────
109
+ // If the text is mostly paragraphs with NO lists, NO commands, it's
110
+ // probably explaining something rather than describing steps.
111
+ const paragraphLines = lines.filter(l => l.trim().length > 80 && !l.match(/^\s*[\d-*•]/)).length;
112
+ const totalNonEmpty = lines.filter(l => l.trim().length > 0).length;
113
+ if (totalNonEmpty > 0 && paragraphLines / totalNonEmpty > 0.7 && score < 0.5) {
114
+ score *= 0.5;
115
+ signals.push('mostly-prose-demote');
116
+ }
117
+ // ─── NEGATIVE: dialog / Q&A pattern ──────────────────────────────────
118
+ // "You: ... Claude: ..." or "Q: ... A: ..."
119
+ const dialogRe = /^(?:you|claude|gpt|user|assistant|q|a)[:>]\s+/im;
120
+ if (dialogRe.test(text)) {
121
+ score *= 0.6;
122
+ signals.push('dialog-demote');
123
+ }
124
+ const isRunbook = score >= 0.55;
125
+ // ─── Extract steps if confident enough ───────────────────────────────
126
+ let steps = [];
127
+ let suggestedTitle = null;
128
+ if (score >= 0.4) {
129
+ steps = extractSteps(lines, numberedItems, bulletItems, fencedBlocks);
130
+ suggestedTitle = extractTitle(text, lines, steps);
131
+ }
132
+ return {
133
+ isRunbook,
134
+ confidence: Math.min(score, 1.0),
135
+ steps,
136
+ suggestedTitle,
137
+ signals,
138
+ };
139
+ }
140
+ // ─── Step extraction ──────────────────────────────────────────────────────
141
+ function extractSteps(lines, numbered, bullets, fenced) {
142
+ const out = [];
143
+ // Prefer numbered list as the canonical step structure.
144
+ const source = numbered.length >= 2 ? numbered : bullets.length >= 3 ? bullets : [];
145
+ for (const item of source) {
146
+ // Look ahead for an attached code block (line after this list item).
147
+ let code;
148
+ const nextLine = lines[item.idx + 1];
149
+ if (nextLine?.match(/^\s*```/)) {
150
+ // Walk until closing fence
151
+ const fenceLines = [];
152
+ for (let j = item.idx + 2; j < lines.length; j++) {
153
+ if (lines[j].match(/^\s*```/))
154
+ break;
155
+ fenceLines.push(lines[j]);
156
+ }
157
+ if (fenceLines.length > 0) {
158
+ code = fenceLines.join('\n').slice(0, 1000);
159
+ }
160
+ }
161
+ else {
162
+ // Inline code in the step text itself
163
+ const inlineCode = item.text.match(/`([^`]+)`/);
164
+ if (inlineCode)
165
+ code = inlineCode[1];
166
+ }
167
+ out.push({
168
+ text: item.text.replace(/`([^`]+)`/g, '$1').slice(0, 500),
169
+ ...(code ? { code } : {}),
170
+ sourceLine: item.idx,
171
+ });
172
+ if (out.length >= 20)
173
+ break;
174
+ }
175
+ // If we don't have list-based steps but DO have fenced code blocks,
176
+ // synthesize each block as a step.
177
+ if (out.length === 0 && fenced.length >= 2) {
178
+ for (const f of fenced) {
179
+ out.push({
180
+ text: 'Run',
181
+ code: f[1].trim().slice(0, 500),
182
+ });
183
+ if (out.length >= 10)
184
+ break;
185
+ }
186
+ }
187
+ return out;
188
+ }
189
+ /** Extract a title from the text — first markdown heading, or imperative line. */
190
+ function extractTitle(text, lines, steps) {
191
+ // Markdown heading first
192
+ const headingMatch = text.match(/^#+\s+(.{5,120}?)$/m);
193
+ if (headingMatch)
194
+ return headingMatch[1].trim();
195
+ // First line if it's short + descriptive
196
+ const firstLine = lines.find(l => l.trim().length > 0);
197
+ if (firstLine && firstLine.length < 100 && !firstLine.match(/^[\d-*•]/)) {
198
+ return firstLine.trim();
199
+ }
200
+ // Synthesize from first step
201
+ if (steps.length > 0) {
202
+ return `${steps[0].text.slice(0, 80)}...`;
203
+ }
204
+ return null;
205
+ }
206
+ //# sourceMappingURL=runbook-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runbook-detector.js","sourceRoot":"","sources":["../../../src/lib/context-engine/runbook-detector.ts"],"names":[],"mappings":"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"}