frappe-builder 1.1.0-dev.22 → 1.1.0-dev.24
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/dist/cli.mjs +1 -1
- package/dist/{init-BUkSYk2l.mjs → init-Gp1MgJD2.mjs} +44 -0
- package/extensions/frappe-tools.ts +22 -3
- package/extensions/frappe-workflow.ts +1 -1
- package/package.json +1 -1
- package/tools/frappe-context7.ts +28 -32
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-28T14:
|
|
5
|
+
updated_at: 2026-03-28T14:23:24.878Z
|
|
6
6
|
|
|
7
7
|
components:
|
|
8
8
|
- id: final-comp
|
|
9
9
|
sort_order: 0
|
|
10
10
|
status: complete
|
|
11
|
-
completed_at: 2026-03-28T14:
|
|
11
|
+
completed_at: 2026-03-28T14:23:24.878Z
|
|
12
12
|
|
|
13
13
|
progress:
|
|
14
14
|
done: 1
|
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-Gp1MgJD2.mjs");
|
|
18
18
|
await runInit();
|
|
19
19
|
process.exit(0);
|
|
20
20
|
}
|
|
@@ -148,6 +148,7 @@ async function runInit(opts = {}) {
|
|
|
148
148
|
else skipped.push(".gitignore (entry already present)");
|
|
149
149
|
await setupContextMode(homeDir);
|
|
150
150
|
setupMcp2cli(homeDir);
|
|
151
|
+
setupContext7();
|
|
151
152
|
console.log("\nFiles written:");
|
|
152
153
|
for (const f of written) console.log(` ✓ ${f}`);
|
|
153
154
|
if (skipped.length > 0) {
|
|
@@ -279,5 +280,48 @@ function setupMcp2cli(homeDir) {
|
|
|
279
280
|
if (bakeCreate.status !== 0) console.warn(` ⚠ mcp2cli bake failed: ${bakeCreate.stderr?.toString().trim()}`);
|
|
280
281
|
else console.log(" ✓ mcp2cli @context-mode baked — agent can now call: mcp2cli @context-mode <tool>");
|
|
281
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Bakes the context7 cloud MCP server as @context7 and installs the
|
|
285
|
+
* netresearch context7 Claude Code skill for general-purpose library research.
|
|
286
|
+
*
|
|
287
|
+
* Non-fatal — failures are logged as warnings, never abort init.
|
|
288
|
+
*/
|
|
289
|
+
function setupContext7() {
|
|
290
|
+
console.log("\n[context7 MCP bake + skill]");
|
|
291
|
+
if (spawnSync("mcp2cli", [
|
|
292
|
+
"bake",
|
|
293
|
+
"show",
|
|
294
|
+
"context7"
|
|
295
|
+
], { stdio: "pipe" }).status === 0) console.log(" ✓ mcp2cli @context7 already baked");
|
|
296
|
+
else {
|
|
297
|
+
const bakeCreate = spawnSync("mcp2cli", [
|
|
298
|
+
"bake",
|
|
299
|
+
"create",
|
|
300
|
+
"context7",
|
|
301
|
+
"--mcp",
|
|
302
|
+
"https://mcp.context7.com/mcp"
|
|
303
|
+
], { stdio: "pipe" });
|
|
304
|
+
if (bakeCreate.status !== 0) {
|
|
305
|
+
console.warn(` ⚠ context7 bake failed: ${bakeCreate.stderr?.toString().trim()}`);
|
|
306
|
+
console.warn(" Install mcp2cli first: uv tool install mcp2cli");
|
|
307
|
+
} else console.log(" ✓ mcp2cli @context7 baked — agent can now call: mcp2cli @context7 resolve-library-id");
|
|
308
|
+
}
|
|
309
|
+
spawnSync("npx", [
|
|
310
|
+
"skills",
|
|
311
|
+
"add",
|
|
312
|
+
"knowsuchagency/mcp2cli",
|
|
313
|
+
"--skill",
|
|
314
|
+
"mcp2cli"
|
|
315
|
+
], { stdio: "pipe" });
|
|
316
|
+
if (spawnSync("claude", [
|
|
317
|
+
"plugin",
|
|
318
|
+
"marketplace",
|
|
319
|
+
"add",
|
|
320
|
+
"netresearch/claude-code-marketplace"
|
|
321
|
+
], { stdio: "pipe" }).status !== 0) {
|
|
322
|
+
console.warn(" ⚠ context7 skill install failed (netresearch marketplace unavailable)");
|
|
323
|
+
console.warn(" Install manually: claude plugin marketplace add netresearch/claude-code-marketplace");
|
|
324
|
+
} else console.log(" ✓ context7 Claude Code skill installed — use /context7 for library research");
|
|
325
|
+
}
|
|
282
326
|
//#endregion
|
|
283
327
|
export { runInit };
|
|
@@ -6,7 +6,7 @@ import { invokeDebugger, endDebug } from "../tools/debug-tools.js";
|
|
|
6
6
|
import { spawnAgent } from "../tools/agent-tools.js";
|
|
7
7
|
import { scaffoldDoctype, benchExecute, runTests } from "../tools/bench-tools.js";
|
|
8
8
|
import { frappeQuery } from "../tools/frappe-query-tools.js";
|
|
9
|
-
import { getFrappeDocs } from "../tools/frappe-context7.js";
|
|
9
|
+
import { getLibraryDocs, getFrappeDocs } from "../tools/frappe-context7.js";
|
|
10
10
|
|
|
11
11
|
// pi.registerTool's execute callback is untyped (pi is `any`); params are
|
|
12
12
|
// enforced at runtime via TypeBox schemas. One explicit-any alias avoids
|
|
@@ -212,12 +212,31 @@ export default function (pi: any) {
|
|
|
212
212
|
},
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// General-purpose library docs tool via context7 — bypasses phase guard via ALWAYS_ALLOWED_TOOLS
|
|
216
|
+
pi.registerTool({
|
|
217
|
+
name: "get_library_docs",
|
|
218
|
+
label: "Get Library Docs",
|
|
219
|
+
description:
|
|
220
|
+
"Retrieves up-to-date documentation for any library via context7 MCP (React, Next.js, Frappe, ERPNext, Python, etc.). Raw web content never enters the LLM context. Valid in any phase.",
|
|
221
|
+
parameters: Type.Object({
|
|
222
|
+
query: Type.String({ description: "What you want to know (e.g. 'how do hooks work', 'useEffect cleanup')" }),
|
|
223
|
+
library: Type.Optional(Type.String({ description: "Library name (e.g. 'frappe', 'react', 'nextjs'). Defaults to 'frappe'." })),
|
|
224
|
+
}),
|
|
225
|
+
execute: async (_toolCallId: string, params: ToolParams) => {
|
|
226
|
+
const result = await getLibraryDocs(params);
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
229
|
+
details: result,
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Frappe-specific alias kept for backwards compatibility
|
|
216
235
|
pi.registerTool({
|
|
217
236
|
name: "get_frappe_docs",
|
|
218
237
|
label: "Get Frappe Docs",
|
|
219
238
|
description:
|
|
220
|
-
"Retrieves
|
|
239
|
+
"Retrieves Frappe/ERPNext documentation via context7. Alias for get_library_docs with library='frappe'. Valid in any phase.",
|
|
221
240
|
parameters: Type.Object({
|
|
222
241
|
topic: Type.String({ description: "Documentation topic (e.g. 'DocType', 'hooks', 'frappe.db')" }),
|
|
223
242
|
version: Type.Optional(Type.String({ description: "Frappe version to scope docs (e.g. 'v15')" })),
|
|
@@ -51,7 +51,7 @@ function buildBlockedResponse(
|
|
|
51
51
|
* Never throws — always returns a value or undefined.
|
|
52
52
|
*/
|
|
53
53
|
// Tools valid in all phases — never blocked by the phase guard (FR34)
|
|
54
|
-
const ALWAYS_ALLOWED_TOOLS = ["invoke_debugger", "end_debug", "spawn_agent", "get_frappe_docs", "get_audit_log", "get_project_status"];
|
|
54
|
+
const ALWAYS_ALLOWED_TOOLS = ["invoke_debugger", "end_debug", "spawn_agent", "get_frappe_docs", "get_library_docs", "get_audit_log", "get_project_status"];
|
|
55
55
|
|
|
56
56
|
export function beforeToolCall(
|
|
57
57
|
toolName: string,
|
package/package.json
CHANGED
package/tools/frappe-context7.ts
CHANGED
|
@@ -1,70 +1,66 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
|
|
3
|
-
export interface
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export interface GetLibraryDocsArgs {
|
|
4
|
+
query: string;
|
|
5
|
+
library?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export interface
|
|
8
|
+
export interface GetLibraryDocsResult {
|
|
9
9
|
snippet?: string;
|
|
10
|
-
|
|
10
|
+
query?: string;
|
|
11
|
+
library?: string;
|
|
11
12
|
source?: "context7";
|
|
12
13
|
error?: string;
|
|
13
14
|
fallback?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const CONTEXT7_UNAVAILABLE:
|
|
17
|
+
const CONTEXT7_UNAVAILABLE: GetLibraryDocsResult = {
|
|
17
18
|
error: "context7 unavailable",
|
|
18
|
-
fallback: "search
|
|
19
|
+
fallback: "search library docs manually",
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* Retrieves
|
|
23
|
-
* Raw web content never enters the LLM context — only the
|
|
23
|
+
* Retrieves up-to-date documentation via context7 MCP for any library.
|
|
24
|
+
* Raw web content never enters the LLM context — only the snippet is returned.
|
|
24
25
|
* Returns a graceful error on any failure (timeout, MCP unavailable, not found).
|
|
25
26
|
* Never throws.
|
|
26
27
|
*
|
|
27
28
|
* Flow:
|
|
28
|
-
* 1. Resolve
|
|
29
|
-
* 2. Fetch
|
|
29
|
+
* 1. Resolve library ID via context7 resolve-library-id
|
|
30
|
+
* 2. Fetch docs for query via context7 query-docs
|
|
30
31
|
*/
|
|
31
|
-
export async function
|
|
32
|
+
export async function getLibraryDocs({ query, library = "frappe" }: GetLibraryDocsArgs): Promise<GetLibraryDocsResult> {
|
|
32
33
|
try {
|
|
33
|
-
// Step 1: resolve
|
|
34
|
-
const libraryName = version ? `frappe@${version}` : "frappe";
|
|
34
|
+
// Step 1: resolve library ID
|
|
35
35
|
const resolveResult = await execa(
|
|
36
36
|
"mcp2cli",
|
|
37
|
-
["context7", "resolve-library-id", "--
|
|
37
|
+
["@context7", "resolve-library-id", "--library-name", library, "--query", query],
|
|
38
38
|
{ timeout: 10_000 }
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
libraryId = parsed.libraryId;
|
|
46
|
-
} catch {
|
|
47
|
-
return CONTEXT7_UNAVAILABLE;
|
|
48
|
-
}
|
|
41
|
+
// Response is plain text listing — extract first library ID via regex
|
|
42
|
+
const idMatch = resolveResult.stdout.match(/Context7-compatible library ID:\s*(\S+)/);
|
|
43
|
+
if (!idMatch?.[1]) return CONTEXT7_UNAVAILABLE;
|
|
44
|
+
const libraryId = idMatch[1];
|
|
49
45
|
|
|
50
|
-
// Step 2: fetch
|
|
46
|
+
// Step 2: fetch docs for the query
|
|
51
47
|
const docsResult = await execa(
|
|
52
48
|
"mcp2cli",
|
|
53
|
-
[
|
|
54
|
-
"context7",
|
|
55
|
-
"get-library-docs",
|
|
56
|
-
"--libraryId", libraryId,
|
|
57
|
-
"--topic", topic,
|
|
58
|
-
"--tokens", "5000",
|
|
59
|
-
],
|
|
49
|
+
["@context7", "query-docs", "--library-id", libraryId, "--query", query],
|
|
60
50
|
{ timeout: 15_000 }
|
|
61
51
|
);
|
|
62
52
|
|
|
63
53
|
const snippet = docsResult.stdout?.trim();
|
|
64
54
|
if (!snippet) return CONTEXT7_UNAVAILABLE;
|
|
65
55
|
|
|
66
|
-
return { snippet,
|
|
56
|
+
return { snippet, query, library, source: "context7" };
|
|
67
57
|
} catch {
|
|
68
58
|
return CONTEXT7_UNAVAILABLE;
|
|
69
59
|
}
|
|
70
60
|
}
|
|
61
|
+
|
|
62
|
+
/** Backwards-compatible alias for Frappe-specific doc lookups. */
|
|
63
|
+
export async function getFrappeDocs({ topic, version }: { topic: string; version?: string }): Promise<GetLibraryDocsResult> {
|
|
64
|
+
const library = version ? `frappe@${version}` : "frappe";
|
|
65
|
+
return getLibraryDocs({ query: topic, library });
|
|
66
|
+
}
|