Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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 <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 <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 <id>` | Discover stable `data-testid` and `data-proofloop` selectors. |
| `proofloop template --list` / `proofloop template <id> --write` | List or write starter proof-loop templates. |
Expand Down
5 changes: 4 additions & 1 deletion dist/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function usage() {
" charts latest write local JSON/SVG proof charts",
" receipt verify --file <path> verify app-produced proof receipts",
" runner run|resume|status|report durable append-only task runner with budget and resume",
" target [--url <url>] [--write-runner-plan] [--write-browser-smoke] recommend benchmark families and write target receipt",
" target [--url <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 <text>] [--write-runner-plan] [--run]",
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions dist/contextReport.d.ts
Original file line number Diff line number Diff line change
@@ -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;
125 changes: 125 additions & 0 deletions dist/contextReport.js
Original file line number Diff line number Diff line change
@@ -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)}`;
}
1 change: 1 addition & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
1 change: 1 addition & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; } });
5 changes: 5 additions & 0 deletions dist/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
};
Expand Down Expand Up @@ -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:",
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion dist/targetPlan.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export type ProofloopTargetResult = {
plan: ProofloopTargetPlan;
planPath: string;
runnerPlanPath?: string;
reportPath?: string;
latestReportPath?: string;
};
export type ProofloopTargetOptions = {
root: string;
Expand Down Expand Up @@ -85,5 +87,5 @@ export declare function buildProofloopTargetPlan(args: {
generatedAt?: string;
}): ProofloopTargetPlan;
export declare function classifyBenchmarkFamilies(textInput: string, scripts?: Record<string, string>, 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 {};
24 changes: 20 additions & 4 deletions dist/targetPlan.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -240,14 +255,15 @@ 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}` : ""}`,
`families=${plan.summary.recommendedFamilies} configured=${plan.summary.configuredAdapters} blocked=${plan.summary.blockedFamilies} liveReachable=${String(plan.summary.liveUrlReachable)}`,
`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}`);
Expand Down
Loading
Loading