@chronova/wiki-agent 1.3.0 → 1.4.0

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.
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Wiki Agent
2
2
 
3
- A standalone Ollama-only documentation agent. It inspects your source code and generates a wiki under `.wiki/` in your project root.
3
+ [![npm version](https://img.shields.io/npm/v/@chronova/wiki-agent.svg)](https://www.npmjs.com/package/@chronova/wiki-agent)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+
6
+ A standalone Ollama-only documentation agent. It inspects your source code and generates a wiki under `.wiki/` in your project root, with optional publishing to the GitHub Wiki tab.
4
7
 
5
8
  ## Features
6
9
 
@@ -8,25 +11,27 @@ A standalone Ollama-only documentation agent. It inspects your source code and g
8
11
  - **Local or Cloud** — connect to a local Ollama server or Ollama Cloud with an API key
9
12
  - **TUI + Headless** — interactive terminal UI or `--print` for CI/CD
10
13
  - **Two commands** — `--init` to create docs from scratch, `--update` to refresh existing docs
14
+ - **GitHub Wiki tab publishing** — `--wiki` flag generates a workflow that pushes generated pages directly to `<repo>.wiki.git`
11
15
  - **Configurable** — global config in `~/.wiki/`, project config in `.wiki/`
12
16
  - **GitHub Actions** — `--init` automatically creates a scheduled update workflow in your repo
13
17
  - **Repo instructions** — reads `AGENTS.md` or `CLAUDE.md` from the project root and follows all conventions documented there
14
- - **Change reports** — each run writes `.wiki/.last-update-report.md` with created/edited pages, used as the PR body in CI
15
- - **Self-invocation guard** — the agent cannot recursively invoke `wiki` or `wiki-agent` via shell commands
18
+ - **Change reports** — each run writes `.wiki/.last-update-report.md` with created/edited pages, used as the staging PR body in CI
19
+ - **Restricted toolset** — the agent can only read files, write under `.wiki/`, and run read-only git subcommands; there is no shell tool
16
20
 
17
21
  ## Quickstart
18
22
 
19
23
  ### 1. Install
20
24
 
21
- Build from source and install globally with bun:
25
+ Install globally from npm:
22
26
 
23
27
  ```bash
24
- git clone https://github.com/nx-solutions-ug/wiki-agent.git
25
- cd wiki-agent
26
- npm install
27
- npx tsc -p tsconfig.json
28
- bun pm pack
29
- cd ~/.bun/install/global && bun add /path/to/wiki-agent/wiki-agent-0.1.0.tgz
28
+ npm install -g @chronova/wiki-agent
29
+ ```
30
+
31
+ Or with bun:
32
+
33
+ ```bash
34
+ bun add -g @chronova/wiki-agent
30
35
  ```
31
36
 
32
37
  Verify the install:
@@ -52,12 +57,21 @@ This launches the TUI where you select Ollama Local or Cloud and enter your API
52
57
  # Initialize documentation (also creates .github/workflows/update-wiki.yml)
53
58
  wiki --init
54
59
 
60
+ # Initialize and publish to the GitHub Wiki tab
61
+ wiki --init --wiki
62
+
55
63
  # Update existing documentation
56
64
  wiki --update
57
65
 
66
+ # Update and publish to the GitHub Wiki tab
67
+ wiki --update --wiki
68
+
58
69
  # Headless mode (for CI)
59
70
  wiki --update --print
60
71
 
72
+ # Headless mode with wiki tab publishing
73
+ wiki --update --print --wiki
74
+
61
75
  # Specify a model override
62
76
  wiki --init --print --model llama3.2
63
77
  ```
@@ -109,21 +123,20 @@ Environment variables take priority over config files.
109
123
 
110
124
  ## GitHub Actions
111
125
 
112
- Running `wiki --init` automatically creates `.github/workflows/update-wiki.yml` in your repo. The workflow publishes generated pages to your repository's **GitHub Wiki tab**, not to the main repo's file tree:
126
+ Running `wiki --init --wiki` automatically creates `.github/workflows/update-wiki.yml` in your repo. With `--wiki`, the workflow publishes generated pages to your repository's **GitHub Wiki tab**; without `--wiki` it only stages `.wiki/` and opens a staging PR.
113
127
 
114
128
  1. Generates a GitHub App token if `APP_CLIENT_ID` and `APP_PRIVATE_KEY` secrets are set (falls back to `GITHUB_TOKEN`)
115
129
  2. Checks out your repo, clones and builds wiki-agent from `nx-solutions-ug/wiki-agent`
116
- 3. Runs `wiki --update --print` with your Ollama Cloud credentials, staging pages under `.wiki/`
130
+ 3. Runs `wiki --update --print --verbose` (with `--wiki` if the flag was passed at `--init` time), staging pages under `.wiki/`
117
131
  4. Probes the wiki remote (`<repo>.wiki.git`) with `git ls-remote` to detect whether the wiki has been initialized
118
- 5. If there are content changes and the wiki is initialized: clones `<repo>.wiki.git`, syncs the `.wiki/` content (excluding `config.json`, `.last-update-report.md`, `.last-updated.json`) via `rsync`, commits on a `wiki/update-<timestamp>` branch, and pushes
119
- 6. Opens a pull request against the wiki repo (`--repo <owner>/<repo>.wiki`, base `master`) with `.wiki/.last-update-report.md` as the body a human merges to make updates live
120
- 7. Always opens a `docs: wiki staging snapshot` pull request against the main repo with the `.wiki/` changes, so the staged content stays auditable
132
+ 5. If there are content changes and the wiki is initialized: clones `<repo>.wiki.git`, syncs the staged `.wiki/` content (excluding `config.json` and the run metadata) via `rsync`, commits, and **pushes directly to `master`** — the wiki goes live immediately (no PR, no review gate)
133
+ 6. Always opens a `docs: wiki staging snapshot` pull request against the main repo with the `.wiki/` changes, so the staged content stays auditable
121
134
 
122
135
  ### Bootstrap the wiki first
123
136
 
124
137
  GitHub wikis must be initialized once through the UI before they can be pushed to programmatically. Open the **Wiki** tab in your repository, create the first page (any content), then run the workflow. Until then the publish step is skipped with a warning; the staging PR still opens so you can inspect the generated content.
125
138
 
126
- The full workflow is written to `.github/workflows/update-wiki.yml` by `wiki --init`. See that file (or the template in [`src/agent.ts`](src/agent.ts) `createWorkflowFile`) for the authoritative, current definition. The steps in brief: generate token → checkout → build wiki-agent → run `--update --print` → probe wiki remote → if there are content changes and the wiki is initialized, clone `<repo>.wiki.git`, `rsync` the staged `.wiki/` content (excluding `config.json` and the run metadata), commit on `wiki/update-<timestamp>`, push → open a PR against the wiki repo (base `master`) → open a `docs: wiki staging snapshot` PR against the main repo.
139
+ The full workflow is written to `.github/workflows/update-wiki.yml` by `wiki --init`. See that file (or the template in [`src/agent.ts`](src/agent.ts) `createWorkflowFile`) for the authoritative, current definition.
127
140
 
128
141
  ### Required secrets
129
142
 
@@ -160,7 +173,7 @@ Each run produces:
160
173
  ```
161
174
 
162
175
  - `.last-updated.json` — `{ "lastUpdated": "2026-07-17T10:30:00.000Z" }`
163
- - `.last-update-report.md` — markdown report listing created and edited pages, used as the PR body in CI
176
+ - `.last-update-report.md` — markdown report listing created and edited pages, used as the staging PR body in CI
164
177
  - `index.md` files — auto-generated for each directory, listing pages and subdirectories with frontmatter titles/descriptions
165
178
 
166
179
  ## Development
@@ -175,11 +188,12 @@ bun pm pack
175
188
  ## How it works
176
189
 
177
190
  1. The agent reads `AGENTS.md` or `CLAUDE.md` from the project root and follows all conventions documented there
178
- 2. It inspects your source code using filesystem tools (read, grep, glob, execute)
179
- 3. It generates wiki pages under `.wiki/` with YAML frontmatter
191
+ 2. It inspects your source code using a restricted, read-only toolset: `read_file`, `ls`, `glob`, `grep`, `ast_grep`, `ast_search`, and a read-only `git` tool (whitelisted subcommands only — no mutating git, no shell)
192
+ 3. It generates wiki pages under `.wiki/` with YAML frontmatter using `write_file` and `edit_file` (the only mutating tools, constrained to `.wiki/`)
180
193
  4. After the run, `index.md` files are synchronized for each directory
181
194
  5. `.last-updated.json` and `.last-update-report.md` are written
182
195
  6. On `--init`, a GitHub Actions workflow is created for scheduled updates
183
196
  7. In update mode, only pages affected by recent changes are refreshed
197
+ 8. With `--wiki`, the workflow also publishes to the GitHub Wiki tab by pushing directly to `<repo>.wiki.git` `master`
184
198
 
185
- The agent uses a manual tool-calling loop with the Ollama chat API — no LangChain or LangGraph dependency. The recursion limit prevents infinite loops. The `execute` tool blocks self-invocation to prevent recursive runs.
199
+ The agent uses a manual tool-calling loop with the Ollama chat API — no LangChain or LangGraph dependency. The recursion limit prevents infinite loops. There is no general-purpose shell tool; the agent cannot execute arbitrary commands on the host system.
package/dist/agent.js CHANGED
@@ -250,7 +250,7 @@ async function createWorkflowFile(projectRoot, wikiPublish) {
250
250
  "",
251
251
  ];
252
252
  if (wikiPublish) {
253
- workflow.push(" - name: Repository coordinates", " id: coords", " run: echo \"owner_repo=${GITHUB_REPOSITORY}\" >> $GITHUB_OUTPUT", "", " - name: Detect wiki initialization", " id: wiki-init", " env:", " TOKEN: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " run: |", " REMOTE=\"https://x-access-token:${TOKEN}@github.com/${{ steps.coords.outputs.owner_repo }}.wiki.git\"", " if git ls-remote --exit-code \"$REMOTE\" HEAD >/dev/null 2>&1; then", " echo \"initialized=true\" >> $GITHUB_OUTPUT", " else", " echo \"initialized=false\" >> $GITHUB_OUTPUT", " echo \"::warning::Wiki is not initialized. Create the first page in the GitHub UI (Wiki tab -> New Page), then rerun. Staging PR will still be opened.\" >> $GITHUB_OUTPUT", " fi", "", " - name: Publish to wiki repo", " id: publish", " if: steps.report.outputs.has_changes == 'true' && steps.wiki-init.outputs.initialized == 'true'", " env:", " TOKEN: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " run: |", " BRANCH=\"wiki/update-${{ steps.timestamp.outputs.timestamp }}\"", " WIKI_URL=\"https://x-access-token:${TOKEN}@github.com/${{ steps.coords.outputs.owner_repo }}.wiki.git\"", " rm -rf /tmp/wiki", " git clone \"$WIKI_URL\" /tmp/wiki", " cd /tmp/wiki", " git checkout -b \"$BRANCH\"", " # --exclude='.git' protects the wiki clone's .git directory from --delete.", " rsync -a --delete \\", " --exclude='.git' \\", " --exclude='.last-update-report.md' \\", " --exclude='.last-updated.json' \\", " --exclude='config.json' \\", " \"$GITHUB_WORKSPACE/.wiki/\" ./", " git add -A", " if ! git diff --cached --quiet; then", " git -c user.name='wiki-agent[bot]' -c user.email='bot@wiki-agent' \\", " commit -m \"docs: update wiki\"", " if ! git push origin \"$BRANCH\" 2>&1 | tee /tmp/wiki-push.log; then", " echo \"::error::Failed to push to the wiki repo. Ensure the GitHub App has contents:write on the repository (which covers the wiki), or set a WIKI_PUSH_TOKEN secret with repo scope.\"", " exit 1", " fi", " echo \"branch=$BRANCH\" >> $GITHUB_OUTPUT", " else", " echo \"::warning::No net wiki content changes after sync; skipping wiki push.\"", " echo \"branch=\" >> $GITHUB_OUTPUT", " fi", "", " - name: Open wiki update pull request", " if: steps.report.outputs.has_changes == 'true' && steps.wiki-init.outputs.initialized == 'true' && steps.publish.outputs.branch != ''", " env:", " GH_TOKEN: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " run: |", " gh pr create --repo \"${{ steps.coords.outputs.owner_repo }}.wiki\" \\", " --head \"wiki/update-${{ steps.timestamp.outputs.timestamp }}\" \\", " --base master \\", " --title \"docs: update wiki\" \\", " --body-file .wiki/.last-update-report.md", "");
253
+ workflow.push(" - name: Repository coordinates", " id: coords", " run: echo \"owner_repo=${GITHUB_REPOSITORY}\" >> $GITHUB_OUTPUT", "", " - name: Detect wiki initialization", " id: wiki-init", " env:", " TOKEN: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " run: |", " REMOTE=\"https://x-access-token:${TOKEN}@github.com/${{ steps.coords.outputs.owner_repo }}.wiki.git\"", " if git ls-remote --exit-code \"$REMOTE\" HEAD >/dev/null 2>&1; then", " echo \"initialized=true\" >> $GITHUB_OUTPUT", " else", " echo \"initialized=false\" >> $GITHUB_OUTPUT", " echo \"::warning::Wiki is not initialized. Create the first page in the GitHub UI (Wiki tab -> New Page), then rerun. Staging PR will still be opened.\" >> $GITHUB_OUTPUT", " fi", "", " - name: Publish to wiki repo", " id: publish", " if: steps.report.outputs.has_changes == 'true' && steps.wiki-init.outputs.initialized == 'true'", " env:", " TOKEN: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " run: |", " WIKI_URL=\"https://x-access-token:${TOKEN}@github.com/${{ steps.coords.outputs.owner_repo }}.wiki.git\"", " rm -rf /tmp/wiki /tmp/wiki-flat", " node /tmp/wiki-agent/dist/flatten-wiki.js \"$GITHUB_WORKSPACE/.wiki\" /tmp/wiki-flat", " git clone \"$WIKI_URL\" /tmp/wiki", " cd /tmp/wiki", " # rsync the flattened wiki output (flat names, Home.md, _Sidebar.md).", " # --exclude='.git' protects the wiki clone's .git directory from --delete.", " rsync -a --delete \\", " --exclude='.git' \\", " /tmp/wiki-flat/ ./", " git add -A", " if ! git diff --cached --quiet; then", " git -c user.name='wiki-agent[bot]' -c user.email='bot@wiki-agent' \\", " commit -m \"docs: update wiki\"", " if ! git push origin master 2>&1 | tee /tmp/wiki-push.log; then", " echo \"::error::Failed to push to the wiki repo. Ensure the GitHub App has contents:write on the repository (which covers the wiki), or set a WIKI_PUSH_TOKEN secret with repo scope.\"", " exit 1", " fi", " echo \"published=true\" >> $GITHUB_OUTPUT", " else", " echo \"::warning::No net wiki content changes after sync; skipping wiki push.\"", " echo \"published=false\" >> $GITHUB_OUTPUT", " fi");
254
254
  }
255
255
  workflow.push(" - name: Create wiki staging snapshot pull request", " uses: peter-evans/create-pull-request@v8", " if: steps.report.outputs.has_changes == 'true'", " with:", " token: ${{ secrets.WIKI_PUSH_TOKEN || steps.token.outputs.token || secrets.GITHUB_TOKEN }}", " branch: wiki/staging-${{ steps.timestamp.outputs.timestamp }}", " add-paths: .wiki", ' title: "docs: wiki staging snapshot"', ' body: ${{ steps.report.outputs.body }}');
256
256
  await writeFile(workflowPath, workflow.join("\n") + "\n", "utf8");
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Flattens the nested .wiki/ directory structure into a flat set of
3
+ * GitHub Wiki-compatible files in the output directory.
4
+ *
5
+ * Conversion rules:
6
+ * - .wiki/index.md → Home.md (the wiki landing page)
7
+ * - .wiki/quickstart.md → Quickstart.md
8
+ * - .wiki/architecture/index.md → Architecture.md
9
+ * - .wiki/architecture/overview.md → Architecture-Overview.md
10
+ * - .wiki/cli/usage.md → CLI-Usage.md
11
+ * - Internal markdown links are rewritten from relative .md paths to flat
12
+ * wiki page names (e.g. [Text](./cli/usage.md) → [Text](CLI-Usage))
13
+ * - _Sidebar.md is generated for navigation
14
+ * - Metadata files (.last-update-report.md, .last-updated.json, config.json, _plan.md) are excluded
15
+ *
16
+ * @param wikiRoot - path to the .wiki/ directory (source)
17
+ * @param outputDir - path to the output directory (will be created/cleaned)
18
+ */
19
+ export declare function flattenWiki(wikiRoot: string, outputDir: string): Promise<void>;
@@ -0,0 +1,227 @@
1
+ import { readFile, writeFile, mkdir, rm, readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ /**
4
+ * Characters forbidden in GitHub Wiki page filenames.
5
+ * The wiki title is derived from the filename, so these must be stripped/replaced.
6
+ */
7
+ const FORBIDDEN_CHARS = /[\\/:*?"<>|]/g;
8
+ /** Files excluded from the wiki publish (metadata, config, plan). */
9
+ const EXCLUDED = new Set([
10
+ ".last-update-report.md",
11
+ ".last-updated.json",
12
+ "config.json",
13
+ "_plan.md",
14
+ ]);
15
+ /**
16
+ * Converts a filename like "github-actions" or "overview" to a GitHub Wiki
17
+ * page name. Dash/underscore-separated words become PascalCase:
18
+ * "github-actions" → "GitHub-Actions", "overview" → "Overview",
19
+ * "index" → "Home" (for the root index only).
20
+ */
21
+ function toWikiPageName(filename) {
22
+ return filename
23
+ .split(/[-_]/)
24
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
25
+ .join("-");
26
+ }
27
+ /**
28
+ * Cleans a filename for GitHub Wiki: replaces forbidden characters and
29
+ * spaces with dashes, collapses repeated dashes.
30
+ */
31
+ function cleanWikiFilename(name) {
32
+ return name
33
+ .replace(FORBIDDEN_CHARS, "-")
34
+ .replace(/\s+/g, "-")
35
+ .replace(/-+/g, "-")
36
+ .replace(/^-|-$/g, "");
37
+ }
38
+ /**
39
+ * Computes the flat wiki filename for a source .wiki/ file given its
40
+ * relative path. Examples:
41
+ * index.md → Home.md
42
+ * quickstart.md → Quickstart.md
43
+ * architecture/index.md → Architecture.md
44
+ * architecture/overview.md → Architecture-Overview.md
45
+ * cli/usage.md → CLI-Usage.md
46
+ */
47
+ function flatWikiFilename(relPath) {
48
+ const parts = relPath.split(path.sep);
49
+ const basename = parts[parts.length - 1].replace(/\.md$/, "");
50
+ if (basename === "index") {
51
+ if (parts.length === 1) {
52
+ return "Home.md";
53
+ }
54
+ return `${toWikiPageName(parts[parts.length - 2])}.md`;
55
+ }
56
+ const nameParts = parts.slice(0, -1).map((p) => toWikiPageName(p));
57
+ nameParts.push(toWikiPageName(basename));
58
+ return `${cleanWikiFilename(nameParts.join("-"))}.md`;
59
+ }
60
+ /**
61
+ * Extracts the title from YAML frontmatter, falling back to the wiki name.
62
+ */
63
+ function extractTitle(content, wikiName) {
64
+ const fmMatch = content.match(/^---\n[\s\S]*?title:\s*(.+?)\n[\s\S]*?---/);
65
+ if (fmMatch) {
66
+ return fmMatch[1].trim().replace(/['"]/g, "");
67
+ }
68
+ return wikiName.replace(/-/g, " ");
69
+ }
70
+ /**
71
+ * Recursively walks the source .wiki/ directory and collects all .md files
72
+ * with their relative paths, excluding metadata and config files.
73
+ */
74
+ async function collectMarkdownFiles(srcDir, baseDir) {
75
+ const results = [];
76
+ async function walk(dir) {
77
+ const entries = await readdir(dir, { withFileTypes: true });
78
+ for (const entry of entries) {
79
+ const absPath = path.join(dir, entry.name);
80
+ const relPath = path.relative(baseDir, absPath);
81
+ if (entry.isDirectory()) {
82
+ await walk(absPath);
83
+ }
84
+ else if (entry.name.endsWith(".md") && !EXCLUDED.has(entry.name)) {
85
+ results.push({ relPath, absPath });
86
+ }
87
+ }
88
+ }
89
+ await walk(srcDir);
90
+ return results;
91
+ }
92
+ /**
93
+ * Rewrites internal markdown links from the nested .wiki/ structure to
94
+ * flat wiki-page links. Uses a map of source-relative paths → flat wiki
95
+ * names to correctly resolve links like `./cli/usage.md` → `CLI-Usage`
96
+ * (not just `Usage`). Directory links like `./architecture/` resolve to
97
+ * the section's index page (`Architecture`). Anchor fragments are preserved.
98
+ *
99
+ * @param content - the markdown file content
100
+ * @param sourceRelDir - the directory of the source file, relative to .wiki/ (e.g. "cli", "")
101
+ * @param pathMap - map of source-relative .md paths (without ./ prefix) → flat wiki names
102
+ */
103
+ function rewriteLinks(content, sourceRelDir, pathMap) {
104
+ return content.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, text, url) => {
105
+ // Only rewrite internal .md links or directory links (not http URLs)
106
+ if (url.startsWith("http://") || url.startsWith("https://")) {
107
+ return match;
108
+ }
109
+ // Split off anchor fragment
110
+ const hashIdx = url.indexOf("#");
111
+ const anchor = hashIdx >= 0 ? url.slice(hashIdx) : "";
112
+ const linkPath = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
113
+ // Resolve the link relative to the source file's directory
114
+ const resolved = path
115
+ .normalize(path.join(sourceRelDir, linkPath))
116
+ .replace(/^\.\//, "");
117
+ // Look up in the path map
118
+ const wikiName = pathMap.get(resolved);
119
+ if (wikiName) {
120
+ return `[${text}](${wikiName}${anchor})`;
121
+ }
122
+ // Handle directory links (e.g. ./architecture/ → architecture/index.md)
123
+ if (!linkPath.endsWith(".md")) {
124
+ const dirIndex = pathMap.get(`${resolved}/index.md`);
125
+ if (dirIndex) {
126
+ return `[${text}](${dirIndex}${anchor})`;
127
+ }
128
+ const dirIndexNoSlash = pathMap.get(`${resolved.replace(/\/$/, "")}/index.md`);
129
+ if (dirIndexNoSlash) {
130
+ return `[${text}](${dirIndexNoSlash}${anchor})`;
131
+ }
132
+ }
133
+ // Leave unresolvable links as-is
134
+ return match;
135
+ });
136
+ }
137
+ /**
138
+ * Generates the _Sidebar.md content from the collected pages, grouped by
139
+ * their original top-level directory (or "Guides" for root-level pages).
140
+ */
141
+ function generateSidebar(pages) {
142
+ const sections = new Map();
143
+ for (const page of pages) {
144
+ const parts = page.relPath.split(path.sep);
145
+ const section = parts.length > 1 ? parts[0] : "Guides";
146
+ const key = toWikiPageName(section);
147
+ if (!sections.has(key)) {
148
+ sections.set(key, []);
149
+ }
150
+ sections.get(key).push({ wikiName: page.wikiName, title: page.title });
151
+ }
152
+ const lines = ["# Navigation", "", "- [Home](Home)", ""];
153
+ for (const [sectionName, sectionPages] of sections) {
154
+ lines.push(`## ${sectionName}`, "");
155
+ for (const page of sectionPages) {
156
+ if (page.wikiName === "Home")
157
+ continue;
158
+ lines.push(`- [${page.title}](${page.wikiName})`);
159
+ }
160
+ lines.push("");
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+ /**
165
+ * Flattens the nested .wiki/ directory structure into a flat set of
166
+ * GitHub Wiki-compatible files in the output directory.
167
+ *
168
+ * Conversion rules:
169
+ * - .wiki/index.md → Home.md (the wiki landing page)
170
+ * - .wiki/quickstart.md → Quickstart.md
171
+ * - .wiki/architecture/index.md → Architecture.md
172
+ * - .wiki/architecture/overview.md → Architecture-Overview.md
173
+ * - .wiki/cli/usage.md → CLI-Usage.md
174
+ * - Internal markdown links are rewritten from relative .md paths to flat
175
+ * wiki page names (e.g. [Text](./cli/usage.md) → [Text](CLI-Usage))
176
+ * - _Sidebar.md is generated for navigation
177
+ * - Metadata files (.last-update-report.md, .last-updated.json, config.json, _plan.md) are excluded
178
+ *
179
+ * @param wikiRoot - path to the .wiki/ directory (source)
180
+ * @param outputDir - path to the output directory (will be created/cleaned)
181
+ */
182
+ export async function flattenWiki(wikiRoot, outputDir) {
183
+ // Clean and create output dir
184
+ await rm(outputDir, { recursive: true, force: true });
185
+ await mkdir(outputDir, { recursive: true });
186
+ // Collect all markdown files
187
+ const files = await collectMarkdownFiles(wikiRoot, wikiRoot);
188
+ // Build a path map: source-relative path → flat wiki name
189
+ // e.g. "cli/usage.md" → "CLI-Usage", "index.md" → "Home"
190
+ const pathMap = new Map();
191
+ for (const file of files) {
192
+ const flatName = flatWikiFilename(file.relPath).replace(/\.md$/, "");
193
+ pathMap.set(file.relPath, flatName);
194
+ }
195
+ const pageInfos = [];
196
+ for (const file of files) {
197
+ const content = await readFile(file.absPath, "utf8");
198
+ const flatFile = flatWikiFilename(file.relPath);
199
+ const wikiName = flatFile.replace(/\.md$/, "");
200
+ // Determine the source file's directory relative to .wiki/
201
+ const sourceRelDir = path.dirname(file.relPath) === "." ? "" : path.dirname(file.relPath);
202
+ // Rewrite links
203
+ const rewritten = rewriteLinks(content, sourceRelDir, pathMap);
204
+ // Extract title for sidebar
205
+ const title = extractTitle(content, wikiName);
206
+ pageInfos.push({ relPath: file.relPath, wikiName, title });
207
+ // Write the file
208
+ await writeFile(path.join(outputDir, flatFile), rewritten, "utf8");
209
+ }
210
+ // Generate _Sidebar.md
211
+ const sidebar = generateSidebar(pageInfos);
212
+ await writeFile(path.join(outputDir, "_Sidebar.md"), sidebar, "utf8");
213
+ }
214
+ // CLI entrypoint: node dist/flatten-wiki.js <wiki-root> <output-dir>
215
+ if (process.argv[1] && process.argv[1].endsWith("flatten-wiki.js")) {
216
+ const [wikiRoot, outputDir] = process.argv.slice(2);
217
+ if (!wikiRoot || !outputDir) {
218
+ console.error("Usage: node flatten-wiki.js <wiki-root> <output-dir>");
219
+ process.exit(1);
220
+ }
221
+ flattenWiki(wikiRoot, outputDir)
222
+ .then(() => console.log(`Flattened ${wikiRoot} to ${outputDir}/`))
223
+ .catch((error) => {
224
+ console.error(error instanceof Error ? error.message : String(error));
225
+ process.exit(1);
226
+ });
227
+ }
package/dist/prompt.js CHANGED
@@ -39,7 +39,14 @@ You cannot run build tools, package managers, test runners, scripts, or any prog
39
39
 
40
40
  # Output location
41
41
  - Write documentation under .wiki/ in the project root. Use paths such as .wiki/quickstart.md, .wiki/architecture/overview.md, .wiki/cli/usage.md.
42
- - Your output is published to the repository's GitHub Wiki tab after the run. Wiki page titles come from filenames, so never use the characters \ / : * ? " < > | in wiki file names.
42
+ - Your output is published to the repository's GitHub Wiki tab after the run. A conversion step flattens the nested .wiki/ directory structure into the flat format GitHub Wikis require:
43
+ - .wiki/index.md becomes Home.md (the wiki landing page)
44
+ - .wiki/architecture/index.md becomes Architecture.md (section landing page)
45
+ - .wiki/architecture/overview.md becomes Architecture-Overview.md (dash-joined flat name)
46
+ - .wiki/cli/usage.md becomes CLI-Usage.md
47
+ - A _Sidebar.md navigation file is generated automatically from the page structure
48
+ - Internal markdown links (e.g. [Text](./architecture/overview.md)) are rewritten to flat wiki page names (e.g. [Text](Architecture-Overview))
49
+ - Keep using nested directory paths and relative .md links in your source files — the conversion step handles the flattening. Never use the characters \ / : * ? " < > | in wiki file names.
43
50
  - Never write markdown files outside .wiki/.
44
51
  - Each wiki page must start with YAML frontmatter:
45
52
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chronova/wiki-agent",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Standalone Ollama-only documentation agent",
5
5
  "type": "module",
6
6
  "bin": {