imprint-mcp 0.6.4 → 0.6.6

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 (38) hide show
  1. package/README.md +5 -0
  2. package/package.json +1 -1
  3. package/prompts/audit-agent.md +3 -3
  4. package/prompts/auth-compile-agent.md +46 -4
  5. package/prompts/compile-agent.md +36 -12
  6. package/prompts/live-verifier-agent.md +15 -5
  7. package/prompts/request-triage.md +51 -9
  8. package/prompts/tool-candidate-detection.md +7 -1
  9. package/prompts/tool-planning.md +1 -1
  10. package/src/cli.ts +71 -13
  11. package/src/imprint/audit.ts +23 -5
  12. package/src/imprint/auth-compile-agent.ts +11 -7
  13. package/src/imprint/auth-compile-tools.ts +127 -18
  14. package/src/imprint/auth-verifier.ts +2 -1
  15. package/src/imprint/backend-ladder.ts +20 -0
  16. package/src/imprint/build-plan.ts +21 -0
  17. package/src/imprint/claude-cli-compile.ts +7 -1
  18. package/src/imprint/codex-cli-compile.ts +6 -0
  19. package/src/imprint/compile-agent.ts +50 -9
  20. package/src/imprint/compile-tools.ts +308 -25
  21. package/src/imprint/compile-verification.ts +11 -2
  22. package/src/imprint/compile.ts +298 -69
  23. package/src/imprint/effects.ts +9 -0
  24. package/src/imprint/live-verifier.ts +455 -34
  25. package/src/imprint/login.ts +30 -12
  26. package/src/imprint/mcp-compile-server.ts +56 -5
  27. package/src/imprint/mcp-live-verifier-server.ts +43 -10
  28. package/src/imprint/probe-backends.ts +6 -0
  29. package/src/imprint/recording-request.ts +7 -2
  30. package/src/imprint/replay-capture.ts +48 -29
  31. package/src/imprint/request-capture.ts +14 -0
  32. package/src/imprint/runtime.ts +102 -22
  33. package/src/imprint/teach-state.ts +162 -21
  34. package/src/imprint/teach.ts +629 -226
  35. package/src/imprint/tool-candidates.ts +138 -0
  36. package/src/imprint/tool-plan.ts +2 -0
  37. package/src/imprint/triage-selection.ts +44 -0
  38. package/src/imprint/types.ts +39 -3
@@ -340,6 +340,8 @@ export async function runWithLadder(
340
340
  initialState?: Record<string, unknown>;
341
341
  /** Optional credential override used by auth verification. */
342
342
  credentials?: CredentialStore;
343
+ /** Caller cancellation for bounded verification work. */
344
+ signal?: AbortSignal;
343
345
  },
344
346
  ): Promise<LadderResult> {
345
347
  if (ladder.length === 0) {
@@ -437,6 +439,7 @@ export async function runWithLadder(
437
439
  options?.cdpPool,
438
440
  options?.initialState,
439
441
  options?.credentials,
442
+ options?.signal,
440
443
  );
441
444
  break;
442
445
  case 'stealth-fetch': {
@@ -1036,6 +1039,7 @@ async function runCdpReplay(
1036
1039
  cdpPool?: Map<string, CdpBrowserFetch>,
1037
1040
  callerState?: Record<string, unknown>,
1038
1041
  credentialOverride?: CredentialStore,
1042
+ signal?: AbortSignal,
1039
1043
  ): Promise<ToolResult> {
1040
1044
  let baseUrl: string;
1041
1045
  try {
@@ -1105,6 +1109,9 @@ async function runCdpReplay(
1105
1109
  // declares it — never a blanket default. See AuthConfig.crossOriginCookieReinjection.
1106
1110
  reinjectCrossOriginCookies: tool.workflow.authConfig?.crossOriginCookieReinjection ?? false,
1107
1111
  });
1112
+ // Register immediately so caller cancellation can close a browser that is
1113
+ // still launching or minting its first jar.
1114
+ if (cdpPool) cdpPool.set(poolKey, cf);
1108
1115
  }
1109
1116
 
1110
1117
  try {
@@ -1148,6 +1155,7 @@ async function runCdpReplay(
1148
1155
  snapshotCookies: cf.snapshotCookies.bind(cf),
1149
1156
  }
1150
1157
  : undefined,
1158
+ signal,
1151
1159
  });
1152
1160
 
1153
1161
  if (result.ok) {
@@ -1597,6 +1605,8 @@ export async function runWorkflowWithLadder(opts: {
1597
1605
  /** Pin execution to a single rung, bypassing the parallel probe AND the winner
1598
1606
  * memo. AuthVerifier pins `cdp-replay` so all actions share one browser. */
1599
1607
  forceBackend?: ConcreteBackend;
1608
+ /** Caller cancellation for bounded verification work. */
1609
+ signal?: AbortSignal;
1600
1610
  }): Promise<LadderResult> {
1601
1611
  if (!existsSync(opts.workflowPath)) {
1602
1612
  throw new Error(`runWorkflowWithLadder: workflow.json not found at ${opts.workflowPath}`);
@@ -1674,6 +1684,7 @@ export async function runWorkflowWithLadder(opts: {
1674
1684
  cdpPool,
1675
1685
  initialState: opts.initialState,
1676
1686
  credentials: opts.credentials,
1687
+ signal: opts.signal,
1677
1688
  });
1678
1689
  }
1679
1690
 
@@ -1731,6 +1742,7 @@ export async function runWorkflowWithLadder(opts: {
1731
1742
  cdpPool,
1732
1743
  initialState: opts.initialState,
1733
1744
  credentials: opts.credentials,
1745
+ signal: opts.signal,
1734
1746
  });
1735
1747
  const r = await settleProbeWithDeadline(inner, PROBE_TIMEOUT_MS, {
1736
1748
  result: { ok: false, error: 'NETWORK', message: 'probe deadline exceeded' },
@@ -1781,6 +1793,7 @@ export async function runWorkflowWithLadder(opts: {
1781
1793
  cdpPool,
1782
1794
  initialState: opts.initialState,
1783
1795
  credentials: opts.credentials,
1796
+ signal: opts.signal,
1784
1797
  });
1785
1798
  if (probeReachable(seqResult.result)) {
1786
1799
  compileWinningBackend.set(memoKey, seqResult.usedBackend);
@@ -1804,6 +1817,7 @@ export async function runWorkflowWithLadder(opts: {
1804
1817
  cdpPool,
1805
1818
  initialState: opts.initialState,
1806
1819
  credentials: opts.credentials,
1820
+ signal: opts.signal,
1807
1821
  });
1808
1822
  if (isProbeReachable(result.result)) {
1809
1823
  compileWinningBackend.set(memoKey, result.usedBackend);
@@ -1849,6 +1863,7 @@ export function resolveWorkflowTool(
1849
1863
  browser?: BrowserNavigationTransport;
1850
1864
  initialState?: Record<string, unknown>;
1851
1865
  credentials?: CredentialStore;
1866
+ signal?: AbortSignal;
1852
1867
  }
1853
1868
  | undefined;
1854
1869
  return executeWorkflow({
@@ -1859,6 +1874,7 @@ export function resolveWorkflowTool(
1859
1874
  fetchImpl: o?.fetchImpl,
1860
1875
  browser: o?.browser,
1861
1876
  initialState: o?.initialState,
1877
+ signal: o?.signal,
1862
1878
  });
1863
1879
  },
1864
1880
  };
@@ -1896,6 +1912,8 @@ export async function renderWorkflowRequests(opts: {
1896
1912
  params: Record<string, string | number | boolean>;
1897
1913
  workflowPath?: string;
1898
1914
  credentials?: CredentialStore;
1915
+ /** Synthetic captured state for offline request tests. */
1916
+ initialState?: Record<string, unknown>;
1899
1917
  recordedResponseFor?: (
1900
1918
  method: string,
1901
1919
  url: string,
@@ -1930,7 +1948,9 @@ export async function renderWorkflowRequests(opts: {
1930
1948
  params: opts.params,
1931
1949
  credentials: opts.credentials,
1932
1950
  workflowPath: opts.workflowPath,
1951
+ initialState: opts.initialState,
1933
1952
  fetchImpl,
1953
+ persistAuthState: false,
1934
1954
  });
1935
1955
  return { requests: captured, result };
1936
1956
  }
@@ -183,6 +183,8 @@ type DynamicValueFinding = z.infer<typeof DynamicValueFindingSchema>;
183
183
 
184
184
  const PerToolPlanSchema = z.object({
185
185
  toolName: z.string().regex(/^[a-z][a-z0-9_]*$/),
186
+ /** Callable prerequisite tools chosen by candidate detection. */
187
+ dependsOnTools: z.array(z.string().regex(/^[a-z][a-z0-9_]*$/)).default([]),
186
188
  usesSharedModules: z.array(z.string()).default([]),
187
189
  loadBearingSeqs: z.array(z.number().int().nonnegative()).default([]),
188
190
  parserGuidance: z.string().default(''),
@@ -692,6 +694,8 @@ export function topoLevelsForTools<T extends { toolName: string }>(
692
694
  (t) => {
693
695
  if (!plan) return [];
694
696
  const deps = new Set<string>();
697
+ const toolPlan = plan.perTool.find((candidate) => candidate.toolName === t.toolName);
698
+ for (const dependency of toolPlan?.dependsOnTools ?? []) deps.add(dependency);
695
699
  for (const tp of resolveTokenParams(plan, t.toolName)) deps.add(tp.sourceTool);
696
700
  // General contract edges: a producer_tool input depends on its producer; any
697
701
  // auth input depends on the authenticate tool (filtered out by kahnLevels
@@ -862,6 +866,20 @@ export function validateBuildPlan(
862
866
  const plan = BuildPlanSchema.parse(normalizeRawPlan(input));
863
867
  if (selected && selected.length > 0) {
864
868
  const names = new Set(selected.map((t) => (typeof t === 'string' ? t : t.toolName)));
869
+ const dependenciesByName = new Map(
870
+ selected.flatMap((candidate) =>
871
+ typeof candidate === 'string'
872
+ ? []
873
+ : [
874
+ [
875
+ candidate.toolName,
876
+ candidate.dependsOnTools.filter(
877
+ (dependency) => names.has(dependency) && dependency !== candidate.toolName,
878
+ ),
879
+ ] as const,
880
+ ],
881
+ ),
882
+ );
865
883
  plan.perTool = plan.perTool.filter((t) => names.has(t.toolName));
866
884
  for (const name of names) {
867
885
  if (!plan.perTool.some((t) => t.toolName === name)) {
@@ -873,6 +891,9 @@ export function validateBuildPlan(
873
891
  if (plan.perTool.length === 0) {
874
892
  throw new Error('Build plan has no perTool entries for the selected tools.');
875
893
  }
894
+ for (const tool of plan.perTool) {
895
+ tool.dependsOnTools = dependenciesByName.get(tool.toolName) ?? [];
896
+ }
876
897
  }
877
898
  // Deterministic requiredInputs ordering for stable sidecar JSON.
878
899
  for (const t of plan.perTool) {
@@ -51,6 +51,7 @@ import {
51
51
  traceLlmIoEnabled,
52
52
  traced,
53
53
  } from './tracing.ts';
54
+ import type { SharedTriageSelection } from './triage-selection.ts';
54
55
  import type { Session } from './types.ts';
55
56
 
56
57
  const log = createLog('compile-claude-cli');
@@ -127,7 +128,7 @@ interface CompileViaClaudeCliOptions {
127
128
  onProgress?: (p: CompileAgentProgress) => void;
128
129
  /** Called when wall-clock deadline is reached; return ms to extend or null to time out. */
129
130
  onDeadlineReached?: OnDeadlineReached;
130
- /** Retain parser.test.ts after successful verification. Mirrors the
131
+ /** Retain agent-generated tests after successful verification. Mirrors the
131
132
  * in-process loop's `keepTest`. */
132
133
  keepTest?: boolean;
133
134
  candidate?: ToolCandidate;
@@ -140,6 +141,8 @@ interface CompileViaClaudeCliOptions {
140
141
  toolPlan?: string;
141
142
  /** Revise existing generated artifacts from durable verification feedback. */
142
143
  revisionMode?: boolean;
144
+ /** Shared triage result for irreversible-effect propagation in the MCP server. */
145
+ sharedTriageSelection?: SharedTriageSelection;
143
146
  /** Present → drive an auth compile rather than a data compile. */
144
147
  authMode?: AuthCliCompileMode;
145
148
  /** Auth segments only: resume a prior segment's claude session with a new user
@@ -377,6 +380,9 @@ async function runClaudeCliAttempt(opts: CompileViaClaudeCliOptions): Promise<Co
377
380
  ...(opts.buildPlanPath ? ['--build-plan-path', opts.buildPlanPath] : []),
378
381
  ...(opts.sharedModules ? ['--shared-modules-json', JSON.stringify(opts.sharedModules)] : []),
379
382
  ...(opts.revisionMode ? ['--revision-mode'] : []),
383
+ ...(opts.sharedTriageSelection
384
+ ? ['--shared-triage-json', JSON.stringify(opts.sharedTriageSelection)]
385
+ : []),
380
386
  ];
381
387
  allowedToolNames = [
382
388
  'read_session_summary',
@@ -34,6 +34,7 @@ import {
34
34
  traceToolIoEnabled,
35
35
  traced,
36
36
  } from './tracing.ts';
37
+ import type { SharedTriageSelection } from './triage-selection.ts';
37
38
  import type { Session } from './types.ts';
38
39
 
39
40
  const log = createLog('compile-codex-cli');
@@ -143,6 +144,8 @@ interface CompileViaCodexCliOptions {
143
144
  toolPlan?: string;
144
145
  /** Revise existing generated artifacts from durable verification feedback. */
145
146
  revisionMode?: boolean;
147
+ /** Shared triage result for irreversible-effect propagation in the MCP server. */
148
+ sharedTriageSelection?: SharedTriageSelection;
146
149
  /** Present → drive an auth compile rather than a data compile. */
147
150
  authMode?: AuthCliCompileMode;
148
151
  /** Auth segments only: resume the same non-interactive Codex session. */
@@ -302,6 +305,9 @@ async function compileViaCodexCliImpl(
302
305
  ...(opts.buildPlanPath ? ['--build-plan-path', opts.buildPlanPath] : []),
303
306
  ...(opts.sharedModules ? ['--shared-modules-json', JSON.stringify(opts.sharedModules)] : []),
304
307
  ...(opts.revisionMode ? ['--revision-mode'] : []),
308
+ ...(opts.sharedTriageSelection
309
+ ? ['--shared-triage-json', JSON.stringify(opts.sharedTriageSelection)]
310
+ : []),
305
311
  ];
306
312
  const { assignedSharedModules } = resolvePlanSliceFromFile(
307
313
  opts.buildPlanPath,
@@ -28,12 +28,14 @@ import {
28
28
  } from './compile-agent-types.ts';
29
29
  import { formatCandidateContext, formatToolPlan } from './compile-agent-types.ts';
30
30
  import {
31
+ applyIrreversibleVerificationWaiver,
31
32
  applyLiveVerification,
32
33
  applyParamVerification,
33
34
  buildCompileTools,
34
35
  externalVerification,
35
36
  } from './compile-tools.ts';
36
37
  import { type Replacement, extractCredentials } from './credential-extract.ts';
38
+ import { workflowHasIrreversibleEffect } from './effects.ts';
37
39
  import {
38
40
  isInfrastructureOnlyInconclusiveReport,
39
41
  mergeSemanticParamVerification,
@@ -61,7 +63,12 @@ import { rebindExistingBackendsCacheToWorkflow } from './probe-backends.ts';
61
63
  import { detectPageMintedHeaders, redactSession } from './redact.ts';
62
64
  import type { ClassifiedValue } from './session-diff.ts';
63
65
  import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
64
- import { type Session, SessionSchema } from './types.ts';
66
+ import {
67
+ type SharedTriageSelection,
68
+ applySharedTriageSelection,
69
+ minimalSharedTriageSelection,
70
+ } from './triage-selection.ts';
71
+ import { type Session, SessionSchema, WorkflowSchema } from './types.ts';
65
72
 
66
73
  export type { CompileAgentProgress } from './compile-agent-types.ts';
67
74
 
@@ -126,8 +133,8 @@ interface CompileAgentOptions {
126
133
  llmProvider?: ToolUseProvider;
127
134
  /** Progress callback with verification cycle information. */
128
135
  onProgress?: (p: CompileAgentProgress) => void;
129
- /** Retain parser.test.ts after successful verification. By default it's
130
- * deleted (the test reads the gitignored redacted session at
136
+ /** Retain generated tests after successful verification. By default they are
137
+ * deleted (parser tests read the gitignored redacted session at
131
138
  * $IMPRINT_SESSION_PATH, so it's not reproducible elsewhere — keeping it
132
139
  * on disk just confuses `bun test`). Pass true with `--keep-test` to
133
140
  * inspect the agent's test output locally. */
@@ -161,6 +168,8 @@ interface CompileAgentOptions {
161
168
  /** Revise an existing generated artifact, using durable verification feedback
162
169
  * as the starting point instead of rebuilding it from raw capture. */
163
170
  revisionMode?: boolean;
171
+ /** Shared triage decision propagated into all compile transports. */
172
+ preTriagedSession?: SharedTriageSelection;
164
173
  }
165
174
 
166
175
  function formatRevisionMode(enabled: boolean | undefined): string {
@@ -220,6 +229,12 @@ export async function compileAgent(opts: CompileAgentOptions): Promise<CompileAg
220
229
  );
221
230
  }
222
231
  }
232
+ if (opts.preTriagedSession) {
233
+ session = applySharedTriageSelection(session, opts.preTriagedSession, {
234
+ candidate: opts.candidate,
235
+ sharedContext: opts.sharedContext,
236
+ });
237
+ }
223
238
 
224
239
  // 3. Determine the generated tool directory.
225
240
  const absoluteToolDir = opts.outDir ?? localSiteDir(session.site);
@@ -321,6 +336,9 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
321
336
  toolPlan: opts.toolPlan,
322
337
  revisionMode: opts.revisionMode,
323
338
  model: opts.llmConfig?.model,
339
+ sharedTriageSelection: opts.preTriagedSession
340
+ ? minimalSharedTriageSelection(opts.preTriagedSession)
341
+ : undefined,
324
342
  });
325
343
  }
326
344
  if (resolvedProvider.name === 'codex-cli') {
@@ -340,6 +358,9 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
340
358
  toolPlan: opts.toolPlan,
341
359
  revisionMode: opts.revisionMode,
342
360
  model: opts.llmConfig?.model,
361
+ sharedTriageSelection: opts.preTriagedSession
362
+ ? minimalSharedTriageSelection(opts.preTriagedSession)
363
+ : undefined,
343
364
  });
344
365
  }
345
366
  if (!isToolUseProvider(resolvedProvider)) {
@@ -426,10 +447,11 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
426
447
  expectedToolName: opts.candidate?.toolName,
427
448
  likelyParams: opts.candidate?.likelyParams,
428
449
  candidateRequestSeqs: opts.candidate?.requestSeqs,
429
- // Widen Fix B's variation pool to the dependency requests (e.g. a
430
- // bootstrap GET) so a session token that varies only across dependency
431
- // seqs and is then frozen as a literal in the tool's request is caught.
432
- dependencyRequestSeqs: opts.candidate?.dependencySeqs,
450
+ // Widen checks to requests this tool depends on (bootstrap/login/etc.).
451
+ dependencyRequestSeqs: [
452
+ ...(opts.candidate?.dependencySeqs ?? []),
453
+ ...(opts.sharedContext?.loginRequestSeqs ?? []),
454
+ ],
433
455
  assignedSharedModules,
434
456
  tokenParams,
435
457
  tokenParamShapes,
@@ -445,6 +467,25 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
445
467
  log(`verification warnings (non-blocking):\n${warnings.join('\n')}`);
446
468
  }
447
469
 
470
+ let workflow: ReturnType<typeof WorkflowSchema.parse> | undefined;
471
+ if (failures.length === 0) {
472
+ workflow = WorkflowSchema.parse(
473
+ JSON.parse(readFileSync(pathJoin(absoluteToolDir, 'workflow.json'), 'utf8')),
474
+ );
475
+ }
476
+ if (workflow && workflowHasIrreversibleEffect(workflow)) {
477
+ warnings.push(...applyIrreversibleVerificationWaiver(absoluteToolDir, workflow));
478
+ message = result.doneSummary ?? 'Task completed';
479
+ message += `\n\nWarnings:\n${warnings.join('\n')}`;
480
+ if (!opts.keepTest) {
481
+ for (const file of ['parser.test.ts', 'request.test.ts', 'integration.test.ts']) {
482
+ const testPath = pathJoin(absoluteToolDir, file);
483
+ if (existsSync(testPath)) unlinkSync(testPath);
484
+ }
485
+ }
486
+ break;
487
+ }
488
+
448
489
  let semanticReviewCompleted = false;
449
490
  let semanticReviewAttempted = false;
450
491
  if (failures.length === 0) {
@@ -488,7 +529,7 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
488
529
  message = result.doneSummary ?? 'Task completed';
489
530
  message += `\n\nWarnings:\n${warnings.join('\n')}`;
490
531
  if (!opts.keepTest) {
491
- for (const f of ['parser.test.ts', 'integration.test.ts']) {
532
+ for (const f of ['parser.test.ts', 'request.test.ts', 'integration.test.ts']) {
492
533
  const testPath = pathJoin(absoluteToolDir, f);
493
534
  if (existsSync(testPath)) unlinkSync(testPath);
494
535
  }
@@ -561,7 +602,7 @@ Begin by calling read_session_summary to orient yourself, then proceed per the s
561
602
  message += `\n\nWarnings:\n${allWarnings.join('\n')}`;
562
603
  }
563
604
  if (!opts.keepTest) {
564
- for (const f of ['parser.test.ts', 'integration.test.ts']) {
605
+ for (const f of ['parser.test.ts', 'request.test.ts', 'integration.test.ts']) {
565
606
  const testPath = pathJoin(absoluteToolDir, f);
566
607
  if (existsSync(testPath)) unlinkSync(testPath);
567
608
  }