grok-bot-os 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ node_modules/
2
+ site/.astro/
3
+ site/dist/
4
+ site/node_modules/
5
+ .polyglot-cache.json
6
+ __pycache__/
7
+ *.pyc
8
+ dist/
9
+ *.egg-info/
10
+ .venv/
11
+
12
+ .swarm/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mcp-tool-shop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.5
2
+ Name: grok-bot-os
3
+ Version: 1.0.0
4
+ Summary: Operating system for Grok Bot teams: slow-burn Health A queue. Not the testing-os dogfood-swarm CLI.
5
+ Project-URL: Homepage, https://mcp-tool-shop-org.github.io/grok-bot-os/
6
+ Project-URL: Repository, https://github.com/mcp-tool-shop-org/grok-bot-os
7
+ Author: mcp-tool-shop
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ <p align="center">
14
+ <img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/grok-bot-os/readme.png" width="280" alt="grok-bot-os">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> | <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
19
+ </p>
20
+
21
+ # grok-bot-os
22
+
23
+ <p align="center">
24
+ <a href="https://github.com/mcp-tool-shop-org/grok-bot-os/actions"><img src="https://github.com/mcp-tool-shop-org/grok-bot-os/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
25
+ <a href="https://www.npmjs.com/package/@mcptoolshop/grok-bot-os"><img src="https://img.shields.io/npm/v/@mcptoolshop/grok-bot-os.svg" alt="npm"></a>
26
+ <a href="https://pypi.org/project/grok-bot-os/"><img src="https://img.shields.io/pypi/v/grok-bot-os.svg" alt="PyPI"></a>
27
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"></a>
28
+ <a href="https://mcp-tool-shop-org.github.io/grok-bot-os/"><img src="https://img.shields.io/badge/landing-page-0ea5e9.svg" alt="Landing page"></a>
29
+ </p>
30
+
31
+ Versioned operating system for **Grok Bot** teams. Orchestrator, Builder,
32
+ Reviewer, and Researcher sit in a Grok Bot group chat. **Coordinator** is
33
+ Grok Build on the operator's machine — not a Grok Bot. Work moves through a
34
+ live `QUEUE.md` on the Bot VM. Default cadence: one coding job at a time,
35
+ many in review, Coordinator merges when the human is back. Health A is the
36
+ **default lane**, not the ceiling — Coordinator can open Health B–D and
37
+ named features on this same queue.
38
+
39
+ **Status:** v1.0.0 on PyPI (`grok-bot-os`), npm (`@mcptoolshop/grok-bot-os`), and GitHub Releases.
40
+
41
+ ## What this is not
42
+
43
+ - Not the `testing-os` dogfood-swarm CLI (`swarm init` / 10-phase).
44
+ - Not `swarm-control-plane` (SQLite waves, domain freeze, receipts).
45
+ - Not Role OS. Role OS orchestrates Codex. This OS orchestrates Grok Bots
46
+ on the cloud VM via QUEUE.
47
+
48
+ Stay on **this queue** for one-locus work (Health A default; later classes
49
+ when Coordinator names them). Escalate to swarm-control-plane only when
50
+ the job is **cross-cutting** (frozen domains, wave receipts — Kim 2025 is
51
+ about seats, not a Health A ceiling). Grounding:
52
+ [`docs/RESEARCH.md`](docs/RESEARCH.md).
53
+
54
+ ## Install / use
55
+
56
+ Registries (optional — the live OS is still a git clone on the Bot VM):
57
+
58
+ ```
59
+ pip install grok-bot-os
60
+ npm install @mcptoolshop/grok-bot-os
61
+ ```
62
+
63
+ `pip` installs `route-scratch` and `check-queue-fields`. The Bot VM still
64
+ clones this repo per `CLONE-POLICY.md` and runs `python3 scripts/route-scratch.py`.
65
+
66
+ 1. Clone the OS and the consumer per `CLONE-POLICY.md`. Coordinator
67
+ workstation: a local clone of this repo. Bot VM:
68
+ `/workspace/studio/<repo>` only after a ticket names `owner/repo`.
69
+ Never copy the Coordinator tree onto the VM. Never copy `.swarm`.
70
+ Bots never copy the scratch transport clone.
71
+ 2. The generic OS contract is `SLOW-BURN.md` in this git. Consumer holds
72
+ (PR numbers, hours) live in `consumers/<name>.md`. Do **not** overwrite
73
+ a VM `SLOW-BURN.md` that still carries consumer PR numbers until
74
+ Orchestrator is reading **both** git files.
75
+ 3. Paste **all four** Bot profiles (Name / Title / Description) from
76
+ `bots/orchestrator.md`, `bots/builder.md`, `bots/reviewer.md`, and
77
+ `bots/researcher.md` **before** adding anyone to the group. Then add
78
+ those four to one Grok Bot **group chat** — still four seats, no fifth
79
+ (Kim 2025). Coordinator is not a Bot (`bots/coordinator.md`).
80
+ 4. Seed live `QUEUE.md` on the VM from `QUEUE.template.md` plus the
81
+ consumer file (stop / prerequisite / owner / fallback in full). Live
82
+ QUEUE and `TO-COORDINATOR.md` stay on the VM.
83
+ 5. Run Orchestrator once (clock or `@Orchestrator`) so it pulls consumer
84
+ `main` and assigns the first `ready` row. **The first coding job is an
85
+ Orchestrator @**, never a human paste and never “add Orchestrator after
86
+ idle.”
87
+ 6. Only then may Builder code. Scratch routing: `ROUTE.md` +
88
+ `scripts/route-scratch.py` on the VM. Poll with `gh api` only (no
89
+ `git fetch`). Fail-closed: seed last-sha first; do not advance it on
90
+ exit != 0. Empty stdout after exit 0 = silent. Researcher
91
+ `SendToAgent`s Bot `DELIVER`s only.
92
+
93
+ Coordinator session-open (Grok Build TUI on the operator's machine):
94
+ `git pull` the local scratch-transport clone, then read new
95
+ `to: grok-build` envelopes. Do not paste Bot-group logs.
96
+ `TO-COORDINATOR.md` is the VM append-only log, not the transport. The
97
+ TUI has no inbound ping and no API to Grok Bot. Do not reconstruct
98
+ state from the group transcript (KC 2026).
99
+
100
+ ## QUEUE contract (do not compress)
101
+
102
+ Every QUEUE row, Orchestrator `@`, and scratch ticket carries **stop /
103
+ prerequisite / owner / fallback** in full. **`see SLOW-BURN` is
104
+ forbidden** (Sun 2026). Ready Dependabot / patch rows skip Reviewer.
105
+ Hold / major / behavior still require Reviewer 🛑. Coordinator still
106
+ merges every `coordinator-merge`.
107
+
108
+ Builder does not review itself. Reviewer pass on a mergeable PR is 🛑.
109
+ Merge stays on Grok Build (Panickssery 2024; Kambhampati 2024).
110
+
111
+ Finalize emojis, last line only: ✅ Bot done, no Coordinator · 🔧 rework ·
112
+ 🛑 Coordinator must act.
113
+
114
+ ## Trust model
115
+
116
+ **Touches:** public git playbooks; `gh api` against
117
+ `mcp-tool-shop/rig-bridge-scratch` from an already-authenticated `gh`
118
+ on the VM; markdown QUEUE on the VM.
119
+
120
+ **Does not touch:** npm registry, consumer `main`, Coordinator swarm
121
+ sqlite (`.swarm`, `~\.grok\*.sqlite`), PATs, `auth.json`,
122
+ `mcp_credentials.json`, telemetry, GitHub MCP token fields.
123
+
124
+ **Permissions:** read this repo; `gh api` as `mcp-tool-shop` for scratch
125
+ routing. Bots never merge. Coordinator merges every `coordinator-merge`
126
+ (`--admin` if Actions dark). Reviewer 🛑 is required for hold / major /
127
+ behavior. Ready patch / Dependabot PRs skip Reviewer. Auto-merge is an
128
+ optional later consumer-side alternative, not this repo's CI. No
129
+ Dependabot merge of hold PRs.
130
+
131
+ No telemetry. No secrets in source or in tool-call examples.
132
+
133
+ ## Layout
134
+
135
+ | Path | What |
136
+ |------|------|
137
+ | `SLOW-BURN.md` | Generic OS: seats, QUEUE class, pipeline, emojis |
138
+ | `ROUTE.md` | Scratch `to:` ids + poll + Orchestrator outbox |
139
+ | `CLONE-POLICY.md` | What the VM may clone; Coordinator `git pull` of `the local scratch-transport clone` |
140
+ | `QUEUE.template.md` | Columns including class + four constraint fields |
141
+ | `TO-COORDINATOR.template.md` | VM append-only log format (not the transport) |
142
+ | `bots/*.md` | Paste-ready profiles. `coordinator.md` is not a Bot |
143
+ | `scripts/route-scratch.py` | `gh api` compare → DELIVER lines |
144
+ | `consumers/` | Per-repo catalog, holds, hours |
145
+ | `docs/RESEARCH.md` | Study-swarm citations + implications |
146
+ | `site/` | Landing + Starlight handbook |
147
+
148
+ ## License
149
+
150
+ MIT.
151
+
152
+ ---
153
+
154
+ Built by <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a>
@@ -0,0 +1,142 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/grok-bot-os/readme.png" width="280" alt="grok-bot-os">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="README.md">English</a> | <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
7
+ </p>
8
+
9
+ # grok-bot-os
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/mcp-tool-shop-org/grok-bot-os/actions"><img src="https://github.com/mcp-tool-shop-org/grok-bot-os/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
+ <a href="https://www.npmjs.com/package/@mcptoolshop/grok-bot-os"><img src="https://img.shields.io/npm/v/@mcptoolshop/grok-bot-os.svg" alt="npm"></a>
14
+ <a href="https://pypi.org/project/grok-bot-os/"><img src="https://img.shields.io/pypi/v/grok-bot-os.svg" alt="PyPI"></a>
15
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"></a>
16
+ <a href="https://mcp-tool-shop-org.github.io/grok-bot-os/"><img src="https://img.shields.io/badge/landing-page-0ea5e9.svg" alt="Landing page"></a>
17
+ </p>
18
+
19
+ Versioned operating system for **Grok Bot** teams. Orchestrator, Builder,
20
+ Reviewer, and Researcher sit in a Grok Bot group chat. **Coordinator** is
21
+ Grok Build on the operator's machine — not a Grok Bot. Work moves through a
22
+ live `QUEUE.md` on the Bot VM. Default cadence: one coding job at a time,
23
+ many in review, Coordinator merges when the human is back. Health A is the
24
+ **default lane**, not the ceiling — Coordinator can open Health B–D and
25
+ named features on this same queue.
26
+
27
+ **Status:** v1.0.0 on PyPI (`grok-bot-os`), npm (`@mcptoolshop/grok-bot-os`), and GitHub Releases.
28
+
29
+ ## What this is not
30
+
31
+ - Not the `testing-os` dogfood-swarm CLI (`swarm init` / 10-phase).
32
+ - Not `swarm-control-plane` (SQLite waves, domain freeze, receipts).
33
+ - Not Role OS. Role OS orchestrates Codex. This OS orchestrates Grok Bots
34
+ on the cloud VM via QUEUE.
35
+
36
+ Stay on **this queue** for one-locus work (Health A default; later classes
37
+ when Coordinator names them). Escalate to swarm-control-plane only when
38
+ the job is **cross-cutting** (frozen domains, wave receipts — Kim 2025 is
39
+ about seats, not a Health A ceiling). Grounding:
40
+ [`docs/RESEARCH.md`](docs/RESEARCH.md).
41
+
42
+ ## Install / use
43
+
44
+ Registries (optional — the live OS is still a git clone on the Bot VM):
45
+
46
+ ```
47
+ pip install grok-bot-os
48
+ npm install @mcptoolshop/grok-bot-os
49
+ ```
50
+
51
+ `pip` installs `route-scratch` and `check-queue-fields`. The Bot VM still
52
+ clones this repo per `CLONE-POLICY.md` and runs `python3 scripts/route-scratch.py`.
53
+
54
+ 1. Clone the OS and the consumer per `CLONE-POLICY.md`. Coordinator
55
+ workstation: a local clone of this repo. Bot VM:
56
+ `/workspace/studio/<repo>` only after a ticket names `owner/repo`.
57
+ Never copy the Coordinator tree onto the VM. Never copy `.swarm`.
58
+ Bots never copy the scratch transport clone.
59
+ 2. The generic OS contract is `SLOW-BURN.md` in this git. Consumer holds
60
+ (PR numbers, hours) live in `consumers/<name>.md`. Do **not** overwrite
61
+ a VM `SLOW-BURN.md` that still carries consumer PR numbers until
62
+ Orchestrator is reading **both** git files.
63
+ 3. Paste **all four** Bot profiles (Name / Title / Description) from
64
+ `bots/orchestrator.md`, `bots/builder.md`, `bots/reviewer.md`, and
65
+ `bots/researcher.md` **before** adding anyone to the group. Then add
66
+ those four to one Grok Bot **group chat** — still four seats, no fifth
67
+ (Kim 2025). Coordinator is not a Bot (`bots/coordinator.md`).
68
+ 4. Seed live `QUEUE.md` on the VM from `QUEUE.template.md` plus the
69
+ consumer file (stop / prerequisite / owner / fallback in full). Live
70
+ QUEUE and `TO-COORDINATOR.md` stay on the VM.
71
+ 5. Run Orchestrator once (clock or `@Orchestrator`) so it pulls consumer
72
+ `main` and assigns the first `ready` row. **The first coding job is an
73
+ Orchestrator @**, never a human paste and never “add Orchestrator after
74
+ idle.”
75
+ 6. Only then may Builder code. Scratch routing: `ROUTE.md` +
76
+ `scripts/route-scratch.py` on the VM. Poll with `gh api` only (no
77
+ `git fetch`). Fail-closed: seed last-sha first; do not advance it on
78
+ exit != 0. Empty stdout after exit 0 = silent. Researcher
79
+ `SendToAgent`s Bot `DELIVER`s only.
80
+
81
+ Coordinator session-open (Grok Build TUI on the operator's machine):
82
+ `git pull` the local scratch-transport clone, then read new
83
+ `to: grok-build` envelopes. Do not paste Bot-group logs.
84
+ `TO-COORDINATOR.md` is the VM append-only log, not the transport. The
85
+ TUI has no inbound ping and no API to Grok Bot. Do not reconstruct
86
+ state from the group transcript (KC 2026).
87
+
88
+ ## QUEUE contract (do not compress)
89
+
90
+ Every QUEUE row, Orchestrator `@`, and scratch ticket carries **stop /
91
+ prerequisite / owner / fallback** in full. **`see SLOW-BURN` is
92
+ forbidden** (Sun 2026). Ready Dependabot / patch rows skip Reviewer.
93
+ Hold / major / behavior still require Reviewer 🛑. Coordinator still
94
+ merges every `coordinator-merge`.
95
+
96
+ Builder does not review itself. Reviewer pass on a mergeable PR is 🛑.
97
+ Merge stays on Grok Build (Panickssery 2024; Kambhampati 2024).
98
+
99
+ Finalize emojis, last line only: ✅ Bot done, no Coordinator · 🔧 rework ·
100
+ 🛑 Coordinator must act.
101
+
102
+ ## Trust model
103
+
104
+ **Touches:** public git playbooks; `gh api` against
105
+ `mcp-tool-shop/rig-bridge-scratch` from an already-authenticated `gh`
106
+ on the VM; markdown QUEUE on the VM.
107
+
108
+ **Does not touch:** npm registry, consumer `main`, Coordinator swarm
109
+ sqlite (`.swarm`, `~\.grok\*.sqlite`), PATs, `auth.json`,
110
+ `mcp_credentials.json`, telemetry, GitHub MCP token fields.
111
+
112
+ **Permissions:** read this repo; `gh api` as `mcp-tool-shop` for scratch
113
+ routing. Bots never merge. Coordinator merges every `coordinator-merge`
114
+ (`--admin` if Actions dark). Reviewer 🛑 is required for hold / major /
115
+ behavior. Ready patch / Dependabot PRs skip Reviewer. Auto-merge is an
116
+ optional later consumer-side alternative, not this repo's CI. No
117
+ Dependabot merge of hold PRs.
118
+
119
+ No telemetry. No secrets in source or in tool-call examples.
120
+
121
+ ## Layout
122
+
123
+ | Path | What |
124
+ |------|------|
125
+ | `SLOW-BURN.md` | Generic OS: seats, QUEUE class, pipeline, emojis |
126
+ | `ROUTE.md` | Scratch `to:` ids + poll + Orchestrator outbox |
127
+ | `CLONE-POLICY.md` | What the VM may clone; Coordinator `git pull` of `the local scratch-transport clone` |
128
+ | `QUEUE.template.md` | Columns including class + four constraint fields |
129
+ | `TO-COORDINATOR.template.md` | VM append-only log format (not the transport) |
130
+ | `bots/*.md` | Paste-ready profiles. `coordinator.md` is not a Bot |
131
+ | `scripts/route-scratch.py` | `gh api` compare → DELIVER lines |
132
+ | `consumers/` | Per-repo catalog, holds, hours |
133
+ | `docs/RESEARCH.md` | Study-swarm citations + implications |
134
+ | `site/` | Landing + Starlight handbook |
135
+
136
+ ## License
137
+
138
+ MIT.
139
+
140
+ ---
141
+
142
+ Built by <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a>
@@ -0,0 +1,32 @@
1
+ """Installed entry points for grok-bot-os (PyPI). Source of truth is scripts/."""
2
+ from __future__ import annotations
3
+
4
+ import importlib.util
5
+ from pathlib import Path
6
+
7
+ __version__ = "1.0.0"
8
+
9
+
10
+ def _load(filename: str):
11
+ here = Path(__file__).resolve().parent
12
+ candidates = (
13
+ here / filename,
14
+ here.parent / "scripts" / filename,
15
+ )
16
+ for path in candidates:
17
+ if path.is_file():
18
+ spec = importlib.util.spec_from_file_location(path.stem.replace("-", "_"), path)
19
+ if spec is None or spec.loader is None:
20
+ continue
21
+ mod = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(mod)
23
+ return mod
24
+ raise FileNotFoundError(filename)
25
+
26
+
27
+ def route_scratch_main() -> int:
28
+ return int(_load("route-scratch.py").main())
29
+
30
+
31
+ def check_queue_fields_main() -> int:
32
+ return int(_load("check_queue_fields.py").main())
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "grok-bot-os"
7
+ version = "1.0.0"
8
+ description = "Operating system for Grok Bot teams: slow-burn Health A queue. Not the testing-os dogfood-swarm CLI."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "mcp-tool-shop" }]
13
+ urls = { Homepage = "https://mcp-tool-shop-org.github.io/grok-bot-os/", Repository = "https://github.com/mcp-tool-shop-org/grok-bot-os" }
14
+
15
+ [project.scripts]
16
+ route-scratch = "grok_bot_os:route_scratch_main"
17
+ check-queue-fields = "grok_bot_os:check_queue_fields_main"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["grok_bot_os"]
21
+
22
+ [tool.hatch.build.targets.wheel.force-include]
23
+ "scripts/route-scratch.py" = "grok_bot_os/route-scratch.py"
24
+ "scripts/check_queue_fields.py" = "grok_bot_os/check_queue_fields.py"
25
+
26
+ [tool.hatch.build.targets.sdist]
27
+ include = [
28
+ "/grok_bot_os",
29
+ "/scripts",
30
+ "/README.md",
31
+ "/LICENSE",
32
+ "/pyproject.toml",
33
+ ]
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env python3
2
+ """Fail-closed four-field gate for QUEUE tables and TO-COORDINATOR list items.
3
+
4
+ Usage: check_queue_fields.py [path ...]
5
+ check_queue_fields.py --list-items [path]
6
+
7
+ Default path is QUEUE.template.md (cwd = repo root). Optional extra paths
8
+ let the VM lint live QUEUE.md / TO-COORDINATOR.md without committing them.
9
+
10
+ Scans only markdown table cells (or `- field:` list items with --list-items
11
+ / auto-detect). Prose such as the template's "see SLOW-BURN" prohibition
12
+ does not self-fail.
13
+
14
+ Does not send, fetch, or write git. No token on argv.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ REQUIRED_FIELDS = ("owner", "stop", "prerequisite", "fallback")
23
+ SEE_SLOW_BURN_RE = re.compile(r"(?i)\bsee\s+slow-burn\b")
24
+ JOB_ID_RE = re.compile(r"^JOB-\S+$", re.I)
25
+ SEP_CELL_RE = re.compile(r"^:?-{3,}:?$")
26
+ LIST_ITEM_RE = re.compile(
27
+ r"^-\s+(stop|prerequisite|owner|fallback)\s*:\s*(.*)$",
28
+ re.I,
29
+ )
30
+ USAGE = "usage: check_queue_fields.py [--list-items] [path ...]"
31
+
32
+
33
+ def fail(code: str, message: str, hint: str, exit_code: int) -> int:
34
+ sys.stderr.write(
35
+ f"code={code} message={message} hint={hint} retryable=false\n"
36
+ )
37
+ return exit_code
38
+
39
+
40
+ def split_row(line: str) -> list[str]:
41
+ line = line.strip()
42
+ if line.startswith("|"):
43
+ line = line[1:]
44
+ if line.endswith("|"):
45
+ line = line[:-1]
46
+ return [cell.strip() for cell in line.split("|")]
47
+
48
+
49
+ def is_separator(line: str) -> bool:
50
+ if "|" not in line:
51
+ return False
52
+ cells = split_row(line)
53
+ if len(cells) < 2:
54
+ return False
55
+ return all(SEP_CELL_RE.fullmatch(cell.replace(" ", "")) for cell in cells)
56
+
57
+
58
+ def parse_tables(text: str) -> list[tuple[list[str], list[list[str]]]]:
59
+ """Return (headers, data_rows) for each markdown table. Ignore prose."""
60
+ lines = text.splitlines()
61
+ tables: list[tuple[list[str], list[list[str]]]] = []
62
+ i = 0
63
+ while i < len(lines):
64
+ line = lines[i]
65
+ if "|" not in line or is_separator(line):
66
+ i += 1
67
+ continue
68
+ if i + 1 >= len(lines) or not is_separator(lines[i + 1]):
69
+ i += 1
70
+ continue
71
+ headers = split_row(line)
72
+ rows: list[list[str]] = []
73
+ i += 2
74
+ while i < len(lines):
75
+ row_line = lines[i]
76
+ if "|" not in row_line or is_separator(row_line):
77
+ break
78
+ rows.append(split_row(row_line))
79
+ i += 1
80
+ tables.append((headers, rows))
81
+ return tables
82
+
83
+
84
+ def _header_names(headers: list[str]) -> list[str]:
85
+ names: list[str] = []
86
+ for raw in headers:
87
+ name = raw.strip().strip("*`").lower()
88
+ names.append(name)
89
+ return names
90
+
91
+
92
+ def check_queue_text(text: str, path: str = "") -> list[str]:
93
+ """Errors for QUEUE markdown tables. Empty list = pass.
94
+
95
+ Does not scan prose outside table cells.
96
+ """
97
+ label = path or "<queue>"
98
+ errors: list[str] = []
99
+ tables = parse_tables(text)
100
+ queue_tables = []
101
+ for headers, rows in tables:
102
+ names = _header_names(headers)
103
+ if "id" in names or any(field in names for field in REQUIRED_FIELDS):
104
+ queue_tables.append((names, rows))
105
+ if not queue_tables:
106
+ errors.append(f"{label}: no QUEUE markdown table")
107
+ return errors
108
+
109
+ for names, rows in queue_tables:
110
+ missing = [field for field in REQUIRED_FIELDS if field not in names]
111
+ if missing:
112
+ for field in missing:
113
+ errors.append(f"{label}: header missing {field}")
114
+ continue
115
+ index = {name: i for i, name in enumerate(names)}
116
+ id_i = index.get("id", 0)
117
+ for row in rows:
118
+ if not row:
119
+ continue
120
+ job_id = row[id_i].strip() if id_i < len(row) else ""
121
+ if not JOB_ID_RE.match(job_id):
122
+ continue
123
+ for field in REQUIRED_FIELDS:
124
+ fi = index[field]
125
+ cell = row[fi].strip() if fi < len(row) else ""
126
+ if not cell:
127
+ errors.append(f"{label}: {job_id} empty {field}")
128
+ elif SEE_SLOW_BURN_RE.search(cell):
129
+ errors.append(
130
+ f"{label}: {job_id} compressed {field}"
131
+ )
132
+ return errors
133
+
134
+
135
+ def check_list_items(
136
+ text: str,
137
+ path: str = "",
138
+ require_all: bool = False,
139
+ ) -> list[str]:
140
+ """Errors for `- stop:` / `- prerequisite:` / `- owner:` / `- fallback:` items.
141
+
142
+ Missing fields are ignored unless require_all is True, so a TO-COORDINATOR
143
+ template that has not yet grown an owner line does not self-fail. Present
144
+ items must be non-empty and uncompressed.
145
+ """
146
+ label = path or "<list>"
147
+ errors: list[str] = []
148
+ found = {field: 0 for field in REQUIRED_FIELDS}
149
+ for raw in text.splitlines():
150
+ match = LIST_ITEM_RE.match(raw.strip())
151
+ if not match:
152
+ continue
153
+ field = match.group(1).lower()
154
+ value = match.group(2).strip()
155
+ found[field] += 1
156
+ if not value:
157
+ errors.append(f"{label}: empty list item {field}")
158
+ elif SEE_SLOW_BURN_RE.search(value):
159
+ errors.append(f"{label}: compressed list item {field}")
160
+ if require_all:
161
+ for field in REQUIRED_FIELDS:
162
+ if found[field] == 0:
163
+ errors.append(f"{label}: missing list item {field}")
164
+ return errors
165
+
166
+
167
+ def looks_like_table(text: str) -> bool:
168
+ lines = text.splitlines()
169
+ for i, line in enumerate(lines):
170
+ if "|" in line and i + 1 < len(lines) and is_separator(lines[i + 1]):
171
+ return True
172
+ return False
173
+
174
+
175
+ def check_path(path: str | Path, list_items: bool | None = None) -> list[str]:
176
+ p = Path(path)
177
+ if not p.is_file():
178
+ return [f"{p}: not a file"]
179
+ text = p.read_text(encoding="utf-8")
180
+ if list_items is True:
181
+ return check_list_items(text, str(p))
182
+ if list_items is False:
183
+ return check_queue_text(text, str(p))
184
+ if looks_like_table(text):
185
+ return check_queue_text(text, str(p))
186
+ return check_list_items(text, str(p))
187
+
188
+
189
+ def main() -> int:
190
+ argv = sys.argv[1:]
191
+ list_items: bool | None = None
192
+ paths: list[str] = []
193
+ for tok in argv:
194
+ if tok in ("--list-items", "--coordinator"):
195
+ list_items = True
196
+ elif tok in ("--table", "--queue"):
197
+ list_items = False
198
+ elif tok in ("-h", "--help"):
199
+ return fail("INPUT_USAGE", USAGE, "optional path; default QUEUE.template.md", 2)
200
+ elif tok.startswith("-"):
201
+ return fail("INPUT_USAGE", f"unknown flag {tok}", USAGE, 2)
202
+ else:
203
+ paths.append(tok)
204
+ if not paths:
205
+ paths = ["QUEUE.template.md"]
206
+
207
+ errors: list[str] = []
208
+ missing_files = []
209
+ for path in paths:
210
+ p = Path(path)
211
+ if not p.is_file():
212
+ missing_files.append(path)
213
+ continue
214
+ errors.extend(check_path(p, list_items=list_items))
215
+ if missing_files:
216
+ return fail(
217
+ "INPUT_USAGE",
218
+ "missing: " + ", ".join(missing_files),
219
+ "pass a QUEUE.md path; live queue is not in git",
220
+ 2,
221
+ )
222
+ if errors:
223
+ for message in errors:
224
+ sys.stderr.write(
225
+ "code=QUEUE_FIELDS "
226
+ f"message={message} "
227
+ "hint=JOB-* rows need uncompressed owner/stop/prerequisite/fallback "
228
+ "retryable=false\n"
229
+ )
230
+ return 1
231
+ return 0
232
+
233
+
234
+ if __name__ == "__main__":
235
+ raise SystemExit(main())