@gaonjs/cli 0.59.0 → 0.60.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/dist/doctor/agents-docs-stale.d.ts +2 -0
- package/dist/doctor/agents-docs-stale.js +37 -0
- package/dist/doctor/fixers/index.js +5 -0
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor.d.ts +2 -1
- package/dist/doctor.js +8 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -5
- package/dist/scaffold/agentsDocs.d.ts +29 -0
- package/dist/scaffold/agentsDocs.js +115 -0
- package/dist/templates/project/AGENTS.md.tpl +5 -3
- package/dist/templates/project/CLAUDE.md.tpl +2 -1
- package/package.json +3 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @gaonjs/cli · doctor · AGENTS 문서 스테일 (결정 449 · 2026-08-07)
|
|
2
|
+
//
|
|
3
|
+
// gaonjs 업그레이드 후에도 프로젝트의 `AGENTS.md`·`agents/*.md` 사본은 스캐폴드
|
|
4
|
+
// 시점 그대로다 — 관례 문서가 낡으면 AI 가 새 표면을 모른 채(또는 폐기된
|
|
5
|
+
// 패턴대로) 코드를 짠다. 설치된 CLI 템플릿(정본)과 byte 비교(저장소
|
|
6
|
+
// agents-docs-sync 게이트와 동일 판정)해 다르면 경고 + 수리 안내를 낸다.
|
|
7
|
+
//
|
|
8
|
+
// 경고(warning)인 이유: 드물게 사용자가 사본에 손댔을 수 있다 — 검사는
|
|
9
|
+
// 표면화만 하고, 덮을지는 사용자가 `gaon g agents-docs`(--check 미리보기)로
|
|
10
|
+
// 결정한다. 2층 구조 미사용 프로젝트(AGENTS.md·agents/ 없음)는 검사 대상
|
|
11
|
+
// 없음 통과(agents-doc-index 와 동일 소급 불강제).
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { agentsDocsStatus } from '../scaffold/agentsDocs.js';
|
|
15
|
+
export async function checkAgentsDocsStale(cwd) {
|
|
16
|
+
const issues = [];
|
|
17
|
+
// 2층 구조 미사용 — 검사 대상 없음.
|
|
18
|
+
if (!existsSync(join(cwd, 'AGENTS.md')) && !existsSync(join(cwd, 'agents'))) {
|
|
19
|
+
return { rule: 'agents-docs-stale', issues };
|
|
20
|
+
}
|
|
21
|
+
for (const s of agentsDocsStatus(cwd)) {
|
|
22
|
+
if (s.state === 'current')
|
|
23
|
+
continue;
|
|
24
|
+
issues.push({
|
|
25
|
+
rule: 'agents-docs-stale',
|
|
26
|
+
level: 'warning',
|
|
27
|
+
file: s.path,
|
|
28
|
+
message: s.state === 'missing'
|
|
29
|
+
? `설치된 gaonjs 템플릿에 있는 관례 문서 ${s.path} 가 프로젝트에 없습니다(업그레이드로 추가된 카테고리).\n` +
|
|
30
|
+
`→ gaon g agents-docs 로 생성하세요(미리보기: gaon g agents-docs --check).`
|
|
31
|
+
: `${s.path} 가 설치된 gaonjs 템플릿(정본)과 다릅니다(${s.note ?? '내용 상이'}) — 업그레이드 후 옛 문서가 남으면 AI 가 낡은 관례로 코드를 짭니다.\n` +
|
|
32
|
+
`→ gaon g agents-docs 로 재동기하세요(미리보기: --check · 사본에 직접 적은 내용이 있다면 먼저 CLAUDE.md 등으로 옮기세요 — 이 문서들은 프레임웍 정본 사본입니다).`,
|
|
33
|
+
detail: { state: s.state, note: s.note },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return { rule: 'agents-docs-stale', issues };
|
|
37
|
+
}
|
|
@@ -40,6 +40,11 @@ export const FIXER_CAPABILITIES = [
|
|
|
40
40
|
hasFixer: false,
|
|
41
41
|
note: '수동 · 액션을 두 개로 분리하거나 JSON 분기를 예외/redirect 로 재편(설계 결정).',
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
rule: 'agents-docs-stale',
|
|
45
|
+
hasFixer: false,
|
|
46
|
+
note: '전용 명령 `gaon g agents-docs` 로 재동기(--check 미리보기 · 결정 449) — 재동기 경로를 하나로 둔다(The One Way · --fix 중복 구현 안 함).',
|
|
47
|
+
},
|
|
43
48
|
{
|
|
44
49
|
rule: 'n-plus-one',
|
|
45
50
|
hasFixer: false,
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-purity' | 'no-auto-import' | 'schema-filename' | 'agents-doc-index' | 'column-casing' | 'model-filename' | 'page-filename' | 'auth-wiring' | 'ui-kit-wiring' | 'route-registration' | 'static-collision' | 'method-override' | 'csrf-wiring' | 'internal-anchor' | 'pageprops-destructure' | 'async-offload' | 'page-layout-breakpoint' | 'link-button-nesting' | 'seal-security' | 'schema-relations' | 'no-import-meta-env' | 'locale-parity' | 'render-return' | 'channel-collision' | 'channel-instance-authorize' | 'dotenv-node-env';
|
|
1
|
+
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-purity' | 'no-auto-import' | 'schema-filename' | 'agents-doc-index' | 'agents-docs-stale' | 'column-casing' | 'model-filename' | 'page-filename' | 'auth-wiring' | 'ui-kit-wiring' | 'route-registration' | 'static-collision' | 'method-override' | 'csrf-wiring' | 'internal-anchor' | 'pageprops-destructure' | 'async-offload' | 'page-layout-breakpoint' | 'link-button-nesting' | 'seal-security' | 'schema-relations' | 'no-import-meta-env' | 'locale-parity' | 'render-return' | 'channel-collision' | 'channel-instance-authorize' | 'dotenv-node-env';
|
|
2
2
|
export type DoctorLevel = 'passed' | 'warning' | 'error';
|
|
3
3
|
export interface DoctorCheck {
|
|
4
4
|
readonly rule: DoctorRule;
|
package/dist/doctor.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { inspectSharedSource, checkSharedPurity, } from './doctor/shared-purity.
|
|
|
12
12
|
export { inspectConfigForAutoImport, inspectPackageJson, checkNoAutoImport, } from './doctor/no-auto-import.js';
|
|
13
13
|
export { expectedSchemaStem, extractTableName, checkSchemaFilename, } from './doctor/schema-filename.js';
|
|
14
14
|
export { extractAgentDocRefs, checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
15
|
+
export { checkAgentsDocsStale } from './doctor/agents-docs-stale.js';
|
|
15
16
|
export { expectedColumnName, extractSnakeColumns, checkColumnCasing, } from './doctor/column-casing.js';
|
|
16
17
|
export { isPascalCase, checkModelFilename } from './doctor/model-filename.js';
|
|
17
18
|
export { checkPageFilename } from './doctor/page-filename.js';
|
|
@@ -32,7 +33,7 @@ export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, }
|
|
|
32
33
|
* 실행할 검사 이름. 지정 없음(undefined) = 31개 모두.
|
|
33
34
|
*/
|
|
34
35
|
/**
|
|
35
|
-
* doctor 정적 검사
|
|
36
|
+
* doctor 정적 검사 32종의 정본 목록(§2.2). `--check=` 필터의 인정 집합도
|
|
36
37
|
* 이 배열을 단일 출처로 삼는다(parseDoctorChecks) — 새 규칙 추가 시 여기만
|
|
37
38
|
* 늘리면 실행·필터·타입이 함께 정합된다(손유지 중복 리스트 표류 방지).
|
|
38
39
|
*/
|
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @gaonjs/cli · `gaon doctor` — 정적 검사 (M9-E · CLI DX 완성 · E-5 확장)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 32 검사를 조립한다:
|
|
5
5
|
* 1) response-mixing (errata E-3 §C · 라이브)
|
|
6
6
|
* 2) n-plus-one (errata E-4 (e))
|
|
7
7
|
* 3) dependency-direction (CLAUDE.md §5 · 4 규칙)
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 7) no-auto-import (errata E-5 §2.4 · v0.15 §1.2)
|
|
12
12
|
* 8) schema-filename (§1.1 · 결정 38 · 스키마 파일명 camelCase)
|
|
13
13
|
* 9) agents-doc-index (§0 색인 ↔ agents/ 실 파일 · 결정 40)
|
|
14
|
+
* 9b) agents-docs-stale (설치 템플릿 ↔ 프로젝트 사본 byte 비교 · 업그레이드 후 스테일 경고 · 결정 449)
|
|
14
15
|
* 10) column-casing (§네이밍 · 결정 43·46 · 컬럼 camelCase)
|
|
15
16
|
* 11) model-filename (§3.4 · 결정 32·46 · 모델 파일명 PascalCase)
|
|
16
17
|
* 12) page-filename (§3.4 · 결정 32·46 · Vue 페이지 파일명 PascalCase)
|
|
@@ -56,6 +57,7 @@ import { checkSharedPurity } from './doctor/shared-purity.js';
|
|
|
56
57
|
import { checkNoAutoImport } from './doctor/no-auto-import.js';
|
|
57
58
|
import { checkSchemaFilename } from './doctor/schema-filename.js';
|
|
58
59
|
import { checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
60
|
+
import { checkAgentsDocsStale } from './doctor/agents-docs-stale.js';
|
|
59
61
|
import { checkColumnCasing } from './doctor/column-casing.js';
|
|
60
62
|
import { checkModelFilename } from './doctor/model-filename.js';
|
|
61
63
|
import { checkPageFilename } from './doctor/page-filename.js';
|
|
@@ -91,6 +93,7 @@ export { inspectSharedSource, checkSharedPurity, } from './doctor/shared-purity.
|
|
|
91
93
|
export { inspectConfigForAutoImport, inspectPackageJson, checkNoAutoImport, } from './doctor/no-auto-import.js';
|
|
92
94
|
export { expectedSchemaStem, extractTableName, checkSchemaFilename, } from './doctor/schema-filename.js';
|
|
93
95
|
export { extractAgentDocRefs, checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
96
|
+
export { checkAgentsDocsStale } from './doctor/agents-docs-stale.js';
|
|
94
97
|
export { expectedColumnName, extractSnakeColumns, checkColumnCasing, } from './doctor/column-casing.js';
|
|
95
98
|
export { isPascalCase, checkModelFilename } from './doctor/model-filename.js';
|
|
96
99
|
export { checkPageFilename } from './doctor/page-filename.js';
|
|
@@ -111,7 +114,7 @@ export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, }
|
|
|
111
114
|
* 실행할 검사 이름. 지정 없음(undefined) = 31개 모두.
|
|
112
115
|
*/
|
|
113
116
|
/**
|
|
114
|
-
* doctor 정적 검사
|
|
117
|
+
* doctor 정적 검사 32종의 정본 목록(§2.2). `--check=` 필터의 인정 집합도
|
|
115
118
|
* 이 배열을 단일 출처로 삼는다(parseDoctorChecks) — 새 규칙 추가 시 여기만
|
|
116
119
|
* 늘리면 실행·필터·타입이 함께 정합된다(손유지 중복 리스트 표류 방지).
|
|
117
120
|
*/
|
|
@@ -125,6 +128,7 @@ export const ALL_RULES = [
|
|
|
125
128
|
'no-auto-import',
|
|
126
129
|
'schema-filename',
|
|
127
130
|
'agents-doc-index',
|
|
131
|
+
'agents-docs-stale',
|
|
128
132
|
'column-casing',
|
|
129
133
|
'model-filename',
|
|
130
134
|
'page-filename',
|
|
@@ -164,6 +168,7 @@ export const RULE_SUMMARIES = {
|
|
|
164
168
|
'no-auto-import': '자동 import',
|
|
165
169
|
'schema-filename': '스키마 파일명',
|
|
166
170
|
'agents-doc-index': 'AGENTS 색인',
|
|
171
|
+
'agents-docs-stale': 'AGENTS 문서 스테일',
|
|
167
172
|
'column-casing': '컬럼 casing',
|
|
168
173
|
'model-filename': '모델 파일명',
|
|
169
174
|
'page-filename': '페이지 파일명',
|
|
@@ -197,6 +202,7 @@ const CHECKERS = {
|
|
|
197
202
|
'no-auto-import': checkNoAutoImport,
|
|
198
203
|
'schema-filename': checkSchemaFilename,
|
|
199
204
|
'agents-doc-index': checkAgentsDocIndex,
|
|
205
|
+
'agents-docs-stale': checkAgentsDocsStale,
|
|
200
206
|
'column-casing': checkColumnCasing,
|
|
201
207
|
'model-filename': checkModelFilename,
|
|
202
208
|
'page-filename': checkPageFilename,
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { runTestCommand, type TestCommandOptions, type TestScope } from "./comma
|
|
|
16
16
|
export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, type AuthScaffoldOptions, type ScaffoldFile, type ScaffoldResult, type GenerateAuthOptions, } from "./generate.js";
|
|
17
17
|
export { writeUiKitScaffold, writeUiKitFiles, uiKitScaffoldFiles, authUiKitFiles, runGenerateUiKitCommand, type UiKitScaffoldOptions, type UiKitScaffoldFile, type UiKitScaffoldResult, type GenerateUiKitOptions, } from "./uikit.js";
|
|
18
18
|
export { runGenerateCommand, planScaffold, parseGenerateArgs, type GenerateType, type GenerateOptions, type GenerateResult, } from "./commands/g.js";
|
|
19
|
+
export { runAgentsDocsCommand, agentsDocsStatus, agentsDocsTemplates, type AgentsDocsCommandOptions, type AgentsDocStatus, type AgentsDocState, } from "./scaffold/agentsDocs.js";
|
|
19
20
|
export { runHubCommand, type HubCommandOptions } from "./hub.js";
|
|
20
21
|
export { runServeCommand, type ServeCommandOptions } from "./serve.js";
|
|
21
22
|
export { computeHealth, DEV_HEALTH_PATH, type DevHealthContext, type GaonHealth, } from "./dev/health.js";
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { runConsoleCommand } from "./commands/console.js";
|
|
|
22
22
|
import { runTestCommand } from "./commands/test.js";
|
|
23
23
|
import { runGenerateAuthCommand } from "./generate.js";
|
|
24
24
|
import { runGenerateUiKitCommand } from "./uikit.js";
|
|
25
|
+
import { runAgentsDocsCommand } from "./scaffold/agentsDocs.js";
|
|
25
26
|
import { runGenerateCommand } from "./commands/g.js";
|
|
26
27
|
import { runHubCommand } from "./hub.js";
|
|
27
28
|
import { runWorkCommand } from "./work.js";
|
|
@@ -43,6 +44,8 @@ export { runTestCommand } from "./commands/test.js";
|
|
|
43
44
|
export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, } from "./generate.js";
|
|
44
45
|
export { writeUiKitScaffold, writeUiKitFiles, uiKitScaffoldFiles, authUiKitFiles, runGenerateUiKitCommand, } from "./uikit.js";
|
|
45
46
|
export { runGenerateCommand, planScaffold, parseGenerateArgs, } from "./commands/g.js";
|
|
47
|
+
// 결정 449: AGENTS 문서 재동기 — 업그레이드 후 스테일 사본을 설치 템플릿으로.
|
|
48
|
+
export { runAgentsDocsCommand, agentsDocsStatus, agentsDocsTemplates, } from "./scaffold/agentsDocs.js";
|
|
46
49
|
export { runHubCommand } from "./hub.js";
|
|
47
50
|
export { runServeCommand } from "./serve.js";
|
|
48
51
|
// dev 전용 라이브 헬스(결정 69). serve(--dev)가 등록하고, 브라우저 e2e 층이
|
|
@@ -126,6 +129,7 @@ function renderHelp(version = VERSION) {
|
|
|
126
129
|
" gaon g job <Name> 비동기 잡 (domain/jobs · later/in/at)",
|
|
127
130
|
" gaon g app <name> 앱 스캐폴드 (apps/<name>/ · routes·controllers·pages·layouts)",
|
|
128
131
|
" gaon g channel <name> 실시간 채널 (정의 + 클라 컴포저블 · --instance = 파라미터화 채널 = 매치·스레드별 동적 방)",
|
|
132
|
+
" gaon g agents-docs AGENTS.md·agents/*.md 를 설치된 gaonjs 정본 템플릿으로 재동기 (업그레이드 후 · 멱등 · --check = 미리보기)",
|
|
129
133
|
" gaon g <type> --overwrite 기존 파일 덮어쓰기 · --app <이름> · --json",
|
|
130
134
|
" gaon mcp 내장 MCP 서버 · AI 도구 7종 (list_routes·get_schema·run_migration·run_tests·read_agent_doc·run_check·run_doctor · --http)",
|
|
131
135
|
" gaon hub 실시간 허브 프로세스 (프레즌스 권위·중계 · 리더 선출 HA)",
|
|
@@ -152,7 +156,7 @@ function renderHelp(version = VERSION) {
|
|
|
152
156
|
* 지정 없음(undefined) = 5 검사 모두 실행. 알 수 없는 이름은 무시(안전).
|
|
153
157
|
*/
|
|
154
158
|
export function parseDoctorChecks(argv) {
|
|
155
|
-
// 인정 집합은 doctor.ts 의 ALL_RULES(정본
|
|
159
|
+
// 인정 집합은 doctor.ts 의 ALL_RULES(정본 32종)를 단일 출처로 쓴다 — 과거
|
|
156
160
|
// 손유지 9종 리스트가 뒤처져 --check=seal-security 같은 16종이 조용히 무시되고
|
|
157
161
|
// 전체 검사로 되돌아가던 표류를 근본 차단한다(결정 168).
|
|
158
162
|
const isKnown = (s) => ALL_RULES.includes(s);
|
|
@@ -173,7 +177,7 @@ export function parseDoctorChecks(argv) {
|
|
|
173
177
|
}
|
|
174
178
|
// 결정 411: 모르는 이름은 여전히 무시하되(안전 방향 — 전체 검사로 넓어짐) **조용히**
|
|
175
179
|
// 넘기지 않는다. 오타 하나가 "그 검사만 돌렸다" 는 착각으로 이어지고, 전부 오타면
|
|
176
|
-
//
|
|
180
|
+
// 32종 전체가 돌아가 선택 실행 의도가 통째로 사라진다.
|
|
177
181
|
if (unknown.length > 0) {
|
|
178
182
|
process.stderr.write(` ! 알 수 없는 검사 이름 무시: ${unknown.join(", ")}\n` +
|
|
179
183
|
` → 지원 이름은 gaon doctor --json 의 rule 값 또는 gaon help 참고` +
|
|
@@ -493,7 +497,7 @@ export function runCli(argv, opts = {}) {
|
|
|
493
497
|
});
|
|
494
498
|
return;
|
|
495
499
|
}
|
|
496
|
-
// `gaon doctor` — 정적 검사(M9-E ·
|
|
500
|
+
// `gaon doctor` — 정적 검사(M9-E · 32 검사 · ALL_RULES 단일 출처). --check=<이름>[,<이름>...] 로
|
|
497
501
|
// 선택 실행, --json 은 자동화 파싱용.
|
|
498
502
|
// exit code (M9-E-Fix): fatal → 2(사용자 오류) / errors > 0 → 1 / 그 외 → 0.
|
|
499
503
|
if (argv[0] === "doctor") {
|
|
@@ -650,6 +654,15 @@ export function runCli(argv, opts = {}) {
|
|
|
650
654
|
process.exitCode = code;
|
|
651
655
|
return;
|
|
652
656
|
}
|
|
657
|
+
// 결정 449: 업그레이드 후 AGENTS 문서 재동기 — 이름 인자 없는 문서 제너레이터.
|
|
658
|
+
if (argv[1] === "agents-docs") {
|
|
659
|
+
const code = runAgentsDocsCommand({
|
|
660
|
+
check: argv.includes("--check"),
|
|
661
|
+
json: argv.includes("--json"),
|
|
662
|
+
});
|
|
663
|
+
process.exitCode = code;
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
653
666
|
const known = ["controller", "model", "page", "job", "app", "channel"];
|
|
654
667
|
const type = argv[1];
|
|
655
668
|
if (type && known.includes(type)) {
|
|
@@ -701,8 +714,8 @@ export function runCli(argv, opts = {}) {
|
|
|
701
714
|
return;
|
|
702
715
|
}
|
|
703
716
|
process.stderr.write(` ✗ 알 수 없는 제너레이터: ${argv[1] ?? "(없음)"}\n` +
|
|
704
|
-
` → 현재 지원: gaon g auth | ui-kit | controller | model | page | job | app | channel\n` +
|
|
705
|
-
` → 옵션: --app <이름> · --overwrite · --json · --instance (channel 전용)\n`);
|
|
717
|
+
` → 현재 지원: gaon g auth | ui-kit | controller | model | page | job | app | channel | agents-docs\n` +
|
|
718
|
+
` → 옵션: --app <이름> · --overwrite · --json · --instance (channel 전용) · --check (agents-docs 전용)\n`);
|
|
706
719
|
process.exitCode = 1;
|
|
707
720
|
return;
|
|
708
721
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** 동기 대상 문서 하나 — path 는 프로젝트 루트 기준 상대 경로(POSIX). */
|
|
2
|
+
export interface AgentsDocTemplate {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly contents: string;
|
|
5
|
+
}
|
|
6
|
+
/** 파일 하나의 동기 상태. */
|
|
7
|
+
export type AgentsDocState = 'current' | 'stale' | 'missing';
|
|
8
|
+
export interface AgentsDocStatus {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly state: AgentsDocState;
|
|
11
|
+
/** stale 일 때 사람용 요지(줄 수 변화). current·missing 은 undefined. */
|
|
12
|
+
readonly note?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 설치된 CLI 템플릿에서 동기 대상 문서 전수를 읽는다 — AGENTS.md +
|
|
16
|
+
* agents/*.md(템플릿 폴더 실측 · 카테고리가 늘면 자동 추종). 토큰 치환은
|
|
17
|
+
* 하지 않는다(이 문서들은 무토큰 원문 — 저장소 테스트가 고정).
|
|
18
|
+
*/
|
|
19
|
+
export declare function agentsDocsTemplates(): AgentsDocTemplate[];
|
|
20
|
+
/** 프로젝트 사본 ↔ 설치 템플릿의 파일별 상태(byte 비교 · 게이트와 동일 판정). */
|
|
21
|
+
export declare function agentsDocsStatus(cwd: string): AgentsDocStatus[];
|
|
22
|
+
export interface AgentsDocsCommandOptions {
|
|
23
|
+
readonly cwd?: string;
|
|
24
|
+
/** 미리보기 — 상태만 보고하고 쓰지 않는다. 스테일·누락이 있으면 exit 1. */
|
|
25
|
+
readonly check?: boolean;
|
|
26
|
+
readonly json?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** `gaon g agents-docs` 본체. 반환 = exit code. */
|
|
29
|
+
export declare function runAgentsDocsCommand(opts?: AgentsDocsCommandOptions): number;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @gaonjs/cli · `gaon g agents-docs` — AGENTS 문서 재동기 (결정 449)
|
|
2
|
+
//
|
|
3
|
+
// gaonjs 업그레이드 후 프로젝트의 `AGENTS.md`·`agents/*.md` 사본이 옛 템플릿
|
|
4
|
+
// 채로 남으면, AI 가 낡은 관례 문서를 읽고 새 표면(예: MemberLeft)을 모른 채
|
|
5
|
+
// 코드를 짠다 — rooms 샘플 2회차가 손 복사로 때운 실사용 갭. 이 명령이
|
|
6
|
+
// 설치된 CLI 의 정본 템플릿으로 사본을 덮어 재동기한다(멱등 · 이미 최신이면
|
|
7
|
+
// no-op).
|
|
8
|
+
//
|
|
9
|
+
// 동기 대상 = AGENTS.md + agents/*.md **만**이다. 이 문서들은 프레임웍 정본의
|
|
10
|
+
// 사본이고(저장소 게이트가 byte 동일성을 강제 · 결정 40) 스캐폴드 토큰도
|
|
11
|
+
// 없어({{TOKEN}} 금지 테스트) byte 비교·통째 덮기가 안전하다. CLAUDE.md 는
|
|
12
|
+
// 제외 — 프로젝트 이름이 치환되고 사용자가 프로젝트 규칙을 덧붙이는 소유
|
|
13
|
+
// 파일이라 덮으면 사용자 내용이 사라진다(결정 449 기각 대안 참조).
|
|
14
|
+
//
|
|
15
|
+
// 안전장치: `--check` 는 쓰지 않고 상태만 보고(스테일이 있으면 exit 1 —
|
|
16
|
+
// CI 에서 드리프트 감지용), 덮을 때는 파일별로 무엇이 바뀌는지 출력한다.
|
|
17
|
+
// doctor `agents-docs-stale` 검사가 같은 byte 비교로 스테일을 표면화한다.
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { templateDir } from '../templates/index.js';
|
|
21
|
+
/**
|
|
22
|
+
* 설치된 CLI 템플릿에서 동기 대상 문서 전수를 읽는다 — AGENTS.md +
|
|
23
|
+
* agents/*.md(템플릿 폴더 실측 · 카테고리가 늘면 자동 추종). 토큰 치환은
|
|
24
|
+
* 하지 않는다(이 문서들은 무토큰 원문 — 저장소 테스트가 고정).
|
|
25
|
+
*/
|
|
26
|
+
export function agentsDocsTemplates() {
|
|
27
|
+
const root = templateDir();
|
|
28
|
+
const out = [
|
|
29
|
+
{ path: 'AGENTS.md', contents: readFileSync(join(root, 'AGENTS.md.tpl'), 'utf8') },
|
|
30
|
+
];
|
|
31
|
+
const agentsDir = join(root, 'agents');
|
|
32
|
+
for (const f of readdirSync(agentsDir).sort()) {
|
|
33
|
+
if (!f.endsWith('.md.tpl'))
|
|
34
|
+
continue;
|
|
35
|
+
out.push({
|
|
36
|
+
path: `agents/${f.slice(0, -'.tpl'.length)}`,
|
|
37
|
+
contents: readFileSync(join(agentsDir, f), 'utf8'),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
const lineCount = (s) => s.split('\n').length;
|
|
43
|
+
/** 프로젝트 사본 ↔ 설치 템플릿의 파일별 상태(byte 비교 · 게이트와 동일 판정). */
|
|
44
|
+
export function agentsDocsStatus(cwd) {
|
|
45
|
+
return agentsDocsTemplates().map(({ path, contents }) => {
|
|
46
|
+
const abs = join(cwd, path);
|
|
47
|
+
if (!existsSync(abs))
|
|
48
|
+
return { path, state: 'missing' };
|
|
49
|
+
const current = readFileSync(abs, 'utf8');
|
|
50
|
+
if (current === contents)
|
|
51
|
+
return { path, state: 'current' };
|
|
52
|
+
return {
|
|
53
|
+
path,
|
|
54
|
+
state: 'stale',
|
|
55
|
+
note: `프로젝트 ${lineCount(current)}줄 ↔ 템플릿 ${lineCount(contents)}줄`,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** `gaon g agents-docs` 본체. 반환 = exit code. */
|
|
60
|
+
export function runAgentsDocsCommand(opts = {}) {
|
|
61
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
62
|
+
// 대상 프로젝트 판별 — AGENTS.md 조차 없는 폴더에 문서를 흩뿌리지 않는다.
|
|
63
|
+
// (agents/ 만 있는 변칙도 gaon 프로젝트로 본다 — 어느 쪽도 없으면 안내 후 종료.)
|
|
64
|
+
if (!existsSync(join(cwd, 'AGENTS.md')) && !existsSync(join(cwd, 'agents'))) {
|
|
65
|
+
process.stderr.write(` ✗ gaon g agents-docs: 여기는 gaon 프로젝트가 아닙니다(AGENTS.md·agents/ 없음).\n` +
|
|
66
|
+
` → 프로젝트 루트에서 실행하세요. 새 프로젝트는 gaon new <name> 이 문서까지 스캐폴드합니다.\n`);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
const statuses = agentsDocsStatus(cwd);
|
|
70
|
+
const stale = statuses.filter((s) => s.state === 'stale');
|
|
71
|
+
const missing = statuses.filter((s) => s.state === 'missing');
|
|
72
|
+
const outdated = [...stale, ...missing];
|
|
73
|
+
if (opts.check) {
|
|
74
|
+
if (opts.json) {
|
|
75
|
+
process.stdout.write(JSON.stringify({ check: true, wrote: [], files: statuses }, null, 2) + '\n');
|
|
76
|
+
return outdated.length > 0 ? 1 : 0;
|
|
77
|
+
}
|
|
78
|
+
if (outdated.length === 0) {
|
|
79
|
+
process.stdout.write(` ✓ agents 문서 ${statuses.length}개 모두 설치 템플릿과 동일(최신)입니다.\n`);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
for (const s of statuses) {
|
|
83
|
+
if (s.state === 'current')
|
|
84
|
+
continue;
|
|
85
|
+
process.stdout.write(s.state === 'missing'
|
|
86
|
+
? ` ! 누락: ${s.path} — 설치 템플릿에 있는데 프로젝트에 없습니다\n`
|
|
87
|
+
: ` ! 스테일: ${s.path} (${s.note})\n`);
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write(` → ${outdated.length}개가 설치 템플릿과 다릅니다. 재동기: gaon g agents-docs (이 문서들은 프레임웍 정본 사본 — 손댄 내용이 있다면 먼저 옮기세요)\n`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
const wrote = [];
|
|
93
|
+
for (const s of outdated) {
|
|
94
|
+
const tpl = agentsDocsTemplates().find((t) => t.path === s.path);
|
|
95
|
+
const abs = join(cwd, s.path);
|
|
96
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
97
|
+
writeFileSync(abs, tpl.contents);
|
|
98
|
+
wrote.push(s.path);
|
|
99
|
+
}
|
|
100
|
+
if (opts.json) {
|
|
101
|
+
process.stdout.write(JSON.stringify({ check: false, wrote, files: agentsDocsStatus(cwd) }, null, 2) + '\n');
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
if (wrote.length === 0) {
|
|
105
|
+
process.stdout.write(` ✓ agents 문서 ${statuses.length}개 모두 이미 최신 — 변경 없음(멱등).\n`);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
for (const s of outdated) {
|
|
109
|
+
process.stdout.write(s.state === 'missing'
|
|
110
|
+
? ` + 생성: ${s.path}\n`
|
|
111
|
+
: ` ~ 재동기: ${s.path} (${s.note})\n`);
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write(` ✓ ${wrote.length}개 문서를 설치 템플릿(정본)으로 재동기했습니다 — ${statuses.length - wrote.length}개는 이미 최신.\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
@@ -113,7 +113,7 @@ Gaon 의 제1 설계 목표는 **"AI 가 개발을 가장 잘하는 프레임웍
|
|
|
113
113
|
컬럼명 · 스키마 파일 ↔ 테이블 ↔ `tables.d.ts` 키 변환 규칙)은
|
|
114
114
|
`agents/data.md` "DB 네이밍" 표가 정본이다 — 먼저 읽는다.
|
|
115
115
|
|
|
116
|
-
### 2.2 `gaon doctor` 검사
|
|
116
|
+
### 2.2 `gaon doctor` 검사 32종
|
|
117
117
|
|
|
118
118
|
1. `response-mixing` — 한 액션 안 render/JSON/redirect 혼용 (E-3)
|
|
119
119
|
2. `n-plus-one` — include 미사용 · loop 안 관계 호출 (E-4)
|
|
@@ -145,7 +145,8 @@ Gaon 의 제1 설계 목표는 **"AI 가 개발을 가장 잘하는 프레임웍
|
|
|
145
145
|
28. `render-return` — 액션이 `this.render`/`this.redirect`/`this.json` 을 호출만 하고 `return` 하지 않음 = 응답이 버려져 조용히 204(백지) — `return this.render(...)` 로 고치라 (결정 340 · 경고)
|
|
146
146
|
29. `channel-collision` — 두 앱이 **같은 이름의 채널**을 각각 정의 = **에러**. 채널 이름은 전역이다(브로드캐스트 subject `gaon.chan.<이름>`·프레즌스 키에 앱 프리픽스 없음) — 한 앱의 broadcast 가 다른 앱 연결로 팬아웃되고 접속자 목록이 병합되며, 두 정의의 `authorize` 가 갈리면 공개 쪽 규칙으로 메시지가 샌다. 앱마다 이름을 분리하거나(클라이언트 `useChannel` 인자도 함께), 일부러 공유하는 채널이면 정의를 `shared/channels/<이름>.ts` 하나에 두고 각 앱 채널 파일에서 재수출하라(재수출은 통과 · 정의 하나 = 인가 규칙 하나) — 잡·리스너의 동명 등록 throw(결정 271)와 같은 계열의 정적 검사 (`agents/realtime.md` §2)
|
|
147
147
|
30. `channel-instance-authorize` — `instance: true` 채널(파라미터화 채널 · 결정 440)에 `authorize` 가 없음 = **경고**. 인스턴스 채널은 임의 문자열 키로 무한 실행 인스턴스(`/gaon/ws/<이름>/<인스턴스>`)가 열리므로, authorize 가 없으면 누구나 아무 인스턴스에나 입장한다. 매치·스레드처럼 참가자가 정해진 채널이면 `authorize(ctx)` 에서 `ctx.instance` 로 입장을 판정하라 — 공개 관전형(누구나 입장)이 의도면 무시해도 된다(재수출 정의는 대상 모듈을 따라가 판정 · `agents/realtime.md` §2.7)
|
|
148
|
-
31. `
|
|
148
|
+
31. `agents-docs-stale` — 이 문서(`AGENTS.md`)·`agents/*.md` 사본이 **설치된 gaonjs 템플릿(정본)과 다름** = **경고**(byte 비교). gaonjs 업그레이드 후 관례 문서가 옛 채로 남으면 AI 가 낡은 관례·없는 표면으로 코드를 짠다 — `gaon g agents-docs` 로 재동기하라(미리보기 `--check` · 멱등 · 사본에 직접 적은 내용은 프로젝트 소유 문서(CLAUDE.md)로 옮긴 뒤 — 이 문서들은 프레임웍 정본 사본이다) (결정 449)
|
|
149
|
+
32. `dotenv-node-env` — 공유 `.env`(·`.env.local`·`.env.example`)에 `NODE_ENV` 가 설정됨 = **경고**. **모드는 명령이 정한다** — `gaon dev` = development · `gaon serve` = production(결정 430). 이 파일들은 개발·운영이 함께 읽으므로 값을 박으면 모드가 양쪽으로 샌다: `production` 이면 `gaon dev` 가 쿠키 Secure·dev 플레이스홀더 secret 거부로 죽고, `development` 면 운영 `gaon serve` 에서 프로덕션 안전장치(플레이스홀더 secret 거부·쿠키 Secure·락 in-memory 폴백 차단)가 통째로 꺼진다(**부팅은 green, 보안만 꺼짐**). `.env` 에서 그 줄을 지우고, 모드별 값이 필요하면 `.env.development`/`.env.production` 오버레이에, 일회성이면 명령 앞에 붙인다(`NODE_ENV=production gaon serve`) — 모드별 오버레이 파일은 검사 대상이 아니다 (결정 430)
|
|
149
150
|
|
|
150
151
|
## 3. 로직 배치 One Way 판단표
|
|
151
152
|
|
|
@@ -206,7 +207,7 @@ Gaon 의 제1 설계 목표는 **"AI 가 개발을 가장 잘하는 프레임웍
|
|
|
206
207
|
```bash
|
|
207
208
|
gaon check # .gaon 재생성 → typecheck + vue-tsc + build + doctor (기본 포함 · --no-doctor 로 뺌 · 결정 157)
|
|
208
209
|
gaon test # vitest — DB·NATS 는 실 인프라 (agents/testing.md)
|
|
209
|
-
gaon doctor # 정적 검사
|
|
210
|
+
gaon doctor # 정적 검사 32종 (§2.2)
|
|
210
211
|
```
|
|
211
212
|
|
|
212
213
|
### 4.1 CLI 명령 (전 명령 `--json` 지원)
|
|
@@ -218,6 +219,7 @@ gaon doctor # 정적 검사 31종 (§2.2)
|
|
|
218
219
|
| `gaon serve` / `work` / `hub` | 운영 프로세스 3종 (웹 · 워커 · 실시간 허브) — **감시 없음** · 배포 배치용(`gaon dev` 가 개발 중엔 셋을 내장 기동) · 웹은 `PORT`, 허브는 `GAON_HUB_PORT` |
|
|
219
220
|
| `gaon g <type> <name>` | 스캐폴드: `auth`·`ui-kit`·`controller`·`model`·`page`·`job`·`app`·`channel` · `g auth --app <앱> --public` = 비-web 앱에 공개 회원가입(`/registration/new`)을 opt-in(기본: web=공개·비-web=역할 게이트 · 결정 155) · `g channel <이름> [--instance]` = 실시간 채널(정의 + 클라 컴포저블 · `--instance` = 파라미터화 채널 = 매치·스레드별 동적 방 · 결정 440·442 · `agents/realtime.md` §2.7) |
|
|
220
221
|
| `gaon g auth --jwt --app <앱>` | **API(JWT) 앱 변형** — 이 명령 **하나**가 앱 폴더째 만든다(토큰 컨트롤러 3종 + `strategy:'jwt'` app.config + `<APP>_JWT_SECRET` 시드 · 페이지·UI 킷·회원가입 없음). `gaon g app` 을 먼저 돌리지 않는다 — 이미 있는 app.config 는 자동 배선을 못 해 exit 1 이고, 쓰지 않는 Vue 프론트가 남는다. web 앱은 세션이 정본이라 `--jwt` 불가 (결정 337·338) |
|
|
222
|
+
| `gaon g agents-docs` | **gaonjs 업그레이드 후 이 문서들(`AGENTS.md`·`agents/*.md`)을 설치된 정본 템플릿으로 재동기** — 멱등(이미 최신 = no-op) · `--check` = 쓰지 않고 스테일 목록만(스테일 있으면 exit 1 · CI 드리프트 감지) · `--json`. doctor `agents-docs-stale`(§2.2) 이 스테일을 경고로 표면화한다 (결정 449) |
|
|
221
223
|
| `gaon gen` / `build` | `gen` = `.gaon` 타입 브리지 + api() 런타임 매니페스트만 재생성(서버·검사 없이) · `build` = 멀티 앱 프론트 프로덕션 빌드(`gaon gen` + `apps/*` 순회 · 앱별 `dist/<앱>`·base=`/<앱>/`) · 결정 127·146 |
|
|
222
224
|
| `gaon db <sub>` | `diff`·`migrate`(`down`)·`status`·`reset`·`seed` (`agents/data.md` §10) |
|
|
223
225
|
| `gaon check` / `test` / `doctor` | 검증 루프 |
|
|
@@ -86,7 +86,7 @@ Gaon 프레임웍 문서: https://gaonjs.dev
|
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
gaon check # .gaon 재생성 → 타입검사+build+doctor (CI 한 번에 · --no-doctor 로 doctor 뺌)
|
|
89
|
-
gaon doctor # 정적 검사
|
|
89
|
+
gaon doctor # 정적 검사 32종 (상세 AGENTS §2.2)
|
|
90
90
|
npm test # Vitest · DB 테스트는 실 Docker 필수 (§9)
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -113,6 +113,7 @@ gaon g model <Name> # 스키마 + 모델 스캐폴드 (E-4)
|
|
|
113
113
|
gaon g page <Path>/<Name> # Vue 페이지 (Inertia SPA · pageProps)
|
|
114
114
|
gaon g job <Name> # 비동기 잡
|
|
115
115
|
gaon g auth # 인증 스캐폴드 (세션 + JWT 옵션)
|
|
116
|
+
gaon g agents-docs # gaonjs 업그레이드 후 AGENTS.md·agents/*.md 를 정본 템플릿으로 재동기 (--check 미리보기)
|
|
116
117
|
gaon db diff # 스키마 ↔ DB 차이 (적용 X)
|
|
117
118
|
gaon db migrate # 실제 적용 + _gaon_migrations 이력
|
|
118
119
|
gaon db seed # domain/seed.ts 실행
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "Gaon CLI — 스캐폴딩·제너레이터·마이그레이션·dev/serve/work/hub·doctor·check (bin: gaon)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"typescript": "^5.9.0",
|
|
34
34
|
"vite": "^7.0.0",
|
|
35
35
|
"@gaonjs/async": "0.22.0",
|
|
36
|
-
"@gaonjs/core": "0.3.0",
|
|
37
36
|
"@gaonjs/config": "0.25.6",
|
|
37
|
+
"@gaonjs/mail": "0.5.1",
|
|
38
38
|
"@gaonjs/i18n": "0.3.1",
|
|
39
39
|
"@gaonjs/data": "0.25.3",
|
|
40
|
-
"@gaonjs/
|
|
40
|
+
"@gaonjs/core": "0.3.0",
|
|
41
41
|
"@gaonjs/web": "0.31.2"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|