check_workflow

The workflow validator. It is the oracle for the planning surfaces: the roadmap grammar, the changelog order, the local links, the llms.txt index, and the text-hygiene checks. Run it with python scripts/check_workflow.py.

Check a repository against the UBC-FRESH dev workflow surfaces.

This module is the oracle for the planning surfaces. It parses ROADMAP.md and CHANGE_LOG.md, resolves local Markdown links and the docs/llms.txt index, and scans the text that a commit would include for defects that must not reach a public repository: control characters, byte-order marks, personal paths, and secret-shaped strings. Outside a git repository, the scan walks the tree and honors the root .gitignore. It uses only the standard library, so it runs in a fresh clone before anything is installed.

Command-line usage:

python scripts/check_workflow.py [ROOT]
python scripts/check_workflow.py --files PATH [PATH ...]

The exit status is 0 when no errors are found. Warnings are printed, but they do not fail the run. Finding codes are stable; see docs/agents/oracles-and-evidence.rst.

class check_workflow.Diagnostic(severity: str, code: str, path: str, line: int, message: str)[source]

Bases: object

One finding: its severity, its stable code, its location, and its message.

severity

Either "error" or "warning".

Type:

str

code

Stable finding code, such as ROADMAP-STATUS.

Type:

str

path

Repository-relative path of the file that the finding concerns.

Type:

str

line

One-based line number, or 0 for a whole-file finding.

Type:

int

message

Explanation of what to change.

Type:

str

render() → str[source]

Render the finding as one line of text.

Returns:

str – The severity, code, location, and message, padded into columns.

class check_workflow.Task(phase: int, number: int, checked: bool, line: int, issue: int | None, subtasks: list[tuple[int, bool]]=<factory>)[source]

Bases: object

A task line in a phase section, together with its subtask lines.

phase

The phase number that the task ID claims.

Type:

int

number

The task number within its phase.

Type:

int

checked

Whether the task checkbox is ticked.

Type:

bool

line

One-based line number of the task line.

Type:

int

issue

The child issue number, when the task line names one.

Type:

int or None

subtasks

One (line number, checked) pair for each subtask line under the task.

Type:

list of tuple of (int, bool)

class check_workflow.Phase(number: int, line: int, parent: str | None = None, branch: str | None = None, status: str | None = None, tasks: list[Task] = <factory>)[source]

Bases: object

A ## Phase <n>: section, its header lines, and its tasks.

number

The phase number from the heading.

Type:

int

line

One-based line number of the heading.

Type:

int

parent

The Parent issue: value, such as #1 or TBD.

Type:

str or None

branch

The Branch: value, without backticks.

Type:

str or None

status

The Status: value.

Type:

str or None

tasks

The tasks listed in the section, in file order.

Type:

list of Task

class check_workflow.IgnoreRule(pattern: Pattern[str], anchored: bool, directory_only: bool, negated: bool)[source]

Bases: object

One rule from the root .gitignore, compiled for the file walk.

pattern

The compiled pattern that the candidate must match in full.

Type:

re.Pattern[str]

anchored

True when the rule contains a slash, so it matches the path from the root. False when it matches the name of any path component.

Type:

bool

directory_only

True when the rule ends in a slash, so it applies to directories only.

Type:

bool

negated

True when the rule starts with !, which re-includes a path that an earlier rule ignores.

Type:

bool

check_workflow.error(code: str, path: str, line: int, message: str) → Diagnostic[source]

Build an error-severity finding.

Parameters:
  • code (str) – Stable finding code, such as LINK-BROKEN.

  • path (str) – Repository-relative path that the finding concerns.

  • line (int) – One-based line number, or 0 for a whole-file finding.

  • message (str) – Explanation of what to change.

Returns:

Diagnostic – A finding with severity "error".

check_workflow.warning(code: str, path: str, line: int, message: str) → Diagnostic[source]

Build a warning-severity finding.

Parameters:
  • code (str) – Stable finding code, such as FILE-RECOMMENDED.

  • path (str) – Repository-relative path that the finding concerns.

  • line (int) – One-based line number, or 0 for a whole-file finding.

  • message (str) – Explanation of what to change.

Returns:

Diagnostic – A finding with severity "warning".

check_workflow.is_ignored(rel: str, *, is_dir: bool, rules: Sequence[IgnoreRule]) → bool[source]

Report whether a path is ignored, the way git reports it.

A path under an ignored directory is ignored, even when a later rule re-includes it, because git does not re-include a file whose directory is excluded.

Parameters:
  • rel (str) – The path, relative to the root, with forward slashes.

  • is_dir (bool) – Whether the path is a directory.

  • rules (sequence of IgnoreRule) – The rules from load_ignore_rules(), in file order.

Returns:

bool – True when the path, or one of the directories that contain it, is ignored.

check_workflow.load_ignore_rules(root: Path) → list[IgnoreRule][source]

Read the root .gitignore rules, with a conservative rule for each unsupported negation.

Only the root file is read. A .gitignore in a subdirectory is not read, and neither is .git/info/exclude, because the walk has no index to consult.

Parameters:

root (Path) – Repository root, or the directory that stands in for one.

Returns:

list of IgnoreRule – The rules in file order. The list is empty when the root has no .gitignore.

check_workflow.iter_text_files(root: Path) → Iterator[Path][source]

Yield the UTF-8 text files that the scans should read.

The candidates are the files that git would commit, when the root is a repository top level. Otherwise they are the files under the root that its .gitignore does not ignore. The tests tree, the validator itself, symbolic links, oversized files, and files that are not valid UTF-8 are skipped in both cases.

Parameters:

root (Path) – Repository root or directory to scan.

Yields:

Path – Absolute path of each file that passes the filters, in sorted order.

check_workflow.parse_roadmap(lines: list[str]) → list[Phase][source]

Parse the phase sections of a roadmap into structured records.

Phase headers, status lines, tasks, and subtasks are recognized by the grammar in docs/reference/workflow-model.rst. Lines outside a phase section are ignored.

Parameters:

lines (list of str) – The roadmap, one entry per line, without line endings.

Returns:

list of Phase – One record for each ## Phase <n>: heading, in file order.

check_workflow.parse_tracker(lines: list[str]) → list[tuple[int, list[str], int]][source]

Find the rows of the Issue Tracker Map table.

Parameters:

lines (list of str) – The roadmap, one entry per line.

Returns:

list of tuple of (int, list of str, int) – For each row that starts with a phase ID: the phase number, the stripped table cells, and the one-based line number.

check_workflow.check_roadmap(path: str, text: str) → list[Diagnostic][source]

Check a roadmap’s phase grammar, phase statuses, and tracker map.

Parameters:
  • path (str) – Name of the roadmap file, used in the findings.

  • text (str) – The full roadmap text.

Returns:

list of Diagnostic – Every finding for the roadmap. The list is empty when the roadmap is consistent.

check_workflow.check_changelog(path: str, text: str) → list[Diagnostic][source]

Check that changelog entries are ISO dates in non-decreasing order.

Parameters:
  • path (str) – Name of the changelog file, used in the findings.

  • text (str) – The full changelog text.

Returns:

list of Diagnostic – Findings for malformed headings, out-of-order entries, and an empty changelog.

check_workflow.check_required_files(root: Path) → list[Diagnostic][source]

Check that the required files exist, and warn about recommended ones.

Parameters:

root (Path) – Repository root to check.

Returns:

list of Diagnostic – FILE-MISSING errors for absent required files, and FILE-RECOMMENDED warnings for absent recommended files.

Check that every local link in a Markdown file points at an existing path.

Parameters:

root (Path) – Repository root to check.

Returns:

list of Diagnostic – LINK-BROKEN errors for each local link whose target does not exist.

check_workflow.check_llms(root: Path) → list[Diagnostic][source]

Check that every link in docs/llms.txt names an existing docs page.

A link that ends in .html maps to the .rst source with the same stem. Any other link is resolved relative to docs/.

Parameters:

root (Path) – Repository root to check.

Returns:

list of Diagnostic – LLMS-BROKEN errors for each link without a matching page. The list is empty when the index is absent, because FILE-MISSING reports that case.

check_workflow.scan_text(rel: str, data: bytes) → list[Diagnostic][source]

Scan one file for control characters, byte-order marks, and unsafe strings.

Parameters:
  • rel (str) – Repository-relative path, used in the findings.

  • data (bytes) – The raw file contents, which must be valid UTF-8.

Returns:

list of Diagnostic – One finding for each defect found. A line can produce more than one finding.

check_workflow.scan_repository(root: Path) → list[Diagnostic][source]

Run the text scans over every file that iter_text_files() yields.

Parameters:

root (Path) – Repository root to scan.

Returns:

list of Diagnostic – All text-hygiene findings for the repository.

check_workflow.check_files(paths: list[Path]) → list[Diagnostic][source]

Run only the text scans on the given files, such as issue body files.

Parameters:

paths (list of Path) – Files to scan.

Returns:

list of Diagnostic – FILE-MISSING for absent files, ENCODING for files that are not valid UTF-8, and the text-hygiene findings for every other file.

check_workflow.run(root: Path) → list[Diagnostic][source]

Run every repository check against a root and return all findings.

Parameters:

root (Path) – Repository root to check.

Returns:

list of Diagnostic – The findings from every check, in a stable order.

check_workflow.main(argv: list[str] | None = None) → int[source]

Run the validator from the command line.

Parameters:

argv (list of str or None, optional) – Arguments to parse. None reads sys.argv.

Returns:

int – 0 when no errors were found, and 1 otherwise.

Raises:

SystemExit – When the argument parser rejects the arguments, or the root is not a directory.