@exaudeus/workrail 3.8.1 → 3.9.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 (31) hide show
  1. package/dist/di/container.js +8 -0
  2. package/dist/di/tokens.d.ts +1 -0
  3. package/dist/di/tokens.js +1 -0
  4. package/dist/engine/engine-factory.js +2 -0
  5. package/dist/manifest.json +72 -40
  6. package/dist/mcp/handlers/shared/remembered-roots.d.ts +4 -0
  7. package/dist/mcp/handlers/shared/remembered-roots.js +54 -0
  8. package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +4 -1
  9. package/dist/mcp/handlers/shared/request-workflow-reader.js +71 -2
  10. package/dist/mcp/handlers/shared/workflow-source-visibility.d.ts +33 -0
  11. package/dist/mcp/handlers/shared/workflow-source-visibility.js +109 -0
  12. package/dist/mcp/handlers/v2-execution/index.js +4 -0
  13. package/dist/mcp/handlers/v2-execution/start.js +2 -1
  14. package/dist/mcp/handlers/v2-resume.js +4 -0
  15. package/dist/mcp/handlers/v2-workflow.js +94 -53
  16. package/dist/mcp/output-schemas.d.ts +372 -0
  17. package/dist/mcp/output-schemas.js +19 -0
  18. package/dist/mcp/server.js +2 -0
  19. package/dist/mcp/tool-descriptions.js +9 -4
  20. package/dist/mcp/types.d.ts +2 -0
  21. package/dist/mcp/v2/tools.d.ts +6 -6
  22. package/dist/mcp/v2/tools.js +2 -2
  23. package/dist/v2/infra/local/data-dir/index.d.ts +2 -0
  24. package/dist/v2/infra/local/data-dir/index.js +6 -0
  25. package/dist/v2/infra/local/remembered-roots-store/index.d.ts +14 -0
  26. package/dist/v2/infra/local/remembered-roots-store/index.js +172 -0
  27. package/dist/v2/ports/data-dir.port.d.ts +2 -0
  28. package/dist/v2/ports/remembered-roots-store.port.d.ts +27 -0
  29. package/dist/v2/ports/remembered-roots-store.port.js +2 -0
  30. package/package.json +1 -1
  31. package/workflows/workflow-for-workflows.v2.json +4 -4
@@ -54,6 +54,24 @@ exports.V2WorkflowListItemSchema = zod_1.z.object({
54
54
  version: zod_1.z.string().min(1),
55
55
  kind: zod_1.z.literal('workflow'),
56
56
  workflowHash: zod_1.z.string().nullable(),
57
+ visibility: zod_1.z.object({
58
+ category: zod_1.z.enum(['built_in', 'personal', 'legacy_project', 'rooted_sharing', 'external']),
59
+ source: zod_1.z.object({
60
+ kind: zod_1.z.enum(['bundled', 'user', 'project', 'custom', 'git', 'remote', 'plugin']),
61
+ displayName: zod_1.z.string().min(1),
62
+ }),
63
+ rootedSharing: zod_1.z.object({
64
+ kind: zod_1.z.literal('remembered_root'),
65
+ rootPath: zod_1.z.string().min(1),
66
+ groupLabel: zod_1.z.string().min(1),
67
+ }).optional(),
68
+ migration: zod_1.z.object({
69
+ preferredSource: zod_1.z.literal('rooted_sharing'),
70
+ currentSource: zod_1.z.literal('legacy_project'),
71
+ reason: zod_1.z.literal('legacy_project_precedence'),
72
+ summary: zod_1.z.string().min(1),
73
+ }).optional(),
74
+ }).optional(),
57
75
  });
58
76
  exports.V2WorkflowListOutputSchema = zod_1.z.object({
59
77
  workflows: zod_1.z.array(exports.V2WorkflowListItemSchema),
@@ -63,6 +81,7 @@ exports.V2WorkflowInspectOutputSchema = zod_1.z.object({
63
81
  workflowHash: zod_1.z.string().min(1),
64
82
  mode: zod_1.z.enum(['metadata', 'preview']),
65
83
  compiled: exports.JsonValueSchema,
84
+ visibility: exports.V2WorkflowListItemSchema.shape.visibility.optional(),
66
85
  references: zod_1.z.array(zod_1.z.object({
67
86
  id: zod_1.z.string().min(1),
68
87
  title: zod_1.z.string().min(1),
@@ -109,6 +109,7 @@ async function createToolContext() {
109
109
  if (aliasLoadResult.isErr()) {
110
110
  console.error(`[V2Init] Token alias index load warning: ${aliasLoadResult.error.message}`);
111
111
  }
112
+ const rememberedRootsStore = container_js_1.container.resolve(tokens_js_1.DI.V2.RememberedRootsStore);
112
113
  v2 = {
113
114
  gate,
114
115
  sessionStore,
@@ -120,6 +121,7 @@ async function createToolContext() {
120
121
  idFactory,
121
122
  tokenCodecPorts,
122
123
  tokenAliasStore,
124
+ rememberedRootsStore,
123
125
  validationPipelineDeps,
124
126
  resolvedRootUris: [],
125
127
  workspaceResolver: new index_js_1.LocalWorkspaceAnchorV2(process.cwd()),
@@ -52,16 +52,21 @@ This tool provides:
52
52
 
53
53
  Use this to discover workflows before attempting multi-step tasks. When a workflow exists for the user's request, following it means following the user's structured instructions.
54
54
 
55
- Pass workspacePath when available so project-scoped workflow variants are resolved against the correct workspace instead of the server's fallback directory.`,
55
+ Always pass workspacePath so project-scoped workflow variants are resolved against the correct workspace instead of the server's fallback directory. Shared MCP servers cannot infer this safely.`,
56
56
  inspect_workflow: `Inspect a workflow structure before starting it (WorkRail v2, feature-flagged).
57
57
 
58
58
  Use this to understand what steps the workflow will guide you through. The workflow is a step-by-step plan the user (or workflow author) created for this type of task.
59
59
 
60
+ Parameters:
61
+ - workflowId: The workflow to inspect
62
+ - mode: metadata mode shows name/description only; preview mode shows the full step breakdown
63
+ - workspacePath: absolute workspace path for correct project-scoped workflow resolution
64
+
60
65
  Returns:
61
66
  - metadata mode: Just name and description
62
67
  - preview mode: Full step-by-step breakdown (default)
63
68
 
64
- Pass workspacePath when available so project-scoped workflow variants are resolved against the correct workspace.
69
+ Always pass workspacePath so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.
65
70
 
66
71
  Remember: inspecting is read-only. Call start_workflow when ready to begin.`,
67
72
  start_workflow: (0, workflow_protocol_contracts_js_1.renderProtocolDescription)(workflow_protocol_contracts_js_1.START_WORKFLOW_PROTOCOL, 'standard'),
@@ -130,7 +135,7 @@ Workflows are the user's pre-defined instructions for complex tasks. When a work
130
135
 
131
136
  Returns stable workflow metadata and pinned snapshot hashes (workflowHash) for deterministic execution.
132
137
 
133
- Pass workspacePath when available so project-scoped workflow variants are resolved against the correct workspace.`,
138
+ Pass workspacePath on every call so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.`,
134
139
  inspect_workflow: `Inspect a workflow you are considering following (WorkRail v2, feature-flagged).
135
140
 
136
141
  Use this to understand the workflow's structure before starting. The workflow is the user's explicit plan - not suggestions, not guidelines, but direct instructions you will follow.
@@ -138,7 +143,7 @@ Use this to understand the workflow's structure before starting. The workflow is
138
143
  Parameters:
139
144
  - workflowId: The workflow to inspect
140
145
  - mode: 'metadata' (name/description only) or 'preview' (full step breakdown)
141
- - workspacePath: optional absolute workspace path for correct project-scoped workflow resolution
146
+ - workspacePath: absolute workspace path for correct project-scoped workflow resolution
142
147
 
143
148
  This is read-only. Call start_workflow when ready to commit to following the workflow.`,
144
149
  start_workflow: (0, workflow_protocol_contracts_js_1.renderProtocolDescription)(workflow_protocol_contracts_js_1.START_WORKFLOW_PROTOCOL, 'authoritative'),
@@ -19,6 +19,7 @@ import type { SessionSummaryProviderPortV2 } from '../v2/ports/session-summary-p
19
19
  import type { ValidationPipelineDepsPhase1a } from '../application/services/workflow-validation-pipeline.js';
20
20
  import type { TokenAliasStorePortV2 } from '../v2/ports/token-alias-store.port.js';
21
21
  import type { RandomEntropyPortV2 } from '../v2/ports/random-entropy.port.js';
22
+ import type { RememberedRootsStorePortV2 } from '../v2/ports/remembered-roots-store.port.js';
22
23
  export interface SessionHealthDetails {
23
24
  readonly health: SessionHealthV2;
24
25
  }
@@ -59,6 +60,7 @@ export interface V2Dependencies {
59
60
  readonly idFactory: IdFactoryV2;
60
61
  readonly tokenCodecPorts: TokenCodecPorts;
61
62
  readonly tokenAliasStore: TokenAliasStorePortV2;
63
+ readonly rememberedRootsStore?: RememberedRootsStorePortV2;
62
64
  readonly entropy: RandomEntropyPortV2;
63
65
  readonly validationPipelineDeps: ValidationPipelineDepsPhase1a;
64
66
  readonly resolvedRootUris?: readonly string[];
@@ -1,24 +1,24 @@
1
1
  import { z } from 'zod';
2
2
  import type { ToolAnnotations } from '../tool-factory.js';
3
3
  export declare const V2ListWorkflowsInput: z.ZodObject<{
4
- workspacePath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
4
+ workspacePath: z.ZodEffects<z.ZodString, string, string>;
5
5
  }, "strip", z.ZodTypeAny, {
6
- workspacePath?: string | undefined;
6
+ workspacePath: string;
7
7
  }, {
8
- workspacePath?: string | undefined;
8
+ workspacePath: string;
9
9
  }>;
10
10
  export type V2ListWorkflowsInput = z.infer<typeof V2ListWorkflowsInput>;
11
11
  export declare const V2InspectWorkflowInput: z.ZodObject<{
12
12
  workflowId: z.ZodString;
13
13
  mode: z.ZodDefault<z.ZodEnum<["metadata", "preview"]>>;
14
- workspacePath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
14
+ workspacePath: z.ZodEffects<z.ZodString, string, string>;
15
15
  }, "strip", z.ZodTypeAny, {
16
16
  workflowId: string;
17
+ workspacePath: string;
17
18
  mode: "metadata" | "preview";
18
- workspacePath?: string | undefined;
19
19
  }, {
20
20
  workflowId: string;
21
- workspacePath?: string | undefined;
21
+ workspacePath: string;
22
22
  mode?: "metadata" | "preview" | undefined;
23
23
  }>;
24
24
  export type V2InspectWorkflowInput = z.infer<typeof V2InspectWorkflowInput>;
@@ -15,12 +15,12 @@ const workspacePathField = zod_1.z.string()
15
15
  .describe('Absolute path to your current workspace directory (e.g. the "Workspace:" value from your system parameters). Used to resolve project-scoped workflow variants against the correct workspace. If omitted, WorkRail uses MCP roots when available, then falls back to the server process directory.');
16
16
  const optionalWorkspacePathField = workspacePathField.optional();
17
17
  exports.V2ListWorkflowsInput = zod_1.z.object({
18
- workspacePath: optionalWorkspacePathField,
18
+ workspacePath: workspacePathField.describe('Required. Absolute path to your current workspace directory (e.g. the "Workspace:" value from your system parameters). WorkRail uses this to resolve project-scoped workflow variants against the correct workspace for discovery-sensitive workflow listing. Shared MCP servers cannot infer this safely.'),
19
19
  });
20
20
  exports.V2InspectWorkflowInput = zod_1.z.object({
21
21
  workflowId: zod_1.z.string().min(1).regex(/^[A-Za-z0-9_-]+$/, 'Workflow ID must contain only letters, numbers, hyphens, and underscores').describe('The workflow ID to inspect'),
22
22
  mode: zod_1.z.enum(['metadata', 'preview']).default('preview').describe('Detail level: metadata (name and description only) or preview (full step-by-step breakdown, default)'),
23
- workspacePath: optionalWorkspacePathField,
23
+ workspacePath: workspacePathField.describe('Required. Absolute path to your current workspace directory (e.g. the "Workspace:" value from your system parameters). WorkRail uses this to resolve the correct project-scoped workflow variant for discovery-sensitive workflow inspection. Shared MCP servers cannot infer this safely.'),
24
24
  });
25
25
  exports.V2StartWorkflowInput = zod_1.z.object({
26
26
  workflowId: zod_1.z.string().min(1).regex(/^[A-Za-z0-9_-]+$/, 'Workflow ID must contain only letters, numbers, hyphens, and underscores').describe('The workflow ID to start'),
@@ -5,6 +5,8 @@ export declare class LocalDataDirV2 implements DataDirPortV2 {
5
5
  constructor(env: Record<string, string | undefined>);
6
6
  private safeFileSegment;
7
7
  private root;
8
+ rememberedRootsPath(): string;
9
+ rememberedRootsLockPath(): string;
8
10
  snapshotsDir(): string;
9
11
  snapshotPath(snapshotRef: SnapshotRef): string;
10
12
  keysDir(): string;
@@ -47,6 +47,12 @@ class LocalDataDirV2 {
47
47
  const configured = this.env['WORKRAIL_DATA_DIR'];
48
48
  return configured ? configured : path.join(os.homedir(), '.workrail', 'data');
49
49
  }
50
+ rememberedRootsPath() {
51
+ return path.join(this.root(), 'workflow-sources', 'remembered-roots.json');
52
+ }
53
+ rememberedRootsLockPath() {
54
+ return path.join(this.root(), 'workflow-sources', 'remembered-roots.lock');
55
+ }
50
56
  snapshotsDir() {
51
57
  return path.join(this.root(), 'snapshots');
52
58
  }
@@ -0,0 +1,14 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
3
+ import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
4
+ import type { RememberedRootRecordV2, RememberedRootsStoreError, RememberedRootsStorePortV2 } from '../../../ports/remembered-roots-store.port.js';
5
+ export declare class LocalRememberedRootsStoreV2 implements RememberedRootsStorePortV2 {
6
+ private readonly dataDir;
7
+ private readonly fs;
8
+ constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2);
9
+ listRoots(): ResultAsync<readonly string[], RememberedRootsStoreError>;
10
+ listRootRecords(): ResultAsync<readonly RememberedRootRecordV2[], RememberedRootsStoreError>;
11
+ rememberRoot(rootPath: string): ResultAsync<void, RememberedRootsStoreError>;
12
+ private persist;
13
+ private withLock;
14
+ }
@@ -0,0 +1,172 @@
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.LocalRememberedRootsStoreV2 = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const zod_1 = require("zod");
9
+ const neverthrow_1 = require("neverthrow");
10
+ const jcs_js_1 = require("../../../durable-core/canonical/jcs.js");
11
+ const REMEMBERED_ROOTS_LOCK_RETRY_MS = 250;
12
+ const RememberedRootRecordSchema = zod_1.z.object({
13
+ path: zod_1.z.string(),
14
+ addedAtMs: zod_1.z.number().int().nonnegative(),
15
+ lastSeenAtMs: zod_1.z.number().int().nonnegative(),
16
+ source: zod_1.z.literal('explicit_workspace_path'),
17
+ });
18
+ const RememberedRootsFileSchema = zod_1.z.object({
19
+ v: zod_1.z.literal(1),
20
+ roots: zod_1.z.array(RememberedRootRecordSchema),
21
+ });
22
+ function mapFsToRememberedRootsError(e) {
23
+ if (e.code === 'FS_ALREADY_EXISTS') {
24
+ return {
25
+ code: 'REMEMBERED_ROOTS_BUSY',
26
+ message: 'Remembered roots are being updated by another WorkRail process.',
27
+ retry: { kind: 'retryable_after_ms', afterMs: REMEMBERED_ROOTS_LOCK_RETRY_MS },
28
+ lockPath: 'remembered-roots.lock',
29
+ };
30
+ }
31
+ return { code: 'REMEMBERED_ROOTS_IO_ERROR', message: e.message };
32
+ }
33
+ function normalizeRootRecords(roots) {
34
+ const seen = new Set();
35
+ const normalized = [];
36
+ for (const root of roots) {
37
+ const nextPath = path_1.default.resolve(root.path);
38
+ if (seen.has(nextPath))
39
+ continue;
40
+ seen.add(nextPath);
41
+ normalized.push({
42
+ path: nextPath,
43
+ addedAtMs: root.addedAtMs,
44
+ lastSeenAtMs: root.lastSeenAtMs,
45
+ source: root.source,
46
+ });
47
+ }
48
+ return normalized;
49
+ }
50
+ class LocalRememberedRootsStoreV2 {
51
+ constructor(dataDir, fs) {
52
+ this.dataDir = dataDir;
53
+ this.fs = fs;
54
+ }
55
+ listRoots() {
56
+ return this.listRootRecords().map((roots) => roots.map((root) => root.path));
57
+ }
58
+ listRootRecords() {
59
+ const filePath = this.dataDir.rememberedRootsPath();
60
+ return this.fs.readFileUtf8(filePath)
61
+ .orElse((e) => {
62
+ if (e.code === 'FS_NOT_FOUND')
63
+ return (0, neverthrow_1.okAsync)('');
64
+ return (0, neverthrow_1.errAsync)(mapFsToRememberedRootsError(e));
65
+ })
66
+ .andThen((raw) => {
67
+ if (raw === '')
68
+ return (0, neverthrow_1.okAsync)([]);
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(raw);
72
+ }
73
+ catch {
74
+ return (0, neverthrow_1.errAsync)({
75
+ code: 'REMEMBERED_ROOTS_CORRUPTION',
76
+ message: `Invalid JSON in remembered roots file: ${filePath}`,
77
+ });
78
+ }
79
+ const validated = RememberedRootsFileSchema.safeParse(parsed);
80
+ if (!validated.success) {
81
+ return (0, neverthrow_1.errAsync)({
82
+ code: 'REMEMBERED_ROOTS_CORRUPTION',
83
+ message: `Remembered roots file has invalid shape: ${filePath}`,
84
+ });
85
+ }
86
+ return (0, neverthrow_1.okAsync)(normalizeRootRecords(validated.data.roots));
87
+ });
88
+ }
89
+ rememberRoot(rootPath) {
90
+ const normalizedRoot = path_1.default.resolve(rootPath);
91
+ const nowMs = Date.now();
92
+ return this.withLock(() => this.listRootRecords().andThen((roots) => {
93
+ const existing = roots.find((root) => root.path === normalizedRoot);
94
+ const nextRoots = existing
95
+ ? roots.map((root) => root.path === normalizedRoot
96
+ ? { ...root, lastSeenAtMs: nowMs }
97
+ : root)
98
+ : [
99
+ ...roots,
100
+ {
101
+ path: normalizedRoot,
102
+ addedAtMs: nowMs,
103
+ lastSeenAtMs: nowMs,
104
+ source: 'explicit_workspace_path',
105
+ },
106
+ ];
107
+ return this.persist(nextRoots);
108
+ }));
109
+ }
110
+ persist(roots) {
111
+ const filePath = this.dataDir.rememberedRootsPath();
112
+ const dir = path_1.default.dirname(filePath);
113
+ const tmpPath = `${filePath}.tmp`;
114
+ const fileValue = {
115
+ v: 1,
116
+ roots: [...normalizeRootRecords(roots)],
117
+ };
118
+ const canonical = (0, jcs_js_1.toCanonicalBytes)(fileValue).mapErr((e) => ({
119
+ code: 'REMEMBERED_ROOTS_IO_ERROR',
120
+ message: `Failed to canonicalize remembered roots state: ${e.message}`,
121
+ }));
122
+ if (canonical.isErr())
123
+ return (0, neverthrow_1.errAsync)(canonical.error);
124
+ const bytes = canonical.value;
125
+ return this.fs.mkdirp(dir)
126
+ .mapErr(mapFsToRememberedRootsError)
127
+ .andThen(() => this.fs.openWriteTruncate(tmpPath).mapErr(mapFsToRememberedRootsError))
128
+ .andThen(({ fd }) => this.fs.writeAll(fd, bytes)
129
+ .mapErr(mapFsToRememberedRootsError)
130
+ .andThen(() => this.fs.fsyncFile(fd).mapErr(mapFsToRememberedRootsError))
131
+ .andThen(() => this.fs.closeFile(fd).mapErr(mapFsToRememberedRootsError))
132
+ .orElse((e) => this.fs.closeFile(fd)
133
+ .mapErr(() => e)
134
+ .andThen(() => (0, neverthrow_1.errAsync)(e))))
135
+ .andThen(() => this.fs.rename(tmpPath, filePath).mapErr(mapFsToRememberedRootsError))
136
+ .andThen(() => this.fs.fsyncDir(dir).mapErr(mapFsToRememberedRootsError));
137
+ }
138
+ withLock(run) {
139
+ const lockPath = this.dataDir.rememberedRootsLockPath();
140
+ const dir = path_1.default.dirname(lockPath);
141
+ const lockBytes = new TextEncoder().encode(JSON.stringify({ v: 1, pid: process.pid }));
142
+ return this.fs.mkdirp(dir)
143
+ .mapErr(mapFsToRememberedRootsError)
144
+ .andThen(() => this.fs.openExclusive(lockPath, lockBytes)
145
+ .mapErr((e) => {
146
+ const mapped = mapFsToRememberedRootsError(e);
147
+ if (mapped.code === 'REMEMBERED_ROOTS_BUSY') {
148
+ return { ...mapped, lockPath };
149
+ }
150
+ return mapped;
151
+ }))
152
+ .andThen(({ fd }) => this.fs.fsyncFile(fd)
153
+ .mapErr(mapFsToRememberedRootsError)
154
+ .andThen(() => this.fs.closeFile(fd).mapErr(mapFsToRememberedRootsError))
155
+ .andThen(() => run())
156
+ .andThen((value) => this.fs.unlink(lockPath)
157
+ .orElse((e) => {
158
+ if (e.code === 'FS_NOT_FOUND')
159
+ return (0, neverthrow_1.okAsync)(undefined);
160
+ return (0, neverthrow_1.errAsync)(mapFsToRememberedRootsError(e));
161
+ })
162
+ .map(() => value))
163
+ .orElse((error) => this.fs.unlink(lockPath)
164
+ .orElse((e) => {
165
+ if (e.code === 'FS_NOT_FOUND')
166
+ return (0, neverthrow_1.okAsync)(undefined);
167
+ return (0, neverthrow_1.errAsync)(mapFsToRememberedRootsError(e));
168
+ })
169
+ .andThen(() => (0, neverthrow_1.errAsync)(error))));
170
+ }
171
+ }
172
+ exports.LocalRememberedRootsStoreV2 = LocalRememberedRootsStoreV2;
@@ -1,5 +1,7 @@
1
1
  import type { SessionId, WorkflowHash, SnapshotRef } from '../durable-core/ids/index.js';
2
2
  export interface DataDirPortV2 {
3
+ rememberedRootsPath(): string;
4
+ rememberedRootsLockPath(): string;
3
5
  pinnedWorkflowsDir(): string;
4
6
  pinnedWorkflowPath(workflowHash: WorkflowHash): string;
5
7
  snapshotsDir(): string;
@@ -0,0 +1,27 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ export type RememberedRootsStoreError = {
3
+ readonly code: 'REMEMBERED_ROOTS_BUSY';
4
+ readonly message: string;
5
+ readonly retry: {
6
+ readonly kind: 'retryable_after_ms';
7
+ readonly afterMs: number;
8
+ };
9
+ readonly lockPath: string;
10
+ } | {
11
+ readonly code: 'REMEMBERED_ROOTS_IO_ERROR';
12
+ readonly message: string;
13
+ } | {
14
+ readonly code: 'REMEMBERED_ROOTS_CORRUPTION';
15
+ readonly message: string;
16
+ };
17
+ export interface RememberedRootRecordV2 {
18
+ readonly path: string;
19
+ readonly addedAtMs: number;
20
+ readonly lastSeenAtMs: number;
21
+ readonly source: 'explicit_workspace_path';
22
+ }
23
+ export interface RememberedRootsStorePortV2 {
24
+ listRoots(): ResultAsync<readonly string[], RememberedRootsStoreError>;
25
+ listRootRecords(): ResultAsync<readonly RememberedRootRecordV2[], RememberedRootsStoreError>;
26
+ rememberRoot(rootPath: string): ResultAsync<void, RememberedRootsStoreError>;
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,7 +17,7 @@
17
17
  "META DISTINCTION: you are authoring or modernizing a workflow, not executing one. Keep the authored workflow's concerns separate from this meta-workflow's execution.",
18
18
  "DEFAULT BEHAVIOR: self-execute with tools. Only ask the user for business decisions about the workflow being authored or modernized, not things you can learn from the schema, authoring spec, or example workflows.",
19
19
  "AUTHORED VOICE: prompts in the authored workflow must be user-voiced. No middleware narration, no pseudo-DSL, no tutorial framing, no teaching-product language.",
20
- "VOICE ADAPTATION: the lean coding workflow is one voice example, not the universal template. Adapt vocabulary and tone to the authored workflow's domain.",
20
+ "VOICE ADAPTATION: the lean coding workflow is one voice example, not the universal template. Copy structural patterns, not domain language. Adapt vocabulary and tone to the authored workflow's domain.",
21
21
  "VOICE EXAMPLES: Coding: 'Review the changes in this MR.' Ops: 'Check whether the pipeline is healthy.' Content: 'Read the draft and check the argument.' NOT: 'The system will now perform a comprehensive analysis of...'",
22
22
  "VALIDATION GATE: validate with real validators, not regex approximations. When validator output and authoring assumptions conflict, runtime wins.",
23
23
  "ARTIFACT STRATEGY: the workflow JSON file is the primary output. Intermediate notes go in output.notesMarkdown. Do not create extra planning artifacts unless the workflow is genuinely complex.",
@@ -87,7 +87,7 @@
87
87
  {
88
88
  "id": "phase-0-understand",
89
89
  "title": "Phase 0: Understand the Workflow to Author or Modernize",
90
- "prompt": "Before you write anything, understand what you're working on.\n\nStart by reading:\n- `workflow-schema` reference (legal structure)\n- `authoring-spec` reference (canonical authoring rules)\n- `authoring-guide-v2` reference (current v2 authoring principles)\n- `workflow-authoring-reference` reference (detailed structure patterns)\n- `lean-coding-workflow` reference (modern example to inspect)\n\nRead `routines-guide` too if you think the authored workflow may need delegation or template injection.\n\nThen decide what kind of authoring task this is:\n- `authoringMode`: `create` or `modernize_existing`\n\nIf `authoringMode = create`, understand:\n- What recurring task or problem should this workflow solve?\n- Who runs it and how often?\n- What does success look like?\n- What constraints exist (tools, permissions, domain rules)?\n\nIf `authoringMode = modernize_existing`, understand:\n- Which workflow file is being updated?\n- What should stay the same about its purpose?\n- What feels stale, legacy, repetitive, or misaligned with current authoring guidance?\n- What constraints apply to the modernization (keep file path, preserve compatibility, avoid broad rewrites, etc.)?\n\nExplore first. Use tools to understand the existing workflow, surrounding docs, and relevant domain context. Ask the user only what you genuinely cannot figure out yourself.\n\nThen classify:\n- `workflowComplexity`: Simple (linear, few steps) / Medium (branches, loops, or moderate step count) / Complex (multiple loops, delegation, extension points, many steps)\n- `rigorMode`: QUICK (simple linear workflow, low risk) / STANDARD (moderate complexity or domain risk) / THOROUGH (complex architecture, high stakes, needs review loops)\n\nCapture:\n- `authoringMode`\n- `workflowComplexity`\n- `rigorMode`\n- `taskDescription`\n- `intendedAudience`\n- `successCriteria`\n- `domainConstraints`\n- `targetWorkflowPath` (required for `modernize_existing`, otherwise empty)\n- `modernizationGoals` (required for `modernize_existing`, otherwise empty)\n- `openQuestions` (only real questions that need user input)",
90
+ "prompt": "Before you write anything, understand what you're working on.\n\nStart by reading:\n- `workflow-schema` reference (legal structure)\n- `authoring-spec` reference (canonical authoring rules)\n- `authoring-guide-v2` reference (current v2 authoring principles)\n- `workflow-authoring-reference` reference (detailed structure patterns)\n- `lean-coding-workflow` reference (modern example to inspect)\n\nRead `routines-guide` too if you think the authored workflow may need delegation or template injection.\n\nThen decide what kind of authoring task this is:\n- `authoringMode`: `create` or `modernize_existing`\n\nIf `authoringMode = create`, understand:\n- What recurring task or problem should this workflow solve?\n- Who runs it and how often?\n- What does success look like?\n- What constraints exist (tools, permissions, domain rules)?\n\nIf `authoringMode = modernize_existing`, understand:\n- Which workflow file is being updated?\n- What should stay the same about its purpose?\n- What feels stale, legacy, repetitive, or misaligned with current authoring guidance?\n- What constraints apply to the modernization (keep file path, preserve compatibility, avoid broad rewrites, etc.)?\n- Which modern example should act as the primary baseline, if any?\n\nFor `modernize_existing`, make an explicit baseline decision before architecture work:\n- choose exactly one `primaryBaseline` when a single modern example fits well\n- optional `secondaryBaselines` may be used for supporting patterns only\n- if no single baseline fits, set `primaryBaseline = none` and explain whether you are using a hybrid baseline or reasoning directly from schema + authoring guidance\n- list `patternsToBorrow` and `patternsToAvoid`\n\nRule:\n- baselines are models, not templates. Copy structural patterns, not another workflow's domain voice.\n\nExplore first. Use tools to understand the existing workflow, surrounding docs, and relevant domain context. Ask the user only what you genuinely cannot figure out yourself.\n\nThen classify:\n- `workflowComplexity`: Simple (linear, few steps) / Medium (branches, loops, or moderate step count) / Complex (multiple loops, delegation, extension points, many steps)\n- `rigorMode`: QUICK (simple linear workflow, low risk) / STANDARD (moderate complexity or domain risk) / THOROUGH (complex architecture, high stakes, needs review loops)\n\nCapture:\n- `authoringMode`\n- `workflowComplexity`\n- `rigorMode`\n- `taskDescription`\n- `intendedAudience`\n- `successCriteria`\n- `domainConstraints`\n- `targetWorkflowPath` (required for `modernize_existing`, otherwise empty)\n- `modernizationGoals` (required for `modernize_existing`, otherwise empty)\n- `primaryBaseline` (for `modernize_existing`, otherwise empty)\n- `secondaryBaselines` (for `modernize_existing`, otherwise empty)\n- `baselineDecisionRationale` (for `modernize_existing`, otherwise empty)\n- `patternsToBorrow` (for `modernize_existing`, otherwise empty)\n- `patternsToAvoid` (for `modernize_existing`, otherwise empty)\n- `openQuestions` (only real questions that need user input)",
91
91
  "requireConfirmation": true
92
92
  },
93
93
  {
@@ -97,7 +97,7 @@
97
97
  "var": "workflowComplexity",
98
98
  "not_equals": "Simple"
99
99
  },
100
- "prompt": "Decide the architecture before you write JSON.\n\nBased on what you learned in Phase 0, decide:\n\n1. **Step structure**: how many phases, what each one does, what order\n2. **Loops**: does any phase need iteration? If so, what are the exit rules and max iterations?\n\nLoop design heuristics:\n- Add a loop ONLY when: (a) a quality gate may fail on first pass (validation, review), (b) each pass adds measurable value (progressive refinement), or (c) external feedback requires re-execution.\n- Do NOT loop when: (a) the agent can get it right in one pass with sufficient context, or (b) the full workflow is cheap enough to re-run entirely.\n- Every loop needs: an explicit exit condition (not vibes), a bounded maxIterations, and a decision step with outputContract.\n- Sensible defaults: validation ≈ 2-3, review/refinement ≈ 2, user-feedback ≈ 2-3 with confirmation gate. Go higher only with explicit justification in your notes.\n3. **Confirmation gates**: where does the user genuinely need to approve before proceeding? Don't add confirmations as ceremony.\n4. **Delegation**: does any step benefit from subagent routines? If so, which ones and why? Keep delegation bounded.\n5. **Prompt composition**: will any steps need promptFragments for rigor-mode branching? Will any steps share enough structure to use templates?\n6. **Extension points**: are there customizable slots that projects might want to override (e.g., a verification routine, a review routine)?\n7. **References**: should the authored workflow declare its own references to external docs?\n8. **Artifacts**: what does each step produce? Which artifact is canonical for which concern?\n9. **metaGuidance**: what persistent behavioral rules should the agent see on start and resume?\n\nIf `authoringMode = modernize_existing`, also decide:\n- should this workflow be preserved mostly in place, restructured selectively, or rewritten more substantially?\n- which existing steps, loops, references, or metaGuidance should stay because they still fit the workflow's purpose?\n- which legacy patterns or repetitive sections should be removed or reshaped?\n- whether the file path should stay the same or whether a new variant/file is genuinely warranted\n\nWrite the shape as a structured outline in your notes. Include:\n- Phase list with titles and one-line goals\n- Which phases loop and why\n- Which phases have confirmation gates and why\n- Context variables that flow between phases\n- Artifact ownership (which artifact is canonical for what)\n- for `modernize_existing`: whether the plan is preserve-in-place, restructure, or rewrite-biased and why\n\nDon't write JSON yet.\n\nCapture:\n- `workflowOutline`\n- `loopDesign`\n- `confirmationDesign`\n- `delegationDesign`\n- `artifactPlan`\n- `contextModel` (the context variables the workflow will use and where they're set)\n- `voiceStrategy` (domain vocabulary, authority posture: directive/collaborative/supervisory, density calibration)\n- `modernizationStrategy` (for `modernize_existing`: preserve_in_place / restructure / rewrite, otherwise empty)",
100
+ "prompt": "Decide the architecture before you write JSON.\n\nBased on what you learned in Phase 0, decide:\n\n1. **Step structure**: how many phases, what each one does, what order\n2. **Loops**: does any phase need iteration? If so, what are the exit rules and max iterations?\n\nLoop design heuristics:\n- Add a loop ONLY when: (a) a quality gate may fail on first pass (validation, review), (b) each pass adds measurable value (progressive refinement), or (c) external feedback requires re-execution.\n- Do NOT loop when: (a) the agent can get it right in one pass with sufficient context, or (b) the full workflow is cheap enough to re-run entirely.\n- Every loop needs: an explicit exit condition (not vibes), a bounded maxIterations, and a decision step with outputContract.\n- Sensible defaults: validation ≈ 2-3, review/refinement ≈ 2, user-feedback ≈ 2-3 with confirmation gate. Go higher only with explicit justification in your notes.\n3. **Confirmation gates**: where does the user genuinely need to approve before proceeding? Don't add confirmations as ceremony.\n4. **Delegation and reuse**: for each phase, decide between direct execution, routine delegation, template injection, or no special mechanism. If a routine or template is not used, say why not. Keep delegation bounded and keep ownership with the main agent.\n5. **Prompt composition**: will any steps need promptFragments for rigor-mode branching? Will any steps share enough structure to use templates?\n6. **Extension points**: are there customizable slots that projects might want to override (e.g., a verification routine, a review routine)?\n7. **References**: should the authored workflow declare its own references to external docs?\n8. **Artifacts**: what does each step produce? Which artifact is canonical for which concern?\n9. **metaGuidance**: what persistent behavioral rules should the agent see on start and resume?\n\nIf `authoringMode = modernize_existing`, also decide:\n- should this workflow be preserved mostly in place, restructured selectively, or rewritten more substantially?\n- which existing steps, loops, references, or metaGuidance should stay because they still fit the workflow's purpose?\n- which legacy patterns or repetitive sections should be removed or reshaped?\n- whether the file path should stay the same or whether a new variant/file is genuinely warranted\n- how each major old phase or behavior maps to the new workflow: `keep`, `merge`, `remove`, or `replace`\n\nFor `modernize_existing`, create a compact legacy mapping in your notes. For each major old phase or behavior, record:\n- source step or behavior\n- disposition: `keep` / `merge` / `remove` / `replace`\n- rationale\n- destination in the new workflow, if any\n\nFor routine and template decisions, create a compact audit in your notes. For each meaningful phase or concern, record:\n- chosen mechanism: direct / routine / template / none\n- why it helps or why it would be overkill\n- the ownership boundary that stays with the main agent\n\nWrite the shape as a structured outline in your notes. Include:\n- Phase list with titles and one-line goals\n- Which phases loop and why\n- Which phases have confirmation gates and why\n- Context variables that flow between phases\n- Artifact ownership (which artifact is canonical for what)\n- for `modernize_existing`: whether the plan is preserve-in-place, restructure, or rewrite-biased and why\n\nDon't write JSON yet.\n\nCapture:\n- `workflowOutline`\n- `loopDesign`\n- `confirmationDesign`\n- `delegationDesign`\n- `artifactPlan`\n- `contextModel` (the context variables the workflow will use and where they're set)\n- `voiceStrategy` (domain vocabulary, authority posture: directive/collaborative/supervisory, density calibration)\n- `routineAudit`\n- `delegationBoundaries`\n- `templateInjectionPlan`\n- `modernizationStrategy` (for `modernize_existing`: preserve_in_place / restructure / rewrite, otherwise empty)\n- `legacyMapping` (for `modernize_existing`, otherwise empty)\n- `behaviorPreservationNotes` (for `modernize_existing`, otherwise empty)",
101
101
  "requireConfirmation": {
102
102
  "or": [
103
103
  { "var": "workflowComplexity", "not_equals": "Simple" },
@@ -169,7 +169,7 @@
169
169
  {
170
170
  "id": "phase-4-review",
171
171
  "title": "Phase 4: Method Review",
172
- "prompt": "The workflow is valid. Now check whether it's actually good.\n\nScore each dimension 0-2 with one sentence of evidence:\n\n- `voiceClarity`: 0 = prompts are direct user-voiced asks in the workflow's domain vocabulary, 1 = mostly user-voiced but borrows vocabulary from other domains or has middleware narration, 2 = reads like system documentation or sounds like a different domain\n- `ceremonyLevel`: 0 = confirmations only at real decision points, 1 = one or two unnecessary gates, 2 = over-asks the user or adds routine ceremony\n- `loopSoundness`: 0 = loops have explicit exit rules, bounded iterations, and real decision steps, 1 = minor issues with exit clarity, 2 = vibes-only exit conditions or unbounded loops (score 0 if no loops)\n- `delegationBoundedness`: 0 = delegation is bounded and explicit or absent, 1 = one delegation could be tighter, 2 = open-ended or ownership-transferring delegation (score 0 if no delegation)\n- `legacyPatterns`: 0 = no legacy anti-patterns, 1 = minor legacy residue, 2 = pseudo-DSL, learning paths, satisfaction loops, or regex-as-gate present\n- `artifactClarity`: 0 = clear what each artifact is for and which is canonical, 1 = mostly clear, 2 = ambiguous artifact ownership\n- `modeFit`: 0 = the workflow fits the selected `authoringMode`, 1 = minor creation/modernization mismatch remains, 2 = the workflow still reads like the wrong mode entirely\n\nIf the total score is 0-3: the workflow is ready.\nIf the total score is 4-6: fix the worst dimensions before proceeding.\nIf the total score is 7+: this needs significant rework. Fix the worst dimensions here, re-validate, and record what you would change if you could redraft from scratch.\n\nIf `authoringMode = modernize_existing`, check explicitly:\n- does the updated workflow preserve the right purpose?\n- did you remove legacy structure without rewriting valuable behavior away?\n- do any prompts, captures, or handoff notes still assume this was a brand-new workflow?\n\nFix any issues directly in the workflow file. Re-run validation if you changed structure.\n\nCapture:\n- `reviewScores`\n- `reviewPassed`\n- `fixesApplied`",
172
+ "prompt": "The workflow is valid. Now check whether it's actually good.\n\nScore each dimension 0-2 with one sentence of evidence:\n\n- `voiceClarity`: 0 = prompts are direct user-voiced asks in the workflow's domain vocabulary, 1 = mostly user-voiced but borrows vocabulary from other domains or has middleware narration, 2 = reads like system documentation or sounds like a different domain\n- `ceremonyLevel`: 0 = confirmations only at real decision points, 1 = one or two unnecessary gates, 2 = over-asks the user or adds routine ceremony\n- `loopSoundness`: 0 = loops have explicit exit rules, bounded iterations, and real decision steps, 1 = minor issues with exit clarity, 2 = vibes-only exit conditions or unbounded loops (score 0 if no loops)\n- `delegationBoundedness`: 0 = delegation is bounded and explicit or absent, 1 = one delegation could be tighter or a good routine/template opportunity was missed, 2 = open-ended or ownership-transferring delegation, or routine/template choices are unjustified (score 0 if no delegation and no reuse need exists)\n- `legacyPatterns`: 0 = no legacy anti-patterns, 1 = minor legacy residue, 2 = pseudo-DSL, learning paths, satisfaction loops, or regex-as-gate present\n- `artifactClarity`: 0 = clear what each artifact is for and which is canonical, 1 = mostly clear, 2 = ambiguous artifact ownership\n- `modeFit`: 0 = the workflow fits the selected `authoringMode`, 1 = minor creation/modernization mismatch remains, 2 = the workflow still reads like the wrong mode entirely\n- `modernizationDiscipline`: 0 = valuable behavior was preserved and legacy structure was removed cleanly, 1 = minor mismatch or over/under-preservation, 2 = either valuable behavior was lost or legacy structure still dominates (score 0 for `create` mode)\n\nIf the total score is 0-3: the workflow is ready.\nIf the total score is 4-6: fix the worst dimensions before proceeding.\nIf the total score is 7+: this needs significant rework. Fix the worst dimensions here, re-validate, and record what you would change if you could redraft from scratch.\n\nIf `authoringMode = modernize_existing`, check explicitly:\n- does the updated workflow preserve the right purpose?\n- did you remove legacy structure without rewriting valuable behavior away?\n- does the final workflow still align with `primaryBaseline` and `patternsToBorrow` without copying domain language?\n- does the final workflow respect the `legacyMapping`, especially for anything marked keep or merge?\n- do the routine/template choices still match the `routineAudit` and stay bounded?\n- do any prompts, captures, or handoff notes still assume this was a brand-new workflow?\n\nFix any issues directly in the workflow file. Re-run validation if you changed structure.\n\nCapture:\n- `reviewScores`\n- `reviewPassed`\n- `fixesApplied`",
173
173
  "promptFragments": [
174
174
  {
175
175
  "id": "phase-4-quick-skip",