@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.
@@ -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
@@ -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 fs50 = __require("fs");
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 (fs50.existsSync(localBin)) return localBin;
1763
+ if (fs51.existsSync(localBin)) return localBin;
1764
1764
  if (sourceExt.includes(path51.extname(baseName))) return void 0;
1765
- const foundExt = sourceExt.find((ext2) => fs50.existsSync(`${localBin}${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 = fs50.realpathSync(this._scriptPath);
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: fs50 = defaultFS } = {}) {
7744
- this.#fs = fsFromOption(fs50);
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(fs50) {
8303
- return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs50 });
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(fs50) {
8332
- return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs50 });
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 fs50 = __require("fs");
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
- fs50.readFile(wasmPath, null, (error2, data) => {
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 fs50 = __require("fs");
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
- fs50.readFile(unescape(url2.pathname), function(error2, data2) {
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 = fs50.readFileSync(unescape(url2.pathname));
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
- fs50.writeFileSync(syncFile, "", "utf8");
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 (fs50.existsSync(syncFile)) {
49414
+ while (fs51.existsSync(syncFile)) {
49415
49415
  }
49416
- self2.responseText = fs50.readFileSync(contentFile, "utf8");
49416
+ self2.responseText = fs51.readFileSync(contentFile, "utf8");
49417
49417
  syncProc.stdin.end();
49418
- fs50.unlinkSync(contentFile);
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, fs50, exportName) {
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, fs50[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 fs50 = await import("fs");
94677
- fs50.writeFileSync(outputFile, code, "utf8");
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 fs46 from "fs";
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 (!fs46.existsSync(filePath)) {
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 fs47 from "fs";
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 (!fs47.existsSync(filePath)) {
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 = fs47.readFileSync(filePath, "utf8");
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
- fs47.writeFileSync(filePath, updated, "utf8");
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 fs48 from "fs";
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 (fs48.existsSync(targetDir)) {
106645
- const stat2 = fs48.statSync(targetDir);
106884
+ if (fs49.existsSync(targetDir)) {
106885
+ const stat2 = fs49.statSync(targetDir);
106646
106886
  if (stat2.isDirectory()) {
106647
- const contents = fs48.readdirSync(targetDir);
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
- fs48.mkdirSync(dir, { recursive: true });
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
- fs48.writeFileSync(
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
- fs48.writeFileSync(
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
- fs48.writeFileSync(path49.join(targetDir, "src", "node-types", "sample.ts"), sampleNodeType);
106738
- fs48.writeFileSync(
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
- fs48.writeFileSync(
106982
+ fs49.writeFileSync(
106743
106983
  path49.join(targetDir, "src", "workflows", "index.ts"),
106744
106984
  "// Export workflows here\n"
106745
106985
  );
106746
- fs48.writeFileSync(
106986
+ fs49.writeFileSync(
106747
106987
  path49.join(targetDir, "src", "patterns", "index.ts"),
106748
106988
  "// Export patterns here\n"
106749
106989
  );
106750
- fs48.writeFileSync(
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
- fs48.writeFileSync(path49.join(targetDir, "README.md"), readme);
106785
- fs48.writeFileSync(
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 (!fs48.existsSync(path49.join(dir, "LICENSE"))) {
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(fs48.readFileSync(path49.join(dir, "package.json"), "utf-8"));
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 fs49 from "fs";
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 fs49.promises.access(filePath);
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 fs49.promises.readFile(filePath, "utf8");
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 fs49.promises.writeFile(filePath, content, "utf8");
107300
+ await fs50.promises.writeFile(filePath, content, "utf8");
107061
107301
  },
107062
107302
  mkdir: async (dirPath) => {
107063
- await fs49.promises.mkdir(dirPath, { recursive: true });
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.11.0" : "0.0.0-dev";
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
@@ -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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerContextTools(mcp: McpServer): void;
3
+ //# sourceMappingURL=tools-context.d.ts.map
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",