phasegate 0.264.0 → 0.315.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 +54 -0
- package/docs/ADR/017-warning-severity-aggregation.md +17 -0
- package/docs/ADR/038-config-state-operation-permission-policy.md +78 -0
- package/docs/guide/installation.md +1 -1
- package/docs/guide/layer-model.md +2 -0
- package/docs/guide/quick-vs-full-mode.md +28 -3
- package/docs/guide/troubleshooting.md +37 -0
- package/docs/templates/agent-context/CLAUDE.md.template.md +6 -6
- package/docs/templates/ci/aidlc-gate.yml +22 -4
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +98 -18
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +25 -3
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-full-mode-session-query-adapter.ts +75 -23
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +45 -27
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +29 -16
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +51 -6
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +35 -26
- package/scripts/harness/ci-governance/composition-root.ts +2 -2
- package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +20 -11
- package/scripts/harness/ci-governance/presentation/handlers/check-repetition-handler.ts +10 -2
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +5 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +10 -7
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +4 -1
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +34 -18
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +1 -1
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +1 -1
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +1 -1
- package/scripts/harness/config-foundation/infrastructure/repositories/file-system-config-repository.ts +27 -18
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +1 -8
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +1 -8
- package/scripts/harness/harness-api/domain/ports/config-query-port.ts +11 -1
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +135 -87
- package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +31 -21
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +2 -22
- package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +23 -8
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +4 -4
- package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +79 -34
- package/scripts/harness/harness-error/application/dto/create-harness-error-input.ts +3 -1
- package/scripts/harness/harness-error/application/dto/harness-error-contract.ts +3 -1
- package/scripts/harness/harness-error/application/mappers/harness-error-contract-mapper.ts +9 -17
- package/scripts/harness/harness-error/application/usecases/create-harness-error-use-case.ts +7 -7
- package/scripts/harness/harness-error/domain/services/harness-error-factory.ts +30 -33
- package/scripts/harness/harness-error/domain/value-objects/error-definition.ts +28 -17
- package/scripts/harness/harness-error/domain/value-objects/harness-error.ts +30 -9
- package/scripts/harness/harness-error/domain/value-objects/remediation-type.ts +30 -0
- package/scripts/harness/harness-error/infrastructure/registry/l2-error-definitions.ts +44 -29
- package/scripts/harness/harness-error/infrastructure/registry/l3-error-definitions.ts +44 -27
- package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +47 -32
- package/scripts/harness/installation/application/checks/claude-context-missing-check.ts +13 -7
- package/scripts/harness/installation/application/checks/config-status-check.ts +52 -0
- package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +6 -0
- package/scripts/harness/installation/application/ports/config-status-probe-port.ts +9 -0
- package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +30 -8
- package/scripts/harness/installation/application/usecases/run-install.ts +234 -53
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +311 -70
- package/scripts/harness/installation/composition-root.ts +13 -3
- package/scripts/harness/installation/domain/check-id.ts +2 -0
- package/scripts/harness/installation/domain/config-status.ts +17 -0
- package/scripts/harness/installation/domain/deployment-manifest.ts +43 -0
- package/scripts/harness/installation/domain/ports/heuristic-check.ts +10 -1
- package/scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts +79 -0
- package/scripts/harness/installation/presentation/cli/doctor-handler.ts +6 -1
- package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +19 -5
- package/scripts/harness/integrations/pre-commit.ts +17 -3
- package/scripts/harness/main.ts +83 -15
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +60 -32
- package/scripts/harness/phase2-extensions/presentation/handlers/check-freshness-handler.ts +17 -9
- package/scripts/harness/quick-mode/application/ports/file-existence-port.ts +15 -0
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +60 -22
- package/scripts/harness/quick-mode/composition-root.ts +25 -15
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +72 -3
- package/scripts/harness/quick-mode/infrastructure/adapters/fs-file-existence-adapter.ts +38 -0
- package/scripts/harness/skill-quality/infrastructure/adapters/file-system-requirement-test-matrix-adapter.ts +51 -8
- package/scripts/harness/skill-quality/presentation/handlers/check-coverage-handler.ts +13 -6
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +5 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-identity-gateway.ts +6 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-status-gateway.ts +15 -2
- package/scripts/harness/validator-system/application/use-cases/aggregate-validation-results-usecase.ts +11 -14
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +32 -7
- package/scripts/harness/validator-system/composition-root.ts +4 -1
- package/scripts/harness/validator-system/domain/ports/ac-coverage-policy-port.ts +10 -1
- package/scripts/harness/validator-system/domain/services/effective-severity-policy.ts +39 -0
- package/scripts/harness/validator-system/domain/value-objects/consistency-report.ts +6 -4
- package/scripts/harness/validator-system/domain/value-objects/drift-report.ts +12 -7
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +11 -3
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +20 -17
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +89 -3
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +49 -20
- package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +35 -14
- package/scripts/harness/validator-system/infrastructure/adapters/traceability-metadata-policy-adapter.ts +13 -8
- package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +19 -10
- package/skills/quick-implementor/SKILL.md +19 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @unit harness-api
|
|
3
3
|
// harness-status-summary.ts — HarnessStatusSummary Value Object
|
|
4
4
|
|
|
5
|
-
import type { LayerHealth, LayerId } from
|
|
5
|
+
import type { LayerHealth, LayerId } from "./layer-health.js";
|
|
6
6
|
|
|
7
7
|
export interface PhaseGateSummary {
|
|
8
8
|
totalStories: number;
|
|
@@ -11,7 +11,7 @@ export interface PhaseGateSummary {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface PresetInfo {
|
|
14
|
-
name:
|
|
14
|
+
name: "minimal" | "standard" | "strict";
|
|
15
15
|
enabledLayers: LayerId[];
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -21,9 +21,21 @@ export interface ConfigSummary {
|
|
|
21
21
|
version: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* WI-328 (github#39 残課題): 実効言語リストとその出所。
|
|
26
|
+
* source:
|
|
27
|
+
* - 'declared' — config の project.languages 宣言
|
|
28
|
+
* - 'detected' — ファイルシステムマーカーからの自動検出(WI-319)
|
|
29
|
+
* - 'fallback' — 検出ゼロで typescript フォールバック
|
|
30
|
+
*/
|
|
31
|
+
export interface LanguageInfo {
|
|
32
|
+
effective: readonly string[];
|
|
33
|
+
source: "declared" | "detected" | "fallback";
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
export interface HookSkipState {
|
|
25
37
|
hookType: string;
|
|
26
|
-
reason:
|
|
38
|
+
reason: "HOOK_DISABLED" | "TIMEOUT_EXCEEDED" | "REENTRY_DETECTED" | string;
|
|
27
39
|
targetPaths: readonly string[];
|
|
28
40
|
observedAt: string;
|
|
29
41
|
}
|
|
@@ -35,7 +47,7 @@ export interface HookHealth {
|
|
|
35
47
|
skipCountsByReason: Readonly<Record<string, number>>;
|
|
36
48
|
applyPatchBypass: {
|
|
37
49
|
nativeApplyPatchIntercepted: boolean;
|
|
38
|
-
backstop:
|
|
50
|
+
backstop: "pre-commit";
|
|
39
51
|
documentationUrl: string;
|
|
40
52
|
};
|
|
41
53
|
}
|
|
@@ -60,18 +72,20 @@ export interface HarnessStatusSummaryProps {
|
|
|
60
72
|
phaseGateSummary: PhaseGateSummary;
|
|
61
73
|
presetInfo: PresetInfo;
|
|
62
74
|
configSummary: ConfigSummary;
|
|
75
|
+
languages?: LanguageInfo;
|
|
63
76
|
hookHealth?: HookHealth;
|
|
64
77
|
baselineHealth?: BaselineHealth;
|
|
65
78
|
operationalWarnings?: readonly OperationalWarning[];
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
const REQUIRED_LAYER_IDS: readonly LayerId[] = [
|
|
81
|
+
const REQUIRED_LAYER_IDS: readonly LayerId[] = ["L1", "L2", "L3", "L4"];
|
|
69
82
|
|
|
70
83
|
export class HarnessStatusSummary {
|
|
71
84
|
readonly layers: readonly LayerHealth[];
|
|
72
85
|
readonly phaseGateSummary: PhaseGateSummary;
|
|
73
86
|
readonly presetInfo: PresetInfo;
|
|
74
87
|
readonly configSummary: ConfigSummary;
|
|
88
|
+
readonly languages: LanguageInfo | undefined;
|
|
75
89
|
readonly hookHealth: HookHealth | undefined;
|
|
76
90
|
readonly baselineHealth: BaselineHealth | undefined;
|
|
77
91
|
readonly operationalWarnings: readonly OperationalWarning[];
|
|
@@ -81,6 +95,7 @@ export class HarnessStatusSummary {
|
|
|
81
95
|
this.phaseGateSummary = props.phaseGateSummary;
|
|
82
96
|
this.presetInfo = props.presetInfo;
|
|
83
97
|
this.configSummary = props.configSummary;
|
|
98
|
+
this.languages = props.languages;
|
|
84
99
|
this.hookHealth = props.hookHealth;
|
|
85
100
|
this.baselineHealth = props.baselineHealth;
|
|
86
101
|
this.operationalWarnings = Object.freeze([...(props.operationalWarnings ?? [])]);
|
|
@@ -91,13 +106,13 @@ export class HarnessStatusSummary {
|
|
|
91
106
|
// INV: 4レイヤー必須
|
|
92
107
|
if (props.layers.length !== 4) {
|
|
93
108
|
throw new Error(
|
|
94
|
-
`HarnessApiDomainError: HarnessStatusSummary requires exactly 4 layers (L1-L4), got ${props.layers.length}
|
|
109
|
+
`HarnessApiDomainError: HarnessStatusSummary requires exactly 4 layers (L1-L4), got ${props.layers.length}`,
|
|
95
110
|
);
|
|
96
111
|
}
|
|
97
112
|
// 重複チェック
|
|
98
113
|
const ids = props.layers.map((l) => l.layerId);
|
|
99
114
|
if (new Set(ids).size !== ids.length) {
|
|
100
|
-
throw new Error(
|
|
115
|
+
throw new Error("HarnessApiDomainError: HarnessStatusSummary has duplicate layerIds");
|
|
101
116
|
}
|
|
102
117
|
// L1-L4 必須チェック
|
|
103
118
|
for (const required of REQUIRED_LAYER_IDS) {
|
|
@@ -113,6 +128,6 @@ export class HarnessStatusSummary {
|
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
isAllLayersHealthy(): boolean {
|
|
116
|
-
return this.layers.every((l) => !l.enabled || l.lastResult ===
|
|
131
|
+
return this.layers.every((l) => !l.enabled || l.lastResult === "pass");
|
|
117
132
|
}
|
|
118
133
|
}
|
package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @layer infrastructure
|
|
2
2
|
// biome-ast-engine-lint-adapter.ts — BiomeAstEngineLintAdapter
|
|
3
3
|
// Wave 2完了後にリアル実装へ差し替え(旧: @stub: wave2-pending)
|
|
4
|
+
// @work-item-id WI-311
|
|
4
5
|
|
|
5
6
|
import type { BiomeLintPort } from '../../domain/ports/biome-lint-port.js';
|
|
6
7
|
import type { HarnessError } from '../../domain/value-objects/harness-api-response.js';
|
|
@@ -36,12 +37,11 @@ function violationToHarnessError(v: RuleViolation): HarnessError {
|
|
|
36
37
|
export class BiomeAstEngineLintAdapter implements BiomeLintPort {
|
|
37
38
|
private readonly stub: IBiomeAstEngineStub;
|
|
38
39
|
|
|
39
|
-
constructor(stub?: IBiomeAstEngineStub) {
|
|
40
|
-
this.stub = stub ?? BiomeAstEngineLintAdapter.createRealImpl();
|
|
40
|
+
constructor(stub?: IBiomeAstEngineStub, rootDir = process.cwd()) {
|
|
41
|
+
this.stub = stub ?? BiomeAstEngineLintAdapter.createRealImpl(rootDir);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
private static createRealImpl(): IBiomeAstEngineStub {
|
|
44
|
-
const rootDir = process.cwd();
|
|
44
|
+
private static createRealImpl(rootDir: string): IBiomeAstEngineStub {
|
|
45
45
|
return {
|
|
46
46
|
async runLint(): Promise<BiomeAstEngineResult> {
|
|
47
47
|
const { createBiomeAstEngineModule } = await import('../../../biome-ast-engine/composition-root.js');
|
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
// @unit harness-api
|
|
3
3
|
// @work-item-id WI-123
|
|
4
4
|
// @work-item-id WI-096
|
|
5
|
+
// @work-item-id WI-328
|
|
5
6
|
// harness-config-query-adapter.ts — HarnessConfigQueryAdapter
|
|
6
7
|
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { dirname, join, resolve } from
|
|
10
|
-
import
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import * as fs from "node:fs/promises";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { resolveProjectLanguages } from "../../../validator-system/infrastructure/adapters/harness-config-validator-config-adapter.js";
|
|
12
|
+
import type { ConfigQueryPort } from "../../domain/ports/config-query-port.js";
|
|
13
|
+
import type {
|
|
14
|
+
BaselineHealth,
|
|
15
|
+
ConfigSummary,
|
|
16
|
+
HookHealth,
|
|
17
|
+
LanguageInfo,
|
|
18
|
+
PhaseGateSummary,
|
|
19
|
+
PresetInfo,
|
|
20
|
+
} from "../../domain/value-objects/harness-status-summary.js";
|
|
21
|
+
import type { LayerId } from "../../domain/value-objects/layer-health.js";
|
|
13
22
|
|
|
14
23
|
interface HarnessConfigJson {
|
|
15
24
|
version: number;
|
|
16
25
|
project: {
|
|
17
26
|
name: string;
|
|
18
|
-
preset:
|
|
27
|
+
preset: "minimal" | "standard" | "strict";
|
|
28
|
+
languages?: string[];
|
|
19
29
|
};
|
|
20
30
|
layers?: Partial<Record<LayerId, { enabled?: boolean }>>;
|
|
21
31
|
paths?: {
|
|
@@ -29,9 +39,9 @@ interface HarnessConfigJson {
|
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
const PRESET_LAYERS: Record<string, LayerId[]> = {
|
|
32
|
-
minimal: [
|
|
33
|
-
standard: [
|
|
34
|
-
strict: [
|
|
42
|
+
minimal: ["L1"],
|
|
43
|
+
standard: ["L1", "L2", "L3"],
|
|
44
|
+
strict: ["L1", "L2", "L3", "L4"],
|
|
35
45
|
};
|
|
36
46
|
|
|
37
47
|
export interface HarnessConfigQueryAdapterOptions {
|
|
@@ -48,7 +58,7 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
48
58
|
|
|
49
59
|
private async readConfig(): Promise<HarnessConfigJson> {
|
|
50
60
|
if (this.cachedConfig !== null) return this.cachedConfig;
|
|
51
|
-
const content = await fs.readFile(this.configPath,
|
|
61
|
+
const content = await fs.readFile(this.configPath, "utf-8");
|
|
52
62
|
this.cachedConfig = JSON.parse(content) as HarnessConfigJson;
|
|
53
63
|
return this.cachedConfig;
|
|
54
64
|
}
|
|
@@ -56,7 +66,7 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
56
66
|
async getPresetInfo(): Promise<PresetInfo> {
|
|
57
67
|
const config = await this.readConfig();
|
|
58
68
|
const preset = config.project.preset;
|
|
59
|
-
const presetLayers = PRESET_LAYERS[preset] ?? [
|
|
69
|
+
const presetLayers = PRESET_LAYERS[preset] ?? ["L1", "L2", "L3"];
|
|
60
70
|
const enabledLayerSet = new Set<LayerId>(presetLayers);
|
|
61
71
|
|
|
62
72
|
for (const [layerId, layerConfig] of Object.entries(config.layers ?? {}) as [LayerId, { enabled?: boolean }][]) {
|
|
@@ -67,17 +77,29 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
const enabledLayers = ([
|
|
80
|
+
const enabledLayers = (["L1", "L2", "L3", "L4"] as const).filter((layerId) => enabledLayerSet.has(layerId));
|
|
71
81
|
return { name: preset, enabledLayers };
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
/**
|
|
85
|
+
* WI-328 (github#39 残課題): 実効言語リストと出所を返す。
|
|
86
|
+
* 解決ロジックは validator-system の resolveProjectLanguages()(WI-319 の
|
|
87
|
+
* 検出テーブル)を再利用し、validator の有効/SKIP 判定と同じ結果を表示する。
|
|
88
|
+
*/
|
|
89
|
+
async getLanguageInfo(): Promise<LanguageInfo> {
|
|
90
|
+
const config = await this.readConfig();
|
|
91
|
+
const rootDir = dirname(resolve(this.configPath));
|
|
92
|
+
const resolved = resolveProjectLanguages(config.project?.languages, rootDir);
|
|
93
|
+
return { effective: resolved.languages, source: resolved.source };
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
async getConfigSummary(): Promise<ConfigSummary> {
|
|
75
97
|
await this.readConfig();
|
|
76
98
|
const stat = await fs.stat(this.configPath);
|
|
77
99
|
return {
|
|
78
100
|
configPath: this.configPath,
|
|
79
101
|
lastModified: stat.mtime.toISOString(),
|
|
80
|
-
version:
|
|
102
|
+
version: "2",
|
|
81
103
|
};
|
|
82
104
|
}
|
|
83
105
|
|
|
@@ -89,7 +111,7 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
89
111
|
async getHookHealth(): Promise<HookHealth> {
|
|
90
112
|
const rootDir = dirname(this.configPath);
|
|
91
113
|
const configuredHooks: string[] = [];
|
|
92
|
-
for (const hookPath of [
|
|
114
|
+
for (const hookPath of [".claude/settings.json", ".codex/hooks.json", ".husky/pre-commit", ".husky/pre-push"]) {
|
|
93
115
|
try {
|
|
94
116
|
await fs.access(join(rootDir, hookPath));
|
|
95
117
|
configuredHooks.push(hookPath);
|
|
@@ -98,7 +120,7 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
98
120
|
}
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
const skipEvents = await readHookSkipEvents(join(rootDir,
|
|
123
|
+
const skipEvents = await readHookSkipEvents(join(rootDir, ".phasegate/hook-skip-events.jsonl"));
|
|
102
124
|
const skipCountsByReason: Record<string, number> = {};
|
|
103
125
|
for (const event of skipEvents) {
|
|
104
126
|
skipCountsByReason[event.reason] = (skipCountsByReason[event.reason] ?? 0) + 1;
|
|
@@ -111,8 +133,8 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
111
133
|
skipCountsByReason,
|
|
112
134
|
applyPatchBypass: {
|
|
113
135
|
nativeApplyPatchIntercepted: false,
|
|
114
|
-
backstop:
|
|
115
|
-
documentationUrl:
|
|
136
|
+
backstop: "pre-commit",
|
|
137
|
+
documentationUrl: "docs/guide/codex-integration.md",
|
|
116
138
|
},
|
|
117
139
|
};
|
|
118
140
|
}
|
|
@@ -121,50 +143,73 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
|
|
|
121
143
|
const config = await this.readConfig();
|
|
122
144
|
const rootDir = dirname(this.configPath);
|
|
123
145
|
const enabled = config.baseline?.enabled ?? true;
|
|
124
|
-
const relativePath = config.baseline?.path ??
|
|
146
|
+
const relativePath = config.baseline?.path ?? ".phasegate/baseline.json";
|
|
125
147
|
const baselinePath = resolve(rootDir, relativePath);
|
|
126
148
|
|
|
127
149
|
if (!enabled) {
|
|
128
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
enabled: false,
|
|
152
|
+
path: relativePath,
|
|
153
|
+
grandfatheredFileCount: 0,
|
|
154
|
+
shaMismatchCount: 0,
|
|
155
|
+
missingFileCount: 0,
|
|
156
|
+
removalRate: 0,
|
|
157
|
+
};
|
|
129
158
|
}
|
|
130
159
|
|
|
131
160
|
let raw: string;
|
|
132
161
|
try {
|
|
133
|
-
raw = await fs.readFile(baselinePath,
|
|
162
|
+
raw = await fs.readFile(baselinePath, "utf-8");
|
|
134
163
|
} catch {
|
|
135
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
enabled: true,
|
|
166
|
+
path: relativePath,
|
|
167
|
+
grandfatheredFileCount: 0,
|
|
168
|
+
shaMismatchCount: 0,
|
|
169
|
+
missingFileCount: 0,
|
|
170
|
+
removalRate: 0,
|
|
171
|
+
};
|
|
136
172
|
}
|
|
137
173
|
|
|
138
|
-
const parsed = JSON.parse(raw) as {
|
|
174
|
+
const parsed = JSON.parse(raw) as {
|
|
175
|
+
files?: Array<{ path?: string; sha1?: string }>;
|
|
176
|
+
entries?: Array<{ path?: string; sha1?: string }>;
|
|
177
|
+
};
|
|
139
178
|
const entries = parsed.files ?? parsed.entries ?? [];
|
|
140
179
|
let shaMismatchCount = 0;
|
|
141
180
|
let missingFileCount = 0;
|
|
142
181
|
for (const entry of entries) {
|
|
143
|
-
if (typeof entry.path !==
|
|
182
|
+
if (typeof entry.path !== "string" || typeof entry.sha1 !== "string") continue;
|
|
144
183
|
try {
|
|
145
184
|
const current = await fs.readFile(resolve(rootDir, entry.path));
|
|
146
|
-
const sha1 = createHash(
|
|
185
|
+
const sha1 = createHash("sha1").update(current).digest("hex");
|
|
147
186
|
if (sha1 !== entry.sha1) shaMismatchCount += 1;
|
|
148
187
|
} catch {
|
|
149
188
|
missingFileCount += 1;
|
|
150
189
|
}
|
|
151
190
|
}
|
|
152
191
|
const grandfatheredFileCount = entries.length;
|
|
153
|
-
const removalRate =
|
|
154
|
-
? 1
|
|
155
|
-
|
|
156
|
-
|
|
192
|
+
const removalRate =
|
|
193
|
+
grandfatheredFileCount === 0 ? 1 : (shaMismatchCount + missingFileCount) / grandfatheredFileCount;
|
|
194
|
+
return {
|
|
195
|
+
enabled: true,
|
|
196
|
+
path: relativePath,
|
|
197
|
+
grandfatheredFileCount,
|
|
198
|
+
shaMismatchCount,
|
|
199
|
+
missingFileCount,
|
|
200
|
+
removalRate,
|
|
201
|
+
};
|
|
157
202
|
}
|
|
158
203
|
}
|
|
159
204
|
|
|
160
|
-
async function readHookSkipEvents(filePath: string): Promise<Array<NonNullable<HookHealth[
|
|
205
|
+
async function readHookSkipEvents(filePath: string): Promise<Array<NonNullable<HookHealth["latestSkip"]>>> {
|
|
161
206
|
try {
|
|
162
|
-
const raw = await fs.readFile(filePath,
|
|
207
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
163
208
|
return raw
|
|
164
|
-
.split(
|
|
209
|
+
.split("\n")
|
|
165
210
|
.filter((line) => line.trim().length > 0)
|
|
166
|
-
.map((line) => JSON.parse(line) as HookHealth[
|
|
167
|
-
.filter((event): event is NonNullable<HookHealth[
|
|
211
|
+
.map((line) => JSON.parse(line) as HookHealth["latestSkip"])
|
|
212
|
+
.filter((event): event is NonNullable<HookHealth["latestSkip"]> => event !== null);
|
|
168
213
|
} catch {
|
|
169
214
|
return [];
|
|
170
215
|
}
|
|
@@ -8,11 +8,13 @@ export interface CreateHarnessErrorInput {
|
|
|
8
8
|
readonly code: string;
|
|
9
9
|
readonly message: string;
|
|
10
10
|
readonly suggestion: string;
|
|
11
|
-
readonly severity?:
|
|
11
|
+
readonly severity?: "error" | "warning";
|
|
12
12
|
readonly adrRef?: string;
|
|
13
13
|
readonly fixExample?: string;
|
|
14
14
|
readonly validatorId: string;
|
|
15
15
|
readonly suggestedSkill?: string;
|
|
16
16
|
readonly scaffoldCommand?: string;
|
|
17
17
|
readonly templatePath?: string;
|
|
18
|
+
/** WI-335: 修復方式分類。省略時は ErrorDefinition の既定(未定義なら manual 扱い)。 */
|
|
19
|
+
readonly remediationType?: "mechanical" | "ai-assisted" | "manual";
|
|
18
20
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
export interface HarnessErrorContract {
|
|
5
5
|
readonly code: string;
|
|
6
|
-
readonly severity:
|
|
6
|
+
readonly severity: "error" | "warning";
|
|
7
7
|
readonly message: string;
|
|
8
8
|
readonly suggestion: string;
|
|
9
9
|
readonly adr_ref?: string;
|
|
@@ -11,4 +11,6 @@ export interface HarnessErrorContract {
|
|
|
11
11
|
readonly suggested_skill?: string;
|
|
12
12
|
readonly scaffold_command?: string;
|
|
13
13
|
readonly template_path?: string;
|
|
14
|
+
/** WI-335: 修復方式分類(mechanical / ai-assisted / manual)。未設定は manual 扱い。 */
|
|
15
|
+
readonly remediation_type?: "mechanical" | "ai-assisted" | "manual";
|
|
14
16
|
}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* HarnessErrorContract への投影Mapper
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import type { HarnessError } from
|
|
7
|
+
|
|
8
|
+
import type { HarnessError } from "../../domain/value-objects/harness-error.js";
|
|
9
|
+
import type { HarnessErrorContract } from "../dto/harness-error-contract.js";
|
|
9
10
|
|
|
10
11
|
export class HarnessErrorContractMapper {
|
|
11
12
|
toReadonlyContract(harnessError: HarnessError): Readonly<HarnessErrorContract> {
|
|
@@ -14,21 +15,12 @@ export class HarnessErrorContractMapper {
|
|
|
14
15
|
severity: harnessError.severity.toString(),
|
|
15
16
|
message: harnessError.message,
|
|
16
17
|
suggestion: harnessError.suggestion,
|
|
17
|
-
...(harnessError.adrRef !== null
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
...(harnessError.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
...(harnessError.suggestedSkill !== null
|
|
24
|
-
? { suggested_skill: harnessError.suggestedSkill }
|
|
25
|
-
: {}),
|
|
26
|
-
...(harnessError.scaffoldCommand !== null
|
|
27
|
-
? { scaffold_command: harnessError.scaffoldCommand }
|
|
28
|
-
: {}),
|
|
29
|
-
...(harnessError.templatePath !== null
|
|
30
|
-
? { template_path: harnessError.templatePath }
|
|
31
|
-
: {}),
|
|
18
|
+
...(harnessError.adrRef !== null ? { adr_ref: harnessError.adrRef.toString() } : {}),
|
|
19
|
+
...(harnessError.fixExample !== null ? { fix_example: harnessError.fixExample.toString() } : {}),
|
|
20
|
+
...(harnessError.suggestedSkill !== null ? { suggested_skill: harnessError.suggestedSkill } : {}),
|
|
21
|
+
...(harnessError.scaffoldCommand !== null ? { scaffold_command: harnessError.scaffoldCommand } : {}),
|
|
22
|
+
...(harnessError.templatePath !== null ? { template_path: harnessError.templatePath } : {}),
|
|
23
|
+
...(harnessError.remediationType !== null ? { remediation_type: harnessError.remediationType } : {}),
|
|
32
24
|
};
|
|
33
25
|
|
|
34
26
|
return Object.freeze(contract);
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 単一draftを HarnessErrorContract へ変換するUseCase
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
7
|
+
|
|
8
|
+
import type { HarnessErrorFactory } from "../../domain/services/harness-error-factory.js";
|
|
9
|
+
import type { CreateHarnessErrorInput } from "../dto/create-harness-error-input.js";
|
|
10
|
+
import type { HarnessErrorContract } from "../dto/harness-error-contract.js";
|
|
11
|
+
import type { HarnessErrorContractMapper } from "../mappers/harness-error-contract-mapper.js";
|
|
11
12
|
|
|
12
13
|
export interface CreateHarnessErrorUseCaseDeps {
|
|
13
14
|
readonly harnessErrorFactory: HarnessErrorFactory;
|
|
@@ -23,9 +24,7 @@ export class CreateHarnessErrorUseCase {
|
|
|
23
24
|
this.contractMapper = deps.contractMapper;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
async execute(
|
|
27
|
-
input: CreateHarnessErrorInput
|
|
28
|
-
): Promise<Readonly<HarnessErrorContract>> {
|
|
27
|
+
async execute(input: CreateHarnessErrorInput): Promise<Readonly<HarnessErrorContract>> {
|
|
29
28
|
const harnessError = await this.harnessErrorFactory.create({
|
|
30
29
|
code: input.code,
|
|
31
30
|
message: input.message,
|
|
@@ -37,6 +36,7 @@ export class CreateHarnessErrorUseCase {
|
|
|
37
36
|
suggestedSkill: input.suggestedSkill,
|
|
38
37
|
scaffoldCommand: input.scaffoldCommand,
|
|
39
38
|
templatePath: input.templatePath,
|
|
39
|
+
remediationType: input.remediationType,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
return this.contractMapper.toReadonlyContract(harnessError);
|
|
@@ -5,33 +5,36 @@
|
|
|
5
5
|
* HarnessErrorFactory ドメインサービス
|
|
6
6
|
* ErrorDefinitionRegistry と Domain ポートを組み合わせて HarnessError を生成する唯一の入口
|
|
7
7
|
*/
|
|
8
|
-
import { AdrReferenceNotFoundError } from
|
|
9
|
-
import { EmptyMessageError } from
|
|
10
|
-
import { EmptySuggestionError } from
|
|
11
|
-
import { InvalidFixExampleError } from
|
|
12
|
-
import { MissingAdrRefError } from
|
|
13
|
-
import { MissingFixExampleError } from
|
|
14
|
-
import type { AdrExistenceCheckerPort } from
|
|
15
|
-
import type { FixExampleValidatorPort } from
|
|
16
|
-
import { AdrRef } from
|
|
17
|
-
import { ErrorCode } from
|
|
18
|
-
import { FixExample } from
|
|
19
|
-
import { HarnessError } from
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import type {
|
|
8
|
+
import { AdrReferenceNotFoundError } from "../errors/adr-reference-not-found-error.js";
|
|
9
|
+
import { EmptyMessageError } from "../errors/empty-message-error.js";
|
|
10
|
+
import { EmptySuggestionError } from "../errors/empty-suggestion-error.js";
|
|
11
|
+
import { InvalidFixExampleError } from "../errors/invalid-fix-example-error.js";
|
|
12
|
+
import { MissingAdrRefError } from "../errors/missing-adr-ref-error.js";
|
|
13
|
+
import { MissingFixExampleError } from "../errors/missing-fix-example-error.js";
|
|
14
|
+
import type { AdrExistenceCheckerPort } from "../ports/adr-existence-checker-port.js";
|
|
15
|
+
import type { FixExampleValidatorPort } from "../ports/fix-example-validator-port.js";
|
|
16
|
+
import { AdrRef } from "../value-objects/adr-ref.js";
|
|
17
|
+
import { ErrorCode } from "../value-objects/error-code.js";
|
|
18
|
+
import { FixExample } from "../value-objects/fix-example.js";
|
|
19
|
+
import { HarnessError } from "../value-objects/harness-error.js";
|
|
20
|
+
import type { RemediationType } from "../value-objects/remediation-type.js";
|
|
21
|
+
import { Severity } from "../value-objects/severity.js";
|
|
22
|
+
import type { ErrorDefinitionRegistry } from "./error-definition-registry.js";
|
|
23
|
+
import type { SeverityContractEnforcer } from "./severity-contract-enforcer.js";
|
|
23
24
|
|
|
24
25
|
export interface CreateHarnessErrorParams {
|
|
25
26
|
readonly code: string;
|
|
26
27
|
readonly message: string;
|
|
27
28
|
readonly suggestion: string;
|
|
28
29
|
readonly validatorId: string;
|
|
29
|
-
readonly requestedSeverity?:
|
|
30
|
+
readonly requestedSeverity?: "error" | "warning";
|
|
30
31
|
readonly adrRef?: string | null;
|
|
31
32
|
readonly fixExample?: string | null;
|
|
32
33
|
readonly suggestedSkill?: string | null;
|
|
33
34
|
readonly scaffoldCommand?: string | null;
|
|
34
35
|
readonly templatePath?: string | null;
|
|
36
|
+
/** WI-335: suggestion の修復方式分類。省略時は ErrorDefinition の既定に従う。 */
|
|
37
|
+
readonly remediationType?: RemediationType | null;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export interface HarnessErrorFactoryDeps {
|
|
@@ -68,18 +71,14 @@ export class HarnessErrorFactory {
|
|
|
68
71
|
const definition = this.registry.getDefinition(errorCode);
|
|
69
72
|
|
|
70
73
|
// 3. severity 解決
|
|
71
|
-
const requestedSeverity = input.requestedSeverity
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
requestedSeverity,
|
|
77
|
-
definition.defaultSeverity
|
|
78
|
-
);
|
|
74
|
+
const requestedSeverity = input.requestedSeverity ? Severity.create(input.requestedSeverity) : undefined;
|
|
75
|
+
const effectiveSeverity = this.severityContractEnforcer.resolveEffectiveSeverity(
|
|
76
|
+
requestedSeverity,
|
|
77
|
+
definition.defaultSeverity,
|
|
78
|
+
);
|
|
79
79
|
|
|
80
80
|
// 4. ADR 解決
|
|
81
|
-
const explicitAdrRef =
|
|
82
|
-
input.adrRef != null ? AdrRef.create(input.adrRef) : undefined;
|
|
81
|
+
const explicitAdrRef = input.adrRef != null ? AdrRef.create(input.adrRef) : undefined;
|
|
83
82
|
const resolvedAdrRef = definition.resolveAdrRef(explicitAdrRef);
|
|
84
83
|
|
|
85
84
|
// 5. ADR 必須チェック
|
|
@@ -96,12 +95,8 @@ export class HarnessErrorFactory {
|
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
// 7. fix_example 解決
|
|
99
|
-
const explicitFixExample =
|
|
100
|
-
|
|
101
|
-
? FixExample.create(input.fixExample)
|
|
102
|
-
: undefined;
|
|
103
|
-
const resolvedFixExample =
|
|
104
|
-
definition.resolveFixExample(explicitFixExample);
|
|
98
|
+
const explicitFixExample = input.fixExample != null ? FixExample.create(input.fixExample) : undefined;
|
|
99
|
+
const resolvedFixExample = definition.resolveFixExample(explicitFixExample);
|
|
105
100
|
|
|
106
101
|
// 8. fix_example 必須チェック
|
|
107
102
|
if (definition.requiresFixExample() && resolvedFixExample === null) {
|
|
@@ -117,7 +112,7 @@ export class HarnessErrorFactory {
|
|
|
117
112
|
});
|
|
118
113
|
if (!validationResult.passed) {
|
|
119
114
|
throw new InvalidFixExampleError(
|
|
120
|
-
`fix_example の検証に失敗しました (code=${errorCode.toString()}): ${validationResult.reason}
|
|
115
|
+
`fix_example の検証に失敗しました (code=${errorCode.toString()}): ${validationResult.reason}`,
|
|
121
116
|
);
|
|
122
117
|
}
|
|
123
118
|
}
|
|
@@ -126,6 +121,7 @@ export class HarnessErrorFactory {
|
|
|
126
121
|
const resolvedSuggestedSkill = definition.resolveSuggestedSkill(input.suggestedSkill);
|
|
127
122
|
const resolvedScaffoldCommand = definition.resolveScaffoldCommand(input.scaffoldCommand);
|
|
128
123
|
const resolvedTemplatePath = definition.resolveTemplatePath(input.templatePath);
|
|
124
|
+
const resolvedRemediationType = definition.resolveRemediationType(input.remediationType);
|
|
129
125
|
|
|
130
126
|
// 11. HarnessError 生成と凍結
|
|
131
127
|
const harnessError = new HarnessError({
|
|
@@ -138,6 +134,7 @@ export class HarnessErrorFactory {
|
|
|
138
134
|
suggestedSkill: resolvedSuggestedSkill,
|
|
139
135
|
scaffoldCommand: resolvedScaffoldCommand,
|
|
140
136
|
templatePath: resolvedTemplatePath,
|
|
137
|
+
remediationType: resolvedRemediationType,
|
|
141
138
|
});
|
|
142
139
|
|
|
143
140
|
return Object.freeze(harnessError);
|