ruvector 0.2.32 → 0.2.33

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 (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2303 -2302
  3. package/bin/cli.js +10245 -10237
  4. package/bin/mcp-policy.js +95 -95
  5. package/bin/mcp-server.js +4054 -4054
  6. package/dist/core/onnx/bundled-parallel.mjs +169 -169
  7. package/dist/core/onnx/embed-worker.mjs +67 -67
  8. package/dist/core/onnx/loader.js +485 -461
  9. package/dist/core/onnx/package.json +3 -3
  10. package/dist/core/onnx/pkg/LICENSE +21 -21
  11. package/dist/core/onnx/pkg/loader.js +348 -348
  12. package/dist/core/onnx/pkg/package.json +3 -3
  13. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.d.ts +112 -112
  14. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js +5 -5
  15. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.js +638 -638
  16. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.wasm.d.ts +29 -29
  17. package/dist/core/parallel-workers.js +439 -439
  18. package/dist/workers/benchmark.js +15 -15
  19. package/package.json +124 -123
  20. package/src/decompiler/api-prober.js +302 -302
  21. package/src/decompiler/index.js +463 -463
  22. package/src/decompiler/metrics.js +86 -86
  23. package/src/decompiler/model-decompiler.js +423 -423
  24. package/src/decompiler/module-splitter.js +498 -498
  25. package/src/decompiler/module-tree.js +142 -142
  26. package/src/decompiler/name-predictor.js +400 -400
  27. package/src/decompiler/npm-fetch.js +176 -176
  28. package/src/decompiler/reconstructor.js +499 -499
  29. package/src/decompiler/reference-tracker.js +285 -285
  30. package/src/decompiler/statement-parser.js +285 -285
  31. package/src/decompiler/style-improver.js +438 -438
  32. package/src/decompiler/subcategories.js +339 -339
  33. package/src/decompiler/validator.js +379 -379
  34. package/src/decompiler/witness.js +140 -140
  35. package/src/optimizer/context.js +149 -0
  36. package/src/optimizer/index.js +177 -0
  37. package/src/optimizer/settings-generator.js +176 -0
  38. package/src/optimizer/tool-schemas.js +190 -0
  39. package/wasm/package.json +26 -26
  40. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  41. package/wasm/ruvector_decompiler_wasm.js +220 -220
  42. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
@@ -1,140 +1,140 @@
1
- /**
2
- * witness.js - SHA-256 witness chain generation and verification.
3
- *
4
- * A witness chain is a Merkle-like structure that cryptographically proves
5
- * the decompiled output derives from a specific input bundle.
6
- *
7
- * Chain structure:
8
- * root = H(source_hash || module_hashes[0] || ... || module_hashes[n])
9
- *
10
- * Each entry records:
11
- * { hash, label, parent }
12
- * so the chain can be verified without re-running the decompiler.
13
- */
14
-
15
- 'use strict';
16
-
17
- const crypto = require('crypto');
18
-
19
- /**
20
- * Compute SHA-256 hash of a string or buffer.
21
- * @param {string|Buffer} data
22
- * @returns {string} hex-encoded hash
23
- */
24
- function sha256(data) {
25
- return crypto.createHash('sha256').update(data).digest('hex');
26
- }
27
-
28
- /**
29
- * Build a witness chain from source and decompiled modules.
30
- *
31
- * @param {string} source - original bundle source code
32
- * @param {Array<{name: string, content: string}>} modules - decompiled modules
33
- * @returns {{
34
- * source_hash: string,
35
- * module_hashes: Array<{name: string, hash: string}>,
36
- * root: string,
37
- * chain: Array<{hash: string, label: string, parent: string|null}>,
38
- * created: string,
39
- * algorithm: string
40
- * }}
41
- */
42
- function buildWitnessChain(source, modules) {
43
- const sourceHash = sha256(source);
44
- const chain = [];
45
- const moduleHashes = [];
46
-
47
- // Root node: the source hash
48
- chain.push({
49
- hash: sourceHash,
50
- label: 'source',
51
- parent: null,
52
- });
53
-
54
- // One node per decompiled module
55
- for (const mod of modules) {
56
- const modHash = sha256(mod.content);
57
- moduleHashes.push({ name: mod.name, hash: modHash });
58
-
59
- chain.push({
60
- hash: modHash,
61
- label: `module:${mod.name}`,
62
- parent: sourceHash,
63
- });
64
- }
65
-
66
- // Compute Merkle root: H(source_hash || mod_hash_0 || ... || mod_hash_n)
67
- const allHashes = sourceHash + moduleHashes.map((m) => m.hash).join('');
68
- const root = sha256(allHashes);
69
-
70
- chain.push({
71
- hash: root,
72
- label: 'root',
73
- parent: sourceHash,
74
- });
75
-
76
- return {
77
- source_hash: sourceHash,
78
- module_hashes: moduleHashes,
79
- root,
80
- chain,
81
- created: new Date().toISOString(),
82
- algorithm: 'sha256',
83
- };
84
- }
85
-
86
- /**
87
- * Verify a witness chain against a source file.
88
- *
89
- * @param {object} witness - the witness object (from buildWitnessChain)
90
- * @param {string} [sourceContent] - original source to verify against (optional)
91
- * @returns {{valid: boolean, chain_length: number, root: string, errors: string[]}}
92
- */
93
- function verifyWitnessChain(witness, sourceContent) {
94
- const errors = [];
95
-
96
- if (!witness || !witness.chain || !witness.root) {
97
- return { valid: false, chain_length: 0, root: '', errors: ['Missing witness data'] };
98
- }
99
-
100
- // Verify source hash if content provided
101
- if (sourceContent) {
102
- const actualSourceHash = sha256(sourceContent);
103
- if (actualSourceHash !== witness.source_hash) {
104
- errors.push(
105
- `Source hash mismatch: expected ${witness.source_hash}, got ${actualSourceHash}`,
106
- );
107
- }
108
- }
109
-
110
- // Verify chain integrity: each node's parent must exist in the chain
111
- const hashSet = new Set(witness.chain.map((n) => n.hash));
112
- for (const node of witness.chain) {
113
- if (node.parent && !hashSet.has(node.parent)) {
114
- errors.push(`Broken chain: node ${node.label} references missing parent ${node.parent}`);
115
- }
116
- }
117
-
118
- // Recompute root from module hashes
119
- if (witness.module_hashes && witness.source_hash) {
120
- const allHashes =
121
- witness.source_hash + witness.module_hashes.map((m) => m.hash).join('');
122
- const expectedRoot = sha256(allHashes);
123
- if (expectedRoot !== witness.root) {
124
- errors.push(`Root mismatch: expected ${expectedRoot}, got ${witness.root}`);
125
- }
126
- }
127
-
128
- return {
129
- valid: errors.length === 0,
130
- chain_length: witness.chain.length,
131
- root: witness.root,
132
- errors,
133
- };
134
- }
135
-
136
- module.exports = {
137
- sha256,
138
- buildWitnessChain,
139
- verifyWitnessChain,
140
- };
1
+ /**
2
+ * witness.js - SHA-256 witness chain generation and verification.
3
+ *
4
+ * A witness chain is a Merkle-like structure that cryptographically proves
5
+ * the decompiled output derives from a specific input bundle.
6
+ *
7
+ * Chain structure:
8
+ * root = H(source_hash || module_hashes[0] || ... || module_hashes[n])
9
+ *
10
+ * Each entry records:
11
+ * { hash, label, parent }
12
+ * so the chain can be verified without re-running the decompiler.
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const crypto = require('crypto');
18
+
19
+ /**
20
+ * Compute SHA-256 hash of a string or buffer.
21
+ * @param {string|Buffer} data
22
+ * @returns {string} hex-encoded hash
23
+ */
24
+ function sha256(data) {
25
+ return crypto.createHash('sha256').update(data).digest('hex');
26
+ }
27
+
28
+ /**
29
+ * Build a witness chain from source and decompiled modules.
30
+ *
31
+ * @param {string} source - original bundle source code
32
+ * @param {Array<{name: string, content: string}>} modules - decompiled modules
33
+ * @returns {{
34
+ * source_hash: string,
35
+ * module_hashes: Array<{name: string, hash: string}>,
36
+ * root: string,
37
+ * chain: Array<{hash: string, label: string, parent: string|null}>,
38
+ * created: string,
39
+ * algorithm: string
40
+ * }}
41
+ */
42
+ function buildWitnessChain(source, modules) {
43
+ const sourceHash = sha256(source);
44
+ const chain = [];
45
+ const moduleHashes = [];
46
+
47
+ // Root node: the source hash
48
+ chain.push({
49
+ hash: sourceHash,
50
+ label: 'source',
51
+ parent: null,
52
+ });
53
+
54
+ // One node per decompiled module
55
+ for (const mod of modules) {
56
+ const modHash = sha256(mod.content);
57
+ moduleHashes.push({ name: mod.name, hash: modHash });
58
+
59
+ chain.push({
60
+ hash: modHash,
61
+ label: `module:${mod.name}`,
62
+ parent: sourceHash,
63
+ });
64
+ }
65
+
66
+ // Compute Merkle root: H(source_hash || mod_hash_0 || ... || mod_hash_n)
67
+ const allHashes = sourceHash + moduleHashes.map((m) => m.hash).join('');
68
+ const root = sha256(allHashes);
69
+
70
+ chain.push({
71
+ hash: root,
72
+ label: 'root',
73
+ parent: sourceHash,
74
+ });
75
+
76
+ return {
77
+ source_hash: sourceHash,
78
+ module_hashes: moduleHashes,
79
+ root,
80
+ chain,
81
+ created: new Date().toISOString(),
82
+ algorithm: 'sha256',
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Verify a witness chain against a source file.
88
+ *
89
+ * @param {object} witness - the witness object (from buildWitnessChain)
90
+ * @param {string} [sourceContent] - original source to verify against (optional)
91
+ * @returns {{valid: boolean, chain_length: number, root: string, errors: string[]}}
92
+ */
93
+ function verifyWitnessChain(witness, sourceContent) {
94
+ const errors = [];
95
+
96
+ if (!witness || !witness.chain || !witness.root) {
97
+ return { valid: false, chain_length: 0, root: '', errors: ['Missing witness data'] };
98
+ }
99
+
100
+ // Verify source hash if content provided
101
+ if (sourceContent) {
102
+ const actualSourceHash = sha256(sourceContent);
103
+ if (actualSourceHash !== witness.source_hash) {
104
+ errors.push(
105
+ `Source hash mismatch: expected ${witness.source_hash}, got ${actualSourceHash}`,
106
+ );
107
+ }
108
+ }
109
+
110
+ // Verify chain integrity: each node's parent must exist in the chain
111
+ const hashSet = new Set(witness.chain.map((n) => n.hash));
112
+ for (const node of witness.chain) {
113
+ if (node.parent && !hashSet.has(node.parent)) {
114
+ errors.push(`Broken chain: node ${node.label} references missing parent ${node.parent}`);
115
+ }
116
+ }
117
+
118
+ // Recompute root from module hashes
119
+ if (witness.module_hashes && witness.source_hash) {
120
+ const allHashes =
121
+ witness.source_hash + witness.module_hashes.map((m) => m.hash).join('');
122
+ const expectedRoot = sha256(allHashes);
123
+ if (expectedRoot !== witness.root) {
124
+ errors.push(`Root mismatch: expected ${expectedRoot}, got ${witness.root}`);
125
+ }
126
+ }
127
+
128
+ return {
129
+ valid: errors.length === 0,
130
+ chain_length: witness.chain.length,
131
+ root: witness.root,
132
+ errors,
133
+ };
134
+ }
135
+
136
+ module.exports = {
137
+ sha256,
138
+ buildWitnessChain,
139
+ verifyWitnessChain,
140
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Context window optimization based on Claude Code's compaction internals.
3
+ *
4
+ * The decompilation of v2.1.91 revealed:
5
+ * - Compaction triggers at ~80% of the context window
6
+ * - The `clear_tool_uses_20250919` API feature enables server-side compaction
7
+ * - Deferred tool loading via ToolSearch saves ~2000 tokens per unused tool
8
+ * - Prompt cache sharing (`promptCacheSharingEnabled`) caches static prefixes
9
+ *
10
+ * This module helps RVAgent structure prompts and manage context to
11
+ * maximise cache hits and minimise unnecessary token usage.
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const { TOOL_NAMES } = require('./tool-schemas');
17
+
18
+ /** Default context window sizes by model family. */
19
+ const MODEL_CONTEXT_SIZES = {
20
+ 'claude-sonnet-4-20250514': 200000,
21
+ 'claude-opus-4-20250514': 200000,
22
+ 'claude-haiku-3.5': 200000,
23
+ default: 200000,
24
+ };
25
+
26
+ /** Approximate token overhead per tool schema sent in the system prompt. */
27
+ const TOKENS_PER_TOOL_SCHEMA = 120;
28
+
29
+ /**
30
+ * Build a deferred tool list — instead of sending all 25+ tool schemas
31
+ * up front (costing ~3000 tokens), only include the tools the task
32
+ * actually needs. The rest can be fetched on demand via ToolSearch.
33
+ *
34
+ * @param {string[]} requiredTools - tool names this task needs
35
+ * @returns {{ included: string[], deferred: string[], tokensSaved: number }}
36
+ */
37
+ function buildDeferredToolList(requiredTools) {
38
+ const required = new Set(requiredTools || []);
39
+ const included = [];
40
+ const deferred = [];
41
+
42
+ for (const tool of TOOL_NAMES) {
43
+ if (required.has(tool)) {
44
+ included.push(tool);
45
+ } else {
46
+ deferred.push(tool);
47
+ }
48
+ }
49
+
50
+ const tokensSaved = deferred.length * TOKENS_PER_TOOL_SCHEMA;
51
+ return { included, deferred, tokensSaved };
52
+ }
53
+
54
+ /**
55
+ * Build a cache-optimised prompt structure.
56
+ *
57
+ * Claude Code's `promptCacheSharingEnabled` caches the longest common
58
+ * prefix across requests. To maximise cache hits:
59
+ * 1. Put static content first (tool schemas, CLAUDE.md rules)
60
+ * 2. Put dynamic/session-specific content after
61
+ *
62
+ * @param {string} staticRules - stable content (tool schemas, rules)
63
+ * @param {string} dynamicContext - session-specific content (files, history)
64
+ * @returns {{ cached: string, dynamic: string, totalTokensEstimate: number }}
65
+ */
66
+ function buildCacheOptimizedPrompt(staticRules, dynamicContext) {
67
+ const staticTokens = estimateTokens(staticRules);
68
+ const dynamicTokens = estimateTokens(dynamicContext);
69
+
70
+ return {
71
+ cached: staticRules,
72
+ dynamic: dynamicContext,
73
+ totalTokensEstimate: staticTokens + dynamicTokens,
74
+ cacheableTokens: staticTokens,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Determine whether compaction should be triggered.
80
+ *
81
+ * From the decompiled source, Claude Code triggers compaction when the
82
+ * token count reaches approximately 80% of the context window. This
83
+ * function mirrors that threshold.
84
+ *
85
+ * @param {number} tokenCount - current token usage
86
+ * @param {number} [maxContext] - max context window (default 200000)
87
+ * @returns {boolean}
88
+ */
89
+ function shouldCompact(tokenCount, maxContext) {
90
+ const max = maxContext || MODEL_CONTEXT_SIZES.default;
91
+ return tokenCount > max * 0.8;
92
+ }
93
+
94
+ /**
95
+ * Estimate the number of tokens in a string.
96
+ * Uses the common heuristic of ~4 characters per token for English text.
97
+ *
98
+ * @param {string} text
99
+ * @returns {number}
100
+ */
101
+ function estimateTokens(text) {
102
+ if (!text) return 0;
103
+ return Math.ceil(text.length / 4);
104
+ }
105
+
106
+ /**
107
+ * Recommend which tools to include for a given task type.
108
+ * This avoids sending all tool schemas when only a subset is needed.
109
+ *
110
+ * @param {string} taskType
111
+ * @returns {string[]}
112
+ */
113
+ function recommendToolsForTask(taskType) {
114
+ const base = ['Read', 'Bash', 'Glob', 'Grep'];
115
+
116
+ const taskTools = {
117
+ coding: [...base, 'Edit', 'Write', 'Agent', 'ToolSearch'],
118
+ research: [...base, 'WebFetch', 'WebSearch', 'Agent'],
119
+ quickfix: [...base, 'Edit'],
120
+ planning: [...base, 'Agent', 'TodoWrite'],
121
+ background: [...base, 'Edit', 'Write', 'Agent'],
122
+ swarm: [...base, 'Edit', 'Write', 'Agent', 'ToolSearch'],
123
+ review: [...base],
124
+ ci: [...base, 'Edit', 'Write'],
125
+ };
126
+
127
+ return taskTools[taskType] || base;
128
+ }
129
+
130
+ /**
131
+ * Get context window size for a model identifier.
132
+ *
133
+ * @param {string} model
134
+ * @returns {number}
135
+ */
136
+ function getContextSize(model) {
137
+ return MODEL_CONTEXT_SIZES[model] || MODEL_CONTEXT_SIZES.default;
138
+ }
139
+
140
+ module.exports = {
141
+ MODEL_CONTEXT_SIZES,
142
+ TOKENS_PER_TOOL_SCHEMA,
143
+ buildDeferredToolList,
144
+ buildCacheOptimizedPrompt,
145
+ shouldCompact,
146
+ estimateTokens,
147
+ recommendToolsForTask,
148
+ getContextSize,
149
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * RVAgent Optimizer — Claude Code configuration profiles (ADR-139).
3
+ *
4
+ * Maps a task type (coding, research, quickfix, …) to an optimal set of
5
+ * `CLAUDE_CODE_*` environment variables and a permission mode. Profiles are
6
+ * derived from the decompiled Claude Code intelligence (see ADR-139) and are
7
+ * consumed by `npx ruvector optimize` and the settings generator.
8
+ *
9
+ * Public API:
10
+ * - PERMISSION_MODES — valid permission-mode strings
11
+ * - listProfiles() — array of profile names
12
+ * - getProfile(name) — { description, permissionMode, env } | null
13
+ * - applyProfile(name) — sets process.env, returns { applied, permissionMode } | null
14
+ * - detectTaskType(prompt) — infer a profile name from a free-text prompt
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /** Permission modes understood by Claude Code's harness. */
20
+ const PERMISSION_MODES = [
21
+ 'default',
22
+ 'acceptEdits',
23
+ 'bypassPermissions',
24
+ 'plan',
25
+ 'dontAsk',
26
+ 'auto',
27
+ ];
28
+
29
+ /**
30
+ * Task profiles. Every env key MUST start with `CLAUDE_CODE_` and every
31
+ * `permissionMode` MUST be a member of {@link PERMISSION_MODES}.
32
+ */
33
+ const PROFILES = {
34
+ coding: {
35
+ description: 'Implementation work — edits with checkpointing and thinking enabled',
36
+ permissionMode: 'acceptEdits',
37
+ env: {
38
+ CLAUDE_CODE_EFFORT_LEVEL: 'high',
39
+ CLAUDE_CODE_THINKING: '1',
40
+ CLAUDE_CODE_AUTO_COMPACT: '1',
41
+ CLAUDE_CODE_FILE_CHECKPOINTING: '1',
42
+ },
43
+ },
44
+ research: {
45
+ description: 'Investigation and analysis — read-heavy, no edits, deep reasoning',
46
+ permissionMode: 'default',
47
+ env: {
48
+ CLAUDE_CODE_EFFORT_LEVEL: 'high',
49
+ CLAUDE_CODE_THINKING: '1',
50
+ CLAUDE_CODE_AUTO_COMPACT: '1',
51
+ },
52
+ },
53
+ quickfix: {
54
+ description: 'Small, fast fixes — brief output, low effort, quick turnaround',
55
+ permissionMode: 'acceptEdits',
56
+ env: {
57
+ CLAUDE_CODE_BRIEF: '1',
58
+ CLAUDE_CODE_EFFORT_LEVEL: 'low',
59
+ CLAUDE_CODE_THINKING: '0',
60
+ },
61
+ },
62
+ planning: {
63
+ description: 'Architecture and design — plan mode, no edits until approved',
64
+ permissionMode: 'plan',
65
+ env: {
66
+ CLAUDE_CODE_EFFORT_LEVEL: 'high',
67
+ CLAUDE_CODE_THINKING: '1',
68
+ },
69
+ },
70
+ background: {
71
+ description: 'Long-running background/daemon work — autonomous, minimal prompts',
72
+ permissionMode: 'bypassPermissions',
73
+ env: {
74
+ CLAUDE_CODE_EFFORT_LEVEL: 'medium',
75
+ CLAUDE_CODE_AUTO_COMPACT: '1',
76
+ CLAUDE_CODE_PROMPT_SUGGESTION: '0',
77
+ },
78
+ },
79
+ swarm: {
80
+ description: 'Multi-agent swarm coordination — autonomous, hooks-driven',
81
+ permissionMode: 'bypassPermissions',
82
+ env: {
83
+ CLAUDE_CODE_EFFORT_LEVEL: 'medium',
84
+ CLAUDE_CODE_AUTO_COMPACT: '1',
85
+ CLAUDE_CODE_THINKING: '1',
86
+ },
87
+ },
88
+ review: {
89
+ description: 'Code review — read-only, no edits, thorough reasoning',
90
+ permissionMode: 'default',
91
+ env: {
92
+ CLAUDE_CODE_EFFORT_LEVEL: 'high',
93
+ CLAUDE_CODE_THINKING: '1',
94
+ CLAUDE_CODE_BRIEF: '0',
95
+ },
96
+ },
97
+ ci: {
98
+ description: 'CI / automation pipelines — fast, non-interactive, no suggestions',
99
+ permissionMode: 'dontAsk',
100
+ env: {
101
+ CLAUDE_CODE_FAST_MODE: '1',
102
+ CLAUDE_CODE_EFFORT_LEVEL: 'low',
103
+ CLAUDE_CODE_PROMPT_SUGGESTION: '0',
104
+ },
105
+ },
106
+ };
107
+
108
+ /**
109
+ * Keyword → profile rules, evaluated in order. The first rule whose regex
110
+ * matches the prompt wins, so more specific task types are listed first.
111
+ */
112
+ const DETECTION_RULES = [
113
+ { type: 'quickfix', re: /\b(typo|quick\s?-?fix|one-?liner|small fix|rename)\b/i },
114
+ { type: 'ci', re: /\b(ci|cd|pipeline|github actions|workflow|deploy)\b/i },
115
+ { type: 'swarm', re: /\b(swarm|multi-?agent|coordinate|orchestrat\w*|hive)\b/i },
116
+ { type: 'background', re: /\b(background|daemon|monitor\w*|watch\b|long-?running)\b/i },
117
+ { type: 'review', re: /\b(review|pull request|\bpr\b|audit|critique)\b/i },
118
+ { type: 'planning', re: /\b(plan\w*|architect\w*|design|roadmap|strategy)\b/i },
119
+ { type: 'research', re: /\b(research|investigat\w*|explore|compare|survey)\b/i },
120
+ { type: 'coding', re: /\b(implement|code|build|add|write|refactor|create|fix|function|feature)\b/i },
121
+ ];
122
+
123
+ /** List all available profile names. */
124
+ function listProfiles() {
125
+ return Object.keys(PROFILES);
126
+ }
127
+
128
+ /** Return a profile by name, or `null` if it does not exist. */
129
+ function getProfile(name) {
130
+ if (!name || !Object.prototype.hasOwnProperty.call(PROFILES, name)) {
131
+ return null;
132
+ }
133
+ return PROFILES[name];
134
+ }
135
+
136
+ /**
137
+ * Apply a profile's env vars to `process.env`. Returns `{ applied, permissionMode }`
138
+ * where `applied` is the map of env vars that were set, or `null` if the profile
139
+ * is unknown.
140
+ */
141
+ function applyProfile(name) {
142
+ const profile = getProfile(name);
143
+ if (!profile) {
144
+ return null;
145
+ }
146
+ const applied = {};
147
+ for (const [key, val] of Object.entries(profile.env)) {
148
+ process.env[key] = val;
149
+ applied[key] = val;
150
+ }
151
+ return { applied, permissionMode: profile.permissionMode };
152
+ }
153
+
154
+ /**
155
+ * Infer the most likely task type from a free-text prompt. Defaults to
156
+ * `'coding'` for empty/unrecognised input.
157
+ */
158
+ function detectTaskType(prompt) {
159
+ if (!prompt || typeof prompt !== 'string') {
160
+ return 'coding';
161
+ }
162
+ for (const rule of DETECTION_RULES) {
163
+ if (rule.re.test(prompt)) {
164
+ return rule.type;
165
+ }
166
+ }
167
+ return 'coding';
168
+ }
169
+
170
+ module.exports = {
171
+ PERMISSION_MODES,
172
+ PROFILES,
173
+ listProfiles,
174
+ getProfile,
175
+ applyProfile,
176
+ detectTaskType,
177
+ };