From d06103f3e583b2b6e5f06f5e242b49a02d24264f Mon Sep 17 00:00:00 2001 From: Jesse Ngolab Date: Fri, 26 Jun 2026 00:15:23 -0500 Subject: [PATCH] fix: add memory fragment type and gracefully format validation errors - Added 'memory' to the FragmentType enum so that `init` scaffolds don't fail schema validation. (Closes #3) - Wrapped manifest loading across all CLI commands (`build`, `pack`, `lint`) to format ZodError validation failures cleanly instead of dumping a raw stack trace. (Closes #4) --- CHANGELOG.md | 8 ++++---- src/cli.ts | 27 ++++++++++++++++++--------- src/core/types.ts | 10 ++++++---- src/core/vendor.ts | 3 ++- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 041d389..5d71112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -## [Unreleased] — 1.0.0 (stable API) +# [1.0.0](https://github.com/jessn-dev/agentic-scope/compare/v0.2.1...v1.0.0) (2026-06-24) -First stable line: the CLI surface, MCP tool surface, and manifest schema are -now considered stable and versioned under SemVer. Released as a **major** because -the vendor-file output format changed (see Changed). +First stable release: the CLI surface, MCP tool surface, and manifest schema are +now stable and versioned under SemVer. Cut as a **major** because the vendor-file +output format changed (see Changed). ### Added - `pack --budget ` to override the manifest token budget per run, and diff --git a/src/cli.ts b/src/cli.ts index 1b7dd15..efc8036 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,11 +2,26 @@ import { Command } from "commander"; import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; +import { ZodError } from "zod"; import { loadManifest, MANIFEST_FILENAME, isProject } from "./core/manifest.js"; import { pack, renderPack, oversizedFragments } from "./core/fragments.js"; import { build, checkBuild, VENDOR_TARGETS, resolveTargets } from "./core/vendor.js"; import { writeSchema, SCHEMA_PATH } from "./core/schema.js"; +function loadManifestOrExit(dir: string) { + try { + return loadManifest(resolve(dir)); + } catch (err) { + if (err instanceof ZodError) { + const msg = err.issues.map((i) => `${i.message} (path: ${i.path.join(".")})`).join("\n "); + console.error(`✗ ${msg}`); + } else { + console.error(`✗ ${err instanceof Error ? err.message : err}`); + } + process.exit(1); + } +} + // Single source of truth for the version: package.json (kept in sync by semantic-release). const pkg = JSON.parse( readFileSync(new URL("../package.json", import.meta.url), "utf8"), @@ -49,13 +64,7 @@ program .argument("[dir]", "Project directory", ".") .action((dir: string) => { const root = resolve(dir); - let loaded; - try { - loaded = loadManifest(root); - } catch (err) { - console.error(`✗ ${err instanceof Error ? err.message : err}`); - process.exit(1); - } + const loaded = loadManifestOrExit(dir); const problems: string[] = []; const ids = new Set(); for (const f of loaded.manifest.fragment) { @@ -92,7 +101,7 @@ program ) .option("--check", "Verify vendor files are up to date with .scope/ (no writes); exit 1 if stale/missing") .action((dir: string, opts: { target?: string[]; check?: boolean }) => { - const loaded = loadManifest(resolve(dir)); + const loaded = loadManifestOrExit(dir); let targets; try { targets = resolveTargets(opts.target); @@ -136,7 +145,7 @@ program taskWords: string[], opts: { dir: string; path?: string[]; budget?: number; exact?: boolean; raw?: boolean }, ) => { - const loaded = loadManifest(resolve(opts.dir)); + const loaded = loadManifestOrExit(opts.dir); const text = taskWords.join(" "); const result = pack(loaded, { text, paths: opts.path }, { budget: opts.budget, exact: opts.exact }); if (opts.raw) { diff --git a/src/core/types.ts b/src/core/types.ts index 2f4edf5..f8d5be3 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -6,15 +6,17 @@ import { z } from "zod"; * - knowledge: static reference data, loaded lazily * - spec: current task requirements * - persona: a swappable agent "hat" + * - memory: decisions, logs, and state */ -export const FragmentType = z.enum(["rule", "knowledge", "spec", "persona"]); +export const FragmentType = z.enum(["rule", "knowledge", "spec", "persona", "memory"]); export type FragmentType = z.infer; /** Default precedence used when ordering matched fragments for a pack. */ export const TYPE_PRECEDENCE: Record = { - rule: 3, - spec: 2, - persona: 1, + rule: 4, + spec: 3, + persona: 2, + memory: 1, knowledge: 0, }; diff --git a/src/core/vendor.ts b/src/core/vendor.ts index 9c793f2..af4023a 100644 --- a/src/core/vendor.ts +++ b/src/core/vendor.ts @@ -49,11 +49,12 @@ export const VENDOR_TARGETS: VendorTarget[] = [ }, ]; -const ORDER: Fragment["type"][] = ["rule", "persona", "spec", "knowledge"]; +const ORDER: Fragment["type"][] = ["rule", "persona", "spec", "memory", "knowledge"]; const HEADINGS: Record = { rule: "Rules (always apply)", persona: "Persona", spec: "Active specs", + memory: "Project memory", knowledge: "Reference knowledge", };