@martinloop/mcp 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +112 -107
  2. package/dist/discovery-metadata.d.ts +6 -6
  3. package/dist/discovery-metadata.js +6 -0
  4. package/dist/discovery-support.js +2 -2
  5. package/dist/resources.d.ts +1 -0
  6. package/dist/resources.js +210 -148
  7. package/dist/server-validation.js +2 -0
  8. package/dist/server.js +87 -1
  9. package/dist/tools/eval.js +29 -5
  10. package/dist/tools/execution-policy.d.ts +11 -0
  11. package/dist/tools/execution-policy.js +47 -0
  12. package/dist/tools/run-dossier.js +2 -2
  13. package/dist/tools/run-loop.d.ts +1 -0
  14. package/dist/tools/run-loop.js +15 -2
  15. package/dist/tools/tool-support.d.ts +3 -1
  16. package/dist/tools/tool-support.js +127 -19
  17. package/dist/tools/triage-runs.js +31 -6
  18. package/dist/vendor/adapters/claude-cli.js +100 -21
  19. package/dist/vendor/adapters/cli-bridge.d.ts +16 -0
  20. package/dist/vendor/adapters/cli-bridge.js +92 -2
  21. package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
  22. package/dist/vendor/adapters/codex-launcher.js +124 -10
  23. package/dist/vendor/adapters/openai-compatible.js +6 -6
  24. package/dist/vendor/contracts/adapter-capabilities.d.ts +17 -0
  25. package/dist/vendor/contracts/adapter-capabilities.js +35 -0
  26. package/dist/vendor/contracts/context-graph.d.ts +48 -0
  27. package/dist/vendor/contracts/context-graph.js +26 -0
  28. package/dist/vendor/contracts/execution-policy.d.ts +78 -0
  29. package/dist/vendor/contracts/execution-policy.js +43 -0
  30. package/dist/vendor/contracts/governance.d.ts +6 -3
  31. package/dist/vendor/contracts/identity.d.ts +29 -0
  32. package/dist/vendor/contracts/identity.js +30 -0
  33. package/dist/vendor/contracts/index.d.ts +40 -2
  34. package/dist/vendor/contracts/index.js +5 -0
  35. package/dist/vendor/contracts/observation.d.ts +21 -0
  36. package/dist/vendor/contracts/observation.js +27 -0
  37. package/dist/vendor/contracts/trajectory.d.ts +26 -0
  38. package/dist/vendor/contracts/trajectory.js +27 -0
  39. package/dist/vendor/contracts/verification.d.ts +28 -0
  40. package/dist/vendor/contracts/verification.js +7 -0
  41. package/dist/vendor/core/context-graph.d.ts +3 -0
  42. package/dist/vendor/core/context-graph.js +371 -0
  43. package/dist/vendor/core/identity.d.ts +20 -0
  44. package/dist/vendor/core/identity.js +86 -0
  45. package/dist/vendor/core/index.d.ts +10 -1
  46. package/dist/vendor/core/index.js +131 -10
  47. package/dist/vendor/core/persistence/integrity.js +2 -2
  48. package/dist/vendor/core/policy-compiler.d.ts +2 -0
  49. package/dist/vendor/core/policy-compiler.js +195 -0
  50. package/dist/vendor/core/policy.d.ts +15 -0
  51. package/dist/vendor/core/policy.js +84 -4
  52. package/dist/vendor/core/rollback.js +32 -12
  53. package/dist/vendor/core/routing.d.ts +40 -0
  54. package/dist/vendor/core/routing.js +185 -0
  55. package/dist/vendor/core/trajectory.d.ts +18 -0
  56. package/dist/vendor/core/trajectory.js +295 -0
  57. package/dist/workflow-state.d.ts +16 -0
  58. package/dist/workflow-state.js +1 -1
  59. package/package.json +76 -75
  60. package/server.json +21 -21
@@ -0,0 +1,30 @@
1
+ export function cloneIdentityClaims(claims) {
2
+ return {
3
+ subject: claims.subject,
4
+ issuer: claims.issuer,
5
+ role: claims.role,
6
+ scopes: [...claims.scopes],
7
+ issuedAt: claims.issuedAt,
8
+ ...(claims.expiresAt ? { expiresAt: claims.expiresAt } : {}),
9
+ ...(claims.workspaceId ? { workspaceId: claims.workspaceId } : {}),
10
+ ...(claims.sessionId ? { sessionId: claims.sessionId } : {}),
11
+ ...(claims.metadata ? { metadata: { ...claims.metadata } } : {})
12
+ };
13
+ }
14
+ export function cloneIdentityToken(token) {
15
+ return {
16
+ schemaVersion: token.schemaVersion,
17
+ algorithm: token.algorithm,
18
+ keyId: token.keyId,
19
+ claims: cloneIdentityClaims(token.claims),
20
+ signature: token.signature
21
+ };
22
+ }
23
+ export function cloneIdentityAttestation(attestation) {
24
+ return {
25
+ valid: attestation.valid,
26
+ verifiedAt: attestation.verifiedAt,
27
+ reason: attestation.reason,
28
+ token: cloneIdentityToken(attestation.token)
29
+ };
30
+ }
@@ -9,6 +9,7 @@ export interface LoopTask {
9
9
  objective: string;
10
10
  repoRoot?: string;
11
11
  verificationPlan: string[];
12
+ verificationTimeoutMs?: number;
12
13
  verificationStack?: VerificationStep[];
13
14
  mutationMode?: MutationMode;
14
15
  executionProfile?: ExecutionProfile;
@@ -73,7 +74,7 @@ export interface ReceiptScope {
73
74
  invocationRoot?: string;
74
75
  runsRoot?: string;
75
76
  }
76
- export type ReceiptIntegrityState = "verified" | "unsigned" | "tamper_detected";
77
+ export type ReceiptIntegrityState = "verified" | "unsigned" | "tamper_detected" | "relocated" | "material_missing" | "selector_noncanonical";
77
78
  export interface ReceiptIntegritySummary {
78
79
  state: ReceiptIntegrityState;
79
80
  keyId?: string;
@@ -91,6 +92,29 @@ export interface LoopArtifact {
91
92
  label: string;
92
93
  uri: string;
93
94
  }
95
+ export type CallStage = "routing" | "planning" | "execution" | "verification" | "retry" | "rollback" | "receipt";
96
+ export type AgentRole = "manager" | "router" | "planner" | "worker" | "verifier" | "reviewer" | "fixer" | "system";
97
+ export interface FirstDelta {
98
+ detected: boolean;
99
+ timestampMs?: number;
100
+ filePath?: string;
101
+ changeType?: "create" | "modify" | "delete" | "patch_proposed";
102
+ /** Elapsed ms from run start to first meaningful workspace change. */
103
+ timeToFirstDeltaMs?: number;
104
+ }
105
+ export interface RoutingEconomics {
106
+ preworkCostUsd: number;
107
+ executionCostUsd: number;
108
+ verificationCostUsd: number;
109
+ retryCostUsd: number;
110
+ totalCostUsd: number;
111
+ preworkBurnPct: number;
112
+ timeToFirstDeltaMs?: number;
113
+ firstDelta?: FirstDelta;
114
+ costPerAcceptedChange?: number;
115
+ routeRecommendation?: "same_route" | "direct_worker" | "manager_required" | "consensus_required";
116
+ routeRecommendationReason?: string;
117
+ }
94
118
  export interface LoopAttempt {
95
119
  attemptId: string;
96
120
  index: number;
@@ -101,6 +125,10 @@ export interface LoopAttempt {
101
125
  summary?: string;
102
126
  failureClass?: FailureClass;
103
127
  intervention?: InterventionType;
128
+ /** Actionable diagnosis from failure classification, injected into the next attempt's prompt. */
129
+ diagnosticHint?: string;
130
+ /** Which stage of the run lifecycle this attempt represents. */
131
+ callStage?: CallStage;
104
132
  }
105
133
  export interface LoopEvent {
106
134
  eventId: string;
@@ -127,6 +155,7 @@ export interface LoopRecord {
127
155
  updatedAt: string;
128
156
  receiptScope?: ReceiptScope;
129
157
  receiptIntegrity?: ReceiptIntegritySummary;
158
+ routingEconomics?: RoutingEconomics;
130
159
  }
131
160
  export interface LoopRecordDraft {
132
161
  loopId?: string;
@@ -371,4 +400,13 @@ export interface RollbackOutcomeArtifact {
371
400
  error?: string;
372
401
  }
373
402
  export { createGovernanceSnapshot } from "./governance.js";
374
- export type { GovernanceSnapshot } from "./governance.js";
403
+ export type { DestructiveActionPolicy, GovernanceSnapshot, PolicyProfile, TelemetryDestination } from "./governance.js";
404
+ export { cloneExecutionPolicy } from "./execution-policy.js";
405
+ export type { ExecutionPolicy, ExecutionPolicyCompileInput, ExecutionPolicyConfigInput, ExecutionPolicyDefaults, ExecutionPolicyProvenanceEntry, ExecutionPolicyRequestInput, RoutingPolicy, RoutingMode } from "./execution-policy.js";
406
+ export { DEFAULT_ROUTING_POLICY } from "./execution-policy.js";
407
+ export { cloneContextGraphSnapshot } from "./context-graph.js";
408
+ export type { ContextGraphBuildOptions, ContextGraphEdge, ContextGraphHit, ContextGraphNode, ContextGraphNodeKind, ContextGraphSnapshot, ContextQuery } from "./context-graph.js";
409
+ export { cloneIdentityAttestation, cloneIdentityClaims, cloneIdentityToken } from "./identity.js";
410
+ export type { IdentityAttestation, IdentityClaims, IdentityToken } from "./identity.js";
411
+ export { cloneCircuitBreakDecision, cloneTrajectoryAssessment } from "./trajectory.js";
412
+ export type { CircuitBreakDecision, TrajectoryAssessment, TrajectorySignal } from "./trajectory.js";
@@ -217,3 +217,8 @@ function hasText(value) {
217
217
  return typeof value === "string" && value.trim().length > 0;
218
218
  }
219
219
  export { createGovernanceSnapshot } from "./governance.js";
220
+ export { cloneExecutionPolicy } from "./execution-policy.js";
221
+ export { DEFAULT_ROUTING_POLICY } from "./execution-policy.js";
222
+ export { cloneContextGraphSnapshot } from "./context-graph.js";
223
+ export { cloneIdentityAttestation, cloneIdentityClaims, cloneIdentityToken } from "./identity.js";
224
+ export { cloneCircuitBreakDecision, cloneTrajectoryAssessment } from "./trajectory.js";
@@ -0,0 +1,21 @@
1
+ export type ChangeObservationStatus = "verified" | "mismatch" | "adapter_only" | "repo_only" | "unavailable";
2
+ export interface ChangeObservationEvidence {
3
+ available: boolean;
4
+ changedFiles: string[];
5
+ diffStats?: {
6
+ filesChanged: number;
7
+ addedLines: number;
8
+ deletedLines: number;
9
+ };
10
+ }
11
+ export interface ChangeObservationReconciliation {
12
+ status: ChangeObservationStatus;
13
+ summary: string;
14
+ adapterReported: ChangeObservationEvidence;
15
+ repoObserved: ChangeObservationEvidence;
16
+ effectiveChangedFiles: string[];
17
+ matchedFiles: string[];
18
+ adapterOnlyFiles: string[];
19
+ repoOnlyFiles: string[];
20
+ }
21
+ export declare function cloneChangeObservationReconciliation(input: ChangeObservationReconciliation): ChangeObservationReconciliation;
@@ -0,0 +1,27 @@
1
+ export function cloneChangeObservationReconciliation(input) {
2
+ return {
3
+ status: input.status,
4
+ summary: input.summary,
5
+ adapterReported: cloneObservationEvidence(input.adapterReported),
6
+ repoObserved: cloneObservationEvidence(input.repoObserved),
7
+ effectiveChangedFiles: [...input.effectiveChangedFiles],
8
+ matchedFiles: [...input.matchedFiles],
9
+ adapterOnlyFiles: [...input.adapterOnlyFiles],
10
+ repoOnlyFiles: [...input.repoOnlyFiles]
11
+ };
12
+ }
13
+ function cloneObservationEvidence(input) {
14
+ return {
15
+ available: input.available,
16
+ changedFiles: [...input.changedFiles],
17
+ ...(input.diffStats
18
+ ? {
19
+ diffStats: {
20
+ filesChanged: input.diffStats.filesChanged,
21
+ addedLines: input.diffStats.addedLines,
22
+ deletedLines: input.diffStats.deletedLines
23
+ }
24
+ }
25
+ : {})
26
+ };
27
+ }
@@ -0,0 +1,26 @@
1
+ import type { InterventionType } from "./index.js";
2
+ export type TrajectoryStatus = "healthy" | "watch" | "drifting" | "stalled";
3
+ export type TrajectoryConfidence = "low" | "medium" | "high";
4
+ export type TrajectorySignalCode = "failure_oscillation" | "attempt_repetition" | "verification_stall" | "objective_drift";
5
+ export interface TrajectorySignal {
6
+ code: TrajectorySignalCode;
7
+ severity: "low" | "medium" | "high";
8
+ summary: string;
9
+ attemptIndexes: number[];
10
+ matchedTerms?: string[];
11
+ }
12
+ export interface TrajectoryAssessment {
13
+ status: TrajectoryStatus;
14
+ confidence: TrajectoryConfidence;
15
+ summary: string;
16
+ signals: TrajectorySignal[];
17
+ recommendedIntervention?: InterventionType;
18
+ }
19
+ export interface CircuitBreakDecision {
20
+ shouldStop: boolean;
21
+ reason: string;
22
+ assessment: TrajectoryAssessment;
23
+ recommendedIntervention?: InterventionType;
24
+ }
25
+ export declare function cloneTrajectoryAssessment(assessment: TrajectoryAssessment): TrajectoryAssessment;
26
+ export declare function cloneCircuitBreakDecision(decision: CircuitBreakDecision): CircuitBreakDecision;
@@ -0,0 +1,27 @@
1
+ export function cloneTrajectoryAssessment(assessment) {
2
+ return {
3
+ status: assessment.status,
4
+ confidence: assessment.confidence,
5
+ summary: assessment.summary,
6
+ ...(assessment.recommendedIntervention
7
+ ? { recommendedIntervention: assessment.recommendedIntervention }
8
+ : {}),
9
+ signals: assessment.signals.map((signal) => ({
10
+ code: signal.code,
11
+ severity: signal.severity,
12
+ summary: signal.summary,
13
+ attemptIndexes: [...signal.attemptIndexes],
14
+ ...(signal.matchedTerms ? { matchedTerms: [...signal.matchedTerms] } : {})
15
+ }))
16
+ };
17
+ }
18
+ export function cloneCircuitBreakDecision(decision) {
19
+ return {
20
+ shouldStop: decision.shouldStop,
21
+ reason: decision.reason,
22
+ assessment: cloneTrajectoryAssessment(decision.assessment),
23
+ ...(decision.recommendedIntervention
24
+ ? { recommendedIntervention: decision.recommendedIntervention }
25
+ : {})
26
+ };
27
+ }
@@ -0,0 +1,28 @@
1
+ export type VerifierExitReason = "passed" | "non_zero_exit" | "timed_out" | "spawn_error" | "invalid_command";
2
+ export type VerifierStepType = "lint" | "typecheck" | "test_targeted" | "test_full" | "custom";
3
+ export interface VerifierStepSnapshot {
4
+ command: string;
5
+ type: VerifierStepType;
6
+ fastFail: boolean;
7
+ passed: boolean;
8
+ exitCode: number;
9
+ exitReason: VerifierExitReason;
10
+ startedAt: string;
11
+ completedAt: string;
12
+ durationMs: number;
13
+ stdout?: string;
14
+ stderr?: string;
15
+ }
16
+ export interface VerifierSnapshot {
17
+ passed: boolean;
18
+ summary: string;
19
+ startedAt: string;
20
+ completedAt: string;
21
+ durationMs: number;
22
+ stepCount: number;
23
+ failedStepCount: number;
24
+ commands: string[];
25
+ steps: VerifierStepSnapshot[];
26
+ combinedOutput?: string;
27
+ }
28
+ export declare function cloneVerifierSnapshot(snapshot: VerifierSnapshot): VerifierSnapshot;
@@ -0,0 +1,7 @@
1
+ export function cloneVerifierSnapshot(snapshot) {
2
+ return {
3
+ ...snapshot,
4
+ commands: [...snapshot.commands],
5
+ steps: snapshot.steps.map((step) => ({ ...step }))
6
+ };
7
+ }
@@ -0,0 +1,3 @@
1
+ import { type ContextGraphBuildOptions, type ContextGraphHit, type ContextGraphSnapshot, type ContextQuery } from "../contracts/index.js";
2
+ export declare function buildContextGraphSnapshot(repoRoot: string, options?: ContextGraphBuildOptions): Promise<ContextGraphSnapshot>;
3
+ export declare function queryContextGraph(snapshot: ContextGraphSnapshot, query: ContextQuery): ContextGraphHit[];
@@ -0,0 +1,371 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { extname, join, posix, relative, resolve } from "node:path";
3
+ import { cloneContextGraphSnapshot } from "../contracts/index.js";
4
+ const TEXT_EXTENSIONS = new Set([
5
+ ".ts",
6
+ ".tsx",
7
+ ".js",
8
+ ".jsx",
9
+ ".mjs",
10
+ ".cjs",
11
+ ".json",
12
+ ".md",
13
+ ".yaml",
14
+ ".yml",
15
+ ".txt",
16
+ ".py",
17
+ ".go",
18
+ ".rs",
19
+ ".java",
20
+ ".sh"
21
+ ]);
22
+ const IGNORED_DIRS = new Set([
23
+ ".git",
24
+ "node_modules",
25
+ "dist",
26
+ "build",
27
+ ".next",
28
+ ".turbo",
29
+ "coverage",
30
+ ".pnpm-store",
31
+ ".npm-cache"
32
+ ]);
33
+ export async function buildContextGraphSnapshot(repoRoot, options = {}) {
34
+ const normalizedRoot = resolve(repoRoot);
35
+ const maxFiles = options.maxFiles ?? 600;
36
+ const maxFileBytes = options.maxFileBytes ?? 96_000;
37
+ const nodes = [];
38
+ const state = { count: 0, truncated: false };
39
+ await walkContextGraph(normalizedRoot, normalizedRoot, nodes, state, { maxFiles, maxFileBytes });
40
+ const edges = buildContextGraphEdges(nodes);
41
+ return cloneContextGraphSnapshot({
42
+ schemaVersion: "martin.context-graph.v1",
43
+ repoRoot: normalizedRoot,
44
+ createdAt: new Date().toISOString(),
45
+ nodeCount: nodes.length,
46
+ edgeCount: edges.length,
47
+ truncated: state.truncated,
48
+ nodes,
49
+ edges
50
+ });
51
+ }
52
+ export function queryContextGraph(snapshot, query) {
53
+ const terms = normalizeQueryTerms(query);
54
+ const pathPrefix = query.pathPrefix?.replace(/\\/gu, "/").replace(/^\.\/+/u, "");
55
+ const limit = query.limit ?? 8;
56
+ return snapshot.nodes
57
+ .filter((node) => {
58
+ if (query.kinds && !query.kinds.includes(node.kind)) {
59
+ return false;
60
+ }
61
+ if (pathPrefix && !node.path.startsWith(pathPrefix)) {
62
+ return false;
63
+ }
64
+ return true;
65
+ })
66
+ .map((node) => scoreContextNode(node, terms))
67
+ .filter((hit) => hit !== undefined)
68
+ .sort((left, right) => right.score - left.score || left.path.localeCompare(right.path))
69
+ .slice(0, limit);
70
+ }
71
+ async function walkContextGraph(repoRoot, currentDir, nodes, state, options) {
72
+ if (state.count >= options.maxFiles) {
73
+ state.truncated = true;
74
+ return;
75
+ }
76
+ const entries = await readdir(currentDir, { withFileTypes: true });
77
+ for (const entry of entries) {
78
+ if (state.count >= options.maxFiles) {
79
+ state.truncated = true;
80
+ break;
81
+ }
82
+ const absPath = join(currentDir, entry.name);
83
+ if (entry.isDirectory()) {
84
+ if (!IGNORED_DIRS.has(entry.name)) {
85
+ await walkContextGraph(repoRoot, absPath, nodes, state, options);
86
+ }
87
+ continue;
88
+ }
89
+ if (!entry.isFile()) {
90
+ continue;
91
+ }
92
+ const extension = extname(entry.name).toLowerCase();
93
+ if (!TEXT_EXTENSIONS.has(extension)) {
94
+ continue;
95
+ }
96
+ const content = await readFile(absPath, "utf8").catch(() => undefined);
97
+ if (!content || content.length > options.maxFileBytes) {
98
+ continue;
99
+ }
100
+ const relPath = relative(repoRoot, absPath).replace(/\\/gu, "/");
101
+ nodes.push(createContextNode(relPath, content));
102
+ state.count += 1;
103
+ }
104
+ }
105
+ function createContextNode(path, content) {
106
+ const kind = classifyContextNodeKind(path);
107
+ return {
108
+ nodeId: path,
109
+ path,
110
+ kind,
111
+ ...(inferLanguage(path) ? { language: inferLanguage(path) } : {}),
112
+ sizeBytes: Buffer.byteLength(content, "utf8"),
113
+ headings: extractHeadings(path, content),
114
+ symbols: extractSymbols(content),
115
+ keywords: extractKeywords(path, content),
116
+ imports: extractImports(path, content)
117
+ };
118
+ }
119
+ function buildContextGraphEdges(nodes) {
120
+ const paths = new Set(nodes.map((node) => node.path));
121
+ const edges = [];
122
+ for (const node of nodes) {
123
+ for (const importTarget of node.imports) {
124
+ const resolvedTarget = resolveContextImport(node.path, importTarget, paths);
125
+ if (resolvedTarget) {
126
+ edges.push({
127
+ fromNodeId: node.nodeId,
128
+ toNodeId: resolvedTarget,
129
+ kind: "imports"
130
+ });
131
+ }
132
+ }
133
+ if (node.kind === "document") {
134
+ const documentTargets = node.keywords
135
+ .map((keyword) => nodes.find((candidate) => candidate.path !== node.path && candidate.path.includes(keyword)))
136
+ .filter((candidate) => candidate !== undefined)
137
+ .slice(0, 3);
138
+ for (const target of documentTargets) {
139
+ edges.push({
140
+ fromNodeId: node.nodeId,
141
+ toNodeId: target.nodeId,
142
+ kind: "documents"
143
+ });
144
+ }
145
+ }
146
+ }
147
+ return dedupeEdges(edges);
148
+ }
149
+ function scoreContextNode(node, terms) {
150
+ if (terms.length === 0) {
151
+ return {
152
+ nodeId: node.nodeId,
153
+ path: node.path,
154
+ kind: node.kind,
155
+ score: 1,
156
+ matchedTerms: [],
157
+ reasons: ["default_match"]
158
+ };
159
+ }
160
+ let score = 0;
161
+ const matchedTerms = new Set();
162
+ const reasons = new Set();
163
+ for (const term of terms) {
164
+ if (node.path.toLowerCase().includes(term)) {
165
+ score += 6;
166
+ matchedTerms.add(term);
167
+ reasons.add("path");
168
+ }
169
+ if (node.keywords.includes(term)) {
170
+ score += 3;
171
+ matchedTerms.add(term);
172
+ reasons.add("keyword");
173
+ }
174
+ if (node.symbols.some((symbol) => symbol.toLowerCase().includes(term))) {
175
+ score += 5;
176
+ matchedTerms.add(term);
177
+ reasons.add("symbol");
178
+ }
179
+ if (node.headings.some((heading) => heading.toLowerCase().includes(term))) {
180
+ score += 4;
181
+ matchedTerms.add(term);
182
+ reasons.add("heading");
183
+ }
184
+ if (node.imports.some((importPath) => importPath.toLowerCase().includes(term))) {
185
+ score += 2;
186
+ matchedTerms.add(term);
187
+ reasons.add("import");
188
+ }
189
+ }
190
+ if (score === 0) {
191
+ return undefined;
192
+ }
193
+ return {
194
+ nodeId: node.nodeId,
195
+ path: node.path,
196
+ kind: node.kind,
197
+ score,
198
+ matchedTerms: [...matchedTerms],
199
+ reasons: [...reasons]
200
+ };
201
+ }
202
+ function normalizeQueryTerms(query) {
203
+ const rawTerms = [
204
+ ...(query.text ? tokenize(query.text) : []),
205
+ ...((query.terms ?? []).flatMap((term) => tokenize(term)))
206
+ ];
207
+ return [...new Set(rawTerms)];
208
+ }
209
+ function classifyContextNodeKind(path) {
210
+ const extension = extname(path).toLowerCase();
211
+ if (path.endsWith("package.json") || path.endsWith("tsconfig.json")) {
212
+ return "manifest";
213
+ }
214
+ if (extension === ".md" || extension === ".txt") {
215
+ return "document";
216
+ }
217
+ if (extension === ".sh") {
218
+ return "script";
219
+ }
220
+ if (extension === ".json" || extension === ".yaml" || extension === ".yml") {
221
+ return "config";
222
+ }
223
+ return "source_file";
224
+ }
225
+ function inferLanguage(path) {
226
+ const extension = extname(path).toLowerCase();
227
+ switch (extension) {
228
+ case ".ts":
229
+ case ".tsx":
230
+ return "typescript";
231
+ case ".js":
232
+ case ".jsx":
233
+ case ".mjs":
234
+ case ".cjs":
235
+ return "javascript";
236
+ case ".md":
237
+ return "markdown";
238
+ case ".json":
239
+ return "json";
240
+ case ".yaml":
241
+ case ".yml":
242
+ return "yaml";
243
+ case ".py":
244
+ return "python";
245
+ case ".sh":
246
+ return "shell";
247
+ default:
248
+ return undefined;
249
+ }
250
+ }
251
+ function extractHeadings(path, content) {
252
+ if (extname(path).toLowerCase() !== ".md") {
253
+ return [];
254
+ }
255
+ return content
256
+ .split(/\r?\n/gu)
257
+ .map((line) => line.trim())
258
+ .filter((line) => line.startsWith("#"))
259
+ .map((line) => line.replace(/^#+\s*/u, "").trim())
260
+ .filter(Boolean)
261
+ .slice(0, 12);
262
+ }
263
+ function extractSymbols(content) {
264
+ const out = new Set();
265
+ const patterns = [
266
+ /export\s+(?:async\s+)?function\s+([A-Za-z0-9_]+)/gu,
267
+ /export\s+class\s+([A-Za-z0-9_]+)/gu,
268
+ /export\s+const\s+([A-Za-z0-9_]+)/gu,
269
+ /function\s+([A-Za-z0-9_]+)/gu,
270
+ /class\s+([A-Za-z0-9_]+)/gu,
271
+ /interface\s+([A-Za-z0-9_]+)/gu,
272
+ /type\s+([A-Za-z0-9_]+)/gu
273
+ ];
274
+ for (const pattern of patterns) {
275
+ for (const match of content.matchAll(pattern)) {
276
+ if (match[1]) {
277
+ out.add(match[1]);
278
+ }
279
+ }
280
+ }
281
+ return [...out].slice(0, 16);
282
+ }
283
+ function extractKeywords(path, content) {
284
+ const out = new Set();
285
+ for (const part of path.split(/[\/._-]+/u)) {
286
+ if (part.length >= 3) {
287
+ out.add(part.toLowerCase());
288
+ }
289
+ }
290
+ for (const token of tokenize(content.split(/\r?\n/gu).slice(0, 16).join(" "))) {
291
+ out.add(token);
292
+ }
293
+ return [...out].slice(0, 24);
294
+ }
295
+ function extractImports(path, content) {
296
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extname(path).toLowerCase())) {
297
+ return [];
298
+ }
299
+ const imports = new Set();
300
+ const patterns = [
301
+ /from\s+["']([^"']+)["']/gu,
302
+ /require\(\s*["']([^"']+)["']\s*\)/gu,
303
+ /import\(\s*["']([^"']+)["']\s*\)/gu
304
+ ];
305
+ for (const pattern of patterns) {
306
+ for (const match of content.matchAll(pattern)) {
307
+ const candidate = match[1]?.trim();
308
+ if (candidate) {
309
+ imports.add(candidate);
310
+ }
311
+ }
312
+ }
313
+ return [...imports].slice(0, 24);
314
+ }
315
+ function resolveContextImport(fromPath, importPath, paths) {
316
+ if (!importPath.startsWith(".")) {
317
+ return undefined;
318
+ }
319
+ const fromDirectory = fromPath.includes("/") ? fromPath.slice(0, fromPath.lastIndexOf("/")) : "";
320
+ const base = posix.normalize(posix.join(fromDirectory, importPath)).replace(/^\.\//u, "");
321
+ const extensionlessBase = extname(base) ? base.slice(0, -extname(base).length) : base;
322
+ const candidates = [
323
+ base,
324
+ extensionlessBase,
325
+ `${extensionlessBase}.ts`,
326
+ `${extensionlessBase}.tsx`,
327
+ `${extensionlessBase}.js`,
328
+ `${extensionlessBase}.jsx`,
329
+ `${extensionlessBase}.md`,
330
+ `${base}.ts`,
331
+ `${base}.tsx`,
332
+ `${base}.js`,
333
+ `${base}.jsx`,
334
+ `${base}.md`,
335
+ `${base}/index.ts`,
336
+ `${base}/index.tsx`,
337
+ `${base}/index.js`
338
+ ];
339
+ return candidates.find((candidate) => paths.has(candidate));
340
+ }
341
+ function dedupeEdges(edges) {
342
+ const seen = new Set();
343
+ return edges.filter((edge) => {
344
+ const key = `${edge.kind}:${edge.fromNodeId}:${edge.toNodeId}`;
345
+ if (seen.has(key)) {
346
+ return false;
347
+ }
348
+ seen.add(key);
349
+ return true;
350
+ });
351
+ }
352
+ function tokenize(input) {
353
+ const normalized = input.replace(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase();
354
+ const stopWords = new Set([
355
+ "the",
356
+ "and",
357
+ "for",
358
+ "with",
359
+ "from",
360
+ "that",
361
+ "this",
362
+ "into",
363
+ "only",
364
+ "keep",
365
+ "make",
366
+ "loop",
367
+ "then",
368
+ "when"
369
+ ]);
370
+ return [...new Set(normalized.match(/[a-z0-9_]{3,}/gu) ?? [])].filter((term) => !stopWords.has(term));
371
+ }
@@ -0,0 +1,20 @@
1
+ import { type IdentityAttestation, type IdentityClaims, type IdentityToken } from "../contracts/index.js";
2
+ export interface LocalIdentityAuthority {
3
+ keyId: string;
4
+ algorithm: "ed25519";
5
+ privateKeyPem: string;
6
+ publicKeyPem: string;
7
+ }
8
+ export declare function createLocalIdentityAuthority(input?: {
9
+ keyId?: string;
10
+ }): LocalIdentityAuthority;
11
+ export declare function issueIdentityToken(input: {
12
+ authority: Pick<LocalIdentityAuthority, "keyId" | "algorithm" | "privateKeyPem">;
13
+ claims: IdentityClaims;
14
+ }): IdentityToken;
15
+ export declare function verifyIdentityToken(input: {
16
+ token: IdentityToken;
17
+ publicKeyPem: string;
18
+ now?: string;
19
+ }): IdentityAttestation;
20
+ export declare function hasIdentityScope(token: IdentityToken, requiredScope: string): boolean;