smart-context-mcp 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/scripts/headless-wrapper.js +2 -1
- package/scripts/report-metrics.js +2 -2
- package/scripts/task-runner.js +2 -1
- package/src/analytics/product-quality.js +176 -1
- package/src/hooks/claude-hooks.js +1 -423
- package/src/hooks/cursor-hooks.js +1 -0
- package/src/orchestration/adapters/claude-adapter.js +426 -0
- package/src/orchestration/adapters/cursor-adapter.js +429 -0
- package/src/orchestration/base-orchestrator.js +242 -0
- package/src/orchestration/headless-wrapper.js +39 -195
- package/src/orchestration/policy/event-policy.js +297 -0
- package/src/task-runner.js +33 -247
- package/src/utils/client-detection.js +33 -0
package/src/task-runner.js
CHANGED
|
@@ -3,10 +3,16 @@ import { persistMetrics } from './metrics.js';
|
|
|
3
3
|
import { countTokens } from './tokenCounter.js';
|
|
4
4
|
import { projectRoot } from './utils/paths.js';
|
|
5
5
|
import { TASK_RUNNER_QUALITY_ANALYTICS_KIND } from './analytics/product-quality.js';
|
|
6
|
+
import { DEFAULT_END_MAX_TOKENS, DEFAULT_START_MAX_TOKENS, resolveManagedStart } from './orchestration/base-orchestrator.js';
|
|
6
7
|
import { runHeadlessWrapper } from './orchestration/headless-wrapper.js';
|
|
7
|
-
import { smartContext } from './tools/smart-context.js';
|
|
8
8
|
import { smartDoctor } from './tools/smart-doctor.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildPreflightSummary,
|
|
11
|
+
buildTaskRunnerAutomaticity,
|
|
12
|
+
buildWorkflowPolicyPayload,
|
|
13
|
+
buildWorkflowPromptWithPolicy,
|
|
14
|
+
runWorkflowPreflight,
|
|
15
|
+
} from './orchestration/policy/event-policy.js';
|
|
10
16
|
import { smartStatus } from './tools/smart-status.js';
|
|
11
17
|
import { smartSummary } from './tools/smart-summary.js';
|
|
12
18
|
import { smartTurn } from './tools/smart-turn.js';
|
|
@@ -21,244 +27,11 @@ import {
|
|
|
21
27
|
buildWorkflowPrompt,
|
|
22
28
|
evaluateRunnerGate,
|
|
23
29
|
} from './task-runner/policy.js';
|
|
30
|
+
import { detectClient } from './utils/client-detection.js';
|
|
24
31
|
|
|
25
|
-
const START_MAX_TOKENS = 350;
|
|
26
|
-
const END_MAX_TOKENS = 350;
|
|
27
32
|
const RUNNER_LOCK_RETRY_ATTEMPTS = 3;
|
|
28
33
|
const RUNNER_LOCK_RETRY_DELAY_MS = 100;
|
|
29
34
|
|
|
30
|
-
const normalizeWhitespace = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
31
|
-
const truncate = (value, maxLength = 160) => {
|
|
32
|
-
const normalized = normalizeWhitespace(value);
|
|
33
|
-
if (normalized.length <= maxLength) {
|
|
34
|
-
return normalized;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (maxLength <= 3) {
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const asArray = (value) => Array.isArray(value) ? value : [];
|
|
45
|
-
const uniqueCompact = (values) => [...new Set(asArray(values).map((value) => normalizeWhitespace(value)).filter(Boolean))];
|
|
46
|
-
const extractContextTopFiles = (topFiles) => uniqueCompact(asArray(topFiles).map((item) => {
|
|
47
|
-
if (typeof item === 'string') {
|
|
48
|
-
return item;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return item?.file ?? item?.path ?? '';
|
|
52
|
-
})).slice(0, 3);
|
|
53
|
-
|
|
54
|
-
const extractPreflightTopFiles = (preflightResult) => {
|
|
55
|
-
if (!preflightResult) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (preflightResult.tool === 'smart_context') {
|
|
60
|
-
return uniqueCompact(asArray(preflightResult.result?.context).map((item) => item?.file).filter(Boolean)).slice(0, 3);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (preflightResult.tool === 'smart_search') {
|
|
64
|
-
return extractContextTopFiles(preflightResult.result?.topFiles);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return [];
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const extractPreflightHints = (preflightResult) => {
|
|
71
|
-
if (!preflightResult) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (preflightResult.tool === 'smart_context') {
|
|
76
|
-
return uniqueCompact(preflightResult.result?.hints).slice(0, 2);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (preflightResult.tool === 'smart_search') {
|
|
80
|
-
const totalMatches = Number(preflightResult.result?.totalMatches ?? 0);
|
|
81
|
-
if (totalMatches > 0) {
|
|
82
|
-
return [`${totalMatches} search match(es) surfaced for the workflow target`];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return [];
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const buildPreflightSummary = (preflightResult) => {
|
|
90
|
-
if (!preflightResult) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const topFiles = extractPreflightTopFiles(preflightResult);
|
|
95
|
-
const hints = extractPreflightHints(preflightResult);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
tool: preflightResult.tool,
|
|
99
|
-
topFiles,
|
|
100
|
-
hints,
|
|
101
|
-
totalMatches: Number(preflightResult.result?.totalMatches ?? 0),
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const buildPreflightTask = ({ workflowProfile, prompt, startResult }) => {
|
|
106
|
-
const normalizedPrompt = normalizeWhitespace(prompt);
|
|
107
|
-
const persistedNextStep = normalizeWhitespace(startResult?.summary?.nextStep);
|
|
108
|
-
const currentFocus = normalizeWhitespace(startResult?.summary?.currentFocus);
|
|
109
|
-
const refreshedTopFiles = extractContextTopFiles(startResult?.refreshedContext?.topFiles);
|
|
110
|
-
|
|
111
|
-
if (workflowProfile.commandName === 'continue' || workflowProfile.commandName === 'resume') {
|
|
112
|
-
if (persistedNextStep) {
|
|
113
|
-
return persistedNextStep;
|
|
114
|
-
}
|
|
115
|
-
if (currentFocus) {
|
|
116
|
-
return currentFocus;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (workflowProfile.commandName === 'task' && currentFocus && persistedNextStep) {
|
|
121
|
-
return `${currentFocus}. ${persistedNextStep}`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (normalizedPrompt) {
|
|
125
|
-
return normalizedPrompt;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (refreshedTopFiles.length > 0) {
|
|
129
|
-
return `Inspect ${refreshedTopFiles.join(', ')} and continue the persisted task`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return workflowProfile.label;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const runWorkflowPreflight = async ({ workflowProfile, prompt, startResult }) => {
|
|
136
|
-
const preflight = workflowProfile.preflight;
|
|
137
|
-
if (!preflight) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const preflightTask = buildPreflightTask({ workflowProfile, prompt, startResult });
|
|
142
|
-
|
|
143
|
-
if (preflight.tool === 'smart_context') {
|
|
144
|
-
const result = await smartContext({
|
|
145
|
-
task: preflightTask,
|
|
146
|
-
detail: preflight.detail ?? 'minimal',
|
|
147
|
-
include: preflight.include ?? ['hints'],
|
|
148
|
-
maxTokens: preflight.maxTokens ?? 1200,
|
|
149
|
-
});
|
|
150
|
-
return {
|
|
151
|
-
tool: 'smart_context',
|
|
152
|
-
request: {
|
|
153
|
-
task: preflightTask,
|
|
154
|
-
detail: preflight.detail ?? 'minimal',
|
|
155
|
-
include: preflight.include ?? ['hints'],
|
|
156
|
-
maxTokens: preflight.maxTokens ?? 1200,
|
|
157
|
-
},
|
|
158
|
-
result,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (preflight.tool === 'smart_search') {
|
|
163
|
-
const result = await smartSearch({
|
|
164
|
-
query: preflightTask,
|
|
165
|
-
intent: preflight.intent ?? workflowProfile.workflowIntent,
|
|
166
|
-
});
|
|
167
|
-
return {
|
|
168
|
-
tool: 'smart_search',
|
|
169
|
-
request: {
|
|
170
|
-
query: preflightTask,
|
|
171
|
-
intent: preflight.intent ?? workflowProfile.workflowIntent,
|
|
172
|
-
},
|
|
173
|
-
result,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return null;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const buildContinuityGuidance = ({ startResult }) => {
|
|
181
|
-
const continuityState = startResult?.continuity?.state ?? 'unknown';
|
|
182
|
-
const lines = [
|
|
183
|
-
`- Continuity: ${continuityState}`,
|
|
184
|
-
];
|
|
185
|
-
const nextStep = normalizeWhitespace(startResult?.summary?.nextStep);
|
|
186
|
-
const currentFocus = normalizeWhitespace(startResult?.summary?.currentFocus);
|
|
187
|
-
const refreshedTopFiles = extractContextTopFiles(startResult?.refreshedContext?.topFiles);
|
|
188
|
-
const recommendedNextTools = asArray(startResult?.recommendedPath?.nextTools)
|
|
189
|
-
.map((tool) => normalizeWhitespace(tool))
|
|
190
|
-
.filter(Boolean)
|
|
191
|
-
.slice(0, 3);
|
|
192
|
-
|
|
193
|
-
if (currentFocus) {
|
|
194
|
-
lines.push(`- Persisted focus: ${truncate(currentFocus, 140)}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (nextStep) {
|
|
198
|
-
lines.push(`- Persisted next step: ${truncate(nextStep, 140)}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (refreshedTopFiles.length > 0) {
|
|
202
|
-
lines.push(`- Refreshed top files: ${refreshedTopFiles.join(', ')}`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (recommendedNextTools.length > 0) {
|
|
206
|
-
lines.push(`- smart_turn suggested: ${recommendedNextTools.join(' -> ')}`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (startResult?.isolatedSession) {
|
|
210
|
-
lines.push('- Session handling: smart_turn already isolated this work from the previous session; revalidate before assuming old focus.');
|
|
211
|
-
} else if (continuityState === 'aligned' || continuityState === 'resume') {
|
|
212
|
-
lines.push('- Session handling: reuse the active session context and stay close to the persisted next step unless the task proves otherwise.');
|
|
213
|
-
} else if (continuityState === 'possible_shift' || continuityState === 'context_mismatch') {
|
|
214
|
-
lines.push('- Session handling: treat this as a shifted slice, validate the working set early, and avoid silent context reuse.');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return lines;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const buildWorkflowPromptWithPolicy = ({ prompt, workflowProfile, preflightSummary, startResult }) => {
|
|
221
|
-
const lines = [
|
|
222
|
-
prompt,
|
|
223
|
-
'',
|
|
224
|
-
'Workflow policy:',
|
|
225
|
-
`- Mode: ${workflowProfile.policyMode}`,
|
|
226
|
-
`- Intent: ${workflowProfile.workflowIntent}`,
|
|
227
|
-
`- Prefer this tool order: ${workflowProfile.nextTools.join(' -> ')}`,
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
if (workflowProfile.checkpointStrategy) {
|
|
231
|
-
lines.push(`- Checkpoint rule: ${workflowProfile.checkpointStrategy}`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
lines.push(...buildContinuityGuidance({ startResult }));
|
|
235
|
-
|
|
236
|
-
if (preflightSummary?.tool) {
|
|
237
|
-
lines.push(`- Preflight: ${preflightSummary.tool}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (preflightSummary?.topFiles?.length) {
|
|
241
|
-
lines.push(`- Focus files: ${preflightSummary.topFiles.join(', ')}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (preflightSummary?.hints?.length) {
|
|
245
|
-
lines.push(`- Signals: ${preflightSummary.hints.map((hint) => truncate(hint, 120)).join(' | ')}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return lines.join('\n');
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const buildWorkflowPolicyPayload = ({ commandName, workflowProfile, preflightSummary }) => ({
|
|
252
|
-
commandName,
|
|
253
|
-
label: workflowProfile.label,
|
|
254
|
-
policyMode: workflowProfile.policyMode,
|
|
255
|
-
intent: workflowProfile.workflowIntent,
|
|
256
|
-
specialized: workflowProfile.specialized,
|
|
257
|
-
nextTools: [...workflowProfile.nextTools],
|
|
258
|
-
checkpointStrategy: workflowProfile.checkpointStrategy,
|
|
259
|
-
preflight: preflightSummary,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
35
|
const isRetriableLockError = (error) => {
|
|
263
36
|
const issue = error?.storageHealth?.issue ?? error?.cause?.storageHealth?.issue ?? null;
|
|
264
37
|
const retriable = error?.storageHealth?.retriable ?? error?.cause?.storageHealth?.retriable ?? false;
|
|
@@ -302,6 +75,15 @@ const recordRunnerMetrics = async ({
|
|
|
302
75
|
?? null;
|
|
303
76
|
const checkpointPersisted = Boolean(endResult && !endResult.checkpoint?.skipped && !endResult.checkpoint?.blocked);
|
|
304
77
|
const checkpointSkipped = Boolean(endResult?.checkpoint?.skipped);
|
|
78
|
+
const automaticity = buildTaskRunnerAutomaticity({
|
|
79
|
+
isWorkflowCommand: WORKFLOW_COMMANDS.has(commandName),
|
|
80
|
+
startResult,
|
|
81
|
+
endResult,
|
|
82
|
+
workflowPolicy,
|
|
83
|
+
usedWrapper,
|
|
84
|
+
overheadTokens: Number(result?.overheadTokens ?? 0),
|
|
85
|
+
managedByBaseOrchestrator: WORKFLOW_COMMANDS.has(commandName),
|
|
86
|
+
});
|
|
305
87
|
|
|
306
88
|
await persistMetrics({
|
|
307
89
|
tool: 'task_runner',
|
|
@@ -334,6 +116,7 @@ const recordRunnerMetrics = async ({
|
|
|
334
116
|
recommendedPathMode,
|
|
335
117
|
checkpointPersisted,
|
|
336
118
|
checkpointSkipped,
|
|
119
|
+
...automaticity,
|
|
337
120
|
},
|
|
338
121
|
timestamp: new Date().toISOString(),
|
|
339
122
|
});
|
|
@@ -359,13 +142,14 @@ const runWorkflowCommand = async ({
|
|
|
359
142
|
});
|
|
360
143
|
const workflowProfile = buildWorkflowPolicyProfile({ commandName });
|
|
361
144
|
|
|
362
|
-
const
|
|
363
|
-
phase: 'start',
|
|
364
|
-
sessionId,
|
|
145
|
+
const startResolution = await withRunnerLockRetry(() => resolveManagedStart({
|
|
365
146
|
prompt: requestedPrompt,
|
|
147
|
+
sessionId,
|
|
366
148
|
ensureSession: true,
|
|
367
|
-
|
|
149
|
+
allowIsolation: false,
|
|
150
|
+
startMaxTokens: DEFAULT_START_MAX_TOKENS,
|
|
368
151
|
}));
|
|
152
|
+
const start = startResolution.startResult;
|
|
369
153
|
|
|
370
154
|
const gate = evaluateRunnerGate({ startResult: start });
|
|
371
155
|
let preflightSummary = null;
|
|
@@ -485,7 +269,7 @@ const runCheckpointCommand = async ({
|
|
|
485
269
|
sessionId,
|
|
486
270
|
event,
|
|
487
271
|
update,
|
|
488
|
-
maxTokens:
|
|
272
|
+
maxTokens: DEFAULT_END_MAX_TOKENS,
|
|
489
273
|
}));
|
|
490
274
|
await recordRunnerMetrics({
|
|
491
275
|
commandName: 'checkpoint',
|
|
@@ -631,7 +415,7 @@ const runCleanupCommand = async ({
|
|
|
631
415
|
|
|
632
416
|
export const runTaskRunner = async ({
|
|
633
417
|
commandName = 'task',
|
|
634
|
-
client =
|
|
418
|
+
client = null,
|
|
635
419
|
prompt = '',
|
|
636
420
|
sessionId,
|
|
637
421
|
event,
|
|
@@ -653,6 +437,8 @@ export const runTaskRunner = async ({
|
|
|
653
437
|
vacuum = false,
|
|
654
438
|
update = {},
|
|
655
439
|
} = {}) => {
|
|
440
|
+
const resolvedClient = client ?? detectClient();
|
|
441
|
+
|
|
656
442
|
if (!RUNNER_COMMANDS.includes(commandName)) {
|
|
657
443
|
throw new Error(`Unsupported task-runner command: ${commandName}`);
|
|
658
444
|
}
|
|
@@ -660,7 +446,7 @@ export const runTaskRunner = async ({
|
|
|
660
446
|
if (WORKFLOW_COMMANDS.has(commandName)) {
|
|
661
447
|
return runWorkflowCommand({
|
|
662
448
|
commandName,
|
|
663
|
-
client,
|
|
449
|
+
client: resolvedClient,
|
|
664
450
|
prompt,
|
|
665
451
|
sessionId,
|
|
666
452
|
event,
|
|
@@ -675,16 +461,16 @@ export const runTaskRunner = async ({
|
|
|
675
461
|
}
|
|
676
462
|
|
|
677
463
|
if (commandName === 'doctor') {
|
|
678
|
-
return runDoctorCommand({ verifyIntegrity, client });
|
|
464
|
+
return runDoctorCommand({ verifyIntegrity, client: resolvedClient });
|
|
679
465
|
}
|
|
680
466
|
|
|
681
467
|
if (commandName === 'status') {
|
|
682
|
-
return runStatusCommand({ format, maxItems, client });
|
|
468
|
+
return runStatusCommand({ format, maxItems, client: resolvedClient });
|
|
683
469
|
}
|
|
684
470
|
|
|
685
471
|
if (commandName === 'checkpoint') {
|
|
686
472
|
return runCheckpointCommand({
|
|
687
|
-
client,
|
|
473
|
+
client: resolvedClient,
|
|
688
474
|
sessionId,
|
|
689
475
|
event,
|
|
690
476
|
update,
|
|
@@ -693,7 +479,7 @@ export const runTaskRunner = async ({
|
|
|
693
479
|
|
|
694
480
|
if (commandName === 'cleanup') {
|
|
695
481
|
return runCleanupCommand({
|
|
696
|
-
client,
|
|
482
|
+
client: resolvedClient,
|
|
697
483
|
cleanupMode,
|
|
698
484
|
apply,
|
|
699
485
|
retentionDays,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const detectClientFromEnv = () => {
|
|
2
|
+
if (process.env.CURSOR_AGENT === '1') {
|
|
3
|
+
return 'cursor';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (process.env.CLAUDE_AGENT === '1') {
|
|
7
|
+
return 'claude';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (process.env.GEMINI_AGENT === '1') {
|
|
11
|
+
return 'gemini';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (process.env.CODEX_AGENT === '1') {
|
|
15
|
+
return 'codex';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return 'generic';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let cachedClient = null;
|
|
22
|
+
|
|
23
|
+
export const detectClient = () => {
|
|
24
|
+
if (cachedClient === null) {
|
|
25
|
+
cachedClient = detectClientFromEnv();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return cachedClient;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const resetClientDetection = () => {
|
|
32
|
+
cachedClient = null;
|
|
33
|
+
};
|