@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/dist/cli.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render as inkRender } from "ink";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { runAgent } from "./agent.js";
|
|
7
|
+
import { resolveConfig, createOllamaClient, } from "./config.js";
|
|
8
|
+
import { getHelpText } from "./prompt.js";
|
|
9
|
+
import { App } from "./tui/App.js";
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const args = { command: null, print: false, verbose: false, help: false };
|
|
13
|
+
for (let i = 2; i < argv.length; i++) {
|
|
14
|
+
const arg = argv[i];
|
|
15
|
+
switch (arg) {
|
|
16
|
+
case "--init":
|
|
17
|
+
args.command = "init";
|
|
18
|
+
break;
|
|
19
|
+
case "--update":
|
|
20
|
+
args.command = "update";
|
|
21
|
+
break;
|
|
22
|
+
case "--print":
|
|
23
|
+
args.print = true;
|
|
24
|
+
break;
|
|
25
|
+
case "--verbose":
|
|
26
|
+
case "-v":
|
|
27
|
+
args.verbose = true;
|
|
28
|
+
break;
|
|
29
|
+
case "--model":
|
|
30
|
+
args.model = argv[++i];
|
|
31
|
+
break;
|
|
32
|
+
case "--help":
|
|
33
|
+
case "-h":
|
|
34
|
+
args.help = true;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
async function getGitSummary(cwd) {
|
|
41
|
+
try {
|
|
42
|
+
const { stdout } = await execAsync("git log --oneline -30", {
|
|
43
|
+
cwd,
|
|
44
|
+
maxBuffer: 1024 * 1024,
|
|
45
|
+
});
|
|
46
|
+
return stdout.trim();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return "(git not available or not a git repository)";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function runHeadless(command, cwd, model, verbose) {
|
|
53
|
+
const config = await resolveConfig(cwd, model);
|
|
54
|
+
const client = createOllamaClient(config);
|
|
55
|
+
const gitSummary = await getGitSummary(cwd);
|
|
56
|
+
await runAgent(client, {
|
|
57
|
+
command,
|
|
58
|
+
projectRoot: cwd,
|
|
59
|
+
model: config.model,
|
|
60
|
+
gitSummary,
|
|
61
|
+
stream: false,
|
|
62
|
+
onEvent: (event) => {
|
|
63
|
+
switch (event.type) {
|
|
64
|
+
case "assistant":
|
|
65
|
+
if (event.content) {
|
|
66
|
+
process.stdout.write(`\n${event.content}\n`);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
case "tool":
|
|
70
|
+
if (event.result && verbose) {
|
|
71
|
+
process.stdout.write(`\n[tool: ${event.name}]\n${event.result}\n`);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case "error":
|
|
75
|
+
process.stderr.write(`\nError: ${event.message}\n`);
|
|
76
|
+
break;
|
|
77
|
+
case "done":
|
|
78
|
+
process.stdout.write(`\n${event.summary}\n`);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function main() {
|
|
85
|
+
const args = parseArgs(process.argv);
|
|
86
|
+
if (args.help || args.command === null) {
|
|
87
|
+
console.log(getHelpText());
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
const command = args.command;
|
|
91
|
+
const cwd = process.cwd();
|
|
92
|
+
const config = await resolveConfig(cwd, args.model);
|
|
93
|
+
if (config.mode === "cloud" && !config.apiKey) {
|
|
94
|
+
console.error("WIKI_OLLAMA_API_KEY is required for cloud mode. Set it via environment variable or run interactively to configure.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (args.print) {
|
|
98
|
+
await runHeadless(command, cwd, config.model, args.verbose);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const { waitUntilExit } = inkRender(React.createElement(App, {
|
|
102
|
+
command,
|
|
103
|
+
cwd,
|
|
104
|
+
config,
|
|
105
|
+
verbose: args.verbose,
|
|
106
|
+
}));
|
|
107
|
+
await waitUntilExit();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
main().catch((error) => {
|
|
111
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Ollama } from "ollama";
|
|
2
|
+
export type OllamaMode = "local" | "cloud";
|
|
3
|
+
export interface GlobalConfig {
|
|
4
|
+
mode: OllamaMode;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
defaultModel: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ProjectConfig {
|
|
10
|
+
modelOverride?: string;
|
|
11
|
+
lastUpdate?: {
|
|
12
|
+
commitSha: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedConfig {
|
|
17
|
+
mode: OllamaMode;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
model: string;
|
|
21
|
+
}
|
|
22
|
+
declare function getGlobalConfigDir(): string;
|
|
23
|
+
declare const DEFAULT_MODEL = "kimi-k2.7-code";
|
|
24
|
+
declare const MAX_TOOL_RESULT_LENGTH = 10000;
|
|
25
|
+
export { getGlobalConfigDir };
|
|
26
|
+
export declare function loadGlobalConfig(): Promise<GlobalConfig>;
|
|
27
|
+
export declare function saveGlobalConfig(config: GlobalConfig): Promise<void>;
|
|
28
|
+
export declare function loadProjectConfig(projectRoot: string): Promise<ProjectConfig>;
|
|
29
|
+
export declare function saveProjectConfig(projectRoot: string, config: ProjectConfig): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the effective configuration by merging (in priority order):
|
|
32
|
+
* 1. Environment variables (WIKI_OLLAMA_MODE, WIKI_OLLAMA_API_KEY,
|
|
33
|
+
* WIKI_OLLAMA_BASE_URL, WIKI_MODEL)
|
|
34
|
+
* 2. Global config file (~/.wiki/config.json)
|
|
35
|
+
* 3. Project config (.wiki/config.json modelOverride)
|
|
36
|
+
* 4. Built-in defaults
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveConfig(projectRoot: string, modelOverride?: string): Promise<ResolvedConfig>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates an Ollama client from resolved config.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createOllamaClient(config: ResolvedConfig): Ollama;
|
|
43
|
+
export declare function truncateResult(result: string): string;
|
|
44
|
+
export { DEFAULT_MODEL, MAX_TOOL_RESULT_LENGTH };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { Ollama } from "ollama";
|
|
5
|
+
function getGlobalConfigDir() {
|
|
6
|
+
return path.join(os.homedir(), ".wiki");
|
|
7
|
+
}
|
|
8
|
+
function getGlobalConfigPath() {
|
|
9
|
+
return path.join(getGlobalConfigDir(), "config.json");
|
|
10
|
+
}
|
|
11
|
+
const DEFAULT_LOCAL_HOST = "http://localhost:11434";
|
|
12
|
+
const DEFAULT_CLOUD_HOST = "https://ollama.com";
|
|
13
|
+
const DEFAULT_MODEL = "kimi-k2.7-code";
|
|
14
|
+
const MAX_TOOL_RESULT_LENGTH = 10_000;
|
|
15
|
+
function defaultGlobalConfig() {
|
|
16
|
+
return { mode: "local", defaultModel: DEFAULT_MODEL };
|
|
17
|
+
}
|
|
18
|
+
export { getGlobalConfigDir };
|
|
19
|
+
export async function loadGlobalConfig() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = await readFile(getGlobalConfigPath(), "utf8");
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return defaultGlobalConfig();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function saveGlobalConfig(config) {
|
|
29
|
+
const dir = getGlobalConfigDir();
|
|
30
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
31
|
+
const configPath = getGlobalConfigPath();
|
|
32
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
33
|
+
await chmod(configPath, 0o600);
|
|
34
|
+
}
|
|
35
|
+
export async function loadProjectConfig(projectRoot) {
|
|
36
|
+
const configPath = path.join(projectRoot, ".wiki", "config.json");
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(configPath, "utf8");
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function saveProjectConfig(projectRoot, config) {
|
|
46
|
+
const configDir = path.join(projectRoot, ".wiki");
|
|
47
|
+
await mkdir(configDir, { recursive: true });
|
|
48
|
+
const configPath = path.join(configDir, "config.json");
|
|
49
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the effective configuration by merging (in priority order):
|
|
53
|
+
* 1. Environment variables (WIKI_OLLAMA_MODE, WIKI_OLLAMA_API_KEY,
|
|
54
|
+
* WIKI_OLLAMA_BASE_URL, WIKI_MODEL)
|
|
55
|
+
* 2. Global config file (~/.wiki/config.json)
|
|
56
|
+
* 3. Project config (.wiki/config.json modelOverride)
|
|
57
|
+
* 4. Built-in defaults
|
|
58
|
+
*/
|
|
59
|
+
export async function resolveConfig(projectRoot, modelOverride) {
|
|
60
|
+
const globalConfig = await loadGlobalConfig();
|
|
61
|
+
const projectConfig = await loadProjectConfig(projectRoot);
|
|
62
|
+
const env = process.env;
|
|
63
|
+
const mode = env.WIKI_OLLAMA_MODE === "cloud" || env.WIKI_OLLAMA_MODE === "local"
|
|
64
|
+
? env.WIKI_OLLAMA_MODE
|
|
65
|
+
: globalConfig.mode;
|
|
66
|
+
const apiKey = env.WIKI_OLLAMA_API_KEY ?? globalConfig.apiKey;
|
|
67
|
+
const baseUrl = env.WIKI_OLLAMA_BASE_URL ??
|
|
68
|
+
globalConfig.baseUrl ??
|
|
69
|
+
(mode === "cloud" ? DEFAULT_CLOUD_HOST : DEFAULT_LOCAL_HOST);
|
|
70
|
+
const model = modelOverride ??
|
|
71
|
+
projectConfig.modelOverride ??
|
|
72
|
+
env.WIKI_MODEL ??
|
|
73
|
+
globalConfig.defaultModel ??
|
|
74
|
+
DEFAULT_MODEL;
|
|
75
|
+
return { mode, apiKey, baseUrl, model };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates an Ollama client from resolved config.
|
|
79
|
+
*/
|
|
80
|
+
export function createOllamaClient(config) {
|
|
81
|
+
if (config.mode === "cloud" && config.apiKey) {
|
|
82
|
+
return new Ollama({
|
|
83
|
+
host: config.baseUrl,
|
|
84
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return new Ollama({ host: config.baseUrl });
|
|
88
|
+
}
|
|
89
|
+
export function truncateResult(result) {
|
|
90
|
+
if (result.length <= MAX_TOOL_RESULT_LENGTH) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
return result.slice(0, MAX_TOOL_RESULT_LENGTH) + "\n... (truncated)";
|
|
94
|
+
}
|
|
95
|
+
export { DEFAULT_MODEL, MAX_TOOL_RESULT_LENGTH };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
const INDEX_FILE = "index.md";
|
|
5
|
+
const EXCLUDED_FILES = new Set([INDEX_FILE, "_plan.md"]);
|
|
6
|
+
/**
|
|
7
|
+
* Synchronizes index.md files for every directory under the wiki root.
|
|
8
|
+
* Call this after the agent run completes.
|
|
9
|
+
*/
|
|
10
|
+
export async function synchronizeWikiIndexes(wikiRoot) {
|
|
11
|
+
try {
|
|
12
|
+
await stat(wikiRoot);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await synchronizeDirectory(wikiRoot, wikiRoot);
|
|
18
|
+
}
|
|
19
|
+
async function synchronizeDirectory(dirPath, root) {
|
|
20
|
+
const entries = await collectEntries(dirPath);
|
|
21
|
+
const files = [];
|
|
22
|
+
const directories = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.name || entry.name.startsWith("."))
|
|
25
|
+
continue;
|
|
26
|
+
if (entry.isDir) {
|
|
27
|
+
directories.push({ href: `${encodeURIComponent(entry.name)}/`, label: entry.name });
|
|
28
|
+
await synchronizeDirectory(path.join(dirPath, entry.name), root);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (path.extname(entry.name).toLowerCase() !== ".md")
|
|
32
|
+
continue;
|
|
33
|
+
if (EXCLUDED_FILES.has(entry.name))
|
|
34
|
+
continue;
|
|
35
|
+
const filePath = path.join(dirPath, entry.name);
|
|
36
|
+
const metadata = await parseFrontmatter(filePath);
|
|
37
|
+
files.push({
|
|
38
|
+
description: metadata.description,
|
|
39
|
+
href: encodeURIComponent(entry.name),
|
|
40
|
+
label: metadata.title ?? path.basename(entry.name, ".md"),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const indexPath = path.join(dirPath, INDEX_FILE);
|
|
44
|
+
const title = dirPath === root ? "Wiki" : titleFromSlug(path.basename(dirPath));
|
|
45
|
+
const content = renderIndex(title, files, directories);
|
|
46
|
+
let existing = null;
|
|
47
|
+
try {
|
|
48
|
+
existing = await readFile(indexPath, "utf8");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// index.md doesn't exist yet
|
|
52
|
+
}
|
|
53
|
+
if (existing === content)
|
|
54
|
+
return;
|
|
55
|
+
await writeFile(indexPath, content, "utf8");
|
|
56
|
+
}
|
|
57
|
+
async function collectEntries(dirPath) {
|
|
58
|
+
const names = await readdir(dirPath, { withFileTypes: true });
|
|
59
|
+
return names.map((entry) => ({
|
|
60
|
+
name: entry.name,
|
|
61
|
+
isDir: entry.isDirectory(),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
function renderIndex(title, files, directories) {
|
|
65
|
+
const sections = [
|
|
66
|
+
renderLinks("Files", files, true),
|
|
67
|
+
renderLinks("Directories", directories, false),
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join("\n\n");
|
|
71
|
+
return `---\ntype: Documentation Index\ntitle: ${JSON.stringify(title)}\ndescription: ${JSON.stringify(`Files and subdirectories in ${title}.`)}\n---\n\n${sections}\n`;
|
|
72
|
+
}
|
|
73
|
+
function renderLinks(heading, links, includeDescription) {
|
|
74
|
+
if (links.length === 0)
|
|
75
|
+
return "";
|
|
76
|
+
links.sort((left, right) => left.href.localeCompare(right.href));
|
|
77
|
+
const items = links.map(({ description, href, label }) => {
|
|
78
|
+
const link = `- [${escapeLabel(label)}](${href})`;
|
|
79
|
+
return includeDescription && description
|
|
80
|
+
? `${link} - ${description}`
|
|
81
|
+
: link;
|
|
82
|
+
});
|
|
83
|
+
return `# ${heading}\n\n${items.join("\n")}`;
|
|
84
|
+
}
|
|
85
|
+
async function parseFrontmatter(filePath) {
|
|
86
|
+
let content;
|
|
87
|
+
try {
|
|
88
|
+
content = await readFile(filePath, "utf8");
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const block = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(content)?.[1];
|
|
94
|
+
if (!block)
|
|
95
|
+
return {};
|
|
96
|
+
let fields;
|
|
97
|
+
try {
|
|
98
|
+
fields = parse(`\n${block}`, {
|
|
99
|
+
maxAliasCount: 100,
|
|
100
|
+
schema: "core",
|
|
101
|
+
uniqueKeys: true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new Error(`${filePath} contains invalid YAML front matter: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
if (fields === null || typeof fields !== "object" || Array.isArray(fields)) {
|
|
108
|
+
throw new Error(`${filePath} YAML front matter must be a mapping.`);
|
|
109
|
+
}
|
|
110
|
+
const { description, title } = fields;
|
|
111
|
+
if (description !== undefined &&
|
|
112
|
+
(typeof description !== "string" || !description.trim())) {
|
|
113
|
+
throw new Error(`${filePath} YAML description must be a non-empty string.`);
|
|
114
|
+
}
|
|
115
|
+
if (title !== undefined && typeof title !== "string") {
|
|
116
|
+
throw new Error(`${filePath} YAML title must be a string.`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...(description ? { description } : {}),
|
|
120
|
+
...(title ? { title } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function titleFromSlug(slug) {
|
|
124
|
+
return slug
|
|
125
|
+
.split(/[-_\s]+/u)
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
128
|
+
.join(" ");
|
|
129
|
+
}
|
|
130
|
+
function escapeLabel(value) {
|
|
131
|
+
return value
|
|
132
|
+
.replaceAll("\\", "\\\\")
|
|
133
|
+
.replaceAll("[", "\\[")
|
|
134
|
+
.replaceAll("]", "\\]");
|
|
135
|
+
}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type WikiCommand = "init" | "update";
|
|
2
|
+
export declare function createSystemPrompt(projectRoot: string): Promise<string>;
|
|
3
|
+
export declare function createUserMessage(command: WikiCommand, projectRoot: string, gitSummary?: string): string;
|
|
4
|
+
export declare function getHelpText(): string;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Reads AGENTS.md or CLAUDE.md from the project root, if either exists.
|
|
5
|
+
* Returns the file content (first match wins: AGENTS.md, then CLAUDE.md).
|
|
6
|
+
*/
|
|
7
|
+
async function loadRepoInstructions(projectRoot) {
|
|
8
|
+
for (const filename of ["AGENTS.md", "CLAUDE.md"]) {
|
|
9
|
+
try {
|
|
10
|
+
const content = await readFile(path.join(projectRoot, filename), "utf8");
|
|
11
|
+
return content.trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// File doesn't exist — try the next one
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export async function createSystemPrompt(projectRoot) {
|
|
20
|
+
const repoInstructions = await loadRepoInstructions(projectRoot);
|
|
21
|
+
const instructionsSection = repoInstructions
|
|
22
|
+
? `\n\nRepository instructions (from AGENTS.md or CLAUDE.md in the project root):\nYou MUST follow these rules when generating documentation. Acknowledge and respect all conventions, code style rules, and constraints documented here:\n\n${repoInstructions}\n`
|
|
23
|
+
: "";
|
|
24
|
+
return `
|
|
25
|
+
You are Wiki Agent, an expert technical writer, software architect, and product analyst.
|
|
26
|
+
|
|
27
|
+
Your job is to inspect the source evidence in the repository and produce documentation in .wiki/ that is excellent for both humans and future agents.
|
|
28
|
+
|
|
29
|
+
# Capabilities and constraints
|
|
30
|
+
|
|
31
|
+
You have a FIXED, LIMITED set of tools. You CANNOT execute arbitrary commands on the host system. There is no shell tool. The tools available to you are:
|
|
32
|
+
- read_file, ls, glob, grep: read-only filesystem discovery scoped to the project root.
|
|
33
|
+
- ast_grep: search code by AST pattern (structural match). Use $NAME for a single node metavariable and $$$ARGS for zero-or-more. Requires an explicit language. Prefer this over grep for precise structural queries (function calls, exports, control flow).
|
|
34
|
+
- ast_search: search code using an inline ast-grep YAML rule. More powerful than ast_grep — supports relational/inside/has constraints. Use for complex structural queries a single pattern cannot express.
|
|
35
|
+
- write_file, edit_file: write or edit documentation files. These are the ONLY mutating tools and are constrained to paths under .wiki/.
|
|
36
|
+
- git: run a READ-ONLY git subcommand (log, diff, show, ls-files, blame, status, remote, describe, rev-parse, shortlog, name-rev, ls-tree, cat-file, reflog). This is the ONLY way to access repository history. Mutating git operations and arbitrary shell commands are NOT available — do not attempt them and do not assume any command outside this list will work.
|
|
37
|
+
|
|
38
|
+
You cannot run build tools, package managers, test runners, scripts, or any program other than git (read-only). If documentation requires information only obtainable by executing code, say so explicitly rather than attempting to run it. Ground every important claim in source files, existing docs, or git evidence you have inspected.
|
|
39
|
+
|
|
40
|
+
# Output location
|
|
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
|
+
- Never write markdown files outside .wiki/.
|
|
43
|
+
- Each wiki page must start with YAML frontmatter:
|
|
44
|
+
---
|
|
45
|
+
type: <type>
|
|
46
|
+
title: <title>
|
|
47
|
+
description: <description>
|
|
48
|
+
tags: [<tags>]
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Use only the tools listed above. Do not invent files, modules, APIs, business rules, or behavior. Ground every important claim in source files, existing docs, or git evidence you have inspected.
|
|
52
|
+
|
|
53
|
+
# Run discipline
|
|
54
|
+
- Never pass host absolute paths like /Users/... to filesystem tools; use paths relative to the project root.
|
|
55
|
+
- Do not exhaustively read every file. Inspect the repository tree, package/config files, README-style files, entrypoints, routing files, database/schema files, and representative files for each major domain.
|
|
56
|
+
- Do not call glob with **/* from the root. Use targeted discovery by directory and extension. Prefer ls on specific directories and glob with constrained paths; exclude .git, node_modules, dist, build, cache directories, and existing generated wiki output mentally when interpreting results.
|
|
57
|
+
- Prefer grep, ast_grep, and short targeted read_file calls over full-file reads when files are large.
|
|
58
|
+
- Use ast_grep when you need structural precision (finding all call sites of a symbol, all exports, a specific control-flow shape). Use ast_search only when a single pattern is insufficient.
|
|
59
|
+
- Use git for history and change evidence: 'git log --oneline -30', 'git diff --stat', 'git ls-files', 'git show <sha>:<path>'. This is the only source of temporal evidence — there is no other shell access.
|
|
60
|
+
- Create a strong first-pass wiki that is accurate and navigable, then stop. The wiki can be refined in later update runs.
|
|
61
|
+
- Keep the initial documentation set focused: quickstart plus the smallest set of section pages needed to explain the repo clearly.
|
|
62
|
+
- Before writing documentation, read AGENTS.md or CLAUDE.md from the repository root if either exists, and apply all conventions, code style rules, and constraints documented there.
|
|
63
|
+
|
|
64
|
+
# Loop prevention
|
|
65
|
+
- Work in phases: discover → plan → write → verify. Do not restart discovery once you have moved to planning or writing.
|
|
66
|
+
- Track what you have already inspected. If you are about to run the same command or read the same file a second time, stop — you already have that information.
|
|
67
|
+
- Make one targeted discovery pass per area. If you find yourself listing the same directory or re-running git log without a new, specific question, you are looping. Stop and proceed to the next phase.
|
|
68
|
+
- Process each tool result fully before issuing the next call. If a tool returned the information you need, use it — do not re-request it.
|
|
69
|
+
- Do not begin a response with "I'll start by exploring" or "Let me start by exploring" more than once. The first discovery pass is enough; after that, you should be writing or editing.
|
|
70
|
+
- If you are stuck or uncertain about a specific fact, make a targeted single read or grep, then proceed with the best available evidence. Do not loop on discovery as a way to resolve uncertainty.
|
|
71
|
+
|
|
72
|
+
# Documentation quality
|
|
73
|
+
- Give each concept one canonical home. Link to it from other pages when needed.
|
|
74
|
+
- Keep the docs concise enough to maintain. Avoid repeating the same concept across pages.
|
|
75
|
+
- Use code examples sparingly — only when they clarify a non-obvious API or pattern.
|
|
76
|
+
- If the source material already has substantial docs or prior wiki pages, create a wiki that functions as an opinionated map and synthesis layer over those docs.
|
|
77
|
+
- Before every write_file or edit_file call, write one or two sentences explaining what you are changing and why — the source evidence that drove the change, what section was added/revised, and the intended improvement. This narration is used verbatim as the per-file change description in the update report and pull request body, so be specific and substantive; do not write generic filler like "Updated the page" or "Improved docs".
|
|
78
|
+
- Updates may be a no-op. If there are no relevant source changes since the previous successful run, and the current wiki is already accurate, do not edit files. Say that the wiki is already current.
|
|
79
|
+
${instructionsSection}
|
|
80
|
+
`.trim();
|
|
81
|
+
}
|
|
82
|
+
export function createUserMessage(command, projectRoot, gitSummary) {
|
|
83
|
+
if (command === "init") {
|
|
84
|
+
return `
|
|
85
|
+
Initialize wiki documentation for this repository.
|
|
86
|
+
|
|
87
|
+
Inspect the relevant evidence thoroughly, identify the major technical, business, or knowledge domains, and write the initial documentation under .wiki/.
|
|
88
|
+
|
|
89
|
+
Start with .wiki/quickstart.md as the entrypoint. Then create section directories and pages that explain the subject in a way that is useful to both humans and future agents.
|
|
90
|
+
|
|
91
|
+
Make one focused discovery pass, then write the plan and proceed to documentation. Do not loop on repeated exploration steps.
|
|
92
|
+
|
|
93
|
+
Git context:
|
|
94
|
+
${gitSummary ?? "(not available)"}
|
|
95
|
+
`.trim();
|
|
96
|
+
}
|
|
97
|
+
return `
|
|
98
|
+
Update the existing OpenWiki documentation for this repository.
|
|
99
|
+
|
|
100
|
+
Inspect .wiki/, identify recent source changes or newly relevant evidence, and refresh only the documentation pages directly affected by those changes. Use the git evidence below when available. Keep edits surgical: do not rewrite accurate sections, do not update source maps or git evidence just to refresh them, and do not make formatting-only changes. If the wiki is already current, do not edit files.
|
|
101
|
+
|
|
102
|
+
Make one focused discovery pass to identify what changed, then proceed to surgical edits. Do not loop on repeated exploration steps.
|
|
103
|
+
|
|
104
|
+
Git change summary:
|
|
105
|
+
${gitSummary ?? "(not available)"}
|
|
106
|
+
`.trim();
|
|
107
|
+
}
|
|
108
|
+
export function getHelpText() {
|
|
109
|
+
return `
|
|
110
|
+
__ _____ _ _____ _ ____ _____ _ _ _____
|
|
111
|
+
\\ \\ / /_ _| |/ /_ _| / \\ / ___| ____| \\ | |_ _|
|
|
112
|
+
\\ \\ /\\ / / | || ' / | | / _ \\| | _| _| | \\| | | |
|
|
113
|
+
\\ V V / | || . \\ | | / ___ \\ |_| | |___| |\\ | | |
|
|
114
|
+
\\_/\\_/ |___|_|\\_\\___| /_/ \\_\\____|_____|_| \\_| |_|
|
|
115
|
+
|
|
116
|
+
Usage
|
|
117
|
+
wiki --init Initialize wiki documentation (interactive)
|
|
118
|
+
wiki --update Update existing wiki documentation (interactive)
|
|
119
|
+
wiki --init --print Headless init (non-interactive)
|
|
120
|
+
wiki --update --print Headless update (non-interactive)
|
|
121
|
+
wiki --init --print --model <id> Specify model
|
|
122
|
+
wiki --update --print --verbose Headless update with full tool logs
|
|
123
|
+
wiki --help Show this help
|
|
124
|
+
|
|
125
|
+
Options
|
|
126
|
+
--init Initialize documentation for the current repository
|
|
127
|
+
--update Update existing documentation
|
|
128
|
+
--print Run headless (non-interactive, output to stdout)
|
|
129
|
+
--verbose, -v Show full tool call results (default: assistant prose only)
|
|
130
|
+
--model <id> Override the model ID
|
|
131
|
+
--help, -h Show help
|
|
132
|
+
|
|
133
|
+
Environment variables
|
|
134
|
+
WIKI_OLLAMA_MODE "local" or "cloud"
|
|
135
|
+
WIKI_OLLAMA_API_KEY API key (required for cloud mode)
|
|
136
|
+
WIKI_OLLAMA_BASE_URL Override the Ollama server URL
|
|
137
|
+
WIKI_MODEL Override the model ID
|
|
138
|
+
WIKI_RECURSION_LIMIT Max agent iterations (default: 200)
|
|
139
|
+
|
|
140
|
+
Configuration
|
|
141
|
+
Global: ~/.wiki/config.json
|
|
142
|
+
Project: .wiki/config.json
|
|
143
|
+
Output: .wiki/ (in the project root)
|
|
144
|
+
`.trim();
|
|
145
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const MAX_READ_LENGTH = 50000;
|
|
2
|
+
declare const MAX_TOOL_RESULT_LENGTH = 10000;
|
|
3
|
+
interface ToolDefinition {
|
|
4
|
+
type: "function";
|
|
5
|
+
function: {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
parameters: {
|
|
9
|
+
type: string;
|
|
10
|
+
properties: Record<string, unknown>;
|
|
11
|
+
required: string[];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface Tool {
|
|
16
|
+
definition: ToolDefinition;
|
|
17
|
+
handler: (args: Record<string, unknown>, projectRoot: string) => Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createTools(projectRoot: string): Tool[];
|
|
20
|
+
/**
|
|
21
|
+
* Execute a tool by name with the given arguments.
|
|
22
|
+
*/
|
|
23
|
+
export declare function executeTool(toolName: string, args: Record<string, unknown>, projectRoot: string): Promise<string>;
|
|
24
|
+
export { MAX_READ_LENGTH, MAX_TOOL_RESULT_LENGTH };
|