@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/README.md +297 -27
- package/dist/index.js +1232 -182
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
1391
|
-
import
|
|
1392
|
-
import
|
|
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
|
|
1400
|
-
import
|
|
1401
|
-
import
|
|
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
|
|
2365
|
-
import
|
|
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 =
|
|
2807
|
-
if (
|
|
3471
|
+
const projectContextPath = path13.join(process.cwd(), ".acore", "context.md");
|
|
3472
|
+
if (fs13.existsSync(projectContextPath) && messages.length > 2) {
|
|
2808
3473
|
try {
|
|
2809
|
-
let contextContent =
|
|
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
|
-
|
|
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
|
|
3034
|
-
import
|
|
3035
|
-
import
|
|
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
|
|
3803
|
+
return path14.join(os13.homedir(), ".acore", "teams");
|
|
3039
3804
|
}
|
|
3040
3805
|
function ensureTeamsDir() {
|
|
3041
3806
|
const dir = getTeamsDir();
|
|
3042
|
-
if (!
|
|
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
|
|
3812
|
+
return path14.join(ensureTeamsDir(), `${slug}.json`);
|
|
3048
3813
|
}
|
|
3049
3814
|
function createTeam(team) {
|
|
3050
3815
|
const fp = teamPath(team.name);
|
|
3051
|
-
|
|
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 (!
|
|
3820
|
+
if (!fs14.existsSync(fp)) return null;
|
|
3056
3821
|
try {
|
|
3057
|
-
return JSON.parse(
|
|
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 (!
|
|
3829
|
+
if (!fs14.existsSync(dir)) return [];
|
|
3065
3830
|
const teams = [];
|
|
3066
|
-
for (const file of
|
|
3831
|
+
for (const file of fs14.readdirSync(dir)) {
|
|
3067
3832
|
if (!file.endsWith(".json")) continue;
|
|
3068
3833
|
try {
|
|
3069
|
-
const content =
|
|
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 (!
|
|
3079
|
-
|
|
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
|
|
3306
|
-
import
|
|
3307
|
-
import
|
|
4070
|
+
import fs15 from "fs";
|
|
4071
|
+
import path15 from "path";
|
|
4072
|
+
import os14 from "os";
|
|
3308
4073
|
function getPlansDir() {
|
|
3309
|
-
const localDir =
|
|
3310
|
-
const localAcore =
|
|
3311
|
-
if (
|
|
3312
|
-
return
|
|
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 (!
|
|
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
|
|
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() ||
|
|
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
|
-
|
|
4154
|
+
fs15.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
3390
4155
|
}
|
|
3391
4156
|
function loadPlan(name) {
|
|
3392
4157
|
const fp = planPath(name);
|
|
3393
|
-
if (!
|
|
3394
|
-
const content =
|
|
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 (!
|
|
4164
|
+
if (!fs15.existsSync(dir)) return [];
|
|
3400
4165
|
const plans = [];
|
|
3401
|
-
for (const file of
|
|
4166
|
+
for (const file of fs15.readdirSync(dir)) {
|
|
3402
4167
|
if (!file.endsWith(".md")) continue;
|
|
3403
|
-
const fp =
|
|
3404
|
-
const content =
|
|
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 (!
|
|
4278
|
+
if (!fs16.existsSync(filePath)) {
|
|
3514
4279
|
return pc5.dim(`No ${label} file found at ${filePath}`);
|
|
3515
4280
|
}
|
|
3516
|
-
return
|
|
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 =
|
|
4556
|
+
const home2 = os15.homedir();
|
|
3792
4557
|
if (!action) {
|
|
3793
|
-
const content = readEcosystemFile(
|
|
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(
|
|
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 =
|
|
3875
|
-
const toolsFile =
|
|
3876
|
-
if (!
|
|
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 =
|
|
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 =
|
|
4655
|
+
const home2 = os15.homedir();
|
|
3891
4656
|
if (!action) {
|
|
3892
|
-
const content = readEcosystemFile(
|
|
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 =
|
|
3915
|
-
const raw = readEcosystemFile(
|
|
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")}
|
|
3930
|
-
` ${pc5.cyan("/skills install")} <name>
|
|
3931
|
-
` ${pc5.cyan("/skills uninstall")} <name>
|
|
3932
|
-
` ${pc5.cyan("/skills search")} <query>
|
|
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 =
|
|
4793
|
+
const home2 = os15.homedir();
|
|
3939
4794
|
if (!action) {
|
|
3940
|
-
const content = readEcosystemFile(
|
|
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 =
|
|
3953
|
-
if (!
|
|
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 =
|
|
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:
|
|
4479
|
-
memory:
|
|
4480
|
-
identity:
|
|
4481
|
-
rules:
|
|
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 (
|
|
4507
|
-
|
|
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
|
-
|
|
4514
|
-
|
|
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.
|
|
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 =
|
|
4576
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4846
|
-
if (
|
|
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
|
-
|
|
4863
|
-
const globalCore =
|
|
4864
|
-
if (
|
|
4865
|
-
let content =
|
|
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
|
-
|
|
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
|
-
|
|
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: ${
|
|
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 =
|
|
4894
|
-
if (!
|
|
4895
|
-
const files =
|
|
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 =
|
|
4905
|
-
if (!
|
|
4906
|
-
|
|
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 =
|
|
5981
|
+
const corePath = path16.join(os15.homedir(), ".acore", "core.md");
|
|
5113
5982
|
let currentShowcase = null;
|
|
5114
|
-
if (
|
|
5115
|
-
const content =
|
|
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
|
|
5431
|
-
import
|
|
5432
|
-
import
|
|
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
|
-
|
|
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 (
|
|
5451
|
-
return JSON.parse(
|
|
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 =
|
|
5459
|
-
if (!
|
|
5460
|
-
|
|
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.
|
|
6443
|
+
matched.add(skillName);
|
|
5488
6444
|
break;
|
|
5489
6445
|
}
|
|
5490
6446
|
}
|
|
5491
6447
|
}
|
|
5492
|
-
|
|
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
|
-
|
|
5517
|
-
const
|
|
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
|
|
6074
|
-
import
|
|
6075
|
-
import
|
|
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 =
|
|
7079
|
+
var HINTS_FILE = path18.join(os17.homedir(), ".aman-agent", "hints-seen.json");
|
|
6114
7080
|
function loadShownHints() {
|
|
6115
7081
|
try {
|
|
6116
|
-
if (
|
|
6117
|
-
const data = JSON.parse(
|
|
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 =
|
|
6127
|
-
|
|
6128
|
-
|
|
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, {
|
|
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 =
|
|
6374
|
-
|
|
6375
|
-
const exportPath =
|
|
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
|
-
|
|
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 =
|
|
7503
|
+
filePath = path19.join(os18.homedir(), filePath.slice(2));
|
|
6516
7504
|
}
|
|
6517
|
-
if (!
|
|
6518
|
-
const ext =
|
|
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 =
|
|
7509
|
+
const stat = fs19.statSync(filePath);
|
|
6522
7510
|
if (stat.size > maxImageBytes) {
|
|
6523
|
-
process.stdout.write(pc7.yellow(` [skipped: ${
|
|
7511
|
+
process.stdout.write(pc7.yellow(` [skipped: ${path19.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
6524
7512
|
`));
|
|
6525
7513
|
continue;
|
|
6526
7514
|
}
|
|
6527
|
-
const data =
|
|
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: ${
|
|
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 =
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
|
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 =
|
|
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
|
|
6870
|
-
import
|
|
6871
|
-
import
|
|
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 =
|
|
6981
|
-
if (
|
|
6982
|
-
|
|
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 =
|
|
7012
|
-
const corePath =
|
|
7013
|
-
if (
|
|
7014
|
-
|
|
7015
|
-
|
|
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 =
|
|
7028
|
-
const rulesPath =
|
|
7029
|
-
if (!
|
|
7030
|
-
|
|
7031
|
-
|
|
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 =
|
|
7044
|
-
const flowPath =
|
|
7045
|
-
if (!
|
|
7046
|
-
|
|
7047
|
-
|
|
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 =
|
|
7050
|
-
const skillPath =
|
|
7051
|
-
if (!
|
|
7052
|
-
|
|
7053
|
-
|
|
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.
|
|
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 =
|
|
7411
|
-
|
|
7412
|
-
|
|
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
|
-
|
|
7416
|
-
|
|
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
|
-
|
|
7422
|
-
|
|
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
|
}
|