diff --git a/.gitattributes b/.gitattributes index e67042f0a..2b55295aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -30,6 +30,14 @@ bin/* text eol=lf *.mjs text eol=lf *.cjs text eol=lf +# Runtime and generated iOS assets are copied byte-for-byte and hash-pinned in +# the GStack 2 provenance manifests. Keep their source bytes platform-neutral +# so a Windows checkout cannot turn CRLF conversion into generated drift. +*.swift text eol=lf +*.h text eol=lf +*.m text eol=lf +*.template text eol=lf + # Binary files — never touch. *.png binary *.jpg binary diff --git a/test/gstack2-skills.test.ts b/test/gstack2-skills.test.ts index 9685b142e..e9a7b0592 100644 --- a/test/gstack2-skills.test.ts +++ b/test/gstack2-skills.test.ts @@ -15,6 +15,14 @@ describe('GStack 2 skill parity', () => { .toBe(blobShaForPath('cso/SKILL.md.tmpl')); }); + test('pins hash-provenance asset sources to LF on every checkout', () => { + const attributes = readFileSync(join(ROOT, '.gitattributes'), 'utf8'); + const normalizedLines = attributes.split(/\r?\n/).map((line) => line.trim().replace(/\s+/g, ' ')); + for (const pattern of ['*.swift', '*.h', '*.m', '*.template']) { + expect(normalizedLines).toContain(`${pattern} text eol=lf`); + } + }); + test('preserves the pinned specialist corpus and generated evidence', () => { const result = runParity(); expect(result.sources).toBe(55);