@synergenius/flow-weaver 0.11.0 → 0.12.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/dist/cli/commands/context.d.ts +13 -0
- package/dist/cli/commands/context.js +53 -0
- package/dist/cli/flow-weaver.mjs +299 -51
- package/dist/cli/index.js +20 -0
- package/dist/context/index.d.ts +30 -0
- package/dist/context/index.js +182 -0
- package/dist/doc-metadata/extractors/mcp-tools.js +40 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools-context.d.ts +3 -0
- package/dist/mcp/tools-context.js +51 -0
- package/docs/reference/cli-reference.md +33 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context command - generate LLM context bundles from documentation and grammar
|
|
3
|
+
*/
|
|
4
|
+
export interface ContextCommandOptions {
|
|
5
|
+
profile?: string;
|
|
6
|
+
topics?: string;
|
|
7
|
+
add?: string;
|
|
8
|
+
grammar?: boolean;
|
|
9
|
+
output?: string;
|
|
10
|
+
list?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function contextCommand(preset: string | undefined, options: ContextCommandOptions): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context command - generate LLM context bundles from documentation and grammar
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import { buildContext, PRESETS, PRESET_NAMES } from '../../context/index.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
export async function contextCommand(preset, options) {
|
|
8
|
+
// --list: show presets and exit
|
|
9
|
+
if (options.list) {
|
|
10
|
+
logger.section('Context Presets');
|
|
11
|
+
logger.newline();
|
|
12
|
+
const maxName = Math.max(...PRESET_NAMES.map((n) => n.length));
|
|
13
|
+
for (const name of PRESET_NAMES) {
|
|
14
|
+
const topics = PRESETS[name];
|
|
15
|
+
logger.log(` ${name.padEnd(maxName + 2)} ${topics.join(', ')}`);
|
|
16
|
+
}
|
|
17
|
+
logger.newline();
|
|
18
|
+
logger.log(' Usage: flow-weaver context [preset] [options]');
|
|
19
|
+
logger.newline();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Validate preset
|
|
23
|
+
const presetName = (preset ?? 'core');
|
|
24
|
+
if (!PRESET_NAMES.includes(presetName) && !options.topics) {
|
|
25
|
+
logger.error(`Unknown preset "${preset}". Available: ${PRESET_NAMES.join(', ')}. Or use --topics to specify topics directly.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Validate profile
|
|
29
|
+
const profile = options.profile ?? 'standalone';
|
|
30
|
+
if (profile !== 'standalone' && profile !== 'assistant') {
|
|
31
|
+
logger.error(`Unknown profile "${profile}". Use "standalone" or "assistant".`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const result = buildContext({
|
|
35
|
+
preset: PRESET_NAMES.includes(presetName) ? presetName : 'core',
|
|
36
|
+
profile: profile,
|
|
37
|
+
topics: options.topics ? options.topics.split(',').map((s) => s.trim()) : undefined,
|
|
38
|
+
addTopics: options.add ? options.add.split(',').map((s) => s.trim()) : undefined,
|
|
39
|
+
includeGrammar: options.grammar !== false,
|
|
40
|
+
});
|
|
41
|
+
// Write output
|
|
42
|
+
if (options.output) {
|
|
43
|
+
fs.writeFileSync(options.output, result.content, 'utf-8');
|
|
44
|
+
logger.success(`Context written to ${options.output}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
process.stdout.write(result.content);
|
|
48
|
+
}
|
|
49
|
+
// Stats to stderr (doesn't pollute piped stdout)
|
|
50
|
+
const stats = `${result.topicCount} topics, ${result.lineCount} lines (${result.profile} profile)`;
|
|
51
|
+
process.stderr.write(`flow-weaver context: ${stats}\n`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=context.js.map
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -936,7 +936,7 @@ var require_command = __commonJS({
|
|
|
936
936
|
var EventEmitter2 = __require("events").EventEmitter;
|
|
937
937
|
var childProcess = __require("child_process");
|
|
938
938
|
var path51 = __require("path");
|
|
939
|
-
var
|
|
939
|
+
var fs51 = __require("fs");
|
|
940
940
|
var process6 = __require("process");
|
|
941
941
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
942
942
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1760,9 +1760,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1760
1760
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1761
1761
|
function findFile(baseDir, baseName) {
|
|
1762
1762
|
const localBin = path51.resolve(baseDir, baseName);
|
|
1763
|
-
if (
|
|
1763
|
+
if (fs51.existsSync(localBin)) return localBin;
|
|
1764
1764
|
if (sourceExt.includes(path51.extname(baseName))) return void 0;
|
|
1765
|
-
const foundExt = sourceExt.find((ext2) =>
|
|
1765
|
+
const foundExt = sourceExt.find((ext2) => fs51.existsSync(`${localBin}${ext2}`));
|
|
1766
1766
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1767
1767
|
return void 0;
|
|
1768
1768
|
}
|
|
@@ -1773,7 +1773,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1773
1773
|
if (this._scriptPath) {
|
|
1774
1774
|
let resolvedScriptPath;
|
|
1775
1775
|
try {
|
|
1776
|
-
resolvedScriptPath =
|
|
1776
|
+
resolvedScriptPath = fs51.realpathSync(this._scriptPath);
|
|
1777
1777
|
} catch (err) {
|
|
1778
1778
|
resolvedScriptPath = this._scriptPath;
|
|
1779
1779
|
}
|
|
@@ -7740,8 +7740,8 @@ var init_esm4 = __esm({
|
|
|
7740
7740
|
*
|
|
7741
7741
|
* @internal
|
|
7742
7742
|
*/
|
|
7743
|
-
constructor(cwd = process.cwd(), pathImpl, sep3, { nocase, childrenCacheSize = 16 * 1024, fs:
|
|
7744
|
-
this.#fs = fsFromOption(
|
|
7743
|
+
constructor(cwd = process.cwd(), pathImpl, sep3, { nocase, childrenCacheSize = 16 * 1024, fs: fs51 = defaultFS } = {}) {
|
|
7744
|
+
this.#fs = fsFromOption(fs51);
|
|
7745
7745
|
if (cwd instanceof URL || cwd.startsWith("file://")) {
|
|
7746
7746
|
cwd = fileURLToPath(cwd);
|
|
7747
7747
|
}
|
|
@@ -8299,8 +8299,8 @@ var init_esm4 = __esm({
|
|
|
8299
8299
|
/**
|
|
8300
8300
|
* @internal
|
|
8301
8301
|
*/
|
|
8302
|
-
newRoot(
|
|
8303
|
-
return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
8302
|
+
newRoot(fs51) {
|
|
8303
|
+
return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs51 });
|
|
8304
8304
|
}
|
|
8305
8305
|
/**
|
|
8306
8306
|
* Return true if the provided path string is an absolute path
|
|
@@ -8328,8 +8328,8 @@ var init_esm4 = __esm({
|
|
|
8328
8328
|
/**
|
|
8329
8329
|
* @internal
|
|
8330
8330
|
*/
|
|
8331
|
-
newRoot(
|
|
8332
|
-
return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
8331
|
+
newRoot(fs51) {
|
|
8332
|
+
return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs51 });
|
|
8333
8333
|
}
|
|
8334
8334
|
/**
|
|
8335
8335
|
* Return true if the provided path string is an absolute path
|
|
@@ -13168,12 +13168,12 @@ var require_binary_search = __commonJS({
|
|
|
13168
13168
|
var require_read_wasm = __commonJS({
|
|
13169
13169
|
"node_modules/source-map/lib/read-wasm.js"(exports2, module2) {
|
|
13170
13170
|
"use strict";
|
|
13171
|
-
var
|
|
13171
|
+
var fs51 = __require("fs");
|
|
13172
13172
|
var path51 = __require("path");
|
|
13173
13173
|
module2.exports = function readWasm() {
|
|
13174
13174
|
return new Promise((resolve35, reject2) => {
|
|
13175
13175
|
const wasmPath = path51.join(__dirname, "mappings.wasm");
|
|
13176
|
-
|
|
13176
|
+
fs51.readFile(wasmPath, null, (error2, data) => {
|
|
13177
13177
|
if (error2) {
|
|
13178
13178
|
reject2(error2);
|
|
13179
13179
|
return;
|
|
@@ -49111,7 +49111,7 @@ var init_workflow_executor = __esm({
|
|
|
49111
49111
|
// node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
|
|
49112
49112
|
var require_XMLHttpRequest = __commonJS({
|
|
49113
49113
|
"node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js"(exports2, module2) {
|
|
49114
|
-
var
|
|
49114
|
+
var fs51 = __require("fs");
|
|
49115
49115
|
var Url = __require("url");
|
|
49116
49116
|
var spawn2 = __require("child_process").spawn;
|
|
49117
49117
|
module2.exports = XMLHttpRequest3;
|
|
@@ -49269,7 +49269,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
49269
49269
|
throw new Error("XMLHttpRequest: Only GET method is supported");
|
|
49270
49270
|
}
|
|
49271
49271
|
if (settings.async) {
|
|
49272
|
-
|
|
49272
|
+
fs51.readFile(unescape(url2.pathname), function(error2, data2) {
|
|
49273
49273
|
if (error2) {
|
|
49274
49274
|
self2.handleError(error2, error2.errno || -1);
|
|
49275
49275
|
} else {
|
|
@@ -49281,7 +49281,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
49281
49281
|
});
|
|
49282
49282
|
} else {
|
|
49283
49283
|
try {
|
|
49284
|
-
this.response =
|
|
49284
|
+
this.response = fs51.readFileSync(unescape(url2.pathname));
|
|
49285
49285
|
this.responseText = this.response.toString("utf8");
|
|
49286
49286
|
this.status = 200;
|
|
49287
49287
|
setState(self2.DONE);
|
|
@@ -49407,15 +49407,15 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
49407
49407
|
} else {
|
|
49408
49408
|
var contentFile = ".node-xmlhttprequest-content-" + process.pid;
|
|
49409
49409
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
49410
|
-
|
|
49410
|
+
fs51.writeFileSync(syncFile, "", "utf8");
|
|
49411
49411
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
49412
49412
|
var syncProc = spawn2(process.argv[0], ["-e", execString]);
|
|
49413
49413
|
var statusText;
|
|
49414
|
-
while (
|
|
49414
|
+
while (fs51.existsSync(syncFile)) {
|
|
49415
49415
|
}
|
|
49416
|
-
self2.responseText =
|
|
49416
|
+
self2.responseText = fs51.readFileSync(contentFile, "utf8");
|
|
49417
49417
|
syncProc.stdin.end();
|
|
49418
|
-
|
|
49418
|
+
fs51.unlinkSync(contentFile);
|
|
49419
49419
|
if (self2.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
|
|
49420
49420
|
var errorObj = JSON.parse(self2.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
|
|
49421
49421
|
self2.handleError(errorObj, 503);
|
|
@@ -64331,12 +64331,12 @@ var require_dist = __commonJS({
|
|
|
64331
64331
|
throw new Error(`Unknown format "${name}"`);
|
|
64332
64332
|
return f;
|
|
64333
64333
|
};
|
|
64334
|
-
function addFormats(ajv, list,
|
|
64334
|
+
function addFormats(ajv, list, fs51, exportName) {
|
|
64335
64335
|
var _a;
|
|
64336
64336
|
var _b;
|
|
64337
64337
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
64338
64338
|
for (const f of list)
|
|
64339
|
-
ajv.addFormat(f,
|
|
64339
|
+
ajv.addFormat(f, fs51[f]);
|
|
64340
64340
|
}
|
|
64341
64341
|
module2.exports = exports2 = formatsPlugin;
|
|
64342
64342
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -94673,8 +94673,8 @@ ${parseResult.errors.join("\n")}`);
|
|
|
94673
94673
|
});
|
|
94674
94674
|
const outputFile = filePath.replace(/\.ts$/, ".inngest.ts");
|
|
94675
94675
|
if (args.write !== false) {
|
|
94676
|
-
const
|
|
94677
|
-
|
|
94676
|
+
const fs51 = await import("fs");
|
|
94677
|
+
fs51.writeFileSync(outputFile, code, "utf8");
|
|
94678
94678
|
}
|
|
94679
94679
|
return makeToolResult({
|
|
94680
94680
|
target: "inngest",
|
|
@@ -103162,6 +103162,199 @@ function registerDebugTools(mcp) {
|
|
|
103162
103162
|
);
|
|
103163
103163
|
}
|
|
103164
103164
|
|
|
103165
|
+
// src/context/index.ts
|
|
103166
|
+
init_grammar_diagrams();
|
|
103167
|
+
var PRESETS = {
|
|
103168
|
+
core: ["concepts", "jsdoc-grammar", "tutorial"],
|
|
103169
|
+
authoring: [
|
|
103170
|
+
"concepts",
|
|
103171
|
+
"jsdoc-grammar",
|
|
103172
|
+
"advanced-annotations",
|
|
103173
|
+
"built-in-nodes",
|
|
103174
|
+
"scaffold",
|
|
103175
|
+
"node-conversion",
|
|
103176
|
+
"patterns"
|
|
103177
|
+
],
|
|
103178
|
+
ops: [
|
|
103179
|
+
"cli-reference",
|
|
103180
|
+
"compilation",
|
|
103181
|
+
"deployment",
|
|
103182
|
+
"export-interface",
|
|
103183
|
+
"debugging",
|
|
103184
|
+
"error-codes"
|
|
103185
|
+
],
|
|
103186
|
+
full: [
|
|
103187
|
+
"concepts",
|
|
103188
|
+
"tutorial",
|
|
103189
|
+
"jsdoc-grammar",
|
|
103190
|
+
"advanced-annotations",
|
|
103191
|
+
"built-in-nodes",
|
|
103192
|
+
"cli-reference",
|
|
103193
|
+
"compilation",
|
|
103194
|
+
"debugging",
|
|
103195
|
+
"deployment",
|
|
103196
|
+
"error-codes",
|
|
103197
|
+
"export-interface",
|
|
103198
|
+
"iterative-development",
|
|
103199
|
+
"marketplace",
|
|
103200
|
+
"node-conversion",
|
|
103201
|
+
"patterns",
|
|
103202
|
+
"scaffold"
|
|
103203
|
+
]
|
|
103204
|
+
};
|
|
103205
|
+
var PRESET_NAMES = Object.keys(PRESETS);
|
|
103206
|
+
var STANDALONE_PREAMBLE = `# Flow Weaver Reference
|
|
103207
|
+
|
|
103208
|
+
Flow Weaver is a TypeScript workflow compiler. You write plain .ts files with
|
|
103209
|
+
JSDoc annotations (@flowWeaver nodeType, @flowWeaver workflow, @input, @output,
|
|
103210
|
+
@connect, @node, @scope). The compiler parses these annotations, validates the
|
|
103211
|
+
graph, and generates executable code inline. The source file is the workflow:
|
|
103212
|
+
no JSON configs, no YAML, no separate graph files.
|
|
103213
|
+
|
|
103214
|
+
Key concepts: node types define reusable processing steps with typed input/output
|
|
103215
|
+
ports. Workflows instantiate nodes and connect their ports. Start and Exit are
|
|
103216
|
+
implicit boundary nodes. The compiler handles execution ordering, type checking,
|
|
103217
|
+
and code generation.`;
|
|
103218
|
+
function buildAssistantPreamble() {
|
|
103219
|
+
const allSlugs = listTopics().map((t) => t.slug);
|
|
103220
|
+
return `# Flow Weaver Context
|
|
103221
|
+
|
|
103222
|
+
You have Flow Weaver MCP tools available (fw_ prefix). Use them to create,
|
|
103223
|
+
modify, validate, compile, and inspect workflows without manual file editing.
|
|
103224
|
+
|
|
103225
|
+
For documentation not included below, call fw_docs(action="read", topic="<slug>").
|
|
103226
|
+
Available topic slugs: ${allSlugs.join(", ")}.
|
|
103227
|
+
|
|
103228
|
+
Tool quick reference:
|
|
103229
|
+
- fw_create_model: Build workflow from structured description (steps + flow path)
|
|
103230
|
+
- fw_implement_node: Replace a declare stub with a real function body
|
|
103231
|
+
- fw_modify / fw_modify_batch: Add/remove nodes, connections, rename, reposition
|
|
103232
|
+
- fw_validate: Check for errors after any change
|
|
103233
|
+
- fw_describe: Inspect structure. Use format "text" for readable, "json" for data
|
|
103234
|
+
- fw_diagram: Visualize. Prefer format "ascii-compact" in chat contexts
|
|
103235
|
+
- fw_compile: Generate executable TypeScript from annotations
|
|
103236
|
+
- fw_docs: Look up reference docs by topic slug
|
|
103237
|
+
- fw_scaffold: Create from templates (sequential, foreach, ai-agent, etc.)
|
|
103238
|
+
|
|
103239
|
+
File conventions: .ts extension, camelCase node names, PascalCase workflow names.`;
|
|
103240
|
+
}
|
|
103241
|
+
function resolveTopics(preset, explicit, add) {
|
|
103242
|
+
const base = explicit ?? PRESETS[preset];
|
|
103243
|
+
const combined = add ? [...base, ...add] : base;
|
|
103244
|
+
return [...new Set(combined)];
|
|
103245
|
+
}
|
|
103246
|
+
function buildGrammarSection() {
|
|
103247
|
+
const grammars = getAllGrammars();
|
|
103248
|
+
const allProductions = [
|
|
103249
|
+
...grammars.port,
|
|
103250
|
+
...grammars.node,
|
|
103251
|
+
...grammars.connect,
|
|
103252
|
+
...grammars.position,
|
|
103253
|
+
...grammars.scope
|
|
103254
|
+
];
|
|
103255
|
+
const ebnf = serializedToEBNF(allProductions);
|
|
103256
|
+
return `## JSDoc Annotation Grammar (EBNF)
|
|
103257
|
+
|
|
103258
|
+
\`\`\`ebnf
|
|
103259
|
+
${ebnf}
|
|
103260
|
+
\`\`\``;
|
|
103261
|
+
}
|
|
103262
|
+
function buildContext(options = {}) {
|
|
103263
|
+
const profile = options.profile ?? "standalone";
|
|
103264
|
+
const preset = options.preset ?? "core";
|
|
103265
|
+
const includeGrammar = options.includeGrammar ?? true;
|
|
103266
|
+
const topicSlugs = resolveTopics(preset, options.topics, options.addTopics);
|
|
103267
|
+
const sections = [];
|
|
103268
|
+
if (profile === "standalone") {
|
|
103269
|
+
sections.push(STANDALONE_PREAMBLE);
|
|
103270
|
+
} else {
|
|
103271
|
+
sections.push(buildAssistantPreamble());
|
|
103272
|
+
}
|
|
103273
|
+
if (includeGrammar) {
|
|
103274
|
+
sections.push(buildGrammarSection());
|
|
103275
|
+
}
|
|
103276
|
+
let topicCount = 0;
|
|
103277
|
+
const includedSlugs = [];
|
|
103278
|
+
for (const slug of topicSlugs) {
|
|
103279
|
+
const doc = readTopic(slug, true);
|
|
103280
|
+
if (!doc) continue;
|
|
103281
|
+
let body = doc.content;
|
|
103282
|
+
if (body.startsWith("# ")) {
|
|
103283
|
+
const lines = body.split("\n");
|
|
103284
|
+
let startLine = 1;
|
|
103285
|
+
if (lines.length > 1 && lines[1].trim() && !lines[1].startsWith("#")) {
|
|
103286
|
+
startLine = 2;
|
|
103287
|
+
}
|
|
103288
|
+
body = lines.slice(startLine).join("\n").replace(/^\n+/, "");
|
|
103289
|
+
}
|
|
103290
|
+
const bodyLines = body.split("\n");
|
|
103291
|
+
let inCode = false;
|
|
103292
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
103293
|
+
if (bodyLines[i].trimStart().startsWith("```")) {
|
|
103294
|
+
inCode = !inCode;
|
|
103295
|
+
continue;
|
|
103296
|
+
}
|
|
103297
|
+
if (!inCode && bodyLines[i].match(/^#{1,5}\s/)) {
|
|
103298
|
+
bodyLines[i] = "##" + bodyLines[i];
|
|
103299
|
+
}
|
|
103300
|
+
}
|
|
103301
|
+
body = bodyLines.join("\n");
|
|
103302
|
+
const heading = doc.name || slug;
|
|
103303
|
+
sections.push(`## ${heading}
|
|
103304
|
+
|
|
103305
|
+
${body}`);
|
|
103306
|
+
topicCount++;
|
|
103307
|
+
includedSlugs.push(slug);
|
|
103308
|
+
}
|
|
103309
|
+
const content = sections.join("\n\n---\n\n");
|
|
103310
|
+
const lineCount = content.split("\n").length;
|
|
103311
|
+
return {
|
|
103312
|
+
content,
|
|
103313
|
+
topicCount,
|
|
103314
|
+
lineCount,
|
|
103315
|
+
topicSlugs: includedSlugs,
|
|
103316
|
+
profile
|
|
103317
|
+
};
|
|
103318
|
+
}
|
|
103319
|
+
|
|
103320
|
+
// src/mcp/tools-context.ts
|
|
103321
|
+
function registerContextTools(mcp) {
|
|
103322
|
+
mcp.tool(
|
|
103323
|
+
"fw_context",
|
|
103324
|
+
'Generate a self-contained LLM context bundle with Flow Weaver documentation, grammar, and conventions. Use preset="core" for basics, "authoring" for writing workflows, "full" for everything, "ops" for CLI/deployment reference.',
|
|
103325
|
+
{
|
|
103326
|
+
preset: external_exports.enum(["core", "authoring", "full", "ops"]).optional().default("core").describe("Topic preset"),
|
|
103327
|
+
profile: external_exports.enum(["standalone", "assistant"]).optional().default("assistant").describe("standalone = full self-contained dump, assistant = assumes MCP tools available"),
|
|
103328
|
+
topics: external_exports.string().optional().describe("Comma-separated topic slugs (overrides preset)"),
|
|
103329
|
+
addTopics: external_exports.string().optional().describe("Comma-separated slugs to add to preset"),
|
|
103330
|
+
includeGrammar: external_exports.boolean().optional().default(true).describe("Include EBNF annotation grammar section")
|
|
103331
|
+
},
|
|
103332
|
+
async (args) => {
|
|
103333
|
+
try {
|
|
103334
|
+
const result = buildContext({
|
|
103335
|
+
preset: args.preset,
|
|
103336
|
+
profile: args.profile,
|
|
103337
|
+
topics: args.topics ? args.topics.split(",").map((s) => s.trim()) : void 0,
|
|
103338
|
+
addTopics: args.addTopics ? args.addTopics.split(",").map((s) => s.trim()) : void 0,
|
|
103339
|
+
includeGrammar: args.includeGrammar
|
|
103340
|
+
});
|
|
103341
|
+
return makeToolResult({
|
|
103342
|
+
profile: result.profile,
|
|
103343
|
+
topicCount: result.topicCount,
|
|
103344
|
+
lineCount: result.lineCount,
|
|
103345
|
+
topicSlugs: result.topicSlugs,
|
|
103346
|
+
content: result.content
|
|
103347
|
+
});
|
|
103348
|
+
} catch (err) {
|
|
103349
|
+
return makeErrorResult(
|
|
103350
|
+
"CONTEXT_ERROR",
|
|
103351
|
+
`fw_context failed: ${err instanceof Error ? err.message : String(err)}`
|
|
103352
|
+
);
|
|
103353
|
+
}
|
|
103354
|
+
}
|
|
103355
|
+
);
|
|
103356
|
+
}
|
|
103357
|
+
|
|
103165
103358
|
// src/mcp/resources.ts
|
|
103166
103359
|
function registerResources(mcp, connection, buffer) {
|
|
103167
103360
|
mcp.resource(
|
|
@@ -103336,6 +103529,7 @@ async function startMcpServer(options) {
|
|
|
103336
103529
|
registerDocsTools(mcp);
|
|
103337
103530
|
registerModelTools(mcp);
|
|
103338
103531
|
registerDebugTools(mcp);
|
|
103532
|
+
registerContextTools(mcp);
|
|
103339
103533
|
registerResources(mcp, connection, buffer);
|
|
103340
103534
|
registerPrompts(mcp);
|
|
103341
103535
|
if (!options._testDeps && options.stdio) {
|
|
@@ -106445,10 +106639,56 @@ async function docsSearchCommand(query, options) {
|
|
|
106445
106639
|
logger.newline();
|
|
106446
106640
|
}
|
|
106447
106641
|
|
|
106642
|
+
// src/cli/commands/context.ts
|
|
106643
|
+
import * as fs46 from "fs";
|
|
106644
|
+
async function contextCommand(preset, options) {
|
|
106645
|
+
if (options.list) {
|
|
106646
|
+
logger.section("Context Presets");
|
|
106647
|
+
logger.newline();
|
|
106648
|
+
const maxName = Math.max(...PRESET_NAMES.map((n) => n.length));
|
|
106649
|
+
for (const name of PRESET_NAMES) {
|
|
106650
|
+
const topics = PRESETS[name];
|
|
106651
|
+
logger.log(` ${name.padEnd(maxName + 2)} ${topics.join(", ")}`);
|
|
106652
|
+
}
|
|
106653
|
+
logger.newline();
|
|
106654
|
+
logger.log(" Usage: flow-weaver context [preset] [options]");
|
|
106655
|
+
logger.newline();
|
|
106656
|
+
return;
|
|
106657
|
+
}
|
|
106658
|
+
const presetName = preset ?? "core";
|
|
106659
|
+
if (!PRESET_NAMES.includes(presetName) && !options.topics) {
|
|
106660
|
+
logger.error(
|
|
106661
|
+
`Unknown preset "${preset}". Available: ${PRESET_NAMES.join(", ")}. Or use --topics to specify topics directly.`
|
|
106662
|
+
);
|
|
106663
|
+
process.exit(1);
|
|
106664
|
+
}
|
|
106665
|
+
const profile = options.profile ?? "standalone";
|
|
106666
|
+
if (profile !== "standalone" && profile !== "assistant") {
|
|
106667
|
+
logger.error(`Unknown profile "${profile}". Use "standalone" or "assistant".`);
|
|
106668
|
+
process.exit(1);
|
|
106669
|
+
}
|
|
106670
|
+
const result = buildContext({
|
|
106671
|
+
preset: PRESET_NAMES.includes(presetName) ? presetName : "core",
|
|
106672
|
+
profile,
|
|
106673
|
+
topics: options.topics ? options.topics.split(",").map((s) => s.trim()) : void 0,
|
|
106674
|
+
addTopics: options.add ? options.add.split(",").map((s) => s.trim()) : void 0,
|
|
106675
|
+
includeGrammar: options.grammar !== false
|
|
106676
|
+
});
|
|
106677
|
+
if (options.output) {
|
|
106678
|
+
fs46.writeFileSync(options.output, result.content, "utf-8");
|
|
106679
|
+
logger.success(`Context written to ${options.output}`);
|
|
106680
|
+
} else {
|
|
106681
|
+
process.stdout.write(result.content);
|
|
106682
|
+
}
|
|
106683
|
+
const stats = `${result.topicCount} topics, ${result.lineCount} lines (${result.profile} profile)`;
|
|
106684
|
+
process.stderr.write(`flow-weaver context: ${stats}
|
|
106685
|
+
`);
|
|
106686
|
+
}
|
|
106687
|
+
|
|
106448
106688
|
// src/cli/commands/status.ts
|
|
106449
106689
|
init_api6();
|
|
106450
106690
|
init_validator();
|
|
106451
|
-
import * as
|
|
106691
|
+
import * as fs47 from "fs";
|
|
106452
106692
|
import * as path47 from "path";
|
|
106453
106693
|
init_error_utils();
|
|
106454
106694
|
function formatPortList(ports) {
|
|
@@ -106458,7 +106698,7 @@ async function statusCommand(input, options = {}) {
|
|
|
106458
106698
|
const { workflowName, json: json2 = false } = options;
|
|
106459
106699
|
try {
|
|
106460
106700
|
const filePath = path47.resolve(input);
|
|
106461
|
-
if (!
|
|
106701
|
+
if (!fs47.existsSync(filePath)) {
|
|
106462
106702
|
if (json2) {
|
|
106463
106703
|
console.log(JSON.stringify({ error: `File not found: ${input}` }));
|
|
106464
106704
|
} else {
|
|
@@ -106550,7 +106790,7 @@ async function statusCommand(input, options = {}) {
|
|
|
106550
106790
|
// src/cli/commands/implement.ts
|
|
106551
106791
|
init_api6();
|
|
106552
106792
|
init_annotation_generator();
|
|
106553
|
-
import * as
|
|
106793
|
+
import * as fs48 from "fs";
|
|
106554
106794
|
import * as path48 from "path";
|
|
106555
106795
|
init_error_utils();
|
|
106556
106796
|
function findDeclareFunction2(source, functionName) {
|
|
@@ -106575,7 +106815,7 @@ async function implementCommand(input, nodeName, options = {}) {
|
|
|
106575
106815
|
const { workflowName, preview = false } = options;
|
|
106576
106816
|
try {
|
|
106577
106817
|
const filePath = path48.resolve(input);
|
|
106578
|
-
if (!
|
|
106818
|
+
if (!fs48.existsSync(filePath)) {
|
|
106579
106819
|
logger.error(`File not found: ${input}`);
|
|
106580
106820
|
process.exit(1);
|
|
106581
106821
|
}
|
|
@@ -106605,7 +106845,7 @@ async function implementCommand(input, nodeName, options = {}) {
|
|
|
106605
106845
|
}
|
|
106606
106846
|
process.exit(1);
|
|
106607
106847
|
}
|
|
106608
|
-
const source =
|
|
106848
|
+
const source = fs48.readFileSync(filePath, "utf8");
|
|
106609
106849
|
const found = findDeclareFunction2(source, stubNodeType.functionName);
|
|
106610
106850
|
if (!found) {
|
|
106611
106851
|
logger.error(`Could not find "declare function ${stubNodeType.functionName}" in source file.`);
|
|
@@ -106620,7 +106860,7 @@ async function implementCommand(input, nodeName, options = {}) {
|
|
|
106620
106860
|
console.log(replacement);
|
|
106621
106861
|
} else {
|
|
106622
106862
|
const updated = source.replace(found.match, replacement);
|
|
106623
|
-
|
|
106863
|
+
fs48.writeFileSync(filePath, updated, "utf8");
|
|
106624
106864
|
logger.success(`Implemented ${stubNodeType.functionName} in ${path48.basename(filePath)}`);
|
|
106625
106865
|
}
|
|
106626
106866
|
} catch (error2) {
|
|
@@ -106630,7 +106870,7 @@ async function implementCommand(input, nodeName, options = {}) {
|
|
|
106630
106870
|
}
|
|
106631
106871
|
|
|
106632
106872
|
// src/cli/commands/market.ts
|
|
106633
|
-
import * as
|
|
106873
|
+
import * as fs49 from "fs";
|
|
106634
106874
|
import * as path49 from "path";
|
|
106635
106875
|
import { execSync as execSync5 } from "child_process";
|
|
106636
106876
|
init_error_utils();
|
|
@@ -106641,10 +106881,10 @@ async function marketInitCommand(name, options = {}) {
|
|
|
106641
106881
|
name = suggested;
|
|
106642
106882
|
}
|
|
106643
106883
|
const targetDir = path49.resolve(name);
|
|
106644
|
-
if (
|
|
106645
|
-
const stat2 =
|
|
106884
|
+
if (fs49.existsSync(targetDir)) {
|
|
106885
|
+
const stat2 = fs49.statSync(targetDir);
|
|
106646
106886
|
if (stat2.isDirectory()) {
|
|
106647
|
-
const contents =
|
|
106887
|
+
const contents = fs49.readdirSync(targetDir);
|
|
106648
106888
|
if (contents.length > 0) {
|
|
106649
106889
|
logger.error(`Directory "${name}" already exists and is not empty`);
|
|
106650
106890
|
process.exit(1);
|
|
@@ -106665,7 +106905,7 @@ async function marketInitCommand(name, options = {}) {
|
|
|
106665
106905
|
path49.join(targetDir, "src", "patterns")
|
|
106666
106906
|
];
|
|
106667
106907
|
for (const dir of dirs) {
|
|
106668
|
-
|
|
106908
|
+
fs49.mkdirSync(dir, { recursive: true });
|
|
106669
106909
|
}
|
|
106670
106910
|
const shortName = name.replace(/^flowweaver-pack-/, "");
|
|
106671
106911
|
const pkg = {
|
|
@@ -106696,7 +106936,7 @@ async function marketInitCommand(name, options = {}) {
|
|
|
106696
106936
|
},
|
|
106697
106937
|
files: ["dist", "flowweaver.manifest.json", "README.md", "LICENSE"]
|
|
106698
106938
|
};
|
|
106699
|
-
|
|
106939
|
+
fs49.writeFileSync(
|
|
106700
106940
|
path49.join(targetDir, "package.json"),
|
|
106701
106941
|
JSON.stringify(pkg, null, 2) + "\n"
|
|
106702
106942
|
);
|
|
@@ -106715,7 +106955,7 @@ async function marketInitCommand(name, options = {}) {
|
|
|
106715
106955
|
include: ["src"],
|
|
106716
106956
|
exclude: ["node_modules", "dist"]
|
|
106717
106957
|
};
|
|
106718
|
-
|
|
106958
|
+
fs49.writeFileSync(
|
|
106719
106959
|
path49.join(targetDir, "tsconfig.json"),
|
|
106720
106960
|
JSON.stringify(tsconfig, null, 2) + "\n"
|
|
106721
106961
|
);
|
|
@@ -106734,20 +106974,20 @@ export function sample(execute: () => void, data: string): { result: string } {
|
|
|
106734
106974
|
return { result: data.toUpperCase() };
|
|
106735
106975
|
}
|
|
106736
106976
|
`;
|
|
106737
|
-
|
|
106738
|
-
|
|
106977
|
+
fs49.writeFileSync(path49.join(targetDir, "src", "node-types", "sample.ts"), sampleNodeType);
|
|
106978
|
+
fs49.writeFileSync(
|
|
106739
106979
|
path49.join(targetDir, "src", "node-types", "index.ts"),
|
|
106740
106980
|
"export { sample } from './sample.js';\n"
|
|
106741
106981
|
);
|
|
106742
|
-
|
|
106982
|
+
fs49.writeFileSync(
|
|
106743
106983
|
path49.join(targetDir, "src", "workflows", "index.ts"),
|
|
106744
106984
|
"// Export workflows here\n"
|
|
106745
106985
|
);
|
|
106746
|
-
|
|
106986
|
+
fs49.writeFileSync(
|
|
106747
106987
|
path49.join(targetDir, "src", "patterns", "index.ts"),
|
|
106748
106988
|
"// Export patterns here\n"
|
|
106749
106989
|
);
|
|
106750
|
-
|
|
106990
|
+
fs49.writeFileSync(
|
|
106751
106991
|
path49.join(targetDir, "src", "index.ts"),
|
|
106752
106992
|
[
|
|
106753
106993
|
"export * from './node-types/index.js';",
|
|
@@ -106781,8 +107021,8 @@ npm run pack # Generate flowweaver.manifest.json
|
|
|
106781
107021
|
npm publish # Publish to npm
|
|
106782
107022
|
\`\`\`
|
|
106783
107023
|
`;
|
|
106784
|
-
|
|
106785
|
-
|
|
107024
|
+
fs49.writeFileSync(path49.join(targetDir, "README.md"), readme);
|
|
107025
|
+
fs49.writeFileSync(
|
|
106786
107026
|
path49.join(targetDir, ".gitignore"),
|
|
106787
107027
|
["node_modules", "dist", "*.tgz", ""].join("\n")
|
|
106788
107028
|
);
|
|
@@ -106853,10 +107093,10 @@ async function marketPublishCommand(directory, options = {}) {
|
|
|
106853
107093
|
const { dryRun = false, tag } = options;
|
|
106854
107094
|
logger.section("Publishing Marketplace Package");
|
|
106855
107095
|
await marketPackCommand(dir, { json: false });
|
|
106856
|
-
if (!
|
|
107096
|
+
if (!fs49.existsSync(path49.join(dir, "LICENSE"))) {
|
|
106857
107097
|
logger.warn("LICENSE file not found \u2014 consider adding one");
|
|
106858
107098
|
}
|
|
106859
|
-
const pkg = JSON.parse(
|
|
107099
|
+
const pkg = JSON.parse(fs49.readFileSync(path49.join(dir, "package.json"), "utf-8"));
|
|
106860
107100
|
logger.info(`Publishing ${pkg.name}@${pkg.version}`);
|
|
106861
107101
|
const npmArgs = ["publish"];
|
|
106862
107102
|
if (dryRun) npmArgs.push("--dry-run");
|
|
@@ -107025,7 +107265,7 @@ function displayInstalledPackage(pkg) {
|
|
|
107025
107265
|
|
|
107026
107266
|
// src/cli/commands/mcp-setup.ts
|
|
107027
107267
|
import { execSync as execSync6 } from "child_process";
|
|
107028
|
-
import * as
|
|
107268
|
+
import * as fs50 from "fs";
|
|
107029
107269
|
import * as path50 from "path";
|
|
107030
107270
|
import * as os3 from "os";
|
|
107031
107271
|
var MCP_COMMAND = "npx";
|
|
@@ -107043,7 +107283,7 @@ function defaultDeps() {
|
|
|
107043
107283
|
},
|
|
107044
107284
|
fileExists: async (filePath) => {
|
|
107045
107285
|
try {
|
|
107046
|
-
await
|
|
107286
|
+
await fs50.promises.access(filePath);
|
|
107047
107287
|
return true;
|
|
107048
107288
|
} catch {
|
|
107049
107289
|
return false;
|
|
@@ -107051,16 +107291,16 @@ function defaultDeps() {
|
|
|
107051
107291
|
},
|
|
107052
107292
|
readFile: async (filePath) => {
|
|
107053
107293
|
try {
|
|
107054
|
-
return await
|
|
107294
|
+
return await fs50.promises.readFile(filePath, "utf8");
|
|
107055
107295
|
} catch {
|
|
107056
107296
|
return null;
|
|
107057
107297
|
}
|
|
107058
107298
|
},
|
|
107059
107299
|
writeFile: async (filePath, content) => {
|
|
107060
|
-
await
|
|
107300
|
+
await fs50.promises.writeFile(filePath, content, "utf8");
|
|
107061
107301
|
},
|
|
107062
107302
|
mkdir: async (dirPath) => {
|
|
107063
|
-
await
|
|
107303
|
+
await fs50.promises.mkdir(dirPath, { recursive: true });
|
|
107064
107304
|
},
|
|
107065
107305
|
cwd: () => process.cwd(),
|
|
107066
107306
|
homedir: () => os3.homedir(),
|
|
@@ -107350,7 +107590,7 @@ async function mcpSetupCommand(options, deps) {
|
|
|
107350
107590
|
|
|
107351
107591
|
// src/cli/index.ts
|
|
107352
107592
|
init_error_utils();
|
|
107353
|
-
var version2 = true ? "0.
|
|
107593
|
+
var version2 = true ? "0.12.0" : "0.0.0-dev";
|
|
107354
107594
|
var program2 = new Command();
|
|
107355
107595
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
107356
107596
|
program2.configureOutput({
|
|
@@ -107678,6 +107918,14 @@ program2.command("docs [args...]").description("Browse reference documentation")
|
|
|
107678
107918
|
process.exit(1);
|
|
107679
107919
|
}
|
|
107680
107920
|
});
|
|
107921
|
+
program2.command("context [preset]").description("Generate LLM context bundle from documentation and grammar").option("--profile <profile>", "Output profile: standalone or assistant", "standalone").option("--topics <slugs>", "Comma-separated topic slugs (overrides preset)").option("--add <slugs>", "Comma-separated topic slugs to add to preset").option("--no-grammar", "Omit EBNF grammar section").option("-o, --output <path>", "Write to file instead of stdout").option("--list", "List available presets and exit").action(async (preset, options) => {
|
|
107922
|
+
try {
|
|
107923
|
+
await contextCommand(preset, options);
|
|
107924
|
+
} catch (error2) {
|
|
107925
|
+
logger.error(`Command failed: ${getErrorMessage(error2)}`);
|
|
107926
|
+
process.exit(1);
|
|
107927
|
+
}
|
|
107928
|
+
});
|
|
107681
107929
|
var marketCmd = program2.command("market").description("Discover, install, and publish marketplace packages");
|
|
107682
107930
|
marketCmd.command("init <name>").description("Scaffold a new marketplace package").option("-d, --description <desc>", "Package description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults", false).action(async (name, options) => {
|
|
107683
107931
|
try {
|
package/dist/cli/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { migrateCommand } from './commands/migrate.js';
|
|
|
32
32
|
import { changelogCommand } from './commands/changelog.js';
|
|
33
33
|
import { stripCommand } from './commands/strip.js';
|
|
34
34
|
import { docsListCommand, docsReadCommand, docsSearchCommand } from './commands/docs.js';
|
|
35
|
+
import { contextCommand } from './commands/context.js';
|
|
35
36
|
import { statusCommand } from './commands/status.js';
|
|
36
37
|
import { implementCommand } from './commands/implement.js';
|
|
37
38
|
import { marketInitCommand, marketPackCommand, marketPublishCommand, marketInstallCommand, marketSearchCommand, marketListCommand, } from './commands/market.js';
|
|
@@ -703,6 +704,25 @@ program
|
|
|
703
704
|
process.exit(1);
|
|
704
705
|
}
|
|
705
706
|
});
|
|
707
|
+
// Context command: generate LLM context bundles
|
|
708
|
+
program
|
|
709
|
+
.command('context [preset]')
|
|
710
|
+
.description('Generate LLM context bundle from documentation and grammar')
|
|
711
|
+
.option('--profile <profile>', 'Output profile: standalone or assistant', 'standalone')
|
|
712
|
+
.option('--topics <slugs>', 'Comma-separated topic slugs (overrides preset)')
|
|
713
|
+
.option('--add <slugs>', 'Comma-separated topic slugs to add to preset')
|
|
714
|
+
.option('--no-grammar', 'Omit EBNF grammar section')
|
|
715
|
+
.option('-o, --output <path>', 'Write to file instead of stdout')
|
|
716
|
+
.option('--list', 'List available presets and exit')
|
|
717
|
+
.action(async (preset, options) => {
|
|
718
|
+
try {
|
|
719
|
+
await contextCommand(preset, options);
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
logger.error(`Command failed: ${getErrorMessage(error)}`);
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
706
726
|
// Marketplace command group
|
|
707
727
|
const marketCmd = program.command('market').description('Discover, install, and publish marketplace packages');
|
|
708
728
|
marketCmd
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM context bundle builder.
|
|
3
|
+
*
|
|
4
|
+
* Composes Flow Weaver documentation, annotation grammar, and a profile-specific
|
|
5
|
+
* preamble into a single markdown document suitable for LLM consumption.
|
|
6
|
+
*/
|
|
7
|
+
export type ContextProfile = 'standalone' | 'assistant';
|
|
8
|
+
export type ContextPreset = 'core' | 'authoring' | 'full' | 'ops';
|
|
9
|
+
export interface ContextOptions {
|
|
10
|
+
preset?: ContextPreset;
|
|
11
|
+
profile?: ContextProfile;
|
|
12
|
+
/** Explicit topic slugs. Overrides the preset's topic list. */
|
|
13
|
+
topics?: string[];
|
|
14
|
+
/** Extra topic slugs appended to the preset. */
|
|
15
|
+
addTopics?: string[];
|
|
16
|
+
/** Include EBNF grammar section. Default true. */
|
|
17
|
+
includeGrammar?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ContextResult {
|
|
20
|
+
content: string;
|
|
21
|
+
topicCount: number;
|
|
22
|
+
lineCount: number;
|
|
23
|
+
topicSlugs: string[];
|
|
24
|
+
profile: ContextProfile;
|
|
25
|
+
}
|
|
26
|
+
export declare const PRESETS: Record<ContextPreset, string[]>;
|
|
27
|
+
export declare const PRESET_NAMES: ContextPreset[];
|
|
28
|
+
export declare function resolveTopics(preset: ContextPreset, explicit?: string[], add?: string[]): string[];
|
|
29
|
+
export declare function buildContext(options?: ContextOptions): ContextResult;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM context bundle builder.
|
|
3
|
+
*
|
|
4
|
+
* Composes Flow Weaver documentation, annotation grammar, and a profile-specific
|
|
5
|
+
* preamble into a single markdown document suitable for LLM consumption.
|
|
6
|
+
*/
|
|
7
|
+
import { readTopic, listTopics } from '../docs/index.js';
|
|
8
|
+
import { getAllGrammars, serializedToEBNF } from '../chevrotain-parser/grammar-diagrams.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Presets
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export const PRESETS = {
|
|
13
|
+
core: ['concepts', 'jsdoc-grammar', 'tutorial'],
|
|
14
|
+
authoring: [
|
|
15
|
+
'concepts',
|
|
16
|
+
'jsdoc-grammar',
|
|
17
|
+
'advanced-annotations',
|
|
18
|
+
'built-in-nodes',
|
|
19
|
+
'scaffold',
|
|
20
|
+
'node-conversion',
|
|
21
|
+
'patterns',
|
|
22
|
+
],
|
|
23
|
+
ops: [
|
|
24
|
+
'cli-reference',
|
|
25
|
+
'compilation',
|
|
26
|
+
'deployment',
|
|
27
|
+
'export-interface',
|
|
28
|
+
'debugging',
|
|
29
|
+
'error-codes',
|
|
30
|
+
],
|
|
31
|
+
full: [
|
|
32
|
+
'concepts',
|
|
33
|
+
'tutorial',
|
|
34
|
+
'jsdoc-grammar',
|
|
35
|
+
'advanced-annotations',
|
|
36
|
+
'built-in-nodes',
|
|
37
|
+
'cli-reference',
|
|
38
|
+
'compilation',
|
|
39
|
+
'debugging',
|
|
40
|
+
'deployment',
|
|
41
|
+
'error-codes',
|
|
42
|
+
'export-interface',
|
|
43
|
+
'iterative-development',
|
|
44
|
+
'marketplace',
|
|
45
|
+
'node-conversion',
|
|
46
|
+
'patterns',
|
|
47
|
+
'scaffold',
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
export const PRESET_NAMES = Object.keys(PRESETS);
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Preambles
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
const STANDALONE_PREAMBLE = `# Flow Weaver Reference
|
|
55
|
+
|
|
56
|
+
Flow Weaver is a TypeScript workflow compiler. You write plain .ts files with
|
|
57
|
+
JSDoc annotations (@flowWeaver nodeType, @flowWeaver workflow, @input, @output,
|
|
58
|
+
@connect, @node, @scope). The compiler parses these annotations, validates the
|
|
59
|
+
graph, and generates executable code inline. The source file is the workflow:
|
|
60
|
+
no JSON configs, no YAML, no separate graph files.
|
|
61
|
+
|
|
62
|
+
Key concepts: node types define reusable processing steps with typed input/output
|
|
63
|
+
ports. Workflows instantiate nodes and connect their ports. Start and Exit are
|
|
64
|
+
implicit boundary nodes. The compiler handles execution ordering, type checking,
|
|
65
|
+
and code generation.`;
|
|
66
|
+
function buildAssistantPreamble() {
|
|
67
|
+
const allSlugs = listTopics().map((t) => t.slug);
|
|
68
|
+
return `# Flow Weaver Context
|
|
69
|
+
|
|
70
|
+
You have Flow Weaver MCP tools available (fw_ prefix). Use them to create,
|
|
71
|
+
modify, validate, compile, and inspect workflows without manual file editing.
|
|
72
|
+
|
|
73
|
+
For documentation not included below, call fw_docs(action="read", topic="<slug>").
|
|
74
|
+
Available topic slugs: ${allSlugs.join(', ')}.
|
|
75
|
+
|
|
76
|
+
Tool quick reference:
|
|
77
|
+
- fw_create_model: Build workflow from structured description (steps + flow path)
|
|
78
|
+
- fw_implement_node: Replace a declare stub with a real function body
|
|
79
|
+
- fw_modify / fw_modify_batch: Add/remove nodes, connections, rename, reposition
|
|
80
|
+
- fw_validate: Check for errors after any change
|
|
81
|
+
- fw_describe: Inspect structure. Use format "text" for readable, "json" for data
|
|
82
|
+
- fw_diagram: Visualize. Prefer format "ascii-compact" in chat contexts
|
|
83
|
+
- fw_compile: Generate executable TypeScript from annotations
|
|
84
|
+
- fw_docs: Look up reference docs by topic slug
|
|
85
|
+
- fw_scaffold: Create from templates (sequential, foreach, ai-agent, etc.)
|
|
86
|
+
|
|
87
|
+
File conventions: .ts extension, camelCase node names, PascalCase workflow names.`;
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Topic resolution
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
export function resolveTopics(preset, explicit, add) {
|
|
93
|
+
const base = explicit ?? PRESETS[preset];
|
|
94
|
+
const combined = add ? [...base, ...add] : base;
|
|
95
|
+
// Deduplicate while preserving order
|
|
96
|
+
return [...new Set(combined)];
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Grammar builder
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
function buildGrammarSection() {
|
|
102
|
+
const grammars = getAllGrammars();
|
|
103
|
+
const allProductions = [
|
|
104
|
+
...grammars.port,
|
|
105
|
+
...grammars.node,
|
|
106
|
+
...grammars.connect,
|
|
107
|
+
...grammars.position,
|
|
108
|
+
...grammars.scope,
|
|
109
|
+
];
|
|
110
|
+
const ebnf = serializedToEBNF(allProductions);
|
|
111
|
+
return `## JSDoc Annotation Grammar (EBNF)\n\n\`\`\`ebnf\n${ebnf}\n\`\`\``;
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Main builder
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
export function buildContext(options = {}) {
|
|
117
|
+
const profile = options.profile ?? 'standalone';
|
|
118
|
+
const preset = options.preset ?? 'core';
|
|
119
|
+
const includeGrammar = options.includeGrammar ?? true;
|
|
120
|
+
const topicSlugs = resolveTopics(preset, options.topics, options.addTopics);
|
|
121
|
+
const sections = [];
|
|
122
|
+
// Preamble
|
|
123
|
+
if (profile === 'standalone') {
|
|
124
|
+
sections.push(STANDALONE_PREAMBLE);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
sections.push(buildAssistantPreamble());
|
|
128
|
+
}
|
|
129
|
+
// Grammar
|
|
130
|
+
if (includeGrammar) {
|
|
131
|
+
sections.push(buildGrammarSection());
|
|
132
|
+
}
|
|
133
|
+
// Topics
|
|
134
|
+
let topicCount = 0;
|
|
135
|
+
const includedSlugs = [];
|
|
136
|
+
for (const slug of topicSlugs) {
|
|
137
|
+
const doc = readTopic(slug, true);
|
|
138
|
+
if (!doc)
|
|
139
|
+
continue;
|
|
140
|
+
// Compact mode prepends "# Name\ndescription\n" which duplicates our heading.
|
|
141
|
+
// Strip the leading heading block so we can use our own consistent ## heading.
|
|
142
|
+
// Then bump all remaining headings down one level so they nest under our ##.
|
|
143
|
+
let body = doc.content;
|
|
144
|
+
if (body.startsWith('# ')) {
|
|
145
|
+
const lines = body.split('\n');
|
|
146
|
+
// Skip the "# Name" line and the description line that follows it
|
|
147
|
+
let startLine = 1;
|
|
148
|
+
if (lines.length > 1 && lines[1].trim() && !lines[1].startsWith('#')) {
|
|
149
|
+
startLine = 2;
|
|
150
|
+
}
|
|
151
|
+
body = lines.slice(startLine).join('\n').replace(/^\n+/, '');
|
|
152
|
+
}
|
|
153
|
+
// Bump all headings down one level (# -> ##, ## -> ###, etc.)
|
|
154
|
+
// so they nest under our ## topic heading. Only transform outside code blocks.
|
|
155
|
+
const bodyLines = body.split('\n');
|
|
156
|
+
let inCode = false;
|
|
157
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
158
|
+
if (bodyLines[i].trimStart().startsWith('```')) {
|
|
159
|
+
inCode = !inCode;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!inCode && bodyLines[i].match(/^#{1,5}\s/)) {
|
|
163
|
+
bodyLines[i] = '##' + bodyLines[i];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
body = bodyLines.join('\n');
|
|
167
|
+
const heading = doc.name || slug;
|
|
168
|
+
sections.push(`## ${heading}\n\n${body}`);
|
|
169
|
+
topicCount++;
|
|
170
|
+
includedSlugs.push(slug);
|
|
171
|
+
}
|
|
172
|
+
const content = sections.join('\n\n---\n\n');
|
|
173
|
+
const lineCount = content.split('\n').length;
|
|
174
|
+
return {
|
|
175
|
+
content,
|
|
176
|
+
topicCount,
|
|
177
|
+
lineCount,
|
|
178
|
+
topicSlugs: includedSlugs,
|
|
179
|
+
profile,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -868,6 +868,46 @@ export const MCP_TOOLS = [
|
|
|
868
868
|
category: 'debug',
|
|
869
869
|
params: [],
|
|
870
870
|
},
|
|
871
|
+
// Context tools (tools-context.ts)
|
|
872
|
+
{
|
|
873
|
+
name: 'fw_context',
|
|
874
|
+
description: 'Generate a self-contained LLM context bundle with Flow Weaver documentation, grammar, and conventions.',
|
|
875
|
+
category: 'query',
|
|
876
|
+
params: [
|
|
877
|
+
{
|
|
878
|
+
name: 'preset',
|
|
879
|
+
type: 'string',
|
|
880
|
+
description: 'Topic preset: core, authoring, full, or ops',
|
|
881
|
+
required: false,
|
|
882
|
+
enum: ['core', 'authoring', 'full', 'ops'],
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: 'profile',
|
|
886
|
+
type: 'string',
|
|
887
|
+
description: 'Output profile: standalone (full dump) or assistant (assumes MCP tools)',
|
|
888
|
+
required: false,
|
|
889
|
+
enum: ['standalone', 'assistant'],
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: 'topics',
|
|
893
|
+
type: 'string',
|
|
894
|
+
description: 'Comma-separated topic slugs (overrides preset)',
|
|
895
|
+
required: false,
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
name: 'addTopics',
|
|
899
|
+
type: 'string',
|
|
900
|
+
description: 'Comma-separated slugs to add to preset',
|
|
901
|
+
required: false,
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: 'includeGrammar',
|
|
905
|
+
type: 'boolean',
|
|
906
|
+
description: 'Include EBNF annotation grammar section (default: true)',
|
|
907
|
+
required: false,
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
},
|
|
871
911
|
];
|
|
872
912
|
/**
|
|
873
913
|
* Extract MCP tool documentation
|
package/dist/mcp/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { registerDiagramTools } from './tools-diagram.js';
|
|
|
13
13
|
import { registerDocsTools } from './tools-docs.js';
|
|
14
14
|
import { registerModelTools } from './tools-model.js';
|
|
15
15
|
import { registerDebugTools } from './tools-debug.js';
|
|
16
|
+
import { registerContextTools } from './tools-context.js';
|
|
16
17
|
import { registerResources } from './resources.js';
|
|
17
18
|
import { registerPrompts } from './prompts.js';
|
|
18
19
|
function parseEventFilterFromEnv() {
|
|
@@ -76,6 +77,7 @@ export async function startMcpServer(options) {
|
|
|
76
77
|
registerDocsTools(mcp);
|
|
77
78
|
registerModelTools(mcp);
|
|
78
79
|
registerDebugTools(mcp);
|
|
80
|
+
registerContextTools(mcp);
|
|
79
81
|
registerResources(mcp, connection, buffer);
|
|
80
82
|
registerPrompts(mcp);
|
|
81
83
|
// Connect transport (only in stdio MCP mode)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildContext } from '../context/index.js';
|
|
3
|
+
import { makeToolResult, makeErrorResult } from './response-utils.js';
|
|
4
|
+
export function registerContextTools(mcp) {
|
|
5
|
+
mcp.tool('fw_context', 'Generate a self-contained LLM context bundle with Flow Weaver documentation, grammar, and conventions. Use preset="core" for basics, "authoring" for writing workflows, "full" for everything, "ops" for CLI/deployment reference.', {
|
|
6
|
+
preset: z
|
|
7
|
+
.enum(['core', 'authoring', 'full', 'ops'])
|
|
8
|
+
.optional()
|
|
9
|
+
.default('core')
|
|
10
|
+
.describe('Topic preset'),
|
|
11
|
+
profile: z
|
|
12
|
+
.enum(['standalone', 'assistant'])
|
|
13
|
+
.optional()
|
|
14
|
+
.default('assistant')
|
|
15
|
+
.describe('standalone = full self-contained dump, assistant = assumes MCP tools available'),
|
|
16
|
+
topics: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Comma-separated topic slugs (overrides preset)'),
|
|
20
|
+
addTopics: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Comma-separated slugs to add to preset'),
|
|
24
|
+
includeGrammar: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.default(true)
|
|
28
|
+
.describe('Include EBNF annotation grammar section'),
|
|
29
|
+
}, async (args) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = buildContext({
|
|
32
|
+
preset: args.preset,
|
|
33
|
+
profile: args.profile,
|
|
34
|
+
topics: args.topics ? args.topics.split(',').map((s) => s.trim()) : undefined,
|
|
35
|
+
addTopics: args.addTopics ? args.addTopics.split(',').map((s) => s.trim()) : undefined,
|
|
36
|
+
includeGrammar: args.includeGrammar,
|
|
37
|
+
});
|
|
38
|
+
return makeToolResult({
|
|
39
|
+
profile: result.profile,
|
|
40
|
+
topicCount: result.topicCount,
|
|
41
|
+
lineCount: result.lineCount,
|
|
42
|
+
topicSlugs: result.topicSlugs,
|
|
43
|
+
content: result.content,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return makeErrorResult('CONTEXT_ERROR', `fw_context failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=tools-context.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: CLI Reference
|
|
3
3
|
description: Complete reference for all Flow Weaver CLI commands, flags, and options
|
|
4
|
-
keywords: [cli, commands, compile, validate, strip, run, watch, dev, serve, export, diagram, diff, doctor, init, migrate, marketplace, plugin, grammar, changelog, openapi, pattern, create, templates]
|
|
4
|
+
keywords: [cli, commands, compile, validate, strip, run, watch, dev, serve, export, diagram, diff, doctor, init, migrate, marketplace, plugin, grammar, changelog, openapi, pattern, create, templates, context]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# CLI Reference
|
|
@@ -34,6 +34,7 @@ Complete reference for all `flow-weaver` CLI commands.
|
|
|
34
34
|
| `changelog` | Generate changelog from git |
|
|
35
35
|
| `market` | Marketplace packages |
|
|
36
36
|
| `plugin` | External plugins |
|
|
37
|
+
| `context` | Generate LLM context bundle |
|
|
37
38
|
| `docs` | Browse reference documentation |
|
|
38
39
|
| `ui` | Send commands to the editor |
|
|
39
40
|
| `listen` | Stream editor events |
|
|
@@ -805,6 +806,37 @@ flow-weaver docs search "missing workflow"
|
|
|
805
806
|
|
|
806
807
|
---
|
|
807
808
|
|
|
809
|
+
### context
|
|
810
|
+
|
|
811
|
+
Generate a self-contained LLM context bundle from documentation and annotation grammar. Two profiles control the output format: `standalone` produces a complete reference for pasting into any LLM, `assistant` produces a leaner version that assumes MCP tools are available.
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
flow-weaver context [preset] [options]
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
| Flag | Description | Default |
|
|
818
|
+
|------|-------------|---------|
|
|
819
|
+
| `--profile <profile>` | `standalone` or `assistant` | `standalone` |
|
|
820
|
+
| `--topics <slugs>` | Comma-separated topic slugs (overrides preset) | — |
|
|
821
|
+
| `--add <slugs>` | Extra topic slugs on top of preset | — |
|
|
822
|
+
| `--no-grammar` | Omit EBNF grammar section | grammar included |
|
|
823
|
+
| `-o, --output <path>` | Write to file instead of stdout | stdout |
|
|
824
|
+
| `--list` | List available presets and exit | — |
|
|
825
|
+
|
|
826
|
+
Built-in presets: `core` (concepts, grammar, tutorial), `authoring` (concepts, grammar, annotations, built-in nodes, scaffold, node-conversion, patterns), `ops` (CLI, compilation, deployment, export, debugging, error-codes), `full` (all 16 topics).
|
|
827
|
+
|
|
828
|
+
**Examples:**
|
|
829
|
+
```bash
|
|
830
|
+
flow-weaver context core | pbcopy
|
|
831
|
+
flow-weaver context full -o .flow-weaver-context.md
|
|
832
|
+
flow-weaver context authoring --profile assistant
|
|
833
|
+
flow-weaver context --topics concepts,jsdoc-grammar,error-codes
|
|
834
|
+
flow-weaver context core --add error-codes
|
|
835
|
+
flow-weaver context --list
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
808
840
|
## Editor Integration
|
|
809
841
|
|
|
810
842
|
### ui focus-node
|
package/package.json
CHANGED