@handsupmin/gc-tree 0.4.4 → 0.5.0
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/README.es.md +12 -0
- package/README.ja.md +12 -0
- package/README.ko.md +12 -0
- package/README.md +12 -0
- package/README.zh.md +12 -0
- package/ascii-logo.txt +9 -0
- package/ascii-tree.txt +44 -0
- package/dist/src/ascii.js +25 -0
- package/dist/src/cli.js +32 -1
- package/dist/src/markdown.js +14 -0
- package/dist/src/onboarding-protocol.js +3 -0
- package/dist/src/resolve.js +2 -1
- package/dist/src/store.js +11 -3
- package/package.json +3 -1
- package/skills/onboard/SKILL.md +3 -0
package/README.es.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
+
```text
|
|
6
|
+
__
|
|
7
|
+
/\ \__
|
|
8
|
+
__ ___\ \ ,_\ _ __ __ __
|
|
9
|
+
/'_ `\ /'___\ \ \/ /\`'__\/'__`\ /'__`\
|
|
10
|
+
/\ \L\ \/\ \__/\ \ \_\ \ \//\ __//\ __/
|
|
11
|
+
\ \____ \ \____\\ \__\\ \_\\ \____\ \____\
|
|
12
|
+
\/___L\ \/____/ \/__/ \/_/ \/____/\/____/
|
|
13
|
+
/\____/
|
|
14
|
+
\_/__/
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
<img src="./logo.png" alt="logo de gc-tree" width="260" />
|
|
6
18
|
|
|
7
19
|
### Contexto global, más allá del proyecto.
|
package/README.ja.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
+
```text
|
|
6
|
+
__
|
|
7
|
+
/\ \__
|
|
8
|
+
__ ___\ \ ,_\ _ __ __ __
|
|
9
|
+
/'_ `\ /'___\ \ \/ /\`'__\/'__`\ /'__`\
|
|
10
|
+
/\ \L\ \/\ \__/\ \ \_\ \ \//\ __//\ __/
|
|
11
|
+
\ \____ \ \____\\ \__\\ \_\\ \____\ \____\
|
|
12
|
+
\/___L\ \/____/ \/__/ \/_/ \/____/\/____/
|
|
13
|
+
/\____/
|
|
14
|
+
\_/__/
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
<img src="./logo.png" alt="gc-tree logo" width="260" />
|
|
6
18
|
|
|
7
19
|
### プロジェクトの外まで届くグローバルコンテキスト。
|
package/README.ko.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
+
```text
|
|
6
|
+
__
|
|
7
|
+
/\ \__
|
|
8
|
+
__ ___\ \ ,_\ _ __ __ __
|
|
9
|
+
/'_ `\ /'___\ \ \/ /\`'__\/'__`\ /'__`\
|
|
10
|
+
/\ \L\ \/\ \__/\ \ \_\ \ \//\ __//\ __/
|
|
11
|
+
\ \____ \ \____\\ \__\\ \_\\ \____\ \____\
|
|
12
|
+
\/___L\ \/____/ \/__/ \/_/ \/____/\/____/
|
|
13
|
+
/\____/
|
|
14
|
+
\_/__/
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
<img src="./logo.png" alt="gc-tree 로고" width="260" />
|
|
6
18
|
|
|
7
19
|
### 프로젝트 밖까지 이어지는 글로벌 컨텍스트.
|
package/README.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
+
```text
|
|
6
|
+
__
|
|
7
|
+
/\ \__
|
|
8
|
+
__ ___\ \ ,_\ _ __ __ __
|
|
9
|
+
/'_ `\ /'___\ \ \/ /\`'__\/'__`\ /'__`\
|
|
10
|
+
/\ \L\ \/\ \__/\ \ \_\ \ \//\ __//\ __/
|
|
11
|
+
\ \____ \ \____\\ \__\\ \_\\ \____\ \____\
|
|
12
|
+
\/___L\ \/____/ \/__/ \/_/ \/____/\/____/
|
|
13
|
+
/\____/
|
|
14
|
+
\_/__/
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
<img src="./logo.png" alt="gc-tree logo" width="260" />
|
|
6
18
|
|
|
7
19
|
### Global context, beyond the project.
|
package/README.zh.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
+
```text
|
|
6
|
+
__
|
|
7
|
+
/\ \__
|
|
8
|
+
__ ___\ \ ,_\ _ __ __ __
|
|
9
|
+
/'_ `\ /'___\ \ \/ /\`'__\/'__`\ /'__`\
|
|
10
|
+
/\ \L\ \/\ \__/\ \ \_\ \ \//\ __//\ __/
|
|
11
|
+
\ \____ \ \____\\ \__\\ \_\\ \____\ \____\
|
|
12
|
+
\/___L\ \/____/ \/__/ \/_/ \/____/\/____/
|
|
13
|
+
/\____/
|
|
14
|
+
\_/__/
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
<img src="./logo.png" alt="gc-tree logo" width="260" />
|
|
6
18
|
|
|
7
19
|
### 不止于项目内部的全局上下文。
|
package/ascii-logo.txt
ADDED
package/ascii-tree.txt
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
QzczXO
|
|
3
|
+
Yvczzvvz
|
|
4
|
+
Juvw cvc
|
|
5
|
+
JzXY YvvXXuvc zXXY
|
|
6
|
+
CvvccvcU zuvX zvvcvvcY
|
|
7
|
+
cvU QvcO \uu: vX: JvX
|
|
8
|
+
Yvv0:zvc \uu: vvY ZcvC
|
|
9
|
+
XvvvvcU UcvvvX YX \uu: 0vc cvvccL XvvvvcY
|
|
10
|
+
Uvc: Ycvv vvv bcu \uv: Ovc vvU Qvc cvC
|
|
11
|
+
UvcO Ocvv cvvX Ycu \uuO Zvc XvvJ Zvca OvvC
|
|
12
|
+
UvvvvvQ vuu LcvvuUcu \uuO ZvczvvvcO cuY QvvvvvJ
|
|
13
|
+
\zcccX Quv cvv Ycvvu \uuO XvvvcX zvY XvU zzzzX
|
|
14
|
+
zvucucvcL Quv cvv Ycu \uuzuvvz zvU XvU cvvcucvcY
|
|
15
|
+
cvU zvc JvvU zvvvQ acu \uvvvY JvvvY CvvU JvY cvX
|
|
16
|
+
UvvzczvvJ 0cvvcXOYvvvzJ cvvYX\uvc Yzvvvz YcvvvC zvvzczvcJ
|
|
17
|
+
LvvvU XcvvzXXcvvcJ Uvvvvvvc JcvvvXXzvvcXO zvvuO
|
|
18
|
+
Cvc OXzzzzzXuvvz zUvvvc OXvvuXXzzzzXO OccO
|
|
19
|
+
Yvvvvvz zvvuU zvvvvvc vvvd nvvc OvvvY wvvvvvY OUvvvX 0vvvvvY
|
|
20
|
+
cvX QvvzXYUcvvvvJ CvvY Mvva uvvc ZvvO cvz: OzvvvvXUUXXvza Ycvz
|
|
21
|
+
cvY acvuuuuuuuvvC UvvcX Mcvh uvvc mvva OXvvz Xvuuuuuuuuvz Xvz
|
|
22
|
+
CcvvvvvY zzvvvvvvvvvcL zvvk nvvc Ovva OvvvcYvvvvvcO Uvvcvvz\
|
|
23
|
+
zzuvvXz Yuvvcv avvZ nvvv mvvY zzvvuU Jzuvvzz
|
|
24
|
+
UvvYYUvv0obhhhhoh*hoXvvvzUvv0 nvvc ZvvJXvvvzhao##h##hhhQvvUJUcvU
|
|
25
|
+
Uvz Yvvvvvvvvvvvvvvvvvvvvv0 uvvv muvvvvvvvvvvvvvvvvvvvvY cvY
|
|
26
|
+
JcvcCcvcb zXvvvvvvvvvvvvvvJO bvvcLcvcC
|
|
27
|
+
CzvzL YuvvvvvvvvvQ CXvYL
|
|
28
|
+
Jcvvvvvm
|
|
29
|
+
CvvvvvvZ
|
|
30
|
+
zvvvvvvY
|
|
31
|
+
cvvvvvvJ
|
|
32
|
+
nuvvvvuc
|
|
33
|
+
uuvvvvuc
|
|
34
|
+
vvvvvvuu
|
|
35
|
+
Jvvvvvvu
|
|
36
|
+
zvvvvvvv
|
|
37
|
+
JvvvvvvU
|
|
38
|
+
vvvvvvvU
|
|
39
|
+
Ovvvvvvvc
|
|
40
|
+
JuvvvvvvuL
|
|
41
|
+
zvvvvvvvvvvY
|
|
42
|
+
OXzvvvvzvvvzcvvvvzXO
|
|
43
|
+
YYcvvvvvvXCzvvvvX:XvvvvvvcXQ
|
|
44
|
+
YYYYYYYYUXO QUUUYUUYC: OcYUYUYYYYz
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
async function readAsciiAsset(fileName) {
|
|
5
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const candidates = [
|
|
7
|
+
join(here, '..', fileName),
|
|
8
|
+
join(here, '..', '..', fileName),
|
|
9
|
+
];
|
|
10
|
+
for (const candidate of candidates) {
|
|
11
|
+
try {
|
|
12
|
+
return await readFile(candidate, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`ascii asset not found: ${fileName}`);
|
|
19
|
+
}
|
|
20
|
+
export async function readAsciiLogo() {
|
|
21
|
+
return readAsciiAsset('ascii-logo.txt');
|
|
22
|
+
}
|
|
23
|
+
export async function readAsciiTree() {
|
|
24
|
+
return readAsciiAsset('ascii-tree.txt');
|
|
25
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { stdin } from 'node:process';
|
|
3
|
+
import { stdin, stderr, stdout } from 'node:process';
|
|
4
|
+
import { readAsciiLogo, readAsciiTree } from './ascii.js';
|
|
4
5
|
import { dispatchGcTreeHook } from './hook.js';
|
|
5
6
|
import { onboardBranch } from './onboard.js';
|
|
6
7
|
import { buildProviderLaunchPlan, maybeLaunchProvider, promptLanguageSelection, promptLaunchProviderSelection, promptProviderSelection, } from './provider.js';
|
|
@@ -20,8 +21,23 @@ function readArg(flag) {
|
|
|
20
21
|
function hasFlag(flag) {
|
|
21
22
|
return process.argv.includes(flag);
|
|
22
23
|
}
|
|
24
|
+
async function readPackageVersion() {
|
|
25
|
+
const candidates = ['../package.json', '../../package.json'];
|
|
26
|
+
for (const relative of candidates) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await readFile(new URL(relative, import.meta.url), 'utf8');
|
|
29
|
+
return JSON.parse(raw).version || '0.0.0';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return '0.0.0';
|
|
36
|
+
}
|
|
23
37
|
function usage() {
|
|
24
38
|
console.error(`Usage:
|
|
39
|
+
gctree --version
|
|
40
|
+
gctree plant
|
|
25
41
|
gctree init [--home DIR] [--provider <claude-code|codex|both>] [--language TEXT] [--target DIR] [--no-launch]
|
|
26
42
|
gctree checkout <branch> [--home DIR]
|
|
27
43
|
gctree checkout -b <branch> [--home DIR]
|
|
@@ -123,13 +139,28 @@ async function launchGuidedFlow({ provider, providerMode, preferredLanguage, tar
|
|
|
123
139
|
return maybeLaunchProvider(plan);
|
|
124
140
|
}
|
|
125
141
|
async function main() {
|
|
142
|
+
if (hasFlag('--version') || hasFlag('-v')) {
|
|
143
|
+
stdout.write(`${await readPackageVersion()}\n`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
126
146
|
const rawCommand = process.argv[2];
|
|
127
147
|
if (!rawCommand || rawCommand === '--help' || rawCommand === 'help')
|
|
128
148
|
usage();
|
|
129
149
|
const command = rawCommand === 'update-gc' || rawCommand === 'ugc' ? 'update-global-context' : rawCommand;
|
|
130
150
|
const home = resolveHome(readArg('--home'));
|
|
131
151
|
switch (command) {
|
|
152
|
+
case 'plant': {
|
|
153
|
+
const asciiTree = await readAsciiTree();
|
|
154
|
+
stdout.write(asciiTree);
|
|
155
|
+
if (!asciiTree.endsWith('\n'))
|
|
156
|
+
stdout.write('\n');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
132
159
|
case 'init': {
|
|
160
|
+
const asciiLogo = await readAsciiLogo();
|
|
161
|
+
stderr.write(asciiLogo);
|
|
162
|
+
if (!asciiLogo.endsWith('\n'))
|
|
163
|
+
stderr.write('\n');
|
|
133
164
|
const providerMode = await maybePromptProviderMode(readArg('--provider'));
|
|
134
165
|
const provider = await resolveLaunchProvider(providerMode);
|
|
135
166
|
const preferredLanguage = await maybePromptLanguage(readArg('--language'));
|
package/dist/src/markdown.js
CHANGED
|
@@ -27,6 +27,9 @@ export function renderDocMarkdown(doc) {
|
|
|
27
27
|
...(doc.tags && doc.tags.length > 0
|
|
28
28
|
? ['## Tags', '', ...doc.tags.map((tag) => `- ${tag}`), '']
|
|
29
29
|
: []),
|
|
30
|
+
...(doc.indexEntries && doc.indexEntries.length > 0
|
|
31
|
+
? ['## Index Entries', '', ...doc.indexEntries.map((entry) => `- ${entry}`), '']
|
|
32
|
+
: []),
|
|
30
33
|
'## Details',
|
|
31
34
|
'',
|
|
32
35
|
body || '(no details yet)',
|
|
@@ -124,6 +127,17 @@ export function parseIndexEntries(indexContent) {
|
|
|
124
127
|
return [{ id: docIdFromPath(path), title: label, label, category, path }];
|
|
125
128
|
});
|
|
126
129
|
}
|
|
130
|
+
export function extractIndexEntries(markdown) {
|
|
131
|
+
const match = String(markdown || '').match(/## Index Entries\s+([\s\S]*?)(?:\n## |$)/);
|
|
132
|
+
if (!match?.[1])
|
|
133
|
+
return [];
|
|
134
|
+
return match[1]
|
|
135
|
+
.split(/\r?\n/)
|
|
136
|
+
.map((line) => line.trim())
|
|
137
|
+
.filter((line) => line.startsWith('- '))
|
|
138
|
+
.map((line) => line.slice(2).trim())
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
}
|
|
127
141
|
export function extractSummary(markdown) {
|
|
128
142
|
const match = String(markdown || '').match(/## Summary\s+([\s\S]*?)(?:\n## |$)/);
|
|
129
143
|
return match?.[1]?.trim() || '';
|
|
@@ -26,6 +26,9 @@ export function onboardingProtocolLines() {
|
|
|
26
26
|
'Synthesize the interview into an encyclopedia-style context set with many small docs instead of a few broad docs.',
|
|
27
27
|
'Prefer category directories such as `docs/role/`, `docs/repos/`, `docs/domain/`, `docs/workflows/`, `docs/conventions/`, and `docs/infra/` whenever that split fits the material.',
|
|
28
28
|
'Prefer one concept, one repository, one workflow, or one convention per file when possible.',
|
|
29
|
+
'Treat `index.md` as concept-first: show the keywords a user or AI would search for, not just broad document titles.',
|
|
30
|
+
'Generate index entries automatically from primary concept names, aliases, repository nicknames, and workflow labels when those are clear.',
|
|
31
|
+
'Split glossary docs when a concept is likely to be searched directly, needs more than a short definition, or carries workflow/constraint details; keep only low-value leftover terms in a shared glossary.',
|
|
29
32
|
'Keep each doc summary-first so the top section gives the gist before deeper details.',
|
|
30
33
|
'Treat `index.md` as a human-readable dictionary-style table of contents grouped by category headings and `label -> path` entries.',
|
|
31
34
|
];
|
package/dist/src/resolve.js
CHANGED
|
@@ -44,7 +44,8 @@ function scoreText(text, query) {
|
|
|
44
44
|
async function readBranchDocs(home, branch) {
|
|
45
45
|
const indexRaw = await readFile(branchIndexPath(home, branch), 'utf8');
|
|
46
46
|
const entries = parseIndexEntries(indexRaw);
|
|
47
|
-
|
|
47
|
+
const uniqueEntries = [...new Map(entries.map((entry) => [entry.path, entry])).values()];
|
|
48
|
+
return Promise.all(uniqueEntries.map(async (entry) => {
|
|
48
49
|
const fullPath = join(branchDir(home, branch), entry.path);
|
|
49
50
|
const raw = await readFile(fullPath, 'utf8');
|
|
50
51
|
return {
|
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, INDEX_WARNING_CHARS, } from './paths.js';
|
|
5
|
-
import { renderIndexMarkdown } from './markdown.js';
|
|
5
|
+
import { extractIndexEntries, 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 = [];
|
|
@@ -115,10 +115,18 @@ export async function writeIndexFromDocs(home, branch) {
|
|
|
115
115
|
const parts = file.replace(/\.md$/i, '').split('/').filter(Boolean);
|
|
116
116
|
const category = parts.length > 1 ? parts[0] : 'general';
|
|
117
117
|
const label = parts.length > 1 ? parts[parts.length - 1] : title;
|
|
118
|
-
|
|
118
|
+
const indexEntries = extractIndexEntries(raw);
|
|
119
|
+
const entryLabels = indexEntries.length > 0 ? indexEntries : [label];
|
|
120
|
+
return entryLabels.map((entryLabel) => ({
|
|
121
|
+
title,
|
|
122
|
+
label: entryLabel,
|
|
123
|
+
category,
|
|
124
|
+
path: `docs/${file}`,
|
|
125
|
+
}));
|
|
119
126
|
}));
|
|
127
|
+
const flatDocs = docs.flat().sort((a, b) => a.label.localeCompare(b.label));
|
|
120
128
|
const meta = await readBranchMeta(home, branch);
|
|
121
|
-
const index = renderIndexMarkdown({ branch, branchSummary: meta.summary, docs });
|
|
129
|
+
const index = renderIndexMarkdown({ branch, branchSummary: meta.summary, docs: flatDocs });
|
|
122
130
|
await writeFile(branchIndexPath(home, branch), index, 'utf8');
|
|
123
131
|
return { index_path: branchIndexPath(home, branch), doc_count: docs.length };
|
|
124
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handsupmin/gc-tree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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,
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"skills",
|
|
10
10
|
"scaffolds",
|
|
11
11
|
"docs",
|
|
12
|
+
"ascii-logo.txt",
|
|
13
|
+
"ascii-tree.txt",
|
|
12
14
|
"README.md",
|
|
13
15
|
"LICENSE"
|
|
14
16
|
],
|
package/skills/onboard/SKILL.md
CHANGED
|
@@ -35,6 +35,9 @@ Use this when a user wants to create global context for a product, company, or w
|
|
|
35
35
|
- prefer an encyclopedia-style context set with many small docs instead of a few broad docs
|
|
36
36
|
- prefer category directories like `docs/role/`, `docs/repos/`, `docs/domain/`, `docs/workflows/`, `docs/conventions/`, and `docs/infra/`
|
|
37
37
|
- prefer one concept, one repository, one workflow, or one convention per file when possible
|
|
38
|
+
- treat `index.md` as concept-first: surface the keywords a user or AI would search for, not just broad document titles
|
|
39
|
+
- generate index entries automatically from primary concept names, aliases, repository nicknames, and workflow labels when those are clear
|
|
40
|
+
- split glossary docs when a concept is likely to be searched directly, needs more than a short definition, or carries workflow/constraint details; keep only low-value leftover terms in a shared glossary
|
|
38
41
|
- keep `index.md` as a human-readable dictionary-style TOC grouped by category headings and `label -> path` entries
|
|
39
42
|
- use onboarding only for an empty gc-branch
|
|
40
43
|
|