Skip to content

Commit 8978e21

Browse files
author
DavidQ
committed
BUILD PR: add shared extraction enforcement guard and block alias usage/regression.
1 parent df128b2 commit 8978e21

10 files changed

Lines changed: 279 additions & 17 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
MODEL: GPT-5.3-codex
22
REASONING: high
33
COMMAND:
4-
Execute docs/pr/BUILD_PR_SHARED_EXTRACTION_16_ALIAS_IMPORTS_NETWORK_SAMPLE_C.md exactly.
5-
Edit only these files:
6-
- games/network_sample_c/game/ReconciliationLayerAdapter.js
7-
- games/network_sample_c/game/StateTimelineBuffer.js
8-
Fail fast if jsconfig.json is missing, the @shared alias is not present, or required shared target files are missing.
4+
Execute docs/pr/BUILD_PR_SHARED_EXTRACTION_17_ENFORCEMENT_GUARD.md exactly.
5+
Edit only these repo files:
6+
- tools/dev/checkSharedExtractionGuard.mjs (new file)
7+
- package.json (only if it already exists and already has a scripts section; add only the one script if missing)
98
Do not expand scope.
10-
Package the delta output to <project folder>/tmp/BUILD_PR_SHARED_EXTRACTION_16_ALIAS_IMPORTS_NETWORK_SAMPLE_C_delta.zip
9+
Package the delta output to <project folder>/tmp/BUILD_PR_SHARED_EXTRACTION_17_ENFORCEMENT_GUARD_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD PR: switch network_sample_c shared helper imports to @shared alias only.
1+
BUILD PR: add shared extraction enforcement guard and block alias usage/regression.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Built the next executable alias rollout step, limited to network_sample_c only and assuming the existing @shared alias bootstrap.
1+
Built an enforcement guard step that protects the shared-extraction work, blocks reintroduced local helpers, blocks fragile relative shared imports, and blocks further @shared alias usage.

docs/dev/reports/file_tree.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
docs/
22
pr/
3-
BUILD_PR_SHARED_EXTRACTION_16_ALIAS_IMPORTS_NETWORK_SAMPLE_C.md
3+
BUILD_PR_SHARED_EXTRACTION_17_ENFORCEMENT_GUARD.md
44
dev/
55
codex_commands.md
66
commit_comment.txt

docs/dev/reports/validation_checklist.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ SESSION VALIDATION GATE
22
- Bundle type: BUILD
33
- One PR purpose only: yes
44
- Exact target files listed: yes
5-
- Config untouched by design: yes
6-
- Fail-fast gate explicit: yes
5+
- Exact guard behavior listed: yes
6+
- Alias usage explicitly treated as disallowed: yes
77
- Scope minimized: yes
88
- ZIP repo-structured and execution-ready: yes
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# BUILD_PR_SHARED_EXTRACTION_17_ENFORCEMENT_GUARD
2+
3+
## Purpose
4+
Add a narrow regression guard that protects the completed shared-extraction work and prevents future drift.
5+
6+
## Single PR Purpose
7+
Create one lightweight repo-local enforcement script that checks for these regressions only:
8+
9+
1. reintroduction of local helper definitions for:
10+
- `asFiniteNumber`
11+
- `asPositiveInteger`
12+
- `isPlainObject`
13+
14+
2. reintroduction of fragile direct shared relative imports like:
15+
- `../shared/`
16+
- `../../shared/`
17+
- `../../../src/shared/`
18+
19+
3. accidental `@shared/` alias usage in repo source files
20+
21+
This BUILD intentionally treats alias usage as disallowed technical debt and prevents further expansion of it.
22+
23+
## Exact Files Allowed
24+
Edit only these 4 files:
25+
26+
1. `tools/dev/checkSharedExtractionGuard.mjs` **(new file)**
27+
2. `package.json` **only if a minimal script entry is needed**
28+
3. `docs/dev/commit_comment.txt` **inside this BUILD bundle only**
29+
4. `docs/dev/next_command.txt` **inside this BUILD bundle only**
30+
31+
Do not edit any source/runtime file other than the one new guard script.
32+
Do not edit any other repo file.
33+
34+
## Exact New File
35+
Create:
36+
37+
`tools/dev/checkSharedExtractionGuard.mjs`
38+
39+
## Exact Script Requirements
40+
The script must:
41+
42+
### 1) Scan only these repo roots if they exist
43+
- `src/`
44+
- `games/`
45+
- `samples/`
46+
- `tools/`
47+
48+
### 2) Inspect only source-like files with these extensions
49+
- `.js`
50+
- `.mjs`
51+
52+
### 3) Ignore:
53+
- `node_modules/`
54+
- `.git/`
55+
- `tmp/`
56+
- generated ZIP outputs
57+
- binary assets
58+
59+
### 4) Report failure if any file contains a local helper definition matching any of these patterns
60+
- `function asFiniteNumber(`
61+
- `function asPositiveInteger(`
62+
- `function isPlainObject(`
63+
- `const asFiniteNumber =`
64+
- `const asPositiveInteger =`
65+
- `const isPlainObject =`
66+
67+
### 5) Report failure if any file contains direct shared relative imports matching any of these substrings
68+
- `../shared/`
69+
- `../../shared/`
70+
- `../../../src/shared/`
71+
- `../../../../shared/`
72+
73+
### 6) Report failure if any file contains alias imports using:
74+
- `@shared/`
75+
76+
### 7) Exit behavior
77+
- exit code `0` when no violations are found
78+
- exit code `1` when one or more violations are found
79+
80+
### 8) Output behavior
81+
Print:
82+
- a short success message when clean
83+
- for failures:
84+
- file path
85+
- matched violation type
86+
- matched text snippet or rule name
87+
88+
## package.json Rule
89+
Only if `package.json` already exists and already has a `scripts` section:
90+
91+
Add exactly one script entry if missing:
92+
```json
93+
"check:shared-extraction-guard": "node tools/dev/checkSharedExtractionGuard.mjs"
94+
```
95+
96+
Rules:
97+
- make the minimum edit only
98+
- do not reformat the whole file
99+
- do not add unrelated scripts
100+
- if `package.json` does not exist, do not create it
101+
- if `scripts` does not exist, do not invent broader package metadata in this PR
102+
103+
## Hard Constraints
104+
- do not modify application logic
105+
- do not edit engine files
106+
- do not edit advanced files
107+
- do not edit sample files
108+
- do not edit network_sample_c source files
109+
- do not create CI config
110+
- do not add lint tooling
111+
- do not add alias support
112+
- keep one PR purpose only
113+
114+
## Validation Checklist
115+
1. Confirm only the allowed files changed
116+
2. Confirm `tools/dev/checkSharedExtractionGuard.mjs` exists
117+
3. Confirm the guard fails on:
118+
- local helper reintroduction
119+
- direct shared relative import strings
120+
- `@shared/` alias strings
121+
4. Confirm the guard exits `0` when clean
122+
5. Confirm no runtime/source logic changed
123+
6. Confirm no CI/lint/toolchain config was added beyond the optional minimal `package.json` script entry
124+
125+
## Non-Goals
126+
- no alias rollout
127+
- no alias cleanup in this PR
128+
- no repo-wide source edits
129+
- no CI integration
130+
- no ESLint rule creation
131+
- no refactor beyond the new guard script

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"type": "module",
33
"scripts": {
44
"test": "node ./scripts/run-node-tests.mjs",
5-
"build:manifest": "node ./scripts/generate-sample-manifest.mjs"
5+
"build:manifest": "node ./scripts/generate-sample-manifest.mjs",
6+
"check:shared-extraction-guard": "node tools/dev/checkSharedExtractionGuard.mjs"
67
}
78
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
4+
const SCAN_ROOTS = ["src", "games", "samples", "tools"];
5+
const ALLOWED_EXTENSIONS = new Set([".js", ".mjs"]);
6+
const IGNORED_DIRS = new Set(["node_modules", ".git", "tmp"]);
7+
8+
const LOCAL_HELPER_RULES = [
9+
{ rule: "local-helper-definition", regex: /function\s+asFiniteNumber\s*\(/g, label: "rule:helper-fn-asFiniteNumber" },
10+
{ rule: "local-helper-definition", regex: /function\s+asPositiveInteger\s*\(/g, label: "rule:helper-fn-asPositiveInteger" },
11+
{ rule: "local-helper-definition", regex: /function\s+isPlainObject\s*\(/g, label: "rule:helper-fn-isPlainObject" },
12+
{ rule: "local-helper-definition", regex: /const\s+asFiniteNumber\s*=/g, label: "rule:helper-const-asFiniteNumber" },
13+
{ rule: "local-helper-definition", regex: /const\s+asPositiveInteger\s*=/g, label: "rule:helper-const-asPositiveInteger" },
14+
{ rule: "local-helper-definition", regex: /const\s+isPlainObject\s*=/g, label: "rule:helper-const-isPlainObject" }
15+
];
16+
17+
const DIRECT_SHARED_IMPORT_RULES = [
18+
{ rule: "direct-shared-relative-import", regex: /\.\.\/shared\//g, label: "rule:relative-shared-depth-1" },
19+
{ rule: "direct-shared-relative-import", regex: /\.\.\/\.\.\/shared\//g, label: "rule:relative-shared-depth-2" },
20+
{ rule: "direct-shared-relative-import", regex: /\.\.\/\.\.\/\.\.\/src\/shared\//g, label: "rule:relative-shared-src-depth-3" },
21+
{ rule: "direct-shared-relative-import", regex: /\.\.\/\.\.\/\.\.\/\.\.\/shared\//g, label: "rule:relative-shared-depth-4" }
22+
];
23+
24+
const ALIAS_RULE = { rule: "shared-alias-import-disallowed", regex: /@shared\//g, label: "rule:shared-alias-marker" };
25+
26+
async function pathExists(targetPath) {
27+
try {
28+
await fs.access(targetPath);
29+
return true;
30+
} catch {
31+
return false;
32+
}
33+
}
34+
35+
async function collectSourceFiles(startDir, outFiles) {
36+
const entries = await fs.readdir(startDir, { withFileTypes: true });
37+
for (const entry of entries) {
38+
const entryPath = path.join(startDir, entry.name);
39+
if (entry.isDirectory()) {
40+
if (IGNORED_DIRS.has(entry.name)) continue;
41+
await collectSourceFiles(entryPath, outFiles);
42+
continue;
43+
}
44+
45+
if (!entry.isFile()) continue;
46+
if (entry.name.toLowerCase().endsWith(".zip")) continue;
47+
48+
const ext = path.extname(entry.name);
49+
if (!ALLOWED_EXTENSIONS.has(ext)) continue;
50+
51+
outFiles.push(entryPath);
52+
}
53+
}
54+
55+
function findViolations(fileContent, filePathFromRoot) {
56+
const violations = [];
57+
58+
for (const check of LOCAL_HELPER_RULES) {
59+
const matches = fileContent.match(check.regex) || [];
60+
for (const _match of matches) {
61+
violations.push({
62+
file: filePathFromRoot,
63+
type: check.rule,
64+
match: check.label
65+
});
66+
}
67+
}
68+
69+
for (const check of DIRECT_SHARED_IMPORT_RULES) {
70+
const matches = fileContent.match(check.regex) || [];
71+
for (const _match of matches) {
72+
violations.push({
73+
file: filePathFromRoot,
74+
type: check.rule,
75+
match: check.label
76+
});
77+
}
78+
}
79+
80+
const aliasMatches = fileContent.match(ALIAS_RULE.regex) || [];
81+
for (const _match of aliasMatches) {
82+
violations.push({
83+
file: filePathFromRoot,
84+
type: ALIAS_RULE.rule,
85+
match: ALIAS_RULE.label
86+
});
87+
}
88+
89+
return violations;
90+
}
91+
92+
async function run() {
93+
const repoRoot = process.cwd();
94+
const filesToScan = [];
95+
96+
for (const root of SCAN_ROOTS) {
97+
const rootPath = path.join(repoRoot, root);
98+
if (!(await pathExists(rootPath))) continue;
99+
await collectSourceFiles(rootPath, filesToScan);
100+
}
101+
102+
const violations = [];
103+
for (const filePath of filesToScan) {
104+
const content = await fs.readFile(filePath, "utf8");
105+
const relPath = path.relative(repoRoot, filePath).replaceAll("\\", "/");
106+
violations.push(...findViolations(content, relPath));
107+
}
108+
109+
if (violations.length === 0) {
110+
console.log("Shared extraction guard passed. No violations found.");
111+
process.exit(0);
112+
}
113+
114+
console.error(`Shared extraction guard failed with ${violations.length} violation(s).`);
115+
for (const violation of violations) {
116+
console.error(
117+
`${violation.file} | ${violation.type} | ${violation.match}`
118+
);
119+
}
120+
process.exit(1);
121+
}
122+
123+
run().catch((error) => {
124+
console.error("Shared extraction guard encountered an unexpected error.");
125+
console.error(error && error.stack ? error.stack : String(error));
126+
process.exit(1);
127+
});

tools/shared/powerShell/find_dupes_called.ps1

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,21 @@
77
# .\find_dupes_called.ps1 | Out-File -FilePath "found_dupes_called.txt" -Encoding utf8
88

99
# Goes up 3 levels to reach HTML-JavaScript-Gaming from tools\shared\powerShell\
10-
Get-ChildItem -Path "$PSScriptRoot\..\..\..\" -Recurse -Filter *.js |
10+
# Get the root path (3 levels up)
11+
$rootPath = Resolve-Path "$PSScriptRoot\..\..\..\"
12+
13+
Get-ChildItem -Path $rootPath -Recurse -Filter *.js |
1114
Select-String -Pattern "function\s+[a-zA-Z0-9_]*\(", "[a-zA-Z0-9_]*\s*=\s*function", "[a-zA-Z0-9_]*:\s*function" |
1215
Group-Object Line |
1316
Where-Object { $_.Count -gt 1 } |
1417
ForEach-Object {
15-
# Print the duplicate line and the count
1618
"($($_.Count)) Duplicate line: $($_.Name.Trim())"
1719

18-
# Loop through each occurrence in the group to show where it is
1920
$_.Group | ForEach-Object {
20-
" -> Line $($_.LineNumber): $($_.Path)"
21+
# Replace the absolute root path with a single backslash
22+
$relativePath = $_.Path -replace [regex]::Escape($rootPath), ""
23+
" -> Line $($_.LineNumber): \$relativePath"
2124
}
22-
"" # Adds a blank line for better readability
25+
""
2326
}
27+
77.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)