@dusky-bluehour/agent-service 0.6.6 → 0.6.8

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.
Files changed (127) hide show
  1. package/README.md +255 -36
  2. package/catalog/tool-catalog.ko.json +92 -57
  3. package/claude-code/README.md +75 -30
  4. package/claude-code/commands/native/cmd-dev-be-api.md +51 -0
  5. package/claude-code/commands/native/cmd-dev-fe-hook-separate.md +51 -0
  6. package/claude-code/commands/native/cmd-dev-fe-ui-componentize.md +51 -0
  7. package/claude-code/commands/native/cmd-dev-perf-optimize.md +51 -0
  8. package/claude-code/commands/native/cmd-dev-sequential-autorun.md +51 -0
  9. package/claude-code/commands/native/cmd-doc-handoff.md +52 -0
  10. package/claude-code/commands/native/cmd-improve-techdebt.md +51 -0
  11. package/claude-code/commands/native/cmd-incident-triage.md +51 -0
  12. package/claude-code/commands/native/cmd-ops-ci-cd-gate.md +51 -0
  13. package/claude-code/commands/native/cmd-ops-deploy.md +51 -0
  14. package/claude-code/commands/native/cmd-ops-monitoring.md +51 -0
  15. package/claude-code/commands/native/cmd-plan-arch-decision.md +51 -0
  16. package/claude-code/commands/native/cmd-plan-implementation-bootstrap.md +51 -0
  17. package/claude-code/commands/native/cmd-plan-prd-details.md +51 -0
  18. package/claude-code/commands/native/cmd-plan-prd-master.md +52 -0
  19. package/claude-code/commands/native/cmd-plan-req-lock.md +52 -0
  20. package/claude-code/commands/native/cmd-review-code.md +51 -0
  21. package/claude-code/commands/native/cmd-sec-dependency-audit.md +51 -0
  22. package/claude-code/commands/native/cmd-sec-threat-model.md +51 -0
  23. package/claude-code/commands/native/cmd-test-unit-integration.md +51 -0
  24. package/claude-code/instructions/CLAUDE.template.md +42 -0
  25. package/claude-code/settings/settings.json +183 -0
  26. package/claude-code/settings/settings.local.json +10 -0
  27. package/codex/README.md +58 -24
  28. package/codex/instructions/AGENTS.permissions.generated.md +121 -0
  29. package/codex/instructions/AGENTS.template.md +18 -3
  30. package/codex/settings/runtime-policy.json +188 -0
  31. package/codex/skills/cmd-dev-be-api/SKILL.md +43 -0
  32. package/codex/skills/cmd-dev-be-api/agents/openai.yaml +4 -0
  33. package/codex/skills/cmd-dev-fe-hook-separate/SKILL.md +43 -0
  34. package/codex/skills/cmd-dev-fe-hook-separate/agents/openai.yaml +4 -0
  35. package/codex/skills/cmd-dev-fe-ui-componentize/SKILL.md +43 -0
  36. package/codex/skills/cmd-dev-fe-ui-componentize/agents/openai.yaml +4 -0
  37. package/codex/skills/cmd-dev-perf-optimize/SKILL.md +43 -0
  38. package/codex/skills/cmd-dev-perf-optimize/agents/openai.yaml +4 -0
  39. package/codex/skills/cmd-dev-sequential-autorun/SKILL.md +43 -0
  40. package/codex/skills/cmd-dev-sequential-autorun/agents/openai.yaml +4 -0
  41. package/codex/skills/cmd-doc-handoff/SKILL.md +43 -0
  42. package/codex/skills/cmd-doc-handoff/agents/openai.yaml +4 -0
  43. package/codex/skills/cmd-improve-techdebt/SKILL.md +43 -0
  44. package/codex/skills/cmd-improve-techdebt/agents/openai.yaml +4 -0
  45. package/codex/skills/cmd-incident-triage/SKILL.md +43 -0
  46. package/codex/skills/cmd-incident-triage/agents/openai.yaml +4 -0
  47. package/codex/skills/cmd-ops-ci-cd-gate/SKILL.md +43 -0
  48. package/codex/skills/cmd-ops-ci-cd-gate/agents/openai.yaml +4 -0
  49. package/codex/skills/cmd-ops-deploy/SKILL.md +43 -0
  50. package/codex/skills/cmd-ops-deploy/agents/openai.yaml +4 -0
  51. package/codex/skills/cmd-ops-monitoring/SKILL.md +43 -0
  52. package/codex/skills/cmd-ops-monitoring/agents/openai.yaml +4 -0
  53. package/codex/skills/cmd-plan-arch-decision/SKILL.md +43 -0
  54. package/codex/skills/cmd-plan-arch-decision/agents/openai.yaml +4 -0
  55. package/codex/skills/cmd-plan-implementation-bootstrap/SKILL.md +43 -0
  56. package/codex/skills/cmd-plan-implementation-bootstrap/agents/openai.yaml +4 -0
  57. package/codex/skills/cmd-plan-prd-details/SKILL.md +43 -0
  58. package/codex/skills/cmd-plan-prd-details/agents/openai.yaml +4 -0
  59. package/codex/skills/cmd-plan-prd-master/SKILL.md +44 -0
  60. package/codex/skills/cmd-plan-prd-master/agents/openai.yaml +4 -0
  61. package/codex/skills/cmd-plan-req-lock/SKILL.md +44 -0
  62. package/codex/skills/cmd-plan-req-lock/agents/openai.yaml +4 -0
  63. package/codex/skills/cmd-review-code/SKILL.md +43 -0
  64. package/codex/skills/cmd-review-code/agents/openai.yaml +4 -0
  65. package/codex/skills/cmd-sec-dependency-audit/SKILL.md +43 -0
  66. package/codex/skills/cmd-sec-dependency-audit/agents/openai.yaml +4 -0
  67. package/codex/skills/cmd-sec-threat-model/SKILL.md +43 -0
  68. package/codex/skills/cmd-sec-threat-model/agents/openai.yaml +4 -0
  69. package/codex/skills/cmd-test-unit-integration/SKILL.md +43 -0
  70. package/codex/skills/cmd-test-unit-integration/agents/openai.yaml +4 -0
  71. package/common/settings/security-policy.json +221 -0
  72. package/common/skills/skill-catalog.json +368 -136
  73. package/common/workflows/workflow-catalog.json +89 -1238
  74. package/gemini/README.md +104 -0
  75. package/gemini/commands/definitions/cmd-dev-be-api.toml +35 -0
  76. package/gemini/commands/definitions/cmd-dev-fe-hook-separate.toml +35 -0
  77. package/gemini/commands/definitions/cmd-dev-fe-ui-componentize.toml +35 -0
  78. package/gemini/commands/definitions/cmd-dev-perf-optimize.toml +35 -0
  79. package/gemini/commands/definitions/cmd-dev-sequential-autorun.toml +35 -0
  80. package/gemini/commands/definitions/cmd-doc-handoff.toml +36 -0
  81. package/gemini/commands/definitions/cmd-improve-techdebt.toml +35 -0
  82. package/gemini/commands/definitions/cmd-incident-triage.toml +35 -0
  83. package/gemini/commands/definitions/cmd-ops-ci-cd-gate.toml +35 -0
  84. package/gemini/commands/definitions/cmd-ops-deploy.toml +35 -0
  85. package/gemini/commands/definitions/cmd-ops-monitoring.toml +35 -0
  86. package/gemini/commands/definitions/cmd-plan-arch-decision.toml +35 -0
  87. package/gemini/commands/definitions/cmd-plan-implementation-bootstrap.toml +35 -0
  88. package/gemini/commands/definitions/cmd-plan-prd-details.toml +35 -0
  89. package/gemini/commands/definitions/cmd-plan-prd-master.toml +36 -0
  90. package/gemini/commands/definitions/cmd-plan-req-lock.toml +36 -0
  91. package/gemini/commands/definitions/cmd-review-code.toml +35 -0
  92. package/gemini/commands/definitions/cmd-sec-dependency-audit.toml +35 -0
  93. package/gemini/commands/definitions/cmd-sec-threat-model.toml +35 -0
  94. package/gemini/commands/definitions/cmd-test-unit-integration.toml +35 -0
  95. package/gemini/gemini-extension.json +6 -0
  96. package/gemini/instructions/GEMINI.template.md +34 -0
  97. package/gemini/settings/editor-policy.json +193 -0
  98. package/{antigravity → gemini}/skills/change-safety-review/SKILL.md +8 -6
  99. package/{antigravity → gemini}/skills/code-review-and-improvement/SKILL.md +8 -3
  100. package/gemini/skills/frontend-repetition-pack/SKILL.md +44 -0
  101. package/gemini/skills/incident-response/SKILL.md +44 -0
  102. package/{antigravity → gemini}/skills/prd-to-production-pipeline/SKILL.md +13 -4
  103. package/{antigravity → gemini}/skills/release-and-operations/SKILL.md +11 -3
  104. package/gemini/skills/security-hardening/SKILL.md +43 -0
  105. package/gemini/skills/service-lifecycle-orchestration/SKILL.md +46 -0
  106. package/{antigravity → gemini}/workflows/workflow-catalog.json +1 -1
  107. package/package.json +4 -4
  108. package/scripts/generate-from-common.mjs +537 -23
  109. package/scripts/init.mjs +310 -41
  110. package/scripts/validate.mjs +264 -32
  111. package/antigravity/README.md +0 -43
  112. package/antigravity/skills/frontend-repetition-pack/SKILL.md +0 -35
  113. package/antigravity/skills/incident-response/SKILL.md +0 -35
  114. package/antigravity/skills/security-hardening/SKILL.md +0 -35
  115. package/antigravity/skills/service-lifecycle-orchestration/SKILL.md +0 -36
  116. package/claude-code/workflows/workflow-catalog.json +0 -688
  117. package/codex/workflows/workflow-catalog.json +0 -450
  118. /package/{antigravity/agents → common/gemini}/agent-catalog.json +0 -0
  119. /package/{antigravity/artifacts → common/gemini}/artifact-catalog.json +0 -0
  120. /package/{common/antigravity → gemini/agents}/agent-catalog.json +0 -0
  121. /package/{common/antigravity → gemini/artifacts}/artifact-catalog.json +0 -0
  122. /package/{antigravity → gemini}/commands/command-catalog.json +0 -0
  123. /package/{antigravity → gemini}/workflows/definitions/WF-FRONTEND-REFACTOR.workflow.yaml +0 -0
  124. /package/{antigravity → gemini}/workflows/definitions/WF-INCIDENT-RESPONSE.workflow.yaml +0 -0
  125. /package/{antigravity → gemini}/workflows/definitions/WF-PRD-TO-PRODUCTION.workflow.yaml +0 -0
  126. /package/{antigravity → gemini}/workflows/definitions/WF-SECURITY-HARDENING.workflow.yaml +0 -0
  127. /package/{antigravity → gemini}/workflows/definitions/WF-SERVICE-E2E.workflow.yaml +0 -0
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
+ gemini: {
30
+ sourceRelativePath: path.join('gemini', 'instructions', 'GEMINI.template.md'),
31
+ destinationRelativePath: 'GEMINI.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
  '옵션:',
@@ -33,7 +48,8 @@ const HELP_TEXT = [
33
48
  ' --tool 도구 선택 (예: codex,claude-code) / all',
34
49
  ' --components 구성요소 선택 (예: skills,workflows,commands) / all',
35
50
  ' --target 설치 경로 (기본: .)',
36
- ' --install-root 도구별 설치 루트 오버라이드 (예: codex=.codex,antigravity=.agent)',
51
+ ' --install-root 도구별 설치 루트 오버라이드 (예: codex=.codex,gemini=.gemini)',
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
- 'pnpm 예시:',
45
- ' pnpm dlx --package=@dusky-bluehour/agent-service@latest tri-agent-manager --interactive',
46
- ' pnpm dlx --package=@dusky-bluehour/agent-service@latest tri-agent-manager update --interactive',
47
- ' pnpm dlx --package=@dusky-bluehour/agent-service@latest tri-agent-manager setup',
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 {};
@@ -240,7 +316,7 @@ function parseInstallRootFlag(installRootFlag, catalog) {
240
316
  const idx = token.indexOf('=');
241
317
  if (idx <= 0 || idx === token.length - 1) {
242
318
  throw new Error(
243
- `--install-root 형식이 올바르지 않습니다: ${token} (예: codex=.codex,antigravity=.agent)`
319
+ `--install-root 형식이 올바르지 않습니다: ${token} (예: codex=.codex,gemini=.gemini)`
244
320
  );
245
321
  }
246
322
 
@@ -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
- `프로젝트 루트에 \`AGENTS.md\`를 만들고 \`${path.join(
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',
@@ -428,8 +501,9 @@ function getToolExecutionGuide(tool, installRoot) {
428
501
  ];
429
502
  }
430
503
 
431
- if (tool.id === 'antigravity') {
504
+ if (tool.id === 'gemini') {
432
505
  return [
506
+ '`GEMINI.md`(자동 생성됨)를 열고 운영 규칙을 확정합니다.',
433
507
  `\`${path.join(
434
508
  installRoot,
435
509
  'workflows',
@@ -454,6 +528,7 @@ function getToolExecutionGuide(tool, installRoot) {
454
528
  function getToolInteractionTips(tool, installRoot) {
455
529
  if (tool.id === 'codex') {
456
530
  return [
531
+ '프로젝트 루트의 `AGENTS.md`를 팀 규칙 단일 진입점으로 유지하세요.',
457
532
  'Codex 대화 입력창에서 `$`를 누르면 사용 가능한 스킬 목록을 바로 열 수 있습니다.',
458
533
  `프로젝트 루트에서 스킬 파일을 직접 확인하려면 \`ls ${path.join(
459
534
  installRoot,
@@ -469,6 +544,7 @@ function getToolInteractionTips(tool, installRoot) {
469
544
 
470
545
  if (tool.id === 'claude-code') {
471
546
  return [
547
+ '프로젝트 루트 `CLAUDE.md`를 최신 상태로 유지하면 세션마다 동일한 기준으로 동작합니다.',
472
548
  `서브에이전트 이름은 \`${path.join(installRoot, 'agents')}\` 경로의 파일명 기준입니다.`,
473
549
  `프로젝트 루트에서 \`ls ${path.join(
474
550
  installRoot,
@@ -478,8 +554,9 @@ function getToolInteractionTips(tool, installRoot) {
478
554
  ];
479
555
  }
480
556
 
481
- if (tool.id === 'antigravity') {
557
+ if (tool.id === 'gemini') {
482
558
  return [
559
+ '`GEMINI.md`를 Manager 단계의 승인 규칙 문서로 사용하세요.',
483
560
  `워크플로우 선택은 \`${path.join(
484
561
  installRoot,
485
562
  'workflows',
@@ -520,6 +597,11 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
520
597
  lines.push(`- 생성 시각: ${new Date().toISOString()}`);
521
598
  lines.push(`- 모드: ${mode}`);
522
599
  lines.push(`- 프리셋: ${selection.presetId ?? '수동/없음'}`);
600
+ lines.push(
601
+ `- 프로젝트 규칙 파일 정책: ${selection.projectRulesMode} (${describeProjectRulesMode(
602
+ selection.projectRulesMode
603
+ )})`
604
+ );
523
605
  lines.push(`- 대상 경로: ${targetDir}`);
524
606
  lines.push('');
525
607
  lines.push('이 문서는 현재 설치 선택값 기준으로 생성되었습니다.');
@@ -538,6 +620,10 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
538
620
  lines.push(`## ${tool.title} (${tool.id})`);
539
621
  lines.push('');
540
622
  lines.push(`- 설치 루트: \`${path.join(targetDir, installRoot)}\``);
623
+ const projectRuleDestination = resolveProjectRuleDestination(tool.id, installRoot);
624
+ if (projectRuleDestination) {
625
+ lines.push(`- 프로젝트 규칙 파일: \`${path.join(targetDir, projectRuleDestination)}\``);
626
+ }
541
627
  if (tool.install_root_basis) {
542
628
  lines.push(`- 경로 기준: ${tool.install_root_basis}`);
543
629
  }
@@ -650,11 +736,11 @@ function resolveComponentsFromFlag(componentFlag, tool, mode) {
650
736
  }
651
737
 
652
738
  function clearTuiScreen() {
653
- output.write('\x1Bc');
739
+ output.write('\x1B[2J\x1B[H');
654
740
  }
655
741
 
656
742
  function formatTuiLine(prefix, label, selected = false) {
657
- const marker = selected ? '' : '';
743
+ const marker = selected ? '\x1B[32m[x]\x1B[0m' : '[ ]';
658
744
  return `${prefix} ${marker} ${label}`;
659
745
  }
660
746
 
@@ -700,7 +786,7 @@ async function runSingleSelectMenu({ title, help, options, defaultIndex = 0 }) {
700
786
  while (true) {
701
787
  renderTuiHeader(title, help);
702
788
  options.forEach((option, index) => {
703
- const pointer = index === cursor ? '👉' : ' ';
789
+ const pointer = index === cursor ? '' : '';
704
790
  output.write(`${pointer} ${option.label}\n`);
705
791
  if (option.description) {
706
792
  output.write(` ${option.description}\n`);
@@ -739,8 +825,8 @@ async function runMultiSelectMenu({
739
825
  const selected = new Set(preselectedIds);
740
826
  const optionRows = [
741
827
  ...choices.map((choice) => ({ kind: 'choice', ...choice })),
742
- { kind: 'action', action: 'toggle-all', label: '🧩 전체 선택/해제' },
743
- { kind: 'action', action: 'done', label: '🚀 선택 완료' }
828
+ { kind: 'action', action: 'toggle-all', label: '[*] 전체 선택/해제' },
829
+ { kind: 'action', action: 'done', label: '[>] 선택 완료' }
744
830
  ];
745
831
  let cursor = 0;
746
832
  let notice = '';
@@ -754,7 +840,7 @@ async function runMultiSelectMenu({
754
840
  output.write('\n');
755
841
 
756
842
  optionRows.forEach((row, index) => {
757
- const pointer = index === cursor ? '👉' : ' ';
843
+ const pointer = index === cursor ? '' : '';
758
844
  if (row.kind === 'choice') {
759
845
  output.write(`${formatTuiLine(pointer, row.label, selected.has(row.id))}\n`);
760
846
  if (row.description) {
@@ -823,7 +909,8 @@ async function promptInteractiveTui({
823
909
  mode,
824
910
  defaultTarget,
825
911
  presetFlag,
826
- installRootOverridesFromFlag
912
+ installRootOverridesFromFlag,
913
+ projectRulesModeFromFlag
827
914
  }) {
828
915
  readline.emitKeypressEvents(input);
829
916
  const wasRawMode = Boolean(input.isRaw);
@@ -856,6 +943,9 @@ async function promptInteractiveTui({
856
943
 
857
944
  const targetDir = path.resolve(process.cwd(), targetInput);
858
945
  const state = await readState(targetDir);
946
+ const defaultProjectRulesMode = normalizeProjectRulesMode(
947
+ projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
948
+ );
859
949
  const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
860
950
 
861
951
  const defaultPresetId =
@@ -938,7 +1028,7 @@ async function promptInteractiveTui({
938
1028
  choices: tool.components.map((component) => ({
939
1029
  id: component.id,
940
1030
  label: `${component.title} (${component.id})`,
941
- description: component.description
1031
+ description: getComponentDescription(tool.id, component)
942
1032
  })),
943
1033
  preselectedIds: defaultComponents,
944
1034
  minSelected: 1
@@ -953,12 +1043,47 @@ async function promptInteractiveTui({
953
1043
  cliOverrides: installRootOverridesFromFlag
954
1044
  });
955
1045
 
1046
+ let projectRulesMode = defaultProjectRulesMode;
1047
+ if (!projectRulesModeFromFlag) {
1048
+ const modeOption = await runSingleSelectMenu({
1049
+ title: '🧭 프로젝트 규칙 파일 정책',
1050
+ help: '조작: 위/아래/좌/우 이동, Enter 선택, q 종료',
1051
+ options: [
1052
+ {
1053
+ id: 'always',
1054
+ label: '🟢 always',
1055
+ description: describeProjectRulesMode('always')
1056
+ },
1057
+ {
1058
+ id: 'if-instructions',
1059
+ label: '🟡 if-instructions',
1060
+ description: describeProjectRulesMode('if-instructions')
1061
+ },
1062
+ {
1063
+ id: 'if-present',
1064
+ label: '🔵 if-present',
1065
+ description: describeProjectRulesMode('if-present')
1066
+ },
1067
+ {
1068
+ id: 'never',
1069
+ label: '⚪ never',
1070
+ description: describeProjectRulesMode('never')
1071
+ }
1072
+ ],
1073
+ defaultIndex: ['always', 'if-instructions', 'if-present', 'never'].indexOf(
1074
+ defaultProjectRulesMode
1075
+ )
1076
+ });
1077
+ projectRulesMode = normalizeProjectRulesMode(modeOption.id);
1078
+ }
1079
+
956
1080
  return {
957
1081
  targetDir,
958
1082
  selectedToolIds,
959
1083
  componentSelection,
960
1084
  presetId: selectedPreset?.id ?? null,
961
- installRootOverrides
1085
+ installRootOverrides,
1086
+ projectRulesMode
962
1087
  };
963
1088
  } finally {
964
1089
  setRawMode(wasRawMode);
@@ -971,7 +1096,8 @@ async function promptInteractiveText({
971
1096
  mode,
972
1097
  defaultTarget,
973
1098
  presetFlag,
974
- installRootOverridesFromFlag
1099
+ installRootOverridesFromFlag,
1100
+ projectRulesModeFromFlag
975
1101
  }) {
976
1102
  const rl = createInterface({ input, output });
977
1103
 
@@ -1006,6 +1132,9 @@ async function promptInteractiveText({
1006
1132
  }
1007
1133
  const targetDir = path.resolve(process.cwd(), targetInput);
1008
1134
  const state = await readState(targetDir);
1135
+ const defaultProjectRulesMode = normalizeProjectRulesMode(
1136
+ projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
1137
+ );
1009
1138
 
1010
1139
  const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
1011
1140
  let selectedPreset = findPreset(catalog, presetFlag);
@@ -1059,7 +1188,7 @@ async function promptInteractiveText({
1059
1188
  console.log(`📦 [${tool.title}] 구성요소 목록`);
1060
1189
  tool.components.forEach((component, index) => {
1061
1190
  console.log(`${index + 1}. ${component.title} (${component.id})`);
1062
- console.log(` - ${component.description}`);
1191
+ console.log(` - ${getComponentDescription(tool.id, component)}`);
1063
1192
  });
1064
1193
 
1065
1194
  let defaultComponents = tool.components.map((c) => c.id);
@@ -1094,12 +1223,32 @@ async function promptInteractiveText({
1094
1223
  cliOverrides: installRootOverridesFromFlag
1095
1224
  });
1096
1225
 
1226
+ let projectRulesMode = defaultProjectRulesMode;
1227
+ if (!projectRulesModeFromFlag) {
1228
+ console.log('');
1229
+ console.log('🧭 프로젝트 규칙 파일 정책');
1230
+ console.log(`1. always - ${describeProjectRulesMode('always')}`);
1231
+ console.log(`2. if-instructions - ${describeProjectRulesMode('if-instructions')}`);
1232
+ console.log(`3. if-present - ${describeProjectRulesMode('if-present')}`);
1233
+ console.log(`4. never - ${describeProjectRulesMode('never')}`);
1234
+ const modeAnswer = await ask('정책을 선택하세요. (번호/ID)', defaultProjectRulesMode);
1235
+ const normalizedModeAnswer = modeAnswer.trim().toLowerCase();
1236
+ const modeByNumber = {
1237
+ '1': 'always',
1238
+ '2': 'if-instructions',
1239
+ '3': 'if-present',
1240
+ '4': 'never'
1241
+ };
1242
+ projectRulesMode = normalizeProjectRulesMode(modeByNumber[normalizedModeAnswer] ?? modeAnswer);
1243
+ }
1244
+
1097
1245
  return {
1098
1246
  targetDir,
1099
1247
  selectedToolIds,
1100
1248
  componentSelection,
1101
1249
  presetId: selectedPreset?.id ?? null,
1102
- installRootOverrides
1250
+ installRootOverrides,
1251
+ projectRulesMode
1103
1252
  };
1104
1253
  } finally {
1105
1254
  rl.close();
@@ -1120,7 +1269,8 @@ function buildSelectionFromFlags({
1120
1269
  toolFlag,
1121
1270
  componentFlag,
1122
1271
  targetFlag,
1123
- installRootOverridesFromFlag
1272
+ installRootOverridesFromFlag,
1273
+ projectRulesModeFromFlag
1124
1274
  }) {
1125
1275
  const preset = findPreset(catalog, presetFlag);
1126
1276
  const presetToolIds = resolveToolsFromPreset(preset, catalog);
@@ -1163,7 +1313,8 @@ function buildSelectionFromFlags({
1163
1313
  selectedToolIds,
1164
1314
  componentSelection,
1165
1315
  presetId: preset?.id ?? null,
1166
- installRootOverrides
1316
+ installRootOverrides,
1317
+ projectRulesMode: normalizeProjectRulesMode(projectRulesModeFromFlag ?? 'always')
1167
1318
  };
1168
1319
  }
1169
1320
 
@@ -1189,6 +1340,54 @@ async function copyEntry(srcPath, destPath, { overwrite, dryRun }) {
1189
1340
  return { status: destExists ? 'overwritten' : 'copied' };
1190
1341
  }
1191
1342
 
1343
+ async function copyProjectRuleBootstrap({
1344
+ toolId,
1345
+ targetDir,
1346
+ installRoot,
1347
+ overwrite,
1348
+ dryRun,
1349
+ selectedComponentIds,
1350
+ projectRulesMode
1351
+ }) {
1352
+ const ruleMeta = PROJECT_RULE_BOOTSTRAP[toolId];
1353
+ if (!ruleMeta) {
1354
+ return null;
1355
+ }
1356
+
1357
+ const normalizedMode = normalizeProjectRulesMode(projectRulesMode ?? 'always');
1358
+ if (normalizedMode === 'never') {
1359
+ return null;
1360
+ }
1361
+
1362
+ const componentIds = new Set(selectedComponentIds ?? []);
1363
+ if (normalizedMode === 'if-instructions' && !componentIds.has('instructions')) {
1364
+ return null;
1365
+ }
1366
+
1367
+ const srcPath = path.join(rootDir, ruleMeta.sourceRelativePath);
1368
+ if (!(await exists(srcPath))) {
1369
+ throw new Error(`[${toolId}] 프로젝트 규칙 템플릿 누락: ${ruleMeta.sourceRelativePath}`);
1370
+ }
1371
+
1372
+ const destinationRelativePath = ruleMeta.destinationRelativePath.replace(
1373
+ '{installRoot}',
1374
+ installRoot || ''
1375
+ );
1376
+ const dstPath = path.join(targetDir, destinationRelativePath);
1377
+
1378
+ if (normalizedMode === 'if-present' && !(await exists(dstPath))) {
1379
+ return null;
1380
+ }
1381
+
1382
+ const result = await copyEntry(srcPath, dstPath, { overwrite, dryRun });
1383
+ return {
1384
+ toolId,
1385
+ source: ruleMeta.sourceRelativePath,
1386
+ destination: destinationRelativePath,
1387
+ status: result.status
1388
+ };
1389
+ }
1390
+
1192
1391
  function statusLabel(status) {
1193
1392
  switch (status) {
1194
1393
  case 'copied':
@@ -1209,17 +1408,17 @@ function statusLabel(status) {
1209
1408
  function statusIcon(status) {
1210
1409
  switch (status) {
1211
1410
  case 'copied':
1212
- return '';
1411
+ return '🟢';
1213
1412
  case 'overwritten':
1214
- return '♻️';
1413
+ return '🔵';
1215
1414
  case 'skipped':
1216
- return '⏭️';
1415
+ return '';
1217
1416
  case 'would-copy':
1218
- return '📝';
1417
+ return '🟡';
1219
1418
  case 'would-overwrite':
1220
- return '🛠️';
1419
+ return '🟣';
1221
1420
  default:
1222
- return '';
1421
+ return '';
1223
1422
  }
1224
1423
  }
1225
1424
 
@@ -1241,8 +1440,19 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
1241
1440
  console.log(`실행 타입 : ${dryRun ? 'dry-run (실제 파일 변경 없음)' : '실행 (파일 변경 반영)'}`);
1242
1441
  console.log(`프리셋 : ${selection.presetId ?? '수동/없음'}`);
1243
1442
  console.log(`대상 경로 : ${targetDir}`);
1443
+ console.log(
1444
+ `규칙 파일 : ${selection.projectRulesMode} (${describeProjectRulesMode(
1445
+ selection.projectRulesMode
1446
+ )})`
1447
+ );
1244
1448
  console.log(`선택 도구 : ${selection.selectedToolIds.length}개`);
1245
1449
 
1450
+ let totalComponents = 0;
1451
+ for (const toolId of selection.selectedToolIds) {
1452
+ totalComponents += (selection.componentSelection[toolId] ?? []).length;
1453
+ }
1454
+ console.log(`총 구성요소 : ${totalComponents}개`);
1455
+
1246
1456
  selection.selectedToolIds.forEach((toolId, index) => {
1247
1457
  const tool = findTool(catalog, toolId);
1248
1458
  if (!tool) {
@@ -1272,6 +1482,20 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
1272
1482
  }
1273
1483
  }
1274
1484
 
1485
+ const projectRuleDestination = resolveProjectRuleDestination(tool.id, installRoot);
1486
+ if (projectRuleDestination) {
1487
+ let applyHint = '적용됨';
1488
+ if (selection.projectRulesMode === 'if-instructions' && !componentIds.includes('instructions')) {
1489
+ applyHint = '`instructions` 미선택으로 이번 실행에서는 생성/업데이트 안 함';
1490
+ } else if (selection.projectRulesMode === 'if-present') {
1491
+ applyHint = '대상 파일이 이미 있을 때만 업데이트';
1492
+ } else if (selection.projectRulesMode === 'never') {
1493
+ applyHint = '생성/업데이트 안 함';
1494
+ }
1495
+ console.log(` 규칙 파일 : ${projectRuleDestination}`);
1496
+ console.log(` 적용 정책 : ${applyHint}`);
1497
+ }
1498
+
1275
1499
  if (tool.install_root_basis) {
1276
1500
  console.log(` 경로 기준 : ${tool.install_root_basis}`);
1277
1501
  }
@@ -1357,6 +1581,7 @@ async function writeState({ catalog, targetDir, packageData, mode, selection })
1357
1581
  package_version: packageData.version,
1358
1582
  updated_at: new Date().toISOString(),
1359
1583
  preset_id: selection.presetId ?? null,
1584
+ project_rules_mode: normalizeProjectRulesMode(selection.projectRulesMode ?? 'always'),
1360
1585
  install_roots: installRoots,
1361
1586
  tools
1362
1587
  };
@@ -1381,6 +1606,22 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1381
1606
 
1382
1607
  const results = [];
1383
1608
 
1609
+ let totalSteps = 0;
1610
+ for (const toolId of selection.selectedToolIds) {
1611
+ const componentIds = selection.componentSelection[toolId] ?? [];
1612
+ totalSteps += 1 + componentIds.length + 1;
1613
+ }
1614
+ let currentStep = 0;
1615
+ const logStep = (label) => {
1616
+ currentStep += 1;
1617
+ const progress = `[${String(currentStep).padStart(String(totalSteps).length, ' ')}/${totalSteps}]`;
1618
+ if (output.isTTY) {
1619
+ output.write(`\r\x1B[K${progress} ${label}`);
1620
+ } else {
1621
+ console.log(`${progress} ${label}`);
1622
+ }
1623
+ };
1624
+
1384
1625
  for (const toolId of selection.selectedToolIds) {
1385
1626
  const tool = findTool(catalog, toolId);
1386
1627
  if (!tool) {
@@ -1391,6 +1632,7 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1391
1632
  const toolDestRoot = path.join(targetDir, installRoot);
1392
1633
  await fs.mkdir(toolDestRoot, { recursive: true });
1393
1634
 
1635
+ logStep(`${tool.title} README 복사 중...`);
1394
1636
  const readmeSrc = path.join(rootDir, tool.root, tool.readme);
1395
1637
  const readmeDst = path.join(toolDestRoot, `README.tri-agent-manager.${tool.id}.md`);
1396
1638
  const readmeResult = await copyEntry(readmeSrc, readmeDst, { overwrite, dryRun });
@@ -1408,6 +1650,7 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1408
1650
  throw new Error(`[${tool.id}] 알 수 없는 구성요소: ${componentId}`);
1409
1651
  }
1410
1652
 
1653
+ logStep(`${tool.title} ${component.title} 복사 중...`);
1411
1654
  const src = path.join(rootDir, tool.root, component.path);
1412
1655
  const componentInstallPath = getComponentInstallPath(component);
1413
1656
  const dst = path.join(targetDir, installRoot, componentInstallPath);
@@ -1419,8 +1662,25 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1419
1662
  status: result.status
1420
1663
  });
1421
1664
  }
1665
+
1666
+ logStep(`${tool.title} 프로젝트 규칙 처리 중...`);
1667
+ const ruleResult = await copyProjectRuleBootstrap({
1668
+ toolId,
1669
+ targetDir,
1670
+ installRoot,
1671
+ overwrite,
1672
+ dryRun,
1673
+ selectedComponentIds: componentIds,
1674
+ projectRulesMode: selection.projectRulesMode
1675
+ });
1676
+ if (ruleResult) {
1677
+ results.push(ruleResult);
1678
+ }
1422
1679
  }
1423
1680
 
1681
+ if (output.isTTY) {
1682
+ output.write('\r\x1B[K');
1683
+ }
1424
1684
  console.log('');
1425
1685
  console.log('━━━━━━━━ 적용 결과 ━━━━━━━━');
1426
1686
  const summaryCounter = {
@@ -1505,8 +1765,9 @@ function printList(catalog) {
1505
1765
  getComponentInstallPath(component)
1506
1766
  )}`
1507
1767
  );
1508
- if (component.description) {
1509
- console.log(` ${component.description}`);
1768
+ const description = getComponentDescription(tool.id, component);
1769
+ if (description) {
1770
+ console.log(` ${description}`);
1510
1771
  }
1511
1772
  });
1512
1773
  });
@@ -1530,7 +1791,7 @@ function printList(catalog) {
1530
1791
  }
1531
1792
 
1532
1793
  console.log('━━━━━━━━ 권장 흐름 ━━━━━━━━');
1533
- console.log(`1) pnpm dlx ${CLI_NAME} --interactive`);
1794
+ console.log(`1) npx --yes --package @dusky-bluehour/agent-service ${CLI_NAME} --interactive`);
1534
1795
  console.log('2) 설치 후 .tri-agent-manager/state.json 기준으로 update');
1535
1796
  console.log('3) update 시 필요한 도구만 부분 갱신');
1536
1797
  }
@@ -1573,6 +1834,9 @@ async function main() {
1573
1834
  }
1574
1835
 
1575
1836
  const installRootOverridesFromFlag = parseInstallRootFlag(options.installRootFlag, catalog);
1837
+ const projectRulesModeFromFlag = options.projectRulesFlag
1838
+ ? normalizeProjectRulesMode(options.projectRulesFlag)
1839
+ : null;
1576
1840
 
1577
1841
  const shouldUseInteractive =
1578
1842
  !options.nonInteractive &&
@@ -1584,7 +1848,8 @@ async function main() {
1584
1848
  mode: command,
1585
1849
  defaultTarget: options.targetFlag,
1586
1850
  presetFlag: options.presetFlag,
1587
- installRootOverridesFromFlag
1851
+ installRootOverridesFromFlag,
1852
+ projectRulesModeFromFlag
1588
1853
  })
1589
1854
  : buildSelectionFromFlags({
1590
1855
  catalog,
@@ -1593,12 +1858,16 @@ async function main() {
1593
1858
  toolFlag: options.toolFlag,
1594
1859
  componentFlag: options.componentFlag,
1595
1860
  targetFlag: options.targetFlag,
1596
- installRootOverridesFromFlag
1861
+ installRootOverridesFromFlag,
1862
+ projectRulesModeFromFlag
1597
1863
  });
1598
1864
 
1599
1865
  const stateForTarget = await readState(rawSelection.targetDir);
1600
1866
  const selection = {
1601
1867
  ...rawSelection,
1868
+ projectRulesMode: normalizeProjectRulesMode(
1869
+ projectRulesModeFromFlag ?? stateForTarget?.project_rules_mode ?? rawSelection.projectRulesMode
1870
+ ),
1602
1871
  installRootOverrides: buildInstallRootOverrides({
1603
1872
  catalog,
1604
1873
  selectedToolIds: rawSelection.selectedToolIds,