@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.
Files changed (98) hide show
  1. package/README.md +20 -28
  2. package/antigravity/README.md +7 -0
  3. package/antigravity/commands/definitions/cmd-dev-be-api.md +43 -0
  4. package/antigravity/commands/definitions/cmd-dev-fe-hook-separate.md +43 -0
  5. package/antigravity/commands/definitions/cmd-dev-fe-ui-componentize.md +43 -0
  6. package/antigravity/commands/definitions/cmd-dev-perf-optimize.md +43 -0
  7. package/antigravity/commands/definitions/cmd-dev-sequential-autorun.md +43 -0
  8. package/antigravity/commands/definitions/cmd-doc-handoff.md +44 -0
  9. package/antigravity/commands/definitions/cmd-improve-techdebt.md +43 -0
  10. package/antigravity/commands/definitions/cmd-incident-triage.md +43 -0
  11. package/antigravity/commands/definitions/cmd-ops-ci-cd-gate.md +43 -0
  12. package/antigravity/commands/definitions/cmd-ops-deploy.md +43 -0
  13. package/antigravity/commands/definitions/cmd-ops-monitoring.md +43 -0
  14. package/antigravity/commands/definitions/cmd-plan-arch-decision.md +43 -0
  15. package/antigravity/commands/definitions/cmd-plan-implementation-bootstrap.md +43 -0
  16. package/antigravity/commands/definitions/cmd-plan-prd-details.md +43 -0
  17. package/antigravity/commands/definitions/cmd-plan-prd-master.md +44 -0
  18. package/antigravity/commands/definitions/cmd-plan-req-lock.md +44 -0
  19. package/antigravity/commands/definitions/cmd-review-code.md +43 -0
  20. package/antigravity/commands/definitions/cmd-sec-dependency-audit.md +43 -0
  21. package/antigravity/commands/definitions/cmd-sec-threat-model.md +43 -0
  22. package/antigravity/commands/definitions/cmd-test-unit-integration.md +43 -0
  23. package/antigravity/instructions/WORKSPACE-RULES.template.md +34 -0
  24. package/antigravity/settings/editor-policy.json +193 -0
  25. package/catalog/tool-catalog.ko.json +77 -16
  26. package/claude-code/README.md +27 -0
  27. package/claude-code/commands/native/cmd-dev-be-api.md +51 -0
  28. package/claude-code/commands/native/cmd-dev-fe-hook-separate.md +51 -0
  29. package/claude-code/commands/native/cmd-dev-fe-ui-componentize.md +51 -0
  30. package/claude-code/commands/native/cmd-dev-perf-optimize.md +51 -0
  31. package/claude-code/commands/native/cmd-dev-sequential-autorun.md +51 -0
  32. package/claude-code/commands/native/cmd-doc-handoff.md +52 -0
  33. package/claude-code/commands/native/cmd-improve-techdebt.md +51 -0
  34. package/claude-code/commands/native/cmd-incident-triage.md +51 -0
  35. package/claude-code/commands/native/cmd-ops-ci-cd-gate.md +51 -0
  36. package/claude-code/commands/native/cmd-ops-deploy.md +51 -0
  37. package/claude-code/commands/native/cmd-ops-monitoring.md +51 -0
  38. package/claude-code/commands/native/cmd-plan-arch-decision.md +51 -0
  39. package/claude-code/commands/native/cmd-plan-implementation-bootstrap.md +51 -0
  40. package/claude-code/commands/native/cmd-plan-prd-details.md +51 -0
  41. package/claude-code/commands/native/cmd-plan-prd-master.md +52 -0
  42. package/claude-code/commands/native/cmd-plan-req-lock.md +52 -0
  43. package/claude-code/commands/native/cmd-review-code.md +51 -0
  44. package/claude-code/commands/native/cmd-sec-dependency-audit.md +51 -0
  45. package/claude-code/commands/native/cmd-sec-threat-model.md +51 -0
  46. package/claude-code/commands/native/cmd-test-unit-integration.md +51 -0
  47. package/claude-code/instructions/CLAUDE.template.md +42 -0
  48. package/claude-code/settings/settings.json +183 -0
  49. package/claude-code/settings/settings.local.json +10 -0
  50. package/codex/README.md +13 -1
  51. package/codex/instructions/AGENTS.permissions.generated.md +121 -0
  52. package/codex/instructions/AGENTS.template.md +18 -3
  53. package/codex/settings/runtime-policy.json +188 -0
  54. package/codex/skills/cmd-dev-be-api/SKILL.md +43 -0
  55. package/codex/skills/cmd-dev-be-api/agents/openai.yaml +4 -0
  56. package/codex/skills/cmd-dev-fe-hook-separate/SKILL.md +43 -0
  57. package/codex/skills/cmd-dev-fe-hook-separate/agents/openai.yaml +4 -0
  58. package/codex/skills/cmd-dev-fe-ui-componentize/SKILL.md +43 -0
  59. package/codex/skills/cmd-dev-fe-ui-componentize/agents/openai.yaml +4 -0
  60. package/codex/skills/cmd-dev-perf-optimize/SKILL.md +43 -0
  61. package/codex/skills/cmd-dev-perf-optimize/agents/openai.yaml +4 -0
  62. package/codex/skills/cmd-dev-sequential-autorun/SKILL.md +43 -0
  63. package/codex/skills/cmd-dev-sequential-autorun/agents/openai.yaml +4 -0
  64. package/codex/skills/cmd-doc-handoff/SKILL.md +43 -0
  65. package/codex/skills/cmd-doc-handoff/agents/openai.yaml +4 -0
  66. package/codex/skills/cmd-improve-techdebt/SKILL.md +43 -0
  67. package/codex/skills/cmd-improve-techdebt/agents/openai.yaml +4 -0
  68. package/codex/skills/cmd-incident-triage/SKILL.md +43 -0
  69. package/codex/skills/cmd-incident-triage/agents/openai.yaml +4 -0
  70. package/codex/skills/cmd-ops-ci-cd-gate/SKILL.md +43 -0
  71. package/codex/skills/cmd-ops-ci-cd-gate/agents/openai.yaml +4 -0
  72. package/codex/skills/cmd-ops-deploy/SKILL.md +43 -0
  73. package/codex/skills/cmd-ops-deploy/agents/openai.yaml +4 -0
  74. package/codex/skills/cmd-ops-monitoring/SKILL.md +43 -0
  75. package/codex/skills/cmd-ops-monitoring/agents/openai.yaml +4 -0
  76. package/codex/skills/cmd-plan-arch-decision/SKILL.md +43 -0
  77. package/codex/skills/cmd-plan-arch-decision/agents/openai.yaml +4 -0
  78. package/codex/skills/cmd-plan-implementation-bootstrap/SKILL.md +43 -0
  79. package/codex/skills/cmd-plan-implementation-bootstrap/agents/openai.yaml +4 -0
  80. package/codex/skills/cmd-plan-prd-details/SKILL.md +43 -0
  81. package/codex/skills/cmd-plan-prd-details/agents/openai.yaml +4 -0
  82. package/codex/skills/cmd-plan-prd-master/SKILL.md +44 -0
  83. package/codex/skills/cmd-plan-prd-master/agents/openai.yaml +4 -0
  84. package/codex/skills/cmd-plan-req-lock/SKILL.md +44 -0
  85. package/codex/skills/cmd-plan-req-lock/agents/openai.yaml +4 -0
  86. package/codex/skills/cmd-review-code/SKILL.md +43 -0
  87. package/codex/skills/cmd-review-code/agents/openai.yaml +4 -0
  88. package/codex/skills/cmd-sec-dependency-audit/SKILL.md +43 -0
  89. package/codex/skills/cmd-sec-dependency-audit/agents/openai.yaml +4 -0
  90. package/codex/skills/cmd-sec-threat-model/SKILL.md +43 -0
  91. package/codex/skills/cmd-sec-threat-model/agents/openai.yaml +4 -0
  92. package/codex/skills/cmd-test-unit-integration/SKILL.md +43 -0
  93. package/codex/skills/cmd-test-unit-integration/agents/openai.yaml +4 -0
  94. package/common/settings/security-policy.json +221 -0
  95. package/package.json +1 -1
  96. package/scripts/generate-from-common.mjs +489 -4
  97. package/scripts/init.mjs +285 -36
  98. 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
- '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 {};
@@ -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',
@@ -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.description
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.description}`);
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
- if (component.description) {
1509
- console.log(` ${component.description}`);
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) pnpm dlx ${CLI_NAME} --interactive`);
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,