From bdb1ba685a3cc1f97ba1ce79fc6f4f20e520f5b6 Mon Sep 17 00:00:00 2001 From: blocksifrdev Date: Tue, 15 Sep 2026 12:27:46 -0400 Subject: [PATCH] Never invent agents in someone else's repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing the path a reader actually takes: clone-free, `npx @blocksifr/pctr init && scan` in a normal Express app with no agents in it at all. PCTR reported 5 agents, 3 tools and a CRITICAL consequence on customers.delete, in the same format as a real finding. The only hint was one dim line during init that scrolls past. For a tool whose entire proposition is telling you what is really there, appearing to invent findings is the worst failure available. Anyone technical who tried the published quickstart on their own project would have concluded it was fabricating, and they would have been right. - init no longer writes the example when discovery finds nothing. It says no agents were found, names what it looks for, and points at --example. - The example is opt-in, and the manifest it writes is marked "example": true. - scan prints an unmissable banner for an example manifest, and refuses to render a scan at all when no agents are declared. - The shared Markdown report carries the same warning above anything that reads as a finding — that one travels into other people's pull requests. - The GitHub Action inherits the behaviour: a repository with no agents now produces an empty scan rather than a false report. 195 tests. Published as 0.1.1. Co-Authored-By: Claude Opus 5 --- packages/pctr/README.md | 5 ++++ packages/pctr/action.yml | 4 ++- packages/pctr/bin/pctr.mjs | 41 ++++++++++++++++++++++------- packages/pctr/package.json | 2 +- packages/pctr/src/render.mjs | 4 +++ packages/pctr/src/report.mjs | 4 +++ packages/pctr/tests/report.test.mjs | 11 +++++++- 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/packages/pctr/README.md b/packages/pctr/README.md index 51a0cf7..265b06a 100644 --- a/packages/pctr/README.md +++ b/packages/pctr/README.md @@ -17,6 +17,11 @@ npx @blocksifr/pctr init # discover agents and tools, write pctr.json npx @blocksifr/pctr scan # what consequences can they reach? ``` +If the project has no agents, `init` says so rather than inventing any. To see how a +scan reads, `npx @blocksifr/pctr init --example` writes a worked example — and every +view of it is labelled as made-up data, so it can never be mistaken for findings about +your code. + Or install it: `npm i -g @blocksifr/pctr`, then just `pctr init` and `pctr scan`. No account. No network. Everything stays in `./pctr.json` and `./.pctr`. diff --git a/packages/pctr/action.yml b/packages/pctr/action.yml index e4f50c2..efc613e 100644 --- a/packages/pctr/action.yml +++ b/packages/pctr/action.yml @@ -44,7 +44,9 @@ runs: set -euo pipefail PCTR="npx --yes @blocksifr/pctr@${{ inputs.version }}" - # init is additive: an existing pctr.json is kept and only extended. + # init is additive: an existing pctr.json is kept and only extended. It never + # fabricates agents, so a repository with none produces an empty scan, not a + # false report. $PCTR init > /dev/null $PCTR scan --share pctr-report.md > /dev/null $PCTR scan --json > pctr-scan.json diff --git a/packages/pctr/bin/pctr.mjs b/packages/pctr/bin/pctr.mjs index 03551f9..2955bc0 100755 --- a/packages/pctr/bin/pctr.mjs +++ b/packages/pctr/bin/pctr.mjs @@ -103,27 +103,48 @@ async function main() { case 'init': { const existing = store.readManifest(); const { manifest, notes, discovered } = discover(process.cwd(), { declared: existing }); - // In CI a fabricated example would produce a false report about someone's repo, - // so --no-example keeps an empty scan empty. - if (!manifest.agents.length && !argv.includes('--no-example')) { - manifest.agents = exampleManifest().agents; - manifest.tools = exampleManifest().tools; - manifest.actions = exampleManifest().actions; - notes.push('No agents detected; wrote a worked example you can edit.'); + // Never invent agents in someone's repository. Fabricated findings presented in + // the same format as real ones is the fastest way to lose a reader's trust. + const wantsExample = argv.includes('--example'); + if (!manifest.agents.length && wantsExample) { + Object.assign(manifest, { + example: true, + agents: exampleManifest().agents, + tools: exampleManifest().tools, + actions: exampleManifest().actions + }); + notes.push('Wrote the bundled example. It describes made-up agents, not this project.'); } const path = store.writeManifest(manifest); if (asJson) return out(null, { path, manifest, notes }); console.log(r.heading('pctr initialized')); console.log(`Wrote ${path}`); - console.log(r.field('Agents', String(discovered.agents || manifest.agents.length))); - console.log(r.field('Tools', String(discovered.tools || manifest.tools.length))); + console.log(r.field('Agents found', String(discovered.agents))); + console.log(r.field('Tools found', String(discovered.tools))); for (const n of notes) console.log(r.dim(`- ${n}`)); + + if (!manifest.agents.length) { + console.log(`\n${r.yellow('No agents found in this project.')}`); + console.log('PCTR looks for MCP servers, and for agent and tool declarations in'); + console.log('your source — OpenAI, Claude, LangGraph, CrewAI, AutoGen, AGT.'); + console.log(`\nDeclare them in ${r.bold('pctr.json')}, or see how it works on a worked example:`); + console.log(` ${r.bold('pctr init --example')} ${r.dim('(made-up agents, clearly labelled)')}`); + return 0; + } console.log(`\nNext: ${r.bold('pctr scan')}`); return 0; } case 'scan': { const graph = loadGraph(); + if (graph.manifest.example && !asJson) console.log(r.exampleBanner()); + if (!graph.manifest.agents?.length) { + return out([r.heading('nothing to scan'), + 'pctr.json declares no agents, so there is nothing that can cause a consequence.', + '', `Run ${r.bold('pctr init')} to discover them, or ${r.bold('pctr init --example')} to see`, + 'how a scan reads on a worked example.'].join('\n'), + { summary: summarize(graph), protected: [] }); + } if (flag('share')) { const report = renderReport(graph); const file = flag('share') !== true ? String(flag('share')) : 'pctr-report.md'; @@ -495,7 +516,7 @@ Usage Options --json Machine-readable output --share [file] scan: write the findings as shareable Markdown - --no-example init: don't write a worked example when nothing is found + --example init: write the bundled worked example (made-up agents) --amount Material parameter: amount --records Material parameter: records affected --params '' Any other material parameters diff --git a/packages/pctr/package.json b/packages/pctr/package.json index 89442d9..2e54108 100644 --- a/packages/pctr/package.json +++ b/packages/pctr/package.json @@ -1,6 +1,6 @@ { "name": "@blocksifr/pctr", - "version": "0.1.0", + "version": "0.1.1", "description": "See what your AI agents can cause, route them safely, and prove what happened. Consequence preview, trust routing and signed execution receipts for AI agents.", "type": "module", "license": "Apache-2.0", diff --git a/packages/pctr/src/render.mjs b/packages/pctr/src/render.mjs index d244826..b9c78a6 100644 --- a/packages/pctr/src/render.mjs +++ b/packages/pctr/src/render.mjs @@ -16,6 +16,10 @@ export const field = (label, value, width = 22) => `${label.length >= width ? `${label} ` : label.padEnd(width)}${value}`; export const chain = (ids) => ids.join(`\n${dim(' |')}\n${dim(' v')}\n`); +// Example data must never be mistaken for findings about the reader's own project. +export const exampleBanner = () => + `\n${yellow('EXAMPLE DATA')} ${dim('— these agents are made up. They are not in this project.')}\n${dim('Run `pctr init` against a project with real agents to scan it.')}`; + export function renderScan(graph) { const s = summarize(graph); const out = [heading('PCTR'), 'Scanning your agents...', '']; diff --git a/packages/pctr/src/report.mjs b/packages/pctr/src/report.mjs index 404e588..71a3442 100644 --- a/packages/pctr/src/report.mjs +++ b/packages/pctr/src/report.mjs @@ -25,6 +25,10 @@ export function renderReport(graph, { title = 'PCTR consequence scan', includeBa const out = []; out.push(`## ${summary.bySeverity.CRITICAL ? '🛑' : '🔍'} ${title}`, ''); + if (graph.manifest?.example) { + out.push('> **Example data.** These agents are made up and are not part of this project. ' + + 'This report is showing how a scan reads, not findings about real software.', ''); + } if (includeBadge) out.push(`![PCTR](${badgeUrl(graph)})`, ''); if (!summary.agents) { out.push(`PCTR found no agents to scan here. Declare them in \`pctr.json\` — see the [manifest format](${HOMEPAGE}#pctrjson).`, ''); diff --git a/packages/pctr/tests/report.test.mjs b/packages/pctr/tests/report.test.mjs index 3576366..6a332ac 100644 --- a/packages/pctr/tests/report.test.mjs +++ b/packages/pctr/tests/report.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { buildGraph } from '../src/graph.mjs'; import { renderReport, badgeUrl } from '../src/report.mjs'; -const graph = (overrides = {}) => buildGraph({ +const manifestOf = (overrides = {}) => ({ principal: 'user:test', agents: [{ id: 'support', trust: 0.8, evidenceAgeSeconds: 10, authority: ['customers.read'], tools: ['db'], delegatesTo: ['admin'] }, { id: 'admin', trust: 0.95, evidenceAgeSeconds: 10, authority: ['customers.*'], tools: ['db'] }], @@ -11,6 +11,7 @@ const graph = (overrides = {}) => buildGraph({ actions: [{ id: 'customers.delete', recordsAffected: 1842, connectedWorkflows: 4 }], ...overrides }); +const graph = (overrides = {}) => buildGraph(manifestOf(overrides)); test('the shared report leads with the consequence, not with agent counts', () => { const report = renderReport(graph()); @@ -51,3 +52,11 @@ test('an empty scan says so rather than inventing findings', () => { test('the badge reflects what was actually found', () => { assert.match(badgeUrl(graph()), /1%20critical%20consequence-critical/); }); + +test('example data is never presented as findings about the reader\'s project', () => { + const example = buildGraph(manifestOf({ example: true })); + const report = renderReport(example); + assert.match(report, /\*\*Example data\.\*\* These agents are made up/); + assert.ok(report.indexOf('Example data') < report.indexOf('Highest priority'), + 'the warning must come before anything that reads as a finding'); +});