graphwiki 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphwiki-0.2.0 → graphwiki-0.3.0}/PKG-INFO +18 -12
- {graphwiki-0.2.0 → graphwiki-0.3.0}/README.md +17 -11
- {graphwiki-0.2.0 → graphwiki-0.3.0}/pyproject.toml +1 -1
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/cli.py +69 -10
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/logging_util.py +1 -5
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/render.py +10 -2
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/skill_install.py +1 -3
- graphwiki-0.3.0/src/graphwiki/skills/graphwiki/SKILL.md +220 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/skills/graphwiki/references/invariants.md +11 -10
- graphwiki-0.3.0/src/graphwiki/skills/graphwiki/references/page-format.md +123 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/writer.py +68 -0
- graphwiki-0.2.0/src/graphwiki/skills/graphwiki/SKILL.md +0 -202
- graphwiki-0.2.0/src/graphwiki/skills/graphwiki/references/page-format.md +0 -127
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/__init__.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/__main__.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/assets/README.md +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/assets/mermaid.min.js +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/bootstrap.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/context.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/emit.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/gates.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/graph_client.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/mcp_server.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/plan.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/py.typed +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/state.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/types.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/watch.py +0 -0
- {graphwiki-0.2.0 → graphwiki-0.3.0}/src/graphwiki/web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: graphwiki
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Graph-grounded code wiki generator: deterministic traversal, LLM writes prose only.
|
|
5
5
|
Keywords: documentation,wiki,code-graph,llm,developer-tools
|
|
6
6
|
Author: Mayur Pise
|
|
@@ -134,6 +134,7 @@ works.
|
|
|
134
134
|
| `ollama` (default) | `http://localhost:11434/v1` | `gemma3` | No |
|
|
135
135
|
| `nim` | `http://localhost:8000/v1` | `gemma` | No |
|
|
136
136
|
| `offline` | none | none | No (no LLM at all) |
|
|
137
|
+
| `agent` | none — a coding assistant writes the prose | No (no LLM/egress); see [Coding-assisted mode](#coding-assisted-mode-no-llm-api) |
|
|
137
138
|
| `grok` | `https://api.x.ai/v1` | `grok-4` | **Yes** — external SaaS |
|
|
138
139
|
| `claude` | `https://api.anthropic.com/v1` | `claude-opus-4-8` | **Yes** — external SaaS |
|
|
139
140
|
| `openai` | `https://api.openai.com/v1` | `gpt-4o` | **Yes** — external SaaS |
|
|
@@ -155,19 +156,22 @@ page is sent unless you pass `--allow-cloud-egress` or set
|
|
|
155
156
|
egress is never silent). A raw non-local `--base-url` (an internal NIM/Triton
|
|
156
157
|
box) is only logged, never blocked. Full policy: [DATA_GOVERNANCE.md](docs/DATA_GOVERNANCE.md).
|
|
157
158
|
|
|
158
|
-
> **Note:**
|
|
159
|
-
>
|
|
160
|
-
>
|
|
161
|
-
> pre-assembled
|
|
162
|
-
>
|
|
163
|
-
> API, not the CLI.
|
|
159
|
+
> **Note:** the cloud/local rows above are model **APIs** reached over HTTP —
|
|
160
|
+
> don't point `--base-url` at an interactive coding-agent CLI. When you want a
|
|
161
|
+
> coding assistant to write the prose, use `--backend agent` (below): it feeds
|
|
162
|
+
> the assistant the SAME stateless, pre-assembled context the API writer gets —
|
|
163
|
+
> via prompt files, with no traversal tools — so citations stay verifiable.
|
|
164
164
|
|
|
165
165
|
### Coding-assisted mode (no LLM API)
|
|
166
166
|
|
|
167
|
-
When no model endpoint is available, use the **graphwiki
|
|
168
|
-
assistant
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
When no model endpoint is available, use `--backend agent` (or the **graphwiki
|
|
168
|
+
skill**) to make a coding assistant the prose writer. Python still plans
|
|
169
|
+
coverage and assembles context; the tool writes the exact `(system prompt,
|
|
170
|
+
context)` pair the API writer sends into `wiki/.agent/prompts/<id>.md`, the
|
|
171
|
+
assistant writes the four `§SECTION:` blocks back into
|
|
172
|
+
`wiki/.agent/responses/<id>.md`, and a re-run feeds them through the same
|
|
173
|
+
`merge → gate → emit` path — the assistant never edits a page. `graphwiki
|
|
174
|
+
validate` gates the result.
|
|
171
175
|
|
|
172
176
|
**Installers place the skill automatically** into every detected coding-assistant
|
|
173
177
|
skill root (Claude Code, Cursor, GitHub Copilot, Grok, OpenAI Codex,
|
|
@@ -182,8 +186,10 @@ skill root (Claude Code, Cursor, GitHub Copilot, Grok, OpenAI Codex,
|
|
|
182
186
|
graphwiki skill install # detected assistants
|
|
183
187
|
graphwiki skill install --all # every known root
|
|
184
188
|
graphwiki skill status
|
|
185
|
-
graphwiki build . --backend
|
|
189
|
+
graphwiki build . --backend agent --no-web
|
|
186
190
|
# In the assistant: /graphwiki or "coding-assisted graphwiki"
|
|
191
|
+
# → narrate wiki/.agent/prompts/* into wiki/.agent/responses/*, then:
|
|
192
|
+
graphwiki build . --backend agent --no-web # ingests the responses
|
|
187
193
|
graphwiki validate .
|
|
188
194
|
```
|
|
189
195
|
|
|
@@ -106,6 +106,7 @@ works.
|
|
|
106
106
|
| `ollama` (default) | `http://localhost:11434/v1` | `gemma3` | No |
|
|
107
107
|
| `nim` | `http://localhost:8000/v1` | `gemma` | No |
|
|
108
108
|
| `offline` | none | none | No (no LLM at all) |
|
|
109
|
+
| `agent` | none — a coding assistant writes the prose | No (no LLM/egress); see [Coding-assisted mode](#coding-assisted-mode-no-llm-api) |
|
|
109
110
|
| `grok` | `https://api.x.ai/v1` | `grok-4` | **Yes** — external SaaS |
|
|
110
111
|
| `claude` | `https://api.anthropic.com/v1` | `claude-opus-4-8` | **Yes** — external SaaS |
|
|
111
112
|
| `openai` | `https://api.openai.com/v1` | `gpt-4o` | **Yes** — external SaaS |
|
|
@@ -127,19 +128,22 @@ page is sent unless you pass `--allow-cloud-egress` or set
|
|
|
127
128
|
egress is never silent). A raw non-local `--base-url` (an internal NIM/Triton
|
|
128
129
|
box) is only logged, never blocked. Full policy: [DATA_GOVERNANCE.md](docs/DATA_GOVERNANCE.md).
|
|
129
130
|
|
|
130
|
-
> **Note:**
|
|
131
|
-
>
|
|
132
|
-
>
|
|
133
|
-
> pre-assembled
|
|
134
|
-
>
|
|
135
|
-
> API, not the CLI.
|
|
131
|
+
> **Note:** the cloud/local rows above are model **APIs** reached over HTTP —
|
|
132
|
+
> don't point `--base-url` at an interactive coding-agent CLI. When you want a
|
|
133
|
+
> coding assistant to write the prose, use `--backend agent` (below): it feeds
|
|
134
|
+
> the assistant the SAME stateless, pre-assembled context the API writer gets —
|
|
135
|
+
> via prompt files, with no traversal tools — so citations stay verifiable.
|
|
136
136
|
|
|
137
137
|
### Coding-assisted mode (no LLM API)
|
|
138
138
|
|
|
139
|
-
When no model endpoint is available, use the **graphwiki
|
|
140
|
-
assistant
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
When no model endpoint is available, use `--backend agent` (or the **graphwiki
|
|
140
|
+
skill**) to make a coding assistant the prose writer. Python still plans
|
|
141
|
+
coverage and assembles context; the tool writes the exact `(system prompt,
|
|
142
|
+
context)` pair the API writer sends into `wiki/.agent/prompts/<id>.md`, the
|
|
143
|
+
assistant writes the four `§SECTION:` blocks back into
|
|
144
|
+
`wiki/.agent/responses/<id>.md`, and a re-run feeds them through the same
|
|
145
|
+
`merge → gate → emit` path — the assistant never edits a page. `graphwiki
|
|
146
|
+
validate` gates the result.
|
|
143
147
|
|
|
144
148
|
**Installers place the skill automatically** into every detected coding-assistant
|
|
145
149
|
skill root (Claude Code, Cursor, GitHub Copilot, Grok, OpenAI Codex,
|
|
@@ -154,8 +158,10 @@ skill root (Claude Code, Cursor, GitHub Copilot, Grok, OpenAI Codex,
|
|
|
154
158
|
graphwiki skill install # detected assistants
|
|
155
159
|
graphwiki skill install --all # every known root
|
|
156
160
|
graphwiki skill status
|
|
157
|
-
graphwiki build . --backend
|
|
161
|
+
graphwiki build . --backend agent --no-web
|
|
158
162
|
# In the assistant: /graphwiki or "coding-assisted graphwiki"
|
|
163
|
+
# → narrate wiki/.agent/prompts/* into wiki/.agent/responses/*, then:
|
|
164
|
+
graphwiki build . --backend agent --no-web # ingests the responses
|
|
159
165
|
graphwiki validate .
|
|
160
166
|
```
|
|
161
167
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Name verified available on PyPI (2026-07-01). npm has an unrelated `graphwiki`
|
|
3
3
|
# but this is a Python-only tool, so there is no conflict.
|
|
4
4
|
name = "graphwiki"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.3.0"
|
|
6
6
|
description = "Graph-grounded code wiki generator: deterministic traversal, LLM writes prose only."
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.11"
|
|
@@ -27,7 +27,14 @@ from .graph_client import EngineError, GraphClient
|
|
|
27
27
|
from .logging_util import echo as _echo
|
|
28
28
|
from .state import State
|
|
29
29
|
from .types import HOME_DIR, ConceptContext, ConceptType, Page
|
|
30
|
-
from .writer import
|
|
30
|
+
from .writer import (
|
|
31
|
+
BACKENDS,
|
|
32
|
+
CLOUD_BACKENDS,
|
|
33
|
+
AgentBridge,
|
|
34
|
+
Writer,
|
|
35
|
+
WriterConfig,
|
|
36
|
+
_serialize_context,
|
|
37
|
+
)
|
|
31
38
|
|
|
32
39
|
app = typer.Typer(add_completion=False, help="Graph-grounded code wiki generator.")
|
|
33
40
|
skill_app = typer.Typer(
|
|
@@ -143,7 +150,9 @@ def _resolve_backend(
|
|
|
143
150
|
)
|
|
144
151
|
raise typer.Exit(code=2)
|
|
145
152
|
preset_url, preset_model = BACKENDS[backend]
|
|
146
|
-
if backend
|
|
153
|
+
if backend in ("offline", "agent"):
|
|
154
|
+
# agent has no endpoint either; an un-narrated page falls back to the
|
|
155
|
+
# offline template, so allow_offline must be set (else fail-loud).
|
|
147
156
|
offline = True
|
|
148
157
|
# Explicit (non-None) flags override the preset; else preset; else Ollama.
|
|
149
158
|
resolved_url = base_url if base_url is not None else (preset_url or ollama_url)
|
|
@@ -196,7 +205,11 @@ def _resolve_backend(
|
|
|
196
205
|
raise typer.Exit(code=2)
|
|
197
206
|
|
|
198
207
|
return WriterConfig(
|
|
199
|
-
base_url=resolved_url,
|
|
208
|
+
base_url=resolved_url,
|
|
209
|
+
model=resolved_model,
|
|
210
|
+
allow_offline=offline,
|
|
211
|
+
api_key=key,
|
|
212
|
+
agent=backend == "agent",
|
|
200
213
|
)
|
|
201
214
|
|
|
202
215
|
|
|
@@ -346,7 +359,27 @@ def _writer_fingerprint(cfg: WriterConfig) -> str:
|
|
|
346
359
|
"""Writer identity for staleness. A change here (backend/model/offline) must
|
|
347
360
|
invalidate all pages even when the graph context is unchanged — otherwise
|
|
348
361
|
``update`` after switching backends is a silent no-op."""
|
|
349
|
-
return f"{cfg.model}|{cfg.base_url}|offline={cfg.allow_offline}"
|
|
362
|
+
return f"{cfg.model}|{cfg.base_url}|offline={cfg.allow_offline}|agent={cfg.agent}"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _report_agent_handoff(bridge: AgentBridge) -> None:
|
|
366
|
+
"""Tell the coding assistant what prose is still owed after an agent-mode run.
|
|
367
|
+
|
|
368
|
+
Pass 1 (no responses yet) reports every page as pending and emits offline
|
|
369
|
+
fallback bodies (gate-valid). The assistant narrates the prompts, then a
|
|
370
|
+
re-run ingests the responses. Idempotent: re-runs re-report the true state.
|
|
371
|
+
"""
|
|
372
|
+
narrated, pending = len(bridge.narrated), len(bridge.pending)
|
|
373
|
+
total = narrated + pending
|
|
374
|
+
_echo(f"[agent] {narrated}/{total} page(s) narrated, {pending} awaiting prose")
|
|
375
|
+
if pending:
|
|
376
|
+
_echo(
|
|
377
|
+
f"[agent] next: for each prompt in {bridge.prompts}/ write §SECTION: "
|
|
378
|
+
f"prose to {bridge.responses}/<same-name>, then re-run "
|
|
379
|
+
"`graphwiki build --backend agent`."
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
_echo("[agent] all pages narrated — run `graphwiki validate` to gate.")
|
|
350
383
|
|
|
351
384
|
|
|
352
385
|
def _fmt_eta(secs: float) -> str:
|
|
@@ -443,7 +476,16 @@ def _run(
|
|
|
443
476
|
|
|
444
477
|
state = State(out)
|
|
445
478
|
writer_fp = _writer_fingerprint(cfg)
|
|
446
|
-
|
|
479
|
+
# Agent mode always regenerates every page: the two-phase (emit prompts →
|
|
480
|
+
# assistant writes responses → re-run to ingest) flow needs the second
|
|
481
|
+
# pass to re-render each page, which staleness would skip on an unchanged
|
|
482
|
+
# graph. Regeneration is offline-cheap, so this costs nothing but LLM-free
|
|
483
|
+
# passes over the plan.
|
|
484
|
+
targets = (
|
|
485
|
+
state.stale(contexts, writer_fp)
|
|
486
|
+
if (incremental and not cfg.agent)
|
|
487
|
+
else contexts
|
|
488
|
+
)
|
|
447
489
|
_echo(
|
|
448
490
|
f"[generate] {len(targets)}/{len(contexts)} pages "
|
|
449
491
|
f"({'incremental' if incremental else 'full'})"
|
|
@@ -457,7 +499,15 @@ def _run(
|
|
|
457
499
|
)
|
|
458
500
|
|
|
459
501
|
_t = time.monotonic()
|
|
460
|
-
|
|
502
|
+
bridge: AgentBridge | None = None
|
|
503
|
+
if cfg.agent:
|
|
504
|
+
key_by_context = {
|
|
505
|
+
_serialize_context(c): c.concept.concept_id for c in targets
|
|
506
|
+
}
|
|
507
|
+
bridge = AgentBridge(out / ".agent", key_by_context)
|
|
508
|
+
writer = Writer(cfg, complete=bridge)
|
|
509
|
+
else:
|
|
510
|
+
writer = Writer(cfg)
|
|
461
511
|
target_pages = _generate_pages(writer, targets)
|
|
462
512
|
_log.debug(
|
|
463
513
|
"generated %d page(s) in %.2fs", len(target_pages), time.monotonic() - _t
|
|
@@ -531,6 +581,8 @@ def _run(
|
|
|
531
581
|
f"{len(target_pages)} page(s) emitted, gates {report.layers}",
|
|
532
582
|
)
|
|
533
583
|
_echo(f"[emit] {out} ({len(target_pages)} page(s) written)")
|
|
584
|
+
if bridge is not None:
|
|
585
|
+
_report_agent_handoff(bridge)
|
|
534
586
|
finally:
|
|
535
587
|
graph.close()
|
|
536
588
|
if writer is not None:
|
|
@@ -609,7 +661,7 @@ def build(
|
|
|
609
661
|
backend: str = typer.Option(
|
|
610
662
|
None,
|
|
611
663
|
"--backend",
|
|
612
|
-
help="Preset: ollama | nim | offline | grok | claude | openai. Sets base-url+model.",
|
|
664
|
+
help="Preset: ollama | nim | offline | agent | grok | claude | openai. Sets base-url+model.",
|
|
613
665
|
),
|
|
614
666
|
base_url: str = typer.Option(
|
|
615
667
|
None, "--base-url", help="OpenAI-compatible base URL (default: local Ollama)."
|
|
@@ -690,7 +742,7 @@ def update(
|
|
|
690
742
|
backend: str = typer.Option(
|
|
691
743
|
None,
|
|
692
744
|
"--backend",
|
|
693
|
-
help="Preset: ollama | nim | offline | grok | claude | openai. Sets base-url+model.",
|
|
745
|
+
help="Preset: ollama | nim | offline | agent | grok | claude | openai. Sets base-url+model.",
|
|
694
746
|
),
|
|
695
747
|
base_url: str = typer.Option(
|
|
696
748
|
None, "--base-url", help="OpenAI-compatible base URL (default: local Ollama)."
|
|
@@ -858,6 +910,11 @@ def _doctor_backend(
|
|
|
858
910
|
if backend == "offline":
|
|
859
911
|
typer.echo("[doctor] backend: offline (deterministic bodies, no LLM/egress)")
|
|
860
912
|
return
|
|
913
|
+
if backend == "agent":
|
|
914
|
+
typer.echo(
|
|
915
|
+
"[doctor] backend: agent (coding assistant writes prose; no LLM/egress)"
|
|
916
|
+
)
|
|
917
|
+
return
|
|
861
918
|
if backend in CLOUD_BACKENDS:
|
|
862
919
|
kind = (
|
|
863
920
|
"CLOUD (needs --allow-cloud-egress + API key; sends code-derived context)"
|
|
@@ -998,7 +1055,9 @@ def _print_skill_results(results: list[skill_install.InstallResult]) -> None:
|
|
|
998
1055
|
"error": "x",
|
|
999
1056
|
}.get(r.status, "?")
|
|
1000
1057
|
dest = str(r.dest) if r.dest else ""
|
|
1001
|
-
detail =
|
|
1058
|
+
detail = (
|
|
1059
|
+
f" ({r.detail})" if r.detail and r.status in ("error", "skipped") else ""
|
|
1060
|
+
)
|
|
1002
1061
|
loc = f" → {dest}" if dest and r.status not in ("skipped",) else ""
|
|
1003
1062
|
typer.echo(f" [{mark}] {r.label}: {r.status}{loc}{detail}")
|
|
1004
1063
|
|
|
@@ -1081,7 +1140,7 @@ def watch(
|
|
|
1081
1140
|
backend: str = typer.Option(
|
|
1082
1141
|
None,
|
|
1083
1142
|
"--backend",
|
|
1084
|
-
help="Preset: ollama | nim | offline | grok | claude | openai. Sets base-url+model.",
|
|
1143
|
+
help="Preset: ollama | nim | offline | agent | grok | claude | openai. Sets base-url+model.",
|
|
1085
1144
|
),
|
|
1086
1145
|
base_url: str = typer.Option(
|
|
1087
1146
|
None, "--base-url", help="OpenAI-compatible base URL (default: local Ollama)."
|
|
@@ -81,11 +81,7 @@ def configure(*, verbosity: int = 0, quiet: bool = False, json: bool = False) ->
|
|
|
81
81
|
if json:
|
|
82
82
|
handler.setFormatter(_JsonFormatter())
|
|
83
83
|
else:
|
|
84
|
-
fmt = (
|
|
85
|
-
"%(levelname)s %(name)s: %(message)s"
|
|
86
|
-
if verbosity >= 2
|
|
87
|
-
else "%(message)s"
|
|
88
|
-
)
|
|
84
|
+
fmt = "%(levelname)s %(name)s: %(message)s" if verbosity >= 2 else "%(message)s"
|
|
89
85
|
handler.setFormatter(logging.Formatter(fmt))
|
|
90
86
|
|
|
91
87
|
_state.update(verbosity=verbosity, quiet=quiet, json=json)
|
|
@@ -109,8 +109,16 @@ def _safe_label(text: str) -> str:
|
|
|
109
109
|
inject markup into the viewer. Map the dangerous chars to readable-but-inert
|
|
110
110
|
equivalents rather than trusting the names to be clean."""
|
|
111
111
|
for bad, good in (
|
|
112
|
-
("&", "+"),
|
|
113
|
-
("
|
|
112
|
+
("&", "+"),
|
|
113
|
+
("<", "("),
|
|
114
|
+
(">", ")"),
|
|
115
|
+
('"', "'"),
|
|
116
|
+
("[", "("),
|
|
117
|
+
("]", ")"),
|
|
118
|
+
("{", "("),
|
|
119
|
+
("}", ")"),
|
|
120
|
+
("|", "/"),
|
|
121
|
+
("`", "'"),
|
|
114
122
|
):
|
|
115
123
|
text = text.replace(bad, good)
|
|
116
124
|
return text
|
|
@@ -218,9 +218,7 @@ def status_skills(*, home: Path | None = None) -> list[InstallResult]:
|
|
|
218
218
|
InstallResult(tool, label, dest, "skipped", "skill not installed")
|
|
219
219
|
)
|
|
220
220
|
elif _trees_equal(src, dest):
|
|
221
|
-
results.append(
|
|
222
|
-
InstallResult(tool, label, dest, "unchanged", "up to date")
|
|
223
|
-
)
|
|
221
|
+
results.append(InstallResult(tool, label, dest, "unchanged", "up to date"))
|
|
224
222
|
else:
|
|
225
223
|
results.append(
|
|
226
224
|
InstallResult(tool, label, dest, "updated", "installed but stale")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphwiki
|
|
3
|
+
description: >
|
|
4
|
+
Coding-assisted graphwiki — generate or refresh a graph-grounded code wiki
|
|
5
|
+
when no LLM API/backend is available. The coding assistant acts as the
|
|
6
|
+
prose writer only; Python still decides coverage and context. Use when the
|
|
7
|
+
user asks to "build graphwiki offline", "coding-assisted graphwiki",
|
|
8
|
+
"graphwiki without ollama/nim/api", "narrate the wiki", "rewrite offline
|
|
9
|
+
stubs", "agent-assisted wiki", or runs /graphwiki. Also use when graphwiki
|
|
10
|
+
build fails for lack of a model endpoint and the user wants quality prose
|
|
11
|
+
instead of deterministic offline templates.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Coding-Assisted GraphWiki
|
|
15
|
+
|
|
16
|
+
Run **graphwiki** with a coding assistant (Claude Code, Grok, Cursor, Codex, …)
|
|
17
|
+
as the writer when no model API is reachable. This is **not** free-form repo
|
|
18
|
+
documentation (OpenWiki/deepagents style). Coverage, context, citations, and
|
|
19
|
+
gates stay owned by the Python tool.
|
|
20
|
+
|
|
21
|
+
**How it works — `--backend agent` makes you a drop-in for the LLM.** The tool
|
|
22
|
+
builds the graph, plans coverage, and assembles each page's context, then writes
|
|
23
|
+
the SAME `(system prompt, context)` pair the online writer sends to an API into
|
|
24
|
+
`wiki/.agent/prompts/<id>.md`. You write the four `§SECTION:` blocks back to
|
|
25
|
+
`wiki/.agent/responses/<id>.md`. A re-run feeds your prose through the exact same
|
|
26
|
+
`merge → assemble → gate → emit` path the API-key writer uses. You never open or
|
|
27
|
+
edit a page under `wiki/` — Python assembles the frontmatter, fact lists, and
|
|
28
|
+
Mermaid **around** your prose, so you cannot corrupt the deterministic parts.
|
|
29
|
+
|
|
30
|
+
## Frozen invariants (do not break)
|
|
31
|
+
|
|
32
|
+
| Layer | Owner | Agent may… |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| What to document (coverage law) | `graphwiki plan` | Never decide, never drop a required page |
|
|
35
|
+
| What facts each page gets | `graphwiki` context assembly | Never invent members/callers/paths |
|
|
36
|
+
| Narrative prose | **this skill / the agent (as the writer)** | Write the four `§SECTION:` blocks into the response file only |
|
|
37
|
+
| Frontmatter, Mermaid, fact lists, page files | Python | **Never write or edit** — assembled around your prose |
|
|
38
|
+
| Validation | `graphwiki validate` | Must pass before claiming done |
|
|
39
|
+
|
|
40
|
+
**Anti-pattern:** grepping the repo and writing new path citations. That
|
|
41
|
+
reintroduces hallucination and fails the citation gate. Ground every claim in
|
|
42
|
+
the context the prompt file hands you (its `# Context` block) — nothing else.
|
|
43
|
+
|
|
44
|
+
Full invariant detail: `references/invariants.md`.
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Prefer uv on host
|
|
50
|
+
uv tool install graphwiki # or: uvx graphwiki …
|
|
51
|
+
# Engine auto-fetches on first build (or pin --engine-bin)
|
|
52
|
+
graphwiki doctor . --backend agent # engine + index healthy; no LLM needed
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If `doctor` fails on the engine, fix that first — the agent cannot replace the
|
|
56
|
+
graph engine.
|
|
57
|
+
|
|
58
|
+
## Modes
|
|
59
|
+
|
|
60
|
+
### A. Assisted build (no LLM API) — default
|
|
61
|
+
|
|
62
|
+
Use when Ollama/NIM/cloud are unavailable or the user wants agent-written prose.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
1. Build graph + emit per-page prompts (deterministic, no network):
|
|
66
|
+
graphwiki build . --backend agent --no-web
|
|
67
|
+
# Indexes the repo, plans coverage, assembles context, writes one prompt to
|
|
68
|
+
# wiki/.agent/prompts/<id>.md per page, and emits offline-fallback pages so
|
|
69
|
+
# the wiki is gate-valid immediately. The final [agent] line reports how many
|
|
70
|
+
# pages still await prose. (optional: --out PATH, --allow-defer GLOB, --engine-bin PATH)
|
|
71
|
+
|
|
72
|
+
2. Narrate each prompt (see "Narration recipe"): for every file in
|
|
73
|
+
wiki/.agent/prompts/, write §SECTION prose to wiki/.agent/responses/<same-name>.
|
|
74
|
+
Prioritize high x-hotspot-score pages first if the tree is large.
|
|
75
|
+
|
|
76
|
+
3. Ingest + gate (re-run is idempotent):
|
|
77
|
+
graphwiki build . --backend agent --no-web
|
|
78
|
+
graphwiki validate .
|
|
79
|
+
# The re-run feeds your responses through the same writer path an LLM uses.
|
|
80
|
+
# Repeat step 2 → step 3 until [agent] reports "0 awaiting prose".
|
|
81
|
+
|
|
82
|
+
4. Optional viewer:
|
|
83
|
+
graphwiki web --no-open # or open the URL
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Your responses persist in `wiki/.agent/responses/` — that directory is the
|
|
87
|
+
durable narration store. Every `build --backend agent` re-derives the pages from
|
|
88
|
+
`(graph + responses)`, so the flow is fully reproducible.
|
|
89
|
+
|
|
90
|
+
### B. Assisted refresh (after code changes)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
1. graphwiki build . --backend agent --no-web
|
|
94
|
+
# Re-indexes; each page's prompt now reflects current context; existing
|
|
95
|
+
# responses are re-applied. Pages whose context moved get an updated prompt.
|
|
96
|
+
2. Re-narrate the prompts whose # Context changed (or that the user named);
|
|
97
|
+
leave the rest — their prior response still applies.
|
|
98
|
+
3. graphwiki build . --backend agent --no-web && graphwiki validate .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### C. Editorial polish (prose only)
|
|
102
|
+
|
|
103
|
+
Existing wiki already gate-valid; user wants better narrative.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
1. Edit the §SECTION prose in wiki/.agent/responses/<id>.md (NOT the page under
|
|
107
|
+
wiki/ — that is derived and will be overwritten on the next run).
|
|
108
|
+
2. graphwiki build . --backend agent --no-web # re-derives the page
|
|
109
|
+
3. graphwiki validate .
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### D. Consume wiki while coding (read path)
|
|
113
|
+
|
|
114
|
+
Not generation. Point the agent at `wiki/.ai-context.md` and relevant pages,
|
|
115
|
+
or run `graphwiki serve` (MCP: `read_wiki_structure` / `read_wiki_contents` /
|
|
116
|
+
`ask_question`). Do not invent pages in this mode.
|
|
117
|
+
|
|
118
|
+
## Pending pages (what still needs prose)
|
|
119
|
+
|
|
120
|
+
Two signals, in order of authority:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Authoritative: the run's own report
|
|
124
|
+
# [agent] 3/17 page(s) narrated, 14 awaiting prose
|
|
125
|
+
# A prompt with no matching response file is pending:
|
|
126
|
+
comm -23 <(cd wiki/.agent/prompts && ls) <(cd wiki/.agent/responses && ls 2>/dev/null)
|
|
127
|
+
|
|
128
|
+
# Secondary: a derived page still carrying the offline template signature
|
|
129
|
+
rg -n "documented from the code knowledge graph" wiki -g '*.md'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
After you narrate and re-run, the pending count drops and the offline signature
|
|
133
|
+
disappears from that page.
|
|
134
|
+
|
|
135
|
+
## Narration recipe (one page)
|
|
136
|
+
|
|
137
|
+
Load `references/page-format.md` once; apply for every page.
|
|
138
|
+
|
|
139
|
+
1. **Read** the prompt file `wiki/.agent/prompts/<id>.md`. It contains:
|
|
140
|
+
- `# System instructions` — the writer contract (identical to the LLM's
|
|
141
|
+
system prompt). It defines the four sections and the `§SECTION:` markers.
|
|
142
|
+
- `# Context — the ONLY source of truth` — concept type, title, resource,
|
|
143
|
+
public surface, members, child modules, callers, blast radius, tests.
|
|
144
|
+
2. **Do not** open source files or the page under `wiki/` to "learn more" for new
|
|
145
|
+
citations. The `# Context` block is the complete, only source of truth.
|
|
146
|
+
Optional peek at `resource` only to phrase responsibility accurately; still
|
|
147
|
+
cite only names/paths already in the context.
|
|
148
|
+
3. **Write only** the four `§SECTION:` blocks to
|
|
149
|
+
`wiki/.agent/responses/<id>.md`, each introduced by a line that is EXACTLY:
|
|
150
|
+
```
|
|
151
|
+
§SECTION: purpose
|
|
152
|
+
§SECTION: architecture
|
|
153
|
+
§SECTION: key_types
|
|
154
|
+
§SECTION: fit
|
|
155
|
+
```
|
|
156
|
+
Nothing else — no frontmatter, no `##` headings, no code fences, no fact
|
|
157
|
+
lists. Python assembles all of that around your prose.
|
|
158
|
+
4. **Prose rules** (they are also stated in the prompt's system block — follow it):
|
|
159
|
+
- Concrete, not boilerplate; explain the graph facts, do not restate raw lists
|
|
160
|
+
- Reference ONLY symbols/paths present in the `# Context` block
|
|
161
|
+
- A short bulleted list is allowed ONLY in `key_types`
|
|
162
|
+
- If a list (callers/tests/public surface) is empty, say so plainly (leaf / no
|
|
163
|
+
public surface / untested) — do not invent
|
|
164
|
+
- Clear depth floors (the tool's anti-stub gate; offline fallback already
|
|
165
|
+
clears them, so match or exceed): Subsystem ≥200, Module/Feature/Entrypoint
|
|
166
|
+
≥120, API/DataModel ≥80 chars of prose
|
|
167
|
+
|
|
168
|
+
## Batch strategy
|
|
169
|
+
|
|
170
|
+
For large wikis:
|
|
171
|
+
|
|
172
|
+
1. Run `build --backend agent` once to emit all prompts.
|
|
173
|
+
2. Sort prompts by their page's `x-hotspot-score` (highest first — hotspot lives
|
|
174
|
+
in the emitted page frontmatter, or narrate the prompts flagged pending).
|
|
175
|
+
3. Narrate responses in batches of 5–15, then run
|
|
176
|
+
`graphwiki build --backend agent && graphwiki validate .` so failures stay local.
|
|
177
|
+
4. Skip deferred / non-required concepts (no prompt is emitted for them).
|
|
178
|
+
|
|
179
|
+
Do not hand-create prompt or page files — re-run `graphwiki build --backend
|
|
180
|
+
agent` to (re)materialize the plan.
|
|
181
|
+
|
|
182
|
+
## Failure handling
|
|
183
|
+
|
|
184
|
+
| Symptom | Action |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `doctor` engine missing | Fix engine (`--engine-bin` or auto-fetch); do not free-write a wiki |
|
|
187
|
+
| `validate` structure/quality fail | Narrate the named page's response deeper (below the anti-stub floor) |
|
|
188
|
+
| `validate` citations fail | Remove the invented path token from that response; re-run + re-validate |
|
|
189
|
+
| `validate` coverage fail | Re-run `build --backend agent` — do not invent missing pages |
|
|
190
|
+
| A response is ignored on re-run | Confirm the filename matches its prompt exactly (`<id>.md`) and has `§SECTION:` markers |
|
|
191
|
+
| User wants cloud/local API instead | Prefer `graphwiki build . --backend ollama\|nim\|…` over this skill |
|
|
192
|
+
|
|
193
|
+
## Done criteria
|
|
194
|
+
|
|
195
|
+
- [ ] `graphwiki validate .` exits 0
|
|
196
|
+
- [ ] The run reports `0 awaiting prose` (every required page has a response)
|
|
197
|
+
- [ ] No page still contains `documented from the code knowledge graph`
|
|
198
|
+
- [ ] You wrote only response files — never a page under `wiki/`
|
|
199
|
+
- [ ] Summary for the user: pages narrated, validate OK, how to serve
|
|
200
|
+
(`graphwiki web` or `graphwiki serve`)
|
|
201
|
+
|
|
202
|
+
## CLI cheat sheet
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
graphwiki doctor . --backend agent
|
|
206
|
+
graphwiki build . --backend agent --no-web # emit prompts + ingest responses
|
|
207
|
+
graphwiki validate .
|
|
208
|
+
graphwiki web
|
|
209
|
+
graphwiki serve --out wiki # needs mcp extra
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Backend note: `agent` is a first-class backend — it substitutes YOU for the LLM
|
|
213
|
+
in the writer's one completion call. The offline scaffold still exists as the
|
|
214
|
+
per-page fallback when a response is missing (`--backend offline` for a pure
|
|
215
|
+
deterministic wiki with no agent). See product README.
|
|
216
|
+
|
|
217
|
+
## Additional resources
|
|
218
|
+
|
|
219
|
+
- `references/invariants.md` — why agents must not traverse for generation
|
|
220
|
+
- `references/page-format.md` — the `§SECTION:` response contract + what Python owns
|
|
@@ -23,8 +23,10 @@ plugged in as free-form backends.
|
|
|
23
23
|
and citation ground truth.
|
|
24
24
|
|
|
25
25
|
OpenWiki-style deepagents (shell + FS tools freely traversing the repo to write
|
|
26
|
-
docs) is explicitly **WON'T-DO** on the generation path.
|
|
27
|
-
|
|
26
|
+
docs) is explicitly **WON'T-DO** on the generation path. Writing bounded
|
|
27
|
+
prose-only `§SECTION:` responses to the tool's per-page prompts (`--backend
|
|
28
|
+
agent`) is the only agent role that fits — you substitute for the LLM's one
|
|
29
|
+
completion call, nothing more.
|
|
28
30
|
|
|
29
31
|
## Citation rule (anti-hallucination)
|
|
30
32
|
|
|
@@ -32,12 +34,11 @@ Any path-shaped inline-code token (e.g. `` `src/foo/bar.py` ``, `` `writer.py` `
|
|
|
32
34
|
in a page must resolve to a path the graph indexed. The citation gate enforces
|
|
33
35
|
this on validate when the engine is available.
|
|
34
36
|
|
|
35
|
-
**Safe citation set for agents
|
|
37
|
+
**Safe citation set for agents** (all drawn from the prompt's `# Context` block):
|
|
36
38
|
|
|
37
|
-
- `
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
- symbols already listed on the page
|
|
39
|
+
- `RESOURCE` from the context
|
|
40
|
+
- names/paths already in `PUBLIC SURFACE`, `USED BY`, `BLAST RADIUS`, `TESTS`,
|
|
41
|
+
`CHILD MODULES`, or `MEMBERS`
|
|
41
42
|
|
|
42
43
|
**Unsafe:** any new path discovered by reading the filesystem during narration.
|
|
43
44
|
|
|
@@ -46,11 +47,11 @@ this on validate when the engine is available.
|
|
|
46
47
|
| Mode | Prose source | Network | When |
|
|
47
48
|
|---|---|---|---|
|
|
48
49
|
| `--backend offline` | Deterministic templates from graph facts | None | CI, scaffold, demos |
|
|
49
|
-
|
|
|
50
|
+
| **`--backend agent` (this skill)** | Coding assistant, facts from the prompt's `# Context` | None for generation | No API; want quality prose |
|
|
50
51
|
| `--backend ollama\|nim\|…` | Model API completion | Local or cloud (gated) | Normal automated runs |
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
diagrams stay tool-authored.
|
|
53
|
+
Agent mode reuses the offline template as the per-page fallback, so frontmatter,
|
|
54
|
+
lists, and diagrams stay tool-authored no matter which pages you narrate.
|
|
54
55
|
|
|
55
56
|
## Incremental honesty
|
|
56
57
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Response format (`§SECTION:` writer contract)
|
|
2
|
+
|
|
3
|
+
Under `--backend agent` you write **response files**, not pages. Python derives
|
|
4
|
+
each page under `wiki/` from `(graph context + your response)`. You never open a
|
|
5
|
+
page file.
|
|
6
|
+
|
|
7
|
+
Default output tree: `<repo>/wiki/` (`--out` overrides).
|
|
8
|
+
|
|
9
|
+
## Tree
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
wiki/
|
|
13
|
+
.ai-context.md # index — tool-owned
|
|
14
|
+
architecture.md # system Mermaid — tool-owned
|
|
15
|
+
log.md # run log — tool-owned
|
|
16
|
+
.state/ # refresh hashes — tool-owned
|
|
17
|
+
.agent/
|
|
18
|
+
prompts/<id>.md # tool-emitted: system prompt + context. READ ONLY.
|
|
19
|
+
responses/<id>.md # YOU write this: the four §SECTION blocks.
|
|
20
|
+
systems/*.md # DERIVED page (Subsystem + Module) — do not edit
|
|
21
|
+
features/*.md # DERIVED page (Feature) — do not edit
|
|
22
|
+
entrypoints/*.md # DERIVED page (Entrypoint) — do not edit
|
|
23
|
+
reference/*.md # DERIVED page (API, DataModel, …) — do not edit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The prompt file you read (`wiki/.agent/prompts/<id>.md`)
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
<!-- graphwiki agent writer prompt: <id> — DO NOT EDIT. ... -->
|
|
30
|
+
|
|
31
|
+
# System instructions
|
|
32
|
+
<the writer contract: the four sections, the §SECTION: markers, the prose rules>
|
|
33
|
+
|
|
34
|
+
# Context — the ONLY source of truth
|
|
35
|
+
CONCEPT TYPE: <Module|Subsystem|Feature|Entrypoint|…>
|
|
36
|
+
TITLE: <title>
|
|
37
|
+
RESOURCE: <canonical path or symbol>
|
|
38
|
+
PUBLIC SURFACE: <symbols…> # present only if non-empty
|
|
39
|
+
MEMBERS: <symbols…>
|
|
40
|
+
CHILD MODULES: <names…>
|
|
41
|
+
USED BY (callers): <names…>
|
|
42
|
+
BLAST RADIUS (dependents): <names…>
|
|
43
|
+
TESTS: <paths…>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The context is a fixed extract from the code knowledge graph. It is the **only**
|
|
47
|
+
source of truth. Never introduce a name, path, caller, or test not present in it.
|
|
48
|
+
|
|
49
|
+
## The response file you write (`wiki/.agent/responses/<id>.md`)
|
|
50
|
+
|
|
51
|
+
Exactly four blocks, each opened by a line that is EXACTLY the marker (no `##`
|
|
52
|
+
heading, no numbering, no frontmatter, no code fences):
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
§SECTION: purpose
|
|
56
|
+
1–2 short paragraphs: what this concept is and the responsibility it owns.
|
|
57
|
+
Concrete, not boilerplate.
|
|
58
|
+
|
|
59
|
+
§SECTION: architecture
|
|
60
|
+
1 short paragraph: how it is structured and how it relates to what it depends on
|
|
61
|
+
or is used by. If the context lists child modules or a diagram, narrate what it
|
|
62
|
+
shows — Python renders the actual Mermaid; you do not.
|
|
63
|
+
|
|
64
|
+
§SECTION: key_types
|
|
65
|
+
A few sentences on the most important types/functions in the public surface and
|
|
66
|
+
what each is for. Reference ONLY names in the context. A short bulleted list is
|
|
67
|
+
allowed here (and only here). If the surface is empty, say so in one sentence.
|
|
68
|
+
|
|
69
|
+
§SECTION: fit
|
|
70
|
+
1 short paragraph: who calls it, what breaks if it changes (its blast radius),
|
|
71
|
+
how it is tested. Ground every claim in the callers/blast-radius/tests given.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Parsing is on the markers: any preamble before the first `§SECTION:` and any
|
|
75
|
+
unknown marker is dropped. A section you omit is filled from the offline
|
|
76
|
+
template, so a partial response still yields a complete page — but an empty or
|
|
77
|
+
marker-less response leaves the page on the offline stub.
|
|
78
|
+
|
|
79
|
+
## What Python owns (you never write it)
|
|
80
|
+
|
|
81
|
+
- The YAML frontmatter (`type`, `title`, `description` routing key, `resource`,
|
|
82
|
+
`x-callers`, `x-grounded-paths`, `x-hotspot-score`)
|
|
83
|
+
- The `## Purpose & Capabilities` / `## Architecture` / `## Key Types` /
|
|
84
|
+
`## How It Fits` headings
|
|
85
|
+
- Every fact panel: `**Child modules:**`, `**Public surface:**`, `**Used by:**`,
|
|
86
|
+
`**Blast radius** …`, `**Tests:**` and their list items
|
|
87
|
+
- The `` ```mermaid `` diagram fences
|
|
88
|
+
|
|
89
|
+
`type` is a frozen vocabulary and `description` is a routing decision ("should I
|
|
90
|
+
open this?"), not a summary — both are tool-owned; do not attempt to influence
|
|
91
|
+
them from a response.
|
|
92
|
+
|
|
93
|
+
## Example (shape only)
|
|
94
|
+
|
|
95
|
+
Prompt `# Context` says: `TITLE: writer`, `RESOURCE: src/graphwiki/writer.py`,
|
|
96
|
+
`PUBLIC SURFACE: Writer, WriterConfig, WriterError`, `MEMBERS: 12 symbols`.
|
|
97
|
+
|
|
98
|
+
Response `wiki/.agent/responses/writer.md`:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
§SECTION: purpose
|
|
102
|
+
`writer` owns page narration for a single pre-assembled concept. It turns graph
|
|
103
|
+
context into structured body sections, using either an OpenAI-compatible
|
|
104
|
+
completion or deterministic offline templates. It is fed one concept at a time;
|
|
105
|
+
it does not plan coverage or query the graph.
|
|
106
|
+
|
|
107
|
+
§SECTION: architecture
|
|
108
|
+
It is a thin renderer around one completion call: the config selects the backend,
|
|
109
|
+
the writer serializes the context, and the parsed sections are merged with
|
|
110
|
+
deterministic fact panels assembled by the caller.
|
|
111
|
+
|
|
112
|
+
§SECTION: key_types
|
|
113
|
+
`Writer` runs the per-page render; `WriterConfig` carries backend/model/offline
|
|
114
|
+
identity; `WriterError` fails a page loud when an online completion is empty.
|
|
115
|
+
|
|
116
|
+
§SECTION: fit
|
|
117
|
+
It sits below planning and context assembly and above emit. Changing its section
|
|
118
|
+
contract ripples to every page, so its blast radius is the whole wiki; the writer
|
|
119
|
+
test suite exercises the offline, partial, and fail-loud paths.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Python renders the frontmatter, the four headings, and the public-surface / used-by
|
|
123
|
+
/ blast-radius / tests panels around this prose.
|
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
from collections.abc import Callable
|
|
25
25
|
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
26
27
|
from typing import Any
|
|
27
28
|
|
|
28
29
|
from .types import HOME_DIR, ConceptContext, Frontmatter, Page
|
|
@@ -87,6 +88,7 @@ BACKENDS: dict[str, tuple[str, str]] = {
|
|
|
87
88
|
"ollama": ("http://localhost:11434/v1", "gemma3"),
|
|
88
89
|
"nim": ("http://localhost:8000/v1", "gemma"),
|
|
89
90
|
"offline": ("", ""),
|
|
91
|
+
"agent": ("", ""), # coding assistant writes prose; no endpoint (see AgentBridge)
|
|
90
92
|
"grok": ("https://api.x.ai/v1", "grok-4"),
|
|
91
93
|
"claude": ("https://api.anthropic.com/v1", "claude-opus-4-8"),
|
|
92
94
|
"openai": ("https://api.openai.com/v1", "gpt-4o"),
|
|
@@ -104,6 +106,7 @@ class WriterConfig:
|
|
|
104
106
|
api_key: str = "not-needed" # local endpoints ignore it
|
|
105
107
|
temperature: float = 0.0 # determinism-friendly
|
|
106
108
|
allow_offline: bool = False # deterministic body if no LLM
|
|
109
|
+
agent: bool = False # coding assistant is the writer (see AgentBridge)
|
|
107
110
|
|
|
108
111
|
|
|
109
112
|
class WriterError(Exception):
|
|
@@ -458,3 +461,68 @@ def _assemble_body(sections: dict[str, str], ctx: ConceptContext) -> str:
|
|
|
458
461
|
parts.append("")
|
|
459
462
|
|
|
460
463
|
return "\n".join(parts).rstrip() + "\n"
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# ── agent backend: coding assistant as a drop-in for the LLM ─────────────────
|
|
467
|
+
_AGENT_PROMPT_MARK = "graphwiki agent writer prompt"
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class AgentBridge:
|
|
471
|
+
"""File-backed ``Complete`` — the coding assistant IS the model.
|
|
472
|
+
|
|
473
|
+
Realizes ``--backend agent``, the true-parity path for coding assistants:
|
|
474
|
+
instead of an LLM call, each page's ``(SYSTEM_PROMPT, serialized-context)``
|
|
475
|
+
— the EXACT pair the online writer sends — is written to
|
|
476
|
+
``<out>/.agent/prompts/<concept_id>.md``. When the assistant has already
|
|
477
|
+
written the matching ``<out>/.agent/responses/<concept_id>.md`` (raw
|
|
478
|
+
``§SECTION:`` prose), its text is returned so the SAME
|
|
479
|
+
``_merge_sections`` → ``_assemble_body`` → gate path renders the page — no
|
|
480
|
+
different code path from the API-key writer. A page with no response yet
|
|
481
|
+
returns "" so the offline template fills it (build stays green); it is
|
|
482
|
+
reported as ``pending`` for the handoff.
|
|
483
|
+
|
|
484
|
+
The assistant never sees frontmatter, fact lists, or Mermaid — Python
|
|
485
|
+
assembles those AFTER this returns, so agent prose cannot corrupt the
|
|
486
|
+
deterministic sections (the footgun of the old scaffold-then-edit skill).
|
|
487
|
+
|
|
488
|
+
``key_by_context`` maps each page's serialized context back to its
|
|
489
|
+
concept_id so prompt/response files are human-addressable. Two concepts with
|
|
490
|
+
identical assembled context legitimately share a file — identical context
|
|
491
|
+
means identical prose, which is the deterministic-correct outcome.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
def __init__(self, agent_dir: Path, key_by_context: dict[str, str]) -> None:
|
|
495
|
+
self.prompts = agent_dir / "prompts"
|
|
496
|
+
self.responses = agent_dir / "responses"
|
|
497
|
+
self.prompts.mkdir(parents=True, exist_ok=True)
|
|
498
|
+
self.responses.mkdir(parents=True, exist_ok=True)
|
|
499
|
+
self._key = key_by_context
|
|
500
|
+
self.pending: list[str] = []
|
|
501
|
+
self.narrated: list[str] = []
|
|
502
|
+
|
|
503
|
+
def _concept_id(self, user: str) -> str:
|
|
504
|
+
cid = self._key.get(user)
|
|
505
|
+
if cid:
|
|
506
|
+
return cid
|
|
507
|
+
import hashlib
|
|
508
|
+
|
|
509
|
+
return "ctx-" + hashlib.sha1(user.encode("utf-8")).hexdigest()[:12]
|
|
510
|
+
|
|
511
|
+
def __call__(self, system: str, user: str) -> str:
|
|
512
|
+
cid = self._concept_id(user)
|
|
513
|
+
(self.prompts / f"{cid}.md").write_text(
|
|
514
|
+
f"<!-- {_AGENT_PROMPT_MARK}: {cid} — DO NOT EDIT.\n"
|
|
515
|
+
f" Write your prose (the four §SECTION: blocks described below)\n"
|
|
516
|
+
f" to ../responses/{cid}.md, then re-run"
|
|
517
|
+
f" `graphwiki build --backend agent`. -->\n\n"
|
|
518
|
+
f"# System instructions\n\n{system}\n\n"
|
|
519
|
+
f"# Context — the ONLY source of truth\n\n{user}\n",
|
|
520
|
+
encoding="utf-8",
|
|
521
|
+
)
|
|
522
|
+
resp = self.responses / f"{cid}.md"
|
|
523
|
+
text = resp.read_text(encoding="utf-8").strip() if resp.is_file() else ""
|
|
524
|
+
if text:
|
|
525
|
+
self.narrated.append(cid)
|
|
526
|
+
return text
|
|
527
|
+
self.pending.append(cid)
|
|
528
|
+
return ""
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: graphwiki
|
|
3
|
-
description: >
|
|
4
|
-
Coding-assisted graphwiki — generate or refresh a graph-grounded code wiki
|
|
5
|
-
when no LLM API/backend is available. The coding assistant acts as the
|
|
6
|
-
prose writer only; Python still decides coverage and context. Use when the
|
|
7
|
-
user asks to "build graphwiki offline", "coding-assisted graphwiki",
|
|
8
|
-
"graphwiki without ollama/nim/api", "narrate the wiki", "rewrite offline
|
|
9
|
-
stubs", "agent-assisted wiki", or runs /graphwiki. Also use when graphwiki
|
|
10
|
-
build fails for lack of a model endpoint and the user wants quality prose
|
|
11
|
-
instead of deterministic offline templates.
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Coding-Assisted GraphWiki
|
|
15
|
-
|
|
16
|
-
Run **graphwiki** with a coding assistant (Claude Code, Grok, Cursor, Codex, …)
|
|
17
|
-
as the writer when no model API is reachable. This is **not** free-form repo
|
|
18
|
-
documentation (OpenWiki/deepagents style). Coverage, context, citations, and
|
|
19
|
-
gates stay owned by the Python tool.
|
|
20
|
-
|
|
21
|
-
## Frozen invariants (do not break)
|
|
22
|
-
|
|
23
|
-
| Layer | Owner | Agent may… |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| What to document (coverage law) | `graphwiki plan` | Never decide, never drop a required page |
|
|
26
|
-
| What facts each page gets | `graphwiki` context assembly | Never invent members/callers/paths |
|
|
27
|
-
| Narrative prose | **this skill / the agent** | Rewrite only the prose under the four `##` sections |
|
|
28
|
-
| Frontmatter, Mermaid, fact lists | Python / offline scaffold | **Never edit** |
|
|
29
|
-
| Validation | `graphwiki validate` | Must pass before claiming done |
|
|
30
|
-
|
|
31
|
-
**Anti-pattern:** grepping the repo and writing new path citations. That
|
|
32
|
-
reintroduces hallucination and fails the citation gate. Ground every claim in
|
|
33
|
-
facts already present on the page (frontmatter + deterministic sections).
|
|
34
|
-
|
|
35
|
-
Full invariant detail: `references/invariants.md`.
|
|
36
|
-
|
|
37
|
-
## Prerequisites
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
# Prefer uv on host
|
|
41
|
-
uv tool install graphwiki # or: uvx graphwiki …
|
|
42
|
-
# Engine auto-fetches on first build (or pin --engine-bin)
|
|
43
|
-
graphwiki doctor . # engine + index healthy; no LLM needed
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
If `doctor` fails on the engine, fix that first — the agent cannot replace the
|
|
47
|
-
graph engine.
|
|
48
|
-
|
|
49
|
-
## Modes
|
|
50
|
-
|
|
51
|
-
### A. Assisted build (no LLM API) — default
|
|
52
|
-
|
|
53
|
-
Use when Ollama/NIM/cloud are unavailable or the user wants agent-written prose.
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
1. Scaffold (deterministic, no network):
|
|
57
|
-
graphwiki build . --backend offline --no-web
|
|
58
|
-
# optional: --out PATH, --allow-defer GLOB, --engine-bin PATH
|
|
59
|
-
|
|
60
|
-
2. Narrate every required page under wiki/ (see "Narration recipe").
|
|
61
|
-
Prioritize high x-hotspot-score pages first if the tree is large.
|
|
62
|
-
|
|
63
|
-
3. Gate:
|
|
64
|
-
graphwiki validate .
|
|
65
|
-
|
|
66
|
-
4. Optional viewer:
|
|
67
|
-
graphwiki web --no-open # or open the URL
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### B. Assisted update (incremental)
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
1. graphwiki update . --backend offline --no-web
|
|
74
|
-
2. Narrate only pages whose body still matches the offline stub signature
|
|
75
|
-
(see "Stub detection") or that the user named.
|
|
76
|
-
3. graphwiki validate .
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### C. Editorial polish (prose only)
|
|
80
|
-
|
|
81
|
-
Existing wiki already gate-valid; user wants better narrative without a rebuild.
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
1. Do NOT re-run build (preserves state hashes).
|
|
85
|
-
2. Edit prose only under the four ## sections.
|
|
86
|
-
3. graphwiki validate .
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### D. Consume wiki while coding (read path)
|
|
90
|
-
|
|
91
|
-
Not generation. Point the agent at `wiki/.ai-context.md` and relevant pages,
|
|
92
|
-
or run `graphwiki serve` (MCP: `read_wiki_structure` / `read_wiki_contents` /
|
|
93
|
-
`ask_question`). Do not invent pages in this mode.
|
|
94
|
-
|
|
95
|
-
## Stub detection
|
|
96
|
-
|
|
97
|
-
Offline scaffold prose is graph-grounded but thin. Identify stubs by content:
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
# Offline template signature (primary)
|
|
101
|
-
rg -n "documented from the code knowledge graph" wiki -g '*.md'
|
|
102
|
-
|
|
103
|
-
# Optional: short Architecture-only stubs
|
|
104
|
-
rg -n "The graph records no internal sub-structure" wiki -g '*.md'
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
A page that still contains either phrase needs narration. After rewrite, those
|
|
108
|
-
phrases must be gone.
|
|
109
|
-
|
|
110
|
-
## Narration recipe (one page)
|
|
111
|
-
|
|
112
|
-
Load `references/page-format.md` once; apply for every page.
|
|
113
|
-
|
|
114
|
-
1. **Read** the page fully (frontmatter + body). The page *is* the context —
|
|
115
|
-
it already carries resource, callers, blast radius, public surface, tests,
|
|
116
|
-
child modules, Mermaid.
|
|
117
|
-
2. **Do not** open source files to "learn more" for new citations. Optional
|
|
118
|
-
peek at `resource` only to phrase responsibility accurately; still cite only
|
|
119
|
-
names/paths already on the page.
|
|
120
|
-
3. **Rewrite only** the narrative paragraphs under:
|
|
121
|
-
- `## Purpose & Capabilities`
|
|
122
|
-
- `## Architecture`
|
|
123
|
-
- `## Key Types`
|
|
124
|
-
- `## How It Fits`
|
|
125
|
-
4. **Leave untouched:**
|
|
126
|
-
- Entire YAML frontmatter
|
|
127
|
-
- `**Child modules:**` lists
|
|
128
|
-
- Fenced ` ```mermaid ` blocks
|
|
129
|
-
- `**Public surface:**` / `**Used by:**` / `**Blast radius**` / `**Tests:**` lists
|
|
130
|
-
5. **Prose rules** (same intent as the online writer system prompt):
|
|
131
|
-
- Concrete, not boilerplate; explain the graph facts, do not restate raw lists
|
|
132
|
-
- Reference ONLY symbols/paths already present on the page
|
|
133
|
-
- Inline-code path citations (`foo/bar.py`) must resolve to something the
|
|
134
|
-
graph indexed — safest: only cite `resource`, list basenames, or symbols
|
|
135
|
-
already shown
|
|
136
|
-
- No new frontmatter, no code fences in narrative, no inventing callers/tests
|
|
137
|
-
- Clear anti-stub floors (chars of body including deterministic sections —
|
|
138
|
-
offline scaffold already clears them; keep depth, do not shrink):
|
|
139
|
-
Subsystem ≥200, Module/Feature/Entrypoint ≥120, API/DataModel ≥80
|
|
140
|
-
6. **Write** the file in place under `wiki/` (default `--out`).
|
|
141
|
-
|
|
142
|
-
### Quality bar per section
|
|
143
|
-
|
|
144
|
-
| Section | Write |
|
|
145
|
-
|---|---|
|
|
146
|
-
| Purpose & Capabilities | 1–2 short paragraphs: what it owns; why it exists |
|
|
147
|
-
| Architecture | How it is structured; narrate Mermaid/children if present — do not redraw |
|
|
148
|
-
| Key Types | What the important public symbols are *for* (names must already be listed) |
|
|
149
|
-
| How It Fits | Callers, blast radius, tests — ground every claim in the page's lists |
|
|
150
|
-
|
|
151
|
-
If a list is empty, say so plainly (leaf / no public surface / untested) — do not invent.
|
|
152
|
-
|
|
153
|
-
## Batch strategy
|
|
154
|
-
|
|
155
|
-
For large wikis:
|
|
156
|
-
|
|
157
|
-
1. Run offline scaffold once.
|
|
158
|
-
2. Sort targets by `x-hotspot-score` descending (frontmatter).
|
|
159
|
-
3. Narrate in batches of 5–15 pages; after each batch run
|
|
160
|
-
`graphwiki validate .` so failures stay local.
|
|
161
|
-
4. Skip deferred / non-required concepts (they may be absent from disk by design).
|
|
162
|
-
|
|
163
|
-
Do not hand-create pages for concepts missing from the plan — re-run
|
|
164
|
-
`graphwiki build/update --backend offline` to materialize them.
|
|
165
|
-
|
|
166
|
-
## Failure handling
|
|
167
|
-
|
|
168
|
-
| Symptom | Action |
|
|
169
|
-
|---|---|
|
|
170
|
-
| `doctor` engine missing | Fix engine (`--engine-bin` or auto-fetch); do not free-write a wiki |
|
|
171
|
-
| `validate` structure/quality fail | Fix the named page; usually leftover TODO or stub-thin prose |
|
|
172
|
-
| `validate` citations fail | Remove or replace the invented path token; re-validate |
|
|
173
|
-
| `validate` coverage fail | Re-run offline build/update — do not invent missing pages |
|
|
174
|
-
| User wants cloud/local API instead | Prefer `graphwiki build . --backend ollama\|nim\|…` over this skill |
|
|
175
|
-
|
|
176
|
-
## Done criteria
|
|
177
|
-
|
|
178
|
-
- [ ] `graphwiki validate .` exits 0
|
|
179
|
-
- [ ] No page still contains `documented from the code knowledge graph`
|
|
180
|
-
- [ ] Frontmatter and deterministic fact lists unchanged from scaffold
|
|
181
|
-
- [ ] Summary for the user: page count narrated, validate OK, how to serve
|
|
182
|
-
(`graphwiki web` or `graphwiki serve`)
|
|
183
|
-
|
|
184
|
-
## CLI cheat sheet
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
graphwiki doctor .
|
|
188
|
-
graphwiki build . --backend offline --no-web
|
|
189
|
-
graphwiki update . --backend offline --no-web
|
|
190
|
-
graphwiki validate .
|
|
191
|
-
graphwiki web
|
|
192
|
-
graphwiki serve --out wiki # needs mcp extra
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Backend note: coding-agent CLIs are intentionally **not** `--backend` values.
|
|
196
|
-
This skill is the supported agent path: scaffold offline → agent narrates →
|
|
197
|
-
gates. See product README.
|
|
198
|
-
|
|
199
|
-
## Additional resources
|
|
200
|
-
|
|
201
|
-
- `references/invariants.md` — why agents must not traverse for generation
|
|
202
|
-
- `references/page-format.md` — frontmatter + section contract + edit boundaries
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Wiki page format (edit contract)
|
|
2
|
-
|
|
3
|
-
Default output tree: `<repo>/wiki/` (`--out` overrides).
|
|
4
|
-
|
|
5
|
-
## Tree
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
wiki/
|
|
9
|
-
.ai-context.md # index — regenerate via tool, do not hand-edit
|
|
10
|
-
architecture.md # system Mermaid — tool-owned
|
|
11
|
-
log.md # run log — tool-owned
|
|
12
|
-
.state/ # refresh hashes — tool-owned, do not edit
|
|
13
|
-
systems/*.md # Subsystem + Module
|
|
14
|
-
features/*.md # Feature
|
|
15
|
-
entrypoints/*.md # Entrypoint
|
|
16
|
-
reference/*.md # API, DataModel, Dependency, ADR, Runbook (when present)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Frontmatter (never rewrite)
|
|
20
|
-
|
|
21
|
-
```yaml
|
|
22
|
-
---
|
|
23
|
-
description: <routing key — "open when…"; not a summary>
|
|
24
|
-
resource: <canonical path or symbol>
|
|
25
|
-
title: <concept title>
|
|
26
|
-
type: Module | Subsystem | Feature | Entrypoint | …
|
|
27
|
-
x-callers: […]
|
|
28
|
-
x-grounded-paths: […]
|
|
29
|
-
x-hotspot-score: <float or null>
|
|
30
|
-
---
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
- `type` is a frozen vocabulary — do not invent types.
|
|
34
|
-
- `description` is a **routing decision** for agents ("should I open this?"),
|
|
35
|
-
not a prose summary. Leave the offline/tool value unless the user explicitly
|
|
36
|
-
asks to improve routing keys (then keep the same shape).
|
|
37
|
-
|
|
38
|
-
## Body structure (edit boundaries)
|
|
39
|
-
|
|
40
|
-
```markdown
|
|
41
|
-
## Purpose & Capabilities
|
|
42
|
-
|
|
43
|
-
<NARRATIVE — agent may rewrite>
|
|
44
|
-
|
|
45
|
-
## Architecture
|
|
46
|
-
|
|
47
|
-
<NARRATIVE — agent may rewrite>
|
|
48
|
-
|
|
49
|
-
**Child modules:** # KEEP if present
|
|
50
|
-
- `…`
|
|
51
|
-
|
|
52
|
-
```mermaid # KEEP entire fence if present
|
|
53
|
-
…
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Key Types
|
|
57
|
-
|
|
58
|
-
<NARRATIVE — agent may rewrite>
|
|
59
|
-
|
|
60
|
-
**Public surface:** # KEEP if present
|
|
61
|
-
- `…`
|
|
62
|
-
|
|
63
|
-
## How It Fits
|
|
64
|
-
|
|
65
|
-
<NARRATIVE — agent may rewrite>
|
|
66
|
-
|
|
67
|
-
**Used by:** # KEEP if present
|
|
68
|
-
- `…`
|
|
69
|
-
|
|
70
|
-
**Blast radius** — …: # KEEP if present
|
|
71
|
-
- `…`
|
|
72
|
-
|
|
73
|
-
**Tests:** # KEEP if present
|
|
74
|
-
- `…`
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Editable
|
|
78
|
-
|
|
79
|
-
- Paragraphs (and optional short bullets **only** under Key Types) that sit
|
|
80
|
-
directly under the four `##` headings, **above** the bold fact panels.
|
|
81
|
-
|
|
82
|
-
### Forbidden to change
|
|
83
|
-
|
|
84
|
-
- YAML frontmatter block
|
|
85
|
-
- Heading titles (`## Purpose & Capabilities`, etc.)
|
|
86
|
-
- Bold labels and every list item under them
|
|
87
|
-
- Mermaid code fences
|
|
88
|
-
- Adding new top-level `##` sections
|
|
89
|
-
- Adding code-sample fences in narrative (prose only)
|
|
90
|
-
|
|
91
|
-
## Offline stub signature
|
|
92
|
-
|
|
93
|
-
Scaffold purpose lines look like:
|
|
94
|
-
|
|
95
|
-
```text
|
|
96
|
-
`{title}` is a {module|subsystem|…} documented from the code knowledge graph; its
|
|
97
|
-
canonical resource is `{resource}`. …
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Replace that entire purpose paragraph with real narrative. Same for architecture
|
|
101
|
-
lines that only say "The graph records no internal sub-structure…" when richer
|
|
102
|
-
context (children, mermaid, callers) is available to explain.
|
|
103
|
-
|
|
104
|
-
## Example good edit (shape only)
|
|
105
|
-
|
|
106
|
-
**Before (offline):**
|
|
107
|
-
|
|
108
|
-
```markdown
|
|
109
|
-
## Purpose & Capabilities
|
|
110
|
-
|
|
111
|
-
`writer` is a module documented from the code knowledge graph; its canonical
|
|
112
|
-
resource is `src/graphwiki/writer.py`. The graph associates 12 symbol(s) with it,
|
|
113
|
-
of which 4 form its public surface.
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**After (assisted):**
|
|
117
|
-
|
|
118
|
-
```markdown
|
|
119
|
-
## Purpose & Capabilities
|
|
120
|
-
|
|
121
|
-
`writer` owns page narration for a single pre-assembled concept. It turns graph
|
|
122
|
-
context into frontmatter plus structured body sections, using either an
|
|
123
|
-
OpenAI-compatible completion or deterministic offline templates. Callers feed it
|
|
124
|
-
one concept at a time; it does not plan coverage or query the graph.
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Facts (resource, public surface list below) stay exactly as scaffolded.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|