fix(ids): skip unfilled placeholders in extractPurpose priority chain - #749
Conversation
Closes the cosmetic follow-up noted after PR #747 — CodeRabbit's "Outside diff range" comments about entity-registry self-entry drift and the `component-creation-guide` purpose field reading "Analyzes provided dataset to identify patterns and insights" (clearly unrelated to a component creation guide). ## Bug 1: purpose inference matched body text it shouldn't have `extractPurpose()` in `populate-entity-registry.js` had a fallback regex: ```js const descMatch = content.match(/(?:description|purpose|summary)[:]\s*(.+)/i); ``` This is case-insensitive and unanchored — it matched ANY occurrence in the file body, including example output inside installer transcripts and fenced code blocks. For `component-creation-guide.md`, it picked up line 132: ``` ? Task description: Analyzes provided dataset to identify patterns and insights ``` …which is example output showing what a user might type, not a description of the guide itself. Fix: restructured `extractPurpose()` to a stricter priority chain: 1. **YAML frontmatter** — `description:` (or aliases) inside the `---` block at the top of the file. Anchored to the frontmatter region so body matches can't leak in. 2. **`## Purpose` section** — first non-empty line. 3. **`## Overview` section** — same shape as Purpose. Many guides use Overview instead (e.g. component-creation-guide.md itself). 4. **First `# Title` heading** — the document's name. 5. **Generic fallback** — `Entity at <path>`. The previous body-level `(?:description|purpose|summary)[:]` regex was REMOVED deliberately. Its false-positive rate (matching example output, installer transcripts, code block content) outweighed the narrow legitimate case it covered, which is now handled by the frontmatter branch. ## Bug 2: self-entry drift in `entities.data.entity-registry` The registry contains a record for itself because the data layer scan picks up every `.yaml`. That record's `lastVerified` was stuck at whatever value the previous scan happened to write, and the `checksum` was likewise stale relative to the regenerated file. Fix: added `syncSelfRegistryEntry()` which runs after the registry object is assembled but before yaml.dump. It: - Sets `lastVerified` to the same `lastUpdated` timestamp written into `metadata.lastUpdated` (single source of truth for this regen event). - Sets `checksum` to a sentinel value `sha256:<self-reference>` because hashing the registry from inside the registry is circular — any computed hash invalidates itself the moment it's written. The sentinel signals "skip this comparison" to downstream validators; trying to compute a "real" hash here would just produce a number that doesn't match its own input. The sync is guarded: it only fires when the self-entry's `path` field is `.aiox-core/data/entity-registry.yaml`. The companion `entity-registry.js` module under `entities.modules.*` is NOT self-referential and is left alone. ## Validation Regenerated the registry on this branch. After the fix: - `grep -c "Analyzes provided dataset" .aiox-core/data/entity-registry.yaml` → `0` - `grep -c "Add MCP Server Task" .aiox-core/data/entity-registry.yaml` → `0` (was the wrong purpose on the yaml self-entry) - The yaml self-entry's `lastVerified` now matches `metadata.lastUpdated` exactly - The yaml self-entry's `checksum` is the sentinel as expected The `component-creation-guide` entity is no longer in the registry at all — the file lives under `.aiox-core/core/docs/` which is not in `SCAN_CONFIG` (the `modules` scan covers `.aiox-core/core` but only for `*.js`/`*.mjs`, not `*.md`). Its previous presence was an artifact of a prior scan run with broader config; this regen cleans it up. If we want to start tracking framework docs as entities, that's a separate concern handled in a follow-up to SCAN_CONFIG. ## Out of scope for this commit A second-order cleanup that this PR does NOT do: there are still some purposes in the registry that look like leaked template placeholders (`'{Brief description of what this task does...}'`, `'{{TASK_TITLE}}'`, `'*${taskName.replace(...)}'`). Those come from files whose own bodies contain unfilled handlebars or JS literals — the inference is technically working, the source is the problem. Separate from this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous suite had a test "extracts from description field" that fed a bare `description: ...` string and expected it to be matched. That was exactly the body-level fallback regex that produced the "Analyzes provided dataset" false purpose on component-creation-guide.md (reported by CodeRabbit on PR #747). The refactor in this PR intentionally removed that match, so the test must be updated to reflect the new contract. Changes to the test file: * Replaced "extracts from description field" with two replacement tests: - "extracts from YAML frontmatter `description:` field" — feeds a `---\ndescription: X\n---` block and expects extraction from it. - "handles quoted YAML frontmatter description" — feeds `description: "X"` inside frontmatter and expects the quotes to be stripped. * Added "falls back to ## Overview when no Purpose section" — covers the new strategy 3 in the priority chain (many guides like component-creation-guide.md use Overview instead of Purpose). * Added TWO regression tests for the bug that motivated the refactor: - "does NOT match body-level `description:` outside frontmatter" — reproduces the exact component-creation-guide pattern (Overview section + a fenced code block with `Task description:` inside) and asserts the Overview wins. - "does NOT extract from `description:` lines without YAML frontmatter delimiters" — bare `description: X` without `---` delimiters falls through to the `# Title` header, NOT to the body line. Test result: 79/79 pass. Existing 5 tests in the suite still pass. Two new tests cover the YAML frontmatter branch (priority 1) and the new `## Overview` branch (priority 3). Two regression tests lock in the removal of the body-level matcher so it can't silently come back.
Follow-up to PR #748. With body-level matching gone, the priority chain still picked up garbage purposes from files that have literal unfilled template placeholders or JS interpolation in the SOURCES the chain reads: YAML frontmatter, `## Purpose` sections, `# Title` headings. The extractor was technically working — the source data was just full of `{Brief description...}`, `{{TASK_TITLE}}`, and `${context.componentName}` strings that nobody ever filled in. ## What this PR does Adds `looksLikePlaceholder(candidate)` that detects: 1. Whole-string single placeholder: `{Brief...}`, `{{var}}`, `${ctx.x}` 2. Leading-token placeholder: `*${name}foo`, `${ctx.x} bar`, `{Title} extra` 3. Dominant interpolation (>30% of string): catches `${icon} @${id} — ${name}${archetype !== 'Specialist' ? ` (${archetype})` : ''} | ${title}` where most of the string is `${}` blocks The detector is conservative on real prose: - `Manage feature flags with { enabled: true } syntax` → keeps - `Use this skill when... ${variable not present}` (<30% interpolation) → keeps - Empty / whitespace / nullish → keeps (caller handles) Wired into all four `extractPurpose` strategies (frontmatter, `## Purpose`, `## Overview`, `# Title`). When a strategy produces a placeholder, it falls through to the next strategy. When all four produce placeholders, the function returns the generic `Entity at <path>` fallback rather than a nonsense purpose. ## Validation Counted placeholders in `.aiox-core/data/entity-registry.yaml` before and after this PR: ``` $ grep -E "purpose: ['\"]?\{[A-Z]|purpose:.*\{\{|purpose:.*\\\$\\{" \ .aiox-core/data/entity-registry.yaml | wc -l # Before: 16 # After: 0 ``` Examples of entries that now have sensible purposes (or sensible generic fallbacks) instead of unfilled templates: - `*${taskName.replace(/-/g, '-')}` → now resolves via header or falls to `Entity at <path>` - `{Brief description of what this task does and when to use it}` → now falls through to the next strategy - `Generated: ${new Date().toISOString()}` → captured by dominant- interpolation heuristic and skipped - `${icon} @${id} — ${name}${archetype !== ...}` → captured by dominant-interpolation heuristic and skipped ## Tests 12 new tests added to `tests/core/ids/populate-entity-registry.test.js`: - 4 cover `extractPurpose` falling through placeholders at each strategy boundary - 8 cover `looksLikePlaceholder` directly (positive cases for each pattern + negative cases for normal prose that should NOT trip it) Suite: 91/91 pass (was 79/79 on PR #748). ## Why not just fix the source files? The placeholders live in templates and generators — they're SUPPOSED to be unfilled when stored in the framework. They get filled at use- time when the template instantiates an output. The registry's inference is reading the template body as if it were a finished doc, which is the actual bug. Fixing the inference (this PR) is correct; trying to fill the templates would break their template-ness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughScript adds a placeholder-detection helper and integrates it into purpose extraction (frontmatter, headers, title), exports the helper and a sync helper, updates tests to cover placeholder cases, and regenerates the install manifest entries for the modified files. ChangesEntity Registry Enhancement
Possibly related PRs
Suggested labels
Suggested reviewers
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.aiox-core/development/scripts/populate-entity-registry.js (1)
751-760: ⚡ Quick winAdd targeted tests for self-entry sync invariants.
This new path mutates
lastVerifiedandchecksumfor a special case but currently lacks explicit regression tests. Please add coverage for: (1) matching self path updates both fields, and (2) non-matching path leaves entry untouched.Also applies to: 796-812
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.aiox-core/development/scripts/populate-entity-registry.js around lines 751 - 760, Add unit tests for the self-entry sync invariants in populate-entity-registry by creating tests that call syncSelfRegistryEntry(registry, lastUpdated, SELF_CHECKSUM_SENTINEL) and assert two scenarios: (1) when registry contains an entry whose path equals the self path, verify that its lastVerified is updated to lastUpdated and its checksum is set to SELF_CHECKSUM_SENTINEL; (2) when no entry matches the self path, verify that existing entries' lastVerified and checksum remain unchanged. Use representative registry fixtures and explicitly reference syncSelfRegistryEntry, SELF_CHECKSUM_SENTINEL, lastVerified, checksum, registry, and lastUpdated in assertions to prevent regressions.tests/core/ids/populate-entity-registry.test.js (1)
34-35: ⚡ Quick winAdd regression tests for registry self-entry synchronization behavior.
Given the new self-sync logic in the script, add assertions that
entities.data['entity-registry']gets sentinel checksum +lastVerified === metadata.lastUpdatedonly when path matches.aiox-core/data/entity-registry.yaml.Also applies to: 1051-1052
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/core/ids/populate-entity-registry.test.js` around lines 34 - 35, Update the test cases to assert the registry self-entry sync behavior: in the populate-entity-registry.test.js (inside the describe('extractEntityId()') block and the other spot around the same behavior) add assertions that entities.data['entity-registry'] has the sentinel checksum and that entities.data['entity-registry'].lastVerified === metadata.lastUpdated only when the file path equals '.aiox-core/data/entity-registry.yaml'; ensure the negative case (different path) asserts that lastVerified is not set/unchanged and checksum not overwritten. Reference the existing test helpers/objects (entities, metadata, and the path string) when adding these assertions so they validate the new self-sync logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.aiox-core/development/scripts/populate-entity-registry.js:
- Around line 222-223: The frontmatter/section extraction regexes (e.g., the
regex used for frontmatterMatch via content.match and the similar regexes around
lines 235-246) are anchored to "\n" only and will fail on CRLF or when the
delimiter is at EOF; update them to accept optional CR before LF and allow the
closing delimiter to be followed by either a newline or end-of-string (use \r?\n
and allow (?:\r?\n|$) or equivalent) so the frontmatter and other section
matches succeed on CRLF files and when the delimiter is at EOF.
---
Nitpick comments:
In @.aiox-core/development/scripts/populate-entity-registry.js:
- Around line 751-760: Add unit tests for the self-entry sync invariants in
populate-entity-registry by creating tests that call
syncSelfRegistryEntry(registry, lastUpdated, SELF_CHECKSUM_SENTINEL) and assert
two scenarios: (1) when registry contains an entry whose path equals the self
path, verify that its lastVerified is updated to lastUpdated and its checksum is
set to SELF_CHECKSUM_SENTINEL; (2) when no entry matches the self path, verify
that existing entries' lastVerified and checksum remain unchanged. Use
representative registry fixtures and explicitly reference syncSelfRegistryEntry,
SELF_CHECKSUM_SENTINEL, lastVerified, checksum, registry, and lastUpdated in
assertions to prevent regressions.
In `@tests/core/ids/populate-entity-registry.test.js`:
- Around line 34-35: Update the test cases to assert the registry self-entry
sync behavior: in the populate-entity-registry.test.js (inside the
describe('extractEntityId()') block and the other spot around the same behavior)
add assertions that entities.data['entity-registry'] has the sentinel checksum
and that entities.data['entity-registry'].lastVerified === metadata.lastUpdated
only when the file path equals '.aiox-core/data/entity-registry.yaml'; ensure
the negative case (different path) asserts that lastVerified is not
set/unchanged and checksum not overwritten. Reference the existing test
helpers/objects (entities, metadata, and the path string) when adding these
assertions so they validate the new self-sync logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ca3a215c-9ea4-460d-a175-2c3ed6d48359
📒 Files selected for processing (4)
.aiox-core/data/entity-registry.yaml.aiox-core/development/scripts/populate-entity-registry.js.aiox-core/install-manifest.yamltests/core/ids/populate-entity-registry.test.js
| const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); | ||
| if (frontmatterMatch) { |
There was a problem hiding this comment.
Make extraction regexes newline-agnostic to avoid missed purposes on CRLF/EOF content.
The current patterns only match \n and can miss valid frontmatter/section extraction on CRLF files or sections that end at EOF without a trailing newline, causing fallback to weaker strategies.
💡 Suggested fix
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
- const purposeMatch = content.match(/^##\s*Purpose\s*\n+([\s\S]*?)(?=\n##|\n---|\n$)/im);
+ const purposeMatch = content.match(/^##\s*Purpose\s*\r?\n+([\s\S]*?)(?=\r?\n##|\r?\n---|\s*$)/im);
- const overviewMatch = content.match(/^##\s*Overview\s*\n+([\s\S]*?)(?=\n##|\n---|\n$)/im);
+ const overviewMatch = content.match(/^##\s*Overview\s*\r?\n+([\s\S]*?)(?=\r?\n##|\r?\n---|\s*$)/im);Also applies to: 235-246
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.aiox-core/development/scripts/populate-entity-registry.js around lines 222
- 223, The frontmatter/section extraction regexes (e.g., the regex used for
frontmatterMatch via content.match and the similar regexes around lines 235-246)
are anchored to "\n" only and will fail on CRLF or when the delimiter is at EOF;
update them to accept optional CR before LF and allow the closing delimiter to
be followed by either a newline or end-of-string (use \r?\n and allow
(?:\r?\n|$) or equivalent) so the frontmatter and other section matches succeed
on CRLF files and when the delimiter is at EOF.
…tests, regen registry+manifest
📊 Coverage ReportCoverage report not available
Generated by PR Automation (Story 6.1) |
Addresses CodeRabbit feedback on PR #749 — the self-entry sync logic introduced in PR #748 was load-bearing (lastVerified + checksum drift was the original CodeRabbit complaint) but lacked targeted tests. Tests added cover the four invariants: 1. **Self-path match → mutates both fields**: when the entry's path equals `.aiox-core/data/entity-registry.yaml`, `lastVerified` becomes the new timestamp and `checksum` becomes the sentinel. 2. **Non-self path → no mutation**: the companion entry pointing at `.aiox-core/core/doctor/checks/entity-registry.js` (a real JS module, NOT self-referential) must NOT be touched. Catches the most likely regression: if the path guard is removed, the JS module entry would get clobbered with the yaml sentinel. 3. **Missing entities.data** is a no-op (defensive). 4. **Missing self-entry** is a no-op + leaves other data entries alone (defensive). 5. **Malformed registry** (`{}`, `null`) does not throw. Required exporting `syncSelfRegistryEntry` from the script's `module.exports` and consuming it in the test file. Suite: 96/96 pass (was 91/91).
Addressed in a3cccd7 — exported syncSelfRegistryEntry + added 5 regression tests: (1) self-path match mutates both fields, (2) non-self JS module entry left untouched, (3-5) defensive no-ops for missing entities.data/self-entry/malformed registry. 96/96 jest pass.
* chore(release): bump to 5.2.7 — consolidate PRs #744-#750 from 5.2.6 Consolidates 7 PRs merged after the 5.2.6 release. None of them required an immediate hotfix on their own, but together they unblock cohort students from a real CI/CD failure mode (#739 Bug 1) and remove three sources of registry drift. ## What ships in 5.2.7 - **#744** (66b302a) — Pipeline hardening: `publish_legacy_aiox_core` race fix, smoke timeout 90s→240s with dual visibility check, structured notify summary, Windows path escape fix via WORKSPACE_DIR env var. Plus the canonical `docs/guides/release-procedure.md` SOP. - **#745** (342ef63) — Ecosystem alignment: `installation-troubleshooting.md` Issue 10 (install-side recovery for the npm-hijack symptom), aiox-pro-access.md link, slim wrappers for publish-npm.md/release-management.md tasks pointing at the SOP, devops.md Release Procedure section. - **#746** (ef7a352) — Internal `.aiox-core/package.json` rebranded `@aiox-fullstack/core@4.31.1` → `@aiox-squads/core-internal@5.2.7` (private, no phantom peerDeps). New `scripts/validate-aiox-core-namespace.js` (5-rule gate) wired into `validate:publish`. Closes #739 Bug 2. - **#747** (e5cd355) — 70 substitutions across 17 files realigning legacy `aiox-core/{templates,checklists,agents,tasks,workflows,agent-teams}/<X>` references to current `.aiox-core/...` layout. Unblocks `@po *validate-story-draft`. Closes #741. - **#748** (1f97f6f) — IDS `extractPurpose()` rewritten as strict priority chain (frontmatter → ## Purpose → ## Overview → # Title → fallback). Removed body-level regex that picked up example transcripts as purposes. New `syncSelfRegistryEntry()` writes sentinel `sha256:<self-reference>` checksum for the registry's own record. - **#749** (70fa5e2) — `looksLikePlaceholder()` filter excludes 16 garbage purposes (`{Brief...}`, `{{TASK_TITLE}}`, `${context.componentName}`). - **#750** (21782cc) — Installer honors `--ci` / `--yes` / `--skipPrompts` flags at the CLAUDE.md merge prompt. Non-interactive runs no longer block waiting for keyboard input. Closes #739 Bug 1. ## Version sites bumped (5 — up from 4 in the previous SOP) - `package.json` (root): 5.2.6 → 5.2.7 - `compat/aiox-core/package.json`: version + dep["@aiox-squads/core"] 5.2.6 → 5.2.7 - `packages/installer/package.json`: 3.3.5 → 3.3.6 (#750 touched installer src) - `.aiox-core/package.json` (internal): 5.2.6 → 5.2.7 (validate-aiox-core-namespace enforces lockstep with root — new in 5.2.7, first release under the gate) - `package-lock.json` refresh - `CHANGELOG.md`: new [5.2.7] entry - `docs/guides/release-procedure.md`: bump the "4 sites" rule to "5 sites" reflecting the validate-aiox-core-namespace gate added in PR #746 ## Validation - `npm run lint` → exit 0 - `npm run test:ci` → 8510 passed (151 skipped), 344/355 suites, 229s - `npx jest tests/installer/ --no-coverage` → 276/276 pass - `npm run validate:publish` → PASS (12911 files; namespace sync verified) Pro install hijack fix (#742) shipped in 5.2.6 and remains in production — this release does not re-iterate it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address CodeRabbit nitpick on release-procedure.md lockstep wording PR #751 CodeRabbit review flagged that the "Bump all five version sites in lockstep" wording was ambiguous — the table below explicitly treats `packages/installer/package.json` as conditional ("patch bump if installer changed; otherwise leave"). Operators reading only line 36 could over-bump the installer. Reworded to distinguish: - Root + internal manifest + compat: MUST stay in lockstep at same version - packages/installer: bumped ONLY when installer source changed (not every release) Also explicit on the two failure-mode detection layers: - Smoke tests (catch silent publish mismatches) - validate-aiox-core-namespace.js wired into validate:publish (rule 4 enforces lockstep) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…SynkraAI#749) * fix(ids): scope purpose inference + sync self-entry on regen Closes the cosmetic follow-up noted after PR SynkraAI#747 — CodeRabbit's "Outside diff range" comments about entity-registry self-entry drift and the `component-creation-guide` purpose field reading "Analyzes provided dataset to identify patterns and insights" (clearly unrelated to a component creation guide). ## Bug 1: purpose inference matched body text it shouldn't have `extractPurpose()` in `populate-entity-registry.js` had a fallback regex: ```js const descMatch = content.match(/(?:description|purpose|summary)[:]\s*(.+)/i); ``` This is case-insensitive and unanchored — it matched ANY occurrence in the file body, including example output inside installer transcripts and fenced code blocks. For `component-creation-guide.md`, it picked up line 132: ``` ? Task description: Analyzes provided dataset to identify patterns and insights ``` …which is example output showing what a user might type, not a description of the guide itself. Fix: restructured `extractPurpose()` to a stricter priority chain: 1. **YAML frontmatter** — `description:` (or aliases) inside the `---` block at the top of the file. Anchored to the frontmatter region so body matches can't leak in. 2. **`## Purpose` section** — first non-empty line. 3. **`## Overview` section** — same shape as Purpose. Many guides use Overview instead (e.g. component-creation-guide.md itself). 4. **First `# Title` heading** — the document's name. 5. **Generic fallback** — `Entity at <path>`. The previous body-level `(?:description|purpose|summary)[:]` regex was REMOVED deliberately. Its false-positive rate (matching example output, installer transcripts, code block content) outweighed the narrow legitimate case it covered, which is now handled by the frontmatter branch. ## Bug 2: self-entry drift in `entities.data.entity-registry` The registry contains a record for itself because the data layer scan picks up every `.yaml`. That record's `lastVerified` was stuck at whatever value the previous scan happened to write, and the `checksum` was likewise stale relative to the regenerated file. Fix: added `syncSelfRegistryEntry()` which runs after the registry object is assembled but before yaml.dump. It: - Sets `lastVerified` to the same `lastUpdated` timestamp written into `metadata.lastUpdated` (single source of truth for this regen event). - Sets `checksum` to a sentinel value `sha256:<self-reference>` because hashing the registry from inside the registry is circular — any computed hash invalidates itself the moment it's written. The sentinel signals "skip this comparison" to downstream validators; trying to compute a "real" hash here would just produce a number that doesn't match its own input. The sync is guarded: it only fires when the self-entry's `path` field is `.aiox-core/data/entity-registry.yaml`. The companion `entity-registry.js` module under `entities.modules.*` is NOT self-referential and is left alone. ## Validation Regenerated the registry on this branch. After the fix: - `grep -c "Analyzes provided dataset" .aiox-core/data/entity-registry.yaml` → `0` - `grep -c "Add MCP Server Task" .aiox-core/data/entity-registry.yaml` → `0` (was the wrong purpose on the yaml self-entry) - The yaml self-entry's `lastVerified` now matches `metadata.lastUpdated` exactly - The yaml self-entry's `checksum` is the sentinel as expected The `component-creation-guide` entity is no longer in the registry at all — the file lives under `.aiox-core/core/docs/` which is not in `SCAN_CONFIG` (the `modules` scan covers `.aiox-core/core` but only for `*.js`/`*.mjs`, not `*.md`). Its previous presence was an artifact of a prior scan run with broader config; this regen cleans it up. If we want to start tracking framework docs as entities, that's a separate concern handled in a follow-up to SCAN_CONFIG. ## Out of scope for this commit A second-order cleanup that this PR does NOT do: there are still some purposes in the registry that look like leaked template placeholders (`'{Brief description of what this task does...}'`, `'{{TASK_TITLE}}'`, `'*${taskName.replace(...)}'`). Those come from files whose own bodies contain unfilled handlebars or JS literals — the inference is technically working, the source is the problem. Separate from this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(ids): align extractPurpose tests with refactored priority chain The previous suite had a test "extracts from description field" that fed a bare `description: ...` string and expected it to be matched. That was exactly the body-level fallback regex that produced the "Analyzes provided dataset" false purpose on component-creation-guide.md (reported by CodeRabbit on PR SynkraAI#747). The refactor in this PR intentionally removed that match, so the test must be updated to reflect the new contract. Changes to the test file: * Replaced "extracts from description field" with two replacement tests: - "extracts from YAML frontmatter `description:` field" — feeds a `---\ndescription: X\n---` block and expects extraction from it. - "handles quoted YAML frontmatter description" — feeds `description: "X"` inside frontmatter and expects the quotes to be stripped. * Added "falls back to ## Overview when no Purpose section" — covers the new strategy 3 in the priority chain (many guides like component-creation-guide.md use Overview instead of Purpose). * Added TWO regression tests for the bug that motivated the refactor: - "does NOT match body-level `description:` outside frontmatter" — reproduces the exact component-creation-guide pattern (Overview section + a fenced code block with `Task description:` inside) and asserts the Overview wins. - "does NOT extract from `description:` lines without YAML frontmatter delimiters" — bare `description: X` without `---` delimiters falls through to the `# Title` header, NOT to the body line. Test result: 79/79 pass. Existing 5 tests in the suite still pass. Two new tests cover the YAML frontmatter branch (priority 1) and the new `## Overview` branch (priority 3). Two regression tests lock in the removal of the body-level matcher so it can't silently come back. * fix(ids): skip unfilled placeholders in extractPurpose priority chain Follow-up to PR SynkraAI#748. With body-level matching gone, the priority chain still picked up garbage purposes from files that have literal unfilled template placeholders or JS interpolation in the SOURCES the chain reads: YAML frontmatter, `## Purpose` sections, `# Title` headings. The extractor was technically working — the source data was just full of `{Brief description...}`, `{{TASK_TITLE}}`, and `${context.componentName}` strings that nobody ever filled in. ## What this PR does Adds `looksLikePlaceholder(candidate)` that detects: 1. Whole-string single placeholder: `{Brief...}`, `{{var}}`, `${ctx.x}` 2. Leading-token placeholder: `*${name}foo`, `${ctx.x} bar`, `{Title} extra` 3. Dominant interpolation (>30% of string): catches `${icon} @${id} — ${name}${archetype !== 'Specialist' ? ` (${archetype})` : ''} | ${title}` where most of the string is `${}` blocks The detector is conservative on real prose: - `Manage feature flags with { enabled: true } syntax` → keeps - `Use this skill when... ${variable not present}` (<30% interpolation) → keeps - Empty / whitespace / nullish → keeps (caller handles) Wired into all four `extractPurpose` strategies (frontmatter, `## Purpose`, `## Overview`, `# Title`). When a strategy produces a placeholder, it falls through to the next strategy. When all four produce placeholders, the function returns the generic `Entity at <path>` fallback rather than a nonsense purpose. ## Validation Counted placeholders in `.aiox-core/data/entity-registry.yaml` before and after this PR: ``` $ grep -E "purpose: ['\"]?\{[A-Z]|purpose:.*\{\{|purpose:.*\\\$\\{" \ .aiox-core/data/entity-registry.yaml | wc -l # Before: 16 # After: 0 ``` Examples of entries that now have sensible purposes (or sensible generic fallbacks) instead of unfilled templates: - `*${taskName.replace(/-/g, '-')}` → now resolves via header or falls to `Entity at <path>` - `{Brief description of what this task does and when to use it}` → now falls through to the next strategy - `Generated: ${new Date().toISOString()}` → captured by dominant- interpolation heuristic and skipped - `${icon} @${id} — ${name}${archetype !== ...}` → captured by dominant-interpolation heuristic and skipped ## Tests 12 new tests added to `tests/core/ids/populate-entity-registry.test.js`: - 4 cover `extractPurpose` falling through placeholders at each strategy boundary - 8 cover `looksLikePlaceholder` directly (positive cases for each pattern + negative cases for normal prose that should NOT trip it) Suite: 91/91 pass (was 79/79 on PR SynkraAI#748). ## Why not just fix the source files? The placeholders live in templates and generators — they're SUPPOSED to be unfilled when stored in the framework. They get filled at use- time when the template instantiates an output. The registry's inference is reading the template body as if it were a finished doc, which is the actual bug. Fixing the inference (this PR) is correct; trying to fill the templates would break their template-ness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(ids): add 5 regression tests for syncSelfRegistryEntry Addresses CodeRabbit feedback on PR SynkraAI#749 — the self-entry sync logic introduced in PR SynkraAI#748 was load-bearing (lastVerified + checksum drift was the original CodeRabbit complaint) but lacked targeted tests. Tests added cover the four invariants: 1. **Self-path match → mutates both fields**: when the entry's path equals `.aiox-core/data/entity-registry.yaml`, `lastVerified` becomes the new timestamp and `checksum` becomes the sentinel. 2. **Non-self path → no mutation**: the companion entry pointing at `.aiox-core/core/doctor/checks/entity-registry.js` (a real JS module, NOT self-referential) must NOT be touched. Catches the most likely regression: if the path guard is removed, the JS module entry would get clobbered with the yaml sentinel. 3. **Missing entities.data** is a no-op (defensive). 4. **Missing self-entry** is a no-op + leaves other data entries alone (defensive). 5. **Malformed registry** (`{}`, `null`) does not throw. Required exporting `syncSelfRegistryEntry` from the script's `module.exports` and consuming it in the test file. Suite: 96/96 pass (was 91/91). --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m 5.2.6 (SynkraAI#751) * chore(release): bump to 5.2.7 — consolidate PRs SynkraAI#744-SynkraAI#750 from 5.2.6 Consolidates 7 PRs merged after the 5.2.6 release. None of them required an immediate hotfix on their own, but together they unblock cohort students from a real CI/CD failure mode (SynkraAI#739 Bug 1) and remove three sources of registry drift. ## What ships in 5.2.7 - **SynkraAI#744** (d949918) — Pipeline hardening: `publish_legacy_aiox_core` race fix, smoke timeout 90s→240s with dual visibility check, structured notify summary, Windows path escape fix via WORKSPACE_DIR env var. Plus the canonical `docs/guides/release-procedure.md` SOP. - **SynkraAI#745** (b728a39) — Ecosystem alignment: `installation-troubleshooting.md` Issue 10 (install-side recovery for the npm-hijack symptom), aiox-pro-access.md link, slim wrappers for publish-npm.md/release-management.md tasks pointing at the SOP, devops.md Release Procedure section. - **SynkraAI#746** (dea939e) — Internal `.aiox-core/package.json` rebranded `@aiox-fullstack/core@4.31.1` → `@aiox-squads/core-internal@5.2.7` (private, no phantom peerDeps). New `scripts/validate-aiox-core-namespace.js` (5-rule gate) wired into `validate:publish`. Closes SynkraAI#739 Bug 2. - **SynkraAI#747** (e874103) — 70 substitutions across 17 files realigning legacy `aiox-core/{templates,checklists,agents,tasks,workflows,agent-teams}/<X>` references to current `.aiox-core/...` layout. Unblocks `@po *validate-story-draft`. Closes SynkraAI#741. - **SynkraAI#748** (e8f8761) — IDS `extractPurpose()` rewritten as strict priority chain (frontmatter → ## Purpose → ## Overview → # Title → fallback). Removed body-level regex that picked up example transcripts as purposes. New `syncSelfRegistryEntry()` writes sentinel `sha256:<self-reference>` checksum for the registry's own record. - **SynkraAI#749** (6113083) — `looksLikePlaceholder()` filter excludes 16 garbage purposes (`{Brief...}`, `{{TASK_TITLE}}`, `${context.componentName}`). - **SynkraAI#750** (1d16b51) — Installer honors `--ci` / `--yes` / `--skipPrompts` flags at the CLAUDE.md merge prompt. Non-interactive runs no longer block waiting for keyboard input. Closes SynkraAI#739 Bug 1. ## Version sites bumped (5 — up from 4 in the previous SOP) - `package.json` (root): 5.2.6 → 5.2.7 - `compat/aiox-core/package.json`: version + dep["@aiox-squads/core"] 5.2.6 → 5.2.7 - `packages/installer/package.json`: 3.3.5 → 3.3.6 (SynkraAI#750 touched installer src) - `.aiox-core/package.json` (internal): 5.2.6 → 5.2.7 (validate-aiox-core-namespace enforces lockstep with root — new in 5.2.7, first release under the gate) - `package-lock.json` refresh - `CHANGELOG.md`: new [5.2.7] entry - `docs/guides/release-procedure.md`: bump the "4 sites" rule to "5 sites" reflecting the validate-aiox-core-namespace gate added in PR SynkraAI#746 ## Validation - `npm run lint` → exit 0 - `npm run test:ci` → 8510 passed (151 skipped), 344/355 suites, 229s - `npx jest tests/installer/ --no-coverage` → 276/276 pass - `npm run validate:publish` → PASS (12911 files; namespace sync verified) Pro install hijack fix (SynkraAI#742) shipped in 5.2.6 and remains in production — this release does not re-iterate it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address CodeRabbit nitpick on release-procedure.md lockstep wording PR SynkraAI#751 CodeRabbit review flagged that the "Bump all five version sites in lockstep" wording was ambiguous — the table below explicitly treats `packages/installer/package.json` as conditional ("patch bump if installer changed; otherwise leave"). Operators reading only line 36 could over-bump the installer. Reworded to distinguish: - Root + internal manifest + compat: MUST stay in lockstep at same version - packages/installer: bumped ONLY when installer source changed (not every release) Also explicit on the two failure-mode detection layers: - Smoke tests (catch silent publish mismatches) - validate-aiox-core-namespace.js wired into validate:publish (rule 4 enforces lockstep) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to PR #748. With body-level matching gone, the priority chain still picked up garbage purposes from files that have literal unfilled template placeholders or JS interpolation in the sources the chain reads. The extractor was working — the source data was the problem (~16 entries in the registry had purposes like
{Brief description...},{{TASK_TITLE}},*${taskName.replace(...)}).Changes
New
looksLikePlaceholder(candidate)detects:{Brief...},{{var}},${ctx.x}*${name}foo,${ctx.x} bar,{Title} extra${icon} @${id} — ${name}${archetype !== ...}where most of the string is${}blocksConservative on real prose:
Manage feature flags with { enabled: true } syntax→ keepsUse this skill when... ${variable not present}(<30% interpolation) → keepsWired into all 4
extractPurposestrategies (frontmatter,## Purpose,## Overview,# Title). When a strategy produces a placeholder, falls through to next. When all produce placeholders, returns genericEntity at <path>fallback.Validation
Tests
12 new tests added — 4 cover priority-chain fall-through at each placeholder boundary, 8 cover
looksLikePlaceholderdirectly (positive + negative cases).Suite: 91/91 pass (was 79/79 on PR #748).
Why not fix the source files?
Placeholders live in templates/generators — they're SUPPOSED to be unfilled when stored. They get filled at use-time when the template instantiates. Registry inference reading template bodies as finished docs is the actual bug. Fixing the inference (this PR) is correct; trying to fill templates would break their template-ness.
Related
entity-registryself-entry +component-creation-guidepurpose drift🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores