diff --git a/CLAUDE.md b/CLAUDE.md index 753b3b5..286e21a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ python3 install.py # --cli-only, --dev, --prefix, --verify, --uninstal # Pipe a single command (non-interactive; cli.py detects a non-tty stdin) echo '/search "q2 plan"' | python3 -m glean_code -# Run the full test suite (1,148 tests, stdlib unittest โ€” works with or without pytest) +# Run the full test suite (1,151 tests, stdlib unittest โ€” works with or without pytest) python3 -m pytest tests/ python3 -m unittest discover tests/ diff --git a/README.md b/README.md index d6570e6..76766c5 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ The full Glean Code REPL โ€” slash commands, status bar, mock/live switching, se | ๐Ÿ” **[SSO / OAuth](docs/SSO_OAUTH.md)** ยท **[Secure tokens](docs/SECURE_TOKENS.md)** | Browser sign-in, secure refs, the masking matrix | | ๐Ÿ”Œ **[MCP server](docs/MCP.md)** | Glean as native tools in Claude Code, Claude Desktop, Cursor | | ๐Ÿ›๏ธ **[Architecture](docs/ARCHITECTURE.md)** ยท **[REST paths](docs/REST_PATHS.md)** | Module map, request flow, endpoints, how to add a command | -| โœ… **[Testing](docs/TESTING.md)** | Running the 1,148-test suite and what it covers | +| โœ… **[Testing](docs/TESTING.md)** | Running the 1,151-test suite and what it covers | | ๐Ÿ›Ÿ **[Support](SUPPORT.md)** ยท **[Changelog](CHANGELOG.md)** | How to report a bug ยท release history | > [!NOTE] diff --git a/assets/graph_example.png b/assets/graph_example.png new file mode 100644 index 0000000..fa626cd Binary files /dev/null and b/assets/graph_example.png differ diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index a0a4bf2..a010ad1 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -589,7 +589,28 @@ Four kinds of node, four kinds of edge, and every edge carries the evidence that /graph "access review" --datasource confluence --min-shared 3 ``` -**Output** โ€” A terminal summary: node and edge counts by kind, the hubs by degree, connected clusters with the node anchoring each, and the strongest content links with their shared terms. With `--html`, also an interactive page: pan, zoom, drag, and click a node to see every edge and why it exists. +**Output** โ€” A terminal summary: node and edge counts by kind, the hubs by degree, connected clusters with the node anchoring each, and the strongest content links with their shared terms. + +```text +27 nodes 10 doc ยท 4 person ยท 5 source ยท 8 container +43 edges 10 authored_by ยท 10 in_source ยท 10 in_container ยท 13 shares_term + +hubs + โ–ช doc Quarterly Access Review Procedure 9 edges + โ–ช doc SEC-241 โ€” Enforce MFA on internal service dashboards 7 edges + โ—† person Nina Kowalski 6 edges + +strongest content links + Quarterly Access Review Procedure + โ†“ shares account, long-lived, access, service (5.1) + SEC-233 โ€” Rotate service account keys before audit window +``` + +With `--html`, also an interactive page โ€” pan, zoom, drag, and click a node to see every edge and why it exists: + +![The /graph HTML view with one document selected and its edges listed](../assets/graph_example.png) + +Full detail on the model and the scoring: [Knowledge Graph](GRAPH.md). **Mock mode** โ€” Fully supported. The mock corpus carries an author, datasource and container on all seventy documents, so the graph is complete offline. diff --git a/docs/GRAPH.md b/docs/GRAPH.md index 9aa52ef..9067ff5 100644 --- a/docs/GRAPH.md +++ b/docs/GRAPH.md @@ -83,7 +83,15 @@ edges in score order. ## The HTML view `--html ` writes a self-contained page: one file, no CDN, no framework, no network โ€” -the same constraint `/flow timeline` works under. Inside it: +the same constraint `/flow timeline` works under. + +![The /graph HTML view: a force-directed graph of the access review result set, with one document selected and its edges listed](../assets/graph_example.png) + +*`/graph "access review" --html access.html` against the mock corpus, with the hub document +selected. Its neighbours stay lit while everything else dims, and the panel lists all nine of +its edges โ€” each with the words that earned it and the score they carry.* + +Inside it: - A force-directed layout, seeded from deterministic ring positions so the same graph opens the same way, then relaxed in the browser. diff --git a/docs/TESTING.md b/docs/TESTING.md index 7344270..862c4c7 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -21,13 +21,13 @@ files, and they outrank the `Glean Code.app` launcher in `Cmd+Space`: export PYTHONPYCACHEPREFIX="$HOME/.cache/python" ``` -1,148 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, the knowledge graph, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools). +1,151 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, the knowledge graph, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools). ## Development notes Notes on the test suite added during development of glean-code-cli. -All 1,148 tests pass. Here's what was added across the development passes: +All 1,151 tests pass. Here's what was added across the development passes: `tests/test_commands_extended.py` (155 new tests) โ€” covers all previously untested commands: diff --git a/tests/test_graph.py b/tests/test_graph.py index 47cf246..e3adc2b 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -305,5 +305,30 @@ def test_registered_with_help_docs(self): self.assertIn("--html", DOCS["graph"]["usage"]) +class TestDocs(unittest.TestCase): + """The screenshot in the docs is an asset like any other: it can go missing.""" + + REPO = Path(__file__).resolve().parents[1] + + def test_screenshot_exists_and_is_a_png(self): + shot = self.REPO / "assets" / "graph_example.png" + self.assertTrue(shot.is_file(), "assets/graph_example.png is missing") + self.assertEqual(shot.read_bytes()[:8], b"\x89PNG\r\n\x1a\n") + + def test_docs_reference_the_screenshot_with_a_working_path(self): + for name in ("GRAPH.md", "COMMANDS.md"): + doc = self.REPO / "docs" / name + body = doc.read_text(encoding="utf-8") + self.assertIn("../assets/graph_example.png", body, name) + # the path is relative to the document, so resolve it from there + self.assertTrue((doc.parent / "../assets/graph_example.png").resolve().is_file()) + + def test_screenshot_has_alt_text(self): + body = (self.REPO / "docs" / "GRAPH.md").read_text(encoding="utf-8") + line = next(l for l in body.split("\n") if "graph_example.png" in l) + alt = line[line.index("![") + 2:line.index("]")] + self.assertGreater(len(alt), 20, "alt text should describe the image") + + if __name__ == "__main__": unittest.main()