fix(dsh): command-check в списках пакета/проверок, self-check логирует до wiring и уважает конфиг (T4.1-T4.4, #69)
This commit is contained in:
parent
f1bbbe0412
commit
9a5fa8cac2
@ -104,11 +104,22 @@ export function apply(ctx, rawConfig = {}) {
|
||||
const guard = createGuard({ hooksDir, logger: ctx.logger })
|
||||
|
||||
const selfCheckReport =
|
||||
config.enabled && config.selfCheck ? collectSelfCheck(ctx) : null
|
||||
config.enabled && config.selfCheck ? collectSelfCheck(ctx, config) : null
|
||||
const guardsOk = selfCheckReport
|
||||
? selfCheckReport.find((entry) => entry.feature === 'guards')?.ok === true
|
||||
: true
|
||||
|
||||
// Log every not-ok feature before any wiring runs, so a service that is
|
||||
// missing from this DSH version leaves a diagnostic line even when a later
|
||||
// wiring block throws on first use.
|
||||
if (selfCheckReport) {
|
||||
for (const entry of selfCheckReport) {
|
||||
if (!entry.ok) {
|
||||
ctx.logger?.error?.('[dsh-harness] self-check: %s not-ok: %s', entry.feature, entry.detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enabled && config.guards && guardsOk) {
|
||||
// A synchronous, monotonic guard: it runs after the extensible
|
||||
// `tools/pre-execute` waterfall, so no other listener can turn a denial
|
||||
@ -165,21 +176,13 @@ export function apply(ctx, rawConfig = {}) {
|
||||
if (config.checkCommand) {
|
||||
registerCheckCommand({
|
||||
commands: commandCtx.commands,
|
||||
collect: () => collectSelfCheck(ctx),
|
||||
collect: () => collectSelfCheck(ctx, config),
|
||||
logger: ctx.logger,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (selfCheckReport) {
|
||||
for (const entry of selfCheckReport) {
|
||||
if (!entry.ok) {
|
||||
ctx.logger?.error?.('[dsh-harness] self-check: %s not-ok: %s', entry.feature, entry.detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.logger?.info?.(
|
||||
'[dsh-harness] ready: hooks=%s, bootstrap=%s, /role=%s',
|
||||
config.guards ? hooksDir : 'off',
|
||||
|
||||
@ -32,16 +32,25 @@ const CONTRACT = [
|
||||
|
||||
/**
|
||||
* Inspect a ctx-like object without calling anything on it.
|
||||
* @param {object} [config] - bundle config; a feature whose config switch is
|
||||
* false is skipped entirely (workingDirectory is always checked, and
|
||||
* `commands` is checked while either `roleCommand` or `checkCommand` is on).
|
||||
* @returns {{feature: 'guards'|'bootstrap'|'commands'|'workingDirectory',
|
||||
* ok: boolean, detail: string}[]}
|
||||
*/
|
||||
export function collectSelfCheck(ctxLike) {
|
||||
return CONTRACT.map(({ feature, label, available }) => {
|
||||
const ok = available(ctxLike)
|
||||
return {
|
||||
feature,
|
||||
ok,
|
||||
detail: ok ? `${label}: available` : `${label}: missing or not a function`,
|
||||
}
|
||||
})
|
||||
export function collectSelfCheck(ctxLike, config = {}) {
|
||||
const disabled = {
|
||||
guards: config?.guards === false,
|
||||
bootstrap: config?.bootstrap === false,
|
||||
commands: config?.roleCommand === false && config?.checkCommand === false,
|
||||
}
|
||||
return CONTRACT.filter(({ feature }) => feature === 'workingDirectory' || !disabled[feature])
|
||||
.map(({ feature, label, available }) => {
|
||||
const ok = available(ctxLike)
|
||||
return {
|
||||
feature,
|
||||
ok,
|
||||
detail: ok ? `${label}: available` : `${label}: missing or not a function`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"files": [
|
||||
"index.js",
|
||||
"command-role.js",
|
||||
"command-check.js",
|
||||
"lib",
|
||||
"dsh-tools.md",
|
||||
"cordis.patch.yml",
|
||||
|
||||
@ -55,10 +55,13 @@ test('runtime files have no external @deepseek-ai imports', () => {
|
||||
for (const file of [
|
||||
'index.js',
|
||||
'command-role.js',
|
||||
'command-check.js',
|
||||
'lib/message.js',
|
||||
'lib/guards.js',
|
||||
'lib/bootstrap.js',
|
||||
'lib/role.js',
|
||||
'lib/legacy.js',
|
||||
'lib/selfcheck.js',
|
||||
]) {
|
||||
const source = readFileSync(join(bundleDir, file), 'utf8')
|
||||
assert.doesNotMatch(source, /from ['"]@deepseek-ai\//, `${file} must not import DSH internals`)
|
||||
|
||||
@ -23,7 +23,7 @@ ensure_bundle_hooks() {
|
||||
|
||||
ensure_bundle_hooks
|
||||
|
||||
for f in dsh/bundle/index.js dsh/bundle/command-role.js dsh/bundle/lib/*.js dsh/tests/*.mjs; do
|
||||
for f in dsh/bundle/index.js dsh/bundle/command-role.js dsh/bundle/command-check.js dsh/bundle/lib/*.js dsh/tests/*.mjs; do
|
||||
node --check "$f"
|
||||
done
|
||||
|
||||
|
||||
@ -55,6 +55,45 @@ test('collectSelfCheck flags missing on()', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('collectSelfCheck skips features disabled by config', () => {
|
||||
const report = collectSelfCheck(fullCtx(), {
|
||||
guards: false,
|
||||
bootstrap: false,
|
||||
roleCommand: false,
|
||||
checkCommand: false,
|
||||
})
|
||||
assert.deepEqual(
|
||||
report.map((entry) => entry.feature),
|
||||
['workingDirectory'],
|
||||
)
|
||||
})
|
||||
|
||||
test('collectSelfCheck does not flag a disabled feature that is also missing', () => {
|
||||
const ctx = fullCtx()
|
||||
ctx.tools = {}
|
||||
ctx.on = undefined
|
||||
const report = collectSelfCheck(ctx, { guards: false, bootstrap: false })
|
||||
assert.deepEqual(
|
||||
report.map((entry) => entry.feature),
|
||||
['commands', 'workingDirectory'],
|
||||
)
|
||||
assert.ok(report.every((entry) => entry.ok), 'nothing not-ok in the report')
|
||||
|
||||
const noInject = fullCtx()
|
||||
noInject.inject = undefined
|
||||
const commandsOff = collectSelfCheck(noInject, { roleCommand: false, checkCommand: false })
|
||||
assert.ok(!commandsOff.some((entry) => entry.feature === 'commands'), 'commands skipped')
|
||||
assert.ok(commandsOff.every((entry) => entry.ok), 'missing inject not flagged')
|
||||
|
||||
const roleOnly = collectSelfCheck(noInject, { roleCommand: false })
|
||||
assert.ok(roleOnly.some((entry) => entry.feature === 'commands'), 'commands still checked')
|
||||
assert.equal(
|
||||
roleOnly.find((entry) => entry.feature === 'commands').ok,
|
||||
false,
|
||||
'checkCommand alone still needs ctx.inject',
|
||||
)
|
||||
})
|
||||
|
||||
test('registerCheckCommand builds report text', async () => {
|
||||
let captured
|
||||
const commands = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user