@martinloop/mcp 0.3.5 → 0.3.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.
Files changed (118) hide show
  1. package/README.md +137 -137
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +2 -0
  4. package/dist/prompts.js +19 -1
  5. package/dist/resources.d.ts +3 -0
  6. package/dist/resources.js +125 -17
  7. package/dist/server-validation.d.ts +1 -1
  8. package/dist/server-validation.js +12 -0
  9. package/dist/server.js +18 -7
  10. package/dist/tools/doctor.js +6 -0
  11. package/dist/tools/run-loop.d.ts +2 -1
  12. package/dist/tools/run-loop.js +5 -1
  13. package/dist/tools/tool-support.d.ts +1 -1
  14. package/dist/tools/tool-support.js +2 -1
  15. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +146 -0
  16. package/dist/vendor/adapters/adapters/src/claude-cli.js +1100 -0
  17. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +103 -0
  18. package/dist/vendor/adapters/adapters/src/cli-bridge.js +569 -0
  19. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +78 -0
  20. package/dist/vendor/adapters/adapters/src/codex-launcher.js +663 -0
  21. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +10 -0
  22. package/dist/vendor/adapters/adapters/src/direct-provider.js +40 -0
  23. package/dist/vendor/adapters/adapters/src/index.d.ts +8 -0
  24. package/dist/vendor/adapters/adapters/src/index.js +8 -0
  25. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +62 -0
  26. package/dist/vendor/adapters/adapters/src/openai-compatible.js +306 -0
  27. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +17 -0
  28. package/dist/vendor/adapters/adapters/src/runtime-support.js +59 -0
  29. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +8 -0
  30. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +40 -0
  31. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +8 -0
  32. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +12 -0
  33. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +9 -0
  34. package/dist/vendor/adapters/adapters/src/verifier-only.js +63 -0
  35. package/dist/vendor/adapters/claude-cli.js +21 -20
  36. package/dist/vendor/adapters/cli-bridge.d.ts +7 -0
  37. package/dist/vendor/adapters/cli-bridge.js +94 -8
  38. package/dist/vendor/adapters/codex-launcher.js +18 -7
  39. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +48 -0
  40. package/dist/vendor/adapters/contracts/src/context-graph.js +26 -0
  41. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +78 -0
  42. package/dist/vendor/adapters/contracts/src/execution-policy.js +43 -0
  43. package/dist/vendor/adapters/contracts/src/governance.d.ts +24 -0
  44. package/dist/vendor/adapters/contracts/src/governance.js +11 -0
  45. package/dist/vendor/adapters/contracts/src/identity.d.ts +29 -0
  46. package/dist/vendor/adapters/contracts/src/identity.js +30 -0
  47. package/dist/vendor/adapters/contracts/src/index.d.ts +412 -0
  48. package/dist/vendor/adapters/contracts/src/index.js +225 -0
  49. package/dist/vendor/adapters/contracts/src/operator.d.ts +19 -0
  50. package/dist/vendor/adapters/contracts/src/operator.js +11 -0
  51. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +26 -0
  52. package/dist/vendor/adapters/contracts/src/trajectory.js +27 -0
  53. package/dist/vendor/adapters/core/src/compiler.d.ts +52 -0
  54. package/dist/vendor/adapters/core/src/compiler.js +52 -0
  55. package/dist/vendor/adapters/core/src/context-graph.d.ts +3 -0
  56. package/dist/vendor/adapters/core/src/context-graph.js +371 -0
  57. package/dist/vendor/adapters/core/src/context-integrity.d.ts +26 -0
  58. package/dist/vendor/adapters/core/src/context-integrity.js +80 -0
  59. package/dist/vendor/adapters/core/src/grounding.d.ts +38 -0
  60. package/dist/vendor/adapters/core/src/grounding.js +273 -0
  61. package/dist/vendor/adapters/core/src/identity.d.ts +20 -0
  62. package/dist/vendor/adapters/core/src/identity.js +86 -0
  63. package/dist/vendor/adapters/core/src/index.d.ts +186 -0
  64. package/dist/vendor/adapters/core/src/index.js +1495 -0
  65. package/dist/vendor/adapters/core/src/leash.d.ts +48 -0
  66. package/dist/vendor/adapters/core/src/leash.js +497 -0
  67. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +18 -0
  68. package/dist/vendor/adapters/core/src/persistence/compiler.js +34 -0
  69. package/dist/vendor/adapters/core/src/persistence/index.d.ts +10 -0
  70. package/dist/vendor/adapters/core/src/persistence/index.js +5 -0
  71. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +39 -0
  72. package/dist/vendor/adapters/core/src/persistence/integrity.js +252 -0
  73. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +23 -0
  74. package/dist/vendor/adapters/core/src/persistence/ledger.js +9 -0
  75. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +63 -0
  76. package/dist/vendor/adapters/core/src/persistence/memory-store.js +115 -0
  77. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +66 -0
  78. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +140 -0
  79. package/dist/vendor/adapters/core/src/persistence/store.d.ts +89 -0
  80. package/dist/vendor/adapters/core/src/persistence/store.js +125 -0
  81. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +85 -0
  82. package/dist/vendor/adapters/core/src/persistence/trace-store.js +125 -0
  83. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +2 -0
  84. package/dist/vendor/adapters/core/src/policy-compiler.js +195 -0
  85. package/dist/vendor/adapters/core/src/policy.d.ts +156 -0
  86. package/dist/vendor/adapters/core/src/policy.js +704 -0
  87. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +53 -0
  88. package/dist/vendor/adapters/core/src/repo-analyzer.js +263 -0
  89. package/dist/vendor/adapters/core/src/rollback.d.ts +15 -0
  90. package/dist/vendor/adapters/core/src/rollback.js +253 -0
  91. package/dist/vendor/adapters/core/src/routing.d.ts +82 -0
  92. package/dist/vendor/adapters/core/src/routing.js +336 -0
  93. package/dist/vendor/adapters/core/src/test-integrity.d.ts +64 -0
  94. package/dist/vendor/adapters/core/src/test-integrity.js +197 -0
  95. package/dist/vendor/adapters/core/src/trajectory.d.ts +18 -0
  96. package/dist/vendor/adapters/core/src/trajectory.js +295 -0
  97. package/dist/vendor/adapters/openai-compatible.js +84 -51
  98. package/dist/vendor/adapters/verifier-only.js +2 -2
  99. package/dist/vendor/contracts/index.d.ts +2 -2
  100. package/dist/vendor/contracts/index.js +1 -0
  101. package/dist/vendor/core/compiler.js +4 -9
  102. package/dist/vendor/core/index.d.ts +10 -2
  103. package/dist/vendor/core/index.js +32 -91
  104. package/dist/vendor/core/persistence/memory-store.d.ts +63 -0
  105. package/dist/vendor/core/persistence/memory-store.js +115 -0
  106. package/dist/vendor/core/persistence/trace-store.d.ts +85 -0
  107. package/dist/vendor/core/persistence/trace-store.js +125 -0
  108. package/dist/vendor/core/repo-analyzer.d.ts +53 -0
  109. package/dist/vendor/core/repo-analyzer.js +263 -0
  110. package/dist/vendor/core/rollback.js +2 -3
  111. package/dist/vendor/core/routing.d.ts +42 -0
  112. package/dist/vendor/core/routing.js +156 -5
  113. package/dist/vendor/core/test-integrity.d.ts +64 -0
  114. package/dist/vendor/core/test-integrity.js +197 -0
  115. package/dist/workflow-state.d.ts +1 -1
  116. package/dist/workflow-state.js +12 -4
  117. package/package.json +1 -1
  118. package/server.json +2 -2
@@ -13,7 +13,11 @@ export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, ver
13
13
  export { compileExecutionPolicy } from "./policy-compiler.js";
14
14
  export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
15
15
  // ─── Routing economics ─────────────────────────────────────────────────────
16
- export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
16
+ export { classifyRoute, evaluatePreworkBurnPolicy, resolveModelForTier, selectBestEngine } from "./routing.js";
17
+ // ─── Test Integrity Validation ──────────────────────────────────────────────
18
+ export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
19
+ // ─── Repo Style Analyzer ────────────────────────────────────────────────────
20
+ export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
17
21
  // ─── Context Integrity Pre-gate ──────────────────────────────────────────────
18
22
  export { runContextIntegrityPrecheck } from "./context-integrity.js";
19
23
  // ─── Prompt packet compiler ──────────────────────────────────────────────────
@@ -21,6 +25,10 @@ export { compilePromptPacket } from "./compiler.js";
21
25
  // ─── Persistence (RunStore, LedgerEvent, FileRunStore) ──────────────────────
22
26
  export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
23
27
  export { compileAndPersistContext } from "./persistence/index.js";
28
+ // ─── MartinLoop Memory Store ────────────────────────────────────────────────
29
+ export { appendMemory, readMemoryEntries, getPreference, buildMemorySummary, recordPreference, recordConsent } from "./persistence/memory-store.js";
30
+ // ─── Trace Intelligence Store ───────────────────────────────────────────────
31
+ export { appendTraceEntry, readTraceEntries, aggregateTraces, getHistoricalDirectSuccessRate } from "./persistence/trace-store.js";
24
32
  /**
25
33
  * Admission gate — must pass before any attempt is executed.
26
34
  * Evaluates budget headroom, oscillation, and repetitive failure patterns.
@@ -178,7 +186,6 @@ export async function runMartin(input) {
178
186
  let currentAdapterIndex = 0;
179
187
  let currentAdapter = adapterChain[currentAdapterIndex] ?? input.adapter;
180
188
  let useCompressedContext = false;
181
- const isVerifyOnly = input.task.mutationMode === "verify_only";
182
189
  const executionProfile = resolveExecutionProfile({
183
190
  executionProfile: input.task.executionProfile,
184
191
  allowedNetworkDomains: input.task.allowedNetworkDomains
@@ -676,93 +683,7 @@ export async function runMartin(input) {
676
683
  // a git repo) and silently return [], which would falsely trigger no_code_change.
677
684
  const changedFileEvidenceAvailable = result.execution?.changedFiles !== undefined || changedFiles.length > 0;
678
685
  const isVerifierOnlyAdapter = executingAdapter.adapterId === "direct:verifier:verify-only";
679
- const patchTruthCountsEdits = !isVerifyOnly && !isVerifierOnlyAdapter && changedFileEvidenceAvailable;
680
- if (isVerifyOnly && changedFiles.length > 0) {
681
- const patchDecision = evaluatePatchDecision({
682
- verificationPassed: verification.passed,
683
- previousVerifierScore,
684
- verifierScore: verification.passed ? 1 : 0,
685
- scopeViolationCount: changedFiles.length,
686
- changedFileCount: changedFiles.length,
687
- diffNovelty: 1,
688
- diffStats: result.execution?.diffStats,
689
- costUsd: getUsageUsd(result.usage),
690
- summary: result.summary
691
- });
692
- const verifyOnlyExitDecision = {
693
- shouldExit: true,
694
- lifecycleState: "human_escalation",
695
- status: "exited",
696
- reason: "Verify-only mode forbids file changes.",
697
- failureClass: "safety_leash_blocked",
698
- safetySurface: "filesystem",
699
- reasonCode: "verify_only_write_attempt"
700
- };
701
- const rollbackOutcome = await restoreRollbackBoundary({
702
- repoRoot: request.context.repoRoot,
703
- boundary: rollbackBoundary,
704
- restoredAt: attemptCompletedAt,
705
- decision: patchDecision.decision
706
- });
707
- if (input.store) {
708
- const verifyOnlyViolation = {
709
- kind: "path_not_allowed",
710
- message: `Verify-only mode forbids changed files: ${changedFiles.join(", ")}`,
711
- file: changedFiles[0]
712
- };
713
- await input.store.writeAttemptArtifacts(loop.loopId, currentAttemptIndex, {
714
- compiledContext,
715
- leash: createLeashArtifact({
716
- surface: "filesystem",
717
- reason: verifyOnlyExitDecision.reason,
718
- violations: [verifyOnlyViolation]
719
- }, currentAttemptIndex),
720
- patchScore: patchDecision.score,
721
- patchDecision: toPatchDecisionArtifact(patchDecision),
722
- ...(rollbackBoundary ? { rollbackBoundary } : {}),
723
- ...(rollbackOutcome ? { rollbackOutcome } : {})
724
- });
725
- await input.store.appendLedger(loop.loopId, makeLedgerEvent({
726
- kind: "safety.violations_found",
727
- runId: loop.loopId,
728
- attemptIndex: currentAttemptIndex,
729
- payload: {
730
- surface: "filesystem",
731
- blocked: true,
732
- attemptIndex: currentAttemptIndex,
733
- violations: [
734
- {
735
- kind: "path_not_allowed",
736
- message: verifyOnlyExitDecision.reason,
737
- files: changedFiles
738
- }
739
- ]
740
- }
741
- }));
742
- await input.store.appendLedger(loop.loopId, makeLedgerEvent({
743
- kind: "attempt.discarded",
744
- runId: loop.loopId,
745
- attemptIndex: currentAttemptIndex,
746
- payload: {
747
- decision: patchDecision.decision,
748
- reason: patchDecision.summary,
749
- reasonCodes: patchDecision.reasonCodes,
750
- score: patchDecision.score.score
751
- }
752
- }));
753
- await input.store.appendLedger(loop.loopId, makeLedgerEvent({
754
- kind: "run.exited",
755
- runId: loop.loopId,
756
- payload: createRunExitPayload(verifyOnlyExitDecision)
757
- }));
758
- }
759
- const finalizedLoop = finalizeLoop(loop, verifyOnlyExitDecision, now(), idFactory);
760
- await persistLoopRecordIfSupported(input.store, finalizedLoop);
761
- return {
762
- loop: finalizedLoop,
763
- decision: verifyOnlyExitDecision
764
- };
765
- }
686
+ const patchTruthCountsEdits = !isVerifierOnlyAdapter && changedFileEvidenceAvailable;
766
687
  const filesystemDecision = evaluateFilesystemLeash({
767
688
  repoRoot: request.context.repoRoot,
768
689
  changedFiles,
@@ -1009,7 +930,7 @@ export async function runMartin(input) {
1009
930
  }
1010
931
  }
1011
932
  if (patchDecision && patchDecision.decision !== "KEEP" && !failure) {
1012
- failure = classifyPatchDecisionFailure(patchDecision);
933
+ failure = classifyPatchDecisionFailure(patchDecision, result);
1013
934
  loop = applyPatchFailureToLoop(loop, {
1014
935
  attemptId,
1015
936
  summary: patchDecision.summary,
@@ -1403,7 +1324,19 @@ function toPatchDecisionArtifact(decision) {
1403
1324
  reasonCodes: decision.reasonCodes
1404
1325
  };
1405
1326
  }
1406
- function classifyPatchDecisionFailure(decision) {
1327
+ const SANDBOX_WRITE_BLOCKED_PATTERNS = [
1328
+ /createprocessasuserw failed:\s*\d+/iu,
1329
+ /windows sandbox: runner error/iu,
1330
+ /writing is blocked by read-only sandbox/iu,
1331
+ /read-only filesystem sandbox/iu,
1332
+ /approval is disabled/iu
1333
+ ];
1334
+ function detectSandboxWriteBlocked(result) {
1335
+ const sources = [result.summary, result.failure?.message]
1336
+ .filter((value) => typeof value === "string" && value.trim().length > 0);
1337
+ return sources.some((source) => SANDBOX_WRITE_BLOCKED_PATTERNS.some((pattern) => pattern.test(source)));
1338
+ }
1339
+ function classifyPatchDecisionFailure(decision, result) {
1407
1340
  if (decision.reasonCodes.includes("grounding_failure")) {
1408
1341
  return {
1409
1342
  failureClass: "repo_grounding_failure",
@@ -1445,6 +1378,14 @@ function classifyPatchDecisionFailure(decision) {
1445
1378
  recommendedIntervention: "escalate_human"
1446
1379
  };
1447
1380
  }
1381
+ if (result && detectSandboxWriteBlocked(result)) {
1382
+ return {
1383
+ failureClass: "sandbox_write_blocked",
1384
+ rationale: "The adapter reported a completed patch but the workspace-write sandbox blocked the underlying file write, so no trustworthy code change landed on disk.",
1385
+ retryable: true,
1386
+ recommendedIntervention: "switch_adapter"
1387
+ };
1388
+ }
1448
1389
  return {
1449
1390
  failureClass: "no_progress",
1450
1391
  rationale: "Patch truth discarded the attempt because it did not produce a trustworthy code change.",
@@ -0,0 +1,63 @@
1
+ /**
2
+ * MartinLoop Memory Store
3
+ *
4
+ * Append-only, never-overwriting persistent memory. Records user consent
5
+ * signals, preferences, behavioral patterns, and learning outcomes so
6
+ * MartinLoop gets smarter with every session.
7
+ *
8
+ * Design principles:
9
+ * - Append-only JSONL — entries are never deleted or overwritten
10
+ * - Structured by kind — each memory entry has a type and a key
11
+ * - Source-aware — distinguishes explicit user input from inferred patterns
12
+ * - Confidence-scored — inferred memories degrade gracefully
13
+ *
14
+ * Storage: <runsRoot>/_martin/memory.jsonl
15
+ */
16
+ export type MemoryKind = "consent" | "preference" | "pattern" | "feedback" | "budget" | "model";
17
+ export interface MemoryEntry {
18
+ /** ISO-8601 timestamp */
19
+ timestamp: string;
20
+ /** What kind of memory this is */
21
+ kind: MemoryKind;
22
+ /** Dot-notated key, e.g. "model.direct.claude", "budget.auth.usd", "gate.bypassed" */
23
+ key: string;
24
+ /** The memory payload */
25
+ value: unknown;
26
+ /** Did the user say it explicitly, or did Martin observe it? */
27
+ source: "explicit" | "inferred";
28
+ /** Confidence score 0-1 — inferred memories should be < 0.8 */
29
+ confidence: number;
30
+ }
31
+ export interface MemorySummary {
32
+ totalEntries: number;
33
+ byKind: Partial<Record<MemoryKind, MemoryEntry[]>>;
34
+ recentPreferences: MemoryEntry[];
35
+ recentConsents: MemoryEntry[];
36
+ recentFeedback: MemoryEntry[];
37
+ }
38
+ /**
39
+ * Append a memory entry. Never overwrites — always appends.
40
+ */
41
+ export declare function appendMemory(runsRoot: string, entry: MemoryEntry): Promise<void>;
42
+ /**
43
+ * Read all memory entries in order (oldest first).
44
+ */
45
+ export declare function readMemoryEntries(runsRoot: string): Promise<MemoryEntry[]>;
46
+ /**
47
+ * Get the most recent memory entry for a specific key.
48
+ * Returns undefined if no entry exists for that key.
49
+ */
50
+ export declare function getPreference(runsRoot: string, key: string): Promise<MemoryEntry | undefined>;
51
+ /**
52
+ * Build a summary of memory entries for MCP resource and MartinLoop context injection.
53
+ * Returns the top 20 most recent entries per kind.
54
+ */
55
+ export declare function buildMemorySummary(entries: MemoryEntry[]): MemorySummary;
56
+ /**
57
+ * Record a preference from user input.
58
+ */
59
+ export declare function recordPreference(runsRoot: string, key: string, value: unknown, source?: "explicit" | "inferred"): Promise<void>;
60
+ /**
61
+ * Record a consent signal (user approved or denied a governance action).
62
+ */
63
+ export declare function recordConsent(runsRoot: string, action: string, approved: boolean, context?: Record<string, unknown>): Promise<void>;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * MartinLoop Memory Store
3
+ *
4
+ * Append-only, never-overwriting persistent memory. Records user consent
5
+ * signals, preferences, behavioral patterns, and learning outcomes so
6
+ * MartinLoop gets smarter with every session.
7
+ *
8
+ * Design principles:
9
+ * - Append-only JSONL — entries are never deleted or overwritten
10
+ * - Structured by kind — each memory entry has a type and a key
11
+ * - Source-aware — distinguishes explicit user input from inferred patterns
12
+ * - Confidence-scored — inferred memories degrade gracefully
13
+ *
14
+ * Storage: <runsRoot>/_martin/memory.jsonl
15
+ */
16
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
17
+ import { join, resolve } from "node:path";
18
+ const MEMORY_DIRECTORY = "_martin";
19
+ const MEMORY_FILENAME = "memory.jsonl";
20
+ function resolveMemoryPath(runsRoot) {
21
+ return join(resolve(runsRoot), MEMORY_DIRECTORY, MEMORY_FILENAME);
22
+ }
23
+ /**
24
+ * Append a memory entry. Never overwrites — always appends.
25
+ */
26
+ export async function appendMemory(runsRoot, entry) {
27
+ const memPath = resolveMemoryPath(runsRoot);
28
+ await mkdir(join(resolve(runsRoot), MEMORY_DIRECTORY), { recursive: true });
29
+ await appendFile(memPath, JSON.stringify(entry) + "\n", "utf8");
30
+ }
31
+ /**
32
+ * Read all memory entries in order (oldest first).
33
+ */
34
+ export async function readMemoryEntries(runsRoot) {
35
+ const memPath = resolveMemoryPath(runsRoot);
36
+ try {
37
+ const raw = await readFile(memPath, "utf8");
38
+ return raw
39
+ .split("\n")
40
+ .filter((line) => line.trim().length > 0)
41
+ .map((line) => {
42
+ try {
43
+ return JSON.parse(line);
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ })
49
+ .filter((e) => e !== null);
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ }
55
+ /**
56
+ * Get the most recent memory entry for a specific key.
57
+ * Returns undefined if no entry exists for that key.
58
+ */
59
+ export async function getPreference(runsRoot, key) {
60
+ const entries = await readMemoryEntries(runsRoot);
61
+ // Return the most recent entry for this key (last in file)
62
+ const matches = entries.filter((e) => e.key === key);
63
+ return matches[matches.length - 1];
64
+ }
65
+ /**
66
+ * Build a summary of memory entries for MCP resource and MartinLoop context injection.
67
+ * Returns the top 20 most recent entries per kind.
68
+ */
69
+ export function buildMemorySummary(entries) {
70
+ const byKind = {};
71
+ for (const entry of entries) {
72
+ if (!byKind[entry.kind]) {
73
+ byKind[entry.kind] = [];
74
+ }
75
+ byKind[entry.kind].push(entry);
76
+ }
77
+ // Keep only the 20 most recent per kind
78
+ for (const kind of Object.keys(byKind)) {
79
+ byKind[kind] = byKind[kind].slice(-20);
80
+ }
81
+ const recent = entries.slice(-50);
82
+ return {
83
+ totalEntries: entries.length,
84
+ byKind,
85
+ recentPreferences: recent.filter((e) => e.kind === "preference"),
86
+ recentConsents: recent.filter((e) => e.kind === "consent"),
87
+ recentFeedback: recent.filter((e) => e.kind === "feedback")
88
+ };
89
+ }
90
+ /**
91
+ * Record a preference from user input.
92
+ */
93
+ export async function recordPreference(runsRoot, key, value, source = "explicit") {
94
+ await appendMemory(runsRoot, {
95
+ timestamp: new Date().toISOString(),
96
+ kind: "preference",
97
+ key,
98
+ value,
99
+ source,
100
+ confidence: source === "explicit" ? 1.0 : 0.6
101
+ });
102
+ }
103
+ /**
104
+ * Record a consent signal (user approved or denied a governance action).
105
+ */
106
+ export async function recordConsent(runsRoot, action, approved, context) {
107
+ await appendMemory(runsRoot, {
108
+ timestamp: new Date().toISOString(),
109
+ kind: "consent",
110
+ key: `consent.${action}`,
111
+ value: { approved, context },
112
+ source: "explicit",
113
+ confidence: 1.0
114
+ });
115
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Append-Only Trace Store
3
+ *
4
+ * Aggregates run data over time to build institutional memory. Every governed
5
+ * run's routing decision, cost, failure class, and outcome is appended to a
6
+ * local JSONL trace file. Data is NEVER overwritten — only appended.
7
+ *
8
+ * The trace store feeds:
9
+ * - classifyRoute() with historicalDirectSuccessRate from real past runs
10
+ * - Cost prediction models with actual spend data
11
+ * - Failure pattern detection across runs
12
+ * - Team analytics and optimization recommendations
13
+ *
14
+ * Storage: `<runsRoot>/_martin/trace-log.jsonl` (one JSON object per line)
15
+ */
16
+ export interface TraceEntry {
17
+ /** ISO-8601 timestamp */
18
+ timestamp: string;
19
+ /** Unique loop identifier */
20
+ loopId: string;
21
+ /** The objective that was governed */
22
+ objective: string;
23
+ /** Which engine executed the run */
24
+ engine: string;
25
+ /** Route selected by classifyRoute */
26
+ selectedRoute: "direct" | "manager" | "consensus";
27
+ /** Route classifier confidence (0-1) */
28
+ routeConfidence: number;
29
+ /** Budget allocated (USD) */
30
+ budgetUsd: number;
31
+ /** Actual spend (USD) */
32
+ actualCostUsd: number;
33
+ /** Pre-work burn percentage */
34
+ preWorkBurnPct: number;
35
+ /** Number of attempts */
36
+ attempts: number;
37
+ /** Final loop status */
38
+ status: string;
39
+ /** Final lifecycle state */
40
+ lifecycleState: string;
41
+ /** Failure class of last attempt (if failed) */
42
+ failureClass?: string;
43
+ /** Whether verification passed */
44
+ verificationPassed: boolean;
45
+ /** Number of files changed */
46
+ filesChanged: number;
47
+ /** Working directory (hashed for privacy) */
48
+ workspaceHash: string;
49
+ }
50
+ export interface TraceAggregation {
51
+ totalRuns: number;
52
+ directRuns: number;
53
+ managerRuns: number;
54
+ consensusRuns: number;
55
+ directSuccessRate: number;
56
+ managerSuccessRate: number;
57
+ averageCostUsd: number;
58
+ averagePreWorkBurnPct: number;
59
+ totalSpendUsd: number;
60
+ totalSavedUsd: number;
61
+ topFailureClasses: Array<{
62
+ failureClass: string;
63
+ count: number;
64
+ }>;
65
+ averageAttemptsPerRun: number;
66
+ lastUpdated: string;
67
+ }
68
+ /**
69
+ * Append a trace entry to the local trace store. Never overwrites.
70
+ */
71
+ export declare function appendTraceEntry(runsRoot: string, entry: TraceEntry): Promise<void>;
72
+ /**
73
+ * Read all trace entries from the local trace store.
74
+ */
75
+ export declare function readTraceEntries(runsRoot: string): Promise<TraceEntry[]>;
76
+ /**
77
+ * Aggregate trace entries into summary statistics.
78
+ * Used by classifyRoute() to get historicalDirectSuccessRate.
79
+ */
80
+ export declare function aggregateTraces(entries: TraceEntry[]): TraceAggregation;
81
+ /**
82
+ * Get the historical direct success rate for use in route classification.
83
+ * Returns undefined if insufficient data (< 5 runs).
84
+ */
85
+ export declare function getHistoricalDirectSuccessRate(runsRoot: string): Promise<number | undefined>;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Append-Only Trace Store
3
+ *
4
+ * Aggregates run data over time to build institutional memory. Every governed
5
+ * run's routing decision, cost, failure class, and outcome is appended to a
6
+ * local JSONL trace file. Data is NEVER overwritten — only appended.
7
+ *
8
+ * The trace store feeds:
9
+ * - classifyRoute() with historicalDirectSuccessRate from real past runs
10
+ * - Cost prediction models with actual spend data
11
+ * - Failure pattern detection across runs
12
+ * - Team analytics and optimization recommendations
13
+ *
14
+ * Storage: `<runsRoot>/_martin/trace-log.jsonl` (one JSON object per line)
15
+ */
16
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
17
+ import { join, resolve } from "node:path";
18
+ const TRACE_DIRECTORY = "_martin";
19
+ const TRACE_FILENAME = "trace-log.jsonl";
20
+ function resolveTracePath(runsRoot) {
21
+ return join(resolve(runsRoot), TRACE_DIRECTORY, TRACE_FILENAME);
22
+ }
23
+ /**
24
+ * Append a trace entry to the local trace store. Never overwrites.
25
+ */
26
+ export async function appendTraceEntry(runsRoot, entry) {
27
+ const tracePath = resolveTracePath(runsRoot);
28
+ await mkdir(join(resolve(runsRoot), TRACE_DIRECTORY), { recursive: true });
29
+ const line = JSON.stringify(entry) + "\n";
30
+ await appendFile(tracePath, line, "utf8");
31
+ }
32
+ /**
33
+ * Read all trace entries from the local trace store.
34
+ */
35
+ export async function readTraceEntries(runsRoot) {
36
+ const tracePath = resolveTracePath(runsRoot);
37
+ try {
38
+ const raw = await readFile(tracePath, "utf8");
39
+ return raw
40
+ .split("\n")
41
+ .filter((line) => line.trim().length > 0)
42
+ .map((line) => {
43
+ try {
44
+ return JSON.parse(line);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ })
50
+ .filter((entry) => entry !== null);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ /**
57
+ * Aggregate trace entries into summary statistics.
58
+ * Used by classifyRoute() to get historicalDirectSuccessRate.
59
+ */
60
+ export function aggregateTraces(entries) {
61
+ if (entries.length === 0) {
62
+ return {
63
+ totalRuns: 0,
64
+ directRuns: 0,
65
+ managerRuns: 0,
66
+ consensusRuns: 0,
67
+ directSuccessRate: 0,
68
+ managerSuccessRate: 0,
69
+ averageCostUsd: 0,
70
+ averagePreWorkBurnPct: 0,
71
+ totalSpendUsd: 0,
72
+ totalSavedUsd: 0,
73
+ topFailureClasses: [],
74
+ averageAttemptsPerRun: 0,
75
+ lastUpdated: new Date().toISOString()
76
+ };
77
+ }
78
+ const direct = entries.filter((e) => e.selectedRoute === "direct");
79
+ const manager = entries.filter((e) => e.selectedRoute === "manager");
80
+ const consensus = entries.filter((e) => e.selectedRoute === "consensus");
81
+ const directSuccesses = direct.filter((e) => e.status === "completed" && e.verificationPassed);
82
+ const managerSuccesses = manager.filter((e) => e.status === "completed" && e.verificationPassed);
83
+ const totalCost = entries.reduce((sum, e) => sum + e.actualCostUsd, 0);
84
+ const totalBudget = entries.reduce((sum, e) => sum + e.budgetUsd, 0);
85
+ const totalAttempts = entries.reduce((sum, e) => sum + e.attempts, 0);
86
+ const totalBurnPct = entries.reduce((sum, e) => sum + e.preWorkBurnPct, 0);
87
+ // Count failure classes
88
+ const failureCounts = new Map();
89
+ for (const entry of entries) {
90
+ if (entry.failureClass) {
91
+ failureCounts.set(entry.failureClass, (failureCounts.get(entry.failureClass) ?? 0) + 1);
92
+ }
93
+ }
94
+ const topFailureClasses = [...failureCounts.entries()]
95
+ .map(([failureClass, count]) => ({ failureClass, count }))
96
+ .sort((a, b) => b.count - a.count)
97
+ .slice(0, 10);
98
+ return {
99
+ totalRuns: entries.length,
100
+ directRuns: direct.length,
101
+ managerRuns: manager.length,
102
+ consensusRuns: consensus.length,
103
+ directSuccessRate: direct.length > 0 ? directSuccesses.length / direct.length : 0,
104
+ managerSuccessRate: manager.length > 0 ? managerSuccesses.length / manager.length : 0,
105
+ averageCostUsd: Math.round((totalCost / entries.length) * 100) / 100,
106
+ averagePreWorkBurnPct: Math.round(totalBurnPct / entries.length),
107
+ totalSpendUsd: Math.round(totalCost * 100) / 100,
108
+ totalSavedUsd: Math.round((totalBudget - totalCost) * 100) / 100,
109
+ topFailureClasses,
110
+ averageAttemptsPerRun: Math.round((totalAttempts / entries.length) * 10) / 10,
111
+ lastUpdated: entries[entries.length - 1]?.timestamp ?? new Date().toISOString()
112
+ };
113
+ }
114
+ /**
115
+ * Get the historical direct success rate for use in route classification.
116
+ * Returns undefined if insufficient data (< 5 runs).
117
+ */
118
+ export async function getHistoricalDirectSuccessRate(runsRoot) {
119
+ const entries = await readTraceEntries(runsRoot);
120
+ const directRuns = entries.filter((e) => e.selectedRoute === "direct");
121
+ if (directRuns.length < 5)
122
+ return undefined;
123
+ const successes = directRuns.filter((e) => e.status === "completed" && e.verificationPassed);
124
+ return successes.length / directRuns.length;
125
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Repo Style Analyzer
3
+ *
4
+ * Scans a repository to detect coding conventions, patterns, and style
5
+ * so that governed agent runs produce code matching the existing codebase.
6
+ *
7
+ * Detects:
8
+ * - Naming conventions (camelCase, snake_case, PascalCase, kebab-case)
9
+ * - Test framework (Vitest, Jest, Mocha, node:test)
10
+ * - Package manager (pnpm, npm, yarn, bun)
11
+ * - Module system (ESM, CJS, mixed)
12
+ * - File organization pattern (feature folders, type folders, flat)
13
+ * - TypeScript strictness level
14
+ * - Comment density
15
+ * - Formatting (tabs vs spaces, semicolons, quotes)
16
+ */
17
+ export interface RepoStyleProfile {
18
+ /** Detected package manager */
19
+ packageManager: "pnpm" | "npm" | "yarn" | "bun" | "unknown";
20
+ /** Detected module system */
21
+ moduleSystem: "esm" | "cjs" | "mixed" | "unknown";
22
+ /** Detected test framework */
23
+ testFramework: "vitest" | "jest" | "mocha" | "node-test" | "unknown";
24
+ /** Primary language */
25
+ language: "typescript" | "javascript" | "mixed";
26
+ /** TypeScript strict mode enabled */
27
+ typescriptStrict: boolean;
28
+ /** Dominant naming convention for files */
29
+ fileNaming: "kebab-case" | "camelCase" | "PascalCase" | "snake_case" | "mixed";
30
+ /** Dominant naming convention for variables/functions */
31
+ codeNaming: "camelCase" | "snake_case" | "mixed";
32
+ /** Uses semicolons */
33
+ semicolons: boolean;
34
+ /** Single vs double quotes */
35
+ quotes: "single" | "double" | "mixed";
36
+ /** Indentation style */
37
+ indentation: "tabs" | "2-spaces" | "4-spaces" | "mixed";
38
+ /** Average lines per file (source files) */
39
+ avgLinesPerFile: number;
40
+ /** File organization pattern */
41
+ organization: "feature-folders" | "type-folders" | "flat" | "mixed";
42
+ /** Raw signals for prompt injection */
43
+ promptDirectives: string[];
44
+ }
45
+ /**
46
+ * Analyze a repository and return a style profile.
47
+ */
48
+ export declare function analyzeRepoStyle(rootDir: string): RepoStyleProfile;
49
+ /**
50
+ * Build prompt directives from the style profile.
51
+ * These get injected into the agent's prompt to match repo conventions.
52
+ */
53
+ export declare function buildPromptDirectives(profile: RepoStyleProfile): string[];