openhermes 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,7 +97,7 @@ The LLM reads rules on demand via the injected paths. Memory directories auto-cr
97
97
 
98
98
  ## Memory Architecture
99
99
 
100
- Nine memory classes, all schema-validated before persistence, stored at `~/.config/opencode/openhermes/memory/`:
100
+ Nine memory classes, all schema-validated before persistence, stored at `~/.local/share/opencode/openhermes/memory/`:
101
101
 
102
102
  | Class | Format | Purpose |
103
103
  |-------|--------|---------|
@@ -111,6 +111,14 @@ Nine memory classes, all schema-validated before persistence, stored at `~/.conf
111
111
  | `verification_receipt` | JSON | Cached verification results keyed by artifact fingerprint |
112
112
  | `recall` | JSON | Session-start cache aggregating active state for compaction injection |
113
113
 
114
+ OpenHermes follows the same storage contract as OpenCode itself — see [OpenCode docs on storage](https://opencode.ai/docs/troubleshooting/#storage):
115
+
116
+ | What | Where |
117
+ |---|---|
118
+ | Config (schemas, archive) | `~/.config/opencode/openhermes/` |
119
+ | Durable memory + runtime state | `~/.local/share/opencode/openhermes/` |
120
+ | Derived recall cache | `~/.cache/opencode/openhermes/recall/` |
121
+
114
122
  **Runtime hardening**: All records pass through `sanitizeRecord()` (strips `__proto__`, `constructor`, `prototype`), `redactSensitiveText()` (strips bearer tokens, API keys, passwords), and `truncateText()` before persistence. Schema validation gate runs before every write.
115
123
 
116
124
  ---
@@ -153,7 +161,7 @@ permission.replied
153
161
 
154
162
  ## Bundled Harness
155
163
 
156
- The full OpenHermes framework ships inside the package — 45 files across 6 directories:
164
+ The full OpenHermes framework ships inside the package — 60 files across 6 directories:
157
165
 
158
166
  ```
159
167
  harness/
@@ -190,7 +198,7 @@ harness/
190
198
  ├── prompts/ (7 files)
191
199
  │ # Subagent prompt templates: architect, build-error-resolver,
192
200
  │ # code-reviewer, e2e-runner, explore, planner, security-reviewer
193
- └── commands/ (8 files)
201
+ └── commands/ (7 files)
194
202
  # Slash command templates: build-fix, code-review, doctor,
195
203
  # learn, memory-search, plan, security
196
204
  ```
package/autorecall.mjs CHANGED
@@ -2,21 +2,7 @@ import path from "node:path"
2
2
  import os from "node:os"
3
3
  import fs from "node:fs"
4
4
  import { atomicWriteJson, fingerprintEnvironment, isTruthy, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
5
-
6
- function getHarnessRoot(directory) {
7
- const home = process.env.USERPROFILE || os.homedir()
8
- const configRoot = path.join(home, ".config", "opencode")
9
- const projectHarness = path.join(directory, ".opencode", "openhermes")
10
- const projectMemory = path.join(projectHarness, "memory")
11
- if (isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) {
12
- try {
13
- fs.accessSync(projectMemory)
14
- return projectHarness
15
- } catch {
16
- }
17
- }
18
- return path.join(configRoot, "openhermes")
19
- }
5
+ import { getDataRoot, getCacheRoot, getMemoryRoot, getRecallRoot, getRuntimeRoot } from "./lib/paths.mjs"
20
6
 
21
7
  function readJson(fp, fallback) {
22
8
  try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
@@ -88,31 +74,52 @@ function formatMemoryWriteGap(memory) {
88
74
  return `## Memory Write Gap\nThese memory classes are empty: ${gaps.join(", ")}. Write at least one ${gaps[0]} this session.`
89
75
  }
90
76
 
91
- async function loadMemoryAndWriteCache(harnessRoot, projectKey, directory) {
77
+ async function loadMemoryAndWriteCache(projectKey, directory) {
78
+ const OLD_MEMORY = path.join(os.homedir(), ".config", "opencode", "openhermes", "memory")
79
+ const OLD_CACHE = path.join(os.homedir(), ".config", "opencode", "openhermes", "memory", "recall")
80
+ const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
81
+ if (!fs.existsSync(SENTINEL)) {
82
+ if (fs.existsSync(OLD_MEMORY)) {
83
+ fs.cpSync(OLD_MEMORY, getMemoryRoot(), { recursive: true })
84
+ fs.rmSync(OLD_MEMORY, { recursive: true, force: true })
85
+ }
86
+ if (fs.existsSync(OLD_CACHE)) {
87
+ fs.mkdirSync(getRecallRoot(), { recursive: true })
88
+ const files = fs.readdirSync(OLD_CACHE).filter(f => f.endsWith(".json"))
89
+ for (const f of files) fs.cpSync(path.join(OLD_CACHE, f), path.join(getRecallRoot(), f))
90
+ }
91
+ const oldRuntime = path.join(os.homedir(), ".config", "opencode", "openhermes", "runtime")
92
+ if (fs.existsSync(oldRuntime)) {
93
+ fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
94
+ fs.rmSync(oldRuntime, { recursive: true, force: true })
95
+ }
96
+ fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
97
+ }
98
+
92
99
  const memory = { constraints: [], decisions: [], mistakes: [], checkpoint: null, pendingSkillCandidates: [] }
93
- const fingerprint = buildEnvironmentFingerprint(harnessRoot, directory, projectKey)
100
+ const fingerprint = buildEnvironmentFingerprint(getDataRoot(), directory, projectKey)
94
101
 
95
- const constraintsIndex = readJson(path.join(harnessRoot, "memory", "constraints", "index.json"), [])
102
+ const constraintsIndex = readJson(path.join(getMemoryRoot(), "constraints", "index.json"), [])
96
103
  if (Array.isArray(constraintsIndex)) memory.constraints = constraintsIndex.filter(e => e.status === "active")
97
104
 
98
- const decisionsIndex = readJson(path.join(harnessRoot, "memory", "decisions", "index.json"), [])
105
+ const decisionsIndex = readJson(path.join(getMemoryRoot(), "decisions", "index.json"), [])
99
106
  if (Array.isArray(decisionsIndex)) {
100
107
  memory.decisions = decisionsIndex
101
108
  .filter(e => e.status === "active")
102
- .map(entry => loadMemoryRecord(harnessRoot, "decisions", entry))
109
+ .map(entry => loadMemoryRecord(getDataRoot(), "decisions", entry))
103
110
  .filter(validateMemoryRecord)
104
111
  }
105
112
 
106
- const allMistakes = readJsonl(path.join(harnessRoot, "memory", "mistakes", "mistakes.jsonl"))
113
+ const allMistakes = readJsonl(path.join(getMemoryRoot(), "mistakes", "mistakes.jsonl"))
107
114
  if (allMistakes.length) memory.mistakes = allMistakes.filter(e => e.status === "active").slice(0, 5)
108
115
 
109
- const checkpointIndex = readJson(path.join(harnessRoot, "memory", "checkpoints", "index.json"), [])
116
+ const checkpointIndex = readJson(path.join(getMemoryRoot(), "checkpoints", "index.json"), [])
110
117
  if (Array.isArray(checkpointIndex) && checkpointIndex.length > 0) {
111
118
  const latest = checkpointIndex.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0]
112
- memory.checkpoint = readJson(path.join(harnessRoot, "memory", "checkpoints", `${latest.id}.json`), null)
119
+ memory.checkpoint = readJson(path.join(getMemoryRoot(), "checkpoints", `${latest.id}.json`), null)
113
120
  }
114
121
 
115
- const backlogIndex = readJson(path.join(harnessRoot, "memory", "backlog", "index.json"), [])
122
+ const backlogIndex = readJson(path.join(getMemoryRoot(), "backlog", "index.json"), [])
116
123
  if (Array.isArray(backlogIndex)) {
117
124
  memory.pendingSkillCandidates = backlogIndex.filter(e =>
118
125
  e.status === "open" && (e.summary || "").includes("skill-candidate")
@@ -130,13 +137,13 @@ async function loadMemoryAndWriteCache(harnessRoot, projectKey, directory) {
130
137
  const context = contextParts.join("\n\n")
131
138
  const boundedContext = context ? truncateText(context, 12000) : null
132
139
 
133
- const cacheDir = path.join(harnessRoot, "memory", "recall")
140
+ const cacheDir = getRecallRoot()
134
141
  fs.mkdirSync(cacheDir, { recursive: true })
135
142
  atomicWriteJson(path.join(cacheDir, "cache.json"), sanitizeRecord({
136
143
  context: boundedContext,
137
144
  project: projectKey,
138
145
  trust_mode: isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS) ? "project" : "global",
139
- harness_root: harnessRoot,
146
+ harness_root: getDataRoot(),
140
147
  project_root: directory,
141
148
  updated_at: new Date().toISOString(),
142
149
  fingerprint,
@@ -158,9 +165,8 @@ export const AutorecallPlugin = async ({ project, directory }) => {
158
165
  return {
159
166
  event: async ({ event }) => {
160
167
  if (event.type === "session.created") {
161
- const harnessRoot = getHarnessRoot(directory)
162
168
  const projectKey = project?.name || path.basename(directory)
163
- await loadMemoryAndWriteCache(harnessRoot, projectKey, directory)
169
+ await loadMemoryAndWriteCache(projectKey, directory)
164
170
  }
165
171
  },
166
172
  }
package/bootstrap.mjs CHANGED
@@ -35,7 +35,7 @@ Snapshot before mutation. Never delete unrelated files. Never assume \`%USERPROF
35
35
  | Category | Items |
36
36
  |----------|-------|
37
37
  | **Native tools** | \`read\`, \`write\`, \`edit\`, \`glob\`, \`grep\`, \`bash\`, \`task\`, \`webfetch\`, \`skill\`, \`todowrite\`, \`todoread\` |
38
- | **MCP: openhermes-memory** | \`hm_put\`, \`hm_get\`, \`hm_list\`, \`hm_latest\`, \`hm_search\` |
38
+ | **In-process tools** | \`hm_put\`, \`hm_get\`, \`hm_list\`, \`hm_latest\`, \`hm_search\` |
39
39
  | **Memory recall cache** | \`openhermes/memory/recall/cache.json\` — read on session start, no MCP round-trip |
40
40
  | **Subagents** | \`explore\` (read-only), \`general\` (multi-step), \`architect\`, \`planner\`, \`build-error-resolver\`, \`code-reviewer\`, \`security-reviewer\`, \`e2e-runner\` |
41
41
  | **Plugins** | \`curator\` (checkpoints, mistakes, audit, compaction), \`autorecall\` (recall cache on \`session.created\`), \`skill-builder\` (complex session detection) |
package/curator.mjs CHANGED
@@ -5,6 +5,7 @@ import { findUnsupportedSchemaKeywords, validateSchema } from "./lib/schema-vali
5
5
  import { atomicWriteJson, fingerprintEnvironment, fingerprintFile, isTruthy, redactSensitiveText, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
6
6
  import { fileURLToPath } from "node:url"
7
7
  import { dirname } from "node:path"
8
+ import { getConfigRoot, getDataRoot, getMemoryRoot, getRuntimeRoot, getArchiveRoot, getSchemaRoot } from "./lib/paths.mjs"
8
9
 
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
11
 
@@ -19,21 +20,6 @@ function curatorLog(message) {
19
20
  process.stderr.write(`${message}\n`)
20
21
  }
21
22
 
22
- function getHarnessRoot(directory) {
23
- const home = process.env.USERPROFILE || os.homedir()
24
- const configRoot = path.join(home, ".config", "opencode")
25
- const projectHarness = path.join(directory, ".opencode", "openhermes")
26
- const projectMemory = path.join(projectHarness, "memory")
27
- if (isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) {
28
- try {
29
- fs.accessSync(projectMemory)
30
- return projectHarness
31
- } catch {
32
- }
33
- }
34
- return path.join(configRoot, "openhermes")
35
- }
36
-
37
23
  function readJson(fp, fallback) {
38
24
  try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
39
25
  }
@@ -92,8 +78,7 @@ function updateLoopState(root, patch) {
92
78
  }
93
79
 
94
80
  function loadSchema(classId) {
95
- const home = process.env.USERPROFILE || os.homedir()
96
- const fp = path.join(home, ".config", "opencode", "openhermes", "schemas", `${classId}.schema.json`)
81
+ const fp = path.join(getSchemaRoot(), `${classId}.schema.json`)
97
82
  try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch {}
98
83
  const bundled = path.join(__dirname, "schemas", `${classId}.schema.json`)
99
84
  try { return JSON.parse(fs.readFileSync(bundled, "utf8")) } catch { return null }
@@ -242,11 +227,16 @@ function writeMistakeRecord(root, project, directory, error) {
242
227
  project: project?.name || path.basename(directory),
243
228
  environment_fingerprint: environmentFingerprint,
244
229
  }
245
- const dir = path.join(root, "memory", "mistakes")
230
+ const dir = path.join(getMemoryRoot(), "mistakes")
246
231
  fs.mkdirSync(dir, { recursive: true })
247
232
  const fp = path.join(dir, "mistakes.jsonl")
248
- const line = JSON.stringify(sanitizeRecord(record, { maxStringLength: 4000 }))
249
- try { fs.appendFileSync(fp, line + "\n") } catch { fs.writeFileSync(fp, line + "\n") }
233
+ const safeRecord = sanitizeRecord(record, { maxStringLength: 4000 })
234
+ let entries = []
235
+ try { entries = fs.readFileSync(fp, "utf8").trim().split("\n").filter(Boolean).map(l => JSON.parse(l)) } catch {}
236
+ const idx = entries.findIndex(e => e.id === safeRecord.id)
237
+ if (idx >= 0) entries[idx] = safeRecord; else entries.push(safeRecord)
238
+ const text = entries.map(e => JSON.stringify(e)).join("\n")
239
+ fs.writeFileSync(fp, text ? text + "\n" : "", "utf8")
250
240
  curatorLog(`[curator] mistake logged: ${id} - ${safeLogMessage(errorMsg, 80)}`)
251
241
  return id
252
242
  }
@@ -298,7 +288,7 @@ function writeVerificationReceipt(root, project, directory, checkpointId) {
298
288
 
299
289
  async function handleSessionIdle(directory, project) {
300
290
  try {
301
- const root = getHarnessRoot(directory)
291
+ const root = getDataRoot()
302
292
  const checkpointId = await writeCheckpoint(root, project, directory, "session.idle", null)
303
293
  if (checkpointId) {
304
294
  writeVerificationReceipt(root, project, directory, checkpointId)
@@ -310,7 +300,7 @@ async function handleSessionIdle(directory, project) {
310
300
 
311
301
  async function handleSessionCompacted(directory, project) {
312
302
  try {
313
- const root = getHarnessRoot(directory)
303
+ const root = getDataRoot()
314
304
  const ts = new Date().toISOString()
315
305
  updateLoopState(root, {
316
306
  status: "compacted",
@@ -326,7 +316,7 @@ async function handleSessionCompacted(directory, project) {
326
316
 
327
317
  async function handleSessionError(directory, project, event) {
328
318
  try {
329
- const root = getHarnessRoot(directory)
319
+ const root = getDataRoot()
330
320
  const ts = new Date().toISOString()
331
321
  const errorMsg = typeof event?.error === "object" && event.error !== null
332
322
  ? (event.error.message || JSON.stringify(event.error).slice(0, 200))
@@ -347,7 +337,7 @@ async function handleSessionError(directory, project, event) {
347
337
 
348
338
  async function handlePermissionReplied(directory, project, event) {
349
339
  try {
350
- const root = getHarnessRoot(directory)
340
+ const root = getDataRoot()
351
341
  const ts = new Date().toISOString()
352
342
  const id = `audit_perm_${ts.replace(/[:.]/g, "-")}`
353
343
  const environmentFingerprint = buildEnvironmentFingerprint(root, directory, project)
@@ -387,7 +377,14 @@ async function handlePermissionReplied(directory, project, event) {
387
377
  environment_fingerprint: environmentFingerprint,
388
378
  }
389
379
  const safeRecord = sanitizeRecord(record, { maxStringLength: 4000 })
390
- if (!validateRecordAgainstSchema(safeRecord)) return
380
+ const auditSchema = loadSchema("audit") || readJson(path.join(__dirname, "schemas", "audit.schema.json"), null)
381
+ if (auditSchema) {
382
+ const unsupported = findUnsupportedSchemaKeywords(auditSchema)
383
+ if (!unsupported.length) {
384
+ const errors = validateSchema(auditSchema, safeRecord, "$")
385
+ if (errors.length) return
386
+ }
387
+ }
391
388
  const dir = path.join(root, "memory", "audits")
392
389
  fs.mkdirSync(dir, { recursive: true })
393
390
  atomicWriteJson(path.join(dir, `${id}.json`), safeRecord)
@@ -401,6 +398,20 @@ async function handlePermissionReplied(directory, project, event) {
401
398
  export const CuratorPlugin = async ({ project, directory }) => {
402
399
  return {
403
400
  event: async ({ event }) => {
401
+ const OLD_DATA = path.join(os.homedir(), ".config", "opencode", "openhermes", "memory")
402
+ const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
403
+ if (!fs.existsSync(SENTINEL)) {
404
+ if (fs.existsSync(OLD_DATA)) {
405
+ fs.cpSync(OLD_DATA, getMemoryRoot(), { recursive: true })
406
+ fs.rmSync(OLD_DATA, { recursive: true, force: true })
407
+ }
408
+ const oldRuntime = path.join(os.homedir(), ".config", "opencode", "openhermes", "runtime")
409
+ if (fs.existsSync(oldRuntime)) {
410
+ fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
411
+ fs.rmSync(oldRuntime, { recursive: true, force: true })
412
+ }
413
+ fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
414
+ }
404
415
  if (event.type === "session.idle") {
405
416
  await handleSessionIdle(directory, project)
406
417
  } else if (event.type === "session.compacted") {
@@ -415,7 +426,7 @@ export const CuratorPlugin = async ({ project, directory }) => {
415
426
  },
416
427
  "experimental.session.compacting": async (input, output) => {
417
428
  try {
418
- const root = getHarnessRoot(directory)
429
+ const root = getDataRoot()
419
430
  const projectKey = project?.name || path.basename(directory)
420
431
  const checkpointIndex = readJson(path.join(root, "memory", "checkpoints", "index.json"), [])
421
432
  const constraintsIndex = readJson(path.join(root, "memory", "constraints", "index.json"), [])
@@ -6,11 +6,11 @@ import os from "os"
6
6
 
7
7
  import { atomicWriteJson, fingerprintEnvironment, sanitizeRecord, truncateText } from "./hardening.mjs"
8
8
  import { findUnsupportedSchemaKeywords, validateSchema } from "./schema-validator.mjs"
9
+ import { getMemoryRoot, getConfigRoot, getDataRoot, getSchemaRoot, getRuntimeRoot } from "../lib/paths.mjs"
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
12
  const PACKAGE_SCHEMAS = path.resolve(__dirname, "..", "schemas")
12
- const ROOT = path.join(os.homedir(), ".config", "opencode", "openhermes")
13
- const MEMORY_DIR = path.join(ROOT, "memory")
13
+ const MEMORY_DIR = getMemoryRoot()
14
14
 
15
15
  const CLASSES = ["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]
16
16
  const PLURALS = { audit: "audits", checkpoint: "checkpoints", mistake: "mistakes", instinct: "instincts", decision: "decisions", constraint: "constraints", backlog: "backlog", verification_receipt: "verification_receipts" }
@@ -141,7 +141,7 @@ function handlePut(cls, id, dataStr) {
141
141
  const now = new Date().toISOString()
142
142
  const record = { ...parsed, id, class: cls, source: parsed.source ?? "agent", status: parsed.status ?? "active", created_at: parsed.created_at ?? now, updated_at: now }
143
143
 
144
- const schema = readJSON(path.join(PACKAGE_SCHEMAS, `${cls}.schema.json`), null)
144
+ const schema = readJSON(path.join(getSchemaRoot(), `${cls}.schema.json`), null) || readJSON(path.join(PACKAGE_SCHEMAS, `${cls}.schema.json`), null)
145
145
  if (schema) {
146
146
  const unsupported = findUnsupportedSchemaKeywords(schema)
147
147
  if (unsupported.length) return `Unsupported schema keywords: ${unsupported.join(", ")}`
@@ -209,7 +209,7 @@ function handleSearch(query, scope, classes, project, limit) {
209
209
 
210
210
  export const MemoryToolsPlugin = async () => {
211
211
  fs.mkdirSync(MEMORY_DIR, { recursive: true })
212
- fs.mkdirSync(path.join(ROOT, "runtime"), { recursive: true })
212
+ fs.mkdirSync(getRuntimeRoot(), { recursive: true })
213
213
 
214
214
  return {
215
215
  tool: {
package/lib/paths.mjs ADDED
@@ -0,0 +1,52 @@
1
+ import path from "node:path"
2
+ import os from "node:os"
3
+ import fs from "node:fs"
4
+
5
+ const HOME = process.env.USERPROFILE || os.homedir()
6
+ const CONFIG_ROOT = path.join(HOME, ".config", "opencode", "openhermes")
7
+ const DATA_ROOT = path.join(HOME, ".local", "share", "opencode", "openhermes")
8
+ const CACHE_ROOT = path.join(HOME, ".cache", "opencode", "openhermes")
9
+
10
+ function resolveRoot(envVar, fallback) {
11
+ if (!isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) return fallback
12
+ const cwd = process.cwd()
13
+ const project = path.join(cwd, ".opencode", "openhermes")
14
+ try { fs.accessSync(path.join(project, "memory")); return project } catch {}
15
+ return fallback
16
+ }
17
+
18
+ export function getConfigRoot() {
19
+ return resolveRoot("OPENCODE_ALLOW_PROJECT_HARNESS", CONFIG_ROOT)
20
+ }
21
+
22
+ export function getDataRoot() {
23
+ return resolveRoot("OPENCODE_ALLOW_PROJECT_HARNESS", DATA_ROOT)
24
+ }
25
+
26
+ export function getCacheRoot() {
27
+ return CACHE_ROOT
28
+ }
29
+
30
+ export function getMemoryRoot() {
31
+ return path.join(getDataRoot(), "memory")
32
+ }
33
+
34
+ export function getRuntimeRoot() {
35
+ return path.join(getDataRoot(), "runtime")
36
+ }
37
+
38
+ export function getRecallRoot() {
39
+ return path.join(getCacheRoot(), "recall")
40
+ }
41
+
42
+ export function getArchiveRoot() {
43
+ return path.join(getConfigRoot(), "archive")
44
+ }
45
+
46
+ export function getSchemaRoot() {
47
+ return path.join(getConfigRoot(), "schemas")
48
+ }
49
+
50
+ function isTruthy(value) {
51
+ return /^(1|true|yes|on)$/i.test(String(value || ""))
52
+ }
@@ -1,9 +1,9 @@
1
1
  import fs from "fs"
2
2
  import path from "path"
3
3
  import os from "os"
4
+ import { getMemoryRoot, getDataRoot } from "../../lib/paths.mjs"
4
5
 
5
- const ROOT = path.join(os.homedir(), ".config", "opencode", "openhermes")
6
- const MEMORY_DIR = path.join(ROOT, "memory")
6
+ const MEMORY_DIR = getMemoryRoot()
7
7
 
8
8
  const CLASSES = ["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]
9
9
  const PLURALS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhermes",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "OpenHermes plugin suite for OpenCode — autonomous checkpointing, native memory tools, subagent routing, slash commands, and skill-candidate detection.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/skill-builder.mjs CHANGED
@@ -2,17 +2,7 @@ import path from "node:path"
2
2
  import fs from "node:fs"
3
3
  import os from "node:os"
4
4
  import { atomicWriteJson, fingerprintEnvironment, isTruthy, sanitizeRecord } from "./lib/hardening.mjs"
5
-
6
- function getHarnessRoot(directory) {
7
- const home = process.env.USERPROFILE || os.homedir()
8
- const configRoot = path.join(home, ".config", "opencode")
9
- const projectHarness = path.join(directory, ".opencode", "openhermes")
10
- const projectMemory = path.join(projectHarness, "memory")
11
- if (isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) {
12
- try { fs.accessSync(projectMemory); return projectHarness } catch {}
13
- }
14
- return path.join(configRoot, "openhermes")
15
- }
5
+ import { getConfigRoot, getDataRoot, getMemoryRoot } from "./lib/paths.mjs"
16
6
 
17
7
  function readJson(fp, fallback) {
18
8
  try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
@@ -50,7 +40,7 @@ export const SkillBuilderPlugin = async ({ project, directory }) => {
50
40
 
51
41
  if (isComplex) {
52
42
  try {
53
- const root = getHarnessRoot(directory)
43
+ const root = getDataRoot()
54
44
  const ts = new Date().toISOString()
55
45
  const id = `bl_skill_candidate_${ts.replace(/[:.]/g, "-")}`
56
46
  const backlogIndexPath = path.join(root, "memory", "backlog", "index.json")