portable-agent-layer 0.20.0 → 0.22.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.
Files changed (38) hide show
  1. package/README.md +4 -3
  2. package/assets/agents/gemini-researcher.md +73 -0
  3. package/assets/agents/grok-researcher.md +10 -1
  4. package/assets/agents/perplexity-researcher.md +67 -0
  5. package/assets/skills/analyze-youtube/SKILL.md +1 -1
  6. package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +3 -3
  7. package/assets/skills/create-pdf/SKILL.md +53 -76
  8. package/assets/skills/fyzz-chat-api/SKILL.md +3 -3
  9. package/assets/skills/fyzz-chat-api/tools/fyzz-api.ts +4 -4
  10. package/assets/skills/research/SKILL.md +9 -9
  11. package/assets/skills/research/tools/gemini-search.ts +186 -0
  12. package/assets/skills/research/tools/grok-search.ts +3 -3
  13. package/assets/skills/research/tools/perplexity-search.ts +150 -0
  14. package/assets/templates/PAL/ALGORITHM.md +71 -9
  15. package/assets/templates/PAL/WORK_TRACKING.md +2 -9
  16. package/package.json +1 -1
  17. package/src/cli/index.ts +18 -6
  18. package/src/hooks/handlers/rating.ts +1 -1
  19. package/src/hooks/handlers/relationship.ts +2 -2
  20. package/src/hooks/handlers/session-name.ts +1 -1
  21. package/src/hooks/handlers/work-learning.ts +9 -0
  22. package/src/hooks/lib/claude-md.ts +17 -5
  23. package/src/hooks/lib/context.ts +35 -55
  24. package/src/hooks/lib/export.ts +3 -2
  25. package/src/hooks/lib/graduation.ts +1 -1
  26. package/src/hooks/lib/inference.ts +1 -1
  27. package/src/hooks/lib/paths.ts +1 -0
  28. package/src/hooks/lib/readme-sync.ts +6 -6
  29. package/src/hooks/lib/security.ts +5 -1
  30. package/src/hooks/lib/work-tracking.ts +29 -42
  31. package/src/targets/claude/install.ts +2 -0
  32. package/src/targets/cursor/install.ts +2 -0
  33. package/src/targets/lib.ts +93 -0
  34. package/src/targets/opencode/install.ts +2 -0
  35. package/src/tools/agent/algorithm-reflect.ts +120 -0
  36. package/src/tools/agent/wisdom-frame.ts +0 -2
  37. package/assets/agents/claude-researcher.md +0 -43
  38. package/assets/agents/investigative-researcher.md +0 -44
@@ -15,12 +15,7 @@ import { readSessionNames } from "./session-names";
15
15
  import { buildSetupPrompt, readSetupState, remainingSteps, STEP_ORDER } from "./setup";
16
16
  import { computeSignalTrends, formatTrends } from "./signal-trends";
17
17
  import { readFramePrinciples } from "./wisdom";
18
- import {
19
- activeProjects,
20
- readSessions,
21
- recentSessions,
22
- staleProjects,
23
- } from "./work-tracking";
18
+ import { readProjectHistory, readSessions, recentSessions } from "./work-tracking";
24
19
 
25
20
  interface PalSettings {
26
21
  loadAtStartup?: { files?: string[] };
@@ -82,44 +77,16 @@ export function loadActiveWork(): { text: string; summary: string | null } | nul
82
77
  try {
83
78
  const cwd = process.cwd();
84
79
  const allRecent = recentSessions(48);
85
- const projects = activeProjects();
86
- const stale = staleProjects(7);
87
80
 
88
- if (allRecent.length === 0 && projects.length === 0) return null;
81
+ if (allRecent.length === 0) return null;
89
82
 
90
83
  const lines: string[] = [];
91
84
 
92
- if (allRecent.length > 0) {
93
- lines.push("## Recent Work (last 48h)");
94
- for (const s of allRecent.slice(-10).reverse()) {
95
- const ago = formatAgo(s.ts);
96
- const here = s.cwd === cwd ? " *" : "";
97
- lines.push(`- [${s.status}] ${s.name} — ${ago}${here}`);
98
- }
99
- }
100
-
101
- if (projects.length > 0) {
102
- lines.push("", "### Active Projects");
103
- for (const p of projects) {
104
- const sessionCount = p.sessions.length;
105
- const ago = formatAgo(p.updated);
106
- lines.push(`- **${p.name}** (${sessionCount} sessions, last: ${ago})`);
107
- if (p.nextSteps.length > 0) {
108
- lines.push(` Next: ${p.nextSteps[0]}`);
109
- }
110
- if (p.blockers.length > 0) {
111
- lines.push(` Blockers: ${p.blockers.join(", ")}`);
112
- } else {
113
- lines.push(" Blockers: None");
114
- }
115
- }
116
- }
117
-
118
- if (stale.length > 0) {
119
- lines.push("", "### Stale Projects (>7d inactive)");
120
- for (const p of stale) {
121
- lines.push(`- **${p.name}** — last active ${formatAgo(p.updated)}`);
122
- }
85
+ lines.push("## Recent Work (last 48h)");
86
+ for (const s of allRecent.slice(-10).reverse()) {
87
+ const ago = formatAgo(s.ts);
88
+ const here = s.cwd === cwd ? " *" : "";
89
+ lines.push(`- [${s.status}] ${s.name} ${ago}${here}`);
123
90
  }
124
91
 
125
92
  // Summary from most recent session
@@ -240,25 +207,15 @@ export function loadLearningDigest(): string {
240
207
  const entries = readLearnings(paths.sessionLearning(), 10);
241
208
  if (entries.length === 0) return "";
242
209
 
243
- const thisProject = entries.filter((e) => e.cwd === cwd).slice(0, 4);
244
- const other = entries.filter((e) => e.cwd !== cwd).slice(0, 3);
210
+ // This-project learnings are now in loadProjectHistoryContext(); only show cross-project here
211
+ const other = entries.filter((e) => e.cwd !== cwd).slice(0, 5);
245
212
 
246
- if (thisProject.length === 0 && other.length === 0) return "";
213
+ if (other.length === 0) return "";
247
214
 
248
215
  const lines: string[] = [];
249
216
 
250
- if (thisProject.length > 0) {
251
- lines.push("## This Project Recent Sessions");
252
- for (const e of thisProject) {
253
- lines.push(`- **${e.title}**`);
254
- if (e.insights) lines.push(` ${e.insights.split("\n")[0].slice(0, 150)}`);
255
- }
256
- }
257
-
258
- if (other.length > 0) {
259
- lines.push(thisProject.length > 0 ? "" : "", "## Other Recent Learnings");
260
- for (const e of other) lines.push(`- ${e.title}`);
261
- }
217
+ lines.push("## Other Recent Learnings");
218
+ for (const e of other) lines.push(`- ${e.title}`);
262
219
 
263
220
  return lines.join("\n");
264
221
  } catch {
@@ -346,6 +303,25 @@ export function loadSignalTrends(): string {
346
303
  }
347
304
  }
348
305
 
306
+ /** Load per-project session history for the current working directory */
307
+ export function loadProjectHistoryContext(): string {
308
+ try {
309
+ const cwd = process.cwd();
310
+ const entries = readProjectHistory(cwd, 15);
311
+ if (entries.length === 0) return "";
312
+
313
+ const lines: string[] = ["## This Project — Session History"];
314
+ for (const e of entries) {
315
+ lines.push(`- **${e.title}** (${e.date})`);
316
+ if (e.summary) lines.push(` ${e.summary.split("\n")[0].slice(0, 150)}`);
317
+ }
318
+
319
+ return lines.join("\n");
320
+ } catch {
321
+ return "";
322
+ }
323
+ }
324
+
349
325
  /** Load recent relationship notes (today + yesterday) */
350
326
  export function loadRelationshipContext(): string {
351
327
  try {
@@ -373,6 +349,9 @@ export function buildSystemReminder(): string {
373
349
  ? loadRelationshipContext()
374
350
  : "";
375
351
  const digest = isEnabled(settings, "learningDigest") ? loadLearningDigest() : "";
352
+ const projectHistory = isEnabled(settings, "projectHistory")
353
+ ? loadProjectHistoryContext()
354
+ : "";
376
355
  const trends = isEnabled(settings, "signalTrends") ? loadSignalTrends() : "";
377
356
  const failures = isEnabled(settings, "failurePatterns") ? loadFailurePatterns() : "";
378
357
  const synthesis = isEnabled(settings, "synthesis")
@@ -384,6 +363,7 @@ export function buildSystemReminder(): string {
384
363
  if (wisdom) parts.push(wisdom);
385
364
  if (opinions) parts.push(opinions);
386
365
  if (relationship) parts.push(relationship);
366
+ if (projectHistory) parts.push(projectHistory);
387
367
  if (digest) parts.push(digest);
388
368
  if (synthesis) parts.push(synthesis);
389
369
  if (trends) parts.push(trends);
@@ -15,7 +15,8 @@ const EXPORT_DIRS = ["telos", "memory"];
15
15
  const SKIP_PATTERNS = ["memory/downloads"];
16
16
 
17
17
  function shouldSkip(relPath: string): boolean {
18
- return SKIP_PATTERNS.some((p) => relPath.startsWith(p));
18
+ const normalized = relPath.replaceAll("\\", "/");
19
+ return SKIP_PATTERNS.some((p) => normalized.startsWith(p));
19
20
  }
20
21
 
21
22
  /** Recursively collect all files under a directory, returning paths relative to root. */
@@ -32,7 +33,7 @@ function walkDir(dir: string, root: string): string[] {
32
33
  if (entry.isDirectory()) {
33
34
  files.push(...walkDir(fullPath, root));
34
35
  } else if (entry.isFile()) {
35
- files.push(relPath);
36
+ files.push(relPath.replaceAll("\\", "/"));
36
37
  }
37
38
  }
38
39
  return files;
@@ -214,7 +214,7 @@ async function generateRecommendations(
214
214
  ratings: RatingsSummary | null
215
215
  ): Promise<string[]> {
216
216
  if (candidates.length === 0 && !ratings) return [];
217
- if (!process.env.ANTHROPIC_API_KEY) {
217
+ if (!process.env.PAL_ANTHROPIC_API_KEY) {
218
218
  return candidates
219
219
  .slice(0, 3)
220
220
  .map(
@@ -21,7 +21,7 @@ export interface InferenceResult {
21
21
  }
22
22
 
23
23
  export async function inference(opts: InferenceOptions): Promise<InferenceResult> {
24
- const apiKey = process.env.ANTHROPIC_API_KEY;
24
+ const apiKey = process.env.PAL_ANTHROPIC_API_KEY;
25
25
  if (!apiKey) return { success: false };
26
26
 
27
27
  const {
@@ -57,6 +57,7 @@ export const paths = {
57
57
  relationship: () => ensureDir(home("memory", "relationship")),
58
58
  entities: () => ensureDir(home("memory", "entities")),
59
59
  failures: () => ensureDir(home("memory", "learning", "failures")),
60
+ projectHistory: () => ensureDir(home("memory", "projects")),
60
61
  sessionLearning: () => ensureDir(home("memory", "learning", "session")),
61
62
  synthesis: () => ensureDir(home("memory", "learning", "synthesis")),
62
63
  backups: () => ensureDir(home("backups")),
@@ -58,21 +58,21 @@ function extractEnvVars(): string[] {
58
58
  }
59
59
  }
60
60
 
61
- // ANTHROPIC_API_KEY from inference.ts
61
+ // PAL_ANTHROPIC_API_KEY from inference.ts
62
62
  const inferenceFile = resolve(pkg, "src", "hooks", "lib", "inference.ts");
63
63
  if (existsSync(inferenceFile)) {
64
64
  const content = readFileSync(inferenceFile, "utf-8");
65
- if (content.includes("ANTHROPIC_API_KEY")) {
66
- vars.add("ANTHROPIC_API_KEY");
65
+ if (content.includes("PAL_ANTHROPIC_API_KEY")) {
66
+ vars.add("PAL_ANTHROPIC_API_KEY");
67
67
  }
68
68
  }
69
69
 
70
- // GEMINI_API_KEY from youtube-analyze.ts
70
+ // PAL_GEMINI_API_KEY from youtube-analyze.ts
71
71
  const youtubeFile = resolve(pkg, "src", "tools", "youtube-analyze.ts");
72
72
  if (existsSync(youtubeFile)) {
73
73
  const content = readFileSync(youtubeFile, "utf-8");
74
- if (content.includes("GEMINI_API_KEY")) {
75
- vars.add("GEMINI_API_KEY");
74
+ if (content.includes("PAL_GEMINI_API_KEY")) {
75
+ vars.add("PAL_GEMINI_API_KEY");
76
76
  }
77
77
  }
78
78
 
@@ -34,6 +34,8 @@ export const HOOK_MANAGED_FILES = [
34
34
  "debug.log.prev",
35
35
  "opinions.json",
36
36
  "pal-settings.json",
37
+ "skill-index.json",
38
+ "algorithm-reflections.jsonl",
37
39
  ];
38
40
 
39
41
  /** Hook-managed directories — AI must not write to or delete from these */
@@ -44,7 +46,9 @@ export const HOOK_MANAGED_DIRS = [
44
46
  "memory/learning/synthesis",
45
47
  "memory/relationship",
46
48
  "memory/wisdom/state",
47
- ".agents/PAL",
49
+ "memory/projects",
50
+ ".agents/PAL/memory",
51
+ ".agents/PAL/telos",
48
52
  ];
49
53
 
50
54
  /** Escape a string for use in a RegExp */
@@ -1,12 +1,11 @@
1
1
  /**
2
- * Structured work tracking: session history + persistent projects.
2
+ * Structured work tracking: session history + per-project history.
3
3
  * Used by both Claude Code (StopOrchestrator) and opencode (plugin).
4
4
  */
5
5
 
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { resolve } from "node:path";
8
8
  import { ensureDir, paths } from "./paths";
9
- import { now } from "./time";
10
9
 
11
10
  // ── Session Records ──────────────────────────────────────────────
12
11
 
@@ -143,51 +142,39 @@ export function extractHandoff(lastAssistant: string): string {
143
142
  return cleaned;
144
143
  }
145
144
 
146
- // ── Persistent Projects ──────────────────────────────────────────
145
+ // ── Per-Project History ──────────────────────────────────────────
147
146
 
148
- export interface Project {
149
- id: string;
150
- name: string;
151
- created: string;
152
- updated: string;
153
- status: "active" | "paused" | "completed";
154
- objectives: string[];
155
- decisions: string[];
156
- completed: string[];
157
- blockers: string[];
158
- nextSteps: string[];
159
- handoff: string;
160
- sessions: string[];
147
+ export interface ProjectHistoryEntry {
148
+ date: string;
149
+ title: string;
150
+ summary: string;
151
+ insights: string;
161
152
  }
162
153
 
163
- function projectsPath(): string {
164
- return resolve(ensureDir(paths.state()), "projects.json");
154
+ /** Convert a cwd path to a filesystem-safe slug (last directory segment) */
155
+ export function cwdToSlug(cwd: string): string {
156
+ const normalized = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
157
+ return normalized.split("/").pop() || "unknown";
165
158
  }
166
159
 
167
- export function readProjects(): Record<string, Project> {
168
- const p = projectsPath();
169
- if (!existsSync(p)) return {};
160
+ /** Append a learning entry to the project's history.jsonl */
161
+ export function appendProjectHistory(cwd: string, entry: ProjectHistoryEntry): void {
162
+ const slug = cwdToSlug(cwd);
163
+ const dir = ensureDir(resolve(paths.projectHistory(), slug));
164
+ const historyPath = resolve(dir, "history.jsonl");
165
+ const line = `${JSON.stringify(entry)}\n`;
166
+ appendFileSync(historyPath, line, "utf-8");
167
+ }
168
+
169
+ /** Read the project history for a given cwd */
170
+ export function readProjectHistory(cwd: string, limit = 15): ProjectHistoryEntry[] {
171
+ const slug = cwdToSlug(cwd);
172
+ const historyPath = resolve(paths.projectHistory(), slug, "history.jsonl");
173
+ if (!existsSync(historyPath)) return [];
170
174
  try {
171
- return JSON.parse(readFileSync(p, "utf-8"));
175
+ const lines = readFileSync(historyPath, "utf-8").trim().split("\n").filter(Boolean);
176
+ return lines.slice(-limit).map((line) => JSON.parse(line) as ProjectHistoryEntry);
172
177
  } catch {
173
- return {};
178
+ return [];
174
179
  }
175
180
  }
176
-
177
- export function writeProject(project: Project): void {
178
- const projects = readProjects();
179
- project.updated = now();
180
- projects[project.id] = project;
181
- writeFileSync(projectsPath(), JSON.stringify(projects, null, 2), "utf-8");
182
- }
183
-
184
- export function activeProjects(): Project[] {
185
- return Object.values(readProjects()).filter((p) => p.status === "active");
186
- }
187
-
188
- export function staleProjects(days = 7): Project[] {
189
- const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
190
- return Object.values(readProjects()).filter(
191
- (p) => p.status === "active" && new Date(p.updated).getTime() < cutoff
192
- );
193
- }
@@ -15,6 +15,7 @@ import {
15
15
  countAgents,
16
16
  countMd,
17
17
  countSkills,
18
+ generateSkillIndex,
18
19
  loadSettingsTemplate,
19
20
  log,
20
21
  mergeSettings,
@@ -50,6 +51,7 @@ log.success("Merged PAL settings into settings.json");
50
51
  // --- Copy skills ---
51
52
  const skillsDir = resolve(CLAUDE_DIR, "skills");
52
53
  copySkills(skillsDir);
54
+ generateSkillIndex();
53
55
 
54
56
  // --- Copy agents ---
55
57
  copyAgents();
@@ -12,6 +12,7 @@ import {
12
12
  copyPalDocs,
13
13
  copySkills,
14
14
  countSkills,
15
+ generateSkillIndex,
15
16
  loadCursorHooksTemplate,
16
17
  log,
17
18
  mergeCursorHooks,
@@ -44,6 +45,7 @@ log.success("Merged PAL hooks into hooks.json");
44
45
  // --- Symlink skills to ~/.cursor/skills/ ---
45
46
  const cursorSkillsDir = resolve(CURSOR_DIR, "skills");
46
47
  copySkills(cursorSkillsDir);
48
+ generateSkillIndex();
47
49
 
48
50
  // --- Copy PAL system docs ---
49
51
  const palDocsCount = copyPalDocs();
@@ -536,6 +536,99 @@ export function removeAgentsFromOpencode(ocAgentsDir: string): string[] {
536
536
  return removed;
537
537
  }
538
538
 
539
+ // --- Skill Index ---
540
+
541
+ interface SkillIndexEntry {
542
+ name: string;
543
+ description: string;
544
+ triggers: string[];
545
+ }
546
+
547
+ interface SkillIndex {
548
+ generated: string;
549
+ totalSkills: number;
550
+ skills: Record<string, SkillIndexEntry>;
551
+ }
552
+
553
+ /** Extract trigger keywords from a skill description */
554
+ function extractTriggers(description: string): string[] {
555
+ // Extract "Use when ..." phrases and key terms
556
+ const triggers = new Set<string>();
557
+
558
+ const useWhen = description.match(/Use when\s+(.+?)(?:\.|$)/i);
559
+ if (useWhen) {
560
+ const words = useWhen[1]
561
+ .toLowerCase()
562
+ .split(/[,\s]+/)
563
+ .filter(
564
+ (w) =>
565
+ w.length > 3 &&
566
+ !["when", "this", "that", "with", "from", "about", "your", "the"].includes(w)
567
+ );
568
+ for (const w of words) triggers.add(w);
569
+ }
570
+
571
+ // Extract domain terms from full description
572
+ const terms = description
573
+ .toLowerCase()
574
+ .match(
575
+ /\b(research|analyze|extract|summarize|review|debug|reflect|council|debate|brainstorm|first.principles|security|pdf|youtube|telos|goals|projects|beliefs|challenges|opinion|skill|create)\b/g
576
+ );
577
+ if (terms) for (const t of terms) triggers.add(t);
578
+
579
+ return [...triggers];
580
+ }
581
+
582
+ /**
583
+ * Generate skill-index.json from installed skills in ~/.agents/skills/.
584
+ * Called during install after skills are symlinked.
585
+ */
586
+ export function generateSkillIndex(): number {
587
+ if (!existsSync(AGENTS_SKILLS_DIR)) return 0;
588
+
589
+ const index: SkillIndex = {
590
+ generated: new Date().toISOString(),
591
+ totalSkills: 0,
592
+ skills: {},
593
+ };
594
+
595
+ for (const name of readdirSync(AGENTS_SKILLS_DIR)) {
596
+ const skillMd = resolve(AGENTS_SKILLS_DIR, name, "SKILL.md");
597
+ if (!existsSync(skillMd)) continue;
598
+
599
+ try {
600
+ const content = readFileSync(skillMd, "utf-8");
601
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
602
+ if (!fmMatch) continue;
603
+
604
+ const fm = fmMatch[1];
605
+ const nameMatch = fm.match(/^name:\s*(.+)$/m);
606
+ const descMatch = fm.match(/^description:\s*"?(.+?)"?\s*$/m);
607
+ if (!nameMatch) continue;
608
+
609
+ const skillName = nameMatch[1].trim();
610
+ const description = descMatch?.[1]?.trim() ?? "";
611
+
612
+ index.skills[skillName] = {
613
+ name: skillName,
614
+ description,
615
+ triggers: extractTriggers(description),
616
+ };
617
+ index.totalSkills++;
618
+ } catch {
619
+ /* skip unreadable skills */
620
+ }
621
+ }
622
+
623
+ // Write to state directory
624
+ const stateDir = resolve(palHome(), "memory", "state");
625
+ mkdirSync(stateDir, { recursive: true });
626
+ writeJson(resolve(stateDir, "skill-index.json"), index);
627
+ log.info(`Skill index: ${index.totalSkills} skills indexed`);
628
+
629
+ return index.totalSkills;
630
+ }
631
+
539
632
  /** Count skill subdirectories in ~/.agents/skills/ */
540
633
  export function countSkills(): number {
541
634
  if (!existsSync(AGENTS_SKILLS_DIR)) return 0;
@@ -12,6 +12,7 @@ import {
12
12
  copyPalDocs,
13
13
  copySkills,
14
14
  countSkills,
15
+ generateSkillIndex,
15
16
  log,
16
17
  writeJson,
17
18
  } from "../lib";
@@ -54,6 +55,7 @@ try {
54
55
  // --- 3. Install skills into ~/.agents/skills/ ---
55
56
  const claudeSkillsDir = resolve(platform.claudeDir(), "skills");
56
57
  copySkills(claudeSkillsDir);
58
+ generateSkillIndex();
57
59
  log.success("Installed skills to ~/.agents/skills/");
58
60
 
59
61
  // --- 4. Install agents into ~/.config/opencode/agents/ ---
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * AlgorithmReflect — Append structured algorithm reflections to JSONL.
4
+ *
5
+ * Records algorithm performance data after each LEARN phase.
6
+ * Creates a queryable dataset for improving the algorithm over time.
7
+ *
8
+ * Usage:
9
+ * bun run tool:algorithm-reflect --task "description" --criteria 5 --passed 4 --failed 1 --sentiment 7 \
10
+ * --q1 "Should have read the file before planning" \
11
+ * --q2 "Could have parallelized the explore agents" \
12
+ * --q3 "Missed the implicit constraint about cross-platform"
13
+ */
14
+
15
+ import { appendFileSync, existsSync, mkdirSync } from "node:fs";
16
+ import { resolve } from "node:path";
17
+ import { parseArgs } from "node:util";
18
+ import { paths } from "../../hooks/lib/paths";
19
+
20
+ // ── Types ──
21
+
22
+ interface AlgorithmReflection {
23
+ timestamp: string;
24
+ task: string;
25
+ criteria_count: number;
26
+ criteria_passed: number;
27
+ criteria_failed: number;
28
+ sentiment: number;
29
+ q1: string;
30
+ q2: string;
31
+ q3: string;
32
+ }
33
+
34
+ // ── Core ──
35
+
36
+ function reflectionsPath(): string {
37
+ const dir = resolve(paths.learning(), "reflections");
38
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
39
+ return resolve(dir, "algorithm-reflections.jsonl");
40
+ }
41
+
42
+ export function appendReflection(reflection: AlgorithmReflection): {
43
+ success: boolean;
44
+ message: string;
45
+ path: string;
46
+ } {
47
+ const p = reflectionsPath();
48
+ const line = `${JSON.stringify(reflection)}\n`;
49
+ appendFileSync(p, line, "utf-8");
50
+
51
+ return {
52
+ success: true,
53
+ message: `Reflection logged: ${reflection.criteria_passed}/${reflection.criteria_count} passed, sentiment ${reflection.sentiment}/10`,
54
+ path: p,
55
+ };
56
+ }
57
+
58
+ // ── CLI ──
59
+
60
+ function run() {
61
+ const { values } = parseArgs({
62
+ args: Bun.argv.slice(2),
63
+ options: {
64
+ task: { type: "string" },
65
+ criteria: { type: "string" },
66
+ passed: { type: "string" },
67
+ failed: { type: "string" },
68
+ sentiment: { type: "string" },
69
+ q1: { type: "string" },
70
+ q2: { type: "string" },
71
+ q3: { type: "string" },
72
+ help: { type: "boolean", short: "h" },
73
+ },
74
+ });
75
+
76
+ if (values.help) {
77
+ console.log(`
78
+ AlgorithmReflect — Log algorithm performance after LEARN phase
79
+
80
+ Usage:
81
+ bun run tool:algorithm-reflect --task "description" --criteria N --passed N --failed N --sentiment 1-10 \\
82
+ --q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
83
+
84
+ Arguments:
85
+ --task Brief task description
86
+ --criteria Total criteria count
87
+ --passed Criteria passed
88
+ --failed Criteria failed
89
+ --sentiment Implied satisfaction 1-10
90
+ --q1 Q1 — Self: what I'd do differently
91
+ --q2 Q2 — Algorithm: structural improvement
92
+ --q3 Q3 — AI: reasoning blind spot
93
+
94
+ Output: algorithm-reflections.jsonl in memory/learning/reflections/
95
+ `);
96
+ process.exit(0);
97
+ }
98
+
99
+ if (!values.task || !values.q1 || !values.q2 || !values.q3) {
100
+ console.error("Required: --task, --q1, --q2, --q3");
101
+ process.exit(1);
102
+ }
103
+
104
+ const reflection: AlgorithmReflection = {
105
+ timestamp: new Date().toISOString(),
106
+ task: values.task,
107
+ criteria_count: parseInt(values.criteria || "0", 10),
108
+ criteria_passed: parseInt(values.passed || "0", 10),
109
+ criteria_failed: parseInt(values.failed || "0", 10),
110
+ sentiment: Math.max(1, Math.min(10, parseInt(values.sentiment || "5", 10))),
111
+ q1: values.q1,
112
+ q2: values.q2,
113
+ q3: values.q3,
114
+ };
115
+
116
+ const result = appendReflection(reflection);
117
+ console.log(JSON.stringify(result, null, 2));
118
+ }
119
+
120
+ if (import.meta.main) run();
@@ -167,8 +167,6 @@ ${type === "anti-pattern" ? `### ${observation}\n- **Severity:** Medium\n- **Fre
167
167
  `- ${date()}: Principle candidate — ${observation}`
168
168
  );
169
169
  break;
170
-
171
- case "evolution":
172
170
  default:
173
171
  content = appendToSection(content, "## Evolution Log", evolutionEntry);
174
172
  break;
@@ -1,43 +0,0 @@
1
- ---
2
- name: claude-researcher
3
- description: Deep research with academic rigor — query decomposition, multi-source synthesis, scholarly depth. Use for research tasks requiring thorough analysis.
4
- tools: WebSearch, WebFetch, Read, Grep, Glob
5
- model: sonnet
6
- ---
7
-
8
- You are a research specialist focused on **depth and academic rigor**.
9
-
10
- ## Methodology
11
-
12
- 1. **Decompose** the query into 2-3 sub-questions that target the core of the topic
13
- 2. **Search** each sub-question using WebSearch — prioritize authoritative sources (papers, docs, official sites)
14
- 3. **Read** the most promising results with WebFetch to extract detail
15
- 4. **Synthesize** findings into a structured analysis
16
-
17
- ## Guidelines
18
-
19
- - Prioritize primary sources over summaries
20
- - Distinguish between established facts, expert consensus, and speculation
21
- - Note methodology limitations when citing research
22
- - If a claim has no strong source, say so — do not fabricate citations
23
- - Keep findings concise but substantive
24
-
25
- ## Output Format
26
-
27
- ```markdown
28
- ## Findings
29
-
30
- [Numbered list of key discoveries, each with a brief explanation]
31
-
32
- ## Sources
33
-
34
- [Verified URLs with one-line descriptions — only include URLs you actually visited]
35
-
36
- ## Confidence
37
-
38
- [High/Medium/Low rating per finding, with brief justification]
39
-
40
- ## Gaps
41
-
42
- [What couldn't be answered or needs further investigation]
43
- ```