@exaudeus/workrail 3.19.0 → 3.20.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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>WorkRail Console</title>
7
- <script type="module" crossorigin src="/console/assets/index-QhCFuxQV.js"></script>
8
- <link rel="stylesheet" crossorigin href="/console/assets/index-ibLhWBmX.css">
7
+ <script type="module" crossorigin src="/console/assets/index-De5T1Dpm.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/console/assets/index-B8NW82uK.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -369,16 +369,16 @@
369
369
  "sha256": "5fe866e54f796975dec5d8ba9983aefd86074db212d3fccd64eed04bc9f0b3da",
370
370
  "bytes": 8011
371
371
  },
372
- "console/assets/index-QhCFuxQV.js": {
373
- "sha256": "0096a86267fdf969abc3e3976ca44b3d3447271c7394345c8d6c27c084e6412c",
374
- "bytes": 719937
372
+ "console/assets/index-B8NW82uK.css": {
373
+ "sha256": "8588d6702df899c724f5abce3e45dd4a6f7ee06bf2b280cb3909f9cabcc0a32c",
374
+ "bytes": 58801
375
375
  },
376
- "console/assets/index-ibLhWBmX.css": {
377
- "sha256": "346e55635d4d3dc2836dae83edb8563872721bf4b0e7e1ecf47fb9603424c206",
378
- "bytes": 58826
376
+ "console/assets/index-De5T1Dpm.js": {
377
+ "sha256": "bad5fc857501a1253c800b6863e95cc854f3c8a52fea80959306ba32e15fd907",
378
+ "bytes": 721395
379
379
  },
380
380
  "console/index.html": {
381
- "sha256": "4232baa5128b860125a649d8e135da858f07290bbb9da04861945649b53ac85d",
381
+ "sha256": "3165437d9c2abc907ab1333dde87b0064e7c8fba62c3ffd2567e8bc02e75329b",
382
382
  "bytes": 417
383
383
  },
384
384
  "core/error-handler.d.ts": {
@@ -734,12 +734,12 @@
734
734
  "bytes": 2785
735
735
  },
736
736
  "mcp/handlers/shared/request-workflow-reader.d.ts": {
737
- "sha256": "342f82d59945aa043e7edf5ae0566d7790cdeec223932c59e86c38fc6322155e",
738
- "bytes": 1919
737
+ "sha256": "94eae7516f1ca0aef0c9995d0fd41fc206abb001970cebcac35b912624341d0a",
738
+ "bytes": 2103
739
739
  },
740
740
  "mcp/handlers/shared/request-workflow-reader.js": {
741
- "sha256": "a32e240ffc984018b56c6e074c894e079d8511dd2fb7a4f36898e32e2b0e62e6",
742
- "bytes": 10418
741
+ "sha256": "cfd2946ec9deb52c21611b21fc07859f35edaaf2c85d15743833380352dedf6f",
742
+ "bytes": 11702
743
743
  },
744
744
  "mcp/handlers/shared/with-timeout.d.ts": {
745
745
  "sha256": "31ca3db008cb5cd439e0d1132bc4b29be0900c403c452931e3a24a50e45beb54",
@@ -754,8 +754,16 @@
754
754
  "bytes": 1772
755
755
  },
756
756
  "mcp/handlers/shared/workflow-source-visibility.js": {
757
- "sha256": "46a1df4b1effa2c16da9d4beaff8081765a04700a4defc23a8a45373f0f7091a",
758
- "bytes": 4600
757
+ "sha256": "59f5b6f50a46a708c19649ab6dc0c90f21d9d4f151b52f454dc38a2dad344717",
758
+ "bytes": 4526
759
+ },
760
+ "mcp/handlers/shared/workspace-path-utils.d.ts": {
761
+ "sha256": "c660cf0605f3d23c2826d69c5ba47aea4866ab929a94cb5cdde882cedde047ab",
762
+ "bytes": 169
763
+ },
764
+ "mcp/handlers/shared/workspace-path-utils.js": {
765
+ "sha256": "c375c508fb830dc87b930eaeb99a1443b075c2d3f4f323875c3fb56107f82a05",
766
+ "bytes": 1261
759
767
  },
760
768
  "mcp/handlers/v2-advance-core.d.ts": {
761
769
  "sha256": "0a07ea15caf7b0a3123d3b0b44c2b85392d090dd140bf82370fd0e84d1aa5b0e",
@@ -1094,20 +1102,20 @@
1094
1102
  "bytes": 3534
1095
1103
  },
1096
1104
  "mcp/transports/shutdown-hooks.d.ts": {
1097
- "sha256": "956a334d06ba7b967992e1ebfef67e2bac1e40cf3c1c151684d5aa7a3166f64e",
1098
- "bytes": 334
1105
+ "sha256": "0a0e638aa9b1ca714e9c9efe9bcb9b147ef802f05c71ba6be481d59d3c45c800",
1106
+ "bytes": 502
1099
1107
  },
1100
1108
  "mcp/transports/shutdown-hooks.js": {
1101
- "sha256": "5f5e1411db09d05588731bbaa08fc42db48a9023e87043e95aa01cd8ad426191",
1102
- "bytes": 1937
1109
+ "sha256": "1bbf5e8203adc7172cb8a3a4cf987ded4e747a993cc6c9346651ff5f192b1abb",
1110
+ "bytes": 2582
1103
1111
  },
1104
1112
  "mcp/transports/stdio-entry.d.ts": {
1105
1113
  "sha256": "4ced3c9e00ef67555781dea74315290eea8a9dbffd38155bc00c3fb07b0c1794",
1106
1114
  "bytes": 59
1107
1115
  },
1108
1116
  "mcp/transports/stdio-entry.js": {
1109
- "sha256": "aa2f863ee779a0f31c729e54d929dfd5cf416424f63044f8d0eda956bdcc9b9e",
1110
- "bytes": 3702
1117
+ "sha256": "d934d0a7bb5a5d4331ce424aa754c063dac0a2f914d6b9d5f75489740d325b0a",
1118
+ "bytes": 4039
1111
1119
  },
1112
1120
  "mcp/transports/transport-mode.d.ts": {
1113
1121
  "sha256": "1c59128ab0174bd2a113fff17521e6339ca367f2b8980c2f2c164ec393c10518",
@@ -2270,12 +2278,12 @@
2270
2278
  "bytes": 7393
2271
2279
  },
2272
2280
  "v2/infra/local/session-lock/index.d.ts": {
2273
- "sha256": "3b381b372da6039df678dfddf377600547e1067cf921cc1b4cdf578e5df0a1bb",
2274
- "bytes": 853
2281
+ "sha256": "e180a4202587af03e09dc82e2d8557ee2d3139ac6c5f1edee9a277a7032b9986",
2282
+ "bytes": 883
2275
2283
  },
2276
2284
  "v2/infra/local/session-lock/index.js": {
2277
- "sha256": "3ad09372cb30d959016cc2a69e91de7c7f3a251c22048a917d9862fd0d8a77f9",
2278
- "bytes": 1700
2285
+ "sha256": "a1cd3eaea35faa014922fe1502a9e029a58528421586c67ab44dc7df0183a0fe",
2286
+ "bytes": 2793
2279
2287
  },
2280
2288
  "v2/infra/local/session-store/index.d.ts": {
2281
2289
  "sha256": "33b18ccce92374f8e444a61c63238634087d222f09e834b49dd2ea38d2be3796",
@@ -31,6 +31,8 @@ export interface WorkflowReaderForRequestResult {
31
31
  readonly stalePaths: readonly string[];
32
32
  readonly managedSourceRecords: readonly ManagedSourceRecordV2[];
33
33
  readonly staleManagedRecords: readonly ManagedSourceRecordV2[];
34
+ readonly excludedByScope: readonly string[];
34
35
  readonly managedStoreError?: string;
35
36
  }
37
+ export declare function filterRememberedRootsForWorkspace(allRoots: readonly string[], workspace: string): Promise<readonly string[]>;
36
38
  export declare function createWorkflowReaderForRequest(options: RequestWorkflowReaderOptions): Promise<WorkflowReaderForRequestResult>;
@@ -8,6 +8,7 @@ exports.hasRequestWorkspaceSignal = hasRequestWorkspaceSignal;
8
8
  exports.resolveRequestWorkspaceDirectory = resolveRequestWorkspaceDirectory;
9
9
  exports.toProjectWorkflowDirectory = toProjectWorkflowDirectory;
10
10
  exports.discoverRootedWorkflowDirectories = discoverRootedWorkflowDirectories;
11
+ exports.filterRememberedRootsForWorkspace = filterRememberedRootsForWorkspace;
11
12
  exports.createWorkflowReaderForRequest = createWorkflowReaderForRequest;
12
13
  const promises_1 = __importDefault(require("fs/promises"));
13
14
  const path_1 = __importDefault(require("path"));
@@ -15,6 +16,7 @@ const url_1 = require("url");
15
16
  const enhanced_multi_source_workflow_storage_js_1 = require("../../../infrastructure/storage/enhanced-multi-source-workflow-storage.js");
16
17
  const schema_validating_workflow_storage_js_1 = require("../../../infrastructure/storage/schema-validating-workflow-storage.js");
17
18
  const with_timeout_js_1 = require("./with-timeout.js");
19
+ const workspace_path_utils_js_1 = require("./workspace-path-utils.js");
18
20
  const SKIP_DIRS = new Set([
19
21
  '.git', 'node_modules',
20
22
  'build', 'dist', 'out', 'target',
@@ -99,10 +101,26 @@ async function _doWalk(cacheKey, roots, now) {
99
101
  walkCache.set(cacheKey, { result, expiresAt: now + WALK_CACHE_TTL_MS });
100
102
  return result;
101
103
  }
104
+ async function filterRememberedRootsForWorkspace(allRoots, workspace) {
105
+ const ancestorRoots = allRoots.filter((r) => (0, workspace_path_utils_js_1.isWorkspaceAncestor)(r, workspace));
106
+ const nonAncestorRoots = allRoots.filter((r) => !(0, workspace_path_utils_js_1.isWorkspaceAncestor)(r, workspace));
107
+ let siblingRoots = [];
108
+ if (nonAncestorRoots.length > 0) {
109
+ const workspaceCommonDir = await (0, workspace_path_utils_js_1.getGitCommonDir)(workspace);
110
+ if (workspaceCommonDir !== null) {
111
+ const commonDirResults = await Promise.all(nonAncestorRoots.map((r) => (0, workspace_path_utils_js_1.getGitCommonDir)(r)));
112
+ siblingRoots = nonAncestorRoots.filter((_, i) => commonDirResults[i] === workspaceCommonDir);
113
+ }
114
+ }
115
+ return [...ancestorRoots, ...siblingRoots];
116
+ }
102
117
  async function createWorkflowReaderForRequest(options) {
103
118
  const workspaceDirectory = resolveRequestWorkspaceDirectory(options);
104
119
  const projectWorkflowDirectory = toProjectWorkflowDirectory(workspaceDirectory);
105
- const rememberedRoots = await listRememberedRoots(options.rememberedRootsStore);
120
+ const allRememberedRoots = await listRememberedRoots(options.rememberedRootsStore);
121
+ const resolvedWorkspace = path_1.default.resolve(workspaceDirectory);
122
+ const rememberedRoots = await filterRememberedRootsForWorkspace(allRememberedRoots, resolvedWorkspace);
123
+ const excludedByScope = allRememberedRoots.filter((root) => !rememberedRoots.includes(root));
106
124
  let discoveryResult;
107
125
  try {
108
126
  discoveryResult = await (0, with_timeout_js_1.withTimeout)(discoverRootedWorkflowDirectories(rememberedRoots), DISCOVERY_TIMEOUT_MS, 'workflow_root_discovery');
@@ -159,6 +177,7 @@ async function createWorkflowReaderForRequest(options) {
159
177
  stalePaths: allStalePaths,
160
178
  managedSourceRecords: activeManagedRecords,
161
179
  staleManagedRecords,
180
+ excludedByScope,
162
181
  ...(managedStoreError !== undefined ? { managedStoreError } : {}),
163
182
  };
164
183
  }
@@ -8,6 +8,7 @@ exports.detectWorkflowMigrationGuidance = detectWorkflowMigrationGuidance;
8
8
  exports.isCompositeWorkflowReader = isCompositeWorkflowReader;
9
9
  exports.deriveGroupLabel = deriveGroupLabel;
10
10
  const path_1 = __importDefault(require("path"));
11
+ const workspace_path_utils_js_1 = require("./workspace-path-utils.js");
11
12
  function toWorkflowVisibility(workflow, rememberedRoots, options = {}) {
12
13
  const source = {
13
14
  kind: workflow.source.kind,
@@ -70,10 +71,7 @@ function deriveRootedSharingContext(workflow, rememberedRoots) {
70
71
  const sourcePath = path_1.default.resolve(workflow.source.directoryPath);
71
72
  for (const record of rememberedRoots) {
72
73
  const rootPath = path_1.default.resolve(record.path);
73
- const relative = path_1.default.relative(rootPath, sourcePath);
74
- const isUnderRoot = relative.length === 0 ||
75
- (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
76
- if (!isUnderRoot)
74
+ if (!(0, workspace_path_utils_js_1.isWorkspaceAncestor)(rootPath, sourcePath))
77
75
  continue;
78
76
  return {
79
77
  kind: 'remembered_root',
@@ -0,0 +1,2 @@
1
+ export declare function isWorkspaceAncestor(root: string, workspace: string): boolean;
2
+ export declare function getGitCommonDir(dirPath: string): Promise<string | null>;
@@ -0,0 +1,29 @@
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.isWorkspaceAncestor = isWorkspaceAncestor;
7
+ exports.getGitCommonDir = getGitCommonDir;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const child_process_1 = require("child_process");
11
+ const util_1 = require("util");
12
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
13
+ function isWorkspaceAncestor(root, workspace) {
14
+ const rel = path_1.default.relative(path_1.default.resolve(root), path_1.default.resolve(workspace));
15
+ return rel.length === 0 || (!rel.startsWith('..') && !path_1.default.isAbsolute(rel));
16
+ }
17
+ async function getGitCommonDir(dirPath) {
18
+ try {
19
+ const { stdout } = await execFileAsync('git', ['-C', dirPath, 'rev-parse', '--git-common-dir'], { encoding: 'utf-8', timeout: 500 });
20
+ const raw = stdout.trim();
21
+ if (!raw)
22
+ return null;
23
+ const resolved = path_1.default.resolve(dirPath, raw);
24
+ return await promises_1.default.realpath(resolved);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
@@ -2,6 +2,10 @@ export interface ShutdownHookOptions {
2
2
  readonly onBeforeTerminate: () => Promise<void>;
3
3
  }
4
4
  export declare function wireShutdownHooks(opts: ShutdownHookOptions): void;
5
+ export interface StdoutShutdownOptions {
6
+ readonly stdout?: NodeJS.WritableStream;
7
+ }
8
+ export declare function wireStdoutShutdown(opts?: StdoutShutdownOptions): void;
5
9
  export interface StdinShutdownOptions {
6
10
  readonly stdin?: NodeJS.ReadableStream;
7
11
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.wireShutdownHooks = wireShutdownHooks;
4
+ exports.wireStdoutShutdown = wireStdoutShutdown;
4
5
  exports.wireStdinShutdown = wireStdinShutdown;
5
6
  const container_js_1 = require("../../di/container.js");
6
7
  const tokens_js_1 = require("../../di/tokens.js");
@@ -29,6 +30,20 @@ function wireShutdownHooks(opts) {
29
30
  })();
30
31
  });
31
32
  }
33
+ function wireStdoutShutdown(opts) {
34
+ const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
35
+ const stdout = opts?.stdout ?? process.stdout;
36
+ stdout.on('error', (err) => {
37
+ const code = err.code;
38
+ if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED') {
39
+ console.error('[MCP] stdout pipe broken (client disconnected), initiating shutdown');
40
+ }
41
+ else {
42
+ console.error('[MCP] stdout error:', err);
43
+ }
44
+ shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' });
45
+ });
46
+ }
32
47
  function wireStdinShutdown(opts) {
33
48
  const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
34
49
  const stdin = opts?.stdin ?? process.stdin;
@@ -46,6 +46,13 @@ async function fetchInitialRootsWithTimeout(server) {
46
46
  ]);
47
47
  }
48
48
  async function startStdioServer() {
49
+ process.on('uncaughtException', (err) => {
50
+ console.error('[MCP] Uncaught exception -- process will exit:', err);
51
+ });
52
+ process.on('unhandledRejection', (reason) => {
53
+ console.error('[MCP] Unhandled promise rejection:', reason);
54
+ process.exit(1);
55
+ });
49
56
  const { server, ctx, rootsManager } = await (0, server_js_1.composeServer)();
50
57
  const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
51
58
  const { RootsListChangedNotificationSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
@@ -59,6 +66,7 @@ async function startStdioServer() {
59
66
  console.error('[Roots] Failed to fetch updated roots after change notification');
60
67
  }
61
68
  });
69
+ (0, shutdown_hooks_js_1.wireStdoutShutdown)();
62
70
  const transport = new StdioServerTransport();
63
71
  await server.connect(transport);
64
72
  console.error('[Transport] WorkRail MCP Server running on stdio');
@@ -9,6 +9,7 @@ export declare class LocalSessionLockV2 implements SessionLockPortV2 {
9
9
  private readonly fs;
10
10
  private readonly clock;
11
11
  constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, clock: TimeClockPortV2);
12
+ private clearIfStaleLock;
12
13
  acquire(sessionId: SessionId): ResultAsync<SessionLockHandleV2, SessionLockError>;
13
14
  release(handle: SessionLockHandleV2): ResultAsync<void, SessionLockError>;
14
15
  }
@@ -1,12 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalSessionLockV2 = void 0;
4
+ const neverthrow_1 = require("neverthrow");
4
5
  class LocalSessionLockV2 {
5
6
  constructor(dataDir, fs, clock) {
6
7
  this.dataDir = dataDir;
7
8
  this.fs = fs;
8
9
  this.clock = clock;
9
10
  }
11
+ clearIfStaleLock(lockPath) {
12
+ return this.fs
13
+ .readFileUtf8(lockPath)
14
+ .map((content) => {
15
+ try {
16
+ const data = JSON.parse(content);
17
+ const pid = typeof data.pid === 'number' ? data.pid : null;
18
+ if (pid === null)
19
+ return false;
20
+ try {
21
+ process.kill(pid, 0);
22
+ return false;
23
+ }
24
+ catch (e) {
25
+ return e.code === 'ESRCH';
26
+ }
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ })
32
+ .andThen((isStale) => {
33
+ if (!isStale)
34
+ return (0, neverthrow_1.okAsync)(undefined);
35
+ console.error(`[SessionLock] Removing stale lock at ${lockPath} (process no longer alive)`);
36
+ return this.fs.unlink(lockPath);
37
+ })
38
+ .orElse(() => (0, neverthrow_1.okAsync)(undefined));
39
+ }
10
40
  acquire(sessionId) {
11
41
  const sessionDir = this.dataDir.sessionDir(sessionId);
12
42
  const lockPath = this.dataDir.sessionLockPath(sessionId);
@@ -23,6 +53,7 @@ class LocalSessionLockV2 {
23
53
  };
24
54
  return this.fs
25
55
  .mkdirp(sessionDir)
56
+ .andThen(() => this.clearIfStaleLock(lockPath))
26
57
  .andThen(() => this.fs.openExclusive(lockPath, new TextEncoder().encode(JSON.stringify({
27
58
  v: 1,
28
59
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.19.0",
3
+ "version": "3.20.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -47,6 +47,8 @@
47
47
  "validate:workflow-discovery": "node scripts/validate-workflow-discovery.js",
48
48
  "precommit": "npm run validate:registry",
49
49
  "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);}\"",
50
+ "dev:mcp": "pkill -f 'dist/mcp-server.js' 2>/dev/null; sleep 0.5; WORKRAIL_TRANSPORT=http WORKRAIL_ENABLE_SESSION_TOOLS=true node dist/mcp-server.js",
51
+ "dev:mcp:watch": "WORKRAIL_TRANSPORT=http WORKRAIL_ENABLE_SESSION_TOOLS=true nodemon --watch dist --ext js --delay 2 --exec 'node dist/mcp-server.js'",
50
52
  "web:dev": "npm run build && WORKRAIL_ENABLE_SESSION_TOOLS=true node dist/mcp-server.js",
51
53
  "web:ci": "WORKRAIL_ENABLE_SESSION_TOOLS=true node dist/mcp-server.js",
52
54
  "web:typecheck": "tsc -p tsconfig.web.json",
@@ -113,17 +115,20 @@
113
115
  "@semantic-release/exec": "^7.1.0",
114
116
  "@semantic-release/git": "^10.0.1",
115
117
  "@semantic-release/github": "^12.0.2",
118
+ "@testing-library/react": "^16.3.2",
119
+ "@testing-library/user-event": "^14.6.1",
116
120
  "@types/cors": "^2.8.19",
117
121
  "@types/express": "^5.0.3",
118
122
  "@types/node": "^20.19.9",
119
123
  "@types/semver": "^7.7.0",
120
124
  "@vitest/ui": "^3.2.4",
121
125
  "conventional-changelog-conventionalcommits": "^9.1.0",
122
- "fast-check": "^3.15.0",
126
+ "fast-check": "^4.6.0",
123
127
  "happy-dom": "^20.0.11",
124
- "jsdom": "^27.0.0",
128
+ "jsdom": "^29.0.2",
125
129
  "lit": "^3.3.1",
126
130
  "node-fetch": "^3.3.2",
131
+ "nodemon": "^3.0.0",
127
132
  "semantic-release": "^25.0.3",
128
133
  "ts-morph": "^27.0.2",
129
134
  "typescript": "^5.9.3",
@@ -170,12 +170,11 @@
170
170
  "State what result the authored workflow should reliably produce for its user.",
171
171
  "List the criteria that would make the workflow feel genuinely satisfying in practice.",
172
172
  "Name the biggest likely failure mode and the most dangerous false-confidence mode.",
173
- "State what would make the workflow technically correct but still disappointing.",
174
- "Build a success-criteria-to-mechanism map: for each item in userSatisfactionCriteria, name the specific step(s) that enforce it and the enforcement mechanism (gate, self-audit, second pass, example contrast, rubric). Any criterion with no named mechanism is a gap -- either add a mechanism to the planned architecture or flag it as a known weakness to address in Phase 3."
173
+ "State what would make the workflow technically correct but still disappointing."
175
174
  ],
176
175
  "outputRequired": {
177
- "notesMarkdown": "Effectiveness target, satisfaction criteria, failure modes, false-confidence risks, and the success-criteria-to-mechanism map with any gaps identified.",
178
- "context": "Capture effectivenessTarget, userSatisfactionCriteria, primaryFailureMode, dangerousFalseConfidenceModes, likelyWeakOutcomeModes, trustRisk, and successCriteriaToMechanismMap."
176
+ "notesMarkdown": "Effectiveness target, satisfaction criteria, failure modes, and false-confidence risks.",
177
+ "context": "Capture effectivenessTarget, userSatisfactionCriteria, primaryFailureMode, dangerousFalseConfidenceModes, likelyWeakOutcomeModes, and trustRisk."
179
178
  },
180
179
  "verify": [
181
180
  "The authored workflow now has a clear outcome bar, not just an authoring bar."
@@ -493,12 +492,11 @@
493
492
  "Reviewer-family or validator output is evidence, not authority."
494
493
  ],
495
494
  "procedure": [
496
- "Score these dimensions 0-2 with one sentence of evidence each: `voiceClarity`, `ceremonyLevel`, `loopSoundness`, `delegationBoundedness`, `artifactClarity`, `taskEffectiveness`, `falseConfidenceResistance`, `stateMinimality`, `coverageSharpness`, `domainFit`, `handoffUtility`, `rigorAdaptability` (0 = adapts to complexity/rigor levels, 2 = single-weight), `enforcementStrength` (0 = behavioral rules have structural teeth; 2 = important rules are prose-only with no enforcement mechanism), `outputQualityMechanisms` (0 = each key generative step has a concrete enforcement mechanism for output quality such as a self-audit, second pass, example contrast, or rubric; 1 = some generative steps rely only on metaGuidance or prose rules; 2 = generative steps have no quality enforcement beyond prompt wording), and `modernizationDiscipline` (0 = every valueInventory item preserved, equivalently replaced with justification, or dropped with justification; 2 = items missing or replaced with weaker versions without justification -- score 0 for create mode).",
497
- "For each generative step (any step whose output is judged on content quality rather than structural correctness), run an adversarial trace: what does a lazy or average agent do here, and does the prompt prevent it? A step where the lazy path produces a plausible-looking but shallow result scores poorly on `outputQualityMechanisms`. Check each such step against the successCriteriaToMechanismMap from Phase 1 -- any criterion whose mechanism lives in this step must actually be enforced here.",
495
+ "Score these dimensions 0-2 with one sentence of evidence each: `voiceClarity`, `ceremonyLevel`, `loopSoundness`, `delegationBoundedness`, `artifactClarity`, `taskEffectiveness`, `falseConfidenceResistance`, `stateMinimality`, `coverageSharpness`, `domainFit`, `handoffUtility`, `rigorAdaptability` (0 = adapts to complexity/rigor levels, 2 = single-weight), `enforcementStrength` (0 = behavioral rules have structural teeth; 2 = important rules are prose-only with no enforcement mechanism), and `modernizationDiscipline` (0 = every valueInventory item preserved, equivalently replaced with justification, or dropped with justification; 2 = items missing or replaced with weaker versions without justification -- score 0 for create mode).",
498
496
  "If delegation is available and rigor is THOROUGH, run an adversarial review bundle with these lenses: `engine_native_reviewer`, `task_effectiveness_reviewer`, `state_economy_reviewer`, `false_confidence_reviewer`, `domain_fit_reviewer`, and `maintainer_reviewer`.",
499
497
  "Synthesize what the review confirmed, what it challenged, and what changed your mind.",
500
498
  "When scoring `falseConfidenceResistance`, explicitly check: do the workflow's quality gates protect edge cases and degraded paths, or only the happy path? A workflow that passes its own checks on ideal input but fails silently on minimal or unexpected input scores 2.",
501
- "Set hard-gate failures whenever any of these are materially weak: `taskEffectiveness`, `falseConfidenceResistance`, `stateMinimality`, `coverageSharpness`, `domainFit`, `handoffUtility`, or `outputQualityMechanisms`.",
499
+ "Set hard-gate failures whenever any of these are materially weak: `taskEffectiveness`, `falseConfidenceResistance`, `stateMinimality`, `coverageSharpness`, `domainFit`, or `handoffUtility`.",
502
500
  "Set `authoringIntegrityPassed = true` only if structural and authoring-quality dimensions are all acceptable. Set `outcomeEffectivenessPassed = true` only if the workflow is likely to achieve satisfying results for the user."
503
501
  ],
504
502
  "outputRequired": {
@@ -550,7 +548,6 @@
550
548
  { "type": "contains", "value": "handoffUtility", "message": "Review must score handoffUtility" },
551
549
  { "type": "contains", "value": "rigorAdaptability", "message": "Review must score rigorAdaptability" },
552
550
  { "type": "contains", "value": "enforcementStrength", "message": "Review must score enforcementStrength" },
553
- { "type": "contains", "value": "outputQualityMechanisms", "message": "Review must score outputQualityMechanisms" },
554
551
  {
555
552
  "type": "contains",
556
553
  "value": "modernizationDiscipline",
@@ -668,31 +665,6 @@
668
665
  },
669
666
  "notesOptional": true,
670
667
  "requireConfirmation": false
671
- },
672
- {
673
- "id": "phase-8-process-retrospective",
674
- "title": "Phase 8: Process Retrospective",
675
- "promptBlocks": {
676
- "goal": "While this run is still fresh, identify gaps in the workflow-for-workflows process itself -- not in the authored workflow.",
677
- "constraints": [
678
- "This step is about the authoring process, not the authored workflow. Do not summarize the workflow again.",
679
- "Be honest. If the process caught everything, say so. If it missed something, say specifically where and why."
680
- ],
681
- "procedure": [
682
- "Look back at the full run. Were any weaknesses in the authored workflow only identified post-hoc -- after the quality gate loop, by the user, or by a later reviewer?",
683
- "For each weakness identified late: name the Phase where it should have been caught, explain why the current step failed to surface it, and propose a specific change to that step's procedure or scoring rubric that would catch it on the next run.",
684
- "Check the successCriteriaToMechanismMap from Phase 1 against the final workflow: did every criterion end up with a concrete enforcement mechanism? Any criterion that is still prose-only in the final file is a gap in the Phase 1 or Phase 3 process.",
685
- "If nothing was missed and the process caught all meaningful issues in-band, say so explicitly and explain what worked."
686
- ],
687
- "outputRequired": {
688
- "notesMarkdown": "Process gaps found (or confirmed absent), where they should have been caught, and concrete proposed changes to workflow-for-workflows. This output is raw material for improving this workflow.",
689
- "context": "Capture processGaps and suggestedProcessImprovements."
690
- },
691
- "verify": [
692
- "The retrospective identifies specific step-level changes, not vague general improvements."
693
- ]
694
- },
695
- "requireConfirmation": false
696
668
  }
697
669
  ],
698
670
  "validatedAgainstSpecVersion": 3