xrefkit-skills-batch-regression 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. xrefkit_skills_batch_regression-0.4.1/PKG-INFO +27 -0
  2. xrefkit_skills_batch_regression-0.4.1/README.md +19 -0
  3. xrefkit_skills_batch_regression-0.4.1/pyproject.toml +29 -0
  4. xrefkit_skills_batch_regression-0.4.1/setup.cfg +4 -0
  5. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/__init__.py +11 -0
  6. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/cli.py +14 -0
  7. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/package_manifest.yaml +42 -0
  8. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/entry.md +24 -0
  9. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/fixtures/config.json +6 -0
  10. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/fixtures/new-results.json +1 -0
  11. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/fixtures/old-results.json +1 -0
  12. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/adapter-contract.md +25 -0
  13. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/code-table-extraction.md +23 -0
  14. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/config-schema.md +25 -0
  15. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/config.template.json +31 -0
  16. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/csharp-sql-analysis.md +17 -0
  17. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/fixture-notes.md +8 -0
  18. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/references/workflow.md +44 -0
  19. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/scripts/batch_regression.py +152 -0
  20. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skill_assets/scripts/code_tables.py +77 -0
  21. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression/skills/batch_impact_regression.skill.yaml +44 -0
  22. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/PKG-INFO +27 -0
  23. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/SOURCES.txt +25 -0
  24. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/dependency_links.txt +1 -0
  25. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/entry_points.txt +5 -0
  26. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/requires.txt +1 -0
  27. xrefkit_skills_batch_regression-0.4.1/src/xrefkit_skills_batch_regression.egg-info/top_level.txt +1 -0
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: xrefkit-skills-batch-regression
3
+ Version: 0.4.1
4
+ Summary: XRefKit Skill Package for C# and SQL Server batch impact analysis and combination regression
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: xrefkit<0.5.0,>=0.4.0
8
+
9
+ # xrefkit-skills-batch-regression
10
+
11
+ XRefKit Skill Package v0.4.1 for impact analysis and combination regression of
12
+ existing C# batches backed by SQL Server stored procedures.
13
+
14
+ The package provides a discoverable XRefKit v2 Skill Package and bundles the
15
+ repository-native Skill procedure, deterministic combination/result tools,
16
+ source condition extraction, configuration references, and synthetic fixtures.
17
+ Real batch and database execution still requires an isolated adapter supplied
18
+ by the consuming project.
19
+
20
+ Install:
21
+
22
+ ```powershell
23
+ python -m pip install xrefkit-skills-batch-regression==0.4.1
24
+ ```
25
+
26
+ The package is discovered through the `xrefkit.skill_packages` entry-point
27
+ group and includes the deterministic tools under `skill_assets/scripts/`.
@@ -0,0 +1,19 @@
1
+ # xrefkit-skills-batch-regression
2
+
3
+ XRefKit Skill Package v0.4.1 for impact analysis and combination regression of
4
+ existing C# batches backed by SQL Server stored procedures.
5
+
6
+ The package provides a discoverable XRefKit v2 Skill Package and bundles the
7
+ repository-native Skill procedure, deterministic combination/result tools,
8
+ source condition extraction, configuration references, and synthetic fixtures.
9
+ Real batch and database execution still requires an isolated adapter supplied
10
+ by the consuming project.
11
+
12
+ Install:
13
+
14
+ ```powershell
15
+ python -m pip install xrefkit-skills-batch-regression==0.4.1
16
+ ```
17
+
18
+ The package is discovered through the `xrefkit.skill_packages` entry-point
19
+ group and includes the deterministic tools under `skill_assets/scripts/`.
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xrefkit-skills-batch-regression"
7
+ version = "0.4.1"
8
+ description = "XRefKit Skill Package for C# and SQL Server batch impact analysis and combination regression"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "xrefkit>=0.4.0,<0.5.0",
13
+ ]
14
+
15
+ [project.entry-points."xrefkit.skill_packages"]
16
+ batch_regression = "xrefkit_skills_batch_regression:package_root"
17
+
18
+ [project.scripts]
19
+ xrefkit-batch-regression = "xrefkit_skills_batch_regression.cli:main"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
23
+
24
+ [tool.setuptools.package-data]
25
+ "xrefkit_skills_batch_regression" = [
26
+ "package_manifest.yaml",
27
+ "skills/*.yaml",
28
+ "skill_assets/**/*",
29
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """XRefKit Skill Package for batch impact and combination regression."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def package_root() -> Path:
9
+ """Return the installed package root containing package_manifest.yaml."""
10
+
11
+ return Path(__file__).parent
@@ -0,0 +1,14 @@
1
+ """Convenience entry point for the bundled deterministic batch tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def main() -> int:
10
+ scripts = Path(__file__).parent / "skill_assets" / "scripts"
11
+ sys.path.insert(0, str(scripts))
12
+ from batch_regression import main as run # type: ignore
13
+
14
+ return run()
@@ -0,0 +1,42 @@
1
+ package_id: xrefkit.skills.batch_regression
2
+ package_type: skill_package
3
+ version: 0.4.1
4
+
5
+ requires:
6
+ xrefkit_core: ">=2.0.0 <3.0.0"
7
+
8
+ provides:
9
+ skills:
10
+ - id: batch.impact_regression
11
+ xid: xid-skill-batch-impact-regression
12
+ path: skills/batch_impact_regression.skill.yaml
13
+ contract_role: base_skill
14
+ required_outputs:
15
+ - decision_table
16
+ - orthogonal_table
17
+ - regression_report
18
+ - evidence
19
+ - unknowns
20
+ - human_handoffs
21
+ required_knowledge: []
22
+ required_review_axes: []
23
+ must_not:
24
+ - treat_source_conditions_as_business_truth
25
+ - treat_baseline_results_as_business_correctness
26
+ - execute_against_production
27
+ - auto_accept_unexplained_differences
28
+ - claim_dynamic_sql_is_resolved_without_runtime_evidence
29
+
30
+ fragments: []
31
+ knowledge: []
32
+ review_axes: []
33
+ schemas: []
34
+ templates: []
35
+
36
+ contract:
37
+ can_be_extended_by_local_skill: true
38
+ local_skill_can_weaken_contract: false
39
+ local_can_weaken_required_outputs: false
40
+ local_can_override_must_not: false
41
+ requires_unknown_declaration: true
42
+ requires_traceability: true
@@ -0,0 +1,24 @@
1
+ # Batch Impact Regression
2
+
3
+ Analyze existing C# batches and SQL Server stored-procedure execution paths
4
+ when a requirement changes behavior and the combination space is large.
5
+
6
+ Follow the bundled 15-step workflow in `references/workflow.md`. Inspect C#
7
+ and SQL as one execution path, keep business constraints separate from source
8
+ conditions, and stop when dynamic dispatch, DB side effects, or expected
9
+ differences lack evidence.
10
+
11
+ Use the deterministic tools under `scripts/`:
12
+
13
+ ```powershell
14
+ python scripts/batch_regression.py extract-tables <source-root> -o tables.json
15
+ python scripts/batch_regression.py report <config.json> <old.json> <new.json> -o report.json
16
+ ```
17
+
18
+ The first command creates source-backed decision-table evidence, inferred
19
+ factors, and a strength-2 pairwise covering-table candidate. The second
20
+ compares old/new adapter records. Neither command connects to a real database;
21
+ use an isolated adapter conforming to `references/adapter-contract.md`.
22
+
23
+ The human remains responsible for business validity, baseline correctness,
24
+ planned differences, unexplained differences, and release disposition.
@@ -0,0 +1,6 @@
1
+ {
2
+ "combination": {"elements": [{"name":"a","values":[1,2]}, {"name":"b","values":["x","y"]}], "constraints": [{"id":"fixture-ban","kind":"forbidden","when":{"all":[{"field":"a","op":"eq","value":2},{"field":"b","op":"eq","value":"y"}]},"reason":"fixture-only constraint","evidence":["fixtures/config.json"]}]},
3
+ "comparison": {"key_fields":["a","b"],"fields":["status","result","updated_rows"],"ignore_fields":["execution_id"],"normalize_fields":{}},
4
+ "planned_differences": [{"id":"fixture-change","when":{"all":[{"field":"a","op":"eq","value":1},{"field":"b","op":"eq","value":"y"}]},"fields":["result"],"relation":"fixture-only planned difference"}],
5
+ "regression_set":{"max_size":10,"seed":7}
6
+ }
@@ -0,0 +1 @@
1
+ [{"input":{"a":1,"b":"x"},"status":"success","result":10,"updated_rows":1,"execution_id":"new-1"},{"input":{"a":1,"b":"y"},"status":"success","result":11,"updated_rows":1,"execution_id":"new-2"},{"input":{"a":2,"b":"x"},"status":"success","result":99,"updated_rows":1,"execution_id":"new-3"}]
@@ -0,0 +1 @@
1
+ [{"input":{"a":1,"b":"x"},"status":"success","result":10,"updated_rows":1,"execution_id":"old-1"},{"input":{"a":1,"b":"y"},"status":"success","result":10,"updated_rows":1,"execution_id":"old-2"},{"input":{"a":2,"b":"x"},"status":"business_error","result":null,"updated_rows":0,"execution_id":"old-3"}]
@@ -0,0 +1,25 @@
1
+ <!-- xid: F4818190FD09 -->
2
+ <a id="xid-F4818190FD09"></a>
3
+
4
+ # Adapter boundary
5
+
6
+ The Skill does not claim to connect to a real batch or database. An adapter
7
+ must produce versioned JSON records with:
8
+
9
+ ```json
10
+ {
11
+ "input": {"dimension": "value"},
12
+ "status": "success|business_error|system_error|not_executed",
13
+ "result": {"field": "value"},
14
+ "error": {"code": "...", "message": "..."},
15
+ "updated_rows": 0,
16
+ "path_refs": ["path-id"],
17
+ "evidence_refs": ["file-or-log"]
18
+ }
19
+ ```
20
+
21
+ An implementation may wrap `dotnet`, a test harness, or SQL Server tooling,
22
+ but it must enforce the configured timeout, isolate the DB, capture exit code,
23
+ stdout/stderr, result sets, return values, OUTPUT values, exceptions, and side
24
+ effect checks. The deterministic scripts consume artifacts; they do not invent
25
+ adapter output.
@@ -0,0 +1,23 @@
1
+ <!-- xid: 8E50930676E2 -->
2
+ <a id="xid-8E50930676E2"></a>
3
+
4
+ # Code table extraction
5
+
6
+ Run:
7
+
8
+ ```powershell
9
+ python scripts/batch_regression.py extract-tables <source-root> -o <report.json>
10
+ ```
11
+
12
+ The scanner reads `.cs` and `.sql` files and emits:
13
+
14
+ - `decision_table`: each statically recognized `if`, `when`, or `case` condition with source path and line
15
+ - `factors`: fields and literal comparison values with evidence references
16
+ - `orthogonal_table`: deterministic strength-2 pairwise covering rows
17
+ - `uncertainties`: expressions that were found but not resolved statically
18
+
19
+ The output is evidence for review. It does not prove that a condition is an
20
+ 業務ルール, does not infer branch outcomes, and does not replace compilation,
21
+ Roslyn analysis, SQL Server parsing, or human confirmation. Dynamic SQL,
22
+ reflection, generated code, macros, configuration-driven rules, and complex
23
+ multi-line expressions may remain unresolved.
@@ -0,0 +1,25 @@
1
+ <!-- xid: A0B8B60D3325 -->
2
+ <a id="xid-A0B8B60D3325"></a>
3
+
4
+ # Configuration schema
5
+
6
+ The JSON template is intentionally declarative. Predicates use `all` or
7
+ `any` clauses with `eq`, `neq`, `in`, `not_in`, `exists`, and `missing`.
8
+ `forbidden` excludes a candidate; `business_invalid`, `upstream_absent`, and
9
+ `uncertain` classify it without pretending to prove the business rule.
10
+
11
+ Top-level fields:
12
+
13
+ - `system`: `solution`, `project`, `command`, `test_db`, `old_version`, `new_version`, `timeout_seconds`, `parallelism`, `side_effect_check`
14
+ - `combination.elements`: ordered `{name, values}` dimensions
15
+ - `combination.constraints`: `{id, kind, when, reason, evidence}`
16
+ - `comparison`: `key_fields`, `fields`, `ignore_fields`, `normalize_fields`
17
+ - `planned_differences`: `{id, when, fields, relation, evidence}`
18
+ - `paths`: `{id, description, csharp, stored_procedures, child_stored_procedures, dynamic_sql, dynamic_sp_name}` for trace links
19
+ - `regression_set`: `max_size`, `seed`
20
+
21
+ Source-table extraction is invoked separately with the source root; it does not
22
+ require a comparison result file or a database adapter.
23
+
24
+ Constraint and expectation predicates are data, not Python or SQL. No example
25
+ in this Skill claims an actual business rule; template values are placeholders.
@@ -0,0 +1,31 @@
1
+ {
2
+ "system": {
3
+ "solution": "path/to/app.sln",
4
+ "project": "path/to/batch.csproj",
5
+ "command": "dotnet run --project path/to/batch.csproj -- ...",
6
+ "test_db": "isolated-test-db-connection-placeholder",
7
+ "old_version": "old-build-or-commit",
8
+ "new_version": "new-build-or-commit",
9
+ "timeout_seconds": 60,
10
+ "parallelism": 1,
11
+ "side_effect_check": "before-after row counts and changed keys; human-approved rollback/restore"
12
+ },
13
+ "combination": {
14
+ "elements": [
15
+ {"name": "dimension_a", "values": ["value_a1", "value_a2"]},
16
+ {"name": "dimension_b", "values": ["value_b1", "value_b2"]}
17
+ ],
18
+ "constraints": [
19
+ {"id": "constraint-placeholder", "kind": "forbidden", "when": {"all": [{"field": "dimension_a", "op": "eq", "value": "value_a1"}, {"field": "dimension_b", "op": "eq", "value": "value_b2"}]}, "reason": "REPLACE_WITH_EVIDENCED_RULE", "evidence": ["human-approved-source"]}
20
+ ]
21
+ },
22
+ "comparison": {
23
+ "key_fields": ["dimension_a", "dimension_b"],
24
+ "fields": ["status", "result", "updated_rows"],
25
+ "ignore_fields": ["execution_id", "started_at", "finished_at"],
26
+ "normalize_fields": {"started_at": "datetime", "finished_at": "datetime"}
27
+ },
28
+ "planned_differences": [],
29
+ "paths": [],
30
+ "regression_set": {"max_size": 20, "seed": 7}
31
+ }
@@ -0,0 +1,17 @@
1
+ <!-- xid: 9C03927FF35B -->
2
+ <a id="xid-9C03927FF35B"></a>
3
+
4
+ # C# and SQL Server analysis checklist
5
+
6
+ Analyze one execution path across both layers. Record evidence locations for:
7
+
8
+ - C# schedule/CLI entry, command construction, SP name resolution, parameter names/types/size/precision/scale, NULL/empty/default/date/decimal conversion, timeout, retries, cancellation, result-set mapping, return value, OUTPUT parameters, and exceptions.
9
+ - SP definitions and child SPs, functions, views, tables, temp tables, table variables, dynamic SQL, dynamic object names, validation/calculation/update branches, row counts, transaction boundaries, isolation, locks, commits/rollbacks, triggers, and error propagation.
10
+ - Cross-boundary type, precision/scale, rounding, collation, date/time-zone, NULL, empty-string, default-value, and encoding behavior.
11
+
12
+ Do not conclude from C# alone or SQL alone. If dynamic SQL or dynamic SP
13
+ resolution cannot be closed statically, record the unresolved target and the
14
+ affected candidate count as `uncertain` until runtime evidence closes it.
15
+ Never run a write-capable batch against production. Prefer a cloned/snapshot
16
+ DB, isolated schema, disposable database, or transaction rollback, and prove
17
+ the side-effect check with before/after row counts and changed keys.
@@ -0,0 +1,8 @@
1
+ <!-- xid: B1EDF6CFC6BE -->
2
+ <a id="xid-B1EDF6CFC6BE"></a>
3
+
4
+ # Fixture boundary
5
+
6
+ The files under `fixtures/` contain synthetic values only. They demonstrate
7
+ the adapter record shape and classification mechanics; they are not business
8
+ constraints, production results, or evidence about any real batch.
@@ -0,0 +1,44 @@
1
+ <!-- xid: 427525B4935A -->
2
+ <a id="xid-427525B4935A"></a>
3
+
4
+ # Workflow and decision rules
5
+
6
+ ## 15-step sequence
7
+
8
+ 1. Confirm system boundary, executable command, versions, isolated DB, and adapter.
9
+ 2. Locate C# entry points and every SP invocation and parameter mapping.
10
+ 3. Trace child SPs, functions, views, tables, temp objects, table variables, dynamic SQL, transactions, outputs, result sets, and exceptions.
11
+ 4. Extract combination dimensions and values from evidence; mark missing values unknown.
12
+ 5. Record business, technical, and upstream constraints in separate ledgers.
13
+ 6. Compute Cartesian candidate count and post-constraint count deterministically.
14
+ 7. Prove safe execution with a small input and verify DB side effects.
15
+ 8. Decide full-run feasibility from measured duration, timeout, parallelism, and side effects.
16
+ 9. Capture the old version as observed baseline, never as business truth.
17
+ 10. Define the new requirement predicate and allowed differences with evidence and owner.
18
+ 11. Run old and new on identical serialized inputs through an isolated adapter.
19
+ 12. Normalize and classify each result; retain traceability for every difference.
20
+ 13. Select a deterministic daily reduced regression set covering dimensions, constraints, paths, and difference classes.
21
+ 14. Generate a release-time full-run procedure with rollback/restore and stop gates.
22
+ 15. Escalate unresolved decisions; do not close while material unknowns remain unowned.
23
+
24
+ ## Classification precedence
25
+
26
+ `system_error` > `uncertain` > `business_invalid` > `upstream_absent` >
27
+ result comparison. An explicitly configured `business_invalid` or
28
+ `upstream_absent` predicate is evidence for classification, not a substitute
29
+ for human approval. A missing predicate or unresolved dynamic path is
30
+ `uncertain`, not valid.
31
+
32
+ ## Result comparison
33
+
34
+ Compare configured fields after removing or normalizing only configured
35
+ non-deterministic fields. `planned_difference` requires a matching expected
36
+ difference rule. Any other changed value is `unexplained_difference`.
37
+ Unchanged successful outcomes are `baseline_match`. Missing execution records
38
+ are `not_executed`.
39
+
40
+ ## Human decisions
41
+
42
+ Present the candidate input, counts, evidence, and impact of each option for:
43
+ business validity, baseline defects, expected differences, upstream absence,
44
+ and release disposition. Do not fill these decisions by inference.
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env python3
2
+ """Deterministic combination and old/new batch-result analysis.
3
+
4
+ This module deliberately has no database or process adapter. Adapters emit the
5
+ JSON record contract in references/adapter-contract.md; this tool consumes it.
6
+ """
7
+ from __future__ import annotations
8
+ import argparse, csv, hashlib, itertools, json, random, sys
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from code_tables import extract_source_tables, pairwise_table
12
+
13
+ STATUSES = {"success", "business_error", "system_error", "not_executed"}
14
+
15
+ def predicate(value, spec):
16
+ if not spec: return False
17
+ clauses = spec.get("all", spec.get("any", []))
18
+ matched = [clause(value, c) for c in clauses]
19
+ return all(matched) if "all" in spec else any(matched)
20
+
21
+ def clause(row, c):
22
+ field, op, expected = c["field"], c["op"], c.get("value")
23
+ present = field in row and row[field] is not None
24
+ actual = row.get(field)
25
+ if op == "eq": return actual == expected
26
+ if op == "neq": return actual != expected
27
+ if op == "in": return actual in expected if isinstance(expected, (list, tuple, set)) else False
28
+ if op == "not_in": return actual not in expected if isinstance(expected, (list, tuple, set)) else False
29
+ if op == "exists": return present
30
+ if op == "missing": return not present
31
+ return False
32
+
33
+ def generate_candidates(config):
34
+ elements = config["combination"]["elements"]
35
+ names = [e["name"] for e in elements]
36
+ candidates = [dict(zip(names, values)) for values in itertools.product(*(e["values"] for e in elements))]
37
+ all_count = len(candidates)
38
+ kept, classifications = [], []
39
+ for row in candidates:
40
+ excluded = None
41
+ for c in config["combination"].get("constraints", []):
42
+ kind = c["kind"]
43
+ matched = predicate(row, c.get("when"))
44
+ missing_required = kind in ("required", "required_if") and matched and not row.get(c.get("field"))
45
+ if matched and (kind in ("forbidden", "business_invalid", "upstream_absent", "uncertain") or missing_required):
46
+ excluded = c
47
+ break
48
+ if excluded:
49
+ kind = excluded["kind"]
50
+ classification = {"forbidden": "business_invalid", "business_invalid": "business_invalid", "upstream_absent": "upstream_absent", "uncertain": "uncertain"}.get(kind, "business_invalid")
51
+ classifications.append({"input": row, "classification": classification, "constraint": excluded})
52
+ else:
53
+ kept.append(row)
54
+ classifications.append({"input": row, "classification": "candidate"})
55
+ return {"all_candidate_count": all_count, "post_constraint_count": len(kept), "candidates": kept, "classifications": classifications}
56
+
57
+ def analyze_paths(paths):
58
+ """Summarize static execution-path evidence without pretending to resolve dynamic dispatch."""
59
+ result = {"path_count": len(paths), "dual_layer_paths": 0, "child_sp_paths": 0, "dynamic_unknowns": []}
60
+ for path in paths:
61
+ if path.get("csharp") and path.get("stored_procedures"): result["dual_layer_paths"] += 1
62
+ if path.get("child_stored_procedures"): result["child_sp_paths"] += 1
63
+ if path.get("dynamic_sql") or path.get("dynamic_sp_name"):
64
+ result["dynamic_unknowns"].append({"path_id": path.get("id"), "reason": "dynamic SQL or stored-procedure target requires runtime evidence"})
65
+ return result
66
+
67
+ def normalize(record, config):
68
+ record = json.loads(json.dumps(record, ensure_ascii=False))
69
+ for field in config.get("comparison", {}).get("ignore_fields", []): record.pop(field, None)
70
+ for field, kind in config.get("comparison", {}).get("normalize_fields", {}).items():
71
+ if field not in record: continue
72
+ if kind == "datetime":
73
+ try: record[field] = datetime.fromisoformat(str(record[field]).replace("Z", "+00:00")).isoformat()
74
+ except ValueError: record[field] = "<invalid-datetime>"
75
+ elif kind == "number":
76
+ try: record[field] = float(record[field])
77
+ except (TypeError, ValueError): record[field] = "<invalid-number>"
78
+ return record
79
+
80
+ def compare(old_records, new_records, config):
81
+ key = lambda r: tuple(r.get("input", {}).get(k) for k in config["comparison"]["key_fields"])
82
+ old, new = {key(r): r for r in old_records}, {key(r): r for r in new_records}
83
+ expected = config.get("planned_differences", [])
84
+ rows = []
85
+ for k in sorted(set(old) | set(new), key=str):
86
+ o, n = old.get(k), new.get(k)
87
+ if not o or not n: cls = "not_executed"
88
+ elif n.get("status") == "system_error" or o.get("status") == "system_error": cls = "system_error"
89
+ elif n.get("status") == "not_executed": cls = "not_executed"
90
+ elif n.get("status") == "business_error" and o.get("status") != "business_error": cls = "business_invalid"
91
+ elif n.get("status") not in STATUSES or o.get("status") not in STATUSES: cls = "uncertain"
92
+ else:
93
+ on, nn = normalize(o, config), normalize(n, config)
94
+ fields = config["comparison"].get("fields", [])
95
+ changed = [f for f in fields if on.get(f) != nn.get(f)]
96
+ if not changed: cls = "baseline_match"
97
+ else:
98
+ hit = next((p for p in expected if predicate(n.get("input", {}), p.get("when")) and set(changed).issubset(set(p.get("fields", [])))), None)
99
+ cls = "planned_difference" if hit else "unexplained_difference"
100
+ rows.append({"input": (n or o).get("input", {}), "old": o, "new": n, "classification": cls,
101
+ "path_refs": sorted(set((o or {}).get("path_refs", []) + (n or {}).get("path_refs", []))),
102
+ "planned_relation": next((p.get("relation") for p in expected if predicate((n or o).get("input", {}), p.get("when"))), None)})
103
+ return rows
104
+
105
+ def summarize(candidate_report, comparisons, config):
106
+ counts = {k: 0 for k in ["baseline_match", "business_invalid", "system_error", "planned_difference", "unexplained_difference", "uncertain", "not_executed", "upstream_absent"]}
107
+ for row in candidate_report["classifications"]:
108
+ if row["classification"] == "business_invalid": counts["business_invalid"] += 1
109
+ for row in comparisons: counts[row["classification"]] = counts.get(row["classification"], 0) + 1
110
+ old_records = [row["old"] for row in comparisons if row.get("old")]
111
+ new_records = [row["new"] for row in comparisons if row.get("new")]
112
+ paths = {}
113
+ for row in comparisons:
114
+ for path in row.get("path_refs", []) or ["unattributed"]: paths[path] = paths.get(path, 0) + 1
115
+ return {**counts, "all_candidate_count": candidate_report["all_candidate_count"], "post_constraint_count": candidate_report["post_constraint_count"],
116
+ "current_normal_count": sum(r.get("status") == "success" for r in old_records),
117
+ "business_error_count": sum(r.get("status") == "business_error" for r in old_records),
118
+ "new_system_error_count": sum(r.get("status") == "system_error" for r in new_records),
119
+ "unexecuted_count": counts.get("not_executed", 0),
120
+ "processed_count": len(comparisons), "path_counts": paths, "representative_regression_set_count": len(select_regression_set(comparisons, config)),
121
+ "analysis": analyze_paths(config.get("paths", [])),
122
+ "human_judgments_required": ["business validity", "baseline defects", "planned differences", "unexplained differences", "release disposition"]}
123
+
124
+ def select_regression_set(rows, config):
125
+ limit, seed = config.get("regression_set", {}).get("max_size", 100), config.get("regression_set", {}).get("seed", 0)
126
+ by_class = {}
127
+ for row in rows: by_class.setdefault(row["classification"], []).append(row)
128
+ selected = []
129
+ for cls in sorted(by_class): selected.append(sorted(by_class[cls], key=lambda r: json.dumps(r["input"], sort_keys=True))[0])
130
+ rest = [r for r in rows if r not in selected]; random.Random(seed).shuffle(rest)
131
+ return (selected + rest)[:limit]
132
+
133
+ def load(path): return json.loads(Path(path).read_text(encoding="utf-8"))
134
+ def main(argv=None):
135
+ p = argparse.ArgumentParser(); sub = p.add_subparsers(dest="cmd", required=True)
136
+ g = sub.add_parser("generate"); g.add_argument("config"); g.add_argument("-o", "--output", required=True)
137
+ c = sub.add_parser("compare"); c.add_argument("config"); c.add_argument("old"); c.add_argument("new"); c.add_argument("-o", "--output", required=True)
138
+ r = sub.add_parser("report"); r.add_argument("config"); r.add_argument("old"); r.add_argument("new"); r.add_argument("-o", "--output", required=True)
139
+ t = sub.add_parser("extract-tables"); t.add_argument("root"); t.add_argument("-o", "--output", required=True)
140
+ args = p.parse_args(argv)
141
+ if args.cmd == "extract-tables":
142
+ out = extract_source_tables(args.root)
143
+ out["orthogonal_table"] = pairwise_table(out["factors"])
144
+ else:
145
+ config = load(args.config); candidates = generate_candidates(config)
146
+ if args.cmd == "generate": out = candidates
147
+ else:
148
+ comparisons = compare(load(args.old), load(args.new), config)
149
+ if args.cmd == "compare": out = comparisons
150
+ else: out = {"summary": summarize(candidates, comparisons, config), "comparisons": comparisons, "regression_set": select_regression_set(comparisons, config)}
151
+ Path(args.output).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8"); return 0
152
+ if __name__ == "__main__": sys.exit(main())
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """Conservative, evidence-preserving extraction of decision and pairwise tables.
3
+
4
+ This is a syntax scanner, not a C# or T-SQL compiler. Unresolved expressions
5
+ remain explicit in ``uncertainties`` and must not be promoted to business rules.
6
+ """
7
+ from __future__ import annotations
8
+ import itertools, re
9
+ from pathlib import Path
10
+
11
+ CONDITION_RE = re.compile(r"\b(if|when|case)\b(?P<expr>[^\r\n]*)", re.I)
12
+ COMPARISON_RE = re.compile(r"(?P<field>[\[\]A-Za-z_][\w.\[\]]*)\s*(?P<op>==|!=|>=|<=|>|<|=|\bIN\b|\bIS\b)\s*(?P<value>\([^\)]*\)|[^,;\s\)]+)", re.I)
13
+ STRING_RE = re.compile(r"'(?:''|[^'])*'|\"(?:\\.|[^\"])*\"")
14
+ IDENTIFIER_RE = re.compile(r"^[\[\]A-Za-z_][\w.\[\]]*$")
15
+ KEYWORDS = {"if", "when", "case", "then", "else", "and", "or", "not", "is", "null", "true", "false"}
16
+
17
+ def _clean(value):
18
+ value = value.strip().strip("[]")
19
+ return value
20
+
21
+ def _literal_values(value):
22
+ value = value.strip()
23
+ if value.startswith("(") and value.endswith(")"):
24
+ parts = value[1:-1].split(",")
25
+ return [_clean(p).strip("'\"") for p in parts if p.strip()]
26
+ if STRING_RE.fullmatch(value): return [value[1:-1].replace("''", "'").replace('\\"', '"')]
27
+ if re.fullmatch(r"-?\d+(?:\.\d+)?", value): return [value]
28
+ if value.upper() in {"NULL", "TRUE", "FALSE"}: return [value.upper()]
29
+ return []
30
+
31
+ def _language(path): return "csharp" if path.suffix.lower() == ".cs" else "sql"
32
+
33
+ def extract_source_tables(root):
34
+ root = Path(root)
35
+ rows, factors, uncertainties, files = [], {}, [], []
36
+ paths = sorted(p for p in root.rglob("*") if p.is_file() and p.suffix.lower() in {".cs", ".sql"})
37
+ for path in paths:
38
+ rel = str(path.relative_to(root)).replace("\\", "/")
39
+ files.append(rel)
40
+ for line_no, line in enumerate(path.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
41
+ for match in CONDITION_RE.finditer(line):
42
+ expr = match.group("expr").strip()
43
+ expr = re.sub(r"^\(?\s*(?:when|case)\b", "", expr, flags=re.I).strip()
44
+ expr = re.split(r"\bthen\b", expr, maxsplit=1, flags=re.I)[0].strip()
45
+ expr = expr.rstrip("{").strip().rstrip(")").strip()
46
+ comparisons = []
47
+ for comp in COMPARISON_RE.finditer(expr):
48
+ field, op, raw = _clean(comp.group("field")), comp.group("op").upper(), comp.group("value")
49
+ if field.lower() in KEYWORDS: continue
50
+ values = _literal_values(raw)
51
+ if not values:
52
+ uncertainties.append({"source": f"{rel}:{line_no}", "expression": expr, "reason": "comparison value or expression is not statically literal"})
53
+ values = ["<unknown>"]
54
+ comparisons.append({"field": field, "operator": op, "values": values})
55
+ factors.setdefault(field, {"values": set(), "evidence": []})["values"].update(values)
56
+ factors[field]["evidence"].append(f"{rel}:{line_no}")
57
+ if comparisons:
58
+ rows.append({"id": f"DT-{len(rows)+1:04d}", "source": f"{rel}:{line_no}", "language": _language(path), "construct": match.group(1).lower(), "condition": expr, "conditions": comparisons, "outcome": "<human-confirmation-required>"})
59
+ elif expr and not IDENTIFIER_RE.fullmatch(expr):
60
+ uncertainties.append({"source": f"{rel}:{line_no}", "expression": expr, "reason": "condition found but no deterministic comparison extracted"})
61
+ factor_list = [{"name": name, "values": sorted(data["values"], key=str), "evidence": sorted(set(data["evidence"]))} for name, data in sorted(factors.items())]
62
+ return {"source_root": str(root), "files": files, "factors": factor_list, "decision_table": rows, "uncertainties": uncertainties}
63
+
64
+ def pairwise_table(factors):
65
+ """Create a deterministic strength-2 covering table from extracted factors."""
66
+ if not factors: return {"strength": 2, "rows": [], "uncovered_pairs": []}
67
+ names, domains = [f["name"] for f in factors], [f["values"] or ["<unknown>"] for f in factors]
68
+ if len(names) == 1: return {"strength": 2, "rows": [{names[0]: v} for v in domains[0]], "uncovered_pairs": []}
69
+ required = {(i, j, a, b) for i in range(len(names)) for j in range(i+1, len(names)) for a in domains[i] for b in domains[j]}
70
+ candidates = [dict(zip(names, values)) for values in itertools.product(*domains)]
71
+ selected = []
72
+ while required:
73
+ best = max(candidates, key=lambda row: sum((i, j, row[names[i]], row[names[j]]) in required for i in range(len(names)) for j in range(i+1, len(names))))
74
+ selected.append(best)
75
+ required -= {(i, j, best[names[i]], best[names[j]]) for i in range(len(names)) for j in range(i+1, len(names))}
76
+ candidates.remove(best)
77
+ return {"strength": 2, "rows": selected, "uncovered_pairs": sorted([list(x) for x in required], key=str)}
@@ -0,0 +1,44 @@
1
+ skill_id: batch.impact_regression
2
+ xid: xid-skill-batch-impact-regression
3
+ role: base_skill_contract
4
+
5
+ entry:
6
+ xid: xid-entry-batch-impact-regression
7
+ path: skill_assets/entry.md
8
+ load_policy: required_inline
9
+
10
+ required_fragments:
11
+ - id: batch_regression_workflow
12
+ xid: xid-fragment-batch-regression-workflow
13
+ path: skill_assets/references/workflow.md
14
+ load_policy: required_inline
15
+ - id: batch_regression_adapter_boundary
16
+ xid: xid-fragment-batch-regression-adapter-boundary
17
+ path: skill_assets/references/adapter-contract.md
18
+ load_policy: required_inline
19
+
20
+ branches: []
21
+
22
+ required_outputs:
23
+ - decision_table
24
+ - orthogonal_table
25
+ - regression_report
26
+ - evidence
27
+ - unknowns
28
+ - human_handoffs
29
+
30
+ required_knowledge: []
31
+ review_axes: []
32
+ schemas: []
33
+
34
+ must_not:
35
+ - treat_source_conditions_as_business_truth
36
+ - treat_baseline_results_as_business_correctness
37
+ - execute_against_production
38
+ - auto_accept_unexplained_differences
39
+ - claim_dynamic_sql_is_resolved_without_runtime_evidence
40
+
41
+ extension_policy:
42
+ can_be_extended_by_local_skill: true
43
+ local_can_weaken_required_outputs: false
44
+ local_can_override_must_not: false
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: xrefkit-skills-batch-regression
3
+ Version: 0.4.1
4
+ Summary: XRefKit Skill Package for C# and SQL Server batch impact analysis and combination regression
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: xrefkit<0.5.0,>=0.4.0
8
+
9
+ # xrefkit-skills-batch-regression
10
+
11
+ XRefKit Skill Package v0.4.1 for impact analysis and combination regression of
12
+ existing C# batches backed by SQL Server stored procedures.
13
+
14
+ The package provides a discoverable XRefKit v2 Skill Package and bundles the
15
+ repository-native Skill procedure, deterministic combination/result tools,
16
+ source condition extraction, configuration references, and synthetic fixtures.
17
+ Real batch and database execution still requires an isolated adapter supplied
18
+ by the consuming project.
19
+
20
+ Install:
21
+
22
+ ```powershell
23
+ python -m pip install xrefkit-skills-batch-regression==0.4.1
24
+ ```
25
+
26
+ The package is discovered through the `xrefkit.skill_packages` entry-point
27
+ group and includes the deterministic tools under `skill_assets/scripts/`.
@@ -0,0 +1,25 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/xrefkit_skills_batch_regression/__init__.py
4
+ src/xrefkit_skills_batch_regression/cli.py
5
+ src/xrefkit_skills_batch_regression/package_manifest.yaml
6
+ src/xrefkit_skills_batch_regression.egg-info/PKG-INFO
7
+ src/xrefkit_skills_batch_regression.egg-info/SOURCES.txt
8
+ src/xrefkit_skills_batch_regression.egg-info/dependency_links.txt
9
+ src/xrefkit_skills_batch_regression.egg-info/entry_points.txt
10
+ src/xrefkit_skills_batch_regression.egg-info/requires.txt
11
+ src/xrefkit_skills_batch_regression.egg-info/top_level.txt
12
+ src/xrefkit_skills_batch_regression/skill_assets/entry.md
13
+ src/xrefkit_skills_batch_regression/skill_assets/fixtures/config.json
14
+ src/xrefkit_skills_batch_regression/skill_assets/fixtures/new-results.json
15
+ src/xrefkit_skills_batch_regression/skill_assets/fixtures/old-results.json
16
+ src/xrefkit_skills_batch_regression/skill_assets/references/adapter-contract.md
17
+ src/xrefkit_skills_batch_regression/skill_assets/references/code-table-extraction.md
18
+ src/xrefkit_skills_batch_regression/skill_assets/references/config-schema.md
19
+ src/xrefkit_skills_batch_regression/skill_assets/references/config.template.json
20
+ src/xrefkit_skills_batch_regression/skill_assets/references/csharp-sql-analysis.md
21
+ src/xrefkit_skills_batch_regression/skill_assets/references/fixture-notes.md
22
+ src/xrefkit_skills_batch_regression/skill_assets/references/workflow.md
23
+ src/xrefkit_skills_batch_regression/skill_assets/scripts/batch_regression.py
24
+ src/xrefkit_skills_batch_regression/skill_assets/scripts/code_tables.py
25
+ src/xrefkit_skills_batch_regression/skills/batch_impact_regression.skill.yaml
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ xrefkit-batch-regression = xrefkit_skills_batch_regression.cli:main
3
+
4
+ [xrefkit.skill_packages]
5
+ batch_regression = xrefkit_skills_batch_regression:package_root