phasegate 0.283.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 (71) hide show
  1. package/CHANGELOG.md +16 -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/layer-model.md +2 -0
  5. package/docs/guide/quick-vs-full-mode.md +28 -3
  6. package/docs/guide/troubleshooting.md +37 -0
  7. package/docs/templates/agent-context/CLAUDE.md.template.md +6 -6
  8. package/package.json +1 -1
  9. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +98 -18
  10. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +25 -3
  11. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-full-mode-session-query-adapter.ts +75 -23
  12. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +18 -3
  13. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +51 -6
  14. package/scripts/harness/ci-governance/composition-root.ts +2 -2
  15. package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +20 -11
  16. package/scripts/harness/ci-governance/presentation/handlers/check-repetition-handler.ts +10 -2
  17. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +1 -1
  18. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +1 -1
  19. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +1 -1
  20. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -3
  21. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +2 -22
  22. package/scripts/harness/harness-error/application/dto/create-harness-error-input.ts +3 -1
  23. package/scripts/harness/harness-error/application/dto/harness-error-contract.ts +3 -1
  24. package/scripts/harness/harness-error/application/mappers/harness-error-contract-mapper.ts +9 -17
  25. package/scripts/harness/harness-error/application/usecases/create-harness-error-use-case.ts +7 -7
  26. package/scripts/harness/harness-error/domain/services/harness-error-factory.ts +30 -33
  27. package/scripts/harness/harness-error/domain/value-objects/error-definition.ts +28 -17
  28. package/scripts/harness/harness-error/domain/value-objects/harness-error.ts +30 -9
  29. package/scripts/harness/harness-error/domain/value-objects/remediation-type.ts +30 -0
  30. package/scripts/harness/harness-error/infrastructure/registry/l2-error-definitions.ts +44 -29
  31. package/scripts/harness/harness-error/infrastructure/registry/l3-error-definitions.ts +44 -27
  32. package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +47 -32
  33. package/scripts/harness/installation/application/checks/claude-context-missing-check.ts +13 -7
  34. package/scripts/harness/installation/application/checks/config-status-check.ts +52 -0
  35. package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +6 -0
  36. package/scripts/harness/installation/application/ports/config-status-probe-port.ts +9 -0
  37. package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +30 -8
  38. package/scripts/harness/installation/application/usecases/run-install.ts +44 -11
  39. package/scripts/harness/installation/application/usecases/run-reconcile.ts +44 -11
  40. package/scripts/harness/installation/composition-root.ts +13 -3
  41. package/scripts/harness/installation/domain/check-id.ts +2 -0
  42. package/scripts/harness/installation/domain/config-status.ts +17 -0
  43. package/scripts/harness/installation/domain/ports/heuristic-check.ts +10 -1
  44. package/scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts +79 -0
  45. package/scripts/harness/installation/presentation/cli/doctor-handler.ts +6 -1
  46. package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +19 -5
  47. package/scripts/harness/integrations/pre-commit.ts +17 -3
  48. package/scripts/harness/main.ts +57 -9
  49. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +60 -32
  50. package/scripts/harness/phase2-extensions/presentation/handlers/check-freshness-handler.ts +17 -9
  51. package/scripts/harness/quick-mode/application/ports/file-existence-port.ts +15 -0
  52. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +60 -22
  53. package/scripts/harness/quick-mode/composition-root.ts +25 -15
  54. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +72 -3
  55. package/scripts/harness/quick-mode/infrastructure/adapters/fs-file-existence-adapter.ts +38 -0
  56. package/scripts/harness/skill-quality/infrastructure/adapters/file-system-requirement-test-matrix-adapter.ts +51 -8
  57. package/scripts/harness/skill-quality/presentation/handlers/check-coverage-handler.ts +13 -6
  58. package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +5 -1
  59. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-identity-gateway.ts +6 -1
  60. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-status-gateway.ts +15 -2
  61. package/scripts/harness/validator-system/application/use-cases/aggregate-validation-results-usecase.ts +11 -14
  62. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +8 -0
  63. package/scripts/harness/validator-system/domain/services/effective-severity-policy.ts +39 -0
  64. package/scripts/harness/validator-system/domain/value-objects/consistency-report.ts +6 -4
  65. package/scripts/harness/validator-system/domain/value-objects/drift-report.ts +12 -7
  66. package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +11 -3
  67. package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +20 -17
  68. package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +35 -14
  69. package/scripts/harness/validator-system/infrastructure/adapters/traceability-metadata-policy-adapter.ts +13 -8
  70. package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +19 -10
  71. package/skills/quick-implementor/SKILL.md +19 -0
@@ -171,6 +171,9 @@ export class RunL3ValidatorsUseCase {
171
171
  severity: "error",
172
172
  message: `カバレッジ不足: 現在値 ${coverageData.overallCoverage}%、不足 ${threshold - coverageData.overallCoverage}%`,
173
173
  suggestion: `テストカバレッジを ${threshold}% 以上に引き上げてください`,
174
+ // WI-335: 閾値未達の解消はテスト追加(AI/人間の判断)が必要。
175
+ // L3-003 の registry 既定は mechanical だが、この finding は機械適用不能なので明示的に上書きする。
176
+ remediationType: "ai-assisted",
174
177
  },
175
178
  ],
176
179
  0,
@@ -190,8 +193,13 @@ export class RunL3ValidatorsUseCase {
190
193
  code: "L3-003",
191
194
  severity: "error",
192
195
  message: `coverageThreshold=${threshold}% が設定されていますがカバレッジレポートが見つかりません(テストをカバレッジ付きで実行してください)`,
196
+ // WI-335: 選択肢 (b)「layers.L3.coverageThreshold を 0 に設定する」は config 編集のみで
197
+ // 完結する機械適用可能な opt-out(WI-317 / github#37)なので mechanical と宣言する。
198
+ // この文言(layers.L3.coverageThreshold を 0)を変える場合は remediation-round-trip
199
+ // テスト(機械適用器が同じ文言を解析する)が fail する。
193
200
  suggestion:
194
201
  '次のいずれかで解消してください: (a) テストをカバレッジ付きで実行してレポートを生成する(例: vitest --coverage)、(b) カバレッジゲートを opt-out するなら config の layers.L3.coverageThreshold を 0 に設定する、(c) 非 JS/TS プロジェクトなら project.languages を宣言する(例: ["python"]。L3-003 自体が unsupported-language SKIP になる)',
202
+ remediationType: "mechanical",
195
203
  },
196
204
  ],
197
205
  0,
@@ -0,0 +1,39 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-332
4
+
5
+ /**
6
+ * effective-severity-policy — 実効 severity 判定の単一ソース (ADR-017 / ADR-021)。
7
+ *
8
+ * validator 1件が「実質 pass」かを severity-aware に判定するルール。
9
+ * WI-332 以前は同じ判定式が 3 箇所(validate 集約 usecase / harness-api CiCheckResult /
10
+ * pre-commit の手動集約)に複製・乖離しており、#38(complete-check だけ warning-only で
11
+ * exit 1)型の回帰を招いた。以後、実効判定は必ずこの関数を経由すること。
12
+ *
13
+ * 判定ルール(ADR-017 Decision の集計セマンティクスそのもの):
14
+ * - skipped: 実質 pass
15
+ * - passed=true: 実質 pass
16
+ * - passed=false かつ error severity(!= warning)を含む: fail
17
+ * - passed=false かつ errors=[](severity 判定不能): 安全側に倒して fail
18
+ * - passed=false かつ warning のみ: failOnWarning=false(既定)で実質 pass、true で fail
19
+ */
20
+
21
+ /**
22
+ * 判定に必要な最小構造。validator-system の ValidationResultContract と
23
+ * harness-api の ValidatorCheckItem の双方が構造的に満たす。
24
+ */
25
+ export interface EffectiveSeverityCheckItem {
26
+ readonly passed: boolean;
27
+ readonly skipped?: boolean;
28
+ readonly errors?: readonly { readonly severity: string }[];
29
+ }
30
+
31
+ export function isEffectivelyPassed(item: EffectiveSeverityCheckItem, failOnWarning = false): boolean {
32
+ if (item.skipped || item.passed) return true;
33
+ const errors = item.errors ?? [];
34
+ const hasNonWarningError = errors.some((e) => e.severity !== "warning");
35
+ const hasWarnings = errors.some((e) => e.severity === "warning");
36
+ const isEmptyFail = errors.length === 0;
37
+ const hasFail = isEmptyFail || hasNonWarningError || (failOnWarning && hasWarnings);
38
+ return !hasFail;
39
+ }
@@ -6,7 +6,7 @@
6
6
  * ConsistencyReport 値オブジェクト
7
7
  * 設計文書間のレイヤー整合性検証結果VO(L4-002専用)
8
8
  */
9
- import type { HarnessErrorLike } from './validation-result.js';
9
+ import type { HarnessErrorLike } from "./validation-result.js";
10
10
 
11
11
  export interface MismatchPair {
12
12
  readonly expected: string;
@@ -48,10 +48,12 @@ export class ConsistencyReport {
48
48
  // ADR-017 / WI-094: error catalog の defaultSeverity: warning と整合
49
49
  toHarnessErrors(): readonly HarnessErrorLike[] {
50
50
  return this.mismatchPairs.map((pair) => ({
51
- code: { value: 'L4-002', toString: () => 'L4-002' },
52
- severity: { value: 'warning', toString: () => 'warning' },
51
+ code: { value: "L4-002", toString: () => "L4-002" },
52
+ severity: { value: "warning", toString: () => "warning" },
53
53
  message: `レイヤー整合性違反: expected "${pair.expected}" but got "${pair.actual}" at ${pair.location}`,
54
- suggestion: pair.nextAction ?? '設計文書間のレイヤー依存方向を統一してください',
54
+ suggestion: pair.nextAction ?? "設計文書間のレイヤー依存方向を統一してください",
55
+ // WI-335: 文書間整合の回復は「どちらの記述が正か」の判断を伴う(ai-assisted)。
56
+ remediationType: "ai-assisted" as const,
55
57
  }));
56
58
  }
57
59
  }
@@ -5,9 +5,9 @@
5
5
  * DriftReport 値オブジェクト
6
6
  * 設計文書とコード実装の双方向乖離検出結果VO(L4-001専用)
7
7
  */
8
- import type { HarnessErrorLike } from './validation-result.js';
8
+ import type { HarnessErrorLike } from "./validation-result.js";
9
9
 
10
- export type DriftDirection = 'design→code' | 'code→design';
10
+ export type DriftDirection = "design→code" | "code→design";
11
11
 
12
12
  export interface DriftReportProps {
13
13
  readonly direction: DriftDirection;
@@ -29,14 +29,16 @@ export class DriftReport {
29
29
  this.direction = props.direction;
30
30
  this.unitName = props.unitName;
31
31
  this.element = props.element;
32
- this.recommendation = props.recommendation ?? (props.description ?? '');
32
+ this.recommendation = props.recommendation ?? props.description ?? "";
33
33
  this.location = Object.freeze(props.location ?? {});
34
34
  Object.freeze(this);
35
35
  }
36
36
 
37
37
  static create(props: DriftReportProps): DriftReport {
38
- if (props.direction !== 'design→code' && props.direction !== 'code→design') {
39
- throw new Error(`Invalid DriftReport direction: "${props.direction}". Must be "design→code" or "code→design" (INV-10)`);
38
+ if (props.direction !== "design→code" && props.direction !== "code→design") {
39
+ throw new Error(
40
+ `Invalid DriftReport direction: "${props.direction}". Must be "design→code" or "code→design" (INV-10)`,
41
+ );
40
42
  }
41
43
  return new DriftReport(props);
42
44
  }
@@ -44,10 +46,13 @@ export class DriftReport {
44
46
  toHarnessError(): HarnessErrorLike {
45
47
  // ADR-017 / WI-094: error catalog の defaultSeverity: warning と整合
46
48
  return {
47
- code: { value: 'L4-001', toString: () => 'L4-001' },
48
- severity: { value: 'warning', toString: () => 'warning' },
49
+ code: { value: "L4-001", toString: () => "L4-001" },
50
+ severity: { value: "warning", toString: () => "warning" },
49
51
  message: `乖離検出 [${this.direction}] Unit: ${this.unitName}, Element: ${this.element}`,
50
52
  suggestion: this.recommendation,
53
+ // WI-335: design drift の解消は設計意図の理解が必要で機械適用不能。AI が設計文書を
54
+ // 読んで自己修正できる分類(ai-assisted)。
55
+ remediationType: "ai-assisted",
51
56
  };
52
57
  }
53
58
 
@@ -5,7 +5,7 @@
5
5
  * ValidationResult 値オブジェクト
6
6
  * バリデータ実行結果のスナップショット(不変)
7
7
  */
8
- import type { ValidatorId } from './validator-id.js';
8
+ import type { ValidatorId } from "./validator-id.js";
9
9
 
10
10
  /** HarnessError の最小互換型(harness-error Unit の HarnessError との疎結合) */
11
11
  export interface HarnessErrorLike {
@@ -13,6 +13,12 @@ export interface HarnessErrorLike {
13
13
  readonly severity: { readonly value?: string; toString(): string };
14
14
  readonly message: string;
15
15
  readonly suggestion: string;
16
+ /**
17
+ * WI-335: suggestion の修復方式分類。未設定は 'manual' 扱い(機械適用可能と過剰宣言しない)。
18
+ * 'mechanical' を宣言したエラーは remediation-round-trip テストで
19
+ * 「エラー → suggestion を機械適用 → 再実行 → pass」が CI 保証される。
20
+ */
21
+ readonly remediationType?: "mechanical" | "ai-assisted" | "manual";
16
22
  [key: string]: unknown;
17
23
  }
18
24
 
@@ -49,10 +55,12 @@ export class ValidationResult {
49
55
  throw new Error(`ValidationResult durationMs must be >= 0 (got: ${props.durationMs})`);
50
56
  }
51
57
  if (props.passed && props.errors.length > 0) {
52
- throw new Error('ValidationResult invariant violation: passed=true but errors is not empty (INV-5)');
58
+ throw new Error("ValidationResult invariant violation: passed=true but errors is not empty (INV-5)");
53
59
  }
54
60
  if (props.skipped && (!props.passed || props.errors.length > 0)) {
55
- throw new Error('ValidationResult invariant violation: skipped=true requires passed=true and empty errors (INV-8)');
61
+ throw new Error(
62
+ "ValidationResult invariant violation: skipped=true requires passed=true and empty errors (INV-8)",
63
+ );
56
64
  }
57
65
  return new ValidationResult(props);
58
66
  }
@@ -5,11 +5,12 @@
5
5
  *
6
6
  * FileSystemSecurityPatternScannerAdapter — SecurityPatternScannerPort実装
7
7
  */
8
- import type { SecurityPatternScannerPort } from '../../domain/ports/security-pattern-scanner-port.js';
9
- import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
10
- import { readFile } from 'node:fs/promises';
11
8
 
12
- const ALLOWLIST_MARKER = 'phasegate-allow-secret-fixture';
9
+ import { readFile } from "node:fs/promises";
10
+ import type { SecurityPatternScannerPort } from "../../domain/ports/security-pattern-scanner-port.js";
11
+ import type { HarnessErrorLike } from "../../domain/value-objects/validation-result.js";
12
+
13
+ const ALLOWLIST_MARKER = "phasegate-allow-secret-fixture";
13
14
 
14
15
  interface SecurityPattern {
15
16
  readonly ruleId: string;
@@ -18,15 +19,15 @@ interface SecurityPattern {
18
19
  }
19
20
 
20
21
  const SECURITY_PATTERNS: readonly SecurityPattern[] = Object.freeze([
21
- { ruleId: 'secret.openai', pattern: /\b(?:sk|rk|sess)-[a-zA-Z0-9_-]{20,}\b/g, description: 'OpenAI token family' },
22
- { ruleId: 'secret.github', pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, description: 'GitHub token family' },
23
- { ruleId: 'secret.aws-access-key', pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, description: 'AWS access key id' },
24
- { ruleId: 'secret.npm', pattern: /\bnpm_[A-Za-z0-9]{24,}\b/g, description: 'npm token family' },
25
- { ruleId: 'secret.slack', pattern: /\bxox[abprs]-[A-Za-z0-9-]{20,}\b/g, description: 'Slack token family' },
22
+ { ruleId: "secret.openai", pattern: /\b(?:sk|rk|sess)-[a-zA-Z0-9_-]{20,}\b/g, description: "OpenAI token family" },
23
+ { ruleId: "secret.github", pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, description: "GitHub token family" },
24
+ { ruleId: "secret.aws-access-key", pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, description: "AWS access key id" },
25
+ { ruleId: "secret.npm", pattern: /\bnpm_[A-Za-z0-9]{24,}\b/g, description: "npm token family" },
26
+ { ruleId: "secret.slack", pattern: /\bxox[abprs]-[A-Za-z0-9-]{20,}\b/g, description: "Slack token family" },
26
27
  {
27
- ruleId: 'secret.keyword-context',
28
+ ruleId: "secret.keyword-context",
28
29
  pattern: /\b(?:API_KEY|api_key|apikey|password|PASSWORD|passwd|secret|token)\b\s*[:=]\s*["'][^"']{8,}["']/g,
29
- description: 'keyword-context secret',
30
+ description: "keyword-context secret",
30
31
  },
31
32
  ]);
32
33
 
@@ -39,8 +40,8 @@ export class FileSystemSecurityPatternScannerAdapter implements SecurityPatternS
39
40
 
40
41
  for (const filePath of targetPaths) {
41
42
  try {
42
- const content = await readFile(filePath, 'utf-8');
43
- const lines = content.split('\n');
43
+ const content = await readFile(filePath, "utf-8");
44
+ const lines = content.split("\n");
44
45
  lines.forEach((line, idx) => {
45
46
  // WI-120: allowlist は行/領域スコープ。以前はファイル内のどこかに
46
47
  // マーカーが 1 つでもあればファイル全体をスキップしていたため、同一
@@ -54,12 +55,14 @@ export class FileSystemSecurityPatternScannerAdapter implements SecurityPatternS
54
55
  pattern.lastIndex = 0;
55
56
  const matches = [...line.matchAll(pattern)];
56
57
  for (const match of matches) {
57
- const secretValue = match[0] ?? '';
58
+ const secretValue = match[0] ?? "";
58
59
  findings.push({
59
- code: { value: 'L3-001', toString: () => 'L3-001' },
60
- severity: { value: 'error', toString: () => 'error' },
60
+ code: { value: "L3-001", toString: () => "L3-001" },
61
+ severity: { value: "error", toString: () => "error" },
61
62
  message: `セキュリティ問題: ${description} (${ruleId}) at ${filePath}:${idx + 1} value=${redactSecret(secretValue)}`,
62
63
  suggestion: `${ruleId}: 秘密情報は環境変数または秘密管理サービスを使用してください。fixture/docs のダミー値は ${ALLOWLIST_MARKER} を明示してください。`,
64
+ // WI-335: 秘密情報の無効化・ローテーション・保管方式の選定は人間の判断が必須(manual)。
65
+ remediationType: "manual",
63
66
  });
64
67
  }
65
68
  }
@@ -88,6 +91,6 @@ function isAllowlisted(line: string, previousLine: string | undefined): boolean
88
91
 
89
92
  function redactSecret(secretValue: string): string {
90
93
  const value = secretValue.trim();
91
- if (value.length <= 8) return '<redacted>';
94
+ if (value.length <= 8) return "<redacted>";
92
95
  return `${value.slice(0, 3)}...<redacted:${value.length}>`;
93
96
  }
@@ -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を確認する。