@synergenius/flow-weaver 0.17.1 → 0.17.2

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.
Files changed (121) hide show
  1. package/dist/api/index.d.ts +4 -1
  2. package/dist/api/index.js +4 -1
  3. package/dist/api/templates.js +2 -2
  4. package/dist/api/validate.d.ts +2 -2
  5. package/dist/api/validate.js +6 -6
  6. package/dist/api/validation-registry.d.ts +10 -0
  7. package/dist/api/validation-registry.js +10 -0
  8. package/dist/ast/types.d.ts +91 -4
  9. package/dist/built-in-nodes/invoke-workflow.d.ts +1 -1
  10. package/dist/built-in-nodes/invoke-workflow.js +1 -1
  11. package/dist/chevrotain-parser/connect-parser.js +25 -7
  12. package/dist/cli/commands/compile.d.ts +5 -9
  13. package/dist/cli/commands/compile.js +21 -14
  14. package/dist/cli/commands/dev.d.ts +2 -13
  15. package/dist/cli/commands/dev.js +10 -204
  16. package/dist/cli/commands/doctor.js +6 -3
  17. package/dist/cli/commands/export.d.ts +8 -17
  18. package/dist/cli/commands/export.js +8 -17
  19. package/dist/cli/commands/init-personas.d.ts +12 -3
  20. package/dist/cli/commands/init-personas.js +27 -4
  21. package/dist/cli/commands/init.d.ts +2 -2
  22. package/dist/cli/commands/init.js +5 -11
  23. package/dist/cli/flow-weaver.mjs +61463 -60910
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +9 -7
  26. package/dist/cli/templates/index.d.ts +20 -1
  27. package/dist/cli/templates/index.js +66 -15
  28. package/dist/cli/templates/nodes/human-approval.js +2 -3
  29. package/dist/cli/templates/nodes/rag-retriever.js +1 -1
  30. package/dist/constants.d.ts +7 -0
  31. package/dist/constants.js +13 -3
  32. package/dist/context/index.js +13 -3
  33. package/dist/deployment/config/loader.js +2 -1
  34. package/dist/deployment/core/adapters.d.ts +1 -25
  35. package/dist/deployment/core/adapters.js +0 -95
  36. package/dist/deployment/core/formatters.d.ts +0 -15
  37. package/dist/deployment/core/formatters.js +0 -24
  38. package/dist/deployment/index.d.ts +7 -5
  39. package/dist/deployment/index.js +8 -5
  40. package/dist/deployment/types.d.ts +2 -45
  41. package/dist/diagram/html-viewer.js +65 -32
  42. package/dist/diagram/renderer.js +9 -6
  43. package/dist/diagram/theme.js +4 -0
  44. package/dist/diagram/types.d.ts +2 -0
  45. package/dist/doc-metadata/extractors/annotations.js +5 -5
  46. package/dist/doc-metadata/extractors/cli-commands.js +1 -1
  47. package/dist/doc-metadata/extractors/mcp-tools.js +1 -2
  48. package/dist/docs/index.d.ts +28 -1
  49. package/dist/docs/index.js +95 -28
  50. package/dist/export/index.d.ts +2 -3
  51. package/dist/{deployment/targets/cicd-base.d.ts → extensions/cicd/base-target.d.ts} +35 -36
  52. package/dist/{deployment/targets/cicd-base.js → extensions/cicd/base-target.js} +97 -57
  53. package/dist/{validation/cicd-detection.d.ts → extensions/cicd/detection.d.ts} +2 -2
  54. package/dist/{validation/cicd-detection.js → extensions/cicd/detection.js} +13 -1
  55. package/dist/extensions/cicd/docs/cicd.md +395 -0
  56. package/dist/extensions/cicd/index.d.ts +10 -0
  57. package/dist/extensions/cicd/index.js +10 -0
  58. package/dist/extensions/cicd/register.d.ts +11 -0
  59. package/dist/extensions/cicd/register.js +62 -0
  60. package/dist/extensions/cicd/rules.d.ts +30 -0
  61. package/dist/{validation/cicd-rules.js → extensions/cicd/rules.js} +60 -56
  62. package/dist/extensions/cicd/tag-handler.d.ts +14 -0
  63. package/dist/extensions/cicd/tag-handler.js +488 -0
  64. package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-docker.d.ts +1 -1
  65. package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-matrix.d.ts +1 -1
  66. package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-multi-env.d.ts +1 -1
  67. package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-test-deploy.d.ts +1 -1
  68. package/dist/extensions/index.d.ts +12 -0
  69. package/dist/extensions/index.js +12 -0
  70. package/dist/extensions/inngest/dev-mode.d.ts +9 -0
  71. package/dist/extensions/inngest/dev-mode.js +213 -0
  72. package/dist/{generator/inngest.d.ts → extensions/inngest/generator.d.ts} +2 -2
  73. package/dist/{generator/inngest.js → extensions/inngest/generator.js} +4 -4
  74. package/dist/extensions/inngest/index.d.ts +2 -0
  75. package/dist/extensions/inngest/index.js +2 -0
  76. package/dist/extensions/inngest/register.d.ts +6 -0
  77. package/dist/extensions/inngest/register.js +23 -0
  78. package/dist/extensions/inngest/templates/ai-agent-durable.d.ts +8 -0
  79. package/dist/{cli/templates/workflows → extensions/inngest/templates}/ai-agent-durable.js +8 -8
  80. package/dist/{cli/templates/workflows → extensions/inngest/templates}/ai-pipeline-durable.d.ts +2 -2
  81. package/dist/{cli/templates/workflows → extensions/inngest/templates}/ai-pipeline-durable.js +7 -7
  82. package/dist/generated-version.d.ts +1 -1
  83. package/dist/generated-version.js +1 -1
  84. package/dist/generator/compile-target-registry.d.ts +20 -0
  85. package/dist/generator/compile-target-registry.js +20 -0
  86. package/dist/generator/dev-mode-registry.d.ts +27 -0
  87. package/dist/generator/dev-mode-registry.js +20 -0
  88. package/dist/index.d.ts +4 -0
  89. package/dist/index.js +3 -0
  90. package/dist/jsdoc-parser.d.ts +12 -114
  91. package/dist/jsdoc-parser.js +57 -362
  92. package/dist/marketplace/index.d.ts +2 -2
  93. package/dist/marketplace/index.js +1 -1
  94. package/dist/marketplace/registry.d.ts +39 -1
  95. package/dist/marketplace/registry.js +77 -0
  96. package/dist/marketplace/types.d.ts +76 -3
  97. package/dist/mcp/server.d.ts +1 -0
  98. package/dist/mcp/server.js +2 -0
  99. package/dist/mcp/tools-export.js +3 -3
  100. package/dist/mcp/tools-query.js +17 -11
  101. package/dist/mcp/tools-template.js +1 -1
  102. package/dist/parser/tag-registry.d.ts +47 -0
  103. package/dist/parser/tag-registry.js +57 -0
  104. package/dist/parser.d.ts +3 -0
  105. package/dist/parser.js +10 -23
  106. package/dist/validation/rule-registry.d.ts +36 -0
  107. package/dist/validation/rule-registry.js +37 -0
  108. package/dist/validator.js +3 -3
  109. package/docs/reference/concepts.md +2 -1
  110. package/docs/reference/deployment.md +21 -0
  111. package/docs/reference/jsdoc-grammar.md +242 -1
  112. package/docs/reference/scaffold.md +0 -6
  113. package/package.json +9 -1
  114. package/dist/cli/templates/workflows/ai-agent-durable.d.ts +0 -8
  115. package/dist/export/templates.d.ts +0 -24
  116. package/dist/export/templates.js +0 -186
  117. package/dist/validation/cicd-rules.d.ts +0 -62
  118. /package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-docker.js +0 -0
  119. /package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-matrix.js +0 -0
  120. /package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-multi-env.js +0 -0
  121. /package/dist/{cli/templates/workflows → extensions/cicd/templates}/cicd-test-deploy.js +0 -0
@@ -97,4 +97,81 @@ export function getInstalledPackageManifest(projectDir, packageName) {
97
97
  return null;
98
98
  }
99
99
  }
100
+ /**
101
+ * Discover all tag handlers from installed pack manifests.
102
+ */
103
+ export async function discoverTagHandlers(projectDir) {
104
+ const packages = await listInstalledPackages(projectDir);
105
+ const handlers = [];
106
+ for (const pkg of packages) {
107
+ const manifest = pkg.manifest;
108
+ if (!manifest.tagHandlers)
109
+ continue;
110
+ for (const handler of manifest.tagHandlers) {
111
+ handlers.push({
112
+ ...handler,
113
+ absoluteFile: path.join(pkg.path, handler.file),
114
+ packageName: pkg.name,
115
+ });
116
+ }
117
+ }
118
+ return handlers;
119
+ }
120
+ /**
121
+ * Discover all validation rule sets from installed pack manifests.
122
+ */
123
+ export async function discoverValidationRuleSets(projectDir) {
124
+ const packages = await listInstalledPackages(projectDir);
125
+ const ruleSets = [];
126
+ for (const pkg of packages) {
127
+ const manifest = pkg.manifest;
128
+ if (!manifest.validationRuleSets)
129
+ continue;
130
+ for (const ruleSet of manifest.validationRuleSets) {
131
+ ruleSets.push({
132
+ ...ruleSet,
133
+ absoluteFile: path.join(pkg.path, ruleSet.file),
134
+ packageName: pkg.name,
135
+ });
136
+ }
137
+ }
138
+ return ruleSets;
139
+ }
140
+ /**
141
+ * Discover all doc topics from installed pack manifests.
142
+ */
143
+ export async function discoverDocTopics(projectDir) {
144
+ const packages = await listInstalledPackages(projectDir);
145
+ const topics = [];
146
+ for (const pkg of packages) {
147
+ const manifest = pkg.manifest;
148
+ if (!manifest.docs)
149
+ continue;
150
+ for (const doc of manifest.docs) {
151
+ topics.push({
152
+ ...doc,
153
+ absoluteFile: path.join(pkg.path, doc.file),
154
+ packageName: pkg.name,
155
+ });
156
+ }
157
+ }
158
+ return topics;
159
+ }
160
+ /**
161
+ * Discover all init contributions from installed pack manifests.
162
+ */
163
+ export async function discoverInitContributions(projectDir) {
164
+ const packages = await listInstalledPackages(projectDir);
165
+ const contributions = [];
166
+ for (const pkg of packages) {
167
+ const manifest = pkg.manifest;
168
+ if (!manifest.initContributions)
169
+ continue;
170
+ contributions.push({
171
+ ...manifest.initContributions,
172
+ packageName: pkg.name,
173
+ });
174
+ }
175
+ return contributions;
176
+ }
100
177
  //# sourceMappingURL=registry.js.map
@@ -8,8 +8,8 @@
8
8
  import type { TDataType } from '../ast/types.js';
9
9
  /** Auto-generated manifest describing a marketplace package's contents. */
10
10
  export type TMarketplaceManifest = {
11
- /** Manifest schema version (currently 1) */
12
- manifestVersion: 1;
11
+ /** Manifest schema version */
12
+ manifestVersion: 1 | 2;
13
13
  /** npm package name */
14
14
  name: string;
15
15
  /** Semver version */
@@ -28,6 +28,14 @@ export type TMarketplaceManifest = {
28
28
  patterns: TManifestPattern[];
29
29
  /** Export targets provided by this package */
30
30
  exportTargets?: TManifestExportTarget[];
31
+ /** Tag handlers contributed by this pack (v2) */
32
+ tagHandlers?: TManifestTagHandler[];
33
+ /** Validation rule sets contributed by this pack (v2) */
34
+ validationRuleSets?: TManifestValidationRuleSet[];
35
+ /** Documentation topics contributed by this pack (v2) */
36
+ docs?: TManifestDocTopic[];
37
+ /** Init contributions: use cases and templates (v2) */
38
+ initContributions?: TManifestInitContribution;
31
39
  /** External dependency information */
32
40
  dependencies?: {
33
41
  /** Flow Weaver peer dependency constraints */
@@ -38,7 +46,7 @@ export type TMarketplaceManifest = {
38
46
  };
39
47
  /** An export target provided by a marketplace package. */
40
48
  export type TManifestExportTarget = {
41
- /** Target identifier (e.g. "lambda", "azure-pipelines") */
49
+ /** Target identifier */
42
50
  name: string;
43
51
  /** Human-readable description */
44
52
  description?: string;
@@ -152,6 +160,71 @@ export type TInstalledPackage = {
152
160
  /** Absolute path to the package in node_modules */
153
161
  path: string;
154
162
  };
163
+ /**
164
+ * A tag handler contributed by a pack. Declares which JSDoc tags the pack handles,
165
+ * the deploy namespace to store results in, and the JS file exporting the handler.
166
+ */
167
+ export type TManifestTagHandler = {
168
+ /** Tag name(s) this handler processes (e.g., "secret", "runner") */
169
+ tags: string[];
170
+ /** Deploy namespace for storing parsed data (e.g., "cicd") */
171
+ namespace: string;
172
+ /** Applicable scope: workflow-level tags, nodeType-level tags, or both */
173
+ scope: 'workflow' | 'nodeType' | 'both';
174
+ /** Relative path to the compiled JS file exporting the handler function */
175
+ file: string;
176
+ /** Named export from the file (default: "default") */
177
+ exportName?: string;
178
+ };
179
+ /**
180
+ * A validation rule set contributed by a pack. Declares a detect function
181
+ * and rules export for conditional validation.
182
+ */
183
+ export type TManifestValidationRuleSet = {
184
+ /** Human-readable name for this rule set */
185
+ name: string;
186
+ /** Deploy namespace this rule set applies to (e.g., "cicd") */
187
+ namespace: string;
188
+ /** Relative path to the compiled JS file exporting detect and getRules */
189
+ file: string;
190
+ /** Named export for the detect function (default: "detect") */
191
+ detectExport?: string;
192
+ /** Named export for the getRules function (default: "getRules") */
193
+ rulesExport?: string;
194
+ };
195
+ /**
196
+ * A documentation topic contributed by a pack.
197
+ */
198
+ export type TManifestDocTopic = {
199
+ /** Topic slug (used in fw_docs read) */
200
+ slug: string;
201
+ /** Human-readable topic name */
202
+ name: string;
203
+ /** Topic description */
204
+ description?: string;
205
+ /** Search keywords */
206
+ keywords?: string[];
207
+ /** Context presets this topic should be included in */
208
+ presets?: string[];
209
+ /** Relative path to the markdown file */
210
+ file: string;
211
+ };
212
+ /**
213
+ * Init contributions from a pack: use case entries and template IDs.
214
+ */
215
+ export type TManifestInitContribution = {
216
+ /** Use case entry for fw init prompts */
217
+ useCase?: {
218
+ /** Use case ID (must be unique across all packs) */
219
+ id: string;
220
+ /** Display name */
221
+ name: string;
222
+ /** Brief description */
223
+ description: string;
224
+ };
225
+ /** Template IDs this pack provides (must match IDs in the template registry) */
226
+ templates?: string[];
227
+ };
155
228
  export type TMarketInitConfig = {
156
229
  /** Package name (e.g., flowweaver-pack-openai) */
157
230
  name: string;
@@ -1,3 +1,4 @@
1
+ import '../extensions/index.js';
1
2
  import type { McpServerOptions } from './types.js';
2
3
  export declare function startMcpServer(options: McpServerOptions): Promise<void>;
3
4
  export declare function mcpServerCommand(options: McpServerOptions): Promise<void>;
@@ -1,3 +1,5 @@
1
+ // Load built-in extensions (CI/CD, etc.) before tool registration
2
+ import '../extensions/index.js';
1
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
5
  import { DEFAULT_SERVER_URL } from '../defaults.js';
@@ -11,11 +11,11 @@ import { parseWorkflow } from '../api/index.js';
11
11
  import { createTargetRegistry } from '../deployment/index.js';
12
12
  import { makeToolResult, makeErrorResult } from './response-utils.js';
13
13
  export function registerExportTools(mcp) {
14
- mcp.tool('fw_export', 'Export workflows as serverless deployments. Generates handler code, platform config, and deploy instructions.', {
14
+ mcp.tool('fw_export', 'Export workflows as serverless deployments. Generates platform-native config files and deploy instructions. Available targets depend on installed packs.', {
15
15
  filePath: z.string().describe('Path to the workflow .ts file'),
16
16
  target: z
17
17
  .string()
18
- .describe('Deployment target platform (e.g. lambda, vercel, cloudflare, inngest, github-actions, gitlab-ci). Run with --list-targets to see installed targets.'),
18
+ .describe('Deployment target platform. Run with --list-targets to see installed targets.'),
19
19
  outputDir: z.string().describe('Output directory for generated files'),
20
20
  serviceName: z
21
21
  .string()
@@ -40,7 +40,7 @@ export function registerExportTools(mcp) {
40
40
  durableSteps: z
41
41
  .boolean()
42
42
  .optional()
43
- .describe('Use deep generator with per-node Inngest steps for durability (inngest target only)'),
43
+ .describe('Use deep generator with per-node durable steps'),
44
44
  }, async (args) => {
45
45
  try {
46
46
  const filePath = path.resolve(args.filePath);
@@ -8,7 +8,7 @@ import { WorkflowDiffer } from '../diff/WorkflowDiffer.js';
8
8
  import { formatDiff } from '../diff/formatDiff.js';
9
9
  import { makeToolResult, makeErrorResult, addHintsToItems } from './response-utils.js';
10
10
  import { getFriendlyError } from '../friendly-errors.js';
11
- import { generateInngestFunction } from '../generator/inngest.js';
11
+ import { compileTargetRegistry } from '../generator/compile-target-registry.js';
12
12
  import { AnnotationParser } from '../parser.js';
13
13
  /** Detect MULTIPLE_WORKFLOWS_FOUND marker in parse errors and return the right error code */
14
14
  function parseErrorCode(errors) {
@@ -130,7 +130,7 @@ export function registerQueryTools(mcp) {
130
130
  });
131
131
  mcp.tool('fw_compile', 'Compile a workflow to executable code. Only regenerates code inside @flow-weaver-runtime ' +
132
132
  'and @flow-weaver-body marker sections — user code outside markers is preserved. ' +
133
- 'Set production: true to strip debug instrumentation. Use target=inngest for per-node step.run() durability.', {
133
+ 'Set production: true to strip debug instrumentation. Custom targets are available via registered extensions.', {
134
134
  filePath: z.string().describe('Path to the workflow file'),
135
135
  write: z.boolean().optional().describe('Whether to write the output file (default: true)'),
136
136
  production: z
@@ -139,9 +139,9 @@ export function registerQueryTools(mcp) {
139
139
  .describe('Production mode — no debug events (default: false)'),
140
140
  workflowName: z.string().optional().describe('Specific workflow name'),
141
141
  target: z
142
- .enum(['typescript', 'inngest'])
142
+ .string()
143
143
  .optional()
144
- .describe('Compilation target: typescript (default) or inngest (per-node step.run)'),
144
+ .describe('Compilation target: typescript (default) or a registered extension target'),
145
145
  cron: z.string().optional().describe('Cron schedule expression (e.g. "0 9 * * *"). Overrides @trigger annotation.'),
146
146
  serve: z.boolean().optional().describe('Generate serve() handler for HTTP framework integration'),
147
147
  framework: z.enum(['next', 'express', 'hono', 'fastify', 'remix']).optional().describe('Framework adapter for serve handler (requires serve=true)'),
@@ -151,10 +151,16 @@ export function registerQueryTools(mcp) {
151
151
  }, async (args) => {
152
152
  try {
153
153
  const filePath = path.resolve(args.filePath);
154
- if (args.target === 'inngest') {
155
- // Use deep Inngest generator
156
- const parser = new AnnotationParser();
157
- const parseResult = parser.parse(filePath);
154
+ const customTarget = args.target && args.target !== 'typescript'
155
+ ? compileTargetRegistry.get(args.target)
156
+ : undefined;
157
+ if (args.target && args.target !== 'typescript') {
158
+ if (!customTarget) {
159
+ const available = compileTargetRegistry.getNames();
160
+ return makeErrorResult('COMPILE_ERROR', `Unknown compile target: ${args.target}. Available: typescript${available.length ? ', ' + available.join(', ') : ''}`);
161
+ }
162
+ const annotationParser = new AnnotationParser();
163
+ const parseResult = annotationParser.parse(filePath);
158
164
  if (parseResult.errors.length > 0) {
159
165
  return makeErrorResult('PARSE_ERROR', `Parse errors:\n${parseResult.errors.join('\n')}`);
160
166
  }
@@ -182,19 +188,19 @@ export function registerQueryTools(mcp) {
182
188
  workflow.options = workflow.options || {};
183
189
  workflow.options.timeout = args.timeout;
184
190
  }
185
- const code = generateInngestFunction(workflow, allNodeTypes, {
191
+ const code = customTarget.compile(workflow, allNodeTypes, {
186
192
  production: args.production ?? false,
187
193
  typedEvents: args.typedEvents,
188
194
  serveHandler: args.serve,
189
195
  framework: args.framework,
190
196
  });
191
- const outputFile = filePath.replace(/\.ts$/, '.inngest.ts');
197
+ const outputFile = filePath.replace(/\.ts$/, `.${args.target}.ts`);
192
198
  if (args.write !== false) {
193
199
  const fs = await import('fs');
194
200
  fs.writeFileSync(outputFile, code, 'utf8');
195
201
  }
196
202
  return makeToolResult({
197
- target: 'inngest',
203
+ target: args.target,
198
204
  outputFile,
199
205
  workflowName: workflow.name,
200
206
  code: args.write === false ? code : undefined,
@@ -38,7 +38,7 @@ export function registerTemplateTools(mcp) {
38
38
  return makeToolResult(result);
39
39
  });
40
40
  mcp.tool('fw_scaffold', 'Create a workflow or node from a template.', {
41
- template: z.string().describe('Template name (e.g. "sequential", "validator")'),
41
+ template: z.string().describe('Template name (e.g. "sequential", "validator", "ai-agent")'),
42
42
  filePath: z.string().describe('Output file path'),
43
43
  name: z.string().optional().describe('Workflow/node function name'),
44
44
  preview: z
@@ -0,0 +1,47 @@
1
+ /**
2
+ * TagHandlerRegistry: dispatch table for pack-contributed JSDoc tag handlers.
3
+ *
4
+ * Packs declare tag handlers in their manifest. The parser populates this
5
+ * registry at startup and delegates unknown tags to it before emitting
6
+ * "unknown tag" warnings.
7
+ */
8
+ export type TTagHandlerContext = {
9
+ /** The deploy map for this handler's namespace. Handlers mutate this directly. */
10
+ deploy: Record<string, unknown>;
11
+ /** Accumulator for parser warnings. */
12
+ warnings: string[];
13
+ };
14
+ /**
15
+ * A handler function for a pack-contributed tag.
16
+ * Receives the tag name, the comment text after the tag, and a context
17
+ * object with the deploy map for the handler's namespace.
18
+ */
19
+ export type TTagHandlerFn = (tagName: string, comment: string, ctx: TTagHandlerContext) => void;
20
+ export type TRegisteredTagHandler = {
21
+ namespace: string;
22
+ scope: 'workflow' | 'nodeType' | 'both';
23
+ handler: TTagHandlerFn;
24
+ };
25
+ /**
26
+ * Registry mapping tag names to pack-provided handler functions.
27
+ * Used by the JSDoc parser to delegate tags it doesn't natively handle.
28
+ */
29
+ export declare class TagHandlerRegistry {
30
+ private handlers;
31
+ /** Register a handler for one or more tag names. */
32
+ register(tags: string[], namespace: string, scope: 'workflow' | 'nodeType' | 'both', handler: TTagHandlerFn): void;
33
+ /** Check if a handler is registered for the given tag. */
34
+ has(tagName: string): boolean;
35
+ /** Get all registered tag names. */
36
+ getRegisteredTags(): string[];
37
+ /**
38
+ * Handle a tag by delegating to the registered handler.
39
+ * The handler writes parsed data into `deployMap[namespace]`.
40
+ *
41
+ * @returns true if the tag was handled, false if no handler was found.
42
+ */
43
+ handle(tagName: string, comment: string, blockScope: 'workflow' | 'nodeType', deployMap: Record<string, Record<string, unknown>>, warnings: string[]): boolean;
44
+ }
45
+ /** Global tag handler registry singleton. Extensions register handlers here at startup. */
46
+ export declare const tagHandlerRegistry: TagHandlerRegistry;
47
+ //# sourceMappingURL=tag-registry.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TagHandlerRegistry: dispatch table for pack-contributed JSDoc tag handlers.
3
+ *
4
+ * Packs declare tag handlers in their manifest. The parser populates this
5
+ * registry at startup and delegates unknown tags to it before emitting
6
+ * "unknown tag" warnings.
7
+ */
8
+ /**
9
+ * Registry mapping tag names to pack-provided handler functions.
10
+ * Used by the JSDoc parser to delegate tags it doesn't natively handle.
11
+ */
12
+ export class TagHandlerRegistry {
13
+ handlers = new Map();
14
+ /** Register a handler for one or more tag names. */
15
+ register(tags, namespace, scope, handler) {
16
+ const entry = { namespace, scope, handler };
17
+ for (const tag of tags) {
18
+ this.handlers.set(tag, entry);
19
+ }
20
+ }
21
+ /** Check if a handler is registered for the given tag. */
22
+ has(tagName) {
23
+ return this.handlers.has(tagName);
24
+ }
25
+ /** Get all registered tag names. */
26
+ getRegisteredTags() {
27
+ return [...this.handlers.keys()];
28
+ }
29
+ /**
30
+ * Handle a tag by delegating to the registered handler.
31
+ * The handler writes parsed data into `deployMap[namespace]`.
32
+ *
33
+ * @returns true if the tag was handled, false if no handler was found.
34
+ */
35
+ handle(tagName, comment, blockScope, deployMap, warnings) {
36
+ const entry = this.handlers.get(tagName);
37
+ if (!entry)
38
+ return false;
39
+ // Check scope compatibility
40
+ if (entry.scope !== 'both' && entry.scope !== blockScope) {
41
+ warnings.push(`@${tagName} is registered for ${entry.scope} blocks, not ${blockScope} blocks.`);
42
+ return true; // consumed, but with a warning
43
+ }
44
+ // Ensure the namespace slot exists
45
+ if (!deployMap[entry.namespace]) {
46
+ deployMap[entry.namespace] = {};
47
+ }
48
+ entry.handler(tagName, comment, {
49
+ deploy: deployMap[entry.namespace],
50
+ warnings,
51
+ });
52
+ return true;
53
+ }
54
+ }
55
+ /** Global tag handler registry singleton. Extensions register handlers here at startup. */
56
+ export const tagHandlerRegistry = new TagHandlerRegistry();
57
+ //# sourceMappingURL=tag-registry.js.map
package/dist/parser.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Project } from 'ts-morph';
2
2
  import type { TNodeTypeAST, TWorkflowAST, TPatternAST } from './ast/types.js';
3
+ import { type TagHandlerRegistry } from './parser/tag-registry.js';
3
4
  export interface ParseResult {
4
5
  workflows: TWorkflowAST[];
5
6
  nodeTypes: TNodeTypeAST[];
@@ -29,6 +30,8 @@ export declare class AnnotationParser {
29
30
  private importCache;
30
31
  private importStack;
31
32
  private parseCache;
33
+ /** Tag handler registry. Defaults to the global singleton (pre-populated by extensions). */
34
+ tagRegistry: TagHandlerRegistry;
32
35
  constructor();
33
36
  private computeHash;
34
37
  private detectMinorEdit;
package/dist/parser.js CHANGED
@@ -14,6 +14,7 @@ import { getPackageExports } from './npm-packages.js';
14
14
  import { getSharedProject } from './shared-project.js';
15
15
  import { LRUCache } from './utils/lru-cache.js';
16
16
  import { COERCION_NODE_TYPES, COERCE_TYPE_MAP } from './built-in-nodes/coercion-types.js';
17
+ import { tagHandlerRegistry } from './parser/tag-registry.js';
17
18
  /**
18
19
  * Convert a TExternalNodeType to a TNodeTypeAST with sensible defaults.
19
20
  * Used to merge runtime-loaded node types into the parser's available types.
@@ -73,6 +74,8 @@ export class AnnotationParser {
73
74
  importCache = new LRUCache(200);
74
75
  importStack = new Set();
75
76
  parseCache = new LRUCache(100);
77
+ /** Tag handler registry. Defaults to the global singleton (pre-populated by extensions). */
78
+ tagRegistry = tagHandlerRegistry;
76
79
  constructor() {
77
80
  this.project = getSharedProject();
78
81
  }
@@ -591,7 +594,7 @@ export class AnnotationParser {
591
594
  const nodeTypes = [];
592
595
  extractFunctionLikes(sourceFile).forEach((fn) => {
593
596
  // Parse JSDoc comments
594
- const config = jsdocParser.parseNodeType(fn, warnings);
597
+ const config = jsdocParser.parseNodeType(fn, warnings, this.tagRegistry);
595
598
  if (!config) {
596
599
  const jsdocText = fn.getJsDocs().map((d) => d.getFullText()).join('');
597
600
  if (jsdocText.includes('@flowWeaver nodeType')) {
@@ -765,7 +768,7 @@ export class AnnotationParser {
765
768
  extractWorkflowSignatures(sourceFile, filePath, warnings) {
766
769
  const workflows = [];
767
770
  extractFunctionLikes(sourceFile).forEach((fn) => {
768
- const config = jsdocParser.parseWorkflow(fn, warnings);
771
+ const config = jsdocParser.parseWorkflow(fn, warnings, this.tagRegistry);
769
772
  if (!config)
770
773
  return;
771
774
  const functionName = fn.getName() || 'anonymous';
@@ -823,7 +826,7 @@ export class AnnotationParser {
823
826
  .filter((name) => !!name);
824
827
  allFunctions.forEach((fn) => {
825
828
  // Parse JSDoc comments
826
- const config = jsdocParser.parseWorkflow(fn, warnings);
829
+ const config = jsdocParser.parseWorkflow(fn, warnings, this.tagRegistry);
827
830
  if (!config) {
828
831
  const jsdocText = fn.getJsDocs().map((d) => d.getFullText()).join('');
829
832
  if (jsdocText.includes('@flowWeaver workflow')) {
@@ -990,11 +993,7 @@ export class AnnotationParser {
990
993
  ...(Object.keys(ui).length > 0 && { ui }),
991
994
  ...((config.strictTypes !== undefined || config.autoConnect ||
992
995
  config.trigger || config.cancelOn || config.retries !== undefined ||
993
- config.timeout || config.throttle ||
994
- config.secrets || config.runner || config.caches || config.artifacts ||
995
- config.environments || config.matrix || config.services ||
996
- config.concurrency || config.cicdTriggers ||
997
- config.deploy) && {
996
+ config.timeout || config.throttle || config.deploy) && {
998
997
  options: {
999
998
  ...(config.strictTypes !== undefined && { strictTypes: config.strictTypes }),
1000
999
  ...(config.autoConnect && { autoConnect: true }),
@@ -1003,21 +1002,9 @@ export class AnnotationParser {
1003
1002
  ...(config.retries !== undefined && { retries: config.retries }),
1004
1003
  ...(config.timeout && { timeout: config.timeout }),
1005
1004
  ...(config.throttle && { throttle: config.throttle }),
1006
- // CI/CD domain options (grouped)
1007
- ...((config.secrets || config.runner || config.caches || config.artifacts ||
1008
- config.environments || config.matrix || config.services ||
1009
- config.concurrency || config.cicdTriggers) && {
1010
- cicd: {
1011
- ...(config.secrets && { secrets: config.secrets }),
1012
- ...(config.runner && { runner: config.runner }),
1013
- ...(config.caches && { caches: config.caches }),
1014
- ...(config.artifacts && { artifacts: config.artifacts }),
1015
- ...(config.environments && { environments: config.environments }),
1016
- ...(config.matrix && { matrix: config.matrix }),
1017
- ...(config.services && { services: config.services }),
1018
- ...(config.concurrency && { concurrency: config.concurrency }),
1019
- ...(config.cicdTriggers && { triggers: config.cicdTriggers }),
1020
- },
1005
+ // CI/CD data comes from deploy['cicd'] (populated by extension tag handler)
1006
+ ...(config.deploy?.['cicd'] && {
1007
+ cicd: config.deploy['cicd'],
1021
1008
  }),
1022
1009
  // Per-target deployment config
1023
1010
  ...(config.deploy && { deploy: config.deploy }),
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ValidationRuleRegistry: dynamic dispatch for pack-contributed validation rules.
3
+ *
4
+ * Replaces the hardcoded `isCICDWorkflow() ? getCICDValidationRules() : []`
5
+ * pattern. Each registered rule set has a detect predicate and a lazy rule loader.
6
+ * The validate API calls `getApplicableRules(ast)` and merges results.
7
+ */
8
+ import type { TValidationRule, TWorkflowAST } from '../ast/types.js';
9
+ export type TValidationRuleSet = {
10
+ /** Human-readable name for this rule set */
11
+ name: string;
12
+ /** Deploy namespace this rule set applies to */
13
+ namespace: string;
14
+ /** Predicate: should these rules run for this workflow? */
15
+ detect: (ast: TWorkflowAST) => boolean;
16
+ /** Lazy loader for the actual rules. Called only when detect returns true. */
17
+ getRules: () => TValidationRule[];
18
+ };
19
+ /**
20
+ * Registry for dynamically contributed validation rule sets.
21
+ * Core rule sets (like CI/CD) register themselves at startup.
22
+ * Pack-contributed rule sets are loaded from manifests.
23
+ */
24
+ export declare class ValidationRuleRegistry {
25
+ private ruleSets;
26
+ /** Register a validation rule set. */
27
+ register(ruleSet: TValidationRuleSet): void;
28
+ /**
29
+ * Get all validation rules applicable to the given workflow AST.
30
+ * Runs each registered detect predicate and collects rules from matching sets.
31
+ */
32
+ getApplicableRules(ast: TWorkflowAST): TValidationRule[];
33
+ /** Get the number of registered rule sets. */
34
+ get size(): number;
35
+ }
36
+ //# sourceMappingURL=rule-registry.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ValidationRuleRegistry: dynamic dispatch for pack-contributed validation rules.
3
+ *
4
+ * Replaces the hardcoded `isCICDWorkflow() ? getCICDValidationRules() : []`
5
+ * pattern. Each registered rule set has a detect predicate and a lazy rule loader.
6
+ * The validate API calls `getApplicableRules(ast)` and merges results.
7
+ */
8
+ /**
9
+ * Registry for dynamically contributed validation rule sets.
10
+ * Core rule sets (like CI/CD) register themselves at startup.
11
+ * Pack-contributed rule sets are loaded from manifests.
12
+ */
13
+ export class ValidationRuleRegistry {
14
+ ruleSets = [];
15
+ /** Register a validation rule set. */
16
+ register(ruleSet) {
17
+ this.ruleSets.push(ruleSet);
18
+ }
19
+ /**
20
+ * Get all validation rules applicable to the given workflow AST.
21
+ * Runs each registered detect predicate and collects rules from matching sets.
22
+ */
23
+ getApplicableRules(ast) {
24
+ const rules = [];
25
+ for (const ruleSet of this.ruleSets) {
26
+ if (ruleSet.detect(ast)) {
27
+ rules.push(...ruleSet.getRules());
28
+ }
29
+ }
30
+ return rules;
31
+ }
32
+ /** Get the number of registered rule sets. */
33
+ get size() {
34
+ return this.ruleSets.length;
35
+ }
36
+ }
37
+ //# sourceMappingURL=rule-registry.js.map
package/dist/validator.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RESERVED_NODE_NAMES, isStartNode, isExitNode, isExecutePort, isReservedNodeName, VALID_NODE_COLORS, EXECUTION_STRATEGIES, } from './constants.js';
1
+ import { RESERVED_NODE_NAMES, isStartNode, isExitNode, isPseudoNode, isExecutePort, isReservedNodeName, VALID_NODE_COLORS, EXECUTION_STRATEGIES, } from './constants.js';
2
2
  import { findClosestMatches } from './utils/string-distance.js';
3
3
  import { parseFunctionSignature } from './jsdoc-port-sync/signature-parser.js';
4
4
  import { checkTypeCompatibilityFromStrings } from './type-checker.js';
@@ -316,7 +316,7 @@ export class WorkflowValidator {
316
316
  const fromNode = conn.from.node;
317
317
  const fromPort = conn.from.port;
318
318
  const connLocation = this.getConnectionLocation(conn);
319
- if (!isStartNode(fromNode) && !instanceMap.has(fromNode)) {
319
+ if (!isStartNode(fromNode) && !isPseudoNode(fromNode) && !instanceMap.has(fromNode)) {
320
320
  const instanceIds = [...instanceMap.keys()];
321
321
  const suggestions = findClosestMatches(fromNode, instanceIds);
322
322
  const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
@@ -627,7 +627,7 @@ export class WorkflowValidator {
627
627
  workflow.connections.forEach((conn) => {
628
628
  const fromNode = conn.from.node;
629
629
  const toNode = conn.to.node;
630
- if (!isStartNode(fromNode) && !isExitNode(fromNode)) {
630
+ if (!isStartNode(fromNode) && !isExitNode(fromNode) && !isPseudoNode(fromNode)) {
631
631
  referencedNodes.add(fromNode);
632
632
  }
633
633
  if (!isStartNode(toNode) && !isExitNode(toNode)) {
@@ -78,6 +78,7 @@ That is it. Two expression-mode functions, one workflow annotation, zero boilerp
78
78
  | Pull execution, merge strategies | `advanced-annotations` | jsdoc-grammar |
79
79
  | Compile to Inngest | `compilation` | cli-reference |
80
80
  | Deploy to cloud | `deployment` | compilation |
81
+ | Build a CI/CD pipeline | `cicd` | deployment, scaffold |
81
82
  | Use delay/waitForEvent/mocks | `built-in-nodes` | debugging |
82
83
  | Publish marketplace packages | `marketplace` | — |
83
84
 
@@ -98,7 +99,7 @@ flow-weaver watch <file> # Watch mode
98
99
  flow-weaver dev <file> # Watch + compile + run in one command
99
100
  flow-weaver serve [dir] # HTTP server exposing workflows as endpoints
100
101
  flow-weaver diagram <file> # Generate SVG diagram
101
- flow-weaver export <file> # Export as serverless function (lambda/vercel/cloudflare/inngest)
102
+ flow-weaver export <file> # Export as serverless function or CI/CD pipeline
102
103
  flow-weaver docs # Browse documentation
103
104
  flow-weaver docs <topic> # Read a specific topic
104
105
  flow-weaver docs search <q> # Search across all docs