@sk8metal/michi-cli 0.3.0 → 0.5.0

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 (237) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/scripts/__tests__/spec-impl-workflow.test.js +4 -2
  3. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -1
  4. package/dist/scripts/config/config-schema.d.ts +52 -0
  5. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  6. package/dist/scripts/config/config-schema.js +25 -0
  7. package/dist/scripts/config/config-schema.js.map +1 -1
  8. package/dist/scripts/config-global.d.ts +10 -0
  9. package/dist/scripts/config-global.d.ts.map +1 -0
  10. package/dist/scripts/config-global.js +111 -0
  11. package/dist/scripts/config-global.js.map +1 -0
  12. package/dist/scripts/confluence-sync.d.ts +22 -4
  13. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  14. package/dist/scripts/confluence-sync.js +22 -12
  15. package/dist/scripts/confluence-sync.js.map +1 -1
  16. package/dist/scripts/jira-sync.d.ts.map +1 -1
  17. package/dist/scripts/jira-sync.js +201 -167
  18. package/dist/scripts/jira-sync.js.map +1 -1
  19. package/dist/scripts/list-projects.js.map +1 -1
  20. package/dist/scripts/multi-project-estimate.js.map +1 -1
  21. package/dist/scripts/phase-runner.d.ts +1 -1
  22. package/dist/scripts/phase-runner.d.ts.map +1 -1
  23. package/dist/scripts/phase-runner.js +295 -522
  24. package/dist/scripts/phase-runner.js.map +1 -1
  25. package/dist/scripts/pr-automation.d.ts.map +1 -1
  26. package/dist/scripts/pr-automation.js +11 -3
  27. package/dist/scripts/pr-automation.js.map +1 -1
  28. package/dist/scripts/pre-flight-check.d.ts.map +1 -1
  29. package/dist/scripts/pre-flight-check.js +10 -6
  30. package/dist/scripts/pre-flight-check.js.map +1 -1
  31. package/dist/scripts/resource-dashboard.js.map +1 -1
  32. package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
  33. package/dist/scripts/spec-impl-workflow.js +23 -7
  34. package/dist/scripts/spec-impl-workflow.js.map +1 -1
  35. package/dist/scripts/template/renderer.d.ts +1 -1
  36. package/dist/scripts/template/renderer.d.ts.map +1 -1
  37. package/dist/scripts/test-interactive.d.ts.map +1 -1
  38. package/dist/scripts/test-interactive.js +0 -15
  39. package/dist/scripts/test-interactive.js.map +1 -1
  40. package/dist/scripts/test-new-features.js +6 -3
  41. package/dist/scripts/test-new-features.js.map +1 -1
  42. package/dist/scripts/test-spec-generator.d.ts.map +1 -1
  43. package/dist/scripts/test-spec-generator.js +1 -2
  44. package/dist/scripts/test-spec-generator.js.map +1 -1
  45. package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
  46. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
  47. package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
  48. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  49. package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
  50. package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
  51. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
  52. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
  53. package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
  54. package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
  55. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
  56. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
  57. package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
  58. package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
  59. package/dist/scripts/utils/config-loader.d.ts +14 -3
  60. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  61. package/dist/scripts/utils/config-loader.js +284 -46
  62. package/dist/scripts/utils/config-loader.js.map +1 -1
  63. package/dist/scripts/utils/config-sections.d.ts +54 -0
  64. package/dist/scripts/utils/config-sections.d.ts.map +1 -0
  65. package/dist/scripts/utils/config-sections.js +178 -0
  66. package/dist/scripts/utils/config-sections.js.map +1 -0
  67. package/dist/scripts/utils/config-validator.d.ts +4 -0
  68. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  69. package/dist/scripts/utils/config-validator.js +57 -1
  70. package/dist/scripts/utils/config-validator.js.map +1 -1
  71. package/dist/scripts/utils/confluence-approval.d.ts.map +1 -1
  72. package/dist/scripts/utils/confluence-approval.js +5 -3
  73. package/dist/scripts/utils/confluence-approval.js.map +1 -1
  74. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  75. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  76. package/dist/scripts/utils/env-config.d.ts +1 -1
  77. package/dist/scripts/utils/env-config.d.ts.map +1 -1
  78. package/dist/scripts/utils/env-config.js +2 -14
  79. package/dist/scripts/utils/env-config.js.map +1 -1
  80. package/dist/scripts/utils/interactive-helpers.d.ts +32 -0
  81. package/dist/scripts/utils/interactive-helpers.d.ts.map +1 -0
  82. package/dist/scripts/utils/interactive-helpers.js +92 -0
  83. package/dist/scripts/utils/interactive-helpers.js.map +1 -0
  84. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -1
  85. package/dist/scripts/utils/jira-issue-type-fetcher.js +27 -18
  86. package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -1
  87. package/dist/scripts/utils/project-meta.d.ts +9 -0
  88. package/dist/scripts/utils/project-meta.d.ts.map +1 -1
  89. package/dist/scripts/utils/project-meta.js +22 -0
  90. package/dist/scripts/utils/project-meta.js.map +1 -1
  91. package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -1
  92. package/dist/scripts/utils/release-notes-generator.js +2 -1
  93. package/dist/scripts/utils/release-notes-generator.js.map +1 -1
  94. package/dist/scripts/utils/security-validator.d.ts +55 -0
  95. package/dist/scripts/utils/security-validator.d.ts.map +1 -0
  96. package/dist/scripts/utils/security-validator.js +232 -0
  97. package/dist/scripts/utils/security-validator.js.map +1 -0
  98. package/dist/scripts/utils/spec-updater.d.ts +19 -0
  99. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  100. package/dist/scripts/utils/spec-updater.js.map +1 -1
  101. package/dist/scripts/utils/tasks-converter.d.ts.map +1 -1
  102. package/dist/scripts/utils/tasks-converter.js +2 -2
  103. package/dist/scripts/utils/tasks-converter.js.map +1 -1
  104. package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -1
  105. package/dist/scripts/utils/tasks-format-validator.js +0 -12
  106. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  107. package/dist/scripts/utils/test-runner.d.ts.map +1 -1
  108. package/dist/scripts/utils/test-runner.js +3 -2
  109. package/dist/scripts/utils/test-runner.js.map +1 -1
  110. package/dist/scripts/validate-phase.d.ts +1 -1
  111. package/dist/scripts/validate-phase.d.ts.map +1 -1
  112. package/dist/scripts/validate-phase.js +12 -62
  113. package/dist/scripts/validate-phase.js.map +1 -1
  114. package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
  115. package/dist/scripts/workflow-orchestrator.js +11 -16
  116. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  117. package/dist/src/__tests__/integration/setup/init.test.d.ts +5 -0
  118. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +1 -0
  119. package/dist/src/__tests__/integration/setup/init.test.js +352 -0
  120. package/dist/src/__tests__/integration/setup/init.test.js.map +1 -0
  121. package/dist/src/cli.d.ts.map +1 -1
  122. package/dist/src/cli.js +67 -21
  123. package/dist/src/cli.js.map +1 -1
  124. package/dist/src/commands/__tests__/init.test.d.ts +5 -0
  125. package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
  126. package/dist/src/commands/__tests__/init.test.js +255 -0
  127. package/dist/src/commands/__tests__/init.test.js.map +1 -0
  128. package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
  129. package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
  130. package/dist/src/commands/__tests__/migrate.test.js +216 -0
  131. package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
  132. package/dist/src/commands/config-validate.d.ts +9 -0
  133. package/dist/src/commands/config-validate.d.ts.map +1 -0
  134. package/dist/src/commands/config-validate.js +90 -0
  135. package/dist/src/commands/config-validate.js.map +1 -0
  136. package/dist/src/commands/init.d.ts +29 -0
  137. package/dist/src/commands/init.d.ts.map +1 -0
  138. package/dist/src/commands/init.js +513 -0
  139. package/dist/src/commands/init.js.map +1 -0
  140. package/dist/src/commands/migrate.d.ts +25 -0
  141. package/dist/src/commands/migrate.d.ts.map +1 -0
  142. package/dist/src/commands/migrate.js +341 -0
  143. package/dist/src/commands/migrate.js.map +1 -0
  144. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  145. package/dist/src/commands/setup-existing.js +0 -1
  146. package/dist/src/commands/setup-existing.js.map +1 -1
  147. package/dist/vitest.config.d.ts.map +1 -1
  148. package/dist/vitest.config.js +32 -8
  149. package/dist/vitest.config.js.map +1 -1
  150. package/docs/michi-development/design/config-unification.md +4789 -0
  151. package/docs/user-guide/getting-started/github-token-setup.md +2 -1
  152. package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
  153. package/docs/user-guide/getting-started/quick-start.md +1 -1
  154. package/docs/user-guide/getting-started/setup.md +35 -14
  155. package/docs/user-guide/guides/customization.md +64 -11
  156. package/docs/user-guide/guides/workflow.md +35 -21
  157. package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
  158. package/docs/user-guide/hands-on/claude-setup.md +2 -2
  159. package/docs/user-guide/hands-on/cursor-setup.md +2 -2
  160. package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
  161. package/docs/user-guide/reference/config.md +30 -5
  162. package/docs/user-guide/reference/quick-reference.md +68 -74
  163. package/docs/user-guide/testing/test-planning-flow.md +4 -0
  164. package/env.example +1 -1
  165. package/package.json +3 -5
  166. package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
  167. package/scripts/config/config-schema.ts +40 -0
  168. package/scripts/config-global.ts +160 -0
  169. package/scripts/confluence-sync.ts +91 -27
  170. package/scripts/jira-sync.ts +284 -218
  171. package/scripts/list-projects.ts +2 -2
  172. package/scripts/multi-project-estimate.ts +3 -3
  173. package/scripts/phase-runner.ts +391 -594
  174. package/scripts/pr-automation.ts +15 -5
  175. package/scripts/pre-flight-check.ts +20 -9
  176. package/scripts/pre-publish-check.sh +3 -34
  177. package/scripts/resource-dashboard.ts +4 -4
  178. package/scripts/spec-impl-workflow.ts +23 -7
  179. package/scripts/template/renderer.ts +1 -1
  180. package/scripts/test-interactive.ts +0 -19
  181. package/scripts/test-new-features.ts +10 -7
  182. package/scripts/test-npm-package.sh +3 -34
  183. package/scripts/test-spec-generator.ts +3 -7
  184. package/scripts/utils/__tests__/config-loader.test.ts +149 -0
  185. package/scripts/utils/__tests__/config-validator.test.ts +2 -0
  186. package/scripts/utils/__tests__/env-config.test.ts +0 -2
  187. package/scripts/utils/__tests__/project-meta.test.ts +192 -0
  188. package/scripts/utils/__tests__/security-validator.test.ts +272 -0
  189. package/scripts/utils/config-loader.ts +328 -68
  190. package/scripts/utils/config-sections.ts +316 -0
  191. package/scripts/utils/config-validator.ts +66 -1
  192. package/scripts/utils/confluence-approval.ts +8 -6
  193. package/scripts/utils/confluence-hierarchy.ts +27 -27
  194. package/scripts/utils/env-config.ts +2 -14
  195. package/scripts/utils/interactive-helpers.ts +135 -0
  196. package/scripts/utils/jira-issue-type-fetcher.ts +29 -21
  197. package/scripts/utils/project-meta.ts +27 -0
  198. package/scripts/utils/release-notes-generator.ts +3 -2
  199. package/scripts/utils/security-validator.ts +286 -0
  200. package/scripts/utils/spec-updater.ts +37 -15
  201. package/scripts/utils/tasks-converter.ts +4 -6
  202. package/scripts/utils/tasks-format-validator.ts +0 -13
  203. package/scripts/utils/test-runner.ts +4 -3
  204. package/scripts/validate-phase.ts +21 -80
  205. package/scripts/workflow-orchestrator.ts +16 -25
  206. package/templates/claude/commands/kiro/kiro-spec-impl.md +5 -1
  207. package/templates/claude/commands/kiro/kiro-spec-tasks.md +3 -1
  208. package/templates/claude/commands/michi/confluence-sync.md +8 -2
  209. package/templates/claude/commands/michi/design-review.md +4 -0
  210. package/templates/claude/commands/michi/e2e-plan.md +4 -0
  211. package/templates/claude/commands/michi/license-check.md +4 -0
  212. package/templates/claude/commands/michi/pr-resolve.md +4 -0
  213. package/templates/claude/commands/michi/project-switch.md +8 -2
  214. package/templates/claude/commands/michi/spec-design.md +78 -0
  215. package/templates/claude/commands/michi/spec-impl.md +716 -0
  216. package/templates/claude/commands/michi/test-planning.md +174 -0
  217. package/templates/claude/commands/michi/validate-design.md +58 -0
  218. package/templates/claude/commands/michi/version-audit.md +4 -0
  219. package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
  220. package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
  221. package/templates/michi/cc-sdd-overrides/README.md +8 -0
  222. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +53 -0
  223. package/dist/scripts/config-interactive.d.ts +0 -10
  224. package/dist/scripts/config-interactive.d.ts.map +0 -1
  225. package/dist/scripts/config-interactive.js +0 -372
  226. package/dist/scripts/config-interactive.js.map +0 -1
  227. package/dist/scripts/setup-existing-project.d.ts +0 -15
  228. package/dist/scripts/setup-existing-project.d.ts.map +0 -1
  229. package/dist/scripts/setup-existing-project.js +0 -455
  230. package/dist/scripts/setup-existing-project.js.map +0 -1
  231. package/dist/scripts/setup-interactive.d.ts +0 -10
  232. package/dist/scripts/setup-interactive.d.ts.map +0 -1
  233. package/dist/scripts/setup-interactive.js +0 -413
  234. package/dist/scripts/setup-interactive.js.map +0 -1
  235. package/scripts/config-interactive.ts +0 -550
  236. package/scripts/setup-existing-project.ts +0 -585
  237. package/scripts/setup-interactive.ts +0 -565
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 対話的設定の共通ヘルパー関数
3
+ */
4
+
5
+ import * as readline from 'readline';
6
+
7
+ /**
8
+ * readlineインターフェースを作成
9
+ */
10
+ export function createInterface(): readline.Interface {
11
+ return readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ });
15
+ }
16
+
17
+ /**
18
+ * 質問を表示して回答を取得
19
+ */
20
+ export function question(rl: readline.Interface, query: string): Promise<string> {
21
+ return new Promise((resolve) => {
22
+ rl.question(query, (answer) => {
23
+ resolve(answer.trim());
24
+ });
25
+ });
26
+ }
27
+
28
+ /**
29
+ * 選択肢から選択
30
+ */
31
+ export async function select(
32
+ rl: readline.Interface,
33
+ prompt: string,
34
+ choices: Array<{ value: string; label: string; description?: string }>,
35
+ defaultValue?: string,
36
+ maxRetries: number = 3,
37
+ ): Promise<string> {
38
+ if (choices.length === 0) {
39
+ throw new Error('select: choices must contain at least one option');
40
+ }
41
+
42
+ console.log(`\n${prompt}`);
43
+ choices.forEach((choice, index) => {
44
+ const defaultMark = defaultValue === choice.value ? ' (デフォルト)' : '';
45
+ const desc = choice.description ? ` - ${choice.description}` : '';
46
+ console.log(` ${index + 1}. ${choice.label}${desc}${defaultMark}`);
47
+ });
48
+
49
+ const answer = await question(
50
+ rl,
51
+ `\n選択してください [1-${choices.length}]: `,
52
+ );
53
+
54
+ if (!answer && defaultValue) {
55
+ return defaultValue;
56
+ }
57
+
58
+ const index = parseInt(answer, 10) - 1;
59
+ if (index >= 0 && index < choices.length) {
60
+ return choices[index].value;
61
+ }
62
+
63
+ if (defaultValue) {
64
+ return defaultValue;
65
+ }
66
+
67
+ // 無効な入力の場合は再試行(最大試行回数まで)
68
+ if (maxRetries > 0) {
69
+ console.log(
70
+ `⚠️ 無効な選択です。もう一度入力してください(残り試行回数: ${maxRetries})。`,
71
+ );
72
+ return select(rl, prompt, choices, defaultValue, maxRetries - 1);
73
+ }
74
+
75
+ // 最大試行回数に達した場合はデフォルト値または最初の選択肢を返す
76
+ // 注: choices.length > 0 は関数冒頭で保証されている
77
+ const fallbackValue = defaultValue || choices[0].value;
78
+ console.log(
79
+ `⚠️ 最大試行回数に達しました。デフォルト値を使用します: ${fallbackValue}`,
80
+ );
81
+ return fallbackValue;
82
+ }
83
+
84
+ /**
85
+ * Yes/No質問
86
+ */
87
+ export async function confirm(
88
+ rl: readline.Interface,
89
+ prompt: string,
90
+ defaultValue: boolean = true,
91
+ ): Promise<boolean> {
92
+ const defaultText = defaultValue ? '[Y/n]' : '[y/N]';
93
+ const answer = await question(rl, `${prompt} ${defaultText}: `);
94
+
95
+ if (!answer) {
96
+ return defaultValue;
97
+ }
98
+
99
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
100
+ }
101
+
102
+ /**
103
+ * 複数選択
104
+ */
105
+ export async function multiSelect(
106
+ rl: readline.Interface,
107
+ prompt: string,
108
+ choices: Array<{ value: string; label: string }>,
109
+ defaults: string[] = [],
110
+ ): Promise<string[]> {
111
+ console.log(`\n${prompt}`);
112
+ choices.forEach((choice, index) => {
113
+ const checked = defaults.includes(choice.value) ? '[x]' : '[ ]';
114
+ console.log(` ${checked} ${index + 1}. ${choice.label}`);
115
+ });
116
+
117
+ const answer = await question(
118
+ rl,
119
+ '\n選択してください(カンマ区切り、例: 1,2,3): ',
120
+ );
121
+
122
+ if (!answer && defaults.length > 0) {
123
+ return defaults;
124
+ }
125
+
126
+ if (!answer) {
127
+ return [];
128
+ }
129
+
130
+ const indices = answer
131
+ .split(',')
132
+ .map((s) => parseInt(s.trim(), 10) - 1)
133
+ .filter((i) => i >= 0 && i < choices.length);
134
+ return indices.map((i) => choices[i].value);
135
+ }
@@ -100,38 +100,46 @@ export async function getProjectIssueTypes(
100
100
  });
101
101
 
102
102
  const issueTypes = response.data.issueTypes || [];
103
-
104
- return issueTypes.map((it: any) => ({
103
+
104
+ return issueTypes.map((it: Partial<IssueTypeInfo> & { id: string; name: string }) => ({
105
105
  id: it.id,
106
106
  name: it.name,
107
107
  description: it.description || undefined,
108
108
  iconUrl: it.iconUrl || undefined,
109
109
  subtask: it.subtask || false
110
110
  }));
111
- } catch (error: any) {
111
+ } catch (error: unknown) {
112
112
  // エラーをログに記録(デバッグ用)
113
- if (error.response) {
114
- // HTTPエラー(4xx, 5xx)
115
- const status = error.response.status;
116
- const statusText = error.response.statusText;
117
-
118
- if (status === 401) {
119
- console.error('❌ JIRA認証に失敗しました。認証情報を確認してください。');
120
- } else if (status === 403) {
121
- console.error('❌ JIRAへのアクセス権限がありません。');
122
- } else if (status === 404) {
123
- console.error(`❌ JIRAプロジェクト "${projectKey}" が見つかりません。`);
113
+ // axios.isAxiosError()またはerror.response/requestの存在で判定
114
+ if (axios.isAxiosError(error) || (typeof error === 'object' && error !== null && ('response' in error || 'request' in error))) {
115
+ const axiosError = error as { response?: { status: number; statusText: string }; request?: unknown; message?: string };
116
+ if (axiosError.response) {
117
+ // HTTPエラー(4xx, 5xx)
118
+ const status = axiosError.response.status;
119
+ const statusText = axiosError.response.statusText;
120
+
121
+ if (status === 401) {
122
+ console.error('❌ JIRA認証に失敗しました。認証情報を確認してください。');
123
+ } else if (status === 403) {
124
+ console.error('❌ JIRAへのアクセス権限がありません。');
125
+ } else if (status === 404) {
126
+ console.error(`❌ JIRAプロジェクト "${projectKey}" が見つかりません。`);
127
+ } else {
128
+ console.error(`❌ JIRA APIエラー: ${status} ${statusText}`);
129
+ }
130
+ } else if (axiosError.request) {
131
+ // ネットワークエラー
132
+ console.error('❌ JIRA APIへの接続に失敗しました。ネットワークを確認してください。');
124
133
  } else {
125
- console.error(`❌ JIRA APIエラー: ${status} ${statusText}`);
134
+ // その他のエラー
135
+ const message = axiosError.message || String(error);
136
+ console.error(`❌ エラー: ${message}`);
126
137
  }
127
- } else if (error.request) {
128
- // ネットワークエラー
129
- console.error('❌ JIRA APIへの接続に失敗しました。ネットワークを確認してください。');
130
138
  } else {
131
- // その他のエラー
132
- console.error(`❌ エラー: ${error.message}`);
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ console.error(`❌ エラー: ${message}`);
133
141
  }
134
-
142
+
135
143
  return null;
136
144
  }
137
145
  }
@@ -67,3 +67,30 @@ Team: ${meta.team.join(', ')}
67
67
  `.trim();
68
68
  }
69
69
 
70
+ /**
71
+ * GitHub リポジトリ情報を取得
72
+ * repository フィールドから owner/repo 形式を抽出
73
+ *
74
+ * @param projectRoot プロジェクトルートディレクトリ(デフォルト: カレントディレクトリ)
75
+ * @returns owner/repo 形式のリポジトリ情報
76
+ * @throws リポジトリ情報が見つからない、または無効な形式の場合
77
+ */
78
+ export function getRepositoryInfo(projectRoot: string = process.cwd()): string {
79
+ const meta = loadProjectMeta(projectRoot);
80
+
81
+ if (!meta.repository) {
82
+ throw new Error('Repository information not found in project.json');
83
+ }
84
+
85
+ // URL形式から owner/repo を抽出
86
+ // 例: https://github.com/owner/repo.git -> owner/repo
87
+ // 例: git@github.com:owner/repo.git -> owner/repo
88
+ const match = meta.repository.match(/github\.com[:/]([\w-]+\/[\w-]+)(\.git)?/);
89
+
90
+ if (!match) {
91
+ throw new Error(`Invalid GitHub repository format: ${meta.repository}`);
92
+ }
93
+
94
+ return match[1];
95
+ }
96
+
@@ -101,8 +101,9 @@ export async function getCommits(
101
101
  }
102
102
 
103
103
  return commits;
104
- } catch (error: any) {
105
- console.error('Failed to get commits:', error.message);
104
+ } catch (error: unknown) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ console.error('Failed to get commits:', message);
106
107
  return [];
107
108
  }
108
109
  }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * security-validator.ts
3
+ * セキュリティ検証ユーティリティ
4
+ *
5
+ * 環境変数やAPIトークンなど機密情報のバリデーションとセキュリティチェックを行います。
6
+ */
7
+
8
+ import { statSync } from 'fs';
9
+
10
+ /**
11
+ * バリデーション結果
12
+ */
13
+ export interface ValidationResult {
14
+ isValid: boolean;
15
+ errors: string[];
16
+ warnings: string[];
17
+ }
18
+
19
+ /**
20
+ * APIトークン形式の検証
21
+ */
22
+ export function validateAtlassianToken(token: string): ValidationResult {
23
+ const errors: string[] = [];
24
+ const warnings: string[] = [];
25
+
26
+ if (!token || token.trim() === '') {
27
+ errors.push('Atlassian API token is empty');
28
+ return { isValid: false, errors, warnings };
29
+ }
30
+
31
+ if (token.includes(' ')) {
32
+ errors.push('Atlassian API token contains spaces');
33
+ }
34
+
35
+ if (token === 'your-token-here' || token === 'test-token' || token === 'token123') {
36
+ errors.push('Atlassian API token is a placeholder value');
37
+ }
38
+
39
+ if (token.length < 20) {
40
+ warnings.push('Atlassian API token seems too short (expected >20 characters)');
41
+ }
42
+
43
+ return {
44
+ isValid: errors.length === 0,
45
+ errors,
46
+ warnings,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * GitHub Personal Access Token の検証
52
+ */
53
+ export function validateGitHubToken(token: string): ValidationResult {
54
+ const errors: string[] = [];
55
+ const warnings: string[] = [];
56
+
57
+ if (!token || token.trim() === '') {
58
+ errors.push('GitHub token is empty');
59
+ } else if (token.includes(' ')) {
60
+ errors.push('GitHub token contains spaces');
61
+ } else if (!token.startsWith('ghp_') && !token.startsWith('github_pat_')) {
62
+ warnings.push('GitHub token does not start with expected prefix (ghp_ or github_pat_)');
63
+ } else if (token === 'ghp_xxx' || token === 'your-github-token' || token === 'github-token') {
64
+ errors.push('GitHub token is a placeholder value');
65
+ } else if (token.length < 30) {
66
+ warnings.push('GitHub token seems too short');
67
+ }
68
+
69
+ return {
70
+ isValid: errors.length === 0,
71
+ errors,
72
+ warnings,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * メールアドレス形式の検証
78
+ */
79
+ export function validateEmail(email: string): ValidationResult {
80
+ const errors: string[] = [];
81
+ const warnings: string[] = [];
82
+
83
+ if (!email || email.trim() === '') {
84
+ errors.push('Email is empty');
85
+ } else {
86
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
87
+ if (!emailRegex.test(email)) {
88
+ errors.push('Email format is invalid');
89
+ } else if (
90
+ email === 'user@example.com' ||
91
+ email.includes('your-email') ||
92
+ email === 'test@example.com'
93
+ ) {
94
+ errors.push('Email is a placeholder value');
95
+ }
96
+ }
97
+
98
+ return {
99
+ isValid: errors.length === 0,
100
+ errors,
101
+ warnings,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * URL形式の検証
107
+ */
108
+ export function validateUrl(url: string, expectedDomain?: string): ValidationResult {
109
+ const errors: string[] = [];
110
+ const warnings: string[] = [];
111
+
112
+ if (!url || url.trim() === '') {
113
+ errors.push('URL is empty');
114
+ } else {
115
+ try {
116
+ const parsedUrl = new URL(url);
117
+
118
+ if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
119
+ errors.push(`Invalid URL protocol: ${parsedUrl.protocol} (expected https: or http:)`);
120
+ }
121
+
122
+ if (parsedUrl.protocol === 'http:') {
123
+ warnings.push('URL uses insecure http:// protocol (consider using https://)');
124
+ }
125
+
126
+ if (expectedDomain && !parsedUrl.hostname.includes(expectedDomain)) {
127
+ errors.push(`URL hostname ${parsedUrl.hostname} does not contain expected domain: ${expectedDomain}`);
128
+ }
129
+
130
+ if (url === 'https://example.com' || url === 'https://your-domain.atlassian.net') {
131
+ errors.push('URL is a placeholder value');
132
+ }
133
+ } catch (error) {
134
+ errors.push(`URL parsing failed: ${error instanceof Error ? error.message : error}`);
135
+ }
136
+ }
137
+
138
+ return {
139
+ isValid: errors.length === 0,
140
+ errors,
141
+ warnings,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Atlassian URL の検証
147
+ */
148
+ export function validateAtlassianUrl(url: string): ValidationResult {
149
+ const result = validateUrl(url, 'atlassian.net');
150
+
151
+ if (result.isValid) {
152
+ try {
153
+ const parsedUrl = new URL(url);
154
+ if (!parsedUrl.hostname.endsWith('.atlassian.net')) {
155
+ result.errors.push('Atlassian URL must end with .atlassian.net');
156
+ result.isValid = false;
157
+ }
158
+ } catch {
159
+ // Already handled in validateUrl
160
+ }
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ /**
167
+ * GitHub リポジトリ URL の検証
168
+ */
169
+ export function validateGitHubRepositoryUrl(url: string): ValidationResult {
170
+ const errors: string[] = [];
171
+ const warnings: string[] = [];
172
+
173
+ if (!url || url.trim() === '') {
174
+ errors.push('Repository URL is empty');
175
+ } else {
176
+ // GitHub URL 形式のパターン
177
+ const httpsPattern = /^https:\/\/github\.com\/[\w-]+\/[\w-]+(\.git)?$/;
178
+ const sshPattern = /^git@github\.com:[\w-]+\/[\w-]+(\.git)?$/;
179
+
180
+ if (!httpsPattern.test(url) && !sshPattern.test(url)) {
181
+ errors.push(
182
+ 'Repository URL must be in format: https://github.com/owner/repo.git or git@github.com:owner/repo.git',
183
+ );
184
+ }
185
+
186
+ if (url.includes('your-org') || url.includes('your-repo')) {
187
+ errors.push('Repository URL contains placeholder values');
188
+ }
189
+ }
190
+
191
+ return {
192
+ isValid: errors.length === 0,
193
+ errors,
194
+ warnings,
195
+ };
196
+ }
197
+
198
+ /**
199
+ * 環境変数の包括的検証
200
+ */
201
+ export interface EnvValidationConfig {
202
+ atlassianUrl?: string;
203
+ atlassianEmail?: string;
204
+ atlassianApiToken?: string;
205
+ githubOrg?: string;
206
+ githubToken?: string;
207
+ repositoryUrl?: string;
208
+ }
209
+
210
+ export function validateEnvironmentConfig(config: EnvValidationConfig): ValidationResult {
211
+ const allErrors: string[] = [];
212
+ const allWarnings: string[] = [];
213
+
214
+ if (config.atlassianUrl !== undefined) {
215
+ const result = validateAtlassianUrl(config.atlassianUrl);
216
+ allErrors.push(...result.errors.map((e) => `ATLASSIAN_URL: ${e}`));
217
+ allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_URL: ${w}`));
218
+ }
219
+
220
+ if (config.atlassianEmail !== undefined) {
221
+ const result = validateEmail(config.atlassianEmail);
222
+ allErrors.push(...result.errors.map((e) => `ATLASSIAN_EMAIL: ${e}`));
223
+ allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_EMAIL: ${w}`));
224
+ }
225
+
226
+ if (config.atlassianApiToken !== undefined) {
227
+ const result = validateAtlassianToken(config.atlassianApiToken);
228
+ allErrors.push(...result.errors.map((e) => `ATLASSIAN_API_TOKEN: ${e}`));
229
+ allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_API_TOKEN: ${w}`));
230
+ }
231
+
232
+ if (config.githubOrg !== undefined) {
233
+ if (!config.githubOrg || config.githubOrg.trim() === '') {
234
+ allErrors.push('GITHUB_ORG: GitHub organization is empty');
235
+ } else if (config.githubOrg.includes('your-org')) {
236
+ allErrors.push('GITHUB_ORG: GitHub organization is a placeholder value');
237
+ }
238
+ }
239
+
240
+ if (config.githubToken !== undefined) {
241
+ const result = validateGitHubToken(config.githubToken);
242
+ allErrors.push(...result.errors.map((e) => `GITHUB_TOKEN: ${e}`));
243
+ allWarnings.push(...result.warnings.map((w) => `GITHUB_TOKEN: ${w}`));
244
+ }
245
+
246
+ if (config.repositoryUrl !== undefined) {
247
+ const result = validateGitHubRepositoryUrl(config.repositoryUrl);
248
+ allErrors.push(...result.errors.map((e) => `repository: ${e}`));
249
+ allWarnings.push(...result.warnings.map((w) => `repository: ${w}`));
250
+ }
251
+
252
+ return {
253
+ isValid: allErrors.length === 0,
254
+ errors: allErrors,
255
+ warnings: allWarnings,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * ファイルパーミッションの検証(Unix系のみ)
261
+ */
262
+ export function validateFilePermissions(filePath: string, expectedMode: number = 0o600): ValidationResult {
263
+ const errors: string[] = [];
264
+ const warnings: string[] = [];
265
+
266
+ try {
267
+ const stats = statSync(filePath);
268
+ const actualMode = stats.mode & 0o777;
269
+
270
+ if (actualMode !== expectedMode) {
271
+ const actualOctal = actualMode.toString(8);
272
+ const expectedOctal = expectedMode.toString(8);
273
+ warnings.push(
274
+ `File permissions are ${actualOctal} but should be ${expectedOctal} for security. Run: chmod ${expectedOctal} ${filePath}`,
275
+ );
276
+ }
277
+ } catch (error) {
278
+ errors.push(`Failed to check file permissions: ${error instanceof Error ? error.message : error}`);
279
+ }
280
+
281
+ return {
282
+ isValid: errors.length === 0,
283
+ errors,
284
+ warnings,
285
+ };
286
+ }
@@ -29,12 +29,24 @@ export interface SpecJson {
29
29
  url?: string;
30
30
  title?: string;
31
31
  };
32
+ // 旧形式フィールド(後方互換性のため)
33
+ requirementsPageId?: string;
34
+ requirementsUrl?: string;
35
+ designPageId?: string;
36
+ designUrl?: string;
37
+ tasksPageId?: string;
38
+ tasksUrl?: string;
32
39
  };
33
40
  jira?: {
34
41
  projectKey?: string;
35
42
  epicKey?: string;
36
43
  epicUrl?: string;
37
44
  storyKeys?: string[];
45
+ // 旧形式フィールド(後方互換性のため)
46
+ stories?: {
47
+ created?: number;
48
+ total?: number;
49
+ };
38
50
  };
39
51
  environmentSetup?: {
40
52
  completed?: boolean;
@@ -48,6 +60,16 @@ export interface SpecJson {
48
60
  designCompleted?: boolean;
49
61
  tasksCompleted?: boolean;
50
62
  jiraSyncCompleted?: boolean;
63
+ // 旧形式フィールド(後方互換性のため)
64
+ requirements?: {
65
+ completed?: boolean;
66
+ };
67
+ design?: {
68
+ completed?: boolean;
69
+ };
70
+ tasks?: {
71
+ completed?: boolean;
72
+ };
51
73
  };
52
74
  lastUpdated?: string;
53
75
  }
@@ -144,14 +166,14 @@ export function updateSpecJsonAfterConfluenceSync(
144
166
 
145
167
  // 旧形式(後方互換性のため併記)
146
168
  if (docType === 'requirements') {
147
- (spec.confluence as any).requirementsPageId = pageInfo.pageId;
148
- (spec.confluence as any).requirementsUrl = pageInfo.url;
169
+ spec.confluence.requirementsPageId = pageInfo.pageId;
170
+ spec.confluence.requirementsUrl = pageInfo.url;
149
171
  } else if (docType === 'design') {
150
- (spec.confluence as any).designPageId = pageInfo.pageId;
151
- (spec.confluence as any).designUrl = pageInfo.url;
172
+ spec.confluence.designPageId = pageInfo.pageId;
173
+ spec.confluence.designUrl = pageInfo.url;
152
174
  } else if (docType === 'tasks') {
153
- (spec.confluence as any).tasksPageId = pageInfo.pageId;
154
- (spec.confluence as any).tasksUrl = pageInfo.url;
175
+ spec.confluence.tasksPageId = pageInfo.pageId;
176
+ spec.confluence.tasksUrl = pageInfo.url;
155
177
  }
156
178
 
157
179
  // マイルストーンを更新
@@ -163,24 +185,24 @@ export function updateSpecJsonAfterConfluenceSync(
163
185
  if (docType === 'requirements') {
164
186
  spec.milestones.requirementsCompleted = true;
165
187
  // 旧形式(後方互換性のため併記)
166
- if (!(spec.milestones as any).requirements) {
167
- (spec.milestones as any).requirements = {};
188
+ if (!spec.milestones.requirements) {
189
+ spec.milestones.requirements = {};
168
190
  }
169
- (spec.milestones as any).requirements.completed = true;
191
+ spec.milestones.requirements.completed = true;
170
192
  } else if (docType === 'design') {
171
193
  spec.milestones.designCompleted = true;
172
194
  // 旧形式(後方互換性のため併記)
173
- if (!(spec.milestones as any).design) {
174
- (spec.milestones as any).design = {};
195
+ if (!spec.milestones.design) {
196
+ spec.milestones.design = {};
175
197
  }
176
- (spec.milestones as any).design.completed = true;
198
+ spec.milestones.design.completed = true;
177
199
  } else if (docType === 'tasks') {
178
200
  spec.milestones.tasksCompleted = true;
179
201
  // 旧形式(後方互換性のため併記)
180
- if (!(spec.milestones as any).tasks) {
181
- (spec.milestones as any).tasks = {};
202
+ if (!spec.milestones.tasks) {
203
+ spec.milestones.tasks = {};
182
204
  }
183
- (spec.milestones as any).tasks.completed = true;
205
+ spec.milestones.tasks.completed = true;
184
206
  }
185
207
 
186
208
  saveSpecJson(featureName, spec, projectRoot);
@@ -4,8 +4,7 @@
4
4
  * AI-DLC形式のtasks.mdをMichiが期待するPhase構造に変換
5
5
  */
6
6
 
7
- import { writeFileSync, copyFileSync, existsSync } from 'fs';
8
- import { join, dirname } from 'path';
7
+ import { writeFileSync, copyFileSync } from 'fs';
9
8
  import type {
10
9
  AIDLCDocument,
11
10
  AIDLCCategory,
@@ -20,7 +19,6 @@ import { readFileSync } from 'fs';
20
19
  import {
21
20
  getWeekdayNotation,
22
21
  getWeekdayRangeNotation,
23
- getWeekNumber,
24
22
  ensureBusinessDay,
25
23
  } from './business-days.js';
26
24
 
@@ -77,8 +75,8 @@ interface PhaseMapping {
77
75
  */
78
76
  function mapCategoryToPhase(
79
77
  category: AIDLCCategory,
80
- index: number,
81
- totalCategories: number,
78
+ _index: number,
79
+ _totalCategories: number,
82
80
  ): PhaseMapping {
83
81
  const title = category.title.toLowerCase();
84
82
 
@@ -364,7 +362,7 @@ export function convertToMichiFormat(
364
362
  const group = phaseGroups.get(mapping.phaseId)!;
365
363
 
366
364
  // タスクをStoryに変換
367
- category.tasks.forEach((task, taskIndex) => {
365
+ category.tasks.forEach(task => {
368
366
  const storyContent = convertTaskToStory(
369
367
  task,
370
368
  mapping.phaseId,
@@ -41,19 +41,6 @@ export function validateTasksFormat(tasksPath: string): void {
41
41
 
42
42
  // 2. フェーズ構造の検証(新旧両方をサポート)
43
43
 
44
- // 新ワークフロー構造(推奨)
45
- const newWorkflowPhases = [
46
- 'Phase 0.1:', // 要件定義
47
- 'Phase 0.2:', // 設計
48
- 'Phase 1:', // 環境構築(任意)
49
- 'Phase 2:', // TDD実装
50
- 'Phase A:', // PR前自動テスト(任意)
51
- 'Phase 3:', // 追加QA(任意)
52
- 'Phase B:', // リリース準備テスト(任意)
53
- 'Phase 4:', // リリース準備
54
- 'Phase 5:', // リリース
55
- ];
56
-
57
44
  // 旧6-Phase構造(互換性のためサポート)
58
45
  const legacyPhases = [
59
46
  'Phase 0: 要件定義(Requirements)',