pi-orchestrator-config 4.10.0 → 4.11.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/extensions/orchestrator/dreaming.ts +2 -2
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/memory-embeddings.ts +3 -2
- package/extensions/orchestrator/memory-promotion.ts +2 -1
- package/extensions/orchestrator/memory-provenance.ts +3 -2
- package/extensions/orchestrator/memory-scoring.ts +4 -3
- package/extensions/orchestrator/memory-tools.ts +3 -2
- package/extensions/orchestrator/memory-tree.ts +2 -1
- package/extensions/orchestrator/memory-write.ts +5 -4
- package/extensions/orchestrator/pi-config-review-state.ts +14 -6
- package/extensions/orchestrator/project-settings.ts +4 -0
- package/extensions/orchestrator/promotion-queue.ts +2 -1
- package/extensions/orchestrator/review-learning.ts +1228 -0
- package/extensions/orchestrator/situation-report.ts +3 -2
- package/extensions/orchestrator/utils.ts +67 -16
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/package.json +1 -1
- package/settings-keys.json +7 -0
|
@@ -26,7 +26,7 @@ import { setSlot } from "./status-bar.js";
|
|
|
26
26
|
import { rebuildAndOrganize } from "./situation-report.js";
|
|
27
27
|
import { runPromotionPass } from "./memory-promotion.js";
|
|
28
28
|
import { mergeProvenancePending } from "./memory-provenance.js";
|
|
29
|
-
import { getProjectTmpDir, shouldSkipOneshotShutdownDream } from "./utils.js";
|
|
29
|
+
import { getProjectTmpDir, getProjectMemoryDir, shouldSkipOneshotShutdownDream } from "./utils.js";
|
|
30
30
|
import { fileLog } from "../shared/file-logger.js";
|
|
31
31
|
|
|
32
32
|
// Scoring rebuild runs every 30 minutes (cheap, no LLM — just rescores and reorganizes)
|
|
@@ -101,7 +101,7 @@ export function registerDreaming(
|
|
|
101
101
|
updateDreamStatus();
|
|
102
102
|
currentDreamId = ""; // Reset until we get the ID from spawnAsyncAgent
|
|
103
103
|
const { agents } = discoverAgents(cwd, "user");
|
|
104
|
-
const topicsDir = path.join(cwd, "
|
|
104
|
+
const topicsDir = path.join(getProjectMemoryDir(cwd), "topics");
|
|
105
105
|
const { id } = spawnAsyncAgent(
|
|
106
106
|
"worker",
|
|
107
107
|
`Memory dreaming — analyze session and maintain topic files.\n` +
|
|
@@ -37,6 +37,7 @@ import { registerSessionSearch } from "./session-search.js";
|
|
|
37
37
|
import { registerSettingsTui } from "./settings-tui.js";
|
|
38
38
|
import { registerMarkdownTransformer } from "./markdown-transformer.js";
|
|
39
39
|
import { registerGraft } from "./graft.js";
|
|
40
|
+
import { registerReviewLearning } from "./review-learning.js";
|
|
40
41
|
import { ensureGitSshTimeout, isRunningInContainer, terminalNotify } from "./utils.js";
|
|
41
42
|
import { createLogger } from "../shared/logger.js";
|
|
42
43
|
import { registerProviderStreamLog } from "../shared/provider-stream-log.js";
|
|
@@ -112,6 +113,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
112
113
|
registerNvim(pi);
|
|
113
114
|
registerPreferenceExtractor(pi);
|
|
114
115
|
registerMemoryTools(pi);
|
|
116
|
+
registerReviewLearning(pi);
|
|
115
117
|
registerSessionSearch(pi);
|
|
116
118
|
registerSettingsTui(pi);
|
|
117
119
|
registerMarkdownTransformer(pi);
|
|
@@ -14,6 +14,7 @@ import { join } from "node:path";
|
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
15
|
import { createCachedStore } from "./state-jsonl.js";
|
|
16
16
|
import type { JsonlStateStore } from "./state-jsonl.js";
|
|
17
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
17
18
|
|
|
18
19
|
const EMBEDDING_DIM = 384;
|
|
19
20
|
export const EMBEDDING_POOLING = "mean";
|
|
@@ -42,12 +43,12 @@ const LEGACY_STORE_FILENAME = "embeddings.json";
|
|
|
42
43
|
|
|
43
44
|
function getEmbeddingStore(cwd: string): JsonlStateStore<EmbeddingStore> {
|
|
44
45
|
return createCachedStore<EmbeddingStore>(
|
|
45
|
-
|
|
46
|
+
getProjectMemoryDir(cwd), STORE_FILENAME_JSONL, LEGACY_STORE_FILENAME, { compactThreshold: 30 },
|
|
46
47
|
);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function getStorePath(cwd: string): string {
|
|
50
|
-
return join(cwd,
|
|
51
|
+
return join(getProjectMemoryDir(cwd), STORE_FILENAME_JSONL);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function loadStore(cwd: string): EmbeddingStore {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { createLogger } from "../shared/logger.js";
|
|
12
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
12
13
|
import {
|
|
13
14
|
type EnforcementAction,
|
|
14
15
|
type EnforcementTrigger,
|
|
@@ -159,7 +160,7 @@ export function markTopicEntryEnforced(
|
|
|
159
160
|
): boolean {
|
|
160
161
|
const topicName = CATEGORY_TO_TOPIC[category];
|
|
161
162
|
if (!topicName) return false;
|
|
162
|
-
const topicPath = join(cwd, "
|
|
163
|
+
const topicPath = join(getProjectMemoryDir(cwd), "topics", `${topicName}.md`);
|
|
163
164
|
if (!existsSync(topicPath)) return false;
|
|
164
165
|
|
|
165
166
|
let content = readFileSync(topicPath, "utf-8");
|
|
@@ -13,6 +13,7 @@ import { join } from "node:path";
|
|
|
13
13
|
import { entryHash, mutateScores, type MemoryCategory } from "./memory-scoring.js";
|
|
14
14
|
import { createCachedStore } from "./state-jsonl.js";
|
|
15
15
|
import type { JsonlStateStore } from "./state-jsonl.js";
|
|
16
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
16
17
|
|
|
17
18
|
export interface ProvenancePendingItem {
|
|
18
19
|
category: MemoryCategory | string;
|
|
@@ -31,12 +32,12 @@ const LEGACY_PROVENANCE_FILENAME = "provenance-pending.json";
|
|
|
31
32
|
|
|
32
33
|
function getProvenanceStore(cwd: string): JsonlStateStore<ProvenancePendingFile> {
|
|
33
34
|
return createCachedStore<ProvenancePendingFile>(
|
|
34
|
-
|
|
35
|
+
getProjectMemoryDir(cwd), PROVENANCE_FILENAME_JSONL, LEGACY_PROVENANCE_FILENAME,
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function getProvenancePendingPath(cwd: string): string {
|
|
39
|
-
return join(cwd,
|
|
40
|
+
return join(getProjectMemoryDir(cwd), PROVENANCE_FILENAME_JSONL);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function writeProvenancePending(
|
|
@@ -16,6 +16,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
16
16
|
import { createCachedStore } from "./state-jsonl.js";
|
|
17
17
|
import type { JsonlStateStore } from "./state-jsonl.js";
|
|
18
18
|
import { getSetting } from "./project-settings.js";
|
|
19
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
19
20
|
|
|
20
21
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
21
22
|
|
|
@@ -246,12 +247,12 @@ const LEGACY_SCORES_FILENAME = "memory-scores.json";
|
|
|
246
247
|
|
|
247
248
|
function getScoresStore(cwd: string): JsonlStateStore<ScoresFile> {
|
|
248
249
|
return createCachedStore<ScoresFile>(
|
|
249
|
-
|
|
250
|
+
getProjectMemoryDir(cwd), SCORES_FILENAME_JSONL, LEGACY_SCORES_FILENAME, { compactThreshold: 50 },
|
|
250
251
|
);
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
export function getScoresPath(cwd: string): string {
|
|
254
|
-
return join(cwd,
|
|
255
|
+
return join(getProjectMemoryDir(cwd), SCORES_FILENAME_JSONL);
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
/**
|
|
@@ -320,7 +321,7 @@ export function loadScores(cwd: string): ScoresFile {
|
|
|
320
321
|
* imports loadScores/entryHash from this module).
|
|
321
322
|
*/
|
|
322
323
|
function parseTopicLinesForMigration(cwd: string): { category: string; text: string }[] {
|
|
323
|
-
const topicsDir = join(cwd, "
|
|
324
|
+
const topicsDir = join(getProjectMemoryDir(cwd), "topics");
|
|
324
325
|
const out: { category: string; text: string }[] = [];
|
|
325
326
|
if (!existsSync(topicsDir)) return out;
|
|
326
327
|
try {
|
|
@@ -17,6 +17,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
17
17
|
import { Type } from "typebox";
|
|
18
18
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
20
21
|
import {
|
|
21
22
|
loadScores,
|
|
22
23
|
entryHash,
|
|
@@ -365,7 +366,7 @@ function registerMemoryRemove(pi: ExtensionAPI): void {
|
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
const topicName = CATEGORY_TO_TOPIC[category as keyof typeof CATEGORY_TO_TOPIC];
|
|
368
|
-
const topicPath = join(cwd, "
|
|
369
|
+
const topicPath = join(getProjectMemoryDir(cwd), "topics", `${topicName}.md`);
|
|
369
370
|
|
|
370
371
|
if (!existsSync(topicPath)) {
|
|
371
372
|
return { content: [{ type: "text", text: `Topic file not found for category "${category}".` }] };
|
|
@@ -517,7 +518,7 @@ function registerMemoryEdit(pi: ExtensionAPI): void {
|
|
|
517
518
|
}
|
|
518
519
|
|
|
519
520
|
const topicName = CATEGORY_TO_TOPIC[category as keyof typeof CATEGORY_TO_TOPIC];
|
|
520
|
-
const topicPath = join(cwd, "
|
|
521
|
+
const topicPath = join(getProjectMemoryDir(cwd), "topics", `${topicName}.md`);
|
|
521
522
|
|
|
522
523
|
if (!existsSync(topicPath)) {
|
|
523
524
|
return { content: [{ type: "text", text: `Topic file not found for category "${category}".` }] };
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { createLogger } from "../shared/logger.js";
|
|
14
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
14
15
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
15
16
|
import { join } from "node:path";
|
|
16
17
|
import {
|
|
@@ -58,7 +59,7 @@ export interface TopicEntry {
|
|
|
58
59
|
// ── Topic Directory ────────────────────────────────────────────────────────
|
|
59
60
|
|
|
60
61
|
function getTopicsDir(cwd: string): string {
|
|
61
|
-
return join(cwd, "
|
|
62
|
+
return join(getProjectMemoryDir(cwd), "topics");
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// ── Topic File I/O ─────────────────────────────────────────────────────────
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { createLogger } from "../shared/logger.js";
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
22
23
|
import {
|
|
23
24
|
type MemoryCategory,
|
|
24
25
|
type MemoryScope,
|
|
@@ -298,7 +299,7 @@ function supersedeEntry(cwd: string, category: string, text: string): void {
|
|
|
298
299
|
try {
|
|
299
300
|
const topicName = CATEGORY_TO_TOPIC[category as MemoryCategory];
|
|
300
301
|
if (!topicName) return;
|
|
301
|
-
const topicPath = join(cwd, "
|
|
302
|
+
const topicPath = join(getProjectMemoryDir(cwd), "topics", `${topicName}.md`);
|
|
302
303
|
if (!existsSync(topicPath)) return;
|
|
303
304
|
const content = readFileSync(topicPath, "utf-8");
|
|
304
305
|
const match = findTopicLine(content, category, text);
|
|
@@ -334,7 +335,7 @@ function supersedeEntry(cwd: string, category: string, text: string): void {
|
|
|
334
335
|
function topicCapRejection(cwd: string, category: string, entryLine: string): AddMemoryResult | null {
|
|
335
336
|
const topicName = CATEGORY_TO_TOPIC[category as MemoryCategory];
|
|
336
337
|
if (!topicName) return null;
|
|
337
|
-
const topicPath = join(cwd, "
|
|
338
|
+
const topicPath = join(getProjectMemoryDir(cwd), "topics", `${topicName}.md`);
|
|
338
339
|
let content = "";
|
|
339
340
|
if (existsSync(topicPath)) {
|
|
340
341
|
content = readFileSync(topicPath, "utf-8");
|
|
@@ -600,7 +601,7 @@ export async function addMemoryEntry(cwd: string, input: AddMemoryInput): Promis
|
|
|
600
601
|
// 5. Topic cap + write (cap precheck already ran in the override path;
|
|
601
602
|
// this re-check is a race safety net — another writer may have appended)
|
|
602
603
|
const topicName = CATEGORY_TO_TOPIC[category as MemoryCategory];
|
|
603
|
-
const topicsDir = join(cwd, "
|
|
604
|
+
const topicsDir = join(getProjectMemoryDir(cwd), "topics");
|
|
604
605
|
const topicPath = join(topicsDir, `${topicName}.md`);
|
|
605
606
|
|
|
606
607
|
if (!existsSync(topicsDir)) {
|
|
@@ -683,7 +684,7 @@ export async function addMemoryEntry(cwd: string, input: AddMemoryInput): Promis
|
|
|
683
684
|
* entries cannot outlive their scope (issue #915).
|
|
684
685
|
*/
|
|
685
686
|
export function pruneExpiredEntries(cwd: string): number {
|
|
686
|
-
const topicsDir = join(cwd, "
|
|
687
|
+
const topicsDir = join(getProjectMemoryDir(cwd), "topics");
|
|
687
688
|
if (!existsSync(topicsDir)) return 0;
|
|
688
689
|
const scores = loadScores(cwd);
|
|
689
690
|
const now = Date.now();
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* On first access, migrates any legacy pi-config-review-state.json automatically.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { getSetting } from "./project-settings.js";
|
|
16
16
|
import { createCachedStore } from "./state-jsonl.js";
|
|
17
17
|
import type { JsonlStateStore } from "./state-jsonl.js";
|
|
18
|
-
import { resolveWorktreeRoot
|
|
18
|
+
import { resolveWorktreeRoot } from "./utils.js";
|
|
19
19
|
|
|
20
20
|
type StateTransitionCallback = (state: ReviewState) => void;
|
|
21
21
|
let onTransitionCb: StateTransitionCallback | null = null;
|
|
@@ -48,13 +48,21 @@ export interface ReviewState {
|
|
|
48
48
|
const STATE_FILE_JSONL = "pi-config-review-state.jsonl";
|
|
49
49
|
const LEGACY_STATE_FILE = "pi-config-review-state.json";
|
|
50
50
|
|
|
51
|
+
/** Per-worktree data dir — deliberately NOT the shared getProjectDataDir:
|
|
52
|
+
* review state must stay per-worktree (one review loop per issue branch),
|
|
53
|
+
* so it joins the worktree root directly (issue #918 kept this per-cwd). */
|
|
54
|
+
function getWorktreeDataDir(cwd: string): string {
|
|
55
|
+
const dir = join(resolveWorktreeRoot(cwd), ".pi", "data");
|
|
56
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
function getStore(cwd: string): JsonlStateStore<ReviewState> {
|
|
52
|
-
|
|
53
|
-
return createCachedStore<ReviewState>(dataDir, STATE_FILE_JSONL, LEGACY_STATE_FILE);
|
|
61
|
+
return createCachedStore<ReviewState>(getWorktreeDataDir(cwd), STATE_FILE_JSONL, LEGACY_STATE_FILE);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
export function statePath(cwd: string): string {
|
|
57
|
-
return join(
|
|
65
|
+
return join(getWorktreeDataDir(cwd), STATE_FILE_JSONL);
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
function defaultState(): ReviewState {
|
|
@@ -108,7 +116,7 @@ function acquireLock(cwd: string): boolean {
|
|
|
108
116
|
const maxRetries = 100;
|
|
109
117
|
for (let i = 0; i < maxRetries; i++) {
|
|
110
118
|
try {
|
|
111
|
-
|
|
119
|
+
getWorktreeDataDir(cwd);
|
|
112
120
|
writeFileSync(lock, String(process.pid), { flag: "wx" });
|
|
113
121
|
return true;
|
|
114
122
|
} catch {
|
|
@@ -100,6 +100,8 @@ interface ProjectSettings {
|
|
|
100
100
|
pidiff_stale_lock_timeout_ms?: number;
|
|
101
101
|
/** Pidiff: daemon startup timeout in seconds. */
|
|
102
102
|
pidiff_daemon_startup_timeout_s?: number;
|
|
103
|
+
/** Distill addressed review findings into memory lessons after `reviews address`. Default: true. */
|
|
104
|
+
review_learning?: boolean;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/** Settings metadata is loaded from settings-keys.json by the cycle-free settings source. */
|
|
@@ -150,6 +152,7 @@ const PROJECT_SETTINGS_KEYS: (keyof ProjectSettings)[] = [
|
|
|
150
152
|
"coms_probe_timeout_ms",
|
|
151
153
|
"pidiff_stale_lock_timeout_ms",
|
|
152
154
|
"pidiff_daemon_startup_timeout_s",
|
|
155
|
+
"review_learning",
|
|
153
156
|
];
|
|
154
157
|
for (const key of PROJECT_SETTINGS_KEYS) {
|
|
155
158
|
if (!(key in SETTINGS_KEYS)) {
|
|
@@ -482,6 +485,7 @@ export function getSetting(cwd: string, key: "coms_entry_grace_period_ms"): numb
|
|
|
482
485
|
export function getSetting(cwd: string, key: "coms_probe_timeout_ms"): number;
|
|
483
486
|
export function getSetting(cwd: string, key: "pidiff_stale_lock_timeout_ms"): number;
|
|
484
487
|
export function getSetting(cwd: string, key: "pidiff_daemon_startup_timeout_s"): number;
|
|
488
|
+
export function getSetting(cwd: string, key: "review_learning"): boolean;
|
|
485
489
|
export function getSetting(cwd: string, key: string): boolean | string | number | string[] | Record<string, { provider?: string | null; model?: string | null }> {
|
|
486
490
|
const settings = getSettings(cwd);
|
|
487
491
|
const def = SETTINGS_KEYS[key];
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
|
+
import { getProjectMemoryDir } from "./utils.js";
|
|
11
12
|
|
|
12
13
|
export type PromotionDestination =
|
|
13
14
|
| "memory"
|
|
@@ -45,7 +46,7 @@ const DESTINATIONS: PromotionDestination[] = [
|
|
|
45
46
|
const STATUSES: PromotionStatus[] = ["proposed", "applied", "rejected"];
|
|
46
47
|
|
|
47
48
|
export function getPromotionsPath(cwd: string): string {
|
|
48
|
-
return join(cwd, "
|
|
49
|
+
return join(getProjectMemoryDir(cwd), "promotions.md");
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/** Stable id from destination + category + text (dedup key). */
|