phasegate 0.191.0 → 0.222.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 (77) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.ja.md +52 -15
  3. package/README.md +39 -11
  4. package/docs/ADR/030-injection-threat-model-and-trust-root.md +145 -0
  5. package/docs/guide/hooks-integration.md +101 -1
  6. package/docs/guide/installation.md +1 -1
  7. package/docs/guide/quick-vs-full-mode.md +1 -1
  8. package/docs/guide/skills-overview.md +7 -8
  9. package/package.json +1 -1
  10. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +131 -63
  11. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +31 -4
  12. package/scripts/harness/agent-integration/presentation/spotlight.ts +65 -0
  13. package/scripts/harness/biome-ast-engine/application/mappers/build-harness-error-payload-output-mapper.ts +3 -3
  14. package/scripts/harness/biome-ast-engine/infrastructure/mappers/rule-violation-code-mapper.ts +3 -3
  15. package/scripts/harness/biome-ast-engine/presentation/cli/harness-lint-command-handler.ts +6 -4
  16. package/scripts/harness/ci-governance/application/dto/pin-integrity-input.ts +8 -0
  17. package/scripts/harness/ci-governance/application/dto/pin-integrity-output.ts +9 -0
  18. package/scripts/harness/ci-governance/application/dto/verify-integrity-input.ts +7 -0
  19. package/scripts/harness/ci-governance/application/dto/verify-integrity-output.ts +10 -0
  20. package/scripts/harness/ci-governance/application/usecases/pin-integrity-usecase.ts +60 -0
  21. package/scripts/harness/ci-governance/application/usecases/verify-integrity-usecase.ts +46 -0
  22. package/scripts/harness/ci-governance/composition-root.ts +67 -64
  23. package/scripts/harness/ci-governance/domain/ports/integrity-manifest-repository-port.ts +14 -0
  24. package/scripts/harness/ci-governance/domain/ports/sha256-hasher-port.ts +10 -0
  25. package/scripts/harness/ci-governance/domain/services/integrity-checker.ts +42 -0
  26. package/scripts/harness/ci-governance/domain/value-objects/integrity-drift.ts +16 -0
  27. package/scripts/harness/ci-governance/domain/value-objects/integrity-manifest.ts +49 -0
  28. package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +40 -0
  29. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-sha256-hasher-adapter.ts +17 -0
  30. package/scripts/harness/ci-governance/infrastructure/adapters/harness-api-command-existence-adapter.ts +7 -73
  31. package/scripts/harness/ci-governance/infrastructure/adapters/integrity-manifest-json-repository-adapter.ts +83 -0
  32. package/scripts/harness/ci-governance/presentation/handlers/integrity-handler.ts +76 -0
  33. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +60 -28
  34. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +33 -8
  35. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +90 -0
  36. package/scripts/harness/harness-error/infrastructure/registry/l1-error-definitions.ts +9 -9
  37. package/scripts/harness/installation/application/bundled-skill-selection.ts +2 -5
  38. package/scripts/harness/installation/application/usecases/run-reconcile.ts +69 -1
  39. package/scripts/harness/main.ts +257 -105
  40. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +65 -3
  41. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +44 -40
  42. package/scripts/harness/setup/skill-deployer.ts +2 -4
  43. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +82 -49
  44. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +133 -53
  45. package/scripts/harness/validator-system/composition-root.ts +153 -99
  46. package/scripts/harness/validator-system/domain/ports/coverage-attestation-gating-policy-port.ts +14 -0
  47. package/scripts/harness/validator-system/domain/ports/coverage-attestation-verification-policy-port.ts +30 -0
  48. package/scripts/harness/validator-system/domain/ports/injection-scan-policy-port.ts +14 -0
  49. package/scripts/harness/validator-system/domain/services/coverage-attestation-gating-service.ts +56 -0
  50. package/scripts/harness/validator-system/domain/services/coverage-attestation-verification-service.ts +45 -0
  51. package/scripts/harness/validator-system/domain/services/injection-pattern-scan-service.ts +118 -0
  52. package/scripts/harness/validator-system/domain/value-objects/attestation-verification-report.ts +59 -0
  53. package/scripts/harness/validator-system/domain/value-objects/coverage-gating-report.ts +67 -0
  54. package/scripts/harness/validator-system/domain/value-objects/injection-scan-report.ts +55 -0
  55. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +37 -31
  56. package/scripts/harness/validator-system/infrastructure/adapters/adr-foundation-reference-adapter.ts +13 -7
  57. package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-gating-adapter.ts +87 -0
  58. package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-verification-adapter.ts +165 -0
  59. package/scripts/harness/validator-system/infrastructure/adapters/file-system-injection-scan-adapter.ts +82 -0
  60. package/skills/README.md +1 -1
  61. package/skills/codebase-mapper/SKILL.md +1 -1
  62. package/skills/consistency-checker/references//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +1 -1
  63. package/skills/doc-health-checker/SKILL.md +148 -0
  64. package/skills/release-publisher/SKILL.md +101 -0
  65. package/skills/skill-creator/SKILL.md +74 -332
  66. package/skills/story-implementor/SKILL.md +52 -0
  67. package/skills/story-mapper/SKILL.md +4 -0
  68. package/skills/story-writer/SKILL.md +9 -0
  69. package/skills/uiux-designer/references/uiux-design-template.md +4 -4
  70. package/skills/unit-designer/SKILL.md +3 -1
  71. package/templates/.claude/scripts/deny-check.sh +260 -0
  72. package/skills/doc-freshness-checker/SKILL.md +0 -140
  73. package/skills/implementation-planner/SKILL.md +0 -169
  74. package/skills/implementation-planner/references/document-structure.md +0 -116
  75. package/skills/implementation-planner/references/plan-template.md +0 -177
  76. package/skills/implementation-planner/references/workflow.md +0 -164
  77. package/skills/pointer-validator/SKILL.md +0 -105
@@ -0,0 +1,118 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-259
4
+
5
+ /**
6
+ * WI-259 / ADR-030 §Decision.3.④ — advisory インジェクションスキャナ(L3-006)の判定ドメインサービス。
7
+ *
8
+ * 指示搭載ファイル群に対し既知のインジェクションパターンを行単位で照合する純粋サービス。
9
+ * INV-A: 生成する finding は必ず severity='warning'(error / violation を一切生成しない — §4.(b))。
10
+ * INV-B: HTML コメント内の指示上書きは html-comment-instruction に一本化し instruction-override として二重報告しない。
11
+ * INV-C: どのパターンにも該当しない対象は finding を生成しない(無音 → pass)。
12
+ *
13
+ * パターンは narrow に設計し、自リポジトリの正当な文書(散文)に誤検知しないことを実 corpus で実証する。
14
+ */
15
+
16
+ import {
17
+ type InjectionFinding,
18
+ InjectionScanReport,
19
+ type InjectionScanTarget,
20
+ } from "../value-objects/injection-scan-report.js";
21
+
22
+ /** 指示上書きフレーズ(英/日)。narrow な定型のみ。 */
23
+ const OVERRIDE_PATTERNS: readonly RegExp[] = [
24
+ /ignore\s+(?:all\s+|any\s+)?(?:previous|prior|above)\s+(?:instructions|rules)/i,
25
+ /disregard\s+(?:your|all)\s+(?:instructions|training)/i,
26
+ /これまでの指示を無視/,
27
+ /以前の指示を無視/,
28
+ /上記の指示を無視/,
29
+ /指示を(?:全て|すべて)無視/,
30
+ /システムプロンプトを無視/,
31
+ ];
32
+
33
+ /** HTML コメントを含む行か(`<!--` の存在で判定。行内コメント・複数行コメント開始のいずれも拾う)。 */
34
+ const HTML_COMMENT_LINE = /<!--/;
35
+
36
+ /** 不可視 Unicode: zero-width (U+200B-U+200D, U+FEFF) + bidi 制御 (U+202A-U+202E, U+2066-U+2069)。 */
37
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: bidi/zero-width 制御文字の検出が目的
38
+ const INVISIBLE_UNICODE = /[​-‍‪-‮⁦-⁩]/;
39
+
40
+ /** 連続 200 文字以上の base64 文字列塊。 */
41
+ const BASE64_BLOB = /[A-Za-z0-9+/]{200,}={0,2}/;
42
+
43
+ export class InjectionPatternScanService {
44
+ scan(targets: readonly InjectionScanTarget[]): InjectionScanReport {
45
+ const findings: InjectionFinding[] = [];
46
+
47
+ for (const target of targets) {
48
+ const lines = target.content.split(/\r?\n/);
49
+ for (let i = 0; i < lines.length; i++) {
50
+ const line = lines[i];
51
+ const lineNumber = i + 1;
52
+
53
+ const hasOverride = OVERRIDE_PATTERNS.some((pattern) => pattern.test(line));
54
+ if (hasOverride) {
55
+ // INV-B: HTML コメント内の指示上書きは html-comment-instruction に一本化する。
56
+ if (HTML_COMMENT_LINE.test(line)) {
57
+ findings.push(
58
+ this.finding(
59
+ "html-comment-instruction",
60
+ target.path,
61
+ lineNumber,
62
+ "HTML コメント内にエージェント宛ての指示上書きフレーズが埋め込まれています。",
63
+ "隠蔽された指示の可能性があります。人間レビューで意図を確認してください(advisory)。",
64
+ ),
65
+ );
66
+ } else {
67
+ findings.push(
68
+ this.finding(
69
+ "instruction-override",
70
+ target.path,
71
+ lineNumber,
72
+ "指示上書きフレーズ(例: ignore previous instructions / これまでの指示を無視)を検出しました。",
73
+ "インジェクションの可能性があります。人間レビューで意図を確認してください(advisory)。",
74
+ ),
75
+ );
76
+ }
77
+ }
78
+
79
+ if (INVISIBLE_UNICODE.test(line)) {
80
+ findings.push(
81
+ this.finding(
82
+ "invisible-unicode",
83
+ target.path,
84
+ lineNumber,
85
+ "不可視 Unicode 文字(zero-width / bidi 制御)を検出しました。",
86
+ "見えない文字で指示を隠蔽している可能性があります。人間レビューで確認してください(advisory)。",
87
+ ),
88
+ );
89
+ }
90
+
91
+ if (BASE64_BLOB.test(line)) {
92
+ findings.push(
93
+ this.finding(
94
+ "base64-blob",
95
+ target.path,
96
+ lineNumber,
97
+ "連続 200 文字以上の base64 らしき塊を検出しました。",
98
+ "エンコードされた指示・ペイロードの可能性があります。人間レビューで確認してください(advisory)。",
99
+ ),
100
+ );
101
+ }
102
+ }
103
+ }
104
+
105
+ return InjectionScanReport.create(findings);
106
+ }
107
+
108
+ private finding(
109
+ kind: InjectionFinding["kind"],
110
+ sourcePath: string,
111
+ lineNumber: number,
112
+ message: string,
113
+ suggestion: string,
114
+ ): InjectionFinding {
115
+ // INV-A: severity は必ず 'warning'。
116
+ return { kind, severity: "warning", sourcePath, lineNumber, message, suggestion };
117
+ }
118
+ }
@@ -0,0 +1,59 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-268
4
+
5
+ /**
6
+ * WI-268 / ADR-030 §Decision.1・§Decision.3.②(第2段) — coverage_report の attestation
7
+ * 参照を requirement-test-matrix に対して authoritative に突合する L3-007 用ドメインモデル。
8
+ *
9
+ * L2-016(bare ✅ の遮断=参照の形状のみ)の authoritative 相棒。参照 id が本ランのテスト
10
+ * corpus から再生成された matrix 上に実在し、テスト参照を持つことを機械検証する。解決不能な
11
+ * 参照(空手形の attestation)は fail-closed の error として遮断する。
12
+ */
13
+
14
+ /** coverage_report 内の 1 件の `<!-- @attestation <id> -->` 参照。 */
15
+ export interface AttestationReference {
16
+ /** 参照 id(story-id 形式を期待。例 `H05-02`)。 */
17
+ readonly id: string;
18
+ /** 参照を含む coverage_report の project-relative パス。 */
19
+ readonly sourcePath: string;
20
+ /** 参照行(1 起点)。 */
21
+ readonly lineNumber: number;
22
+ }
23
+
24
+ /**
25
+ * matrix 由来の「解決可能なスコープ」集合。
26
+ * resolvableScopeIds = matrix 上に存在し、かつ testReferences を 1 件以上持つ story-id 集合。
27
+ */
28
+ export interface AttestationScopeEvidence {
29
+ readonly resolvableScopeIds: ReadonlySet<string>;
30
+ }
31
+
32
+ /** 1 件の突合違反(解決不能な参照)。severity は常に 'error'(fail-closed / blocking tier)。 */
33
+ export interface AttestationVerificationFinding {
34
+ readonly severity: "error";
35
+ readonly sourcePath: string;
36
+ readonly lineNumber: number;
37
+ readonly message: string;
38
+ readonly suggestion: string;
39
+ }
40
+
41
+ /**
42
+ * 突合判定レポート。findings はすべて error(fail-closed)。
43
+ */
44
+ export class AttestationVerificationReport {
45
+ readonly findings: readonly AttestationVerificationFinding[];
46
+
47
+ private constructor(findings: readonly AttestationVerificationFinding[]) {
48
+ this.findings = Object.freeze([...findings]);
49
+ Object.freeze(this);
50
+ }
51
+
52
+ static create(findings: readonly AttestationVerificationFinding[]): AttestationVerificationReport {
53
+ return new AttestationVerificationReport(findings);
54
+ }
55
+
56
+ hasFindings(): boolean {
57
+ return this.findings.length > 0;
58
+ }
59
+ }
@@ -0,0 +1,67 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-258
4
+
5
+ /**
6
+ * WI-258 / ADR-030 §Decision.3.② — coverage_report の attestation ゲート(L2-016)用ドメインモデル。
7
+ *
8
+ * coverage_report.md の ✅ 主張に attestation 参照(`<!-- @attestation <id> -->`)を要求する
9
+ * anti-laundering の value-object 群。L2 は参照の存在・形状のみを検証し(bare ✅ を fail-closed で遮断)、
10
+ * attestation レコードとの authoritative 突合は L3 が担う(ADR-030)。
11
+ */
12
+
13
+ /** 1 件の ✅ 主張(AC が「達成」と主張する行)。 */
14
+ export interface CoverageClaim {
15
+ /** ✅ を含む行番号(1 起点)。 */
16
+ readonly lineNumber: number;
17
+ /** 同一行 or 直前の連続コメント行に `@attestation` 参照があるか。 */
18
+ readonly hasAttestationRef: boolean;
19
+ }
20
+
21
+ /** 1 coverage_report ファイルの走査結果。 */
22
+ export interface CoverageReportGatingModel {
23
+ readonly path: string;
24
+ /** `<!-- @coverage-gating: ungated-legacy -->` マーカー(ファイル全体免除)の有無。 */
25
+ readonly hasLegacyMarker: boolean;
26
+ readonly claims: readonly CoverageClaim[];
27
+ }
28
+
29
+ /** ゲート判定の 1 finding。 */
30
+ export interface CoverageGatingFinding {
31
+ readonly severity: "error" | "warning";
32
+ readonly sourcePath: string;
33
+ readonly message: string;
34
+ readonly suggestion: string;
35
+ }
36
+
37
+ /**
38
+ * ゲート判定レポート。violations は fail-closed の error、warnings は legacy 可視化。
39
+ */
40
+ export class CoverageGatingReport {
41
+ readonly violations: readonly CoverageGatingFinding[];
42
+ readonly warnings: readonly CoverageGatingFinding[];
43
+ readonly legacyCount: number;
44
+
45
+ private constructor(
46
+ violations: readonly CoverageGatingFinding[],
47
+ warnings: readonly CoverageGatingFinding[],
48
+ legacyCount: number,
49
+ ) {
50
+ this.violations = Object.freeze([...violations]);
51
+ this.warnings = Object.freeze([...warnings]);
52
+ this.legacyCount = legacyCount;
53
+ Object.freeze(this);
54
+ }
55
+
56
+ static create(
57
+ violations: readonly CoverageGatingFinding[],
58
+ warnings: readonly CoverageGatingFinding[],
59
+ legacyCount: number,
60
+ ): CoverageGatingReport {
61
+ return new CoverageGatingReport(violations, warnings, legacyCount);
62
+ }
63
+
64
+ hasViolations(): boolean {
65
+ return this.violations.length > 0;
66
+ }
67
+ }
@@ -0,0 +1,55 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-259
4
+
5
+ /**
6
+ * WI-259 / ADR-030 §Decision.3.④ — advisory インジェクションスキャナ(L3-006)用ドメインモデル。
7
+ *
8
+ * 指示搭載ファイルに対する既知インジェクションパターンの検出結果を表す value-object 群。
9
+ * 全 finding は severity='warning'(advisory)。error / violation は構造上生成しない(§4.(b))。
10
+ */
11
+
12
+ /** 検出種別。 */
13
+ export type InjectionFindingKind =
14
+ | "instruction-override"
15
+ | "invisible-unicode"
16
+ | "base64-blob"
17
+ | "html-comment-instruction";
18
+
19
+ /** 1 走査対象ファイル(infra が解決)。 */
20
+ export interface InjectionScanTarget {
21
+ /** project-relative パス(報告用)。 */
22
+ readonly path: string;
23
+ readonly content: string;
24
+ }
25
+
26
+ /** 1 件の検出。severity は常に 'warning'。 */
27
+ export interface InjectionFinding {
28
+ readonly kind: InjectionFindingKind;
29
+ readonly severity: "warning";
30
+ readonly sourcePath: string;
31
+ /** 1 起点の行番号。 */
32
+ readonly lineNumber: number;
33
+ readonly message: string;
34
+ readonly suggestion: string;
35
+ }
36
+
37
+ /**
38
+ * スキャン判定レポート。findings はすべて warning(advisory)。
39
+ */
40
+ export class InjectionScanReport {
41
+ readonly findings: readonly InjectionFinding[];
42
+
43
+ private constructor(findings: readonly InjectionFinding[]) {
44
+ this.findings = Object.freeze([...findings]);
45
+ Object.freeze(this);
46
+ }
47
+
48
+ static create(findings: readonly InjectionFinding[]): InjectionScanReport {
49
+ return new InjectionScanReport(findings);
50
+ }
51
+
52
+ hasFindings(): boolean {
53
+ return this.findings.length > 0;
54
+ }
55
+ }
@@ -11,13 +11,16 @@
11
11
  * WI-156 で L4-006 を追加
12
12
  * WI-222 (HF2-05) で L4-007(ac-level-traceability, default-OFF advisory)を追加
13
13
  * WI-227 (H16-03) で L3-005(ac-bound-coverage, default-OFF fail-closed)を追加
14
+ * WI-258 (ADR-030 §Decision.3.②) で L2-016(coverage-attestation-gating, fail-closed)を追加
15
+ * WI-259 (ADR-030 §Decision.3.④) で L3-006(injection-scan, advisory warning-only)を追加
16
+ * WI-268 (ADR-030 §Decision.1・§Decision.3.② 第2段) で L3-007(coverage-attestation-verification, fail-closed)を追加
14
17
  */
15
18
 
16
19
  export class InvalidValidatorIdError extends Error {
17
20
  readonly invalidValue: string;
18
21
  constructor(raw: string) {
19
22
  super(`Invalid validator ID: "${raw}". Must be one of the registered valid IDs.`);
20
- this.name = 'InvalidValidatorIdError';
23
+ this.name = "InvalidValidatorIdError";
21
24
  this.invalidValue = raw;
22
25
  }
23
26
  }
@@ -26,38 +29,41 @@ const VALIDATOR_ID_PATTERN = /^L[0-4]-\d{3}$/;
26
29
 
27
30
  /** バリデータID -> バリデータ名のマップ */
28
31
  const VALIDATOR_NAME_MAP: Record<string, string> = {
29
- 'L1-017': 'it-test-mock-detection',
30
- 'L1-018': 'stub-comment-detection',
31
- 'L2-001': 'phase-gate',
32
- 'L2-002': 'metadata',
33
- 'L2-003': 'test-quality',
34
- 'L2-013': 'cli-e2e-test-existence',
35
- 'L2-014': 'work-item-status-staleness',
36
- 'L2-015': 'contract-traceability-coverage',
37
- 'L3-001': 'security',
38
- 'L3-002': 'performance',
39
- 'L3-003': 'coverage',
40
- 'L3-004': 'nyquist',
41
- 'L3-005': 'ac-bound-coverage',
42
- 'L4-001': 'drift-detect',
43
- 'L4-002': 'consistency-check',
44
- 'L4-003': 'dead-code',
45
- 'L4-004': 'doc-freshness',
46
- 'L4-005': 'pointer-validation',
47
- 'L4-006': 'skill-catalog-drift',
48
- 'L4-007': 'ac-level-traceability',
32
+ "L1-017": "it-test-mock-detection",
33
+ "L1-018": "stub-comment-detection",
34
+ "L2-001": "phase-gate",
35
+ "L2-002": "metadata",
36
+ "L2-003": "test-quality",
37
+ "L2-013": "cli-e2e-test-existence",
38
+ "L2-014": "work-item-status-staleness",
39
+ "L2-015": "contract-traceability-coverage",
40
+ "L2-016": "coverage-attestation-gating",
41
+ "L3-001": "security",
42
+ "L3-002": "performance",
43
+ "L3-003": "coverage",
44
+ "L3-004": "nyquist",
45
+ "L3-005": "ac-bound-coverage",
46
+ "L3-006": "injection-scan",
47
+ "L3-007": "coverage-attestation-verification",
48
+ "L4-001": "drift-detect",
49
+ "L4-002": "consistency-check",
50
+ "L4-003": "dead-code",
51
+ "L4-004": "doc-freshness",
52
+ "L4-005": "pointer-validation",
53
+ "L4-006": "skill-catalog-drift",
54
+ "L4-007": "ac-level-traceability",
49
55
  };
50
56
 
51
57
  /** バリデータ名 -> バリデータID の逆引きマップ */
52
58
  const NAME_TO_ID_MAP: Record<string, string> = {
53
59
  ...Object.fromEntries(Object.entries(VALIDATOR_NAME_MAP).map(([id, name]) => [name, id])),
54
- 'drift-detector': 'L4-001',
55
- 'consistency-checker': 'L4-002',
56
- 'dead-code-detector': 'L4-003',
57
- 'doc-freshness-checker': 'L4-004',
58
- 'pointer-validator': 'L4-005',
59
- 'skill-catalog-drift': 'L4-006',
60
- 'ac-level-traceability': 'L4-007',
60
+ "drift-detector": "L4-001",
61
+ "consistency-checker": "L4-002",
62
+ "dead-code-detector": "L4-003",
63
+ "doc-freshness-checker": "L4-004",
64
+ "pointer-validator": "L4-005",
65
+ "skill-catalog-drift": "L4-006",
66
+ "ac-level-traceability": "L4-007",
61
67
  };
62
68
 
63
69
  /** 有効なValidatorID集合 */
@@ -65,12 +71,12 @@ const VALID_IDS = new Set(Object.keys(VALIDATOR_NAME_MAP));
65
71
 
66
72
  export class ValidatorId {
67
73
  readonly value: string;
68
- readonly layer: 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
74
+ readonly layer: "L0" | "L1" | "L2" | "L3" | "L4";
69
75
  readonly sequence: string;
70
76
 
71
77
  private constructor(value: string) {
72
78
  this.value = value;
73
- this.layer = value.substring(0, 2) as 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
79
+ this.layer = value.substring(0, 2) as "L0" | "L1" | "L2" | "L3" | "L4";
74
80
  this.sequence = value.substring(3);
75
81
  Object.freeze(this);
76
82
  }
@@ -93,7 +99,7 @@ export class ValidatorId {
93
99
  return new ValidatorId(id);
94
100
  }
95
101
 
96
- getLayer(): 'L0' | 'L1' | 'L2' | 'L3' | 'L4' {
102
+ getLayer(): "L0" | "L1" | "L2" | "L3" | "L4" {
97
103
  return this.layer;
98
104
  }
99
105
 
@@ -4,9 +4,12 @@
4
4
  *
5
5
  * AdrFoundationReferenceAdapter — AdrReferencePort実装
6
6
  */
7
- import type { AdrReferencePort, AdrMetadata } from '../../domain/ports/adr-reference-port.js';
7
+ import * as path from "node:path";
8
+ import type { AdrMetadata, AdrReferencePort } from "../../domain/ports/adr-reference-port.js";
8
9
 
9
10
  export class AdrFoundationReferenceAdapter implements AdrReferencePort {
11
+ constructor(private readonly rootDir: string = process.cwd()) {}
12
+
10
13
  async exists(adrRef: string): Promise<boolean> {
11
14
  try {
12
15
  const adr = await this.findAdr(adrRef);
@@ -35,19 +38,22 @@ export class AdrFoundationReferenceAdapter implements AdrReferencePort {
35
38
  }
36
39
 
37
40
  private async findAdr(adrRef: string): Promise<{
38
- readonly getFrontmatter: () => { readonly title: string; readonly status: { readonly value: AdrMetadata['status'] } };
41
+ readonly getFrontmatter: () => {
42
+ readonly title: string;
43
+ readonly status: { readonly value: AdrMetadata["status"] };
44
+ };
39
45
  readonly toAdrRef: () => string;
40
46
  } | null> {
41
- const { createAdrFoundationModule } = await import('../../../adr-foundation/composition-root.js');
42
- const { AdrId } = await import('../../../adr-foundation/domain/value-objects/adr-id.js');
43
- const mod = createAdrFoundationModule(process.cwd());
47
+ const { createAdrFoundationModule } = await import("../../../adr-foundation/composition-root.js");
48
+ const { AdrId } = await import("../../../adr-foundation/domain/value-objects/adr-id.js");
49
+ const mod = createAdrFoundationModule(path.join(this.rootDir, "docs", "ADR"));
44
50
  const repository = mod.adrRepository;
45
51
 
46
- if ('findById' in repository && typeof repository.findById === 'function') {
52
+ if ("findById" in repository && typeof repository.findById === "function") {
47
53
  return repository.findById(AdrId.create(adrRef));
48
54
  }
49
55
 
50
- if ('findAll' in repository && typeof repository.findAll === 'function') {
56
+ if ("findAll" in repository && typeof repository.findAll === "function") {
51
57
  const adrs = await repository.findAll();
52
58
  return adrs.find((adr) => adr.toAdrRef() === adrRef) ?? null;
53
59
  }
@@ -0,0 +1,87 @@
1
+ // @unit validator-system
2
+ // @layer infrastructure
3
+ // @work-item-id WI-258
4
+
5
+ import { readdir, readFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import type { CoverageAttestationGatingPolicyPort } from "../../domain/ports/coverage-attestation-gating-policy-port.js";
8
+ import type { CoverageClaim, CoverageReportGatingModel } from "../../domain/value-objects/coverage-gating-report.js";
9
+
10
+ const COVERAGE_REPORT_FILE = "coverage_report.md";
11
+ const CONSTRUCTION_REL = join("docs", "product", "construction");
12
+ const LEGACY_MARKER = /<!--\s*@coverage-gating:\s*ungated-legacy\s*-->/;
13
+ const ATTESTATION_REF = /<!--\s*@attestation\b/;
14
+ const CHECKMARK = "✅"; // ✅
15
+ const COMMENT_LINE = /^\s*<!--/;
16
+
17
+ /**
18
+ * WI-258 / ADR-030 §Decision.3.② — coverage-attestation-gating (L2-016) の走査アダプタ。
19
+ *
20
+ * `docs/product/construction/*​/coverage_report.md` を cwd 起点で走査し(targetPaths 非依存の
21
+ * corpus 走査。L2-014 と同様に自前でファイル探索する)、各ファイルを CoverageReportGatingModel 化する。
22
+ *
23
+ * - legacy マーカー: `<!-- @coverage-gating: ungated-legacy -->` の有無。
24
+ * - ✅ claim: `✅` を含む行。同一行に `<!-- @attestation ... -->` があるか、
25
+ * 直前の連続コメント行に `@attestation` があれば hasAttestationRef=true。
26
+ */
27
+ export class FileSystemCoverageAttestationGatingAdapter implements CoverageAttestationGatingPolicyPort {
28
+ constructor(private readonly projectRoot: string) {}
29
+
30
+ async collect(): Promise<readonly CoverageReportGatingModel[]> {
31
+ const constructionRoot = join(this.projectRoot, CONSTRUCTION_REL);
32
+ let unitDirs: string[];
33
+ try {
34
+ const entries = await readdir(constructionRoot, { withFileTypes: true });
35
+ unitDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
36
+ } catch {
37
+ return Object.freeze([]);
38
+ }
39
+
40
+ const models: CoverageReportGatingModel[] = [];
41
+ for (const unitDir of unitDirs) {
42
+ const filePath = join(constructionRoot, unitDir, COVERAGE_REPORT_FILE);
43
+ let content: string;
44
+ try {
45
+ content = await readFile(filePath, "utf-8");
46
+ } catch {
47
+ continue;
48
+ }
49
+ // path は project-relative で報告する(メッセージの可搬性のため)。
50
+ const relPath = `${CONSTRUCTION_REL.replace(/\\/g, "/")}/${unitDir}/${COVERAGE_REPORT_FILE}`;
51
+ models.push(this.parse(relPath, content));
52
+ }
53
+ return Object.freeze(models);
54
+ }
55
+
56
+ private parse(path: string, content: string): CoverageReportGatingModel {
57
+ const lines = content.split(/\r?\n/);
58
+ const hasLegacyMarker = lines.some((line) => LEGACY_MARKER.test(line));
59
+
60
+ const claims: CoverageClaim[] = [];
61
+ for (let i = 0; i < lines.length; i++) {
62
+ const line = lines[i];
63
+ if (!line.includes(CHECKMARK)) continue;
64
+ claims.push({
65
+ lineNumber: i + 1,
66
+ hasAttestationRef: this.hasAttestationRef(lines, i),
67
+ });
68
+ }
69
+
70
+ return { path, hasLegacyMarker, claims };
71
+ }
72
+
73
+ /**
74
+ * ✅ 行 index について attestation 参照があるか判定する。
75
+ * (1) 同一行にコメント参照、または
76
+ * (2) 直前の連続するコメント行のいずれかに `@attestation` 参照がある。
77
+ */
78
+ private hasAttestationRef(lines: readonly string[], index: number): boolean {
79
+ if (ATTESTATION_REF.test(lines[index])) return true;
80
+ for (let j = index - 1; j >= 0; j--) {
81
+ const prev = lines[j];
82
+ if (!COMMENT_LINE.test(prev)) break;
83
+ if (ATTESTATION_REF.test(prev)) return true;
84
+ }
85
+ return false;
86
+ }
87
+ }