Skip to content

feat: add adjacent relationships and selfRefField to hierarchy edges#52

Merged
mindsocket merged 2 commits into
mainfrom
feat/relationships
Mar 17, 2026
Merged

feat: add adjacent relationships and selfRefField to hierarchy edges#52
mindsocket merged 2 commits into
mainfrom
feat/relationships

Conversation

@mindsocket
Copy link
Copy Markdown
Owner

Summary

  • Introduces $metadata.relationships for defining adjacent (non-hierarchy) links between node types, with support for format, matchers, fieldOn, field, multi, and embeddedTemplateFields properties
  • Adds selfRefField to hierarchy level config, enabling a separate field for same-type parent relationships distinct from the regular parent field
  • Extends embedded parsing to recognise relationship headings and auto-type list items and table rows beneath them as the relationship's child type — no explicit inline annotations required
  • Replaces resolve-links.ts with resolve-hierarchy-edges.ts and extracts wikilink-utils.ts for cleaner separation of concerns
  • Updates template-sync to generate embedded sections (headings, lists, tables) driven by relationship definitions
  • Adds relationship wikilink validation to the validate command
  • Updates schemas, metaschema, README, docs, and skill references throughout

Test plan

  • bun run test passes (unit tests cover relationship parsing, hierarchy edge resolution, template-sync, and validation)
  • bun run test:smoke passes against all registered spaces in config.json
  • Schema with relationships array validates correctly via ost-tools schemas validate
  • Embedded relationship headings (lists and tables) are parsed and typed correctly via ost-tools dump
  • template-sync generates correct embedded sections for schemas with relationships
  • Dangling wikilinks in relationship fields are reported as validation errors

@mindsocket
Copy link
Copy Markdown
Owner Author


Code review

Found 4 issues:

  1. buildTargetIndex loses normalization and duplicate detection - The new buildTargetIndex function removed the trim() normalization and empty string check from the old addTarget helper, causing whitespace-only targets to be added and same-node duplicates to be incorrectly marked as ambiguous (git history shows this is a regression)

https://github.com/rw Ньюburn/ost-tools/blob/78ebaa8ed24b4c105afaf3390226c41d70c4d6bf/src/wikilink-utils.ts#L24-L38

  1. Array mutation silently overwrites data - When parentFieldAppend is used, the code overwrites non-array values without reporting an error (CLAUDE.local.md says: "Do not write code that ignores errors. It is far preferable for code to fail loudly so we can fix problems.")

https://github.com/rw Ньюburn/ost-tools/blob/78ebaa8ed24b4c105afaf3390226c41d70c4d6bf/src/parse-embedded.ts#L181-L188

  1. Breaking change: aliases renamed to typeAliases - All existing schema files using .aliases will break

https://github.com/rw Ньюburn/ost-tools/blob/78ebaa8ed24b4c105afaf3390226c41d70c4d6bf/src/types.ts#L75-L76

  1. Missing documentation for "Adjacent Relationships" - PR introduces a major new feature but doesn't document it in docs/concepts.md (CLAUDE.md says: "Use and maintain the definitions there as the source of truth when naming things in code, tests, comments, and documentation.")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Introduce `$metadata.relationships` for defining adjacent (non-hierarchy) links between node types, with support for `format`, `matchers`, `fieldOn`, `field`, `multi`, and `embeddedTemplateFields`
- Add `selfRefField` to hierarchy level config to allow a separate field for same-type parent relationships (distinct from the regular `field`)
- Extend embedded parsing to recognise relationship headings and type list items and table rows beneath them as the relationship's child type, without requiring explicit inline annotations
- Add `wikilink-utils.ts` and replace `resolve-links.ts` with `resolve-hierarchy-edges.ts` to cleanly separate wikilink parsing from edge resolution
- Update `template-sync` to generate embedded sections (headings, lists, tables) from relationship definitions
- Validate relationship wikilinks during the validate command
- Update schemas, metaschema, README, docs, and skill references throughout
@claude
Copy link
Copy Markdown

claude Bot commented Mar 16, 2026

Claude encountered an error —— View job


Code Review

I've thoroughly reviewed PR #52. Here's my analysis of the issues mentioned in the previous review, along with additional findings:


Analysis of Previously Reported Issues

✅ Issue 1: buildTargetIndex normalization - FIXED

The current implementation in src/wikilink-utils.ts (lines 24-42) correctly includes:

  • Line 29: const normalized = target.trim(); - normalization is present
  • Line 30: if (!normalized) continue; - empty strings are skipped
  • Lines 32-37: Proper duplicate/ambiguous detection

The related tests (tests/build-target-index.test.ts) all pass (13 pass, 0 fail). This issue has been resolved.

✅ Issue 2: Array mutation silent overwrite - FIXED

The current implementation in src/parse-embedded.ts (lines 191-209) correctly throws an error when trying to append to a non-array field:

} else {
  // Field exists but is not an array - this is an error
  throw new Error(
    `Cannot append child link to field '${fieldName}' on node '${parentFieldAppend.node.label}': ` +
      `field exists but is not an array (found ${typeof fieldValue}). ` +
      `Child link: ${linkRef}`,
  );
}

This aligns with CLAUDE.md guidance: "fail loudly so we can fix problems."

✅ Issue 4: Missing documentation - NOT AN ISSUE

The docs/concepts.md file (lines 203-214) contains comprehensive documentation for "Adjacent Relationships" including:

  • Definition of adjacent relationships
  • Explanation of field and fieldOn properties
  • Usage examples

This documentation is present and well-maintained.


🔴 Critical Issue: Field Name Mismatch (aliases vs typeAliases)

Location: src/types.ts:76 vs src/metadata-contract.ts:89

There is a breaking inconsistency between the JSON schema and TypeScript types:

File Property Name
src/metadata-contract.ts:89 (schema contract) aliases
src/types.ts:76 (SchemaMetadata interface) typeAliases
All JSON schema files aliases
src/schema.ts:353 aliases (correct)
src/commands/schemas.ts:131 typeAliases (BUG)

Impact: The code at src/commands/schemas.ts:131-132 tries to access metadata.typeAliases:

if (metadata.typeAliases && Object.keys(metadata.typeAliases).length > 0) {
  const aliasParts = Object.entries(metadata.typeAliases).map(([k, v]) => `${k}${v}`);

But the actual field name in the JSON schema is aliases, not typeAliases. This will cause the condition to always be false, and type aliases will never be displayed.

Evidence from schema files:

  • schemas/general.json:17: "aliases": { "experiment": "assumption_test" }
  • README.md:73: "aliases": { "experiment": "assumption_test" }

Fix: The TypeScript interface in src/types.ts:76 should use aliases to match the schema contract, OR all code should be updated to use typeAliases consistently. Given that the JSON schema already uses aliases and src/schema.ts correctly accesses it as aliases, the simplest fix is to change src/types.ts:76 from typeAliases to aliases.

Fix this →


Additional Observations

Strengths

  1. Comprehensive test coverage - The PR adds extensive test files covering relationship parsing, hierarchy edges, and template-sync
  2. Clean separation of concerns - The extraction of wikilink-utils.ts from resolve-links.ts improves code organization
  3. Well-documented feature - The adjacent relationships feature is thoroughly documented in docs/concepts.md
  4. Thoughtful API design - The selfRefField addition provides flexibility for same-type relationships

Minor Suggestions

  • Consider adding a migration guide if the aliases/typeAliases field name change affects existing users

Summary

Status: ⚠️ 1 Critical bug found

The PR implements valuable features for adjacent relationships and same-type parent references, but contains a breaking field name mismatch (aliases vs typeAliases) that will cause runtime errors in the schemas show command.

Recommended Action: Fix the field name inconsistency before merging.


Note: This review is based on static code analysis. The CI environment's pre-commit hooks couldn't run due to missing dependencies (biome, glob, jsonata, ajv, etc.), which is an environmental issue unrelated to the PR code quality.

@mindsocket mindsocket merged commit 4ecb611 into main Mar 17, 2026
1 of 2 checks passed
@mindsocket mindsocket deleted the feat/relationships branch March 17, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant