Initial SQL-only 1C adapter baseline

This commit is contained in:
2026-07-22 03:03:47 +03:00
commit e2503b77e7
545 changed files with 184711 additions and 0 deletions
+334
View File
@@ -0,0 +1,334 @@
#!/usr/bin/env python3
"""Create a read-only 1C change proposal from a task evidence bundle."""
from __future__ import annotations
import argparse
import base64
import json
import re
from pathlib import Path
from typing import Any
def decode_arg(value: str | None, encoded: str | None) -> str | None:
if encoded:
return base64.b64decode(encoded).decode("utf-8")
return value
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def norm(value: Any) -> str:
return re.sub(r"[\s._-]+", "", str(value or "")).casefold()
def words(text: str) -> set[str]:
return set(re.findall(r"[A-Za-zА-Яа-яЁё0-9_]{3,}", text.casefold()))
def infer_intents(text: str) -> list[str]:
w = words(text)
intents = []
if w & {"добавить", "создать", "вывести", "показать"}:
intents.append("add_or_expose")
if w & {"изменить", "исправить", "доработать", "поменять"}:
intents.append("modify")
if w & {"проверить", "проверка", "проанализировать"}:
intents.append("inspect_or_verify")
if w & {"кнопка", "кнопку", "команда", "команду"}:
intents.append("form_command")
if w & {"форма", "форму"}:
intents.append("form")
if w & {"реквизит", "реквизиты", "поле", "поля"}:
intents.append("attribute")
if w & {"проведение", "провести", "запись", "записать"}:
intents.append("object_lifecycle")
return intents or ["inspect_or_verify"]
def unique_list(values: list[Any]) -> list[Any]:
result = []
seen = set()
for value in values:
key = json.dumps(value, ensure_ascii=False, sort_keys=True)
if key in seen:
continue
seen.add(key)
result.append(value)
return result
def collect_search_findings(investigation: dict[str, Any]) -> list[dict[str, Any]]:
findings = []
for search in investigation.get("searches") or []:
for match in search.get("matches") or []:
evidence = match.get("evidence") or {}
findings.append(
{
"search_text": search.get("text"),
"area": match.get("area"),
"name": match.get("name"),
"title": match.get("title"),
"form": match.get("form"),
"origin": match.get("origin"),
"effective_action": match.get("effective_action"),
"path": evidence.get("path") or evidence.get("form_xml_path"),
"line": evidence.get("line"),
"id": evidence.get("id"),
"text": evidence.get("text"),
}
)
return unique_list(findings)
def collect_file_targets(investigation: dict[str, Any]) -> list[dict[str, Any]]:
targets = []
for form_context in investigation.get("forms") or []:
for form in form_context.get("forms") or []:
targets.append(
{
"kind": "form_xml",
"name": form.get("name"),
"origin": form.get("origin"),
"path": form.get("form_xml_path"),
"module_path": form.get("module_path"),
"reason": "selected_form_context",
}
)
for overlay in form.get("extension_overlays") or []:
targets.append(
{
"kind": "form_xml",
"name": overlay.get("name"),
"origin": overlay.get("origin"),
"path": overlay.get("form_xml_path"),
"module_path": overlay.get("module_path"),
"reason": "active_extension_form_overlay",
}
)
for module_context in investigation.get("modules") or []:
for module in module_context.get("modules") or []:
targets.append(
{
"kind": "bsl_module",
"name": module.get("name"),
"origin": module.get("origin"),
"path": module.get("path"),
"relative_path": module.get("relative_path"),
"reason": "selected_module_context",
}
)
for snippet in investigation.get("code_snippets") or []:
targets.append(
{
"kind": "bsl_module",
"name": snippet.get("module"),
"origin": snippet.get("origin"),
"path": snippet.get("path"),
"line": snippet.get("focus_line"),
"reason": f"code_search_hit:{snippet.get('search_text')}",
}
)
return unique_list([target for target in targets if target.get("path") or target.get("module_path")])
def extension_names_from_targets(targets: list[dict[str, Any]]) -> list[str]:
names = []
for target in targets:
origin = target.get("origin") or {}
if origin.get("layer") == "extension" and origin.get("extension"):
names.append(origin["extension"])
return sorted(set(names))
def choose_write_strategy(investigation: dict[str, Any], targets: list[dict[str, Any]]) -> dict[str, Any]:
extension_names = extension_names_from_targets(targets)
active_extensions = ((investigation.get("brief") or {}).get("active_extensions") or [])
if extension_names:
preferred = extension_names[0]
reason = "task evidence already points to an active extension overlay"
elif active_extensions:
preferred = active_extensions[0]
reason = "active extensions exist; extension-first write path is required"
else:
preferred = None
reason = "no active extension target found; create a new extension in a disposable validation loop"
return {
"mode": "extension_first_proposal",
"preferred_extension": preferred,
"reason": reason,
"forbidden": [
"direct SQL metadata/data updates",
"direct Config/ConfigSave/ConfigCAS writes",
"automatic production Designer update/apply",
],
}
def split_write_targets(targets: list[dict[str, Any]], preferred_extension: str | None) -> dict[str, list[dict[str, Any]]]:
write_candidates = []
references = []
for target in targets:
origin = target.get("origin") or {}
if origin.get("layer") == "extension" and (not preferred_extension or origin.get("extension") == preferred_extension):
write_candidates.append(target)
else:
references.append(target)
return {
"write_candidates": unique_list(write_candidates),
"read_only_reference_files": unique_list(references),
}
def summarize_existing_state(findings: list[dict[str, Any]], targets: list[dict[str, Any]]) -> dict[str, Any]:
form_commands = [row for row in findings if row.get("area") in {"form.command", "form.item"}]
code_hits = [row for row in findings if row.get("area") == "module.code"]
attributes = [row for row in findings if row.get("area") == "metadata.attribute"]
return {
"found_form_commands_or_items": form_commands,
"found_metadata_attributes": attributes,
"found_code_hits": code_hits[:20],
"target_files": targets,
}
def build_steps(task_text: str, intents: list[str], investigation: dict[str, Any], findings: list[dict[str, Any]], targets: list[dict[str, Any]]) -> list[dict[str, Any]]:
object_info = ((investigation.get("brief") or {}).get("object") or {})
steps = [
{
"order": 1,
"action": "confirm_target_object",
"object": object_info,
"evidence": investigation.get("candidate"),
},
{
"order": 2,
"action": "review_effective_metadata",
"purpose": "ensure extension-added attributes and modified types are included before code generation",
},
]
next_order = 3
if "form_command" in intents or any(row.get("area") in {"form.command", "form.item"} for row in findings):
steps.append(
{
"order": next_order,
"action": "review_form_command",
"purpose": "determine whether the command already exists or must be added",
"evidence": [row for row in findings if row.get("area") in {"form.command", "form.item"}],
}
)
next_order += 1
if any(row.get("area") == "module.code" for row in findings):
steps.append(
{
"order": next_order,
"action": "review_related_bsl",
"purpose": "inspect code snippets before proposing BSL changes",
"evidence": [row for row in findings if row.get("area") == "module.code"][:10],
}
)
next_order += 1
steps.append(
{
"order": next_order,
"action": "draft_extension_patch",
"purpose": "prepare a reviewable patch against extension source files only",
"candidate_targets": targets[:20],
}
)
steps.append(
{
"order": next_order + 1,
"action": "validate_in_disposable_base",
"purpose": "load/update extension, open affected forms, run smoke scenario, and only then ask for approval",
}
)
return steps
def build_checks(intents: list[str]) -> list[dict[str, Any]]:
checks = [
{"name": "syntax_check", "description": "Run 1C/BSL syntax validation for changed modules."},
{"name": "extension_load", "description": "Package/load the extension into a disposable base."},
{"name": "form_open", "description": "Open affected forms and verify command bindings and handlers."},
]
if "form_command" in intents:
checks.append({"name": "command_invocation", "description": "Invoke the affected form command and verify expected behavior."})
if "object_lifecycle" in intents:
checks.append({"name": "write_posting_smoke", "description": "Create/write/post a test document in a disposable base."})
return checks
def proposal_for_investigation(task_text: str, investigation: dict[str, Any]) -> dict[str, Any]:
intents = infer_intents(task_text)
findings = collect_search_findings(investigation)
targets = collect_file_targets(investigation)
strategy = choose_write_strategy(investigation, targets)
target_split = split_write_targets(targets, strategy.get("preferred_extension"))
return {
"object": (investigation.get("brief") or {}).get("object"),
"candidate": investigation.get("candidate"),
"intents": intents,
"write_strategy": strategy,
"existing_state": summarize_existing_state(findings, targets),
"target_policy": target_split,
"implementation_steps": build_steps(task_text, intents, investigation, findings, targets),
"validation_checks": build_checks(intents),
"open_questions": infer_open_questions(task_text, findings),
}
def infer_open_questions(task_text: str, findings: list[dict[str, Any]]) -> list[str]:
questions = []
if "добав" in task_text.casefold() and any(row.get("area") in {"form.command", "form.item"} for row in findings):
questions.append("The requested command/item appears to already exist; confirm whether the task is to modify existing behavior rather than add a duplicate.")
if not findings:
questions.append("No direct matches were found in the evidence bundle; broaden search terms or inspect more objects before generating a patch.")
return questions
def build_proposal(evidence: dict[str, Any]) -> dict[str, Any]:
task_text = ((evidence.get("task") or {}).get("text") or "")
proposals = [proposal_for_investigation(task_text, item) for item in evidence.get("investigations") or []]
return {
"schema": "onec_task_change_proposal.v1",
"task": evidence.get("task"),
"source_evidence_schema": evidence.get("schema"),
"view": evidence.get("view"),
"proposals": proposals,
"safety": {
"mode": "proposal_only",
"write_status": "blocked_until_write_gates",
"write_contract": "docs/1c-write-path-safety.md",
},
"counts": {
"proposals": len(proposals),
"target_files": sum(len((proposal.get("existing_state") or {}).get("target_files") or []) for proposal in proposals),
"write_candidates": sum(len((proposal.get("target_policy") or {}).get("write_candidates") or []) for proposal in proposals),
"open_questions": sum(len(proposal.get("open_questions") or []) for proposal in proposals),
},
}
def main() -> int:
parser = argparse.ArgumentParser(description="Create a read-only 1C change proposal from evidence.")
parser.add_argument("--evidence", type=Path, required=True)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
result = build_proposal(load_json(args.evidence))
output = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(output, encoding="utf-8")
print(json.dumps({"output": str(args.output), "counts": result["counts"]}, ensure_ascii=False))
else:
print(output)
return 0
if __name__ == "__main__":
raise SystemExit(main())