smart-context-mcp 1.1.0 → 1.2.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.
- package/README.md +110 -20
- package/package.json +10 -7
- package/scripts/init-clients.js +56 -27
- package/scripts/report-metrics.js +5 -0
- package/scripts/report-workflow-metrics.js +255 -0
- package/src/analytics/adoption.js +197 -0
- package/src/storage/sqlite.js +30 -1
- package/src/tools/smart-metrics.js +7 -0
- package/src/tools/smart-read-batch.js +9 -0
- package/src/tools/smart-read.js +21 -1
- package/src/tools/smart-shell.js +33 -9
- package/src/tools/smart-turn.js +1 -0
- package/src/workflow-tracker-stub.js +53 -0
- package/src/workflow-tracker.js +410 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adoption analytics - measures how often agents use devctx tools in practice
|
|
3
|
+
*
|
|
4
|
+
* Limitations:
|
|
5
|
+
* - Complexity is inferred from operation count, not actual task complexity
|
|
6
|
+
* - Can't detect feedback shown (requires agent cooperation)
|
|
7
|
+
* - Can't detect forcing prompts (requires prompt analysis)
|
|
8
|
+
* - Can only measure when devctx IS used, not when it's ignored
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEVCTX_TOOLS = new Set([
|
|
12
|
+
'smart_read',
|
|
13
|
+
'smart_search',
|
|
14
|
+
'smart_context',
|
|
15
|
+
'smart_shell',
|
|
16
|
+
'smart_summary',
|
|
17
|
+
'smart_turn',
|
|
18
|
+
'smart_read_batch',
|
|
19
|
+
'smart_metrics',
|
|
20
|
+
'build_index',
|
|
21
|
+
'warm_cache',
|
|
22
|
+
'git_blame',
|
|
23
|
+
'cross_project',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const inferComplexity = (opCount, fileCount) => {
|
|
27
|
+
if (opCount <= 2 && fileCount <= 1) return 'trivial';
|
|
28
|
+
if (opCount <= 5 && fileCount <= 3) return 'simple';
|
|
29
|
+
if (opCount <= 15 && fileCount <= 10) return 'moderate';
|
|
30
|
+
return 'complex';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const groupBySession = (entries) => {
|
|
34
|
+
const sessions = new Map();
|
|
35
|
+
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const sessionId = entry.sessionId || entry.session_id || 'unknown';
|
|
38
|
+
|
|
39
|
+
if (!sessions.has(sessionId)) {
|
|
40
|
+
sessions.set(sessionId, {
|
|
41
|
+
sessionId,
|
|
42
|
+
operations: [],
|
|
43
|
+
devctxTools: new Set(),
|
|
44
|
+
nativeTools: new Set(),
|
|
45
|
+
filesAccessed: new Set(),
|
|
46
|
+
totalRawTokens: 0,
|
|
47
|
+
totalCompressedTokens: 0,
|
|
48
|
+
totalSavedTokens: 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const session = sessions.get(sessionId);
|
|
53
|
+
session.operations.push(entry);
|
|
54
|
+
|
|
55
|
+
const tool = entry.tool;
|
|
56
|
+
if (DEVCTX_TOOLS.has(tool)) {
|
|
57
|
+
session.devctxTools.add(tool);
|
|
58
|
+
} else if (tool) {
|
|
59
|
+
session.nativeTools.add(tool);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (entry.target) {
|
|
63
|
+
session.filesAccessed.add(entry.target);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
session.totalRawTokens += Number(entry.rawTokens || entry.raw_tokens || 0);
|
|
67
|
+
session.totalCompressedTokens += Number(entry.compressedTokens || entry.compressed_tokens || 0);
|
|
68
|
+
session.totalSavedTokens += Number(entry.savedTokens || entry.saved_tokens || 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Array.from(sessions.values());
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const analyzeAdoption = (entries) => {
|
|
75
|
+
const sessions = groupBySession(entries);
|
|
76
|
+
|
|
77
|
+
const sessionsWithDevctx = sessions.filter(s => s.devctxTools.size > 0);
|
|
78
|
+
const sessionsWithoutDevctx = sessions.filter(s => s.devctxTools.size === 0 && s.operations.length > 0);
|
|
79
|
+
|
|
80
|
+
const byComplexity = {
|
|
81
|
+
trivial: { total: 0, withDevctx: 0 },
|
|
82
|
+
simple: { total: 0, withDevctx: 0 },
|
|
83
|
+
moderate: { total: 0, withDevctx: 0 },
|
|
84
|
+
complex: { total: 0, withDevctx: 0 },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
for (const session of sessions) {
|
|
88
|
+
const complexity = inferComplexity(session.operations.length, session.filesAccessed.size);
|
|
89
|
+
byComplexity[complexity].total += 1;
|
|
90
|
+
if (session.devctxTools.size > 0) {
|
|
91
|
+
byComplexity[complexity].withDevctx += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const nonTrivialSessions = sessions.filter(s => {
|
|
96
|
+
const complexity = inferComplexity(s.operations.length, s.filesAccessed.size);
|
|
97
|
+
return complexity !== 'trivial';
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const nonTrivialWithDevctx = nonTrivialSessions.filter(s => s.devctxTools.size > 0);
|
|
101
|
+
|
|
102
|
+
const toolUsageCount = {};
|
|
103
|
+
for (const session of sessionsWithDevctx) {
|
|
104
|
+
for (const tool of session.devctxTools) {
|
|
105
|
+
toolUsageCount[tool] = (toolUsageCount[tool] || 0) + 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
totalSessions: sessions.length,
|
|
111
|
+
sessionsWithDevctx: sessionsWithDevctx.length,
|
|
112
|
+
sessionsWithoutDevctx: sessionsWithoutDevctx.length,
|
|
113
|
+
adoptionRate: sessions.length > 0
|
|
114
|
+
? Number(((sessionsWithDevctx.length / sessions.length) * 100).toFixed(1))
|
|
115
|
+
: 0,
|
|
116
|
+
|
|
117
|
+
nonTrivial: {
|
|
118
|
+
total: nonTrivialSessions.length,
|
|
119
|
+
withDevctx: nonTrivialWithDevctx.length,
|
|
120
|
+
adoptionRate: nonTrivialSessions.length > 0
|
|
121
|
+
? Number(((nonTrivialWithDevctx.length / nonTrivialSessions.length) * 100).toFixed(1))
|
|
122
|
+
: 0,
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
byComplexity: Object.fromEntries(
|
|
126
|
+
Object.entries(byComplexity).map(([level, stats]) => [
|
|
127
|
+
level,
|
|
128
|
+
{
|
|
129
|
+
...stats,
|
|
130
|
+
adoptionRate: stats.total > 0
|
|
131
|
+
? Number(((stats.withDevctx / stats.total) * 100).toFixed(1))
|
|
132
|
+
: 0,
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
toolUsageCount,
|
|
138
|
+
|
|
139
|
+
avgToolsPerSession: sessionsWithDevctx.length > 0
|
|
140
|
+
? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.devctxTools.size, 0) / sessionsWithDevctx.length).toFixed(1))
|
|
141
|
+
: 0,
|
|
142
|
+
|
|
143
|
+
avgTokenSavingsWhenUsed: sessionsWithDevctx.length > 0
|
|
144
|
+
? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.totalSavedTokens, 0) / sessionsWithDevctx.length).toFixed(0))
|
|
145
|
+
: 0,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const formatAdoptionReport = (stats) => {
|
|
150
|
+
const lines = [];
|
|
151
|
+
|
|
152
|
+
lines.push('');
|
|
153
|
+
lines.push('Adoption Analysis (Inferred from Tool Usage)');
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push(`Total sessions: ${stats.totalSessions}`);
|
|
156
|
+
lines.push(`Sessions with devctx: ${stats.sessionsWithDevctx} (${stats.adoptionRate}%)`);
|
|
157
|
+
lines.push(`Sessions without: ${stats.sessionsWithoutDevctx} (${100 - stats.adoptionRate}%)`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
160
|
+
lines.push('Non-Trivial Tasks Only:');
|
|
161
|
+
lines.push(`Total: ${stats.nonTrivial.total}`);
|
|
162
|
+
lines.push(`With devctx: ${stats.nonTrivial.withDevctx} (${stats.nonTrivial.adoptionRate}%)`);
|
|
163
|
+
lines.push(`Without devctx: ${stats.nonTrivial.total - stats.nonTrivial.withDevctx} (${100 - stats.nonTrivial.adoptionRate}%)`);
|
|
164
|
+
lines.push('');
|
|
165
|
+
|
|
166
|
+
lines.push('By Inferred Complexity:');
|
|
167
|
+
for (const [level, data] of Object.entries(stats.byComplexity)) {
|
|
168
|
+
if (data.total === 0) continue;
|
|
169
|
+
lines.push(`- ${level.padEnd(10)} ${data.withDevctx}/${data.total} (${data.adoptionRate}%)`);
|
|
170
|
+
}
|
|
171
|
+
lines.push('');
|
|
172
|
+
|
|
173
|
+
if (stats.sessionsWithDevctx > 0) {
|
|
174
|
+
lines.push('When devctx IS used:');
|
|
175
|
+
lines.push(`Avg tools/session: ${stats.avgToolsPerSession}`);
|
|
176
|
+
lines.push(`Avg token savings: ${stats.avgTokenSavingsWhenUsed.toLocaleString()} tokens`);
|
|
177
|
+
lines.push('');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lines.push('Top Tools Used:');
|
|
181
|
+
const sortedTools = Object.entries(stats.toolUsageCount)
|
|
182
|
+
.sort((a, b) => b[1] - a[1])
|
|
183
|
+
.slice(0, 5);
|
|
184
|
+
for (const [tool, count] of sortedTools) {
|
|
185
|
+
lines.push(`- ${tool.padEnd(20)} ${count} sessions`);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
|
|
189
|
+
lines.push('Limitations:');
|
|
190
|
+
lines.push('- Complexity inferred from operation count (not actual task complexity)');
|
|
191
|
+
lines.push('- Can only measure when devctx IS used (tool calls visible)');
|
|
192
|
+
lines.push('- Cannot measure feedback shown or forcing prompts (requires agent cooperation)');
|
|
193
|
+
lines.push('- Sessions without devctx may be simple tasks (not adoption failures)');
|
|
194
|
+
lines.push('');
|
|
195
|
+
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
};
|
package/src/storage/sqlite.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { projectRoot } from '../utils/runtime-config.js';
|
|
6
6
|
|
|
7
7
|
export const STATE_DB_FILENAME = 'state.sqlite';
|
|
8
|
-
export const SQLITE_SCHEMA_VERSION =
|
|
8
|
+
export const SQLITE_SCHEMA_VERSION = 5;
|
|
9
9
|
export const ACTIVE_SESSION_SCOPE = 'project';
|
|
10
10
|
export const EXPECTED_TABLES = [
|
|
11
11
|
'active_session',
|
|
@@ -16,6 +16,7 @@ export const EXPECTED_TABLES = [
|
|
|
16
16
|
'session_events',
|
|
17
17
|
'sessions',
|
|
18
18
|
'summary_cache',
|
|
19
|
+
'workflow_metrics',
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
const MIGRATIONS = [
|
|
@@ -145,6 +146,34 @@ const MIGRATIONS = [
|
|
|
145
146
|
ON context_access(session_id, timestamp DESC)`,
|
|
146
147
|
],
|
|
147
148
|
},
|
|
149
|
+
{
|
|
150
|
+
version: 5,
|
|
151
|
+
statements: [
|
|
152
|
+
`CREATE TABLE IF NOT EXISTS workflow_metrics (
|
|
153
|
+
workflow_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
+
workflow_type TEXT NOT NULL,
|
|
155
|
+
session_id TEXT,
|
|
156
|
+
start_time TEXT NOT NULL,
|
|
157
|
+
end_time TEXT,
|
|
158
|
+
duration_ms INTEGER,
|
|
159
|
+
tools_used_json TEXT NOT NULL DEFAULT '[]',
|
|
160
|
+
steps_count INTEGER NOT NULL DEFAULT 0,
|
|
161
|
+
raw_tokens INTEGER NOT NULL DEFAULT 0,
|
|
162
|
+
compressed_tokens INTEGER NOT NULL DEFAULT 0,
|
|
163
|
+
saved_tokens INTEGER NOT NULL DEFAULT 0,
|
|
164
|
+
savings_pct REAL NOT NULL DEFAULT 0,
|
|
165
|
+
baseline_tokens INTEGER NOT NULL DEFAULT 0,
|
|
166
|
+
vs_baseline_pct REAL NOT NULL DEFAULT 0,
|
|
167
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
168
|
+
created_at TEXT NOT NULL,
|
|
169
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE SET NULL
|
|
170
|
+
)`,
|
|
171
|
+
`CREATE INDEX IF NOT EXISTS idx_workflow_metrics_type_created
|
|
172
|
+
ON workflow_metrics(workflow_type, created_at DESC)`,
|
|
173
|
+
`CREATE INDEX IF NOT EXISTS idx_workflow_metrics_session
|
|
174
|
+
ON workflow_metrics(session_id, created_at DESC)`,
|
|
175
|
+
],
|
|
176
|
+
},
|
|
148
177
|
];
|
|
149
178
|
|
|
150
179
|
let sqliteModulePromise = null;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
readMetricsEntries,
|
|
15
15
|
resolveMetricsInput,
|
|
16
16
|
} from '../metrics.js';
|
|
17
|
+
import { analyzeAdoption } from '../analytics/adoption.js';
|
|
17
18
|
|
|
18
19
|
const WINDOW_MS = {
|
|
19
20
|
'24h': 24 * 60 * 60 * 1000,
|
|
@@ -197,6 +198,8 @@ export const smartMetrics = async ({
|
|
|
197
198
|
.filter((entry) => (tool ? entry.tool === tool : true))
|
|
198
199
|
.filter((entry) => (resolvedSessionId ? entry.sessionId === resolvedSessionId : true));
|
|
199
200
|
|
|
201
|
+
const adoption = analyzeAdoption(filteredEntries);
|
|
202
|
+
|
|
200
203
|
return {
|
|
201
204
|
filePath: resolved.storagePath,
|
|
202
205
|
storagePath: resolved.storagePath,
|
|
@@ -212,6 +215,7 @@ export const smartMetrics = async ({
|
|
|
212
215
|
},
|
|
213
216
|
invalidLines,
|
|
214
217
|
summary: aggregateMetrics(filteredEntries),
|
|
218
|
+
adoption,
|
|
215
219
|
latestEntries: buildLatestEntries(filteredEntries, latest),
|
|
216
220
|
};
|
|
217
221
|
}
|
|
@@ -229,6 +233,8 @@ export const smartMetrics = async ({
|
|
|
229
233
|
window,
|
|
230
234
|
});
|
|
231
235
|
|
|
236
|
+
const adoption = analyzeAdoption(entries);
|
|
237
|
+
|
|
232
238
|
return {
|
|
233
239
|
filePath: resolved.storagePath,
|
|
234
240
|
storagePath: resolved.storagePath,
|
|
@@ -244,6 +250,7 @@ export const smartMetrics = async ({
|
|
|
244
250
|
},
|
|
245
251
|
invalidLines,
|
|
246
252
|
summary: aggregateMetrics(entries),
|
|
253
|
+
adoption,
|
|
247
254
|
latestEntries: buildLatestEntries(entries, latest),
|
|
248
255
|
};
|
|
249
256
|
};
|
|
@@ -18,6 +18,15 @@ export const smartReadBatch = async ({ files, maxTokens }) => {
|
|
|
18
18
|
maxTokens: item.maxTokens,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
if (readResult.error) {
|
|
22
|
+
results.push({
|
|
23
|
+
filePath: item.path,
|
|
24
|
+
mode: item.mode ?? 'outline',
|
|
25
|
+
error: readResult.error,
|
|
26
|
+
});
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
const itemTokens = countTokens(readResult.content);
|
|
22
31
|
|
|
23
32
|
if (maxTokens && totalTokens + itemTokens > maxTokens && results.length > 0) {
|
package/src/tools/smart-read.js
CHANGED
|
@@ -363,7 +363,27 @@ const formatContextSections = (sections) => {
|
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
export const smartRead = async ({ filePath, mode = 'outline', startLine, endLine, symbol, maxTokens, context: includeContext }) => {
|
|
366
|
-
|
|
366
|
+
let fullPath, content;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const result = readTextFile(filePath);
|
|
370
|
+
fullPath = result.fullPath;
|
|
371
|
+
content = result.content;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const errorMessage = error.message || String(error);
|
|
374
|
+
return {
|
|
375
|
+
error: errorMessage,
|
|
376
|
+
filePath,
|
|
377
|
+
mode,
|
|
378
|
+
metrics: buildMetrics({
|
|
379
|
+
tool: 'smart_read',
|
|
380
|
+
target: filePath,
|
|
381
|
+
rawText: '',
|
|
382
|
+
compressedText: errorMessage,
|
|
383
|
+
}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
367
387
|
const extension = path.extname(fullPath).toLowerCase();
|
|
368
388
|
const mtime = getFileMtime(fullPath);
|
|
369
389
|
|
package/src/tools/smart-shell.js
CHANGED
|
@@ -6,11 +6,21 @@ import { projectRoot } from '../utils/paths.js';
|
|
|
6
6
|
import { pickRelevantLines, truncate, uniqueLines } from '../utils/text.js';
|
|
7
7
|
|
|
8
8
|
const execFile = promisify(execFileCallback);
|
|
9
|
-
const
|
|
9
|
+
const isShellDisabled = () => process.env.DEVCTX_SHELL_DISABLED === 'true';
|
|
10
|
+
const blockedPattern = /[|&;<>`\n\r$()]/;
|
|
10
11
|
const allowedCommands = new Set(['pwd', 'ls', 'find', 'rg', 'git', 'npm', 'pnpm', 'yarn', 'bun']);
|
|
11
|
-
const allowedGitSubcommands = new Set(['status', 'diff', 'show', 'log', 'branch', 'rev-parse']);
|
|
12
|
+
const allowedGitSubcommands = new Set(['status', 'diff', 'show', 'log', 'branch', 'rev-parse', 'blame']);
|
|
12
13
|
const allowedPackageManagerSubcommands = new Set(['test', 'run', 'lint', 'build', 'typecheck', 'check']);
|
|
13
|
-
const safeRunScriptPattern = /^(test|lint|build|typecheck|check|smoke|verify)(:|$)/;
|
|
14
|
+
const safeRunScriptPattern = /^(test|lint|build|typecheck|check|smoke|verify|eval)(:|$)/;
|
|
15
|
+
const dangerousPatterns = [
|
|
16
|
+
/rm\s+-rf/i,
|
|
17
|
+
/sudo/i,
|
|
18
|
+
/curl.*\|/i,
|
|
19
|
+
/wget.*\|/i,
|
|
20
|
+
/eval/i,
|
|
21
|
+
/exec/i,
|
|
22
|
+
];
|
|
23
|
+
const MAX_COMMAND_LENGTH = 500;
|
|
14
24
|
|
|
15
25
|
const tokenize = (command) => {
|
|
16
26
|
const tokens = [];
|
|
@@ -67,12 +77,26 @@ const tokenize = (command) => {
|
|
|
67
77
|
};
|
|
68
78
|
|
|
69
79
|
const validateCommand = (command, tokens) => {
|
|
80
|
+
if (isShellDisabled()) {
|
|
81
|
+
return 'Shell execution is disabled (DEVCTX_SHELL_DISABLED=true)';
|
|
82
|
+
}
|
|
83
|
+
|
|
70
84
|
if (!command.trim()) {
|
|
71
85
|
return 'Command is empty';
|
|
72
86
|
}
|
|
73
87
|
|
|
74
|
-
if (
|
|
75
|
-
return
|
|
88
|
+
if (command.length > MAX_COMMAND_LENGTH) {
|
|
89
|
+
return `Command too long (max ${MAX_COMMAND_LENGTH} chars)`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (blockedPattern.test(command)) {
|
|
93
|
+
return 'Shell operators are not allowed (|, &, ;, <, >, `, $, (, ))';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const pattern of dangerousPatterns) {
|
|
97
|
+
if (pattern.test(command)) {
|
|
98
|
+
return `Dangerous pattern detected: ${pattern.source}`;
|
|
99
|
+
}
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
if (tokens.length === 0) {
|
|
@@ -82,11 +106,11 @@ const validateCommand = (command, tokens) => {
|
|
|
82
106
|
const [baseCommand, subcommand, thirdToken] = tokens;
|
|
83
107
|
|
|
84
108
|
if (!allowedCommands.has(baseCommand)) {
|
|
85
|
-
return `Command not allowed: ${baseCommand}`;
|
|
109
|
+
return `Command not allowed: ${baseCommand}. Allowed: ${[...allowedCommands].join(', ')}`;
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
if (baseCommand === 'git' && !allowedGitSubcommands.has(subcommand)) {
|
|
89
|
-
return `Git subcommand not allowed: ${subcommand ?? '(missing)'}`;
|
|
113
|
+
return `Git subcommand not allowed: ${subcommand ?? '(missing)'}. Allowed: ${[...allowedGitSubcommands].join(', ')}`;
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
if (baseCommand === 'find') {
|
|
@@ -99,11 +123,11 @@ const validateCommand = (command, tokens) => {
|
|
|
99
123
|
|
|
100
124
|
if (['npm', 'pnpm', 'yarn', 'bun'].includes(baseCommand)) {
|
|
101
125
|
if (!subcommand || !allowedPackageManagerSubcommands.has(subcommand)) {
|
|
102
|
-
return `Package manager subcommand not allowed: ${subcommand ?? '(missing)'}`;
|
|
126
|
+
return `Package manager subcommand not allowed: ${subcommand ?? '(missing)'}. Allowed: ${[...allowedPackageManagerSubcommands].join(', ')}`;
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
if (subcommand === 'run' && (!thirdToken || !safeRunScriptPattern.test(thirdToken))) {
|
|
106
|
-
return `Package manager script not allowed: ${thirdToken ?? '(missing)'}`;
|
|
130
|
+
return `Package manager script not allowed: ${thirdToken ?? '(missing)'}. Allowed pattern: ${safeRunScriptPattern.source}`;
|
|
107
131
|
}
|
|
108
132
|
}
|
|
109
133
|
|
package/src/tools/smart-turn.js
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Stub for workflow tracking (to be implemented in future version)
|
|
2
|
+
// This avoids SQLite issues in tests while keeping the API available
|
|
3
|
+
|
|
4
|
+
export const detectWorkflowType = () => null;
|
|
5
|
+
export const getWorkflowBaseline = () => 0;
|
|
6
|
+
export const startWorkflow = () => null;
|
|
7
|
+
export const endWorkflow = () => null;
|
|
8
|
+
export const getWorkflowMetrics = () => [];
|
|
9
|
+
export const getWorkflowSummaryByType = () => [];
|
|
10
|
+
export const autoTrackWorkflow = () => null;
|
|
11
|
+
|
|
12
|
+
export const WORKFLOW_DEFINITIONS = {
|
|
13
|
+
debugging: {
|
|
14
|
+
name: 'Debugging',
|
|
15
|
+
description: 'Error-first, symbol-focused debugging workflow',
|
|
16
|
+
typicalTools: ['smart_turn', 'smart_search', 'smart_read', 'smart_shell'],
|
|
17
|
+
minTools: 3,
|
|
18
|
+
baselineTokens: 150000,
|
|
19
|
+
pattern: /debug|error|bug|fix|fail/i,
|
|
20
|
+
},
|
|
21
|
+
'code-review': {
|
|
22
|
+
name: 'Code Review',
|
|
23
|
+
description: 'Diff-aware, API-focused code review workflow',
|
|
24
|
+
typicalTools: ['smart_turn', 'smart_context', 'smart_read', 'git_blame', 'smart_shell'],
|
|
25
|
+
minTools: 3,
|
|
26
|
+
baselineTokens: 200000,
|
|
27
|
+
pattern: /review|pr|pull.?request|approve/i,
|
|
28
|
+
},
|
|
29
|
+
refactoring: {
|
|
30
|
+
name: 'Refactoring',
|
|
31
|
+
description: 'Graph-aware, test-verified refactoring workflow',
|
|
32
|
+
typicalTools: ['smart_turn', 'smart_context', 'smart_read', 'git_blame', 'smart_shell'],
|
|
33
|
+
minTools: 3,
|
|
34
|
+
baselineTokens: 180000,
|
|
35
|
+
pattern: /refactor|extract|rename|move|restructure/i,
|
|
36
|
+
},
|
|
37
|
+
testing: {
|
|
38
|
+
name: 'Testing',
|
|
39
|
+
description: 'Coverage-aware, TDD-friendly testing workflow',
|
|
40
|
+
typicalTools: ['smart_turn', 'smart_search', 'smart_read', 'smart_context', 'smart_shell'],
|
|
41
|
+
minTools: 3,
|
|
42
|
+
baselineTokens: 120000,
|
|
43
|
+
pattern: /test|spec|coverage|tdd/i,
|
|
44
|
+
},
|
|
45
|
+
architecture: {
|
|
46
|
+
name: 'Architecture Exploration',
|
|
47
|
+
description: 'Index-first, minimal-detail architecture exploration',
|
|
48
|
+
typicalTools: ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'cross_project'],
|
|
49
|
+
minTools: 3,
|
|
50
|
+
baselineTokens: 300000,
|
|
51
|
+
pattern: /architect|explore|understand|structure|design/i,
|
|
52
|
+
},
|
|
53
|
+
};
|