@martinloop/mcp 0.3.3 → 0.3.5

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 (60) hide show
  1. package/README.md +112 -107
  2. package/dist/discovery-metadata.d.ts +6 -6
  3. package/dist/discovery-metadata.js +6 -0
  4. package/dist/discovery-support.js +2 -2
  5. package/dist/resources.d.ts +1 -0
  6. package/dist/resources.js +210 -148
  7. package/dist/server-validation.js +2 -0
  8. package/dist/server.js +87 -1
  9. package/dist/tools/eval.js +29 -5
  10. package/dist/tools/execution-policy.d.ts +11 -0
  11. package/dist/tools/execution-policy.js +47 -0
  12. package/dist/tools/run-dossier.js +2 -2
  13. package/dist/tools/run-loop.d.ts +1 -0
  14. package/dist/tools/run-loop.js +15 -2
  15. package/dist/tools/tool-support.d.ts +3 -1
  16. package/dist/tools/tool-support.js +127 -19
  17. package/dist/tools/triage-runs.js +31 -6
  18. package/dist/vendor/adapters/claude-cli.js +100 -21
  19. package/dist/vendor/adapters/cli-bridge.d.ts +16 -0
  20. package/dist/vendor/adapters/cli-bridge.js +92 -2
  21. package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
  22. package/dist/vendor/adapters/codex-launcher.js +124 -10
  23. package/dist/vendor/adapters/openai-compatible.js +6 -6
  24. package/dist/vendor/contracts/adapter-capabilities.d.ts +17 -0
  25. package/dist/vendor/contracts/adapter-capabilities.js +35 -0
  26. package/dist/vendor/contracts/context-graph.d.ts +48 -0
  27. package/dist/vendor/contracts/context-graph.js +26 -0
  28. package/dist/vendor/contracts/execution-policy.d.ts +78 -0
  29. package/dist/vendor/contracts/execution-policy.js +43 -0
  30. package/dist/vendor/contracts/governance.d.ts +6 -3
  31. package/dist/vendor/contracts/identity.d.ts +29 -0
  32. package/dist/vendor/contracts/identity.js +30 -0
  33. package/dist/vendor/contracts/index.d.ts +40 -2
  34. package/dist/vendor/contracts/index.js +5 -0
  35. package/dist/vendor/contracts/observation.d.ts +21 -0
  36. package/dist/vendor/contracts/observation.js +27 -0
  37. package/dist/vendor/contracts/trajectory.d.ts +26 -0
  38. package/dist/vendor/contracts/trajectory.js +27 -0
  39. package/dist/vendor/contracts/verification.d.ts +28 -0
  40. package/dist/vendor/contracts/verification.js +7 -0
  41. package/dist/vendor/core/context-graph.d.ts +3 -0
  42. package/dist/vendor/core/context-graph.js +371 -0
  43. package/dist/vendor/core/identity.d.ts +20 -0
  44. package/dist/vendor/core/identity.js +86 -0
  45. package/dist/vendor/core/index.d.ts +10 -1
  46. package/dist/vendor/core/index.js +131 -10
  47. package/dist/vendor/core/persistence/integrity.js +2 -2
  48. package/dist/vendor/core/policy-compiler.d.ts +2 -0
  49. package/dist/vendor/core/policy-compiler.js +195 -0
  50. package/dist/vendor/core/policy.d.ts +15 -0
  51. package/dist/vendor/core/policy.js +84 -4
  52. package/dist/vendor/core/rollback.js +32 -12
  53. package/dist/vendor/core/routing.d.ts +40 -0
  54. package/dist/vendor/core/routing.js +185 -0
  55. package/dist/vendor/core/trajectory.d.ts +18 -0
  56. package/dist/vendor/core/trajectory.js +295 -0
  57. package/dist/workflow-state.d.ts +16 -0
  58. package/dist/workflow-state.js +1 -1
  59. package/package.json +76 -75
  60. package/server.json +21 -21
package/dist/resources.js CHANGED
@@ -5,6 +5,7 @@ import { martinDoctorTool } from "./tools/doctor.js";
5
5
  import { martinTriageRunsTool } from "./tools/triage-runs.js";
6
6
  import { martinRunDossierTool } from "./tools/run-dossier.js";
7
7
  import { inspectRepoSignals, buildPolicyPackDefinition, buildRepoRiskMap } from "./tools/workflow-governance.js";
8
+ import { readWorkflowState } from "./workflow-state.js";
8
9
  import { buildAttemptSnapshot, buildPersistedLoopPreview, buildVerificationHistorySnapshot, parseAttemptIndex, resolveMartinDiscoveryContext, toPrettyJson } from "./discovery-support.js";
9
10
  import { loadDetailedLoopRecord, readLedgerEvents } from "./tools/run-store.js";
10
11
  import { invalidArgumentsError, MartinToolError } from "./tools/tool-errors.js";
@@ -27,7 +28,8 @@ export const MARTIN_STATIC_RESOURCE_URIS = {
27
28
  commandMapGuide: "martin://guides/command-map",
28
29
  ideOnboardingGuide: "martin://guides/ide-onboarding",
29
30
  operatingRulesGuide: "martin://guides/operating-rules",
30
- publishReadinessGuide: "martin://guides/publish-readiness"
31
+ publishReadinessGuide: "martin://guides/publish-readiness",
32
+ governanceStatus: "martin://agent/governance-status"
31
33
  };
32
34
  export const MARTIN_RESOURCE_TEMPLATES = [
33
35
  {
@@ -188,6 +190,13 @@ export const MARTIN_STATIC_RESOURCES = [
188
190
  title: "Martin Publish Readiness Guide",
189
191
  description: "Checklist-oriented guide for MCP publish readiness reviews.",
190
192
  mimeType: "text/markdown"
193
+ },
194
+ {
195
+ uri: MARTIN_STATIC_RESOURCE_URIS.governanceStatus,
196
+ name: "martin_governance_status",
197
+ title: "Martin Governance Status",
198
+ description: "Proactive governance status: whether work is governed, budget remaining, unreceipted runs, and recommended next action. Read this before starting agent work.",
199
+ mimeType: "application/json"
191
200
  }
192
201
  ];
193
202
  export function listMartinResources() {
@@ -237,6 +246,8 @@ export async function readMartinResource(input) {
237
246
  return jsonResource(input.uri, withDiscoveryMetadata(buildRepoRiskMap(inspectRepoSignals(context.workingDirectory)), context.runsRoot));
238
247
  case MARTIN_STATIC_RESOURCE_URIS.verifierResults:
239
248
  return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestVerifierEvidenceResource(context.runsRoot), context.runsRoot));
249
+ case MARTIN_STATIC_RESOURCE_URIS.governanceStatus:
250
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildGovernanceStatusResource(context.runsRoot, context.workingDirectory), context.runsRoot));
240
251
  case MARTIN_STATIC_RESOURCE_URIS.agentNextStep:
241
252
  return jsonResource(input.uri, withDiscoveryMetadata(await buildAgentNextStepResource(context.runsRoot), context.runsRoot));
242
253
  case MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide:
@@ -565,6 +576,57 @@ function compactEmptyState(kind, runsRoot, warnings) {
565
576
  warnings
566
577
  };
567
578
  }
579
+ async function buildGovernanceStatusResource(runsRoot, workingDirectory) {
580
+ const state = await readWorkflowState(runsRoot);
581
+ const mcpState = state.mcp ?? {};
582
+ const hasDoctor = Boolean(mcpState.doctor);
583
+ const hasPlan = Boolean(mcpState.plan);
584
+ const hasPreflight = Boolean(mcpState.preflight);
585
+ const governed = hasDoctor && hasPlan && hasPreflight;
586
+ const latest = await loadLatestRunForCompactResource(runsRoot);
587
+ const budgetRemaining = (!latest.empty && latest.detail)
588
+ ? buildPersistedLoopPreview(latest.detail.loop).remainingBudgetUsd
589
+ : null;
590
+ let unreceiptedRuns = 0;
591
+ try {
592
+ const inspection = await inspectLoopTool({ runsDir: runsRoot });
593
+ const total = inspection.loopCount ?? 0;
594
+ const completed = (inspection.statusBreakdown?.completed ?? 0) + (inspection.statusBreakdown?.failed ?? 0);
595
+ unreceiptedRuns = Math.max(0, total - completed);
596
+ }
597
+ catch {
598
+ // No runs yet
599
+ }
600
+ const missingSteps = [];
601
+ if (!hasDoctor)
602
+ missingSteps.push("martin_doctor");
603
+ if (!hasPlan)
604
+ missingSteps.push("martin_plan");
605
+ if (!hasPreflight)
606
+ missingSteps.push("martin_preflight");
607
+ const recommendedAction = !hasDoctor
608
+ ? "Run martin_doctor to confirm environment before any work."
609
+ : !hasPlan
610
+ ? "Run martin_plan with your objective to scope the task."
611
+ : !hasPreflight
612
+ ? "Run martin_preflight to validate the run contract before execution."
613
+ : "Governance complete. You may proceed with martin_run.";
614
+ return {
615
+ kind: "governance-status",
616
+ governed,
617
+ workflowReceipts: {
618
+ doctor: hasDoctor ? { recordedAt: mcpState.doctor.recordedAt } : null,
619
+ plan: hasPlan ? { recordedAt: mcpState.plan.recordedAt } : null,
620
+ preflight: hasPreflight ? { recordedAt: mcpState.preflight.recordedAt } : null
621
+ },
622
+ missingSteps,
623
+ budgetRemaining,
624
+ unreceiptedRuns,
625
+ recommendedAction,
626
+ requiredSequence: ["martin_doctor", "martin_plan", "martin_preflight", "martin_run", "martin_dossier"],
627
+ warning: governed ? undefined : "This session is NOT governed. Complete the required sequence before making changes."
628
+ };
629
+ }
568
630
  function buildCurrentPoliciesResource(workingDirectory) {
569
631
  const signals = inspectRepoSignals(workingDirectory);
570
632
  const recommended = buildPolicyPackDefinition(undefined, signals);
@@ -626,173 +688,173 @@ function inferAgentNextStep(loop, verification) {
626
688
  }
627
689
  function buildMcpUsageGuide(runsRoot) {
628
690
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
629
- return `# Martin Loop MCP Usage
630
-
631
- Discovery revision: \`${metadata.discoveryRevision}\`
632
- Server version: \`${metadata.serverVersion}\`
633
- Runs root: \`${runsRoot}\`
634
-
635
- Martin Loop exposes governed coding workflows over MCP. Use the server health and run-store views to decide when to preflight, execute, inspect, or escalate.
636
-
637
- ## Recommended Flow
638
-
639
- 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` or call \`martin_doctor\` to confirm the environment and run-store visibility.
640
- 2. Use the \`martin_governed_coding_kickoff\` prompt to frame a governed coding request.
641
- 3. Call \`martin_preflight\` before \`martin_run\` when the task or safety envelope is non-trivial.
642
- 4. After execution, inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`martin://runs/{loopId}\`, and \`martin://runs/{loopId}/verification\`.
643
- 5. For low-context agents, read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, or \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\` before asking for full JSON.
644
- 6. Read \`${MARTIN_STATIC_RESOURCE_URIS.triage}\` or call \`martin_triage_runs\` to prioritize which run needs attention first.
645
- 7. Use \`martin_debug_failed_run\` when a loop exits failed, budget-bound, or escalated.
646
-
647
- ## Current Martin MCP Surface
648
-
649
- - Tools: \`martin_run\`, \`martin_inspect\`, \`martin_status\`, \`martin_doctor\`, \`martin_preflight\`, \`martin_list_runs\`, \`martin_triage_runs\`, \`martin_get_run\`, \`martin_get_attempt\`, \`martin_get_verification_results\`, \`martin_run_dossier\`
650
- - Static resources: \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\`, \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`${MARTIN_STATIC_RESOURCE_URIS.triage}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide}\`
651
- - Resource templates: \`martin://runs/{loopId}\`, \`martin://runs/{loopId}/attempts/{attemptIndex}\`, \`martin://runs/{loopId}/verification\`
652
- - Prompts: \`martin_start\`, \`martin_preflight\`, \`martin_triage\`, \`martin_resume\`, \`martin_prove\`, \`martin_release_check\`, \`martin_governed_coding_kickoff\`, \`martin_debug_failed_run\`, \`martin_publish_readiness_review\`, \`martin_triage_run_store\`
653
-
654
- ## Notes
655
-
656
- - Discovery helpers read the existing Martin run-store; they do not create new schema.
657
- - Verification history is derived from persisted \`verification.completed\` evidence in loop records and ledgers.
658
- - Attempt inspection stays aligned with the same loop selectors used by \`martin_status\` and \`martin_inspect\`.
691
+ return `# Martin Loop MCP Usage
692
+
693
+ Discovery revision: \`${metadata.discoveryRevision}\`
694
+ Server version: \`${metadata.serverVersion}\`
695
+ Runs root: \`${runsRoot}\`
696
+
697
+ Martin Loop exposes governed coding workflows over MCP. Use the server health and run-store views to decide when to preflight, execute, inspect, or escalate.
698
+
699
+ ## Recommended Flow
700
+
701
+ 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` or call \`martin_doctor\` to confirm the environment and run-store visibility.
702
+ 2. Use the \`martin_governed_coding_kickoff\` prompt to frame a governed coding request.
703
+ 3. Call \`martin_preflight\` before \`martin_run\` when the task or safety envelope is non-trivial.
704
+ 4. After execution, inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`martin://runs/{loopId}\`, and \`martin://runs/{loopId}/verification\`.
705
+ 5. For low-context agents, read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, or \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\` before asking for full JSON.
706
+ 6. Read \`${MARTIN_STATIC_RESOURCE_URIS.triage}\` or call \`martin_triage_runs\` to prioritize which run needs attention first.
707
+ 7. Use \`martin_debug_failed_run\` when a loop exits failed, budget-bound, or escalated.
708
+
709
+ ## Current Martin MCP Surface
710
+
711
+ - Tools: \`martin_run\`, \`martin_inspect\`, \`martin_status\`, \`martin_doctor\`, \`martin_preflight\`, \`martin_list_runs\`, \`martin_triage_runs\`, \`martin_get_run\`, \`martin_get_attempt\`, \`martin_get_verification_results\`, \`martin_run_dossier\`
712
+ - Static resources: \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\`, \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`${MARTIN_STATIC_RESOURCE_URIS.triage}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide}\`
713
+ - Resource templates: \`martin://runs/{loopId}\`, \`martin://runs/{loopId}/attempts/{attemptIndex}\`, \`martin://runs/{loopId}/verification\`
714
+ - Prompts: \`martin_start\`, \`martin_preflight\`, \`martin_triage\`, \`martin_resume\`, \`martin_prove\`, \`martin_release_check\`, \`martin_governed_coding_kickoff\`, \`martin_debug_failed_run\`, \`martin_publish_readiness_review\`, \`martin_triage_run_store\`
715
+
716
+ ## Notes
717
+
718
+ - Discovery helpers read the existing Martin run-store; they do not create new schema.
719
+ - Verification history is derived from persisted \`verification.completed\` evidence in loop records and ledgers.
720
+ - Attempt inspection stays aligned with the same loop selectors used by \`martin_status\` and \`martin_inspect\`.
659
721
  `;
660
722
  }
661
723
  function buildAgentStartGuide(runsRoot) {
662
724
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
663
- return `# Agent Start Here
664
-
665
- Discovery revision: \`${metadata.discoveryRevision}\`
666
- Server version: \`${metadata.serverVersion}\`
667
- Runs root: \`${runsRoot}\`
668
-
669
- Use Martin Loop as the local governor before you spend agent tokens or retry a failed coding loop.
670
-
671
- ## Cheap Default Flow
672
-
673
- 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\` first. It is the smallest "what should I do now?" payload.
674
- 2. If no runs exist, call \`martin_doctor\`, then \`martin_preflight\`.
675
- 3. If a run exists, read \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\` before reading full run JSON.
676
- 4. If the verifier failed, use prompt \`martin_debug_failed_run\` or \`martin_triage\`.
677
- 5. If you need a shareable receipt, read \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`.
678
-
679
- ## Install Profiles
680
-
681
- - \`minimal\` (default): doctor, preflight, list, triage, dossier. Good for low tool bloat.
682
- - \`diagnostic\`: read-only inspection tools for debugging without execution.
683
- - \`full-local\`: all local tools, including \`martin_run\`.
684
- - \`github-review\`: review-oriented local profile for PR drafting and reviewer evidence.
685
- - \`starter\` and \`full\`: compatibility aliases.
686
-
687
- ## Copy-Paste Agent Rule
688
-
689
- Before running or retrying an autonomous coding task, call Martin. Prefer compact resources first, then full dossiers only when the compact receipt says more evidence is needed. Never claim success unless Martin shows verifier-backed completion or a clear blocked reason.
725
+ return `# Agent Start Here
726
+
727
+ Discovery revision: \`${metadata.discoveryRevision}\`
728
+ Server version: \`${metadata.serverVersion}\`
729
+ Runs root: \`${runsRoot}\`
730
+
731
+ Use Martin Loop as the local governor before you spend agent tokens or retry a failed coding loop.
732
+
733
+ ## Cheap Default Flow
734
+
735
+ 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\` first. It is the smallest "what should I do now?" payload.
736
+ 2. If no runs exist, call \`martin_doctor\`, then \`martin_preflight\`.
737
+ 3. If a run exists, read \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\` before reading full run JSON.
738
+ 4. If the verifier failed, use prompt \`martin_debug_failed_run\` or \`martin_triage\`.
739
+ 5. If you need a shareable receipt, read \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`.
740
+
741
+ ## Install Profiles
742
+
743
+ - \`minimal\` (default): doctor, preflight, list, triage, dossier. Good for low tool bloat.
744
+ - \`diagnostic\`: read-only inspection tools for debugging without execution.
745
+ - \`full-local\`: all local tools, including \`martin_run\`.
746
+ - \`github-review\`: review-oriented local profile for PR drafting and reviewer evidence.
747
+ - \`starter\` and \`full\`: compatibility aliases.
748
+
749
+ ## Copy-Paste Agent Rule
750
+
751
+ Before running or retrying an autonomous coding task, call Martin. Prefer compact resources first, then full dossiers only when the compact receipt says more evidence is needed. Never claim success unless Martin shows verifier-backed completion or a clear blocked reason.
690
752
  `;
691
753
  }
692
754
  function buildCommandMapGuide(runsRoot) {
693
755
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
694
- return `# Martin Command Map
695
-
696
- Discovery revision: \`${metadata.discoveryRevision}\`
697
- Server version: \`${metadata.serverVersion}\`
698
- Runs root: \`${runsRoot}\`
699
-
700
- Use this guide when an IDE or agent needs to know which Martin surface to call next.
701
-
702
- ## Default Governed Sequence
703
-
704
- 1. \`martin_doctor\` — confirm environment and visibility.
705
- 2. \`martin_plan\` — propose the bounded approach without spending a run.
706
- 3. \`martin_preflight\` — turn the plan into an explicit contract.
707
- 4. \`martin_run\` — execute only after the contract is safe.
708
- 5. \`martin_dossier\` — inspect what happened and what Martin prevented.
709
- 6. \`martin_eval\` — convert the result into review posture when needed.
710
-
711
- ## When To Use Which Surface
712
-
713
- - Need the smallest next action: \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`
714
- - Need a quick receipt: \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`
715
- - Need a shareable proof object: \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`
716
- - Need a full run review: \`martin_dossier\`
717
- - Need to decide whether to retry: \`martin_triage_runs\`
718
- - Need IDE setup guidance: \`${MARTIN_STATIC_RESOURCE_URIS.ideOnboardingGuide}\`
756
+ return `# Martin Command Map
757
+
758
+ Discovery revision: \`${metadata.discoveryRevision}\`
759
+ Server version: \`${metadata.serverVersion}\`
760
+ Runs root: \`${runsRoot}\`
761
+
762
+ Use this guide when an IDE or agent needs to know which Martin surface to call next.
763
+
764
+ ## Default Governed Sequence
765
+
766
+ 1. \`martin_doctor\` — confirm environment and visibility.
767
+ 2. \`martin_plan\` — propose the bounded approach without spending a run.
768
+ 3. \`martin_preflight\` — turn the plan into an explicit contract.
769
+ 4. \`martin_run\` — execute only after the contract is safe.
770
+ 5. \`martin_dossier\` — inspect what happened and what Martin prevented.
771
+ 6. \`martin_eval\` — convert the result into review posture when needed.
772
+
773
+ ## When To Use Which Surface
774
+
775
+ - Need the smallest next action: \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`
776
+ - Need a quick receipt: \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`
777
+ - Need a shareable proof object: \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`
778
+ - Need a full run review: \`martin_dossier\`
779
+ - Need to decide whether to retry: \`martin_triage_runs\`
780
+ - Need IDE setup guidance: \`${MARTIN_STATIC_RESOURCE_URIS.ideOnboardingGuide}\`
719
781
  `;
720
782
  }
721
783
  function buildIdeOnboardingGuide(runsRoot) {
722
784
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
723
- return `# Martin IDE Onboarding
724
-
725
- Discovery revision: \`${metadata.discoveryRevision}\`
726
- Server version: \`${metadata.serverVersion}\`
727
- Runs root: \`${runsRoot}\`
728
-
729
- Use this guide after the MCP server is installed so the host naturally uses MartinLoop during real work.
730
-
731
- ## IDE Default Flow
732
-
733
- 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`
734
- 2. Read \`${MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide}\`
735
- 3. Use prompt \`martin_start\` or \`martin_governed_coding_kickoff\`
736
- 4. Call \`martin_doctor\`
737
- 5. Call \`martin_plan\`
738
- 6. Call \`martin_preflight\`
739
- 7. Only then call \`martin_run\`
740
-
741
- ## Goal
742
-
743
- MartinLoop should become the normal path for coding work in the host, not a manual extra step the human has to remember.
785
+ return `# Martin IDE Onboarding
786
+
787
+ Discovery revision: \`${metadata.discoveryRevision}\`
788
+ Server version: \`${metadata.serverVersion}\`
789
+ Runs root: \`${runsRoot}\`
790
+
791
+ Use this guide after the MCP server is installed so the host naturally uses MartinLoop during real work.
792
+
793
+ ## IDE Default Flow
794
+
795
+ 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`
796
+ 2. Read \`${MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide}\`
797
+ 3. Use prompt \`martin_start\` or \`martin_governed_coding_kickoff\`
798
+ 4. Call \`martin_doctor\`
799
+ 5. Call \`martin_plan\`
800
+ 6. Call \`martin_preflight\`
801
+ 7. Only then call \`martin_run\`
802
+
803
+ ## Goal
804
+
805
+ MartinLoop should become the normal path for coding work in the host, not a manual extra step the human has to remember.
744
806
  `;
745
807
  }
746
808
  function buildOperatingRulesGuide(runsRoot) {
747
809
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
748
- return `# Martin Operating Rules
749
-
750
- Discovery revision: \`${metadata.discoveryRevision}\`
751
- Server version: \`${metadata.serverVersion}\`
752
- Runs root: \`${runsRoot}\`
753
-
754
- Use these rules inside the IDE or MCP host as the default MartinLoop operating posture.
755
-
756
- ## Required Sequence Before Real Work
757
-
758
- 1. \`martin_doctor\`
759
- 2. \`martin_plan\`
760
- 3. \`martin_preflight\`
761
- 4. \`martin_run\`
762
- 5. \`martin_dossier\`
763
-
764
- ## Enforcement Rule
765
-
766
- Do not execute real coding work through MartinLoop until doctor and preflight have both been used for the current task.
767
-
768
- ## Completion Rule
769
-
770
- Do not claim success until MartinLoop shows verifier-backed completion or an explicit blocked reason backed by evidence.
810
+ return `# Martin Operating Rules
811
+
812
+ Discovery revision: \`${metadata.discoveryRevision}\`
813
+ Server version: \`${metadata.serverVersion}\`
814
+ Runs root: \`${runsRoot}\`
815
+
816
+ Use these rules inside the IDE or MCP host as the default MartinLoop operating posture.
817
+
818
+ ## Required Sequence Before Real Work
819
+
820
+ 1. \`martin_doctor\`
821
+ 2. \`martin_plan\`
822
+ 3. \`martin_preflight\`
823
+ 4. \`martin_run\`
824
+ 5. \`martin_dossier\`
825
+
826
+ ## Enforcement Rule
827
+
828
+ Do not execute real coding work through MartinLoop until doctor and preflight have both been used for the current task.
829
+
830
+ ## Completion Rule
831
+
832
+ Do not claim success until MartinLoop shows verifier-backed completion or an explicit blocked reason backed by evidence.
771
833
  `;
772
834
  }
773
835
  function buildPublishReadinessGuide(runsRoot) {
774
836
  const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
775
- return `# Martin MCP Publish Readiness
776
-
777
- Discovery revision: \`${metadata.discoveryRevision}\`
778
- Server version: \`${metadata.serverVersion}\`
779
- Runs root: \`${runsRoot}\`
780
-
781
- Use this guide when reviewing whether the public MCP package is ready to publish, promote, or hand off for registry submission.
782
-
783
- ## Review Areas
784
-
785
- 1. Environment health: confirm \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` shows a sane working directory, runs root, and engine availability for the target mode.
786
- 2. Discovery surface: verify tools, prompts, static resources, and resource templates are all discoverable and named consistently.
787
- 3. Run evidence: inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\` and, when relevant, \`martin://runs/{loopId}/verification\` to confirm verification outcomes are explainable from persisted data.
788
- 4. Packaging lane: run the MCP package test, build, and smoke-pack commands before calling the package publish-ready.
789
- 5. Findings-first reporting: call out missing schema, integration gaps, triage-critical runs, and verification blockers before summarizing strengths.
790
-
791
- ## Evidence Expectations
792
-
793
- - Prefer concrete run IDs, attempt indices, and verification summaries over vague statements.
794
- - Distinguish local package validation from live registry readiness.
795
- - If a discovery helper exists but is not yet wired into the server, report that as an integration gap instead of treating the capability as fully shipped.
837
+ return `# Martin MCP Publish Readiness
838
+
839
+ Discovery revision: \`${metadata.discoveryRevision}\`
840
+ Server version: \`${metadata.serverVersion}\`
841
+ Runs root: \`${runsRoot}\`
842
+
843
+ Use this guide when reviewing whether the public MCP package is ready to publish, promote, or hand off for registry submission.
844
+
845
+ ## Review Areas
846
+
847
+ 1. Environment health: confirm \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` shows a sane working directory, runs root, and engine availability for the target mode.
848
+ 2. Discovery surface: verify tools, prompts, static resources, and resource templates are all discoverable and named consistently.
849
+ 3. Run evidence: inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\` and, when relevant, \`martin://runs/{loopId}/verification\` to confirm verification outcomes are explainable from persisted data.
850
+ 4. Packaging lane: run the MCP package test, build, and smoke-pack commands before calling the package publish-ready.
851
+ 5. Findings-first reporting: call out missing schema, integration gaps, triage-critical runs, and verification blockers before summarizing strengths.
852
+
853
+ ## Evidence Expectations
854
+
855
+ - Prefer concrete run IDs, attempt indices, and verification summaries over vague statements.
856
+ - Distinguish local package validation from live registry readiness.
857
+ - If a discovery helper exists but is not yet wired into the server, report that as an integration gap instead of treating the capability as fully shipped.
796
858
  `;
797
859
  }
798
860
  function withDiscoveryMetadata(value, runsRoot) {
@@ -148,6 +148,7 @@ function validateRunInput(args) {
148
148
  "maxUsd",
149
149
  "maxIterations",
150
150
  "maxTokens",
151
+ "verifyTimeoutMs",
151
152
  "maxMinutes",
152
153
  "maxFilesChanged",
153
154
  "maxCommands",
@@ -176,6 +177,7 @@ function validateRunInput(args) {
176
177
  ...optionalPositiveNumber(record.maxUsd, "maxUsd"),
177
178
  ...optionalPositiveInteger(record.maxIterations, "maxIterations"),
178
179
  ...optionalPositiveInteger(record.maxTokens, "maxTokens"),
180
+ ...optionalPositiveInteger(record.verifyTimeoutMs, "verifyTimeoutMs"),
179
181
  ...optionalPositiveInteger(record.maxMinutes, "maxMinutes"),
180
182
  ...optionalPositiveInteger(record.maxFilesChanged, "maxFilesChanged"),
181
183
  ...optionalPositiveInteger(record.maxCommands, "maxCommands"),
package/dist/server.js CHANGED
@@ -43,6 +43,7 @@ import { createRunControlReceipt } from "./tools/run-controls.js";
43
43
  import { martinTriageRunsTool } from "./tools/triage-runs.js";
44
44
  import { runLoopTool } from "./tools/run-loop.js";
45
45
  import { MARTIN_ENGINE_VALUES } from "./tools/tool-support.js";
46
+ import { classifyRoute } from "./vendor/core/index.js";
46
47
  import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
47
48
  import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
48
49
  import { normalizeLoopBudget } from "./tools/workflow-governance.js";
@@ -118,7 +119,7 @@ const verificationSchema = {
118
119
  type: "object",
119
120
  additionalProperties: true,
120
121
  properties: {
121
- status: { type: "string", enum: ["passed", "failed", "unavailable"] },
122
+ status: { type: "string", enum: ["passed", "failed", "contradicted", "not_run"] },
122
123
  eventCount: { type: "integer" },
123
124
  ledgerEventCount: { type: "integer" },
124
125
  latestAttemptIndex: { type: "integer" },
@@ -1370,6 +1371,59 @@ export function createMartinMcpServer(serverInfo) {
1370
1371
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1371
1372
  },
1372
1373
  outputSchema: prReviewOutputSchema
1374
+ },
1375
+ {
1376
+ name: "martin_estimate",
1377
+ description: "Estimate the cost, recommended route, and Pre Work Burn for an objective without spending anything. Use before martin_run to understand what a task will cost.",
1378
+ annotations: {
1379
+ readOnlyHint: true,
1380
+ idempotentHint: true
1381
+ },
1382
+ inputSchema: {
1383
+ type: "object",
1384
+ additionalProperties: false,
1385
+ properties: {
1386
+ objective: {
1387
+ type: "string",
1388
+ description: "The coding task to estimate."
1389
+ },
1390
+ engine: {
1391
+ type: "string",
1392
+ enum: [...MARTIN_ENGINE_VALUES],
1393
+ description: "Which agent CLI would be used. Defaults to claude."
1394
+ },
1395
+ budgetUsd: {
1396
+ type: "number",
1397
+ exclusiveMinimum: 0,
1398
+ description: "Budget ceiling for estimation. Defaults to 5."
1399
+ },
1400
+ fileScope: {
1401
+ type: "array",
1402
+ items: { type: "string" },
1403
+ description: "Optional file paths to scope the estimate."
1404
+ }
1405
+ },
1406
+ required: ["objective"]
1407
+ },
1408
+ outputSchema: {
1409
+ type: "object",
1410
+ additionalProperties: true,
1411
+ properties: {
1412
+ objective: { type: "string" },
1413
+ engine: { type: "string" },
1414
+ budgetUsd: { type: "number" },
1415
+ selectedMode: { type: "string" },
1416
+ confidence: { type: "number" },
1417
+ expectedCostUsd: { type: "number" },
1418
+ expectedPreworkBurnPct: { type: "number" },
1419
+ reason: { type: "array", items: { type: "string" } },
1420
+ blockedSteps: { type: "array", items: { type: "string" } },
1421
+ compressed: { type: "boolean" },
1422
+ compressionSummary: { type: "string" },
1423
+ recommendedBudgetUsd: { type: "number" }
1424
+ },
1425
+ required: ["objective", "engine", "budgetUsd", "selectedMode", "confidence", "expectedCostUsd", "expectedPreworkBurnPct", "reason"]
1426
+ }
1373
1427
  }
1374
1428
  ]
1375
1429
  }));
@@ -1580,6 +1634,38 @@ export function createMartinMcpServer(serverInfo) {
1580
1634
  const output = await martinReviewPrTool(input);
1581
1635
  return createToolSuccessResult(output, `PR review verdict for ${output.loopId}: ${output.verdict}.`);
1582
1636
  }
1637
+ if (name === "martin_estimate") {
1638
+ const input = args;
1639
+ const objective = input.objective;
1640
+ const engine = input.engine ?? "claude";
1641
+ const budgetUsd = input.budgetUsd ?? 5;
1642
+ const fileScope = input.fileScope ?? [];
1643
+ const route = classifyRoute({
1644
+ objective,
1645
+ verificationPlan: [],
1646
+ budgetUsd,
1647
+ allowedPaths: fileScope,
1648
+ scopedFileCount: fileScope.length > 0 ? fileScope.length : undefined
1649
+ });
1650
+ const recommendedBudgetUsd = route.selectedMode === "direct"
1651
+ ? Math.max(2, Math.round(route.expectedCostUsd * 3 * 100) / 100)
1652
+ : Math.max(5, Math.round(route.expectedCostUsd * 2 * 100) / 100);
1653
+ const output = {
1654
+ objective,
1655
+ engine,
1656
+ budgetUsd,
1657
+ selectedMode: route.selectedMode,
1658
+ confidence: route.confidence,
1659
+ expectedCostUsd: route.expectedCostUsd,
1660
+ expectedPreworkBurnPct: route.expectedPreworkBurnPct,
1661
+ reason: route.reason,
1662
+ blockedSteps: route.blockedSteps,
1663
+ compressed: route.compressed,
1664
+ ...(route.compressionSummary ? { compressionSummary: route.compressionSummary } : {}),
1665
+ recommendedBudgetUsd
1666
+ };
1667
+ return createToolSuccessResult(output, `Estimate: ${route.selectedMode} route, ~$${route.expectedCostUsd.toFixed(2)} expected cost, ${route.expectedPreworkBurnPct}% pre-work burn. Recommended budget: $${recommendedBudgetUsd.toFixed(2)}.`);
1668
+ }
1583
1669
  return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
1584
1670
  }
1585
1671
  catch (error) {
@@ -15,15 +15,20 @@ export async function martinEvalTool(input) {
15
15
  verifiers: detail.loop.task?.verificationPlan ?? [],
16
16
  signals
17
17
  });
18
+ const observationMismatch = hasObservationMismatch(detail.loop.events ?? []);
18
19
  const checks = {
19
20
  taskCompletion: detail.loop.status === "completed" ? "passed" : detail.loop.status === "exited" ? "warning" : "failed",
20
21
  verifier: verification.status === "passed"
21
22
  ? "passed"
22
- : verification.status === "failed"
23
+ : verification.status === "failed" || verification.status === "contradicted"
23
24
  ? "failed"
24
25
  : "warning",
25
- diffDiscipline: (detail.loop.task?.allowedPaths?.length ?? 0) > 0 ? "passed" : "warning",
26
- regressionRisk: verification.status === "passed" ? "passed" : "warning",
26
+ diffDiscipline: observationMismatch
27
+ ? "failed"
28
+ : (detail.loop.task?.allowedPaths?.length ?? 0) > 0
29
+ ? "passed"
30
+ : "warning",
31
+ regressionRisk: verification.status === "passed" && !observationMismatch ? "passed" : "warning",
27
32
  securityRisk: risk.level === "high" ? "failed" : risk.level === "medium" ? "warning" : "passed",
28
33
  reviewability: detail.loop.attempts.length > 0 && (detail.loop.events?.length ?? 0) > 0 ? "passed" : "warning"
29
34
  };
@@ -35,7 +40,7 @@ export async function martinEvalTool(input) {
35
40
  score -= checks.securityRisk === "failed" ? 20 : checks.securityRisk === "warning" ? 10 : 0;
36
41
  score -= checks.reviewability === "warning" ? 8 : 0;
37
42
  score = Math.max(0, score);
38
- const grade = verification.status === "unavailable"
43
+ const grade = verification.status === "not_run" || observationMismatch
39
44
  ? "insufficient_evidence"
40
45
  : score >= 90
41
46
  ? "mergeable"
@@ -44,7 +49,14 @@ export async function martinEvalTool(input) {
44
49
  : score >= 55
45
50
  ? "needs_review"
46
51
  : "blocked";
47
- const warnings = [...detail.warnings, ...verification.warnings, ...risk.reasons];
52
+ const warnings = [
53
+ ...detail.warnings,
54
+ ...verification.warnings,
55
+ ...risk.reasons,
56
+ ...(observationMismatch
57
+ ? ["Change observation mismatch: adapter-reported files differ from repo-observed files."]
58
+ : [])
59
+ ];
48
60
  return {
49
61
  source: detail.source,
50
62
  sourceKind: detail.sourceKind,
@@ -64,3 +76,15 @@ export async function martinEvalTool(input) {
64
76
  : `Run ${detail.loop.loopId} is blocked from promotion by verification or risk gaps.`
65
77
  };
66
78
  }
79
+ function hasObservationMismatch(events) {
80
+ return events.some((event) => {
81
+ if (event.type !== "observation.reconciled") {
82
+ return false;
83
+ }
84
+ const observation = isRecord(event.payload?.["observation"]) ? event.payload?.["observation"] : undefined;
85
+ return observation?.["status"] === "mismatch";
86
+ });
87
+ }
88
+ function isRecord(value) {
89
+ return typeof value === "object" && value !== null;
90
+ }
@@ -0,0 +1,11 @@
1
+ import { type ExecutionPolicy } from "../vendor/contracts/index.js";
2
+ export interface CompileMcpExecutionPolicyInput {
3
+ workingDirectory: string;
4
+ maxUsd?: number;
5
+ maxIterations?: number;
6
+ maxTokens?: number;
7
+ verificationPlan?: string[];
8
+ allowedPaths?: string[];
9
+ deniedPaths?: string[];
10
+ }
11
+ export declare function compileMcpExecutionPolicy(input: CompileMcpExecutionPolicyInput): ExecutionPolicy;