mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
feat(agents-config): add root AGENTS.md thin-pointer specification (#159)
Root AGENTS.md pointer file per the architecture decision in discussion #78: documents both config roots (.agents/skills/ portable portal skills, .claude/ orchestration) and the profile entry points, carries a framework_version marker registered in both version tools. Design case made by @erikpr1994 in the #78 architecture thread; implementation by @jovin-nicholas.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
framework_version: 1.0.0
|
||||
---
|
||||
|
||||
# Agent Guidelines: AI Job Search
|
||||
|
||||
This workspace is structured to manage job search activities, scraper tools, CVs, cover letters, and interview preparation.
|
||||
|
||||
## Thin-Pointer Design (Single Source of Truth)
|
||||
|
||||
To prevent duplication and configuration drift across different AI agent frameworks (Claude Code, Google Antigravity, Codex, Cursor, Gemini CLI, etc.), this workspace uses a unified thin-pointer design. All agent runtimes should load the canonical specifications and candidate profiles from the files and directories below:
|
||||
|
||||
1. **Personal Candidate Profile:**
|
||||
- The candidate profile, contact details, education, and target preferences are defined in [CLAUDE.md](CLAUDE.md) and the individual profile methodology files under [.claude/skills/job-application-assistant/](.claude/skills/job-application-assistant/) (specifically `01-*.md` etc.).
|
||||
2. **Canonical Workflow Specifications:**
|
||||
- The step-by-step instructions and triggers for tasks (setup, scrape, rank, apply, upskill, interview) are defined in the [.claude/](.claude/) directory (specifically under `.claude/skills/` and `.claude/commands/`).
|
||||
- Do not duplicate these rules or specifications. Treat `.claude/` files as the single source of truth.
|
||||
3. **Portal Search Skills:**
|
||||
- Job-portal search CLIs live under [.agents/skills/](.agents/skills/) in the portable Agent Skills format (with a `SKILL.md` per portal). Codex and Antigravity discover these automatically; the `/scrape` workflow in [.claude/skills/job-scraper/](.claude/skills/job-scraper/) orchestrates them.
|
||||
@@ -17,6 +17,11 @@ ROOT = Path(__file__).resolve().parent.parent
|
||||
SKILL_DIR = ROOT / ".claude/skills/job-application-assistant"
|
||||
FRAMEWORK_FILES = sorted(SKILL_DIR.glob("*.md"))
|
||||
|
||||
# Add root AGENTS.md if it exists
|
||||
root_agents = ROOT / "AGENTS.md"
|
||||
if root_agents.exists():
|
||||
FRAMEWORK_FILES.append(root_agents)
|
||||
|
||||
def run_git(args: list[str]) -> tuple[int, str, str]:
|
||||
res = subprocess.run(["git"] + args, cwd=str(ROOT), capture_output=True, text=True)
|
||||
return res.returncode, res.stdout, res.stderr
|
||||
|
||||
@@ -20,16 +20,16 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SKILL_DIR = ".claude/skills/job-application-assistant"
|
||||
FRAMEWORK_FILES = [
|
||||
"01-candidate-profile.md",
|
||||
"02-behavioral-profile.md",
|
||||
"03-writing-style.md",
|
||||
"04-job-evaluation.md",
|
||||
"05-cv-templates.md",
|
||||
"06-cover-letter-templates.md",
|
||||
"07-interview-prep.md",
|
||||
"SKILL.md",
|
||||
".claude/skills/job-application-assistant/01-candidate-profile.md",
|
||||
".claude/skills/job-application-assistant/02-behavioral-profile.md",
|
||||
".claude/skills/job-application-assistant/03-writing-style.md",
|
||||
".claude/skills/job-application-assistant/04-job-evaluation.md",
|
||||
".claude/skills/job-application-assistant/05-cv-templates.md",
|
||||
".claude/skills/job-application-assistant/06-cover-letter-templates.md",
|
||||
".claude/skills/job-application-assistant/07-interview-prep.md",
|
||||
".claude/skills/job-application-assistant/SKILL.md",
|
||||
"AGENTS.md",
|
||||
]
|
||||
|
||||
def run_git(args: list[str]) -> tuple[int, str, str]:
|
||||
@@ -94,10 +94,10 @@ def main() -> int:
|
||||
updates_available = []
|
||||
errors = []
|
||||
|
||||
for filename in FRAMEWORK_FILES:
|
||||
local_path = ROOT / SKILL_DIR / filename
|
||||
for rel_path in FRAMEWORK_FILES:
|
||||
local_path = ROOT / rel_path
|
||||
if not local_path.exists():
|
||||
print(f"Local file missing: {SKILL_DIR}/{filename}")
|
||||
print(f"Local file missing: {rel_path}")
|
||||
continue
|
||||
|
||||
# Get local version
|
||||
@@ -105,7 +105,7 @@ def main() -> int:
|
||||
local_ver = get_framework_version_from_text(local_text)
|
||||
|
||||
# Get upstream version
|
||||
rc, upstream_text, _ = run_git(["show", f"{ref}:{SKILL_DIR}/{filename}"])
|
||||
rc, upstream_text, _ = run_git(["show", f"{ref}:{rel_path}"])
|
||||
if rc != 0:
|
||||
# File might not exist upstream yet
|
||||
continue
|
||||
@@ -113,19 +113,20 @@ def main() -> int:
|
||||
upstream_ver = get_framework_version_from_text(upstream_text)
|
||||
|
||||
if not local_ver:
|
||||
errors.append(f"Local file {filename} is missing 'framework_version' in frontmatter.")
|
||||
errors.append(f"Local file {rel_path} is missing 'framework_version' in frontmatter.")
|
||||
continue
|
||||
if not upstream_ver:
|
||||
continue
|
||||
|
||||
if parse_semver(upstream_ver) > parse_semver(local_ver):
|
||||
updates_available.append({
|
||||
"filename": filename,
|
||||
"filename": Path(rel_path).name,
|
||||
"local": local_ver,
|
||||
"upstream": upstream_ver,
|
||||
"path": f"{SKILL_DIR}/{filename}"
|
||||
"path": rel_path
|
||||
})
|
||||
|
||||
|
||||
if errors:
|
||||
print("Configuration errors:")
|
||||
for err in errors:
|
||||
|
||||
Reference in New Issue
Block a user