@tienne/gestalt 0.120.1 → 0.121.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.ko.md +22 -3
- package/README.md +33 -3
- package/dist/package.json +4 -3
- package/dist/plugin/.codex-plugin/plugin.json +40 -0
- package/dist/plugin/review-agents/writing-reviewer/AGENT.md +4 -2
- package/dist/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -1
- package/dist/plugin/role-agents/humanize-monolith/AGENT.md +5 -2
- package/dist/plugin/scripts/acquisition.mjs +96 -0
- package/dist/plugin/scripts/gestalt-cli.mjs +144 -0
- package/dist/plugin/scripts/npm-environment.mjs +64 -0
- package/dist/plugin/scripts/package-owner.mjs +20 -0
- package/dist/plugin/scripts/resolve-cli.mjs +22 -0
- package/dist/plugin/skills/_shared/agent-delegation.md +2 -0
- package/dist/plugin/skills/_shared/agent-model.md +2 -0
- package/dist/plugin/skills/_shared/cli-launcher.md +47 -36
- package/dist/plugin/skills/_shared/host-runtime.md +47 -0
- package/dist/plugin/skills/agent/SKILL.md +2 -0
- package/dist/plugin/skills/architecture/SKILL.md +2 -0
- package/dist/plugin/skills/blast-radius/SKILL.md +2 -0
- package/dist/plugin/skills/brief/SKILL.md +2 -0
- package/dist/plugin/skills/build-graph/SKILL.md +2 -0
- package/dist/plugin/skills/diff-radius/SKILL.md +2 -0
- package/dist/plugin/skills/dispatch/SKILL.md +2 -0
- package/dist/plugin/skills/doubt/SKILL.md +2 -0
- package/dist/plugin/skills/execute/SKILL.md +3 -1
- package/dist/plugin/skills/explain/SKILL.md +2 -0
- package/dist/plugin/skills/handoff/SKILL.md +2 -0
- package/dist/plugin/skills/interview/SKILL.md +2 -0
- package/dist/plugin/skills/jira-create/SKILL.md +2 -0
- package/dist/plugin/skills/lens/SKILL.md +2 -0
- package/dist/plugin/skills/local-pr/SKILL.md +4 -2
- package/dist/plugin/skills/market-scan/SKILL.md +2 -0
- package/dist/plugin/skills/pr/SKILL.md +6 -14
- package/dist/plugin/skills/presentation/SKILL.md +2 -0
- package/dist/plugin/skills/research/SKILL.md +2 -0
- package/dist/plugin/skills/reverse-plan/SKILL.md +2 -0
- package/dist/plugin/skills/review/SKILL.md +15 -18
- package/dist/plugin/skills/review-loop/SKILL.md +13 -10
- package/dist/plugin/skills/review-reply/SKILL.md +7 -15
- package/dist/plugin/skills/screen-ref/SKILL.md +2 -0
- package/dist/plugin/skills/setup/SKILL.md +9 -6
- package/dist/plugin/skills/share/SKILL.md +4 -2
- package/dist/plugin/skills/ship/SKILL.md +3 -1
- package/dist/plugin/skills/slack-send/SKILL.md +2 -0
- package/dist/plugin/skills/solve/SKILL.md +2 -0
- package/dist/plugin/skills/spec/SKILL.md +3 -1
- package/dist/plugin/skills/ux-research/SKILL.md +2 -0
- package/dist/plugin/skills/video-digest/SKILL.md +2 -0
- package/dist/schemas/gestalt.schema.json +25 -4
- package/dist/src/cli/commands/init.d.ts +2 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +6 -2
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/setup.d.ts +4 -1
- package/dist/src/cli/commands/setup.d.ts.map +1 -1
- package/dist/src/cli/commands/setup.js +2 -1
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +15 -3
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/config.d.ts +169 -150
- package/dist/src/core/config.d.ts.map +1 -1
- package/dist/src/core/config.js +41 -11
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/model-diagnostics.d.ts +9 -0
- package/dist/src/core/model-diagnostics.d.ts.map +1 -0
- package/dist/src/core/model-diagnostics.js +47 -0
- package/dist/src/core/model-diagnostics.js.map +1 -0
- package/dist/src/core/types.d.ts +3 -3
- package/dist/src/execute/prompts.d.ts.map +1 -1
- package/dist/src/execute/prompts.js +4 -1
- package/dist/src/execute/prompts.js.map +1 -1
- package/dist/src/mcp/client-host.d.ts +12 -0
- package/dist/src/mcp/client-host.d.ts.map +1 -0
- package/dist/src/mcp/client-host.js +41 -0
- package/dist/src/mcp/client-host.js.map +1 -0
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +20 -6
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/status.d.ts +22 -7
- package/dist/src/mcp/tools/status.d.ts.map +1 -1
- package/dist/src/mcp/tools/status.js +8 -3
- package/dist/src/mcp/tools/status.js.map +1 -1
- package/package.json +4 -3
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/review-agents/writing-reviewer/AGENT.md +4 -2
- package/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -1
- package/plugin/role-agents/humanize-monolith/AGENT.md +5 -2
- package/plugin/scripts/acquisition.mjs +96 -0
- package/plugin/scripts/gestalt-cli.mjs +144 -0
- package/plugin/scripts/npm-environment.mjs +64 -0
- package/plugin/scripts/package-owner.mjs +20 -0
- package/plugin/scripts/resolve-cli.mjs +22 -0
- package/plugin/skills/_shared/agent-delegation.md +2 -0
- package/plugin/skills/_shared/agent-model.md +2 -0
- package/plugin/skills/_shared/cli-launcher.md +47 -36
- package/plugin/skills/_shared/host-runtime.md +47 -0
- package/plugin/skills/agent/SKILL.md +2 -0
- package/plugin/skills/architecture/SKILL.md +2 -0
- package/plugin/skills/blast-radius/SKILL.md +2 -0
- package/plugin/skills/brief/SKILL.md +2 -0
- package/plugin/skills/build-graph/SKILL.md +2 -0
- package/plugin/skills/diff-radius/SKILL.md +2 -0
- package/plugin/skills/dispatch/SKILL.md +2 -0
- package/plugin/skills/doubt/SKILL.md +2 -0
- package/plugin/skills/execute/SKILL.md +3 -1
- package/plugin/skills/explain/SKILL.md +2 -0
- package/plugin/skills/handoff/SKILL.md +2 -0
- package/plugin/skills/interview/SKILL.md +2 -0
- package/plugin/skills/jira-create/SKILL.md +2 -0
- package/plugin/skills/lens/SKILL.md +2 -0
- package/plugin/skills/local-pr/SKILL.md +4 -2
- package/plugin/skills/market-scan/SKILL.md +2 -0
- package/plugin/skills/pr/SKILL.md +6 -14
- package/plugin/skills/presentation/SKILL.md +2 -0
- package/plugin/skills/research/SKILL.md +2 -0
- package/plugin/skills/reverse-plan/SKILL.md +2 -0
- package/plugin/skills/review/SKILL.md +15 -18
- package/plugin/skills/review-loop/SKILL.md +13 -10
- package/plugin/skills/review-reply/SKILL.md +7 -15
- package/plugin/skills/screen-ref/SKILL.md +2 -0
- package/plugin/skills/setup/SKILL.md +9 -6
- package/plugin/skills/share/SKILL.md +4 -2
- package/plugin/skills/ship/SKILL.md +3 -1
- package/plugin/skills/slack-send/SKILL.md +2 -0
- package/plugin/skills/solve/SKILL.md +2 -0
- package/plugin/skills/spec/SKILL.md +3 -1
- package/plugin/skills/ux-research/SKILL.md +2 -0
- package/plugin/skills/video-digest/SKILL.md +2 -0
- package/schemas/gestalt.schema.json +25 -4
package/plugin/mcp.json
CHANGED
|
@@ -53,17 +53,19 @@ git diff --word-diff=porcelain -- <파일>
|
|
|
53
53
|
|
|
54
54
|
한 조각이 룰에 걸리는지 보려면 그 조각이 들어간 **문장 전체**를 읽어야 합니다. 조각만 떼어 보면 문장 구조로 걸리는 룰(A-18 좌향 수식, C-11 연결어미 뒤 쉼표, E-2 종결어미 반복)을 판정할 수 없습니다. 판정은 문장으로 하되 이슈로 올릴지는 그 룰에 걸린 부분이 바뀐 조각 안에 있는지로 정합니다.
|
|
55
55
|
|
|
56
|
+
아래 런처 경로는 이 문서 디렉토리 기준입니다. 실행 전에 절대 경로로 풉니다. 작업 레포의 동명 파일은 사용하지 않습니다.
|
|
57
|
+
|
|
56
58
|
**정적 탐지를 먼저 돌립니다.** 정규식으로 잡히는 룰은 사람이 눈으로 세는 것보다 정확합니다.
|
|
57
59
|
|
|
58
60
|
```bash
|
|
59
|
-
gestalt humanize-check --before <파일> --after <파일> --register doc --json
|
|
61
|
+
node "../../scripts/gestalt-cli.mjs" humanize-check --before <파일> --after <파일> --register doc --json
|
|
60
62
|
```
|
|
61
63
|
|
|
62
64
|
같은 파일을 `--before`와 `--after`에 둘 다 넘기면 윤문 전후 비교가 아니라 그 파일에 남아 있는 S1 패턴을 세어 줍니다. 잡히는 룰은 21개뿐이니 나머지는 읽고 판정합니다.
|
|
63
65
|
|
|
64
66
|
`--register`는 대상 문서의 성격에 맞춰 고릅니다. 문서는 `doc`, 리뷰 코멘트나 대화체는 `chat`, 보고서는 `report`입니다. `chat`에서는 몇몇 S2가 S1으로 올라갑니다. 사람은 대화에서 개념을 명사 덩어리로 뭉치지 않기 때문입니다.
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
런처를 못 찾거나 버전 해석이 실패하면 그 단계를 건너뛰고 직접 읽어 판정합니다. 건너뛴 사실을 보고합니다.
|
|
67
69
|
|
|
68
70
|
## 어휘 선택을 볼 때
|
|
69
71
|
|
|
@@ -214,9 +214,10 @@
|
|
|
214
214
|
## 코드 검사 (자가 채점보다 위)
|
|
215
215
|
|
|
216
216
|
아래 등급은 모델이 스스로 매기는 참고값이다. 실제 판단은 코드가 한다.
|
|
217
|
+
런처 경로는 이 문서 디렉토리 기준이다. 실행 전에 절대 경로로 풀고 작업 레포의 동명 파일을 사용하지 않는다.
|
|
217
218
|
|
|
218
219
|
```bash
|
|
219
|
-
gestalt humanize-check --before before.md --after after.md --register chat
|
|
220
|
+
node "../../../scripts/gestalt-cli.mjs" humanize-check --before before.md --after after.md --register chat
|
|
220
221
|
```
|
|
221
222
|
|
|
222
223
|
변경률, S1 잔존, 보호 토큰 생존, 구조 보존 4가지 측면에서 각각 측정하고 exit code로 답한다
|
|
@@ -74,11 +74,14 @@ S2
|
|
|
74
74
|
|
|
75
75
|
### 0단계 — 스캔 (룰을 좁힌다)
|
|
76
76
|
|
|
77
|
+
아래 런처 경로는 이 문서 디렉토리 기준이다. 실행 전에 절대 경로로 풀고 작업 레포의 동명 파일을 사용하지 않는다.
|
|
78
|
+
런처를 못 찾거나 버전 해석이 실패하면 스캔과 코드 검사를 건너뛰고 직접 읽어 판정한다. 건너뛴 사실을 보고한다.
|
|
79
|
+
|
|
77
80
|
원문이 파일로 있으면 먼저 스캔을 돌린다. 말투는 산문이 `doc`, 리뷰 코멘트와 슬랙이 `chat`,
|
|
78
81
|
보고서가 `report`다.
|
|
79
82
|
|
|
80
83
|
```bash
|
|
81
|
-
gestalt humanize-scan --file <원문> --register <doc|chat|report>
|
|
84
|
+
node "../../scripts/gestalt-cli.mjs" humanize-scan --file <원문> --register <doc|chat|report>
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
종료 코드로도 답한다. 걸린 게 있으면 0, 어투는 깨끗한데 맞춤법만 걸리면 11, 탐지기가 가리는 범위에서 0건이면 10이다. 11이면 어투는 그대로 두고 맞춤법 절만 고쳐서 낸다.
|
|
@@ -202,7 +205,7 @@ summary에 적는다. 댈 게 없으면 그건 안 고친 것이라 원문을
|
|
|
202
205
|
위 3단계 자가검증은 스스로 매기는 참고값이다. 원문과 윤문본이 파일로 있으면 판단은 코드에 맡긴다.
|
|
203
206
|
|
|
204
207
|
```bash
|
|
205
|
-
gestalt humanize-check --before <원문> --after <윤문본> --register chat --attempt 1
|
|
208
|
+
node "../../scripts/gestalt-cli.mjs" humanize-check --before <원문> --after <윤문본> --register chat --attempt 1
|
|
206
209
|
```
|
|
207
210
|
|
|
208
211
|
변경률, S1 제거율, 보호 토큰 생존, 구조 보존, 신규 유입을 각각 측정하고 exit code로 답한다
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { Buffer } from 'node:buffer';
|
|
4
|
+
import { setTimeout, clearTimeout } from 'node:timers';
|
|
5
|
+
|
|
6
|
+
// One cancellation scope spans npm resolution, version verification and cwd cleanup.
|
|
7
|
+
// A child's own signal/timeout is a failed attempt, never a user's cancellation.
|
|
8
|
+
export function createAcquisition({ cwd, env }) {
|
|
9
|
+
let current;
|
|
10
|
+
let signal;
|
|
11
|
+
const cancel = (received) => {
|
|
12
|
+
signal = received;
|
|
13
|
+
current?.cancel(received);
|
|
14
|
+
};
|
|
15
|
+
const terminate = () => cancel('SIGTERM');
|
|
16
|
+
const hangup = () => cancel('SIGHUP');
|
|
17
|
+
const interrupt = () => cancel('SIGINT');
|
|
18
|
+
process.on('SIGTERM', terminate);
|
|
19
|
+
process.on('SIGHUP', hangup);
|
|
20
|
+
process.on('SIGINT', interrupt);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
get signal() {
|
|
24
|
+
return signal;
|
|
25
|
+
},
|
|
26
|
+
run(command, args, timeoutMs) {
|
|
27
|
+
return new Promise((resolveResult) => {
|
|
28
|
+
const child = spawn(command, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
29
|
+
let stdout = '';
|
|
30
|
+
let bytes = 0;
|
|
31
|
+
let failed = false;
|
|
32
|
+
let interruptTimer;
|
|
33
|
+
let killTimer;
|
|
34
|
+
let drainTimer;
|
|
35
|
+
const stop = (received) => {
|
|
36
|
+
clearTimeout(interruptTimer);
|
|
37
|
+
child.kill(received);
|
|
38
|
+
// Give npm a chance to forward cancellation first. KILL targets only this
|
|
39
|
+
// direct child; descendants can survive if npm cannot relay the signal.
|
|
40
|
+
killTimer ??= setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
41
|
+
};
|
|
42
|
+
const active = {
|
|
43
|
+
cancel(received) {
|
|
44
|
+
if (received === 'SIGINT') {
|
|
45
|
+
// Do not duplicate group INT. Any surviving child gets TERM after grace.
|
|
46
|
+
interruptTimer ??= setTimeout(() => stop('SIGTERM'), 2000);
|
|
47
|
+
} else stop(received);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
current = active;
|
|
51
|
+
const deadline = setTimeout(() => {
|
|
52
|
+
failed = true;
|
|
53
|
+
stop('SIGTERM');
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
child.stdout.setEncoding('utf8');
|
|
56
|
+
child.stdout.on('data', (chunk) => {
|
|
57
|
+
bytes += Buffer.byteLength(chunk);
|
|
58
|
+
if (bytes > 64 * 1024) {
|
|
59
|
+
failed = true;
|
|
60
|
+
stop('SIGTERM');
|
|
61
|
+
} else stdout += chunk;
|
|
62
|
+
});
|
|
63
|
+
child.stderr.resume();
|
|
64
|
+
child.once('error', () => {
|
|
65
|
+
failed = true;
|
|
66
|
+
});
|
|
67
|
+
child.once('exit', () => {
|
|
68
|
+
clearTimeout(deadline);
|
|
69
|
+
clearTimeout(interruptTimer);
|
|
70
|
+
clearTimeout(killTimer);
|
|
71
|
+
if (current === active) current = undefined;
|
|
72
|
+
// Descendants can inherit both pipes after npm exits. Drain briefly, then
|
|
73
|
+
// release both handles so they cannot keep resolution open indefinitely.
|
|
74
|
+
drainTimer = setTimeout(() => {
|
|
75
|
+
child.stdout.destroy();
|
|
76
|
+
child.stderr.destroy();
|
|
77
|
+
}, 1000);
|
|
78
|
+
});
|
|
79
|
+
child.once('close', (status) => {
|
|
80
|
+
clearTimeout(deadline);
|
|
81
|
+
clearTimeout(interruptTimer);
|
|
82
|
+
clearTimeout(killTimer);
|
|
83
|
+
clearTimeout(drainTimer);
|
|
84
|
+
if (current === active) current = undefined;
|
|
85
|
+
resolveResult({ status: failed ? null : status, stdout });
|
|
86
|
+
});
|
|
87
|
+
if (signal) active.cancel(signal);
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
dispose() {
|
|
91
|
+
process.off('SIGTERM', terminate);
|
|
92
|
+
process.off('SIGHUP', hangup);
|
|
93
|
+
process.off('SIGINT', interrupt);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { ownsPackage } from './package-owner.mjs';
|
|
15
|
+
import { createAcquisition } from './acquisition.mjs';
|
|
16
|
+
import { npmEnvironment } from './npm-environment.mjs';
|
|
17
|
+
|
|
18
|
+
const scripts = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pluginRoot = resolve(scripts, '..');
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const fail = (message) => {
|
|
22
|
+
process.stderr.write(`gestalt CLI: ${message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let version;
|
|
27
|
+
try {
|
|
28
|
+
version = JSON.parse(readFileSync(join(pluginRoot, '.codex-plugin/plugin.json'), 'utf8')).version;
|
|
29
|
+
if (
|
|
30
|
+
typeof version !== 'string' ||
|
|
31
|
+
!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)
|
|
32
|
+
) {
|
|
33
|
+
throw new Error('invalid version');
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
fail('cannot read the bundled plugin version; reinstall the plugin.');
|
|
37
|
+
}
|
|
38
|
+
if (Number(process.versions.node.split('.')[0]) < 22) fail('Node.js >=22 is required.');
|
|
39
|
+
|
|
40
|
+
// A development checkout is identified by the wrapper's own location, never cwd.
|
|
41
|
+
// Without tsx, use a matching compiled entry if available, then the exact npm pin.
|
|
42
|
+
const checkout = resolve(pluginRoot, '..');
|
|
43
|
+
let localArgs;
|
|
44
|
+
let sourceTsconfig;
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(readFileSync(join(checkout, 'package.json'), 'utf8'));
|
|
47
|
+
const entry = join(checkout, 'bin/gestalt.ts');
|
|
48
|
+
if (pkg.name === '@tienne/gestalt' && pkg.version === version && existsSync(entry)) {
|
|
49
|
+
const tsx = join(checkout, 'node_modules/tsx/dist/loader.mjs');
|
|
50
|
+
// Keep the target project's tsconfig paths out of the source loader.
|
|
51
|
+
if (existsSync(tsx)) {
|
|
52
|
+
localArgs = ['--import', tsx, entry];
|
|
53
|
+
sourceTsconfig = join(checkout, 'tsconfig.json');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!localArgs && pkg.name === '@tienne/gestalt' && pkg.version === version) {
|
|
57
|
+
const compiled = [join(checkout, 'bin/gestalt.js'), join(checkout, 'dist/bin/gestalt.js')].find(
|
|
58
|
+
existsSync,
|
|
59
|
+
);
|
|
60
|
+
if (compiled) localArgs = [compiled];
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// A copied plugin is deliberately independent of its containing project.
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let bin;
|
|
67
|
+
if (!localArgs) {
|
|
68
|
+
const neutral = mkdtempSync(join(tmpdir(), 'gestalt-cli-'));
|
|
69
|
+
const acquisition = createAcquisition({ cwd: neutral, env: npmEnvironment(process.env) });
|
|
70
|
+
try {
|
|
71
|
+
writeFileSync(join(neutral, 'package.json'), '{"private":true}\n');
|
|
72
|
+
for (const offline of [true, false]) {
|
|
73
|
+
if (!offline)
|
|
74
|
+
process.stderr.write(`gestalt CLI: resolving @tienne/gestalt@${version} from npm.\n`);
|
|
75
|
+
const result = await acquisition.run(
|
|
76
|
+
'npm',
|
|
77
|
+
[
|
|
78
|
+
'exec',
|
|
79
|
+
'--yes',
|
|
80
|
+
`--prefix=${neutral}`,
|
|
81
|
+
'--no-workspaces',
|
|
82
|
+
...(offline ? ['--offline'] : []),
|
|
83
|
+
`--package=@tienne/gestalt@${version}`,
|
|
84
|
+
'--',
|
|
85
|
+
process.execPath,
|
|
86
|
+
join(scripts, 'resolve-cli.mjs'),
|
|
87
|
+
version,
|
|
88
|
+
],
|
|
89
|
+
180_000,
|
|
90
|
+
);
|
|
91
|
+
if (acquisition.signal) break;
|
|
92
|
+
if (result.status !== 0) continue;
|
|
93
|
+
try {
|
|
94
|
+
const candidate = JSON.parse(result.stdout.trim());
|
|
95
|
+
if (typeof candidate !== 'string') continue;
|
|
96
|
+
bin = realpathSync(candidate);
|
|
97
|
+
if (!ownsPackage(bin, version)) {
|
|
98
|
+
bin = undefined;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Do not trust the resolver process or a stale global executable.
|
|
102
|
+
const check = await acquisition.run(process.execPath, [bin, '--version'], 10_000);
|
|
103
|
+
if (acquisition.signal) break;
|
|
104
|
+
if (check.status === 0 && check.stdout.trim() === version) break;
|
|
105
|
+
bin = undefined;
|
|
106
|
+
} catch {
|
|
107
|
+
bin = undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
rmSync(neutral, { recursive: true, force: true });
|
|
113
|
+
} finally {
|
|
114
|
+
acquisition.dispose();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (acquisition.signal) process.kill(process.pid, acquisition.signal);
|
|
118
|
+
if (!bin)
|
|
119
|
+
fail(
|
|
120
|
+
`could not resolve @tienne/gestalt@${version}; check npm, network access, and its native dependencies. No other version was executed.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const child = spawn(process.execPath, [...(localArgs ?? [bin]), ...args], {
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
...(sourceTsconfig ? { env: { ...process.env, TSX_TSCONFIG_PATH: sourceTsconfig } } : {}),
|
|
128
|
+
});
|
|
129
|
+
const terminate = () => child.kill('SIGTERM');
|
|
130
|
+
const hangup = () => child.kill('SIGHUP');
|
|
131
|
+
// Group Ctrl-C reaches the child: do not forward it twice. Programmatic callers
|
|
132
|
+
// must use SIGTERM to cancel only this pid. Signal tests cover both delivery modes.
|
|
133
|
+
const interrupt = () => {};
|
|
134
|
+
process.on('SIGTERM', terminate);
|
|
135
|
+
process.on('SIGHUP', hangup);
|
|
136
|
+
process.on('SIGINT', interrupt);
|
|
137
|
+
child.once('error', (error) => fail(error.message));
|
|
138
|
+
child.once('exit', (code, signal) => {
|
|
139
|
+
process.off('SIGTERM', terminate);
|
|
140
|
+
process.off('SIGHUP', hangup);
|
|
141
|
+
process.off('SIGINT', interrupt);
|
|
142
|
+
if (signal) process.kill(process.pid, signal);
|
|
143
|
+
else process.exit(code ?? 1);
|
|
144
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
// Registry settings may deliberately use a provider token. Preserve referenced
|
|
7
|
+
// names before filtering known credentials; native-build and network settings pass through.
|
|
8
|
+
export function npmEnvironment(source) {
|
|
9
|
+
const env = { ...source };
|
|
10
|
+
const find = (name) => Object.keys(env).find((key) => key.toLowerCase() === name);
|
|
11
|
+
const get = (name) => {
|
|
12
|
+
const key = find(name);
|
|
13
|
+
return key === undefined ? undefined : env[key];
|
|
14
|
+
};
|
|
15
|
+
const referenced = new Set();
|
|
16
|
+
// Match npm's optional references and escaped dollar signs in configuration.
|
|
17
|
+
const envExpr = /(?<!\\)(\\*)\$\{([^${}?]+)(\?)?\}/g;
|
|
18
|
+
const collect = (text) => {
|
|
19
|
+
for (const match of text.matchAll(envExpr)) {
|
|
20
|
+
if (match[1].length % 2 === 0) referenced.add(match[2].toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const expand = (path) =>
|
|
24
|
+
path.replace(envExpr, (whole, escapes, key, optional) => {
|
|
25
|
+
if (escapes.length % 2) return whole.slice((escapes.length + 1) / 2);
|
|
26
|
+
const value = env[key] ?? (optional ? '' : '${' + key + '}');
|
|
27
|
+
return escapes.slice(escapes.length / 2) + value;
|
|
28
|
+
});
|
|
29
|
+
const home = get('home') ?? get('userprofile') ?? homedir();
|
|
30
|
+
const files = [
|
|
31
|
+
get('npm_config_userconfig') ?? join(home, '.npmrc'),
|
|
32
|
+
get('npm_config_globalconfig') ??
|
|
33
|
+
join(get('npm_config_prefix') ?? dirname(dirname(process.execPath)), 'etc/npmrc'),
|
|
34
|
+
];
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
try {
|
|
37
|
+
const text = readFileSync(expand(file).replace(/^~(?=[/\\])/, home), 'utf8');
|
|
38
|
+
collect(
|
|
39
|
+
text
|
|
40
|
+
.split('\n')
|
|
41
|
+
.filter((line) => !/^\s*[#;]/.test(line))
|
|
42
|
+
.join('\n'),
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
// Missing/unreadable npm settings are handled by npm itself.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const [key, value] of Object.entries(env)) {
|
|
49
|
+
if (/^npm_config_/i.test(key)) collect(value);
|
|
50
|
+
}
|
|
51
|
+
for (const key of Object.keys(env)) {
|
|
52
|
+
if (/^npm_|^node_auth_token$/i.test(key) || referenced.has(key.toLowerCase())) continue;
|
|
53
|
+
if (
|
|
54
|
+
/^(ANTHROPIC_|OPENAI_|CLAUDE_CODE_OAUTH_|GEMINI_|CODEX_|XAI_|GROK_|GOOGLE_API_KEY$|AWS_ACCESS_KEY_ID$|AWS_SECRET_ACCESS_KEY$|AWS_SESSION_TOKEN$|GH_TOKEN$|GITHUB_TOKEN$)/i.test(
|
|
55
|
+
key,
|
|
56
|
+
) ||
|
|
57
|
+
/(_API_KEY|_SECRET|_SECRET_KEY|_CLIENT_SECRET|_PASSWORD|_ACCESS_TOKEN|_AUTH_TOKEN|_BOT_TOKEN)$|^HF_TOKEN$/i.test(
|
|
58
|
+
key,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
delete env[key];
|
|
62
|
+
}
|
|
63
|
+
return env;
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// The nearest manifest owns the bin; an unrelated parent must not vouch for it.
|
|
5
|
+
export function ownsPackage(bin, version) {
|
|
6
|
+
try {
|
|
7
|
+
let directory = dirname(bin);
|
|
8
|
+
while (directory !== dirname(directory)) {
|
|
9
|
+
const manifest = join(directory, 'package.json');
|
|
10
|
+
if (existsSync(manifest)) {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
12
|
+
return pkg.name === '@tienne/gestalt' && pkg.version === version;
|
|
13
|
+
}
|
|
14
|
+
directory = dirname(directory);
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { delimiter, join } from 'node:path';
|
|
4
|
+
import { ownsPackage } from './package-owner.mjs';
|
|
5
|
+
|
|
6
|
+
// npm exec prepends its package bin directory. Verify the owning package instead
|
|
7
|
+
// of accepting a same-named executable inherited from the user's PATH.
|
|
8
|
+
const expected = process.argv[2];
|
|
9
|
+
for (const entry of (process.env.PATH ?? '').split(delimiter)) {
|
|
10
|
+
if (!entry) continue;
|
|
11
|
+
const candidate = join(entry, 'gestalt');
|
|
12
|
+
try {
|
|
13
|
+
const bin = realpathSync(candidate);
|
|
14
|
+
if (ownsPackage(bin, expected)) {
|
|
15
|
+
process.stdout.write(JSON.stringify(bin));
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
// Broken PATH entries and unrelated packages do not resolve this pin.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
process.exitCode = 1;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 에이전트를 서브에이전트로 위임하기 (공유 규칙)
|
|
2
2
|
|
|
3
|
+
> **Codex 등 다른 호스트에서는** [`host-runtime.md`](./host-runtime.md)를 먼저 적용한다. `inherit`는 모델 인자 생략이며 Claude 별칭 폴백을 강제하지 않는다.
|
|
4
|
+
|
|
3
5
|
> **현재 적용: `review`, `pr`, `brief`, `presentation`, `review-reply`, `slack-send`, `jira-create`, `ship`, `explain`, `research`, `ux-research`, `reverse-plan`.** 에이전트를 불러 쓰는 스킬은 전부 위임한다. 새 스킬이 `ges_agent get`을 메인에서 하면 그건 예외가 아니라 빠뜨린 것이다.
|
|
4
6
|
|
|
5
7
|
## 왜 필요한가
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 에이전트 tier로 모델 고르기 (공유 규칙)
|
|
2
2
|
|
|
3
|
+
> **Codex 등 다른 호스트에서는** [`host-runtime.md`](./host-runtime.md)를 먼저 적용한다. `inherit`는 모델 인자 생략이며 Claude 별칭 폴백을 강제하지 않는다.
|
|
4
|
+
|
|
3
5
|
`ges_agent`의 `get`과 `list`는 둘 다 `tier`와 **해석된 `model`** 을 돌려준다. 에이전트 frontmatter의
|
|
4
6
|
tier가 "이 역할이 어느 정도 모델을 필요로 하나"를 선언하고 서버가 그걸 호스트 Agent 도구가 받는
|
|
5
7
|
별칭으로 옮겨준 값이다. 둘의 차이는 `systemPrompt`가 딸려오느냐뿐이다. `get`은 한 에이전트의
|
|
@@ -1,45 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 플러그인과 같은 버전의 CLI 실행
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
플러그인으로 설치되면 호스트가 복사해 가는 건 `plugin/` 아래뿐이다. `bin/`도 `package.json`도 `node_modules`도 그 안에 없다. 그래서 스킬 본문에 `pnpm tsx bin/gestalt.ts ...`만 적혀 있으면 게슈탈트 레포 밖에서는 그 명령이 원리상 안 돈다.
|
|
8
|
-
|
|
9
|
-
그때 읽는 쪽이 하는 말이 "의존성이 안 깔려 있어서 못 돌립니다"다. 실제로는 전역 `gestalt`가 깔려 있어 돌릴 수 있는데도 스킬이 적어준 형태 하나가 막혀서 포기한다. 어투 검사처럼 **필수 검사인 단계가 이렇게 조용히 건너뛰어지면** 막으려던 문장이 그대로 나간다.
|
|
10
|
-
|
|
11
|
-
그래서 호출 형태를 스킬마다 박아두지 않고 한 번 판정해서 쓴다.
|
|
12
|
-
|
|
13
|
-
## 판정
|
|
14
|
-
|
|
15
|
-
CLI를 처음 부르기 전에 한 번 돌린다.
|
|
3
|
+
스킬 본문의 `gestalt ...` 예시는 아래 런처로 실행한다. 전역 `gestalt` 설치는 필요하지 않다.
|
|
4
|
+
이 문서가 있는 디렉토리(`.../skills/_shared`)에서 두 단계 위가 플러그인 루트다.
|
|
5
|
+
런처는 그 아래 `scripts/gestalt-cli.mjs`에 있다. 예를 들어 문서가 `/cache/gestalt/skills/_shared/cli-launcher.md`이면 런처는 `/cache/gestalt/scripts/gestalt-cli.mjs`다.
|
|
6
|
+
현재 작업 레포의 동명 파일이나 `bin/gestalt.ts`를 찾아 실행하지 않는다.
|
|
16
7
|
|
|
17
8
|
```bash
|
|
18
|
-
|
|
19
|
-
echo "OK gestalt"
|
|
20
|
-
elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
|
|
21
|
-
echo "OK pnpm"
|
|
22
|
-
else
|
|
23
|
-
echo "MISSING"
|
|
24
|
-
fi
|
|
9
|
+
node "/플러그인/절대경로/scripts/gestalt-cli.mjs" humanize-scan --file "$scanTmp/report.md" --register report
|
|
25
10
|
```
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
루트 `skills` 심링크로 읽었다면 먼저 이 문서 경로를 `realpath`로 풀고 부모 경로를 계산한다.
|
|
13
|
+
런처는 자신의 위치를 기준으로 다음을 수행한다.
|
|
14
|
+
|
|
15
|
+
1. `.codex-plugin/plugin.json`에서 설치된 플러그인의 버전을 읽는다. 버전을 모르면 중단한다.
|
|
16
|
+
2. 로컬 의존성이 있는 게슈탈트 개발 체크아웃이면 그 TypeScript 엔트리를 사용한다.
|
|
17
|
+
같은 버전의 npm 설치본 안에 빌드된 CLI가 있으면 그 엔트리를 사용한다.
|
|
18
|
+
소스만 있고 의존성이 없는 플러그인 설치본은 다음 npm 해석 단계로 진행한다.
|
|
19
|
+
3. 설치된 복사본이면 npm 캐시에서 정확히 같은 `@tienne/gestalt@<version>`을 해석하고
|
|
20
|
+
캐시가 없으면 같은 버전을 다운로드한다. 패키지 이름과 버전, 실제 CLI 버전을 확인한다.
|
|
21
|
+
4. 실제 명령은 원래 작업 디렉토리에서 실행하며 stdin, 인자, 종료 코드를 보존한다.
|
|
22
|
+
터미널 Ctrl-C는 자식도 함께 받으므로 중복 전달하지 않는다. 래퍼 pid만 지정해 취소할 때는 SIGTERM을 사용한다.
|
|
23
|
+
SIGTERM과 SIGHUP은 npm 실행과 버전 확인, 실제 명령에 전달하고 자식 종료를 기다린다.
|
|
24
|
+
npm 실행과 버전 확인 중에는 SIGTERM이나 SIGHUP을 보낸 뒤 5초간 자식이 끝나지 않으면 SIGKILL을 보낸다.
|
|
25
|
+
SIGKILL은 직접 실행한 프로세스 하나에만 보낸다. npm이 신호를 자식에게 넘기지 못하고 멈추면 설치 스크립트 같은 자손은 남을 수 있다.
|
|
26
|
+
이 두 단계에서 SIGINT를 받으면 SIGINT를 다시 전달하지 않고 2초 동안 자식이 스스로 끝나기를 기다린다.
|
|
27
|
+
그래도 남아 있으면 SIGTERM을 보낸다. 래퍼 pid만 받은 SIGINT도 이 경로로 정리된다.
|
|
28
|
+
취소한 뒤에는 임시 디렉토리를 지운다. 오프라인 해석이나 버전 확인이 실패하거나 시간을 넘기면 같은 버전을 온라인으로 한 번 더 시도한다.
|
|
29
|
+
온라인 시도도 실패하거나 시간을 넘기면 사용자 취소가 아닌 오류로 알린다.
|
|
30
|
+
|
|
31
|
+
npm 해석은 별도의 임시 디렉토리에서 한다. 작업 레포의 로컬 패키지나 다른 버전의 전역 CLI를
|
|
32
|
+
선택하지 않으며 `latest`로 떨어지지 않는다.
|
|
33
|
+
npm으로 CLI 패키지를 받는 동안에는 알려진 비밀 환경 변수를 npm에 넘기지 않는다.
|
|
34
|
+
모델 공급자 접두어(`ANTHROPIC_`, `OPENAI_` 등)로 시작하는 변수와 클라우드, GitHub의 인증 변수(`AWS_SECRET_ACCESS_KEY`, `GITHUB_TOKEN` 등)가 여기에 든다.
|
|
35
|
+
이름이 `_API_KEY`, `_PASSWORD`, `_SECRET`, `_AUTH_TOKEN` 등으로 끝나는 변수도 대소문자 구분 없이 제외한다. 이 필터는 알려진 이름과 패턴만 검사한다.
|
|
36
|
+
`NPM_*`, `NODE_AUTH_TOKEN`과 `npm_config_*`는 보존한다. 사용자 `.npmrc`와 전역 설정 파일에서 `${NAME}` 또는 `${NAME?}`으로 참조한 변수도 인증에 필요하므로 예외로 남긴다.
|
|
37
|
+
사용자 설정 경로는 `npm_config_userconfig` 또는 홈의 `.npmrc`다. 전역 설정은 `npm_config_globalconfig` 또는 npm prefix의 `etc/npmrc`에서 읽는다. prefix를 지정하지 않았으면 Node 설치 경로를 기준으로 한다.
|
|
38
|
+
프록시와 CA 설정은 유지하며 최종 CLI에는 원래 환경을 전달한다. 처음 실행할 때 Node.js >=22와 npm, 네트워크가 필요할 수 있다.
|
|
39
|
+
셸 함수가 필요하면 명령을 문자열 변수로 합치지 말고 인자를 그대로 넘긴다.
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## 없을 때
|
|
41
|
+
```bash
|
|
42
|
+
gestalt() { node "/플러그인/절대경로/scripts/gestalt-cli.mjs" "$@"; }
|
|
43
|
+
```
|
|
38
44
|
|
|
39
|
-
`
|
|
45
|
+
셸 함수는 `xargs`의 외부 실행 파일이 아니다. **파이프라인과 `xargs`를 포함한 모든 예시**에서도
|
|
46
|
+
`gestalt`를 `node`와 런처의 절대 경로로 치환한다. 함수 선언만 하고 `xargs gestalt`를 실행하지 않는다.
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
```bash
|
|
49
|
+
find "$scanTmp" -name '*.md' -print0 | xargs -0 node "/플러그인/절대경로/scripts/gestalt-cli.mjs" humanize-scan --register chat
|
|
50
|
+
```
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
`humanize-scan`의 종료 코드 `10`은 검사 실행 실패가 아니라 탐지 없음이다. 그 명령의 계약대로 읽는다.
|
|
53
|
+
`xargs`는 자식의 종료 코드 `10`을 그대로 전달하지 않는다. 검사 판정에는 파일별 출력의 `EXIT=` 값을 사용한다. 단일 파일처럼 파일별 표지가 없는 호출에서 종료 코드가 필요하면 `xargs` 대신 런처를 직접 실행한다.
|
|
54
|
+
런처를 찾지 못했거나 버전 해석이 실패하면 필수 검사와 그 수치에 의존하는 단계는 중단하고 오류를 알린다.
|
|
55
|
+
그 밖의 선택적 단계는 실행하지 못한 단계를 완료 보고에 적는다. 검사 결과를 직접 지어내거나
|
|
56
|
+
다른 버전의 실행을 같은 결과로 보고하지 않는다. MCP 도구는 [`tool-availability.md`](./tool-availability.md)를 따른다.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 호스트별 도구 호출
|
|
2
|
+
|
|
3
|
+
스킬 본문의 `Agent`, `Explore`, `AskUserQuestion`, `TaskCreate`는 Claude Code 표기다.
|
|
4
|
+
실행 전에 이 문서를 읽고 **현재 호스트가 실제로 제공한 도구**로 옮긴다. Codex에서는 이 문서가
|
|
5
|
+
다른 문서의 Claude 전용 타입, 모델 필수 인자, `sonnet` 폴백 지시보다 우선한다.
|
|
6
|
+
MCP 도구의 입력과 2-Call 제출 계약은 호스트가 바뀌어도 그대로다.
|
|
7
|
+
|
|
8
|
+
모든 호스트에서 스킬의 `gestalt ...` 예시는 먼저 [CLI 실행 지침](./cli-launcher.md)을 읽고
|
|
9
|
+
버전 고정 런처로 실행한다. 개별 스킬에 이 링크가 없어도 적용하며 파이프라인과 `xargs` 호출도 포함한다.
|
|
10
|
+
|
|
11
|
+
## Codex
|
|
12
|
+
|
|
13
|
+
`ges_status {}`의 `hostClient`가 `codex`이면 Codex 모델 기본값은 `inherit`다.
|
|
14
|
+
`client`는 실행 컨텍스트를 기록할 파일의 선택이라 `both`일 수도 있다. 이 값을 호스트 판정과 혼동하지 않는다.
|
|
15
|
+
`hostClient`가 없는 이전 서버와 연결돼 있어도 현재 호스트가 Codex이면 서버의 Claude 별칭을 그대로 모델 인자로 보내지 않는다. `fable`, `haiku`, `sonnet`, `opus`는 이때 `inherit`로 취급한다. 명시한 다른 모델 ID는 유지한다. 이전 서버에는 `ges_execute` 요청의 `client: "codex"`도 명시해 컨텍스트 기록 대상을 맞추고 플러그인 갱신을 안내한다.
|
|
16
|
+
사용자가 명시한 모델 ID와 실제 도구의 지원 여부를 우선한다. Claude 모델 별칭을 GPT 모델로 추측해서 바꾸지 않는다.
|
|
17
|
+
실행 태스크의 자동 `task.model`이 `haiku`, `sonnet`, `opus`이면 기존 tier 힌트다. 각각 `ges_status`의
|
|
18
|
+
`tierModels.frugal`, `tierModels.standard`, `tierModels.frontier`로 해석한 뒤 아래 모델 규칙을 적용한다.
|
|
19
|
+
명시적인 Codex 모델 ID는 다시 매핑하지 않는다.
|
|
20
|
+
|
|
21
|
+
| 본문의 표기 | Codex에서 수행할 일 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `Agent`의 `gestalt:` 에이전트 타입 | [`agent-delegation.md`](./agent-delegation.md)의 Explore 폴백 프롬프트를 사용해 범용 서브에이전트 안에서 `ges_agent get`으로 페르소나를 받는다. 해당 문서의 절차대로 룰북 디렉토리를 절대 경로로 풀어 프롬프트에 직접 넣는다. 경로를 못 풀었으면 그 사실을 보고한다. `subagent_type`은 전달하지 않는다. |
|
|
24
|
+
| `Explore` | 읽기 전용 조사를 위임한다. 실제 읽기 전용 타입이 없으면 파일 수정, 커밋, 외부 전송 금지를 프롬프트에 명시하고 도구로 강제된 제한이라고 말하지 않는다. |
|
|
25
|
+
| `Agent.model`, `reasoningModel`, `tierModels` | `inherit`이면 모델 인자를 생략한다. 명시 모델 ID는 스폰 도구가 받는 경우에만 전달한다. |
|
|
26
|
+
| `effort` | 해당 도구가 받는 `reasoning_effort` 또는 동등한 인자로 옮긴다. 지원하지 않는 인자는 보내지 않는다. |
|
|
27
|
+
| `AskUserQuestion` | 현재 모드의 질문 도구가 있으면 쓰고, 없으면 일반 대화로 묻는다. 필수 답변과 승인 없이 종속 단계를 실행하지 않는다. |
|
|
28
|
+
| `TaskCreate`, `TaskUpdate`, `TaskList` | 호스트의 진행 계획 도구가 있으면 쓰고, 없으면 짧은 진행 메시지로 알린다. 진행 패널 부재는 작업을 막지 않는다. |
|
|
29
|
+
| `Bash`, `Read`, `Grep`, `Glob`, `Edit`, `Write` | 호스트의 셸, 파일 읽기, 검색, 패치 도구를 사용한다. 명령 인자와 쓰기 범위는 그대로 지킨다. |
|
|
30
|
+
|
|
31
|
+
`spawn_agent`, `send_message`, `followup_task`, `wait_agent`가 제공되면 그 도구로 위임하고 결과를 기다린다.
|
|
32
|
+
스폰 스키마가 `fork_turns: "all"`일 때 모델 재지정을 금지하면, 상속 모델은 그대로 두고
|
|
33
|
+
모델이나 effort를 명시해야 할 때만 `fork_turns: "none"`으로 필수 맥락과 절대 경로를 직접 전달한다.
|
|
34
|
+
이 매개변수가 없는 호스트에는 그 인자를 보내지 않는다. 특정 Codex 버전의 도구명을 필수 조건으로 삼지 않는다.
|
|
35
|
+
|
|
36
|
+
스폰 도구가 명시한 모델을 거절했을 때만 아래 순서로 재시도한다.
|
|
37
|
+
`ges_status.hostClient`가 `codex`인 새 서버에서는 `reasoningModelFallback`이 `inherit`가 아니면 그 값으로 한 번 재시도한다. `hostClient`가 없는 이전 서버에서는 Claude 기본 별칭이 아닌 폴백 ID만 사용한다. 그것도 거절되거나 별도 폴백 ID가 없으면 세션 상속으로 한 번 재시도하고 끝낸다. 서버 기본값인 Claude 별칭으로 재시도하지 않는다. `inherit`를 지원 모델 이름처럼 전달하지 않는다. 모델 선택을 지원하지 않는 호스트에서는
|
|
38
|
+
상속으로 실행하고 완료 보고에 모델 지정이 적용되지 않았음을 적는다.
|
|
39
|
+
|
|
40
|
+
서브에이전트가 없으면 메인이 `ges_agent get`으로 페르소나를 받아 직접 수행할 수 있다.
|
|
41
|
+
이때 컨텍스트가 메인에 실렸음을 완료 보고에 밝힌다. **독립된 판정자가 필요한 단계**는 `doubt`, review의 3.5 `continuity-judge`, 3.7 `suggestion-verifier`다. 이 셋은 메인의 자기 검토로 대체하지 않는다. 제안 검증을 못 돌렸으면 review 지침에 따라 미실행을 알린다. 정합 심급을 못 돌렸으면 그 사실을 적고 Pass를 승인으로 게시하지 않는다. MCP 자체의 부재는
|
|
42
|
+
[`tool-availability.md`](./tool-availability.md)를 따르며 도구 없이 세션이나 검사 결과를 만들지 않는다.
|
|
43
|
+
|
|
44
|
+
## Claude Code와 그 밖의 호스트
|
|
45
|
+
|
|
46
|
+
Claude Code는 기존 `Agent` 타입과 모델 별칭을 그대로 쓴다. `inherit`를 명시했다면 모델 인자를 생략한다.
|
|
47
|
+
Grok 등 다른 호스트도 실제 제공된 도구에 맞춰 같은 의미로 옮기며 지원 여부를 모르는 도구 이름이나 모델을 만들지 않는다.
|
|
@@ -22,6 +22,8 @@ outputs:
|
|
|
22
22
|
|
|
23
23
|
# Agent Skill
|
|
24
24
|
|
|
25
|
+
> **호스트별 도구와 모델 호출** → [`../_shared/host-runtime.md`](../_shared/host-runtime.md). 실행 전에 읽고 현재 호스트에 맞게 적용한다.
|
|
26
|
+
|
|
25
27
|
Invoke any Gestalt Role or Review agent directly, outside the Gestalt pipeline.
|
|
26
28
|
|
|
27
29
|
> **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
|
|
@@ -61,6 +61,8 @@ outputs:
|
|
|
61
61
|
|
|
62
62
|
# Architecture Skill
|
|
63
63
|
|
|
64
|
+
> **호스트별 도구와 모델 호출** → [`../_shared/host-runtime.md`](../_shared/host-runtime.md). 실행 전에 읽고 현재 호스트에 맞게 적용한다.
|
|
65
|
+
|
|
64
66
|
흩어진 코드와 문서에서 아키텍처 그림을 근거와 함께 그린다. 탐색과 IR 작성은 이 세션이 하고 `ges_architecture` 도구는 IR을 검증하고 결정적으로 그리기만 한다. 서버는 LLM을 부르지 않는다.
|
|
65
67
|
|
|
66
68
|
이 스킬이 지키는 원칙은 하나다. **그림에 그은 실선은 전부 "코드나 스펙에서 확인했다"는 주장이다.** 틀린 실선 하나가 그림 전체의 신뢰를 깎는다. 그래서 근거를 못 찾은 연결은 실선으로 긋지 않고 점선이나 미해결 질문으로 남긴다. 빈칸이 많은 그림이 틀린 그림보다 낫다.
|
|
@@ -73,6 +73,8 @@ outputs:
|
|
|
73
73
|
|
|
74
74
|
# Blast Radius Skill
|
|
75
75
|
|
|
76
|
+
> **호스트별 도구와 모델 호출** → [`../_shared/host-runtime.md`](../_shared/host-runtime.md). 실행 전에 읽고 현재 호스트에 맞게 적용한다.
|
|
77
|
+
|
|
76
78
|
최근 코드 변경의 영향 범위를 분석해 **읽어야 할 파일만** 컨텍스트에 제공합니다. 불필요한 파일 읽기를 줄여 LLM 토큰 사용을 최소화합니다.
|
|
77
79
|
|
|
78
80
|
> **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
|
|
@@ -41,6 +41,8 @@ routing: {}
|
|
|
41
41
|
|
|
42
42
|
# Brief Skill
|
|
43
43
|
|
|
44
|
+
> **호스트별 도구와 모델 호출** → [`../_shared/host-runtime.md`](../_shared/host-runtime.md). 실행 전에 읽고 현재 호스트에 맞게 적용한다.
|
|
45
|
+
|
|
44
46
|
성과 분석과 의사결정, 기획 문서를 이해관계자 설득용 산문으로 작성합니다. `impact-writer` 에이전트가 초안을 쓰고 `humanize-monolith`가 다듬는 워크플로우입니다. 코드 중심 기술문서(API·README·튜토리얼)는 이 스킬이 아니라 `technical-writer` 영역입니다.
|
|
45
47
|
|
|
46
48
|
> **읽어온 텍스트를 다루는 규칙** → [`../_shared/untrusted-input.md`](../_shared/untrusted-input.md)
|
|
@@ -33,6 +33,8 @@ outputs:
|
|
|
33
33
|
|
|
34
34
|
# Build Graph Skill
|
|
35
35
|
|
|
36
|
+
> **호스트별 도구와 모델 호출** → [`../_shared/host-runtime.md`](../_shared/host-runtime.md). 실행 전에 읽고 현재 호스트에 맞게 적용한다.
|
|
37
|
+
|
|
36
38
|
코드베이스를 정적 분석해 코드 지식 그래프를 빌드합니다. 이 그래프를 바탕으로 `/blast-radius` 스킬을 사용할 수 있습니다.
|
|
37
39
|
|
|
38
40
|
> **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
|