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.
Files changed (92) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/docs/ADR/017-warning-severity-aggregation.md +17 -0
  3. package/docs/ADR/038-config-state-operation-permission-policy.md +78 -0
  4. package/docs/guide/installation.md +1 -1
  5. package/docs/guide/layer-model.md +2 -0
  6. package/docs/guide/quick-vs-full-mode.md +28 -3
  7. package/docs/guide/troubleshooting.md +37 -0
  8. package/docs/templates/agent-context/CLAUDE.md.template.md +6 -6
  9. package/docs/templates/ci/aidlc-gate.yml +22 -4
  10. package/package.json +2 -2
  11. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +98 -18
  12. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +25 -3
  13. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-full-mode-session-query-adapter.ts +75 -23
  14. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +45 -27
  15. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +29 -16
  16. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +51 -6
  17. package/scripts/harness/agent-integration/presentation/stop-hook.ts +35 -26
  18. package/scripts/harness/ci-governance/composition-root.ts +2 -2
  19. package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +20 -11
  20. package/scripts/harness/ci-governance/presentation/handlers/check-repetition-handler.ts +10 -2
  21. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +5 -1
  22. package/scripts/harness/config-foundation/domain/harness-config.ts +10 -7
  23. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +4 -1
  24. package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +34 -18
  25. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +1 -1
  26. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +1 -1
  27. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +1 -1
  28. package/scripts/harness/config-foundation/infrastructure/repositories/file-system-config-repository.ts +27 -18
  29. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +1 -8
  30. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +1 -8
  31. package/scripts/harness/harness-api/domain/ports/config-query-port.ts +11 -1
  32. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +135 -87
  33. package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +31 -21
  34. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +2 -22
  35. package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +23 -8
  36. package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +4 -4
  37. package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +79 -34
  38. package/scripts/harness/harness-error/application/dto/create-harness-error-input.ts +3 -1
  39. package/scripts/harness/harness-error/application/dto/harness-error-contract.ts +3 -1
  40. package/scripts/harness/harness-error/application/mappers/harness-error-contract-mapper.ts +9 -17
  41. package/scripts/harness/harness-error/application/usecases/create-harness-error-use-case.ts +7 -7
  42. package/scripts/harness/harness-error/domain/services/harness-error-factory.ts +30 -33
  43. package/scripts/harness/harness-error/domain/value-objects/error-definition.ts +28 -17
  44. package/scripts/harness/harness-error/domain/value-objects/harness-error.ts +30 -9
  45. package/scripts/harness/harness-error/domain/value-objects/remediation-type.ts +30 -0
  46. package/scripts/harness/harness-error/infrastructure/registry/l2-error-definitions.ts +44 -29
  47. package/scripts/harness/harness-error/infrastructure/registry/l3-error-definitions.ts +44 -27
  48. package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +47 -32
  49. package/scripts/harness/installation/application/checks/claude-context-missing-check.ts +13 -7
  50. package/scripts/harness/installation/application/checks/config-status-check.ts +52 -0
  51. package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +6 -0
  52. package/scripts/harness/installation/application/ports/config-status-probe-port.ts +9 -0
  53. package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +30 -8
  54. package/scripts/harness/installation/application/usecases/run-install.ts +234 -53
  55. package/scripts/harness/installation/application/usecases/run-reconcile.ts +311 -70
  56. package/scripts/harness/installation/composition-root.ts +13 -3
  57. package/scripts/harness/installation/domain/check-id.ts +2 -0
  58. package/scripts/harness/installation/domain/config-status.ts +17 -0
  59. package/scripts/harness/installation/domain/deployment-manifest.ts +43 -0
  60. package/scripts/harness/installation/domain/ports/heuristic-check.ts +10 -1
  61. package/scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts +79 -0
  62. package/scripts/harness/installation/presentation/cli/doctor-handler.ts +6 -1
  63. package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +19 -5
  64. package/scripts/harness/integrations/pre-commit.ts +17 -3
  65. package/scripts/harness/main.ts +83 -15
  66. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +60 -32
  67. package/scripts/harness/phase2-extensions/presentation/handlers/check-freshness-handler.ts +17 -9
  68. package/scripts/harness/quick-mode/application/ports/file-existence-port.ts +15 -0
  69. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +60 -22
  70. package/scripts/harness/quick-mode/composition-root.ts +25 -15
  71. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +72 -3
  72. package/scripts/harness/quick-mode/infrastructure/adapters/fs-file-existence-adapter.ts +38 -0
  73. package/scripts/harness/skill-quality/infrastructure/adapters/file-system-requirement-test-matrix-adapter.ts +51 -8
  74. package/scripts/harness/skill-quality/presentation/handlers/check-coverage-handler.ts +13 -6
  75. package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +5 -1
  76. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-identity-gateway.ts +6 -1
  77. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-status-gateway.ts +15 -2
  78. package/scripts/harness/validator-system/application/use-cases/aggregate-validation-results-usecase.ts +11 -14
  79. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +32 -7
  80. package/scripts/harness/validator-system/composition-root.ts +4 -1
  81. package/scripts/harness/validator-system/domain/ports/ac-coverage-policy-port.ts +10 -1
  82. package/scripts/harness/validator-system/domain/services/effective-severity-policy.ts +39 -0
  83. package/scripts/harness/validator-system/domain/value-objects/consistency-report.ts +6 -4
  84. package/scripts/harness/validator-system/domain/value-objects/drift-report.ts +12 -7
  85. package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +11 -3
  86. package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +20 -17
  87. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +89 -3
  88. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +49 -20
  89. package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +35 -14
  90. package/scripts/harness/validator-system/infrastructure/adapters/traceability-metadata-policy-adapter.ts +13 -8
  91. package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +19 -10
  92. package/skills/quick-implementor/SKILL.md +19 -0
@@ -5,11 +5,15 @@
5
5
  * @work-item-id WI-212
6
6
  * @work-item-id WI-301
7
7
  * @work-item-id WI-302
8
+ * @work-item-id WI-319
9
+ * @work-item-id WI-328
8
10
  *
9
11
  * HarnessConfigValidatorConfigAdapter — ValidatorConfigPort実装
10
12
  * HarnessConfigV2からLayerConfig VOを構築する
11
13
  */
12
14
 
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { join } from "node:path";
13
17
  import type { ValidatorConfigPort } from "../../domain/ports/validator-config-port.js";
14
18
  import { LayerConfig } from "../../domain/value-objects/layer-config.js";
15
19
  import { ValidatorId } from "../../domain/value-objects/validator-id.js";
@@ -38,11 +42,92 @@ export interface HarnessConfigV2Like {
38
42
  world?: { enabled?: boolean };
39
43
  }
40
44
 
45
+ /**
46
+ * WI-319 (github#39): project.languages 未宣言時にファイルシステムから言語を検出するためのマーカー定義。
47
+ * typescript は package.json の存在自体を根拠にできない(phasegate 導入時に phasegate 用
48
+ * package.json が置かれるため)ので、この表には含めず hasTypescriptMarker() で個別判定する。
49
+ */
50
+ const LANGUAGE_MARKER_FILES: ReadonlyArray<{ readonly language: string; readonly markers: readonly string[] }> = [
51
+ { language: "python", markers: ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt"] },
52
+ { language: "go", markers: ["go.mod"] },
53
+ { language: "rust", markers: ["Cargo.toml"] },
54
+ { language: "java", markers: ["pom.xml", "build.gradle", "build.gradle.kts"] },
55
+ { language: "ruby", markers: ["Gemfile"] },
56
+ { language: "php", markers: ["composer.json"] },
57
+ ];
58
+
59
+ /** WI-328: 実効言語リストの出所。status 表示等で「なぜこの言語判定になったか」を示す。 */
60
+ export type ProjectLanguageSource = "declared" | "detected" | "fallback";
61
+
62
+ export interface ResolvedProjectLanguages {
63
+ readonly languages: readonly string[];
64
+ readonly source: ProjectLanguageSource;
65
+ }
66
+
67
+ /**
68
+ * WI-328 (github#39 残課題): 実効言語リストとその出所を解決する唯一の実装。
69
+ * getProjectLanguages()(WI-319 の検出ロジック)と phasegate:status の言語表示が
70
+ * 同じテーブル・同じ優先順位を共有するために export する。
71
+ *
72
+ * 優先順位:
73
+ * 1. declared — config の project.languages 宣言(従来どおり最優先)
74
+ * 2. detected — project root のマーカーファイルから検出(WI-319 / github#39)
75
+ * 3. fallback — 検出ゼロなら typescript フォールバック(純 JS リポジトリ等の挙動維持)
76
+ */
77
+ export function resolveProjectLanguages(
78
+ declaredLanguages: readonly string[] | undefined,
79
+ rootDir: string,
80
+ ): ResolvedProjectLanguages {
81
+ if (declaredLanguages && declaredLanguages.length > 0) {
82
+ return { languages: [...declaredLanguages], source: "declared" };
83
+ }
84
+ const detected = detectLanguagesFromFilesystem(rootDir);
85
+ if (detected.length > 0) {
86
+ return { languages: detected, source: "detected" };
87
+ }
88
+ return { languages: ["typescript"], source: "fallback" };
89
+ }
90
+
91
+ function detectLanguagesFromFilesystem(rootDir: string): string[] {
92
+ const detected: string[] = [];
93
+ if (hasTypescriptMarker(rootDir)) detected.push("typescript");
94
+ for (const { language, markers } of LANGUAGE_MARKER_FILES) {
95
+ if (markers.some((marker) => existsSync(join(rootDir, marker)))) {
96
+ detected.push(language);
97
+ }
98
+ }
99
+ return detected;
100
+ }
101
+
102
+ /**
103
+ * typescript の根拠は tsconfig.json の存在、または package.json の
104
+ * dependencies / devDependencies に typescript があることのみ。
105
+ * package.json の存在自体は根拠にしない(phasegate 導入時に phasegate 用
106
+ * package.json が置かれるため、誤検出の原因になる)。
107
+ */
108
+ function hasTypescriptMarker(rootDir: string): boolean {
109
+ if (existsSync(join(rootDir, "tsconfig.json"))) return true;
110
+ const packageJsonPath = join(rootDir, "package.json");
111
+ if (!existsSync(packageJsonPath)) return false;
112
+ try {
113
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
114
+ dependencies?: Record<string, string>;
115
+ devDependencies?: Record<string, string>;
116
+ };
117
+ return parsed.dependencies?.typescript !== undefined || parsed.devDependencies?.typescript !== undefined;
118
+ } catch {
119
+ // parse 失敗は typescript 根拠なしとして扱う(throw しない)
120
+ return false;
121
+ }
122
+ }
123
+
41
124
  export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort {
42
125
  private readonly config: HarnessConfigV2Like;
126
+ private readonly rootDir: string;
43
127
 
44
- constructor(config: HarnessConfigV2Like) {
128
+ constructor(config: HarnessConfigV2Like, rootDir: string = process.cwd()) {
45
129
  this.config = config;
130
+ this.rootDir = rootDir;
46
131
  }
47
132
 
48
133
  async getLayerConfig(layer: "L2" | "L3" | "L4"): Promise<LayerConfig> {
@@ -95,8 +180,9 @@ export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort
95
180
  }
96
181
 
97
182
  async getProjectLanguages(): Promise<readonly string[]> {
98
- const languages = this.config.project?.languages;
99
- return languages && languages.length > 0 ? [...languages] : ["typescript"];
183
+ // WI-319 の宣言優先 → FS 検出 → typescript フォールバックの解決は
184
+ // resolveProjectLanguages() に一本化(WI-328 status 表示と共有)。
185
+ return [...resolveProjectLanguages(this.config.project?.languages, this.rootDir).languages];
100
186
  }
101
187
 
102
188
  private normalizeValidatorId(idOrName: string): string {
@@ -18,18 +18,25 @@
18
18
  * (layers.L3.requirementMatrixPath)から供給される。未指定時は既定値
19
19
  * `.harness/requirement-test-matrix.json` を用いる。マトリクスが不在の場合は
20
20
  * fail-closed で「設定されているが不在」という実行可能なメッセージを返す。
21
+ *
22
+ * FRESH PROJECT SKIP (WI-324): 例外として「matrix 不在 かつ StoryCatalog の
23
+ * story がゼロ」の場合のみ skipped=true を返す。phasegate 導入直後(story 未作成・
24
+ * matrix 未生成)のオンボーディングを fail-closed で阻害しないため。story が
25
+ * 1件でも存在するのに matrix が不在なら「あるべき matrix が消えた事故」として
26
+ * 従来どおり fail-closed。story 数の取得自体に失敗した場合も判定不能として
27
+ * 保守的に fail-closed 側へ倒す。
21
28
  */
22
- import { access } from 'node:fs/promises';
23
- import { isAbsolute, join } from 'node:path';
24
- import type { AcCoveragePolicyPort } from '../../domain/ports/ac-coverage-policy-port.js';
25
- import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
29
+ import { access } from "node:fs/promises";
30
+ import { isAbsolute, join } from "node:path";
31
+ import type { AcCoveragePolicyPort } from "../../domain/ports/ac-coverage-policy-port.js";
32
+ import type { HarnessErrorLike } from "../../domain/value-objects/validation-result.js";
26
33
 
27
- const DEFAULT_MATRIX_PATH = '.harness/requirement-test-matrix.json';
34
+ const DEFAULT_MATRIX_PATH = ".harness/requirement-test-matrix.json";
28
35
 
29
36
  function toL3004Error(message: string, suggestion: string): HarnessErrorLike {
30
37
  return {
31
- code: { value: 'L3-004', toString: () => 'L3-004' },
32
- severity: { value: 'error', toString: () => 'error' },
38
+ code: { value: "L3-004", toString: () => "L3-004" },
39
+ severity: { value: "error", toString: () => "error" },
33
40
  message,
34
41
  suggestion,
35
42
  };
@@ -39,21 +46,29 @@ export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
39
46
  async checkCoverage(context: { matrixFilePath?: string }): Promise<{
40
47
  passed: boolean;
41
48
  errors: readonly HarnessErrorLike[];
49
+ skipped?: boolean;
50
+ skipReason?: string;
42
51
  }> {
43
52
  const rootDir = process.cwd();
44
53
  const relativeOrAbsolute =
45
- context.matrixFilePath && context.matrixFilePath.length > 0
46
- ? context.matrixFilePath
47
- : DEFAULT_MATRIX_PATH;
48
- const matrixFilePath = isAbsolute(relativeOrAbsolute)
49
- ? relativeOrAbsolute
50
- : join(rootDir, relativeOrAbsolute);
54
+ context.matrixFilePath && context.matrixFilePath.length > 0 ? context.matrixFilePath : DEFAULT_MATRIX_PATH;
55
+ const matrixFilePath = isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : join(rootDir, relativeOrAbsolute);
51
56
 
52
57
  // マトリクス不在は threshold-configured-but-absent と同様に fail-closed で
53
58
  // 明確なメッセージを返す(ENOENT の生メッセージで落とさない)。
59
+ // 例外(WI-324): story がゼロのフレッシュプロジェクトのみ SKIP を返す。
54
60
  try {
55
61
  await access(matrixFilePath);
56
62
  } catch {
63
+ if (await this.hasZeroStories(rootDir)) {
64
+ return {
65
+ passed: true,
66
+ errors: [],
67
+ skipped: true,
68
+ skipReason:
69
+ "story 未作成のため L3-004 をスキップ(story 作成後に requirement-test-matrix を生成すると有効化されます)",
70
+ };
71
+ }
57
72
  return {
58
73
  passed: false,
59
74
  errors: [
@@ -69,7 +84,7 @@ export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
69
84
  // 有効 storyId 一覧を traceability-model の StoryCatalog から取得する(REAL registry)
70
85
  const storyIds = await this.loadValidStoryIds(rootDir);
71
86
 
72
- const { createNyquistValidationModule } = await import('../../../nyquist-validation/composition-root.js');
87
+ const { createNyquistValidationModule } = await import("../../../nyquist-validation/composition-root.js");
73
88
  const mod = createNyquistValidationModule({
74
89
  getStoryIds: async () => storyIds,
75
90
  });
@@ -79,9 +94,7 @@ export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
79
94
 
80
95
  return {
81
96
  passed: output.passed,
82
- errors: output.errors.map((err) =>
83
- toL3004Error(err.message, ''),
84
- ),
97
+ errors: output.errors.map((err) => toL3004Error(err.message, "")),
85
98
  };
86
99
  } catch (error) {
87
100
  const message = error instanceof Error ? error.message : String(error);
@@ -91,19 +104,35 @@ export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
91
104
  errors: [
92
105
  toL3004Error(
93
106
  `AC網羅ゲートの検査に失敗しました(fail-closed): ${message}`,
94
- 'requirement-test-matrix.json の存在・形式・スキーマ整合性を確認してください',
107
+ "requirement-test-matrix.json の存在・形式・スキーマ整合性を確認してください",
95
108
  ),
96
109
  ],
97
110
  };
98
111
  }
99
112
  }
100
113
 
114
+ /**
115
+ * WI-324: 「story がゼロ」判定。StoryCatalog(traceability-model)の登録 story 数を
116
+ * シグナルとする。StoryCatalog は requirement-test-matrix の生成元と同じ
117
+ * user_stories.md を読むため、「matrix が本来存在しうるか」と一貫した判定になる。
118
+ * catalog 読み込み自体が失敗した場合(config 不正等)は判定不能として false を返し、
119
+ * 呼び出し側を保守的に fail-closed へ倒す。
120
+ */
121
+ private async hasZeroStories(rootDir: string): Promise<boolean> {
122
+ try {
123
+ const storyIds = await this.loadValidStoryIds(rootDir);
124
+ return storyIds.length === 0;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
101
130
  private async loadValidStoryIds(rootDir: string): Promise<readonly string[]> {
102
- const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
131
+ const { createConfigFoundationModule } = await import("../../../config-foundation/composition-root.js");
103
132
  const configModule = createConfigFoundationModule();
104
133
  const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
105
134
 
106
- const { createTraceabilityModelModule } = await import('../../../traceability-model/composition-root.js');
135
+ const { createTraceabilityModelModule } = await import("../../../traceability-model/composition-root.js");
107
136
  const traceModule = createTraceabilityModelModule(rootDir, {
108
137
  pathRoots: { designDocsRoot: resolvedConfig.config.paths.designDocs },
109
138
  });
@@ -4,8 +4,27 @@
4
4
  *
5
5
  * PhaseDependencyPhaseGatePolicyAdapter — PhaseGatePolicyPort実装
6
6
  */
7
- import type { PhaseGatePolicyPort } from '../../domain/ports/phase-gate-policy-port.js';
8
- import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
7
+ import type { PhaseGatePolicyPort } from "../../domain/ports/phase-gate-policy-port.js";
8
+ import type { HarnessErrorLike } from "../../domain/value-objects/validation-result.js";
9
+
10
+ /**
11
+ * WI-357 (issue #29): `validate --layer L2` の失敗出力には従来
12
+ * 'phase gate prerequisites are not met' という事実の再掲しか載らず、
13
+ * 「何を作れば通るのか」がどの経路からも得られなかった。
14
+ * hook の block メッセージ側にしか復旧手順がないため、CLI から検証した
15
+ * エージェントは不足文書を推測で書くしかなくなる。
16
+ *
17
+ * 実行可能な復旧手順(scaffold コマンド)と、文書のセクション構成の在り処
18
+ * (skills/<skill>/SKILL.md、未配置なら `phasegate skills info`)を suggestion に載せる。
19
+ */
20
+ export function buildPhaseGateRecoverySuggestion(unitName: string): string {
21
+ const unitArg = unitName.trim().length > 0 ? unitName.trim() : "<unit-id>";
22
+ return [
23
+ `不足している設計文書を scaffold で作成してください: npx phasegate scaffold-design --unit ${unitArg} --phase <logical|domain|uiux|unit-test|it-test> --apply`,
24
+ "各文書のセクション構成は導入先 repo の skills/<skill>/SKILL.md に記載されています(logical_design.md → skills/logical-designer/SKILL.md、domain_model.md → skills/domain-designer/SKILL.md、unit plan → skills/unit-designer/SKILL.md)。",
25
+ "skills/ が repo に無い場合は `npx phasegate skills info <skill-name>` で同じ内容を stdout から読めます。",
26
+ ].join("\n");
27
+ }
9
28
 
10
29
  export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPort {
11
30
  async checkPrerequisites(context: { unitName: string; currentPhase: string }): Promise<{
@@ -18,11 +37,13 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
18
37
 
19
38
  try {
20
39
  // WI-085: paths config を phase-dependency-model に流入させる
21
- const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
22
- const { toPhaseConfigSection } = await import('../../../config-foundation/application/mappers/phase-config-section-mapper.js');
40
+ const { createConfigFoundationModule } = await import("../../../config-foundation/composition-root.js");
41
+ const { toPhaseConfigSection } = await import(
42
+ "../../../config-foundation/application/mappers/phase-config-section-mapper.js"
43
+ );
23
44
  const configModule = createConfigFoundationModule();
24
45
  const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
25
- const { createPhaseDependencyModelModule } = await import('../../../phase-dependency-model/composition-root.js');
46
+ const { createPhaseDependencyModelModule } = await import("../../../phase-dependency-model/composition-root.js");
26
47
  const mod = createPhaseDependencyModelModule({
27
48
  rootDir: process.cwd(),
28
49
  phaseConfig: toPhaseConfigSection(resolvedConfig.config),
@@ -42,10 +63,10 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
42
63
  satisfied: false,
43
64
  violations: [
44
65
  {
45
- code: { value: 'L2-001', toString: () => 'L2-001' },
46
- severity: { value: 'error', toString: () => 'error' },
66
+ code: { value: "L2-001", toString: () => "L2-001" },
67
+ severity: { value: "error", toString: () => "error" },
47
68
  message: result.text,
48
- suggestion: 'phase gate prerequisites are not met',
69
+ suggestion: buildPhaseGateRecoverySuggestion(context.unitName),
49
70
  },
50
71
  ],
51
72
  };
@@ -57,10 +78,10 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
57
78
  satisfied: false,
58
79
  violations: [
59
80
  {
60
- code: { value: 'L2-002', toString: () => 'L2-002' },
61
- severity: { value: 'error', toString: () => 'error' },
81
+ code: { value: "L2-002", toString: () => "L2-002" },
82
+ severity: { value: "error", toString: () => "error" },
62
83
  message: `phase gate check returned unexpected exit code ${result.exitCode}; prerequisites cannot be confirmed`,
63
- suggestion: 'phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)',
84
+ suggestion: "phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)",
64
85
  },
65
86
  ],
66
87
  };
@@ -73,10 +94,10 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
73
94
  satisfied: false,
74
95
  violations: [
75
96
  {
76
- code: { value: 'L2-002', toString: () => 'L2-002' },
77
- severity: { value: 'error', toString: () => 'error' },
97
+ code: { value: "L2-002", toString: () => "L2-002" },
98
+ severity: { value: "error", toString: () => "error" },
78
99
  message: `phase gate prerequisite check could not be evaluated: ${message}`,
79
- suggestion: 'phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)',
100
+ suggestion: "phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)",
80
101
  },
81
102
  ],
82
103
  };
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * TraceabilityMetadataPolicyAdapter — MetadataPolicyPort実装
6
6
  */
7
- import type { MetadataPolicyPort } from '../../domain/ports/metadata-policy-port.js';
8
- import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
7
+ import type { MetadataPolicyPort } from "../../domain/ports/metadata-policy-port.js";
8
+ import type { HarnessErrorLike } from "../../domain/value-objects/validation-result.js";
9
9
 
10
10
  export class TraceabilityMetadataPolicyAdapter implements MetadataPolicyPort {
11
11
  async validateMetadata(context: { filePath: string; fileContent: string }): Promise<{
@@ -19,21 +19,26 @@ export class TraceabilityMetadataPolicyAdapter implements MetadataPolicyPort {
19
19
  const hasLayer = /@layer\s+\S+/.test(fileContent);
20
20
  const hasStoryId = /@story-id\s+H\d{2}-\d{2}/.test(fileContent) || /@story\s+H\d{2}-\d{2}/.test(fileContent);
21
21
 
22
+ // WI-335: この suggestion は「<directive> <値> を先頭コメントに追加」という機械適用可能な
23
+ // 文言契約を持つ(remediationType: 'mechanical')。文言をこの形から変える場合は
24
+ // remediation-round-trip テスト(機械適用器が同じ文言を解析する)が fail する。
22
25
  if (!hasUnit) {
23
26
  errors.push({
24
- code: { value: 'L2-002', toString: () => 'L2-002' },
25
- severity: { value: 'error', toString: () => 'error' },
27
+ code: { value: "L2-002", toString: () => "L2-002" },
28
+ severity: { value: "error", toString: () => "error" },
26
29
  message: `メタデータ不足: @unit アノテーションがありません (${filePath})`,
27
- suggestion: '// @unit <unit-name> を先頭コメントに追加してください',
30
+ suggestion: "// @unit <unit-name> を先頭コメントに追加してください",
31
+ remediationType: "mechanical",
28
32
  });
29
33
  }
30
34
 
31
35
  if (!hasLayer) {
32
36
  errors.push({
33
- code: { value: 'L2-002', toString: () => 'L2-002' },
34
- severity: { value: 'error', toString: () => 'error' },
37
+ code: { value: "L2-002", toString: () => "L2-002" },
38
+ severity: { value: "error", toString: () => "error" },
35
39
  message: `メタデータ不足: @layer アノテーションがありません (${filePath})`,
36
- suggestion: '// @layer domain|application|infrastructure|presentation を先頭コメントに追加してください',
40
+ suggestion: "// @layer domain|application|infrastructure|presentation を先頭コメントに追加してください",
41
+ remediationType: "mechanical",
37
42
  });
38
43
  }
39
44
 
@@ -4,34 +4,43 @@
4
4
  *
5
5
  * AgentValidationResultFormatter — AIエージェント向け詳細テキスト
6
6
  */
7
- import type { AggregatedValidationReport } from '../../application/dto/aggregated-validation-report.js';
7
+ import type { AggregatedValidationReport } from "../../application/dto/aggregated-validation-report.js";
8
8
 
9
9
  export class AgentValidationResultFormatter {
10
10
  format(report: AggregatedValidationReport): string {
11
11
  const lines: string[] = [];
12
- lines.push('VALIDATION_REPORT');
13
- lines.push(`OVERALL: ${report.overallPassed ? 'PASS' : 'FAIL'}`);
14
- lines.push(`TOTAL: ${report.totalValidators} PASSED: ${report.passedValidators} FAILED: ${report.failedValidators} SKIPPED: ${report.skippedValidators}`);
15
- lines.push('');
12
+ lines.push("VALIDATION_REPORT");
13
+ lines.push(`OVERALL: ${report.overallPassed ? "PASS" : "FAIL"}`);
14
+ lines.push(
15
+ `TOTAL: ${report.totalValidators} PASSED: ${report.passedValidators} FAILED: ${report.failedValidators} SKIPPED: ${report.skippedValidators}`,
16
+ );
17
+ lines.push("");
16
18
 
17
19
  for (const result of report.results) {
18
20
  lines.push(`VALIDATOR: ${result.validatorId}`);
19
- lines.push(`STATUS: ${result.skipped ? 'SKIPPED' : result.passed ? 'PASSED' : 'FAILED'}`);
21
+ lines.push(`STATUS: ${result.skipped ? "SKIPPED" : result.passed ? "PASSED" : "FAILED"}`);
20
22
  lines.push(`DURATION: ${result.durationMs}ms`);
21
23
  if (result.skipped && result.skipReason) {
22
24
  lines.push(`SKIP_REASON: ${result.skipReason}`);
23
25
  }
24
26
  if (result.errors.length > 0) {
25
- lines.push('ERRORS:');
27
+ lines.push("ERRORS:");
26
28
  for (const error of result.errors) {
27
29
  lines.push(` - CODE: ${error.code}`);
28
30
  lines.push(` MESSAGE: ${error.message}`);
29
- lines.push(` SUGGESTION: ${error.suggestion}`);
31
+ // WI-357: 復旧手順を載せた suggestion は複数行になりうる。
32
+ // 素朴に埋め込むと 2 行目以降がインデントを失い、ERRORS ブロックの
33
+ // 構造が壊れて機械読みできなくなるため継続行を明示的に字下げする。
34
+ const suggestionLines = String(error.suggestion).split("\n");
35
+ lines.push(` SUGGESTION: ${suggestionLines[0]}`);
36
+ for (const continuation of suggestionLines.slice(1)) {
37
+ lines.push(` ${continuation}`);
38
+ }
30
39
  }
31
40
  }
32
- lines.push('');
41
+ lines.push("");
33
42
  }
34
43
 
35
- return lines.join('\n');
44
+ return lines.join("\n");
36
45
  }
37
46
  }
@@ -21,6 +21,25 @@ Quick Mode下での軽微変更実装スキル。story-implementorの緩和版
21
21
  | `test` | テスト追加・修正(新機能のテストではない) |
22
22
  | `config` | 設定変更、依存バージョン更新 |
23
23
 
24
+ > **カテゴリ判定はファイルパスベース(意図ベースではない)**
25
+ > pre-tool-use hook は変更の「意図」ではなく **書き込み対象パス** からカテゴリを機械判定する。
26
+ > 特に `config` は allowlist で、以下だけが `config` に落ちる:
27
+ > `*.config.json` / `*.config.ts` / `phasegate.config.json` / `.github/workflows/*.y(a)ml` /
28
+ > リポジトリ直下の `.gitignore` `.gitattributes` `.editorconfig` `.npmrc` `.nvmrc` `tsconfig.json` `tsconfig.*.json` / `.husky/` 配下。
29
+ > allowlist 外のパスは新規作成(CREATE)なら `feature` に落ちて **必ずブロックされる**(fail-closed)。
30
+ >
31
+ > ブロックされたら、まず分類を確認する:
32
+ > ```bash
33
+ > npx phasegate check-change-category --paths <file1>,<file2>
34
+ > ```
35
+ > 判定が妥当で、かつ許可カテゴリの一時拡張が必要なら CLI 経由で計画・適用する
36
+ > (`phasegate.config.json` の手編集は保護ファイルとしてブロックされる):
37
+ > ```bash
38
+ > npx phasegate config:plan --intent quick-mode-relax --dry-run --json # 差分確認
39
+ > npx phasegate config:plan --intent quick-mode-relax --apply --json # 承認後に適用
40
+ > ```
41
+ > 判定が `feature` / `domain` / `api` なら、それは Quick Mode のスコープ外。`story-implementor` に切り替える。
42
+
24
43
  ### WI-aware trivial path(ISSUE-026 Phase D)
25
44
 
26
45
  作業対象に `WI-XXX` が明示されている場合、最初に `docs/inception/{unit}/WI-XXX/description.md` または `docs/inception/_cross/WI-XXX/description.md` のfrontmatterを確認する。