@dzhechkov/harness-core 0.8.6 → 0.8.10
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/.dz-manifest.json +257 -53
- package/README.md +48 -1
- package/dist/amendment-trace.d.ts.map +1 -1
- package/dist/amendment-trace.js +12 -1
- package/dist/amendment-trace.js.map +1 -1
- package/dist/compounding.d.ts +54 -0
- package/dist/compounding.d.ts.map +1 -1
- package/dist/compounding.js +221 -1
- package/dist/compounding.js.map +1 -1
- package/dist/eta.d.ts +92 -0
- package/dist/eta.d.ts.map +1 -0
- package/dist/eta.js +488 -0
- package/dist/eta.js.map +1 -0
- package/dist/feature-adr-checkpoints.js +1 -1
- package/dist/feature-adr-decision-recall.d.ts +167 -0
- package/dist/feature-adr-decision-recall.d.ts.map +1 -0
- package/dist/feature-adr-decision-recall.js +519 -0
- package/dist/feature-adr-decision-recall.js.map +1 -0
- package/dist/feature-adr-landing.d.ts +37 -0
- package/dist/feature-adr-landing.d.ts.map +1 -0
- package/dist/feature-adr-landing.js +59 -0
- package/dist/feature-adr-landing.js.map +1 -0
- package/dist/feature-adr-routing.d.ts +2 -2
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +7 -11
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard-promotion.d.ts +41 -0
- package/dist/guard-promotion.d.ts.map +1 -1
- package/dist/guard-promotion.js +218 -4
- package/dist/guard-promotion.js.map +1 -1
- package/dist/guard-volume.d.ts +108 -0
- package/dist/guard-volume.d.ts.map +1 -0
- package/dist/guard-volume.js +536 -0
- package/dist/guard-volume.js.map +1 -0
- package/dist/guard.d.ts +9 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +55 -4
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/integration-apply.d.ts +25 -0
- package/dist/integration-apply.d.ts.map +1 -0
- package/dist/integration-apply.js +299 -0
- package/dist/integration-apply.js.map +1 -0
- package/dist/integration-evidence.d.ts +46 -0
- package/dist/integration-evidence.d.ts.map +1 -0
- package/dist/integration-evidence.js +44 -0
- package/dist/integration-evidence.js.map +1 -0
- package/dist/integration-probe-worker.d.ts +22 -0
- package/dist/integration-probe-worker.d.ts.map +1 -0
- package/dist/integration-probe-worker.js +334 -0
- package/dist/integration-probe-worker.js.map +1 -0
- package/dist/integrations-verify.d.ts +60 -0
- package/dist/integrations-verify.d.ts.map +1 -0
- package/dist/integrations-verify.js +194 -0
- package/dist/integrations-verify.js.map +1 -0
- package/dist/lesson-generalization.d.ts +29 -0
- package/dist/lesson-generalization.d.ts.map +1 -0
- package/dist/lesson-generalization.js +84 -0
- package/dist/lesson-generalization.js.map +1 -0
- package/dist/operations.d.ts +27 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +178 -2
- package/dist/operations.js.map +1 -1
- package/dist/patterns.d.ts +27 -1
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +211 -45
- package/dist/patterns.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +20 -17
- package/dist/setup.js.map +1 -1
- package/dist/target-integrations.d.ts +65 -0
- package/dist/target-integrations.d.ts.map +1 -0
- package/dist/target-integrations.js +152 -0
- package/dist/target-integrations.js.map +1 -0
- package/dist/vector-tier.d.ts +6 -1
- package/dist/vector-tier.d.ts.map +1 -1
- package/dist/vector-tier.js +32 -7
- package/dist/vector-tier.js.map +1 -1
- package/package.json +9 -8
- package/sbom.json +562 -52
- package/src/amendment-trace.ts +12 -1
- package/src/compounding.ts +300 -1
- package/src/eta.ts +590 -0
- package/src/feature-adr-checkpoints.ts +1 -1
- package/src/feature-adr-decision-recall.ts +652 -0
- package/src/feature-adr-landing.ts +109 -0
- package/src/feature-adr-routing.ts +7 -11
- package/src/guard-promotion.ts +245 -4
- package/src/guard-volume.ts +752 -0
- package/src/guard.ts +70 -4
- package/src/index.ts +63 -2
- package/src/integration-apply.ts +332 -0
- package/src/integration-evidence.ts +89 -0
- package/src/integration-probe-worker.ts +310 -0
- package/src/integration-receipts/claude-code/mcp/2.1.235.json +35 -0
- package/src/integrations-verify.ts +258 -0
- package/src/lesson-generalization.ts +115 -0
- package/src/operations.ts +199 -2
- package/src/patterns.ts +252 -43
- package/src/setup.ts +20 -17
- package/src/target-integrations.ts +225 -0
- package/src/vector-tier.ts +44 -14
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Exact-version receipt and evidence classifiers. */
|
|
2
|
+
|
|
3
|
+
import type { IntegrationComponent, IntegrationReasonCode } from '@dzhechkov/core';
|
|
4
|
+
|
|
5
|
+
import type { TargetName } from './targets.js';
|
|
6
|
+
|
|
7
|
+
export interface ProbeReceipt {
|
|
8
|
+
readonly schemaVersion: 1;
|
|
9
|
+
readonly target: TargetName;
|
|
10
|
+
readonly component: IntegrationComponent;
|
|
11
|
+
readonly productSurface: string;
|
|
12
|
+
readonly runtimeVersion: string;
|
|
13
|
+
readonly scope: 'project' | 'user' | 'plugin';
|
|
14
|
+
readonly carrierPath: string;
|
|
15
|
+
readonly serializerId: string;
|
|
16
|
+
readonly probeCommandId: string;
|
|
17
|
+
readonly probeCommand: readonly string[];
|
|
18
|
+
readonly timeoutMs: number;
|
|
19
|
+
readonly observedExitCode: number;
|
|
20
|
+
readonly transcriptSha256: `sha256:${string}`;
|
|
21
|
+
readonly parsedObservation: {
|
|
22
|
+
readonly registrationId: string;
|
|
23
|
+
readonly scope: 'project';
|
|
24
|
+
readonly status: string;
|
|
25
|
+
readonly approval: 'pending' | 'approved' | 'unknown';
|
|
26
|
+
readonly ready: boolean;
|
|
27
|
+
};
|
|
28
|
+
readonly wrongPathNegativeControl: {
|
|
29
|
+
readonly carrierPath: string;
|
|
30
|
+
readonly probeCommand: readonly string[];
|
|
31
|
+
readonly timeoutMs: number;
|
|
32
|
+
readonly observedExitCode: number;
|
|
33
|
+
readonly transcriptSha256: `sha256:${string}`;
|
|
34
|
+
readonly registrationObserved: false;
|
|
35
|
+
readonly reasonCode: 'POST_WRITE_REGISTRATION_NOT_OBSERVED';
|
|
36
|
+
};
|
|
37
|
+
readonly transportFamily: 'stdio-local' | 'http';
|
|
38
|
+
readonly fieldFamilies: readonly string[];
|
|
39
|
+
readonly observedAt: string;
|
|
40
|
+
readonly documentation: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const CLAUDE_MCP_RECEIPT: ProbeReceipt = {
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
target: 'claude-code',
|
|
46
|
+
component: 'mcp',
|
|
47
|
+
productSurface: 'claude-code-cli',
|
|
48
|
+
runtimeVersion: '2.1.235',
|
|
49
|
+
scope: 'project',
|
|
50
|
+
carrierPath: '.mcp.json',
|
|
51
|
+
serializerId: 'claude-project-mcp-json-v1',
|
|
52
|
+
probeCommandId: 'claude-project-mcp-get-v1',
|
|
53
|
+
probeCommand: ['claude', 'mcp', 'get', 'dz-registration-probe'],
|
|
54
|
+
timeoutMs: 30_000,
|
|
55
|
+
observedExitCode: 0,
|
|
56
|
+
transcriptSha256: 'sha256:0e30c79de6381c7e7b93a39cc644cb282b6c95ceaeb4a0f5a94fbfecb5c71549',
|
|
57
|
+
parsedObservation: {
|
|
58
|
+
registrationId: 'dz-registration-probe',
|
|
59
|
+
scope: 'project',
|
|
60
|
+
status: 'Pending approval',
|
|
61
|
+
approval: 'pending',
|
|
62
|
+
ready: false,
|
|
63
|
+
},
|
|
64
|
+
wrongPathNegativeControl: {
|
|
65
|
+
carrierPath: '.claude/.mcp.json',
|
|
66
|
+
probeCommand: ['claude', 'mcp', 'get', 'dz-wrong-path-probe'],
|
|
67
|
+
timeoutMs: 30_000,
|
|
68
|
+
observedExitCode: 1,
|
|
69
|
+
transcriptSha256: 'sha256:aba424ed140ee785947f314942566ae4bccc7b7c44ca9be785efbf36be0f62d5',
|
|
70
|
+
registrationObserved: false,
|
|
71
|
+
reasonCode: 'POST_WRITE_REGISTRATION_NOT_OBSERVED',
|
|
72
|
+
},
|
|
73
|
+
transportFamily: 'stdio-local',
|
|
74
|
+
fieldFamilies: ['type', 'command', 'args'],
|
|
75
|
+
observedAt: '2026-08-30T19:22:15.366Z',
|
|
76
|
+
documentation: ['https://code.claude.com/docs/en/mcp'],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface EvidenceAssessment {
|
|
80
|
+
readonly eligible: boolean;
|
|
81
|
+
readonly reasonCode?: IntegrationReasonCode;
|
|
82
|
+
readonly receipt?: ProbeReceipt;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function assessClaudeMcpEvidence(runtimeVersion: string | undefined): EvidenceAssessment {
|
|
86
|
+
if (runtimeVersion === undefined) return { eligible: false, reasonCode: 'TARGET_BINARY_UNAVAILABLE' };
|
|
87
|
+
if (runtimeVersion !== CLAUDE_MCP_RECEIPT.runtimeVersion) return { eligible: false, reasonCode: 'RECEIPT_STALE' };
|
|
88
|
+
return { eligible: true, receipt: CLAUDE_MCP_RECEIPT };
|
|
89
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/** Isolated bounded subprocess runner used by integration registration probes. */
|
|
2
|
+
|
|
3
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
4
|
+
import { realpathSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export const PROBE_REDACTION_MARKER = '[REDACTED]';
|
|
9
|
+
export const PROBE_TRUNCATION_MARKER = '[TRUNCATED]';
|
|
10
|
+
const WORKER_INPUT_MAX_BYTES = 64 * 1024;
|
|
11
|
+
const REDACTION_WORDS = ['bearer', 'token', 'secret', 'password', 'apikey', 'api_key', 'api-key'] as const;
|
|
12
|
+
const ASSIGNMENT_WORDS = new Set(REDACTION_WORDS.slice(1));
|
|
13
|
+
|
|
14
|
+
export interface ProbeWorkerRequest {
|
|
15
|
+
readonly command: string;
|
|
16
|
+
readonly args: readonly string[];
|
|
17
|
+
readonly cwd: string;
|
|
18
|
+
readonly timeoutMs: number;
|
|
19
|
+
readonly streamMaxBytes: number;
|
|
20
|
+
readonly aggregateMaxBytes: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProbeWorkerResult {
|
|
24
|
+
readonly status: number | null;
|
|
25
|
+
readonly signal: string | null;
|
|
26
|
+
readonly stdoutBase64: string;
|
|
27
|
+
readonly stderrBase64: string;
|
|
28
|
+
readonly errorCode?: string;
|
|
29
|
+
readonly truncated: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type RedactionState = 'normal' | 'word' | 'passthrough-word' | 'bearer-space' | 'bearer-value' | 'key-space' | 'key-value-space' | 'key-value';
|
|
33
|
+
|
|
34
|
+
/** Stateful UTF-8 redactor: raw chunks are transformed immediately and are never retained. */
|
|
35
|
+
class StreamingProbeRedactor {
|
|
36
|
+
readonly #decoder = new TextDecoder('utf-8', { fatal: true });
|
|
37
|
+
readonly #sanitized: string[] = [];
|
|
38
|
+
#state: RedactionState = 'normal';
|
|
39
|
+
#word = '';
|
|
40
|
+
#keyBuffer = '';
|
|
41
|
+
#invalidUtf8 = false;
|
|
42
|
+
#finished = false;
|
|
43
|
+
|
|
44
|
+
write(chunk: Uint8Array): boolean {
|
|
45
|
+
if (this.#finished || this.#invalidUtf8) return false;
|
|
46
|
+
try {
|
|
47
|
+
this.#consume(this.#decoder.decode(chunk, { stream: true }));
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
this.#invalidUtf8 = true;
|
|
51
|
+
this.#sanitized.length = 0;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
finish(): { bytes: Buffer; invalidUtf8: boolean } {
|
|
57
|
+
if (!this.#finished && !this.#invalidUtf8) {
|
|
58
|
+
try {
|
|
59
|
+
this.#consume(this.#decoder.decode());
|
|
60
|
+
if (this.#state === 'word') this.#emit(this.#word);
|
|
61
|
+
else if (this.#state === 'key-space') this.#emit(this.#keyBuffer);
|
|
62
|
+
} catch {
|
|
63
|
+
this.#invalidUtf8 = true;
|
|
64
|
+
this.#sanitized.length = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
this.#finished = true;
|
|
68
|
+
return {
|
|
69
|
+
bytes: this.#invalidUtf8 ? Buffer.alloc(0) : Buffer.from(this.#sanitized.join(''), 'utf8'),
|
|
70
|
+
invalidUtf8: this.#invalidUtf8,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#emit(value: string): void { this.#sanitized.push(value); }
|
|
75
|
+
#wordChar(value: string): boolean { return /^[A-Za-z0-9_-]$/.test(value); }
|
|
76
|
+
#secretEnd(value: string, comma: boolean): boolean {
|
|
77
|
+
return /\s/.test(value) || value === '"' || value === "'" || (comma && value === ',');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#consume(text: string): void {
|
|
81
|
+
for (const value of text) this.#character(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#character(value: string): void {
|
|
85
|
+
if (this.#state === 'normal') {
|
|
86
|
+
if (this.#wordChar(value)) { this.#word = value; this.#state = 'word'; }
|
|
87
|
+
else this.#emit(value);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (this.#state === 'word') {
|
|
91
|
+
if (this.#wordChar(value)) {
|
|
92
|
+
this.#word += value;
|
|
93
|
+
if (!REDACTION_WORDS.some((word) => word.startsWith(this.#word.toLowerCase()))) {
|
|
94
|
+
this.#emit(this.#word); this.#word = ''; this.#state = 'passthrough-word';
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const word = this.#word;
|
|
99
|
+
const lower = word.toLowerCase();
|
|
100
|
+
this.#word = '';
|
|
101
|
+
if (lower === 'bearer' && /\s/.test(value)) {
|
|
102
|
+
this.#emit(word + value); this.#state = 'bearer-space'; return;
|
|
103
|
+
}
|
|
104
|
+
if (ASSIGNMENT_WORDS.has(lower as typeof REDACTION_WORDS[number]) && (/\s/.test(value) || value === ':' || value === '=')) {
|
|
105
|
+
if (value === ':' || value === '=') { this.#emit(word + value); this.#state = 'key-value-space'; }
|
|
106
|
+
else { this.#keyBuffer = word + value; this.#state = 'key-space'; }
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.#emit(word); this.#state = 'normal'; this.#character(value); return;
|
|
110
|
+
}
|
|
111
|
+
if (this.#state === 'passthrough-word') {
|
|
112
|
+
if (this.#wordChar(value)) this.#emit(value);
|
|
113
|
+
else { this.#state = 'normal'; this.#character(value); }
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (this.#state === 'bearer-space') {
|
|
117
|
+
if (/\s/.test(value)) this.#emit(value);
|
|
118
|
+
else if (this.#secretEnd(value, false)) { this.#state = 'normal'; this.#character(value); }
|
|
119
|
+
else { this.#emit(PROBE_REDACTION_MARKER); this.#state = 'bearer-value'; }
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (this.#state === 'bearer-value') {
|
|
123
|
+
if (this.#secretEnd(value, false)) { this.#state = 'normal'; this.#character(value); }
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (this.#state === 'key-space') {
|
|
127
|
+
if (/\s/.test(value)) this.#keyBuffer += value;
|
|
128
|
+
else if (value === ':' || value === '=') { this.#emit(this.#keyBuffer + value); this.#keyBuffer = ''; this.#state = 'key-value-space'; }
|
|
129
|
+
else { this.#emit(this.#keyBuffer); this.#keyBuffer = ''; this.#state = 'normal'; this.#character(value); }
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.#state === 'key-value-space') {
|
|
133
|
+
if (/\s/.test(value)) this.#emit(value);
|
|
134
|
+
else if (this.#secretEnd(value, true)) { this.#state = 'normal'; this.#character(value); }
|
|
135
|
+
else { this.#emit(PROBE_REDACTION_MARKER); this.#state = 'key-value'; }
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.#secretEnd(value, true)) { this.#state = 'normal'; this.#character(value); }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function redactProbeText(input: string): string {
|
|
143
|
+
const redactor = new StreamingProbeRedactor();
|
|
144
|
+
redactor.write(Buffer.from(input, 'utf8'));
|
|
145
|
+
return redactor.finish().bytes.toString('utf8');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function killProcessTree(child: ChildProcess): void {
|
|
149
|
+
if (child.pid === undefined) return;
|
|
150
|
+
if (process.platform !== 'win32') {
|
|
151
|
+
try {
|
|
152
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
153
|
+
return;
|
|
154
|
+
} catch {
|
|
155
|
+
child.kill('SIGKILL');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Windows has no negative-PID process groups. taskkill /T is the native tree
|
|
160
|
+
// primitive; the direct kill remains a fail-safe if taskkill itself is absent.
|
|
161
|
+
const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], {
|
|
162
|
+
stdio: 'ignore',
|
|
163
|
+
windowsHide: true,
|
|
164
|
+
});
|
|
165
|
+
killer.once('error', () => child.kill('SIGKILL'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function validateRequest(value: unknown): ProbeWorkerRequest {
|
|
169
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid worker request');
|
|
170
|
+
const row = value as Record<string, unknown>;
|
|
171
|
+
if (typeof row['command'] !== 'string' || typeof row['cwd'] !== 'string' ||
|
|
172
|
+
!Array.isArray(row['args']) || !row['args'].every((arg) => typeof arg === 'string')) {
|
|
173
|
+
throw new Error('invalid worker command');
|
|
174
|
+
}
|
|
175
|
+
for (const key of ['timeoutMs', 'streamMaxBytes', 'aggregateMaxBytes'] as const) {
|
|
176
|
+
if (!Number.isSafeInteger(row[key]) || (row[key] as number) <= 0) throw new Error(`invalid worker ${key}`);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
command: row['command'], cwd: row['cwd'], args: row['args'] as string[],
|
|
180
|
+
timeoutMs: row['timeoutMs'] as number,
|
|
181
|
+
streamMaxBytes: row['streamMaxBytes'] as number,
|
|
182
|
+
aggregateMaxBytes: row['aggregateMaxBytes'] as number,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function withTruncationMarker(bytes: Buffer, maxBytes: number): Buffer {
|
|
187
|
+
const marker = Buffer.from(PROBE_TRUNCATION_MARKER, 'utf8');
|
|
188
|
+
if (marker.length >= maxBytes) return marker.subarray(0, maxBytes);
|
|
189
|
+
const keep = Math.min(bytes.length, maxBytes - marker.length);
|
|
190
|
+
let prefix = bytes.subarray(0, keep);
|
|
191
|
+
while (prefix.length > 0) {
|
|
192
|
+
try {
|
|
193
|
+
new TextDecoder('utf-8', { fatal: true }).decode(prefix);
|
|
194
|
+
break;
|
|
195
|
+
} catch {
|
|
196
|
+
prefix = prefix.subarray(0, prefix.length - 1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return Buffer.concat([prefix, marker]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function runProbeWorker(request: ProbeWorkerRequest): Promise<ProbeWorkerResult> {
|
|
203
|
+
return new Promise((resolveResult) => {
|
|
204
|
+
const stdout = new StreamingProbeRedactor();
|
|
205
|
+
const stderr = new StreamingProbeRedactor();
|
|
206
|
+
let stdoutBytes = 0;
|
|
207
|
+
let stderrBytes = 0;
|
|
208
|
+
let truncated = false;
|
|
209
|
+
let truncatedStream: 'stdout' | 'stderr' | undefined;
|
|
210
|
+
let timedOut = false;
|
|
211
|
+
let settled = false;
|
|
212
|
+
let invalidUtf8 = false;
|
|
213
|
+
let spawnError: string | undefined;
|
|
214
|
+
const child = spawn(request.command, [...request.args], {
|
|
215
|
+
cwd: request.cwd,
|
|
216
|
+
detached: process.platform !== 'win32',
|
|
217
|
+
env: process.env,
|
|
218
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
219
|
+
windowsHide: true,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const killOnce = (): void => {
|
|
223
|
+
if (!child.killed) killProcessTree(child);
|
|
224
|
+
};
|
|
225
|
+
const capture = (stream: 'stdout' | 'stderr', chunk: Buffer): void => {
|
|
226
|
+
if (settled || truncated) return;
|
|
227
|
+
const ownBytes = stream === 'stdout' ? stdoutBytes : stderrBytes;
|
|
228
|
+
const remaining = Math.max(0, Math.min(
|
|
229
|
+
request.streamMaxBytes - ownBytes,
|
|
230
|
+
request.aggregateMaxBytes - stdoutBytes - stderrBytes,
|
|
231
|
+
));
|
|
232
|
+
const accepted = chunk.subarray(0, Math.min(chunk.length, remaining));
|
|
233
|
+
if (accepted.length > 0 && !(stream === 'stdout' ? stdout : stderr).write(accepted)) {
|
|
234
|
+
invalidUtf8 = true;
|
|
235
|
+
killOnce();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (stream === 'stdout') stdoutBytes += accepted.length;
|
|
239
|
+
else stderrBytes += accepted.length;
|
|
240
|
+
if (accepted.length < chunk.length) {
|
|
241
|
+
truncated = true;
|
|
242
|
+
truncatedStream = stream;
|
|
243
|
+
killOnce();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
child.stdout?.on('data', (chunk: Buffer) => capture('stdout', chunk));
|
|
247
|
+
child.stderr?.on('data', (chunk: Buffer) => capture('stderr', chunk));
|
|
248
|
+
child.once('error', (error: NodeJS.ErrnoException) => {
|
|
249
|
+
spawnError = error.code ?? 'PROCESS_ERROR';
|
|
250
|
+
});
|
|
251
|
+
const timer = setTimeout(() => {
|
|
252
|
+
timedOut = true;
|
|
253
|
+
killOnce();
|
|
254
|
+
}, request.timeoutMs);
|
|
255
|
+
timer.unref();
|
|
256
|
+
child.once('close', (status, signal) => {
|
|
257
|
+
settled = true;
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
const cleanOut = stdout.finish();
|
|
260
|
+
const cleanErr = stderr.finish();
|
|
261
|
+
invalidUtf8 ||= cleanOut.invalidUtf8 || cleanErr.invalidUtf8;
|
|
262
|
+
const stdoutLimit = Math.min(
|
|
263
|
+
request.streamMaxBytes,
|
|
264
|
+
Math.max(0, request.aggregateMaxBytes - cleanErr.bytes.length),
|
|
265
|
+
);
|
|
266
|
+
const stderrLimit = Math.min(
|
|
267
|
+
request.streamMaxBytes,
|
|
268
|
+
Math.max(0, request.aggregateMaxBytes - cleanOut.bytes.length),
|
|
269
|
+
);
|
|
270
|
+
const stdoutBytesOut = truncatedStream === 'stdout'
|
|
271
|
+
? withTruncationMarker(cleanOut.bytes, stdoutLimit)
|
|
272
|
+
: cleanOut.bytes;
|
|
273
|
+
const stderrBytesOut = truncatedStream === 'stderr'
|
|
274
|
+
? withTruncationMarker(cleanErr.bytes, stderrLimit)
|
|
275
|
+
: cleanErr.bytes;
|
|
276
|
+
resolveResult({
|
|
277
|
+
status,
|
|
278
|
+
signal,
|
|
279
|
+
stdoutBase64: stdoutBytesOut.toString('base64'),
|
|
280
|
+
stderrBase64: stderrBytesOut.toString('base64'),
|
|
281
|
+
...(timedOut ? { errorCode: 'ETIMEDOUT' }
|
|
282
|
+
: truncated ? { errorCode: 'OUTPUT_TRUNCATED' }
|
|
283
|
+
: invalidUtf8 ? { errorCode: 'INVALID_UTF8' }
|
|
284
|
+
: spawnError !== undefined ? { errorCode: spawnError } : {}),
|
|
285
|
+
truncated,
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function main(): Promise<void> {
|
|
292
|
+
const chunks: Buffer[] = [];
|
|
293
|
+
let size = 0;
|
|
294
|
+
for await (const chunk of process.stdin) {
|
|
295
|
+
const bytes = Buffer.from(chunk as Uint8Array);
|
|
296
|
+
size += bytes.length;
|
|
297
|
+
if (size > WORKER_INPUT_MAX_BYTES) throw new Error('worker request too large');
|
|
298
|
+
chunks.push(bytes);
|
|
299
|
+
}
|
|
300
|
+
const request = validateRequest(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
301
|
+
process.stdout.write(`${JSON.stringify(await runProbeWorker(request))}\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const invokedPath = process.argv[1];
|
|
305
|
+
if (invokedPath !== undefined && realpathSync(resolve(invokedPath)) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
306
|
+
main().catch((error) => {
|
|
307
|
+
process.stderr.write(`integration probe worker failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"target": "claude-code",
|
|
4
|
+
"component": "mcp",
|
|
5
|
+
"productSurface": "claude-code-cli",
|
|
6
|
+
"runtimeVersion": "2.1.235",
|
|
7
|
+
"scope": "project",
|
|
8
|
+
"carrierPath": ".mcp.json",
|
|
9
|
+
"serializerId": "claude-project-mcp-json-v1",
|
|
10
|
+
"probeCommandId": "claude-project-mcp-get-v1",
|
|
11
|
+
"probeCommand": ["claude", "mcp", "get", "dz-registration-probe"],
|
|
12
|
+
"timeoutMs": 30000,
|
|
13
|
+
"observedExitCode": 0,
|
|
14
|
+
"transcriptSha256": "sha256:0e30c79de6381c7e7b93a39cc644cb282b6c95ceaeb4a0f5a94fbfecb5c71549",
|
|
15
|
+
"parsedObservation": {
|
|
16
|
+
"registrationId": "dz-registration-probe",
|
|
17
|
+
"scope": "project",
|
|
18
|
+
"status": "Pending approval",
|
|
19
|
+
"approval": "pending",
|
|
20
|
+
"ready": false
|
|
21
|
+
},
|
|
22
|
+
"wrongPathNegativeControl": {
|
|
23
|
+
"carrierPath": ".claude/.mcp.json",
|
|
24
|
+
"probeCommand": ["claude", "mcp", "get", "dz-wrong-path-probe"],
|
|
25
|
+
"timeoutMs": 30000,
|
|
26
|
+
"observedExitCode": 1,
|
|
27
|
+
"transcriptSha256": "sha256:aba424ed140ee785947f314942566ae4bccc7b7c44ca9be785efbf36be0f62d5",
|
|
28
|
+
"registrationObserved": false,
|
|
29
|
+
"reasonCode": "POST_WRITE_REGISTRATION_NOT_OBSERVED"
|
|
30
|
+
},
|
|
31
|
+
"transportFamily": "stdio-local",
|
|
32
|
+
"fieldFamilies": ["type", "command", "args"],
|
|
33
|
+
"observedAt": "2026-08-30T19:22:15.366Z",
|
|
34
|
+
"documentation": ["https://code.claude.com/docs/en/mcp"]
|
|
35
|
+
}
|