agent-recall-core 3.4.18 → 3.4.21
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 +404 -981
- package/dist/index.d.ts +45 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/naming.d.ts +109 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +198 -0
- package/dist/naming.js.map +1 -0
- package/dist/palace/awareness.d.ts +6 -0
- package/dist/palace/awareness.d.ts.map +1 -1
- package/dist/palace/awareness.js +19 -0
- package/dist/palace/awareness.js.map +1 -1
- package/dist/palace/fsrs.d.ts +64 -0
- package/dist/palace/fsrs.d.ts.map +1 -0
- package/dist/palace/fsrs.js +86 -0
- package/dist/palace/fsrs.js.map +1 -0
- package/dist/palace/hopfield.d.ts +108 -0
- package/dist/palace/hopfield.d.ts.map +1 -0
- package/dist/palace/hopfield.js +237 -0
- package/dist/palace/hopfield.js.map +1 -0
- package/dist/palace/obsidian.d.ts.map +1 -1
- package/dist/palace/obsidian.js +25 -1
- package/dist/palace/obsidian.js.map +1 -1
- package/dist/palace/pipeline.d.ts +61 -0
- package/dist/palace/pipeline.d.ts.map +1 -0
- package/dist/palace/pipeline.js +289 -0
- package/dist/palace/pipeline.js.map +1 -0
- package/dist/palace/skills.d.ts +71 -0
- package/dist/palace/skills.d.ts.map +1 -0
- package/dist/palace/skills.js +225 -0
- package/dist/palace/skills.js.map +1 -0
- package/dist/storage/behavior-policies.d.ts +54 -0
- package/dist/storage/behavior-policies.d.ts.map +1 -0
- package/dist/storage/behavior-policies.js +98 -0
- package/dist/storage/behavior-policies.js.map +1 -0
- package/dist/storage/corrections.d.ts +54 -0
- package/dist/storage/corrections.d.ts.map +1 -1
- package/dist/storage/corrections.js +93 -4
- package/dist/storage/corrections.js.map +1 -1
- package/dist/storage/cwd-allowlist.d.ts +33 -0
- package/dist/storage/cwd-allowlist.d.ts.map +1 -0
- package/dist/storage/cwd-allowlist.js +106 -0
- package/dist/storage/cwd-allowlist.js.map +1 -0
- package/dist/storage/dream-health.d.ts +19 -0
- package/dist/storage/dream-health.d.ts.map +1 -0
- package/dist/storage/dream-health.js +79 -0
- package/dist/storage/dream-health.js.map +1 -0
- package/dist/storage/paths.d.ts +12 -0
- package/dist/storage/paths.d.ts.map +1 -1
- package/dist/storage/paths.js +37 -15
- package/dist/storage/paths.js.map +1 -1
- package/dist/storage/project.d.ts +6 -0
- package/dist/storage/project.d.ts.map +1 -1
- package/dist/storage/project.js +26 -1
- package/dist/storage/project.js.map +1 -1
- package/dist/storage/session.d.ts.map +1 -1
- package/dist/storage/session.js +16 -1
- package/dist/storage/session.js.map +1 -1
- package/dist/tools-logic/bootstrap.d.ts.map +1 -1
- package/dist/tools-logic/bootstrap.js +69 -6
- package/dist/tools-logic/bootstrap.js.map +1 -1
- package/dist/tools-logic/check-action.d.ts +55 -0
- package/dist/tools-logic/check-action.d.ts.map +1 -0
- package/dist/tools-logic/check-action.js +152 -0
- package/dist/tools-logic/check-action.js.map +1 -0
- package/dist/tools-logic/dashboard-export.d.ts +75 -0
- package/dist/tools-logic/dashboard-export.d.ts.map +1 -0
- package/dist/tools-logic/dashboard-export.js +271 -0
- package/dist/tools-logic/dashboard-export.js.map +1 -0
- package/dist/tools-logic/pipeline-close.d.ts +22 -0
- package/dist/tools-logic/pipeline-close.d.ts.map +1 -0
- package/dist/tools-logic/pipeline-close.js +66 -0
- package/dist/tools-logic/pipeline-close.js.map +1 -0
- package/dist/tools-logic/pipeline-current.d.ts +11 -0
- package/dist/tools-logic/pipeline-current.d.ts.map +1 -0
- package/dist/tools-logic/pipeline-current.js +8 -0
- package/dist/tools-logic/pipeline-current.js.map +1 -0
- package/dist/tools-logic/pipeline-list.d.ts +11 -0
- package/dist/tools-logic/pipeline-list.d.ts.map +1 -0
- package/dist/tools-logic/pipeline-list.js +8 -0
- package/dist/tools-logic/pipeline-list.js.map +1 -0
- package/dist/tools-logic/pipeline-open.d.ts +23 -0
- package/dist/tools-logic/pipeline-open.d.ts.map +1 -0
- package/dist/tools-logic/pipeline-open.js +109 -0
- package/dist/tools-logic/pipeline-open.js.map +1 -0
- package/dist/tools-logic/pipeline-show.d.ts +34 -0
- package/dist/tools-logic/pipeline-show.d.ts.map +1 -0
- package/dist/tools-logic/pipeline-show.js +134 -0
- package/dist/tools-logic/pipeline-show.js.map +1 -0
- package/dist/tools-logic/register-rule.d.ts +15 -0
- package/dist/tools-logic/register-rule.d.ts.map +1 -0
- package/dist/tools-logic/register-rule.js +13 -0
- package/dist/tools-logic/register-rule.js.map +1 -0
- package/dist/tools-logic/session-end-reflect.d.ts +57 -0
- package/dist/tools-logic/session-end-reflect.d.ts.map +1 -0
- package/dist/tools-logic/session-end-reflect.js +117 -0
- package/dist/tools-logic/session-end-reflect.js.map +1 -0
- package/dist/tools-logic/session-end.d.ts +29 -0
- package/dist/tools-logic/session-end.d.ts.map +1 -1
- package/dist/tools-logic/session-end.js +34 -0
- package/dist/tools-logic/session-end.js.map +1 -1
- package/dist/tools-logic/session-start-lite.d.ts +26 -0
- package/dist/tools-logic/session-start-lite.d.ts.map +1 -0
- package/dist/tools-logic/session-start-lite.js +46 -0
- package/dist/tools-logic/session-start-lite.js.map +1 -0
- package/dist/tools-logic/session-start.d.ts +26 -0
- package/dist/tools-logic/session-start.d.ts.map +1 -1
- package/dist/tools-logic/session-start.js +53 -7
- package/dist/tools-logic/session-start.js.map +1 -1
- package/dist/tools-logic/skill-list.d.ts +19 -0
- package/dist/tools-logic/skill-list.d.ts.map +1 -0
- package/dist/tools-logic/skill-list.js +20 -0
- package/dist/tools-logic/skill-list.js.map +1 -0
- package/dist/tools-logic/skill-recall.d.ts +26 -0
- package/dist/tools-logic/skill-recall.d.ts.map +1 -0
- package/dist/tools-logic/skill-recall.js +32 -0
- package/dist/tools-logic/skill-recall.js.map +1 -0
- package/dist/tools-logic/skill-write.d.ts +23 -0
- package/dist/tools-logic/skill-write.d.ts.map +1 -0
- package/dist/tools-logic/skill-write.js +46 -0
- package/dist/tools-logic/skill-write.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior policies — high-salience, always-loaded IF-THEN rules.
|
|
3
|
+
*
|
|
4
|
+
* Solves item 6 of the real-usage feedback brief: a user mid-session taught
|
|
5
|
+
* the agent "humans use voice-to-text → reorganize before acting." The agent
|
|
6
|
+
* had no tool to register this as a permanent behavior rule; it could only
|
|
7
|
+
* save it as an insight string that might or might not surface again.
|
|
8
|
+
*
|
|
9
|
+
* Storage: ~/.agent-recall/projects/<slug>/palace/behavior-policies.json
|
|
10
|
+
* Shape: { rules: [{ id, name, when, do, created, hits }] }
|
|
11
|
+
*
|
|
12
|
+
* Always-loaded at session_start with HIGH salience (above regular insights).
|
|
13
|
+
* These are behavior commitments, not facts — they police what the agent does,
|
|
14
|
+
* not what it knows.
|
|
15
|
+
*/
|
|
16
|
+
export interface BehaviorRule {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
/** Trigger condition — when this rule applies. */
|
|
20
|
+
when: string;
|
|
21
|
+
/** Required action — what to do when triggered. */
|
|
22
|
+
do: string;
|
|
23
|
+
/** ISO timestamp of creation. */
|
|
24
|
+
created: string;
|
|
25
|
+
/** Times this rule was retrieved at session_start. Monotonic counter. */
|
|
26
|
+
hits: number;
|
|
27
|
+
}
|
|
28
|
+
export interface BehaviorPoliciesFile {
|
|
29
|
+
rules: BehaviorRule[];
|
|
30
|
+
}
|
|
31
|
+
export declare function readBehaviorPolicies(slug: string): BehaviorPoliciesFile;
|
|
32
|
+
export interface RegisterRuleInput {
|
|
33
|
+
project: string;
|
|
34
|
+
name: string;
|
|
35
|
+
when: string;
|
|
36
|
+
do: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RegisterRuleResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
rule_id?: string;
|
|
41
|
+
total_rules?: number;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Register a new behavior policy. Idempotent: if a rule with the same name +
|
|
46
|
+
* when + do already exists, returns the existing rule_id instead of duplicating.
|
|
47
|
+
*/
|
|
48
|
+
export declare function registerBehaviorRule(input: RegisterRuleInput): RegisterRuleResult;
|
|
49
|
+
/**
|
|
50
|
+
* Increment the `hits` counter on every rule. Called from session_start when
|
|
51
|
+
* rules are loaded — lets us see which rules are "alive" via dashboard later.
|
|
52
|
+
*/
|
|
53
|
+
export declare function recordPolicyLoad(slug: string): void;
|
|
54
|
+
//# sourceMappingURL=behavior-policies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavior-policies.d.ts","sourceRoot":"","sources":["../../src/storage/behavior-policies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAUD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAWvE;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CAgCjF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAcnD"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior policies — high-salience, always-loaded IF-THEN rules.
|
|
3
|
+
*
|
|
4
|
+
* Solves item 6 of the real-usage feedback brief: a user mid-session taught
|
|
5
|
+
* the agent "humans use voice-to-text → reorganize before acting." The agent
|
|
6
|
+
* had no tool to register this as a permanent behavior rule; it could only
|
|
7
|
+
* save it as an insight string that might or might not surface again.
|
|
8
|
+
*
|
|
9
|
+
* Storage: ~/.agent-recall/projects/<slug>/palace/behavior-policies.json
|
|
10
|
+
* Shape: { rules: [{ id, name, when, do, created, hits }] }
|
|
11
|
+
*
|
|
12
|
+
* Always-loaded at session_start with HIGH salience (above regular insights).
|
|
13
|
+
* These are behavior commitments, not facts — they police what the agent does,
|
|
14
|
+
* not what it knows.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { palaceDir } from "./paths.js";
|
|
19
|
+
import { ensureDir } from "./fs-utils.js";
|
|
20
|
+
function policyPath(slug) {
|
|
21
|
+
return path.join(palaceDir(slug), "behavior-policies.json");
|
|
22
|
+
}
|
|
23
|
+
function newRuleId() {
|
|
24
|
+
return "rule_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8);
|
|
25
|
+
}
|
|
26
|
+
export function readBehaviorPolicies(slug) {
|
|
27
|
+
const p = policyPath(slug);
|
|
28
|
+
if (!fs.existsSync(p))
|
|
29
|
+
return { rules: [] };
|
|
30
|
+
try {
|
|
31
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
if (!parsed || !Array.isArray(parsed.rules))
|
|
34
|
+
return { rules: [] };
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { rules: [] };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register a new behavior policy. Idempotent: if a rule with the same name +
|
|
43
|
+
* when + do already exists, returns the existing rule_id instead of duplicating.
|
|
44
|
+
*/
|
|
45
|
+
export function registerBehaviorRule(input) {
|
|
46
|
+
const name = (input.name ?? "").trim();
|
|
47
|
+
const when = (input.when ?? "").trim();
|
|
48
|
+
const doField = (input.do ?? "").trim();
|
|
49
|
+
if (!name)
|
|
50
|
+
return { success: false, error: "name required" };
|
|
51
|
+
if (!when)
|
|
52
|
+
return { success: false, error: "when (trigger) required" };
|
|
53
|
+
if (!doField)
|
|
54
|
+
return { success: false, error: "do (required action) required" };
|
|
55
|
+
const current = readBehaviorPolicies(input.project);
|
|
56
|
+
const existing = current.rules.find((r) => r.name === name && r.when === when && r.do === doField);
|
|
57
|
+
if (existing) {
|
|
58
|
+
return { success: true, rule_id: existing.id, total_rules: current.rules.length };
|
|
59
|
+
}
|
|
60
|
+
const rule = {
|
|
61
|
+
id: newRuleId(),
|
|
62
|
+
name,
|
|
63
|
+
when,
|
|
64
|
+
do: doField,
|
|
65
|
+
created: new Date().toISOString(),
|
|
66
|
+
hits: 0,
|
|
67
|
+
};
|
|
68
|
+
const next = { rules: [...current.rules, rule] };
|
|
69
|
+
const dir = palaceDir(input.project);
|
|
70
|
+
ensureDir(dir);
|
|
71
|
+
const target = policyPath(input.project);
|
|
72
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
73
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
74
|
+
fs.renameSync(tmp, target);
|
|
75
|
+
return { success: true, rule_id: rule.id, total_rules: next.rules.length };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Increment the `hits` counter on every rule. Called from session_start when
|
|
79
|
+
* rules are loaded — lets us see which rules are "alive" via dashboard later.
|
|
80
|
+
*/
|
|
81
|
+
export function recordPolicyLoad(slug) {
|
|
82
|
+
const current = readBehaviorPolicies(slug);
|
|
83
|
+
if (current.rules.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const next = {
|
|
86
|
+
rules: current.rules.map((r) => ({ ...r, hits: r.hits + 1 })),
|
|
87
|
+
};
|
|
88
|
+
const target = policyPath(slug);
|
|
89
|
+
try {
|
|
90
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
91
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
92
|
+
fs.renameSync(tmp, target);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// best-effort — counter loss is acceptable
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=behavior-policies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavior-policies.js","sourceRoot":"","sources":["../../src/storage/behavior-policies.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,50 @@ export interface CorrectionRecord {
|
|
|
16
16
|
kind?: "correction" | "insight" | "hunch" | "fact";
|
|
17
17
|
weight?: number;
|
|
18
18
|
active?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Outcome KPIs — closes the learning loop.
|
|
21
|
+
* V9 (research vantage 9, 2026-05-30): the only KPI that matters is
|
|
22
|
+
* "does the same bug recur after this correction was retrieved?"
|
|
23
|
+
*/
|
|
24
|
+
retrieved_count?: number;
|
|
25
|
+
heeded_count?: number;
|
|
26
|
+
recurrence_count?: number;
|
|
27
|
+
precision?: number;
|
|
28
|
+
last_retrieved?: string;
|
|
29
|
+
last_outcome?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface CorrectionOutcome {
|
|
32
|
+
correction_id: string;
|
|
33
|
+
project: string;
|
|
34
|
+
/** "heeded" = agent's action honored the warning. "recurred" = same bug happened again. */
|
|
35
|
+
kind: "retrieved" | "heeded" | "recurred";
|
|
36
|
+
/** ISO timestamp */
|
|
37
|
+
at: string;
|
|
38
|
+
/** Free-text evidence — what made you decide. */
|
|
39
|
+
evidence?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface CorrectionKPI {
|
|
42
|
+
project: string;
|
|
43
|
+
total: number;
|
|
44
|
+
active: number;
|
|
45
|
+
retrieved: number;
|
|
46
|
+
heeded: number;
|
|
47
|
+
recurred: number;
|
|
48
|
+
/** Aggregate precision = sum(heeded) / sum(retrieved). NaN if retrieved=0. */
|
|
49
|
+
precision: number;
|
|
50
|
+
/** Insights below 0.3 precision — archive candidates. */
|
|
51
|
+
noise_candidates: Array<{
|
|
52
|
+
id: string;
|
|
53
|
+
rule: string;
|
|
54
|
+
precision: number;
|
|
55
|
+
}>;
|
|
56
|
+
/** Insights above 0.8 precision with ≥3 retrievals — promote candidates. */
|
|
57
|
+
high_signal: Array<{
|
|
58
|
+
id: string;
|
|
59
|
+
rule: string;
|
|
60
|
+
precision: number;
|
|
61
|
+
retrieved: number;
|
|
62
|
+
}>;
|
|
19
63
|
}
|
|
20
64
|
/**
|
|
21
65
|
* Write a correction to persistent storage.
|
|
@@ -35,4 +79,14 @@ export declare function readActiveCorrections(project: string): CorrectionRecord
|
|
|
35
79
|
* Respects active field — archived corrections (active:false) are excluded.
|
|
36
80
|
*/
|
|
37
81
|
export declare function readP0Corrections(project: string): CorrectionRecord[];
|
|
82
|
+
/**
|
|
83
|
+
* Record an outcome event for a correction (retrieved / heeded / recurred).
|
|
84
|
+
* Appends to _outcomes.jsonl and also updates the correction JSON's counters
|
|
85
|
+
* + precision cache. Atomic per-write.
|
|
86
|
+
*/
|
|
87
|
+
export declare function recordOutcome(outcome: CorrectionOutcome): void;
|
|
88
|
+
/**
|
|
89
|
+
* Aggregate KPIs over all corrections for a project — the "is this learning loop working?" view.
|
|
90
|
+
*/
|
|
91
|
+
export declare function getCorrectionKPIs(project: string): CorrectionKPI;
|
|
38
92
|
//# sourceMappingURL=corrections.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corrections.d.ts","sourceRoot":"","sources":["../../src/storage/corrections.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"corrections.d.ts","sourceRoot":"","sources":["../../src/storage/corrections.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,IAAI,EAAE,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC1C,oBAAoB;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,gBAAgB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzE,4EAA4E;IAC5E,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxF;AA8DD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAenF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAqBnE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAEzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAErE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAqC9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAkChE"}
|
|
@@ -12,14 +12,22 @@ import { ensureDir } from "./fs-utils.js";
|
|
|
12
12
|
// Internal helpers
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
function correctionsDir(project) {
|
|
15
|
-
|
|
15
|
+
// Hardened sanitizer — same rule as storage/paths.ts. No dots (prevents ".." escape).
|
|
16
|
+
const safe = (project || "unnamed")
|
|
17
|
+
.replace(/[^a-zA-Z0-9_\-]/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "")
|
|
19
|
+
.slice(0, 100) || "unnamed";
|
|
16
20
|
const root = getRoot();
|
|
17
21
|
const resolved = path.join(root, "projects", safe, "corrections");
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const rootSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
23
|
+
if (!resolved.startsWith(rootSep)) {
|
|
24
|
+
throw new Error(`Invalid project (path escape): ${project}`);
|
|
20
25
|
}
|
|
21
26
|
return resolved;
|
|
22
27
|
}
|
|
28
|
+
function outcomesPath(project) {
|
|
29
|
+
return path.join(correctionsDir(project), "_outcomes.jsonl");
|
|
30
|
+
}
|
|
23
31
|
/** Auto-detect severity: p0 if uses strong negation/mandate language, else p1. */
|
|
24
32
|
function detectSeverity(text) {
|
|
25
33
|
const p0Patterns = /\bnever\b|\balways\b|\bdon'?t\b|\bdo not\b|\bmust not\b|\bforbid\b|\bprohibit\b/i;
|
|
@@ -63,7 +71,10 @@ export function writeCorrection(project, correction) {
|
|
|
63
71
|
const record = applyCorrectionDefaults({ ...correction, severity }, todayDate());
|
|
64
72
|
const filename = `${record.date}-${slugify(record.rule || record.id)}.json`;
|
|
65
73
|
const filepath = path.join(dir, filename);
|
|
66
|
-
|
|
74
|
+
// Atomic write — tmp + rename, mode 0600
|
|
75
|
+
const tmp = `${filepath}.tmp.${process.pid}.${Date.now()}`;
|
|
76
|
+
fs.writeFileSync(tmp, JSON.stringify(record, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
77
|
+
fs.renameSync(tmp, filepath);
|
|
67
78
|
}
|
|
68
79
|
/**
|
|
69
80
|
* Read all corrections for a project, sorted newest first.
|
|
@@ -102,4 +113,82 @@ export function readActiveCorrections(project) {
|
|
|
102
113
|
export function readP0Corrections(project) {
|
|
103
114
|
return readCorrections(project).filter((r) => r.severity === "p0" && r.active !== false);
|
|
104
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Record an outcome event for a correction (retrieved / heeded / recurred).
|
|
118
|
+
* Appends to _outcomes.jsonl and also updates the correction JSON's counters
|
|
119
|
+
* + precision cache. Atomic per-write.
|
|
120
|
+
*/
|
|
121
|
+
export function recordOutcome(outcome) {
|
|
122
|
+
const dir = correctionsDir(outcome.project);
|
|
123
|
+
ensureDir(dir);
|
|
124
|
+
// Append jsonl event (audit trail).
|
|
125
|
+
const line = JSON.stringify(outcome) + "\n";
|
|
126
|
+
fs.appendFileSync(outcomesPath(outcome.project), line, "utf-8");
|
|
127
|
+
// Update the per-correction file's counters.
|
|
128
|
+
const target = readCorrections(outcome.project).find((r) => r.id === outcome.correction_id);
|
|
129
|
+
if (!target)
|
|
130
|
+
return; // outcome can still be replayed later if record is restored
|
|
131
|
+
const updated = {
|
|
132
|
+
...target,
|
|
133
|
+
retrieved_count: target.retrieved_count ?? 0,
|
|
134
|
+
heeded_count: target.heeded_count ?? 0,
|
|
135
|
+
recurrence_count: target.recurrence_count ?? 0,
|
|
136
|
+
};
|
|
137
|
+
if (outcome.kind === "retrieved") {
|
|
138
|
+
updated.retrieved_count = (updated.retrieved_count ?? 0) + 1;
|
|
139
|
+
updated.last_retrieved = outcome.at;
|
|
140
|
+
}
|
|
141
|
+
else if (outcome.kind === "heeded") {
|
|
142
|
+
updated.heeded_count = (updated.heeded_count ?? 0) + 1;
|
|
143
|
+
updated.last_outcome = outcome.at;
|
|
144
|
+
}
|
|
145
|
+
else if (outcome.kind === "recurred") {
|
|
146
|
+
updated.recurrence_count = (updated.recurrence_count ?? 0) + 1;
|
|
147
|
+
updated.last_outcome = outcome.at;
|
|
148
|
+
}
|
|
149
|
+
const r = updated.retrieved_count ?? 0;
|
|
150
|
+
updated.precision = r > 0 ? Number(((updated.heeded_count ?? 0) / r).toFixed(3)) : undefined;
|
|
151
|
+
// Re-write the JSON file atomically (tmp + rename — prevents truncation on SIGTERM).
|
|
152
|
+
const filename = `${updated.date}-${slugify(updated.rule || updated.id)}.json`;
|
|
153
|
+
const filepath = path.join(dir, filename);
|
|
154
|
+
const tmp = `${filepath}.tmp.${process.pid}.${Date.now()}`;
|
|
155
|
+
fs.writeFileSync(tmp, JSON.stringify(updated, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
156
|
+
fs.renameSync(tmp, filepath);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Aggregate KPIs over all corrections for a project — the "is this learning loop working?" view.
|
|
160
|
+
*/
|
|
161
|
+
export function getCorrectionKPIs(project) {
|
|
162
|
+
const all = readCorrections(project);
|
|
163
|
+
const active = all.filter((r) => r.active !== false);
|
|
164
|
+
let retrieved = 0;
|
|
165
|
+
let heeded = 0;
|
|
166
|
+
let recurred = 0;
|
|
167
|
+
const noise = [];
|
|
168
|
+
const hot = [];
|
|
169
|
+
for (const r of all) {
|
|
170
|
+
retrieved += r.retrieved_count ?? 0;
|
|
171
|
+
heeded += r.heeded_count ?? 0;
|
|
172
|
+
recurred += r.recurrence_count ?? 0;
|
|
173
|
+
const p = r.precision ?? null;
|
|
174
|
+
const ret = r.retrieved_count ?? 0;
|
|
175
|
+
if (p !== null && ret >= 3 && p < 0.3) {
|
|
176
|
+
noise.push({ id: r.id, rule: r.rule, precision: p });
|
|
177
|
+
}
|
|
178
|
+
if (p !== null && ret >= 3 && p >= 0.8) {
|
|
179
|
+
hot.push({ id: r.id, rule: r.rule, precision: p, retrieved: ret });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
project,
|
|
184
|
+
total: all.length,
|
|
185
|
+
active: active.length,
|
|
186
|
+
retrieved,
|
|
187
|
+
heeded,
|
|
188
|
+
recurred,
|
|
189
|
+
precision: retrieved > 0 ? Number((heeded / retrieved).toFixed(3)) : NaN,
|
|
190
|
+
noise_candidates: noise,
|
|
191
|
+
high_signal: hot,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
105
194
|
//# sourceMappingURL=corrections.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corrections.js","sourceRoot":"","sources":["../../src/storage/corrections.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"corrections.js","sourceRoot":"","sources":["../../src/storage/corrections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cwd-allowlist — explicit per-project mapping from working directory to slug.
|
|
3
|
+
*
|
|
4
|
+
* Solves the wrong-project-routing bug: when an agent runs in
|
|
5
|
+
* `~/Projects/prismma-web`, name-based detection used to load the `prismma`
|
|
6
|
+
* (video-gen) project instead of `prismma-gateway`. The allowlist gives an
|
|
7
|
+
* explicit "if cwd starts with any of these paths → use this slug" mapping
|
|
8
|
+
* that wins over git/package.json/cwd-basename heuristics.
|
|
9
|
+
*
|
|
10
|
+
* Storage: ~/.agent-recall/projects/<slug>/palace/cwd-allowlist.json
|
|
11
|
+
* Shape: { "paths": ["/abs/path/one", "/abs/path/two"] }
|
|
12
|
+
* Migration: file is auto-created on first explicit session_start; existing
|
|
13
|
+
* projects without one continue to use the old heuristics.
|
|
14
|
+
*/
|
|
15
|
+
export interface CwdAllowlist {
|
|
16
|
+
paths: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the cwd-allowlist for a single project. Returns empty if missing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readCwdAllowlist(slug: string): CwdAllowlist;
|
|
22
|
+
/**
|
|
23
|
+
* Atomically add an absolute path to the project's cwd-allowlist (idempotent).
|
|
24
|
+
* Normalizes the path (trailing slash removed).
|
|
25
|
+
*/
|
|
26
|
+
export declare function addCwdToAllowlist(slug: string, cwdPath: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Scan every project's allowlist; return the slug whose allowlist contains a
|
|
29
|
+
* path that is a prefix of (or equal to) `cwd`. Longest-prefix wins so nested
|
|
30
|
+
* allowlists resolve correctly. Returns null when nothing matches.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findProjectByCwd(cwd: string): string | null;
|
|
33
|
+
//# sourceMappingURL=cwd-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd-allowlist.d.ts","sourceRoot":"","sources":["../../src/storage/cwd-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAsBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAW3D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAarE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB3D"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cwd-allowlist — explicit per-project mapping from working directory to slug.
|
|
3
|
+
*
|
|
4
|
+
* Solves the wrong-project-routing bug: when an agent runs in
|
|
5
|
+
* `~/Projects/prismma-web`, name-based detection used to load the `prismma`
|
|
6
|
+
* (video-gen) project instead of `prismma-gateway`. The allowlist gives an
|
|
7
|
+
* explicit "if cwd starts with any of these paths → use this slug" mapping
|
|
8
|
+
* that wins over git/package.json/cwd-basename heuristics.
|
|
9
|
+
*
|
|
10
|
+
* Storage: ~/.agent-recall/projects/<slug>/palace/cwd-allowlist.json
|
|
11
|
+
* Shape: { "paths": ["/abs/path/one", "/abs/path/two"] }
|
|
12
|
+
* Migration: file is auto-created on first explicit session_start; existing
|
|
13
|
+
* projects without one continue to use the old heuristics.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { getRoot } from "../types.js";
|
|
18
|
+
import { palaceDir } from "./paths.js";
|
|
19
|
+
import { ensureDir } from "./fs-utils.js";
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a filesystem path for stable matching:
|
|
22
|
+
* - Resolve symlinks (handles macOS /tmp → /private/tmp)
|
|
23
|
+
* - Strip trailing slash
|
|
24
|
+
* Falls back to the input if realpath fails (path doesn't exist yet).
|
|
25
|
+
*/
|
|
26
|
+
function normalizePath(p) {
|
|
27
|
+
let normalized = p;
|
|
28
|
+
try {
|
|
29
|
+
normalized = fs.realpathSync(p);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// path may not exist on disk yet; use the literal string
|
|
33
|
+
}
|
|
34
|
+
return normalized.replace(/\/+$/, "");
|
|
35
|
+
}
|
|
36
|
+
function allowlistPath(slug) {
|
|
37
|
+
return path.join(palaceDir(slug), "cwd-allowlist.json");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read the cwd-allowlist for a single project. Returns empty if missing.
|
|
41
|
+
*/
|
|
42
|
+
export function readCwdAllowlist(slug) {
|
|
43
|
+
const p = allowlistPath(slug);
|
|
44
|
+
if (!fs.existsSync(p))
|
|
45
|
+
return { paths: [] };
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
if (!parsed || !Array.isArray(parsed.paths))
|
|
50
|
+
return { paths: [] };
|
|
51
|
+
return { paths: parsed.paths.filter((s) => typeof s === "string" && s.startsWith("/")) };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { paths: [] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Atomically add an absolute path to the project's cwd-allowlist (idempotent).
|
|
59
|
+
* Normalizes the path (trailing slash removed).
|
|
60
|
+
*/
|
|
61
|
+
export function addCwdToAllowlist(slug, cwdPath) {
|
|
62
|
+
if (!cwdPath || !cwdPath.startsWith("/"))
|
|
63
|
+
return;
|
|
64
|
+
const normalized = normalizePath(cwdPath);
|
|
65
|
+
const current = readCwdAllowlist(slug);
|
|
66
|
+
if (current.paths.includes(normalized))
|
|
67
|
+
return;
|
|
68
|
+
const next = { paths: [...current.paths, normalized].sort() };
|
|
69
|
+
const dir = palaceDir(slug);
|
|
70
|
+
ensureDir(dir);
|
|
71
|
+
const target = allowlistPath(slug);
|
|
72
|
+
// Atomic write — tmp + rename
|
|
73
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
74
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
75
|
+
fs.renameSync(tmp, target);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Scan every project's allowlist; return the slug whose allowlist contains a
|
|
79
|
+
* path that is a prefix of (or equal to) `cwd`. Longest-prefix wins so nested
|
|
80
|
+
* allowlists resolve correctly. Returns null when nothing matches.
|
|
81
|
+
*/
|
|
82
|
+
export function findProjectByCwd(cwd) {
|
|
83
|
+
if (!cwd || !cwd.startsWith("/"))
|
|
84
|
+
return null;
|
|
85
|
+
const normalized = normalizePath(cwd);
|
|
86
|
+
const root = getRoot();
|
|
87
|
+
const projectsDir = path.join(root, "projects");
|
|
88
|
+
if (!fs.existsSync(projectsDir))
|
|
89
|
+
return null;
|
|
90
|
+
let bestMatch = null;
|
|
91
|
+
for (const entry of fs.readdirSync(projectsDir)) {
|
|
92
|
+
if (entry.startsWith("_archived_") || entry.startsWith("."))
|
|
93
|
+
continue;
|
|
94
|
+
const list = readCwdAllowlist(entry);
|
|
95
|
+
for (const p of list.paths) {
|
|
96
|
+
// Exact match OR cwd lives strictly under p
|
|
97
|
+
if (normalized === p || normalized.startsWith(p + "/")) {
|
|
98
|
+
if (!bestMatch || p.length > bestMatch.prefixLength) {
|
|
99
|
+
bestMatch = { slug: entry, prefixLength: p.length };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return bestMatch?.slug ?? null;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=cwd-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd-allowlist.js","sourceRoot":"","sources":["../../src/storage/cwd-allowlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream health — count consecutive failure days from AAM dream logs.
|
|
3
|
+
*
|
|
4
|
+
* Solves the "silent cron failures rot for days" pain: dream auth has been
|
|
5
|
+
* failing for 6 consecutive nights but only surfaces in stray `arstatus` output,
|
|
6
|
+
* never at session_start. This helper lets session_start emit a red banner.
|
|
7
|
+
*
|
|
8
|
+
* Log location: ~/.aam/dreams/run-YYYY-MM-DD.log (AAM-orchestrated dreams)
|
|
9
|
+
* Pattern: a day's log "succeeded" if it contains "Dream complete" or
|
|
10
|
+
* "Dream run complete"; otherwise it "failed".
|
|
11
|
+
*/
|
|
12
|
+
export interface DreamHealth {
|
|
13
|
+
consecutive_failures: number;
|
|
14
|
+
last_failed_date: string | null;
|
|
15
|
+
last_success_date: string | null;
|
|
16
|
+
banner: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare function getDreamHealth(): DreamHealth;
|
|
19
|
+
//# sourceMappingURL=dream-health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dream-health.d.ts","sourceRoot":"","sources":["../../src/storage/dream-health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,WAAW;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAqBD,wBAAgB,cAAc,IAAI,WAAW,CA0C5C"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream health — count consecutive failure days from AAM dream logs.
|
|
3
|
+
*
|
|
4
|
+
* Solves the "silent cron failures rot for days" pain: dream auth has been
|
|
5
|
+
* failing for 6 consecutive nights but only surfaces in stray `arstatus` output,
|
|
6
|
+
* never at session_start. This helper lets session_start emit a red banner.
|
|
7
|
+
*
|
|
8
|
+
* Log location: ~/.aam/dreams/run-YYYY-MM-DD.log (AAM-orchestrated dreams)
|
|
9
|
+
* Pattern: a day's log "succeeded" if it contains "Dream complete" or
|
|
10
|
+
* "Dream run complete"; otherwise it "failed".
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
const DREAMS_DIR = path.join(os.homedir(), ".aam", "dreams");
|
|
16
|
+
const LOOKBACK_DAYS = 7;
|
|
17
|
+
const BANNER_THRESHOLD = 2; // surface when N or more consecutive failures
|
|
18
|
+
function dateNDaysAgo(n) {
|
|
19
|
+
const d = new Date();
|
|
20
|
+
d.setDate(d.getDate() - n);
|
|
21
|
+
return d.toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
function isSuccess(logPath) {
|
|
24
|
+
try {
|
|
25
|
+
const content = fs.readFileSync(logPath, "utf-8");
|
|
26
|
+
return /Dream(?:\s+run)?\s+complete/i.test(content);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function getDreamHealth() {
|
|
33
|
+
const out = {
|
|
34
|
+
consecutive_failures: 0,
|
|
35
|
+
last_failed_date: null,
|
|
36
|
+
last_success_date: null,
|
|
37
|
+
banner: null,
|
|
38
|
+
};
|
|
39
|
+
if (!fs.existsSync(DREAMS_DIR))
|
|
40
|
+
return out;
|
|
41
|
+
// Walk yesterday → 7 days ago. Today is in-progress so we don't count it.
|
|
42
|
+
for (let i = 1; i <= LOOKBACK_DAYS; i++) {
|
|
43
|
+
const dateStr = dateNDaysAgo(i);
|
|
44
|
+
const logPath = path.join(DREAMS_DIR, `run-${dateStr}.log`);
|
|
45
|
+
if (!fs.existsSync(logPath)) {
|
|
46
|
+
// No log = no run that night; treat as a failure (cron didn't fire OR
|
|
47
|
+
// it crashed before writing). Stops the streak if we'd been counting,
|
|
48
|
+
// since "no log" + "missing" are both signs of unhealthy automation.
|
|
49
|
+
if (out.last_success_date === null) {
|
|
50
|
+
out.consecutive_failures++;
|
|
51
|
+
if (out.last_failed_date === null)
|
|
52
|
+
out.last_failed_date = dateStr;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (isSuccess(logPath)) {
|
|
60
|
+
if (out.last_success_date === null)
|
|
61
|
+
out.last_success_date = dateStr;
|
|
62
|
+
break; // streak ended
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
out.consecutive_failures++;
|
|
66
|
+
if (out.last_failed_date === null)
|
|
67
|
+
out.last_failed_date = dateStr;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (out.consecutive_failures >= BANNER_THRESHOLD) {
|
|
71
|
+
const lastSuccess = out.last_success_date ?? `>${LOOKBACK_DAYS} days ago`;
|
|
72
|
+
out.banner =
|
|
73
|
+
`⚠ Dream cron failed ${out.consecutive_failures} nights in a row ` +
|
|
74
|
+
`(last success: ${lastSuccess}). The awareness backfill is broken — ` +
|
|
75
|
+
`check ~/.aam/dreams/run-${out.last_failed_date}.log for auth or network errors.`;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=dream-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dream-health.js","sourceRoot":"","sources":["../../src/storage/dream-health.ts"],"names":[],"mappings":"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"}
|