portable-agent-layer 0.17.0 → 0.18.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/assets/templates/PAL/ALGORITHM.md +30 -9
- package/assets/templates/settings.claude.json +2 -1
- package/package.json +3 -2
- package/src/hooks/lib/paths.ts +1 -0
- package/src/targets/lib.ts +2 -1
- package/src/tools/agent/analyze.ts +157 -0
- package/src/tools/agent/wisdom-frame.ts +235 -0
- package/src/tools/export.ts +23 -17
- package/src/tools/import.ts +65 -77
- package/src/tools/relationship-reflect.ts +80 -85
- package/src/tools/session-summary.ts +44 -41
- package/src/tools/token-cost.ts +134 -92
- package/src/tools/analyze.ts +0 -152
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Core: transition from CURRENT STATE to IDEAL STATE using verifiable criteria. Every criterion is atomic, binary testable, and checked off with evidence.
|
|
4
4
|
|
|
5
|
-
## The
|
|
5
|
+
## The Five Phases
|
|
6
6
|
|
|
7
7
|
All work happens inside these phases. No work outside the phase structure until the Algorithm completes.
|
|
8
8
|
|
|
9
|
-
### ━━━ 👁️ OBSERVE ━━━ 1/
|
|
9
|
+
### ━━━ 👁️ OBSERVE ━━━ 1/5
|
|
10
10
|
|
|
11
11
|
Thinking-only. No tool calls except context recovery (Grep/Glob/Read).
|
|
12
12
|
|
|
@@ -44,7 +44,7 @@ Output:
|
|
|
44
44
|
🏹 CAPABILITIES: [list each selected skill/tool and why]
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
### ━━━ 🧠 PLAN ━━━ 2/
|
|
47
|
+
### ━━━ 🧠 PLAN ━━━ 2/5
|
|
48
48
|
|
|
49
49
|
**Pressure test the criteria:**
|
|
50
50
|
|
|
@@ -59,7 +59,7 @@ Refine criteria if the pressure test reveals gaps. Add criteria for uncovered fa
|
|
|
59
59
|
- Decide execution order — what's serial, what can parallelize
|
|
60
60
|
- If Advanced+ complexity, use EnterPlanMode for user alignment
|
|
61
61
|
|
|
62
|
-
### ━━━ ⚡ EXECUTE ━━━ 3/
|
|
62
|
+
### ━━━ ⚡ EXECUTE ━━━ 3/5
|
|
63
63
|
|
|
64
64
|
Do the work. Invoke selected capabilities via tool calls.
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ Do the work. Invoke selected capabilities via tool calls.
|
|
|
67
67
|
- If a criterion can't be met, flag it immediately — don't defer to VERIFY
|
|
68
68
|
- Make decisions explicit — state why you chose approach A over B
|
|
69
69
|
|
|
70
|
-
### ━━━ ✅ VERIFY ━━━ 4/
|
|
70
|
+
### ━━━ ✅ VERIFY ━━━ 4/5
|
|
71
71
|
|
|
72
72
|
No rubber-stamping. Each criterion needs specific evidence.
|
|
73
73
|
|
|
@@ -88,13 +88,30 @@ For EACH criterion:
|
|
|
88
88
|
|
|
89
89
|
If any criteria failed, fix and re-verify before completing.
|
|
90
90
|
|
|
91
|
+
### ━━━ 📚 LEARN ━━━ 5/5
|
|
92
|
+
|
|
93
|
+
Reflect on the work and capture reusable knowledge. Skip this phase when the work was trivial or purely mechanical.
|
|
94
|
+
|
|
95
|
+
**1. Reflection** (one sentence each):
|
|
96
|
+
- What would I do differently next time?
|
|
97
|
+
- What would a better algorithm have done differently?
|
|
98
|
+
|
|
99
|
+
**2. Wisdom Frame** — if the session produced a genuine, reusable insight:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bun ~/.agents/PAL/tools/wisdom-frame.ts --domain <domain> --observation "insight" [--type principle|contextual-rule|anti-pattern|evolution]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Domains: `development`, `workflow`, `communication`, `infrastructure`, `integration`, or any fitting domain.
|
|
106
|
+
Only write if the insight is **genuine and reusable** — not every session produces one. When in doubt, skip.
|
|
107
|
+
|
|
91
108
|
## Output Format
|
|
92
109
|
|
|
93
110
|
```
|
|
94
111
|
♻️ ALGORITHM ═══════════════════════════
|
|
95
112
|
🗒️ TASK: [brief description]
|
|
96
113
|
|
|
97
|
-
━━━ 👁️ OBSERVE ━━━ 1/
|
|
114
|
+
━━━ 👁️ OBSERVE ━━━ 1/5
|
|
98
115
|
🔎 REVERSE ENGINEERING:
|
|
99
116
|
[reverse engineering output]
|
|
100
117
|
|
|
@@ -103,18 +120,22 @@ If any criteria failed, fix and re-verify before completing.
|
|
|
103
120
|
|
|
104
121
|
🏹 CAPABILITIES: [selected capabilities]
|
|
105
122
|
|
|
106
|
-
━━━ 🧠 PLAN ━━━ 2/
|
|
123
|
+
━━━ 🧠 PLAN ━━━ 2/5
|
|
107
124
|
🧠 RISKS: [risks]
|
|
108
125
|
🧠 PREMORTEM: [failure modes]
|
|
109
126
|
📐 APPROACH: [execution plan]
|
|
110
127
|
|
|
111
|
-
━━━ ⚡ EXECUTE ━━━ 3/
|
|
128
|
+
━━━ ⚡ EXECUTE ━━━ 3/5
|
|
112
129
|
[work happens here]
|
|
113
130
|
|
|
114
|
-
━━━ ✅ VERIFY ━━━ 4/
|
|
131
|
+
━━━ ✅ VERIFY ━━━ 4/5
|
|
115
132
|
✅ VERIFICATION:
|
|
116
133
|
[criterion-by-criterion evidence]
|
|
117
134
|
|
|
118
135
|
🔧 CHANGE: [what changed]
|
|
119
136
|
🗣️ {{IDENTITY_NAME}}: [summary]
|
|
137
|
+
|
|
138
|
+
━━━ 📚 LEARN ━━━ 5/5
|
|
139
|
+
🪞 REFLECT: [what I'd do differently]
|
|
140
|
+
📝 WISDOM: [frame update if genuine insight, or "No new insight"]
|
|
120
141
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"prepare": "husky",
|
|
45
45
|
"install:all": "bun run src/cli/index.ts cli install",
|
|
46
46
|
"uninstall": "bun run src/cli/index.ts cli uninstall",
|
|
47
|
-
"tool:analyze": "bun run src/tools/analyze.ts",
|
|
47
|
+
"tool:analyze": "bun run src/tools/agent/analyze.ts",
|
|
48
|
+
"tool:wisdom-frame": "bun run src/tools/agent/wisdom-frame.ts",
|
|
48
49
|
"tool:reflect": "bun run src/tools/relationship-reflect.ts",
|
|
49
50
|
"tool:export": "bun run src/tools/export.ts",
|
|
50
51
|
"tool:import": "bun run src/tools/import.ts",
|
package/src/hooks/lib/paths.ts
CHANGED
|
@@ -81,5 +81,6 @@ export const assets = {
|
|
|
81
81
|
agentsMdTemplate: () => pkg("assets", "templates", "AGENTS.md.template"),
|
|
82
82
|
claudeSettingsTemplate: () => pkg("assets", "templates", "settings.claude.json"),
|
|
83
83
|
cursorHooksTemplate: () => pkg("assets", "templates", "hooks.cursor.json"),
|
|
84
|
+
agentTools: () => pkg("src", "tools", "agent"),
|
|
84
85
|
palDocs: () => pkg("assets", "templates", "PAL"),
|
|
85
86
|
} as const;
|
package/src/targets/lib.ts
CHANGED
|
@@ -273,10 +273,11 @@ export function copyPalDocs(): number {
|
|
|
273
273
|
count++;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
// Symlink ~/.agents/PAL/telos
|
|
276
|
+
// Symlink ~/.agents/PAL/{telos,memory,tools} → source locations
|
|
277
277
|
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
278
278
|
ensureSymlink(resolve(PAL_DOCS_DIR, "telos"), resolve(palHome(), "telos"), linkType);
|
|
279
279
|
ensureSymlink(resolve(PAL_DOCS_DIR, "memory"), resolve(palHome(), "memory"), linkType);
|
|
280
|
+
ensureSymlink(resolve(PAL_DOCS_DIR, "tools"), assets.agentTools(), linkType);
|
|
280
281
|
|
|
281
282
|
return count;
|
|
282
283
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Unified Learning Analysis — graduation patterns + ratings summary.
|
|
4
|
+
*
|
|
5
|
+
* Reads failures and session learnings, finds recurring patterns,
|
|
6
|
+
* summarizes ratings, and generates recommendations.
|
|
7
|
+
*
|
|
8
|
+
* Usage: bun run tool:analyze
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
import { type AnalysisResult, analyze } from "../../hooks/lib/graduation";
|
|
13
|
+
|
|
14
|
+
// ── ANSI Colors ──
|
|
15
|
+
|
|
16
|
+
const c = {
|
|
17
|
+
bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
|
|
18
|
+
dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
|
|
19
|
+
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
20
|
+
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
21
|
+
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
22
|
+
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
23
|
+
magenta: (s: string) => `\x1b[35m${s}\x1b[0m`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function printReport(result: AnalysisResult): void {
|
|
27
|
+
const hasPatterns = result.candidates.length > 0 || result.emerging.length > 0;
|
|
28
|
+
const hasRatings = result.ratings !== null;
|
|
29
|
+
|
|
30
|
+
if (!hasPatterns && !hasRatings) {
|
|
31
|
+
console.log("\n No patterns or ratings data found.\n");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (result.ratings) {
|
|
36
|
+
const r = result.ratings;
|
|
37
|
+
const avgColor = r.average >= 7 ? c.green : r.average <= 4 ? c.red : c.yellow;
|
|
38
|
+
console.log(
|
|
39
|
+
`\n ${c.bold("Ratings:")} ${avgColor(`${r.average.toFixed(1)}/10`)} avg (${r.total} total)`
|
|
40
|
+
);
|
|
41
|
+
console.log(
|
|
42
|
+
` ${c.red(`Low (≤4): ${r.low.count}`)} | ${c.green(`High (≥7): ${r.high.count}`)}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (result.candidates.length > 0) {
|
|
47
|
+
console.log(
|
|
48
|
+
`\n ${c.bold(c.green(`Graduation Report — ${result.candidates.length} pattern(s) detected`))}\n`
|
|
49
|
+
);
|
|
50
|
+
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
51
|
+
|
|
52
|
+
for (const candidate of result.candidates) {
|
|
53
|
+
console.log(
|
|
54
|
+
` ${c.cyan(`[${candidate.domain}]`)} ${c.bold(`${candidate.entries.length}x`)} occurrences`
|
|
55
|
+
);
|
|
56
|
+
console.log("");
|
|
57
|
+
|
|
58
|
+
for (const entry of candidate.entries) {
|
|
59
|
+
const sourceType = entry.source.startsWith("failure:") ? "failure" : "learning";
|
|
60
|
+
const tag =
|
|
61
|
+
sourceType === "failure"
|
|
62
|
+
? c.red(`[${sourceType}]`)
|
|
63
|
+
: c.yellow(`[${sourceType}]`);
|
|
64
|
+
console.log(
|
|
65
|
+
` ${c.dim(entry.date || "unknown")} ${tag} ${entry.text.slice(0, 100)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`\n ${c.dim("Files:")}`);
|
|
70
|
+
for (const entry of candidate.entries) {
|
|
71
|
+
console.log(` ${c.dim(entry.path)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(
|
|
76
|
+
` Target frame: ${c.magenta(`memory/wisdom/frames/${candidate.domain}.md`)}`
|
|
77
|
+
);
|
|
78
|
+
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (result.emerging.length > 0) {
|
|
83
|
+
console.log(` ${c.bold(c.yellow("Emerging (2x — one more to graduate)"))}\n`);
|
|
84
|
+
for (const group of result.emerging) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ${c.cyan(`[${group.domain}]`)} ${c.bold(`${group.entries.length}x`)}`
|
|
87
|
+
);
|
|
88
|
+
for (const entry of group.entries) {
|
|
89
|
+
const sourceType = entry.source.startsWith("failure:") ? "failure" : "learning";
|
|
90
|
+
const tag =
|
|
91
|
+
sourceType === "failure"
|
|
92
|
+
? c.red(`[${sourceType}]`)
|
|
93
|
+
: c.yellow(`[${sourceType}]`);
|
|
94
|
+
console.log(
|
|
95
|
+
` ${c.dim(entry.date || "unknown")} ${tag} ${entry.text.slice(0, 80)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
console.log(" Files:");
|
|
99
|
+
for (const entry of group.entries) {
|
|
100
|
+
console.log(` ${c.dim(entry.path)}`);
|
|
101
|
+
}
|
|
102
|
+
console.log("");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.recommendations.length > 0) {
|
|
107
|
+
console.log(` ${c.bold("Recommendations:")}\n`);
|
|
108
|
+
for (const rec of result.recommendations) {
|
|
109
|
+
console.log(` ${rec}`);
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (result.candidates.length > 0) {
|
|
115
|
+
console.log(` To crystallize: add a line to the wisdom frame file.`);
|
|
116
|
+
console.log(` Format: ${c.green("- Your principle here [CRYSTAL: 85%]")}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function run() {
|
|
121
|
+
const { values } = parseArgs({
|
|
122
|
+
args: Bun.argv.slice(2),
|
|
123
|
+
options: {
|
|
124
|
+
help: { type: "boolean", short: "h" },
|
|
125
|
+
actionable: { type: "boolean", short: "a" },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (values.help) {
|
|
130
|
+
console.log(`
|
|
131
|
+
PAL Learning Analysis — unified graduation + ratings report
|
|
132
|
+
|
|
133
|
+
Reads all captured failures (rating ≤3) and session learnings,
|
|
134
|
+
groups recurring patterns via Dice similarity on context text,
|
|
135
|
+
and summarizes rating trends.
|
|
136
|
+
|
|
137
|
+
Sections:
|
|
138
|
+
Ratings Overall average, low/high counts
|
|
139
|
+
Graduation Patterns with 3+ occurrences → ready to crystallize
|
|
140
|
+
Emerging Patterns with 2 occurrences → one more to graduate
|
|
141
|
+
|
|
142
|
+
Flags:
|
|
143
|
+
--actionable, -a Generate actionable recommendations via Haiku inference
|
|
144
|
+
|
|
145
|
+
To crystallize a graduated pattern, add it to the target wisdom frame:
|
|
146
|
+
- Your principle here [CRYSTAL: 85%]
|
|
147
|
+
|
|
148
|
+
Usage: bun run tool:analyze [--actionable] [--help]
|
|
149
|
+
`);
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = await analyze({ actionable: values.actionable });
|
|
154
|
+
printReport(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (import.meta.main) run();
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* WisdomFrameUpdater — Update wisdom frames with new observations.
|
|
4
|
+
*
|
|
5
|
+
* Takes a domain and observation, updates the appropriate frame file.
|
|
6
|
+
* Creates the frame if it doesn't exist. Tracks observation count and
|
|
7
|
+
* evolution log. Principles are marked [CRYSTAL: N%] manually when
|
|
8
|
+
* confidence is high enough.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* bun run tool:wisdom-frame --domain communication --observation "prefers bullet points"
|
|
12
|
+
* bun run tool:wisdom-frame --domain development --observation "refactoring without tests caused regressions" --type anti-pattern
|
|
13
|
+
* bun run tool:wisdom-frame --domain workflow --observation "always run type-check after edits" --type principle
|
|
14
|
+
*
|
|
15
|
+
* Types: principle, contextual-rule, anti-pattern, evolution (default)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { resolve } from "node:path";
|
|
20
|
+
import { parseArgs } from "node:util";
|
|
21
|
+
import { paths } from "../../hooks/lib/paths";
|
|
22
|
+
|
|
23
|
+
// ── Types ──
|
|
24
|
+
|
|
25
|
+
type ObservationType = "principle" | "contextual-rule" | "anti-pattern" | "evolution";
|
|
26
|
+
|
|
27
|
+
interface UpdateResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
domain: string;
|
|
30
|
+
type: ObservationType;
|
|
31
|
+
message: string;
|
|
32
|
+
framePath: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Helpers ──
|
|
36
|
+
|
|
37
|
+
function date(): string {
|
|
38
|
+
return new Date().toISOString().slice(0, 10);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseObservationCount(content: string): number {
|
|
42
|
+
const match = content.match(/\*\*Observation Count:\*\*\s*(\d+)/);
|
|
43
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function incrementCount(content: string): string {
|
|
47
|
+
const current = parseObservationCount(content);
|
|
48
|
+
return content.replace(/(\*\*Observation Count:\*\*\s*)\d+/, `$1${current + 1}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function updateDate(content: string): string {
|
|
52
|
+
return content.replace(/(\*\*Last Updated:\*\*\s*)\S+/, `$1${date()}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function appendToSection(
|
|
56
|
+
content: string,
|
|
57
|
+
sectionHeader: string,
|
|
58
|
+
entry: string,
|
|
59
|
+
fallbackBefore?: string
|
|
60
|
+
): string {
|
|
61
|
+
const idx = content.indexOf(sectionHeader);
|
|
62
|
+
|
|
63
|
+
if (idx === -1) {
|
|
64
|
+
// Section doesn't exist — insert before fallback or at end
|
|
65
|
+
const insertAt = fallbackBefore ? content.indexOf(fallbackBefore) : -1;
|
|
66
|
+
const pos = insertAt !== -1 ? insertAt : content.length;
|
|
67
|
+
return `${content.slice(0, pos)}${sectionHeader}\n\n${entry}\n\n${content.slice(pos)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Find end of section (next ## or EOF)
|
|
71
|
+
const afterSection = content.slice(idx + sectionHeader.length);
|
|
72
|
+
const nextSection = afterSection.indexOf("\n## ");
|
|
73
|
+
const insertPoint =
|
|
74
|
+
nextSection === -1 ? content.length : idx + sectionHeader.length + nextSection;
|
|
75
|
+
|
|
76
|
+
return `${content.slice(0, insertPoint)}\n${entry}${content.slice(insertPoint)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Core Update ──
|
|
80
|
+
|
|
81
|
+
export function updateFrame(
|
|
82
|
+
domain: string,
|
|
83
|
+
observation: string,
|
|
84
|
+
type: ObservationType = "evolution"
|
|
85
|
+
): UpdateResult {
|
|
86
|
+
const framesDir = paths.wisdom();
|
|
87
|
+
const framePath = resolve(framesDir, `${domain}.md`);
|
|
88
|
+
|
|
89
|
+
// Create frame if it doesn't exist
|
|
90
|
+
if (!existsSync(framePath)) {
|
|
91
|
+
mkdirSync(framesDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
const content = `# Frame: ${domain.charAt(0).toUpperCase() + domain.slice(1)}
|
|
94
|
+
|
|
95
|
+
## Meta
|
|
96
|
+
- **Domain:** ${domain}
|
|
97
|
+
- **Observation Count:** 1
|
|
98
|
+
- **Last Updated:** ${date()}
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Core Principles
|
|
103
|
+
|
|
104
|
+
*No crystallized principles yet. Observations accumulating.*
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Contextual Rules
|
|
109
|
+
|
|
110
|
+
${type === "contextual-rule" ? `- ${observation} (${date()})` : "*None yet.*"}
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Anti-Patterns
|
|
115
|
+
|
|
116
|
+
${type === "anti-pattern" ? `### ${observation}\n- **Severity:** Medium\n- **Frequency:** Observed` : "*None yet.*"}
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Evolution Log
|
|
121
|
+
- ${date()}: Frame created — ${observation}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
writeFileSync(framePath, content);
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
domain,
|
|
128
|
+
type,
|
|
129
|
+
message: `Created new frame "${domain}" with initial observation`,
|
|
130
|
+
framePath,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update existing frame
|
|
135
|
+
let content = readFileSync(framePath, "utf-8");
|
|
136
|
+
content = incrementCount(content);
|
|
137
|
+
content = updateDate(content);
|
|
138
|
+
|
|
139
|
+
const evolutionEntry = `- ${date()}: ${observation}`;
|
|
140
|
+
|
|
141
|
+
switch (type) {
|
|
142
|
+
case "anti-pattern":
|
|
143
|
+
content = appendToSection(
|
|
144
|
+
content,
|
|
145
|
+
"## Anti-Patterns",
|
|
146
|
+
`\n### ${observation}\n- **Severity:** Medium\n- **Frequency:** Observed`,
|
|
147
|
+
"## Evolution Log"
|
|
148
|
+
);
|
|
149
|
+
content = appendToSection(content, "## Evolution Log", evolutionEntry);
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case "contextual-rule":
|
|
153
|
+
content = appendToSection(
|
|
154
|
+
content,
|
|
155
|
+
"## Contextual Rules",
|
|
156
|
+
`- ${observation} (${date()})`,
|
|
157
|
+
"## Anti-Patterns"
|
|
158
|
+
);
|
|
159
|
+
content = appendToSection(content, "## Evolution Log", evolutionEntry);
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case "principle":
|
|
163
|
+
// Principles logged for manual crystallization — don't auto-add to Core Principles
|
|
164
|
+
content = appendToSection(
|
|
165
|
+
content,
|
|
166
|
+
"## Evolution Log",
|
|
167
|
+
`- ${date()}: Principle candidate — ${observation}`
|
|
168
|
+
);
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case "evolution":
|
|
172
|
+
default:
|
|
173
|
+
content = appendToSection(content, "## Evolution Log", evolutionEntry);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
writeFileSync(framePath, content);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
domain,
|
|
182
|
+
type,
|
|
183
|
+
message: `Updated "${domain}" frame with ${type}: ${observation}`,
|
|
184
|
+
framePath,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── CLI ──
|
|
189
|
+
|
|
190
|
+
function run() {
|
|
191
|
+
const { values } = parseArgs({
|
|
192
|
+
args: Bun.argv.slice(2),
|
|
193
|
+
options: {
|
|
194
|
+
domain: { type: "string", short: "d" },
|
|
195
|
+
observation: { type: "string", short: "o" },
|
|
196
|
+
type: { type: "string", short: "t" },
|
|
197
|
+
help: { type: "boolean", short: "h" },
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (values.help) {
|
|
202
|
+
console.log(`
|
|
203
|
+
WisdomFrameUpdater — Update wisdom frames with observations
|
|
204
|
+
|
|
205
|
+
Usage:
|
|
206
|
+
bun run tool:wisdom-frame --domain <domain> --observation "text" [--type <type>]
|
|
207
|
+
|
|
208
|
+
Domains:
|
|
209
|
+
development, workflow, communication, infrastructure, integration, or any custom domain
|
|
210
|
+
|
|
211
|
+
Types:
|
|
212
|
+
principle High-confidence pattern (logged for manual crystallization)
|
|
213
|
+
contextual-rule Context-specific behavioral rule
|
|
214
|
+
anti-pattern Something to avoid
|
|
215
|
+
evolution General observation (default)
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
bun run tool:wisdom-frame -d workflow -o "always run type-check after edits"
|
|
219
|
+
bun run tool:wisdom-frame -d development -o "mocking DB hides migration bugs" -t anti-pattern
|
|
220
|
+
bun run tool:wisdom-frame -d communication -o "user prefers terse summaries" -t principle
|
|
221
|
+
`);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!values.domain || !values.observation) {
|
|
226
|
+
console.error("Required: --domain and --observation");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const cliType = (values.type || "evolution") as ObservationType;
|
|
231
|
+
const result = updateFrame(values.domain, values.observation, cliType);
|
|
232
|
+
console.log(JSON.stringify(result, null, 2));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (import.meta.main) run();
|
package/src/tools/export.ts
CHANGED
|
@@ -10,25 +10,31 @@ import { resolve } from "node:path";
|
|
|
10
10
|
import { collectExportFiles, exportZip, timestamp } from "../hooks/lib/export";
|
|
11
11
|
import { palHome } from "../hooks/lib/paths";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const dryRun = args.includes("--dry-run");
|
|
15
|
-
const pathArg = args.find((a) => a !== "--dry-run");
|
|
13
|
+
export { collectExportFiles, exportZip };
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
function run() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const dryRun = args.includes("--dry-run");
|
|
18
|
+
const pathArg = args.find((a) => a !== "--dry-run");
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (count === 0) {
|
|
30
|
-
console.log("Nothing to export — no gitignored personal files found.");
|
|
20
|
+
const outputPath = pathArg || resolve(palHome(), `pal-export-${timestamp()}.zip`);
|
|
21
|
+
|
|
22
|
+
if (dryRun) {
|
|
23
|
+
const files = collectExportFiles();
|
|
24
|
+
if (files.length === 0) {
|
|
25
|
+
console.log("Nothing to export — no gitignored personal files found.");
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`Would export ${files.length} files → ${outputPath}\n`);
|
|
28
|
+
for (const f of files) console.log(` ${f}`);
|
|
29
|
+
}
|
|
31
30
|
} else {
|
|
32
|
-
|
|
31
|
+
const count = exportZip(outputPath);
|
|
32
|
+
if (count === 0) {
|
|
33
|
+
console.log("Nothing to export — no gitignored personal files found.");
|
|
34
|
+
} else {
|
|
35
|
+
console.log(`Exported ${count} files → ${outputPath}`);
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
}
|
|
39
|
+
|
|
40
|
+
if (import.meta.main) run();
|