pi-harness-runtime 1.1.327 → 1.1.328
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/package.json +1 -1
- 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.map +1 -1
- package/packages/wiki/dist/index.js +35 -20
- package/packages/wiki/dist/index.js.map +1 -1
- package/packages/wiki/dist/storage.d.ts +27 -5
- package/packages/wiki/dist/storage.d.ts.map +1 -1
- package/packages/wiki/dist/storage.js +149 -39
- package/packages/wiki/dist/storage.js.map +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/wiki/src/index.ts +41 -23
- package/packages/wiki/src/storage.ts +164 -36
- 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
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
readdirSync,
|
|
15
15
|
statSync,
|
|
16
16
|
} from "node:fs";
|
|
17
|
-
import { join, dirname, relative } from "node:path";
|
|
17
|
+
import { join, dirname, relative, resolve } from "node:path";
|
|
18
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
19
19
|
import type {
|
|
20
20
|
WikiConfig,
|
|
@@ -76,24 +76,75 @@ export function parseFrontmatter(
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Simple YAML parser for frontmatter
|
|
79
|
-
* Handles: strings, booleans, numbers, arrays, nested objects
|
|
79
|
+
* Handles: strings, booleans, numbers, arrays, nested objects, YAML list items
|
|
80
80
|
*/
|
|
81
81
|
function parseYaml(yamlStr: string): Record<string, unknown> {
|
|
82
82
|
const result: Record<string, unknown> = {};
|
|
83
83
|
const lines = yamlStr.split("\n");
|
|
84
84
|
const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [];
|
|
85
85
|
|
|
86
|
-
for (
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
const line = lines[i];
|
|
87
88
|
const trimmed = line.trim();
|
|
88
89
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
89
90
|
|
|
90
91
|
const indent = line.search(/\S/);
|
|
91
|
-
const colonIdx = trimmed.indexOf(":");
|
|
92
92
|
|
|
93
|
+
// Handle YAML list items (lines starting with '-')
|
|
94
|
+
if (trimmed.startsWith("-")) {
|
|
95
|
+
const itemContent = trimmed.slice(1).trim();
|
|
96
|
+
const colonIdx = itemContent.indexOf(":");
|
|
97
|
+
|
|
98
|
+
if (colonIdx !== -1) {
|
|
99
|
+
// This is a list item with key-value pairs on same line or next lines
|
|
100
|
+
const key = itemContent.slice(0, colonIdx).trim();
|
|
101
|
+
const value = itemContent.slice(colonIdx + 1).trim();
|
|
102
|
+
|
|
103
|
+
// Pop stack while indent decreases
|
|
104
|
+
while (stack.length > 0 && indent < stack[stack.length - 1].indent) {
|
|
105
|
+
stack.pop();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find or create the array
|
|
109
|
+
const parent = stack.length > 0 ? stack[stack.length - 1].obj : result;
|
|
110
|
+
const arrayKey = parent ? Object.keys(parent).slice(-1)[0] : undefined;
|
|
111
|
+
let array: unknown[];
|
|
112
|
+
|
|
113
|
+
if (arrayKey && Array.isArray(parent[arrayKey])) {
|
|
114
|
+
array = parent[arrayKey] as unknown[];
|
|
115
|
+
} else {
|
|
116
|
+
// Start a new array
|
|
117
|
+
const newArray: unknown[] = [];
|
|
118
|
+
if (parent) {
|
|
119
|
+
parent[arrayKey || key] = newArray;
|
|
120
|
+
} else {
|
|
121
|
+
result[key] = newArray;
|
|
122
|
+
}
|
|
123
|
+
array = newArray;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Create new object for this list item
|
|
127
|
+
const item: Record<string, unknown> = {};
|
|
128
|
+
array.push(item);
|
|
129
|
+
|
|
130
|
+
// Parse the key-value on this line
|
|
131
|
+
const parsedValue = parseValue(value);
|
|
132
|
+
if (parsedValue !== undefined) {
|
|
133
|
+
item[key] = parsedValue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Push this item to stack for nested properties
|
|
137
|
+
stack.push({ obj: item, indent });
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Regular key-value line
|
|
143
|
+
const colonIdx = trimmed.indexOf(":");
|
|
93
144
|
if (colonIdx === -1) continue;
|
|
94
145
|
|
|
95
146
|
const key = trimmed.slice(0, colonIdx).trim();
|
|
96
|
-
|
|
147
|
+
const rawValue = trimmed.slice(colonIdx + 1).trim();
|
|
97
148
|
|
|
98
149
|
// Pop stack while indent decreases
|
|
99
150
|
while (stack.length > 0 && indent <= stack[stack.length - 1].indent) {
|
|
@@ -102,32 +153,41 @@ function parseYaml(yamlStr: string): Record<string, unknown> {
|
|
|
102
153
|
|
|
103
154
|
const parent = stack.length > 0 ? stack[stack.length - 1].obj : result;
|
|
104
155
|
|
|
105
|
-
//
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} else if (/^-?\d+(\.\d+)?$/.test(value as string)) {
|
|
113
|
-
value = parseFloat(value as string);
|
|
114
|
-
} else if ((value as string).startsWith("[") && (value as string).endsWith("]")) {
|
|
115
|
-
// Array
|
|
116
|
-
const arrContent = (value as string).slice(1, -1);
|
|
117
|
-
value = arrContent
|
|
118
|
-
? arrContent.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
119
|
-
: [];
|
|
120
|
-
} else {
|
|
121
|
-
// String - remove quotes
|
|
122
|
-
value = (value as string).replace(/^["']|["']$/g, "");
|
|
156
|
+
// Check if value is empty (nested content follows)
|
|
157
|
+
if (rawValue === "" || rawValue === "~" || rawValue === "null") {
|
|
158
|
+
// Nested content follows - create placeholder
|
|
159
|
+
const nested: Record<string, unknown> = {};
|
|
160
|
+
parent[key] = nested;
|
|
161
|
+
stack.push({ obj: nested, indent });
|
|
162
|
+
continue;
|
|
123
163
|
}
|
|
124
164
|
|
|
125
|
-
parent[key] =
|
|
165
|
+
parent[key] = parseValue(rawValue);
|
|
126
166
|
}
|
|
127
167
|
|
|
128
168
|
return result;
|
|
129
169
|
}
|
|
130
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Parse a YAML scalar value or array
|
|
173
|
+
*/
|
|
174
|
+
type YamlValue = string | number | boolean | null | string[];
|
|
175
|
+
function parseValue(value: string): YamlValue {
|
|
176
|
+
if (value === "" || value === "null" || value === "~") {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (value === "true") return true;
|
|
180
|
+
if (value === "false") return false;
|
|
181
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
182
|
+
return parseFloat(value);
|
|
183
|
+
}
|
|
184
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
185
|
+
const content = value.slice(1, -1);
|
|
186
|
+
return content ? content.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")) : [];
|
|
187
|
+
}
|
|
188
|
+
return value.replace(/^["']|["']$/g, "");
|
|
189
|
+
}
|
|
190
|
+
|
|
131
191
|
/**
|
|
132
192
|
* Serialize frontmatter to YAML string
|
|
133
193
|
*/
|
|
@@ -200,13 +260,35 @@ export class WikiStorage {
|
|
|
200
260
|
private indexMtime = 0; // Track when index was last loaded
|
|
201
261
|
private operations: WikiOperation[] = [];
|
|
202
262
|
private stagedChanges: Map<string, StagedChange> = new Map();
|
|
263
|
+
private initialized = false;
|
|
203
264
|
|
|
204
265
|
constructor(config: WikiConfig) {
|
|
205
266
|
this.config = config;
|
|
206
|
-
|
|
267
|
+
// Don't create directories until explicitly initialized
|
|
268
|
+
// This prevents writes when the feature is disabled
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Initialize storage (call before any read/write operations)
|
|
273
|
+
* This is called automatically by WikiManager when feature is enabled
|
|
274
|
+
*/
|
|
275
|
+
/**
|
|
276
|
+
* Prepare storage for use (load indexes and operations)
|
|
277
|
+
* Called after directories are ensured by initialize()
|
|
278
|
+
*/
|
|
279
|
+
private prepareStorage(): void {
|
|
280
|
+
if (this.initialized) return;
|
|
207
281
|
this.loadIndex();
|
|
208
282
|
this.loadOperations();
|
|
209
283
|
this.loadStaged();
|
|
284
|
+
this.initialized = true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if storage is initialized
|
|
289
|
+
*/
|
|
290
|
+
isInitialized(): boolean {
|
|
291
|
+
return this.initialized;
|
|
210
292
|
}
|
|
211
293
|
|
|
212
294
|
/**
|
|
@@ -303,15 +385,6 @@ verification:
|
|
|
303
385
|
};
|
|
304
386
|
}
|
|
305
387
|
|
|
306
|
-
/**
|
|
307
|
-
* Ensure directory structure exists
|
|
308
|
-
*/
|
|
309
|
-
private ensureStructure(): void {
|
|
310
|
-
if (!existsSync(this.config.wikiRoot)) {
|
|
311
|
-
mkdirSync(this.config.wikiRoot, { recursive: true });
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
388
|
/**
|
|
316
389
|
* Load page index from disk (if changed since last load)
|
|
317
390
|
*/
|
|
@@ -349,16 +422,44 @@ verification:
|
|
|
349
422
|
}
|
|
350
423
|
|
|
351
424
|
/**
|
|
352
|
-
* Save page index to disk (atomic)
|
|
425
|
+
* Save page index to disk (atomic, merges with existing entries)
|
|
353
426
|
*/
|
|
354
427
|
private saveIndex(): void {
|
|
355
|
-
|
|
428
|
+
// Reload from disk to merge (handles concurrent writes)
|
|
429
|
+
const diskEntries = this.loadIndexFromDisk();
|
|
430
|
+
const merged = new Map<string, PageIndexEntry>(diskEntries);
|
|
431
|
+
|
|
432
|
+
// Apply in-memory changes
|
|
433
|
+
for (const [id, entry] of this.index) {
|
|
434
|
+
merged.set(id, entry);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const entries = Array.from(merged.values());
|
|
356
438
|
const content = JSON.stringify(entries, null, 2);
|
|
357
439
|
const tmpFile = this.config.indexFile + ".tmp";
|
|
358
440
|
writeFileSync(tmpFile, content, "utf8");
|
|
359
441
|
renameSync(tmpFile, this.config.indexFile);
|
|
360
442
|
}
|
|
361
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Load index from disk only (no cache)
|
|
446
|
+
*/
|
|
447
|
+
private loadIndexFromDisk(): Map<string, PageIndexEntry> {
|
|
448
|
+
const result = new Map<string, PageIndexEntry>();
|
|
449
|
+
if (!existsSync(this.config.indexFile)) return result;
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const content = readFileSync(this.config.indexFile, "utf8");
|
|
453
|
+
const entries: PageIndexEntry[] = JSON.parse(content);
|
|
454
|
+
for (const entry of entries) {
|
|
455
|
+
result.set(entry.id, entry);
|
|
456
|
+
}
|
|
457
|
+
} catch {
|
|
458
|
+
// Invalid index, return empty
|
|
459
|
+
}
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
|
|
362
463
|
/**
|
|
363
464
|
* Load operations from log
|
|
364
465
|
*/
|
|
@@ -455,7 +556,9 @@ verification:
|
|
|
455
556
|
* Get source snapshot by ID
|
|
456
557
|
*/
|
|
457
558
|
getSnapshot(snapshotId: string): SourceSnapshot | null {
|
|
559
|
+
this.validateSnapshotId(snapshotId);
|
|
458
560
|
const snapFile = join(this.config.rawDir, `${snapshotId}.json`);
|
|
561
|
+
this.ensureWithinDirectory(snapFile, this.config.rawDir);
|
|
459
562
|
if (!existsSync(snapFile)) return null;
|
|
460
563
|
|
|
461
564
|
try {
|
|
@@ -474,8 +577,10 @@ verification:
|
|
|
474
577
|
* Save a wiki page
|
|
475
578
|
*/
|
|
476
579
|
savePage(page: WikiPage): void {
|
|
580
|
+
this.validatePageId(page.frontmatter.id);
|
|
477
581
|
const content = combinePage(page.frontmatter, page.body);
|
|
478
582
|
const pageFile = join(this.config.knowledgeDir, `${page.frontmatter.id}.md`);
|
|
583
|
+
this.ensureWithinDirectory(pageFile, this.config.knowledgeDir);
|
|
479
584
|
const tmpFile = pageFile + ".tmp";
|
|
480
585
|
writeFileSync(tmpFile, content, "utf8");
|
|
481
586
|
renameSync(tmpFile, pageFile);
|
|
@@ -499,7 +604,9 @@ verification:
|
|
|
499
604
|
* Load a wiki page
|
|
500
605
|
*/
|
|
501
606
|
loadPage(pageId: string): WikiPage | null {
|
|
607
|
+
this.validatePageId(pageId);
|
|
502
608
|
const pageFile = join(this.config.knowledgeDir, `${pageId}.md`);
|
|
609
|
+
this.ensureWithinDirectory(pageFile, this.config.knowledgeDir);
|
|
503
610
|
if (!existsSync(pageFile)) return null;
|
|
504
611
|
|
|
505
612
|
const content = readFileSync(pageFile, "utf8");
|
|
@@ -520,6 +627,27 @@ verification:
|
|
|
520
627
|
}
|
|
521
628
|
}
|
|
522
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Validate snapshot ID to prevent path traversal attacks
|
|
632
|
+
*/
|
|
633
|
+
private validateSnapshotId(snapshotId: string): void {
|
|
634
|
+
// Reject paths with directory separators or parent references
|
|
635
|
+
if (snapshotId.includes("/") || snapshotId.includes("\\") || snapshotId.includes("..")) {
|
|
636
|
+
throw new Error(`Invalid snapshot ID: ${snapshotId}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Ensure a path is within the expected directory (defense in depth)
|
|
642
|
+
*/
|
|
643
|
+
private ensureWithinDirectory(filePath: string, expectedDir: string): void {
|
|
644
|
+
const resolved = resolve(filePath);
|
|
645
|
+
const expected = resolve(expectedDir);
|
|
646
|
+
if (!resolved.startsWith(expected + "/") && resolved !== expected) {
|
|
647
|
+
throw new Error(`Path ${filePath} is outside expected directory ${expectedDir}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
523
651
|
/**
|
|
524
652
|
* Delete a wiki page
|
|
525
653
|
*/
|