@dusky-bluehour/agent-service 0.6.5 → 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 (141) hide show
  1. package/README.md +36 -25
  2. package/antigravity/README.md +14 -1
  3. package/antigravity/agents/agent-catalog.json +5 -5
  4. package/antigravity/commands/definitions/cmd-dev-be-api.md +43 -0
  5. package/antigravity/commands/definitions/cmd-dev-fe-hook-separate.md +43 -0
  6. package/antigravity/commands/definitions/cmd-dev-fe-ui-componentize.md +43 -0
  7. package/antigravity/commands/definitions/cmd-dev-perf-optimize.md +43 -0
  8. package/antigravity/commands/definitions/cmd-dev-sequential-autorun.md +43 -0
  9. package/antigravity/commands/definitions/cmd-doc-handoff.md +44 -0
  10. package/antigravity/commands/definitions/cmd-improve-techdebt.md +43 -0
  11. package/antigravity/commands/definitions/cmd-incident-triage.md +43 -0
  12. package/antigravity/commands/definitions/cmd-ops-ci-cd-gate.md +43 -0
  13. package/antigravity/commands/definitions/cmd-ops-deploy.md +43 -0
  14. package/antigravity/commands/definitions/cmd-ops-monitoring.md +43 -0
  15. package/antigravity/commands/definitions/cmd-plan-arch-decision.md +43 -0
  16. package/antigravity/commands/definitions/cmd-plan-implementation-bootstrap.md +43 -0
  17. package/antigravity/commands/definitions/cmd-plan-prd-details.md +43 -0
  18. package/antigravity/commands/definitions/cmd-plan-prd-master.md +44 -0
  19. package/antigravity/commands/definitions/cmd-plan-req-lock.md +44 -0
  20. package/antigravity/commands/definitions/cmd-review-code.md +43 -0
  21. package/antigravity/commands/definitions/cmd-sec-dependency-audit.md +43 -0
  22. package/antigravity/commands/definitions/cmd-sec-threat-model.md +43 -0
  23. package/antigravity/commands/definitions/cmd-test-unit-integration.md +43 -0
  24. package/antigravity/instructions/WORKSPACE-RULES.template.md +34 -0
  25. package/antigravity/settings/editor-policy.json +193 -0
  26. package/antigravity/skills/change-safety-review/SKILL.md +40 -0
  27. package/antigravity/skills/code-review-and-improvement/SKILL.md +20 -0
  28. package/antigravity/skills/frontend-repetition-pack/SKILL.md +20 -0
  29. package/antigravity/skills/incident-response/SKILL.md +20 -0
  30. package/antigravity/skills/prd-to-production-pipeline/SKILL.md +21 -1
  31. package/antigravity/skills/release-and-operations/SKILL.md +20 -0
  32. package/antigravity/skills/security-hardening/SKILL.md +21 -1
  33. package/antigravity/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  34. package/antigravity/workflows/definitions/WF-FRONTEND-REFACTOR.workflow.yaml +38 -0
  35. package/antigravity/workflows/definitions/WF-INCIDENT-RESPONSE.workflow.yaml +41 -0
  36. package/antigravity/workflows/definitions/WF-PRD-TO-PRODUCTION.workflow.yaml +76 -0
  37. package/antigravity/workflows/definitions/WF-SECURITY-HARDENING.workflow.yaml +40 -0
  38. package/antigravity/workflows/definitions/WF-SERVICE-E2E.workflow.yaml +67 -0
  39. package/antigravity/workflows/workflow-catalog.json +5 -5
  40. package/catalog/tool-catalog.ko.json +78 -17
  41. package/claude-code/README.md +31 -1
  42. package/claude-code/agent-teams/team-catalog.json +7 -7
  43. package/claude-code/commands/native/cmd-dev-be-api.md +51 -0
  44. package/claude-code/commands/native/cmd-dev-fe-hook-separate.md +51 -0
  45. package/claude-code/commands/native/cmd-dev-fe-ui-componentize.md +51 -0
  46. package/claude-code/commands/native/cmd-dev-perf-optimize.md +51 -0
  47. package/claude-code/commands/native/cmd-dev-sequential-autorun.md +51 -0
  48. package/claude-code/commands/native/cmd-doc-handoff.md +52 -0
  49. package/claude-code/commands/native/cmd-improve-techdebt.md +51 -0
  50. package/claude-code/commands/native/cmd-incident-triage.md +51 -0
  51. package/claude-code/commands/native/cmd-ops-ci-cd-gate.md +51 -0
  52. package/claude-code/commands/native/cmd-ops-deploy.md +51 -0
  53. package/claude-code/commands/native/cmd-ops-monitoring.md +51 -0
  54. package/claude-code/commands/native/cmd-plan-arch-decision.md +51 -0
  55. package/claude-code/commands/native/cmd-plan-implementation-bootstrap.md +51 -0
  56. package/claude-code/commands/native/cmd-plan-prd-details.md +51 -0
  57. package/claude-code/commands/native/cmd-plan-prd-master.md +52 -0
  58. package/claude-code/commands/native/cmd-plan-req-lock.md +52 -0
  59. package/claude-code/commands/native/cmd-review-code.md +51 -0
  60. package/claude-code/commands/native/cmd-sec-dependency-audit.md +51 -0
  61. package/claude-code/commands/native/cmd-sec-threat-model.md +51 -0
  62. package/claude-code/commands/native/cmd-test-unit-integration.md +51 -0
  63. package/claude-code/instructions/CLAUDE.template.md +42 -0
  64. package/claude-code/settings/settings.json +183 -0
  65. package/claude-code/settings/settings.local.json +10 -0
  66. package/claude-code/skills/change-safety-review/SKILL.md +40 -0
  67. package/claude-code/skills/code-review-and-improvement/SKILL.md +21 -1
  68. package/claude-code/skills/frontend-repetition-pack/SKILL.md +21 -1
  69. package/claude-code/skills/incident-response/SKILL.md +21 -1
  70. package/claude-code/skills/prd-to-production-pipeline/SKILL.md +21 -1
  71. package/claude-code/skills/release-and-operations/SKILL.md +21 -1
  72. package/claude-code/skills/security-hardening/SKILL.md +21 -1
  73. package/claude-code/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  74. package/claude-code/workflows/workflow-catalog.json +8 -8
  75. package/codex/README.md +18 -3
  76. package/codex/automations/automation-recipes.toml +4 -4
  77. package/codex/instructions/AGENTS.permissions.generated.md +121 -0
  78. package/codex/instructions/AGENTS.template.md +24 -8
  79. package/codex/settings/runtime-policy.json +188 -0
  80. package/codex/skills/change-safety-review/SKILL.md +40 -0
  81. package/codex/skills/change-safety-review/agents/openai.yaml +4 -0
  82. package/codex/skills/cmd-dev-be-api/SKILL.md +43 -0
  83. package/codex/skills/cmd-dev-be-api/agents/openai.yaml +4 -0
  84. package/codex/skills/cmd-dev-fe-hook-separate/SKILL.md +43 -0
  85. package/codex/skills/cmd-dev-fe-hook-separate/agents/openai.yaml +4 -0
  86. package/codex/skills/cmd-dev-fe-ui-componentize/SKILL.md +43 -0
  87. package/codex/skills/cmd-dev-fe-ui-componentize/agents/openai.yaml +4 -0
  88. package/codex/skills/cmd-dev-perf-optimize/SKILL.md +43 -0
  89. package/codex/skills/cmd-dev-perf-optimize/agents/openai.yaml +4 -0
  90. package/codex/skills/cmd-dev-sequential-autorun/SKILL.md +43 -0
  91. package/codex/skills/cmd-dev-sequential-autorun/agents/openai.yaml +4 -0
  92. package/codex/skills/cmd-doc-handoff/SKILL.md +43 -0
  93. package/codex/skills/cmd-doc-handoff/agents/openai.yaml +4 -0
  94. package/codex/skills/cmd-improve-techdebt/SKILL.md +43 -0
  95. package/codex/skills/cmd-improve-techdebt/agents/openai.yaml +4 -0
  96. package/codex/skills/cmd-incident-triage/SKILL.md +43 -0
  97. package/codex/skills/cmd-incident-triage/agents/openai.yaml +4 -0
  98. package/codex/skills/cmd-ops-ci-cd-gate/SKILL.md +43 -0
  99. package/codex/skills/cmd-ops-ci-cd-gate/agents/openai.yaml +4 -0
  100. package/codex/skills/cmd-ops-deploy/SKILL.md +43 -0
  101. package/codex/skills/cmd-ops-deploy/agents/openai.yaml +4 -0
  102. package/codex/skills/cmd-ops-monitoring/SKILL.md +43 -0
  103. package/codex/skills/cmd-ops-monitoring/agents/openai.yaml +4 -0
  104. package/codex/skills/cmd-plan-arch-decision/SKILL.md +43 -0
  105. package/codex/skills/cmd-plan-arch-decision/agents/openai.yaml +4 -0
  106. package/codex/skills/cmd-plan-implementation-bootstrap/SKILL.md +43 -0
  107. package/codex/skills/cmd-plan-implementation-bootstrap/agents/openai.yaml +4 -0
  108. package/codex/skills/cmd-plan-prd-details/SKILL.md +43 -0
  109. package/codex/skills/cmd-plan-prd-details/agents/openai.yaml +4 -0
  110. package/codex/skills/cmd-plan-prd-master/SKILL.md +44 -0
  111. package/codex/skills/cmd-plan-prd-master/agents/openai.yaml +4 -0
  112. package/codex/skills/cmd-plan-req-lock/SKILL.md +44 -0
  113. package/codex/skills/cmd-plan-req-lock/agents/openai.yaml +4 -0
  114. package/codex/skills/cmd-review-code/SKILL.md +43 -0
  115. package/codex/skills/cmd-review-code/agents/openai.yaml +4 -0
  116. package/codex/skills/cmd-sec-dependency-audit/SKILL.md +43 -0
  117. package/codex/skills/cmd-sec-dependency-audit/agents/openai.yaml +4 -0
  118. package/codex/skills/cmd-sec-threat-model/SKILL.md +43 -0
  119. package/codex/skills/cmd-sec-threat-model/agents/openai.yaml +4 -0
  120. package/codex/skills/cmd-test-unit-integration/SKILL.md +43 -0
  121. package/codex/skills/cmd-test-unit-integration/agents/openai.yaml +4 -0
  122. package/codex/skills/code-review-and-improvement/SKILL.md +21 -1
  123. package/codex/skills/frontend-repetition-pack/SKILL.md +20 -0
  124. package/codex/skills/incident-response/SKILL.md +21 -1
  125. package/codex/skills/prd-to-production-pipeline/SKILL.md +21 -1
  126. package/codex/skills/release-and-operations/SKILL.md +20 -0
  127. package/codex/skills/security-hardening/SKILL.md +21 -1
  128. package/codex/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  129. package/codex/workflows/workflow-catalog.json +6 -6
  130. package/common/antigravity/agent-catalog.json +72 -0
  131. package/common/antigravity/artifact-catalog.json +184 -0
  132. package/common/claude/subagent-catalog.json +419 -0
  133. package/common/claude/team-catalog.json +69 -0
  134. package/common/commands/command-catalog.json +942 -0
  135. package/common/settings/security-policy.json +221 -0
  136. package/common/skills/skill-catalog.json +566 -0
  137. package/common/workflows/workflow-catalog.json +1550 -0
  138. package/package.json +6 -2
  139. package/scripts/generate-from-common.mjs +872 -0
  140. package/scripts/init.mjs +295 -36
  141. package/scripts/validate.mjs +451 -10
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,11 +503,21 @@ 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',
436
514
  'workflow-catalog.json'
437
515
  )}\`에서 workflow ID를 선택합니다.`,
516
+ `선택한 workflow ID와 같은 이름의 정의 파일을 \`${path.join(
517
+ installRoot,
518
+ 'workflows',
519
+ 'definitions'
520
+ )}\`에서 확인합니다.`,
438
521
  `각 stage의 \`input_artifact\`를 \`${path.join(
439
522
  installRoot,
440
523
  'artifacts'
@@ -449,6 +532,7 @@ function getToolExecutionGuide(tool, installRoot) {
449
532
  function getToolInteractionTips(tool, installRoot) {
450
533
  if (tool.id === 'codex') {
451
534
  return [
535
+ '프로젝트 루트의 `AGENTS.md`를 팀 규칙 단일 진입점으로 유지하세요.',
452
536
  'Codex 대화 입력창에서 `$`를 누르면 사용 가능한 스킬 목록을 바로 열 수 있습니다.',
453
537
  `프로젝트 루트에서 스킬 파일을 직접 확인하려면 \`ls ${path.join(
454
538
  installRoot,
@@ -464,6 +548,7 @@ function getToolInteractionTips(tool, installRoot) {
464
548
 
465
549
  if (tool.id === 'claude-code') {
466
550
  return [
551
+ '프로젝트 루트 `CLAUDE.md`를 최신 상태로 유지하면 세션마다 동일한 기준으로 동작합니다.',
467
552
  `서브에이전트 이름은 \`${path.join(installRoot, 'agents')}\` 경로의 파일명 기준입니다.`,
468
553
  `프로젝트 루트에서 \`ls ${path.join(
469
554
  installRoot,
@@ -475,11 +560,21 @@ function getToolInteractionTips(tool, installRoot) {
475
560
 
476
561
  if (tool.id === 'antigravity') {
477
562
  return [
563
+ `\`${path.join(
564
+ installRoot,
565
+ 'rules',
566
+ 'workspace-core-rules.md'
567
+ )}\`를 Manager 단계의 승인 규칙 문서로 사용하세요.`,
478
568
  `워크플로우 선택은 \`${path.join(
479
569
  installRoot,
480
570
  'workflows',
481
571
  'workflow-catalog.json'
482
572
  )}\` 에서 WF ID를 먼저 고르는 방식으로 진행하세요.`,
573
+ `실행용 정의 파일은 \`${path.join(
574
+ installRoot,
575
+ 'workflows',
576
+ 'definitions'
577
+ )}\` 의 \`*.workflow.yaml\`을 사용하세요.`,
483
578
  `에이전트 역할은 \`${path.join(installRoot, 'agents', 'agent-catalog.json')}\` 에서 확인하세요.`,
484
579
  `입출력 아티팩트 스키마는 \`${path.join(
485
580
  installRoot,
@@ -510,6 +605,11 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
510
605
  lines.push(`- 생성 시각: ${new Date().toISOString()}`);
511
606
  lines.push(`- 모드: ${mode}`);
512
607
  lines.push(`- 프리셋: ${selection.presetId ?? '수동/없음'}`);
608
+ lines.push(
609
+ `- 프로젝트 규칙 파일 정책: ${selection.projectRulesMode} (${describeProjectRulesMode(
610
+ selection.projectRulesMode
611
+ )})`
612
+ );
513
613
  lines.push(`- 대상 경로: ${targetDir}`);
514
614
  lines.push('');
515
615
  lines.push('이 문서는 현재 설치 선택값 기준으로 생성되었습니다.');
@@ -528,6 +628,10 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
528
628
  lines.push(`## ${tool.title} (${tool.id})`);
529
629
  lines.push('');
530
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
+ }
531
635
  if (tool.install_root_basis) {
532
636
  lines.push(`- 경로 기준: ${tool.install_root_basis}`);
533
637
  }
@@ -644,7 +748,7 @@ function clearTuiScreen() {
644
748
  }
645
749
 
646
750
  function formatTuiLine(prefix, label, selected = false) {
647
- const marker = selected ? '' : '';
751
+ const marker = selected ? '🟢' : '';
648
752
  return `${prefix} ${marker} ${label}`;
649
753
  }
650
754
 
@@ -690,7 +794,7 @@ async function runSingleSelectMenu({ title, help, options, defaultIndex = 0 }) {
690
794
  while (true) {
691
795
  renderTuiHeader(title, help);
692
796
  options.forEach((option, index) => {
693
- const pointer = index === cursor ? '👉' : ' ';
797
+ const pointer = index === cursor ? '' : '';
694
798
  output.write(`${pointer} ${option.label}\n`);
695
799
  if (option.description) {
696
800
  output.write(` ${option.description}\n`);
@@ -729,8 +833,8 @@ async function runMultiSelectMenu({
729
833
  const selected = new Set(preselectedIds);
730
834
  const optionRows = [
731
835
  ...choices.map((choice) => ({ kind: 'choice', ...choice })),
732
- { kind: 'action', action: 'toggle-all', label: '🧩 전체 선택/해제' },
733
- { kind: 'action', action: 'done', label: '🚀 선택 완료' }
836
+ { kind: 'action', action: 'toggle-all', label: '🟣 전체 선택/해제' },
837
+ { kind: 'action', action: 'done', label: '🔵 선택 완료' }
734
838
  ];
735
839
  let cursor = 0;
736
840
  let notice = '';
@@ -744,7 +848,7 @@ async function runMultiSelectMenu({
744
848
  output.write('\n');
745
849
 
746
850
  optionRows.forEach((row, index) => {
747
- const pointer = index === cursor ? '👉' : ' ';
851
+ const pointer = index === cursor ? '' : '';
748
852
  if (row.kind === 'choice') {
749
853
  output.write(`${formatTuiLine(pointer, row.label, selected.has(row.id))}\n`);
750
854
  if (row.description) {
@@ -813,7 +917,8 @@ async function promptInteractiveTui({
813
917
  mode,
814
918
  defaultTarget,
815
919
  presetFlag,
816
- installRootOverridesFromFlag
920
+ installRootOverridesFromFlag,
921
+ projectRulesModeFromFlag
817
922
  }) {
818
923
  readline.emitKeypressEvents(input);
819
924
  const wasRawMode = Boolean(input.isRaw);
@@ -846,6 +951,9 @@ async function promptInteractiveTui({
846
951
 
847
952
  const targetDir = path.resolve(process.cwd(), targetInput);
848
953
  const state = await readState(targetDir);
954
+ const defaultProjectRulesMode = normalizeProjectRulesMode(
955
+ projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
956
+ );
849
957
  const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
850
958
 
851
959
  const defaultPresetId =
@@ -928,7 +1036,7 @@ async function promptInteractiveTui({
928
1036
  choices: tool.components.map((component) => ({
929
1037
  id: component.id,
930
1038
  label: `${component.title} (${component.id})`,
931
- description: component.description
1039
+ description: getComponentDescription(tool.id, component)
932
1040
  })),
933
1041
  preselectedIds: defaultComponents,
934
1042
  minSelected: 1
@@ -943,12 +1051,47 @@ async function promptInteractiveTui({
943
1051
  cliOverrides: installRootOverridesFromFlag
944
1052
  });
945
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
+
946
1088
  return {
947
1089
  targetDir,
948
1090
  selectedToolIds,
949
1091
  componentSelection,
950
1092
  presetId: selectedPreset?.id ?? null,
951
- installRootOverrides
1093
+ installRootOverrides,
1094
+ projectRulesMode
952
1095
  };
953
1096
  } finally {
954
1097
  setRawMode(wasRawMode);
@@ -961,7 +1104,8 @@ async function promptInteractiveText({
961
1104
  mode,
962
1105
  defaultTarget,
963
1106
  presetFlag,
964
- installRootOverridesFromFlag
1107
+ installRootOverridesFromFlag,
1108
+ projectRulesModeFromFlag
965
1109
  }) {
966
1110
  const rl = createInterface({ input, output });
967
1111
 
@@ -996,6 +1140,9 @@ async function promptInteractiveText({
996
1140
  }
997
1141
  const targetDir = path.resolve(process.cwd(), targetInput);
998
1142
  const state = await readState(targetDir);
1143
+ const defaultProjectRulesMode = normalizeProjectRulesMode(
1144
+ projectRulesModeFromFlag ?? (mode === 'update' ? state?.project_rules_mode : 'always')
1145
+ );
999
1146
 
1000
1147
  const presets = Array.isArray(catalog.presets) ? catalog.presets : [];
1001
1148
  let selectedPreset = findPreset(catalog, presetFlag);
@@ -1049,7 +1196,7 @@ async function promptInteractiveText({
1049
1196
  console.log(`📦 [${tool.title}] 구성요소 목록`);
1050
1197
  tool.components.forEach((component, index) => {
1051
1198
  console.log(`${index + 1}. ${component.title} (${component.id})`);
1052
- console.log(` - ${component.description}`);
1199
+ console.log(` - ${getComponentDescription(tool.id, component)}`);
1053
1200
  });
1054
1201
 
1055
1202
  let defaultComponents = tool.components.map((c) => c.id);
@@ -1084,12 +1231,32 @@ async function promptInteractiveText({
1084
1231
  cliOverrides: installRootOverridesFromFlag
1085
1232
  });
1086
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
+
1087
1253
  return {
1088
1254
  targetDir,
1089
1255
  selectedToolIds,
1090
1256
  componentSelection,
1091
1257
  presetId: selectedPreset?.id ?? null,
1092
- installRootOverrides
1258
+ installRootOverrides,
1259
+ projectRulesMode
1093
1260
  };
1094
1261
  } finally {
1095
1262
  rl.close();
@@ -1110,7 +1277,8 @@ function buildSelectionFromFlags({
1110
1277
  toolFlag,
1111
1278
  componentFlag,
1112
1279
  targetFlag,
1113
- installRootOverridesFromFlag
1280
+ installRootOverridesFromFlag,
1281
+ projectRulesModeFromFlag
1114
1282
  }) {
1115
1283
  const preset = findPreset(catalog, presetFlag);
1116
1284
  const presetToolIds = resolveToolsFromPreset(preset, catalog);
@@ -1153,7 +1321,8 @@ function buildSelectionFromFlags({
1153
1321
  selectedToolIds,
1154
1322
  componentSelection,
1155
1323
  presetId: preset?.id ?? null,
1156
- installRootOverrides
1324
+ installRootOverrides,
1325
+ projectRulesMode: normalizeProjectRulesMode(projectRulesModeFromFlag ?? 'always')
1157
1326
  };
1158
1327
  }
1159
1328
 
@@ -1179,6 +1348,54 @@ async function copyEntry(srcPath, destPath, { overwrite, dryRun }) {
1179
1348
  return { status: destExists ? 'overwritten' : 'copied' };
1180
1349
  }
1181
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
+
1182
1399
  function statusLabel(status) {
1183
1400
  switch (status) {
1184
1401
  case 'copied':
@@ -1199,17 +1416,17 @@ function statusLabel(status) {
1199
1416
  function statusIcon(status) {
1200
1417
  switch (status) {
1201
1418
  case 'copied':
1202
- return '';
1419
+ return '🟢';
1203
1420
  case 'overwritten':
1204
- return '♻️';
1421
+ return '🔵';
1205
1422
  case 'skipped':
1206
- return '⏭️';
1423
+ return '';
1207
1424
  case 'would-copy':
1208
- return '📝';
1425
+ return '🟡';
1209
1426
  case 'would-overwrite':
1210
- return '🛠️';
1427
+ return '🟣';
1211
1428
  default:
1212
- return '';
1429
+ return '';
1213
1430
  }
1214
1431
  }
1215
1432
 
@@ -1231,6 +1448,11 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
1231
1448
  console.log(`실행 타입 : ${dryRun ? 'dry-run (실제 파일 변경 없음)' : '실행 (파일 변경 반영)'}`);
1232
1449
  console.log(`프리셋 : ${selection.presetId ?? '수동/없음'}`);
1233
1450
  console.log(`대상 경로 : ${targetDir}`);
1451
+ console.log(
1452
+ `규칙 파일 : ${selection.projectRulesMode} (${describeProjectRulesMode(
1453
+ selection.projectRulesMode
1454
+ )})`
1455
+ );
1234
1456
  console.log(`선택 도구 : ${selection.selectedToolIds.length}개`);
1235
1457
 
1236
1458
  selection.selectedToolIds.forEach((toolId, index) => {
@@ -1262,6 +1484,20 @@ function printPlan(catalog, selection, mode, targetDir, dryRun = false) {
1262
1484
  }
1263
1485
  }
1264
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
+
1265
1501
  if (tool.install_root_basis) {
1266
1502
  console.log(` 경로 기준 : ${tool.install_root_basis}`);
1267
1503
  }
@@ -1347,6 +1583,7 @@ async function writeState({ catalog, targetDir, packageData, mode, selection })
1347
1583
  package_version: packageData.version,
1348
1584
  updated_at: new Date().toISOString(),
1349
1585
  preset_id: selection.presetId ?? null,
1586
+ project_rules_mode: normalizeProjectRulesMode(selection.projectRulesMode ?? 'always'),
1350
1587
  install_roots: installRoots,
1351
1588
  tools
1352
1589
  };
@@ -1409,6 +1646,19 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1409
1646
  status: result.status
1410
1647
  });
1411
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
+ }
1412
1662
  }
1413
1663
 
1414
1664
  console.log('');
@@ -1495,8 +1745,9 @@ function printList(catalog) {
1495
1745
  getComponentInstallPath(component)
1496
1746
  )}`
1497
1747
  );
1498
- if (component.description) {
1499
- console.log(` ${component.description}`);
1748
+ const description = getComponentDescription(tool.id, component);
1749
+ if (description) {
1750
+ console.log(` ${description}`);
1500
1751
  }
1501
1752
  });
1502
1753
  });
@@ -1520,7 +1771,7 @@ function printList(catalog) {
1520
1771
  }
1521
1772
 
1522
1773
  console.log('━━━━━━━━ 권장 흐름 ━━━━━━━━');
1523
- console.log(`1) pnpm dlx ${CLI_NAME} --interactive`);
1774
+ console.log(`1) npx --yes --package @dusky-bluehour/agent-service ${CLI_NAME} --interactive`);
1524
1775
  console.log('2) 설치 후 .tri-agent-manager/state.json 기준으로 update');
1525
1776
  console.log('3) update 시 필요한 도구만 부분 갱신');
1526
1777
  }
@@ -1563,6 +1814,9 @@ async function main() {
1563
1814
  }
1564
1815
 
1565
1816
  const installRootOverridesFromFlag = parseInstallRootFlag(options.installRootFlag, catalog);
1817
+ const projectRulesModeFromFlag = options.projectRulesFlag
1818
+ ? normalizeProjectRulesMode(options.projectRulesFlag)
1819
+ : null;
1566
1820
 
1567
1821
  const shouldUseInteractive =
1568
1822
  !options.nonInteractive &&
@@ -1574,7 +1828,8 @@ async function main() {
1574
1828
  mode: command,
1575
1829
  defaultTarget: options.targetFlag,
1576
1830
  presetFlag: options.presetFlag,
1577
- installRootOverridesFromFlag
1831
+ installRootOverridesFromFlag,
1832
+ projectRulesModeFromFlag
1578
1833
  })
1579
1834
  : buildSelectionFromFlags({
1580
1835
  catalog,
@@ -1583,12 +1838,16 @@ async function main() {
1583
1838
  toolFlag: options.toolFlag,
1584
1839
  componentFlag: options.componentFlag,
1585
1840
  targetFlag: options.targetFlag,
1586
- installRootOverridesFromFlag
1841
+ installRootOverridesFromFlag,
1842
+ projectRulesModeFromFlag
1587
1843
  });
1588
1844
 
1589
1845
  const stateForTarget = await readState(rawSelection.targetDir);
1590
1846
  const selection = {
1591
1847
  ...rawSelection,
1848
+ projectRulesMode: normalizeProjectRulesMode(
1849
+ projectRulesModeFromFlag ?? stateForTarget?.project_rules_mode ?? rawSelection.projectRulesMode
1850
+ ),
1592
1851
  installRootOverrides: buildInstallRootOverrides({
1593
1852
  catalog,
1594
1853
  selectedToolIds: rawSelection.selectedToolIds,