phasegate 0.139.0 → 0.140.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.140.0] - 2026-05-09
11
+
12
+ ### Added
13
+
14
+ - **WI-024 — configurable L1 metadata tag names** — `architecture.metadataTags.unit` / `architecture.metadataTags.layer` を biome-ast-engine の L1 parser / analyzer / runner に反映し、`@module` / `@tier` などのプロジェクト語彙を単一の有効タグ名として使えるようにした。
15
+ - 既定値は従来通り `@unit` / `@layer`。
16
+ - カスタムタグ設定時は旧タグを alias として扱わず、欠落メッセージも設定タグ名を表示する。
17
+
10
18
  ## [0.139.0] - 2026-05-09
11
19
 
12
20
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.139.0",
3
+ "version": "0.140.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -60,6 +60,7 @@ export class ResolveEnabledRulesUseCase {
60
60
  const architectureSpec = freezeArchitectureSpec({
61
61
  layers: architecture.layers,
62
62
  allowedDependencies: architecture.allowedDependencies,
63
+ metadataTags: architecture.metadataTags,
63
64
  });
64
65
 
65
66
  return toResolveEnabledRulesOutput(
@@ -16,6 +16,10 @@ export interface ArchitectureProviderInfo {
16
16
  readonly preset: ArchitecturePresetIdValue;
17
17
  readonly layers: readonly string[];
18
18
  readonly allowedDependencies: Readonly<Record<string, readonly string[]>>;
19
+ readonly metadataTags?: {
20
+ readonly unit?: string;
21
+ readonly layer?: string;
22
+ };
19
23
  }
20
24
 
21
25
  export interface RuleConfigProviderPort {
@@ -75,7 +75,7 @@ export class LintRunner {
75
75
  line: 1,
76
76
  column: 1,
77
77
  ruleName: rule.name,
78
- message: '@unitコメントが必要です',
78
+ message: `${architecture.metadataTags.unit}コメントが必要です`,
79
79
  severity: rule.severity,
80
80
  })
81
81
  );
@@ -92,7 +92,7 @@ export class LintRunner {
92
92
  line: 1,
93
93
  column: 1,
94
94
  ruleName: rule.name,
95
- message: '@layerコメントが必要です',
95
+ message: `${architecture.metadataTags.layer}コメントが必要です`,
96
96
  severity: rule.severity,
97
97
  })
98
98
  );
@@ -6,8 +6,25 @@
6
6
  export type ArchitectureSpec = {
7
7
  readonly layers: readonly string[];
8
8
  readonly allowedDependencies: Readonly<Record<string, readonly string[]>>;
9
+ readonly metadataTags: ArchitectureMetadataTags;
9
10
  };
10
11
 
12
+ export type ArchitectureMetadataTags = {
13
+ readonly unit: string;
14
+ readonly layer: string;
15
+ };
16
+
17
+ export type ArchitectureSpecInput = {
18
+ readonly layers: readonly string[];
19
+ readonly allowedDependencies: Readonly<Record<string, readonly string[]>>;
20
+ readonly metadataTags?: Partial<ArchitectureMetadataTags>;
21
+ };
22
+
23
+ export const DEFAULT_METADATA_TAGS: ArchitectureMetadataTags = Object.freeze({
24
+ unit: '@unit',
25
+ layer: '@layer',
26
+ });
27
+
11
28
  const freezeDependencyMap = (
12
29
  map: Record<string, readonly string[]>
13
30
  ): Readonly<Record<string, readonly string[]>> => {
@@ -20,10 +37,14 @@ const freezeDependencyMap = (
20
37
  return Object.freeze(frozen);
21
38
  };
22
39
 
23
- export const freezeArchitectureSpec = (spec: ArchitectureSpec): ArchitectureSpec => {
40
+ export const freezeArchitectureSpec = (spec: ArchitectureSpecInput): ArchitectureSpec => {
24
41
  return Object.freeze({
25
42
  layers: Object.freeze([...spec.layers]),
26
43
  allowedDependencies: freezeDependencyMap({ ...spec.allowedDependencies }),
44
+ metadataTags: Object.freeze({
45
+ ...DEFAULT_METADATA_TAGS,
46
+ ...spec.metadataTags,
47
+ }),
27
48
  });
28
49
  };
29
50
 
@@ -29,6 +29,10 @@ const DEFAULT_ARCHITECTURE: ArchitectureConfigInput = Object.freeze({
29
29
  infrastructure: Object.freeze(['infrastructure', 'application', 'domain']),
30
30
  presentation: Object.freeze(['presentation', 'application', 'domain']),
31
31
  }),
32
+ metadataTags: Object.freeze({
33
+ unit: '@unit',
34
+ layer: '@layer',
35
+ }),
32
36
  });
33
37
 
34
38
  /**
@@ -56,8 +56,8 @@ export class TypeScriptSourceModuleAnalyzerAdapter implements SourceModuleAnalyz
56
56
  }
57
57
 
58
58
  const sourceText = sourceFile.getFullText();
59
- const unitResult = parseUnitComment(sourceText);
60
- const layerResult = parseLayerComment(sourceText);
59
+ const unitResult = parseUnitComment(sourceText, architecture?.metadataTags.unit);
60
+ const layerResult = parseLayerComment(sourceText, architecture?.metadataTags.layer);
61
61
  const densityResult = parseCommentDensity(sourceText);
62
62
  const imports = this.extractImports(sourceFile, filePath);
63
63
  const anyCount = this.countAnyTypes(sourceFile);
@@ -69,7 +69,7 @@ export class TypeScriptSourceModuleAnalyzerAdapter implements SourceModuleAnalyz
69
69
  SourceModuleSnapshot.create(
70
70
  {
71
71
  filePath,
72
- declaredUnit: unitResult.unitNames[0],
72
+ declaredUnit: unitResult.unitNames[0] ?? null,
73
73
  declaredLayer: layerResult.layerName,
74
74
  imports,
75
75
  anyTypeCount: anyCount,
@@ -1,14 +1,22 @@
1
1
  // @unit biome-ast-engine
2
2
  // @layer infrastructure
3
3
 
4
- const LAYER_COMMENT_PATTERN = /^\s*(?:\/\/|\/\*\*?\s*|\*)\s*@layer\s+(\S+)/m;
4
+ const DEFAULT_LAYER_TAG = '@layer';
5
+
6
+ const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
+
8
+ const createLayerCommentPattern = (tagName: string): RegExp =>
9
+ new RegExp(`^\\s*(?:\\/\\/|\\/\\*\\*?\\s*|\\*)\\s*${escapeRegExp(tagName)}\\s+(\\S+)`, 'm');
5
10
 
6
11
  export type LayerCommentResult = {
7
12
  readonly layerName: string | null;
8
13
  };
9
14
 
10
- export const parseLayerComment = (sourceCode: string): LayerCommentResult => {
11
- const match = sourceCode.match(LAYER_COMMENT_PATTERN);
15
+ export const parseLayerComment = (
16
+ sourceCode: string,
17
+ tagName: string = DEFAULT_LAYER_TAG
18
+ ): LayerCommentResult => {
19
+ const match = sourceCode.match(createLayerCommentPattern(tagName));
12
20
 
13
21
  if (!match) {
14
22
  return { layerName: null };
@@ -1,15 +1,23 @@
1
1
  // @unit biome-ast-engine
2
2
  // @layer infrastructure
3
3
 
4
- // カンマ区切り複数ユニット対応 + 複数行の @unit を収集、重複除去。
5
- const UNIT_COMMENT_PATTERN = /^\s*(?:\/\/|\/\*\*?\s*|\*)\s*@unit\s+(.+)/gm;
4
+ // カンマ区切り複数ユニット対応 + 複数行の metadata unit tag を収集、重複除去。
5
+ const DEFAULT_UNIT_TAG = '@unit';
6
+
7
+ const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8
+
9
+ const createUnitCommentPattern = (tagName: string): RegExp =>
10
+ new RegExp(`^\\s*(?:\\/\\/|\\/\\*\\*?\\s*|\\*)\\s*${escapeRegExp(tagName)}\\s+(.+)`, 'gm');
6
11
 
7
12
  export type UnitCommentResult = {
8
13
  readonly unitNames: readonly string[];
9
14
  };
10
15
 
11
- export const parseUnitComment = (sourceCode: string): UnitCommentResult => {
12
- const matches = sourceCode.matchAll(UNIT_COMMENT_PATTERN);
16
+ export const parseUnitComment = (
17
+ sourceCode: string,
18
+ tagName: string = DEFAULT_UNIT_TAG
19
+ ): UnitCommentResult => {
20
+ const matches = sourceCode.matchAll(createUnitCommentPattern(tagName));
13
21
  const names: string[] = [];
14
22
 
15
23
  for (const match of matches) {
@@ -490,6 +490,7 @@ function toArchitectureInput(resolvedConfig: HarnessConfigV2) {
490
490
  preset: resolvedConfig.architecture.preset,
491
491
  layers: resolvedConfig.architecture.layers,
492
492
  allowedDependencies: resolvedConfig.architecture.allowedDependencies,
493
+ metadataTags: resolvedConfig.architecture.metadataTags,
493
494
  };
494
495
  }
495
496