@martinloop/mcp 0.2.0 → 0.2.7

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 (96) hide show
  1. package/README.md +118 -182
  2. package/dist/discovery-metadata.d.ts +21 -0
  3. package/dist/discovery-metadata.js +152 -0
  4. package/dist/discovery-support.d.ts +62 -0
  5. package/dist/discovery-support.js +224 -0
  6. package/dist/package-version.d.ts +1 -0
  7. package/dist/package-version.js +3 -0
  8. package/dist/prompts.d.ts +13 -3
  9. package/dist/prompts.js +537 -74
  10. package/dist/resources.d.ts +35 -5
  11. package/dist/resources.js +788 -71
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +375 -119
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1478 -394
  16. package/dist/tools/doctor.d.ts +2 -0
  17. package/dist/tools/doctor.js +18 -6
  18. package/dist/tools/eval.d.ts +24 -0
  19. package/dist/tools/eval.js +65 -0
  20. package/dist/tools/get-attempt.d.ts +13 -6
  21. package/dist/tools/get-attempt.js +14 -5
  22. package/dist/tools/get-run.d.ts +19 -12
  23. package/dist/tools/get-run.js +20 -11
  24. package/dist/tools/get-status.d.ts +19 -0
  25. package/dist/tools/get-status.js +30 -2
  26. package/dist/tools/get-verification-results.d.ts +10 -7
  27. package/dist/tools/get-verification-results.js +11 -6
  28. package/dist/tools/inspect-loop.d.ts +9 -0
  29. package/dist/tools/inspect-loop.js +11 -2
  30. package/dist/tools/list-runs.d.ts +25 -5
  31. package/dist/tools/list-runs.js +21 -4
  32. package/dist/tools/logs.d.ts +25 -0
  33. package/dist/tools/logs.js +49 -0
  34. package/dist/tools/plan.d.ts +20 -0
  35. package/dist/tools/plan.js +10 -0
  36. package/dist/tools/pr-tools.d.ts +31 -0
  37. package/dist/tools/pr-tools.js +111 -0
  38. package/dist/tools/preflight.d.ts +10 -0
  39. package/dist/tools/preflight.js +18 -4
  40. package/dist/tools/run-controls.d.ts +36 -0
  41. package/dist/tools/run-controls.js +88 -0
  42. package/dist/tools/run-dossier.d.ts +51 -4
  43. package/dist/tools/run-dossier.js +100 -5
  44. package/dist/tools/run-loop.d.ts +19 -0
  45. package/dist/tools/run-loop.js +61 -4
  46. package/dist/tools/run-store.d.ts +57 -3
  47. package/dist/tools/run-store.js +404 -53
  48. package/dist/tools/tool-errors.d.ts +37 -0
  49. package/dist/tools/tool-errors.js +170 -0
  50. package/dist/tools/tool-response.d.ts +16 -0
  51. package/dist/tools/tool-response.js +34 -0
  52. package/dist/tools/tool-support.d.ts +92 -2
  53. package/dist/tools/tool-support.js +385 -63
  54. package/dist/tools/triage-runs.d.ts +33 -0
  55. package/dist/tools/triage-runs.js +138 -0
  56. package/dist/tools/workflow-governance.d.ts +133 -0
  57. package/dist/tools/workflow-governance.js +581 -0
  58. package/dist/vendor/adapters/claude-cli.js +0 -1
  59. package/dist/vendor/adapters/cli-bridge.d.ts +5 -0
  60. package/dist/vendor/adapters/cli-bridge.js +16 -9
  61. package/dist/vendor/adapters/direct-provider.js +0 -1
  62. package/dist/vendor/adapters/index.d.ts +2 -1
  63. package/dist/vendor/adapters/index.js +2 -1
  64. package/dist/vendor/adapters/openai-compatible.d.ts +47 -0
  65. package/dist/vendor/adapters/openai-compatible.js +242 -0
  66. package/dist/vendor/adapters/runtime-support.js +0 -1
  67. package/dist/vendor/adapters/stub-agent-cli.js +0 -1
  68. package/dist/vendor/adapters/stub-direct-provider.js +0 -1
  69. package/dist/vendor/adapters/verifier-only.js +0 -1
  70. package/dist/vendor/contracts/governance.js +0 -1
  71. package/dist/vendor/contracts/index.d.ts +2 -0
  72. package/dist/vendor/contracts/index.js +1 -1
  73. package/dist/vendor/contracts/operator.d.ts +19 -0
  74. package/dist/vendor/contracts/operator.js +11 -0
  75. package/dist/vendor/core/compiler.js +0 -1
  76. package/dist/vendor/core/context-integrity.js +0 -1
  77. package/dist/vendor/core/grounding.js +0 -1
  78. package/dist/vendor/core/index.js +1 -2
  79. package/dist/vendor/core/leash.js +19 -12
  80. package/dist/vendor/core/persistence/compiler.js +0 -1
  81. package/dist/vendor/core/persistence/index.js +0 -1
  82. package/dist/vendor/core/persistence/ledger.js +0 -1
  83. package/dist/vendor/core/persistence/runs-reader.js +0 -1
  84. package/dist/vendor/core/persistence/store.js +0 -1
  85. package/dist/vendor/core/policy.js +0 -1
  86. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  87. package/dist/vendor/core/red-blue/red-phase.js +135 -0
  88. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  89. package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
  90. package/dist/vendor/core/rollback.js +2 -3
  91. package/dist/workflow-state.d.ts +25 -0
  92. package/dist/workflow-state.js +102 -0
  93. package/package.json +12 -7
  94. package/server.json +2 -2
  95. package/dist/tools/cockpit-support.d.ts +0 -69
  96. package/dist/tools/cockpit-support.js +0 -108
@@ -0,0 +1,581 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
5
+ import { detectCliAvailability } from "./tool-support.js";
6
+ const HOST_COMMANDS = {
7
+ claude: "claude",
8
+ codex: "codex",
9
+ cursor: "cursor",
10
+ gemini: "gemini"
11
+ };
12
+ const POLICY_PACKS = {
13
+ "solo-founder": {
14
+ name: "solo-founder",
15
+ summary: "Fast local defaults with light scope controls and proof-first review.",
16
+ defaultAllowedPaths: ["src/**", "tests/**", "docs/**"],
17
+ defaultBlockedPaths: [".env", ".env.*", ".git/**", "node_modules/**", "dist/**"],
18
+ dossierExpectations: ["objective", "files touched", "verifier result", "next action"],
19
+ requireApprovalAtOrAbove: "high"
20
+ },
21
+ "startup-team": {
22
+ name: "startup-team",
23
+ summary: "Team-safe defaults with verifier expectations and branch hygiene.",
24
+ defaultAllowedPaths: ["src/**", "tests/**", "docs/**"],
25
+ defaultBlockedPaths: [
26
+ ".env",
27
+ ".env.*",
28
+ ".git/**",
29
+ "infra/**",
30
+ "deploy/**",
31
+ "node_modules/**"
32
+ ],
33
+ dossierExpectations: [
34
+ "objective",
35
+ "scope",
36
+ "commands run",
37
+ "verifier result",
38
+ "risk flags",
39
+ "review focus"
40
+ ],
41
+ requireApprovalAtOrAbove: "medium"
42
+ },
43
+ "enterprise-strict": {
44
+ name: "enterprise-strict",
45
+ summary: "Fail-closed local policy that blocks infra, dependency, and secret-risk paths by default.",
46
+ defaultAllowedPaths: ["src/**", "tests/**"],
47
+ defaultBlockedPaths: [
48
+ ".env",
49
+ ".env.*",
50
+ ".git/**",
51
+ "infra/**",
52
+ "deploy/**",
53
+ ".github/workflows/**",
54
+ "package.json",
55
+ "pnpm-lock.yaml",
56
+ "package-lock.json",
57
+ "yarn.lock"
58
+ ],
59
+ dossierExpectations: [
60
+ "objective",
61
+ "scope contract",
62
+ "tests",
63
+ "risk score",
64
+ "review blockers",
65
+ "rollback evidence"
66
+ ],
67
+ requireApprovalAtOrAbove: "medium"
68
+ },
69
+ "oss-maintainer": {
70
+ name: "oss-maintainer",
71
+ summary: "OSS-focused policy with docs, tests, and reviewable diff discipline.",
72
+ defaultAllowedPaths: ["packages/**", "tests/**", "docs/**", "README.md", "CHANGELOG.md"],
73
+ defaultBlockedPaths: [".env", ".env.*", ".git/**", "infra/**", "deploy/**"],
74
+ dossierExpectations: [
75
+ "objective",
76
+ "diff summary",
77
+ "tests",
78
+ "risk flags",
79
+ "release notes impact"
80
+ ],
81
+ requireApprovalAtOrAbove: "medium"
82
+ },
83
+ "security-sensitive": {
84
+ name: "security-sensitive",
85
+ summary: "Local security policy with strict approvals around auth, secrets, and privileged files.",
86
+ defaultAllowedPaths: ["src/**", "tests/**"],
87
+ defaultBlockedPaths: [
88
+ ".env",
89
+ ".env.*",
90
+ ".git/**",
91
+ "infra/**",
92
+ "deploy/**",
93
+ ".github/workflows/**",
94
+ "package.json",
95
+ "pnpm-lock.yaml",
96
+ "package-lock.json",
97
+ "yarn.lock",
98
+ "auth/**",
99
+ "payments/**",
100
+ "secrets/**"
101
+ ],
102
+ dossierExpectations: [
103
+ "objective",
104
+ "scope contract",
105
+ "security review",
106
+ "tests",
107
+ "risk score",
108
+ "human approval receipt"
109
+ ],
110
+ requireApprovalAtOrAbove: "medium"
111
+ }
112
+ };
113
+ export function inspectRepoSignals(workingDirectory) {
114
+ const packageScripts = readPackageScripts(workingDirectory);
115
+ const packageManager = detectPackageManager(workingDirectory);
116
+ const frameworks = detectFrameworks(workingDirectory, packageScripts);
117
+ const languages = detectLanguages(workingDirectory, frameworks);
118
+ const verifiers = detectVerifierCommands(packageScripts, packageManager);
119
+ return {
120
+ workingDirectory,
121
+ packageManager,
122
+ languages,
123
+ frameworks,
124
+ verifiers,
125
+ packageScripts,
126
+ git: detectGitState(workingDirectory),
127
+ sensitivePaths: detectSensitivePaths(workingDirectory),
128
+ availableHosts: {
129
+ claude: detectCliAvailability(HOST_COMMANDS.claude),
130
+ codex: detectCliAvailability(HOST_COMMANDS.codex),
131
+ cursor: detectCliAvailability(HOST_COMMANDS.cursor),
132
+ gemini: detectCliAvailability(HOST_COMMANDS.gemini)
133
+ }
134
+ };
135
+ }
136
+ export function buildReadinessReport(signals, runStore) {
137
+ const missingSafeguards = [];
138
+ if (!signals.git.available || !signals.git.isRepo) {
139
+ missingSafeguards.push("Git repository context is unavailable.");
140
+ }
141
+ if (signals.git.isRepo && !signals.git.clean) {
142
+ missingSafeguards.push("Working tree is dirty; agent edits may overlap uncommitted work.");
143
+ }
144
+ if (signals.verifiers.defaultPlan.length === 0) {
145
+ missingSafeguards.push("No test/lint/build verifier plan was detected.");
146
+ }
147
+ if (!runStore.exists) {
148
+ missingSafeguards.push("Martin runs root does not exist yet.");
149
+ }
150
+ if (!signals.git.branch || /^(main|master)$/u.test(signals.git.branch)) {
151
+ missingSafeguards.push("Work is not isolated on a feature branch.");
152
+ }
153
+ let score = 100;
154
+ score -= missingSafeguards.length * 12;
155
+ if (signals.frameworks.length === 0) {
156
+ score -= 8;
157
+ }
158
+ if (!signals.availableHosts.claude.available && !signals.availableHosts.codex.available) {
159
+ score -= 18;
160
+ }
161
+ score = Math.max(0, Math.min(100, score));
162
+ const level = score >= 80 ? "low" : score >= 55 ? "medium" : "high";
163
+ return {
164
+ score,
165
+ level,
166
+ missingSafeguards,
167
+ repo: {
168
+ git: signals.git,
169
+ packageManager: signals.packageManager,
170
+ languages: signals.languages,
171
+ frameworks: signals.frameworks
172
+ },
173
+ safeguards: {
174
+ verifierDetected: signals.verifiers.defaultPlan.length > 0,
175
+ repoScoped: signals.git.isRepo,
176
+ branchSafe: Boolean(signals.git.branch && !/^(main|master)$/u.test(signals.git.branch)),
177
+ runStoreHealthy: runStore.exists
178
+ },
179
+ availableHosts: signals.availableHosts
180
+ };
181
+ }
182
+ export function buildPolicyPackDefinition(policyPack, signals) {
183
+ const name = policyPack ?? inferPolicyPack(signals);
184
+ const base = POLICY_PACKS[name];
185
+ return {
186
+ ...base,
187
+ defaultVerifiers: signals.verifiers.defaultPlan.length > 0
188
+ ? signals.verifiers.defaultPlan
189
+ : fallbackVerifierPlan(signals.packageManager)
190
+ };
191
+ }
192
+ export function buildPlanProposal(workingDirectory, overrides) {
193
+ const signals = inspectRepoSignals(workingDirectory);
194
+ const policy = buildPolicyPackDefinition(overrides.policyPack, signals);
195
+ const scope = inferScopeFromObjective(overrides.objective, policy, overrides);
196
+ const estimatedBudget = buildBudget(overrides, signals);
197
+ const risk = assessRunRisk({
198
+ objective: overrides.objective,
199
+ context: overrides.context,
200
+ allowedPaths: scope.allowedPaths,
201
+ blockedPaths: scope.blockedPaths,
202
+ verifiers: selectVerifiers(policy, overrides.verificationPlan),
203
+ signals
204
+ });
205
+ const approvalRecommendation = risk.level === "high"
206
+ ? "required"
207
+ : risk.level === "medium"
208
+ ? "recommended"
209
+ : "not_required";
210
+ return {
211
+ objective: overrides.objective,
212
+ implementationSummary: buildImplementationSummary(overrides.objective, scope.allowedPaths),
213
+ proposedFileScope: scope,
214
+ proposedVerifiers: selectVerifiers(policy, overrides.verificationPlan),
215
+ estimatedBudget,
216
+ risk,
217
+ approvalRecommendation,
218
+ policyPack: policy,
219
+ nextSteps: [
220
+ "Review the proposed scope and risk reasons.",
221
+ "Run martin_preflight with the same objective, policy pack, and verifier plan.",
222
+ "Execute martin_run only after the run contract looks safe and reviewable."
223
+ ]
224
+ };
225
+ }
226
+ export function buildRunContract(workingDirectory, overrides) {
227
+ const plan = buildPlanProposal(workingDirectory, overrides);
228
+ return {
229
+ objective: overrides.objective,
230
+ ...(overrides.context ? { context: overrides.context } : {}),
231
+ allowedPaths: plan.proposedFileScope.allowedPaths,
232
+ blockedPaths: plan.proposedFileScope.blockedPaths,
233
+ budget: plan.estimatedBudget,
234
+ verifiers: plan.proposedVerifiers,
235
+ risk: plan.risk,
236
+ policyPack: plan.policyPack.name,
237
+ requiresApproval: plan.approvalRecommendation === "required" ||
238
+ shouldRequireApproval(plan.policyPack.requireApprovalAtOrAbove, plan.risk.level)
239
+ };
240
+ }
241
+ export function assessRunRisk(input) {
242
+ const reasons = [];
243
+ let score = 12;
244
+ const text = `${input.objective} ${input.context ?? ""}`.toLowerCase();
245
+ if (input.signals.git.isRepo && !input.signals.git.clean) {
246
+ score += 18;
247
+ reasons.push("Repository has uncommitted changes.");
248
+ }
249
+ if (input.verifiers.length === 0) {
250
+ score += 22;
251
+ reasons.push("No verifier commands are configured for this run.");
252
+ }
253
+ if (input.allowedPaths.length === 0) {
254
+ score += 15;
255
+ reasons.push("No edit allowlist was provided.");
256
+ }
257
+ if (/(auth|login|permission|secret|token|payment|billing|deploy|infra|migration)/u.test(text)) {
258
+ score += 26;
259
+ reasons.push("Objective touches sensitive auth, secret, payment, or deployment concerns.");
260
+ }
261
+ if (/(dependency|package\.json|lockfile|upgrade|install)/u.test(text)) {
262
+ score += 14;
263
+ reasons.push("Objective may require dependency or lockfile changes.");
264
+ }
265
+ if (input.blockedPaths.some((candidate) => /package\.json|lock|workflow|infra/u.test(candidate))) {
266
+ score += 6;
267
+ reasons.push("Policy pack blocks high-risk repo surfaces by default.");
268
+ }
269
+ if (input.signals.sensitivePaths.length > 0 && input.allowedPaths.some((candidate) => candidate === "**" || candidate === "*")) {
270
+ score += 10;
271
+ reasons.push("Wide edit scope overlaps with sensitive repo areas.");
272
+ }
273
+ score = Math.max(0, Math.min(100, score));
274
+ const level = score >= 70 ? "high" : score >= 40 ? "medium" : "low";
275
+ return {
276
+ score,
277
+ level,
278
+ reasons,
279
+ recommendedAction: level === "high"
280
+ ? "require_human_approval"
281
+ : level === "medium"
282
+ ? "review"
283
+ : "proceed"
284
+ };
285
+ }
286
+ export function buildRepoRiskMap(signals) {
287
+ return {
288
+ workingDirectory: signals.workingDirectory,
289
+ packageManager: signals.packageManager,
290
+ frameworks: signals.frameworks,
291
+ sensitivePaths: signals.sensitivePaths,
292
+ recommendedPolicyPack: inferPolicyPack(signals)
293
+ };
294
+ }
295
+ function detectPackageManager(workingDirectory) {
296
+ if (existsSync(path.join(workingDirectory, "pnpm-lock.yaml"))) {
297
+ return "pnpm";
298
+ }
299
+ if (existsSync(path.join(workingDirectory, "package-lock.json"))) {
300
+ return "npm";
301
+ }
302
+ if (existsSync(path.join(workingDirectory, "yarn.lock"))) {
303
+ return "yarn";
304
+ }
305
+ if (existsSync(path.join(workingDirectory, "bun.lockb")) || existsSync(path.join(workingDirectory, "bun.lock"))) {
306
+ return "bun";
307
+ }
308
+ return "unknown";
309
+ }
310
+ function readPackageScripts(workingDirectory) {
311
+ const packageJsonPath = path.join(workingDirectory, "package.json");
312
+ if (!existsSync(packageJsonPath)) {
313
+ return {};
314
+ }
315
+ try {
316
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
317
+ return parsed.scripts ?? {};
318
+ }
319
+ catch {
320
+ return {};
321
+ }
322
+ }
323
+ function detectFrameworks(workingDirectory, scripts) {
324
+ const packageJsonPath = path.join(workingDirectory, "package.json");
325
+ const frameworks = new Set();
326
+ if (existsSync(path.join(workingDirectory, "next.config.js")) || existsSync(path.join(workingDirectory, "next.config.mjs"))) {
327
+ frameworks.add("Next.js");
328
+ }
329
+ if (existsSync(path.join(workingDirectory, "vite.config.ts")) || existsSync(path.join(workingDirectory, "vite.config.js"))) {
330
+ frameworks.add("Vite");
331
+ }
332
+ if (existsSync(packageJsonPath)) {
333
+ try {
334
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
335
+ const deps = {
336
+ ...(parsed.dependencies ?? {}),
337
+ ...(parsed.devDependencies ?? {})
338
+ };
339
+ if ("next" in deps) {
340
+ frameworks.add("Next.js");
341
+ }
342
+ if ("react" in deps) {
343
+ frameworks.add("React");
344
+ }
345
+ if ("vitest" in deps) {
346
+ frameworks.add("Vitest");
347
+ }
348
+ if ("jest" in deps) {
349
+ frameworks.add("Jest");
350
+ }
351
+ if ("typescript" in deps || existsSync(path.join(workingDirectory, "tsconfig.json"))) {
352
+ frameworks.add("TypeScript");
353
+ }
354
+ }
355
+ catch {
356
+ // ignore malformed package metadata here; doctor surfaces the gap elsewhere
357
+ }
358
+ }
359
+ if (Object.keys(scripts).some((key) => key.startsWith("test"))) {
360
+ frameworks.add("Scripted verification");
361
+ }
362
+ return [...frameworks];
363
+ }
364
+ function detectLanguages(workingDirectory, frameworks) {
365
+ const languages = new Set();
366
+ if (existsSync(path.join(workingDirectory, "tsconfig.json")) || frameworks.includes("TypeScript")) {
367
+ languages.add("TypeScript");
368
+ }
369
+ if (existsSync(path.join(workingDirectory, "package.json"))) {
370
+ languages.add("JavaScript");
371
+ }
372
+ if (existsSync(path.join(workingDirectory, "pyproject.toml")) || existsSync(path.join(workingDirectory, "requirements.txt"))) {
373
+ languages.add("Python");
374
+ }
375
+ if (existsSync(path.join(workingDirectory, "go.mod"))) {
376
+ languages.add("Go");
377
+ }
378
+ if (existsSync(path.join(workingDirectory, "Cargo.toml"))) {
379
+ languages.add("Rust");
380
+ }
381
+ return [...languages];
382
+ }
383
+ function detectVerifierCommands(scripts, packageManager) {
384
+ const prefix = packageManager === "pnpm"
385
+ ? "pnpm"
386
+ : packageManager === "yarn"
387
+ ? "yarn"
388
+ : packageManager === "bun"
389
+ ? "bun run"
390
+ : "npm run";
391
+ const test = [];
392
+ const lint = [];
393
+ const build = [];
394
+ for (const key of Object.keys(scripts)) {
395
+ if (/^test($|:|-)/u.test(key)) {
396
+ test.push(commandForScript(prefix, key));
397
+ }
398
+ if (/^lint($|:|-)/u.test(key)) {
399
+ lint.push(commandForScript(prefix, key));
400
+ }
401
+ if (/^build($|:|-)/u.test(key)) {
402
+ build.push(commandForScript(prefix, key));
403
+ }
404
+ }
405
+ const defaultPlan = [...test.slice(0, 1), ...lint.slice(0, 1), ...build.slice(0, 1)];
406
+ return { test, lint, build, defaultPlan };
407
+ }
408
+ function detectGitState(workingDirectory) {
409
+ const availability = spawnSync("git", ["--version"], {
410
+ cwd: workingDirectory,
411
+ encoding: "utf8",
412
+ stdio: ["ignore", "pipe", "pipe"]
413
+ });
414
+ if (availability.status !== 0) {
415
+ return {
416
+ available: false,
417
+ isRepo: false,
418
+ clean: false
419
+ };
420
+ }
421
+ const isRepo = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
422
+ cwd: workingDirectory,
423
+ encoding: "utf8",
424
+ stdio: ["ignore", "pipe", "pipe"]
425
+ });
426
+ if (isRepo.status !== 0 || !/true/u.test(isRepo.stdout ?? "")) {
427
+ return {
428
+ available: true,
429
+ isRepo: false,
430
+ clean: false
431
+ };
432
+ }
433
+ const branch = spawnSync("git", ["branch", "--show-current"], {
434
+ cwd: workingDirectory,
435
+ encoding: "utf8",
436
+ stdio: ["ignore", "pipe", "pipe"]
437
+ }).stdout.trim();
438
+ const status = spawnSync("git", ["status", "--porcelain", "--branch"], {
439
+ cwd: workingDirectory,
440
+ encoding: "utf8",
441
+ stdio: ["ignore", "pipe", "pipe"]
442
+ });
443
+ const statusLines = (status.stdout ?? "")
444
+ .split(/\r?\n/u)
445
+ .map((line) => line.trim())
446
+ .filter(Boolean);
447
+ const dirty = statusLines.some((line) => !line.startsWith("##"));
448
+ const header = statusLines.find((line) => line.startsWith("##"));
449
+ const upstream = header?.match(/\.\.\.([^\s[]+)/u)?.[1];
450
+ const ahead = parseCount(header, /ahead (\d+)/u);
451
+ const behind = parseCount(header, /behind (\d+)/u);
452
+ return {
453
+ available: true,
454
+ isRepo: true,
455
+ clean: !dirty,
456
+ ...(branch ? { branch } : {}),
457
+ ...(upstream ? { upstream } : {}),
458
+ ...(ahead !== undefined ? { ahead } : {}),
459
+ ...(behind !== undefined ? { behind } : {})
460
+ };
461
+ }
462
+ function detectSensitivePaths(workingDirectory) {
463
+ const candidates = [
464
+ ".env",
465
+ ".env.local",
466
+ "infra",
467
+ "deploy",
468
+ ".github/workflows",
469
+ "package.json",
470
+ "pnpm-lock.yaml",
471
+ "package-lock.json",
472
+ "yarn.lock",
473
+ "Dockerfile",
474
+ "docker-compose.yml",
475
+ "auth",
476
+ "payments"
477
+ ];
478
+ return candidates.filter((candidate) => existsSync(path.join(workingDirectory, candidate)));
479
+ }
480
+ function inferPolicyPack(signals) {
481
+ const sensitive = signals.sensitivePaths.join(" ").toLowerCase();
482
+ if (/(auth|payment|workflow|deploy|infra|docker)/u.test(sensitive)) {
483
+ return "security-sensitive";
484
+ }
485
+ if (/README|CHANGELOG/u.test(Object.keys(signals.packageScripts).join(" "))) {
486
+ return "oss-maintainer";
487
+ }
488
+ return signals.git.isRepo && signals.git.clean ? "startup-team" : "solo-founder";
489
+ }
490
+ function inferScopeFromObjective(objective, policy, overrides) {
491
+ if ((overrides.allowedPaths?.length ?? 0) > 0 || (overrides.deniedPaths?.length ?? 0) > 0) {
492
+ return {
493
+ allowedPaths: overrides.allowedPaths ?? [],
494
+ blockedPaths: overrides.deniedPaths ?? []
495
+ };
496
+ }
497
+ const normalized = objective.toLowerCase();
498
+ if (/(readme|docs|changelog|release notes)/u.test(normalized)) {
499
+ return {
500
+ allowedPaths: ["docs/**", "README.md", "CHANGELOG.md"],
501
+ blockedPaths: [...policy.defaultBlockedPaths, "src/**"]
502
+ };
503
+ }
504
+ if (/(test|verifier|spec|coverage)/u.test(normalized)) {
505
+ return {
506
+ allowedPaths: ["src/**", "tests/**"],
507
+ blockedPaths: policy.defaultBlockedPaths
508
+ };
509
+ }
510
+ if (/(mcp|cli|package)/u.test(normalized)) {
511
+ return {
512
+ allowedPaths: ["packages/**", "tests/**", "docs/**"],
513
+ blockedPaths: policy.defaultBlockedPaths
514
+ };
515
+ }
516
+ return {
517
+ allowedPaths: [...policy.defaultAllowedPaths],
518
+ blockedPaths: [...policy.defaultBlockedPaths]
519
+ };
520
+ }
521
+ function buildBudget(overrides, signals) {
522
+ const defaultCommands = signals.verifiers.defaultPlan.length > 0 ? 12 : 8;
523
+ return {
524
+ maxUsd: overrides.maxUsd ?? DEFAULT_BUDGET.maxUsd,
525
+ softLimitUsd: Math.min(overrides.maxUsd ?? DEFAULT_BUDGET.maxUsd, DEFAULT_BUDGET.softLimitUsd),
526
+ maxIterations: overrides.maxIterations ?? DEFAULT_BUDGET.maxIterations,
527
+ maxTokens: overrides.maxTokens ?? DEFAULT_BUDGET.maxTokens,
528
+ maxMinutes: overrides.maxMinutes ?? 20,
529
+ maxFilesChanged: overrides.maxFilesChanged ?? 8,
530
+ maxCommands: overrides.maxCommands ?? defaultCommands
531
+ };
532
+ }
533
+ function selectVerifiers(policy, explicitVerificationPlan) {
534
+ return explicitVerificationPlan && explicitVerificationPlan.length > 0
535
+ ? explicitVerificationPlan
536
+ : policy.defaultVerifiers;
537
+ }
538
+ function buildImplementationSummary(objective, allowedPaths) {
539
+ const scopeHint = allowedPaths.length > 0
540
+ ? `Limit work to ${allowedPaths.slice(0, 3).join(", ")}${allowedPaths.length > 3 ? " and adjacent tests/docs" : ""}.`
541
+ : "Establish a narrower file scope before execution.";
542
+ return `${objective.trim()} ${scopeHint}`.trim();
543
+ }
544
+ function commandForScript(prefix, name) {
545
+ if (prefix === "pnpm") {
546
+ return name === "test" ? "pnpm test" : `pnpm ${name}`;
547
+ }
548
+ if (prefix === "yarn") {
549
+ return name === "test" ? "yarn test" : `yarn ${name}`;
550
+ }
551
+ if (prefix === "bun run") {
552
+ return `bun run ${name}`;
553
+ }
554
+ return `npm run ${name}`;
555
+ }
556
+ function fallbackVerifierPlan(packageManager) {
557
+ switch (packageManager) {
558
+ case "pnpm":
559
+ return ["pnpm test", "pnpm lint", "pnpm build"];
560
+ case "yarn":
561
+ return ["yarn test", "yarn lint", "yarn build"];
562
+ case "bun":
563
+ return ["bun run test", "bun run lint", "bun run build"];
564
+ case "npm":
565
+ return ["npm test", "npm run lint", "npm run build"];
566
+ default:
567
+ return [];
568
+ }
569
+ }
570
+ function shouldRequireApproval(threshold, level) {
571
+ const ordering = ["low", "medium", "high"];
572
+ return ordering.indexOf(level) >= ordering.indexOf(threshold);
573
+ }
574
+ function parseCount(value, pattern) {
575
+ const match = value?.match(pattern)?.[1];
576
+ if (!match) {
577
+ return undefined;
578
+ }
579
+ const parsed = Number.parseInt(match, 10);
580
+ return Number.isFinite(parsed) ? parsed : undefined;
581
+ }
@@ -583,4 +583,3 @@ async function checkNoDiff(repoRoot) {
583
583
  const result = await runSubprocess("git", ["diff", "--name-only", "HEAD"], { cwd: repoRoot, timeoutMs: 5000 });
584
584
  return result.exitCode === 0 && result.stdout.trim().length === 0;
585
585
  }
586
- //# sourceMappingURL=claude-cli.js.map
@@ -26,4 +26,9 @@ export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: n
26
26
  changedFiles?: string[];
27
27
  diffStats?: ReturnType<typeof diffStatsFromNumstat>;
28
28
  }>;
29
+ export interface SpawnPlan {
30
+ command: string;
31
+ args: string[];
32
+ }
33
+ export declare function createSpawnPlan(command: string, args: string[], cwd: string, preserveRawForInjectedSpawn: boolean): SpawnPlan;
29
34
  export declare function splitCommand(command: string): string[];
@@ -140,22 +140,30 @@ export async function readGitExecutionArtifacts(repoRoot, timeoutMs, spawnImpl)
140
140
  ...(diffStats ? { diffStats } : {})
141
141
  };
142
142
  }
143
- function createSpawnPlan(command, args, cwd, preserveRawForInjectedSpawn) {
144
- if (preserveRawForInjectedSpawn || process.platform !== "win32" || isAbsolute(command)) {
143
+ export function createSpawnPlan(command, args, cwd, preserveRawForInjectedSpawn) {
144
+ if (preserveRawForInjectedSpawn || process.platform !== "win32") {
145
145
  return { command, args };
146
146
  }
147
- const resolved = resolveWindowsCommand(command, cwd);
148
- if (!resolved) {
149
- return { command, args };
147
+ // Try to resolve the command to an absolute path using the Windows PATH.
148
+ const resolvedOrUndefined = isAbsolute(command) ? command : resolveWindowsCommand(command, cwd);
149
+ // If resolution failed (command not found in PATH), fall back to cmd.exe shell execution so
150
+ // Windows can resolve the command itself — this covers cases like `pnpm` where the npm global
151
+ // bin directory is present in the shell PATH but not yet visible to this Node.js process.
152
+ if (resolvedOrUndefined === undefined) {
153
+ const cmdStr = [quoteWindowsCmdArg(command), ...args.map(quoteWindowsCmdArg)].join(" ");
154
+ return {
155
+ command: process.env.ComSpec || "cmd.exe",
156
+ args: ["/d", "/s", "/c", cmdStr]
157
+ };
150
158
  }
151
- const extension = extname(resolved).toLowerCase();
159
+ const extension = extname(resolvedOrUndefined).toLowerCase();
152
160
  if (extension === ".cmd" || extension === ".bat") {
153
161
  return {
154
162
  command: process.env.ComSpec || "cmd.exe",
155
- args: ["/d", "/s", "/c", [quoteWindowsCmdArg(resolved), ...args.map(quoteWindowsCmdArg)].join(" ")]
163
+ args: ["/d", "/s", "/c", [quoteWindowsCmdArg(resolvedOrUndefined), ...args.map(quoteWindowsCmdArg)].join(" ")]
156
164
  };
157
165
  }
158
- return { command: resolved, args };
166
+ return { command: resolvedOrUndefined, args };
159
167
  }
160
168
  function resolveWindowsCommand(command, cwd) {
161
169
  const hasPathSegment = command.includes("\\") || command.includes("/");
@@ -250,4 +258,3 @@ function truncate(text, maxLength) {
250
258
  }
251
259
  return `...${text.slice(-(maxLength - 3))}`;
252
260
  }
253
- //# sourceMappingURL=cli-bridge.js.map
@@ -38,4 +38,3 @@ export function createDirectProviderAdapter(options) {
38
38
  }
39
39
  };
40
40
  }
41
- //# sourceMappingURL=direct-provider.js.map
@@ -3,4 +3,5 @@ export { createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions
3
3
  export { createStubAgentCliAdapter, type StubAgentCliAdapterOptions } from "./stub-agent-cli.js";
4
4
  export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
5
5
  export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
6
- export type { SpawnLike, SubprocessResult, VerificationOutcome } from "./cli-bridge.js";
6
+ export { createOpenAiCompatibleAdapter, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
7
+ export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
@@ -3,4 +3,5 @@ export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
3
3
  export { createStubAgentCliAdapter } from "./stub-agent-cli.js";
4
4
  export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter } from "./claude-cli.js";
5
5
  export { createVerifierOnlyAdapter } from "./verifier-only.js";
6
- //# sourceMappingURL=index.js.map
6
+ export { createOpenAiCompatibleAdapter } from "./openai-compatible.js";
7
+ export { createSpawnPlan } from "./cli-bridge.js";