@flyingrobots/graft 0.3.1 → 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/CHANGELOG.md +45 -0
- package/README.md +52 -1
- package/bin/graft.js +36 -8
- package/docs/GUIDE.md +14 -0
- package/package.json +13 -9
- package/src/cli/init.ts +112 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.3.5] - 2026-04-05
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **CI**: use `npx npm@latest` for OIDC trusted publishing — avoids
|
|
13
|
+
self-upgrade breakage on Node 22's bundled npm.
|
|
14
|
+
|
|
15
|
+
## [0.3.4] - 2026-04-05
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **CI**: upgrade npm CLI to >=11.5.1 for OIDC trusted publishing.
|
|
20
|
+
|
|
21
|
+
## [0.3.3] - 2026-04-05
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **CI**: use `npm publish` instead of `pnpm publish` for OIDC
|
|
26
|
+
provenance.
|
|
27
|
+
|
|
28
|
+
## [0.3.2] - 2026-04-05
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`graft init`**: zero-friction project onboarding. Scaffolds
|
|
33
|
+
`.graftignore`, adds `.graft/` to `.gitignore`, generates
|
|
34
|
+
`CLAUDE.md` snippet instructing agents to prefer graft tools,
|
|
35
|
+
and prints Claude Code hook config. Idempotent.
|
|
36
|
+
- **CI**: release workflow attaches npm tarball + SHA256SUMS to
|
|
37
|
+
GitHub releases as downloadable assets.
|
|
38
|
+
- **CI**: npm publish via OIDC provenance (no secret needed).
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **CLI bootstrap**: `bin/graft.js` resolves tsx from the package's
|
|
43
|
+
own `node_modules`, so `graft init` works from any directory.
|
|
44
|
+
- **Docs**: regenerated README, GUIDE, BEARING, and VISION signposts.
|
|
45
|
+
- **package.json**: added `publishConfig`, `homepage`, `bugs`,
|
|
46
|
+
`packageManager`, upgraded keywords for MCP/agent discovery.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **CI**: removed pnpm version override that conflicted with
|
|
51
|
+
`packageManager` field.
|
|
52
|
+
|
|
8
53
|
## [0.3.1] - 2026-04-05
|
|
9
54
|
|
|
10
55
|
### Changed
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ tools (outlines, diffs, symbol history) are useful to anyone.
|
|
|
10
10
|
|
|
11
11
|
## Why
|
|
12
12
|
|
|
13
|
-
Empirical analysis of 1,091 real coding sessions (Blacklight) found
|
|
13
|
+
Empirical analysis of 1,091 real coding sessions ([Blacklight](https://github.com/flyingrobots/blacklight)) found
|
|
14
14
|
that **Read accounts for 96.2 GB of context burden** — 6.6x all
|
|
15
15
|
other tools combined. 58% of reads are full-file. The fattest 2.4%
|
|
16
16
|
of reads produce 24% of raw bytes. Dynamic read caps + session
|
|
@@ -36,6 +36,16 @@ docker run -i --rm -v "$PWD:/workspace" flyingrobots/graft
|
|
|
36
36
|
|
|
37
37
|
## Quick start
|
|
38
38
|
|
|
39
|
+
```bash
|
|
40
|
+
npx @flyingrobots/graft init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Scaffolds `.graftignore`, adds `.graft/` to `.gitignore`, generates
|
|
44
|
+
a `CLAUDE.md` snippet telling agents to prefer graft tools, and
|
|
45
|
+
prints Claude Code hook config.
|
|
46
|
+
|
|
47
|
+
Then add graft to your MCP config:
|
|
48
|
+
|
|
39
49
|
```json
|
|
40
50
|
{
|
|
41
51
|
"mcpServers": {
|
|
@@ -96,6 +106,47 @@ is structured JSON.
|
|
|
96
106
|
| `doctor` | Runtime health check |
|
|
97
107
|
| `stats` | Decision metrics summary |
|
|
98
108
|
|
|
109
|
+
## Claude Code hooks
|
|
110
|
+
|
|
111
|
+
Two hooks work alongside the MCP server to govern native `Read`
|
|
112
|
+
calls — a safety net for when agents bypass graft's tools:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"hooks": {
|
|
117
|
+
"PreToolUse": [
|
|
118
|
+
{
|
|
119
|
+
"matcher": "Read",
|
|
120
|
+
"hooks": [
|
|
121
|
+
{
|
|
122
|
+
"type": "command",
|
|
123
|
+
"command": "node --import tsx node_modules/@flyingrobots/graft/src/hooks/pretooluse-read.ts"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"PostToolUse": [
|
|
129
|
+
{
|
|
130
|
+
"matcher": "Read",
|
|
131
|
+
"hooks": [
|
|
132
|
+
{
|
|
133
|
+
"type": "command",
|
|
134
|
+
"command": "node --import tsx node_modules/@flyingrobots/graft/src/hooks/posttooluse-read.ts"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Add to `.claude/settings.json` in your project root.
|
|
144
|
+
**PreToolUse** blocks banned files before the read.
|
|
145
|
+
**PostToolUse** shows the agent what `safe_read` would have saved.
|
|
146
|
+
|
|
147
|
+
See the **[Setup Guide](docs/GUIDE.md)** for full details on hooks,
|
|
148
|
+
per-editor MCP config, `.graftignore`, and troubleshooting.
|
|
149
|
+
|
|
99
150
|
## Reason codes
|
|
100
151
|
|
|
101
152
|
Every refusal or policy decision includes a machine-readable reason
|
package/bin/graft.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Graft
|
|
4
|
-
//
|
|
3
|
+
// Graft — context governor for coding agents
|
|
4
|
+
// Bootstrap: re-exec with tsx loader resolved from the package's own deps.
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
// If already running under tsx, proceed directly
|
|
15
|
+
if (process.env.__GRAFT_TSX_LOADED === "1") {
|
|
16
|
+
const command = process.argv[2];
|
|
17
|
+
if (command === "init") {
|
|
18
|
+
const { runInit } = await import("../src/cli/init.js");
|
|
19
|
+
runInit();
|
|
20
|
+
} else {
|
|
21
|
+
const { createGraftServer } = await import("../src/mcp/server.js");
|
|
22
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
23
|
+
const graft = createGraftServer();
|
|
24
|
+
const transport = new StdioServerTransport();
|
|
25
|
+
await graft.getMcpServer().connect(transport);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
// Re-exec with tsx loader from our own node_modules
|
|
29
|
+
const tsxPath = require.resolve("tsx/esm");
|
|
30
|
+
const script = join(__dirname, "graft.js");
|
|
31
|
+
try {
|
|
32
|
+
execFileSync(process.execPath, ["--import", tsxPath, script, ...process.argv.slice(2)], {
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
env: { ...process.env, __GRAFT_TSX_LOADED: "1" },
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
process.exit(err?.status ?? 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/docs/GUIDE.md
CHANGED
|
@@ -12,6 +12,20 @@ Or run without installing:
|
|
|
12
12
|
npx @flyingrobots/graft
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Quick setup
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @flyingrobots/graft init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Scaffolds your project for graft in one command:
|
|
22
|
+
- Creates `.graftignore` (template with examples)
|
|
23
|
+
- Adds `.graft/` to `.gitignore`
|
|
24
|
+
- Generates a `CLAUDE.md` snippet instructing agents to prefer graft tools
|
|
25
|
+
- Prints Claude Code hook config for manual setup
|
|
26
|
+
|
|
27
|
+
Idempotent — safe to run again without duplicating entries.
|
|
28
|
+
|
|
15
29
|
## MCP Configuration
|
|
16
30
|
|
|
17
31
|
Graft runs as an MCP server over stdio. Add it to your editor or
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flyingrobots/graft",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Context governor for coding agents — MCP server with policy-enforced reads, structural outlines, and session tracking",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.30.0",
|
|
6
7
|
"bin": {
|
|
7
8
|
"graft": "./bin/graft.js",
|
|
8
9
|
"git-graft": "./bin/graft.js"
|
|
@@ -16,6 +17,14 @@
|
|
|
16
17
|
"README.md",
|
|
17
18
|
"CHANGELOG.md"
|
|
18
19
|
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.build.json",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"pack:check": "pnpm pack --dry-run"
|
|
27
|
+
},
|
|
19
28
|
"engines": {
|
|
20
29
|
"node": ">=20.11.0"
|
|
21
30
|
},
|
|
@@ -65,12 +74,7 @@
|
|
|
65
74
|
"typescript-eslint": "^8.58.0",
|
|
66
75
|
"vitest": "^4.1.2"
|
|
67
76
|
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"test": "vitest run",
|
|
71
|
-
"test:watch": "vitest",
|
|
72
|
-
"lint": "eslint .",
|
|
73
|
-
"typecheck": "tsc --noEmit",
|
|
74
|
-
"pack:check": "pnpm pack --dry-run"
|
|
77
|
+
"pnpm": {
|
|
78
|
+
"onlyBuiltDependencies": []
|
|
75
79
|
}
|
|
76
|
-
}
|
|
80
|
+
}
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
const GRAFTIGNORE_TEMPLATE = `# Graft ignore patterns — files matching these are refused by safe_read.
|
|
5
|
+
# Syntax: same as .gitignore (glob matching via picomatch).
|
|
6
|
+
|
|
7
|
+
# Examples:
|
|
8
|
+
# *.generated.ts
|
|
9
|
+
# vendor/**
|
|
10
|
+
# data/**/*.json
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const AGENT_SNIPPET = `## File reads
|
|
14
|
+
|
|
15
|
+
This project uses [graft](https://github.com/flyingrobots/graft) as
|
|
16
|
+
a context governor. Prefer graft's MCP tools over native file reads:
|
|
17
|
+
|
|
18
|
+
- Use \`safe_read\` instead of \`Read\` for file contents
|
|
19
|
+
- Use \`file_outline\` to see structure before reading
|
|
20
|
+
- Use \`read_range\` with jump table entries for targeted reads
|
|
21
|
+
- Use \`graft_diff\` instead of \`git diff\` for structural changes
|
|
22
|
+
- Use \`explain\` if you get an unfamiliar reason code
|
|
23
|
+
- Call \`set_budget\` at session start if context is tight
|
|
24
|
+
|
|
25
|
+
These tools enforce read policy, cache observations, and track
|
|
26
|
+
session metrics. Native reads bypass all of that.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const GITIGNORE_ENTRY = "\n# Graft runtime data\n.graft/\n";
|
|
30
|
+
|
|
31
|
+
const HOOKS_CONFIG = `
|
|
32
|
+
Add to .claude/settings.json for Claude Code hook integration:
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
"hooks": {
|
|
36
|
+
"PreToolUse": [
|
|
37
|
+
{
|
|
38
|
+
"matcher": "Read",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "node --import tsx node_modules/@flyingrobots/graft/src/hooks/pretooluse-read.ts"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"PostToolUse": [
|
|
48
|
+
{
|
|
49
|
+
"matcher": "Read",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "node --import tsx node_modules/@flyingrobots/graft/src/hooks/posttooluse-read.ts"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
function writeIfMissing(filePath: string, content: string, label: string): void {
|
|
63
|
+
if (fs.existsSync(filePath)) {
|
|
64
|
+
console.log(` exists ${label}`);
|
|
65
|
+
} else {
|
|
66
|
+
fs.writeFileSync(filePath, content);
|
|
67
|
+
console.log(` create ${label}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function appendIfMissing(filePath: string, marker: string, content: string, label: string): void {
|
|
72
|
+
if (fs.existsSync(filePath)) {
|
|
73
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
74
|
+
if (existing.includes(marker)) {
|
|
75
|
+
console.log(` exists ${label} (already has graft entry)`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
fs.appendFileSync(filePath, content);
|
|
79
|
+
console.log(` append ${label}`);
|
|
80
|
+
} else {
|
|
81
|
+
fs.writeFileSync(filePath, content.trimStart());
|
|
82
|
+
console.log(` create ${label}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function runInit(): void {
|
|
87
|
+
const cwd = process.cwd();
|
|
88
|
+
console.log(`\nInitializing graft in ${cwd}\n`);
|
|
89
|
+
|
|
90
|
+
// 1. .graftignore
|
|
91
|
+
writeIfMissing(path.join(cwd, ".graftignore"), GRAFTIGNORE_TEMPLATE, ".graftignore");
|
|
92
|
+
|
|
93
|
+
// 2. .gitignore — append .graft/
|
|
94
|
+
appendIfMissing(path.join(cwd, ".gitignore"), ".graft/", GITIGNORE_ENTRY, ".gitignore");
|
|
95
|
+
|
|
96
|
+
// 3. CLAUDE.md — append agent instructions snippet
|
|
97
|
+
appendIfMissing(path.join(cwd, "CLAUDE.md"), "safe_read", "\n" + AGENT_SNIPPET, "CLAUDE.md");
|
|
98
|
+
|
|
99
|
+
// 4. Print hooks config for manual setup
|
|
100
|
+
console.log(HOOKS_CONFIG);
|
|
101
|
+
|
|
102
|
+
console.log("Done. Add graft to your MCP config:\n");
|
|
103
|
+
console.log(` {
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"graft": {
|
|
106
|
+
"command": "npx",
|
|
107
|
+
"args": ["-y", "@flyingrobots/graft"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`);
|
|
112
|
+
}
|