@martinloop/mcp 0.5.6 → 0.5.8

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 (40) hide show
  1. package/README.md +137 -175
  2. package/dist/arcade/resource.js +20 -20
  3. package/dist/package-version.d.ts +1 -1
  4. package/dist/package-version.js +1 -1
  5. package/dist/resources.js +148 -160
  6. package/dist/server.js +192 -43
  7. package/dist/tools/execution-policy.js +1 -1
  8. package/dist/tools/get-status.d.ts +2 -4
  9. package/dist/tools/get-status.js +6 -3
  10. package/dist/tools/preflight.d.ts +1 -1
  11. package/dist/tools/run-loop.d.ts +1 -1
  12. package/dist/tools/run-loop.js +3 -1
  13. package/dist/tools/run-store.js +1 -1
  14. package/dist/tools/tool-support.d.ts +3 -2
  15. package/dist/tools/tool-support.js +9 -2
  16. package/dist/tools/workflow-governance.d.ts +1 -1
  17. package/dist/tools/workflow-governance.js +1 -1
  18. package/dist/vendor/adapters/openai-compatible.js +6 -6
  19. package/dist/vendor/adapters/verifier-only.js +1 -1
  20. package/dist/vendor/contracts/execution-policy.js +1 -1
  21. package/dist/vendor/contracts/exits.d.ts +1 -1
  22. package/dist/vendor/contracts/governance.d.ts +1 -1
  23. package/dist/vendor/contracts/index.d.ts +1 -1
  24. package/dist/vendor/contracts/index.js +1 -2
  25. package/dist/vendor/contracts/operator.d.ts +1 -1
  26. package/dist/vendor/contracts/operator.js +1 -2
  27. package/dist/vendor/core/compiler.d.ts +2 -2
  28. package/dist/vendor/core/compiler.js +3 -1
  29. package/dist/vendor/core/exits.js +9 -4
  30. package/dist/vendor/core/grounding.d.ts +1 -0
  31. package/dist/vendor/core/grounding.js +28 -1
  32. package/dist/vendor/core/index.d.ts +2 -2
  33. package/dist/vendor/core/index.js +29 -12
  34. package/dist/vendor/core/policy-compiler.js +17 -1
  35. package/dist/vendor/core/policy.d.ts +1 -1
  36. package/dist/vendor/core/policy.js +7 -5
  37. package/dist/vendor/presentation/motion.js +0 -4
  38. package/dist/workflow-state.js +1 -1
  39. package/package.json +95 -81
  40. package/server.json +21 -21
package/dist/server.js CHANGED
@@ -95,8 +95,7 @@ const loopPreviewSchema = {
95
95
  "pressure",
96
96
  "shouldStop",
97
97
  "remainingBudgetUsd",
98
- "remainingIterations",
99
- "remainingTokens"
98
+ "remainingIterations"
100
99
  ]
101
100
  };
102
101
  const budgetSchema = {
@@ -108,7 +107,7 @@ const budgetSchema = {
108
107
  maxIterations: { type: "integer" },
109
108
  maxTokens: { type: "integer" }
110
109
  },
111
- required: ["maxUsd", "softLimitUsd", "maxIterations", "maxTokens"]
110
+ required: ["maxUsd", "softLimitUsd", "maxIterations"]
112
111
  };
113
112
  const costSchema = {
114
113
  type: "object",
@@ -293,7 +292,6 @@ const runOutputSchema = {
293
292
  "shouldStop",
294
293
  "remainingBudgetUsd",
295
294
  "remainingIterations",
296
- "remainingTokens",
297
295
  "engine",
298
296
  "workingDirectory",
299
297
  "budget",
@@ -382,7 +380,6 @@ const statusOutputSchema = {
382
380
  "shouldStop",
383
381
  "remainingBudgetUsd",
384
382
  "remainingIterations",
385
- "remainingTokens",
386
383
  "budget",
387
384
  "inspection"
388
385
  ]
@@ -704,23 +701,142 @@ const planOutputSchema = {
704
701
  };
705
702
  const logsOutputSchema = {
706
703
  type: "object",
707
- additionalProperties: true
704
+ additionalProperties: false,
705
+ properties: {
706
+ source: { type: "string", description: "Resolved path to the loop-record source file." },
707
+ sourceKind: {
708
+ type: "string",
709
+ enum: ["file", "loop_id", "latest", "runs_root"],
710
+ description: "How the run was identified: by file path, loop ID, latest flag, or runs directory."
711
+ },
712
+ loopId: { type: "string", description: "Unique MartinLoop run identifier." },
713
+ logCount: { type: "integer", description: "Number of log entries returned after applying the limit." },
714
+ live: {
715
+ type: "object",
716
+ additionalProperties: false,
717
+ properties: {
718
+ lifecycleState: { type: "string", description: "Current run lifecycle state (e.g. running, completed, cancelled)." },
719
+ pauseState: {
720
+ type: "string",
721
+ enum: ["active", "paused", "cancellation_requested"],
722
+ description: "Whether the run is active, paused, or has a pending cancellation."
723
+ },
724
+ approvalState: {
725
+ type: "string",
726
+ enum: ["not_required", "resume_requested"],
727
+ description: "Whether the run is waiting at a human-approval checkpoint."
728
+ }
729
+ },
730
+ required: ["lifecycleState", "pauseState", "approvalState"]
731
+ },
732
+ entries: {
733
+ type: "array",
734
+ description: "Log entries sorted by timestamp descending, capped at limit.",
735
+ items: {
736
+ type: "object",
737
+ additionalProperties: false,
738
+ properties: {
739
+ timestamp: { type: "string", description: "ISO 8601 timestamp of the event, if present." },
740
+ source: {
741
+ type: "string",
742
+ enum: ["event", "ledger", "control"],
743
+ description: "Origin stream: loop event bus, governance ledger, or operator control receipts."
744
+ },
745
+ kind: { type: "string", description: "Event type or control action name." },
746
+ payload: { type: "object", additionalProperties: true, description: "Event-specific payload data." }
747
+ },
748
+ required: ["source", "kind", "payload"]
749
+ }
750
+ }
751
+ },
752
+ required: ["source", "sourceKind", "loopId", "logCount", "live", "entries"]
708
753
  };
709
754
  const controlOutputSchema = {
710
755
  type: "object",
711
- additionalProperties: true
756
+ additionalProperties: false,
757
+ properties: {
758
+ loopId: { type: "string", description: "MartinLoop run identifier the control was applied to." },
759
+ action: {
760
+ type: "string",
761
+ enum: ["pause", "cancel", "continue"],
762
+ description: "The control action that was recorded."
763
+ },
764
+ controlId: { type: "string", description: "Unique receipt ID for this control action." },
765
+ requestedAt: { type: "string", description: "ISO 8601 timestamp when the control was recorded." },
766
+ requestedBy: { type: "string", description: "Identity that requested the control, if provided." },
767
+ reason: { type: "string", description: "Human-readable reason for the control, if provided." }
768
+ },
769
+ required: ["loopId", "action", "controlId", "requestedAt"]
712
770
  };
713
771
  const evalOutputSchema = {
714
772
  type: "object",
715
- additionalProperties: true
773
+ additionalProperties: false,
774
+ properties: {
775
+ source: { type: "string", description: "Resolved path to the loop-record source file." },
776
+ sourceKind: {
777
+ type: "string",
778
+ enum: ["file", "loop_id", "latest", "runs_root"],
779
+ description: "How the run was identified."
780
+ },
781
+ loopId: { type: "string", description: "Unique MartinLoop run identifier." },
782
+ score: { type: "number", description: "Numeric evaluation score from 0–100." },
783
+ grade: {
784
+ type: "string",
785
+ enum: ["mergeable", "mergeable_with_review", "needs_review", "blocked", "insufficient_evidence"],
786
+ description: "Overall merge readiness grade derived from the six check dimensions."
787
+ },
788
+ checks: {
789
+ type: "object",
790
+ additionalProperties: false,
791
+ properties: {
792
+ taskCompletion: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether the run reached a completed status." },
793
+ verifier: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether automated verifiers passed." },
794
+ diffDiscipline: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether file-scope discipline was maintained." },
795
+ regressionRisk: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether regression risk signals were detected." },
796
+ securityRisk: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether security risk signals were detected." },
797
+ reviewability: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether the PR body and dossier are reviewer-ready." }
798
+ },
799
+ required: ["taskCompletion", "verifier", "diffDiscipline", "regressionRisk", "securityRisk", "reviewability"]
800
+ },
801
+ warnings: { type: "array", items: { type: "string" }, description: "Non-blocking advisory warnings from the evaluation." },
802
+ summary: { type: "string", description: "One-paragraph plain-English evaluation summary." }
803
+ },
804
+ required: ["source", "sourceKind", "loopId", "score", "grade", "checks", "warnings", "summary"]
716
805
  };
717
806
  const prSummaryOutputSchema = {
718
807
  type: "object",
719
- additionalProperties: true
808
+ additionalProperties: false,
809
+ properties: {
810
+ loopId: { type: "string", description: "MartinLoop run identifier used to generate the summary." },
811
+ title: { type: "string", description: "Suggested GitHub pull-request title." },
812
+ body: { type: "string", description: "GitHub-flavoured Markdown PR body containing the run dossier." },
813
+ grade: {
814
+ type: "string",
815
+ enum: ["mergeable", "mergeable_with_review", "needs_review", "blocked", "insufficient_evidence"],
816
+ description: "Verification grade assigned to the run."
817
+ },
818
+ score: { type: "number", description: "Numeric verification score from 0–100." }
819
+ },
820
+ required: ["loopId", "title", "body", "grade", "score"]
720
821
  };
721
822
  const prReviewOutputSchema = {
722
823
  type: "object",
723
- additionalProperties: true
824
+ additionalProperties: false,
825
+ properties: {
826
+ loopId: { type: "string", description: "MartinLoop run identifier the review was performed against." },
827
+ verdict: {
828
+ type: "string",
829
+ enum: ["approve_with_review", "needs_changes", "blocked"],
830
+ description: "Merge verdict: approve_with_review means safe to merge with human review; needs_changes requires fixes; blocked means do not merge."
831
+ },
832
+ findings: {
833
+ type: "array",
834
+ items: { type: "string" },
835
+ description: "Specific findings that informed the verdict."
836
+ },
837
+ summary: { type: "string", description: "Plain-English review summary." }
838
+ },
839
+ required: ["loopId", "verdict", "findings", "summary"]
724
840
  };
725
841
  export function createMartinMcpServer(serverInfo) {
726
842
  const server = new Server({
@@ -1034,20 +1150,26 @@ export function createMartinMcpServer(serverInfo) {
1034
1150
  },
1035
1151
  {
1036
1152
  name: "martin_logs",
1037
- description: "Read recent Martin loop events, ledger entries, and operator control receipts for live observability.",
1153
+ description: "Read recent MartinLoop events, ledger entries, and operator control receipts for a single run. " +
1154
+ "Use to observe live or completed run activity, diagnose stuck or failed runs, or audit operator actions. " +
1155
+ "Do not use to check run completion status — use martin_get_status instead. " +
1156
+ "Do not use to retrieve verification evidence — use martin_get_verification_results instead. " +
1157
+ "This tool only reads persisted run files and does not execute commands, modify state, or contact GitHub.",
1038
1158
  annotations: {
1039
1159
  readOnlyHint: true,
1040
- idempotentHint: true
1160
+ destructiveHint: false,
1161
+ idempotentHint: true,
1162
+ openWorldHint: false
1041
1163
  },
1042
1164
  inputSchema: {
1043
1165
  type: "object",
1044
1166
  additionalProperties: false,
1045
1167
  properties: {
1046
- file: { type: "string" },
1047
- loopId: { type: "string" },
1048
- runsDir: { type: "string" },
1049
- latest: { const: true },
1050
- limit: { type: "integer", minimum: 1 }
1168
+ file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
1169
+ loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
1170
+ runsDir: { type: "string", description: "Override the default run-store root directory. Optional; defaults to the MartinLoop runs directory." },
1171
+ latest: { const: true, description: "When true, loads the most recently updated run in the run store. Mutually exclusive with file and loopId." },
1172
+ limit: { type: "integer", minimum: 1, description: "Maximum number of log entries to return, sorted by timestamp descending. Defaults to 20." }
1051
1173
  },
1052
1174
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1053
1175
  },
@@ -1243,10 +1365,16 @@ export function createMartinMcpServer(serverInfo) {
1243
1365
  },
1244
1366
  {
1245
1367
  name: "martin_get_verification_results",
1246
- description: "Load verification evidence for a Martin run from stored loop events and ledger entries.",
1368
+ description: "Load structured verification evidence for a MartinLoop run — verifier commands, pass/fail outcomes, and contradiction signals — from stored loop events and ledger entries. " +
1369
+ "Use after a run completes to confirm whether automated verifiers passed before merging or promoting the result. " +
1370
+ "Do not use to get a merge-readiness grade — use martin_eval for a scored grade with six check dimensions. " +
1371
+ "Do not use for live run status — use martin_get_status instead. " +
1372
+ "This tool only reads persisted run files and does not execute commands, modify state, or contact GitHub.",
1247
1373
  annotations: {
1248
1374
  readOnlyHint: true,
1249
- idempotentHint: true
1375
+ destructiveHint: false,
1376
+ idempotentHint: true,
1377
+ openWorldHint: false
1250
1378
  },
1251
1379
  inputSchema: {
1252
1380
  type: "object",
@@ -1254,10 +1382,10 @@ export function createMartinMcpServer(serverInfo) {
1254
1382
  properties: {
1255
1383
  file: {
1256
1384
  type: "string",
1257
- description: "Path to a canonical loop-record.json file or run directory."
1385
+ description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId."
1258
1386
  },
1259
- loopId: { type: "string", description: "Loop ID under the run store." },
1260
- runsDir: { type: "string", description: "Optional runs-root override." }
1387
+ loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file." },
1388
+ runsDir: { type: "string", description: "Override the default run-store root directory. Optional." }
1261
1389
  },
1262
1390
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
1263
1391
  },
@@ -1316,19 +1444,26 @@ export function createMartinMcpServer(serverInfo) {
1316
1444
  },
1317
1445
  {
1318
1446
  name: "martin_eval",
1319
- description: "Grade a Martin run for task completion, verifier health, diff discipline, risk, and reviewability.",
1447
+ description: "Grade a MartinLoop run across six dimensions — task completion, verifier health, diff discipline, regression risk, security risk, and reviewability — and return a scored merge-readiness verdict. " +
1448
+ "Use after a governed run completes to decide whether the result is safe to merge or promote. " +
1449
+ "Use before martin_pr_summary or martin_create_pr to confirm the run is merge-ready. " +
1450
+ "Do not use to retrieve raw verification command output — use martin_get_verification_results for that. " +
1451
+ "Do not use to review an existing PR body — use martin_review_pr instead. " +
1452
+ "This tool reads saved run evidence and inspects local git signals; it does not modify state or contact GitHub.",
1320
1453
  annotations: {
1321
1454
  readOnlyHint: true,
1322
- idempotentHint: true
1455
+ destructiveHint: false,
1456
+ idempotentHint: true,
1457
+ openWorldHint: false
1323
1458
  },
1324
1459
  inputSchema: {
1325
1460
  type: "object",
1326
1461
  additionalProperties: false,
1327
1462
  properties: {
1328
- file: { type: "string" },
1329
- loopId: { type: "string" },
1330
- runsDir: { type: "string" },
1331
- latest: { const: true }
1463
+ file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
1464
+ loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
1465
+ runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
1466
+ latest: { const: true, description: "When true, evaluates the most recently updated run in the run store. Mutually exclusive with file and loopId." }
1332
1467
  },
1333
1468
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1334
1469
  },
@@ -1336,20 +1471,27 @@ export function createMartinMcpServer(serverInfo) {
1336
1471
  },
1337
1472
  {
1338
1473
  name: "martin_pr_summary",
1339
- description: "Generate a PR title and body with a MartinLoop dossier block for a completed run.",
1474
+ description: "Generate a GitHub-ready pull-request title and Markdown body from a completed MartinLoop run dossier, including its verification grade and score. " +
1475
+ "Use after a governed run completes when you need PR copy without creating the PR. " +
1476
+ "Use martin_create_pr instead to actually open the PR on GitHub. " +
1477
+ "Use martin_review_pr to evaluate an existing PR body against run evidence. " +
1478
+ "Use martin_eval first if you need a merge-readiness grade before generating the PR body. " +
1479
+ "This tool only reads saved run evidence and does not modify the repository or contact GitHub.",
1340
1480
  annotations: {
1341
1481
  readOnlyHint: true,
1342
- idempotentHint: true
1482
+ destructiveHint: false,
1483
+ idempotentHint: true,
1484
+ openWorldHint: false
1343
1485
  },
1344
1486
  inputSchema: {
1345
1487
  type: "object",
1346
1488
  additionalProperties: false,
1347
1489
  properties: {
1348
- file: { type: "string" },
1349
- loopId: { type: "string" },
1350
- runsDir: { type: "string" },
1351
- latest: { const: true },
1352
- format: { type: "string", enum: ["json", "md", "github-pr"] }
1490
+ file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
1491
+ loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
1492
+ runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
1493
+ latest: { const: true, description: "When true, generates the summary for the most recently updated run. Mutually exclusive with file and loopId." },
1494
+ format: { type: "string", enum: ["json", "md", "github-pr"], description: "Dossier rendering format. Defaults to github-pr for PR body generation. Use md for plain Markdown or json for structured output." }
1353
1495
  },
1354
1496
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1355
1497
  },
@@ -1381,21 +1523,28 @@ export function createMartinMcpServer(serverInfo) {
1381
1523
  },
1382
1524
  {
1383
1525
  name: "martin_review_pr",
1384
- description: "Review a PR or PR draft against the Martin dossier and evaluation evidence.",
1526
+ description: "Review a PR body or draft against the MartinLoop run dossier and evaluation evidence, and return a verdict with specific findings. " +
1527
+ "Use when you have an existing PR body and want to check whether it accurately represents the governed run evidence. " +
1528
+ "Supply prBody to review a specific body string; omit it to evaluate the auto-generated dossier body. " +
1529
+ "Do not use to generate a PR body from scratch — use martin_pr_summary instead. " +
1530
+ "Do not use to open or create a PR — use martin_create_pr instead. " +
1531
+ "This tool only reads saved run evidence and does not modify the repository or contact GitHub.",
1385
1532
  annotations: {
1386
1533
  readOnlyHint: true,
1387
- idempotentHint: true
1534
+ destructiveHint: false,
1535
+ idempotentHint: true,
1536
+ openWorldHint: false
1388
1537
  },
1389
1538
  inputSchema: {
1390
1539
  type: "object",
1391
1540
  additionalProperties: false,
1392
1541
  properties: {
1393
- file: { type: "string" },
1394
- loopId: { type: "string" },
1395
- runsDir: { type: "string" },
1396
- latest: { const: true },
1397
- format: { type: "string", enum: ["json", "md", "github-pr"] },
1398
- prBody: { type: "string" }
1542
+ file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
1543
+ loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
1544
+ runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
1545
+ latest: { const: true, description: "When true, reviews against the most recently updated run. Mutually exclusive with file and loopId." },
1546
+ format: { type: "string", enum: ["json", "md", "github-pr"], description: "Dossier format used when generating the reference body for comparison. Defaults to github-pr." },
1547
+ prBody: { type: "string", description: "The PR body text to review. If omitted, the auto-generated dossier body is evaluated instead." }
1399
1548
  },
1400
1549
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1401
1550
  },
@@ -5,7 +5,7 @@ export function compileMcpExecutionPolicy(input) {
5
5
  maxUsd: input.maxUsd ?? DEFAULT_BUDGET.maxUsd,
6
6
  softLimitUsd: Math.min(DEFAULT_BUDGET.softLimitUsd, input.maxUsd ?? DEFAULT_BUDGET.maxUsd),
7
7
  maxIterations: input.maxIterations ?? DEFAULT_BUDGET.maxIterations,
8
- maxTokens: input.maxTokens ?? DEFAULT_BUDGET.maxTokens
8
+ ...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
9
9
  };
10
10
  const policy = compileExecutionPolicy({
11
11
  configPath: "mcp://inline",
@@ -1,4 +1,3 @@
1
- import type { CostProvenance } from "../vendor/contracts/index.js";
2
1
  import { type LoopPreview } from "./tool-support.js";
3
2
  export interface GetStatusInput {
4
3
  /** JSON-serialized LoopRecord. */
@@ -19,18 +18,17 @@ export interface GetStatusOutput {
19
18
  lifecycleState: string;
20
19
  attempts: number;
21
20
  costUsd: number;
22
- costProvenance: CostProvenance;
23
21
  avoidedUsd: number;
24
22
  pressure: string;
25
23
  shouldStop: boolean;
26
24
  remainingBudgetUsd: number;
27
25
  remainingIterations: number;
28
- remainingTokens: number;
26
+ remainingTokens?: number;
29
27
  budget: {
30
28
  maxUsd: number;
31
29
  softLimitUsd: number;
32
30
  maxIterations: number;
33
- maxTokens: number;
31
+ maxTokens?: number;
34
32
  };
35
33
  inspection: {
36
34
  loop: LoopPreview;
@@ -31,18 +31,21 @@ export async function getStatusTool(input) {
31
31
  lifecycleState: loop.lifecycleState,
32
32
  attempts: loop.attempts.length,
33
33
  costUsd: loop.cost.actualUsd,
34
- costProvenance: loop.cost.provenance ?? "unavailable",
35
34
  avoidedUsd: loop.cost.avoidedUsd ?? 0,
36
35
  pressure: costState.pressure,
37
36
  shouldStop: costState.shouldStop,
38
37
  remainingBudgetUsd: costState.remainingBudgetUsd,
39
38
  remainingIterations: costState.remainingIterations,
40
- remainingTokens: costState.remainingTokens,
39
+ ...(costState.remainingTokens !== undefined
40
+ ? { remainingTokens: costState.remainingTokens }
41
+ : {}),
41
42
  budget: {
42
43
  maxUsd: loop.budget.maxUsd,
43
44
  softLimitUsd: loop.budget.softLimitUsd,
44
45
  maxIterations: loop.budget.maxIterations,
45
- maxTokens: loop.budget.maxTokens
46
+ ...(loop.budget.maxTokens !== undefined
47
+ ? { maxTokens: loop.budget.maxTokens }
48
+ : {})
46
49
  },
47
50
  inspection: {
48
51
  loop: buildLoopPreview(loop)
@@ -51,7 +51,7 @@ export interface MartinPreflightOutput {
51
51
  maxUsd: number;
52
52
  softLimitUsd: number;
53
53
  maxIterations: number;
54
- maxTokens: number;
54
+ maxTokens?: number;
55
55
  };
56
56
  verificationPlan: string[];
57
57
  allowedPaths?: string[];
@@ -32,7 +32,7 @@ export interface RunLoopOutput {
32
32
  shouldStop: boolean;
33
33
  remainingBudgetUsd: number;
34
34
  remainingIterations: number;
35
- remainingTokens: number;
35
+ remainingTokens?: number;
36
36
  engine: MartinEngine;
37
37
  workingDirectory: string;
38
38
  budget: LoopBudget;
@@ -174,7 +174,9 @@ export async function runLoopTool(input) {
174
174
  shouldStop: costState.shouldStop,
175
175
  remainingBudgetUsd: costState.remainingBudgetUsd,
176
176
  remainingIterations: costState.remainingIterations,
177
- remainingTokens: costState.remainingTokens,
177
+ ...(costState.remainingTokens !== undefined
178
+ ? { remainingTokens: costState.remainingTokens }
179
+ : {}),
178
180
  engine,
179
181
  workingDirectory,
180
182
  budget,
@@ -641,7 +641,7 @@ function validateInspectableLoopRecord(value) {
641
641
  !isFiniteNumber(budget["maxUsd"]) ||
642
642
  !isFiniteNumber(budget["softLimitUsd"]) ||
643
643
  !Number.isInteger(budget["maxIterations"]) ||
644
- !Number.isInteger(budget["maxTokens"]) ||
644
+ (budget["maxTokens"] !== undefined && !Number.isInteger(budget["maxTokens"])) ||
645
645
  !isRecord(cost) ||
646
646
  !isFiniteNumber(cost["actualUsd"]) ||
647
647
  !isFiniteNumber(cost["tokensIn"]) ||
@@ -6,7 +6,8 @@ export interface InspectableLoopAttempt extends LoopAttemptRecord {
6
6
  attemptId?: string;
7
7
  summary?: string;
8
8
  }
9
- export interface InspectableLoopRecord extends Omit<LoopRunRecord, "attempts" | "task"> {
9
+ export interface InspectableLoopRecord extends Omit<LoopRunRecord, "attempts" | "task" | "budget"> {
10
+ budget: LoopBudget;
10
11
  attempts: InspectableLoopAttempt[];
11
12
  task?: LoopTask;
12
13
  artifacts?: LoopArtifact[];
@@ -33,7 +34,7 @@ export interface LoopPreview {
33
34
  shouldStop: boolean;
34
35
  remainingBudgetUsd: number;
35
36
  remainingIterations: number;
36
- remainingTokens: number;
37
+ remainingTokens?: number;
37
38
  lastAttempt?: AttemptSummary;
38
39
  routingEconomics?: Record<string, unknown>;
39
40
  }
@@ -157,7 +157,9 @@ export function buildLoopPreview(loop) {
157
157
  shouldStop: costState.shouldStop,
158
158
  remainingBudgetUsd: costState.remainingBudgetUsd,
159
159
  remainingIterations: costState.remainingIterations,
160
- remainingTokens: costState.remainingTokens,
160
+ ...(costState.remainingTokens !== undefined
161
+ ? { remainingTokens: costState.remainingTokens }
162
+ : {}),
161
163
  ...(lastAttempt ? { lastAttempt: buildAttemptSummary(lastAttempt) } : {}),
162
164
  ...(loop.routingEconomics ? { routingEconomics: buildRoutingEconomicsSummary(loop.routingEconomics) } : {})
163
165
  };
@@ -291,7 +293,12 @@ export function buildCostSnapshot(cost) {
291
293
  };
292
294
  }
293
295
  export function buildBudgetSnapshot(budget) {
294
- return { maxUsd: budget.maxUsd, softLimitUsd: budget.softLimitUsd, maxIterations: budget.maxIterations, maxTokens: budget.maxTokens };
296
+ return {
297
+ maxUsd: budget.maxUsd,
298
+ softLimitUsd: budget.softLimitUsd,
299
+ maxIterations: budget.maxIterations,
300
+ ...(budget.maxTokens !== undefined ? { maxTokens: budget.maxTokens } : {})
301
+ };
295
302
  }
296
303
  export function buildSuggestedResourceUris(loopId) {
297
304
  return ["martin://server/health", "martin://runs/recent", "martin://runs/triage", "martin://runs/latest", "martin://runs/latest/summary", "martin://runs/latest/proof-card", "martin://runs/latest/budget-status", "martin://runs/latest/verifier-evidence", "martin://runs/latest/rollback-evidence", "martin://policies/current", "martin://repo/risk-map", "martin://verifiers/results", "martin://agent/next-step", `martin://runs/${loopId}`, `martin://runs/${loopId}/dossier`, `martin://runs/${loopId}/verification`, "martin://guides/mcp-usage", "martin://guides/agent-start", "martin://guides/publish-readiness"];
@@ -50,7 +50,7 @@ export interface MartinPlanBudget {
50
50
  maxUsd: number;
51
51
  softLimitUsd: number;
52
52
  maxIterations: number;
53
- maxTokens: number;
53
+ maxTokens?: number;
54
54
  maxMinutes: number;
55
55
  maxFilesChanged: number;
56
56
  maxCommands: number;
@@ -271,7 +271,7 @@ export function normalizeLoopBudget(overrides = {}) {
271
271
  maxUsd,
272
272
  softLimitUsd,
273
273
  maxIterations: overrides.maxIterations ?? DEFAULT_BUDGET.maxIterations,
274
- maxTokens: overrides.maxTokens ?? DEFAULT_BUDGET.maxTokens
274
+ ...(overrides.maxTokens !== undefined ? { maxTokens: overrides.maxTokens } : {})
275
275
  };
276
276
  }
277
277
  export function assessRunRisk(input) {
@@ -103,12 +103,12 @@ function normalizeOpenAiCompatibleUsage(input) {
103
103
  // ---------------------------------------------------------------------------
104
104
  // Prompt builder
105
105
  // ---------------------------------------------------------------------------
106
- const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
107
- Follow these rules exactly:
108
- - Read the task description carefully and implement only what is asked.
109
- - Do not add features, refactors, or improvements beyond the stated task.
110
- - If the task asks you to write or modify code, output the complete file content with changes applied.
111
- - Be precise, minimal, and test-backed in all changes.
106
+ const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
107
+ Follow these rules exactly:
108
+ - Read the task description carefully and implement only what is asked.
109
+ - Do not add features, refactors, or improvements beyond the stated task.
110
+ - If the task asks you to write or modify code, output the complete file content with changes applied.
111
+ - Be precise, minimal, and test-backed in all changes.
112
112
  - State what you changed and why at the end of your response.`;
113
113
  export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
114
114
  export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
@@ -26,7 +26,7 @@ export function createVerifierOnlyAdapter(options = {}) {
26
26
  const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl, {
27
27
  runId: request.loopId,
28
28
  workspaceId: request.workspaceId,
29
- cwd: workingDirectory
29
+ cwd: workingDirectory,
30
30
  });
31
31
  const changedFiles = shouldTrackVerifierWrites
32
32
  ? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
@@ -14,7 +14,7 @@ export function cloneExecutionPolicy(policy) {
14
14
  maxUsd: policy.budget.maxUsd,
15
15
  softLimitUsd: policy.budget.softLimitUsd,
16
16
  maxIterations: policy.budget.maxIterations,
17
- maxTokens: policy.budget.maxTokens
17
+ ...(policy.budget.maxTokens !== undefined ? { maxTokens: policy.budget.maxTokens } : {})
18
18
  },
19
19
  governance: {
20
20
  policyProfile: policy.governance.policyProfile,
@@ -44,7 +44,7 @@ export interface ExitPolicyV1 {
44
44
  };
45
45
  budget: {
46
46
  maxUsd: number;
47
- maxTokens: number;
47
+ maxTokens?: number;
48
48
  };
49
49
  wallClock: {
50
50
  maxElapsedMs: number;
@@ -5,7 +5,7 @@ export type GovernanceSnapshot = {
5
5
  policyProfile: PolicyProfile;
6
6
  maxUsd: number;
7
7
  softLimitUsd: number;
8
- maxTokens: number;
8
+ maxTokens?: number;
9
9
  maxIterations: number;
10
10
  allowedAdapters: string[];
11
11
  allowedModels: string[];
@@ -63,7 +63,7 @@ export interface LoopBudget {
63
63
  maxUsd: number;
64
64
  softLimitUsd: number;
65
65
  maxIterations: number;
66
- maxTokens: number;
66
+ maxTokens?: number;
67
67
  }
68
68
  export interface LoopCost {
69
69
  actualUsd: number;
@@ -27,8 +27,7 @@ export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
27
27
  export const DEFAULT_BUDGET = {
28
28
  maxUsd: 25,
29
29
  softLimitUsd: 15,
30
- maxIterations: 8,
31
- maxTokens: 80_000
30
+ maxIterations: 8
32
31
  };
33
32
  export const EMPTY_COST = {
34
33
  actualUsd: 0,
@@ -1,4 +1,4 @@
1
- export declare const MARTIN_ERROR_CATEGORIES: readonly ["invalid_input", "environment", "auth", "not_found", "store_unreadable", "verification_failed", "policy_blocked", "budget_exit", "transient", "install_failed"];
1
+ export declare const MARTIN_ERROR_CATEGORIES: readonly ["invalid_input", "environment", "auth", "not_found", "store_unreadable", "verification_failed", "policy_blocked", "budget_exit", "transient"];
2
2
  export type MartinErrorCategory = (typeof MARTIN_ERROR_CATEGORIES)[number];
3
3
  export type MartinOutputMode = "human" | "json" | "quiet";
4
4
  export interface MartinRunSelector {
@@ -7,6 +7,5 @@ export const MARTIN_ERROR_CATEGORIES = [
7
7
  "verification_failed",
8
8
  "policy_blocked",
9
9
  "budget_exit",
10
- "transient",
11
- "install_failed"
10
+ "transient"
12
11
  ];