@dusky-bluehour/agent-service 0.6.4 → 0.6.6

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 (53) hide show
  1. package/README.md +33 -222
  2. package/antigravity/README.md +7 -1
  3. package/antigravity/agents/agent-catalog.json +5 -5
  4. package/antigravity/skills/change-safety-review/SKILL.md +40 -0
  5. package/antigravity/skills/code-review-and-improvement/SKILL.md +20 -0
  6. package/antigravity/skills/frontend-repetition-pack/SKILL.md +20 -0
  7. package/antigravity/skills/incident-response/SKILL.md +20 -0
  8. package/antigravity/skills/prd-to-production-pipeline/SKILL.md +21 -1
  9. package/antigravity/skills/release-and-operations/SKILL.md +20 -0
  10. package/antigravity/skills/security-hardening/SKILL.md +21 -1
  11. package/antigravity/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  12. package/antigravity/workflows/definitions/WF-FRONTEND-REFACTOR.workflow.yaml +38 -0
  13. package/antigravity/workflows/definitions/WF-INCIDENT-RESPONSE.workflow.yaml +41 -0
  14. package/antigravity/workflows/definitions/WF-PRD-TO-PRODUCTION.workflow.yaml +76 -0
  15. package/antigravity/workflows/definitions/WF-SECURITY-HARDENING.workflow.yaml +40 -0
  16. package/antigravity/workflows/definitions/WF-SERVICE-E2E.workflow.yaml +67 -0
  17. package/antigravity/workflows/workflow-catalog.json +5 -5
  18. package/catalog/tool-catalog.ko.json +1 -1
  19. package/claude-code/README.md +4 -1
  20. package/claude-code/agent-teams/team-catalog.json +7 -7
  21. package/claude-code/skills/change-safety-review/SKILL.md +40 -0
  22. package/claude-code/skills/code-review-and-improvement/SKILL.md +21 -1
  23. package/claude-code/skills/frontend-repetition-pack/SKILL.md +21 -1
  24. package/claude-code/skills/incident-response/SKILL.md +21 -1
  25. package/claude-code/skills/prd-to-production-pipeline/SKILL.md +21 -1
  26. package/claude-code/skills/release-and-operations/SKILL.md +21 -1
  27. package/claude-code/skills/security-hardening/SKILL.md +21 -1
  28. package/claude-code/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  29. package/claude-code/workflows/workflow-catalog.json +8 -8
  30. package/codex/README.md +5 -2
  31. package/codex/automations/automation-recipes.toml +4 -4
  32. package/codex/instructions/AGENTS.template.md +6 -5
  33. package/codex/skills/change-safety-review/SKILL.md +40 -0
  34. package/codex/skills/change-safety-review/agents/openai.yaml +4 -0
  35. package/codex/skills/code-review-and-improvement/SKILL.md +21 -1
  36. package/codex/skills/frontend-repetition-pack/SKILL.md +20 -0
  37. package/codex/skills/incident-response/SKILL.md +21 -1
  38. package/codex/skills/prd-to-production-pipeline/SKILL.md +21 -1
  39. package/codex/skills/release-and-operations/SKILL.md +20 -0
  40. package/codex/skills/security-hardening/SKILL.md +21 -1
  41. package/codex/skills/service-lifecycle-orchestration/SKILL.md +21 -1
  42. package/codex/workflows/workflow-catalog.json +6 -6
  43. package/common/antigravity/agent-catalog.json +72 -0
  44. package/common/antigravity/artifact-catalog.json +184 -0
  45. package/common/claude/subagent-catalog.json +419 -0
  46. package/common/claude/team-catalog.json +69 -0
  47. package/common/commands/command-catalog.json +942 -0
  48. package/common/skills/skill-catalog.json +566 -0
  49. package/common/workflows/workflow-catalog.json +1550 -0
  50. package/package.json +7 -4
  51. package/scripts/generate-from-common.mjs +387 -0
  52. package/scripts/init.mjs +81 -8
  53. package/scripts/validate.mjs +249 -2
package/package.json CHANGED
@@ -1,27 +1,30 @@
1
1
  {
2
2
  "name": "@dusky-bluehour/agent-service",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
7
  "description": "Service operation skills/workflows pack for Claude Code, Antigravity, and Codex",
8
8
  "type": "module",
9
9
  "bin": {
10
- "tri-agent-manager": "scripts/init.mjs",
11
- "tri-agent-os": "scripts/init.mjs"
10
+ "tri-agent-manager": "scripts/init.mjs"
12
11
  },
13
12
  "scripts": {
14
- "validate": "node scripts/validate.mjs",
13
+ "generate": "node scripts/generate-from-common.mjs",
14
+ "generate:check": "node scripts/generate-from-common.mjs --check",
15
+ "validate": "npm run generate:check && node scripts/validate.mjs",
15
16
  "pack:dry-run": "npm_config_cache=.npm-cache npm pack --dry-run",
16
17
  "prepublish:check": "npm run validate && npm run pack:dry-run",
17
18
  "prepublishOnly": "npm run prepublish:check"
18
19
  },
19
20
  "files": [
21
+ "common",
20
22
  "claude-code",
21
23
  "antigravity",
22
24
  "codex",
23
25
  "catalog/tool-catalog.ko.json",
24
26
  "scripts/init.mjs",
27
+ "scripts/generate-from-common.mjs",
25
28
  "scripts/validate.mjs",
26
29
  "README.md",
27
30
  "LICENSE"
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const rootDir = path.resolve(__dirname, '..');
10
+
11
+ const DEFAULT_TOOL_IDS = ['claude-code', 'codex', 'antigravity'];
12
+ const args = new Set(process.argv.slice(2));
13
+ const checkMode = args.has('--check');
14
+
15
+ function usageAndExit() {
16
+ console.log('Usage: node scripts/generate-from-common.mjs [--check]');
17
+ process.exit(1);
18
+ }
19
+
20
+ if (args.size > 1 || (args.size === 1 && !args.has('--check'))) {
21
+ usageAndExit();
22
+ }
23
+
24
+ async function readJson(relativePath) {
25
+ const fullPath = path.join(rootDir, relativePath);
26
+ const raw = await fs.readFile(fullPath, 'utf8');
27
+ return JSON.parse(raw);
28
+ }
29
+
30
+ function toPrettyJson(value) {
31
+ return `${JSON.stringify(value, null, 2)}\n`;
32
+ }
33
+
34
+ function quoteYamlString(value) {
35
+ const normalized = String(value ?? '');
36
+ return `"${normalized.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
37
+ }
38
+
39
+ function renderCodexOpenAiYaml(skill) {
40
+ const ui = skill.codex_openai ?? {};
41
+ return [
42
+ 'skill:',
43
+ ` display_name: ${quoteYamlString(ui.display_name ?? '')}`,
44
+ ` short_description: ${quoteYamlString(ui.short_description ?? '')}`,
45
+ ` default_prompt: ${quoteYamlString(ui.default_prompt ?? '')}`,
46
+ ''
47
+ ].join('\n');
48
+ }
49
+
50
+ function renderSkillMarkdown(skill, toolId) {
51
+ const name = skill.names?.[toolId] ?? skill.id;
52
+ const description = skill.descriptions?.[toolId];
53
+ const procedure = skill.procedures?.[toolId];
54
+ const qualityRules = skill.quality_rules?.[toolId];
55
+ const inputChecklist = skill.input_checklist;
56
+ const reportFormat = skill.report_format;
57
+ const stopConditions = skill.stop_conditions;
58
+
59
+ if (typeof description !== 'string' || !Array.isArray(procedure) || !Array.isArray(qualityRules)) {
60
+ throw new Error(`[skills] ${skill.id}(${toolId}) 정의가 불완전합니다.`);
61
+ }
62
+ if (!Array.isArray(inputChecklist) || !Array.isArray(reportFormat) || !Array.isArray(stopConditions)) {
63
+ throw new Error(`[skills] ${skill.id} 공통 체크리스트/리포트/중단조건 정의가 누락되었습니다.`);
64
+ }
65
+
66
+ const procedureLines = procedure.map((step, index) => `${index + 1}. ${step}`);
67
+ const checklistLines = inputChecklist.map((item) => `- ${item}`);
68
+ const reportLines = reportFormat.map((item) => `- ${item}`);
69
+ const stopLines = stopConditions.map((item) => `- ${item}`);
70
+ const qualityRuleLines = qualityRules.map((rule) => `- ${rule}`);
71
+
72
+ return [
73
+ '---',
74
+ `name: ${name}`,
75
+ `description: ${description}`,
76
+ '---',
77
+ '',
78
+ '# 시작 전 체크리스트',
79
+ '',
80
+ ...checklistLines,
81
+ '',
82
+ '# 실행 절차',
83
+ '',
84
+ ...procedureLines,
85
+ '',
86
+ '# 결과 보고 형식',
87
+ '',
88
+ ...reportLines,
89
+ '',
90
+ '# 중단 조건',
91
+ '',
92
+ ...stopLines,
93
+ '',
94
+ '# 품질 규칙',
95
+ '',
96
+ ...qualityRuleLines,
97
+ ''
98
+ ].join('\n');
99
+ }
100
+
101
+ function renderClaudeSubagentMarkdown(subagent) {
102
+ const tools = Array.isArray(subagent.tools) ? subagent.tools.join(', ') : '';
103
+ const bodyLines = Array.isArray(subagent.body_lines) ? subagent.body_lines : [];
104
+ return [
105
+ '---',
106
+ `name: ${subagent.name ?? subagent.id}`,
107
+ `description: ${subagent.description ?? ''}`,
108
+ `tools: ${tools}`,
109
+ `model: ${subagent.model ?? 'sonnet'}`,
110
+ '---',
111
+ '',
112
+ ...bodyLines,
113
+ ''
114
+ ].join('\n');
115
+ }
116
+
117
+ function renderYamlList(lines, indent, values) {
118
+ if (!Array.isArray(values) || values.length === 0) {
119
+ lines.push(`${indent}[]`);
120
+ return;
121
+ }
122
+ for (const value of values) {
123
+ lines.push(`${indent}- ${quoteYamlString(value)}`);
124
+ }
125
+ }
126
+
127
+ function renderAntigravityWorkflowDefinition(workflow) {
128
+ const lines = [];
129
+ lines.push('# generated by scripts/generate-from-common.mjs');
130
+ lines.push('workflow:');
131
+ lines.push(` id: ${quoteYamlString(workflow.id)}`);
132
+ lines.push(` name: ${quoteYamlString(workflow.name)}`);
133
+ lines.push(` summary: ${quoteYamlString(workflow.summary ?? '')}`);
134
+ lines.push(` orchestrator: ${quoteYamlString(workflow.orchestrator ?? '')}`);
135
+ lines.push(` when_to_use: ${quoteYamlString(workflow.when_to_use ?? '')}`);
136
+ lines.push(' stages:');
137
+
138
+ for (const stage of workflow.stages ?? []) {
139
+ lines.push(` - order: ${Number(stage.order ?? 0)}`);
140
+ lines.push(` stage_id: ${quoteYamlString(stage.stage_id ?? '')}`);
141
+ lines.push(` agent: ${quoteYamlString(stage.agent ?? '')}`);
142
+ lines.push(' commands:');
143
+ renderYamlList(lines, ' ', stage.commands);
144
+ lines.push(` input_artifact: ${quoteYamlString(stage.input_artifact ?? '')}`);
145
+ lines.push(` output_artifact: ${quoteYamlString(stage.output_artifact ?? '')}`);
146
+ lines.push(' exit_criteria:');
147
+ renderYamlList(lines, ' ', stage.exit_criteria);
148
+ }
149
+
150
+ lines.push(' final_outputs:');
151
+ renderYamlList(lines, ' ', workflow.final_outputs);
152
+ lines.push('');
153
+ return lines.join('\n');
154
+ }
155
+
156
+ function buildWorkflowCatalogByTool(commonWorkflowCatalog) {
157
+ const toolIds = Array.isArray(commonWorkflowCatalog.tools)
158
+ ? commonWorkflowCatalog.tools
159
+ : DEFAULT_TOOL_IDS;
160
+ const validToolSet = new Set(toolIds);
161
+
162
+ const workflowById = new Map();
163
+ for (const workflow of commonWorkflowCatalog.workflows ?? []) {
164
+ if (!workflow?.id || typeof workflow.id !== 'string') {
165
+ throw new Error('[workflows] workflow id가 비어 있습니다.');
166
+ }
167
+ if (workflowById.has(workflow.id)) {
168
+ throw new Error(`[workflows] workflow id 중복: ${workflow.id}`);
169
+ }
170
+ workflowById.set(workflow.id, workflow);
171
+ }
172
+
173
+ const catalogs = {};
174
+ for (const toolId of toolIds) {
175
+ const workflowPolicy = commonWorkflowCatalog.workflow_policy?.[toolId] ?? {};
176
+ const order = Array.isArray(commonWorkflowCatalog.workflow_order?.[toolId])
177
+ ? commonWorkflowCatalog.workflow_order[toolId]
178
+ : [];
179
+
180
+ const orderedIds = [];
181
+ const seen = new Set();
182
+ for (const workflowId of order) {
183
+ if (!workflowById.has(workflowId)) {
184
+ throw new Error(`[workflows] ${toolId} workflow_order가 없는 workflow를 참조합니다: ${workflowId}`);
185
+ }
186
+ const profile = workflowById.get(workflowId).profiles?.[toolId];
187
+ if (!profile) {
188
+ throw new Error(`[workflows] ${toolId} profile 누락: ${workflowId}`);
189
+ }
190
+ if (seen.has(workflowId)) {
191
+ throw new Error(`[workflows] ${toolId} workflow_order 중복: ${workflowId}`);
192
+ }
193
+ seen.add(workflowId);
194
+ orderedIds.push(workflowId);
195
+ }
196
+
197
+ for (const workflow of commonWorkflowCatalog.workflows ?? []) {
198
+ const profile = workflow.profiles?.[toolId];
199
+ if (!profile) continue;
200
+ if (seen.has(workflow.id)) continue;
201
+ seen.add(workflow.id);
202
+ orderedIds.push(workflow.id);
203
+ }
204
+
205
+ const workflows = orderedIds.map((workflowId) => {
206
+ const workflow = workflowById.get(workflowId);
207
+ const profile = workflow.profiles?.[toolId];
208
+ if (!profile) {
209
+ throw new Error(`[workflows] ${toolId} profile 누락: ${workflowId}`);
210
+ }
211
+ return {
212
+ id: workflowId,
213
+ ...profile
214
+ };
215
+ });
216
+
217
+ catalogs[toolId] = {
218
+ schema_version: '1.0.0',
219
+ workflow_policy: workflowPolicy,
220
+ workflows
221
+ };
222
+ }
223
+
224
+ for (const workflow of commonWorkflowCatalog.workflows ?? []) {
225
+ const profileTools = Object.keys(workflow.profiles ?? {});
226
+ for (const toolId of profileTools) {
227
+ if (!validToolSet.has(toolId)) {
228
+ throw new Error(`[workflows] workflow(${workflow.id})에 알 수 없는 tool profile이 있습니다: ${toolId}`);
229
+ }
230
+ }
231
+ }
232
+
233
+ return { toolIds, catalogs };
234
+ }
235
+
236
+ async function ensureDir(filePath) {
237
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
238
+ }
239
+
240
+ async function readIfExists(filePath) {
241
+ try {
242
+ return await fs.readFile(filePath, 'utf8');
243
+ } catch (error) {
244
+ if (error.code === 'ENOENT') {
245
+ return null;
246
+ }
247
+ throw error;
248
+ }
249
+ }
250
+
251
+ async function writeOrCheck(filePath, expected, state) {
252
+ const current = await readIfExists(filePath);
253
+
254
+ if (checkMode) {
255
+ if (current !== expected) {
256
+ state.outOfDate.push(path.relative(rootDir, filePath));
257
+ }
258
+ return;
259
+ }
260
+
261
+ if (current === expected) {
262
+ state.unchanged += 1;
263
+ return;
264
+ }
265
+
266
+ await ensureDir(filePath);
267
+ await fs.writeFile(filePath, expected, 'utf8');
268
+ state.written += 1;
269
+ }
270
+
271
+ async function pruneAntigravityWorkflowDefinitions(expectedFileNames, state) {
272
+ const dir = path.join(rootDir, 'antigravity', 'workflows', 'definitions');
273
+ await fs.mkdir(dir, { recursive: true });
274
+ const entries = await fs.readdir(dir, { withFileTypes: true });
275
+ for (const entry of entries) {
276
+ if (!entry.isFile() || !entry.name.endsWith('.workflow.yaml')) {
277
+ continue;
278
+ }
279
+ if (expectedFileNames.has(entry.name)) {
280
+ continue;
281
+ }
282
+ const removePath = path.join(dir, entry.name);
283
+ if (checkMode) {
284
+ state.outOfDate.push(path.relative(rootDir, removePath));
285
+ continue;
286
+ }
287
+ await fs.unlink(removePath);
288
+ state.deleted += 1;
289
+ }
290
+ }
291
+
292
+ async function main() {
293
+ const commands = await readJson('common/commands/command-catalog.json');
294
+ const commonWorkflowCatalog = await readJson('common/workflows/workflow-catalog.json');
295
+ const skillCatalog = await readJson('common/skills/skill-catalog.json');
296
+ const claudeSubagents = await readJson('common/claude/subagent-catalog.json');
297
+ const claudeTeams = await readJson('common/claude/team-catalog.json');
298
+ const antigravityAgents = await readJson('common/antigravity/agent-catalog.json');
299
+ const antigravityArtifacts = await readJson('common/antigravity/artifact-catalog.json');
300
+ const { toolIds, catalogs: workflowCatalogByTool } = buildWorkflowCatalogByTool(
301
+ commonWorkflowCatalog
302
+ );
303
+
304
+ const state = {
305
+ written: 0,
306
+ unchanged: 0,
307
+ deleted: 0,
308
+ outOfDate: []
309
+ };
310
+
311
+ for (const toolId of toolIds) {
312
+ const commandPath = path.join(rootDir, toolId, 'commands', 'command-catalog.json');
313
+ await writeOrCheck(commandPath, toPrettyJson(commands), state);
314
+ }
315
+
316
+ for (const toolId of toolIds) {
317
+ const catalog = workflowCatalogByTool?.[toolId];
318
+ if (!catalog) {
319
+ throw new Error(`[workflows] tool 누락: ${toolId}`);
320
+ }
321
+ const workflowPath = path.join(rootDir, toolId, 'workflows', 'workflow-catalog.json');
322
+ await writeOrCheck(workflowPath, toPrettyJson(catalog), state);
323
+ }
324
+
325
+ const antigravityWorkflowNames = new Set();
326
+ for (const workflow of workflowCatalogByTool.antigravity?.workflows ?? []) {
327
+ const filename = `${workflow.id}.workflow.yaml`;
328
+ antigravityWorkflowNames.add(filename);
329
+ const definitionPath = path.join(rootDir, 'antigravity', 'workflows', 'definitions', filename);
330
+ await writeOrCheck(definitionPath, renderAntigravityWorkflowDefinition(workflow), state);
331
+ }
332
+ await pruneAntigravityWorkflowDefinitions(antigravityWorkflowNames, state);
333
+
334
+ for (const skill of skillCatalog.skills ?? []) {
335
+ for (const toolId of toolIds) {
336
+ const skillPath = path.join(rootDir, toolId, 'skills', skill.id, 'SKILL.md');
337
+ await writeOrCheck(skillPath, renderSkillMarkdown(skill, toolId), state);
338
+ }
339
+
340
+ const codexYamlPath = path.join(rootDir, 'codex', 'skills', skill.id, 'agents', 'openai.yaml');
341
+ await writeOrCheck(codexYamlPath, renderCodexOpenAiYaml(skill), state);
342
+ }
343
+
344
+ for (const subagent of claudeSubagents.subagents ?? []) {
345
+ const subagentPath = path.join(rootDir, 'claude-code', 'subagents', `${subagent.id}.md`);
346
+ await writeOrCheck(subagentPath, renderClaudeSubagentMarkdown(subagent), state);
347
+ }
348
+
349
+ await writeOrCheck(
350
+ path.join(rootDir, 'claude-code', 'agent-teams', 'team-catalog.json'),
351
+ toPrettyJson(claudeTeams),
352
+ state
353
+ );
354
+
355
+ await writeOrCheck(
356
+ path.join(rootDir, 'antigravity', 'agents', 'agent-catalog.json'),
357
+ toPrettyJson(antigravityAgents),
358
+ state
359
+ );
360
+
361
+ await writeOrCheck(
362
+ path.join(rootDir, 'antigravity', 'artifacts', 'artifact-catalog.json'),
363
+ toPrettyJson(antigravityArtifacts),
364
+ state
365
+ );
366
+
367
+ if (checkMode) {
368
+ if (state.outOfDate.length > 0) {
369
+ console.error('Generated files are out of date:');
370
+ for (const relPath of [...new Set(state.outOfDate)].sort()) {
371
+ console.error(`- ${relPath}`);
372
+ }
373
+ process.exit(1);
374
+ }
375
+ console.log('All generated files are up to date.');
376
+ return;
377
+ }
378
+
379
+ console.log(
380
+ `Generation complete. written=${state.written}, unchanged=${state.unchanged}, deleted=${state.deleted}`
381
+ );
382
+ }
383
+
384
+ main().catch((error) => {
385
+ console.error(error instanceof Error ? error.message : String(error));
386
+ process.exit(1);
387
+ });
package/scripts/init.mjs CHANGED
@@ -13,7 +13,6 @@ const rootDir = path.resolve(__dirname, '..');
13
13
  const catalogPath = path.join(rootDir, 'catalog', 'tool-catalog.ko.json');
14
14
  const packageJsonPath = path.join(rootDir, 'package.json');
15
15
  const CLI_NAME = 'tri-agent-manager';
16
- const CLI_COMPAT_NAME = 'tri-agent-os';
17
16
  const STATE_DIR_NAME = '.tri-agent-manager';
18
17
  const LEGACY_STATE_DIR_NAME = '.tri-agent-os';
19
18
  const GUIDE_FILE_NAME = 'USAGE.ko.md';
@@ -42,14 +41,11 @@ const HELP_TEXT = [
42
41
  ' --non-interactive 비대화형 모드 강제',
43
42
  ' (중복 선택 입력은 자동으로 1회로 정리됨)',
44
43
  '',
45
- '호환 별칭:',
46
- ` ${CLI_COMPAT_NAME} ... (동일 동작)`,
47
- '',
48
44
  'pnpm 예시:',
49
- ' pnpm dlx tri-agent-manager --interactive',
50
- ' pnpm dlx tri-agent-manager update --interactive',
51
- ' pnpm dlx tri-agent-manager setup',
52
- ` pnpm exec ${CLI_NAME} list`
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'
53
49
  ].join('\n');
54
50
 
55
51
  function parseArgs(argv) {
@@ -439,6 +435,11 @@ function getToolExecutionGuide(tool, installRoot) {
439
435
  'workflows',
440
436
  'workflow-catalog.json'
441
437
  )}\`에서 workflow ID를 선택합니다.`,
438
+ `선택한 workflow ID와 같은 이름의 정의 파일을 \`${path.join(
439
+ installRoot,
440
+ 'workflows',
441
+ 'definitions'
442
+ )}\`에서 확인합니다.`,
442
443
  `각 stage의 \`input_artifact\`를 \`${path.join(
443
444
  installRoot,
444
445
  'artifacts'
@@ -450,6 +451,57 @@ function getToolExecutionGuide(tool, installRoot) {
450
451
  return ['선택한 workflow의 stage.commands를 순서대로 실행합니다.'];
451
452
  }
452
453
 
454
+ function getToolInteractionTips(tool, installRoot) {
455
+ if (tool.id === 'codex') {
456
+ return [
457
+ 'Codex 대화 입력창에서 `$`를 누르면 사용 가능한 스킬 목록을 바로 열 수 있습니다.',
458
+ `프로젝트 루트에서 스킬 파일을 직접 확인하려면 \`ls ${path.join(
459
+ installRoot,
460
+ 'skills'
461
+ )}\` 를 실행하세요.`,
462
+ `워크플로우 목록과 설명은 \`${path.join(
463
+ installRoot,
464
+ 'workflows',
465
+ 'workflow-catalog.json'
466
+ )}\` 에서 확인하세요.`
467
+ ];
468
+ }
469
+
470
+ if (tool.id === 'claude-code') {
471
+ return [
472
+ `서브에이전트 이름은 \`${path.join(installRoot, 'agents')}\` 경로의 파일명 기준입니다.`,
473
+ `프로젝트 루트에서 \`ls ${path.join(
474
+ installRoot,
475
+ 'agents'
476
+ )} | sed 's/\\.md$//'\` 로 서브에이전트 이름을 확인하세요.`,
477
+ `Claude 스킬 목록은 \`ls ${path.join(installRoot, 'skills')}\` 로 확인하세요.`
478
+ ];
479
+ }
480
+
481
+ if (tool.id === 'antigravity') {
482
+ return [
483
+ `워크플로우 선택은 \`${path.join(
484
+ installRoot,
485
+ 'workflows',
486
+ 'workflow-catalog.json'
487
+ )}\` 에서 WF ID를 먼저 고르는 방식으로 진행하세요.`,
488
+ `실행용 정의 파일은 \`${path.join(
489
+ installRoot,
490
+ 'workflows',
491
+ 'definitions'
492
+ )}\` 의 \`*.workflow.yaml\`을 사용하세요.`,
493
+ `에이전트 역할은 \`${path.join(installRoot, 'agents', 'agent-catalog.json')}\` 에서 확인하세요.`,
494
+ `입출력 아티팩트 스키마는 \`${path.join(
495
+ installRoot,
496
+ 'artifacts',
497
+ 'artifact-catalog.json'
498
+ )}\` 기준으로 맞추세요.`
499
+ ];
500
+ }
501
+
502
+ return ['도구별 workflow 문서와 skill 문서를 기준으로 실행 순서를 고정하세요.'];
503
+ }
504
+
453
505
  function renderMarkdownTable(headers, rows) {
454
506
  const head = `| ${headers.join(' | ')} |`;
455
507
  const divider = `| ${headers.map(() => '---').join(' | ')} |`;
@@ -508,6 +560,14 @@ async function writeUsageGuide({ catalog, selection, targetDir, mode }) {
508
560
  lines.push(`${index + 1}. ${step}`);
509
561
  });
510
562
 
563
+ lines.push('');
564
+ lines.push('### 도구별 실행 팁');
565
+ lines.push('아래 팁은 프로젝트 루트에서 바로 확인할 수 있는 방법입니다.');
566
+ const interactionTips = getToolInteractionTips(tool, installRoot);
567
+ interactionTips.forEach((tip, index) => {
568
+ lines.push(`${index + 1}. ${tip}`);
569
+ });
570
+
511
571
  if (workflowSummary.length > 0) {
512
572
  lines.push('');
513
573
  lines.push('### 워크플로우 선택 가이드');
@@ -1305,6 +1365,15 @@ async function writeState({ catalog, targetDir, packageData, mode, selection })
1305
1365
  await fs.writeFile(statePath, `${JSON.stringify(statePayload, null, 2)}\n`, 'utf8');
1306
1366
  }
1307
1367
 
1368
+ async function cleanupLegacyStateDir(targetDir) {
1369
+ const legacyDir = path.join(targetDir, LEGACY_STATE_DIR_NAME);
1370
+ if (!(await exists(legacyDir))) {
1371
+ return false;
1372
+ }
1373
+ await fs.rm(legacyDir, { recursive: true, force: true });
1374
+ return true;
1375
+ }
1376
+
1308
1377
  async function runInstallOrUpdate({ catalog, packageData, mode, selection, force, dryRun }) {
1309
1378
  const overwrite = mode === 'update' || force;
1310
1379
  const targetDir = selection.targetDir;
@@ -1397,9 +1466,13 @@ async function runInstallOrUpdate({ catalog, packageData, mode, selection, force
1397
1466
  if (!dryRun) {
1398
1467
  const generatedGuidePath = await writeUsageGuide({ catalog, selection, targetDir, mode });
1399
1468
  await writeState({ catalog, targetDir, packageData, mode, selection });
1469
+ const legacyStateRemoved = await cleanupLegacyStateDir(targetDir);
1400
1470
  console.log('');
1401
1471
  console.log(`실행 가이드 저장: ${generatedGuidePath}`);
1402
1472
  console.log(`상태 파일 저장: ${path.join(targetDir, STATE_DIR_NAME, 'state.json')}`);
1473
+ if (legacyStateRemoved) {
1474
+ console.log(`레거시 상태 폴더 정리: ${path.join(targetDir, LEGACY_STATE_DIR_NAME)}`);
1475
+ }
1403
1476
  }
1404
1477
  }
1405
1478