@thxgg/steward 0.1.17 → 0.1.18
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/.env.example +0 -6
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{By7gAVcL.js → -z_Gr0GN.js} +1 -1
- package/.output/public/_nuxt/{DhKWRjCh.js → 5LlyHjkF.js} +1 -1
- package/.output/public/_nuxt/{CmhLcqDu.js → BA0u_CRT.js} +1 -1
- package/.output/public/_nuxt/{BlTKcjLJ.js → BA4e9-N5.js} +2 -2
- package/.output/public/_nuxt/{DD--ojY9.js → BO8EM227.js} +1 -1
- package/.output/public/_nuxt/{CbJfCtEa.js → C0XT5P3Q.js} +1 -1
- package/.output/public/_nuxt/{BSZqAKg4.js → CGzrvVc6.js} +1 -1
- package/.output/public/_nuxt/{BdjPva1I.js → CJlXUkTg.js} +1 -1
- package/.output/public/_nuxt/{c1sXju8w.js → CZsXZugv.js} +1 -1
- package/.output/public/_nuxt/{BMdjSp24.js → C_HVaH3B.js} +1 -1
- package/.output/public/_nuxt/{4r0X30JV.js → DAnnHVQP.js} +1 -1
- package/.output/public/_nuxt/{CbkpNvIu.js → DEr8q68O.js} +1 -1
- package/.output/public/_nuxt/DrXxYwWw.js +30 -0
- package/.output/public/_nuxt/{Beeir9iR.js → QAzsKGuP.js} +1 -1
- package/.output/public/_nuxt/{Bh3vsUvl.js → TSsR_oCL.js} +1 -1
- package/.output/public/_nuxt/{nX8Sf7cz.js → WUF6Thhn.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/19e0e040-a531-4c25-b46d-a6ca54a1ae3e.json +1 -0
- package/.output/public/_nuxt/{eGCjCghR.js → i9wn3hS7.js} +1 -1
- package/.output/server/chunks/_/git-api.mjs +1 -1
- package/.output/server/chunks/_/prd-service.mjs +138 -6
- package/.output/server/chunks/_/prd-service.mjs.map +1 -1
- package/.output/server/chunks/_/repos.mjs +448 -0
- package/.output/server/chunks/_/repos.mjs.map +1 -0
- package/.output/server/chunks/_/task-graph.mjs +12 -13
- package/.output/server/chunks/_/task-graph.mjs.map +1 -1
- package/.output/server/chunks/_/watcher.mjs +42 -36
- package/.output/server/chunks/_/watcher.mjs.map +1 -1
- package/.output/server/chunks/build/{Detail-MGwP_u2d.mjs → Detail-BQSkP9Zm.mjs} +108 -41
- package/.output/server/chunks/build/Detail-BQSkP9Zm.mjs.map +1 -0
- package/.output/server/chunks/build/{_prd_-C-Aj4fVa.mjs → _prd_-CBR_wm9i.mjs} +2 -4
- package/.output/server/chunks/build/_prd_-CBR_wm9i.mjs.map +1 -0
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/default-Cao5eO80.mjs +0 -2
- package/.output/server/chunks/build/error-404-Bf6kdO80.mjs +0 -2
- package/.output/server/chunks/build/error-500-D_bcARXN.mjs +0 -2
- package/.output/server/chunks/build/index-ljj9uTXI.mjs +0 -2
- package/.output/server/chunks/build/nuxt-link-SvT1nf8Z.mjs +1 -1
- package/.output/server/chunks/build/{repo-graph-EuhMeFt7.mjs → repo-graph-CVnkmn8i.mjs} +2 -4
- package/.output/server/chunks/build/repo-graph-CVnkmn8i.mjs.map +1 -0
- package/.output/server/chunks/build/server.mjs +3 -5
- package/.output/server/chunks/build/usePrd-f7ylhIqs.mjs +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +569 -1595
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/browse.get.mjs +24 -6
- package/.output/server/chunks/routes/api/browse.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/index.post.mjs +21 -7
- package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs +19 -11
- package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs +2 -2
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs +2 -2
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs +2 -2
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +3 -2
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +3 -2
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs +5 -4
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs +5 -4
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs +5 -4
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs +4 -3
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs +4 -3
- package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/refresh-git-repos.post.mjs +2 -2
- package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/runtime.get.mjs +1 -3
- package/.output/server/chunks/routes/api/runtime.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/watch.get.mjs +4 -4
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/index.mjs +1 -3
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/README.md +2 -7
- package/dist/host/src/api/repos.js +0 -2
- package/dist/host/src/api/state.js +1 -7
- package/dist/host/src/index.js +2 -7
- package/dist/host/src/ui.js +2 -7
- package/dist/server/utils/prd-service.js +1 -5
- package/dist/server/utils/prd-state.js +0 -120
- package/dist/server/utils/repos.js +14 -4
- package/dist/server/utils/task-graph.js +11 -11
- package/package.json +1 -1
- package/.output/public/_nuxt/DC6iPLz1.js +0 -30
- package/.output/public/_nuxt/builds/meta/f3f42dbd-d501-442b-871c-3d06157e7aa1.json +0 -1
- package/.output/server/chunks/build/Detail-MGwP_u2d.mjs.map +0 -1
- package/.output/server/chunks/build/_prd_-C-Aj4fVa.mjs.map +0 -1
- package/.output/server/chunks/build/repo-graph-EuhMeFt7.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -55,7 +55,6 @@ Note: `execute` runs in a VM sandbox by design, so globals like `process` are in
|
|
|
55
55
|
```bash
|
|
56
56
|
prd ui
|
|
57
57
|
prd ui --port 3100 --host 127.0.0.1
|
|
58
|
-
prd ui --host 0.0.0.0 --allow-remote
|
|
59
58
|
prd mcp
|
|
60
59
|
```
|
|
61
60
|
|
|
@@ -144,10 +143,8 @@ Detailed API docs and examples: `docs/MCP.md`
|
|
|
144
143
|
|
|
145
144
|
Steward reads local filesystem and git metadata by design.
|
|
146
145
|
|
|
147
|
-
-
|
|
148
|
-
- Non-loopback
|
|
149
|
-
- Optional token auth for remote API access: set `STEWARD_API_TOKEN`
|
|
150
|
-
- For browser sessions, you can initialize the auth cookie with `?token=<STEWARD_API_TOKEN>`
|
|
146
|
+
- UI/API accept loopback requests only
|
|
147
|
+
- Non-loopback requests are rejected
|
|
151
148
|
- Treat as a workstation tool, not a hosted multi-user service
|
|
152
149
|
|
|
153
150
|
## Storage
|
|
@@ -174,8 +171,6 @@ npm run build
|
|
|
174
171
|
| `PRD_STATE_DB_PATH` | Absolute path to SQLite DB file |
|
|
175
172
|
| `PRD_STATE_HOME` | Base directory for DB (`state.db` inside) |
|
|
176
173
|
| `XDG_DATA_HOME` | Fallback base path for default DB location |
|
|
177
|
-
| `STEWARD_ALLOW_REMOTE` | Set to `1` to allow non-loopback requests |
|
|
178
|
-
| `STEWARD_API_TOKEN` | Optional token required for remote `/api/*` access |
|
|
179
174
|
|
|
180
175
|
## OpenCode Bundle
|
|
181
176
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { addRepo, discoverGitRepos, getRepoById, getRepos, removeRepo, updateRepoGitRepos, validateRepoPath } from '../../../server/utils/repos.js';
|
|
2
|
-
import { migrateLegacyStateForRepo } from '../../../server/utils/prd-state.js';
|
|
3
2
|
import { requireCurrentRepo, requireRepo } from './repo-context.js';
|
|
4
3
|
export const repos = {
|
|
5
4
|
async list() {
|
|
@@ -17,7 +16,6 @@ export const repos = {
|
|
|
17
16
|
throw new Error(validation.error || 'Invalid repository path');
|
|
18
17
|
}
|
|
19
18
|
const repo = await addRepo(path, name);
|
|
20
|
-
await migrateLegacyStateForRepo(repo);
|
|
21
19
|
return repo;
|
|
22
20
|
},
|
|
23
21
|
async remove(repoId) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPrdState, getPrdStateSummaries,
|
|
1
|
+
import { getPrdState, getPrdStateSummaries, upsertPrdState } from '../../../server/utils/prd-state.js';
|
|
2
2
|
import { parseProgressFile, parseTasksFile } from '../../../server/utils/state-schema.js';
|
|
3
3
|
import { requireCurrentRepo, requireRepo, requireRepoByPath } from './repo-context.js';
|
|
4
4
|
function mapStateUpdate(payload) {
|
|
@@ -18,34 +18,28 @@ function mapSummaryMap(summaries) {
|
|
|
18
18
|
export const state = {
|
|
19
19
|
async get(repoId, slug) {
|
|
20
20
|
const repo = await requireRepo(repoId);
|
|
21
|
-
await migrateLegacyStateForRepo(repo);
|
|
22
21
|
return await getPrdState(repo.id, slug);
|
|
23
22
|
},
|
|
24
23
|
async getByPath(repoPath, slug) {
|
|
25
24
|
const repo = await requireRepoByPath(repoPath);
|
|
26
|
-
await migrateLegacyStateForRepo(repo);
|
|
27
25
|
return await getPrdState(repo.id, slug);
|
|
28
26
|
},
|
|
29
27
|
async getCurrent(slug) {
|
|
30
28
|
const repo = await requireCurrentRepo();
|
|
31
|
-
await migrateLegacyStateForRepo(repo);
|
|
32
29
|
return await getPrdState(repo.id, slug);
|
|
33
30
|
},
|
|
34
31
|
async summaries(repoId) {
|
|
35
32
|
const repo = await requireRepo(repoId);
|
|
36
|
-
await migrateLegacyStateForRepo(repo);
|
|
37
33
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
38
34
|
return mapSummaryMap(summaries);
|
|
39
35
|
},
|
|
40
36
|
async summariesByPath(repoPath) {
|
|
41
37
|
const repo = await requireRepoByPath(repoPath);
|
|
42
|
-
await migrateLegacyStateForRepo(repo);
|
|
43
38
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
44
39
|
return mapSummaryMap(summaries);
|
|
45
40
|
},
|
|
46
41
|
async summariesCurrent() {
|
|
47
42
|
const repo = await requireCurrentRepo();
|
|
48
|
-
await migrateLegacyStateForRepo(repo);
|
|
49
43
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
50
44
|
return mapSummaryMap(summaries);
|
|
51
45
|
},
|
package/dist/host/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ function printUsage() {
|
|
|
4
4
|
console.log(`prd - Steward CLI
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
prd ui [--preview] [--port <port>] [--host <host>]
|
|
7
|
+
prd ui [--preview] [--port <port>] [--host <host>]
|
|
8
8
|
prd mcp
|
|
9
9
|
|
|
10
10
|
Commands:
|
|
@@ -15,7 +15,6 @@ Options:
|
|
|
15
15
|
--preview Deprecated; ignored (kept for compatibility)
|
|
16
16
|
--port <port> Port for ui mode
|
|
17
17
|
--host <host> Host for ui mode
|
|
18
|
-
--allow-remote Allow non-loopback host binding
|
|
19
18
|
-h, --help Show this help message
|
|
20
19
|
`);
|
|
21
20
|
}
|
|
@@ -27,7 +26,7 @@ function parsePort(value) {
|
|
|
27
26
|
return parsed;
|
|
28
27
|
}
|
|
29
28
|
function parseUiArgs(args) {
|
|
30
|
-
const options = { preview: false
|
|
29
|
+
const options = { preview: false };
|
|
31
30
|
for (let i = 0; i < args.length; i++) {
|
|
32
31
|
const arg = args[i];
|
|
33
32
|
if (arg === '--preview') {
|
|
@@ -52,10 +51,6 @@ function parseUiArgs(args) {
|
|
|
52
51
|
i += 1;
|
|
53
52
|
continue;
|
|
54
53
|
}
|
|
55
|
-
if (arg === '--allow-remote') {
|
|
56
|
-
options.allowRemote = true;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
54
|
throw new Error(`Unknown option for ui: ${arg}`);
|
|
60
55
|
}
|
|
61
56
|
return options;
|
package/dist/host/src/ui.js
CHANGED
|
@@ -35,10 +35,8 @@ export async function runUi(options) {
|
|
|
35
35
|
const env = { ...process.env };
|
|
36
36
|
const hostFromEnv = env.NITRO_HOST || env.HOST;
|
|
37
37
|
const requestedHost = (options.host || hostFromEnv || DEFAULT_UI_HOST).trim();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
throw new Error(`Refusing to bind UI to non-loopback host "${requestedHost}" without explicit opt-in. `
|
|
41
|
-
+ 'Use --allow-remote or set STEWARD_ALLOW_REMOTE=1.');
|
|
38
|
+
if (!isLoopbackHost(requestedHost)) {
|
|
39
|
+
throw new Error(`Refusing to bind UI to non-loopback host "${requestedHost}". Steward only supports loopback hosts.`);
|
|
42
40
|
}
|
|
43
41
|
env.NODE_ENV = env.NODE_ENV || 'production';
|
|
44
42
|
if (options.port !== undefined) {
|
|
@@ -48,9 +46,6 @@ export async function runUi(options) {
|
|
|
48
46
|
}
|
|
49
47
|
env.HOST = requestedHost;
|
|
50
48
|
env.NITRO_HOST = requestedHost;
|
|
51
|
-
if (allowRemote) {
|
|
52
|
-
env.STEWARD_ALLOW_REMOTE = '1';
|
|
53
|
-
}
|
|
54
49
|
const child = spawn(process.execPath, args, {
|
|
55
50
|
cwd: packageRoot,
|
|
56
51
|
stdio: 'inherit',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { resolveCommitRepo } from './git.js';
|
|
4
|
-
import { getPrdState, getPrdStateSummaries
|
|
4
|
+
import { getPrdState, getPrdStateSummaries } from './prd-state.js';
|
|
5
5
|
import { discoverGitRepos, updateRepoGitRepos } from './repos.js';
|
|
6
6
|
const PRD_SLUG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
7
7
|
function normalizePathSlashes(path) {
|
|
@@ -91,7 +91,6 @@ export async function readPrdDocument(repo, prdSlug) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
export async function listPrdDocuments(repo) {
|
|
94
|
-
await migrateLegacyStateForRepo(repo);
|
|
95
94
|
const prdDir = join(repo.path, 'docs', 'prd');
|
|
96
95
|
let prdFiles = [];
|
|
97
96
|
try {
|
|
@@ -140,13 +139,11 @@ export async function listPrdDocuments(repo) {
|
|
|
140
139
|
}
|
|
141
140
|
export async function readPrdTasks(repo, prdSlug) {
|
|
142
141
|
assertValidPrdSlug(prdSlug);
|
|
143
|
-
await migrateLegacyStateForRepo(repo);
|
|
144
142
|
const state = await getPrdState(repo.id, prdSlug);
|
|
145
143
|
return state?.tasks ?? null;
|
|
146
144
|
}
|
|
147
145
|
export async function readPrdProgress(repo, prdSlug) {
|
|
148
146
|
assertValidPrdSlug(prdSlug);
|
|
149
|
-
await migrateLegacyStateForRepo(repo);
|
|
150
147
|
const state = await getPrdState(repo.id, prdSlug);
|
|
151
148
|
return state?.progress ?? null;
|
|
152
149
|
}
|
|
@@ -180,7 +177,6 @@ async function refreshDiscoveredGitRepos(repo) {
|
|
|
180
177
|
}
|
|
181
178
|
export async function resolveTaskCommits(repo, prdSlug, taskId) {
|
|
182
179
|
assertValidPrdSlug(prdSlug);
|
|
183
|
-
await migrateLegacyStateForRepo(repo);
|
|
184
180
|
const state = await getPrdState(repo.id, prdSlug);
|
|
185
181
|
const progress = state?.progress ?? null;
|
|
186
182
|
if (!progress) {
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { emitChange } from './change-events.js';
|
|
4
2
|
import { dbAll, dbGet, dbRun } from './db.js';
|
|
5
3
|
import { parseProgressFile, parseTasksFile } from './state-schema.js';
|
|
6
|
-
const LEGACY_STATE_STABLE_MS = 0;
|
|
7
|
-
const migrationInFlight = new Map();
|
|
8
|
-
const cleanupCompletedRepoIds = new Set();
|
|
9
4
|
function parseStoredJson(raw, fieldName, parseValue) {
|
|
10
5
|
if (!raw) {
|
|
11
6
|
return null;
|
|
@@ -124,118 +119,3 @@ export async function upsertPrdState(repoId, slug, update) {
|
|
|
124
119
|
});
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
|
-
async function readStableLegacyFile(filePath, minFileAgeMs) {
|
|
128
|
-
try {
|
|
129
|
-
const stats = await fs.stat(filePath);
|
|
130
|
-
if (!stats.isFile()) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
if (Date.now() - stats.mtimeMs < minFileAgeMs) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
return await fs.readFile(filePath, 'utf-8');
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
async function readLegacyJsonFile(filePath, label, minFileAgeMs) {
|
|
143
|
-
const content = await readStableLegacyFile(filePath, minFileAgeMs);
|
|
144
|
-
if (!content) {
|
|
145
|
-
return { value: null, imported: false };
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
return { value: JSON.parse(content), imported: true };
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
-
console.warn(`[legacy-state] Skipping invalid ${label} at ${filePath}: ${message}`);
|
|
153
|
-
return { value: null, imported: false };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
async function removeIfExists(filePath) {
|
|
157
|
-
try {
|
|
158
|
-
await fs.unlink(filePath);
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// File may already be removed.
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
async function removeDirIfEmpty(dirPath) {
|
|
165
|
-
try {
|
|
166
|
-
const entries = await fs.readdir(dirPath);
|
|
167
|
-
if (entries.length === 0) {
|
|
168
|
-
await fs.rmdir(dirPath);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
// Directory may not exist or may contain files.
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async function runLegacyStateMigration(repo, cleanupLegacyFiles, minFileAgeMs) {
|
|
176
|
-
const legacyStateDir = join(repo.path, '.claude', 'state');
|
|
177
|
-
const entries = await fs.readdir(legacyStateDir, { withFileTypes: true, encoding: 'utf8' }).catch(() => null);
|
|
178
|
-
if (!entries) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
for (const entry of entries) {
|
|
182
|
-
if (!entry.isDirectory()) {
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
const slug = entry.name;
|
|
186
|
-
const slugDir = join(legacyStateDir, slug);
|
|
187
|
-
const tasksPath = join(slugDir, 'tasks.json');
|
|
188
|
-
const progressPath = join(slugDir, 'progress.json');
|
|
189
|
-
const notesPath = join(slugDir, 'notes.md');
|
|
190
|
-
const [tasksResult, progressResult, notesContent] = await Promise.all([
|
|
191
|
-
readLegacyJsonFile(tasksPath, 'tasks.json', minFileAgeMs),
|
|
192
|
-
readLegacyJsonFile(progressPath, 'progress.json', minFileAgeMs),
|
|
193
|
-
readStableLegacyFile(notesPath, minFileAgeMs)
|
|
194
|
-
]);
|
|
195
|
-
const shouldImportNotes = notesContent !== null;
|
|
196
|
-
const shouldImport = tasksResult.imported || progressResult.imported || shouldImportNotes;
|
|
197
|
-
if (!shouldImport) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
await upsertPrdState(repo.id, slug, {
|
|
201
|
-
...(tasksResult.imported && { tasks: tasksResult.value }),
|
|
202
|
-
...(progressResult.imported && { progress: progressResult.value }),
|
|
203
|
-
...(shouldImportNotes && { notes: notesContent })
|
|
204
|
-
});
|
|
205
|
-
if (cleanupLegacyFiles) {
|
|
206
|
-
if (tasksResult.imported) {
|
|
207
|
-
await removeIfExists(tasksPath);
|
|
208
|
-
}
|
|
209
|
-
if (progressResult.imported) {
|
|
210
|
-
await removeIfExists(progressPath);
|
|
211
|
-
}
|
|
212
|
-
if (shouldImportNotes) {
|
|
213
|
-
await removeIfExists(notesPath);
|
|
214
|
-
}
|
|
215
|
-
await removeDirIfEmpty(slugDir);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (cleanupLegacyFiles) {
|
|
219
|
-
await removeDirIfEmpty(legacyStateDir);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
export async function migrateLegacyStateForRepo(repo, options = {}) {
|
|
223
|
-
const cleanupLegacyFiles = options.cleanupLegacyFiles
|
|
224
|
-
?? !cleanupCompletedRepoIds.has(repo.id);
|
|
225
|
-
const minFileAgeMs = options.minFileAgeMs ?? LEGACY_STATE_STABLE_MS;
|
|
226
|
-
const inFlight = migrationInFlight.get(repo.id);
|
|
227
|
-
if (inFlight) {
|
|
228
|
-
return inFlight;
|
|
229
|
-
}
|
|
230
|
-
const migrationPromise = runLegacyStateMigration(repo, cleanupLegacyFiles, minFileAgeMs)
|
|
231
|
-
.then(() => {
|
|
232
|
-
if (cleanupLegacyFiles) {
|
|
233
|
-
cleanupCompletedRepoIds.add(repo.id);
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
.finally(() => {
|
|
237
|
-
migrationInFlight.delete(repo.id);
|
|
238
|
-
});
|
|
239
|
-
migrationInFlight.set(repo.id, migrationPromise);
|
|
240
|
-
return migrationPromise;
|
|
241
|
-
}
|
|
@@ -274,12 +274,22 @@ export async function discoverGitRepos(basePath, maxDepth = 4) {
|
|
|
274
274
|
return discovered;
|
|
275
275
|
}
|
|
276
276
|
export async function validateRepoPath(path) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
const trimmedPath = path.trim();
|
|
278
|
+
if (trimmedPath.length === 0) {
|
|
279
|
+
return { valid: false, error: 'Path is required' };
|
|
280
|
+
}
|
|
281
|
+
if (trimmedPath.length > 4096) {
|
|
282
|
+
return { valid: false, error: 'Path is too long' };
|
|
283
|
+
}
|
|
284
|
+
if (trimmedPath.includes('\u0000')) {
|
|
285
|
+
return { valid: false, error: 'Path contains invalid characters' };
|
|
286
|
+
}
|
|
287
|
+
const isWindowsAbsolutePath = /^[A-Za-z]:[\\/]/.test(trimmedPath);
|
|
288
|
+
if (!isAbsolute(trimmedPath) && !isWindowsAbsolutePath) {
|
|
281
289
|
return { valid: false, error: 'Path must be absolute' };
|
|
282
290
|
}
|
|
291
|
+
// Normalize the path
|
|
292
|
+
const resolvedPath = resolve(trimmedPath);
|
|
283
293
|
try {
|
|
284
294
|
const stats = await fs.stat(resolvedPath);
|
|
285
295
|
if (!stats.isDirectory()) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { getPrdState, getPrdStateSummaries
|
|
2
|
+
import { getPrdState, getPrdStateSummaries } from './prd-state.js';
|
|
3
3
|
import { resolvePrdMarkdownPath } from './prd-service.js';
|
|
4
4
|
const NODE_SEPARATOR = '::';
|
|
5
5
|
const MISSING_PREFIX = 'missing';
|
|
@@ -139,7 +139,6 @@ function buildGraphFromInputs(inputs) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
export async function buildPrdGraph(repo, prdSlug) {
|
|
142
|
-
await migrateLegacyStateForRepo(repo);
|
|
143
142
|
const [state, prdName] = await Promise.all([
|
|
144
143
|
getPrdState(repo.id, prdSlug),
|
|
145
144
|
resolvePrdName(repo, prdSlug)
|
|
@@ -162,22 +161,23 @@ export async function buildPrdGraph(repo, prdSlug) {
|
|
|
162
161
|
};
|
|
163
162
|
}
|
|
164
163
|
export async function buildRepoGraph(repo) {
|
|
165
|
-
await migrateLegacyStateForRepo(repo);
|
|
166
164
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
167
165
|
const slugs = [...summaries.keys()].sort((a, b) => a.localeCompare(b));
|
|
168
|
-
const inputs =
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
const inputs = (await Promise.all(slugs.map(async (slug) => {
|
|
167
|
+
const [state, prdName] = await Promise.all([
|
|
168
|
+
getPrdState(repo.id, slug),
|
|
169
|
+
resolvePrdName(repo, slug)
|
|
170
|
+
]);
|
|
171
171
|
const tasks = state?.tasks?.tasks;
|
|
172
172
|
if (!Array.isArray(tasks)) {
|
|
173
|
-
|
|
173
|
+
return null;
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
return {
|
|
176
176
|
prdSlug: slug,
|
|
177
|
-
prdName
|
|
177
|
+
prdName,
|
|
178
178
|
tasks
|
|
179
|
-
}
|
|
180
|
-
}
|
|
179
|
+
};
|
|
180
|
+
}))).filter((input) => input !== null);
|
|
181
181
|
const graph = buildGraphFromInputs(inputs);
|
|
182
182
|
return {
|
|
183
183
|
scope: 'repo',
|