@c4a/context-cli 0.5.38-beta.1 → 0.5.38-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -33106,18 +33106,76 @@ function renderSummaryText(value) {
33106
33106
  function escapeRawComment(value) {
33107
33107
  return value.replace(/-->/gu, "--\\>");
33108
33108
  }
33109
- function renderRawBlock(section) {
33109
+ function renderRawBlock(section, renderedContent) {
33110
33110
  const raw = section.raw?.trim();
33111
- if (raw === undefined || raw.length === 0 || raw === section.content.trim())
33111
+ if (raw === undefined || raw.length === 0 || raw === renderedContent.trim())
33112
33112
  return [];
33113
33113
  return ["", "<!-- c4a:raw", escapeRawComment(raw), "/c4a:raw -->"];
33114
33114
  }
33115
- function renderSectionBody(section) {
33115
+ function plainHeadingText(value) {
33116
+ return value.replace(/\[([^\]]+)\]\([^)]+\)/gu, "$1").replace(/`([^`]+)`/gu, "$1").replace(/\*\*([^*]+)\*\*/gu, "$1").replace(/\*([^*]+)\*/gu, "$1").replace(/_([^_]+)_/gu, "$1").replace(/<[^>]+>/gu, "").replace(/[ \t]+/gu, " ").trim().toLowerCase();
33117
+ }
33118
+ function collectHeadingLines(lines) {
33119
+ const headings = [];
33120
+ let fence;
33121
+ for (const [index2, line] of lines.entries()) {
33122
+ const fenceMatch = line.match(/^\s*(```|~~~)/u);
33123
+ if (fenceMatch) {
33124
+ const marker = fenceMatch[1];
33125
+ fence = fence === undefined ? marker : fence === marker ? undefined : fence;
33126
+ continue;
33127
+ }
33128
+ if (fence !== undefined)
33129
+ continue;
33130
+ const heading2 = line.match(/^(#{1,6})[ \t]+(.+?)(?:[ \t]+#+[ \t]*)?$/u);
33131
+ if (!heading2)
33132
+ continue;
33133
+ headings.push({
33134
+ index: index2,
33135
+ level: heading2[1]?.length ?? 1,
33136
+ text: heading2[2]?.trim() ?? ""
33137
+ });
33138
+ }
33139
+ return headings;
33140
+ }
33141
+ function normalizeSectionContentHeadings(input) {
33142
+ const trimmed = input.content.trim();
33143
+ if (trimmed.length === 0)
33144
+ return trimmed;
33145
+ const lines = trimmed.split(/\r?\n/u);
33146
+ const headings = collectHeadingLines(lines);
33147
+ const removableTitleSet = new Set(input.removableTitles.map(plainHeadingText).filter((title) => title.length > 0));
33148
+ const firstHeading = headings[0];
33149
+ if (firstHeading !== undefined && removableTitleSet.has(plainHeadingText(firstHeading.text)) && lines.slice(0, firstHeading.index).every((line) => line.trim().length === 0)) {
33150
+ lines.splice(firstHeading.index, 1);
33151
+ }
33152
+ const remainingHeadings = collectHeadingLines(lines);
33153
+ const minDepth = remainingHeadings.reduce((current, heading2) => current === undefined ? heading2.level : Math.min(current, heading2.level), undefined);
33154
+ const offset = minDepth === undefined ? 0 : Math.max(0, input.minHeadingLevel - minDepth);
33155
+ if (offset === 0)
33156
+ return lines.join(`
33157
+ `).trim();
33158
+ const headingByLine = new Map(remainingHeadings.map((heading2) => [heading2.index, heading2]));
33159
+ return lines.map((line, index2) => {
33160
+ const heading2 = headingByLine.get(index2);
33161
+ if (heading2 === undefined)
33162
+ return line;
33163
+ const level = Math.min(6, heading2.level + offset);
33164
+ return `${"#".repeat(level)} ${heading2.text}`;
33165
+ }).join(`
33166
+ `).trim();
33167
+ }
33168
+ function renderSectionBody(section, minHeadingLevel, groupTitle) {
33169
+ const content3 = normalizeSectionContentHeadings({
33170
+ content: section.content,
33171
+ minHeadingLevel,
33172
+ removableTitles: [section.summary ?? "", groupTitle]
33173
+ });
33116
33174
  return [
33117
33175
  renderSectionComment(section),
33118
33176
  ...section.summary !== undefined ? ["<!-- c4a:summary -->", renderSummaryText(section.summary), "<!-- /c4a:summary -->", ""] : [],
33119
- section.content.trim(),
33120
- ...renderRawBlock(section),
33177
+ content3,
33178
+ ...renderRawBlock(section, content3),
33121
33179
  "<!-- /c4a:section -->"
33122
33180
  ].join(`
33123
33181
  `);
@@ -33140,7 +33198,7 @@ function renderSectionGroups(sections, headingLevel) {
33140
33198
  const groupSections2 = grouped.get(title) ?? [];
33141
33199
  if (groupSections2.length === 0)
33142
33200
  continue;
33143
- const renderedSections = groupSections2.map(renderSectionBody);
33201
+ const renderedSections = groupSections2.map((section) => renderSectionBody(section, headingLevel + 1, title));
33144
33202
  parts.push([`${hashes} ${title}`, "", renderedSections.join(`
33145
33203
 
33146
33204
  `)].join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4a/context-cli",
3
- "version": "0.5.38-beta.1",
3
+ "version": "0.5.38-beta.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "context": "./cli.js"
@@ -441,6 +441,12 @@ function cloneNode(node, children) {
441
441
  cloned.children = children;
442
442
  return cloned;
443
443
  }
444
+ function cloneMarkdownNode(node) {
445
+ return {
446
+ ...node,
447
+ ...node.children !== undefined ? { children: node.children.map(cloneMarkdownNode) } : {}
448
+ };
449
+ }
444
450
  function mdxAttrValueText(value) {
445
451
  if (value === null || value === undefined)
446
452
  return;
@@ -622,6 +628,14 @@ function plainText(node) {
622
628
  return node.value;
623
629
  return (node.children ?? []).map(plainText).join("");
624
630
  }
631
+ function normalizedHeadingLabel(value) {
632
+ return compactWhitespace(value).toLowerCase();
633
+ }
634
+ function headingMatchesTitle(node, title) {
635
+ if (title === undefined || node.type !== "heading")
636
+ return false;
637
+ return normalizedHeadingLabel(plainText(node)) === normalizedHeadingLabel(title);
638
+ }
625
639
  function firstHeadingText(nodes) {
626
640
  for (const node of nodes) {
627
641
  if (node.type === "heading") {
@@ -651,6 +665,56 @@ function stripYamlFrontmatter(raw) {
651
665
  }
652
666
  return raw;
653
667
  }
668
+ function clampHeadingLevel(value) {
669
+ return Math.min(6, Math.max(1, Math.trunc(value)));
670
+ }
671
+ function minHeadingDepth(nodes) {
672
+ let out;
673
+ function visit(node) {
674
+ if (node.type === "heading" && typeof node.depth === "number") {
675
+ out = out === undefined ? node.depth : Math.min(out, node.depth);
676
+ }
677
+ for (const child of node.children ?? [])
678
+ visit(child);
679
+ }
680
+ for (const node of nodes)
681
+ visit(node);
682
+ return out;
683
+ }
684
+ function shiftHeadingDepths(nodes, offset) {
685
+ return nodes.map((node) => {
686
+ const next = cloneMarkdownNode(node);
687
+ if (next.type === "heading" && typeof next.depth === "number") {
688
+ next.depth = clampHeadingLevel(next.depth + offset);
689
+ }
690
+ if (next.children !== undefined) {
691
+ next.children = shiftHeadingDepths(next.children, offset);
692
+ }
693
+ return next;
694
+ });
695
+ }
696
+ function sectionBodyMarkdownFromTree(tree, options) {
697
+ const minHeadingLevel = clampHeadingLevel(options.minHeadingLevel ?? 3);
698
+ const removeTitleHeading = options.removeTitleHeading ?? true;
699
+ const children = (tree.children ?? []).map(cloneMarkdownNode);
700
+ const withoutTitle = removeTitleHeading && children[0] !== undefined && headingMatchesTitle(children[0], options.title) ? children.slice(1) : children;
701
+ const minDepth = minHeadingDepth(withoutTitle);
702
+ const headingOffset = minDepth === undefined ? 0 : Math.max(0, minHeadingLevel - minDepth);
703
+ return normalizeMarkdown(mdxProcessor.stringify({
704
+ ...tree,
705
+ children: shiftHeadingDepths(withoutTitle, headingOffset)
706
+ }));
707
+ }
708
+ function normalizeSectionMarkdown(markdown, options = {}) {
709
+ const source = stripYamlFrontmatter(markdown);
710
+ const tree = mdxProcessor.parse(source);
711
+ const normalized = mdxProcessor.runSync(tree);
712
+ const title = options.title ?? firstHeadingText(normalized.children ?? []);
713
+ return sectionBodyMarkdownFromTree(normalized, {
714
+ ...options,
715
+ ...title !== undefined ? { title } : {}
716
+ });
717
+ }
654
718
  function parseMdxDocument(raw, fallbackTitle) {
655
719
  const source = stripYamlFrontmatter(raw);
656
720
  const tree = mdxProcessor.parse(source);
@@ -658,7 +722,8 @@ function parseMdxDocument(raw, fallbackTitle) {
658
722
  const markdown = normalizeMarkdown(mdxProcessor.stringify(normalized));
659
723
  const blocks = markdownBlocks(markdown);
660
724
  const title = firstHeadingText(normalized.children ?? []) ?? fallbackTitle;
661
- return { title, markdown, blocks };
725
+ const bodyMarkdown = sectionBodyMarkdownFromTree(normalized, { title });
726
+ return { title, markdown, bodyMarkdown, blocks };
662
727
  }
663
728
 
664
729
  // src/aspect-runtime/index.ts
@@ -697,6 +762,7 @@ export {
697
762
  parseMdxDocument,
698
763
  parseJsLikeTokenNames,
699
764
  normalizeSymbolSlug,
765
+ normalizeSectionMarkdown,
700
766
  normalizeComponentSlug,
701
767
  markdownFence,
702
768
  inferComponentName,
@@ -199,7 +199,7 @@ export default defineAspect({
199
199
  node_slug: "target-node",
200
200
  kind: "description",
201
201
  summary: doc.title,
202
- content: doc.markdown,
202
+ content: doc.bodyMarkdown,
203
203
  source: file,
204
204
  artifact: artifactFromRelPath(file.path),
205
205
  });
@@ -208,6 +208,8 @@ export default defineAspect({
208
208
  });
209
209
  ```
210
210
 
211
+ Use `doc.bodyMarkdown` for Section content. It removes a leading document title when it matches `doc.title`, then shifts remaining headings under the rendered Section container. The renderer applies the final heading offset again from the actual node depth, so nested child-node Sections stay structurally valid. `doc.markdown` remains available when a plugin needs the full normalized document.
212
+
211
213
  ---
212
214
 
213
215
  ## Section Projection Protocol (`section-projection.v2`)