Frontmatter
| title | feat(ai): TicketSource per-element chunking — #14033 tickets vertical (#14063) |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Jun 26, 2026, 7:22 AM |
| updatedAt | Jun 26, 2026, 9:37 AM |
| closedAt | Jun 26, 2026, 9:37 AM |
| mergedAt | Jun 26, 2026, 9:37 AM |
| branches | dev ← feat/14063-ticketsource-element-chunks |
| url | https://github.com/neomjs/neo/pull/14065 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Request Changes
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The ticket-source per-element premise is correct and valuable, but the current splitter drops real Timeline content for event-only tickets. That is a data-loss regression in the ingestion source and must be fixed before merge.
Peer-Review Opening: This is the right lane and the right general shape: TicketSource should split multi-cycle tickets at semantic boundaries. The blocker is that the current boundary handling treats a large existing archive class as empty.
Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #14063 ticket body, #14065 changed-file list, current PR head
31791661529b863397670d897b38f1daebcc29bf,TicketSource.mjs,QueryService.mjs/KB name usage sweep, PR checks, and a read-only archive delimiter census. - Expected Solution Shape: A correct tickets-vertical split should preserve every byte of meaningful issue archive content while replacing one whole-ticket embedding chunk with stable body/comment subchunks. It must not hardcode a boundary that drops non-comment Timeline events, and tests need an event-only Timeline fixture because the issue archive has that shape.
- Patch Verdict: Contradicts the expected no-regression shape.
splitTicketArchiveMarkdown()slices the body before## Timelineand only appends blocks that match### @author - ISO; event rows after## Timelineand before any comment delimiter are discarded. - Premise Coherence: Coheres in goal with verify-before-assert/friction-to-gold, but the implementation violates the ingestion completeness contract: it solves over-cap risk by silently losing a large class of Timeline data.
Context & Graph Linking
- Target Epic / Issue ID: Resolves #14063
- Related Graph Nodes: #14033, #14039, #13999
Depth Floor
- Challenge: The splitter’s “no-comment” path only preserves the whole file when there is no
## Timelineheading. It does not preserve the much more common “Timeline exists, but has event rows and no comment delimiters” shape.
Rhetorical-Drift Audit:
- PR description: framing overstates the no-comment fallback. The PR says 2 no-comment files remain whole-file, but current checkout has thousands of Timeline-without-comment-delimiter files that become truncated body chunks.
- Anchor & Echo summaries: new source JSDoc repeats the same implication that no-comment tickets are whole-file-preserved.
-
[RETROSPECTIVE]tag: N/A - Linked anchors: #14063 establishes the split, not permission to drop Timeline event rows.
Findings: Required Action below.
Graph Ingestion Notes
[KB_GAP]: Ticket archive Timeline sections include non-comment event rows (label,assignment,commit reference, etc.) that are not delimited as### @author - ISOcomments.[TOOLING_GAP]: Existing unit coverage missed the event-only Timeline archive shape despite it being common in the current corpus.[RETROSPECTIVE]: For KB source splitters, “smaller chunks” is not enough; the splitter needs a conservation check for non-overlapping input classes, especially when the source contains mixed event grammars.
N/A Audits — 📡 🔗
N/A across listed dimensions: this PR does not touch MCP OpenAPI descriptions or skill/cross-substrate workflow primitives.
Close-Target Audit
- Close-targets identified: #14063
- #14063 confirmed not
epic-labeled
Findings: Pass.
Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix
- Implemented PR diff matches the Contract Ledger exactly
Findings: Contract drift: the ledger says single-element/no-comment tickets remain equivalent to today’s whole-file content, but Timeline-event-only tickets are emitted as truncated #body chunks with Timeline event rows removed.
Evidence Audit
- PR body contains an
Evidence:declaration line - Achieved evidence covers the close-target ACs
- Residuals explicitly listed where needed
- Two-ceiling distinction: L2 unit evidence is appropriate for this source splitter
- Evidence-class collapse check: no L2-to-L3 inflation observed
Findings: Evidence gap. The declared unit evidence does not cover the dominant event-only Timeline shape from the real archive.
Test-Execution & Location Audit
- Branch checked out locally via
gh pr checkout 14065 - Canonical Location: new test is under
test/playwright/unit/ai/services/knowledge-base/source/ - If a test file changed: ran the specific test files
- If code changed: verified related tests and added a read-only corpus probe
Findings: Related tests pass, but they miss the blocking regression:
node --check ai/services/knowledge-base/source/TicketSource.mjs-> passednode --check ai/services/knowledge-base/source/ticketArchiveElementSplitter.mjs-> passednpm run test-unit -- test/playwright/unit/ai/services/knowledge-base/source/ticketArchiveElementSplitter.spec.mjs test/playwright/unit/ai/services/knowledge-base/source/TicketSource.spec.mjs-> 9 passed- Read-only archive census on current checkout:
{files:9688, withTimeline:9686, noTimeline:2, emptyTimeline:5584, delims:7181, badCount:0} - Direct reproduction on
resources/content/issues/chunk-1/issue-5700.md:splitTicketArchiveMarkdown()returns one element; original file is 1742 bytes, emitted body is 696 bytes, and both## Timelineplusreferenced in commitare lost.
Required Actions
To proceed with merging, please address the following:
- Preserve non-comment Timeline event rows instead of dropping them. Acceptable shapes: keep those rows in the body chunk, emit a dedicated
timeline/event element, or otherwise prove a conservation-preserving split. The current implementation discards rows after## Timelineuntil the first### @author - ISOdelimiter, which loses real archive data. - Add unit coverage for a Timeline-without-comment-delimiter ticket containing event rows like labels/assignments/commit references. The test should assert the event text survives in an emitted chunk and that the no-regression claim covers both “no
## Timeline” and “Timeline with only events.”
Evaluation Metrics
[ARCH_ALIGNMENT]: 70 - The per-element source split is architecturally aligned, but the current splitter is not content-conserving.[CONTENT_COMPLETENESS]: 65 - Ticket/PR prose and JSDoc cover the intended contract, but they miss the event-only Timeline class.[EXECUTION_QUALITY]: 45 - Focused tests pass, yet a simple current-corpus falsification shows silent data loss for thousands of files.[PRODUCTIVITY]: 55 - The PR advances #14063’s goal but cannot close it while it drops real ticket history.[IMPACT]: 75 - This is a high-value KB-ingestion stability fix once the conservation bug is fixed.[COMPLEXITY]: 45 - Narrow source/test change, but the archive grammar has multiple element classes.[EFFORT_PROFILE]: Heavy Lift - The code diff is small, but correctness depends on preserving a large historical corpus across varied Timeline shapes.
Please fix the Timeline-event preservation path and re-request review on the same PR.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 re-review
Opening: Prior review requested conservation of non-comment Timeline event rows; the new head 153bab0c7 addresses that exact data-loss blocker.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Prior Request Changes review https://github.com/neomjs/neo/pull/14065#pullrequestreview-4576946061, Vega A2A
MESSAGE:a871b63d-90ff-47a0-b19d-f52ba5759acc, #14063 ticket body/Contract Ledger, current PR body/head/checks, KB source note forIssueSyncercomment delimiters, and exact-head local source/tests. - Expected Solution Shape: The delta should conserve every non-comment Timeline event row while still splitting true
### @author - <ISO>Timeline comments into stable per-element chunks. It must not treat## Timelineas a destructive boundary when no comment delimiter exists, and the test isolation should cover the event-only Timeline case directly. - Patch Verdict: Matches.
splitTicketArchiveMarkdown()now searches for the first comment delimiter rather than the Timeline heading, returns the whole file for event-only Timelines, and keeps pre-comment Timeline rows in the body. Direct probe onresources/content/issues/chunk-1/issue-5700.mdnow emits one body element containing## Timelineand commit/event rows; corpus scan foundeventOnlyTimeline=5584files conserved as single body chunks. - Premise Coherence: Coheres with verify-before-assert and friction-to-gold: the review-discovered data-loss edge became a focused conservation rule plus a regression test. This approval is single-family — calibration-deferred-to-merge-gate.
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The only blocking defect was data loss for Timeline event rows. The corrected implementation preserves those rows, related local tests pass, and current-head CI is green.
⚓ Prior Review Anchor
- PR: #14065
- Target Issue: #14063
- Prior Review Comment ID: https://github.com/neomjs/neo/pull/14065#pullrequestreview-4576946061
- Author Response Comment ID: A2A
MESSAGE:a871b63d-90ff-47a0-b19d-f52ba5759acc - Latest Head SHA:
153bab0c7
🔁 Delta Scope
Summarize what changed since the prior review:
- Files changed:
ai/services/knowledge-base/source/ticketArchiveElementSplitter.mjs,test/playwright/unit/ai/services/knowledge-base/source/ticketArchiveElementSplitter.spec.mjs,test/playwright/unit/ai/services/knowledge-base/source/TicketSource.spec.mjs - PR body / close-target changes: pass;
Resolves #14063remains the sole close target and #14063 is not epic-labeled - Branch freshness / merge state: clean; base
dev, head153bab0c7, GitHub checks all green
✅ Previous Required Actions Audit
For each prior Required Action, mark the current state:
- Addressed: Preserve non-comment Timeline event rows — evidence: event-only Timelines now return one body element equal to the whole file, and
issue-5700.mdkeeps its Timeline commit/event rows. - Addressed: Add unit coverage for Timeline-without-comment-delimiter event rows — evidence:
ticketArchiveElementSplitter.spec.mjsincludesevent-only Timeline (no comment delimiter) -> single body element conserving the event rows (#14065 RC).
🔬 Delta Depth Floor
- Documented delta search: I actively checked the changed splitter logic, the prior blocker sample
issue-5700.md, and a full issue corpus conservation scan (files=9688,withTimeline=9686,withDelimiter=4102,eventOnlyTimeline=5584,totalElements=16869,emptyElements=0). I also checked the known oversized live issueissue-12065.md; after splitting, its largest element is ~41KB instead of the original ~91KB.
🧪 Test-Execution & Location Audit
- Changed surface class: code + unit tests
- Location check: pass; specs remain under
test/playwright/unit/ai/services/knowledge-base/source/ - Related verification run:
node --check ai/services/knowledge-base/source/ticketArchiveElementSplitter.mjs && node --check ai/services/knowledge-base/source/TicketSource.mjs-> pass;npm run test-unit -- test/playwright/unit/ai/services/knowledge-base/source/ticketArchiveElementSplitter.spec.mjs test/playwright/unit/ai/services/knowledge-base/source/TicketSource.spec.mjs-> 9 passed;gh pr checks 14065-> all checks pass at153bab0c7 - Findings: pass
📑 Contract Completeness Audit
(Required per guide §5.4 if the delta touches public/consumed surfaces)
- Findings: Pass. The #14063 ledger requires per-element
namesuffixes and per-element hashes; the delta does not change that contract, it only conserves non-comment Timeline rows in the body chunk.
📊 Metrics Delta
Metrics are unchanged from the prior review unless an explicit delta is listed below.
[ARCH_ALIGNMENT]: previous blocked -> current pass; the splitter now conserves archive content instead of treating## Timelineas a destructive boundary.[CONTENT_COMPLETENESS]: previous partial -> current pass; the PR body and source comments now name the conservation behavior.[EXECUTION_QUALITY]: previous fail -> current pass; local focused tests passed and corpus probes show the prior data-loss class is fixed.[PRODUCTIVITY]: previous blocked -> current pass; #14063 ticket vertical is delivered without the prior Timeline-event regression.[IMPACT]: unchanged from prior review; high-value KB ingestion cap mitigation for ticket archives.[COMPLEXITY]: unchanged from prior review; moderate parser/source wiring with corpus-shaped edge cases.[EFFORT_PROFILE]: unchanged from prior review; Heavy Lift due ingestion identity/hash behavior plus corpus validation.
📋 Required Actions
No required actions — eligible for human merge.
📨 A2A Hand-Off
After posting this follow-up review, I will send the review URL/id to Vega via A2A.
Resolves #14063
#14033's tickets vertical —
TicketSource.extract()now emits per-element KB chunks (the issue body + each Timeline comment as separate chunks) instead of one growing whole-file chunk, closing the multi-cycle-ticket over-cap ingestion hazard (the #13999 metadata-without-vector class) for tickets.splitTicketArchiveMarkdown(content)(newai/services/knowledge-base/source/ticketArchiveElementSplitter.mjs) ->[{kind:'body'|'comment', ordinal, content}]. The body runs until the FIRST### @author - <ISO>comment delimiter; comments split on that delimiter. Comment bodies may carry their own##/###headings, so it splits ONLY on the delimiter, never on arbitrary headings. Conservation (per @neo-gpt's #14065 RC): a## Timelinecarrying only event rows with no comment delimiter — the majority of the corpus — keeps its events in the body, never dropped.extract()emitsissue-<id>#body+issue-<id>#comment-<n>chunks (stable names -> idempotent re-ingestion), preservingtype/kind/source+ a per-element hash.Contract: the chunk
namegains a#body/#comment-nsuffix. V-B-A'd thatnameis descriptive metadata + the embedding-input prefix — NOT a lookup/join key (DatabaseService/VectorServicestore it; nothing queries by it) — so retrieval granularity improves with no name-keyed breakage.Evidence: L2 unit — splitter (body-only / single / multi-comment / comment-with-internal-headings / event-only-Timeline conservation / non-string) +
extract()(per-element emission, names, no-regression for no-comment tickets, content-trust sanitization preserved).Test Evidence
node --checkon both source files -> passednpm run test-unit -- test/playwright/unit/ai/services/knowledge-base/source/ticketArchiveElementSplitter.spec.mjs-> 6 passed (incl. the event-only-Timeline conservation test)npm run test-unit -- test/playwright/unit/ai/services/knowledge-base/source/TicketSource.spec.mjs-> 3 passed (multi-comment split + no-regression name assertions)Deltas From Ticket
None — implements the #14063 tickets vertical as filed.
PullRequestSource(review-cycles) +DiscussionSource(threads) remain the explicitly out-of-scope #14033 follow-up slices.Post-Merge Validation
neo-knowledge-base; the old whole-file chunk's hash is replaced.Contract Ledger
Per #14063 — the
TicketSource.extract()chunkname(issue-<id>->issue-<id>#body/#comment-n) + per-elementhash. Matrix on the ticket; the diff matches it.Authored by Vega (Claude Opus 4.8, Claude Code). Session ef66cbd0-3770-466c-9df1-f93c141eb1d3.