frappe-builder 1.1.0-dev.18 → 1.1.0-dev.21
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/.fb/state.db +0 -0
- package/.frappe-builder/po-approval/implementation-artifacts/sprint-status.yaml +2 -2
- package/AGENTS.md +16 -0
- package/dist/cli.mjs +1 -1
- package/dist/{init-BYJy3ztn.mjs → init-dY-MHOgS.mjs} +113 -1
- package/extensions/frappe-session.ts +4 -1
- package/extensions/frappe-tools.ts +23 -3
- package/package.json +1 -1
- package/tools/context-sandbox.ts +11 -7
package/.fb/state.db
CHANGED
|
Binary file
|
|
@@ -2,13 +2,13 @@ feature_id: po-approval
|
|
|
2
2
|
feature_name: "PO Approval"
|
|
3
3
|
mode: full
|
|
4
4
|
phase: testing
|
|
5
|
-
updated_at: 2026-03-
|
|
5
|
+
updated_at: 2026-03-28T14:09:32.555Z
|
|
6
6
|
|
|
7
7
|
components:
|
|
8
8
|
- id: final-comp
|
|
9
9
|
sort_order: 0
|
|
10
10
|
status: complete
|
|
11
|
-
completed_at: 2026-03-
|
|
11
|
+
completed_at: 2026-03-28T14:09:32.555Z
|
|
12
12
|
|
|
13
13
|
progress:
|
|
14
14
|
done: 1
|
package/AGENTS.md
CHANGED
|
@@ -11,6 +11,22 @@ You are Frappe-Nexus, the primary orchestrator for frappe-builder — a Frappe/E
|
|
|
11
11
|
- Surface state clearly: active project, feature, phase, progress — never work silently
|
|
12
12
|
- Run quality gates before marking any feature complete
|
|
13
13
|
|
|
14
|
+
## Context Window Protection (MANDATORY)
|
|
15
|
+
|
|
16
|
+
**Always use context-mode via mcp2cli.** Pi has no native MCP support — call context-mode tools by running `mcp2cli @context-mode <tool>` in a Bash command. Raw output floods the context window and degrades performance. This is non-negotiable.
|
|
17
|
+
|
|
18
|
+
| Instead of… | Run this Bash command… |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Reading a file to analyse it | `mcp2cli @context-mode ctx_execute_file --path /abs/path --language python --code "print(open('/abs/path').read())"` |
|
|
21
|
+
| Running bash commands with long output | `mcp2cli @context-mode ctx_execute --language shell --code "your-command"` |
|
|
22
|
+
| Searching the codebase | `mcp2cli @context-mode ctx_batch_execute --commands '["grep -r X ."]' --queries '["what does X do"]'` |
|
|
23
|
+
| Follow-up questions after indexing | `mcp2cli @context-mode ctx_search --queries '["question 1", "question 2"]'` |
|
|
24
|
+
| Fetching a URL | `mcp2cli @context-mode ctx_fetch_and_index --url https://...` then `ctx_search` |
|
|
25
|
+
|
|
26
|
+
**Only use direct Read when you are about to Edit the file** — the Edit tool needs file content in context. All other reads must go through `mcp2cli @context-mode`.
|
|
27
|
+
|
|
28
|
+
After every context-mode call, summarise findings in 3–5 bullet points. Never dump raw output into your response.
|
|
29
|
+
|
|
14
30
|
## Constraints
|
|
15
31
|
- Never auto-pilot: every significant action must be visible to the developer
|
|
16
32
|
- Always prefer Frappe built-ins over custom solutions
|
package/dist/cli.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { existsSync } from "node:fs";
|
|
|
14
14
|
*/
|
|
15
15
|
const cmd = process.argv[2];
|
|
16
16
|
if (cmd === "init") {
|
|
17
|
-
const { runInit } = await import("./init-
|
|
17
|
+
const { runInit } = await import("./init-dY-MHOgS.mjs");
|
|
18
18
|
await runInit();
|
|
19
19
|
process.exit(0);
|
|
20
20
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { createInterface } from "node:readline";
|
|
@@ -57,7 +58,8 @@ function patchGitignore(projectRoot, entry) {
|
|
|
57
58
|
}
|
|
58
59
|
async function runInit(opts = {}) {
|
|
59
60
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
60
|
-
const
|
|
61
|
+
const homeDir = homedir();
|
|
62
|
+
const globalConfigDir = join(homeDir, ".frappe-builder");
|
|
61
63
|
const globalConfigPath = join(globalConfigDir, "config.json");
|
|
62
64
|
const projectConfigPath = join(projectRoot, ".frappe-builder-config.json");
|
|
63
65
|
console.log("\n=== frappe-builder Setup ===\n");
|
|
@@ -144,6 +146,8 @@ async function runInit(opts = {}) {
|
|
|
144
146
|
if (gitignoreResult === "patched") written.push(".gitignore (patched)");
|
|
145
147
|
else if (gitignoreResult === "created") written.push(".gitignore (created)");
|
|
146
148
|
else skipped.push(".gitignore (entry already present)");
|
|
149
|
+
await setupContextMode(homeDir);
|
|
150
|
+
setupMcp2cli(homeDir);
|
|
147
151
|
console.log("\nFiles written:");
|
|
148
152
|
for (const f of written) console.log(` ✓ ${f}`);
|
|
149
153
|
if (skipped.length > 0) {
|
|
@@ -155,5 +159,113 @@ async function runInit(opts = {}) {
|
|
|
155
159
|
function printCancelled() {
|
|
156
160
|
console.log("\nSetup cancelled. No files were written.\n");
|
|
157
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Installs and configures the context-mode pi MCP extension.
|
|
164
|
+
* Clones https://github.com/mksglu/context-mode into ~/.pi/extensions/context-mode,
|
|
165
|
+
* builds it, and patches ~/.pi/settings/mcp.json with the server entry.
|
|
166
|
+
*
|
|
167
|
+
* Non-fatal — failures are logged as warnings, never abort init.
|
|
168
|
+
*/
|
|
169
|
+
async function setupContextMode(homeDir) {
|
|
170
|
+
const extDir = join(homeDir, ".pi", "extensions", "context-mode");
|
|
171
|
+
const mcpSettingsDir = join(homeDir, ".pi", "settings");
|
|
172
|
+
const mcpSettingsPath = join(mcpSettingsDir, "mcp.json");
|
|
173
|
+
const startScript = join(extDir, "node_modules", "context-mode", "start.mjs");
|
|
174
|
+
console.log("\n[context-mode MCP extension]");
|
|
175
|
+
if (existsSync(extDir)) console.log(" ✓ context-mode already installed at ~/.pi/extensions/context-mode");
|
|
176
|
+
else {
|
|
177
|
+
console.log(" context-mode not found — installing (requires git + Node.js)...");
|
|
178
|
+
mkdirSync(join(homeDir, ".pi", "extensions"), { recursive: true });
|
|
179
|
+
const clone = spawnSync("git", [
|
|
180
|
+
"clone",
|
|
181
|
+
"https://github.com/mksglu/context-mode.git",
|
|
182
|
+
extDir
|
|
183
|
+
], { stdio: "pipe" });
|
|
184
|
+
if (clone.status !== 0) {
|
|
185
|
+
console.warn(` ⚠ git clone failed: ${clone.stderr?.toString().trim()}`);
|
|
186
|
+
console.warn(" Skipping context-mode setup. Install manually: https://github.com/mksglu/context-mode");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const install = spawnSync("npm", ["install"], {
|
|
190
|
+
cwd: extDir,
|
|
191
|
+
stdio: "pipe"
|
|
192
|
+
});
|
|
193
|
+
if (install.status !== 0) {
|
|
194
|
+
console.warn(` ⚠ npm install failed: ${install.stderr?.toString().trim()}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const build = spawnSync("npm", ["run", "build"], {
|
|
198
|
+
cwd: extDir,
|
|
199
|
+
stdio: "pipe"
|
|
200
|
+
});
|
|
201
|
+
if (build.status !== 0) {
|
|
202
|
+
console.warn(` ⚠ npm run build failed: ${build.stderr?.toString().trim()}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log(" ✓ context-mode installed and built");
|
|
206
|
+
}
|
|
207
|
+
mkdirSync(mcpSettingsDir, { recursive: true });
|
|
208
|
+
let mcpConfig = {};
|
|
209
|
+
if (existsSync(mcpSettingsPath)) try {
|
|
210
|
+
mcpConfig = JSON.parse(readFileSync(mcpSettingsPath, "utf-8"));
|
|
211
|
+
} catch {}
|
|
212
|
+
const servers = mcpConfig.mcpServers ?? {};
|
|
213
|
+
if (servers["context-mode"]) {
|
|
214
|
+
console.log(" ✓ context-mode already in ~/.pi/settings/mcp.json");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
servers["context-mode"] = {
|
|
218
|
+
command: "node",
|
|
219
|
+
args: [startScript]
|
|
220
|
+
};
|
|
221
|
+
mcpConfig.mcpServers = servers;
|
|
222
|
+
writeAtomic(mcpSettingsPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
223
|
+
console.log(" ✓ Added context-mode to ~/.pi/settings/mcp.json");
|
|
224
|
+
console.log(" Restart pi (or frappe-builder) for context-mode to activate.");
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Installs the mcp2cli Claude Code skill and bakes the context-mode connection
|
|
228
|
+
* so the agent can call `mcp2cli @context-mode <tool>` without repeating flags.
|
|
229
|
+
*
|
|
230
|
+
* Non-fatal — failures are logged as warnings, never abort init.
|
|
231
|
+
*/
|
|
232
|
+
function setupMcp2cli(homeDir) {
|
|
233
|
+
const startScript = join(homeDir, ".pi", "extensions", "context-mode", "node_modules", "context-mode", "start.mjs");
|
|
234
|
+
console.log("\n[mcp2cli skill + context-mode bake]");
|
|
235
|
+
const skillAdd = spawnSync("npx", [
|
|
236
|
+
"skills",
|
|
237
|
+
"add",
|
|
238
|
+
"knowsuchagency/mcp2cli",
|
|
239
|
+
"--skill",
|
|
240
|
+
"mcp2cli"
|
|
241
|
+
], { stdio: "pipe" });
|
|
242
|
+
if (skillAdd.status !== 0) {
|
|
243
|
+
console.warn(` ⚠ mcp2cli skill install failed: ${skillAdd.stderr?.toString().trim()}`);
|
|
244
|
+
console.warn(" Install manually: npx skills add knowsuchagency/mcp2cli --skill mcp2cli");
|
|
245
|
+
} else console.log(" ✓ mcp2cli skill installed");
|
|
246
|
+
if (spawnSync("mcp2cli", [
|
|
247
|
+
"bake",
|
|
248
|
+
"show",
|
|
249
|
+
"context-mode"
|
|
250
|
+
], { stdio: "pipe" }).status === 0) {
|
|
251
|
+
console.log(" ✓ mcp2cli @context-mode already baked");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!existsSync(startScript)) {
|
|
255
|
+
console.warn(" ⚠ context-mode start.mjs not found — skipping bake (run init again after context-mode installs)");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const bakeCreate = spawnSync("mcp2cli", [
|
|
259
|
+
"bake",
|
|
260
|
+
"create",
|
|
261
|
+
"context-mode",
|
|
262
|
+
"--mcp-stdio",
|
|
263
|
+
`node ${startScript}`
|
|
264
|
+
], { stdio: "pipe" });
|
|
265
|
+
if (bakeCreate.status !== 0) {
|
|
266
|
+
console.warn(` ⚠ mcp2cli bake failed: ${bakeCreate.stderr?.toString().trim()}`);
|
|
267
|
+
console.warn(" Install mcp2cli first: pip install mcp2cli");
|
|
268
|
+
} else console.log(" ✓ mcp2cli @context-mode baked — agent can now call: mcp2cli @context-mode <tool>");
|
|
269
|
+
}
|
|
158
270
|
//#endregion
|
|
159
271
|
export { runInit };
|
|
@@ -238,7 +238,10 @@ export function buildStateInjection(ctx: SessionContext): string {
|
|
|
238
238
|
`Phase: ${ctx.phase}`,
|
|
239
239
|
`Progress: ${progress}`,
|
|
240
240
|
`Last action: ${ctx.lastTool ?? "none"}`,
|
|
241
|
-
"======================================"
|
|
241
|
+
"======================================",
|
|
242
|
+
"CONTEXT WINDOW RULE: Run `mcp2cli @context-mode <tool>` for ALL reads and searches.",
|
|
243
|
+
"Use ctx_execute_file, ctx_execute, ctx_batch_execute, ctx_search via mcp2cli.",
|
|
244
|
+
"Only use Read directly when you are about to Edit that file.",
|
|
242
245
|
);
|
|
243
246
|
return lines.join("\n");
|
|
244
247
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { startFeature, completeComponent } from "../tools/feature-tools.js";
|
|
2
|
+
import { startFeature, completeComponent, createComponent } from "../tools/feature-tools.js";
|
|
3
3
|
import { setActiveProject, getProjectStatus } from "../tools/project-tools.js";
|
|
4
4
|
import { getAuditLog } from "../state/journal.js";
|
|
5
5
|
import { invokeDebugger, endDebug } from "../tools/debug-tools.js";
|
|
@@ -54,6 +54,26 @@ export default function (pi: any) {
|
|
|
54
54
|
},
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
pi.registerTool({
|
|
58
|
+
name: "create_component",
|
|
59
|
+
label: "Create Component",
|
|
60
|
+
description:
|
|
61
|
+
"Registers a planned unit of work as an in-progress component for the active feature. Call this BEFORE writing any code — one component per logical deliverable. Updates sprint-status.yaml and sets the active component on the session. Valid in planning and implementation phases.",
|
|
62
|
+
parameters: Type.Object({
|
|
63
|
+
featureId: Type.String({ description: "Feature ID (kebab-case slug, e.g. 'po-approval')" }),
|
|
64
|
+
componentId: Type.String({ description: "Component ID (e.g. 'auth-module', 'po-doctype')" }),
|
|
65
|
+
description: Type.Optional(Type.String({ description: "Short description of what this component implements" })),
|
|
66
|
+
sortOrder: Type.Optional(Type.Number({ description: "Order within the feature (lower = earlier)" })),
|
|
67
|
+
}),
|
|
68
|
+
execute: async (_toolCallId: string, params: ToolParams) => {
|
|
69
|
+
const result = createComponent(params);
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
72
|
+
details: result,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
57
77
|
pi.registerTool({
|
|
58
78
|
name: "set_active_project",
|
|
59
79
|
label: "Set Active Project",
|
|
@@ -126,7 +146,7 @@ export default function (pi: any) {
|
|
|
126
146
|
name: "scaffold_doctype",
|
|
127
147
|
label: "Scaffold DocType",
|
|
128
148
|
description:
|
|
129
|
-
"
|
|
149
|
+
"NOT YET IMPLEMENTED (Story 4.2 deferred). Returns an error. Do not call — create DocType JSON fixtures directly instead.",
|
|
130
150
|
parameters: Type.Object({
|
|
131
151
|
name: Type.String({ description: "DocType name in PascalCase (e.g. 'Purchase Order')" }),
|
|
132
152
|
module: Type.String({ description: "Frappe module name (e.g. 'Buying')" }),
|
|
@@ -144,7 +164,7 @@ export default function (pi: any) {
|
|
|
144
164
|
name: "run_tests",
|
|
145
165
|
label: "Run Tests",
|
|
146
166
|
description:
|
|
147
|
-
"
|
|
167
|
+
"NOT YET IMPLEMENTED (Epic 6 deferred). Returns an error. Use bench_execute with 'bench run-tests --app {app}' instead.",
|
|
148
168
|
parameters: Type.Object({
|
|
149
169
|
featureId: Type.Optional(Type.String({ description: "Feature ID to scope tests (optional)" })),
|
|
150
170
|
}),
|
package/package.json
CHANGED
package/tools/context-sandbox.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Output truncation guard for Frappe tool output.
|
|
3
3
|
*
|
|
4
|
-
* Context-mode MCP tools
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Context-mode MCP tools (ctx_execute, ctx_search, etc.) are only callable by
|
|
5
|
+
* the LLM agent — NOT from Node.js extension code. There is no HTTP endpoint
|
|
6
|
+
* to route to from here.
|
|
7
|
+
*
|
|
8
|
+
* This module is a last-resort truncation guard for bench_execute and
|
|
9
|
+
* frappe_query outputs that would otherwise flood the context window.
|
|
10
|
+
* The agent is instructed via AGENTS.md to use ctx_execute / ctx_batch_execute
|
|
11
|
+
* proactively instead of relying on this fallback.
|
|
7
12
|
*
|
|
8
13
|
* NFR17: truncation is NEVER silent — warning is always prepended.
|
|
9
14
|
*/
|
|
@@ -11,12 +16,11 @@
|
|
|
11
16
|
const CHAR_LIMIT = 8192 * 4; // ~8K tokens at 4 chars/token
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
19
|
+
* Applies the 8K truncation guard to raw tool output.
|
|
20
|
+
* Named routeThroughContextMode for API compatibility with existing callers.
|
|
16
21
|
* Never throws.
|
|
17
22
|
*/
|
|
18
23
|
export async function routeThroughContextMode(raw: string): Promise<string> {
|
|
19
|
-
// TODO: wire to context-mode MCP HTTP endpoint when discoverable from extension code
|
|
20
24
|
return applyTruncationFallback(raw);
|
|
21
25
|
}
|
|
22
26
|
|