Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
279918da1This file is pinned to the selected release. Download the original to open it in your design tool.
/**
* `harness dsh check <path>` — the conformance check a DSH must pass before the registry lists it,
* runnable by its author on a plain checkout. It answers the questions the loader would otherwise
* answer at create time, when the user is waiting: does the manifest parse, does every path it names
* exist, does every skills root carry a SKILL.md, is the viewer URL a template Harness can fill.
*
* Static only: nothing here runs setup, doctor, or the viewer. `harness dsh doctor` does that for an
* installed harness, and CI runs the real install in a clean container.
*/
import { existsSync, statSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { isViewerPackage, readDshManifest, type DshManifest } from './manifest.js'
import { skillDirsIn } from './materialize.js'
export interface CheckLine {
level: 'ok' | 'warn' | 'fail'
what: string
}
export interface CheckResult {
ok: boolean
lines: CheckLine[]
manifest: DshManifest | null
}
function isDir(path: string): boolean {
try { return statSync(path).isDirectory() } catch { return false }
}
function isFile(path: string): boolean {
try { return statSync(path).isFile() } catch { return false }
}
/** A command that looks like a path inside the harness must exist there; a shell line is not checked. */
function commandPath(dir: string, command: string): string | null {
if (/[\s;&|<>$`'"\\]/.test(command)) return null
return join(dir, command)
}
export function checkDsh(path: string): CheckResult {
const dir = resolve(path)
const lines: CheckLine[] = []
const add = (level: CheckLine['level'], what: string): void => { lines.push({ level, what }) }
const read = readDshManifest(dir)
if (!read.ok) {
add('fail', read.error)
return { ok: false, lines, manifest: null }
}
const manifest = read.manifest
const viewerPkg = isViewerPackage(manifest)
add('ok', viewerPkg
? `harness.json parses · ${manifest.id} "${manifest.name}" is a viewer package`
: `harness.json parses · ${manifest.id} "${manifest.name}" runs on ${manifest.engine}`)
if (!manifest.description) add('warn', viewerPkg ? 'no description' : 'no description — the picker tile will have none')
if (!manifest.author && !viewerPkg) add('warn', 'no author — the tile will not say who made it')
if (viewerPkg) {
if (manifest.workspace) add('warn', 'a viewer package has no workspace of its own; workspace.* is ignored')
if (manifest.verdict) add('warn', 'a viewer package writes no verdict; the harness that uses it does')
}
const ws = manifest.workspace
if (ws?.template) {
if (isDir(join(dir, ws.template))) add('ok', `workspace.template ${ws.template}/`)
else if (manifest.toolchain?.setup) add('warn', `workspace.template ${ws.template} is not in the checkout; toolchain.setup must create it`)
else add('fail', `workspace.template ${ws.template} is not a directory`)
if (!ws.marker) add('warn', 'workspace.template without workspace.marker: the template is copied on EVERY create')
}
if (ws?.marker && ws.template && isDir(join(dir, ws.template)) && !existsSync(join(dir, ws.template, ws.marker))) {
add('warn', `workspace.marker ${ws.marker} is not in the template, so a fresh workspace stays "fresh" until something else writes it`)
}
if (ws?.init) {
const p = commandPath(dir, ws.init)
if (p === null) add('ok', `workspace.init is a shell line`)
else if (isFile(p)) add('ok', `workspace.init ${ws.init}`)
else add('fail', `workspace.init ${ws.init} does not exist`)
}
const agent = manifest.agent
if (agent?.instructions) {
if (isFile(join(dir, agent.instructions))) add('ok', `agent.instructions ${agent.instructions}`)
// A wrapper that fetches its upstream at setup (the project's own AGENTS.md, template and skills
// arrive with it) has none of them on a plain checkout — by design, the same as fetched skills.
else if (manifest.toolchain?.setup) add('warn', `agent.instructions ${agent.instructions} is not in the checkout; toolchain.setup must create it`)
else add('fail', `agent.instructions ${agent.instructions} does not exist`)
} else if (!viewerPkg) {
// A viewer package may not have an agent section at all, so it is never told it lacks one.
add('warn', 'no agent.instructions — the agent gets no AGENTS.md from this harness')
}
for (const root of agent?.skills ?? []) {
const full = join(dir, root)
if (!isDir(full)) {
// A skills root that setup populates (upstream skills fetched at install, not vendored — the
// shape a project without a licence to copy forces) is absent on a plain checkout by design.
if (manifest.toolchain?.setup) add('warn', `agent.skills ${root} is not in the checkout; toolchain.setup must create it, or the agent gets no skills`)
else add('fail', `agent.skills ${root} is not a directory`)
continue
}
const dirs = skillDirsIn(full)
if (!dirs.length) add('fail', `agent.skills ${root} has no SKILL.md-bearing directory`)
else add('ok', `agent.skills ${root}/ · ${dirs.map((d) => d.slice(full.length + 1) || '.').join(', ')}`)
}
if (!agent?.skills?.length && !viewerPkg) add('warn', 'no agent.skills — a tier-0 harness usually ships at least one')
for (const [key, value] of Object.entries(agent?.env ?? {})) {
if (key.startsWith('HARNESS_')) add('fail', `agent.env.${key} is reserved (HARNESS_* is set by Harness)`)
else if (/\$\{(?!dsh\}|workspace\}|home\})/.test(value)) add('warn', `agent.env.${key} uses a variable Harness does not expand: ${value}`)
}
for (const name of ['setup', 'doctor'] as const) {
const command = manifest.toolchain?.[name]
if (!command) continue
const p = commandPath(dir, command)
if (p === null) add('ok', `toolchain.${name} is a shell line`)
else if (isFile(p)) add('ok', `toolchain.${name} ${command}`)
else add('fail', `toolchain.${name} ${command} does not exist`)
}
if (!manifest.toolchain?.doctor) add('warn', 'no toolchain.doctor — Harness cannot tell the user what is missing before a create')
const viewer = manifest.viewer
if (viewer && 'use' in viewer) {
add('ok', `viewer.use ${viewer.use} — installed with this harness; its command and URL come from that package`)
if (viewer.url !== undefined && !viewer.url.includes('${port}')) add('fail', 'viewer.url has no ${port}: Harness picks the port, the URL must use it')
} else if (viewer) {
const p = commandPath(dir, viewer.command)
if (p === null) add('ok', 'viewer.command is a shell line')
else if (isFile(p)) add('ok', `viewer.command ${viewer.command}`)
else add('fail', `viewer.command ${viewer.command} does not exist`)
if (!viewer.url.includes('${port}')) add('fail', 'viewer.url has no ${port}: Harness picks the port, the URL must use it')
if (!/^https?:\/\/(127\.0\.0\.1|localhost)[:/]/.test(viewer.url)) add('warn', 'viewer.url is not loopback; the pane expects a local server')
if (viewer.url.includes('${artifact}') && !viewer.artifactExtensions?.length) {
add('warn', 'viewer.url uses ${artifact} but no artifactExtensions are declared: only a verdict can name one')
}
}
if (manifest.verdict && !manifest.verdict.startsWith('.harness/')) add('warn', `verdict ${manifest.verdict} is outside .harness/ — allowed, but the convention is .harness/verdict.json`)
return { ok: !lines.some((line) => line.level === 'fail'), lines, manifest }
}
export function formatCheck(result: CheckResult): string {
return result.lines.map((line) => `${line.level.padEnd(4)} ${line.what}`).join('\n')
}