From 8f0343ef56210848aa3167f872ee0e0ea950b81b Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 14:21:33 +0900 Subject: [PATCH 1/8] feat: add ADR relationship graph exports --- .github/PULL_REQUEST_TEMPLATE.md | 22 ++++ CONTRIBUTING.md | 48 +++++++ README.md | 20 +++ SECURITY.md | 34 +++++ adapters/codex/README.md | 17 ++- changelog.md | 7 + docs/decisions/README.md | 16 +++ docs/decisions/relationships.mmd | 13 ++ docs/decisions/relationships.svg | 41 ++++++ ...1-public-readiness-and-adr-graph-design.md | 27 ++++ project-roadmap.md | 7 - skills/adr-toolkit/SKILL.md | 14 ++ skills/adr-toolkit/scripts/adr.py | 9 ++ skills/adr-toolkit/scripts/commands/graph.py | 65 ++++++++++ skills/adr-toolkit/scripts/commands/index.py | 9 +- .../adr-toolkit/scripts/core/relationships.py | 114 +++++++++++++++++ tests/unit/test_adr_cli.py | 14 ++ tests/unit/test_graph_command.py | 120 ++++++++++++++++++ tests/unit/test_index.py | 3 + tests/unit/test_relationships.py | 46 ++++++- 20 files changed, 625 insertions(+), 21 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 docs/decisions/relationships.mmd create mode 100644 docs/decisions/relationships.svg create mode 100644 docs/superpowers/specs/2026-08-31-public-readiness-and-adr-graph-design.md create mode 100644 skills/adr-toolkit/scripts/commands/graph.py create mode 100644 tests/unit/test_graph_command.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..304542a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Summary + +- + +## ADR Impact + +- [ ] I checked whether this changes an accepted architectural decision. +- [ ] I linked related ADRs, or this PR has no ADR impact. +- [ ] I ran `adr.py check` when the change touches governed paths. + +Related ADRs: + +## Verification + +- [ ] `python3 -m pytest -q` +- [ ] `python3 scripts/sync_version.py --check` +- [ ] `python3 skills/adr-toolkit/scripts/adr.py validate --dir docs/decisions --json` +- [ ] `python3 skills/adr-toolkit/scripts/adr.py index --dir docs/decisions --json` + +## Notes + +- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4445b47 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +Thanks for helping improve ADR Toolkit. This project is intentionally small: +the agent can reason and draft, but deterministic scripts own file writes, +IDs, lifecycle transitions, validation, indexes, and machine-readable output. + +## Branches + +Follow the repository policy in `AGENTS.md`. + +- Branch from `develop` for normal work. +- Use `feature/*`, `fix/*`, or `docs/*` for short-lived work branches. +- Merge normal work back to `develop` through a pull request. +- Release branches merge to `master` and back to `develop`. +- Tags named `v*` are created only from `master`. + +## Local Checks + +Run the relevant checks before opening a pull request: + +```bash +python3 -m pytest -q +python3 scripts/sync_version.py --check +python3 skills/adr-toolkit/scripts/adr.py validate --dir docs/decisions --json +python3 skills/adr-toolkit/scripts/adr.py index --dir docs/decisions --json +``` + +For changes that may affect accepted architectural decisions, also run: + +```bash +python3 skills/adr-toolkit/scripts/adr.py check --uncommitted --dir docs/decisions --json +``` + +## ADR Changes + +- Keep ADR source files flat under `docs/decisions/`. +- Do not hand-edit generated indexes or lifecycle links to repair validation + errors. Change the source ADR or use the lifecycle command. +- Use `adr.py create`, `status`, `deprecate`, `supersede`, and `exception` for + deterministic mutations. +- Accepted ADRs are append-only in spirit: use supersession for changed + decisions instead of rewriting history. + +## Public Repository Hygiene + +Public repository branch/tag protection is expected to enforce PRs, required +checks, conversation resolution, and force-push/deletion restrictions on +`develop`, `master`, and `v*` tags once the repository is public. diff --git a/README.md b/README.md index 3840378..5db17d3 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,26 @@ untruncated count and `truncated` is `total > count`. from `related` (used during RECORD's DISCOVER stage to find precedent for a *new* draft, with a broader OR-across-fields match). +## Relationship graph + +`index` embeds a Mermaid relationship graph in `docs/decisions/README.md` +whenever ADRs have `related` or `supersedes` links, so GitHub can render the +navigation view directly. + +For a standalone graph artifact, export Mermaid and SVG files: + +```bash +python skills/adr-toolkit/scripts/adr.py graph --dir docs/decisions --format both --json +``` + +This writes `docs/decisions/relationships.mmd` and +`docs/decisions/relationships.svg`. SVG is the default image artifact because it +stays sharp when zoomed and does not require Mermaid CLI, Node, or a browser. +With `--format both`, `--output build/adr-relationships` is treated as a file +prefix and writes `build/adr-relationships.mmd` plus +`build/adr-relationships.svg`; relative output paths are resolved from +`--root`, matching the other repository-scoped commands. + ### Why storage stays flat ADRs live as `NNNN-slug.md` in one flat `docs/decisions/` directory — no diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..84f604e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# Security Policy + +## Supported Versions + +Security fixes target the latest released version of ADR Toolkit. + +## Reporting a Vulnerability + +Please report security issues privately to the repository owner before opening a +public issue. Include: + +- affected version or commit +- reproduction steps +- expected impact +- whether the issue affects generated ADR content, command execution, path + handling, release automation, or plugin installation + +Do not include exploit details in a public issue until a fix or mitigation is +available. + +## Scope + +In scope: + +- command injection or unsafe path handling in deterministic scripts +- release workflow or version provenance problems +- plugin manifest behavior that can install or expose unintended files +- validation bugs that silently report clean results for invalid ADR state + +Out of scope: + +- disagreement with an ADR's architectural recommendation +- AI-generated prose quality +- unsupported third-party harness behavior outside this repository's adapters diff --git a/adapters/codex/README.md b/adapters/codex/README.md index 2abb0ca..80d8ac4 100644 --- a/adapters/codex/README.md +++ b/adapters/codex/README.md @@ -95,8 +95,8 @@ $ codex plugin add adr-toolkit@adr-toolkit-marketplace --json "pluginId": "adr-toolkit@adr-toolkit-marketplace", "name": "adr-toolkit", "marketplaceName": "adr-toolkit-marketplace", - "version": "0.1.0", - "installedPath": "/plugins/cache/adr-toolkit-marketplace/adr-toolkit/0.1.0", + "version": "0.2.0", + "installedPath": "/plugins/cache/adr-toolkit-marketplace/adr-toolkit/0.2.0", "authPolicy": "ON_INSTALL" } @@ -105,7 +105,7 @@ Marketplace `adr-toolkit-marketplace` /.claude-plugin/marketplace.json PLUGIN STATUS VERSION PATH -adr-toolkit@adr-toolkit-marketplace installed, enabled 0.1.0 +adr-toolkit@adr-toolkit-marketplace installed, enabled 0.2.0 ``` The installed snapshot under `/plugins/cache/` carries the @@ -129,9 +129,8 @@ Two details worth knowing: documented marketplace model, not a defect; the repo root is the correct thing to register, and it works. - **The version Codex reports comes from the plugin manifest it reads.** - Installing from this repo's root reports `0.1.0`, taken from - `.claude-plugin/plugin.json`. Installing a marketplace root whose plugin - manifest is only this adapter's `.codex-plugin/plugin.json` reports - `1.0.0` instead, because that manifest carries no `version` field and - Codex substitutes a default. Adding an explicit `version` to - `.codex-plugin/plugin.json` is tracked as a follow-up. + Installing from this repo's root reports the version in + `.claude-plugin/plugin.json`, which is kept in sync with + `skills/adr-toolkit/VERSION` by `scripts/sync_version.py --check`. The + adapter-local `.codex-plugin/plugin.json` intentionally has no `version` + field because the verified install path does not read it. diff --git a/changelog.md b/changelog.md index dab90e5..05ae2be 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,13 @@ Lightweight human-readable summary of meaningful repository changes. ## Unreleased +- Added ADR relationship graph navigation: `adr.py index` now embeds a Mermaid + graph in the generated decision log when relationships exist, and + `adr.py graph` exports standalone `relationships.mmd` plus a crisp SVG + navigation artifact without requiring Node, Mermaid CLI, or browser + rendering. +- Added public repository readiness docs: `CONTRIBUTING.md`, `SECURITY.md`, + and a pull request template covering ADR impact and local verification. - Added `adr.py search`: finds an existing ADR by keyword (title **and** body — `related`'s keyword match was title-only before), tags, status, `--id`, or the real file path it governs, with deterministic best-match diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 955800a..a6550ab 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -234,3 +234,19 @@ ### Related - ADR-0006 "Localize deterministic ADR generation through repository configuration" related to: ADR-0003 "Localize only index.py's generated strings, not agent-composed text" + +```mermaid +flowchart LR + ADR_0001["ADR-0001
Record architecture decisions"] + ADR_0002["ADR-0002
Limit CHECK's conflict detection to structural evidence only"] + ADR_0003["ADR-0003
Localize only index.py's generated strings, not agent-composed text"] + ADR_0004["ADR-0004
Adapter packaging: manifest-only directories, install-time symlinks, verified formats"] + ADR_0005["ADR-0005
Adopt Git Flow with direct-tag release automation"] + ADR_0006["ADR-0006
Localize deterministic ADR generation through repository configuration"] + ADR_0007["ADR-0007
Promote CHECK's kind-to-confidence mapping to a stable output field"] + ADR_0008["ADR-0008
Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress"] + ADR_0009["ADR-0009
--json is a documented no-op; CLI output is always JSON"] + ADR_0010["ADR-0010
Codex skill-creator's quick_validate.py incompatibility is not this project's problem"] + ADR_0006 -.->|related| ADR_0003 + ADR_0006 -->|supersedes| ADR_0003 +``` diff --git a/docs/decisions/relationships.mmd b/docs/decisions/relationships.mmd new file mode 100644 index 0000000..e07a247 --- /dev/null +++ b/docs/decisions/relationships.mmd @@ -0,0 +1,13 @@ +flowchart LR + ADR_0001["ADR-0001
Record architecture decisions"] + ADR_0002["ADR-0002
Limit CHECK's conflict detection to structural evidence only"] + ADR_0003["ADR-0003
Localize only index.py's generated strings, not agent-composed text"] + ADR_0004["ADR-0004
Adapter packaging: manifest-only directories, install-time symlinks, verified formats"] + ADR_0005["ADR-0005
Adopt Git Flow with direct-tag release automation"] + ADR_0006["ADR-0006
Localize deterministic ADR generation through repository configuration"] + ADR_0007["ADR-0007
Promote CHECK's kind-to-confidence mapping to a stable output field"] + ADR_0008["ADR-0008
Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress"] + ADR_0009["ADR-0009
--json is a documented no-op; CLI output is always JSON"] + ADR_0010["ADR-0010
Codex skill-creator's quick_validate.py incompatibility is not this project's problem"] + ADR_0006 -.->|related| ADR_0003 + ADR_0006 -->|supersedes| ADR_0003 diff --git a/docs/decisions/relationships.svg b/docs/decisions/relationships.svg new file mode 100644 index 0000000..18dc6ae --- /dev/null +++ b/docs/decisions/relationships.svg @@ -0,0 +1,41 @@ + +ADR relationship graph +Vector export of ADR supersession and related-decision links. + + +ADR relationship graph + +ADR-0001 +Record architecture decisions + +ADR-0002 +Limit CHECK's conflict detection ... + +ADR-0003 +Localize only index.py's generate... + +ADR-0004 +Adapter packaging: manifest-only ... + +ADR-0005 +Adopt Git Flow with direct-tag re... + +ADR-0006 +Localize deterministic ADR genera... + +ADR-0007 +Promote CHECK's kind-to-confidenc... + +ADR-0008 +Deterministic CHECK policy except... + +ADR-0009 +--json is a documented no-op; CLI... + +ADR-0010 +Codex skill-creator's quick_valid... + +ADR-0006 related ADR-0003 + +ADR-0006 supersedes ADR-0003 + diff --git a/docs/superpowers/specs/2026-08-31-public-readiness-and-adr-graph-design.md b/docs/superpowers/specs/2026-08-31-public-readiness-and-adr-graph-design.md new file mode 100644 index 0000000..14b0d33 --- /dev/null +++ b/docs/superpowers/specs/2026-08-31-public-readiness-and-adr-graph-design.md @@ -0,0 +1,27 @@ +# Public Readiness and ADR Graph Design + +## Scope + +Prepare ADR Toolkit for public open-source use and add a sharper ADR navigation +view without changing the canonical flat `docs/decisions/` storage model. + +## Decisions + +- Keep ADR Markdown files flat in `docs/decisions/`. +- Add a Mermaid relationship graph to the generated decision index so GitHub can + render it directly. +- Add a deterministic `adr.py graph` export command that writes + `relationships.mmd` and, when requested, a vector `relationships.svg`. +- Generate SVG directly from the same relationship model in Python. The SVG is a + navigation artifact, not a pixel screenshot, so it remains crisp when zoomed. +- Keep PNG out of the default workflow. Raster export can be revisited later if + users explicitly need it. +- Add public repository hygiene docs: contributing guidance, security reporting, + and a pull request template. + +## Validation + +- Unit tests must cover Mermaid block rendering, Mermaid file export, SVG file + export, and graph behavior when no relationships exist. +- Existing ADR validation, index generation, version drift, and full pytest must + pass before completion. diff --git a/project-roadmap.md b/project-roadmap.md index 25e1a1b..6a16644 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -22,13 +22,6 @@ before implementation. Concrete selected work belongs in `improvements.md`. ## ADR navigation and scale -- ~~Add a general search command and relationship visibility.~~ **Done - (2026-08-31).** `adr.py search` (keyword now matches body, not just - title; tag/status/governed-path filters; deterministic ranking) and a - Relationships section in the generated index. See `improvements.md`'s - Done entry and `docs/superpowers/specs/2026-08-31-adr-search-and-relationships-design.md`. -- Render `related`/`supersedes` relationships as a rendered graph image (DOT/ - Mermaid), not just the text-based index lists search/index now provide. - Test whether 500+ decisions require sharding, alternate indexes, or a real search index (this repo has 10 ADRs; substring/tag/path matching is untested at that scale). diff --git a/skills/adr-toolkit/SKILL.md b/skills/adr-toolkit/SKILL.md index e9ff071..c2c3ac0 100644 --- a/skills/adr-toolkit/SKILL.md +++ b/skills/adr-toolkit/SKILL.md @@ -298,3 +298,17 @@ CHECK's conflict detection is deliberately limited to structural evidence from `constraints:` blocks (see `references/conflict-rules.md`). It does not certify the entire architecture and never attempts semantic or AST-level analysis; see `project-roadmap.md` for what that fuller scope would look like. + +`graph` is a read-only navigation export: + +```bash +python skills/adr-toolkit/scripts/adr.py graph --dir docs/decisions --format both --json +``` + +It writes Mermaid (`relationships.mmd`) and SVG (`relationships.svg`) graph +artifacts from the same relationship model used by `index`. SVG is generated +directly by Python so the image stays sharp without requiring Mermaid CLI, +Node, or browser rendering. +When `--format both` and `--output` are used together, `--output` is a file +prefix; for example `--output build/adr-relationships` writes +`build/adr-relationships.mmd` and `build/adr-relationships.svg`. diff --git a/skills/adr-toolkit/scripts/adr.py b/skills/adr-toolkit/scripts/adr.py index cb93296..9d139dc 100755 --- a/skills/adr-toolkit/scripts/adr.py +++ b/skills/adr-toolkit/scripts/adr.py @@ -13,6 +13,7 @@ diff, discover, exception, + graph, index, init, preflight, @@ -138,6 +139,13 @@ def build_parser() -> argparse.ArgumentParser: p_exception.add_argument("--dry-run", dest="dry_run", action="store_true") _add_json_flag(p_exception) + p_graph = sub.add_parser("graph") + p_graph.add_argument("--dir", default="docs/decisions") + p_graph.add_argument("--root", default=".") + p_graph.add_argument("--format", choices=("mermaid", "svg", "both"), default="both") + p_graph.add_argument("--output") + _add_json_flag(p_graph) + p_search = sub.add_parser("search") p_search.add_argument("--id") p_search.add_argument("--keyword") @@ -166,6 +174,7 @@ def build_parser() -> argparse.ArgumentParser: "diff": diff.run, "check": check.run, "exception": exception.run, + "graph": graph.run, "search": search.run, } diff --git a/skills/adr-toolkit/scripts/commands/graph.py b/skills/adr-toolkit/scripts/commands/graph.py new file mode 100644 index 0000000..19c9ffb --- /dev/null +++ b/skills/adr-toolkit/scripts/commands/graph.py @@ -0,0 +1,65 @@ +"""Export ADR relationships as Mermaid and SVG graph artifacts.""" +from pathlib import Path + +from scripts.core import frontmatter as fm +from scripts.core.adr_directory import iter_adr_files +from scripts.core.relationships import render_mermaid, render_svg, resolve +from scripts.core.repository_paths import resolve_from_root + + +def run(args) -> dict: + root = Path(getattr(args, "root", ".")) + adr_dir = resolve_from_root(root, args.dir) + entries = [] + warnings = [] + + for entry, parsed in iter_adr_files(adr_dir): + if parsed is None: + continue + try: + data, _ = fm.parse(entry.read_text(encoding="utf-8")) + except fm.FrontmatterError as exc: + warnings.append({"code": "BAD_FRONTMATTER", "file": entry.name, "detail": str(exc)}) + continue + entries.append({ + "id": data.get("id", f"ADR-{parsed[0]:04d}"), + "filename": entry.name, + "title": data.get("title", parsed[1]), + "related": data.get("related", []), + "supersedes": data.get("supersedes", []), + "superseded_by": data.get("superseded_by"), + }) + + output = getattr(args, "output", None) + format_ = getattr(args, "format", "both") + outputs = [] + if format_ in {"mermaid", "both"}: + path = _output_path(root, adr_dir, output, format_, "mermaid") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(render_mermaid(entries), encoding="utf-8") + outputs.append(str(path)) + if format_ in {"svg", "both"}: + path = _output_path(root, adr_dir, output, format_, "svg") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(render_svg(entries), encoding="utf-8") + outputs.append(str(path)) + + rendered_edges = [edge for edge in resolve(entries) if edge.type in {"related", "supersedes"}] + return { + "ok": True, + "operation": "graph", + "count": len(rendered_edges), + "outputs": outputs, + "warnings": warnings, + } + + +def _output_path(root: Path, adr_dir: Path, output: str, requested_format: str, actual_format: str) -> Path: + suffix = f".{'mmd' if actual_format == 'mermaid' else 'svg'}" + if not output: + return adr_dir / f"relationships{suffix}" + + base = resolve_from_root(root, output) + if requested_format == "both": + return base.with_suffix(suffix) + return base diff --git a/skills/adr-toolkit/scripts/commands/index.py b/skills/adr-toolkit/scripts/commands/index.py index 526f020..b9148df 100644 --- a/skills/adr-toolkit/scripts/commands/index.py +++ b/skills/adr-toolkit/scripts/commands/index.py @@ -5,7 +5,7 @@ from scripts.core.adr_directory import iter_adr_files from scripts.core.config import ConfigError, resolve_locale from scripts.core.locale import load_locale -from scripts.core.relationships import resolve +from scripts.core.relationships import render_mermaid, resolve from scripts.core.repository_paths import resolve_from_root # Last-resort English headers, used when even the English locale file is @@ -156,4 +156,11 @@ def _render(entries: list, strings: dict) -> str: f"{target['id']} \"{target['title']}\"" ) + visual_edges = [edge for edge in edges if edge.type in {"related", "supersedes"}] + if visual_edges: + lines.append("") + lines.append("```mermaid") + lines.extend(render_mermaid(entries).rstrip().splitlines()) + lines.append("```") + return "\n".join(lines) + "\n" diff --git a/skills/adr-toolkit/scripts/core/relationships.py b/skills/adr-toolkit/scripts/core/relationships.py index 865ccbd..d9f8bd0 100644 --- a/skills/adr-toolkit/scripts/core/relationships.py +++ b/skills/adr-toolkit/scripts/core/relationships.py @@ -1,4 +1,5 @@ """Canonical ADR relationship model, shared by index.py and validate.py.""" +from html import escape from typing import NamedTuple @@ -69,3 +70,116 @@ def dfs(node, path, path_set): dfs(node, [], set()) return cycles + + +def render_mermaid(entries: list) -> str: + """Render ADR relationships as GitHub-compatible Mermaid flowchart text.""" + by_id = _entries_by_id(entries) + relationships = resolve(entries) + visual_edges = _visual_edges(relationships) + lines = ["flowchart LR"] + + for adr_id in sorted(by_id): + entry = by_id[adr_id] + label = _mermaid_label(adr_id, entry.get("title", adr_id)) + lines.append(f" {_node_id(adr_id)}[\"{label}\"]") + + if not visual_edges: + lines.append(' EMPTY["No relationships recorded"]') + + for relationship in sorted(visual_edges): + source = _node_id(relationship.source) + target = _node_id(relationship.target) + if relationship.type == "related": + lines.append(f" {source} -.->|related| {target}") + else: + lines.append(f" {source} -->|supersedes| {target}") + + return "\n".join(lines) + "\n" + + +def render_svg(entries: list) -> str: + """Render a small deterministic SVG navigation artifact. + + This is intentionally not a full Mermaid renderer. It uses the same graph + model and writes vector text/lines directly so the exported artifact stays + sharp without requiring Node or browser automation. + """ + by_id = _entries_by_id(entries) + relationships = _visual_edges(resolve(entries)) + ordered_ids = sorted(by_id) + width = 960 + row_height = 82 + edge_height = 28 + node_height = max(1, len(ordered_ids)) * row_height + edge_start = 72 + node_height + height = edge_start + max(1, len(relationships)) * edge_height + 48 + y_by_id = {adr_id: 72 + index * row_height for index, adr_id in enumerate(ordered_ids)} + + lines = [ + f'', + "ADR relationship graph", + "Vector export of ADR supersession and related-decision links.", + '', + '', + 'ADR relationship graph', + ] + + for adr_id in ordered_ids: + y = y_by_id[adr_id] + title = str(by_id[adr_id].get("title", adr_id)) + lines.extend([ + f'', + f'{escape(adr_id)}', + f'{escape(_truncate(title, 36))}', + ]) + + if relationships: + pair_totals = {} + for relationship in relationships: + pair = (relationship.source, relationship.target) + pair_totals[pair] = pair_totals.get(pair, 0) + 1 + pair_seen = {} + for index, relationship in enumerate(sorted(relationships)): + pair = (relationship.source, relationship.target) + pair_seen[pair] = pair_seen.get(pair, 0) + 1 + offset = (pair_seen[pair] - (pair_totals[pair] + 1) / 2) * 14 + source_y = y_by_id.get(relationship.source, 72) + 26 + offset + target_y = y_by_id.get(relationship.target, 72) + 26 + offset + edge_y = edge_start + index * edge_height + label = f"{relationship.source} {relationship.type} {relationship.target}" + dash = ' stroke-dasharray="5 5"' if relationship.type == "related" else "" + lines.extend([ + f'', + f'{escape(label)}', + ]) + else: + lines.append('No relationships recorded') + + lines.append("") + return "\n".join(lines) + "\n" + + +def _entries_by_id(entries: list) -> dict: + return {entry["id"]: entry for entry in entries if entry.get("id")} + + +def _visual_edges(relationships: list) -> list: + return [r for r in relationships if r.type in {"related", "supersedes"}] + + +def _node_id(adr_id: str) -> str: + return adr_id.replace("-", "_") + + +def _mermaid_label(adr_id: str, title: str) -> str: + safe_title = " ".join(str(title).split()) + safe_title = escape(safe_title, quote=False) + safe_title = safe_title.replace('"', "'").replace("[", "(").replace("]", ")") + return f"{adr_id}
{safe_title}" + + +def _truncate(value: str, limit: int) -> str: + if len(value) <= limit: + return value + return value[: limit - 3] + "..." diff --git a/tests/unit/test_adr_cli.py b/tests/unit/test_adr_cli.py index a122cc0..b8378cc 100644 --- a/tests/unit/test_adr_cli.py +++ b/tests/unit/test_adr_cli.py @@ -53,6 +53,20 @@ def test_index_locale_is_unset_when_omitted_so_repository_default_can_win(): assert args.root == "." +def test_graph_command_accepts_format_and_output_options(): + args = adr.build_parser().parse_args([ + "graph", + "--dir", "docs/decisions", + "--format", "svg", + "--output", "docs/decisions/relationships.svg", + ]) + + assert args.operation == "graph" + assert args.format == "svg" + assert args.output == "docs/decisions/relationships.svg" + assert adr.HANDLERS["graph"].__module__ == "scripts.commands.graph" + + def test_init_locale_is_optional_and_constrained(): parser = adr.build_parser() diff --git a/tests/unit/test_graph_command.py b/tests/unit/test_graph_command.py new file mode 100644 index 0000000..c03c378 --- /dev/null +++ b/tests/unit/test_graph_command.py @@ -0,0 +1,120 @@ +from types import SimpleNamespace + +from scripts.commands import graph + + +def _write_adr(adr_dir, filename, *, id_, title, related=None, supersedes=None, superseded_by=None): + lines = [ + "---", + f"id: {id_}", + f"title: {title}", + "status: accepted", + "date: 2026-08-31", + "decision_makers: []", + ] + if related: + lines.append("related:") + lines.extend(f" - {value}" for value in related) + else: + lines.append("related: []") + lines.append("affected_paths: []") + lines.append("tags: []") + lines.append("retrospective: false") + if supersedes: + lines.append("supersedes:") + lines.extend(f" - {value}" for value in supersedes) + if superseded_by: + lines.append(f"superseded_by: {superseded_by}") + lines.extend(["---", "", f"# {title}", ""]) + (adr_dir / filename).write_text("\n".join(lines), encoding="utf-8") + + +def test_graph_command_writes_mermaid_and_svg_exports(tmp_path): + adr_dir = tmp_path / "docs" / "decisions" + adr_dir.mkdir(parents=True) + _write_adr(adr_dir, "0001-old.md", id_="ADR-0001", title="Old", superseded_by="ADR-0002") + _write_adr(adr_dir, "0002-new.md", id_="ADR-0002", title="New", supersedes=["ADR-0001"]) + + result = graph.run(SimpleNamespace( + dir="docs/decisions", + root=str(tmp_path), + output=None, + format="both", + )) + + assert result["ok"] is True + assert result["count"] == 1 + assert result["outputs"] == [ + str(adr_dir / "relationships.mmd"), + str(adr_dir / "relationships.svg"), + ] + assert "ADR_0002 -->|supersedes| ADR_0001" in ( + adr_dir / "relationships.mmd" + ).read_text(encoding="utf-8") + assert "ADR-0002 supersedes ADR-0001" in ( + adr_dir / "relationships.svg" + ).read_text(encoding="utf-8") + + +def test_graph_command_can_write_only_mermaid_to_custom_output(tmp_path): + adr_dir = tmp_path / "docs" / "decisions" + adr_dir.mkdir(parents=True) + _write_adr(adr_dir, "0001-a.md", id_="ADR-0001", title="A", related=["ADR-0002"]) + _write_adr(adr_dir, "0002-b.md", id_="ADR-0002", title="B") + output = tmp_path / "graph.mmd" + + result = graph.run(SimpleNamespace( + dir=str(adr_dir), + root=str(tmp_path), + output=str(output), + format="mermaid", + )) + + assert result["ok"] is True + assert result["outputs"] == [str(output)] + assert "ADR_0001 -.->|related| ADR_0002" in output.read_text(encoding="utf-8") + + +def test_graph_command_uses_custom_output_as_prefix_for_both_formats(tmp_path): + adr_dir = tmp_path / "docs" / "decisions" + adr_dir.mkdir(parents=True) + _write_adr(adr_dir, "0001-a.md", id_="ADR-0001", title="A", related=["ADR-0002"]) + _write_adr(adr_dir, "0002-b.md", id_="ADR-0002", title="B") + + result = graph.run(SimpleNamespace( + dir="docs/decisions", + root=str(tmp_path), + output="build/adr-relationships", + format="both", + )) + + assert result["ok"] is True + assert result["outputs"] == [ + str(tmp_path / "build" / "adr-relationships.mmd"), + str(tmp_path / "build" / "adr-relationships.svg"), + ] + assert (tmp_path / "build" / "adr-relationships.mmd").is_file() + assert (tmp_path / "build" / "adr-relationships.svg").is_file() + + +def test_graph_command_resolves_relative_single_output_against_root(tmp_path, monkeypatch): + caller = tmp_path / "caller" + repo = tmp_path / "repo" + adr_dir = repo / "docs" / "decisions" + caller.mkdir() + adr_dir.mkdir(parents=True) + _write_adr(adr_dir, "0001-a.md", id_="ADR-0001", title="A", related=["ADR-0002"]) + _write_adr(adr_dir, "0002-b.md", id_="ADR-0002", title="B") + monkeypatch.chdir(caller) + + result = graph.run(SimpleNamespace( + dir="docs/decisions", + root=str(repo), + output="docs/decisions/custom.svg", + format="svg", + )) + + assert result["ok"] is True + assert result["outputs"] == [str(repo / "docs" / "decisions" / "custom.svg")] + assert (repo / "docs" / "decisions" / "custom.svg").is_file() + assert not (caller / "docs" / "decisions" / "custom.svg").exists() diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index f917870..a62b60e 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -229,6 +229,8 @@ def test_index_relationships_section_shows_supersession_with_titles(tmp_path): assert "## Relationships" in readme assert "ADR-0003" in readme and "Old decision" in readme assert "ADR-0006" in readme and "New decision" in readme + assert "```mermaid" in readme + assert "ADR_0006 -->|supersedes| ADR_0003" in readme def test_index_relationships_section_omits_adr_with_no_relationships(tmp_path): @@ -245,3 +247,4 @@ def test_index_relationships_section_omits_adr_with_no_relationships(tmp_path): readme = (adr_dir / "README.md").read_text(encoding="utf-8") assert "Lonely decision" not in readme.split("## Relationships")[1] + assert "```mermaid" not in readme diff --git a/tests/unit/test_relationships.py b/tests/unit/test_relationships.py index 97b9dde..0717478 100644 --- a/tests/unit/test_relationships.py +++ b/tests/unit/test_relationships.py @@ -2,15 +2,17 @@ Relationship, find_cycles, missing_targets, + render_mermaid, + render_svg, resolve, supersession_mismatches, ) ENTRIES = [ - {"id": "ADR-0003", "related": [], "supersedes": [], "superseded_by": "ADR-0006"}, - {"id": "ADR-0006", "related": [], "supersedes": ["ADR-0003"], "superseded_by": None}, - {"id": "ADR-0007", "related": ["ADR-0002"], "supersedes": [], "superseded_by": None}, - {"id": "ADR-0002", "related": [], "supersedes": [], "superseded_by": None}, + {"id": "ADR-0003", "title": "Old decision", "related": [], "supersedes": [], "superseded_by": "ADR-0006"}, + {"id": "ADR-0006", "title": "New decision", "related": [], "supersedes": ["ADR-0003"], "superseded_by": None}, + {"id": "ADR-0007", "title": "Confidence field", "related": ["ADR-0002"], "supersedes": [], "superseded_by": None}, + {"id": "ADR-0002", "title": "Structural checks", "related": [], "supersedes": [], "superseded_by": None}, ] @@ -103,3 +105,39 @@ def test_find_cycles_ignores_related_edges(): {"id": "ADR-0002", "related": ["ADR-0001"]}, ] assert find_cycles(resolve(mutually_related)) == [] + + +def test_render_mermaid_includes_supersession_and_related_edges_with_labels(): + mermaid = render_mermaid(ENTRIES) + + assert mermaid.startswith("flowchart LR\n") + assert "ADR_0006[\"ADR-0006
New decision\"]" in mermaid + assert "ADR_0006 -->|supersedes| ADR_0003" in mermaid + assert "ADR_0007 -.->|related| ADR_0002" in mermaid + assert "ADR_0003 -->|superseded by| ADR_0006" not in mermaid + + +def test_render_mermaid_reports_no_relationships_when_graph_has_no_edges(): + mermaid = render_mermaid([{"id": "ADR-0001", "title": "Lonely decision"}]) + + assert "ADR_0001[\"ADR-0001
Lonely decision\"]" in mermaid + assert "No relationships recorded" in mermaid + + +def test_render_mermaid_escapes_title_text_for_label_safety(): + mermaid = render_mermaid([ + {"id": "ADR-0001", "title": "Use \"ports\" [core]\nnow"}, + ]) + + assert "Use <A&B> 'ports' (core) now" in mermaid + assert "\nnow" not in mermaid + + +def test_render_svg_returns_crisp_vector_navigation_artifact(): + svg = render_svg(ENTRIES) + + assert svg.startswith("ADR relationship graph" in svg + assert "ADR-0006" in svg and "New decision" in svg + assert "ADR-0006 supersedes ADR-0003" in svg + assert "ADR-0007 related ADR-0002" in svg From 35c81aab8223b5b77b83465961f24271470ce425 Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 14:32:02 +0900 Subject: [PATCH 2/8] docs: record ADR-0011 for relationship graph and public readiness work Regenerates docs/decisions/README.md and relationships.mmd/svg to include ADR-0011, and updates handoff.md with verification results (395 tests, 11 ADRs validated, 6 graph edges) ahead of PR follow-up. --- ...adr-relationship-graph-public-readiness.md | 125 ++++++++++++++++++ docs/decisions/README.md | 63 +++++++++ docs/decisions/relationships.mmd | 5 + docs/decisions/relationships.svg | 17 ++- handoff.md | 96 ++++++++------ 5 files changed, 265 insertions(+), 41 deletions(-) create mode 100644 docs/decisions/0011-adr-relationship-graph-public-readiness.md diff --git a/docs/decisions/0011-adr-relationship-graph-public-readiness.md b/docs/decisions/0011-adr-relationship-graph-public-readiness.md new file mode 100644 index 0000000..f849353 --- /dev/null +++ b/docs/decisions/0011-adr-relationship-graph-public-readiness.md @@ -0,0 +1,125 @@ +--- +id: ADR-0011 +title: Expose ADR relationships as Mermaid and SVG navigation artifacts +status: accepted +date: 2026-08-31 +locale: en +decision_makers: + - YangSeungHyun +related: + - ADR-0001 + - ADR-0006 + - ADR-0008 + - ADR-0009 +affected_paths: + - README.md + - CONTRIBUTING.md + - SECURITY.md + - .github/PULL_REQUEST_TEMPLATE.md + - project-roadmap.md + - docs/decisions/README.md + - docs/decisions/relationships.mmd + - docs/decisions/relationships.svg + - skills/adr-toolkit/SKILL.md + - skills/adr-toolkit/scripts/adr.py + - skills/adr-toolkit/scripts/commands/graph.py + - skills/adr-toolkit/scripts/commands/index.py + - skills/adr-toolkit/scripts/core/relationships.py + - tests/unit/test_graph_command.py + - tests/unit/test_relationships.py + - tests/unit/test_index.py + - tests/unit/test_adr_cli.py +tags: + - navigation + - graph + - documentation + - governance + - v0.2.0 +retrospective: false +--- + +# Expose ADR relationships as Mermaid and SVG navigation artifacts + +## Context and Problem Statement + +ADR Toolkit intentionally keeps ADR source files flat under `docs/decisions/`, but a growing decision log still needs navigable relationship context. The generated index already listed supersession and related links as text, but users also wanted a graph view and a crisp image artifact. Raster screenshots were not acceptable because relationship graphs rely on small text and lines that become blurry when exported as PNG. + +The same session also prepared the repository for public open-source use with contributor, security, and pull request guidance. That public surface should point contributors at the same deterministic ADR navigation and verification workflow rather than adding a separate documentation system. + +## Decision Drivers + +* Preserve the flat `docs/decisions/` storage model. +* Make relationship navigation visible in GitHub without requiring local tooling. +* Provide a sharp standalone image artifact for documents and reviews. +* Avoid making Node, Mermaid CLI, browser automation, or screenshot rendering required dependencies. +* Keep command output deterministic JSON and path handling consistent with the existing repository-scoped commands. +* Make public contribution guidance ask reviewers to consider ADR impact and verification. + +## Considered Options + +* Keep only the text relationship lists in the generated decision index. +* Embed Mermaid in the generated index and require Mermaid CLI or browser rendering for image export. +* Embed Mermaid in the generated index and generate standalone Mermaid plus Python-rendered SVG artifacts from the same relationship model. + +## Decision Outcome + +Chosen option: **embed Mermaid in the generated index and generate standalone Mermaid plus Python-rendered SVG artifacts**, because GitHub can render Mermaid directly while SVG gives a crisp vector artifact without adding external rendering dependencies. + +`adr.py index` now appends a Mermaid `flowchart LR` block when `related` or `supersedes` edges exist. `adr.py graph --format mermaid|svg|both` exports `relationships.mmd` and/or `relationships.svg`; with `--format both`, a custom `--output` is treated as a prefix so one path can produce both extensions. Relative output paths resolve from `--root`, matching the other repository-scoped commands. + +The public readiness docs added in the same decision are `CONTRIBUTING.md`, `SECURITY.md`, and `.github/PULL_REQUEST_TEMPLATE.md`. They make ADR impact, local verification, and security reporting explicit for future open-source contributors. + +### Consequences + +* Good: GitHub readers get an inline relationship graph without installing anything. +* Good: users who need an image get SVG, which remains sharp when zoomed or embedded in documents. +* Good: graph artifacts reuse the same deterministic relationship model as validation and index generation. +* Good: public contributors now see the expected branch, ADR, security, and verification workflow before opening a PR. +* Bad: the Python SVG renderer is a deliberately small navigation artifact, not a full Mermaid renderer or layout engine. +* Bad: generated `relationships.mmd` and `relationships.svg` are additional files that must be regenerated when ADR relationships change. + +### Confirmation + +* `tests/unit/test_relationships.py` covers Mermaid rendering, label escaping, no-edge behavior, and SVG output. +* `tests/unit/test_graph_command.py` covers default export, custom output prefix behavior for `--format both`, and `--root`-relative output paths. +* `tests/unit/test_index.py` covers Mermaid graph insertion in the generated decision index. +* `tests/unit/test_adr_cli.py` covers parser registration for `adr.py graph`. +* `python3 -m pytest -q` passes with 395 tests. +* `python3 scripts/sync_version.py --check`, `adr.py validate`, `adr.py index`, and `adr.py graph --format both` all pass. + +## Implementation Constraints + +```yaml +constraints: + - id: graph-export-no-external-renderer + kind: forbidden_import + paths: ["skills/adr-toolkit/scripts/commands/graph.py", "skills/adr-toolkit/scripts/core/relationships.py"] + pattern: ["^\\s*import\\s+subprocess\\b", "^\\s*from\\s+subprocess\\b", "^\\s*import\\s+playwright\\b", "^\\s*from\\s+playwright\\b", "^\\s*import\\s+selenium\\b", "^\\s*from\\s+selenium\\b"] + severity: major + message: "ADR graph export must stay deterministic and must not require external renderers or browser automation." +``` + +## Pros and Cons of the Options + +### Keep only text relationship lists + +* Good, because it requires no new command or generated artifact. +* Bad, because relationship navigation remains harder to scan as ADR count grows, and users still lack a sharp visual artifact. + +### Mermaid plus external renderer + +* Good, because Mermaid CLI can produce familiar graph output. +* Bad, because requiring Node, browser automation, or screenshot rendering expands the dependency surface and reintroduces blurry PNG output unless users tune scale settings. + +### Mermaid plus Python-rendered SVG + +* Good, because the README stays GitHub-native and the standalone image remains vector-sharp. +* Good, because it keeps graph export inside the existing deterministic Python package. +* Bad, because the SVG layout is intentionally simple and may need a richer layout strategy if real ADR graphs become dense. + +## Revisit Triggers + +* Real repositories produce dense relationship graphs that the simple SVG layout cannot make readable. +* Users need a PNG export despite SVG availability, and can define acceptable scale/DPI behavior. +* Mermaid syntax compatibility changes on GitHub and breaks the embedded graph. +* Multiple repositories need a combined cross-repository graph rather than one graph per ADR directory. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index a6550ab..864d41a 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -12,6 +12,7 @@ - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) - [ADR-0010 — Codex skill-creator's quick_validate.py incompatibility is not this project's problem](0010-codex-quick-validate-not-applicable.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### Superseded - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) @@ -48,6 +49,7 @@ ### documentation - [ADR-0010 — Codex skill-creator's quick_validate.py incompatibility is not this project's problem](0010-codex-quick-validate-not-applicable.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### exceptions - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) @@ -57,6 +59,10 @@ ### governance - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### graph +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### harness-support - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) @@ -68,6 +74,9 @@ ### mvp-scope - [ADR-0002 — Limit CHECK's conflict detection to structural evidence only](0002-limit-check-s-conflict-detection-to-structural-evidence-only.md) +### navigation +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### output-contract - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) @@ -83,6 +92,7 @@ - [ADR-0007 — Promote CHECK's kind-to-confidence mapping to a stable output field](0007-check-confidence-field.md) - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ## By affected path @@ -96,6 +106,9 @@ ### `.claude-plugin/plugin.json` - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) +### `.github/PULL_REQUEST_TEMPLATE.md` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `.github/workflows/` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) @@ -105,10 +118,17 @@ ### `AGENTS.md` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) +### `CONTRIBUTING.md` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `README.md` - [ADR-0007 — Promote CHECK's kind-to-confidence mapping to a stable output field](0007-check-confidence-field.md) - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### `SECURITY.md` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### `adapters/` - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) @@ -116,9 +136,21 @@ ### `docs/decisions/` - [ADR-0001 — Record architecture decisions](0001-record-architecture-decisions.md) +### `docs/decisions/README.md` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### `docs/decisions/relationships.mmd` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### `docs/decisions/relationships.svg` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `examples/quickstart.md` - [ADR-0007 — Promote CHECK's kind-to-confidence mapping to a stable output field](0007-check-confidence-field.md) +### `project-roadmap.md` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `scripts/sync_version.py` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) @@ -129,6 +161,7 @@ - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) - [ADR-0010 — Codex skill-creator's quick_validate.py incompatibility is not this project's problem](0010-codex-quick-validate-not-applicable.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### `skills/adr-toolkit/VERSION` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) @@ -148,6 +181,7 @@ - [ADR-0006 — Localize deterministic ADR generation through repository configuration](0006-localized-adr-generation.md) - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### `skills/adr-toolkit/scripts/commands/check.py` - [ADR-0002 — Limit CHECK's conflict detection to structural evidence only](0002-limit-check-s-conflict-detection-to-structural-evidence-only.md) @@ -164,9 +198,13 @@ ### `skills/adr-toolkit/scripts/commands/exception.py` - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) +### `skills/adr-toolkit/scripts/commands/graph.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `skills/adr-toolkit/scripts/commands/index.py` - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) - [ADR-0006 — Localize deterministic ADR generation through repository configuration](0006-localized-adr-generation.md) +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) ### `skills/adr-toolkit/scripts/commands/init.py` - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) @@ -192,6 +230,9 @@ - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) - [ADR-0006 — Localize deterministic ADR generation through repository configuration](0006-localized-adr-generation.md) +### `skills/adr-toolkit/scripts/core/relationships.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `skills/adr-toolkit/scripts/core/rendering.py` - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) - [ADR-0006 — Localize deterministic ADR generation through repository configuration](0006-localized-adr-generation.md) @@ -209,11 +250,24 @@ ### `tests/integration/test_cli.py` - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) +### `tests/unit/test_adr_cli.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ### `tests/unit/test_check.py` - [ADR-0007 — Promote CHECK's kind-to-confidence mapping to a stable output field](0007-check-confidence-field.md) +### `tests/unit/test_graph_command.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### `tests/unit/test_index.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + +### `tests/unit/test_relationships.py` +- [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) + ## Chronological (newest first) +- 2026-08-31 — [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) - 2026-08-30 — [ADR-0001 — Record architecture decisions](0001-record-architecture-decisions.md) - 2026-08-30 — [ADR-0002 — Limit CHECK's conflict detection to structural evidence only](0002-limit-check-s-conflict-detection-to-structural-evidence-only.md) - 2026-08-30 — [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) @@ -234,6 +288,10 @@ ### Related - ADR-0006 "Localize deterministic ADR generation through repository configuration" related to: ADR-0003 "Localize only index.py's generated strings, not agent-composed text" +- ADR-0011 "Expose ADR relationships as Mermaid and SVG navigation artifacts" related to: ADR-0001 "Record architecture decisions" +- ADR-0011 "Expose ADR relationships as Mermaid and SVG navigation artifacts" related to: ADR-0006 "Localize deterministic ADR generation through repository configuration" +- ADR-0011 "Expose ADR relationships as Mermaid and SVG navigation artifacts" related to: ADR-0008 "Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress" +- ADR-0011 "Expose ADR relationships as Mermaid and SVG navigation artifacts" related to: ADR-0009 "--json is a documented no-op; CLI output is always JSON" ```mermaid flowchart LR @@ -247,6 +305,11 @@ flowchart LR ADR_0008["ADR-0008
Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress"] ADR_0009["ADR-0009
--json is a documented no-op; CLI output is always JSON"] ADR_0010["ADR-0010
Codex skill-creator's quick_validate.py incompatibility is not this project's problem"] + ADR_0011["ADR-0011
Expose ADR relationships as Mermaid and SVG navigation artifacts"] ADR_0006 -.->|related| ADR_0003 ADR_0006 -->|supersedes| ADR_0003 + ADR_0011 -.->|related| ADR_0001 + ADR_0011 -.->|related| ADR_0006 + ADR_0011 -.->|related| ADR_0008 + ADR_0011 -.->|related| ADR_0009 ``` diff --git a/docs/decisions/relationships.mmd b/docs/decisions/relationships.mmd index e07a247..f36a1e0 100644 --- a/docs/decisions/relationships.mmd +++ b/docs/decisions/relationships.mmd @@ -9,5 +9,10 @@ flowchart LR ADR_0008["ADR-0008
Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress"] ADR_0009["ADR-0009
--json is a documented no-op; CLI output is always JSON"] ADR_0010["ADR-0010
Codex skill-creator's quick_validate.py incompatibility is not this project's problem"] + ADR_0011["ADR-0011
Expose ADR relationships as Mermaid and SVG navigation artifacts"] ADR_0006 -.->|related| ADR_0003 ADR_0006 -->|supersedes| ADR_0003 + ADR_0011 -.->|related| ADR_0001 + ADR_0011 -.->|related| ADR_0006 + ADR_0011 -.->|related| ADR_0008 + ADR_0011 -.->|related| ADR_0009 diff --git a/docs/decisions/relationships.svg b/docs/decisions/relationships.svg index 18dc6ae..7afc55f 100644 --- a/docs/decisions/relationships.svg +++ b/docs/decisions/relationships.svg @@ -1,4 +1,4 @@ - + ADR relationship graph Vector export of ADR supersession and related-decision links. @@ -34,8 +34,19 @@ ADR-0010 Codex skill-creator's quick_valid... + +ADR-0011 +Expose ADR relationships as Merma... -ADR-0006 related ADR-0003 +ADR-0006 related ADR-0003 -ADR-0006 supersedes ADR-0003 +ADR-0006 supersedes ADR-0003 + +ADR-0011 related ADR-0001 + +ADR-0011 related ADR-0006 + +ADR-0011 related ADR-0008 + +ADR-0011 related ADR-0009 diff --git a/handoff.md b/handoff.md index a942d74..d206060 100644 --- a/handoff.md +++ b/handoff.md @@ -2,55 +2,75 @@ ## Current task (2026-08-31) -`v0.2.0` release gate is in progress — the only remaining work -(`improvements.md`'s single open item). Everything else (multilingual ADR -generation, CHECK hardening, exception mechanism, ADR search and -relationship navigation) is implemented and merged into this branch; see -`changelog.md` for what shipped and `git log` for exactly how. +Public-readiness and ADR navigation graph work is implemented on branch +`feature/project-roadmap-implements` in: -Work runs in the linked worktree at -`/Users/yangseunghyeon/orca/workspaces/ADR-toolkit/develop-2`, branch -`feature/v0.2.0-multilingual-and-check-confidence`, pushed to `origin`. +`/Users/yangseunghyeon/orca/workspaces/ADR-toolkit/develop` + +Latest commits: + +- `8f0343e feat: add ADR relationship graph exports` +- ADR-0011 work is in progress in the working tree and should be committed + after final verification. + +Implemented in this session: + +- `adr.py graph --format mermaid|svg|both` exports deterministic Mermaid and + Python-rendered SVG artifacts. +- `adr.py index` embeds a GitHub-renderable Mermaid relationship graph in the + generated decision log. +- Public repository hygiene docs were added: + `CONTRIBUTING.md`, `SECURITY.md`, and `.github/PULL_REQUEST_TEMPLATE.md`. +- `project-roadmap.md` now lists only remaining unscheduled work; completed + graph/search/public-doc items were removed. +- ADR-0011 records the Mermaid/SVG graph and public readiness decision. ## Next step -**PR #2 open against `develop`, CI green (6/6), `mergeable`**: -https://github.com/SHcommit/ADR-toolkit/pull/2 +Commit the ADR-0011 follow-up after verification. Then open or update a PR for +`feature/project-roadmap-implements` into `develop`. -Release gate steps, each needing separate explicit owner approval — do not -perform any of the remaining ones without asking first, "CI is green" does -not imply approval for the next step: +The broader `v0.2.0` release gate still needs separate explicit owner approval +for each step: -1. ✅ **Done** — v0.2.0 version bump (`skills/adr-toolkit/VERSION` → - `0.2.0`, propagated via `scripts/sync_version.py`); CI re-ran green. -2. Merge PR #2 into `develop` (normal merge or squash — ask which). -3. Cut a `release/*` branch into `master` and back into `develop`, per +1. Merge the PR into `develop`. +2. Cut a `release/*` branch into `master` and back into `develop`, per `AGENTS.md`'s Git Flow policy. -4. Push a `v0.2.0` tag on `master` — - `.github/workflows/release.yml` then runs the full suite, verifies - manifest versions against the tag, and publishes the GitHub Release. +3. Push a `v0.2.0` tag on `master`; `.github/workflows/release.yml` then runs + the full suite, verifies manifest versions against the tag, and publishes + the GitHub Release. + +Do not perform merge, release branch, push, or tag operations without explicit +owner approval. + +## Latest local verification + +Before `8f0343e`: + +- `python3 -m pytest -q` -> `395 passed` +- `python3 scripts/sync_version.py --check` -> exit 0 +- `python3 skills/adr-toolkit/scripts/adr.py validate --dir docs/decisions --json` + -> 10 ADRs, no errors + +After ADR-0011 creation: -Latest local verification (2026-08-31): `python3 -m pytest -q` → `386 -passed`; `sync_version.py --check` → exit 0; `adr.py validate` → 10 ADRs, -no errors; `git status --short` → clean. +- `adr.py validate --dir docs/decisions --json` -> 11 ADRs, no errors +- `adr.py index --dir docs/decisions --json` -> 11 ADRs, no warnings +- `adr.py graph --dir docs/decisions --format both --json` -> 6 rendered graph + edges, no warnings -An open, undecided idea from this session: measuring how much LLM-token -input/output the deterministic core saves an agent versus doing the same -work by hand (e.g. CHECK returning a structured finding instead of the -agent scanning a full diff). Deliberately deferred — no scope or -destination decided. +Run the full verification suite again before the ADR-0011 commit. ## Open risks -- CHECK deliberately cannot prove prose, business rationale, or - organizational claims; those remain human-review evidence. The exception - mechanism extends this — an exception is recorded and annotated, never a - silent pass. +- `adr.py check --uncommitted` reports a `VIOLATED` finding for superseded + ADR-0003 when changes touch `index.py`; this branch intentionally follows + ADR-0006, which supersedes ADR-0003. Note that in PR review rather than + treating it as a code defect. +- CHECK deliberately cannot prove prose, business rationale, or organizational + claims; those remain human-review evidence. - Search/relationship matching is deterministic substring/exact/prefix - matching, untested at real scale (this repo has 10 ADRs); revisit - `project-roadmap.md`'s navigation-and-scale items if that changes. + matching, untested at real scale. Revisit roadmap scale items if an adopting + repository reaches hundreds of ADRs. - GitHub branch/tag protection is intentionally unavailable on the current - private plan; configure and API-verify after the repository goes public - (owner's own follow-up, not scheduled here). -- Version bump is done; release branch, push, and tag remain unauthorized - until the owner approves each step above. + private plan; configure and API-verify after the repository goes public. From b8db21157c499174ea1c108ab597e77c6ef0a316 Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 14:40:45 +0900 Subject: [PATCH 3/8] docs: correct stale ADR count in project-roadmap.md ADR-0011 brought the count to 11; the navigation-and-scale note still referenced 10. --- project-roadmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project-roadmap.md b/project-roadmap.md index 6a16644..70a06d3 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -23,7 +23,7 @@ before implementation. Concrete selected work belongs in `improvements.md`. ## ADR navigation and scale - Test whether 500+ decisions require sharding, alternate indexes, or a - real search index (this repo has 10 ADRs; substring/tag/path matching is + real search index (this repo has 11 ADRs; substring/tag/path matching is untested at that scale). - Improve related-decision discovery beyond path/tag/keyword/body-substring only after real misses demonstrate the need for semantic retrieval. From b1bcb9370f353e733010de6d6cd04363d8975b0c Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 14:57:41 +0900 Subject: [PATCH 4/8] docs: add code of conduct and GitHub issue templates Baseline open-source hygiene ahead of the repository going public: a Code of Conduct with report/enforcement contact matching SECURITY.md's convention, and bug report / feature request issue templates scoped to this project's commands and existing roadmap/improvements tracking. --- .github/ISSUE_TEMPLATE/bug_report.md | 39 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 19 ++++++++++ CODE_OF_CONDUCT.md | 44 +++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 CODE_OF_CONDUCT.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2c4e92b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Report a defect in ADR Toolkit +title: "" +labels: bug +--- + +## Summary + +## Steps to Reproduce + +1. +2. +3. + +## Expected Behavior + +## Actual Behavior + +## Environment + +- Harness: Claude Code / Codex CLI / Gemini CLI / Antigravity CLI / generic / other +- ADR Toolkit version (`skills/adr-toolkit/VERSION`): +- OS: + +## Relevant Output + +Paste the command and its output, including `--json` output where +applicable. + +``` +``` + +## Scope + +- [ ] This affects `adr.py validate`, `create`, `index`, `check`, `graph`, + or `search`. +- [ ] This affects a specific harness adapter (Codex, Gemini CLI, + Antigravity, or generic). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..4f7f064 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Propose new functionality for ADR Toolkit +title: "" +labels: enhancement +--- + +## Problem + +What are you trying to do that ADR Toolkit does not support today? + +## Proposed Solution + +## Alternatives Considered + +## Additional Context + +- [ ] I checked `project-roadmap.md` and `improvements.md` for existing + plans that already cover this. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f8fb524 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,44 @@ +# Code of Conduct + +## Our Standard + +Participation in this project — issues, pull requests, discussions, and +reviews — is expected to stay respectful and focused on the technical +problem. Disagreement about a decision or an approach is normal; personal +attacks, harassment, discriminatory language, or deliberately derailing a +discussion are not. + +## Expected Behavior + +- Assume good faith and give reviewers/contributors the benefit of the + doubt. +- Keep feedback specific to the code, design, or decision under + discussion. +- Accept that maintainers may decline a change and explain why, without + that being an invitation to escalate. + +## Unacceptable Behavior + +- Harassment, insults, or discriminatory remarks directed at a person + rather than their contribution. +- Publishing others' private information without consent. +- Sustained disruption of issues, pull requests, or reviews. + +## Reporting + +Report a violation privately to the repository owner rather than escalating +in a public issue or pull request thread. Include what happened, where +(link to the issue/PR/comment), and when. + +## Enforcement + +The repository owner reviews reports and may edit or remove content, warn a +participant, or restrict further participation in the project's spaces, +proportional to the violation. Enforcement decisions are not owed a public +explanation when doing so would expose reporter identity or private +details. + +## Scope + +This applies to all project spaces (issues, pull requests, discussions, +commits, and comments) and to representing the project in public spaces. From e28eb5dbcfc85302d47bd10ee4e508b85ee36c3e Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 15:12:33 +0900 Subject: [PATCH 5/8] docs: record Antigravity CLI adapter verification against agy 1.1.13 Re-ran the same validate/install/list/script-layer checks documented for the Codex and Gemini CLI adapters, this time against the agy CLI, which is in fact available in this environment (contrary to the prior README note). All three script commands (preflight, init, validate) returned "ok": true from the agy-installed snapshot in an isolated HOME, closing the one real Harness parity gap: Antigravity was the only adapter with no end-to-end verification on record. --- adapters/antigravity/README.md | 65 +++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/adapters/antigravity/README.md b/adapters/antigravity/README.md index fc9331b..205d586 100644 --- a/adapters/antigravity/README.md +++ b/adapters/antigravity/README.md @@ -3,6 +3,9 @@ Antigravity plugins are a `plugin.json` marker file plus optional sibling directories (`skills/`, `agents/`, `rules/`), per `antigravity.google/docs/cli/plugins/`. This manifest needs only `name`. +**Manually verified against Antigravity's `agy` CLI 1.1.13** (`agy +--version`): validate, install, and discovery all work — see "Verification +status" below. ## Install @@ -14,18 +17,62 @@ directories (`skills/`, `agents/`, `rules/`), per ln -s "$(pwd)/skills/adr-toolkit" adapters/antigravity/skills/adr-toolkit ``` -2. Install per Antigravity's own plugin-install documentation. -3. Confirm Antigravity lists `adr-toolkit`, then run +2. Install with Antigravity's own plugin CLI: + + ```bash + agy plugin install "$(pwd)/adapters/antigravity" + ``` + +3. Confirm Antigravity lists `adr-toolkit`: + + ```bash + agy plugin list + ``` + +4. Either way, run `python skills/adr-toolkit/scripts/adr.py preflight --json` from your target repository to confirm the script layer works standalone. -**Verification status:** this adapter's manifest and directory layout are -verified against Antigravity's published schema, but — unlike the Codex -and Gemini CLI adapters — no end-to-end install-and-run was performed, -because no `antigravity` CLI binary was available in the development -environment that built this adapter. Treat it as unverified until someone -with the actual CLI confirms it, and update this note when they do. - The symlink is created at install time, not committed to this repo — committing a real symlink breaks on Windows checkouts that don't have `core.symlinks` enabled, which this project's CI can't assume. + +## Verification status + +Manually verified against Antigravity's `agy` CLI 1.1.13 (`agy --version`) +in an isolated `HOME=$(mktemp -d)` so no state was written to the real +`~/.gemini` (Antigravity's plugin CLI stores state under `.gemini/config/` +in the active home directory). + +``` +$ agy plugin validate "$(pwd)/adapters/antigravity" + [ok] /adapters/antigravity + [check] skills : 1 processed + - agents : skipped (not found) + - commands : skipped (not found) + - mcpServers : skipped (not found) + - hooks : skipped (not found) + +$ agy plugin install "$(pwd)/adapters/antigravity" + [ok] adr-toolkit + [check] skills : 1 processed + +$ agy plugin list +{ + "imports": [ + { + "name": "adr-toolkit", + "source": "antigravity", + "importedAt": "2026-08-31T06:11:34Z", + "components": ["skills"] + } + ] +} +``` + +The installed snapshot lands under `/.gemini/config/plugins/adr-toolkit/` +and carries the whole `skills/adr-toolkit/` package. The script layer was +run directly out of it against a scratch git repository, producing correct +`"ok": true` output for `preflight`, `init --dir docs/decisions`, and +`validate --dir docs/decisions` — matching the Codex and Gemini CLI +adapters' verified depth. From 2f2f604d6c58b0f8a2af2081ec8721b6f771a5a4 Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 15:26:56 +0900 Subject: [PATCH 6/8] ci: add harness-parity job for Codex and Gemini CLI end-to-end install Existing tests only check that adapter manifests are schema-valid JSON; nothing catches an upstream CLI change or a manifest edit that breaks real installation. This job installs the real Codex CLI (0.151.0) and Gemini CLI (0.46.0) -- the exact versions manually verified in adapters/codex and adapters/gemini-cli's READMEs -- and drives their own plugin/extension commands (marketplace add, install, list) against this repo, then runs preflight/init/validate from the installed snapshot and asserts "ok": true on each. Every step was dry-run locally against those exact CLI versions before landing this. Antigravity CLI (agy) has no npm/package-registry distribution, so it stays a manually verified adapter only per adapters/antigravity/README.md. --- .github/workflows/test.yml | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f20f9ec..5fb11b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,3 +42,56 @@ jobs: python-version: "3.12" - name: Check manifest versions and descriptions are in sync run: python scripts/sync_version.py --check + + harness-parity: + # Installs the real Codex CLI and Gemini CLI and drives their own plugin + # commands against this repo, the same way a contributor would. Manifest + # JSON shape is already covered by tests/unit/test_*_adapter.py; this job + # instead proves discovery -> install -> list -> the installed skill + # package's script layer still runs, so an upstream CLI change or a + # manifest edit that breaks real installation fails CI instead of + # surfacing later as a user-reported install failure. Antigravity CLI + # (agy) has no public package registry distribution, so it stays a + # manually verified adapter only -- see adapters/antigravity/README.md. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install Codex CLI + run: npm install -g @openai/codex@0.151.0 + - name: Install Gemini CLI + run: npm install -g @google/gemini-cli@0.46.0 + - name: Verify Codex CLI adapter end to end + run: | + set -euo pipefail + export CODEX_HOME="$(mktemp -d)" + REPO_ROOT="$(pwd)" + codex plugin marketplace add "$REPO_ROOT" + INSTALLED_PATH="$(codex plugin add adr-toolkit@adr-toolkit-marketplace --json | jq -r '.installedPath')" + codex plugin list + SCRATCH="$(mktemp -d)" + cd "$SCRATCH" && git init -q + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" preflight --json | jq -e '.ok == true' + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" init --dir docs/decisions --json | jq -e '.ok == true' + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" validate --dir docs/decisions --json | jq -e '.ok == true' + - name: Verify Gemini CLI adapter end to end + run: | + set -euo pipefail + REPO_ROOT="$(pwd)" + mkdir -p adapters/gemini-cli/skills + ln -s "$REPO_ROOT/skills/adr-toolkit" adapters/gemini-cli/skills/adr-toolkit + export HOME="$(mktemp -d)" + gemini extensions validate "$REPO_ROOT/adapters/gemini-cli" + printf 'y\ny\n' | gemini extensions install "$REPO_ROOT/adapters/gemini-cli" --consent + gemini extensions list + INSTALLED_PATH="$HOME/.gemini/extensions/adr-toolkit" + SCRATCH="$(mktemp -d)" + cd "$SCRATCH" && git init -q + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" preflight --json | jq -e '.ok == true' + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" init --dir docs/decisions --json | jq -e '.ok == true' + python3 "$INSTALLED_PATH/skills/adr-toolkit/scripts/adr.py" validate --dir docs/decisions --json | jq -e '.ok == true' From 3058325c7cd69574395d59f51f3cdb9ca8480d78 Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 15:27:57 +0900 Subject: [PATCH 7/8] docs: close out harness-parity session in handoff and roadmap project-roadmap.md's Harness parity section now reflects that Codex/Gemini CLI install verification is automated in CI; the remaining gaps are extending coverage past preflight/init/validate and automating Antigravity once it has an installable distribution. handoff.md rewritten to describe what this session actually did instead of the stale ADR-0011-in-progress state, and flags that harness-parity has only run locally, never on a real GitHub Actions runner yet. --- handoff.md | 82 ++++++++++++++++++++++++++++++++-------------- project-roadmap.md | 14 ++++++-- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/handoff.md b/handoff.md index d206060..4bdfde1 100644 --- a/handoff.md +++ b/handoff.md @@ -2,33 +2,59 @@ ## Current task (2026-08-31) -Public-readiness and ADR navigation graph work is implemented on branch +Public-readiness, ADR navigation graph, and harness-parity work is on branch `feature/project-roadmap-implements` in: `/Users/yangseunghyeon/orca/workspaces/ADR-toolkit/develop` -Latest commits: +Latest commits (newest first): +- `2f2f604 ci: add harness-parity job for Codex and Gemini CLI end-to-end install` +- `e28eb5d docs: record Antigravity CLI adapter verification against agy 1.1.13` +- `b1bcb93 docs: add code of conduct and GitHub issue templates` +- `b8db211 docs: correct stale ADR count in project-roadmap.md` +- `35c81aa docs: record ADR-0011 for relationship graph and public readiness work` - `8f0343e feat: add ADR relationship graph exports` -- ADR-0011 work is in progress in the working tree and should be committed - after final verification. -Implemented in this session: +Working tree is clean; every item below is already committed. + +Implemented this session: - `adr.py graph --format mermaid|svg|both` exports deterministic Mermaid and - Python-rendered SVG artifacts. -- `adr.py index` embeds a GitHub-renderable Mermaid relationship graph in the - generated decision log. -- Public repository hygiene docs were added: - `CONTRIBUTING.md`, `SECURITY.md`, and `.github/PULL_REQUEST_TEMPLATE.md`. -- `project-roadmap.md` now lists only remaining unscheduled work; completed - graph/search/public-doc items were removed. -- ADR-0011 records the Mermaid/SVG graph and public readiness decision. + Python-rendered SVG artifacts; `adr.py index` embeds the Mermaid graph in + the generated decision log. ADR-0011 records this decision. +- Public repository hygiene: `CONTRIBUTING.md`, `SECURITY.md`, + `.github/PULL_REQUEST_TEMPLATE.md`, `CODE_OF_CONDUCT.md`, and + `.github/ISSUE_TEMPLATE/{bug_report,feature_request}.md`. +- Harness parity, manually re-verified end-to-end against the real CLIs + installed on this machine (not just manifest-schema checks): + - Codex CLI 0.151.0 -- `codex plugin marketplace add/add/list`, then + `preflight`/`init`/`validate` from the installed snapshot, all + `"ok": true`. + - Gemini CLI 0.46.0 -- `gemini extensions validate/install/list`, then the + same three commands from the installed snapshot, all `"ok": true`. + - Antigravity CLI (`agy` 1.1.13) -- previously undocumented as available; + `agy plugin validate/install/list` plus the same three commands all + `"ok": true`. `adapters/antigravity/README.md` updated from "unverified" + to a recorded transcript, matching the Codex/Gemini READMEs' format. + - New `harness-parity` CI job (`.github/workflows/test.yml`) automates the + Codex and Gemini checks above on every push/PR, pinned to the exact + verified versions. Every step was dry-run locally against those same + CLI versions before landing. Antigravity stays manual-only: `agy` has no + npm/package-registry distribution, so a CI runner can't install it. + - `project-roadmap.md`'s "Harness parity" section narrowed to what's + actually still open: extending `harness-parity` past + preflight/init/validate to check/search/graph/create, and automating + Antigravity once it has an installable distribution. +- `project-roadmap.md` audited end-to-end against the codebase; confirmed no + other section describes already-shipped work. ## Next step -Commit the ADR-0011 follow-up after verification. Then open or update a PR for -`feature/project-roadmap-implements` into `develop`. +Open or update a PR for `feature/project-roadmap-implements` into `develop` +so the new `harness-parity` CI job actually runs on GitHub's runners at +least once before being trusted (everything above was dry-run locally with +the same pinned CLI versions, but never through the real Actions runner). The broader `v0.2.0` release gate still needs separate explicit owner approval for each step: @@ -41,28 +67,35 @@ for each step: the GitHub Release. Do not perform merge, release branch, push, or tag operations without explicit -owner approval. +owner approval. The repository owner has said the switch to public is coming +"soon" but deliberately not yet -- don't start the "Public and enterprise +governance" roadmap items (they're gated on the repo actually being public). ## Latest local verification -Before `8f0343e`: +After `2f2f604` (current HEAD): - `python3 -m pytest -q` -> `395 passed` - `python3 scripts/sync_version.py --check` -> exit 0 -- `python3 skills/adr-toolkit/scripts/adr.py validate --dir docs/decisions --json` - -> 10 ADRs, no errors - -After ADR-0011 creation: - - `adr.py validate --dir docs/decisions --json` -> 11 ADRs, no errors - `adr.py index --dir docs/decisions --json` -> 11 ADRs, no warnings - `adr.py graph --dir docs/decisions --format both --json` -> 6 rendered graph edges, no warnings +- `harness-parity` job's Codex and Gemini steps dry-run locally against + Codex CLI 0.151.0 and Gemini CLI 0.46.0 -- both passed, matching what the + workflow file runs. -Run the full verification suite again before the ADR-0011 commit. +Run the full verification suite again before any further commit, and after +the PR's first real CI run, confirm `harness-parity` actually went green on +GitHub's runners (not just locally) before relying on it as a gate. ## Open risks +- `harness-parity`'s Codex/Gemini steps have only ever run locally on this + macOS dev machine, never on GitHub's `ubuntu-latest` runners. Watch the + first real CI run for environment differences (npm registry access, + network egress rules, `jq` availability -- present by default on + `ubuntu-latest` but unconfirmed here). - `adr.py check --uncommitted` reports a `VIOLATED` finding for superseded ADR-0003 when changes touch `index.py`; this branch intentionally follows ADR-0006, which supersedes ADR-0003. Note that in PR review rather than @@ -73,4 +106,5 @@ Run the full verification suite again before the ADR-0011 commit. matching, untested at real scale. Revisit roadmap scale items if an adopting repository reaches hundreds of ADRs. - GitHub branch/tag protection is intentionally unavailable on the current - private plan; configure and API-verify after the repository goes public. + private plan; configure and API-verify after the repository goes public + (owner has deferred this deliberately, not blocked on it). diff --git a/project-roadmap.md b/project-roadmap.md index 70a06d3..3054e4e 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -15,8 +15,18 @@ before implementation. Concrete selected work belongs in `improvements.md`. ## Harness parity -- Full cross-harness fixture/golden matrix for Codex, Gemini CLI, and - Antigravity CLI at the depth currently available for Claude Code. +- ~~Automate the Codex CLI and Gemini CLI adapters' install-and-run + verification.~~ **Done (2026-08-31).** `.github/workflows/test.yml`'s + `harness-parity` job installs the real Codex CLI and Gemini CLI and runs + `preflight`/`init`/`validate` from each one's installed snapshot on every + push and pull request. +- Extend `harness-parity` coverage beyond `preflight`/`init`/`validate` to + `check`, `search`, `graph`, and `create` once a real regression in one of + those commands under a specific harness demonstrates the gap matters. +- Automate the Antigravity CLI (`agy`) adapter the same way once it has a + package-registry distribution a CI runner can install non-interactively; + today it has none, so `adapters/antigravity/README.md`'s manual + verification is the only signal. - Harness-specific hook support beyond Claude Code SessionStart when equivalent stable extension points exist. From 178394e8a2c8f6e813d0b50621e21c4a1ae2fb25 Mon Sep 17 00:00:00 2001 From: shcommit Date: Mon, 31 Aug 2026 16:03:02 +0900 Subject: [PATCH 8/8] docs: record SessionStart hook evaluation as not pursued Codex CLI and Gemini CLI now both have stable hook extension points (hooks.json SessionStart/UserPromptSubmit; gemini hooks migrate), so the roadmap item's precondition is technically satisfied. Decided not to pursue it anyway: ADR Toolkit doesn't use a hook even on Claude Code today (pure skill auto-discovery), an always-on session hook cuts against the project's own minimal-interruption principle, and the plausible use cases are already covered by deliberately invoking discover/check. Recorded so the same discussion doesn't have to happen again from scratch. --- project-roadmap.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/project-roadmap.md b/project-roadmap.md index 3054e4e..d64bf98 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -27,8 +27,21 @@ before implementation. Concrete selected work belongs in `improvements.md`. package-registry distribution a CI runner can install non-interactively; today it has none, so `adapters/antigravity/README.md`'s manual verification is the only signal. -- Harness-specific hook support beyond Claude Code SessionStart when equivalent - stable extension points exist. +- ~~Harness-specific hook support beyond Claude Code SessionStart when + equivalent stable extension points exist.~~ **Evaluated, not pursued + (2026-08-31).** The precondition is now true: Codex CLI has a config-driven + `SessionStart`/`UserPromptSubmit` hook system (`~/.codex/hooks.json`), and + Gemini CLI ships `gemini hooks migrate` specifically to port Claude Code + hooks over. But ADR Toolkit doesn't use a hook even on Claude Code today + (it relies entirely on skill auto-discovery), and a hook that fires on + every session regardless of relevance cuts against this project's own + restraint principle (max 3 questions, judge what's significant, minimize + interruption). The plausible use cases (nudge about an unfinished draft + ADR, warn about a governed path) are already covered by deliberately + invoking `discover` and `check` rather than an always-on hook. Revisit + only if real usage shows people miss something that `discover`/`check` + can't catch without a session-start nudge -- not just because the + extension points now exist. ## ADR navigation and scale