portable-agent-layer 0.36.0 → 0.38.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/README.md +1 -0
- package/assets/skills/analyze-pdf/tools/pdf-download.ts +1 -1
- package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
- package/assets/skills/consulting-report/tools/dev.ts +2 -2
- package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
- package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
- package/assets/skills/opinion/tools/opinion.ts +3 -2
- package/assets/skills/presentation/SKILL.md +1 -1
- package/assets/skills/presentation/tools/doctor.ts +2 -5
- package/assets/skills/presentation/tools/lib/inline.ts +6 -11
- package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
- package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
- package/assets/skills/presentation/tools/setup-template.ts +10 -7
- package/assets/skills/projects/SKILL.md +44 -20
- package/assets/skills/research/tools/gemini-search.ts +2 -2
- package/assets/skills/research/tools/grok-search.ts +2 -2
- package/assets/skills/research/tools/perplexity-search.ts +2 -2
- package/assets/skills/telos/tools/update-telos.ts +0 -1
- package/assets/templates/PAL/ALGORITHM.md +27 -3
- package/assets/templates/hooks.codex.json +44 -0
- package/assets/templates/hooks.cursor.json +11 -5
- package/package.json +5 -2
- package/src/cli/index.ts +113 -17
- package/src/cli/migrate.ts +299 -0
- package/src/cli/setup-identity.ts +3 -3
- package/src/cli/setup-telos.ts +0 -1
- package/src/hooks/CompactRecover.ts +11 -5
- package/src/hooks/LoadContext.ts +14 -2
- package/src/hooks/PreCompactPersist.ts +26 -34
- package/src/hooks/SecurityValidator.ts +43 -21
- package/src/hooks/StopOrchestrator.ts +4 -1
- package/src/hooks/UserPromptOrchestrator.ts +4 -2
- package/src/hooks/handlers/auto-graduate.ts +2 -2
- package/src/hooks/handlers/backup.ts +3 -3
- package/src/hooks/handlers/failure.ts +5 -3
- package/src/hooks/handlers/inject-retrieval.ts +29 -6
- package/src/hooks/handlers/persist-last-exchange.ts +76 -0
- package/src/hooks/handlers/rating.ts +2 -1
- package/src/hooks/handlers/readme-sync.ts +3 -2
- package/src/hooks/handlers/session-intelligence.ts +9 -8
- package/src/hooks/handlers/session-name.ts +2 -2
- package/src/hooks/handlers/synthesis.ts +5 -2
- package/src/hooks/handlers/update-counts.ts +3 -2
- package/src/hooks/lib/agent.ts +20 -18
- package/src/hooks/lib/context.ts +45 -117
- package/src/hooks/lib/entities.ts +7 -7
- package/src/hooks/lib/frontmatter.ts +4 -4
- package/src/hooks/lib/graduation.ts +8 -7
- package/src/hooks/lib/inference.ts +6 -2
- package/src/hooks/lib/learning-category.ts +1 -1
- package/src/hooks/lib/learning-store.ts +6 -1
- package/src/hooks/lib/notify.ts +2 -2
- package/src/hooks/lib/opinions.ts +3 -3
- package/src/hooks/lib/paths.ts +2 -0
- package/src/hooks/lib/projects.ts +142 -74
- package/src/hooks/lib/readme-sync.ts +1 -1
- package/src/hooks/lib/relationship.ts +3 -15
- package/src/hooks/lib/retrieval-index.ts +5 -3
- package/src/hooks/lib/retrieval.ts +11 -12
- package/src/hooks/lib/security.ts +22 -18
- package/src/hooks/lib/semi-static.ts +4 -2
- package/src/hooks/lib/session-names.ts +1 -1
- package/src/hooks/lib/settings.ts +1 -1
- package/src/hooks/lib/setup.ts +2 -60
- package/src/hooks/lib/signals.ts +2 -2
- package/src/hooks/lib/stdin.ts +1 -1
- package/src/hooks/lib/stop.ts +13 -6
- package/src/hooks/lib/token-usage.ts +1 -2
- package/src/hooks/lib/transcript.ts +1 -1
- package/src/hooks/lib/wisdom.ts +5 -5
- package/src/hooks/lib/work-tracking.ts +13 -18
- package/src/targets/codex/install.ts +95 -0
- package/src/targets/codex/uninstall.ts +70 -0
- package/src/targets/lib.ts +140 -14
- package/src/targets/opencode/plugin.ts +22 -11
- package/src/tools/agent/algorithm-reflect.ts +1 -1
- package/src/tools/agent/analyze.ts +18 -18
- package/src/tools/agent/handoff-note.ts +1 -1
- package/src/tools/agent/project.ts +375 -75
- package/src/tools/agent/synthesize.ts +6 -42
- package/src/tools/agent/thread.ts +15 -14
- package/src/tools/agent/wisdom-frame.ts +9 -3
- package/src/tools/import.ts +1 -1
- package/src/tools/relationship-reflect.ts +13 -11
- package/src/tools/self-model.ts +20 -16
- package/src/tools/session-summary.ts +3 -3
- package/src/tools/token-cost.ts +15 -16
- package/assets/skills/telos/tools/update-projects.ts +0 -106
package/src/targets/lib.ts
CHANGED
|
@@ -81,7 +81,11 @@ type Settings = Record<string, unknown> & {
|
|
|
81
81
|
export function loadSettingsTemplate(templatePath: string, pkgRoot: string): Settings {
|
|
82
82
|
const raw = readFileSync(templatePath, "utf-8");
|
|
83
83
|
const resolved = raw.replaceAll("{{PKG_ROOT}}", pkgRoot);
|
|
84
|
-
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(resolved) as Settings;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
throw new Error(`Failed to parse settings template at ${templatePath}: ${e}`);
|
|
88
|
+
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
/**
|
|
@@ -95,7 +99,7 @@ export function mergeSettings(existing: Settings, template: Settings): Settings
|
|
|
95
99
|
|
|
96
100
|
// Merge hooks (deduplicate by command)
|
|
97
101
|
if (template.hooks) {
|
|
98
|
-
|
|
102
|
+
result.hooks ??= {};
|
|
99
103
|
for (const [event, entries] of Object.entries(template.hooks)) {
|
|
100
104
|
const current = result.hooks[event] ?? [];
|
|
101
105
|
for (const entry of entries) {
|
|
@@ -110,8 +114,8 @@ export function mergeSettings(existing: Settings, template: Settings): Settings
|
|
|
110
114
|
|
|
111
115
|
// Merge permissions.allow (deduplicate)
|
|
112
116
|
if (template.permissions?.allow) {
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
result.permissions ??= {};
|
|
118
|
+
result.permissions.allow ??= [];
|
|
115
119
|
for (const perm of template.permissions.allow) {
|
|
116
120
|
if (!result.permissions.allow.includes(perm)) {
|
|
117
121
|
result.permissions.allow.push(perm);
|
|
@@ -184,7 +188,11 @@ export function loadCursorHooksTemplate(
|
|
|
184
188
|
): CursorHooks {
|
|
185
189
|
const raw = readFileSync(templatePath, "utf-8");
|
|
186
190
|
const resolved = raw.replaceAll("{{PKG_ROOT}}", pkgRoot);
|
|
187
|
-
|
|
191
|
+
try {
|
|
192
|
+
return JSON.parse(resolved) as CursorHooks;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
throw new Error(`Failed to parse Cursor hooks template at ${templatePath}: ${e}`);
|
|
195
|
+
}
|
|
188
196
|
}
|
|
189
197
|
|
|
190
198
|
/**
|
|
@@ -198,7 +206,7 @@ export function mergeCursorHooks(
|
|
|
198
206
|
const result: CursorHooks = { ...existing, version: existing.version ?? 1 };
|
|
199
207
|
|
|
200
208
|
if (template.hooks) {
|
|
201
|
-
|
|
209
|
+
result.hooks ??= {};
|
|
202
210
|
for (const [event, entries] of Object.entries(template.hooks)) {
|
|
203
211
|
const current = result.hooks[event] ?? [];
|
|
204
212
|
for (const entry of entries) {
|
|
@@ -241,6 +249,103 @@ export function unmergeCursorHooks(
|
|
|
241
249
|
return result;
|
|
242
250
|
}
|
|
243
251
|
|
|
252
|
+
// --- Codex hooks (nested group format, distinct from Cursor's flat format) ---
|
|
253
|
+
|
|
254
|
+
type CodexHookCommand = { type: string; command: string; timeout?: number };
|
|
255
|
+
type CodexHookGroup = { matcher?: string; hooks: CodexHookCommand[] };
|
|
256
|
+
type CodexHooks = { hooks?: Record<string, CodexHookGroup[]> };
|
|
257
|
+
|
|
258
|
+
/** Strip leading env-var assignments so "PAL_AGENT=x bun run ..." → "bun run ..." */
|
|
259
|
+
function canonicalCmd(cmd: string): string {
|
|
260
|
+
return cmd.replace(/^(?:\w+=\S+\s+)+/, "");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function loadCodexHooksTemplate(
|
|
264
|
+
templatePath: string,
|
|
265
|
+
pkgRoot: string
|
|
266
|
+
): CodexHooks {
|
|
267
|
+
const raw = readFileSync(templatePath, "utf-8");
|
|
268
|
+
const resolved = raw.replaceAll("{{PKG_ROOT}}", pkgRoot);
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(resolved) as CodexHooks;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
throw new Error(`Failed to parse Codex hooks template at ${templatePath}: ${e}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Merge PAL hooks into an existing Codex hooks.json. Deduplicates by canonical command path. */
|
|
277
|
+
export function mergeCodexHooks(existing: CodexHooks, template: CodexHooks): CodexHooks {
|
|
278
|
+
const result: CodexHooks = { ...existing };
|
|
279
|
+
if (!template.hooks) return result;
|
|
280
|
+
result.hooks ??= {};
|
|
281
|
+
|
|
282
|
+
// Collect canonical paths of PAL template commands so we can evict stale variants
|
|
283
|
+
const palCanonical = new Set(
|
|
284
|
+
Object.values(template.hooks).flatMap((groups) =>
|
|
285
|
+
groups.flatMap((g) => g.hooks.map((h) => canonicalCmd(h.command)))
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Strip any existing entries (nested or flat) whose canonical path matches a PAL command
|
|
290
|
+
for (const event of Object.keys(result.hooks)) {
|
|
291
|
+
result.hooks[event] = (result.hooks[event] ?? [])
|
|
292
|
+
.map((g) => {
|
|
293
|
+
const flat = g as unknown as CodexHookCommand;
|
|
294
|
+
if (!g.hooks && flat.command && palCanonical.has(canonicalCmd(flat.command))) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const filtered = (g.hooks ?? []).filter(
|
|
298
|
+
(h) => !palCanonical.has(canonicalCmd(h.command))
|
|
299
|
+
);
|
|
300
|
+
return filtered.length > 0 ? { ...g, hooks: filtered } : null;
|
|
301
|
+
})
|
|
302
|
+
.filter((g): g is CodexHookGroup => g !== null);
|
|
303
|
+
if (result.hooks[event].length === 0) delete result.hooks[event];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Add fresh template entries
|
|
307
|
+
for (const [event, groups] of Object.entries(template.hooks)) {
|
|
308
|
+
const current = result.hooks[event] ?? [];
|
|
309
|
+
for (const group of groups) current.push(group);
|
|
310
|
+
result.hooks[event] = current;
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Remove PAL hooks from an existing Codex hooks.json. Preserves user hooks. */
|
|
316
|
+
export function unmergeCodexHooks(
|
|
317
|
+
existing: CodexHooks,
|
|
318
|
+
template: CodexHooks
|
|
319
|
+
): CodexHooks {
|
|
320
|
+
const result: CodexHooks = { ...existing };
|
|
321
|
+
if (!template.hooks || !result.hooks) return result;
|
|
322
|
+
|
|
323
|
+
// Match by canonical path so prefix variants (PAL_AGENT=codex, etc.) are all removed
|
|
324
|
+
const palCanonical = new Set(
|
|
325
|
+
Object.values(template.hooks).flatMap((groups) =>
|
|
326
|
+
groups.flatMap((g) => g.hooks.map((h) => canonicalCmd(h.command)))
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
for (const event of Object.keys(result.hooks)) {
|
|
331
|
+
result.hooks[event] = (result.hooks[event] ?? [])
|
|
332
|
+
.map((g) => {
|
|
333
|
+
const flat = g as unknown as CodexHookCommand;
|
|
334
|
+
if (!g.hooks && flat.command && palCanonical.has(canonicalCmd(flat.command))) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const filtered = (g.hooks ?? []).filter(
|
|
338
|
+
(h) => !palCanonical.has(canonicalCmd(h.command))
|
|
339
|
+
);
|
|
340
|
+
return filtered.length > 0 ? { ...g, hooks: filtered } : null;
|
|
341
|
+
})
|
|
342
|
+
.filter((g): g is CodexHookGroup => g !== null);
|
|
343
|
+
if (result.hooks[event].length === 0) delete result.hooks[event];
|
|
344
|
+
}
|
|
345
|
+
if (Object.keys(result.hooks).length === 0) delete result.hooks;
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
244
349
|
// --- TELOS scaffolding ---
|
|
245
350
|
|
|
246
351
|
/** Copy template files into telos/ without overwriting existing ones */
|
|
@@ -275,6 +380,21 @@ export function scaffoldPalSettings(): void {
|
|
|
275
380
|
copyFileSync(src, dst);
|
|
276
381
|
log.info("Created pal-settings.json from template");
|
|
277
382
|
}
|
|
383
|
+
|
|
384
|
+
// Strip deprecated loadAtStartup.files entries from existing installs.
|
|
385
|
+
// mergeSettings only adds, never removes — deprecated entries persist indefinitely otherwise.
|
|
386
|
+
try {
|
|
387
|
+
const raw = JSON.parse(readFileSync(dst, "utf-8"));
|
|
388
|
+
const files: string[] = raw?.loadAtStartup?.files ?? [];
|
|
389
|
+
const cleaned = files.filter((f: string) => !f.endsWith("PROJECTS.md"));
|
|
390
|
+
if (cleaned.length !== files.length) {
|
|
391
|
+
raw.loadAtStartup.files = cleaned;
|
|
392
|
+
writeFileSync(dst, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
|
|
393
|
+
log.info("Removed deprecated PROJECTS.md from loadAtStartup.files");
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
/* non-fatal — malformed settings left as-is */
|
|
397
|
+
}
|
|
278
398
|
}
|
|
279
399
|
|
|
280
400
|
// --- PAL docs (modular context routing files) ---
|
|
@@ -492,14 +612,14 @@ function extractAgentForPlatform(content: string, platform: AgentPlatform): stri
|
|
|
492
612
|
for (const line of frontmatter.split("\n")) {
|
|
493
613
|
if (!line.trim()) continue;
|
|
494
614
|
|
|
495
|
-
const platformMatch =
|
|
615
|
+
const platformMatch = new RegExp(/^(claude|opencode|cursor):\s*$/).exec(line);
|
|
496
616
|
if (platformMatch) {
|
|
497
617
|
currentPlatform = platformMatch[1] as AgentPlatform;
|
|
498
618
|
continue;
|
|
499
619
|
}
|
|
500
620
|
|
|
501
621
|
if (currentPlatform) {
|
|
502
|
-
if (
|
|
622
|
+
if (new RegExp(/^ {2}/).exec(line)) {
|
|
503
623
|
platformLines[currentPlatform].push(line.slice(2)); // un-indent one level
|
|
504
624
|
continue;
|
|
505
625
|
}
|
|
@@ -580,9 +700,15 @@ export function removeAgentsFromCopilot(copilotAgentsDir: string): string[] {
|
|
|
580
700
|
|
|
581
701
|
/** Load and resolve the Copilot hooks template, substituting PKG_ROOT */
|
|
582
702
|
export function loadCopilotHooksTemplate(templatePath: string, pkgRoot: string): unknown {
|
|
583
|
-
|
|
584
|
-
|
|
703
|
+
const resolved = readFileSync(templatePath, "utf-8").replaceAll(
|
|
704
|
+
"{{PKG_ROOT}}",
|
|
705
|
+
pkgRoot
|
|
585
706
|
);
|
|
707
|
+
try {
|
|
708
|
+
return JSON.parse(resolved);
|
|
709
|
+
} catch (e) {
|
|
710
|
+
throw new Error(`Failed to parse Copilot hooks template at ${templatePath}: ${e}`);
|
|
711
|
+
}
|
|
586
712
|
}
|
|
587
713
|
|
|
588
714
|
// --- Skill Index ---
|
|
@@ -604,7 +730,7 @@ function extractTriggers(description: string): string[] {
|
|
|
604
730
|
// Extract "Use when ..." phrases and key terms
|
|
605
731
|
const triggers = new Set<string>();
|
|
606
732
|
|
|
607
|
-
const useWhen =
|
|
733
|
+
const useWhen = new RegExp(/Use when\s+(.+?)(?:\.|$)/i).exec(description);
|
|
608
734
|
if (useWhen) {
|
|
609
735
|
const words = useWhen[1]
|
|
610
736
|
.toLowerCase()
|
|
@@ -647,12 +773,12 @@ export function generateSkillIndex(): number {
|
|
|
647
773
|
|
|
648
774
|
try {
|
|
649
775
|
const content = readFileSync(skillMd, "utf-8");
|
|
650
|
-
const fmMatch =
|
|
776
|
+
const fmMatch = new RegExp(/^---\n([\s\S]*?)\n---/).exec(content);
|
|
651
777
|
if (!fmMatch) continue;
|
|
652
778
|
|
|
653
779
|
const fm = fmMatch[1];
|
|
654
|
-
const nameMatch =
|
|
655
|
-
const descMatch =
|
|
780
|
+
const nameMatch = new RegExp(/^name:\s*(.+)$/m).exec(fm);
|
|
781
|
+
const descMatch = new RegExp(/^description:\s*"?(.+?)"?\s*$/m).exec(fm);
|
|
656
782
|
if (!nameMatch) continue;
|
|
657
783
|
|
|
658
784
|
const skillName = nameMatch[1].trim();
|
|
@@ -34,6 +34,9 @@ const PALPlugin: Plugin = async ({ directory, client }: PluginInput) => {
|
|
|
34
34
|
const { captureRating } = await lib<typeof import("../../hooks/handlers/rating")>(
|
|
35
35
|
"../handlers/rating.ts"
|
|
36
36
|
);
|
|
37
|
+
const { getRetrievalReminder } = await lib<
|
|
38
|
+
typeof import("../../hooks/handlers/inject-retrieval")
|
|
39
|
+
>("../handlers/inject-retrieval.ts");
|
|
37
40
|
|
|
38
41
|
function partsToText(parts: Array<Record<string, unknown>>): string {
|
|
39
42
|
return parts
|
|
@@ -120,17 +123,25 @@ const PALPlugin: Plugin = async ({ directory, client }: PluginInput) => {
|
|
|
120
123
|
|
|
121
124
|
// --- Capture ratings + session naming from user messages (shared handlers) ---
|
|
122
125
|
"chat.message": async (input, output) => {
|
|
123
|
-
const text =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
const text = partsToText(output.parts ?? []);
|
|
127
|
+
if (!text.trim()) return;
|
|
128
|
+
|
|
129
|
+
const [, , retrievalResult] = await Promise.allSettled([
|
|
130
|
+
captureRating(text, input.sessionID),
|
|
131
|
+
captureSessionName(text, input.sessionID),
|
|
132
|
+
getRetrievalReminder(text),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
if (retrievalResult.status === "fulfilled" && retrievalResult.value) {
|
|
136
|
+
const injected = {
|
|
137
|
+
id: `pal-retrieval-${Date.now()}`,
|
|
138
|
+
sessionID: input.sessionID,
|
|
139
|
+
messageID: input.messageID ?? `pal-msg-${Date.now()}`,
|
|
140
|
+
type: "text" as const,
|
|
141
|
+
text: retrievalResult.value,
|
|
142
|
+
synthetic: true,
|
|
143
|
+
};
|
|
144
|
+
output.parts = [injected, ...(output.parts ?? [])];
|
|
134
145
|
}
|
|
135
146
|
},
|
|
136
147
|
|
|
@@ -40,7 +40,7 @@ function reflectionsPath(): string {
|
|
|
40
40
|
return resolve(dir, "algorithm-reflections.jsonl");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
function appendReflection(reflection: AlgorithmReflection): {
|
|
44
44
|
success: boolean;
|
|
45
45
|
message: string;
|
|
46
46
|
path: string;
|
|
@@ -34,25 +34,26 @@ export function printReport(result: AnalysisResult): void {
|
|
|
34
34
|
|
|
35
35
|
if (result.ratings) {
|
|
36
36
|
const r = result.ratings;
|
|
37
|
-
const
|
|
37
|
+
const lowOrMid = r.average <= 4 ? c.red : c.yellow;
|
|
38
|
+
const avgColor = r.average >= 7 ? c.green : lowOrMid;
|
|
39
|
+
const ratingStr = `${r.average.toFixed(1)}/10`;
|
|
40
|
+
const lowStr = `Low (≤4): ${r.low.count}`;
|
|
41
|
+
const highStr = `High (≥7): ${r.high.count}`;
|
|
38
42
|
console.log(
|
|
39
|
-
`\n ${c.bold("Ratings:")} ${avgColor(
|
|
40
|
-
);
|
|
41
|
-
console.log(
|
|
42
|
-
` ${c.red(`Low (≤4): ${r.low.count}`)} | ${c.green(`High (≥7): ${r.high.count}`)}`
|
|
43
|
+
`\n ${c.bold("Ratings:")} ${avgColor(ratingStr)} avg (${r.total} total)`
|
|
43
44
|
);
|
|
45
|
+
console.log(` ${c.red(lowStr)} | ${c.green(highStr)}`);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
if (result.candidates.length > 0) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
49
|
+
const graduationHeader = `Graduation Report — ${result.candidates.length} pattern(s) detected`;
|
|
50
|
+
console.log(`\n ${c.bold(c.green(graduationHeader))}\n`);
|
|
50
51
|
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
51
52
|
|
|
52
53
|
for (const candidate of result.candidates) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
54
|
+
const domain = `[${candidate.domain}]`;
|
|
55
|
+
const count = `${candidate.entries.length}x`;
|
|
56
|
+
console.log(` ${c.cyan(domain)} ${c.bold(count)} occurrences`);
|
|
56
57
|
console.log("");
|
|
57
58
|
|
|
58
59
|
for (const entry of candidate.entries) {
|
|
@@ -72,9 +73,8 @@ export function printReport(result: AnalysisResult): void {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
console.log("");
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
76
|
+
const framePath = `memory/wisdom/frames/${candidate.domain}.md`;
|
|
77
|
+
console.log(` Target frame: ${c.magenta(framePath)}`);
|
|
78
78
|
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -82,9 +82,9 @@ export function printReport(result: AnalysisResult): void {
|
|
|
82
82
|
if (result.emerging.length > 0) {
|
|
83
83
|
console.log(` ${c.bold(c.yellow("Emerging (2x — one more to graduate)"))}\n`);
|
|
84
84
|
for (const group of result.emerging) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
85
|
+
const domain = `[${group.domain}]`;
|
|
86
|
+
const count = `${group.entries.length}x`;
|
|
87
|
+
console.log(` ${c.cyan(domain)} ${c.bold(count)}`);
|
|
88
88
|
for (const entry of group.entries) {
|
|
89
89
|
const sourceType = entry.source.startsWith("failure:") ? "failure" : "learning";
|
|
90
90
|
const tag =
|
|
@@ -154,4 +154,4 @@ async function run() {
|
|
|
154
154
|
printReport(result);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
if (import.meta.main) run();
|
|
157
|
+
if (import.meta.main) await run();
|
|
@@ -43,7 +43,7 @@ function writeHandoffs(handoffs: Record<string, HandoffEntry>): void {
|
|
|
43
43
|
writeFileSync(handoffPath(), JSON.stringify(trimmed, null, 2), "utf-8");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
function writeHandoffNote(
|
|
47
47
|
cwd: string,
|
|
48
48
|
title: string,
|
|
49
49
|
text: string,
|