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 guard = createGuard({ hooksDir, logger: ctx.logger })
|
||||||
|
|
||||||
const selfCheckReport =
|
const selfCheckReport =
|
||||||
config.enabled && config.selfCheck ? collectSelfCheck(ctx) : null
|
config.enabled && config.selfCheck ? collectSelfCheck(ctx, config) : null
|
||||||
const guardsOk = selfCheckReport
|
const guardsOk = selfCheckReport
|
||||||
? selfCheckReport.find((entry) => entry.feature === 'guards')?.ok === true
|
? selfCheckReport.find((entry) => entry.feature === 'guards')?.ok === true
|
||||||
: 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) {
|
if (config.enabled && config.guards && guardsOk) {
|
||||||
// A synchronous, monotonic guard: it runs after the extensible
|
// A synchronous, monotonic guard: it runs after the extensible
|
||||||
// `tools/pre-execute` waterfall, so no other listener can turn a denial
|
// `tools/pre-execute` waterfall, so no other listener can turn a denial
|
||||||
@ -165,21 +176,13 @@ export function apply(ctx, rawConfig = {}) {
|
|||||||
if (config.checkCommand) {
|
if (config.checkCommand) {
|
||||||
registerCheckCommand({
|
registerCheckCommand({
|
||||||
commands: commandCtx.commands,
|
commands: commandCtx.commands,
|
||||||
collect: () => collectSelfCheck(ctx),
|
collect: () => collectSelfCheck(ctx, config),
|
||||||
logger: ctx.logger,
|
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?.(
|
ctx.logger?.info?.(
|
||||||
'[dsh-harness] ready: hooks=%s, bootstrap=%s, /role=%s',
|
'[dsh-harness] ready: hooks=%s, bootstrap=%s, /role=%s',
|
||||||
config.guards ? hooksDir : 'off',
|
config.guards ? hooksDir : 'off',
|
||||||
|
|||||||
@ -32,16 +32,25 @@ const CONTRACT = [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inspect a ctx-like object without calling anything on it.
|
* 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',
|
* @returns {{feature: 'guards'|'bootstrap'|'commands'|'workingDirectory',
|
||||||
* ok: boolean, detail: string}[]}
|
* ok: boolean, detail: string}[]}
|
||||||
*/
|
*/
|
||||||
export function collectSelfCheck(ctxLike) {
|
export function collectSelfCheck(ctxLike, config = {}) {
|
||||||
return CONTRACT.map(({ feature, label, available }) => {
|
const disabled = {
|
||||||
const ok = available(ctxLike)
|
guards: config?.guards === false,
|
||||||
return {
|
bootstrap: config?.bootstrap === false,
|
||||||
feature,
|
commands: config?.roleCommand === false && config?.checkCommand === false,
|
||||||
ok,
|
}
|
||||||
detail: ok ? `${label}: available` : `${label}: missing or not a function`,
|
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": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
"command-role.js",
|
"command-role.js",
|
||||||
|
"command-check.js",
|
||||||
"lib",
|
"lib",
|
||||||
"dsh-tools.md",
|
"dsh-tools.md",
|
||||||
"cordis.patch.yml",
|
"cordis.patch.yml",
|
||||||
|
|||||||
@ -55,10 +55,13 @@ test('runtime files have no external @deepseek-ai imports', () => {
|
|||||||
for (const file of [
|
for (const file of [
|
||||||
'index.js',
|
'index.js',
|
||||||
'command-role.js',
|
'command-role.js',
|
||||||
|
'command-check.js',
|
||||||
'lib/message.js',
|
'lib/message.js',
|
||||||
'lib/guards.js',
|
'lib/guards.js',
|
||||||
'lib/bootstrap.js',
|
'lib/bootstrap.js',
|
||||||
'lib/role.js',
|
'lib/role.js',
|
||||||
|
'lib/legacy.js',
|
||||||
|
'lib/selfcheck.js',
|
||||||
]) {
|
]) {
|
||||||
const source = readFileSync(join(bundleDir, file), 'utf8')
|
const source = readFileSync(join(bundleDir, file), 'utf8')
|
||||||
assert.doesNotMatch(source, /from ['"]@deepseek-ai\//, `${file} must not import DSH internals`)
|
assert.doesNotMatch(source, /from ['"]@deepseek-ai\//, `${file} must not import DSH internals`)
|
||||||
|
|||||||
@ -23,7 +23,7 @@ ensure_bundle_hooks() {
|
|||||||
|
|
||||||
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"
|
node --check "$f"
|
||||||
done
|
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 () => {
|
test('registerCheckCommand builds report text', async () => {
|
||||||
let captured
|
let captured
|
||||||
const commands = {
|
const commands = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user