portable-agent-layer 0.70.0 → 0.72.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 +5 -1
- package/assets/schema/pal-settings.schema.json +4 -0
- package/assets/skills/onboarding/SKILL.md +109 -0
- package/assets/skills/projects/SKILL.md +11 -2
- package/assets/templates/pal-settings.json +1 -0
- package/package.json +5 -1
- package/src/cli/index.ts +39 -12
- package/src/cli/migrate.ts +1 -1
- package/src/cli/personal-context.ts +67 -0
- package/src/cli/server.ts +13 -7
- package/src/cli/setup-identity.ts +13 -1
- package/src/cli/skill.ts +1 -1
- package/src/hooks/CompactRecover.ts +28 -86
- package/src/hooks/LedgerUnapplied.ts +3 -28
- package/src/hooks/LoadContext.ts +33 -60
- package/src/hooks/SecurityValidator.ts +16 -109
- package/src/hooks/handlers/agenda.ts +223 -0
- package/src/hooks/handlers/failure-principle.ts +19 -44
- package/src/hooks/handlers/inject-retrieval.ts +6 -2
- package/src/hooks/handlers/session-intelligence.ts +13 -70
- package/src/hooks/lib/agenda-store.ts +41 -0
- package/src/hooks/lib/capture-store.ts +103 -0
- package/src/hooks/lib/compact-recall.ts +89 -0
- package/src/hooks/lib/failure-principle.ts +98 -0
- package/src/hooks/lib/ledger-hook.ts +35 -0
- package/src/hooks/lib/ledger.ts +48 -1
- package/src/hooks/lib/paths.ts +0 -1
- package/src/hooks/lib/projects.ts +16 -1
- package/src/hooks/lib/security-gate.ts +159 -0
- package/src/hooks/lib/serves.ts +60 -0
- package/src/hooks/lib/session-context.ts +74 -0
- package/src/hooks/lib/stop.ts +14 -0
- package/src/hooks/lib/telos-goals.ts +144 -0
- package/src/hooks/lib/telos-topics.ts +68 -0
- package/src/hooks/lib/token-usage.ts +3 -1
- package/src/hooks/lib/wall-clock.ts +58 -0
- package/src/tools/agent/algorithm-reflect.ts +28 -97
- package/src/tools/agent/analyze.ts +19 -120
- package/src/tools/agent/handoff-note.ts +40 -70
- package/src/tools/agent/project.ts +47 -136
- package/src/tools/agent/relationship-note.ts +27 -46
- package/src/tools/agent/synthesize.ts +1 -1
- package/src/tools/agent/thread.ts +43 -123
- package/src/tools/control-room/data.ts +332 -0
- package/src/tools/control-room/matrix.ts +182 -0
- package/src/tools/control-room/server.ts +150 -0
- package/src/tools/control-room/ui/agenda.tsx +43 -0
- package/src/tools/control-room/ui/agents.tsx +67 -0
- package/src/tools/control-room/ui/app.css +857 -0
- package/src/tools/control-room/ui/app.tsx +74 -0
- package/src/tools/control-room/ui/board.tsx +82 -0
- package/src/tools/control-room/ui/format.ts +31 -0
- package/src/tools/control-room/ui/handoffs.tsx +37 -0
- package/src/tools/control-room/ui/index.html +19 -0
- package/src/tools/control-room/ui/ledger.tsx +137 -0
- package/src/tools/control-room/ui/matrix.tsx +117 -0
- package/src/tools/control-room/ui/panel.tsx +60 -0
- package/src/tools/control-room/ui/signal.tsx +161 -0
- package/src/tools/ledger/view.ts +3 -0
- package/src/tools/lib/algorithm-reflect.ts +84 -0
- package/src/tools/lib/analyze-report.ts +120 -0
- package/src/tools/lib/handoff-note.ts +88 -0
- package/src/tools/lib/note-flags.ts +59 -0
- package/src/tools/lib/project-isc.ts +151 -0
- package/src/tools/lib/relationship-reflect.ts +402 -0
- package/src/tools/lib/self-model.ts +499 -0
- package/src/tools/lib/session-usage.ts +216 -0
- package/src/tools/lib/skill-doctor.ts +457 -0
- package/src/tools/lib/thread.ts +119 -0
- package/src/tools/lib/token-report.ts +173 -0
- package/src/tools/lib/transcript-usage.ts +42 -0
- package/src/tools/lib/usage-buckets.ts +329 -0
- package/src/tools/relationship-reflect.ts +48 -412
- package/src/tools/self-model.ts +76 -558
- package/src/tools/session-summary.ts +8 -215
- package/src/tools/skill-doctor.ts +9 -444
- package/src/tools/token-cost.ts +18 -428
- package/assets/templates/ledger-page.html +0 -213
- package/src/cli/setup-telos.ts +0 -52
- package/src/hooks/lib/setup.ts +0 -60
- package/src/tools/ledger/server.ts +0 -111
|
@@ -15,87 +15,36 @@
|
|
|
15
15
|
|
|
16
16
|
import { appendFileSync } from "node:fs";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
|
-
import { currentAttribution, type RecordAttribution } from "../../hooks/lib/actor";
|
|
19
|
-
import { encodeAnchor } from "../../hooks/lib/anchor";
|
|
20
18
|
import { paths } from "../../hooks/lib/paths";
|
|
19
|
+
import { buildReflection, intOr, reflectionLine } from "../lib/algorithm-reflect";
|
|
21
20
|
import { emit } from "../lib/emit";
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
const HELP = `
|
|
23
|
+
AlgorithmReflect — Log algorithm performance after LEARN phase
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
Usage:
|
|
26
|
+
bun run tool:algorithm-reflect --task "description" --criteria N --passed N --failed N --sentiment 1-10 \\
|
|
27
|
+
--q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
|
|
28
|
+
|
|
29
|
+
Arguments:
|
|
30
|
+
--task Brief task description
|
|
31
|
+
--criteria Total criteria count
|
|
32
|
+
--passed Criteria passed
|
|
33
|
+
--failed Criteria failed
|
|
34
|
+
--sentiment Implied satisfaction 1-10
|
|
35
|
+
--q1 Q1 — Self: what I'd do differently
|
|
36
|
+
--q2 Q2 — Algorithm: structural improvement
|
|
37
|
+
--q3 Q3 — AI: reasoning blind spot
|
|
38
|
+
--scope general (default) | task-specific — is the algorithm idea reusable or task-bound?
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Output: algorithm-reflections.jsonl in memory/learning/reflections/
|
|
41
|
+
`;
|
|
41
42
|
|
|
42
43
|
function reflectionsPath(): string {
|
|
43
|
-
paths.reflections();
|
|
44
|
+
paths.reflections();
|
|
44
45
|
return paths.reflectionsFile();
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
* Assemble a reflection record from CLI-style input, stamping the current
|
|
49
|
-
* cwd (anchored) and this machine's id. Exported so the stamping logic is
|
|
50
|
-
* directly testable without going through argv parsing.
|
|
51
|
-
*/
|
|
52
|
-
export function buildReflection(input: {
|
|
53
|
-
task: string;
|
|
54
|
-
q1: string;
|
|
55
|
-
q2: string;
|
|
56
|
-
q3: string;
|
|
57
|
-
criteria_count?: number;
|
|
58
|
-
criteria_passed?: number;
|
|
59
|
-
criteria_failed?: number;
|
|
60
|
-
sentiment?: number;
|
|
61
|
-
scope?: string;
|
|
62
|
-
}): AlgorithmReflection {
|
|
63
|
-
return {
|
|
64
|
-
timestamp: new Date().toISOString(),
|
|
65
|
-
cwd: encodeAnchor(process.cwd()),
|
|
66
|
-
...currentAttribution(),
|
|
67
|
-
task: input.task,
|
|
68
|
-
criteria_count: input.criteria_count ?? 0,
|
|
69
|
-
criteria_passed: input.criteria_passed ?? 0,
|
|
70
|
-
criteria_failed: input.criteria_failed ?? 0,
|
|
71
|
-
sentiment: Math.max(1, Math.min(10, input.sentiment ?? 5)),
|
|
72
|
-
q1: input.q1,
|
|
73
|
-
q2: input.q2,
|
|
74
|
-
q3: input.q3,
|
|
75
|
-
// Default to general (the ~94% case); only "task-specific" suppresses it
|
|
76
|
-
// from algorithm-update clustering.
|
|
77
|
-
scope: input.scope === "task-specific" ? "task-specific" : "general",
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function appendReflection(reflection: AlgorithmReflection): {
|
|
82
|
-
success: boolean;
|
|
83
|
-
message: string;
|
|
84
|
-
path: string;
|
|
85
|
-
} {
|
|
86
|
-
const p = reflectionsPath();
|
|
87
|
-
const line = `${JSON.stringify(reflection)}\n`;
|
|
88
|
-
appendFileSync(p, line, "utf-8");
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
success: true,
|
|
92
|
-
message: `Reflection logged: ${reflection.criteria_passed}/${reflection.criteria_count} passed, sentiment ${reflection.sentiment}/10`,
|
|
93
|
-
path: p,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── CLI ──
|
|
98
|
-
|
|
99
48
|
function run() {
|
|
100
49
|
const { values } = parseArgs({
|
|
101
50
|
args: Bun.argv.slice(2),
|
|
@@ -114,26 +63,7 @@ function run() {
|
|
|
114
63
|
});
|
|
115
64
|
|
|
116
65
|
if (values.help) {
|
|
117
|
-
console.log(
|
|
118
|
-
AlgorithmReflect — Log algorithm performance after LEARN phase
|
|
119
|
-
|
|
120
|
-
Usage:
|
|
121
|
-
bun run tool:algorithm-reflect --task "description" --criteria N --passed N --failed N --sentiment 1-10 \\
|
|
122
|
-
--q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
|
|
123
|
-
|
|
124
|
-
Arguments:
|
|
125
|
-
--task Brief task description
|
|
126
|
-
--criteria Total criteria count
|
|
127
|
-
--passed Criteria passed
|
|
128
|
-
--failed Criteria failed
|
|
129
|
-
--sentiment Implied satisfaction 1-10
|
|
130
|
-
--q1 Q1 — Self: what I'd do differently
|
|
131
|
-
--q2 Q2 — Algorithm: structural improvement
|
|
132
|
-
--q3 Q3 — AI: reasoning blind spot
|
|
133
|
-
--scope general (default) | task-specific — is the algorithm idea reusable or task-bound?
|
|
134
|
-
|
|
135
|
-
Output: algorithm-reflections.jsonl in memory/learning/reflections/
|
|
136
|
-
`);
|
|
66
|
+
console.log(HELP);
|
|
137
67
|
process.exit(0);
|
|
138
68
|
}
|
|
139
69
|
|
|
@@ -147,15 +77,16 @@ Output: algorithm-reflections.jsonl in memory/learning/reflections/
|
|
|
147
77
|
q1: values.q1,
|
|
148
78
|
q2: values.q2,
|
|
149
79
|
q3: values.q3,
|
|
150
|
-
criteria_count:
|
|
151
|
-
criteria_passed:
|
|
152
|
-
criteria_failed:
|
|
153
|
-
sentiment:
|
|
80
|
+
criteria_count: intOr(values.criteria, 0),
|
|
81
|
+
criteria_passed: intOr(values.passed, 0),
|
|
82
|
+
criteria_failed: intOr(values.failed, 0),
|
|
83
|
+
sentiment: intOr(values.sentiment, 5),
|
|
154
84
|
scope: values.scope,
|
|
155
85
|
});
|
|
156
86
|
|
|
157
|
-
const
|
|
158
|
-
|
|
87
|
+
const path = reflectionsPath();
|
|
88
|
+
appendFileSync(path, reflectionLine(reflection), "utf-8");
|
|
89
|
+
emit.receipt(path, {
|
|
159
90
|
passed: reflection.criteria_passed,
|
|
160
91
|
of: reflection.criteria_count,
|
|
161
92
|
scope: reflection.scope,
|
|
@@ -5,130 +5,17 @@
|
|
|
5
5
|
* Reads failures and session learnings, finds recurring patterns,
|
|
6
6
|
* summarizes ratings, and generates recommendations.
|
|
7
7
|
*
|
|
8
|
+
* What the report says is in lib/analyze-report.ts.
|
|
9
|
+
*
|
|
8
10
|
* Usage: bun run tool:analyze
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { parseArgs } from "node:util";
|
|
12
14
|
import { writeLastAnalyzeDate } from "../../hooks/lib/analyze-nudge";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
// ── ANSI Colors ──
|
|
16
|
-
|
|
17
|
-
const c = {
|
|
18
|
-
bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
|
|
19
|
-
dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
|
|
20
|
-
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
21
|
-
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
22
|
-
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
23
|
-
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
24
|
-
magenta: (s: string) => `\x1b[35m${s}\x1b[0m`,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function printReport(result: AnalysisResult): void {
|
|
28
|
-
const hasPatterns = result.candidates.length > 0 || result.emerging.length > 0;
|
|
29
|
-
const hasRatings = result.ratings !== null;
|
|
30
|
-
|
|
31
|
-
if (!hasPatterns && !hasRatings) {
|
|
32
|
-
console.log("\n No patterns or ratings data found.\n");
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (result.ratings) {
|
|
37
|
-
const r = result.ratings;
|
|
38
|
-
const lowOrMid = r.average <= 4 ? c.red : c.yellow;
|
|
39
|
-
const avgColor = r.average >= 7 ? c.green : lowOrMid;
|
|
40
|
-
const ratingStr = `${r.average.toFixed(1)}/10`;
|
|
41
|
-
const lowStr = `Low (≤4): ${r.low.count}`;
|
|
42
|
-
const highStr = `High (≥7): ${r.high.count}`;
|
|
43
|
-
console.log(
|
|
44
|
-
`\n ${c.bold("Ratings:")} ${avgColor(ratingStr)} avg (${r.total} total)`
|
|
45
|
-
);
|
|
46
|
-
console.log(` ${c.red(lowStr)} | ${c.green(highStr)}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (result.candidates.length > 0) {
|
|
50
|
-
const graduationHeader = `Graduation Report — ${result.candidates.length} pattern(s) detected`;
|
|
51
|
-
console.log(`\n ${c.bold(c.green(graduationHeader))}\n`);
|
|
52
|
-
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
53
|
-
|
|
54
|
-
for (const candidate of result.candidates) {
|
|
55
|
-
const domain = `[${candidate.domain}]`;
|
|
56
|
-
const count = `${candidate.entries.length}x`;
|
|
57
|
-
console.log(` ${c.cyan(domain)} ${c.bold(count)} occurrences`);
|
|
58
|
-
console.log("");
|
|
59
|
-
|
|
60
|
-
for (const entry of candidate.entries) {
|
|
61
|
-
const sourceType = entry.source.startsWith("failure:") ? "failure" : "learning";
|
|
62
|
-
const tag =
|
|
63
|
-
sourceType === "failure"
|
|
64
|
-
? c.red(`[${sourceType}]`)
|
|
65
|
-
: c.yellow(`[${sourceType}]`);
|
|
66
|
-
console.log(
|
|
67
|
-
` ${c.dim(entry.date || "unknown")} ${tag} ${entry.text.slice(0, 100)}`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log(`\n ${c.dim("Files:")}`);
|
|
72
|
-
for (const entry of candidate.entries) {
|
|
73
|
-
console.log(` ${c.dim(entry.path)}`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log("");
|
|
77
|
-
const framePath = `memory/wisdom/frames/${candidate.domain}.md`;
|
|
78
|
-
console.log(` Target frame: ${c.magenta(framePath)}`);
|
|
79
|
-
console.log(` ${c.dim("─────────────────────────────────────────────────")}\n`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (result.emerging.length > 0) {
|
|
84
|
-
console.log(` ${c.bold(c.yellow("Emerging (2x — one more to graduate)"))}\n`);
|
|
85
|
-
for (const group of result.emerging) {
|
|
86
|
-
const domain = `[${group.domain}]`;
|
|
87
|
-
const count = `${group.entries.length}x`;
|
|
88
|
-
console.log(` ${c.cyan(domain)} ${c.bold(count)}`);
|
|
89
|
-
for (const entry of group.entries) {
|
|
90
|
-
const sourceType = entry.source.startsWith("failure:") ? "failure" : "learning";
|
|
91
|
-
const tag =
|
|
92
|
-
sourceType === "failure"
|
|
93
|
-
? c.red(`[${sourceType}]`)
|
|
94
|
-
: c.yellow(`[${sourceType}]`);
|
|
95
|
-
console.log(
|
|
96
|
-
` ${c.dim(entry.date || "unknown")} ${tag} ${entry.text.slice(0, 80)}`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
console.log(" Files:");
|
|
100
|
-
for (const entry of group.entries) {
|
|
101
|
-
console.log(` ${c.dim(entry.path)}`);
|
|
102
|
-
}
|
|
103
|
-
console.log("");
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (result.recommendations.length > 0) {
|
|
108
|
-
console.log(` ${c.bold("Recommendations:")}\n`);
|
|
109
|
-
for (const rec of result.recommendations) {
|
|
110
|
-
console.log(` ${rec}`);
|
|
111
|
-
}
|
|
112
|
-
console.log("");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (result.candidates.length > 0) {
|
|
116
|
-
console.log(` To crystallize: add a line to the wisdom frame file.`);
|
|
117
|
-
console.log(` Format: ${c.green("- Your principle here [CRYSTAL: 85%]")}\n`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
15
|
+
import { analyze } from "../../hooks/lib/graduation";
|
|
16
|
+
import { reportLines } from "../lib/analyze-report";
|
|
120
17
|
|
|
121
|
-
|
|
122
|
-
const { values } = parseArgs({
|
|
123
|
-
args: argv,
|
|
124
|
-
options: {
|
|
125
|
-
help: { type: "boolean", short: "h" },
|
|
126
|
-
actionable: { type: "boolean", short: "a" },
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (values.help) {
|
|
131
|
-
console.log(`
|
|
18
|
+
const HELP = `
|
|
132
19
|
PAL Learning Analysis — unified graduation + ratings report
|
|
133
20
|
|
|
134
21
|
Reads all captured failures (rating ≤3) and session learnings,
|
|
@@ -147,12 +34,24 @@ export async function run(argv: string[] = Bun.argv.slice(2)) {
|
|
|
147
34
|
- Your principle here [CRYSTAL: 85%]
|
|
148
35
|
|
|
149
36
|
Usage: pal cli analyze [--actionable]
|
|
150
|
-
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export async function run(argv: string[] = Bun.argv.slice(2)) {
|
|
40
|
+
const { values } = parseArgs({
|
|
41
|
+
args: argv,
|
|
42
|
+
options: {
|
|
43
|
+
help: { type: "boolean", short: "h" },
|
|
44
|
+
actionable: { type: "boolean", short: "a" },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (values.help) {
|
|
49
|
+
console.log(HELP);
|
|
151
50
|
process.exit(0);
|
|
152
51
|
}
|
|
153
52
|
|
|
154
53
|
const result = await analyze({ actionable: values.actionable });
|
|
155
|
-
|
|
54
|
+
for (const line of reportLines(result)) console.log(line);
|
|
156
55
|
writeLastAnalyzeDate(new Date().toISOString());
|
|
157
56
|
}
|
|
158
57
|
|
|
@@ -10,60 +10,38 @@
|
|
|
10
10
|
* bun ~/.pal/tools/handoff-note.ts --done # mark completed, suppress next-session injection
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { resolve } from "node:path";
|
|
13
|
+
import { writeFileSync } from "node:fs";
|
|
15
14
|
import { parseArgs } from "node:util";
|
|
16
|
-
import { ensureDir, paths } from "../../hooks/lib/paths";
|
|
17
15
|
import { emit } from "../lib/emit";
|
|
16
|
+
import {
|
|
17
|
+
handoffFile,
|
|
18
|
+
type NoteInput,
|
|
19
|
+
readHandoffs,
|
|
20
|
+
recordNote,
|
|
21
|
+
statusOf,
|
|
22
|
+
} from "../lib/handoff-note";
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
title: string;
|
|
22
|
-
status: "in-progress" | "completed";
|
|
23
|
-
handoff: string;
|
|
24
|
-
artifacts: string[];
|
|
25
|
-
source: "deliberate" | "auto";
|
|
26
|
-
}
|
|
24
|
+
const HELP = `
|
|
25
|
+
HandoffNote — Write a handoff note for the current project
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
Usage:
|
|
28
|
+
bun ~/.pal/tools/handoff-note.ts --title "what we were doing" --text "what remains"
|
|
29
|
+
bun ~/.pal/tools/handoff-note.ts --done # mark session completed
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
31
|
+
Arguments:
|
|
32
|
+
--title Brief title of what was being worked on (5-10 words)
|
|
33
|
+
--text What remains unfinished — decisions made, next steps, blockers
|
|
34
|
+
--waiting What this needs from you before it can move (a decision, an answer, access)
|
|
35
|
+
--done Mark as completed; suppresses "pick up where you left off" injection
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const entries = Object.entries(handoffs);
|
|
45
|
-
const trimmed = entries.length > 20 ? Object.fromEntries(entries.slice(-20)) : handoffs;
|
|
46
|
-
writeFileSync(handoffPath(), JSON.stringify(trimmed, null, 2), "utf-8");
|
|
47
|
-
return Object.keys(trimmed).length;
|
|
48
|
-
}
|
|
37
|
+
Output: writes to memory/state/last-handoff.json keyed by cwd
|
|
38
|
+
`;
|
|
49
39
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
): { file: string; status: HandoffEntry["status"]; kept: number } {
|
|
56
|
-
const handoffs = readHandoffs();
|
|
57
|
-
handoffs[cwd] = {
|
|
58
|
-
timestamp: new Date().toISOString(),
|
|
59
|
-
title,
|
|
60
|
-
status: done ? "completed" : "in-progress",
|
|
61
|
-
handoff: text,
|
|
62
|
-
artifacts: [],
|
|
63
|
-
source: "deliberate",
|
|
64
|
-
};
|
|
65
|
-
const kept = writeHandoffs(handoffs);
|
|
66
|
-
return { file: handoffPath(), status: handoffs[cwd].status, kept };
|
|
40
|
+
function saveNote(note: NoteInput): void {
|
|
41
|
+
const file = handoffFile();
|
|
42
|
+
const store = recordNote(readHandoffs(file), note, new Date());
|
|
43
|
+
writeFileSync(file, JSON.stringify(store, null, 2), "utf-8");
|
|
44
|
+
emit.receipt(file, { status: statusOf(note), entries: Object.keys(store).length });
|
|
67
45
|
}
|
|
68
46
|
|
|
69
47
|
function run() {
|
|
@@ -72,37 +50,24 @@ function run() {
|
|
|
72
50
|
options: {
|
|
73
51
|
title: { type: "string" },
|
|
74
52
|
text: { type: "string" },
|
|
53
|
+
waiting: { type: "string" },
|
|
75
54
|
done: { type: "boolean" },
|
|
76
55
|
help: { type: "boolean", short: "h" },
|
|
77
56
|
},
|
|
78
57
|
});
|
|
79
58
|
|
|
80
59
|
if (values.help) {
|
|
81
|
-
console.log(
|
|
82
|
-
HandoffNote — Write a handoff note for the current project
|
|
83
|
-
|
|
84
|
-
Usage:
|
|
85
|
-
bun ~/.pal/tools/handoff-note.ts --title "what we were doing" --text "what remains"
|
|
86
|
-
bun ~/.pal/tools/handoff-note.ts --done # mark session completed
|
|
87
|
-
|
|
88
|
-
Arguments:
|
|
89
|
-
--title Brief title of what was being worked on (5-10 words)
|
|
90
|
-
--text What remains unfinished — decisions made, next steps, blockers
|
|
91
|
-
--done Mark as completed; suppresses "pick up where you left off" injection
|
|
92
|
-
|
|
93
|
-
Output: writes to memory/state/last-handoff.json keyed by cwd
|
|
94
|
-
`);
|
|
60
|
+
console.log(HELP);
|
|
95
61
|
process.exit(0);
|
|
96
62
|
}
|
|
97
63
|
|
|
98
64
|
if (values.done) {
|
|
99
|
-
|
|
100
|
-
process.cwd(),
|
|
101
|
-
values.title || "session",
|
|
102
|
-
values.text || "",
|
|
103
|
-
true
|
|
104
|
-
);
|
|
105
|
-
emit.receipt(result.file, { status: result.status, entries: result.kept });
|
|
65
|
+
saveNote({
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
title: values.title || "session",
|
|
68
|
+
text: values.text || "",
|
|
69
|
+
done: true,
|
|
70
|
+
});
|
|
106
71
|
process.exit(0);
|
|
107
72
|
}
|
|
108
73
|
|
|
@@ -111,8 +76,13 @@ Output: writes to memory/state/last-handoff.json keyed by cwd
|
|
|
111
76
|
process.exit(1);
|
|
112
77
|
}
|
|
113
78
|
|
|
114
|
-
|
|
115
|
-
|
|
79
|
+
saveNote({
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
title: values.title,
|
|
82
|
+
text: values.text,
|
|
83
|
+
done: false,
|
|
84
|
+
waitingOn: values.waiting,
|
|
85
|
+
});
|
|
116
86
|
}
|
|
117
87
|
|
|
118
88
|
if (import.meta.main) run();
|