@aman_asmuei/aman-agent 0.24.3 → 0.26.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/dist/index.js CHANGED
@@ -1387,18 +1387,18 @@ var McpManager = class {
1387
1387
 
1388
1388
  // src/agent.ts
1389
1389
  import * as readline from "readline";
1390
- import fs16 from "fs";
1391
- import path16 from "path";
1392
- import os15 from "os";
1390
+ import fs19 from "fs";
1391
+ import path19 from "path";
1392
+ import os18 from "os";
1393
1393
  import pc7 from "picocolors";
1394
1394
  import { marked } from "marked";
1395
1395
  import { markedTerminal } from "marked-terminal";
1396
1396
  import logUpdate from "log-update";
1397
1397
 
1398
1398
  // src/commands.ts
1399
- import fs13 from "fs";
1400
- import path13 from "path";
1401
- import os12 from "os";
1399
+ import fs16 from "fs";
1400
+ import path16 from "path";
1401
+ import os15 from "os";
1402
1402
  import { execFileSync as execFileSync3 } from "child_process";
1403
1403
  import pc5 from "picocolors";
1404
1404
 
@@ -2361,8 +2361,9 @@ import pc3 from "picocolors";
2361
2361
  // src/hooks.ts
2362
2362
  import pc2 from "picocolors";
2363
2363
  import * as p2 from "@clack/prompts";
2364
- import fs10 from "fs";
2365
- import path10 from "path";
2364
+ import fs13 from "fs";
2365
+ import path13 from "path";
2366
+ import os12 from "os";
2366
2367
 
2367
2368
  // src/personality.ts
2368
2369
  var FRUSTRATION_SIGNALS = [
@@ -2535,6 +2536,670 @@ async function syncPersonalityToCore(state, mcpManager) {
2535
2536
  }
2536
2537
  }
2537
2538
 
2539
+ // src/postmortem.ts
2540
+ import fs11 from "fs/promises";
2541
+ import path11 from "path";
2542
+ import os11 from "os";
2543
+
2544
+ // src/observation.ts
2545
+ import fs10 from "fs/promises";
2546
+ import path10 from "path";
2547
+ import os10 from "os";
2548
+ var STAT_MAP = {
2549
+ tool_call: "toolCalls",
2550
+ tool_error: "toolErrors",
2551
+ topic_shift: "topicShifts",
2552
+ blocker: "blockers",
2553
+ milestone: "milestones",
2554
+ file_change: "fileChanges"
2555
+ };
2556
+ function defaultObservationsDir() {
2557
+ return path10.join(os10.homedir(), ".acore", "observations");
2558
+ }
2559
+ function createObservationSession(sessionId) {
2560
+ return {
2561
+ sessionId,
2562
+ startedAt: Date.now(),
2563
+ events: [],
2564
+ paused: false,
2565
+ stats: {
2566
+ toolCalls: 0,
2567
+ toolErrors: 0,
2568
+ topicShifts: 0,
2569
+ blockers: 0,
2570
+ milestones: 0,
2571
+ fileChanges: 0
2572
+ }
2573
+ };
2574
+ }
2575
+ function recordEvent(session, event) {
2576
+ if (session.paused) return;
2577
+ const full = { ...event, timestamp: Date.now() };
2578
+ session.events.push(full);
2579
+ const statKey = STAT_MAP[event.type];
2580
+ if (statKey) {
2581
+ session.stats[statKey]++;
2582
+ }
2583
+ }
2584
+ function pauseObservation(session) {
2585
+ session.paused = true;
2586
+ }
2587
+ function resumeObservation(session) {
2588
+ session.paused = false;
2589
+ }
2590
+ async function flushEvents(session, dir) {
2591
+ if (session.events.length === 0) return;
2592
+ const obsDir = dir ?? defaultObservationsDir();
2593
+ await fs10.mkdir(obsDir, { recursive: true });
2594
+ const filePath = path10.join(obsDir, `${session.sessionId}.jsonl`);
2595
+ const lines = session.events.map((e) => JSON.stringify(e)).join("\n") + "\n";
2596
+ await fs10.appendFile(filePath, lines, "utf-8");
2597
+ session.events.length = 0;
2598
+ }
2599
+ async function readObservationEvents(sessionId, dir) {
2600
+ const obsDir = dir ?? defaultObservationsDir();
2601
+ const filePath = path10.join(obsDir, `${sessionId}.jsonl`);
2602
+ try {
2603
+ const content = await fs10.readFile(filePath, "utf-8");
2604
+ return content.trim().split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
2605
+ } catch {
2606
+ return [];
2607
+ }
2608
+ }
2609
+ function getSessionStats(session) {
2610
+ const elapsed = Math.round((Date.now() - session.startedAt) / 6e4);
2611
+ const s = session.stats;
2612
+ const parts = [
2613
+ `Session: ${elapsed} min`,
2614
+ `Tools: ${s.toolCalls} calls (${s.toolErrors} error${s.toolErrors !== 1 ? "s" : ""})`,
2615
+ `Files: ${s.fileChanges} changed`,
2616
+ `Blockers: ${s.blockers}`,
2617
+ `Milestones: ${s.milestones}`
2618
+ ];
2619
+ if (s.topicShifts > 0) parts.push(`Topic shifts: ${s.topicShifts}`);
2620
+ if (session.paused) parts.push("(paused)");
2621
+ return parts.join(" | ");
2622
+ }
2623
+ async function cleanupOldObservations(dir, maxAgeDays = 30) {
2624
+ const obsDir = dir ?? defaultObservationsDir();
2625
+ try {
2626
+ const files = await fs10.readdir(obsDir);
2627
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
2628
+ for (const file of files) {
2629
+ if (!file.endsWith(".jsonl")) continue;
2630
+ const filePath = path10.join(obsDir, file);
2631
+ const stat = await fs10.stat(filePath);
2632
+ if (stat.mtimeMs < cutoff) {
2633
+ await fs10.unlink(filePath);
2634
+ }
2635
+ }
2636
+ } catch {
2637
+ }
2638
+ }
2639
+ function detectTopicShift(recentMessages, previousMessages) {
2640
+ const extractKeywords = (msgs) => {
2641
+ const words = msgs.join(" ").toLowerCase().split(/\W+/).filter((w) => w.length > 3);
2642
+ return new Set(words);
2643
+ };
2644
+ const recent = extractKeywords(recentMessages);
2645
+ const previous = extractKeywords(previousMessages);
2646
+ if (previous.size === 0) return { shifted: false, newTopics: [] };
2647
+ let overlap = 0;
2648
+ for (const word of recent) {
2649
+ if (previous.has(word)) overlap++;
2650
+ }
2651
+ const overlapRatio = previous.size > 0 ? overlap / previous.size : 1;
2652
+ const shifted = overlapRatio < 0.3;
2653
+ const newTopics = shifted ? [...recent].filter((w) => !previous.has(w)).slice(0, 5) : [];
2654
+ return { shifted, newTopics };
2655
+ }
2656
+
2657
+ // src/postmortem.ts
2658
+ function defaultPostmortemsDir() {
2659
+ return path11.join(os11.homedir(), ".acore", "postmortems");
2660
+ }
2661
+ function defaultObservationsDir2() {
2662
+ return path11.join(os11.homedir(), ".acore", "observations");
2663
+ }
2664
+ function shouldAutoPostmortem(session, messages) {
2665
+ if (messages.length < 6) return false;
2666
+ const durationMs = Date.now() - session.startedAt;
2667
+ return session.stats.toolErrors >= 3 || session.stats.blockers >= 2 || durationMs > 60 * 6e4 || hasAbandonedPlanSteps(messages) || hasSustainedFrustration(session, 5);
2668
+ }
2669
+ function hasAbandonedPlanSteps(messages) {
2670
+ const text3 = messages.map((m) => typeof m.content === "string" ? m.content : "").join("\n");
2671
+ const unchecked = (text3.match(/- \[ \]/g) ?? []).length;
2672
+ const checked = (text3.match(/- \[x\]/g) ?? []).length;
2673
+ return checked > 0 && unchecked > 0 && unchecked >= checked;
2674
+ }
2675
+ function hasSustainedFrustration(session, threshold) {
2676
+ return session.stats.blockers >= threshold;
2677
+ }
2678
+ function messageContentToText(content) {
2679
+ if (typeof content === "string") return content;
2680
+ return content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
2681
+ }
2682
+ var POSTMORTEM_PROMPT = `Analyze this session and generate a structured post-mortem report.
2683
+ Return ONLY valid JSON matching this schema (no markdown, no explanation):
2684
+
2685
+ {
2686
+ "summary": "2-3 sentence overview",
2687
+ "goals": ["what the user tried to accomplish"],
2688
+ "completed": ["what actually got done"],
2689
+ "blockers": ["what caused friction"],
2690
+ "decisions": ["key choices made with rationale"],
2691
+ "sentimentArc": "how mood evolved during session",
2692
+ "patterns": ["recurring behaviors worth remembering for future sessions"],
2693
+ "recommendations": ["actionable suggestions for next session"],
2694
+ "crystallizationCandidates": [
2695
+ {
2696
+ "name": "lowercase-kebab-name",
2697
+ "description": "1-sentence description of when this would be useful",
2698
+ "triggers": ["3-8", "trigger", "keywords"],
2699
+ "approach": "1-paragraph context: when and why to use this procedure",
2700
+ "steps": ["ordered step 1", "ordered step 2"],
2701
+ "gotchas": ["common mistake 1"],
2702
+ "confidence": 0.0
2703
+ }
2704
+ ]
2705
+ }
2706
+
2707
+ CRYSTALLIZATION RULES:
2708
+ - Only suggest 0-2 candidates per session \u2014 if nothing qualifies, return an empty array
2709
+ - Only suggest REUSABLE procedures (not one-off tasks specific to today's work)
2710
+ - The user must have demonstrated the procedure in this session
2711
+ - Confidence < 0.6 \u2192 don't suggest at all
2712
+ - Skip vague things like "use library X" \u2014 that's not procedural knowledge
2713
+ - Prefer narrow specific procedures over broad generalizations
2714
+ - Trigger keywords should be highly specific (avoid generic words like "code", "fix", "the")`;
2715
+ async function generatePostmortemReport(sessionId, messages, session, client, obsDir) {
2716
+ try {
2717
+ const events = await readObservationEvents(sessionId, obsDir ?? defaultObservationsDir2());
2718
+ const toolMap = /* @__PURE__ */ new Map();
2719
+ const fileChanges = [];
2720
+ const topicProgression = [];
2721
+ for (const event of events) {
2722
+ if (event.type === "tool_call") {
2723
+ const name = event.data.tool ?? "unknown";
2724
+ const entry = toolMap.get(name) ?? { calls: 0, errors: 0 };
2725
+ entry.calls++;
2726
+ toolMap.set(name, entry);
2727
+ } else if (event.type === "tool_error") {
2728
+ const name = event.data.tool ?? "unknown";
2729
+ const entry = toolMap.get(name) ?? { calls: 0, errors: 0 };
2730
+ entry.errors++;
2731
+ toolMap.set(name, entry);
2732
+ } else if (event.type === "file_change") {
2733
+ const p4 = event.data.path ?? "unknown";
2734
+ if (!fileChanges.includes(p4)) fileChanges.push(p4);
2735
+ } else if (event.type === "topic_shift") {
2736
+ const topics = event.data.newTopics ?? [];
2737
+ topicProgression.push(...topics);
2738
+ }
2739
+ }
2740
+ const toolUsage = [...toolMap.entries()].map(([name, { calls, errors }]) => ({
2741
+ name,
2742
+ count: calls,
2743
+ errorRate: calls > 0 ? Math.round(errors / calls * 100) / 100 : 0
2744
+ }));
2745
+ const recentMessages = messages.slice(-20).map((m) => {
2746
+ const text4 = messageContentToText(m.content);
2747
+ return `${m.role}: ${text4.slice(0, 200)}`;
2748
+ });
2749
+ const obsSnapshot = events.slice(-30).map((e) => `[${e.type}] ${e.summary}`);
2750
+ const durationMin = Math.round((Date.now() - session.startedAt) / 6e4);
2751
+ const prompt = `${POSTMORTEM_PROMPT}
2752
+
2753
+ Session ID: ${sessionId}
2754
+ Duration: ${durationMin} minutes
2755
+ Turns: ${messages.length}
2756
+ Tool calls: ${session.stats.toolCalls} (${session.stats.toolErrors} errors)
2757
+ Blockers: ${session.stats.blockers}
2758
+ Milestones: ${session.stats.milestones}
2759
+
2760
+ Recent messages:
2761
+ ${recentMessages.join("\n")}
2762
+
2763
+ Observations:
2764
+ ${obsSnapshot.join("\n")}`;
2765
+ const response = await client.chat(
2766
+ "You are a session analyst. Output only valid JSON.",
2767
+ [{ role: "user", content: prompt }],
2768
+ () => {
2769
+ }
2770
+ // no-op onChunk — postmortem runs silently
2771
+ );
2772
+ const text3 = messageContentToText(response.message.content);
2773
+ const jsonMatch = text3.match(/\{[\s\S]*\}/);
2774
+ if (!jsonMatch) {
2775
+ log.debug("postmortem", "LLM returned non-JSON response");
2776
+ return null;
2777
+ }
2778
+ const parsed = JSON.parse(jsonMatch[0]);
2779
+ return {
2780
+ sessionId,
2781
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2782
+ duration: durationMin,
2783
+ turnCount: messages.length,
2784
+ summary: parsed.summary ?? "",
2785
+ goals: parsed.goals ?? [],
2786
+ completed: parsed.completed ?? [],
2787
+ blockers: parsed.blockers ?? [],
2788
+ decisions: parsed.decisions ?? [],
2789
+ toolUsage,
2790
+ fileChanges,
2791
+ topicProgression: [...new Set(topicProgression)],
2792
+ sentimentArc: parsed.sentimentArc ?? "",
2793
+ patterns: parsed.patterns ?? [],
2794
+ recommendations: parsed.recommendations ?? [],
2795
+ crystallizationCandidates: Array.isArray(parsed.crystallizationCandidates) ? parsed.crystallizationCandidates : void 0
2796
+ };
2797
+ } catch (err) {
2798
+ log.debug("postmortem", "Failed to generate post-mortem", err);
2799
+ return null;
2800
+ }
2801
+ }
2802
+ function formatPostmortemMarkdown(report) {
2803
+ const lines = [
2804
+ `# Post-Mortem: ${report.date}`,
2805
+ "",
2806
+ `**Session:** ${report.sessionId} | **Duration:** ${report.duration} min | **Turns:** ${report.turnCount}`,
2807
+ "",
2808
+ "## Summary",
2809
+ report.summary,
2810
+ ""
2811
+ ];
2812
+ if (report.goals.length > 0) {
2813
+ lines.push("## Goals");
2814
+ report.goals.forEach((g) => lines.push(`- ${g}`));
2815
+ lines.push("");
2816
+ }
2817
+ if (report.completed.length > 0) {
2818
+ lines.push("## Completed");
2819
+ report.completed.forEach((c) => lines.push(`- [x] ${c}`));
2820
+ lines.push("");
2821
+ }
2822
+ if (report.blockers.length > 0) {
2823
+ lines.push("## Blockers");
2824
+ report.blockers.forEach((b) => lines.push(`- ${b}`));
2825
+ lines.push("");
2826
+ }
2827
+ if (report.decisions.length > 0) {
2828
+ lines.push("## Decisions");
2829
+ report.decisions.forEach((d) => lines.push(`- ${d}`));
2830
+ lines.push("");
2831
+ }
2832
+ if (report.toolUsage.length > 0) {
2833
+ lines.push("## Tool Usage");
2834
+ lines.push("| Tool | Calls | Error Rate |");
2835
+ lines.push("|------|-------|------------|");
2836
+ report.toolUsage.forEach(
2837
+ (t) => lines.push(`| ${t.name} | ${t.count} | ${Math.round(t.errorRate * 100)}% |`)
2838
+ );
2839
+ lines.push("");
2840
+ }
2841
+ if (report.fileChanges.length > 0) {
2842
+ lines.push("## Files Changed");
2843
+ report.fileChanges.forEach((f) => lines.push(`- \`${f}\``));
2844
+ lines.push("");
2845
+ }
2846
+ if (report.topicProgression.length > 0) {
2847
+ lines.push(`## Topics`);
2848
+ lines.push(report.topicProgression.join(" \u2192 "));
2849
+ lines.push("");
2850
+ }
2851
+ if (report.sentimentArc) {
2852
+ lines.push("## Sentiment Arc");
2853
+ lines.push(report.sentimentArc);
2854
+ lines.push("");
2855
+ }
2856
+ if (report.patterns.length > 0) {
2857
+ lines.push("## Patterns");
2858
+ report.patterns.forEach((p4) => lines.push(`- ${p4}`));
2859
+ lines.push("");
2860
+ }
2861
+ if (report.recommendations.length > 0) {
2862
+ lines.push("## Recommendations");
2863
+ report.recommendations.forEach((r) => lines.push(`- ${r}`));
2864
+ lines.push("");
2865
+ }
2866
+ if (report.crystallizationCandidates && report.crystallizationCandidates.length > 0) {
2867
+ lines.push("## Crystallization Candidates");
2868
+ report.crystallizationCandidates.forEach((c) => {
2869
+ lines.push(`- **${c.name}** (confidence ${c.confidence})`);
2870
+ lines.push(` ${c.description}`);
2871
+ });
2872
+ lines.push("");
2873
+ }
2874
+ return lines.join("\n");
2875
+ }
2876
+ async function savePostmortem(report, dir) {
2877
+ const pmDir = dir ?? defaultPostmortemsDir();
2878
+ await fs11.mkdir(pmDir, { recursive: true });
2879
+ const shortId = report.sessionId.slice(0, 4);
2880
+ const fileName = `${report.date}-${shortId}.md`;
2881
+ const filePath = path11.join(pmDir, fileName);
2882
+ const markdown = formatPostmortemMarkdown(report);
2883
+ await fs11.writeFile(filePath, markdown, "utf-8");
2884
+ const jsonPath = filePath.replace(/\.md$/, ".json");
2885
+ try {
2886
+ await fs11.writeFile(jsonPath, JSON.stringify(report, null, 2), "utf-8");
2887
+ } catch (err) {
2888
+ log.debug("postmortem", "JSON sidecar write failed", err);
2889
+ }
2890
+ return filePath;
2891
+ }
2892
+ async function listPostmortems(dir) {
2893
+ const pmDir = dir ?? defaultPostmortemsDir();
2894
+ try {
2895
+ const files = await fs11.readdir(pmDir);
2896
+ return files.filter((f) => f.endsWith(".md")).sort().reverse();
2897
+ } catch {
2898
+ return [];
2899
+ }
2900
+ }
2901
+ async function readPostmortem(name, dir) {
2902
+ const pmDir = dir ?? defaultPostmortemsDir();
2903
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
2904
+ try {
2905
+ return await fs11.readFile(path11.join(pmDir, fileName), "utf-8");
2906
+ } catch {
2907
+ return null;
2908
+ }
2909
+ }
2910
+ async function analyzePostmortemRange(sinceDays, client, dir) {
2911
+ const pmDir = dir ?? defaultPostmortemsDir();
2912
+ try {
2913
+ const files = await listPostmortems(pmDir);
2914
+ const cutoffDate = new Date(Date.now() - sinceDays * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
2915
+ const recentFiles = files.filter((f) => f >= cutoffDate);
2916
+ if (recentFiles.length === 0) return "No post-mortems found in the specified range.";
2917
+ const contents = [];
2918
+ for (const f of recentFiles.slice(0, 10)) {
2919
+ const content = await readPostmortem(f, pmDir);
2920
+ if (content) contents.push(content);
2921
+ }
2922
+ const response = await client.chat(
2923
+ "You are a session analyst. Analyze these post-mortems and identify trends.",
2924
+ [
2925
+ {
2926
+ role: "user",
2927
+ content: `Analyze these ${contents.length} post-mortem reports from the last ${sinceDays} days. Identify:
2928
+ 1. Recurring blockers
2929
+ 2. Productivity patterns
2930
+ 3. Tool reliability issues
2931
+ 4. Topic continuity across sessions
2932
+ 5. Actionable recommendations
2933
+
2934
+ Reports:
2935
+ ${contents.join("\n\n---\n\n")}`
2936
+ }
2937
+ ],
2938
+ () => {
2939
+ }
2940
+ // no-op onChunk
2941
+ );
2942
+ const text3 = messageContentToText(response.message.content);
2943
+ return text3 || null;
2944
+ } catch (err) {
2945
+ log.debug("postmortem", "Failed to analyze range", err);
2946
+ return null;
2947
+ }
2948
+ }
2949
+
2950
+ // src/crystallization.ts
2951
+ import fs12 from "fs/promises";
2952
+ import path12 from "path";
2953
+ var STOPWORDS = /* @__PURE__ */ new Set([
2954
+ "the",
2955
+ "and",
2956
+ "is",
2957
+ "to",
2958
+ "of",
2959
+ "a",
2960
+ "in",
2961
+ "for",
2962
+ "on",
2963
+ "with",
2964
+ "this",
2965
+ "that",
2966
+ "it",
2967
+ "as",
2968
+ "be",
2969
+ "by",
2970
+ "or",
2971
+ "at",
2972
+ "an",
2973
+ "from",
2974
+ "code",
2975
+ "fix",
2976
+ "do",
2977
+ "use",
2978
+ "make",
2979
+ "get",
2980
+ "set",
2981
+ "run",
2982
+ "we",
2983
+ "i"
2984
+ ]);
2985
+ var MAX_REJECTIONS = 100;
2986
+ var MARKER_RE = /<!--\s*aman-auto\s+([^>]+?)\s*-->/;
2987
+ function sanitizeName(input) {
2988
+ const cleaned = input.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2989
+ if (cleaned.length === 0) {
2990
+ throw new Error(`Cannot sanitize name: "${input}" produced empty result`);
2991
+ }
2992
+ return cleaned;
2993
+ }
2994
+ function validateCandidate(raw) {
2995
+ if (!raw || typeof raw !== "object") return null;
2996
+ const c = raw;
2997
+ if (typeof c.name !== "string" || c.name.trim() === "") return null;
2998
+ if (typeof c.description !== "string") return null;
2999
+ if (typeof c.approach !== "string") return null;
3000
+ if (!Array.isArray(c.triggers) || c.triggers.length === 0) return null;
3001
+ if (c.triggers.length > 10) return null;
3002
+ if (!Array.isArray(c.steps)) return null;
3003
+ if (typeof c.confidence !== "number") return null;
3004
+ if (!Number.isFinite(c.confidence)) return null;
3005
+ if (c.confidence < 0.6) return null;
3006
+ const triggers = Array.from(
3007
+ new Set(
3008
+ c.triggers.filter((t) => typeof t === "string").map((t) => t.toLowerCase().trim()).filter((t) => t.length > 0 && !STOPWORDS.has(t))
3009
+ )
3010
+ );
3011
+ if (triggers.length === 0) return null;
3012
+ let name;
3013
+ try {
3014
+ name = sanitizeName(c.name);
3015
+ } catch {
3016
+ return null;
3017
+ }
3018
+ return {
3019
+ name,
3020
+ description: c.description,
3021
+ triggers,
3022
+ approach: c.approach,
3023
+ steps: c.steps.filter((s) => typeof s === "string"),
3024
+ gotchas: Array.isArray(c.gotchas) ? c.gotchas.filter((g) => typeof g === "string") : [],
3025
+ confidence: Math.min(1, Math.max(0, c.confidence))
3026
+ };
3027
+ }
3028
+ function toTitleCase(kebab) {
3029
+ return kebab.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3030
+ }
3031
+ function formatSkillMarkdown(candidate, postmortemFilename) {
3032
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3033
+ const heading = toTitleCase(candidate.name);
3034
+ const triggerStr = candidate.triggers.join(",");
3035
+ const lines = [
3036
+ `# ${heading}`,
3037
+ `<!-- aman-auto source=postmortem date=${date} confidence=${candidate.confidence} triggers="${triggerStr}" -->`,
3038
+ "",
3039
+ "## When to use",
3040
+ candidate.approach,
3041
+ "",
3042
+ "## Steps",
3043
+ ...candidate.steps.map((s, i) => `${i + 1}. ${s}`),
3044
+ ""
3045
+ ];
3046
+ if (candidate.gotchas.length > 0) {
3047
+ lines.push("## Gotchas");
3048
+ lines.push(...candidate.gotchas.map((g) => `- ${g}`));
3049
+ lines.push("");
3050
+ }
3051
+ lines.push(`<!-- generated from ${postmortemFilename} -->`);
3052
+ lines.push("");
3053
+ return lines.join("\n");
3054
+ }
3055
+ function parseMarkerComment(line) {
3056
+ const match = line.match(MARKER_RE);
3057
+ if (!match) return null;
3058
+ const attrs = {};
3059
+ const attrRe = /(\w+)=(?:"([^"]*)"|(\S+))/g;
3060
+ let m;
3061
+ while ((m = attrRe.exec(match[1])) !== null) {
3062
+ attrs[m[1]] = m[2] ?? m[3] ?? "";
3063
+ }
3064
+ if (!attrs.triggers) return null;
3065
+ const triggers = attrs.triggers.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
3066
+ if (triggers.length === 0) return null;
3067
+ return {
3068
+ source: attrs.source ?? "unknown",
3069
+ date: attrs.date ?? "",
3070
+ confidence: attrs.confidence ? Number(attrs.confidence) : 0,
3071
+ triggers
3072
+ };
3073
+ }
3074
+ function extractSkillsWithMarkers(skillsMdContent) {
3075
+ const result = /* @__PURE__ */ new Map();
3076
+ const lines = skillsMdContent.split("\n");
3077
+ for (let i = 0; i < lines.length; i++) {
3078
+ const line = lines[i];
3079
+ if (line.startsWith("# ") && i + 1 < lines.length) {
3080
+ const headingText = line.slice(2).trim();
3081
+ const nextLine = lines[i + 1];
3082
+ const marker = parseMarkerComment(nextLine);
3083
+ if (marker) {
3084
+ try {
3085
+ const skillName = sanitizeName(headingText);
3086
+ result.set(skillName, marker);
3087
+ } catch {
3088
+ log.debug("crystallization", `cannot sanitize heading: ${headingText}`);
3089
+ }
3090
+ }
3091
+ }
3092
+ }
3093
+ return result;
3094
+ }
3095
+ function findCollision(name, triggers, existing) {
3096
+ if (existing.has(name)) {
3097
+ return { collides: true, collidesWith: name, reason: "exact name match" };
3098
+ }
3099
+ const triggerSet = new Set(triggers);
3100
+ for (const [otherName, otherData] of existing) {
3101
+ const otherTriggers = new Set(otherData.triggers);
3102
+ const intersection = [...triggerSet].filter((t) => otherTriggers.has(t)).length;
3103
+ const union = (/* @__PURE__ */ new Set([...triggerSet, ...otherTriggers])).size;
3104
+ const overlap = union > 0 ? intersection / union : 0;
3105
+ if (overlap >= 0.8) {
3106
+ return {
3107
+ collides: true,
3108
+ collidesWith: otherName,
3109
+ reason: `${Math.round(overlap * 100)}% trigger overlap`
3110
+ };
3111
+ }
3112
+ }
3113
+ return { collides: false };
3114
+ }
3115
+ async function writeSkillToFile(candidate, skillsMdPath, postmortemFilename) {
3116
+ try {
3117
+ await fs12.mkdir(path12.dirname(skillsMdPath), { recursive: true });
3118
+ let existingContent = "";
3119
+ try {
3120
+ existingContent = await fs12.readFile(skillsMdPath, "utf-8");
3121
+ } catch {
3122
+ existingContent = "# Skills\n\n";
3123
+ }
3124
+ if (existingContent.trim() === "") {
3125
+ existingContent = "# Skills\n\n";
3126
+ }
3127
+ const existingSkills = extractSkillsWithMarkers(existingContent);
3128
+ const collision = findCollision(candidate.name, candidate.triggers, existingSkills);
3129
+ if (collision.collides) {
3130
+ log.debug("crystallization", `collision detected: ${collision.reason}`);
3131
+ return {
3132
+ written: false,
3133
+ filePath: skillsMdPath,
3134
+ skillName: candidate.name,
3135
+ reason: `collision with "${collision.collidesWith}" (${collision.reason})`
3136
+ };
3137
+ }
3138
+ const skillMarkdown = formatSkillMarkdown(candidate, postmortemFilename);
3139
+ const separator = existingContent.endsWith("\n\n") ? "" : existingContent.endsWith("\n") ? "\n" : "\n\n";
3140
+ await fs12.writeFile(
3141
+ skillsMdPath,
3142
+ existingContent + separator + skillMarkdown,
3143
+ "utf-8"
3144
+ );
3145
+ return {
3146
+ written: true,
3147
+ filePath: skillsMdPath,
3148
+ skillName: candidate.name
3149
+ };
3150
+ } catch (err) {
3151
+ log.warn("crystallization", "writeSkillToFile failed", err);
3152
+ return {
3153
+ written: false,
3154
+ filePath: skillsMdPath,
3155
+ skillName: candidate.name,
3156
+ reason: err instanceof Error ? err.message : String(err)
3157
+ };
3158
+ }
3159
+ }
3160
+ async function appendCrystallizationLog(entry, logPath) {
3161
+ try {
3162
+ await fs12.mkdir(path12.dirname(logPath), { recursive: true });
3163
+ let existing = [];
3164
+ try {
3165
+ const content = await fs12.readFile(logPath, "utf-8");
3166
+ existing = JSON.parse(content);
3167
+ if (!Array.isArray(existing)) existing = [];
3168
+ } catch {
3169
+ existing = [];
3170
+ }
3171
+ existing.push(entry);
3172
+ await fs12.writeFile(logPath, JSON.stringify(existing, null, 2), "utf-8");
3173
+ } catch (err) {
3174
+ log.debug("crystallization", "appendCrystallizationLog failed", err);
3175
+ }
3176
+ }
3177
+ async function appendRejection(candidate, postmortemFilename, rejectionsPath) {
3178
+ try {
3179
+ await fs12.mkdir(path12.dirname(rejectionsPath), { recursive: true });
3180
+ let existing = [];
3181
+ try {
3182
+ const content = await fs12.readFile(rejectionsPath, "utf-8");
3183
+ existing = JSON.parse(content);
3184
+ if (!Array.isArray(existing)) existing = [];
3185
+ } catch {
3186
+ existing = [];
3187
+ }
3188
+ existing.push({
3189
+ name: candidate.name,
3190
+ rejectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3191
+ fromPostmortem: postmortemFilename,
3192
+ triggers: candidate.triggers
3193
+ });
3194
+ while (existing.length > MAX_REJECTIONS) {
3195
+ existing.shift();
3196
+ }
3197
+ await fs12.writeFile(rejectionsPath, JSON.stringify(existing, null, 2), "utf-8");
3198
+ } catch (err) {
3199
+ log.debug("crystallization", "appendRejection failed", err);
3200
+ }
3201
+ }
3202
+
2538
3203
  // src/hooks.ts
2539
3204
  function getTimeContext() {
2540
3205
  const now = /* @__PURE__ */ new Date();
@@ -2767,7 +3432,7 @@ async function onWorkflowMatch(userInput, ctx) {
2767
3432
  isHookCall = false;
2768
3433
  }
2769
3434
  }
2770
- async function onSessionEnd(ctx, messages, sessionId) {
3435
+ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
2771
3436
  try {
2772
3437
  if (ctx.config.autoSessionSave && messages.length > 2) {
2773
3438
  console.log(pc2.dim("\n Saving conversation to memory..."));
@@ -2803,10 +3468,10 @@ async function onSessionEnd(ctx, messages, sessionId) {
2803
3468
  }
2804
3469
  console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
2805
3470
  }
2806
- const projectContextPath = path10.join(process.cwd(), ".acore", "context.md");
2807
- if (fs10.existsSync(projectContextPath) && messages.length > 2) {
3471
+ const projectContextPath = path13.join(process.cwd(), ".acore", "context.md");
3472
+ if (fs13.existsSync(projectContextPath) && messages.length > 2) {
2808
3473
  try {
2809
- let contextContent = fs10.readFileSync(projectContextPath, "utf-8");
3474
+ let contextContent = fs13.readFileSync(projectContextPath, "utf-8");
2810
3475
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2811
3476
  let lastUserMsg = "";
2812
3477
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -2824,7 +3489,7 @@ async function onSessionEnd(ctx, messages, sessionId) {
2824
3489
  - Recent decisions: [see memory]
2825
3490
  - Temp notes: [cleared]`;
2826
3491
  contextContent = contextContent.replace(sessionPattern, newSession);
2827
- fs10.writeFileSync(projectContextPath, contextContent, "utf-8");
3492
+ fs13.writeFileSync(projectContextPath, contextContent, "utf-8");
2828
3493
  log.debug("hooks", `Updated project context: ${projectContextPath}`);
2829
3494
  }
2830
3495
  } catch (err) {
@@ -2877,6 +3542,106 @@ async function onSessionEnd(ctx, messages, sessionId) {
2877
3542
  }
2878
3543
  }
2879
3544
  }
3545
+ if (ctx.config.autoPostmortem !== false && observationSession && shouldAutoPostmortem(observationSession, messages)) {
3546
+ try {
3547
+ const client = ctx.llmClient;
3548
+ if (client) {
3549
+ const report = await generatePostmortemReport(
3550
+ sessionId,
3551
+ messages,
3552
+ observationSession,
3553
+ client
3554
+ );
3555
+ if (report) {
3556
+ const filePath = await savePostmortem(report);
3557
+ console.log(pc2.dim(`
3558
+ Post-mortem saved \u2192 ${filePath}`));
3559
+ for (const pattern of report.patterns) {
3560
+ try {
3561
+ await memoryStore({
3562
+ content: pattern,
3563
+ type: "pattern",
3564
+ tags: ["postmortem", "auto"],
3565
+ confidence: 0.7
3566
+ });
3567
+ } catch {
3568
+ }
3569
+ }
3570
+ if (report.crystallizationCandidates && report.crystallizationCandidates.length > 0) {
3571
+ const skillsMdPath = path13.join(os12.homedir(), ".askill", "skills.md");
3572
+ const logPath = path13.join(
3573
+ os12.homedir(),
3574
+ ".aman-agent",
3575
+ "crystallization-log.json"
3576
+ );
3577
+ const rejectionsPath = path13.join(
3578
+ os12.homedir(),
3579
+ ".aman-agent",
3580
+ "crystallization-rejections.json"
3581
+ );
3582
+ const postmortemFilename = `${report.date}-${report.sessionId.slice(0, 4)}.md`;
3583
+ console.log(
3584
+ pc2.dim(`
3585
+ Crystallization candidates: ${report.crystallizationCandidates.length}`)
3586
+ );
3587
+ let skipAll = false;
3588
+ for (const rawCandidate of report.crystallizationCandidates) {
3589
+ if (skipAll) break;
3590
+ const candidate = validateCandidate(rawCandidate);
3591
+ if (!candidate) {
3592
+ log.debug("hooks", "candidate failed validation");
3593
+ continue;
3594
+ }
3595
+ const choice = await p2.select({
3596
+ message: `Crystallize "${candidate.name}" as a reusable skill?`,
3597
+ options: [
3598
+ { value: "accept", label: "Yes \u2014 write to ~/.askill/skills.md" },
3599
+ { value: "reject", label: "No \u2014 skip this one" },
3600
+ { value: "skip-all", label: "Skip all crystallization for this session" }
3601
+ ],
3602
+ initialValue: "reject"
3603
+ });
3604
+ if (p2.isCancel(choice) || choice === "skip-all") {
3605
+ skipAll = true;
3606
+ break;
3607
+ }
3608
+ if (choice === "accept") {
3609
+ const result = await writeSkillToFile(
3610
+ candidate,
3611
+ skillsMdPath,
3612
+ postmortemFilename
3613
+ );
3614
+ if (result.written) {
3615
+ console.log(
3616
+ pc2.green(` \u2713 Crystallized: ${candidate.name} \u2192 ${result.filePath}`)
3617
+ );
3618
+ console.log(pc2.dim(` Triggers: ${candidate.triggers.join(", ")}`));
3619
+ console.log(pc2.dim(` Will auto-activate next session.`));
3620
+ await appendCrystallizationLog(
3621
+ {
3622
+ name: candidate.name,
3623
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3624
+ fromPostmortem: postmortemFilename,
3625
+ confidence: candidate.confidence,
3626
+ triggers: candidate.triggers
3627
+ },
3628
+ logPath
3629
+ );
3630
+ } else {
3631
+ console.log(pc2.yellow(` \u2298 Could not crystallize: ${result.reason}`));
3632
+ }
3633
+ } else {
3634
+ console.log(pc2.dim(` Skipped: ${candidate.name}`));
3635
+ await appendRejection(candidate, postmortemFilename, rejectionsPath);
3636
+ }
3637
+ }
3638
+ }
3639
+ }
3640
+ }
3641
+ } catch (err) {
3642
+ log.debug("hooks", "auto post-mortem failed", err);
3643
+ }
3644
+ }
2880
3645
  } catch (err) {
2881
3646
  log.warn("hooks", "session end hook failed", err);
2882
3647
  }
@@ -3030,43 +3795,43 @@ async function delegatePipeline(steps, initialInput, client, mcpManager, options
3030
3795
  }
3031
3796
 
3032
3797
  // src/teams.ts
3033
- import fs11 from "fs";
3034
- import path11 from "path";
3035
- import os10 from "os";
3798
+ import fs14 from "fs";
3799
+ import path14 from "path";
3800
+ import os13 from "os";
3036
3801
  import pc4 from "picocolors";
3037
3802
  function getTeamsDir() {
3038
- return path11.join(os10.homedir(), ".acore", "teams");
3803
+ return path14.join(os13.homedir(), ".acore", "teams");
3039
3804
  }
3040
3805
  function ensureTeamsDir() {
3041
3806
  const dir = getTeamsDir();
3042
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
3807
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
3043
3808
  return dir;
3044
3809
  }
3045
3810
  function teamPath(name) {
3046
3811
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
3047
- return path11.join(ensureTeamsDir(), `${slug}.json`);
3812
+ return path14.join(ensureTeamsDir(), `${slug}.json`);
3048
3813
  }
3049
3814
  function createTeam(team) {
3050
3815
  const fp = teamPath(team.name);
3051
- fs11.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
3816
+ fs14.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
3052
3817
  }
3053
3818
  function loadTeam(name) {
3054
3819
  const fp = teamPath(name);
3055
- if (!fs11.existsSync(fp)) return null;
3820
+ if (!fs14.existsSync(fp)) return null;
3056
3821
  try {
3057
- return JSON.parse(fs11.readFileSync(fp, "utf-8"));
3822
+ return JSON.parse(fs14.readFileSync(fp, "utf-8"));
3058
3823
  } catch {
3059
3824
  return null;
3060
3825
  }
3061
3826
  }
3062
3827
  function listTeams() {
3063
3828
  const dir = getTeamsDir();
3064
- if (!fs11.existsSync(dir)) return [];
3829
+ if (!fs14.existsSync(dir)) return [];
3065
3830
  const teams = [];
3066
- for (const file of fs11.readdirSync(dir)) {
3831
+ for (const file of fs14.readdirSync(dir)) {
3067
3832
  if (!file.endsWith(".json")) continue;
3068
3833
  try {
3069
- const content = fs11.readFileSync(path11.join(dir, file), "utf-8");
3834
+ const content = fs14.readFileSync(path14.join(dir, file), "utf-8");
3070
3835
  teams.push(JSON.parse(content));
3071
3836
  } catch {
3072
3837
  }
@@ -3075,8 +3840,8 @@ function listTeams() {
3075
3840
  }
3076
3841
  function deleteTeam(name) {
3077
3842
  const fp = teamPath(name);
3078
- if (!fs11.existsSync(fp)) return false;
3079
- fs11.unlinkSync(fp);
3843
+ if (!fs14.existsSync(fp)) return false;
3844
+ fs14.unlinkSync(fp);
3080
3845
  return true;
3081
3846
  }
3082
3847
  async function runTeam(team, task, client, mcpManager, tools) {
@@ -3302,23 +4067,23 @@ var BUILT_IN_TEAMS = [
3302
4067
  ];
3303
4068
 
3304
4069
  // src/plans.ts
3305
- import fs12 from "fs";
3306
- import path12 from "path";
3307
- import os11 from "os";
4070
+ import fs15 from "fs";
4071
+ import path15 from "path";
4072
+ import os14 from "os";
3308
4073
  function getPlansDir() {
3309
- const localDir = path12.join(process.cwd(), ".acore", "plans");
3310
- const localAcore = path12.join(process.cwd(), ".acore");
3311
- if (fs12.existsSync(localAcore)) return localDir;
3312
- return path12.join(os11.homedir(), ".acore", "plans");
4074
+ const localDir = path15.join(process.cwd(), ".acore", "plans");
4075
+ const localAcore = path15.join(process.cwd(), ".acore");
4076
+ if (fs15.existsSync(localAcore)) return localDir;
4077
+ return path15.join(os14.homedir(), ".acore", "plans");
3313
4078
  }
3314
4079
  function ensurePlansDir() {
3315
4080
  const dir = getPlansDir();
3316
- if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
4081
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
3317
4082
  return dir;
3318
4083
  }
3319
4084
  function planPath(name) {
3320
4085
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3321
- return path12.join(ensurePlansDir(), `${slug}.md`);
4086
+ return path15.join(ensurePlansDir(), `${slug}.md`);
3322
4087
  }
3323
4088
  function serializePlan(plan) {
3324
4089
  const lines = [];
@@ -3344,7 +4109,7 @@ function parsePlan(content, filePath) {
3344
4109
  const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
3345
4110
  const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
3346
4111
  const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
3347
- const name = nameMatch?.[1]?.trim() || path12.basename(filePath, ".md");
4112
+ const name = nameMatch?.[1]?.trim() || path15.basename(filePath, ".md");
3348
4113
  const goal = goalMatch?.[1]?.trim() || "";
3349
4114
  const createdAt = createdMatch?.[1]?.trim() || "";
3350
4115
  const updatedAt = updatedMatch?.[1]?.trim() || "";
@@ -3386,22 +4151,22 @@ function createPlan(name, goal, steps) {
3386
4151
  }
3387
4152
  function savePlan(plan) {
3388
4153
  const fp = planPath(plan.name);
3389
- fs12.writeFileSync(fp, serializePlan(plan), "utf-8");
4154
+ fs15.writeFileSync(fp, serializePlan(plan), "utf-8");
3390
4155
  }
3391
4156
  function loadPlan(name) {
3392
4157
  const fp = planPath(name);
3393
- if (!fs12.existsSync(fp)) return null;
3394
- const content = fs12.readFileSync(fp, "utf-8");
4158
+ if (!fs15.existsSync(fp)) return null;
4159
+ const content = fs15.readFileSync(fp, "utf-8");
3395
4160
  return parsePlan(content, fp);
3396
4161
  }
3397
4162
  function listPlans() {
3398
4163
  const dir = getPlansDir();
3399
- if (!fs12.existsSync(dir)) return [];
4164
+ if (!fs15.existsSync(dir)) return [];
3400
4165
  const plans = [];
3401
- for (const file of fs12.readdirSync(dir)) {
4166
+ for (const file of fs15.readdirSync(dir)) {
3402
4167
  if (!file.endsWith(".md")) continue;
3403
- const fp = path12.join(dir, file);
3404
- const content = fs12.readFileSync(fp, "utf-8");
4168
+ const fp = path15.join(dir, file);
4169
+ const content = fs15.readFileSync(fp, "utf-8");
3405
4170
  const plan = parsePlan(content, fp);
3406
4171
  if (plan) plans.push(plan);
3407
4172
  }
@@ -3510,10 +4275,10 @@ import {
3510
4275
  } from "@aman_asmuei/arules-core";
3511
4276
  var AGENT_SCOPE = process.env.AMAN_AGENT_SCOPE ?? "dev:agent";
3512
4277
  function readEcosystemFile(filePath, label) {
3513
- if (!fs13.existsSync(filePath)) {
4278
+ if (!fs16.existsSync(filePath)) {
3514
4279
  return pc5.dim(`No ${label} file found at ${filePath}`);
3515
4280
  }
3516
- return fs13.readFileSync(filePath, "utf-8").trim();
4281
+ return fs16.readFileSync(filePath, "utf-8").trim();
3517
4282
  }
3518
4283
  function parseCommand(input) {
3519
4284
  const trimmed = input.trim();
@@ -3788,9 +4553,9 @@ ${result.violations.map((v) => ` - ${v}`).join("\n")}`)
3788
4553
  };
3789
4554
  }
3790
4555
  async function handleWorkflowsCommand(action, args, ctx) {
3791
- const home2 = os12.homedir();
4556
+ const home2 = os15.homedir();
3792
4557
  if (!action) {
3793
- const content = readEcosystemFile(path13.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4558
+ const content = readEcosystemFile(path16.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
3794
4559
  return { handled: true, output: content };
3795
4560
  }
3796
4561
  if (action === "add") {
@@ -3812,7 +4577,7 @@ async function handleWorkflowsCommand(action, args, ctx) {
3812
4577
  return { handled: true, output: pc5.yellow("Usage: /workflows get <name>") };
3813
4578
  }
3814
4579
  const name = args.join(" ").toLowerCase();
3815
- const raw = readEcosystemFile(path13.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4580
+ const raw = readEcosystemFile(path16.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
3816
4581
  if (raw.startsWith("No ")) {
3817
4582
  return { handled: true, output: raw };
3818
4583
  }
@@ -3871,12 +4636,12 @@ async function handleToolsCommand(action, args, _ctx) {
3871
4636
  return { handled: true, output: pc5.yellow("Usage: /tools search <query...>") };
3872
4637
  }
3873
4638
  const query = args.join(" ").toLowerCase();
3874
- const home2 = os12.homedir();
3875
- const toolsFile = path13.join(home2, ".akit", "tools.md");
3876
- if (!fs13.existsSync(toolsFile)) {
4639
+ const home2 = os15.homedir();
4640
+ const toolsFile = path16.join(home2, ".akit", "tools.md");
4641
+ if (!fs16.existsSync(toolsFile)) {
3877
4642
  return { handled: true, output: pc5.dim(`No tools file found. Use 'npx @aman_asmuei/akit search ${args.join(" ")}' to search the registry.`) };
3878
4643
  }
3879
- const raw = fs13.readFileSync(toolsFile, "utf-8").trim();
4644
+ const raw = fs16.readFileSync(toolsFile, "utf-8").trim();
3880
4645
  const lines = raw.split("\n");
3881
4646
  const matches = lines.filter((l) => l.toLowerCase().includes(query));
3882
4647
  if (matches.length === 0) {
@@ -3887,9 +4652,9 @@ async function handleToolsCommand(action, args, _ctx) {
3887
4652
  return handleAkitCommand(action, args);
3888
4653
  }
3889
4654
  async function handleSkillsCommand(action, args, ctx) {
3890
- const home2 = os12.homedir();
4655
+ const home2 = os15.homedir();
3891
4656
  if (!action) {
3892
- const content = readEcosystemFile(path13.join(home2, ".askill", "skills.md"), "skills (askill)");
4657
+ const content = readEcosystemFile(path16.join(home2, ".askill", "skills.md"), "skills (askill)");
3893
4658
  return { handled: true, output: content };
3894
4659
  }
3895
4660
  if (action === "install") {
@@ -3911,8 +4676,8 @@ async function handleSkillsCommand(action, args, ctx) {
3911
4676
  return { handled: true, output: pc5.yellow("Usage: /skills search <query...>") };
3912
4677
  }
3913
4678
  const query = args.join(" ").toLowerCase();
3914
- const home3 = os12.homedir();
3915
- const raw = readEcosystemFile(path13.join(home3, ".askill", "skills.md"), "skills (askill)");
4679
+ const home3 = os15.homedir();
4680
+ const raw = readEcosystemFile(path16.join(home3, ".askill", "skills.md"), "skills (askill)");
3916
4681
  if (raw.startsWith("No ")) {
3917
4682
  return { handled: true, output: raw };
3918
4683
  }
@@ -3923,21 +4688,111 @@ async function handleSkillsCommand(action, args, ctx) {
3923
4688
  }
3924
4689
  return { handled: true, output: [pc5.bold(`Skills matching "${query}":`), ...matches].join("\n") };
3925
4690
  }
4691
+ if (action === "list") {
4692
+ const autoOnly = args.includes("--auto");
4693
+ if (autoOnly) {
4694
+ const logPath = path16.join(os15.homedir(), ".aman-agent", "crystallization-log.json");
4695
+ try {
4696
+ const content2 = fs16.readFileSync(logPath, "utf-8");
4697
+ const entries = JSON.parse(content2);
4698
+ if (entries.length === 0) {
4699
+ return { handled: true, output: pc5.dim("No crystallized skills yet.") };
4700
+ }
4701
+ const lines = [pc5.bold(`Crystallized skills (${entries.length}):`)];
4702
+ for (const entry of entries) {
4703
+ const date = entry.createdAt.slice(0, 10);
4704
+ lines.push(` ${pc5.cyan(entry.name)} (${date}, conf ${entry.confidence})`);
4705
+ lines.push(pc5.dim(` triggers: ${entry.triggers.join(", ")}`));
4706
+ }
4707
+ return { handled: true, output: lines.join("\n") };
4708
+ } catch {
4709
+ return { handled: true, output: pc5.dim("No crystallized skills yet.") };
4710
+ }
4711
+ }
4712
+ const content = readEcosystemFile(path16.join(home2, ".askill", "skills.md"), "skills (askill)");
4713
+ return { handled: true, output: content };
4714
+ }
4715
+ if (action === "crystallize") {
4716
+ const pmDir = path16.join(os15.homedir(), ".acore", "postmortems");
4717
+ try {
4718
+ const files = fs16.readdirSync(pmDir);
4719
+ const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
4720
+ if (jsonFiles.length === 0) {
4721
+ return {
4722
+ handled: true,
4723
+ output: pc5.dim("No post-mortems found. Run a session that triggers a post-mortem first.")
4724
+ };
4725
+ }
4726
+ const latest = jsonFiles[0];
4727
+ const content = fs16.readFileSync(path16.join(pmDir, latest), "utf-8");
4728
+ const report = JSON.parse(content);
4729
+ if (!report.crystallizationCandidates || report.crystallizationCandidates.length === 0) {
4730
+ return {
4731
+ handled: true,
4732
+ output: pc5.dim(`No crystallization candidates in the most recent post-mortem (${latest}). Run a longer session or wait for the next auto-postmortem.`)
4733
+ };
4734
+ }
4735
+ const skillsMdPath = path16.join(os15.homedir(), ".askill", "skills.md");
4736
+ const logPath = path16.join(os15.homedir(), ".aman-agent", "crystallization-log.json");
4737
+ const postmortemFilename = latest.replace(/\.json$/, ".md");
4738
+ const lines = [
4739
+ pc5.bold(`Found ${report.crystallizationCandidates.length} candidate(s) in ${latest}:`)
4740
+ ];
4741
+ let written = 0;
4742
+ for (const raw of report.crystallizationCandidates) {
4743
+ const candidate = validateCandidate(raw);
4744
+ if (!candidate) {
4745
+ const rawName = raw.name ?? "unknown";
4746
+ lines.push(pc5.dim(` \u2298 ${rawName} \u2014 failed validation`));
4747
+ continue;
4748
+ }
4749
+ const result = await writeSkillToFile(candidate, skillsMdPath, postmortemFilename);
4750
+ if (result.written) {
4751
+ written++;
4752
+ lines.push(pc5.green(` \u2713 Crystallized: ${candidate.name}`));
4753
+ await appendCrystallizationLog(
4754
+ {
4755
+ name: candidate.name,
4756
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4757
+ fromPostmortem: postmortemFilename,
4758
+ confidence: candidate.confidence,
4759
+ triggers: candidate.triggers
4760
+ },
4761
+ logPath
4762
+ );
4763
+ } else {
4764
+ lines.push(pc5.yellow(` \u2298 ${candidate.name} \u2014 ${result.reason}`));
4765
+ }
4766
+ }
4767
+ if (written > 0) {
4768
+ lines.push("");
4769
+ lines.push(pc5.dim(`Crystallized skills will auto-activate in your next session.`));
4770
+ }
4771
+ return { handled: true, output: lines.join("\n") };
4772
+ } catch (err) {
4773
+ return {
4774
+ handled: true,
4775
+ output: pc5.red(`Failed to load post-mortems: ${err instanceof Error ? err.message : String(err)}`)
4776
+ };
4777
+ }
4778
+ }
3926
4779
  if (action === "help") {
3927
4780
  return { handled: true, output: [
3928
4781
  pc5.bold("Skills commands:"),
3929
- ` ${pc5.cyan("/skills")} View installed skills`,
3930
- ` ${pc5.cyan("/skills install")} <name> Install a skill`,
3931
- ` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill`,
3932
- ` ${pc5.cyan("/skills search")} <query> Search skills by name/description`
4782
+ ` ${pc5.cyan("/skills")} View installed skills`,
4783
+ ` ${pc5.cyan("/skills install")} <name> Install a skill`,
4784
+ ` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill`,
4785
+ ` ${pc5.cyan("/skills search")} <query> Search skills by name/description`,
4786
+ ` ${pc5.cyan("/skills crystallize")} Crystallize skills from most recent post-mortem`,
4787
+ ` ${pc5.cyan("/skills list --auto")} List crystallized (auto-created) skills`
3933
4788
  ].join("\n") };
3934
4789
  }
3935
4790
  return { handled: true, output: pc5.yellow(`Unknown action: /skills ${action}. Try /skills --help`) };
3936
4791
  }
3937
4792
  async function handleEvalCommand(action, args, ctx) {
3938
- const home2 = os12.homedir();
4793
+ const home2 = os15.homedir();
3939
4794
  if (!action) {
3940
- const content = readEcosystemFile(path13.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
4795
+ const content = readEcosystemFile(path16.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
3941
4796
  return { handled: true, output: content };
3942
4797
  }
3943
4798
  if (action === "milestone") {
@@ -3949,11 +4804,11 @@ async function handleEvalCommand(action, args, ctx) {
3949
4804
  return { handled: true, output };
3950
4805
  }
3951
4806
  if (action === "report") {
3952
- const evalFile = path13.join(home2, ".aeval", "eval.md");
3953
- if (!fs13.existsSync(evalFile)) {
4807
+ const evalFile = path16.join(home2, ".aeval", "eval.md");
4808
+ if (!fs16.existsSync(evalFile)) {
3954
4809
  return { handled: true, output: pc5.dim("No eval report found. Log milestones with /eval milestone <text>.") };
3955
4810
  }
3956
- const content = fs13.readFileSync(evalFile, "utf-8").trim();
4811
+ const content = fs16.readFileSync(evalFile, "utf-8").trim();
3957
4812
  return { handled: true, output: [pc5.bold("Eval Report"), "", content].join("\n") };
3958
4813
  }
3959
4814
  return { handled: true, output: pc5.yellow(`Unknown action: /eval ${action}. Use /eval, /eval report, or /eval milestone <text>.`) };
@@ -4445,7 +5300,7 @@ function handleHelp() {
4445
5300
  ` ${pc5.cyan("/rules")} View rules [add|remove|toggle ...]`,
4446
5301
  ` ${pc5.cyan("/workflows")} View workflows [add|remove ...]`,
4447
5302
  ` ${pc5.cyan("/akit")} Manage tools [add|remove <tool>]`,
4448
- ` ${pc5.cyan("/skills")} View skills [install|uninstall ...]`,
5303
+ ` ${pc5.cyan("/skills")} View skills [install|uninstall|crystallize|list --auto]`,
4449
5304
  ` ${pc5.cyan("/eval")} View evaluation [milestone ...]`,
4450
5305
  ` ${pc5.cyan("/memory")} View recent memories [search|fts|since|stats|export|clear|timeline]`,
4451
5306
  ` ${pc5.cyan("/reminder")} Manage reminders [set|check|done]`,
@@ -4463,6 +5318,8 @@ function handleHelp() {
4463
5318
  ` ${pc5.cyan("/showcase")} Browse & switch companion templates`,
4464
5319
  ` ${pc5.cyan("/delegate")} Delegate tasks to sub-agents`,
4465
5320
  ` ${pc5.cyan("/team")} Manage agent teams`,
5321
+ ` ${pc5.cyan("/observe")} Session observation dashboard [pause|resume]`,
5322
+ ` ${pc5.cyan("/postmortem")} Generate post-mortem [last|list|--since 7d]`,
4466
5323
  ` ${pc5.cyan("/update")} Check for updates`,
4467
5324
  ` ${pc5.cyan("/reset")} Full reset [all|memory|config|identity|rules]`,
4468
5325
  ` ${pc5.cyan("/clear")} Clear conversation history`,
@@ -4475,10 +5332,10 @@ function handleSave() {
4475
5332
  }
4476
5333
  function handleReset(action) {
4477
5334
  const dirs = {
4478
- config: path13.join(os12.homedir(), ".aman-agent"),
4479
- memory: path13.join(os12.homedir(), ".amem"),
4480
- identity: path13.join(os12.homedir(), ".acore"),
4481
- rules: path13.join(os12.homedir(), ".arules")
5335
+ config: path16.join(os15.homedir(), ".aman-agent"),
5336
+ memory: path16.join(os15.homedir(), ".amem"),
5337
+ identity: path16.join(os15.homedir(), ".acore"),
5338
+ rules: path16.join(os15.homedir(), ".arules")
4482
5339
  };
4483
5340
  if (action === "help" || !action) {
4484
5341
  return {
@@ -4503,15 +5360,15 @@ function handleReset(action) {
4503
5360
  const removed = [];
4504
5361
  for (const target of targets) {
4505
5362
  const dir = dirs[target];
4506
- if (fs13.existsSync(dir)) {
4507
- fs13.rmSync(dir, { recursive: true, force: true });
5363
+ if (fs16.existsSync(dir)) {
5364
+ fs16.rmSync(dir, { recursive: true, force: true });
4508
5365
  removed.push(target);
4509
5366
  }
4510
5367
  }
4511
5368
  if (targets.includes("config")) {
4512
5369
  const configDir = dirs.config;
4513
- fs13.mkdirSync(configDir, { recursive: true });
4514
- fs13.writeFileSync(path13.join(configDir, ".reconfig"), "", "utf-8");
5370
+ fs16.mkdirSync(configDir, { recursive: true });
5371
+ fs16.writeFileSync(path16.join(configDir, ".reconfig"), "", "utf-8");
4515
5372
  }
4516
5373
  if (removed.length === 0) {
4517
5374
  return { handled: true, output: pc5.dim("Nothing to reset \u2014 directories don't exist.") };
@@ -4528,7 +5385,7 @@ function handleReset(action) {
4528
5385
  function handleUpdate() {
4529
5386
  try {
4530
5387
  const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
4531
- const local = true ? "0.24.3" : "unknown";
5388
+ const local = true ? "0.26.0" : "unknown";
4532
5389
  if (current === local) {
4533
5390
  return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
4534
5391
  }
@@ -4572,11 +5429,11 @@ function handleExportCommand() {
4572
5429
  return { handled: true, exportConversation: true };
4573
5430
  }
4574
5431
  function handleDebugCommand() {
4575
- const logPath = path13.join(os12.homedir(), ".aman-agent", "debug.log");
4576
- if (!fs13.existsSync(logPath)) {
5432
+ const logPath = path16.join(os15.homedir(), ".aman-agent", "debug.log");
5433
+ if (!fs16.existsSync(logPath)) {
4577
5434
  return { handled: true, output: pc5.dim("No debug log found.") };
4578
5435
  }
4579
- const content = fs13.readFileSync(logPath, "utf-8");
5436
+ const content = fs16.readFileSync(logPath, "utf-8");
4580
5437
  const lines = content.trim().split("\n");
4581
5438
  const last20 = lines.slice(-20).join("\n");
4582
5439
  return { handled: true, output: pc5.bold("Debug Log (last 20 entries):\n") + pc5.dim(last20) };
@@ -4774,7 +5631,7 @@ ${result.response}`
4774
5631
  };
4775
5632
  }
4776
5633
  function handleProfileCommand(action, args) {
4777
- const profilesDir = path13.join(os12.homedir(), ".acore", "profiles");
5634
+ const profilesDir = path16.join(os15.homedir(), ".acore", "profiles");
4778
5635
  if (action === "me") {
4779
5636
  const user = loadUserIdentity();
4780
5637
  if (!user) {
@@ -4842,8 +5699,8 @@ ${pc5.dim("Edit with: /profile edit")}` };
4842
5699
  };
4843
5700
  }
4844
5701
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
4845
- const profileDir = path13.join(profilesDir, slug);
4846
- if (fs13.existsSync(profileDir)) {
5702
+ const profileDir = path16.join(profilesDir, slug);
5703
+ if (fs16.existsSync(profileDir)) {
4847
5704
  return { handled: true, output: pc5.yellow(`Profile already exists: ${slug}`) };
4848
5705
  }
4849
5706
  const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
@@ -4859,16 +5716,16 @@ ${pc5.dim("Edit with: /profile edit")}` };
4859
5716
  Use: aman-agent --profile ${slug}`
4860
5717
  };
4861
5718
  }
4862
- fs13.mkdirSync(profileDir, { recursive: true });
4863
- const globalCore = path13.join(os12.homedir(), ".acore", "core.md");
4864
- if (fs13.existsSync(globalCore)) {
4865
- let content = fs13.readFileSync(globalCore, "utf-8");
5719
+ fs16.mkdirSync(profileDir, { recursive: true });
5720
+ const globalCore = path16.join(os15.homedir(), ".acore", "core.md");
5721
+ if (fs16.existsSync(globalCore)) {
5722
+ let content = fs16.readFileSync(globalCore, "utf-8");
4866
5723
  const aiName = name.charAt(0).toUpperCase() + name.slice(1);
4867
5724
  content = content.replace(/^# .+$/m, `# ${aiName}`);
4868
- fs13.writeFileSync(path13.join(profileDir, "core.md"), content, "utf-8");
5725
+ fs16.writeFileSync(path16.join(profileDir, "core.md"), content, "utf-8");
4869
5726
  } else {
4870
5727
  const aiName = name.charAt(0).toUpperCase() + name.slice(1);
4871
- fs13.writeFileSync(path13.join(profileDir, "core.md"), `# ${aiName}
5728
+ fs16.writeFileSync(path16.join(profileDir, "core.md"), `# ${aiName}
4872
5729
 
4873
5730
  ## Identity
4874
5731
  - Role: ${aiName} is your AI companion
@@ -4881,7 +5738,7 @@ ${pc5.dim("Edit with: /profile edit")}` };
4881
5738
  return {
4882
5739
  handled: true,
4883
5740
  output: pc5.green(`Profile created: ${slug}`) + `
4884
- Edit: ${path13.join(profileDir, "core.md")}
5741
+ Edit: ${path16.join(profileDir, "core.md")}
4885
5742
  Use: aman-agent --profile ${slug}
4886
5743
 
4887
5744
  ${pc5.dim("Add rules.md or skills.md for profile-specific overrides.")}`
@@ -4890,9 +5747,9 @@ ${pc5.dim("Edit with: /profile edit")}` };
4890
5747
  case "show": {
4891
5748
  const name = args[0];
4892
5749
  if (!name) return { handled: true, output: pc5.yellow("Usage: /profile show <name>") };
4893
- const profileDir = path13.join(profilesDir, name);
4894
- if (!fs13.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
4895
- const files = fs13.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
5750
+ const profileDir = path16.join(profilesDir, name);
5751
+ if (!fs16.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
5752
+ const files = fs16.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
4896
5753
  const lines = files.map((f) => ` ${f}`);
4897
5754
  return { handled: true, output: `Profile: ${pc5.bold(name)}
4898
5755
  Files:
@@ -4901,9 +5758,9 @@ ${lines.join("\n")}` };
4901
5758
  case "delete": {
4902
5759
  const name = args[0];
4903
5760
  if (!name) return { handled: true, output: pc5.yellow("Usage: /profile delete <name>") };
4904
- const profileDir = path13.join(profilesDir, name);
4905
- if (!fs13.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
4906
- fs13.rmSync(profileDir, { recursive: true });
5761
+ const profileDir = path16.join(profilesDir, name);
5762
+ if (!fs16.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
5763
+ fs16.rmSync(profileDir, { recursive: true });
4907
5764
  return { handled: true, output: pc5.dim(`Profile deleted: ${name}`) };
4908
5765
  }
4909
5766
  case "help":
@@ -4927,7 +5784,7 @@ ${lines.join("\n")}` };
4927
5784
  return { handled: true, output: pc5.yellow(`Unknown profile action: ${action}. Try /profile help`) };
4928
5785
  }
4929
5786
  }
4930
- function handlePlanCommand(action, args) {
5787
+ function handlePlanCommand(action, args, ctx) {
4931
5788
  if (!action) {
4932
5789
  const active = getActivePlan();
4933
5790
  if (!active) {
@@ -4956,17 +5813,29 @@ function handlePlanCommand(action, args) {
4956
5813
  case "done": {
4957
5814
  const active = getActivePlan();
4958
5815
  if (!active) return { handled: true, output: pc5.yellow("No active plan.") };
5816
+ const recordPlanMilestone = (stepIndex) => {
5817
+ if (ctx?.observationSession) {
5818
+ const step = active.steps[stepIndex];
5819
+ recordEvent(ctx.observationSession, {
5820
+ type: "milestone",
5821
+ summary: `Plan step done: ${step.text}`,
5822
+ data: { plan: active.name, stepIndex, stepText: step.text }
5823
+ });
5824
+ }
5825
+ };
4959
5826
  if (args.length > 0) {
4960
5827
  const stepNum = parseInt(args[0], 10);
4961
5828
  if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
4962
5829
  return { handled: true, output: pc5.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
4963
5830
  }
4964
5831
  markStepDone(active, stepNum - 1);
5832
+ recordPlanMilestone(stepNum - 1);
4965
5833
  return { handled: true, output: pc5.green(`Step ${stepNum} done!`) + "\n\n" + formatPlan(active) };
4966
5834
  }
4967
5835
  const next = active.steps.findIndex((s) => !s.done);
4968
5836
  if (next < 0) return { handled: true, output: pc5.green("All steps already complete!") };
4969
5837
  markStepDone(active, next);
5838
+ recordPlanMilestone(next);
4970
5839
  return { handled: true, output: pc5.green(`Step ${next + 1} done!`) + "\n\n" + formatPlan(active) };
4971
5840
  }
4972
5841
  case "undo": {
@@ -5109,10 +5978,10 @@ function handleShowcaseCommand(action, args) {
5109
5978
  Or place it as a sibling directory to aman-agent.`
5110
5979
  };
5111
5980
  }
5112
- const corePath = path13.join(os12.homedir(), ".acore", "core.md");
5981
+ const corePath = path16.join(os15.homedir(), ".acore", "core.md");
5113
5982
  let currentShowcase = null;
5114
- if (fs13.existsSync(corePath)) {
5115
- const content = fs13.readFileSync(corePath, "utf-8");
5983
+ if (fs16.existsSync(corePath)) {
5984
+ const content = fs16.readFileSync(corePath, "utf-8");
5116
5985
  const nameMatch = content.match(/^# (.+)/m);
5117
5986
  if (nameMatch) {
5118
5987
  const coreName = nameMatch[1].trim().toLowerCase();
@@ -5283,8 +6152,76 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
5283
6152
  "delegate",
5284
6153
  "team",
5285
6154
  "showcase",
5286
- "file"
6155
+ "file",
6156
+ "observe",
6157
+ "postmortem"
5287
6158
  ]);
6159
+ async function handleObserveCommand(action, ctx) {
6160
+ if (!ctx.observationSession) {
6161
+ return {
6162
+ handled: true,
6163
+ output: pc5.dim("Observation is disabled. Enable with recordObservations: true in config.")
6164
+ };
6165
+ }
6166
+ switch (action) {
6167
+ case "pause":
6168
+ pauseObservation(ctx.observationSession);
6169
+ return { handled: true, output: pc5.dim("Observation paused. Use /observe resume to continue.") };
6170
+ case "resume":
6171
+ resumeObservation(ctx.observationSession);
6172
+ return { handled: true, output: pc5.dim("Observation resumed.") };
6173
+ default:
6174
+ return { handled: true, output: getSessionStats(ctx.observationSession) };
6175
+ }
6176
+ }
6177
+ async function handlePostmortemCommand(action, args, ctx) {
6178
+ switch (action) {
6179
+ case "last": {
6180
+ const files = await listPostmortems();
6181
+ if (files.length === 0) return { handled: true, output: pc5.dim("No post-mortems found.") };
6182
+ const content = await readPostmortem(files[0]);
6183
+ return { handled: true, output: content ?? pc5.red("Could not read post-mortem.") };
6184
+ }
6185
+ case "list": {
6186
+ const files = await listPostmortems();
6187
+ if (files.length === 0) return { handled: true, output: pc5.dim("No post-mortems found.") };
6188
+ return { handled: true, output: "Post-mortems:\n" + files.map((f) => ` ${f}`).join("\n") };
6189
+ }
6190
+ default: {
6191
+ const allArgs = action ? [action, ...args] : args;
6192
+ const sinceIdx = allArgs.indexOf("--since");
6193
+ if (sinceIdx !== -1 && allArgs[sinceIdx + 1]) {
6194
+ const daysStr = allArgs[sinceIdx + 1];
6195
+ const days = parseInt(daysStr.replace("d", ""), 10) || 7;
6196
+ if (!ctx.llmClient) {
6197
+ return { handled: true, output: pc5.red("LLM client not available for analysis.") };
6198
+ }
6199
+ const analysis = await analyzePostmortemRange(days, ctx.llmClient);
6200
+ return { handled: true, output: analysis ?? pc5.red("Could not analyze post-mortems.") };
6201
+ }
6202
+ if (!ctx.observationSession || !ctx.llmClient || !ctx.messages) {
6203
+ return {
6204
+ handled: true,
6205
+ output: pc5.dim("Cannot generate post-mortem: missing session context.")
6206
+ };
6207
+ }
6208
+ const report = await generatePostmortemReport(
6209
+ ctx.observationSession.sessionId,
6210
+ ctx.messages,
6211
+ ctx.observationSession,
6212
+ ctx.llmClient
6213
+ );
6214
+ if (!report) return { handled: true, output: pc5.red("Could not generate post-mortem.") };
6215
+ const filePath = await savePostmortem(report);
6216
+ return {
6217
+ handled: true,
6218
+ output: formatPostmortemMarkdown(report) + `
6219
+
6220
+ ${pc5.dim(`Saved \u2192 ${filePath}`)}`
6221
+ };
6222
+ }
6223
+ }
6224
+ }
5288
6225
  async function handleCommand(input, ctx) {
5289
6226
  const trimmed = input.trim();
5290
6227
  if (!trimmed.startsWith("/")) return { handled: false };
@@ -5332,7 +6269,7 @@ async function handleCommand(input, ctx) {
5332
6269
  case "reset":
5333
6270
  return handleReset(action);
5334
6271
  case "plan":
5335
- return handlePlanCommand(action, args);
6272
+ return handlePlanCommand(action, args, ctx);
5336
6273
  case "profile":
5337
6274
  return handleProfileCommand(action, args);
5338
6275
  case "delegate":
@@ -5348,6 +6285,10 @@ async function handleCommand(input, ctx) {
5348
6285
  case "update":
5349
6286
  case "upgrade":
5350
6287
  return handleUpdate();
6288
+ case "observe":
6289
+ return handleObserveCommand(action, ctx);
6290
+ case "postmortem":
6291
+ return handlePostmortemCommand(action, args, ctx);
5351
6292
  default:
5352
6293
  return { handled: false };
5353
6294
  }
@@ -5427,9 +6368,10 @@ ${summaryParts.slice(0, 20).join("\n")}
5427
6368
  }
5428
6369
 
5429
6370
  // src/skill-engine.ts
5430
- import fs14 from "fs";
5431
- import path14 from "path";
5432
- import os13 from "os";
6371
+ import fs17 from "fs";
6372
+ import fsp from "fs/promises";
6373
+ import path17 from "path";
6374
+ import os16 from "os";
5433
6375
  var SKILL_TRIGGERS = {
5434
6376
  testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
5435
6377
  "api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
@@ -5444,20 +6386,34 @@ var SKILL_TRIGGERS = {
5444
6386
  typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
5445
6387
  accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
5446
6388
  };
5447
- var LEVEL_FILE = path14.join(os13.homedir(), ".aman-agent", "skill-levels.json");
6389
+ async function loadRuntimeTriggers(skillsMdPath) {
6390
+ try {
6391
+ const content = await fsp.readFile(skillsMdPath, "utf-8");
6392
+ const skills = extractSkillsWithMarkers(content);
6393
+ const result = /* @__PURE__ */ new Map();
6394
+ for (const [name, marker] of skills) {
6395
+ result.set(name, marker.triggers);
6396
+ }
6397
+ return result;
6398
+ } catch (err) {
6399
+ log.debug("skill-engine", "loadRuntimeTriggers failed", err);
6400
+ return /* @__PURE__ */ new Map();
6401
+ }
6402
+ }
6403
+ var LEVEL_FILE = path17.join(os16.homedir(), ".aman-agent", "skill-levels.json");
5448
6404
  function loadSkillLevels() {
5449
6405
  try {
5450
- if (fs14.existsSync(LEVEL_FILE)) {
5451
- return JSON.parse(fs14.readFileSync(LEVEL_FILE, "utf-8"));
6406
+ if (fs17.existsSync(LEVEL_FILE)) {
6407
+ return JSON.parse(fs17.readFileSync(LEVEL_FILE, "utf-8"));
5452
6408
  }
5453
6409
  } catch {
5454
6410
  }
5455
6411
  return {};
5456
6412
  }
5457
6413
  function saveSkillLevels(levels) {
5458
- const dir = path14.dirname(LEVEL_FILE);
5459
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
5460
- fs14.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
6414
+ const dir = path17.dirname(LEVEL_FILE);
6415
+ if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
6416
+ fs17.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
5461
6417
  }
5462
6418
  function computeLevel(activations) {
5463
6419
  if (activations >= 50) return { level: 5, label: "Expert" };
@@ -5476,20 +6432,28 @@ function recordActivation(skillName) {
5476
6432
  saveSkillLevels(levels);
5477
6433
  return computeLevel(levels[skillName].activations);
5478
6434
  }
5479
- function matchSkills(userInput, installedSkillNames) {
6435
+ function matchSkills(userInput, installedSkillNames, runtimeTriggers = /* @__PURE__ */ new Map()) {
5480
6436
  const input = userInput.toLowerCase();
5481
- const matched = [];
6437
+ const matched = /* @__PURE__ */ new Set();
5482
6438
  for (const skillName of installedSkillNames) {
5483
6439
  const triggers = SKILL_TRIGGERS[skillName];
5484
6440
  if (!triggers) continue;
5485
6441
  for (const trigger of triggers) {
5486
6442
  if (input.includes(trigger)) {
5487
- matched.push(skillName);
6443
+ matched.add(skillName);
5488
6444
  break;
5489
6445
  }
5490
6446
  }
5491
6447
  }
5492
- return matched;
6448
+ for (const [skillName, triggers] of runtimeTriggers) {
6449
+ for (const trigger of triggers) {
6450
+ if (input.includes(trigger)) {
6451
+ matched.add(skillName);
6452
+ break;
6453
+ }
6454
+ }
6455
+ }
6456
+ return Array.from(matched);
5493
6457
  }
5494
6458
  function formatSkillContext(skillName, skillContent, level) {
5495
6459
  let depthHint;
@@ -5513,8 +6477,10 @@ async function autoTriggerSkills(userInput, mcpManager) {
5513
6477
  const result = await mcpManager.callTool("skill_list", {});
5514
6478
  const skills = JSON.parse(result);
5515
6479
  const installed = skills.filter((s) => s.installed).map((s) => s.name);
5516
- if (installed.length === 0) return "";
5517
- const matched = matchSkills(userInput, installed);
6480
+ const skillsMdPath = path17.join(os16.homedir(), ".askill", "skills.md");
6481
+ const runtimeTriggers = await loadRuntimeTriggers(skillsMdPath);
6482
+ if (installed.length === 0 && runtimeTriggers.size === 0) return "";
6483
+ const matched = matchSkills(userInput, installed, runtimeTriggers);
5518
6484
  if (matched.length === 0) return "";
5519
6485
  const blocks = [];
5520
6486
  for (const skillName of matched.slice(0, 2)) {
@@ -6070,9 +7036,9 @@ function humanizeError(message) {
6070
7036
  }
6071
7037
 
6072
7038
  // src/hints.ts
6073
- import fs15 from "fs";
6074
- import path15 from "path";
6075
- import os14 from "os";
7039
+ import fs18 from "fs";
7040
+ import path18 from "path";
7041
+ import os17 from "os";
6076
7042
  var HINTS = [
6077
7043
  {
6078
7044
  id: "eval",
@@ -6110,11 +7076,11 @@ function getHint(state, ctx) {
6110
7076
  }
6111
7077
  return null;
6112
7078
  }
6113
- var HINTS_FILE = path15.join(os14.homedir(), ".aman-agent", "hints-seen.json");
7079
+ var HINTS_FILE = path18.join(os17.homedir(), ".aman-agent", "hints-seen.json");
6114
7080
  function loadShownHints() {
6115
7081
  try {
6116
- if (fs15.existsSync(HINTS_FILE)) {
6117
- const data = JSON.parse(fs15.readFileSync(HINTS_FILE, "utf-8"));
7082
+ if (fs18.existsSync(HINTS_FILE)) {
7083
+ const data = JSON.parse(fs18.readFileSync(HINTS_FILE, "utf-8"));
6118
7084
  return new Set(Array.isArray(data) ? data : []);
6119
7085
  }
6120
7086
  } catch {
@@ -6123,9 +7089,9 @@ function loadShownHints() {
6123
7089
  }
6124
7090
  function saveShownHints(shown) {
6125
7091
  try {
6126
- const dir = path15.dirname(HINTS_FILE);
6127
- fs15.mkdirSync(dir, { recursive: true });
6128
- fs15.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
7092
+ const dir = path18.dirname(HINTS_FILE);
7093
+ fs18.mkdirSync(dir, { recursive: true });
7094
+ fs18.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
6129
7095
  } catch {
6130
7096
  }
6131
7097
  }
@@ -6262,10 +7228,14 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
6262
7228
  await bgTasks.waitAll();
6263
7229
  bgTasks.displayCompleted();
6264
7230
  }
7231
+ if (observationSession) {
7232
+ await flushEvents(observationSession).catch(() => {
7233
+ });
7234
+ }
6265
7235
  if (mcpManager && hooksConfig) {
6266
7236
  try {
6267
- const hookCtx = { mcpManager, config: hooksConfig };
6268
- await onSessionEnd(hookCtx, messages, sessionId);
7237
+ const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
7238
+ await onSessionEnd(hookCtx, messages, sessionId, observationSession);
6269
7239
  } catch (err) {
6270
7240
  log.debug("agent", "session end hook failed on SIGINT", err);
6271
7241
  }
@@ -6314,6 +7284,13 @@ Type a message, ${pc7.dim("/help")} for commands, or ${pc7.dim("/quit")} to exit
6314
7284
  log.warn("agent", "session start hook failed", err);
6315
7285
  }
6316
7286
  }
7287
+ let observationSession;
7288
+ let prevSentiment;
7289
+ if (hooksConfig?.recordObservations !== false) {
7290
+ observationSession = createObservationSession(sessionId);
7291
+ cleanupOldObservations().catch(() => {
7292
+ });
7293
+ }
6317
7294
  while (true) {
6318
7295
  if (bgTasks.hasCompleted) {
6319
7296
  const completed = bgTasks.collectCompleted();
@@ -6353,13 +7330,24 @@ ${task.result}`
6353
7330
  }
6354
7331
  const input = await prompt();
6355
7332
  if (!input.trim()) continue;
6356
- const cmdResult = await handleCommand(input, { model, mcpManager, llmClient: client, tools });
7333
+ const cmdResult = await handleCommand(input, {
7334
+ model,
7335
+ mcpManager,
7336
+ llmClient: client,
7337
+ tools,
7338
+ observationSession,
7339
+ messages
7340
+ });
6357
7341
  if (cmdResult.handled) {
6358
7342
  if (cmdResult.quit) {
7343
+ if (observationSession) {
7344
+ await flushEvents(observationSession).catch(() => {
7345
+ });
7346
+ }
6359
7347
  if (mcpManager && hooksConfig) {
6360
7348
  try {
6361
- const hookCtx = { mcpManager, config: hooksConfig };
6362
- await onSessionEnd(hookCtx, messages, sessionId);
7349
+ const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
7350
+ await onSessionEnd(hookCtx, messages, sessionId, observationSession);
6363
7351
  } catch (err) {
6364
7352
  log.debug("agent", "session end hook failed on quit", err);
6365
7353
  }
@@ -6370,9 +7358,9 @@ ${task.result}`
6370
7358
  }
6371
7359
  if (cmdResult.exportConversation) {
6372
7360
  try {
6373
- const exportDir = path16.join(os15.homedir(), ".aman-agent", "exports");
6374
- fs16.mkdirSync(exportDir, { recursive: true });
6375
- const exportPath = path16.join(exportDir, `${sessionId}.md`);
7361
+ const exportDir = path19.join(os18.homedir(), ".aman-agent", "exports");
7362
+ fs19.mkdirSync(exportDir, { recursive: true });
7363
+ const exportPath = path19.join(exportDir, `${sessionId}.md`);
6376
7364
  const lines = [
6377
7365
  `# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
6378
7366
  `**Model:** ${model}`,
@@ -6386,7 +7374,7 @@ ${task.result}`
6386
7374
  lines.push(`${label} ${msg.content}`, "");
6387
7375
  }
6388
7376
  }
6389
- fs16.writeFileSync(exportPath, lines.join("\n"), "utf-8");
7377
+ fs19.writeFileSync(exportPath, lines.join("\n"), "utf-8");
6390
7378
  console.log(pc7.green(`Exported to ${exportPath}`));
6391
7379
  } catch {
6392
7380
  console.log(pc7.red("Failed to export conversation."));
@@ -6512,25 +7500,25 @@ ${knowledgeItem.content}
6512
7500
  for (const match of filePathMatches) {
6513
7501
  let filePath = match[1];
6514
7502
  if (filePath.startsWith("~/")) {
6515
- filePath = path16.join(os15.homedir(), filePath.slice(2));
7503
+ filePath = path19.join(os18.homedir(), filePath.slice(2));
6516
7504
  }
6517
- if (!fs16.existsSync(filePath) || !fs16.statSync(filePath).isFile()) continue;
6518
- const ext = path16.extname(filePath).toLowerCase();
7505
+ if (!fs19.existsSync(filePath) || !fs19.statSync(filePath).isFile()) continue;
7506
+ const ext = path19.extname(filePath).toLowerCase();
6519
7507
  if (imageExts.has(ext)) {
6520
7508
  try {
6521
- const stat = fs16.statSync(filePath);
7509
+ const stat = fs19.statSync(filePath);
6522
7510
  if (stat.size > maxImageBytes) {
6523
- process.stdout.write(pc7.yellow(` [skipped: ${path16.basename(filePath)} \u2014 exceeds 20MB limit]
7511
+ process.stdout.write(pc7.yellow(` [skipped: ${path19.basename(filePath)} \u2014 exceeds 20MB limit]
6524
7512
  `));
6525
7513
  continue;
6526
7514
  }
6527
- const data = fs16.readFileSync(filePath).toString("base64");
7515
+ const data = fs19.readFileSync(filePath).toString("base64");
6528
7516
  const mediaType = mimeMap[ext] || "image/png";
6529
7517
  imageBlocks.push({
6530
7518
  type: "image",
6531
7519
  source: { type: "base64", media_type: mediaType, data }
6532
7520
  });
6533
- process.stdout.write(pc7.dim(` [attached image: ${path16.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
7521
+ process.stdout.write(pc7.dim(` [attached image: ${path19.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
6534
7522
  `));
6535
7523
  } catch {
6536
7524
  process.stdout.write(pc7.dim(` [could not read image: ${filePath}]
@@ -6538,7 +7526,7 @@ ${knowledgeItem.content}
6538
7526
  }
6539
7527
  } else if (textExts.has(ext) || ext === "") {
6540
7528
  try {
6541
- const content = fs16.readFileSync(filePath, "utf-8");
7529
+ const content = fs19.readFileSync(filePath, "utf-8");
6542
7530
  const maxChars = 5e4;
6543
7531
  const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
6544
7532
 
@@ -6548,7 +7536,7 @@ ${knowledgeItem.content}
6548
7536
  <file path="${filePath}" size="${content.length} chars">
6549
7537
  ${trimmed}
6550
7538
  </file>`;
6551
- process.stdout.write(pc7.dim(` [attached: ${path16.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
7539
+ process.stdout.write(pc7.dim(` [attached: ${path19.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
6552
7540
  `));
6553
7541
  } catch {
6554
7542
  process.stdout.write(pc7.dim(` [could not read: ${filePath}]
@@ -6557,7 +7545,7 @@ ${trimmed}
6557
7545
  } else if (docExts.has(ext)) {
6558
7546
  if (mcpManager) {
6559
7547
  try {
6560
- process.stdout.write(pc7.dim(` [converting: ${path16.basename(filePath)}...]
7548
+ process.stdout.write(pc7.dim(` [converting: ${path19.basename(filePath)}...]
6561
7549
  `));
6562
7550
  const converted = await mcpManager.callTool("doc_convert", { path: filePath });
6563
7551
  if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
@@ -6566,7 +7554,7 @@ ${trimmed}
6566
7554
  <file path="${filePath}" format="${ext}">
6567
7555
  ${converted.slice(0, 5e4)}
6568
7556
  </file>`;
6569
- process.stdout.write(pc7.dim(` [attached: ${path16.basename(filePath)} (converted from ${ext})]
7557
+ process.stdout.write(pc7.dim(` [attached: ${path19.basename(filePath)} (converted from ${ext})]
6570
7558
  `));
6571
7559
  } else {
6572
7560
  textContent += `
@@ -6578,7 +7566,7 @@ ${converted}
6578
7566
  `));
6579
7567
  }
6580
7568
  } catch {
6581
- process.stdout.write(pc7.dim(` [could not convert: ${path16.basename(filePath)}]
7569
+ process.stdout.write(pc7.dim(` [could not convert: ${path19.basename(filePath)}]
6582
7570
  `));
6583
7571
  }
6584
7572
  } else {
@@ -6659,6 +7647,33 @@ ${converted}
6659
7647
  });
6660
7648
  syncPersonalityToCore(state, mcpManager).catch(() => {
6661
7649
  });
7650
+ if (observationSession && prevSentiment !== state.sentiment.dominant) {
7651
+ recordEvent(observationSession, {
7652
+ type: "sentiment_shift",
7653
+ summary: `${prevSentiment ?? "neutral"} \u2192 ${state.sentiment.dominant}`,
7654
+ data: { from: prevSentiment ?? "neutral", to: state.sentiment.dominant }
7655
+ });
7656
+ prevSentiment = state.sentiment.dominant;
7657
+ }
7658
+ if (observationSession && state.sentiment.frustration > 0.6) {
7659
+ recordEvent(observationSession, {
7660
+ type: "blocker",
7661
+ summary: "User expressing frustration",
7662
+ data: { frustrationLevel: state.sentiment.frustration }
7663
+ });
7664
+ }
7665
+ if (observationSession && recentUserMsgs.length >= 6) {
7666
+ const recent = recentUserMsgs.slice(-3);
7667
+ const previous = recentUserMsgs.slice(-6, -3);
7668
+ const shift = detectTopicShift(recent, previous);
7669
+ if (shift.shifted) {
7670
+ recordEvent(observationSession, {
7671
+ type: "topic_shift",
7672
+ summary: `Topics: ${shift.newTopics.join(", ")}`,
7673
+ data: { newTopics: shift.newTopics }
7674
+ });
7675
+ }
7676
+ }
6662
7677
  const nudge = formatWellbeingNudge(state);
6663
7678
  if (nudge) {
6664
7679
  augmentedSystemPrompt += "\n" + nudge;
@@ -6757,7 +7772,38 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
6757
7772
  }
6758
7773
  process.stdout.write(pc7.dim(` [using ${toolUse.name}...]
6759
7774
  `));
6760
- const result = await mcpManager.callTool(toolUse.name, toolUse.input);
7775
+ const toolStartMs = Date.now();
7776
+ let result;
7777
+ try {
7778
+ result = await mcpManager.callTool(toolUse.name, toolUse.input);
7779
+ } catch (toolErr) {
7780
+ const errMsg = toolErr instanceof Error ? toolErr.message : String(toolErr);
7781
+ if (observationSession) {
7782
+ recordEvent(observationSession, {
7783
+ type: "tool_error",
7784
+ summary: `${toolUse.name}: ${errMsg}`,
7785
+ data: { tool: toolUse.name, error: errMsg }
7786
+ });
7787
+ }
7788
+ throw toolErr;
7789
+ }
7790
+ if (observationSession) {
7791
+ const durationMs = Date.now() - toolStartMs;
7792
+ recordEvent(observationSession, {
7793
+ type: "tool_call",
7794
+ summary: `${toolUse.name} (${durationMs}ms)`,
7795
+ data: { tool: toolUse.name, durationMs, success: true }
7796
+ });
7797
+ const FILE_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "file_create", "file_delete"]);
7798
+ if (FILE_TOOLS.has(toolUse.name)) {
7799
+ const filePath = toolUse.input?.path ?? "unknown";
7800
+ recordEvent(observationSession, {
7801
+ type: "file_change",
7802
+ summary: `${toolUse.name}: ${String(filePath)}`,
7803
+ data: { tool: toolUse.name, path: filePath }
7804
+ });
7805
+ }
7806
+ }
6761
7807
  const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
6762
7808
  if (!skipLogging) {
6763
7809
  try {
@@ -6799,6 +7845,10 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
6799
7845
  });
6800
7846
  log.debug("agent", `checkpoint saved at turn ${currentTurn}`);
6801
7847
  }
7848
+ if (observationSession && observationSession.events.length >= 5) {
7849
+ flushEvents(observationSession).catch(() => {
7850
+ });
7851
+ }
6802
7852
  if (hooksConfig?.extractMemories) {
6803
7853
  const assistantText = typeof response.message.content === "string" ? response.message.content : response.message.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
6804
7854
  if (assistantText) {
@@ -6820,7 +7870,7 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
6820
7870
  }
6821
7871
  if (hooksConfig?.featureHints) {
6822
7872
  hintState.turnCount++;
6823
- const hasWorkflows = fs16.existsSync(path16.join(os15.homedir(), ".aflow", "flow.md"));
7873
+ const hasWorkflows = fs19.existsSync(path19.join(os18.homedir(), ".aflow", "flow.md"));
6824
7874
  const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
6825
7875
  const hint = getHint(hintState, { hasWorkflows, memoryCount });
6826
7876
  if (hint) {
@@ -6866,9 +7916,9 @@ async function saveConversationToMemory(messages, sessionId) {
6866
7916
  }
6867
7917
 
6868
7918
  // src/index.ts
6869
- import fs17 from "fs";
6870
- import path17 from "path";
6871
- import os16 from "os";
7919
+ import fs20 from "fs";
7920
+ import path20 from "path";
7921
+ import os19 from "os";
6872
7922
 
6873
7923
  // src/presets.ts
6874
7924
  var PRESETS = {
@@ -6977,9 +8027,9 @@ ${wfSections}`;
6977
8027
 
6978
8028
  // src/index.ts
6979
8029
  async function autoDetectConfig() {
6980
- const reconfigMarker = path17.join(os16.homedir(), ".aman-agent", ".reconfig");
6981
- if (fs17.existsSync(reconfigMarker)) {
6982
- fs17.unlinkSync(reconfigMarker);
8030
+ const reconfigMarker = path20.join(os19.homedir(), ".aman-agent", ".reconfig");
8031
+ if (fs20.existsSync(reconfigMarker)) {
8032
+ fs20.unlinkSync(reconfigMarker);
6983
8033
  return null;
6984
8034
  }
6985
8035
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
@@ -7008,11 +8058,11 @@ async function autoDetectConfig() {
7008
8058
  return null;
7009
8059
  }
7010
8060
  function bootstrapEcosystem() {
7011
- const home2 = os16.homedir();
7012
- const corePath = path17.join(home2, ".acore", "core.md");
7013
- if (fs17.existsSync(corePath)) return false;
7014
- fs17.mkdirSync(path17.join(home2, ".acore"), { recursive: true });
7015
- fs17.writeFileSync(corePath, [
8061
+ const home2 = os19.homedir();
8062
+ const corePath = path20.join(home2, ".acore", "core.md");
8063
+ if (fs20.existsSync(corePath)) return false;
8064
+ fs20.mkdirSync(path20.join(home2, ".acore"), { recursive: true });
8065
+ fs20.writeFileSync(corePath, [
7016
8066
  "# Aman",
7017
8067
  "",
7018
8068
  "## Personality",
@@ -7024,11 +8074,11 @@ function bootstrapEcosystem() {
7024
8074
  "## Session",
7025
8075
  "_New companion \u2014 no prior sessions._"
7026
8076
  ].join("\n"), "utf-8");
7027
- const rulesDir = path17.join(home2, ".arules");
7028
- const rulesPath = path17.join(rulesDir, "rules.md");
7029
- if (!fs17.existsSync(rulesPath)) {
7030
- fs17.mkdirSync(rulesDir, { recursive: true });
7031
- fs17.writeFileSync(rulesPath, [
8077
+ const rulesDir = path20.join(home2, ".arules");
8078
+ const rulesPath = path20.join(rulesDir, "rules.md");
8079
+ if (!fs20.existsSync(rulesPath)) {
8080
+ fs20.mkdirSync(rulesDir, { recursive: true });
8081
+ fs20.writeFileSync(rulesPath, [
7032
8082
  "# Guardrails",
7033
8083
  "",
7034
8084
  "## safety",
@@ -7040,22 +8090,22 @@ function bootstrapEcosystem() {
7040
8090
  "- Respect the user's preferences stored in memory"
7041
8091
  ].join("\n"), "utf-8");
7042
8092
  }
7043
- const flowDir = path17.join(home2, ".aflow");
7044
- const flowPath = path17.join(flowDir, "flow.md");
7045
- if (!fs17.existsSync(flowPath)) {
7046
- fs17.mkdirSync(flowDir, { recursive: true });
7047
- fs17.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
8093
+ const flowDir = path20.join(home2, ".aflow");
8094
+ const flowPath = path20.join(flowDir, "flow.md");
8095
+ if (!fs20.existsSync(flowPath)) {
8096
+ fs20.mkdirSync(flowDir, { recursive: true });
8097
+ fs20.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
7048
8098
  }
7049
- const skillDir = path17.join(home2, ".askill");
7050
- const skillPath = path17.join(skillDir, "skills.md");
7051
- if (!fs17.existsSync(skillPath)) {
7052
- fs17.mkdirSync(skillDir, { recursive: true });
7053
- fs17.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
8099
+ const skillDir = path20.join(home2, ".askill");
8100
+ const skillPath = path20.join(skillDir, "skills.md");
8101
+ if (!fs20.existsSync(skillPath)) {
8102
+ fs20.mkdirSync(skillDir, { recursive: true });
8103
+ fs20.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
7054
8104
  }
7055
8105
  return true;
7056
8106
  }
7057
8107
  var program = new Command();
7058
- program.name("aman-agent").description("Your AI companion, running locally").version("0.24.3").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).option("--profile <name>", "Use a specific agent profile (e.g., coder, writer, researcher)").action(async (options) => {
8108
+ program.name("aman-agent").description("Your AI companion, running locally").version("0.26.0").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).option("--profile <name>", "Use a specific agent profile (e.g., coder, writer, researcher)").action(async (options) => {
7059
8109
  p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
7060
8110
  let config = loadConfig();
7061
8111
  if (!config) {
@@ -7407,19 +8457,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
7407
8457
  });
7408
8458
  if (p3.isCancel(preset)) process.exit(0);
7409
8459
  const result = applyPreset(preset, name || "Aman");
7410
- const home2 = os16.homedir();
7411
- fs17.mkdirSync(path17.join(home2, ".acore"), { recursive: true });
7412
- fs17.writeFileSync(path17.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
8460
+ const home2 = os19.homedir();
8461
+ fs20.mkdirSync(path20.join(home2, ".acore"), { recursive: true });
8462
+ fs20.writeFileSync(path20.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
7413
8463
  p3.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
7414
8464
  if (result.rulesMd) {
7415
- fs17.mkdirSync(path17.join(home2, ".arules"), { recursive: true });
7416
- fs17.writeFileSync(path17.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
8465
+ fs20.mkdirSync(path20.join(home2, ".arules"), { recursive: true });
8466
+ fs20.writeFileSync(path20.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
7417
8467
  const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
7418
8468
  p3.log.success(`${ruleCount} rules set`);
7419
8469
  }
7420
8470
  if (result.flowMd) {
7421
- fs17.mkdirSync(path17.join(home2, ".aflow"), { recursive: true });
7422
- fs17.writeFileSync(path17.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
8471
+ fs20.mkdirSync(path20.join(home2, ".aflow"), { recursive: true });
8472
+ fs20.writeFileSync(path20.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
7423
8473
  const wfCount = (result.flowMd.match(/^## /gm) || []).length;
7424
8474
  p3.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
7425
8475
  }