@junghanacs/entwurf 0.12.10 → 0.13.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.
Files changed (86) hide show
  1. package/AGENTS.md +6 -3
  2. package/BASELINE.md +56 -163
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +117 -261
  6. package/README.md +74 -399
  7. package/VERIFY.md +59 -85
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +231 -0
  10. package/docs/external-mcp-host.md +132 -0
  11. package/docs/fresh-cut-policy.md +99 -0
  12. package/docs/setup-clean-host.md +123 -328
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +155 -11
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  21. package/mcp/entwurf-bridge/src/index.ts +21 -48
  22. package/package.json +14 -9
  23. package/pi/settings.reference.json +1 -1
  24. package/pi-extensions/acp-provider.ts +20 -10
  25. package/pi-extensions/entwurf-control.ts +20 -49
  26. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  27. package/pi-extensions/lib/acp/augment.ts +99 -2
  28. package/pi-extensions/lib/acp/backend-adapter.ts +190 -14
  29. package/pi-extensions/lib/acp/backend.ts +310 -37
  30. package/pi-extensions/lib/acp/config.ts +19 -5
  31. package/pi-extensions/lib/acp/engraving.ts +46 -2
  32. package/pi-extensions/lib/acp/event-mapper.ts +26 -16
  33. package/pi-extensions/lib/acp/models.ts +69 -7
  34. package/pi-extensions/lib/acp/overlay.ts +234 -5
  35. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  36. package/prompts/engraving.md +13 -5
  37. package/run.sh +386 -119
  38. package/scripts/check-acp-carrier-augment.ts +246 -10
  39. package/scripts/check-acp-cortex.ts +668 -0
  40. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  41. package/scripts/check-acp-provider-surface.ts +50 -6
  42. package/scripts/check-acp-sdk-surface.ts +60 -21
  43. package/scripts/check-acp-session-reuse.ts +64 -1
  44. package/scripts/check-acp-stop-reason.ts +342 -0
  45. package/scripts/check-entwurf-v2-surface.ts +41 -0
  46. package/scripts/check-gate-qualification.ts +7 -2
  47. package/scripts/check-probe-cli-shim.ts +879 -0
  48. package/scripts/check-probe-ordering.ts +2482 -0
  49. package/scripts/check-release-gate-outcomes.ts +332 -0
  50. package/scripts/check-shell-quote.ts +4 -4
  51. package/scripts/fixtures/probe-cli-shim +20 -0
  52. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  53. package/scripts/lib/live-skip.ts +33 -0
  54. package/scripts/lib/probe-acp-turn.ts +228 -0
  55. package/scripts/lib/probe-cli-shim.ts +464 -0
  56. package/scripts/lib/probe-cli-target.ts +165 -0
  57. package/scripts/lib/probe-event-log.ts +383 -0
  58. package/scripts/lib/probe-verdict.ts +1213 -0
  59. package/scripts/lib/step-outcome.sh +88 -0
  60. package/scripts/mutants/acp-augment.json +106 -0
  61. package/scripts/mutants/acp-cortex.json +196 -0
  62. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  63. package/scripts/mutants/acp-stop-reason.json +80 -0
  64. package/scripts/mutants/probe-ordering.json +1054 -0
  65. package/scripts/mutants/release-gate.json +105 -0
  66. package/scripts/mutants/v2-surface.json +26 -15
  67. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  68. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  69. package/scripts/smoke-acp-cortex-live.ts +386 -0
  70. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  71. package/scripts/smoke-acp-mcp-live.ts +2 -2
  72. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  73. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
  74. package/scripts/smoke-acp-overlay-live.ts +2 -2
  75. package/scripts/smoke-acp-provider-live.ts +2 -2
  76. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  77. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  78. package/scripts/smoke-acp-skill-live.ts +2 -2
  79. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  80. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  81. package/scripts/smoke-agy-native-push-live.ts +4 -3
  82. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  83. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  84. package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
  85. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
  86. package/scripts/smoke-meta-install-state.sh +4 -0
@@ -0,0 +1,228 @@
1
+ // §11-7 ordering probe — the ACP-side turn driver (probe-dedicated raw client).
2
+ //
3
+ // §11-7 names two client seams and requires picking one explicitly. This lane
4
+ // picks the RAW CLIENT (not the stdio wire proxy), so it carries §11-7's bound:
5
+ // it is NOT the production path and must be gated as issuing the SAME calls,
6
+ // arguments, and order as the backend's real sequence (backend.ts runNewTurn) —
7
+ // otherwise the probe measures a lookalike. check-probe-ordering is that gate;
8
+ // it drives this exact function over a recording fake connection.
9
+ //
10
+ // Production values arrive through the `adapter` / `enrichMcpServers` seams —
11
+ // the LIVE runner tsc-emits pi-extensions and injects the REAL claudeAdapter +
12
+ // enrichMcpServersWithEnvelope (backend-adapter.ts carries `.js` value imports,
13
+ // so it is not strip-types-loadable from here; the emit-then-import pattern is
14
+ // the house one, see check-acp-session-reuse). This module therefore holds ONLY
15
+ // the orchestration backend.ts owns: phase order, arg assembly, timeouts, and
16
+ // phase-attributed failure — each pinned against backend.ts source by the gate
17
+ // so production drift turns the gate red instead of silently unbinding the probe.
18
+ //
19
+ // Every phase start/end is stamped into the shared NDJSON log — including
20
+ // set-model, because §11-7 measured that dropping it misreads an enforceModel
21
+ // stall as C or D.
22
+
23
+ import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
24
+ import type { AcpConnectionLike } from "../../pi-extensions/lib/acp/acp-client.ts";
25
+ import type { ResolvedAcpConfig } from "../../pi-extensions/lib/acp/config.ts";
26
+ import { PROBE_EVENTS, type ProbeEventName } from "./probe-event-log.ts";
27
+
28
+ export interface ProbePhaseTimeouts {
29
+ initializeMs: number;
30
+ newSessionMs: number;
31
+ setModelMs: number;
32
+ }
33
+
34
+ // BOOTSTRAP phase timeouts — MUST equal backend.ts's INITIALIZE/NEW_SESSION/
35
+ // SET_MODEL_TIMEOUT_MS (check-probe-ordering pins them against the source).
36
+ // §11-7: a bootstrap-phase D verdict is only readable against production's own
37
+ // boundaries, so the probe may not invent its own.
38
+ export const PROBE_PHASE_TIMEOUTS: ProbePhaseTimeouts = {
39
+ initializeMs: 30_000,
40
+ newSessionMs: 30_000,
41
+ setModelMs: 30_000,
42
+ };
43
+
44
+ // The prompt phase has NO production counterpart to match: backend.ts ends a
45
+ // prompt on lifecycle events only (resolve / abort / child gone) and carries no
46
+ // wall-clock cutoff, by policy — an active turn is not a failed turn for being
47
+ // long. This value is therefore the MEASUREMENT HARNESS's own bounded
48
+ // observation horizon: it stops a probe RUN from hanging forever. It is not a
49
+ // production contract, not an equivalence, and not a deadline any §11-7 verdict
50
+ // is read against. Horizon expiry means the run's prompt window is
51
+ // INCONCLUSIVE — the artifact is preserved and read as "not measured", never as
52
+ // a model of production killing a turn.
53
+ export const PROBE_PROMPT_OBSERVATION_MS = 600_000;
54
+
55
+ /** The two adapter methods the turn drives — satisfied by the REAL claudeAdapter
56
+ * (emitted) in the LIVE runner and by recording stubs in the gate. */
57
+ export interface ProbeAdapterSeam {
58
+ buildSessionMeta(
59
+ params: { modelId: string; nativeModelId: string; config: ResolvedAcpConfig },
60
+ carrier: string | null,
61
+ ): Record<string, unknown> | undefined;
62
+ enforceModel(params: {
63
+ connection: AcpConnectionLike;
64
+ acpSessionId: string;
65
+ modelId: string;
66
+ nativeModelId: string;
67
+ }): Promise<void>;
68
+ }
69
+
70
+ /** Production's enrichMcpServersWithEnvelope shape (config.ts). */
71
+ export type ProbeMcpEnricher = (
72
+ servers: ResolvedAcpConfig["mcpServers"],
73
+ envelope: { modelId?: string; piSessionId?: string },
74
+ ) => ResolvedAcpConfig["mcpServers"];
75
+
76
+ /** The turn phases in production order. `initialize` failures are P0/I0 input
77
+ * (run-invalidating), the rest map to phase-qualified D readings. */
78
+ export type ProbeTurnPhase = "initialize" | "newSession" | "enforceModel" | "prompt";
79
+
80
+ /** A phase-attributed turn failure — the classifier needs WHICH wire step died,
81
+ * never just that the turn died. */
82
+ export class ProbePhaseError extends Error {
83
+ readonly phase: ProbeTurnPhase;
84
+ readonly timedOut: boolean;
85
+
86
+ constructor(phase: ProbeTurnPhase, cause: Error, timedOut: boolean) {
87
+ super(`${phase}: ${cause.message}`);
88
+ this.phase = phase;
89
+ this.timedOut = timedOut;
90
+ this.cause = cause;
91
+ }
92
+ }
93
+
94
+ class PhaseTimeoutError extends Error {}
95
+
96
+ // Same guard shape as backend.ts withTimeout / smoke-acp-raw-turn-live: always
97
+ // clear the timer so a passing turn cannot pin the event loop on a stale timer.
98
+ function withPhaseTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
99
+ let timer: ReturnType<typeof setTimeout> | undefined;
100
+ const timeout = new Promise<never>((_, reject) => {
101
+ timer = setTimeout(() => reject(new PhaseTimeoutError(`${label} timed out after ${ms}ms`)), ms);
102
+ });
103
+ return Promise.race([p, timeout]).finally(() => {
104
+ if (timer) clearTimeout(timer);
105
+ });
106
+ }
107
+
108
+ export interface ProbeTurnParams {
109
+ cwd: string;
110
+ modelId: string;
111
+ nativeModelId: string;
112
+ config: ResolvedAcpConfig;
113
+ /** Production carrier (adapter.loadCarrier result) — folded into `_meta` by
114
+ * buildSessionMeta so the probe's newSession matches production shape. */
115
+ carrier: string | null;
116
+ promptText: string;
117
+ adapter: ProbeAdapterSeam;
118
+ enrichMcpServers: ProbeMcpEnricher;
119
+ log: (event: ProbeEventName, payload?: Record<string, unknown>) => void;
120
+ /** Bootstrap boundaries (production-pinned). Defaults to PROBE_PHASE_TIMEOUTS. */
121
+ timeouts?: ProbePhaseTimeouts;
122
+ /** Harness observation horizon for the prompt phase — NOT a production deadline. */
123
+ promptObservationMs?: number;
124
+ }
125
+
126
+ export interface ProbeTurnResult {
127
+ acpSessionId: string;
128
+ stopReason: string | undefined;
129
+ }
130
+
131
+ async function runPhase<T>(
132
+ phase: ProbeTurnPhase,
133
+ startEvent: ProbeEventName,
134
+ endEvent: ProbeEventName,
135
+ log: ProbeTurnParams["log"],
136
+ ms: number,
137
+ body: () => Promise<T>,
138
+ ): Promise<T> {
139
+ log(startEvent, { timeoutMs: ms });
140
+ try {
141
+ const result = await withPhaseTimeout(phase, body(), ms);
142
+ log(endEvent, { ok: true });
143
+ return result;
144
+ } catch (err) {
145
+ const error = err instanceof Error ? err : new Error(String(err));
146
+ const timedOut = error instanceof PhaseTimeoutError;
147
+ log(endEvent, { ok: false, timedOut, error: error.message });
148
+ throw new ProbePhaseError(phase, error, timedOut);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Drive ONE production-shaped turn over an already-connected ACP connection:
154
+ *
155
+ * initialize → newSession → enforceModel(setSessionConfigOption) → prompt
156
+ *
157
+ * The connection seam is `AcpConnectionLike` — the same seam backend.ts drives —
158
+ * so the sameness gate runs this function over a recording fake with zero
159
+ * launch/IO coupling.
160
+ */
161
+ export async function driveProbeTurn(connection: AcpConnectionLike, params: ProbeTurnParams): Promise<ProbeTurnResult> {
162
+ const t = params.timeouts ?? PROBE_PHASE_TIMEOUTS;
163
+ const log = params.log;
164
+
165
+ await runPhase("initialize", PROBE_EVENTS.initializeStart, PROBE_EVENTS.initializeEnd, log, t.initializeMs, () =>
166
+ connection.initialize({
167
+ protocolVersion: PROTOCOL_VERSION,
168
+ clientCapabilities: {},
169
+ clientInfo: { name: "entwurf", version: "s2d" },
170
+ }),
171
+ );
172
+
173
+ // _meta + wire servers exactly as backend.ts assembles them (S2g / carrier-less shape): the
174
+ // injected production buildSessionMeta (omitted key when undefined) + the
175
+ // injected production envelope enrichment over the resolved config.
176
+ const sessionMeta = params.adapter.buildSessionMeta(
177
+ { modelId: params.modelId, nativeModelId: params.nativeModelId, config: params.config },
178
+ params.carrier,
179
+ );
180
+ const wireMcpServers = params.enrichMcpServers(params.config.mcpServers, {
181
+ modelId: params.modelId,
182
+ piSessionId: process.env.PI_SESSION_ID?.trim() || undefined,
183
+ });
184
+ const newSessionArgs =
185
+ sessionMeta === undefined
186
+ ? { cwd: params.cwd, mcpServers: wireMcpServers }
187
+ : { cwd: params.cwd, mcpServers: wireMcpServers, _meta: sessionMeta };
188
+
189
+ const created = await runPhase(
190
+ "newSession",
191
+ PROBE_EVENTS.newSessionStart,
192
+ PROBE_EVENTS.newSessionEnd,
193
+ log,
194
+ t.newSessionMs,
195
+ () => connection.newSession(newSessionArgs),
196
+ );
197
+ const acpSessionId = created?.sessionId;
198
+ if (!acpSessionId) {
199
+ throw new ProbePhaseError("newSession", new Error("newSession returned no sessionId"), false);
200
+ }
201
+
202
+ await runPhase("enforceModel", PROBE_EVENTS.setModelStart, PROBE_EVENTS.setModelEnd, log, t.setModelMs, () =>
203
+ params.adapter.enforceModel({
204
+ connection,
205
+ acpSessionId,
206
+ modelId: params.modelId,
207
+ nativeModelId: params.nativeModelId,
208
+ }),
209
+ );
210
+
211
+ // The horizon below bounds THIS MEASUREMENT RUN, not the turn: production has
212
+ // no prompt deadline, so an expiry here is an inconclusive observation.
213
+ const promptObservationMs = params.promptObservationMs ?? PROBE_PROMPT_OBSERVATION_MS;
214
+ const promptResult = await runPhase(
215
+ "prompt",
216
+ PROBE_EVENTS.promptStart,
217
+ PROBE_EVENTS.promptEnd,
218
+ log,
219
+ promptObservationMs,
220
+ () =>
221
+ connection.prompt({
222
+ sessionId: acpSessionId,
223
+ prompt: [{ type: "text", text: params.promptText }],
224
+ }),
225
+ );
226
+
227
+ return { acpSessionId, stopReason: promptResult?.stopReason };
228
+ }