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"
|
SKILL_DIR = ROOT / ".claude/skills/job-application-assistant"
|
||||||
FRAMEWORK_FILES = sorted(SKILL_DIR.glob("*.md"))
|
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]:
|
def run_git(args: list[str]) -> tuple[int, str, str]:
|
||||||
res = subprocess.run(["git"] + args, cwd=str(ROOT), capture_output=True, text=True)
|
res = subprocess.run(["git"] + args, cwd=str(ROOT), capture_output=True, text=True)
|
||||||
return res.returncode, res.stdout, res.stderr
|
return res.returncode, res.stdout, res.stderr
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent.parent
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
SKILL_DIR = ".claude/skills/job-application-assistant"
|
|
||||||
FRAMEWORK_FILES = [
|
FRAMEWORK_FILES = [
|
||||||
"01-candidate-profile.md",
|
".claude/skills/job-application-assistant/01-candidate-profile.md",
|
||||||
"02-behavioral-profile.md",
|
".claude/skills/job-application-assistant/02-behavioral-profile.md",
|
||||||
"03-writing-style.md",
|
".claude/skills/job-application-assistant/03-writing-style.md",
|
||||||
"04-job-evaluation.md",
|
".claude/skills/job-application-assistant/04-job-evaluation.md",
|
||||||
"05-cv-templates.md",
|
".claude/skills/job-application-assistant/05-cv-templates.md",
|
||||||
"06-cover-letter-templates.md",
|
".claude/skills/job-application-assistant/06-cover-letter-templates.md",
|
||||||
"07-interview-prep.md",
|
".claude/skills/job-application-assistant/07-interview-prep.md",
|
||||||
"SKILL.md",
|
".claude/skills/job-application-assistant/SKILL.md",
|
||||||
|
"AGENTS.md",
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_git(args: list[str]) -> tuple[int, str, str]:
|
def run_git(args: list[str]) -> tuple[int, str, str]:
|
||||||
@@ -94,10 +94,10 @@ def main() -> int:
|
|||||||
updates_available = []
|
updates_available = []
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
for filename in FRAMEWORK_FILES:
|
for rel_path in FRAMEWORK_FILES:
|
||||||
local_path = ROOT / SKILL_DIR / filename
|
local_path = ROOT / rel_path
|
||||||
if not local_path.exists():
|
if not local_path.exists():
|
||||||
print(f"Local file missing: {SKILL_DIR}/{filename}")
|
print(f"Local file missing: {rel_path}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get local version
|
# Get local version
|
||||||
@@ -105,7 +105,7 @@ def main() -> int:
|
|||||||
local_ver = get_framework_version_from_text(local_text)
|
local_ver = get_framework_version_from_text(local_text)
|
||||||
|
|
||||||
# Get upstream version
|
# 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:
|
if rc != 0:
|
||||||
# File might not exist upstream yet
|
# File might not exist upstream yet
|
||||||
continue
|
continue
|
||||||
@@ -113,19 +113,20 @@ def main() -> int:
|
|||||||
upstream_ver = get_framework_version_from_text(upstream_text)
|
upstream_ver = get_framework_version_from_text(upstream_text)
|
||||||
|
|
||||||
if not local_ver:
|
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
|
continue
|
||||||
if not upstream_ver:
|
if not upstream_ver:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if parse_semver(upstream_ver) > parse_semver(local_ver):
|
if parse_semver(upstream_ver) > parse_semver(local_ver):
|
||||||
updates_available.append({
|
updates_available.append({
|
||||||
"filename": filename,
|
"filename": Path(rel_path).name,
|
||||||
"local": local_ver,
|
"local": local_ver,
|
||||||
"upstream": upstream_ver,
|
"upstream": upstream_ver,
|
||||||
"path": f"{SKILL_DIR}/{filename}"
|
"path": rel_path
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
print("Configuration errors:")
|
print("Configuration errors:")
|
||||||
for err in errors:
|
for err in errors:
|
||||||
|
|||||||
Reference in New Issue
Block a user