Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/pr-title-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand All @@ -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:
Expand Down
22 changes: 5 additions & 17 deletions skills/agentsmd-generator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,27 @@ 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`.
- Read its `key=value` lines as the **factual baseline** for the steps below. Use `-C <dir>` to scope a subdirectory or `--depth <n>` to widen/trim the tree.
- 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").
Expand Down Expand Up @@ -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.
4 changes: 2 additions & 2 deletions skills/agentsmd-generator/scripts/repo-inventory
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion skills/jj-case-insensitive-clone-fix/scripts/diagnose
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ case "${1:-}" in
exit 0
;;
--stdin)
names="$(cat | awk 'NF')"
names="$(awk 'NF')"
;;
"")
echo "Error: missing <git-url> (or --stdin)" >&2
Expand Down
4 changes: 1 addition & 3 deletions skills/jj-case-insensitive-clone-fix/scripts/jj-clone
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 1 addition & 7 deletions skills/sdd-linear/scripts/assess-linear-sdd-state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion skills/work-breakdown/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 2 additions & 19 deletions tests/test_assess_linear_sdd_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -29,36 +29,31 @@ 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
assert result["detailed_state"] == "S1_START"
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": []}
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
{
Expand All @@ -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(
Expand Down
6 changes: 1 addition & 5 deletions tests/test_install_linear_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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()
Expand All @@ -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()
Expand Down
3 changes: 1 addition & 2 deletions tests/test_jj_clone_diagnose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/test_repo_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions tests/test_research_metadata.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 2 additions & 5 deletions tests/test_skill_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test_upstream_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`"
)


Expand Down
Loading