diff --git a/PROJECT_STATUS.json b/PROJECT_STATUS.json index dc8bfb64..3bb09671 100644 --- a/PROJECT_STATUS.json +++ b/PROJECT_STATUS.json @@ -785,8 +785,8 @@ "docs/release/1.0-RELEASE-CHECKLIST.md" ], "limitations": [ - "memory-recall 1.0.5 is published; the 1.1.0 source release candidate on main is not yet published to npm", - "Publishing, tagging, or signing the 1.1.0 release candidate requires explicit maintainer approval", + "memory-recall 1.1.0 is the package release candidate; verify `npm view memory-recall version` returns 1.1.0 before using the global Recall Map quickstart", + "Publishing, tagging, or signing the 1.1.0 release candidate remains a maintainer action", "No signing keys, release credentials, hosted publishing automation, external writes, or external adapter activation are added" ] }, diff --git a/README.md b/README.md index 782aa8af..77d9a1ee 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,18 @@ Memory Recall turns a repository into a governed context source. New agent sessions get reviewed repo facts, changed-file impact, required local reads, and proof of what was sent instead of a giant pasted transcript. -The current source checkout is the 1.1.0 release candidate. npm still serves -1.0.5, which does not include Recall Map. Until 1.1.0 is published, run the -current quickstart from a source checkout: +The source and package release candidate are 1.1.0. Check the registry before +using the global install path: + +```bash +npm view memory-recall version +npm install -g memory-recall@1.1.0 +recall setup +recall map --root . --sqlite .local/memory.sqlite --format summary +recall handoff +``` + +If the registry still reports an older version, use the source checkout: ```bash git clone https://github.com/rebel0789/Memory-Recall.git @@ -36,8 +45,8 @@ npm run recall -- handoff ``` `recall setup` creates only local state. `recall map` is the explicit first -read-only repository scan; it does not run silently during setup. After 1.1.0 -is published, the global `recall` commands are the equivalent installed path. +read-only repository scan; it does not run silently during setup. Source +checkouts use `npm run recall -- ` in place of `recall `. Normal quickstarts use `recall`. The [developer-first product contract](docs/product/memory-recall-developer-first.md) defines the `implemented`, `experimental`, and `unsupported` capability @@ -59,30 +68,29 @@ for preserved legacy names and URIs. ## Five-Minute Path ```bash -# Install the current source release candidate. -git clone https://github.com/rebel0789/Memory-Recall.git -cd Memory-Recall -npm install +# Confirm and install the 1.1.0 package. +npm view memory-recall version +npm install -g memory-recall@1.1.0 # Create local state only. This does not scan the repository. -npm run recall -- setup +recall setup # Run the first explicit read-only repository map. -npm run recall -- map --root . --sqlite .local/memory.sqlite --format summary +recall map --root . --sqlite .local/memory.sqlite --format summary # Run the handoff verification gate. -npm run recall -- verify +recall verify # Preview local agent setup. This writes nothing. -npm run recall -- connect codex --dry-run --format json -npm run recall -- mcp install --client claude-code --dry-run --format json +recall connect codex --dry-run --format json +recall mcp install --client claude-code --dry-run --format json # Build governed memory proposals. Facts are not trusted until reviewed. -npm run recall -- memory ingest --root . --sqlite .local/memory.sqlite --format json -npm run recall -- memory review --root . --sqlite .local/memory.sqlite --format summary +recall memory ingest --root . --sqlite .local/memory.sqlite --format json +recall memory review --root . --sqlite .local/memory.sqlite --format summary # Produce the compact handoff for the next coding agent. -npm run recall -- handoff +recall handoff ``` The default path is local and explicit: no hosted account, no model API key, no @@ -93,9 +101,9 @@ consented provider request to create pending proposals from selected documentation. Start with the body-free plan: ```bash -npm run recall -- semantic plan --harness codex --root . --dry-run -npm run recall -- semantic task --harness codex --root . -npm run recall -- semantic import --input semantic-result.json --root . --sqlite .local/memory.sqlite +recall semantic plan --harness codex --root . --dry-run +recall semantic task --harness codex --root . +recall semantic import --input semantic-result.json --root . --sqlite .local/memory.sqlite ``` The CLI does not invoke the harness. Direct API execution requires a user-owned @@ -137,9 +145,13 @@ billing claims. | --- | ---: | --- | | Session cursor-delta fixture | 6/6 current answers and 72% lower estimated delivery than full resend | `recall bench session --read-only --root . --format json` | | Temporal current-truth fixture | 10/10 correct and clean; not a token-saving claim | `recall bench temporal --read-only --root . --format json` | -| In-repo structured-ingest sufficiency | 12/12 checkout-derived answers present after structured ingest | `recall bench realqa --read-only --root . --format json` | +| In-repo structured-ingest sufficiency | 12/12 checkout-derived answers present after structured ingest | Source checkout: `npm run recall -- bench realqa --read-only --root . --format json` | | Truth-floor regression gate | Fixture-backed merge gate, not a user-task benchmark | `recall benchmark truth-floor --suite benchmark-truth-floor --dataset evals/benchmark-truth-floor/cases.v1.json --format json` | +The package bundles the session, temporal, and truth-floor fixtures. The +structured-ingest check measures this repository's own status and provider +manifests, so run it only from a Memory Recall source checkout. + Memory Recall is designed for changing repository context, repeated handoffs, and reviewed local truth. It is not a hosted memory API, semantic embedding service, graph database, billing-meter replacement, or @@ -252,12 +264,12 @@ boundaries. ## Status -Source release candidate: **1.1.0**. Published npm version: **1.0.5**. +Release candidate: **1.1.0**. Registry version: verify with `npm view memory-recall version`. | Surface | Status | | --- | --- | | Source checkout | 1.1.0 local-ready release candidate | -| npm package | 1.0.5 published; Recall Map quickstart awaits the 1.1.0 release | +| npm package | 1.1.0 publish-ready; use only after the registry reports 1.1.0 | | CLI | `recall` | | Marketplace / plugin registry | Manifest prepared; not submitted | | Client hooks | Opt-in Codex/Claude connect writer; dry-run/manual fallback | diff --git a/REPOSITORY_MANIFEST.json b/REPOSITORY_MANIFEST.json index f20c3547..011a3af1 100644 --- a/REPOSITORY_MANIFEST.json +++ b/REPOSITORY_MANIFEST.json @@ -1,9 +1,9 @@ { "schemaVersion": "1.0.0", "project": "memory-recall", - "generatedAt": "2026-07-15T11:16:55.792Z", + "generatedAt": "2026-07-15T14:04:23.410Z", "fileCount": 923, - "totalBytes": 6428286, + "totalBytes": 6437920, "exclusions": [ ".git/**", ".local/**", @@ -457,8 +457,8 @@ }, { "path": "apps/cli/oaf.mjs", - "bytes": 459579, - "sha256": "fa7200d942591e92ef954ac3879cf1d9556cf1aa907a52658066ad1002ea9a4b" + "bytes": 460424, + "sha256": "62512b730c7146f2ed243d22a9071290bd00301d28cce64d69b34461b1774787" }, { "path": "apps/web/AGENTS.md", @@ -837,8 +837,8 @@ }, { "path": "docs/benchmarks.md", - "bytes": 8317, - "sha256": "0bbc131af4531e534aa028b0a88391af1c07f913349271885ebc3d9cbfe00e0e" + "bytes": 8750, + "sha256": "d486819287f1f97c1f51efded6dd7bf6c8491dc8e7a135c9cfaba9ed8f14b212" }, { "path": "docs/implementation/AGENT_EXECUTION_PLAYBOOK.md", @@ -1093,12 +1093,12 @@ { "path": "docs/release/1.0-PROVENANCE.json", "bytes": 2414, - "sha256": "fb281b9df61cd9cf0012eebec67a668dd74635666fd2ea623ff8d983359de1cc" + "sha256": "559f17ed84af28b5896f6bd346c3fd37fc8d4e1cd3b7c30886a907c3585c858b" }, { "path": "docs/release/1.0-READINESS-REPORT.md", "bytes": 3391, - "sha256": "5ed33403f42cfc4fa86ba01c06c797ca7d641ea1db80b26e8764c9d39e023df6" + "sha256": "c143026eca72465202cc3f5a713e2c1efe98c03dac8e0423a34301a6760667fc" }, { "path": "docs/release/1.0-RELEASE-CHECKLIST.md", @@ -1108,7 +1108,7 @@ { "path": "docs/release/1.0-REPRODUCIBILITY.md", "bytes": 1723, - "sha256": "b099296e900294decc60d6b48c5ad0ff4b6d1f6a2b34181c50499573b2ce4f2d" + "sha256": "ffaa143ba56be41c18e99ddee9826b518d2e609eaead617743a9ad7e02db0648" }, { "path": "docs/release/1.0-SBOM.json", @@ -1257,8 +1257,8 @@ }, { "path": "docs/usage/token-savings.md", - "bytes": 3138, - "sha256": "49caa5e970ac5a6125e42d5d5e521e4903971375cb32fcb6d2d112dfb7af1d6c" + "bytes": 3359, + "sha256": "b0bff7b987fded359fc2e46f3ced300f7ae29abe6b078c45e291b31b14e76c90" }, { "path": "docs/usage/troubleshooting.md", @@ -2407,8 +2407,8 @@ }, { "path": "package.json", - "bytes": 4490, - "sha256": "7571776a443d6c4e5d91a824c8056b23c20d6378b8b194a316bd206a4b208359" + "bytes": 4476, + "sha256": "0b4ad199b2c9bcb0d4832f3da1747633cee59deb65ec94d3a33f83292bef5dff" }, { "path": "packages/adapter-contracts/package.json", @@ -3447,8 +3447,8 @@ }, { "path": "PROJECT_STATUS.json", - "bytes": 60855, - "sha256": "d69f9d5cd81b825dd4421b13a48e47090ed422a84fa10746a3f965291ba2d9f2" + "bytes": 60894, + "sha256": "66a4cea825b1893e0f234f0f8ae71906dae322756cdde7cccfb967a4d44e2a83" }, { "path": "providers/AGENTS.md", @@ -3662,8 +3662,8 @@ }, { "path": "README.md", - "bytes": 14677, - "sha256": "6290d03d15a62bd121fea8007824b5c14327a6e7ce4e20f45f0d11b6f584aa23" + "bytes": 14946, + "sha256": "032ed4f09e75d866df80450c60f17f83dd772ea4a4d09ead0481156e407cc4e4" }, { "path": "REPOSITORY_MAP.md", @@ -3762,8 +3762,8 @@ }, { "path": "scripts/consumer-smoke.mjs", - "bytes": 8884, - "sha256": "49c8f2d2df28b45dad7133e1f3f3d01cc819c57f2be037bc91c6fa926f2ad447" + "bytes": 15345, + "sha256": "c1a2e0bdd7b67707249d2dc8b62680c5c879e502b3716eb598cd782e12b1c134" }, { "path": "scripts/context-recall-eval.mjs", @@ -4192,8 +4192,8 @@ }, { "path": "tests/cli.test.mjs", - "bytes": 307264, - "sha256": "22a45353019af82c88a2d721e831ed360f5a1c6570aad556fb9de914da2af8f5" + "bytes": 307634, + "sha256": "308e9a00e6e07b882b14318fc5de57060ce1f1cde71c6115bcee6f32f930b55d" }, { "path": "tests/content-intelligence.test.mjs", @@ -4462,8 +4462,8 @@ }, { "path": "tests/release-readiness.test.mjs", - "bytes": 20683, - "sha256": "156a29dd7ad2a8a13288dcd312072492b5b68e9ac2eb1a87111a64e751f7acd5" + "bytes": 21729, + "sha256": "56124e792cccb107c8528e50342dd81106235944828e17f37aecc4b270559af7" }, { "path": "tests/replay.test.mjs", @@ -4527,8 +4527,8 @@ }, { "path": "tests/usage-docs.test.mjs", - "bytes": 17642, - "sha256": "b48a48ba58941a19d4fcca4cc7a7a7cc2cce3ef53d743dc7d8b0cbf7e2b7ca70" + "bytes": 17606, + "sha256": "885efd11e5c90f3c41e2c45d452e802176f4b7523eb71756f8e05a674a7c6f1f" }, { "path": "tests/web-shell.test.mjs", diff --git a/apps/cli/oaf.mjs b/apps/cli/oaf.mjs index 18902b34..d39eb0e5 100755 --- a/apps/cli/oaf.mjs +++ b/apps/cli/oaf.mjs @@ -3349,7 +3349,7 @@ async function benchmarkCommand(values) { } const suite = option(values, '--suite') ?? 'benchmark-truth-floor'; - const datasetPath = option(values, '--dataset') ?? 'evals/benchmark-truth-floor/cases.v1.json'; + const datasetPath = option(values, '--dataset'); const format = option(values, '--format') ?? 'json'; if (suite !== 'benchmark-truth-floor') { console.error(`unsupported suite: ${suite}`); @@ -3363,7 +3363,8 @@ async function benchmarkCommand(values) { } try { - const input = JSON.parse(await readFile(datasetPath, 'utf8')); + const datasetSource = await resolveBenchmarkDataset(datasetPath, 'evals/benchmark-truth-floor/cases.v1.json'); + const input = JSON.parse(await readFile(datasetSource.path, 'utf8')); const dataset = createBenchmarkDataset({ ...input, suite }); const report = await runBenchmarkTruthFloor(dataset, { clock: () => '2026-06-23T00:00:00.000Z', @@ -3428,8 +3429,8 @@ async function buildLocomoBenchmarkReport(values) { const sampleLimit = parseIntegerOption(values, '--sample', 0); const missLimit = parseIntegerOption(values, '--miss-limit', 12); const memorySource = option(values, '--memory-source') ?? 'turns+observations'; - const datasetPath = option(values, '--dataset') ?? 'evals/locomo/smoke.v1.json'; - const dataset = await loadLocomoDataset(datasetPath); + const datasetSource = await resolveBenchmarkDataset(option(values, '--dataset'), 'evals/locomo/smoke.v1.json'); + const dataset = await loadLocomoDataset(datasetSource.path); const scratchRoot = await mkdtemp(path.join(tmpdir(), 'oaf-locomo-')); const scratchSqlite = path.join(scratchRoot, 'memory.sqlite'); const cases = []; @@ -3516,7 +3517,7 @@ async function buildLocomoBenchmarkReport(values) { dataset: { id: dataset.id, version: dataset.version, - ref: datasetPath.startsWith('/') ? 'local-absolute-dataset' : `workspace://${toPosix(datasetPath)}`, + ref: datasetSource.ref, sampleCount: dataset.samples.length, evaluatedSampleCount: sampleStats.length, qaCount: dataset.samples.reduce((sum, sample) => sum + (Array.isArray(sample.qa) ? sample.qa.length : 0), 0), @@ -3996,8 +3997,8 @@ async function buildTemporalBenchmarkReport(values) { const generatedAt = fixedNow(); const tokenBudget = parseIntegerOption(values, '--token-budget', parseIntegerOption(values, '--budget', 1024)); const recallLimit = parseIntegerOption(values, '--limit', 8); - const datasetPath = option(values, '--dataset') ?? 'evals/temporal/gold.v1.json'; - const dataset = await loadTemporalDataset(datasetPath); + const datasetSource = await resolveBenchmarkDataset(option(values, '--dataset'), 'evals/temporal/gold.v1.json'); + const dataset = await loadTemporalDataset(datasetSource.path); const scratchRoot = await mkdtemp(path.join(tmpdir(), 'oaf-temporal-')); const scratchSqlite = path.join(scratchRoot, 'memory.sqlite'); const sqliteValues = ['--sqlite', 'memory.sqlite']; @@ -4088,7 +4089,7 @@ async function buildTemporalBenchmarkReport(values) { dataset: { id: dataset.id, version: dataset.version, - ref: datasetPath.startsWith('/') ? 'local-absolute-dataset' : `workspace://${toPosix(datasetPath)}`, + ref: datasetSource.ref, caseCount: dataset.cases.length }, budget: { @@ -4174,8 +4175,8 @@ async function buildSessionBenchmarkReport(values) { const workspaceId = option(values, '--workspace-id') ?? option(values, '--workspace') ?? 'ws_local'; const tokenBudget = parseIntegerOption(values, '--token-budget', parseIntegerOption(values, '--budget', 1024)); const recallLimit = parseIntegerOption(values, '--limit', 8); - const datasetPath = option(values, '--dataset') ?? 'evals/temporal/gold.v1.json'; - const dataset = await loadTemporalDataset(datasetPath); + const datasetSource = await resolveBenchmarkDataset(option(values, '--dataset'), 'evals/temporal/gold.v1.json'); + const dataset = await loadTemporalDataset(datasetSource.path); const item = dataset.cases[0]; const scratchRoot = await mkdtemp(path.join(tmpdir(), 'oaf-session-')); const scratchSqlite = path.join(scratchRoot, 'memory.sqlite'); @@ -4263,7 +4264,7 @@ async function buildSessionBenchmarkReport(values) { dataset: { id: dataset.id, version: dataset.version, - ref: datasetPath.startsWith('/') ? 'local-absolute-dataset' : `workspace://${toPosix(datasetPath)}`, + ref: datasetSource.ref, caseCount: dataset.cases.length, sessionCaseId: item.id }, @@ -4624,6 +4625,29 @@ async function loadTemporalDataset(datasetPath) { return { id: parsed.id, version: parsed.version ?? '1.0.0', cases }; } +async function resolveBenchmarkDataset(requestedPath, bundledPath) { + const selectedPath = requestedPath ?? bundledPath; + const workspacePath = path.resolve(selectedPath); + const workspaceFile = await stat(workspacePath).catch(() => null); + if (workspaceFile?.isFile()) { + return { + path: workspacePath, + ref: path.isAbsolute(selectedPath) ? 'local-absolute-dataset' : `workspace://${toPosix(selectedPath)}` + }; + } + + if (!path.isAbsolute(selectedPath) && toPosix(path.normalize(selectedPath)) === bundledPath) { + const packagePath = path.join(PACKAGE_ROOT, ...bundledPath.split('/')); + const packageFile = await stat(packagePath).catch(() => null); + if (packageFile?.isFile()) return { path: packagePath, ref: `package://${bundledPath}` }; + } + + return { + path: selectedPath, + ref: path.isAbsolute(selectedPath) ? 'local-absolute-dataset' : `workspace://${toPosix(selectedPath)}` + }; +} + function normalizeTemporalCase(item, index) { if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('bench temporal case must be an object'); const id = String(item.id ?? `case_${index + 1}`); @@ -4745,8 +4769,8 @@ async function buildSufficiencyBenchmarkReport(values) { const generatedAt = fixedNow(); const tokenBudget = parseIntegerOption(values, '--token-budget', parseIntegerOption(values, '--budget', 1024)); const recallLimit = parseIntegerOption(values, '--limit', 8); - const datasetPath = option(values, '--dataset') ?? 'evals/sufficiency/gold.v1.json'; - const dataset = await loadSufficiencyDataset(datasetPath); + const datasetSource = await resolveBenchmarkDataset(option(values, '--dataset'), 'evals/sufficiency/gold.v1.json'); + const dataset = await loadSufficiencyDataset(datasetSource.path); const scratchRoot = await mkdtemp(path.join(tmpdir(), 'oaf-sufficiency-')); const scratchSqlite = path.join(scratchRoot, 'memory.sqlite'); const sqliteValues = ['--sqlite', 'memory.sqlite']; @@ -4852,7 +4876,7 @@ async function buildSufficiencyBenchmarkReport(values) { dataset: { id: dataset.id, version: dataset.version, - ref: datasetPath.startsWith('/') ? 'local-absolute-dataset' : `workspace://${toPosix(datasetPath)}`, + ref: datasetSource.ref, caseCount: dataset.cases.length }, budget: { diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 703be5c9..468858de 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -50,7 +50,7 @@ with `npm run recall --`. | Context-recall gate | `npm run eval:context-recall -- --budgets 8000` from a source checkout. | | Session delta | `recall bench session --read-only --root . --format json` | | Temporal current truth | `recall bench temporal --read-only --root . --format json` | -| Structured-ingest sufficiency | `recall bench realqa --read-only --root . --format json` | +| Structured-ingest sufficiency | `npm run recall -- bench realqa --read-only --root . --format json` from a Memory Recall source checkout | ## Result artifact @@ -68,6 +68,12 @@ Do not cite a protocol schema example as a completed benchmark run. Preserve the command, checkout revision, JSON output, and `reportFingerprint` when the command provides one. +The npm package bundles the truth-floor, session, temporal, sufficiency, and +LoCoMo fixtures. Installed benchmark commands resolve those exact fixture paths +from the package when they are absent from the target repository. The +structured-ingest command remains source-checkout-only because it derives its +questions from this repository's `PROJECT_STATUS.json` and provider manifests. + ## Pass condition | Claim | Pass condition | diff --git a/docs/release/1.0-PROVENANCE.json b/docs/release/1.0-PROVENANCE.json index 8bfdaf45..9a7c6bda 100644 --- a/docs/release/1.0-PROVENANCE.json +++ b/docs/release/1.0-PROVENANCE.json @@ -21,13 +21,13 @@ "mergePerformedByThisTask": false }, "sourceHashes": { - "package.json": "sha256:7571776a443d6c4e5d91a824c8056b23c20d6378b8b194a316bd206a4b208359", + "package.json": "sha256:0b4ad199b2c9bcb0d4832f3da1747633cee59deb65ec94d3a33f83292bef5dff", "package-lock.json": "sha256:66bac759b6c28f95c29eca52c0b42e9bb24237a4a926fb67a42093f9ee425ed7", - "PROJECT_STATUS.json": "sha256:d69f9d5cd81b825dd4421b13a48e47090ed422a84fa10746a3f965291ba2d9f2", + "PROJECT_STATUS.json": "sha256:66a4cea825b1893e0f234f0f8ae71906dae322756cdde7cccfb967a4d44e2a83", "planning/backlog.json": "sha256:a404e5704ba3bf3be635a655d2d73d84eff7eba3ce43ab511b83967cceab6a5f", "adapters/catalog.json": "sha256:11c22a150f847a560be3e64c8280c7757368d813b5f70304938ab7915694c283", "docs/usage/support-matrix.md": "sha256:dd4108828b742e8d06112e6abdfa4119f90a8b034b417b9e7e41f6924dcf26a6", - "docs/benchmarks.md": "sha256:0bbc131af4531e534aa028b0a88391af1c07f913349271885ebc3d9cbfe00e0e", + "docs/benchmarks.md": "sha256:d486819287f1f97c1f51efded6dd7bf6c8491dc8e7a135c9cfaba9ed8f14b212", "THIRD_PARTY.md": "sha256:adbbffba498346ac25ff8f064db76ae93543d3c1990d37f1137d9122f0742dd7", "SECURITY.md": "sha256:af8c910a7f2092f3adc8a9be30f79618d040101c6eda0fda705fdd8f9327b472", "GOVERNANCE.md": "sha256:479e1fe15e56b58bd33b41cab67244de87b6f62b491bea0761337a5b87cd7e52" @@ -39,7 +39,7 @@ }, { "path": "docs/benchmarks.md", - "sha256": "sha256:0bbc131af4531e534aa028b0a88391af1c07f913349271885ebc3d9cbfe00e0e" + "sha256": "sha256:d486819287f1f97c1f51efded6dd7bf6c8491dc8e7a135c9cfaba9ed8f14b212" } ], "generatedFiles": [ diff --git a/docs/release/1.0-READINESS-REPORT.md b/docs/release/1.0-READINESS-REPORT.md index c715b0f3..e9609b0b 100644 --- a/docs/release/1.0-READINESS-REPORT.md +++ b/docs/release/1.0-READINESS-REPORT.md @@ -34,7 +34,7 @@ Publication state: npm-published. This evidence is prepared for a maintainer mer | Page | Scope | Revision fingerprint | | --- | --- | --- | | [Support matrix](../usage/support-matrix.md) | `docs/usage/support-matrix.md`: Implemented, experimental, and unsupported client paths with reversal boundaries. | `sha256:dd4108828b742e8d06112e6abdfa4119f90a8b034b417b9e7e41f6924dcf26a6` | -| [Benchmark proof](../benchmarks.md) | `docs/benchmarks.md`: Public local claims, datasets, baselines, commands, artifacts, and limitations. | `sha256:0bbc131af4531e534aa028b0a88391af1c07f913349271885ebc3d9cbfe00e0e` | +| [Benchmark proof](../benchmarks.md) | `docs/benchmarks.md`: Public local claims, datasets, baselines, commands, artifacts, and limitations. | `sha256:d486819287f1f97c1f51efded6dd7bf6c8491dc8e7a135c9cfaba9ed8f14b212` | The support matrix is the public contract for install and reversal paths. The benchmark page is the public source of truth for local measurement claims and diff --git a/docs/release/1.0-REPRODUCIBILITY.md b/docs/release/1.0-REPRODUCIBILITY.md index 38a856f0..ffa2a422 100644 --- a/docs/release/1.0-REPRODUCIBILITY.md +++ b/docs/release/1.0-REPRODUCIBILITY.md @@ -33,7 +33,7 @@ Counts are a dated snapshot; command results and HANDOFF_VERIFICATION.json are a | Page | Scope | SHA-256 | | --- | --- | --- | | `docs/usage/support-matrix.md` | Implemented, experimental, and unsupported client paths with reversal boundaries. | `sha256:dd4108828b742e8d06112e6abdfa4119f90a8b034b417b9e7e41f6924dcf26a6` | -| `docs/benchmarks.md` | Public local claims, datasets, baselines, commands, artifacts, and limitations. | `sha256:0bbc131af4531e534aa028b0a88391af1c07f913349271885ebc3d9cbfe00e0e` | +| `docs/benchmarks.md` | Public local claims, datasets, baselines, commands, artifacts, and limitations. | `sha256:d486819287f1f97c1f51efded6dd7bf6c8491dc8e7a135c9cfaba9ed8f14b212` | Regenerate these release artifacts after changing either public page so support boundaries and benchmark limitations remain tied to the same reviewed source. diff --git a/docs/usage/token-savings.md b/docs/usage/token-savings.md index 7c760c7d..a6f0836b 100644 --- a/docs/usage/token-savings.md +++ b/docs/usage/token-savings.md @@ -22,7 +22,6 @@ a fixed product-wide percentage or provider-billing statement. ```bash recall bench session --read-only --root . --format json recall bench temporal --read-only --root . --format json -recall bench realqa --read-only --root . --format json recall benchmark truth-floor --suite benchmark-truth-floor --dataset evals/benchmark-truth-floor/cases.v1.json --format json recall graph stats --root . --format summary recall graph search --root . --query "auth workflow" --format summary @@ -31,8 +30,16 @@ recall graph trace --root . --symbol runAuthWorkflow --format summary The session fixture measures cursor deltas against full resend. The temporal fixture measures current, clean truth against a keyword timeline baseline; it -is not a savings claim. The `realqa` command is an in-repo structured-ingest -sufficiency check, not real-world question answering. +is not a savings claim. These commands resolve their bundled datasets from the +installed package when the target repository does not contain them. + +The `realqa` command is a source-checkout-only structured-ingest sufficiency +check over Memory Recall's own status and provider manifests. It is not +real-world question answering: + +```bash +npm run recall -- bench realqa --read-only --root . --format json +``` ## LoCoMo method, not a headline diff --git a/package.json b/package.json index f43c13a1..09d2dfd6 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "scripts/", "services/", "skills/", - "tests/", "tools/", "workflows/" ], diff --git a/scripts/consumer-smoke.mjs b/scripts/consumer-smoke.mjs index 3f84f66a..9b131d0c 100644 --- a/scripts/consumer-smoke.mjs +++ b/scripts/consumer-smoke.mjs @@ -12,37 +12,86 @@ const temp = await mkdtemp(path.join(os.tmpdir(), 'oaf-consumer-smoke-')); const workspace = path.join(temp, 'workspace'); const home = path.join(temp, 'home'); const data = path.join(temp, 'data'); +const packDirectory = path.join(temp, 'pack'); +const prefix = path.join(temp, 'prefix'); const password = 'correct horse battery staple'; let server = null; try { await mkdir(path.join(workspace, 'src'), { recursive: true }); await mkdir(home, { recursive: true }); + await mkdir(packDirectory, { recursive: true }); await writeFile(path.join(workspace, 'package.json'), JSON.stringify({ name: 'consumer-smoke-target' }, null, 2)); await writeFile(path.join(workspace, 'AGENTS.md'), 'Use local context handoffs and proposal-gated memory.'); await writeFile(path.join(workspace, 'src', 'app.js'), 'export function launchSmoke(){ return "ready"; }\n'); - const pack = runJson('npm', ['pack', '--dry-run', '--json']); - must(pack[0]?.files?.some((file) => file.path === 'apps/cli/oaf.mjs'), 'package includes oaf bin'); - - const map = runJson(process.execPath, ['apps/cli/oaf.mjs', 'map', '--root', workspace, '--sqlite', '.local/memory.sqlite', '--changed', 'src/app.js', '--query', 'launch smoke', '--format', 'json']); + const [pack] = runJson('npm', ['pack', '--pack-destination', packDirectory, '--json'], { cwd: root }); + must(pack?.files?.some((file) => file.path === 'apps/cli/oaf.mjs'), 'package includes recall bin'); + must(!pack?.files?.some((file) => file.path.startsWith('tests/')), 'package excludes checkout-only tests'); + const tarball = path.join(packDirectory, pack.filename); + run('npm', ['install', '-g', '--prefix', prefix, tarball, '--ignore-scripts', '--no-audit', '--no-fund'], { cwd: root, env: { ...process.env, HOME: home } }); + const recall = path.join(prefix, 'bin', 'recall'); + const packageRoot = path.join(prefix, 'lib', 'node_modules', 'memory-recall'); + const env = { ...process.env, HOME: home, PATH: `${path.join(prefix, 'bin')}${path.delimiter}${process.env.PATH}` }; + + const version = run(recall, ['--version'], { cwd: workspace, env }); + must(version.stdout.trim() === '1.1.0', 'installed recall reports the release version'); + const setupOutput = run(recall, ['setup'], { cwd: workspace, env }); + must(setupOutput.stdout.includes('Setup created only local state'), 'installed setup creates explicit local state'); + + const map = runJson(recall, ['map', '--root', '.', '--sqlite', '.local/memory.sqlite', '--changed', 'src/app.js', '--query', 'launch smoke', '--format', 'json'], { cwd: workspace, env }); must(validateJsonSchema(recallMapSchema, map).valid, 'Recall Map JSON is the exact strict schema object'); must(!Object.hasOwn(map, 'command'), 'Recall Map JSON has no transport command envelope'); must(map.safeguards?.readOnly === true && map.safeguards?.localFilesWritten === 0, 'Recall Map is read-only'); must(map.architecture?.impact?.changedLocators?.includes('workspace://src/app.js'), 'Recall Map maps the target workspace change'); must(!JSON.stringify(map).includes(workspace), 'Recall Map redacts local workspace paths'); - const preview = runJson(process.execPath, ['apps/cli/oaf.mjs', 'mcp', 'install', '--client', 'codex', '--home', home, '--root', root, '--format', 'json']); + const preview = runJson(recall, ['mcp', 'install', '--client', 'codex', '--home', home, '--root', '.', '--format', 'json'], { cwd: workspace, env }); must(preview.safeguards?.homeConfigMutated === false, 'mcp install preview is dry-run'); - const applied = runJson(process.execPath, ['apps/cli/oaf.mjs', 'mcp', 'install', '--client', 'codex', '--home', home, '--root', root, '--apply', '--confirm', preview.planFingerprint, '--format', 'json']); + const applied = runJson(recall, ['mcp', 'install', '--client', 'codex', '--home', home, '--root', '.', '--apply', '--confirm', preview.planFingerprint, '--format', 'json'], { cwd: workspace, env }); must(applied.safeguards?.homeConfigMutated === true, 'mcp install applies only after confirm'); - const mcpSmoke = runJson(process.execPath, ['apps/cli/oaf.mjs', 'mcp', 'smoke', 'context-pack', '--read-only', '--from', 'codex', '--root', workspace, '--objective', 'Consumer smoke', '--step', 'verify context readback', '--target', 'codex', '--changed', 'src/app.js', '--format', 'json']); + const mcpSmoke = runJson(recall, ['mcp', 'smoke', 'context-pack', '--read-only', '--from', 'codex', '--root', '.', '--objective', 'Consumer smoke', '--step', 'verify context readback', '--target', 'codex', '--changed', 'src/app.js', '--format', 'json'], { cwd: workspace, env }); must(mcpSmoke.checks?.resourceRead && mcpSmoke.safeguards?.readOnly === true, 'mcp context-pack smoke is read-only'); + const connect = runJson(recall, ['connect', 'codex', '--dry-run', '--format', 'json'], { cwd: workspace, env }); + must(connect.dryRun === true && connect.safeguards?.homeConfigMutated === false, 'connect remains a dry-run by default'); + const claudeInstall = runJson(recall, ['mcp', 'install', '--client', 'claude-code', '--dry-run', '--format', 'json'], { cwd: workspace, env }); + must(claudeInstall.safeguards?.homeConfigMutated === false, 'Claude Code MCP install preview writes nothing'); + const semanticPlan = runJson(recall, ['semantic', 'plan', '--harness', 'codex', '--root', '.', '--dry-run'], { cwd: workspace, env }); + must(semanticPlan.safeguards?.rawSourceBodiesIncluded === false && semanticPlan.sourceSummary?.selectedCount > 0, 'semantic plan is body-free'); + const semanticTask = run(recall, ['semantic', 'task', '--harness', 'codex', '--root', '.'], { cwd: workspace, env }).stdout; + const semanticPacket = JSON.parse(semanticTask.match(/\n([\s\S]+)\n<\/semantic_setup_packet>/u)?.[1] ?? 'null'); + must(semanticPacket?.packetFingerprint === semanticPlan.packetFingerprint, 'semantic task matches its installed plan'); + await writeFile(path.join(workspace, 'semantic-result.json'), JSON.stringify({ + schemaVersion: '1.0.0', + packetFingerprint: semanticPacket.packetFingerprint, + facts: [{ sourceIds: [semanticPacket.sources[0].sourceId], subject: 'project:consumer-smoke', predicate: 'uses', object: 'governed memory', text: 'The consumer smoke uses governed memory.' }] + })); + const semanticImport = runJson(recall, ['semantic', 'import', '--input', 'semantic-result.json', '--root', '.', '--sqlite', '.local/memory.sqlite'], { cwd: workspace, env }); + must(semanticImport.proposals?.count === 1 && semanticImport.safeguards?.networkCalls === 0, 'semantic import creates one local pending proposal'); + const ingest = runJson(recall, ['memory', 'ingest', '--root', '.', '--sqlite', '.local/memory.sqlite', '--format', 'json'], { cwd: workspace, env }); + must(ingest.safeguards?.activeMemoryCreated === 0 && ingest.summary?.proposalCount > 0, 'memory ingest creates proposals only'); + must(run(recall, ['memory', 'review', '--root', '.', '--sqlite', '.local/memory.sqlite', '--format', 'summary'], { cwd: workspace, env }).stdout.includes('Memory Review'), 'memory review renders the pending queue'); + const refine = runJson(recall, ['memory', 'refine', '--read-only', '--root', '.', '--sqlite', '.local/memory.sqlite', '--format', 'json'], { cwd: workspace, env }); + must(refine.safeguards?.readOnly === true && refine.summary?.activeMemoryCreated === 0, 'memory refine is read-only'); + must(isReadyHandoff(run(recall, ['verify'], { cwd: workspace, env }).stdout), 'verify runs the installed handoff gate'); + must(isReadyHandoff(run(recall, ['handoff'], { cwd: workspace, env }).stdout), 'handoff renders from the installed package'); + must(run(recall, ['token-saver'], { cwd: workspace, env }).stdout.includes('Token Saver'), 'token-saver renders from the installed package'); + must(run(recall, ['graph', 'stats', '--root', '.', '--format', 'summary'], { cwd: workspace, env }).stdout.includes('Graph Stats'), 'graph stats works from the installed package'); + must(run(recall, ['graph', 'search', '--root', '.', '--query', 'launchSmoke', '--format', 'summary'], { cwd: workspace, env }).stdout.includes('Graph Search'), 'graph search works from the installed package'); + must(run(recall, ['graph', 'trace', '--root', '.', '--symbol', 'launchSmoke', '--format', 'summary'], { cwd: workspace, env }).stdout.includes('Graph Trace'), 'graph trace works from the installed package'); + must(isReadyHandoff(run(recall, ['context', 'handoff', '--read-only', '--from', 'codex', '--root', '.', '--objective', 'Prepare handoff', '--step', 'select next agent context', '--target', 'codex', '--changed', 'src/app.js', '--format', 'summary'], { cwd: workspace, env }).stdout), 'explicit context handoff works from the installed package'); + const session = runJson(recall, ['bench', 'session', '--read-only', '--root', '.', '--format', 'json'], { cwd: workspace, env }); + must(session.headline?.correctnessGatePassed === true && session.dataset?.ref === 'package://evals/temporal/gold.v1.json', 'session benchmark uses its bundled fixture'); + const temporal = runJson(recall, ['bench', 'temporal', '--read-only', '--root', '.', '--format', 'json'], { cwd: workspace, env }); + must(temporal.headline?.oafWins === true && temporal.dataset?.ref === 'package://evals/temporal/gold.v1.json', 'temporal benchmark uses its bundled fixture'); + const truthFloor = runJson(recall, ['benchmark', 'truth-floor', '--suite', 'benchmark-truth-floor', '--dataset', 'evals/benchmark-truth-floor/cases.v1.json', '--format', 'json'], { cwd: workspace, env }); + must(truthFloor.gateDecision === 'pass', 'truth-floor benchmark uses its bundled fixture'); + const port = await freePort(); - server = spawn(process.execPath, ['services/control-api/src/server.mjs'], { - cwd: root, + server = spawn(recall, ['serve'], { + cwd: workspace, env: { ...process.env, OAF_PORT: String(port), OAF_DATA_DIR: data, OAF_WORKSPACE_ROOT: workspace, HOME: home }, stdio: ['ignore', 'pipe', 'pipe'] }); @@ -96,7 +145,10 @@ try { }); must(graph.status === 200 && graph.body.graph?.summary?.nodeCount > 0 && graph.body.impact?.changedLocators?.includes('workspace://src/app.js'), `Inspect source graph previews changed code: ${graph.status} ${graph.text}`); - console.log('PASS consumer package dry-run'); + must(!JSON.stringify(map).includes(packageRoot), 'installed commands do not leak package paths'); + console.log('PASS packed tarball installed in isolated HOME'); + console.log('PASS README CLI contract from installed recall binary'); + console.log('PASS bundled benchmark fixtures from installed recall binary'); console.log('PASS consumer Recall Map first-run report'); console.log('PASS consumer temp HOME mcp install'); console.log('PASS consumer real MCP client smoke'); @@ -109,9 +161,14 @@ try { await rm(temp, { recursive: true, force: true }); } -function runJson(command, args) { - const result = spawnSync(command, args, { cwd: root, encoding: 'utf8' }); +function run(command, args, options = {}) { + const result = spawnSync(command, args, { encoding: 'utf8', ...options }); if (result.status !== 0) throw new Error(`${command} ${args.join(' ')} failed: ${result.stderr || result.stdout}`); + return result; +} + +function runJson(command, args, options = {}) { + const result = run(command, args, options); return JSON.parse(result.stdout); } @@ -119,6 +176,10 @@ function must(condition, message) { if (!condition) throw new Error(message); } +function isReadyHandoff(output) { + return output.includes('State: ready') && output.includes('MCP readback: ok'); +} + async function freePort() { const probe = http.createServer(); probe.listen(0, '127.0.0.1'); diff --git a/tests/cli.test.mjs b/tests/cli.test.mjs index a6a3c2ea..978f6ef8 100644 --- a/tests/cli.test.mjs +++ b/tests/cli.test.mjs @@ -1289,7 +1289,7 @@ test('graph trace summary prints terminal locator and prototype scope',()=>{cons test('graph trace locator prefix filters inbound callers',()=>{const root=mkdtempSync(path.join(os.tmpdir(),'oaf-cli-graph-trace-prefix-'));mkdirSync(path.join(root,'lib'),{recursive:true});mkdirSync(path.join(root,'test'),{recursive:true});writeFileSync(path.join(root,'lib','reply.js'),['export function send(reply) {',' return reply','}'].join('\n'));writeFileSync(path.join(root,'lib','route.js'),["import { send } from './reply.js'",'export function routeHandler(reply) {',' return send(reply)','}'].join('\n'));writeFileSync(path.join(root,'test','reply.test.js'),["import { send } from '../lib/reply.js'",'export function testCaller(reply) {',' return send(reply)','}'].join('\n'));const env={...process.env,OAF_FIXED_NOW:'2026-06-23T00:00:00.000Z'};const result=spawnSync(process.execPath,['apps/cli/oaf.mjs','graph','trace','--root',root,'--symbol','send','--direction','inbound','--locator-prefix','workspace://lib/','--format','summary'],{encoding:'utf8',env});assert.equal(result.status,0,result.stderr);assert.match(result.stdout,/routeHandler workspace:\/\/lib\/route\.js/);assert.doesNotMatch(result.stdout,/testCaller/);assert.doesNotMatch(result.stdout,/workspace:\/\/test\//);}); test('graph export alias summary prints the exported symbol locator',()=>{const root=mkdtempSync(path.join(os.tmpdir(),'oaf-cli-graph-export-alias-'));mkdirSync(path.join(root,'src'),{recursive:true});writeFileSync(path.join(root,'src','local-alias.ts'),['function helper() {'," return 'helper';",'}','','class Hono {',' request() { return true; }','}','','export { Hono as HonoBase };'].join('\n'));const env={...process.env,OAF_FIXED_NOW:'2026-06-23T00:00:00.000Z'};const search=spawnSync(process.execPath,['apps/cli/oaf.mjs','graph','search','--root',root,'--query','HonoBase export','--edge-kinds','exports','--format','summary'],{encoding:'utf8',env});assert.equal(search.status,0,search.stderr);assert.match(search.stdout,/HonoBase -> Hono \(class\) workspace:\/\/src\/local-alias\.ts#L5-L7/);assert.doesNotMatch(search.stdout,/helper/);const trace=spawnSync(process.execPath,['apps/cli/oaf.mjs','graph','trace','--root',root,'--symbol','HonoBase','--edge-kinds','exports','--format','summary'],{encoding:'utf8',env});assert.equal(trace.status,0,trace.stderr);assert.match(trace.stdout,/Start nodes: 1/);assert.match(trace.stdout,/Hono workspace:\/\/src\/local-alias\.ts#L5-L7/);}); test('context graph preview rejects non-dry-run mode',()=>{const root=mkdtempSync(path.join(os.tmpdir(),'oaf-cli-source-graph-'));mkdirSync(path.join(root,'src'),{recursive:true});writeFileSync(path.join(root,'src','auth.ts'),'export function approveTokenReset() { return true; }');const result=spawnSync(process.execPath,['apps/cli/oaf.mjs','context','graph','preview','--root',root,'--query','approve token reset','--format','json'],{encoding:'utf8'});assert.equal(result.status,2);assert.match(result.stderr,/context graph preview --dry-run is required/)}); -test('benchmark truth-floor emits sanitized JSON and gates failures',()=>{const env={...process.env,OAF_COMMIT_SHA:'1234567890abcdef1234567890abcdef12345678'};const passing=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','benchmark-truth-floor','--dataset','evals/benchmark-truth-floor/cases.v1.json','--format','json'],{encoding:'utf8',env});assert.equal(passing.status,0,passing.stderr);const report=JSON.parse(passing.stdout);assert.equal(report.gateDecision,'pass');assert.equal(report.commitSha,'1234567890abcdef1234567890abcdef12345678');assert.equal(report.safeguards.externalAdaptersEnabled,0);assert.equal(report.safeguards.externalWritesEnabled,false);assert(!passing.stdout.includes('/Users/rebel'));assert(!passing.stdout.includes('credential-sentinel-value'));const root=mkdtempSync(path.join(os.tmpdir(),'oaf-cli-benchmark-'));const failingPath=path.join(root,'cases.v1.json');const failingDataset=JSON.parse(readFileSync('evals/benchmark-truth-floor/cases.v1.json','utf8'));failingDataset.cases=[{...failingDataset.cases[0],requiredEvidenceIds:['ev_missing']}];writeFileSync(failingPath,JSON.stringify(failingDataset,null,2));const failing=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','benchmark-truth-floor','--dataset',failingPath,'--format','json'],{encoding:'utf8',env});assert.equal(failing.status,1,failing.stderr);const failedReport=JSON.parse(failing.stdout);assert.equal(failedReport.gateDecision,'fail');const unsupported=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','hosted-models','--dataset','evals/benchmark-truth-floor/cases.v1.json','--format','json'],{encoding:'utf8',env});assert.equal(unsupported.status,2);assert.match(unsupported.stderr,/unsupported suite/i)}); +test('benchmark truth-floor emits sanitized JSON and gates failures',()=>{const env={...process.env,OAF_COMMIT_SHA:'1234567890abcdef1234567890abcdef12345678'};const passing=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','benchmark-truth-floor','--dataset','evals/benchmark-truth-floor/cases.v1.json','--format','json'],{encoding:'utf8',env});assert.equal(passing.status,0,passing.stderr);const report=JSON.parse(passing.stdout);assert.equal(report.gateDecision,'pass');assert.equal(report.commitSha,'1234567890abcdef1234567890abcdef12345678');assert.equal(report.safeguards.externalAdaptersEnabled,0);assert.equal(report.safeguards.externalWritesEnabled,false);assert(!passing.stdout.includes('/Users/rebel'));assert(!passing.stdout.includes('credential-sentinel-value'));const root=mkdtempSync(path.join(os.tmpdir(),'oaf-cli-benchmark-'));const failingPath=path.join(root,'cases.v1.json');const failingDataset=JSON.parse(readFileSync('evals/benchmark-truth-floor/cases.v1.json','utf8'));failingDataset.cases=[{...failingDataset.cases[0],requiredEvidenceIds:['ev_missing']}];writeFileSync(failingPath,JSON.stringify(failingDataset,null,2));const failing=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','benchmark-truth-floor','--dataset',failingPath,'--format','json'],{encoding:'utf8',env});assert.equal(failing.status,1,failing.stderr);const failedReport=JSON.parse(failing.stdout);assert.equal(failedReport.gateDecision,'fail');const missing=spawnSync(process.execPath,[path.join(process.cwd(),'apps/cli/oaf.mjs'),'benchmark','truth-floor','--dataset','missing-cases.json','--format','json'],{cwd:root,encoding:'utf8',env});assert.equal(missing.status,2);assert.match(missing.stderr,/missing-cases\.json/);assert.doesNotMatch(missing.stderr,new RegExp(root.replace(/[.*+?^${}()|[\]\\]/gu,'\\$&')));const unsupported=spawnSync(process.execPath,['apps/cli/oaf.mjs','benchmark','truth-floor','--suite','hosted-models','--dataset','evals/benchmark-truth-floor/cases.v1.json','--format','json'],{encoding:'utf8',env});assert.equal(unsupported.status,2);assert.match(unsupported.stderr,/unsupported suite/i)}); test('bench sufficiency compares governed recall with same-budget keyword snippets',()=>{ const result=spawnSync(process.execPath,['apps/cli/oaf.mjs','bench','sufficiency','--read-only','--root','.','--budget','640','--format','json'],{encoding:'utf8'}); assert.equal(result.status,0,result.stderr); diff --git a/tests/release-readiness.test.mjs b/tests/release-readiness.test.mjs index b650daf7..8b9f9a1f 100644 --- a/tests/release-readiness.test.mjs +++ b/tests/release-readiness.test.mjs @@ -105,7 +105,7 @@ test('repository manifest generator ignores Cargo target build output directorie assert.equal(paths.some((filePath) => filePath.startsWith('rust/target/')), false); }); -test('npm package excludes private scratch local config and generated state', () => { +test('npm package contains the runtime contract without checkout-only test weight', () => { const result = spawnSync('npm', ['pack', '--dry-run', '--json'], { encoding: 'utf8' }); assert.equal(result.status, 0, result.stderr); const [pack] = JSON.parse(result.stdout); @@ -120,8 +120,12 @@ test('npm package excludes private scratch local config and generated state', () assert.equal(paths.includes('README.md'), true); assert.equal(paths.includes('apps/cli/oaf.mjs'), true); assert.equal(paths.includes('scripts/verify-handoff.mjs'), true); + assert.equal(paths.some((filePath) => filePath.startsWith('tests/')), false); assert.equal(paths.some((filePath) => filePath.startsWith('adapters/') && filePath.endsWith('/README.md')), false); assert.equal(paths.some((filePath) => filePath.startsWith('adapters/') && filePath.endsWith('/UPSTREAM.lock')), true); + assert.ok(pack.entryCount <= 830, `npm package has ${pack.entryCount} files; expected at most 830`); + assert.ok(pack.size <= 1_220_000, `npm package is ${pack.size} compressed bytes; expected at most 1,220,000`); + assert.ok(pack.unpackedSize <= 5_250_000, `npm package is ${pack.unpackedSize} unpacked bytes; expected at most 5,250,000`); }); test('installed npm package setup does not re-pack generated local state', () => { @@ -165,6 +169,12 @@ test('installed npm package setup does not re-pack generated local state', () => assert.match(map.stdout, /src\/index\.ts/); assert.doesNotMatch(map.stdout, new RegExp(packageRoot.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'))); assert.equal(existsSync(path.join(packageRoot, '.local', 'state.json')), false); + const truthFloor = spawnSync(recall, ['benchmark', 'truth-floor', '--suite', 'benchmark-truth-floor', '--dataset', 'evals/benchmark-truth-floor/cases.v1.json', '--format', 'json'], { cwd: work, encoding: 'utf8', env }); + assert.equal(truthFloor.status, 0, truthFloor.stderr); + assert.equal(JSON.parse(truthFloor.stdout).gateDecision, 'pass'); + const temporal = spawnSync(recall, ['bench', 'temporal', '--read-only', '--root', '.', '--format', 'json'], { cwd: work, encoding: 'utf8', env }); + assert.equal(temporal.status, 0, temporal.stderr); + assert.equal(JSON.parse(temporal.stdout).headline.oafWins, true); const hookPlan = spawnSync(oaf, ['hook', 'install', '--agent', 'codex', '--dry-run', '--format', 'json'], { cwd: work, encoding: 'utf8', env }); assert.equal(hookPlan.status, 0, hookPlan.stderr); const hookReport = JSON.parse(hookPlan.stdout); diff --git a/tests/usage-docs.test.mjs b/tests/usage-docs.test.mjs index e70371f1..dfd6e024 100644 --- a/tests/usage-docs.test.mjs +++ b/tests/usage-docs.test.mjs @@ -6,15 +6,17 @@ function read(path) { return readFileSync(path, 'utf8'); } -test('README and status separate the source candidate from the published package', () => { +test('README and status keep the release candidate honest across the registry handoff', () => { const readme = read('README.md'); const status = JSON.parse(read('PROJECT_STATUS.json')); const release = status.capabilities.find((capability) => capability.id === 'release.readiness'); const web = status.capabilities.find((capability) => capability.id === 'bootstrap.web'); - assert.match(readme, /current source checkout is the 1\.1\.0 release candidate\. npm still serves\s+1\.0\.5, which does not include Recall Map/); - assert.match(readme, /npm run recall -- setup\nnpm run recall -- map --root \. --sqlite \.local\/memory\.sqlite --format summary\nnpm run recall -- handoff/); - assert.match(release.limitations.join('\n'), /1\.0\.5 is published; the 1\.1\.0 source release candidate on main is not yet published/); + assert.match(readme, /npm view memory-recall version/); + assert.match(readme, /npm install -g memory-recall@1\.1\.0\nrecall setup\nrecall map --root \. --sqlite \.local\/memory\.sqlite --format summary\nrecall handoff/); + assert.match(readme, /If the registry still reports an older version, use the source checkout/); + assert.doesNotMatch(readme, /npm still serves\s+1\.0\.5/); + assert.match(release.limitations.join('\n'), /1\.1\.0 is the package release candidate; verify `npm view memory-recall version` returns 1\.1\.0/); assert.match(web.limitations.join('\n'), /confirm-gated proposal approval through the authenticated loopback API/); }); @@ -108,9 +110,8 @@ test('public usage docs avoid stale task and missing context-file examples while assert.match(contract, /Implemented: local JS\/TS static graph, reviewed SQLite memory, read-only MCP\./); assert.match(contract, /Experimental: Rust acceleration paths require a local build before explicit invocation\./); assert.match(contract, /Unsupported: automatic transcript capture, write-capable MCP, hosted sync, and non-JS\/TS source graph analysis\./); - assert.match(readme, /npm run recall -- setup\nnpm run recall -- map --root \. --sqlite \.local\/memory\.sqlite --format summary\nnpm run recall -- handoff/); - assert.match(readme, /current source checkout is the 1\.1\.0 release candidate\. npm still serves\s+1\.0\.5, which does not include Recall Map/); - assert.match(readme, /Source release candidate: \*\*1\.1\.0\*\*\. Published npm version: \*\*1\.0\.5\*\*\./); + assert.match(readme, /npm install -g memory-recall@1\.1\.0\nrecall setup\nrecall map --root \. --sqlite \.local\/memory\.sqlite --format summary\nrecall handoff/); + assert.match(readme, /Release candidate: \*\*1\.1\.0\*\*\. Registry version: verify with `npm view memory-recall version`\./); assert.match(readme, /`recall setup` creates only local state\. `recall map` is the explicit first\s+read-only repository scan; it does not run silently during setup\./); assert.match(normalHandoff, /recall setup\nrecall map --root \. --sqlite \.local\/memory\.sqlite --format summary\nrecall handoff/); assert.match(normalHandoff, /`recall setup` creates only local Recall state in the current repository; it\s+does not scan source files\. `recall map` is the explicit first read-only scan\./);