@exaudeus/workrail 1.16.0 → 1.17.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.
Files changed (57) hide show
  1. package/dist/application/services/compiler/feature-registry.js +18 -0
  2. package/dist/application/services/compiler/prompt-blocks.js +2 -1
  3. package/dist/application/services/compiler/ref-registry.js +36 -0
  4. package/dist/application/services/validation-engine.d.ts +1 -0
  5. package/dist/application/services/validation-engine.js +14 -0
  6. package/dist/application/services/workflow-validation-pipeline.d.ts +96 -0
  7. package/dist/application/services/workflow-validation-pipeline.js +94 -0
  8. package/dist/application/use-cases/raw-workflow-file-scanner.d.ts +18 -0
  9. package/dist/application/use-cases/raw-workflow-file-scanner.js +91 -0
  10. package/dist/application/use-cases/validate-workflow-file.d.ts +17 -0
  11. package/dist/application/use-cases/validate-workflow-file.js +96 -0
  12. package/dist/application/use-cases/validate-workflow-json.d.ts +2 -1
  13. package/dist/application/use-cases/validate-workflow-json.js +67 -13
  14. package/dist/application/use-cases/validate-workflow-registry.d.ts +72 -0
  15. package/dist/application/use-cases/validate-workflow-registry.js +215 -0
  16. package/dist/application/validation.d.ts +4 -0
  17. package/dist/application/validation.js +16 -0
  18. package/dist/cli/commands/validate.js +15 -0
  19. package/dist/cli.js +10 -1
  20. package/dist/infrastructure/storage/caching-workflow-storage.d.ts +1 -0
  21. package/dist/infrastructure/storage/caching-workflow-storage.js +3 -0
  22. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.d.ts +2 -1
  23. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +8 -21
  24. package/dist/infrastructure/storage/file-workflow-storage.d.ts +0 -1
  25. package/dist/infrastructure/storage/file-workflow-storage.js +15 -36
  26. package/dist/infrastructure/storage/schema-validating-workflow-storage.d.ts +1 -0
  27. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +16 -6
  28. package/dist/infrastructure/storage/workflow-resolution.d.ts +62 -0
  29. package/dist/infrastructure/storage/workflow-resolution.js +150 -0
  30. package/dist/manifest.json +140 -68
  31. package/dist/mcp/handlers/v2-execution/replay.d.ts +1 -1
  32. package/dist/mcp/handlers/v2-execution/replay.js +37 -21
  33. package/dist/mcp/handlers/v2-execution/start.js +35 -13
  34. package/dist/mcp/handlers/v2-execution-helpers.d.ts +9 -11
  35. package/dist/mcp/handlers/v2-execution-helpers.js +6 -18
  36. package/dist/mcp/output-schemas.d.ts +20 -20
  37. package/dist/mcp/server.d.ts +18 -0
  38. package/dist/mcp/server.js +8 -1
  39. package/dist/mcp/transports/http-entry.d.ts +1 -0
  40. package/dist/mcp/transports/http-entry.js +87 -0
  41. package/dist/mcp/transports/http-listener.d.ts +9 -0
  42. package/dist/mcp/transports/http-listener.js +64 -0
  43. package/dist/mcp/transports/stdio-entry.d.ts +1 -0
  44. package/dist/mcp/transports/stdio-entry.js +92 -0
  45. package/dist/mcp/transports/transport-mode.d.ts +7 -0
  46. package/dist/mcp/transports/transport-mode.js +18 -0
  47. package/dist/mcp-server.js +21 -5
  48. package/dist/types/storage.d.ts +1 -0
  49. package/dist/v2/durable-core/domain/prompt-renderer.js +13 -7
  50. package/dist/v2/durable-core/domain/start-construction.d.ts +22 -0
  51. package/dist/v2/durable-core/domain/start-construction.js +31 -0
  52. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
  53. package/dist/v2/read-only/v1-to-v2-shim.d.ts +5 -0
  54. package/dist/v2/read-only/v1-to-v2-shim.js +18 -0
  55. package/package.json +3 -2
  56. package/workflows/bug-investigation.agentic.v2.json +134 -0
  57. package/workflows/mr-review-workflow.agentic.v2.json +238 -0
@@ -0,0 +1,9 @@
1
+ import { type Application } from 'express';
2
+ export interface HttpListener {
3
+ readonly app: Application;
4
+ readonly requestedPort: number;
5
+ getBoundPort(): number | null;
6
+ start(): Promise<void>;
7
+ stop(): Promise<void>;
8
+ }
9
+ export declare function createHttpListener(requestedPort: number): HttpListener;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createHttpListener = createHttpListener;
7
+ const express_1 = __importDefault(require("express"));
8
+ const http_1 = require("http");
9
+ function createHttpListener(requestedPort) {
10
+ const app = (0, express_1.default)();
11
+ let state = { kind: 'not_started' };
12
+ return {
13
+ app,
14
+ requestedPort,
15
+ getBoundPort() {
16
+ return state.kind === 'running' ? state.boundPort : null;
17
+ },
18
+ async start() {
19
+ if (state.kind === 'running') {
20
+ throw new Error('[HttpListener] Already started');
21
+ }
22
+ if (state.kind === 'stopped') {
23
+ throw new Error('[HttpListener] Cannot restart a stopped listener');
24
+ }
25
+ return new Promise((resolve, reject) => {
26
+ const server = (0, http_1.createServer)(app);
27
+ server.on('error', (err) => {
28
+ if (err.code === 'EADDRINUSE') {
29
+ reject(new Error(`[HttpListener] Port ${requestedPort} is already in use. ` +
30
+ `Set WORKRAIL_HTTP_PORT to a different port or stop the conflicting process.`));
31
+ }
32
+ else {
33
+ reject(err);
34
+ }
35
+ });
36
+ server.listen(requestedPort, () => {
37
+ const addr = server.address();
38
+ const boundPort = addr && typeof addr === 'object' ? addr.port : requestedPort;
39
+ state = { kind: 'running', server, boundPort };
40
+ console.error(`[HttpListener] MCP HTTP transport listening on port ${boundPort}`);
41
+ resolve();
42
+ });
43
+ });
44
+ },
45
+ async stop() {
46
+ if (state.kind !== 'running') {
47
+ return;
48
+ }
49
+ const serverToClose = state.server;
50
+ return new Promise((resolve, reject) => {
51
+ serverToClose.close((err) => {
52
+ if (err) {
53
+ reject(err);
54
+ }
55
+ else {
56
+ state = { kind: 'stopped' };
57
+ console.error(`[HttpListener] MCP HTTP transport stopped`);
58
+ resolve();
59
+ }
60
+ });
61
+ });
62
+ },
63
+ };
64
+ }
@@ -0,0 +1 @@
1
+ export declare function startStdioServer(): Promise<void>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.startStdioServer = startStdioServer;
37
+ const server_js_1 = require("../server.js");
38
+ const container_js_1 = require("../../di/container.js");
39
+ const tokens_js_1 = require("../../di/tokens.js");
40
+ async function startStdioServer() {
41
+ const { server, ctx, rootsManager } = await (0, server_js_1.composeServer)();
42
+ const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
43
+ const { RootsListChangedNotificationSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
44
+ server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
45
+ try {
46
+ const result = await server.listRoots();
47
+ rootsManager.updateRootUris(result.roots.map((r) => r.uri));
48
+ console.error(`[Roots] Updated workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}`);
49
+ }
50
+ catch {
51
+ console.error('[Roots] Failed to fetch updated roots after change notification');
52
+ }
53
+ });
54
+ const transport = new StdioServerTransport();
55
+ await server.connect(transport);
56
+ try {
57
+ const result = await server.listRoots();
58
+ rootsManager.updateRootUris(result.roots.map((r) => r.uri));
59
+ console.error(`[Roots] Initial workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}`);
60
+ }
61
+ catch {
62
+ console.error('[Roots] Client does not support roots/list; workspace context will use server CWD fallback');
63
+ }
64
+ console.error('[Transport] WorkRail MCP Server running on stdio');
65
+ const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
66
+ const processSignals = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessSignals);
67
+ const terminator = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessTerminator);
68
+ processSignals.on('SIGINT', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGINT' }));
69
+ processSignals.on('SIGTERM', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGTERM' }));
70
+ processSignals.on('SIGHUP', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' }));
71
+ process.stdin.on('end', () => {
72
+ console.error('[MCP] stdin closed, initiating shutdown');
73
+ shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' });
74
+ });
75
+ let shutdownStarted = false;
76
+ shutdownEvents.onShutdown((event) => {
77
+ if (shutdownStarted)
78
+ return;
79
+ shutdownStarted = true;
80
+ void (async () => {
81
+ try {
82
+ console.error(`[Shutdown] Requested by ${event.signal}. Stopping services...`);
83
+ await ctx.httpServer?.stop();
84
+ terminator.terminate({ kind: 'success' });
85
+ }
86
+ catch (err) {
87
+ console.error('[Shutdown] Error while stopping services:', err);
88
+ terminator.terminate({ kind: 'failure' });
89
+ }
90
+ })();
91
+ });
92
+ }
@@ -0,0 +1,7 @@
1
+ export type TransportMode = {
2
+ readonly kind: 'stdio';
3
+ } | {
4
+ readonly kind: 'http';
5
+ readonly port: number;
6
+ };
7
+ export declare function resolveTransportMode(env: NodeJS.ProcessEnv): TransportMode;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTransportMode = resolveTransportMode;
4
+ function resolveTransportMode(env) {
5
+ const transport = env.WORKRAIL_TRANSPORT ?? 'stdio';
6
+ if (transport === 'http') {
7
+ const portStr = env.WORKRAIL_HTTP_PORT ?? '3100';
8
+ const port = parseInt(portStr, 10);
9
+ if (isNaN(port) || port <= 0 || port > 65535) {
10
+ throw new Error(`Invalid WORKRAIL_HTTP_PORT: "${portStr}". Must be a number between 1-65535.`);
11
+ }
12
+ return { kind: 'http', port };
13
+ }
14
+ if (transport !== 'stdio') {
15
+ throw new Error(`Invalid WORKRAIL_TRANSPORT: "${transport}". Must be 'stdio' or 'http'.`);
16
+ }
17
+ return { kind: 'stdio' };
18
+ }
@@ -2,10 +2,26 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.startServer = void 0;
5
+ const transport_mode_js_1 = require("./mcp/transports/transport-mode.js");
6
+ const stdio_entry_js_1 = require("./mcp/transports/stdio-entry.js");
7
+ const http_entry_js_1 = require("./mcp/transports/http-entry.js");
8
+ const assert_never_js_1 = require("./runtime/assert-never.js");
5
9
  var server_js_1 = require("./mcp/server.js");
6
10
  Object.defineProperty(exports, "startServer", { enumerable: true, get: function () { return server_js_1.startServer; } });
7
- const server_js_2 = require("./mcp/server.js");
8
- (0, server_js_2.startServer)().catch((error) => {
9
- console.error('Fatal error running server:', error);
10
- process.exit(1);
11
- });
11
+ const mode = (0, transport_mode_js_1.resolveTransportMode)(process.env);
12
+ switch (mode.kind) {
13
+ case 'stdio':
14
+ (0, stdio_entry_js_1.startStdioServer)().catch((error) => {
15
+ console.error('[stdio] Fatal error:', error);
16
+ process.exit(1);
17
+ });
18
+ break;
19
+ case 'http':
20
+ (0, http_entry_js_1.startHttpServer)(mode.port).catch((error) => {
21
+ console.error('[http] Fatal error:', error);
22
+ process.exit(1);
23
+ });
24
+ break;
25
+ default:
26
+ (0, assert_never_js_1.assertNever)(mode);
27
+ }
@@ -12,6 +12,7 @@ export interface IWorkflowStorage extends IWorkflowReader {
12
12
  export interface ICompositeWorkflowStorage extends IWorkflowReader {
13
13
  readonly kind: 'composite';
14
14
  getSources(): readonly WorkflowSource[];
15
+ getStorageInstances(): readonly IWorkflowStorage[];
15
16
  }
16
17
  export type AnyWorkflowStorage = IWorkflowStorage | ICompositeWorkflowStorage;
17
18
  export declare function isSingleSourceStorage(storage: AnyWorkflowStorage): storage is IWorkflowStorage;
@@ -170,16 +170,22 @@ function loadRecoveryProjections(args) {
170
170
  }
171
171
  function renderPendingPrompt(args) {
172
172
  const step = (0, workflow_js_1.getStepById)(args.workflow, args.stepId);
173
- const baseTitle = step?.title ?? args.stepId;
174
- const basePrompt = step?.prompt ?? `Pending step: ${args.stepId}`;
175
- const requireConfirmation = Boolean(step?.requireConfirmation);
176
- const functionReferences = step?.functionReferences ?? [];
177
- const outputContract = step && typeof step === 'object' && 'outputContract' in step
173
+ if (!step) {
174
+ return (0, neverthrow_1.err)({
175
+ code: 'RENDER_FAILED',
176
+ message: `Step '${args.stepId}' not found in workflow '${args.workflow.definition.id}'`,
177
+ });
178
+ }
179
+ const baseTitle = step.title;
180
+ const basePrompt = step.prompt;
181
+ const requireConfirmation = Boolean(step.requireConfirmation);
182
+ const functionReferences = step.functionReferences ?? [];
183
+ const outputContract = 'outputContract' in step
178
184
  ? step.outputContract
179
185
  : undefined;
180
186
  const isExitStep = outputContract?.contractRef === index_js_2.LOOP_CONTROL_CONTRACT_REF;
181
187
  const loopBanner = buildLoopContextBanner({ loopPath: args.loopPath, isExitStep });
182
- const validationCriteria = step?.validationCriteria;
188
+ const validationCriteria = step.validationCriteria;
183
189
  const requirements = (0, validation_requirements_extractor_js_1.extractValidationRequirements)(validationCriteria);
184
190
  const requirementsSection = requirements.length > 0
185
191
  ? `\n\n**OUTPUT REQUIREMENTS:**\n${requirements.map(r => `- ${r}`).join('\n')}`
@@ -189,7 +195,7 @@ function renderPendingPrompt(args) {
189
195
  ? `\n\n**OUTPUT REQUIREMENTS (System):**\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
190
196
  : '';
191
197
  const isNotesOptional = outputContract !== undefined ||
192
- (step !== null && step !== undefined && 'notesOptional' in step && step.notesOptional === true);
198
+ ('notesOptional' in step && step.notesOptional === true);
193
199
  const notesSection = isNotesOptional
194
200
  ? ''
195
201
  : '\n\n**NOTES REQUIRED (System):** You must include `output.notesMarkdown` when advancing. ' +
@@ -0,0 +1,22 @@
1
+ import type { Workflow } from '../../../types/workflow.js';
2
+ import type { CompiledWorkflowSnapshotV1 } from '../schemas/compiled-workflow/index.js';
3
+ import { type Result } from 'neverthrow';
4
+ export type StartabilityFailure = {
5
+ readonly reason: 'no_steps';
6
+ readonly detail: 'Workflow has no steps in authored form';
7
+ } | {
8
+ readonly reason: 'first_step_not_in_executable';
9
+ readonly authoredStepId: string;
10
+ readonly detail: string;
11
+ } | {
12
+ readonly reason: 'no_reachable_step';
13
+ readonly detail: 'Interpreter returned isComplete=true with zero completed steps';
14
+ } | {
15
+ readonly reason: 'interpreter_error';
16
+ readonly detail: string;
17
+ };
18
+ export declare function resolveFirstStep(authoredWorkflow: Workflow, pinnedSnapshot: Extract<CompiledWorkflowSnapshotV1, {
19
+ sourceKind: 'v1_pinned';
20
+ }>): Result<{
21
+ readonly id: string;
22
+ }, StartabilityFailure>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveFirstStep = resolveFirstStep;
4
+ const neverthrow_1 = require("neverthrow");
5
+ function resolveFirstStep(authoredWorkflow, pinnedSnapshot) {
6
+ const firstStep = authoredWorkflow.definition.steps[0];
7
+ if (!firstStep) {
8
+ return (0, neverthrow_1.err)({
9
+ reason: 'no_steps',
10
+ detail: 'Workflow has no steps in authored form',
11
+ });
12
+ }
13
+ const firstStepId = firstStep.id;
14
+ const definition = pinnedSnapshot.definition;
15
+ const steps = typeof definition === 'object' &&
16
+ definition !== null &&
17
+ !Array.isArray(definition) &&
18
+ 'steps' in definition &&
19
+ Array.isArray(definition.steps)
20
+ ? definition.steps
21
+ : [];
22
+ const executableStep = steps.find((s) => typeof s === 'object' && s !== null && 'id' in s && s.id === firstStepId);
23
+ if (!executableStep) {
24
+ return (0, neverthrow_1.err)({
25
+ reason: 'first_step_not_in_executable',
26
+ authoredStepId: firstStepId,
27
+ detail: `Step '${firstStepId}' from authored workflow steps[0] not found in executable workflow`,
28
+ });
29
+ }
30
+ return (0, neverthrow_1.ok)({ id: firstStepId });
31
+ }
@@ -12,12 +12,12 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
12
12
  prompt: z.ZodString;
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  stepId: string;
15
- prompt: string;
16
15
  title: string;
16
+ prompt: string;
17
17
  }, {
18
18
  stepId: string;
19
- prompt: string;
20
19
  title: string;
20
+ prompt: string;
21
21
  }>;
22
22
  }, "strip", z.ZodTypeAny, {
23
23
  name: string;
@@ -28,8 +28,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
28
28
  sourceKind: "v1_preview";
29
29
  preview: {
30
30
  stepId: string;
31
- prompt: string;
32
31
  title: string;
32
+ prompt: string;
33
33
  };
34
34
  }, {
35
35
  name: string;
@@ -40,8 +40,8 @@ export declare const CompiledWorkflowSnapshotV1Schema: z.ZodDiscriminatedUnion<"
40
40
  sourceKind: "v1_preview";
41
41
  preview: {
42
42
  stepId: string;
43
- prompt: string;
44
43
  title: string;
44
+ prompt: string;
45
45
  };
46
46
  }>, z.ZodObject<{
47
47
  schemaVersion: z.ZodLiteral<1>;
@@ -82,12 +82,12 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
82
82
  prompt: z.ZodString;
83
83
  }, "strip", z.ZodTypeAny, {
84
84
  stepId: string;
85
- prompt: string;
86
85
  title: string;
86
+ prompt: string;
87
87
  }, {
88
88
  stepId: string;
89
- prompt: string;
90
89
  title: string;
90
+ prompt: string;
91
91
  }>;
92
92
  }, "strip", z.ZodTypeAny, {
93
93
  name: string;
@@ -98,8 +98,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
98
98
  sourceKind: "v1_preview";
99
99
  preview: {
100
100
  stepId: string;
101
- prompt: string;
102
101
  title: string;
102
+ prompt: string;
103
103
  };
104
104
  }, {
105
105
  name: string;
@@ -110,8 +110,8 @@ export declare const CompiledWorkflowSnapshotSchema: z.ZodDiscriminatedUnion<"so
110
110
  sourceKind: "v1_preview";
111
111
  preview: {
112
112
  stepId: string;
113
- prompt: string;
114
113
  title: string;
114
+ prompt: string;
115
115
  };
116
116
  }>, z.ZodObject<{
117
117
  schemaVersion: z.ZodLiteral<1>;
@@ -1,8 +1,13 @@
1
1
  import type { Workflow } from '../../types/workflow.js';
2
2
  import type { CompiledWorkflowSnapshotV1 } from '../durable-core/schemas/compiled-workflow/index.js';
3
+ import { type Result } from 'neverthrow';
4
+ import type { DomainError } from '../../domain/execution/error.js';
3
5
  export declare function compileV1WorkflowToV2PreviewSnapshot(workflow: Workflow): Extract<CompiledWorkflowSnapshotV1, {
4
6
  sourceKind: 'v1_preview';
5
7
  }>;
6
8
  export declare function compileV1WorkflowToPinnedSnapshot(workflow: Workflow): Extract<CompiledWorkflowSnapshotV1, {
7
9
  sourceKind: 'v1_pinned';
8
10
  }>;
11
+ export declare function normalizeV1WorkflowToPinnedSnapshot(workflow: Workflow): Result<Extract<CompiledWorkflowSnapshotV1, {
12
+ sourceKind: 'v1_pinned';
13
+ }>, DomainError>;
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compileV1WorkflowToV2PreviewSnapshot = compileV1WorkflowToV2PreviewSnapshot;
4
4
  exports.compileV1WorkflowToPinnedSnapshot = compileV1WorkflowToPinnedSnapshot;
5
+ exports.normalizeV1WorkflowToPinnedSnapshot = normalizeV1WorkflowToPinnedSnapshot;
5
6
  const workflow_compiler_js_1 = require("../../application/services/workflow-compiler.js");
7
+ const neverthrow_1 = require("neverthrow");
6
8
  function compileV1WorkflowToV2PreviewSnapshot(workflow) {
7
9
  const firstStep = workflow.definition.steps[0];
8
10
  if (!firstStep) {
@@ -63,3 +65,19 @@ function compileV1WorkflowToPinnedSnapshot(workflow) {
63
65
  definition: resolvedDefinition,
64
66
  };
65
67
  }
68
+ function normalizeV1WorkflowToPinnedSnapshot(workflow) {
69
+ const resolved = (0, workflow_compiler_js_1.resolveDefinitionSteps)(workflow.definition.steps, workflow.definition.features ?? []);
70
+ if (resolved.isErr()) {
71
+ return (0, neverthrow_1.err)(resolved.error);
72
+ }
73
+ const resolvedDefinition = { ...workflow.definition, steps: resolved.value };
74
+ return (0, neverthrow_1.ok)({
75
+ schemaVersion: 1,
76
+ sourceKind: 'v1_pinned',
77
+ workflowId: workflow.definition.id,
78
+ name: workflow.definition.name,
79
+ description: workflow.definition.description,
80
+ version: workflow.definition.version,
81
+ definition: resolvedDefinition,
82
+ });
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,8 +31,9 @@
31
31
  "dev": "npm run build && node dist/mcp-server.js",
32
32
  "watch": "tsc --watch",
33
33
  "validate:workflows": "bash scripts/validate-workflows.sh",
34
+ "validate:registry": "node scripts/validate-workflows-registry.ts",
34
35
  "validate:workflow-discovery": "node scripts/validate-workflow-discovery.js",
35
- "precommit": "npm run validate:workflows",
36
+ "precommit": "npm run validate:registry",
36
37
  "preinstall": "node -e \"const v=parseInt(process.versions.node.split('.')[0],10); if(v<20){console.error('WorkRail requires Node.js >=20. Current: '+process.versions.node+'\\nPlease upgrade: https://nodejs.org/'); process.exit(1);}\"",
37
38
  "web:dev": "npm run build && WORKRAIL_ENABLE_SESSION_TOOLS=true node dist/mcp-server.js",
38
39
  "web:ci": "WORKRAIL_ENABLE_SESSION_TOOLS=true node dist/mcp-server.js",
@@ -0,0 +1,134 @@
1
+ {
2
+ "id": "bug-investigation-agentic",
3
+ "name": "Bug Investigation (v2 • Notes-First • WorkRail Executor)",
4
+ "version": "2.0.0",
5
+ "description": "A v2-first bug investigation workflow focused on moving from theory to proof with notes-first durability, explicit trigger fields, de-anchored fresh-eye review, and investigation-only handoff boundaries.",
6
+ "recommendedPreferences": {
7
+ "recommendedAutonomy": "guided",
8
+ "recommendedRiskPolicy": "conservative"
9
+ },
10
+ "preconditions": [
11
+ "User has a specific bug report, failing test, or unexpected behavior to investigate.",
12
+ "Agent has codebase access and can run tests, commands, or other deterministic evidence-gathering steps.",
13
+ "Some reproduction information or observable symptom is available, even if incomplete."
14
+ ],
15
+ "clarificationPrompts": [
16
+ "What are the repro steps, observed symptoms, and expected behavior?",
17
+ "How reproducible is this issue? (always, intermittent, unknown)",
18
+ "What environment, logs, failing tests, or constraints are already known?"
19
+ ],
20
+ "metaGuidance": [
21
+ "DEFAULT BEHAVIOR: self-execute with tools. Only ask the user for missing external facts, permissions, or business decisions you cannot resolve yourself.",
22
+ "V2 DURABILITY: use output.notesMarkdown and explicit context variables as the durable investigation state. Do NOT rely on BUG_investigation.md, BUG_hypotheses.md, or other markdown artifacts as required workflow memory.",
23
+ "ARTIFACT STRATEGY: markdown artifacts are optional human-facing outputs only. If created, they should be derived from notes/context state, not serve as the source of truth.",
24
+ "MAIN AGENT OWNS INVESTIGATION: the main agent owns truth, synthesis, evidence gathering, diagnosis, and final handoff.",
25
+ "SUBAGENT MODEL: use the WorkRail Executor only. Do not refer to Builder/Researcher identities.",
26
+ "AUDITOR MODEL: prefer delegation for context auditing, alternate hypothesis generation, adversarial challenge, and execution simulation. Do not hand off investigation ownership.",
27
+ "PARALLELISM: parallelize independent cognition; serialize synthesis, instrumentation changes, evidence interpretation, and final diagnosis.",
28
+ "PROOF OVER THEORY: code reading creates theories, not proof. A diagnosis is only strong when evidence separates it from the strongest alternatives.",
29
+ "ANTI-ANCHORING: in STANDARD and THOROUGH, at least one hypothesis-generation pass must be de-anchored from the main agent's preferred explanation.",
30
+ "DIAGNOSIS MODEL: allow `single_cause`, `multi_factor`, `root_plus_downstream`, `working_as_designed`, and `inconclusive_but_narrowed` outcomes. Do not force every investigation into a single-cause winner.",
31
+ "TRIGGERS: WorkRail can only react to explicit outputs. Use structural fields like `contextUnknownCount`, `hypothesesConsideredCount`, `hasStrongAlternative`, `contradictionCount`, `unresolvedEvidenceGapCount`, and `diagnosisConfidenceBand`.",
32
+ "BOUNDARY: this workflow investigates and proves root cause. It may describe high-level fix direction and likely files, but must not create implementation plans, patch sequencing, PR plans, or code-writing momentum."
33
+ ],
34
+ "steps": [
35
+ {
36
+ "id": "phase-0-triage-and-intake",
37
+ "title": "Phase 0: Triage (Bug Intake • Risk • Mode)",
38
+ "prompt": "Understand the bug report and choose the right rigor.\n\nCapture:\n- `bugSummary`: concise statement of the issue\n- `reproSummary`: repro steps, symptoms, expected behavior, environment notes\n- `investigationComplexity`: Small / Medium / Large\n- `riskLevel`: Low / Medium / High\n- `rigorMode`: QUICK / STANDARD / THOROUGH\n- `automationLevel`: High / Medium / Low\n- `maxParallelism`: 0 / 2 / 3\n\nDecision guidance:\n- QUICK: clear repro, narrow surface area, low ambiguity\n- STANDARD: moderate ambiguity, moderate system breadth, or meaningful risk\n- THOROUGH: high ambiguity, high-risk production impact, broad surface area, or multiple plausible causes\n\nSet context variables:\n- `bugSummary`\n- `reproSummary`\n- `investigationComplexity`\n- `riskLevel`\n- `rigorMode`\n- `automationLevel`\n- `maxParallelism`\n- `reproducibilityConfidence` (High / Medium / Low)\n\nAsk for confirmation only if the chosen rigor materially affects expectations or if critical repro details are still missing.",
39
+ "requireConfirmation": true
40
+ },
41
+ {
42
+ "id": "phase-0b-minimum-inputs-gate",
43
+ "title": "Phase 0b: Minimum Inputs Gate",
44
+ "prompt": "If critical inputs are missing, ask only for the minimum needed to investigate.\n\nPossible asks:\n- missing repro steps or failing test command\n- missing expected behavior\n- missing environment constraints or permissions\n- missing logs or stack traces when the codebase alone cannot answer the gap\n\nDo NOT ask for information you can discover with tools.",
45
+ "requireConfirmation": {
46
+ "or": [
47
+ { "var": "automationLevel", "equals": "Low" },
48
+ { "var": "automationLevel", "equals": "Medium" }
49
+ ]
50
+ }
51
+ },
52
+ {
53
+ "id": "phase-1-context-and-execution-understanding",
54
+ "title": "Phase 1: Context + Execution Understanding",
55
+ "prompt": "Build the minimum complete understanding needed to investigate correctly.\n\nDo the main investigation yourself using tools.\n\nDeliverable:\n- relevant files, modules, entry points, and call chain sketch\n- execution summary from trigger to failure or surprising behavior\n- suspicious points and explicit unknowns\n- whether the issue might plausibly be `working_as_designed`\n\nSet context variables:\n- `contextSummary`\n- `executionSummary`\n- `candidateFiles`\n- `contextUnknownCount`\n- `executionPathCount`\n- `suspiciousPointCount`\n- `workingAsDesignedStillPlausible`\n- `retriageNeeded`\n\nComputation rules:\n- `contextUnknownCount` = number of unresolved technical unknowns that materially affect hypothesis quality or evidence design\n- `executionPathCount` = number of materially distinct execution paths currently in play\n- `suspiciousPointCount` = count of concrete suspicious locations or transitions worth explaining\n- set `retriageNeeded = true` if the real surface area or risk is clearly larger than Phase 0 assumed\n\nMode-adaptive context audit:\n- QUICK: self-check only\n- STANDARD: if `contextUnknownCount > 0` and delegation is available, spawn TWO WorkRail Executors SIMULTANEOUSLY running `routine-context-gathering` with focus=COMPLETENESS and focus=DEPTH, then synthesize both outputs\n- THOROUGH: if delegation is available, spawn TWO WorkRail Executors SIMULTANEOUSLY running `routine-context-gathering` with focus=COMPLETENESS and focus=DEPTH, then synthesize both outputs",
56
+ "requireConfirmation": false
57
+ },
58
+ {
59
+ "id": "phase-1b-retriage-after-context",
60
+ "title": "Phase 1b: Re-Triage After Context",
61
+ "runCondition": {
62
+ "var": "retriageNeeded",
63
+ "equals": true
64
+ },
65
+ "prompt": "Reassess investigation scope after real context is known.\n\nReview:\n- `contextUnknownCount`\n- `executionPathCount`\n- `suspiciousPointCount`\n- actual systems/components involved\n- whether risk or ambiguity is larger than originally assessed\n\nDo:\n- confirm or adjust `investigationComplexity`\n- confirm or adjust `riskLevel`\n- confirm or adjust `rigorMode`\n- confirm or adjust `maxParallelism`\n\nSet context variables:\n- `investigationComplexity`\n- `riskLevel`\n- `rigorMode`\n- `maxParallelism`\n- `retriageChanged`\n\nRule:\n- upgrade rigor when the real investigation surface is broader or riskier than expected",
66
+ "requireConfirmation": {
67
+ "or": [
68
+ { "var": "retriageChanged", "equals": true },
69
+ { "var": "automationLevel", "equals": "Low" }
70
+ ]
71
+ }
72
+ },
73
+ {
74
+ "id": "phase-2-hypothesis-generation-and-shortlist",
75
+ "title": "Phase 2: Hypothesis Generation + Shortlist",
76
+ "prompt": "Generate and rank plausible explanations without over-committing early.\n\nRequired candidate types:\n- normal code/logic explanation(s)\n- state/data explanation(s) where applicable\n- integration/environment explanation(s) where applicable\n- `working_as_designed` when still plausible\n- multi-factor explanation if interacting causes plausibly explain the symptoms better than any single cause\n\nMode-adaptive generation:\n- QUICK: self-generate at least 3 materially distinct hypotheses\n- STANDARD: if delegation is available, spawn TWO or THREE WorkRail Executors SIMULTANEOUSLY for hypothesis generation from different lenses; at least one pass must be de-anchored from your current leading theory\n- THOROUGH: if delegation is available, spawn THREE WorkRail Executors SIMULTANEOUSLY for logic, data/state, and integration/environment lenses; at least one pass must be de-anchored from your current leading theory\n\nDe-anchored packet rule:\n- include the bug report, repro summary, symptoms, candidate files, and key references\n- do NOT frame the work package around your preferred explanation as the only story\n- if you already have a favorite theory, pass it separately as `currentLeadingTheory`\n\nAfter generation:\n- merge duplicates\n- rank hypotheses\n- explicitly decide whether a strong alternative remains\n- force one more generation pass if all candidates cluster in the same pattern family\n\nSet context variables:\n- `candidateHypotheses`\n- `hypothesesConsideredCount`\n- `hasStrongAlternative`\n- `leadingHypothesisConfidenceBand`\n- `currentLeadingTheory`\n\nComputation rules:\n- `hypothesesConsideredCount` = number of materially distinct viable explanations after merging duplicates\n- minimums: QUICK >= 3; STANDARD/THOROUGH >= 4 when `workingAsDesignedStillPlausible = true`, otherwise >= 3\n- `hasStrongAlternative = true` when a non-leading hypothesis still explains most symptoms with medium-or-better evidence fit\n\nRules:\n- if `hypothesesConsideredCount` is below the mode minimum, this phase cannot close\n- if `hasStrongAlternative = true`, challenge before the evidence loop can close",
77
+ "requireConfirmation": false
78
+ },
79
+ {
80
+ "id": "phase-3-evidence-strategy-and-instrumentation",
81
+ "title": "Phase 3: Evidence Strategy + Instrumentation",
82
+ "prompt": "Design the minimum evidence plan needed to separate the strongest remaining explanations.\n\nDo:\n- define what evidence would prove or disprove each top explanation\n- identify the minimum instrumentation, tests, commands, or observations needed\n- optionally use simulation to find the most informative observation points\n- make instrumentation changes or test additions only as needed for evidence collection\n\nMode-adaptive simulation:\n- QUICK: self-design the evidence plan\n- STANDARD: optionally spawn ONE WorkRail Executor running `routine-execution-simulation` when runtime/state-flow uncertainty is still meaningful\n- THOROUGH: if delegation is available, spawn ONE WorkRail Executor running `routine-execution-simulation` to improve evidence leverage before finalizing the plan\n\nSet context variables:\n- `evidenceStrategy`\n- `instrumentationCoverageScore`\n- `unresolvedEvidenceGapCount`\n- `evidenceRounds`\n\nComputation rules:\n- `instrumentationCoverageScore` = rough 1-10 score for how well the evidence plan can separate the top explanations\n- `unresolvedEvidenceGapCount` = number of still-missing observations needed to separate top remaining explanations\n- initialize `evidenceRounds = 0`\n\nRule:\n- this workflow may add instrumentation or tests to gather proof, but it must not drift into implementation planning or fix sequencing",
83
+ "requireConfirmation": false
84
+ },
85
+ {
86
+ "id": "phase-4-evidence-loop",
87
+ "type": "loop",
88
+ "title": "Phase 4: Evidence Collection Loop",
89
+ "loop": {
90
+ "type": "while",
91
+ "conditionSource": {
92
+ "kind": "artifact_contract",
93
+ "contractRef": "wr.contracts.loop_control",
94
+ "loopId": "bug_evidence_loop"
95
+ },
96
+ "maxIterations": 5
97
+ },
98
+ "body": [
99
+ {
100
+ "id": "phase-4a-collect-and-synthesize-evidence",
101
+ "title": "Collect and Synthesize Evidence",
102
+ "prompt": "Run the planned experiments, repros, tests, or commands and synthesize what they imply.\n\nAlways:\n- collect evidence against the leading theory and the strongest alternative(s)\n- update which explanations remain viable\n- identify contradictions explicitly\n- keep the investigation-only boundary intact\n\nSet context variables:\n- `evidenceRounds`\n- `evidenceStrengthByHypothesis`\n- `contradictionCount`\n- `unresolvedEvidenceGapCount`\n- `diagnosisType`\n- `residualUncertainty`\n\nComputation rules:\n- increment `evidenceRounds` once per completed pass\n- `contradictionCount` = number of material observations that directly conflict with the current leading diagnosis\n- `diagnosisType` must be one of: `single_cause`, `multi_factor`, `root_plus_downstream`, `working_as_designed`, `inconclusive_but_narrowed`\n\nStop / reopen guidance:\n- continue if `unresolvedEvidenceGapCount > 0`\n- continue if `contradictionCount > 0`\n- reopen the shortlist if the challenger breaks the leader or new evidence materially contradicts the current top two\n- allow bounded inconclusive status only when further evidence is not realistically available and the field has been meaningfully narrowed",
103
+ "requireConfirmation": false
104
+ },
105
+ {
106
+ "id": "phase-4b-loop-decision",
107
+ "title": "Evidence Loop Decision",
108
+ "prompt": "Decide whether the evidence loop should continue.\n\nDecision rules:\n- if `contradictionCount > 0` → continue\n- else if `unresolvedEvidenceGapCount > 0` → continue\n- else if `hasStrongAlternative = true` and the alternative is not meaningfully weaker → continue\n- else if `diagnosisType = inconclusive_but_narrowed` and further evidence is not realistically available → stop with bounded uncertainty\n- else → stop\n\nOutput exactly:\n```json\n{\n \"artifacts\": [{\n \"kind\": \"wr.loop_control\",\n \"loopId\": \"bug_evidence_loop\",\n \"decision\": \"continue\"\n }]\n}\n```",
109
+ "requireConfirmation": true,
110
+ "outputContract": {
111
+ "contractRef": "wr.contracts.loop_control"
112
+ }
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "id": "phase-5-diagnosis-validation",
118
+ "title": "Phase 5: Diagnosis Validation Bundle",
119
+ "prompt": "Stress-test the current diagnosis before handoff.\n\nSet `diagnosisConfidenceBand` using these rules:\n- High = all symptoms explained, no material contradictions, no unresolved evidence gaps\n- Medium = likely diagnosis, but one bounded uncertainty remains\n- Low = multiple viable explanations remain or contradictions are unresolved\n\nMode-adaptive validation:\n- QUICK: self-challenge; if `diagnosisConfidenceBand != High` or contradictions remain, optionally spawn ONE WorkRail Executor running `routine-hypothesis-challenge`\n- STANDARD: if delegation is available, spawn TWO WorkRail Executors SIMULTANEOUSLY running `routine-hypothesis-challenge` and `routine-execution-simulation`\n- THOROUGH: if delegation is available, spawn THREE WorkRail Executors SIMULTANEOUSLY running `routine-hypothesis-challenge`, `routine-execution-simulation`, and an additional `routine-hypothesis-challenge` pass focused on breaking the current diagnosis from a different angle\n\nParallel-output synthesis rules:\n- if 2+ validators raise serious concerns, reopen evidence or shortlist work\n- if exactly one validator raises a concern, investigate it before escalating\n- if no validator can materially break the diagnosis and `contradictionCount = 0`, proceed to handoff\n\nSet context variables:\n- `diagnosisConfidenceBand`\n- `validationFindingsCountBySeverity`\n- `validationSummary`\n\nBoundary rule:\n- allowed: high-level fix direction, likely files involved, verification recommendations\n- not allowed: implementation plan, patch sequencing, PR plan, or code-writing momentum",
120
+ "requireConfirmation": {
121
+ "or": [
122
+ { "var": "diagnosisConfidenceBand", "equals": "Low" },
123
+ { "var": "contradictionCount", "not_equals": 0 }
124
+ ]
125
+ }
126
+ },
127
+ {
128
+ "id": "phase-6-final-handoff",
129
+ "title": "Phase 6: Final Handoff",
130
+ "prompt": "Provide the final investigation handoff.\n\nInclude:\n- concise bug summary and repro summary\n- diagnosis type (`single_cause`, `multi_factor`, `root_plus_downstream`, `working_as_designed`, or `inconclusive_but_narrowed`)\n- proof summary and why the conclusion is justified\n- strongest alternative(s) that were ruled out\n- residual uncertainty, if any\n- high-level fix direction only\n- likely files or areas involved\n- verification recommendations for whoever implements the fix\n\nOptional artifact:\n- create `bug_handoff.md` only if it materially improves human handoff; it must be derived from the notes/context state, not treated as required workflow memory\n\nRule:\n- do not implement the fix, create a PR plan, or hand off a patch sequence",
131
+ "requireConfirmation": true
132
+ }
133
+ ]
134
+ }