diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index 63d5fcd..28e6e52 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -38,15 +38,12 @@ jobs: didn't match the configured pattern. Please ensure that the subject doesn't start with an uppercase character. - # Add comment when PR title is invalid - uses: marocchino/sticky-pull-request-comment@v2 if: always() && (steps.lint_pr_title.outputs.error_message != null) with: header: pr-title-lint-error message: | - ๐Ÿ‘‹ Thank you for opening this pull request! - - We require PR titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/). + PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/). **Error details:** @@ -60,7 +57,6 @@ jobs: Please update your PR title and the check will run automatically. - # Delete comment when PR title is fixed - uses: marocchino/sticky-pull-request-comment@v2 if: ${{ steps.lint_pr_title.outputs.error_message == null }} with: diff --git a/skills/agentsmd-generator/SKILL.md b/skills/agentsmd-generator/SKILL.md index a85e274..352f3f4 100644 --- a/skills/agentsmd-generator/SKILL.md +++ b/skills/agentsmd-generator/SKILL.md @@ -3,27 +3,17 @@ name: agentsmd-generator description: Generate project-level AGENTS.md guides that capture conventions, workflows, and required follow-up tasks. Use when a repository needs clear agent onboarding covering structure, tooling, testing, task flow, README expectations, and conventional commit summaries. license: MIT allowed-tools: Read Write Edit Bash(ls:*) Bash(git:*) Bash(just:*) Bash(make:*) Bash(tree:*) Bash(scripts/repo-inventory:*) -metadata: - generated-at: "2026-01-10T00:00:00Z" - group: "enablement" - category: "documentation" - difficulty: "intermediate" - step-count: "4" --- # Agent Context Generator -## What You'll Do -- ๐Ÿ” Inventory the repository's structure, capture a `.gitignore`-aware `tree` output, and record automation entry points (preferring `just`/`make` tasks when available) -- ๐Ÿงญ Capture coding conventions, directory ownership, testing expectations, and review workflows so future agents can navigate confidently -- ๐Ÿงฉ Produce an `AGENTS.md` file following the opinionated section order below, honoring scope rules for nested directories -- โœ… Embed universal wrap-up tasks: ensure the README is updated after significant code changes and summarize changes per conventional commits while resolving any open questions with the developer +Inventory the repo, capture conventions and workflows, then write `AGENTS.md` in the section order below. Prefer `just`/`make`/`task` entry points. Include wrap-up duties: update the README after significant changes, and summarize work in conventional commits. --- ## Phase 1 ยท Understand the Repository -> **CRITICAL: The codebase is the sole source of truth.** Never trust repo documentation (README, CONTRIBUTING, docs/, etc.) as authoritative. Treat all documentation as potentially stale or wrong. Always validate claims by inspecting actual source files, configs, scripts, CI pipelines, and dependency manifests. When documentation contradicts the code, the code wins. Flag discrepancies for the developer. +The codebase is the source of truth. Treat README, CONTRIBUTING, and other docs as hints that may be stale. Validate claims against source, configs, scripts, CI, and manifests. When docs contradict the code, the code wins โ€” flag the discrepancy. 1. **Run the bundled inventory script first** - Run [`scripts/repo-inventory`](scripts/repo-inventory) from the repo root. It performs the deterministic data-gathering so you don't run a dozen probes by hand: a `.git`/`.jj`-pruned, gitignore-aware `tree` (with `tree --prune` and `git ls-files` fallbacks), detected `languages`/`package_managers`, automation `runners` plus `make_targets`/`just_recipes`, `ci_files`, and `env_files`. @@ -31,9 +21,9 @@ metadata: - This is fact-gathering only; everything below adds the judgment the script cannot infer (ownership, intent, stale-doc reconciliation). 2. **Check for existing AGENTS.md** - Use `glob` or the inventory tree to discover current files. Determine scope inheritance so you can update or extend instead of duplicating. -3. **Skim Docs as Hints Only** - - Skim `README.md`, `CONTRIBUTING.md`, and other onboarding docs for clues about project philosophy, setup, and workflows. - - **Do NOT accept doc claims at face value.** Cross-reference every stated convention, command, tool, or workflow against the actual codebase (and the inventory) before including it in AGENTS.md. +3. **Skim docs as hints** + - Skim `README.md`, `CONTRIBUTING.md`, and other onboarding docs for setup and workflow clues. + - Cross-reference every stated convention, command, or workflow against the codebase (and the inventory) before including it in AGENTS.md. - If `docs/` or `documentation/` exists, scan for references but verify each against the code. 4. **Survey Project Layout** - Start from the inventory `[tree]` and `languages`. Add primary build targets and ownership the script can't infer (e.g., "`src/ui` maintained by Frontend team"). @@ -176,5 +166,3 @@ Only create these per-directory guides after confirming with the developer which - A short summary of major sections added/updated. - Confirmation that README and conventional commit reminders are present. - Any follow-up suggestions (e.g., missing tests or outdated scripts). - -Use this skill whenever a repo lacks AGENTS context or when existing instructions are incomplete or outdated. The goal is to leave future agents with a single, trustworthy map of the project, its tooling, and the expectations for finishing tasks responsibly. diff --git a/skills/agentsmd-generator/scripts/repo-inventory b/skills/agentsmd-generator/scripts/repo-inventory index 9670b48..0c7ad3d 100755 --- a/skills/agentsmd-generator/scripts/repo-inventory +++ b/skills/agentsmd-generator/scripts/repo-inventory @@ -114,8 +114,8 @@ for f in Justfile justfile Makefile makefile GNUmakefile Taskfile.yml Taskfile.y done if [[ -n "$makefile" ]]; then - # Same `## help`-style convention used by this repo's own Makefile. - # `|| true` keeps a no-match `grep` (exit 1, with pipefail) from aborting. + # GNU make `target: ## help` convention. `|| true` keeps a no-match grep + # from aborting under pipefail. make_targets="$(grep -E '^[a-zA-Z0-9_.-]+:.*?## ' "$makefile" 2>/dev/null \ | sed -E 's/^([a-zA-Z0-9_.-]+):.*/\1/' | sort -u | paste -sd, - || true)" fi diff --git a/skills/jj-case-insensitive-clone-fix/scripts/diagnose b/skills/jj-case-insensitive-clone-fix/scripts/diagnose index eca8c75..5349e5b 100755 --- a/skills/jj-case-insensitive-clone-fix/scripts/diagnose +++ b/skills/jj-case-insensitive-clone-fix/scripts/diagnose @@ -38,7 +38,7 @@ case "${1:-}" in exit 0 ;; --stdin) - names="$(cat | awk 'NF')" + names="$(awk 'NF')" ;; "") echo "Error: missing (or --stdin)" >&2 diff --git a/skills/jj-case-insensitive-clone-fix/scripts/jj-clone b/skills/jj-case-insensitive-clone-fix/scripts/jj-clone index a0da47d..e343063 100755 --- a/skills/jj-case-insensitive-clone-fix/scripts/jj-clone +++ b/skills/jj-case-insensitive-clone-fix/scripts/jj-clone @@ -144,9 +144,7 @@ fallback_clone() { clone_log="$(mktemp -t jj-clone.XXXXXX)" trap 'rm -f "$clone_log"' EXIT -if jj git clone "$url" "$target" 2>&1 | tee "$clone_log"; then - : # success -else +if ! jj git clone "$url" "$target" 2>&1 | tee "$clone_log"; then rc=$? if grep -qE 'Failed to update refs:' "$clone_log"; then echo >&2 diff --git a/skills/sdd-linear/scripts/assess-linear-sdd-state.py b/skills/sdd-linear/scripts/assess-linear-sdd-state.py index b47b00b..1dd7ab4 100755 --- a/skills/sdd-linear/scripts/assess-linear-sdd-state.py +++ b/skills/sdd-linear/scripts/assess-linear-sdd-state.py @@ -70,13 +70,7 @@ def _passed(section: dict | None) -> bool: def _subissue_progress(snapshot: dict) -> tuple[bool, int]: - """Return ``(any_exist, incomplete_count)`` for the executable sub-issues. - - Accepts either the compact ``subissue_counts`` summary (authoritative when - present) or the explicit ``subissues`` list. The compact form lets the - sub-agent report a large feature as ``{"total": 50, "terminal": 49}`` - instead of enumerating every sub-issue. - """ + """Return ``(any_exist, incomplete_count)``. ``subissue_counts`` wins if present.""" counts = snapshot.get("subissue_counts") if isinstance(counts, dict) and counts.get("total") is not None: total = int(counts.get("total") or 0) diff --git a/skills/work-breakdown/SKILL.md b/skills/work-breakdown/SKILL.md index d9d2800..0e97123 100644 --- a/skills/work-breakdown/SKILL.md +++ b/skills/work-breakdown/SKILL.md @@ -9,7 +9,7 @@ Analyze the provided features/issues and produce a breakdown of work suitable fo ## Process -1. **Gather context** โ€” Fetch issue details, read relevant source code using the `codebase-exploration` skill and the `research_codebase` skill (for more detailed analysis - when required) +1. **Gather context** โ€” Fetch issue details, read relevant source code; use the `research_codebase` skill when a deeper map is needed 2. **Identify work units** โ€” Break input into discrete, independently deliverable units - these units of work should be independently verifiable, either manually or using automated testing. 3. **Map dependencies** โ€” Which units depend on others? What must be sequenced vs parallelized? 4. **Assess risks** โ€” Flag units of work that touch the same file, projects/repositories, entities, or layers (merge conflict risk, network layer, data layer, etc.) across multiple systems diff --git a/tests/test_assess_linear_sdd_state.py b/tests/test_assess_linear_sdd_state.py index 36d905f..3a62051 100644 --- a/tests/test_assess_linear_sdd_state.py +++ b/tests/test_assess_linear_sdd_state.py @@ -11,7 +11,7 @@ import json -from conftest import requires, run_script +from conftest import run_script SKILL = "sdd-linear" SCRIPT = "assess-linear-sdd-state.py" @@ -29,7 +29,6 @@ def decide(snapshot: dict) -> dict: AUDIT_PASS = {"present": True, "status": "PASS"} -@requires("python3") def test_s1_start_when_no_spec_issue() -> None: result = decide({}) assert result["phase"] == 1 @@ -37,28 +36,24 @@ def test_s1_start_when_no_spec_issue() -> None: assert result["spec"] is None -@requires("python3") def test_s1_questions_when_scratch_questions_file_present() -> None: result = decide({"questions_file_present": True}) assert result["phase"] == 1 assert result["detailed_state"] == "S1_QUESTIONS" -@requires("python3") def test_s1_when_issue_exists_but_description_missing() -> None: result = decide({"spec_issue": {"identifier": "ENG-1", "description_present": False}}) assert result["phase"] == 1 assert result["detailed_state"] == "S1_START" -@requires("python3") def test_s2_start_when_task_list_attachment_missing() -> None: result = decide({"spec_issue": SPEC, "task_list_attachment_present": False}) assert result["phase"] == 2 assert result["detailed_state"] == "S2_START" -@requires("python3") def test_s2_parents_done_when_no_subissues_yet() -> None: result = decide( {"spec_issue": SPEC, "task_list_attachment_present": True, "subissues": []} @@ -67,7 +62,6 @@ def test_s2_parents_done_when_no_subissues_yet() -> None: assert result["detailed_state"] == "S2_PARENTS_DONE" -@requires("python3") def test_s2_subtasks_done_when_audit_missing() -> None: result = decide( { @@ -81,7 +75,6 @@ def test_s2_subtasks_done_when_audit_missing() -> None: assert result["detailed_state"] == "S2_SUBTASKS_DONE" -@requires("python3") def test_s2_audit_failed_on_fail_status() -> None: result = decide( { @@ -95,7 +88,6 @@ def test_s2_audit_failed_on_fail_status() -> None: assert result["detailed_state"] == "S2_AUDIT_FAILED" -@requires("python3") def test_s2_audit_failed_on_unknown_status() -> None: # A present-but-unverified audit must not advance the workflow. result = decide( @@ -110,7 +102,6 @@ def test_s2_audit_failed_on_unknown_status() -> None: assert result["detailed_state"] == "S2_AUDIT_FAILED" -@requires("python3") def test_s3_midflight_when_a_subissue_is_incomplete() -> None: result = decide( { @@ -124,7 +115,6 @@ def test_s3_midflight_when_a_subissue_is_incomplete() -> None: assert result["detailed_state"] == "S3_MIDFLIGHT" -@requires("python3") def test_s4_start_when_all_done_and_validation_missing() -> None: result = decide( { @@ -139,7 +129,6 @@ def test_s4_start_when_all_done_and_validation_missing() -> None: assert result["detailed_state"] == "S4_START" -@requires("python3") def test_s4_failed_when_validation_fails() -> None: result = decide( { @@ -154,7 +143,6 @@ def test_s4_failed_when_validation_fails() -> None: assert result["detailed_state"] == "S4_FAILED" -@requires("python3") def test_s4_complete_when_validation_passes() -> None: result = decide( { @@ -169,7 +157,6 @@ def test_s4_complete_when_validation_passes() -> None: assert result["detailed_state"] == "S4_COMPLETE" -@requires("python3") def test_canceled_subissue_counts_as_terminal() -> None: # A canceled executable task should not block progression to validation. result = decide( @@ -185,10 +172,9 @@ def test_canceled_subissue_counts_as_terminal() -> None: assert result["detailed_state"] == "S4_START" -# --- compact subissue_counts form (avoids enumerating every sub-issue) --- +# Compact subissue_counts form. -@requires("python3") def test_compact_counts_parents_done_when_total_zero() -> None: result = decide( { @@ -201,7 +187,6 @@ def test_compact_counts_parents_done_when_total_zero() -> None: assert result["detailed_state"] == "S2_PARENTS_DONE" -@requires("python3") def test_compact_counts_midflight_when_some_incomplete() -> None: result = decide( { @@ -215,7 +200,6 @@ def test_compact_counts_midflight_when_some_incomplete() -> None: assert result["detailed_state"] == "S3_MIDFLIGHT" -@requires("python3") def test_compact_counts_validation_when_all_terminal() -> None: result = decide( { @@ -230,7 +214,6 @@ def test_compact_counts_validation_when_all_terminal() -> None: assert result["detailed_state"] == "S4_START" -@requires("python3") def test_compact_counts_take_precedence_over_subissues_list() -> None: # When both forms are present, the compact counts are authoritative. result = decide( diff --git a/tests/test_install_linear_agent.py b/tests/test_install_linear_agent.py index b5b63f3..4c8db96 100644 --- a/tests/test_install_linear_agent.py +++ b/tests/test_install_linear_agent.py @@ -9,14 +9,13 @@ from pathlib import Path -from conftest import parse_kv, requires, run_script +from conftest import parse_kv, run_script SKILL = "sdd-linear" SCRIPT = "install-linear-agent.sh" AGENT_FILE = "linear-project-manager.md" -@requires("bash") def test_installs_into_empty_dest(tmp_path: Path) -> None: dest = tmp_path / "agents" @@ -30,7 +29,6 @@ def test_installs_into_empty_dest(tmp_path: Path) -> None: assert "name: linear-project-manager" in target.read_text(encoding="utf-8") -@requires("bash") def test_is_idempotent_and_non_destructive(tmp_path: Path) -> None: dest = tmp_path / "agents" dest.mkdir() @@ -41,11 +39,9 @@ def test_is_idempotent_and_non_destructive(tmp_path: Path) -> None: assert proc.returncode == 0, proc.stderr assert parse_kv(proc.stdout)["status"] == "exists" - # Existing agent is left untouched. assert target.read_text(encoding="utf-8") == "custom user content\n" -@requires("bash") def test_force_overwrites_existing(tmp_path: Path) -> None: dest = tmp_path / "agents" dest.mkdir() diff --git a/tests/test_jj_clone_diagnose.py b/tests/test_jj_clone_diagnose.py index 7bdbada..9ad37f8 100644 --- a/tests/test_jj_clone_diagnose.py +++ b/tests/test_jj_clone_diagnose.py @@ -68,8 +68,7 @@ def test_url_mode_detects_packed_collision(tmp_path: Path) -> None: assert "release" in proc.stdout -def test_jj_clone_help_still_works_after_refactor() -> None: - # jj-clone now sources lib.sh; make sure that wiring didn't break startup. +def test_jj_clone_help() -> None: proc = run_script(SKILL, "jj-clone", "--help") assert proc.returncode == 0 diff --git a/tests/test_repo_inventory.py b/tests/test_repo_inventory.py index 3e4d682..2122668 100644 --- a/tests/test_repo_inventory.py +++ b/tests/test_repo_inventory.py @@ -9,7 +9,7 @@ from pathlib import Path -from conftest import git, init_repo, parse_kv, requires, run_script +from conftest import commit_file, init_repo, parse_kv, run_script SKILL = "agentsmd-generator" SCRIPT = "repo-inventory" @@ -76,7 +76,7 @@ def test_detects_ci_and_env_files(tmp_path: Path) -> None: def test_emits_tree_section(tmp_path: Path) -> None: repo = init_repo(tmp_path / "proj") - (repo / "README.md").write_text("# hi\n", encoding="utf-8") + commit_file(repo, "README.md", "# hi\n", "docs: readme") out = run_script(SKILL, SCRIPT, cwd=repo).stdout diff --git a/tests/test_research_metadata.py b/tests/test_research_metadata.py index 7e2b2c2..b8aa263 100644 --- a/tests/test_research_metadata.py +++ b/tests/test_research_metadata.py @@ -1,9 +1,7 @@ """Behavior tests for the research_codebase `spec_metadata.sh` script. -The script prints the deterministic frontmatter metadata block (date, -researcher, git_commit, branch, repository, last_updated*) for a research -document. It is self-contained and bundled with the skill, replacing the -previously-external `hack/spec_metadata.sh`. +The script prints the frontmatter metadata block (date, researcher, +git_commit, branch, repository, last_updated*) for a research document. """ from __future__ import annotations diff --git a/tests/test_skill_contract.py b/tests/test_skill_contract.py index 121fd95..4649971 100644 --- a/tests/test_skill_contract.py +++ b/tests/test_skill_contract.py @@ -16,7 +16,8 @@ ROOT = Path(__file__).resolve().parents[1] SKILLS_DIR = ROOT / "skills" -# Skills expected to be present in this repository. +# Authored skills that must remain present (vendored ones are covered by +# tests/test_upstream_catalog.py). EXPECTED_SKILLS = { "agentsmd-generator", "bro", @@ -44,10 +45,6 @@ def parse_frontmatter(path: Path) -> dict: return data -def test_skills_directory_exists() -> None: - assert SKILLS_DIR.is_dir(), "skills/ directory must exist" - - def test_expected_skills_present() -> None: found = {p.parent.name for p in skill_md_paths()} missing = EXPECTED_SKILLS - found diff --git a/tests/test_upstream_catalog.py b/tests/test_upstream_catalog.py index 5d41e42..68a52b4 100644 --- a/tests/test_upstream_catalog.py +++ b/tests/test_upstream_catalog.py @@ -43,7 +43,7 @@ def has_license(folder: Path) -> bool: def test_catalog_and_lock_agree() -> None: assert catalog_names() == set(lock_skills()), ( "upstream-skills.toml and upstream-skills.lock.json disagree; " - "run `make sync-upstream-skills`" + "run `task sync-upstream-skills`" )