9 Commits
Author SHA1 Message Date
gitea-actions a350178434 Release v0.1.3 [skip release] [skip actions] 2026-09-26 06:07:51 +00:00
NotExpectedYet 7f1dfddb83 Merge branch 'master' of https://git.notexpectedyet.com/Not-Expected-Yet/dsh-recall
Release / release (push) Successful in 6s
2026-09-26 07:07:33 +01:00
NotExpectedYet 233215e6c9 fix: draw the settings on the plugin card, not a click deeper
The page was registered into `plugins.row.config`, keyed
`<package>#<row id>`. That seat puts a page behind the ROW's title button on the
bundle's card — the right home for one row of a bundle that declares several,
and the reasoning the previous commit wrote down. What it missed is that this
bundle declares exactly one row, so its settings became reachable only by
opening the card and noticing that the row title had turned into a button.
Reported from a real install as "it shows the plugin just no settings", while
the sibling plugin's settings were visible: that one registers into
`plugins.bundle.config`.

`plugins.bundle.config` is the same document, dispatched on the card itself:
`PackageDetail` gates a section on `ledger.bundles.has(pkg.name)` and renders
`renderSlot("plugins.bundle.config", { view: "page" }, { entryKey: pkg.name })`
between the description and the rows. So the key becomes the bundle's package
name, and the form draws where someone opening "dsh-recall" is already looking.

The section gets no `form` prop at this seat, unlike the row page — which is
fine, because the card reads its own form from `configForms` rather than
depending on the one handed in.

Verified by serving the bundle from a profile that links this checkout and
reading the artifact back: the served `client.js` carries
`plugins.bundle.config` at the registration and names the retired seat only in
prose. The tests pin the new key and now fail if either the seat or the key
drifts.
2026-09-26 07:05:52 +01:00
gitea-actions df2fa0f54b Release v0.1.2 [skip release] [skip actions] 2026-09-26 05:53:08 +00:00
NotExpectedYet 1f42d2a821 Merge branch 'master' of https://git.notexpectedyet.com/Not-Expected-Yet/dsh-recall
Release / release (push) Successful in 6s
2026-09-26 06:52:50 +01:00
NotExpectedYet 269360a4ea docs: lead the install instructions with the published git spec
`dsh plugin --profile web add /path/to/dsh-recall` was the only form given,
which is the development one: it is what you type against a checkout, not what
somebody installing this repository types. The published spec now comes first,
exactly as the maintainer writes it, with the `#semver:^0.1.0` variant beside it
because that is what keeps `dsh plugin update` following release tags — the
Releases section explains why, and this points at it.

The directory form stays, reframed as the alternative it is: a `link:`
dependency that tracks a working tree, so an edit needs no reinstall.

The directory form is the one exercised in a profile here; the remote form
cannot be resolved from this machine, which has no credentials for that host.
2026-09-26 06:51:49 +01:00
gitea-actions 44b73b587e Release v0.1.1 [skip release] [skip actions] 2026-09-26 05:49:16 +00:00
NotExpectedYet b85b107f6d Merge branch 'master' of https://git.notexpectedyet.com/Not-Expected-Yet/dsh-recall
Release / release (push) Successful in 7s
2026-09-26 06:49:01 +01:00
NotExpectedYet a48e05585e fix: remove one machine's addresses and paths from the shipped defaults
The plugin was developed against an Ollama on a private LAN address, and that
address had reached places that ship:

- the `config` parameter description the MODEL reads on every request;
- the commented example in `cordis.patch.yml`, the layer every profile applies;
- `scripts/smoke.mjs` and `scripts/measure.mjs` endpoint defaults;
- both measure scripts' default workspace, which was an absolute local path
  and named a private repository;
- README examples and test fixtures.

All of them now use `http://127.0.0.1:11434`, which is Ollama's documented
default and belongs to no particular machine — kept rather than emptied because
`validateSettings` requires an http(s) URL, so an empty default would turn every
call into INVALID_SETTINGS until someone configured it. Test fixtures use
192.0.2.10 instead: TEST-NET-1 is reserved for documentation and cannot point at
a host anybody runs. The `focus` and `config` examples are generic, and the
workspace defaults are the working directory.

Two script bugs surfaced once the defaults stopped pointing at a live box, and
are fixed here. `measure.mjs` built its own configuration literal, which was ten
fields short of the schema — harmless while nothing validated it, and a refused
call the moment `execute` began checking the section it was handed; it now
resolves its configuration through the plugin's own `Config`, so it cannot drift
again. It also crashed on a failed search (`first.index.scanned` of a result
that has no index) instead of reporting the endpoint it could not reach, which
is now the ordinary case. `scope-measure.mjs` divided by a scope that does not
exist in any workspace without focus markers, and now says so.

Also corrects `cordis.patch.yml`'s description of what the row registers: in
0.1.7 the exported `Config` is the settings registration, not a namespace on
`ctx.settings`.
2026-09-26 06:47:25 +01:00
11 changed files with 179 additions and 78 deletions

No files matched your search

+21
View File
@@ -2,6 +2,27 @@
Every release, newest first, generated from the commits in each release. Every release, newest first, generated from the commits in each release.
## v0.1.3 — 2026-09-26
### Fixed
- draw the settings on the plugin card, not a click deeper
## v0.1.2 — 2026-09-26
### Documented
- lead the install instructions with the published git spec
## v0.1.1 — 2026-09-26
### Fixed
- remove one machine's addresses and paths from the shipped defaults
## v0.1.0 — 2026-09-26 ## v0.1.0 — 2026-09-26
### Breaking ### Breaking
+39 -10
View File
@@ -49,6 +49,22 @@ about a millisecond; the only per-query network cost is one embedding call.
## Install ## Install
```sh
dsh plugin --profile web add git+https://git.notexpectedyet.com/Not-Expected-Yet/dsh-recall.git
```
That form installs whatever the default branch holds, pinned to a commit in the
profile's lockfile. The semver fragment installs the newest release tag instead,
and is what makes `dsh plugin update` follow releases — see
[Releases](#releases) for the tag and versioning rules:
```sh
dsh plugin --profile web add 'git+https://git.notexpectedyet.com/Not-Expected-Yet/dsh-recall.git#semver:^0.1.0'
```
To run your own copy instead, add the directory — that becomes a `link:`
dependency which tracks the working tree, so an edit needs no reinstall:
```sh ```sh
dsh plugin --profile web add /path/to/dsh-recall dsh plugin --profile web add /path/to/dsh-recall
``` ```
@@ -62,12 +78,14 @@ a fact:
- id: dsh-recall - id: dsh-recall
name: dsh-recall name: dsh-recall
config: config:
embeddingApiBase: http://10.50.1.2:11434 embeddingApiBase: http://127.0.0.1:11434 # replace with your endpoint
``` ```
Or edit every setting from the UI: **Plugins → dsh-recall → Configure**. The Or edit every setting from the UI: **Plugins** in the sidebar → the
plugin ships **two halves**, and the page appears only when both are present and **dsh-recall** card → its settings are on that card, below the description.
the deployment serves the namespace: (Not Settings → *Built-in plugins*: that section is a read-only inventory of
what the deployment ships.) The plugin ships **two halves**, and the section
appears only when both are present and the deployment serves the namespace:
- the **host half** (`index.js`) exports a `Config` schema whose every field is - the **host half** (`index.js`) exports a `Config` schema whose every field is
`.volatile()`. That schema *is* the settings page's description — the Harness `.volatile()`. That schema *is* the settings page's description — the Harness
@@ -76,11 +94,16 @@ the deployment serves the namespace:
a schema without those markers has no settings surface at all rather than an a schema without those markers has no settings surface at all rather than an
empty one; empty one;
- the **browser half** (`client/client.js`) renders that form into the Plugins - the **browser half** (`client/client.js`) renders that form into the Plugins
panel's per-row configuration seat, `plugins.row.config`, keyed panel's per-bundle configuration seat, `plugins.bundle.config`, keyed by the
`<bundle package>#<row id>` — here `dsh-recall#dsh-recall`. Registering it is bundle's package name. Registering it is what draws the section; without it
what adds the **Configure** control to the plugin's row. there is no settings UI however complete the schema is.
A served namespace with no page draws no control, and a page whose namespace is The sibling seat `plugins.row.config` — keyed `<package>#<row id>` — draws one
row's configuration on that row's own page. That is the right home for one row
of a bundle that declares several; this bundle declares one, so its settings
belong on the card.
A served namespace with no page draws nothing, and a page whose namespace is
not served is deliberately not registered — so both are required. A restart is not served is deliberately not registered — so both are required. A restart is
needed for a newly installed browser half to appear, because client bundle bytes needed for a newly installed browser half to appear, because client bundle bytes
are snapshotted when the profile activates. are snapshotted when the profile activates.
@@ -92,7 +115,7 @@ document, so the value survives a restart and stays visible in
Every value is also checkable from the tool, without touching a file: Every value is also checkable from the tool, without touching a file:
``` ```
recall action=configure config={"embeddingApiBase":"http://10.50.1.2:11434"} recall action=configure config={"embeddingApiBase":"http://127.0.0.1:11434"}
``` ```
The API key is declared `role('secret')`, so it is stripped from every settings The API key is declared `role('secret')`, so it is stripped from every settings
@@ -391,11 +414,17 @@ would publish.
## Development ## Development
```sh ```sh
npm test # 91 tests, no network required npm test # the whole suite; no network required
node scripts/smoke.mjs [workspace] [query] # live retrieval check node scripts/smoke.mjs [workspace] [query] # live retrieval check
node scripts/measure.mjs [workspace] [query] [budgetMs] node scripts/measure.mjs [workspace] [query] [budgetMs]
``` ```
Both live scripts default to the endpoint the schema defaults to
(`http://127.0.0.1:11434`) and to a model `ollama pull` can fetch, so they need
no configuration on a local Ollama. Point them at another host with
`RECALL_API_BASE` and `RECALL_MODEL`; `[workspace]` defaults to the working
directory.
The `lib/` tests run anywhere. `test/config.test.js` and `test/plugin.test.js` The `lib/` tests run anywhere. `test/config.test.js` and `test/plugin.test.js`
import `index.js`, which imports `@deepseek-ai/dsh-tools` and import `index.js`, which imports `@deepseek-ai/dsh-tools` and
`@deepseek-ai/schemastery` — resolvable once the plugin is installed into a `@deepseek-ai/schemastery` — resolvable once the plugin is installed into a
+23 -16
View File
@@ -12,10 +12,11 @@
* binding `settingsScope` and registering into a `settings.plugin.item` slot. * binding `settingsScope` and registering into a `settings.plugin.item` slot.
* Neither exists in 0.1.7. The generated settings document is keyed by the * Neither exists in 0.1.7. The generated settings document is keyed by the
* plugin's own profile entry id, and a plugin that wants a page renders it as a * plugin's own profile entry id, and a plugin that wants a page renders it as a
* contribution to the Plugins panel: `plugins.row.config`, keyed * contribution to the Plugins panel — `plugins.bundle.config`, keyed by the
* `<bundle package>#<row id>`. That key is what puts a **configure** control on * bundle's package name, which the page draws as a section on the plugin's own
* the plugin's row — without a contribution there is no control and no page, * card. Without a contribution there is no section, however complete the schema
* however complete the schema is. * is; the sibling seat `plugins.row.config` would instead put it one click
* deeper, on a single row's page.
* *
* **The page is the framework's form, not a form of our own.** The card used to * **The page is the framework's form, not a form of our own.** The card used to
* reproduce `PluginCard`'s chrome by hand — the `<li>` root, the disclosure * reproduce `PluginCard`'s chrome by hand — the `<li>` root, the disclosure
@@ -70,13 +71,17 @@ window.__ModuleLoader__.load({
/** /**
* The seat this plugin's configuration page renders in. * The seat this plugin's configuration page renders in.
* *
* Keyed `<bundle package name>#<row id>`, both as the profile's patch * `plugins.bundle.config`, keyed by the BUNDLE's package name: the Plugins
* declares them. For this bundle the two coincide, which is a coincidence * page draws that contribution as a section on the plugin's own card,
* and not a rule — the constants are kept separate so that stays visible. * between its description and its rows. That is where someone opening
* "dsh-recall" expects its settings — the alternative seat,
* `plugins.row.config` (keyed `<package>#<row id>`), draws them on a page
* one click deeper, behind the row's title button, which is the right home
* for one row of a bundle that declares several. This bundle declares one
* row, so the card is the honest place.
*/ */
const BUNDLE_PACKAGE = 'dsh-recall' const BUNDLE_PACKAGE = 'dsh-recall'
const BUNDLE_ROW = 'dsh-recall' const BUNDLE_CONFIG_KEY = BUNDLE_PACKAGE
const ROW_CONFIG_KEY = `${BUNDLE_PACKAGE}#${BUNDLE_ROW}`
const TITLE = 'Recall' const TITLE = 'Recall'
const DESCRIPTION = const DESCRIPTION =
@@ -486,10 +491,12 @@ window.__ModuleLoader__.load({
/** /**
* The plugin's configuration page. * The plugin's configuration page.
* *
* The Plugins page asks a configuration entry twice: once for a one-liner to * The seat asks for `page`, the form with its save control; the `summary`
* stand in for a missing description (`summary`), and once for the body of * branch answers the other shape a configuration entry can be asked for —
* the plugin's own page (`page`). Only the second draws a save control — * the one-liner standing in for a missing description — because a component
* and only one save writes, because the form discards its drafts on unmount. * registered under a seat that only ever asks for `page` is also the natural
* thing to reuse where that shape arrives. Only a save writes, and the form
* discards its drafts on unmount.
*/ */
function RecallSettingsCard(props) { function RecallSettingsCard(props) {
if (props.view === 'summary') return DESCRIPTION if (props.view === 'summary') return DESCRIPTION
@@ -705,11 +712,11 @@ window.__ModuleLoader__.load({
scoped.effect( scoped.effect(
() => () =>
scoped.configForms.whileServed([SETTINGS_ENTRY], () => scoped.configForms.whileServed([SETTINGS_ENTRY], () =>
scoped.slots.inject('plugins.row.config', () => scoped.slots.inject('plugins.bundle.config', () =>
scoped.slots.register( scoped.slots.register(
{ {
name: 'plugins.row.config', name: 'plugins.bundle.config',
key: ROW_CONFIG_KEY, key: BUNDLE_CONFIG_KEY,
inject: () => card.inject(), inject: () => card.inject(),
}, },
RecallSettingsCard, RecallSettingsCard,
+4 -2
View File
@@ -1,7 +1,9 @@
# The layer this bundle contributes to a dsh profile that lists it. # The layer this bundle contributes to a dsh profile that lists it.
# #
# One row: the dsh-recall host plugin, which registers exactly one tool # One row: the dsh-recall host plugin, which registers exactly one tool
# (`recall`) on `ctx.tools` and one settings namespace on `ctx.settings`. # (`recall`) on `ctx.tools`. Its settings are the `Config` schema it exports —
# the Harness keys a form per profile entry by this row's id — so there is no
# section to register and nothing to attach at load.
# #
# There is deliberately NO `config:` block. Every value the plugin needs is # There is deliberately NO `config:` block. Every value the plugin needs is
# either discovered at runtime (the workspace, from the calling session) or # either discovered at runtime (the workspace, from the calling session) or
@@ -14,7 +16,7 @@
# - id: dsh-recall # - id: dsh-recall
# name: dsh-recall # name: dsh-recall
# config: # config:
# embeddingApiBase: http://10.50.1.2:11434 # embeddingApiBase: http://127.0.0.1:11434 # replace with your endpoint
- insert: - insert:
- id: dsh-recall - id: dsh-recall
+7 -6
View File
@@ -32,10 +32,11 @@
* warm-up is available as an explicit background action. * warm-up is available as an explicit background action.
* *
* **Why the endpoint is configuration and not a constant.** The Ollama default * **Why the endpoint is configuration and not a constant.** The Ollama default
* of `127.0.0.1:11434` is a convention, not a fact — this deployment runs Ollama * of `127.0.0.1:11434` is a convention, not a fact — a deployment often runs
* on another host. Baking one machine's address into a bundle layer that other * Ollama on another host. Baking one machine's address into a bundle layer that
* machines apply is exactly the mistake the composition docs warn about, so the * other machines apply is exactly the mistake the composition docs warn about,
* endpoint is a schema default the profile can override, at rest or live. * so the endpoint is a schema default the profile can override, at rest or
* live.
*/ */
import { readFile, stat } from 'node:fs/promises' import { readFile, stat } from 'node:fs/promises'
@@ -965,7 +966,7 @@ export function apply(ctx, config) {
type: 'string', type: 'string',
description: description:
'Scope this whole call — index and search — to one workspace-relative directory, for example ' + 'Scope this whole call — index and search — to one workspace-relative directory, for example ' +
'"src/controllers/room/occupancy". Use it when the session is working on one module: the index ' + '"src/services/billing". Use it when the session is working on one module: the index ' +
'then holds only that module, so its near-identical neighbours cannot outrank it and the first ' + 'then holds only that module, so its near-identical neighbours cannot outrank it and the first ' +
'search does not wait on the rest of the repository. Empty uses the configured focus, then the ' + 'search does not wait on the rest of the repository. Empty uses the configured focus, then the ' +
'nearest enclosing directory that carries a CONTROLLER.md, DEBUG_UI.md or AGENTS.md.', 'nearest enclosing directory that carries a CONTROLLER.md, DEBUG_UI.md or AGENTS.md.',
@@ -980,7 +981,7 @@ export function apply(ctx, config) {
type: 'json', type: 'json',
description: description:
'Settings patch for action "configure", using this plugin\'s own camelCase field names, for ' + 'Settings patch for action "configure", using this plugin\'s own camelCase field names, for ' +
'example {"embeddingApiBase":"http://10.50.1.2:11434"}.', 'example {"embeddingApiBase":"http://127.0.0.1:11434"}.',
}, },
}, },
output: { output: {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dsh-recall", "name": "dsh-recall",
"version": "0.1.0", "version": "0.1.3",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Local-embedding code recall for DeepSeek Harness — returns ranked excerpts with line ranges instead of whole files, so the context window stops growing with every read.", "description": "Local-embedding code recall for DeepSeek Harness — returns ranked excerpts with line ranges instead of whole files, so the context window stops growing with every read.",
+34 -20
View File
@@ -19,38 +19,37 @@ import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os' import { tmpdir } from 'node:os'
import { join } from 'node:path' import { join } from 'node:path'
import { apply } from '../index.js' import { apply, Config, settingsSnapshot } from '../index.js'
const WORKSPACE = process.argv[2] ?? '/root/workspaces/ha-runtime' // Whatever repository you run this against, and whatever you want to ask it.
const QUERY = process.argv[3] ?? 'how does the occupancy state machine decide a room is occupied' // The defaults are the working directory and a query that names a behaviour
// rather than a symbol — which is the kind of question the plugin exists for.
const WORKSPACE = process.argv[2] ?? process.cwd()
const QUERY = process.argv[3] ?? 'where is the retry policy applied'
const BUDGET_MS = Number(process.argv[4] ?? 90_000) const BUDGET_MS = Number(process.argv[4] ?? 90_000)
const API_BASE = process.env.RECALL_API_BASE ?? 'http://10.50.1.2:11434' // The endpoint to measure against: the Ollama convention unless RECALL_API_BASE
// says otherwise. Nothing here is specific to the machine this was written on.
const API_BASE = process.env.RECALL_API_BASE ?? 'http://127.0.0.1:11434'
const MODEL = process.env.RECALL_MODEL ?? 'unclemusclez/jina-embeddings-v2-base-code:latest' const MODEL = process.env.RECALL_MODEL ?? 'unclemusclez/jina-embeddings-v2-base-code:latest'
const config = { // The measurement's configuration, stated as the few values it cares about and
// resolved through the plugin's own schema — so this script can never drift
// from the Config the plugin validates against. A hand-written literal used to
// stand here, and it was missing ten fields: harmless while nothing checked,
// and a refused call the moment `execute` began validating the section it was
// handed.
const config = settingsSnapshot(
Config({
embeddingApiBase: API_BASE, embeddingApiBase: API_BASE,
embeddingModel: MODEL, embeddingModel: MODEL,
embeddingDimensions: 768,
embeddingApiKey: '',
embeddingBatchSize: 32, embeddingBatchSize: 32,
embeddingConcurrency: 4, embeddingConcurrency: 4,
embeddingTimeoutMs: 120_000, embeddingTimeoutMs: 120_000,
extensions: [],
exclude: [],
maxFiles: 20_000,
maxFileBytes: 1_048_576,
chunkLines: 48,
maxChunkLines: 80,
overlapLines: 8,
defaultLimit: 8,
maxLimit: 25,
perFileLimit: 2,
excerptLines: 40, excerptLines: 40,
minSimilarity: 0.2,
syncBudgetMs: BUDGET_MS, syncBudgetMs: BUDGET_MS,
stateDir: '', }),
} )
/** The smallest context `apply` touches, with no settings provider mounted. */ /** The smallest context `apply` touches, with no settings provider mounted. */
function context() { function context() {
@@ -92,6 +91,21 @@ async function main() {
const coldMs = Date.now() - coldStart const coldMs = Date.now() - coldStart
console.log('--- FIRST SEARCH (bounded cold start) ---') console.log('--- FIRST SEARCH (bounded cold start) ---')
console.log(`elapsed : ${(coldMs / 1000).toFixed(1)}s (budget ${BUDGET_MS / 1000}s)`) console.log(`elapsed : ${(coldMs / 1000).toFixed(1)}s (budget ${BUDGET_MS / 1000}s)`)
// A search that did not run has no index or results to report, and the tool
// already says why. Without this the first `first.index.*` line throws a
// TypeError that buries the actual cause — an endpoint that is not there is
// the ordinary case now that the default is the loopback convention.
if (first.ok !== true) {
console.log(`failed : ${first.errorCode ?? 'UNKNOWN'}`)
console.log(`note : ${first.note ?? '(none)'}`)
if (first.remedy !== undefined) console.log(`remedy : ${first.remedy}`)
console.log('')
console.log(`nothing to measure: point RECALL_API_BASE at a reachable endpoint (currently ${API_BASE}).`)
process.exitCode = 1
return
}
console.log(`scanned files : ${first.index.scanned}`) console.log(`scanned files : ${first.index.scanned}`)
console.log(`indexed files : ${first.index.files}`) console.log(`indexed files : ${first.index.files}`)
console.log(`chunks : ${first.index.chunks}`) console.log(`chunks : ${first.index.chunks}`)
+23 -6
View File
@@ -6,10 +6,9 @@
* cold-start cost they imply are all arithmetic over the chunker, which is * cold-start cost they imply are all arithmetic over the chunker, which is
* deterministic and offline. * deterministic and offline.
* *
* The cold-start projection uses the throughput actually measured against the * The cold-start projection uses a throughput measured against a live endpoint
* real endpoint on this repository: 2,257 chunks embedded in 90s, about 25 * during development: 2,257 chunks embedded in 90s, about 25 chunks/s. It is a
* chunks/s. It is a projection from a real figure, not a guess, and it is * projection from a real figure, not a guess, and it is labelled as one.
* labelled as a projection.
* *
* node scripts/scope-measure.mjs [workspace] * node scripts/scope-measure.mjs [workspace]
*/ */
@@ -20,9 +19,15 @@ import { join } from 'node:path'
import { chunkFile } from '../lib/chunk.js' import { chunkFile } from '../lib/chunk.js'
import { listCodeFiles } from '../lib/walk.js' import { listCodeFiles } from '../lib/walk.js'
const WORKSPACE = process.argv[2] ?? '/root/workspaces/ha-runtime' // The repository to measure; the working directory by default, so the script
// carries no assumption about where anyone keeps their code.
const WORKSPACE = process.argv[2] ?? process.cwd()
/** Chunks per second, measured on this repository against the live endpoint. */ /**
* Chunks per second, measured against a live endpoint during development
* (2,257 chunks in 90s). A measured constant, not a guess — and a projection
* input, which is why the cold-start figures below are labelled as projections.
*/
const MEASURED_CHUNKS_PER_SECOND = 2257 / 90 const MEASURED_CHUNKS_PER_SECOND = 2257 / 90
/** Count files and chunks for one directory, relative to a base for display. */ /** Count files and chunks for one directory, relative to a base for display. */
@@ -98,6 +103,18 @@ async function main() {
if (scopes.length > 10) console.log(` … and ${scopes.length - 10} more`) if (scopes.length > 10) console.log(` … and ${scopes.length - 10} more`)
console.log('') console.log('')
// A workspace with no marker files has no scopes to compare, and that is a
// normal answer rather than a failure — the plugin falls back to the whole
// workspace there. Say so and stop, instead of dividing by the scope that
// does not exist.
if (scopes.length === 0) {
console.log('=== WHAT SCOPING BUYS ===')
console.log(' nothing here: no directory in this workspace carries a focus marker,')
console.log(' so every call indexes the whole workspace. Add a marker file (see')
console.log(' focusMarkers in the README) to a module you work on to create one.')
return
}
const median = scopes[Math.floor(scopes.length / 2)] const median = scopes[Math.floor(scopes.length / 2)]
const totalScoped = scopes.reduce((sum, scope) => sum + scope.chunks, 0) const totalScoped = scopes.reduce((sum, scope) => sum + scope.chunks, 0)
console.log('=== WHAT SCOPING BUYS ===') console.log('=== WHAT SCOPING BUYS ===')
+4 -1
View File
@@ -19,7 +19,10 @@ import { rankChunks } from '../lib/rank.js'
import { RecallIndex } from '../lib/store.js' import { RecallIndex } from '../lib/store.js'
import { listCodeFiles } from '../lib/walk.js' import { listCodeFiles } from '../lib/walk.js'
const API_BASE = process.env.RECALL_API_BASE ?? 'http://10.50.1.2:11434' // The endpoint to check against. The default is the Ollama convention, which
// is what the plugin's own schema defaults to; point RECALL_API_BASE at your
// own host when it is somewhere else.
const API_BASE = process.env.RECALL_API_BASE ?? 'http://127.0.0.1:11434'
const MODEL = process.env.RECALL_MODEL ?? 'unclemusclez/jina-embeddings-v2-base-code:latest' const MODEL = process.env.RECALL_MODEL ?? 'unclemusclez/jina-embeddings-v2-base-code:latest'
const DIMENSIONS = Number(process.env.RECALL_DIMENSIONS ?? 768) const DIMENSIONS = Number(process.env.RECALL_DIMENSIONS ?? 768)
+18 -13
View File
@@ -7,10 +7,13 @@
* leaving the page to be eyeballed in a running app, because the failures this * leaving the page to be eyeballed in a running app, because the failures this
* file exists to catch are structural and invisible until someone opens Plugins: * file exists to catch are structural and invisible until someone opens Plugins:
* *
* - the configuration page is registered into `plugins.row.config` under * - the configuration page is registered into `plugins.bundle.config` under
* `<bundle package>#<row id>`. The 0.1.5 seat (`settings.plugin.item`, bound * the bundle's package name, which is the seat the Plugins page draws as a
* through `settingsScope`) does not exist in 0.1.7, so a page left in it * section on the plugin's own card. The 0.1.5 seat (`settings.plugin.item`,
* registers against a slot nobody declares and simply never appears. * bound through `settingsScope`) does not exist in 0.1.7, so a page left in
* it registers against a slot nobody declares and simply never appears; the
* sibling seat `plugins.row.config` renders one click deeper instead, on a
* single row's page, which is not where someone opening the card looks.
* - the right-sidebar tab needs a TYPE registered before its body means * - the right-sidebar tab needs a TYPE registered before its body means
* anything: the keyed body slot is dispatched under the definition's `id`. * anything: the keyed body slot is dispatched under the definition's `id`.
* - the field list here and the `Config` schema in index.js must name the same * - the field list here and the `Config` schema in index.js must name the same
@@ -322,7 +325,7 @@ function mount(options = {}) {
* does, so the component under test sees the props a real render would give it. * does, so the component under test sees the props a real render would give it.
*/ */
const card = (view) => { const card = (view) => {
const item = byName.get('plugins.row.config') const item = byName.get('plugins.bundle.config')
assert.ok(item !== undefined, 'no settings page was registered') assert.ok(item !== undefined, 'no settings page was registered')
const props = { ...item.meta.inject(), view } const props = { ...item.meta.inject(), view }
delete props.hooks delete props.hooks
@@ -364,13 +367,15 @@ test('the client module claims the slot registry in its inject list', () => {
assert.deepEqual(module.inject, ['slots']) assert.deepEqual(module.inject, ['slots'])
}) })
test('the configuration page claims the 0.1.7 seat, keyed as the patch declares it', () => { test('the configuration page claims the seat the plugin card draws', () => {
const { registered } = mount() const { registered } = mount()
const page = registered.find((item) => item.meta.name === 'plugins.row.config') const page = registered.find((item) => item.meta.name === 'plugins.bundle.config')
assert.ok(page !== undefined, 'the page must register into plugins.row.config') assert.ok(page !== undefined, 'the page must register into plugins.bundle.config')
// `<bundle package name>#<row id>`: the row id is the one this bundle's own // Keyed by the BUNDLE's package name, which is what the Plugins page
// cordis.patch.yml declares, which is what the Plugins page dispatches by. // dispatches this seat with (`entryKey: pkg.name`). The sibling seat,
assert.equal(page.meta.key, 'dsh-recall#dsh-recall') // `plugins.row.config`, would be keyed `<package>#<row id>` and would render
// one click deeper, on the row's own page.
assert.equal(page.meta.key, 'dsh-recall')
assert.equal(typeof page.meta.inject, 'function') assert.equal(typeof page.meta.inject, 'function')
}) })
@@ -385,7 +390,7 @@ test('no page is registered for a namespace the Host does not serve', () => {
// onto fields nothing would accept. // onto fields nothing would accept.
const { registered } = mount({ served: ['some-other-plugin'] }) const { registered } = mount({ served: ['some-other-plugin'] })
assert.equal( assert.equal(
registered.some((item) => item.meta.name === 'plugins.row.config'), registered.some((item) => item.meta.name === 'plugins.bundle.config'),
false, false,
) )
}) })
@@ -431,7 +436,7 @@ test('the browser half contributes three seats, none of them the retired 0.1.5 o
const { registered } = mount() const { registered } = mount()
assert.deepEqual(registered.map((item) => item.meta.name).sort(), [ assert.deepEqual(registered.map((item) => item.meta.name).sort(), [
'conversation.session.header.corner', 'conversation.session.header.corner',
'plugins.row.config', 'plugins.bundle.config',
'sidebar.right.pane.tab', 'sidebar.right.pane.tab',
]) ])
}) })
+5 -3
View File
@@ -42,8 +42,10 @@ test('Config fills every field from an empty input, so apply can rely on default
}) })
test('Config keeps an explicit value and fills the rest', () => { test('Config keeps an explicit value and fills the rest', () => {
const resolved = resolve({ embeddingApiBase: 'http://10.50.1.2:11434', embeddingConcurrency: 8 }) // A TEST-NET-1 address (RFC 5737): reserved for documentation, so a fixture
assert.equal(resolved.embeddingApiBase, 'http://10.50.1.2:11434') // can never point at a host anybody actually runs.
const resolved = resolve({ embeddingApiBase: 'http://192.0.2.10:11434', embeddingConcurrency: 8 })
assert.equal(resolved.embeddingApiBase, 'http://192.0.2.10:11434')
assert.equal(resolved.embeddingConcurrency, 8) assert.equal(resolved.embeddingConcurrency, 8)
assert.equal(resolved.embeddingBatchSize, 16) assert.equal(resolved.embeddingBatchSize, 16)
assert.equal(resolved.embeddingTimeoutMs, 30_000) assert.equal(resolved.embeddingTimeoutMs, 30_000)
@@ -135,7 +137,7 @@ test('validateSettings rejects an endpoint that is not a URL', () => {
// so it is the protocol check that actually catches the common mistake. // so it is the protocol check that actually catches the common mistake.
assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: 'localhost:11434' })), /must be http or https/) assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: 'localhost:11434' })), /must be http or https/)
assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: '' })), /not a URL/) assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: '' })), /not a URL/)
assert.doesNotThrow(() => validateSettings(settingsWith({ embeddingApiBase: 'http://10.50.1.2:11434' }))) assert.doesNotThrow(() => validateSettings(settingsWith({ embeddingApiBase: 'http://192.0.2.10:11434' })))
}) })
test('validateSettings rejects a non-http protocol', () => { test('validateSettings rejects a non-http protocol', () => {