@chronova/wiki-agent 1.1.4
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 +248 -0
- package/dist/agent.d.ts +35 -0
- package/dist/agent.js +317 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +113 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +95 -0
- package/dist/index-middleware.d.ts +5 -0
- package/dist/index-middleware.js +135 -0
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +145 -0
- package/dist/tools.d.ts +24 -0
- package/dist/tools.js +471 -0
- package/dist/tui/App.d.ts +10 -0
- package/dist/tui/App.js +31 -0
- package/dist/tui/CredentialsSetup.d.ts +8 -0
- package/dist/tui/CredentialsSetup.js +74 -0
- package/dist/tui/RunView.d.ts +12 -0
- package/dist/tui/RunView.js +94 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Wiki Agent
|
|
2
|
+
|
|
3
|
+
A standalone Ollama-only documentation agent. It inspects your source code and generates a wiki under `.wiki/` in your project root.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Ollama-only** — uses the native `ollama` SDK, no LangChain dependency
|
|
8
|
+
- **Local or Cloud** — connect to a local Ollama server or Ollama Cloud with an API key
|
|
9
|
+
- **TUI + Headless** — interactive terminal UI or `--print` for CI/CD
|
|
10
|
+
- **Two commands** — `--init` to create docs from scratch, `--update` to refresh existing docs
|
|
11
|
+
- **Configurable** — global config in `~/.wiki/`, project config in `.wiki/`
|
|
12
|
+
- **GitHub Actions** — `--init` automatically creates a scheduled update workflow in your repo
|
|
13
|
+
- **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
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
### 1. Install
|
|
20
|
+
|
|
21
|
+
Build from source and install globally with bun:
|
|
22
|
+
|
|
23
|
+
```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
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Verify the install:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
wiki --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Configure
|
|
39
|
+
|
|
40
|
+
Run interactively once to set up credentials:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd your-project
|
|
44
|
+
wiki --init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This launches the TUI where you select Ollama Local or Cloud and enter your API key (if cloud). The default model is `kimi-k2.7-code`.
|
|
48
|
+
|
|
49
|
+
### 3. Use
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Initialize documentation (also creates .github/workflows/update-wiki.yml)
|
|
53
|
+
wiki --init
|
|
54
|
+
|
|
55
|
+
# Update existing documentation
|
|
56
|
+
wiki --update
|
|
57
|
+
|
|
58
|
+
# Headless mode (for CI)
|
|
59
|
+
wiki --update --print
|
|
60
|
+
|
|
61
|
+
# Specify a model override
|
|
62
|
+
wiki --init --print --model llama3.2
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
### Global config (`~/.wiki/config.json`)
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mode": "local",
|
|
72
|
+
"defaultModel": "kimi-k2.7-code"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For cloud:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mode": "cloud",
|
|
81
|
+
"apiKey": "your-api-key",
|
|
82
|
+
"defaultModel": "kimi-k2.7-code"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Project config (`.wiki/config.json`)
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"modelOverride": "llama3.2",
|
|
91
|
+
"lastUpdate": {
|
|
92
|
+
"commitSha": "abc1234",
|
|
93
|
+
"timestamp": "2026-07-17T00:00:00Z"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Environment variables
|
|
99
|
+
|
|
100
|
+
| Variable | Description | Default |
|
|
101
|
+
|----------|-------------|---------|
|
|
102
|
+
| `WIKI_OLLAMA_MODE` | `"local"` or `"cloud"` | from config |
|
|
103
|
+
| `WIKI_OLLAMA_API_KEY` | API key for cloud mode | from config |
|
|
104
|
+
| `WIKI_OLLAMA_BASE_URL` | Override Ollama server URL | `http://localhost:11434` / `https://ollama.com` |
|
|
105
|
+
| `WIKI_MODEL` | Override model ID | from config |
|
|
106
|
+
| `WIKI_RECURSION_LIMIT` | Max agent iterations | `200` |
|
|
107
|
+
|
|
108
|
+
Environment variables take priority over config files.
|
|
109
|
+
|
|
110
|
+
## GitHub Actions
|
|
111
|
+
|
|
112
|
+
Running `wiki --init` automatically creates `.github/workflows/update-wiki.yml` in your repo. The workflow:
|
|
113
|
+
|
|
114
|
+
1. Generates a GitHub App token if `APP_CLIENT_ID` and `APP_PRIVATE_KEY` secrets are set (falls back to `GITHUB_TOKEN`)
|
|
115
|
+
2. Checks out your repo
|
|
116
|
+
3. Clones and builds wiki-agent from `nx-solutions-ug/wiki-agent`
|
|
117
|
+
4. Runs `wiki --update --print` with your Ollama Cloud credentials
|
|
118
|
+
5. Reads `.wiki/.last-update-report.md` and uses it as the PR body
|
|
119
|
+
6. Creates a pull request with the `.wiki/` changes
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
name: Wiki Update
|
|
123
|
+
on:
|
|
124
|
+
workflow_dispatch:
|
|
125
|
+
push:
|
|
126
|
+
branches:
|
|
127
|
+
- main
|
|
128
|
+
schedule:
|
|
129
|
+
- cron: "0 8 * * *"
|
|
130
|
+
permissions:
|
|
131
|
+
contents: write
|
|
132
|
+
pull-requests: write
|
|
133
|
+
jobs:
|
|
134
|
+
update:
|
|
135
|
+
runs-on: ubuntu-latest
|
|
136
|
+
steps:
|
|
137
|
+
- name: Generate token
|
|
138
|
+
id: token
|
|
139
|
+
uses: actions/create-github-app-token@v3
|
|
140
|
+
with:
|
|
141
|
+
client-id: ${{ secrets.APP_CLIENT_ID }}
|
|
142
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
143
|
+
continue-on-error: true
|
|
144
|
+
|
|
145
|
+
- uses: actions/checkout@v7
|
|
146
|
+
with:
|
|
147
|
+
token: ${{ steps.token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
148
|
+
|
|
149
|
+
- uses: actions/setup-node@v7
|
|
150
|
+
with:
|
|
151
|
+
node-version: "25"
|
|
152
|
+
|
|
153
|
+
- name: Build Wiki Agent
|
|
154
|
+
run: |
|
|
155
|
+
git clone --branch main --depth 1 https://github.com/nx-solutions-ug/wiki-agent.git /tmp/wiki-agent
|
|
156
|
+
cd /tmp/wiki-agent
|
|
157
|
+
npm install
|
|
158
|
+
npx tsc -p tsconfig.json
|
|
159
|
+
|
|
160
|
+
- name: Run Wiki Agent
|
|
161
|
+
run: node /tmp/wiki-agent/dist/cli.js --update --print
|
|
162
|
+
env:
|
|
163
|
+
WIKI_OLLAMA_MODE: cloud
|
|
164
|
+
WIKI_OLLAMA_API_KEY: ${{ secrets.WIKI_OLLAMA_API_KEY }}
|
|
165
|
+
WIKI_MODEL: ${{ vars.WIKI_MODEL || 'kimi-k2.7-code' }}
|
|
166
|
+
|
|
167
|
+
- name: Generate timestamp
|
|
168
|
+
id: timestamp
|
|
169
|
+
run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
|
|
170
|
+
|
|
171
|
+
- name: Read update report
|
|
172
|
+
id: report
|
|
173
|
+
run: |
|
|
174
|
+
if [ -f .wiki/.last-update-report.md ]; then
|
|
175
|
+
echo "body<<EOF" >> $GITHUB_OUTPUT
|
|
176
|
+
cat .wiki/.last-update-report.md >> $GITHUB_OUTPUT
|
|
177
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
178
|
+
else
|
|
179
|
+
echo "body=Automated wiki documentation update." >> $GITHUB_OUTPUT
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
- uses: peter-evans/create-pull-request@v8
|
|
183
|
+
with:
|
|
184
|
+
token: ${{ steps.token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
185
|
+
add-paths: .wiki
|
|
186
|
+
branch: wiki/update-${{ steps.timestamp.outputs.timestamp }}
|
|
187
|
+
commit-message: "docs: update wiki"
|
|
188
|
+
title: "docs: update wiki"
|
|
189
|
+
body: ${{ steps.report.outputs.body }}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Required secrets
|
|
193
|
+
|
|
194
|
+
| Secret | Required | Description |
|
|
195
|
+
|--------|----------|-------------|
|
|
196
|
+
| `WIKI_OLLAMA_API_KEY` | Yes | Ollama Cloud API key |
|
|
197
|
+
| `APP_CLIENT_ID` | No | GitHub App client ID for token generation (falls back to `GITHUB_TOKEN`) |
|
|
198
|
+
| `APP_PRIVATE_KEY` | No | GitHub App private key |
|
|
199
|
+
|
|
200
|
+
### Optional variables
|
|
201
|
+
|
|
202
|
+
| Variable | Default | Description |
|
|
203
|
+
|----------|---------|-------------|
|
|
204
|
+
| `WIKI_MODEL` | `kimi-k2.7-code` | Model ID override |
|
|
205
|
+
|
|
206
|
+
## Output
|
|
207
|
+
|
|
208
|
+
Each run produces:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
.wiki/
|
|
212
|
+
├── .last-updated.json # ISO timestamp of last run
|
|
213
|
+
├── .last-update-report.md # Change report (created/edited pages)
|
|
214
|
+
├── config.json # Project-specific config
|
|
215
|
+
├── quickstart.md # Entry point
|
|
216
|
+
├── architecture/
|
|
217
|
+
│ ├── index.md # Auto-generated directory index
|
|
218
|
+
│ └── overview.md
|
|
219
|
+
├── cli/
|
|
220
|
+
│ ├── index.md
|
|
221
|
+
│ └── usage.md
|
|
222
|
+
└── index.md # Root directory index
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- `.last-updated.json` — `{ "lastUpdated": "2026-07-17T10:30:00.000Z" }`
|
|
226
|
+
- `.last-update-report.md` — markdown report listing created and edited pages, used as the PR body in CI
|
|
227
|
+
- `index.md` files — auto-generated for each directory, listing pages and subdirectories with frontmatter titles/descriptions
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
bun install
|
|
233
|
+
npx tsc -p tsconfig.json
|
|
234
|
+
npx vitest run
|
|
235
|
+
bun pm pack
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## How it works
|
|
239
|
+
|
|
240
|
+
1. The agent reads `AGENTS.md` or `CLAUDE.md` from the project root and follows all conventions documented there
|
|
241
|
+
2. It inspects your source code using filesystem tools (read, grep, glob, execute)
|
|
242
|
+
3. It generates wiki pages under `.wiki/` with YAML frontmatter
|
|
243
|
+
4. After the run, `index.md` files are synchronized for each directory
|
|
244
|
+
5. `.last-updated.json` and `.last-update-report.md` are written
|
|
245
|
+
6. On `--init`, a GitHub Actions workflow is created for scheduled updates
|
|
246
|
+
7. In update mode, only pages affected by recent changes are refreshed
|
|
247
|
+
|
|
248
|
+
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.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type WikiCommand } from "./prompt.js";
|
|
2
|
+
import { Ollama } from "ollama";
|
|
3
|
+
export type AgentEvent = {
|
|
4
|
+
type: "assistant";
|
|
5
|
+
content: string;
|
|
6
|
+
} | {
|
|
7
|
+
type: "tool";
|
|
8
|
+
name: string;
|
|
9
|
+
result: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "error";
|
|
12
|
+
message: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "done";
|
|
15
|
+
summary: string;
|
|
16
|
+
};
|
|
17
|
+
export interface RunOptions {
|
|
18
|
+
command: WikiCommand;
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
model: string;
|
|
21
|
+
gitSummary?: string;
|
|
22
|
+
maxIterations?: number;
|
|
23
|
+
stream?: boolean;
|
|
24
|
+
onEvent?: (event: AgentEvent) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function runAgent(client: Ollama, options: RunOptions): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Generates a markdown report of what changed during this run.
|
|
29
|
+
* Written to .wiki/.last-update-report.md and used as the PR body.
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateUpdateReport(command: WikiCommand, changedFiles: {
|
|
32
|
+
action: string;
|
|
33
|
+
path: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
}[]): string;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createSystemPrompt, createUserMessage } from "./prompt.js";
|
|
4
|
+
import { createTools, executeTool } from "./tools.js";
|
|
5
|
+
import { synchronizeWikiIndexes } from "./index-middleware.js";
|
|
6
|
+
const DEFAULT_MAX_ITERATIONS = 200;
|
|
7
|
+
function resolveMaxIterations() {
|
|
8
|
+
const env = process.env.WIKI_RECURSION_LIMIT;
|
|
9
|
+
if (!env) {
|
|
10
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
11
|
+
}
|
|
12
|
+
const parsed = Number.parseInt(env, 10);
|
|
13
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) {
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Normalizes tool call arguments to an object. The Ollama API returns
|
|
20
|
+
* arguments as a JSON string or as a parsed object depending on the model —
|
|
21
|
+
* handle both.
|
|
22
|
+
*/
|
|
23
|
+
function normalizeToolCallArgs(args) {
|
|
24
|
+
if (args === null || args === undefined) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
if (typeof args === "string") {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(args);
|
|
30
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Malformed JSON — return empty so the tool gets called with no args
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
if (typeof args === "object" && !Array.isArray(args)) {
|
|
41
|
+
return args;
|
|
42
|
+
}
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
export async function runAgent(client, options) {
|
|
46
|
+
const { command, projectRoot, model, gitSummary, maxIterations, stream = false, onEvent = () => { }, } = options;
|
|
47
|
+
const maxIter = maxIterations ?? resolveMaxIterations();
|
|
48
|
+
const tools = createTools(projectRoot);
|
|
49
|
+
const systemPrompt = await createSystemPrompt(projectRoot);
|
|
50
|
+
const userMessage = createUserMessage(command, projectRoot, gitSummary);
|
|
51
|
+
const messages = [
|
|
52
|
+
{ role: "system", content: systemPrompt },
|
|
53
|
+
{ role: "user", content: userMessage },
|
|
54
|
+
];
|
|
55
|
+
const changedFiles = [];
|
|
56
|
+
for (let i = 0; i < maxIter; i++) {
|
|
57
|
+
let assistantContent = "";
|
|
58
|
+
let toolCalls = [];
|
|
59
|
+
try {
|
|
60
|
+
if (stream) {
|
|
61
|
+
const streamResponse = await client.chat({
|
|
62
|
+
model,
|
|
63
|
+
messages: messages,
|
|
64
|
+
tools: tools.map((t) => t.definition),
|
|
65
|
+
stream: true,
|
|
66
|
+
});
|
|
67
|
+
for await (const chunk of streamResponse) {
|
|
68
|
+
if (chunk.message?.content) {
|
|
69
|
+
assistantContent += chunk.message.content;
|
|
70
|
+
onEvent({ type: "assistant", content: chunk.message.content });
|
|
71
|
+
}
|
|
72
|
+
if (chunk.message?.tool_calls) {
|
|
73
|
+
for (const tc of chunk.message.tool_calls) {
|
|
74
|
+
if (tc.function?.name) {
|
|
75
|
+
toolCalls.push({
|
|
76
|
+
function: {
|
|
77
|
+
name: tc.function.name,
|
|
78
|
+
arguments: normalizeToolCallArgs(tc.function.arguments),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const result = await client.chat({
|
|
88
|
+
model,
|
|
89
|
+
messages: messages,
|
|
90
|
+
tools: tools.map((t) => t.definition),
|
|
91
|
+
stream: false,
|
|
92
|
+
});
|
|
93
|
+
const msgContent = result.message?.content;
|
|
94
|
+
assistantContent = typeof msgContent === "string" ? msgContent : "";
|
|
95
|
+
onEvent({ type: "assistant", content: assistantContent });
|
|
96
|
+
if (result.message?.tool_calls) {
|
|
97
|
+
for (const tc of result.message.tool_calls) {
|
|
98
|
+
if (tc.function?.name) {
|
|
99
|
+
toolCalls.push({
|
|
100
|
+
function: {
|
|
101
|
+
name: tc.function.name,
|
|
102
|
+
arguments: normalizeToolCallArgs(tc.function.arguments),
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
if (assistantContent) {
|
|
113
|
+
onEvent({
|
|
114
|
+
type: "done",
|
|
115
|
+
summary: `Agent completed with API warning: ${message}`,
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
onEvent({ type: "error", message });
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
const assistantMessage = {
|
|
123
|
+
role: "assistant",
|
|
124
|
+
content: assistantContent,
|
|
125
|
+
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
126
|
+
};
|
|
127
|
+
messages.push(assistantMessage);
|
|
128
|
+
if (toolCalls.length === 0) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
for (const toolCall of toolCalls) {
|
|
132
|
+
const toolName = toolCall.function.name;
|
|
133
|
+
const args = toolCall.function.arguments;
|
|
134
|
+
onEvent({ type: "tool", name: toolName, result: "" });
|
|
135
|
+
const result = await executeTool(toolName, args, projectRoot);
|
|
136
|
+
if (toolName === "write_file" || toolName === "edit_file") {
|
|
137
|
+
const filePath = typeof args.path === "string" ? args.path : "unknown";
|
|
138
|
+
if (result.startsWith("Wrote ") || result.startsWith("Edited ")) {
|
|
139
|
+
// Use the assistant's prose preceding this tool call as the
|
|
140
|
+
// human-readable description of what changed. Falls back to the
|
|
141
|
+
// tool result if the model didn't narrate the change.
|
|
142
|
+
const description = assistantContent.trim() || result;
|
|
143
|
+
changedFiles.push({
|
|
144
|
+
action: toolName === "write_file" ? "created" : "edited",
|
|
145
|
+
path: filePath,
|
|
146
|
+
description,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
onEvent({ type: "tool", name: toolName, result });
|
|
151
|
+
// Ollama uses tool_name (not tool_call_id) to associate tool results
|
|
152
|
+
messages.push({
|
|
153
|
+
role: "tool",
|
|
154
|
+
content: result,
|
|
155
|
+
tool_name: toolName,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await createWorkflowFile(projectRoot);
|
|
160
|
+
onEvent({ type: "tool", name: "create_workflow", result: "Created .github/workflows/update-wiki.yml" });
|
|
161
|
+
if (changedFiles.length === 0) {
|
|
162
|
+
onEvent({ type: "done", summary: "Wiki is already current. No files changed." });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await synchronizeWikiIndexes(path.join(projectRoot, ".wiki"));
|
|
166
|
+
await writeFile(path.join(projectRoot, ".wiki", ".last-updated.json"), JSON.stringify({ lastUpdated: new Date().toISOString() }, null, 2) + "\n", "utf8");
|
|
167
|
+
const report = generateUpdateReport(command, changedFiles);
|
|
168
|
+
await writeFile(path.join(projectRoot, ".wiki", ".last-update-report.md"), report, "utf8");
|
|
169
|
+
onEvent({ type: "done", summary: "Agent run complete" });
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Creates a GitHub Actions workflow file in the target repo that checks out
|
|
173
|
+
* the wiki-agent source, builds it, and runs --update --print on a schedule.
|
|
174
|
+
*/
|
|
175
|
+
async function createWorkflowFile(projectRoot) {
|
|
176
|
+
const workflowsDir = path.join(projectRoot, ".github", "workflows");
|
|
177
|
+
const workflowPath = path.join(workflowsDir, "update-wiki.yml");
|
|
178
|
+
await mkdir(workflowsDir, { recursive: true });
|
|
179
|
+
const workflow = [
|
|
180
|
+
"name: Wiki Update",
|
|
181
|
+
"",
|
|
182
|
+
"on:",
|
|
183
|
+
" workflow_dispatch:",
|
|
184
|
+
" push:",
|
|
185
|
+
" branches:",
|
|
186
|
+
" - main",
|
|
187
|
+
" schedule:",
|
|
188
|
+
' - cron: "0 8 * * *"',
|
|
189
|
+
"",
|
|
190
|
+
"permissions:",
|
|
191
|
+
" contents: write",
|
|
192
|
+
" pull-requests: write",
|
|
193
|
+
"",
|
|
194
|
+
"jobs:",
|
|
195
|
+
" update:",
|
|
196
|
+
" runs-on: ubuntu-latest",
|
|
197
|
+
" steps:",
|
|
198
|
+
" - name: Generate token",
|
|
199
|
+
" id: token",
|
|
200
|
+
" uses: actions/create-github-app-token@v3",
|
|
201
|
+
" with:",
|
|
202
|
+
" client-id: ${{ secrets.APP_CLIENT_ID }}",
|
|
203
|
+
" private-key: ${{ secrets.APP_PRIVATE_KEY }}",
|
|
204
|
+
" continue-on-error: true",
|
|
205
|
+
"",
|
|
206
|
+
" - name: Check out repository",
|
|
207
|
+
" uses: actions/checkout@v7",
|
|
208
|
+
" with:",
|
|
209
|
+
" token: ${{ steps.token.outputs.token || secrets.GITHUB_TOKEN }}",
|
|
210
|
+
"",
|
|
211
|
+
" - name: Set up Node.js",
|
|
212
|
+
" uses: actions/setup-node@v7",
|
|
213
|
+
" with:",
|
|
214
|
+
' node-version: "25"',
|
|
215
|
+
"",
|
|
216
|
+
" - name: Build Wiki Agent",
|
|
217
|
+
" run: |",
|
|
218
|
+
" git clone --branch main --depth 1 https://github.com/nx-solutions-ug/wiki-agent.git /tmp/wiki-agent",
|
|
219
|
+
" cd /tmp/wiki-agent",
|
|
220
|
+
" npm install",
|
|
221
|
+
" npx tsc -p tsconfig.json",
|
|
222
|
+
"",
|
|
223
|
+
" - name: Run Wiki Agent",
|
|
224
|
+
" run: node /tmp/wiki-agent/dist/cli.js --update --print --verbose",
|
|
225
|
+
" env:",
|
|
226
|
+
" WIKI_OLLAMA_MODE: cloud",
|
|
227
|
+
' WIKI_OLLAMA_API_KEY: ${{ secrets.WIKI_OLLAMA_API_KEY }}',
|
|
228
|
+
" WIKI_MODEL: ${{ vars.WIKI_MODEL || 'kimi-k2.7-code' }}",
|
|
229
|
+
" - name: Generate timestamp",
|
|
230
|
+
" id: timestamp",
|
|
231
|
+
" run: echo \"timestamp=$(date +%s)\" >> $GITHUB_OUTPUT",
|
|
232
|
+
"",
|
|
233
|
+
" - name: Check for changes",
|
|
234
|
+
" id: report",
|
|
235
|
+
" run: |",
|
|
236
|
+
" # Collect changes under .wiki (tracked + untracked), excluding",
|
|
237
|
+
" # the run metadata files. Only content changes open a PR.",
|
|
238
|
+
" changes=$(git status --porcelain .wiki | sed 's/^...//' | grep -vE '^\\.wiki/\\.(last-update-report\\.md|last-updated\\.json)$' | sed '/^[[:space:]]*$/d')",
|
|
239
|
+
" if [ -n \"$changes\" ]; then",
|
|
240
|
+
" echo \"has_changes=true\" >> $GITHUB_OUTPUT",
|
|
241
|
+
" echo \"body<<EOF\" >> $GITHUB_OUTPUT",
|
|
242
|
+
" cat .wiki/.last-update-report.md >> $GITHUB_OUTPUT",
|
|
243
|
+
" echo \"\" >> $GITHUB_OUTPUT",
|
|
244
|
+
" echo \"EOF\" >> $GITHUB_OUTPUT",
|
|
245
|
+
" else",
|
|
246
|
+
" echo \"has_changes=false\" >> $GITHUB_OUTPUT",
|
|
247
|
+
" fi",
|
|
248
|
+
"",
|
|
249
|
+
" - name: Create Wiki update pull request",
|
|
250
|
+
" uses: peter-evans/create-pull-request@v8",
|
|
251
|
+
" if: steps.report.outputs.has_changes == 'true'",
|
|
252
|
+
" with:",
|
|
253
|
+
" token: ${{ steps.token.outputs.token || secrets.GITHUB_TOKEN }}",
|
|
254
|
+
" branch: wiki/update-${{ steps.timestamp.outputs.timestamp }}",
|
|
255
|
+
" add-paths: .wiki",
|
|
256
|
+
' title: "docs: update wiki"',
|
|
257
|
+
' body: ${{ steps.report.outputs.body }}',
|
|
258
|
+
].join("\n");
|
|
259
|
+
await writeFile(workflowPath, workflow, "utf8");
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Generates a markdown report of what changed during this run.
|
|
263
|
+
* Written to .wiki/.last-update-report.md and used as the PR body.
|
|
264
|
+
*/
|
|
265
|
+
export function generateUpdateReport(command, changedFiles) {
|
|
266
|
+
const timestamp = new Date().toISOString();
|
|
267
|
+
const actionLabel = command === "init" ? "Initialized" : "Updated";
|
|
268
|
+
if (changedFiles.length === 0) {
|
|
269
|
+
return [
|
|
270
|
+
`# Wiki ${actionLabel}`,
|
|
271
|
+
"",
|
|
272
|
+
"No files were changed. The wiki is already current.",
|
|
273
|
+
].join("\n") + "\n";
|
|
274
|
+
}
|
|
275
|
+
const created = changedFiles.filter((f) => f.action === "created");
|
|
276
|
+
const edited = changedFiles.filter((f) => f.action === "edited");
|
|
277
|
+
const lines = [
|
|
278
|
+
`# Wiki ${actionLabel}`,
|
|
279
|
+
"",
|
|
280
|
+
`Run completed at ${timestamp}.`,
|
|
281
|
+
"",
|
|
282
|
+
];
|
|
283
|
+
if (created.length > 0) {
|
|
284
|
+
lines.push("## New pages", "");
|
|
285
|
+
for (const f of created) {
|
|
286
|
+
lines.push(`- \`${f.path}\``);
|
|
287
|
+
if (f.description && f.description.trim()) {
|
|
288
|
+
lines.push(...formatDescription(f.description));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
lines.push("");
|
|
292
|
+
}
|
|
293
|
+
if (edited.length > 0) {
|
|
294
|
+
lines.push("## Updated pages", "");
|
|
295
|
+
for (const f of edited) {
|
|
296
|
+
lines.push(`- \`${f.path}\``);
|
|
297
|
+
if (f.description && f.description.trim()) {
|
|
298
|
+
lines.push(...formatDescription(f.description));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
lines.push("");
|
|
302
|
+
}
|
|
303
|
+
lines.push("## Summary", "", `This ${command === "init" ? "initialization" : "update"} run ${created.length > 0 ? `created ${created.length} page${created.length > 1 ? "s" : ""}` : ""}${created.length > 0 && edited.length > 0 ? " and " : ""}${edited.length > 0 ? `edited ${edited.length} page${edited.length > 1 ? "s" : ""}` : ""}.`);
|
|
304
|
+
return lines.join("\n") + "\n";
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Formats a change description as indented markdown under a file listing.
|
|
308
|
+
* Collapses whitespace, truncates overly long prose, and wraps it as a
|
|
309
|
+
* blockquote so it renders cleanly under the `- \`path\`` bullet.
|
|
310
|
+
*/
|
|
311
|
+
function formatDescription(description) {
|
|
312
|
+
const trimmed = description.trim().replace(/\s+/g, " ");
|
|
313
|
+
const maxLen = 500;
|
|
314
|
+
const text = trimmed.length > maxLen ? trimmed.slice(0, maxLen) + "…" : trimmed;
|
|
315
|
+
// Indent under the bullet as a nested blockquote
|
|
316
|
+
return ["", ` > ${text}`, ""];
|
|
317
|
+
}
|
package/dist/cli.d.ts
ADDED