@lawaty/lacode 0.1.1 → 0.1.3

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.
@@ -16,7 +16,7 @@ document; everything here is testable offline with a stub client and a
16
16
  `mkdtempSync` state directory.
17
17
 
18
18
  **Terminology.** A *slot* is one entry in `presets/free-tier.json`, carrying a
19
- model and a `weight`. A *base* is a pooled agent type (`explore-fast`), and its slot
19
+ model and a `weight`. A *base* is a pooled agent type (`explore`), and its slot
20
20
  holders are `<base>-<slot>`, hidden in `opencode.jsonc`. A *claim* is a running
21
21
  spawn recorded in `claims.<pid>.json`. A *context map* is the five-file
22
22
  `.opencode/context/` codemap described in Epic G.
@@ -45,7 +45,7 @@ spawn recorded in `claims.<pid>.json`. A *context map* is the five-file
45
45
  **Story ID(s) for tests:** US-1
46
46
  **Acceptance criteria:**
47
47
  - [ ] Given the root agent `build` with read/glob/grep denied, When a read-family tool is invoked, Then the call is denied and the agent is pointed at an exploration base rather than executing inline.
48
- - [ ] Given a pooled base such as `explore-fast` is spawned, When the plugin rewrites the spawn, Then the concrete model selected is a slot declared in `presets/free-tier.json`.
48
+ - [ ] Given a pooled base such as `explore` is spawned, When the plugin rewrites the spawn, Then the concrete model selected is a slot declared in `presets/free-tier.json`.
49
49
  - [ ] Given a pool whose slots are all zero-cost, When N exploration spawns run, Then none of them is dispatched to a paid model.
50
50
 
51
51
  ### US-2: One knob selects the exploration/implementation model
@@ -125,9 +125,9 @@ The reconciliation with US-7 is that **acceptance and endorsement are different
125
125
  - [ ] Given a slot names a paid model, When the pool loads, Then the slot is accepted and routed to without a free-tier filter rejecting it.
126
126
  - [ ] Given a model id contains a vendor prefix with slashes (e.g. `openrouter/nvidia/model:free`), When it is split into provider and model id, Then the split is on the first slash only and the id tail is preserved.
127
127
  - [ ] Given more slots are declared than the pool allows, When the pool loads, Then the surplus entries are dropped with a warning naming the cap.
128
- - [ ] Given a provider configured with **no** `whitelist` key, When the config hook binds a slot or a `deepModel` on that provider, Then no `whitelist` is written for it, because an absent whitelist already allows every model the provider offers.
129
- - [ ] Given a provider the pool never names a slot or a `deepModel` on, When the config hook runs, Then that provider entry is left exactly as it was, with no `whitelist` key added.
130
- - [ ] Given a provider that already carries a `whitelist`, When the config hook binds a slot model or a `deepModel` on it, Then the model id is appended and every entry the user wrote is preserved in order.
128
+ - [ ] Given a provider configured with **no** `whitelist` key, When the config hook binds a slot on that provider, Then no `whitelist` is written for it, because an absent whitelist already allows every model the provider offers.
129
+ - [ ] Given a provider the pool never names a slot on, When the config hook runs, Then that provider entry is left exactly as it was, with no `whitelist` key added.
130
+ - [ ] Given a provider that already carries a `whitelist`, When the config hook binds a slot model on it, Then the model id is appended and every entry the user wrote is preserved in order.
131
131
  - [ ] Given any provider after the config hook has run, When its whitelist is inspected, Then it is either absent or non-empty, because an empty allowlist names no model at all and opencode deletes the provider outright.
132
132
 
133
133
  `provider.<name>.whitelist` is an **allowlist**, not a to-do list: opencode
@@ -183,7 +183,8 @@ does not.
183
183
  **Story ID(s) for tests:** US-11
184
184
  **Acceptance criteria:**
185
185
  - [ ] Given a session went busy and is now idle, When the idle event fires, Then a `done` notification is delivered.
186
- - [ ] Given a session errors, When the error event fires, Then an `error` notification is delivered and it bypasses the duplicate-suppression cooldown.
186
+ - [ ] Given a session ends with an unrecognized error, When the error event fires, Then an `error` notification is delivered, it bypasses the duplicate-suppression cooldown, and it carries the error's own message.
187
+ - [ ] Given a recognized rate-limit or quota error, or a user abort, When a session error event fires, Then no failure notification is raised — the pool already handles the former and the user already knows the latter.
187
188
  - [ ] Given a session that never went busy, When it goes idle, Then no notification is sent.
188
189
  - [ ] Given a subagent session or a cartographer session, When it goes idle, Then it is suppressed.
189
190
  - [ ] Given the notify helper exits non-zero or writes to stderr, When the result is evaluated, Then the delivery is reported as failed rather than sent.
@@ -257,7 +258,7 @@ does not.
257
258
  - [ ] Given a body containing `"`, `'`, `;`, `$(…)` or backticks, When the macOS send runs, Then it reaches `display notification` byte-for-byte as data and nothing is executed.
258
259
  - [ ] Given a machine has no notifier for its platform, When a notification is sent, Then the result is a failed delivery with a reason and no exception, and `lacode doctor` names the channel that would run — or reports that there is none — instead of reporting a binary that is not used there.
259
260
  - [ ] Given the CLI runs on macOS, When the send does not complete, Then it is still bounded by a timeout, exactly as on Linux.
260
- - [ ] Given the repository's own shell scripts, When each is classified, Then the doc says which side of the hop it runs on — `bin/oc` and `bin/oc-notify` on the remote host that runs opencode, `bin/oc-notify-receiver` and `bin/oc-sync` on the desktop — and says plainly why the desktop pair is Linux-only (a `notify-send` channel behind a systemd user unit), so a Mac user is not sent to debug an ssh script that was never meant to run on their machine.
261
+ - [ ] Given the notification hop, When it is documented, Then the doc leads with the shipped path — the npm-installed plugin, a direct ssh hop (`ControlMaster`) set up by `lacode tunnel setup`, and the portable `lacode receive` forced command that delivers through the platform channel (`notify-send` or `osascript`) — and marks the older reverse-forward scripts (`bin/oc`, `bin/oc-notify`, `bin/oc-notify-receiver`, `bin/oc-sync`) as the author's legacy tooling, so a Mac user is not sent to debug a script that was never meant to run on their machine.
261
262
  - [ ] Given a machine without GNU `timeout` or with bash older than 4.0, When `bin/oc-sync` runs, Then it refuses **before** touching anything, names each missing prerequisite, and names the `lacode` subcommand to use instead. It must never report a misleading cause: the host list is read with `mapfile`, so without it the run used to die at "no hosts configured" — a confident wrong answer that sends the reader to edit `.env`.
262
263
  - [ ] Given `bin/oc-notify-receiver` runs where `notify-send` does not exist, When it is invoked, Then it exits non-zero and names both the missing channel and the portable path, because its stderr reaches whoever opened the ssh connection and nobody else, and a bare "command not found" reads as a desktop that quietly stopped notifying.
263
264
  - [ ] Given the payload is parsed, When the receiver splits `$SSH_ORIGINAL_COMMAND`, Then it splits on newlines only using constructs that exist in bash 3.2 as well as bash 5, and the suite **proves the split by running the receiver** rather than by finding the word `mapfile` in the file. Under bash 3.2 — the version macOS ships — a bash-4-only builtin left every field at its default, so the popup said "LaCode" with an empty body and no error anywhere: the same silent blank notification the argv bug caused once, reached again through a different door.
@@ -329,17 +330,17 @@ makes agents read it before they explore, then keeps it current on its own.
329
330
 
330
331
  **Story ID(s) for tests:** US-19
331
332
  **Acceptance criteria:**
332
- - [ ] Given a config that defines none of LaCode's agents, When the config hook runs, Then a `context-manager` subagent is injected, and it carries no model of its own, because the pool is the only thing that decides which model runs it.
333
- - [ ] Given the injected agent set, When the slot variants are built, Then a hidden `context-manager-<slot>` variant exists for every slot the pool can hand out, so a cartography run borrows shared capacity instead of a dedicated model.
333
+ - [ ] Given a config that defines none of LaCode's agents, When the config hook runs, Then a `context` subagent is injected, and it carries no model of its own, because the pool is the only thing that decides which model runs it.
334
+ - [ ] Given the injected agent set, When the slot variants are built, Then a hidden `context-<slot>` variant exists for every slot the pool can hand out, so a cartography run borrows shared capacity instead of a dedicated model.
334
335
  - [ ] Given a user config with no `instructions` array, When the config hook runs, Then the standing context-map rule is injected, naming all five map files, telling the agent to check with `ls` and never the `glob` tool, to read only the file relevant to the task, and to start from the canonical entry points the map lists.
335
- - [ ] Given the injected `context-manager`, When its prompt is inspected, Then it names the five files it owns, requires a short "Start here" list of canonical files per significant subsystem, and forbids file inventories — the map answers "what should I look at first?", never "tell me everything".
336
+ - [ ] Given the injected `context`, When its prompt is inspected, Then it names the five files it owns, requires a short "Start here" list of canonical files per significant subsystem, and forbids file inventories — the map answers "what should I look at first?", never "tell me everything".
336
337
 
337
338
  ### US-20: The map stays current without being asked
338
339
  **As a** user, **I want** the map refreshed automatically after a session that edited code, **so that** agents are never misled by stale documentation.
339
340
 
340
341
  **Story ID(s) for tests:** US-20
341
342
  **Acceptance criteria:**
342
- - [ ] Given a file inside the project was edited, When the root session next goes idle, Then a `context-manager` session is created and prompted with the edited paths listed, rather than nothing happening.
343
+ - [ ] Given a file inside the project was edited, When the root session next goes idle, Then a `context` session is created and prompted with the edited paths listed, rather than nothing happening.
343
344
  - [ ] Given `.opencode/context/` does not exist, When the automatic run fires, Then it is told to bootstrap the map; given that the directory exists, Then it is told to make an incremental update.
344
345
  - [ ] Given an idle event with no pending edit, or one from a subagent session, or one following an edit inside the map itself or inside a build directory, Then no cartographer is spawned.
345
346
  - [ ] Given an automatic run borrowed a pool slot, When it completes, Then the slot is released and the cartographer session going idle does not spawn a second run.
@@ -351,15 +352,15 @@ makes agents read it before they explore, then keeps it current on its own.
351
352
 
352
353
  **Story ID(s) for tests:** US-21
353
354
 
354
- Enforcement is split deliberately, and the split matters: **what the permission table enforces** is that `context-manager` can write the map and that *no other agent can*; **what the prompt enforces** is that every other agent knows to report map-relevant findings instead of making them. The permission half is the load-bearing one — it holds regardless of what the model decides to do — but it cannot tell an agent what it *should* have done, so the instruction is not redundant and is not going away.
355
+ Enforcement is split deliberately, and the split matters: **what the permission table enforces** is that `context` can write the map and that *no other agent can*; **what the prompt enforces** is that every other agent knows to report map-relevant findings instead of making them. The permission half is the load-bearing one — it holds regardless of what the model decides to do — but it cannot tell an agent what it *should* have done, so the instruction is not redundant and is not going away.
355
356
 
356
357
  **Acceptance criteria:**
357
- - [ ] Given the injected agent set, When every agent's `edit` permission is evaluated against a path inside `.opencode/context/`, Then `context-manager` is the only agent whose winning rule for that path is `allow`, and every agent that may otherwise write files carries a `deny` on both spellings of the map path — so the map is closed to non-cartographers by permission, not only by instruction.
358
+ - [ ] Given the injected agent set, When every agent's `edit` permission is evaluated against a path inside `.opencode/context/`, Then `context` is the only agent whose winning rule for that path is `allow`, and every agent that may otherwise write files carries a `deny` on both spellings of the map path — so the map is closed to non-cartographers by permission, not only by instruction.
358
359
  - [ ] Given an agent's `edit` rules are evaluated the way opencode evaluates them, When the narrow map `deny` is declared before a broad `"*": "allow"`, Then the broad rule wins and the agent is reported as a second writer; so the deny is required to be the LAST entry, and the shipped table must be declared in that order.
359
- - [ ] Given the `context-manager` permission rule, When it is evaluated for a normally-rooted project and for a project rooted at `/`, Then both spellings of the map path are allowed, because opencode evaluates file permissions relative to the project root.
360
+ - [ ] Given the `context` permission rule, When it is evaluated for a normally-rooted project and for a project rooted at `/`, Then both spellings of the map path are allowed, because opencode evaluates file permissions relative to the project root.
360
361
  - [ ] Given a config whose one-writer rule allows neither spelling, When the writer rule is verified, Then it reports which rules it saw, that the map is not writable, and that both forms are required — instead of leaving a map that silently never updates.
361
362
  - [ ] Given a config in which a non-cartographer agent would be granted the map, When the writer rule is verified, Then it reports that agent by name together with the rule that wins for it, and the report is silent once the deny is narrowed and placed last.
362
- - [ ] Given an agent other than `context-manager` finishes work, When the standing rule reaches it, Then it is told never to edit the map itself and to report what belongs in it instead, so that a denied agent knows what to do rather than only that it was refused.
363
+ - [ ] Given an agent other than `context` finishes work, When the standing rule reaches it, Then it is told never to edit the map itself and to report what belongs in it instead, so that a denied agent knows what to do rather than only that it was refused.
363
364
  - [ ] Given `edit` is denied for the map, When the same agents read the map, Then reading is unaffected: the rule narrows the `edit` tool only, so the map stays readable by exactly the agents that are told to check it.
364
365
 
365
366
  ### US-22: On-demand map operations
@@ -367,7 +368,7 @@ Enforcement is split deliberately, and the split matters: **what the permission
367
368
 
368
369
  **Story ID(s) for tests:** US-22
369
370
  **Acceptance criteria:**
370
- - [ ] Given a config that defines none of LaCode's commands, When the config hook runs, Then `/context-init`, `/context-update` and `/context-review` are injected, each targeting `context-manager` as a subtask.
371
+ - [ ] Given a config that defines none of LaCode's commands, When the config hook runs, Then `/context-init`, `/context-update` and `/context-review` are injected, each targeting `context` as a subtask.
371
372
  - [ ] Given an injected command template, When opencode substitutes `$ARGUMENTS`, Then `/context-update` carries the user's focus text in the placeholder's place and the procedure around it is unchanged.
372
373
  - [ ] Given the three commands have been injected, When their templates are inspected, Then each names the procedure it delegates to (bootstrap, incremental update, or review) and none carries an unsubstituted placeholder other than `/context-update`'s single `$ARGUMENTS`.
373
374
  - [ ] Given the user's config already defines one of the three commands, When the config hook runs, Then that command is left byte-identical while the other two are still added.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawaty/lacode",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Ultimate opencode setup for Go users — cost-controlled pooled subagents, hang recovery, and notifications for opencode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -4,7 +4,7 @@
4
4
  "plugin options -> ~/.config/lacode/pool.json -> this file -> a built-in fallback.",
5
5
  "",
6
6
  "src/lib/pool.ts reads it at config time and rewrites every pooled variant agent",
7
- "(explore-fast-N, implement-fast-N, context-manager-N) plus the provider whitelists",
7
+ "(explore-N, implement-N, context-N) plus the provider whitelists",
8
8
  "to match, so no model is written down next to an agent anywhere.",
9
9
  "Slot N is the same model for every base agent, which is what makes load shared.",
10
10
  "",