portable-agent-layer 0.3.0 → 0.4.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/package.json +2 -1
- package/src/cli/index.ts +72 -2
- package/src/hooks/handlers/failure.ts +49 -44
- package/src/hooks/handlers/rating.ts +12 -18
- package/src/hooks/handlers/work-learning.ts +28 -13
- package/src/hooks/lib/context.ts +82 -24
- package/src/hooks/lib/frontmatter.ts +95 -0
- package/src/hooks/lib/graduation.ts +499 -0
- package/src/hooks/lib/models.ts +4 -4
- package/src/hooks/lib/security.ts +2 -0
- package/src/hooks/lib/tags.ts +89 -0
- package/src/targets/opencode/plugin.ts +7 -6
- package/src/tools/graduate.ts +51 -0
- package/src/tools/pattern-synthesis.ts +11 -14
- package/src/tools/token-cost.ts +35 -5
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { resolve } from "node:path";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
|
+
import { stringify } from "../hooks/lib/frontmatter";
|
|
18
19
|
import { HAIKU_MODEL } from "../hooks/lib/models";
|
|
19
20
|
import { palHome } from "../hooks/lib/paths";
|
|
20
21
|
|
|
@@ -260,19 +261,15 @@ async function analyzeRatings(
|
|
|
260
261
|
|
|
261
262
|
function formatReport(result: SynthesisResult): string {
|
|
262
263
|
const date = new Date().toISOString().slice(0, 10);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
"",
|
|
273
|
-
"## Top Issues",
|
|
274
|
-
"",
|
|
275
|
-
];
|
|
264
|
+
|
|
265
|
+
const meta: Record<string, unknown> = {
|
|
266
|
+
period: result.period,
|
|
267
|
+
date,
|
|
268
|
+
total_ratings: result.totalRatings,
|
|
269
|
+
average_rating: result.avgRating.toFixed(1),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const lines: string[] = ["## Top Issues", ""];
|
|
276
273
|
|
|
277
274
|
if (result.topIssues.length > 0) {
|
|
278
275
|
for (let i = 0; i < result.topIssues.length; i++) {
|
|
@@ -324,7 +321,7 @@ function formatReport(result: SynthesisResult): string {
|
|
|
324
321
|
""
|
|
325
322
|
);
|
|
326
323
|
|
|
327
|
-
return lines.join("\n");
|
|
324
|
+
return stringify(meta, lines.join("\n"));
|
|
328
325
|
}
|
|
329
326
|
|
|
330
327
|
function writeReport(result: SynthesisResult, period: string): string {
|
package/src/tools/token-cost.ts
CHANGED
|
@@ -47,6 +47,16 @@ function emptyBucket(): Bucket {
|
|
|
47
47
|
return { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0, calls: 0 };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function findPricing(model: string): (typeof MODEL_PRICING)[string] | null {
|
|
51
|
+
// Exact match first
|
|
52
|
+
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
53
|
+
// Prefix match (e.g. "claude-sonnet-4-5-20250929" matches "claude-sonnet-4-5")
|
|
54
|
+
for (const key of Object.keys(MODEL_PRICING)) {
|
|
55
|
+
if (model.startsWith(key)) return MODEL_PRICING[key];
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
function costForUsage(
|
|
51
61
|
model: string,
|
|
52
62
|
input: number,
|
|
@@ -54,7 +64,7 @@ function costForUsage(
|
|
|
54
64
|
cacheWrite: number,
|
|
55
65
|
cacheRead: number
|
|
56
66
|
): number {
|
|
57
|
-
const p =
|
|
67
|
+
const p = findPricing(model);
|
|
58
68
|
if (!p) return 0;
|
|
59
69
|
return (
|
|
60
70
|
(input * p.input +
|
|
@@ -159,14 +169,34 @@ function readClaudeCode(): {
|
|
|
159
169
|
|
|
160
170
|
for (const projDir of projectDirs) {
|
|
161
171
|
const projPath = resolve(claudeDir, projDir);
|
|
162
|
-
|
|
172
|
+
// Project dir is like "-Users-rico-Development-git-myproject" — extract last meaningful segment
|
|
173
|
+
const segments = projDir.replace(/^-/, "").split("-");
|
|
174
|
+
const projName = segments.length > 1 ? segments.slice(-1)[0] : projDir;
|
|
163
175
|
|
|
164
176
|
if (typeof args.project === "string" && !projName.includes(args.project)) continue;
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
// Collect all JSONL files: top-level + subagent directories
|
|
179
|
+
const jsonlFiles: string[] = [];
|
|
180
|
+
|
|
181
|
+
for (const entry of readdirSync(projPath, { withFileTypes: true })) {
|
|
182
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
183
|
+
jsonlFiles.push(resolve(projPath, entry.name));
|
|
184
|
+
} else if (entry.isDirectory()) {
|
|
185
|
+
// Check for subagent transcripts inside session directories
|
|
186
|
+
const subagentsDir = resolve(projPath, entry.name, "subagents");
|
|
187
|
+
try {
|
|
188
|
+
for (const sub of readdirSync(subagentsDir)) {
|
|
189
|
+
if (sub.endsWith(".jsonl")) {
|
|
190
|
+
jsonlFiles.push(resolve(subagentsDir, sub));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
/* no subagents dir */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
167
198
|
|
|
168
|
-
for (const
|
|
169
|
-
const filepath = resolve(projPath, file);
|
|
199
|
+
for (const filepath of jsonlFiles) {
|
|
170
200
|
let content: string;
|
|
171
201
|
try {
|
|
172
202
|
content = readFileSync(filepath, "utf-8");
|