fix(opensource-pipeline): drop copied reports from the index even when .gitignore re-includes them

A .gitignore rule outranks .git/info/exclude, so a source project that ships !FORK_REPORT.md made git add -A stage the report. Remove both reports from the index after staging, in the forker's first commit and in every fold-in amend. The history test now runs both documented blocks against a negating .gitignore, prints the Passed/Failed summary for run-all.js, and skips on Windows like the publish-guard test.
This commit is contained in:
Wen committed 2026-10-19 07:23:00 -03:00
1 parent 35909e548e
commit f493d9ff66
3 files changed
+58 -20

No files matched your search

+4 -1
View File
@@ -147,6 +147,7 @@ cd TARGET_DIR
git init git init
printf '%s\n' FORK_REPORT.md SANITIZATION_REPORT.md >> .git/info/exclude printf '%s\n' FORK_REPORT.md SANITIZATION_REPORT.md >> .git/info/exclude
git add -A git add -A
git rm -q --cached --ignore-unmatch FORK_REPORT.md SANITIZATION_REPORT.md
git commit -m "Initial open-source release git commit -m "Initial open-source release
Forked from private source. All secrets stripped, internal references Forked from private source. All secrets stripped, internal references
@@ -156,7 +157,9 @@ replaced with configurable placeholders. See .env.example for configuration."
This commit is provisional. The pipeline amends it after sanitizer fixes and packaging, so the This commit is provisional. The pipeline amends it after sanitizer fixes and packaging, so the
published history stays one commit (see the opensource-pipeline skill, Step 3). The reports never published history stays one commit (see the opensource-pipeline skill, Step 3). The reports never
go into it: `.git/info/exclude` is written before `git add -A`, so even a report already present in go into it: `.git/info/exclude` is written before `git add -A`, so even a report already present in
the source project (copied by Step 2) stays untracked. the source project (copied by Step 2) stays untracked. A `.gitignore` rule outranks
`.git/info/exclude`, so a copied `!FORK_REPORT.md` would re-include it; `git rm --cached` drops
the reports from the index in that case.
### Step 7: Generate Fork Report ### Step 7: Generate Fork Report
+2 -1
View File
@@ -91,8 +91,9 @@ sanitizer passed. The reports stay out of it. Fold changes in with:
cd "{STAGING_PATH}" cd "{STAGING_PATH}"
grep -qxF SANITIZATION_REPORT.md .git/info/exclude || printf '%s\n' FORK_REPORT.md SANITIZATION_REPORT.md >> .git/info/exclude grep -qxF SANITIZATION_REPORT.md .git/info/exclude || printf '%s\n' FORK_REPORT.md SANITIZATION_REPORT.md >> .git/info/exclude
git add -A git add -A
git rm -q --cached --ignore-unmatch FORK_REPORT.md SANITIZATION_REPORT.md
git commit --amend --no-edit git commit --amend --no-edit
git status --porcelain # must print nothing git status --porcelain -uno # must print nothing
git rev-list --count HEAD # must print 1 git rev-list --count HEAD # must print 1
``` ```
+52 -18
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Runs the documented Step 6 of agents/opensource-forker.md in a throwaway dir that already * Runs the documented Step 6 of agents/opensource-forker.md, then the opensource-pipeline fold-in
* holds FORK_REPORT.md / SANITIZATION_REPORT.md (as if the source project shipped them), and * block, in a throwaway dir that already holds FORK_REPORT.md / SANITIZATION_REPORT.md (as if the
* checks they stay out of the first commit. * source project shipped them, with a .gitignore that re-includes them), and checks they stay out
* of the single published commit.
* *
* Run with: node tests/skills/opensource-forker-history.test.js * Run with: node tests/skills/opensource-forker-history.test.js
*/ */
@@ -15,19 +16,20 @@ const os = require('os');
const path = require('path'); const path = require('path');
const { spawnSync } = require('child_process'); const { spawnSync } = require('child_process');
const AGENT_MD = path.join(__dirname, '..', '..', 'agents', 'opensource-forker.md'); const ROOT = path.join(__dirname, '..', '..');
const NAME = 'pre-existing reports stay out of the published fork commit';
const step = fs.readFileSync(AGENT_MD, 'utf8').split('### Step 6: Clean Git History')[1]; function bashBlock(file, after) {
assert.ok(step, 'opensource-forker.md is missing "### Step 6: Clean Git History"'); const section = fs.readFileSync(path.join(ROOT, file), 'utf8').split(after)[1];
const block = step.match(/```bash\n([\s\S]*?)```/); assert.ok(section, `${file} is missing "${after}"`);
assert.ok(block, 'no bash block in Step 6'); const block = section.match(/```bash\n([\s\S]*?)```/);
assert.ok(block, `no bash block after "${after}" in ${file}`);
return block[1];
}
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'oss-forker-')); function run(script, dir) {
try { const r = spawnSync('bash', ['-ec', script], {
for (const f of ['README.md', 'FORK_REPORT.md', 'SANITIZATION_REPORT.md']) { cwd: dir,
fs.writeFileSync(path.join(dir, f), 'x\n');
}
const r = spawnSync('bash', ['-ec', block[1].replaceAll('TARGET_DIR', dir)], {
encoding: 'utf8', encoding: 'utf8',
env: { env: {
...process.env, ...process.env,
@@ -36,13 +38,45 @@ try {
}, },
}); });
assert.strictEqual(r.status, 0, r.stderr); assert.strictEqual(r.status, 0, r.stderr);
return r.stdout;
}
if (process.platform === 'win32') {
// The documented blocks are bash; a Windows temp path does not survive `cd`.
console.log(` - skipped: ${NAME} (bash test on Windows)`);
console.log('\nPassed: 0');
console.log('Failed: 0');
process.exit(0);
}
const step6 = bashBlock('agents/opensource-forker.md', '### Step 6: Clean Git History');
const foldIn = bashBlock('skills/opensource-pipeline/SKILL.md', 'Fold changes in with:');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'oss-forker-'));
let failed = 0;
try {
for (const f of ['README.md', 'FORK_REPORT.md', 'SANITIZATION_REPORT.md']) {
fs.writeFileSync(path.join(dir, f), 'x\n');
}
// A .gitignore rule outranks .git/info/exclude, so a negation would re-include the reports.
fs.writeFileSync(path.join(dir, '.gitignore'), '!FORK_REPORT.md\n!SANITIZATION_REPORT.md\n');
run(step6.replaceAll('TARGET_DIR', dir), dir);
fs.writeFileSync(path.join(dir, 'CLAUDE.md'), 'packaged\n');
const out = run(foldIn.replaceAll('{STAGING_PATH}', dir), dir).trim().split('\n');
assert.strictEqual(out.at(-1), '1', `fold-in must keep one commit, got: ${out.at(-1)}`);
const files = spawnSync('git', ['ls-tree', '-r', '--name-only', 'HEAD'], { cwd: dir, encoding: 'utf8' }).stdout.trim(); const files = spawnSync('git', ['ls-tree', '-r', '--name-only', 'HEAD'], { cwd: dir, encoding: 'utf8' }).stdout.trim();
assert.strictEqual(files, 'README.md', `first commit must hold only README.md, got: ${files}`); assert.strictEqual(files, '.gitignore\nCLAUDE.md\nREADME.md', `commit must not hold the reports, got: ${files}`);
console.log(' ✓ pre-existing reports stay out of the first fork commit'); console.log(` ✓ ${NAME}`);
} catch (err) { } catch (err) {
console.log(' ✗ pre-existing reports stay out of the first fork commit'); failed = 1;
console.log(` ✗ ${NAME}`);
console.log(` Error: ${err.message}`); console.log(` Error: ${err.message}`);
process.exitCode = 1;
} finally { } finally {
fs.rmSync(dir, { recursive: true, force: true }); fs.rmSync(dir, { recursive: true, force: true });
} }
console.log(`\nPassed: ${1 - failed}`);
console.log(`Failed: ${failed}`);
process.exitCode = failed;