@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 +34 -20
- package/dist/agent.js +1 -1
- package/dist/flatten-wiki.d.ts +19 -0
- package/dist/flatten-wiki.js +227 -0
- package/dist/prompt.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Wiki Agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@chronova/wiki-agent)
|
|
4
|
+
[](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
|
-
- **
|
|
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
|
-
|
|
25
|
+
Install globally from npm:
|
|
22
26
|
|
|
23
27
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
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
|
|
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
|
|
119
|
-
6.
|
|
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.
|
|
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
|
|
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.
|
|
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: |", "
|
|
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.
|
|
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
|
---
|