diff --git a/ANTIGRAVITY.md b/ANTIGRAVITY.md index 1c201ad3a9..ba1c7594af 100644 --- a/ANTIGRAVITY.md +++ b/ANTIGRAVITY.md @@ -70,10 +70,29 @@ GitGalaxy scans itself and outputs intelligence to `/docs/gitgalaxy_architecture - Fastest path in an active session: `python tests/tools/self_scan.py` regenerates it in place. - If you'd rather not run a local scan, the `gitgalaxy.yml` workflow's "Full Report" job now publishes a fresh copy as a `gitgalaxy-self-scan-db` build artifact on every merge to main -- pull the latest one from that workflow's most recent run instead. -## 7. Submitting Pull Requests +## 7. Extraction Hardening & Adversarial Testing + +When tasked with "hardening extraction coverage" or similar testing epics, you must avoid **Self-Consistency Bias** (writing tests that merely pass against the *current implementation* instead of the *actual ground truth*). + +To ensure rigorous, adversarial testing: +1. **Define "Valid" from Ground Truth First**: Before looking at the current regex implementation, determine what syntax is valid by consulting official language documentation, a ground-truth corpus, or other established rules in the same file (e.g. cross-referencing `branch`, `safety`, and `state_mutation` rules to find missing opcodes). +2. **Do Not Cement Implementation Flaws**: If a structurally valid syntax does not match the current regex, the regex is buggy, NOT the syntax. Never write a negative/invalid test to "prove" the regex correctly ignores it. +3. **Cross-Rule Validation**: Actively check sibling rules in `language_standards.py`. If `args` extracts registers, make sure it covers the instructions listed in `state_mutation` that mutate those registers. +4. **Corpus Grepping**: When in doubt about whether a syntax exists or how common it is, `grep` the actual corpus (e.g. in `language-crucible`) to find empirical evidence of the shape and frequency of certain keywords or patterns. +5. **Multi-Round Adversarial Checks**: + - *Round 1 (Breadth)*: Gather keywords, opcodes, and structures from external docs and sibling rules. + - *Round 2 (Regex)*: Check the regex against this gathered ground truth. Identify missing captures and false positives. + - *Round 3 (Corpus)*: Verify fixes against real corpus data using `crucible_check.py` to ensure the modifications match reality. + +## 8. Submitting Pull Requests When working in this repository, **always work on a side branch and submit a PR to `main`. Do not merge or push directly to `main`.** +Before submitting a Pull Request, you must run the following deterministic tools in your belt to test out and fix your changes, acting as your local CI pipeline: +1. **Unit Tests:** `venv/bin/python -m pytest tests/` +2. **Lint & Types (Ruff, Mypy):** `PATH="$PWD/venv/bin:$PATH" python tests/tools/audit_check.py` (run with `--regenerate` or run `ruff format .` if auto-fixes are needed). +3. **Golden Fixtures (Crucible):** `venv/bin/python tests/tools/crucible_check.py` (run with `--update --yes` if the output intentionally changed due to better parsing rules). + When generating or submitting a Pull Request for this repository, it is critical to provide comprehensive context for reviewers. -- **Always add a thorough description:** Outline the changes, the rationale, and any structural boundaries or tests added. Do not leave the PR body blank or sparse. +- **Always add a thorough description:** Outline the changes, the rationale, and any structural boundaries or tests added. Explain *why* any golden masters changed. Do not leave the PR body blank or sparse. - **Add relevant labels:** Ensure the PR has descriptive labels attached so it integrates correctly into the project's tracking and CI processes. diff --git a/gitgalaxy/core/guidestar_lens.py b/gitgalaxy/core/guidestar_lens.py index 94d7ac11c6..e41b3b6b37 100644 --- a/gitgalaxy/core/guidestar_lens.py +++ b/gitgalaxy/core/guidestar_lens.py @@ -274,9 +274,9 @@ def _parse_package_json(self, path: Path): scripts = data.get("scripts", {}) for name, cmd in scripts.items(): files = re.findall(r"([a-zA-Z0-9_\-\./]+\.(?:js|ts|mjs|cjs))", cmd) - for f in files: + for file_path in files: self._inject_intent_lock( - f, "javascript", 0.85, f"Manifest Script (package.json:scripts:{name})" + file_path, "javascript", 0.85, f"Manifest Script (package.json:scripts:{name})" ) except Exception as e: self.logger.debug(f"GuideStar: failed to parse manifest '{path}': {e}") @@ -291,9 +291,9 @@ def _parse_makefile(self, path: Path): matches = re.findall(r"(?:SRCS|SOURCES|FILES|TARGET)\s*[+:]?=\s*(.*)", content, re.I) for m in matches: files = m.split() - for f in files: - if "." in f: - self._inject_intent_lock(f, "unknown", 0.85, "Manifest Source (Makefile)") + for file_path in files: + if "." in file_path: + self._inject_intent_lock(file_path, "unknown", 0.85, "Manifest Source (Makefile)") # Strategy 2: Find target lines like 'build: main.o' targets = re.findall(r"^([a-zA-Z0-9_\-]+)\s*:", content, re.M) diff --git a/gitgalaxy/standards/language_standards.py b/gitgalaxy/standards/language_standards.py index 0ff20ef188..0ae02655c2 100644 --- a/gitgalaxy/standards/language_standards.py +++ b/gitgalaxy/standards/language_standards.py @@ -6439,7 +6439,7 @@ class PrismConfigSchema(TypedDict): # 2. args (Parameters / Coupling) # Assembly uses ABI registers for parameter coupling. "args": re.compile( - r"\b([er]di|[er]si|[er]dx|[er]cx|[er][89]|x[0-7]|w[0-7]|v[0-7]|xmm[0-7])\b", + r"\b([er]di|[er]si|[er]dx|[er]cx|[er][89]|x[0-7]|w[0-7]|v[0-7]|xmm[0-7]|r[0-7])\b", re.I, ), # 3. linear (Sequential Boundaries) @@ -6451,12 +6451,15 @@ class PrismConfigSchema(TypedDict): # 4. func_start (Executable Logic Anchors) # Subroutine entry points. EXCLUDES data labels or local loop markers. "func_start": re.compile( - r"^[ \t]*(?!\.L|\.LC|\d|\.text|\.data|\.bss)([a-zA-Z_][a-zA-Z0-9_.$]*)(?=[ \t]*:)", + r"^[ \t]*(?!\.L|\.LC|\d|\.text|\.data|\.bss)([a-zA-Z_?@.][a-zA-Z0-9_.$?@]*)(?=[ \t]*:)", re.M, ), # 5. class_start (Object / Entity Declarations) # Maps to assembler structure definition macros. - "class_start": re.compile(r"^[ \t]*(?:struc|STRUCT|\.struct)\s+[a-zA-Z_]\w*", re.M | re.I), + "class_start": re.compile( + r"^[ \t]*(?:(?:struc|STRUCT|\.struct)\s+[a-zA-Z_?@.][a-zA-Z0-9_.$?@]*|[a-zA-Z_?@.][a-zA-Z0-9_.$?@]*\s+(?:struc|STRUCT|\.struct))\b", + re.M | re.I, + ), # --- PHASE 2: RISK & STRUCTURAL INTEGRITY --- # 6. safety (Defensive Programming / Validation) # Stack preservation and defensive frame setups. @@ -6529,9 +6532,9 @@ class PrismConfigSchema(TypedDict): # Complex SIB addressing and block replication. "reflection_metaprogramming": re.compile(r"\[\s*[a-zA-Z0-9_]+\s*\+\s*[a-zA-Z0-9_]+\s*\*\s*\d+", re.I), # 24. import (Dependency Inclusions) - "import": re.compile(r"^[ \t]*(?:%include|\.include|\.incbin)\b", re.M | re.I), + "import": re.compile(r"^[ \t]*(?:%include|\.include|\.incbin|INCLUDE|INCLUDELIB)\b", re.M | re.I), "_dependency_capture": re.compile( - r"^[ \t]*(?:%include|\.include|\.incbin)\s+(?:['\"]([^'\"]+)['\"]|([^'\"\s]+))", + r"^[ \t]*(?:%include|\.include|\.incbin|INCLUDE|INCLUDELIB)\s+(?:['\"]([^'\"]+)['\"]|([^'\"\s]+))", re.M | re.I, ), # 25. ownership (Authorship Metadata) diff --git a/scratch.py b/scratch.py new file mode 100644 index 0000000000..0426f29cb1 --- /dev/null +++ b/scratch.py @@ -0,0 +1,21 @@ +import json +import subprocess + + +def get_json(rev): + out = subprocess.check_output(["git", "show", f"{rev}:tests/golden_master_zero_dep_audit.json"]) + return json.loads(out) + + +old_data = get_json("HEAD~1") +new_data = get_json("HEAD") + +files = new_data["6. Parsed Files (Scanned Artifacts)"]["assembly/bootos"]["Files"] +for path, new_f in files.items(): + old_f = old_data["6. Parsed Files (Scanned Artifacts)"]["assembly/bootos"]["Files"][path] + print(f"\n{path} changes:") + old_sigs = old_f.get("7. Structural Signatures (Net Mitigated Signals)", {}) + new_sigs = new_f.get("7. Structural Signatures (Net Mitigated Signals)", {}) + for k, v in new_sigs.items(): + if old_sigs.get(k) != v: + print(f" {k}: {old_sigs.get(k)} -> {v}") diff --git a/tests/core_engine/test_prism.py b/tests/core_engine/test_prism.py index 9c7ddf2a39..622add2348 100644 --- a/tests/core_engine/test_prism.py +++ b/tests/core_engine/test_prism.py @@ -237,9 +237,7 @@ def test_prism_format_and_xml_bypass(prism_engine): def test_prism_php_string_extraction(prism_engine): """Proves PHP Heredoc strings are stripped to the documentation stream.""" prism_engine.languages["php"] = {"lexical_family": "standard_block"} - prism_engine.PHP_HEREDOC_PATTERN = re.compile( - r"<<<[\'\"]?([a-zA-Z0-9_]+)[\'\"]?\n(?:.*\n)*?\s*\1;?", re.M - ) + prism_engine.PHP_HEREDOC_PATTERN = re.compile(r"<<<[\'\"]?([a-zA-Z0-9_]+)[\'\"]?\n(?:.*\n)*?\s*\1;?", re.M) content = "", + 'import "foo.s"', + ], + "pathological": [ + ('%include \t\t "macros.inc"', "macros.inc"), + (".include\t\t\t'defs.s'", "defs.s"), + ("INCLUDE \t\t macros.inc", "macros.inc"), + ("INCLUDELIB \t\t kernel32.lib", "kernel32.lib"), + ('\t\t%include\t\t"macros.inc"\t\t', "macros.inc"), + ], +} + + +@pytest.mark.parametrize("payload,expected_name", DEPENDENCY_CASES["valid"]) +def test_assembly_dependency_valid(payload, expected_name): + assert_valid_dependency_match(ASSEMBLY_RULES["_dependency_capture"], payload, expected_name, "assembly.dependency") + + +@pytest.mark.parametrize("payload", DEPENDENCY_CASES["invalid"]) +def test_assembly_dependency_invalid(payload): + assert_invalid_no_match(ASSEMBLY_RULES["_dependency_capture"], payload, "assembly.dependency") + + +@pytest.mark.parametrize("payload,expected_name", DEPENDENCY_CASES["pathological"]) +def test_assembly_dependency_pathological(payload, expected_name): + assert_pathological_dependency_match( + ASSEMBLY_RULES["_dependency_capture"], payload, expected_name, "assembly.dependency" + ) + + +def test_assembly_dependency_redos_immunity(): + assert_redos_immune(ASSEMBLY_RULES["_dependency_capture"], "%include" + " \t" * 50000 + '"A"', timeout_sec=1.0) diff --git a/tests/extraction/languages/test_cobol.py b/tests/extraction/languages/test_cobol.py index 0a25a89f92..465df5ab9f 100644 --- a/tests/extraction/languages/test_cobol.py +++ b/tests/extraction/languages/test_cobol.py @@ -337,7 +337,9 @@ def test_cobol_args_redos_immunity(): @pytest.mark.parametrize("payload,expected_path", DEPENDENCY_CASES["valid"]) def test_cobol_dependency_capture_valid(payload, expected_path): - assert_valid_dependency_match(COBOL_RULES["_dependency_capture"], payload, expected_path, "cobol._dependency_capture") + assert_valid_dependency_match( + COBOL_RULES["_dependency_capture"], payload, expected_path, "cobol._dependency_capture" + ) @pytest.mark.parametrize("payload", DEPENDENCY_CASES["invalid"]) diff --git a/tests/extraction/languages/test_powershell.py b/tests/extraction/languages/test_powershell.py index b20bffb5c7..c1463a257d 100644 --- a/tests/extraction/languages/test_powershell.py +++ b/tests/extraction/languages/test_powershell.py @@ -52,7 +52,10 @@ # Syntax-era / feature coverage ("workflow TargetFunc {", "TargetFunc"), # PS Workflow (Windows PowerShell era) ("[int] TargetFunc() {", "TargetFunc"), # PS class typed method - ("[Dictionary[string,int]] TargetFunc() {", "TargetFunc"), # nested generic return type -- was a real bug, now fixed + ( + "[Dictionary[string,int]] TargetFunc() {", + "TargetFunc", + ), # nested generic return type -- was a real bug, now fixed ("TargetFunc([string]$name) {", "TargetFunc"), # PS class constructor -- was a real bug, now fixed ("TargetFunc() {", "TargetFunc"), # parameterless constructor -- was a real bug, now fixed ("function global:TargetFunc {", "TargetFunc"), # scope-qualified -- was a real bug, now fixed diff --git a/tests/extraction/languages/test_scala.py b/tests/extraction/languages/test_scala.py index 6b1502360c..b9fafc5a46 100644 --- a/tests/extraction/languages/test_scala.py +++ b/tests/extraction/languages/test_scala.py @@ -238,7 +238,9 @@ def test_scala_args_nested_generic_bound_regression(): java/typescript/python/rust/csharp/kotlin/swift). """ args = SCALA_RULES["args"] - assert args.search("def TargetFunc[T <: Comparable[T]](x: T): T = {"), "nested generic bound args detection regressed" + assert args.search("def TargetFunc[T <: Comparable[T]](x: T): T = {"), ( + "nested generic bound args detection regressed" + ) def test_scala_args_backtick_identifier_regression(): @@ -361,7 +363,9 @@ def test_scala_class_start_generic_with_extends_no_rule11_exposure(): @pytest.mark.parametrize("payload,expected_path", DEPENDENCY_CASES["valid"]) def test_scala_dependency_capture_valid(payload, expected_path): - assert_valid_dependency_match(SCALA_RULES["_dependency_capture"], payload, expected_path, "scala._dependency_capture") + assert_valid_dependency_match( + SCALA_RULES["_dependency_capture"], payload, expected_path, "scala._dependency_capture" + ) @pytest.mark.parametrize("payload", DEPENDENCY_CASES["invalid"]) @@ -390,9 +394,7 @@ def test_scala_dependency_capture_multi_import_no_longer_bleeds_across_statement cannot bleed past an explicit brace block. """ dep = SCALA_RULES["_dependency_capture"] - realistic_file = ( - "import scala.util.Try\nimport scala.collection.mutable.{Map, Set}\n\ncase class Foo(x: Int)\n" - ) + realistic_file = "import scala.util.Try\nimport scala.collection.mutable.{Map, Set}\n\ncase class Foo(x: Int)\n" matches = list(dep.finditer(realistic_file)) captured = [m.group(m.lastindex) for m in matches] assert captured == ["scala.util.Try", "scala.collection.mutable.{Map, Set}"], ( diff --git a/tests/extraction/languages/test_shell.py b/tests/extraction/languages/test_shell.py index ac9bde999d..dc6cff774d 100644 --- a/tests/extraction/languages/test_shell.py +++ b/tests/extraction/languages/test_shell.py @@ -299,7 +299,9 @@ def test_shell_args_redos_immunity(): @pytest.mark.parametrize("payload,expected_path", DEPENDENCY_CASES["valid"]) def test_shell_dependency_capture_valid(payload, expected_path): - assert_valid_dependency_match(SHELL_RULES["_dependency_capture"], payload, expected_path, "shell._dependency_capture") + assert_valid_dependency_match( + SHELL_RULES["_dependency_capture"], payload, expected_path, "shell._dependency_capture" + ) @pytest.mark.parametrize("payload", DEPENDENCY_CASES["invalid"]) diff --git a/tests/extraction/languages/test_sqlite.py b/tests/extraction/languages/test_sqlite.py index 95a2462263..4447e29ff3 100644 --- a/tests/extraction/languages/test_sqlite.py +++ b/tests/extraction/languages/test_sqlite.py @@ -55,8 +55,14 @@ "my_trigger", ), # schema-qualified -- was a real bug, now fixed ("CREATE INDEX main.idx_email ON users(email);", "idx_email"), # schema-qualified index - ('CREATE VIEW "group" AS SELECT 1;', "group"), # double-quoted (reserved-word name) -- was a real bug, now fixed - ("CREATE INDEX [my index] ON users(email);", "my index"), # bracket-quoted with space -- was a real bug, now fixed + ( + 'CREATE VIEW "group" AS SELECT 1;', + "group", + ), # double-quoted (reserved-word name) -- was a real bug, now fixed + ( + "CREATE INDEX [my index] ON users(email);", + "my index", + ), # bracket-quoted with space -- was a real bug, now fixed ("CREATE TRIGGER `my_trigger` AFTER INSERT ON users BEGIN\nEND;", "my_trigger"), # backtick-quoted ], "invalid": [ @@ -348,7 +354,9 @@ def test_sqlite_args_redos_immunity(): @pytest.mark.parametrize("payload,expected_path", DEPENDENCY_CASES["valid"]) def test_sqlite_dependency_capture_valid(payload, expected_path): - assert_valid_dependency_match(SQLITE_RULES["_dependency_capture"], payload, expected_path, "sqlite._dependency_capture") + assert_valid_dependency_match( + SQLITE_RULES["_dependency_capture"], payload, expected_path, "sqlite._dependency_capture" + ) @pytest.mark.parametrize("payload", DEPENDENCY_CASES["invalid"]) diff --git a/tests/extraction/test_dependency_extraction_strict.py b/tests/extraction/test_dependency_extraction_strict.py index 59a39544a6..0e7ac794a0 100644 --- a/tests/extraction/test_dependency_extraction_strict.py +++ b/tests/extraction/test_dependency_extraction_strict.py @@ -67,14 +67,6 @@ "invalid": ["CHARACTER(LEN=10) :: INCLUDE_FILE"], "pathological": [("USE \n , \n INTRINSIC \n :: \n omp_lib", "omp_lib")], }, - "assembly": { - "valid": [ - ('%include "macros.inc"', "macros.inc"), - ('.include "defs.s"', "defs.s"), - ], - "invalid": ["include_flag db 1"], - "pathological": [('%include \n "syscalls.inc"', "syscalls.inc")], - }, "lua": { "valid": [("require 'math'", "math"), ('local ffi = require("ffi")', "ffi")], "invalid": ["local require_path = ''"], diff --git a/tests/extraction/test_function_extraction_strict.py b/tests/extraction/test_function_extraction_strict.py index f1110f5db6..ce458ef505 100644 --- a/tests/extraction/test_function_extraction_strict.py +++ b/tests/extraction/test_function_extraction_strict.py @@ -179,11 +179,6 @@ ], "pathological": [("( \n define \n ( \n TargetFunc \n x \n )", "TargetFunc")], }, - "assembly": { - "valid": [("TargetFunc:", "TargetFunc"), ("_TargetFunc:", "_TargetFunc")], - "invalid": ["jmp TargetFunc", "call TargetFunc", ".data:"], - "pathological": [("_TargetFunc \t :", "_TargetFunc")], - }, "dockerfile": { "valid": [ ("RUN apt-get update", "RUN"), @@ -197,60 +192,60 @@ "valid": [ ("function TargetFunc()", "TargetFunc"), ("export const TargetFunc = async () =>", "TargetFunc"), - ("public get TargetFunc()", "TargetFunc") + ("public get TargetFunc()", "TargetFunc"), ], "invalid": ["class TargetFunc", "type TargetFunc", "interface TargetFunc"], - "pathological": [("public \n async \n get \n TargetFunc \n (", "TargetFunc")] + "pathological": [("public \n async \n get \n TargetFunc \n (", "TargetFunc")], }, "zig": { "valid": [("fn TargetFunc()", "TargetFunc"), ("pub fn TargetFunc()", "TargetFunc")], "invalid": ["const TargetFunc = struct", "var TargetFunc"], - "pathological": [("pub \n export \n fn \n TargetFunc \n (", "TargetFunc")] + "pathological": [("pub \n export \n fn \n TargetFunc \n (", "TargetFunc")], }, "solidity": { "valid": [("function TargetFunc()", "TargetFunc"), ("modifier TargetFunc()", "TargetFunc")], "invalid": ["contract TargetFunc", "struct TargetFunc"], - "pathological": [("function \n TargetFunc \n (", "TargetFunc")] + "pathological": [("function \n TargetFunc \n (", "TargetFunc")], }, "groovy": { "valid": [("def TargetFunc()", "TargetFunc"), ("public void TargetFunc()", "TargetFunc")], "invalid": ["class TargetFunc", "if (TargetFunc)"], - "pathological": [("public \t static \t def \t TargetFunc \t (", "TargetFunc")] + "pathological": [("public \t static \t def \t TargetFunc \t (", "TargetFunc")], }, "jcl": { "valid": [("//TargetFunc EXEC PGM=PROG", "TargetFunc")], "invalid": ["//TargetFunc DD DSN=", "//* TargetFunc EXEC"], - "pathological": [("//TargetFunc \t EXEC ", "TargetFunc")] + "pathological": [("//TargetFunc \t EXEC ", "TargetFunc")], }, "m4": { "valid": [("m4_define(`TargetFunc',", "m4_define"), ("AC_DEFUN([TargetFunc],", "AC_DEFUN")], "invalid": ["TargetFunc()", "define TargetFunc"], - "pathological": [("m4_define \n (`TargetFunc',", "m4_define")] + "pathological": [("m4_define \n (`TargetFunc',", "m4_define")], }, "yacc": { "valid": [("TargetFunc:", "TargetFunc")], "invalid": ["%token TargetFunc", "case TargetFunc:"], - "pathological": [("TargetFunc \t :", "TargetFunc")] + "pathological": [("TargetFunc \t :", "TargetFunc")], }, "css": { "valid": [("@media (max-width: 600px) {", "@media"), ("@keyframes TargetFunc {", "@keyframes")], "invalid": [".TargetFunc {", "#TargetFunc {"], - "pathological": [("@media \n (max-width) \n {", "@media")] + "pathological": [("@media \n (max-width) \n {", "@media")], }, "html": { "valid": [("