Files
dsh-facet/test/presets.test.js
T
NotExpectedYet 4bc4296494 fix: stop the facet preset naming a prompt variable the harness dropped
The preset's persona row copied the deployment's suffix, "Your working directory
is {{cwd}}". The harness substitutes only registered prompt variables and throws
on an unregistered one during prompt assembly — so when a harness release stopped
registering `cwd` (only `provider` and `model` remain), every session on this
preset failed its first turn with

  unknown prompt variable "{{cwd}}" in section "deployment:persona-suffix"

and chat was dead rather than degraded. The row now carries the prefix only.

The folder is stated where it can follow the session instead: the facet binding
already names the facet, and now names the session folder as well when the two
differ — which is every conversation that started higher up and was narrowed, and
exactly the case where relative tool paths need saying out loud.

`test/presets.test.js` makes this class of failure impossible to reintroduce
silently: it scans every shipped preset for a variable reference and requires the
name to be one the harness registers, and holds this bundle's own rule and
binding text to no references at all. Restoring the old suffix fails it.
2026-10-19 09:56:26 +01:00

62 lines
2.6 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync, readdirSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
import { facetContextText, facetRuleText } from '../lib/scope.js'
const PRESETS = join(dirname(fileURLToPath(import.meta.url)), '..', 'presets')
/**
* The prompt variables the harness registers for a section.
*
* `dsh-agent-loop` puts each of these into the prompt registry, and an assembly
* substitutes only what is registered. An unknown reference is not rendered as
* text — interpolation throws, and the throw takes the entire turn with it, so
* the user sees "This turn failed" and nothing else. A harness release that
* stopped registering `cwd` is exactly how this bundle's preset stopped
* composing sessions: the persona suffix it copied from the deployment said
* "Your working directory is …" with that variable in it.
*/
const REGISTERED = ['provider', 'model']
/** Every `{{name}}` reference in one text, as the name alone. */
function referencesIn(text) {
return [...text.matchAll(/\{\{([^{}]*)\}\}/g)].map((match) => match[1])
}
test('no preset references a prompt variable the harness does not register', () => {
const files = readdirSync(PRESETS).filter((name) => name.endsWith('.yml'))
assert.ok(files.length > 0, 'the bundle ships at least one preset')
const found = []
for (const file of files) {
for (const name of referencesIn(readFileSync(join(PRESETS, file), 'utf8'))) found.push([file, name])
}
// The model reference is the one that has to keep working, so the guard cannot
// be satisfied by a preset that references nothing at all.
assert.ok(
found.some(([, name]) => name === 'model'),
'a preset still names the model',
)
for (const [file, name] of found) {
assert.ok(
REGISTERED.includes(name),
`${file} references {{${name}}}; the harness registers only ${REGISTERED.join(', ')}, and an unregistered reference fails the whole turn`,
)
}
})
test('the text this bundle contributes to the prompt holds no variable references', () => {
// The rule and the binding are rebuilt from real values on every request, so a
// placeholder in either would be a reference the harness never resolves — the
// same failure, this time from the bundle's own hand.
const rule = facetRuleText()
const binding = facetContextText({ cwd: '/w', root: '/w/src', extras: ['/w/shared'], mode: 'ask' })
assert.ok(rule.length > 0 && binding.length > 0, 'both halves contribute text')
assert.deepEqual(referencesIn(rule), [])
assert.deepEqual(referencesIn(binding), [])
})