@thxgg/steward 0.1.13 → 0.1.15
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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/-k8zG74W.js +61 -0
- package/.output/public/_nuxt/B-5VWizU.js +1 -0
- package/.output/public/_nuxt/{Bq6edYSd.js → BDqHART1.js} +1 -1
- package/.output/public/_nuxt/BMAq0QVD.js +42 -0
- package/.output/public/_nuxt/BPeTf9dd.js +1 -0
- package/.output/public/_nuxt/{dOaEkD-3.js → BubpH_wW.js} +1 -1
- package/.output/public/_nuxt/C2HGkiSP.js +1 -0
- package/.output/public/_nuxt/CMu9GKTH.js +4 -0
- package/.output/public/_nuxt/{BZ1iIOYp.js → CVvrkZkq.js} +1 -1
- package/.output/public/_nuxt/C_NevjZD.js +3 -0
- package/.output/public/_nuxt/Detail.CzXXlavD.css +1 -0
- package/.output/public/_nuxt/_prd_.KTotLoF_.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/c1a7997d-8d53-4718-ad03-a977e05e2523.json +1 -0
- package/.output/public/_nuxt/entry.LcDOtJnR.css +1 -0
- package/.output/public/_nuxt/{BFv4l3hn.js → nYTZJhvT.js} +1 -1
- package/.output/public/_nuxt/{kTT8NKtq.js → qKRNa41x.js} +1 -1
- package/.output/public/_nuxt/qt5OEWHC.js +1 -0
- package/.output/public/_nuxt/{C897Egk9.js → uTyw4SRK.js} +1 -1
- package/.output/public/_nuxt/{DoNqd8jQ.js → wbj-mIhK.js} +1 -1
- package/.output/server/chunks/_/task-graph.mjs +196 -0
- package/.output/server/chunks/_/task-graph.mjs.map +1 -0
- package/.output/server/chunks/build/{_prd_-CkKfJB6U.mjs → Detail-DC-KJQ1f.mjs} +911 -1688
- package/.output/server/chunks/build/Detail-DC-KJQ1f.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-D0sb4vsK.mjs +4 -0
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-D0sb4vsK.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles.CkSjCQ0r.mjs +10 -0
- package/.output/server/chunks/build/DiffViewer-styles.CkSjCQ0r.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles.FJJuYjYB.mjs +8 -0
- package/.output/server/chunks/build/DiffViewer-styles.FJJuYjYB.mjs.map +1 -0
- package/.output/server/chunks/build/_prd_-C1C4GAhW.mjs +1596 -0
- package/.output/server/chunks/build/_prd_-C1C4GAhW.mjs.map +1 -0
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/{default-B5nw9_Xg.mjs → default-DWCOHHTE.mjs} +32 -20
- package/.output/server/chunks/build/default-DWCOHHTE.mjs.map +1 -0
- package/.output/server/chunks/build/{index-CTpuP9Mj.mjs → index-CckL_NBD.mjs} +2 -2
- package/.output/server/chunks/build/index-CckL_NBD.mjs.map +1 -0
- package/.output/server/chunks/build/{index-D21S97KB.mjs → index-QVeSHT3L.mjs} +3 -3
- package/.output/server/chunks/build/index-QVeSHT3L.mjs.map +1 -0
- package/.output/server/chunks/build/repo-graph-CTEkxiYd.mjs +205 -0
- package/.output/server/chunks/build/repo-graph-CTEkxiYd.mjs.map +1 -0
- package/.output/server/chunks/build/server.mjs +12 -3
- package/.output/server/chunks/build/styles.mjs +2 -2
- package/.output/server/chunks/build/{usePrd-YhvN6Ary.mjs → usePrd-SqcxGyFU.mjs} +20 -2
- package/.output/server/chunks/build/usePrd-SqcxGyFU.mjs.map +1 -0
- package/.output/server/chunks/nitro/nitro.mjs +620 -588
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +41 -0
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +42 -0
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs +1 -1
- package/.output/server/node_modules/@vue-flow/background/dist/vue-flow-background.mjs +126 -0
- package/.output/server/node_modules/@vue-flow/background/package.json +73 -0
- package/.output/server/node_modules/@vue-flow/controls/dist/vue-flow-controls.mjs +207 -0
- package/.output/server/node_modules/@vue-flow/controls/package.json +77 -0
- package/.output/server/node_modules/@vue-flow/core/dist/vue-flow-core.mjs +10186 -0
- package/.output/server/node_modules/@vue-flow/core/package.json +95 -0
- package/.output/server/package.json +4 -1
- package/README.md +10 -1
- package/dist/app/types/graph.js +1 -0
- package/dist/host/src/mcp.js +2 -0
- package/dist/host/src/prompts.js +127 -0
- package/dist/server/utils/task-graph.js +190 -0
- package/docs/MCP.md +13 -1
- package/package.json +5 -1
- package/.output/public/_nuxt/BuQdImno.js +0 -1
- package/.output/public/_nuxt/CMUOpExW.js +0 -3
- package/.output/public/_nuxt/DE885CbX.js +0 -1
- package/.output/public/_nuxt/DomrzX-T.js +0 -76
- package/.output/public/_nuxt/R2cvz8mH.js +0 -4
- package/.output/public/_nuxt/_prd_.DYvuV73Q.css +0 -1
- package/.output/public/_nuxt/builds/meta/2ad99048-24f9-4cf6-8622-6c088fe0a244.json +0 -1
- package/.output/public/_nuxt/entry.Dk19PK4d.css +0 -1
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-ZdBUa15f.mjs +0 -4
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-ZdBUa15f.mjs.map +0 -1
- package/.output/server/chunks/build/DiffViewer-styles.CoMVrk_N.mjs +0 -8
- package/.output/server/chunks/build/DiffViewer-styles.CoMVrk_N.mjs.map +0 -1
- package/.output/server/chunks/build/DiffViewer-styles.cLfMOdMh.mjs +0 -10
- package/.output/server/chunks/build/DiffViewer-styles.cLfMOdMh.mjs.map +0 -1
- package/.output/server/chunks/build/_prd_-CkKfJB6U.mjs.map +0 -1
- package/.output/server/chunks/build/default-B5nw9_Xg.mjs.map +0 -1
- package/.output/server/chunks/build/index-CTpuP9Mj.mjs.map +0 -1
- package/.output/server/chunks/build/index-D21S97KB.mjs.map +0 -1
- package/.output/server/chunks/build/usePrd-YhvN6Ary.mjs.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vue-flow/core",
|
|
3
|
+
"version": "1.48.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Burak Cakmakoglu<78412429+bcakmakoglu@users.noreply.github.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bcakmakoglu/vue-flow",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://vueflow.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/bcakmakoglu/vue-flow/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"vue",
|
|
18
|
+
"flow",
|
|
19
|
+
"diagram",
|
|
20
|
+
"editor",
|
|
21
|
+
"graph",
|
|
22
|
+
"node",
|
|
23
|
+
"link",
|
|
24
|
+
"port",
|
|
25
|
+
"slot",
|
|
26
|
+
"vue3",
|
|
27
|
+
"composition-api",
|
|
28
|
+
"vue-flow",
|
|
29
|
+
"vueflow",
|
|
30
|
+
"typescript"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/vue-flow-core.js",
|
|
33
|
+
"module": "./dist/vue-flow-core.mjs",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"unpkg": "./dist/vue-flow-core.iife.js",
|
|
36
|
+
"jsdelivr": "./dist/vue-flow-core.iife.js",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/vue-flow-core.mjs",
|
|
41
|
+
"require": "./dist/vue-flow-core.js"
|
|
42
|
+
},
|
|
43
|
+
"./dist/style.css": "./dist/style.css",
|
|
44
|
+
"./dist/theme-default.css": "./dist/theme-default.css"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"*.d.ts"
|
|
49
|
+
],
|
|
50
|
+
"sideEffects": [
|
|
51
|
+
"*.css"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"registry": "https://registry.npmjs.org/"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"vue": "^3.3.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@vueuse/core": "^10.5.0",
|
|
62
|
+
"d3-drag": "^3.0.0",
|
|
63
|
+
"d3-interpolate": "^3.0.1",
|
|
64
|
+
"d3-selection": "^3.0.0",
|
|
65
|
+
"d3-zoom": "^3.0.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@rollup/plugin-replace": "^5.0.3",
|
|
69
|
+
"@types/d3-drag": "^3.0.7",
|
|
70
|
+
"@types/d3-interpolate": "^3.0.4",
|
|
71
|
+
"@types/d3-selection": "^3.0.11",
|
|
72
|
+
"@types/d3-transition": "^3.0.9",
|
|
73
|
+
"@types/d3-zoom": "^3.0.8",
|
|
74
|
+
"@vitejs/plugin-vue": "^4.4.0",
|
|
75
|
+
"autoprefixer": "^10.4.16",
|
|
76
|
+
"postcss": "^8.4.31",
|
|
77
|
+
"postcss-cli": "^10.1.0",
|
|
78
|
+
"postcss-nested": "^6.0.1",
|
|
79
|
+
"vite": "^4.4.11",
|
|
80
|
+
"vue-tsc": "^1.8.16",
|
|
81
|
+
"@tooling/eslint-config": "0.0.0",
|
|
82
|
+
"@tooling/tsconfig": "0.0.0"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"dev": "pnpm types:watch & pnpm build:watch",
|
|
86
|
+
"build": "vite build && vite build -c vite.config.iife.ts",
|
|
87
|
+
"build:watch": "vite build --watch & vite build -c vite.config.iife.ts --watch",
|
|
88
|
+
"types": "vue-tsc --declaration --emitDeclarationOnly && tsc -p ./tsconfig.build.json && shx rm -rf tmp && pnpm lint:dist",
|
|
89
|
+
"types:watch": "vue-tsc --declaration --emitDeclarationOnly --watch & tsc -p ./tsconfig.build.json --watch",
|
|
90
|
+
"theme": "postcss src/style.css -o dist/style.css && postcss src/theme-default.css -o dist/theme-default.css",
|
|
91
|
+
"lint": "eslint --ext .js,.ts,.vue ./",
|
|
92
|
+
"lint:dist": "eslint --ext \".ts,.tsx\" -c .eslintrc.js --fix --ignore-pattern !**/* ./dist",
|
|
93
|
+
"test": "exit 0"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thxgg/steward-prod",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": true,
|
|
6
6
|
"dependencies": {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"@swc/helpers": "0.5.18",
|
|
22
22
|
"@tanstack/virtual-core": "3.13.18",
|
|
23
23
|
"@tanstack/vue-virtual": "3.13.18",
|
|
24
|
+
"@vue-flow/background": "1.3.2",
|
|
25
|
+
"@vue-flow/controls": "1.1.3",
|
|
26
|
+
"@vue-flow/core": "1.48.2",
|
|
24
27
|
"@vue/compiler-core": "3.5.28",
|
|
25
28
|
"@vue/compiler-dom": "3.5.28",
|
|
26
29
|
"@vue/compiler-ssr": "3.5.28",
|
package/README.md
CHANGED
|
@@ -69,9 +69,10 @@ prd mcp
|
|
|
69
69
|
│
|
|
70
70
|
▼
|
|
71
71
|
┌─────────────────────────────────────────┐
|
|
72
|
-
│
|
|
72
|
+
│ Codemode MCP (`execute` + prompts) │
|
|
73
73
|
│ - VM sandbox │
|
|
74
74
|
│ - APIs: repos, prds, git, state │
|
|
75
|
+
│ - Prompts: create/break/complete flow │
|
|
75
76
|
└─────────────────────────────────────────┘
|
|
76
77
|
│
|
|
77
78
|
▼
|
|
@@ -85,6 +86,14 @@ prd mcp
|
|
|
85
86
|
|
|
86
87
|
Steward exposes one MCP tool: `execute`.
|
|
87
88
|
|
|
89
|
+
It also exposes workflow prompts:
|
|
90
|
+
|
|
91
|
+
- `create_prd(feature_request)`
|
|
92
|
+
- `break_into_tasks(prd_slug)`
|
|
93
|
+
- `complete_next_task(prd_slug)`
|
|
94
|
+
|
|
95
|
+
In OpenCode these are shown as MCP slash entries like `/prd:create_prd:mcp`.
|
|
96
|
+
|
|
88
97
|
```js
|
|
89
98
|
const repo = await repos.current()
|
|
90
99
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/host/src/mcp.js
CHANGED
|
@@ -4,6 +4,7 @@ import { z } from 'zod';
|
|
|
4
4
|
import { assertSqliteRuntimeSupport } from '../../server/utils/db.js';
|
|
5
5
|
import { execute } from './executor.js';
|
|
6
6
|
import { getExecuteToolDescription } from './help.js';
|
|
7
|
+
import { registerStewardPrompts } from './prompts.js';
|
|
7
8
|
function serializeEnvelope(envelope) {
|
|
8
9
|
try {
|
|
9
10
|
return JSON.stringify(envelope, null, 2);
|
|
@@ -55,6 +56,7 @@ export async function runMcpServer() {
|
|
|
55
56
|
name: 'steward',
|
|
56
57
|
version: '0.1.0'
|
|
57
58
|
});
|
|
59
|
+
registerStewardPrompts(server);
|
|
58
60
|
server.tool('execute', getExecuteToolDescription(), {
|
|
59
61
|
code: z.string().optional()
|
|
60
62
|
}, async ({ code }) => {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function textMessage(text) {
|
|
3
|
+
return {
|
|
4
|
+
role: 'user',
|
|
5
|
+
content: {
|
|
6
|
+
type: 'text',
|
|
7
|
+
text
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function createPrdPrompt(featureRequest) {
|
|
12
|
+
return [
|
|
13
|
+
'You are using Steward MCP to create a new Product Requirements Document (PRD).',
|
|
14
|
+
'',
|
|
15
|
+
'User request:',
|
|
16
|
+
featureRequest,
|
|
17
|
+
'',
|
|
18
|
+
'Workflow:',
|
|
19
|
+
'1. Clarify any major ambiguities with targeted questions before writing the PRD.',
|
|
20
|
+
'2. Derive a kebab-case slug and create docs/prd/<slug>.md.',
|
|
21
|
+
'3. Use this PRD structure at minimum:',
|
|
22
|
+
' - # PRD: <Feature Name>',
|
|
23
|
+
' - **Author:** Generated',
|
|
24
|
+
' - **Date:** <YYYY-MM-DD>',
|
|
25
|
+
' - **Status:** Draft',
|
|
26
|
+
' - Problem Statement, Users, Proposed Solution, Scope, Technical Considerations, Success Criteria, Risks.',
|
|
27
|
+
'4. Before finishing, verify Steward repository registration with the execute tool:',
|
|
28
|
+
' const existing = await repos.list()',
|
|
29
|
+
' return existing.map((repo) => ({ id: repo.id, name: repo.name, path: repo.path }))',
|
|
30
|
+
' If no repository is registered, register this repository with repos.add(<absolute_path>, <optional_name>).',
|
|
31
|
+
'5. Do not generate tasks in this step.',
|
|
32
|
+
'6. End with:',
|
|
33
|
+
' - created PRD path',
|
|
34
|
+
' - chosen slug',
|
|
35
|
+
' - key product decisions and open questions',
|
|
36
|
+
' - recommended next command: /prd:break_into_tasks <slug> (shown as :mcp in autocomplete).'
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
function breakIntoTasksPrompt(prdSlug) {
|
|
40
|
+
return [
|
|
41
|
+
'You are converting a PRD into structured Steward task state.',
|
|
42
|
+
'',
|
|
43
|
+
'PRD slug:',
|
|
44
|
+
prdSlug,
|
|
45
|
+
'',
|
|
46
|
+
'Workflow:',
|
|
47
|
+
'1. Load docs/prd/<slug>.md. If not found, list available PRDs and stop with a clear correction.',
|
|
48
|
+
'2. Update PRD status from Draft to Approved in the markdown file.',
|
|
49
|
+
'3. Build tasks JSON with this shape:',
|
|
50
|
+
' { prd: { name, source, createdAt }, tasks: [{ id, category, title, description, steps, passes, dependencies, priority, status }] }',
|
|
51
|
+
'4. Use these category values only: setup, feature, integration, testing, documentation.',
|
|
52
|
+
'5. Use these priority values only: critical, high, medium, low.',
|
|
53
|
+
'6. Set every generated task status to pending and make passes an array of testable criteria.',
|
|
54
|
+
'7. Build progress JSON with this shape:',
|
|
55
|
+
' { prdName, totalTasks, completed, inProgress, blocked, startedAt, lastUpdated, patterns, taskLogs }',
|
|
56
|
+
' Initialize with completed=0, inProgress=0, blocked=0, startedAt=null, patterns=[], taskLogs=[].',
|
|
57
|
+
'8. Persist state through execute tool:',
|
|
58
|
+
` await state.upsertCurrent("${prdSlug}", { tasks: <tasksJson>, progress: <progressJson> })`,
|
|
59
|
+
` return await state.getCurrent("${prdSlug}")`,
|
|
60
|
+
'9. Report task count, category breakdown, critical path, and recommended next command:',
|
|
61
|
+
` /prd:complete_next_task ${prdSlug} (shown as :mcp in autocomplete).`
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
function completeNextTaskPrompt(prdSlug) {
|
|
65
|
+
return [
|
|
66
|
+
'You are completing the next PRD task with Steward state tracking and commits.',
|
|
67
|
+
'',
|
|
68
|
+
'PRD slug:',
|
|
69
|
+
prdSlug,
|
|
70
|
+
'',
|
|
71
|
+
'Workflow:',
|
|
72
|
+
`1. Load PRD state using execute tool: state.getCurrent("${prdSlug}").`,
|
|
73
|
+
` If tasks are missing, stop and instruct to run /prd:break_into_tasks ${prdSlug}.`,
|
|
74
|
+
'2. Select the next task:',
|
|
75
|
+
' - first task with status=in_progress, otherwise',
|
|
76
|
+
' - first pending task whose dependencies are all completed.',
|
|
77
|
+
'3. Immediately mark the task in progress and save with state.upsertCurrent:',
|
|
78
|
+
' - task.status = in_progress',
|
|
79
|
+
' - task.startedAt = ISO timestamp (if absent)',
|
|
80
|
+
' - progress.inProgress updated',
|
|
81
|
+
' - progress.lastUpdated updated',
|
|
82
|
+
' - taskLogs entry exists with { taskId, status: in_progress, startedAt }',
|
|
83
|
+
'4. Implement only this task in repository files.',
|
|
84
|
+
'5. Run validation loops relevant to the project before commit (typecheck, tests, lint, format/build where applicable).',
|
|
85
|
+
'6. Commit task-related changes with a concise conventional commit message.',
|
|
86
|
+
'7. Capture commit SHAs and update taskLogs[].commits (use { sha, repo } objects for nested repos when needed).',
|
|
87
|
+
'8. Mark task completed and save with state.upsertCurrent:',
|
|
88
|
+
' - task.status = completed',
|
|
89
|
+
' - task.completedAt = ISO timestamp',
|
|
90
|
+
' - progress.completed / progress.inProgress / progress.lastUpdated updated',
|
|
91
|
+
' - taskLogs entry updated with completedAt, implemented, filesChanged, learnings, commits',
|
|
92
|
+
'9. If all tasks are completed, output exactly: <tasks>COMPLETE</tasks>.',
|
|
93
|
+
'10. Report what changed, which commit(s) were created, and the next pending task if any.'
|
|
94
|
+
].join('\n');
|
|
95
|
+
}
|
|
96
|
+
export function registerStewardPrompts(server) {
|
|
97
|
+
server.registerPrompt('create_prd', {
|
|
98
|
+
title: 'Create PRD',
|
|
99
|
+
description: 'Create a PRD markdown document and prepare Steward workflow context.',
|
|
100
|
+
argsSchema: {
|
|
101
|
+
feature_request: z.string().describe('Feature request, idea, or problem statement for the PRD')
|
|
102
|
+
}
|
|
103
|
+
}, async ({ feature_request }) => ({
|
|
104
|
+
description: 'Guided workflow for creating a PRD file.',
|
|
105
|
+
messages: [textMessage(createPrdPrompt(feature_request))]
|
|
106
|
+
}));
|
|
107
|
+
server.registerPrompt('break_into_tasks', {
|
|
108
|
+
title: 'Break Into Tasks',
|
|
109
|
+
description: 'Convert a PRD into tasks.json/progress.json style state and save it.',
|
|
110
|
+
argsSchema: {
|
|
111
|
+
prd_slug: z.string().describe('PRD slug, usually the filename in docs/prd without .md')
|
|
112
|
+
}
|
|
113
|
+
}, async ({ prd_slug }) => ({
|
|
114
|
+
description: 'Workflow for converting an approved PRD into task state.',
|
|
115
|
+
messages: [textMessage(breakIntoTasksPrompt(prd_slug))]
|
|
116
|
+
}));
|
|
117
|
+
server.registerPrompt('complete_next_task', {
|
|
118
|
+
title: 'Complete Next Task',
|
|
119
|
+
description: 'Complete the next in-progress/pending task and persist state updates.',
|
|
120
|
+
argsSchema: {
|
|
121
|
+
prd_slug: z.string().describe('PRD slug whose next task should be completed')
|
|
122
|
+
}
|
|
123
|
+
}, async ({ prd_slug }) => ({
|
|
124
|
+
description: 'Workflow for task execution, verification, commit capture, and state persistence.',
|
|
125
|
+
messages: [textMessage(completeNextTaskPrompt(prd_slug))]
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getPrdState, getPrdStateSummaries, migrateLegacyStateForRepo } from './prd-state.js';
|
|
4
|
+
const NODE_SEPARATOR = '::';
|
|
5
|
+
const MISSING_PREFIX = 'missing';
|
|
6
|
+
export function createTaskNodeId(prdSlug, taskId) {
|
|
7
|
+
return `${prdSlug}${NODE_SEPARATOR}${taskId}`;
|
|
8
|
+
}
|
|
9
|
+
function createMissingNodeId(prdSlug, taskId) {
|
|
10
|
+
return `${MISSING_PREFIX}${NODE_SEPARATOR}${prdSlug}${NODE_SEPARATOR}${taskId}`;
|
|
11
|
+
}
|
|
12
|
+
function createEdgeId(source, target) {
|
|
13
|
+
return `${source}->${target}`;
|
|
14
|
+
}
|
|
15
|
+
function parseDependency(rawDependency, currentPrdSlug) {
|
|
16
|
+
const trimmed = rawDependency.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return {
|
|
19
|
+
prdSlug: currentPrdSlug,
|
|
20
|
+
taskId: rawDependency,
|
|
21
|
+
reference: rawDependency
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const parts = trimmed.split(NODE_SEPARATOR);
|
|
25
|
+
if (parts.length === 2) {
|
|
26
|
+
const [prdSlug, taskId] = parts;
|
|
27
|
+
if (prdSlug && taskId) {
|
|
28
|
+
return {
|
|
29
|
+
prdSlug,
|
|
30
|
+
taskId,
|
|
31
|
+
reference: `${prdSlug}${NODE_SEPARATOR}${taskId}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
prdSlug: currentPrdSlug,
|
|
37
|
+
taskId: trimmed,
|
|
38
|
+
reference: trimmed
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function resolvePrdName(repo, prdSlug) {
|
|
42
|
+
const prdPath = join(repo.path, 'docs', 'prd', `${prdSlug}.md`);
|
|
43
|
+
try {
|
|
44
|
+
const content = await fs.readFile(prdPath, 'utf-8');
|
|
45
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
46
|
+
return h1Match?.[1]?.trim() || prdSlug;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return prdSlug;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function buildStats(taskNodes, unresolvedCount) {
|
|
53
|
+
const pending = taskNodes.filter((node) => node.status === 'pending').length;
|
|
54
|
+
const inProgress = taskNodes.filter((node) => node.status === 'in_progress').length;
|
|
55
|
+
const completed = taskNodes.filter((node) => node.status === 'completed').length;
|
|
56
|
+
return {
|
|
57
|
+
total: taskNodes.length,
|
|
58
|
+
pending,
|
|
59
|
+
inProgress,
|
|
60
|
+
completed,
|
|
61
|
+
unresolved: unresolvedCount
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function sortNodes(nodes) {
|
|
65
|
+
return [...nodes].sort((a, b) => {
|
|
66
|
+
if (a.kind !== b.kind) {
|
|
67
|
+
return a.kind === 'task' ? -1 : 1;
|
|
68
|
+
}
|
|
69
|
+
return a.id.localeCompare(b.id);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function sortEdges(edges) {
|
|
73
|
+
return [...edges].sort((a, b) => a.id.localeCompare(b.id));
|
|
74
|
+
}
|
|
75
|
+
function buildGraphFromInputs(inputs) {
|
|
76
|
+
const taskNodes = new Map();
|
|
77
|
+
const missingNodes = new Map();
|
|
78
|
+
const edges = new Map();
|
|
79
|
+
for (const input of inputs) {
|
|
80
|
+
for (const task of input.tasks) {
|
|
81
|
+
const nodeId = createTaskNodeId(input.prdSlug, task.id);
|
|
82
|
+
taskNodes.set(nodeId, {
|
|
83
|
+
id: nodeId,
|
|
84
|
+
kind: 'task',
|
|
85
|
+
taskId: task.id,
|
|
86
|
+
prdSlug: input.prdSlug,
|
|
87
|
+
prdName: input.prdName,
|
|
88
|
+
title: task.title,
|
|
89
|
+
status: task.status,
|
|
90
|
+
category: task.category,
|
|
91
|
+
priority: task.priority
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const input of inputs) {
|
|
96
|
+
for (const task of input.tasks) {
|
|
97
|
+
const targetId = createTaskNodeId(input.prdSlug, task.id);
|
|
98
|
+
for (const rawDependency of task.dependencies) {
|
|
99
|
+
const dependency = parseDependency(rawDependency, input.prdSlug);
|
|
100
|
+
const sourceTaskId = createTaskNodeId(dependency.prdSlug, dependency.taskId);
|
|
101
|
+
if (taskNodes.has(sourceTaskId)) {
|
|
102
|
+
const edgeId = createEdgeId(sourceTaskId, targetId);
|
|
103
|
+
edges.set(edgeId, {
|
|
104
|
+
id: edgeId,
|
|
105
|
+
source: sourceTaskId,
|
|
106
|
+
target: targetId,
|
|
107
|
+
type: 'dependency'
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const missingNodeId = createMissingNodeId(dependency.prdSlug, dependency.taskId);
|
|
112
|
+
if (!missingNodes.has(missingNodeId)) {
|
|
113
|
+
missingNodes.set(missingNodeId, {
|
|
114
|
+
id: missingNodeId,
|
|
115
|
+
kind: 'external',
|
|
116
|
+
title: `Missing: ${dependency.reference}`,
|
|
117
|
+
unresolved: true,
|
|
118
|
+
dependencyRef: dependency.reference
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const unresolvedEdgeId = createEdgeId(missingNodeId, targetId);
|
|
122
|
+
edges.set(unresolvedEdgeId, {
|
|
123
|
+
id: unresolvedEdgeId,
|
|
124
|
+
source: missingNodeId,
|
|
125
|
+
target: targetId,
|
|
126
|
+
type: 'dependency',
|
|
127
|
+
unresolved: true
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const taskNodeList = [...taskNodes.values()];
|
|
133
|
+
const nodeList = sortNodes([...taskNodeList, ...missingNodes.values()]);
|
|
134
|
+
const edgeList = sortEdges([...edges.values()]);
|
|
135
|
+
return {
|
|
136
|
+
nodes: nodeList,
|
|
137
|
+
edges: edgeList,
|
|
138
|
+
stats: buildStats(taskNodeList, missingNodes.size)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export async function buildPrdGraph(repo, prdSlug) {
|
|
142
|
+
await migrateLegacyStateForRepo(repo);
|
|
143
|
+
const [state, prdName] = await Promise.all([
|
|
144
|
+
getPrdState(repo.id, prdSlug),
|
|
145
|
+
resolvePrdName(repo, prdSlug)
|
|
146
|
+
]);
|
|
147
|
+
const tasks = Array.isArray(state?.tasks?.tasks) ? state.tasks.tasks : [];
|
|
148
|
+
const graph = buildGraphFromInputs([
|
|
149
|
+
{
|
|
150
|
+
prdSlug,
|
|
151
|
+
prdName,
|
|
152
|
+
tasks
|
|
153
|
+
}
|
|
154
|
+
]);
|
|
155
|
+
return {
|
|
156
|
+
scope: 'prd',
|
|
157
|
+
repoId: repo.id,
|
|
158
|
+
prdSlug,
|
|
159
|
+
nodes: graph.nodes,
|
|
160
|
+
edges: graph.edges,
|
|
161
|
+
stats: graph.stats
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export async function buildRepoGraph(repo) {
|
|
165
|
+
await migrateLegacyStateForRepo(repo);
|
|
166
|
+
const summaries = await getPrdStateSummaries(repo.id);
|
|
167
|
+
const slugs = [...summaries.keys()].sort((a, b) => a.localeCompare(b));
|
|
168
|
+
const inputs = [];
|
|
169
|
+
for (const slug of slugs) {
|
|
170
|
+
const state = await getPrdState(repo.id, slug);
|
|
171
|
+
const tasks = state?.tasks?.tasks;
|
|
172
|
+
if (!Array.isArray(tasks)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
inputs.push({
|
|
176
|
+
prdSlug: slug,
|
|
177
|
+
prdName: await resolvePrdName(repo, slug),
|
|
178
|
+
tasks
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const graph = buildGraphFromInputs(inputs);
|
|
182
|
+
return {
|
|
183
|
+
scope: 'repo',
|
|
184
|
+
repoId: repo.id,
|
|
185
|
+
prds: inputs.map((input) => input.prdSlug).sort((a, b) => a.localeCompare(b)),
|
|
186
|
+
nodes: graph.nodes,
|
|
187
|
+
edges: graph.edges,
|
|
188
|
+
stats: graph.stats
|
|
189
|
+
};
|
|
190
|
+
}
|
package/docs/MCP.md
CHANGED
|
@@ -4,7 +4,9 @@ Steward ships a codemode MCP server.
|
|
|
4
4
|
|
|
5
5
|
- Command: `prd mcp`
|
|
6
6
|
- Transport: stdio
|
|
7
|
-
-
|
|
7
|
+
- MCP surface:
|
|
8
|
+
- Tool: `execute`
|
|
9
|
+
- Prompts: `create_prd`, `break_into_tasks`, `complete_next_task`
|
|
8
10
|
|
|
9
11
|
The `execute` tool runs JavaScript in a VM sandbox with these APIs in scope:
|
|
10
12
|
|
|
@@ -98,6 +100,16 @@ In-sandbox discovery helper:
|
|
|
98
100
|
|
|
99
101
|
- `steward.help()`
|
|
100
102
|
|
|
103
|
+
## MCP Prompts
|
|
104
|
+
|
|
105
|
+
Steward also exposes MCP prompts so MCP clients can surface command-like workflows.
|
|
106
|
+
|
|
107
|
+
- `create_prd(feature_request)`
|
|
108
|
+
- `break_into_tasks(prd_slug)`
|
|
109
|
+
- `complete_next_task(prd_slug)`
|
|
110
|
+
|
|
111
|
+
In OpenCode, these appear in slash-command autocomplete as MCP commands (for example `/prd:create_prd:mcp`) and insert as `/prd:create_prd` when selected.
|
|
112
|
+
|
|
101
113
|
## Available APIs
|
|
102
114
|
|
|
103
115
|
### `repos`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thxgg/steward",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Local-first PRD workflow steward with codemode MCP and web UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "thxgg",
|
|
@@ -54,7 +54,11 @@
|
|
|
54
54
|
"prepack": "npm run build"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"@dagrejs/dagre": "^2.0.4",
|
|
57
58
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
59
|
+
"@vue-flow/background": "^1.3.2",
|
|
60
|
+
"@vue-flow/controls": "^1.1.3",
|
|
61
|
+
"@vue-flow/core": "^1.48.2",
|
|
58
62
|
"@vueuse/core": "^14.1.0",
|
|
59
63
|
"chokidar": "^5.0.0",
|
|
60
64
|
"class-variance-authority": "^0.7.1",
|