11import test from 'node:test' ;
22import assert from 'node:assert/strict' ;
3+ import { execSync } from 'node:child_process' ;
34import { existsSync , readdirSync , readFileSync } from 'node:fs' ;
45import { join } from 'node:path' ;
56
67const root = new URL ( '..' , import . meta. url ) ;
78const path = ( ...parts ) => join ( root . pathname , ...parts ) ;
89
10+ function gitLsFiles ( ) {
11+ return execSync ( 'git ls-files' , { cwd : root . pathname , encoding : 'utf-8' } ) . trim ( ) . split ( '\n' ) . filter ( Boolean ) ;
12+ }
13+
14+ function readSystemPaths ( ) {
15+ const updater = readFileSync ( path ( 'update-system.mjs' ) , 'utf-8' ) ;
16+ const match = updater . match ( / c o n s t S Y S T E M _ P A T H S = \[ ( [ \s \S ] * ?) \] ; / ) ;
17+ assert . ok ( match , 'SYSTEM_PATHS should be defined in update-system.mjs' ) ;
18+ return [ ...match [ 1 ] . matchAll ( / ' ( [ ^ ' ] + ) ' / g) ] . map ( ( item ) => item [ 1 ] ) ;
19+ }
20+
921test ( 'user-facing setup docs contain copy-pasteable clone commands' , ( ) => {
1022 for ( const file of [ 'README.md' , 'docs/SETUP.md' ] ) {
1123 const content = readFileSync ( path ( file ) , 'utf-8' ) ;
@@ -20,6 +32,9 @@ test('README project map stays aligned with shipped workflow families', () => {
2032 for ( const shippedItem of [ 'analytics.md' , 'feed-scan.md' , 'negotiate.md' , 'equity.md' , 'import-cv.mjs' ] ) {
2133 assert . match ( readme , new RegExp ( shippedItem . replace ( '.' , '\\.' ) ) , `${ shippedItem } should be visible in README` ) ;
2234 }
35+ for ( const localeDir of [ 'de/' , 'es/' , 'fr/' , 'hi/' , 'ja/' , 'pt/' ] ) {
36+ assert . match ( readme , new RegExp ( localeDir . replace ( '/' , '\\/' ) ) , `${ localeDir } should be visible in README` ) ;
37+ }
2338} ) ;
2439
2540test ( 'README includes GitHub-renderable demo and adoption links' , ( ) => {
@@ -63,6 +78,89 @@ test('system updater uses explicit docs paths', () => {
6378 }
6479} ) ;
6580
81+ test ( 'system updater covers every tracked system file' , ( ) => {
82+ const systemPaths = readSystemPaths ( ) ;
83+ const userExact = new Set ( [
84+ 'article-digest.md' ,
85+ 'config/profile.yml' ,
86+ 'cv.md' ,
87+ 'feeds.yml' ,
88+ 'modes/_profile.md' ,
89+ 'portals.yml' ,
90+ ] ) ;
91+ const userPrefixes = [
92+ 'batch/logs/' ,
93+ 'batch/tracker-additions/' ,
94+ 'data/' ,
95+ 'interview-prep/' ,
96+ 'jds/' ,
97+ 'output/' ,
98+ 'reports/' ,
99+ ] ;
100+ const generatedExact = new Set ( [ 'dashboard/dashboard' ] ) ;
101+
102+ const isCovered = ( file ) => systemPaths . some ( ( entry ) => (
103+ entry . endsWith ( '/' ) ? file . startsWith ( entry ) : file === entry
104+ ) ) ;
105+
106+ const missing = gitLsFiles ( ) . filter ( ( file ) => (
107+ ! userExact . has ( file ) &&
108+ ! generatedExact . has ( file ) &&
109+ ! userPrefixes . some ( ( prefix ) => file . startsWith ( prefix ) ) &&
110+ ! isCovered ( file )
111+ ) ) ;
112+
113+ assert . deepEqual ( missing , [ ] ) ;
114+ } ) ;
115+
116+ test ( 'data contract documents current user and system boundaries' , ( ) => {
117+ const contract = readFileSync ( path ( 'DATA_CONTRACT.md' ) , 'utf-8' ) ;
118+
119+ for ( const userPath of [ 'feeds.yml' , 'jds/*' , 'batch/tracker-additions/*' ] ) {
120+ assert . match ( contract , new RegExp ( userPath . replaceAll ( '*' , '\\*' ) ) , `${ userPath } should be documented as user-layer data` ) ;
121+ }
122+
123+ for ( const systemPath of [
124+ 'lib/**' ,
125+ 'tests/**' ,
126+ 'templates/feeds.example.yml' ,
127+ 'templates/cv-i18n.yml' ,
128+ 'docs/assets/*' ,
129+ 'examples/*' ,
130+ 'modes/{de,es,fr,hi,ja,pt}/' ,
131+ ] ) {
132+ assert . match ( contract , new RegExp ( systemPath . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ) , `${ systemPath } should be documented as system-layer data` ) ;
133+ }
134+ } ) ;
135+
136+ test ( 'root-level English modes do not contain known Spanish instruction leakage' , ( ) => {
137+ const rootModeFiles = readdirSync ( path ( 'modes' ) ) . filter ( ( file ) => file . endsWith ( '.md' ) && ! file . startsWith ( '_' ) ) ;
138+ const leakedPhrases = [
139+ 'Modo:' ,
140+ 'Configuración' ,
141+ 'Ejecución recomendada' ,
142+ 'Leer `portals.yml`' ,
143+ 'Idiomas soportados' ,
144+ 'Usar el template' ,
145+ 'Post-generación' ,
146+ 'Para cada empresa' ,
147+ ] ;
148+
149+ for ( const file of rootModeFiles ) {
150+ const content = readFileSync ( path ( 'modes' , file ) , 'utf-8' ) ;
151+ for ( const phrase of leakedPhrases ) {
152+ assert . equal ( content . includes ( phrase ) , false , `${ file } should not contain Spanish phrase: ${ phrase } ` ) ;
153+ }
154+ }
155+ } ) ;
156+
157+ test ( 'root gitignore covers common Python virtual environments' , ( ) => {
158+ const gitignore = readFileSync ( path ( '.gitignore' ) , 'utf-8' ) ;
159+ for ( const entry of [ '.venv/' , 'venv/' , 'env/' ] ) {
160+ assert . match ( gitignore , new RegExp ( `^${ entry . replace ( '.' , '\\.' ) . replace ( '/' , '\\/' ) } $` , 'm' ) , `${ entry } should be ignored` ) ;
161+ }
162+ } ) ;
163+
66164test ( 'mode files use canonical user-layer paths' , ( ) => {
67165 const modeDir = path ( 'modes' ) ;
68166 const modeFiles = readdirSync ( modeDir ) . filter ( ( file ) => file . endsWith ( '.md' ) ) ;
0 commit comments