Step 1 of the control-marking task, and deliberately server-only: no handle is drawn yet. This project already shipped a range handle for `profile.interval`, which compileGeometry ignores, so the control dragged and changed nothing. The consumer comes first now. Two kinds join the taxonomy — `junction-crosswalk-inset` and `junction-stop-line-offset`, both on the existing `junction-approach` anchor. The solver writes them onto the approach entry, `applyDirectJunctionPlans` carries them onto the compiled approach, and `compileControlMarkings` reads them in place of the module constants it used for every junction. They move markings without reshaping the junction, so unlike width and cutback they deliberately do not trigger a boundary recompute. `applyJunctionConstraint` becomes an explicit switch. Its trailing `else` had meant every kind that was not approach-width fell through to the cutback validator, so a new kind would have been silently validated and written as a cutback. The same non-exhaustive shape in the test fixture's `valueFor` is fixed the same way, and now throws for an unnamed kind rather than answering with a corner radius. design.md's taxonomy is updated with it — a test asserts the two cannot drift, which is what caught the omission. Measured on a 41-road workspace with 8 crossings: both constraints change their marking geometry, neither drags the other, and out-of-range blocks instead of clamping. That measurement is not in the suite: the synthetic junction resolves `junction_inset_m` to 0 because its crossing never binds to a plan, and the committed OSM fixture has no crossings at all. The tests assert the wiring the handles will depend on — values reaching the approach entry, distinct branches, blocking diagnostics — and the gap is recorded in the test itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
191 lines
5.0 KiB
Python
Executable File
191 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Developer management utilities.
|
|
|
|
Provides:
|
|
init_developer - Initialize developer
|
|
ensure_developer - Ensure developer is initialized (exit if not)
|
|
show_developer_info - Show developer information
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from .paths import (
|
|
DIR_WORKFLOW,
|
|
DIR_WORKSPACE,
|
|
DIR_TASKS,
|
|
FILE_DEVELOPER,
|
|
FILE_JOURNAL_PREFIX,
|
|
get_repo_root,
|
|
get_developer,
|
|
check_developer,
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# Developer Initialization
|
|
# =============================================================================
|
|
|
|
def init_developer(name: str, repo_root: Path | None = None) -> bool:
|
|
"""Initialize developer.
|
|
|
|
Creates:
|
|
- .trellis/.developer file with developer info
|
|
- .trellis/workspace/<name>/ directory structure
|
|
- Initial journal file and index.md
|
|
|
|
Args:
|
|
name: Developer name.
|
|
repo_root: Repository root path. Defaults to auto-detected.
|
|
|
|
Returns:
|
|
True on success, False on error.
|
|
"""
|
|
if not name:
|
|
print("Error: developer name is required", file=sys.stderr)
|
|
return False
|
|
|
|
if repo_root is None:
|
|
repo_root = get_repo_root()
|
|
|
|
dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
|
|
workspace_dir = repo_root / DIR_WORKFLOW / DIR_WORKSPACE / name
|
|
|
|
# Create .developer file
|
|
initialized_at = datetime.now().isoformat()
|
|
try:
|
|
dev_file.write_text(
|
|
f"name={name}\ninitialized_at={initialized_at}\n",
|
|
encoding="utf-8"
|
|
)
|
|
except (OSError, IOError) as e:
|
|
print(f"Error: Failed to create .developer file: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
# Create workspace directory structure
|
|
try:
|
|
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
except (OSError, IOError) as e:
|
|
print(f"Error: Failed to create workspace directory: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
# Create initial journal file
|
|
journal_file = workspace_dir / f"{FILE_JOURNAL_PREFIX}1.md"
|
|
if not journal_file.exists():
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
journal_content = f"""# Journal - {name} (Part 1)
|
|
|
|
> AI development session journal
|
|
> Started: {today}
|
|
|
|
---
|
|
|
|
"""
|
|
try:
|
|
journal_file.write_text(journal_content, encoding="utf-8")
|
|
except (OSError, IOError) as e:
|
|
print(f"Error: Failed to create journal file: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
# Create index.md with markers for auto-update
|
|
index_file = workspace_dir / "index.md"
|
|
if not index_file.exists():
|
|
index_content = f"""# Workspace Index - {name}
|
|
|
|
> Journal tracking for AI development sessions.
|
|
|
|
---
|
|
|
|
## Current Status
|
|
|
|
<!-- @@@auto:current-status -->
|
|
- **Active File**: `journal-1.md`
|
|
- **Total Sessions**: 0
|
|
- **Last Active**: -
|
|
<!-- @@@/auto:current-status -->
|
|
|
|
---
|
|
|
|
## Active Documents
|
|
|
|
<!-- @@@auto:active-documents -->
|
|
| File | Lines | Status |
|
|
|------|-------|--------|
|
|
| `journal-1.md` | ~0 | Active |
|
|
<!-- @@@/auto:active-documents -->
|
|
|
|
---
|
|
|
|
## Session History
|
|
|
|
<!-- @@@auto:session-history -->
|
|
| # | Date | Title | Commits | Branch |
|
|
|---|------|-------|---------|--------|
|
|
<!-- @@@/auto:session-history -->
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- Sessions are appended to journal files
|
|
- New journal file created when current exceeds 2000 lines
|
|
- Use `add_session.py` to record sessions
|
|
"""
|
|
try:
|
|
index_file.write_text(index_content, encoding="utf-8")
|
|
except (OSError, IOError) as e:
|
|
print(f"Error: Failed to create index.md: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
print(f"Developer initialized: {name}")
|
|
print(f" .developer file: {dev_file}")
|
|
print(f" Workspace dir: {workspace_dir}")
|
|
|
|
return True
|
|
|
|
|
|
def ensure_developer(repo_root: Path | None = None) -> None:
|
|
"""Ensure developer is initialized, exit if not.
|
|
|
|
Args:
|
|
repo_root: Repository root path. Defaults to auto-detected.
|
|
"""
|
|
if repo_root is None:
|
|
repo_root = get_repo_root()
|
|
|
|
if not check_developer(repo_root):
|
|
print("Error: Developer not initialized.", file=sys.stderr)
|
|
print(f"Run: python3 ./{DIR_WORKFLOW}/scripts/init_developer.py <your-name>", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def show_developer_info(repo_root: Path | None = None) -> None:
|
|
"""Show developer information.
|
|
|
|
Args:
|
|
repo_root: Repository root path. Defaults to auto-detected.
|
|
"""
|
|
if repo_root is None:
|
|
repo_root = get_repo_root()
|
|
|
|
developer = get_developer(repo_root)
|
|
|
|
if not developer:
|
|
print("Developer: (not initialized)")
|
|
else:
|
|
print(f"Developer: {developer}")
|
|
print(f"Workspace: {DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/")
|
|
print(f"Tasks: {DIR_WORKFLOW}/{DIR_TASKS}/")
|
|
|
|
|
|
# =============================================================================
|
|
# Main Entry (for testing)
|
|
# =============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
show_developer_info()
|