pi-harness-runtime 1.1.324 → 1.1.326
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/policy-loader.d.ts +8 -1
- package/packages/agent-policy/dist/policy-loader.d.ts.map +1 -1
- package/packages/agent-policy/dist/policy-loader.js +23 -5
- package/packages/agent-policy/dist/policy-loader.js.map +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/policy-loader.ts +28 -7
- package/packages/agent-policy/test/policy-retention.test.ts +43 -1
- package/packages/agent-policy/test/policy.test.ts +7 -3
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/dist/index.d.ts +132 -0
- package/packages/wiki/dist/index.d.ts.map +1 -0
- package/packages/wiki/dist/index.js +448 -0
- package/packages/wiki/dist/index.js.map +1 -0
- package/packages/wiki/dist/storage.d.ts +142 -0
- package/packages/wiki/dist/storage.d.ts.map +1 -0
- package/packages/wiki/dist/storage.js +603 -0
- package/packages/wiki/dist/storage.js.map +1 -0
- package/packages/wiki/dist/types.d.ts +189 -0
- package/packages/wiki/dist/types.d.ts.map +1 -0
- package/packages/wiki/dist/types.js +30 -0
- package/packages/wiki/dist/types.js.map +1 -0
- package/packages/wiki/package.json +22 -0
- package/packages/wiki/src/index.ts +578 -0
- package/packages/wiki/src/storage.ts +696 -0
- package/packages/wiki/src/types.ts +263 -0
- package/packages/wiki/test/basic.test.ts +242 -0
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiki Package - Main API
|
|
3
|
+
*
|
|
4
|
+
* Implements LLM Wiki integration per wiki/llm-wiki-integration-minimax-plan.md
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join, resolve, relative } from "node:path";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import {
|
|
11
|
+
WikiStorage,
|
|
12
|
+
createWikiStorage,
|
|
13
|
+
computeHash,
|
|
14
|
+
now,
|
|
15
|
+
parseFrontmatter,
|
|
16
|
+
combinePage,
|
|
17
|
+
} from "./storage.js";
|
|
18
|
+
import type {
|
|
19
|
+
WikiConfig,
|
|
20
|
+
WikiPage,
|
|
21
|
+
WikiPageFrontmatter,
|
|
22
|
+
SourceSnapshot,
|
|
23
|
+
WikiOperation,
|
|
24
|
+
WikiSearchFilters,
|
|
25
|
+
WikiSearchResult,
|
|
26
|
+
LintFinding,
|
|
27
|
+
StagedChange,
|
|
28
|
+
WikiInitResult,
|
|
29
|
+
PageStatus,
|
|
30
|
+
PageKind,
|
|
31
|
+
} from "./types.js";
|
|
32
|
+
import { toSourceRef } from "./types.js";
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Feature Flag
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if wiki feature is enabled
|
|
40
|
+
*/
|
|
41
|
+
export function isWikiEnabled(): boolean {
|
|
42
|
+
return process.env.HARNESS_KNOWLEDGE_WIKI === "1";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Require wiki to be enabled
|
|
47
|
+
*/
|
|
48
|
+
function requireWiki(): void {
|
|
49
|
+
if (!isWikiEnabled()) {
|
|
50
|
+
throw new Error("Wiki feature is disabled. Set HARNESS_KNOWLEDGE_WIKI=1 to enable.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Wiki Manager
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Wiki manager - main API for wiki operations
|
|
60
|
+
*/
|
|
61
|
+
export class WikiManager {
|
|
62
|
+
private storage: WikiStorage;
|
|
63
|
+
private config: WikiConfig;
|
|
64
|
+
|
|
65
|
+
constructor(storage: WikiStorage) {
|
|
66
|
+
this.storage = storage;
|
|
67
|
+
this.config = storage.getConfig();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get wiki configuration
|
|
72
|
+
*/
|
|
73
|
+
getConfig(): WikiConfig {
|
|
74
|
+
return this.config;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ========================================================================
|
|
78
|
+
// Initialization
|
|
79
|
+
// ========================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize wiki structure
|
|
83
|
+
*/
|
|
84
|
+
initialize(): WikiInitResult {
|
|
85
|
+
requireWiki();
|
|
86
|
+
return this.storage.initialize();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if wiki is initialized
|
|
91
|
+
*/
|
|
92
|
+
isInitialized(): boolean {
|
|
93
|
+
return existsSync(this.config.knowledgeDir);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ========================================================================
|
|
97
|
+
// Source Ingestion
|
|
98
|
+
// ========================================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Ingest a source file and create snapshot
|
|
102
|
+
*/
|
|
103
|
+
ingestSource(
|
|
104
|
+
sourcePath: string,
|
|
105
|
+
requestId?: string
|
|
106
|
+
): { snapshot: SourceSnapshot; operation: WikiOperation } {
|
|
107
|
+
requireWiki();
|
|
108
|
+
|
|
109
|
+
// Validate source path
|
|
110
|
+
const resolvedPath = resolve(sourcePath);
|
|
111
|
+
if (!existsSync(resolvedPath)) {
|
|
112
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Read content
|
|
116
|
+
const content = readFileSync(resolvedPath, "utf8");
|
|
117
|
+
|
|
118
|
+
// Log operation
|
|
119
|
+
const operation = this.storage.logOperation("ingest", { sourcePath }, requestId);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Create snapshot
|
|
123
|
+
const snapshot = this.storage.createSnapshot(resolvedPath, content, operation.id);
|
|
124
|
+
|
|
125
|
+
// Complete operation
|
|
126
|
+
this.storage.completeOperation(operation.id, "success");
|
|
127
|
+
|
|
128
|
+
return { snapshot, operation };
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
131
|
+
this.storage.completeOperation(operation.id, "failed", error);
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get source snapshot
|
|
138
|
+
*/
|
|
139
|
+
getSnapshot(snapshotId: string): SourceSnapshot | null {
|
|
140
|
+
requireWiki();
|
|
141
|
+
return this.storage.getSnapshot(snapshotId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ========================================================================
|
|
145
|
+
// Page Operations
|
|
146
|
+
// ========================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a new wiki page
|
|
150
|
+
*/
|
|
151
|
+
createPage(params: {
|
|
152
|
+
title: string;
|
|
153
|
+
kind: PageKind;
|
|
154
|
+
body: string;
|
|
155
|
+
sources?: SourceSnapshot[];
|
|
156
|
+
relatedPages?: string[];
|
|
157
|
+
codeVersion?: string;
|
|
158
|
+
}): WikiPage {
|
|
159
|
+
requireWiki();
|
|
160
|
+
|
|
161
|
+
const id = `page-${randomUUID().slice(0, 8)}`;
|
|
162
|
+
const timestamp = now();
|
|
163
|
+
|
|
164
|
+
const frontmatter: WikiPageFrontmatter = {
|
|
165
|
+
id,
|
|
166
|
+
title: params.title,
|
|
167
|
+
kind: params.kind,
|
|
168
|
+
projectId: this.config.projectId,
|
|
169
|
+
status: "proposed",
|
|
170
|
+
createdAt: timestamp,
|
|
171
|
+
updatedAt: timestamp,
|
|
172
|
+
sources: (params.sources ?? []).map(toSourceRef),
|
|
173
|
+
relatedPages: params.relatedPages ?? [],
|
|
174
|
+
codeVersion: params.codeVersion,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const page: WikiPage = {
|
|
178
|
+
frontmatter,
|
|
179
|
+
body: params.body,
|
|
180
|
+
rawFrontmatter: "",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
this.storage.savePage(page);
|
|
184
|
+
return page;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Update an existing page
|
|
189
|
+
*/
|
|
190
|
+
updatePage(
|
|
191
|
+
pageId: string,
|
|
192
|
+
updates: Partial<{
|
|
193
|
+
title: string;
|
|
194
|
+
status: PageStatus;
|
|
195
|
+
body: string;
|
|
196
|
+
sources: SourceSnapshot[];
|
|
197
|
+
relatedPages: string[];
|
|
198
|
+
codeVersion: string;
|
|
199
|
+
verification: WikiPageFrontmatter["verification"];
|
|
200
|
+
}>
|
|
201
|
+
): WikiPage {
|
|
202
|
+
requireWiki();
|
|
203
|
+
|
|
204
|
+
const existing = this.storage.loadPage(pageId);
|
|
205
|
+
if (!existing) {
|
|
206
|
+
throw new Error(`Page not found: ${pageId}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const updatedFrontmatter: WikiPageFrontmatter = {
|
|
210
|
+
...existing.frontmatter,
|
|
211
|
+
...updates,
|
|
212
|
+
sources: updates.sources ? updates.sources.map(toSourceRef) : existing.frontmatter.sources,
|
|
213
|
+
updatedAt: now(),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const page: WikiPage = {
|
|
217
|
+
frontmatter: updatedFrontmatter,
|
|
218
|
+
body: updates.body ?? existing.body,
|
|
219
|
+
rawFrontmatter: existing.rawFrontmatter,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.storage.savePage(page);
|
|
223
|
+
return page;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Load a wiki page
|
|
228
|
+
*/
|
|
229
|
+
loadPage(pageId: string): WikiPage | null {
|
|
230
|
+
requireWiki();
|
|
231
|
+
return this.storage.loadPage(pageId);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Delete a wiki page
|
|
236
|
+
*/
|
|
237
|
+
deletePage(pageId: string): void {
|
|
238
|
+
requireWiki();
|
|
239
|
+
this.storage.deletePage(pageId);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get all pages
|
|
244
|
+
*/
|
|
245
|
+
getAllPages(): WikiPage[] {
|
|
246
|
+
requireWiki();
|
|
247
|
+
return this.storage.getAllPages();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ========================================================================
|
|
251
|
+
// Search
|
|
252
|
+
// ========================================================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Search wiki pages
|
|
256
|
+
*/
|
|
257
|
+
search(query: string, filters?: WikiSearchFilters, limit = 10): WikiSearchResult[] {
|
|
258
|
+
requireWiki();
|
|
259
|
+
return this.storage.search(query, filters, limit);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get page index
|
|
264
|
+
*/
|
|
265
|
+
getIndex() {
|
|
266
|
+
requireWiki();
|
|
267
|
+
return this.storage.getIndex();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ========================================================================
|
|
271
|
+
// Staged Changes
|
|
272
|
+
// ========================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Stage a change for later application
|
|
276
|
+
*/
|
|
277
|
+
stageChange(pageId: string, expectedRevision: string, newContent: string): StagedChange {
|
|
278
|
+
requireWiki();
|
|
279
|
+
|
|
280
|
+
const existing = this.storage.loadPage(pageId);
|
|
281
|
+
if (!existing) {
|
|
282
|
+
throw new Error(`Page not found: ${pageId}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const change: StagedChange = {
|
|
286
|
+
pageId,
|
|
287
|
+
expectedRevision,
|
|
288
|
+
newContent,
|
|
289
|
+
operationId: "",
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
this.storage.stageChange(change);
|
|
293
|
+
return change;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get staged change for a page
|
|
298
|
+
*/
|
|
299
|
+
getStagedChange(pageId: string): StagedChange | undefined {
|
|
300
|
+
requireWiki();
|
|
301
|
+
return this.storage.getStagedChange(pageId);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Clear staged change
|
|
306
|
+
*/
|
|
307
|
+
clearStagedChange(pageId: string): void {
|
|
308
|
+
requireWiki();
|
|
309
|
+
this.storage.clearStagedChange(pageId);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ========================================================================
|
|
313
|
+
// Apply Changes
|
|
314
|
+
// ========================================================================
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Apply staged change with conflict detection
|
|
318
|
+
*/
|
|
319
|
+
applyChange(pageId: string, requestId?: string): WikiPage {
|
|
320
|
+
requireWiki();
|
|
321
|
+
|
|
322
|
+
const staged = this.storage.getStagedChange(pageId);
|
|
323
|
+
if (!staged) {
|
|
324
|
+
throw new Error(`No staged change for page: ${pageId}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const existing = this.storage.loadPage(pageId);
|
|
328
|
+
if (!existing) {
|
|
329
|
+
throw new Error(`Page not found: ${pageId}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Check for conflicts
|
|
333
|
+
const currentRevision = computeHash(existing.rawFrontmatter || "");
|
|
334
|
+
if (staged.expectedRevision !== currentRevision) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Conflict: page was modified. Expected revision ${staged.expectedRevision}, found ${currentRevision}`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Parse new content
|
|
341
|
+
const parsed = parseFrontmatter(staged.newContent);
|
|
342
|
+
if (!parsed.frontmatter) {
|
|
343
|
+
throw new Error("Invalid page content: missing frontmatter");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Create updated page
|
|
347
|
+
const page: WikiPage = {
|
|
348
|
+
frontmatter: {
|
|
349
|
+
...parsed.frontmatter,
|
|
350
|
+
id: pageId,
|
|
351
|
+
updatedAt: now(),
|
|
352
|
+
} as WikiPageFrontmatter,
|
|
353
|
+
body: parsed.body,
|
|
354
|
+
rawFrontmatter: parsed.raw,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
this.storage.savePage(page);
|
|
358
|
+
this.storage.clearStagedChange(pageId);
|
|
359
|
+
|
|
360
|
+
return page;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ========================================================================
|
|
364
|
+
// Lint
|
|
365
|
+
// ========================================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Run lint checks on wiki
|
|
369
|
+
*/
|
|
370
|
+
lint(scope?: "all" | "pages" | "links" | "freshness"): LintFinding[] {
|
|
371
|
+
requireWiki();
|
|
372
|
+
|
|
373
|
+
const findings: LintFinding[] = [];
|
|
374
|
+
|
|
375
|
+
if (scope === undefined || scope === "all" || scope === "pages") {
|
|
376
|
+
findings.push(...this.lintPages());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (scope === undefined || scope === "all" || scope === "links") {
|
|
380
|
+
findings.push(...this.lintLinks());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (scope === undefined || scope === "all" || scope === "freshness") {
|
|
384
|
+
findings.push(...this.lintFreshness());
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return findings;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Lint page structure
|
|
392
|
+
*/
|
|
393
|
+
private lintPages(): LintFinding[] {
|
|
394
|
+
const findings: LintFinding[] = [];
|
|
395
|
+
const pages = this.storage.getAllPages();
|
|
396
|
+
|
|
397
|
+
for (const page of pages) {
|
|
398
|
+
// Check required frontmatter fields
|
|
399
|
+
if (!page.frontmatter.id) {
|
|
400
|
+
findings.push({
|
|
401
|
+
severity: "error",
|
|
402
|
+
category: "structure",
|
|
403
|
+
pageId: page.frontmatter.id,
|
|
404
|
+
message: "Missing id in frontmatter",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!page.frontmatter.title) {
|
|
409
|
+
findings.push({
|
|
410
|
+
severity: "error",
|
|
411
|
+
category: "structure",
|
|
412
|
+
pageId: page.frontmatter.id,
|
|
413
|
+
message: "Missing title in frontmatter",
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!page.frontmatter.kind) {
|
|
418
|
+
findings.push({
|
|
419
|
+
severity: "error",
|
|
420
|
+
category: "structure",
|
|
421
|
+
pageId: page.frontmatter.id,
|
|
422
|
+
message: "Missing kind in frontmatter",
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Check for missing sources in non-reference pages
|
|
427
|
+
if (
|
|
428
|
+
page.frontmatter.kind !== "reference" &&
|
|
429
|
+
page.frontmatter.sources.length === 0
|
|
430
|
+
) {
|
|
431
|
+
findings.push({
|
|
432
|
+
severity: "warning",
|
|
433
|
+
category: "evidence",
|
|
434
|
+
pageId: page.frontmatter.id,
|
|
435
|
+
message: "Page has no source citations",
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Check for verified status without evidence
|
|
440
|
+
if (page.frontmatter.status === "verified" && !page.frontmatter.verification) {
|
|
441
|
+
findings.push({
|
|
442
|
+
severity: "warning",
|
|
443
|
+
category: "evidence",
|
|
444
|
+
pageId: page.frontmatter.id,
|
|
445
|
+
message: "Page has 'verified' status but no verification evidence",
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return findings;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Lint link validity
|
|
455
|
+
*/
|
|
456
|
+
private lintLinks(): LintFinding[] {
|
|
457
|
+
const findings: LintFinding[] = [];
|
|
458
|
+
const pages = this.storage.getAllPages();
|
|
459
|
+
const pageIds = new Set(pages.map((p) => p.frontmatter.id));
|
|
460
|
+
|
|
461
|
+
for (const page of pages) {
|
|
462
|
+
// Check related pages exist
|
|
463
|
+
for (const relatedId of page.frontmatter.relatedPages) {
|
|
464
|
+
if (!pageIds.has(relatedId)) {
|
|
465
|
+
findings.push({
|
|
466
|
+
severity: "warning",
|
|
467
|
+
category: "links",
|
|
468
|
+
pageId: page.frontmatter.id,
|
|
469
|
+
message: `Related page not found: ${relatedId}`,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Check source snapshots exist
|
|
475
|
+
for (const source of page.frontmatter.sources) {
|
|
476
|
+
if (!this.storage.getSnapshot(source.sourceId)) {
|
|
477
|
+
findings.push({
|
|
478
|
+
severity: "warning",
|
|
479
|
+
category: "links",
|
|
480
|
+
pageId: page.frontmatter.id,
|
|
481
|
+
message: `Source snapshot not found: ${source.sourceId}`,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return findings;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Lint freshness
|
|
492
|
+
*/
|
|
493
|
+
private lintFreshness(): LintFinding[] {
|
|
494
|
+
const findings: LintFinding[] = [];
|
|
495
|
+
const pages = this.storage.getAllPages();
|
|
496
|
+
const nowTime = Date.now();
|
|
497
|
+
const staleThreshold = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
498
|
+
|
|
499
|
+
for (const page of pages) {
|
|
500
|
+
const updatedTime = new Date(page.frontmatter.updatedAt).getTime();
|
|
501
|
+
const age = nowTime - updatedTime;
|
|
502
|
+
|
|
503
|
+
// Flag old pages without verification
|
|
504
|
+
if (
|
|
505
|
+
page.frontmatter.status !== "verified" &&
|
|
506
|
+
page.frontmatter.status !== "superseded" &&
|
|
507
|
+
age > staleThreshold
|
|
508
|
+
) {
|
|
509
|
+
findings.push({
|
|
510
|
+
severity: "info",
|
|
511
|
+
category: "freshness",
|
|
512
|
+
pageId: page.frontmatter.id,
|
|
513
|
+
message: `Page has not been updated in ${Math.floor(age / (24 * 60 * 60 * 1000))} days`,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Check if sources have changed
|
|
518
|
+
for (const source of page.frontmatter.sources) {
|
|
519
|
+
const snapshot = this.storage.getSnapshot(source.sourceId);
|
|
520
|
+
if (snapshot) {
|
|
521
|
+
const snapTime = new Date(snapshot.capturedAt).getTime();
|
|
522
|
+
if (snapTime < updatedTime) {
|
|
523
|
+
findings.push({
|
|
524
|
+
severity: "warning",
|
|
525
|
+
category: "freshness",
|
|
526
|
+
pageId: page.frontmatter.id,
|
|
527
|
+
message: `Source ${source.sourceId} was captured before page was last updated`,
|
|
528
|
+
suggestion: "Consider re-ingesting source to verify content is current",
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return findings;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ========================================================================
|
|
539
|
+
// Operations
|
|
540
|
+
// ========================================================================
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Get recent operations
|
|
544
|
+
*/
|
|
545
|
+
getRecentOperations(limit = 50): WikiOperation[] {
|
|
546
|
+
requireWiki();
|
|
547
|
+
return this.storage.getRecentOperations(limit);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ============================================================================
|
|
552
|
+
// Factory
|
|
553
|
+
// ============================================================================
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Create wiki manager for a workspace
|
|
557
|
+
*/
|
|
558
|
+
export function createWikiManager(workspaceRoot: string, projectId: string): WikiManager {
|
|
559
|
+
const storage = createWikiStorage(workspaceRoot, projectId);
|
|
560
|
+
return new WikiManager(storage);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Re-export storage class
|
|
564
|
+
export { WikiStorage, createWikiStorage } from "./storage.js";
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Check if wiki exists in workspace
|
|
568
|
+
*/
|
|
569
|
+
export function wikiExists(workspaceRoot: string): boolean {
|
|
570
|
+
const wikiRoot = join(workspaceRoot, "wiki");
|
|
571
|
+
return existsSync(join(wikiRoot, "knowledge"));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ============================================================================
|
|
575
|
+
// Exports
|
|
576
|
+
// ============================================================================
|
|
577
|
+
|
|
578
|
+
export * from "./types.js";
|