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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$WikiRoot = "~/Documents/llm-wiki",
|
|
3
|
+
[string]$ConfigPath = ""
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = "Stop"
|
|
7
|
+
|
|
8
|
+
$PluginName = "oh-my-llmwikimode"
|
|
9
|
+
|
|
10
|
+
function Get-HomePath {
|
|
11
|
+
if (-not [string]::IsNullOrWhiteSpace($env:USERPROFILE)) {
|
|
12
|
+
return $env:USERPROFILE
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return $HOME
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Expand-UserPath {
|
|
19
|
+
param([string]$InputPath)
|
|
20
|
+
|
|
21
|
+
if ([string]::IsNullOrWhiteSpace($InputPath)) {
|
|
22
|
+
return $InputPath
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
$homePath = Get-HomePath
|
|
26
|
+
if ($InputPath -eq "~") {
|
|
27
|
+
return $homePath
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ($InputPath.StartsWith("~/") -or $InputPath.StartsWith("~\")) {
|
|
31
|
+
return Join-Path $homePath $InputPath.Substring(2)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [System.IO.Path]::GetFullPath($InputPath)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Get-DefaultOpenCodeConfigDir {
|
|
38
|
+
return Join-Path (Get-HomePath) ".config\opencode"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ConvertTo-PlainObject {
|
|
42
|
+
param($InputObject)
|
|
43
|
+
|
|
44
|
+
if ($null -eq $InputObject) {
|
|
45
|
+
return $null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
|
|
49
|
+
$result = [ordered]@{}
|
|
50
|
+
foreach ($property in $InputObject.PSObject.Properties) {
|
|
51
|
+
$result[$property.Name] = ConvertTo-PlainObject $property.Value
|
|
52
|
+
}
|
|
53
|
+
return $result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (($InputObject -is [System.Collections.IEnumerable]) -and -not ($InputObject -is [string])) {
|
|
57
|
+
$items = @()
|
|
58
|
+
foreach ($item in $InputObject) {
|
|
59
|
+
$items += ,(ConvertTo-PlainObject $item)
|
|
60
|
+
}
|
|
61
|
+
return ,$items
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return $InputObject
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Read-OpenCodeConfig {
|
|
68
|
+
param([string]$TargetConfigPath)
|
|
69
|
+
|
|
70
|
+
if (-not (Test-Path -LiteralPath $TargetConfigPath)) {
|
|
71
|
+
return [ordered]@{}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$rawJson = Get-Content -LiteralPath $TargetConfigPath -Raw -Encoding UTF8
|
|
75
|
+
if ([string]::IsNullOrWhiteSpace($rawJson)) {
|
|
76
|
+
return [ordered]@{}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
$parsed = $rawJson | ConvertFrom-Json
|
|
81
|
+
$plain = ConvertTo-PlainObject $parsed
|
|
82
|
+
if (-not ($plain -is [System.Collections.IDictionary])) {
|
|
83
|
+
throw "opencode.json root must be a JSON object."
|
|
84
|
+
}
|
|
85
|
+
return $plain
|
|
86
|
+
} catch {
|
|
87
|
+
throw "Could not parse $TargetConfigPath. Fix the JSON first; no changes were written. $($_.Exception.Message)"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function New-LlmWikiPluginEntry {
|
|
92
|
+
param([string]$ConfiguredWikiRoot)
|
|
93
|
+
|
|
94
|
+
return ,@(
|
|
95
|
+
$PluginName,
|
|
96
|
+
[ordered]@{
|
|
97
|
+
wikiRoot = $ConfiguredWikiRoot
|
|
98
|
+
autoMemoryEnabled = $true
|
|
99
|
+
autoSearchEnabled = $true
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function Test-LlmWikiPluginEntry {
|
|
105
|
+
param($Entry)
|
|
106
|
+
|
|
107
|
+
$items = @($Entry)
|
|
108
|
+
return ($items.Count -ge 1 -and [string]$items[0] -eq $PluginName)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Add-OrUpdatePluginEntry {
|
|
112
|
+
param(
|
|
113
|
+
[System.Collections.IDictionary]$Config,
|
|
114
|
+
[string]$ConfiguredWikiRoot
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
$pluginEntry = New-LlmWikiPluginEntry $ConfiguredWikiRoot
|
|
118
|
+
$existingPlugins = @()
|
|
119
|
+
if ($Config.Contains("plugin") -and $null -ne $Config["plugin"]) {
|
|
120
|
+
foreach ($entry in @($Config["plugin"])) {
|
|
121
|
+
$existingPlugins += ,$entry
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
$updatedPlugins = @()
|
|
126
|
+
$found = $false
|
|
127
|
+
foreach ($entry in $existingPlugins) {
|
|
128
|
+
if (Test-LlmWikiPluginEntry $entry) {
|
|
129
|
+
$updatedPlugins += ,$pluginEntry
|
|
130
|
+
$found = $true
|
|
131
|
+
} else {
|
|
132
|
+
$updatedPlugins += ,$entry
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (-not $found) {
|
|
137
|
+
$updatedPlugins += ,$pluginEntry
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
$Config["plugin"] = @($updatedPlugins)
|
|
141
|
+
return $Config
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function Get-PluginSnippetJson {
|
|
145
|
+
param([string]$ConfiguredWikiRoot)
|
|
146
|
+
|
|
147
|
+
$snippet = [ordered]@{
|
|
148
|
+
plugin = @(
|
|
149
|
+
(New-LlmWikiPluginEntry $ConfiguredWikiRoot)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
return ($snippet | ConvertTo-Json -Depth 20)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Ensure-WikiDirectories {
|
|
156
|
+
param([string]$ResolvedWikiRoot)
|
|
157
|
+
|
|
158
|
+
$directories = @(
|
|
159
|
+
$ResolvedWikiRoot,
|
|
160
|
+
(Join-Path $ResolvedWikiRoot "inbox"),
|
|
161
|
+
(Join-Path $ResolvedWikiRoot "problems"),
|
|
162
|
+
(Join-Path (Join-Path $ResolvedWikiRoot "editorial") "lessons"),
|
|
163
|
+
(Join-Path $ResolvedWikiRoot "raw"),
|
|
164
|
+
(Join-Path $ResolvedWikiRoot ".system")
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
foreach ($directory in $directories) {
|
|
168
|
+
if (-not (Test-Path -LiteralPath $directory)) {
|
|
169
|
+
New-Item -ItemType Directory -Path $directory -Force | Out-Null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function Write-NextSteps {
|
|
175
|
+
param(
|
|
176
|
+
[string]$ResolvedWikiRoot,
|
|
177
|
+
[string]$TargetConfigPath
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
Write-Host ""
|
|
181
|
+
Write-Host "LLM Wiki installation complete." -ForegroundColor Green
|
|
182
|
+
Write-Host "Wiki root: $ResolvedWikiRoot"
|
|
183
|
+
Write-Host "OpenCode config: $TargetConfigPath"
|
|
184
|
+
Write-Host ""
|
|
185
|
+
Write-Host "Next steps:"
|
|
186
|
+
Write-Host "1. Restart OpenCode."
|
|
187
|
+
Write-Host "2. Ask OpenCode to check your wiki status."
|
|
188
|
+
Write-Host ('3. Optional CLI check: npx llmwiki lint --wiki-root "{0}"' -f $WikiRoot)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
$resolvedWikiRoot = Expand-UserPath $WikiRoot
|
|
192
|
+
$configDir = Get-DefaultOpenCodeConfigDir
|
|
193
|
+
if (-not [string]::IsNullOrWhiteSpace($ConfigPath)) {
|
|
194
|
+
$resolvedConfigPath = Expand-UserPath $ConfigPath
|
|
195
|
+
$configDir = Split-Path -Parent $resolvedConfigPath
|
|
196
|
+
} else {
|
|
197
|
+
$resolvedConfigPath = Join-Path $configDir "opencode.json"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Ensure-WikiDirectories $resolvedWikiRoot
|
|
201
|
+
|
|
202
|
+
if (-not (Test-Path -LiteralPath $configDir)) {
|
|
203
|
+
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
$configExists = Test-Path -LiteralPath $resolvedConfigPath
|
|
207
|
+
if ($configExists) {
|
|
208
|
+
Write-Host "Existing OpenCode config found: $resolvedConfigPath"
|
|
209
|
+
Write-Host "A timestamped backup will be created before any change."
|
|
210
|
+
$answer = Read-Host "Continue updating opencode.json? [y/N]"
|
|
211
|
+
if ($answer -notmatch "(?i)^(y|yes)$") {
|
|
212
|
+
Write-Host "Config not modified. Add this snippet manually if you want to finish setup:"
|
|
213
|
+
Write-Host (Get-PluginSnippetJson $WikiRoot)
|
|
214
|
+
exit 0
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
218
|
+
$backupPath = "$resolvedConfigPath.bak.$timestamp"
|
|
219
|
+
Copy-Item -LiteralPath $resolvedConfigPath -Destination $backupPath -Force
|
|
220
|
+
Write-Host "Backup written: $backupPath"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
$config = Read-OpenCodeConfig $resolvedConfigPath
|
|
224
|
+
$config = Add-OrUpdatePluginEntry $config $WikiRoot
|
|
225
|
+
$configJson = $config | ConvertTo-Json -Depth 20
|
|
226
|
+
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
227
|
+
[System.IO.File]::WriteAllText($resolvedConfigPath, $configJson + [Environment]::NewLine, $utf8NoBom)
|
|
228
|
+
|
|
229
|
+
Write-NextSteps -ResolvedWikiRoot $resolvedWikiRoot -TargetConfigPath $resolvedConfigPath
|
package/src/config.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration and path resolution for oh-my-llmwikimode
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Expand `~` in a path to the user's home directory.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} inputPath
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function expandHome(inputPath) {
|
|
15
|
+
if (!inputPath || typeof inputPath !== "string") {
|
|
16
|
+
return inputPath;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (inputPath.startsWith("~/")) {
|
|
20
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (inputPath === "~") {
|
|
24
|
+
return os.homedir();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return inputPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve wiki root path with precedence:
|
|
32
|
+
* 1. Explicit plugin option `wikiRoot`
|
|
33
|
+
* 2. Environment variable `LLMWIKIMODE_WIKI_ROOT`
|
|
34
|
+
* 3. Default `~/Documents/llm-wiki`
|
|
35
|
+
*
|
|
36
|
+
* @param {object} options - Plugin options from opencode.json
|
|
37
|
+
* @returns {string} - Resolved absolute path
|
|
38
|
+
*/
|
|
39
|
+
export function resolveWikiRoot(options = {}) {
|
|
40
|
+
const fromOption = options.wikiRoot;
|
|
41
|
+
const fromEnv = process.env.LLMWIKIMODE_WIKI_ROOT;
|
|
42
|
+
const defaultPath = "~/Documents/llm-wiki";
|
|
43
|
+
|
|
44
|
+
const rawPath = fromOption || fromEnv || defaultPath;
|
|
45
|
+
return path.resolve(expandHome(rawPath));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve state directory path.
|
|
50
|
+
* Default: `~/.config/opencode/.llmwikimode/`
|
|
51
|
+
*
|
|
52
|
+
* @returns {string} - Resolved absolute path
|
|
53
|
+
*/
|
|
54
|
+
export function resolveStateDir() {
|
|
55
|
+
return path.resolve(expandHome("~/.config/opencode/.llmwikimode"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get full configuration object.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} options - Plugin options from opencode.json
|
|
62
|
+
* @returns {object}
|
|
63
|
+
*/
|
|
64
|
+
export function getConfig(options = {}) {
|
|
65
|
+
return {
|
|
66
|
+
wikiRoot: resolveWikiRoot(options),
|
|
67
|
+
stateDir: resolveStateDir(),
|
|
68
|
+
autoMemoryEnabled: options.autoMemoryEnabled !== false,
|
|
69
|
+
verboseAutoMemory: options.verboseAutoMemory === true,
|
|
70
|
+
autoSearchEnabled: options.autoSearchEnabled !== false,
|
|
71
|
+
contextBudget: options.contextBudget || 2500,
|
|
72
|
+
maxEntries: options.maxEntries || 3,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getCuratorPaths, readCuratorQueue } from "./queue.js";
|
|
4
|
+
import { validateConsolidationProposal, validateLessonCandidateArtifact } from "./schema.js";
|
|
5
|
+
|
|
6
|
+
function compareStrings(left, right) {
|
|
7
|
+
return String(left ?? "").localeCompare(String(right ?? ""));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function summarizeCard(card) {
|
|
11
|
+
return {
|
|
12
|
+
id: card.id,
|
|
13
|
+
kind: card.kind,
|
|
14
|
+
title: card.title,
|
|
15
|
+
status: card.status,
|
|
16
|
+
review_required: card.review_required,
|
|
17
|
+
artifact_path: card.artifact_path,
|
|
18
|
+
source_paths: card.source_paths,
|
|
19
|
+
scoring_hints: card.scoring_hints,
|
|
20
|
+
created_at: card.created_at,
|
|
21
|
+
updated_at: card.updated_at,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function summarizeCandidate(candidate, artifactPath) {
|
|
26
|
+
return {
|
|
27
|
+
id: candidate.id,
|
|
28
|
+
title: candidate.title,
|
|
29
|
+
status: candidate.status,
|
|
30
|
+
review_required: candidate.review_required,
|
|
31
|
+
artifact_path: artifactPath,
|
|
32
|
+
source_paths: candidate.source_refs.map((ref) => ref.path),
|
|
33
|
+
related_paths: candidate.related_refs.map((ref) => ref.path),
|
|
34
|
+
proposed_title: candidate.proposed_lesson.title,
|
|
35
|
+
proposed_tags: candidate.proposed_lesson.tags,
|
|
36
|
+
scoring_hints: candidate.scoring_hints,
|
|
37
|
+
created_at: candidate.created_at,
|
|
38
|
+
updated_at: candidate.updated_at,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function summarizeProposal(proposal, artifactPath) {
|
|
43
|
+
return {
|
|
44
|
+
id: proposal.id,
|
|
45
|
+
title: proposal.title,
|
|
46
|
+
status: proposal.status,
|
|
47
|
+
review_required: proposal.review_required,
|
|
48
|
+
artifact_path: artifactPath,
|
|
49
|
+
entry_paths: proposal.entry_refs.map((ref) => ref.path),
|
|
50
|
+
rationale: proposal.rationale,
|
|
51
|
+
proposed_actions: proposal.proposed_actions,
|
|
52
|
+
scoring_hints: proposal.scoring_hints,
|
|
53
|
+
created_at: proposal.created_at,
|
|
54
|
+
updated_at: proposal.updated_at,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readJsonArtifacts(directory, relativePrefix, validator, summarizer) {
|
|
59
|
+
if (!fs.existsSync(directory)) return [];
|
|
60
|
+
return fs.readdirSync(directory)
|
|
61
|
+
.filter((fileName) => fileName.endsWith(".json"))
|
|
62
|
+
.sort(compareStrings)
|
|
63
|
+
.flatMap((fileName) => {
|
|
64
|
+
const filePath = path.join(directory, fileName);
|
|
65
|
+
const artifactPath = `${relativePrefix}/${fileName}`;
|
|
66
|
+
try {
|
|
67
|
+
const validation = validator(JSON.parse(fs.readFileSync(filePath, "utf-8")));
|
|
68
|
+
if (!validation.valid) return [];
|
|
69
|
+
return [summarizer(validation.value, artifactPath)];
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readAuditSummary(wikiRoot) {
|
|
77
|
+
const paths = getCuratorPaths(wikiRoot);
|
|
78
|
+
const actionCounts = {};
|
|
79
|
+
let auditCount = 0;
|
|
80
|
+
let lastRecord = null;
|
|
81
|
+
|
|
82
|
+
if (fs.existsSync(paths.auditFile)) {
|
|
83
|
+
for (const line of fs.readFileSync(paths.auditFile, "utf-8").split(/\r?\n/).filter(Boolean)) {
|
|
84
|
+
try {
|
|
85
|
+
const record = JSON.parse(line);
|
|
86
|
+
const action = String(record.action || "unknown");
|
|
87
|
+
actionCounts[action] = (actionCounts[action] || 0) + 1;
|
|
88
|
+
auditCount += 1;
|
|
89
|
+
lastRecord = record;
|
|
90
|
+
} catch {
|
|
91
|
+
actionCounts.malformed = (actionCounts.malformed || 0) + 1;
|
|
92
|
+
auditCount += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
audit_count: auditCount,
|
|
99
|
+
action_counts: Object.fromEntries(Object.entries(actionCounts).sort(([left], [right]) => compareStrings(left, right))),
|
|
100
|
+
last_action: lastRecord?.action || "",
|
|
101
|
+
last_actor: lastRecord?.actor || "",
|
|
102
|
+
last_timestamp: lastRecord?.timestamp || "",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function buildCuratorBrowserData(wikiRoot) {
|
|
107
|
+
const paths = getCuratorPaths(wikiRoot);
|
|
108
|
+
const queue = readCuratorQueue(wikiRoot);
|
|
109
|
+
const queueCards = queue.cards.map(summarizeCard);
|
|
110
|
+
const lessonCandidates = readJsonArtifacts(
|
|
111
|
+
paths.candidatesDir,
|
|
112
|
+
".system/curator/lesson-candidates",
|
|
113
|
+
validateLessonCandidateArtifact,
|
|
114
|
+
summarizeCandidate
|
|
115
|
+
);
|
|
116
|
+
const consolidationProposals = readJsonArtifacts(
|
|
117
|
+
paths.proposalsDir,
|
|
118
|
+
".system/curator/consolidation-proposals",
|
|
119
|
+
validateConsolidationProposal,
|
|
120
|
+
summarizeProposal
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
queue_cards: queueCards,
|
|
125
|
+
lesson_candidates: lessonCandidates,
|
|
126
|
+
consolidation_proposals: consolidationProposals,
|
|
127
|
+
audit_summary: {
|
|
128
|
+
...readAuditSummary(wikiRoot),
|
|
129
|
+
card_count: queueCards.length,
|
|
130
|
+
lesson_candidate_count: lessonCandidates.length,
|
|
131
|
+
consolidation_proposal_count: consolidationProposals.length,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|