@thxgg/steward 0.1.12 → 0.1.14
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/b57a8fc3-6a38-4f58-b2ae-54768412ea40.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/_/git.mjs.map +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 +4 -4
- 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 +669 -637
- 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/dist/app/types/graph.js +1 -0
- package/dist/host/src/api/git.js +71 -1
- package/dist/host/src/help.js +12 -0
- package/dist/server/utils/git.js +104 -1
- package/dist/server/utils/task-graph.js +190 -0
- package/docs/MCP.md +21 -0
- 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/6f66fabf-cc26-482b-8adf-f8731dd68f83.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.14",
|
|
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",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/host/src/api/git.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { getCommitDiff, getCommitInfo, getFileContent, getFileDiff, isGitRepo, validatePathInRepo } from '../../../server/utils/git.js';
|
|
1
|
+
import { commitStagedChanges, getCommitDiff, getCommitInfo, getFileContent, getFileDiff, getWorkingTreeStatus, isGitRepo, stagePaths, validatePathInRepo } from '../../../server/utils/git.js';
|
|
2
2
|
import { requireRepo } from './repo-context.js';
|
|
3
|
+
function toGitStatus(status) {
|
|
4
|
+
const hasStagedChanges = status.staged.length > 0;
|
|
5
|
+
const hasChanges = hasStagedChanges || status.unstaged.length > 0 || status.untracked.length > 0;
|
|
6
|
+
return {
|
|
7
|
+
...status,
|
|
8
|
+
hasChanges,
|
|
9
|
+
hasStagedChanges
|
|
10
|
+
};
|
|
11
|
+
}
|
|
3
12
|
function resolveGitRepoPath(repo, repoPath) {
|
|
4
13
|
if (!repoPath) {
|
|
5
14
|
return repo.path;
|
|
@@ -15,6 +24,15 @@ function resolveGitRepoPath(repo, repoPath) {
|
|
|
15
24
|
return matchedRepo.absolutePath;
|
|
16
25
|
}
|
|
17
26
|
export const git = {
|
|
27
|
+
async getStatus(repoId, repoPath) {
|
|
28
|
+
const repo = await requireRepo(repoId);
|
|
29
|
+
const gitRepoPath = resolveGitRepoPath(repo, repoPath);
|
|
30
|
+
if (!await isGitRepo(gitRepoPath)) {
|
|
31
|
+
throw new Error('Resolved path is not a git repository');
|
|
32
|
+
}
|
|
33
|
+
const status = await getWorkingTreeStatus(gitRepoPath);
|
|
34
|
+
return toGitStatus(status);
|
|
35
|
+
},
|
|
18
36
|
async getCommits(repoId, shas, repoPath) {
|
|
19
37
|
if (!Array.isArray(shas) || shas.length === 0) {
|
|
20
38
|
throw new Error('At least one SHA is required');
|
|
@@ -85,5 +103,57 @@ export const git = {
|
|
|
85
103
|
throw new Error('Resolved path is not a git repository');
|
|
86
104
|
}
|
|
87
105
|
return await getFileContent(gitRepoPath, commit, file);
|
|
106
|
+
},
|
|
107
|
+
async commitIfChanged(repoId, message, options) {
|
|
108
|
+
if (!message || !message.trim()) {
|
|
109
|
+
throw new Error('message is required');
|
|
110
|
+
}
|
|
111
|
+
const repo = await requireRepo(repoId);
|
|
112
|
+
const relativeRepoPath = options?.repoPath || '';
|
|
113
|
+
const gitRepoPath = resolveGitRepoPath(repo, relativeRepoPath);
|
|
114
|
+
if (!await isGitRepo(gitRepoPath)) {
|
|
115
|
+
throw new Error('Resolved path is not a git repository');
|
|
116
|
+
}
|
|
117
|
+
const paths = Array.isArray(options?.paths)
|
|
118
|
+
? options.paths.filter((path) => typeof path === 'string' && path.trim().length > 0)
|
|
119
|
+
: [];
|
|
120
|
+
if (paths.length > 0) {
|
|
121
|
+
await stagePaths(gitRepoPath, paths);
|
|
122
|
+
}
|
|
123
|
+
const statusBefore = await getWorkingTreeStatus(gitRepoPath);
|
|
124
|
+
if (statusBefore.staged.length === 0) {
|
|
125
|
+
const noChanges = statusBefore.unstaged.length === 0 && statusBefore.untracked.length === 0;
|
|
126
|
+
return {
|
|
127
|
+
committed: false,
|
|
128
|
+
repoPath: relativeRepoPath,
|
|
129
|
+
staged: statusBefore.staged,
|
|
130
|
+
unstaged: statusBefore.unstaged,
|
|
131
|
+
untracked: statusBefore.untracked,
|
|
132
|
+
reason: noChanges ? 'no_changes' : 'no_staged_changes'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const commit = await commitStagedChanges(gitRepoPath, message);
|
|
136
|
+
if (!commit) {
|
|
137
|
+
return {
|
|
138
|
+
committed: false,
|
|
139
|
+
repoPath: relativeRepoPath,
|
|
140
|
+
staged: statusBefore.staged,
|
|
141
|
+
unstaged: statusBefore.unstaged,
|
|
142
|
+
untracked: statusBefore.untracked,
|
|
143
|
+
reason: 'no_staged_changes'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const statusAfter = await getWorkingTreeStatus(gitRepoPath);
|
|
147
|
+
return {
|
|
148
|
+
committed: true,
|
|
149
|
+
repoPath: relativeRepoPath,
|
|
150
|
+
staged: statusAfter.staged,
|
|
151
|
+
unstaged: statusAfter.unstaged,
|
|
152
|
+
untracked: statusAfter.untracked,
|
|
153
|
+
sha: commit.sha,
|
|
154
|
+
shortSha: commit.shortSha,
|
|
155
|
+
message: commit.message,
|
|
156
|
+
committedFiles: commit.files
|
|
157
|
+
};
|
|
88
158
|
}
|
|
89
159
|
};
|
package/dist/host/src/help.js
CHANGED
|
@@ -39,6 +39,10 @@ const HELP = {
|
|
|
39
39
|
}
|
|
40
40
|
],
|
|
41
41
|
git: [
|
|
42
|
+
{
|
|
43
|
+
signature: 'git.getStatus(repoId, repoPath?)',
|
|
44
|
+
description: 'Load working tree status (staged/unstaged/untracked)'
|
|
45
|
+
},
|
|
42
46
|
{ signature: 'git.getCommits(repoId, shas, repoPath?)', description: 'Load commit metadata' },
|
|
43
47
|
{ signature: 'git.getDiff(repoId, commit, repoPath?)', description: 'Load full commit diff' },
|
|
44
48
|
{
|
|
@@ -48,6 +52,10 @@ const HELP = {
|
|
|
48
52
|
{
|
|
49
53
|
signature: 'git.getFileContent(repoId, commit, file, repoPath?)',
|
|
50
54
|
description: 'Load file content at commit'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
signature: 'git.commitIfChanged(repoId, message, options?)',
|
|
58
|
+
description: 'Stage optional paths and commit when staged changes exist'
|
|
51
59
|
}
|
|
52
60
|
],
|
|
53
61
|
state: [
|
|
@@ -86,6 +94,10 @@ const HELP = {
|
|
|
86
94
|
{
|
|
87
95
|
title: 'Upsert without repoId',
|
|
88
96
|
code: `await state.upsertCurrent('prd-viewer', {\n notes: '# Updated from MCP'\n})\n\nreturn { saved: true }`
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: 'Commit task-related changes when present',
|
|
100
|
+
code: `const repo = await repos.current()\n\nconst result = await git.commitIfChanged(repo.id, 'docs: update task notes', {\n paths: ['docs/prd/prd-viewer.md']\n})\n\nreturn result`
|
|
89
101
|
}
|
|
90
102
|
]
|
|
91
103
|
};
|
package/dist/server/utils/git.js
CHANGED
|
@@ -55,6 +55,109 @@ export function validatePathInRepo(repoPath, filePath) {
|
|
|
55
55
|
const relativePath = relative(resolvedRepo, resolvedFile);
|
|
56
56
|
return !relativePath.startsWith('..') && !isAbsolute(relativePath);
|
|
57
57
|
}
|
|
58
|
+
function dedupeAndSort(values) {
|
|
59
|
+
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
60
|
+
}
|
|
61
|
+
function normalizeStatusPath(rawPath) {
|
|
62
|
+
const trimmed = rawPath.trim();
|
|
63
|
+
if (!trimmed.includes(' -> ')) {
|
|
64
|
+
return trimmed;
|
|
65
|
+
}
|
|
66
|
+
const segments = trimmed.split(' -> ');
|
|
67
|
+
return segments[segments.length - 1]?.trim() || trimmed;
|
|
68
|
+
}
|
|
69
|
+
function normalizePathForGit(repoPath, path) {
|
|
70
|
+
if (!validatePathInRepo(repoPath, path)) {
|
|
71
|
+
throw new Error(`Invalid file path: ${path}`);
|
|
72
|
+
}
|
|
73
|
+
const absolutePath = isAbsolute(path)
|
|
74
|
+
? resolve(path)
|
|
75
|
+
: resolve(repoPath, path);
|
|
76
|
+
const relativePath = relative(resolve(repoPath), absolutePath);
|
|
77
|
+
if (!relativePath || relativePath === '.') {
|
|
78
|
+
throw new Error('Path must point to a file or subdirectory inside the repository');
|
|
79
|
+
}
|
|
80
|
+
return relativePath;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get working tree changes split by staged/unstaged/untracked buckets.
|
|
84
|
+
*/
|
|
85
|
+
export async function getWorkingTreeStatus(repoPath) {
|
|
86
|
+
const output = await execGit(repoPath, ['status', '--porcelain']);
|
|
87
|
+
const staged = new Set();
|
|
88
|
+
const unstaged = new Set();
|
|
89
|
+
const untracked = new Set();
|
|
90
|
+
const lines = output
|
|
91
|
+
.split('\n')
|
|
92
|
+
.map(line => line.trimEnd())
|
|
93
|
+
.filter(line => line.length >= 3);
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const indexStatus = line.charAt(0);
|
|
96
|
+
const worktreeStatus = line.charAt(1);
|
|
97
|
+
const path = normalizeStatusPath(line.slice(3));
|
|
98
|
+
if (!path) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (indexStatus === '?' && worktreeStatus === '?') {
|
|
102
|
+
untracked.add(path);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (indexStatus !== ' ' && indexStatus !== '?') {
|
|
106
|
+
staged.add(path);
|
|
107
|
+
}
|
|
108
|
+
if (worktreeStatus !== ' ') {
|
|
109
|
+
unstaged.add(path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
staged: dedupeAndSort(staged),
|
|
114
|
+
unstaged: dedupeAndSort(unstaged),
|
|
115
|
+
untracked: dedupeAndSort(untracked)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Stage explicit paths in a repository.
|
|
120
|
+
*/
|
|
121
|
+
export async function stagePaths(repoPath, paths) {
|
|
122
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
const normalizedPaths = dedupeAndSort(paths
|
|
126
|
+
.map(path => path.trim())
|
|
127
|
+
.filter(path => path.length > 0)
|
|
128
|
+
.map(path => normalizePathForGit(repoPath, path)));
|
|
129
|
+
if (normalizedPaths.length === 0) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
await execGit(repoPath, ['add', '--', ...normalizedPaths]);
|
|
133
|
+
return normalizedPaths;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Commit currently staged changes. Returns null when nothing is staged.
|
|
137
|
+
*/
|
|
138
|
+
export async function commitStagedChanges(repoPath, message) {
|
|
139
|
+
const trimmedMessage = message.trim();
|
|
140
|
+
if (!trimmedMessage) {
|
|
141
|
+
throw new Error('Commit message is required');
|
|
142
|
+
}
|
|
143
|
+
const stagedOutput = await execGit(repoPath, ['diff', '--cached', '--name-only']);
|
|
144
|
+
const stagedFiles = stagedOutput
|
|
145
|
+
.split('\n')
|
|
146
|
+
.map(line => line.trim())
|
|
147
|
+
.filter(line => line.length > 0);
|
|
148
|
+
if (stagedFiles.length === 0) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
await execGit(repoPath, ['commit', '-m', trimmedMessage]);
|
|
152
|
+
const sha = (await execGit(repoPath, ['rev-parse', 'HEAD'])).trim();
|
|
153
|
+
const shortSha = (await execGit(repoPath, ['rev-parse', '--short', 'HEAD'])).trim();
|
|
154
|
+
return {
|
|
155
|
+
sha,
|
|
156
|
+
shortSha,
|
|
157
|
+
message: trimmedMessage,
|
|
158
|
+
files: stagedFiles
|
|
159
|
+
};
|
|
160
|
+
}
|
|
58
161
|
/**
|
|
59
162
|
* Get commit information by SHA
|
|
60
163
|
*/
|
|
@@ -333,7 +436,7 @@ async function commitExistsInRepo(repoPath, sha) {
|
|
|
333
436
|
*
|
|
334
437
|
* @param repoConfig - The repository configuration with optional gitRepos
|
|
335
438
|
* @param sha - The commit SHA to find
|
|
336
|
-
* @returns
|
|
439
|
+
* @returns Resolved commit data for the matching repo, or throws if not found
|
|
337
440
|
*/
|
|
338
441
|
export async function findRepoForCommit(repoConfig, sha) {
|
|
339
442
|
// Validate SHA format
|
|
@@ -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
|
@@ -119,10 +119,12 @@ In-sandbox discovery helper:
|
|
|
119
119
|
|
|
120
120
|
### `git`
|
|
121
121
|
|
|
122
|
+
- `git.getStatus(repoId, repoPath?)`
|
|
122
123
|
- `git.getCommits(repoId, shas, repoPath?)`
|
|
123
124
|
- `git.getDiff(repoId, commit, repoPath?)`
|
|
124
125
|
- `git.getFileDiff(repoId, commit, file, repoPath?)`
|
|
125
126
|
- `git.getFileContent(repoId, commit, file, repoPath?)`
|
|
127
|
+
- `git.commitIfChanged(repoId, message, options?)`
|
|
126
128
|
|
|
127
129
|
### `state`
|
|
128
130
|
|
|
@@ -181,6 +183,24 @@ return await Promise.all(commits.map(async (entry) => ({
|
|
|
181
183
|
})))
|
|
182
184
|
```
|
|
183
185
|
|
|
186
|
+
Commit task-related changes when present:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
const repo = await repos.current()
|
|
190
|
+
|
|
191
|
+
const commit = await git.commitIfChanged(repo.id, 'test: add task graph coverage', {
|
|
192
|
+
paths: ['app/components/graph/Explorer.spec.ts']
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return commit
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`git.commitIfChanged` behavior:
|
|
199
|
+
|
|
200
|
+
- stages only the explicit `options.paths` list when provided
|
|
201
|
+
- commits only when staged changes exist
|
|
202
|
+
- returns `committed: false` with `reason: "no_changes" | "no_staged_changes"` instead of creating empty commits
|
|
203
|
+
|
|
184
204
|
Inspect signatures at runtime:
|
|
185
205
|
|
|
186
206
|
```js
|
|
@@ -238,4 +258,5 @@ return { saved: true }
|
|
|
238
258
|
|
|
239
259
|
- This server is for trusted local development.
|
|
240
260
|
- APIs can read local filesystem and git history for registered repositories.
|
|
261
|
+
- `git.commitIfChanged` can create local commits when staged changes exist.
|
|
241
262
|
- Do not expose this server to untrusted environments.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thxgg/steward",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|