@flyingrobots/graft 0.4.0 → 0.5.0
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/ARCHITECTURE.md +386 -0
- package/CHANGELOG.md +47 -0
- package/CODE_OF_CONDUCT.md +65 -0
- package/README.md +153 -17
- package/bin/graft.js +4 -14
- package/docs/ADVANCED_GUIDE.md +49 -0
- package/docs/CLI.md +43 -0
- package/docs/GUIDE.md +321 -32
- package/docs/MCP.md +44 -0
- package/package.json +15 -4
- package/src/adapters/node-fs.ts +4 -0
- package/src/adapters/node-git.ts +47 -0
- package/src/adapters/node-process-runner.ts +27 -0
- package/src/cli/index-cmd.ts +75 -11
- package/src/cli/init.ts +808 -57
- package/src/cli/main.ts +437 -0
- package/src/contracts/capabilities.ts +341 -0
- package/src/contracts/causal-ontology.ts +622 -0
- package/src/contracts/causal-surface-next-action.ts +18 -0
- package/src/contracts/output-schemas.ts +1169 -0
- package/src/git/diff.ts +25 -21
- package/src/git/target-git-hook-bootstrap.ts +56 -0
- package/src/hooks/posttooluse-read.ts +21 -74
- package/src/hooks/pretooluse-read.ts +20 -56
- package/src/hooks/read-governor.ts +95 -0
- package/src/hooks/read-messages.ts +53 -0
- package/src/mcp/burden.ts +123 -0
- package/src/mcp/cache.ts +51 -0
- package/src/mcp/cached-file.ts +10 -8
- package/src/mcp/context.ts +65 -2
- package/src/mcp/daemon-control-plane.ts +554 -0
- package/src/mcp/daemon-job-scheduler.ts +279 -0
- package/src/mcp/daemon-repos.ts +216 -0
- package/src/mcp/daemon-server.ts +396 -0
- package/src/mcp/daemon-worker-pool.ts +310 -0
- package/src/mcp/daemon-worker-process.ts +52 -0
- package/src/mcp/metrics.ts +108 -1
- package/src/mcp/monitor-tick-job.ts +99 -0
- package/src/mcp/persisted-local-history.ts +1246 -0
- package/src/mcp/persistent-monitor-runtime.ts +549 -0
- package/src/mcp/policy.ts +84 -0
- package/src/mcp/receipt.ts +82 -12
- package/src/mcp/repo-concurrency.ts +318 -0
- package/src/mcp/repo-state.ts +777 -0
- package/src/mcp/repo-tool-job.ts +302 -0
- package/src/mcp/run-capture-config.ts +33 -0
- package/src/mcp/runtime-causal-context.ts +72 -0
- package/src/mcp/runtime-observability.ts +219 -0
- package/src/mcp/runtime-staged-target.ts +161 -0
- package/src/mcp/runtime-workspace-overlay.ts +255 -0
- package/src/mcp/semantic-transition-guidance.ts +60 -0
- package/src/mcp/semantic-transition-summary.ts +130 -0
- package/src/mcp/server.ts +696 -55
- package/src/mcp/stdio-server.ts +12 -0
- package/src/mcp/stdio.ts +2 -5
- package/src/mcp/tools/activity-view.ts +325 -0
- package/src/mcp/tools/causal-attach.ts +67 -0
- package/src/mcp/tools/causal-status.ts +58 -0
- package/src/mcp/tools/changed-since.ts +13 -11
- package/src/mcp/tools/code-find.ts +164 -0
- package/src/mcp/tools/code-refs.ts +466 -0
- package/src/mcp/tools/code-show.ts +252 -0
- package/src/mcp/tools/daemon-monitors.ts +14 -0
- package/src/mcp/tools/daemon-repos.ts +22 -0
- package/src/mcp/tools/daemon-sessions.ts +14 -0
- package/src/mcp/tools/daemon-status.ts +12 -0
- package/src/mcp/tools/doctor.ts +45 -2
- package/src/mcp/tools/explain.ts +4 -0
- package/src/mcp/tools/file-outline.ts +7 -3
- package/src/mcp/tools/git-files.ts +73 -0
- package/src/mcp/tools/graft-diff.ts +12 -4
- package/src/mcp/tools/map.ts +92 -38
- package/src/mcp/tools/monitor-pause.ts +18 -0
- package/src/mcp/tools/monitor-resume.ts +18 -0
- package/src/mcp/tools/monitor-start.ts +20 -0
- package/src/mcp/tools/monitor-stop.ts +18 -0
- package/src/mcp/tools/precision-match.ts +51 -0
- package/src/mcp/tools/precision-query.ts +127 -0
- package/src/mcp/tools/precision.ts +312 -0
- package/src/mcp/tools/run-capture.ts +126 -44
- package/src/mcp/tools/safe-read.ts +14 -12
- package/src/mcp/tools/since.ts +7 -2
- package/src/mcp/tools/state.ts +11 -3
- package/src/mcp/tools/stats.ts +5 -1
- package/src/mcp/tools/workspace-authorizations.ts +14 -0
- package/src/mcp/tools/workspace-authorize.ts +20 -0
- package/src/mcp/tools/workspace-bind.ts +25 -0
- package/src/mcp/tools/workspace-rebind.ts +25 -0
- package/src/mcp/tools/workspace-revoke.ts +18 -0
- package/src/mcp/tools/workspace-status.ts +12 -0
- package/src/mcp/warp-pool.ts +36 -0
- package/src/mcp/workspace-router.ts +984 -0
- package/src/operations/file-outline.ts +12 -2
- package/src/operations/graft-diff.ts +56 -10
- package/src/operations/safe-read.ts +27 -4
- package/src/operations/state.ts +6 -9
- package/src/parser/lang.ts +19 -3
- package/src/parser/outline.ts +191 -2
- package/src/parser/types.ts +9 -1
- package/src/policy/types.ts +4 -3
- package/src/ports/filesystem.ts +1 -0
- package/src/ports/git.ts +16 -0
- package/src/ports/process-runner.ts +22 -0
- package/src/release/security-gate.ts +102 -0
- package/src/session/tracker.ts +31 -0
- package/src/version.ts +3 -0
- package/src/warp/indexer.ts +171 -56
- package/src/warp/observers.ts +1 -1
- package/src/warp/open.ts +4 -3
- package/src/warp/plumbing.d.ts +5 -1
- package/src/warp/writer-id.ts +30 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { runMonitorTickJob, type MonitorTickWorkerJob, type MonitorTickWorkerResult } from "./monitor-tick-job.js";
|
|
2
|
+
import { runRepoToolJob, type RepoToolWorkerJob, type RepoToolWorkerResult } from "./repo-tool-job.js";
|
|
3
|
+
|
|
4
|
+
interface MonitorTickWorkerRequest {
|
|
5
|
+
readonly requestId: string;
|
|
6
|
+
readonly kind: "monitor_tick";
|
|
7
|
+
readonly job: MonitorTickWorkerJob;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RepoToolWorkerRequest {
|
|
11
|
+
readonly requestId: string;
|
|
12
|
+
readonly kind: "repo_tool";
|
|
13
|
+
readonly job: RepoToolWorkerJob;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface WorkerSuccessMessage {
|
|
17
|
+
readonly requestId: string;
|
|
18
|
+
readonly ok: true;
|
|
19
|
+
readonly result: MonitorTickWorkerResult | RepoToolWorkerResult;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface WorkerFailureMessage {
|
|
23
|
+
readonly requestId: string;
|
|
24
|
+
readonly ok: false;
|
|
25
|
+
readonly error: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type WorkerRequest = MonitorTickWorkerRequest | RepoToolWorkerRequest;
|
|
29
|
+
type WorkerResponse = WorkerSuccessMessage | WorkerFailureMessage;
|
|
30
|
+
|
|
31
|
+
process.on("message", (request: WorkerRequest) => {
|
|
32
|
+
void (async () => {
|
|
33
|
+
try {
|
|
34
|
+
const result = request.kind === "monitor_tick"
|
|
35
|
+
? await runMonitorTickJob(request.job)
|
|
36
|
+
: await runRepoToolJob(request.job);
|
|
37
|
+
const response: WorkerResponse = {
|
|
38
|
+
requestId: request.requestId,
|
|
39
|
+
ok: true,
|
|
40
|
+
result,
|
|
41
|
+
};
|
|
42
|
+
process.send?.(response);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const response: WorkerResponse = {
|
|
45
|
+
requestId: request.requestId,
|
|
46
|
+
ok: false,
|
|
47
|
+
error: error instanceof Error ? error.message : String(error),
|
|
48
|
+
};
|
|
49
|
+
process.send?.(response);
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
});
|
package/src/mcp/metrics.ts
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
// Session metrics — replaces loose counters in server.ts
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
import type { McpToolName } from "../contracts/output-schemas.js";
|
|
6
|
+
import {
|
|
7
|
+
emptyBurdenByKind,
|
|
8
|
+
freezeBurdenByKind,
|
|
9
|
+
burdenKindForTool,
|
|
10
|
+
cloneBurdenByKind,
|
|
11
|
+
type BurdenByKind,
|
|
12
|
+
} from "./burden.js";
|
|
13
|
+
|
|
5
14
|
export interface MetricsSnapshot {
|
|
6
15
|
readonly reads: number;
|
|
7
16
|
readonly outlines: number;
|
|
@@ -9,6 +18,17 @@ export interface MetricsSnapshot {
|
|
|
9
18
|
readonly cacheHits: number;
|
|
10
19
|
readonly bytesReturned: number;
|
|
11
20
|
readonly bytesAvoided: number;
|
|
21
|
+
readonly burdenByKind: Readonly<BurdenByKind>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MetricsDelta {
|
|
25
|
+
readonly reads: number;
|
|
26
|
+
readonly outlines: number;
|
|
27
|
+
readonly refusals: number;
|
|
28
|
+
readonly cacheHits: number;
|
|
29
|
+
readonly bytesReturned: number;
|
|
30
|
+
readonly bytesAvoided: number;
|
|
31
|
+
readonly burdenByKind: Readonly<BurdenByKind>;
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
export class Metrics {
|
|
@@ -18,6 +38,19 @@ export class Metrics {
|
|
|
18
38
|
private totalCacheHits = 0;
|
|
19
39
|
private totalBytesAvoidedByCache = 0;
|
|
20
40
|
private cumulativeBytesReturned = 0;
|
|
41
|
+
private burdenByKind: BurdenByKind = emptyBurdenByKind();
|
|
42
|
+
|
|
43
|
+
static fromSnapshot(snapshot: MetricsSnapshot): Metrics {
|
|
44
|
+
const metrics = new Metrics();
|
|
45
|
+
metrics.totalReads = snapshot.reads;
|
|
46
|
+
metrics.totalOutlines = snapshot.outlines;
|
|
47
|
+
metrics.totalRefusals = snapshot.refusals;
|
|
48
|
+
metrics.totalCacheHits = snapshot.cacheHits;
|
|
49
|
+
metrics.totalBytesAvoidedByCache = snapshot.bytesAvoided;
|
|
50
|
+
metrics.cumulativeBytesReturned = snapshot.bytesReturned;
|
|
51
|
+
metrics.burdenByKind = cloneBurdenByKind(snapshot.burdenByKind);
|
|
52
|
+
return metrics;
|
|
53
|
+
}
|
|
21
54
|
|
|
22
55
|
recordRead(): void {
|
|
23
56
|
this.totalReads++;
|
|
@@ -36,8 +69,48 @@ export class Metrics {
|
|
|
36
69
|
this.totalBytesAvoidedByCache += bytesAvoided;
|
|
37
70
|
}
|
|
38
71
|
|
|
39
|
-
|
|
72
|
+
recordToolResult(tool: McpToolName, n: number): void {
|
|
40
73
|
this.cumulativeBytesReturned += n;
|
|
74
|
+
const kind = burdenKindForTool(tool);
|
|
75
|
+
const current = this.burdenByKind[kind];
|
|
76
|
+
this.burdenByKind = {
|
|
77
|
+
...this.burdenByKind,
|
|
78
|
+
[kind]: {
|
|
79
|
+
calls: current.calls + 1,
|
|
80
|
+
bytesReturned: current.bytesReturned + n,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
applyDelta(delta: MetricsDelta): void {
|
|
86
|
+
this.totalReads += delta.reads;
|
|
87
|
+
this.totalOutlines += delta.outlines;
|
|
88
|
+
this.totalRefusals += delta.refusals;
|
|
89
|
+
this.totalCacheHits += delta.cacheHits;
|
|
90
|
+
this.totalBytesAvoidedByCache += delta.bytesAvoided;
|
|
91
|
+
this.cumulativeBytesReturned += delta.bytesReturned;
|
|
92
|
+
this.burdenByKind = {
|
|
93
|
+
read: {
|
|
94
|
+
calls: this.burdenByKind.read.calls + delta.burdenByKind.read.calls,
|
|
95
|
+
bytesReturned: this.burdenByKind.read.bytesReturned + delta.burdenByKind.read.bytesReturned,
|
|
96
|
+
},
|
|
97
|
+
search: {
|
|
98
|
+
calls: this.burdenByKind.search.calls + delta.burdenByKind.search.calls,
|
|
99
|
+
bytesReturned: this.burdenByKind.search.bytesReturned + delta.burdenByKind.search.bytesReturned,
|
|
100
|
+
},
|
|
101
|
+
shell: {
|
|
102
|
+
calls: this.burdenByKind.shell.calls + delta.burdenByKind.shell.calls,
|
|
103
|
+
bytesReturned: this.burdenByKind.shell.bytesReturned + delta.burdenByKind.shell.bytesReturned,
|
|
104
|
+
},
|
|
105
|
+
state: {
|
|
106
|
+
calls: this.burdenByKind.state.calls + delta.burdenByKind.state.calls,
|
|
107
|
+
bytesReturned: this.burdenByKind.state.bytesReturned + delta.burdenByKind.state.bytesReturned,
|
|
108
|
+
},
|
|
109
|
+
diagnostic: {
|
|
110
|
+
calls: this.burdenByKind.diagnostic.calls + delta.burdenByKind.diagnostic.calls,
|
|
111
|
+
bytesReturned: this.burdenByKind.diagnostic.bytesReturned + delta.burdenByKind.diagnostic.bytesReturned,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
41
114
|
}
|
|
42
115
|
|
|
43
116
|
snapshot(): MetricsSnapshot {
|
|
@@ -48,6 +121,40 @@ export class Metrics {
|
|
|
48
121
|
cacheHits: this.totalCacheHits,
|
|
49
122
|
bytesReturned: this.cumulativeBytesReturned,
|
|
50
123
|
bytesAvoided: this.totalBytesAvoidedByCache,
|
|
124
|
+
burdenByKind: freezeBurdenByKind(cloneBurdenByKind(this.burdenByKind)),
|
|
51
125
|
};
|
|
52
126
|
}
|
|
53
127
|
}
|
|
128
|
+
|
|
129
|
+
export function diffMetrics(before: MetricsSnapshot, after: MetricsSnapshot): MetricsDelta {
|
|
130
|
+
return {
|
|
131
|
+
reads: after.reads - before.reads,
|
|
132
|
+
outlines: after.outlines - before.outlines,
|
|
133
|
+
refusals: after.refusals - before.refusals,
|
|
134
|
+
cacheHits: after.cacheHits - before.cacheHits,
|
|
135
|
+
bytesReturned: after.bytesReturned - before.bytesReturned,
|
|
136
|
+
bytesAvoided: after.bytesAvoided - before.bytesAvoided,
|
|
137
|
+
burdenByKind: freezeBurdenByKind({
|
|
138
|
+
read: {
|
|
139
|
+
calls: after.burdenByKind.read.calls - before.burdenByKind.read.calls,
|
|
140
|
+
bytesReturned: after.burdenByKind.read.bytesReturned - before.burdenByKind.read.bytesReturned,
|
|
141
|
+
},
|
|
142
|
+
search: {
|
|
143
|
+
calls: after.burdenByKind.search.calls - before.burdenByKind.search.calls,
|
|
144
|
+
bytesReturned: after.burdenByKind.search.bytesReturned - before.burdenByKind.search.bytesReturned,
|
|
145
|
+
},
|
|
146
|
+
shell: {
|
|
147
|
+
calls: after.burdenByKind.shell.calls - before.burdenByKind.shell.calls,
|
|
148
|
+
bytesReturned: after.burdenByKind.shell.bytesReturned - before.burdenByKind.shell.bytesReturned,
|
|
149
|
+
},
|
|
150
|
+
state: {
|
|
151
|
+
calls: after.burdenByKind.state.calls - before.burdenByKind.state.calls,
|
|
152
|
+
bytesReturned: after.burdenByKind.state.bytesReturned - before.burdenByKind.state.bytesReturned,
|
|
153
|
+
},
|
|
154
|
+
diagnostic: {
|
|
155
|
+
calls: after.burdenByKind.diagnostic.calls - before.burdenByKind.diagnostic.calls,
|
|
156
|
+
bytesReturned: after.burdenByKind.diagnostic.bytesReturned - before.burdenByKind.diagnostic.bytesReturned,
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { indexCommits } from "../warp/indexer.js";
|
|
2
|
+
import { nodeGit } from "../adapters/node-git.js";
|
|
3
|
+
import { openWarp } from "../warp/open.js";
|
|
4
|
+
|
|
5
|
+
export interface MonitorTickWorkerJob {
|
|
6
|
+
readonly repoId: string;
|
|
7
|
+
readonly worktreeRoot: string;
|
|
8
|
+
readonly writerId: string;
|
|
9
|
+
readonly lastIndexedCommit: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type MonitorTickWorkerResult =
|
|
13
|
+
| {
|
|
14
|
+
readonly ok: true;
|
|
15
|
+
readonly headAtStart: string | null;
|
|
16
|
+
readonly currentHead: string | null;
|
|
17
|
+
readonly lastIndexedCommit: string | null;
|
|
18
|
+
readonly backlogCommits: number;
|
|
19
|
+
readonly commitsIndexed: number;
|
|
20
|
+
readonly patchesWritten: number;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
readonly ok: false;
|
|
24
|
+
readonly error: string;
|
|
25
|
+
readonly currentHead: string | null;
|
|
26
|
+
readonly backlogCommits: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function readGit(cwd: string, args: readonly string[]): Promise<string | null> {
|
|
30
|
+
const result = await nodeGit.run({ cwd, args });
|
|
31
|
+
if (result.error !== undefined || result.status !== 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const trimmed = result.stdout.trim();
|
|
35
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readHeadCommit(cwd: string): Promise<string | null> {
|
|
39
|
+
return readGit(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function countPendingCommits(
|
|
43
|
+
cwd: string,
|
|
44
|
+
from: string | null,
|
|
45
|
+
to: string | null,
|
|
46
|
+
): Promise<number> {
|
|
47
|
+
if (to === null) return 0;
|
|
48
|
+
const range = from === null ? to : `${from}..${to}`;
|
|
49
|
+
const output = await readGit(cwd, ["rev-list", "--count", range]);
|
|
50
|
+
if (output === null) return 0;
|
|
51
|
+
const parsed = Number.parseInt(output, 10);
|
|
52
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runMonitorTickJob(job: MonitorTickWorkerJob): Promise<MonitorTickWorkerResult> {
|
|
56
|
+
const headAtStart = await readHeadCommit(job.worktreeRoot);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const warp = await openWarp({
|
|
60
|
+
cwd: job.worktreeRoot,
|
|
61
|
+
writerId: job.writerId,
|
|
62
|
+
});
|
|
63
|
+
const result = await indexCommits(warp, {
|
|
64
|
+
cwd: job.worktreeRoot,
|
|
65
|
+
git: nodeGit,
|
|
66
|
+
...(job.lastIndexedCommit !== null ? { from: job.lastIndexedCommit } : {}),
|
|
67
|
+
...(headAtStart !== null ? { to: headAtStart } : {}),
|
|
68
|
+
});
|
|
69
|
+
const currentHead = await readHeadCommit(job.worktreeRoot);
|
|
70
|
+
const lastIndexedCommit = headAtStart ?? job.lastIndexedCommit;
|
|
71
|
+
const backlogCommits = await countPendingCommits(
|
|
72
|
+
job.worktreeRoot,
|
|
73
|
+
lastIndexedCommit,
|
|
74
|
+
currentHead,
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
headAtStart,
|
|
79
|
+
currentHead,
|
|
80
|
+
lastIndexedCommit,
|
|
81
|
+
backlogCommits,
|
|
82
|
+
commitsIndexed: result.commitsIndexed,
|
|
83
|
+
patchesWritten: result.patchesWritten,
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const currentHead = await readHeadCommit(job.worktreeRoot);
|
|
87
|
+
const backlogCommits = await countPendingCommits(
|
|
88
|
+
job.worktreeRoot,
|
|
89
|
+
job.lastIndexedCommit,
|
|
90
|
+
currentHead,
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
currentHead,
|
|
96
|
+
backlogCommits,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|