@handsupmin/gc-tree 0.7.0 → 0.7.1

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.
@@ -7,6 +7,29 @@ export function slugify(value) {
7
7
  .replace(/^-|-$/g, '');
8
8
  return normalized || 'doc';
9
9
  }
10
+ function parseDocReference(value) {
11
+ const trimmed = String(value || '').trim().replace(/^\.?\//, '');
12
+ if (!trimmed)
13
+ return null;
14
+ const normalized = trimmed.replace(/\\/g, '/');
15
+ const docsMatch = normalized.match(/^docs\/([^/]+)\/([^/]+?)(?:\.md)?$/i);
16
+ if (docsMatch) {
17
+ const category = normalizeCategory(docsMatch[1]);
18
+ const slug = slugify(docsMatch[2]);
19
+ if (slug === 'index')
20
+ return null;
21
+ return { category, slug, label: docsMatch[2].replace(/\.md$/i, '').trim() };
22
+ }
23
+ const directMatch = normalized.match(/^(role|repos|domain|workflows|conventions|infra|verification)\/([^/]+?)(?:\.md)?$/i);
24
+ if (directMatch) {
25
+ const category = normalizeCategory(directMatch[1]);
26
+ const slug = slugify(directMatch[2]);
27
+ if (slug === 'index')
28
+ return null;
29
+ return { category, slug, label: directMatch[2].replace(/\.md$/i, '').trim() };
30
+ }
31
+ return null;
32
+ }
10
33
  export function ensureSummary(summary) {
11
34
  const trimmed = String(summary || '').trim();
12
35
  if (!trimmed) {
@@ -158,6 +181,35 @@ export function extractIndexEntries(markdown) {
158
181
  .map((line) => line.slice(2).trim())
159
182
  .filter(Boolean);
160
183
  }
184
+ export function normalizeIndexEntry(value, fallback) {
185
+ const reference = parseDocReference(value);
186
+ if (reference) {
187
+ return {
188
+ category: reference.category,
189
+ label: reference.label,
190
+ };
191
+ }
192
+ const trimmed = String(value || '').trim();
193
+ if (!trimmed)
194
+ return null;
195
+ if (/^docs\/index\.md$/i.test(trimmed) || /^index\.md$/i.test(trimmed))
196
+ return null;
197
+ return {
198
+ category: fallback.category,
199
+ label: trimmed,
200
+ };
201
+ }
202
+ export function inferDocPlacement(input) {
203
+ for (const candidate of [input.slug, input.indexLabel]) {
204
+ const reference = parseDocReference(candidate || '');
205
+ if (reference) {
206
+ return { category: reference.category, slug: reference.slug };
207
+ }
208
+ }
209
+ const explicitCategory = input.category ? slugify(input.category) : null;
210
+ const slug = slugify(input.slug || input.indexLabel || input.title);
211
+ return { category: explicitCategory, slug };
212
+ }
161
213
  export function extractSummary(markdown) {
162
214
  const match = String(markdown || '').match(/## Summary\s+([\s\S]*?)(?:\n## |$)/);
163
215
  return match?.[1]?.trim() || '';
@@ -1,13 +1,13 @@
1
1
  import { mkdir, rm, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
- import { renderDocMarkdown, slugify } from './markdown.js';
3
+ import { inferDocPlacement, renderDocMarkdown } from './markdown.js';
4
4
  import { branchDocsDir, DEFAULT_BRANCH } from './paths.js';
5
5
  import { ensureBranchExists, updateBranchMeta, writeIndexFromDocs } from './store.js';
6
6
  function docRelativePath(doc) {
7
7
  if (doc.slug?.includes('/'))
8
8
  return `${doc.slug.replace(/\.md$/i, '')}.md`;
9
- const fileBase = slugify(doc.slug || doc.indexLabel || doc.title);
10
- const category = doc.category ? slugify(doc.category) : '';
9
+ const { category, slug } = inferDocPlacement(doc);
10
+ const fileBase = slug;
11
11
  return category ? `${category}/${fileBase}.md` : `${fileBase}.md`;
12
12
  }
13
13
  export async function onboardBranch({ home, input, branch, }) {
package/dist/src/store.js CHANGED
@@ -2,7 +2,7 @@ import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { branchDir, branchDocsDir, branchIndexPath, branchMetaPath, branchesRoot, DEFAULT_BRANCH, headPath, } from './paths.js';
5
- import { extractIndexEntries, renderIndexMarkdown } from './markdown.js';
5
+ import { extractIndexEntries, inferDocPlacement, normalizeIndexEntry, renderIndexMarkdown } from './markdown.js';
6
6
  async function listDocRelativePaths(dir, prefix = '') {
7
7
  const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
8
8
  const files = [];
@@ -113,14 +113,19 @@ export async function writeIndexFromDocs(home, branch) {
113
113
  const raw = await readFile(join(docsDir, file), 'utf8');
114
114
  const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() || file.replace(/\.md$/i, '').replace(/-/g, ' ');
115
115
  const parts = file.replace(/\.md$/i, '').split('/').filter(Boolean);
116
- const category = parts.length > 1 ? parts[0] : 'general';
117
- const label = parts.length > 1 ? parts[parts.length - 1] : title;
118
- const indexEntries = extractIndexEntries(raw);
119
- const entryLabels = indexEntries.length > 0 ? indexEntries : [label];
120
- return entryLabels.map((entryLabel) => ({
116
+ const fallbackCategory = parts.length > 1 ? parts[0] : 'general';
117
+ const fallbackLabel = parts.length > 1 ? parts[parts.length - 1] : title;
118
+ const inferred = inferDocPlacement({ title, indexLabel: title });
119
+ const docCategory = inferred.category || fallbackCategory;
120
+ const indexEntries = extractIndexEntries(raw)
121
+ .map((entry) => normalizeIndexEntry(entry, { category: docCategory, label: fallbackLabel }))
122
+ .filter((entry) => Boolean(entry));
123
+ const entryLabels = indexEntries.length > 0 ? indexEntries : [{ category: docCategory, label: fallbackLabel }];
124
+ const uniqueEntries = [...new Map(entryLabels.map((entry) => [`${entry.category}::${entry.label}`, entry])).values()];
125
+ return uniqueEntries.map((entry) => ({
121
126
  title,
122
- label: entryLabel,
123
- category,
127
+ label: entry.label,
128
+ category: entry.category,
124
129
  path: `docs/${file}`,
125
130
  }));
126
131
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handsupmin/gc-tree",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Global Context Tree, a lightweight branch-aware global context orchestrator for AI coding tools",
5
5
  "type": "module",
6
6
  "private": false,