oh-my-llmwikimode 1.0.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/LICENSE +21 -0
- package/README.md +494 -0
- package/bin/llmwiki.js +1493 -0
- package/docs/INSTALLATION.md +228 -0
- package/docs/SCOPE_LOCK.md +79 -0
- package/docs/STAGE1_GUIDE.md +265 -0
- package/docs/STAGE2_AGENT_TEAM_GUIDE.md +141 -0
- package/docs/STAGE3_CONVERSATIONAL_GROWTH_GUIDE.md +50 -0
- package/docs/TEST_WORKSHEET.md +120 -0
- package/docs/github-private-bootstrap.md +53 -0
- package/docs/release.md +79 -0
- package/docs/stage4-slice1-manual-test.md +259 -0
- package/docs/stage4-slice1-user-guide.md +269 -0
- package/docs/user-guide-ko.md +452 -0
- package/package.json +76 -0
- package/scripts/install-llmwiki.ps1 +229 -0
- package/src/config.js +74 -0
- package/src/curator/browser-data.js +134 -0
- package/src/curator/queue.js +324 -0
- package/src/curator/schema.js +237 -0
- package/src/curator/scoring.js +83 -0
- package/src/hooks.js +199 -0
- package/src/librarian/schema.js +218 -0
- package/src/librarian/weekly-digest.js +478 -0
- package/src/security.js +127 -0
- package/src/server.js +860 -0
- package/src/stage4/graph-reasoning/analyzer.js +255 -0
- package/src/stage4/graph-reasoning/browser-data.js +130 -0
- package/src/stage4/graph-reasoning/index.js +35 -0
- package/src/stage4/graph-reasoning/loader.js +122 -0
- package/src/stage4/graph-reasoning/queue.js +154 -0
- package/src/stage4/graph-reasoning/schema.js +190 -0
- package/src/team/browser-data.js +142 -0
- package/src/team/capabilities.js +79 -0
- package/src/team/dispatch.js +108 -0
- package/src/team/queue.js +290 -0
- package/src/team/schema.js +225 -0
- package/src/team/shared-memory.js +183 -0
- package/src/todo/browser-data.js +71 -0
- package/src/todo/queue.js +159 -0
- package/src/todo/schema.js +90 -0
- package/src/utils/embedding-model.js +111 -0
- package/src/wiki/alias-suggestions.js +180 -0
- package/src/wiki/browser-data.js +284 -0
- package/src/wiki/doctor.js +218 -0
- package/src/wiki/entry-normalizer.js +139 -0
- package/src/wiki/ingest.js +443 -0
- package/src/wiki/lesson-proposal-analyzer.js +463 -0
- package/src/wiki/lesson-proposal-manager.js +331 -0
- package/src/wiki/lesson-template.js +182 -0
- package/src/wiki/lint.js +294 -0
- package/src/wiki/notebooklm-adapter.js +264 -0
- package/src/wiki/query.js +304 -0
- package/src/wiki/raw-manager.js +400 -0
- package/src/wiki/search-feedback.js +211 -0
- package/src/wiki/semantic-index.js +333 -0
- package/src/wiki/semantic-search.js +170 -0
- package/src/wiki/source-ledger.js +370 -0
- package/src/wiki/store.js +1329 -0
- package/src/wiki/usage-events.js +144 -0
package/bin/llmwiki.js
ADDED
|
@@ -0,0 +1,1493 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { expandHome } from "../src/config.js";
|
|
7
|
+
import { buildCuratorBrowserData } from "../src/curator/browser-data.js";
|
|
8
|
+
import { proposeConsolidation, readCuratorQueue, suggestLessonCandidate } from "../src/curator/queue.js";
|
|
9
|
+
import { generateWeeklyDigest, getDigest, listDigests } from "../src/librarian/weekly-digest.js";
|
|
10
|
+
import { rebuildBrowserData } from "../src/wiki/browser-data.js";
|
|
11
|
+
import { runGraphReasoningAnalysis, buildGraphReasoningBrowserData } from "../src/stage4/graph-reasoning/browser-data.js";
|
|
12
|
+
import { readGraphReasoningQueue } from "../src/stage4/graph-reasoning/queue.js";
|
|
13
|
+
import { addTodo, listTodos, markTodoDone, removeTodo } from "../src/todo/queue.js";
|
|
14
|
+
import { createDispatchPacketArtifact } from "../src/team/dispatch.js";
|
|
15
|
+
import {
|
|
16
|
+
appendTaskEvidence,
|
|
17
|
+
createAgentGroup,
|
|
18
|
+
createAgentProfile,
|
|
19
|
+
createTaskCard,
|
|
20
|
+
readAgentTeamQueue,
|
|
21
|
+
updateTaskStatus,
|
|
22
|
+
} from "../src/team/queue.js";
|
|
23
|
+
import { ingestKnowledge } from "../src/wiki/ingest.js";
|
|
24
|
+
import { lintWiki } from "../src/wiki/lint.js";
|
|
25
|
+
import { importSourceFile, readSourceRecords } from "../src/wiki/source-ledger.js";
|
|
26
|
+
import { doctorWiki } from "../src/wiki/doctor.js";
|
|
27
|
+
import { buildIndex, ensureWikiStructure, getWikiPaths, promoteEntry, rejectEntry } from "../src/wiki/store.js";
|
|
28
|
+
import { queryWiki } from "../src/wiki/query.js";
|
|
29
|
+
import {
|
|
30
|
+
appendSearchFailure,
|
|
31
|
+
readSearchFailures,
|
|
32
|
+
} from "../src/wiki/search-feedback.js";
|
|
33
|
+
import {
|
|
34
|
+
generateAliasSuggestion,
|
|
35
|
+
listAliasSuggestions,
|
|
36
|
+
} from "../src/wiki/alias-suggestions.js";
|
|
37
|
+
import {
|
|
38
|
+
listRawContents,
|
|
39
|
+
getRawSummary,
|
|
40
|
+
validateRawStructure,
|
|
41
|
+
} from "../src/wiki/raw-manager.js";
|
|
42
|
+
import {
|
|
43
|
+
generateLessonProposals,
|
|
44
|
+
} from "../src/wiki/lesson-proposal-analyzer.js";
|
|
45
|
+
import {
|
|
46
|
+
listProposals,
|
|
47
|
+
getProposal,
|
|
48
|
+
applyProposal,
|
|
49
|
+
} from "../src/wiki/lesson-proposal-manager.js";
|
|
50
|
+
import {
|
|
51
|
+
buildSemanticIndex,
|
|
52
|
+
getSemanticIndexSize,
|
|
53
|
+
loadSemanticIndex,
|
|
54
|
+
updateSemanticIndex,
|
|
55
|
+
} from "../src/wiki/semantic-index.js";
|
|
56
|
+
import { semanticSearch } from "../src/wiki/semantic-search.js";
|
|
57
|
+
|
|
58
|
+
const DEFAULT_WIKI_ROOT = "~/Documents/llm-wiki";
|
|
59
|
+
|
|
60
|
+
const COMMANDS = {
|
|
61
|
+
init: {
|
|
62
|
+
usage: "llmwiki init [--wiki-root ~/Documents/llm-wiki] [options]",
|
|
63
|
+
description: "Create the local wiki directory structure and an OpenCode config snippet.",
|
|
64
|
+
},
|
|
65
|
+
lint: {
|
|
66
|
+
usage: "llmwiki lint [options]",
|
|
67
|
+
description: "Validate wiki entries.",
|
|
68
|
+
},
|
|
69
|
+
doctor: {
|
|
70
|
+
usage: "llmwiki doctor [options]",
|
|
71
|
+
description: "Run read-only core health checks and print a dry-run migration plan.",
|
|
72
|
+
},
|
|
73
|
+
query: {
|
|
74
|
+
usage: "llmwiki query [options] <query>",
|
|
75
|
+
description: "Search wiki entries using the current derived index.",
|
|
76
|
+
},
|
|
77
|
+
ingest: {
|
|
78
|
+
usage: "llmwiki ingest [options]",
|
|
79
|
+
description: "Safely ingest local knowledge into inbox/ or project/ candidate entries.",
|
|
80
|
+
},
|
|
81
|
+
promote: {
|
|
82
|
+
usage: "llmwiki promote [options] <entry-path>",
|
|
83
|
+
description: "Promote an inbox/ or problems/ entry to a lesson.",
|
|
84
|
+
},
|
|
85
|
+
reject: {
|
|
86
|
+
usage: "llmwiki reject [options] <entry-path>",
|
|
87
|
+
description: "Mark an entry as rejected.",
|
|
88
|
+
},
|
|
89
|
+
rebuild: {
|
|
90
|
+
usage: "llmwiki rebuild [options]",
|
|
91
|
+
description: "Rebuild .system/index.json and .system/graph.json.",
|
|
92
|
+
},
|
|
93
|
+
audit: {
|
|
94
|
+
usage: "llmwiki audit [--limit 10] [options]",
|
|
95
|
+
description: "Print recent auto-memory audit entries.",
|
|
96
|
+
},
|
|
97
|
+
todo: {
|
|
98
|
+
usage: "llmwiki todo <add|list|done|remove> [options]",
|
|
99
|
+
description: "Manage the primary simple local TODO list.",
|
|
100
|
+
},
|
|
101
|
+
team: {
|
|
102
|
+
usage: "llmwiki team <list|create-task|create-profile|create-group|approve|status|generate-packet|evidence> [options]",
|
|
103
|
+
description: "Manage local approval-first Agent Team queue artifacts.",
|
|
104
|
+
},
|
|
105
|
+
curator: {
|
|
106
|
+
usage: "llmwiki curator <list|suggest-lesson|propose-consolidation> [options]",
|
|
107
|
+
description: "Manage local artifact-only Curator review packets.",
|
|
108
|
+
},
|
|
109
|
+
digest: {
|
|
110
|
+
usage: "llmwiki digest <generate|list|show> [options]",
|
|
111
|
+
description: "Generate, list, or show review-required weekly digest reports.",
|
|
112
|
+
},
|
|
113
|
+
"source-ledger": {
|
|
114
|
+
usage: "llmwiki source-ledger <import|list|search> [options]",
|
|
115
|
+
description: "Import source files into the source ledger, list recorded sources, or search raw evidence.",
|
|
116
|
+
},
|
|
117
|
+
stage4: {
|
|
118
|
+
usage: "llmwiki stage4 <graph-reasoning list|graph-reasoning analyze> [options]",
|
|
119
|
+
description: "Run Stage 4 Personal Local Graph Reasoning analysis.",
|
|
120
|
+
},
|
|
121
|
+
"search-feedback": {
|
|
122
|
+
usage: "llmwiki search-feedback <list|record|suggest-aliases> [options]",
|
|
123
|
+
description: "List search failures, record a zero-result query, or generate alias suggestions.",
|
|
124
|
+
},
|
|
125
|
+
"semantic-search": {
|
|
126
|
+
usage: "llmwiki semantic-search <query> [options]",
|
|
127
|
+
description: "Run local semantic search over the embedding index.",
|
|
128
|
+
},
|
|
129
|
+
"semantic-index": {
|
|
130
|
+
usage: "llmwiki semantic-index build | semantic-index status | semantic-index update [options]",
|
|
131
|
+
description: "Build, inspect, or incrementally update the local semantic embedding index.",
|
|
132
|
+
},
|
|
133
|
+
raw: {
|
|
134
|
+
usage: "llmwiki raw <list|status|validate> [options]",
|
|
135
|
+
description: "List raw contents, show raw statistics, or validate raw directory structure.",
|
|
136
|
+
},
|
|
137
|
+
"lesson-proposal": {
|
|
138
|
+
usage: "llmwiki lesson-proposal <analyze|list|show|apply> [options]",
|
|
139
|
+
description: "Analyze usage events, list proposals, show a proposal, or apply a proposal to create a lesson.",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function print(message, isError = false) {
|
|
144
|
+
const stream = isError ? process.stderr : process.stdout;
|
|
145
|
+
stream.write(`${message}\n`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function commandList() {
|
|
149
|
+
return Object.entries(COMMANDS)
|
|
150
|
+
.map(([name, command]) => ` ${name.padEnd(8)} ${command.description}`)
|
|
151
|
+
.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function globalOptionsHelp() {
|
|
155
|
+
return [
|
|
156
|
+
"Options:",
|
|
157
|
+
` --wiki-root <path> Wiki root directory (default: ${DEFAULT_WIKI_ROOT})`,
|
|
158
|
+
" --project <name> Target project for ingest/store (creates projects/<name>/inbox/)",
|
|
159
|
+
" --tags <tags> Comma-separated tags for ingest/todo add",
|
|
160
|
+
" --browser-data Rebuild .system/browser-data.json (rebuild only)",
|
|
161
|
+
" --limit <n> Number of audit entries to print (audit only)",
|
|
162
|
+
" --json Output JSON",
|
|
163
|
+
" --format context Output untrusted context for query results",
|
|
164
|
+
" --related <paths> Comma-separated related entries (curator suggest-lesson)",
|
|
165
|
+
" --entries <paths> Comma-separated entries (curator propose-consolidation)",
|
|
166
|
+
" --rationale <text> Reviewer rationale (curator propose-consolidation)",
|
|
167
|
+
" -h, --help Show help",
|
|
168
|
+
].join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function mainHelp() {
|
|
172
|
+
return [
|
|
173
|
+
"Usage: llmwiki <command> [options]",
|
|
174
|
+
"",
|
|
175
|
+
"Commands:",
|
|
176
|
+
commandList(),
|
|
177
|
+
"",
|
|
178
|
+
globalOptionsHelp(),
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function commandHelp(commandName) {
|
|
183
|
+
const command = COMMANDS[commandName];
|
|
184
|
+
return [
|
|
185
|
+
`Usage: ${command.usage}`,
|
|
186
|
+
"",
|
|
187
|
+
command.description,
|
|
188
|
+
"",
|
|
189
|
+
globalOptionsHelp(),
|
|
190
|
+
].join("\n");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function helpPayload(commandName) {
|
|
194
|
+
if (!commandName) {
|
|
195
|
+
return {
|
|
196
|
+
usage: "llmwiki <command> [options]",
|
|
197
|
+
commands: Object.fromEntries(
|
|
198
|
+
Object.entries(COMMANDS).map(([name, command]) => [name, command.description])
|
|
199
|
+
),
|
|
200
|
+
defaultWikiRoot: DEFAULT_WIKI_ROOT,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const command = COMMANDS[commandName];
|
|
205
|
+
return {
|
|
206
|
+
usage: command.usage,
|
|
207
|
+
command: commandName,
|
|
208
|
+
description: command.description,
|
|
209
|
+
defaultWikiRoot: DEFAULT_WIKI_ROOT,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const TEAM_VALUE_OPTIONS = new Set([
|
|
214
|
+
"--agent-profile",
|
|
215
|
+
"--agent-profile-id",
|
|
216
|
+
"--allowed",
|
|
217
|
+
"--approved-by",
|
|
218
|
+
"--approved-capabilities",
|
|
219
|
+
"--artifact",
|
|
220
|
+
"--blocked-reason",
|
|
221
|
+
"--capabilities",
|
|
222
|
+
"--criteria",
|
|
223
|
+
"--denied",
|
|
224
|
+
"--description",
|
|
225
|
+
"--entries",
|
|
226
|
+
"--goal",
|
|
227
|
+
"--id",
|
|
228
|
+
"--kind",
|
|
229
|
+
"--policy",
|
|
230
|
+
"--profiles",
|
|
231
|
+
"--rationale",
|
|
232
|
+
"--related",
|
|
233
|
+
"--requires-approval",
|
|
234
|
+
"--role",
|
|
235
|
+
"--role-id",
|
|
236
|
+
"--scope",
|
|
237
|
+
"--summary",
|
|
238
|
+
"--template",
|
|
239
|
+
"--title",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
function parseList(value) {
|
|
243
|
+
return String(value ?? "")
|
|
244
|
+
.split(",")
|
|
245
|
+
.map((item) => item.trim())
|
|
246
|
+
.filter(Boolean);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function applyTeamValueOption(parsed, optionName, value) {
|
|
250
|
+
if (!TEAM_VALUE_OPTIONS.has(optionName)) return false;
|
|
251
|
+
|
|
252
|
+
if (optionName === "--agent-profile" || optionName === "--agent-profile-id") {
|
|
253
|
+
parsed.agentProfileId = value;
|
|
254
|
+
} else if (optionName === "--allowed") {
|
|
255
|
+
parsed.allowedCapabilities.push(...parseList(value));
|
|
256
|
+
} else if (optionName === "--approved-by") {
|
|
257
|
+
parsed.approvedBy = value;
|
|
258
|
+
} else if (optionName === "--approved-capabilities") {
|
|
259
|
+
parsed.approvedCapabilities.push(...parseList(value));
|
|
260
|
+
} else if (optionName === "--artifact") {
|
|
261
|
+
parsed.artifacts.push(...parseList(value));
|
|
262
|
+
} else if (optionName === "--blocked-reason") {
|
|
263
|
+
parsed.blockedReason = value;
|
|
264
|
+
} else if (optionName === "--capabilities") {
|
|
265
|
+
parsed.capabilities.push(...parseList(value));
|
|
266
|
+
} else if (optionName === "--criteria") {
|
|
267
|
+
parsed.criteria.push(...parseList(value));
|
|
268
|
+
} else if (optionName === "--denied") {
|
|
269
|
+
parsed.deniedCapabilities.push(...parseList(value));
|
|
270
|
+
} else if (optionName === "--description") {
|
|
271
|
+
parsed.description = value;
|
|
272
|
+
} else if (optionName === "--entries") {
|
|
273
|
+
parsed.entries.push(...parseList(value));
|
|
274
|
+
} else if (optionName === "--goal") {
|
|
275
|
+
parsed.goal = value;
|
|
276
|
+
} else if (optionName === "--id") {
|
|
277
|
+
parsed.id = value;
|
|
278
|
+
} else if (optionName === "--kind") {
|
|
279
|
+
parsed.kind = value;
|
|
280
|
+
} else if (optionName === "--policy") {
|
|
281
|
+
parsed.policy = value;
|
|
282
|
+
} else if (optionName === "--profiles") {
|
|
283
|
+
parsed.profiles.push(...parseList(value));
|
|
284
|
+
} else if (optionName === "--rationale") {
|
|
285
|
+
parsed.rationale = value;
|
|
286
|
+
} else if (optionName === "--related") {
|
|
287
|
+
parsed.related.push(...parseList(value));
|
|
288
|
+
} else if (optionName === "--requires-approval") {
|
|
289
|
+
parsed.requiresApprovalCapabilities.push(...parseList(value));
|
|
290
|
+
} else if (optionName === "--role" || optionName === "--role-id") {
|
|
291
|
+
parsed.roleId = value;
|
|
292
|
+
} else if (optionName === "--scope") {
|
|
293
|
+
parsed.scope = value;
|
|
294
|
+
} else if (optionName === "--summary") {
|
|
295
|
+
parsed.summary = value;
|
|
296
|
+
} else if (optionName === "--template") {
|
|
297
|
+
parsed.template = value;
|
|
298
|
+
} else if (optionName === "--title") {
|
|
299
|
+
parsed.title = value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseArgs(argv) {
|
|
306
|
+
const parsed = {
|
|
307
|
+
args: [],
|
|
308
|
+
command: null,
|
|
309
|
+
allowedCapabilities: [],
|
|
310
|
+
browserData: false,
|
|
311
|
+
deniedCapabilities: [],
|
|
312
|
+
description: null,
|
|
313
|
+
format: "json",
|
|
314
|
+
help: false,
|
|
315
|
+
inputPath: null,
|
|
316
|
+
json: false,
|
|
317
|
+
kind: "custom",
|
|
318
|
+
limit: 10,
|
|
319
|
+
limitProvided: false,
|
|
320
|
+
agentProfileId: "main-opencode",
|
|
321
|
+
approvedBy: null,
|
|
322
|
+
approvedCapabilities: [],
|
|
323
|
+
artifacts: [],
|
|
324
|
+
blockedReason: null,
|
|
325
|
+
capabilities: [],
|
|
326
|
+
criteria: [],
|
|
327
|
+
goal: null,
|
|
328
|
+
id: null,
|
|
329
|
+
policy: "approval-first",
|
|
330
|
+
profiles: [],
|
|
331
|
+
rationale: null,
|
|
332
|
+
related: [],
|
|
333
|
+
requiresApprovalCapabilities: [],
|
|
334
|
+
roleId: "builder",
|
|
335
|
+
scope: "custom",
|
|
336
|
+
summary: null,
|
|
337
|
+
source: "manual",
|
|
338
|
+
stdin: false,
|
|
339
|
+
tags: null,
|
|
340
|
+
template: "Return evidence only.",
|
|
341
|
+
title: null,
|
|
342
|
+
wikiRoot: DEFAULT_WIKI_ROOT,
|
|
343
|
+
wikiRootInput: DEFAULT_WIKI_ROOT,
|
|
344
|
+
entries: [],
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
348
|
+
const token = argv[index];
|
|
349
|
+
|
|
350
|
+
if (token === "--") {
|
|
351
|
+
parsed.args.push(...argv.slice(index + 1));
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (token.startsWith("--") && token.includes("=")) {
|
|
356
|
+
const equalsIndex = token.indexOf("=");
|
|
357
|
+
const optionName = token.slice(0, equalsIndex);
|
|
358
|
+
const optionValue = token.slice(equalsIndex + 1);
|
|
359
|
+
if (TEAM_VALUE_OPTIONS.has(optionName)) {
|
|
360
|
+
if (!optionValue) {
|
|
361
|
+
return { ...parsed, error: `${optionName} requires a value` };
|
|
362
|
+
}
|
|
363
|
+
applyTeamValueOption(parsed, optionName, optionValue);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (TEAM_VALUE_OPTIONS.has(token)) {
|
|
369
|
+
const optionValue = argv[index + 1];
|
|
370
|
+
if (!optionValue) {
|
|
371
|
+
return { ...parsed, error: `${token} requires a value` };
|
|
372
|
+
}
|
|
373
|
+
applyTeamValueOption(parsed, token, optionValue);
|
|
374
|
+
index += 1;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (token === "--help" || token === "-h") {
|
|
379
|
+
parsed.help = true;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (token === "--json") {
|
|
384
|
+
parsed.json = true;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (token === "--browser-data") {
|
|
389
|
+
parsed.browserData = true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (token === "--limit") {
|
|
394
|
+
const limit = argv[index + 1];
|
|
395
|
+
if (!limit) {
|
|
396
|
+
return { ...parsed, error: "--limit requires a number" };
|
|
397
|
+
}
|
|
398
|
+
const parsedLimit = Number.parseInt(limit, 10);
|
|
399
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
|
400
|
+
return { ...parsed, error: "--limit must be a positive integer" };
|
|
401
|
+
}
|
|
402
|
+
parsed.limit = parsedLimit;
|
|
403
|
+
parsed.limitProvided = true;
|
|
404
|
+
index += 1;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (token.startsWith("--limit=")) {
|
|
409
|
+
const limit = token.slice("--limit=".length);
|
|
410
|
+
const parsedLimit = Number.parseInt(limit, 10);
|
|
411
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
|
412
|
+
return { ...parsed, error: "--limit must be a positive integer" };
|
|
413
|
+
}
|
|
414
|
+
parsed.limit = parsedLimit;
|
|
415
|
+
parsed.limitProvided = true;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (token === "--stdin") {
|
|
420
|
+
parsed.stdin = true;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (token === "--input-path") {
|
|
425
|
+
const inputPath = argv[index + 1];
|
|
426
|
+
if (!inputPath) {
|
|
427
|
+
return { ...parsed, error: "--input-path requires a path" };
|
|
428
|
+
}
|
|
429
|
+
parsed.inputPath = inputPath;
|
|
430
|
+
index += 1;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (token.startsWith("--input-path=")) {
|
|
435
|
+
const inputPath = token.slice("--input-path=".length);
|
|
436
|
+
if (!inputPath) {
|
|
437
|
+
return { ...parsed, error: "--input-path requires a path" };
|
|
438
|
+
}
|
|
439
|
+
parsed.inputPath = inputPath;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (token === "--source") {
|
|
444
|
+
const source = argv[index + 1];
|
|
445
|
+
if (!source) {
|
|
446
|
+
return { ...parsed, error: "--source requires a value" };
|
|
447
|
+
}
|
|
448
|
+
parsed.source = source;
|
|
449
|
+
index += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (token.startsWith("--source=")) {
|
|
454
|
+
const source = token.slice("--source=".length);
|
|
455
|
+
if (!source) {
|
|
456
|
+
return { ...parsed, error: "--source requires a value" };
|
|
457
|
+
}
|
|
458
|
+
parsed.source = source;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (token === "--title") {
|
|
463
|
+
const title = argv[index + 1];
|
|
464
|
+
if (!title) {
|
|
465
|
+
return { ...parsed, error: "--title requires a value" };
|
|
466
|
+
}
|
|
467
|
+
parsed.title = title;
|
|
468
|
+
index += 1;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (token.startsWith("--title=")) {
|
|
473
|
+
const title = token.slice("--title=".length);
|
|
474
|
+
if (!title) {
|
|
475
|
+
return { ...parsed, error: "--title requires a value" };
|
|
476
|
+
}
|
|
477
|
+
parsed.title = title;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (token === "--tags") {
|
|
482
|
+
const tags = argv[index + 1];
|
|
483
|
+
if (!tags) {
|
|
484
|
+
return { ...parsed, error: "--tags requires a value" };
|
|
485
|
+
}
|
|
486
|
+
parsed.tags = tags;
|
|
487
|
+
index += 1;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (token.startsWith("--tags=")) {
|
|
492
|
+
const tags = token.slice("--tags=".length);
|
|
493
|
+
if (!tags) {
|
|
494
|
+
return { ...parsed, error: "--tags requires a value" };
|
|
495
|
+
}
|
|
496
|
+
parsed.tags = tags;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (token === "--query") {
|
|
501
|
+
const query = argv[index + 1];
|
|
502
|
+
if (!query) {
|
|
503
|
+
return { ...parsed, error: "--query requires a value" };
|
|
504
|
+
}
|
|
505
|
+
parsed.query = query;
|
|
506
|
+
index += 1;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (token.startsWith("--query=")) {
|
|
511
|
+
const query = token.slice("--query=".length);
|
|
512
|
+
if (!query) {
|
|
513
|
+
return { ...parsed, error: "--query requires a value" };
|
|
514
|
+
}
|
|
515
|
+
parsed.query = query;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (token === "--result-count") {
|
|
520
|
+
const resultCount = argv[index + 1];
|
|
521
|
+
if (!resultCount) {
|
|
522
|
+
return { ...parsed, error: "--result-count requires a number" };
|
|
523
|
+
}
|
|
524
|
+
const parsedResultCount = Number.parseInt(resultCount, 10);
|
|
525
|
+
if (!Number.isInteger(parsedResultCount) || parsedResultCount < 0) {
|
|
526
|
+
return { ...parsed, error: "--result-count must be a non-negative integer" };
|
|
527
|
+
}
|
|
528
|
+
parsed.resultCount = parsedResultCount;
|
|
529
|
+
index += 1;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (token.startsWith("--result-count=")) {
|
|
534
|
+
const resultCount = token.slice("--result-count=".length);
|
|
535
|
+
const parsedResultCount = Number.parseInt(resultCount, 10);
|
|
536
|
+
if (!Number.isInteger(parsedResultCount) || parsedResultCount < 0) {
|
|
537
|
+
return { ...parsed, error: "--result-count must be a non-negative integer" };
|
|
538
|
+
}
|
|
539
|
+
parsed.resultCount = parsedResultCount;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (token === "--stdin") {
|
|
544
|
+
parsed.stdin = true;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (token === "--browser-data") {
|
|
549
|
+
parsed.browserData = true;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (token === "--format") {
|
|
554
|
+
const format = argv[index + 1];
|
|
555
|
+
if (!format) {
|
|
556
|
+
return { ...parsed, error: "--format requires a value" };
|
|
557
|
+
}
|
|
558
|
+
if (format !== "context") {
|
|
559
|
+
return { ...parsed, error: `Unsupported format: ${format}` };
|
|
560
|
+
}
|
|
561
|
+
parsed.format = format;
|
|
562
|
+
index += 1;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (token.startsWith("--format=")) {
|
|
567
|
+
const format = token.slice("--format=".length);
|
|
568
|
+
if (!format) {
|
|
569
|
+
return { ...parsed, error: "--format requires a value" };
|
|
570
|
+
}
|
|
571
|
+
if (format !== "context") {
|
|
572
|
+
return { ...parsed, error: `Unsupported format: ${format}` };
|
|
573
|
+
}
|
|
574
|
+
parsed.format = format;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (token === "--project") {
|
|
579
|
+
const project = argv[index + 1];
|
|
580
|
+
if (!project) {
|
|
581
|
+
return { ...parsed, error: "--project requires a name" };
|
|
582
|
+
}
|
|
583
|
+
parsed.project = project;
|
|
584
|
+
index += 1;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (token.startsWith("--project=")) {
|
|
589
|
+
const project = token.slice("--project=".length);
|
|
590
|
+
if (!project) {
|
|
591
|
+
return { ...parsed, error: "--project requires a name" };
|
|
592
|
+
}
|
|
593
|
+
parsed.project = project;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (token === "--wiki-root") {
|
|
598
|
+
const wikiRoot = argv[index + 1];
|
|
599
|
+
if (!wikiRoot) {
|
|
600
|
+
return { ...parsed, error: "--wiki-root requires a path" };
|
|
601
|
+
}
|
|
602
|
+
parsed.wikiRoot = wikiRoot;
|
|
603
|
+
parsed.wikiRootInput = wikiRoot;
|
|
604
|
+
index += 1;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (token.startsWith("--wiki-root=")) {
|
|
609
|
+
const wikiRoot = token.slice("--wiki-root=".length);
|
|
610
|
+
if (!wikiRoot) {
|
|
611
|
+
return { ...parsed, error: "--wiki-root requires a path" };
|
|
612
|
+
}
|
|
613
|
+
parsed.wikiRoot = wikiRoot;
|
|
614
|
+
parsed.wikiRootInput = wikiRoot;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (token.startsWith("-")) {
|
|
619
|
+
return { ...parsed, error: `Unknown option: ${token}` };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (!parsed.command) {
|
|
623
|
+
parsed.command = token;
|
|
624
|
+
} else {
|
|
625
|
+
parsed.args.push(token);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
parsed.wikiRoot = path.resolve(expandHome(parsed.wikiRoot));
|
|
630
|
+
return parsed;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function humanMessage(result) {
|
|
634
|
+
if (result.message) return result.message;
|
|
635
|
+
if (typeof result.ok === "boolean") {
|
|
636
|
+
const errorCount = result.errors?.length || 0;
|
|
637
|
+
const warningCount = result.warnings?.length || 0;
|
|
638
|
+
return result.ok
|
|
639
|
+
? `Lint passed for ${result.entries?.length || 0} entr${(result.entries?.length || 0) === 1 ? "y" : "ies"}.`
|
|
640
|
+
: `Lint found ${errorCount} error(s) and ${warningCount} warning(s).`;
|
|
641
|
+
}
|
|
642
|
+
if (result.success === false) return result.error || "Command failed";
|
|
643
|
+
return "OK";
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function outputResult(result, json) {
|
|
647
|
+
if (json) {
|
|
648
|
+
print(JSON.stringify(result, null, 2), result.success === false);
|
|
649
|
+
} else {
|
|
650
|
+
print(humanMessage(result), result.success === false);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function stubResult(command, wikiRoot) {
|
|
655
|
+
return {
|
|
656
|
+
success: true,
|
|
657
|
+
command,
|
|
658
|
+
implemented: false,
|
|
659
|
+
wikiRoot,
|
|
660
|
+
message: `${command} is not implemented yet.`,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function createOpenCodeSnippet(wikiRootInput) {
|
|
665
|
+
return {
|
|
666
|
+
plugin: [
|
|
667
|
+
["oh-my-llmwikimode", {
|
|
668
|
+
wikiRoot: wikiRootInput,
|
|
669
|
+
autoMemoryEnabled: true,
|
|
670
|
+
autoSearchEnabled: true,
|
|
671
|
+
}],
|
|
672
|
+
],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function defaultOpenCodeConfigPath() {
|
|
677
|
+
return path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function initializeWikiDirectories(wikiRoot) {
|
|
681
|
+
const paths = getWikiPaths(wikiRoot);
|
|
682
|
+
const directoryPaths = [
|
|
683
|
+
paths.root,
|
|
684
|
+
paths.inbox,
|
|
685
|
+
paths.problems,
|
|
686
|
+
paths.lessons,
|
|
687
|
+
paths.projects,
|
|
688
|
+
paths.rawRoot,
|
|
689
|
+
paths.rawSources,
|
|
690
|
+
paths.rawNotebooklm,
|
|
691
|
+
paths.rawPacks,
|
|
692
|
+
paths.system,
|
|
693
|
+
];
|
|
694
|
+
const directories = directoryPaths.map((directoryPath) => ({
|
|
695
|
+
path: directoryPath,
|
|
696
|
+
created: !fs.existsSync(directoryPath),
|
|
697
|
+
}));
|
|
698
|
+
|
|
699
|
+
ensureWikiStructure(paths);
|
|
700
|
+
return directories;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function runInit({ wikiRoot, wikiRootInput }) {
|
|
704
|
+
const directories = initializeWikiDirectories(wikiRoot);
|
|
705
|
+
const snippet = createOpenCodeSnippet(wikiRootInput);
|
|
706
|
+
const snippetPath = path.join(wikiRoot, ".system", "opencode.plugin-snippet.json");
|
|
707
|
+
|
|
708
|
+
fs.writeFileSync(snippetPath, `${JSON.stringify(snippet, null, 2)}\n`, "utf-8");
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
success: true,
|
|
712
|
+
wikiRoot,
|
|
713
|
+
directories,
|
|
714
|
+
snippetPath,
|
|
715
|
+
opencodeConfigPath: defaultOpenCodeConfigPath(),
|
|
716
|
+
snippet,
|
|
717
|
+
message: [
|
|
718
|
+
`Initialized LLM Wiki at ${wikiRoot}.`,
|
|
719
|
+
`OpenCode snippet written to ${snippetPath}.`,
|
|
720
|
+
`Merge the snippet into ${defaultOpenCodeConfigPath()}, then restart OpenCode.`,
|
|
721
|
+
].join("\n"),
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function runLint({ wikiRoot }) {
|
|
726
|
+
return lintWiki(wikiRoot);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function runDoctor({ wikiRoot }) {
|
|
730
|
+
return doctorWiki(wikiRoot);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function runQuery({ args, wikiRoot, format }) {
|
|
734
|
+
if (args.length === 0) {
|
|
735
|
+
return { success: false, error: "query requires search text" };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const query = args.join(" ");
|
|
739
|
+
return queryWiki(wikiRoot, query, { format });
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function runIngest({ args, inputPath, source, stdin, tags, title, wikiRoot, project }) {
|
|
743
|
+
const inputText = stdin ? fs.readFileSync(0, "utf-8") : undefined;
|
|
744
|
+
return ingestKnowledge({
|
|
745
|
+
wikiRoot,
|
|
746
|
+
inputText,
|
|
747
|
+
inputPath: inputPath || args[0],
|
|
748
|
+
source,
|
|
749
|
+
title,
|
|
750
|
+
tags,
|
|
751
|
+
project,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function runPromote({ args, wikiRoot }) {
|
|
756
|
+
const entryPath = args[0];
|
|
757
|
+
if (!entryPath) {
|
|
758
|
+
return { success: false, error: "promote requires an entry path" };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const result = promoteEntry(wikiRoot, entryPath);
|
|
762
|
+
if (!result.success) return result;
|
|
763
|
+
|
|
764
|
+
buildIndex(wikiRoot);
|
|
765
|
+
rebuildBrowserData(wikiRoot);
|
|
766
|
+
return {
|
|
767
|
+
...result,
|
|
768
|
+
wikiRoot,
|
|
769
|
+
message: `Promoted to lesson: ${result.path}`,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function runReject({ args, wikiRoot }) {
|
|
774
|
+
const entryPath = args[0];
|
|
775
|
+
if (!entryPath) {
|
|
776
|
+
return { success: false, error: "reject requires an entry path" };
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const result = rejectEntry(wikiRoot, entryPath);
|
|
780
|
+
if (!result.success) return result;
|
|
781
|
+
|
|
782
|
+
buildIndex(wikiRoot);
|
|
783
|
+
rebuildBrowserData(wikiRoot);
|
|
784
|
+
return {
|
|
785
|
+
...result,
|
|
786
|
+
wikiRoot,
|
|
787
|
+
message: `Rejected entry: ${result.path}`,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function runRebuild({ wikiRoot }) {
|
|
792
|
+
const index = buildIndex(wikiRoot);
|
|
793
|
+
const indexPath = path.join(wikiRoot, ".system", "index.json");
|
|
794
|
+
const graphPath = path.join(wikiRoot, ".system", "graph.json");
|
|
795
|
+
const browserDataResult = rebuildBrowserData(wikiRoot);
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
success: true,
|
|
799
|
+
wikiRoot,
|
|
800
|
+
indexPath,
|
|
801
|
+
graphPath,
|
|
802
|
+
browserData: true,
|
|
803
|
+
browserDataPath: browserDataResult.path,
|
|
804
|
+
browserDataMeta: browserDataResult.data.meta,
|
|
805
|
+
entries: index.entries.length,
|
|
806
|
+
message: `Rebuilt index, graph, and browser data for ${index.entries.length} entr${index.entries.length === 1 ? "y" : "ies"}.`,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function parseAuditLine(line) {
|
|
811
|
+
try {
|
|
812
|
+
return JSON.parse(line);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
return {
|
|
815
|
+
timestamp: "",
|
|
816
|
+
action: "error",
|
|
817
|
+
reason: `Malformed audit line: ${error.message}`,
|
|
818
|
+
entry_id: "",
|
|
819
|
+
source: "auto-memory-audit.jsonl",
|
|
820
|
+
raw: line,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function runAudit({ limit, wikiRoot }) {
|
|
826
|
+
const auditPath = path.join(wikiRoot, ".system", "auto-memory-audit.jsonl");
|
|
827
|
+
const entries = fs.existsSync(auditPath)
|
|
828
|
+
? fs.readFileSync(auditPath, "utf-8")
|
|
829
|
+
.split(/\r?\n/)
|
|
830
|
+
.filter(Boolean)
|
|
831
|
+
.slice(-limit)
|
|
832
|
+
.map(parseAuditLine)
|
|
833
|
+
: [];
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
success: true,
|
|
837
|
+
wikiRoot,
|
|
838
|
+
path: auditPath,
|
|
839
|
+
limit,
|
|
840
|
+
entries,
|
|
841
|
+
message: entries.length > 0
|
|
842
|
+
? entries.map((entry) => JSON.stringify(entry)).join("\n")
|
|
843
|
+
: "No auto-memory audit entries found.",
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function refreshTeamBrowserData(wikiRoot) {
|
|
848
|
+
rebuildBrowserData(wikiRoot);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function refreshTodoBrowserData(wikiRoot) {
|
|
852
|
+
rebuildBrowserData(wikiRoot);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function runTodo(parsed) {
|
|
856
|
+
const action = parsed.args[0] || "list";
|
|
857
|
+
|
|
858
|
+
if (action === "add") {
|
|
859
|
+
const title = (parsed.title || parsed.args.slice(1).join(" ")).trim();
|
|
860
|
+
if (!title) return { success: false, error: "todo add requires a title" };
|
|
861
|
+
const result = addTodo(parsed.wikiRoot, {
|
|
862
|
+
title,
|
|
863
|
+
project: parsed.project,
|
|
864
|
+
tags: parseList(parsed.tags),
|
|
865
|
+
}, { actor: "cli" });
|
|
866
|
+
if (result.success) refreshTodoBrowserData(parsed.wikiRoot);
|
|
867
|
+
return { ...result, wikiRoot: parsed.wikiRoot };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (action === "list") {
|
|
871
|
+
const result = listTodos(parsed.wikiRoot, { project: parsed.project });
|
|
872
|
+
return { ...result, wikiRoot: parsed.wikiRoot };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (action === "done") {
|
|
876
|
+
const idOrTitle = parsed.args.slice(1).join(" ").trim();
|
|
877
|
+
if (!idOrTitle) return { success: false, error: "todo done requires an id or title" };
|
|
878
|
+
const result = markTodoDone(parsed.wikiRoot, idOrTitle, { actor: "cli" });
|
|
879
|
+
if (result.success) refreshTodoBrowserData(parsed.wikiRoot);
|
|
880
|
+
return { ...result, wikiRoot: parsed.wikiRoot };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (action === "remove") {
|
|
884
|
+
const idOrTitle = parsed.args.slice(1).join(" ").trim();
|
|
885
|
+
if (!idOrTitle) return { success: false, error: "todo remove requires an id or title" };
|
|
886
|
+
const result = removeTodo(parsed.wikiRoot, idOrTitle, { actor: "cli" });
|
|
887
|
+
if (result.success) refreshTodoBrowserData(parsed.wikiRoot);
|
|
888
|
+
return { ...result, wikiRoot: parsed.wikiRoot };
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return { success: false, error: `Unknown todo subcommand: ${action}` };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function approvalFromParsed(parsed) {
|
|
895
|
+
return {
|
|
896
|
+
approved_by: parsed.approvedBy || "cli",
|
|
897
|
+
approved_at: new Date().toISOString(),
|
|
898
|
+
approved_capabilities: parsed.approvedCapabilities,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function requireTeamTaskId(args, action) {
|
|
903
|
+
const taskId = args[1];
|
|
904
|
+
if (!taskId) return { success: false, error: `team ${action} requires a task id` };
|
|
905
|
+
return { success: true, taskId };
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function runTeam(parsed) {
|
|
909
|
+
const action = parsed.args[0] || "list";
|
|
910
|
+
|
|
911
|
+
if (action === "list") {
|
|
912
|
+
return {
|
|
913
|
+
success: true,
|
|
914
|
+
wikiRoot: parsed.wikiRoot,
|
|
915
|
+
queue: readAgentTeamQueue(parsed.wikiRoot),
|
|
916
|
+
message: "Agent Team queue loaded.",
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (action === "create-task") {
|
|
921
|
+
const result = createTaskCard(parsed.wikiRoot, {
|
|
922
|
+
id: parsed.id,
|
|
923
|
+
title: parsed.title,
|
|
924
|
+
role_id: parsed.roleId,
|
|
925
|
+
agent_profile_id: parsed.agentProfileId,
|
|
926
|
+
goal: parsed.goal,
|
|
927
|
+
success_criteria: parsed.criteria,
|
|
928
|
+
capability_request: parsed.capabilities,
|
|
929
|
+
}, { actor: "cli" });
|
|
930
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (action === "create-profile") {
|
|
935
|
+
const result = createAgentProfile(parsed.wikiRoot, {
|
|
936
|
+
id: parsed.id,
|
|
937
|
+
label: parsed.title,
|
|
938
|
+
kind: parsed.kind,
|
|
939
|
+
default_role_id: parsed.roleId,
|
|
940
|
+
scope: parsed.scope,
|
|
941
|
+
capability_policy: {
|
|
942
|
+
allowed: parsed.allowedCapabilities,
|
|
943
|
+
requires_approval: parsed.requiresApprovalCapabilities,
|
|
944
|
+
denied: parsed.deniedCapabilities,
|
|
945
|
+
},
|
|
946
|
+
dispatch_template: parsed.template,
|
|
947
|
+
}, { actor: "cli" });
|
|
948
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (action === "create-group") {
|
|
953
|
+
const result = createAgentGroup(parsed.wikiRoot, {
|
|
954
|
+
id: parsed.id,
|
|
955
|
+
label: parsed.title,
|
|
956
|
+
agent_profile_ids: parsed.profiles,
|
|
957
|
+
default_queue_policy: parsed.policy,
|
|
958
|
+
description: parsed.description || "",
|
|
959
|
+
}, { actor: "cli" });
|
|
960
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (action === "approve") {
|
|
965
|
+
const taskIdResult = requireTeamTaskId(parsed.args, action);
|
|
966
|
+
if (!taskIdResult.success) return taskIdResult;
|
|
967
|
+
const result = updateTaskStatus(parsed.wikiRoot, taskIdResult.taskId, "approved", {
|
|
968
|
+
actor: "cli",
|
|
969
|
+
approval: approvalFromParsed(parsed),
|
|
970
|
+
});
|
|
971
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
972
|
+
return result;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (action === "status") {
|
|
976
|
+
const taskIdResult = requireTeamTaskId(parsed.args, action);
|
|
977
|
+
if (!taskIdResult.success) return taskIdResult;
|
|
978
|
+
const status = parsed.args[2];
|
|
979
|
+
if (!status) return { success: false, error: "team status requires a target status" };
|
|
980
|
+
const options = { actor: "cli" };
|
|
981
|
+
if (parsed.blockedReason) options.blocked_reason = parsed.blockedReason;
|
|
982
|
+
if (parsed.approvedBy || parsed.approvedCapabilities.length > 0) options.approval = approvalFromParsed(parsed);
|
|
983
|
+
const result = updateTaskStatus(parsed.wikiRoot, taskIdResult.taskId, status, options);
|
|
984
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (action === "generate-packet") {
|
|
989
|
+
const taskIdResult = requireTeamTaskId(parsed.args, action);
|
|
990
|
+
if (!taskIdResult.success) return taskIdResult;
|
|
991
|
+
const result = createDispatchPacketArtifact(parsed.wikiRoot, taskIdResult.taskId, { actor: "cli" });
|
|
992
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
993
|
+
return result;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (action === "evidence") {
|
|
997
|
+
const taskIdResult = requireTeamTaskId(parsed.args, action);
|
|
998
|
+
if (!taskIdResult.success) return taskIdResult;
|
|
999
|
+
const result = appendTaskEvidence(parsed.wikiRoot, taskIdResult.taskId, {
|
|
1000
|
+
id: parsed.id || `evidence-${taskIdResult.taskId}`,
|
|
1001
|
+
agent_profile_id: parsed.agentProfileId,
|
|
1002
|
+
summary: parsed.summary,
|
|
1003
|
+
artifacts: parsed.artifacts,
|
|
1004
|
+
created_at: new Date().toISOString(),
|
|
1005
|
+
}, { actor: "cli" });
|
|
1006
|
+
if (result.success) refreshTeamBrowserData(parsed.wikiRoot);
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return { success: false, error: `Unknown team subcommand: ${action}` };
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function refreshCuratorBrowserData(wikiRoot) {
|
|
1014
|
+
rebuildBrowserData(wikiRoot);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function runCurator(parsed) {
|
|
1018
|
+
const action = parsed.args[0] || "list";
|
|
1019
|
+
|
|
1020
|
+
if (action === "list") {
|
|
1021
|
+
return {
|
|
1022
|
+
success: true,
|
|
1023
|
+
wikiRoot: parsed.wikiRoot,
|
|
1024
|
+
queue: readCuratorQueue(parsed.wikiRoot),
|
|
1025
|
+
curator: buildCuratorBrowserData(parsed.wikiRoot),
|
|
1026
|
+
message: "Curator queue loaded.",
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (action === "suggest-lesson") {
|
|
1031
|
+
const result = suggestLessonCandidate(parsed.wikiRoot, {
|
|
1032
|
+
source: parsed.source,
|
|
1033
|
+
related: parsed.related,
|
|
1034
|
+
title: parsed.title,
|
|
1035
|
+
summary: parsed.summary,
|
|
1036
|
+
}, { actor: "cli" });
|
|
1037
|
+
if (result.success) refreshCuratorBrowserData(parsed.wikiRoot);
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (action === "propose-consolidation") {
|
|
1042
|
+
const positionalEntries = parsed.args.slice(1);
|
|
1043
|
+
const result = proposeConsolidation(parsed.wikiRoot, {
|
|
1044
|
+
entries: parsed.entries.length > 0 ? parsed.entries : positionalEntries,
|
|
1045
|
+
rationale: parsed.rationale,
|
|
1046
|
+
title: parsed.title,
|
|
1047
|
+
}, { actor: "cli" });
|
|
1048
|
+
if (result.success) refreshCuratorBrowserData(parsed.wikiRoot);
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return { success: false, error: `Unknown curator subcommand: ${action}` };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function runDigest(parsed) {
|
|
1056
|
+
const action = parsed.args[0] || "list";
|
|
1057
|
+
|
|
1058
|
+
if (action === "generate") {
|
|
1059
|
+
return generateWeeklyDigest(parsed.wikiRoot, { actor: "cli" });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (action === "list") {
|
|
1063
|
+
return listDigests(parsed.wikiRoot);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (action === "show") {
|
|
1067
|
+
const digestId = parsed.args[1] || parsed.id;
|
|
1068
|
+
if (!digestId) return { success: false, error: "digest show requires a digest ID" };
|
|
1069
|
+
return getDigest(parsed.wikiRoot, digestId);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return { success: false, error: `Unknown digest subcommand: ${action}` };
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function refreshGraphReasoningBrowserData(wikiRoot) {
|
|
1076
|
+
rebuildBrowserData(wikiRoot);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function runSourceLedger(parsed) {
|
|
1080
|
+
const action = parsed.args[0] || "list";
|
|
1081
|
+
|
|
1082
|
+
if (action === "import") {
|
|
1083
|
+
const filePath = parsed.args[1] || parsed.inputPath;
|
|
1084
|
+
if (!filePath) {
|
|
1085
|
+
return { success: false, error: "source-ledger import requires a file path" };
|
|
1086
|
+
}
|
|
1087
|
+
const result = importSourceFile(parsed.wikiRoot, filePath, {
|
|
1088
|
+
actor: "cli",
|
|
1089
|
+
source_id: parsed.id,
|
|
1090
|
+
title: parsed.title,
|
|
1091
|
+
kind: parsed.kind,
|
|
1092
|
+
});
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (action === "list") {
|
|
1097
|
+
const result = readSourceRecords(parsed.wikiRoot);
|
|
1098
|
+
if (!result.success) return result;
|
|
1099
|
+
const sorted = [...result.records].sort(function (a, b) {
|
|
1100
|
+
var byTime = String(a.imported_at || "").localeCompare(String(b.imported_at || ""));
|
|
1101
|
+
if (byTime !== 0) return byTime;
|
|
1102
|
+
return String(a.source_id || "").localeCompare(String(b.source_id || ""));
|
|
1103
|
+
});
|
|
1104
|
+
return { success: true, records: sorted, message: "Source ledger has " + sorted.length + " record(s)." };
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (action === "search") {
|
|
1108
|
+
const query = parsed.args[1] || parsed.summary;
|
|
1109
|
+
if (!query) {
|
|
1110
|
+
return { success: false, error: "source-ledger search requires a query" };
|
|
1111
|
+
}
|
|
1112
|
+
const recordsResult = readSourceRecords(parsed.wikiRoot);
|
|
1113
|
+
if (!recordsResult.success) return recordsResult;
|
|
1114
|
+
const queryLower = String(query).toLowerCase();
|
|
1115
|
+
const hits = [];
|
|
1116
|
+
for (const r of recordsResult.records) {
|
|
1117
|
+
let matches = false;
|
|
1118
|
+
let snippet = String(r.title || "");
|
|
1119
|
+
// Check metadata
|
|
1120
|
+
if (snippet.toLowerCase().includes(queryLower)) matches = true;
|
|
1121
|
+
if (String(r.source_path || "").toLowerCase().includes(queryLower)) matches = true;
|
|
1122
|
+
if (String(r.kind || "").toLowerCase().includes(queryLower)) matches = true;
|
|
1123
|
+
// For text sources, search content
|
|
1124
|
+
if (r.redaction === "content_redacted" || r.redaction === "content_clean") {
|
|
1125
|
+
try {
|
|
1126
|
+
const contentPath = path.join(parsed.wikiRoot, "raw", "sources", "files", r.source_id, "content.txt");
|
|
1127
|
+
if (fs.existsSync(contentPath)) {
|
|
1128
|
+
const content = fs.readFileSync(contentPath, "utf-8");
|
|
1129
|
+
if (content.toLowerCase().includes(queryLower)) {
|
|
1130
|
+
matches = true;
|
|
1131
|
+
// Extract a snippet around the match
|
|
1132
|
+
const idx = content.toLowerCase().indexOf(queryLower);
|
|
1133
|
+
const start = Math.max(0, idx - 50);
|
|
1134
|
+
const end = Math.min(content.length, idx + queryLower.length + 50);
|
|
1135
|
+
snippet = content.slice(start, end).replace(/\s+/g, " ").trim();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
} catch {
|
|
1139
|
+
// Ignore read errors
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (matches) {
|
|
1143
|
+
hits.push({
|
|
1144
|
+
source_id: r.source_id,
|
|
1145
|
+
path: r.source_path,
|
|
1146
|
+
chunk_id: "source",
|
|
1147
|
+
snippet: snippet.slice(0, 200),
|
|
1148
|
+
score: 1,
|
|
1149
|
+
review_status: "raw_evidence",
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return { success: true, hits, query, message: "Source ledger search: " + hits.length + " hit(s)." };
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return { success: false, error: "Unknown source-ledger subcommand: " + action };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function runStage4(parsed) {
|
|
1160
|
+
const action = parsed.args[0] || "";
|
|
1161
|
+
|
|
1162
|
+
if (action === "graph-reasoning") {
|
|
1163
|
+
const subAction = parsed.args[1] || "list";
|
|
1164
|
+
|
|
1165
|
+
if (subAction === "list") {
|
|
1166
|
+
return {
|
|
1167
|
+
success: true,
|
|
1168
|
+
wikiRoot: parsed.wikiRoot,
|
|
1169
|
+
queue: readGraphReasoningQueue(parsed.wikiRoot),
|
|
1170
|
+
graph_reasoning: buildGraphReasoningBrowserData(parsed.wikiRoot),
|
|
1171
|
+
message: "Stage 4 graph reasoning queue loaded.",
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (subAction === "analyze") {
|
|
1176
|
+
const result = runGraphReasoningAnalysis(parsed.wikiRoot, { actor: "cli" });
|
|
1177
|
+
if (result.success) refreshGraphReasoningBrowserData(parsed.wikiRoot);
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return { success: false, error: `Unknown stage4 graph-reasoning subcommand: ${subAction}` };
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return { success: false, error: `Unknown stage4 subcommand: ${action}` };
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function runRaw(parsed) {
|
|
1188
|
+
const action = parsed.args[0] || "list";
|
|
1189
|
+
|
|
1190
|
+
if (action === "list") {
|
|
1191
|
+
const result = listRawContents(parsed.wikiRoot);
|
|
1192
|
+
return {
|
|
1193
|
+
success: true,
|
|
1194
|
+
wikiRoot: parsed.wikiRoot,
|
|
1195
|
+
data: result,
|
|
1196
|
+
message: parsed.json
|
|
1197
|
+
? undefined
|
|
1198
|
+
: `Sources: ${result.sources.length}, NotebookLM: ${result.notebooklm.length}, Packs: ${result.packs.length}`,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (action === "status") {
|
|
1203
|
+
const result = getRawSummary(parsed.wikiRoot);
|
|
1204
|
+
return {
|
|
1205
|
+
success: true,
|
|
1206
|
+
wikiRoot: parsed.wikiRoot,
|
|
1207
|
+
data: result,
|
|
1208
|
+
message: parsed.json
|
|
1209
|
+
? undefined
|
|
1210
|
+
: `Raw total: ${result.total_files} files, ${result.total_bytes} bytes`,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (action === "validate") {
|
|
1215
|
+
const result = validateRawStructure(parsed.wikiRoot);
|
|
1216
|
+
return {
|
|
1217
|
+
success: result.valid,
|
|
1218
|
+
wikiRoot: parsed.wikiRoot,
|
|
1219
|
+
data: result,
|
|
1220
|
+
message: result.valid
|
|
1221
|
+
? "Raw structure is valid."
|
|
1222
|
+
: `Raw structure invalid: ${result.checks.filter((c) => c.required && !c.exists).map((c) => c.path).join(", ")}`,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return { success: false, error: `Unknown raw subcommand: ${action}` };
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function runSearchFeedback(parsed) {
|
|
1230
|
+
const action = parsed.args[0] || "list";
|
|
1231
|
+
|
|
1232
|
+
if (action === "list") {
|
|
1233
|
+
const result = readSearchFailures(parsed.wikiRoot, { limit: parsed.limit || 100 });
|
|
1234
|
+
if (!result.success) return result;
|
|
1235
|
+
return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
wikiRoot: parsed.wikiRoot,
|
|
1238
|
+
failures: result.events,
|
|
1239
|
+
count: result.events.length,
|
|
1240
|
+
message: result.events.length > 0
|
|
1241
|
+
? result.events.map((e) => JSON.stringify(e)).join("\n")
|
|
1242
|
+
: "No search failures recorded.",
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (action === "record") {
|
|
1247
|
+
const query = parsed.query || parsed.summary || parsed.args[1] || "";
|
|
1248
|
+
const resultCount = Number(parsed.resultCount ?? parsed.args[2] ?? 0);
|
|
1249
|
+
if (!query) {
|
|
1250
|
+
return { success: false, error: "search-feedback record requires --query or a positional argument" };
|
|
1251
|
+
}
|
|
1252
|
+
const result = appendSearchFailure(parsed.wikiRoot, {
|
|
1253
|
+
query,
|
|
1254
|
+
result_count: resultCount,
|
|
1255
|
+
max_results: parsed.limit || 3,
|
|
1256
|
+
source: "manual_cli",
|
|
1257
|
+
});
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (action === "suggest-aliases") {
|
|
1262
|
+
const failuresResult = readSearchFailures(parsed.wikiRoot, { limit: 50 });
|
|
1263
|
+
if (!failuresResult.success) return failuresResult;
|
|
1264
|
+
|
|
1265
|
+
const index = buildIndex(parsed.wikiRoot);
|
|
1266
|
+
const suggestions = [];
|
|
1267
|
+
for (const failure of failuresResult.events) {
|
|
1268
|
+
const result = generateAliasSuggestion(parsed.wikiRoot, failure, index);
|
|
1269
|
+
if (result.success) {
|
|
1270
|
+
suggestions.push(result.skipped
|
|
1271
|
+
? { skipped: true, reason: result.reason, query: failure.query }
|
|
1272
|
+
: { created: true, id: result.artifact.id, path: result.path }
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
return {
|
|
1278
|
+
success: true,
|
|
1279
|
+
wikiRoot: parsed.wikiRoot,
|
|
1280
|
+
suggestions,
|
|
1281
|
+
generated: suggestions.filter((s) => s.created).length,
|
|
1282
|
+
skipped: suggestions.filter((s) => s.skipped).length,
|
|
1283
|
+
message: `Generated ${suggestions.filter((s) => s.created).length} alias suggestion(s), skipped ${suggestions.filter((s) => s.skipped).length}.`,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return { success: false, error: `Unknown search-feedback subcommand: ${action}` };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
async function runSemanticSearch(parsed) {
|
|
1291
|
+
const query = parsed.args.join(" ").trim();
|
|
1292
|
+
if (!query) {
|
|
1293
|
+
return { success: false, error: "semantic-search requires a query" };
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const searchResult = await semanticSearch(parsed.wikiRoot, query, {
|
|
1297
|
+
topK: parsed.limitProvided ? parsed.limit : 5,
|
|
1298
|
+
});
|
|
1299
|
+
const results = searchResult.results;
|
|
1300
|
+
|
|
1301
|
+
return {
|
|
1302
|
+
success: true,
|
|
1303
|
+
wikiRoot: parsed.wikiRoot,
|
|
1304
|
+
...searchResult,
|
|
1305
|
+
count: results.length,
|
|
1306
|
+
message: results.length > 0
|
|
1307
|
+
? results.map((result) => `${result.similarity.toFixed(4)} ${result.path} ${result.title}`).join("\n")
|
|
1308
|
+
: "No semantic results found.",
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
async function runSemanticIndex(parsed) {
|
|
1313
|
+
const action = parsed.args[0] || "status";
|
|
1314
|
+
|
|
1315
|
+
if (action === "build") {
|
|
1316
|
+
const result = await buildSemanticIndex(parsed.wikiRoot);
|
|
1317
|
+
if (!result.success) return result;
|
|
1318
|
+
return {
|
|
1319
|
+
...result,
|
|
1320
|
+
wikiRoot: parsed.wikiRoot,
|
|
1321
|
+
message: `Built semantic index with ${result.count} entr${result.count === 1 ? "y" : "ies"}.`,
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if (action === "status") {
|
|
1326
|
+
const result = loadSemanticIndex(parsed.wikiRoot);
|
|
1327
|
+
if (!result.success) return result;
|
|
1328
|
+
const fileSize = fs.existsSync(result.path) ? fs.statSync(result.path).size : 0;
|
|
1329
|
+
const lastBuild = result.entries.reduce((latest, entry) => {
|
|
1330
|
+
const ts = String(entry.ts || "");
|
|
1331
|
+
return ts > latest ? ts : latest;
|
|
1332
|
+
}, "");
|
|
1333
|
+
return {
|
|
1334
|
+
success: true,
|
|
1335
|
+
wikiRoot: parsed.wikiRoot,
|
|
1336
|
+
path: result.path,
|
|
1337
|
+
count: getSemanticIndexSize(parsed.wikiRoot),
|
|
1338
|
+
size_bytes: fileSize,
|
|
1339
|
+
last_build: lastBuild || null,
|
|
1340
|
+
message: `Semantic index: ${result.entries.length} entr${result.entries.length === 1 ? "y" : "ies"}, ${fileSize} bytes.`,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (action === "update") {
|
|
1345
|
+
const result = await updateSemanticIndex(parsed.wikiRoot);
|
|
1346
|
+
if (!result.success) return result;
|
|
1347
|
+
return {
|
|
1348
|
+
...result,
|
|
1349
|
+
wikiRoot: parsed.wikiRoot,
|
|
1350
|
+
message: `Updated semantic index with ${result.count} entr${result.count === 1 ? "y" : "ies"}.`,
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return { success: false, error: `Unknown semantic-index subcommand: ${action}` };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function runLessonProposal(parsed) {
|
|
1358
|
+
const action = parsed.args[0] || "list";
|
|
1359
|
+
|
|
1360
|
+
if (action === "analyze") {
|
|
1361
|
+
const result = generateLessonProposals(parsed.wikiRoot, {
|
|
1362
|
+
days: parsed.limitProvided ? parsed.limit : 30,
|
|
1363
|
+
});
|
|
1364
|
+
if (!result.success) return result;
|
|
1365
|
+
return {
|
|
1366
|
+
...result,
|
|
1367
|
+
wikiRoot: parsed.wikiRoot,
|
|
1368
|
+
message: `Analyzed ${result.total_events} usage events. Generated ${result.proposals_generated} proposal(s), skipped ${result.proposals_skipped} duplicate(s).`,
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (action === "list") {
|
|
1373
|
+
const result = listProposals(parsed.wikiRoot);
|
|
1374
|
+
if (!result.success) return result;
|
|
1375
|
+
return {
|
|
1376
|
+
...result,
|
|
1377
|
+
wikiRoot: parsed.wikiRoot,
|
|
1378
|
+
message: result.count > 0
|
|
1379
|
+
? `${result.count} lesson proposal(s) found.`
|
|
1380
|
+
: "No lesson proposals found.",
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (action === "show") {
|
|
1385
|
+
const proposalId = parsed.args[1] || parsed.id;
|
|
1386
|
+
if (!proposalId) {
|
|
1387
|
+
return { success: false, error: "lesson-proposal show requires a proposal ID" };
|
|
1388
|
+
}
|
|
1389
|
+
const result = getProposal(parsed.wikiRoot, proposalId);
|
|
1390
|
+
if (!result.success) return result;
|
|
1391
|
+
return {
|
|
1392
|
+
...result,
|
|
1393
|
+
wikiRoot: parsed.wikiRoot,
|
|
1394
|
+
message: `Proposal: ${result.proposal.proposed_title} (${result.proposal.status})`,
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (action === "apply") {
|
|
1399
|
+
const proposalId = parsed.args[1] || parsed.id;
|
|
1400
|
+
if (!proposalId) {
|
|
1401
|
+
return { success: false, error: "lesson-proposal apply requires a proposal ID" };
|
|
1402
|
+
}
|
|
1403
|
+
const result = applyProposal(parsed.wikiRoot, proposalId, {
|
|
1404
|
+
actor: "cli",
|
|
1405
|
+
now: parsed.now,
|
|
1406
|
+
});
|
|
1407
|
+
if (result.success) {
|
|
1408
|
+
try {
|
|
1409
|
+
buildIndex(parsed.wikiRoot);
|
|
1410
|
+
rebuildBrowserData(parsed.wikiRoot);
|
|
1411
|
+
} catch (rebuildError) {
|
|
1412
|
+
return {
|
|
1413
|
+
success: true,
|
|
1414
|
+
warning: `Lesson created, but index rebuild failed: ${rebuildError.message}`,
|
|
1415
|
+
...result,
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return result;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return { success: false, error: `Unknown lesson-proposal subcommand: ${action}` };
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
const HANDLERS = {
|
|
1426
|
+
init: runInit,
|
|
1427
|
+
lint: runLint,
|
|
1428
|
+
doctor: runDoctor,
|
|
1429
|
+
query: runQuery,
|
|
1430
|
+
ingest: runIngest,
|
|
1431
|
+
promote: runPromote,
|
|
1432
|
+
reject: runReject,
|
|
1433
|
+
rebuild: runRebuild,
|
|
1434
|
+
audit: runAudit,
|
|
1435
|
+
todo: runTodo,
|
|
1436
|
+
team: runTeam,
|
|
1437
|
+
curator: runCurator,
|
|
1438
|
+
digest: runDigest,
|
|
1439
|
+
"source-ledger": runSourceLedger,
|
|
1440
|
+
stage4: runStage4,
|
|
1441
|
+
"search-feedback": runSearchFeedback,
|
|
1442
|
+
"semantic-search": runSemanticSearch,
|
|
1443
|
+
"semantic-index": runSemanticIndex,
|
|
1444
|
+
raw: runRaw,
|
|
1445
|
+
"lesson-proposal": runLessonProposal,
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
function handleHelp(commandName, json) {
|
|
1449
|
+
if (json) {
|
|
1450
|
+
print(JSON.stringify(helpPayload(commandName), null, 2));
|
|
1451
|
+
} else if (commandName) {
|
|
1452
|
+
print(commandHelp(commandName));
|
|
1453
|
+
} else {
|
|
1454
|
+
print(mainHelp());
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
async function main() {
|
|
1459
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
1460
|
+
|
|
1461
|
+
if (parsed.error) {
|
|
1462
|
+
outputResult({ success: false, error: parsed.error }, parsed.json);
|
|
1463
|
+
return 1;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
if (!parsed.command) {
|
|
1467
|
+
handleHelp(null, parsed.json);
|
|
1468
|
+
return 0;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
if (!Object.hasOwn(COMMANDS, parsed.command)) {
|
|
1472
|
+
outputResult({ success: false, error: `Unknown command: ${parsed.command}` }, parsed.json);
|
|
1473
|
+
return 1;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if (parsed.help) {
|
|
1477
|
+
handleHelp(parsed.command, parsed.json);
|
|
1478
|
+
return 0;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const result = await HANDLERS[parsed.command](parsed);
|
|
1482
|
+
outputResult(result, parsed.json);
|
|
1483
|
+
return result.success === false ? 1 : 0;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
main()
|
|
1487
|
+
.then((code) => {
|
|
1488
|
+
process.exitCode = code;
|
|
1489
|
+
})
|
|
1490
|
+
.catch((error) => {
|
|
1491
|
+
outputResult({ success: false, error: error.message }, process.argv.includes("--json"));
|
|
1492
|
+
process.exitCode = 1;
|
|
1493
|
+
});
|