State Enforcement (v2.0):
- .state file as single source of truth for task phase
- Approval gates for research, decomposition, design, test_design
- status.py --transition refuses illegal phase transitions
- status.py --validate-folder detects out-of-order artifacts
- status.py --audit checks all tasks for violations
- status.py --create-task is the only valid way to create tasks
- Pre-v2.0 tasks without .state are UNTRACKED -- all commands refuse them
- New --upgrade command bootstraps .state files for existing tasks
Project Scoping:
- --project flag added to all status.py commands across 16+ files
- _find_project_dir errors instead of silently falling back to ~/.automaton/
- --scope-check marks framework files OUT_OF_SCOPE when working on a project
- Dashboard handlers use stored project_root instead of re-detecting from CWD
- Prompts reference ~/.automaton/scripts/vram_detect.py (not {project}/.automaton/)
Harness Integration:
- status.py --can-edit now supports project-level checks (no --task required)
- --can-edit --file checks file scope without --task
- --json output for machine-readable harness integration
- opencode plugin (plugins/automaton-guard/plugin.ts) intercepts edit/write
- Git pre-commit hook (scripts/git-hooks/pre-commit) blocks commits without task
- Formal integration contract (contracts/harness-integration.md)
Other:
- upgrade.sh delegates to status.py --upgrade instead of manual heuristics
- Phase prompts reference --project {project} for multi-project scoping
- 200 tests passing (14 new)
This commit is contained in:
@@ -9,10 +9,13 @@ ROOT = Path(__file__).resolve().parent.parent
|
||||
PROMPTS_DIR = ROOT / "prompts"
|
||||
TEMPLATES_DIR = ROOT / "templates"
|
||||
|
||||
# Legacy path pattern that should no longer appear.
|
||||
# Legacy path pattern that should no longer appear (with placeholder).
|
||||
LEGACY_PATH = re.compile(r"\{project\}/tasks/\{task-name\}/")
|
||||
# Canonical path pattern that should be used instead.
|
||||
CANONICAL_PATH = re.compile(r"\{project\}/\.automaton/tasks/\{task-name\}/")
|
||||
# Concrete legacy path pattern: {project}/tasks/ followed by any name.
|
||||
# Catches paths like {project}/tasks/onboarding/ that use concrete names.
|
||||
CONCRETE_LEGACY_PATH = re.compile(r"\{project\}/tasks/[\w-]+/")
|
||||
|
||||
|
||||
def _markdown_files(*directories: Path) -> list[Path]:
|
||||
@@ -34,6 +37,32 @@ def test_no_legacy_task_paths(path: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", _markdown_files(PROMPTS_DIR, TEMPLATES_DIR))
|
||||
def test_no_concrete_legacy_task_paths(path: Path) -> None:
|
||||
"""Every prompt/template must not use {project}/tasks/ with concrete task names."""
|
||||
text = path.read_text(encoding="utf-8")
|
||||
# Exclude the migration detection reference, which legitimately mentions the deprecated path.
|
||||
concrete_matches = CONCRETE_LEGACY_PATH.findall(text)
|
||||
# Filter out the onboarding migration detection section that references the legacy path.
|
||||
if concrete_matches and "Migration Check" in text:
|
||||
lines = text.splitlines()
|
||||
filtered = []
|
||||
in_migration = False
|
||||
for line in lines:
|
||||
if "Migration Check" in line or "Migration Detection" in line:
|
||||
in_migration = True
|
||||
if in_migration and line.startswith("#") and "Migration" not in line:
|
||||
in_migration = False
|
||||
if not in_migration:
|
||||
for m in CONCRETE_LEGACY_PATH.findall(line):
|
||||
filtered.append(m)
|
||||
concrete_matches = filtered
|
||||
assert not concrete_matches, (
|
||||
f"Found concrete legacy task path in {path.relative_to(ROOT)}: {concrete_matches}\n"
|
||||
"Use {project}/.automaton/tasks/ instead of {project}/tasks/."
|
||||
)
|
||||
|
||||
|
||||
def test_canonical_path_present_in_prompts() -> None:
|
||||
"""At least one prompt uses the canonical path (sanity check)."""
|
||||
found = False
|
||||
|
||||
Reference in New Issue
Block a user