@dusky-bluehour/agent-service 0.6.6 → 0.6.7
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.md +20 -28
- package/antigravity/README.md +7 -0
- package/antigravity/commands/definitions/cmd-dev-be-api.md +43 -0
- package/antigravity/commands/definitions/cmd-dev-fe-hook-separate.md +43 -0
- package/antigravity/commands/definitions/cmd-dev-fe-ui-componentize.md +43 -0
- package/antigravity/commands/definitions/cmd-dev-perf-optimize.md +43 -0
- package/antigravity/commands/definitions/cmd-dev-sequential-autorun.md +43 -0
- package/antigravity/commands/definitions/cmd-doc-handoff.md +44 -0
- package/antigravity/commands/definitions/cmd-improve-techdebt.md +43 -0
- package/antigravity/commands/definitions/cmd-incident-triage.md +43 -0
- package/antigravity/commands/definitions/cmd-ops-ci-cd-gate.md +43 -0
- package/antigravity/commands/definitions/cmd-ops-deploy.md +43 -0
- package/antigravity/commands/definitions/cmd-ops-monitoring.md +43 -0
- package/antigravity/commands/definitions/cmd-plan-arch-decision.md +43 -0
- package/antigravity/commands/definitions/cmd-plan-implementation-bootstrap.md +43 -0
- package/antigravity/commands/definitions/cmd-plan-prd-details.md +43 -0
- package/antigravity/commands/definitions/cmd-plan-prd-master.md +44 -0
- package/antigravity/commands/definitions/cmd-plan-req-lock.md +44 -0
- package/antigravity/commands/definitions/cmd-review-code.md +43 -0
- package/antigravity/commands/definitions/cmd-sec-dependency-audit.md +43 -0
- package/antigravity/commands/definitions/cmd-sec-threat-model.md +43 -0
- package/antigravity/commands/definitions/cmd-test-unit-integration.md +43 -0
- package/antigravity/instructions/WORKSPACE-RULES.template.md +34 -0
- package/antigravity/settings/editor-policy.json +193 -0
- package/catalog/tool-catalog.ko.json +77 -16
- package/claude-code/README.md +27 -0
- package/claude-code/commands/native/cmd-dev-be-api.md +51 -0
- package/claude-code/commands/native/cmd-dev-fe-hook-separate.md +51 -0
- package/claude-code/commands/native/cmd-dev-fe-ui-componentize.md +51 -0
- package/claude-code/commands/native/cmd-dev-perf-optimize.md +51 -0
- package/claude-code/commands/native/cmd-dev-sequential-autorun.md +51 -0
- package/claude-code/commands/native/cmd-doc-handoff.md +52 -0
- package/claude-code/commands/native/cmd-improve-techdebt.md +51 -0
- package/claude-code/commands/native/cmd-incident-triage.md +51 -0
- package/claude-code/commands/native/cmd-ops-ci-cd-gate.md +51 -0
- package/claude-code/commands/native/cmd-ops-deploy.md +51 -0
- package/claude-code/commands/native/cmd-ops-monitoring.md +51 -0
- package/claude-code/commands/native/cmd-plan-arch-decision.md +51 -0
- package/claude-code/commands/native/cmd-plan-implementation-bootstrap.md +51 -0
- package/claude-code/commands/native/cmd-plan-prd-details.md +51 -0
- package/claude-code/commands/native/cmd-plan-prd-master.md +52 -0
- package/claude-code/commands/native/cmd-plan-req-lock.md +52 -0
- package/claude-code/commands/native/cmd-review-code.md +51 -0
- package/claude-code/commands/native/cmd-sec-dependency-audit.md +51 -0
- package/claude-code/commands/native/cmd-sec-threat-model.md +51 -0
- package/claude-code/commands/native/cmd-test-unit-integration.md +51 -0
- package/claude-code/instructions/CLAUDE.template.md +42 -0
- package/claude-code/settings/settings.json +183 -0
- package/claude-code/settings/settings.local.json +10 -0
- package/codex/README.md +13 -1
- package/codex/instructions/AGENTS.permissions.generated.md +121 -0
- package/codex/instructions/AGENTS.template.md +18 -3
- package/codex/settings/runtime-policy.json +188 -0
- package/codex/skills/cmd-dev-be-api/SKILL.md +43 -0
- package/codex/skills/cmd-dev-be-api/agents/openai.yaml +4 -0
- package/codex/skills/cmd-dev-fe-hook-separate/SKILL.md +43 -0
- package/codex/skills/cmd-dev-fe-hook-separate/agents/openai.yaml +4 -0
- package/codex/skills/cmd-dev-fe-ui-componentize/SKILL.md +43 -0
- package/codex/skills/cmd-dev-fe-ui-componentize/agents/openai.yaml +4 -0
- package/codex/skills/cmd-dev-perf-optimize/SKILL.md +43 -0
- package/codex/skills/cmd-dev-perf-optimize/agents/openai.yaml +4 -0
- package/codex/skills/cmd-dev-sequential-autorun/SKILL.md +43 -0
- package/codex/skills/cmd-dev-sequential-autorun/agents/openai.yaml +4 -0
- package/codex/skills/cmd-doc-handoff/SKILL.md +43 -0
- package/codex/skills/cmd-doc-handoff/agents/openai.yaml +4 -0
- package/codex/skills/cmd-improve-techdebt/SKILL.md +43 -0
- package/codex/skills/cmd-improve-techdebt/agents/openai.yaml +4 -0
- package/codex/skills/cmd-incident-triage/SKILL.md +43 -0
- package/codex/skills/cmd-incident-triage/agents/openai.yaml +4 -0
- package/codex/skills/cmd-ops-ci-cd-gate/SKILL.md +43 -0
- package/codex/skills/cmd-ops-ci-cd-gate/agents/openai.yaml +4 -0
- package/codex/skills/cmd-ops-deploy/SKILL.md +43 -0
- package/codex/skills/cmd-ops-deploy/agents/openai.yaml +4 -0
- package/codex/skills/cmd-ops-monitoring/SKILL.md +43 -0
- package/codex/skills/cmd-ops-monitoring/agents/openai.yaml +4 -0
- package/codex/skills/cmd-plan-arch-decision/SKILL.md +43 -0
- package/codex/skills/cmd-plan-arch-decision/agents/openai.yaml +4 -0
- package/codex/skills/cmd-plan-implementation-bootstrap/SKILL.md +43 -0
- package/codex/skills/cmd-plan-implementation-bootstrap/agents/openai.yaml +4 -0
- package/codex/skills/cmd-plan-prd-details/SKILL.md +43 -0
- package/codex/skills/cmd-plan-prd-details/agents/openai.yaml +4 -0
- package/codex/skills/cmd-plan-prd-master/SKILL.md +44 -0
- package/codex/skills/cmd-plan-prd-master/agents/openai.yaml +4 -0
- package/codex/skills/cmd-plan-req-lock/SKILL.md +44 -0
- package/codex/skills/cmd-plan-req-lock/agents/openai.yaml +4 -0
- package/codex/skills/cmd-review-code/SKILL.md +43 -0
- package/codex/skills/cmd-review-code/agents/openai.yaml +4 -0
- package/codex/skills/cmd-sec-dependency-audit/SKILL.md +43 -0
- package/codex/skills/cmd-sec-dependency-audit/agents/openai.yaml +4 -0
- package/codex/skills/cmd-sec-threat-model/SKILL.md +43 -0
- package/codex/skills/cmd-sec-threat-model/agents/openai.yaml +4 -0
- package/codex/skills/cmd-test-unit-integration/SKILL.md +43 -0
- package/codex/skills/cmd-test-unit-integration/agents/openai.yaml +4 -0
- package/common/settings/security-policy.json +221 -0
- package/package.json +1 -1
- package/scripts/generate-from-common.mjs +489 -4
- package/scripts/init.mjs +285 -36
- package/scripts/validate.mjs +208 -9
package/scripts/init.mjs
CHANGED
|
@@ -16,6 +16,21 @@ const CLI_NAME = 'tri-agent-manager';
|
|
|
16
16
|
const STATE_DIR_NAME = '.tri-agent-manager';
|
|
17
17
|
const LEGACY_STATE_DIR_NAME = '.tri-agent-os';
|
|
18
18
|
const GUIDE_FILE_NAME = 'USAGE.ko.md';
|
|
19
|
+
const PROJECT_RULE_MODE_VALUES = new Set(['always', 'if-instructions', 'if-present', 'never']);
|
|
20
|
+
const PROJECT_RULE_BOOTSTRAP = {
|
|
21
|
+
'claude-code': {
|
|
22
|
+
sourceRelativePath: path.join('claude-code', 'instructions', 'CLAUDE.template.md'),
|
|
23
|
+
destinationRelativePath: 'CLAUDE.md'
|
|
24
|
+
},
|
|
25
|
+
codex: {
|
|
26
|
+
sourceRelativePath: path.join('codex', 'instructions', 'AGENTS.template.md'),
|
|
27
|
+
destinationRelativePath: 'AGENTS.md'
|
|
28
|
+
},
|
|
29
|
+
antigravity: {
|
|
30
|
+
sourceRelativePath: path.join('antigravity', 'instructions', 'WORKSPACE-RULES.template.md'),
|
|
31
|
+
destinationRelativePath: '{installRoot}/rules/workspace-core-rules.md'
|
|
32
|
+
}
|
|
33
|
+
};
|
|
19
34
|
|
|
20
35
|
const HELP_TEXT = [
|
|
21
36
|
`${CLI_NAME} 사용법`,
|
|
@@ -24,8 +39,8 @@ const HELP_TEXT = [
|
|
|
24
39
|
` ${CLI_NAME} list`,
|
|
25
40
|
` ${CLI_NAME} setup (install + interactive 별칭)`,
|
|
26
41
|
` ${CLI_NAME} wizard (install + interactive 별칭)`,
|
|
27
|
-
` ${CLI_NAME} install [--preset <id>] [--tool <ids>] [--components <ids>] [--target <path>] [--force] [--dry-run] [--yes]`,
|
|
28
|
-
` ${CLI_NAME} update [--preset <id>] [--tool <ids>] [--components <ids>] [--target <path>] [--dry-run] [--yes]`,
|
|
42
|
+
` ${CLI_NAME} install [--preset <id>] [--tool <ids>] [--components <ids>] [--target <path>] [--install-root <map>] [--project-rules <mode>] [--force] [--dry-run] [--yes]`,
|
|
43
|
+
` ${CLI_NAME} update [--preset <id>] [--tool <ids>] [--components <ids>] [--target <path>] [--install-root <map>] [--project-rules <mode>] [--dry-run] [--yes]`,
|
|
29
44
|
` ${CLI_NAME} init (install의 별칭)`,
|
|
30
45
|
'',
|
|
31
46
|
'옵션:',
|
|
@@ -34,6 +49,7 @@ const HELP_TEXT = [
|
|
|
34
49
|
' --components 구성요소 선택 (예: skills,workflows,commands) / all',
|
|
35
50
|
' --target 설치 경로 (기본: .)',
|
|
36
51
|
' --install-root 도구별 설치 루트 오버라이드 (예: codex=.codex,antigravity=.agent)',
|
|
52
|
+
' --project-rules 프로젝트 규칙 파일 정책 (always|if-instructions|if-present|never)',
|
|
37
53
|
' --force install 시 기존 파일 덮어쓰기',
|
|
38
54
|
' --dry-run 복사하지 않고 작업 계획만 출력',
|
|
39
55
|
' --yes 확인 프롬프트 생략',
|
|
@@ -41,11 +57,10 @@ const HELP_TEXT = [
|
|
|
41
57
|
' --non-interactive 비대화형 모드 강제',
|
|
42
58
|
' (중복 선택 입력은 자동으로 1회로 정리됨)',
|
|
43
59
|
'',
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
' pnpm dlx --package=@dusky-bluehour/agent-service@latest tri-agent-manager list'
|
|
60
|
+
'npx 예시:',
|
|
61
|
+
' npx --yes --package @dusky-bluehour/agent-service tri-agent-manager --interactive',
|
|
62
|
+
' npx --yes --package @dusky-bluehour/agent-service tri-agent-manager update --interactive',
|
|
63
|
+
' npx --yes --package @dusky-bluehour/agent-service tri-agent-manager list'
|
|
49
64
|
].join('\n');
|
|
50
65
|
|
|
51
66
|
function parseArgs(argv) {
|
|
@@ -73,6 +88,7 @@ function parseArgs(argv) {
|
|
|
73
88
|
componentFlag: getFlag('--components'),
|
|
74
89
|
targetFlag: getFlag('--target') ?? '.',
|
|
75
90
|
installRootFlag: getFlag('--install-root'),
|
|
91
|
+
projectRulesFlag: getFlag('--project-rules'),
|
|
76
92
|
force: hasFlag('--force'),
|
|
77
93
|
dryRun: hasFlag('--dry-run'),
|
|
78
94
|
yes: hasFlag('--yes'),
|
|
@@ -207,6 +223,15 @@ function getComponentInstallPath(component) {
|
|
|
207
223
|
return installPath || component.path;
|
|
208
224
|
}
|
|
209
225
|
|
|
226
|
+
function getComponentDescription(toolId, component) {
|
|
227
|
+
const base = component?.description ?? '';
|
|
228
|
+
if (component?.id !== 'instructions' || !PROJECT_RULE_BOOTSTRAP[toolId]) {
|
|
229
|
+
return base;
|
|
230
|
+
}
|
|
231
|
+
const suffix = '규칙 파일 정책: always | if-instructions | if-present | never';
|
|
232
|
+
return base ? `${base} (${suffix})` : suffix;
|
|
233
|
+
}
|
|
234
|
+
|
|
210
235
|
function normalizeInstallRoot(rawRoot, toolId) {
|
|
211
236
|
const trimmed = String(rawRoot ?? '').trim();
|
|
212
237
|
if (!trimmed) {
|
|
@@ -227,6 +252,57 @@ function normalizeInstallRoot(rawRoot, toolId) {
|
|
|
227
252
|
return normalized.replace(/\/+$/, '');
|
|
228
253
|
}
|
|
229
254
|
|
|
255
|
+
function normalizeProjectRulesMode(rawMode) {
|
|
256
|
+
const mode = String(rawMode ?? '')
|
|
257
|
+
.trim()
|
|
258
|
+
.toLowerCase();
|
|
259
|
+
|
|
260
|
+
if (!mode) {
|
|
261
|
+
return 'always';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const alias = {
|
|
265
|
+
on: 'always',
|
|
266
|
+
all: 'always',
|
|
267
|
+
instructions: 'if-instructions',
|
|
268
|
+
selected: 'if-instructions',
|
|
269
|
+
present: 'if-present',
|
|
270
|
+
existing: 'if-present',
|
|
271
|
+
off: 'never',
|
|
272
|
+
none: 'never'
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const normalized = alias[mode] ?? mode;
|
|
276
|
+
if (!PROJECT_RULE_MODE_VALUES.has(normalized)) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`--project-rules 값이 올바르지 않습니다: ${rawMode} (always|if-instructions|if-present|never)`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return normalized;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function describeProjectRulesMode(mode) {
|
|
286
|
+
switch (mode) {
|
|
287
|
+
case 'always':
|
|
288
|
+
return '항상 생성/업데이트';
|
|
289
|
+
case 'if-instructions':
|
|
290
|
+
return '`instructions` 구성요소를 선택한 경우에만 생성/업데이트';
|
|
291
|
+
case 'if-present':
|
|
292
|
+
return '대상 규칙 파일이 이미 존재할 때만 업데이트';
|
|
293
|
+
case 'never':
|
|
294
|
+
return '생성/업데이트하지 않음';
|
|
295
|
+
default:
|
|
296
|
+
return mode;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveProjectRuleDestination(toolId, installRoot) {
|
|
301
|
+
const meta = PROJECT_RULE_BOOTSTRAP[toolId];
|
|
302
|
+
if (!meta) return null;
|
|
303
|
+
return meta.destinationRelativePath.replace('{installRoot}', installRoot || '');
|
|
304
|
+
}
|
|
305
|
+
|
|
230
306
|
function parseInstallRootFlag(installRootFlag, catalog) {
|
|
231
307
|
if (!installRootFlag) {
|
|
232
308
|
return {};
|
|
@@ -399,11 +475,7 @@ async function loadToolWorkflowSummary(tool, selection) {
|
|
|
399
475
|
function getToolExecutionGuide(tool, installRoot) {
|
|
400
476
|
if (tool.id === 'codex') {
|
|
401
477
|
return [
|
|
402
|
-
|
|
403
|
-
installRoot,
|
|
404
|
-
'instructions',
|
|
405
|
-
'AGENTS.template.md'
|
|
406
|
-
)}\`를 기반으로 팀 규칙을 확정합니다.`,
|
|
478
|
+
'프로젝트 루트의 `AGENTS.md`(자동 생성됨)를 열고 팀 규칙으로 보완합니다.',
|
|
407
479
|
`\`${path.join(
|
|
408
480
|
installRoot,
|
|
409
481
|
'workflows',
|
|
@@ -415,6 +487,7 @@ function getToolExecutionGuide(tool, installRoot) {
|
|
|
415
487
|
|
|
416
488
|
if (tool.id === 'claude-code') {
|
|
417
489
|
return [
|
|
490
|
+
'프로젝트 루트의 `CLAUDE.md`(자동 생성됨)를 열고 팀 컨텍스트를 채웁니다.',
|
|
418
491
|
`\`${path.join(
|
|
419
492
|
installRoot,
|
|
420
493
|
'workflows',
|
|
@@ -430,6 +503,11 @@ function getToolExecutionGuide(tool, installRoot) {
|
|
|
430
503
|
|
|
431
504
|
if (tool.id === 'antigravity') {
|
|
432
505
|
return [
|
|
506
|
+
`\`${path.join(
|
|
507
|
+
installRoot,
|
|
508
|
+
'rules',
|
|
509
|
+
'workspace-core-rules.md'
|
|
510
|
+
)}\`(자동 생성됨)를 열고 운영 규칙을 확정합니다.`,
|
|
433
511
|
`\`${path.join(
|
|
434
512
|
installRoot,
|
|
435
513
|
'workflows',
|
|
@@ -454,6 +532,7 @@ function getToolExecutionGuide(tool, installRoot) {
|
|
|
454
532
|
function getToolInteractionTips(tool, installRoot) {
|
|
455
533
|
if (tool.id === 'codex') {
|
|
456
534
|
return [
|
|
535
|
+
'프로젝트 루트의 `AGENTS.md`를 팀 규칙 단일 진입점으로 유지하세요.',
|
|
457
536
|
'Codex 대화 입력창에서 `$`를 누르면 사용 가능한 스킬 목록을 바로 열 수 있습니다.',
|
|
458
537
|
`프로젝트 루트에서 스킬 파일을 직접 확인하려면 \`ls ${path.join(
|
|
459
538
|
installRoot,
|
|
@@ -469,6 +548,7 @@ function getToolInteractionTips(tool, installRoot) {
|
|
|
469
548
|
|
|
470
549
|
if (tool.id === 'claude-code') {
|
|
471
550
|
return [
|
|
551
|
+
'프로젝트 루트 `CLAUDE.md`를 최신 상태로 유지하면 세션마다 동일한 기준으로 동작합니다.',
|
|
472
552
|
`서브에이전트 이름은 \`${path.join(installRoot, 'agents')}\` 경로의 파일명 기준입니다.`,
|
|
473
553
|
`프로젝트 루트에서 \`ls ${path.join(
|
|
474
554
|
installRoot,
|
|
@@ -480,6 +560,11 @@ function getToolInteractionTips(tool, installRoot) {
|
|
|
480
560
|
|
|
481
561
|
if (tool.id === 'antigravity') {
|
|
482
562
|
return [
|
|
563
|
+
`\`${path.join(
|
|
564
|
+
installRoot,
|
|
565
|
+
'rules',
|
|
566
|
+
'workspace-core-rules.md'
|
|
567
|
+
)}\`를 Manager 단계의 승인 규칙 문서로 사용하세요.`,
|
|
483
568
|
`워크플로우 선택은 \`${path.join(
|
|
484
569
|
installRoot,
|
|
485
570
|
'workflows',
|
|
@@ -520,6 +605,11 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
|
|
|
520
605
|
lines.push(`- 생성 시각: ${new Date().toISOString()}`);
|
|
521
606
|
lines.push(`- 모드: ${mode}`);
|
|
522
607
|
lines.push(`- 프리셋: ${selection.presetId ?? '수동/없음'}`);
|
|
608
|
+
lines.push(
|
|
609
|
+
`- 프로젝트 규칙 파일 정책: ${selection.projectRulesMode} (${describeProjectRulesMode(
|
|
610
|
+
selection.projectRulesMode
|
|
611
|
+
)})`
|
|
612
|
+
);
|
|
523
613
|
lines.push(`- 대상 경로: ${targetDir}`);
|
|
524
614
|
lines.push('');
|
|
525
615
|
lines.push('이 문서는 현재 설치 선택값 기준으로 생성되었습니다.');
|
|
@@ -538,6 +628,10 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
|
|
|
538
628
|
lines.push(`## ${tool.title} (${tool.id})`);
|
|
539
629
|
lines.push('');
|
|
540
630
|
lines.push(`- 설치 루트: \`${path.join(targetDir, installRoot)}\``);
|
|
631
|
+
const projectRuleDestination = resolveProjectRuleDestination(tool.id, installRoot);
|
|
632
|
+
if (projectRuleDestination) {
|
|
633
|
+
lines.push(`- 프로젝트 규칙 파일: \`${path.join(targetDir, projectRuleDestination)}\``);
|
|
634
|
+
}
|
|
541
635
|
if (tool.install_root_basis) {
|
|
542
636
|
lines.push(`- 경로 기준: ${tool.install_root_basis}`);
|
|
543
637
|
}
|
|
@@ -654,7 +748,7 @@ function clearTuiScreen() {
|
|
|
654
748
|
}
|
|
655
749
|
|
|
656
750
|
function formatTuiLine(prefix, label, selected = false) {
|
|
657
|
-
const marker = selected ? '
|
|
751
|
+
const marker = selected ? '🟢' : '⚪';
|
|
658
752
|
return `${prefix} ${marker} ${label}`;
|
|
659
753
|
}
|
|
660
754
|
|
|
@@ -700,7 +794,7 @@ async function runSingleSelectMenu({ title, help, options, defaultIndex = 0 }) {
|
|
|
700
794
|
while (true) {
|
|
701
795
|
renderTuiHeader(title, help);
|
|
702
796
|
options.forEach((option, index) => {
|
|
703
|
-
const pointer = index === cursor ? '
|
|
797
|
+
const pointer = index === cursor ? '◉' : '○';
|
|
704
798
|
output.write(`${pointer} ${option.label}\n`);
|
|
705
799
|
if (option.description) {
|
|
706
800
|
output.write(` ${option.description}\n`);
|
|
@@ -739,8 +833,8 @@ async function runMultiSelectMenu({
|
|
|
739
833
|
const selected = new Set(preselectedIds);
|
|
740
834
|
const optionRows = [
|
|
741
835
|
...choices.map((choice) => ({ kind: 'choice', ...choice })),
|
|
742
|
-
{ kind: 'action', action: 'toggle-all', label: '
|
|
743
|
-
{ kind: 'action', action: 'done', label: '
|
|
836
|
+
{ kind: 'action', action: 'toggle-all', label: '🟣 전체 선택/해제' },
|
|
837
|
+
{ kind: 'action', action: 'done', label: '🔵 선택 완료' }
|
|
744
838
|
];
|
|
745
839
|
let cursor = 0;
|
|
746
840
|
let notice = '';
|
|
@@ -754,7 +848,7 @@ async function runMultiSelectMenu({
|
|
|
754
848
|
output.write('\n');
|
|
755
849
|
|
|
756
850
|
optionRows.forEach((row, index) => {
|
|
757
|
-
const pointer = index === cursor ? '
|
|
851
|
+
const pointer = index === cursor ? '◉' : '○';
|
|
758
852
|
if (row.kind === 'choice') {
|
|
759
853
|
output.write(`${formatTuiLine(pointer, row.label, selected.has(row.id))}\n`);
|
|
760
854
|
if (row.description) {
|
|
@@ -823,7 +917,8 @@ async function promptInteractiveTui({
|
|
|
823
917
|
mode,
|
|
824
918
|
defaultTarget,
|
|
825
919
|
presetFlag,
|
|
826
|
-
installRootOverridesFromFlag
|
|
920
|
+
installRootOverridesFromFlag,
|
|
921
|
+
projectRulesModeFromFlag
|
|
827
922
|
}) {
|
|
828
923
|
readline.emitKeypressEvents(input);
|
|
829
924
|
const wasRawMode = Boolean(input.isRaw);
|
|
@@ -856,6 +951,9 @@ async function promptInteractiveTui({
|
|
|
856
951
|
|
|
857
952
|
const targetDir = path.resolve(process.cwd(), targetInput);
|
|
858
953
|
const state = await readState(targetDir);
|
|
954
|
+
const defaultProjectRulesMode = normalizeProjectRulesMode(
|
|
955
|
+
projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
|
|
956
|
+
);
|
|
859
957
|
const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
|
|
860
958
|
|
|
861
959
|
const defaultPresetId =
|
|
@@ -938,7 +1036,7 @@ async function promptInteractiveTui({
|
|
|
938
1036
|
choices: tool.components.map((component) => ({
|
|
939
1037
|
id: component.id,
|
|
940
1038
|
label: `${component.title} (${component.id})`,
|
|
941
|
-
description: component
|
|
1039
|
+
description: getComponentDescription(tool.id, component)
|
|
942
1040
|
})),
|
|
943
1041
|
preselectedIds: defaultComponents,
|
|
944
1042
|
minSelected: 1
|
|
@@ -953,12 +1051,47 @@ async function promptInteractiveTui({
|
|
|
953
1051
|
cliOverrides: installRootOverridesFromFlag
|
|
954
1052
|
});
|
|
955
1053
|
|
|
1054
|
+
let projectRulesMode = defaultProjectRulesMode;
|
|
1055
|
+
if (!projectRulesModeFromFlag) {
|
|
1056
|
+
const modeOption = await runSingleSelectMenu({
|
|
1057
|
+
title: '🧭 프로젝트 규칙 파일 정책',
|
|
1058
|
+
help: '조작: 위/아래/좌/우 이동, Enter 선택, q 종료',
|
|
1059
|
+
options: [
|
|
1060
|
+
{
|
|
1061
|
+
id: 'always',
|
|
1062
|
+
label: '🟢 always',
|
|
1063
|
+
description: describeProjectRulesMode('always')
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
id: 'if-instructions',
|
|
1067
|
+
label: '🟡 if-instructions',
|
|
1068
|
+
description: describeProjectRulesMode('if-instructions')
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
id: 'if-present',
|
|
1072
|
+
label: '🔵 if-present',
|
|
1073
|
+
description: describeProjectRulesMode('if-present')
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
id: 'never',
|
|
1077
|
+
label: '⚪ never',
|
|
1078
|
+
description: describeProjectRulesMode('never')
|
|
1079
|
+
}
|
|
1080
|
+
],
|
|
1081
|
+
defaultIndex: ['always', 'if-instructions', 'if-present', 'never'].indexOf(
|
|
1082
|
+
defaultProjectRulesMode
|
|
1083
|
+
)
|
|
1084
|
+
});
|
|
1085
|
+
projectRulesMode = normalizeProjectRulesMode(modeOption.id);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
956
1088
|
return {
|
|
957
1089
|
targetDir,
|
|
958
1090
|
selectedToolIds,
|
|
959
1091
|
componentSelection,
|
|
960
1092
|
presetId: selectedPreset?.id ?? null,
|
|
961
|
-
installRootOverrides
|
|
1093
|
+
installRootOverrides,
|
|
1094
|
+
projectRulesMode
|
|
962
1095
|
};
|
|
963
1096
|
} finally {
|
|
964
1097
|
setRawMode(wasRawMode);
|
|
@@ -971,7 +1104,8 @@ async function promptInteractiveText({
|
|
|
971
1104
|
mode,
|
|
972
1105
|
defaultTarget,
|
|
973
1106
|
presetFlag,
|
|
974
|
-
installRootOverridesFromFlag
|
|
1107
|
+
installRootOverridesFromFlag,
|
|
1108
|
+
projectRulesModeFromFlag
|
|
975
1109
|
}) {
|
|
976
1110
|
const rl = createInterface({ input, output });
|
|
977
1111
|
|
|
@@ -1006,6 +1140,9 @@ async function promptInteractiveText({
|
|
|
1006
1140
|
}
|
|
1007
1141
|
const targetDir = path.resolve(process.cwd(), targetInput);
|
|
1008
1142
|
const state = await readState(targetDir);
|
|
1143
|
+
const defaultProjectRulesMode = normalizeProjectRulesMode(
|
|
1144
|
+
projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
|
|
1145
|
+
);
|
|
1009
1146
|
|
|
1010
1147
|
const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
|
|
1011
1148
|
let selectedPreset = findPreset(catalog, presetFlag);
|
|
@@ -1059,7 +1196,7 @@ async function promptInteractiveText({
|
|
|
1059
1196
|
console.log(`📦 [${tool.title}] 구성요소 목록`);
|
|
1060
1197
|
tool.components.forEach((component, index) => {
|
|
1061
1198
|
console.log(`${index + 1}. ${component.title} (${component.id})`);
|
|
1062
|
-
console.log(` - ${component
|
|
1199
|
+
console.log(` - ${getComponentDescription(tool.id, component)}`);
|
|
1063
1200
|
});
|
|
1064
1201
|
|
|
1065
1202
|
let defaultComponents = tool.components.map((c) => c.id);
|
|
@@ -1094,12 +1231,32 @@ async function promptInteractiveText({
|
|
|
1094
1231
|
cliOverrides: installRootOverridesFromFlag
|
|
1095
1232
|
});
|
|
1096
1233
|
|
|
1234
|
+
let projectRulesMode = defaultProjectRulesMode;
|
|
1235
|
+
if (!projectRulesModeFromFlag) {
|
|
1236
|
+
console.log('');
|
|
1237
|
+
console.log('🧭 프로젝트 규칙 파일 정책');
|
|
1238
|
+
console.log(`1. always - ${describeProjectRulesMode('always')}`);
|
|
1239
|
+
console.log(`2. if-instructions - ${describeProjectRulesMode('if-instructions')}`);
|
|
1240
|
+
console.log(`3. if-present - ${describeProjectRulesMode('if-present')}`);
|
|
1241
|
+
console.log(`4. never - ${describeProjectRulesMode('never')}`);
|
|
1242
|
+
const modeAnswer = await ask('정책을 선택하세요. (번호/ID)', defaultProjectRulesMode);
|
|
1243
|
+
const normalizedModeAnswer = modeAnswer.trim().toLowerCase();
|
|
1244
|
+
const modeByNumber = {
|
|
1245
|
+
'1': 'always',
|
|
1246
|
+
'2': 'if-instructions',
|
|
1247
|
+
'3': 'if-present',
|
|
1248
|
+
'4': 'never'
|
|
1249
|
+
};
|
|
1250
|
+
projectRulesMode = normalizeProjectRulesMode(modeByNumber[normalizedModeAnswer] ?? modeAnswer);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1097
1253
|
return {
|
|
1098
1254
|
targetDir,
|
|
1099
1255
|
selectedToolIds,
|
|
1100
1256
|
componentSelection,
|
|
1101
1257
|
presetId: selectedPreset?.id ?? null,
|
|
1102
|
-
installRootOverrides
|
|
1258
|
+
installRootOverrides,
|
|
1259
|
+
projectRulesMode
|
|
1103
1260
|
};
|
|
1104
1261
|
} finally {
|
|
1105
1262
|
rl.close();
|
|
@@ -1120,7 +1277,8 @@ function buildSelectionFromFlags({
|
|
|
1120
1277
|
toolFlag,
|
|
1121
1278
|
componentFlag,
|
|
1122
1279
|
targetFlag,
|
|
1123
|
-
installRootOverridesFromFlag
|
|
1280
|
+
installRootOverridesFromFlag,
|
|
1281
|
+
projectRulesModeFromFlag
|
|
1124
1282
|
}) {
|
|
1125
1283
|
const preset = findPreset(catalog, presetFlag);
|
|
1126
1284
|
const presetToolIds = resolveToolsFromPreset(preset, catalog);
|
|
@@ -1163,7 +1321,8 @@ function buildSelectionFromFlags({
|
|
|
1163
1321
|
selectedToolIds,
|
|
1164
1322
|
componentSelection,
|
|
1165
1323
|
presetId: preset?.id ?? null,
|
|
1166
|
-
installRootOverrides
|
|
1324
|
+
installRootOverrides,
|
|
1325
|
+
projectRulesMode: normalizeProjectRulesMode(projectRulesModeFromFlag ?? 'always')
|
|
1167
1326
|
};
|
|
1168
1327
|
}
|
|
1169
1328
|
|
|
@@ -1189,6 +1348,54 @@ async function copyEntry(srcPath, destPath, { overwrite, dryRun }) {
|
|
|
1189
1348
|
return { status: destExists ? 'overwritten' : 'copied' };
|
|
1190
1349
|
}
|
|
1191
1350
|
|
|
1351
|
+
async function copyProjectRuleBootstrap({
|
|
1352
|
+
toolId,
|
|
1353
|
+
targetDir,
|
|
1354
|
+
installRoot,
|
|
1355
|
+
overwrite,
|
|
1356
|
+
dryRun,
|
|
1357
|
+
selectedComponentIds,
|
|
1358
|
+
projectRulesMode
|
|
1359
|
+
}) {
|
|
1360
|
+
const ruleMeta = PROJECT_RULE_BOOTSTRAP[toolId];
|
|
1361
|
+
if (!ruleMeta) {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const normalizedMode = normalizeProjectRulesMode(projectRulesMode ?? 'always');
|
|
1366
|
+
if (normalizedMode === 'never') {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const componentIds = new Set(selectedComponentIds ?? []);
|
|
1371
|
+
if (normalizedMode === 'if-instructions' && !componentIds.has('instructions')) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const srcPath = path.join(rootDir, ruleMeta.sourceRelativePath);
|
|
1376
|
+
if (!(await exists(srcPath))) {
|
|
1377
|
+
throw new Error(`[${toolId}] 프로젝트 규칙 템플릿 누락: ${ruleMeta.sourceRelativePath}`);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const destinationRelativePath = ruleMeta.destinationRelativePath.replace(
|
|
1381
|
+
'{installRoot}',
|
|
1382
|
+
installRoot || ''
|
|
1383
|
+
);
|
|
1384
|
+
const dstPath = path.join(targetDir, destinationRelativePath);
|
|
1385
|
+
|
|
1386
|
+
if (normalizedMode === 'if-present' && !(await exists(dstPath))) {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
const result = await copyEntry(srcPath, dstPath, { overwrite, dryRun });
|
|
1391
|
+
return {
|
|
1392
|
+
toolId,
|
|
1393
|
+
source: ruleMeta.sourceRelativePath,
|
|
1394
|
+
destination: destinationRelativePath,
|
|
1395
|
+
status: result.status
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1192
1399
|
function statusLabel(status) {
|
|
1193
1400
|
switch (status) {
|
|
1194
1401
|
case 'copied':
|
|
@@ -1209,17 +1416,17 @@ function statusLabel(status) {
|
|
|
1209
1416
|
function statusIcon(status) {
|
|
1210
1417
|
switch (status) {
|
|
1211
1418
|
case 'copied':
|
|
1212
|
-
return '
|
|
1419
|
+
return '🟢';
|
|
1213
1420
|
case 'overwritten':
|
|
1214
|
-
return '
|
|
1421
|
+
return '🔵';
|
|
1215
1422
|
case 'skipped':
|
|
1216
|
-
return '
|
|
1423
|
+
return '⚪';
|
|
1217
1424
|
case 'would-copy':
|
|
1218
|
-
return '
|
|
1425
|
+
return '🟡';
|
|
1219
1426
|
case 'would-overwrite':
|
|
1220
|
-
return '
|
|
1427
|
+
return '🟣';
|
|
1221
1428
|
default:
|
|
1222
|
-
return '
|
|
1429
|
+
return '○';
|
|
1223
1430
|
}
|
|
1224
1431
|
}
|
|
1225
1432
|
|
|
@@ -1241,6 +1448,11 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
|
|
|
1241
1448
|
console.log(`실행 타입 : ${dryRun ? 'dry-run (실제 파일 변경 없음)' : '실행 (파일 변경 반영)'}`);
|
|
1242
1449
|
console.log(`프리셋 : ${selection.presetId ?? '수동/없음'}`);
|
|
1243
1450
|
console.log(`대상 경로 : ${targetDir}`);
|
|
1451
|
+
console.log(
|
|
1452
|
+
`규칙 파일 : ${selection.projectRulesMode} (${describeProjectRulesMode(
|
|
1453
|
+
selection.projectRulesMode
|
|
1454
|
+
)})`
|
|
1455
|
+
);
|
|
1244
1456
|
console.log(`선택 도구 : ${selection.selectedToolIds.length}개`);
|
|
1245
1457
|
|
|
1246
1458
|
selection.selectedToolIds.forEach((toolId, index) => {
|
|
@@ -1272,6 +1484,20 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
|
|
|
1272
1484
|
}
|
|
1273
1485
|
}
|
|
1274
1486
|
|
|
1487
|
+
const projectRuleDestination = resolveProjectRuleDestination(tool.id, installRoot);
|
|
1488
|
+
if (projectRuleDestination) {
|
|
1489
|
+
let applyHint = '적용됨';
|
|
1490
|
+
if (selection.projectRulesMode === 'if-instructions' && !componentIds.includes('instructions')) {
|
|
1491
|
+
applyHint = '`instructions` 미선택으로 이번 실행에서는 생성/업데이트 안 함';
|
|
1492
|
+
} else if (selection.projectRulesMode === 'if-present') {
|
|
1493
|
+
applyHint = '대상 파일이 이미 있을 때만 업데이트';
|
|
1494
|
+
} else if (selection.projectRulesMode === 'never') {
|
|
1495
|
+
applyHint = '생성/업데이트 안 함';
|
|
1496
|
+
}
|
|
1497
|
+
console.log(` 규칙 파일 : ${projectRuleDestination}`);
|
|
1498
|
+
console.log(` 적용 정책 : ${applyHint}`);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1275
1501
|
if (tool.install_root_basis) {
|
|
1276
1502
|
console.log(` 경로 기준 : ${tool.install_root_basis}`);
|
|
1277
1503
|
}
|
|
@@ -1357,6 +1583,7 @@ async function writeState({ catalog, targetDir, packageData, mode, selection })
|
|
|
1357
1583
|
package_version: packageData.version,
|
|
1358
1584
|
updated_at: new Date().toISOString(),
|
|
1359
1585
|
preset_id: selection.presetId ?? null,
|
|
1586
|
+
project_rules_mode: normalizeProjectRulesMode(selection.projectRulesMode ?? 'always'),
|
|
1360
1587
|
install_roots: installRoots,
|
|
1361
1588
|
tools
|
|
1362
1589
|
};
|
|
@@ -1419,6 +1646,19 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
|
|
|
1419
1646
|
status: result.status
|
|
1420
1647
|
});
|
|
1421
1648
|
}
|
|
1649
|
+
|
|
1650
|
+
const ruleResult = await copyProjectRuleBootstrap({
|
|
1651
|
+
toolId,
|
|
1652
|
+
targetDir,
|
|
1653
|
+
installRoot,
|
|
1654
|
+
overwrite,
|
|
1655
|
+
dryRun,
|
|
1656
|
+
selectedComponentIds: componentIds,
|
|
1657
|
+
projectRulesMode: selection.projectRulesMode
|
|
1658
|
+
});
|
|
1659
|
+
if (ruleResult) {
|
|
1660
|
+
results.push(ruleResult);
|
|
1661
|
+
}
|
|
1422
1662
|
}
|
|
1423
1663
|
|
|
1424
1664
|
console.log('');
|
|
@@ -1505,8 +1745,9 @@ function printList(catalog) {
|
|
|
1505
1745
|
getComponentInstallPath(component)
|
|
1506
1746
|
)}`
|
|
1507
1747
|
);
|
|
1508
|
-
|
|
1509
|
-
|
|
1748
|
+
const description = getComponentDescription(tool.id, component);
|
|
1749
|
+
if (description) {
|
|
1750
|
+
console.log(` ${description}`);
|
|
1510
1751
|
}
|
|
1511
1752
|
});
|
|
1512
1753
|
});
|
|
@@ -1530,7 +1771,7 @@ function printList(catalog) {
|
|
|
1530
1771
|
}
|
|
1531
1772
|
|
|
1532
1773
|
console.log('━━━━━━━━ 권장 흐름 ━━━━━━━━');
|
|
1533
|
-
console.log(`1)
|
|
1774
|
+
console.log(`1) npx --yes --package @dusky-bluehour/agent-service ${CLI_NAME} --interactive`);
|
|
1534
1775
|
console.log('2) 설치 후 .tri-agent-manager/state.json 기준으로 update');
|
|
1535
1776
|
console.log('3) update 시 필요한 도구만 부분 갱신');
|
|
1536
1777
|
}
|
|
@@ -1573,6 +1814,9 @@ async function main() {
|
|
|
1573
1814
|
}
|
|
1574
1815
|
|
|
1575
1816
|
const installRootOverridesFromFlag = parseInstallRootFlag(options.installRootFlag, catalog);
|
|
1817
|
+
const projectRulesModeFromFlag = options.projectRulesFlag
|
|
1818
|
+
? normalizeProjectRulesMode(options.projectRulesFlag)
|
|
1819
|
+
: null;
|
|
1576
1820
|
|
|
1577
1821
|
const shouldUseInteractive =
|
|
1578
1822
|
!options.nonInteractive &&
|
|
@@ -1584,7 +1828,8 @@ async function main() {
|
|
|
1584
1828
|
mode: command,
|
|
1585
1829
|
defaultTarget: options.targetFlag,
|
|
1586
1830
|
presetFlag: options.presetFlag,
|
|
1587
|
-
installRootOverridesFromFlag
|
|
1831
|
+
installRootOverridesFromFlag,
|
|
1832
|
+
projectRulesModeFromFlag
|
|
1588
1833
|
})
|
|
1589
1834
|
: buildSelectionFromFlags({
|
|
1590
1835
|
catalog,
|
|
@@ -1593,12 +1838,16 @@ async function main() {
|
|
|
1593
1838
|
toolFlag: options.toolFlag,
|
|
1594
1839
|
componentFlag: options.componentFlag,
|
|
1595
1840
|
targetFlag: options.targetFlag,
|
|
1596
|
-
installRootOverridesFromFlag
|
|
1841
|
+
installRootOverridesFromFlag,
|
|
1842
|
+
projectRulesModeFromFlag
|
|
1597
1843
|
});
|
|
1598
1844
|
|
|
1599
1845
|
const stateForTarget = await readState(rawSelection.targetDir);
|
|
1600
1846
|
const selection = {
|
|
1601
1847
|
...rawSelection,
|
|
1848
|
+
projectRulesMode: normalizeProjectRulesMode(
|
|
1849
|
+
projectRulesModeFromFlag ?? stateForTarget?.project_rules_mode ?? rawSelection.projectRulesMode
|
|
1850
|
+
),
|
|
1602
1851
|
installRootOverrides: buildInstallRootOverrides({
|
|
1603
1852
|
catalog,
|
|
1604
1853
|
selectedToolIds: rawSelection.selectedToolIds,
|