dsh-engramory 0.2.0 → 0.2.1

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 (3) hide show
  1. package/README.md +27 -19
  2. package/index.js +35 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -18,14 +18,17 @@ because the index loads every session and everything past the cap silently stops
18
18
  recalled. On most hosts that limit is rules plus a checker the agent has to remember to
19
19
  run. dsh exposes `ctx.tools.guard()` — a synchronous, **monotonic** refusal: once a guard
20
20
  returns a reason, no later listener can turn it back into an allow. So here the limit is
21
- enforced, not requested — **once the plugin is actually installed and running**; a
22
- profile install currently fails upstream (see "Known limits"), and until then the cap on
23
- dsh is still rules plus the checker. Outside Claude Code, this is the only host where a
24
- deterministic cap is even possible.
25
-
26
- **The protocol arrives with the plugin.** `ctx.skills.register()` contributes the skill at
27
- runtime, so it does not depend on landing files in one of the five skill roots dsh scans —
28
- a path that is easy to get subtly wrong and fails silently when you do.
21
+ enforced, not requested. Use **0.2.1 or later**: 0.2.0 shipped before profile installs
22
+ worked upstream and carried an `inject` declaration this Cordis does not accept — it
23
+ installed but never activated (issue #8). Outside Claude Code, dsh is the first host with
24
+ the shim actually written (a few others expose equivalent pre-write seams — see
25
+ PORTING.md — but have no shim yet).
26
+
27
+ **The protocol arrives with the plugin.** The skill is registered at runtime through
28
+ dsh's skill registry — via a reactive `ctx.inject(['skills'], …)` child, so a profile
29
+ without a registry still gets the cap and one whose registry activates late still gets
30
+ the skill — which means it does not depend on landing files in one of the five skill
31
+ roots dsh scans, a path that is easy to get subtly wrong and fails silently when you do.
29
32
 
30
33
  ## Install
31
34
 
@@ -91,18 +94,23 @@ newline, so an index sitting exactly at the cap stays writable.
91
94
  index cannot be grown further, and a shrinking write always passes.
92
95
  - **The tool roster follows dsh's documented tool-fs contract** (`write`/`edit` with
93
96
  `file_path`, `str_replace_editor` with `path`) and is deliberately conservative:
94
- unknown tools pass. Until the plugin runs inside a real profile this mapping is
95
- documentation-verified only.
96
- - **Not yet verified end to end inside dsh.** The guard's decision table is covered by
97
- `node --test` (20 cases, run in Engramory's CI), and the wiring for the AGENTS.md block
98
- and skill discovery was dogfooded against a live `deepseek-v4-flash` session. But
99
- installing a *third-party plugin* into a profile currently fails on dsh 0.1.0-rc.6:
100
- `dsh plugin` shells out to `pnpm` (not bundled), and a direct `pnpm add` then dies on
101
- `ERR_PNPM_FETCH_404` for `@deepseek-ai/dsh-type-meta`, a package the published tree
102
- depends on but which is not on the registry. That is a preview-packaging problem
103
- upstream, not a plugin defect; this note goes away once a profile install resolves.
97
+ unknown tools pass.
98
+ - **0.2.0 never activated; 0.2.1 is the first version that runs.** While the rc.6
99
+ preview bug blocked all third-party profile installs upstream, the only coverage a
100
+ plugin could have was mocks — and 0.2.0's mock hid two activation bugs: an
101
+ older-Cordis `{ required, optional }` inject shape (read as waiting for services
102
+ literally named `required`/`optional`, pending forever) and a bare `ctx.skills` read
103
+ of an undeclared service (throws under Cordis' reflective context). The first field
104
+ install caught both the moment installs became possible (issue #8). 0.2.1 fixes them,
105
+ verified end to end on a live dsh 0.1.0-rc.7 web profile (0.2.0 reproduces the boot
106
+ failure byte for byte; 0.2.1 boots and serves) and against the vendored
107
+ `@deepseek-ai/cordis` resolver (activation with, without, and with a late-mounted
108
+ skill registry). The test mock now mirrors the reflective-context access rules, and
109
+ the guard's decision table stays covered by `node --test` (22 cases, run in
110
+ Engramory's CI).
104
111
  - dsh is a developer preview and its plugin API can change. This plugin deliberately
105
- touches only `ctx.tools.guard()` and `ctx.skills.register()` so it stays cheap to fix.
112
+ touches only `ctx.tools.guard()` and a reactive `ctx.inject(['skills'], …)` child
113
+ that registers the skill, so it stays cheap to fix.
106
114
 
107
115
  ## License
108
116
 
package/index.js CHANGED
@@ -7,8 +7,9 @@
7
7
  * monotonic refusal — once a guard returns a reason, no later waterfall
8
8
  * listener can turn it back into an allow. That makes it a stronger seam than
9
9
  * most hosts expose, and it is why the 200-line / 25 KB limit can be enforced
10
- * here rather than merely asked for. Everywhere except Claude Code, Engramory's
11
- * cap degrades to "rules plus a checker the agent has to remember to run".
10
+ * here rather than merely asked for. Without a shim like this one, everywhere
11
+ * except Claude Code the cap degrades to "rules plus a checker the agent has to
12
+ * remember to run".
12
13
  * 2. Skill delivery that does not depend on install paths. Registering at runtime
13
14
  * sidesteps the five-root scan entirely, so the protocol is present because the
14
15
  * plugin is loaded, not because a directory happened to be right.
@@ -27,9 +28,14 @@ import { basename } from 'node:path'
27
28
 
28
29
  export const name = 'engramory'
29
30
 
30
- // `tools` carries the cap and is required. `skills` is optional so the plugin still
31
- // loads (and still caps) on a profile that mounts no skill registry.
32
- export const inject = { required: ['tools'], optional: ['skills'] }
31
+ // `tools` carries the cap and is the plugin's only declared dependency. Cordis reads
32
+ // `inject` as an array of service names (or an object KEYED by them) and treats every
33
+ // entry as a hard wait — the `{ required, optional }` shape is older-Cordis syntax that
34
+ // the build dsh vendors resolves as services literally named "required"/"optional",
35
+ // which never appear, so the plugin sat pending forever (issue #8). `skills` is
36
+ // deliberately NOT declared: the cap must mount even on a profile with no skill
37
+ // registry, so apply() probes it at runtime instead.
38
+ export const inject = ['tools']
33
39
 
34
40
  /** Mirrors hooks/engramory_index_guard.py — the caps are the protocol's, not this port's. */
35
41
  const DEFAULT_INDEX_NAME = 'MEMORY.md'
@@ -57,19 +63,30 @@ export function apply(ctx, config = {}) {
57
63
 
58
64
  ctx.tools.guard((exec) => refuseOversizedIndex(exec, settings))
59
65
 
60
- if (ctx.skills && config.registerSkill !== false) {
61
- const skill = config.skill ?? builtinSkillBody()
62
- ctx.skills.register({
63
- name: 'engramory',
64
- description:
65
- 'Curated file-based long-term memory: recall through MEMORY.md at the start of ' +
66
- 'a task, save durable user/feedback/project/reference facts, and sync before ' +
67
- 'compacting or opening a fresh thread.',
68
- whenToUse:
69
- 'Starting or resuming work, learning something durable worth a future session, ' +
70
- 'or approaching a compact/clear/new-thread boundary.',
71
- source: 'runtime',
72
- content: skill,
66
+ // The skill registry is optional AND may activate after this plugin — fiber order
67
+ // between unrelated providers is not guaranteed, so a one-shot probe here would
68
+ // silently skip registration on such boots. ctx.inject() is Cordis' reactive form:
69
+ // the callback runs once `skills` is available (however late), re-runs if the
70
+ // registry reloads, and never makes the cap wait. A bare `ctx.skills` read at THIS
71
+ // level would throw (undeclared service — issue #8's second crash); inside the
72
+ // callback the service is declared, so the read is legal.
73
+ if (config.registerSkill !== false) {
74
+ ctx.inject(['skills'], (inner) => {
75
+ const skill = config.skill ?? builtinSkillBody()
76
+ // effect() ties the registration to the child fiber: it is disposed when the
77
+ // registry unloads, so a reload cannot accumulate duplicate registrations.
78
+ inner.effect(() => inner.skills.register({
79
+ name: 'engramory',
80
+ description:
81
+ 'Curated file-based long-term memory: recall through MEMORY.md at the start of ' +
82
+ 'a task, save durable user/feedback/project/reference facts, and sync before ' +
83
+ 'compacting or opening a fresh thread.',
84
+ whenToUse:
85
+ 'Starting or resuming work, learning something durable worth a future session, ' +
86
+ 'or approaching a compact/clear/new-thread boundary.',
87
+ source: 'runtime',
88
+ content: skill,
89
+ }))
73
90
  })
74
91
  }
75
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-engramory",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Curated file-based long-term memory for DeepSeek Harness — human-readable markdown notes, one plain-file store shared across hosts (git-ignored when it lives inside a project repo), and a deterministic index cap enforced by ctx.tools.guard()",
5
5
  "keywords": [
6
6
  "dsh-plugin",