@thxgg/steward 0.1.20 → 0.1.22
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/{Dn6yoG20.js → B4iDhohi.js} +2 -2
- package/.output/public/_nuxt/{CV5HFGKm.js → BQBm6AsP.js} +1 -1
- package/.output/public/_nuxt/{D6aZT905.js → BhBgWPCX.js} +1 -1
- package/.output/public/_nuxt/{BTHAQvnA.js → BuvhTQbX.js} +1 -1
- package/.output/public/_nuxt/{a87LfEfa.js → CL_YIWu4.js} +1 -1
- package/.output/public/_nuxt/CZWhz2fI.js +1 -0
- package/.output/public/_nuxt/C_5PUO5r.js +1 -0
- package/.output/public/_nuxt/{C2LWefrW.js → C_Sxjx0M.js} +1 -1
- package/.output/public/_nuxt/CjDuB5VD.js +1 -0
- package/.output/public/_nuxt/{DDV2bymk.js → CuOw9LiK.js} +1 -1
- package/.output/public/_nuxt/{CzCDTesu.js → DQivOuJJ.js} +1 -1
- package/.output/public/_nuxt/DUrbBKgG.js +3 -0
- package/.output/public/_nuxt/{B7kcsnX1.js → Du8Y9zEm.js} +1 -1
- package/.output/public/_nuxt/{DSqaInP-.js → HZy5mmMm.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/3e08a0b0-262b-4fb5-b213-4aa35df11afb.json +1 -0
- package/.output/public/_nuxt/entry.xHdymH38.css +1 -0
- package/.output/public/_nuxt/{D9fz0wy8.js → okqELTtf.js} +1 -1
- package/.output/public/_nuxt/{ClxxfTZn.js → rG99Mq9f.js} +1 -1
- package/.output/public/_nuxt/rTDTR03M.js +30 -0
- package/.output/server/chunks/_/git-api.mjs +10 -23
- package/.output/server/chunks/_/git-api.mjs.map +1 -1
- package/.output/server/chunks/_/prd-service.mjs +1 -3
- package/.output/server/chunks/_/prd-service.mjs.map +1 -1
- package/.output/server/chunks/_/watcher.mjs +1 -2
- package/.output/server/chunks/_/watcher.mjs.map +1 -1
- package/.output/server/chunks/build/_prd_-DY25apyl.mjs +2 -1
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/default-BKKgG7HJ.mjs +2 -1
- package/.output/server/chunks/build/error-404-Bf6kdO80.mjs +2 -1
- package/.output/server/chunks/build/error-500-D_bcARXN.mjs +2 -1
- package/.output/server/chunks/build/index-DE1tjHAd.mjs +2 -1
- package/.output/server/chunks/build/nuxt-link-SvT1nf8Z.mjs +1 -1
- package/.output/server/chunks/build/repo-graph-CUcJKW1F.mjs +2 -1
- package/.output/server/chunks/build/server.mjs +3 -2
- package/.output/server/chunks/build/usePrd-hXZOmvAv.mjs +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +1510 -690
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/browse.get.mjs +1 -0
- package/.output/server/chunks/routes/api/browse.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get.mjs +3 -3
- package/.output/server/chunks/routes/api/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs +3 -5
- 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 +3 -5
- package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs +3 -5
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs +3 -5
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +3 -5
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/archive.post.mjs +2 -4
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/archive.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +3 -5
- 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 +3 -5
- 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 +3 -5
- 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 +3 -5
- 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 +3 -5
- 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 +3 -5
- 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 +3 -3
- package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs +3 -3
- package/.output/server/chunks/routes/api/runtime.get.mjs +3 -2
- package/.output/server/chunks/routes/api/runtime.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/state-migration/status.get.mjs +3 -2
- package/.output/server/chunks/routes/api/state-migration/status.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/watch.get.mjs +3 -3
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/index.mjs +3 -2
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/dist/host/src/api/git.js +14 -7
- package/dist/server/utils/git-repo-path.js +63 -0
- package/dist/server/utils/git.js +26 -9
- package/dist/server/utils/state-migration.js +206 -0
- package/package.json +1 -1
- package/.output/public/_nuxt/B3PShd4B.js +0 -3
- package/.output/public/_nuxt/B9j1BHt9.js +0 -1
- package/.output/public/_nuxt/BGSfDLaX.js +0 -1
- package/.output/public/_nuxt/DK5VWQk7.js +0 -1
- package/.output/public/_nuxt/builds/meta/37438177-5e14-4a46-9af4-eae491ba1f24.json +0 -1
- package/.output/public/_nuxt/entry.Dp3jx0Yw.css +0 -1
- package/.output/public/_nuxt/zhOijcjw.js +0 -30
- package/.output/server/chunks/_/git.mjs +0 -296
- package/.output/server/chunks/_/git.mjs.map +0 -1
- package/.output/server/chunks/_/repos.mjs +0 -272
- 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 {
|
|
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';
|
package/.output/server/index.mjs
CHANGED
|
@@ -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 {
|
|
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 '
|
|
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":";;;;;;;;;;;"}
|
package/dist/host/src/api/git.js
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
+
const normalizedRepoPath = normalizeCommitRepoRefPath(repo, repoPath);
|
|
19
|
+
if (normalizedRepoPath === null) {
|
|
20
|
+
throw new Error('Invalid repo path: path traversal not allowed');
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/server/utils/git.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
|
-
import {
|
|
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,
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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);
|