ai-lsc/scripts/backfill_default_licenses.py

140 lines
4.0 KiB
Python
Executable File

"""Second-pass license backfill: for every tool that STILL doesn't have
a `license` field after backfill_tool_licenses.py ran, inject
`"license": "Proprietary"` (the defensive default — the gate will
require individual acceptance).
After running this, the validator should pass with 0 missing-license
errors. The user can then review the tools marked "Proprietary" and
update their licenses in defaults.py / layer files if any are actually
open-source.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path("/home/z/my-project/workspace/ai-lsc")
LAYER_DIR = ROOT / "src/ai_lsc/registry/layers"
DEFAULTS_PATH = ROOT / "src/ai_lsc/registry/defaults.py"
DEFAULT_LICENSE = "Proprietary"
_DESC_RE = re.compile(
r'(?P<indent>[ \t]+)"description":\s*(?:"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\'),?',
re.DOTALL,
)
_LICENSE_RE = re.compile(
r'(?P<indent>[ \t]+)"license":\s*(?:"[^"]+"|\'[^\']+\'),?\n',
)
def backfill_text(text: str) -> tuple[str, int, list[str]]:
"""Backfill `license` fields with the default for any tool block
that doesn't already have one. Returns (new_text, count, tool_ids_filled).
"""
updated = 0
filled_ids = []
pos = 0
out = []
for m in re.finditer(r"'([a-zA-Z0-9_]+)':\s*\{", text):
tool_id = m.group(1)
if tool_id in ("TOOLS",):
continue
# Find block end via brace-depth counting
block_start = m.end() - 1
depth = 0
i = block_start
in_string = False
string_char = None
block_end = -1
while i < len(text):
ch = text[i]
if in_string:
if ch == '\\':
i += 2
continue
if ch == string_char:
in_string = False
string_char = None
i += 1
continue
if ch in ('"', "'"):
in_string = True
string_char = ch
i += 1
continue
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
end = i + 1
if end < len(text) and text[end] == ',':
end += 1
block_end = end
break
i += 1
if block_end == -1:
continue
block = text[block_start:block_end]
# Skip if already has a license field
if _LICENSE_RE.search(block):
continue
# Inject license after description
desc_match = _DESC_RE.search(block)
if not desc_match:
continue
spdx = DEFAULT_LICENSE
insert_at = desc_match.end()
indent = desc_match.group("indent")
new_block = (
block[:insert_at]
+ "\n"
+ f'{indent}"license": \'{spdx}\','
+ block[insert_at:]
)
out.append(text[pos:m.start()])
out.append(text[m.start():block_start])
out.append(new_block)
pos = block_end
updated += 1
filled_ids.append(tool_id)
out.append(text[pos:])
return "".join(out), updated, filled_ids
def main() -> int:
total = 0
all_filled = []
for path in [DEFAULTS_PATH, *sorted(LAYER_DIR.glob("*.py"))]:
if path.name == "__init__.py":
continue
original = path.read_text(encoding="utf-8")
new_text, count, filled = backfill_text(original)
if count:
path.write_text(new_text, encoding="utf-8")
print(f" {path.name}: {count} tools defaulted to {DEFAULT_LICENSE!r}")
for tid in filled:
print(f" - {tid}")
total += count
all_filled.extend(filled)
print(f"\nDefaulted {total} tool(s) to {DEFAULT_LICENSE!r}.")
print("Review these and update their licenses if any are actually open-source.")
return 0
if __name__ == "__main__":
sys.exit(main())