@exaudeus/workrail 3.14.0 → 3.15.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 (62) hide show
  1. package/dist/application/services/validation-engine.js +4 -9
  2. package/dist/application/services/workflow-compiler.js +4 -6
  3. package/dist/console/assets/index-BZYIjrzJ.js +28 -0
  4. package/dist/console/assets/index-OLCKbDdm.css +1 -0
  5. package/dist/console/index.html +2 -2
  6. package/dist/engine/engine-factory.js +2 -2
  7. package/dist/engine/types.d.ts +1 -1
  8. package/dist/manifest.json +63 -63
  9. package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +5 -0
  10. package/dist/mcp/handlers/shared/request-workflow-reader.js +47 -2
  11. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +1 -1
  12. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +4 -5
  13. package/dist/mcp/handlers/v2-advance-core/index.js +1 -1
  14. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +1 -1
  15. package/dist/mcp/handlers/v2-execution/start.d.ts +1 -0
  16. package/dist/mcp/handlers/v2-execution/start.js +20 -1
  17. package/dist/mcp/handlers/v2-workflow.d.ts +20 -0
  18. package/dist/mcp/handlers/v2-workflow.js +119 -10
  19. package/dist/mcp/output-schemas.d.ts +109 -8
  20. package/dist/mcp/output-schemas.js +31 -11
  21. package/dist/mcp/server.js +48 -1
  22. package/dist/mcp/tool-descriptions.js +17 -9
  23. package/dist/mcp/v2/tools.d.ts +6 -0
  24. package/dist/mcp/v2/tools.js +2 -0
  25. package/dist/mcp/workflow-protocol-contracts.js +5 -1
  26. package/dist/types/workflow-definition.d.ts +1 -2
  27. package/dist/v2/infra/local/workspace-anchor/index.js +4 -1
  28. package/dist/v2/usecases/console-routes.js +49 -1
  29. package/dist/v2/usecases/console-service.d.ts +1 -0
  30. package/dist/v2/usecases/console-service.js +4 -1
  31. package/dist/v2/usecases/console-types.d.ts +12 -0
  32. package/dist/v2/usecases/worktree-service.js +55 -7
  33. package/package.json +2 -2
  34. package/spec/authoring-spec.json +82 -1
  35. package/spec/workflow-tags.json +132 -0
  36. package/spec/workflow.schema.json +3 -11
  37. package/workflows/adaptive-ticket-creation.json +40 -22
  38. package/workflows/architecture-scalability-audit.json +65 -31
  39. package/workflows/bug-investigation.agentic.v2.json +36 -14
  40. package/workflows/coding-task-workflow-agentic.json +50 -38
  41. package/workflows/coding-task-workflow-agentic.lean.v2.json +124 -37
  42. package/workflows/coding-task-workflow-agentic.v2.json +90 -30
  43. package/workflows/cross-platform-code-conversion.v2.json +168 -48
  44. package/workflows/document-creation-workflow.json +47 -17
  45. package/workflows/documentation-update-workflow.json +8 -8
  46. package/workflows/intelligent-test-case-generation.json +2 -2
  47. package/workflows/learner-centered-course-workflow.json +267 -267
  48. package/workflows/mr-review-workflow.agentic.v2.json +81 -14
  49. package/workflows/personal-learning-materials-creation-branched.json +175 -175
  50. package/workflows/presentation-creation.json +159 -159
  51. package/workflows/production-readiness-audit.json +54 -15
  52. package/workflows/relocation-workflow-us.json +44 -35
  53. package/workflows/routines/tension-driven-design.json +1 -1
  54. package/workflows/scoped-documentation-workflow.json +25 -25
  55. package/workflows/test-artifact-loop-control.json +1 -2
  56. package/workflows/ui-ux-design-workflow.json +327 -0
  57. package/workflows/workflow-diagnose-environment.json +1 -1
  58. package/workflows/workflow-for-workflows.json +507 -484
  59. package/workflows/workflow-for-workflows.v2.json +43 -11
  60. package/workflows/wr.discovery.json +112 -30
  61. package/dist/console/assets/index-DW78t31j.css +0 -1
  62. package/dist/console/assets/index-EsSXrC_a.js +0 -28
@@ -48,12 +48,17 @@ This tool provides:
48
48
  - Field descriptions and validation rules
49
49
  - Examples of valid patterns and formats
50
50
  - Schema version and metadata information`,
51
- list_workflows: `Lists available workflows using WorkRail v2 (feature-flagged). Returns workflow metadata plus pinned snapshot hashes for deterministic execution.
51
+ list_workflows: `Lists available workflows. When a workflow exists for the user's request, following it means following the user's structured instructions.
52
52
 
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.
53
+ Discovery pattern (token-efficient do this):
54
+ - Preferred: read the workrail://tags MCP resource — tag definitions with no tool call.
55
+ - Or: call with no tags → returns tagSummary (~500 tokens) with when-to-use phrases per tag.
56
+ - Then: call with tags=["<tagId>"] → returns the filtered workflow list for that tag.
57
+ - Shortcut: if a workflow ID in tagSummary examples[] already matches, call start_workflow directly — skip the second list call.
58
+ - Multiple tags: if more than one tag fits, pick the most specific one.
54
59
 
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
- inspect_workflow: `Inspect a workflow structure before starting it (WorkRail v2, feature-flagged).
60
+ Always pass workspacePath so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.`,
61
+ inspect_workflow: `Inspect a workflow structure before starting it.
57
62
 
58
63
  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
64
 
@@ -139,14 +144,17 @@ Returns validation result with specific errors and suggestions if invalid.`,
139
144
  get_workflow_schema: `Get the workflow JSON schema for creating or editing workflows.
140
145
 
141
146
  Returns the complete schema definition including required fields, valid patterns, and constraints. Use this as reference when authoring workflow JSON.`,
142
- list_workflows: `List available workflows via WorkRail v2 (feature-flagged).
147
+ list_workflows: `Lists available workflows. When a workflow exists for the user's request, you MUST use it instead of improvising your own approach.
143
148
 
144
- Workflows are the user's pre-defined instructions for complex tasks. When a workflow exists for the user's request, you MUST use it instead of improvising your own approach.
145
-
146
- Returns stable workflow metadata and pinned snapshot hashes (workflowHash) for deterministic execution.
149
+ Discovery pattern (token-efficient do this):
150
+ - Preferred: read the workrail://tags MCP resource — tag definitions with no tool call.
151
+ - Or: call with no tags returns tagSummary (~500 tokens) with when-to-use phrases per tag.
152
+ - Then: call with tags=["<tagId>"] → returns the filtered workflow list for that tag.
153
+ - Shortcut: if a workflow ID in tagSummary examples[] already matches, call start_workflow directly — skip the second list call.
154
+ - Multiple tags: if more than one tag fits, pick the most specific one.
147
155
 
148
156
  Pass workspacePath on every call so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.`,
149
- inspect_workflow: `Inspect a workflow you are considering following (WorkRail v2, feature-flagged).
157
+ inspect_workflow: `Inspect a workflow you are considering following.
150
158
 
151
159
  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.
152
160
 
@@ -3,12 +3,15 @@ import type { ToolAnnotations } from '../tool-factory.js';
3
3
  export declare const V2ListWorkflowsInput: z.ZodObject<{
4
4
  workspacePath: z.ZodEffects<z.ZodString, string, string>;
5
5
  includeSources: z.ZodOptional<z.ZodBoolean>;
6
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
7
  }, "strip", z.ZodTypeAny, {
7
8
  workspacePath: string;
8
9
  includeSources?: boolean | undefined;
10
+ tags?: string[] | undefined;
9
11
  }, {
10
12
  workspacePath: string;
11
13
  includeSources?: boolean | undefined;
14
+ tags?: string[] | undefined;
12
15
  }>;
13
16
  export type V2ListWorkflowsInput = z.infer<typeof V2ListWorkflowsInput>;
14
17
  export declare const V2InspectWorkflowInput: z.ZodObject<{
@@ -28,12 +31,15 @@ export type V2InspectWorkflowInput = z.infer<typeof V2InspectWorkflowInput>;
28
31
  export declare const V2StartWorkflowInput: z.ZodObject<{
29
32
  workflowId: z.ZodString;
30
33
  workspacePath: z.ZodEffects<z.ZodString, string, string>;
34
+ goal: z.ZodString;
31
35
  }, "strip", z.ZodTypeAny, {
32
36
  workflowId: string;
33
37
  workspacePath: string;
38
+ goal: string;
34
39
  }, {
35
40
  workflowId: string;
36
41
  workspacePath: string;
42
+ goal: string;
37
43
  }>;
38
44
  export type V2StartWorkflowInput = z.infer<typeof V2StartWorkflowInput>;
39
45
  export declare const V2ContinueWorkflowInputShape: z.ZodObject<{
@@ -17,6 +17,7 @@ const optionalWorkspacePathField = workspacePathField.optional();
17
17
  exports.V2ListWorkflowsInput = zod_1.z.object({
18
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
  includeSources: zod_1.z.boolean().optional().describe('When true, includes a source catalog in the response showing where workflows come from (built-in, project-scoped, rooted-sharing, external), with effective and shadowed workflow counts per source. Omit or set false for the default workflow-list-only response.'),
20
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Filter by one or more domain tags (e.g. ["coding"], ["review_audit", "investigation"]). When present, returns the full workflow list for workflows matching any of the specified tags. When absent, returns a compact tagSummary instead of the full list — use this first to discover which tags exist, then call again with a specific tag. Valid tags: coding, review_audit, investigation, design, documentation, tickets, learning, routines, authoring.'),
20
21
  });
21
22
  exports.V2InspectWorkflowInput = zod_1.z.object({
22
23
  workflowId: zod_1.z.string().min(1).regex(/^([a-z0-9_-]+|[a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)$/, 'Workflow ID must be a valid legacy ID (e.g. my-workflow) or namespaced ID (e.g. wr.discovery)').describe('The workflow ID to inspect'),
@@ -26,6 +27,7 @@ exports.V2InspectWorkflowInput = zod_1.z.object({
26
27
  exports.V2StartWorkflowInput = zod_1.z.object({
27
28
  workflowId: zod_1.z.string().min(1).regex(/^([a-z0-9_-]+|[a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)$/, 'Workflow ID must be a valid legacy ID (e.g. my-workflow) or namespaced ID (e.g. wr.discovery)').describe('The workflow ID to start'),
28
29
  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 and to anchor the session to the correct repo for future resume_session discovery. Shared MCP servers cannot infer this safely.'),
30
+ goal: zod_1.z.string().min(1).describe('A short sentence describing what you are trying to accomplish (e.g. "implement OAuth refresh token rotation", "review PR #47 before merge", "investigate why the build fails on CI").'),
29
31
  });
30
32
  exports.V2ContinueWorkflowInputShape = zod_1.z.object({
31
33
  workspacePath: optionalWorkspacePathField,
@@ -46,7 +46,7 @@ function findAliasFieldConflicts(value, aliasMap) {
46
46
  }
47
47
  exports.START_WORKFLOW_PROTOCOL = {
48
48
  canonicalParams: {
49
- required: ['workflowId', 'workspacePath'],
49
+ required: ['workflowId', 'workspacePath', 'goal'],
50
50
  optional: [],
51
51
  },
52
52
  descriptions: {
@@ -57,11 +57,13 @@ exports.START_WORKFLOW_PROTOCOL = {
57
57
  'Follow the returned step exactly; treat it as the user\'s current instruction.',
58
58
  'When the step is done, call continue_workflow with the returned continueToken.',
59
59
  'Always pass workspacePath. Shared MCP servers cannot safely infer which repo/workspace you mean.',
60
+ 'Always pass goal. A short sentence describing what you are trying to accomplish (e.g. "implement OAuth refresh token rotation").',
60
61
  'Only pass context on later continue_workflow calls if facts changed.',
61
62
  ],
62
63
  examplePayload: {
63
64
  workflowId: 'coding-task-workflow-agentic',
64
65
  workspacePath: '/Users/you/git/my-project',
66
+ goal: 'implement OAuth refresh token rotation',
65
67
  },
66
68
  returns: 'Step instructions plus continueToken and checkpointToken in the structured response.',
67
69
  },
@@ -72,10 +74,12 @@ exports.START_WORKFLOW_PROTOCOL = {
72
74
  'Execute the returned step exactly as written.',
73
75
  'When the step is complete, call continue_workflow with the returned continueToken.',
74
76
  'Pass workspacePath on every call. Shared MCP servers cannot safely infer the correct workspace.',
77
+ 'Pass goal on every call. A short sentence describing what you are trying to accomplish.',
75
78
  ],
76
79
  examplePayload: {
77
80
  workflowId: 'coding-task-workflow-agentic',
78
81
  workspacePath: '/Users/you/git/my-project',
82
+ goal: 'implement OAuth refresh token rotation',
79
83
  },
80
84
  returns: 'Step instructions plus continueToken and checkpointToken in the structured response.',
81
85
  },
@@ -23,8 +23,7 @@ export interface AssessmentDefinition {
23
23
  readonly dimensions: readonly AssessmentDimensionDefinition[];
24
24
  }
25
25
  export interface AssessmentConsequenceTriggerDefinition {
26
- readonly dimensionId: string;
27
- readonly equalsLevel: string;
26
+ readonly anyEqualsLevel: string;
28
27
  }
29
28
  export interface AssessmentFollowupRequiredEffectDefinition {
30
29
  readonly kind: 'require_followup';
@@ -33,7 +33,10 @@ class LocalWorkspaceAnchorV2 {
33
33
  }
34
34
  async runGitCommands(cwd) {
35
35
  const anchors = [];
36
- const repoRoot = await this.gitCommand('git rev-parse --show-toplevel', cwd);
36
+ const gitCommonDir = await this.gitCommand('git rev-parse --path-format=absolute --git-common-dir', cwd);
37
+ if (!gitCommonDir)
38
+ return anchors;
39
+ const repoRoot = gitCommonDir.replace(/\/\.git\/?$/, '').trim() || null;
37
40
  if (!repoRoot)
38
41
  return anchors;
39
42
  const repoRootHash = hashRepoRoot(repoRoot);
@@ -8,6 +8,39 @@ const express_1 = __importDefault(require("express"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const worktree_service_js_1 = require("./worktree-service.js");
11
+ const sseClients = new Set();
12
+ let sseDebounceTimer = null;
13
+ function broadcastChange() {
14
+ if (sseDebounceTimer !== null)
15
+ return;
16
+ sseDebounceTimer = setTimeout(() => {
17
+ sseDebounceTimer = null;
18
+ for (const client of sseClients) {
19
+ try {
20
+ client.write('data: {"type":"change"}\n\n');
21
+ }
22
+ catch {
23
+ sseClients.delete(client);
24
+ }
25
+ }
26
+ }, 200);
27
+ }
28
+ function watchSessionsDir(sessionsDir) {
29
+ try {
30
+ fs_1.default.mkdirSync(sessionsDir, { recursive: true });
31
+ }
32
+ catch { }
33
+ let watcher = null;
34
+ try {
35
+ watcher = fs_1.default.watch(sessionsDir, { recursive: true }, () => {
36
+ broadcastChange();
37
+ });
38
+ watcher.on('error', () => { });
39
+ }
40
+ catch {
41
+ }
42
+ return () => { watcher?.close(); };
43
+ }
11
44
  function resolveConsoleDist() {
12
45
  const releasedDist = path_1.default.join(__dirname, '../../console');
13
46
  if (fs_1.default.existsSync(releasedDist))
@@ -21,6 +54,19 @@ function resolveConsoleDist() {
21
54
  return null;
22
55
  }
23
56
  function mountConsoleRoutes(app, consoleService) {
57
+ const stopWatcher = watchSessionsDir(consoleService.getSessionsDir());
58
+ process.once('exit', stopWatcher);
59
+ app.get('/api/v2/workspace/events', (req, res) => {
60
+ res.setHeader('Content-Type', 'text/event-stream');
61
+ res.setHeader('Cache-Control', 'no-cache');
62
+ res.setHeader('Connection', 'keep-alive');
63
+ res.setHeader('X-Accel-Buffering', 'no');
64
+ res.flushHeaders();
65
+ res.write('data: {"type":"connected"}\n\n');
66
+ sseClients.add(res);
67
+ req.on('close', () => { sseClients.delete(res); });
68
+ res.on('close', () => { sseClients.delete(res); });
69
+ });
24
70
  app.get('/api/v2/sessions', async (_req, res) => {
25
71
  const result = await consoleService.getSessionList();
26
72
  result.match((data) => res.json({ success: true, data }), (error) => res.status(500).json({ success: false, error: error.message }));
@@ -37,7 +83,9 @@ function mountConsoleRoutes(app, consoleService) {
37
83
  if (Date.now() > repoRootsExpiresAt) {
38
84
  cwdRepoRootPromise ?? (cwdRepoRootPromise = (0, worktree_service_js_1.resolveRepoRoot)(process.cwd()));
39
85
  const cwdRoot = await cwdRepoRootPromise;
40
- const repoRootSet = new Set(sessions.map(s => s.repoRoot).filter((r) => r !== null));
86
+ const rawRoots = sessions.map(s => s.repoRoot).filter((r) => r !== null);
87
+ const resolvedRoots = await Promise.all(rawRoots.map(r => (0, worktree_service_js_1.resolveRepoRoot)(r)));
88
+ const repoRootSet = new Set(resolvedRoots.filter((r) => r !== null));
41
89
  if (cwdRoot !== null)
42
90
  repoRootSet.add(cwdRoot);
43
91
  cachedRepoRoots = [...repoRootSet];
@@ -15,6 +15,7 @@ export interface ConsoleServicePorts {
15
15
  export declare class ConsoleService {
16
16
  private readonly ports;
17
17
  constructor(ports: ConsoleServicePorts);
18
+ getSessionsDir(): string;
18
19
  getSessionList(): ResultAsync<ConsoleSessionListResponse, ConsoleServiceError>;
19
20
  getSessionDetail(sessionIdStr: string): ResultAsync<ConsoleSessionDetail, ConsoleServiceError>;
20
21
  getNodeDetail(sessionIdStr: string, nodeId: string): ResultAsync<ConsoleNodeDetail, ConsoleServiceError>;
@@ -19,6 +19,9 @@ class ConsoleService {
19
19
  constructor(ports) {
20
20
  this.ports = ports;
21
21
  }
22
+ getSessionsDir() {
23
+ return this.ports.dataDir.sessionsDir();
24
+ }
22
25
  getSessionList() {
23
26
  return this.ports.directoryListing
24
27
  .readdirWithMtime(this.ports.dataDir.sessionsDir())
@@ -243,7 +246,7 @@ function deriveSessionTitle(events) {
243
246
  for (const key of TITLE_CONTEXT_KEYS) {
244
247
  const val = runCtx.context[key];
245
248
  if (typeof val === 'string' && val.trim().length > 0) {
246
- return truncateTitle(val.trim());
249
+ return val.trim();
247
250
  }
248
251
  }
249
252
  }
@@ -83,6 +83,11 @@ export interface ConsoleArtifact {
83
83
  readonly byteLength: number;
84
84
  readonly content: unknown;
85
85
  }
86
+ export type FileChangeStatus = 'modified' | 'added' | 'deleted' | 'untracked' | 'renamed' | 'other';
87
+ export interface ChangedFile {
88
+ readonly status: FileChangeStatus;
89
+ readonly path: string;
90
+ }
86
91
  export interface ConsoleWorktreeSummary {
87
92
  readonly path: string;
88
93
  readonly name: string;
@@ -91,8 +96,15 @@ export interface ConsoleWorktreeSummary {
91
96
  readonly headMessage: string;
92
97
  readonly headTimestampMs: number;
93
98
  readonly changedCount: number;
99
+ readonly changedFiles: readonly ChangedFile[];
94
100
  readonly aheadCount: number;
101
+ readonly unpushedCommits: readonly {
102
+ readonly hash: string;
103
+ readonly message: string;
104
+ }[];
105
+ readonly isMerged: boolean;
95
106
  readonly activeSessionCount: number;
107
+ readonly description?: string;
96
108
  }
97
109
  export interface ConsoleRepoWorktrees {
98
110
  readonly repoName: string;
@@ -44,25 +44,69 @@ function parseWorktreePorcelain(raw) {
44
44
  }
45
45
  return entries;
46
46
  }
47
+ function parseFileStatus(xy) {
48
+ if (xy === '??')
49
+ return 'untracked';
50
+ const x = xy[0] ?? ' ';
51
+ const y = xy[1] ?? ' ';
52
+ if (x === 'R')
53
+ return 'renamed';
54
+ if (x === 'A')
55
+ return 'added';
56
+ if (x === 'D' || y === 'D')
57
+ return 'deleted';
58
+ if (x === 'M' || y === 'M')
59
+ return 'modified';
60
+ return 'other';
61
+ }
62
+ function parseChangedFiles(statusRaw) {
63
+ if (!statusRaw)
64
+ return [];
65
+ return statusRaw
66
+ .split('\n')
67
+ .filter(line => line.trim().length > 0)
68
+ .map(line => ({
69
+ status: parseFileStatus(line.slice(0, 2)),
70
+ path: line.slice(3),
71
+ }));
72
+ }
73
+ const MAIN_BRANCH_REF = 'origin/main';
47
74
  async function enrichWorktree(wt) {
48
- const [logRaw, statusRaw, aheadRaw] = await Promise.all([
75
+ const descriptionKey = wt.branch ? `branch.${wt.branch}.description` : null;
76
+ const [logRaw, statusRaw, aheadRaw, descriptionRaw, unpushedLogRaw, mergedBranchesRaw] = await Promise.all([
49
77
  git(wt.path, ['log', '-1', '--format=%h%n%s%n%ct']),
50
78
  git(wt.path, ['status', '--short']),
51
- git(wt.path, ['rev-list', '--count', 'origin/main..HEAD']),
79
+ git(wt.path, ['rev-list', '--count', `${MAIN_BRANCH_REF}..HEAD`]),
80
+ descriptionKey ? git(wt.path, ['config', descriptionKey]) : Promise.resolve(null),
81
+ git(wt.path, ['log', `${MAIN_BRANCH_REF}..HEAD`, '--oneline']),
82
+ wt.branch && wt.branch !== 'main' ? git(wt.path, ['branch', '--merged', MAIN_BRANCH_REF]) : Promise.resolve(null),
52
83
  ]);
53
84
  const [hashLine, messageLine, timestampLine] = logRaw?.split('\n') ?? [];
54
85
  const headHash = hashLine?.trim() || wt.head.slice(0, 7);
55
86
  const headMessage = messageLine?.trim() ?? '';
56
87
  const headTimestampMs = timestampLine ? parseInt(timestampLine.trim(), 10) * 1000 : 0;
57
- const changedCount = statusRaw !== null
58
- ? statusRaw.split('\n').filter(l => l.trim()).length
59
- : 0;
88
+ const changedFiles = statusRaw !== null ? parseChangedFiles(statusRaw) : [];
89
+ const changedCount = changedFiles.length;
60
90
  const parsedAhead = aheadRaw !== null ? parseInt(aheadRaw, 10) : NaN;
61
91
  const aheadCount = isNaN(parsedAhead) ? 0 : parsedAhead;
62
- return { headHash, headMessage, headTimestampMs, changedCount, aheadCount };
92
+ const unpushedCommits = unpushedLogRaw
93
+ ? unpushedLogRaw.split('\n').filter(l => l.trim().length > 0).map(line => ({
94
+ hash: line.slice(0, 7),
95
+ message: line.slice(8),
96
+ }))
97
+ : [];
98
+ const isMerged = wt.branch !== null &&
99
+ wt.branch !== 'main' &&
100
+ mergedBranchesRaw !== null &&
101
+ mergedBranchesRaw.split('\n').some(line => line.trim() === wt.branch);
102
+ const branchDescription = descriptionRaw?.trim() ?? '';
103
+ return { headHash, headMessage, headTimestampMs, changedCount, changedFiles, aheadCount, unpushedCommits, isMerged, branchDescription };
63
104
  }
64
105
  async function resolveRepoRoot(path) {
65
- return git(path, ['rev-parse', '--show-toplevel']);
106
+ const commonDir = await git(path, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
107
+ if (commonDir === null)
108
+ return null;
109
+ return commonDir.replace(/\/\.git\/?$/, '') || null;
66
110
  }
67
111
  async function enrichRepo(repoRoot, activeSessions) {
68
112
  const porcelain = await git(repoRoot, ['worktree', 'list', '--porcelain']);
@@ -85,8 +129,12 @@ async function enrichRepo(repoRoot, activeSessions) {
85
129
  headMessage: e.headMessage,
86
130
  headTimestampMs: e.headTimestampMs,
87
131
  changedCount: e.changedCount,
132
+ changedFiles: e.changedFiles,
88
133
  aheadCount: e.aheadCount,
134
+ unpushedCommits: e.unpushedCommits,
135
+ isMerged: e.isMerged,
89
136
  activeSessionCount: wt.branch ? (activeSessions.counts.get(wt.branch) ?? 0) : 0,
137
+ ...(e.branchDescription ? { description: e.branchDescription } : {}),
90
138
  }];
91
139
  });
92
140
  return [...worktrees].sort((a, b) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "dev": "npm run build && node dist/mcp-server.js",
40
40
  "watch": "tsc --watch",
41
41
  "validate:workflows": "bash scripts/validate-workflows.sh",
42
- "validate:registry": "node scripts/validate-workflows-registry.ts",
42
+ "validate:registry": "node --experimental-strip-types scripts/validate-workflows-registry.ts",
43
43
  "stamp-workflow": "node scripts/stamp-workflow.ts",
44
44
  "validate:authoring-spec": "node scripts/validate-authoring-spec.js",
45
45
  "validate:authoring-docs": "node scripts/generate-authoring-docs.js --check",
@@ -3,7 +3,7 @@
3
3
  "version": 3,
4
4
  "title": "WorkRail Authoring Rules",
5
5
  "purpose": "Canonical current rules for authoring good WorkRail workflows. workflow.schema.json remains the source of truth for legal structure.",
6
- "lastReviewed": "2026-03-21",
6
+ "lastReviewed": "2026-04-04",
7
7
  "principles": [
8
8
  "Schema defines what is valid. These rules define what is good.",
9
9
  "Prefer current authoring rules over design rationale or historical notes.",
@@ -931,6 +931,87 @@
931
931
  }
932
932
  ]
933
933
  },
934
+ {
935
+ "id": "assessment-gates",
936
+ "title": "Assessment gates",
937
+ "rules": [
938
+ {
939
+ "id": "assessment-use-for-bounded-judgment",
940
+ "status": "active",
941
+ "level": "recommended",
942
+ "scope": [
943
+ "workflow.assessments",
944
+ "step.assessmentRefs",
945
+ "step.assessmentConsequences"
946
+ ],
947
+ "rule": "Use assessment gates when a step needs the agent to submit a bounded, durable judgment before the workflow can safely advance -- not for generic scoring or ceremony.",
948
+ "why": "Assessment gates force explicit structured reasoning at a decision point and durably record the result. Generic scoring that never affects routing adds noise without value.",
949
+ "enforcement": ["advisory"],
950
+ "sourceRefs": [
951
+ {
952
+ "kind": "example",
953
+ "path": "workflows/mr-review-workflow.agentic.v2.json",
954
+ "note": "Uses a three-dimension readiness gate on the final validation step."
955
+ },
956
+ {
957
+ "kind": "example",
958
+ "path": "workflows/bug-investigation.agentic.v2.json",
959
+ "note": "Uses a single-dimension diagnosis readiness gate before handoff."
960
+ }
961
+ ],
962
+ "checks": [
963
+ "The assessment is at a step where a wrong answer would produce a bad handoff.",
964
+ "At least one dimension has a consequence that keeps the step pending and requires follow-up.",
965
+ "The assessment result is durably recorded and useful to a future resume agent."
966
+ ],
967
+ "antiPatterns": [
968
+ "Using assessments as a generic five-level scoring rubric with no routing consequence",
969
+ "Declaring an assessment but never referencing it from any step"
970
+ ]
971
+ },
972
+ {
973
+ "id": "assessment-dimension-orthogonality",
974
+ "status": "active",
975
+ "level": "required",
976
+ "scope": [
977
+ "workflow.assessments"
978
+ ],
979
+ "rule": "Each dimension in an assessment must capture a distinct failure mode that the other dimensions cannot catch. A dimension that restates existing workflow state (such as a generic 'confidence' dimension that mirrors the workflow's confidence band) adds ceremony without structure.",
980
+ "why": "The value of multiple dimensions is that each one independently blocks advancement for a different reason. Correlated or vague dimensions collapse to a single gate with extra steps.",
981
+ "enforcement": ["advisory"],
982
+ "checks": [
983
+ "Each dimension is independently checkable without needing to know the result of the others.",
984
+ "No dimension restates a context variable the workflow already computes (e.g. recommendationConfidenceBand).",
985
+ "A 'low' rating on any one dimension alone justifies a follow-up, regardless of the others."
986
+ ],
987
+ "antiPatterns": [
988
+ "A single 'confidence' dimension that mirrors the workflow's existing confidence band",
989
+ "Multiple dimensions that all reduce to 'did I do a good job overall'",
990
+ "Dimensions so correlated that one being low always means the others are low too"
991
+ ]
992
+ },
993
+ {
994
+ "id": "assessment-v1-constraints",
995
+ "status": "active",
996
+ "level": "required",
997
+ "scope": [
998
+ "step.assessmentRefs",
999
+ "step.assessmentConsequences"
1000
+ ],
1001
+ "rule": "V1 supports exactly one assessmentRef per step and at most one assessmentConsequences entry per step. Use anyEqualsLevel as the trigger -- the engine checks all submitted dimensions and fires if any equals that level.",
1002
+ "why": "anyEqualsLevel is the only trigger form. It works for both single-dimension and multi-dimension assessments without requiring the author to choose between two forms.",
1003
+ "enforcement": ["schema"],
1004
+ "checks": [
1005
+ "No more than one assessmentRefs entry per step.",
1006
+ "No more than one assessmentConsequences entry per step.",
1007
+ "The consequence uses anyEqualsLevel to declare which level blocks -- not a named dimension."
1008
+ ],
1009
+ "antiPatterns": [
1010
+ "Trying to encode multiple consequences via workarounds"
1011
+ ]
1012
+ }
1013
+ ]
1014
+ },
934
1015
  {
935
1016
  "id": "delegation",
936
1017
  "title": "Delegation and subagents",
@@ -0,0 +1,132 @@
1
+ {
2
+ "$schema": "./workflow-tags.schema.json",
3
+ "version": 1,
4
+ "tags": [
5
+ {
6
+ "id": "coding",
7
+ "displayName": "Coding & Development",
8
+ "when": [
9
+ "implementing a feature or task",
10
+ "refactoring or improving existing code",
11
+ "converting code between platforms or languages"
12
+ ],
13
+ "examples": ["coding-task-workflow-agentic", "cross-platform-code-conversion"]
14
+ },
15
+ {
16
+ "id": "review_audit",
17
+ "displayName": "Review & Audit",
18
+ "when": [
19
+ "reviewing a merge request before merging",
20
+ "auditing a service for production readiness",
21
+ "checking architecture for scalability issues"
22
+ ],
23
+ "examples": ["mr-review-workflow-agentic", "production-readiness-audit"]
24
+ },
25
+ {
26
+ "id": "investigation",
27
+ "displayName": "Investigation & Debugging",
28
+ "when": [
29
+ "diagnosing a bug or unexpected behavior in code",
30
+ "diagnosing tool, environment, or MCP server issues"
31
+ ],
32
+ "examples": ["bug-investigation-agentic", "workflow-diagnose-environment"]
33
+ },
34
+ {
35
+ "id": "design",
36
+ "displayName": "Design & Discovery",
37
+ "when": [
38
+ "designing a UI or UX feature from scratch",
39
+ "exploring and thinking through a problem or decision",
40
+ "generating and comparing design candidates"
41
+ ],
42
+ "examples": ["ui-ux-design-workflow", "wr.discovery"]
43
+ },
44
+ {
45
+ "id": "documentation",
46
+ "displayName": "Documentation",
47
+ "when": [
48
+ "creating new documentation for a project, feature, or API",
49
+ "updating or maintaining existing documentation",
50
+ "writing documentation for a single bounded component or concept"
51
+ ],
52
+ "examples": ["document-creation-workflow", "documentation-update-workflow"]
53
+ },
54
+ {
55
+ "id": "tickets",
56
+ "displayName": "Tickets & Planning",
57
+ "when": [
58
+ "creating Jira tickets for a feature or epic",
59
+ "grooming and refining a backlog",
60
+ "generating test cases from ticket requirements"
61
+ ],
62
+ "examples": ["adaptive-ticket-creation", "ticket-grooming"]
63
+ },
64
+ {
65
+ "id": "learning",
66
+ "displayName": "Learning & Personal",
67
+ "when": [
68
+ "creating learning materials or a course",
69
+ "designing a presentation",
70
+ "making a major personal decision like where to relocate"
71
+ ],
72
+ "examples": ["personal-learning-materials-creation-branched", "presentation-creation"]
73
+ },
74
+ {
75
+ "id": "routines",
76
+ "displayName": "Routines",
77
+ "when": [
78
+ "gathering context about a codebase",
79
+ "generating design candidates",
80
+ "running a hypothesis challenge or adversarial review",
81
+ "running a final verification pass"
82
+ ],
83
+ "examples": ["routine-context-gathering", "routine-hypothesis-challenge"]
84
+ },
85
+ {
86
+ "id": "authoring",
87
+ "displayName": "Workflow Authoring",
88
+ "when": [
89
+ "creating a new WorkRail workflow from scratch",
90
+ "modernizing or updating an existing workflow"
91
+ ],
92
+ "examples": ["workflow-for-workflows"]
93
+ }
94
+ ],
95
+ "workflows": {
96
+ "adaptive-ticket-creation": { "tags": ["tickets"] },
97
+ "architecture-scalability-audit": { "tags": ["review_audit"] },
98
+ "bug-investigation-agentic": { "tags": ["investigation"] },
99
+ "coding-task-workflow-agentic": { "tags": ["coding"] },
100
+ "cross-platform-code-conversion": { "tags": ["coding"] },
101
+ "document-creation-workflow": { "tags": ["documentation"] },
102
+ "documentation-update-workflow": { "tags": ["documentation"] },
103
+ "intelligent-test-case-generation": { "tags": ["tickets", "coding"] },
104
+ "mr-review-workflow-agentic": { "tags": ["review_audit"] },
105
+ "personal-learning-course-design": { "tags": ["learning"] },
106
+ "personal-learning-materials-creation-branched": { "tags": ["learning"] },
107
+ "presentation-creation": { "tags": ["learning"] },
108
+ "production-readiness-audit": { "tags": ["review_audit"] },
109
+ "relocation-workflow-us": { "tags": ["learning"] },
110
+ "routine-context-gathering": { "tags": ["routines"] },
111
+ "routine-design-review": { "tags": ["routines"] },
112
+ "routine-execution-simulation": { "tags": ["routines"] },
113
+ "routine-feature-implementation": { "tags": ["routines", "coding"] },
114
+ "routine-final-verification": { "tags": ["routines"] },
115
+ "routine-hypothesis-challenge": { "tags": ["routines"] },
116
+ "routine-ideation": { "tags": ["routines"] },
117
+ "routine-parallel-work-partitioning": { "tags": ["routines"] },
118
+ "routine-philosophy-alignment": { "tags": ["routines"] },
119
+ "routine-plan-analysis": { "tags": ["routines"] },
120
+ "routine-plan-generation": { "tags": ["routines"] },
121
+ "routine-tension-driven-design": { "tags": ["routines"] },
122
+ "scoped-documentation-workflow": { "tags": ["documentation"] },
123
+ "ticket-grooming": { "tags": ["tickets"] },
124
+ "ui-ux-design-workflow": { "tags": ["design"] },
125
+ "workflow-diagnose-environment": { "tags": ["investigation"] },
126
+ "workflow-for-workflows": { "tags": ["authoring"] },
127
+ "wr.discovery": { "tags": ["design", "investigation"] },
128
+ "test-artifact-loop-control": { "tags": ["coding"], "hidden": true },
129
+ "test-session-persistence": { "tags": ["coding"], "hidden": true },
130
+ "test-missing-context": { "tags": ["coding"], "hidden": true }
131
+ }
132
+ }
@@ -1064,23 +1064,15 @@
1064
1064
  },
1065
1065
  "assessmentConsequenceTrigger": {
1066
1066
  "type": "object",
1067
- "description": "Exact-match trigger on one declared assessment dimension and one declared canonical level.",
1067
+ "description": "Fires when ANY dimension in the submitted assessment equals the given level. The consequence blocks advancement until the agent addresses all dimensions that scored at that level.",
1068
1068
  "properties": {
1069
- "dimensionId": {
1070
- "type": "string",
1071
- "minLength": 1,
1072
- "maxLength": 64
1073
- },
1074
- "equalsLevel": {
1069
+ "anyEqualsLevel": {
1075
1070
  "type": "string",
1076
1071
  "minLength": 1,
1077
1072
  "maxLength": 64
1078
1073
  }
1079
1074
  },
1080
- "required": [
1081
- "dimensionId",
1082
- "equalsLevel"
1083
- ],
1075
+ "required": ["anyEqualsLevel"],
1084
1076
  "additionalProperties": false
1085
1077
  },
1086
1078
  "assessmentConsequenceEffect": {