@blundergoat/goat-flow
Version:
AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.
384 lines • 19.3 kB
JavaScript
/**
* Shared building blocks for composing agent quality-review prompts.
*
* Collects the cross-mode helpers the per-mode composers reuse: shell/JSON/date
* escaping for embedded snippets, project-path shaping that survives Windows and
* UNC roots, audit-summary rendering, prior-report delta context, bounded
* learning-loop context, and the focused JSON-report contract appended to the end
* of every prompt. Pure string assembly; the only I/O is the `package.json` read
* behind `inferQualityScope`.
*/
import { existsSync, readFileSync } from "node:fs";
import { join, posix } from "node:path";
import { QUALITY_REPORT_KIND } from "../quality/schema.js";
import { getPackageVersion } from "../paths.js";
import { renderLearningLoopContext, selectLearningLoopContext, } from "./learning-loop-context.js";
/**
* Build the forward-slash project sub-path that goes inside a Bash snippet in
* the prompt. On Windows `path.resolve` returns backslashes and (worse) drive-
* prefixes POSIX-shape inputs; `path.posix.join` keeps the input shape and
* forces forward-slash separators for the appended segment. Backslashes are
* normalised first so UNC roots (`\\server\share`) survive as `//server/share`;
* the leading slash that `posix.join` collapses on UNC inputs is then restored
* so quality writes still target the network share, not a local absolute path.
*
* @param projectPath - absolute project root; may be a Windows path or a UNC root (`\\server\share`)
* @param sub - POSIX-shaped sub-path to append, e.g. `.goat-flow/logs/quality`
* @returns forward-slash path safe to embed in a generated Bash snippet, with the UNC root preserved
*/
export function toShellProjectPath(projectPath, sub) {
const normalized = projectPath.replace(/\\/g, "/");
const isUnc = normalized.startsWith("//");
const joined = posix.join(normalized, sub);
return isUnc && !joined.startsWith("//") ? "/" + joined : joined;
}
/**
* Format one date as YYYY-MM-DD using the local calendar day, not UTC.
*
* @param date - day to format; defaults to the current local time
* @returns the date as a zero-padded YYYY-MM-DD string
*/
export function formatLocalDate(date = new Date()) {
const year = String(date.getFullYear());
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
/**
* Render one JSON-safe string literal for the embedded example block.
*
* @param value - raw string to embed in the prompt's JSON example
* @returns the value as a quoted, escaped JSON string literal
*/
export function jsonString(value) {
return JSON.stringify(value);
}
/**
* Render a Bash single-quoted literal so generated snippets do not expand `$` or backticks.
*
* @param value - raw string to quote for a generated shell snippet
* @returns a single-quoted Bash literal with embedded quotes escaped as `'\''`
*/
export function shellSingleQuote(value) {
return `'${value.replace(/'/g, "'\\''")}'`;
}
/**
* Infer the report scope from package metadata; recover as consumer when metadata is unreadable.
*
* @param projectPath - project root whose `package.json` name field is inspected
* @returns `framework-self` when the package is `@blundergoat/goat-flow`, otherwise `consumer`
* (also `consumer` when `package.json` is missing or unparseable)
*/
export function inferQualityScope(projectPath) {
const packagePath = join(projectPath, "package.json");
try {
if (!existsSync(packagePath))
return "consumer";
const raw = JSON.parse(readFileSync(packagePath, "utf-8"));
return raw.name === "@blundergoat/goat-flow"
? "framework-self"
: "consumer";
}
catch {
return "consumer";
}
}
/**
* Render the audit summary block because reviewers need setup failures before qualitative judgment.
*
* @param report - completed audit report whose scope results and concern scores are summarised
* @returns a Markdown block listing setup/agent pass-fail plus harness-completeness percentages
*/
export function renderAuditSummary(report) {
const lines = [];
const scopes = [
["setup", "GOAT Flow Setup"],
["agent", "Agent Setup"],
];
for (const [scope, label] of scopes) {
const scopeReport = report.scopes[scope];
if (!scopeReport)
continue;
const status = scopeReport.status === "pass" ? "PASS" : "FAIL";
lines.push(`- **${label}**: ${status}`);
if (scopeReport.failures.length > 0) {
for (const failure of scopeReport.failures) {
lines.push(` - ${failure.check}: ${failure.message}`);
}
}
}
if (report.concerns) {
const keys = [
"context",
"constraints",
"verification",
"recovery",
"feedback_loop",
];
lines.push("");
lines.push("Harness completeness (structural integrity, not quality assessment):");
for (const key of keys) {
const concern = report.concerns[key];
const limits = concern.limits.length > 0
? `; limits: ${concern.limits.join(" | ")}`
: "";
lines.push(`- ${key}: ${concern.status === "pass" ? "PASS" : "FAIL"} (${concern.score}%; metrics=${concern.metrics}${limits})`);
}
}
return lines.join("\n");
}
/**
* Render the summary text returned when no audit report is embedded.
*
* @param reason - why audit data is absent (failed run vs fast cache miss)
* @returns a one-line summary phrased for that reason
*/
export function renderAuditUnavailableSummary(reason) {
if (reason === "fast-cache-only") {
return "Audit data not loaded (fast cache-only mode had no cached report).";
}
return "Audit data unavailable (audit could not complete).";
}
/**
* Render the heading used when no audit report is embedded.
*
* @param reason - why audit data is absent (failed run vs fast cache miss)
* @returns a bold Markdown heading marking the audit as not-loaded or unavailable
*/
export function renderAuditUnavailableHeading(reason) {
if (reason === "fast-cache-only") {
return "**Audit: NOT LOADED (FAST CACHE-ONLY MODE)**";
}
return "**Audit: UNAVAILABLE**";
}
/**
* Render the fallback note used when audit data is unavailable.
*
* @param reason - why audit data is absent (failed run vs fast cache miss)
* @returns a blockquote telling the reviewer not to infer setup failure from the gap
*/
export function renderDegradedNote(reason) {
if (reason === "fast-cache-only") {
return [
"",
"> **Note:** The dashboard requested a fast quality prompt and no cached audit report was available.",
"> This does not mean the audit failed. Run the Re-audit action or `goat-flow audit . --harness --agent <id>` for live audit status.",
"> Continue the assessment, but do not infer setup failure from this cache miss.",
"",
].join("\n");
}
return [
"",
"> **Note:** The automated audit could not complete on this project.",
"> This may indicate missing config, broken setup, or an incomplete install.",
"> Proceed with the assessment anyway - your findings may catch what the audit could not.",
"",
].join("\n");
}
/** Return the finding severity rank. */
function findingSeverityRank(severity) {
if (severity === "BLOCKER")
return 0;
if (severity === "MAJOR")
return 1;
return 2;
}
/**
* Return the operator-facing label for a quality prompt mode.
*
* @param mode - quality prompt mode being rendered
* @returns the human-readable label shown to operators (e.g. `Harness Engineering`)
*/
export function qualityModeLabel(mode) {
if (mode === "process")
return "Process";
if (mode === "harness")
return "Harness Engineering";
if (mode === "skills")
return "Skills";
return "Agent Installation";
}
/**
* Describe which workspace or target the selected quality mode should assess.
*
* @param mode - quality prompt mode being rendered
* @returns a sentence naming the workspace or target the mode's assessment covers
*/
export function qualityModeTargetScope(mode) {
if (mode === "process") {
return "controlling goat-flow workspace, plus selected target only when it is a goat-flow installation";
}
if (mode === "harness") {
return "selected target project harness, interpreted from the controlling workspace";
}
if (mode === "skills") {
return "controlling goat-flow workspace skills and shared references";
}
return "selected project and selected agent installation";
}
const WRITE_POLICY_MARKERS = ["write", "no-write", "read-only"];
const LOCAL_ARTIFACT_MARKERS = [
"gitignored",
"local artifact",
"local-state",
".goat-flow/logs",
".goat-flow/plans",
"critique snapshot",
"scratchpad",
"quality report",
"session log",
"task-local",
];
// Quality prompts may request semantic anchors for durable follow-up, but
// automatic tracked learning-loop writes belong to CLI-owned code after opt-in.
function includesAnyMarker(text, markers) {
return markers.some((marker) => text.includes(marker));
}
/** Return true for legacy prior findings that conflict with the current
* reporting-only contract, where gitignored local artifacts are not findings. */
function isSupersededLocalArtifactWriteFinding(finding) {
const text = `${finding.summary} ${finding.detail}`.toLowerCase();
const referencesWritePolicy = includesAnyMarker(text, WRITE_POLICY_MARKERS);
const referencesLocalArtifact = includesAnyMarker(text, LOCAL_ARTIFACT_MARKERS);
return referencesWritePolicy && referencesLocalArtifact;
}
/** Rewrite legacy prior-finding phrasing before embedding it in new quality prompts. */
function renderPriorFindingSummary(summary) {
return summary.replace(/\bstrict no-write\b/gi, "tracked-file write restriction");
}
/**
* Escape Markdown table cell content emitted from scorer details.
*
* @param value - raw cell text that may contain pipes or newlines
* @returns single-line cell text with `|` escaped and line breaks flattened to spaces
*/
export function markdownTableCell(value) {
return value.replaceAll("|", "\\|").replace(/\r?\n/g, " ");
}
export function renderPriorReportContext(priorReport, qualityMode) {
const lines = [];
lines.push("---");
lines.push("");
lines.push("## Prior report context");
lines.push("");
if (priorReport) {
const currentContractFindings = priorReport.report.findings.filter((finding) => !isSupersededLocalArtifactWriteFinding(finding));
const omittedPriorFindingCount = priorReport.report.findings.length - currentContractFindings.length;
const priorHighSeverityCount = currentContractFindings.filter((finding) => finding.severity === "BLOCKER" || finding.severity === "MAJOR").length;
const priorTopFindings = [...currentContractFindings]
.sort((left, right) => {
const severityDiff = findingSeverityRank(left.severity) -
findingSeverityRank(right.severity);
if (severityDiff !== 0)
return severityDiff;
return left.id.localeCompare(right.id);
})
.slice(0, 3);
lines.push(`Latest same-agent report: \`${priorReport.id}\` (${priorReport.report.run_date})`);
lines.push(`- Setup total: ${priorReport.report.scores.setup.total}/100`);
lines.push(`- System total: ${priorReport.report.scores.system.total}/100`);
lines.push(`- Prior BLOCKER + MAJOR count: ${priorHighSeverityCount}`);
if (omittedPriorFindingCount > 0) {
lines.push(`- Omitted ${omittedPriorFindingCount} prior local-artifact write finding(s) that conflict with the current contract: gitignored logs, scratchpad notes, critique snapshots, quality reports, and task-local state do not count as writes.`);
}
lines.push("- Top prior findings by severity:");
if (priorTopFindings.length === 0) {
lines.push(" - none after applying the current local-artifact contract");
}
else {
for (const finding of priorTopFindings) {
lines.push(` - \`${finding.id}\` | ${finding.severity} | ${finding.type} | ${renderPriorFindingSummary(finding.summary)}`);
}
}
lines.push("");
lines.push('For the final JSON block in THIS run, use `delta_tag: "persisted"` when a current finding materially matches a prior finding by type/file/line. Use `delta_tag: "new"` when it does not. Do NOT emit `resolved` in current findings - resolved issues are derived later by `goat-flow quality diff` when a prior finding id disappears from a later run.');
lines.push(`Set top-level \`prior_report_id\` to \`${priorReport.id}\` so readers can tell that \`delta_tag: "new"\` means newly discovered relative to that same-agent report, not necessarily newly introduced in the codebase.`);
}
else {
const modeText = qualityMode === "agent-setup" ? "" : `${qualityMode} `;
lines.push(`No prior same-agent ${modeText}quality report exists for this project.`);
lines.push("For the final JSON block in this run, omit `delta_tag` or set it to `null` for every finding.");
lines.push("Set top-level `prior_report_id` to `null` because no prior same-agent report context was provided.");
}
return lines.join("\n");
}
export function renderBoundedLearningLoopContext(sharedFacts, qualityMode) {
if (!sharedFacts)
return "";
if (qualityMode !== "agent-setup" && qualityMode !== "harness")
return "";
const surface = qualityMode === "harness" ? "quality-harness" : "quality-agent-setup";
return renderLearningLoopContext(selectLearningLoopContext(sharedFacts, { surface }));
}
export function appendFocusedReportContract(lines, input) {
lines.push("---");
lines.push("");
lines.push("### Write the JSON report");
lines.push("");
lines.push("Do **not** emit the JSON as a fenced block in your reply. Write it as a file to `.goat-flow/logs/quality/` - that path is gitignored and expected. No tracked-file writes or implementation edits are permitted.");
lines.push("");
lines.push("**Filename format:** `YYYY-MM-DD-HHMM-<agent>-<rand5>.json`");
lines.push("");
lines.push("```bash");
lines.push('STAMP="$(date +"%Y-%m-%d-%H%M")"');
lines.push("RAND=\"$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 5)\"");
lines.push(`QUALITY_DIR=${shellSingleQuote(toShellProjectPath(input.projectPath, ".goat-flow/logs/quality"))}`);
lines.push(`FILE="\${QUALITY_DIR}/\${STAMP}-${input.agent}-\${RAND}.json"`);
lines.push('mkdir -p "$QUALITY_DIR"');
lines.push("# (then write the JSON below to $FILE)");
lines.push("```");
lines.push("");
lines.push("**JSON body shape:**");
lines.push("");
lines.push("```json");
lines.push("{");
lines.push(` "report_kind": ${jsonString(QUALITY_REPORT_KIND)},`);
lines.push(` "goat_flow_version": ${jsonString(getPackageVersion())},`);
lines.push(` "agent": ${jsonString(input.agent)},`);
lines.push(` "project_path": ${jsonString(input.projectPath)},`);
lines.push(` "run_date": ${jsonString(input.runDate)},`);
lines.push(` "audit_status": ${jsonString(input.auditStatus)},`);
lines.push(` "scope": ${jsonString(inferQualityScope(input.projectPath))},`);
lines.push(` "rubric_version": ${jsonString(getPackageVersion())},`);
lines.push(` "quality_mode": ${jsonString(input.qualityMode)},`);
lines.push(` "prior_report_id": ${input.priorReport ? jsonString(input.priorReport.id) : "null"},`);
lines.push(' "scores": {');
lines.push(' "setup": { "total": 0, "accuracy": 0, "relevance": 0, "completeness": 0, "friction": 0 },');
lines.push(' "system": { "total": 0, "usefulness": 0, "signal_to_noise": 0, "adaptability": 0, "learnability": 0 }');
lines.push(" },");
lines.push(' "findings": [');
lines.push(` { "type": "framework_flaw", "severity": "MAJOR", "file": ".goat-flow/architecture.md", "line": null, "summary": "One-line finding summary", "detail": "Why it matters", "evidence_quality": "OBSERVED", "evidence_method": "static-analysis", "delta_tag": ${input.priorReport ? '"new"' : "null"} }`);
lines.push(" ]");
lines.push("}");
lines.push("```");
lines.push("");
lines.push("JSON rules:");
lines.push("- `scores.*` axis values must use exact `0 | 5 | 10 | 15 | 20 | 25` increments and each axis sum must equal its `total` exactly.");
lines.push("- Allowed `type` values: `setup_quality`, `skill_flaw`, `contradiction`, `false_path`, `content_quality`, `framework_flaw`.");
lines.push("- Allowed `severity` values: `BLOCKER`, `MAJOR`, `MINOR`.");
lines.push("- `evidence_quality` is REQUIRED on every finding. Allowed values: `OBSERVED` or `INFERRED`.");
lines.push("- `evidence_method` is REQUIRED on every finding. Allowed values: `runtime-probe`, `static-analysis`, or `mixed`.");
lines.push("- Runtime-backed findings SHOULD include compact evidence fields when useful: `evidence_command`, `evidence_exit_code`, `evidence_summary`, `evidence_warning_count`, and `evidence_excerpt`. Keep these single-line and concise; do not paste raw terminal blocks.");
lines.push(`- \`quality_mode\` is REQUIRED for new reports generated from this prompt. Use \`${jsonString(input.qualityMode)}\` for this ${qualityModeLabel(input.qualityMode)} assessment.`);
lines.push(`- \`prior_report_id\` must be ${input.priorReport ? `\`${input.priorReport.id}\`` : "`null`"} for this run. This makes \`delta_tag\` traceable to the same-agent baseline.`);
if (input.priorReport) {
lines.push('- `delta_tag` is REQUIRED on every current finding and must be either `"new"` or `"persisted"`.');
}
else {
lines.push("- `delta_tag` must be `null` or omitted when no prior report context exists.");
}
lines.push("- Do NOT include an `id` field.");
lines.push("- Do NOT include extra top-level keys or extra finding keys outside this contract.");
lines.push("");
lines.push("**Validate before confirming.** After writing the file, run:");
lines.push("");
lines.push("```bash");
lines.push('goat-flow quality validate "$FILE" # or: node --import tsx src/cli/cli.ts quality validate "$FILE"');
lines.push('ls -la "$FILE"');
lines.push("```");
lines.push("");
lines.push("If command execution is unavailable, do not claim validation passed. Confirm instead with: `Wrote unvalidated quality report to .goat-flow/logs/quality/<your-filename>.json; validation unavailable: <exact reason>`.");
lines.push("");
lines.push("**End of response:** After validate passes, confirm in prose with a single line: `Wrote quality report to .goat-flow/logs/quality/<your-filename>.json`. Do not include the JSON inline in your reply.");
}
//# sourceMappingURL=compose-quality-common.js.map