@thxgg/steward 0.1.23 → 0.1.24
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/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/8c342d49-fe70-4f67-a987-821c16f86125.json +1 -0
- package/.output/server/chunks/build/styles.mjs +2 -2
- package/.output/server/chunks/nitro/nitro.mjs +504 -504
- package/.output/server/package.json +1 -1
- package/README.md +14 -9
- package/dist/host/src/api/repo-context.js +19 -2
- package/dist/host/src/prompts.js +60 -36
- package/docs/MCP.md +15 -4
- package/package.json +1 -1
- package/.output/public/_nuxt/builds/meta/bd99c09c-d991-4bcb-8c66-ab2088e1da03.json +0 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Add to your MCP client config:
|
|
|
33
33
|
```json
|
|
34
34
|
{
|
|
35
35
|
"mcpServers": {
|
|
36
|
-
"
|
|
36
|
+
"steward": {
|
|
37
37
|
"command": "npx",
|
|
38
38
|
"args": ["-y", "@thxgg/steward", "mcp"]
|
|
39
39
|
}
|
|
@@ -41,6 +41,8 @@ Add to your MCP client config:
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
The MCP server key (`steward` above) controls the prompt prefix in slash commands.
|
|
45
|
+
|
|
44
46
|
Steward MCP requires a Node runtime with built-in sqlite support (`node:sqlite`) for `repos`, `prds`, and `state` APIs.
|
|
45
47
|
If you see `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite`, run with sqlite enabled:
|
|
46
48
|
|
|
@@ -89,10 +91,14 @@ Steward exposes one MCP tool: `execute`.
|
|
|
89
91
|
It also exposes workflow prompts:
|
|
90
92
|
|
|
91
93
|
- `create_prd(feature_request)`
|
|
92
|
-
- `break_into_tasks(prd_slug)`
|
|
93
|
-
- `complete_next_task(prd_slug)`
|
|
94
|
+
- `break_into_tasks(prd_slug?)`
|
|
95
|
+
- `complete_next_task(prd_slug?)`
|
|
96
|
+
|
|
97
|
+
In OpenCode these are shown as MCP slash entries like `/steward:create_prd:mcp` and inserted as `/steward:create_prd`.
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
- `prd_slug` is optional for break/complete prompts.
|
|
100
|
+
- When omitted, Steward workflows auto-resolve the slug from repo state.
|
|
101
|
+
- `complete_next_task` includes required commit hygiene (one-line commit message, no `Co-authored-by`, no task-related dirty changes left behind).
|
|
96
102
|
|
|
97
103
|
```js
|
|
98
104
|
const repo = await repos.current()
|
|
@@ -175,13 +181,12 @@ npm run build
|
|
|
175
181
|
| `PRD_STATE_HOME` | Base directory for DB (`state.db` inside) |
|
|
176
182
|
| `XDG_DATA_HOME` | Fallback base path for default DB location |
|
|
177
183
|
|
|
178
|
-
## OpenCode
|
|
184
|
+
## OpenCode Integration
|
|
179
185
|
|
|
180
|
-
|
|
186
|
+
Steward now uses MCP-registered prompts as the single workflow surface.
|
|
181
187
|
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
- Script: `prd-db.mjs`
|
|
188
|
+
- Use MCP prompts directly (for example `/steward:create_prd`, `/steward:break_into_tasks`, `/steward:complete_next_task`).
|
|
189
|
+
- This repository no longer ships separate OpenCode command/skill bundles for PRD workflows.
|
|
185
190
|
|
|
186
191
|
## License
|
|
187
192
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
1
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
2
2
|
import { getRepoById, getRepos } from '../../../server/utils/repos.js';
|
|
3
3
|
export class RepoLookupError extends Error {
|
|
4
4
|
code;
|
|
@@ -22,6 +22,18 @@ function formatKnownRepos(knownRepos) {
|
|
|
22
22
|
.map((repo) => `${repo.id} (${repo.name}) ${repo.path}`)
|
|
23
23
|
.join('; ');
|
|
24
24
|
}
|
|
25
|
+
function isPathWithin(basePath, candidatePath) {
|
|
26
|
+
const relativePath = relative(resolve(basePath), resolve(candidatePath));
|
|
27
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
|
|
28
|
+
}
|
|
29
|
+
function resolveRepoFromCwd(repos, cwd) {
|
|
30
|
+
const matches = repos.filter((repo) => isPathWithin(repo.path, cwd));
|
|
31
|
+
if (matches.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
matches.sort((left, right) => resolve(right.path).length - resolve(left.path).length);
|
|
35
|
+
return matches[0] ?? null;
|
|
36
|
+
}
|
|
25
37
|
export async function requireRepo(repoId) {
|
|
26
38
|
const repo = await getRepoById(repoId);
|
|
27
39
|
if (repo) {
|
|
@@ -56,5 +68,10 @@ export async function requireCurrentRepo() {
|
|
|
56
68
|
if (knownRepos.length === 0) {
|
|
57
69
|
throw new RepoLookupError('No repositories are registered. Use repos.add(path) first.', 'NO_REPOS', { knownRepos });
|
|
58
70
|
}
|
|
59
|
-
|
|
71
|
+
const cwd = resolve(process.cwd());
|
|
72
|
+
const repoFromCwd = resolveRepoFromCwd(allRepos, cwd);
|
|
73
|
+
if (repoFromCwd) {
|
|
74
|
+
return repoFromCwd;
|
|
75
|
+
}
|
|
76
|
+
throw new RepoLookupError(`Cannot resolve a current repository because ${knownRepos.length} repositories are registered and the working directory does not map to a registered repo. Use an explicit repoId or by-path API. CWD: ${cwd}. Known repositories: ${formatKnownRepos(knownRepos)}`, 'AMBIGUOUS_REPO', { cwd, knownRepos });
|
|
60
77
|
}
|
package/dist/host/src/prompts.js
CHANGED
|
@@ -33,64 +33,88 @@ function createPrdPrompt(featureRequest) {
|
|
|
33
33
|
' - created PRD path',
|
|
34
34
|
' - chosen slug',
|
|
35
35
|
' - key product decisions and open questions',
|
|
36
|
-
' - recommended next
|
|
36
|
+
' - recommended next MCP prompt: /<your-mcp-server-prefix>:break_into_tasks <slug> (autocomplete may show an extra :mcp suffix).'
|
|
37
37
|
].join('\n');
|
|
38
38
|
}
|
|
39
39
|
function breakIntoTasksPrompt(prdSlug) {
|
|
40
|
+
const resolvedInput = typeof prdSlug === 'string' ? prdSlug.trim() : '';
|
|
40
41
|
return [
|
|
41
42
|
'You are converting a PRD into structured Steward task state.',
|
|
42
43
|
'',
|
|
43
|
-
|
|
44
|
-
prdSlug,
|
|
44
|
+
`PRD slug input: ${resolvedInput || '<auto-resolve>'}`,
|
|
45
45
|
'',
|
|
46
46
|
'Workflow:',
|
|
47
|
-
'1.
|
|
48
|
-
'
|
|
49
|
-
'
|
|
47
|
+
'1. Resolve repository and PRD slug before writing anything:',
|
|
48
|
+
' - Resolve repo with execute tool: const repo = await repos.current().',
|
|
49
|
+
' - If slug input is present, use it as resolvedSlug.',
|
|
50
|
+
' - If slug input is missing, list PRDs: const prdList = await prds.list(repo.id).',
|
|
51
|
+
' - Auto-pick resolvedSlug deterministically:',
|
|
52
|
+
' a) prefer PRDs with hasState=false, newest modifiedAt first, then slug ascending',
|
|
53
|
+
' b) otherwise pick newest modifiedAt, then slug ascending',
|
|
54
|
+
' - If no PRDs exist, stop and ask user to run create_prd first.',
|
|
55
|
+
'2. Load docs/prd/<resolvedSlug>.md. If missing, list available PRDs and stop with a clear correction.',
|
|
56
|
+
'3. Update PRD status from Draft to Approved in the markdown file.',
|
|
57
|
+
'4. Build tasks JSON with this shape:',
|
|
50
58
|
' { prd: { name, source, createdAt }, tasks: [{ id, category, title, description, steps, passes, dependencies, priority, status }] }',
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
59
|
+
'5. Use these category values only: setup, feature, integration, testing, documentation.',
|
|
60
|
+
'6. Use these priority values only: critical, high, medium, low.',
|
|
61
|
+
'7. Set every generated task status to pending and make passes an array of testable criteria.',
|
|
62
|
+
'8. Build progress JSON with this shape:',
|
|
55
63
|
' { prdName, totalTasks, completed, inProgress, blocked, startedAt, lastUpdated, patterns, taskLogs }',
|
|
56
64
|
' Initialize with completed=0, inProgress=0, blocked=0, startedAt=null, patterns=[], taskLogs=[].',
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
'
|
|
61
|
-
|
|
65
|
+
'9. Persist state through execute tool:',
|
|
66
|
+
' await state.upsertCurrent(resolvedSlug, { tasks: tasksJson, progress: progressJson })',
|
|
67
|
+
' return await state.getCurrent(resolvedSlug)',
|
|
68
|
+
'10. Report task count, category breakdown, critical path, resolvedSlug, and recommended next MCP prompt:',
|
|
69
|
+
' /<your-mcp-server-prefix>:complete_next_task <resolvedSlug> (autocomplete may show an extra :mcp suffix).'
|
|
62
70
|
].join('\n');
|
|
63
71
|
}
|
|
64
72
|
function completeNextTaskPrompt(prdSlug) {
|
|
73
|
+
const resolvedInput = typeof prdSlug === 'string' ? prdSlug.trim() : '';
|
|
65
74
|
return [
|
|
66
75
|
'You are completing the next PRD task with Steward state tracking and commits.',
|
|
67
76
|
'',
|
|
68
|
-
|
|
69
|
-
prdSlug,
|
|
77
|
+
`PRD slug input: ${resolvedInput || '<auto-resolve>'}`,
|
|
70
78
|
'',
|
|
71
79
|
'Workflow:',
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'
|
|
80
|
+
'1. Resolve repository and PRD slug before any task updates:',
|
|
81
|
+
' - Resolve repo with execute tool: const repo = await repos.current().',
|
|
82
|
+
' - If slug input is present, use it as resolvedSlug.',
|
|
83
|
+
' - If slug input is missing, list PRDs: const prdList = await prds.list(repo.id).',
|
|
84
|
+
' - Build actionable candidates where hasState=true and completedCount < taskCount.',
|
|
85
|
+
' - Auto-pick resolvedSlug as the latest actionable candidate (modifiedAt desc, then slug asc).',
|
|
86
|
+
' - If no actionable candidates exist, fall back to latest PRD with hasState=true, then latest PRD overall.',
|
|
87
|
+
' - If no PRD exists, stop and ask user to run create_prd first.',
|
|
88
|
+
'2. Load PRD state with execute tool: state.getCurrent(resolvedSlug).',
|
|
89
|
+
' If tasks are missing, stop and instruct to run /<your-mcp-server-prefix>:break_into_tasks <resolvedSlug>.',
|
|
90
|
+
'3. Select the next task:',
|
|
75
91
|
' - first task with status=in_progress, otherwise',
|
|
76
92
|
' - first pending task whose dependencies are all completed.',
|
|
77
|
-
'
|
|
93
|
+
'4. Immediately mark the task in progress and save with state.upsertCurrent:',
|
|
78
94
|
' - task.status = in_progress',
|
|
79
95
|
' - task.startedAt = ISO timestamp (if absent)',
|
|
80
96
|
' - progress.inProgress updated',
|
|
81
97
|
' - progress.lastUpdated updated',
|
|
82
98
|
' - taskLogs entry exists with { taskId, status: in_progress, startedAt }',
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
99
|
+
'5. Implement only this task in repository files.',
|
|
100
|
+
'6. Run validation loops relevant to the project before commit (typecheck, tests, lint, format/build where applicable).',
|
|
101
|
+
'7. Commit requirements (mandatory when task-related files changed):',
|
|
102
|
+
' - Stage only task-related files (do not stage unrelated work).',
|
|
103
|
+
' - Create at least one commit before finishing when task-related changes exist.',
|
|
104
|
+
' - Use a one-line conventional commit subject (single -m line, no body).',
|
|
105
|
+
' - Do not include trailers or footers, especially no Co-authored-by.',
|
|
106
|
+
' - Prefer execute tool git.commitIfChanged(repo.id, message, { paths: taskRelatedFiles }).',
|
|
107
|
+
' - If task-related changes exist, commit result must be committed=true before finishing.',
|
|
108
|
+
' - If commit fails due to checks/hooks, fix issues and create a new commit.',
|
|
109
|
+
' - Verify task-related files are not left uncommitted before final output.',
|
|
110
|
+
'8. Capture commit SHAs and update taskLogs[].commits (use { sha, repo } objects for nested repos when needed).',
|
|
111
|
+
'9. Mark task completed and save with state.upsertCurrent:',
|
|
88
112
|
' - task.status = completed',
|
|
89
113
|
' - task.completedAt = ISO timestamp',
|
|
90
114
|
' - progress.completed / progress.inProgress / progress.lastUpdated updated',
|
|
91
115
|
' - taskLogs entry updated with completedAt, implemented, filesChanged, learnings, commits',
|
|
92
|
-
'
|
|
93
|
-
'
|
|
116
|
+
'10. If all tasks are completed, output exactly: <tasks>COMPLETE</tasks>.',
|
|
117
|
+
'11. Report what changed, resolvedSlug, which commit(s) were created, and the next pending task if any.'
|
|
94
118
|
].join('\n');
|
|
95
119
|
}
|
|
96
120
|
export function registerStewardPrompts(server) {
|
|
@@ -100,28 +124,28 @@ export function registerStewardPrompts(server) {
|
|
|
100
124
|
argsSchema: {
|
|
101
125
|
feature_request: z.string().describe('Feature request, idea, or problem statement for the PRD')
|
|
102
126
|
}
|
|
103
|
-
}, async (
|
|
127
|
+
}, async (args) => ({
|
|
104
128
|
description: 'Guided workflow for creating a PRD file.',
|
|
105
|
-
messages: [textMessage(createPrdPrompt(feature_request))]
|
|
129
|
+
messages: [textMessage(createPrdPrompt(args.feature_request))]
|
|
106
130
|
}));
|
|
107
131
|
server.registerPrompt('break_into_tasks', {
|
|
108
132
|
title: 'Break Into Tasks',
|
|
109
133
|
description: 'Convert a PRD into tasks.json/progress.json style state and save it.',
|
|
110
134
|
argsSchema: {
|
|
111
|
-
prd_slug: z.string().describe('PRD slug
|
|
135
|
+
prd_slug: z.string().optional().describe('Optional PRD slug (filename in docs/prd without .md). Auto-resolved when omitted.')
|
|
112
136
|
}
|
|
113
|
-
}, async (
|
|
137
|
+
}, async (args) => ({
|
|
114
138
|
description: 'Workflow for converting an approved PRD into task state.',
|
|
115
|
-
messages: [textMessage(breakIntoTasksPrompt(prd_slug))]
|
|
139
|
+
messages: [textMessage(breakIntoTasksPrompt(args.prd_slug))]
|
|
116
140
|
}));
|
|
117
141
|
server.registerPrompt('complete_next_task', {
|
|
118
142
|
title: 'Complete Next Task',
|
|
119
143
|
description: 'Complete the next in-progress/pending task and persist state updates.',
|
|
120
144
|
argsSchema: {
|
|
121
|
-
prd_slug: z.string().describe('PRD slug whose next task should be completed')
|
|
145
|
+
prd_slug: z.string().optional().describe('Optional PRD slug whose next task should be completed. Auto-resolved when omitted.')
|
|
122
146
|
}
|
|
123
|
-
}, async (
|
|
147
|
+
}, async (args) => ({
|
|
124
148
|
description: 'Workflow for task execution, verification, commit capture, and state persistence.',
|
|
125
|
-
messages: [textMessage(completeNextTaskPrompt(prd_slug))]
|
|
149
|
+
messages: [textMessage(completeNextTaskPrompt(args.prd_slug))]
|
|
126
150
|
}));
|
|
127
151
|
}
|
package/docs/MCP.md
CHANGED
|
@@ -40,7 +40,7 @@ Example MCP client config:
|
|
|
40
40
|
```json
|
|
41
41
|
{
|
|
42
42
|
"mcpServers": {
|
|
43
|
-
"
|
|
43
|
+
"steward": {
|
|
44
44
|
"command": "prd",
|
|
45
45
|
"args": ["mcp"]
|
|
46
46
|
}
|
|
@@ -48,6 +48,8 @@ Example MCP client config:
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
The MCP server key (`steward` above) determines slash prefix names in clients.
|
|
52
|
+
|
|
51
53
|
## Runtime Requirements
|
|
52
54
|
|
|
53
55
|
- `repos`, `prds`, and `state` APIs require sqlite runtime support.
|
|
@@ -105,10 +107,19 @@ In-sandbox discovery helper:
|
|
|
105
107
|
Steward also exposes MCP prompts so MCP clients can surface command-like workflows.
|
|
106
108
|
|
|
107
109
|
- `create_prd(feature_request)`
|
|
108
|
-
- `break_into_tasks(prd_slug)`
|
|
109
|
-
- `complete_next_task(prd_slug)`
|
|
110
|
+
- `break_into_tasks(prd_slug?)`
|
|
111
|
+
- `complete_next_task(prd_slug?)`
|
|
112
|
+
|
|
113
|
+
In OpenCode, these appear in slash-command autocomplete as MCP commands (for example `/steward:create_prd:mcp`) and insert as `/steward:create_prd` when selected.
|
|
114
|
+
|
|
115
|
+
Notes:
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
- `prd_slug` is optional for break/complete prompts and auto-resolves when omitted.
|
|
118
|
+
- `complete_next_task` requires commit hygiene when task-related files changed:
|
|
119
|
+
- at least one commit is created
|
|
120
|
+
- one-line commit subject only
|
|
121
|
+
- no `Co-authored-by` footer/trailers
|
|
122
|
+
- no task-related dirty files left behind
|
|
112
123
|
|
|
113
124
|
## Available APIs
|
|
114
125
|
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"bd99c09c-d991-4bcb-8c66-ab2088e1da03","timestamp":1772218681776,"prerendered":[]}
|