@sk8metal/michi-cli 0.10.1 → 0.11.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 (104) hide show
  1. package/README.md +77 -847
  2. package/dist/scripts/phase-runner.js +1 -1
  3. package/dist/scripts/phase-runner.js.map +1 -1
  4. package/dist/scripts/utils/multi-repo-validator.d.ts +18 -0
  5. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -1
  6. package/dist/scripts/utils/multi-repo-validator.js +42 -0
  7. package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
  8. package/dist/scripts/utils/tasks-format-validator.js +3 -3
  9. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  10. package/docs/README.md +20 -83
  11. package/docs/getting-started/configuration.md +379 -0
  12. package/docs/getting-started/installation.md +59 -0
  13. package/docs/getting-started/quick-start.md +76 -0
  14. package/docs/guides/ai-tools.md +311 -0
  15. package/docs/guides/atlassian-integration.md +116 -0
  16. package/docs/guides/claude-code.md +155 -0
  17. package/docs/guides/multi-repo.md +117 -0
  18. package/docs/guides/workflow.md +382 -0
  19. package/docs/reference/ai-commands.md +92 -0
  20. package/docs/reference/cli.md +756 -0
  21. package/docs/reference/environment-variables.md +192 -0
  22. package/docs/troubleshooting.md +543 -0
  23. package/package.json +1 -1
  24. package/scripts/phase-runner.ts +1 -1
  25. package/scripts/utils/__tests__/multi-repo-validator.test.ts +159 -1
  26. package/scripts/utils/multi-repo-validator.ts +50 -0
  27. package/scripts/utils/tasks-format-validator.ts +3 -3
  28. package/templates/claude/agents/e2e-first-planner/AGENT.md +1 -1
  29. package/templates/claude/agents/pr-resolver/AGENT.md +15 -3
  30. package/templates/claude/commands/michi/e2e-plan.md +1 -1
  31. package/templates/claude/commands/michi/spec-design.md +2 -2
  32. package/templates/claude/commands/michi/spec-tasks.md +156 -0
  33. package/templates/claude/commands/michi/test-planning.md +1 -1
  34. package/templates/claude/commands/michi/validate-design.md +3 -3
  35. package/templates/claude/commands/michi-multi-repo/impl-all.md +30 -1
  36. package/templates/claude/commands/michi-multi-repo/propagate-specs.md +14 -1
  37. package/templates/claude/commands/michi-multi-repo/spec-review.md +16 -2
  38. package/templates/claude-agent/agents/repo-spec-executor.md +1 -1
  39. package/templates/claude-agent/commands/michi/spec-tasks.md +117 -0
  40. package/templates/claude-agent/rules/code-size-monitor.md +26 -0
  41. package/templates/claude-agent/rules/code-size-rules.md +32 -0
  42. package/templates/codex/AGENTS.override.md +1 -1
  43. package/templates/codex/rules/README.md +2 -2
  44. package/templates/cursor/commands/michi/spec-tasks.md +117 -0
  45. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +1 -1
  46. package/docs/context.md +0 -59
  47. package/docs/michi-development/contributing/development.md +0 -341
  48. package/docs/michi-development/contributing/release.md +0 -365
  49. package/docs/michi-development/design/config-unification.md +0 -733
  50. package/docs/michi-development/design/design-config-current-state.md +0 -330
  51. package/docs/michi-development/design/design-config-implementation.md +0 -628
  52. package/docs/michi-development/design/design-config-migration.md +0 -952
  53. package/docs/michi-development/design/design-config-security.md +0 -771
  54. package/docs/michi-development/design/design-config-solution.md +0 -583
  55. package/docs/michi-development/design/design-config-testing.md +0 -892
  56. package/docs/michi-development/testing/manual-verification-flow.md +0 -871
  57. package/docs/michi-development/testing/manual-verification-other-tools.md +0 -1279
  58. package/docs/michi-development/testing/manual-verification-troubleshooting.md +0 -122
  59. package/docs/michi-development/testing/pre-publish-checklist.md +0 -560
  60. package/docs/michi-development/testing-strategy.md +0 -87
  61. package/docs/plan.md +0 -275
  62. package/docs/user-guide/getting-started/github-token-setup.md +0 -510
  63. package/docs/user-guide/getting-started/new-repository-setup.md +0 -704
  64. package/docs/user-guide/getting-started/quick-start.md +0 -212
  65. package/docs/user-guide/getting-started/setup.md +0 -819
  66. package/docs/user-guide/guides/agent-skills-integration.md +0 -222
  67. package/docs/user-guide/guides/customization.md +0 -537
  68. package/docs/user-guide/guides/internationalization.md +0 -540
  69. package/docs/user-guide/guides/migration-guide.md +0 -138
  70. package/docs/user-guide/guides/multi-project.md +0 -368
  71. package/docs/user-guide/guides/multi-repo-guide.md +0 -1590
  72. package/docs/user-guide/guides/phase-automation.md +0 -419
  73. package/docs/user-guide/guides/workflow.md +0 -574
  74. package/docs/user-guide/hands-on/README.md +0 -142
  75. package/docs/user-guide/hands-on/claude-agent-setup.md +0 -597
  76. package/docs/user-guide/hands-on/claude-setup.md +0 -452
  77. package/docs/user-guide/hands-on/cursor-setup.md +0 -353
  78. package/docs/user-guide/hands-on/troubleshooting.md +0 -964
  79. package/docs/user-guide/hands-on/verification-checklist.md +0 -439
  80. package/docs/user-guide/hands-on/workflow-walkthrough.md +0 -1078
  81. package/docs/user-guide/reference/config.md +0 -589
  82. package/docs/user-guide/reference/multi-repo-api.md +0 -771
  83. package/docs/user-guide/reference/quick-reference.md +0 -297
  84. package/docs/user-guide/reference/security-test-payloads.md +0 -50
  85. package/docs/user-guide/reference/tasks-template.md +0 -550
  86. package/docs/user-guide/release/ci-setup-java.md +0 -114
  87. package/docs/user-guide/release/ci-setup-nodejs.md +0 -94
  88. package/docs/user-guide/release/ci-setup-php.md +0 -102
  89. package/docs/user-guide/release/ci-setup-troubleshooting.md +0 -94
  90. package/docs/user-guide/release/ci-setup.md +0 -188
  91. package/docs/user-guide/release/release-flow.md +0 -476
  92. package/docs/user-guide/templates/test-specs/README.md +0 -173
  93. package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +0 -553
  94. package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +0 -435
  95. package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +0 -454
  96. package/docs/user-guide/templates/test-specs/security-test-spec-template.md +0 -625
  97. package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +0 -328
  98. package/docs/user-guide/testing/integration-tests.md +0 -312
  99. package/docs/user-guide/testing/tdd-cycle.md +0 -349
  100. package/docs/user-guide/testing/test-execution-flow.md +0 -396
  101. package/docs/user-guide/testing/test-failure-handling.md +0 -521
  102. package/docs/user-guide/testing/test-planning-flow.md +0 -185
  103. package/docs/user-guide/testing-strategy.md +0 -185
  104. package/docs/verification-guide.md +0 -518
@@ -1,771 +0,0 @@
1
- # Michi 設定統合設計書 - セキュリティとパフォーマンス
2
-
3
- **バージョン**: 1.0
4
- **作成日**: 2025-01-11
5
- **ステータス**: Draft
6
- **親ドキュメント**: [config-unification.md](./config-unification.md)
7
-
8
- ---
9
-
10
- ## 9. セキュリティとパフォーマンス
11
-
12
- 設定統一に伴うセキュリティとパフォーマンスの考慮事項です。
13
-
14
- ### 9.1 セキュリティ対策
15
-
16
- #### 9.1.1 認証情報の保護
17
-
18
- **脅威モデル**
19
-
20
- | 脅威 | 影響 | 対策 |
21
- |------|------|------|
22
- | **認証情報の漏洩** | 高 | ファイルパーミッション、.gitignore |
23
- | **環境変数の漏洩** | 中 | .env.example の提供、警告メッセージ |
24
- | **不正アクセス** | 高 | 最小権限の原則、トークンの有効期限 |
25
- | **中間者攻撃** | 中 | HTTPS必須、証明書検証 |
26
-
27
- **実装されるセキュリティ対策**
28
-
29
- 1. **ファイルパーミッションの強制**
30
-
31
- ```typescript
32
- // scripts/utils/security.ts
33
- import { chmodSync, statSync } from 'fs';
34
-
35
- /**
36
- * 機密ファイルのパーミッションを強制する
37
- */
38
- export function enforceSecurePermissions(filePath: string): void {
39
- const stats = statSync(filePath);
40
- const mode = stats.mode & 0o777;
41
-
42
- // 600 (rw-------) 以外の場合は警告
43
- if (mode !== 0o600) {
44
- console.warn(`⚠️ 警告: ${filePath} のパーミッションが不適切です`);
45
- console.warn(` 現在: ${mode.toString(8)}, 推奨: 600`);
46
- console.warn(` 自動的に 600 に変更します...`);
47
-
48
- chmodSync(filePath, 0o600);
49
- console.log(`✅ パーミッションを 600 に変更しました`);
50
- }
51
- }
52
-
53
- /**
54
- * グローバル .env ファイルの作成時にパーミッションを設定
55
- */
56
- export function createSecureEnvFile(filePath: string, content: string): void {
57
- writeFileSync(filePath, content, { mode: 0o600 });
58
- console.log(`✅ セキュアなファイルを作成しました: ${filePath}`);
59
- }
60
- ```
61
-
62
- 2. **.gitignore の自動更新**
63
-
64
- ```typescript
65
- // scripts/utils/gitignore.ts
66
- import { readFileSync, writeFileSync, existsSync } from 'fs';
67
-
68
- /**
69
- * .gitignore に機密ファイルを追加する
70
- */
71
- export function ensureGitignore(projectDir: string): void {
72
- const gitignorePath = join(projectDir, '.gitignore');
73
- const requiredEntries = [
74
- '.env',
75
- '.env.local',
76
- '~/.michi/.env',
77
- '.michi-backup-*/',
78
- '.michi/migration.log'
79
- ];
80
-
81
- let content = existsSync(gitignorePath)
82
- ? readFileSync(gitignorePath, 'utf-8')
83
- : '';
84
-
85
- let added = false;
86
- for (const entry of requiredEntries) {
87
- if (!content.includes(entry)) {
88
- content += `\n${entry}`;
89
- added = true;
90
- }
91
- }
92
-
93
- if (added) {
94
- writeFileSync(gitignorePath, content.trim() + '\n');
95
- console.log('✅ .gitignore を更新しました');
96
- }
97
- }
98
- ```
99
-
100
- 3. **環境変数の検証**
101
-
102
- ```typescript
103
- // src/config/validation.ts
104
- import { z } from 'zod';
105
-
106
- /**
107
- * 認証情報の形式を検証する
108
- */
109
- export const CredentialsSchema = z.object({
110
- // Atlassian API Token (24文字の英数字)
111
- confluenceApiToken: z.string()
112
- .min(24, 'API token is too short')
113
- .regex(/^[A-Za-z0-9]+$/, 'Invalid API token format'),
114
-
115
- jiraApiToken: z.string()
116
- .min(24, 'API token is too short')
117
- .regex(/^[A-Za-z0-9]+$/, 'Invalid API token format'),
118
-
119
- // GitHub Personal Access Token (ghp_ または ghp_classic プレフィックス)
120
- githubToken: z.string()
121
- .regex(
122
- /^(ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82})$/,
123
- 'Invalid GitHub token format'
124
- ),
125
- });
126
-
127
- /**
128
- * 機密情報が含まれていないかチェック
129
- */
130
- export function detectSecretsInLogs(message: string): string {
131
- // トークンのパターンにマッチする部分をマスクする
132
- return message
133
- .replace(/ghp_[A-Za-z0-9]{36}/g, 'ghp_****')
134
- .replace(/github_pat_[A-Za-z0-9_]{82}/g, 'github_pat_****')
135
- .replace(/[A-Za-z0-9]{24,}/g, (match) => {
136
- // 24文字以上の英数字はトークンの可能性があるのでマスク
137
- return match.substring(0, 4) + '****';
138
- });
139
- }
140
- ```
141
-
142
- #### 9.1.2 ファイルパーミッションの管理
143
-
144
- **パーミッション設定基準**
145
-
146
- | ファイル | パーミッション | 理由 |
147
- |---------|--------------|------|
148
- | `~/.michi/.env` | 600 (rw-------) | 組織の認証情報を含む |
149
- | `.env` | 600 (rw-------) | プロジェクトの認証情報を含む |
150
- | `.michi/config.json` | 644 (rw-r--r--) | 認証情報を含まない |
151
- | `.michi/project.json` | 644 (rw-r--r--) | 認証情報を含まない |
152
- | `.michi-backup-*/*` | 700 (rwx------) | バックアップディレクトリ |
153
-
154
- **自動チェック機構**
155
-
156
- ```typescript
157
- // src/config/config-loader.ts (updated)
158
- export class ConfigLoader {
159
- private validateFilePermissions(): void {
160
- const sensitiveFiles = [
161
- this.globalEnvPath,
162
- join(this.projectDir, '.env')
163
- ];
164
-
165
- for (const filePath of sensitiveFiles) {
166
- if (!existsSync(filePath)) continue;
167
-
168
- const stats = statSync(filePath);
169
- const mode = stats.mode & 0o777;
170
-
171
- if (mode > 0o600) {
172
- throw new SecurityError(
173
- 'INSECURE_PERMISSIONS',
174
- `File ${filePath} has insecure permissions: ${mode.toString(8)}`,
175
- { filePath, mode, expected: 0o600 }
176
- );
177
- }
178
- }
179
- }
180
-
181
- public load(): AppConfig {
182
- // パーミッションチェック
183
- this.validateFilePermissions();
184
-
185
- // 設定読み込み
186
- // ...
187
- }
188
- }
189
- ```
190
-
191
- #### 9.1.3 入力検証
192
-
193
- **URL検証**
194
-
195
- ```typescript
196
- // src/config/validators.ts
197
- import { z } from 'zod';
198
-
199
- /**
200
- * Atlassian URL(Confluence/JIRA)の検証
201
- */
202
- export const AtlassianUrlSchema = z.string()
203
- .url('Invalid URL format')
204
- .regex(
205
- /^https:\/\/[a-zA-Z0-9-]+\.atlassian\.net$/,
206
- 'Must be a valid Atlassian Cloud URL (https://xxx.atlassian.net)'
207
- );
208
-
209
- /**
210
- * GitHub リポジトリURLの検証
211
- */
212
- export const GitHubRepoUrlSchema = z.string()
213
- .refine(
214
- (url) => {
215
- const httpsPattern = /^https:\/\/github\.com\/[^/]+\/[^/]+(\.git)?$/;
216
- const sshPattern = /^git@github\.com:[^/]+\/[^/]+(\.git)?$/;
217
- return httpsPattern.test(url) || sshPattern.test(url);
218
- },
219
- 'Must be a valid GitHub repository URL'
220
- );
221
-
222
- /**
223
- * メールアドレスの検証
224
- */
225
- export const EmailSchema = z.string()
226
- .email('Invalid email format')
227
- .max(254, 'Email too long');
228
- ```
229
-
230
- **コマンドインジェクション対策**
231
-
232
- ```typescript
233
- // scripts/utils/exec-safe.ts
234
- import { execSync } from 'child_process';
235
-
236
- /**
237
- * 安全なコマンド実行(シェルインジェクション対策)
238
- */
239
- export function execSafe(
240
- command: string,
241
- args: string[],
242
- options?: any
243
- ): string {
244
- // コマンドと引数を分離して実行
245
- // execSync の shell: false オプションを使用
246
- const fullCommand = `${command} ${args.map(escapeArg).join(' ')}`;
247
-
248
- return execSync(fullCommand, {
249
- ...options,
250
- shell: false, // シェルを経由しない
251
- encoding: 'utf-8'
252
- });
253
- }
254
-
255
- /**
256
- * シェル引数のエスケープ
257
- */
258
- function escapeArg(arg: string): string {
259
- // シングルクォートで囲み、内部のシングルクォートをエスケープ
260
- return `'${arg.replace(/'/g, "'\\''")}'`;
261
- }
262
- ```
263
-
264
- #### 9.1.4 セキュアなデフォルト設定
265
-
266
- **デフォルト値の設計**
267
-
268
- ```typescript
269
- // src/config/defaults.ts
270
-
271
- export const SECURE_DEFAULTS = {
272
- // HTTPS 強制
273
- confluence: {
274
- useHttps: true,
275
- verifySsl: true,
276
- },
277
-
278
- jira: {
279
- useHttps: true,
280
- verifySsl: true,
281
- },
282
-
283
- github: {
284
- useHttps: true,
285
- },
286
-
287
- // API呼び出しのタイムアウト(DoS対策)
288
- api: {
289
- timeout: 30000, // 30秒
290
- maxRetries: 3,
291
- retryDelay: 1000, // 1秒
292
- },
293
-
294
- // ロギング
295
- logging: {
296
- maskSecrets: true, // 機密情報を自動マスク
297
- level: 'info',
298
- },
299
- } as const;
300
- ```
301
-
302
- **設定値のサニタイズ**
303
-
304
- ```typescript
305
- // src/config/sanitize.ts
306
-
307
- /**
308
- * ログ出力前に機密情報をマスクする
309
- */
310
- export function sanitizeForLog(config: AppConfig): AppConfig {
311
- return {
312
- ...config,
313
- confluence: config.confluence ? {
314
- ...config.confluence,
315
- apiToken: '****',
316
- password: '****',
317
- } : undefined,
318
- jira: config.jira ? {
319
- ...config.jira,
320
- apiToken: '****',
321
- password: '****',
322
- } : undefined,
323
- github: config.github ? {
324
- ...config.github,
325
- token: config.github.token?.substring(0, 7) + '****',
326
- } : undefined,
327
- };
328
- }
329
- ```
330
-
331
- ### 9.2 パフォーマンス最適化
332
-
333
- #### 9.2.1 設定読み込みの最適化
334
-
335
- **問題点**
336
-
337
- - 毎回ファイルを読み込むとI/Oコストが高い
338
- - 複数のコマンド実行時に同じファイルを何度も読む
339
- - Zodバリデーションが重い
340
-
341
- **解決策: キャッシュ機構**
342
-
343
- ```typescript
344
- // src/config/config-loader.ts (updated)
345
-
346
- export class ConfigLoader {
347
- private static cache: Map<string, {
348
- config: AppConfig;
349
- timestamp: number;
350
- hash: string;
351
- }> = new Map();
352
-
353
- private static CACHE_TTL = 60000; // 1分
354
-
355
- /**
356
- * ファイルのハッシュ値を計算
357
- */
358
- private getFileHash(filePath: string): string {
359
- if (!existsSync(filePath)) return '';
360
-
361
- const content = readFileSync(filePath, 'utf-8');
362
- return createHash('sha256').update(content).digest('hex');
363
- }
364
-
365
- /**
366
- * キャッシュキーを生成
367
- */
368
- private getCacheKey(): string {
369
- const globalHash = this.getFileHash(this.globalEnvPath);
370
- const projectConfigHash = this.getFileHash(this.projectConfigPath);
371
- const projectEnvHash = this.getFileHash(this.projectEnvPath);
372
-
373
- return `${globalHash}:${projectConfigHash}:${projectEnvHash}`;
374
- }
375
-
376
- /**
377
- * キャッシュから設定を取得
378
- */
379
- public load(options: { noCache?: boolean } = {}): AppConfig {
380
- const cacheKey = this.getCacheKey();
381
- const now = Date.now();
382
-
383
- // キャッシュチェック
384
- if (!options.noCache) {
385
- const cached = ConfigLoader.cache.get(cacheKey);
386
- if (cached && now - cached.timestamp < ConfigLoader.CACHE_TTL) {
387
- return cached.config;
388
- }
389
- }
390
-
391
- // 設定を読み込み
392
- const config = this.loadInternal();
393
-
394
- // キャッシュに保存
395
- ConfigLoader.cache.set(cacheKey, {
396
- config,
397
- timestamp: now,
398
- hash: cacheKey
399
- });
400
-
401
- return config;
402
- }
403
-
404
- /**
405
- * キャッシュをクリア
406
- */
407
- public static clearCache(): void {
408
- ConfigLoader.cache.clear();
409
- }
410
- }
411
- ```
412
-
413
- **ベンチマーク目標**
414
-
415
- | 操作 | 目標時間 | 現在値 |
416
- |------|---------|--------|
417
- | 初回読み込み | < 100ms | TBD |
418
- | キャッシュヒット | < 10ms | TBD |
419
- | バリデーション | < 50ms | TBD |
420
- | マージ処理 | < 20ms | TBD |
421
-
422
- #### 9.2.2 メモリ使用量
423
-
424
- **メモリプロファイリング**
425
-
426
- ```typescript
427
- // scripts/benchmark/memory-profile.ts
428
- import { ConfigLoader } from '../src/config/config-loader.js';
429
-
430
- function measureMemoryUsage() {
431
- const before = process.memoryUsage();
432
-
433
- // 100回設定を読み込む
434
- for (let i = 0; i < 100; i++) {
435
- const loader = new ConfigLoader(process.cwd());
436
- loader.load();
437
- }
438
-
439
- const after = process.memoryUsage();
440
-
441
- console.log('Memory Usage:');
442
- console.log(` Heap Used: ${(after.heapUsed - before.heapUsed) / 1024 / 1024}MB`);
443
- console.log(` External: ${(after.external - before.external) / 1024 / 1024}MB`);
444
- }
445
-
446
- measureMemoryUsage();
447
- ```
448
-
449
- **最適化目標**
450
-
451
- - 1回の設定読み込み: < 5MB
452
- - キャッシュ保持: < 10MB (100エントリ)
453
- - ピークメモリ: < 50MB
454
-
455
- #### 9.2.3 キャッシュ戦略
456
-
457
- **多層キャッシュ**
458
-
459
- ```typescript
460
- // src/config/cache-strategy.ts
461
-
462
- interface CacheStrategy {
463
- get(key: string): AppConfig | null;
464
- set(key: string, value: AppConfig): void;
465
- invalidate(key: string): void;
466
- clear(): void;
467
- }
468
-
469
- /**
470
- * LRU(Least Recently Used)キャッシュ
471
- */
472
- class LRUCache implements CacheStrategy {
473
- private cache: Map<string, { value: AppConfig; timestamp: number }>;
474
- private maxSize: number;
475
-
476
- constructor(maxSize: number = 100) {
477
- this.cache = new Map();
478
- this.maxSize = maxSize;
479
- }
480
-
481
- get(key: string): AppConfig | null {
482
- const entry = this.cache.get(key);
483
- if (!entry) return null;
484
-
485
- // アクセスされたエントリを最後に移動(LRU)
486
- this.cache.delete(key);
487
- this.cache.set(key, entry);
488
-
489
- return entry.value;
490
- }
491
-
492
- set(key: string, value: AppConfig): void {
493
- // サイズ制限チェック
494
- if (this.cache.size >= this.maxSize) {
495
- // 最も古いエントリを削除
496
- const oldestKey = this.cache.keys().next().value;
497
- this.cache.delete(oldestKey);
498
- }
499
-
500
- this.cache.set(key, {
501
- value,
502
- timestamp: Date.now()
503
- });
504
- }
505
-
506
- invalidate(key: string): void {
507
- this.cache.delete(key);
508
- }
509
-
510
- clear(): void {
511
- this.cache.clear();
512
- }
513
- }
514
-
515
- /**
516
- * TTL(Time To Live)付きキャッシュ
517
- */
518
- class TTLCache implements CacheStrategy {
519
- private cache: Map<string, { value: AppConfig; expiry: number }>;
520
- private ttl: number;
521
-
522
- constructor(ttl: number = 60000) {
523
- this.cache = new Map();
524
- this.ttl = ttl;
525
- }
526
-
527
- get(key: string): AppConfig | null {
528
- const entry = this.cache.get(key);
529
- if (!entry) return null;
530
-
531
- // 有効期限チェック
532
- if (Date.now() > entry.expiry) {
533
- this.cache.delete(key);
534
- return null;
535
- }
536
-
537
- return entry.value;
538
- }
539
-
540
- set(key: string, value: AppConfig): void {
541
- this.cache.set(key, {
542
- value,
543
- expiry: Date.now() + this.ttl
544
- });
545
- }
546
-
547
- invalidate(key: string): void {
548
- this.cache.delete(key);
549
- }
550
-
551
- clear(): void {
552
- this.cache.clear();
553
- }
554
- }
555
- ```
556
-
557
- ### 9.3 監査とロギング
558
-
559
- #### 9.3.1 設定変更の監査ログ
560
-
561
- ```typescript
562
- // src/config/audit-log.ts
563
- import { writeFileSync, appendFileSync, existsSync } from 'fs';
564
- import { join } from 'path';
565
-
566
- interface AuditLogEntry {
567
- timestamp: string;
568
- user: string;
569
- action: string;
570
- target: string;
571
- changes: Record<string, { old: any; new: any }>;
572
- success: boolean;
573
- error?: string;
574
- }
575
-
576
- export class AuditLogger {
577
- private logPath: string;
578
-
579
- constructor(projectDir: string) {
580
- this.logPath = join(projectDir, '.michi', 'audit.log');
581
- }
582
-
583
- /**
584
- * 設定変更を記録
585
- */
586
- public logConfigChange(
587
- action: string,
588
- target: string,
589
- changes: Record<string, { old: any; new: any }>,
590
- success: boolean,
591
- error?: string
592
- ): void {
593
- const entry: AuditLogEntry = {
594
- timestamp: new Date().toISOString(),
595
- user: process.env.USER || 'unknown',
596
- action,
597
- target,
598
- changes: this.sanitizeChanges(changes),
599
- success,
600
- error
601
- };
602
-
603
- const logLine = JSON.stringify(entry) + '\n';
604
-
605
- if (!existsSync(this.logPath)) {
606
- writeFileSync(this.logPath, logLine, { mode: 0o600 });
607
- } else {
608
- appendFileSync(this.logPath, logLine);
609
- }
610
- }
611
-
612
- /**
613
- * 機密情報をマスク
614
- */
615
- private sanitizeChanges(
616
- changes: Record<string, { old: any; new: any }>
617
- ): Record<string, { old: any; new: any }> {
618
- const sensitiveKeys = ['apiToken', 'token', 'password', 'secret'];
619
-
620
- const sanitized: Record<string, { old: any; new: any }> = {};
621
-
622
- for (const [key, value] of Object.entries(changes)) {
623
- if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
624
- sanitized[key] = {
625
- old: '****',
626
- new: '****'
627
- };
628
- } else {
629
- sanitized[key] = value;
630
- }
631
- }
632
-
633
- return sanitized;
634
- }
635
-
636
- /**
637
- * 監査ログを取得
638
- */
639
- public getAuditLog(limit?: number): AuditLogEntry[] {
640
- if (!existsSync(this.logPath)) return [];
641
-
642
- const content = readFileSync(this.logPath, 'utf-8');
643
- const lines = content.trim().split('\n');
644
-
645
- const entries = lines
646
- .map(line => {
647
- try {
648
- return JSON.parse(line) as AuditLogEntry;
649
- } catch {
650
- return null;
651
- }
652
- })
653
- .filter((entry): entry is AuditLogEntry => entry !== null);
654
-
655
- if (limit) {
656
- return entries.slice(-limit);
657
- }
658
-
659
- return entries;
660
- }
661
- }
662
- ```
663
-
664
- **使用例**
665
-
666
- ```typescript
667
- // michi migrate コマンド内
668
- const auditLogger = new AuditLogger(projectDir);
669
-
670
- try {
671
- // 移行前の設定を記録
672
- const oldConfig = readOldConfig();
673
- const newConfig = performMigration();
674
-
675
- // 変更内容を計算
676
- const changes = calculateChanges(oldConfig, newConfig);
677
-
678
- // 成功をログ
679
- auditLogger.logConfigChange(
680
- 'migrate',
681
- '~/.michi/.env',
682
- changes,
683
- true
684
- );
685
- } catch (error) {
686
- // 失敗をログ
687
- auditLogger.logConfigChange(
688
- 'migrate',
689
- '~/.michi/.env',
690
- {},
691
- false,
692
- error.message
693
- );
694
- throw error;
695
- }
696
- ```
697
-
698
- #### 9.3.2 セキュリティイベントの記録
699
-
700
- ```typescript
701
- // src/config/security-logger.ts
702
-
703
- export class SecurityLogger {
704
- /**
705
- * 不正なアクセス試行を記録
706
- */
707
- public logUnauthorizedAccess(
708
- resource: string,
709
- reason: string
710
- ): void {
711
- const event = {
712
- timestamp: new Date().toISOString(),
713
- type: 'UNAUTHORIZED_ACCESS',
714
- resource,
715
- reason,
716
- user: process.env.USER,
717
- pid: process.pid
718
- };
719
-
720
- console.error('🚨 セキュリティイベント:', JSON.stringify(event));
721
-
722
- // セキュリティログに記録
723
- this.appendToSecurityLog(event);
724
- }
725
-
726
- /**
727
- * 機密ファイルへのアクセスを記録
728
- */
729
- public logSensitiveFileAccess(
730
- filePath: string,
731
- operation: 'read' | 'write'
732
- ): void {
733
- const event = {
734
- timestamp: new Date().toISOString(),
735
- type: 'SENSITIVE_FILE_ACCESS',
736
- filePath,
737
- operation,
738
- user: process.env.USER,
739
- pid: process.pid
740
- };
741
-
742
- this.appendToSecurityLog(event);
743
- }
744
-
745
- private appendToSecurityLog(event: any): void {
746
- const logPath = join(os.homedir(), '.michi', 'security.log');
747
- const logLine = JSON.stringify(event) + '\n';
748
-
749
- mkdirSync(dirname(logPath), { recursive: true });
750
- appendFileSync(logPath, logLine, { mode: 0o600 });
751
- }
752
- }
753
- ```
754
-
755
- ### 9.4 セキュリティチェックリスト
756
-
757
- リリース前に確認すべき項目:
758
-
759
- - [ ] すべての機密ファイルが .gitignore に追加されている
760
- - [ ] ファイルパーミッションが適切(600 for .env files)
761
- - [ ] Zodバリデーションがすべての入力に適用されている
762
- - [ ] ログに機密情報が含まれていない
763
- - [ ] HTTPS が強制されている
764
- - [ ] エラーメッセージに内部情報が含まれていない
765
- - [ ] セキュリティテストが全てパスしている
766
- - [ ] 監査ログが正しく記録されている
767
- - [ ] セキュリティドキュメントが最新である
768
- - [ ] 脆弱性スキャンを実行している(npm audit)
769
-
770
- ---
771
-