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.
- package/.aiwf/00_PROJECT_MANIFEST.md +29 -16
- package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/M03_milestone_meta.md +55 -0
- package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/SPECS_AIWF_Simplified_Plan.md +221 -0
- package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/S03_M02_sprint_meta.md +0 -1
- package/.aiwf/test-results-s02.json +5 -5
- package/.claude/commands/aiwf/aiwf_add_task_to_sprint.md +283 -0
- package/.claude/commands/aiwf/aiwf_install_git_hooks.md +63 -0
- package/.claude/commands/aiwf/aiwf_link_feature_branch.md +43 -0
- package/.claude/commands/aiwf/aiwf_link_feature_commit.md +46 -0
- package/package.json +1 -1
- package/src/cli/index.js +17 -2
- package/src/commands/create-project.js +10 -7
- package/src/commands/sprint-task.js +262 -0
- package/src/lib/file-downloader.js +5 -0
- package/src/lib/installer.js +14 -4
- package/tests/integration/test-persona-system/.aiwf/metrics/metrics.json +833 -0
- package/tests/unit/sprint-task.test.js +172 -0
- package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +0 -7
- package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +0 -7
- package/.aiwf/sessions/session_1752877860664_huziadmbo.json +0 -7
- package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +0 -7
- package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +0 -7
- package/.aiwf/sessions/session_1752877861874_y32sputuc.json +0 -7
- package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +0 -7
- package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +0 -7
- package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +0 -7
- package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +0 -7
- package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +0 -7
- package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +0 -7
- package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +0 -7
- package/.aiwf/sessions/session_1752877866509_dawntfpab.json +0 -7
- package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +0 -7
- package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +0 -7
- package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +0 -7
- package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +0 -7
- package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +0 -7
- package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +0 -7
- package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +0 -7
- package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +0 -7
- package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +0 -7
- package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +0 -7
- package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +0 -7
- package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +0 -7
- package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +0 -7
- package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +0 -7
- package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +0 -7
- package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +0 -7
- package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +0 -7
- package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +0 -7
- package/.aiwf/sessions/session_1752877874454_heicyuryb.json +0 -7
- package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +0 -7
- package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +0 -7
- package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +0 -7
- package/.aiwf/sessions/session_1752877876218_44807kztz.json +0 -7
- package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +0 -7
- package/.aiwf/sessions/session_1752877876543_p605aud1m.json +0 -7
- package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +0 -7
- package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +0 -7
- package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +0 -7
- package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +0 -7
- package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +0 -7
- package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +0 -7
- package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +0 -7
- package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +0 -7
- package/.aiwf/sessions/session_1752877878886_20pjsfour.json +0 -7
- package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +0 -7
- package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +0 -7
- package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +0 -7
- package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +0 -7
- package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +0 -7
- package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +0 -7
- package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +0 -7
- package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +0 -7
- package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +0 -7
- package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +0 -7
- package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +0 -7
- package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +0 -7
- package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +0 -7
- package/.aiwf/sessions/session_1752877884733_hk46eenel.json +0 -7
- package/.aiwf/sessions/session_1752877884738_c62j6214r.json +0 -7
- package/.aiwf/sessions/session_1752877884771_temhxehoz.json +0 -7
- package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +0 -7
- package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +0 -7
- package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +0 -7
- package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +0 -7
- package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +0 -7
- package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +0 -7
- package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +0 -7
- package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +0 -7
- package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +0 -7
- package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +0 -7
- package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +0 -7
- package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +0 -7
- package/.aiwf/sessions/session_1752877889956_szphy2qma.json +0 -7
- package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +0 -7
- package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +0 -7
- package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +0 -7
- package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +0 -7
- package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +0 -7
- package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +0 -7
- package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +0 -7
- package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +0 -7
- package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +0 -7
- package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +0 -7
- package/.aiwf/sessions/session_1752877894082_x3bghawox.json +0 -7
- package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +0 -7
- package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +0 -7
- package/test-token-system.js +0 -88
- package/tests/temp_invalid_json/invalid.json +0 -1
- /package/{.aiwf/templates → templates}/README.md +0 -0
- /package/{.aiwf/templates → templates}/api-server/config.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.aiwf/config.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.aiwf/feature-ledger.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.aiwf/personas/backend-engineer.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.aiwf/scripts/cli.js +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.env.example +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/.eslintrc.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/README.md +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/jest.config.js +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/nodemon.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/package.json +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/app.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/config/swagger.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/controllers/aiwfController.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/controllers/authController.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/controllers/statusController.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/index.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/middleware/aiwfMiddleware.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/middleware/authMiddleware.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/middleware/errorHandler.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/middleware/notFoundHandler.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/middleware/requestLogger.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/routes/aiwf.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/routes/index.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/routes/v1/index.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/src/utils/logger.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/tests/app.test.ts +0 -0
- /package/{.aiwf/templates → templates}/api-server/template/tsconfig.json +0 -0
- /package/{.aiwf/templates → templates}/npm-library/config.json +0 -0
- /package/{.aiwf/templates → templates}/npm-library/template/LICENSE +0 -0
- /package/{.aiwf/templates → templates}/npm-library/template/README.md +0 -0
- /package/{.aiwf/templates → templates}/npm-library/template/package.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/config.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/.aiwf/config.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/.aiwf/feature-ledger.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/.aiwf/personas/fullstack-developer.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/.aiwf/scripts/cli.js +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/.eslintrc.cjs +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/README.md +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/index.html +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/package.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/postcss.config.js +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/public/vite.svg +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/App.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/components/Layout.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/index.css +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/main.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/pages/AiwfDashboard.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/pages/HomePage.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/pages/NotFound.tsx +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/stores/aiwfStore.ts +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/types/global.d.ts +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/src/vite-env.d.ts +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/tailwind.config.js +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/tsconfig.json +0 -0
- /package/{.aiwf/templates → templates}/web-app/template/tsconfig.node.json +0 -0
- /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
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
|
|
26
|
+
// Install command
|
|
26
27
|
program
|
|
27
|
-
.command('install'
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|
package/src/lib/installer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|