@exaudeus/workrail 3.8.2 → 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 (30) 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
@@ -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.2",
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": {