@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.
- package/README.md +112 -107
- package/dist/discovery-metadata.d.ts +6 -6
- package/dist/discovery-metadata.js +6 -0
- package/dist/discovery-support.js +2 -2
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +210 -148
- package/dist/server-validation.js +2 -0
- package/dist/server.js +87 -1
- package/dist/tools/eval.js +29 -5
- package/dist/tools/execution-policy.d.ts +11 -0
- package/dist/tools/execution-policy.js +47 -0
- package/dist/tools/run-dossier.js +2 -2
- package/dist/tools/run-loop.d.ts +1 -0
- package/dist/tools/run-loop.js +15 -2
- package/dist/tools/tool-support.d.ts +3 -1
- package/dist/tools/tool-support.js +127 -19
- package/dist/tools/triage-runs.js +31 -6
- package/dist/vendor/adapters/claude-cli.js +100 -21
- package/dist/vendor/adapters/cli-bridge.d.ts +16 -0
- package/dist/vendor/adapters/cli-bridge.js +92 -2
- package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
- package/dist/vendor/adapters/codex-launcher.js +124 -10
- package/dist/vendor/adapters/openai-compatible.js +6 -6
- package/dist/vendor/contracts/adapter-capabilities.d.ts +17 -0
- package/dist/vendor/contracts/adapter-capabilities.js +35 -0
- package/dist/vendor/contracts/context-graph.d.ts +48 -0
- package/dist/vendor/contracts/context-graph.js +26 -0
- package/dist/vendor/contracts/execution-policy.d.ts +78 -0
- package/dist/vendor/contracts/execution-policy.js +43 -0
- package/dist/vendor/contracts/governance.d.ts +6 -3
- package/dist/vendor/contracts/identity.d.ts +29 -0
- package/dist/vendor/contracts/identity.js +30 -0
- package/dist/vendor/contracts/index.d.ts +40 -2
- package/dist/vendor/contracts/index.js +5 -0
- package/dist/vendor/contracts/observation.d.ts +21 -0
- package/dist/vendor/contracts/observation.js +27 -0
- package/dist/vendor/contracts/trajectory.d.ts +26 -0
- package/dist/vendor/contracts/trajectory.js +27 -0
- package/dist/vendor/contracts/verification.d.ts +28 -0
- package/dist/vendor/contracts/verification.js +7 -0
- package/dist/vendor/core/context-graph.d.ts +3 -0
- package/dist/vendor/core/context-graph.js +371 -0
- package/dist/vendor/core/identity.d.ts +20 -0
- package/dist/vendor/core/identity.js +86 -0
- package/dist/vendor/core/index.d.ts +10 -1
- package/dist/vendor/core/index.js +131 -10
- package/dist/vendor/core/persistence/integrity.js +2 -2
- package/dist/vendor/core/policy-compiler.d.ts +2 -0
- package/dist/vendor/core/policy-compiler.js +195 -0
- package/dist/vendor/core/policy.d.ts +15 -0
- package/dist/vendor/core/policy.js +84 -4
- package/dist/vendor/core/rollback.js +32 -12
- package/dist/vendor/core/routing.d.ts +40 -0
- package/dist/vendor/core/routing.js +185 -0
- package/dist/vendor/core/trajectory.d.ts +18 -0
- package/dist/vendor/core/trajectory.js +295 -0
- package/dist/workflow-state.d.ts +16 -0
- package/dist/workflow-state.js +1 -1
- package/package.json +76 -75
- 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,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;
|