diff --git a/README.md b/README.md index 708d32655..1b1e9b837 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ npx -y rea-agents doctor | Ask an agent to investigate an app and build a feature | Install the skill, then talk to your agent | | Inspect or decompile one part of an app from the Terminal | `rea analyze` or `rea decompile` | | Validate or canonicalize an Evidence v2 bundle | `rea evidence-import` or `rea evidence-export` | +| Import source as historical reference | `rea import-reference-source` | Filesystem evidence commands and MCP file tools are disabled until the operator approves absolute roots: @@ -136,6 +137,13 @@ rea evidence-import /absolute/path/to/evidence/bundle.json rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json ``` +Historical source import requires a separate allowlist and never treats source as current behavioral authority: + +```bash +export REA_REFERENCE_ROOTS_JSON='["/absolute/path/to/source"]' +rea import-reference-source /absolute/path/to/source +``` + Exports never replace an existing file unless `--overwrite` is explicit. Imports are size/depth bounded, validate every Evidence v2 ID and manifest, and never execute bundle content. Completion metadata is verifier-owned. Run `npm run completion:generate` after changing contracts, schemas, providers, scenarios, package metadata, verifier inputs, or the managed skill. CI runs `npm run completion:check`; it detects stale or tampered hashes and never rewrites a branch. Unsupported and unknown outcomes remain separate from passes, and a pass or failure requires indexed Evidence v2 IDs. diff --git a/metadata/completion-manifest.json b/metadata/completion-manifest.json index db0be3e64..d1febdcf3 100644 --- a/metadata/completion-manifest.json +++ b/metadata/completion-manifest.json @@ -16,7 +16,7 @@ }, { "path": "scripts/verify-package.mjs", - "sha256": "ffad65be3d5dde87c1e2d7b8886949066531e5b82933596b947507aab5f45b58" + "sha256": "b3809e3b7593fcaf16c9f962fbfaea105caca168c2ed8fd07336804ebb581174" }, { "path": "scripts/verify-real-hopper.mjs", diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs index 7ae784565..cf0c74032 100644 --- a/scripts/verify-package.mjs +++ b/scripts/verify-package.mjs @@ -24,6 +24,7 @@ const exec = promisify(execFile); const root = process.cwd(); const workspace = await mkdtemp(join(tmpdir(), "rea-package-")); const evidenceRoot = join(workspace, "evidence"); +const referenceRoot = join(workspace, "reference-source"); let tarball; try { @@ -44,7 +45,21 @@ try { const home = join(workspace, "home"); const fakeBin = join(workspace, "bin"); await mkdir(fakeBin, { recursive: true }); - await mkdir(evidenceRoot, { recursive: true }); + await Promise.all([ + mkdir(evidenceRoot, { recursive: true }), + mkdir(join(referenceRoot, "src"), { recursive: true }), + ]); + await Promise.all([ + writeFile( + join(referenceRoot, "src", "main.ts"), + 'import "./dependency";\n', + ), + writeFile( + join(referenceRoot, "src", "dependency.ts"), + "export const value = 1;\n", + ), + writeFile(join(referenceRoot, ".env"), "PACKAGE_SECRET_SENTINEL=1\n"), + ]); const brew = join(fakeBin, "brew"); const swVers = join(fakeBin, "sw_vers"); const npx = join(fakeBin, "npx"); @@ -84,6 +99,8 @@ try { HOPPER_LAUNCHER_PATH: hopper, REA_NPX_LOG: npxLog, REA_EVIDENCE_ROOTS_JSON: JSON.stringify([evidenceRoot]), + REA_REFERENCE_ROOTS_JSON: JSON.stringify([referenceRoot]), + REA_REFERENCE_SECRET_PATTERNS_JSON: JSON.stringify([".env"]), }; await exec( "npm", @@ -99,6 +116,7 @@ try { !help.includes("setup") || !help.includes("inventory-artifact") || !help.includes("extract-artifact") || + !help.includes("import-reference-source") || !llms.includes("decompile") || !llms.includes("inventory-artifact") || doctor.healthy !== expectedDoctorHealth || @@ -124,6 +142,20 @@ try { artifactInventory.normalized_result?.manifest?.root_format !== "zip" ) throw new Error("packaged artifact inventory CLI failed"); + const referenceImport = json( + await run( + cli, + ["import-reference-source", referenceRoot, "--json"], + environment, + ), + ); + if ( + referenceImport.authority !== "historical-reference" || + referenceImport.root_alias !== "$REFERENCE_ROOT" || + referenceImport.relationships?.[0]?.resolution !== "internal" || + JSON.stringify(referenceImport).includes("PACKAGE_SECRET_SENTINEL") + ) + throw new Error("packaged historical reference import CLI failed"); const { createEvidence } = await import( new URL("../dist/domain/evidence.js", import.meta.url) ); diff --git a/src/cli.ts b/src/cli.ts index b976dc031..05bc1f20b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -14,6 +14,7 @@ import { exportEvidenceBundleCommand, importEvidenceBundleCommand, } from "./application/EvidenceBundleCommands.js"; +import { importReferenceSource } from "./application/ReferenceSourceImport.js"; /** * Build the one-shot Incur CLI without starting Hopper at import time. @@ -96,9 +97,40 @@ export const createCli = (): ReturnType => { registerNativeCommands(cli, logger); registerArtifactCommands(cli, logger); registerEvidenceCommands(cli, logger); + registerReferenceSourceCommand(cli, logger); return cli; }; +const registerReferenceSourceCommand = ( + cli: ReturnType, + logger: Logger, +): void => { + cli.command("import-reference-source", { + description: "Import a bounded source tree as historical reference only", + args: z.object({ + root: z + .string() + .describe("Source root allowed by REA_REFERENCE_ROOTS_JSON"), + }), + run: ({ args }) => + logCliCommand(logger, "import-reference-source", async () => { + const config = parseConfig(process.env); + if (!config.ok) + return { error: config.error._tag, message: config.error.message }; + const imported = await importReferenceSource({ + root: args.root, + caller: "rea-cli", + policy: config.value.referenceSourcePolicy, + importer: PRODUCT_IDENTITY.packageName, + importerVersion: null, + }); + return imported.ok + ? imported.value + : { error: imported.error.code, message: imported.error.message }; + }), + }); +}; + const registerArtifactCommands = ( cli: ReturnType, logger: Logger,