scripts.scan_implementation
Reference › scripts › scan_implementation
scripts/scan_implementation.py is a thin command-line shim over scan_implementation_report in orchestrata_ultimate.verification.implementation_scanner. It scans a project for a spec's implementation and prints the result as Prolog facts. A spec-verification step or a human runs it; tests/scripts/test_scan_implementation.py covers it. Compare parse_spec_requirements, which does the same for the requirements side.
Usage is scan_implementation.py <spec_id> <project_dir>; fewer than two arguments prints usage and exits 1. Each fact is printed on standard output as a clause ending in a full stop. The scanner prints diagnostics of its own, so main captures standard output during the scan and replays any captured text to standard error, keeping the fact stream clean; the JS and TS serializer runs node as a child process and captures its own output for the same reason, and a test pins this. A summary line always prints, giving the counts of Python files parsed out of those discovered, JS and TS files, other files and JS and TS symbols, so a scan that found nothing can be told from one that scanned nothing.
At a glance
| Source | scripts/scan_implementation.py |
| Lines of code | 125 |
| Classes | 0 |
| Functions | 1 |
| Imports (internal) | orchestrata_ultimate.verification.implementation_scanner |
| Imported by | none |
| Tests that import it | tests/scripts/test_scan_implementation.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
main |
main() -> None |
CLI entry point for scan_implementation. |
Source
#!/usr/bin/env python3
"""Thin CLI shim for the implementation scanner.
STDOUT contract: this process writes ONLY Prolog ``implemented(SpecId,
Identifier).`` fact lines to stdout, one per line, and nothing else --
consumers (scripts/gate.py and friends) parse stdout as a fact stream.
Every human diagnostic, including any banner ``scan_implementation()``'s
own dependencies print internally (e.g. ``CodebaseAnalyzer.analyze()``'s
"Found N Python files in ..." line), is redirected to stderr instead. A
project with zero matching facts prints zero stdout lines -- empty is a
valid answer, a banner is not.
EXIT CONTRACT -- the part that makes an empty stdout READABLE:
0 the scan completed; stdout is the COMPLETE fact stream for this
project (zero lines means the project genuinely yields no
evidence)
1 usage error; nothing was scanned
2 the scan was DEGRADED -- an evidence source could not be read at
all (e.g. the JS/TS toolchain failed), so stdout is INCOMPLETE and
must not be treated as "this project implements nothing"
Without rc=2 an empty stdout is ambiguous, and that ambiguity is not
hypothetical: commit 29bbf088 deleted all non-Python evidence collection
and every consumer read the resulting silence as clean gaps. "Found
nothing" and "could not look" are different answers and get different
exit codes.
The stderr summary line reports what was actually examined -- Python,
JS/TS and other file counts -- so a reader never has to infer coverage
from the absence of output. ``CodebaseAnalyzer``'s own "Found 0 Python
files" line is true but describes only the Python half; on a JS/TS
project it reads as "nothing was scanned" unless the summary follows it.
WHAT THE FACTS MEAN: the second argument is a CODE IDENTIFIER found in
the project -- "this symbol exists" -- not a requirement id. That is a
different question from "which requirements are done", which
``implementation_scanner.match_requirements_to_evidence`` answers with
``implemented(SpecId, ReqId)`` facts that join against
``spec_completeness.pl``. The two share the ``implemented/2`` predicate
NAME but not its meaning, so feeding this stream into those rules yields
zero coverage rather than an error. Use the matcher, not this CLI, for a
coverage figure.
Usage:
python3 scripts/scan_implementation.py 014 /tmp/workspace
"""
import contextlib
import io
import sys
from orchestrata_ultimate.verification.implementation_scanner import (
scan_implementation_report,
)
#: rc for "an evidence source could not be read"; see the EXIT CONTRACT.
EXIT_DEGRADED = 2
def main() -> None:
"""CLI entry point for scan_implementation."""
if len(sys.argv) < 3:
print(
"Usage: scan_implementation.py <spec_id> <project_dir>",
file=sys.stderr,
)
sys.exit(1)
spec_id = sys.argv[1]
project_dir = sys.argv[2]
# scan_implementation_report() delegates to CodebaseAnalyzer.analyze(),
# which prints diagnostics straight to stdout. Capture stdout for the
# whole call so any such output is redirected to stderr instead of
# corrupting the Prolog fact stream this CLI promises on stdout. Use
# try/finally so diagnostics emitted before a mid-scan exception still
# reach stderr rather than being silently dropped.
#
# This guard is Python-level: contextlib.redirect_stdout rebinds
# sys.stdout, it does NOT redirect file descriptor 1, so it could not
# contain a child process that inherited stdout. The scan does spawn
# one (the JS/TS serializer runs node), and that call captures its own
# child output rather than letting it inherit -- which is what keeps
# the fact stream clean. Pinned by a test, not by assumption.
captured = io.StringIO()
try:
with contextlib.redirect_stdout(captured):
outcome = scan_implementation_report(spec_id, project_dir)
finally:
diagnostics = captured.getvalue()
if diagnostics:
sys.stderr.write(diagnostics)
for fact in outcome.facts:
print(f"{fact}.")
# The summary always prints: on a project with zero facts it is the
# only thing distinguishing "scanned 12 files, found nothing" from
# "scanned nothing".
print(
f"scan_implementation: scanned python={outcome.python_parsed}"
f"/{outcome.python_discovered} js_ts={outcome.js_ts_files} "
f"other={outcome.other_files}; js_ts_symbols={outcome.js_ts_symbols} "
f"facts={len(outcome.facts)}",
file=sys.stderr,
)
if outcome.degraded:
for diagnostic in outcome.diagnostics:
print(
f"scan_implementation: DEGRADED: {diagnostic}",
file=sys.stderr,
)
print(
"scan_implementation: the fact stream above is INCOMPLETE -- "
"an evidence source could not be read. Do not read it as "
"'this project implements nothing'.",
file=sys.stderr,
)
sys.exit(EXIT_DEGRADED)
if __name__ == "__main__":
main()