phasegate 0.254.0 → 0.283.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 +60 -0
- package/README.ja.md +6 -3
- package/README.md +6 -3
- package/docs/contracts/attestation-v2.schema.json +110 -0
- package/docs/guide/cli-reference.md +12 -1
- package/docs/guide/configuration.md +56 -0
- package/docs/guide/installation.md +1 -1
- package/docs/templates/ci/aidlc-gate.yml +60 -2
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
- package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
- package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +30 -27
- package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +29 -16
- package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +35 -26
- package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
- package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
- package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
- package/scripts/harness/attestation/composition-root.ts +5 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
- package/scripts/harness/attestation/index.ts +7 -1
- package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +40 -9
- package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +70 -87
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +80 -88
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +34 -18
- package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
- package/scripts/harness/config-foundation/index.ts +14 -15
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
- 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 +84 -8
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +84 -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 +125 -86
- 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 +18 -4
- 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/installation/application/usecases/run-install.ts +199 -51
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +277 -69
- package/scripts/harness/installation/domain/deployment-manifest.ts +43 -0
- package/scripts/harness/integrations/pre-commit.ts +169 -27
- package/scripts/harness/main.ts +200 -136
- package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
- package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
- package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
- package/scripts/harness/traceability-model/composition-root.ts +5 -0
- package/scripts/harness/traceability-model/index.ts +9 -0
- package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +57 -6
- package/scripts/harness/validator-system/composition-root.ts +25 -7
- package/scripts/harness/validator-system/domain/ports/ac-coverage-policy-port.ts +10 -1
- package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
- package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
- package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +126 -18
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +49 -20
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
- package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
- package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
- package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
- package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
- package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
- package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
- package/scripts/harness/world-model/composition-root.ts +62 -33
- package/scripts/harness/world-model/index.ts +11 -0
- package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
- package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
- package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +6 -2
|
@@ -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
|
}
|