@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.
Files changed (94) hide show
  1. package/.env.example +0 -6
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/_nuxt/{By7gAVcL.js → -z_Gr0GN.js} +1 -1
  4. package/.output/public/_nuxt/{DhKWRjCh.js → 5LlyHjkF.js} +1 -1
  5. package/.output/public/_nuxt/{CmhLcqDu.js → BA0u_CRT.js} +1 -1
  6. package/.output/public/_nuxt/{BlTKcjLJ.js → BA4e9-N5.js} +2 -2
  7. package/.output/public/_nuxt/{DD--ojY9.js → BO8EM227.js} +1 -1
  8. package/.output/public/_nuxt/{CbJfCtEa.js → C0XT5P3Q.js} +1 -1
  9. package/.output/public/_nuxt/{BSZqAKg4.js → CGzrvVc6.js} +1 -1
  10. package/.output/public/_nuxt/{BdjPva1I.js → CJlXUkTg.js} +1 -1
  11. package/.output/public/_nuxt/{c1sXju8w.js → CZsXZugv.js} +1 -1
  12. package/.output/public/_nuxt/{BMdjSp24.js → C_HVaH3B.js} +1 -1
  13. package/.output/public/_nuxt/{4r0X30JV.js → DAnnHVQP.js} +1 -1
  14. package/.output/public/_nuxt/{CbkpNvIu.js → DEr8q68O.js} +1 -1
  15. package/.output/public/_nuxt/DrXxYwWw.js +30 -0
  16. package/.output/public/_nuxt/{Beeir9iR.js → QAzsKGuP.js} +1 -1
  17. package/.output/public/_nuxt/{Bh3vsUvl.js → TSsR_oCL.js} +1 -1
  18. package/.output/public/_nuxt/{nX8Sf7cz.js → WUF6Thhn.js} +1 -1
  19. package/.output/public/_nuxt/builds/latest.json +1 -1
  20. package/.output/public/_nuxt/builds/meta/19e0e040-a531-4c25-b46d-a6ca54a1ae3e.json +1 -0
  21. package/.output/public/_nuxt/{eGCjCghR.js → i9wn3hS7.js} +1 -1
  22. package/.output/server/chunks/_/git-api.mjs +1 -1
  23. package/.output/server/chunks/_/prd-service.mjs +138 -6
  24. package/.output/server/chunks/_/prd-service.mjs.map +1 -1
  25. package/.output/server/chunks/_/repos.mjs +448 -0
  26. package/.output/server/chunks/_/repos.mjs.map +1 -0
  27. package/.output/server/chunks/_/task-graph.mjs +12 -13
  28. package/.output/server/chunks/_/task-graph.mjs.map +1 -1
  29. package/.output/server/chunks/_/watcher.mjs +42 -36
  30. package/.output/server/chunks/_/watcher.mjs.map +1 -1
  31. package/.output/server/chunks/build/{Detail-MGwP_u2d.mjs → Detail-BQSkP9Zm.mjs} +108 -41
  32. package/.output/server/chunks/build/Detail-BQSkP9Zm.mjs.map +1 -0
  33. package/.output/server/chunks/build/{_prd_-C-Aj4fVa.mjs → _prd_-CBR_wm9i.mjs} +2 -4
  34. package/.output/server/chunks/build/_prd_-CBR_wm9i.mjs.map +1 -0
  35. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  36. package/.output/server/chunks/build/default-Cao5eO80.mjs +0 -2
  37. package/.output/server/chunks/build/error-404-Bf6kdO80.mjs +0 -2
  38. package/.output/server/chunks/build/error-500-D_bcARXN.mjs +0 -2
  39. package/.output/server/chunks/build/index-ljj9uTXI.mjs +0 -2
  40. package/.output/server/chunks/build/nuxt-link-SvT1nf8Z.mjs +1 -1
  41. package/.output/server/chunks/build/{repo-graph-EuhMeFt7.mjs → repo-graph-CVnkmn8i.mjs} +2 -4
  42. package/.output/server/chunks/build/repo-graph-CVnkmn8i.mjs.map +1 -0
  43. package/.output/server/chunks/build/server.mjs +3 -5
  44. package/.output/server/chunks/build/usePrd-f7ylhIqs.mjs +1 -1
  45. package/.output/server/chunks/nitro/nitro.mjs +569 -1595
  46. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/routes/api/browse.get.mjs +24 -6
  48. package/.output/server/chunks/routes/api/browse.get.mjs.map +1 -1
  49. package/.output/server/chunks/routes/api/index.get.mjs +2 -2
  50. package/.output/server/chunks/routes/api/index.post.mjs +21 -7
  51. package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
  52. package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs +19 -11
  53. package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs.map +1 -1
  54. package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs +2 -2
  55. package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs +2 -2
  56. package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs +2 -2
  57. package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +3 -2
  58. package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -1
  59. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +3 -2
  60. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs.map +1 -1
  61. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs +5 -4
  62. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs.map +1 -1
  63. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs +5 -4
  64. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs.map +1 -1
  65. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs +5 -4
  66. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs.map +1 -1
  67. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs +4 -3
  68. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs.map +1 -1
  69. package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs +4 -3
  70. package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs.map +1 -1
  71. package/.output/server/chunks/routes/api/repos/_repoId/refresh-git-repos.post.mjs +2 -2
  72. package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs +2 -2
  73. package/.output/server/chunks/routes/api/runtime.get.mjs +1 -3
  74. package/.output/server/chunks/routes/api/runtime.get.mjs.map +1 -1
  75. package/.output/server/chunks/routes/api/watch.get.mjs +4 -4
  76. package/.output/server/chunks/routes/renderer.mjs +1 -1
  77. package/.output/server/index.mjs +1 -3
  78. package/.output/server/index.mjs.map +1 -1
  79. package/.output/server/package.json +1 -1
  80. package/README.md +2 -7
  81. package/dist/host/src/api/repos.js +0 -2
  82. package/dist/host/src/api/state.js +1 -7
  83. package/dist/host/src/index.js +2 -7
  84. package/dist/host/src/ui.js +2 -7
  85. package/dist/server/utils/prd-service.js +1 -5
  86. package/dist/server/utils/prd-state.js +0 -120
  87. package/dist/server/utils/repos.js +14 -4
  88. package/dist/server/utils/task-graph.js +11 -11
  89. package/package.json +1 -1
  90. package/.output/public/_nuxt/DC6iPLz1.js +0 -30
  91. package/.output/public/_nuxt/builds/meta/f3f42dbd-d501-442b-871c-3d06157e7aa1.json +0 -1
  92. package/.output/server/chunks/build/Detail-MGwP_u2d.mjs.map +0 -1
  93. package/.output/server/chunks/build/_prd_-C-Aj4fVa.mjs.map +0 -1
  94. package/.output/server/chunks/build/repo-graph-EuhMeFt7.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thxgg/steward-prod",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
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
- - By default, UI/API only accept loopback requests
148
- - Non-loopback access requires explicit opt-in (`--allow-remote` or `STEWARD_ALLOW_REMOTE=1`)
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, migrateLegacyStateForRepo, upsertPrdState } from '../../../server/utils/prd-state.js';
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
  },
@@ -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>] [--allow-remote]
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, allowRemote: 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;
@@ -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
- const allowRemote = options.allowRemote || env.STEWARD_ALLOW_REMOTE === '1';
39
- if (!isLoopbackHost(requestedHost) && !allowRemote) {
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, migrateLegacyStateForRepo } from './prd-state.js';
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
- // Normalize the path
278
- const resolvedPath = resolve(path);
279
- // Ensure path is absolute (starts with / on Unix or drive letter on Windows)
280
- if (!resolvedPath.startsWith('/') && !/^[A-Za-z]:/.test(resolvedPath)) {
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, migrateLegacyStateForRepo } from './prd-state.js';
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
- for (const slug of slugs) {
170
- const state = await getPrdState(repo.id, slug);
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
- continue;
173
+ return null;
174
174
  }
175
- inputs.push({
175
+ return {
176
176
  prdSlug: slug,
177
- prdName: await resolvePrdName(repo, slug),
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thxgg/steward",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Local-first PRD workflow steward with codemode MCP and web UI.",
5
5
  "type": "module",
6
6
  "author": "thxgg",