@martinloop/mcp 0.5.1 → 0.5.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/README.md +52 -14
- package/dist/server-validation.js +2 -0
- package/dist/server.js +30 -3
- package/dist/tools/run-loop.d.ts +1 -0
- package/dist/tools/run-loop.js +25 -4
- package/dist/tools/tool-response.d.ts +9 -1
- package/dist/tools/tool-response.js +7 -5
- package/dist/tools/tool-support.js +62 -271
- package/dist/vendor/adapters/claude-cli.d.ts +9 -0
- package/dist/vendor/adapters/claude-cli.js +19 -2
- package/dist/vendor/adapters/codex-capabilities.d.ts +69 -0
- package/dist/vendor/adapters/codex-capabilities.js +265 -0
- package/dist/vendor/adapters/codex-cli.d.ts +23 -0
- package/dist/vendor/adapters/codex-cli.js +72 -0
- package/dist/vendor/adapters/codex-host.d.ts +107 -0
- package/dist/vendor/adapters/codex-host.js +595 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
- package/dist/vendor/adapters/codex-launcher.js +17 -766
- package/dist/vendor/adapters/index.d.ts +4 -2
- package/dist/vendor/adapters/index.js +4 -2
- package/dist/vendor/contracts/agent-execution.d.ts +9 -0
- package/dist/vendor/contracts/agent-execution.js +14 -0
- package/dist/vendor/contracts/index.d.ts +9 -0
- package/dist/vendor/contracts/index.js +1 -0
- package/dist/vendor/core/grounding.js +42 -7
- package/dist/vendor/core/index.d.ts +5 -2
- package/dist/vendor/core/index.js +12 -3
- package/dist/vendor/presentation/governed-plan.d.ts +33 -0
- package/dist/vendor/presentation/governed-plan.js +146 -0
- package/dist/vendor/presentation/index.d.ts +7 -0
- package/dist/vendor/presentation/index.js +7 -0
- package/dist/vendor/presentation/motion.d.ts +12 -0
- package/dist/vendor/presentation/motion.js +123 -0
- package/dist/vendor/presentation/status.d.ts +8 -0
- package/dist/vendor/presentation/status.js +11 -0
- package/dist/vendor/presentation/table.d.ts +11 -0
- package/dist/vendor/presentation/table.js +47 -0
- package/dist/vendor/presentation/text.d.ts +6 -0
- package/dist/vendor/presentation/text.js +30 -0
- package/dist/vendor/presentation/theme.d.ts +31 -0
- package/dist/vendor/presentation/theme.js +82 -0
- package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
- package/dist/vendor/presentation/verified-handoff.js +151 -0
- package/package.json +3 -2
- package/server.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
|
|
2
|
-
export { createAgentCliAdapter, createClaudeCliAdapter,
|
|
2
|
+
export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
|
|
3
|
+
export { createCodexCliAdapter, type CodexCliAdapterOptions } from "./codex-cli.js";
|
|
3
4
|
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
|
|
4
5
|
export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
|
|
5
|
-
|
|
6
|
+
/** @internal Test-only compatibility export. Public package builders strip this surface. */
|
|
7
|
+
export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexApprovalCapability, type CodexAutonomyResolution, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexFlagScope, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult, type CodexPromptTransport, type CodexSandboxCapability, checkCodexSandboxPreflight, probeFilesystemWriteCapability, type CodexSandboxPreflightOk, type CodexSandboxPreflightOutcome, type CodexSandboxPreflightReadOnly } from "./codex-launcher.js";
|
|
6
8
|
export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
-
export { createAgentCliAdapter, createClaudeCliAdapter,
|
|
2
|
+
export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
|
|
3
|
+
export { createCodexCliAdapter } from "./codex-cli.js";
|
|
3
4
|
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
|
|
4
5
|
export { createVerifierOnlyAdapter } from "./verifier-only.js";
|
|
5
|
-
|
|
6
|
+
/** @internal Test-only compatibility export. Public package builders strip this surface. */
|
|
7
|
+
export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, checkCodexSandboxPreflight, probeFilesystemWriteCapability } from "./codex-launcher.js";
|
|
6
8
|
export { createSpawnPlan } from "./cli-bridge.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_AGENT_EXECUTION_INTENT: "governed-autonomous";
|
|
2
|
+
export type AgentExecutionIntent = typeof DEFAULT_AGENT_EXECUTION_INTENT;
|
|
3
|
+
/** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
|
|
4
|
+
export declare const GOVERNED_AUTONOMOUS_BOUNDARY: {
|
|
5
|
+
readonly interaction: "non-interactive";
|
|
6
|
+
readonly writeScope: "workspace-only";
|
|
7
|
+
};
|
|
8
|
+
export declare const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300000;
|
|
9
|
+
export declare function normalizeProviderExecutionTimeoutMs(value: number | undefined): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_AGENT_EXECUTION_INTENT = "governed-autonomous";
|
|
2
|
+
/** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
|
|
3
|
+
export const GOVERNED_AUTONOMOUS_BOUNDARY = {
|
|
4
|
+
interaction: "non-interactive",
|
|
5
|
+
writeScope: "workspace-only"
|
|
6
|
+
};
|
|
7
|
+
export const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300_000;
|
|
8
|
+
export function normalizeProviderExecutionTimeoutMs(value) {
|
|
9
|
+
const resolved = value ?? DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS;
|
|
10
|
+
if (!Number.isFinite(resolved) || resolved <= 0) {
|
|
11
|
+
throw new Error("providerExecutionTimeoutMs must be a positive finite number.");
|
|
12
|
+
}
|
|
13
|
+
return resolved;
|
|
14
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* portfolio snapshots and routing economics.
|
|
9
9
|
*/
|
|
10
10
|
import type { TerminationEnvelopeV1 } from "./exits.js";
|
|
11
|
+
import type { AgentExecutionIntent } from "./agent-execution.js";
|
|
12
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
|
|
13
|
+
export type { AgentExecutionIntent } from "./agent-execution.js";
|
|
11
14
|
export type LoopStatus = "queued" | "running" | "verifying" | "completed" | "failed" | "exited";
|
|
12
15
|
export type LoopLifecycleState = "created" | "running" | "verifying" | "completed" | "budget_exit" | "diminishing_returns" | "stuck_exit" | "human_escalation" | "wall_clock" | "error_threshold" | "external_event";
|
|
13
16
|
export declare const FAILURE_CLASSES: readonly ["logic_error", "hallucination", "syntax_error", "type_error", "test_regression", "scope_creep", "no_progress", "repo_grounding_failure", "verification_failure", "environment_mismatch", "budget_pressure", "safety_leash_blocked", "sandbox_write_blocked"];
|
|
@@ -27,6 +30,10 @@ export interface LoopTask {
|
|
|
27
30
|
executionProfile?: ExecutionProfile;
|
|
28
31
|
allowedNetworkDomains?: string[];
|
|
29
32
|
approvalPolicy?: ApprovalPolicy;
|
|
33
|
+
/** Provider-neutral execution posture. Defaults to governed autonomous execution. */
|
|
34
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
35
|
+
/** Hard wall-clock limit for one provider coding process. */
|
|
36
|
+
providerExecutionTimeoutMs?: number;
|
|
30
37
|
/** Glob patterns for files the agent is allowed to modify. Empty = no restriction. */
|
|
31
38
|
allowedPaths?: string[];
|
|
32
39
|
/** Glob patterns for files the agent must never modify. */
|
|
@@ -103,6 +110,8 @@ export interface ReceiptScope {
|
|
|
103
110
|
writableRoot?: string;
|
|
104
111
|
/** How the effective sandbox was determined. */
|
|
105
112
|
capabilitySource?: "probe" | "configured" | "unknown";
|
|
113
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
114
|
+
providerExecutionTimeoutMs?: number;
|
|
106
115
|
/** Enforced demo changes (DEMO.md-only enforcement output). */
|
|
107
116
|
demoChangedFiles?: string[];
|
|
108
117
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* creating and managing loop records, handling events, validating batches, and tracking
|
|
8
8
|
* portfolio snapshots and routing economics.
|
|
9
9
|
*/
|
|
10
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
|
|
10
11
|
export const FAILURE_CLASSES = [
|
|
11
12
|
"logic_error",
|
|
12
13
|
"hallucination",
|
|
@@ -196,7 +196,14 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
196
196
|
const indexedPaths = new Set(index.files.map((file) => file.path));
|
|
197
197
|
const indexedSymbols = new Set(index.files.flatMap((file) => file.symbols.map((symbol) => symbol.toLowerCase())));
|
|
198
198
|
const referencedFiles = new Set();
|
|
199
|
+
const addedFiles = new Set();
|
|
199
200
|
const diffFilePattern = /^(?:---|\+\+\+)\s+[ab]\/(.+)$/gm;
|
|
201
|
+
const addedFilePattern = /^---\s+\/dev\/null\r?\n\+\+\+\s+b\/(.+)$/gm;
|
|
202
|
+
for (const match of diff.matchAll(addedFilePattern)) {
|
|
203
|
+
const filePath = match[1]?.trim();
|
|
204
|
+
if (filePath)
|
|
205
|
+
addedFiles.add(filePath);
|
|
206
|
+
}
|
|
200
207
|
for (const match of diff.matchAll(diffFilePattern)) {
|
|
201
208
|
const filePath = match[1]?.trim();
|
|
202
209
|
if (filePath && filePath !== "/dev/null") {
|
|
@@ -204,7 +211,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
for (const filePath of referencedFiles) {
|
|
207
|
-
if (indexedPaths.has(filePath)) {
|
|
214
|
+
if (indexedPaths.has(filePath) || addedFiles.has(filePath)) {
|
|
208
215
|
resolvedFiles.push(filePath);
|
|
209
216
|
}
|
|
210
217
|
else {
|
|
@@ -227,7 +234,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
227
234
|
}
|
|
228
235
|
const addedLinePattern = /^\+(?!\+\+)(.+)$/gm;
|
|
229
236
|
const importPattern = /(?:import|require)\s*(?:.*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
230
|
-
const
|
|
237
|
+
const callableIdentifierPattern = /(?<!\.)\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
231
238
|
const symbolKeywords = new Set([
|
|
232
239
|
"const",
|
|
233
240
|
"let",
|
|
@@ -244,10 +251,33 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
244
251
|
"true",
|
|
245
252
|
"false",
|
|
246
253
|
"null",
|
|
247
|
-
"undefined"
|
|
254
|
+
"undefined",
|
|
255
|
+
"if",
|
|
256
|
+
"for",
|
|
257
|
+
"while",
|
|
258
|
+
"switch",
|
|
259
|
+
"catch",
|
|
260
|
+
"typeof"
|
|
248
261
|
]);
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
const addedLines = [...diff.matchAll(addedLinePattern)].map((match) => match[1] ?? "");
|
|
263
|
+
const declaredSymbols = new Set();
|
|
264
|
+
const declarationPattern = /\b(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
265
|
+
for (const line of addedLines) {
|
|
266
|
+
for (const declarationMatch of line.matchAll(declarationPattern)) {
|
|
267
|
+
const symbol = declarationMatch[1];
|
|
268
|
+
if (symbol)
|
|
269
|
+
declaredSymbols.add(symbol.toLowerCase());
|
|
270
|
+
}
|
|
271
|
+
if (/^\s*import\b/u.test(line)) {
|
|
272
|
+
const importClause = line.match(/^\s*import\s+(.+?)\s+from\s+['"]/u)?.[1] ?? "";
|
|
273
|
+
for (const importSymbol of importClause.matchAll(/\b(?:as\s+)?([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
|
|
274
|
+
const symbol = importSymbol[1];
|
|
275
|
+
if (symbol && symbol !== "as")
|
|
276
|
+
declaredSymbols.add(symbol.toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const line of addedLines) {
|
|
251
281
|
for (const importMatch of line.matchAll(importPattern)) {
|
|
252
282
|
const reference = importMatch[1] ?? "";
|
|
253
283
|
if (!reference.startsWith(".") && !reference.startsWith("/")) {
|
|
@@ -259,6 +289,9 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
259
289
|
const hasMatch = index.files.some((file) => {
|
|
260
290
|
const withoutExt = file.path.replace(/\.(js|ts|tsx|jsx)$/, "");
|
|
261
291
|
return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
|
|
292
|
+
}) || [...addedFiles].some((filePath) => {
|
|
293
|
+
const withoutExt = filePath.replace(/\.(js|ts|tsx|jsx)$/, "");
|
|
294
|
+
return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
|
|
262
295
|
});
|
|
263
296
|
if (!hasMatch) {
|
|
264
297
|
violations.push({
|
|
@@ -268,13 +301,15 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
268
301
|
});
|
|
269
302
|
}
|
|
270
303
|
}
|
|
271
|
-
for (const symbolMatch of line.matchAll(
|
|
304
|
+
for (const symbolMatch of line.matchAll(callableIdentifierPattern)) {
|
|
272
305
|
const symbol = symbolMatch[1];
|
|
273
306
|
if (!symbol)
|
|
274
307
|
continue;
|
|
275
308
|
if (symbolKeywords.has(symbol))
|
|
276
309
|
continue;
|
|
277
|
-
if (
|
|
310
|
+
if (/^[A-Z]/u.test(symbol))
|
|
311
|
+
continue;
|
|
312
|
+
if (declaredSymbols.has(symbol.toLowerCase()))
|
|
278
313
|
continue;
|
|
279
314
|
if (indexedSymbols.has(symbol.toLowerCase()))
|
|
280
315
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApprovalPolicy, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
1
|
+
import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
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";
|
|
@@ -7,7 +7,8 @@ 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
|
-
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";
|
|
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
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
11
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
13
|
export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
|
|
13
14
|
export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
|
|
@@ -69,6 +70,8 @@ export interface MartinAdapterRequest {
|
|
|
69
70
|
executionProfile?: ExecutionProfile;
|
|
70
71
|
allowedNetworkDomains?: string[];
|
|
71
72
|
approvalPolicy?: ApprovalPolicy;
|
|
73
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
74
|
+
providerExecutionTimeoutMs?: number;
|
|
72
75
|
focus: string;
|
|
73
76
|
remainingBudgetUsd: number;
|
|
74
77
|
remainingIterations: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
3
|
+
import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
4
4
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
5
5
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
6
6
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
@@ -15,6 +15,7 @@ import { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
15
15
|
import { decideCircuitBreak } from "./trajectory.js";
|
|
16
16
|
import { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
|
|
17
17
|
import { verifierActuallyPassed, } from "./verified-handoff.js";
|
|
18
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
18
19
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
19
20
|
export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
|
|
20
21
|
export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
|
|
@@ -160,12 +161,16 @@ export function distillContext(loop, options = {}) {
|
|
|
160
161
|
export async function runMartin(input) {
|
|
161
162
|
const now = input.now ?? (() => new Date().toISOString());
|
|
162
163
|
const idFactory = input.idFactory;
|
|
164
|
+
const providerExecutionTimeoutMs = normalizeProviderExecutionTimeoutMs(input.task.providerExecutionTimeoutMs);
|
|
165
|
+
const agentExecutionIntent = input.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT;
|
|
163
166
|
let loop = createLoopRecord({
|
|
164
167
|
workspaceId: input.workspaceId,
|
|
165
168
|
projectId: input.projectId,
|
|
166
|
-
task: input.task,
|
|
169
|
+
task: { ...input.task, agentExecutionIntent, providerExecutionTimeoutMs },
|
|
167
170
|
budget: input.budget,
|
|
168
|
-
...(input.receiptScope
|
|
171
|
+
...(input.receiptScope
|
|
172
|
+
? { receiptScope: { ...input.receiptScope, agentExecutionIntent, providerExecutionTimeoutMs } }
|
|
173
|
+
: {}),
|
|
169
174
|
...(input.teamId ? { teamId: input.teamId } : {}),
|
|
170
175
|
...(input.metadata ? { metadata: input.metadata } : {})
|
|
171
176
|
}, { now: now(), idFactory });
|
|
@@ -655,6 +660,8 @@ export async function runMartin(input) {
|
|
|
655
660
|
? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
|
|
656
661
|
: {}),
|
|
657
662
|
...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
|
|
663
|
+
agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
|
|
664
|
+
providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
|
|
658
665
|
focus: distilled.focus,
|
|
659
666
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
660
667
|
remainingIterations: distilled.constraints.remainingIterations,
|
|
@@ -745,6 +752,8 @@ export async function runMartin(input) {
|
|
|
745
752
|
? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
|
|
746
753
|
: {}),
|
|
747
754
|
...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
|
|
755
|
+
agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
|
|
756
|
+
providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
|
|
748
757
|
focus: distilled.focus,
|
|
749
758
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
750
759
|
remainingIterations: distilled.constraints.remainingIterations,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LoopBudget, ReceiptScope } from "../contracts/index.js";
|
|
2
|
+
import { type RenderEnvironment } from "./theme.js";
|
|
3
|
+
export type PlanStageState = "READY" | "BLOCKED" | "NEXT" | "CONDITIONAL";
|
|
4
|
+
export interface GovernedPlanStage {
|
|
5
|
+
label: string;
|
|
6
|
+
state: PlanStageState;
|
|
7
|
+
gate: string;
|
|
8
|
+
purpose: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GovernedRunPlanView {
|
|
11
|
+
ready: boolean;
|
|
12
|
+
task: string;
|
|
13
|
+
engine: string;
|
|
14
|
+
mode: "live" | "proof";
|
|
15
|
+
budget: LoopBudget;
|
|
16
|
+
verifier: string[];
|
|
17
|
+
receiptScope: ReceiptScope;
|
|
18
|
+
policyProfile: string;
|
|
19
|
+
blockingIssues: string[];
|
|
20
|
+
warnings: string[];
|
|
21
|
+
stages: GovernedPlanStage[];
|
|
22
|
+
}
|
|
23
|
+
export declare function buildGovernedPlanStages(input: Pick<GovernedRunPlanView, "ready" | "engine" | "verifier" | "blockingIssues">): GovernedPlanStage[];
|
|
24
|
+
export declare function renderGovernedRunPlan(input: GovernedRunPlanView, options?: {
|
|
25
|
+
width?: number;
|
|
26
|
+
environment?: RenderEnvironment;
|
|
27
|
+
}): string;
|
|
28
|
+
/**
|
|
29
|
+
* Renders a GovernedRunPlanView as plain Markdown (no ANSI).
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderGovernedRunPlanMarkdown(input: GovernedRunPlanView, options?: {
|
|
32
|
+
includeStatus?: boolean;
|
|
33
|
+
}): string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { renderTable } from "./table.js";
|
|
2
|
+
import { horizontalRule, terminalWidth, truncateVisible } from "./text.js";
|
|
3
|
+
import { bold, paint, } from "./theme.js";
|
|
4
|
+
function stageTone(state) {
|
|
5
|
+
switch (state) {
|
|
6
|
+
case "READY":
|
|
7
|
+
return "success";
|
|
8
|
+
case "BLOCKED":
|
|
9
|
+
return "danger";
|
|
10
|
+
case "NEXT":
|
|
11
|
+
return "info";
|
|
12
|
+
case "CONDITIONAL":
|
|
13
|
+
return "warning";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function buildGovernedPlanStages(input) {
|
|
17
|
+
const blocked = !input.ready || input.blockingIssues.length > 0;
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
label: "Contract & scope",
|
|
21
|
+
state: blocked ? "BLOCKED" : "READY",
|
|
22
|
+
gate: "request + paths",
|
|
23
|
+
purpose: "Bind the task to its governed workspace",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: "Budget & policy",
|
|
27
|
+
state: blocked ? "BLOCKED" : "READY",
|
|
28
|
+
gate: "hard cap + policy",
|
|
29
|
+
purpose: "Arm execution limits before work starts",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: "Agent execution",
|
|
33
|
+
state: blocked ? "BLOCKED" : "NEXT",
|
|
34
|
+
gate: input.engine,
|
|
35
|
+
purpose: "Run the selected coding agent",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "Verification",
|
|
39
|
+
state: input.verifier.length > 0 && !blocked ? "NEXT" : "BLOCKED",
|
|
40
|
+
gate: input.verifier.length > 0 ? input.verifier.join(", ") : "no verifier",
|
|
41
|
+
purpose: "Run configured completion checks",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
label: "Recovery",
|
|
45
|
+
state: "CONDITIONAL",
|
|
46
|
+
gate: "stop / fail",
|
|
47
|
+
purpose: "Preserve rollback and recovery evidence",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Proof",
|
|
51
|
+
state: blocked ? "BLOCKED" : "NEXT",
|
|
52
|
+
gate: "receipt",
|
|
53
|
+
purpose: "Produce the final governed record",
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function bounded(line, width) {
|
|
58
|
+
return truncateVisible(line, width);
|
|
59
|
+
}
|
|
60
|
+
export function renderGovernedRunPlan(input, options = {}) {
|
|
61
|
+
const environment = options.environment ?? {};
|
|
62
|
+
const width = terminalWidth(options.width);
|
|
63
|
+
const rows = input.stages.map((stage) => ({
|
|
64
|
+
...stage,
|
|
65
|
+
renderedState: paint(stage.state, stageTone(stage.state), environment),
|
|
66
|
+
}));
|
|
67
|
+
const lines = [
|
|
68
|
+
horizontalRule(width, "━"),
|
|
69
|
+
bounded(bold("MARTINLOOP", environment) +
|
|
70
|
+
" ▸ " +
|
|
71
|
+
paint("GOVERNED RUN PLAN", input.ready ? "success" : "danger", environment), width),
|
|
72
|
+
horizontalRule(width, "━"),
|
|
73
|
+
"",
|
|
74
|
+
bounded(paint("Task".padEnd(18), "info", environment) + input.task, width),
|
|
75
|
+
bounded(paint("Engine".padEnd(18), "info", environment) + input.engine, width),
|
|
76
|
+
bounded(paint("Mode".padEnd(18), "info", environment) + input.mode, width),
|
|
77
|
+
"",
|
|
78
|
+
renderTable(rows, [
|
|
79
|
+
{ header: "STAGE", value: (row) => row.label, minWidth: 18, maxWidth: 24 },
|
|
80
|
+
{
|
|
81
|
+
header: "STATUS",
|
|
82
|
+
value: (row) => row.renderedState,
|
|
83
|
+
minWidth: 12,
|
|
84
|
+
maxWidth: 14,
|
|
85
|
+
},
|
|
86
|
+
{ header: "GATE", value: (row) => row.gate, minWidth: 18, maxWidth: 36 },
|
|
87
|
+
{ header: "PURPOSE", value: (row) => row.purpose, minWidth: 24 },
|
|
88
|
+
], width),
|
|
89
|
+
"",
|
|
90
|
+
bounded(bold("Budget cap", environment) +
|
|
91
|
+
" " +
|
|
92
|
+
paint("$" + input.budget.maxUsd.toFixed(2), "success", environment), width),
|
|
93
|
+
bounded(bold("Soft limit", environment) +
|
|
94
|
+
" $" +
|
|
95
|
+
input.budget.softLimitUsd.toFixed(2), width),
|
|
96
|
+
bounded(bold("Attempts", environment) + " " + input.budget.maxIterations, width),
|
|
97
|
+
bounded(bold("Verifier", environment) +
|
|
98
|
+
" " +
|
|
99
|
+
(input.verifier.join(", ") || "NOT CONFIGURED"), width),
|
|
100
|
+
bounded(bold("Policy", environment) + " " + input.policyProfile, width),
|
|
101
|
+
];
|
|
102
|
+
if (input.blockingIssues.length > 0) {
|
|
103
|
+
lines.push("", bold("BLOCKING", environment), ...input.blockingIssues.map((issue) => bounded(" " + paint("✗", "danger", environment) + " " + issue, width)));
|
|
104
|
+
}
|
|
105
|
+
if (input.warnings.length > 0) {
|
|
106
|
+
lines.push("", bold("WARNINGS", environment), ...input.warnings.map((warning) => bounded(" " + paint("!", "warning", environment) + " " + warning, width)));
|
|
107
|
+
}
|
|
108
|
+
lines.push("", input.ready
|
|
109
|
+
? paint("✓ READY TO EXECUTE", "success", environment)
|
|
110
|
+
: paint("✗ PREFLIGHT BLOCKED", "danger", environment), horizontalRule(width, "━"));
|
|
111
|
+
return lines.map((line) => bounded(line, width)).join("\n");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Renders a GovernedRunPlanView as plain Markdown (no ANSI).
|
|
115
|
+
*/
|
|
116
|
+
export function renderGovernedRunPlanMarkdown(input, options = {}) {
|
|
117
|
+
void options;
|
|
118
|
+
const lines = [
|
|
119
|
+
`## MartinLoop Governed Run Plan — ${input.ready ? "READY" : "BLOCKED"}`,
|
|
120
|
+
"",
|
|
121
|
+
"| Field | Value |",
|
|
122
|
+
"|-------|-------|",
|
|
123
|
+
`| Task | ${input.task} |`,
|
|
124
|
+
`| Engine | ${input.engine} |`,
|
|
125
|
+
`| Mode | ${input.mode} |`,
|
|
126
|
+
`| Budget cap | $${input.budget.maxUsd.toFixed(2)} |`,
|
|
127
|
+
`| Soft limit | $${input.budget.softLimitUsd.toFixed(2)} |`,
|
|
128
|
+
`| Max attempts | ${input.budget.maxIterations} |`,
|
|
129
|
+
`| Verifier | ${input.verifier.join(", ") || "NOT CONFIGURED"} |`,
|
|
130
|
+
`| Policy | ${input.policyProfile} |`,
|
|
131
|
+
"",
|
|
132
|
+
"### Stages",
|
|
133
|
+
"",
|
|
134
|
+
"| Stage | Status | Gate | Purpose |",
|
|
135
|
+
"|-------|--------|------|---------|",
|
|
136
|
+
...input.stages.map((s) => `| ${s.label} | ${s.state} | ${s.gate} | ${s.purpose} |`),
|
|
137
|
+
];
|
|
138
|
+
if (input.blockingIssues.length > 0) {
|
|
139
|
+
lines.push("", "### Blocking Issues", "", ...input.blockingIssues.map((issue) => `- ✗ ${issue}`));
|
|
140
|
+
}
|
|
141
|
+
if (input.warnings.length > 0) {
|
|
142
|
+
lines.push("", "### Warnings", "", ...input.warnings.map((w) => `- ${w}`));
|
|
143
|
+
}
|
|
144
|
+
lines.push("", input.ready ? "**✓ READY TO EXECUTE**" : "**✗ PREFLIGHT BLOCKED**");
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RenderEnvironment } from "./theme.js";
|
|
2
|
+
export interface MotionEnvironment extends RenderEnvironment {
|
|
3
|
+
readonly outputMode?: "human" | "json" | "quiet";
|
|
4
|
+
readonly stdinIsTty?: boolean;
|
|
5
|
+
readonly stdoutIsTty?: boolean;
|
|
6
|
+
readonly ci?: boolean;
|
|
7
|
+
readonly noMotion?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function canAnimate(env?: MotionEnvironment): boolean;
|
|
10
|
+
export declare function animateBannerShine(input: string, env?: MotionEnvironment): Promise<void>;
|
|
11
|
+
export declare function withSpinner<T>(label: string, task: () => Promise<T>, env?: MotionEnvironment): Promise<T>;
|
|
12
|
+
export declare function writeHumanOutputWithShine(text: string, env?: MotionEnvironment): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { paint, terminalAnsi } from "./theme.js";
|
|
3
|
+
import { stripAnsi } from "./text.js";
|
|
4
|
+
function envFlag(name) {
|
|
5
|
+
const value = process.env[name];
|
|
6
|
+
return value === "1" || value === "true" || value === "yes";
|
|
7
|
+
}
|
|
8
|
+
export function canAnimate(env = {}) {
|
|
9
|
+
const mode = env.outputMode ?? "human";
|
|
10
|
+
if (mode !== "human") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const noColor = env.noColor ?? Object.prototype.hasOwnProperty.call(process.env, "NO_COLOR");
|
|
14
|
+
if (noColor) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const stdoutTty = env.stdoutIsTty ?? process.stdout.isTTY === true;
|
|
18
|
+
if (!stdoutTty) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const ci = env.ci ?? Boolean(process.env.CI);
|
|
22
|
+
if (ci) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const noMotion = env.noMotion ??
|
|
26
|
+
(envFlag("MARTIN_NO_MOTION") || envFlag("MARTIN_REDUCED_MOTION"));
|
|
27
|
+
if (noMotion) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const term = env.term ?? process.env.TERM;
|
|
31
|
+
return term !== "dumb";
|
|
32
|
+
}
|
|
33
|
+
function shineCharacter(character, distance, env) {
|
|
34
|
+
if (character === " ") {
|
|
35
|
+
return character;
|
|
36
|
+
}
|
|
37
|
+
if (distance === 0) {
|
|
38
|
+
return paint(character, "plain", { ...env, color: "always" });
|
|
39
|
+
}
|
|
40
|
+
if (distance === 1) {
|
|
41
|
+
return paint(character, "info", { ...env, color: "always" });
|
|
42
|
+
}
|
|
43
|
+
return paint(character, "brand", { ...env, color: "always" });
|
|
44
|
+
}
|
|
45
|
+
export async function animateBannerShine(input, env = {}) {
|
|
46
|
+
if (!canAnimate(env)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const target = [...stripAnsi(input)].slice(0, 100);
|
|
50
|
+
if (target.length === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
process.stdout.write(terminalAnsi.hideCursor);
|
|
55
|
+
for (let center = -2; center < target.length + 4; center += 1) {
|
|
56
|
+
const frame = target
|
|
57
|
+
.map((character, index) => shineCharacter(character, Math.abs(index - center), env))
|
|
58
|
+
.join("");
|
|
59
|
+
process.stdout.write(terminalAnsi.cursorHome +
|
|
60
|
+
terminalAnsi.eraseLine +
|
|
61
|
+
frame +
|
|
62
|
+
terminalAnsi.reset);
|
|
63
|
+
await sleep(18);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
process.stdout.write(terminalAnsi.cursorHome +
|
|
68
|
+
terminalAnsi.eraseLine +
|
|
69
|
+
terminalAnsi.reset +
|
|
70
|
+
terminalAnsi.showCursor);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
74
|
+
export async function withSpinner(label, task, env = {}) {
|
|
75
|
+
if (!canAnimate(env)) {
|
|
76
|
+
return task();
|
|
77
|
+
}
|
|
78
|
+
let frame = 0;
|
|
79
|
+
process.stdout.write(terminalAnsi.hideCursor);
|
|
80
|
+
const timer = setInterval(() => {
|
|
81
|
+
const glyph = spinnerFrames[frame % spinnerFrames.length] ?? "•";
|
|
82
|
+
frame += 1;
|
|
83
|
+
process.stdout.write(terminalAnsi.cursorHome +
|
|
84
|
+
terminalAnsi.eraseLine +
|
|
85
|
+
paint(glyph, "brand", { ...env, color: "always" }) +
|
|
86
|
+
" " +
|
|
87
|
+
label);
|
|
88
|
+
}, 70);
|
|
89
|
+
try {
|
|
90
|
+
return await task();
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
clearInterval(timer);
|
|
94
|
+
process.stdout.write(terminalAnsi.cursorHome +
|
|
95
|
+
terminalAnsi.eraseLine +
|
|
96
|
+
terminalAnsi.reset +
|
|
97
|
+
terminalAnsi.showCursor);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function writeHumanOutputWithShine(text, env = {}) {
|
|
101
|
+
if (!text) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!canAnimate(env)) {
|
|
105
|
+
process.stdout.write(text + "\n");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const lines = text.split("\n");
|
|
109
|
+
const headingIndex = lines.findIndex((line) => stripAnsi(line).toUpperCase().includes("MARTINLOOP"));
|
|
110
|
+
if (headingIndex < 0) {
|
|
111
|
+
process.stdout.write(text + "\n");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (headingIndex > 0) {
|
|
115
|
+
process.stdout.write(lines.slice(0, headingIndex).join("\n") + "\n");
|
|
116
|
+
}
|
|
117
|
+
const heading = lines[headingIndex] ?? "";
|
|
118
|
+
await animateBannerShine(heading, env);
|
|
119
|
+
process.stdout.write(heading + "\n");
|
|
120
|
+
if (headingIndex < lines.length - 1) {
|
|
121
|
+
process.stdout.write(lines.slice(headingIndex + 1).join("\n") + "\n");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MartinTone } from "./theme.js";
|
|
2
|
+
export type ProofStatus = "VERIFIED" | "NEEDS_REVIEW" | "STOPPED" | "FAILED" | "RUNNING" | "PENDING";
|
|
3
|
+
export interface StatusPresentation {
|
|
4
|
+
readonly glyph: string;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly tone: MartinTone;
|
|
7
|
+
}
|
|
8
|
+
export declare function statusPresentation(status: ProofStatus): StatusPresentation;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const presentations = {
|
|
2
|
+
VERIFIED: { glyph: "✓", label: "VERIFIED", tone: "success" },
|
|
3
|
+
NEEDS_REVIEW: { glyph: "!", label: "NEEDS REVIEW", tone: "warning" },
|
|
4
|
+
STOPPED: { glyph: "■", label: "STOPPED", tone: "danger" },
|
|
5
|
+
FAILED: { glyph: "×", label: "FAILED", tone: "danger" },
|
|
6
|
+
RUNNING: { glyph: "◆", label: "RUNNING", tone: "info" },
|
|
7
|
+
PENDING: { glyph: "○", label: "PENDING", tone: "muted" },
|
|
8
|
+
};
|
|
9
|
+
export function statusPresentation(status) {
|
|
10
|
+
return presentations[status];
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TableColumn<Row> {
|
|
2
|
+
readonly header: string;
|
|
3
|
+
readonly value: (row: Row) => string;
|
|
4
|
+
readonly minWidth?: number;
|
|
5
|
+
readonly maxWidth?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RenderTableOptions {
|
|
8
|
+
readonly separator?: string;
|
|
9
|
+
readonly width?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function renderTable<Row>(rows: readonly Row[], columns: readonly TableColumn<Row>[], options?: RenderTableOptions | number): string;
|