phasegate 0.44.0 → 0.63.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 (52) hide show
  1. package/README.ja.md +32 -0
  2. package/README.md +33 -0
  3. package/docs/guide/codex-integration.md +162 -0
  4. package/docs/guide/quick-vs-full-mode.md +141 -0
  5. package/package.json +1 -1
  6. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +60 -0
  7. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +299 -0
  8. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +54 -0
  9. package/scripts/harness/agent-integration/presentation/user-prompt-submit-hook.ts +70 -0
  10. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +15 -0
  11. package/scripts/harness/integrations/pre-commit.ts +128 -28
  12. package/scripts/harness/main.ts +87 -9
  13. package/scripts/harness/phase2-extensions/application/dto/check-initial-creation-expiration-input.ts +9 -0
  14. package/scripts/harness/phase2-extensions/application/dto/check-initial-creation-expiration-output.ts +17 -0
  15. package/scripts/harness/phase2-extensions/application/usecases/check-initial-creation-expiration-usecase.ts +103 -0
  16. package/scripts/harness/phase2-extensions/composition-root.ts +22 -0
  17. package/scripts/harness/phase2-extensions/domain/aggregates/initial-creation-expiration-rule.ts +103 -0
  18. package/scripts/harness/phase2-extensions/domain/ports/frontmatter-reader-port.ts +17 -0
  19. package/scripts/harness/phase2-extensions/domain/ports/initial-creation-age-port.ts +9 -0
  20. package/scripts/harness/phase2-extensions/domain/ports/initial-creation-expiration-config-port.ts +9 -0
  21. package/scripts/harness/phase2-extensions/domain/services/initial-creation-expiration-check-service.ts +54 -0
  22. package/scripts/harness/phase2-extensions/domain/value-objects/initial-creation-age.ts +54 -0
  23. package/scripts/harness/phase2-extensions/infrastructure/adapters/git-log-initial-creation-age-adapter.ts +77 -0
  24. package/scripts/harness/phase2-extensions/infrastructure/adapters/harness-config-initial-creation-expiration-adapter.ts +57 -0
  25. package/scripts/harness/phase2-extensions/infrastructure/adapters/markdown-frontmatter-reader-adapter.ts +56 -0
  26. package/scripts/harness/phase2-extensions/presentation/formatters/initial-creation-expiration-result-formatter.ts +23 -0
  27. package/scripts/harness/phase2-extensions/presentation/handlers/check-initial-creation-expiration-handler.ts +38 -0
  28. package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +20 -0
  29. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +83 -0
  30. package/scripts/harness/quick-mode/composition-root.ts +12 -0
  31. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +38 -32
  32. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +36 -4
  33. package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +6 -0
  34. package/scripts/harness/quick-mode/presentation/formatters/change-category-formatter.ts +42 -0
  35. package/scripts/harness/quick-mode/presentation/handlers/check-change-category-handler.ts +50 -0
  36. package/scripts/harness/setup/skill-deployer.ts +30 -0
  37. package/scripts/harness/traceability-model/composition-root.ts +14 -0
  38. package/scripts/harness/traceability-model/domain/value-objects/project-relative-path.ts +3 -0
  39. package/scripts/harness/traceability-model/infrastructure/parsers/markdown-story-annotation-parser.ts +39 -4
  40. package/scripts/harness/traceability-model/presentation/cli/validate-metadata-command-handler.ts +103 -9
  41. package/skills/domain-designer/SKILL.md +34 -0
  42. package/skills/it-test-logic-designer/SKILL.md +16 -0
  43. package/skills/logical-designer/SKILL.md +34 -0
  44. package/skills/quick-implementor/SKILL.md +10 -1
  45. package/skills/scenario-test-logic-designer/SKILL.md +16 -0
  46. package/skills/story-implementor/SKILL.md +58 -0
  47. package/skills/unit-designer/SKILL.md +41 -0
  48. package/skills/unit-test-logic-designer/SKILL.md +18 -0
  49. package/templates/.codex/hooks.json +63 -0
  50. package/templates/logical_design.template.md +79 -0
  51. package/templates/source.template.ts +18 -0
  52. package/templates/test.template.ts +37 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @layer presentation
3
+ * @unit quick-mode
4
+ * @story H10-05
5
+ *
6
+ * phasegate check-change-category CLI のハンドラ
7
+ */
8
+
9
+ import type { ClassifyChangeCategoryUseCase } from '../../application/usecases/classify-change-category-usecase.js';
10
+ import { ChangeCategoryFormatter, type ChangeCategoryOutputFormat } from '../formatters/change-category-formatter.js';
11
+
12
+ export interface CheckChangeCategoryHandlerDeps {
13
+ useCase: Pick<ClassifyChangeCategoryUseCase, 'execute'>;
14
+ writer?: (s: string) => void;
15
+ }
16
+
17
+ export interface CheckChangeCategoryHandlerOptions {
18
+ paths?: string;
19
+ format?: ChangeCategoryOutputFormat;
20
+ failOnFullRequired?: boolean;
21
+ }
22
+
23
+ export interface CheckChangeCategoryHandlerResult {
24
+ exitCode: number;
25
+ }
26
+
27
+ export class CheckChangeCategoryHandler {
28
+ private readonly useCase: Pick<ClassifyChangeCategoryUseCase, 'execute'>;
29
+ private readonly writer: (s: string) => void;
30
+ private readonly formatter = new ChangeCategoryFormatter();
31
+
32
+ constructor(deps: CheckChangeCategoryHandlerDeps) {
33
+ this.useCase = deps.useCase;
34
+ this.writer = deps.writer ?? ((s: string) => process.stdout.write(s));
35
+ }
36
+
37
+ async handle(options: CheckChangeCategoryHandlerOptions): Promise<CheckChangeCategoryHandlerResult> {
38
+ const { paths: pathsRaw, format = 'human', failOnFullRequired = false } = options;
39
+
40
+ const paths = pathsRaw
41
+ ? pathsRaw.split(',').map((p) => p.trim()).filter((p) => p.length > 0)
42
+ : [];
43
+
44
+ const contract = await this.useCase.execute({ paths });
45
+ this.writer(this.formatter.format(contract, format));
46
+
47
+ const exitCode = failOnFullRequired && contract.fullModeRequired ? 1 : 0;
48
+ return { exitCode };
49
+ }
50
+ }
@@ -391,3 +391,33 @@ export async function deployHuskyHook(
391
391
 
392
392
  return { created: true, path: targetPath };
393
393
  }
394
+
395
+ export interface DeployCodexHooksResult {
396
+ created: boolean;
397
+ path: string;
398
+ }
399
+
400
+ /**
401
+ * Codex CLI 向け hooks.json を対象プロジェクトの .codex/ にデプロイする。
402
+ * 既存があればスキップする (ユーザー定義 hooks を上書きしない)。
403
+ * ISSUE-013 Wave 2: --agent codex フラグ経由で init から呼び出される。
404
+ */
405
+ export async function deployCodexHooks(
406
+ harnessRoot: string,
407
+ projectRoot: string,
408
+ ): Promise<DeployCodexHooksResult> {
409
+ const targetPath = join(projectRoot, '.codex', 'hooks.json');
410
+
411
+ try {
412
+ await fs.access(targetPath);
413
+ return { created: false, path: targetPath };
414
+ } catch {
415
+ // 配置先が存在しない場合は新規作成
416
+ }
417
+
418
+ const sourcePath = join(harnessRoot, 'templates', '.codex', 'hooks.json');
419
+ await fs.mkdir(join(projectRoot, '.codex'), { recursive: true });
420
+ await fs.copyFile(sourcePath, targetPath);
421
+
422
+ return { created: true, path: targetPath };
423
+ }
@@ -14,6 +14,8 @@ import { MetadataValidator } from './domain/services/metadata-validator.js';
14
14
  import { TraceabilityChainBuilder } from './domain/services/traceability-chain-builder.js';
15
15
  import { StoryIdAliasResolver } from './domain/services/story-id-alias-resolver.js';
16
16
  import { ValidateImplementationMetadataUseCase } from './application/usecases/validate-implementation-metadata-usecase.js';
17
+ import { ValidateDesignStoryAnnotationsUseCase } from './application/usecases/validate-design-story-annotations-usecase.js';
18
+ import { ValidateTestStoryMetadataUseCase } from './application/usecases/validate-test-story-metadata-usecase.js';
17
19
  import { ValidateMetadataCommandHandler } from './presentation/cli/validate-metadata-command-handler.js';
18
20
  import { ProjectRelativePath } from './domain/value-objects/project-relative-path.js';
19
21
 
@@ -45,10 +47,22 @@ export function createTraceabilityModelModule(rootDir: string) {
45
47
  metadataReaderPort: metadataReader,
46
48
  validator: metadataValidator,
47
49
  });
50
+ const validateDesignStoryAnnotationsUseCase =
51
+ new ValidateDesignStoryAnnotationsUseCase({
52
+ designDocumentPort: designDocument,
53
+ validator: metadataValidator,
54
+ });
55
+ const validateTestStoryMetadataUseCase =
56
+ new ValidateTestStoryMetadataUseCase({
57
+ metadataReaderPort: metadataReader,
58
+ validator: metadataValidator,
59
+ });
48
60
 
49
61
  // Presentation handlers
50
62
  const validateMetadataCommandHandler = new ValidateMetadataCommandHandler({
51
63
  validateImplementationMetadataUseCase,
64
+ validateDesignStoryAnnotationsUseCase,
65
+ validateTestStoryMetadataUseCase,
52
66
  createProjectRelativePath: (value: string) =>
53
67
  ProjectRelativePath.create(value),
54
68
  });
@@ -7,9 +7,12 @@
7
7
  const ALLOWED_PROJECT_ROOTS = new Set(['docs', 'scripts']);
8
8
 
9
9
  export class ProjectRelativePathError extends Error {
10
+ readonly value: string;
11
+
10
12
  constructor(value: string) {
11
13
  super(`ProjectRelativePathが不正です: ${value}`);
12
14
  this.name = 'ProjectRelativePathError';
15
+ this.value = value;
13
16
  }
14
17
  }
15
18
 
@@ -2,7 +2,8 @@
2
2
  * @layer infrastructure
3
3
  * @unit traceability-model
4
4
  *
5
- * Markdown本文から @story-id HXX-XX の独立行を抽出するパーサー
5
+ * Markdown本文から @story-id HXX-XX の独立行を抽出するパーサー。
6
+ * code-span (backtick) と code-fence (``` / ~~~) 内部の @story-id は prose として扱わず無視する。
6
7
  */
7
8
 
8
9
  export interface ParsedStoryAnnotation {
@@ -14,22 +15,56 @@ export interface ParsedStoryAnnotation {
14
15
 
15
16
  const STORY_ID_LINE_PATTERN = /^@story-id\s+(.+)$/;
16
17
  const STORY_ID_INLINE_PATTERN = /@story-id\s+(\S+)/;
18
+ const BACKTICK_FENCE_PATTERN = /^\s*```/;
19
+ const TILDE_FENCE_PATTERN = /^\s*~~~/;
20
+
21
+ type FenceChar = '`' | '~' | null;
22
+
23
+ function detectFenceChar(line: string): FenceChar {
24
+ if (BACKTICK_FENCE_PATTERN.test(line)) return '`';
25
+ if (TILDE_FENCE_PATTERN.test(line)) return '~';
26
+ return null;
27
+ }
28
+
29
+ function stripCodeSpans(line: string): string {
30
+ return line.replace(/`[^`\n]*`/g, '');
31
+ }
17
32
 
18
33
  /**
19
34
  * Markdown本文から @story-id 注釈を抽出する。
20
35
  * 行頭/行末空白を除去して独立行判定し、次行のコンテキストを contextLine として保持する。
36
+ * code-fence / code-span 内部の @story-id は除外する。
21
37
  */
22
38
  export function parseStoryAnnotations(
23
39
  content: string,
24
40
  ): readonly ParsedStoryAnnotation[] {
25
41
  const lines = content.split('\n');
26
42
  const annotations: ParsedStoryAnnotation[] = [];
43
+ let fenceChar: FenceChar = null;
27
44
 
28
45
  for (let i = 0; i < lines.length; i++) {
29
- const trimmedLine = lines[i].trim();
46
+ const rawLine = lines[i];
47
+ const trimmedLine = rawLine.trim();
30
48
  const lineNumber = i + 1;
31
49
 
32
- const standaloneMatch = STORY_ID_LINE_PATTERN.exec(trimmedLine);
50
+ const detected = detectFenceChar(rawLine);
51
+ if (fenceChar === null) {
52
+ if (detected !== null) {
53
+ fenceChar = detected;
54
+ }
55
+ if (fenceChar !== null) {
56
+ continue;
57
+ }
58
+ } else {
59
+ if (detected === fenceChar) {
60
+ fenceChar = null;
61
+ }
62
+ continue;
63
+ }
64
+
65
+ const sanitizedLine = stripCodeSpans(trimmedLine);
66
+
67
+ const standaloneMatch = STORY_ID_LINE_PATTERN.exec(sanitizedLine);
33
68
  if (standaloneMatch) {
34
69
  const contextLine =
35
70
  i + 1 < lines.length ? lines[i + 1].trim() : '';
@@ -42,7 +77,7 @@ export function parseStoryAnnotations(
42
77
  continue;
43
78
  }
44
79
 
45
- const inlineMatch = STORY_ID_INLINE_PATTERN.exec(trimmedLine);
80
+ const inlineMatch = STORY_ID_INLINE_PATTERN.exec(sanitizedLine);
46
81
  if (inlineMatch) {
47
82
  const contextLine =
48
83
  i + 1 < lines.length ? lines[i + 1].trim() : '';
@@ -4,8 +4,13 @@
4
4
  */
5
5
 
6
6
  import type { ValidateImplementationMetadataUseCase } from '../../application/usecases/validate-implementation-metadata-usecase.js';
7
+ import type { ValidateDesignStoryAnnotationsUseCase } from '../../application/usecases/validate-design-story-annotations-usecase.js';
8
+ import type { ValidateTestStoryMetadataUseCase } from '../../application/usecases/validate-test-story-metadata-usecase.js';
7
9
  import type { MetadataValidationOutput } from '../../application/dto/metadata-validation-output.js';
8
- import type { ProjectRelativePath } from '../../domain/value-objects/project-relative-path.js';
10
+ import {
11
+ ProjectRelativePathError,
12
+ type ProjectRelativePath,
13
+ } from '../../domain/value-objects/project-relative-path.js';
9
14
 
10
15
  export interface ValidateMetadataCommandInput {
11
16
  readonly filePaths: readonly string[];
@@ -19,21 +24,39 @@ export interface ValidateMetadataCommandOutput {
19
24
  }
20
25
 
21
26
  type PathFactory = (value: string) => ProjectRelativePath;
27
+ type ImplUseCase = Pick<ValidateImplementationMetadataUseCase, 'execute'>;
28
+ type DesignUseCase = Pick<ValidateDesignStoryAnnotationsUseCase, 'execute'>;
29
+ type TestUseCase = Pick<ValidateTestStoryMetadataUseCase, 'execute'>;
30
+
31
+ const DESIGN_DOCUMENT_EXTENSIONS: ReadonlySet<string> = new Set([
32
+ '.md',
33
+ '.mdx',
34
+ '.markdown',
35
+ ]);
36
+ const TEST_FILE_SUFFIXES = Object.freeze([
37
+ '.test.ts',
38
+ '.test.tsx',
39
+ '.spec.ts',
40
+ '.spec.tsx',
41
+ ]);
22
42
 
23
43
  export interface ValidateMetadataCommandHandlerDeps {
24
- readonly validateImplementationMetadataUseCase: Pick<
25
- ValidateImplementationMetadataUseCase,
26
- 'execute'
27
- >;
44
+ readonly validateImplementationMetadataUseCase: ImplUseCase;
45
+ readonly validateDesignStoryAnnotationsUseCase: DesignUseCase;
46
+ readonly validateTestStoryMetadataUseCase?: TestUseCase;
28
47
  readonly createProjectRelativePath: PathFactory;
29
48
  }
30
49
 
31
50
  export class ValidateMetadataCommandHandler {
32
- private readonly useCase: Pick<ValidateImplementationMetadataUseCase, 'execute'>;
51
+ private readonly implUseCase: ImplUseCase;
52
+ private readonly designUseCase: DesignUseCase;
53
+ private readonly testUseCase?: TestUseCase;
33
54
  private readonly createPath: PathFactory;
34
55
 
35
56
  constructor(deps: ValidateMetadataCommandHandlerDeps) {
36
- this.useCase = deps.validateImplementationMetadataUseCase;
57
+ this.implUseCase = deps.validateImplementationMetadataUseCase;
58
+ this.designUseCase = deps.validateDesignStoryAnnotationsUseCase;
59
+ this.testUseCase = deps.validateTestStoryMetadataUseCase;
37
60
  this.createPath = deps.createProjectRelativePath;
38
61
  }
39
62
 
@@ -50,7 +73,26 @@ export class ValidateMetadataCommandHandler {
50
73
 
51
74
  try {
52
75
  const paths = input.filePaths.map((p) => this.createPath(p));
53
- const results = await this.useCase.execute(paths);
76
+ const { designPaths, testPaths, implPaths } = this.classify(paths);
77
+
78
+ const [designResults, testResults, implResults] = await Promise.all([
79
+ designPaths.length > 0
80
+ ? this.designUseCase.execute(designPaths)
81
+ : Promise.resolve([] as readonly MetadataValidationOutput[]),
82
+ testPaths.length > 0 && this.testUseCase
83
+ ? this.testUseCase.execute(testPaths)
84
+ : Promise.resolve([] as readonly MetadataValidationOutput[]),
85
+ implPaths.length > 0
86
+ ? this.implUseCase.execute(implPaths)
87
+ : Promise.resolve([] as readonly MetadataValidationOutput[]),
88
+ ]);
89
+
90
+ const results = this.mergePreservingOrder(paths, [
91
+ ...designResults,
92
+ ...testResults,
93
+ ...implResults,
94
+ ]);
95
+
54
96
  const hasFailures = results.some((r) => !r.valid);
55
97
  const text = input.json
56
98
  ? JSON.stringify({ results }, null, 2)
@@ -61,7 +103,16 @@ export class ValidateMetadataCommandHandler {
61
103
  results,
62
104
  text,
63
105
  });
64
- } catch {
106
+ } catch (err) {
107
+ if (err instanceof ProjectRelativePathError) {
108
+ return Object.freeze({
109
+ exitCode: 2,
110
+ results: Object.freeze([]),
111
+ text:
112
+ `Error: invalid file path: "${err.value}"\n` +
113
+ ` Hint: paths must be project-relative and start with 'docs/' or 'scripts/'.`,
114
+ });
115
+ }
65
116
  return Object.freeze({
66
117
  exitCode: 2,
67
118
  results: Object.freeze([]),
@@ -70,6 +121,49 @@ export class ValidateMetadataCommandHandler {
70
121
  }
71
122
  }
72
123
 
124
+ private classify(paths: readonly ProjectRelativePath[]): {
125
+ readonly designPaths: readonly ProjectRelativePath[];
126
+ readonly testPaths: readonly ProjectRelativePath[];
127
+ readonly implPaths: readonly ProjectRelativePath[];
128
+ } {
129
+ const designPaths: ProjectRelativePath[] = [];
130
+ const testPaths: ProjectRelativePath[] = [];
131
+ const implPaths: ProjectRelativePath[] = [];
132
+ for (const path of paths) {
133
+ if (DESIGN_DOCUMENT_EXTENSIONS.has(path.extname())) {
134
+ designPaths.push(path);
135
+ } else if (this.isTestFile(path) && this.testUseCase) {
136
+ testPaths.push(path);
137
+ } else {
138
+ implPaths.push(path);
139
+ }
140
+ }
141
+ return { designPaths, testPaths, implPaths };
142
+ }
143
+
144
+ private isTestFile(path: ProjectRelativePath): boolean {
145
+ const value = path.toString();
146
+ return TEST_FILE_SUFFIXES.some((suffix) => value.endsWith(suffix));
147
+ }
148
+
149
+ private mergePreservingOrder(
150
+ orderedPaths: readonly ProjectRelativePath[],
151
+ results: readonly MetadataValidationOutput[],
152
+ ): readonly MetadataValidationOutput[] {
153
+ const byPath = new Map<string, MetadataValidationOutput>();
154
+ for (const result of results) {
155
+ byPath.set(result.filePath, result);
156
+ }
157
+ const ordered: MetadataValidationOutput[] = [];
158
+ for (const path of orderedPaths) {
159
+ const result = byPath.get(path.toString());
160
+ if (result) {
161
+ ordered.push(result);
162
+ }
163
+ }
164
+ return Object.freeze(ordered);
165
+ }
166
+
73
167
  private formatText(results: readonly MetadataValidationOutput[]): string {
74
168
  const lines: string[] = [];
75
169
  for (const r of results) {
@@ -142,6 +142,40 @@ DDD戦術パターンを用いてドメインモデルを設計するスキル
142
142
 
143
143
  ---
144
144
 
145
+ ## 🔗 成果物のトレーサビリティメタデータ(必須)
146
+
147
+ Phase 2 で生成する設計文書には、以下 2 種類のメタデータを emit する。`MetadataValidator.validateDesignDocument` が検証対象とし、ISSUE-008 Phase B-2/B-3 完了後は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
148
+
149
+ ### 1. YAML frontmatter(新規作成時)
150
+
151
+ 文書先頭に以下を付与する。既存文書の改訂時は省略してよい。
152
+
153
+ ```yaml
154
+ ---
155
+ traceability:
156
+ initial_creation: true
157
+ ---
158
+ ```
159
+
160
+ `initial_creation: true` は「新規作成であり、後述の `@story-id` 注釈が必須」であることを示す。
161
+
162
+ ### 2. `@story-id` インライン注釈
163
+
164
+ ユーザーストーリーに紐づく集約・エンティティ・VO・ドメインイベントの直前に `@story-id HXX-XX` を独立行で記述する。
165
+
166
+ ```markdown
167
+ @story-id H03-02
168
+ ### 集約: Order(注文)
169
+ ```
170
+
171
+ 形式ルール:
172
+ - **独立行** — 他のテキストと混在させない
173
+ - **直後に設計要素** — 空行を挟まない
174
+ - **StoryCatalog 存在** — `HXX-XX` は `docs/product/user_stories.md` に存在する ID
175
+ - **複数ストーリー時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
176
+
177
+ ---
178
+
145
179
  ## 注意事項
146
180
 
147
181
  - **ファイル配置は `docs/folder_management_rules.md` に従うこと**
@@ -231,6 +231,22 @@ Repositoryテストでは、`getTestClient()` でDBに直接クエリし永続
231
231
  - **WARNのみFAIL** → Opusが直接修正してから完了とする
232
232
  - **全PASS** → 完了
233
233
 
234
+ ## 🔗 テストファイルのトレーサビリティメタデータ(必須)
235
+
236
+ Phase 2 で設計する IT テストファイル(`*.test.ts` / `*.it.test.ts`)の疑似コード冒頭には、ファイル先頭コメントブロックに `// @story HXX-XX` を emit するよう明記する。`MetadataValidator.validateTest` が検証対象とし、ISSUE-008 Phase C-2 以降は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
237
+
238
+ ```typescript
239
+ // @unit <被テストコードと同じ Unit ID>
240
+ // @layer <被テストコードと同じ layer>
241
+ // @story H03-02
242
+ ```
243
+
244
+ 形式ルール:
245
+ - `@unit` / `@layer` と同じヘッダーコメントブロックに配置
246
+ - `HXX-XX` は `docs/product/user_stories.md` に存在する ID(StoryCatalog)
247
+ - 複数ストーリーをカバーする IT テストは `// @story H03-01, H03-02` のようにカンマ区切りで列挙
248
+ - 目的: US↔テストの逆引きを機械化(test-coverage-checker / nyquist の集計入力)
249
+
234
250
  ## 注意事項
235
251
 
236
252
  - **テストコードは生成しない**(設計文書のみ)— 実装は `story-implementor` スキル(codex-delegator経由、またはメインセッションで直接実行)が行う
@@ -179,6 +179,40 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
179
179
 
180
180
  ---
181
181
 
182
+ ## 🔗 成果物のトレーサビリティメタデータ(必須)
183
+
184
+ Phase 2 で生成する設計文書には、以下 2 種類のメタデータを emit する。`MetadataValidator.validateDesignDocument` が検証対象とし、ISSUE-008 Phase B-2/B-3 完了後は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
185
+
186
+ ### 1. YAML frontmatter(新規作成時)
187
+
188
+ 文書先頭に以下を付与する。既存文書の改訂時は省略してよい。
189
+
190
+ ```yaml
191
+ ---
192
+ traceability:
193
+ initial_creation: true
194
+ ---
195
+ ```
196
+
197
+ `initial_creation: true` は「新規作成であり、後述の `@story-id` 注釈が必須」であることを示す。
198
+
199
+ ### 2. `@story-id` インライン注釈
200
+
201
+ ユーザーストーリーに紐づく設計要素の直前に `@story-id HXX-XX` を独立行で記述する。
202
+
203
+ ```markdown
204
+ @story-id H03-02
205
+ ### ユースケース: 注文を確定する
206
+ ```
207
+
208
+ 形式ルール:
209
+ - **独立行** — 他のテキストと混在させない
210
+ - **直後に設計要素** — 空行を挟まない
211
+ - **StoryCatalog 存在** — `HXX-XX` は `docs/product/user_stories.md` に存在する ID
212
+ - **複数ストーリー時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
213
+
214
+ ---
215
+
182
216
  ## 注意事項
183
217
 
184
218
  - **コードスニペットは生成しない**(設計文書のみ)
@@ -62,7 +62,16 @@ Quick Mode下での軽微変更実装スキル。story-implementorの緩和版
62
62
 
63
63
  1. **修正コードの実装** — 最小限の変更で問題を解決する
64
64
  2. **テストの確認/追加** — 修正に対応するテストが存在するか確認、なければ追加
65
- 3. **L1チェック** `@unit` / `@layer` コメントの維持を確認
65
+ 3. **L1チェック(メタデータ付与)**
66
+ - **既存ファイル編集時**: `@unit` / `@layer` コメントを勝手に削除しない(意図せぬ欠落は L1-001 / L1-002 違反)
67
+ - **新規テストファイル作成時**: 先頭に必ず以下 3 行を付与する
68
+ ```typescript
69
+ // @unit <被テストコードと同じ Unit ID>
70
+ // @layer <被テストコードと同じ layer>
71
+ // @story <HXX-XX 形式のストーリーID>
72
+ ```
73
+ `@story` は `docs/inception/{unit}/{story_id}/` の `story_id` を使用。複数カバー時は `// @story H09-01, H09-02` のように列挙。
74
+ - **新規の非テストソースファイル作成**: Quick Mode のスコープ外 — `story-implementor` にエスカレーションする(quick-implementor は既存コードの修正と新規テスト追加が主スコープ)
66
75
  4. **L2チェック** — metadata 整合性、テスト品質(AAA, actual変数, 日本語テスト名)を確認
67
76
 
68
77
  ### Step 4: 検証
@@ -203,6 +203,22 @@ TDD実装フェーズ
203
203
  - **WARNのみFAIL** → Opusが直接修正してから完了とする
204
204
  - **全PASS** → 完了
205
205
 
206
+ ## 🔗 テストファイルのトレーサビリティメタデータ(必須)
207
+
208
+ Phase 2 で設計するシナリオ / E2E テストファイル(`*.spec.ts` / `*.e2e.test.ts`)の疑似コード冒頭には、ファイル先頭コメントブロックに `// @story HXX-XX` を emit するよう明記する。`MetadataValidator.validateTest` が検証対象とし、ISSUE-008 Phase C-2 以降は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
209
+
210
+ ```typescript
211
+ // @unit <対象 Unit ID(フロントエンドの場合は presentation / BFF Unit)>
212
+ // @layer <presentation | e2e>
213
+ // @story H09-01
214
+ ```
215
+
216
+ 形式ルール:
217
+ - `@unit` / `@layer` と同じヘッダーコメントブロックに配置
218
+ - `HXX-XX` は `docs/product/user_stories.md` に存在する ID(StoryCatalog)
219
+ - シナリオが複数 US を横断する場合は `// @story H09-01, H09-02` のようにカンマ区切りで列挙
220
+ - 目的: US↔シナリオテストの逆引きを機械化(test-coverage-checker / nyquist の集計入力)
221
+
206
222
  ## 注意事項
207
223
 
208
224
  - **テストコードは生成しない**(設計文書のみ)— 実装は `story-implementor` スキル(codex-delegator経由、またはメインセッションで直接実行)が行う
@@ -108,6 +108,64 @@ model: codex
108
108
 
109
109
  ---
110
110
 
111
+ ## ⚠️ 生成ファイルへのメタデータ付与(必須)
112
+
113
+ **新規ソースファイルを作成する際、ファイル先頭に必ず `@unit` / `@layer` メタデータを記述する。これは L1 Biome ルール (`L1-001: require-unit-comment`, `L1-002: require-layer-comment`) の要件であり、欠落した状態での生成は許容されない。**
114
+
115
+ ### TypeScript / JavaScript ファイル
116
+
117
+ ```typescript
118
+ // @unit <対象Unit名 — logical_design.md の Unit ID を使用>
119
+ // @layer <domain | application | infrastructure | presentation>
120
+
121
+ // 以下、実装コード
122
+ ```
123
+
124
+ ### `@unit` の決定方法
125
+
126
+ - `docs/product/construction/{unit}/logical_design.md` の Unit ID をそのまま引用する
127
+ - ストーリー固有実装の場合は、該当ストーリーが属する Unit の ID を使用
128
+ - **複数 Unit にまたがる場合は story-implementor のスコープ違反** — `implementation-readiness-checker` に差し戻し、Unit 分割を検討する
129
+
130
+ ### `@layer` の決定方法
131
+
132
+ 生成ファイルの配置パスから機械的に決定する:
133
+
134
+ | 配置パス | `@layer` の値 |
135
+ |---------|-------------|
136
+ | `**/domain/**` | `domain` |
137
+ | `**/application/**` | `application` |
138
+ | `**/infrastructure/**` | `infrastructure` |
139
+ | `**/presentation/**` | `presentation` |
140
+
141
+ Clean Architecture の 4 層構成に従わないプロジェクトは、プロジェクト側で層名を定義した上で phasegate のルール設定に同期させること。
142
+
143
+ ### テストファイルの場合
144
+
145
+ テストファイル(`**/__tests__/**`, `*.test.ts`, `*.spec.ts`)には `@unit` / `@layer` に加えて **`@story` タグを付与する**:
146
+
147
+ ```typescript
148
+ // @unit <被テストコードと同じ Unit ID>
149
+ // @layer <被テストコードと同じ layer>
150
+ // @story <HXX-XX 形式のストーリーID>
151
+ ```
152
+
153
+ - `@story` は `docs/inception/{unit}/{story_id}/` の `story_id` を使用
154
+ - 複数ストーリーをカバーするテストは `// @story H09-01, H09-02` のように列挙
155
+ - US↔テストの逆引きが機械的に可能になることが目的(test-coverage-checker / nyquist が集計に使用)
156
+
157
+ ### 検証
158
+
159
+ 実装完了後、以下で L1 違反が無いことを確認する:
160
+
161
+ ```bash
162
+ npx phasegate lint
163
+ ```
164
+
165
+ `L1-001` / `L1-002` で違反が検出された場合、本スキル終了前に必ず解決すること。スキルを抜けた後で付け直すのはアンチパターン(直後の L1 有効化時に全違反がまとめて噴出する)。
166
+
167
+ ---
168
+
111
169
  ## ⚠️ 2フェーズ実行ルール
112
170
 
113
171
  **このスキルは必ず2フェーズに分けて実行する。Phase 1で実装計画を作成し、人間の承認を得てからPhase 2でTDD実装を行う。Phase 1とPhase 2を同時に実行してはならない。**
@@ -154,6 +154,47 @@ Unit分割の方針・グルーピングの根拠・不明点を整理し、人
154
154
 
155
155
  ---
156
156
 
157
+ ## 🔗 成果物のトレーサビリティメタデータ(必須)
158
+
159
+ Phase 2 で生成する Unit 定義文書には、以下 2 種類のメタデータを emit する。`MetadataValidator.validateDesignDocument` が検証対象とし、ISSUE-008 Phase B-2/B-3 完了後は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
160
+
161
+ ### 1. YAML frontmatter(新規作成時)
162
+
163
+ `docs/product/units/{unit_name}.md` の先頭に以下を付与する。既存 Unit 定義の改訂時は省略してよい。`integration_contract.md` は Unit 横断のため任意。
164
+
165
+ ```yaml
166
+ ---
167
+ traceability:
168
+ initial_creation: true
169
+ ---
170
+ ```
171
+
172
+ `initial_creation: true` は「新規作成であり、後述の `@story-id` 注釈が必須」であることを示す。
173
+
174
+ ### 2. `@story-id` インライン注釈
175
+
176
+ ユーザーストーリーに紐づく機能要件・エンドポイント定義の直前に `@story-id HXX-XX` を独立行で記述する。
177
+
178
+ ```markdown
179
+ @story-id H03-02
180
+ ### 機能要件: 注文確定
181
+ ```
182
+
183
+ 形式ルール:
184
+ - **独立行** — 他のテキストと混在させない
185
+ - **直後に設計要素** — 空行を挟まない
186
+ - **StoryCatalog 存在** — `HXX-XX` は `docs/product/user_stories.md` に存在する ID
187
+ - **複数ストーリー時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
188
+
189
+ ### 3. Phase 3 レビューでの BLOCK 確認
190
+
191
+ Phase 3 レビューで以下を BLOCK 基準として確認する:
192
+ - 新規作成文書に `initial_creation: true` frontmatter が付与されているか
193
+ - 担当ストーリーに対応する機能要件の直前に `@story-id` が配置されているか
194
+ - 上記形式ルールに準拠しているか
195
+
196
+ ---
197
+
157
198
  ## Phase 3: レビュー(Opus review)
158
199
 
159
200
  ### 実行主体
@@ -189,6 +189,24 @@ TDD実装フェーズ
189
189
 
190
190
  ---
191
191
 
192
+ ## 🔗 テストファイルのトレーサビリティメタデータ(必須)
193
+
194
+ Phase 2 で設計するテストファイル(`*.test.ts` / `*.spec.ts`)の疑似コード冒頭には、ファイル先頭コメントブロックに `// @story HXX-XX` を emit するよう明記する。`MetadataValidator.validateTest` が検証対象とし、ISSUE-008 Phase C-2 以降は `npx phasegate validate-metadata` / pre-commit で自動チェックされる。
195
+
196
+ ```typescript
197
+ // @unit <被テストコードと同じ Unit ID>
198
+ // @layer <被テストコードと同じ layer>
199
+ // @story H03-02
200
+ ```
201
+
202
+ 形式ルール:
203
+ - `@unit` / `@layer` と同じヘッダーコメントブロックに配置
204
+ - `HXX-XX` は `docs/product/user_stories.md` に存在する ID(StoryCatalog)
205
+ - 複数ストーリーをカバーするテストは `// @story H03-01, H03-02` のようにカンマ区切りで列挙
206
+ - 目的: US↔テストの逆引きを機械化(test-coverage-checker / nyquist の集計入力)
207
+
208
+ ---
209
+
192
210
  ## 注意事項
193
211
 
194
212
  - **テストコードは生成しない**(設計文書のみ)— 実装は `story-implementor` が行う