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.
@@ -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
- const lines: string[] = [
264
- "# Learning Pattern Synthesis",
265
- "",
266
- `**Period:** ${result.period}`,
267
- `**Generated:** ${date}`,
268
- `**Total Ratings:** ${result.totalRatings}`,
269
- `**Average Rating:** ${result.avgRating.toFixed(1)}/10`,
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 {
@@ -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 = MODEL_PRICING[model];
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
- const projName = projDir.split("-").pop() ?? projDir;
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
- const jsonlFiles = readdirSync(projPath).filter((f) => f.endsWith(".jsonl"));
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 file of jsonlFiles) {
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");