|
| 1 | +<!doctype html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | +<meta charset="utf-8"> |
| 5 | +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | +<title>Contributing — GraphARC</title> |
| 7 | +<meta name="description" content="Tests, the live-marker rule, PR conventions, and the honesty rule for docs."> |
| 8 | +<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📚</text></svg>"> |
| 9 | +<link rel="stylesheet" href="style.css"> |
| 10 | +</head> |
| 11 | +<body> |
| 12 | +<div class="layout"> |
| 13 | +<nav class="side" aria-label="Documentation contents"> |
| 14 | + <a class="brand" href="index.html"><b>GraphARC<span class="amp"> docs</span></b><span>a governed agent runtime on LangGraph</span></a> |
| 15 | + <ul> |
| 16 | +<li class="nav-chapter"><a href="index.html">Overview</a> |
| 17 | +</li> |
| 18 | +<li class="nav-chapter"><a href="getting-started.html">Getting started</a> |
| 19 | +</li> |
| 20 | +<li class="nav-chapter"><a href="models.html">Models</a> |
| 21 | +</li> |
| 22 | +<li class="nav-chapter"><a href="agents-and-tools.html">Agents & tools</a> |
| 23 | +</li> |
| 24 | +<li class="nav-chapter"><a href="verification-and-memory.html">Verification & memory</a> |
| 25 | +</li> |
| 26 | +<li class="nav-chapter"><a href="governance.html">Governance</a> |
| 27 | +</li> |
| 28 | +<li class="nav-chapter"><a href="serving-and-ops.html">Serving & operations</a> |
| 29 | +</li> |
| 30 | +<li class="nav-chapter"><a href="roadmap.html">Roadmap</a> |
| 31 | +</li> |
| 32 | +<li class="nav-chapter"><a class="active" href="contributing.html">Contributing</a> |
| 33 | +<ul><li><a href="#intro">Intro</a></li><li><a href="#setup">Setup</a></li><li><a href="#running-the-tests">Running the tests</a></li><li><a href="#the-live-marker-rule">The live-marker rule</a></li><li><a href="#writing-a-change-pr-conventions">Writing a change (PR conventions</a></li><li><a href="#the-honesty-rule-for-documentation">The honesty rule for documentation</a></li><li><a href="#packaging-changes">Packaging changes</a></li><li><a href="#releasing">Releasing</a></li></ul> |
| 34 | +</li> |
| 35 | + </ul> |
| 36 | +</nav> |
| 37 | +<main> |
| 38 | +<header class="page-head"> |
| 39 | + <h1>Contributing</h1> |
| 40 | + <p class="sub">Tests, the live-marker rule, PR conventions, and the honesty rule for docs.</p> |
| 41 | +</header> |
| 42 | +<h2 id="intro"><a class="anchor" href="#intro">Intro</a></h2> |
| 43 | +<p>The file covers the three things easiest to get wrong in this repo: how to run the tests, the rule that keeps live tests from spending your money, and the standard the project holds documentation to.</p> |
| 44 | +<h2 id="setup"><a class="anchor" href="#setup">Setup</a></h2> |
| 45 | +<p>The toolchain is uv. <code>uv.lock</code> is committed, so everyone resolves to the same versions. <code>--all-extras</code> matters, because several tests skip themselves when an optional dependency is missing — syncing only the dev group silently shrinks the suite instead of failing.</p> |
| 46 | +<figure class="term"><figcaption class="term-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="term-cmd">bash</span></figcaption><pre class="term-body">git clone https://github.com/CodeGraphContext/GraphARC |
| 47 | +cd GraphARC |
| 48 | +uv sync --all-extras --group dev</pre></figure> |
| 49 | +<h2 id="running-the-tests"><a class="anchor" href="#running-the-tests">Running the tests</a></h2> |
| 50 | +<p>Standard pytest invocations for the whole suite, a single file, a pattern, or first-failure. Lint and autofix with ruff. Both are what CI runs (<code>.github/workflows/ci.yml</code>), across Python 3.12, 3.13 and 3.14.</p> |
| 51 | +<figure class="term"><figcaption class="term-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="term-cmd">bash</span></figcaption><pre class="term-body">uv run pytest # the whole suite |
| 52 | +uv run pytest tests/test_server.py # one file |
| 53 | +uv run pytest -k retrieval # one pattern |
| 54 | +uv run pytest -x -q # stop at the first failure</pre></figure> |
| 55 | +<figure class="term"><figcaption class="term-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="term-cmd">bash</span></figcaption><pre class="term-body">uv run ruff check . --fix</pre></figure> |
| 56 | +<h2 id="the-live-marker-rule"><a class="anchor" href="#the-live-marker-rule">The live-marker rule</a></h2> |
| 57 | +<p><strong>A test marked <code>live</code> calls a real model backend and spends real money.</strong> Never run <code>pytest -m live</code> casually, never in CI, and never because a failing test looked like it might pass against a real model. The mechanics, so you can tell when they are broken: <code>addopts</code> in <code>pyproject.toml</code> carries <code>-m 'not live'</code> and must stay there; <code>addopts</code> also carries <code>--strict-markers</code>, which is part of the same guarantee rather than a style preference, since without it a misspelled <code>@pytest.mark.lvie</code> is only a warning and the test calls the API on a plain <code>pytest</code>; every marker must be registered in the <code>markers</code> table before use, and <code>tests/test_packaging.py</code> fails if the tree uses one that is not; <code>required_plugins</code> names <code>pytest-asyncio</code> and <code>pytest-timeout</code>, because registering their markers locally would make a missing plugin silent with no timeout enforced; and CI has a <code>live-marker-guard</code> job comparing the default selection against the <code>-m live</code> selection, failing if anything is in both.</p> |
| 58 | +<figure class="term"><figcaption class="term-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="term-cmd">bash</span></figcaption><pre class="term-body">pytest # 'not live' — every paid test is deselected |
| 59 | +pytest -m live # opt in, deliberately, with your own key</pre></figure> |
| 60 | +<div class="codeblock"><div class="code-lang">python</div><pre class="code-body">@pytest.mark.live |
| 61 | +<span class="tok-k">def</span> test_something_against_a_real_backend(): |
| 62 | + ...</pre></div> |
| 63 | +<aside class="edge"><span class="edge-tag">sharp edge</span> Mark a live test, keep it out of any fixture the default suite touches, and say in the test what it costs to run.</aside> |
| 64 | +<h2 id="writing-a-change-pr-conventions"><a class="anchor" href="#writing-a-change-pr-conventions">Writing a change (PR conventions</a></h2> |
| 65 | +<p><strong>One concern per PR</strong> — a fix and the refactor around it are two PRs. <strong>A bug fix comes with a test that fails without it</strong>: prove it by reverting the fix, watching the new test go red, and putting the fix back; a test that passes either way is documentation, not a regression guard. <strong>Never weaken an existing test to make a change pass</strong> — if an assertion is genuinely wrong, say so in the PR and explain why in the same breath as changing it. <strong>Keep <code>ruff check .</code> clean</strong>; line length is 100. <strong>Note behaviour changes in the commit message</strong> when you change behaviour, the public API, or what ships.</p> |
| 66 | +<h2 id="the-honesty-rule-for-documentation"><a class="anchor" href="#the-honesty-rule-for-documentation">The honesty rule for documentation</a></h2> |
| 67 | +<p>Enforced hardest, because this repo has broken it before and the ROADMAP still carries a <code>!</code> legend entry for claims that shipped while being false. <strong>A docstring, a README line, or a comment must not claim a guarantee the code does not provide</strong> — not "aspirationally", not "once the TODO lands", not because the happy path happens to hold. Concretely: if a function is confined, sandboxed, budgeted, atomic, ordered or durable <strong>only under conditions</strong>, name the conditions in the docstring (<code>render_context</code> documents the single case where it overshoots <code>max_tokens</code>; <code>LocalExecutor</code> is named for what it does not do). If something is defense in depth rather than a boundary, say which (<code>SandboxedExecutor</code> is an audit-hook sandbox, not a kernel boundary, and its docstring says so). If a name oversells the thing — the way an exact-string match was once labelled "GraphRAG" — rename it or write down precisely what it does (<code>HashingEmbedder</code>'s docstring states it is lexical, not semantic). If an extra, config key or parameter exists but nothing implements it yet, say that where a reader will hit it — the optional-dependency table in <code>pyproject.toml</code> states, per extra, whether anything under <code>grapharc/</code> imports it today. When you fix something, fix the prose in the same commit: a stale docstring that used to be true is exactly as harmful as one that was never true.</p> |
| 68 | +<aside class="edge"><span class="edge-tag">sharp edge</span> The failure mode to avoid is confident prose over a guarantee the code does not provide. A missing feature is fine; a feature documented as working that is not will cost somebody a debugging session, or will be trusted with something it cannot hold.</aside> |
| 69 | +<aside class="edge"><span class="edge-tag">sharp edge</span> If you cannot close a gap, write the gap down — <code>ROADMAP.md</code> has a <em>Known gaps</em> section for exactly that.</aside> |
| 70 | +<h2 id="packaging-changes"><a class="anchor" href="#packaging-changes">Packaging changes</a></h2> |
| 71 | +<p><code>pyproject.toml</code> is the authority for what ships. <code>[tool.hatch.build.targets.sdist].include</code> is an allowlist, so a new top-level file that should ship has to be added there; <code>MANIFEST.in</code> mirrors that list for readers and tools, hatchling never reads it, and <code>tests/test_packaging.py</code> fails if the two drift. <code>[tool.hatch.build].ignore-vcs</code> is on deliberately, because with hatchling's default a <code>.gitignore</code> entry doubles as a build exclusion — verified to drop the whole <code>grapharc/tools</code> subpackage out of the wheel with no error and a successful build; junk you want kept out belongs in <code>[tool.hatch.build].exclude</code>. After a packaging change, build and install into a throwaway environment rather than trusting the build's exit code. CI does the same on every PR, for both the wheel and the sdist.</p> |
| 72 | +<figure class="term"><figcaption class="term-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="term-cmd">bash</span></figcaption><pre class="term-body">uv build |
| 73 | +uv venv /tmp/check && uv pip install --python /tmp/check/bin/python "$(echo dist/*.whl)[all]" |
| 74 | +cd /tmp && /tmp/check/bin/python -c "import grapharc, pkgutil, importlib |
| 75 | +[importlib.import_module(m.name) for m in pkgutil.walk_packages(grapharc.__path__, 'grapharc.')]" |
| 76 | +/tmp/check/bin/grapharc --version</pre></figure> |
| 77 | +<h2 id="releasing"><a class="anchor" href="#releasing">Releasing</a></h2> |
| 78 | +<p>Tag-driven. <code>.github/workflows/release.yml</code> refuses a tag that disagrees with the version in <code>pyproject.toml</code>, builds, verifies the artifacts in clean environments, and publishes through PyPI Trusted Publishing. There is no API token in this repository or in its secrets. Steps: bump <code>version</code> in <code>pyproject.toml</code> <strong>and</strong> <code>__version__</code> in <code>grapharc/__init__.py</code> (CI fails if the two disagree); tag the release, with the commit log as the record of what changed; tag <code>vX.Y.Z</code> and push it.</p> |
| 79 | +<aside class="edge"><span class="edge-tag">sharp edge</span> The three numbered steps are partly redundant — step 2 and step 3 both describe tagging.</aside> |
| 80 | +<p>Source files read in full: <code>/home/shashank/Desktop/GraphARC/README.md</code>, <code>/home/shashank/Desktop/GraphARC/ROADMAP.md</code>, <code>/home/shashank/Desktop/GraphARC/CONTRIBUTING.md</code>. Referenced but not read: <code>/home/shashank/Desktop/GraphARC/docs/diagrams/</code> (architecture.png, 03-agent-node.png, grapharc-architecture.drawio, architecture.py), <code>/home/shashank/Desktop/GraphARC/docs/cookbook/</code>, <code>/home/shashank/Desktop/GraphARC/grapharc/examples/plan_incident.py</code>, <code>/home/shashank/Desktop/GraphARC/grapharc/policy/example.toml</code>, <code>/home/shashank/Desktop/GraphARC/grapharc/stdlib.py</code>.</p> |
| 81 | +<footer class="pager-row"> |
| 82 | +<a class="pager prev" href="roadmap.html">← Roadmap</a><span class="pager-spacer"></span> |
| 83 | +</footer> |
| 84 | +<footer class="colophon"> |
| 85 | + Generated from the repository's README and cookbook (docs/cookbook/01–06) · |
| 86 | + <a href="https://github.com/CodeGraphContext/GraphARC">CodeGraphContext/GraphARC</a> · MIT license |
| 87 | +</footer> |
| 88 | +</main> |
| 89 | +</div> |
| 90 | + |
| 91 | +<script> |
| 92 | +(function () { |
| 93 | + var links = document.querySelectorAll('.nav-chapter ul a'); |
| 94 | + var byId = {}; |
| 95 | + links.forEach(function (a) { byId[a.getAttribute('href').slice(1)] = a; }); |
| 96 | + var current = null; |
| 97 | + var obs = new IntersectionObserver(function (entries) { |
| 98 | + entries.forEach(function (e) { |
| 99 | + if (e.isIntersecting) { |
| 100 | + var a = byId[e.target.id]; |
| 101 | + if (a) { |
| 102 | + if (current) current.classList.remove('active'); |
| 103 | + a.classList.add('active'); |
| 104 | + current = a; |
| 105 | + } |
| 106 | + } |
| 107 | + }); |
| 108 | + }, { rootMargin: '0px 0px -70% 0px' }); |
| 109 | + document.querySelectorAll('main h2[id]').forEach(function (h) { obs.observe(h); }); |
| 110 | +})(); |
| 111 | +</script> |
| 112 | +</body> |
| 113 | +</html> |
0 commit comments