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
|
@@ -126,11 +126,11 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
|
|
|
126
126
|
|
|
127
127
|
// Handlers
|
|
128
128
|
const generateCiTemplateHandler = new GenerateCiTemplateHandler(generateCiTemplateUseCase, renderCiTemplateUseCase);
|
|
129
|
-
const migrateAgentsMdHandler = new MigrateAgentsMdHandler(migrateAgentsMdUseCase);
|
|
129
|
+
const migrateAgentsMdHandler = new MigrateAgentsMdHandler(migrateAgentsMdUseCase, validatePointersUseCase);
|
|
130
130
|
const refreshAgentContextHandler = new RefreshAgentContextHandler(refreshAgentContextUseCase);
|
|
131
131
|
const refreshClaudeMdHandler = new RefreshClaudeMdHandler(refreshClaudeMdUseCase);
|
|
132
132
|
const checkAgentContextHandler = new CheckAgentContextHandler(checkAgentContextUseCase);
|
|
133
|
-
const checkRepetitionHandler = new CheckRepetitionHandler(checkEscalationUseCase);
|
|
133
|
+
const checkRepetitionHandler = new CheckRepetitionHandler(checkEscalationUseCase, resetRepetitionUseCase);
|
|
134
134
|
const createBaselineHandler = new CreateBaselineHandler(createBaselineUseCase);
|
|
135
135
|
const scaffoldDesignHandler = new ScaffoldDesignHandler(scaffoldDesignUseCase);
|
|
136
136
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit ci-governance
|
|
4
|
+
* @work-item-id WI-331
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
export interface ClaudeMdTemplateValues {
|
|
@@ -9,20 +10,28 @@ export interface ClaudeMdTemplateValues {
|
|
|
9
10
|
readonly presets: readonly string[];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const USER_SECTION_START =
|
|
13
|
-
const USER_SECTION_END =
|
|
14
|
-
const DEFAULT_USER_SECTION =
|
|
13
|
+
const USER_SECTION_START = "<!-- phasegate:user-section:start -->";
|
|
14
|
+
const USER_SECTION_END = "<!-- phasegate:user-section:end -->";
|
|
15
|
+
const DEFAULT_USER_SECTION = "Project-specific agent instructions go here.";
|
|
15
16
|
|
|
16
17
|
export class ClaudeMdComposer {
|
|
18
|
+
// Rebuilds CLAUDE.md from the current template, carrying over the existing
|
|
19
|
+
// user-section body. Because the whole file is re-rendered, a legacy file
|
|
20
|
+
// whose user-section still sits inside the managed block (pre-WI-331
|
|
21
|
+
// template shape) is migrated to the current outside-of-block structure.
|
|
17
22
|
compose(template: string, existing: string | null, values: ClaudeMdTemplateValues): string {
|
|
18
23
|
const userSection = this.extractUserSection(existing) ?? DEFAULT_USER_SECTION;
|
|
19
|
-
|
|
20
|
-
.replace(
|
|
21
|
-
.replace(
|
|
22
|
-
.replace(
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
.
|
|
24
|
+
const rendered = template
|
|
25
|
+
.replace("{{PHASEGATE_COMMANDS}}", this.toList(values.commands))
|
|
26
|
+
.replace("{{PHASEGATE_SKILLS}}", this.toList(values.skills))
|
|
27
|
+
.replace("{{PHASEGATE_PRESETS}}", this.toList(values.presets))
|
|
28
|
+
// Collapse blank-line runs before injecting the user body so
|
|
29
|
+
// user-authored spacing is preserved byte-for-byte.
|
|
30
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
31
|
+
// Replacer function keeps `$`-sequences in the user body from being
|
|
32
|
+
// interpreted as String.replace substitution patterns.
|
|
33
|
+
.replace("{{PHASEGATE_USER_SECTION}}", () => userSection);
|
|
34
|
+
return `${rendered.trimEnd()}\n`;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
private extractUserSection(existing: string | null): string | null {
|
|
@@ -34,6 +43,6 @@ export class ClaudeMdComposer {
|
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
private toList(values: readonly string[]): string {
|
|
37
|
-
return values.map((value) => `- \`${value}\``).join(
|
|
46
|
+
return values.map((value) => `- \`${value}\``).join("\n");
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -29,6 +29,13 @@ export class CheckRepetitionHandler {
|
|
|
29
29
|
async handle(args: CheckRepetitionHandlerArgs): Promise<CheckRepetitionHandlerResult> {
|
|
30
30
|
const { errorCode, reset = false, format = 'human' } = args;
|
|
31
31
|
|
|
32
|
+
if (errorCode.trim().length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
exitCode: 1,
|
|
35
|
+
output: 'Error: --code <errorCode> is required. Usage: phasegate ci:check-repetition --code <errorCode> [--reset] [--json]',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
if (reset && this.resetUseCase) {
|
|
33
40
|
const result = await this.resetUseCase.execute({ errorCode, confirmedResolution: true });
|
|
34
41
|
|
|
@@ -49,9 +56,10 @@ export class CheckRepetitionHandler {
|
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
const result = await this.checkUseCase.execute({ errorCode });
|
|
59
|
+
const exitCode = result.escalated === true ? 1 : 0;
|
|
52
60
|
|
|
53
61
|
if (format === 'json') {
|
|
54
|
-
return { exitCode
|
|
62
|
+
return { exitCode, output: JSON.stringify(result, null, 2) };
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
const lines: string[] = [];
|
|
@@ -63,6 +71,6 @@ export class CheckRepetitionHandler {
|
|
|
63
71
|
lines.push(` Escalated: ${result.escalated}`);
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
return { exitCode
|
|
74
|
+
return { exitCode, output: lines.join('\n') };
|
|
67
75
|
}
|
|
68
76
|
}
|
package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @work-item-id WI-300
|
|
11
11
|
* @work-item-id WI-301
|
|
12
12
|
* @work-item-id WI-302
|
|
13
|
+
* @work-item-id WI-320
|
|
13
14
|
*/
|
|
14
15
|
import type { HarnessConfigV2 } from "../../domain/harness-config.js";
|
|
15
16
|
|
|
@@ -54,7 +55,10 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
|
|
|
54
55
|
return {
|
|
55
56
|
project: {
|
|
56
57
|
preset: resolvedConfig.project.preset,
|
|
57
|
-
|
|
58
|
+
// WI-320 (github#39): 未宣言時に ["typescript"] を注入せず undefined のまま渡す。
|
|
59
|
+
// HarnessConfigValidatorConfigAdapter.getProjectLanguages() が未宣言時のみ
|
|
60
|
+
// ファイルシステム検出(WI-319)にフォールバックできるようにするため。
|
|
61
|
+
...(resolvedConfig.project.languages === undefined ? {} : { languages: resolvedConfig.project.languages }),
|
|
58
62
|
},
|
|
59
63
|
paths: {
|
|
60
64
|
designDocs: resolvedConfig.paths.designDocs,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @work-item-id WI-212
|
|
6
6
|
* @work-item-id WI-219
|
|
7
7
|
* @work-item-id WI-300
|
|
8
|
+
* @work-item-id WI-327
|
|
8
9
|
*/
|
|
9
10
|
import { ConfigFoundationDomainError } from "./errors/config-foundation-domain-error.js";
|
|
10
11
|
import { ConfigValidationError } from "./errors/config-validation-error.js";
|
|
@@ -48,13 +49,15 @@ export interface HarnessConfigSourceDocument {
|
|
|
48
49
|
preset: PresetId;
|
|
49
50
|
languages?: string[];
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// WI-327: project 以外の top-level セクションは手書き config で省略可。
|
|
53
|
+
// 省略時は PresetResolutionService が防御プリセット(project.preset)のデフォルト値で補完する。
|
|
54
|
+
layers?: DeepPartial<HarnessConfigResolvedDocument["layers"]>;
|
|
55
|
+
quickMode?: Partial<HarnessConfigResolvedDocument["quickMode"]>;
|
|
56
|
+
phaseDependencies?: HarnessConfigResolvedDocument["phaseDependencies"];
|
|
57
|
+
planningMode?: HarnessConfigResolvedDocument["planningMode"];
|
|
58
|
+
harnesses?: Partial<HarnessConfigResolvedDocument["harnesses"]>;
|
|
59
|
+
paths?: HarnessConfigResolvedDocument["paths"];
|
|
60
|
+
reporting?: HarnessConfigResolvedDocument["reporting"];
|
|
58
61
|
ci?: HarnessConfigResolvedDocument["ci"];
|
|
59
62
|
validate?: HarnessConfigResolvedDocument["validate"];
|
|
60
63
|
preCommit?: Partial<HarnessConfigResolvedDocument["preCommit"]>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-212
|
|
5
5
|
* @work-item-id WI-300
|
|
6
|
+
* @work-item-id WI-320
|
|
6
7
|
*/
|
|
7
8
|
import { ConfigFoundationDomainError } from "../errors/config-foundation-domain-error.js";
|
|
8
9
|
import type { HarnessConfigResolvedDocument, HarnessConfigSourceDocument } from "../harness-config.js";
|
|
@@ -163,7 +164,9 @@ export class PresetResolutionService {
|
|
|
163
164
|
project: {
|
|
164
165
|
name: sourceDocument.project.name,
|
|
165
166
|
preset: sourceDocument.project.preset,
|
|
166
|
-
|
|
167
|
+
// WI-320 (github#39): 未宣言時に ["typescript"] を注入しない。「未宣言」を resolved config まで
|
|
168
|
+
// 生存させ、validator-system adapter のファイルシステム言語検出(WI-319)を実 CLI 経路で有効にする。
|
|
169
|
+
...(sourceDocument.project.languages === undefined ? {} : { languages: sourceDocument.project.languages }),
|
|
167
170
|
},
|
|
168
171
|
layers,
|
|
169
172
|
quickMode: deepMerge(presetDefinition.quickMode, sourceDocument.quickMode, "quickMode"),
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-212
|
|
5
|
+
* @work-item-id WI-320
|
|
5
6
|
*
|
|
6
7
|
* ProjectConfig値オブジェクト - プロジェクト名とPresetを保持する
|
|
7
8
|
*/
|
|
8
|
-
import { ConfigValidationError } from
|
|
9
|
-
import { Preset } from
|
|
9
|
+
import { ConfigValidationError } from "../errors/config-validation-error.js";
|
|
10
|
+
import { Preset } from "./preset.js";
|
|
10
11
|
|
|
11
12
|
interface ProjectConfigProps {
|
|
12
13
|
readonly name: string;
|
|
@@ -17,29 +18,37 @@ interface ProjectConfigProps {
|
|
|
17
18
|
export class ProjectConfig {
|
|
18
19
|
readonly name: string;
|
|
19
20
|
readonly preset: Preset;
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* WI-320 (github#39): 宣言があった場合のみ保持する(undefined = 未宣言)。
|
|
23
|
+
* default `['typescript']` の注入をやめ、「未宣言」シグナルを下流
|
|
24
|
+
* (validator-system のファイルシステム言語検出、WI-319)まで生存させる。
|
|
25
|
+
*/
|
|
26
|
+
readonly languages?: readonly string[];
|
|
21
27
|
|
|
22
28
|
constructor(props: ProjectConfigProps) {
|
|
23
|
-
if (!props.name || props.name.trim() ===
|
|
24
|
-
throw new ConfigValidationError(
|
|
25
|
-
}
|
|
26
|
-
const sourceLanguages = props.languages ?? ['typescript'];
|
|
27
|
-
if (sourceLanguages.length === 0) {
|
|
28
|
-
throw new ConfigValidationError('ProjectConfig: languages must not be empty');
|
|
29
|
+
if (!props.name || props.name.trim() === "") {
|
|
30
|
+
throw new ConfigValidationError("ProjectConfig: name must not be empty");
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
if (props.languages !== undefined) {
|
|
33
|
+
if (props.languages.length === 0) {
|
|
34
|
+
throw new ConfigValidationError("ProjectConfig: languages must not be empty");
|
|
35
|
+
}
|
|
36
|
+
const normalizedLanguages = props.languages
|
|
37
|
+
.map((language) => language.trim())
|
|
38
|
+
.filter((language) => language.length > 0);
|
|
39
|
+
if (normalizedLanguages.length !== props.languages.length) {
|
|
40
|
+
throw new ConfigValidationError("ProjectConfig: languages must contain non-empty strings");
|
|
41
|
+
}
|
|
42
|
+
this.languages = Object.freeze([...normalizedLanguages]);
|
|
33
43
|
}
|
|
34
44
|
this.name = props.name;
|
|
35
45
|
this.preset = props.preset;
|
|
36
|
-
this.languages = Object.freeze([...normalizedLanguages]);
|
|
37
46
|
Object.freeze(this);
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
static create(raw: { name: string; preset: string; languages?: readonly string[] }): ProjectConfig {
|
|
41
50
|
const preset = Preset.create(raw.preset);
|
|
42
|
-
return new ProjectConfig({ name: raw.name, preset, languages: raw.languages
|
|
51
|
+
return new ProjectConfig({ name: raw.name, preset, languages: raw.languages });
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
rename(name: string): ProjectConfig {
|
|
@@ -51,9 +60,16 @@ export class ProjectConfig {
|
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
equals(other: ProjectConfig): boolean {
|
|
54
|
-
return this.name === other.name
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
return this.name === other.name && this.preset.equals(other.preset) && this.languagesEqual(other.languages);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private languagesEqual(otherLanguages: readonly string[] | undefined): boolean {
|
|
67
|
+
if (this.languages === undefined || otherLanguages === undefined) {
|
|
68
|
+
return this.languages === otherLanguages;
|
|
69
|
+
}
|
|
70
|
+
return (
|
|
71
|
+
this.languages.length === otherLanguages.length &&
|
|
72
|
+
this.languages.every((language, index) => language === otherLanguages[index])
|
|
73
|
+
);
|
|
58
74
|
}
|
|
59
75
|
}
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
* @layer infrastructure
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
*/
|
|
5
|
-
import * as fs from
|
|
6
|
-
import path from
|
|
7
|
-
import type { ConfigRepositoryPort } from
|
|
5
|
+
import * as fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import type { ConfigRepositoryPort } from "../../domain/ports/config-repository-port.js";
|
|
8
8
|
|
|
9
|
-
const DEFAULT_CONFIG_FILE_NAME =
|
|
10
|
-
const PERSONAL_CONFIG_PATH = path.join(
|
|
9
|
+
const DEFAULT_CONFIG_FILE_NAME = "phasegate.config.json";
|
|
10
|
+
const PERSONAL_CONFIG_PATH = path.join(".phasegate-local", DEFAULT_CONFIG_FILE_NAME);
|
|
11
11
|
|
|
12
12
|
export class ConfigNotFoundError extends Error {
|
|
13
13
|
readonly configPath: string;
|
|
14
14
|
|
|
15
15
|
constructor(configPath: string) {
|
|
16
16
|
super(`Config file not found: ${configPath}`);
|
|
17
|
-
this.name =
|
|
17
|
+
this.name = "ConfigNotFoundError";
|
|
18
18
|
this.configPath = configPath;
|
|
19
19
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
20
20
|
}
|
|
@@ -24,15 +24,28 @@ export class ConfigPersistenceError extends Error {
|
|
|
24
24
|
readonly configPath: string;
|
|
25
25
|
readonly cause?: unknown;
|
|
26
26
|
|
|
27
|
-
constructor(configPath: string, cause?: unknown) {
|
|
28
|
-
super(`Failed to persist config: ${configPath}`);
|
|
29
|
-
this.name =
|
|
27
|
+
constructor(configPath: string, cause?: unknown, message?: string) {
|
|
28
|
+
super(message ?? `Failed to persist config: ${configPath}`);
|
|
29
|
+
this.name = "ConfigPersistenceError";
|
|
30
30
|
this.configPath = configPath;
|
|
31
31
|
this.cause = cause;
|
|
32
32
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* WI-325: load 経路の JSON パース失敗専用エラー。
|
|
38
|
+
* ConfigPersistenceError のサブクラスなので、既存の
|
|
39
|
+
* `instanceof ConfigPersistenceError` fail-open 判定(main.ts / WI-314)は無変更で動く。
|
|
40
|
+
*/
|
|
41
|
+
export class ConfigParseError extends ConfigPersistenceError {
|
|
42
|
+
constructor(configPath: string, cause?: unknown) {
|
|
43
|
+
super(configPath, cause, `Failed to parse config JSON: ${configPath}`);
|
|
44
|
+
this.name = "ConfigParseError";
|
|
45
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
async function exists(targetPath: string): Promise<boolean> {
|
|
37
50
|
try {
|
|
38
51
|
await fs.access(targetPath);
|
|
@@ -69,14 +82,10 @@ async function findNearestConfig(startDirectory: string): Promise<string | null>
|
|
|
69
82
|
|
|
70
83
|
export class FileSystemConfigRepository implements ConfigRepositoryPort {
|
|
71
84
|
async load(configPath?: string): Promise<{ path: string; document: unknown }> {
|
|
72
|
-
const resolvedPath = configPath
|
|
73
|
-
? path.resolve(configPath)
|
|
74
|
-
: await findNearestConfig(process.cwd());
|
|
85
|
+
const resolvedPath = configPath ? path.resolve(configPath) : await findNearestConfig(process.cwd());
|
|
75
86
|
|
|
76
87
|
if (!resolvedPath) {
|
|
77
|
-
throw new ConfigNotFoundError(
|
|
78
|
-
path.resolve(process.cwd(), DEFAULT_CONFIG_FILE_NAME),
|
|
79
|
-
);
|
|
88
|
+
throw new ConfigNotFoundError(path.resolve(process.cwd(), DEFAULT_CONFIG_FILE_NAME));
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
if (!(await exists(resolvedPath))) {
|
|
@@ -84,7 +93,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
|
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
try {
|
|
87
|
-
const raw = await fs.readFile(resolvedPath,
|
|
96
|
+
const raw = await fs.readFile(resolvedPath, "utf8");
|
|
88
97
|
|
|
89
98
|
return {
|
|
90
99
|
path: resolvedPath,
|
|
@@ -92,7 +101,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
|
|
|
92
101
|
};
|
|
93
102
|
} catch (error) {
|
|
94
103
|
if (error instanceof SyntaxError) {
|
|
95
|
-
throw new
|
|
104
|
+
throw new ConfigParseError(resolvedPath, error);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
throw error;
|
|
@@ -104,7 +113,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
|
|
|
104
113
|
|
|
105
114
|
try {
|
|
106
115
|
const serialized = `${JSON.stringify(document, null, 2)}\n`;
|
|
107
|
-
await fs.writeFile(resolvedPath, serialized,
|
|
116
|
+
await fs.writeFile(resolvedPath, serialized, "utf8");
|
|
108
117
|
} catch (error) {
|
|
109
118
|
throw new ConfigPersistenceError(resolvedPath, error);
|
|
110
119
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// @layer domain
|
|
2
2
|
// config-query-port.ts
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ConfigSummary,
|
|
6
|
+
LanguageInfo,
|
|
7
|
+
PhaseGateSummary,
|
|
8
|
+
PresetInfo,
|
|
9
|
+
} from "../value-objects/harness-status-summary.js";
|
|
5
10
|
|
|
6
11
|
export interface ConfigQueryPort {
|
|
7
12
|
getPresetInfo(): Promise<PresetInfo>;
|
|
8
13
|
getConfigSummary(): Promise<ConfigSummary>;
|
|
9
14
|
getPhaseGateSummary(): Promise<PhaseGateSummary>;
|
|
15
|
+
/**
|
|
16
|
+
* WI-328: 実効言語リストと出所(declared / detected / fallback)を返す。
|
|
17
|
+
* 後方互換のため optional — 未実装アダプタでは status に languages を出さない。
|
|
18
|
+
*/
|
|
19
|
+
getLanguageInfo?(): Promise<LanguageInfo>;
|
|
10
20
|
}
|