openhermes 2.8.0 → 4.0.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.
Files changed (158) hide show
  1. package/CONTEXT.md +18 -0
  2. package/ETHOS.md +15 -0
  3. package/README.md +135 -292
  4. package/bootstrap.mjs +174 -512
  5. package/harness/agents/openhermes.md +87 -0
  6. package/harness/codex/CONSTITUTION.md +70 -148
  7. package/harness/codex/ROUTING.md +126 -0
  8. package/harness/commands/oh-doctor.md +26 -0
  9. package/harness/instructions/CONVENTIONS.md +206 -206
  10. package/harness/instructions/RUNTIME.md +54 -31
  11. package/harness/skills/oh-builder/SKILL.md +98 -0
  12. package/harness/skills/oh-caveman/SKILL.md +33 -0
  13. package/harness/skills/oh-expert/SKILL.md +121 -0
  14. package/harness/skills/oh-freeze/SKILL.md +28 -0
  15. package/harness/skills/oh-gauntlet/SKILL.md +119 -0
  16. package/harness/skills/oh-grill/SKILL.md +77 -0
  17. package/harness/skills/oh-guard/SKILL.md +33 -0
  18. package/harness/skills/oh-handoff/SKILL.md +33 -0
  19. package/harness/skills/oh-health/SKILL.md +90 -0
  20. package/harness/skills/oh-init/SKILL.md +78 -0
  21. package/harness/skills/oh-investigate/SKILL.md +35 -0
  22. package/harness/skills/oh-issue/SKILL.md +36 -0
  23. package/harness/skills/oh-learn/SKILL.md +28 -0
  24. package/harness/skills/oh-manifest/SKILL.md +84 -0
  25. package/harness/skills/oh-plan-review/SKILL.md +128 -0
  26. package/harness/skills/oh-planner/SKILL.md +157 -0
  27. package/harness/skills/oh-prd/SKILL.md +35 -0
  28. package/harness/skills/oh-retro/SKILL.md +33 -0
  29. package/harness/skills/oh-review/SKILL.md +110 -0
  30. package/harness/skills/oh-security/SKILL.md +110 -0
  31. package/harness/skills/oh-ship/SKILL.md +39 -0
  32. package/harness/skills/oh-skill-craft/SKILL.md +107 -0
  33. package/harness/skills/oh-skills-link/SKILL.md +29 -0
  34. package/harness/skills/oh-skills-list/SKILL.md +31 -0
  35. package/harness/skills/oh-triage/SKILL.md +36 -0
  36. package/index.mjs +3 -60
  37. package/lib/harness-resolver.mjs +77 -0
  38. package/lib/logger.mjs +62 -0
  39. package/package.json +49 -53
  40. package/test/plugins-behavioral.test.mjs +64 -0
  41. package/test/plugins.test.mjs +62 -0
  42. package/autorecall.mjs +0 -237
  43. package/curator.mjs +0 -482
  44. package/harness/commands/build-fix.md +0 -60
  45. package/harness/commands/checkpoint.md +0 -68
  46. package/harness/commands/code-review.md +0 -71
  47. package/harness/commands/doctor.md +0 -42
  48. package/harness/commands/eval.md +0 -89
  49. package/harness/commands/go-build.md +0 -87
  50. package/harness/commands/go-review.md +0 -71
  51. package/harness/commands/harness-audit.md +0 -90
  52. package/harness/commands/learn.md +0 -37
  53. package/harness/commands/loop-start.md +0 -38
  54. package/harness/commands/loop-status.md +0 -30
  55. package/harness/commands/memory-search.md +0 -37
  56. package/harness/commands/model-route.md +0 -32
  57. package/harness/commands/ohc.md +0 -13
  58. package/harness/commands/orchestrate.md +0 -88
  59. package/harness/commands/plan.md +0 -53
  60. package/harness/commands/quality-gate.md +0 -35
  61. package/harness/commands/refactor-clean.md +0 -102
  62. package/harness/commands/rust-build.md +0 -78
  63. package/harness/commands/rust-review.md +0 -65
  64. package/harness/commands/security.md +0 -93
  65. package/harness/commands/setup-pm.md +0 -65
  66. package/harness/commands/skill-create.md +0 -99
  67. package/harness/commands/test-coverage.md +0 -80
  68. package/harness/commands/update-codemaps.md +0 -81
  69. package/harness/commands/update-docs.md +0 -67
  70. package/harness/commands/verify.md +0 -68
  71. package/harness/prompts/architect.txt +0 -189
  72. package/harness/prompts/build-cpp.md +0 -98
  73. package/harness/prompts/build-error-resolver.md +0 -44
  74. package/harness/prompts/build-go.md +0 -340
  75. package/harness/prompts/build-java.md +0 -140
  76. package/harness/prompts/build-kotlin.md +0 -137
  77. package/harness/prompts/build-rust.md +0 -108
  78. package/harness/prompts/code-reviewer.md +0 -40
  79. package/harness/prompts/doc-updater.md +0 -206
  80. package/harness/prompts/docs-lookup.md +0 -71
  81. package/harness/prompts/e2e-runner.txt +0 -317
  82. package/harness/prompts/explore.md +0 -42
  83. package/harness/prompts/harness-optimizer.md +0 -42
  84. package/harness/prompts/loop-operator.md +0 -53
  85. package/harness/prompts/planner.md +0 -37
  86. package/harness/prompts/refactor-cleaner.md +0 -256
  87. package/harness/prompts/review-cpp.md +0 -81
  88. package/harness/prompts/review-database.md +0 -261
  89. package/harness/prompts/review-go.md +0 -257
  90. package/harness/prompts/review-java.md +0 -113
  91. package/harness/prompts/review-kotlin.md +0 -143
  92. package/harness/prompts/review-python.md +0 -101
  93. package/harness/prompts/review-rust.md +0 -77
  94. package/harness/prompts/security-reviewer.md +0 -42
  95. package/harness/prompts/tdd-guide.md +0 -228
  96. package/harness/rules/audit.md +0 -84
  97. package/harness/rules/checkpointing.md +0 -75
  98. package/harness/rules/context-loading.md +0 -33
  99. package/harness/rules/credential-exposure.md +0 -0
  100. package/harness/rules/delegation.md +0 -80
  101. package/harness/rules/handoff.md +0 -267
  102. package/harness/rules/memory-management.md +0 -28
  103. package/harness/rules/precedence.md +0 -52
  104. package/harness/rules/promotion.md +0 -46
  105. package/harness/rules/ranking.md +0 -64
  106. package/harness/rules/retrieval.md +0 -94
  107. package/harness/rules/runtime-guards.md +0 -196
  108. package/harness/rules/self-heal.md +0 -79
  109. package/harness/rules/session-start.md +0 -34
  110. package/harness/rules/skills-management.md +0 -165
  111. package/harness/rules/state-drift.md +0 -192
  112. package/harness/rules/verification.md +0 -88
  113. package/harness/scripts/sync-commands.mjs +0 -259
  114. package/harness/skills/.bundled_manifest +0 -17
  115. package/harness/skills/.usage.json +0 -6
  116. package/harness/skills/api-design/SKILL.md +0 -523
  117. package/harness/skills/backend-patterns/SKILL.md +0 -598
  118. package/harness/skills/coding-standards/SKILL.md +0 -549
  119. package/harness/skills/e2e-testing/SKILL.md +0 -326
  120. package/harness/skills/frontend-patterns/SKILL.md +0 -642
  121. package/harness/skills/frontend-slides/SKILL.md +0 -184
  122. package/harness/skills/security-review/SKILL.md +0 -495
  123. package/harness/skills/strategic-compact/SKILL.md +0 -131
  124. package/harness/skills/tdd-workflow/SKILL.md +0 -463
  125. package/harness/skills/verification-loop/SKILL.md +0 -126
  126. package/lib/ambient-memory.mjs +0 -167
  127. package/lib/handoff.mjs +0 -171
  128. package/lib/hardening.mjs +0 -146
  129. package/lib/memory-tools-plugin.mjs +0 -368
  130. package/lib/ohc/block-sync.mjs +0 -69
  131. package/lib/ohc/compress/search.mjs +0 -152
  132. package/lib/ohc/compress/state.mjs +0 -76
  133. package/lib/ohc/config.mjs +0 -185
  134. package/lib/ohc/message-ids.mjs +0 -178
  135. package/lib/ohc/notify.mjs +0 -135
  136. package/lib/ohc/protected-patterns.mjs +0 -55
  137. package/lib/ohc/prune-apply.mjs +0 -134
  138. package/lib/ohc/pruner.mjs +0 -608
  139. package/lib/ohc/reaper.mjs +0 -70
  140. package/lib/ohc/state.mjs +0 -265
  141. package/lib/ohc/strategies/deduplication.mjs +0 -72
  142. package/lib/ohc/strategies/index.mjs +0 -2
  143. package/lib/ohc/strategies/purge-errors.mjs +0 -43
  144. package/lib/ohc/token-utils.mjs +0 -26
  145. package/lib/ohc/updater.mjs +0 -132
  146. package/lib/paths.mjs +0 -49
  147. package/lib/schema-validator.mjs +0 -79
  148. package/lib/search.mjs +0 -48
  149. package/schemas/audit.schema.json +0 -82
  150. package/schemas/backlog.schema.json +0 -63
  151. package/schemas/checkpoint.schema.json +0 -65
  152. package/schemas/constraint.schema.json +0 -62
  153. package/schemas/decision.schema.json +0 -63
  154. package/schemas/instinct.schema.json +0 -63
  155. package/schemas/loop-state.schema.json +0 -33
  156. package/schemas/mistake.schema.json +0 -64
  157. package/schemas/verification_receipt.schema.json +0 -88
  158. package/skill-builder.mjs +0 -88
@@ -0,0 +1,64 @@
1
+ import { describe, it, before } from "node:test"
2
+ import assert from "node:assert/strict"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+
8
+ describe("BootstrapPlugin behavior", () => {
9
+ let mod
10
+
11
+ before(async () => {
12
+ mod = await import("../bootstrap.mjs")
13
+ })
14
+
15
+ it("registers package-local skills, commands, agents, and instructions", async () => {
16
+ const plugin = await mod.BootstrapPlugin({ directory: __dirname })
17
+ const config = { skills: { paths: [] }, command: {}, agent: {}, instructions: [] }
18
+
19
+ await plugin.config(config)
20
+
21
+ assert.ok(config.skills.paths.some(p => p.endsWith(path.join("harness", "skills"))))
22
+ assert.ok(config.command["oh-doctor"])
23
+ assert.ok(config.agent.OpenHermes)
24
+ assert.equal(config.default_agent, "OpenHermes")
25
+ assert.ok(config.instructions.some(p => p.endsWith(path.join("harness", "codex", "CONSTITUTION.md"))))
26
+ assert.ok(config.instructions.some(p => p.endsWith(path.join("harness", "instructions", "RUNTIME.md"))))
27
+ })
28
+
29
+ it("loads markdown manifests into command and agent config", async () => {
30
+ const plugin = await mod.BootstrapPlugin({ directory: __dirname })
31
+ const config = { skills: { paths: [] }, command: {}, agent: {}, instructions: [] }
32
+
33
+ await plugin.config(config)
34
+
35
+ assert.match(config.command["oh-doctor"].template, /Inspect the current OpenHermes\/OpenCode setup/)
36
+ assert.equal(config.command["oh-doctor"].agent, "OpenHermes")
37
+ assert.match(config.agent.OpenHermes.prompt, /You are OpenHermes, the primary orchestrator/)
38
+ assert.equal(config.agent.OpenHermes.mode, "primary")
39
+ })
40
+
41
+ it("injects bootstrap text only once", async () => {
42
+ const plugin = await mod.BootstrapPlugin({ directory: __dirname })
43
+ const output = {
44
+ messages: [
45
+ {
46
+ info: { role: "user" },
47
+ parts: [
48
+ { type: "text", text: "actual user request" },
49
+ ],
50
+ },
51
+ ],
52
+ }
53
+
54
+ await plugin["experimental.chat.messages.transform"]({}, output)
55
+ await plugin["experimental.chat.messages.transform"]({}, output)
56
+
57
+ assert.match(output.messages[0].parts[0].text, /OPENHERMES_BOOTSTRAP/)
58
+ assert.match(output.messages[0].parts[1].text, /actual user request/)
59
+ assert.equal(
60
+ output.messages[0].parts.filter(part => typeof part.text === "string" && part.text.includes("OPENHERMES_BOOTSTRAP")).length,
61
+ 1,
62
+ )
63
+ })
64
+ })
@@ -0,0 +1,62 @@
1
+ import { describe, it, before } from "node:test"
2
+ import assert from "node:assert/strict"
3
+ import fs from "node:fs"
4
+ import os from "node:os"
5
+ import path from "node:path"
6
+
7
+ describe("plugin exports", () => {
8
+ it("index.mjs default exports plugin", async () => {
9
+ const pkg = await import("../index.mjs")
10
+ assert.ok(typeof pkg.default === "function")
11
+ })
12
+
13
+ it("bootstrap.mjs exports BootstrapPlugin", async () => {
14
+ const mod = await import("../bootstrap.mjs")
15
+ assert.ok(typeof mod.BootstrapPlugin === "function")
16
+ })
17
+ })
18
+
19
+ describe("bootstrap helpers", () => {
20
+ let mod
21
+
22
+ before(async () => {
23
+ mod = await import("../bootstrap.mjs")
24
+ })
25
+
26
+ it("re-exports harness resolver helpers", async () => {
27
+ const { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir } = mod
28
+ assert.ok(typeof resolveHarnessRoot === "function")
29
+ assert.ok(typeof setHarnessRootForTest === "function")
30
+ assert.ok(typeof getHarnessDir === "function")
31
+ })
32
+
33
+ it("resolveHarnessRoot picks complete harness root", async () => {
34
+ const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openhermes-harness-"))
35
+ const badRoot = path.join(tmpRoot, "bad")
36
+ const goodRoot = path.join(tmpRoot, "good")
37
+
38
+ fs.mkdirSync(path.join(badRoot, "codex"), { recursive: true })
39
+ fs.writeFileSync(path.join(badRoot, "codex", "CONSTITUTION.md"), "# incomplete\n")
40
+
41
+ const requiredFiles = [
42
+ ["codex", "CONSTITUTION.md"],
43
+ ["instructions", "RUNTIME.md"],
44
+ ["skills", "oh-plan", "SKILL.md"],
45
+ ]
46
+
47
+ for (const parts of requiredFiles) {
48
+ const filePath = path.join(goodRoot, ...parts)
49
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
50
+ fs.writeFileSync(filePath, "ok\n")
51
+ }
52
+
53
+ const resolved = mod.resolveHarnessRoot({ candidateRoots: [badRoot, goodRoot] })
54
+ assert.equal(resolved, goodRoot)
55
+ })
56
+
57
+ it("setHarnessRootForTest overrides harness resolution", async () => {
58
+ mod.setHarnessRootForTest("/custom/harness")
59
+ assert.equal(mod.getHarnessDir(), "/custom/harness")
60
+ mod.setHarnessRootForTest(undefined)
61
+ })
62
+ })
package/autorecall.mjs DELETED
@@ -1,237 +0,0 @@
1
- import path from "node:path"
2
- import os from "node:os"
3
- import fs from "node:fs"
4
- import { atomicWriteJson, buildEnvironmentFingerprint, fingerprintEnvironment, isTruthy, readJson, readJsonl, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
5
- import { getDataRoot, getCacheRoot, getMemoryRoot, getRecallRoot, getRuntimeRoot } from "./lib/paths.mjs"
6
-
7
- const OLD_BASE = path.join(os.homedir(), ".config", "opencode", "openhermes")
8
- const BOILERPLATE_SUMMARY = /^(Idle checkpoint for|Pre-compaction checkpoint for|Placeholder checkpoint|Session in progress|No active checkpoint)/i
9
- const PLURALS = { audit: "audits", checkpoint: "checkpoints", mistake: "mistakes", instinct: "instincts", decision: "decisions", constraint: "constraints", backlog: "backlog", verification_receipt: "verification_receipts" }
10
-
11
- function classDir(cls) { return path.join(getMemoryRoot(), PLURALS[cls]) }
12
-
13
- function hasExpired(r) {
14
- if (r?.status === "expired" || r?.status === "decayed") return true
15
- if (r?.decay_at && Date.parse(r.decay_at) < Date.now()) return true
16
- if (r?.expires_at && Date.parse(r.expires_at) < Date.now()) return true
17
- return false
18
- }
19
-
20
- function sweepStaleRecords() {
21
- const classes = ["checkpoints", "constraints", "decisions", "instincts", "audits", "backlog", "verification_receipts"]
22
- let swept = 0
23
- for (const plural of classes) {
24
- const dir = path.join(getMemoryRoot(), plural)
25
- let files = []
26
- try { files = fs.readdirSync(dir).filter(f => f.endsWith(".json") && f !== "index.json") } catch { continue }
27
- for (const f of files) {
28
- const fp = path.join(dir, f)
29
- const record = readJson(fp, null)
30
- if (!record || !hasExpired(record)) continue
31
- if (record.status === "expired" || record.status === "decayed") continue
32
- record.status = "expired"
33
- record.updated_at = new Date().toISOString()
34
- atomicWriteJson(fp, record)
35
-
36
- const indexPath = path.join(dir, "index.json")
37
- let index = readJson(indexPath, [])
38
- if (Array.isArray(index)) {
39
- const idx = index.findIndex(e => e?.id === record.id)
40
- if (idx >= 0) { index[idx].status = "expired"; index[idx].updated_at = record.updated_at }
41
- atomicWriteJson(indexPath, index)
42
- }
43
- swept++
44
- }
45
- }
46
- return swept
47
- }
48
-
49
- function sweepBoilerplateCheckpoints() {
50
- const dir = path.join(getMemoryRoot(), "checkpoints")
51
- let files = []
52
- try { files = fs.readdirSync(dir).filter(f => f.endsWith(".json") && f !== "index.json") } catch { return 0 }
53
- const cutoff = Date.now() - 86400000
54
- let archived = 0
55
- for (const f of files) {
56
- const fp = path.join(dir, f)
57
- const record = readJson(fp, null)
58
- if (!record || record.status === "expired" || record.status === "archived") continue
59
- if (!BOILERPLATE_SUMMARY.test(record.summary || "")) continue
60
- const ts = Date.parse(record.updated_at || record.created_at || 0)
61
- if (Number.isNaN(ts) || ts > cutoff) continue
62
- record.status = "archived"
63
- record.archived_at = new Date().toISOString()
64
- record.updated_at = record.archived_at
65
- atomicWriteJson(fp, record)
66
-
67
- const indexPath = path.join(dir, "index.json")
68
- let index = readJson(indexPath, [])
69
- if (Array.isArray(index)) {
70
- const idx = index.findIndex(e => e?.id === record.id)
71
- if (idx >= 0) { index[idx].status = "archived"; index[idx].updated_at = record.updated_at }
72
- atomicWriteJson(indexPath, index)
73
- }
74
- archived++
75
- }
76
- return archived
77
- }
78
-
79
- function loadMemoryRecord(root, className, entry) {
80
- const recordPath = path.join(root, "memory", className, `${entry.id}.json`)
81
- const record = readJson(recordPath, null)
82
- if (record && typeof record === "object") return record
83
- return {
84
- ...entry,
85
- class: className,
86
- scope: entry.scope || "harness",
87
- }
88
- }
89
-
90
- function formatContext(memory) {
91
- const parts = []
92
- if (memory.checkpoint) parts.push(`## Active Checkpoint\n${memory.checkpoint.summary || "N/A"}\n`)
93
- if (memory.constraints.length) parts.push(`## Active Constraints\n${memory.constraints.map(c => `- ${c.summary}`).join("\n")}\n`)
94
- if (memory.decisions.length) parts.push(`## Recent Decisions\n${memory.decisions.slice(0, 3).map(d => `- ${d.summary}`).join("\n")}\n`)
95
- if (memory.mistakes.length) parts.push(`## Recent Mistakes (top ${Math.min(3, memory.mistakes.length)})\n${memory.mistakes.slice(0, 3).map(m => `- ${m.summary}`).join("\n")}\n`)
96
- return parts.join("\n")
97
- }
98
-
99
- function validateMemoryRecord(record) {
100
- const required = ["id", "class", "scope", "summary", "status"]
101
- const missing = required.filter(r => !record[r])
102
- if (missing.length) return false
103
- if (record.confidence !== undefined && record.confidence < 0.3) return false
104
- return true
105
- }
106
-
107
- function formatBacklogNudge(candidates) {
108
- if (!candidates.length) return null
109
- const count = candidates.length
110
- const top = candidates.slice(0, 3).map((c, i) => `${i + 1}. ${c.summary || c.title || "unnamed candidate"}`).join("\n")
111
- return [
112
- `## Pending Skill Candidates (${count} open)`,
113
- `The skill creation loop has ${count} unprocessed candidates.`,
114
- `CRITICAL: Process the oldest candidate via /learn on this session start.`,
115
- `Top candidates:`,
116
- top,
117
- `Trigger: /learn to create skills from these sessions.`,
118
- `If none are skill-worthy, close them via ohc_save with status:"closed".`
119
- ].join("\n")
120
- }
121
-
122
- function formatMemoryWriteGap(memory) {
123
- const gaps = []
124
- if (memory.constraints.length === 0) gaps.push("constraints")
125
- if (memory.decisions.length === 0) gaps.push("decisions")
126
- if (!gaps.length) return null
127
- return `## Memory Write Gap\nThese memory classes are empty: ${gaps.join(", ")}. Write at least one ${gaps[0]} this session.`
128
- }
129
-
130
- export async function refreshRecallCache(projectKey, directory) {
131
- await loadMemoryAndWriteCache(projectKey, directory)
132
- }
133
-
134
- async function loadMemoryAndWriteCache(projectKey, directory) {
135
- sweepStaleRecords()
136
- sweepBoilerplateCheckpoints()
137
- const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
138
- if (!fs.existsSync(SENTINEL)) {
139
- const oldMemory = path.join(OLD_BASE, "memory")
140
- const oldCache = path.join(oldMemory, "recall")
141
- if (fs.existsSync(oldMemory)) {
142
- fs.cpSync(oldMemory, getMemoryRoot(), { recursive: true })
143
- if (fs.existsSync(oldCache)) {
144
- fs.mkdirSync(getRecallRoot(), { recursive: true })
145
- const files = fs.readdirSync(oldCache).filter(f => f.endsWith(".json"))
146
- for (const f of files) fs.cpSync(path.join(oldCache, f), path.join(getRecallRoot(), f))
147
- }
148
- fs.rmSync(oldMemory, { recursive: true, force: true })
149
- }
150
- const oldRuntime = path.join(OLD_BASE, "runtime")
151
- if (fs.existsSync(oldRuntime)) {
152
- fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
153
- fs.rmSync(oldRuntime, { recursive: true, force: true })
154
- }
155
- const oldArchive = path.join(OLD_BASE, "archive")
156
- if (fs.existsSync(oldArchive)) {
157
- fs.rmSync(oldArchive, { recursive: true, force: true })
158
- }
159
- fs.mkdirSync(path.dirname(SENTINEL), { recursive: true })
160
- fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
161
- }
162
-
163
- const memory = { constraints: [], decisions: [], mistakes: [], checkpoint: null, pendingSkillCandidates: [] }
164
- const fingerprint = buildEnvironmentFingerprint(getDataRoot(), directory, { name: projectKey })
165
-
166
- const constraintsIndex = readJson(path.join(getMemoryRoot(), "constraints", "index.json"), [])
167
- if (Array.isArray(constraintsIndex)) memory.constraints = constraintsIndex.filter(e => e.status === "active")
168
-
169
- const decisionsIndex = readJson(path.join(getMemoryRoot(), "decisions", "index.json"), [])
170
- if (Array.isArray(decisionsIndex)) {
171
- memory.decisions = decisionsIndex
172
- .filter(e => e.status === "active")
173
- .map(entry => loadMemoryRecord(getDataRoot(), "decisions", entry))
174
- .filter(validateMemoryRecord)
175
- }
176
-
177
- const allMistakes = readJsonl(path.join(getMemoryRoot(), "mistakes", "mistakes.jsonl"))
178
- if (allMistakes.length) memory.mistakes = allMistakes.filter(e => e.status === "active").slice(0, 5)
179
-
180
- const checkpointIndex = readJson(path.join(getMemoryRoot(), "checkpoints", "index.json"), [])
181
- if (Array.isArray(checkpointIndex) && checkpointIndex.length > 0) {
182
- const latest = checkpointIndex.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0]
183
- memory.checkpoint = readJson(path.join(getMemoryRoot(), "checkpoints", `${latest.id}.json`), null)
184
- }
185
-
186
- const backlogIndex = readJson(path.join(getMemoryRoot(), "backlog", "index.json"), [])
187
- if (Array.isArray(backlogIndex)) {
188
- memory.pendingSkillCandidates = backlogIndex.filter(e =>
189
- e.status === "open" && Array.isArray(e.tags) && e.tags.includes("skill-candidate")
190
- )
191
- }
192
-
193
- const contextParts = []
194
- const baseContext = formatContext(memory)
195
- if (baseContext) contextParts.push(baseContext)
196
- const backlogNudge = formatBacklogNudge(memory.pendingSkillCandidates)
197
- if (backlogNudge) contextParts.push(backlogNudge)
198
- const writeGap = formatMemoryWriteGap(memory)
199
- if (writeGap) contextParts.push(writeGap)
200
-
201
- const context = contextParts.join("\n\n")
202
- const boundedContext = context ? truncateText(context, 12000) : null
203
-
204
- const cacheDir = getRecallRoot()
205
- fs.mkdirSync(cacheDir, { recursive: true })
206
- atomicWriteJson(path.join(cacheDir, "cache.json"), sanitizeRecord({
207
- context: boundedContext,
208
- project: projectKey,
209
- trust_mode: isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS) ? "project" : "global",
210
- harness_root: getDataRoot(),
211
- project_root: directory,
212
- updated_at: new Date().toISOString(),
213
- fingerprint,
214
- freshness_marker: {
215
- updated_at: new Date().toISOString(),
216
- ttl_ms: 1800000,
217
- },
218
- stats: {
219
- constraints: memory.constraints.length,
220
- decisions: memory.decisions.length,
221
- mistakes: memory.mistakes.length,
222
- has_checkpoint: !!memory.checkpoint,
223
- pending_skill_candidates: memory.pendingSkillCandidates.length
224
- }
225
- }, { maxStringLength: 4000 }))
226
- }
227
-
228
- export const AutorecallPlugin = async ({ project, directory }) => {
229
- return {
230
- event: async ({ event }) => {
231
- if (event.type === "session.created") {
232
- const projectKey = project?.name || path.basename(directory)
233
- await loadMemoryAndWriteCache(projectKey, directory)
234
- }
235
- },
236
- }
237
- }