aiwf 0.3.5 → 0.3.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 (170) hide show
  1. package/.aiwf/00_PROJECT_MANIFEST.md +29 -16
  2. package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/M03_milestone_meta.md +55 -0
  3. package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/SPECS_AIWF_Simplified_Plan.md +221 -0
  4. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/S03_M02_sprint_meta.md +0 -1
  5. package/.aiwf/test-results-s02.json +5 -5
  6. package/.claude/commands/aiwf/aiwf_add_task_to_sprint.md +283 -0
  7. package/.claude/commands/aiwf/aiwf_install_git_hooks.md +63 -0
  8. package/.claude/commands/aiwf/aiwf_link_feature_branch.md +43 -0
  9. package/.claude/commands/aiwf/aiwf_link_feature_commit.md +46 -0
  10. package/package.json +1 -1
  11. package/src/cli/index.js +17 -2
  12. package/src/commands/create-project.js +10 -7
  13. package/src/commands/sprint-task.js +262 -0
  14. package/src/lib/file-downloader.js +5 -0
  15. package/src/lib/installer.js +14 -4
  16. package/tests/integration/test-persona-system/.aiwf/metrics/metrics.json +833 -0
  17. package/tests/unit/sprint-task.test.js +172 -0
  18. package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +0 -7
  19. package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +0 -7
  20. package/.aiwf/sessions/session_1752877860664_huziadmbo.json +0 -7
  21. package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +0 -7
  22. package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +0 -7
  23. package/.aiwf/sessions/session_1752877861874_y32sputuc.json +0 -7
  24. package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +0 -7
  25. package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +0 -7
  26. package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +0 -7
  27. package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +0 -7
  28. package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +0 -7
  29. package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +0 -7
  30. package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +0 -7
  31. package/.aiwf/sessions/session_1752877866509_dawntfpab.json +0 -7
  32. package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +0 -7
  33. package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +0 -7
  34. package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +0 -7
  35. package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +0 -7
  36. package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +0 -7
  37. package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +0 -7
  38. package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +0 -7
  39. package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +0 -7
  40. package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +0 -7
  41. package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +0 -7
  42. package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +0 -7
  43. package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +0 -7
  44. package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +0 -7
  45. package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +0 -7
  46. package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +0 -7
  47. package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +0 -7
  48. package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +0 -7
  49. package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +0 -7
  50. package/.aiwf/sessions/session_1752877874454_heicyuryb.json +0 -7
  51. package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +0 -7
  52. package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +0 -7
  53. package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +0 -7
  54. package/.aiwf/sessions/session_1752877876218_44807kztz.json +0 -7
  55. package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +0 -7
  56. package/.aiwf/sessions/session_1752877876543_p605aud1m.json +0 -7
  57. package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +0 -7
  58. package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +0 -7
  59. package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +0 -7
  60. package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +0 -7
  61. package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +0 -7
  62. package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +0 -7
  63. package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +0 -7
  64. package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +0 -7
  65. package/.aiwf/sessions/session_1752877878886_20pjsfour.json +0 -7
  66. package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +0 -7
  67. package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +0 -7
  68. package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +0 -7
  69. package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +0 -7
  70. package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +0 -7
  71. package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +0 -7
  72. package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +0 -7
  73. package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +0 -7
  74. package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +0 -7
  75. package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +0 -7
  76. package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +0 -7
  77. package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +0 -7
  78. package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +0 -7
  79. package/.aiwf/sessions/session_1752877884733_hk46eenel.json +0 -7
  80. package/.aiwf/sessions/session_1752877884738_c62j6214r.json +0 -7
  81. package/.aiwf/sessions/session_1752877884771_temhxehoz.json +0 -7
  82. package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +0 -7
  83. package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +0 -7
  84. package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +0 -7
  85. package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +0 -7
  86. package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +0 -7
  87. package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +0 -7
  88. package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +0 -7
  89. package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +0 -7
  90. package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +0 -7
  91. package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +0 -7
  92. package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +0 -7
  93. package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +0 -7
  94. package/.aiwf/sessions/session_1752877889956_szphy2qma.json +0 -7
  95. package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +0 -7
  96. package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +0 -7
  97. package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +0 -7
  98. package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +0 -7
  99. package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +0 -7
  100. package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +0 -7
  101. package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +0 -7
  102. package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +0 -7
  103. package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +0 -7
  104. package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +0 -7
  105. package/.aiwf/sessions/session_1752877894082_x3bghawox.json +0 -7
  106. package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +0 -7
  107. package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +0 -7
  108. package/test-token-system.js +0 -88
  109. package/tests/temp_invalid_json/invalid.json +0 -1
  110. /package/{.aiwf/templates → templates}/README.md +0 -0
  111. /package/{.aiwf/templates → templates}/api-server/config.json +0 -0
  112. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/config.json +0 -0
  113. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/feature-ledger.json +0 -0
  114. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/personas/backend-engineer.json +0 -0
  115. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/scripts/cli.js +0 -0
  116. /package/{.aiwf/templates → templates}/api-server/template/.env.example +0 -0
  117. /package/{.aiwf/templates → templates}/api-server/template/.eslintrc.json +0 -0
  118. /package/{.aiwf/templates → templates}/api-server/template/README.md +0 -0
  119. /package/{.aiwf/templates → templates}/api-server/template/jest.config.js +0 -0
  120. /package/{.aiwf/templates → templates}/api-server/template/nodemon.json +0 -0
  121. /package/{.aiwf/templates → templates}/api-server/template/package.json +0 -0
  122. /package/{.aiwf/templates → templates}/api-server/template/src/app.ts +0 -0
  123. /package/{.aiwf/templates → templates}/api-server/template/src/config/swagger.ts +0 -0
  124. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/aiwfController.ts +0 -0
  125. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/authController.ts +0 -0
  126. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/statusController.ts +0 -0
  127. /package/{.aiwf/templates → templates}/api-server/template/src/index.ts +0 -0
  128. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/aiwfMiddleware.ts +0 -0
  129. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/authMiddleware.ts +0 -0
  130. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/errorHandler.ts +0 -0
  131. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/notFoundHandler.ts +0 -0
  132. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/requestLogger.ts +0 -0
  133. /package/{.aiwf/templates → templates}/api-server/template/src/routes/aiwf.ts +0 -0
  134. /package/{.aiwf/templates → templates}/api-server/template/src/routes/index.ts +0 -0
  135. /package/{.aiwf/templates → templates}/api-server/template/src/routes/v1/index.ts +0 -0
  136. /package/{.aiwf/templates → templates}/api-server/template/src/utils/logger.ts +0 -0
  137. /package/{.aiwf/templates → templates}/api-server/template/tests/app.test.ts +0 -0
  138. /package/{.aiwf/templates → templates}/api-server/template/tsconfig.json +0 -0
  139. /package/{.aiwf/templates → templates}/npm-library/config.json +0 -0
  140. /package/{.aiwf/templates → templates}/npm-library/template/LICENSE +0 -0
  141. /package/{.aiwf/templates → templates}/npm-library/template/README.md +0 -0
  142. /package/{.aiwf/templates → templates}/npm-library/template/package.json +0 -0
  143. /package/{.aiwf/templates → templates}/web-app/config.json +0 -0
  144. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/config.json +0 -0
  145. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/feature-ledger.json +0 -0
  146. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/personas/fullstack-developer.json +0 -0
  147. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/scripts/cli.js +0 -0
  148. /package/{.aiwf/templates → templates}/web-app/template/.eslintrc.cjs +0 -0
  149. /package/{.aiwf/templates → templates}/web-app/template/README.md +0 -0
  150. /package/{.aiwf/templates → templates}/web-app/template/index.html +0 -0
  151. /package/{.aiwf/templates → templates}/web-app/template/package.json +0 -0
  152. /package/{.aiwf/templates → templates}/web-app/template/postcss.config.js +0 -0
  153. /package/{.aiwf/templates → templates}/web-app/template/public/vite.svg +0 -0
  154. /package/{.aiwf/templates → templates}/web-app/template/src/App.tsx +0 -0
  155. /package/{.aiwf/templates → templates}/web-app/template/src/components/Layout.tsx +0 -0
  156. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -0
  157. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -0
  158. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -0
  159. /package/{.aiwf/templates → templates}/web-app/template/src/index.css +0 -0
  160. /package/{.aiwf/templates → templates}/web-app/template/src/main.tsx +0 -0
  161. /package/{.aiwf/templates → templates}/web-app/template/src/pages/AiwfDashboard.tsx +0 -0
  162. /package/{.aiwf/templates → templates}/web-app/template/src/pages/HomePage.tsx +0 -0
  163. /package/{.aiwf/templates → templates}/web-app/template/src/pages/NotFound.tsx +0 -0
  164. /package/{.aiwf/templates → templates}/web-app/template/src/stores/aiwfStore.ts +0 -0
  165. /package/{.aiwf/templates → templates}/web-app/template/src/types/global.d.ts +0 -0
  166. /package/{.aiwf/templates → templates}/web-app/template/src/vite-env.d.ts +0 -0
  167. /package/{.aiwf/templates → templates}/web-app/template/tailwind.config.js +0 -0
  168. /package/{.aiwf/templates → templates}/web-app/template/tsconfig.json +0 -0
  169. /package/{.aiwf/templates → templates}/web-app/template/tsconfig.node.json +0 -0
  170. /package/{.aiwf/templates → templates}/web-app/template/vite.config.ts +0 -0
@@ -0,0 +1,63 @@
1
+ # Install Git Hooks
2
+
3
+ Install or manage AIWF Git hooks for automatic Feature-Git integration.
4
+
5
+ ## Command Usage
6
+
7
+ ```bash
8
+ # Install hooks
9
+ /project:aiwf:install_git_hooks
10
+
11
+ # Check status
12
+ /project:aiwf:install_git_hooks status
13
+
14
+ # Uninstall hooks
15
+ /project:aiwf:install_git_hooks uninstall
16
+ ```
17
+
18
+ ## What It Does
19
+
20
+ ### Install (default)
21
+ 1. Backs up existing Git hooks if present
22
+ 2. Installs AIWF pre-commit hook
23
+ 3. Installs AIWF post-commit hook
24
+ 4. Installs AIWF post-merge hook
25
+
26
+ ### Status
27
+ - Shows which hooks are installed
28
+ - Indicates if AIWF hooks or other hooks are active
29
+
30
+ ### Uninstall
31
+ - Removes AIWF hooks
32
+ - Restores original hooks from backup if available
33
+
34
+ ## Hooks Functionality
35
+
36
+ ### Pre-commit Hook
37
+ - Scans staged files for Feature ID references
38
+ - Prepares for feature tracking
39
+
40
+ ### Post-commit Hook
41
+ - Extracts Feature ID from commit message
42
+ - Automatically links commit to feature
43
+ - Updates feature status (planned → active)
44
+
45
+ ### Post-merge Hook
46
+ - Detects merges to main/master branch
47
+ - Updates feature status to completed
48
+ - Finalizes feature metrics
49
+
50
+ ## Implementation
51
+
52
+ Execute the following command:
53
+
54
+ ```bash
55
+ node .aiwf/scripts/install-git-hooks.cjs {{COMMAND}}
56
+ ```
57
+
58
+ ## Notes
59
+
60
+ - Must be run from a Git repository root
61
+ - Preserves existing hooks as .aiwf-backup files
62
+ - Hooks are non-intrusive and fast
63
+ - Can be safely installed/uninstalled anytime
@@ -0,0 +1,43 @@
1
+ # Link Feature Branch
2
+
3
+ Link the current Git branch to a Feature in the Feature Ledger.
4
+
5
+ ## Command Usage
6
+
7
+ ```bash
8
+ /project:aiwf:link_feature_branch <feature-id>
9
+ ```
10
+
11
+ ## Parameters
12
+
13
+ - `feature-id`: The Feature ID to link the current branch to (e.g., FL001)
14
+
15
+ ## What It Does
16
+
17
+ 1. Gets the current Git branch name
18
+ 2. Updates the feature's git_branch field
19
+ 3. Establishes the branch-feature relationship
20
+
21
+ ## Examples
22
+
23
+ ```bash
24
+ # On branch feature/FL001-user-auth
25
+ /project:aiwf:link_feature_branch FL001
26
+
27
+ # Creates the link between current branch and feature
28
+ ```
29
+
30
+ ## Implementation
31
+
32
+ Execute the following command:
33
+
34
+ ```bash
35
+ node .aiwf/scripts/git-integration.cjs link-branch {{FEATURE_ID}}
36
+ ```
37
+
38
+ ## Notes
39
+
40
+ - Must be run from within a Git repository
41
+ - The current branch will be linked to the feature
42
+ - Overwrites any existing branch link
43
+ - Useful when branch naming doesn't follow the FL### pattern
@@ -0,0 +1,46 @@
1
+ # Link Feature Commit
2
+
3
+ Manually link a Git commit to a Feature in the Feature Ledger.
4
+
5
+ ## Command Usage
6
+
7
+ ```bash
8
+ /project:aiwf:link_feature_commit <feature-id> <commit-hash>
9
+ ```
10
+
11
+ ## Parameters
12
+
13
+ - `feature-id`: The Feature ID (e.g., FL001)
14
+ - `commit-hash`: The Git commit hash to link (full or short hash)
15
+
16
+ ## What It Does
17
+
18
+ 1. Retrieves commit information (message, author, date, stats)
19
+ 2. Links the commit to the specified feature
20
+ 3. Updates feature status if needed (planned → active)
21
+ 4. Records the commit in the feature's git_commits array
22
+
23
+ ## Examples
24
+
25
+ ```bash
26
+ # Link a specific commit to feature FL001
27
+ /project:aiwf:link_feature_commit FL001 abc123def
28
+
29
+ # Link the latest commit
30
+ /project:aiwf:link_feature_commit FL002 HEAD
31
+ ```
32
+
33
+ ## Implementation
34
+
35
+ Execute the following command:
36
+
37
+ ```bash
38
+ node .aiwf/scripts/git-integration.cjs link-commit {{FEATURE_ID}} {{COMMIT_HASH}}
39
+ ```
40
+
41
+ ## Notes
42
+
43
+ - The commit must exist in the current repository
44
+ - The feature file must exist in the Feature Ledgers
45
+ - Duplicate commits are automatically ignored
46
+ - Feature status automatically updates from 'planned' to 'active' on first commit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiwf",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "AI Workflow Framework for Claude Code with multi-language support (Korean/English)",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/cli/index.js CHANGED
@@ -6,6 +6,7 @@ import { installAIWF } from '../lib/installer.js';
6
6
  import AIToolCommand from '../commands/ai-tool.js';
7
7
  import PersonaCommand from '../commands/persona.js';
8
8
  import { CacheCLI } from './cache-cli.js';
9
+ import { addTaskToSprint } from '../commands/sprint-task.js';
9
10
 
10
11
  // Parse version from package.json
11
12
  import { readFileSync } from 'fs';
@@ -22,9 +23,9 @@ program
22
23
  .version(packageJson.version)
23
24
  .description('AIWF - AI Workflow Framework');
24
25
 
25
- // Install command (default action)
26
+ // Install command
26
27
  program
27
- .command('install', { isDefault: true })
28
+ .command('install')
28
29
  .description('Install AIWF framework')
29
30
  .option('-f, --force', 'Force install without prompts')
30
31
  .action((options) => installAIWF({ ...options, debugLog: true }));
@@ -202,5 +203,19 @@ persona
202
203
  await personaCmd.resetPersona();
203
204
  });
204
205
 
206
+ // Sprint task management command
207
+ program
208
+ .command('sprint-task <sprintId> <taskTitle>')
209
+ .alias('st')
210
+ .description('Add a task to an existing sprint / 스프린트에 태스크 추가')
211
+ .action(async (sprintId, taskTitle) => {
212
+ try {
213
+ await addTaskToSprint(sprintId, taskTitle);
214
+ } catch (error) {
215
+ console.error(chalk.red(`오류: ${error.message}`));
216
+ process.exit(1);
217
+ }
218
+ });
219
+
205
220
  // Parse command line arguments
206
221
  program.parse(process.argv);
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const prompts = require('prompts');
6
- const chalk = require('chalk');
7
- const ora = require('ora');
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import prompts from 'prompts';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
8
9
 
9
- const TEMPLATES_DIR = path.join(__dirname, '../templates');
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const TEMPLATES_DIR = path.join(__dirname, '../../templates');
10
13
 
11
14
  async function createProject() {
12
15
  console.log(chalk.cyan('\n🚀 AIWF 프로젝트 생성기\n'));
@@ -148,4 +151,4 @@ if (require.main === module) {
148
151
  });
149
152
  }
150
153
 
151
- module.exports = { createProject };
154
+ export { createProject };
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 스프린트 태스크 관리 명령어
5
+ * 스프린트에 개별 태스크를 추가하고 관리하는 기능
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import chalk from 'chalk';
11
+ import ora from 'ora';
12
+
13
+ // 프로젝트 루트에서 .aiwf 디렉토리 찾기
14
+ async function findProjectRoot(startDir = process.cwd()) {
15
+ let currentDir = startDir;
16
+
17
+ while (currentDir !== path.parse(currentDir).root) {
18
+ try {
19
+ const aiwfPath = path.join(currentDir, '.aiwf');
20
+ await fs.access(aiwfPath);
21
+ return currentDir;
22
+ } catch {
23
+ currentDir = path.dirname(currentDir);
24
+ }
25
+ }
26
+
27
+ throw new Error('.aiwf 디렉토리를 찾을 수 없습니다. AIWF 프로젝트 내에서 실행해주세요.');
28
+ }
29
+
30
+ // 스프린트 존재 확인
31
+ async function verifySprintExists(projectRoot, sprintId) {
32
+ const sprintsDir = path.join(projectRoot, '.aiwf', '03_SPRINTS');
33
+
34
+ try {
35
+ const entries = await fs.readdir(sprintsDir);
36
+ const sprintDir = entries.find(entry => entry.startsWith(sprintId + '_'));
37
+
38
+ if (!sprintDir) {
39
+ throw new Error(`스프린트 ${sprintId}를 찾을 수 없습니다.`);
40
+ }
41
+
42
+ return path.join(sprintsDir, sprintDir);
43
+ } catch (error) {
44
+ throw new Error(`스프린트 디렉토리 읽기 실패: ${error.message}`);
45
+ }
46
+ }
47
+
48
+ // 다음 태스크 번호 결정
49
+ async function getNextTaskNumber(sprintPath, sprintId) {
50
+ try {
51
+ const files = await fs.readdir(sprintPath);
52
+ const taskFiles = files.filter(f => f.match(/^T\d+_/));
53
+
54
+ if (taskFiles.length === 0) {
55
+ return 'T01';
56
+ }
57
+
58
+ const numbers = taskFiles.map(f => {
59
+ const match = f.match(/^T(\d+)_/);
60
+ return match ? parseInt(match[1]) : 0;
61
+ });
62
+
63
+ const maxNumber = Math.max(...numbers);
64
+ return `T${String(maxNumber + 1).padStart(2, '0')}`;
65
+ } catch {
66
+ return 'T01';
67
+ }
68
+ }
69
+
70
+ // 태스크 파일 생성
71
+ async function createTaskFile(sprintPath, taskNumber, sprintId, taskTitle) {
72
+ const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
73
+ const fileName = `${taskNumber}_${sprintId}_${taskTitle.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_가-힣]/g, '')}.md`;
74
+ const filePath = path.join(sprintPath, fileName);
75
+
76
+ const content = `# ${taskNumber}: ${taskTitle}
77
+
78
+ **스프린트**: ${sprintId}
79
+ **생성일**: ${timestamp}
80
+ **상태**: pending
81
+ **우선순위**: medium
82
+ **예상 시간**: TBD
83
+
84
+ ## 개요
85
+
86
+ [태스크에 대한 간단한 설명을 작성하세요]
87
+
88
+ ## 요구사항
89
+
90
+ - [ ] [구체적인 요구사항 1]
91
+ - [ ] [구체적인 요구사항 2]
92
+ - [ ] [구체적인 요구사항 3]
93
+
94
+ ## 승인 기준
95
+
96
+ - [ ] [완료 조건 1]
97
+ - [ ] [완료 조건 2]
98
+ - [ ] [완료 조건 3]
99
+
100
+ ## 기술 가이드라인
101
+
102
+ - [관련 아키텍처 결정 참조]
103
+ - [기존 코드베이스 패턴 참조]
104
+ - [사용할 기술 스택]
105
+
106
+ ## 구현 노트
107
+
108
+ - [구현 시 고려사항]
109
+ - [잠재적 리스크]
110
+ - [테스트 전략]
111
+
112
+ ## 참조
113
+
114
+ - [관련 문서나 코드 링크]
115
+ `;
116
+
117
+ await fs.writeFile(filePath, content);
118
+ return { filePath, fileName, taskNumber };
119
+ }
120
+
121
+ // 스프린트 메타 파일 업데이트
122
+ async function updateSprintMeta(sprintPath, taskNumber, taskTitle) {
123
+ const files = await fs.readdir(sprintPath);
124
+ const metaFile = files.find(f => f.includes('sprint_meta.md'));
125
+
126
+ if (!metaFile) {
127
+ console.warn(chalk.yellow('⚠️ 스프린트 메타 파일을 찾을 수 없습니다.'));
128
+ return;
129
+ }
130
+
131
+ const metaPath = path.join(sprintPath, metaFile);
132
+ let content = await fs.readFile(metaPath, 'utf-8');
133
+
134
+ // 태스크 섹션 찾기 및 업데이트
135
+ const taskSectionRegex = /## 태스크 목록[\s\S]*?(?=##|$)/;
136
+ const taskMatch = content.match(taskSectionRegex);
137
+
138
+ if (taskMatch) {
139
+ const newTaskLine = `- [ ] ${taskNumber}: ${taskTitle} - 상태: pending\n`;
140
+ const updatedSection = taskMatch[0] + newTaskLine;
141
+ content = content.replace(taskSectionRegex, updatedSection);
142
+ } else {
143
+ // 태스크 섹션이 없으면 추가
144
+ content += `\n## 태스크 목록\n\n- [ ] ${taskNumber}: ${taskTitle} - 상태: pending\n`;
145
+ }
146
+
147
+ await fs.writeFile(metaPath, content);
148
+ }
149
+
150
+ // 매니페스트 업데이트
151
+ async function updateManifest(projectRoot, sprintId, taskNumber, taskTitle) {
152
+ const manifestPath = path.join(projectRoot, '.aiwf', '00_PROJECT_MANIFEST.md');
153
+
154
+ try {
155
+ let content = await fs.readFile(manifestPath, 'utf-8');
156
+
157
+ // 최종 업데이트 날짜 변경
158
+ const dateRegex = /\*\*마지막 업데이트\*\*: \d{4}-\d{2}-\d{2}/;
159
+ const today = new Date().toISOString().slice(0, 10);
160
+ content = content.replace(dateRegex, `**마지막 업데이트**: ${today}`);
161
+
162
+ // 활성 태스크 섹션에 추가
163
+ const activeTasksRegex = /## 활성 태스크[\s\S]*?### 계획됨/;
164
+ const match = content.match(activeTasksRegex);
165
+
166
+ if (match) {
167
+ const newTaskLine = `\n- **${taskNumber}_${sprintId}**: ${taskTitle} (pending → ${today} 생성)\n`;
168
+ const updatedSection = match[0].replace('### 계획됨', newTaskLine + '### 계획됨');
169
+ content = content.replace(activeTasksRegex, updatedSection);
170
+ }
171
+
172
+ // 태스크 통계 업데이트
173
+ const statsRegex = /- \*\*총 태스크\*\*: (\d+)/;
174
+ const statsMatch = content.match(statsRegex);
175
+ if (statsMatch) {
176
+ const total = parseInt(statsMatch[1]) + 1;
177
+ content = content.replace(statsRegex, `- **총 태스크**: ${total}`);
178
+ }
179
+
180
+ await fs.writeFile(manifestPath, content);
181
+ } catch (error) {
182
+ console.warn(chalk.yellow(`⚠️ 매니페스트 업데이트 실패: ${error.message}`));
183
+ }
184
+ }
185
+
186
+ // 메인 함수
187
+ export async function addTaskToSprint(sprintId, taskTitle) {
188
+ const spinner = ora('스프린트에 태스크 추가 중...').start();
189
+
190
+ try {
191
+ // 1. 프로젝트 루트 찾기
192
+ const projectRoot = await findProjectRoot();
193
+
194
+ // 2. 스프린트 존재 확인
195
+ spinner.text = '스프린트 확인 중...';
196
+ const sprintPath = await verifySprintExists(projectRoot, sprintId);
197
+
198
+ // 3. 다음 태스크 번호 결정
199
+ spinner.text = '태스크 번호 결정 중...';
200
+ const taskNumber = await getNextTaskNumber(sprintPath, sprintId);
201
+
202
+ // 4. 태스크 파일 생성
203
+ spinner.text = '태스크 파일 생성 중...';
204
+ const { filePath, fileName, taskNumber: finalTaskNumber } = await createTaskFile(
205
+ sprintPath,
206
+ taskNumber,
207
+ sprintId,
208
+ taskTitle
209
+ );
210
+
211
+ // 5. 스프린트 메타 업데이트
212
+ spinner.text = '스프린트 메타 업데이트 중...';
213
+ await updateSprintMeta(sprintPath, finalTaskNumber, taskTitle);
214
+
215
+ // 6. 매니페스트 업데이트
216
+ spinner.text = '프로젝트 매니페스트 업데이트 중...';
217
+ await updateManifest(projectRoot, sprintId, finalTaskNumber, taskTitle);
218
+
219
+ spinner.succeed('태스크 추가 완료!');
220
+
221
+ // 성공 메시지
222
+ console.log('\n' + chalk.green('✅ 태스크 추가 완료!'));
223
+ console.log('\n📄 생성된 파일: ' + chalk.cyan(fileName));
224
+ console.log('🏃 스프린트: ' + chalk.yellow(sprintId));
225
+ console.log('📋 태스크: ' + chalk.yellow(`${finalTaskNumber} - ${taskTitle}`));
226
+ console.log('⏱️ 상태: ' + chalk.blue('pending'));
227
+
228
+ console.log('\n' + chalk.bold('📝 다음 단계:'));
229
+ console.log('1. 태스크 파일을 열어 세부사항 작성');
230
+ console.log(`2. ${chalk.cyan(`/aiwf:do_task ${finalTaskNumber}`)}로 작업 시작`);
231
+ console.log(`3. 완료 후 ${chalk.cyan(`/aiwf:update_task_status ${finalTaskNumber} completed`)} 실행`);
232
+
233
+ console.log('\n💡 팁: 태스크 목록 확인은 ' + chalk.cyan(`/aiwf:list_sprint_tasks ${sprintId}`));
234
+
235
+ return { success: true, taskNumber: finalTaskNumber, filePath };
236
+
237
+ } catch (error) {
238
+ spinner.fail(`태스크 추가 실패: ${error.message}`);
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ // CLI 실행
244
+ if (import.meta.url === `file://${process.argv[1]}`) {
245
+ const args = process.argv.slice(2);
246
+
247
+ if (args.length < 2) {
248
+ console.error(chalk.red('사용법: aiwf-sprint-task <스프린트ID> <태스크 제목>'));
249
+ console.error(chalk.gray('예시: aiwf-sprint-task S03 "API 성능 최적화"'));
250
+ process.exit(1);
251
+ }
252
+
253
+ const [sprintId, ...titleParts] = args;
254
+ const taskTitle = titleParts.join(' ').replace(/^["']|["']$/g, '');
255
+
256
+ addTaskToSprint(sprintId, taskTitle).catch(error => {
257
+ console.error(chalk.red(`오류: ${error.message}`));
258
+ process.exit(1);
259
+ });
260
+ }
261
+
262
+ export default addTaskToSprint;
@@ -1,5 +1,6 @@
1
1
  import https from 'https';
2
2
  import { createWriteStream } from 'fs';
3
+ import fs from 'fs/promises';
3
4
  import { pipeline } from 'stream/promises';
4
5
  import path from 'path';
5
6
 
@@ -109,6 +110,10 @@ async function fetchContent(url) {
109
110
  * @returns {Promise<void>}
110
111
  */
111
112
  async function downloadFile(url, destPath) {
113
+ // Ensure destination directory exists
114
+ const destDir = path.dirname(destPath);
115
+ await fs.mkdir(destDir, { recursive: true });
116
+
112
117
  return new Promise((resolve, reject) => {
113
118
  https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (response) => {
114
119
  if (response.statusCode !== 200) {
@@ -182,9 +182,14 @@ async function handleExistingInstallation(msg, options) {
182
182
  async function createDirectoryStructure() {
183
183
  const aiwfDirs = getAllAiwfDirectories();
184
184
 
185
+ // Create AIWF directories
185
186
  for (const dir of aiwfDirs) {
186
187
  await fs.mkdir(dir, { recursive: true });
187
188
  }
189
+
190
+ // Create tool-specific directories
191
+ await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
192
+ await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
188
193
  }
189
194
 
190
195
  /**
@@ -301,7 +306,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
301
306
  // Try to download rules/global from GitHub
302
307
  try {
303
308
  await downloadDirectory(
304
- `${GITHUB_CONTENT_PREFIX}/rules/global`,
309
+ `rules/global`,
305
310
  tmpRulesGlobal,
306
311
  spinner,
307
312
  debugLog
@@ -310,20 +315,25 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
310
315
  } catch (error) {
311
316
  logWithSpinner(spinner, `Failed to download rules/global from GitHub, trying local files`, debugLog);
312
317
  // Fallback to local files
313
- const localRulesGlobal = 'rules/global';
318
+ const localRulesGlobal = path.join(process.cwd(), 'rules/global');
314
319
  try {
320
+ logWithSpinner(spinner, `Trying to access: ${localRulesGlobal}`, debugLog);
315
321
  await fs.access(localRulesGlobal);
322
+ logWithSpinner(spinner, `Successfully accessed local rules/global`, debugLog);
316
323
  // Copy local files to temp directory
317
324
  await fs.mkdir(tmpRulesGlobal, { recursive: true });
318
325
  const files = await fs.readdir(localRulesGlobal);
326
+ logWithSpinner(spinner, `Found ${files.length} files in rules/global: ${files.join(', ')}`, debugLog);
319
327
  for (const file of files) {
320
328
  const srcPath = path.join(localRulesGlobal, file);
321
329
  const destPath = path.join(tmpRulesGlobal, file);
322
330
  await fs.copyFile(srcPath, destPath);
331
+ logWithSpinner(spinner, `Copied: ${file}`, debugLog);
323
332
  }
324
333
  useLocalGlobal = true;
325
334
  logWithSpinner(spinner, `Using local rules/global directory`, debugLog);
326
335
  } catch (localError) {
336
+ logWithSpinner(spinner, `Local rules/global error: ${localError.message}`, debugLog);
327
337
  logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
328
338
  }
329
339
  }
@@ -331,7 +341,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
331
341
  // Try to download rules/manual from GitHub
332
342
  try {
333
343
  await downloadDirectory(
334
- `${GITHUB_CONTENT_PREFIX}/rules/manual`,
344
+ `rules/manual`,
335
345
  tmpRulesManual,
336
346
  spinner,
337
347
  debugLog
@@ -340,7 +350,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
340
350
  } catch (error) {
341
351
  logWithSpinner(spinner, `Failed to download rules/manual from GitHub, trying local files`, debugLog);
342
352
  // Fallback to local files
343
- const localRulesManual = 'rules/manual';
353
+ const localRulesManual = path.join(process.cwd(), 'rules/manual');
344
354
  try {
345
355
  await fs.access(localRulesManual);
346
356
  // Copy local files to temp directory