mirror of
https://github.com/gnekt/My-Brain-Is-Full-Crew.git
synced 2026-08-31 04:25:36 +00:00
Rebuilt from scratch following Codex CLI's actual architecture (as outlined in the review on PR #26). Closes the integration gap with a proper build adapter, correct install paths, TOML agent files, and all the architectural differences documented and tested. What changed vs the previous attempt (PR #26): - Agents: build adapter generates .toml files (name/description/ developer_instructions) into dist/codex-cli/.codex/agents/ instead of copying .md files with sed transforms - Skills: installed to .agents/skills/ (correct Codex discovery path) instead of .codex/skills/ - Dispatcher: AGENTS.md uses a root-context orchestration header that works within agents.max_depth=1 constraints; named-agent routing replaced with embedded-instructions workaround for the known spawn_agents limitation (openai/codex#15250) - Tool compat: AskUserQuestion and request_user_input removed; all prompts adapted to Codex's actual tool set and approval/confirmation flow - Installer/updater: launchme.sh --platform codex-cli and updateme.sh with Codex auto-detection, creating the correct split layout (AGENTS.md + .codex/agents/ + .codex/config.toml + .agents/skills/) - Tests: new per-adapter test suite (tests/adapters/codex-cli/), install/update smoke (tests/scripts/codex-cli-install.test.sh), and a four-platform parity gate that proves Codex changes do not regress Claude Code, Gemini CLI, or OpenCode - Docs: new codex-cli.md guide, codex-migration.md for users switching from other platforms, and README/getting-started/examples updated for four-platform positioning - Bash harness: .gitattributes added to enforce LF on .sh files; harness LF-normalized so tests/run.sh works on Windows checkouts - .gitignore: .planning/ added (internal GSD workflow artifacts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
tests/support/toml_smoke_check.py — Validate TOML files generated by Phase 1 adapters.
|
|
|
|
Usage:
|
|
python tests/support/toml_smoke_check.py path/to/file.toml
|
|
|
|
Exit 0 if the file parses under the subset of TOML features used in Phase 1:
|
|
- Standard [table] headers
|
|
- Quoted table names: [mcp_servers."Google Calendar"]
|
|
- key = "string"
|
|
- key = true / false
|
|
- key = ["a", "b"] (array of strings)
|
|
- env = { KEY = "VALUE" } (inline table)
|
|
- Comments (# ...)
|
|
- Blank lines
|
|
- key = '''...''' (multiline literal string, any content)
|
|
- key = \"\"\"...\"\"\" (multiline basic string, any content)
|
|
|
|
Exit 1 with a clear error message if parsing fails.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Token patterns for Phase 1 TOML subset
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Blank line or comment
|
|
RE_BLANK = re.compile(r'^\s*(#.*)?$')
|
|
|
|
# Standard table header: [agents] or [mcp_servers.Gmail]
|
|
RE_TABLE = re.compile(r'^\[([A-Za-z0-9_.-]+)\]\s*(#.*)?$')
|
|
|
|
# TOML basic string content used inside quoted table keys for this test suite.
|
|
# The generated keys only need literal characters plus escaped quote/backslash.
|
|
# Keeping the subset narrow helps catch accidental raw backslashes such as
|
|
# [mcp_servers."foo\bar"], which would change the key meaning.
|
|
_TOML_ESCAPE = r'\\(?:["\\])'
|
|
_TOML_SAFE_CHAR = r'[^"\\]'
|
|
_TOML_QSTR = rf'(?:{_TOML_SAFE_CHAR}|{_TOML_ESCAPE})*'
|
|
|
|
# Quoted table header: [mcp_servers."Google Calendar"]
|
|
RE_TABLE_QUOTED = re.compile(
|
|
rf'^\[([A-Za-z0-9_.-]+"{_TOML_QSTR}")\]\s*(#.*)?$'
|
|
)
|
|
|
|
# key = "string"
|
|
RE_KEY_STRING = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*"([^"\\]|\\.)*"\s*(#.*)?$')
|
|
|
|
# key = true or key = false
|
|
RE_KEY_BOOL = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*(true|false)\s*(#.*)?$')
|
|
|
|
# key = integer
|
|
RE_KEY_INT = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*-?\d+\s*(#.*)?$')
|
|
|
|
# key = ["a", "b", ...] (array of strings — may span a single line only)
|
|
RE_KEY_ARRAY = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*\[.*\]\s*(#.*)?$')
|
|
|
|
# key = { KEY = "VALUE", ... } (inline table — single line)
|
|
RE_KEY_INLINE_TABLE = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*\{.*\}\s*(#.*)?$')
|
|
|
|
# key = ''' (start of multiline literal string)
|
|
RE_MULTILINE_LIT_START = re.compile(r"^[A-Za-z0-9_.-]+\s*=\s*'''")
|
|
|
|
# key = """ (start of multiline basic string)
|
|
RE_MULTILINE_BASIC_START = re.compile(r'^[A-Za-z0-9_.-]+\s*=\s*"""')
|
|
|
|
# Closing delimiter for multiline literal: line that is exactly ''' or ends with '''
|
|
RE_MULTILINE_LIT_END = re.compile(r"'''$")
|
|
|
|
# Closing delimiter for multiline basic: line that is exactly """ or ends with """
|
|
RE_MULTILINE_BASIC_END = re.compile(r'"""$')
|
|
|
|
# Collect all line-level patterns (order matters — most specific first)
|
|
LINE_PATTERNS = [
|
|
RE_BLANK,
|
|
RE_TABLE_QUOTED,
|
|
RE_TABLE,
|
|
RE_KEY_STRING,
|
|
RE_KEY_BOOL,
|
|
RE_KEY_INT,
|
|
RE_KEY_ARRAY,
|
|
RE_KEY_INLINE_TABLE,
|
|
RE_MULTILINE_LIT_START,
|
|
RE_MULTILINE_BASIC_START,
|
|
]
|
|
|
|
|
|
def check_file(path: str) -> None:
|
|
try:
|
|
with open(path, encoding='utf-8') as fh:
|
|
lines = fh.readlines()
|
|
except FileNotFoundError:
|
|
print(f"ERROR: File not found: {path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
except OSError as exc:
|
|
print(f"ERROR: Cannot read {path}: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
errors = []
|
|
in_multiline_lit = False # inside ''' ... '''
|
|
in_multiline_basic = False # inside """ ... """
|
|
|
|
for lineno, raw in enumerate(lines, start=1):
|
|
line = raw.rstrip('\n')
|
|
|
|
# Inside a multiline literal string: any content is valid until '''
|
|
if in_multiline_lit:
|
|
if RE_MULTILINE_LIT_END.search(line):
|
|
in_multiline_lit = False
|
|
# All lines inside a multiline literal are valid — no error
|
|
continue
|
|
|
|
# Inside a multiline basic string: any content is valid until """
|
|
if in_multiline_basic:
|
|
if RE_MULTILINE_BASIC_END.search(line):
|
|
in_multiline_basic = False
|
|
# All lines inside a multiline basic string are valid — no error
|
|
continue
|
|
|
|
# Check if this line opens a multiline string
|
|
if RE_MULTILINE_LIT_START.match(line):
|
|
# Check if it's also closed on the same line (inline)
|
|
rest = re.sub(r"^[A-Za-z0-9_.-]+\s*=\s*'''", "", line)
|
|
if rest.endswith("'''"):
|
|
pass # Single-line multiline literal (unusual but valid)
|
|
else:
|
|
in_multiline_lit = True
|
|
continue
|
|
|
|
if RE_MULTILINE_BASIC_START.match(line):
|
|
rest = re.sub(r'^[A-Za-z0-9_.-]+\s*=\s*"""', "", line)
|
|
if rest.endswith('"""'):
|
|
pass # Single-line multiline basic (unusual but valid)
|
|
else:
|
|
in_multiline_basic = True
|
|
continue
|
|
|
|
matched = any(pat.match(line) for pat in LINE_PATTERNS)
|
|
if not matched:
|
|
errors.append(f" line {lineno}: unexpected syntax: {line!r}")
|
|
|
|
if in_multiline_lit:
|
|
errors.append(" Unterminated multiline literal string (''' not closed)")
|
|
if in_multiline_basic:
|
|
errors.append(' Unterminated multiline basic string (""" not closed)')
|
|
|
|
if errors:
|
|
print(f"TOML parse error in {path}:", file=sys.stderr)
|
|
for err in errors:
|
|
print(err, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"OK: {path}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 2:
|
|
print(f"Usage: {sys.argv[0]} <file.toml>", file=sys.stderr)
|
|
sys.exit(2)
|
|
check_file(sys.argv[1])
|