@vaubaan/harness 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +1 -1
- package/README.md +229 -112
- package/dist/agents/coding.agent.md +1 -2
- package/dist/agents/merge.agent.md +2 -2
- package/dist/agents/planning.agent.md +1 -1
- package/dist/agents/review.agent.md +1 -1
- package/dist/cli/index.js +1438 -1287
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +804 -667
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +29 -6
- package/dist/init.js +1145 -470
- package/dist/init.js.map +1 -1
- package/dist/linters/index.js +3 -2
- package/dist/linters/index.js.map +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +41 -8
- package/dist/render.js.map +1 -1
- package/dist/skills/spec-writer.skill.md +1 -1
- package/dist/skills/test-plan-writer.skill.md +1 -1
- package/package.json +9 -4
- package/dist/{render-B9_kNtQc.d.ts → render-Cu2XrkkY.d.ts} +3 -3
package/dist/linters/index.js
CHANGED
|
@@ -350,9 +350,10 @@ var frontmatterTracks = {
|
|
|
350
350
|
};
|
|
351
351
|
|
|
352
352
|
// src/linters/gitignore-harness.ts
|
|
353
|
-
var
|
|
353
|
+
var REQUIRED_GITIGNORE_ENTRIES = [
|
|
354
354
|
".harness-overrides/",
|
|
355
355
|
".harness-manifest.json",
|
|
356
|
+
".harness/",
|
|
356
357
|
".claude/",
|
|
357
358
|
".codex/",
|
|
358
359
|
".opencode/",
|
|
@@ -381,7 +382,7 @@ var gitignoreHarness = {
|
|
|
381
382
|
const lines = new Set(
|
|
382
383
|
text.split("\n").map((l) => l.trim()).filter(Boolean)
|
|
383
384
|
);
|
|
384
|
-
for (const entry of
|
|
385
|
+
for (const entry of REQUIRED_GITIGNORE_ENTRIES) {
|
|
385
386
|
const variants = [entry, entry.replace(/\/$/, "")];
|
|
386
387
|
if (!variants.some((v) => lines.has(v))) {
|
|
387
388
|
issues.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/linters/util.ts","../../src/linters/adr-no-tracks.ts","../../src/linters/cardinal-files.ts","../../src/linters/config.ts","../../src/linters/cross-links.ts","../../src/linters/decision-schema.ts","../../src/linters/doc-layout.ts","../../src/linters/frontmatter-tracks.ts","../../src/linters/gitignore-harness.ts","../../src/linters/orphan-decisions.ts","../../src/linters/quality-schema.ts","../../src/linters/readme-presence.ts","../../src/linters/readme-sections.ts","../../src/linters/spec-has-content.ts","../../src/linters/spec-tech-agnostic.ts","../../src/linters/tracks-drift.ts","../../src/linters/index.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\n\nexport function fileExists(repoRoot: string, relativePath: string): boolean {\n return existsSync(join(repoRoot, relativePath));\n}\n\nexport function readText(repoRoot: string, relativePath: string): string {\n return readFileSync(join(repoRoot, relativePath), \"utf8\");\n}\n\nexport function listMarkdown(repoRoot: string, dir: string): string[] {\n const cwd = join(repoRoot, dir);\n if (!existsSync(cwd)) return [];\n return fg\n .sync(\"**/*.md\", { cwd, absolute: false, ignore: [\"**/node_modules/**\", \"**/dist/**\"] })\n .filter((f) => !f.endsWith(\"README.md\"))\n .map((f) => join(dir, f));\n}\n\nexport function listAllMarkdown(repoRoot: string, dir: string): string[] {\n const cwd = join(repoRoot, dir);\n if (!existsSync(cwd)) return [];\n return fg\n .sync(\"**/*.md\", { cwd, absolute: false, ignore: [\"**/node_modules/**\", \"**/dist/**\"] })\n .map((f) => join(dir, f));\n}\n\nexport function dirExists(repoRoot: string, relativePath: string): boolean {\n const full = join(repoRoot, relativePath);\n if (!existsSync(full)) return false;\n return statSync(full).isDirectory();\n}\n\nexport function parseFrontmatter(text: string): { data: Record<string, unknown>; body: string } {\n const m = matter(text);\n return { data: m.data, body: m.content };\n}\n\nexport interface GitLogEntry {\n hash: string;\n timestamp: number;\n}\n\nexport function gitLastCommitForPath(\n repoRoot: string,\n relativePath: string,\n): GitLogEntry | undefined {\n try {\n const out = execFileSync(\"git\", [\"log\", \"-1\", \"--format=%H:%ct\", \"--\", relativePath], {\n cwd: repoRoot,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n if (!out) return undefined;\n const idx = out.indexOf(\":\");\n if (idx < 0) return undefined;\n const hash = out.slice(0, idx);\n const ts = Number.parseInt(out.slice(idx + 1), 10);\n return { hash, timestamp: ts };\n } catch {\n return undefined;\n }\n}\n\nexport function gitCommitsSince(\n repoRoot: string,\n relativePath: string,\n sinceCommit: string,\n): number {\n try {\n const out = execFileSync(\n \"git\",\n [\"log\", `${sinceCommit}..HEAD`, \"--format=%H\", \"--\", relativePath],\n {\n cwd: repoRoot,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (!out) return 0;\n return out.split(\"\\n\").filter(Boolean).length;\n } catch {\n return 0;\n }\n}\n\nexport function relPath(repoRoot: string, abs: string): string {\n return relative(repoRoot, abs);\n}\n\nexport function findLineNumber(text: string, needle: string | RegExp): number | undefined {\n const lines = text.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n if (typeof needle === \"string\" ? line.includes(needle) : needle.test(line)) {\n return i + 1;\n }\n }\n return undefined;\n}\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, parseFrontmatter, readText } from \"./util.js\";\n\nconst ADR_DIR = \"docs/decisions\";\n\nexport const adrNoTracks: Linter = {\n id: \"lint:adr-no-tracks\",\n pillars: [\"decisions\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/decision\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, ADR_DIR);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!(\"tracks\" in data)) continue;\n const value = data.tracks;\n if (Array.isArray(value) && value.length === 0) continue;\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message:\n \"ADRs must not declare `tracks:` (or must set it to []). ADRs document a decision frozen in time, not the current state of code — use status: superseded to mark them obsolete instead.\",\n });\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists } from \"./util.js\";\n\nconst REQUIRED_FILES = [\n \"docs/tests/integration-tests.md\",\n \"docs/tests/e2e-tests.md\",\n \"docs/workflow/coding-workflow.md\",\n];\n\nexport const cardinalFiles: Linter = {\n id: \"lint:cardinal-files\",\n pillars: [\"tests\", \"workflow\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/test-plan\", \"/workflow\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const f of REQUIRED_FILES) {\n if (!fileExists(repoRoot, f)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `missing required cardinal file: ${f}`,\n });\n }\n }\n return issues;\n },\n};\n","import { cosmiconfigSync } from \"cosmiconfig\";\n\nexport interface HarnessLintConfig {\n ignore: ReadonlyArray<string>;\n}\n\nconst DEFAULT_CONFIG: HarnessLintConfig = { ignore: [] };\n\nexport function loadLintConfig(repoRoot: string): HarnessLintConfig {\n const explorer = cosmiconfigSync(\"harness\", {\n searchPlaces: [\n \"package.json\",\n \".harnessrc\",\n \".harnessrc.json\",\n \".harnessrc.yaml\",\n \".harnessrc.yml\",\n \"harness.config.js\",\n \"harness.config.cjs\",\n ],\n });\n const result = explorer.search(repoRoot);\n if (!result || result.isEmpty) return DEFAULT_CONFIG;\n\n const raw = result.config as Record<string, unknown> | undefined;\n if (!raw) return DEFAULT_CONFIG;\n\n const lint = raw.lint as Record<string, unknown> | undefined;\n if (!lint) return DEFAULT_CONFIG;\n\n const ignore = lint.ignore;\n if (!Array.isArray(ignore)) return DEFAULT_CONFIG;\n\n return { ignore: ignore.filter((x): x is string => typeof x === \"string\") };\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintContext, LintIssue, Linter } from \"./types.js\";\nimport { readText } from \"./util.js\";\n\nconst LINK_REGEX = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\nfunction maskCodeSpans(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, (m) => m.replace(/[^\\n]/g, \" \"))\n .replace(/`[^`\\n]*`/g, (m) => \" \".repeat(m.length));\n}\n\nfunction isExternalOrAnchor(url: string): boolean {\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) return true;\n if (url.startsWith(\"mailto:\")) return true;\n if (url.startsWith(\"#\")) return true;\n return false;\n}\n\nfunction stripFragment(url: string): string {\n const i = url.indexOf(\"#\");\n return i < 0 ? url : url.slice(0, i);\n}\n\nfunction resolveLink(repoRoot: string, sourceFile: string, link: string): string {\n const noFrag = stripFragment(link);\n if (isAbsolute(noFrag)) return join(repoRoot, noFrag);\n return resolve(dirname(join(repoRoot, sourceFile)), noFrag);\n}\n\nexport const crossLinks: Linter = {\n id: \"lint:cross-links\",\n pillars: [\"all\"],\n defaultSeverity: \"error\",\n run(repoRoot: string, ctx?: LintContext): LintIssue[] {\n const extra = ctx?.config.ignore ?? [];\n const issues: LintIssue[] = [];\n const allDocs = fg.sync(\"**/*.md\", {\n cwd: repoRoot,\n absolute: false,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \".harness-overrides/**\",\n \"skills/**\",\n \"agents/**\",\n \"commands/**\",\n \"templates/**\",\n \"**/*.skill.md\",\n \"**/*.cmd.md\",\n \"**/*.agent.md\",\n ...extra,\n ],\n });\n for (const f of allDocs) {\n const text = maskCodeSpans(readText(repoRoot, f));\n const lines = text.split(\"\\n\");\n for (let lineNo = 0; lineNo < lines.length; lineNo++) {\n const line = lines[lineNo] ?? \"\";\n LINK_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\n match = LINK_REGEX.exec(line);\n while (match !== null) {\n const url = match[2];\n if (url && !isExternalOrAnchor(url)) {\n const noFrag = stripFragment(url);\n if (noFrag !== \"\" && !noFrag.startsWith(\"data:\")) {\n const target = resolveLink(repoRoot, f, url);\n if (!existsSync(target)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n line: lineNo + 1,\n message: `broken link: ${url}`,\n });\n }\n }\n }\n match = LINK_REGEX.exec(line);\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, readText } from \"./util.js\";\n\n// English headings are canonical; the French ones are still accepted so\n// projects written before the switch to English keep passing.\nconst REQUIRED_SECTIONS: Array<{ label: string; pattern: RegExp }> = [\n { label: \"Context\", pattern: /^##\\s+(Context|Contexte)\\b/m },\n { label: \"Decision\", pattern: /^##\\s+(Decision|D[ée]cision)\\b/m },\n { label: \"Consequences\", pattern: /^##\\s+(Consequences|Cons[ée]quences)\\b/m },\n {\n label: \"Rejected alternatives\",\n pattern: /^##\\s+(Rejected\\s+alternatives|Alternatives(\\s+rejet[ée]es)?)\\b/im,\n },\n];\n\nexport const decisionSchema: Linter = {\n id: \"lint:decision-schema\",\n pillars: [\"decisions\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/decision\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, \"docs/decisions\");\n for (const f of files) {\n const text = readText(repoRoot, f);\n for (const { label, pattern } of REQUIRED_SECTIONS) {\n if (!pattern.test(text)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `missing required section: \"${label}\"`,\n });\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists } from \"./util.js\";\n\nconst REQUIRED_DIRS = [\n \"docs/spec\",\n \"docs/decisions\",\n \"docs/handbook\",\n \"docs/tests\",\n \"docs/workflow\",\n];\n\nexport const docLayout: Linter = {\n id: \"lint:doc-layout\",\n pillars: [\"all\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of REQUIRED_DIRS) {\n if (!dirExists(repoRoot, dir)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: dir,\n message: `missing required directory: ${dir}/`,\n });\n }\n }\n return issues;\n },\n};\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, parseFrontmatter, readText } from \"./util.js\";\n\nconst TRACKED_DIRS = [\"docs/handbook\", \"docs/tests\", \"docs/workflow\"];\n\nfunction trackedPathExists(repoRoot: string, pattern: string): boolean {\n if (pattern.includes(\"*\")) {\n return fg.sync(pattern, { cwd: repoRoot, absolute: false }).length > 0;\n }\n return existsSync(join(repoRoot, pattern));\n}\n\nexport const frontmatterTracks: Linter = {\n id: \"lint:frontmatter-tracks\",\n pillars: [\"handbook\", \"tests\", \"workflow\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/handbook\", \"/test-plan\", \"/workflow\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of TRACKED_DIRS) {\n const files = listMarkdown(repoRoot, dir);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!Array.isArray(data.tracks)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: \"missing frontmatter `tracks:` array\",\n });\n continue;\n }\n for (const tracked of data.tracks as unknown[]) {\n if (typeof tracked !== \"string\") {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: \"`tracks` entries must be strings\",\n });\n continue;\n }\n if (!trackedPathExists(repoRoot, tracked)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `tracked path does not exist: ${tracked}`,\n });\n }\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists, readText } from \"./util.js\";\n\nconst REQUIRED_ENTRIES = [\n \".harness-overrides/\",\n \".harness-manifest.json\",\n \".claude/\",\n \".codex/\",\n \".opencode/\",\n \".agents/\",\n \".vaubaan/\",\n \"AGENTS.md\",\n \"CLAUDE.md\",\n \".mcp.json\",\n];\n\nexport const gitignoreHarness: Linter = {\n id: \"lint:gitignore-harness\",\n pillars: [\"repo-root\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n if (!fileExists(repoRoot, \".gitignore\")) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \".gitignore\",\n message: \".gitignore is missing at repo root\",\n });\n return issues;\n }\n const text = readText(repoRoot, \".gitignore\");\n const lines = new Set(\n text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean),\n );\n for (const entry of REQUIRED_ENTRIES) {\n const variants = [entry, entry.replace(/\\/$/, \"\")];\n if (!variants.some((v) => lines.has(v))) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \".gitignore\",\n message: `.gitignore must contain \"${entry}\"`,\n });\n }\n }\n return issues;\n },\n};\n","import { basename } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintContext, LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, readText } from \"./util.js\";\n\nexport const orphanDecisions: Linter = {\n id: \"lint:orphan-decisions\",\n pillars: [\"decisions\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/handbook\"],\n run(repoRoot: string, ctx?: LintContext): LintIssue[] {\n const extra = ctx?.config.ignore ?? [];\n const issues: LintIssue[] = [];\n const adrs = listMarkdown(repoRoot, \"docs/decisions\");\n if (adrs.length === 0) return issues;\n\n const otherDocs = fg.sync(\"**/*.md\", {\n cwd: repoRoot,\n absolute: false,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \".harness-overrides/**\",\n \"docs/decisions/**\",\n \"skills/**\",\n \"agents/**\",\n \"commands/**\",\n \"templates/**\",\n ...extra,\n ],\n });\n\n const referenceCorpus = otherDocs.map((f) => readText(repoRoot, f)).join(\"\\n\");\n\n for (const adr of adrs) {\n const filename = basename(adr);\n const stem = filename.replace(/\\.md$/, \"\");\n const referenced = referenceCorpus.includes(filename) || referenceCorpus.includes(stem);\n if (!referenced) {\n issues.push({\n linter: this.id,\n severity: \"warning\",\n file: adr,\n message: \"ADR is not referenced by any other doc file (orphan)\",\n });\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists, fileExists, readText } from \"./util.js\";\n\nconst QUALITY_DIR = \"docs/quality\";\nconst CARDINAL_FILE = `${QUALITY_DIR}/code-quality.md`;\n\n// English headings are canonical; the French ones are still accepted so\n// projects written before the switch to English keep passing.\nconst REQUIRED_SECTIONS: ReadonlyArray<{ label: string; pattern: RegExp }> = [\n {\n label: \"Mechanical checks\",\n pattern: /^##\\s+(Mechanical\\s+checks|V[ée]rifications\\s+m[ée]caniques)\\s*$/im,\n },\n { label: \"Project rules\", pattern: /^##\\s+(Project\\s+rules|R[èe]gles\\s+du\\s+projet)\\s*$/im },\n {\n label: \"Accepted deviations\",\n pattern: /^##\\s+(Accepted\\s+deviations|[ÉE]carts\\s+assum[ée]s)\\s*$/im,\n },\n];\n\n/**\n * `docs/quality/` is an optional pillar: absent directory = nothing to\n * report. Once the directory exists, its cardinal file and sections are\n * required.\n */\nexport const qualitySchema: Linter = {\n id: \"lint:quality-schema\",\n pillars: [\"quality\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n if (!dirExists(repoRoot, QUALITY_DIR)) return [];\n if (!fileExists(repoRoot, CARDINAL_FILE)) {\n return [\n {\n linter: this.id,\n severity: \"error\",\n file: CARDINAL_FILE,\n message: `missing required cardinal file: ${CARDINAL_FILE}`,\n },\n ];\n }\n const text = readText(repoRoot, CARDINAL_FILE);\n return REQUIRED_SECTIONS.filter(({ pattern }) => !pattern.test(text)).map(({ label }) => ({\n linter: this.id,\n severity: \"error\" as const,\n file: CARDINAL_FILE,\n message: `missing required section: \"${label}\"`,\n }));\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists } from \"./util.js\";\n\nexport const readmePresence: Linter = {\n id: \"lint:readme-presence\",\n pillars: [\"readme\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/readme\"],\n run(repoRoot: string): LintIssue[] {\n if (fileExists(repoRoot, \"README.md\")) return [];\n return [\n {\n linter: this.id,\n severity: \"error\",\n file: \"README.md\",\n message: \"README.md is missing at repo root\",\n },\n ];\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists, readText } from \"./util.js\";\n\nconst REQUIRED_SECTIONS = [\n /^#\\s+\\S/m,\n /^##?\\s+(What is this|Quick start)/im,\n /^##?\\s+(Quick start|Install|Getting started)/im,\n];\n\nconst REQUIRED_LABELS = [\"title (# H1)\", \"What is this / intro\", \"Quick start / Install\"];\n\nexport const readmeSections: Linter = {\n id: \"lint:readme-sections\",\n pillars: [\"readme\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/readme\"],\n run(repoRoot: string): LintIssue[] {\n if (!fileExists(repoRoot, \"README.md\")) return [];\n const text = readText(repoRoot, \"README.md\");\n const issues: LintIssue[] = [];\n for (let i = 0; i < REQUIRED_SECTIONS.length; i++) {\n const pattern = REQUIRED_SECTIONS[i];\n const label = REQUIRED_LABELS[i];\n if (pattern && !pattern.test(text)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \"README.md\",\n message: `missing required section: ${label}`,\n });\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists, listMarkdown } from \"./util.js\";\n\nexport const specHasContent: Linter = {\n id: \"lint:spec-has-content\",\n pillars: [\"spec\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/spec\"],\n run(repoRoot: string): LintIssue[] {\n if (!dirExists(repoRoot, \"docs/spec\")) return [];\n const files = listMarkdown(repoRoot, \"docs/spec\");\n if (files.length > 0) return [];\n return [\n {\n linter: this.id,\n severity: \"warning\",\n file: \"docs/spec\",\n message: \"docs/spec/ has no markdown files — write at least one spec document\",\n },\n ];\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { findLineNumber, listMarkdown, readText } from \"./util.js\";\n\nfunction maskFencedBlocks(text: string): string {\n return text.replace(/```[\\s\\S]*?```/g, (m) => m.replace(/[^\\n]/g, \" \"));\n}\n\nconst TECH_BLOCKLIST: Array<{ pattern: RegExp; keyword: string }> = [\n { pattern: /\\bNext\\.?js\\b/i, keyword: \"Next.js\" },\n { pattern: /\\bSupabase\\b/i, keyword: \"Supabase\" },\n { pattern: /\\bVercel\\b/i, keyword: \"Vercel\" },\n { pattern: /\\bReact\\b/, keyword: \"React\" },\n { pattern: /\\bTypeScript\\b/, keyword: \"TypeScript\" },\n { pattern: /\\bDocker\\b/, keyword: \"Docker\" },\n { pattern: /\\bKubernetes\\b/i, keyword: \"Kubernetes\" },\n { pattern: /\\bPostgres(QL)?\\b/i, keyword: \"Postgres\" },\n { pattern: /\\bMySQL\\b/i, keyword: \"MySQL\" },\n { pattern: /\\bRedis\\b/i, keyword: \"Redis\" },\n { pattern: /\\bnpm\\b/, keyword: \"npm\" },\n { pattern: /\\bpnpm\\b/, keyword: \"pnpm\" },\n { pattern: /\\byarn\\b/, keyword: \"yarn\" },\n];\n\nexport const specTechAgnostic: Linter = {\n id: \"lint:spec-tech-agnostic\",\n pillars: [\"spec\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/spec\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, \"docs/spec\");\n for (const f of files) {\n const text = maskFencedBlocks(readText(repoRoot, f));\n for (const { pattern, keyword } of TECH_BLOCKLIST) {\n if (pattern.test(text)) {\n const issue: LintIssue = {\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `forbidden tech keyword \"${keyword}\" — should be in handbook/ or decisions/`,\n };\n const line = findLineNumber(text, pattern);\n if (line !== undefined) issue.line = line;\n issues.push(issue);\n }\n }\n }\n return issues;\n },\n};\n","import fg from \"fast-glob\";\nimport type { LintIssue, Linter } from \"./types.js\";\nimport {\n gitCommitsSince,\n gitLastCommitForPath,\n listMarkdown,\n parseFrontmatter,\n readText,\n} from \"./util.js\";\n\nconst TRACKED_DIRS = [\"docs/handbook\", \"docs/tests\", \"docs/workflow\"];\nconst DEFAULT_DRIFT_THRESHOLD = 10;\n\nfunction expandGlob(repoRoot: string, pattern: string): string[] {\n if (pattern.includes(\"*\")) {\n return fg.sync(pattern, { cwd: repoRoot, absolute: false });\n }\n return [pattern];\n}\n\nexport const tracksDrift: Linter = {\n id: \"lint:tracks-drift\",\n pillars: [\"handbook\", \"tests\", \"workflow\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/drift\", \"/handbook\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of TRACKED_DIRS) {\n const files = listMarkdown(repoRoot, dir);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!Array.isArray(data.tracks)) continue;\n const docCommit = gitLastCommitForPath(repoRoot, f);\n if (!docCommit) continue;\n const threshold =\n typeof data.driftThreshold === \"number\" && data.driftThreshold > 0\n ? data.driftThreshold\n : DEFAULT_DRIFT_THRESHOLD;\n let driftCount = 0;\n for (const tracked of data.tracks) {\n if (typeof tracked !== \"string\") continue;\n const targets = expandGlob(repoRoot, tracked);\n for (const t of targets.length > 0 ? targets : [tracked]) {\n driftCount += gitCommitsSince(repoRoot, t, docCommit.hash);\n }\n }\n if (driftCount > threshold) {\n issues.push({\n linter: this.id,\n severity: \"warning\",\n file: f,\n message: `tracked files changed in ${driftCount} commits since doc last edit (threshold ${threshold})`,\n });\n }\n }\n }\n return issues;\n },\n};\n","import { adrNoTracks } from \"./adr-no-tracks.js\";\nimport { cardinalFiles } from \"./cardinal-files.js\";\nimport { loadLintConfig } from \"./config.js\";\nimport { crossLinks } from \"./cross-links.js\";\nimport { decisionSchema } from \"./decision-schema.js\";\nimport { docLayout } from \"./doc-layout.js\";\nimport { frontmatterTracks } from \"./frontmatter-tracks.js\";\nimport { gitignoreHarness } from \"./gitignore-harness.js\";\nimport { orphanDecisions } from \"./orphan-decisions.js\";\nimport { qualitySchema } from \"./quality-schema.js\";\nimport { readmePresence } from \"./readme-presence.js\";\nimport { readmeSections } from \"./readme-sections.js\";\nimport { specHasContent } from \"./spec-has-content.js\";\nimport { specTechAgnostic } from \"./spec-tech-agnostic.js\";\nimport { tracksDrift } from \"./tracks-drift.js\";\nimport type { LintContext, LintIssue, LintReport, Linter } from \"./types.js\";\n\nexport const ALL_LINTERS: ReadonlyArray<Linter> = [\n readmePresence,\n readmeSections,\n docLayout,\n cardinalFiles,\n decisionSchema,\n adrNoTracks,\n frontmatterTracks,\n tracksDrift,\n specTechAgnostic,\n specHasContent,\n crossLinks,\n orphanDecisions,\n gitignoreHarness,\n qualitySchema,\n];\n\nexport interface RunOptions {\n pillar?: string;\n only?: ReadonlyArray<string>;\n}\n\nexport function runLinters(repoRoot: string, opts: RunOptions = {}): LintReport {\n const config = loadLintConfig(repoRoot);\n const ctx: LintContext = { repoRoot, config };\n const filtered = ALL_LINTERS.filter((l) => {\n if (opts.only && !opts.only.includes(l.id)) return false;\n if (opts.pillar && !l.pillars.includes(opts.pillar) && !l.pillars.includes(\"all\")) return false;\n return true;\n });\n const issues: LintIssue[] = [];\n for (const linter of filtered) {\n try {\n issues.push(...linter.run(repoRoot, ctx));\n } catch (err) {\n issues.push({\n linter: linter.id,\n severity: \"error\",\n file: \"<internal>\",\n message: `linter crashed: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n const errors = issues.filter((i) => i.severity === \"error\").length;\n const warnings = issues.filter((i) => i.severity === \"warning\").length;\n return {\n version: 1,\n summary: { errors, warnings, pass: errors === 0 },\n issues,\n };\n}\n\nfunction suggestedSkillsFor(linterId: string): ReadonlyArray<string> {\n const linter = ALL_LINTERS.find((l) => l.id === linterId);\n return linter?.suggestedSkills ?? [];\n}\n\nexport function formatReportText(report: LintReport): string {\n const lines: string[] = [];\n for (const issue of report.issues) {\n const location = issue.line !== undefined ? `:${issue.line}` : \"\";\n lines.push(`${issue.file}${location}: ${issue.severity}[${issue.linter}]\\n ${issue.message}`);\n const hints = suggestedSkillsFor(issue.linter);\n if (hints.length > 0) {\n lines.push(` → Try: ${hints.join(\", \")}`);\n }\n }\n const { errors, warnings, pass } = report.summary;\n lines.push(\"\");\n lines.push(\n `${errors} ${errors === 1 ? \"error\" : \"errors\"}, ${warnings} ${warnings === 1 ? \"warning\" : \"warnings\"}. ${pass ? \"✔ CI passes.\" : \"✘ CI fails.\"}`,\n );\n return lines.join(\"\\n\");\n}\n\nexport type { HarnessLintConfig } from \"./config.js\";\nexport { loadLintConfig } from \"./config.js\";\nexport type { LintContext, Linter, LintIssue, LintReport, Severity } from \"./types.js\";\n"],"mappings":"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","names":["existsSync","join","fg","join","fg","existsSync","existsSync","join","fg","fg","existsSync","join","fg","fg","REQUIRED_SECTIONS","REQUIRED_SECTIONS","fg","TRACKED_DIRS","fg"]}
|
|
1
|
+
{"version":3,"sources":["../../src/linters/util.ts","../../src/linters/adr-no-tracks.ts","../../src/linters/cardinal-files.ts","../../src/linters/config.ts","../../src/linters/cross-links.ts","../../src/linters/decision-schema.ts","../../src/linters/doc-layout.ts","../../src/linters/frontmatter-tracks.ts","../../src/linters/gitignore-harness.ts","../../src/linters/orphan-decisions.ts","../../src/linters/quality-schema.ts","../../src/linters/readme-presence.ts","../../src/linters/readme-sections.ts","../../src/linters/spec-has-content.ts","../../src/linters/spec-tech-agnostic.ts","../../src/linters/tracks-drift.ts","../../src/linters/index.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\n\nexport function fileExists(repoRoot: string, relativePath: string): boolean {\n return existsSync(join(repoRoot, relativePath));\n}\n\nexport function readText(repoRoot: string, relativePath: string): string {\n return readFileSync(join(repoRoot, relativePath), \"utf8\");\n}\n\nexport function listMarkdown(repoRoot: string, dir: string): string[] {\n const cwd = join(repoRoot, dir);\n if (!existsSync(cwd)) return [];\n return fg\n .sync(\"**/*.md\", { cwd, absolute: false, ignore: [\"**/node_modules/**\", \"**/dist/**\"] })\n .filter((f) => !f.endsWith(\"README.md\"))\n .map((f) => join(dir, f));\n}\n\nexport function listAllMarkdown(repoRoot: string, dir: string): string[] {\n const cwd = join(repoRoot, dir);\n if (!existsSync(cwd)) return [];\n return fg\n .sync(\"**/*.md\", { cwd, absolute: false, ignore: [\"**/node_modules/**\", \"**/dist/**\"] })\n .map((f) => join(dir, f));\n}\n\nexport function dirExists(repoRoot: string, relativePath: string): boolean {\n const full = join(repoRoot, relativePath);\n if (!existsSync(full)) return false;\n return statSync(full).isDirectory();\n}\n\nexport function parseFrontmatter(text: string): { data: Record<string, unknown>; body: string } {\n const m = matter(text);\n return { data: m.data, body: m.content };\n}\n\nexport interface GitLogEntry {\n hash: string;\n timestamp: number;\n}\n\nexport function gitLastCommitForPath(\n repoRoot: string,\n relativePath: string,\n): GitLogEntry | undefined {\n try {\n const out = execFileSync(\"git\", [\"log\", \"-1\", \"--format=%H:%ct\", \"--\", relativePath], {\n cwd: repoRoot,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n if (!out) return undefined;\n const idx = out.indexOf(\":\");\n if (idx < 0) return undefined;\n const hash = out.slice(0, idx);\n const ts = Number.parseInt(out.slice(idx + 1), 10);\n return { hash, timestamp: ts };\n } catch {\n return undefined;\n }\n}\n\nexport function gitCommitsSince(\n repoRoot: string,\n relativePath: string,\n sinceCommit: string,\n): number {\n try {\n const out = execFileSync(\n \"git\",\n [\"log\", `${sinceCommit}..HEAD`, \"--format=%H\", \"--\", relativePath],\n {\n cwd: repoRoot,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (!out) return 0;\n return out.split(\"\\n\").filter(Boolean).length;\n } catch {\n return 0;\n }\n}\n\nexport function relPath(repoRoot: string, abs: string): string {\n return relative(repoRoot, abs);\n}\n\nexport function findLineNumber(text: string, needle: string | RegExp): number | undefined {\n const lines = text.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n if (typeof needle === \"string\" ? line.includes(needle) : needle.test(line)) {\n return i + 1;\n }\n }\n return undefined;\n}\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, parseFrontmatter, readText } from \"./util.js\";\n\nconst ADR_DIR = \"docs/decisions\";\n\nexport const adrNoTracks: Linter = {\n id: \"lint:adr-no-tracks\",\n pillars: [\"decisions\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/decision\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, ADR_DIR);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!(\"tracks\" in data)) continue;\n const value = data.tracks;\n if (Array.isArray(value) && value.length === 0) continue;\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message:\n \"ADRs must not declare `tracks:` (or must set it to []). ADRs document a decision frozen in time, not the current state of code — use status: superseded to mark them obsolete instead.\",\n });\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists } from \"./util.js\";\n\nconst REQUIRED_FILES = [\n \"docs/tests/integration-tests.md\",\n \"docs/tests/e2e-tests.md\",\n \"docs/workflow/coding-workflow.md\",\n];\n\nexport const cardinalFiles: Linter = {\n id: \"lint:cardinal-files\",\n pillars: [\"tests\", \"workflow\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/test-plan\", \"/workflow\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const f of REQUIRED_FILES) {\n if (!fileExists(repoRoot, f)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `missing required cardinal file: ${f}`,\n });\n }\n }\n return issues;\n },\n};\n","import { cosmiconfigSync } from \"cosmiconfig\";\n\nexport interface HarnessLintConfig {\n ignore: ReadonlyArray<string>;\n}\n\nconst DEFAULT_CONFIG: HarnessLintConfig = { ignore: [] };\n\nexport function loadLintConfig(repoRoot: string): HarnessLintConfig {\n const explorer = cosmiconfigSync(\"harness\", {\n searchPlaces: [\n \"package.json\",\n \".harnessrc\",\n \".harnessrc.json\",\n \".harnessrc.yaml\",\n \".harnessrc.yml\",\n \"harness.config.js\",\n \"harness.config.cjs\",\n ],\n });\n const result = explorer.search(repoRoot);\n if (!result || result.isEmpty) return DEFAULT_CONFIG;\n\n const raw = result.config as Record<string, unknown> | undefined;\n if (!raw) return DEFAULT_CONFIG;\n\n const lint = raw.lint as Record<string, unknown> | undefined;\n if (!lint) return DEFAULT_CONFIG;\n\n const ignore = lint.ignore;\n if (!Array.isArray(ignore)) return DEFAULT_CONFIG;\n\n return { ignore: ignore.filter((x): x is string => typeof x === \"string\") };\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintContext, LintIssue, Linter } from \"./types.js\";\nimport { readText } from \"./util.js\";\n\nconst LINK_REGEX = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\nfunction maskCodeSpans(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, (m) => m.replace(/[^\\n]/g, \" \"))\n .replace(/`[^`\\n]*`/g, (m) => \" \".repeat(m.length));\n}\n\nfunction isExternalOrAnchor(url: string): boolean {\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) return true;\n if (url.startsWith(\"mailto:\")) return true;\n if (url.startsWith(\"#\")) return true;\n return false;\n}\n\nfunction stripFragment(url: string): string {\n const i = url.indexOf(\"#\");\n return i < 0 ? url : url.slice(0, i);\n}\n\nfunction resolveLink(repoRoot: string, sourceFile: string, link: string): string {\n const noFrag = stripFragment(link);\n if (isAbsolute(noFrag)) return join(repoRoot, noFrag);\n return resolve(dirname(join(repoRoot, sourceFile)), noFrag);\n}\n\nexport const crossLinks: Linter = {\n id: \"lint:cross-links\",\n pillars: [\"all\"],\n defaultSeverity: \"error\",\n run(repoRoot: string, ctx?: LintContext): LintIssue[] {\n const extra = ctx?.config.ignore ?? [];\n const issues: LintIssue[] = [];\n const allDocs = fg.sync(\"**/*.md\", {\n cwd: repoRoot,\n absolute: false,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \".harness-overrides/**\",\n \"skills/**\",\n \"agents/**\",\n \"commands/**\",\n \"templates/**\",\n \"**/*.skill.md\",\n \"**/*.cmd.md\",\n \"**/*.agent.md\",\n ...extra,\n ],\n });\n for (const f of allDocs) {\n const text = maskCodeSpans(readText(repoRoot, f));\n const lines = text.split(\"\\n\");\n for (let lineNo = 0; lineNo < lines.length; lineNo++) {\n const line = lines[lineNo] ?? \"\";\n LINK_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\n match = LINK_REGEX.exec(line);\n while (match !== null) {\n const url = match[2];\n if (url && !isExternalOrAnchor(url)) {\n const noFrag = stripFragment(url);\n if (noFrag !== \"\" && !noFrag.startsWith(\"data:\")) {\n const target = resolveLink(repoRoot, f, url);\n if (!existsSync(target)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n line: lineNo + 1,\n message: `broken link: ${url}`,\n });\n }\n }\n }\n match = LINK_REGEX.exec(line);\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, readText } from \"./util.js\";\n\n// English headings are canonical; the French ones are still accepted so\n// projects written before the switch to English keep passing.\nconst REQUIRED_SECTIONS: Array<{ label: string; pattern: RegExp }> = [\n { label: \"Context\", pattern: /^##\\s+(Context|Contexte)\\b/m },\n { label: \"Decision\", pattern: /^##\\s+(Decision|D[ée]cision)\\b/m },\n { label: \"Consequences\", pattern: /^##\\s+(Consequences|Cons[ée]quences)\\b/m },\n {\n label: \"Rejected alternatives\",\n pattern: /^##\\s+(Rejected\\s+alternatives|Alternatives(\\s+rejet[ée]es)?)\\b/im,\n },\n];\n\nexport const decisionSchema: Linter = {\n id: \"lint:decision-schema\",\n pillars: [\"decisions\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/decision\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, \"docs/decisions\");\n for (const f of files) {\n const text = readText(repoRoot, f);\n for (const { label, pattern } of REQUIRED_SECTIONS) {\n if (!pattern.test(text)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `missing required section: \"${label}\"`,\n });\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists } from \"./util.js\";\n\nconst REQUIRED_DIRS = [\n \"docs/spec\",\n \"docs/decisions\",\n \"docs/handbook\",\n \"docs/tests\",\n \"docs/workflow\",\n];\n\nexport const docLayout: Linter = {\n id: \"lint:doc-layout\",\n pillars: [\"all\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of REQUIRED_DIRS) {\n if (!dirExists(repoRoot, dir)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: dir,\n message: `missing required directory: ${dir}/`,\n });\n }\n }\n return issues;\n },\n};\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, parseFrontmatter, readText } from \"./util.js\";\n\nconst TRACKED_DIRS = [\"docs/handbook\", \"docs/tests\", \"docs/workflow\"];\n\nfunction trackedPathExists(repoRoot: string, pattern: string): boolean {\n if (pattern.includes(\"*\")) {\n return fg.sync(pattern, { cwd: repoRoot, absolute: false }).length > 0;\n }\n return existsSync(join(repoRoot, pattern));\n}\n\nexport const frontmatterTracks: Linter = {\n id: \"lint:frontmatter-tracks\",\n pillars: [\"handbook\", \"tests\", \"workflow\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/handbook\", \"/test-plan\", \"/workflow\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of TRACKED_DIRS) {\n const files = listMarkdown(repoRoot, dir);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!Array.isArray(data.tracks)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: \"missing frontmatter `tracks:` array\",\n });\n continue;\n }\n for (const tracked of data.tracks as unknown[]) {\n if (typeof tracked !== \"string\") {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: \"`tracks` entries must be strings\",\n });\n continue;\n }\n if (!trackedPathExists(repoRoot, tracked)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `tracked path does not exist: ${tracked}`,\n });\n }\n }\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists, readText } from \"./util.js\";\n\n/** Paths the harness generates or writes at runtime; none belongs in Git. */\nexport const REQUIRED_GITIGNORE_ENTRIES: ReadonlyArray<string> = [\n \".harness-overrides/\",\n \".harness-manifest.json\",\n \".harness/\",\n \".claude/\",\n \".codex/\",\n \".opencode/\",\n \".agents/\",\n \".vaubaan/\",\n \"AGENTS.md\",\n \"CLAUDE.md\",\n \".mcp.json\",\n];\n\nexport const gitignoreHarness: Linter = {\n id: \"lint:gitignore-harness\",\n pillars: [\"repo-root\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n if (!fileExists(repoRoot, \".gitignore\")) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \".gitignore\",\n message: \".gitignore is missing at repo root\",\n });\n return issues;\n }\n const text = readText(repoRoot, \".gitignore\");\n const lines = new Set(\n text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean),\n );\n for (const entry of REQUIRED_GITIGNORE_ENTRIES) {\n const variants = [entry, entry.replace(/\\/$/, \"\")];\n if (!variants.some((v) => lines.has(v))) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \".gitignore\",\n message: `.gitignore must contain \"${entry}\"`,\n });\n }\n }\n return issues;\n },\n};\n","import { basename } from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { LintContext, LintIssue, Linter } from \"./types.js\";\nimport { listMarkdown, readText } from \"./util.js\";\n\nexport const orphanDecisions: Linter = {\n id: \"lint:orphan-decisions\",\n pillars: [\"decisions\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/handbook\"],\n run(repoRoot: string, ctx?: LintContext): LintIssue[] {\n const extra = ctx?.config.ignore ?? [];\n const issues: LintIssue[] = [];\n const adrs = listMarkdown(repoRoot, \"docs/decisions\");\n if (adrs.length === 0) return issues;\n\n const otherDocs = fg.sync(\"**/*.md\", {\n cwd: repoRoot,\n absolute: false,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \".harness-overrides/**\",\n \"docs/decisions/**\",\n \"skills/**\",\n \"agents/**\",\n \"commands/**\",\n \"templates/**\",\n ...extra,\n ],\n });\n\n const referenceCorpus = otherDocs.map((f) => readText(repoRoot, f)).join(\"\\n\");\n\n for (const adr of adrs) {\n const filename = basename(adr);\n const stem = filename.replace(/\\.md$/, \"\");\n const referenced = referenceCorpus.includes(filename) || referenceCorpus.includes(stem);\n if (!referenced) {\n issues.push({\n linter: this.id,\n severity: \"warning\",\n file: adr,\n message: \"ADR is not referenced by any other doc file (orphan)\",\n });\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists, fileExists, readText } from \"./util.js\";\n\nconst QUALITY_DIR = \"docs/quality\";\nconst CARDINAL_FILE = `${QUALITY_DIR}/code-quality.md`;\n\n// English headings are canonical; the French ones are still accepted so\n// projects written before the switch to English keep passing.\nconst REQUIRED_SECTIONS: ReadonlyArray<{ label: string; pattern: RegExp }> = [\n {\n label: \"Mechanical checks\",\n pattern: /^##\\s+(Mechanical\\s+checks|V[ée]rifications\\s+m[ée]caniques)\\s*$/im,\n },\n { label: \"Project rules\", pattern: /^##\\s+(Project\\s+rules|R[èe]gles\\s+du\\s+projet)\\s*$/im },\n {\n label: \"Accepted deviations\",\n pattern: /^##\\s+(Accepted\\s+deviations|[ÉE]carts\\s+assum[ée]s)\\s*$/im,\n },\n];\n\n/**\n * `docs/quality/` is an optional pillar: absent directory = nothing to\n * report. Once the directory exists, its cardinal file and sections are\n * required.\n */\nexport const qualitySchema: Linter = {\n id: \"lint:quality-schema\",\n pillars: [\"quality\"],\n defaultSeverity: \"error\",\n run(repoRoot: string): LintIssue[] {\n if (!dirExists(repoRoot, QUALITY_DIR)) return [];\n if (!fileExists(repoRoot, CARDINAL_FILE)) {\n return [\n {\n linter: this.id,\n severity: \"error\",\n file: CARDINAL_FILE,\n message: `missing required cardinal file: ${CARDINAL_FILE}`,\n },\n ];\n }\n const text = readText(repoRoot, CARDINAL_FILE);\n return REQUIRED_SECTIONS.filter(({ pattern }) => !pattern.test(text)).map(({ label }) => ({\n linter: this.id,\n severity: \"error\" as const,\n file: CARDINAL_FILE,\n message: `missing required section: \"${label}\"`,\n }));\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists } from \"./util.js\";\n\nexport const readmePresence: Linter = {\n id: \"lint:readme-presence\",\n pillars: [\"readme\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/readme\"],\n run(repoRoot: string): LintIssue[] {\n if (fileExists(repoRoot, \"README.md\")) return [];\n return [\n {\n linter: this.id,\n severity: \"error\",\n file: \"README.md\",\n message: \"README.md is missing at repo root\",\n },\n ];\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { fileExists, readText } from \"./util.js\";\n\nconst REQUIRED_SECTIONS = [\n /^#\\s+\\S/m,\n /^##?\\s+(What is this|Quick start)/im,\n /^##?\\s+(Quick start|Install|Getting started)/im,\n];\n\nconst REQUIRED_LABELS = [\"title (# H1)\", \"What is this / intro\", \"Quick start / Install\"];\n\nexport const readmeSections: Linter = {\n id: \"lint:readme-sections\",\n pillars: [\"readme\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/readme\"],\n run(repoRoot: string): LintIssue[] {\n if (!fileExists(repoRoot, \"README.md\")) return [];\n const text = readText(repoRoot, \"README.md\");\n const issues: LintIssue[] = [];\n for (let i = 0; i < REQUIRED_SECTIONS.length; i++) {\n const pattern = REQUIRED_SECTIONS[i];\n const label = REQUIRED_LABELS[i];\n if (pattern && !pattern.test(text)) {\n issues.push({\n linter: this.id,\n severity: \"error\",\n file: \"README.md\",\n message: `missing required section: ${label}`,\n });\n }\n }\n return issues;\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { dirExists, listMarkdown } from \"./util.js\";\n\nexport const specHasContent: Linter = {\n id: \"lint:spec-has-content\",\n pillars: [\"spec\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/spec\"],\n run(repoRoot: string): LintIssue[] {\n if (!dirExists(repoRoot, \"docs/spec\")) return [];\n const files = listMarkdown(repoRoot, \"docs/spec\");\n if (files.length > 0) return [];\n return [\n {\n linter: this.id,\n severity: \"warning\",\n file: \"docs/spec\",\n message: \"docs/spec/ has no markdown files — write at least one spec document\",\n },\n ];\n },\n};\n","import type { LintIssue, Linter } from \"./types.js\";\nimport { findLineNumber, listMarkdown, readText } from \"./util.js\";\n\nfunction maskFencedBlocks(text: string): string {\n return text.replace(/```[\\s\\S]*?```/g, (m) => m.replace(/[^\\n]/g, \" \"));\n}\n\nconst TECH_BLOCKLIST: Array<{ pattern: RegExp; keyword: string }> = [\n { pattern: /\\bNext\\.?js\\b/i, keyword: \"Next.js\" },\n { pattern: /\\bSupabase\\b/i, keyword: \"Supabase\" },\n { pattern: /\\bVercel\\b/i, keyword: \"Vercel\" },\n { pattern: /\\bReact\\b/, keyword: \"React\" },\n { pattern: /\\bTypeScript\\b/, keyword: \"TypeScript\" },\n { pattern: /\\bDocker\\b/, keyword: \"Docker\" },\n { pattern: /\\bKubernetes\\b/i, keyword: \"Kubernetes\" },\n { pattern: /\\bPostgres(QL)?\\b/i, keyword: \"Postgres\" },\n { pattern: /\\bMySQL\\b/i, keyword: \"MySQL\" },\n { pattern: /\\bRedis\\b/i, keyword: \"Redis\" },\n { pattern: /\\bnpm\\b/, keyword: \"npm\" },\n { pattern: /\\bpnpm\\b/, keyword: \"pnpm\" },\n { pattern: /\\byarn\\b/, keyword: \"yarn\" },\n];\n\nexport const specTechAgnostic: Linter = {\n id: \"lint:spec-tech-agnostic\",\n pillars: [\"spec\"],\n defaultSeverity: \"error\",\n suggestedSkills: [\"/spec\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n const files = listMarkdown(repoRoot, \"docs/spec\");\n for (const f of files) {\n const text = maskFencedBlocks(readText(repoRoot, f));\n for (const { pattern, keyword } of TECH_BLOCKLIST) {\n if (pattern.test(text)) {\n const issue: LintIssue = {\n linter: this.id,\n severity: \"error\",\n file: f,\n message: `forbidden tech keyword \"${keyword}\" — should be in handbook/ or decisions/`,\n };\n const line = findLineNumber(text, pattern);\n if (line !== undefined) issue.line = line;\n issues.push(issue);\n }\n }\n }\n return issues;\n },\n};\n","import fg from \"fast-glob\";\nimport type { LintIssue, Linter } from \"./types.js\";\nimport {\n gitCommitsSince,\n gitLastCommitForPath,\n listMarkdown,\n parseFrontmatter,\n readText,\n} from \"./util.js\";\n\nconst TRACKED_DIRS = [\"docs/handbook\", \"docs/tests\", \"docs/workflow\"];\nconst DEFAULT_DRIFT_THRESHOLD = 10;\n\nfunction expandGlob(repoRoot: string, pattern: string): string[] {\n if (pattern.includes(\"*\")) {\n return fg.sync(pattern, { cwd: repoRoot, absolute: false });\n }\n return [pattern];\n}\n\nexport const tracksDrift: Linter = {\n id: \"lint:tracks-drift\",\n pillars: [\"handbook\", \"tests\", \"workflow\"],\n defaultSeverity: \"warning\",\n suggestedSkills: [\"/drift\", \"/handbook\"],\n run(repoRoot: string): LintIssue[] {\n const issues: LintIssue[] = [];\n for (const dir of TRACKED_DIRS) {\n const files = listMarkdown(repoRoot, dir);\n for (const f of files) {\n const text = readText(repoRoot, f);\n const { data } = parseFrontmatter(text);\n if (!Array.isArray(data.tracks)) continue;\n const docCommit = gitLastCommitForPath(repoRoot, f);\n if (!docCommit) continue;\n const threshold =\n typeof data.driftThreshold === \"number\" && data.driftThreshold > 0\n ? data.driftThreshold\n : DEFAULT_DRIFT_THRESHOLD;\n let driftCount = 0;\n for (const tracked of data.tracks) {\n if (typeof tracked !== \"string\") continue;\n const targets = expandGlob(repoRoot, tracked);\n for (const t of targets.length > 0 ? targets : [tracked]) {\n driftCount += gitCommitsSince(repoRoot, t, docCommit.hash);\n }\n }\n if (driftCount > threshold) {\n issues.push({\n linter: this.id,\n severity: \"warning\",\n file: f,\n message: `tracked files changed in ${driftCount} commits since doc last edit (threshold ${threshold})`,\n });\n }\n }\n }\n return issues;\n },\n};\n","import { adrNoTracks } from \"./adr-no-tracks.js\";\nimport { cardinalFiles } from \"./cardinal-files.js\";\nimport { loadLintConfig } from \"./config.js\";\nimport { crossLinks } from \"./cross-links.js\";\nimport { decisionSchema } from \"./decision-schema.js\";\nimport { docLayout } from \"./doc-layout.js\";\nimport { frontmatterTracks } from \"./frontmatter-tracks.js\";\nimport { gitignoreHarness } from \"./gitignore-harness.js\";\nimport { orphanDecisions } from \"./orphan-decisions.js\";\nimport { qualitySchema } from \"./quality-schema.js\";\nimport { readmePresence } from \"./readme-presence.js\";\nimport { readmeSections } from \"./readme-sections.js\";\nimport { specHasContent } from \"./spec-has-content.js\";\nimport { specTechAgnostic } from \"./spec-tech-agnostic.js\";\nimport { tracksDrift } from \"./tracks-drift.js\";\nimport type { LintContext, LintIssue, LintReport, Linter } from \"./types.js\";\n\nexport const ALL_LINTERS: ReadonlyArray<Linter> = [\n readmePresence,\n readmeSections,\n docLayout,\n cardinalFiles,\n decisionSchema,\n adrNoTracks,\n frontmatterTracks,\n tracksDrift,\n specTechAgnostic,\n specHasContent,\n crossLinks,\n orphanDecisions,\n gitignoreHarness,\n qualitySchema,\n];\n\nexport interface RunOptions {\n pillar?: string;\n only?: ReadonlyArray<string>;\n}\n\nexport function runLinters(repoRoot: string, opts: RunOptions = {}): LintReport {\n const config = loadLintConfig(repoRoot);\n const ctx: LintContext = { repoRoot, config };\n const filtered = ALL_LINTERS.filter((l) => {\n if (opts.only && !opts.only.includes(l.id)) return false;\n if (opts.pillar && !l.pillars.includes(opts.pillar) && !l.pillars.includes(\"all\")) return false;\n return true;\n });\n const issues: LintIssue[] = [];\n for (const linter of filtered) {\n try {\n issues.push(...linter.run(repoRoot, ctx));\n } catch (err) {\n issues.push({\n linter: linter.id,\n severity: \"error\",\n file: \"<internal>\",\n message: `linter crashed: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n const errors = issues.filter((i) => i.severity === \"error\").length;\n const warnings = issues.filter((i) => i.severity === \"warning\").length;\n return {\n version: 1,\n summary: { errors, warnings, pass: errors === 0 },\n issues,\n };\n}\n\nfunction suggestedSkillsFor(linterId: string): ReadonlyArray<string> {\n const linter = ALL_LINTERS.find((l) => l.id === linterId);\n return linter?.suggestedSkills ?? [];\n}\n\nexport function formatReportText(report: LintReport): string {\n const lines: string[] = [];\n for (const issue of report.issues) {\n const location = issue.line !== undefined ? `:${issue.line}` : \"\";\n lines.push(`${issue.file}${location}: ${issue.severity}[${issue.linter}]\\n ${issue.message}`);\n const hints = suggestedSkillsFor(issue.linter);\n if (hints.length > 0) {\n lines.push(` → Try: ${hints.join(\", \")}`);\n }\n }\n const { errors, warnings, pass } = report.summary;\n lines.push(\"\");\n lines.push(\n `${errors} ${errors === 1 ? \"error\" : \"errors\"}, ${warnings} ${warnings === 1 ? \"warning\" : \"warnings\"}. ${pass ? \"✔ CI passes.\" : \"✘ CI fails.\"}`,\n );\n return lines.join(\"\\n\");\n}\n\nexport type { HarnessLintConfig } from \"./config.js\";\nexport { loadLintConfig } from \"./config.js\";\nexport type { LintContext, Linter, LintIssue, LintReport, Severity } from \"./types.js\";\n"],"mappings":"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","names":["existsSync","join","fg","join","fg","existsSync","existsSync","join","fg","fg","existsSync","join","fg","fg","REQUIRED_SECTIONS","REQUIRED_SECTIONS","fg","TRACKED_DIRS","fg"]}
|
package/dist/render.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { H as Harness, a as HarnessOverrides, R as RenderError, b as RenderInput, c as RenderOutput, e as applyOverrides, r as render } from './render-
|
|
1
|
+
export { H as Harness, a as HarnessOverrides, R as RenderError, b as RenderInput, c as RenderOutput, e as applyOverrides, r as render } from './render-Cu2XrkkY.js';
|
|
2
2
|
import './parser/index.js';
|
|
3
3
|
import 'zod';
|
package/dist/render.js
CHANGED
|
@@ -387,18 +387,26 @@ ${body}` });
|
|
|
387
387
|
renderStopHook() {
|
|
388
388
|
const content = `import { spawnSync } from "node:child_process";
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
// Runs the harness lint and returns the report to inject, or "" when the
|
|
391
|
+
// turn may end.
|
|
392
|
+
const lintReport = (directory, sessionID) => {
|
|
393
|
+
const r = spawnSync(
|
|
394
|
+
"npx",
|
|
395
|
+
["-y", "${STOP_HOOK_PACKAGE}", "stop-hook", "--engine", "opencode", "--cwd", directory],
|
|
396
|
+
{ input: JSON.stringify({ sessionID }), encoding: "utf-8" },
|
|
397
|
+
);
|
|
398
|
+
const report = (r.stderr ?? "").trim();
|
|
399
|
+
return r.status === 2 ? report : "";
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// OpenCode 1.x entry point, exposed as server() below.
|
|
403
|
+
const HarnessStopHook = async ({ client, directory }) => ({
|
|
391
404
|
event: async ({ event }) => {
|
|
392
405
|
if (event.type !== "session.idle") return;
|
|
393
406
|
const sessionID = event.properties?.sessionID;
|
|
394
407
|
if (!sessionID) return;
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
["-y", "${STOP_HOOK_PACKAGE}", "stop-hook", "--engine", "opencode", "--cwd", directory],
|
|
398
|
-
{ input: JSON.stringify({ sessionID }), encoding: "utf-8" },
|
|
399
|
-
);
|
|
400
|
-
const report = (r.stderr ?? "").trim();
|
|
401
|
-
if (r.status !== 2 || report === "") return;
|
|
408
|
+
const report = lintReport(directory, sessionID);
|
|
409
|
+
if (report === "") return;
|
|
402
410
|
// OpenCode defaults to its build agent when a prompt omits agent.
|
|
403
411
|
// Preserve the last conversation persona, excluding compaction summaries.
|
|
404
412
|
const history = await client.session.messages({ path: { id: sessionID } }).catch(() => undefined);
|
|
@@ -414,6 +422,31 @@ export const HarnessStopHook = async ({ client, directory }) => ({
|
|
|
414
422
|
});
|
|
415
423
|
},
|
|
416
424
|
});
|
|
425
|
+
|
|
426
|
+
// One default export for both majors: OpenCode 1.x calls server(),
|
|
427
|
+
// OpenCode 2.x calls setup(). A V2 session keeps its agent, so the report
|
|
428
|
+
// is sent without naming the persona.
|
|
429
|
+
export default {
|
|
430
|
+
id: "vaubaan-harness-stop-hook",
|
|
431
|
+
server: HarnessStopHook,
|
|
432
|
+
setup(context) {
|
|
433
|
+
const directory = context.location.directory;
|
|
434
|
+
const controller = new AbortController();
|
|
435
|
+
const listen = async () => {
|
|
436
|
+
for await (const event of context.event.subscribe({ signal: controller.signal })) {
|
|
437
|
+
if (event.type !== "session.idle") continue;
|
|
438
|
+
if (event.location?.directory && event.location.directory !== directory) continue;
|
|
439
|
+
const sessionID = event.data?.sessionID;
|
|
440
|
+
if (!sessionID) continue;
|
|
441
|
+
const report = lintReport(directory, sessionID);
|
|
442
|
+
if (report === "") continue;
|
|
443
|
+
await context.session.prompt({ sessionID, text: report }).catch(() => undefined);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
listen().catch(() => undefined);
|
|
447
|
+
return () => controller.abort();
|
|
448
|
+
},
|
|
449
|
+
};
|
|
417
450
|
`;
|
|
418
451
|
return {
|
|
419
452
|
files: [{ path: `${BASE3}/plugins/harness-stop-hook.ts`, content }],
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp.ts","../src/renderers/common.ts","../src/types.ts","../src/renderers/claude-code.ts","../src/renderers/codex-cli.ts","../src/renderers/opencode.ts","../src/renderers/index.ts","../src/render.ts"],"sourcesContent":["export const MCP_SERVER_IDS = [\"context7\"] as const;\nexport type McpServerId = (typeof MCP_SERVER_IDS)[number];\n\nexport interface McpServerSpec {\n id: McpServerId;\n description: string;\n command: string;\n args: ReadonlyArray<string>;\n env?: Record<string, string>;\n}\n\nexport const MCP_SERVERS: Record<McpServerId, McpServerSpec> = {\n context7: {\n id: \"context7\",\n description:\n \"Upstash Context7 — fetches up-to-date library docs via resolve-library-id + get-library-docs.\",\n command: \"npx\",\n args: [\"-y\", \"@upstash/context7-mcp\"],\n },\n};\n\nexport function resolveMcpIds(requested: ReadonlyArray<string>): McpServerId[] {\n return requested.map((id) => {\n if (!(MCP_SERVER_IDS as ReadonlyArray<string>).includes(id)) {\n throw new Error(`unknown MCP server: ${id} (expected one of ${MCP_SERVER_IDS.join(\", \")})`);\n }\n return id as McpServerId;\n });\n}\n","import { stringify as yamlStringify } from \"yaml\";\nimport type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport {\n CAPABILITIES_MATRIX,\n type Capability,\n DEGRADED_CAPABILITIES,\n type Engine,\n type RenderedFile,\n} from \"../types.js\";\n\nexport interface UmbrellaExtras {\n availableSkills: ReadonlyArray<Skill>;\n availableCommands: ReadonlyArray<Cmd>;\n hasProjectSkills?: boolean;\n hasQualityDoc?: boolean;\n}\n\nexport interface RenderResult {\n files: RenderedFile[];\n warnings: string[];\n}\n\nexport interface Renderer {\n readonly engine: Engine;\n renderSkill(skill: Skill): RenderResult;\n renderCmd(cmd: Cmd): RenderResult;\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult;\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult;\n renderStopHook(): RenderResult;\n}\n\nexport const STOP_HOOK_PACKAGE = \"@vaubaan/harness\";\n\nexport interface CapabilityCheck {\n blockingWarnings: string[];\n degradedWarnings: string[];\n}\n\nexport function checkSkillCapabilities(skill: Skill, engine: Engine): CapabilityCheck {\n const blockingWarnings: string[] = [];\n const degradedWarnings: string[] = [];\n const supported = CAPABILITIES_MATRIX[engine];\n const degraded = DEGRADED_CAPABILITIES[engine] ?? [];\n if (!skill.meta.requires) return { blockingWarnings, degradedWarnings };\n for (const cap of skill.meta.requires) {\n if (!supported.includes(cap as Capability)) {\n blockingWarnings.push(\n `skill \"${skill.meta.id}\" requires \"${cap}\" not supported by \"${engine}\" — skipped`,\n );\n } else if (degraded.includes(cap as Capability)) {\n degradedWarnings.push(\n `skill \"${skill.meta.id}\" requires \"${cap}\" — supported by \"${engine}\" with degradation`,\n );\n }\n }\n return { blockingWarnings, degradedWarnings };\n}\n\nexport function isSkillEngineAllowed(skill: Skill, engine: Engine): boolean {\n if (!skill.meta.engines) return true;\n return skill.meta.engines.includes(engine);\n}\n\nexport function yamlFrontmatter(data: Record<string, unknown>): string {\n const rendered = yamlStringify(data, { lineWidth: 0 }).trimEnd();\n return `---\\n${rendered}\\n---\\n`;\n}\n\nconst SCALAR_NEEDS_QUOTE = /[^a-zA-Z0-9_./-]/;\n\nfunction tomlEscapeString(s: string): string {\n return `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\")}\"`;\n}\n\nfunction tomlValue(v: unknown): string {\n if (Array.isArray(v)) {\n return `[${v.map((x) => tomlValue(x)).join(\", \")}]`;\n }\n if (typeof v === \"string\") {\n if (v.includes(\"\\n\")) return tomlEscapeString(v);\n return SCALAR_NEEDS_QUOTE.test(v) ? tomlEscapeString(v) : `\"${v}\"`;\n }\n if (typeof v === \"boolean\" || typeof v === \"number\") {\n return String(v);\n }\n return tomlEscapeString(String(v));\n}\n\n/** TOML multi-line basic string (`\"\"\"…\"\"\"`), used for Codex `developer_instructions`. */\nexport function tomlMultilineString(s: string): string {\n const escaped = s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"\"\"/g, '\"\"\\\\\"');\n return `\"\"\"\\n${escaped}\\n\"\"\"`;\n}\n\nexport function tomlQuoteString(s: string): string {\n return tomlValue(s);\n}\n\nexport function tomlInlineArray(items: ReadonlyArray<unknown>): string {\n return tomlValue([...items]);\n}\n\nexport function tomlInlineTable(table: Record<string, string>): string {\n const entries = Object.entries(table).map(([k, v]) => `${k} = ${tomlValue(v)}`);\n return `{ ${entries.join(\", \")} }`;\n}\n\nexport function frontmatterDataForSkill(skill: Skill): Record<string, unknown> {\n // Agent Skills spec (agentskills.io): `name` must match the directory name.\n const data: Record<string, unknown> = {\n name: skill.meta.id,\n description: skill.meta.description,\n };\n if (skill.meta.tags && skill.meta.tags.length > 0) data.tags = skill.meta.tags;\n return data;\n}\n\nexport function frontmatterDataForCmd(cmd: Cmd): Record<string, unknown> {\n const data: Record<string, unknown> = {\n id: cmd.meta.id,\n trigger: cmd.meta.trigger,\n description: cmd.meta.description,\n };\n if (cmd.meta.args && cmd.meta.args.length > 0) {\n data.args = cmd.meta.args.map((a) => {\n if (a.type === \"enum\") {\n return a.default !== undefined\n ? { name: a.name, type: a.type, values: a.values, default: a.default }\n : { name: a.name, type: a.type, values: a.values };\n }\n return a.default !== undefined\n ? { name: a.name, type: a.type, default: a.default }\n : { name: a.name, type: a.type };\n });\n }\n return data;\n}\n\n/**\n * Personas carry their role contract only: engines discover skills and\n * commands natively, so listing them per persona is pure context cost.\n */\nexport function buildAgentBody(agent: Agent): string {\n return `${agent.body.trim()}\\n`;\n}\n\nexport function buildUmbrellaBody(_agents: ReadonlyArray<Agent>, extras: UmbrellaExtras): string {\n const triggerBySkill = new Map(\n extras.availableCommands.map((c) => [c.meta.invokes_skill, c.meta.trigger]),\n );\n const skillLines = extras.availableSkills.map((s) => {\n const trigger = triggerBySkill.get(s.meta.id);\n const label = trigger ? `\\`${s.meta.id}\\` (\\`${trigger}\\`)` : `\\`${s.meta.id}\\``;\n return `- ${label} — ${s.meta.description.split(\"\\n\")[0]}`;\n });\n const sections: string[] = [\n \"# How we work\",\n \"\",\n \"This file describes **how to work in this project**. The **business context** (what the project does, how to run it, which subsystems it contains) lives in `README.md` and `docs/`. Read them first.\",\n \"\",\n \"## Project map\",\n \"\",\n \"- `README.md` — pitch, quick start, scripts. Entry point.\",\n \"- `docs/spec/<subsystem>.md` — *what* and *why* (tech-agnostic).\",\n \"- `docs/handbook/<feature>.md` — *how it actually works* today.\",\n \"- `docs/decisions/<slug>.md` — ADRs: structural choices and rejected alternatives.\",\n \"- `docs/tests/integration-tests.md` & `e2e-tests.md` — core scenarios.\",\n \"- `docs/workflow/coding-workflow.md` — CI, branches, PR, release.\",\n ...(extras.hasProjectSkills\n ? [\"- `docs/skills/*.skill.md` — the project's domain skills.\"]\n : []),\n ...(extras.hasQualityDoc\n ? [\n \"- `docs/quality/code-quality.md` — the project's code-quality rules: mechanical checks, judgment rules, accepted deviations. Read it before writing or auditing code.\",\n ]\n : []),\n \"\",\n \"## Doc-first\",\n \"\",\n \"The order is: **understand the docs → fill the gaps → code → update the docs → commit**. If the docs are silent or stale on something you change, fill the gap upfront with the doc-skill writers — not downstream, not after the fact.\",\n \"\",\n \"- Missing spec for a subsystem you touch → **do not code**, report the gap.\",\n \"- Implicit structural decision in your changes → an ADR (`/decision`).\",\n \"- Changed feature behavior → update the matching handbook (`/handbook`).\",\n \"- Suspected code ↔ doc drift → `/drift` before moving on.\",\n ];\n if (skillLines.length > 0) {\n sections.push(\"\", \"## Doc-skills\", \"\", skillLines.join(\"\\n\"));\n }\n return `${sections.join(\"\\n\")}\\n`;\n}\n","export const AGENT_IDS = [\"coding\", \"review\", \"merge\", \"planning\"] as const;\nexport type AgentId = (typeof AGENT_IDS)[number];\n\nexport const ENGINES = [\"claude-code\", \"codex-cli\", \"opencode\"] as const;\nexport type Engine = (typeof ENGINES)[number];\n\nexport const ENGINE_ROOT_DIR: Record<Engine, string> = {\n \"claude-code\": \".claude\",\n \"codex-cli\": \".codex\",\n opencode: \".opencode\",\n};\n\nexport const CAPABILITIES = [\"multi-step\", \"slash-command\", \"inline-exec\"] as const;\nexport type Capability = (typeof CAPABILITIES)[number];\n\nexport interface RenderedFile {\n path: string;\n content: string;\n}\n\nexport interface CapabilitiesMatrix {\n [engine: string]: ReadonlyArray<Capability>;\n}\n\nexport const CAPABILITIES_MATRIX: Record<Engine, ReadonlyArray<Capability>> = {\n \"claude-code\": [\"multi-step\", \"slash-command\", \"inline-exec\"],\n \"codex-cli\": [\"multi-step\", \"slash-command\", \"inline-exec\"],\n opencode: [\"multi-step\", \"slash-command\", \"inline-exec\"],\n};\n\nexport const DEGRADED_CAPABILITIES: Partial<Record<Engine, ReadonlyArray<Capability>>> = {\n \"codex-cli\": [\"multi-step\"],\n};\n\nexport function engineSupports(engine: Engine, capability: Capability): boolean {\n return CAPABILITIES_MATRIX[engine].includes(capability);\n}\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForCmd,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"claude-code\";\nconst BASE = \".claude\";\n\nexport const claudeCodeRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${BASE}/skills/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n renderCmd(cmd: Cmd): RenderResult {\n const content = `${yamlFrontmatter(frontmatterDataForCmd(cmd))}\\n${cmd.body}\\n`;\n return {\n files: [{ path: `${BASE}/commands/${cmd.meta.id}.md`, content }],\n warnings: [],\n };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n const subagentFrontmatter = yamlFrontmatter({\n name: agent.meta.id,\n description: agent.meta.description,\n });\n const body = buildAgentBody(agent);\n const content = `${subagentFrontmatter}\\n${body}`;\n files.push({ path: `${BASE}/agents/${agent.meta.id}.md`, content });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const mcpServers: Record<string, Record<string, unknown>> = {};\n for (const s of servers) {\n const entry: Record<string, unknown> = { command: s.command, args: [...s.args] };\n if (s.env) entry.env = s.env;\n mcpServers[s.id] = entry;\n }\n const content = `${JSON.stringify({ mcpServers }, null, 2)}\\n`;\n return { files: [{ path: \".mcp.json\", content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const settings = {\n hooks: {\n Stop: [\n {\n matcher: \"\",\n hooks: [\n {\n type: \"command\",\n command: `npx -y ${STOP_HOOK_PACKAGE} stop-hook --engine claude`,\n },\n ],\n },\n ],\n },\n };\n return {\n files: [{ path: `${BASE}/settings.json`, content: `${JSON.stringify(settings, null, 2)}\\n` }],\n warnings: [],\n };\n },\n};\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n tomlInlineArray,\n tomlInlineTable,\n tomlMultilineString,\n tomlQuoteString,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"codex-cli\";\nconst BASE = \".codex\";\n// Codex discovers repo skills under `.agents/skills/` only — `.codex/skills/` is not scanned.\nconst SKILLS_BASE = \".agents/skills\";\n\nexport const codexCliRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${SKILLS_BASE}/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n // Codex custom prompts are deprecated in favour of skills: nothing to render.\n renderCmd(_cmd: Cmd): RenderResult {\n return { files: [], warnings: [] };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n const content = [\n `name = ${tomlQuoteString(agent.meta.id)}`,\n `description = ${tomlQuoteString(agent.meta.description)}`,\n `developer_instructions = ${tomlMultilineString(buildAgentBody(agent).trimEnd())}`,\n \"\",\n ].join(\"\\n\");\n files.push({ path: `${BASE}/agents/${agent.meta.id}.toml`, content });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const blocks: string[] = [];\n for (const s of servers) {\n const lines = [\n `[mcp_servers.${s.id}]`,\n `command = ${tomlQuoteString(s.command)}`,\n `args = ${tomlInlineArray([...s.args])}`,\n ];\n if (s.env) lines.push(`env = ${tomlInlineTable(s.env)}`);\n blocks.push(lines.join(\"\\n\"));\n }\n const content = `${blocks.join(\"\\n\\n\")}\\n`;\n return { files: [{ path: `${BASE}/config.toml`, content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const hooks = {\n hooks: {\n Stop: [\n {\n hooks: [\n {\n type: \"command\",\n command: `npx -y ${STOP_HOOK_PACKAGE} stop-hook --engine codex`,\n },\n ],\n },\n ],\n },\n };\n return {\n files: [{ path: `${BASE}/hooks.json`, content: `${JSON.stringify(hooks, null, 2)}\\n` }],\n warnings: [],\n };\n },\n};\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"opencode\";\nconst BASE = \".opencode\";\n\nexport const opencodeRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${BASE}/skills/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n renderCmd(cmd: Cmd): RenderResult {\n // OpenCode names the command after the file; only `description` is a known key.\n const content = `${yamlFrontmatter({ description: cmd.meta.description })}\\n${cmd.body}\\n`;\n return {\n files: [{ path: `${BASE}/commands/${cmd.meta.id}.md`, content }],\n warnings: [],\n };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n // The file name is the agent name. `mode: all` keeps each persona usable\n // both as a session's primary agent and as a subagent.\n const fm = yamlFrontmatter({\n description: agent.meta.description,\n mode: \"all\",\n });\n const body = buildAgentBody(agent);\n files.push({ path: `${BASE}/agents/${agent.meta.id}.md`, content: `${fm}\\n${body}` });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const mcp: Record<string, Record<string, unknown>> = {};\n for (const s of servers) {\n const entry: Record<string, unknown> = {\n type: \"local\",\n command: [s.command, ...s.args],\n enabled: true,\n };\n if (s.env) entry.environment = s.env;\n mcp[s.id] = entry;\n }\n const config = { $schema: \"https://opencode.ai/config.json\", mcp };\n const content = `${JSON.stringify(config, null, 2)}\\n`;\n return { files: [{ path: `${BASE}/opencode.json`, content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const content = `import { spawnSync } from \"node:child_process\";\n\nexport const HarnessStopHook = async ({ client, directory }) => ({\n event: async ({ event }) => {\n if (event.type !== \"session.idle\") return;\n const sessionID = event.properties?.sessionID;\n if (!sessionID) return;\n const r = spawnSync(\n \"npx\",\n [\"-y\", \"${STOP_HOOK_PACKAGE}\", \"stop-hook\", \"--engine\", \"opencode\", \"--cwd\", directory],\n { input: JSON.stringify({ sessionID }), encoding: \"utf-8\" },\n );\n const report = (r.stderr ?? \"\").trim();\n if (r.status !== 2 || report === \"\") return;\n // OpenCode defaults to its build agent when a prompt omits agent.\n // Preserve the last conversation persona, excluding compaction summaries.\n const history = await client.session.messages({ path: { id: sessionID } }).catch(() => undefined);\n if (history?.error || !Array.isArray(history?.data)) return;\n const current = history.data.findLast(({ info }) =>\n (info.role === \"user\" || (info.role === \"assistant\" && !info.summary)) &&\n typeof info.agent === \"string\" && info.agent.trim() !== \"\",\n );\n if (!current) return;\n await client.session.prompt({\n path: { id: sessionID },\n body: { agent: current.info.agent, parts: [{ type: \"text\", text: report }] },\n });\n },\n});\n`;\n return {\n files: [{ path: `${BASE}/plugins/harness-stop-hook.ts`, content }],\n warnings: [],\n };\n },\n};\n","import type { Engine } from \"../types.js\";\nimport { claudeCodeRenderer } from \"./claude-code.js\";\nimport { codexCliRenderer } from \"./codex-cli.js\";\nimport type { Renderer } from \"./common.js\";\nimport { opencodeRenderer } from \"./opencode.js\";\n\nexport const RENDERERS: Record<Engine, Renderer> = {\n \"claude-code\": claudeCodeRenderer,\n \"codex-cli\": codexCliRenderer,\n opencode: opencodeRenderer,\n};\n\nexport function getRenderer(engine: Engine): Renderer {\n return RENDERERS[engine];\n}\n\nexport type { Renderer, RenderResult } from \"./common.js\";\n","import { MCP_SERVERS, type McpServerId } from \"./mcp.js\";\nimport type { Agent } from \"./parser/agent.js\";\nimport type { Cmd } from \"./parser/cmd.js\";\nimport type { Skill } from \"./parser/skill.js\";\nimport { buildUmbrellaBody } from \"./renderers/common.js\";\nimport { getRenderer } from \"./renderers/index.js\";\nimport type { Engine, RenderedFile } from \"./types.js\";\n\nexport interface Harness {\n agents: ReadonlyArray<Agent>;\n skills: ReadonlyArray<Skill>;\n cmds: ReadonlyArray<Cmd>;\n}\n\nexport interface HarnessOverrides {\n add?: Partial<Harness>;\n disable?: ReadonlyArray<string>;\n}\n\nexport interface RenderInput {\n harness: Harness;\n engines: ReadonlyArray<Engine>;\n overrides?: HarnessOverrides;\n projectSkills?: ReadonlyArray<Skill>;\n /** True when the target project ships `docs/quality/code-quality.md` (optional pillar). */\n hasQualityDoc?: boolean;\n mcpServers?: ReadonlyArray<McpServerId>;\n /**\n * When true (default), each engine's renderer emits a Stop-hook config\n * that calls `vaubaan-harness stop-hook --engine <engine>` and injects\n * the lint report (with skill suggestions) into the running session\n * whenever the worktree is dirty and lint fails. Pass `false` to skip\n * the hook configs entirely (kill-switch at install time; runtime\n * kill-switch is `HARNESS_LINT_DISABLE=1`).\n */\n withStopHook?: boolean;\n}\n\nexport interface RenderOutput {\n files: RenderedFile[];\n warnings: string[];\n}\n\nexport class RenderError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RenderError\";\n }\n}\n\nfunction mergeById<T extends { meta: { id: string } }>(\n stock: ReadonlyArray<T>,\n added: ReadonlyArray<T> | undefined,\n): T[] {\n const result = new Map<string, T>();\n for (const item of stock) result.set(item.meta.id, item);\n if (added) {\n for (const item of added) result.set(item.meta.id, item);\n }\n return Array.from(result.values());\n}\n\nfunction removeByIds<T extends { meta: { id: string } }>(\n items: ReadonlyArray<T>,\n disabled: ReadonlyArray<string>,\n): T[] {\n if (disabled.length === 0) return [...items];\n const disabledSet = new Set(disabled);\n return items.filter((item) => !disabledSet.has(item.meta.id));\n}\n\nexport function applyOverrides(harness: Harness, overrides: HarnessOverrides | undefined): Harness {\n const disabled = overrides?.disable ?? [];\n return {\n agents: removeByIds(mergeById(harness.agents, overrides?.add?.agents), disabled),\n skills: removeByIds(mergeById(harness.skills, overrides?.add?.skills), disabled),\n cmds: removeByIds(mergeById(harness.cmds, overrides?.add?.cmds), disabled),\n };\n}\n\n// `@AGENTS.md` is Claude Code's import syntax: the umbrella is really loaded,\n// on every version and provider, and never read twice alongside native\n// AGENTS.md support (cf. ADR engine-native-layouts-1-0).\nconst CLAUDE_MD_STUB = \"@AGENTS.md\\n\";\n\n// OpenCode also scans `.claude/skills/` and `.agents/skills/`. It goes last so\n// its own copy of a skill is dropped when another rendered engine exposes it.\nfunction opencodeLast(engines: ReadonlyArray<Engine>): Engine[] {\n return [...engines].sort((a, b) => Number(a === \"opencode\") - Number(b === \"opencode\"));\n}\n\nexport function render(input: RenderInput): RenderOutput {\n const { engines } = input;\n if (engines.length === 0) {\n throw new RenderError(\"at least one engine must be selected\");\n }\n const base: Harness = input.projectSkills?.length\n ? { ...input.harness, skills: mergeById(input.harness.skills, input.projectSkills) }\n : input.harness;\n const harness = applyOverrides(base, input.overrides);\n if (harness.agents.length === 0) {\n throw new RenderError(\"No agents found in harness\");\n }\n\n const files: RenderedFile[] = [];\n const warnings: string[] = [];\n\n // Track skills + commands successfully exposed on at least one engine,\n // for the engine-neutral umbrella's \"Available skills/commands\" sections.\n const exposedSkills = new Map<string, Skill>();\n const exposedCmds = new Map<string, Cmd>();\n\n for (const engine of opencodeLast(engines)) {\n const renderer = getRenderer(engine);\n\n const renderedSkills: Skill[] = [];\n for (const skill of harness.skills) {\n const result = renderer.renderSkill(skill);\n warnings.push(...result.warnings);\n if (result.files.length > 0) {\n const alreadyVisible = engine === \"opencode\" && exposedSkills.has(skill.meta.id);\n if (!alreadyVisible) files.push(...result.files);\n renderedSkills.push(skill);\n exposedSkills.set(skill.meta.id, skill);\n }\n }\n\n const renderedSkillIds = new Set(renderedSkills.map((s) => s.meta.id));\n const renderedCmds: Cmd[] = [];\n for (const cmd of harness.cmds) {\n if (!renderedSkillIds.has(cmd.meta.invokes_skill)) continue;\n const result = renderer.renderCmd(cmd);\n warnings.push(...result.warnings);\n if (result.files.length > 0) {\n files.push(...result.files);\n renderedCmds.push(cmd);\n exposedCmds.set(cmd.meta.id, cmd);\n }\n }\n\n const agentsResult = renderer.renderAgents(harness.agents);\n warnings.push(...agentsResult.warnings);\n files.push(...agentsResult.files);\n\n if (input.mcpServers && input.mcpServers.length > 0) {\n const specs = input.mcpServers.map((id) => MCP_SERVERS[id]);\n const mcpResult = renderer.renderMcp(specs);\n warnings.push(...mcpResult.warnings);\n files.push(...mcpResult.files);\n }\n\n if (input.withStopHook !== false) {\n const hookResult = renderer.renderStopHook();\n warnings.push(...hookResult.warnings);\n files.push(...hookResult.files);\n }\n }\n\n // Engine-neutral umbrella at the root\n const hasProjectSkills = (input.projectSkills?.length ?? 0) > 0;\n const umbrellaContent = buildUmbrellaBody(harness.agents, {\n availableSkills: Array.from(exposedSkills.values()),\n availableCommands: Array.from(exposedCmds.values()),\n hasProjectSkills,\n hasQualityDoc: input.hasQualityDoc === true,\n });\n files.push({ path: \"AGENTS.md\", content: umbrellaContent });\n\n // CLAUDE.md stub only if claude-code is enabled (Claude Code reads it historically)\n if (engines.includes(\"claude-code\")) {\n files.push({ path: \"CLAUDE.md\", content: CLAUDE_MD_STUB });\n }\n\n return { files, warnings };\n}\n"],"mappings":"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","names":["ENGINE","BASE","ENGINE","BASE"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/renderers/common.ts","../src/types.ts","../src/renderers/claude-code.ts","../src/renderers/codex-cli.ts","../src/renderers/opencode.ts","../src/renderers/index.ts","../src/render.ts"],"sourcesContent":["export const MCP_SERVER_IDS = [\"context7\"] as const;\nexport type McpServerId = (typeof MCP_SERVER_IDS)[number];\n\nexport interface McpServerSpec {\n id: McpServerId;\n description: string;\n command: string;\n args: ReadonlyArray<string>;\n env?: Record<string, string>;\n}\n\nexport const MCP_SERVERS: Record<McpServerId, McpServerSpec> = {\n context7: {\n id: \"context7\",\n description:\n \"Upstash Context7 — fetches up-to-date library docs via resolve-library-id + get-library-docs.\",\n command: \"npx\",\n args: [\"-y\", \"@upstash/context7-mcp\"],\n },\n};\n\nexport function resolveMcpIds(requested: ReadonlyArray<string>): McpServerId[] {\n return requested.map((id) => {\n if (!(MCP_SERVER_IDS as ReadonlyArray<string>).includes(id)) {\n throw new Error(`unknown MCP server: ${id} (expected one of ${MCP_SERVER_IDS.join(\", \")})`);\n }\n return id as McpServerId;\n });\n}\n","import { stringify as yamlStringify } from \"yaml\";\nimport type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport {\n CAPABILITIES_MATRIX,\n type Capability,\n DEGRADED_CAPABILITIES,\n type Engine,\n type RenderedFile,\n} from \"../types.js\";\n\nexport interface UmbrellaExtras {\n availableSkills: ReadonlyArray<Skill>;\n availableCommands: ReadonlyArray<Cmd>;\n hasProjectSkills?: boolean;\n hasQualityDoc?: boolean;\n}\n\nexport interface RenderResult {\n files: RenderedFile[];\n warnings: string[];\n}\n\nexport interface Renderer {\n readonly engine: Engine;\n renderSkill(skill: Skill): RenderResult;\n renderCmd(cmd: Cmd): RenderResult;\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult;\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult;\n renderStopHook(): RenderResult;\n}\n\nexport const STOP_HOOK_PACKAGE = \"@vaubaan/harness\";\n\nexport interface CapabilityCheck {\n blockingWarnings: string[];\n degradedWarnings: string[];\n}\n\nexport function checkSkillCapabilities(skill: Skill, engine: Engine): CapabilityCheck {\n const blockingWarnings: string[] = [];\n const degradedWarnings: string[] = [];\n const supported = CAPABILITIES_MATRIX[engine];\n const degraded = DEGRADED_CAPABILITIES[engine] ?? [];\n if (!skill.meta.requires) return { blockingWarnings, degradedWarnings };\n for (const cap of skill.meta.requires) {\n if (!supported.includes(cap as Capability)) {\n blockingWarnings.push(\n `skill \"${skill.meta.id}\" requires \"${cap}\" not supported by \"${engine}\" — skipped`,\n );\n } else if (degraded.includes(cap as Capability)) {\n degradedWarnings.push(\n `skill \"${skill.meta.id}\" requires \"${cap}\" — supported by \"${engine}\" with degradation`,\n );\n }\n }\n return { blockingWarnings, degradedWarnings };\n}\n\nexport function isSkillEngineAllowed(skill: Skill, engine: Engine): boolean {\n if (!skill.meta.engines) return true;\n return skill.meta.engines.includes(engine);\n}\n\nexport function yamlFrontmatter(data: Record<string, unknown>): string {\n const rendered = yamlStringify(data, { lineWidth: 0 }).trimEnd();\n return `---\\n${rendered}\\n---\\n`;\n}\n\nconst SCALAR_NEEDS_QUOTE = /[^a-zA-Z0-9_./-]/;\n\nfunction tomlEscapeString(s: string): string {\n return `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\")}\"`;\n}\n\nfunction tomlValue(v: unknown): string {\n if (Array.isArray(v)) {\n return `[${v.map((x) => tomlValue(x)).join(\", \")}]`;\n }\n if (typeof v === \"string\") {\n if (v.includes(\"\\n\")) return tomlEscapeString(v);\n return SCALAR_NEEDS_QUOTE.test(v) ? tomlEscapeString(v) : `\"${v}\"`;\n }\n if (typeof v === \"boolean\" || typeof v === \"number\") {\n return String(v);\n }\n return tomlEscapeString(String(v));\n}\n\n/** TOML multi-line basic string (`\"\"\"…\"\"\"`), used for Codex `developer_instructions`. */\nexport function tomlMultilineString(s: string): string {\n const escaped = s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"\"\"/g, '\"\"\\\\\"');\n return `\"\"\"\\n${escaped}\\n\"\"\"`;\n}\n\nexport function tomlQuoteString(s: string): string {\n return tomlValue(s);\n}\n\nexport function tomlInlineArray(items: ReadonlyArray<unknown>): string {\n return tomlValue([...items]);\n}\n\nexport function tomlInlineTable(table: Record<string, string>): string {\n const entries = Object.entries(table).map(([k, v]) => `${k} = ${tomlValue(v)}`);\n return `{ ${entries.join(\", \")} }`;\n}\n\nexport function frontmatterDataForSkill(skill: Skill): Record<string, unknown> {\n // Agent Skills spec (agentskills.io): `name` must match the directory name.\n const data: Record<string, unknown> = {\n name: skill.meta.id,\n description: skill.meta.description,\n };\n if (skill.meta.tags && skill.meta.tags.length > 0) data.tags = skill.meta.tags;\n return data;\n}\n\nexport function frontmatterDataForCmd(cmd: Cmd): Record<string, unknown> {\n const data: Record<string, unknown> = {\n id: cmd.meta.id,\n trigger: cmd.meta.trigger,\n description: cmd.meta.description,\n };\n if (cmd.meta.args && cmd.meta.args.length > 0) {\n data.args = cmd.meta.args.map((a) => {\n if (a.type === \"enum\") {\n return a.default !== undefined\n ? { name: a.name, type: a.type, values: a.values, default: a.default }\n : { name: a.name, type: a.type, values: a.values };\n }\n return a.default !== undefined\n ? { name: a.name, type: a.type, default: a.default }\n : { name: a.name, type: a.type };\n });\n }\n return data;\n}\n\n/**\n * Personas carry their role contract only: engines discover skills and\n * commands natively, so listing them per persona is pure context cost.\n */\nexport function buildAgentBody(agent: Agent): string {\n return `${agent.body.trim()}\\n`;\n}\n\nexport function buildUmbrellaBody(_agents: ReadonlyArray<Agent>, extras: UmbrellaExtras): string {\n const triggerBySkill = new Map(\n extras.availableCommands.map((c) => [c.meta.invokes_skill, c.meta.trigger]),\n );\n const skillLines = extras.availableSkills.map((s) => {\n const trigger = triggerBySkill.get(s.meta.id);\n const label = trigger ? `\\`${s.meta.id}\\` (\\`${trigger}\\`)` : `\\`${s.meta.id}\\``;\n return `- ${label} — ${s.meta.description.split(\"\\n\")[0]}`;\n });\n const sections: string[] = [\n \"# How we work\",\n \"\",\n \"This file describes **how to work in this project**. The **business context** (what the project does, how to run it, which subsystems it contains) lives in `README.md` and `docs/`. Read them first.\",\n \"\",\n \"## Project map\",\n \"\",\n \"- `README.md` — pitch, quick start, scripts. Entry point.\",\n \"- `docs/spec/<subsystem>.md` — *what* and *why* (tech-agnostic).\",\n \"- `docs/handbook/<feature>.md` — *how it actually works* today.\",\n \"- `docs/decisions/<slug>.md` — ADRs: structural choices and rejected alternatives.\",\n \"- `docs/tests/integration-tests.md` & `e2e-tests.md` — core scenarios.\",\n \"- `docs/workflow/coding-workflow.md` — CI, branches, PR, release.\",\n ...(extras.hasProjectSkills\n ? [\"- `docs/skills/*.skill.md` — the project's domain skills.\"]\n : []),\n ...(extras.hasQualityDoc\n ? [\n \"- `docs/quality/code-quality.md` — the project's code-quality rules: mechanical checks, judgment rules, accepted deviations. Read it before writing or auditing code.\",\n ]\n : []),\n \"\",\n \"## Doc-first\",\n \"\",\n \"The order is: **understand the docs → fill the gaps → code → update the docs → commit**. If the docs are silent or stale on something you change, fill the gap upfront with the doc-skill writers — not downstream, not after the fact.\",\n \"\",\n \"- Missing spec for a subsystem you touch → **do not code**, report the gap.\",\n \"- Implicit structural decision in your changes → an ADR (`/decision`).\",\n \"- Changed feature behavior → update the matching handbook (`/handbook`).\",\n \"- Suspected code ↔ doc drift → `/drift` before moving on.\",\n ];\n if (skillLines.length > 0) {\n sections.push(\"\", \"## Doc-skills\", \"\", skillLines.join(\"\\n\"));\n }\n return `${sections.join(\"\\n\")}\\n`;\n}\n","export const AGENT_IDS = [\"coding\", \"review\", \"merge\", \"planning\"] as const;\nexport type AgentId = (typeof AGENT_IDS)[number];\n\nexport const ENGINES = [\"claude-code\", \"codex-cli\", \"opencode\"] as const;\nexport type Engine = (typeof ENGINES)[number];\n\nexport const ENGINE_ROOT_DIR: Record<Engine, string> = {\n \"claude-code\": \".claude\",\n \"codex-cli\": \".codex\",\n opencode: \".opencode\",\n};\n\nexport const CAPABILITIES = [\"multi-step\", \"slash-command\", \"inline-exec\"] as const;\nexport type Capability = (typeof CAPABILITIES)[number];\n\nexport interface RenderedFile {\n path: string;\n content: string;\n}\n\nexport interface CapabilitiesMatrix {\n [engine: string]: ReadonlyArray<Capability>;\n}\n\nexport const CAPABILITIES_MATRIX: Record<Engine, ReadonlyArray<Capability>> = {\n \"claude-code\": [\"multi-step\", \"slash-command\", \"inline-exec\"],\n \"codex-cli\": [\"multi-step\", \"slash-command\", \"inline-exec\"],\n opencode: [\"multi-step\", \"slash-command\", \"inline-exec\"],\n};\n\nexport const DEGRADED_CAPABILITIES: Partial<Record<Engine, ReadonlyArray<Capability>>> = {\n \"codex-cli\": [\"multi-step\"],\n};\n\nexport function engineSupports(engine: Engine, capability: Capability): boolean {\n return CAPABILITIES_MATRIX[engine].includes(capability);\n}\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForCmd,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"claude-code\";\nconst BASE = \".claude\";\n\nexport const claudeCodeRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${BASE}/skills/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n renderCmd(cmd: Cmd): RenderResult {\n const content = `${yamlFrontmatter(frontmatterDataForCmd(cmd))}\\n${cmd.body}\\n`;\n return {\n files: [{ path: `${BASE}/commands/${cmd.meta.id}.md`, content }],\n warnings: [],\n };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n const subagentFrontmatter = yamlFrontmatter({\n name: agent.meta.id,\n description: agent.meta.description,\n });\n const body = buildAgentBody(agent);\n const content = `${subagentFrontmatter}\\n${body}`;\n files.push({ path: `${BASE}/agents/${agent.meta.id}.md`, content });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const mcpServers: Record<string, Record<string, unknown>> = {};\n for (const s of servers) {\n const entry: Record<string, unknown> = { command: s.command, args: [...s.args] };\n if (s.env) entry.env = s.env;\n mcpServers[s.id] = entry;\n }\n const content = `${JSON.stringify({ mcpServers }, null, 2)}\\n`;\n return { files: [{ path: \".mcp.json\", content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const settings = {\n hooks: {\n Stop: [\n {\n matcher: \"\",\n hooks: [\n {\n type: \"command\",\n command: `npx -y ${STOP_HOOK_PACKAGE} stop-hook --engine claude`,\n },\n ],\n },\n ],\n },\n };\n return {\n files: [{ path: `${BASE}/settings.json`, content: `${JSON.stringify(settings, null, 2)}\\n` }],\n warnings: [],\n };\n },\n};\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n tomlInlineArray,\n tomlInlineTable,\n tomlMultilineString,\n tomlQuoteString,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"codex-cli\";\nconst BASE = \".codex\";\n// Codex discovers repo skills under `.agents/skills/` only — `.codex/skills/` is not scanned.\nconst SKILLS_BASE = \".agents/skills\";\n\nexport const codexCliRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${SKILLS_BASE}/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n // Codex custom prompts are deprecated in favour of skills: nothing to render.\n renderCmd(_cmd: Cmd): RenderResult {\n return { files: [], warnings: [] };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n const content = [\n `name = ${tomlQuoteString(agent.meta.id)}`,\n `description = ${tomlQuoteString(agent.meta.description)}`,\n `developer_instructions = ${tomlMultilineString(buildAgentBody(agent).trimEnd())}`,\n \"\",\n ].join(\"\\n\");\n files.push({ path: `${BASE}/agents/${agent.meta.id}.toml`, content });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const blocks: string[] = [];\n for (const s of servers) {\n const lines = [\n `[mcp_servers.${s.id}]`,\n `command = ${tomlQuoteString(s.command)}`,\n `args = ${tomlInlineArray([...s.args])}`,\n ];\n if (s.env) lines.push(`env = ${tomlInlineTable(s.env)}`);\n blocks.push(lines.join(\"\\n\"));\n }\n const content = `${blocks.join(\"\\n\\n\")}\\n`;\n return { files: [{ path: `${BASE}/config.toml`, content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const hooks = {\n hooks: {\n Stop: [\n {\n hooks: [\n {\n type: \"command\",\n command: `npx -y ${STOP_HOOK_PACKAGE} stop-hook --engine codex`,\n },\n ],\n },\n ],\n },\n };\n return {\n files: [{ path: `${BASE}/hooks.json`, content: `${JSON.stringify(hooks, null, 2)}\\n` }],\n warnings: [],\n };\n },\n};\n","import type { McpServerSpec } from \"../mcp.js\";\nimport type { Agent } from \"../parser/agent.js\";\nimport type { Cmd } from \"../parser/cmd.js\";\nimport type { Skill } from \"../parser/skill.js\";\nimport type { Engine } from \"../types.js\";\nimport {\n type RenderResult,\n type Renderer,\n STOP_HOOK_PACKAGE,\n buildAgentBody,\n checkSkillCapabilities,\n frontmatterDataForSkill,\n isSkillEngineAllowed,\n yamlFrontmatter,\n} from \"./common.js\";\n\nconst ENGINE: Engine = \"opencode\";\nconst BASE = \".opencode\";\n\nexport const opencodeRenderer: Renderer = {\n engine: ENGINE,\n renderSkill(skill: Skill): RenderResult {\n if (!isSkillEngineAllowed(skill, ENGINE)) {\n return {\n files: [],\n warnings: [`skill \"${skill.meta.id}\" excluded from engine \"${ENGINE}\"`],\n };\n }\n const check = checkSkillCapabilities(skill, ENGINE);\n if (check.blockingWarnings.length > 0) {\n return { files: [], warnings: check.blockingWarnings };\n }\n const content = `${yamlFrontmatter(frontmatterDataForSkill(skill))}\\n${skill.body}\\n`;\n return {\n files: [{ path: `${BASE}/skills/${skill.meta.id}/SKILL.md`, content }],\n warnings: check.degradedWarnings,\n };\n },\n renderCmd(cmd: Cmd): RenderResult {\n // OpenCode names the command after the file; only `description` is a known key.\n const content = `${yamlFrontmatter({ description: cmd.meta.description })}\\n${cmd.body}\\n`;\n return {\n files: [{ path: `${BASE}/commands/${cmd.meta.id}.md`, content }],\n warnings: [],\n };\n },\n renderAgents(agents: ReadonlyArray<Agent>): RenderResult {\n const files = [];\n for (const agent of agents) {\n // The file name is the agent name. `mode: all` keeps each persona usable\n // both as a session's primary agent and as a subagent.\n const fm = yamlFrontmatter({\n description: agent.meta.description,\n mode: \"all\",\n });\n const body = buildAgentBody(agent);\n files.push({ path: `${BASE}/agents/${agent.meta.id}.md`, content: `${fm}\\n${body}` });\n }\n return { files, warnings: [] };\n },\n renderMcp(servers: ReadonlyArray<McpServerSpec>): RenderResult {\n if (servers.length === 0) return { files: [], warnings: [] };\n const mcp: Record<string, Record<string, unknown>> = {};\n for (const s of servers) {\n const entry: Record<string, unknown> = {\n type: \"local\",\n command: [s.command, ...s.args],\n enabled: true,\n };\n if (s.env) entry.environment = s.env;\n mcp[s.id] = entry;\n }\n const config = { $schema: \"https://opencode.ai/config.json\", mcp };\n const content = `${JSON.stringify(config, null, 2)}\\n`;\n return { files: [{ path: `${BASE}/opencode.json`, content }], warnings: [] };\n },\n renderStopHook(): RenderResult {\n const content = `import { spawnSync } from \"node:child_process\";\n\n// Runs the harness lint and returns the report to inject, or \"\" when the\n// turn may end.\nconst lintReport = (directory, sessionID) => {\n const r = spawnSync(\n \"npx\",\n [\"-y\", \"${STOP_HOOK_PACKAGE}\", \"stop-hook\", \"--engine\", \"opencode\", \"--cwd\", directory],\n { input: JSON.stringify({ sessionID }), encoding: \"utf-8\" },\n );\n const report = (r.stderr ?? \"\").trim();\n return r.status === 2 ? report : \"\";\n};\n\n// OpenCode 1.x entry point, exposed as server() below.\nconst HarnessStopHook = async ({ client, directory }) => ({\n event: async ({ event }) => {\n if (event.type !== \"session.idle\") return;\n const sessionID = event.properties?.sessionID;\n if (!sessionID) return;\n const report = lintReport(directory, sessionID);\n if (report === \"\") return;\n // OpenCode defaults to its build agent when a prompt omits agent.\n // Preserve the last conversation persona, excluding compaction summaries.\n const history = await client.session.messages({ path: { id: sessionID } }).catch(() => undefined);\n if (history?.error || !Array.isArray(history?.data)) return;\n const current = history.data.findLast(({ info }) =>\n (info.role === \"user\" || (info.role === \"assistant\" && !info.summary)) &&\n typeof info.agent === \"string\" && info.agent.trim() !== \"\",\n );\n if (!current) return;\n await client.session.prompt({\n path: { id: sessionID },\n body: { agent: current.info.agent, parts: [{ type: \"text\", text: report }] },\n });\n },\n});\n\n// One default export for both majors: OpenCode 1.x calls server(),\n// OpenCode 2.x calls setup(). A V2 session keeps its agent, so the report\n// is sent without naming the persona.\nexport default {\n id: \"vaubaan-harness-stop-hook\",\n server: HarnessStopHook,\n setup(context) {\n const directory = context.location.directory;\n const controller = new AbortController();\n const listen = async () => {\n for await (const event of context.event.subscribe({ signal: controller.signal })) {\n if (event.type !== \"session.idle\") continue;\n if (event.location?.directory && event.location.directory !== directory) continue;\n const sessionID = event.data?.sessionID;\n if (!sessionID) continue;\n const report = lintReport(directory, sessionID);\n if (report === \"\") continue;\n await context.session.prompt({ sessionID, text: report }).catch(() => undefined);\n }\n };\n listen().catch(() => undefined);\n return () => controller.abort();\n },\n};\n`;\n return {\n files: [{ path: `${BASE}/plugins/harness-stop-hook.ts`, content }],\n warnings: [],\n };\n },\n};\n","import type { Engine } from \"../types.js\";\nimport { claudeCodeRenderer } from \"./claude-code.js\";\nimport { codexCliRenderer } from \"./codex-cli.js\";\nimport type { Renderer } from \"./common.js\";\nimport { opencodeRenderer } from \"./opencode.js\";\n\nexport const RENDERERS: Record<Engine, Renderer> = {\n \"claude-code\": claudeCodeRenderer,\n \"codex-cli\": codexCliRenderer,\n opencode: opencodeRenderer,\n};\n\nexport function getRenderer(engine: Engine): Renderer {\n return RENDERERS[engine];\n}\n\nexport type { Renderer, RenderResult } from \"./common.js\";\n","import { MCP_SERVERS, type McpServerId } from \"./mcp.js\";\nimport type { Agent } from \"./parser/agent.js\";\nimport type { Cmd } from \"./parser/cmd.js\";\nimport type { Skill } from \"./parser/skill.js\";\nimport { buildUmbrellaBody } from \"./renderers/common.js\";\nimport { getRenderer } from \"./renderers/index.js\";\nimport type { Engine, RenderedFile } from \"./types.js\";\n\nexport interface Harness {\n agents: ReadonlyArray<Agent>;\n skills: ReadonlyArray<Skill>;\n cmds: ReadonlyArray<Cmd>;\n}\n\nexport interface HarnessOverrides {\n add?: Partial<Harness>;\n disable?: ReadonlyArray<string>;\n}\n\nexport interface RenderInput {\n harness: Harness;\n engines: ReadonlyArray<Engine>;\n overrides?: HarnessOverrides;\n projectSkills?: ReadonlyArray<Skill>;\n /** True when the target project ships `docs/quality/code-quality.md` (optional pillar). */\n hasQualityDoc?: boolean;\n mcpServers?: ReadonlyArray<McpServerId>;\n /**\n * When true (default), each engine's renderer emits a Stop-hook config\n * that calls `vaubaan-harness stop-hook --engine <engine>` and injects\n * the lint report (with skill suggestions) into the running session\n * whenever the worktree is dirty and lint fails. Pass `false` to skip\n * the hook configs entirely (kill-switch at install time; runtime\n * kill-switch is `HARNESS_LINT_DISABLE=1`).\n */\n withStopHook?: boolean;\n}\n\nexport interface RenderOutput {\n files: RenderedFile[];\n warnings: string[];\n}\n\nexport class RenderError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RenderError\";\n }\n}\n\nfunction mergeById<T extends { meta: { id: string } }>(\n stock: ReadonlyArray<T>,\n added: ReadonlyArray<T> | undefined,\n): T[] {\n const result = new Map<string, T>();\n for (const item of stock) result.set(item.meta.id, item);\n if (added) {\n for (const item of added) result.set(item.meta.id, item);\n }\n return Array.from(result.values());\n}\n\nfunction removeByIds<T extends { meta: { id: string } }>(\n items: ReadonlyArray<T>,\n disabled: ReadonlyArray<string>,\n): T[] {\n if (disabled.length === 0) return [...items];\n const disabledSet = new Set(disabled);\n return items.filter((item) => !disabledSet.has(item.meta.id));\n}\n\nexport function applyOverrides(harness: Harness, overrides: HarnessOverrides | undefined): Harness {\n const disabled = overrides?.disable ?? [];\n return {\n agents: removeByIds(mergeById(harness.agents, overrides?.add?.agents), disabled),\n skills: removeByIds(mergeById(harness.skills, overrides?.add?.skills), disabled),\n cmds: removeByIds(mergeById(harness.cmds, overrides?.add?.cmds), disabled),\n };\n}\n\n// `@AGENTS.md` is Claude Code's import syntax: the umbrella is really loaded,\n// on every version and provider, and never read twice alongside native\n// AGENTS.md support (cf. ADR engine-native-layouts-1-0).\nconst CLAUDE_MD_STUB = \"@AGENTS.md\\n\";\n\n// OpenCode also scans `.claude/skills/` and `.agents/skills/`. It goes last so\n// its own copy of a skill is dropped when another rendered engine exposes it.\nfunction opencodeLast(engines: ReadonlyArray<Engine>): Engine[] {\n return [...engines].sort((a, b) => Number(a === \"opencode\") - Number(b === \"opencode\"));\n}\n\nexport function render(input: RenderInput): RenderOutput {\n const { engines } = input;\n if (engines.length === 0) {\n throw new RenderError(\"at least one engine must be selected\");\n }\n const base: Harness = input.projectSkills?.length\n ? { ...input.harness, skills: mergeById(input.harness.skills, input.projectSkills) }\n : input.harness;\n const harness = applyOverrides(base, input.overrides);\n if (harness.agents.length === 0) {\n throw new RenderError(\"No agents found in harness\");\n }\n\n const files: RenderedFile[] = [];\n const warnings: string[] = [];\n\n // Track skills + commands successfully exposed on at least one engine,\n // for the engine-neutral umbrella's \"Available skills/commands\" sections.\n const exposedSkills = new Map<string, Skill>();\n const exposedCmds = new Map<string, Cmd>();\n\n for (const engine of opencodeLast(engines)) {\n const renderer = getRenderer(engine);\n\n const renderedSkills: Skill[] = [];\n for (const skill of harness.skills) {\n const result = renderer.renderSkill(skill);\n warnings.push(...result.warnings);\n if (result.files.length > 0) {\n const alreadyVisible = engine === \"opencode\" && exposedSkills.has(skill.meta.id);\n if (!alreadyVisible) files.push(...result.files);\n renderedSkills.push(skill);\n exposedSkills.set(skill.meta.id, skill);\n }\n }\n\n const renderedSkillIds = new Set(renderedSkills.map((s) => s.meta.id));\n const renderedCmds: Cmd[] = [];\n for (const cmd of harness.cmds) {\n if (!renderedSkillIds.has(cmd.meta.invokes_skill)) continue;\n const result = renderer.renderCmd(cmd);\n warnings.push(...result.warnings);\n if (result.files.length > 0) {\n files.push(...result.files);\n renderedCmds.push(cmd);\n exposedCmds.set(cmd.meta.id, cmd);\n }\n }\n\n const agentsResult = renderer.renderAgents(harness.agents);\n warnings.push(...agentsResult.warnings);\n files.push(...agentsResult.files);\n\n if (input.mcpServers && input.mcpServers.length > 0) {\n const specs = input.mcpServers.map((id) => MCP_SERVERS[id]);\n const mcpResult = renderer.renderMcp(specs);\n warnings.push(...mcpResult.warnings);\n files.push(...mcpResult.files);\n }\n\n if (input.withStopHook !== false) {\n const hookResult = renderer.renderStopHook();\n warnings.push(...hookResult.warnings);\n files.push(...hookResult.files);\n }\n }\n\n // Engine-neutral umbrella at the root\n const hasProjectSkills = (input.projectSkills?.length ?? 0) > 0;\n const umbrellaContent = buildUmbrellaBody(harness.agents, {\n availableSkills: Array.from(exposedSkills.values()),\n availableCommands: Array.from(exposedCmds.values()),\n hasProjectSkills,\n hasQualityDoc: input.hasQualityDoc === true,\n });\n files.push({ path: \"AGENTS.md\", content: umbrellaContent });\n\n // CLAUDE.md stub only if claude-code is enabled (Claude Code reads it historically)\n if (engines.includes(\"claude-code\")) {\n files.push({ path: \"CLAUDE.md\", content: CLAUDE_MD_STUB });\n }\n\n return { files, warnings };\n}\n"],"mappings":"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","names":["ENGINE","BASE","ENGINE","BASE"]}
|
|
@@ -52,7 +52,7 @@ no Next.js, no Supabase, no code blocks. The technical *how* lives in
|
|
|
52
52
|
implementation details — just the external shape)?
|
|
53
53
|
- What **cross-cutting constraints** (security, perf, compat)?
|
|
54
54
|
6. **Draft** the spec following the style of the neighboring specs.
|
|
55
|
-
Minimal sections (see §9.2 of the
|
|
55
|
+
Minimal sections (see §9.2 of the harness spec):
|
|
56
56
|
- Status (greenfield / overhaul / stable)
|
|
57
57
|
- Vocabulary
|
|
58
58
|
- Vision
|
|
@@ -83,7 +83,7 @@ live next to the code and do not need a separate plan.
|
|
|
83
83
|
cross-link, `doc-audit` flags the spec as "without a test plan".
|
|
84
84
|
- **Explicit known gaps**. Listing what we *do not test yet* is as
|
|
85
85
|
important as listing what we test. Avoids false confidence.
|
|
86
|
-
- **E2E-specific conventions** (see
|
|
86
|
+
- **E2E-specific conventions** (see harness spec §9.2): for
|
|
87
87
|
`e2e-tests.md`, document the e2e org (dedicated Stripe org /
|
|
88
88
|
Supabase project / Vercel project), fixture naming, and the
|
|
89
89
|
sweeper / cleanup so as not to pollute.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaubaan/harness",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Project conventions as code for Claude Code, Codex and OpenCode — skills, agents, commands and linters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Vaubaan",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=20"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
-
"vaubaan-harness": "
|
|
22
|
+
"vaubaan-harness": "dist/cli/index.js"
|
|
23
23
|
},
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
@@ -43,7 +43,12 @@
|
|
|
43
43
|
"import": "./dist/init.js"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
|
-
"files": [
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"NOTICE"
|
|
51
|
+
],
|
|
47
52
|
"scripts": {
|
|
48
53
|
"build": "tsup && node scripts/copy-content.mjs",
|
|
49
54
|
"dev": "tsup --watch",
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Agent, Skill, Cmd } from './parser/index.js';
|
|
2
2
|
|
|
3
|
-
declare const MCP_SERVER_IDS: readonly ["context7"];
|
|
4
|
-
type McpServerId = (typeof MCP_SERVER_IDS)[number];
|
|
5
|
-
|
|
6
3
|
declare const ENGINES: readonly ["claude-code", "codex-cli", "opencode"];
|
|
7
4
|
type Engine = (typeof ENGINES)[number];
|
|
8
5
|
interface RenderedFile {
|
|
@@ -10,6 +7,9 @@ interface RenderedFile {
|
|
|
10
7
|
content: string;
|
|
11
8
|
}
|
|
12
9
|
|
|
10
|
+
declare const MCP_SERVER_IDS: readonly ["context7"];
|
|
11
|
+
type McpServerId = (typeof MCP_SERVER_IDS)[number];
|
|
12
|
+
|
|
13
13
|
interface Harness {
|
|
14
14
|
agents: ReadonlyArray<Agent>;
|
|
15
15
|
skills: ReadonlyArray<Skill>;
|