phasegate 0.91.0 → 0.107.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 +169 -0
- package/README.ja.md +15 -7
- package/README.md +24 -4
- package/docs/ADR/ADR-015-architecture-preset.md +183 -0
- package/docs/guide/codex-integration.md +7 -2
- package/docs/guide/installation.md +10 -2
- package/docs/guide/preset-selection.md +170 -0
- package/docs/guide/quick-vs-full-mode.md +3 -3
- package/docs/guide/retrofit-adoption.md +19 -2
- package/docs/guide/skills-overview.md +1 -1
- package/package.json +7 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -100
- package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +3 -3
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +26 -30
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +6 -2
- package/scripts/harness/biome-ast-engine/application/dto/analyze-import-graph-input.ts +3 -0
- package/scripts/harness/biome-ast-engine/application/dto/resolve-enabled-rules-output.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/mappers/resolve-enabled-rules-output-mapper.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/analyze-import-graph-usecase.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/execute-lint-usecase.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/usecases/resolve-enabled-rules-usecase.ts +31 -3
- package/scripts/harness/biome-ast-engine/composition-root.ts +10 -2
- package/scripts/harness/biome-ast-engine/domain/ports/rule-config-provider-port.ts +16 -0
- package/scripts/harness/biome-ast-engine/domain/ports/source-module-analyzer-port.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/value-objects/architecture-spec.ts +38 -0
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-boundary.ts +7 -8
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-name.ts +15 -23
- package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +11 -4
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/harness-config-provider-adapter.ts +29 -3
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +22 -15
- package/scripts/harness/biome-ast-engine/infrastructure/mappers/source-module-snapshot-mapper.ts +26 -17
- package/scripts/harness/config-foundation/application/dto/resolved-config-output.ts +1 -0
- package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +34 -2
- package/scripts/harness/config-foundation/application/usecases/migrate-schema-use-case.ts +89 -0
- package/scripts/harness/config-foundation/composition-root.ts +8 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +6 -0
- package/scripts/harness/config-foundation/domain/services/architecture-resolution-service.ts +257 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-config.ts +66 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-preset-catalog.ts +75 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +546 -0
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +18 -6
- package/scripts/harness/config-foundation/presentation/cli/migrate-schema-command-handler.ts +83 -0
- package/scripts/harness/integrations/pre-commit.ts +211 -52
- package/scripts/harness/main.ts +460 -340
- package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +2 -4
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +54 -17
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +154 -36
- package/scripts/harness/setup/skill-deployer.ts +140 -102
- package/scripts/harness/skill-quality/domain/errors/skill-quality-error.ts +24 -23
- package/scripts/harness/skill-quality/domain/value-objects/commit-message.ts +23 -7
- package/scripts/harness/traceability-model/application/usecases/apply-work-item-migration-usecase.ts +52 -0
- package/scripts/harness/traceability-model/application/usecases/plan-work-item-migration-usecase.ts +29 -0
- package/scripts/harness/traceability-model/application/usecases/validate-design-story-annotations-usecase.ts +83 -18
- package/scripts/harness/traceability-model/composition-root.ts +48 -30
- package/scripts/harness/traceability-model/domain/ports/design-document-port.ts +9 -15
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-apply-port.ts +11 -0
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-source-port.ts +9 -0
- package/scripts/harness/traceability-model/domain/services/work-item-migration-planner.ts +162 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +57 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-migration-candidate.ts +47 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-apply-gateway.ts +110 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-source-gateway.ts +182 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/markdown-design-document-gateway.ts +29 -43
- package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +136 -0
- package/scripts/harness/traceability-model/presentation/cli/migrate-work-items-command-handler.ts +186 -0
- package/skills/quick-implementor/SKILL.md +17 -1
- package/templates/.husky/commit-msg +1 -0
|
@@ -45,6 +45,7 @@ export class ExecuteLintUseCase {
|
|
|
45
45
|
const resolvedRules = await this.resolveEnabledRulesUseCase.execute();
|
|
46
46
|
const analyzed = await this.analyzeImportGraphUseCase.execute({
|
|
47
47
|
targets: input.targets,
|
|
48
|
+
architecture: resolvedRules.architectureSpec,
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
if (input.includeBiomeNative !== false) {
|
|
@@ -58,6 +59,7 @@ export class ExecuteLintUseCase {
|
|
|
58
59
|
snapshots: analyzed.snapshots,
|
|
59
60
|
importGraph: analyzed.importGraph,
|
|
60
61
|
durationMs,
|
|
62
|
+
architecture: resolvedRules.architectureSpec,
|
|
61
63
|
});
|
|
62
64
|
const report = LintReport.create({
|
|
63
65
|
violations: baseReport.violations,
|
package/scripts/harness/biome-ast-engine/application/usecases/resolve-enabled-rules-usecase.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { RuleConfigProviderPort } from '../../domain/ports/rule-config-provider-port.js';
|
|
7
7
|
import type { RuleDefinitionRegistry } from '../../domain/services/rule-definition-registry.js';
|
|
8
|
+
import { freezeArchitectureSpec } from '../../domain/value-objects/architecture-spec.js';
|
|
8
9
|
import type { ResolveEnabledRulesInput } from '../dto/resolve-enabled-rules-input.js';
|
|
9
10
|
import type { ResolveEnabledRulesOutput } from '../dto/resolve-enabled-rules-output.js';
|
|
10
11
|
import { toResolveEnabledRulesOutput } from '../mappers/resolve-enabled-rules-output-mapper.js';
|
|
@@ -16,6 +17,13 @@ export interface ResolveEnabledRulesUseCaseDeps {
|
|
|
16
17
|
readonly ruleDefinitionRegistry: RuleDefinitionResolver;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
const FLAT_AUTO_DISABLED_RULES = Object.freeze([
|
|
21
|
+
'require-unit-comment',
|
|
22
|
+
'require-layer-comment',
|
|
23
|
+
'no-layer-violation',
|
|
24
|
+
'enforce-folder-structure',
|
|
25
|
+
] as const);
|
|
26
|
+
|
|
19
27
|
export class ResolveEnabledRulesUseCase {
|
|
20
28
|
private readonly ruleConfigProviderPort: RuleConfigProviderPort;
|
|
21
29
|
private readonly ruleDefinitionRegistry: RuleDefinitionResolver;
|
|
@@ -29,15 +37,35 @@ export class ResolveEnabledRulesUseCase {
|
|
|
29
37
|
input: ResolveEnabledRulesInput = {}
|
|
30
38
|
): Promise<Readonly<ResolveEnabledRulesOutput>> {
|
|
31
39
|
const config = await this.ruleConfigProviderPort.getL1Config();
|
|
32
|
-
const
|
|
40
|
+
const architecture = await this.ruleConfigProviderPort.getArchitecture();
|
|
41
|
+
const overrideRules = input.overrideRules ?? {};
|
|
42
|
+
const mergedRules: Record<string, 'error' | 'warning' | 'off'> = {
|
|
33
43
|
...config.rules,
|
|
34
|
-
...
|
|
44
|
+
...overrideRules,
|
|
35
45
|
};
|
|
46
|
+
|
|
47
|
+
if (architecture.preset === 'flat') {
|
|
48
|
+
for (const ruleName of FLAT_AUTO_DISABLED_RULES) {
|
|
49
|
+
if (!(ruleName in mergedRules)) {
|
|
50
|
+
mergedRules[ruleName] = 'off';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
const resolved = this.ruleDefinitionRegistry.resolveEnabled({
|
|
37
56
|
l1Enabled: config.enabled,
|
|
38
57
|
rules: mergedRules,
|
|
39
58
|
});
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
const architectureSpec = freezeArchitectureSpec({
|
|
61
|
+
layers: architecture.layers,
|
|
62
|
+
allowedDependencies: architecture.allowedDependencies,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return toResolveEnabledRulesOutput(
|
|
66
|
+
resolved.enabledRules,
|
|
67
|
+
resolved.skippedRules,
|
|
68
|
+
architectureSpec
|
|
69
|
+
);
|
|
42
70
|
}
|
|
43
71
|
}
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
// @layer composition
|
|
3
3
|
|
|
4
4
|
import { SystemClockAdapter } from './infrastructure/adapters/system-clock-adapter.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
HarnessConfigProviderAdapter,
|
|
7
|
+
type ArchitectureConfigInput,
|
|
8
|
+
type L1ConfigInput,
|
|
9
|
+
} from './infrastructure/adapters/harness-config-provider-adapter.js';
|
|
6
10
|
import { HarnessErrorFormatterAdapter } from './infrastructure/adapters/harness-error-formatter-adapter.js';
|
|
7
11
|
import { NodeWorkspaceFileAdapter } from './infrastructure/adapters/node-workspace-file-adapter.js';
|
|
8
12
|
import { TypeScriptSourceModuleAnalyzerAdapter } from './infrastructure/adapters/typescript-source-module-analyzer-adapter.js';
|
|
@@ -20,6 +24,7 @@ import { HarnessLintCommandHandler } from './presentation/cli/harness-lint-comma
|
|
|
20
24
|
|
|
21
25
|
export interface BiomeAstEngineModuleOptions {
|
|
22
26
|
readonly l1Config?: L1ConfigInput;
|
|
27
|
+
readonly architecture?: ArchitectureConfigInput;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export function createBiomeAstEngineModule(
|
|
@@ -27,7 +32,10 @@ export function createBiomeAstEngineModule(
|
|
|
27
32
|
options?: BiomeAstEngineModuleOptions,
|
|
28
33
|
) {
|
|
29
34
|
const clockPort = new SystemClockAdapter();
|
|
30
|
-
const ruleConfigProviderPort = new HarnessConfigProviderAdapter(
|
|
35
|
+
const ruleConfigProviderPort = new HarnessConfigProviderAdapter(
|
|
36
|
+
options?.l1Config,
|
|
37
|
+
options?.architecture,
|
|
38
|
+
);
|
|
31
39
|
const violationFormatterPort = new HarnessErrorFormatterAdapter();
|
|
32
40
|
const workspaceFilePort = new NodeWorkspaceFileAdapter({ rootDir });
|
|
33
41
|
const sourceModuleAnalyzerPort = new TypeScriptSourceModuleAnalyzerAdapter({ rootDir });
|
|
@@ -3,9 +3,25 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
export type ArchitecturePresetIdValue =
|
|
7
|
+
| 'clean'
|
|
8
|
+
| 'strict-ddd'
|
|
9
|
+
| 'onion'
|
|
10
|
+
| 'hexagonal'
|
|
11
|
+
| 'layered'
|
|
12
|
+
| 'flat'
|
|
13
|
+
| 'custom';
|
|
14
|
+
|
|
15
|
+
export interface ArchitectureProviderInfo {
|
|
16
|
+
readonly preset: ArchitecturePresetIdValue;
|
|
17
|
+
readonly layers: readonly string[];
|
|
18
|
+
readonly allowedDependencies: Readonly<Record<string, readonly string[]>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
export interface RuleConfigProviderPort {
|
|
7
22
|
getL1Config(): Promise<{
|
|
8
23
|
enabled: boolean;
|
|
9
24
|
rules: Record<string, 'error' | 'warning' | 'off'>;
|
|
10
25
|
}>;
|
|
26
|
+
getArchitecture(): Promise<ArchitectureProviderInfo>;
|
|
11
27
|
}
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { ArchitectureSpec } from '../value-objects/architecture-spec.js';
|
|
6
7
|
import { FilePath } from '../value-objects/file-path.js';
|
|
7
8
|
import { SourceModuleSnapshot } from '../value-objects/source-module-snapshot.js';
|
|
8
9
|
|
|
9
10
|
export interface SourceModuleAnalyzerPort {
|
|
10
|
-
analyzeMany(
|
|
11
|
+
analyzeMany(
|
|
12
|
+
files: readonly FilePath[],
|
|
13
|
+
architecture?: ArchitectureSpec
|
|
14
|
+
): Promise<readonly SourceModuleSnapshot[]>;
|
|
11
15
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { RuleDefinitionRegistry } from './rule-definition-registry.js';
|
|
7
|
+
import { CLEAN_PRESET_SPEC, type ArchitectureSpec } from '../value-objects/architecture-spec.js';
|
|
7
8
|
import { ImportGraph, matchesPattern } from '../value-objects/import-graph.js';
|
|
8
9
|
import { LayerBoundary } from '../value-objects/layer-boundary.js';
|
|
9
10
|
import { LintReport } from '../value-objects/lint-report.js';
|
|
@@ -16,6 +17,7 @@ type LintRunnerParams = {
|
|
|
16
17
|
readonly snapshots: readonly SourceModuleSnapshot[];
|
|
17
18
|
readonly importGraph: ImportGraph;
|
|
18
19
|
readonly durationMs: number;
|
|
20
|
+
readonly architecture?: ArchitectureSpec;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const toNumber = (value: unknown, fallback: number): number =>
|
|
@@ -37,6 +39,8 @@ export class LintRunner {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
run(params: LintRunnerParams): LintReport {
|
|
42
|
+
const architecture = params.architecture ?? CLEAN_PRESET_SPEC;
|
|
43
|
+
|
|
40
44
|
for (const rule of params.rules) {
|
|
41
45
|
this.ruleDefinitionRegistry.getByName(rule.name);
|
|
42
46
|
}
|
|
@@ -100,7 +104,7 @@ export class LintRunner {
|
|
|
100
104
|
const ignorePatterns = toStringArray(rule.config.ignorePatterns, Object.freeze([]));
|
|
101
105
|
|
|
102
106
|
for (const edge of params.importGraph.findLayerViolations(
|
|
103
|
-
LayerBoundary.standardMatrix(),
|
|
107
|
+
LayerBoundary.standardMatrix(architecture),
|
|
104
108
|
layerByFile,
|
|
105
109
|
ignorePatterns
|
|
106
110
|
)) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit biome-ast-engine
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ArchitectureSpec = {
|
|
7
|
+
readonly layers: readonly string[];
|
|
8
|
+
readonly allowedDependencies: Readonly<Record<string, readonly string[]>>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const freezeDependencyMap = (
|
|
12
|
+
map: Record<string, readonly string[]>
|
|
13
|
+
): Readonly<Record<string, readonly string[]>> => {
|
|
14
|
+
const frozen: Record<string, readonly string[]> = {};
|
|
15
|
+
|
|
16
|
+
for (const [key, value] of Object.entries(map)) {
|
|
17
|
+
frozen[key] = Object.freeze([...value]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Object.freeze(frozen);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const freezeArchitectureSpec = (spec: ArchitectureSpec): ArchitectureSpec => {
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
layers: Object.freeze([...spec.layers]),
|
|
26
|
+
allowedDependencies: freezeDependencyMap({ ...spec.allowedDependencies }),
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const CLEAN_PRESET_SPEC: ArchitectureSpec = freezeArchitectureSpec({
|
|
31
|
+
layers: ['domain', 'application', 'infrastructure', 'presentation'],
|
|
32
|
+
allowedDependencies: {
|
|
33
|
+
domain: ['domain'],
|
|
34
|
+
application: ['application', 'domain'],
|
|
35
|
+
infrastructure: ['infrastructure', 'application', 'domain'],
|
|
36
|
+
presentation: ['presentation', 'application', 'domain'],
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { CLEAN_PRESET_SPEC, type ArchitectureSpec } from './architecture-spec.js';
|
|
6
7
|
import { LayerName } from './layer-name.js';
|
|
7
8
|
|
|
8
9
|
type LayerBoundaryProps = {
|
|
@@ -11,8 +12,6 @@ type LayerBoundaryProps = {
|
|
|
11
12
|
readonly allowed: boolean;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const LAYER_NAMES = Object.freeze(['domain', 'application', 'infrastructure', 'presentation'] as const);
|
|
15
|
-
|
|
16
15
|
export class LayerBoundary {
|
|
17
16
|
readonly sourceLayer: LayerName;
|
|
18
17
|
readonly targetLayer: LayerName;
|
|
@@ -28,16 +27,16 @@ export class LayerBoundary {
|
|
|
28
27
|
return Object.freeze(new LayerBoundary(props));
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
static standardMatrix(): readonly LayerBoundary[] {
|
|
30
|
+
static standardMatrix(spec: ArchitectureSpec = CLEAN_PRESET_SPEC): readonly LayerBoundary[] {
|
|
32
31
|
return Object.freeze(
|
|
33
|
-
|
|
34
|
-
const sourceLayer = LayerName.fromString(source);
|
|
32
|
+
spec.layers.flatMap((source) => {
|
|
33
|
+
const sourceLayer = LayerName.fromString(source, spec);
|
|
35
34
|
|
|
36
|
-
return
|
|
35
|
+
return spec.layers.map((target) =>
|
|
37
36
|
LayerBoundary.create({
|
|
38
37
|
sourceLayer,
|
|
39
|
-
targetLayer: LayerName.fromString(target),
|
|
40
|
-
allowed: sourceLayer.canDependOn(LayerName.fromString(target)),
|
|
38
|
+
targetLayer: LayerName.fromString(target, spec),
|
|
39
|
+
allowed: sourceLayer.canDependOn(LayerName.fromString(target, spec)),
|
|
41
40
|
})
|
|
42
41
|
);
|
|
43
42
|
})
|
|
@@ -3,50 +3,40 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { type ArchitectureSpec, CLEAN_PRESET_SPEC } from "./architecture-spec.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
'domain',
|
|
10
|
-
'application',
|
|
11
|
-
'infrastructure',
|
|
12
|
-
'presentation',
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
const ALLOWED_DEPENDENCIES: Readonly<Record<LayerNameValue, readonly LayerNameValue[]>> = {
|
|
16
|
-
domain: Object.freeze(['domain']),
|
|
17
|
-
application: Object.freeze(['application', 'domain']),
|
|
18
|
-
infrastructure: Object.freeze(['infrastructure', 'application', 'domain']),
|
|
19
|
-
presentation: Object.freeze(['presentation', 'application', 'domain']),
|
|
20
|
-
};
|
|
8
|
+
export type LayerNameValue = string;
|
|
21
9
|
|
|
22
10
|
export class InvalidLayerNameError extends Error {
|
|
23
11
|
constructor(value: string) {
|
|
24
12
|
super(`Invalid LayerName: ${value}`);
|
|
25
|
-
this.name =
|
|
13
|
+
this.name = "InvalidLayerNameError";
|
|
26
14
|
}
|
|
27
15
|
}
|
|
28
16
|
|
|
29
17
|
export class LayerName {
|
|
30
18
|
readonly value: LayerNameValue;
|
|
19
|
+
private readonly spec: ArchitectureSpec;
|
|
31
20
|
|
|
32
|
-
private constructor(value: LayerNameValue) {
|
|
21
|
+
private constructor(value: LayerNameValue, spec: ArchitectureSpec) {
|
|
33
22
|
this.value = value;
|
|
23
|
+
this.spec = spec;
|
|
34
24
|
}
|
|
35
25
|
|
|
36
|
-
static fromString(value: string): LayerName {
|
|
37
|
-
if (!
|
|
26
|
+
static fromString(value: string, spec: ArchitectureSpec = CLEAN_PRESET_SPEC): LayerName {
|
|
27
|
+
if (!spec.layers.includes(value)) {
|
|
38
28
|
throw new InvalidLayerNameError(value);
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
return Object.freeze(new LayerName(value as
|
|
31
|
+
return Object.freeze(new LayerName(value, spec)) as LayerName;
|
|
42
32
|
}
|
|
43
33
|
|
|
44
|
-
static tryFromString(value: string): LayerName | null {
|
|
45
|
-
if (!
|
|
34
|
+
static tryFromString(value: string, spec: ArchitectureSpec = CLEAN_PRESET_SPEC): LayerName | null {
|
|
35
|
+
if (!spec.layers.includes(value)) {
|
|
46
36
|
return null;
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
return Object.freeze(new LayerName(value as
|
|
39
|
+
return Object.freeze(new LayerName(value, spec)) as LayerName;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
42
|
equals(other: LayerName): boolean {
|
|
@@ -54,7 +44,9 @@ export class LayerName {
|
|
|
54
44
|
}
|
|
55
45
|
|
|
56
46
|
canDependOn(target: LayerName): boolean {
|
|
57
|
-
|
|
47
|
+
const allowed = this.spec.allowedDependencies[this.value] ?? [];
|
|
48
|
+
|
|
49
|
+
return allowed.includes(target.value);
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
toPathSegment(): string {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { CLEAN_PRESET_SPEC, type ArchitectureSpec } from './architecture-spec.js';
|
|
6
7
|
import { FilePath } from './file-path.js';
|
|
7
8
|
import { ImportEdge } from './import-edge.js';
|
|
8
9
|
import { LayerName } from './layer-name.js';
|
|
@@ -30,7 +31,10 @@ const ensureNonNegative = (value: number, label: string): number => {
|
|
|
30
31
|
return value;
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
const normalizeDeclaredLayer = (
|
|
34
|
+
const normalizeDeclaredLayer = (
|
|
35
|
+
value: LayerName | string | null,
|
|
36
|
+
spec: ArchitectureSpec
|
|
37
|
+
): LayerName | null => {
|
|
34
38
|
if (value === null) {
|
|
35
39
|
return null;
|
|
36
40
|
}
|
|
@@ -40,7 +44,7 @@ const normalizeDeclaredLayer = (value: LayerName | string | null): LayerName | n
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
if (typeof value === 'string') {
|
|
43
|
-
return LayerName.tryFromString(value);
|
|
47
|
+
return LayerName.tryFromString(value, spec);
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
return null;
|
|
@@ -77,8 +81,11 @@ export class SourceModuleSnapshot {
|
|
|
77
81
|
this.hasRawLayerAnnotation = props.hasRawLayerAnnotation;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
static create(
|
|
81
|
-
|
|
84
|
+
static create(
|
|
85
|
+
props: SourceModuleSnapshotProps,
|
|
86
|
+
spec: ArchitectureSpec = CLEAN_PRESET_SPEC
|
|
87
|
+
): SourceModuleSnapshot {
|
|
88
|
+
const declaredLayer = normalizeDeclaredLayer(props.declaredLayer, spec);
|
|
82
89
|
|
|
83
90
|
return Object.freeze(
|
|
84
91
|
new SourceModuleSnapshot({
|
package/scripts/harness/biome-ast-engine/infrastructure/adapters/harness-config-provider-adapter.ts
CHANGED
|
@@ -3,27 +3,49 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ArchitectureProviderInfo,
|
|
8
|
+
RuleConfigProviderPort,
|
|
9
|
+
} from '../../domain/ports/rule-config-provider-port.js';
|
|
7
10
|
|
|
8
11
|
export interface L1ConfigInput {
|
|
9
12
|
readonly enabled: boolean;
|
|
10
13
|
readonly rules: Record<string, 'error' | 'warning' | 'off'>;
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
export type ArchitectureConfigInput = ArchitectureProviderInfo;
|
|
17
|
+
|
|
13
18
|
const DEFAULT_L1_CONFIG: L1ConfigInput = {
|
|
14
19
|
enabled: true,
|
|
15
20
|
rules: {},
|
|
16
21
|
};
|
|
17
22
|
|
|
23
|
+
const DEFAULT_ARCHITECTURE: ArchitectureConfigInput = Object.freeze({
|
|
24
|
+
preset: 'clean',
|
|
25
|
+
layers: Object.freeze(['domain', 'application', 'infrastructure', 'presentation']),
|
|
26
|
+
allowedDependencies: Object.freeze({
|
|
27
|
+
domain: Object.freeze(['domain']),
|
|
28
|
+
application: Object.freeze(['application', 'domain']),
|
|
29
|
+
infrastructure: Object.freeze(['infrastructure', 'application', 'domain']),
|
|
30
|
+
presentation: Object.freeze(['presentation', 'application', 'domain']),
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
|
|
18
34
|
/**
|
|
19
35
|
* RuleConfigProviderPort の実装。
|
|
20
|
-
* 外部から L1
|
|
36
|
+
* 外部から L1 設定と architecture 情報を注入可能。未指定時はデフォルト
|
|
37
|
+
* (L1: enabled=true, rules={}、architecture: clean preset)。
|
|
21
38
|
*/
|
|
22
39
|
export class HarnessConfigProviderAdapter implements RuleConfigProviderPort {
|
|
23
40
|
private readonly l1Config: L1ConfigInput;
|
|
41
|
+
private readonly architecture: ArchitectureConfigInput;
|
|
24
42
|
|
|
25
|
-
constructor(
|
|
43
|
+
constructor(
|
|
44
|
+
injectedL1Config?: L1ConfigInput,
|
|
45
|
+
injectedArchitecture?: ArchitectureConfigInput,
|
|
46
|
+
) {
|
|
26
47
|
this.l1Config = injectedL1Config ?? DEFAULT_L1_CONFIG;
|
|
48
|
+
this.architecture = injectedArchitecture ?? DEFAULT_ARCHITECTURE;
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
async getL1Config(): Promise<{
|
|
@@ -35,4 +57,8 @@ export class HarnessConfigProviderAdapter implements RuleConfigProviderPort {
|
|
|
35
57
|
rules: { ...this.l1Config.rules },
|
|
36
58
|
};
|
|
37
59
|
}
|
|
60
|
+
|
|
61
|
+
async getArchitecture(): Promise<ArchitectureProviderInfo> {
|
|
62
|
+
return this.architecture;
|
|
63
|
+
}
|
|
38
64
|
}
|
|
@@ -7,6 +7,7 @@ import * as ts from 'typescript';
|
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import type { FilePath } from '../../domain/value-objects/file-path.js';
|
|
9
9
|
import type { SourceModuleAnalyzerPort } from '../../domain/ports/source-module-analyzer-port.js';
|
|
10
|
+
import type { ArchitectureSpec } from '../../domain/value-objects/architecture-spec.js';
|
|
10
11
|
import { SourceModuleSnapshot } from '../../domain/value-objects/source-module-snapshot.js';
|
|
11
12
|
import { ImportEdge } from '../../domain/value-objects/import-edge.js';
|
|
12
13
|
import { FilePath as FilePathVO } from '../../domain/value-objects/file-path.js';
|
|
@@ -30,7 +31,10 @@ export class TypeScriptSourceModuleAnalyzerAdapter implements SourceModuleAnalyz
|
|
|
30
31
|
this.rootDir = deps.rootDir;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
async analyzeMany(
|
|
34
|
+
async analyzeMany(
|
|
35
|
+
files: readonly FilePath[],
|
|
36
|
+
architecture?: ArchitectureSpec
|
|
37
|
+
): Promise<readonly SourceModuleSnapshot[]> {
|
|
34
38
|
const absolutePaths = files.map((f) => path.resolve(this.rootDir, f.toString()));
|
|
35
39
|
|
|
36
40
|
const program = ts.createProgram(absolutePaths, {
|
|
@@ -62,20 +66,23 @@ export class TypeScriptSourceModuleAnalyzerAdapter implements SourceModuleAnalyz
|
|
|
62
66
|
const isEntrypoint = this.isEntrypointCandidate(filePath);
|
|
63
67
|
|
|
64
68
|
snapshots.push(
|
|
65
|
-
SourceModuleSnapshot.create(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
SourceModuleSnapshot.create(
|
|
70
|
+
{
|
|
71
|
+
filePath,
|
|
72
|
+
declaredUnit: unitResult.unitNames[0],
|
|
73
|
+
declaredLayer: layerResult.layerName,
|
|
74
|
+
imports,
|
|
75
|
+
anyTypeCount: anyCount,
|
|
76
|
+
typedNodeCount: typedCount,
|
|
77
|
+
commentLineCount: densityResult.commentLineCount,
|
|
78
|
+
logicalLineCount: densityResult.logicalLineCount,
|
|
79
|
+
repeatedCommentBlocks: densityResult.repeatedCommentBlocks,
|
|
80
|
+
duplicationFingerprints: [],
|
|
81
|
+
exportedSymbols: exports,
|
|
82
|
+
isEntrypointCandidate: isEntrypoint,
|
|
83
|
+
},
|
|
84
|
+
architecture
|
|
85
|
+
)
|
|
79
86
|
);
|
|
80
87
|
}
|
|
81
88
|
|
package/scripts/harness/biome-ast-engine/infrastructure/mappers/source-module-snapshot-mapper.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @unit biome-ast-engine
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { ArchitectureSpec } from '../../domain/value-objects/architecture-spec.js';
|
|
6
7
|
import { FilePath } from '../../domain/value-objects/file-path.js';
|
|
7
8
|
import { ImportEdge } from '../../domain/value-objects/import-edge.js';
|
|
8
9
|
import { SourceModuleSnapshot } from '../../domain/value-objects/source-module-snapshot.js';
|
|
@@ -31,7 +32,10 @@ export type RawModuleData = {
|
|
|
31
32
|
/**
|
|
32
33
|
* AST解析結果の生データを SourceModuleSnapshot 値オブジェクトへ変換する。
|
|
33
34
|
*/
|
|
34
|
-
export const toSourceModuleSnapshot = (
|
|
35
|
+
export const toSourceModuleSnapshot = (
|
|
36
|
+
raw: RawModuleData,
|
|
37
|
+
architecture?: ArchitectureSpec
|
|
38
|
+
): SourceModuleSnapshot => {
|
|
35
39
|
const filePath = FilePath.fromWorkspaceRelative(raw.filePath);
|
|
36
40
|
|
|
37
41
|
const imports = raw.imports.map((edge) =>
|
|
@@ -42,25 +46,30 @@ export const toSourceModuleSnapshot = (raw: RawModuleData): SourceModuleSnapshot
|
|
|
42
46
|
})
|
|
43
47
|
);
|
|
44
48
|
|
|
45
|
-
return SourceModuleSnapshot.create(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
return SourceModuleSnapshot.create(
|
|
50
|
+
{
|
|
51
|
+
filePath,
|
|
52
|
+
declaredUnit: raw.declaredUnit,
|
|
53
|
+
declaredLayer: raw.declaredLayer,
|
|
54
|
+
imports,
|
|
55
|
+
anyTypeCount: raw.anyTypeCount,
|
|
56
|
+
typedNodeCount: raw.typedNodeCount,
|
|
57
|
+
commentLineCount: raw.commentLineCount,
|
|
58
|
+
logicalLineCount: raw.logicalLineCount,
|
|
59
|
+
repeatedCommentBlocks: raw.repeatedCommentBlocks,
|
|
60
|
+
duplicationFingerprints: raw.duplicationFingerprints,
|
|
61
|
+
exportedSymbols: raw.exportedSymbols,
|
|
62
|
+
isEntrypointCandidate: raw.isEntrypointCandidate,
|
|
63
|
+
},
|
|
64
|
+
architecture
|
|
65
|
+
);
|
|
59
66
|
};
|
|
60
67
|
|
|
61
68
|
/**
|
|
62
69
|
* 複数の生データを一括変換する。
|
|
63
70
|
*/
|
|
64
71
|
export const toSourceModuleSnapshots = (
|
|
65
|
-
rawList: readonly RawModuleData[]
|
|
66
|
-
|
|
72
|
+
rawList: readonly RawModuleData[],
|
|
73
|
+
architecture?: ArchitectureSpec
|
|
74
|
+
): readonly SourceModuleSnapshot[] =>
|
|
75
|
+
rawList.map((raw) => toSourceModuleSnapshot(raw, architecture));
|