@martinloop/mcp 0.2.5 → 0.3.0

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 (70) hide show
  1. package/README.md +40 -132
  2. package/dist/discovery-metadata.d.ts +10 -5
  3. package/dist/discovery-metadata.js +95 -5
  4. package/dist/package-version.d.ts +1 -1
  5. package/dist/package-version.js +1 -1
  6. package/dist/prompts.d.ts +1 -1
  7. package/dist/prompts.js +93 -1
  8. package/dist/resources.d.ts +9 -1
  9. package/dist/resources.js +247 -16
  10. package/dist/server-validation.d.ts +2 -1
  11. package/dist/server-validation.js +124 -0
  12. package/dist/server.js +379 -5
  13. package/dist/tools/doctor.d.ts +14 -1
  14. package/dist/tools/doctor.js +43 -8
  15. package/dist/tools/eval.d.ts +24 -0
  16. package/dist/tools/eval.js +66 -0
  17. package/dist/tools/get-run.d.ts +2 -0
  18. package/dist/tools/get-run.js +2 -1
  19. package/dist/tools/get-status.d.ts +8 -0
  20. package/dist/tools/get-status.js +18 -0
  21. package/dist/tools/get-verification-results.d.ts +2 -0
  22. package/dist/tools/get-verification-results.js +2 -1
  23. package/dist/tools/logs.d.ts +25 -0
  24. package/dist/tools/logs.js +49 -0
  25. package/dist/tools/plan.d.ts +20 -0
  26. package/dist/tools/plan.js +10 -0
  27. package/dist/tools/pr-tools.d.ts +31 -0
  28. package/dist/tools/pr-tools.js +112 -0
  29. package/dist/tools/preflight.d.ts +24 -1
  30. package/dist/tools/preflight.js +47 -7
  31. package/dist/tools/run-controls.d.ts +36 -0
  32. package/dist/tools/run-controls.js +88 -0
  33. package/dist/tools/run-dossier.d.ts +16 -0
  34. package/dist/tools/run-dossier.js +64 -2
  35. package/dist/tools/run-loop.d.ts +3 -2
  36. package/dist/tools/run-loop.js +52 -13
  37. package/dist/tools/tool-errors.d.ts +1 -1
  38. package/dist/tools/tool-errors.js +1 -1
  39. package/dist/tools/tool-support.d.ts +6 -3
  40. package/dist/tools/tool-support.js +37 -3
  41. package/dist/tools/workflow-governance.d.ts +133 -0
  42. package/dist/tools/workflow-governance.js +581 -0
  43. package/dist/vendor/adapters/claude-cli.d.ts +25 -0
  44. package/dist/vendor/adapters/claude-cli.js +279 -19
  45. package/dist/vendor/adapters/cli-bridge.d.ts +6 -0
  46. package/dist/vendor/adapters/cli-bridge.js +58 -9
  47. package/dist/vendor/adapters/codex-launcher.d.ts +44 -0
  48. package/dist/vendor/adapters/codex-launcher.js +247 -0
  49. package/dist/vendor/adapters/index.d.ts +4 -2
  50. package/dist/vendor/adapters/index.js +4 -1
  51. package/dist/vendor/adapters/openai-compatible.d.ts +62 -0
  52. package/dist/vendor/adapters/openai-compatible.js +267 -0
  53. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  54. package/dist/vendor/adapters/runtime-support.js +8 -1
  55. package/dist/vendor/adapters/verifier-only.js +4 -3
  56. package/dist/vendor/contracts/index.d.ts +39 -0
  57. package/dist/vendor/contracts/index.js +2 -0
  58. package/dist/vendor/core/index.d.ts +23 -3
  59. package/dist/vendor/core/index.js +88 -15
  60. package/dist/vendor/core/persistence/index.d.ts +2 -0
  61. package/dist/vendor/core/persistence/index.js +1 -0
  62. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  63. package/dist/vendor/core/persistence/integrity.js +239 -0
  64. package/dist/vendor/core/persistence/store.d.ts +7 -0
  65. package/dist/vendor/core/persistence/store.js +25 -1
  66. package/dist/vendor/core/policy.d.ts +9 -0
  67. package/dist/workflow-state.d.ts +25 -0
  68. package/dist/workflow-state.js +102 -0
  69. package/package.json +3 -3
  70. package/server.json +2 -2
@@ -0,0 +1,66 @@
1
+ import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
2
+ import { resolveTrustedLoopRepoRoot } from "../server-validation.js";
3
+ import { buildVerificationSummary } from "./tool-support.js";
4
+ import { assessRunRisk, inspectRepoSignals } from "./workflow-governance.js";
5
+ export async function martinEvalTool(input) {
6
+ const detail = await loadDetailedLoopRecord(input);
7
+ const ledgerEvents = await readLedgerEvents(detail);
8
+ const verification = buildVerificationSummary(detail.loop, ledgerEvents);
9
+ const repoRoot = resolveTrustedLoopRepoRoot(detail.loop.task?.repoRoot);
10
+ const signals = inspectRepoSignals(repoRoot);
11
+ const risk = assessRunRisk({
12
+ objective: detail.loop.task?.objective ?? detail.loop.loopId,
13
+ allowedPaths: detail.loop.task?.allowedPaths ?? [],
14
+ blockedPaths: detail.loop.task?.deniedPaths ?? [],
15
+ verifiers: detail.loop.task?.verificationPlan ?? [],
16
+ signals
17
+ });
18
+ const checks = {
19
+ taskCompletion: detail.loop.status === "completed" ? "passed" : detail.loop.status === "exited" ? "warning" : "failed",
20
+ verifier: verification.status === "passed"
21
+ ? "passed"
22
+ : verification.status === "failed"
23
+ ? "failed"
24
+ : "warning",
25
+ diffDiscipline: (detail.loop.task?.allowedPaths?.length ?? 0) > 0 ? "passed" : "warning",
26
+ regressionRisk: verification.status === "passed" ? "passed" : "warning",
27
+ securityRisk: risk.level === "high" ? "failed" : risk.level === "medium" ? "warning" : "passed",
28
+ reviewability: detail.loop.attempts.length > 0 && (detail.loop.events?.length ?? 0) > 0 ? "passed" : "warning"
29
+ };
30
+ let score = 100;
31
+ score -= checks.taskCompletion === "failed" ? 25 : checks.taskCompletion === "warning" ? 10 : 0;
32
+ score -= checks.verifier === "failed" ? 25 : checks.verifier === "warning" ? 10 : 0;
33
+ score -= checks.diffDiscipline === "warning" ? 8 : 0;
34
+ score -= checks.regressionRisk === "warning" ? 10 : 0;
35
+ score -= checks.securityRisk === "failed" ? 20 : checks.securityRisk === "warning" ? 10 : 0;
36
+ score -= checks.reviewability === "warning" ? 8 : 0;
37
+ score = Math.max(0, score);
38
+ const grade = verification.status === "unavailable"
39
+ ? "insufficient_evidence"
40
+ : score >= 90
41
+ ? "mergeable"
42
+ : score >= 75
43
+ ? "mergeable_with_review"
44
+ : score >= 55
45
+ ? "needs_review"
46
+ : "blocked";
47
+ const warnings = [...detail.warnings, ...verification.warnings, ...risk.reasons];
48
+ return {
49
+ source: detail.source,
50
+ sourceKind: detail.sourceKind,
51
+ loopId: detail.loop.loopId,
52
+ score,
53
+ grade,
54
+ checks: { ...checks },
55
+ warnings,
56
+ summary: grade === "mergeable"
57
+ ? `Run ${detail.loop.loopId} looks mergeable with verifier-backed completion.`
58
+ : grade === "mergeable_with_review"
59
+ ? `Run ${detail.loop.loopId} looks mergeable with review; inspect dossier and risk notes first.`
60
+ : grade === "needs_review"
61
+ ? `Run ${detail.loop.loopId} needs review before promotion.`
62
+ : grade === "insufficient_evidence"
63
+ ? `Run ${detail.loop.loopId} does not have enough evidence for a safe promotion decision.`
64
+ : `Run ${detail.loop.loopId} is blocked from promotion by verification or risk gaps.`
65
+ };
66
+ }
@@ -1,4 +1,5 @@
1
1
  import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetRunInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -12,6 +13,7 @@ export interface MartinGetRunOutput {
12
13
  budget: ReturnType<typeof buildBudgetSnapshot>;
13
14
  cost: ReturnType<typeof buildCostSnapshot>;
14
15
  verification: ReturnType<typeof buildVerificationSummary>;
16
+ receiptIntegrity: ReceiptIntegritySummary;
15
17
  artifacts: ReturnType<typeof buildArtifactSummary>;
16
18
  inspection: {
17
19
  runsRoot: string;
@@ -1,4 +1,4 @@
1
- import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
1
+ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, resolveReceiptIntegrity, buildVerificationSummary } from "./tool-support.js";
2
2
  import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
3
3
  export async function martinGetRunTool(input) {
4
4
  const detail = await loadDetailedLoopRecord(input);
@@ -11,6 +11,7 @@ export async function martinGetRunTool(input) {
11
11
  budget: buildBudgetSnapshot(detail.loop.budget),
12
12
  cost: buildCostSnapshot(detail.loop.cost),
13
13
  verification,
14
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
14
15
  artifacts: buildArtifactSummary(detail.loop),
15
16
  inspection: {
16
17
  runsRoot: detail.runsRoot,
@@ -33,5 +33,13 @@ export interface GetStatusOutput {
33
33
  inspection: {
34
34
  loop: LoopPreview;
35
35
  };
36
+ live: {
37
+ phase: string;
38
+ pauseState: "active" | "paused" | "cancellation_requested";
39
+ approvalState: "not_required" | "resume_requested";
40
+ commandsRun: number;
41
+ filesTouched: number;
42
+ verifierStep?: string;
43
+ };
36
44
  }
37
45
  export declare function getStatusTool(input: GetStatusInput): Promise<GetStatusOutput>;
@@ -1,9 +1,19 @@
1
1
  import { evaluateCostGovernor } from "../vendor/core/index.js";
2
2
  import { loadLoopRecordForStatus } from "./run-store.js";
3
3
  import { buildLoopPreview } from "./tool-support.js";
4
+ import { readRunControlState } from "./run-controls.js";
4
5
  export async function getStatusTool(input) {
5
6
  const resolved = await loadLoopRecordForStatus(input);
6
7
  const loop = resolved.loop;
8
+ const control = input.loopJson !== undefined
9
+ ? {
10
+ requestedState: "active",
11
+ approvalState: "not_required",
12
+ receipts: []
13
+ }
14
+ : await readRunControlState(input);
15
+ const latestEvent = loop.events?.at(-1);
16
+ const changedFiles = loop.artifacts?.filter((artifact) => artifact.kind === "diff").length ?? 0;
7
17
  const costState = evaluateCostGovernor({
8
18
  budget: loop.budget,
9
19
  cost: {
@@ -35,6 +45,14 @@ export async function getStatusTool(input) {
35
45
  },
36
46
  inspection: {
37
47
  loop: buildLoopPreview(loop)
48
+ },
49
+ live: {
50
+ phase: latestEvent?.lifecycleState ?? loop.lifecycleState,
51
+ pauseState: control.requestedState,
52
+ approvalState: control.approvalState,
53
+ commandsRun: loop.attempts.length,
54
+ filesTouched: changedFiles,
55
+ ...(loop.task?.verificationPlan?.[0] ? { verifierStep: loop.task.verificationPlan[0] } : {})
38
56
  }
39
57
  };
40
58
  }
@@ -1,4 +1,5 @@
1
1
  import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetVerificationResultsInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -9,6 +10,7 @@ export interface MartinGetVerificationResultsOutput {
9
10
  sourceKind: "file" | "loop_id" | "latest" | "runs_root";
10
11
  loop: ReturnType<typeof buildLoopPreview>;
11
12
  verification: ReturnType<typeof buildVerificationSummary>;
13
+ receiptIntegrity: ReceiptIntegritySummary;
12
14
  warnings: string[];
13
15
  }
14
16
  export declare function martinGetVerificationResultsTool(input: MartinGetVerificationResultsInput): Promise<MartinGetVerificationResultsOutput>;
@@ -1,4 +1,4 @@
1
- import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
1
+ import { buildLoopPreview, buildVerificationSummary, resolveReceiptIntegrity } from "./tool-support.js";
2
2
  import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
3
3
  export async function martinGetVerificationResultsTool(input) {
4
4
  const detail = await loadDetailedLoopRecord(input);
@@ -9,6 +9,7 @@ export async function martinGetVerificationResultsTool(input) {
9
9
  sourceKind: detail.sourceKind,
10
10
  loop: buildLoopPreview(detail.loop),
11
11
  verification,
12
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
12
13
  warnings: [...detail.warnings, ...verification.warnings]
13
14
  };
14
15
  }
@@ -0,0 +1,25 @@
1
+ export interface MartinLogsInput {
2
+ file?: string;
3
+ loopId?: string;
4
+ runsDir?: string;
5
+ latest?: boolean;
6
+ limit?: number;
7
+ }
8
+ export interface MartinLogsOutput {
9
+ source: string;
10
+ sourceKind: "file" | "loop_id" | "latest" | "runs_root";
11
+ loopId: string;
12
+ logCount: number;
13
+ live: {
14
+ lifecycleState: string;
15
+ pauseState: "active" | "paused" | "cancellation_requested";
16
+ approvalState: "not_required" | "resume_requested";
17
+ };
18
+ entries: Array<{
19
+ timestamp?: string;
20
+ source: "event" | "ledger" | "control";
21
+ kind: string;
22
+ payload: Record<string, unknown>;
23
+ }>;
24
+ }
25
+ export declare function martinLogsTool(input: MartinLogsInput): Promise<MartinLogsOutput>;
@@ -0,0 +1,49 @@
1
+ import { readRunControlState } from "./run-controls.js";
2
+ import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
3
+ export async function martinLogsTool(input) {
4
+ const detail = await loadDetailedLoopRecord(input);
5
+ const ledgerEvents = await readLedgerEvents(detail);
6
+ const controls = await readRunControlState(detail);
7
+ const limit = input.limit ?? 20;
8
+ const eventEntries = (detail.loop.events ?? []).map((event) => ({
9
+ timestamp: event.timestamp,
10
+ source: "event",
11
+ kind: event.type,
12
+ payload: event.payload ?? {}
13
+ }));
14
+ const ledgerEntries = ledgerEvents.map((event) => ({
15
+ timestamp: event.timestamp,
16
+ source: "ledger",
17
+ kind: event.kind,
18
+ payload: (event.payload ?? {})
19
+ }));
20
+ const controlEntries = controls.receipts.map((receipt) => ({
21
+ timestamp: receipt.requestedAt,
22
+ source: "control",
23
+ kind: `run.${receipt.action}`,
24
+ payload: {
25
+ controlId: receipt.controlId,
26
+ ...(receipt.reason ? { reason: receipt.reason } : {}),
27
+ ...(receipt.requestedBy ? { requestedBy: receipt.requestedBy } : {})
28
+ }
29
+ }));
30
+ const entries = [...eventEntries, ...ledgerEntries, ...controlEntries]
31
+ .sort((left, right) => {
32
+ const leftTime = left.timestamp ? new Date(left.timestamp).getTime() : 0;
33
+ const rightTime = right.timestamp ? new Date(right.timestamp).getTime() : 0;
34
+ return rightTime - leftTime;
35
+ })
36
+ .slice(0, limit);
37
+ return {
38
+ source: detail.source,
39
+ sourceKind: detail.sourceKind,
40
+ loopId: detail.loop.loopId,
41
+ logCount: entries.length,
42
+ live: {
43
+ lifecycleState: detail.loop.lifecycleState,
44
+ pauseState: controls.requestedState,
45
+ approvalState: controls.approvalState
46
+ },
47
+ entries
48
+ };
49
+ }
@@ -0,0 +1,20 @@
1
+ import { type MartinPlanProposal, type MartinPolicyPack } from "./workflow-governance.js";
2
+ export interface MartinPlanInput {
3
+ objective: string;
4
+ workingDirectory?: string;
5
+ context?: string;
6
+ verificationPlan?: string[];
7
+ allowedPaths?: string[];
8
+ deniedPaths?: string[];
9
+ policyPack?: MartinPolicyPack;
10
+ maxUsd?: number;
11
+ maxIterations?: number;
12
+ maxTokens?: number;
13
+ maxMinutes?: number;
14
+ maxFilesChanged?: number;
15
+ maxCommands?: number;
16
+ }
17
+ export interface MartinPlanOutput extends MartinPlanProposal {
18
+ workingDirectory: string;
19
+ }
20
+ export declare function martinPlanTool(input: MartinPlanInput): Promise<MartinPlanOutput>;
@@ -0,0 +1,10 @@
1
+ import { resolveSafeRepoRoot } from "../server-validation.js";
2
+ import { buildPlanProposal } from "./workflow-governance.js";
3
+ export async function martinPlanTool(input) {
4
+ const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
5
+ const proposal = buildPlanProposal(workingDirectory, input);
6
+ return {
7
+ workingDirectory,
8
+ ...proposal
9
+ };
10
+ }
@@ -0,0 +1,31 @@
1
+ import { type MartinRunDossierInput } from "./run-dossier.js";
2
+ export interface MartinPrSummaryOutput {
3
+ loopId: string;
4
+ title: string;
5
+ body: string;
6
+ grade: string;
7
+ score: number;
8
+ }
9
+ export interface MartinCreatePrInput extends MartinRunDossierInput {
10
+ title?: string;
11
+ base?: string;
12
+ execute?: boolean;
13
+ }
14
+ export interface MartinCreatePrOutput extends MartinPrSummaryOutput {
15
+ execute: boolean;
16
+ created: boolean;
17
+ url?: string;
18
+ branch?: string;
19
+ }
20
+ export interface MartinReviewPrInput extends MartinRunDossierInput {
21
+ prBody?: string;
22
+ }
23
+ export interface MartinReviewPrOutput {
24
+ loopId: string;
25
+ verdict: "approve_with_review" | "needs_changes" | "blocked";
26
+ findings: string[];
27
+ summary: string;
28
+ }
29
+ export declare function martinPrSummaryTool(input: MartinRunDossierInput): Promise<MartinPrSummaryOutput>;
30
+ export declare function martinCreatePrTool(input: MartinCreatePrInput): Promise<MartinCreatePrOutput>;
31
+ export declare function martinReviewPrTool(input: MartinReviewPrInput): Promise<MartinReviewPrOutput>;
@@ -0,0 +1,112 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { loadDetailedLoopRecord } from "./run-store.js";
3
+ import { martinRunDossierTool } from "./run-dossier.js";
4
+ import { martinEvalTool } from "./eval.js";
5
+ import { resolveTrustedLoopRepoRoot } from "../server-validation.js";
6
+ import { detectCliAvailability } from "./tool-support.js";
7
+ import { MartinToolError } from "./tool-errors.js";
8
+ export async function martinPrSummaryTool(input) {
9
+ const dossier = await martinRunDossierTool({ ...input, format: "github-pr" });
10
+ const evaluation = await martinEvalTool(input);
11
+ const title = `martin: ${trimForTitle(dossier.loop.objective)}`;
12
+ const body = dossier.rendered ?? "";
13
+ return {
14
+ loopId: dossier.loop.loopId,
15
+ title,
16
+ body,
17
+ grade: evaluation.grade,
18
+ score: evaluation.score
19
+ };
20
+ }
21
+ export async function martinCreatePrTool(input) {
22
+ const summary = await martinPrSummaryTool(input);
23
+ const detail = await loadDetailedLoopRecord(input);
24
+ const repoRoot = resolveTrustedLoopRepoRoot(detail.loop.task?.repoRoot);
25
+ const branch = readGitValue(repoRoot, ["branch", "--show-current"]);
26
+ const title = input.title?.trim() || summary.title;
27
+ if (!input.execute) {
28
+ return {
29
+ ...summary,
30
+ title,
31
+ execute: false,
32
+ created: false,
33
+ ...(branch ? { branch } : {})
34
+ };
35
+ }
36
+ const gh = detectCliAvailability("gh");
37
+ if (!gh.available) {
38
+ throw new MartinToolError("engine_unavailable", "GitHub CLI is not available on PATH.", {
39
+ category: "environment",
40
+ suggestion: "Install gh or rerun martin_create_pr with execute=false to preview the PR body.",
41
+ retryable: false
42
+ });
43
+ }
44
+ const args = ["pr", "create", "--title", title, "--body", summary.body];
45
+ if (input.base) {
46
+ args.push("--base", input.base);
47
+ }
48
+ const result = spawnSync("gh", args, {
49
+ cwd: repoRoot,
50
+ encoding: "utf8",
51
+ stdio: ["ignore", "pipe", "pipe"]
52
+ });
53
+ if (result.status !== 0) {
54
+ throw new MartinToolError("tool_execution_failed", "GitHub PR creation failed.", {
55
+ category: "transient",
56
+ suggestion: (result.stderr || result.stdout || "Check gh auth and branch state.").trim(),
57
+ retryable: false
58
+ });
59
+ }
60
+ const url = `${result.stdout ?? ""}`.split(/\r?\n/u).map((line) => line.trim()).find(Boolean);
61
+ return {
62
+ ...summary,
63
+ title,
64
+ execute: true,
65
+ created: true,
66
+ ...(url ? { url } : {}),
67
+ ...(branch ? { branch } : {})
68
+ };
69
+ }
70
+ export async function martinReviewPrTool(input) {
71
+ const summary = await martinPrSummaryTool(input);
72
+ const evaluation = await martinEvalTool(input);
73
+ const findings = [];
74
+ if (evaluation.grade === "blocked" || evaluation.grade === "insufficient_evidence") {
75
+ findings.push("Run evidence is not strong enough for a safe merge decision.");
76
+ }
77
+ if (evaluation.checks.verifier !== "passed") {
78
+ findings.push("Verifier evidence is not green.");
79
+ }
80
+ if (evaluation.checks.securityRisk !== "passed") {
81
+ findings.push("Risk score requires closer human review.");
82
+ }
83
+ if (input.prBody && !/MartinLoop Run Dossier/iu.test(input.prBody)) {
84
+ findings.push("PR body is missing the MartinLoop dossier section.");
85
+ }
86
+ const verdict = findings.length === 0
87
+ ? "approve_with_review"
88
+ : findings.some((finding) => /not strong enough|not green/iu.test(finding))
89
+ ? "blocked"
90
+ : "needs_changes";
91
+ return {
92
+ loopId: summary.loopId,
93
+ verdict,
94
+ findings,
95
+ summary: verdict === "approve_with_review"
96
+ ? "PR evidence looks reviewable."
97
+ : verdict === "needs_changes"
98
+ ? "PR needs changes before review is complete."
99
+ : "PR is blocked by evidence or verifier gaps."
100
+ };
101
+ }
102
+ function trimForTitle(value) {
103
+ return value.length > 60 ? `${value.slice(0, 57).trimEnd()}...` : value;
104
+ }
105
+ function readGitValue(cwd, args) {
106
+ const result = spawnSync("git", args, {
107
+ cwd,
108
+ encoding: "utf8",
109
+ stdio: ["ignore", "pipe", "pipe"]
110
+ });
111
+ return result.status === 0 ? result.stdout.trim() || undefined : undefined;
112
+ }
@@ -1,12 +1,19 @@
1
+ import { type CodexHostPlatform } from "../vendor/adapters/index.js";
1
2
  import { type MartinEngine } from "./tool-support.js";
3
+ import { buildPolicyPackDefinition, type MartinPlanProposal, type MartinPolicyPack, type MartinRiskAssessment, type MartinRunContract } from "./workflow-governance.js";
2
4
  export interface MartinPreflightInput {
3
5
  objective: string;
4
6
  workingDirectory?: string;
5
7
  engine?: MartinEngine;
6
8
  model?: string;
9
+ context?: string;
10
+ policyPack?: MartinPolicyPack;
7
11
  maxUsd?: number;
8
12
  maxIterations?: number;
9
13
  maxTokens?: number;
14
+ maxMinutes?: number;
15
+ maxFilesChanged?: number;
16
+ maxCommands?: number;
10
17
  verificationPlan?: string[];
11
18
  allowedPaths?: string[];
12
19
  deniedPaths?: string[];
@@ -17,8 +24,14 @@ export interface MartinPreflightOutput {
17
24
  ok: boolean;
18
25
  summary: string;
19
26
  warnings: string[];
27
+ scope: {
28
+ invocationRoot: string;
29
+ workingDirectory: string;
30
+ repoRoot: string;
31
+ runsRoot: string;
32
+ };
20
33
  readiness: {
21
- mode: "live" | "stub";
34
+ mode: "live" | "proof";
22
35
  liveMode: boolean;
23
36
  engineReady: boolean;
24
37
  };
@@ -46,6 +59,12 @@ export interface MartinPreflightOutput {
46
59
  detail: string;
47
60
  resolvedPath?: string;
48
61
  };
62
+ codexDiagnostics?: {
63
+ hostPlatform: CodexHostPlatform;
64
+ nativeInstallValid: boolean;
65
+ launchReady: boolean;
66
+ summary: string;
67
+ };
49
68
  runsRoot: string;
50
69
  pathScope: {
51
70
  repoRoot: string;
@@ -58,5 +77,9 @@ export interface MartinPreflightOutput {
58
77
  loopRecordPathPattern: string;
59
78
  };
60
79
  };
80
+ policy: ReturnType<typeof buildPolicyPackDefinition>;
81
+ risk: MartinRiskAssessment;
82
+ runContract: MartinRunContract;
83
+ plan: MartinPlanProposal;
61
84
  }
62
85
  export declare function martinPreflightTool(input: MartinPreflightInput): Promise<MartinPreflightOutput>;
@@ -1,12 +1,22 @@
1
+ import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
1
2
  import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
2
3
  import { resolveRunsRoot } from "../vendor/core/index.js";
3
4
  import { resolveSafeRepoRoot } from "../server-validation.js";
4
5
  import { formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
6
+ import { buildPlanProposal, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
5
7
  export async function martinPreflightTool(input) {
6
8
  const executionMode = resolveExecutionMode();
9
+ const workspaceRoot = resolveSafeRepoRoot();
7
10
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
11
+ const signals = inspectRepoSignals(workingDirectory);
8
12
  const engine = input.engine ?? "claude";
9
- const engineAvailability = getEngineAvailability(engine);
13
+ const engineAvailability = engine === "codex" ? resolveCliCommandAvailability("codex") : getEngineAvailability(engine);
14
+ const codexProbe = executionMode.liveMode && engine === "codex" && engineAvailability.available
15
+ ? probeCodexLaunch({
16
+ workingDirectory,
17
+ availability: engineAvailability
18
+ })
19
+ : undefined;
10
20
  const warnings = [];
11
21
  const allowedPaths = input.allowedPaths ?? [];
12
22
  const deniedPaths = input.deniedPaths ?? [];
@@ -18,11 +28,14 @@ export async function martinPreflightTool(input) {
18
28
  ...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
19
29
  };
20
30
  if (!executionMode.liveMode) {
21
- warnings.push("Stub mode is active; preflight only proves configuration shape, not live CLI readiness.");
31
+ warnings.push("Proof mode is active; preflight only proves configuration shape, not live CLI readiness.");
22
32
  }
23
33
  else if (!engineAvailability.available) {
24
34
  warnings.push(`Requested engine '${engine}' is not available on PATH.`);
25
35
  }
36
+ else if (engine === "codex" && codexProbe && !codexProbe.ok) {
37
+ warnings.push(codexProbe.summary);
38
+ }
26
39
  if ((input.verificationPlan?.length ?? 0) === 0) {
27
40
  warnings.push("No verificationPlan was provided; Martin can run, but completion confidence will be lower.");
28
41
  }
@@ -32,17 +45,30 @@ export async function martinPreflightTool(input) {
32
45
  if (overlappingScopes.length > 0) {
33
46
  warnings.push(`Some path patterns appear in both allowedPaths and deniedPaths: ${overlappingScopes.join(", ")}.`);
34
47
  }
35
- const ok = !executionMode.liveMode || engineAvailability.available;
48
+ const plan = buildPlanProposal(workingDirectory, input);
49
+ const runContract = buildRunContract(workingDirectory, input);
50
+ const policy = buildPolicyPackDefinition(input.policyPack, signals);
51
+ const engineReady = !executionMode.liveMode ||
52
+ (engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
53
+ const ok = engineReady;
36
54
  return {
37
55
  ok,
38
56
  summary: ok
39
- ? `Preflight ready for ${engine} in ${workingDirectory} with a ${formatUsd(budget.maxUsd)} budget cap.`
40
- : `Preflight blocked: ${engine} is not available for live execution.`,
57
+ ? `Preflight ready for ${engine} in ${workingDirectory} with a ${formatUsd(budget.maxUsd)} budget cap and ${runContract.risk.level} risk.`
58
+ : `Preflight blocked: ${engine === "codex" && codexProbe && !codexProbe.ok
59
+ ? codexProbe.summary
60
+ : `${engine} is not available for live execution.`}`,
41
61
  warnings,
62
+ scope: {
63
+ invocationRoot: workspaceRoot,
64
+ workingDirectory,
65
+ repoRoot: workingDirectory,
66
+ runsRoot: resolveRunsRoot(process.env)
67
+ },
42
68
  readiness: {
43
69
  mode: executionMode.mode,
44
70
  liveMode: executionMode.liveMode,
45
- engineReady: !executionMode.liveMode || engineAvailability.available
71
+ engineReady
46
72
  },
47
73
  normalized: {
48
74
  objective: input.objective,
@@ -65,6 +91,16 @@ export async function martinPreflightTool(input) {
65
91
  ? { resolvedPath: engineAvailability.resolvedPath }
66
92
  : {})
67
93
  },
94
+ ...(codexProbe
95
+ ? {
96
+ codexDiagnostics: {
97
+ hostPlatform: codexProbe.diagnosis.hostPlatform,
98
+ nativeInstallValid: codexProbe.diagnosis.nativeInstallValid,
99
+ launchReady: codexProbe.ok,
100
+ summary: codexProbe.summary
101
+ }
102
+ }
103
+ : {}),
68
104
  runsRoot: resolveRunsRoot(process.env),
69
105
  pathScope: {
70
106
  repoRoot: workingDirectory,
@@ -76,6 +112,10 @@ export async function martinPreflightTool(input) {
76
112
  runDirectoryPattern: "<runsRoot>/<loopId>/",
77
113
  loopRecordPathPattern: "<runsRoot>/<loopId>/loop-record.json"
78
114
  }
79
- }
115
+ },
116
+ policy,
117
+ risk: runContract.risk,
118
+ runContract,
119
+ plan
80
120
  };
81
121
  }
@@ -0,0 +1,36 @@
1
+ import { type DetailedLoopSource } from "./run-store.js";
2
+ export type MartinControlAction = "pause" | "cancel" | "continue";
3
+ export interface MartinRunControlRequestInput {
4
+ file?: string;
5
+ loopId?: string;
6
+ runsDir?: string;
7
+ latest?: boolean;
8
+ reason?: string;
9
+ requestedBy?: string;
10
+ }
11
+ export interface MartinRunControlReceipt {
12
+ controlId: string;
13
+ loopId: string;
14
+ action: MartinControlAction;
15
+ requestedAt: string;
16
+ reason?: string;
17
+ requestedBy?: string;
18
+ }
19
+ export interface MartinRunControlState {
20
+ requestedState: "active" | "paused" | "cancellation_requested";
21
+ latestReceipt?: MartinRunControlReceipt;
22
+ approvalState: "not_required" | "resume_requested";
23
+ receipts: MartinRunControlReceipt[];
24
+ receiptPath?: string;
25
+ }
26
+ export interface MartinRunControlOutput {
27
+ ok: boolean;
28
+ summary: string;
29
+ loopId: string;
30
+ requestedAction: MartinControlAction;
31
+ state: MartinRunControlState;
32
+ }
33
+ export declare function createRunControlReceipt(action: MartinControlAction, input: MartinRunControlRequestInput): Promise<MartinRunControlOutput>;
34
+ export declare function readRunControlState(detailOrInput: DetailedLoopSource | MartinRunControlRequestInput): Promise<MartinRunControlState>;
35
+ export declare function readControlReceipts(receiptPath: string): Promise<MartinRunControlReceipt[]>;
36
+ export declare function validateControlReason(value?: string): string | undefined;