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

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(`
@@ -51606,12 +51664,34 @@ function isMeaningfulTerm(term) {
51606
51664
  return compacted.length >= 4;
51607
51665
  }
51608
51666
  function uniqueTerms(node3) {
51609
- return [...new Set([node3.slug, node3.title, ...node3.aliases ?? []].filter((term) => typeof term === "string").map((term) => term.trim()).filter(isMeaningfulTerm))];
51667
+ const out2 = [];
51668
+ const seen = new Set;
51669
+ const push2 = (kind, term) => {
51670
+ if (typeof term !== "string")
51671
+ return;
51672
+ const value = term.trim();
51673
+ if (!isMeaningfulTerm(value))
51674
+ return;
51675
+ const key = `${kind}:${value}`;
51676
+ if (seen.has(key))
51677
+ return;
51678
+ seen.add(key);
51679
+ out2.push({ value, kind });
51680
+ };
51681
+ push2("slug", node3.slug);
51682
+ push2("title", node3.title);
51683
+ for (const alias of node3.aliases ?? [])
51684
+ push2("alias", alias);
51685
+ return out2;
51610
51686
  }
51611
51687
  function containsAsciiLikeTerm(text5, term) {
51612
51688
  const pattern = new RegExp(`(^|${ASCII_BOUNDARY})${escapeRegExp(term.toLowerCase())}($|${ASCII_BOUNDARY})`, "iu");
51613
51689
  return pattern.test(text5);
51614
51690
  }
51691
+ function containsAsciiLikeNameTerm(text5, term) {
51692
+ const pattern = new RegExp(`(^|${ASCII_NAME_BOUNDARY})${escapeRegExp(term.toLowerCase())}($|${ASCII_NAME_BOUNDARY})`, "iu");
51693
+ return pattern.test(text5);
51694
+ }
51615
51695
  function containsCjkTerm(text5, term) {
51616
51696
  const normalized = term.toLowerCase();
51617
51697
  let index2 = text5.indexOf(normalized);
@@ -51634,16 +51714,35 @@ function containsStrongTerm(text5, compactText, term) {
51634
51714
  const compacted = compact(normalized);
51635
51715
  return compacted.length >= 6 && compactText.includes(compacted);
51636
51716
  }
51717
+ function containsTerm(text5, compactText, term) {
51718
+ if (term.kind === "slug")
51719
+ return containsStrongTerm(text5, compactText, term.value);
51720
+ const normalized = term.value.normalize("NFKC").toLowerCase();
51721
+ if (CJK_RE2.test(normalized))
51722
+ return containsCjkTerm(text5, normalized);
51723
+ return containsAsciiLikeNameTerm(text5, normalized);
51724
+ }
51725
+ function currentCodePackage(input) {
51726
+ return input.currentCodePackage ?? input.knownNodes.find((node3) => node3.slug === input.currentNodeSlug)?.codePackage;
51727
+ }
51728
+ function canInferFromTerm(input) {
51729
+ if (input.term.kind === "slug")
51730
+ return true;
51731
+ if (input.currentCodePackage === undefined || input.targetCodePackage === undefined)
51732
+ return true;
51733
+ return input.currentCodePackage === input.targetCodePackage;
51734
+ }
51637
51735
  function inferRefersToNodes(input) {
51638
51736
  const existing = input.existing ?? [];
51639
51737
  const output = [...existing];
51640
51738
  const seen = new Set(output);
51641
51739
  const text5 = input.text.normalize("NFKC").toLowerCase();
51642
51740
  const compactText = compact(input.text);
51741
+ const currentPackage = currentCodePackage(input);
51643
51742
  for (const node3 of input.knownNodes) {
51644
51743
  if (node3.slug === input.currentNodeSlug || seen.has(node3.slug))
51645
51744
  continue;
51646
- if (uniqueTerms(node3).some((term) => containsStrongTerm(text5, compactText, term))) {
51745
+ if (uniqueTerms(node3).some((term) => canInferFromTerm({ term, currentCodePackage: currentPackage, targetCodePackage: node3.codePackage }) && containsTerm(text5, compactText, term))) {
51647
51746
  output.push(node3.slug);
51648
51747
  seen.add(node3.slug);
51649
51748
  }
@@ -51661,7 +51760,8 @@ async function collectKnownRefersToNodes(ctxDir) {
51661
51760
  bySlug.set(node3.parsed.node.id, {
51662
51761
  slug: node3.parsed.node.id,
51663
51762
  title: node3.parsed.node.title,
51664
- aliases: node3.parsed.node.aliases ?? []
51763
+ aliases: node3.parsed.node.aliases ?? [],
51764
+ ...node3.parsed.node.code_package !== undefined ? { codePackage: node3.parsed.node.code_package } : {}
51665
51765
  });
51666
51766
  }
51667
51767
  let activeSources = new Set;
@@ -51680,7 +51780,7 @@ async function collectKnownRefersToNodes(ctxDir) {
51680
51780
  }
51681
51781
  return [...bySlug.values()];
51682
51782
  }
51683
- var ASCII_BOUNDARY = "[^\\p{Letter}\\p{Number}_]", CJK_RE2;
51783
+ var ASCII_BOUNDARY = "[^\\p{Letter}\\p{Number}_]", ASCII_NAME_BOUNDARY = "[^\\p{Letter}\\p{Number}_/-]", CJK_RE2;
51684
51784
  var init_refersToInference = __esm(() => {
51685
51785
  init_alignPlan();
51686
51786
  init_sources();
@@ -58354,11 +58454,12 @@ function sectionText(section) {
58354
58454
  return [section.summary, section.content, section.detail].filter((value) => typeof value === "string").join(`
58355
58455
  `);
58356
58456
  }
58357
- async function withInferredRefersToNodes(ctxDir, nodeSlug, input) {
58457
+ async function withInferredRefersToNodes(ctxDir, nodeSlug, currentCodePackage2, input) {
58358
58458
  const inferred = inferRefersToNodes({
58359
58459
  text: [input.summary, input.content, input.detail].filter((value) => typeof value === "string").join(`
58360
58460
  `),
58361
58461
  currentNodeSlug: nodeSlug,
58462
+ currentCodePackage: currentCodePackage2,
58362
58463
  knownNodes: await collectKnownRefersToNodes(ctxDir),
58363
58464
  existing: input.refers_to_nodes
58364
58465
  });
@@ -58366,7 +58467,7 @@ async function withInferredRefersToNodes(ctxDir, nodeSlug, input) {
58366
58467
  }
58367
58468
  async function mdriveSectionAdd(input) {
58368
58469
  const located = await locateNode(input.ctxDir, input.nodeSlug);
58369
- const sectionInput = await withInferredRefersToNodes(input.ctxDir, input.nodeSlug, input.input);
58470
+ const sectionInput = await withInferredRefersToNodes(input.ctxDir, input.nodeSlug, located.parsed.node.code_package, input.input);
58370
58471
  const section = createSection(input.nodeSlug, sectionInput, nextSectionId(located.parsed.sections), located.parsed.node.type);
58371
58472
  reassignSections(located.parsed, [...located.parsed.sections, section]);
58372
58473
  await atomicRewriteRootFile(located.filePath, located.root);
@@ -58457,6 +58558,7 @@ async function mdriveSectionUpdate(input) {
58457
58558
  const inferred = inferRefersToNodes({
58458
58559
  text: sectionText(section),
58459
58560
  currentNodeSlug: input.nodeSlug,
58561
+ currentCodePackage: located.parsed.node.code_package,
58460
58562
  knownNodes: await collectKnownRefersToNodes(input.ctxDir),
58461
58563
  existing: section.refers_to_nodes
58462
58564
  });
@@ -58492,7 +58594,7 @@ async function mdriveSectionSupersede(input) {
58492
58594
  const oldSection = findSection(input.nodeSlug, located.parsed, input.oldSectionId);
58493
58595
  oldSection.status = SectionStatus.deprecated;
58494
58596
  const nextId = nextSectionId(located.parsed.sections);
58495
- const sectionInput = await withInferredRefersToNodes(input.ctxDir, input.nodeSlug, input.input);
58597
+ const sectionInput = await withInferredRefersToNodes(input.ctxDir, input.nodeSlug, located.parsed.node.code_package, input.input);
58496
58598
  const added = createSection(input.nodeSlug, sectionInput, nextId, located.parsed.node.type);
58497
58599
  added.relations = [
58498
58600
  ...added.relations ?? [],
@@ -98471,6 +98573,7 @@ function inferredRefersToNodes(input) {
98471
98573
  const inferred = inferRefersToNodes({
98472
98574
  text: input.text,
98473
98575
  currentNodeSlug: input.nodeSlug,
98576
+ currentCodePackage: input.currentCodePackage,
98474
98577
  knownNodes: input.knownNodes,
98475
98578
  existing: input.existing
98476
98579
  });
@@ -98491,6 +98594,7 @@ function createProjectionSection(input) {
98491
98594
  const inferred = inferredRefersToNodes({
98492
98595
  knownNodes: input.knownNodes,
98493
98596
  nodeSlug: input.row.node_slug,
98597
+ currentCodePackage: input.node.parsed.node.code_package,
98494
98598
  text: sectionText2(section)
98495
98599
  });
98496
98600
  if (inferred !== undefined)
@@ -98524,15 +98628,19 @@ function updateProjectionSection(input) {
98524
98628
  input.section.status = SectionStatus.active;
98525
98629
  changed = true;
98526
98630
  }
98527
- if (contentChanged) {
98528
- const inferred = inferredRefersToNodes({
98529
- knownNodes: input.knownNodes,
98530
- nodeSlug: input.row.node_slug,
98531
- text: sectionText2(input.section),
98532
- existing: input.section.refers_to_nodes
98533
- });
98534
- if (inferred !== undefined)
98631
+ const inferred = inferredRefersToNodes({
98632
+ knownNodes: input.knownNodes,
98633
+ nodeSlug: input.row.node_slug,
98634
+ currentCodePackage: input.node.parsed.node.code_package,
98635
+ text: sectionText2(input.section)
98636
+ });
98637
+ if (JSON.stringify(input.section.refers_to_nodes ?? undefined) !== JSON.stringify(inferred)) {
98638
+ if (inferred === undefined) {
98639
+ delete input.section.refers_to_nodes;
98640
+ } else {
98535
98641
  input.section.refers_to_nodes = inferred;
98642
+ }
98643
+ changed = true;
98536
98644
  }
98537
98645
  const issues = validateSection(input.section);
98538
98646
  if (issues.length > 0) {
@@ -98775,7 +98883,7 @@ function applyProjectableRows(input) {
98775
98883
  input.touchedNodes.add(node3);
98776
98884
  input.touchedFiles.add(node3.filePath);
98777
98885
  added += 1;
98778
- } else if (updateProjectionSection({ section: current.section, row, sourceRef, knownNodes: input.knownNodes })) {
98886
+ } else if (updateProjectionSection({ section: current.section, node: node3, row, sourceRef, knownNodes: input.knownNodes })) {
98779
98887
  input.touchedNodes.add(node3);
98780
98888
  input.touchedFiles.add(node3.filePath);
98781
98889
  updated += 1;
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.3",
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`)