test: V1 E2E pipeline + --no-write flag for ingest helper (Lane F)

E2E pipeline test exercises the full Lane A → B → C value loop:
  1. Set up fake $HOME with all 8 memory source types as fixtures
  2. gstack-memory-ingest --probe verifies counts match disk
  3. gstack-memory-ingest --incremental writes state with schema_version: 1
  4. Idempotency: re-run reports 0 changes
  5. --probe distinguishes new vs unchanged after first incremental
  6. gstack-gbrain-sync --dry-run previews 3 stages
  7. --no-code --no-brain-sync --quiet writes sync state with 1 stage entry
  8. office-hours/SKILL.md V1 manifest dispatches 4 queries (mode=manifest)
  9. Datamark envelope wraps every loaded section (Section 1D + D12)
 10. Layer 1 fallback when no skill specified — default 3-section manifest
 11. plan-ceo-review/SKILL.md manifest also dispatches (regression for V1
     manifest authoring across all 6 V1 skills)

Side effect: bin/gstack-memory-ingest.ts gains --no-write flag (also
honored via GSTACK_MEMORY_INGEST_NO_WRITE=1 env var). Skips gbrain put_page
calls while still updating the state file. Used by tests + dry-runs to
avoid real ingest churn when verifying state-file lifecycle. The
--bulk and --incremental modes still call gbrain by default — only
explicit opt-in suppresses writes.

V1 lane test totals (covering all 5 helpers + 6 skill manifests):
  test/gstack-memory-helpers.test.ts     22 tests
  test/gstack-memory-ingest.test.ts      15 tests
  test/gstack-gbrain-sync.test.ts         8 tests
  test/gstack-brain-context-load.test.ts 10 tests
  test/skill-e2e-memory-pipeline.test.ts 10 tests
  ────────────────────────────────────── ─────────
  TOTAL                                  65 passing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-01 20:15:15 -07:00
parent a4b23c436b
commit a0136e2132
2 changed files with 301 additions and 6 deletions

View File

@@ -71,6 +71,7 @@ interface CliArgs {
allHistory: boolean;
sources: Set<MemoryType>;
limit: number | null;
noWrite: boolean;
}
type MemoryType =
@@ -172,6 +173,8 @@ Options:
--all-history Walk transcripts older than 90 days too.
--sources <list> Comma-separated subset: ${ALL_TYPES.join(",")}
--limit <N> Stop after N pages written (smoke testing).
--no-write Skip gbrain put_page calls (still updates state file).
Used by tests + dry runs without actual ingest.
--help This text.
`);
}
@@ -185,6 +188,7 @@ function parseArgs(): CliArgs {
let allHistory = false;
let limit: number | null = null;
let sources: Set<MemoryType> = new Set(ALL_TYPES);
let noWrite = process.env.GSTACK_MEMORY_INGEST_NO_WRITE === "1";
for (let i = 0; i < args.length; i++) {
const a = args[i];
@@ -196,6 +200,7 @@ function parseArgs(): CliArgs {
case "--benchmark": benchmark = true; break;
case "--include-unattributed": includeUnattributed = true; break;
case "--all-history": allHistory = true; break;
case "--no-write": noWrite = true; break;
case "--limit":
limit = parseInt(args[++i] || "0", 10);
if (!Number.isFinite(limit) || limit <= 0) {
@@ -223,7 +228,7 @@ function parseArgs(): CliArgs {
}
}
return { mode, quiet, benchmark, includeUnattributed, allHistory, sources, limit };
return { mode, quiet, benchmark, includeUnattributed, allHistory, sources, limit, noWrite };
}
// ── State file ─────────────────────────────────────────────────────────────
@@ -891,11 +896,13 @@ async function ingestPass(args: CliArgs): Promise<BulkResult> {
continue;
}
const result = await withErrorContext(
`put_page:${page.slug}`,
async () => gbrainPutPage(page),
"gstack-memory-ingest"
);
const result = args.noWrite
? { ok: true }
: await withErrorContext(
`put_page:${page.slug}`,
async () => gbrainPutPage(page),
"gstack-memory-ingest"
);
if (!result.ok) {
failed++;
if (!args.quiet) {