diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a46ae2..9be369e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,9 @@ jobs: - name: Test (vitest) run: npm test + - name: ProofLoop gate + run: node dist/cli.js gate + - name: Smoke — CLI runs with zero runtime deps run: | node dist/cli.js help diff --git a/README.md b/README.md index 904a724..f3b5883 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ npx proofloop target --dir . --write-runner-plan `target` fetches the live URL or scans the codebase, recommends benchmark families with evidence, detects any already-configured benchmark/browser scripts, writes `.proofloop/target/latest-target-plan.json`, and can write a runnable -`.proofloop/runner/target.plan.json`. It does not invent official scores; missing adapters and -official scorer paths are recorded as blockers. +`.proofloop/runner/target.plan.json`. It also writes a dated, LangChain-docs-style context page at +`.proofloop/reports/latest.md` for the next human or coding agent to read before continuing the run. +It does not invent official scores; missing adapters and official scorer paths are recorded as +blockers. When `--write-browser-smoke` is provided with `--url` in a repo with `package.json`, Proof Loop writes `proofloop/browser/live-smoke.spec.ts` and a `proofloop:live-smoke` package script. That turns @@ -45,7 +47,7 @@ npx proofloop init --agent auto --live # config + manifest + agent docs + scrip npx proofloop doctor --json # setup checks and fix commands npx proofloop manifest --dense # compact repo status for agents npx proofloop ui contract --dense # stable selectors/actions/assertions -npx proofloop target --write-runner-plan # benchmark-family plan + runnable adapter discovery +npx proofloop target --write-runner-plan # benchmark plan + context report + runner discovery npx proofloop prompt # kickoff prompt to paste into your coding agent npx proofloop this-repo --goal "proofloop my latest updates" --write-runner-plan npx proofloop runner run --plan proofloop.runner.json --budget-usd 100 @@ -78,6 +80,11 @@ a protected path: the gate definition is not the agent's to move. The CLI stays primary. `npx proofloop mcp` exposes the same compact read-only surfaces to MCP clients without loading broad repo context. +ProofLoop also ships an Agent OS markdown pack under `docs/agent-os/`. It adapts the Room OS +human-world-model idea into deterministic proof supervision: goals are contracts, the world model is +the current target plus receipts/blockers, memory is mined from prior failures, and workers do not +grade their own work. + For a non-technical kickoff, tell Claude/Codex: "proofloop my latest repo" or "proofloop my latest updates." The agent-facing command is: @@ -153,7 +160,7 @@ script. With neither, it reports `no_gate` with exit code 2. An unconfigured gat | `proofloop init --agent auto --live` | Add agent docs, manifest, package aliases, workflows, and rubrics. | | `proofloop doctor [--json]` | Report node/git/agent readiness, manifest/docs/scripts, Playwright/browser readiness, GitHub workflow, UI contracts, and fix commands. | | `proofloop manifest [--json\|--dense]` | Print project status: stack, commands, proof gates, workflows, UI contracts, blockers. | -| `proofloop target [--url ] [--write-runner-plan] [--write-browser-smoke] [--json]` | Recommend benchmark families from a URL/codebase, detect or scaffold configured adapters, and write target/runner plan receipts. | +| `proofloop target [--url ] [--write-runner-plan] [--write-browser-smoke] [--json]` | Recommend benchmark families from a URL/codebase, detect or scaffold configured adapters, and write target/runner plan receipts plus `.proofloop/reports/latest.md`. | | `proofloop docs agents --dense` | Print compact agent workflow instructions. | | `proofloop ui contract\|component ` | Discover stable `data-testid` and `data-proofloop` selectors. | | `proofloop template --list` / `proofloop template --write` | List or write starter proof-loop templates. | diff --git a/dist/cli.js b/dist/cli.js index dd620ff..d7e692f 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -96,7 +96,7 @@ function usage() { " charts latest write local JSON/SVG proof charts", " receipt verify --file verify app-produced proof receipts", " runner run|resume|status|report durable append-only task runner with budget and resume", - " target [--url ] [--write-runner-plan] [--write-browser-smoke] recommend benchmark families and write target receipt", + " target [--url ] [--write-runner-plan] [--write-browser-smoke] recommend benchmark families and write target/context receipts", " mcp start the optional read-only MCP server", " prompt print the one-prompt kickoff", " this-repo [--goal ] [--write-runner-plan] [--run]", @@ -211,6 +211,9 @@ function runDocsCommand(sub, options) { "setup=npx proofloop init --agent auto --live", "doctor=npx proofloop doctor --json", "manifest=npx proofloop manifest --dense", + "target=npx proofloop target --write-runner-plan", + "context=.proofloop/reports/latest.md", + "agent-os=docs/agent-os/README.md", "ui=npx proofloop ui contract --dense", "gate=npx proofloop gate", "resume=npx proofloop resume --dense", diff --git a/dist/contextReport.d.ts b/dist/contextReport.d.ts new file mode 100644 index 0000000..10fd738 --- /dev/null +++ b/dist/contextReport.d.ts @@ -0,0 +1,17 @@ +import type { ProofloopTargetPlan } from "./targetPlan"; +export type ProofloopContextReportResult = { + reportPath: string; + latestPath: string; + text: string; +}; +export type ProofloopContextReportInputs = { + root: string; + targetPlan: ProofloopTargetPlan; + targetPlanPath: string; + runnerPlanPath?: string; + generatedAt?: string; +}; +export declare function writeProofloopContextReport(inputs: ProofloopContextReportInputs): ProofloopContextReportResult; +export declare function renderProofloopContextReport(inputs: ProofloopContextReportInputs & { + generatedAt: string; +}): string; diff --git a/dist/contextReport.js b/dist/contextReport.js new file mode 100644 index 0000000..7fe2902 --- /dev/null +++ b/dist/contextReport.js @@ -0,0 +1,125 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.writeProofloopContextReport = writeProofloopContextReport; +exports.renderProofloopContextReport = renderProofloopContextReport; +const node_fs_1 = require("node:fs"); +const node_path_1 = require("node:path"); +const project_1 = require("./project"); +const REPORT_ROOT = ".proofloop/reports"; +function writeProofloopContextReport(inputs) { + const root = (0, node_path_1.resolve)(inputs.root); + const generatedAt = inputs.generatedAt ?? new Date().toISOString(); + const runId = reportRunId(generatedAt); + const reportPath = (0, node_path_1.join)(root, REPORT_ROOT, runId, "proofloop-context.md"); + const latestPath = (0, node_path_1.join)(root, REPORT_ROOT, "latest.md"); + const text = renderProofloopContextReport({ ...inputs, root, generatedAt }); + (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(reportPath), { recursive: true }); + (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(latestPath), { recursive: true }); + (0, node_fs_1.writeFileSync)(reportPath, text, "utf8"); + (0, node_fs_1.writeFileSync)(latestPath, text, "utf8"); + return { reportPath, latestPath, text }; +} +function renderProofloopContextReport(inputs) { + const manifest = (0, project_1.buildProofloopProjectManifest)(inputs.root); + const gate = (0, project_1.buildReport)(inputs.root); + const target = inputs.targetPlan.target; + const runnerTasks = inputs.targetPlan.runnerPlan?.tasks ?? []; + const recommendations = inputs.targetPlan.recommendations; + const blockers = [...manifest.knownBlockers, ...inputs.targetPlan.blocked]; + const sourceRows = [ + ["target plan", inputs.targetPlanPath], + ...(inputs.runnerPlanPath ? [["runner plan", inputs.runnerPlanPath]] : []), + ["latest context report", (0, node_path_1.join)(inputs.root, REPORT_ROOT, "latest.md")], + ["manifest", (0, node_path_1.join)(inputs.root, ".proofloop", "manifest.json")], + ["gate state", (0, node_path_1.join)(inputs.root, ".proofloop", "gate-state.json")], + ]; + return [ + `# ProofLoop Context Report: ${manifest.repo.name}`, + "", + `Generated: ${inputs.generatedAt}`, + "", + "This page is deterministic. It is rendered from local files, URL fetch metadata, target-plan receipts, runner-plan receipts, and gate receipts. It is not an LLM-written claim that the app works.", + "", + "## Target", + "", + `- Kind: ${target.kind}`, + `- Repo root: ${target.root ?? manifest.repo.root}`, + `- Package: ${target.packageName ?? manifest.repo.name}`, + `- URL: ${target.url ?? "none"}`, + `- HTTP status: ${target.httpStatus ?? "not fetched"}`, + `- Title: ${target.title ?? "unknown"}`, + "", + "## Current Repo State", + "", + `- App: ${manifest.repo.app}`, + `- App reason: ${manifest.repo.appReason}`, + `- Stack: ${manifest.repo.stack.join(", ") || "unknown"}`, + `- Config: ${manifest.config.kind}${manifest.config.path ? ` (${manifest.config.path})` : ""}`, + `- Agent docs: ${manifest.agentInstructions.filter((entry) => entry.exists).map((entry) => entry.path).join(", ") || "missing"}`, + `- Workflows: ${manifest.workflows.join(", ") || "none"}`, + `- UI contracts: ${manifest.uiContracts.slice(0, 12).map((contract) => contract.id).join(", ") || "none"}`, + "", + "## Benchmark And Proof Targeting", + "", + "| Family | Fit | Confidence | Adapter | Scorer | Evidence |", + "|---|---|---:|---|---|---|", + ...recommendations.map((entry) => `| ${entry.id} | ${entry.fit} | ${entry.confidence.toFixed(2)} | ${entry.adapterStatus} | ${entry.officialScoreStatus} | ${escapeTable(entry.evidence.slice(0, 4).join("; "))} |`), + "", + "## Runnable Plan", + "", + runnerTasks.length + ? [ + "| Task | Command | Cost estimate |", + "|---|---|---:|", + ...runnerTasks.map((task) => `| ${task.id} | ${escapeTable(task.command)} | ${money(task.estimatedCostUsd ?? 0)} |`), + ].join("\n") + : "No runner tasks were generated. Add proof checks, browser scripts, or benchmark adapters.", + "", + "## Gate Receipt", + "", + codeFence(gate.text.trim() || JSON.stringify(gate.json, null, 2), "text"), + "", + "## Not Done / Blocked", + "", + blockers.length ? blockers.map((blocker) => `- ${blocker}`).join("\n") : "- No blockers recorded by the current manifest or target plan.", + "", + "## Next Actions", + "", + inputs.targetPlan.nextActions.map((action) => `- ${action}`).join("\n"), + "", + "## Source Receipts", + "", + "| Source | Path |", + "|---|---|", + ...sourceRows.map(([label, path]) => `| ${label} | ${escapeTable(path)} |`), + "", + "## Agent Handoff", + "", + "Give this file to a coding agent together with the repo. The agent should treat the blockers above as the open work queue and should not claim done until `npx proofloop gate` or the configured runner plan passes.", + "", + codeFence([ + "npx proofloop doctor --json", + "npx proofloop manifest --dense", + target.url ? `npx proofloop target --url ${target.url} --write-runner-plan` : "npx proofloop target --write-runner-plan", + inputs.runnerPlanPath ? `npx proofloop runner run --plan ${inputs.runnerPlanPath} --budget-usd 100` : "npx proofloop gate", + "npx proofloop report latest", + ].join("\n"), "bash"), + "", + "## Honesty Boundary", + "", + inputs.targetPlan.honesty, + "", + ].join("\n"); +} +function reportRunId(iso) { + return iso.replace(/[:.]/g, "-").replace(/[^0-9A-Za-z_-]+/g, "-"); +} +function escapeTable(value) { + return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " "); +} +function codeFence(value, info) { + return ["```" + info, value, "```"].join("\n"); +} +function money(value) { + return `$${value.toFixed(value < 0.01 ? 6 : 4)}`; +} diff --git a/dist/index.d.ts b/dist/index.d.ts index cc1f980..22034f0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -19,5 +19,6 @@ export * from "./mcp"; export * from "./runner"; export * from "./layeredPlan"; export * from "./targetPlan"; +export * from "./contextReport"; export * from "./receipts"; export { runCli } from "./cli"; diff --git a/dist/index.js b/dist/index.js index 783ec1e..8f0c12f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -36,6 +36,7 @@ __exportStar(require("./mcp"), exports); __exportStar(require("./runner"), exports); __exportStar(require("./layeredPlan"), exports); __exportStar(require("./targetPlan"), exports); +__exportStar(require("./contextReport"), exports); __exportStar(require("./receipts"), exports); var cli_1 = require("./cli"); Object.defineProperty(exports, "runCli", { enumerable: true, get: function () { return cli_1.runCli; } }); diff --git a/dist/project.js b/dist/project.js index a252474..78d00d9 100644 --- a/dist/project.js +++ b/dist/project.js @@ -45,6 +45,7 @@ const GENERATED_PACKAGE_SCRIPTS = { "proofloop:gate": "npx proofloop gate", "proofloop:resume": "npx proofloop resume --dense", "proofloop:doctor": "npx proofloop doctor --json", + "proofloop:target": "npx proofloop target --write-runner-plan", "proofloop:report": "npx proofloop report latest", "proofloop:charts": "npx proofloop charts latest", }; @@ -146,12 +147,15 @@ function buildAgentDocBlock(agent) { "- `npx proofloop doctor --json` reports exact missing setup checks with fix commands.", "- `npx proofloop manifest --dense` prints compact repo status, proof gates, workflows, and UI contracts.", "- `npx proofloop ui contract --dense` lists stable selectors before browser work.", + "- `npx proofloop target --write-runner-plan` writes target plan JSON and `.proofloop/reports/latest.md`.", + "- Read `docs/agent-os/README.md` for the ProofLoop Agent OS doctrine pack when present.", "", "Loop contract:", "- `npx proofloop this-repo --live` starts a local proof loop for this repo.", "- `npx proofloop gate` is the completion gate; transcript summaries are not proof.", "- `npx proofloop resume --dense` prints the next action after a stop or failure.", "- `npx proofloop report latest` summarizes the latest gate receipt.", + "- `.proofloop/reports/latest.md` is the dated context page to hand to another coding agent.", "- `npx proofloop charts latest` writes local proof charts from gate receipts.", "", "Guardrails:", @@ -241,6 +245,7 @@ function buildProofloopProjectManifest(root) { init: "npx proofloop init --agent auto --live", doctor: "npx proofloop doctor --json", manifest: "npx proofloop manifest --dense", + target: "npx proofloop target --write-runner-plan", live: "npx proofloop this-repo --live", gate: "npx proofloop gate", resume: "npx proofloop resume --dense", diff --git a/dist/targetPlan.d.ts b/dist/targetPlan.d.ts index d2149ea..13155aa 100644 --- a/dist/targetPlan.d.ts +++ b/dist/targetPlan.d.ts @@ -47,6 +47,8 @@ export type ProofloopTargetResult = { plan: ProofloopTargetPlan; planPath: string; runnerPlanPath?: string; + reportPath?: string; + latestReportPath?: string; }; export type ProofloopTargetOptions = { root: string; @@ -85,5 +87,5 @@ export declare function buildProofloopTargetPlan(args: { generatedAt?: string; }): ProofloopTargetPlan; export declare function classifyBenchmarkFamilies(textInput: string, scripts?: Record, hasLiveUrl?: boolean, seedEvidence?: string[]): ProofloopBenchmarkRecommendation[]; -export declare function formatProofloopTargetPlanDense(plan: ProofloopTargetPlan, planPath: string, runnerPlanPath?: string): string; +export declare function formatProofloopTargetPlanDense(plan: ProofloopTargetPlan, planPath: string, runnerPlanPath?: string, reportPath?: string): string; export {}; diff --git a/dist/targetPlan.js b/dist/targetPlan.js index 8459dbe..2e4bf93 100644 --- a/dist/targetPlan.js +++ b/dist/targetPlan.js @@ -7,6 +7,7 @@ exports.classifyBenchmarkFamilies = classifyBenchmarkFamilies; exports.formatProofloopTargetPlanDense = formatProofloopTargetPlanDense; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); +const contextReport_1 = require("./contextReport"); const layeredPlan_1 = require("./layeredPlan"); const DEFAULT_TARGET_PLAN_PATH = (0, node_path_1.join)(".proofloop", "target", "latest-target-plan.json"); const DEFAULT_TARGET_RUNNER_PLAN_PATH = (0, node_path_1.join)(".proofloop", "runner", "target.plan.json"); @@ -111,10 +112,16 @@ async function runProofloopTarget(options) { try { const result = await writeProofloopTargetPlan(options); if (options.json) { - log(JSON.stringify({ ...result.plan, planPath: result.planPath, runnerPlanPath: result.runnerPlanPath ?? null }, null, 2)); + log(JSON.stringify({ + ...result.plan, + planPath: result.planPath, + runnerPlanPath: result.runnerPlanPath ?? null, + reportPath: result.reportPath ?? null, + latestReportPath: result.latestReportPath ?? null, + }, null, 2)); } else { - log(formatProofloopTargetPlanDense(result.plan, result.planPath, result.runnerPlanPath)); + log(formatProofloopTargetPlanDense(result.plan, result.planPath, result.runnerPlanPath, result.reportPath)); } return result; } @@ -147,7 +154,15 @@ async function writeProofloopTargetPlan(options) { runnerPlanPath = (0, node_path_1.resolve)(root, DEFAULT_TARGET_RUNNER_PLAN_PATH); writeJson(runnerPlanPath, plan.runnerPlan); } - return { exitCode: 0, plan, planPath, ...(runnerPlanPath ? { runnerPlanPath } : {}) }; + const report = (0, contextReport_1.writeProofloopContextReport)({ root, targetPlan: plan, targetPlanPath: planPath, ...(runnerPlanPath ? { runnerPlanPath } : {}) }); + return { + exitCode: 0, + plan, + planPath, + ...(runnerPlanPath ? { runnerPlanPath } : {}), + reportPath: report.reportPath, + latestReportPath: report.latestPath, + }; } function buildProofloopTargetPlan(args) { const root = (0, node_path_1.resolve)(args.root); @@ -240,7 +255,7 @@ function classifyBenchmarkFamilies(textInput, scripts = {}, hasLiveUrl = false, } return recommendations.sort((a, b) => b.confidence - a.confidence || a.id.localeCompare(b.id)); } -function formatProofloopTargetPlanDense(plan, planPath, runnerPlanPath) { +function formatProofloopTargetPlanDense(plan, planPath, runnerPlanPath, reportPath) { const lines = [ "proofloop-target-plan", `target=${plan.target.kind}${plan.target.url ? ` url=${plan.target.url}` : ""}${plan.target.packageName ? ` package=${plan.target.packageName}` : ""}`, @@ -248,6 +263,7 @@ function formatProofloopTargetPlanDense(plan, planPath, runnerPlanPath) { `officialScoreReady=${String(plan.summary.officialScoreReady)} runnerPlanReady=${String(plan.summary.runnerPlanReady)}`, `plan=${planPath}`, ...(runnerPlanPath ? [`runnerPlan=${runnerPlanPath}`] : []), + ...(reportPath ? [`report=${reportPath}`] : []), ]; for (const rec of plan.recommendations.slice(0, 8)) { lines.push(`family=${rec.id} fit=${rec.fit} confidence=${rec.confidence.toFixed(2)} adapter=${rec.adapterStatus} scorer=${rec.officialScoreStatus}`); diff --git a/docs/agent-os/README.md b/docs/agent-os/README.md new file mode 100644 index 0000000..1f70e5a --- /dev/null +++ b/docs/agent-os/README.md @@ -0,0 +1,47 @@ +# ProofLoop Agent OS Markdown Pack + +This pack is the human-readable operating system for ProofLoop. It gives users and coding agents a shared language for targets, claims, receipts, gates, workers, budgets, permissions, traces, and readiness. + +ProofLoop is not trying to imitate consciousness. It copies the useful human workflow: + +```mermaid +flowchart LR + observe["Observe target"] --> model["Update proof world model"] + model --> plan["Plan checks and adapters"] + plan --> act["Run gates, browser smoke, or runner tasks"] + act --> verify["Verify receipts"] + verify --> memory["Write report and lessons"] + memory --> model +``` + +## Core Files + +- [soul.md](soul.md) - product constitution and honesty boundary. +- [skills.md](skills.md) - verifier skills and expected inputs/outputs. +- [world.md](world.md) - ProofLoop world model schema. +- [loop.md](loop.md) - observe, target, plan, run, verify, report. +- [harness.md](harness.md) - runner, gates, receipts, and retries. +- [context.md](context.md) - what belongs in agent context. +- [memory.md](memory.md) - what gets persisted across runs. +- [goals.md](goals.md) - goal and claim semantics. +- [workers.md](workers.md) - worker lifecycle for command tasks and browser smoke. +- [delegation.md](delegation.md) - how external coding agents should be handed work. +- [permissions.md](permissions.md) - actions that need approval. +- [budget.md](budget.md) - cost, time, token, and task limits. +- [visibility.md](visibility.md) - user-facing state and reports. +- [interrupts.md](interrupts.md) - how steering changes the proof plan. +- [collaboration.md](collaboration.md) - human, agent, and CI roles. +- [artifact.md](artifact.md) - receipts, reports, traces, and deliverables. +- [evals.md](evals.md) - capability and regression checks. +- [failure-modes.md](failure-modes.md) - known ways agents fake done. +- [trace-schema.md](trace-schema.md) - event names for trace-compatible receipts. +- [readiness.md](readiness.md) - when ProofLoop can honestly say ready. +- [research.md](research.md) - research grounding for world-model and context engineering. + +## Templates + +- [templates/room-template.md](templates/room-template.md) +- [templates/skill-template.md](templates/skill-template.md) +- [templates/worker-template.md](templates/worker-template.md) +- [templates/eval-template.md](templates/eval-template.md) + diff --git a/docs/agent-os/artifact.md b/docs/agent-os/artifact.md new file mode 100644 index 0000000..109da8b --- /dev/null +++ b/docs/agent-os/artifact.md @@ -0,0 +1,17 @@ +# artifact.md + +Artifacts are proof-bearing outputs. + +Examples: + +- Gate state JSON. +- Runner state JSON. +- Runner ledger JSONL. +- Target plan JSON. +- Context report Markdown. +- Browser screenshot or trace. +- Official scorer output. +- Cost ledger. + +Artifacts should be reproducible, timestamped, and tied to the command or target that produced them. + diff --git a/docs/agent-os/budget.md b/docs/agent-os/budget.md new file mode 100644 index 0000000..e9963fc --- /dev/null +++ b/docs/agent-os/budget.md @@ -0,0 +1,15 @@ +# budget.md + +Budgets make long-running proof work survivable. + +Budget dimensions: + +- Dollars. +- Task count. +- Runtime. +- Model route. +- Browser sessions. +- External tool calls. + +When a budget is exhausted, the runner should stop with `blocked_budget`, write the ledger, and preserve the next queued task. + diff --git a/docs/agent-os/collaboration.md b/docs/agent-os/collaboration.md new file mode 100644 index 0000000..6a6bb24 --- /dev/null +++ b/docs/agent-os/collaboration.md @@ -0,0 +1,12 @@ +# collaboration.md + +ProofLoop collaboration roles: + +- User: owns goal, permissions, budget, and final product judgment. +- Coding agent: changes code and fixes failures. +- ProofLoop: plans checks, supervises execution, protects receipts, and reports blockers. +- CI: clean-room backstop for gate commands. +- Hosted service: optional runner for teams that cannot run the harness locally. + +No role should grade its own unverified output. + diff --git a/docs/agent-os/context.md b/docs/agent-os/context.md new file mode 100644 index 0000000..e6a6803 --- /dev/null +++ b/docs/agent-os/context.md @@ -0,0 +1,20 @@ +# context.md + +Context is scarce. ProofLoop gives agents compact state instead of full transcripts. + +## Include + +- Current user goal. +- Latest target report. +- Gate status and failing checks. +- Target recommendations and blockers. +- Runner state and ledger path. +- Stable UI contracts. +- Permission and budget limits. + +## Exclude + +- Raw long logs unless debugging a specific failure. +- Stale reports when a newer target report exists. +- Worker self-reports that lack receipts. + diff --git a/docs/agent-os/delegation.md b/docs/agent-os/delegation.md new file mode 100644 index 0000000..beb50ed --- /dev/null +++ b/docs/agent-os/delegation.md @@ -0,0 +1,14 @@ +# delegation.md + +Delegation means handing a clear proof context to a coding agent. + +The handoff packet should include: + +- Latest context report. +- Exact blockers. +- Allowed files and protected files. +- Commands to run. +- What receipt counts as done. + +Subagents may implement fixes. ProofLoop still owns the final gate. + diff --git a/docs/agent-os/evals.md b/docs/agent-os/evals.md new file mode 100644 index 0000000..b3bbbaf --- /dev/null +++ b/docs/agent-os/evals.md @@ -0,0 +1,17 @@ +# evals.md + +ProofLoop evals test verifier behavior. + +Core eval families: + +- Empty gate is not a pass. +- Failing check blocks done. +- Forged gate pass is blocked by protected paths. +- Tool-use deny-list fails closed on missing logs. +- Target planner labels missing adapters. +- Browser smoke is separate from official benchmark scoring. +- Runner resumes after interruption. +- Watch/dev scripts are not emitted as proof tasks. + +When an agent discovers a new failure mode, add a test or template blocker. + diff --git a/docs/agent-os/failure-modes.md b/docs/agent-os/failure-modes.md new file mode 100644 index 0000000..243d057 --- /dev/null +++ b/docs/agent-os/failure-modes.md @@ -0,0 +1,16 @@ +# failure-modes.md + +Known failure modes: + +- Claiming done from transcript text. +- Editing `.proofloop/gate-state.json` to forge a pass. +- Weakening `proofloop.config.json`. +- Removing CI gate workflows. +- Treating a browser smoke test as an official benchmark score. +- Treating proxy proof as official score proof. +- Certifying an empty tool-use log. +- Hiding blocked adapters behind a green summary. +- Emitting non-terminating watch tasks into a runner plan. + +The fix is not better wording. The fix is a gate, guard, receipt, test, or blocker. + diff --git a/docs/agent-os/goals.md b/docs/agent-os/goals.md new file mode 100644 index 0000000..813aebd --- /dev/null +++ b/docs/agent-os/goals.md @@ -0,0 +1,13 @@ +# goals.md + +ProofLoop goals are proof contracts. + +Examples: + +- "Run the repo's existing test suite and block done if it fails." +- "Classify the app and produce benchmark-family recommendations." +- "Scaffold a live URL smoke test and include it in the runner plan." +- "Verify official benchmark score artifacts with the upstream scorer." + +Goal status must be derived from receipts: passed, failed, no_gate, blocked, or not_configured. + diff --git a/docs/agent-os/harness.md b/docs/agent-os/harness.md new file mode 100644 index 0000000..59be6a8 --- /dev/null +++ b/docs/agent-os/harness.md @@ -0,0 +1,15 @@ +# harness.md + +The harness is the part that reality-checks agent work. + +## Harness Pieces + +- `proofloop gate`: deterministic completion gate. +- `proofloop target`: target scan, benchmark matching, runner-plan discovery, and context report generation. +- `proofloop runner`: durable command execution with ledger, locks, budget, resume, and secret redaction. +- `proofloop hooks`: stop gate and protected path guards. +- `proofloop tooluse`: expected tool-use contracts. +- `proofloop receipt verify`: app-produced receipt verification. + +Harness tasks must terminate. Dev servers, watch mode, preview servers, and interactive prompts are not proof tasks. + diff --git a/docs/agent-os/interrupts.md b/docs/agent-os/interrupts.md new file mode 100644 index 0000000..673c797 --- /dev/null +++ b/docs/agent-os/interrupts.md @@ -0,0 +1,14 @@ +# interrupts.md + +Interrupts are user steering events. + +Examples: + +- "Also test the live URL." +- "Keep it under $100." +- "Use free models first." +- "Do not use memory mode." +- "Publish to npm after CI." + +ProofLoop should convert interrupts into target-plan changes, runner budget changes, permission blockers, or new receipts. It should not overwrite prior goals unless the user explicitly replaces them. + diff --git a/docs/agent-os/loop.md b/docs/agent-os/loop.md new file mode 100644 index 0000000..53605ce --- /dev/null +++ b/docs/agent-os/loop.md @@ -0,0 +1,15 @@ +# loop.md + +ProofLoop runs a verification loop: + +1. Observe the repo and optional live URL. +2. Build or refresh the target plan. +3. Identify benchmark families, scripts, gates, UI contracts, and blockers. +4. Generate or reuse runner tasks. +5. Run deterministic gates or supervised runner tasks. +6. Verify receipts. +7. Write charts, reports, and next actions. +8. Keep unresolved blockers visible. + +The loop is allowed to continue work. It is not allowed to mark its own work done without proof. + diff --git a/docs/agent-os/memory.md b/docs/agent-os/memory.md new file mode 100644 index 0000000..b2ccd2b --- /dev/null +++ b/docs/agent-os/memory.md @@ -0,0 +1,17 @@ +# memory.md + +ProofLoop memory is receipt memory. + +## Durable Memory + +- `.proofloop/gate-state.json` +- `.proofloop/target/latest-target-plan.json` +- `.proofloop/runner/runs/*/state.json` +- `.proofloop/runner/runs/*/ledger.jsonl` +- `.proofloop/reports/latest.md` +- `.proofloop/charts/latest.json` + +## Consolidation Rule + +If the same failure repeats, turn it into a rule, test, protected path, target blocker, or runner task. Do not leave it only in chat history. + diff --git a/docs/agent-os/permissions.md b/docs/agent-os/permissions.md new file mode 100644 index 0000000..b4620a5 --- /dev/null +++ b/docs/agent-os/permissions.md @@ -0,0 +1,21 @@ +# permissions.md + +ProofLoop distinguishes local proof from external side effects. + +## Usually Allowed + +- Read local repo files. +- Write `.proofloop/` generated reports and receipts. +- Run local deterministic commands. +- Fetch a user-provided HTTP or HTTPS URL for target scanning. + +## Requires Care + +- Publishing packages. +- Sending emails or form submissions. +- Changing DNS, deployment settings, or secrets. +- Running official scorers that call paid models. +- Uploading private code or artifacts to a hosted service. + +ProofLoop should record permission blockers instead of pretending the action happened. + diff --git a/docs/agent-os/readiness.md b/docs/agent-os/readiness.md new file mode 100644 index 0000000..e9ac252 --- /dev/null +++ b/docs/agent-os/readiness.md @@ -0,0 +1,16 @@ +# readiness.md + +ProofLoop readiness means the proof story is complete enough to state honestly. + +Checklist: + +- `npx proofloop doctor --json` has no unhandled setup blockers. +- `npx proofloop target --write-runner-plan` wrote a target plan and context report. +- All generated runner tasks terminate. +- `npx proofloop gate` passes or the runner report shows passed. +- Browser claims have browser receipts. +- Official benchmark claims have official scorer outputs or an explicitly recorded equivalent judge contract. +- Remaining blockers are listed in the latest context report. + +If any item is missing, say what is not done. + diff --git a/docs/agent-os/research.md b/docs/agent-os/research.md new file mode 100644 index 0000000..ead5bf4 --- /dev/null +++ b/docs/agent-os/research.md @@ -0,0 +1,12 @@ +# research.md + +Research grounding for this pack: + +- World Models: agents benefit from compact spatial and temporal representations that support simulation before action. https://arxiv.org/abs/1803.10122 +- Generative Agents: believable behavior uses observation, memory, reflection, planning, and action. https://arxiv.org/abs/2304.03442 +- LangChain context engineering: agents need the right information at the right lifecycle step. https://docs.langchain.com/oss/python/langchain/context-engineering +- Anthropic effective agents: reliable agents need clear success criteria, feedback loops, and human oversight. https://www.anthropic.com/engineering/building-effective-agents +- Anthropic context engineering: long-running agents need curated context, not raw transcript stuffing. https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents + +ProofLoop adapts these ideas to verification: world state is proof state, memory is receipts, and feedback is deterministic gates plus external scorer output. + diff --git a/docs/agent-os/skills.md b/docs/agent-os/skills.md new file mode 100644 index 0000000..2ee4a0a --- /dev/null +++ b/docs/agent-os/skills.md @@ -0,0 +1,15 @@ +# skills.md + +ProofLoop skills are verifier skills. They are not personality traits. + +| Skill | Input | Output | Failure mode | +|---|---|---|---| +| Target scan | Repo and optional URL | Target plan JSON and context report | Overclaiming benchmark readiness | +| Gate run | `proofloop.config.json` or npm test fallback | Gate receipt | Empty checks treated as pass | +| Browser smoke | URL and Playwright repo | Smoke spec and runner task | Treating smoke as full user workflow | +| Runner supervision | Runner plan | Append-only ledger and state | Non-terminating watch tasks | +| Tool-use verification | Contract and log | Pass/fail contract receipt | Empty log certified as safe | +| Context report | Target plan, manifest, gate state | Markdown handoff page | LLM prose inventing state | + +Every skill must expose inputs, outputs, receipts, and blocker language. + diff --git a/docs/agent-os/soul.md b/docs/agent-os/soul.md new file mode 100644 index 0000000..60af06c --- /dev/null +++ b/docs/agent-os/soul.md @@ -0,0 +1,17 @@ +# soul.md + +ProofLoop's soul is simple: the gate decides when work is done. + +## Principles + +- A self-report is not proof. +- A screenshot is useful evidence, but not a gate by itself. +- Product-path proof, proxy benchmark proof, and official scorer output are separate claims. +- A verifier must not let the worker weaken the verifier. +- Every scan should produce a dated, portable context report. +- Every blocker should remain visible until a deterministic gate, scorer, or receipt closes it. + +## Product Boundary + +ProofLoop may schedule commands, generate plans, write reports, and scaffold smoke adapters. It does not magically know an app's official benchmark score unless the app provides the official scorer or a recorded equivalent judge contract. + diff --git a/docs/agent-os/templates/eval-template.md b/docs/agent-os/templates/eval-template.md new file mode 100644 index 0000000..2f6fc53 --- /dev/null +++ b/docs/agent-os/templates/eval-template.md @@ -0,0 +1,18 @@ +# Eval Template + +## Eval + +Name: + +## Setup + +## Command + +```bash + +``` + +## Expected Result + +## Regression Protected + diff --git a/docs/agent-os/templates/room-template.md b/docs/agent-os/templates/room-template.md new file mode 100644 index 0000000..7c978be --- /dev/null +++ b/docs/agent-os/templates/room-template.md @@ -0,0 +1,22 @@ +# Room / Target Template + +## Target + +- Repo: +- URL: +- Environment: +- Owner: + +## Claims To Prove + +- Claim: +- Required receipt: +- Blocker: + +## Commands + +```bash +npx proofloop target --url --write-runner-plan +npx proofloop gate +``` + diff --git a/docs/agent-os/templates/skill-template.md b/docs/agent-os/templates/skill-template.md new file mode 100644 index 0000000..d25c3df --- /dev/null +++ b/docs/agent-os/templates/skill-template.md @@ -0,0 +1,22 @@ +# Skill Template + +## Skill + +Name: + +## Inputs + +- + +## Outputs + +- + +## Receipts + +- + +## Failure Modes + +- + diff --git a/docs/agent-os/templates/worker-template.md b/docs/agent-os/templates/worker-template.md new file mode 100644 index 0000000..6e995b3 --- /dev/null +++ b/docs/agent-os/templates/worker-template.md @@ -0,0 +1,20 @@ +# Worker Template + +## Worker + +Id: + +## Command + +```bash + +``` + +## Timeout + +## Budget + +## Expected Receipt + +## Retry Policy + diff --git a/docs/agent-os/trace-schema.md b/docs/agent-os/trace-schema.md new file mode 100644 index 0000000..00a2c44 --- /dev/null +++ b/docs/agent-os/trace-schema.md @@ -0,0 +1,22 @@ +# trace-schema.md + +Recommended trace events: + +- `target_scan_started` +- `target_scan_completed` +- `benchmark_family_matched` +- `adapter_configured` +- `adapter_missing` +- `runner_plan_written` +- `context_report_written` +- `gate_started` +- `gate_completed` +- `task_started` +- `task_completed` +- `budget_blocked` +- `receipt_verified` +- `permission_required` +- `claim_rejected` + +Each event should include timestamp, target id, command or receipt path, status, and blocker text when relevant. + diff --git a/docs/agent-os/visibility.md b/docs/agent-os/visibility.md new file mode 100644 index 0000000..fdad676 --- /dev/null +++ b/docs/agent-os/visibility.md @@ -0,0 +1,15 @@ +# visibility.md + +Users need to see the state that drives decisions. + +Visible surfaces: + +- Target plan JSON for machines. +- Context report Markdown for humans and agents. +- Runner report for task, family, model, and cost summaries. +- Gate receipt for pass/fail state. +- Charts for quick inspection. +- Blocker lists that persist until closed. + +The UI or CLI should never hide missing official scorer status behind a green-looking summary. + diff --git a/docs/agent-os/workers.md b/docs/agent-os/workers.md new file mode 100644 index 0000000..1437dfb --- /dev/null +++ b/docs/agent-os/workers.md @@ -0,0 +1,15 @@ +# workers.md + +ProofLoop workers are supervised tasks, not autonomous personalities. + +## Lifecycle + +- queued +- running +- passed +- failed +- blocked_budget +- paused + +Each worker needs an id, command, working directory, optional environment, timeout, and cost estimate. The runner writes state before and after each task so the run can resume after interruption. + diff --git a/docs/agent-os/world.md b/docs/agent-os/world.md new file mode 100644 index 0000000..87020ae --- /dev/null +++ b/docs/agent-os/world.md @@ -0,0 +1,27 @@ +# world.md + +ProofLoop's world model describes proof reality, not application business logic. + +```ts +type ProofWorld = { + target: Repo | LiveUrl | HybridTarget; + claims: Claim[]; + gates: Gate[]; + receipts: Receipt[]; + tools: ToolAffordance[]; + budgets: Budget[]; + permissions: Permission[]; + blockers: Blocker[]; + risks: Risk[]; + reports: ContextReport[]; +}; +``` + +## Core Entities + +- Target: the codebase, live URL, or both. +- Claim: a statement someone wants to make, such as "live browser verified" or "official score ready". +- Receipt: machine-readable proof produced by a gate, runner, browser test, scorer, or tool-use contract. +- Blocker: a known missing adapter, missing scorer, failing check, missing permission, or exhausted budget. +- Report: dated Markdown snapshot that a human or agent can read. + diff --git a/package.json b/package.json index 2d430ad..d9db0b4 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "files": [ "dist", "templates", + "docs/agent-os", "README.md", "LICENSE" ], @@ -37,7 +38,15 @@ "prepublishOnly": "npm run build", "pretest": "npm run build", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "proofloop:init": "npx proofloop init --agent auto --live", + "proofloop:live": "npx proofloop this-repo --live", + "proofloop:gate": "npx proofloop gate", + "proofloop:resume": "npx proofloop resume --dense", + "proofloop:doctor": "npx proofloop doctor --json", + "proofloop:target": "npx proofloop target --write-runner-plan", + "proofloop:report": "npx proofloop report latest", + "proofloop:charts": "npx proofloop charts latest" }, "devDependencies": { "@webcontainer/api": "^1.6.4", diff --git a/proofloop.config.json b/proofloop.config.json new file mode 100644 index 0000000..85802c6 --- /dev/null +++ b/proofloop.config.json @@ -0,0 +1,22 @@ +{ + "app": "ProofLoop CLI and static site", + "workflow": "Scan a repo or live URL, produce deterministic proof receipts, generate the context handoff report, and refuse fake done through the gate.", + "gate": { + "checks": [ + { + "name": "build", + "command": "npm run build" + }, + { + "name": "tests", + "command": "npm test" + } + ] + }, + "immutable": [ + "templates/github-proofloop-gate.yml" + ], + "protectedPaths": [ + "docs/agent-os/" + ] +} diff --git a/src/cli.ts b/src/cli.ts index 2f806e0..29c0953 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -112,7 +112,7 @@ function usage(): string { " charts latest write local JSON/SVG proof charts", " receipt verify --file verify app-produced proof receipts", " runner run|resume|status|report durable append-only task runner with budget and resume", - " target [--url ] [--write-runner-plan] [--write-browser-smoke] recommend benchmark families and write target receipt", + " target [--url ] [--write-runner-plan] [--write-browser-smoke] recommend benchmark families and write target/context receipts", " mcp start the optional read-only MCP server", " prompt print the one-prompt kickoff", " this-repo [--goal ] [--write-runner-plan] [--run]", @@ -251,6 +251,9 @@ function runDocsCommand(sub: string | undefined, options: Record entry.exists).map((entry) => entry.path).join(", ") || "missing"}`, + `- Workflows: ${manifest.workflows.join(", ") || "none"}`, + `- UI contracts: ${manifest.uiContracts.slice(0, 12).map((contract) => contract.id).join(", ") || "none"}`, + "", + "## Benchmark And Proof Targeting", + "", + "| Family | Fit | Confidence | Adapter | Scorer | Evidence |", + "|---|---|---:|---|---|---|", + ...recommendations.map((entry) => `| ${entry.id} | ${entry.fit} | ${entry.confidence.toFixed(2)} | ${entry.adapterStatus} | ${entry.officialScoreStatus} | ${escapeTable(entry.evidence.slice(0, 4).join("; "))} |`), + "", + "## Runnable Plan", + "", + runnerTasks.length + ? [ + "| Task | Command | Cost estimate |", + "|---|---|---:|", + ...runnerTasks.map((task) => `| ${task.id} | ${escapeTable(task.command)} | ${money(task.estimatedCostUsd ?? 0)} |`), + ].join("\n") + : "No runner tasks were generated. Add proof checks, browser scripts, or benchmark adapters.", + "", + "## Gate Receipt", + "", + codeFence(gate.text.trim() || JSON.stringify(gate.json, null, 2), "text"), + "", + "## Not Done / Blocked", + "", + blockers.length ? blockers.map((blocker) => `- ${blocker}`).join("\n") : "- No blockers recorded by the current manifest or target plan.", + "", + "## Next Actions", + "", + inputs.targetPlan.nextActions.map((action) => `- ${action}`).join("\n"), + "", + "## Source Receipts", + "", + "| Source | Path |", + "|---|---|", + ...sourceRows.map(([label, path]) => `| ${label} | ${escapeTable(path)} |`), + "", + "## Agent Handoff", + "", + "Give this file to a coding agent together with the repo. The agent should treat the blockers above as the open work queue and should not claim done until `npx proofloop gate` or the configured runner plan passes.", + "", + codeFence([ + "npx proofloop doctor --json", + "npx proofloop manifest --dense", + target.url ? `npx proofloop target --url ${target.url} --write-runner-plan` : "npx proofloop target --write-runner-plan", + inputs.runnerPlanPath ? `npx proofloop runner run --plan ${inputs.runnerPlanPath} --budget-usd 100` : "npx proofloop gate", + "npx proofloop report latest", + ].join("\n"), "bash"), + "", + "## Honesty Boundary", + "", + inputs.targetPlan.honesty, + "", + ].join("\n"); +} + +function reportRunId(iso: string): string { + return iso.replace(/[:.]/g, "-").replace(/[^0-9A-Za-z_-]+/g, "-"); +} + +function escapeTable(value: string): string { + return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " "); +} + +function codeFence(value: string, info: string): string { + return ["```" + info, value, "```"].join("\n"); +} + +function money(value: number): string { + return `$${value.toFixed(value < 0.01 ? 6 : 4)}`; +} diff --git a/src/index.ts b/src/index.ts index cc1f980..22034f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,5 +19,6 @@ export * from "./mcp"; export * from "./runner"; export * from "./layeredPlan"; export * from "./targetPlan"; +export * from "./contextReport"; export * from "./receipts"; export { runCli } from "./cli"; diff --git a/src/project.ts b/src/project.ts index b592c28..bfe5c27 100644 --- a/src/project.ts +++ b/src/project.ts @@ -41,6 +41,7 @@ const GENERATED_PACKAGE_SCRIPTS: Record = { "proofloop:gate": "npx proofloop gate", "proofloop:resume": "npx proofloop resume --dense", "proofloop:doctor": "npx proofloop doctor --json", + "proofloop:target": "npx proofloop target --write-runner-plan", "proofloop:report": "npx proofloop report latest", "proofloop:charts": "npx proofloop charts latest", }; @@ -182,12 +183,15 @@ function buildAgentDocBlock(agent: ProofloopAgentKind): string { "- `npx proofloop doctor --json` reports exact missing setup checks with fix commands.", "- `npx proofloop manifest --dense` prints compact repo status, proof gates, workflows, and UI contracts.", "- `npx proofloop ui contract --dense` lists stable selectors before browser work.", + "- `npx proofloop target --write-runner-plan` writes target plan JSON and `.proofloop/reports/latest.md`.", + "- Read `docs/agent-os/README.md` for the ProofLoop Agent OS doctrine pack when present.", "", "Loop contract:", "- `npx proofloop this-repo --live` starts a local proof loop for this repo.", "- `npx proofloop gate` is the completion gate; transcript summaries are not proof.", "- `npx proofloop resume --dense` prints the next action after a stop or failure.", "- `npx proofloop report latest` summarizes the latest gate receipt.", + "- `.proofloop/reports/latest.md` is the dated context page to hand to another coding agent.", "- `npx proofloop charts latest` writes local proof charts from gate receipts.", "", "Guardrails:", @@ -274,6 +278,7 @@ export function buildProofloopProjectManifest(root: string): ProofloopProjectMan init: "npx proofloop init --agent auto --live", doctor: "npx proofloop doctor --json", manifest: "npx proofloop manifest --dense", + target: "npx proofloop target --write-runner-plan", live: "npx proofloop this-repo --live", gate: "npx proofloop gate", resume: "npx proofloop resume --dense", diff --git a/src/targetPlan.ts b/src/targetPlan.ts index 63ae1be..73a282c 100644 --- a/src/targetPlan.ts +++ b/src/targetPlan.ts @@ -1,6 +1,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import type { Dirent } from "node:fs"; import { dirname, join, resolve } from "node:path"; +import { writeProofloopContextReport } from "./contextReport"; import { buildProofloopLayeredRunnerPlan } from "./layeredPlan"; import type { ProofloopRunnerPlan, ProofloopRunnerTaskPlan } from "./runner"; @@ -52,6 +53,8 @@ export type ProofloopTargetResult = { plan: ProofloopTargetPlan; planPath: string; runnerPlanPath?: string; + reportPath?: string; + latestReportPath?: string; }; export type ProofloopTargetOptions = { @@ -208,9 +211,15 @@ export async function runProofloopTarget(options: ProofloopTargetOptions): Promi try { const result = await writeProofloopTargetPlan(options); if (options.json) { - log(JSON.stringify({ ...result.plan, planPath: result.planPath, runnerPlanPath: result.runnerPlanPath ?? null }, null, 2)); + log(JSON.stringify({ + ...result.plan, + planPath: result.planPath, + runnerPlanPath: result.runnerPlanPath ?? null, + reportPath: result.reportPath ?? null, + latestReportPath: result.latestReportPath ?? null, + }, null, 2)); } else { - log(formatProofloopTargetPlanDense(result.plan, result.planPath, result.runnerPlanPath)); + log(formatProofloopTargetPlanDense(result.plan, result.planPath, result.runnerPlanPath, result.reportPath)); } return result; } catch (error) { @@ -244,8 +253,16 @@ export async function writeProofloopTargetPlan(options: ProofloopTargetOptions): runnerPlanPath = resolve(root, DEFAULT_TARGET_RUNNER_PLAN_PATH); writeJson(runnerPlanPath, plan.runnerPlan); } + const report = writeProofloopContextReport({ root, targetPlan: plan, targetPlanPath: planPath, ...(runnerPlanPath ? { runnerPlanPath } : {}) }); - return { exitCode: 0, plan, planPath, ...(runnerPlanPath ? { runnerPlanPath } : {}) }; + return { + exitCode: 0, + plan, + planPath, + ...(runnerPlanPath ? { runnerPlanPath } : {}), + reportPath: report.reportPath, + latestReportPath: report.latestPath, + }; } export function buildProofloopTargetPlan(args: { @@ -355,7 +372,7 @@ export function classifyBenchmarkFamilies( return recommendations.sort((a, b) => b.confidence - a.confidence || a.id.localeCompare(b.id)); } -export function formatProofloopTargetPlanDense(plan: ProofloopTargetPlan, planPath: string, runnerPlanPath?: string): string { +export function formatProofloopTargetPlanDense(plan: ProofloopTargetPlan, planPath: string, runnerPlanPath?: string, reportPath?: string): string { const lines = [ "proofloop-target-plan", `target=${plan.target.kind}${plan.target.url ? ` url=${plan.target.url}` : ""}${plan.target.packageName ? ` package=${plan.target.packageName}` : ""}`, @@ -363,6 +380,7 @@ export function formatProofloopTargetPlanDense(plan: ProofloopTargetPlan, planPa `officialScoreReady=${String(plan.summary.officialScoreReady)} runnerPlanReady=${String(plan.summary.runnerPlanReady)}`, `plan=${planPath}`, ...(runnerPlanPath ? [`runnerPlan=${runnerPlanPath}`] : []), + ...(reportPath ? [`report=${reportPath}`] : []), ]; for (const rec of plan.recommendations.slice(0, 8)) { lines.push(`family=${rec.id} fit=${rec.fit} confidence=${rec.confidence.toFixed(2)} adapter=${rec.adapterStatus} scorer=${rec.officialScoreStatus}`); diff --git a/tests/agentOsDocs.test.ts b/tests/agentOsDocs.test.ts new file mode 100644 index 0000000..43a01dc --- /dev/null +++ b/tests/agentOsDocs.test.ts @@ -0,0 +1,20 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +const root = process.cwd(); + +describe("ProofLoop Agent OS docs pack", () => { + it("ships the human/agent context doctrine and research notes", () => { + const readme = join(root, "docs", "agent-os", "README.md"); + const research = join(root, "docs", "agent-os", "research.md"); + + expect(existsSync(readme)).toBe(true); + expect(existsSync(research)).toBe(true); + + expect(readFileSync(readme, "utf8")).toContain("ProofLoop Agent OS Markdown Pack"); + expect(readFileSync(research, "utf8")).toContain("https://arxiv.org/abs/1803.10122"); + expect(readFileSync(research, "utf8")).toContain("https://docs.langchain.com/oss/python/langchain/context-engineering"); + expect(readFileSync(research, "utf8")).toContain("https://www.anthropic.com/engineering/building-effective-agents"); + }); +}); diff --git a/tests/projectSetup.test.ts b/tests/projectSetup.test.ts index b4d5648..e464cb4 100644 --- a/tests/projectSetup.test.ts +++ b/tests/projectSetup.test.ts @@ -47,7 +47,9 @@ describe("agent-friendly project setup", () => { const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { scripts: Record }; expect(pkg.scripts["proofloop:init"]).toBe("npx proofloop init --agent auto --live"); + expect(pkg.scripts["proofloop:target"]).toBe("npx proofloop target --write-runner-plan"); expect(pkg.scripts["proofloop:charts"]).toBe("npx proofloop charts latest"); + expect(readFileSync(join(root, "AGENTS.md"), "utf8")).toContain(".proofloop/reports/latest.md"); const manifest = buildProofloopProjectManifest(root); expect(manifest.repo.stack).toContain("Vite"); diff --git a/tests/targetPlan.test.ts b/tests/targetPlan.test.ts index 7dc946d..4ce7cfc 100644 --- a/tests/targetPlan.test.ts +++ b/tests/targetPlan.test.ts @@ -59,14 +59,20 @@ describe("proofloop target planner", () => { const planPath = join(root, ".proofloop", "target", "latest-target-plan.json"); const runnerPlanPath = join(root, ".proofloop", "runner", "target.plan.json"); + const reportPath = join(root, ".proofloop", "reports", "latest.md"); expect(existsSync(planPath)).toBe(true); expect(existsSync(runnerPlanPath)).toBe(true); + expect(existsSync(reportPath)).toBe(true); const plan = JSON.parse(readFileSync(planPath, "utf8")) as ProofloopTargetPlan; + const report = readFileSync(reportPath, "utf8"); const accounting = plan.recommendations.find((entry) => entry.id === "bankertoolbench"); const spreadsheet = plan.recommendations.find((entry) => entry.id === "spreadsheetbench-v1"); expect(plan.target.kind).toBe("codebase"); + expect(report).toContain("# ProofLoop Context Report"); + expect(report).toContain("ledger-room"); + expect(report).toContain("## Not Done / Blocked"); expect(accounting?.adapterStatus).toBe("configured"); expect(accounting?.configuredScripts.map((script) => script.name)).toContain("benchmark:accounting"); expect(accounting?.evidence.join("\n")).toContain("trial balance"); @@ -101,6 +107,8 @@ describe("proofloop target planner", () => { expect(result.exitCode).toBe(0); expect(existsSync(result.planPath)).toBe(true); + expect(result.reportPath && existsSync(result.reportPath)).toBe(true); + expect(result.latestReportPath && existsSync(result.latestReportPath)).toBe(true); expect(result.runnerPlanPath && existsSync(result.runnerPlanPath)).toBe(true); expect(result.plan.target.kind).toBe("live-url"); expect(result.plan.target.httpStatus).toBe(200); @@ -110,6 +118,7 @@ describe("proofloop target planner", () => { expect(result.plan.runnerPlan?.tasks.some((task) => task.id === "target.url-reachable")).toBe(true); expect(result.plan.blocked.join("\n")).toContain("no Playwright/Cypress/browser script"); expect(logs.join("\n")).toContain('"schema": "proofloop-target-plan-v1"'); + expect(readFileSync(result.latestReportPath!, "utf8")).toContain("## Agent Handoff"); }); it("can scaffold a Playwright live-smoke adapter for a URL target", async () => { @@ -143,6 +152,7 @@ describe("proofloop target planner", () => { const browser = result.plan.recommendations.find((entry) => entry.id === "live-browser-smoke"); expect(existsSync(specPath)).toBe(true); + expect(result.latestReportPath && existsSync(result.latestReportPath)).toBe(true); expect(readFileSync(specPath, "utf8")).toContain(url); expect(pkg.scripts["proofloop:live-smoke"]).toBe("playwright test proofloop/browser/live-smoke.spec.ts"); expect(browser?.adapterStatus).toBe("configured");