@aman_asmuei/aman-agent 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1387,18 +1387,18 @@ var McpManager = class {
1387
1387
 
1388
1388
  // src/agent.ts
1389
1389
  import * as readline from "readline";
1390
- import fs18 from "fs";
1391
- import path18 from "path";
1392
- import os17 from "os";
1390
+ import fs20 from "fs";
1391
+ import path20 from "path";
1392
+ import os19 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 fs15 from "fs";
1400
- import path15 from "path";
1401
- import os14 from "os";
1399
+ import fs17 from "fs";
1400
+ import path17 from "path";
1401
+ import os16 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 fs12 from "fs";
2365
- import path12 from "path";
2364
+ import fs14 from "fs";
2365
+ import path14 from "path";
2366
+ import os13 from "os";
2366
2367
 
2367
2368
  // src/personality.ts
2368
2369
  var FRUSTRATION_SIGNALS = [
@@ -2523,13 +2524,19 @@ function formatWellbeingNudge(state) {
2523
2524
  if (!state.wellbeingNudge) return null;
2524
2525
  return WELLBEING_NUDGES[state.wellbeingNudge] || null;
2525
2526
  }
2526
- async function syncPersonalityToCore(state, mcpManager) {
2527
+ async function syncPersonalityToCore(state, mcpManager, modelMetrics) {
2527
2528
  try {
2528
- await mcpManager.callTool("identity_update_dynamics", {
2529
+ const payload = {
2529
2530
  currentRead: state.currentRead,
2530
2531
  energy: state.energy,
2531
2532
  activeMode: state.activeMode
2532
- });
2533
+ };
2534
+ if (modelMetrics) {
2535
+ payload.trust = `${(modelMetrics.trustScore * 100).toFixed(0)}%`;
2536
+ payload.sessions = modelMetrics.totalSessions;
2537
+ payload.sentimentTrend = modelMetrics.sentimentTrend;
2538
+ }
2539
+ await mcpManager.callTool("identity_update_dynamics", payload);
2533
2540
  } catch (err) {
2534
2541
  log.debug("personality", "identity_update_dynamics failed", err);
2535
2542
  }
@@ -2689,8 +2696,28 @@ Return ONLY valid JSON matching this schema (no markdown, no explanation):
2689
2696
  "decisions": ["key choices made with rationale"],
2690
2697
  "sentimentArc": "how mood evolved during session",
2691
2698
  "patterns": ["recurring behaviors worth remembering for future sessions"],
2692
- "recommendations": ["actionable suggestions for next session"]
2693
- }`;
2699
+ "recommendations": ["actionable suggestions for next session"],
2700
+ "crystallizationCandidates": [
2701
+ {
2702
+ "name": "lowercase-kebab-name",
2703
+ "description": "1-sentence description of when this would be useful",
2704
+ "triggers": ["3-8", "trigger", "keywords"],
2705
+ "approach": "1-paragraph context: when and why to use this procedure",
2706
+ "steps": ["ordered step 1", "ordered step 2"],
2707
+ "gotchas": ["common mistake 1"],
2708
+ "confidence": 0.0
2709
+ }
2710
+ ]
2711
+ }
2712
+
2713
+ CRYSTALLIZATION RULES:
2714
+ - Only suggest 0-2 candidates per session \u2014 if nothing qualifies, return an empty array
2715
+ - Only suggest REUSABLE procedures (not one-off tasks specific to today's work)
2716
+ - The user must have demonstrated the procedure in this session
2717
+ - Confidence < 0.6 \u2192 don't suggest at all
2718
+ - Skip vague things like "use library X" \u2014 that's not procedural knowledge
2719
+ - Prefer narrow specific procedures over broad generalizations
2720
+ - Trigger keywords should be highly specific (avoid generic words like "code", "fix", "the")`;
2694
2721
  async function generatePostmortemReport(sessionId, messages, session, client, obsDir) {
2695
2722
  try {
2696
2723
  const events = await readObservationEvents(sessionId, obsDir ?? defaultObservationsDir2());
@@ -2770,7 +2797,8 @@ ${obsSnapshot.join("\n")}`;
2770
2797
  topicProgression: [...new Set(topicProgression)],
2771
2798
  sentimentArc: parsed.sentimentArc ?? "",
2772
2799
  patterns: parsed.patterns ?? [],
2773
- recommendations: parsed.recommendations ?? []
2800
+ recommendations: parsed.recommendations ?? [],
2801
+ crystallizationCandidates: Array.isArray(parsed.crystallizationCandidates) ? parsed.crystallizationCandidates : void 0
2774
2802
  };
2775
2803
  } catch (err) {
2776
2804
  log.debug("postmortem", "Failed to generate post-mortem", err);
@@ -2841,6 +2869,14 @@ function formatPostmortemMarkdown(report) {
2841
2869
  report.recommendations.forEach((r) => lines.push(`- ${r}`));
2842
2870
  lines.push("");
2843
2871
  }
2872
+ if (report.crystallizationCandidates && report.crystallizationCandidates.length > 0) {
2873
+ lines.push("## Crystallization Candidates");
2874
+ report.crystallizationCandidates.forEach((c) => {
2875
+ lines.push(`- **${c.name}** (confidence ${c.confidence})`);
2876
+ lines.push(` ${c.description}`);
2877
+ });
2878
+ lines.push("");
2879
+ }
2844
2880
  return lines.join("\n");
2845
2881
  }
2846
2882
  async function savePostmortem(report, dir) {
@@ -2851,6 +2887,12 @@ async function savePostmortem(report, dir) {
2851
2887
  const filePath = path11.join(pmDir, fileName);
2852
2888
  const markdown = formatPostmortemMarkdown(report);
2853
2889
  await fs11.writeFile(filePath, markdown, "utf-8");
2890
+ const jsonPath = filePath.replace(/\.md$/, ".json");
2891
+ try {
2892
+ await fs11.writeFile(jsonPath, JSON.stringify(report, null, 2), "utf-8");
2893
+ } catch (err) {
2894
+ log.debug("postmortem", "JSON sidecar write failed", err);
2895
+ }
2854
2896
  return filePath;
2855
2897
  }
2856
2898
  async function listPostmortems(dir) {
@@ -2911,6 +2953,511 @@ ${contents.join("\n\n---\n\n")}`
2911
2953
  }
2912
2954
  }
2913
2955
 
2956
+ // src/crystallization.ts
2957
+ import fs12 from "fs/promises";
2958
+ import path12 from "path";
2959
+ var STOPWORDS = /* @__PURE__ */ new Set([
2960
+ "the",
2961
+ "and",
2962
+ "is",
2963
+ "to",
2964
+ "of",
2965
+ "a",
2966
+ "in",
2967
+ "for",
2968
+ "on",
2969
+ "with",
2970
+ "this",
2971
+ "that",
2972
+ "it",
2973
+ "as",
2974
+ "be",
2975
+ "by",
2976
+ "or",
2977
+ "at",
2978
+ "an",
2979
+ "from",
2980
+ "code",
2981
+ "fix",
2982
+ "do",
2983
+ "use",
2984
+ "make",
2985
+ "get",
2986
+ "set",
2987
+ "run",
2988
+ "we",
2989
+ "i"
2990
+ ]);
2991
+ var MAX_REJECTIONS = 100;
2992
+ var MARKER_RE = /<!--\s*aman-auto\s+([^>]+?)\s*-->/;
2993
+ function sanitizeName(input) {
2994
+ const cleaned = input.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2995
+ if (cleaned.length === 0) {
2996
+ throw new Error(`Cannot sanitize name: "${input}" produced empty result`);
2997
+ }
2998
+ return cleaned;
2999
+ }
3000
+ function validateCandidate(raw) {
3001
+ if (!raw || typeof raw !== "object") return null;
3002
+ const c = raw;
3003
+ if (typeof c.name !== "string" || c.name.trim() === "") return null;
3004
+ if (typeof c.description !== "string") return null;
3005
+ if (typeof c.approach !== "string") return null;
3006
+ if (!Array.isArray(c.triggers) || c.triggers.length === 0) return null;
3007
+ if (c.triggers.length > 10) return null;
3008
+ if (!Array.isArray(c.steps)) return null;
3009
+ if (typeof c.confidence !== "number") return null;
3010
+ if (!Number.isFinite(c.confidence)) return null;
3011
+ if (c.confidence < 0.6) return null;
3012
+ const triggers = Array.from(
3013
+ new Set(
3014
+ c.triggers.filter((t) => typeof t === "string").map((t) => t.toLowerCase().trim()).filter((t) => t.length > 0 && !STOPWORDS.has(t))
3015
+ )
3016
+ );
3017
+ if (triggers.length === 0) return null;
3018
+ let name;
3019
+ try {
3020
+ name = sanitizeName(c.name);
3021
+ } catch {
3022
+ return null;
3023
+ }
3024
+ return {
3025
+ name,
3026
+ description: c.description,
3027
+ triggers,
3028
+ approach: c.approach,
3029
+ steps: c.steps.filter((s) => typeof s === "string"),
3030
+ gotchas: Array.isArray(c.gotchas) ? c.gotchas.filter((g) => typeof g === "string") : [],
3031
+ confidence: Math.min(1, Math.max(0, c.confidence))
3032
+ };
3033
+ }
3034
+ function toTitleCase(kebab) {
3035
+ return kebab.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3036
+ }
3037
+ function formatSkillMarkdown(candidate, postmortemFilename) {
3038
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3039
+ const heading = toTitleCase(candidate.name);
3040
+ const triggerStr = candidate.triggers.join(",");
3041
+ const lines = [
3042
+ `# ${heading}`,
3043
+ `<!-- aman-auto source=postmortem date=${date} confidence=${candidate.confidence} triggers="${triggerStr}" -->`,
3044
+ "",
3045
+ "## When to use",
3046
+ candidate.approach,
3047
+ "",
3048
+ "## Steps",
3049
+ ...candidate.steps.map((s, i) => `${i + 1}. ${s}`),
3050
+ ""
3051
+ ];
3052
+ if (candidate.gotchas.length > 0) {
3053
+ lines.push("## Gotchas");
3054
+ lines.push(...candidate.gotchas.map((g) => `- ${g}`));
3055
+ lines.push("");
3056
+ }
3057
+ lines.push(`<!-- generated from ${postmortemFilename} -->`);
3058
+ lines.push("");
3059
+ return lines.join("\n");
3060
+ }
3061
+ function parseMarkerComment(line) {
3062
+ const match = line.match(MARKER_RE);
3063
+ if (!match) return null;
3064
+ const attrs = {};
3065
+ const attrRe = /(\w+)=(?:"([^"]*)"|(\S+))/g;
3066
+ let m;
3067
+ while ((m = attrRe.exec(match[1])) !== null) {
3068
+ attrs[m[1]] = m[2] ?? m[3] ?? "";
3069
+ }
3070
+ if (!attrs.triggers) return null;
3071
+ const triggers = attrs.triggers.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
3072
+ if (triggers.length === 0) return null;
3073
+ return {
3074
+ source: attrs.source ?? "unknown",
3075
+ date: attrs.date ?? "",
3076
+ confidence: attrs.confidence ? Number(attrs.confidence) : 0,
3077
+ triggers
3078
+ };
3079
+ }
3080
+ function extractSkillsWithMarkers(skillsMdContent) {
3081
+ const result = /* @__PURE__ */ new Map();
3082
+ const lines = skillsMdContent.split("\n");
3083
+ for (let i = 0; i < lines.length; i++) {
3084
+ const line = lines[i];
3085
+ if (line.startsWith("# ") && i + 1 < lines.length) {
3086
+ const headingText = line.slice(2).trim();
3087
+ const nextLine = lines[i + 1];
3088
+ const marker = parseMarkerComment(nextLine);
3089
+ if (marker) {
3090
+ try {
3091
+ const skillName = sanitizeName(headingText);
3092
+ result.set(skillName, marker);
3093
+ } catch {
3094
+ log.debug("crystallization", `cannot sanitize heading: ${headingText}`);
3095
+ }
3096
+ }
3097
+ }
3098
+ }
3099
+ return result;
3100
+ }
3101
+ function findCollision(name, triggers, existing) {
3102
+ if (existing.has(name)) {
3103
+ return { collides: true, collidesWith: name, reason: "exact name match" };
3104
+ }
3105
+ const triggerSet = new Set(triggers);
3106
+ for (const [otherName, otherData] of existing) {
3107
+ const otherTriggers = new Set(otherData.triggers);
3108
+ const intersection = [...triggerSet].filter((t) => otherTriggers.has(t)).length;
3109
+ const union = (/* @__PURE__ */ new Set([...triggerSet, ...otherTriggers])).size;
3110
+ const overlap = union > 0 ? intersection / union : 0;
3111
+ if (overlap >= 0.8) {
3112
+ return {
3113
+ collides: true,
3114
+ collidesWith: otherName,
3115
+ reason: `${Math.round(overlap * 100)}% trigger overlap`
3116
+ };
3117
+ }
3118
+ }
3119
+ return { collides: false };
3120
+ }
3121
+ async function writeSkillToFile(candidate, skillsMdPath, postmortemFilename) {
3122
+ try {
3123
+ await fs12.mkdir(path12.dirname(skillsMdPath), { recursive: true });
3124
+ let existingContent = "";
3125
+ try {
3126
+ existingContent = await fs12.readFile(skillsMdPath, "utf-8");
3127
+ } catch {
3128
+ existingContent = "# Skills\n\n";
3129
+ }
3130
+ if (existingContent.trim() === "") {
3131
+ existingContent = "# Skills\n\n";
3132
+ }
3133
+ const existingSkills = extractSkillsWithMarkers(existingContent);
3134
+ const collision = findCollision(candidate.name, candidate.triggers, existingSkills);
3135
+ if (collision.collides) {
3136
+ log.debug("crystallization", `collision detected: ${collision.reason}`);
3137
+ return {
3138
+ written: false,
3139
+ filePath: skillsMdPath,
3140
+ skillName: candidate.name,
3141
+ reason: `collision with "${collision.collidesWith}" (${collision.reason})`
3142
+ };
3143
+ }
3144
+ const skillMarkdown = formatSkillMarkdown(candidate, postmortemFilename);
3145
+ const separator = existingContent.endsWith("\n\n") ? "" : existingContent.endsWith("\n") ? "\n" : "\n\n";
3146
+ await fs12.writeFile(
3147
+ skillsMdPath,
3148
+ existingContent + separator + skillMarkdown,
3149
+ "utf-8"
3150
+ );
3151
+ return {
3152
+ written: true,
3153
+ filePath: skillsMdPath,
3154
+ skillName: candidate.name
3155
+ };
3156
+ } catch (err) {
3157
+ log.warn("crystallization", "writeSkillToFile failed", err);
3158
+ return {
3159
+ written: false,
3160
+ filePath: skillsMdPath,
3161
+ skillName: candidate.name,
3162
+ reason: err instanceof Error ? err.message : String(err)
3163
+ };
3164
+ }
3165
+ }
3166
+ async function appendCrystallizationLog(entry, logPath) {
3167
+ try {
3168
+ await fs12.mkdir(path12.dirname(logPath), { recursive: true });
3169
+ let existing = [];
3170
+ try {
3171
+ const content = await fs12.readFile(logPath, "utf-8");
3172
+ existing = JSON.parse(content);
3173
+ if (!Array.isArray(existing)) existing = [];
3174
+ } catch {
3175
+ existing = [];
3176
+ }
3177
+ existing.push(entry);
3178
+ await fs12.writeFile(logPath, JSON.stringify(existing, null, 2), "utf-8");
3179
+ } catch (err) {
3180
+ log.debug("crystallization", "appendCrystallizationLog failed", err);
3181
+ }
3182
+ }
3183
+ async function appendRejection(candidate, postmortemFilename, rejectionsPath) {
3184
+ try {
3185
+ await fs12.mkdir(path12.dirname(rejectionsPath), { recursive: true });
3186
+ let existing = [];
3187
+ try {
3188
+ const content = await fs12.readFile(rejectionsPath, "utf-8");
3189
+ existing = JSON.parse(content);
3190
+ if (!Array.isArray(existing)) existing = [];
3191
+ } catch {
3192
+ existing = [];
3193
+ }
3194
+ existing.push({
3195
+ name: candidate.name,
3196
+ rejectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3197
+ fromPostmortem: postmortemFilename,
3198
+ triggers: candidate.triggers
3199
+ });
3200
+ while (existing.length > MAX_REJECTIONS) {
3201
+ existing.shift();
3202
+ }
3203
+ await fs12.writeFile(rejectionsPath, JSON.stringify(existing, null, 2), "utf-8");
3204
+ } catch (err) {
3205
+ log.debug("crystallization", "appendRejection failed", err);
3206
+ }
3207
+ }
3208
+
3209
+ // src/user-model.ts
3210
+ import fs13 from "fs/promises";
3211
+ import path13 from "path";
3212
+ import os12 from "os";
3213
+ var MAX_SESSIONS = 30;
3214
+ var TRUST_ALPHA = 0.3;
3215
+ var MIN_SESSIONS_FOR_FEED_FORWARD = 5;
3216
+ var MIN_SESSIONS_FOR_CORRELATIONS = 10;
3217
+ function defaultModelPath() {
3218
+ return path13.join(os12.homedir(), ".acore", "user-model.json");
3219
+ }
3220
+ function createEmptyModel() {
3221
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3222
+ return {
3223
+ version: 1,
3224
+ sessions: [],
3225
+ profile: emptyProfile(),
3226
+ createdAt: now,
3227
+ updatedAt: now
3228
+ };
3229
+ }
3230
+ function emptyProfile() {
3231
+ return {
3232
+ trustScore: 0.5,
3233
+ trustTrajectory: "stable",
3234
+ totalSessions: 0,
3235
+ preferredTimePeriod: "afternoon",
3236
+ energyDistribution: {},
3237
+ avgSessionMinutes: 0,
3238
+ baselineFrustration: 0,
3239
+ baselineExcitement: 0,
3240
+ sentimentTrend: "stable",
3241
+ frustrationCorrelations: { toolErrors: 0, longSessions: 0, lateNight: 0 },
3242
+ avgTurnsPerSession: 0,
3243
+ engagementTrend: "stable",
3244
+ nudgeStats: {}
3245
+ };
3246
+ }
3247
+ async function loadUserModel(filePath) {
3248
+ const fp = filePath ?? defaultModelPath();
3249
+ try {
3250
+ const raw = await fs13.readFile(fp, "utf-8");
3251
+ const parsed = JSON.parse(raw);
3252
+ if (parsed?.version !== 1) return null;
3253
+ return parsed;
3254
+ } catch {
3255
+ return null;
3256
+ }
3257
+ }
3258
+ async function saveUserModel(model, filePath) {
3259
+ const fp = filePath ?? defaultModelPath();
3260
+ const dir = path13.dirname(fp);
3261
+ await fs13.mkdir(dir, { recursive: true });
3262
+ const tmp = fp + `.tmp-${Date.now()}`;
3263
+ await fs13.writeFile(tmp, JSON.stringify(model, null, 2), "utf-8");
3264
+ await fs13.rename(tmp, fp);
3265
+ }
3266
+ function aggregateSession(model, snapshot) {
3267
+ const sessions = [...model.sessions, snapshot];
3268
+ while (sessions.length > MAX_SESSIONS) {
3269
+ sessions.shift();
3270
+ }
3271
+ const totalSessions = model.profile.totalSessions + 1;
3272
+ const profile = computeProfile(sessions, totalSessions);
3273
+ return {
3274
+ ...model,
3275
+ sessions,
3276
+ profile,
3277
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3278
+ };
3279
+ }
3280
+ function computeProfile(sessions, totalSessions) {
3281
+ if (sessions.length === 0) return { ...emptyProfile(), totalSessions };
3282
+ const n = sessions.length;
3283
+ let trustScore = 0.5;
3284
+ for (const s of sessions) {
3285
+ trustScore = TRUST_ALPHA * ratingSignal(s) + (1 - TRUST_ALPHA) * trustScore;
3286
+ }
3287
+ const trustTrajectory = computeTrustTrajectory(sessions);
3288
+ const baselineFrustration = avg(sessions.map((s) => s.avgFrustration));
3289
+ const baselineExcitement = avg(sessions.map((s) => s.avgExcitement));
3290
+ const sentimentTrend = computeSentimentTrend(sessions);
3291
+ const energyDistribution = {};
3292
+ for (const s of sessions) {
3293
+ energyDistribution[s.timePeriod] = (energyDistribution[s.timePeriod] || 0) + 1;
3294
+ }
3295
+ const preferredTimePeriod = Object.entries(energyDistribution).sort(
3296
+ (a, b) => b[1] - a[1]
3297
+ )[0]?.[0] ?? "afternoon";
3298
+ const avgSessionMinutes = avg(sessions.map((s) => s.durationMinutes));
3299
+ const avgTurnsPerSession = avg(sessions.map((s) => s.turnCount));
3300
+ const engagementTrend = computeLinearTrend(sessions.map((s) => s.turnCount));
3301
+ const frustrationCorrelations = n >= MIN_SESSIONS_FOR_CORRELATIONS ? {
3302
+ toolErrors: pearsonR(
3303
+ sessions.map((s) => s.avgFrustration),
3304
+ sessions.map((s) => s.toolErrors)
3305
+ ),
3306
+ longSessions: pearsonR(
3307
+ sessions.map((s) => s.avgFrustration),
3308
+ sessions.map((s) => s.durationMinutes)
3309
+ ),
3310
+ lateNight: pearsonR(
3311
+ sessions.map((s) => s.avgFrustration),
3312
+ sessions.map((s) => s.timePeriod === "late-night" || s.timePeriod === "night" ? 1 : 0)
3313
+ )
3314
+ } : { toolErrors: 0, longSessions: 0, lateNight: 0 };
3315
+ const nudgeStats = {};
3316
+ for (const s of sessions) {
3317
+ const ratingVal = ratingToNumber(s.rating);
3318
+ for (const nudge of s.wellbeingNudges) {
3319
+ if (!nudgeStats[nudge]) nudgeStats[nudge] = { fired: 0, sessionRatingAfter: 0 };
3320
+ nudgeStats[nudge].fired++;
3321
+ nudgeStats[nudge].sessionRatingAfter += ratingVal;
3322
+ }
3323
+ }
3324
+ for (const key of Object.keys(nudgeStats)) {
3325
+ if (nudgeStats[key].fired > 0) {
3326
+ nudgeStats[key].sessionRatingAfter /= nudgeStats[key].fired;
3327
+ }
3328
+ }
3329
+ return {
3330
+ trustScore,
3331
+ trustTrajectory,
3332
+ totalSessions,
3333
+ preferredTimePeriod,
3334
+ energyDistribution,
3335
+ avgSessionMinutes,
3336
+ baselineFrustration,
3337
+ baselineExcitement,
3338
+ sentimentTrend,
3339
+ frustrationCorrelations,
3340
+ avgTurnsPerSession,
3341
+ engagementTrend,
3342
+ nudgeStats
3343
+ };
3344
+ }
3345
+ function feedForward(model) {
3346
+ if (model.profile.totalSessions < MIN_SESSIONS_FOR_FEED_FORWARD) return null;
3347
+ const p4 = model.profile;
3348
+ const overrides = {
3349
+ compactGreeting: false,
3350
+ frustrationNudgeThreshold: 0.6,
3351
+ defaultToPersonalMode: false
3352
+ };
3353
+ const nightSessions = (p4.energyDistribution["late-night"] || 0) + (p4.energyDistribution["night"] || 0);
3354
+ const totalInWindow = model.sessions.length;
3355
+ if (totalInWindow > 0 && nightSessions / totalInWindow >= 0.7 && p4.baselineFrustration < 0.3) {
3356
+ overrides.energyOverride = "steady";
3357
+ }
3358
+ if (p4.trustScore > 0.8) {
3359
+ overrides.compactGreeting = true;
3360
+ }
3361
+ if (p4.frustrationCorrelations.toolErrors > 0.4) {
3362
+ overrides.frustrationNudgeThreshold = 0.4;
3363
+ }
3364
+ if (p4.sentimentTrend === "worsening") {
3365
+ overrides.defaultToPersonalMode = true;
3366
+ }
3367
+ return overrides;
3368
+ }
3369
+ function clamp(val, min, max) {
3370
+ return Math.max(min, Math.min(max, val));
3371
+ }
3372
+ function avg(values) {
3373
+ if (values.length === 0) return 0;
3374
+ return values.reduce((sum, v) => sum + v, 0) / values.length;
3375
+ }
3376
+ function ratingSignal(session) {
3377
+ if (session.rating === "great") return 1;
3378
+ if (session.rating === "good") return 0.75;
3379
+ if (session.rating === "okay") return 0.5;
3380
+ if (session.rating === "frustrating") return 0.25;
3381
+ let implicit = 1;
3382
+ implicit -= session.avgFrustration * 0.4;
3383
+ implicit -= session.toolErrors > 3 ? 0.2 : 0;
3384
+ implicit -= session.blockers > 2 ? 0.2 : 0;
3385
+ implicit += session.milestones > 0 ? 0.1 : 0;
3386
+ return clamp(implicit, 0, 1);
3387
+ }
3388
+ function ratingToNumber(rating) {
3389
+ if (rating === "great") return 1;
3390
+ if (rating === "good") return 0.75;
3391
+ if (rating === "okay") return 0.5;
3392
+ if (rating === "frustrating") return 0.25;
3393
+ return 0.5;
3394
+ }
3395
+ function pearsonR(x, y) {
3396
+ const n = x.length;
3397
+ if (n < 3) return 0;
3398
+ const mx = avg(x);
3399
+ const my = avg(y);
3400
+ let num = 0;
3401
+ let dx2 = 0;
3402
+ let dy2 = 0;
3403
+ for (let i = 0; i < n; i++) {
3404
+ const dx = x[i] - mx;
3405
+ const dy = y[i] - my;
3406
+ num += dx * dy;
3407
+ dx2 += dx * dx;
3408
+ dy2 += dy * dy;
3409
+ }
3410
+ const denom = Math.sqrt(dx2 * dy2);
3411
+ if (denom === 0) return 0;
3412
+ return num / denom;
3413
+ }
3414
+ function computeTrustTrajectory(sessions) {
3415
+ if (sessions.length < 10) return "stable";
3416
+ const recent5 = sessions.slice(-5).map(ratingSignal);
3417
+ const prev5 = sessions.slice(-10, -5).map(ratingSignal);
3418
+ const recentAvg = avg(recent5);
3419
+ const prevAvg = avg(prev5);
3420
+ const delta = recentAvg - prevAvg;
3421
+ if (delta > 0.1) return "ascending";
3422
+ if (delta < -0.1) return "declining";
3423
+ return "stable";
3424
+ }
3425
+ function computeSentimentTrend(sessions) {
3426
+ if (sessions.length < 5) return "stable";
3427
+ const frustrations = sessions.slice(-10).map((s) => s.avgFrustration);
3428
+ const slope = linearSlope(frustrations);
3429
+ if (slope > 0.02) return "worsening";
3430
+ if (slope < -0.02) return "improving";
3431
+ return "stable";
3432
+ }
3433
+ function computeLinearTrend(values) {
3434
+ if (values.length < 5) return "stable";
3435
+ const recent = values.slice(-10);
3436
+ const slope = linearSlope(recent);
3437
+ const mean = avg(recent);
3438
+ const relativeSlope = mean > 0 ? slope / mean : slope;
3439
+ if (relativeSlope > 0.03) return "increasing";
3440
+ if (relativeSlope < -0.03) return "decreasing";
3441
+ return "stable";
3442
+ }
3443
+ function linearSlope(values) {
3444
+ const n = values.length;
3445
+ if (n < 2) return 0;
3446
+ let sumX = 0;
3447
+ let sumY = 0;
3448
+ let sumXY = 0;
3449
+ let sumX2 = 0;
3450
+ for (let i = 0; i < n; i++) {
3451
+ sumX += i;
3452
+ sumY += values[i];
3453
+ sumXY += i * values[i];
3454
+ sumX2 += i * i;
3455
+ }
3456
+ const denom = n * sumX2 - sumX * sumX;
3457
+ if (denom === 0) return 0;
3458
+ return (n * sumXY - sumX * sumY) / denom;
3459
+ }
3460
+
2914
3461
  // src/hooks.ts
2915
3462
  function getTimeContext() {
2916
3463
  const now = /* @__PURE__ */ new Date();
@@ -3058,6 +3605,29 @@ ${contextInjection}`;
3058
3605
  sessionMinutes: 0,
3059
3606
  turnCount: 0
3060
3607
  });
3608
+ try {
3609
+ const model = await loadUserModel();
3610
+ if (model) {
3611
+ const overrides = feedForward(model);
3612
+ if (overrides) {
3613
+ log.debug("hooks", `Feed-forward active (trust=${model.profile.trustScore.toFixed(2)}, sessions=${model.profile.totalSessions})`);
3614
+ if (overrides.energyOverride && (period === "late-night" || period === "night")) {
3615
+ state.energy = overrides.energyOverride;
3616
+ }
3617
+ if (overrides.defaultToPersonalMode && state.activeMode === "Default") {
3618
+ state.activeMode = "Personal";
3619
+ }
3620
+ if (overrides.compactGreeting) {
3621
+ greeting += "\n<user-model-context>High trust user (score: " + model.profile.trustScore.toFixed(2) + ", " + model.profile.totalSessions + " sessions). Keep greeting compact \u2014 they know you well.</user-model-context>";
3622
+ }
3623
+ if (model.profile.sentimentTrend === "worsening") {
3624
+ greeting += "\n<user-model-context>Sentiment trend is worsening across recent sessions. Be more attentive and patient.</user-model-context>";
3625
+ }
3626
+ }
3627
+ }
3628
+ } catch (err) {
3629
+ log.debug("hooks", "user model feed-forward failed", err);
3630
+ }
3061
3631
  syncPersonalityToCore(state, ctx.mcpManager).catch(() => {
3062
3632
  });
3063
3633
  const nudge = formatWellbeingNudge(state);
@@ -3179,10 +3749,10 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3179
3749
  }
3180
3750
  console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
3181
3751
  }
3182
- const projectContextPath = path12.join(process.cwd(), ".acore", "context.md");
3183
- if (fs12.existsSync(projectContextPath) && messages.length > 2) {
3752
+ const projectContextPath = path14.join(process.cwd(), ".acore", "context.md");
3753
+ if (fs14.existsSync(projectContextPath) && messages.length > 2) {
3184
3754
  try {
3185
- let contextContent = fs12.readFileSync(projectContextPath, "utf-8");
3755
+ let contextContent = fs14.readFileSync(projectContextPath, "utf-8");
3186
3756
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3187
3757
  let lastUserMsg = "";
3188
3758
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -3200,28 +3770,28 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3200
3770
  - Recent decisions: [see memory]
3201
3771
  - Temp notes: [cleared]`;
3202
3772
  contextContent = contextContent.replace(sessionPattern, newSession);
3203
- fs12.writeFileSync(projectContextPath, contextContent, "utf-8");
3773
+ fs14.writeFileSync(projectContextPath, contextContent, "utf-8");
3204
3774
  log.debug("hooks", `Updated project context: ${projectContextPath}`);
3205
3775
  }
3206
3776
  } catch (err) {
3207
3777
  log.debug("hooks", "project context update failed", err);
3208
3778
  }
3209
3779
  }
3780
+ const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
3781
+ const hour = (/* @__PURE__ */ new Date()).getHours();
3782
+ let period;
3783
+ if (hour < 6) period = "late-night";
3784
+ else if (hour < 12) period = "morning";
3785
+ else if (hour < 17) period = "afternoon";
3786
+ else if (hour < 21) period = "evening";
3787
+ else period = "night";
3788
+ const turnCount = messages.filter((m) => m.role === "user").length;
3789
+ const finalState = computePersonality({
3790
+ timePeriod: period,
3791
+ sessionMinutes,
3792
+ turnCount
3793
+ });
3210
3794
  if (ctx.config.personalityAdapt !== false) {
3211
- const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
3212
- const hour = (/* @__PURE__ */ new Date()).getHours();
3213
- let period;
3214
- if (hour < 6) period = "late-night";
3215
- else if (hour < 12) period = "morning";
3216
- else if (hour < 17) period = "afternoon";
3217
- else if (hour < 21) period = "evening";
3218
- else period = "night";
3219
- const turnCount = messages.filter((m) => m.role === "user").length;
3220
- const finalState = computePersonality({
3221
- timePeriod: period,
3222
- sessionMinutes,
3223
- turnCount
3224
- });
3225
3795
  try {
3226
3796
  isHookCall = true;
3227
3797
  await syncPersonalityToCore(finalState, ctx.mcpManager);
@@ -3229,6 +3799,7 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3229
3799
  isHookCall = false;
3230
3800
  }
3231
3801
  }
3802
+ let sessionRating;
3232
3803
  if (ctx.config.evalPrompt) {
3233
3804
  const rating = await p2.select({
3234
3805
  message: "Quick rating for this session?",
@@ -3241,10 +3812,11 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3241
3812
  initialValue: "skip"
3242
3813
  });
3243
3814
  if (!p2.isCancel(rating) && rating !== "skip") {
3815
+ sessionRating = rating;
3244
3816
  try {
3245
3817
  isHookCall = true;
3246
3818
  await ctx.mcpManager.callTool("eval_log", {
3247
- rating,
3819
+ rating: sessionRating,
3248
3820
  highlights: "Quick session rating",
3249
3821
  improvements: ""
3250
3822
  });
@@ -3253,6 +3825,51 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3253
3825
  }
3254
3826
  }
3255
3827
  }
3828
+ if (turnCount >= 2 && sessionMinutes >= 1) {
3829
+ try {
3830
+ const snapshot = {
3831
+ sessionId,
3832
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
3833
+ durationMinutes: sessionMinutes,
3834
+ turnCount,
3835
+ dominantSentiment: finalState.sentiment.dominant,
3836
+ avgFrustration: finalState.sentiment.frustration,
3837
+ avgExcitement: finalState.sentiment.excitement,
3838
+ avgConfusion: finalState.sentiment.confusion,
3839
+ avgFatigue: finalState.sentiment.fatigue,
3840
+ toolCalls: observationSession?.stats.toolCalls ?? 0,
3841
+ toolErrors: observationSession?.stats.toolErrors ?? 0,
3842
+ blockers: observationSession?.stats.blockers ?? 0,
3843
+ milestones: observationSession?.stats.milestones ?? 0,
3844
+ topicShifts: observationSession?.stats.topicShifts ?? 0,
3845
+ peakEnergy: finalState.energy,
3846
+ primaryMode: finalState.activeMode,
3847
+ timePeriod: period,
3848
+ rating: sessionRating,
3849
+ hadPostmortem: false,
3850
+ // updated below if postmortem is generated
3851
+ wellbeingNudges: finalState.wellbeingNudge ? [finalState.wellbeingNudge] : []
3852
+ };
3853
+ const model = await loadUserModel() ?? createEmptyModel();
3854
+ const updated = aggregateSession(model, snapshot);
3855
+ await saveUserModel(updated);
3856
+ log.debug("hooks", `User model updated (session ${updated.profile.totalSessions})`);
3857
+ if (ctx.config.personalityAdapt !== false) {
3858
+ try {
3859
+ isHookCall = true;
3860
+ await syncPersonalityToCore(finalState, ctx.mcpManager, {
3861
+ trustScore: updated.profile.trustScore,
3862
+ totalSessions: updated.profile.totalSessions,
3863
+ sentimentTrend: updated.profile.sentimentTrend
3864
+ });
3865
+ } finally {
3866
+ isHookCall = false;
3867
+ }
3868
+ }
3869
+ } catch (err) {
3870
+ log.debug("hooks", "user model aggregation failed", err);
3871
+ }
3872
+ }
3256
3873
  if (ctx.config.autoPostmortem !== false && observationSession && shouldAutoPostmortem(observationSession, messages)) {
3257
3874
  try {
3258
3875
  const client = ctx.llmClient;
@@ -3278,6 +3895,75 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
3278
3895
  } catch {
3279
3896
  }
3280
3897
  }
3898
+ if (report.crystallizationCandidates && report.crystallizationCandidates.length > 0) {
3899
+ const skillsMdPath = path14.join(os13.homedir(), ".askill", "skills.md");
3900
+ const logPath = path14.join(
3901
+ os13.homedir(),
3902
+ ".aman-agent",
3903
+ "crystallization-log.json"
3904
+ );
3905
+ const rejectionsPath = path14.join(
3906
+ os13.homedir(),
3907
+ ".aman-agent",
3908
+ "crystallization-rejections.json"
3909
+ );
3910
+ const postmortemFilename = `${report.date}-${report.sessionId.slice(0, 4)}.md`;
3911
+ console.log(
3912
+ pc2.dim(`
3913
+ Crystallization candidates: ${report.crystallizationCandidates.length}`)
3914
+ );
3915
+ let skipAll = false;
3916
+ for (const rawCandidate of report.crystallizationCandidates) {
3917
+ if (skipAll) break;
3918
+ const candidate = validateCandidate(rawCandidate);
3919
+ if (!candidate) {
3920
+ log.debug("hooks", "candidate failed validation");
3921
+ continue;
3922
+ }
3923
+ const choice = await p2.select({
3924
+ message: `Crystallize "${candidate.name}" as a reusable skill?`,
3925
+ options: [
3926
+ { value: "accept", label: "Yes \u2014 write to ~/.askill/skills.md" },
3927
+ { value: "reject", label: "No \u2014 skip this one" },
3928
+ { value: "skip-all", label: "Skip all crystallization for this session" }
3929
+ ],
3930
+ initialValue: "reject"
3931
+ });
3932
+ if (p2.isCancel(choice) || choice === "skip-all") {
3933
+ skipAll = true;
3934
+ break;
3935
+ }
3936
+ if (choice === "accept") {
3937
+ const result = await writeSkillToFile(
3938
+ candidate,
3939
+ skillsMdPath,
3940
+ postmortemFilename
3941
+ );
3942
+ if (result.written) {
3943
+ console.log(
3944
+ pc2.green(` \u2713 Crystallized: ${candidate.name} \u2192 ${result.filePath}`)
3945
+ );
3946
+ console.log(pc2.dim(` Triggers: ${candidate.triggers.join(", ")}`));
3947
+ console.log(pc2.dim(` Will auto-activate next session.`));
3948
+ await appendCrystallizationLog(
3949
+ {
3950
+ name: candidate.name,
3951
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3952
+ fromPostmortem: postmortemFilename,
3953
+ confidence: candidate.confidence,
3954
+ triggers: candidate.triggers
3955
+ },
3956
+ logPath
3957
+ );
3958
+ } else {
3959
+ console.log(pc2.yellow(` \u2298 Could not crystallize: ${result.reason}`));
3960
+ }
3961
+ } else {
3962
+ console.log(pc2.dim(` Skipped: ${candidate.name}`));
3963
+ await appendRejection(candidate, postmortemFilename, rejectionsPath);
3964
+ }
3965
+ }
3966
+ }
3281
3967
  }
3282
3968
  }
3283
3969
  } catch (err) {
@@ -3437,43 +4123,43 @@ async function delegatePipeline(steps, initialInput, client, mcpManager, options
3437
4123
  }
3438
4124
 
3439
4125
  // src/teams.ts
3440
- import fs13 from "fs";
3441
- import path13 from "path";
3442
- import os12 from "os";
4126
+ import fs15 from "fs";
4127
+ import path15 from "path";
4128
+ import os14 from "os";
3443
4129
  import pc4 from "picocolors";
3444
4130
  function getTeamsDir() {
3445
- return path13.join(os12.homedir(), ".acore", "teams");
4131
+ return path15.join(os14.homedir(), ".acore", "teams");
3446
4132
  }
3447
4133
  function ensureTeamsDir() {
3448
4134
  const dir = getTeamsDir();
3449
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
4135
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
3450
4136
  return dir;
3451
4137
  }
3452
4138
  function teamPath(name) {
3453
4139
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
3454
- return path13.join(ensureTeamsDir(), `${slug}.json`);
4140
+ return path15.join(ensureTeamsDir(), `${slug}.json`);
3455
4141
  }
3456
4142
  function createTeam(team) {
3457
4143
  const fp = teamPath(team.name);
3458
- fs13.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
4144
+ fs15.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
3459
4145
  }
3460
4146
  function loadTeam(name) {
3461
4147
  const fp = teamPath(name);
3462
- if (!fs13.existsSync(fp)) return null;
4148
+ if (!fs15.existsSync(fp)) return null;
3463
4149
  try {
3464
- return JSON.parse(fs13.readFileSync(fp, "utf-8"));
4150
+ return JSON.parse(fs15.readFileSync(fp, "utf-8"));
3465
4151
  } catch {
3466
4152
  return null;
3467
4153
  }
3468
4154
  }
3469
4155
  function listTeams() {
3470
4156
  const dir = getTeamsDir();
3471
- if (!fs13.existsSync(dir)) return [];
4157
+ if (!fs15.existsSync(dir)) return [];
3472
4158
  const teams = [];
3473
- for (const file of fs13.readdirSync(dir)) {
4159
+ for (const file of fs15.readdirSync(dir)) {
3474
4160
  if (!file.endsWith(".json")) continue;
3475
4161
  try {
3476
- const content = fs13.readFileSync(path13.join(dir, file), "utf-8");
4162
+ const content = fs15.readFileSync(path15.join(dir, file), "utf-8");
3477
4163
  teams.push(JSON.parse(content));
3478
4164
  } catch {
3479
4165
  }
@@ -3482,8 +4168,8 @@ function listTeams() {
3482
4168
  }
3483
4169
  function deleteTeam(name) {
3484
4170
  const fp = teamPath(name);
3485
- if (!fs13.existsSync(fp)) return false;
3486
- fs13.unlinkSync(fp);
4171
+ if (!fs15.existsSync(fp)) return false;
4172
+ fs15.unlinkSync(fp);
3487
4173
  return true;
3488
4174
  }
3489
4175
  async function runTeam(team, task, client, mcpManager, tools) {
@@ -3709,23 +4395,23 @@ var BUILT_IN_TEAMS = [
3709
4395
  ];
3710
4396
 
3711
4397
  // src/plans.ts
3712
- import fs14 from "fs";
3713
- import path14 from "path";
3714
- import os13 from "os";
4398
+ import fs16 from "fs";
4399
+ import path16 from "path";
4400
+ import os15 from "os";
3715
4401
  function getPlansDir() {
3716
- const localDir = path14.join(process.cwd(), ".acore", "plans");
3717
- const localAcore = path14.join(process.cwd(), ".acore");
3718
- if (fs14.existsSync(localAcore)) return localDir;
3719
- return path14.join(os13.homedir(), ".acore", "plans");
4402
+ const localDir = path16.join(process.cwd(), ".acore", "plans");
4403
+ const localAcore = path16.join(process.cwd(), ".acore");
4404
+ if (fs16.existsSync(localAcore)) return localDir;
4405
+ return path16.join(os15.homedir(), ".acore", "plans");
3720
4406
  }
3721
4407
  function ensurePlansDir() {
3722
4408
  const dir = getPlansDir();
3723
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
4409
+ if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
3724
4410
  return dir;
3725
4411
  }
3726
4412
  function planPath(name) {
3727
4413
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3728
- return path14.join(ensurePlansDir(), `${slug}.md`);
4414
+ return path16.join(ensurePlansDir(), `${slug}.md`);
3729
4415
  }
3730
4416
  function serializePlan(plan) {
3731
4417
  const lines = [];
@@ -3751,7 +4437,7 @@ function parsePlan(content, filePath) {
3751
4437
  const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
3752
4438
  const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
3753
4439
  const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
3754
- const name = nameMatch?.[1]?.trim() || path14.basename(filePath, ".md");
4440
+ const name = nameMatch?.[1]?.trim() || path16.basename(filePath, ".md");
3755
4441
  const goal = goalMatch?.[1]?.trim() || "";
3756
4442
  const createdAt = createdMatch?.[1]?.trim() || "";
3757
4443
  const updatedAt = updatedMatch?.[1]?.trim() || "";
@@ -3793,22 +4479,22 @@ function createPlan(name, goal, steps) {
3793
4479
  }
3794
4480
  function savePlan(plan) {
3795
4481
  const fp = planPath(plan.name);
3796
- fs14.writeFileSync(fp, serializePlan(plan), "utf-8");
4482
+ fs16.writeFileSync(fp, serializePlan(plan), "utf-8");
3797
4483
  }
3798
4484
  function loadPlan(name) {
3799
4485
  const fp = planPath(name);
3800
- if (!fs14.existsSync(fp)) return null;
3801
- const content = fs14.readFileSync(fp, "utf-8");
4486
+ if (!fs16.existsSync(fp)) return null;
4487
+ const content = fs16.readFileSync(fp, "utf-8");
3802
4488
  return parsePlan(content, fp);
3803
4489
  }
3804
4490
  function listPlans() {
3805
4491
  const dir = getPlansDir();
3806
- if (!fs14.existsSync(dir)) return [];
4492
+ if (!fs16.existsSync(dir)) return [];
3807
4493
  const plans = [];
3808
- for (const file of fs14.readdirSync(dir)) {
4494
+ for (const file of fs16.readdirSync(dir)) {
3809
4495
  if (!file.endsWith(".md")) continue;
3810
- const fp = path14.join(dir, file);
3811
- const content = fs14.readFileSync(fp, "utf-8");
4496
+ const fp = path16.join(dir, file);
4497
+ const content = fs16.readFileSync(fp, "utf-8");
3812
4498
  const plan = parsePlan(content, fp);
3813
4499
  if (plan) plans.push(plan);
3814
4500
  }
@@ -3917,10 +4603,10 @@ import {
3917
4603
  } from "@aman_asmuei/arules-core";
3918
4604
  var AGENT_SCOPE = process.env.AMAN_AGENT_SCOPE ?? "dev:agent";
3919
4605
  function readEcosystemFile(filePath, label) {
3920
- if (!fs15.existsSync(filePath)) {
4606
+ if (!fs17.existsSync(filePath)) {
3921
4607
  return pc5.dim(`No ${label} file found at ${filePath}`);
3922
4608
  }
3923
- return fs15.readFileSync(filePath, "utf-8").trim();
4609
+ return fs17.readFileSync(filePath, "utf-8").trim();
3924
4610
  }
3925
4611
  function parseCommand(input) {
3926
4612
  const trimmed = input.trim();
@@ -3998,24 +4684,76 @@ async function handleIdentityCommand(action, args, _ctx) {
3998
4684
  }
3999
4685
  }
4000
4686
  if (action === "dynamics") {
4687
+ if (args.includes("--json")) {
4688
+ const model2 = await loadUserModel();
4689
+ if (!model2) return { handled: true, output: pc5.dim("No user model yet. Complete a few sessions first.") };
4690
+ return { handled: true, output: JSON.stringify(model2, null, 2) };
4691
+ }
4692
+ if (args.includes("--reset")) {
4693
+ const modelPath = defaultModelPath();
4694
+ if (fs17.existsSync(modelPath)) {
4695
+ fs17.unlinkSync(modelPath);
4696
+ return { handled: true, output: pc5.green("User model reset. Starting fresh.") };
4697
+ }
4698
+ return { handled: true, output: pc5.dim("No user model to reset.") };
4699
+ }
4001
4700
  const updates = {};
4002
4701
  for (const arg of args) {
4003
4702
  const eq = arg.indexOf("=");
4004
4703
  if (eq > 0) updates[arg.slice(0, eq)] = arg.slice(eq + 1);
4005
4704
  }
4006
- if (!Object.keys(updates).length) {
4007
- return { handled: true, output: pc5.yellow("Usage: /identity dynamics energy=high mode=focused read='Book Title'") };
4705
+ if (Object.keys(updates).length > 0) {
4706
+ try {
4707
+ await acoreUpdateDynamics({
4708
+ energy: updates.energy,
4709
+ activeMode: updates.mode,
4710
+ currentRead: updates.read
4711
+ }, AGENT_SCOPE);
4712
+ return { handled: true, output: `Dynamics updated: ${Object.entries(updates).map(([k, v]) => `${k}=${v}`).join(", ")}` };
4713
+ } catch (err) {
4714
+ return { handled: true, output: pc5.red(`Dynamics error: ${err instanceof Error ? err.message : String(err)}`) };
4715
+ }
4716
+ }
4717
+ const model = await loadUserModel();
4718
+ if (!model) {
4719
+ return { handled: true, output: pc5.dim("No user model yet. Complete a few sessions to start building your profile.") };
4008
4720
  }
4009
- try {
4010
- await acoreUpdateDynamics({
4011
- energy: updates.energy,
4012
- activeMode: updates.mode,
4013
- currentRead: updates.read
4014
- }, AGENT_SCOPE);
4015
- return { handled: true, output: `Dynamics updated: ${Object.entries(updates).map(([k, v]) => `${k}=${v}`).join(", ")}` };
4016
- } catch (err) {
4017
- return { handled: true, output: pc5.red(`Dynamics error: ${err instanceof Error ? err.message : String(err)}`) };
4721
+ const p4 = model.profile;
4722
+ const trustBar = "\u2588".repeat(Math.round(p4.trustScore * 10)) + "\u2591".repeat(10 - Math.round(p4.trustScore * 10));
4723
+ const frustBar = "\u2588".repeat(Math.round(p4.baselineFrustration * 10)) + "\u2591".repeat(10 - Math.round(p4.baselineFrustration * 10));
4724
+ const lines = [
4725
+ pc5.bold(" Dynamic User Model"),
4726
+ "",
4727
+ ` ${pc5.cyan("Trust")} ${trustBar} ${(p4.trustScore * 100).toFixed(0)}% ${p4.trustTrajectory === "ascending" ? pc5.green("\u2191") : p4.trustTrajectory === "declining" ? pc5.red("\u2193") : "\u2192"}`,
4728
+ ` ${pc5.cyan("Sessions")} ${p4.totalSessions} total (${model.sessions.length} in window)`,
4729
+ ` ${pc5.cyan("Sentiment")} ${frustBar} frustration baseline ${p4.sentimentTrend === "improving" ? pc5.green("improving") : p4.sentimentTrend === "worsening" ? pc5.red("worsening") : "stable"}`,
4730
+ "",
4731
+ ` ${pc5.cyan("Preferred")} ${p4.preferredTimePeriod} (${Object.entries(p4.energyDistribution).map(([k, v]) => `${k}: ${v}`).join(", ")})`,
4732
+ ` ${pc5.cyan("Avg session")} ${p4.avgSessionMinutes.toFixed(0)} min, ${p4.avgTurnsPerSession.toFixed(0)} turns ${p4.engagementTrend === "increasing" ? pc5.green("\u2191") : p4.engagementTrend === "decreasing" ? pc5.red("\u2193") : "\u2192"}`
4733
+ ];
4734
+ if (p4.totalSessions >= 10) {
4735
+ const corrs = [];
4736
+ if (Math.abs(p4.frustrationCorrelations.toolErrors) > 0.3) {
4737
+ corrs.push(`tool errors (${p4.frustrationCorrelations.toolErrors.toFixed(2)})`);
4738
+ }
4739
+ if (Math.abs(p4.frustrationCorrelations.longSessions) > 0.3) {
4740
+ corrs.push(`long sessions (${p4.frustrationCorrelations.longSessions.toFixed(2)})`);
4741
+ }
4742
+ if (Math.abs(p4.frustrationCorrelations.lateNight) > 0.3) {
4743
+ corrs.push(`late night (${p4.frustrationCorrelations.lateNight.toFixed(2)})`);
4744
+ }
4745
+ if (corrs.length > 0) {
4746
+ lines.push(` ${pc5.cyan("Frustration")} correlates with: ${corrs.join(", ")}`);
4747
+ }
4748
+ }
4749
+ const nudgeKeys = Object.keys(p4.nudgeStats);
4750
+ if (nudgeKeys.length > 0) {
4751
+ lines.push("");
4752
+ lines.push(` ${pc5.cyan("Nudges")} ${nudgeKeys.map((k) => `${k}: ${p4.nudgeStats[k].fired}\xD7`).join(", ")}`);
4018
4753
  }
4754
+ lines.push("");
4755
+ lines.push(pc5.dim(` Use --json for raw data, --reset to start fresh`));
4756
+ return { handled: true, output: lines.join("\n") };
4019
4757
  }
4020
4758
  if (action === "summary") {
4021
4759
  try {
@@ -4039,7 +4777,10 @@ async function handleIdentityCommand(action, args, _ctx) {
4039
4777
  pc5.bold("Identity commands:"),
4040
4778
  ` ${pc5.cyan("/identity")} View current identity`,
4041
4779
  ` ${pc5.cyan("/identity update")} <section> Update a section`,
4780
+ ` ${pc5.cyan("/identity dynamics")} View user model (trust, sentiment, patterns)`,
4042
4781
  ` ${pc5.cyan("/identity dynamics")} key=val Update dynamic fields (energy, mode, read)`,
4782
+ ` ${pc5.cyan("/identity dynamics")} --json Raw JSON user model`,
4783
+ ` ${pc5.cyan("/identity dynamics")} --reset Reset user model`,
4043
4784
  ` ${pc5.cyan("/identity summary")} Show structured identity summary`
4044
4785
  ].join("\n")
4045
4786
  };
@@ -4195,9 +4936,9 @@ ${result.violations.map((v) => ` - ${v}`).join("\n")}`)
4195
4936
  };
4196
4937
  }
4197
4938
  async function handleWorkflowsCommand(action, args, ctx) {
4198
- const home2 = os14.homedir();
4939
+ const home2 = os16.homedir();
4199
4940
  if (!action) {
4200
- const content = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4941
+ const content = readEcosystemFile(path17.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4201
4942
  return { handled: true, output: content };
4202
4943
  }
4203
4944
  if (action === "add") {
@@ -4219,7 +4960,7 @@ async function handleWorkflowsCommand(action, args, ctx) {
4219
4960
  return { handled: true, output: pc5.yellow("Usage: /workflows get <name>") };
4220
4961
  }
4221
4962
  const name = args.join(" ").toLowerCase();
4222
- const raw = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4963
+ const raw = readEcosystemFile(path17.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
4223
4964
  if (raw.startsWith("No ")) {
4224
4965
  return { handled: true, output: raw };
4225
4966
  }
@@ -4278,12 +5019,12 @@ async function handleToolsCommand(action, args, _ctx) {
4278
5019
  return { handled: true, output: pc5.yellow("Usage: /tools search <query...>") };
4279
5020
  }
4280
5021
  const query = args.join(" ").toLowerCase();
4281
- const home2 = os14.homedir();
4282
- const toolsFile = path15.join(home2, ".akit", "tools.md");
4283
- if (!fs15.existsSync(toolsFile)) {
5022
+ const home2 = os16.homedir();
5023
+ const toolsFile = path17.join(home2, ".akit", "tools.md");
5024
+ if (!fs17.existsSync(toolsFile)) {
4284
5025
  return { handled: true, output: pc5.dim(`No tools file found. Use 'npx @aman_asmuei/akit search ${args.join(" ")}' to search the registry.`) };
4285
5026
  }
4286
- const raw = fs15.readFileSync(toolsFile, "utf-8").trim();
5027
+ const raw = fs17.readFileSync(toolsFile, "utf-8").trim();
4287
5028
  const lines = raw.split("\n");
4288
5029
  const matches = lines.filter((l) => l.toLowerCase().includes(query));
4289
5030
  if (matches.length === 0) {
@@ -4294,9 +5035,9 @@ async function handleToolsCommand(action, args, _ctx) {
4294
5035
  return handleAkitCommand(action, args);
4295
5036
  }
4296
5037
  async function handleSkillsCommand(action, args, ctx) {
4297
- const home2 = os14.homedir();
5038
+ const home2 = os16.homedir();
4298
5039
  if (!action) {
4299
- const content = readEcosystemFile(path15.join(home2, ".askill", "skills.md"), "skills (askill)");
5040
+ const content = readEcosystemFile(path17.join(home2, ".askill", "skills.md"), "skills (askill)");
4300
5041
  return { handled: true, output: content };
4301
5042
  }
4302
5043
  if (action === "install") {
@@ -4318,8 +5059,8 @@ async function handleSkillsCommand(action, args, ctx) {
4318
5059
  return { handled: true, output: pc5.yellow("Usage: /skills search <query...>") };
4319
5060
  }
4320
5061
  const query = args.join(" ").toLowerCase();
4321
- const home3 = os14.homedir();
4322
- const raw = readEcosystemFile(path15.join(home3, ".askill", "skills.md"), "skills (askill)");
5062
+ const home3 = os16.homedir();
5063
+ const raw = readEcosystemFile(path17.join(home3, ".askill", "skills.md"), "skills (askill)");
4323
5064
  if (raw.startsWith("No ")) {
4324
5065
  return { handled: true, output: raw };
4325
5066
  }
@@ -4330,21 +5071,111 @@ async function handleSkillsCommand(action, args, ctx) {
4330
5071
  }
4331
5072
  return { handled: true, output: [pc5.bold(`Skills matching "${query}":`), ...matches].join("\n") };
4332
5073
  }
5074
+ if (action === "list") {
5075
+ const autoOnly = args.includes("--auto");
5076
+ if (autoOnly) {
5077
+ const logPath = path17.join(os16.homedir(), ".aman-agent", "crystallization-log.json");
5078
+ try {
5079
+ const content2 = fs17.readFileSync(logPath, "utf-8");
5080
+ const entries = JSON.parse(content2);
5081
+ if (entries.length === 0) {
5082
+ return { handled: true, output: pc5.dim("No crystallized skills yet.") };
5083
+ }
5084
+ const lines = [pc5.bold(`Crystallized skills (${entries.length}):`)];
5085
+ for (const entry of entries) {
5086
+ const date = entry.createdAt.slice(0, 10);
5087
+ lines.push(` ${pc5.cyan(entry.name)} (${date}, conf ${entry.confidence})`);
5088
+ lines.push(pc5.dim(` triggers: ${entry.triggers.join(", ")}`));
5089
+ }
5090
+ return { handled: true, output: lines.join("\n") };
5091
+ } catch {
5092
+ return { handled: true, output: pc5.dim("No crystallized skills yet.") };
5093
+ }
5094
+ }
5095
+ const content = readEcosystemFile(path17.join(home2, ".askill", "skills.md"), "skills (askill)");
5096
+ return { handled: true, output: content };
5097
+ }
5098
+ if (action === "crystallize") {
5099
+ const pmDir = path17.join(os16.homedir(), ".acore", "postmortems");
5100
+ try {
5101
+ const files = fs17.readdirSync(pmDir);
5102
+ const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
5103
+ if (jsonFiles.length === 0) {
5104
+ return {
5105
+ handled: true,
5106
+ output: pc5.dim("No post-mortems found. Run a session that triggers a post-mortem first.")
5107
+ };
5108
+ }
5109
+ const latest = jsonFiles[0];
5110
+ const content = fs17.readFileSync(path17.join(pmDir, latest), "utf-8");
5111
+ const report = JSON.parse(content);
5112
+ if (!report.crystallizationCandidates || report.crystallizationCandidates.length === 0) {
5113
+ return {
5114
+ handled: true,
5115
+ output: pc5.dim(`No crystallization candidates in the most recent post-mortem (${latest}). Run a longer session or wait for the next auto-postmortem.`)
5116
+ };
5117
+ }
5118
+ const skillsMdPath = path17.join(os16.homedir(), ".askill", "skills.md");
5119
+ const logPath = path17.join(os16.homedir(), ".aman-agent", "crystallization-log.json");
5120
+ const postmortemFilename = latest.replace(/\.json$/, ".md");
5121
+ const lines = [
5122
+ pc5.bold(`Found ${report.crystallizationCandidates.length} candidate(s) in ${latest}:`)
5123
+ ];
5124
+ let written = 0;
5125
+ for (const raw of report.crystallizationCandidates) {
5126
+ const candidate = validateCandidate(raw);
5127
+ if (!candidate) {
5128
+ const rawName = raw.name ?? "unknown";
5129
+ lines.push(pc5.dim(` \u2298 ${rawName} \u2014 failed validation`));
5130
+ continue;
5131
+ }
5132
+ const result = await writeSkillToFile(candidate, skillsMdPath, postmortemFilename);
5133
+ if (result.written) {
5134
+ written++;
5135
+ lines.push(pc5.green(` \u2713 Crystallized: ${candidate.name}`));
5136
+ await appendCrystallizationLog(
5137
+ {
5138
+ name: candidate.name,
5139
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5140
+ fromPostmortem: postmortemFilename,
5141
+ confidence: candidate.confidence,
5142
+ triggers: candidate.triggers
5143
+ },
5144
+ logPath
5145
+ );
5146
+ } else {
5147
+ lines.push(pc5.yellow(` \u2298 ${candidate.name} \u2014 ${result.reason}`));
5148
+ }
5149
+ }
5150
+ if (written > 0) {
5151
+ lines.push("");
5152
+ lines.push(pc5.dim(`Crystallized skills will auto-activate in your next session.`));
5153
+ }
5154
+ return { handled: true, output: lines.join("\n") };
5155
+ } catch (err) {
5156
+ return {
5157
+ handled: true,
5158
+ output: pc5.red(`Failed to load post-mortems: ${err instanceof Error ? err.message : String(err)}`)
5159
+ };
5160
+ }
5161
+ }
4333
5162
  if (action === "help") {
4334
5163
  return { handled: true, output: [
4335
5164
  pc5.bold("Skills commands:"),
4336
- ` ${pc5.cyan("/skills")} View installed skills`,
4337
- ` ${pc5.cyan("/skills install")} <name> Install a skill`,
4338
- ` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill`,
4339
- ` ${pc5.cyan("/skills search")} <query> Search skills by name/description`
5165
+ ` ${pc5.cyan("/skills")} View installed skills`,
5166
+ ` ${pc5.cyan("/skills install")} <name> Install a skill`,
5167
+ ` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill`,
5168
+ ` ${pc5.cyan("/skills search")} <query> Search skills by name/description`,
5169
+ ` ${pc5.cyan("/skills crystallize")} Crystallize skills from most recent post-mortem`,
5170
+ ` ${pc5.cyan("/skills list --auto")} List crystallized (auto-created) skills`
4340
5171
  ].join("\n") };
4341
5172
  }
4342
5173
  return { handled: true, output: pc5.yellow(`Unknown action: /skills ${action}. Try /skills --help`) };
4343
5174
  }
4344
5175
  async function handleEvalCommand(action, args, ctx) {
4345
- const home2 = os14.homedir();
5176
+ const home2 = os16.homedir();
4346
5177
  if (!action) {
4347
- const content = readEcosystemFile(path15.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
5178
+ const content = readEcosystemFile(path17.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
4348
5179
  return { handled: true, output: content };
4349
5180
  }
4350
5181
  if (action === "milestone") {
@@ -4356,11 +5187,11 @@ async function handleEvalCommand(action, args, ctx) {
4356
5187
  return { handled: true, output };
4357
5188
  }
4358
5189
  if (action === "report") {
4359
- const evalFile = path15.join(home2, ".aeval", "eval.md");
4360
- if (!fs15.existsSync(evalFile)) {
5190
+ const evalFile = path17.join(home2, ".aeval", "eval.md");
5191
+ if (!fs17.existsSync(evalFile)) {
4361
5192
  return { handled: true, output: pc5.dim("No eval report found. Log milestones with /eval milestone <text>.") };
4362
5193
  }
4363
- const content = fs15.readFileSync(evalFile, "utf-8").trim();
5194
+ const content = fs17.readFileSync(evalFile, "utf-8").trim();
4364
5195
  return { handled: true, output: [pc5.bold("Eval Report"), "", content].join("\n") };
4365
5196
  }
4366
5197
  return { handled: true, output: pc5.yellow(`Unknown action: /eval ${action}. Use /eval, /eval report, or /eval milestone <text>.`) };
@@ -4852,7 +5683,7 @@ function handleHelp() {
4852
5683
  ` ${pc5.cyan("/rules")} View rules [add|remove|toggle ...]`,
4853
5684
  ` ${pc5.cyan("/workflows")} View workflows [add|remove ...]`,
4854
5685
  ` ${pc5.cyan("/akit")} Manage tools [add|remove <tool>]`,
4855
- ` ${pc5.cyan("/skills")} View skills [install|uninstall ...]`,
5686
+ ` ${pc5.cyan("/skills")} View skills [install|uninstall|crystallize|list --auto]`,
4856
5687
  ` ${pc5.cyan("/eval")} View evaluation [milestone ...]`,
4857
5688
  ` ${pc5.cyan("/memory")} View recent memories [search|fts|since|stats|export|clear|timeline]`,
4858
5689
  ` ${pc5.cyan("/reminder")} Manage reminders [set|check|done]`,
@@ -4884,10 +5715,10 @@ function handleSave() {
4884
5715
  }
4885
5716
  function handleReset(action) {
4886
5717
  const dirs = {
4887
- config: path15.join(os14.homedir(), ".aman-agent"),
4888
- memory: path15.join(os14.homedir(), ".amem"),
4889
- identity: path15.join(os14.homedir(), ".acore"),
4890
- rules: path15.join(os14.homedir(), ".arules")
5718
+ config: path17.join(os16.homedir(), ".aman-agent"),
5719
+ memory: path17.join(os16.homedir(), ".amem"),
5720
+ identity: path17.join(os16.homedir(), ".acore"),
5721
+ rules: path17.join(os16.homedir(), ".arules")
4891
5722
  };
4892
5723
  if (action === "help" || !action) {
4893
5724
  return {
@@ -4912,15 +5743,15 @@ function handleReset(action) {
4912
5743
  const removed = [];
4913
5744
  for (const target of targets) {
4914
5745
  const dir = dirs[target];
4915
- if (fs15.existsSync(dir)) {
4916
- fs15.rmSync(dir, { recursive: true, force: true });
5746
+ if (fs17.existsSync(dir)) {
5747
+ fs17.rmSync(dir, { recursive: true, force: true });
4917
5748
  removed.push(target);
4918
5749
  }
4919
5750
  }
4920
5751
  if (targets.includes("config")) {
4921
5752
  const configDir = dirs.config;
4922
- fs15.mkdirSync(configDir, { recursive: true });
4923
- fs15.writeFileSync(path15.join(configDir, ".reconfig"), "", "utf-8");
5753
+ fs17.mkdirSync(configDir, { recursive: true });
5754
+ fs17.writeFileSync(path17.join(configDir, ".reconfig"), "", "utf-8");
4924
5755
  }
4925
5756
  if (removed.length === 0) {
4926
5757
  return { handled: true, output: pc5.dim("Nothing to reset \u2014 directories don't exist.") };
@@ -4937,7 +5768,7 @@ function handleReset(action) {
4937
5768
  function handleUpdate() {
4938
5769
  try {
4939
5770
  const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
4940
- const local = true ? "0.25.0" : "unknown";
5771
+ const local = true ? "0.27.0" : "unknown";
4941
5772
  if (current === local) {
4942
5773
  return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
4943
5774
  }
@@ -4981,11 +5812,11 @@ function handleExportCommand() {
4981
5812
  return { handled: true, exportConversation: true };
4982
5813
  }
4983
5814
  function handleDebugCommand() {
4984
- const logPath = path15.join(os14.homedir(), ".aman-agent", "debug.log");
4985
- if (!fs15.existsSync(logPath)) {
5815
+ const logPath = path17.join(os16.homedir(), ".aman-agent", "debug.log");
5816
+ if (!fs17.existsSync(logPath)) {
4986
5817
  return { handled: true, output: pc5.dim("No debug log found.") };
4987
5818
  }
4988
- const content = fs15.readFileSync(logPath, "utf-8");
5819
+ const content = fs17.readFileSync(logPath, "utf-8");
4989
5820
  const lines = content.trim().split("\n");
4990
5821
  const last20 = lines.slice(-20).join("\n");
4991
5822
  return { handled: true, output: pc5.bold("Debug Log (last 20 entries):\n") + pc5.dim(last20) };
@@ -5183,7 +6014,7 @@ ${result.response}`
5183
6014
  };
5184
6015
  }
5185
6016
  function handleProfileCommand(action, args) {
5186
- const profilesDir = path15.join(os14.homedir(), ".acore", "profiles");
6017
+ const profilesDir = path17.join(os16.homedir(), ".acore", "profiles");
5187
6018
  if (action === "me") {
5188
6019
  const user = loadUserIdentity();
5189
6020
  if (!user) {
@@ -5251,8 +6082,8 @@ ${pc5.dim("Edit with: /profile edit")}` };
5251
6082
  };
5252
6083
  }
5253
6084
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
5254
- const profileDir = path15.join(profilesDir, slug);
5255
- if (fs15.existsSync(profileDir)) {
6085
+ const profileDir = path17.join(profilesDir, slug);
6086
+ if (fs17.existsSync(profileDir)) {
5256
6087
  return { handled: true, output: pc5.yellow(`Profile already exists: ${slug}`) };
5257
6088
  }
5258
6089
  const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
@@ -5268,16 +6099,16 @@ ${pc5.dim("Edit with: /profile edit")}` };
5268
6099
  Use: aman-agent --profile ${slug}`
5269
6100
  };
5270
6101
  }
5271
- fs15.mkdirSync(profileDir, { recursive: true });
5272
- const globalCore = path15.join(os14.homedir(), ".acore", "core.md");
5273
- if (fs15.existsSync(globalCore)) {
5274
- let content = fs15.readFileSync(globalCore, "utf-8");
6102
+ fs17.mkdirSync(profileDir, { recursive: true });
6103
+ const globalCore = path17.join(os16.homedir(), ".acore", "core.md");
6104
+ if (fs17.existsSync(globalCore)) {
6105
+ let content = fs17.readFileSync(globalCore, "utf-8");
5275
6106
  const aiName = name.charAt(0).toUpperCase() + name.slice(1);
5276
6107
  content = content.replace(/^# .+$/m, `# ${aiName}`);
5277
- fs15.writeFileSync(path15.join(profileDir, "core.md"), content, "utf-8");
6108
+ fs17.writeFileSync(path17.join(profileDir, "core.md"), content, "utf-8");
5278
6109
  } else {
5279
6110
  const aiName = name.charAt(0).toUpperCase() + name.slice(1);
5280
- fs15.writeFileSync(path15.join(profileDir, "core.md"), `# ${aiName}
6111
+ fs17.writeFileSync(path17.join(profileDir, "core.md"), `# ${aiName}
5281
6112
 
5282
6113
  ## Identity
5283
6114
  - Role: ${aiName} is your AI companion
@@ -5290,7 +6121,7 @@ ${pc5.dim("Edit with: /profile edit")}` };
5290
6121
  return {
5291
6122
  handled: true,
5292
6123
  output: pc5.green(`Profile created: ${slug}`) + `
5293
- Edit: ${path15.join(profileDir, "core.md")}
6124
+ Edit: ${path17.join(profileDir, "core.md")}
5294
6125
  Use: aman-agent --profile ${slug}
5295
6126
 
5296
6127
  ${pc5.dim("Add rules.md or skills.md for profile-specific overrides.")}`
@@ -5299,9 +6130,9 @@ ${pc5.dim("Edit with: /profile edit")}` };
5299
6130
  case "show": {
5300
6131
  const name = args[0];
5301
6132
  if (!name) return { handled: true, output: pc5.yellow("Usage: /profile show <name>") };
5302
- const profileDir = path15.join(profilesDir, name);
5303
- if (!fs15.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
5304
- const files = fs15.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
6133
+ const profileDir = path17.join(profilesDir, name);
6134
+ if (!fs17.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
6135
+ const files = fs17.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
5305
6136
  const lines = files.map((f) => ` ${f}`);
5306
6137
  return { handled: true, output: `Profile: ${pc5.bold(name)}
5307
6138
  Files:
@@ -5310,9 +6141,9 @@ ${lines.join("\n")}` };
5310
6141
  case "delete": {
5311
6142
  const name = args[0];
5312
6143
  if (!name) return { handled: true, output: pc5.yellow("Usage: /profile delete <name>") };
5313
- const profileDir = path15.join(profilesDir, name);
5314
- if (!fs15.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
5315
- fs15.rmSync(profileDir, { recursive: true });
6144
+ const profileDir = path17.join(profilesDir, name);
6145
+ if (!fs17.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
6146
+ fs17.rmSync(profileDir, { recursive: true });
5316
6147
  return { handled: true, output: pc5.dim(`Profile deleted: ${name}`) };
5317
6148
  }
5318
6149
  case "help":
@@ -5530,10 +6361,10 @@ function handleShowcaseCommand(action, args) {
5530
6361
  Or place it as a sibling directory to aman-agent.`
5531
6362
  };
5532
6363
  }
5533
- const corePath = path15.join(os14.homedir(), ".acore", "core.md");
6364
+ const corePath = path17.join(os16.homedir(), ".acore", "core.md");
5534
6365
  let currentShowcase = null;
5535
- if (fs15.existsSync(corePath)) {
5536
- const content = fs15.readFileSync(corePath, "utf-8");
6366
+ if (fs17.existsSync(corePath)) {
6367
+ const content = fs17.readFileSync(corePath, "utf-8");
5537
6368
  const nameMatch = content.match(/^# (.+)/m);
5538
6369
  if (nameMatch) {
5539
6370
  const coreName = nameMatch[1].trim().toLowerCase();
@@ -5920,9 +6751,10 @@ ${summaryParts.slice(0, 20).join("\n")}
5920
6751
  }
5921
6752
 
5922
6753
  // src/skill-engine.ts
5923
- import fs16 from "fs";
5924
- import path16 from "path";
5925
- import os15 from "os";
6754
+ import fs18 from "fs";
6755
+ import fsp from "fs/promises";
6756
+ import path18 from "path";
6757
+ import os17 from "os";
5926
6758
  var SKILL_TRIGGERS = {
5927
6759
  testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
5928
6760
  "api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
@@ -5937,20 +6769,34 @@ var SKILL_TRIGGERS = {
5937
6769
  typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
5938
6770
  accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
5939
6771
  };
5940
- var LEVEL_FILE = path16.join(os15.homedir(), ".aman-agent", "skill-levels.json");
6772
+ async function loadRuntimeTriggers(skillsMdPath) {
6773
+ try {
6774
+ const content = await fsp.readFile(skillsMdPath, "utf-8");
6775
+ const skills = extractSkillsWithMarkers(content);
6776
+ const result = /* @__PURE__ */ new Map();
6777
+ for (const [name, marker] of skills) {
6778
+ result.set(name, marker.triggers);
6779
+ }
6780
+ return result;
6781
+ } catch (err) {
6782
+ log.debug("skill-engine", "loadRuntimeTriggers failed", err);
6783
+ return /* @__PURE__ */ new Map();
6784
+ }
6785
+ }
6786
+ var LEVEL_FILE = path18.join(os17.homedir(), ".aman-agent", "skill-levels.json");
5941
6787
  function loadSkillLevels() {
5942
6788
  try {
5943
- if (fs16.existsSync(LEVEL_FILE)) {
5944
- return JSON.parse(fs16.readFileSync(LEVEL_FILE, "utf-8"));
6789
+ if (fs18.existsSync(LEVEL_FILE)) {
6790
+ return JSON.parse(fs18.readFileSync(LEVEL_FILE, "utf-8"));
5945
6791
  }
5946
6792
  } catch {
5947
6793
  }
5948
6794
  return {};
5949
6795
  }
5950
6796
  function saveSkillLevels(levels) {
5951
- const dir = path16.dirname(LEVEL_FILE);
5952
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5953
- fs16.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
6797
+ const dir = path18.dirname(LEVEL_FILE);
6798
+ if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
6799
+ fs18.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
5954
6800
  }
5955
6801
  function computeLevel(activations) {
5956
6802
  if (activations >= 50) return { level: 5, label: "Expert" };
@@ -5969,20 +6815,28 @@ function recordActivation(skillName) {
5969
6815
  saveSkillLevels(levels);
5970
6816
  return computeLevel(levels[skillName].activations);
5971
6817
  }
5972
- function matchSkills(userInput, installedSkillNames) {
6818
+ function matchSkills(userInput, installedSkillNames, runtimeTriggers = /* @__PURE__ */ new Map()) {
5973
6819
  const input = userInput.toLowerCase();
5974
- const matched = [];
6820
+ const matched = /* @__PURE__ */ new Set();
5975
6821
  for (const skillName of installedSkillNames) {
5976
6822
  const triggers = SKILL_TRIGGERS[skillName];
5977
6823
  if (!triggers) continue;
5978
6824
  for (const trigger of triggers) {
5979
6825
  if (input.includes(trigger)) {
5980
- matched.push(skillName);
6826
+ matched.add(skillName);
6827
+ break;
6828
+ }
6829
+ }
6830
+ }
6831
+ for (const [skillName, triggers] of runtimeTriggers) {
6832
+ for (const trigger of triggers) {
6833
+ if (input.includes(trigger)) {
6834
+ matched.add(skillName);
5981
6835
  break;
5982
6836
  }
5983
6837
  }
5984
6838
  }
5985
- return matched;
6839
+ return Array.from(matched);
5986
6840
  }
5987
6841
  function formatSkillContext(skillName, skillContent, level) {
5988
6842
  let depthHint;
@@ -6006,8 +6860,10 @@ async function autoTriggerSkills(userInput, mcpManager) {
6006
6860
  const result = await mcpManager.callTool("skill_list", {});
6007
6861
  const skills = JSON.parse(result);
6008
6862
  const installed = skills.filter((s) => s.installed).map((s) => s.name);
6009
- if (installed.length === 0) return "";
6010
- const matched = matchSkills(userInput, installed);
6863
+ const skillsMdPath = path18.join(os17.homedir(), ".askill", "skills.md");
6864
+ const runtimeTriggers = await loadRuntimeTriggers(skillsMdPath);
6865
+ if (installed.length === 0 && runtimeTriggers.size === 0) return "";
6866
+ const matched = matchSkills(userInput, installed, runtimeTriggers);
6011
6867
  if (matched.length === 0) return "";
6012
6868
  const blocks = [];
6013
6869
  for (const skillName of matched.slice(0, 2)) {
@@ -6563,9 +7419,9 @@ function humanizeError(message) {
6563
7419
  }
6564
7420
 
6565
7421
  // src/hints.ts
6566
- import fs17 from "fs";
6567
- import path17 from "path";
6568
- import os16 from "os";
7422
+ import fs19 from "fs";
7423
+ import path19 from "path";
7424
+ import os18 from "os";
6569
7425
  var HINTS = [
6570
7426
  {
6571
7427
  id: "eval",
@@ -6603,11 +7459,11 @@ function getHint(state, ctx) {
6603
7459
  }
6604
7460
  return null;
6605
7461
  }
6606
- var HINTS_FILE = path17.join(os16.homedir(), ".aman-agent", "hints-seen.json");
7462
+ var HINTS_FILE = path19.join(os18.homedir(), ".aman-agent", "hints-seen.json");
6607
7463
  function loadShownHints() {
6608
7464
  try {
6609
- if (fs17.existsSync(HINTS_FILE)) {
6610
- const data = JSON.parse(fs17.readFileSync(HINTS_FILE, "utf-8"));
7465
+ if (fs19.existsSync(HINTS_FILE)) {
7466
+ const data = JSON.parse(fs19.readFileSync(HINTS_FILE, "utf-8"));
6611
7467
  return new Set(Array.isArray(data) ? data : []);
6612
7468
  }
6613
7469
  } catch {
@@ -6616,9 +7472,9 @@ function loadShownHints() {
6616
7472
  }
6617
7473
  function saveShownHints(shown) {
6618
7474
  try {
6619
- const dir = path17.dirname(HINTS_FILE);
6620
- fs17.mkdirSync(dir, { recursive: true });
6621
- fs17.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
7475
+ const dir = path19.dirname(HINTS_FILE);
7476
+ fs19.mkdirSync(dir, { recursive: true });
7477
+ fs19.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
6622
7478
  } catch {
6623
7479
  }
6624
7480
  }
@@ -6885,9 +7741,9 @@ ${task.result}`
6885
7741
  }
6886
7742
  if (cmdResult.exportConversation) {
6887
7743
  try {
6888
- const exportDir = path18.join(os17.homedir(), ".aman-agent", "exports");
6889
- fs18.mkdirSync(exportDir, { recursive: true });
6890
- const exportPath = path18.join(exportDir, `${sessionId}.md`);
7744
+ const exportDir = path20.join(os19.homedir(), ".aman-agent", "exports");
7745
+ fs20.mkdirSync(exportDir, { recursive: true });
7746
+ const exportPath = path20.join(exportDir, `${sessionId}.md`);
6891
7747
  const lines = [
6892
7748
  `# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
6893
7749
  `**Model:** ${model}`,
@@ -6901,7 +7757,7 @@ ${task.result}`
6901
7757
  lines.push(`${label} ${msg.content}`, "");
6902
7758
  }
6903
7759
  }
6904
- fs18.writeFileSync(exportPath, lines.join("\n"), "utf-8");
7760
+ fs20.writeFileSync(exportPath, lines.join("\n"), "utf-8");
6905
7761
  console.log(pc7.green(`Exported to ${exportPath}`));
6906
7762
  } catch {
6907
7763
  console.log(pc7.red("Failed to export conversation."));
@@ -7027,25 +7883,25 @@ ${knowledgeItem.content}
7027
7883
  for (const match of filePathMatches) {
7028
7884
  let filePath = match[1];
7029
7885
  if (filePath.startsWith("~/")) {
7030
- filePath = path18.join(os17.homedir(), filePath.slice(2));
7886
+ filePath = path20.join(os19.homedir(), filePath.slice(2));
7031
7887
  }
7032
- if (!fs18.existsSync(filePath) || !fs18.statSync(filePath).isFile()) continue;
7033
- const ext = path18.extname(filePath).toLowerCase();
7888
+ if (!fs20.existsSync(filePath) || !fs20.statSync(filePath).isFile()) continue;
7889
+ const ext = path20.extname(filePath).toLowerCase();
7034
7890
  if (imageExts.has(ext)) {
7035
7891
  try {
7036
- const stat = fs18.statSync(filePath);
7892
+ const stat = fs20.statSync(filePath);
7037
7893
  if (stat.size > maxImageBytes) {
7038
- process.stdout.write(pc7.yellow(` [skipped: ${path18.basename(filePath)} \u2014 exceeds 20MB limit]
7894
+ process.stdout.write(pc7.yellow(` [skipped: ${path20.basename(filePath)} \u2014 exceeds 20MB limit]
7039
7895
  `));
7040
7896
  continue;
7041
7897
  }
7042
- const data = fs18.readFileSync(filePath).toString("base64");
7898
+ const data = fs20.readFileSync(filePath).toString("base64");
7043
7899
  const mediaType = mimeMap[ext] || "image/png";
7044
7900
  imageBlocks.push({
7045
7901
  type: "image",
7046
7902
  source: { type: "base64", media_type: mediaType, data }
7047
7903
  });
7048
- process.stdout.write(pc7.dim(` [attached image: ${path18.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
7904
+ process.stdout.write(pc7.dim(` [attached image: ${path20.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
7049
7905
  `));
7050
7906
  } catch {
7051
7907
  process.stdout.write(pc7.dim(` [could not read image: ${filePath}]
@@ -7053,7 +7909,7 @@ ${knowledgeItem.content}
7053
7909
  }
7054
7910
  } else if (textExts.has(ext) || ext === "") {
7055
7911
  try {
7056
- const content = fs18.readFileSync(filePath, "utf-8");
7912
+ const content = fs20.readFileSync(filePath, "utf-8");
7057
7913
  const maxChars = 5e4;
7058
7914
  const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
7059
7915
 
@@ -7063,7 +7919,7 @@ ${knowledgeItem.content}
7063
7919
  <file path="${filePath}" size="${content.length} chars">
7064
7920
  ${trimmed}
7065
7921
  </file>`;
7066
- process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
7922
+ process.stdout.write(pc7.dim(` [attached: ${path20.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
7067
7923
  `));
7068
7924
  } catch {
7069
7925
  process.stdout.write(pc7.dim(` [could not read: ${filePath}]
@@ -7072,7 +7928,7 @@ ${trimmed}
7072
7928
  } else if (docExts.has(ext)) {
7073
7929
  if (mcpManager) {
7074
7930
  try {
7075
- process.stdout.write(pc7.dim(` [converting: ${path18.basename(filePath)}...]
7931
+ process.stdout.write(pc7.dim(` [converting: ${path20.basename(filePath)}...]
7076
7932
  `));
7077
7933
  const converted = await mcpManager.callTool("doc_convert", { path: filePath });
7078
7934
  if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
@@ -7081,7 +7937,7 @@ ${trimmed}
7081
7937
  <file path="${filePath}" format="${ext}">
7082
7938
  ${converted.slice(0, 5e4)}
7083
7939
  </file>`;
7084
- process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (converted from ${ext})]
7940
+ process.stdout.write(pc7.dim(` [attached: ${path20.basename(filePath)} (converted from ${ext})]
7085
7941
  `));
7086
7942
  } else {
7087
7943
  textContent += `
@@ -7093,7 +7949,7 @@ ${converted}
7093
7949
  `));
7094
7950
  }
7095
7951
  } catch {
7096
- process.stdout.write(pc7.dim(` [could not convert: ${path18.basename(filePath)}]
7952
+ process.stdout.write(pc7.dim(` [could not convert: ${path20.basename(filePath)}]
7097
7953
  `));
7098
7954
  }
7099
7955
  } else {
@@ -7397,7 +8253,7 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
7397
8253
  }
7398
8254
  if (hooksConfig?.featureHints) {
7399
8255
  hintState.turnCount++;
7400
- const hasWorkflows = fs18.existsSync(path18.join(os17.homedir(), ".aflow", "flow.md"));
8256
+ const hasWorkflows = fs20.existsSync(path20.join(os19.homedir(), ".aflow", "flow.md"));
7401
8257
  const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
7402
8258
  const hint = getHint(hintState, { hasWorkflows, memoryCount });
7403
8259
  if (hint) {
@@ -7443,9 +8299,9 @@ async function saveConversationToMemory(messages, sessionId) {
7443
8299
  }
7444
8300
 
7445
8301
  // src/index.ts
7446
- import fs19 from "fs";
7447
- import path19 from "path";
7448
- import os18 from "os";
8302
+ import fs21 from "fs";
8303
+ import path21 from "path";
8304
+ import os20 from "os";
7449
8305
 
7450
8306
  // src/presets.ts
7451
8307
  var PRESETS = {
@@ -7554,9 +8410,9 @@ ${wfSections}`;
7554
8410
 
7555
8411
  // src/index.ts
7556
8412
  async function autoDetectConfig() {
7557
- const reconfigMarker = path19.join(os18.homedir(), ".aman-agent", ".reconfig");
7558
- if (fs19.existsSync(reconfigMarker)) {
7559
- fs19.unlinkSync(reconfigMarker);
8413
+ const reconfigMarker = path21.join(os20.homedir(), ".aman-agent", ".reconfig");
8414
+ if (fs21.existsSync(reconfigMarker)) {
8415
+ fs21.unlinkSync(reconfigMarker);
7560
8416
  return null;
7561
8417
  }
7562
8418
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
@@ -7585,11 +8441,11 @@ async function autoDetectConfig() {
7585
8441
  return null;
7586
8442
  }
7587
8443
  function bootstrapEcosystem() {
7588
- const home2 = os18.homedir();
7589
- const corePath = path19.join(home2, ".acore", "core.md");
7590
- if (fs19.existsSync(corePath)) return false;
7591
- fs19.mkdirSync(path19.join(home2, ".acore"), { recursive: true });
7592
- fs19.writeFileSync(corePath, [
8444
+ const home2 = os20.homedir();
8445
+ const corePath = path21.join(home2, ".acore", "core.md");
8446
+ if (fs21.existsSync(corePath)) return false;
8447
+ fs21.mkdirSync(path21.join(home2, ".acore"), { recursive: true });
8448
+ fs21.writeFileSync(corePath, [
7593
8449
  "# Aman",
7594
8450
  "",
7595
8451
  "## Personality",
@@ -7601,11 +8457,11 @@ function bootstrapEcosystem() {
7601
8457
  "## Session",
7602
8458
  "_New companion \u2014 no prior sessions._"
7603
8459
  ].join("\n"), "utf-8");
7604
- const rulesDir = path19.join(home2, ".arules");
7605
- const rulesPath = path19.join(rulesDir, "rules.md");
7606
- if (!fs19.existsSync(rulesPath)) {
7607
- fs19.mkdirSync(rulesDir, { recursive: true });
7608
- fs19.writeFileSync(rulesPath, [
8460
+ const rulesDir = path21.join(home2, ".arules");
8461
+ const rulesPath = path21.join(rulesDir, "rules.md");
8462
+ if (!fs21.existsSync(rulesPath)) {
8463
+ fs21.mkdirSync(rulesDir, { recursive: true });
8464
+ fs21.writeFileSync(rulesPath, [
7609
8465
  "# Guardrails",
7610
8466
  "",
7611
8467
  "## safety",
@@ -7617,22 +8473,22 @@ function bootstrapEcosystem() {
7617
8473
  "- Respect the user's preferences stored in memory"
7618
8474
  ].join("\n"), "utf-8");
7619
8475
  }
7620
- const flowDir = path19.join(home2, ".aflow");
7621
- const flowPath = path19.join(flowDir, "flow.md");
7622
- if (!fs19.existsSync(flowPath)) {
7623
- fs19.mkdirSync(flowDir, { recursive: true });
7624
- fs19.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
8476
+ const flowDir = path21.join(home2, ".aflow");
8477
+ const flowPath = path21.join(flowDir, "flow.md");
8478
+ if (!fs21.existsSync(flowPath)) {
8479
+ fs21.mkdirSync(flowDir, { recursive: true });
8480
+ fs21.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
7625
8481
  }
7626
- const skillDir = path19.join(home2, ".askill");
7627
- const skillPath = path19.join(skillDir, "skills.md");
7628
- if (!fs19.existsSync(skillPath)) {
7629
- fs19.mkdirSync(skillDir, { recursive: true });
7630
- fs19.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
8482
+ const skillDir = path21.join(home2, ".askill");
8483
+ const skillPath = path21.join(skillDir, "skills.md");
8484
+ if (!fs21.existsSync(skillPath)) {
8485
+ fs21.mkdirSync(skillDir, { recursive: true });
8486
+ fs21.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
7631
8487
  }
7632
8488
  return true;
7633
8489
  }
7634
8490
  var program = new Command();
7635
- program.name("aman-agent").description("Your AI companion, running locally").version("0.25.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) => {
8491
+ program.name("aman-agent").description("Your AI companion, running locally").version("0.27.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) => {
7636
8492
  p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
7637
8493
  let config = loadConfig();
7638
8494
  if (!config) {
@@ -7984,19 +8840,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
7984
8840
  });
7985
8841
  if (p3.isCancel(preset)) process.exit(0);
7986
8842
  const result = applyPreset(preset, name || "Aman");
7987
- const home2 = os18.homedir();
7988
- fs19.mkdirSync(path19.join(home2, ".acore"), { recursive: true });
7989
- fs19.writeFileSync(path19.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
8843
+ const home2 = os20.homedir();
8844
+ fs21.mkdirSync(path21.join(home2, ".acore"), { recursive: true });
8845
+ fs21.writeFileSync(path21.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
7990
8846
  p3.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
7991
8847
  if (result.rulesMd) {
7992
- fs19.mkdirSync(path19.join(home2, ".arules"), { recursive: true });
7993
- fs19.writeFileSync(path19.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
8848
+ fs21.mkdirSync(path21.join(home2, ".arules"), { recursive: true });
8849
+ fs21.writeFileSync(path21.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
7994
8850
  const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
7995
8851
  p3.log.success(`${ruleCount} rules set`);
7996
8852
  }
7997
8853
  if (result.flowMd) {
7998
- fs19.mkdirSync(path19.join(home2, ".aflow"), { recursive: true });
7999
- fs19.writeFileSync(path19.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
8854
+ fs21.mkdirSync(path21.join(home2, ".aflow"), { recursive: true });
8855
+ fs21.writeFileSync(path21.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
8000
8856
  const wfCount = (result.flowMd.match(/^## /gm) || []).length;
8001
8857
  p3.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
8002
8858
  }