@handsupmin/gc-tree 0.7.0 → 0.7.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/dist/src/cli.js +9 -4
- package/dist/src/markdown.js +117 -2
- package/dist/src/onboard.js +6 -3
- package/dist/src/store.js +15 -9
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -139,6 +139,11 @@ async function launchGuidedFlow({ provider, providerMode, preferredLanguage, tar
|
|
|
139
139
|
return plan;
|
|
140
140
|
return maybeLaunchProvider(plan);
|
|
141
141
|
}
|
|
142
|
+
function printJsonUnlessLaunched(payload, launch) {
|
|
143
|
+
if (launch?.launched)
|
|
144
|
+
return;
|
|
145
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
146
|
+
}
|
|
142
147
|
async function main() {
|
|
143
148
|
if (hasFlag('--version') || hasFlag('-v')) {
|
|
144
149
|
stdout.write(`${await readPackageVersion()}\n`);
|
|
@@ -189,7 +194,7 @@ async function main() {
|
|
|
189
194
|
noLaunch: hasFlag('--no-launch'),
|
|
190
195
|
})
|
|
191
196
|
: null;
|
|
192
|
-
|
|
197
|
+
printJsonUnlessLaunched({
|
|
193
198
|
...result,
|
|
194
199
|
provider_mode: settings.provider_mode,
|
|
195
200
|
preferred_provider: settings.preferred_provider,
|
|
@@ -197,7 +202,7 @@ async function main() {
|
|
|
197
202
|
global_scaffold: globalScaffold,
|
|
198
203
|
scaffold,
|
|
199
204
|
launch,
|
|
200
|
-
},
|
|
205
|
+
}, launch);
|
|
201
206
|
return;
|
|
202
207
|
}
|
|
203
208
|
case 'checkout': {
|
|
@@ -289,7 +294,7 @@ async function main() {
|
|
|
289
294
|
command: 'gc-onboard',
|
|
290
295
|
noLaunch: hasFlag('--no-launch'),
|
|
291
296
|
});
|
|
292
|
-
|
|
297
|
+
printJsonUnlessLaunched({ mode: 'guided_onboarding', gc_branch: gcBranch, preferred_provider: provider, scaffold, launch }, launch);
|
|
293
298
|
return;
|
|
294
299
|
}
|
|
295
300
|
case 'verify-onboarding': {
|
|
@@ -486,7 +491,7 @@ async function main() {
|
|
|
486
491
|
command: 'gc-update-global-context',
|
|
487
492
|
noLaunch: hasFlag('--no-launch'),
|
|
488
493
|
});
|
|
489
|
-
|
|
494
|
+
printJsonUnlessLaunched({ mode: 'guided_update', gc_branch: gcBranch, preferred_provider: provider, scaffold, launch }, launch);
|
|
490
495
|
return;
|
|
491
496
|
}
|
|
492
497
|
case '__apply-update': {
|
package/dist/src/markdown.js
CHANGED
|
@@ -7,6 +7,63 @@ 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
|
+
}
|
|
33
|
+
function parseTitlePlacement(value) {
|
|
34
|
+
const trimmed = String(value || '').trim();
|
|
35
|
+
if (!trimmed)
|
|
36
|
+
return null;
|
|
37
|
+
if (/^index$/i.test(trimmed))
|
|
38
|
+
return { category: 'index', slug: 'index', label: 'Index' };
|
|
39
|
+
const match = trimmed.match(/^(role|repo|repos|repository|repositories|domain|workflow|workflows|convention|conventions|infra|verification)\s*:\s*(.+)$/i);
|
|
40
|
+
if (!match)
|
|
41
|
+
return null;
|
|
42
|
+
const rawCategory = match[1].toLowerCase();
|
|
43
|
+
const categoryMap = {
|
|
44
|
+
role: 'role',
|
|
45
|
+
repo: 'repos',
|
|
46
|
+
repos: 'repos',
|
|
47
|
+
repository: 'repos',
|
|
48
|
+
repositories: 'repos',
|
|
49
|
+
domain: 'domain',
|
|
50
|
+
workflow: 'workflows',
|
|
51
|
+
workflows: 'workflows',
|
|
52
|
+
convention: 'conventions',
|
|
53
|
+
conventions: 'conventions',
|
|
54
|
+
infra: 'infra',
|
|
55
|
+
verification: 'verification',
|
|
56
|
+
};
|
|
57
|
+
const label = match[2].trim();
|
|
58
|
+
const category = categoryMap[rawCategory];
|
|
59
|
+
if (!category || !label)
|
|
60
|
+
return null;
|
|
61
|
+
return {
|
|
62
|
+
category,
|
|
63
|
+
slug: slugify(label),
|
|
64
|
+
label,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
10
67
|
export function ensureSummary(summary) {
|
|
11
68
|
const trimmed = String(summary || '').trim();
|
|
12
69
|
if (!trimmed) {
|
|
@@ -17,6 +74,10 @@ export function ensureSummary(summary) {
|
|
|
17
74
|
export function renderDocMarkdown(doc) {
|
|
18
75
|
const summary = ensureSummary(doc.summary);
|
|
19
76
|
const body = String(doc.body || '').trim();
|
|
77
|
+
const normalizedIndexEntries = [...new Set([
|
|
78
|
+
...(doc.indexLabel?.trim() ? [doc.indexLabel.trim()] : []),
|
|
79
|
+
...(doc.indexEntries || []).map((entry) => String(entry || '').trim()).filter(Boolean),
|
|
80
|
+
])];
|
|
20
81
|
return [
|
|
21
82
|
`# ${doc.title.trim()}`,
|
|
22
83
|
'',
|
|
@@ -27,8 +88,8 @@ export function renderDocMarkdown(doc) {
|
|
|
27
88
|
...(doc.tags && doc.tags.length > 0
|
|
28
89
|
? ['## Tags', '', ...doc.tags.map((tag) => `- ${tag}`), '']
|
|
29
90
|
: []),
|
|
30
|
-
...(
|
|
31
|
-
? ['## Index Entries', '', ...
|
|
91
|
+
...(normalizedIndexEntries.length > 0
|
|
92
|
+
? ['## Index Entries', '', ...normalizedIndexEntries.map((entry) => `- ${entry}`), '']
|
|
32
93
|
: []),
|
|
33
94
|
'## Details',
|
|
34
95
|
'',
|
|
@@ -158,6 +219,60 @@ export function extractIndexEntries(markdown) {
|
|
|
158
219
|
.map((line) => line.slice(2).trim())
|
|
159
220
|
.filter(Boolean);
|
|
160
221
|
}
|
|
222
|
+
export function normalizeIndexEntry(value, fallback) {
|
|
223
|
+
const reference = parseDocReference(value);
|
|
224
|
+
if (reference) {
|
|
225
|
+
return {
|
|
226
|
+
category: reference.category,
|
|
227
|
+
label: reference.label,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const trimmed = String(value || '').trim();
|
|
231
|
+
if (!trimmed)
|
|
232
|
+
return null;
|
|
233
|
+
if (/^docs\/index\.md$/i.test(trimmed) || /^index\.md$/i.test(trimmed))
|
|
234
|
+
return null;
|
|
235
|
+
const titlePlacement = parseTitlePlacement(trimmed);
|
|
236
|
+
if (titlePlacement) {
|
|
237
|
+
if (titlePlacement.category === 'index')
|
|
238
|
+
return null;
|
|
239
|
+
return {
|
|
240
|
+
category: titlePlacement.category,
|
|
241
|
+
label: titlePlacement.label,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
category: fallback.category,
|
|
246
|
+
label: trimmed,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
export function inferDocPlacement(input) {
|
|
250
|
+
for (const candidate of [input.slug, input.indexLabel]) {
|
|
251
|
+
const reference = parseDocReference(candidate || '');
|
|
252
|
+
if (reference) {
|
|
253
|
+
return { category: reference.category, slug: reference.slug, label: reference.label, isIndexDoc: false };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const titlePlacement = parseTitlePlacement(input.title);
|
|
257
|
+
const explicitLabel = String(input.indexLabel || '').trim();
|
|
258
|
+
const preferredLabel = explicitLabel && !parseDocReference(explicitLabel) ? explicitLabel : input.title.trim();
|
|
259
|
+
if (titlePlacement) {
|
|
260
|
+
return {
|
|
261
|
+
category: titlePlacement.category === 'index' ? null : titlePlacement.category,
|
|
262
|
+
slug: titlePlacement.slug,
|
|
263
|
+
label: explicitLabel && !parseDocReference(explicitLabel) ? explicitLabel : titlePlacement.label,
|
|
264
|
+
isIndexDoc: titlePlacement.category === 'index',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const explicitCategory = input.category ? slugify(input.category) : null;
|
|
268
|
+
const slug = slugify(input.slug || input.indexLabel || input.title);
|
|
269
|
+
return {
|
|
270
|
+
category: explicitCategory,
|
|
271
|
+
slug,
|
|
272
|
+
label: preferredLabel,
|
|
273
|
+
isIndexDoc: slug === 'index' && explicitCategory === null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
161
276
|
export function extractSummary(markdown) {
|
|
162
277
|
const match = String(markdown || '').match(/## Summary\s+([\s\S]*?)(?:\n## |$)/);
|
|
163
278
|
return match?.[1]?.trim() || '';
|
package/dist/src/onboard.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import {
|
|
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
|
|
10
|
-
const
|
|
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, }) {
|
|
@@ -17,6 +17,9 @@ export async function onboardBranch({ home, input, branch, }) {
|
|
|
17
17
|
await mkdir(branchDocsDir(home, targetBranch), { recursive: true });
|
|
18
18
|
const written = [];
|
|
19
19
|
for (const doc of input.docs) {
|
|
20
|
+
const inferred = inferDocPlacement(doc);
|
|
21
|
+
if (inferred.isIndexDoc)
|
|
22
|
+
continue;
|
|
20
23
|
const fullPath = join(branchDocsDir(home, targetBranch), docRelativePath(doc));
|
|
21
24
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
22
25
|
await writeFile(fullPath, renderDocMarkdown(doc), 'utf8');
|
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 = [];
|
|
@@ -109,18 +109,24 @@ export async function writeIndexFromDocs(home, branch) {
|
|
|
109
109
|
const docsDir = branchDocsDir(home, branch);
|
|
110
110
|
await mkdir(docsDir, { recursive: true });
|
|
111
111
|
const files = await listDocRelativePaths(docsDir);
|
|
112
|
-
const docs = await Promise.all(files.map(async (file) => {
|
|
112
|
+
const docs = await Promise.all(files.filter((file) => !/(^|\/)index\.md$/i.test(file)).map(async (file) => {
|
|
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
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
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 });
|
|
119
|
+
const docCategory = inferred.category || fallbackCategory;
|
|
120
|
+
const docLabel = inferred.label || fallbackLabel;
|
|
121
|
+
const indexEntries = extractIndexEntries(raw)
|
|
122
|
+
.map((entry) => normalizeIndexEntry(entry, { category: docCategory, label: docLabel }))
|
|
123
|
+
.filter((entry) => Boolean(entry));
|
|
124
|
+
const entryLabels = indexEntries.length > 0 ? indexEntries : [{ category: docCategory, label: docLabel }];
|
|
125
|
+
const uniqueEntries = [...new Map(entryLabels.map((entry) => [`${entry.category}::${entry.label}`, entry])).values()];
|
|
126
|
+
return uniqueEntries.map((entry) => ({
|
|
121
127
|
title,
|
|
122
|
-
label:
|
|
123
|
-
category,
|
|
128
|
+
label: entry.label,
|
|
129
|
+
category: entry.category,
|
|
124
130
|
path: `docs/${file}`,
|
|
125
131
|
}));
|
|
126
132
|
}));
|