@martinloop/mcp 0.8.6 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/tools/preflight.js +8 -4
- package/dist/vendor/adapters/claude-cli.js +26 -5
- package/dist/vendor/contracts/index.d.ts +21 -0
- package/dist/vendor/contracts/swarm.js +1 -1
- package/dist/vendor/core/index.d.ts +5 -4
- package/dist/vendor/core/index.js +80 -19
- package/dist/vendor/core/leash.d.ts +11 -1
- package/dist/vendor/core/leash.js +77 -3
- package/dist/vendor/core/output-directories.d.ts +7 -0
- package/dist/vendor/core/output-directories.js +36 -0
- package/dist/vendor/core/rollback.d.ts +24 -0
- package/dist/vendor/core/rollback.js +372 -19
- package/dist/vendor/core/test-integrity.d.ts +18 -0
- package/dist/vendor/core/test-integrity.js +91 -1
- package/dist/vendor/core/test-surface.d.ts +27 -0
- package/dist/vendor/core/test-surface.js +228 -0
- package/dist/vendor/core/verified-handoff.d.ts +1 -0
- package/dist/vendor/core/verified-handoff.js +6 -0
- package/dist/vendor/core/verifier-input.d.ts +2 -0
- package/dist/vendor/core/verifier-input.js +59 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.
|
|
1
|
+
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.7";
|
package/dist/package-version.js
CHANGED
package/dist/tools/preflight.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
|
|
2
|
-
import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, } from "../vendor/core/index.js";
|
|
2
|
+
import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, describeMalformedVerifier, } from "../vendor/core/index.js";
|
|
3
3
|
import { MARTIN_MCP_PACKAGE_VERSION } from "../package-version.js";
|
|
4
4
|
import { resolveSafeRepoRoot } from "../server-validation.js";
|
|
5
5
|
import { createSkippedCliAvailability, formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
|
|
@@ -27,6 +27,9 @@ export async function martinPreflightTool(input) {
|
|
|
27
27
|
})
|
|
28
28
|
: undefined;
|
|
29
29
|
const warnings = [];
|
|
30
|
+
const malformedVerifiers = (input.verificationPlan ?? [])
|
|
31
|
+
.map((command) => describeMalformedVerifier(command))
|
|
32
|
+
.filter((reason) => reason !== undefined);
|
|
30
33
|
const allowedPaths = input.allowedPaths ?? [];
|
|
31
34
|
const deniedPaths = input.deniedPaths ?? [];
|
|
32
35
|
const overlappingScopes = allowedPaths.filter((candidate) => deniedPaths.includes(candidate));
|
|
@@ -47,6 +50,7 @@ export async function martinPreflightTool(input) {
|
|
|
47
50
|
if ((input.verificationPlan?.length ?? 0) === 0) {
|
|
48
51
|
warnings.push("No verificationPlan was provided; Martin can run, but completion confidence will be lower.");
|
|
49
52
|
}
|
|
53
|
+
warnings.push(...malformedVerifiers);
|
|
50
54
|
if ((input.allowedPaths?.length ?? 0) === 0) {
|
|
51
55
|
warnings.push("No allowedPaths were provided; Martin will rely on the broader repo root scope.");
|
|
52
56
|
}
|
|
@@ -58,7 +62,7 @@ export async function martinPreflightTool(input) {
|
|
|
58
62
|
const policy = buildPolicyPackDefinition(input.policyPack, signals);
|
|
59
63
|
const engineReady = !executionMode.liveMode ||
|
|
60
64
|
(engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
|
|
61
|
-
const ok = engineReady;
|
|
65
|
+
const ok = engineReady && malformedVerifiers.length === 0;
|
|
62
66
|
const receiptScope = {
|
|
63
67
|
invocationRoot: workspaceRoot,
|
|
64
68
|
workingDirectory,
|
|
@@ -69,9 +73,9 @@ export async function martinPreflightTool(input) {
|
|
|
69
73
|
ok,
|
|
70
74
|
summary: ok
|
|
71
75
|
? `Preflight ready for ${engine} in ${workingDirectory} with a ${formatUsd(budget.maxUsd)} budget cap and ${runContract.risk.level} risk.`
|
|
72
|
-
: `Preflight blocked: ${engine === "codex" && codexProbe && !codexProbe.ok
|
|
76
|
+
: `Preflight blocked: ${malformedVerifiers[0] ?? (engine === "codex" && codexProbe && !codexProbe.ok
|
|
73
77
|
? codexProbe.summary
|
|
74
|
-
: `${engine} is not available for live execution.`}`,
|
|
78
|
+
: `${engine} is not available for live execution.`)}`,
|
|
75
79
|
warnings,
|
|
76
80
|
receiptScope,
|
|
77
81
|
scope: {
|
|
@@ -152,7 +152,7 @@ function extractClaudeObservedModel(stdout) {
|
|
|
152
152
|
return undefined;
|
|
153
153
|
}
|
|
154
154
|
function extractUsage(parsed, modelLabel, modelSource) {
|
|
155
|
-
if (!parsed
|
|
155
|
+
if (!parsed) {
|
|
156
156
|
return normalizeUsage({
|
|
157
157
|
actualUsd: 0,
|
|
158
158
|
tokensIn: 0,
|
|
@@ -160,11 +160,12 @@ function extractUsage(parsed, modelLabel, modelSource) {
|
|
|
160
160
|
provenance: "unavailable"
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
163
|
+
const usage = parsed.usage ?? {};
|
|
164
|
+
const promptTokens = usage.inputTokens ?? usage.input_tokens ?? 0;
|
|
165
|
+
const cacheCreationInputTokens = usage.cacheCreationInputTokens ?? usage.cache_creation_input_tokens ?? 0;
|
|
166
|
+
const cachedInputTokens = usage.cacheReadInputTokens ?? usage.cache_read_input_tokens ?? 0;
|
|
166
167
|
const tokensIn = promptTokens + cachedInputTokens + cacheCreationInputTokens;
|
|
167
|
-
const tokensOut =
|
|
168
|
+
const tokensOut = usage.outputTokens ?? usage.output_tokens ?? 0;
|
|
168
169
|
const pricingResolution = resolveModelPricing(modelLabel);
|
|
169
170
|
// Prefer Claude's own authoritative total_cost_usd (present on the final
|
|
170
171
|
// `result` event in json/stream-json output) over our pricing-table estimate,
|
|
@@ -653,6 +654,13 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate,
|
|
|
653
654
|
* the final `result` event, which carries the same `result`/`usage`/
|
|
654
655
|
* `total_cost_usd` fields as the single-blob `json` format.
|
|
655
656
|
*/
|
|
657
|
+
function findClaudeResultError(result) {
|
|
658
|
+
const errorSubtype = result.subtype?.startsWith("error") === true;
|
|
659
|
+
if (result.is_error !== true && !errorSubtype) {
|
|
660
|
+
return undefined;
|
|
661
|
+
}
|
|
662
|
+
return truncate(result.result ?? result.error ?? result.subtype ?? "unknown error", 500);
|
|
663
|
+
}
|
|
656
664
|
function parseStreamJsonResult(stdout) {
|
|
657
665
|
let lastResult;
|
|
658
666
|
for (const rawLine of stdout.split(/\r?\n/u)) {
|
|
@@ -880,6 +888,19 @@ export function createAgentCliAdapter(options) {
|
|
|
880
888
|
failure: { message: failureMessage }
|
|
881
889
|
};
|
|
882
890
|
}
|
|
891
|
+
// A Claude session that ended in an error (API failure, max turns) did no
|
|
892
|
+
// verified work: never let the verifier certify the untouched workspace.
|
|
893
|
+
const claudeResultError = parsed ? findClaudeResultError(parsed) : undefined;
|
|
894
|
+
if (claudeResultError !== undefined) {
|
|
895
|
+
const failureMessage = `${options.command} reported an error: ${claudeResultError}`;
|
|
896
|
+
return {
|
|
897
|
+
status: "failed",
|
|
898
|
+
summary: `${options.command} session ended in an error before verifier execution.`,
|
|
899
|
+
usage: extractUsage(parsed, options.model, options.model ? "explicit_override" : "agent_default"),
|
|
900
|
+
verification: { passed: false, summary: `Verifier not run: ${failureMessage}` },
|
|
901
|
+
failure: { message: failureMessage }
|
|
902
|
+
};
|
|
903
|
+
}
|
|
883
904
|
const producedStructuredCompletion = parsed?.result !== undefined ||
|
|
884
905
|
(codexJsonlResult !== undefined &&
|
|
885
906
|
(agentResult.exitCode === 0 || codexTurnCompleted(agentResult.stdout))) ||
|
|
@@ -51,6 +51,8 @@ export interface ApprovalPolicy {
|
|
|
51
51
|
migrations?: boolean;
|
|
52
52
|
configChanges?: boolean;
|
|
53
53
|
externalWrites?: boolean;
|
|
54
|
+
/** Allows an attempt to remove or rewrite lines in existing test files. */
|
|
55
|
+
testChanges?: boolean;
|
|
54
56
|
}
|
|
55
57
|
export interface VerificationStep {
|
|
56
58
|
/** Shell command to run. */
|
|
@@ -430,9 +432,23 @@ export interface RollbackBoundaryArtifact {
|
|
|
430
432
|
strategy: RollbackBoundaryStrategy;
|
|
431
433
|
capturedAt: string;
|
|
432
434
|
headRef?: string;
|
|
435
|
+
/** Branch HEAD pointed at when the boundary was captured; absent when detached. */
|
|
436
|
+
headBranch?: string;
|
|
433
437
|
trackedDirtyFiles: string[];
|
|
434
438
|
untrackedFiles: string[];
|
|
435
439
|
snapshots: RollbackFileSnapshot[];
|
|
440
|
+
/**
|
|
441
|
+
* Git-ignored files present at the boundary. Dependency and build output
|
|
442
|
+
* directories are recorded as a single collapsed `dir/` entry.
|
|
443
|
+
*/
|
|
444
|
+
ignoredFiles?: RollbackIgnoredFileStamp[];
|
|
445
|
+
}
|
|
446
|
+
export interface RollbackIgnoredFileStamp {
|
|
447
|
+
path: string;
|
|
448
|
+
size: number;
|
|
449
|
+
mtimeMs: number;
|
|
450
|
+
/** Content hash for non-output ignored files; contents are never persisted. */
|
|
451
|
+
sha256?: string;
|
|
436
452
|
}
|
|
437
453
|
export type RollbackOutcomeStatus = "restored" | "not_required" | "failed" | "unavailable";
|
|
438
454
|
export interface RollbackOutcomeArtifact {
|
|
@@ -450,6 +466,11 @@ export interface RollbackOutcomeArtifact {
|
|
|
450
466
|
};
|
|
451
467
|
restoredFiles: string[];
|
|
452
468
|
deletedFiles: string[];
|
|
469
|
+
/** Set when attempt-created commits were rewound to the boundary HEAD. */
|
|
470
|
+
rewoundHead?: {
|
|
471
|
+
from: string;
|
|
472
|
+
to: string;
|
|
473
|
+
};
|
|
453
474
|
error?: string;
|
|
454
475
|
}
|
|
455
476
|
export { createGovernanceSnapshot } from "./governance.js";
|
|
@@ -456,7 +456,7 @@ function budgetWidened(parent, child) {
|
|
|
456
456
|
&& (child.maxTokens === undefined || child.maxTokens > parent.maxTokens));
|
|
457
457
|
}
|
|
458
458
|
function approvalWidened(parent, child) {
|
|
459
|
-
const keys = ["dependencyAdds", "migrations", "configChanges", "externalWrites"];
|
|
459
|
+
const keys = ["dependencyAdds", "migrations", "configChanges", "externalWrites", "testChanges"];
|
|
460
460
|
return keys.some((key) => child[key] === true && parent[key] !== true);
|
|
461
461
|
}
|
|
462
462
|
function addDuplicateErrors(values, code, path, errors) {
|
|
@@ -2,14 +2,15 @@ import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffRece
|
|
|
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, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
5
|
+
import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
6
6
|
import { type RunStore } from "./persistence/index.js";
|
|
7
7
|
import { type ExitPolicyOverrides } from "./exits.js";
|
|
8
8
|
import { type ExitSignalSource } from "./exit-signal.js";
|
|
9
9
|
import { type VerifierExecutionBinding } from "./verified-handoff.js";
|
|
10
10
|
export type { AgentExecutionIntent, 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";
|
|
11
11
|
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
12
|
-
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
|
+
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary };
|
|
13
|
+
export { describeMalformedVerifier } from "./verifier-input.js";
|
|
13
14
|
export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
|
|
14
15
|
export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
|
|
15
16
|
export type { GroundingScanResult, GroundingViolation, GroundingViolationKind, RepoGroundingHit, RepoGroundingIndex } from "./grounding.js";
|
|
@@ -24,7 +25,7 @@ export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
|
24
25
|
export type { AvoidedUsdInput } from "./savings.js";
|
|
25
26
|
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
26
27
|
export type { RouteDecision, RouteClassificationInput } from "./routing.js";
|
|
27
|
-
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
|
|
28
|
+
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode, isTestPath, findWeakenedTestFiles } from "./test-integrity.js";
|
|
28
29
|
export type { TestIntegrityReport, TestIntegrityFinding } from "./test-integrity.js";
|
|
29
30
|
export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
|
|
30
31
|
export type { RepoStyleProfile } from "./repo-analyzer.js";
|
|
@@ -32,7 +33,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
32
33
|
export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
|
|
33
34
|
export { compilePromptPacket } from "./compiler.js";
|
|
34
35
|
export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
|
|
35
|
-
export { buildVerifiedHandoff, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
36
|
+
export { buildVerifiedHandoff, NO_FILE_CHANGES_WARNING, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
36
37
|
export type { BoundVerifierEvidence, BuildVerifiedHandoffInput, VerifierExecutionBinding, } from "./verified-handoff.js";
|
|
37
38
|
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
38
39
|
export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
@@ -2,7 +2,8 @@ import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, ap
|
|
|
2
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } 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, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
5
|
+
import { captureRollbackBoundary, findEscapingLinks, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
6
|
+
import { findWeakenedTestFiles } from "./test-integrity.js";
|
|
6
7
|
import { compilePromptPacket } from "./compiler.js";
|
|
7
8
|
import { makeLedgerEvent, resolveRunsRoot, runDir } from "./persistence/index.js";
|
|
8
9
|
import { createDefaultExitPolicy, evaluateExitPolicy, hashProgressState, toLegacyExitDecision } from "./exits.js";
|
|
@@ -14,7 +15,8 @@ import { decideCircuitBreak } from "./trajectory.js";
|
|
|
14
15
|
import { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
|
|
15
16
|
import { verifierActuallyPassed, } from "./verified-handoff.js";
|
|
16
17
|
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
17
|
-
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
18
|
+
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary };
|
|
19
|
+
export { describeMalformedVerifier } from "./verifier-input.js";
|
|
18
20
|
export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
|
|
19
21
|
export { ExternalOutcomeContractError, ExternalOutcomePolicyError, hashExternalOutcomeContract, verifyExternalOutcomes, writeExternalOutcomeEvidence, } from "./external-outcome.js";
|
|
20
22
|
export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
|
|
@@ -24,7 +26,7 @@ export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
|
24
26
|
// ─── Routing economics ─────────────────────────────────────────────────────
|
|
25
27
|
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
26
28
|
// ─── Test Integrity Validation ──────────────────────────────────────────────
|
|
27
|
-
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
|
|
29
|
+
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode, isTestPath, findWeakenedTestFiles } from "./test-integrity.js";
|
|
28
30
|
// ─── Repo Style Analyzer ────────────────────────────────────────────────────
|
|
29
31
|
export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
|
|
30
32
|
// ─── Context Integrity Pre-gate ──────────────────────────────────────────────
|
|
@@ -32,7 +34,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
32
34
|
// ─── Prompt packet compiler ──────────────────────────────────────────────────
|
|
33
35
|
export { compilePromptPacket } from "./compiler.js";
|
|
34
36
|
// ─── Verified Handoff builder ────────────────────────────────────────────────
|
|
35
|
-
export { buildVerifiedHandoff, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
37
|
+
export { buildVerifiedHandoff, NO_FILE_CHANGES_WARNING, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
36
38
|
// ─── Persistence (RunStore, LedgerEvent, FileRunStore) ──────────────────────
|
|
37
39
|
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
38
40
|
export { compileAndPersistContext } from "./persistence/index.js";
|
|
@@ -1073,9 +1075,37 @@ export async function runMartin(input) {
|
|
|
1073
1075
|
}
|
|
1074
1076
|
}));
|
|
1075
1077
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1078
|
+
let changedFiles = [];
|
|
1079
|
+
try {
|
|
1080
|
+
changedFiles = tracksWorkspaceMutations
|
|
1081
|
+
? resolveChangedFiles(result, request.context.repoRoot, rollbackBoundary)
|
|
1082
|
+
: [];
|
|
1083
|
+
}
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
for (const filePath of normalizeFileEffectPaths(result.execution?.changedFiles ?? [])) {
|
|
1086
|
+
touchedDuringExecution.add(filePath);
|
|
1087
|
+
}
|
|
1088
|
+
const observationExitDecision = {
|
|
1089
|
+
shouldExit: true,
|
|
1090
|
+
lifecycleState: "human_escalation",
|
|
1091
|
+
status: "exited",
|
|
1092
|
+
reason: `Workspace change observation became unavailable: ${fileEffectErrorMessage(error)}`,
|
|
1093
|
+
reasonCode: "workspace_observation_unavailable"
|
|
1094
|
+
};
|
|
1095
|
+
if (input.store) {
|
|
1096
|
+
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1097
|
+
kind: "run.exited",
|
|
1098
|
+
runId: loop.loopId,
|
|
1099
|
+
payload: createRunExitPayload(observationExitDecision)
|
|
1100
|
+
}));
|
|
1101
|
+
}
|
|
1102
|
+
await stopSignalMonitorAndDrainDiagnostics();
|
|
1103
|
+
const terminationEnvelope = await persistGuardStop(observationExitDecision);
|
|
1104
|
+
const finalizedLoop = await finalizeCurrentLoop(observationExitDecision, now());
|
|
1105
|
+
const finalizedWithEnvelope = { ...finalizedLoop, terminationEnvelope };
|
|
1106
|
+
await persistLoopRecordIfSupported(input.store, finalizedWithEnvelope);
|
|
1107
|
+
return { loop: finalizedWithEnvelope, decision: observationExitDecision };
|
|
1108
|
+
}
|
|
1079
1109
|
for (const filePath of normalizeFileEffectPaths(changedFiles)) {
|
|
1080
1110
|
touchedDuringExecution.add(filePath);
|
|
1081
1111
|
}
|
|
@@ -1097,9 +1127,17 @@ export async function runMartin(input) {
|
|
|
1097
1127
|
// returned a non-empty list. A repoRoot alone is insufficient — git may fail (e.g. not
|
|
1098
1128
|
// a git repo) and silently return [], which would falsely trigger no_code_change.
|
|
1099
1129
|
const changedFileEvidenceAvailable = result.execution?.changedFiles !== undefined || changedFiles.length > 0;
|
|
1130
|
+
const ignoredChanges = tracksWorkspaceMutations
|
|
1131
|
+
? listIgnoredFileChangesSinceBoundary({ repoRoot: request.context.repoRoot, boundary: rollbackBoundary })
|
|
1132
|
+
: { created: [], modified: [], deleted: [] };
|
|
1133
|
+
const escapingLinks = tracksWorkspaceMutations && request.context.repoRoot
|
|
1134
|
+
? findEscapingLinks(request.context.repoRoot, [...changedFiles, ...ignoredChanges.created])
|
|
1135
|
+
: [];
|
|
1100
1136
|
const filesystemDecision = evaluateFilesystemLeash({
|
|
1101
1137
|
repoRoot: request.context.repoRoot,
|
|
1102
1138
|
changedFiles,
|
|
1139
|
+
ignoredFiles: [...ignoredChanges.created, ...ignoredChanges.modified, ...ignoredChanges.deleted],
|
|
1140
|
+
escapingLinks,
|
|
1103
1141
|
allowedPaths: request.context.allowedPaths,
|
|
1104
1142
|
deniedPaths: request.context.deniedPaths,
|
|
1105
1143
|
mutationMode: request.context.mutationMode
|
|
@@ -1127,7 +1165,14 @@ export async function runMartin(input) {
|
|
|
1127
1165
|
repoRoot: request.context.repoRoot,
|
|
1128
1166
|
boundary: rollbackBoundary,
|
|
1129
1167
|
restoredAt: attemptCompletedAt,
|
|
1130
|
-
decision: patchDecision.decision
|
|
1168
|
+
decision: patchDecision.decision,
|
|
1169
|
+
// Attempt-created ignored violations are removed. Modified or deleted
|
|
1170
|
+
// baseline ignored violations are restored from the bounded in-memory snapshot.
|
|
1171
|
+
removePaths: violatingIgnoredPaths(filesystemDecision.violations, ignoredChanges.created),
|
|
1172
|
+
restoreIgnoredPaths: violatingIgnoredPaths(filesystemDecision.violations, [
|
|
1173
|
+
...ignoredChanges.modified,
|
|
1174
|
+
...ignoredChanges.deleted
|
|
1175
|
+
])
|
|
1131
1176
|
});
|
|
1132
1177
|
if (input.store) {
|
|
1133
1178
|
await input.store.writeAttemptArtifacts(loop.loopId, currentAttemptIndex, {
|
|
@@ -1176,8 +1221,16 @@ export async function runMartin(input) {
|
|
|
1176
1221
|
decision: filesystemExitDecision
|
|
1177
1222
|
};
|
|
1178
1223
|
}
|
|
1224
|
+
const weakenedTestFiles = tracksWorkspaceMutations && request.context.repoRoot && rollbackBoundary?.headRef
|
|
1225
|
+
? findWeakenedTestFiles({
|
|
1226
|
+
repoRoot: request.context.repoRoot,
|
|
1227
|
+
baseRef: rollbackBoundary.headRef,
|
|
1228
|
+
changedFiles
|
|
1229
|
+
})
|
|
1230
|
+
: [];
|
|
1179
1231
|
const changeApprovalDecision = evaluateChangeApprovalLeash({
|
|
1180
1232
|
changedFiles,
|
|
1233
|
+
weakenedTestFiles,
|
|
1181
1234
|
executionProfile: request.context.executionProfile,
|
|
1182
1235
|
approvalPolicy: request.context.approvalPolicy
|
|
1183
1236
|
});
|
|
@@ -1550,6 +1603,8 @@ function safetyLeashReasonCode(decision, safetySurface) {
|
|
|
1550
1603
|
return "migration_approval_required";
|
|
1551
1604
|
case "config_change_approval_required":
|
|
1552
1605
|
return "config_change_approval_required";
|
|
1606
|
+
case "test_change_approval_required":
|
|
1607
|
+
return "test_change_approval_required";
|
|
1553
1608
|
default:
|
|
1554
1609
|
return `${safetySurface}_safety_block`;
|
|
1555
1610
|
}
|
|
@@ -1595,7 +1650,8 @@ function observeRunFileEffects(input) {
|
|
|
1595
1650
|
try {
|
|
1596
1651
|
const netChangedAtCompletion = normalizeFileEffectPaths(listAttemptChangedFilesSinceBoundary({
|
|
1597
1652
|
repoRoot: input.repoRoot,
|
|
1598
|
-
boundary: input.boundary
|
|
1653
|
+
boundary: input.boundary,
|
|
1654
|
+
includeBaselineSnapshotChanges: true
|
|
1599
1655
|
}));
|
|
1600
1656
|
const netChanged = new Set(netChangedAtCompletion);
|
|
1601
1657
|
return {
|
|
@@ -1714,18 +1770,23 @@ function mergeCostProvenance(previous, current) {
|
|
|
1714
1770
|
}
|
|
1715
1771
|
return COST_PROVENANCE_RANK[current] < COST_PROVENANCE_RANK[previous] ? current : previous;
|
|
1716
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Git-backed observation that degrades the same way `resolveChangedFiles` does:
|
|
1775
|
+
* when Git becomes unavailable after the attempt, fall back only if the adapter
|
|
1776
|
+
* reported its own changed files; otherwise fail closed.
|
|
1777
|
+
*/
|
|
1778
|
+
function violatingIgnoredPaths(violations, ignoredPaths) {
|
|
1779
|
+
const violating = new Set(violations.map((violation) => violation.file?.replace(/\/$/u, "")).filter(Boolean));
|
|
1780
|
+
return ignoredPaths.filter((filePath) => violating.has(filePath.replace(/\/$/u, "")));
|
|
1781
|
+
}
|
|
1717
1782
|
function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
1718
1783
|
const adapterReported = normalizeFileEffectPaths(result.execution?.changedFiles ?? []);
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
return adapterReported;
|
|
1726
|
-
}
|
|
1727
|
-
throw error;
|
|
1728
|
-
}
|
|
1784
|
+
const gitObserved = listAttemptChangedFilesSinceBoundary({
|
|
1785
|
+
repoRoot,
|
|
1786
|
+
boundary: rollbackBoundary,
|
|
1787
|
+
includeBaselineSnapshotChanges: true
|
|
1788
|
+
});
|
|
1789
|
+
return normalizeFileEffectPaths([...adapterReported, ...gitObserved]);
|
|
1729
1790
|
}
|
|
1730
1791
|
function buildPatchDiff(result, changedFiles) {
|
|
1731
1792
|
if (result.execution?.patch) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApprovalPolicy, ExecutionProfile, LoopTask } from "../contracts/index.js";
|
|
2
2
|
export type SafetySurface = "command" | "filesystem" | "secret" | "spend" | "network" | "dependency";
|
|
3
|
-
export type SafetyViolationKind = "command_blocked" | "path_not_allowed" | "path_denied" | "path_outside_repo" | "network_blocked" | "dependency_approval_required" | "migration_approval_required" | "config_change_approval_required" | "secret_value" | "protected_path";
|
|
3
|
+
export type SafetyViolationKind = "command_blocked" | "path_not_allowed" | "path_denied" | "path_outside_repo" | "network_blocked" | "dependency_approval_required" | "migration_approval_required" | "config_change_approval_required" | "test_change_approval_required" | "secret_value" | "protected_path";
|
|
4
4
|
export interface SafetyViolation {
|
|
5
5
|
kind: SafetyViolationKind;
|
|
6
6
|
message: string;
|
|
@@ -30,6 +30,14 @@ export declare function evaluateVerificationLeash(task: Pick<LoopTask, "verifica
|
|
|
30
30
|
export declare function evaluateFilesystemLeash(input: {
|
|
31
31
|
repoRoot?: string;
|
|
32
32
|
changedFiles: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Git-ignored files the attempt created or modified. These are checked only
|
|
35
|
+
* against denied paths and protected secret files, never allowed paths, so
|
|
36
|
+
* dependency installs and build output stay unaffected.
|
|
37
|
+
*/
|
|
38
|
+
ignoredFiles?: string[];
|
|
39
|
+
/** Symlinks or junctions created inside the repo that resolve outside it. */
|
|
40
|
+
escapingLinks?: string[];
|
|
33
41
|
allowedPaths?: string[];
|
|
34
42
|
deniedPaths?: string[];
|
|
35
43
|
mutationMode?: "edit" | "read_only" | "verify_only";
|
|
@@ -43,6 +51,8 @@ export declare function resolveExecutionProfile(input: {
|
|
|
43
51
|
}): ResolvedExecutionProfile;
|
|
44
52
|
export declare function evaluateChangeApprovalLeash(input: {
|
|
45
53
|
changedFiles: string[];
|
|
54
|
+
/** Existing test files the attempt deleted or removed lines from. */
|
|
55
|
+
weakenedTestFiles?: string[];
|
|
46
56
|
executionProfile?: ExecutionProfile;
|
|
47
57
|
approvalPolicy?: ApprovalPolicy;
|
|
48
58
|
}): SafetyLeashDecision;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
import { isInsideDependencyOrBuildDirectory } from "./output-directories.js";
|
|
2
3
|
const BLOCKED_PATTERNS = [
|
|
3
4
|
/(^|[\s;&|`(])rm\s+-rf(\s|$)/iu,
|
|
4
5
|
/git\s+reset\s+--hard/iu,
|
|
@@ -186,8 +187,13 @@ export function evaluateVerificationLeash(task) {
|
|
|
186
187
|
};
|
|
187
188
|
}
|
|
188
189
|
export function evaluateFilesystemLeash(input) {
|
|
189
|
-
const violations = []
|
|
190
|
-
|
|
190
|
+
const violations = (input.escapingLinks ?? []).map((link) => ({
|
|
191
|
+
kind: "path_outside_repo",
|
|
192
|
+
file: link,
|
|
193
|
+
message: `Link inside the repo points outside the configured repo root: ${link}`
|
|
194
|
+
}));
|
|
195
|
+
const escapingLinks = new Set(input.escapingLinks ?? []);
|
|
196
|
+
for (const rawFile of input.changedFiles.filter((file) => !escapingLinks.has(file))) {
|
|
191
197
|
const normalized = normalizeChangedFile(rawFile, input.repoRoot);
|
|
192
198
|
if (normalized.outsideRepo) {
|
|
193
199
|
violations.push({
|
|
@@ -218,6 +224,10 @@ export function evaluateFilesystemLeash(input) {
|
|
|
218
224
|
continue;
|
|
219
225
|
}
|
|
220
226
|
const allowedPaths = input.allowedPaths ?? [];
|
|
227
|
+
if (isProtectedSecretFile(file) && !isExplicitlyAllowed(file, allowedPaths)) {
|
|
228
|
+
violations.push(protectedSecretViolation(file));
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
221
231
|
if (allowedPaths.length > 0 && !allowedPaths.some((pattern) => matchesPathPattern(file, pattern))) {
|
|
222
232
|
violations.push({
|
|
223
233
|
kind: "path_not_allowed",
|
|
@@ -226,6 +236,25 @@ export function evaluateFilesystemLeash(input) {
|
|
|
226
236
|
});
|
|
227
237
|
}
|
|
228
238
|
}
|
|
239
|
+
for (const rawFile of input.ignoredFiles ?? []) {
|
|
240
|
+
const file = normalizePathForMatching(rawFile);
|
|
241
|
+
if (!file) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if ((input.deniedPaths ?? []).some((pattern) => matchesPathPattern(file, pattern))) {
|
|
245
|
+
violations.push({
|
|
246
|
+
kind: "path_denied",
|
|
247
|
+
file,
|
|
248
|
+
message: `Git-ignored file matches a denylisted path: ${file}`
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (!isInsideDependencyOrBuildDirectory(file) &&
|
|
253
|
+
isProtectedSecretFile(file) &&
|
|
254
|
+
!isExplicitlyAllowed(file, input.allowedPaths ?? [])) {
|
|
255
|
+
violations.push(protectedSecretViolation(file));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
229
258
|
if (violations.length > 0) {
|
|
230
259
|
return {
|
|
231
260
|
allowed: false,
|
|
@@ -357,6 +386,15 @@ export function evaluateChangeApprovalLeash(input) {
|
|
|
357
386
|
});
|
|
358
387
|
}
|
|
359
388
|
}
|
|
389
|
+
if (!input.approvalPolicy?.testChanges) {
|
|
390
|
+
for (const file of input.weakenedTestFiles ?? []) {
|
|
391
|
+
violations.push({
|
|
392
|
+
kind: "test_change_approval_required",
|
|
393
|
+
file,
|
|
394
|
+
message: `Removing or rewriting existing test code requires approval: ${file}`
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
360
398
|
if (violations.length > 0) {
|
|
361
399
|
return {
|
|
362
400
|
allowed: false,
|
|
@@ -366,7 +404,7 @@ export function evaluateChangeApprovalLeash(input) {
|
|
|
366
404
|
profile: profile.name,
|
|
367
405
|
blockedCommands: [],
|
|
368
406
|
violations,
|
|
369
|
-
reason: "Safety leash blocked dependency, migration, or
|
|
407
|
+
reason: "Safety leash blocked dependency, migration, configuration, or existing-test changes that require approval."
|
|
370
408
|
};
|
|
371
409
|
}
|
|
372
410
|
return {
|
|
@@ -408,6 +446,42 @@ function normalizeChangedFile(file, repoRoot) {
|
|
|
408
446
|
outsideRepo: false
|
|
409
447
|
};
|
|
410
448
|
}
|
|
449
|
+
const SAFE_ENV_TEMPLATE_SUFFIXES = new Set(["example", "sample", "template", "dist", "defaults"]);
|
|
450
|
+
const PRIVATE_KEY_BASENAMES = new Set(["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"]);
|
|
451
|
+
const SECRET_MATERIAL_EXTENSIONS = new Set(["pem", "key", "p12", "pfx"]);
|
|
452
|
+
/** Files that conventionally hold credentials or private key material. */
|
|
453
|
+
function isProtectedSecretFile(file) {
|
|
454
|
+
const leaf = normalizePathForMatching(file).split("/").at(-1) ?? "";
|
|
455
|
+
if (leaf === ".env") {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (leaf.startsWith(".env.")) {
|
|
459
|
+
return !SAFE_ENV_TEMPLATE_SUFFIXES.has(leaf.slice(".env.".length).toLowerCase());
|
|
460
|
+
}
|
|
461
|
+
if (PRIVATE_KEY_BASENAMES.has(leaf)) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
const extension = leaf.includes(".") ? leaf.split(".").at(-1)?.toLowerCase() : undefined;
|
|
465
|
+
return extension !== undefined && SECRET_MATERIAL_EXTENSIONS.has(extension);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* A protected file counts as allowed only when an allowed-path entry targets it
|
|
469
|
+
* specifically; broad wildcards such as `**` or `src/*` do not approve secrets.
|
|
470
|
+
*/
|
|
471
|
+
function isExplicitlyAllowed(file, allowedPaths) {
|
|
472
|
+
return allowedPaths.some((pattern) => {
|
|
473
|
+
const leafPattern = normalizePathForMatching(pattern).split("/").at(-1) ?? "";
|
|
474
|
+
return !/^\*+$/u.test(leafPattern) && matchesPathPattern(file, pattern);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function protectedSecretViolation(file) {
|
|
478
|
+
return {
|
|
479
|
+
kind: "protected_path",
|
|
480
|
+
file,
|
|
481
|
+
message: `Changed file is a protected secret or key file: ${file}. ` +
|
|
482
|
+
"Name it explicitly in allowed paths to permit agent writes."
|
|
483
|
+
};
|
|
484
|
+
}
|
|
411
485
|
function matchesPathPattern(file, pattern) {
|
|
412
486
|
const normalizedFile = normalizePathForMatching(file);
|
|
413
487
|
const normalizedPattern = normalizePathForMatching(pattern);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory names that hold dependency installs or build output. Git-ignored
|
|
3
|
+
* content under them is tracked as a single collapsed entry and is not checked
|
|
4
|
+
* file by file for protected secrets.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEPENDENCY_AND_BUILD_DIRECTORIES: ReadonlySet<string>;
|
|
7
|
+
export declare function isInsideDependencyOrBuildDirectory(repoPath: string): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory names that hold dependency installs or build output. Git-ignored
|
|
3
|
+
* content under them is tracked as a single collapsed entry and is not checked
|
|
4
|
+
* file by file for protected secrets.
|
|
5
|
+
*/
|
|
6
|
+
export const DEPENDENCY_AND_BUILD_DIRECTORIES = new Set([
|
|
7
|
+
"node_modules",
|
|
8
|
+
"bower_components",
|
|
9
|
+
"jspm_packages",
|
|
10
|
+
".pnpm-store",
|
|
11
|
+
".yarn",
|
|
12
|
+
".venv",
|
|
13
|
+
"venv",
|
|
14
|
+
"__pycache__",
|
|
15
|
+
".mypy_cache",
|
|
16
|
+
".pytest_cache",
|
|
17
|
+
".ruff_cache",
|
|
18
|
+
".tox",
|
|
19
|
+
".gradle",
|
|
20
|
+
".next",
|
|
21
|
+
".nuxt",
|
|
22
|
+
".turbo",
|
|
23
|
+
".cache",
|
|
24
|
+
".parcel-cache",
|
|
25
|
+
"target",
|
|
26
|
+
"dist",
|
|
27
|
+
"build",
|
|
28
|
+
"out",
|
|
29
|
+
"coverage",
|
|
30
|
+
"vendor"
|
|
31
|
+
]);
|
|
32
|
+
export function isInsideDependencyOrBuildDirectory(repoPath) {
|
|
33
|
+
const segments = repoPath.replace(/\\/gu, "/").split("/").filter(Boolean);
|
|
34
|
+
// A trailing collapsed entry such as `node_modules/` is itself the directory.
|
|
35
|
+
return segments.some((segment) => DEPENDENCY_AND_BUILD_DIRECTORIES.has(segment));
|
|
36
|
+
}
|
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
import type { PatchDecision, RollbackBoundaryArtifact, RollbackOutcomeArtifact } from "../contracts/index.js";
|
|
2
|
+
export interface IgnoredFileChanges {
|
|
3
|
+
created: string[];
|
|
4
|
+
modified: string[];
|
|
5
|
+
deleted: string[];
|
|
6
|
+
}
|
|
2
7
|
export declare class RollbackStateUnavailableError extends Error {
|
|
3
8
|
constructor(message: string);
|
|
4
9
|
}
|
|
5
10
|
export declare function listAttemptChangedFilesSinceBoundary(input: {
|
|
6
11
|
repoRoot?: string;
|
|
7
12
|
boundary?: RollbackBoundaryArtifact;
|
|
13
|
+
/** Include content changes to dirty/untracked paths that existed at the boundary. */
|
|
14
|
+
includeBaselineSnapshotChanges?: boolean;
|
|
8
15
|
}): string[];
|
|
9
16
|
export declare function captureRollbackBoundary(input: {
|
|
10
17
|
repoRoot?: string;
|
|
11
18
|
capturedAt: string;
|
|
12
19
|
}): Promise<RollbackBoundaryArtifact | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Git-ignored files the attempt created or modified since the boundary.
|
|
22
|
+
* Boundaries recorded before the ignored inventory existed report nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function listIgnoredFileChangesSinceBoundary(input: {
|
|
25
|
+
repoRoot?: string;
|
|
26
|
+
boundary?: RollbackBoundaryArtifact;
|
|
27
|
+
}): IgnoredFileChanges;
|
|
13
28
|
export declare function restoreRollbackBoundary(input: {
|
|
14
29
|
repoRoot?: string;
|
|
15
30
|
boundary?: RollbackBoundaryArtifact;
|
|
16
31
|
restoredAt: string;
|
|
17
32
|
decision: PatchDecision;
|
|
33
|
+
/** Extra attempt-created paths (for example ignored files) to delete. */
|
|
34
|
+
removePaths?: string[];
|
|
35
|
+
/** Pre-existing ignored paths to restore from the in-memory boundary. */
|
|
36
|
+
restoreIgnoredPaths?: string[];
|
|
18
37
|
}): Promise<RollbackOutcomeArtifact | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Repo-relative links (symlinks or junctions) among `paths` whose target
|
|
40
|
+
* resolves outside the repository. Paths are first collapsed to the link.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findEscapingLinks(repoRoot: string, paths: string[]): string[];
|