Add schema validation to the `json-cas put` command to ensure data
integrity. The CLI now validates the payload against the specified
schema before storing, and exits with a non-zero code and descriptive
error message if validation fails.
Changes:
- Add schema existence check in cmdPut()
- Add payload validation before storing
- Exit with error code 1 on validation failure
- Provide helpful error messages indicating the file and schema
- Add comprehensive test suite with 16 test scenarios covering:
- Valid data (regression tests)
- Type mismatches (new validation)
- Schema errors (edge cases)
- Integration with existing features
- Error message quality
The hash command continues to work without validation (dry-run
consistency), and schema put continues to use its own validation.
Fixes#50
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In-memory rendering of { type, value } envelopes without store writes.
Store is optional and read-only (for expanding nested cas_ref references).
CLI: ucas render --pipe/-p reads JSON from stdin.
Core: renderDirect(typeHash, value, store?, options?) for programmatic use.
Fixes#48
1. Deduplicate RenderOptions type
- Remove duplicate definition from liquid-render.ts
- Import from render.ts instead (canonical location)
2. Remove unused _globalDecay parameter
- Remove from renderNode function signature
- Update all call sites
3. Fix test numbering gaps
- Suite 4: Renumber 4.4→4.2, 4.5→4.3
- Suite 7→6: Renumber 7.1→6.1, 7.2→6.2, 7.4→6.3
- Suite 8→7: Renumber all 8.x→7.x tests
- Suite 10→8: Renumber 10.1→8.1
- Result: consecutive suite numbering (1-8)
4. CLI test status: All 95 tests pass (no pre-existing failures found)
Fixes#46
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates LiquidJS template rendering into the CLI render command.
When a template is registered for a node's schema type via the variable
system (@ucas/template/text/<schema-hash>), the CLI will use the template
for rendering. Otherwise, it falls back to YAML output.
Changes:
- Modified cmdRender in index.ts to use renderAsync with variable store
- Added Suite 6: CLI Integration with Templates (5 comprehensive tests)
- Fixed template file format: templates must be JSON-encoded strings
- Removed unused render import from index.ts
- Renamed unused globalDecay parameter in liquid-render.ts
Test coverage increased from 336 to 341 tests, all passing.
Fixes#40
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes reviewer feedback:
1. Fixed TypeScript strict mode error where ctx.engine was possibly null
- Refactored to pass store/varStore/globalDecay directly to createLiquidEngine
- Eliminated RenderContext type that caused circular dependency
- Engine is now properly typed as Liquid (non-nullable)
2. Removed dynamic imports from production code
- Changed render.ts to use static import of renderWithTemplate
- Changed hasTemplate to use static import of putSchema
- Complies with CLAUDE.md convention against dynamic imports
All tests pass (336 tests), build succeeds with no TypeScript errors,
lint checks pass with only 1 minor warning about unused parameter
(which is actually used in recursive calls).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement ucas template subcommands for managing template storage:
- template set <schema-hash> <file> | --inline <text>: Store template text in CAS
- template get <schema-hash>: Retrieve template as raw text
- template list: List all templates with preview
- template delete <schema-hash>: Delete template variable binding
Templates are stored as plain text under @string schema and bound to
variables using the naming pattern @ucas/template/text/<schema-hash>.
Fixes#38
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- var set <name> <hash> (upsert, replaces create+update)
- var get <name> --schema <hash> (schema required)
- var delete <name> [--schema <hash>] (optional schema)
- var list [prefix] (replaces --scope)
- var tag <name> --schema <hash> ...
- 41 CLI tests, all passing
Fixes#34
Ref #31
Migrate CLI var subcommands from ULID ID model to (name, schema) composite key model.
- Replace var create/update with unified var set (upsert semantics)
- Update var get to require --schema parameter for precise query
- Enhance var delete with batch (no --schema) and precise (with --schema) modes
- Refactor var list to use positional prefix parameter
- Update var tag to target composite keys
- Add comprehensive test suite (41 tests, 100% coverage)
- Update Variable schema: remove id/scope, add name field
Fixes#34
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses Review Round 2 feedback for variable model refactor:
1. Remove create() method - set() is now the unified entry point
2. Remove VariableDuplicateError class (only used by create())
3. Clean up dead code: Array.isArray(existing) checks in update()/remove()/tag()
4. Add tag/label conflict validation to set() update path
5. Migrate gc.test.ts from create() to set()
Changes:
- Delete create() method (lines 381-467) and VariableDuplicateError class
- Remove Array.isArray checks from 3 methods (always null, never array)
- Remove orphaned delete() JSDoc comment
- Add 3 new tests for set() update path tag/label conflict validation
- Replace 10 create() calls with set() in gc.test.ts
Test results: 193 pass (190 existing + 3 new)
Build: clean, Lint: clean
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes#33
## Changes
### 1. Enhanced Name Validation
- Added `validateName()` private method with comprehensive validation rules
- Updated `InvalidVariableNameError` to include specific `reason` field
- Validation rules:
- Each segment must match [a-zA-Z0-9._-]+
- Segments separated by /
- No empty segments (e.g., a//b)
- No leading/trailing slashes (e.g., /a or a/)
- Applied to all mutating operations: set(), create(), update(), tag()
### 2. New set() Upsert Method
- Implements upsert semantics: insert if not exists, update if exists
- Checks (name, schema) pair existence using extractSchema(value)
- On update: preserves created timestamp, updates value and updated timestamp
- Preserves existing tags/labels when called without options
- Replaces tags/labels when called with options
- Allows same name with different schemas
### 3. Optional Schema in get()
- Overloaded signature: get(name) and get(name, schema)
- get(name) without schema:
- Returns null when no variables exist
- Returns single Variable when one schema variant exists
- Returns Variable[] when multiple schema variants exist
- get(name, schema) with schema:
- Returns Variable | null for exact match
- Includes complete tags and labels
### 4. Renamed delete() to remove() with Optional Schema
- Overloaded signature: remove(name) and remove(name, schema)
- remove(name) without schema:
- Deletes all schema variants for the name
- Returns Variable[] (all deleted variants)
- Returns empty array [] when nothing found
- remove(name, schema) with schema:
- Deletes specific (name, schema) variant
- Returns single Variable
- Throws VariableNotFoundError when not found
- Cascades deletion to tags and labels via foreign key constraints
### 5. Code Quality Improvements
- Fixed `any` type usage, replaced with `unknown` and proper type guards
- Fixed string concatenation to use template literals
- Updated all internal get() calls to handle new return types
- Applied strict null checks and array checks
### 6. Comprehensive Test Coverage
- 36 tests covering all new behaviors
- Test suites for:
- set() upsert method (7 tests)
- get() with optional schema (6 tests)
- remove() with optional schema (6 tests)
- Name validation (6 tests)
- Integration workflows (2 tests)
- Legacy methods (2 tests)
- Database schema verification
- List and tag operations
- All tests pass: bun test (151 pass, 0 fail)
## Verification
- ✅ bun test - All 151 tests pass
- ✅ bun run build - Clean TypeScript build
- ✅ bunx biome check - No lint errors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements RFC-31 Phase 1 - refactors the Variable model to use a composite
primary key of (name, schema) instead of the previous ULID id + scope approach.
Key changes:
1. **Type Model**:
- Removed `id: VariableId` and `scope: string` fields
- Added `name: string` as part of composite key with `schema: Hash`
- Variables with same name but different schemas are now distinct entities
2. **Database Schema**:
- Changed primary key from `id` to `(name, schema)`
- Updated foreign keys in `variable_tags` and `variable_labels` tables
- Replaced scope-based indexes with name-based indexes
- Enabled foreign key constraints for proper cascade deletes
3. **CRUD Operations** - all methods updated to use `(name, schema)`:
- `create(name, value, options)` - validates unique (name, schema)
- `get(name, schema)` - retrieves by composite key
- `update(name, schema, value)` - updates with schema validation
- `delete(name, schema)` - deletes with cascade to tags/labels
- `list({ namePrefix?, schema?, tags?, labels? })` - filters by name prefix and schema
- `tag(name, schema, operations)` - manages tags/labels by composite key
4. **Error Types**:
- New: `VariableDuplicateError` for duplicate `(name, schema)` pairs
- New: `InvalidVariableNameError` for empty names
- Removed: `InvalidScopeError` (no longer needed)
- Updated: `VariableNotFoundError` to reference `(name, schema)`
5. **GC Adaptation**:
- Garbage collection works correctly with refactored model
- Preserves nodes referenced by variables across all schemas
- Global collection across all variable names and schemas
6. **Tests**:
- Added comprehensive test suite covering all new functionality
- Database schema validation tests
- CRUD operation tests with composite keys
- Multi-schema scenarios (same name, different schemas)
- Tag/label management tests
- GC integration tests
- End-to-end workflow tests
7. **Breaking Changes**:
- This is a backward-incompatible change
- CLI commands will need updates in a future phase (out of scope for Phase 1)
- Data migration is out of scope for Phase 1
Fixes#32
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements garbage collection (GC) with mark-and-sweep algorithm:
- Mark phase: recursively walks references from all variable values (global, not scoped)
- Sweep phase: deletes unmarked CAS nodes
- Schema preservation: schemas referenced by reachable nodes are preserved
- Bootstrap preservation: self-referencing meta-schema always preserved
New features:
- Core gc() function in packages/json-cas/src/gc.ts with GcStats interface
- Extended Store interface with listAll() and delete() methods
- CLI command: json-cas gc (outputs JSON stats)
- Comprehensive test suite with 16 test scenarios
Implements: #23
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements comprehensive tag/label functionality for variables:
## Core Features
- Tags: key-value pairs with same-key override semantics
- Labels: bare identifiers
- Deletion syntax: `:name` removes tag or label
- Mutual exclusion: tag keys and label names cannot coexist
- Unified `var tag` command for all tag/label operations
## Data Model
- Extended Variable type with tags/labels fields
- New variable_tags and variable_labels SQLite tables
- Foreign key constraints with CASCADE delete
- Proper indexes for efficient querying
## Query Capabilities
- Filter by scope (hierarchical prefix matching)
- Filter by tags (key:value pairs, AND logic)
- Filter by labels (bare names, AND logic)
- Combined filtering (scope + tags/labels)
## CLI Commands
- `json-cas var create --tag <tag>...` - initial tags/labels
- `json-cas var tag <id> <tag>...` - add/update/delete
- `json-cas var list --tag <tag>...` - query with filters
## Implementation Details
- TagLabelConflictError and InvalidTagFormatError types
- Atomic batch operations with rollback
- 46 comprehensive tests for tags/labels
- Backward compatible with Phase 1
- All 214 tests pass
Closes#22
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes#27
Changes:
1. Variable uses type instead of interface
2. Add JSON envelope output {type, value} to all CLI var commands
3. Add list method with scope prefix matching to VariableStore and CLI
4. Fix var-db path to default to <storePath>/variables.db instead of <defaultStorePath>/variables.db
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Applied monorepo normalization:
- Updated TypeScript to use composite project references with NodeNext
- Configured Biome for linting and formatting
- Standardized package.json metadata across all packages
- Set up changesets for version management and npm publishing
- Added vitest test infrastructure to all packages
- Created Gitea Actions CI pipeline
- Added solve-issue workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add oneOf to BOOTSTRAP_PAYLOAD (meta-schema)
- Add oneOf to ALLOWED_SCHEMA_KEYS
- Add oneOf validation in isValidSchema
- Add test 2.7b for oneOf acceptance
- Remove oneOf from unsupported keywords test
Required by workflow's solve-issue.yaml which uses oneOf for
discriminated union frontmatter schemas.