@martinloop/mcp 0.3.4 → 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.
- package/dist/discovery-metadata.d.ts +6 -6
- package/dist/discovery-metadata.js +6 -0
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +63 -1
- package/dist/server.js +86 -0
- package/dist/tools/run-store.js +4 -4
- package/dist/tools/tool-support.d.ts +2 -0
- package/dist/tools/tool-support.js +16 -1
- package/dist/vendor/adapters/claude-cli.js +31 -2
- package/dist/vendor/adapters/cli-bridge.d.ts +10 -0
- package/dist/vendor/adapters/cli-bridge.js +30 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
- package/dist/vendor/adapters/codex-launcher.js +59 -2
- package/dist/vendor/contracts/execution-policy.d.ts +16 -0
- package/dist/vendor/contracts/execution-policy.js +9 -0
- package/dist/vendor/contracts/index.d.ts +28 -1
- package/dist/vendor/contracts/index.js +1 -0
- package/dist/vendor/core/index.d.ts +5 -3
- package/dist/vendor/core/index.js +93 -28
- package/dist/vendor/core/persistence/store.js +13 -0
- package/dist/vendor/core/policy.d.ts +13 -0
- package/dist/vendor/core/policy.js +13 -0
- package/dist/vendor/core/rollback.d.ts +4 -0
- package/dist/vendor/core/rollback.js +15 -0
- package/dist/vendor/core/routing.d.ts +40 -0
- package/dist/vendor/core/routing.js +185 -0
- package/dist/workflow-state.d.ts +16 -0
- package/dist/workflow-state.js +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -92,6 +92,29 @@ export interface LoopArtifact {
|
|
|
92
92
|
label: string;
|
|
93
93
|
uri: string;
|
|
94
94
|
}
|
|
95
|
+
export type CallStage = "routing" | "planning" | "execution" | "verification" | "retry" | "rollback" | "receipt";
|
|
96
|
+
export type AgentRole = "manager" | "router" | "planner" | "worker" | "verifier" | "reviewer" | "fixer" | "system";
|
|
97
|
+
export interface FirstDelta {
|
|
98
|
+
detected: boolean;
|
|
99
|
+
timestampMs?: number;
|
|
100
|
+
filePath?: string;
|
|
101
|
+
changeType?: "create" | "modify" | "delete" | "patch_proposed";
|
|
102
|
+
/** Elapsed ms from run start to first meaningful workspace change. */
|
|
103
|
+
timeToFirstDeltaMs?: number;
|
|
104
|
+
}
|
|
105
|
+
export interface RoutingEconomics {
|
|
106
|
+
preworkCostUsd: number;
|
|
107
|
+
executionCostUsd: number;
|
|
108
|
+
verificationCostUsd: number;
|
|
109
|
+
retryCostUsd: number;
|
|
110
|
+
totalCostUsd: number;
|
|
111
|
+
preworkBurnPct: number;
|
|
112
|
+
timeToFirstDeltaMs?: number;
|
|
113
|
+
firstDelta?: FirstDelta;
|
|
114
|
+
costPerAcceptedChange?: number;
|
|
115
|
+
routeRecommendation?: "same_route" | "direct_worker" | "manager_required" | "consensus_required";
|
|
116
|
+
routeRecommendationReason?: string;
|
|
117
|
+
}
|
|
95
118
|
export interface LoopAttempt {
|
|
96
119
|
attemptId: string;
|
|
97
120
|
index: number;
|
|
@@ -104,6 +127,8 @@ export interface LoopAttempt {
|
|
|
104
127
|
intervention?: InterventionType;
|
|
105
128
|
/** Actionable diagnosis from failure classification, injected into the next attempt's prompt. */
|
|
106
129
|
diagnosticHint?: string;
|
|
130
|
+
/** Which stage of the run lifecycle this attempt represents. */
|
|
131
|
+
callStage?: CallStage;
|
|
107
132
|
}
|
|
108
133
|
export interface LoopEvent {
|
|
109
134
|
eventId: string;
|
|
@@ -130,6 +155,7 @@ export interface LoopRecord {
|
|
|
130
155
|
updatedAt: string;
|
|
131
156
|
receiptScope?: ReceiptScope;
|
|
132
157
|
receiptIntegrity?: ReceiptIntegritySummary;
|
|
158
|
+
routingEconomics?: RoutingEconomics;
|
|
133
159
|
}
|
|
134
160
|
export interface LoopRecordDraft {
|
|
135
161
|
loopId?: string;
|
|
@@ -376,7 +402,8 @@ export interface RollbackOutcomeArtifact {
|
|
|
376
402
|
export { createGovernanceSnapshot } from "./governance.js";
|
|
377
403
|
export type { DestructiveActionPolicy, GovernanceSnapshot, PolicyProfile, TelemetryDestination } from "./governance.js";
|
|
378
404
|
export { cloneExecutionPolicy } from "./execution-policy.js";
|
|
379
|
-
export type { ExecutionPolicy, ExecutionPolicyCompileInput, ExecutionPolicyConfigInput, ExecutionPolicyDefaults, ExecutionPolicyProvenanceEntry, ExecutionPolicyRequestInput } from "./execution-policy.js";
|
|
405
|
+
export type { ExecutionPolicy, ExecutionPolicyCompileInput, ExecutionPolicyConfigInput, ExecutionPolicyDefaults, ExecutionPolicyProvenanceEntry, ExecutionPolicyRequestInput, RoutingPolicy, RoutingMode } from "./execution-policy.js";
|
|
406
|
+
export { DEFAULT_ROUTING_POLICY } from "./execution-policy.js";
|
|
380
407
|
export { cloneContextGraphSnapshot } from "./context-graph.js";
|
|
381
408
|
export type { ContextGraphBuildOptions, ContextGraphEdge, ContextGraphHit, ContextGraphNode, ContextGraphNodeKind, ContextGraphSnapshot, ContextQuery } from "./context-graph.js";
|
|
382
409
|
export { cloneIdentityAttestation, cloneIdentityClaims, cloneIdentityToken } from "./identity.js";
|
|
@@ -218,6 +218,7 @@ function hasText(value) {
|
|
|
218
218
|
}
|
|
219
219
|
export { createGovernanceSnapshot } from "./governance.js";
|
|
220
220
|
export { cloneExecutionPolicy } from "./execution-policy.js";
|
|
221
|
+
export { DEFAULT_ROUTING_POLICY } from "./execution-policy.js";
|
|
221
222
|
export { cloneContextGraphSnapshot } from "./context-graph.js";
|
|
222
223
|
export { cloneIdentityAttestation, cloneIdentityClaims, cloneIdentityToken } from "./identity.js";
|
|
223
224
|
export { cloneCircuitBreakDecision, cloneTrajectoryAssessment } from "./trajectory.js";
|
|
@@ -2,10 +2,10 @@ import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type F
|
|
|
2
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
|
|
3
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
4
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
5
|
-
import { captureRollbackBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
5
|
+
import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
6
6
|
import { type RunStore } from "./persistence/index.js";
|
|
7
|
-
export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase } from "../contracts/index.js";
|
|
8
|
-
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, restoreRollbackBoundary };
|
|
7
|
+
export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
|
|
8
|
+
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
9
9
|
export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
|
|
10
10
|
export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
|
|
11
11
|
export type { GroundingScanResult, GroundingViolation, GroundingViolationKind, RepoGroundingHit, RepoGroundingIndex } from "./grounding.js";
|
|
@@ -14,6 +14,8 @@ export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, ver
|
|
|
14
14
|
export type { LocalIdentityAuthority } from "./identity.js";
|
|
15
15
|
export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
16
16
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
17
|
+
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
18
|
+
export type { RouteDecision, RouteClassificationInput } from "./routing.js";
|
|
17
19
|
export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
18
20
|
export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
|
|
19
21
|
export { compilePromptPacket } from "./compiler.js";
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
3
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
4
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
5
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
6
|
-
import { captureRollbackBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
5
|
+
import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
7
6
|
import { compilePromptPacket } from "./compiler.js";
|
|
8
7
|
import { makeLedgerEvent, resolveRunsRoot, runDir } from "./persistence/index.js";
|
|
9
8
|
import { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
10
9
|
import { decideCircuitBreak } from "./trajectory.js";
|
|
11
|
-
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, restoreRollbackBoundary };
|
|
10
|
+
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
12
11
|
export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
|
|
13
12
|
export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
|
|
14
13
|
export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
15
14
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
15
|
+
// ─── Routing economics ─────────────────────────────────────────────────────
|
|
16
|
+
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
16
17
|
// ─── Context Integrity Pre-gate ──────────────────────────────────────────────
|
|
17
18
|
export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
18
19
|
// ─── Prompt packet compiler ──────────────────────────────────────────────────
|
|
@@ -263,6 +264,13 @@ export async function runMartin(input) {
|
|
|
263
264
|
// Explicit PolicyPhase state machine — starts at GATHER, advances per attempt
|
|
264
265
|
let currentPhase = "GATHER";
|
|
265
266
|
let phaseRetryCount = 0;
|
|
267
|
+
// Routing economics: track first meaningful workspace delta
|
|
268
|
+
const runStartMs = Date.now();
|
|
269
|
+
let firstDeltaDetected = false;
|
|
270
|
+
let firstDeltaTimestampMs;
|
|
271
|
+
let firstDeltaFilePath;
|
|
272
|
+
let firstDeltaChangeType;
|
|
273
|
+
let preDeltaCostUsd = 0;
|
|
266
274
|
while (loop.attempts.length < loop.budget.maxIterations) {
|
|
267
275
|
const distilled = distillContext(loop, {
|
|
268
276
|
maxRecentAttempts: useCompressedContext ? 1 : (input.maxRecentAttempts ?? 3)
|
|
@@ -647,8 +655,22 @@ export async function runMartin(input) {
|
|
|
647
655
|
}));
|
|
648
656
|
}
|
|
649
657
|
const changedFiles = tracksWorkspaceMutations
|
|
650
|
-
? resolveChangedFiles(result, request.context.repoRoot)
|
|
658
|
+
? resolveChangedFiles(result, request.context.repoRoot, rollbackBoundary)
|
|
651
659
|
: [];
|
|
660
|
+
// Routing economics: detect first meaningful workspace delta
|
|
661
|
+
if (!firstDeltaDetected && changedFiles.length > 0) {
|
|
662
|
+
const meaningfulFile = changedFiles.find((f) => !isMartinLoopMetadata(f));
|
|
663
|
+
if (meaningfulFile) {
|
|
664
|
+
firstDeltaDetected = true;
|
|
665
|
+
firstDeltaTimestampMs = Date.now();
|
|
666
|
+
firstDeltaFilePath = meaningfulFile;
|
|
667
|
+
firstDeltaChangeType = "modify";
|
|
668
|
+
preDeltaCostUsd = roundUsd(loop.cost.actualUsd - getUsageUsd(result.usage));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (!firstDeltaDetected) {
|
|
672
|
+
preDeltaCostUsd = loop.cost.actualUsd;
|
|
673
|
+
}
|
|
652
674
|
// Evidence is only reliable when the adapter explicitly reported files OR git actually
|
|
653
675
|
// returned a non-empty list. A repoRoot alone is insufficient — git may fail (e.g. not
|
|
654
676
|
// a git repo) and silently return [], which would falsely trigger no_code_change.
|
|
@@ -1071,7 +1093,8 @@ export async function runMartin(input) {
|
|
|
1071
1093
|
payload: createRunExitPayload(decision)
|
|
1072
1094
|
}));
|
|
1073
1095
|
}
|
|
1074
|
-
const
|
|
1096
|
+
const routingInput = { runStartMs, firstDeltaDetected, firstDeltaTimestampMs, firstDeltaFilePath, firstDeltaChangeType, preDeltaCostUsd };
|
|
1097
|
+
const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory, routingInput);
|
|
1075
1098
|
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
1076
1099
|
return {
|
|
1077
1100
|
loop: finalizedLoop,
|
|
@@ -1092,7 +1115,8 @@ export async function runMartin(input) {
|
|
|
1092
1115
|
payload: createRunExitPayload(decision)
|
|
1093
1116
|
}));
|
|
1094
1117
|
}
|
|
1095
|
-
const
|
|
1118
|
+
const routingInput = { runStartMs, firstDeltaDetected, firstDeltaTimestampMs, firstDeltaFilePath, firstDeltaChangeType, preDeltaCostUsd };
|
|
1119
|
+
const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory, routingInput);
|
|
1096
1120
|
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
1097
1121
|
return {
|
|
1098
1122
|
loop: finalizedLoop,
|
|
@@ -1142,19 +1166,78 @@ function safetyLeashReasonCode(decision, safetySurface) {
|
|
|
1142
1166
|
return `${safetySurface}_safety_block`;
|
|
1143
1167
|
}
|
|
1144
1168
|
}
|
|
1145
|
-
function finalizeLoop(loop, decision, timestamp, idFactory) {
|
|
1169
|
+
function finalizeLoop(loop, decision, timestamp, idFactory, routingEconomicsInput) {
|
|
1146
1170
|
const finalized = appendLoopEvent(loop, {
|
|
1147
1171
|
type: "run.completed",
|
|
1148
1172
|
lifecycleState: decision.lifecycleState,
|
|
1149
1173
|
payload: createRunExitPayload(decision)
|
|
1150
1174
|
}, { now: timestamp, idFactory });
|
|
1175
|
+
const routingEconomics = routingEconomicsInput
|
|
1176
|
+
? buildRoutingEconomics(finalized, routingEconomicsInput, decision)
|
|
1177
|
+
: undefined;
|
|
1151
1178
|
return {
|
|
1152
1179
|
...finalized,
|
|
1153
1180
|
status: decision.status,
|
|
1154
1181
|
lifecycleState: decision.lifecycleState,
|
|
1155
|
-
updatedAt: timestamp
|
|
1182
|
+
updatedAt: timestamp,
|
|
1183
|
+
...(routingEconomics ? { routingEconomics } : {})
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function buildRoutingEconomics(loop, input, decision) {
|
|
1187
|
+
const totalCost = loop.cost.actualUsd;
|
|
1188
|
+
const preworkCost = roundUsd(Math.max(input.preDeltaCostUsd, 0));
|
|
1189
|
+
const executionCost = roundUsd(Math.max(totalCost - preworkCost, 0));
|
|
1190
|
+
const preworkBurnPct = totalCost > 0 ? Math.round((preworkCost / totalCost) * 100) : 0;
|
|
1191
|
+
const timeToFirstDeltaMs = input.firstDeltaTimestampMs
|
|
1192
|
+
? input.firstDeltaTimestampMs - input.runStartMs
|
|
1193
|
+
: undefined;
|
|
1194
|
+
const accepted = decision.lifecycleState === "completed";
|
|
1195
|
+
// Route recommendation based on prework burn
|
|
1196
|
+
let routeRecommendation;
|
|
1197
|
+
let routeRecommendationReason;
|
|
1198
|
+
if (preworkBurnPct > 50) {
|
|
1199
|
+
routeRecommendation = "direct_worker";
|
|
1200
|
+
routeRecommendationReason = `${String(preworkBurnPct)}% of spend was pre-work coordination. Use direct execution for similar tasks.`;
|
|
1201
|
+
}
|
|
1202
|
+
else if (preworkBurnPct > 30) {
|
|
1203
|
+
routeRecommendation = "direct_worker";
|
|
1204
|
+
routeRecommendationReason = `${String(preworkBurnPct)}% pre-work burn is high. Consider direct execution.`;
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
routeRecommendation = "same_route";
|
|
1208
|
+
routeRecommendationReason = `Pre-work burn at ${String(preworkBurnPct)}% is within acceptable range.`;
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
preworkCostUsd: preworkCost,
|
|
1212
|
+
executionCostUsd: executionCost,
|
|
1213
|
+
verificationCostUsd: 0,
|
|
1214
|
+
retryCostUsd: 0,
|
|
1215
|
+
totalCostUsd: totalCost,
|
|
1216
|
+
preworkBurnPct,
|
|
1217
|
+
timeToFirstDeltaMs,
|
|
1218
|
+
firstDelta: {
|
|
1219
|
+
detected: input.firstDeltaDetected,
|
|
1220
|
+
...(input.firstDeltaTimestampMs ? { timestampMs: input.firstDeltaTimestampMs } : {}),
|
|
1221
|
+
...(input.firstDeltaFilePath ? { filePath: input.firstDeltaFilePath } : {}),
|
|
1222
|
+
...(input.firstDeltaChangeType ? { changeType: input.firstDeltaChangeType } : {}),
|
|
1223
|
+
...(timeToFirstDeltaMs !== undefined ? { timeToFirstDeltaMs } : {})
|
|
1224
|
+
},
|
|
1225
|
+
...(accepted ? { costPerAcceptedChange: totalCost } : {}),
|
|
1226
|
+
routeRecommendation,
|
|
1227
|
+
routeRecommendationReason
|
|
1156
1228
|
};
|
|
1157
1229
|
}
|
|
1230
|
+
const MARTINLOOP_METADATA_PATTERNS = [
|
|
1231
|
+
/^PROGRESS\.md$/u,
|
|
1232
|
+
/^\.martin\//u,
|
|
1233
|
+
/\.lock$/u,
|
|
1234
|
+
/^node_modules\//u,
|
|
1235
|
+
/^\.git\//u,
|
|
1236
|
+
/^\.cache\//u,
|
|
1237
|
+
];
|
|
1238
|
+
function isMartinLoopMetadata(filePath) {
|
|
1239
|
+
return MARTINLOOP_METADATA_PATTERNS.some((p) => p.test(filePath));
|
|
1240
|
+
}
|
|
1158
1241
|
async function persistLoopRecordIfSupported(store, loop) {
|
|
1159
1242
|
await store?.writeLoopRecord?.(loop.loopId, loop);
|
|
1160
1243
|
}
|
|
@@ -1190,29 +1273,11 @@ function mergeCostProvenance(previous, current) {
|
|
|
1190
1273
|
}
|
|
1191
1274
|
return COST_PROVENANCE_RANK[current] < COST_PROVENANCE_RANK[previous] ? current : previous;
|
|
1192
1275
|
}
|
|
1193
|
-
function resolveChangedFiles(result, repoRoot) {
|
|
1276
|
+
function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
1194
1277
|
if (result.execution?.changedFiles !== undefined) {
|
|
1195
1278
|
return result.execution.changedFiles;
|
|
1196
1279
|
}
|
|
1197
|
-
|
|
1198
|
-
return [];
|
|
1199
|
-
}
|
|
1200
|
-
try {
|
|
1201
|
-
const diff = spawnSync("git", ["diff", "--name-only", "HEAD", "--", "."], {
|
|
1202
|
-
cwd: repoRoot,
|
|
1203
|
-
encoding: "utf8"
|
|
1204
|
-
});
|
|
1205
|
-
if (diff.status !== 0 || typeof diff.stdout !== "string") {
|
|
1206
|
-
return [];
|
|
1207
|
-
}
|
|
1208
|
-
return diff.stdout
|
|
1209
|
-
.split(/\r?\n/u)
|
|
1210
|
-
.map((entry) => entry.trim())
|
|
1211
|
-
.filter(Boolean);
|
|
1212
|
-
}
|
|
1213
|
-
catch {
|
|
1214
|
-
return [];
|
|
1215
|
-
}
|
|
1280
|
+
return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
|
|
1216
1281
|
}
|
|
1217
1282
|
function buildPatchDiff(result, changedFiles) {
|
|
1218
1283
|
// Use structured diff stats to build a minimal diff header if no raw diff is available
|
|
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { writeReceiptIntegrityMaterial } from "./integrity.js";
|
|
6
6
|
// ─── FileRunStore implementation ─────────────────────────────────────────────
|
|
7
|
+
const RUN_INDEX_FILENAME = "run-index.ndjson";
|
|
7
8
|
export function resolveRunsRoot(env = process.env) {
|
|
8
9
|
return env["MARTIN_RUNS_DIR"]?.trim() ??
|
|
9
10
|
join(homedir(), ".martin", "runs");
|
|
@@ -84,6 +85,7 @@ export function createFileRunStore(options = {}) {
|
|
|
84
85
|
const dir = runDir(runsRoot, runId);
|
|
85
86
|
await mkdir(dir, { recursive: true });
|
|
86
87
|
await writeJsonFile(join(dir, "loop-record.json"), loop);
|
|
88
|
+
await appendRunIndexRecord(runsRoot, loop);
|
|
87
89
|
const ledgerRaw = await readFile(join(dir, "ledger.jsonl"), "utf8").catch(() => "");
|
|
88
90
|
const ledgerEntries = ledgerRaw
|
|
89
91
|
.split(/\r?\n/u)
|
|
@@ -110,3 +112,14 @@ export function createFileRunStore(options = {}) {
|
|
|
110
112
|
async function writeJsonFile(path, value) {
|
|
111
113
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
112
114
|
}
|
|
115
|
+
async function appendRunIndexRecord(runsRoot, loop) {
|
|
116
|
+
const line = JSON.stringify({
|
|
117
|
+
loopId: loop.loopId,
|
|
118
|
+
workspaceId: loop.workspaceId,
|
|
119
|
+
projectId: loop.projectId,
|
|
120
|
+
status: loop.status,
|
|
121
|
+
lifecycleState: loop.lifecycleState,
|
|
122
|
+
updatedAt: loop.updatedAt
|
|
123
|
+
});
|
|
124
|
+
await appendFile(join(runsRoot, RUN_INDEX_FILENAME), `${line}\n`, "utf8");
|
|
125
|
+
}
|
|
@@ -74,6 +74,19 @@ export declare function evaluateCostGovernor(input: {
|
|
|
74
74
|
cost: LoopCost;
|
|
75
75
|
attemptsUsed: number;
|
|
76
76
|
}): CostGovernorState;
|
|
77
|
+
export interface CostPerOutcome {
|
|
78
|
+
costPerAcceptedChange: number | undefined;
|
|
79
|
+
costPerAttempt: number;
|
|
80
|
+
acceptanceRate: number;
|
|
81
|
+
wastedCoordinationUsd: number;
|
|
82
|
+
}
|
|
83
|
+
export declare function calculateCostPerOutcome(input: {
|
|
84
|
+
totalCostUsd: number;
|
|
85
|
+
preworkCostUsd: number;
|
|
86
|
+
attemptCount: number;
|
|
87
|
+
accepted: boolean;
|
|
88
|
+
verificationPassed: boolean;
|
|
89
|
+
}): CostPerOutcome;
|
|
77
90
|
export declare function inferExit(input: {
|
|
78
91
|
loop: {
|
|
79
92
|
budget: LoopBudget;
|
|
@@ -196,6 +196,19 @@ export function evaluateCostGovernor(input) {
|
|
|
196
196
|
remainingTokens
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
|
+
export function calculateCostPerOutcome(input) {
|
|
200
|
+
const costPerAttempt = input.attemptCount > 0
|
|
201
|
+
? roundUsd(input.totalCostUsd / input.attemptCount)
|
|
202
|
+
: 0;
|
|
203
|
+
return {
|
|
204
|
+
costPerAcceptedChange: input.accepted ? input.totalCostUsd : undefined,
|
|
205
|
+
costPerAttempt,
|
|
206
|
+
acceptanceRate: input.accepted ? 1 : 0,
|
|
207
|
+
wastedCoordinationUsd: input.accepted
|
|
208
|
+
? roundUsd(input.preworkCostUsd)
|
|
209
|
+
: roundUsd(input.totalCostUsd)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
199
212
|
export function inferExit(input) {
|
|
200
213
|
if (input.lastResult.status === "completed" && input.lastResult.verification.passed) {
|
|
201
214
|
return {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { PatchDecision, RollbackBoundaryArtifact, RollbackOutcomeArtifact } from "../contracts/index.js";
|
|
2
|
+
export declare function listAttemptChangedFilesSinceBoundary(input: {
|
|
3
|
+
repoRoot?: string;
|
|
4
|
+
boundary?: RollbackBoundaryArtifact;
|
|
5
|
+
}): string[];
|
|
2
6
|
export declare function captureRollbackBoundary(input: {
|
|
3
7
|
repoRoot?: string;
|
|
4
8
|
capturedAt: string;
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
export function listAttemptChangedFilesSinceBoundary(input) {
|
|
5
|
+
if (!input.repoRoot) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const repoState = readRepoState(input.repoRoot);
|
|
9
|
+
if (!input.boundary) {
|
|
10
|
+
return uniqueSorted([...repoState.trackedDirtyFiles, ...repoState.untrackedFiles]);
|
|
11
|
+
}
|
|
12
|
+
const baselineTracked = new Set(input.boundary.trackedDirtyFiles);
|
|
13
|
+
const baselineUntracked = new Set(input.boundary.untrackedFiles);
|
|
14
|
+
return uniqueSorted([
|
|
15
|
+
...repoState.trackedDirtyFiles.filter((filePath) => !baselineTracked.has(filePath)),
|
|
16
|
+
...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
4
19
|
export async function captureRollbackBoundary(input) {
|
|
5
20
|
if (!input.repoRoot) {
|
|
6
21
|
return undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route classification and compression for governed agent runs.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task needs manager/planner orchestration or can go
|
|
5
|
+
* directly to a worker, based on task complexity signals. Prevents
|
|
6
|
+
* wasted coordination cost (Pre Work Burn) on simple tasks.
|
|
7
|
+
*/
|
|
8
|
+
import type { RoutingMode, RoutingPolicy } from "../contracts/index.js";
|
|
9
|
+
export interface RouteDecision {
|
|
10
|
+
selectedMode: RoutingMode;
|
|
11
|
+
confidence: number;
|
|
12
|
+
expectedCostUsd: number;
|
|
13
|
+
expectedPreworkBurnPct: number;
|
|
14
|
+
reason: string[];
|
|
15
|
+
blockedSteps: string[];
|
|
16
|
+
compressed: boolean;
|
|
17
|
+
compressionSummary?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RouteClassificationInput {
|
|
20
|
+
objective: string;
|
|
21
|
+
verificationPlan: string[];
|
|
22
|
+
repoRoot?: string;
|
|
23
|
+
allowedPaths?: string[];
|
|
24
|
+
deniedPaths?: string[];
|
|
25
|
+
budgetUsd: number;
|
|
26
|
+
policy?: Partial<RoutingPolicy>;
|
|
27
|
+
/** Number of files the task references or is scoped to. */
|
|
28
|
+
scopedFileCount?: number;
|
|
29
|
+
/** Historical success rate for similar tasks with direct execution (0-1). */
|
|
30
|
+
historicalDirectSuccessRate?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function classifyRoute(input: RouteClassificationInput): RouteDecision;
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether a run's Pre Work Burn exceeds the routing policy limits.
|
|
35
|
+
* Returns a block reason if exceeded, undefined if within bounds.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluatePreworkBurnPolicy(preworkCostUsd: number, totalCostUsd: number, policy?: Partial<RoutingPolicy>): {
|
|
38
|
+
exceeded: boolean;
|
|
39
|
+
reason?: string;
|
|
40
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route classification and compression for governed agent runs.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task needs manager/planner orchestration or can go
|
|
5
|
+
* directly to a worker, based on task complexity signals. Prevents
|
|
6
|
+
* wasted coordination cost (Pre Work Burn) on simple tasks.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_ROUTING_POLICY } from "../contracts/index.js";
|
|
9
|
+
const SECURITY_KEYWORDS = [
|
|
10
|
+
"auth", "authentication", "authorization", "credential", "secret",
|
|
11
|
+
"token", "password", "oauth", "jwt", "session", "csrf", "xss",
|
|
12
|
+
"injection", "encryption", "certificate", "tls", "ssl", "permission"
|
|
13
|
+
];
|
|
14
|
+
const ARCHITECTURE_KEYWORDS = [
|
|
15
|
+
"refactor", "architecture", "redesign", "restructure", "migration",
|
|
16
|
+
"database schema", "api design", "service boundary", "microservice",
|
|
17
|
+
"event sourcing", "cqrs", "distributed"
|
|
18
|
+
];
|
|
19
|
+
const MIGRATION_KEYWORDS = [
|
|
20
|
+
"migration", "migrate", "upgrade", "downgrade", "schema change",
|
|
21
|
+
"data migration", "database migration", "breaking change"
|
|
22
|
+
];
|
|
23
|
+
const MULTI_FILE_KEYWORDS = [
|
|
24
|
+
"across", "all files", "every file", "codebase-wide", "global",
|
|
25
|
+
"rename everywhere", "update all", "system-wide"
|
|
26
|
+
];
|
|
27
|
+
function extractComplexitySignals(input) {
|
|
28
|
+
const lower = input.objective.toLowerCase();
|
|
29
|
+
return {
|
|
30
|
+
objectiveLength: input.objective.length,
|
|
31
|
+
fileCount: input.scopedFileCount ?? estimateFileCount(input),
|
|
32
|
+
hasSecurityKeywords: SECURITY_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
33
|
+
hasArchitectureKeywords: ARCHITECTURE_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
34
|
+
hasMigrationKeywords: MIGRATION_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
35
|
+
hasMultiFileKeywords: MULTI_FILE_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
36
|
+
verifierCount: input.verificationPlan.length,
|
|
37
|
+
budgetUsd: input.budgetUsd,
|
|
38
|
+
scopeRestricted: (input.allowedPaths?.length ?? 0) > 0 || (input.deniedPaths?.length ?? 0) > 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function estimateFileCount(input) {
|
|
42
|
+
const pathCount = (input.allowedPaths?.length ?? 0) + (input.deniedPaths?.length ?? 0);
|
|
43
|
+
if (pathCount > 0)
|
|
44
|
+
return pathCount;
|
|
45
|
+
// Heuristic: short objectives with specific file refs → 1-2 files
|
|
46
|
+
const fileRefs = input.objective.match(/[\w/.-]+\.\w{1,5}/g);
|
|
47
|
+
return fileRefs?.length ?? 1;
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Confidence scoring
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
function calculateDirectConfidence(signals, input) {
|
|
53
|
+
let confidence = 0.7; // Base: most tasks can go direct
|
|
54
|
+
// Boost for simple tasks
|
|
55
|
+
if (signals.objectiveLength < 200)
|
|
56
|
+
confidence += 0.1;
|
|
57
|
+
if (signals.fileCount <= 2)
|
|
58
|
+
confidence += 0.1;
|
|
59
|
+
if (signals.scopeRestricted)
|
|
60
|
+
confidence += 0.05;
|
|
61
|
+
if (signals.verifierCount === 1)
|
|
62
|
+
confidence += 0.05;
|
|
63
|
+
// Penalize for complex tasks
|
|
64
|
+
if (signals.hasSecurityKeywords)
|
|
65
|
+
confidence -= 0.25;
|
|
66
|
+
if (signals.hasArchitectureKeywords)
|
|
67
|
+
confidence -= 0.2;
|
|
68
|
+
if (signals.hasMigrationKeywords)
|
|
69
|
+
confidence -= 0.3;
|
|
70
|
+
if (signals.hasMultiFileKeywords)
|
|
71
|
+
confidence -= 0.15;
|
|
72
|
+
if (signals.objectiveLength > 500)
|
|
73
|
+
confidence -= 0.1;
|
|
74
|
+
if (signals.fileCount > 5)
|
|
75
|
+
confidence -= 0.15;
|
|
76
|
+
if (signals.budgetUsd > 10)
|
|
77
|
+
confidence -= 0.1;
|
|
78
|
+
// Historical evidence
|
|
79
|
+
if (input.historicalDirectSuccessRate !== undefined) {
|
|
80
|
+
confidence = confidence * 0.6 + input.historicalDirectSuccessRate * 0.4;
|
|
81
|
+
}
|
|
82
|
+
return Math.max(0, Math.min(1, confidence));
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Public API
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
export function classifyRoute(input) {
|
|
88
|
+
const policy = { ...DEFAULT_ROUTING_POLICY, ...input.policy };
|
|
89
|
+
const signals = extractComplexitySignals(input);
|
|
90
|
+
const directConfidence = calculateDirectConfidence(signals, input);
|
|
91
|
+
const reason = [];
|
|
92
|
+
const blockedSteps = [];
|
|
93
|
+
// Policy override: forced mode
|
|
94
|
+
if (policy.mode === "direct") {
|
|
95
|
+
return {
|
|
96
|
+
selectedMode: "direct",
|
|
97
|
+
confidence: 1,
|
|
98
|
+
expectedCostUsd: estimateDirectCost(input.budgetUsd),
|
|
99
|
+
expectedPreworkBurnPct: 5,
|
|
100
|
+
reason: ["Policy forces direct execution mode."],
|
|
101
|
+
blockedSteps: ["manager", "planner", "router", "consensus"],
|
|
102
|
+
compressed: true,
|
|
103
|
+
compressionSummary: "Policy: direct mode — skipping all orchestration."
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (policy.mode === "consensus") {
|
|
107
|
+
return {
|
|
108
|
+
selectedMode: "consensus",
|
|
109
|
+
confidence: 0.5,
|
|
110
|
+
expectedCostUsd: input.budgetUsd * 0.8,
|
|
111
|
+
expectedPreworkBurnPct: 35,
|
|
112
|
+
reason: ["Policy forces consensus mode."],
|
|
113
|
+
blockedSteps: [],
|
|
114
|
+
compressed: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Adaptive routing
|
|
118
|
+
if (directConfidence >= policy.skipOrchestrationIfConfidenceAbove) {
|
|
119
|
+
reason.push(`Direct confidence ${(directConfidence * 100).toFixed(0)}% exceeds threshold ${(policy.skipOrchestrationIfConfidenceAbove * 100).toFixed(0)}%.`);
|
|
120
|
+
if (signals.objectiveLength < 200)
|
|
121
|
+
reason.push("Short, focused objective.");
|
|
122
|
+
if (signals.fileCount <= 2)
|
|
123
|
+
reason.push(`Scoped to ${String(signals.fileCount)} file(s).`);
|
|
124
|
+
if (signals.scopeRestricted)
|
|
125
|
+
reason.push("Path scope is restricted.");
|
|
126
|
+
blockedSteps.push("manager", "consensus");
|
|
127
|
+
return {
|
|
128
|
+
selectedMode: "direct",
|
|
129
|
+
confidence: directConfidence,
|
|
130
|
+
expectedCostUsd: estimateDirectCost(input.budgetUsd),
|
|
131
|
+
expectedPreworkBurnPct: 8,
|
|
132
|
+
reason,
|
|
133
|
+
blockedSteps,
|
|
134
|
+
compressed: true,
|
|
135
|
+
compressionSummary: `Compressed from manager→planner→worker to direct worker. Estimated savings: ${String(Math.round(input.budgetUsd * 0.3 * 100) / 100)} USD.`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Complex task — needs orchestration
|
|
139
|
+
if (signals.hasSecurityKeywords)
|
|
140
|
+
reason.push("Security-sensitive task detected.");
|
|
141
|
+
if (signals.hasMigrationKeywords)
|
|
142
|
+
reason.push("Migration task detected.");
|
|
143
|
+
if (signals.hasArchitectureKeywords)
|
|
144
|
+
reason.push("Architectural scope detected.");
|
|
145
|
+
if (signals.fileCount > 5)
|
|
146
|
+
reason.push(`Touches ${String(signals.fileCount)}+ files.`);
|
|
147
|
+
const needsConsensus = signals.hasSecurityKeywords && signals.hasMigrationKeywords;
|
|
148
|
+
return {
|
|
149
|
+
selectedMode: needsConsensus ? "consensus" : "manager",
|
|
150
|
+
confidence: directConfidence,
|
|
151
|
+
expectedCostUsd: input.budgetUsd * (needsConsensus ? 0.8 : 0.6),
|
|
152
|
+
expectedPreworkBurnPct: needsConsensus ? 40 : 25,
|
|
153
|
+
reason,
|
|
154
|
+
blockedSteps: needsConsensus ? [] : ["consensus"],
|
|
155
|
+
compressed: false
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function estimateDirectCost(budgetUsd) {
|
|
159
|
+
// Direct execution typically uses 30-50% of budget
|
|
160
|
+
return Math.round(budgetUsd * 0.35 * 100) / 100;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Checks whether a run's Pre Work Burn exceeds the routing policy limits.
|
|
164
|
+
* Returns a block reason if exceeded, undefined if within bounds.
|
|
165
|
+
*/
|
|
166
|
+
export function evaluatePreworkBurnPolicy(preworkCostUsd, totalCostUsd, policy = {}) {
|
|
167
|
+
const resolved = { ...DEFAULT_ROUTING_POLICY, ...policy };
|
|
168
|
+
if (!resolved.enabled) {
|
|
169
|
+
return { exceeded: false };
|
|
170
|
+
}
|
|
171
|
+
if (preworkCostUsd > resolved.maxPreworkCostUsd) {
|
|
172
|
+
return {
|
|
173
|
+
exceeded: true,
|
|
174
|
+
reason: `Pre-work cost $${preworkCostUsd.toFixed(2)} exceeds routing policy cap of $${resolved.maxPreworkCostUsd.toFixed(2)}.`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const burnPct = totalCostUsd > 0 ? (preworkCostUsd / totalCostUsd) * 100 : 0;
|
|
178
|
+
if (burnPct > resolved.maxPreworkBudgetPct) {
|
|
179
|
+
return {
|
|
180
|
+
exceeded: true,
|
|
181
|
+
reason: `Pre Work Burn at ${burnPct.toFixed(0)}% exceeds routing policy cap of ${String(resolved.maxPreworkBudgetPct)}%.`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { exceeded: false };
|
|
185
|
+
}
|
package/dist/workflow-state.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { LoopBudget, ReceiptScope } from "./vendor/contracts/index.js";
|
|
2
2
|
type McpWorkflowStepName = "doctor" | "plan" | "preflight";
|
|
3
|
+
interface McpWorkflowReceipt {
|
|
4
|
+
step: McpWorkflowStepName;
|
|
5
|
+
recordedAt: string;
|
|
6
|
+
workingDirectory: string;
|
|
7
|
+
objectiveKey?: string;
|
|
8
|
+
engine?: string;
|
|
9
|
+
verificationPlanKey?: string;
|
|
10
|
+
scopeKey?: string;
|
|
11
|
+
pathScopeKey?: string;
|
|
12
|
+
budgetKey?: string;
|
|
13
|
+
}
|
|
14
|
+
interface WorkflowState {
|
|
15
|
+
version: 1;
|
|
16
|
+
mcp?: Partial<Record<McpWorkflowStepName, McpWorkflowReceipt>>;
|
|
17
|
+
}
|
|
3
18
|
export interface RecordMcpWorkflowStepInput {
|
|
4
19
|
runsRoot: string;
|
|
5
20
|
step: McpWorkflowStepName;
|
|
@@ -31,4 +46,5 @@ export interface McpRunGateResult {
|
|
|
31
46
|
}
|
|
32
47
|
export declare function recordMcpWorkflowStep(input: RecordMcpWorkflowStepInput): Promise<void>;
|
|
33
48
|
export declare function evaluateMcpRunGate(input: EvaluateMcpRunGateInput): Promise<McpRunGateResult>;
|
|
49
|
+
export declare function readWorkflowState(runsRoot: string): Promise<WorkflowState>;
|
|
34
50
|
export {};
|
package/dist/workflow-state.js
CHANGED
|
@@ -76,7 +76,7 @@ export async function evaluateMcpRunGate(input) {
|
|
|
76
76
|
missingSteps
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
async function readWorkflowState(runsRoot) {
|
|
79
|
+
export async function readWorkflowState(runsRoot) {
|
|
80
80
|
const statePath = resolveWorkflowStatePath(runsRoot);
|
|
81
81
|
try {
|
|
82
82
|
const raw = await readFile(statePath, "utf8");
|