@thxgg/steward 0.1.20 → 0.1.21

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 (69) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/builds/latest.json +1 -1
  3. package/.output/public/_nuxt/builds/meta/c98d132b-2ccd-4cf3-88e6-4f203447e539.json +1 -0
  4. package/.output/server/chunks/_/git-api.mjs +10 -23
  5. package/.output/server/chunks/_/git-api.mjs.map +1 -1
  6. package/.output/server/chunks/_/prd-service.mjs +1 -3
  7. package/.output/server/chunks/_/prd-service.mjs.map +1 -1
  8. package/.output/server/chunks/_/watcher.mjs +1 -2
  9. package/.output/server/chunks/_/watcher.mjs.map +1 -1
  10. package/.output/server/chunks/build/_prd_-DY25apyl.mjs +2 -1
  11. package/.output/server/chunks/build/default-BKKgG7HJ.mjs +2 -1
  12. package/.output/server/chunks/build/error-404-Bf6kdO80.mjs +2 -1
  13. package/.output/server/chunks/build/error-500-D_bcARXN.mjs +2 -1
  14. package/.output/server/chunks/build/index-DE1tjHAd.mjs +2 -1
  15. package/.output/server/chunks/build/nuxt-link-SvT1nf8Z.mjs +1 -1
  16. package/.output/server/chunks/build/repo-graph-CUcJKW1F.mjs +2 -1
  17. package/.output/server/chunks/build/server.mjs +3 -2
  18. package/.output/server/chunks/build/usePrd-hXZOmvAv.mjs +1 -1
  19. package/.output/server/chunks/nitro/nitro.mjs +1473 -653
  20. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  21. package/.output/server/chunks/routes/api/browse.get.mjs +1 -0
  22. package/.output/server/chunks/routes/api/browse.get.mjs.map +1 -1
  23. package/.output/server/chunks/routes/api/index.get.mjs +3 -3
  24. package/.output/server/chunks/routes/api/index.post.mjs +2 -2
  25. package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs +3 -5
  26. package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs.map +1 -1
  27. package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs +3 -5
  28. package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs.map +1 -1
  29. package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs +3 -5
  30. package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs.map +1 -1
  31. package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs +3 -5
  32. package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs.map +1 -1
  33. package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +3 -5
  34. package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -1
  35. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/archive.post.mjs +2 -4
  36. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/archive.post.mjs.map +1 -1
  37. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +3 -5
  38. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs.map +1 -1
  39. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs +3 -5
  40. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs.map +1 -1
  41. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs +3 -5
  42. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs.map +1 -1
  43. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs +3 -5
  44. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs.map +1 -1
  45. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs +3 -5
  46. package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs.map +1 -1
  47. package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs +3 -5
  48. package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs.map +1 -1
  49. package/.output/server/chunks/routes/api/repos/_repoId/refresh-git-repos.post.mjs +3 -3
  50. package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs +3 -3
  51. package/.output/server/chunks/routes/api/runtime.get.mjs +3 -2
  52. package/.output/server/chunks/routes/api/runtime.get.mjs.map +1 -1
  53. package/.output/server/chunks/routes/api/state-migration/status.get.mjs +3 -2
  54. package/.output/server/chunks/routes/api/state-migration/status.get.mjs.map +1 -1
  55. package/.output/server/chunks/routes/api/watch.get.mjs +3 -3
  56. package/.output/server/chunks/routes/renderer.mjs +1 -1
  57. package/.output/server/index.mjs +3 -2
  58. package/.output/server/index.mjs.map +1 -1
  59. package/.output/server/package.json +1 -1
  60. package/dist/host/src/api/git.js +14 -7
  61. package/dist/server/utils/git-repo-path.js +63 -0
  62. package/dist/server/utils/git.js +26 -9
  63. package/dist/server/utils/state-migration.js +206 -0
  64. package/package.json +1 -1
  65. package/.output/public/_nuxt/builds/meta/37438177-5e14-4a46-9af4-eae491ba1f24.json +0 -1
  66. package/.output/server/chunks/_/git.mjs +0 -296
  67. package/.output/server/chunks/_/git.mjs.map +0 -1
  68. package/.output/server/chunks/_/repos.mjs +0 -272
  69. package/.output/server/chunks/_/repos.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { createRenderer, getRequestDependencies, getPreloadLinks, getPrefetchLinks } from 'vue-bundle-renderer/runtime';
2
- import { n as joinRelativeURL, u as useRuntimeConfig, o as getResponseStatusText, q as getResponseStatus, t as defineRenderHandler, g as getQuery, c as createError, v as destr, w as getRouteRules, x as joinURL, y as useNitroApp } from '../nitro/nitro.mjs';
2
+ import { K as joinRelativeURL, F as useRuntimeConfig, L as getResponseStatusText, M as getResponseStatus, N as defineRenderHandler, g as getQuery, c as createError, O as destr, P as getRouteRules, Q as joinURL, R as useNitroApp } from '../nitro/nitro.mjs';
3
3
  import { renderToString } from 'vue/server-renderer';
4
4
  import { createHead as createHead$1, propsToString, renderSSRHead } from 'unhead/server';
5
5
  import { stringify, uneval } from 'devalue';
@@ -1,12 +1,13 @@
1
1
  import process from 'node:process';globalThis._importMeta_={url:import.meta.url,env:process.env};import 'node:http';
2
2
  import 'node:https';
3
- export { O as default } from './chunks/nitro/nitro.mjs';
3
+ export { a6 as default } from './chunks/nitro/nitro.mjs';
4
4
  import 'node:events';
5
5
  import 'node:buffer';
6
6
  import 'node:fs';
7
7
  import 'node:path';
8
8
  import 'node:crypto';
9
9
  import 'node:os';
10
- import 'zod';
10
+ import 'node:child_process';
11
11
  import 'node:url';
12
+ import 'zod';
12
13
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"names":[],"mappings":";;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thxgg/steward-prod",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
@@ -1,3 +1,5 @@
1
+ import { resolve } from 'node:path';
2
+ import { normalizeCommitRepoRefPath } from '../../../server/utils/git-repo-path.js';
1
3
  import { commitStagedChanges, getCommitDiff, getCommitInfo, getFileContent, getFileDiff, getWorkingTreeStatus, isGitRepo, stagePaths, validatePathInRepo } from '../../../server/utils/git.js';
2
4
  import { requireRepo } from './repo-context.js';
3
5
  function toGitStatus(status) {
@@ -13,15 +15,20 @@ function resolveGitRepoPath(repo, repoPath) {
13
15
  if (!repoPath) {
14
16
  return repo.path;
15
17
  }
16
- if (!repo.gitRepos || repo.gitRepos.length === 0) {
17
- throw new Error('repo parameter provided but no git repos discovered in this repository');
18
+ const normalizedRepoPath = normalizeCommitRepoRefPath(repo, repoPath);
19
+ if (normalizedRepoPath === null) {
20
+ throw new Error('Invalid repo path: path traversal not allowed');
18
21
  }
19
- const matchedRepo = repo.gitRepos.find((gitRepo) => gitRepo.relativePath === repoPath);
20
- if (!matchedRepo) {
21
- const available = repo.gitRepos.map((gitRepo) => gitRepo.relativePath).join(', ');
22
- throw new Error(`repo "${repoPath}" is not a discovered git repo. Available: ${available}`);
22
+ if (!normalizedRepoPath) {
23
+ return repo.path;
24
+ }
25
+ const matchedRepo = (repo.gitRepos || []).find((gitRepo) => {
26
+ return gitRepo.relativePath === normalizedRepoPath;
27
+ });
28
+ if (matchedRepo) {
29
+ return matchedRepo.absolutePath;
23
30
  }
24
- return matchedRepo.absolutePath;
31
+ return resolve(repo.path, normalizedRepoPath);
25
32
  }
26
33
  export const git = {
27
34
  async getStatus(repoId, repoPath) {
@@ -0,0 +1,63 @@
1
+ import { basename, isAbsolute, relative, resolve } from 'node:path';
2
+ export function normalizePathSlashes(path) {
3
+ return path.replaceAll('\\', '/');
4
+ }
5
+ export function isWithinPath(parentPath, candidatePath) {
6
+ const relativePath = relative(resolve(parentPath), resolve(candidatePath));
7
+ return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
8
+ }
9
+ export function normalizeRepoRelativePath(repoRoot, repoPath) {
10
+ const absolutePath = isAbsolute(repoPath)
11
+ ? resolve(repoPath)
12
+ : resolve(repoRoot, repoPath);
13
+ if (!isWithinPath(repoRoot, absolutePath)) {
14
+ return null;
15
+ }
16
+ const relativePath = relative(resolve(repoRoot), absolutePath);
17
+ if (!relativePath || relativePath === '.') {
18
+ return '';
19
+ }
20
+ return normalizePathSlashes(relativePath);
21
+ }
22
+ function normalizeComparablePath(value) {
23
+ return normalizePathSlashes(value).replace(/\/+$/, '');
24
+ }
25
+ function getKnownRepoPaths(repo) {
26
+ const knownPaths = new Set();
27
+ for (const gitRepo of repo.gitRepos || []) {
28
+ knownPaths.add(normalizePathSlashes(gitRepo.relativePath));
29
+ }
30
+ return knownPaths;
31
+ }
32
+ /**
33
+ * Normalize a commit repo reference or API repo parameter to canonical relative form.
34
+ *
35
+ * - `''` means repository root git repo.
36
+ * - Non-empty values are normalized relative paths under repo root.
37
+ * - Returns `null` when the path attempts traversal outside repo root.
38
+ */
39
+ export function normalizeCommitRepoRefPath(repo, repoPath) {
40
+ const repoRoot = resolve(repo.path);
41
+ const trimmed = repoPath.trim();
42
+ if (!trimmed || trimmed === '.' || trimmed === './') {
43
+ return '';
44
+ }
45
+ const normalizedRelativePath = normalizeRepoRelativePath(repoRoot, trimmed);
46
+ if (normalizedRelativePath === null) {
47
+ return null;
48
+ }
49
+ if (normalizedRelativePath === '') {
50
+ return '';
51
+ }
52
+ const knownRepoPaths = getKnownRepoPaths(repo);
53
+ if (knownRepoPaths.has(normalizedRelativePath)) {
54
+ return normalizedRelativePath;
55
+ }
56
+ const comparable = normalizeComparablePath(trimmed);
57
+ const repoName = normalizeComparablePath(repo.name);
58
+ const repoBaseName = normalizeComparablePath(basename(repoRoot));
59
+ if (comparable === repoName || comparable === repoBaseName) {
60
+ return '';
61
+ }
62
+ return normalizedRelativePath;
63
+ }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { promises as fs } from 'node:fs';
3
- import { join, resolve, relative, isAbsolute } from 'node:path';
3
+ import { resolve, relative, isAbsolute } from 'node:path';
4
+ import { normalizeCommitRepoRefPath } from './git-repo-path.js';
4
5
  /**
5
6
  * Execute a git command and return stdout
6
7
  */
@@ -472,7 +473,8 @@ export async function findRepoForCommit(repoConfig, sha) {
472
473
  }
473
474
  /**
474
475
  * Resolve a commit entry (string or CommitRef) to its repository information.
475
- * For CommitRef objects, returns immediately (O(1)).
476
+ * For CommitRef objects, validates and normalizes stored repo context, then falls
477
+ * back to SHA lookup when stored context is stale.
476
478
  * For string SHAs, searches repositories to find the commit.
477
479
  *
478
480
  * @param repoConfig - The repository configuration
@@ -480,13 +482,28 @@ export async function findRepoForCommit(repoConfig, sha) {
480
482
  * @returns Resolved commit information with repo path
481
483
  */
482
484
  export async function resolveCommitRepo(repoConfig, commitEntry) {
483
- // If it's a CommitRef object, we already have the repo info (O(1))
484
- if (typeof commitEntry === 'object' && commitEntry.sha && commitEntry.repo) {
485
- return {
486
- sha: commitEntry.sha,
487
- repoPath: commitEntry.repo,
488
- absolutePath: join(repoConfig.path, commitEntry.repo),
489
- };
485
+ const hasExplicitRepoPath = typeof commitEntry === 'object'
486
+ && commitEntry !== null
487
+ && typeof commitEntry.sha === 'string'
488
+ && Object.prototype.hasOwnProperty.call(commitEntry, 'repo')
489
+ && typeof commitEntry.repo === 'string';
490
+ // If it's a CommitRef object, normalize and validate its repo info first.
491
+ if (hasExplicitRepoPath) {
492
+ const normalizedRepoPath = normalizeCommitRepoRefPath(repoConfig, commitEntry.repo);
493
+ if (normalizedRepoPath !== null) {
494
+ const absolutePath = normalizedRepoPath
495
+ ? resolve(repoConfig.path, normalizedRepoPath)
496
+ : repoConfig.path;
497
+ if (await isGitRepo(absolutePath) && await commitExistsInRepo(absolutePath, commitEntry.sha)) {
498
+ return {
499
+ sha: commitEntry.sha,
500
+ repoPath: normalizedRepoPath,
501
+ absolutePath,
502
+ };
503
+ }
504
+ }
505
+ // Stored repo context can become stale or malformed; fall back to lookup by SHA.
506
+ return findRepoForCommit(repoConfig, commitEntry.sha);
490
507
  }
491
508
  // It's a string SHA, need to search for it
492
509
  const sha = typeof commitEntry === 'string' ? commitEntry : commitEntry.sha;
@@ -1,8 +1,13 @@
1
1
  import { emitChange } from './change-events.js';
2
2
  import { dbAll, dbExec, dbGet, dbRun } from './db.js';
3
+ import { findRepoForCommit } from './git.js';
4
+ import { normalizeCommitRepoRefPath, normalizePathSlashes } from './git-repo-path.js';
5
+ import { getRepos } from './repos.js';
3
6
  import { needsProgressMigration, parseStoredProgressFile, parseTasksFile } from './state-schema.js';
4
7
  const MIGRATION_VERSION = 'progress-json-v2';
5
8
  const MIGRATION_META_KEY = `state-migration:${MIGRATION_VERSION}`;
9
+ const COMMIT_REPO_MIGRATION_VERSION = 'commit-repo-ref-v1';
10
+ const COMMIT_REPO_MIGRATION_META_KEY = `state-migration:${COMMIT_REPO_MIGRATION_VERSION}`;
6
11
  let status = {
7
12
  state: 'idle',
8
13
  version: MIGRATION_VERSION,
@@ -118,6 +123,50 @@ async function writeMigrationMarker(totalRows, migratedRows) {
118
123
  updated_at = excluded.updated_at
119
124
  `, [MIGRATION_META_KEY, JSON.stringify(marker), completedAt]);
120
125
  }
126
+ async function readCommitRepoMigrationMarker() {
127
+ const row = await dbGet('SELECT value FROM app_meta WHERE key = ?', [COMMIT_REPO_MIGRATION_META_KEY]);
128
+ if (!row?.value) {
129
+ return null;
130
+ }
131
+ try {
132
+ const parsed = JSON.parse(row.value);
133
+ if (!parsed || typeof parsed !== 'object') {
134
+ return null;
135
+ }
136
+ if (parsed.version !== COMMIT_REPO_MIGRATION_VERSION) {
137
+ return null;
138
+ }
139
+ if (typeof parsed.completedAt !== 'string') {
140
+ return null;
141
+ }
142
+ if (typeof parsed.totalRows !== 'number'
143
+ || typeof parsed.migratedRows !== 'number'
144
+ || typeof parsed.unresolvedRefs !== 'number') {
145
+ return null;
146
+ }
147
+ return parsed;
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ async function writeCommitRepoMigrationMarker(totalRows, migratedRows, unresolvedRefs) {
154
+ const completedAt = nowIso();
155
+ const marker = {
156
+ version: COMMIT_REPO_MIGRATION_VERSION,
157
+ completedAt,
158
+ totalRows,
159
+ migratedRows,
160
+ unresolvedRefs
161
+ };
162
+ await dbRun(`
163
+ INSERT INTO app_meta (key, value, updated_at)
164
+ VALUES (?, ?, ?)
165
+ ON CONFLICT(key) DO UPDATE SET
166
+ value = excluded.value,
167
+ updated_at = excluded.updated_at
168
+ `, [COMMIT_REPO_MIGRATION_META_KEY, JSON.stringify(marker), completedAt]);
169
+ }
121
170
  async function migrateProgressRows() {
122
171
  await ensureMetaTable();
123
172
  const marker = await readMigrationMarker();
@@ -185,9 +234,166 @@ async function migrateProgressRows() {
185
234
  await writeMigrationMarker(status.totalRows, status.migratedRows);
186
235
  markCompleted();
187
236
  }
237
+ function getKnownGitRepoPaths(repo) {
238
+ const known = new Set();
239
+ for (const gitRepo of repo.gitRepos || []) {
240
+ known.add(normalizePathSlashes(gitRepo.relativePath));
241
+ }
242
+ return known;
243
+ }
244
+ async function normalizeTaskLogCommitRepos(repo, taskLogs) {
245
+ const knownRepoPaths = getKnownGitRepoPaths(repo);
246
+ let changed = false;
247
+ let unresolvedRefs = 0;
248
+ const normalizedLogs = [];
249
+ for (const taskLog of taskLogs) {
250
+ if (!Array.isArray(taskLog.commits) || taskLog.commits.length === 0) {
251
+ normalizedLogs.push(taskLog);
252
+ continue;
253
+ }
254
+ let taskLogChanged = false;
255
+ const normalizedCommits = [];
256
+ for (const commitEntry of taskLog.commits) {
257
+ if (typeof commitEntry === 'string') {
258
+ normalizedCommits.push(commitEntry);
259
+ continue;
260
+ }
261
+ const normalizedRepoPath = normalizeCommitRepoRefPath(repo, commitEntry.repo);
262
+ if (normalizedRepoPath !== null) {
263
+ let canonicalRepoPath = normalizedRepoPath;
264
+ if (canonicalRepoPath !== '' && !knownRepoPaths.has(canonicalRepoPath)) {
265
+ try {
266
+ const resolved = await findRepoForCommit(repo, commitEntry.sha);
267
+ canonicalRepoPath = resolved.repoPath;
268
+ }
269
+ catch {
270
+ // Keep normalized repo path for unknown/untracked nested repos.
271
+ }
272
+ }
273
+ if (canonicalRepoPath !== commitEntry.repo) {
274
+ changed = true;
275
+ taskLogChanged = true;
276
+ normalizedCommits.push({
277
+ sha: commitEntry.sha,
278
+ repo: canonicalRepoPath
279
+ });
280
+ }
281
+ else {
282
+ normalizedCommits.push(commitEntry);
283
+ }
284
+ continue;
285
+ }
286
+ try {
287
+ const resolved = await findRepoForCommit(repo, commitEntry.sha);
288
+ changed = true;
289
+ taskLogChanged = true;
290
+ normalizedCommits.push({
291
+ sha: commitEntry.sha,
292
+ repo: resolved.repoPath
293
+ });
294
+ }
295
+ catch {
296
+ unresolvedRefs += 1;
297
+ normalizedCommits.push(commitEntry);
298
+ }
299
+ }
300
+ if (taskLogChanged) {
301
+ normalizedLogs.push({
302
+ ...taskLog,
303
+ commits: normalizedCommits
304
+ });
305
+ }
306
+ else {
307
+ normalizedLogs.push(taskLog);
308
+ }
309
+ }
310
+ return {
311
+ taskLogs: normalizedLogs,
312
+ changed,
313
+ unresolvedRefs
314
+ };
315
+ }
316
+ async function migrateCommitRepoRefs() {
317
+ await ensureMetaTable();
318
+ const marker = await readCommitRepoMigrationMarker();
319
+ if (marker) {
320
+ return;
321
+ }
322
+ const [rows, repos] = await Promise.all([
323
+ dbAll(`
324
+ SELECT repo_id, slug, tasks_json, progress_json
325
+ FROM prd_states
326
+ WHERE progress_json IS NOT NULL
327
+ ORDER BY repo_id ASC, slug ASC
328
+ `),
329
+ getRepos()
330
+ ]);
331
+ const repoById = new Map(repos.map((repo) => [repo.id, repo]));
332
+ let migratedRows = 0;
333
+ let unresolvedRefs = 0;
334
+ for (const row of rows) {
335
+ const repo = repoById.get(row.repo_id);
336
+ if (!repo || !row.progress_json) {
337
+ continue;
338
+ }
339
+ try {
340
+ const parsedProgress = JSON.parse(row.progress_json);
341
+ let tasksCountHint;
342
+ let prdNameFallback;
343
+ if (row.tasks_json) {
344
+ try {
345
+ const tasks = parseTasksFile(JSON.parse(row.tasks_json));
346
+ tasksCountHint = tasks.tasks.length;
347
+ prdNameFallback = tasks.prd.name;
348
+ }
349
+ catch {
350
+ // Keep fallback below when tasks_json is malformed.
351
+ }
352
+ }
353
+ const progress = parseStoredProgressFile(parsedProgress, {
354
+ prdNameFallback: prdNameFallback || row.slug,
355
+ totalTasksHint: tasksCountHint
356
+ });
357
+ const normalized = await normalizeTaskLogCommitRepos(repo, progress.taskLogs);
358
+ unresolvedRefs += normalized.unresolvedRefs;
359
+ if (!normalized.changed) {
360
+ continue;
361
+ }
362
+ const updatedAt = nowIso();
363
+ await dbRun(`
364
+ UPDATE prd_states
365
+ SET progress_json = ?, updated_at = ?
366
+ WHERE repo_id = ? AND slug = ?
367
+ `, [
368
+ JSON.stringify({
369
+ ...progress,
370
+ taskLogs: normalized.taskLogs
371
+ }),
372
+ updatedAt,
373
+ row.repo_id,
374
+ row.slug
375
+ ]);
376
+ migratedRows += 1;
377
+ emitChange({
378
+ type: 'change',
379
+ path: `state://${row.repo_id}/${row.slug}/progress.json`,
380
+ repoId: row.repo_id,
381
+ category: 'progress'
382
+ });
383
+ }
384
+ catch {
385
+ // Ignore malformed rows; they remain untouched.
386
+ }
387
+ }
388
+ await writeCommitRepoMigrationMarker(rows.length, migratedRows, unresolvedRefs);
389
+ }
188
390
  async function runMigration() {
189
391
  try {
190
392
  await migrateProgressRows();
393
+ if (status.state === 'failed') {
394
+ return;
395
+ }
396
+ await migrateCommitRepoRefs();
191
397
  }
192
398
  catch (error) {
193
399
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thxgg/steward",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Local-first PRD workflow steward with codemode MCP and web UI.",
5
5
  "type": "module",
6
6
  "author": "thxgg",
@@ -1 +0,0 @@
1
- {"id":"37438177-5e14-4a46-9af4-eae491ba1f24","timestamp":1772204472038,"prerendered":[]}