@shirlytaylor73/superharness 1.5.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/lib/codex-installer.js +228 -0
  4. package/bin/lib/interactive-select.js +96 -0
  5. package/bin/superharness.js +67 -0
  6. package/package.json +52 -0
  7. package/plugins/superharness/.claude-plugin/plugin.json +19 -0
  8. package/plugins/superharness/.codex-plugin/plugin.json +31 -0
  9. package/plugins/superharness/.mcp.json +9 -0
  10. package/plugins/superharness/CODE_OF_CONDUCT.md +79 -0
  11. package/plugins/superharness/LICENSE +21 -0
  12. package/plugins/superharness/README.md +57 -0
  13. package/plugins/superharness/agents/code-reviewer.md +48 -0
  14. package/plugins/superharness/archived-skills/using-superpowers/SKILL.md +140 -0
  15. package/plugins/superharness/archived-skills/using-superpowers/references/codex-tools.md +25 -0
  16. package/plugins/superharness/archived-skills/using-superpowers/references/copilot-tools.md +52 -0
  17. package/plugins/superharness/archived-skills/using-superpowers/references/gemini-tools.md +33 -0
  18. package/plugins/superharness/archived-skills/using-superpowers/references/hermes-tools.md +44 -0
  19. package/plugins/superharness/commands/free.md +6 -0
  20. package/plugins/superharness/commands/rollback.md +30 -0
  21. package/plugins/superharness/commands-codex/free.md +29 -0
  22. package/plugins/superharness/commands-codex/rollback.md +33 -0
  23. package/plugins/superharness/hooks/hooks-codex.json +50 -0
  24. package/plugins/superharness/hooks/hooks.json +50 -0
  25. package/plugins/superharness/hooks/lib/free-mode-check.mjs +27 -0
  26. package/plugins/superharness/hooks/run-hook.cmd +58 -0
  27. package/plugins/superharness/hooks/workflow-context +4 -0
  28. package/plugins/superharness/hooks/workflow-context.mjs +184 -0
  29. package/plugins/superharness/hooks/workflow-post-transition +4 -0
  30. package/plugins/superharness/hooks/workflow-post-transition.mjs +89 -0
  31. package/plugins/superharness/hooks/workflow-pre-tool-use +4 -0
  32. package/plugins/superharness/hooks/workflow-pre-tool-use.mjs +97 -0
  33. package/plugins/superharness/hooks/workflow-stop +4 -0
  34. package/plugins/superharness/hooks/workflow-stop.mjs +136 -0
  35. package/plugins/superharness/scripts/rollback.mjs +86 -0
  36. package/plugins/superharness/scripts/set-free-mode.mjs +77 -0
  37. package/plugins/superharness/skills/brainstorming/SKILL.md +182 -0
  38. package/plugins/superharness/skills/brainstorming/scripts/frame-template.html +214 -0
  39. package/plugins/superharness/skills/brainstorming/scripts/helper.js +88 -0
  40. package/plugins/superharness/skills/brainstorming/scripts/server.cjs +338 -0
  41. package/plugins/superharness/skills/brainstorming/scripts/start-server.sh +153 -0
  42. package/plugins/superharness/skills/brainstorming/scripts/stop-server.sh +55 -0
  43. package/plugins/superharness/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  44. package/plugins/superharness/skills/brainstorming/visual-companion.md +286 -0
  45. package/plugins/superharness/skills/chinese-code-review/SKILL.md +277 -0
  46. package/plugins/superharness/skills/chinese-commit-conventions/SKILL.md +364 -0
  47. package/plugins/superharness/skills/chinese-documentation/SKILL.md +448 -0
  48. package/plugins/superharness/skills/chinese-git-workflow/SKILL.md +547 -0
  49. package/plugins/superharness/skills/dispatching-parallel-agents/SKILL.md +186 -0
  50. package/plugins/superharness/skills/exploration/SKILL.md +197 -0
  51. package/plugins/superharness/skills/finishing/SKILL.md +200 -0
  52. package/plugins/superharness/skills/intake/SKILL.md +134 -0
  53. package/plugins/superharness/skills/mcp-builder/SKILL.md +255 -0
  54. package/plugins/superharness/skills/parallel-execution/SKILL.md +368 -0
  55. package/plugins/superharness/skills/parallel-execution/implementer-prompt.md +144 -0
  56. package/plugins/superharness/skills/parallel-execution/spec-reviewer-prompt.md +84 -0
  57. package/plugins/superharness/skills/parallel-execution/wave-final-manual-qa-prompt.md +61 -0
  58. package/plugins/superharness/skills/parallel-execution/wave-final-quality-prompt.md +59 -0
  59. package/plugins/superharness/skills/parallel-execution/wave-final-scope-fidelity-prompt.md +69 -0
  60. package/plugins/superharness/skills/parallel-execution/wave-final-spec-prompt.md +56 -0
  61. package/plugins/superharness/skills/planning/SKILL.md +265 -0
  62. package/plugins/superharness/skills/planning/plan-document-reviewer-prompt.md +80 -0
  63. package/plugins/superharness/skills/receiving-code-review/SKILL.md +213 -0
  64. package/plugins/superharness/skills/requesting-code-review/SKILL.md +107 -0
  65. package/plugins/superharness/skills/requesting-code-review/code-reviewer.md +146 -0
  66. package/plugins/superharness/skills/serial-execution/SKILL.md +183 -0
  67. package/plugins/superharness/skills/systematic-debugging/CREATION-LOG.md +119 -0
  68. package/plugins/superharness/skills/systematic-debugging/SKILL.md +320 -0
  69. package/plugins/superharness/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  70. package/plugins/superharness/skills/systematic-debugging/condition-based-waiting.md +115 -0
  71. package/plugins/superharness/skills/systematic-debugging/defense-in-depth.md +122 -0
  72. package/plugins/superharness/skills/systematic-debugging/find-polluter.sh +63 -0
  73. package/plugins/superharness/skills/systematic-debugging/root-cause-tracing.md +169 -0
  74. package/plugins/superharness/skills/systematic-debugging/test-academic.md +14 -0
  75. package/plugins/superharness/skills/systematic-debugging/test-pressure-1.md +58 -0
  76. package/plugins/superharness/skills/systematic-debugging/test-pressure-2.md +68 -0
  77. package/plugins/superharness/skills/systematic-debugging/test-pressure-3.md +69 -0
  78. package/plugins/superharness/skills/test-driven-development/SKILL.md +371 -0
  79. package/plugins/superharness/skills/test-driven-development/testing-anti-patterns.md +299 -0
  80. package/plugins/superharness/skills/trivial/SKILL.md +118 -0
  81. package/plugins/superharness/skills/using-git-worktrees/SKILL.md +218 -0
  82. package/plugins/superharness/skills/verification/SKILL.md +139 -0
  83. package/plugins/superharness/skills/workflow-runner/SKILL.md +172 -0
  84. package/plugins/superharness/skills/writing-skills/SKILL.md +655 -0
  85. package/plugins/superharness/skills/writing-skills/anthropic-best-practices.md +1149 -0
  86. package/plugins/superharness/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  87. package/plugins/superharness/skills/writing-skills/graphviz-conventions.dot +172 -0
  88. package/plugins/superharness/skills/writing-skills/persuasion-principles.md +187 -0
  89. package/plugins/superharness/skills/writing-skills/render-graphs.js +168 -0
  90. package/plugins/superharness/skills/writing-skills/testing-skills-with-subagents.md +385 -0
  91. package/plugins/superharness/workflow/default-workflow.yaml +84 -0
  92. package/plugins/superharness/workflow-state-server/bootstrap.js +44 -0
  93. package/plugins/superharness/workflow-state-server/package-lock.json +2853 -0
  94. package/plugins/superharness/workflow-state-server/package.json +22 -0
  95. package/plugins/superharness/workflow-state-server/render-context.js +124 -0
  96. package/plugins/superharness/workflow-state-server/schema.sql +39 -0
  97. package/plugins/superharness/workflow-state-server/server.js +290 -0
  98. package/plugins/superharness/workflow-state-server/state.js +424 -0
  99. package/plugins/superharness/workflow-state-server/validate-workflow.js +165 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "superharness-workflow-state-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "main": "server.js",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js",
12
+ "test": "vitest run"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0",
16
+ "better-sqlite3": "^12.0.0",
17
+ "yaml": "^2.8.0"
18
+ },
19
+ "devDependencies": {
20
+ "vitest": "^4.0.0"
21
+ }
22
+ }
@@ -0,0 +1,124 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
5
+
6
+ function requireStateNode(workflowGraph, stateName) {
7
+ const node = workflowGraph?.states?.get(stateName);
8
+ if (!node) {
9
+ throw new Error(`workflow state not found: ${stateName}`);
10
+ }
11
+ return node;
12
+ }
13
+
14
+ export function stripFrontmatter(markdown) {
15
+ return markdown.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
16
+ }
17
+
18
+ export function resolveSkillPath({ skillsDir, skillName }) {
19
+ if (!SKILL_NAME_PATTERN.test(skillName)) {
20
+ throw new Error(`invalid skill name: ${skillName}`);
21
+ }
22
+ const skillPath = path.join(skillsDir, skillName, 'SKILL.md');
23
+ if (!fs.existsSync(skillPath)) {
24
+ throw new Error(`skill not found: ${skillName}`);
25
+ }
26
+ return skillPath;
27
+ }
28
+
29
+ function renderFacts({ stateInfo, node }) {
30
+ return [
31
+ 'Runtime facts:',
32
+ `- current_state: ${stateInfo.state}`,
33
+ `- previous_state: ${stateInfo.previous_state ?? 'null'}`,
34
+ `- active_skill: ${stateInfo.active_skill ?? node.skill ?? 'null'}`,
35
+ `- allowed_transitions: ${JSON.stringify(node.next)}`,
36
+ '- state_directory: .superharness/',
37
+ ].join('\n');
38
+ }
39
+
40
+ function renderRules() {
41
+ return [
42
+ 'Rules:',
43
+ '- Follow the active skill below for this turn.',
44
+ '- If the state exit condition is met, call transition_state with a non-empty reason.',
45
+ '- Do not edit .superharness/ directly.',
46
+ '- If workflow config or state cannot be loaded, stop business work and report the error.',
47
+ ].join('\n');
48
+ }
49
+
50
+ function renderRouterGuard() {
51
+ return [
52
+ 'Router guard:',
53
+ '- Select serial_execution or parallel_execution based on the written plan and platform capability.',
54
+ '- If execution cannot safely proceed, transition to systematic_debugging with a concrete reason.',
55
+ ].join('\n');
56
+ }
57
+
58
+ export function renderWorkflowContext({ stateInfo, workflowGraph, skillsDir }) {
59
+ if (!stateInfo?.state) {
60
+ throw new Error('stateInfo.state is required');
61
+ }
62
+
63
+ const node = requireStateNode(workflowGraph, stateInfo.state);
64
+ const parts = [
65
+ '<SUPERHARNESS_WORKFLOW_STATE>',
66
+ renderFacts({ stateInfo, node }),
67
+ '',
68
+ renderRules(),
69
+ ];
70
+
71
+ if (node.type === 'router') {
72
+ parts.push('', renderRouterGuard(), '</SUPERHARNESS_WORKFLOW_STATE>');
73
+ return parts.join('\n') + renderStrictAppendix({ silent_stop_allowed: node.silent_stop_allowed });
74
+ }
75
+
76
+ const skillName = stateInfo.active_skill ?? node.skill;
77
+ if (skillName) {
78
+ const skillPath = resolveSkillPath({ skillsDir, skillName });
79
+ const skillContent = stripFrontmatter(fs.readFileSync(skillPath, 'utf8')).trim();
80
+ parts.push('', `--- Active skill: ${skillName} ---`, skillContent);
81
+ }
82
+
83
+ parts.push('</SUPERHARNESS_WORKFLOW_STATE>');
84
+ return parts.join('\n') + renderStrictAppendix({ silent_stop_allowed: node.silent_stop_allowed });
85
+ }
86
+
87
+ export function renderActiveSkill({ stateName, skillsDir, skillName }) {
88
+ // State 名可能是 underscore (serial_execution),skill 目录是 hyphen (serial-execution)。
89
+ // 若 caller 显式提供 skillName(来自 graph.states.get(state).skill),用它做目录查找;
90
+ // 否则回退到 stateName(兼容 1:1 命名的 state,如 intake/trivial/planning)。
91
+ const lookupName = skillName ?? stateName;
92
+ const skillPath = resolveSkillPath({ skillsDir, skillName: lookupName });
93
+ const skillContent = stripFrontmatter(fs.readFileSync(skillPath, 'utf8')).trim();
94
+ return [
95
+ `[Superharness] 已切到新状态,本轮继续遵循以下 SKILL:`,
96
+ ``,
97
+ `--- Active skill: ${stateName} ---`,
98
+ skillContent,
99
+ ].join('\n');
100
+ }
101
+
102
+ export function renderStrictAppendix({ silent_stop_allowed }) {
103
+ if (silent_stop_allowed) return '';
104
+ return [
105
+ '',
106
+ '⚠ 当前 state 不允许本轮沉默结束——必须在本轮调用 transition_state 切到下一态,',
107
+ '若本轮工作未完成请继续完成再切;若无法继续请用 AskUserQuestion 让用户决定。',
108
+ ].join('\n');
109
+ }
110
+
111
+ export function renderStopWorkContext({ reason }) {
112
+ return [
113
+ '<SUPERHARNESS_WORKFLOW_STATE>',
114
+ 'Runtime status: unavailable',
115
+ `Reason: ${reason || 'workflow state context could not be loaded'}`,
116
+ '',
117
+ 'Rules:',
118
+ '- Stop business work.',
119
+ '- Report the workflow error to the user.',
120
+ '- Do not invent workflow state.',
121
+ '- Do not edit .superharness/ directly.',
122
+ '</SUPERHARNESS_WORKFLOW_STATE>',
123
+ ].join('\n');
124
+ }
@@ -0,0 +1,39 @@
1
+ CREATE TABLE IF NOT EXISTS workflow_state (
2
+ workspace_id TEXT PRIMARY KEY,
3
+ state TEXT NOT NULL,
4
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','completed')),
5
+ previous_state TEXT,
6
+ active_skill TEXT,
7
+ task_summary TEXT,
8
+ failure_summary TEXT,
9
+ updated_at INTEGER NOT NULL,
10
+ free_mode INTEGER NOT NULL DEFAULT 0,
11
+ free_started_at INTEGER
12
+ );
13
+
14
+ CREATE TABLE IF NOT EXISTS workflow_transition_log (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ workspace_id TEXT NOT NULL,
17
+ from_state TEXT,
18
+ to_state TEXT NOT NULL,
19
+ previous_state TEXT,
20
+ reason TEXT NOT NULL,
21
+ source TEXT NOT NULL CHECK (source IN ('hook','agent-tool','user-reset')),
22
+ created_at INTEGER NOT NULL
23
+ );
24
+
25
+ CREATE INDEX IF NOT EXISTS idx_workflow_transition_workspace
26
+ ON workflow_transition_log(workspace_id, id);
27
+
28
+ CREATE TABLE IF NOT EXISTS workflow_turn (
29
+ workspace_id TEXT PRIMARY KEY,
30
+ turn_id TEXT NOT NULL,
31
+ block_count INTEGER NOT NULL DEFAULT 0,
32
+ stop_block_released INTEGER NOT NULL DEFAULT 0,
33
+ release_reason TEXT,
34
+ created_at INTEGER NOT NULL
35
+ );
36
+
37
+ -- Note: idx_workflow_transition_turn is created in state.js::ensureTurnIdColumn
38
+ -- because the turn_id column is added via ALTER for backwards compatibility
39
+ -- with pre-v1.3.0 databases.
@@ -0,0 +1,290 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import {
7
+ ListToolsRequestSchema,
8
+ CallToolRequestSchema,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { loadWorkflowConfig, buildWorkflowGraph } from './validate-workflow.js';
11
+ import {
12
+ openWorkflowStateStore,
13
+ getWorkflowState,
14
+ classifyRequest,
15
+ transitionWorkflowState,
16
+ listWorkflowHistory,
17
+ resolveWorkflowDbPath,
18
+ getTurn,
19
+ releaseTurnBlock,
20
+ assertNotFreeMode,
21
+ } from './state.js';
22
+ import { renderWorkflowContext } from './render-context.js';
23
+
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+ const DEFAULT_PLUGIN_ROOT = path.resolve(__dirname, '..');
26
+
27
+ function loadInstalledSkills(pluginRoot = DEFAULT_PLUGIN_ROOT) {
28
+ const skillsDir = path.join(pluginRoot, 'skills');
29
+ if (!fs.existsSync(skillsDir)) return new Set();
30
+ return new Set(
31
+ fs.readdirSync(skillsDir, { withFileTypes: true })
32
+ .filter((entry) => entry.isDirectory())
33
+ .filter((entry) => fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
34
+ .map((entry) => entry.name),
35
+ );
36
+ }
37
+
38
+ function createRuntime({
39
+ workspaceRoot = process.cwd(),
40
+ pluginRoot = DEFAULT_PLUGIN_ROOT,
41
+ } = {}) {
42
+ const config = loadWorkflowConfig({ pluginRoot, workspaceRoot });
43
+ const workflowGraph = buildWorkflowGraph(config, {
44
+ installedSkills: loadInstalledSkills(pluginRoot),
45
+ });
46
+ const store = openWorkflowStateStore({
47
+ mode: process.env.SUPERHARNESS_WORKFLOW_STATE_MODE,
48
+ dbPath: process.env.SUPERHARNESS_WORKFLOW_STATE_DB
49
+ ?? resolveWorkflowDbPath({ workspaceRoot }),
50
+ });
51
+
52
+ return {
53
+ store,
54
+ workflowGraph,
55
+ skillsDir: path.join(pluginRoot, 'skills'),
56
+ };
57
+ }
58
+
59
+ let defaultRuntime;
60
+ function getDefaultRuntime() {
61
+ if (!defaultRuntime) {
62
+ defaultRuntime = createRuntime();
63
+ }
64
+ return defaultRuntime;
65
+ }
66
+
67
+ function wrap(handler) {
68
+ return async (args = {}) => {
69
+ try {
70
+ return await handler(args);
71
+ } catch (error) {
72
+ return {
73
+ error: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+ };
77
+ }
78
+
79
+ const RELEASE_STOP_BLOCK_DESCRIPTION =
80
+ '在 silent_stop_allowed=false 的 state 内 agent 无法继续工作时,由用户通过 AskUserQuestion 明确授权"终止本轮"后调用此工具。绝不可在没有用户明确授权前调用。';
81
+
82
+ const PLACEHOLDER_REASONS = new Set(['ok', 'start', '用户请求']);
83
+
84
+ export async function handleReleaseStopBlock({ store, args }) {
85
+ const { workspaceRoot, reason } = args ?? {};
86
+ assertNotFreeMode(store, workspaceRoot);
87
+ const trimmed = typeof reason === 'string' ? reason.trim() : '';
88
+ if (!trimmed || PLACEHOLDER_REASONS.has(trimmed)) {
89
+ throw new Error('reason must be non-empty and non-placeholder');
90
+ }
91
+ const turnRow = getTurn(store, { workspaceRoot });
92
+ if (!turnRow) {
93
+ throw new Error('no active turn for workspace');
94
+ }
95
+ releaseTurnBlock(store, { workspaceRoot, reason: trimmed });
96
+
97
+ // audit: write a transition_log row tagged with [escape] prefix.
98
+ // schema requires to_state NOT NULL, so use current workflow_state.state
99
+ // (or a sentinel if no workflow_state row exists yet).
100
+ const resolvedWorkspaceId = path.resolve(workspaceRoot);
101
+ const stateRow = store.prepare(
102
+ 'SELECT state FROM workflow_state WHERE workspace_id = ?',
103
+ ).get(resolvedWorkspaceId);
104
+ const sentinelState = stateRow?.state ?? 'turn';
105
+ const turnIdRow = store.prepare(
106
+ 'SELECT turn_id FROM workflow_turn WHERE workspace_id = ?',
107
+ ).get(resolvedWorkspaceId);
108
+ store.prepare(`
109
+ INSERT INTO workflow_transition_log (
110
+ workspace_id, from_state, to_state, previous_state, reason, source, turn_id, created_at
111
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
112
+ `).run(
113
+ resolvedWorkspaceId,
114
+ sentinelState,
115
+ sentinelState,
116
+ null,
117
+ `[escape] ${trimmed}`,
118
+ 'agent-tool',
119
+ turnIdRow?.turn_id ?? null,
120
+ Date.now(),
121
+ );
122
+
123
+ return { ok: true };
124
+ }
125
+
126
+ export function createTools(getRuntime = getDefaultRuntime) {
127
+ return [
128
+ {
129
+ name: 'get_state',
130
+ description: 'Return current workflow state and rendered workflow context.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ required: ['workspaceRoot'],
134
+ properties: {
135
+ workspaceRoot: { type: 'string' },
136
+ },
137
+ },
138
+ handler: wrap((args) => {
139
+ const runtime = getRuntime();
140
+ const state = getWorkflowState(runtime.store, {
141
+ workspaceRoot: args.workspaceRoot,
142
+ workflowGraph: runtime.workflowGraph,
143
+ });
144
+ return {
145
+ ...state,
146
+ context: renderWorkflowContext({
147
+ stateInfo: state,
148
+ workflowGraph: runtime.workflowGraph,
149
+ skillsDir: runtime.skillsDir,
150
+ }),
151
+ };
152
+ }),
153
+ },
154
+ {
155
+ name: 'classify_request',
156
+ description: 'Record a task or failure summary for the active workflow.',
157
+ inputSchema: {
158
+ type: 'object',
159
+ required: ['workspaceRoot', 'reason'],
160
+ properties: {
161
+ workspaceRoot: { type: 'string' },
162
+ task_summary: { type: 'string' },
163
+ failure_summary: { type: 'string' },
164
+ reason: { type: 'string' },
165
+ },
166
+ },
167
+ handler: wrap((args) => {
168
+ const runtime = getRuntime();
169
+ return classifyRequest(runtime.store, {
170
+ workspaceRoot: args.workspaceRoot,
171
+ workflowGraph: runtime.workflowGraph,
172
+ task_summary: args.task_summary,
173
+ failure_summary: args.failure_summary,
174
+ reason: args.reason,
175
+ });
176
+ }),
177
+ },
178
+ {
179
+ name: 'transition_state',
180
+ description: 'Validate and execute a workflow state transition.',
181
+ inputSchema: {
182
+ type: 'object',
183
+ required: ['workspaceRoot', 'from_state', 'to_state', 'reason'],
184
+ properties: {
185
+ workspaceRoot: { type: 'string' },
186
+ from_state: { type: 'string' },
187
+ to_state: { type: 'string' },
188
+ previous_state: { type: 'string' },
189
+ reason: { type: 'string' },
190
+ },
191
+ },
192
+ handler: wrap((args) => {
193
+ const runtime = getRuntime();
194
+ return transitionWorkflowState(runtime.store, {
195
+ workspaceRoot: args.workspaceRoot,
196
+ workflowGraph: runtime.workflowGraph,
197
+ from_state: args.from_state,
198
+ to_state: args.to_state,
199
+ previous_state: args.previous_state,
200
+ reason: args.reason,
201
+ source: 'agent-tool',
202
+ });
203
+ }),
204
+ },
205
+ {
206
+ name: 'list_history',
207
+ description: 'List workflow transition history for a workspace.',
208
+ inputSchema: {
209
+ type: 'object',
210
+ required: ['workspaceRoot'],
211
+ properties: {
212
+ workspaceRoot: { type: 'string' },
213
+ },
214
+ },
215
+ handler: wrap((args) => {
216
+ const runtime = getRuntime();
217
+ return listWorkflowHistory(runtime.store, {
218
+ workspaceRoot: args.workspaceRoot,
219
+ });
220
+ }),
221
+ },
222
+ {
223
+ name: 'release_stop_block',
224
+ description: RELEASE_STOP_BLOCK_DESCRIPTION,
225
+ inputSchema: {
226
+ type: 'object',
227
+ required: ['workspaceRoot', 'reason'],
228
+ properties: {
229
+ workspaceRoot: { type: 'string' },
230
+ reason: { type: 'string' },
231
+ },
232
+ },
233
+ handler: wrap((args) => {
234
+ const runtime = getRuntime();
235
+ return handleReleaseStopBlock({ store: runtime.store, args });
236
+ }),
237
+ },
238
+ ];
239
+ }
240
+
241
+ export const TOOLS = createTools(getDefaultRuntime);
242
+
243
+ export function createMcpServer({ tools = TOOLS } = {}) {
244
+ const server = new Server(
245
+ {
246
+ name: 'superharness-workflow-state',
247
+ version: '0.1.0',
248
+ },
249
+ {
250
+ capabilities: {
251
+ tools: {},
252
+ },
253
+ },
254
+ );
255
+
256
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
257
+ tools: tools.map(({ name, description, inputSchema }) => ({
258
+ name,
259
+ description,
260
+ inputSchema,
261
+ })),
262
+ }));
263
+
264
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
265
+ const tool = tools.find((candidate) => candidate.name === request.params.name);
266
+ if (!tool) {
267
+ return {
268
+ isError: true,
269
+ content: [{ type: 'text', text: `unknown tool: ${request.params.name}` }],
270
+ };
271
+ }
272
+
273
+ const result = await tool.handler(request.params.arguments ?? {});
274
+ return {
275
+ isError: !!result?.error,
276
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
277
+ };
278
+ });
279
+
280
+ return server;
281
+ }
282
+
283
+ export async function main() {
284
+ const server = createMcpServer();
285
+ await server.connect(new StdioServerTransport());
286
+ }
287
+
288
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
289
+ await main();
290
+ }