fix(migration): re-review MAJOR (30-fix-references) — drop stale repository.directory + test the package.json branch
The /trekreview re-review's high-effort deep read of 30-fix-references.mjs surfaced 2 MAJOR (both pre-existing in the original delivery, not remediation regressions): - PLAN_EXECUTE_DRIFT: the rewriter set repository.url but left a monorepo-relative repository.directory (e.g. llm-security's plugins/llm-security) intact, so the standalone package.json/plugin.json shipped a directory pointing nowhere. Now dropped in BOTH the plugin.json (3a) and package.json (3b) object branches; idempotency preserved (gated on 'directory' in repository). - MISSING_TEST: the package.json rewrite branch only ran against voyage/llm-security but the only test used graceful-handoff (no package.json) — unguarded. Added a test driving both branches against a synthetic llm-security extract (pre-init .git, fixtures carrying repository.directory), asserting reconciliation + directory-drop on both files + idempotency. Independent code-correctness reviewer re-verified both RESOLVED, no new issue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fef4b33c97
commit
85a8ee345e
2 changed files with 62 additions and 7 deletions
|
|
@ -6,7 +6,9 @@
|
|||
// 2. Rewrites any remaining ../../README.md and ../../.claude-plugin/marketplace.json reference
|
||||
// (e.g. graceful-handoff README footer, linkedin-studio remediation docs) to the absolute catalog URL.
|
||||
// 3. Sets plugin.json `repository` (and package.json homepage/repository/bugs, when present) to the
|
||||
// standalone open/<plugin> HTTPS URL read from plugin-map.json.
|
||||
// standalone open/<plugin> HTTPS URL read from plugin-map.json, and DROPS any monorepo-relative
|
||||
// `repository.directory` sub-path (e.g. llm-security's "plugins/llm-security") — it points nowhere
|
||||
// once the content lives at the standalone repo root.
|
||||
// ai-psychosis already points at open/ai-psychosis (verify-only no-op, M14). Idempotent: a second run
|
||||
// rewrites zero files. Reports every file it touched. NULL push (D8) — operates only inside $WORK/<key>.
|
||||
//
|
||||
|
|
@ -62,14 +64,19 @@ async function main() {
|
|||
if (next !== orig) { await fs.writeFile(file, next, 'utf8'); changed.push(path.relative(dest, file)); }
|
||||
}
|
||||
|
||||
// 3a) plugin.json repository → standalone URL
|
||||
// 3a) plugin.json repository → standalone URL (+ drop any stale monorepo-relative repository.directory)
|
||||
const pjPath = path.join(dest, '.claude-plugin', 'plugin.json');
|
||||
if (await exists(pjPath)) {
|
||||
const pj = await readJson(pjPath);
|
||||
const cur = pj.repository && typeof pj.repository === 'object' ? pj.repository.url : pj.repository;
|
||||
if (cur !== base) {
|
||||
if (pj.repository && typeof pj.repository === 'object') pj.repository.url = base;
|
||||
else pj.repository = base;
|
||||
let touched = false;
|
||||
if (pj.repository && typeof pj.repository === 'object') {
|
||||
if (pj.repository.url !== base) { pj.repository.url = base; touched = true; }
|
||||
// A monorepo-relative repository.directory points nowhere in the standalone repo — drop it.
|
||||
if ('directory' in pj.repository) { delete pj.repository.directory; touched = true; }
|
||||
} else if (pj.repository !== base) {
|
||||
pj.repository = base; touched = true;
|
||||
}
|
||||
if (touched) {
|
||||
await fs.writeFile(pjPath, JSON.stringify(pj, null, 2) + '\n', 'utf8');
|
||||
changed.push('.claude-plugin/plugin.json');
|
||||
}
|
||||
|
|
@ -83,6 +90,8 @@ async function main() {
|
|||
if (pkg.homepage !== base) { pkg.homepage = base; touched = true; }
|
||||
if (pkg.repository && typeof pkg.repository === 'object') {
|
||||
if (pkg.repository.url !== base) { pkg.repository.url = base; touched = true; }
|
||||
// Drop any stale monorepo-relative repository.directory (points nowhere in the standalone repo).
|
||||
if ('directory' in pkg.repository) { delete pkg.repository.directory; touched = true; }
|
||||
} else if (pkg.repository && pkg.repository !== base) { pkg.repository = base; touched = true; }
|
||||
if (pkg.bugs && typeof pkg.bugs === 'object' && pkg.bugs.url !== `${base}/issues`) {
|
||||
pkg.bugs.url = `${base}/issues`; touched = true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -45,3 +46,48 @@ test('a second run rewrites zero files (idempotent)', () => {
|
|||
assert.equal(r.status, 0, r.stderr);
|
||||
assert.match(r.stdout, /rewrote 0 file\(s\)/, `expected no-op, got:\n${r.stdout}`);
|
||||
});
|
||||
|
||||
// The package.json branch never runs against graceful-handoff (it has no root package.json), so it was
|
||||
// previously unguarded. Drive it directly against a SYNTHETIC extract for a key that carries a monorepo
|
||||
// repository.directory (llm-security) — pre-init .git so the script skips extraction and rewrites in place.
|
||||
test('package.json + plugin.json reconciled, stale repository.directory dropped (synthetic llm-security)', () => {
|
||||
const tmpWork = mkdtempSync(path.join(os.tmpdir(), 'fixref-pkg-'));
|
||||
try {
|
||||
const dest = path.join(tmpWork, 'llm-security');
|
||||
mkdirSync(path.join(dest, '.claude-plugin'), { recursive: true });
|
||||
assert.equal(spawnSync('git', ['-C', dest, 'init', '-q']).status, 0, 'git init must succeed');
|
||||
|
||||
const monoUrl = 'https://git.fromaitochitta.com/open/ktg-plugin-marketplace';
|
||||
writeFileSync(path.join(dest, 'package.json'), JSON.stringify({
|
||||
name: 'llm-security', version: '7.7.2',
|
||||
homepage: monoUrl,
|
||||
repository: { type: 'git', url: monoUrl, directory: 'plugins/llm-security' },
|
||||
bugs: { url: `${monoUrl}/issues` },
|
||||
}, null, 2) + '\n');
|
||||
writeFileSync(path.join(dest, '.claude-plugin', 'plugin.json'), JSON.stringify({
|
||||
name: 'llm-security',
|
||||
repository: { type: 'git', url: monoUrl, directory: 'plugins/llm-security' },
|
||||
}, null, 2) + '\n');
|
||||
|
||||
const run = () => spawnSync(process.execPath, [SCRIPT, 'llm-security'], { encoding: 'utf8', env: { ...process.env, WORK: tmpWork } });
|
||||
const r = run();
|
||||
assert.equal(r.status, 0, `fix-references failed:\n${r.stdout}\n${r.stderr}`);
|
||||
|
||||
const base = 'https://git.fromaitochitta.com/open/llm-security';
|
||||
const pkg = JSON.parse(readFileSync(path.join(dest, 'package.json'), 'utf8'));
|
||||
assert.equal(pkg.homepage, base, 'package.json homepage must be reconciled');
|
||||
assert.equal(pkg.repository.url, base, 'package.json repository.url must be reconciled');
|
||||
assert.equal(pkg.bugs.url, `${base}/issues`, 'package.json bugs.url must be reconciled');
|
||||
assert.ok(!('directory' in pkg.repository), 'stale repository.directory must be dropped from package.json');
|
||||
|
||||
const pj = JSON.parse(readFileSync(path.join(dest, '.claude-plugin', 'plugin.json'), 'utf8'));
|
||||
assert.equal(pj.repository.url, base, 'plugin.json repository.url must be reconciled');
|
||||
assert.ok(!('directory' in pj.repository), 'stale repository.directory must be dropped from plugin.json');
|
||||
|
||||
const r2 = run();
|
||||
assert.equal(r2.status, 0, r2.stderr);
|
||||
assert.match(r2.stdout, /rewrote 0 file\(s\)/, `expected idempotent no-op, got:\n${r2.stdout}`);
|
||||
} finally {
|
||||
rmSync(tmpWork, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue