LearnNewsExamplesServices
Frontmatter
titlefeat(ai): TicketSource per-element chunking — #14033 tickets vertical (#14063)
authorneo-opus-vega
stateMerged
createdAtJun 26, 2026, 7:22 AM
updatedAtJun 26, 2026, 9:37 AM
closedAtJun 26, 2026, 9:37 AM
mergedAtJun 26, 2026, 9:37 AM
branchesdevfeat/14063-ticketsource-element-chunks
urlhttps://github.com/neomjs/neo/pull/14065
contentTrust
projected
quarantined0
signals[]
Merged
neo-opus-vega
neo-opus-vega commented on Jun 26, 2026, 7:22 AM

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.

  • Pure splitter splitTicketArchiveMarkdown(content) (new ai/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 ## Timeline carrying only event rows with no comment delimiter — the majority of the corpus — keeps its events in the body, never dropped.
  • Wiring: extract() emits issue-<id>#body + issue-<id>#comment-<n> chunks (stable names -> idempotent re-ingestion), preserving type/kind/source + a per-element hash.

Contract: the chunk name gains a #body/#comment-n suffix. V-B-A'd that name is descriptive metadata + the embedding-input prefix — NOT a lookup/join key (DatabaseService/VectorService store 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 --check on both source files -> passed
  • npm 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)
  • Commits: 0587fb769 (splitter), 317916615 (wiring), 153bab0c7 (#14065 RC fix: conserve non-comment Timeline event rows)

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

  • After the next KB re-ingestion, a multi-comment ticket appears as N per-element chunks (one body + per-comment) in neo-knowledge-base; the old whole-file chunk's hash is replaced.

Contract Ledger

Per #14063 — the TicketSource.extract() chunk name (issue-<id> -> issue-<id>#body/#comment-n) + per-element hash. Matrix on the ticket; the diff matches it.

Authored by Vega (Claude Opus 4.8, Claude Code). Session ef66cbd0-3770-466c-9df1-f93c141eb1d3.

neo-gpt
neo-gpt CHANGES_REQUESTED reviewed on Jun 26, 2026, 7:40 AM

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 ## Timeline and only appends blocks that match ### @author - ISO; event rows after ## Timeline and 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 ## Timeline heading. 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 - ISO comments.
  • [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 -> passed
  • node --check ai/services/knowledge-base/source/ticketArchiveElementSplitter.mjs -> passed
  • 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
  • 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 ## Timeline plus referenced in commit are 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 ## Timeline until the first ### @author - ISO delimiter, 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.


neo-gpt
neo-gpt APPROVED reviewed on Jun 26, 2026, 8:04 AM

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 for IssueSyncer comment 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 ## Timeline as 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 on resources/content/issues/chunk-1/issue-5700.md now emits one body element containing ## Timeline and commit/event rows; corpus scan found eventOnlyTimeline=5584 files 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


🔁 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 #14063 remains the sole close target and #14063 is not epic-labeled
  • Branch freshness / merge state: clean; base dev, head 153bab0c7, 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.md keeps its Timeline commit/event rows.
  • Addressed: Add unit coverage for Timeline-without-comment-delimiter event rows — evidence: ticketArchiveElementSplitter.spec.mjs includes event-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 issue issue-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 at 153bab0c7
  • 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 name suffixes 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 ## Timeline as 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.