Swyrlz / github_read_inference_v235.py
kamiloki's picture
Manual verified §wyrlz candidate from GitHub
6badfd7 verified
Raw History Blame Contribute Delete
15.5 kB
"""Read-only user question -> existing evidence command, never tool authority.
Scope comes from explicit GitHub wording or a previously verified same-thread
repository context. This classifier selects an allowed read, not the answer:
GitHub results, pinned revisions and inspection limits still own the facts.
"""
from __future__ import annotations
import re
_MUTATION = re.compile(
r"\b(?:add|build|create|delete|deploy|dispatch|edit|fix|"
r"implement|install|merge|modify|patch|push|refactor|remove|rename|"
r"rerun|retry|rewrite|run|trigger|update|upload|write)\b", re.I)
_REPO = re.compile(r"\b(?:github|repo|repository)\b", re.I)
_READ = re.compile(
r"\b(?:analy[sz]e|architecture|compare|count|describe|directory|"
r"directories|display|browse|view|explain|explore|file|files|folder|folders|history|inspect|"
r"how|largest|list|locate|modified|overview|read|relate|related|relationship|report|"
r"search|see|show|size|source|structure|summari[sz]e|tree|what|"
r"where|which|how many|when)\b", re.I)
_PATH = re.compile(
r"(?<![\w./])(?:[A-Za-z0-9_§.+-]+/){1,6}[A-Za-z0-9_§.+-]+"
r"|(?<![\w./])[A-Za-z0-9_§.+-]+\.(?:py|js|mjs|ts|tsx|jsx|"
r"html|css|json|md|txt|yaml|yml|toml|kt|java|go|rs|sh|xml|sql)",
re.I)
_BARE = re.compile(r"\b(?:folder|directory|under|inside|within|in)\s+"
r"(?:the\s+)?(?:folder\s+|directory\s+)?"
r"([A-Za-z0-9_§.-]{2,80})(?=\s|$)", re.I)
_BAD_PATH = re.compile(r"[\\\x00-\x1f?#%*{}\[\]<>|]")
_COMMAND = re.compile(r"^@github\s+", re.I)
_PREFIX = re.compile(r"^@github\s+(?:ask\s+)?", re.I)
_EXPLICIT_COMMAND = re.compile(
r"^(?:@github\s+)(?:files|stats|largest|report|read|open|view|"
r"history|search|compare|project status|chat status|permissions|"
r"capabilities|help)(?:\s|$)", re.I)
_DIRECT_PATH_READ = re.compile(
# Exact, explicit, multi-component file reads can use the signed-in
# account's selected repo even in a new thread. The authorized GitHub
# command handler re-verifies access and the path before returning data.
r"^(?:please\s+)?(?:read|open|view)\s+(?:the\s+)?(?:file\s+)?"
r"((?:[A-Za-z0-9_§.+-]+/){1,6}[A-Za-z0-9_§.+-]+\."
r"(?:py|js|mjs|ts|tsx|jsx|html|css|json|md|txt|yaml|yml|toml|"
r"kt|java|go|rs|sh|xml|sql))[?.!\s]*$", re.I)
def _safe(path):
path = str(path or "").strip().strip("`\"'").rstrip(".,!?").strip("/")
if (not path or len(path) > 160 or _BAD_PATH.search(path)
or any(p in ("", ".", "..") for p in path.split("/"))):
return ""
return path
def _scope_context(context):
if not isinstance(context, dict):
return "", ""
# The account/tool layer separately re-verifies repository access. These
# hints are never trusted as GitHub authorization or as source evidence.
directory = _safe(context.get("directory"))
file_path = _safe(context.get("lastFile"))
return directory, file_path
def _paths(question):
found = []
for m in _PATH.finditer(question):
item = _safe(m.group(0))
if item and item.casefold() not in [x.casefold() for x in found]:
found.append(item)
if len(found) == 8:
break
return found
_CONVERSATIONAL_REFLECTION = re.compile(
r"\b(?:what do you think|what(?:'s| is) your (?:opinion|take)|"
r"(?:do|did) you (?:like|notice)|what (?:would|do) you say|"
r"what you would say|from your perspective|"
r"just wanted to (?:see|hear|know)|wanted to see what)\b", re.I)
_EXPLICIT_READ_REQUEST = re.compile(
r"^(?:(?:please\s+)|(?:(?:can|could|would)\s+you\s+(?:please\s+)?))?"
r"(?:read|open|view|show|list|inspect|review|analy[sz]e|search|find)\b", re.I)
_TECHNICAL_REVIEW = re.compile(
r"\b(?:source|code|function|module|architecture|implementation|"
r"logic|security|test coverage|structure|design|bugs?|"
r"performance|technical|§tart|github|repo|repository|"
r"analy[sz]e|review|debug|audit|"
r"inspect|explain)\b", re.I)
def is_conversational_reflection(prompt):
"""An opinion or social follow-up, not an implicit GitHub read/review.
Intent is evaluated independently of existing verified repository scope:
a file mention is context, not a request to fetch that file again.
Explicit read/analysis actions and technical questions remain tool-eligible.
"""
raw = str(prompt or "").strip()
return bool(
raw and _CONVERSATIONAL_REFLECTION.search(raw)
and not _COMMAND.match(raw)
and not _EXPLICIT_READ_REQUEST.match(raw)
and not _TECHNICAL_REVIEW.search(raw)
)
def classify_read(prompt, context=None):
"""Return one *existing* Stage-1 @GitHub read command, or None.
A natural-language question is not a source of new GitHub capabilities.
Do not route writes, freeform dialogue, private-history recall, or ambiguous
pronouns with no verified thread context into a repo operation.
"""
raw = str(prompt or "").strip()
if not raw or len(raw) > 600 or "\n" in raw or "\r" in raw:
return None
if re.fullmatch(
r"(?i)(?:what (?:is|are)|how does) (?:github|(?:a|the) (?:git )?repo(?:sitory)?)"
r"(?: work)?[?.!]*", raw):
return None # General GitHub education is not a connected-repository read.
if _EXPLICIT_COMMAND.match(raw):
return None # existing command owners handle their own parameters
# v289: explicit repo-maturity questions select a pinned read-only
# classification, never a guessed implementation or write capability.
if re.fullmatch(
r"(?i)(?:(?:is|was)\s+(?:our|my|the)\s+(?:github\s+)?(?:repo|repository)\s+"
r"(?:empty|populated|ready\s+for\s+testing)|"
r"how\s+ready\s+is\s+(?:our|my|the)\s+(?:github\s+)?(?:repo|repository))"
r"[?.!]*", raw):
return "@GitHub readiness"
if is_conversational_reflection(raw):
return None # social response owns this turn; retained GitHub scope is data
# Match the entire read-only command BEFORE verb scans. Folder segments
# such as "write-action-tests" are path data, not instructions to write.
# No thread context is synthesized: the connected account's repository
# and current Git revision are resolved inside the existing Stage-1 gate.
direct = _DIRECT_PATH_READ.fullmatch(raw)
if direct:
path = _safe(direct[1])
if path:
return "@GitHub read file " + path
if _MUTATION.search(raw) or re.search(r"\bcommit\s+(?:my\s+|the\s+|these\s+)?(?:changes?|code|files?)\b", raw, re.I):
return None
if re.search(r"\b(?:remember|recall|previous chat|chat history|"
r"imported conversations?|chatgpt archive)\b", raw, re.I):
return None
directory, last_file = _scope_context(context)
# A verified root is still a repository scope, even with directory="".
verified_root = bool(isinstance(context, dict)
and re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", str(context.get("repo") or ""))
and re.fullmatch(r"[a-f0-9]{40}", str(context.get("sourceSha") or ""))
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]{0,100}", str(context.get("branch") or ""))
and context.get("kind") in ("directory", "file"))
explicit = bool(_COMMAND.match(raw) or _REPO.search(raw))
# A short answer to a verified directory/file-list turn can identify a
# filename already shown in the repository. Discursive openers ("ok so")
# do not change the operation: the EXISTING authenticated GitHub resolver
# must prove a unique name/stem match. Never trust or invent a model path.
selection = None
if verified_root and context.get("kind") == "directory":
selection_prompt = re.sub(
r"(?i)^(?:(?:ok(?:ay)?|alright)[,!\s]+)?(?:so[,!\s]+)?",
"", raw).strip()
candidate = re.fullmatch(
r"(?:the|that|this)\s+([A-Za-z0-9_§.+-]{2,100})"
r"(?:\s+(?:one|file))?[?.!\s]*",
selection_prompt, re.I)
if candidate:
name = candidate[1]
if (("." in name or re.search(r"\b(?:one|file)\b", selection_prompt, re.I))
and name.casefold() not in (
"first", "second", "third", "last", "next",
"other", "same", "right", "left", "good", "bad",
"nice", "new", "old", "correct", "file")):
selection = _safe(name)
if not explicit and not (directory or last_file or verified_root):
return None
# GitHub provenance narrows a real source operation; it does not turn
# generic demonstratives ("it", "that") or a bare "what" into one.
# Literal paths / singular source nouns are strong enough. Generic
# plural requests ("show the files", "how many files") continue through
# the existing directory-scoped followup resolver. Keep the verified
# single-file "explain how it works" continuation.
if (not explicit and not selection
and not re.search(
r"\b(?:file|folder|directory|repo|repository|source|code|"
r"module|function|path|project)\b", raw, re.I)
and not _paths(raw)
and not (last_file and re.fullmatch(
r"(?i)explain how it works[?.!]*", raw))):
return None
q = _PREFIX.sub("", raw) if _COMMAND.match(raw) else raw
if not _READ.search(q) and not selection:
return None
lower = q.casefold()
paths = _paths(q)
branch_match = re.search(r"\b(?:on|from)\s+branch\s+([A-Za-z0-9][A-Za-z0-9._/-]{0,100})\b", q, re.I)
named_branch = branch_match[1] if branch_match else ""
if not named_branch and isinstance(context, dict):
candidate = str(context.get("branch") or "")
rev = str(context.get("sourceSha") or "")
if (candidate not in ("", "main", "master")
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]{0,100}", candidate)
and re.fullmatch(r"[a-f0-9]{40}", rev)):
named_branch = candidate
if branch_match:
paths = [p for p in paths if p != branch_match[1]]
suffix = (" on branch " + named_branch) if named_branch else ""
if selection:
return "@GitHub read file " + selection + suffix
# Simple file-list questions need an inventory, not a source-analysis report.
# Recursive listing includes files below the root, not just top-level items.
if re.fullmatch(r"(?:what(?: are)?|which)(?: the)? files(?: are)? (?:in|of) (?:my|our|the) (?:github )?(?:repo|repository)[?.!\s]*", lower):
return "@GitHub files" + suffix # Reuse v223 interactive directory explorer.
# Bare filename answers to a previous file-location exchange are read-only
# source requests, not instructions to generate a new program.
nominal = re.fullmatch(
r"(?:the|this|that)\s+([A-Za-z0-9_§.+/-]+\.(?:py|js|mjs|ts|tsx|jsx|html|css|json|md|txt|yaml|yml|kt|java|go|rs|sh|xml|sql))\s+file[?.!\s]*",
q, re.I)
if nominal and verified_root:
named = _safe(nominal[1])
if named:
return "@GitHub read file " + named + suffix
# Locate a descriptive test file by matching the *repository path*, not
# by inventing a file or replaying the previous whole-source report.
if re.fullmatch(
r"(?:can|could|would) you (?:please )?locate (?:the|a) test file (?:in|from) (?:the|my|our) (?:github )?(?:repo|repository)[?.!\s]*",
lower):
return "@GitHub search test" + suffix
# A directory named in a question is a literal scope, never a guessed
# implementation owner. Do not pick random matching files by meaning.
if not paths:
end_scope = re.search(
r"\b([A-Za-z0-9_§.-]{2,80})\s+(?:folder|directory)\b", q, re.I)
if end_scope:
candidate = _safe(end_scope[1])
if candidate not in ("the", "this", "that", "current", "our", "my"):
paths = [candidate]
if not paths:
m = _BARE.search(q)
if m and m[1].lower() not in (
"repo", "repository", "github", "project", "source", "code",
"file", "files", "folder", "directory", "this", "that", "our",
"my", "the", "here", "which", "what", "how", "git", "history"):
if re.search(r"\b(?:folder|directory|under|inside|within|files?\s+in|directories\s+in)\b", q, re.I):
paths = [_safe(m[1])]
scoped = paths[:]
if last_file and paths and re.search(r"\b(?:that|this|the|original)\s+(?:original\s+)?file\b", lower):
scoped = [last_file] + [p for p in paths if p != last_file]
if not scoped and re.search(r"\b(?:this|that|those|same|last|previous|original|first)\s+"
r"(?:(?:original|first|previous|source|earlier)\s+)*files?\b|\b(?:it|that file)\b",lower):
if last_file:
scoped = [last_file]
elif not explicit:
return None
if not scoped and re.search(r"\b(?:this|that|current|same)\s+"
r"(?:folder|directory)\b|\bhere\b", lower):
if directory:
scoped = [directory]
elif not explicit:
return None
if re.search(r"\b(?:how many|count|number of|total|size of)\b", lower) and re.search(
r"\b(?:files?|folders?|directories|directory|bytes|size)\b", lower):
return "@GitHub stats" + (" " + scoped[0] if scoped else "") + suffix
if re.search(r"\b(?:largest|biggest)\b", lower) and re.search(r"\bfiles?\b", lower):
return "@GitHub largest 15" + (" " + scoped[0] if scoped else "") + suffix
if re.search(r"\b(?:history|when|last modified|last changed|"
r"first added|changed|modified|changes? (?:to|in))\b",lower):
if scoped:
return "@GitHub history " + scoped[0] + suffix
return "@GitHub report structure /" + suffix
# A named singular file is a deterministic repository read, including
# conversational "can you see <name> file" phrasing. Do not send it
# through the generic GitHub command parser or programming inference.
if scoped and re.search(r"\b(?:see|find|show|read|open|view)\b", lower) and re.search(r"\bfile\b", lower):
return "@GitHub read file " + scoped[0] + suffix
if re.search(r"\b(?:list|show|see|browse|open|display|view)\b", lower) and re.search(
r"\b(?:files?|folders?|directories|contents|tree)\b", lower):
if re.search(r"\b(?:all|every|entire|complete|recursive|again)\b", lower) and re.search(
r"\b(?:files?|tree)\b", lower):
return "@GitHub files recursive" + (" " + scoped[0] if scoped else "") + suffix
return "@GitHub files" + (" " + scoped[0] if scoped else "") + suffix
if re.search(r"\b(?:read|open|show)\b", lower) and re.search(r"\bfile\b", lower):
if scoped:
return "@GitHub read file " + scoped[0] + suffix
return None # explicit target required for a source-file read
if re.search(r"\b(?:explain|explore|describe|summari[sz]e|analy[sz]e|"
r"inspect|how|what|which|where|architecture|"
r"dependencies|related|relationship|work|purpose|overview)\b", lower):
if scoped:
cmd = "@GitHub report deep " + " | ".join(scoped)
return cmd + (" on branch " + named_branch if named_branch else "")
return "@GitHub report structure /" + (" on branch " + named_branch if named_branch else "")
return None